[Win32] [C++] CreateProcessAsUser – #4 セキュリティ記述子

年内に書ききろうと思っていたのに、結局年を跨いでしまった。不吉だ。
皆さま明けましておめでとうございます。

たぶん CreateProcessAsUser シリーズはこれで最後です。

ソースファイル中に、_GUI と _TRACING という定数を定義しています。

  • _GUI ・・・ DACL への ACE 追加を行なうかどうか
  • _TRACING ・・・ デバッグ用の情報を出力するかどうか

_TRACING で出力される情報を使って、セキュリティ記述子に関する補足です。

-runas オプションを付けてメモ帳を別ユーザーで実行します。

>logue -runas kimaber@contoso password c:\windows\syswow64\notepad

SID: S-1-5-21-2857284654-3416964824-2551679015-513
SID: S-1-1-0
SID: S-1-5-32-545
SID: S-1-5-4
SID: S-1-2-1
SID: S-1-5-11
SID: S-1-5-15
SID: S-1-5-5-0-4408862 (Logon)

PID      : 0x948
HWINSTA  : 0xd8
HDESK    : 0xd4
Logon SID: 0079ACE8
—–

青字で示した部分は、CreateProcessUser が返すプロセス トークンに含まれる SID の一覧です。
ログオン SID が S-1-5-5-0-4408862  であることが分かります。

エンターキーを押し、先に進みます。

Original SD: 0079A8B8
New SD     : 0079ACC8
–>

AddAceToWindowStation の中で、ウィンドウ ステーション オブジェクトのセキュリティ記述子を変更する直前のタイミングで止まります。Original SD は GetUserObjectSecurity API で取得されるポインタで、New SD は SetSecurityDescriptorDacl で 新しい DACL を設定した後のセキュリティ記述子を示すポインタです。

ユーザー モード デバッガーで、!sd を使ってみます。

0:002> !sd 79a8b8
->Revision: 0x1
->Sbz1    : 0x0
->Control : 0x8004
            SE_DACL_PRESENT
            SE_SELF_RELATIVE
->Owner   :  is NULL
->Group   :  is NULL
->Dacl    :  is NULL
->Sacl    :  is NULL

0:002> !sd 79acc8
->Revision: 0x1
->Sbz1    : 0x0
->Control : 0x4
            SE_DACL_PRESENT
->Owner   :  is NULL
->Group   :  is NULL
->Dacl    :
->Dacl    : ->AclRevision: 0x2
->Dacl    : ->Sbz1       : 0x0
->Dacl    : ->AclSize    : 0x11c
->Dacl    : ->AceCount   : 0xb
->Dacl    : ->Sbz2       : 0x0
->Dacl    : ->Ace[0]: ->AceType: ACCESS_ALLOWED_ACE_TYPE
->Dacl    : ->Ace[0]: ->AceFlags: 0x4
->Dacl    : ->Ace[0]:             NO_PROPAGATE_INHERIT_ACE
->Dacl    : ->Ace[0]: ->AceSize: 0x24
->Dacl    : ->Ace[0]: ->Mask : 0x00000024
->Dacl    : ->Ace[0]: ->SID: S-1-5-21-2857284654-3416964824-2551679015-500

(略)

->Sacl    :  is NULL

おかしいですね。Original SD の ACL が NULL です。
セキュリティ記述子は _SECURITY_DESCRIPTOR という構造体なので、!sd ではなく dt コマンドで見てみます。

0:002> dt _security_descriptor 79a8b8
ntdll!_SECURITY_DESCRIPTOR
   +0x000 Revision         : 0x1 ”
   +0x001 Sbz1             : 0 ”
   +0x002 Control          : 0x8004
   +0x004 Owner            : (null)
   +0x008 Group            : (null)
   +0x00c Sacl             : (null)
   +0x010 Dacl             : 0x00000014 _ACL
0:002> dt _security_descriptor 79acc8
ntdll!_SECURITY_DESCRIPTOR
   +0x000 Revision         : 0x1 ”
   +0x001 Sbz1             : 0 ”
   +0x002 Control          : 4
   +0x004 Owner            : (null)
   +0x008 Group            : (null)
   +0x00c Sacl             : (null)
   +0x010 Dacl             : 0x0079a9f0 _ACL

Original SD の方は、DACL が 0x14 という値です。ポインタではないみたいです。 ここで重要になってくるのが Control の値で、見てみると、0x8000 のビットが違います。これは !sd の結果に出ていますが、SE_SELF_RELATIVE というフラグです。

セキュリティ記述子には、absolute と self-relative という 2 種類のフォーマットがあり、実はユーザーモードの !sd コマンドは self-relative フォーマットを正しく解釈できないようです。

Absolute and Self-Relative Security Descriptors
http://technet.microsoft.com/library/aa374807

上のページに記載がある通り、absolute フォーマットは各種情報をポインターとして保持しますが、self-relative はオフセットとして保持しています。self-relative は、セキュリティ記述子が 1 まとまりのメモリ ブロック (a contiguous block of memory) としてバッファー上に確保されています。

!sd コマンドが使えないので、!acl コマンドを使って DACL を見る必要があります。

0:002> !acl 79a8b8+14
ACL is:
ACL is: ->AclRevision: 0x2
ACL is: ->Sbz1       : 0x0
ACL is: ->AclSize    : 0x11c
ACL is: ->AceCount   : 0x9
ACL is: ->Sbz2       : 0x0
ACL is: ->Ace[0]: ->AceType: ACCESS_ALLOWED_ACE_TYPE
ACL is: ->Ace[0]: ->AceFlags: 0x4
ACL is: ->Ace[0]:             NO_PROPAGATE_INHERIT_ACE
ACL is: ->Ace[0]: ->AceSize: 0x24
ACL is: ->Ace[0]: ->Mask : 0x00000024
ACL is: ->Ace[0]: ->SID: S-1-5-21-2857284654-3416964824-2551679015-500

(略)

ACL is: ->Ace[7]: ->AceType: ACCESS_ALLOWED_ACE_TYPE
ACL is: ->Ace[7]: ->AceFlags: 0xb
ACL is: ->Ace[7]:             OBJECT_INHERIT_ACE
ACL is: ->Ace[7]:             CONTAINER_INHERIT_ACE
ACL is: ->Ace[7]:             INHERIT_ONLY_ACE
ACL is: ->Ace[7]: ->AceSize: 0x14
ACL is: ->Ace[7]: ->Mask : 0xf0000000
ACL is: ->Ace[7]: ->SID: S-1-5-18

ACL is: ->Ace[8]: ->AceType: ACCESS_ALLOWED_ACE_TYPE
ACL is: ->Ace[8]: ->AceFlags: 0x4
ACL is: ->Ace[8]:             NO_PROPAGATE_INHERIT_ACE
ACL is: ->Ace[8]: ->AceSize: 0x14
ACL is: ->Ace[8]: ->Mask : 0x000f037f
ACL is: ->Ace[8]: ->SID: S-1-5-18

0:002> !acl 0x0079a9f0
ACL is:
ACL is: ->AclRevision: 0x2
ACL is: ->Sbz1       : 0x0
ACL is: ->AclSize    : 0x11c
ACL is: ->AceCount   : 0xb
ACL is: ->Sbz2       : 0x0
ACL is: ->Ace[0]: ->AceType: ACCESS_ALLOWED_ACE_TYPE
ACL is: ->Ace[0]: ->AceFlags: 0x4
ACL is: ->Ace[0]:             NO_PROPAGATE_INHERIT_ACE
ACL is: ->Ace[0]: ->AceSize: 0x24
ACL is: ->Ace[0]: ->Mask : 0x00000024
ACL is: ->Ace[0]: ->SID: S-1-5-21-2857284654-3416964824-2551679015-500

(略)

ACL is: ->Ace[7]: ->AceType: ACCESS_ALLOWED_ACE_TYPE
ACL is: ->Ace[7]: ->AceFlags: 0xb
ACL is: ->Ace[7]:             OBJECT_INHERIT_ACE
ACL is: ->Ace[7]:             CONTAINER_INHERIT_ACE
ACL is: ->Ace[7]:             INHERIT_ONLY_ACE
ACL is: ->Ace[7]: ->AceSize: 0x14
ACL is: ->Ace[7]: ->Mask : 0xf0000000
ACL is: ->Ace[7]: ->SID: S-1-5-18

ACL is: ->Ace[8]: ->AceType: ACCESS_ALLOWED_ACE_TYPE
ACL is: ->Ace[8]: ->AceFlags: 0x4
ACL is: ->Ace[8]:             NO_PROPAGATE_INHERIT_ACE
ACL is: ->Ace[8]: ->AceSize: 0x14
ACL is: ->Ace[8]: ->Mask : 0x000f037f
ACL is: ->Ace[8]: ->SID: S-1-5-18

ACL is: ->Ace[9]: ->AceType: ACCESS_ALLOWED_ACE_TYPE
ACL is: ->Ace[9]: ->AceFlags: 0xb
ACL is: ->Ace[9]:             OBJECT_INHERIT_ACE
ACL is: ->Ace[9]:             CONTAINER_INHERIT_ACE
ACL is: ->Ace[9]:             INHERIT_ONLY_ACE
ACL is: ->Ace[9]: ->AceSize: 0x1c
ACL is: ->Ace[9]: ->Mask : 0xf0000000
ACL is: ->Ace[9]: ->SID: S-1-5-5-0-4408862

ACL is: ->Ace[10]: ->AceType: ACCESS_ALLOWED_ACE_TYPE
ACL is: ->Ace[10]: ->AceFlags: 0x4
ACL is: ->Ace[10]:             NO_PROPAGATE_INHERIT_ACE
ACL is: ->Ace[10]: ->AceSize: 0x1c
ACL is: ->Ace[10]: ->Mask : 0x000f037f
ACL is: ->Ace[10]: ->SID: S-1-5-5-0-4408862

CreateProcessAsUser が作ったログオン SID である S-1-5-5-0-4408862 に対する ACE が、正しく 2 つ追加されていることが確認できました。

エンターキーを押して、プログラムを進めます。今度はデスクトップ オブジェクトの DACL です。

Original SD: 00796140
New SD     : 0079ACC8
–>

デスクトップ オブジェクトもウィンドウ ステーションと同様なので省略し、さらにエンターを押すと、メモ帳が起動します。プログラムは WaitForSingleObject で、プロセスが終了するまで待機します。

ここでカーネル デバッガーを使って、実際のカーネル オブジェクト上の DACL が変わっているかどうかを確認します。
最初の出力から、プロセス ID が 0x948、ウィンドウ ステーションのハンドルが d8 、デスクトップは d4 と分かっているので・・・

kd> !handle d8 7 0x948

Searching for Process with Cid == 948
PROCESS fffffa8001caa060
    SessionId: 1  Cid: 0948    Peb: 7efdf000  ParentCid: 0910
    DirBase: 19c90000  ObjectTable: fffff8a001efa9b0  HandleCount:  58.
    Image: Logue.exe

Handle table at fffff8a00170c000 with 58 entries in use

00d8: Object: fffffa8001a6e7c0  GrantedAccess: 00060000 Entry: fffff8a00170c360
Object: fffffa8001a6e7c0  Type: (fffffa8000ca7b40) WindowStation
    ObjectHeader: fffffa8001a6e790 (new version)
        HandleCount: 26  PointerCount: 43
        Directory Object: fffff8a0021e8720  Name: WinSta0

kd> !handle d4 7 0x948

Searching for Process with Cid == 948
PROCESS fffffa8001caa060
    SessionId: 1  Cid: 0948    Peb: 7efdf000  ParentCid: 0910
    DirBase: 19c90000  ObjectTable: fffff8a001efa9b0  HandleCount:  58.
    Image: Logue.exe

Handle table at fffff8a00170c000 with 58 entries in use

00d4: Object: fffffa8001cfe830  GrantedAccess: 00060081 Entry: fffff8a00170c350
Object: fffffa8001cfe830  Type: (fffffa8000ca79f0) Desktop
    ObjectHeader: fffffa8001cfe800 (new version)
        HandleCount: 13  PointerCount: 495
        Directory Object: 00000000  Name: Default

前の記事から、SecurityDescriptor はオブジェクト ヘッダーから 0x28 バイト目にあり、かつ _EX_FAST_REF 構造なので・・・

kd> dt _security_descriptor poi(fffffa8001a6e790+28)&0xffffffff`fffffff0
nt!_SECURITY_DESCRIPTOR
   +0x000 Revision         : 0x1 ”
   +0x001 Sbz1             : 0 ”
   +0x002 Control          : 0x8014
   +0x008 Owner            : 0x00000014`0000015c Void
   +0x010 Group            : 0x001c0002`00000030 Void
   +0x018 Sacl             : 0x00140011`00000001 _ACL
   +0x020 Dacl             : 0x00000101`00000001 _ACL
kd> dt _security_descriptor poi(fffffa8001cfe800+28)&0xffffffff`fffffff0
nt!_SECURITY_DESCRIPTOR
   +0x000 Revision         : 0x1 ”
   +0x001 Sbz1             : 0 ”
   +0x002 Control          : 0x8014
   +0x008 Owner            : 0x00000014`000000c0 Void
   +0x010 Group            : 0x001c0002`00000030 Void
   +0x018 Sacl             : 0x00140011`00000001 _ACL
   +0x020 Dacl             : 0x00000101`00000001 _ACL

おや、New SD は absolute フォーマットだったのに自動的に self-relative フォーマットに変換されています。まあ、そういうものなのでしょう。それに SACL も勝手に追加されています。おそらく継承によるものです。

カーネル デバッガーの !sd コマンドでは、self-relative フォーマットのセキュリティ記述子もダンプすることができます。
ログオン SID である S-1-5-5-0-4408862 に対する ACE が無事追加されていることが確認できました。

kd> !sd poi(fffffa8001a6e790+28)&0xffffffff`fffffff0
->Revision: 0x1
->Sbz1    : 0x0
->Control : 0x8014
            SE_DACL_PRESENT
            SE_SACL_PRESENT
            SE_SELF_RELATIVE
->Owner   : S-1-5-32-544
->Group   : S-1-5-18
->Dacl    :
->Dacl    : ->AclRevision: 0x2
->Dacl    : ->Sbz1       : 0x0
->Dacl    : ->AclSize    : 0x11c
->Dacl    : ->AceCount   : 0xb
->Dacl    : ->Sbz2       : 0x0
->Dacl    : ->Ace[0]: ->AceType: ACCESS_ALLOWED_ACE_TYPE
->Dacl    : ->Ace[0]: ->AceFlags: 0x4
->Dacl    : ->Ace[0]:             NO_PROPAGATE_INHERIT_ACE
->Dacl    : ->Ace[0]: ->AceSize: 0x24
->Dacl    : ->Ace[0]: ->Mask : 0x00000024
->Dacl    : ->Ace[0]: ->SID: S-1-5-21-2857284654-3416964824-2551679015-500

(略)

->Dacl    : ->Ace[9]: ->AceType: ACCESS_ALLOWED_ACE_TYPE
->Dacl    : ->Ace[9]: ->AceFlags: 0xb
->Dacl    : ->Ace[9]:             OBJECT_INHERIT_ACE
->Dacl    : ->Ace[9]:             CONTAINER_INHERIT_ACE
->Dacl    : ->Ace[9]:             INHERIT_ONLY_ACE
->Dacl    : ->Ace[9]: ->AceSize: 0x1c
->Dacl    : ->Ace[9]: ->Mask : 0xf0000000
->Dacl    : ->Ace[9]: ->SID: S-1-5-5-0-4408862

->Dacl    : ->Ace[10]: ->AceType: ACCESS_ALLOWED_ACE_TYPE
->Dacl    : ->Ace[10]: ->AceFlags: 0x4
->Dacl    : ->Ace[10]:             NO_PROPAGATE_INHERIT_ACE
->Dacl    : ->Ace[10]: ->AceSize: 0x1c
->Dacl    : ->Ace[10]: ->Mask : 0x000f037f
->Dacl    : ->Ace[10]: ->SID: S-1-5-5-0-4408862

->Sacl    :
->Sacl    : ->AclRevision: 0x2
->Sacl    : ->Sbz1       : 0x0
->Sacl    : ->AclSize    : 0x1c
->Sacl    : ->AceCount   : 0x1
->Sacl    : ->Sbz2       : 0x0
->Sacl    : ->Ace[0]: ->AceType: SYSTEM_MANDATORY_LABEL_ACE_TYPE
->Sacl    : ->Ace[0]: ->AceFlags: 0x0
->Sacl    : ->Ace[0]: ->AceSize: 0x14
->Sacl    : ->Ace[0]: ->Mask : 0x00000001
->Sacl    : ->Ace[0]: ->SID: S-1-16-4096

kd> !sd poi(fffffa8001cfe800+28)&0xffffffff`fffffff0
->Revision: 0x1
->Sbz1    : 0x0
->Control : 0x8014
            SE_DACL_PRESENT
            SE_SACL_PRESENT
            SE_SELF_RELATIVE
->Owner   : S-1-5-32-544
->Group   : S-1-5-18
->Dacl    :
->Dacl    : ->AclRevision: 0x2
->Dacl    : ->Sbz1       : 0x0
->Dacl    : ->AclSize    : 0x80
->Dacl    : ->AceCount   : 0x5
->Dacl    : ->Sbz2       : 0x0
->Dacl    : ->Ace[0]: ->AceType: ACCESS_ALLOWED_ACE_TYPE
->Dacl    : ->Ace[0]: ->AceFlags: 0x0
->Dacl    : ->Ace[0]: ->AceSize: 0x1c
->Dacl    : ->Ace[0]: ->Mask : 0x000f01ff
->Dacl    : ->Ace[0]: ->SID: S-1-5-5-0-3706178

(略)

->Dacl    : ->Ace[4]: ->AceType: ACCESS_ALLOWED_ACE_TYPE
->Dacl    : ->Ace[4]: ->AceFlags: 0x0
->Dacl    : ->Ace[4]: ->AceSize: 0x1c
->Dacl    : ->Ace[4]: ->Mask : 0x000f01ff
->Dacl    : ->Ace[4]: ->SID: S-1-5-5-0-4408862

->Sacl    :
->Sacl    : ->AclRevision: 0x2
->Sacl    : ->Sbz1       : 0x0
->Sacl    : ->AclSize    : 0x1c
->Sacl    : ->AceCount   : 0x1
->Sacl    : ->Sbz2       : 0x0
->Sacl    : ->Ace[0]: ->AceType: SYSTEM_MANDATORY_LABEL_ACE_TYPE
->Sacl    : ->Ace[0]: ->AceFlags: 0x0
->Sacl    : ->Ace[0]: ->AceSize: 0x14
->Sacl    : ->Ace[0]: ->Mask : 0x00000001
->Sacl    : ->Ace[0]: ->SID: S-1-16-4096

メモ帳を閉じ、エンターキーを 2 回押すとプログラムが終了します。

Original SD: 0079C2A8
New SD     : 0079AD08
–> エンターを押す

Original SD: 007961B8
New SD     : 0079AD08
–> エンターを押す

このタイミングで、DACL が元に戻っているかどうかを確認します。ウィンドウ ステーションとデスクトップ オブジェクトのアドレスは変わらないので、さっきと同じコマンドで確認します。

kd> !sd poi(fffffa8001a6e790+28)&0xffffffff`fffffff0
->Revision: 0x1
->Sbz1    : 0x0
->Control : 0x8014
            SE_DACL_PRESENT
            SE_SACL_PRESENT
            SE_SELF_RELATIVE
->Owner   : S-1-5-32-544
->Group   : S-1-5-18
->Dacl    :
->Dacl    : ->AclRevision: 0x2
->Dacl    : ->Sbz1       : 0x0
->Dacl    : ->AclSize    : 0x11c
->Dacl    : ->AceCount   : 0x9
->Dacl    : ->Sbz2       : 0x0
->Dacl    : ->Ace[0]: ->AceType: ACCESS_ALLOWED_ACE_TYPE
->Dacl    : ->Ace[0]: ->AceFlags: 0x4
->Dacl    : ->Ace[0]:             NO_PROPAGATE_INHERIT_ACE
->Dacl    : ->Ace[0]: ->AceSize: 0x24
->Dacl    : ->Ace[0]: ->Mask : 0x00000024
->Dacl    : ->Ace[0]: ->SID: S-1-5-21-2857284654-3416964824-2551679015-500

(略)

->Dacl    : ->Ace[7]: ->AceType: ACCESS_ALLOWED_ACE_TYPE
->Dacl    : ->Ace[7]: ->AceFlags: 0xb
->Dacl    : ->Ace[7]:             OBJECT_INHERIT_ACE
->Dacl    : ->Ace[7]:             CONTAINER_INHERIT_ACE
->Dacl    : ->Ace[7]:             INHERIT_ONLY_ACE
->Dacl    : ->Ace[7]: ->AceSize: 0x14
->Dacl    : ->Ace[7]: ->Mask : 0xf0000000
->Dacl    : ->Ace[7]: ->SID: S-1-5-18

->Dacl    : ->Ace[8]: ->AceType: ACCESS_ALLOWED_ACE_TYPE
->Dacl    : ->Ace[8]: ->AceFlags: 0x4
->Dacl    : ->Ace[8]:             NO_PROPAGATE_INHERIT_ACE
->Dacl    : ->Ace[8]: ->AceSize: 0x14
->Dacl    : ->Ace[8]: ->Mask : 0x000f037f
->Dacl    : ->Ace[8]: ->SID: S-1-5-18

->Sacl    :
->Sacl    : ->AclRevision: 0x2
->Sacl    : ->Sbz1       : 0x0
->Sacl    : ->AclSize    : 0x1c
->Sacl    : ->AceCount   : 0x1
->Sacl    : ->Sbz2       : 0x0
->Sacl    : ->Ace[0]: ->AceType: SYSTEM_MANDATORY_LABEL_ACE_TYPE
->Sacl    : ->Ace[0]: ->AceFlags: 0x0
->Sacl    : ->Ace[0]: ->AceSize: 0x14
->Sacl    : ->Ace[0]: ->Mask : 0x00000001
->Sacl    : ->Ace[0]: ->SID: S-1-16-4096

kd> !sd poi(fffffa8001cfe800+28)&0xffffffff`fffffff0
->Revision: 0x1
->Sbz1    : 0x0
->Control : 0x8014
            SE_DACL_PRESENT
            SE_SACL_PRESENT
            SE_SELF_RELATIVE
->Owner   : S-1-5-32-544
->Group   : S-1-5-18
->Dacl    :
->Dacl    : ->AclRevision: 0x2
->Dacl    : ->Sbz1       : 0x0
->Dacl    : ->AclSize    : 0x80
->Dacl    : ->AceCount   : 0x4
->Dacl    : ->Sbz2       : 0x0
->Dacl    : ->Ace[0]: ->AceType: ACCESS_ALLOWED_ACE_TYPE
->Dacl    : ->Ace[0]: ->AceFlags: 0x0
->Dacl    : ->Ace[0]: ->AceSize: 0x1c
->Dacl    : ->Ace[0]: ->Mask : 0x000f01ff
->Dacl    : ->Ace[0]: ->SID: S-1-5-5-0-3706178

(略)

->Dacl    : ->Ace[3]: ->AceType: ACCESS_ALLOWED_ACE_TYPE
->Dacl    : ->Ace[3]: ->AceFlags: 0x0
->Dacl    : ->Ace[3]: ->AceSize: 0x14
->Dacl    : ->Ace[3]: ->Mask : 0x000f01ff
->Dacl    : ->Ace[3]: ->SID: S-1-5-18

->Sacl    :
->Sacl    : ->AclRevision: 0x2
->Sacl    : ->Sbz1       : 0x0
->Sacl    : ->AclSize    : 0x1c
->Sacl    : ->AceCount   : 0x1
->Sacl    : ->Sbz2       : 0x0
->Sacl    : ->Ace[0]: ->AceType: SYSTEM_MANDATORY_LABEL_ACE_TYPE
->Sacl    : ->Ace[0]: ->AceFlags: 0x0
->Sacl    : ->Ace[0]: ->AceSize: 0x14
->Sacl    : ->Ace[0]: ->Mask : 0x00000001
->Sacl    : ->Ace[0]: ->SID: S-1-16-4096

S-1-5-5-0-4408862 に対する ACE だけが消えていることが確認できました。

最後に、セキュリティ記述子のサイズについての補足です。
セキュリティ記述子に関連する構造は、大体こんな風になっています。

  • SecurityDescriptor = _SECURITY_DESCRIPTOR strucutre + DACLs + m*SACLs
  • ACL = _ACL structure + ACEs
  • ACE = _ACE_HEADER structure + SID

将来の拡張を含めた汎用性の維持という観点から、SID のサイズは可変です。このため、SID を連続したメモリ上に並べた ACL や Security Descriptor は可変にならざるを得ません。

ここで、2 月の記事で引用したこの KB。

INFO: Computing the Size of a New ACL
http://support.microsoft.com/kb/102103/en

これは、既存の ACL に ACCESS_ALLOWED_ACE (アクセス許可 ACE) を 1 つ加えたときのサイズを計算する式です。

dwNewACLSize = AclInfo.AclBytesInUse
               + sizeof(ACCESS_ALLOWED_ACE)
               + GetLengthSid(UserSID)
               – sizeof(DWORD);

ACCESS_ALLOWED_ACE のサイズと、GetLengthSid で計算した SID のサイズを足すのは直感的に分かりますが、DWORD を引くのはなんなんだ、と。KB の本文を見ると、こう書いてあります。

Subtracting out the size of a DWORD is the final adjustment needed to obtain the exact size. This adjust is to compensate for a place holder member in the ACCESS_ALLOWED_ACE structure which is used in variable length ACEs.

ACCESS_ALLOWED_ACE のプレースホルダーらしいです。そんなわけで WinNT.h で定義されている構造を見ます。

typedef struct _ACCESS_ALLOWED_ACE {
    ACE_HEADER Header;
    ACCESS_MASK Mask;
    DWORD SidStart;
} ACCESS_ALLOWED_ACE;

プレース ホルダーは SidStart ですね。SID は、_ACCESS_ALLOWED_ACE 構造体の直後に始まるのではなく、SidStart のアドレスから始まるので、構造体のサイズと SID のサイズを足した後、重複する SidStart 分の DWORD を引く必要があるのです。

ACCESS_ALLOWED_ACE 以外にも ACE の種類はたくさんあり、一覧が以下のページにあります。
ちなみに、Object specifig ACE は、ACE の中に GUID を 2 つ含んでいるため、サイズが大きいです。

ACE
http://msdn.microsoft.com/en-us/library/aa374912(v=vs.85).aspx

もう 1 つ重要なのは _ACL 構造体です。

typedef struct _ACL {
    BYTE  AclRevision;
    BYTE  Sbz1;
    WORD   AclSize;
    WORD   AceCount;
    WORD   Sbz2;
} ACL;
typedef ACL *PACL;

まあ普通の構造体ですが、AclSize が WORD 型であるところがポイントです。つまり、ACL のサイズが 64KB を超えることは構造上不可能なのです。これは比較的有名な 64K 問題で、KB も出ています。

Maximum number of ACEs in an ACL
http://support.microsoft.com/kb/166348/en

さて、セキュリティ記述子のサイズが分かったところでプログラムに戻ります。サンプルを再掲。

Starting an Interactive Client Process in C++
http://msdn.microsoft.com/en-us/library/aa379608(v=vs.85).aspx

MSDN のサンプルでは、新たなセキュリティ記述子である psdNew という変数に対して、既存のセキュリティ記述子 psd のサイズである dwSdSizeNeeded の値をそのまま使ってヒープ メモリを確保しています。

psd = (PSECURITY_DESCRIPTOR)HeapAlloc(
      GetProcessHeap(),
      HEAP_ZERO_MEMORY,
      dwSdSizeNeeded);

if (psd == NULL)
   __leave;

psdNew = (PSECURITY_DESCRIPTOR)HeapAlloc(
      GetProcessHeap(),
      HEAP_ZERO_MEMORY,
      dwSdSizeNeeded);

if (psdNew == NULL)
   __leave;

これはおかしな話です。なぜなら、ACE を追加すると ACL のサイズは増え、それに伴ってセキュリティ記述子のサイズは大きくなるはずだからです。なぜバッファー オーバー ラン (BOR) を引き起こさないのでしょうか。その秘密が SetSecurityDescriptorDacl の動きにあります。それをデバッガーで確認します。たまには Release ビルド版をデバッグしてみます。

advapi32!SetSecurityDescriptorDacl は、あちこち飛んだ挙句、ntdll!RtlSetDaclSecurityDescriptor に行きつきます。結局 ntdll.dll に実装されているのです。プログラムを実行して、ntdll!RtlSetDaclSecurityDescriptor が AddAccessAllowedAceBasedSID から呼ばれたときに止めます。

0:000> bl
0 e 779a2cc2     0001 (0001)  0:**** ntdll!RtlSetDaclSecurityDescriptor
0:000> k
ChildEBP RetAddr
0044f93c 76b6c6b3 ntdll!RtlSetDaclSecurityDescriptor
WARNING: Stack unwind information not available. Following frames may be wrong.
0044f954 012711ee KERNELBASE!SetSecurityDescriptorDacl+0x17
0044f9bc 012718a9 Logue!AddAccessAllowedAceBasedSID+0x1ee
0044fa5c 01271c9d Logue!RunAs+0x1e9
0044fa7c 74cd263d Logue!wmain+0x8d
*** ERROR: Symbol file could not be found.  Defaulted to export symbols for C:\Windows\syswow64\kernel32.dll –
0044fac8 76bb33ca MSVCR100!initterm+0x16
0044fad4 77999ed2 kernel32!BaseThreadInitThunk+0x12
0044fb14 77999ea5 ntdll!__RtlUserThreadStart+0x70
0044fb2c 00000000 ntdll!_RtlUserThreadStart+0x1b

0:000> bp 012711ee
0:000> g
Breakpoint 1 hit
eax=00000001 ebx=00000002 ecx=00000004 edx=007bdef0 esi=0044fa44 edi=007bdef0
eip=012711ee esp=0044f96c ebp=0044f9bc iopl=0         nv up ei pl nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000202
Logue!AddAccessAllowedAceBasedSID+0x1ee:
012711ee 85c0            test    eax,eax
0:000> ub
Logue!AddAccessAllowedAceBasedSID+0x1db:
012711db 85c0            test    eax,eax
012711dd 7459            je      Logue!AddAccessAllowedAceBasedSID+0x238 (01271238)
012711df 8b45e8          mov     eax,dword ptr [ebp-18h]
012711e2 6a00            push    0
012711e4 57              push    edi
012711e5 6a01            push    1
012711e7 50              push    eax
012711e8 ff1564402701    call    dword ptr [Logue!_imp__SetSecurityDescriptorDacl (01274064)]

0:000> dt _security_descriptor poi(@ebp-18h)
ntdll!_SECURITY_DESCRIPTOR
   +0x000 Revision         : 0x1 ”
   +0x001 Sbz1             : 0 ”
   +0x002 Control          : 4
   +0x004 Owner            : (null)
   +0x008 Group            : (null)
   +0x00c Sacl             : (null)
   +0x010 Dacl             : 0x007bdef0 _ACL
0:000> r @edi
edi=007bdef0

SetSecurityDescriptorDacl が終わった直後の AddAccessAllowedAceBasedSID で止めます。それが 012711ee です。

SetSecurityDescriptorDacl に渡している引数を確認すると、第一引数の PSECURITY_DESCRIPTOR が eax レジスタで、DACL である第三引数の PACL は edi レジスタです。関数実行後に eax レジスタは変わってしまうので、その元を辿ると、ebp-18 から mov しているので、このローカル変数領域がセキュリティ記述子です。

ebp-18 のセキュリティ記述子内の DACL と、edi レジスタの DACL のポインタは同じ値 (0x007bdef0) になっています。つまり、SetSecurityDescriptorDacl は渡した DACL を別のバッファーにコピーすることなく、そのまま代入しているのです。当然、このセキュリティ記述子は absolute フォーマットになります。

MSDN のサンプルで、新規作成したセキュリティ記述子をオリジナルのセキュリティ記述子のバッファー サイズにしても問題ない理由が分かりました。この記事の最初で確かめたように、オリジナルのセキュリティ記述子は Self-relative でした。すなわち、セキュリティ記述子のバッファー サイズである dwSdSizeNeeded には ACL のサイズも含まれています。しかし、新規作成されるセキュリティ記述子は absolute フォーマットになるため、ACL のサイズは不要です。BOR になるどころか、ヒープの無駄遣いです。(といっても KB オーダーですが)

そこで、作り直したプログラムの AddAccessAllowedAceBasedSID 関数では、新しいセキュリティ記述子のバッファー サイズは SECURITY_DESCRIPTOR_MIN_LENGTH 定数を使っています。定義は以下のようになっており、各種フィールドはポインターの分のサイズが確保されます。absolute フォーマットならこれで十分です。

#define SECURITY_DESCRIPTOR_MIN_LENGTH   (sizeof(SECURITY_DESCRIPTOR))

typedef struct _SECURITY_DESCRIPTOR {
   BYTE  Revision;
   BYTE  Sbz1;
   SECURITY_DESCRIPTOR_CONTROL Control;
   PSID Owner;
   PSID Group;
   PACL Sacl;
   PACL Dacl;

   } SECURITY_DESCRIPTOR, *PISECURITY_DESCRIPTOR;

[Win32] [C++] CreateProcessAsUser – #2 トークン編

次に、CreateProcessAsUser で STATUS_DLL_INIT_FAILED (=0xc0000142) エラーが発生する現象についてです。
こんなダイアログが表示されます。

「アプリケーションを正しく起動できませんでした (0xc0000142)。」
image

STATUS_DLL_INIT_FAILED エラーという名前からして、アクセス許可エラーではなく、何らかの DLL の初期化に失敗したようです。win32k とか、GUI スレッドに関連していそうです。

このダイアログを表示させているプロセスは、CreateProcessAsUser を実行したプロセスや、実行しようとしたプロセス (上の例だと notepad.exe) ではなく、csrss.exe です。CSRSS とは、Client Server Run-Time Subsystem の略で、win32 サブシステムのユーザー モード部分です。

とりあえず、デバッガーでスタックを見てみます。環境は Windows Server 2008 R2 SP1 です。tasklist で csrss を見ると Services と Console がありますが、ポップアップを表示させているのは Console の方です。

ユーザー モードで見ると、以下のスレッドがメッセージボックスを表示させていることが分かります。しかし、CsrApiRequestThread という関数からいきなりハードエラーが発生しているので、ここからは何も分かりません。カーネル モードから見ても、芳しい結果は得られず保留。CsrApiRequestThread の内容をごりごり見るしかないのかもしれない。ちょっとこれは大変そうなのでパス。

   4  Id: 560.450 Suspend: 1 Teb: 000007ff`fffdd000 Unfrozen
Child-SP          RetAddr           Call Site
00000000`00a4f198 00000000`77584bc4 USER32!ZwUserWaitMessage+0xa
00000000`00a4f1a0 00000000`77584edd USER32!DialogBox2+0x274
00000000`00a4f230 00000000`775d2920 USER32!InternalDialogBox+0x135
00000000`00a4f290 00000000`775d1c15 USER32!SoftModalMessageBox+0x9b4
00000000`00a4f3c0 00000000`775d146b USER32!MessageBoxWorker+0x31d
00000000`00a4f580 000007fe`fd702de9 USER32!MessageBoxTimeoutW+0xb3
00000000`00a4f650 000007fe`fd7031e8 winsrv!HardErrorHandler+0x33d
00000000`00a4f7f0 000007fe`fd703562 winsrv!ProcessHardErrorRequest+0xe8
00000000`00a4f860 000007fe`fd754a04 winsrv!UserHardErrorEx+0x356
00000000`00a4f8f0 000007fe`fd7550f4 CSRSRV!QueueHardError+0x138
00000000`00a4f930 00000000`777e4a00 CSRSRV!CsrApiRequestThread+0x510
00000000`00a4fc40 00000000`00000000 ntdll!RtlUserThreadStart+0x25

さて、STATUS_DLL_INIT_FAILED エラーの詳細は不明ですが、解決方法は分かっているわけです。2 月に記事を書いたときには ACL についてよく知らなかったので適当に MSDN のサンプルをコピペしましたが、もう少し真面目に見てみます。

サンプルはこれでした。

Starting an Interactive Client Process in C++
http://msdn.microsoft.com/en-us/library/aa379608(v=VS.85).aspx

CreateProcessAsUser を実行する前の主な処理は、こんな感じです。

  1. LogonUser API でログオン処理を行ない、トークンを生成
  2. GetLogonSID で、1. のトークンからログオン SID を取得
  3. AddAceToWindowStation で、ウィンドウ ステーション オブジェクトの DACL に
    2. の SID に対するアクセス許可 ACE を追加
  4. AddAceToDesktop で、デスクトップ オブジェクトの DACL に
    2. の SID に対するアクセス許可 ACE を追加
  5. CreateProcessAsUser 実行

まずは、GetLogonSID の処理から見てみます。ここで取得される SID はログオン SID といって、ユーザーやセキュリティ グループに割り当てられた SID とは別のものです。ログオン セッション毎にユニークな値になっています。つまり、CreateProcessAsUser が返すトークンに紐付いたログオン SID は毎回異なっています。

logon SID
http://msdn.microsoft.com/en-us/library/ms721592(v=VS.85).aspx#_security_logon_sid_gly

A security identifier (SID) that identifies a logon session. You can use the logon SID in a DACL to control access during a logon session. A logon SID is valid until the user logs off. A logon SID is unique while the computer is running; no other logon session will have the same logon SID. However, the set of possible logon SIDs is reset when the computer starts up. To retrieve the logon SID from an access token, call the GetTokenInformation function for TokenGroups.

ログオン SID は !token コマンドで見ることができます。そして、トークン オブジェクトのアドレスは !process コマンドで出力することができます。しかし、それだと何も面白くないので、もう少し捻ります。

プロセスに紐付く情報は、基本的に _EPROCESS 構造体から辿ることができます。トークンも例外ではありません。

kd> dt _EPROCESS Token
nt!_EPROCESS
   +0x208 Token : _EX_FAST_REF
kd> dt _EX_FAST_REF
nt!_EX_FAST_REF
   +0x000 Object           : Ptr64 Void
   +0x000 RefCnt           : Pos 0, 4 Bits
   +0x000 Value            : Uint8B

_EPROCESS::Token は単なるポインターではなく、_EX_FAST_REF という構造になっています。これは、以下のサイトの情報にあるように下位ビット (32bit なら 3、64bit なら 4 ビット) を RefCnt として保持させている構造体です。よって正しいポインターを得るためには、下位ビットを 0 にクリアする必要があります。

http://www.eggheadcafe.com/microsoft/Windows-Debugging/30558733/kernel-debug-unable-to-get-min-sid-header.aspx

適当にプロセスを 2 つ選んで、トークンを見てみます。_EPROCESS の中で Token のオフセットは 0x208 でした。

kd> !process 0 0 cmd.exe
PROCESS fffffa8001ccd060
    SessionId: 1  Cid: 0910    Peb: 7fffffde000  ParentCid: 0b80
    DirBase: 0363f000  ObjectTable: fffff8a001c59a30  HandleCount:  92.
    Image: cmd.exe

PROCESS fffffa8001c81860
    SessionId: 1  Cid: 0a1c    Peb: 7fffffdf000  ParentCid: 0b80
    DirBase: 15c1b000  ObjectTable: fffff8a001cebb60  HandleCount:  23.
    Image: cmd.exe

kd> dq fffffa8001ccd060+208 l1
fffffa80`01ccd268  fffff8a0`01d1c95b

kd> dq fffffa8001c81860+208 l1
fffffa80`01c81a68  fffff8a0`022a095f

kd> !token fffff8a0`01d1c950 -n
_TOKEN fffff8a001d1c950
TS Session ID: 0x1
User: S-1-5-21-2857284654-3416964824-2551679015-500 (no name mapped)
Groups:
00 S-1-5-21-2857284654-3416964824-2551679015-513 (no name mapped)
    Attributes – Mandatory Default Enabled
01 S-1-1-0 (Well Known Group: localhost\Everyone)
    Attributes – Mandatory Default Enabled
02 S-1-5-32-545 (Alias: BUILTIN\Users)
    Attributes – Mandatory Default Enabled
03 S-1-5-32-544 (Alias: BUILTIN\Administrators)
    Attributes – Mandatory Default Enabled Owner
04 S-1-5-4 (Well Known Group: NT AUTHORITY\INTERACTIVE)
    Attributes – Mandatory Default Enabled
05 S-1-2-1 (Well Known Group: localhost\CONSOLE LOGON)
    Attributes – Mandatory Default Enabled
06 S-1-5-11 (Well Known Group: NT AUTHORITY\Authenticated Users)
    Attributes – Mandatory Default Enabled
07 S-1-5-15 (Well Known Group: NT AUTHORITY\This Organization)
    Attributes – Mandatory Default Enabled
08 S-1-5-5-0-3706178 (no name mapped)
    Attributes – Mandatory Default Enabled LogonId

09 S-1-2-0 (Well Known Group: localhost\LOCAL)
    Attributes – Mandatory Default Enabled
10 S-1-5-21-2857284654-3416964824-2551679015-512 (no name mapped)
    Attributes – Mandatory Default Enabled
11 S-1-5-21-2857284654-3416964824-2551679015-520 (no name mapped)
    Attributes – Mandatory Default Enabled
12 S-1-5-21-2857284654-3416964824-2551679015-519 (no name mapped)
    Attributes – Mandatory Default Enabled
13 S-1-5-21-2857284654-3416964824-2551679015-518 (no name mapped)
    Attributes – Mandatory Default Enabled
14 S-1-5-21-2857284654-3416964824-2551679015-3683 (no name mapped)
    Attributes – Mandatory Default Enabled GroupResource
15 S-1-5-21-2857284654-3416964824-2551679015-572 (no name mapped)
    Attributes – Mandatory Default Enabled GroupResource
16 S-1-16-12288 Unrecognized SID
    Attributes – GroupIntegrity GroupIntegrityEnabled
Primary Group: S-1-5-21-2857284654-3416964824-2551679015-513 (no name mapped)
Privs:
(略)
Authentication ID:         (0,388d63)
Impersonation Level:       Anonymous
TokenType:                 Primary
Source: User32             TokenFlags: 0x2000 ( Token in use )
Token ID: 38f126           ParentToken ID: 0
Modified ID:               (0, 38d8de)
RestrictedSidCount: 0      RestrictedSids: 0000000000000000
OriginatingLogonSession: 3e7

kd> !token fffff8a0`022a0950 -n
_TOKEN fffff8a0022a0950
TS Session ID: 0x1
User: S-1-5-21-2857284654-3416964824-2551679015-500 (no name mapped)
Groups:
00 S-1-5-21-2857284654-3416964824-2551679015-513 (no name mapped)
    Attributes – Mandatory Default Enabled
01 S-1-1-0 (Well Known Group: localhost\Everyone)
    Attributes – Mandatory Default Enabled
02 S-1-5-32-545 (Alias: BUILTIN\Users)
    Attributes – Mandatory Default Enabled
03 S-1-5-32-544 (Alias: BUILTIN\Administrators)
    Attributes – Mandatory Default Enabled Owner
04 S-1-5-4 (Well Known Group: NT AUTHORITY\INTERACTIVE)
    Attributes – Mandatory Default Enabled
05 S-1-2-1 (Well Known Group: localhost\CONSOLE LOGON)
    Attributes – Mandatory Default Enabled
06 S-1-5-11 (Well Known Group: NT AUTHORITY\Authenticated Users)
    Attributes – Mandatory Default Enabled
07 S-1-5-15 (Well Known Group: NT AUTHORITY\This Organization)
    Attributes – Mandatory Default Enabled
08 S-1-5-5-0-3706178 (no name mapped)
    Attributes – Mandatory Default Enabled LogonId

09 S-1-2-0 (Well Known Group: localhost\LOCAL)
    Attributes – Mandatory Default Enabled
10 S-1-5-21-2857284654-3416964824-2551679015-512 (no name mapped)
    Attributes – Mandatory Default Enabled
11 S-1-5-21-2857284654-3416964824-2551679015-520 (no name mapped)
    Attributes – Mandatory Default Enabled
12 S-1-5-21-2857284654-3416964824-2551679015-519 (no name mapped)
    Attributes – Mandatory Default Enabled
13 S-1-5-21-2857284654-3416964824-2551679015-518 (no name mapped)
    Attributes – Mandatory Default Enabled
14 S-1-5-21-2857284654-3416964824-2551679015-3683 (no name mapped)
    Attributes – Mandatory Default Enabled GroupResource
15 S-1-5-21-2857284654-3416964824-2551679015-572 (no name mapped)
    Attributes – Mandatory Default Enabled GroupResource
16 S-1-16-12288 Unrecognized SID
    Attributes – GroupIntegrity GroupIntegrityEnabled
Primary Group: S-1-5-21-2857284654-3416964824-2551679015-513 (no name mapped)
Privs:
(略)
Authentication ID:         (0,388d63)
Impersonation Level:       Anonymous
TokenType:                 Primary
Source: User32             TokenFlags: 0x2000 ( Token in use )
Token ID: 3a609d           ParentToken ID: 0
Modified ID:               (0, 3a5ce1)
RestrictedSidCount: 0      RestrictedSids: 0000000000000000
OriginatingLogonSession: 3e7

どちらのプロセスも、S-1-5-5-0-3706178 というログオン SID をトークンの中に保持していることが分かります。ログオン SID は、S-1-5-5- で始まっていることから判断することができます。

Well-known security identifiers in Windows operating systems
http://support.microsoft.com/kb/243330/en

以上が GetLogonSID の処理でした。UI を持つプロセスは、デスクトップやウィンドウ ステーション オブジェクトに対してアクセス権を持っていなければならず、そのためにはログオン セッションのログオン SID に対して許可されていなければならないようです。つまり、オブジェクトの DACL に ACE を追加する必要があります。

ACL に関しては、半年ほど前に SDDL をパースするプログラムを書きました。

[Win32] [C++] CUI tool to parse SDDL Strings and account SIDs
https://msmania.wordpress.com/2011/06/30/win32-c-cui-tool-to-parse-sddl-strings-and-account-sids/

オブジェクトが持つセキュリティ記述子 (Security Descriptor) は、オブジェクト ヘッダー nt!_OBJECT_HEADER が保持しています。そこで、デスクトップとウィンドウ ステーションのセキュリティ記述子を見てみます。

winobj で見ることができるように、ウィンドウ ステーション オブジェクトは \Windows\WindowStations\WinSta0 のような名前を持っていますが、デスクトップ オブジェクトにはオブジェクト マネージャーの名前空間に名前を持っていないため、プロセスのハンドルテーブルから探します。

kd> !process 0 0 notepad.exe
PROCESS fffffa8001be6b30
    SessionId: 1  Cid: 0310    Peb: 7fffffdf000  ParentCid: 0910
    DirBase: 07a6b000  ObjectTable: fffff8a00192f600  HandleCount:  90.
    Image: notepad.exe

kd> !handle 0 7 fffffa8001be6b30
    PROCESS fffffa8001be6b30
    SessionId: 1  Cid: 0310    Peb: 7fffffdf000  ParentCid: 0910
    DirBase: 07a6b000  ObjectTable: fffff8a00192f600  HandleCount:  90.
    Image: notepad.exe

Handle table at fffff8a0019e0000 with 90 entries in use

(略)

0034: Object: fffffa8001a6e7c0  GrantedAccess: 000f037f Entry: fffff8a0019e00d0
Object: fffffa8001a6e7c0  Type: (fffffa8000ca7b40) WindowStation
    ObjectHeader: fffffa8001a6e790 (new version)
        HandleCount: 23  PointerCount: 39
        Directory Object: fffff8a0021e8720  Name: WinSta0

0038: Object: fffffa8001cfe830  GrantedAccess: 000f01ff Entry: fffff8a0019e00e0
Object: fffffa8001cfe830  Type: (fffffa8000ca79f0) Desktop
    ObjectHeader: fffffa8001cfe800 (new version)
        HandleCount: 11  PointerCount: 494
        Directory Object: 00000000  Name: Default

(略)

オブジェクトのセキュリティ記述子は、オブジェクト ヘッダー nt!_OBJECT_HEADER に含まれています。

kd> dt _object_header
nt!_OBJECT_HEADER
   +0x000 PointerCount     : Int8B
   +0x008 HandleCount      : Int8B
   +0x008 NextToFree       : Ptr64 Void
   +0x010 Lock             : _EX_PUSH_LOCK
   +0x018 TypeIndex        : UChar
   +0x019 TraceFlags       : UChar
   +0x01a InfoMask         : UChar
   +0x01b Flags            : UChar
   +0x020 ObjectCreateInfo : Ptr64 _OBJECT_CREATE_INFORMATION
   +0x020 QuotaBlockCharged : Ptr64 Void
   +0x028 SecurityDescriptor : Ptr64 Void
   +0x030 Body             : _QUAD

セキュリティ識別子は、!sd でダンプできます。そこで、SecurityDescriptor メンバーに対して !sd を実行すると・・・

kd> dt _object_header fffffa8001cfe800
nt!_OBJECT_HEADER
   +0x000 PointerCount     : 0n494
   +0x008 HandleCount      : 0n11
   +0x008 NextToFree       : 0x00000000`0000000b Void
   +0x010 Lock             : _EX_PUSH_LOCK
   +0x018 TypeIndex        : 0x15 ”
   +0x019 TraceFlags       : 0 ”
   +0x01a InfoMask         : 0xe ”
   +0x01b Flags            : 0 ”
   +0x020 ObjectCreateInfo : 0xfffff800`0166ac00 _OBJECT_CREATE_INFORMATION
   +0x020 QuotaBlockCharged : 0xfffff800`0166ac00 Void
   +0x028 SecurityDescriptor : 0xfffff8a0`01c133ee Void
   +0x030 Body             : _QUAD

kd> !sd 0xfffff8a0`01c133ee
1100000001001c: Unable to get MIN SID header
1100000001001c: Unable to read in Owner in SD

エラーになりました。ポインターが間違っているようです。ポインターの値をよくみると、64bit ポインターなのに 64bit 境界になっていないことに気づきます。64bit 境界ということでポインターは下位 4 ビットが必ず 0 か 8 になるはずですが、SecurityDescriptor の値の下位 4 ビットは e になっています。ここで思い出すのは、さっき出てきた _EX_FAST_REF 構造です。というか、上で引用したページにも書いてありましたが SecurityDescriptor フィールドは _EX_FAST_REF 構造になっているため、下位 4 ビットを 0 にすることでセキュリティ記述子のアドレスが得られます。SecurityDescriptor はオフセット 0x28 バイトなので・・・

▼ ウィンドウ ステーション "Winsta0"

kd> !sd poi(fffffa8001a6e790+28)&0xffffffff`fffffff0
->Revision: 0x1
->Sbz1    : 0x0
->Control : 0x8014
            SE_DACL_PRESENT
            SE_SACL_PRESENT
            SE_SELF_RELATIVE
->Owner   : S-1-5-32-544
->Group   : S-1-5-18
->Dacl    :
->Dacl    : ->AclRevision: 0x2
->Dacl    : ->Sbz1       : 0x0
->Dacl    : ->AclSize    : 0xe4
->Dacl    : ->AceCount   : 0x9
->Dacl    : ->Sbz2       : 0x0
->Dacl    : ->Ace[0]: ->AceType: ACCESS_ALLOWED_ACE_TYPE
->Dacl    : ->Ace[0]: ->AceFlags: 0x4
->Dacl    : ->Ace[0]:             NO_PROPAGATE_INHERIT_ACE
->Dacl    : ->Ace[0]: ->AceSize: 0x24
->Dacl    : ->Ace[0]: ->Mask : 0x00000024
->Dacl    : ->Ace[0]: ->SID: S-1-5-21-2857284654-3416964824-2551679015-500

->Dacl    : ->Ace[1]: ->AceType: ACCESS_ALLOWED_ACE_TYPE
->Dacl    : ->Ace[1]: ->AceFlags: 0xb
->Dacl    : ->Ace[1]:             OBJECT_INHERIT_ACE
->Dacl    : ->Ace[1]:             CONTAINER_INHERIT_ACE
->Dacl    : ->Ace[1]:             INHERIT_ONLY_ACE
->Dacl    : ->Ace[1]: ->AceSize: 0x1c
->Dacl    : ->Ace[1]: ->Mask : 0xf0000000
->Dacl    : ->Ace[1]: ->SID: S-1-5-5-0-3706178

->Dacl    : ->Ace[2]: ->AceType: ACCESS_ALLOWED_ACE_TYPE
->Dacl    : ->Ace[2]: ->AceFlags: 0x4
->Dacl    : ->Ace[2]:             NO_PROPAGATE_INHERIT_ACE
->Dacl    : ->Ace[2]: ->AceSize: 0x1c
->Dacl    : ->Ace[2]: ->Mask : 0x000f037f
->Dacl    : ->Ace[2]: ->SID: S-1-5-5-0-3706178

(略)

->Sacl    :
->Sacl    : ->AclRevision: 0x2
->Sacl    : ->Sbz1       : 0x0
->Sacl    : ->AclSize    : 0x1c
->Sacl    : ->AceCount   : 0x1
->Sacl    : ->Sbz2       : 0x0
->Sacl    : ->Ace[0]: ->AceType: SYSTEM_MANDATORY_LABEL_ACE_TYPE
->Sacl    : ->Ace[0]: ->AceFlags: 0x0
->Sacl    : ->Ace[0]: ->AceSize: 0x14
->Sacl    : ->Ace[0]: ->Mask : 0x00000001
->Sacl    : ->Ace[0]: ->SID: S-1-16-4096

▼ デスクトップ "Default"

kd> !sd poi(fffffa8001cfe800+28)&0xffffffff`fffffff0
->Revision: 0x1
->Sbz1    : 0x0
->Control : 0x8014
            SE_DACL_PRESENT
            SE_SACL_PRESENT
            SE_SELF_RELATIVE
->Owner   : S-1-5-32-544
->Group   : S-1-5-18
->Dacl    :
->Dacl    : ->AclRevision: 0x2
->Dacl    : ->Sbz1       : 0x0
->Dacl    : ->AclSize    : 0x64
->Dacl    : ->AceCount   : 0x4
->Dacl    : ->Sbz2       : 0x0
->Dacl    : ->Ace[0]: ->AceType: ACCESS_ALLOWED_ACE_TYPE
->Dacl    : ->Ace[0]: ->AceFlags: 0x0
->Dacl    : ->Ace[0]: ->AceSize: 0x1c
->Dacl    : ->Ace[0]: ->Mask : 0x000f01ff
->Dacl    : ->Ace[0]: ->SID: S-1-5-5-0-3706178

(略)

->Sacl    :
->Sacl    : ->AclRevision: 0x2
->Sacl    : ->Sbz1       : 0x0
->Sacl    : ->AclSize    : 0x1c
->Sacl    : ->AceCount   : 0x1
->Sacl    : ->Sbz2       : 0x0
->Sacl    : ->Ace[0]: ->AceType: SYSTEM_MANDATORY_LABEL_ACE_TYPE
->Sacl    : ->Ace[0]: ->AceFlags: 0x0
->Sacl    : ->Ace[0]: ->AceSize: 0x14
->Sacl    : ->Ace[0]: ->Mask : 0x00000001
->Sacl    : ->Ace[0]: ->SID: S-1-16-4096

これでセキュリティ記述子をダンプできました。

まず注目すべきは紫色で示した SID です。これは S-1-5-5- で始まっているのでログオン SID です。現在のログオン セッションに対して、アクセス許可の設定がされていることが分かります。

次に、ログオン SID が割り当てられている ACE に注目します。DACL の中でログオン SID が割り当てられている ACE は全て ACCESS_ALLOWED_ACE_TYPE のアクセス許可 ACE です。そして、ウィンドウ ステーションでは 2 つの ACE があるのに対して、デスクトップは 1 つです。ウィンドウ ステーションに設定された 2 つの ACE では、AceFlags と Mask の値が異なっています。AceFlags の定数を見ると分かりますが、1 つは継承可能で、もう 1 つは継承を止める ACE になっています。つまり、Mask で設定されたアクセス許可の種類に応じて、継承させるものと継承させないものを区別していることが分かります。

ACE Inheritance Rules
http://msdn.microsoft.com/en-us/library/windows/desktop/aa374924(v=vs.85).aspx

ACCESS_MASK
http://msdn.microsoft.com/en-us/library/aa374892(v=vs.85).aspx

MSDN のサンプルに戻ります。サンプルにおいては、オブジェクトの DACL に ログオン SID の ACE を追加する処理がAddAceToWindowStation と AddAceToDesktop との 2 つの関数に分けてあります。この理由が上記の内容になります。デスクトップは 1 つの ACE を追加すればいいですが、ウィンドウ ステーションには、2 つの ACE を追加しているため、関数を分けているようです。

AddAceToWindowStation では、AddAce API を 2 回呼ぶことで 2 つの ACE を ACL に追加しています。一方 AddAceToDesktop では、AddAccessAllowedAce API を 1 回呼んで ACE を追加しています。

このサンプルは実はもっと効率よく書けます。まず、AddAceToWindowStation と AddAceToDesktop とでは共通する処理がほとんどです。というのも、いずれの関数も流れは共通で、異なっているのは以下の 5. の部分だけです。

  1. オブジェクト ハンドルからセキュリティ記述子を取得 (GetUserObjectSecurity API)
  2. 新たなセキュリティ記述子を作成 (InitializeSecurityDescriptor API)
  3. 新たな ACL を作成 (InitializeAcl API)
  4. 1. で取得したセキュリティ記述子の DACL を 3. の ACL に コピー
  5. SID に対するアクセス許可 ACE を 4. の ACL に追加
  6. 2. のセキュリティ記述子に 5. の ACL を設定 (SetSecurityDescriptorDacl API)
  7. オブジェクトに 6. の セキュリティ記述子を設定 (SetUserObjectSecurity API)

AddAceToDesktop で使っている AddAccessAllowedAce API では AceFlags を指定できませんが、AddAccessAllowedAceEx API を使うと AceFlags を指定できるので、AddAceToWindowStation と AddAceToDesktop とのいずれにおいても AddAccessAllowedAceEx を使って ACE を追加すれば、わざわざ ACE のサイズを計算して AddAce を使わなくて済みます。

AddAccessAllowedAce function
http://msdn.microsoft.com/en-us/library/aa374947

AddAccessAllowedAceEx function
http://msdn.microsoft.com/en-us/library/aa374951(v=VS.85).aspx

そこで、AddAceToWindowStation と AddAceToDesktop の ACE を追加する処理以外を AddAccessAllowedAceBasedSID という関数でまとめて、ACE の追加は AddAccessAllowedAceEx を使ってプログラムを書き直してみました。

さらに、追加した ACE を削除する関数 RemoveAccessAllowedAcesBasedSID も作りました。これは、指定した SID に対するアクセス許可 ACE のみだけを削除する関数です。

AddAceToWindowStation と AddAceToDesktop は ACE のフラグとマスクの配列を渡すだけになりました。
にも関わらず、エラー処理をするとコードが長くなってしまうのが Win32 の面倒なところ。

//
// dacl.cpp
//

#include <Windows.h>
#include <Sddl.h>
#include <stdio.h>

#include "logue.h"

// add ACCESS_ALLOWED_ACEs of the specified SID to the object’s DACL
BOOL AddAccessAllowedAceBasedSID(HANDLE Object, PSID Sid, DWORD AceCount,
                                CONST DWORD AceFlags[], CONST DWORD AccessMasks[]) {
    BOOL Ret= FALSE;
    SECURITY_INFORMATION DaclInfo= DACL_SECURITY_INFORMATION;
    PACL Acl= NULL; // no need to free
    PACL AclNew= NULL;
    PSECURITY_DESCRIPTOR Sd= NULL;
    PSECURITY_DESCRIPTOR SdNew= NULL;
    DWORD SdSize= 0;
    DWORD SdSizeNeeded= 0;
    ACL_SIZE_INFORMATION AclSizeInfo;
    DWORD AclSize= 0;
    BOOL DaclPresent;
    BOOL DaclDefaulted;

    //
    // Obtain DACL from the object.
    //
http://msdn.microsoft.com/en-us/library/aa379573
    //
    if ( !GetUserObjectSecurity(Object, &DaclInfo, Sd, 0, &SdSizeNeeded) ) {
        if ( GetLastError()!=ERROR_INSUFFICIENT_BUFFER )
            goto cleanup;
           
        Sd= (PSECURITY_DESCRIPTOR)HeapAlloc(GetProcessHeap(),
              HEAP_ZERO_MEMORY, SdSizeNeeded);
        if ( Sd==NULL ) goto cleanup;

        SdSize= SdSizeNeeded;
        if ( !GetUserObjectSecurity(Object,
                &DaclInfo, Sd, SdSize, &SdSizeNeeded) )
            goto cleanup;
    }

    // Obtain the DACL from the security descriptor.
    if ( !GetSecurityDescriptorDacl(Sd, &DaclPresent, &Acl, &DaclDefaulted) )
        goto cleanup;

    // Initialize.
    ZeroMemory(&AclSizeInfo, sizeof(ACL_SIZE_INFORMATION));
    AclSizeInfo.AclBytesInUse = sizeof(ACL);
    if ( Acl ) {
        if (!GetAclInformation(Acl, (LPVOID)&AclSizeInfo, sizeof(ACL_SIZE_INFORMATION), AclSizeInformation) )
            goto cleanup;
    }

    // Create a new ACL
    // (original ACL + new ACCESS_ALLOWED_ACEs)
    AclSize= AclSizeInfo.AclBytesInUse +
        AceCount * (sizeof(ACCESS_ALLOWED_ACE) +
        GetLengthSid(Sid) – sizeof(DWORD));
    AclNew= (PACL)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, AclSize);
    if ( AclNew==NULL ) goto cleanup;

    if ( !InitializeAcl(AclNew, AclSize, ACL_REVISION) )
        goto cleanup;

    // If DACL is present, copy all the ACEs to a new DACL.
    if ( DaclPresent && AclSizeInfo.AceCount ) {
        for ( DWORD i=0; i < AclSizeInfo.AceCount; ++i ) {
            PVOID Ace= NULL;
            if ( !GetAce(Acl, i, &Ace) ) goto cleanup;

            if (!AddAce(AclNew, ACL_REVISION, MAXDWORD,
                        Ace, ((PACE_HEADER)Ace)->AceSize) )
                goto cleanup;
        }
    }

    // Add new ACEs of specified SID to the DACL
    for ( DWORD i=0 ; i<AceCount ; ++i ) {
        if (!AddAccessAllowedAceEx(AclNew, ACL_REVISION,
                                   AceFlags[i], AccessMasks[i], Sid) )
            goto cleanup;
    }

    // Create a new security descriptor.
    // SECURITY_DESCRIPTOR_MIN_LENGTH is enough
    // because SetSecurityDescriptorDacl creates absolute security descriptor
    SdNew = (PSECURITY_DESCRIPTOR)HeapAlloc(GetProcessHeap(),
      HEAP_ZERO_MEMORY, SECURITY_DESCRIPTOR_MIN_LENGTH);
    if ( SdNew==NULL ) goto cleanup;

    if ( !InitializeSecurityDescriptor(SdNew, SECURITY_DESCRIPTOR_REVISION) )
        goto cleanup;

    // Set new DACL to the new security descriptor.
    // (this security descriptor becomes an absolute SD)
    if ( !SetSecurityDescriptorDacl(SdNew, TRUE, AclNew, FALSE) )
        goto cleanup;
   
#ifdef _TRACING
    wprintf(L"Original SD: %p\n", Sd);
    wprintf(L"New SD     : %p\n", SdNew);
    wprintf(L"–>\n");
    getwchar();
#endif

    // Set the new security descriptor for the desktop object.
    if (!SetUserObjectSecurity(Object, &DaclInfo, SdNew))
        goto cleanup;

    Ret= TRUE;

cleanup:
    if ( AclNew ) HeapFree(GetProcessHeap(), 0, AclNew);
    if ( Sd ) HeapFree(GetProcessHeap(), 0, Sd);
    if ( SdNew ) HeapFree(GetProcessHeap(), 0, SdNew);

    return Ret;
}

// add ACCESS_ALLOWED_ACEs of the specified SID to the object’s DACL
BOOL RemoveAccessAllowedAcesBasedSID(HANDLE Object, PSID Sid) {
    BOOL Ret= FALSE;
    SECURITY_INFORMATION DaclInfo= DACL_SECURITY_INFORMATION;
    PACL Acl= NULL; // no need to free
    PACL AclNew= NULL;
    PSECURITY_DESCRIPTOR Sd= NULL;
    PSECURITY_DESCRIPTOR SdNew= NULL;
    DWORD SdSize= 0;
    DWORD SdSizeNeeded= 0;
    ACL_SIZE_INFORMATION AclSizeInfo;
    DWORD AclSize= 0;
    BOOL DaclPresent;
    BOOL DaclDefaulted;

    //
    // Obtain DACL from the object.
    //
http://msdn.microsoft.com/en-us/library/aa379573
    //
    if ( !GetUserObjectSecurity(Object, &DaclInfo, Sd, 0, &SdSizeNeeded) ) {
        if ( GetLastError()!=ERROR_INSUFFICIENT_BUFFER )
            goto cleanup;
           
        Sd= (PSECURITY_DESCRIPTOR)HeapAlloc(GetProcessHeap(),
               HEAP_ZERO_MEMORY, SdSizeNeeded);
        if ( Sd==NULL ) goto cleanup;

        SdSize= SdSizeNeeded;
        if ( !GetUserObjectSecurity(Object, &DaclInfo,
                                    Sd, SdSize, &SdSizeNeeded) )
            goto cleanup;
    }

    // Obtain the DACL from the security descriptor.
    if ( !GetSecurityDescriptorDacl(Sd, &DaclPresent, &Acl, &DaclDefaulted) )
        goto cleanup;

    if ( !DaclPresent || !Acl || Acl->AceCount==0 ) {
        // nothing to do for Null DACL or Empty DACL
        //
http://technet.microsoft.com/ja-jp/query/aa379286
        Ret= TRUE;
        goto cleanup;
    }

    // Initialize.
    ZeroMemory(&AclSizeInfo, sizeof(ACL_SIZE_INFORMATION));
    if (!GetAclInformation(Acl, (LPVOID)&AclSizeInfo,
            sizeof(ACL_SIZE_INFORMATION), AclSizeInformation) )
        goto cleanup;

    // Create an ACL copy
    AclSize= AclSizeInfo.AclBytesInUse;
    AclNew= (PACL)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, AclSize);
    if ( AclNew==NULL ) goto cleanup;

    if ( !InitializeAcl(AclNew, AclSize, ACL_REVISION) )
        goto cleanup;
   
    // do not copy ACCESS_ALLOWED_ACEs of the specified SID
    if ( DaclPresent && AclSizeInfo.AceCount ) {
        for ( DWORD i=0; i < AclSizeInfo.AceCount; ++i ) {
            PVOID Ace= NULL;
            if ( !GetAce(Acl, i, &Ace) ) goto cleanup;
           
            if ( ((PACE_HEADER)Ace)->AceType==ACCESS_ALLOWED_ACE_TYPE &&
                    EqualSid(Sid, &((ACCESS_ALLOWED_ACE*)Ace)->SidStart) )
                continue;

            if (!AddAce(AclNew, ACL_REVISION, MAXDWORD,
                        Ace, ((PACE_HEADER)Ace)->AceSize) )
                goto cleanup;
        }
    }
   
    // Create a new security descriptor.
    // SECURITY_DESCRIPTOR_MIN_LENGTH is enough
    // because SetSecurityDescriptorDacl creates absolute security descriptor
    SdNew = (PSECURITY_DESCRIPTOR)HeapAlloc(GetProcessHeap(),
             HEAP_ZERO_MEMORY, SECURITY_DESCRIPTOR_MIN_LENGTH);
    if ( SdNew==NULL ) goto cleanup;

    if ( !InitializeSecurityDescriptor(SdNew, SECURITY_DESCRIPTOR_REVISION) )
        goto cleanup;

    // Set new DACL to the new security descriptor.
    // (this security descriptor becomes an absolute SD)
    if ( !SetSecurityDescriptorDacl(SdNew, TRUE, AclNew, FALSE) )
        goto cleanup;
   
#ifdef _TRACING
    wprintf(L"Original SD: %p\n", Sd);
    wprintf(L"New SD     : %p\n", SdNew);
    wprintf(L"–>\n");
    getwchar();
#endif

    // Set the new security descriptor for the desktop object.
    if (!SetUserObjectSecurity(Object, &DaclInfo, SdNew))
        goto cleanup;

    Ret= TRUE;

cleanup:
    if ( AclNew ) HeapFree(GetProcessHeap(), 0, AclNew);
    if ( Sd ) HeapFree(GetProcessHeap(), 0, Sd);
    if ( SdNew ) HeapFree(GetProcessHeap(), 0, SdNew);

    return Ret;
}

BOOL AddAceToDesktop(HDESK Desktop, PSID Sid) {
    CONST DWORD AccessMasks[1] = {
        DESKTOP_READOBJECTS | DESKTOP_CREATEWINDOW | DESKTOP_CREATEMENU |
        DESKTOP_HOOKCONTROL | DESKTOP_JOURNALRECORD |
        DESKTOP_JOURNALPLAYBACK |
        DESKTOP_ENUMERATE | DESKTOP_WRITEOBJECTS | DESKTOP_SWITCHDESKTOP |
        STANDARD_RIGHTS_REQUIRED
    };

    DWORD AceFlags[1] = {0};

    return AddAccessAllowedAceBasedSID(Desktop, Sid,
                                       1, AceFlags, AccessMasks);
}

BOOL AddAceToWindowStation(HWINSTA Winsta, PSID Sid) {
    CONST DWORD AccessMasks[2] = {
        GENERIC_READ | GENERIC_WRITE | GENERIC_EXECUTE | GENERIC_ALL,
        WINSTA_ENUMDESKTOPS | WINSTA_READATTRIBUTES |
        WINSTA_ACCESSCLIPBOARD |
        WINSTA_CREATEDESKTOP | WINSTA_WRITEATTRIBUTES |
        WINSTA_ACCESSGLOBALATOMS |
        WINSTA_EXITWINDOWS | WINSTA_ENUMERATE | WINSTA_READSCREEN |
        STANDARD_RIGHTS_REQUIRED};

    CONST DWORD AceFlags[2] = {
        CONTAINER_INHERIT_ACE|INHERIT_ONLY_ACE|OBJECT_INHERIT_ACE,
        NO_PROPAGATE_INHERIT_ACE
    };   

    return AddAccessAllowedAceBasedSID(Winsta, Sid,
                                       2, AceFlags, AccessMasks);
}

BOOL GetLogonSidFromToken(HANDLE Token, PSID *outSid) {
    BOOL Ret= FALSE;
    DWORD TokenGroupLength= 0;
    DWORD SidLength= 0;
    PTOKEN_GROUPS TokenGroup= NULL;
    LPWSTR SidString= NULL;

    if ( !GetTokenInformation(Token, TokenGroups,
                             (LPVOID)TokenGroup, 0, &TokenGroupLength) &&
            GetLastError()!=ERROR_INSUFFICIENT_BUFFER ) {
        wprintf(L"GetTokenInformation (1st chance) failed – 0x%08x\n",
                  GetLastError());
        goto Cleanup;
    }

    TokenGroup= (PTOKEN_GROUPS)HeapAlloc(GetProcessHeap(),
                    HEAP_ZERO_MEMORY, TokenGroupLength);
    if ( !TokenGroup ) {
        wprintf(L"HeapAlloc failed – 0x%08x\n", GetLastError());
        goto Cleanup;
    }

    if ( !GetTokenInformation(Token, TokenGroups, (LPVOID)TokenGroup,
                              TokenGroupLength, &TokenGroupLength) ) {
        wprintf(L"GetTokenInformation failed (2nd chance) – 0x%08x\n",
                    GetLastError());
        goto Cleanup;
    }

    //
    // SE_GROUP_LOGON_ID
    // The SID is a logon SID that identifies the logon session
    // associated with an access token.
    //
http://technet.microsoft.com/en-us/library/aa379624
    //
    for ( DWORD i=0 ; i<TokenGroup->GroupCount ; ++i ) {
        if ( SidString ) LocalFree(SidString);
        ConvertSidToStringSid(TokenGroup->Groups[i].Sid, &SidString);
        wprintf(L"SID: %s", SidString);

        if ( (TokenGroup->Groups[i].Attributes&SE_GROUP_LOGON_ID)==
               SE_GROUP_LOGON_ID ) {
            SidLength= GetLengthSid(TokenGroup->Groups[i].Sid);

            *outSid= (PSID)HeapAlloc(GetProcessHeap(),
                     HEAP_ZERO_MEMORY, SidLength);
            if ( *outSid==NULL ) {

                wprintf(L"HeapAlloc failed – 0x%08x\n", GetLastError());
                goto Cleanup;
            }

            if ( !CopySid(SidLength, *outSid, TokenGroup->Groups[i].Sid) ) {
                wprintf(L"CopySid failed – 0x%08x\n", GetLastError());
                HeapFree(GetProcessHeap(), 0, (LPVOID)*outSid);
                goto Cleanup;
            }

            wprintf(L" (Logon)\n");
            break;
        }
        else
            wprintf(L"\n");
    }
   
    Ret= TRUE;

Cleanup:
    if ( SidString ) LocalFree(SidString);
    if ( TokenGroup ) HeapFree(GetProcessHeap(), 0, TokenGroup);
    return Ret;
}

[Win32] [C++] CUI tool to parse SDDL Strings and account SIDs

Windows のアクセス許可設定の要と言えば、ACL (= Access Control List) です。これについては、@IT で特集が組まれていて、詳しく、かつ分かりやすい記事になっています。

http://www.atmarkit.co.jp/fwin2k/win2ktips/700whatisacl/whatisacl.html

この ACL に実際に出会うときというのは、レジストリに書かれたバイナリだったり、SDDL 文字列だったりするわけですが、プログラム上では、SECURITY_DESCRIPTOR 構造体として出会うことが多いです。

typedef struct _SECURITY_DESCRIPTOR {
   BYTE  Revision;
   BYTE  Sbz1;
   SECURITY_DESCRIPTOR_CONTROL Control;
   PSID Owner;
   PSID Group;
   PACL Sacl;
   PACL Dacl;

} SECURITY_DESCRIPTOR, *PISECURITY_DESCRIPTOR;

typedef PVOID PSECURITY_DESCRIPTOR;

ポインタの定義が PVOID になっているところがミソです。ポインタから構造体のメンバーに直接アクセスすることはなく、GetSecurityDescriptor 何ちゃらという API を使って値を取り出します。おそらく、SECURITY_DESCRIPTOR 構造体がバージョンによって大きく仕様変更される可能性があるからでしょう。

多くのアプリケーションは、AccessCheckAndAuditAlarm 関数を使って、この SECURITY_DESCRIPTOR とアクセス権限を照合しています。ここにブレークポイントを置いてデバッグすると幸せになれるときがあるかも。
http://msdn.microsoft.com/en-us/library/aa374823(v=vs.85).aspx

SECURITY_DESCRIPTOR に含まれる SID や ACL は、以下のような微妙な定義のポインタがあるだけで、直接値を見ることはできません。これはバージョン間の互換性というよりは、SID や ACL の長さが可変であることが理由かと思います。

typedef PVOID PSID;

typedef struct _ACL {
    BYTE  AclRevision;
    BYTE  Sbz1; // パディング
    WORD   AclSize;
    WORD   AceCount;
    WORD   Sbz2; // パディング
    // ヘッダーのみで ACE は含まれていない
} ACL;
typedef ACL *PACL;

そんなこんなで、SECURITY_DESRIPTOR, ACL (SACL | DACL), ACE などの登場人物は扱いにくいというイメージが定着しています。この得体の知れない SECURITY_DESRIPTOR を人間が読めるようにしたのが SDDL 文字列だったりするわけですが、フラグを全部暗記するのは大変です。大体の構造は覚えておいたほうがいいと思いますけどね。

SDDL 文字列は、身近なところでは cacls コマンドで見ることができます。

c:\Windows\System32>cacls advapi32.dll /s
c:\Windows\System32\advapi32.dll "D:PAI(A;;FA;;;S-1-5-80-956008885-3418522649-1831038044-1853292631-2271478464)(A;;0x1200a9;;;BA)(A;;0x1200a9;;;SY)(A;;0x1200a9;;;BU)"

SDDL フォーマットは実際単純で、ここに全部書いてあります。
http://msdn.microsoft.com/en-us/library/aa379570.aspx

今回作ったツールは、日々の業務で利用頻度が高いわりに、調べると意外と簡単にできない以下の操作が可能です。

  • 任意のユーザー (グループ) アカウントと SID の相互変換
  • SDDL の解析

前者のツールは数多くあるようですが、SDDL の解析ツールは出回っていないような気がします。要するにデバッガーの !sd コマンドです。

基本的には API を呼ぶだけなので、アルゴリズム的にトリッキーなところはありません。アカウントと SID の変換は LookupAccountSid と LookupAccountName を使うだけですし、SDDL 関連は、ConvertStringSecurityDescriptorToSecurityDescriptor を呼んで SECURITY_DESCRIPTOR を取得してから、GetSecurityDescriptor~ を呼ぶだけです。ただ、ACE が種類によって異なる構造になっているので厄介です。

ソースを貼る前に、出力結果を載せておきます。SDDL に関しては、以下の MSDN のサンプルと基本的に同じになるように作ってあります。
http://msdn.microsoft.com/en-us/library/aa379570.aspx

> acehack -sddl O:BAG:BAD:(A;;RPWPCCDCLCRCWOWDSDSW;;;SY)(OA;;CCDC;bf967aa8-0de6-11d0-a285-00aa003049e2;;PO)(A;;RPLCRC;;;AU)S:(AU;SAFA;WDWOSDWPCCDCSW;;;WD)

O:BAG:BAD:(A;;RPWPCCDCLCRCWOWDSDSW;;;SY)(OA;;CCDC;bf967aa8-0de6-11d0-a285-00aa003049e2;;PO)(A;;RPLCRC;;;AU)S:(AU;SAFA;WDWOSDWPCCDCSW;;;WD)
Revision:     0x00000001
Control:      0x8014
                  SE_DACL_PRESENT
                  SE_SACL_PRESENT
                  SE_SELF_RELATIVE
RMControl:    0x00
Owner:        S-1-5-32-544
PrimaryGroup: S-1-5-32-544

DACL
    Revision: 0x04
    Size:     0x005c
    AceCount: 0x0003
    Ace[0]
        AceType:       0x00 (ACCESS_ALLOWED_ACE_TYPE)
        AceFlags:      0x00
        AceSize:       0x0014
        Access Mask:   0x000f003f
                            DELETE
                            READ_CONTROL
                            WRITE_DAC
                            WRITE_OWNER
                            Others(0x0000003f)
        Ace Sid:       S-1-5-18
    Ace[1]
        AceType:       0x05 (ACCESS_ALLOWED_OBJECT_ACE_TYPE)
        AceFlags:      0x00
        AceSize:       0x002c
        Access Mask:   0x00000003
                            ADS_RIGHT_DS_CREATE_CHILD
                            ADS_RIGHT_DS_DELETE_CHILD
        Access Flags:  0x00000001 (ACE_OBJECT_TYPE_PRESENT)
        ObjectType:    {bf967aa8-0de6-11d0-a285-00aa003049e2}
        InhObjectType: Not defined
        Ace Sid:       S-1-5-32-550
    Ace[2]
        AceType:       0x00 (ACCESS_ALLOWED_ACE_TYPE)
        AceFlags:      0x00
        AceSize:       0x0014
        Access Mask:   0x00020014
                            READ_CONTROL
                            Others(0x00000014)
        Ace Sid:       S-1-5-11

SACL
    Revision: 0x02
    Size:     0x001c
    AceCount: 0x0001
    Ace[0]
        AceType:       0x02 (SYSTEM_AUDIT_ACE_TYPE)
        AceFlags:      0xc0
                           SUCCESSFUL_ACCESS_ACE_FLAG
                           FAILED_ACCESS_ACE_FLAG
        AceSize:       0x0014
        Access Mask:   0x000d002b
                            DELETE
                            WRITE_DAC
                            WRITE_OWNER
                            Others(0x0000002b)
        Ace Sid:       S-1-1-0

 

アカウント系は以下のような出力となります。

> acehack -account "network service"
Account: network service
Domain:  NT AUTHORITY
SID:     S-1-5-20
Type:    SidTypeWellKnownGroup

> acehack -sid S-1-5-80-956008885-3418522649-1831038044-1853292631-2271478464
SID:     S-1-5-80-956008885-3418522649-1831038044-1853292631-2271478464
Type:    SidTypeWellKnownGroup
Account: NT SERVICE\TrustedInstaller

ソースファイルは以下の 2 つです。参考にした URL をところどころにコメントとして入れてあります。 ビルドする場合は、rpcrt4.lib もリンクさせる必要があります。これは GUID 構造体→ 文字列 の返還に UuidToString を使っているためです。

まずは main.cpp
引数をパースしているだけです。

//
// main.cpp
//

#include <Windows.h>
#include <stdio.h>
#include <ctype.h>
#include <string.h>

#define MAXLEN_OPMODE 16

BOOL OpmodeSid(LPCWSTR StringSid);
BOOL OpmodeAccount(LPCWSTR Account);
// BOOL OpmodeAccount(); not used
BOOL OpmodeSddl(LPCWSTR Sddl);

/*

#### Usage

  acehack opmode <Option>

#### Opmode

  SID ( http://msdn.microsoft.com/en-us/library/aa379597 )

    acehack -SID S-1-5-64-21
    acehack -SID BA
      shows user or group name from specified SID

  SDDL ( http://msdn.microsoft.com/en-us/library/aa379567(v=VS.85).aspx )

    acehack -SDDL D:(A;ID;FA;;;BA)(A;ID;FA;;;SY)(A;ID;0x1301bf;;;AU)(A;ID;0x1200a9;;;BU)
      parses specified SDDL string

  Account

    acehack -Account System
      shows account SID

*/

void ShowUsage() {
    wprintf(L"\n#### Usage\n\n  acehack opmode <Option>\n\n#### Opmode\n\n");
    wprintf(L"  SID  (
http://msdn.microsoft.com/en-us/library/aa379597 )\n\n");
    wprintf(L"    acehack -SID S-1-5-64-21\n");
    wprintf(L"    acehack -SID BA\n      shows user or group name from specified SID \n\n");
    wprintf(L"  SDDL (
http://msdn.microsoft.com/en-us/library/aa379567(v=VS.85).aspx )\n\n");
    wprintf(L"    acehack -SDDL D:(A;ID;FA;;;BA)(A;ID;FA;;;SY)(A;ID;0x1301bf;;;AU)(A;ID;0x1200a9;;;BU)\n");
    wprintf(L"      parses specified SDDL string\n\n  Account\n\n");
    wprintf(L"    acehack -Account System\n      shows account SID\n");
}

static wchar_t upperstr[MAXLEN_OPMODE+1];
const wchar_t *ToUpper(const wchar_t *s) {
    for ( int i=0 ; i<MAXLEN_OPMODE+1 ; ++i ) {
        upperstr[i]= toupper(s[i]);
        if ( s[i]==0 )
            return upperstr;
    }
    upperstr[MAXLEN_OPMODE]= 0;
    return upperstr;
}

int wmain(int argc, wchar_t *argv[]) {
    if ( argc<3 ) {
        ShowUsage();
        return 1;
    }

    const wchar_t *UpperOpmode= ToUpper(argv[1]);
    if ( wcscmp(UpperOpmode, L"-SID")==0 ) {
        OpmodeSid(argv[2]);
    }
    else if ( wcscmp(UpperOpmode, L"-SDDL")==0 ) {
        OpmodeSddl(argv[2]);
    }
    else if ( wcscmp(UpperOpmode, L"-ACCOUNT")==0 ) {
        OpmodeAccount(argv[2]);
    }
    else {
        wprintf(L"%s  =>  bad command.\n", argv[1]);
        return 1;
    }

    return 0;
}

次にメインルーチンの acehack.cpp
定数定義に行数を消費しまくりです。

//
// acehack.cpp
//

#include <Windows.h>
#include <Sddl.h>
#include <strsafe.h>
#include <stdio.h>
#include <Iads.h>

BOOL OpmodeAccount();
BOOL OpmodeAccount(LPCWSTR Account);
BOOL OpmodeSid(LPCWSTR StringSid);
BOOL OpmodeSddl(LPCWSTR Sddl);

/*
http://msdn.microsoft.com/en-us/library/aa379601(VS.85).aspx

typedef enum _SID_NAME_USE {
    SidTypeUser = 1,
    SidTypeGroup,
    SidTypeDomain,
    SidTypeAlias,
    SidTypeWellKnownGroup,
    SidTypeDeletedAccount,
    SidTypeInvalid,
    SidTypeUnknown,
    SidTypeComputer,
    SidTypeLabel
} SID_NAME_USE, *PSID_NAME_USE;
*/

#define MAX_SIDTYPE 32
const wchar_t SidTypeMapping[][MAX_SIDTYPE]= {
    L"",
    L"SidTypeUser",
    L"SidTypeGroup",
    L"SidTypeDomain",
    L"SidTypeAlias",
    L"SidTypeWellKnownGroup",
    L"SidTypeDeletedAccount",
    L"SidTypeInvalid",
    L"SidTypeUnknown",
    L"SidTypeComputer",
    L"SidTypeLabel"
};

/*
http://msdn.microsoft.com/en-us/library/aa379566(v=VS.85).aspx

#define SE_OWNER_DEFAULTED               (0x0001)
#define SE_GROUP_DEFAULTED               (0x0002)
#define SE_DACL_PRESENT                  (0x0004)
#define SE_DACL_DEFAULTED                (0x0008)
#define SE_SACL_PRESENT                  (0x0010)
#define SE_SACL_DEFAULTED                (0x0020)
#define SE_DACL_AUTO_INHERIT_REQ         (0x0100)
#define SE_SACL_AUTO_INHERIT_REQ         (0x0200)
#define SE_DACL_AUTO_INHERITED           (0x0400)
#define SE_SACL_AUTO_INHERITED           (0x0800)
#define SE_DACL_PROTECTED                (0x1000)
#define SE_SACL_PROTECTED                (0x2000)
#define SE_RM_CONTROL_VALID              (0x4000)
#define SE_SELF_RELATIVE                 (0x8000)
*/

#define MAX_SDCONTROL 32

struct SDCONTROL_MAPPING {
    SECURITY_DESCRIPTOR_CONTROL Flag;
    WCHAR ControlName[MAX_SDCONTROL];
};
const SDCONTROL_MAPPING SdControlMapping[]= {
    {SE_OWNER_DEFAULTED, L"SE_OWNER_DEFAULTED"},
    {SE_GROUP_DEFAULTED, L"SE_GROUP_DEFAULTED"},
    {SE_DACL_PRESENT, L"SE_DACL_PRESENT"},
    {SE_DACL_DEFAULTED, L"SE_DACL_DEFAULTED"},
    {SE_SACL_PRESENT, L"SE_SACL_PRESENT"},
    {SE_SACL_DEFAULTED, L"SE_SACL_DEFAULTED"},
    {SE_DACL_AUTO_INHERIT_REQ, L"SE_DACL_AUTO_INHERIT_REQ"},
    {SE_SACL_AUTO_INHERIT_REQ, L"SE_SACL_AUTO_INHERIT_REQ"},
    {SE_DACL_AUTO_INHERITED, L"SE_DACL_AUTO_INHERITED"},
    {SE_SACL_AUTO_INHERITED, L"SE_SACL_AUTO_INHERITED"},
    {SE_DACL_PROTECTED, L"SE_DACL_PROTECTED"},
    {SE_SACL_PROTECTED, L"SE_SACL_PROTECTED"},
    {SE_RM_CONTROL_VALID, L"SE_RM_CONTROL_VALID"},
    {SE_SELF_RELATIVE, L"SE_SELF_RELATIVE"},
    {0, L""},
};

/*
http://msdn.microsoft.com/en-us/library/aa374919

#define ACCESS_MIN_MS_ACE_TYPE                  (0x0)-
#define ACCESS_ALLOWED_ACE_TYPE                 (0x0)
#define ACCESS_DENIED_ACE_TYPE                  (0x1)
#define SYSTEM_AUDIT_ACE_TYPE                   (0x2)
#define SYSTEM_ALARM_ACE_TYPE                   (0x3)
#define ACCESS_MAX_MS_V2_ACE_TYPE               (0x3)-
#define ACCESS_ALLOWED_COMPOUND_ACE_TYPE        (0x4)
#define ACCESS_MAX_MS_V3_ACE_TYPE               (0x4)-
#define ACCESS_MIN_MS_OBJECT_ACE_TYPE           (0x5)-
#define ACCESS_ALLOWED_OBJECT_ACE_TYPE          (0x5)
#define ACCESS_DENIED_OBJECT_ACE_TYPE           (0x6)
#define SYSTEM_AUDIT_OBJECT_ACE_TYPE            (0x7)
#define SYSTEM_ALARM_OBJECT_ACE_TYPE            (0x8)
#define ACCESS_MAX_MS_OBJECT_ACE_TYPE           (0x8)-
#define ACCESS_MAX_MS_V4_ACE_TYPE               (0x8)-
#define ACCESS_MAX_MS_ACE_TYPE                  (0x8)-
#define ACCESS_ALLOWED_CALLBACK_ACE_TYPE        (0x9)
#define ACCESS_DENIED_CALLBACK_ACE_TYPE         (0xA)
#define ACCESS_ALLOWED_CALLBACK_OBJECT_ACE_TYPE (0xB)
#define ACCESS_DENIED_CALLBACK_OBJECT_ACE_TYPE  (0xC)
#define SYSTEM_AUDIT_CALLBACK_ACE_TYPE          (0xD)
#define SYSTEM_ALARM_CALLBACK_ACE_TYPE          (0xE)
#define SYSTEM_AUDIT_CALLBACK_OBJECT_ACE_TYPE   (0xF)
#define SYSTEM_ALARM_CALLBACK_OBJECT_ACE_TYPE   (0x10)
#define SYSTEM_MANDATORY_LABEL_ACE_TYPE         (0x11)
#define ACCESS_MAX_MS_V5_ACE_TYPE               (0x11)-
*/

#define MAX_ACETYPE 41
const WCHAR AceTypeMapping[][MAX_ACETYPE]= {
    L"ACCESS_ALLOWED_ACE_TYPE",
    L"ACCESS_DENIED_ACE_TYPE",
    L"SYSTEM_AUDIT_ACE_TYPE",
    L"SYSTEM_ALARM_ACE_TYPE",
    L"ACCESS_ALLOWED_COMPOUND_ACE_TYPE",
    L"ACCESS_ALLOWED_OBJECT_ACE_TYPE",
    L"ACCESS_DENIED_OBJECT_ACE_TYPE",
    L"SYSTEM_AUDIT_OBJECT_ACE_TYPE",
    L"SYSTEM_ALARM_OBJECT_ACE_TYPE",
    L"ACCESS_ALLOWED_CALLBACK_ACE_TYPE",
    L"ACCESS_DENIED_CALLBACK_ACE_TYPE",
    L"ACCESS_ALLOWED_CALLBACK_OBJECT_ACE_TYPE",
    L"ACCESS_DENIED_CALLBACK_OBJECT_ACE_TYPE",
    L"SYSTEM_AUDIT_CALLBACK_ACE_TYPE",
    L"SYSTEM_ALARM_CALLBACK_ACE_TYPE",
    L"SYSTEM_AUDIT_CALLBACK_OBJECT_ACE_TYPE",
    L"SYSTEM_ALARM_CALLBACK_OBJECT_ACE_TYPE",
    L"SYSTEM_MANDATORY_LABEL_ACE_TYPE",

    L"Unknown Type",
};

/*
#define OBJECT_INHERIT_ACE               (0x1)
#define CONTAINER_INHERIT_ACE            (0x2)
#define NO_PROPAGATE_INHERIT_ACE         (0x4)
#define INHERIT_ONLY_ACE                 (0x8)
#define INHERITED_ACE                    (0x10)
#define SUCCESSFUL_ACCESS_ACE_FLAG       (0x40)
#define FAILED_ACCESS_ACE_FLAG           (0x80)
*/

#define MAX_ACEFLAG 32

struct ACEFLAG_MAPPING {
    BYTE Flag;
    WCHAR Name[MAX_ACEFLAG];
};
const ACEFLAG_MAPPING AceFlagMapping[]= {
    {OBJECT_INHERIT_ACE, L"OBJECT_INHERIT_ACE"},
    {CONTAINER_INHERIT_ACE, L"CONTAINER_INHERIT_ACE"},
    {NO_PROPAGATE_INHERIT_ACE, L"NO_PROPAGATE_INHERIT_ACE"},
    {INHERIT_ONLY_ACE, L"INHERIT_ONLY_ACE"},
    {INHERITED_ACE, L"INHERITED_ACE"},
    {SUCCESSFUL_ACCESS_ACE_FLAG, L"SUCCESSFUL_ACCESS_ACE_FLAG"},
    {FAILED_ACCESS_ACE_FLAG, L"FAILED_ACCESS_ACE_FLAG"},
    {0, L""}
};

/*
http://msdn.microsoft.com/en-us/library/aa374892

#define DELETE                           (0x00010000L)
#define READ_CONTROL                     (0x00020000L)
#define WRITE_DAC                        (0x00040000L)
#define WRITE_OWNER                      (0x00080000L)
#define SYNCHRONIZE                      (0x00100000L)
#define ACCESS_SYSTEM_SECURITY           (0x01000000L)
#define MAXIMUM_ALLOWED                  (0x02000000L)
#define GENERIC_ALL                      (0x10000000L)
#define GENERIC_EXECUTE                  (0x20000000L)
#define GENERIC_WRITE                    (0x40000000L)
#define GENERIC_READ                     (0x80000000L)
*/

#define MAX_ACESSMASK 40

struct ACCESSMASK_MAPPING {
    ACCESS_MASK Flag;
    WCHAR Name[MAX_ACESSMASK];
};

ACCESSMASK_MAPPING AccessMaskMaping[]= {
    {DELETE, L"DELETE"},
    {READ_CONTROL, L"READ_CONTROL"},
    {WRITE_DAC, L"WRITE_DAC"},
    {WRITE_OWNER, L"WRITE_OWNER"},
    {SYNCHRONIZE, L"SYNCHRONIZE"},
    {ACCESS_SYSTEM_SECURITY, L"ACCESS_SYSTEM_SECURITY"},
    {MAXIMUM_ALLOWED, L"MAXIMUM_ALLOWED"},
    {GENERIC_ALL, L"GENERIC_ALL"},
    {GENERIC_EXECUTE, L"GENERIC_EXECUTE"},
    {GENERIC_WRITE, L"GENERIC_WRITE"},
    {GENERIC_READ, L"GENERIC_READ"},
    {0, L""}
};

/*
http://msdn.microsoft.com/en-us/library/aa965848

#define SYSTEM_MANDATORY_LABEL_NO_WRITE_UP         0x1
#define SYSTEM_MANDATORY_LABEL_NO_READ_UP          0x2
#define SYSTEM_MANDATORY_LABEL_NO_EXECUTE_UP       0x4
*/
ACCESSMASK_MAPPING SysMandatoryMapping[]= {
    {SYSTEM_MANDATORY_LABEL_NO_WRITE_UP, L"SYSTEM_MANDATORY_LABEL_NO_WRITE_UP"},
    {SYSTEM_MANDATORY_LABEL_NO_READ_UP, L"SYSTEM_MANDATORY_LABEL_NO_READ_UP"},
    {SYSTEM_MANDATORY_LABEL_NO_EXECUTE_UP, L"SYSTEM_MANDATORY_LABEL_NO_EXECUTE_UP"},
    {0, L""}
};

/*
http://msdn.microsoft.com/en-us/library/aa772285(v=vs.85).aspx

  ADS_RIGHT_DS_CREATE_CHILD          = 0x1,
  ADS_RIGHT_DS_DELETE_CHILD          = 0x2,
  ADS_RIGHT_ACTRL_DS_LIST            = 0x4,
  ADS_RIGHT_DS_SELF                  = 0x8,
  ADS_RIGHT_DS_READ_PROP             = 0x10,
  ADS_RIGHT_DS_WRITE_PROP            = 0x20,
  ADS_RIGHT_DS_DELETE_TREE           = 0x40,
  ADS_RIGHT_DS_LIST_OBJECT           = 0x80,
  ADS_RIGHT_DS_CONTROL_ACCESS        = 0x100
*/

ACCESSMASK_MAPPING AdsRightMapping[]= {
    {ADS_RIGHT_DS_CREATE_CHILD, L"ADS_RIGHT_DS_CREATE_CHILD"},
    {ADS_RIGHT_DS_DELETE_CHILD, L"ADS_RIGHT_DS_DELETE_CHILD"},
    {ADS_RIGHT_ACTRL_DS_LIST, L"ADS_RIGHT_ACTRL_DS_LIST"},
    {ADS_RIGHT_DS_SELF, L"ADS_RIGHT_DS_SELF"},
    {ADS_RIGHT_DS_READ_PROP, L"ADS_RIGHT_DS_READ_PROP"},
    {ADS_RIGHT_DS_WRITE_PROP, L"ADS_RIGHT_DS_WRITE_PROP"},
    {ADS_RIGHT_DS_LIST_OBJECT, L"ADS_RIGHT_DS_LIST_OBJECT"},
    {ADS_RIGHT_DS_CONTROL_ACCESS, L"ADS_RIGHT_DS_CONTROL_ACCESS"},
    {0, L""}
};

/*
http://msdn.microsoft.com/en-us/library/aa374857.aspx
#define ACE_OBJECT_TYPE_PRESENT           0x1
#define ACE_INHERITED_OBJECT_TYPE_PRESENT 0x2
*/

#define MAX_OBJECTTYPE 64
#define OBJECTTYPE_SUPPORTED_MAX ACE_OBJECT_TYPE_PRESENT|ACE_INHERITED_OBJECT_TYPE_PRESENT

const WCHAR ObjectTypeMapping[][MAX_OBJECTTYPE]= {
    L"",
    L"ACE_OBJECT_TYPE_PRESENT",
    L"ACE_INHERITED_OBJECT_TYPE_PRESENT",
    L"ACE_OBJECT_TYPE_PRESENT|ACE_INHERITED_OBJECT_TYPE_PRESENT",
   
    L"Unknown"
};

#define GOTO_CLEANUP(ERRMSG) \
    if ( !ret ) { \
        wprintf(ERRMSG, GetLastError()); \
        ret= FALSE; \
        goto cleanup; \
    }

BOOL OpmodeSid(LPCWSTR StringSid) {
    BOOL ret= FALSE;
    PSID Sid= NULL;
    DWORD NameLength= 0;
    DWORD DomainLength= 0;
    SID_NAME_USE SidType;
    LPWSTR Account= NULL;
    LPWSTR Name= NULL;

    ret= ConvertStringSidToSid(StringSid, &Sid);
    GOTO_CLEANUP(L"ConvertStringSidToSid failed (%d)\n");

    ret= LookupAccountSid(NULL, Sid,
        NULL, &NameLength,
        NULL, &DomainLength,
        &SidType);
   
    int LenTotal= DomainLength+NameLength+1;
    Account= new WCHAR[NameLength];
    Name= new WCHAR[LenTotal];
    ret= LookupAccountSid(NULL, Sid,
        Account, &NameLength,
        Name, &DomainLength,
        &SidType);
    GOTO_CLEANUP(L"LookupAccountSid failed (%d)\n");
   
    StringCchCat(Name, LenTotal, L"\\");
    StringCchCat(Name, LenTotal, Account);

    // result
    wprintf(L"SID:     %s\n", StringSid);
    wprintf(L"Type:    %s\n", SidTypeMapping[SidType]);
    wprintf(L"Account: %s\n", Name);

    ret= TRUE;

cleanup:
    if ( Account ) delete [] Account;
    if ( Name ) delete [] Name;
    if ( Sid ) LocalFree(Sid);

    return ret;
}

BOOL OpmodeAccount() {
    BOOL ret= FALSE;
    LPWSTR User= NULL;
    DWORD UserLength= 0;

    ret= GetUserName(NULL, &UserLength);
    if ( !ret && GetLastError()!=ERROR_INSUFFICIENT_BUFFER ) {
        wprintf(L"GetUserName failed (%d)\n", GetLastError());
        ret= FALSE;
        goto cleanup;
    }

    User= new WCHAR[UserLength];
    ret= GetUserName(User, &UserLength);
    GOTO_CLEANUP(L"GetUserName failed (%d)\n");

    OpmodeAccount(User);

    return TRUE;

cleanup:
    if ( User ) delete [] User;
    return ret;
}

BOOL OpmodeAccount(LPCWSTR Account) {
    BOOL ret= FALSE;
    PSID Sid= NULL;
    DWORD SidLength= 0;
    LPWSTR Domain= NULL;
    DWORD DomainLength= 0;
    SID_NAME_USE SidType;
    LPWSTR StringSid= NULL;

    ret= LookupAccountName(NULL, Account,
        NULL, &SidLength,
        NULL, &DomainLength,
        &SidType);

    Sid= new BYTE[SidLength];
    Domain= new WCHAR[DomainLength];

    ret= LookupAccountName(NULL, Account,
        Sid, &SidLength,
        Domain, &DomainLength,
        &SidType);
    GOTO_CLEANUP(L"LookupAccountName failed (%d)\n");

    ret= ConvertSidToStringSid(Sid, &StringSid);
    GOTO_CLEANUP(L"ConvertSidToStringSid failed (%d)\n");

    // result
    wprintf(L"Account: %s\n", Account);
    wprintf(L"Domain:  %s\n", Domain);
    wprintf(L"SID:     %s\n", StringSid);
    wprintf(L"Type:    %s\n", SidTypeMapping[SidType]);

    ret= TRUE;

cleanup:
    if ( StringSid ) LocalFree(StringSid);
    if ( Sid ) delete [] Sid;
    if ( Domain ) delete [] Domain;
    return ret;
}

// http://msdn.microsoft.com/en-us/library/aa374912(v=VS.85).aspx
// http://msdn.microsoft.com/en-us/library/aa374919
void ShowAce(BYTE AceType, PVOID RawAce) {
    BOOL ret= FALSE;
    int i= 0;

    switch ( AceType ) {
    case ACCESS_ALLOWED_ACE_TYPE:
    case ACCESS_ALLOWED_CALLBACK_ACE_TYPE:
    case ACCESS_DENIED_ACE_TYPE:
    case ACCESS_DENIED_CALLBACK_ACE_TYPE:
    case SYSTEM_ALARM_ACE_TYPE:
    case SYSTEM_ALARM_CALLBACK_ACE_TYPE:
    case SYSTEM_AUDIT_ACE_TYPE:
    case SYSTEM_AUDIT_CALLBACK_ACE_TYPE:
    case SYSTEM_MANDATORY_LABEL_ACE_TYPE:
        {
            PACCESS_ALLOWED_ACE Ace= (PACCESS_ALLOWED_ACE)RawAce;
            LPWSTR Sid= NULL; LPCWSTR SidUnknown= L"Unknown";
            ret= ConvertSidToStringSid((PSID)&Ace->SidStart, &Sid);
            if ( !ret )
                wprintf(L"ConvertSidToStringSid failed (%d)\n",
                  GetLastError());

            ACCESS_MASK Mask= Ace->Mask;
            wprintf(L"        Access Mask:   0x%08x\n", Mask);
            for ( i=0 ; AccessMaskMaping[i].Flag!=0 ; ++i ) {
                if ( AccessMaskMaping[i].Flag&Mask ) {
                    wprintf(L"                            %s\n",
                      AccessMaskMaping[i].Name);
                    Mask&= ~AccessMaskMaping[i].Flag;
                }
            }
            if ( Mask ) {
                if ( AceType==SYSTEM_MANDATORY_LABEL_ACE_TYPE ) {
                    for ( i=0 ; SysMandatoryMapping[i].Flag!=0 ; ++i ) {
                        if ( SysMandatoryMapping[i].Flag&Mask ) {
                            wprintf(L"                            %s\n",
                              SysMandatoryMapping[i].Name);
                            Mask&= ~SysMandatoryMapping[i].Flag;
                        }
                    }
                }

                if ( Mask )
                    wprintf(L"                            Others(0x%08x)\n",
                      Mask);
            }

            wprintf(L"        Ace Sid:       %s\n", Sid ? Sid : SidUnknown);

            if ( Sid) LocalFree(Sid);
        }
        break;
       
    case ACCESS_ALLOWED_OBJECT_ACE_TYPE:
    case ACCESS_ALLOWED_CALLBACK_OBJECT_ACE_TYPE:
    case ACCESS_DENIED_OBJECT_ACE_TYPE:
    case ACCESS_DENIED_CALLBACK_OBJECT_ACE_TYPE:
    case SYSTEM_ALARM_OBJECT_ACE_TYPE:
    case SYSTEM_ALARM_CALLBACK_OBJECT_ACE_TYPE:
    case SYSTEM_AUDIT_OBJECT_ACE_TYPE:
    case SYSTEM_AUDIT_CALLBACK_OBJECT_ACE_TYPE:
        {
            PACCESS_ALLOWED_OBJECT_ACE Ace=
              (PACCESS_ALLOWED_OBJECT_ACE)RawAce;
            LPWSTR Sid= NULL; LPCWSTR SidUnknown= L"Unknown";
            LPGUID GuidObj= NULL;
            LPGUID GuidInheritedObj= NULL;
            PSID SidOffset= NULL;
            RPC_WSTR GuidString= NULL;
            RPC_STATUS RpcRet= RPC_S_OK;

            ACCESS_MASK Mask= Ace->Mask;
            wprintf(L"        Access Mask:   0x%08x\n", Mask);
            for ( i=0 ; AccessMaskMaping[i].Flag!=0 ; ++i ) {
                if ( AccessMaskMaping[i].Flag&Mask ) {
                    wprintf(L"                            %s\n",
                      AccessMaskMaping[i].Name);
                    Mask&= ~AccessMaskMaping[i].Flag;
                }
            }
            if ( Mask ) {
                if ( Ace->Flags&ACE_OBJECT_TYPE_PRESENT ) {
                    for ( i=0 ; AdsRightMapping[i].Flag!=0 ; ++i ) {
                        if ( AdsRightMapping[i].Flag&Mask ) {
                            wprintf(L"                            %s\n",
                              AdsRightMapping[i].Name);
                            Mask&= ~AdsRightMapping[i].Flag;
                        }
                    }
                }

                if ( Mask )
                    wprintf(L"                            Others(0x%08x)\n",
                      Mask);
            }
           
            wprintf(L"        Access Flags:  0x%08x (%s)\n", Ace->Flags,
                ObjectTypeMapping[min(
                  Ace->Flags, OBJECTTYPE_SUPPORTED_MAX+1)]);
            switch ( Ace->Flags ) {
            case 0:
                GuidObj= GuidInheritedObj= NULL;
                SidOffset= (PSID)&Ace->ObjectType;
                break;
            case ACE_OBJECT_TYPE_PRESENT:
                GuidObj= &Ace->ObjectType;
                GuidInheritedObj= NULL;
                SidOffset= (PSID)&Ace->InheritedObjectType;
                break;
            case ACE_INHERITED_OBJECT_TYPE_PRESENT:
                GuidObj= NULL;
                GuidInheritedObj= &Ace->ObjectType;
                SidOffset= (PSID)&Ace->InheritedObjectType;
                break;
            case ACE_OBJECT_TYPE_PRESENT|ACE_INHERITED_OBJECT_TYPE_PRESENT:
                GuidObj= &Ace->ObjectType;
                GuidInheritedObj= &Ace->InheritedObjectType;
                SidOffset= (PSID)&Ace->SidStart;
                break;
            default:
                GuidObj= GuidInheritedObj= NULL;
                SidOffset= NULL;
                break;
            }
           
            if ( GuidObj ) {
                RpcRet= UuidToString(GuidObj, &GuidString);
                if ( RpcRet==RPC_S_OK )
                    wprintf(L"        ObjectType:    {%s}\n", GuidString);
                else
                    wprintf(L"UuidToString failed (%d)\n", RpcRet);

                if ( GuidString ) RpcStringFree(&GuidString);
            }
            else
                wprintf(L"        ObjectType:    Not defined\n");

            if ( GuidInheritedObj ) {
                RpcRet= UuidToString(GuidObj, &GuidString);
               
                if ( RpcRet==RPC_S_OK )
                    wprintf(L"        InhObjectType: {%s}\n", GuidString);
                else
                    wprintf(L"UuidToString failed (%d)\n", RpcRet);

                if ( GuidString ) RpcStringFree(&GuidString);
            }
            else
                wprintf(L"        InhObjectType: Not defined\n");
           
            ret= ConvertSidToStringSid(SidOffset, &Sid);
            if ( !ret )
                wprintf(L"ConvertSidToStringSid failed (%d)\n",
                 
GetLastError());
            wprintf(L"        Ace Sid:       %s\n", Sid ? Sid : SidUnknown);

            if ( Sid) LocalFree(Sid);
        }
        break;
        
       
    case ACCESS_ALLOWED_COMPOUND_ACE_TYPE: // reserved
    default:
        break;
    }
}

// http://msdn.microsoft.com/en-us/library/aa374912(v=VS.85).aspx
void ShowAcl(PACL Acl) {
    if ( Acl==NULL || Acl->AceCount==0 )
        return;

    BOOL ret= FALSE;
    PACE_HEADER AceHeader= NULL;

    for ( int i=0 ; i<Acl->AceCount ; ++i ) {
        AceHeader= NULL;
        ret= GetAce(Acl, i, (LPVOID*)&AceHeader);
        if ( !ret ) {
            wprintf(L"    Ace[%d] -> GetAce failed (%d)\n",
              i, GetLastError());
            continue;
        }

        wprintf(L"    Ace[%d]\n", i);
        wprintf(L"        AceType:       0x%02x (%s)\n", AceHeader->AceType,
            AceTypeMapping[min(AceHeader->AceType,
              ACCESS_MAX_MS_V5_ACE_TYPE+1)]);
        wprintf(L"        AceFlags:      0x%02x\n", AceHeader->AceFlags);
        for ( int i=0 ; AceFlagMapping[i].Flag!=0 ; ++i ) {
            if ( AceHeader->AceFlags&AceFlagMapping[i].Flag )
                wprintf(L"                           %s\n",
                  AceFlagMapping[i].Name);
        }
        wprintf(L"        AceSize:       0x%04x\n", AceHeader->AceSize);

        ShowAce(AceHeader->AceType, AceHeader);
    }
}

BOOL OpmodeSddl(LPCWSTR Sddl) {
    BOOL ret= FALSE;
    PSECURITY_DESCRIPTOR Sd= NULL;
    ULONG SdLength= 0;
    SECURITY_DESCRIPTOR_CONTROL SdCtrl= 0;
    DWORD SdRevision= 0;
    UCHAR RmControl= 0;
    BOOL Default= FALSE;
    PSID Owner= NULL;
    LPWSTR OwnerString= NULL;
    PSID Group= NULL;
    LPWSTR GroupString= NULL;
    BOOL DaclPresent= FALSE;
    PACL Dacl= NULL;
    BOOL SaclPresent= FALSE;
    PACL Sacl= NULL;

    ret= ConvertStringSecurityDescriptorToSecurityDescriptor(
        Sddl, SDDL_REVISION_1, &Sd, &SdLength);
    GOTO_CLEANUP(
      L"ConvertStringSecurityDescriptorToSecurityDescriptor failed (%d)\n");

    // Control & Revision
    ret= GetSecurityDescriptorControl(Sd, &SdCtrl, &SdRevision);
    GOTO_CLEANUP(L"GetSecurityDescriptorControl failed (%d)\n");

    // RMControl
    ret= GetSecurityDescriptorRMControl(Sd, &RmControl);
    GOTO_CLEANUP(L"GetSecurityDescriptorRMControl failed (%d)\n");

    // Owner
    ret= GetSecurityDescriptorOwner(Sd, &Owner, &Default);
    GOTO_CLEANUP(L"GetSecurityDescriptorOwner failed (%d)\n");
    if ( Owner ) {
        ret= ConvertSidToStringSid(Owner, &OwnerString);
        GOTO_CLEANUP(L"ConvertSidToStringSid failed (%d)\n");
    }

    // PrimaryGroup
    ret= GetSecurityDescriptorGroup(Sd, &Group, &Default);
    GOTO_CLEANUP(L"GetSecurityDescriptorGroup failed (%d)\n");
    if ( Group ) {
        ret= ConvertSidToStringSid(Group, &GroupString);
        GOTO_CLEANUP(L"ConvertSidToStringSid failed (%d)\n");
    }

    // Dacl
    ret= GetSecurityDescriptorDacl(Sd, &DaclPresent, &Dacl, &Default);
    GOTO_CLEANUP(L"GetSecurityDescriptorDacl failed (%d)\n");

    // Sacl
    ret= GetSecurityDescriptorSacl(Sd, &SaclPresent, &Sacl, &Default);
    GOTO_CLEANUP(L"GetSecurityDescriptorSacl failed (%d)\n");

    // result
    wprintf(L"%s\n", Sddl);
    wprintf(L"Revision:     0x%08x\n", SdRevision);

    SECURITY_DESCRIPTOR_CONTROL SdCtrlCopy= SdCtrl;
    wprintf(L"Control:      0x%04x\n", SdCtrl);
    for ( int i=0 ; SdControlMapping[i].Flag!=0 ; ++i ) {
        if ( SdControlMapping[i].Flag&SdCtrl ) {
            wprintf(L"                  %s\n",
              SdControlMapping[i].ControlName);
            SdCtrlCopy&= ~SdControlMapping[i].Flag;
        }
    }
    if ( SdCtrlCopy )
        wprintf(L"                  Others(0x%04x)\n", SdCtrlCopy);

    wprintf(L"RMControl:    0x%02x\n", RmControl);
   
    if ( Owner )
        wprintf(L"Owner:        %s\n", OwnerString);
    else
        wprintf(L"Owner:        No owner\n");

    if ( Group )
        wprintf(L"PrimaryGroup: %s\n", GroupString);
    else
        wprintf(L"PrimaryGroup: No group\n");

    wprintf(L"\n");

    if ( !DaclPresent )
        wprintf(L"No DACL\n");
    else if ( Dacl==NULL )
        // NULL ACLs are not supported in SDDL, but just in case…
        wprintf(L"NULL DACL\n");
    else {
        wprintf(L"DACL\n");
        wprintf(L"    Revision: 0x%02x\n", Dacl->AclRevision);
        wprintf(L"    Size:     0x%04x\n", Dacl->AclSize);
        wprintf(L"    AceCount: 0x%04x\n", Dacl->AceCount);
        ShowAcl(Dacl);
    }
   
    wprintf(L"\n");
   
    if ( !SaclPresent )
        wprintf(L"No SACL\n");
    else if ( Sacl==NULL )
        wprintf(L"NULL SACL\n");
    else {
        wprintf(L"SACL\n");
        wprintf(L"    Revision: 0x%02x\n", Sacl->AclRevision);
        wprintf(L"    Size:     0x%04x\n", Sacl->AclSize);
        wprintf(L"    AceCount: 0x%04x\n", Sacl->AceCount);
        ShowAcl(Sacl);
    }

    ret= TRUE;
   
cleanup:
    if ( OwnerString ) LocalFree(OwnerString);
    if ( Sd ) LocalFree(Sd);
    return ret;
}