CodeZine(コードジン)

特集ページ一覧

SIDによるアカウントの列挙

SIDの構造とSID関数の使い方

  • ブックマーク
  • LINEで送る
  • このエントリーをはてなブックマークに追加
2006/07/12 00:00

システムに存在するアカウントは、SIDと呼ばれる可変長の数値で識別されています。SIDは一意になるよう設計されており、特定のアカウントのSIDはある法則に従って作成されます。本稿では、その法則を利用して特定のコンピュータ上に存在するアカウントの名前を列挙する方法を紹介します。

目次
サンプルプログラムの実行結果
サンプルプログラムの実行結果

はじめに

 システムに存在するアカウントは、SIDと呼ばれる可変長の数値で識別されています。SIDは一意になるよう設計されており、特定のアカウントのSIDはある法則に従って作成されます。本稿では、その法則を利用して特定のコンピュータ上に存在するアカウントの名前を列挙する方法を紹介します。

対象読者

  • セキュリティ関係のプログラムを作成しようと考えている方
  • C言語とWindows APIを用いたプログラムに興味がある方

必要な環境

 サンプルプログラムはWindows 2000以上で動作し、コンパイラはVisual Studio 2003を用いています。ソースコードは、リソーススクリプトや独自のヘッダーファイルに依存しない単一のC++ファイルで構成されているため、このファイルの中身をコピーして張り付けるだけで、他の開発環境でもコンパイルすることができると思われます。

SIDとは何か

 SID(シッドと読む)は、システムがユーザーやグループ、コンピュータ、ドメインなどを識別するために使う可変長の数値です。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には、そのコンピュータの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関数について説明します。

アカウント名からSIDを取得
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のサイズです。peUseSID_NAME_USEは、アカウントのタイプ(ユーザーかグループかなど)を表す列挙型です。存在しないアカウント名を指定したときの戻り値はFALSEとなります。また、第1引数のコンピュータ名が不正であるときなどもFALSEとなります。

 次は、LookupAccountSid関数です。

SIDからアカウント名を取得
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を取得する手順を示しています。サンプルコードの抜粋ではありませんが、サンプルでも似たような処理を行っています。

ローカルコンピュータの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の呼び出しが成功するようになります。


  • ブックマーク
  • LINEで送る
  • このエントリーをはてなブックマークに追加

著者プロフィール

All contents copyright © 2005-2020 Shoeisha Co., Ltd. All rights reserved. ver.1.5