はじめに
システムに存在するアカウントは、SIDと呼ばれる可変長の数値で識別されています。SIDは一意になるよう設計されており、特定のアカウントのSIDはある法則に従って作成されます。本稿では、その法則を利用して特定のコンピュータ上に存在するアカウントの名前を列挙する方法を紹介します。
対象読者
- セキュリティ関係のプログラムを作成しようと考えている方
- C言語とWindows APIを用いたプログラムに興味がある方
必要な環境
サンプルプログラムはWindows 2000以上で動作し、コンパイラはVisual Studio 2003を用いています。ソースコードは、リソーススクリプトや独自のヘッダーファイルに依存しない単一のC++ファイルで構成されているため、このファイルの中身をコピーして張り付けるだけで、他の開発環境でもコンパイルすることができると思われます。
SIDとは何か
SID(シッドと読む)は、システムがユーザーやグループ、コンピュータ、ドメインなどを識別するために使う可変長の数値です。SIDはセキュリティ識別子とも呼ばれ、その値はあらゆるシステム上で決して重複することのないように作られています。しかしながら、SIDは完全にランダムに作られているわけではなく、ある一定のビット幅は特定の値を持つように設計されています。この特定の値を強調するために、SIDは数値ではなくテキスト形式で表現されることがよくあります。
SIDの構造
次の図は、とあるコンピュータのSIDをテキスト化したものです。
テキスト化されたSIDは一連の文字列がSIDであることを示すべく、Sで始まります。リビジョンレベルは常に1となり、識別子機関値は、0から5までのいずれかを取ります。これらが何の意味を持つかは今回の話題とはあまり関係がないので割愛します。サブ機関値はSIDによって複数存在しますが、どのようなSIDも最低、1つのサブ機関値(またはRID)を持つことになります。サブ機関値の値は意味を持つわけではなく、SIDを一意にすることが目的です。
次にもう一つのSIDを示します。このSIDは、先に示したコンピュータ上に存在するとあるアカウントのSIDです。
見て分かるようにこのSIDは、最後のサブ機関値以外は、上記したコンピュータのSIDと同じになっています。これは、コンピュータ上に存在するアカウントのSIDには、そのコンピュータのSIDをベースとする決まりがあるからです。このようなSIDの最後のサブ機関値はRIDと呼ばれ、アカウント番号の役割を果たします。システムは、アカウントのRIDを1000からカウントすることになっています。つまり、最初に作成されたアカウントのRIDは1000です。次に作成されたアカウントは1001です。上図のアカウントのRIDは1005となっているので、このアカウントは6番目に作成されたアカウントだということが分かります。
RIDの応用
これまでの話でポイントとなるのは次の2つです。
- アカウントのSIDは、コンピュータのSIDにRIDを付加したものである。
- アカウントのRIDは、1000から始まる。
このことから、あるコンピュータのSIDを取得すると、そのコンピュータ上に存在するアカウントを列挙できるという考えが浮かびます。アカウントのRIDはどのようなシステムでも1000から始まることになっているので、for
文などで1000から適当な数までカウントしていけばよいのです。
コンピュータのSIDを取得する
それでは、実際にSIDを用いたプログラムを見ていくことにします。まず、SIDを操作する上で核となる関数を紹介し、その後にコンピュータのSIDを取得します。
LookupAccountNameとLookupAccountSid
まず、アカウント名からそのアカウントに関連しているSIDを取得するLookupAccountName
関数について説明します。
BOOL LookupAccountName( LPCTSTR lpSystemName, LPCTSTR lpAccountName, PSID Sid, LPDWORD cbSid, LPTSTR ReferencedDomainName, LPDWORD cchReferencedDomainName, PSID_NAME_USE peUse );
lpSystemName
は、第2引数で指定したアカウントが存在するコンピュータ名です。NULLを指定するとローカルコンピュータと解釈されます。lpAccountName
には、SIDを取得したいアカウント名を指定します。Sid
にアカウントのSIDが返ります。プログラムではSIDをPSID
型で表現することになります。cbSid
は、Sid
のサイズです。ReferencedDomainName
は、アカウント名が見つかったドメインの名前が返ります。cbReferencedDomainName
は、ReferencedDomainName
のサイズです。peUse
のSID_NAME_USE
は、アカウントのタイプ(ユーザーかグループかなど)を表す列挙型です。存在しないアカウント名を指定したときの戻り値はFALSEとなります。また、第1引数のコンピュータ名が不正であるときなどもFALSEとなります。
次は、LookupAccountSid
関数です。
BOOL LookupAccountSid( LPCTSTR lpSystemName, PSID lpSid, LPTSTR lpName, LPDWORD cchName, LPTSTR lpReferencedDomainName, LPDWORD cchReferencedDomainName, PSID_NAME_USE peUse );
LookupAccountName
との相違は第2引数と第3引数です。lpSid
に適切なSIDを指定すると、lpName
に関連するアカウント名が返ります。LookupAccountName
にしろ、LookupAccountSid
にしろ重要なのは第3引数までなのですが、それ以外の引数にも有効なアドレスを指定しなければなりません。
LookupAccountNameを呼び出す
次の簡易コードは、ローカルコンピュータのSIDを取得する手順を示しています。サンプルコードの抜粋ではありませんが、サンプルでも似たような処理を行っています。
static BOOL ConvertNameToSid(LPTSTR lpszComputer, LPTSTR lpszName, PSID *ppsid) { TCHAR szDomain[256]; DWORD dwSidLen = 0; DWORD dwDomainLen = sizeof(szDomain); SID_NAME_USE snu; LookupAccountName(lpszComputer, lpszName, NULL, &dwSidLen, szDomain, &dwDomainLen, &snu); *ppsid = (PSID)HeapAlloc(GetProcessHeap(), 0, dwSidLen); return LookupAccountName(lpszComputer, lpszName, *ppsid, &dwSidLen, szDomain, &dwDomainLen, &snu); } static PSID GetLocalComputerSid(void) { DWORD dwBufferSize; TCHAR szComputer[256]; dwBufferSize = sizeof(szComputer); GetComputerName(szComputer, &dwBufferSize); ConvertNameToSid(NULL, szComputer, pSidComputer); return pSidComputer; }
GetLocalComputerSid
は、ローカルコンピュータのSIDを返す関数です。まず、GetComputerName
でローカルコンピュータ名を取得し、それを自作関数のConvertNameToSid
に指定します。この関数は、LookupAccountName
の呼び出しをラッピングしています。1回目のLookupAccountName
の呼び出し時点ではSIDのサイズが分からないので、第3引数にNULLを指定し、dwSidLen
を初期化することに専念します。これにより、dwSidLen
にはSIDのサイズが格納されるので、2回目のLookupAccountName
の呼び出しが成功するようになります。