[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++] 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;
}