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