2022年12月22日

ユーザを偽装してSYSTEMサービスからネットワークリソースにアクセスする

このアーティクルはDelphi Advent Calendar 2022の22日目の記事です(20日ぶり8回目)。

以前WNetAddConnection2を使って他のPCのネットワーク共有にアクセスする方法について書きましたが、これをサービスから実行するとWNetAddConnection2が時々エラー1312(ERROR_NO_SUCH_LOGON_SESSION)になる、という現象が発生します。調べてみたところ、サービスの場合はSystemではなくNetworkServiceで動作していないとこのような状態になるようです(常にエラーになるわけではなく、成功したりエラーになったりという感じ)。もちろんサービスをNetworkServiceで実行すればネットワークアクセスでエラーになることはないのですが、ローカルコンピュータに対するアクセスがUsersグループ相当に制限されてしまいます(サービスで使用する(Local)System/LocalService/NetworkServiceアカウントについてはWindowsテクニカルドキュメント(旧MSDN)のService User AccountsLocalService AccountNetworkService AccountLocalSystem Accountや@ITのWindowsのサービスで使用される「System」「Local Service」「Network Service」アカウントとは?:Tech TIPSを参照)。

そこでSystemアカウントで動作するサービスから実行することを前提に、ネットワークリソースにアクセスするときだけNetworkServiceアカウントに偽装するようにしてみます。
まずLogonUserでNetworkServiceとしてログインし、返されたトークンハンドルでImpersonateLoggedOnUserを呼び出すことでユーザを偽装します。
const
  LOGON32_LOGON_NEW_CREDENTIALS   = 9;
  {$EXTERNALSYM LOGON32_LOGON_NEW_CREDENTIALS}
var
  LogonUserUser: String;
  LogonUserDomain: String;
  LogonUserLogonType: DWORD;
  LogonUserLogonProvider: DWORD;
  hToken: THandle;
begin
  { Logon as 'NetworkService' user }
  LogonUserUser := 'NetworkService';
  LogonUserDomain := 'NT AUTHORITY';
  LogonUserLogonType := LOGON32_LOGON_NEW_CREDENTIALS;
  LogonUserLogonProvider := LOGON32_PROVIDER_DEFAULT;
  if LogonUser(PChar(LogonUserUser),PChar(LogonUserDomain),nil,
               LogonUserLogonType,LogonUserLogonProvider,hToken) = False then
  begin
    RaiseLastOSError;
  end;

  { Impersonate }
  if ImpersonateLoggedOnUser(hToken) = False then
  begin
    RaiseLastOSError;
  end;
これでこの後WNetAddConnection2で接続を試みるときにSystemアカウントではなくNetworkServiceアカウントであるかのように扱われます。

切断するときはまずWNetCancelConnection2を呼び出した後で、RevertToSelfで偽装を解除し、CloseHandleでトークンハンドルをクローズすることでログオフします。
  { Revert impersonation }
  if RevertToSelf() = False then
  begin
    RaiseLastOSError;
  end;

  { Loggoff }
  CloseHandle(hToken);
  hToken := INVALID_HANDLE_VALUE;
なおプロセス上で複数のスレッドが動作している場合(特にサービス)など、同一の共有リソース('\\<computername>\<sharename>')に対してWNetAddConnection2をネストして呼び出すとERROR_ALREADY_ASSIGNEDでエラーになります。これを防ぐには同一の共有リソースに対してWNetAddConnection2/WNetCancelConnection2が1回ずつ呼び出されるように、接続している共有リソースをプロセス全体で適切に管理する必要もあります。

2022年12月5日

2022年12月2日

InterBase 2020 Update 4

InterBase 2020 Update 4がリリースされています。バージョンは14.4.0.804となっています。

InterBase 2020U4 リリースノート - InterBase (en)
InterBase 2020 Update 4 の新機能 - InterBase (en)
Resolved Defects - InterBase (en)

Embarcadero InterBase 2020 Update 4 のリリース (en)

レコード型のフィールドのオフセットを取得する

このアーティクルはDelphi Advent Calendar 2022の2日目の記事です(3年ぶり7回目)。

Delphiのレコード型(Cの構造体に相当)は、スタックに配置することができるということ以外にも、特定のメモリレイアウトを定義することができることから、固定長ファイルや指定されたメモリ上のデータを解釈するために使われることがあります。このような場合に、定義したレコード型の特定のフィールドのオフセットが(主にデバッグ用に)欲しくなったりするのですが、Delphiには(SizeOfはあるのに)C/C++のoffsetofマクロ(stddef.h)のようなものが存在しません(RSP-39559)。そこで軽く検索してみたところStack Overflowにそのものずばりな投稿がありました。

delphi - Get Position of a struct var AKA Offset of record field - Stack Overflow
Delphi: Offset of record field - Stack Overflow

ではちょっと試してみましょう。まずレコード型とそのポインタ型を定義します。
type
  TFoo1 = record
    Bar: Boolean;
    Baz: Double;
    Qux: array [0..8] of Byte;
    Quux: Integer;
  end;
  PFoo1 = ^TFoo1;
これで
procedure TForm1.Button1Click(Sender: TObject);
begin
  Memo1.Lines.Add(Format('TFoo1: SizeOf=%d',[SizeOf(TFoo1)]));
  Memo1.Lines.Add(Format('TFoo1.Bar: offset=%d',[NativeUInt(@(PFoo1(nil)^.Bar))]));
  Memo1.Lines.Add(Format('TFoo1.Baz: offset=%d',[NativeUInt(@(PFoo1(nil)^.Baz))]));
  Memo1.Lines.Add(Format('TFoo1.Qux: offset=%d',[NativeUInt(@(PFoo1(nil)^.Qux))]));
  Memo1.Lines.Add(Format('TFoo1.Quux: offset=%d',[NativeUInt(@(PFoo1(nil)^.Quux))]));
end;
のように、nilをレコード型へのポインタにキャスト→フィールドを参照→そのアドレスを取得→整数に変換とすることで、そのフィールドのオフセット値を取得できます(アドレスと同じサイズの符号なし整数はNativeUInt型)。では実行してみましょう。

TFoo1: SizeOf=32
TFoo1.Bar: offset=0
TFoo1.Baz: offset=8
TFoo1.Qux: offset=16
TFoo1.Quux: offset=28
Delphiのレコード型のデフォルトのアライメントマスクに従って配置されていることがわかります(TFoo1.QuuxのオフセットがDelphiのデフォルトのフィールドのアライメント(構造体アライメント)の8バイトに従った32ではなく、型(Integer)のサイズである4バイトで28になっていることに注意)。
しかし最初に書いたように、特定の(外部で規定された)メモリレイアウトに簡単にアクセスできるようにするためにレコード型を使用する、という目的を考えると、レコード型にはpackedを指定して、明示的にパディングするような場合が多いと思われます。ではレコード型をpacked recordにしてみましょう。
type
  TFoo2 = packed record
    Bar: Boolean;
    Baz: Double;
    Qux: array [0..8] of Byte;
    Quux: Integer;
  end;
  PFoo2 = ^TFoo2;

procedure TForm1.Button2Click(Sender: TObject);
begin
  Memo1.Lines.Add(Format('TFoo2: SizeOf=%d',[SizeOf(TFoo2)]));
  Memo1.Lines.Add(Format('TFoo2.Bar: offset=%d',[NativeUInt(@(PFoo2(nil)^.Bar))]));
  Memo1.Lines.Add(Format('TFoo2.Baz: offset=%d',[NativeUInt(@(PFoo2(nil)^.Baz))]));
  Memo1.Lines.Add(Format('TFoo2.Qux: offset=%d',[NativeUInt(@(PFoo2(nil)^.Qux))]));
  Memo1.Lines.Add(Format('TFoo2.Quux: offset=%d',[NativeUInt(@(PFoo2(nil)^.Quux))]));
end;
実行してみます。

TFoo2: SizeOf=22
TFoo2.Bar: offset=0
TFoo2.Baz: offset=1
TFoo2.Qux: offset=9
TFoo2.Quux: offset=18
意図通り、すべてのフィールドがパディングされることなく並んでいることがわかります。

逆アセンブルを見ると、コンパイラは各メンバのオフセットを知っててnil(=0)に加算しているので、最初からこれをもらう方法があれば…とは思います。また上記のStack Overflowの2番目の投稿にはRTTIを使った方法も紹介されていますが、RTTIのテーブルでループを回しながら文字列の比較をする必要があるため、それに比べればnilを使った方法のほうが優れていると考えられます。

ところで、packedを指定せずデフォルトのアライメントマスクに従って配置された場合、順序型はそのサイズでアライメントされる、との記述があります。つまり
type
  TFoo3 = record
    Bar: array [0..8] of Byte;
    Baz: Boolean;
    Qux: array [0..8] of Byte;
  end;
  PFoo3 = ^TFoo3;
のように1バイトアライメントされている配列のフィールドの次に1バイトの順序型のフィールドがあると、デフォルトのアライメントの8バイトに従ったパディングが行われることなく、連続した配置になる、ということになります(逆も同じ)。
procedure TForm1.Button3Click(Sender: TObject);
begin
  Memo1.Lines.Add(Format('TFoo3: SizeOf=%d',[SizeOf(TFoo3)]));
  Memo1.Lines.Add(Format('TFoo3.Bar: offset=%d',[NativeUInt(@(PFoo3(nil)^.Bar))]));
  Memo1.Lines.Add(Format('TFoo3.Baz: offset=%d',[NativeUInt(@(PFoo3(nil)^.Baz))]));
  Memo1.Lines.Add(Format('TFoo3.Qux: offset=%d',[NativeUInt(@(PFoo3(nil)^.Qux))]));
end;
実行してみると、

TFoo3: SizeOf=19
TFoo3.Bar: offset=0
TFoo3.Baz: offset=9
TFoo3.Qux: offset=10
と、確かにその通りになっています。またDelphi 2007までは型仕様が共通のフィールド(A, B: Extended;のように複数のフィールドが","で並べられて同じ型が指定されているフィールド)は暗黙にpackedとなる、という仕様も存在していました(いま知りました)。

このようなことから、レコード型を使用するときは、packedを指定せず完全にコンパイラにお任せにして特定のメモリレイアウトを必要としないようにするか、packedを指定してパディングも明示的に配置して特定のメモリレイアウトになるようにコーディングするか、どちらかにするべきだと考えられます(この結論そのものは当たり前すぎるものですが)。

なおpackedを指定してメモリレイアウトを完全に自分で制御する場合など、定義したレコード型が正しいサイズになっているかどうかは
{$IF SizeOf(TFoo2) <> 22}
{$MESSAGE ERROR 'SizeOf(TFoo2) is not 22 bytes.'}
{$IFEND}
のようにコンパイル時にチェックすることができます(Delphi 11.2 Alexandriaだとこれを書いた瞬間にLSPで判定が行われるため、{$MESSAGE}の行がエラー表示になるか淡色表示になるかでわかりますが)。

2022年10月17日

2022年10月5日

RAD Studio/Delphi/C++Builder 11.2 Alexandria Patch 1

RAD Studio/Delphi/C++Builder 11.2 AlexandriaのPatch 1がリリースされています。iOSシミュレータおよびカメラコンポーネント、バルーンヘルプコンポーネントなどの不具合の修正が含まれています。またWindows x64のライブラリパスが正しくない問題についても対応策が示されています。GetItまたはポータルサイトからダウンロード、インストールすることができます。

RAD Studio 11.2 Patch 1

RAD Studio 11.1 Alexandria Patch 1 のリリース (en)

2022年9月5日

[書籍]インサイドWindows 第7版 下

紀伊國屋書店新宿本店インサイドWindows 第6版 下の改訂版(ではもはやないかも)でWindows Internals, Part 2, 7th Edition (Amazon US)の翻訳である

インサイドWindows 第7版 下 (Amazon)/Andrea AllieviMark E. RussinovichAlex Ionescu、David A. Solomon著、山内和朗訳/日経BP/ISBN 9784296080205/8,800円

を購入。上巻(Part 1)が刊行されて4年超、おめでとうございます。

2022年8月12日

2022年7月15日

RAD Studio/C++Builder 11 Alexandria C++Builder Code Insight Update (11.1.5)

RAD Studio/Delphi/C++Builder 11 AlexandriaのC++Builder Code Insight Update (11.1.5)がリリースされています。IDEのC++Builder関係の機能の品質改善が行われています。Delphiのみを使用しているユーザにはインストールは推奨されていません。インストールには有効なUpdate Subscriptionが必要です。Registered Products Portalからダウンロードできます。

RAD Studio, C++Builder 11.1.5 ISO
RAD Studio, C++Builder 11.1.5 Web Install

What's New in 11.1.5 - RAD Studio

RAD Studio/C++Builder 11.1.5 Alexandria リリースのお知らせ (en)

2022年7月4日

[書籍]現代暗号技術入門

紀伊國屋書店新宿本店Real-World Cryptography (Amazn US)の翻訳である

現代暗号技術入門 (Amazon)/David Wong著/高橋聡訳/日経BP/ISBN 9784296080199/3,740円

を購入。

2022年6月21日

[書籍][ebook]Delphi Thread Safety Patterns

Luluで注文した

Delphi Thread Safety Patterns/Dalija Prasnikar、Neven Prasnikar Jr.著/FastSpring/64.50USD(printed+ebook)

が配送されてきました(今回の配送はFedEx/日本郵政で、アメリカ合衆国のニューヨーク市ジャマイカからの発送でした)。2022/06/02に注文して19日目の到着、ebookとのセットで書籍の分が50.00USDのところ25.00USD+7.94USD(shipping)=32.94USD、書籍とebookの合計で65.39USD=8,691JPY(1USD=132.934JPY)でした。現時点(2022/06/21)ではまだAmazonなどでの扱いはなく、書籍で入手したい場合はebookとのセットのみになります。

2022/10/18追記: 各国のAmazonでの扱いが始まったとのことです。日本のAmazonでも購入可能(8,084JPY)になっています。なおAmazon USでは49.95USDです。円安め…。

2022年6月15日

現在実行している環境(プロセッサアーキテクチャ)を調べる

Windowsの実行環境としては、現時点(2022年06月)でx86(32bit)版、x64(64bit)版、ARM版があります。一方でDelphiがサポートするターゲットプラットフォームにはWindows 32ビット(x86)、Windows 64ビット(x64)があります。組み合わせとして、x86のプログラムはx86、x64、ARMのいずれでも、またx64のプログラムはx64、ARM(Windows 11のみ)で動作します。逆に動作しない組み合わせはx64のプログラムとx86、ARM(Windows 10)ということになります(Delphiではいまのところ作れませんが、ARMのプログラムはx86/x64では動作せず、ARM版でのみ動作します)。これはWindowsのWOW64(エミュレータ)やDynamic Binary Translator(JIT)によるもので、後方互換性を極めて重視するMicrosoftらしいよくできた仕組みです。これによりWindows上で動作するプログラムは実行環境のことをあまり気にしなくてもよいのですが、まれに(ターゲットプラットフォームではなく)実行環境によって動作を変えたい、ということがあります。
実行環境を調べるにはいくつかの方法がありますが、IsWow64Process2() を使わずにWowA64を検出する · GitHubによると、Windows 10 Version 1511以降であればWin32APIのIsWow64Process2を、それ以前(Windows XP/Vista/7/8/8.1/10 Version 1507(RTM))であればWin32APIのGetNativeSystemInfoを使うのがよいようです(Windows 2000ではGetSystemInfo)。それでは実装してみましょう。
type
{$SCOPEDENUMS ON}
  TProcessorArchitecture = (x86,   // Intel x86
                            x64,   // AMD64/Intel 64
                            ARM);  // ARM

  TIsWow64Process2Func = function (hProcess: THandle; var ProcessMachine: USHORT; var NativeMachine: USHORT): BOOL; stdcall;
  TGetSystemInfoFunc = procedure (var lpSystemInfo: TSystemInfo); stdcall;

const
  IMAGE_FILE_MACHINE_ARM64 = $AA64;

function GetProcessorArchitecture: TProcessorArchitecture;
var
  IsWow64Process2Func: TIsWow64Process2Func;
  ProcessMachine: USHORT;
  NativeMachine: USHORT;
  GetSystemInfoFunc: TGetSystemInfoFunc;
  SI: TSystemInfo;
begin

  { Use IsWow64Process2 on Windows 10 Version 1511 or later }
  @IsWow64Process2Func := GetProcAddress(GetModuleHandle(kernel32),'IsWow64Process2');
  if Assigned(IsWow64Process2Func) = True then
  begin
    if IsWow64Process2Func(GetCurrentProcess,ProcessMachine,NativeMachine) = True then
    begin
      case NativeMachine of
        IMAGE_FILE_MACHINE_I386:
        begin
          Result := TProcessorArchitecture.x86;
          Exit;
        end;

        IMAGE_FILE_MACHINE_AMD64:
        begin
          Result := TProcessorArchitecture.x64;
          Exit;
        end;

        IMAGE_FILE_MACHINE_ARM64:
        begin
          Result := TProcessorArchitecture.ARM;
          Exit;
        end;
      end;
    end;
  end;

  { Use GetNativeSystemInfo (Windows XP or later) or GetSystemInfo (Windows 2000) }
  FillChar(SI,SizeOf(SI),0);
  @GetSystemInfoFunc := GetProcAddress(GetModuleHandle(kernel32),'GetNativeSystemInfo');
  if Assigned(GetSystemInfoFunc) = True then
  begin
    GetSystemInfoFunc(SI);
  end
  else
  begin
    GetSystemInfo(SI);
  end;

  if SI.wProcessorArchitecture = PROCESSOR_ARCHITECTURE_AMD64 then
  begin
    Result := TProcessorArchitecture.x64;
  end
  else
  begin
    Result := TProcessorArchitecture.x86;
  end;

end;
IsWow64Process2、GetNativeSystemInfo、GetSystemInfoの順にフォールバックして情報を取得するようになっています。

Microsoft Monthly Update 2022/06

今日はMicrosoftのセキュリティアップデートの日です。
June 2022 Security Updates - リリース ノート - セキュリティ更新プログラム ガイド - Microsoft
2022 年 6 月のセキュリティ更新プログラム (月例) – Microsoft Security Response Center

2022年6月9日

2022年5月27日

RAD Studio/Delphi/C++Builder 11.1 Alexandria Windows 11 Win32 Debugging Patch

RAD Studio/Delphi/C++Builder 11.1 AlexandriaのWindows 11 Win32 Debugging Patchがリリースされています。Windows 11上でWin32アプリケーションをデバッグしているときにスレッド待機チェーンの問題で1分程度のフリーズが発生することがある問題に対応します。リモートデバッグを使用している場合はPAServerを更新する必要があります。現時点(2022/05/27)でGetIt経由でインストールすることができます。

RAD Studio 11.1向け 「Windows 11 Win32 Debugging Patch」のリリース (en)

また長らくリリースが遅れていたRAD Studio 11/11.1 Alexandria用のIDE pluginの(Parnassus) BookmarksとNavigatorもGetItからインストールできるようになっています(有効なサブスクリプションが必要)。

RAD Studio 11向けプラグイン「 BookmarksならびにNavigator」の リリースのお知らせ (en)

2022年4月28日

RAD Studio/Delphi/C++Builder 11.1 Alexandria Patch 1

RAD Studio/Delphi/C++Builder 11.1 AlexandriaのPatch 1がリリースされています。macOSのPAServerではPython 2.7が必要になります。GetItまたはポータルサイトからダウンロード、インストールすることができます。

RAD Studio 11.1 Patch 1

RAD Studio 11.1 Alexandria Patch 1 のリリース (en)

2022年2月15日

Firebirdロードマップ(2021/12)

Firebirdのロードマップが2021/12に更新されていました。

Firebird: Roadmap

適当な要約
  • Firebird 2.0 - 2012年に開発終了。最新版は2012年04月の2.0.7
  • Firebird 2.1 - 2014年に開発終了。最新版は2014年12月の2.1.7
  • Firebird 2.5 - 2019年に開発終了。最新版は2019年06月の2.5.9
  • Firebird 3.0 - 安定版。最新版は2022年02月の3.0.9で、半年ごとの更新。
  • Firebird 4.0 - 安定版。最新版は2021年12月の4.0.1で、四半期ごとの更新、次回(4.0.2)は2022年内を予定。
  • Firebird 5.0 - 2021年05月に開発開始。

Firebird 3.0.9

Firebird 3.0.9がリリースされています。

Firebird: Firebird 3.0.9
Firebird 3.0.9 Release Notes (PDF)

2022年1月12日

RAD Studio/Delphi/C++Builder 11.0 Alexandria PAServer January Patch

RAD Studio/Delphi/C++Builder 11.0 AlexandriaのPAServer January Patchがリリースされています。macOS 12 Monterey上でiOSアプリケーションのコード署名が無効になる問題RSP-36648を修正します。PAServer January PatchはmacOS用のPAServerのみを置き換えます(Patch 1/November Patchの内容は含まれません)。PAServerのバージョンは13.0.12.2となります。GetItまたはポータルサイトからダウンロード、インストールすることができます。

RAD Studio 11 PAServer January Patch

RAD Studio 11 Alexandria January PAServer Patchがリリースされました (en)

Microsoft Monthly Update 2022/01

今日はMicrosoftのセキュリティアップデートの日です。
2022 年 1 月のセキュリティ更新プログラム - リリース ノート - セキュリティ更新プログラム ガイド - Microsoft
2022 年 1 月のセキュリティ更新プログラム (月例) – Microsoft Security Response Center