LoginSignup
6
0

More than 5 years have passed since last update.

リバースエンジニアリングへの道 - その3

Last updated at Posted at 2018-05-15

リバースエンジニアリングへの道

出田 守です。
最近、情報セキュリティに興味を持ち、『リバースエンジニアリング-Pythonによるバイナリ解析技法』という本(以降、「教科書」と呼びます)を読みました。
「こんな世界があるのか!かっこいい!」と感動し、私も触れてみたいということでド素人からリバースエンジニアリングができるまでを書いていきたいと思います。
ちなみに、教科書ではPython言語が使用されているので私もPython言語を使用しています。
ここを見ていただいた諸先輩方からの意見をお待ちしております。

軌跡

リバースエンジニアリングへの道 - その2

環境

OS: Windows10 64bit Home (日本語)
CPU: Intel® Core™ i3-6006U CPU @ 2.00GHz × 1
メモリ: 2048MB
Python: 3.6.5

私の環境は、普段Ubuntu16.04を使っていますが、ここではWindows10 64bitを仮想マシン上で立ち上げております。
ちなみに教科書では、Windowsの32bitで紹介されています。

自作デバッガへの道 - その3 「スレッドの取得・レジスタの捕捉」

前回は、、プロセスのアタッチ・デタッチをする方法を学習しました。
今回は、レジスタの各値を取得する方法を学ぼうと思います。
レジスタを取得するには、そのプロセスの中のスレッドを取得する必要があるみたいです。

スレッド

スレッドとは、プロセス内で実際に実行される最小の処理単位のことをいうみたいです。
どのプロセスにもメインスレッドが存在します。
よく聞くマルチスレッドは、名前の通りスレッドを複数生成してそれぞれのスレッドにタスクを振り分け実行速度を向上させます。

まず、スレッドを取得する前にプロセス内の情報を取得する必要があります。
これにはWindows API関数「CreateToolhelp32Snapshot」を使用します。

スナップショットの作成「CreateToolhelp32Snapshot」

このAPI関数はプロセスのスナップショットを作成します。そのとき、スナップショットにヒープリスト、モジュールリスト、プロセスリスト、スレッドリストを取得することが出来ます。
なお、名前に32が付いていますが、64bitでも使用できるみたいです。古いWindows時代の名残みたいです。

CreateToolhelp32Snapshotの仕様は以下です。

MSDN-CreateToolhelp32Snapshot

HANDLE WINAPI CreateToolhelp32Snapshot(
  DWORD dwFlags,
  DWORD th32ProcessID
);

引数のdwFlagsは、スナップショットにどの情報を含めたいか指定します。
今回は、スレッドリストを取得したいのでTH32CS_SNAPTHREADを指定します。
TH32CS_SNAPTHREADは以下に書いてありました。
MSDN-CreateToolhelp32Snapshot function
th32ProcessIDは、取得したいプロセスIDです。

では、実際にCreateToolhelp32Snapshotを使ってみます。

test_snapshot1.py
from ctypes  import *
from defines import *

kernel32 = windll.kernel32

pid = input("pid: ")

if kernel32.CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, int(pid)):
    print("successed")
else:
    print(WinError(GetLastError()))

スナップショットが成功すれば、ハンドルを返します。
ただ、このスクリプトを試したところどんなPIDを入力しても成功してしまいました。
また、プロセスをアタッチしてスナップショットを取るのと、アタッチしたあとにスナップショットを取るのとでは戻り値に違いがありました。

test_snapshot2.py
from ctypes  import *
from defines import *

kernel32 = windll.kernel32

pid = input("pid: ")

if kernel32.DebugActiveProcess(int(pid)):
    print("attached :", pid)
    snapshot = kernel32.CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, int(pid))
    print(snapshot)
    if snapshot:
        print("successed")
    else:
        print(WinError(GetLastError()))
    kernel32.DebugActiveProcessStop(int(pid))
    print("detached :", pid)
else:
    print("Error pid:", pid)
    print(WinError(GetLastError()))

if kernel32.CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, int(pid)):
    print("successed")
else:
    print(WinError(GetLastError()))

どうするのが正解か分かりませんが、
とりあえずそれぞれのハンドルが使えるのか試してみたいと思います。

スレッドリストの情報が取得できれば、次はスレッドを列挙します。
スレッドを列挙するために使用するWindows API関数は「Thread32First」と「Thread32Next」です。

スレッドの列挙「Thread32First」・「Thread32Next」

スレッドリストからスレッドの列挙を開始するためにまずThread32Firstを使用します。
スレッドリストから次のスレッドを参照するにはThread32Nextを使用します。
Thread32FirstとThread32Nextの仕様は以下です。

MSDN-Thread32First

BOOL WINAPI Thread32First(
  HANDLE hSnapshot,
  LPTHREADENTRY32 lpte
);

MSDN-Thread32Next

BOOL WINAPI Thread32Next(
  HANDLE hSnapshot,
  LPTHREADENTRY32 lpte
);

引数のhSnapshotは、CreateToolhelp32Snapshotで取得したスレッドリストのスナップショットです。
lpteは、THREADENTRY32構造体へのポインタです。

THREADENTRY32構造体の仕様は以下です。

MSDN-THREADENTRY32

typedef struct tagTHREADENTRY32 {
  DWORD dwSize;
  DWORD cntUsage;
  DWORD th32ThreadID;
  DWORD th32OwnerProcessID;
  LONG tpBasePri;
  LONG tpDeltaPri;
  DWORD dwFlags;
} THREADENTRY32, *PTHREADENTRY32;

dwSizeは、この構造体自身のサイズです。これを指定しないとThread32Firstの実行が失敗するみたいです。
cntUsageは、使われていないみたいなので0が設定されます。
th32ThreadIDは、スレッドIDです。
th32OwnerProcessIDは、そのスレッドのプロセスIDです。
tpBasePriは、スレッドの優先度を示す値だと思います。
tpDeltaPri, dwFlagsは、使われていないみたいなので0が設定されます。

では、実際にこれらの関数を試してみます。
まずは、プロセスをアタッチせずにスナップショットを取った場合です。

test_threadlist1.py
from ctypes  import *
from defines import *

kernel32 = windll.kernel32

pid = input("pid: ")

snapshot = kernel32.CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, int(pid))
lpte = THREADENTRY32()
lpte.dwSize = sizeof(lpte)
ret = kernel32.Thread32First(snapshot, byref(lpte))
while ret:
    print("PID: ", lpte.th32OwnerProcessID)
    print("TID: ", lpte.th32ThreadID)
    ret = kernel32.Thread32Next(snapshot, byref(lpte))

ここで迷ったのが、Thread32Firstをした直後のTHREAD32ENTRY構造体の中身は何も格納されていませんでした。「失敗したのかな?」と思ったのですが、Thread32Nextで進めていくと、情報が格納されていることが確認できました。
次に、プロセスをアタッチした場合を確認しました。

test_threadlist2.py
from ctypes  import *
from defines import *

kernel32 = windll.kernel32

pid = input("pid: ")

if kernel32.DebugActiveProcess(int(pid)):
    print("attached :", pid)
    snapshot = kernel32.CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, int(pid))
    print(snapshot)
    if snapshot:
        print("successed")
        lpte = THREADENTRY32()
        lpte.dwSize = sizeof(lpte)
        ret = kernel32.Thread32First(snapshot, byref(lpte))
        while ret:
            print("PID: ", lpte.th32OwnerProcessID)
            print("TID: ", lpte.th32ThreadID)
            ret = kernel32.Thread32Next(snapshot, byref(lpte))
    else:
        print(WinError(GetLastError()))
    kernel32.DebugActiveProcessStop(int(pid))
    print("detached :", pid)
else:
    print("Error pid:", pid)
    print(WinError(GetLastError()))

これも、ちゃんと情報が格納されていました。
結局はアタッチしてもしなくてもスレッドは列挙できるようです。

では、該当プロセスのスレッドIDが取得できたので、スレッドのハンドルを取得したいと思います。
スレッドのハンドルを取得するWindows API関数は「OpenThread」です。

スレッドのハンドルを取得「OpenThread」

OpenThreadの仕様は以下です。
MSDN-OpenThread

HANDLE OpenThread(
  DWORD dwDesiredAccess,
  BOOL bInheritHandle,
  DWORD dwThreadId
);

引数のdwDesiredAccessは、ハンドルにどのようなアクセス権を付加するか指定します。ここでは、全てのアクセス権を付加するTHREAD_ALL_ACCESSを指定します。教科書では、0x001F03FFを指定していたのでそれに従います。
他のアクセス権については、以下参照。
MSDN-Thread Security and Access Rights
hInheritHandleは、新しいプロセスに対してハンドルを継承させるか指定します。教科書ではNoneを指定していました。
dwThreadIdは、スレッドIDを指定します。

では、OpenThread関数を試してみます。

test_openthread.py
from ctypes  import *
from defines import *

kernel32 = windll.kernel32

pid = input("pid: ")

snapshot = kernel32.CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, int(pid))
lpte = THREADENTRY32()
lpte.dwSize = sizeof(lpte)
res = kernel32.Thread32First(snapshot, byref(lpte))
while res:
    print("PID: ", lpte.th32OwnerProcessID)
    if lpte.th32OwnerProcessID == int(pid):
        print("    TID: ", lpte.th32ThreadID)
        handle = kernel32.OpenThread(THREAD_ALL_ACCESS, None, lpte.th32ThreadID)
        # handle = kernel32.OpenThread(THREAD_ALL_ACCESS, None, 9991) # invalid
        if handle:
            print("    handle: ", handle)
        else:
            print(WinError(GetLastError()))
    res = kernel32.Thread32Next(snapshot, byref(lpte))

成功するとスレッドのハンドルを取得できます。失敗すると0が戻り値となるようです。

スレッドのハンドルを取得できたので、
次は該当のプロセスIDの各スレッドからレジスタ値を取得してみます。
レジスタ値を取得するために使用するWindows API関数は「GetThreadContext」です。
また、レジスタ値を変更するのに使用するWindows API関数は「SetThreadContext」です。
これらは、スレッド内のコンテキストデータを取得・変更する関数です。


ここでコンテキストについて調べてみました。
コンテキストとは、プログラムが何らかの操作に必要な制御情報らしいです。
また、コンテキストスイッチとは、複数のプロセスが一つのCPUを共有できるように、レジスタ値をコンテキストとして保持しておき、プログラムの実行に応じてコンテキストを切り替えることだそうです。
参照: コンテキスト


レジスタ値の取得・変更「GetThreadContext」・「SetThreadContext」

GetThreadContextとSetThreadContextの仕様は以下です。

MSDN-GetThreadContext

BOOL GetThreadContext(
  HANDLE hThread, // コンテキストを持つスレッドのハンドル
  LPCONTEXT lpContext // コンテキストを受け取る構造体のアドレス
);

MSDN-SetThreadContext

BOOL SetThreadContext(
  HANDLE hThread, // このテキストを持つスレッドのハンドル
  CONST CONTEXT *lpContext // コンテキストが入った構造体のアドレス
);

引数のhThreadは先程OpenThread関数で取得したスレッドハンドルです。
lpContextは、CONTEXT構造体です。

CONTEXT構造体の仕様を調べると、以下がヒットしました。
MSDN-CONTEXT
しかし、Windows SDKのWinNT.hを見てくれとの記述が・・・
今更ながらWindows SDKに各ヘッダファイルがあることを知りました。


ということで、ちょっと脱線してWindows SDKをインストールします。
Windows 10 SDK download
インストールすると私の環境では、
C:\Program Files (x86)\Windows Kits\10\Include\10.0.17134.0\um
にWinNT.hがありました。


WinNT.hには32bitや64bitなどのCONTEXT構造体が定義されています。
教科書では32bitですが今回は64bitなので、以下になります。

typedef struct DECLSPEC_ALIGN(16) _CONTEXT {

  //
  // Register parameter home addresses.
  //
  // N.B. These fields are for convience - they could be used to extend the
  // context record in the future.
  //

  DWORD64 P1Home;
  DWORD64 P2Home;
  DWORD64 P3Home;
  DWORD64 P4Home;
  DWORD64 P5Home;
  DWORD64 P6Home;

  //
  // Control flags.
  //

  DWORD ContextFlags;
  DWORD MxCsr;

  //
  // Segment Registers and processor flags.
  //

  WORD SegCs;
  WORD SegDs;
  WORD SegEs;
  WORD SegFs;
  WORD SegGs;
  WORD SegSs;
  DWORD EFlags;

  //
  // Debug registers
  //

  DWORD64 Dr0;
  DWORD64 Dr1;
  DWORD64 Dr2;
  DWORD64 Dr3;
  DWORD64 Dr6;
  DWORD64 Dr7;

  //
  // Integer registers.
  //

  DWORD64 Rax;
  DWORD64 Rcx;
  DWORD64 Rdx;
  DWORD64 Rbx;
  DWORD64 Rsp;
  DWORD64 Rbp;
  DWORD64 Rsi;
  DWORD64 Rdi;
  DWORD64 R8;
  DWORD64 R9;
  DWORD64 R10;
  DWORD64 R11;
  DWORD64 R12;
  DWORD64 R13;
  DWORD64 R14;
  DWORD64 R15;

  //
  // Program counter.
  //

  DWORD64 Rip;

  //
  // Floating point state.
  //

  union {
    XMM_SAVE_AREA32 FltSave;
    struct {
      M128A Header[2];
      M128A Legacy[8];
      M128A Xmm0;
      M128A Xmm1;
      M128A Xmm2;
      M128A Xmm3;
      M128A Xmm4;
      M128A Xmm5;
      M128A Xmm6;
      M128A Xmm7;
      M128A Xmm8;
      M128A Xmm9;
      M128A Xmm10;
      M128A Xmm11;
      M128A Xmm12;
      M128A Xmm13;
      M128A Xmm14;
      M128A Xmm15;
    } DUMMYSTRUCTNAME;
  } DUMMYUNIONNAME;

  //
  // Vector registers.
  //

  M128A VectorRegister[26];
  DWORD64 VectorControl;

  //
  // Special debug control registers.
  //

  DWORD64 DebugControl;
  DWORD64 LastBranchToRip;
  DWORD64 LastBranchFromRip;
  DWORD64 LastExceptionToRip;
  DWORD64 LastExceptionFromRip;
} CONTEXT, *PCONTEXT;

レジスタの名前などが確認できますね。
ちなみに、レジスタの呼ばれ方が32bitでは例えばEAXなのが64bitではRAXと呼ばれるみたいですね。
DECLSPEC_ALIGN(#)は、#bitに合わせてデータを整列させるみたいです。ざっくり調べた感じだと最近のCPU命令ではこのように整列させないといけないのと、実行の効率化のためらしいです。
M128Aは、構造体です。
M128A構造体は以下です。

typedef struct DECLSPEC_ALIGN(16) _M128A {
  ULONGLONG Low;
  LONGLONG High;
} M128A, *PM128A;

XMM_SAVE_AREA32はXSAVE_FORMAT構造体のようです。
XSAVE_FORMAT構造体は以下です。

typedef struct DECLSPEC_ALIGN(16) _XSAVE_FORMAT {
  WORD ControlWord;
  WORD StatusWord;
  BYTE TagWord;
  BYTE Reserved1;
  WORD ErrorOpcode;
  DWORD ErrorOffset;
  WORD ErrorSelector;
  WORD Reserved2;
  DWORD DataOffset;
  WORD DataSelector;
  WORD Reserved3;
  DWORD MxCsr;
  DWORD MxCsr_Mask;
  M128A FloatRegisters[8];
#if defined(_WIN64)
  M128A XmmRegisters[16];
  BYTE Reserved4[96];
#else
  M128A XmmRegisters[8];
  BYTE Reserved4[224];
#endif
} XSAVE_FORMAT, *PXSAVE_FORMAT;

それでは、GetThreadContext関数を使ってレジスタ値を取得してみます。

構造体・共用体定義

defines.py
class M128A(Structure):
    _fields_ = [
        ("Low",  ULONGLONG),
        ("High", LONGLONG),
    ]

class XSAVE_FORMAT(Structure):
    _fields_ = [
        ("ControlWord",    WORD), 
        ("StatusWord",     WORD), 
        ("TagWord",        BYTE), 
        ("Reservedl",      BYTE), 
        ("ErrorOpcode",    WORD), 
        ("ErrorOffset",    DWORD),
        ("ErrorSelector",  WORD), 
        ("Reserved2",      WORD), 
        ("DataOffset",     DWORD),
        ("DataSelector",   WORD), 
        ("Reserved3",      WORD), 
        ("MxCsr",          DWORD),
        ("MxCsr_Mask",     DWORD),
        ("FloatRegisters", M128A*8),
        ("XmmRegisters",   M128A*16),
        ("Reserved4",      BYTE*96),
    ]

class DUMMYSTRUCTNAME(Structure):
    _fields_ = [
        ("Header", M128A*2),
        ("Legacy", M128A*8),
        ("Xmm0",   M128A),
        ("Xmm1",   M128A),
        ("Xmm2",   M128A),
        ("Xmm3",   M128A),
        ("Xmm4",   M128A),
        ("Xmm5",   M128A),
        ("Xmm6",   M128A),
        ("Xmm7",   M128A),
        ("Xmm8",   M128A),
        ("Xmm9",   M128A),
        ("Xmm10",  M128A),
        ("Xmm11",  M128A),
        ("Xmm12",  M128A),
        ("Xmm13",  M128A),
        ("Xmm14",  M128A),
        ("Xmm15",  M128A),
    ]

XMM_SAVE_AREA32 = XSAVE_FORMAT
class DUMMYUNIONNAME(Union):
    _fields_ = [
        ("FltSave",         XMM_SAVE_AREA32),
        ("dummystructname", DUMMYSTRUCTNAME)
    ]

class CONTEXT(Structure):
    _fields_ = [
        ("P1Home",               DWORD64),
        ("P2Home",               DWORD64),
        ("P3Home",               DWORD64),
        ("P4Home",               DWORD64),
        ("P5Home",               DWORD64),
        ("P6Home",               DWORD64),
        ("ContextFlags",         DWORD),  
        ("MxCsr",                DWORD),  
        ("SegCs",                WORD),   
        ("SegDs",                WORD),   
        ("SegEs",                WORD),   
        ("SegFs",                WORD),   
        ("SegGs",                WORD),   
        ("SegSs",                WORD),   
        ("EFlags",               DWORD),  
        ("Dr0",                  DWORD64),
        ("Dr1",                  DWORD64),
        ("Dr2",                  DWORD64),
        ("Dr3",                  DWORD64),
        ("Dr6",                  DWORD64),
        ("Dr7",                  DWORD64),
        ("Rax",                  DWORD64),
        ("Rcx",                  DWORD64),
        ("Rdx",                  DWORD64),
        ("Rbx",                  DWORD64),
        ("Rsp",                  DWORD64),
        ("Rbp",                  DWORD64),
        ("Rsi",                  DWORD64),
        ("Rdi",                  DWORD64),
        ("R8",                   DWORD64),
        ("R9",                   DWORD64),
        ("R10",                  DWORD64),
        ("R11",                  DWORD64),
        ("R12",                  DWORD64),
        ("R13",                  DWORD64),
        ("R14",                  DWORD64),
        ("R15",                  DWORD64),
        ("Rip",                  DWORD64),
        ("dummyunionname",       DUMMYUNIONNAME),
        ("VectorRegister",       M128A*26),
        ("VectorControl",        DWORD64),
        ("DebugControl",         DWORD64),
        ("LastBranchToRip",      DWORD64),
        ("LastBranchFromRip",    DWORD64),
        ("LastExceptionToRip",   DWORD64),
        ("LastExceptionFromRip", DWORD64),
    ]

たぶん合っているはず・・・
次にテストプログラムを書いていきます。

テストプログラム

test_register.py
from ctypes  import *
from defines import *

kernel32 = windll.kernel32

pid = input("pid: ")

snapshot = kernel32.CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, int(pid))
lpte = THREADENTRY32()
lpte.dwSize = sizeof(lpte)
res = kernel32.Thread32First(snapshot, byref(lpte))
while res:
    print("PID: ", lpte.th32OwnerProcessID)
    if lpte.th32OwnerProcessID == int(pid):
        print("    TID: ", lpte.th32ThreadID)
        handle = kernel32.OpenThread(THREAD_ALL_ACCESS, None, lpte.th32ThreadID)
        if handle:
            print("    handle: ", handle)
            context = CONTEXT()
            context.ContextFlags = CONTEXT_FULL
            res2 = kernel32.GetThreadContext(handle, byref(context))
            if res2:
                print("[Rip]0x{:016X}".format(context.Rip))
                print("[Rax]0x{:016X}".format(context.Rax))
                print("[Rcx]0x{:016X}".format(context.Rcx))
                print("[Rdx]0x{:016X}".format(context.Rdx))
                print("[Rbx]0x{:016X}".format(context.Rbx))
                print("[Rsp]0x{:016X}".format(context.Rsp))
                print("[Rbp]0x{:016X}".format(context.Rsp))
                print("[Rsi]0x{:016X}".format(context.Rsi))
                print("[Rdi]0x{:016X}".format(context.Rdi))
            else:
                print(WinError(GetLastError()))
        else:
            print(WinError(GetLastError()))

    res = kernel32.Thread32Next(snapshot, byref(lpte))

例によって電卓のレジスタ値を取得しました。
ただ、電卓を操作したうえで再度レジスタ値を取得しても最初に取得した値と変わらないレジスタもありました。
あと、RIP、RSPはなぜかずっと0のままでした。失敗しているのでしょうか?重要なレジスタ値は隠されているのでしょうか?


(Update:2018/05/30)失礼しました!

上記でRIP, RSPなどが取得できませんでした。
原因はContextFlagsを設定しているところで、CONTEXT_FULLの値に誤りがありました。

context.ContextFlags = CONTEXT_FULL

winnt.hを見て正しい値に修正すると正しく取得することができました。

defines.py
...
CONTEXT_AMD64                   = 0x00100000
CONTEXT_CONTROL                 = CONTEXT_AMD64|0x00000001
CONTEXT_INTEGER                 = CONTEXT_AMD64|0x00000002
CONTEXT_SEGMENTS                = CONTEXT_AMD64|0x00000004
CONTEXT_FLOATING_POINT          = CONTEXT_AMD64|0x00000008
CONTEXT_DEBUG_REGISTERS         = CONTEXT_AMD64|0x00000010
CONTEXT_FULL                    = CONTEXT_CONTROL|CONTEXT_INTEGER|CONTEXT_FLOATING_POINT
CONTEXT_ALL                     = CONTEXT_CONTROL|CONTEXT_INTEGER|CONTEXT_SEGMENTS|CONTEXT_FLOATING_POINT|CONTEXT_DEBUG_REGISTERS
...

ちなみにレジスタ値の設定も出来ました。

test_registers2.py
from ctypes  import *
from defines import *

kernel32 = windll.kernel32

pid = input("pid: ")

snapshot = kernel32.CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, int(pid))
lpte = THREADENTRY32()
lpte.dwSize = sizeof(lpte)
res = kernel32.Thread32First(snapshot, byref(lpte))
while res:
    # print("PID: ", lpte.th32OwnerProcessID)
    if lpte.th32OwnerProcessID == int(pid):
        print("    TID: ", lpte.th32ThreadID)
        handle = kernel32.OpenThread(THREAD_ALL_ACCESS, None, lpte.th32ThreadID)
        if handle:
            # print("    handle: ", handle)
            context = CONTEXT()
            context.ContextFlags = CONTEXT_ALL
            res2 = kernel32.GetThreadContext(handle, byref(context))
            context.Rip = 1
            context.Rax = 1
            context.Rbx = 2
            context.Rcx = 3
            if not kernel32.SetThreadContext(handle, context):
                print(WinError(GetLastError()))
            if res2:
                print("[Rip]0x{:016X}".format(context.Rip))
                print("[Rax]0x{:016X}".format(context.Rax))
                print("[Rcx]0x{:016X}".format(context.Rcx))
                print("[Rdx]0x{:016X}".format(context.Rdx))
                print("[Rbx]0x{:016X}".format(context.Rbx))
                print("[Rsp]0x{:016X}".format(context.Rsp))
                print("[Rbp]0x{:016X}".format(context.Rsp))
                print("[Rsi]0x{:016X}".format(context.Rsi))
                print("[Rdi]0x{:016X}".format(context.Rdi))
            else:
                print(WinError(GetLastError()))
        else:
            print(WinError(GetLastError()))

    res = kernel32.Thread32Next(snapshot, byref(lpte))

こうすると、Rip=1, Rax=1, Rbx=2, Rcx=3と設定されているのが確認できます。
Ripの取得は出来ませんが、設定はできるみたいですね。

とりあえず、これでレジスタの値は取得できたので、自作デバッガへここまでの機能を追加します。

my_debugger.py
class Debugger():
    ・・・
    def open_thread(self, thread_id):
        h_thread = kernel32.OpenThread(THREAD_ALL_ACCESS, None, thread_id)
        if h_thread is not 0:
            return h_thread
        else:
            print("[*] Could not obtain a valid thread handle.")
            return False

    def enumerate_threads(self):
        lpte = THREADENTRY32()
        thread_list  = []
        snapshot     = kernel32.CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, self.pid)

        if snapshot is not None:
            lpte.dwSize = sizeof(lpte)
            success     = kernel32.Thread32First(snapshot, byref(lpte))
            while success:
                if lpte.th32OwnerProcessID==self.pid:
                    thread_list.append(lpte.th32ThreadID)
                success = kernel32.Thread32Next(snapshot, byref(lpte))
            kernel32.CloseHandle(snapshot)
            return thread_list
        else:
            return False

    def get_thread_context(self, thread_id=None, h_thread=None):
        context = CONTEXT()
        context.ContextFlags = CONTEXT_ALL
        if h_thread is None:
            h_thread = self.open_thread(thread_id)
        if kernel32.GetThreadContext(h_thread, byref(context)):
            kernel32.CloseHandle(h_thread)
            return context
        else:
            return False

以上で、スレッドの取得とレジスタの捕捉が出来ました。

まとめ

・スレッドは、プロセス内の最小実行単位
・CreateToolhelp32Snapshot関数でスナップショットを取得すれば、スレッドリスト以外にもモジュールリストなど興味深いものも参照できる
・スレッドリストを列挙するには、Thread32First関数とThread32Next関数を用いる
・スレッドのハンドル取得は、OpenThread関数を用いる
・レジスタ値取得にはGetThreadContext関数、レジスタ値設定にはSetThreadContext関数を用いる

番外編

CreateToolhelp32Snapshotで取得できる情報にモジュールリストがありました。
ちょっと興味があるので、モジュールリストも列挙してみます。

モジュールリスト列挙「Module32First」・「Module32Next」

モジュールリスト列挙にはWindows API関数「Module32First」・「Module32Next」を使用します。
「Module32First」・「Module32Next」仕様は以下です。

BOOL WINAPI Module32First(
  HANDLE hSnapshot,
  LPMODULEENTRY32 lpme
);

BOOL WINAPI Module32Next(
HANDLE hSnapshot,

LPMODULEENTRY32 lpme
);

スレッドリストとほとんど同じですね。
引数のhSnapshotはCreateToolhelp32Snapshotで取得できるスナップショットで、
lpmeはMODULEENTRY32構造体です。

MODULEENTRY32構造体の仕様は以下です。

typedef struct tagMODULEENTRY32 {
  DWORD dwSize;
  DWORD th32ModuleID;
  DWORD th32ProcessID;
  DWORD GlblcntUsage;
  DWORD ProccntUsage;
  BYTE *modBaseAddr;
  DWORD modBaseSize;
  HMODULE hModule;
  TCHAR szModule[MAX_MODULE_NAME32 + 1];
  TCHAR szExePath[MAX_PATH];
} MODULEENTRY32, *PMODULEENTRY32;

dwSizeはこの構造体のサイズです。使用する際は、先にdwSizeを設定しておく必要があります。
th32ModuleIDは使われていないみたいで常に1が設定されます。
th32ProcessIDはプロセスIDです。
GlblcntUsage, ProccntUsageはあまり意味がない値らしいです。
modBaseAddrはモジュールのベースアドレスです。
modBaseSizeはモジュールのサイズです。
hModuleはモジュールのハンドルです。
szModuleはモジュールの名前です。
szExePathはモジュールのパスです。

なかなか興味深いですね。

では、モジュールリストを列挙してみます。

defines.py
class MODULEENTRY32(Structure):
    _fields_ = [
        ("dwSize",             DWORD),
        ("th32ModuleID",       DWORD),
        ("th32ProcessID",      DWORD),
        ("GlblcntUsage",       DWORD),
        ("ProccntUsage",       DWORD),
        ("modBaseAddr",        POINTER(BYTE)),
        ("modBaseSize",        DWORD),
        ("hModule",            HMODULE),
        ("szModule",           TCHAR*256),
        ("szExePath",          TCHAR*260),
    ]
test_modulelist.py
from ctypes  import *
from defines import *

kernel32 = windll.kernel32

pid = input("pid: ")

snapshot = kernel32.CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, int(pid))
lpme = MODULEENTRY32()
lpme.dwSize = sizeof(lpme)
res = kernel32.Module32First(snapshot, byref(lpme))
while res:
    if lpme.th32ProcessID==int(pid):
        print("PID:         ", lpme.th32ProcessID)
        print("MID:         ", lpme.th32ModuleID)
        print("MODULE_NAME: ", lpme.szModule)
        print("MODULE_PATH: ", lpme.szExePath)
    res = kernel32.Module32Next(snapshot, byref(lpme))

ちゃんと取得できました。
モジュールリストに関しては、指定したプロセスIDのものしか取れませんでした。
また、モジュールIDに関してはやはり常に1を指し示していました。

デバッグ特権を取得

教科書とは別に『デバッガによるx86プログラム解析入門【x64対応版】』を読みました。
(この本もかなり分かりやすく書いており非常に参考になりました!以降「教科書2」と呼びます)
ここには、デバッグ特権を取得することでWindowsから制約を受けることなくデバッグすることが出来るようになるらしいです。本編でRIPやRSPなど一部レジスタ値が取得できなかったのはデバッグ特権を取得しなかったせいかなと思い、試してみることにします。

デバッグ特権を取得するために使用するWindows API関数は「AdjustTokenPrivileges」です。
仕様は以下です。
AdjustTokenPrivileges

BOOL AdjustTokenPrivileges(
  HANDLE TokenHandle, // 特権を保持するトークンのハンドル
  BOOL DisableAllPrivileges,
// すべての特権を無効にするためのフラグ
  PTOKEN_PRIVILEGES NewState,
// 新しい特権情報へのポインタ
  DWORD BufferLength, // PreviousState バッファのバイト単位のサイズ
  PTOKEN_PRIVILEGES PreviousState,
// 変更を加えられた特権の元の状態を受け取る
  PDWORD ReturnLength // PreviousState バッファが必要とするサイズを受け取る
);

引数のTokenHandleは、必要な権限を保持したそのプロセスのトークンのハンドルです。
DisableAllPrivilegesは、すべての特権を無効にするためのフラグです。
NewStateはTOKEN_PRIVILEGES構造体です。
BufferLengthは、PreviousStateが指すバッファのサイズです。
PreviousStateは、変更前の状態を保持したい場合は、TOKEN_PRIVILEGES構造体のポインタを指定します。
ReturnRengthは、PreviousStateが受け取るのに必要なバッファサイズが格納されるのだと思います。

TOKEN_PRIVILEGE構造体は以下です。
MSDN-TOKEN_PRIVILEGE

typedef struct _TOKEN_PRIVILEGES {
  DWORD PrivilegeCount;
  LUID_AND_ATTRIBUTES Privileges[ANYSIZE_ARRAY];
} TOKEN_PRIVILEGES, *PTOKEN_PRIVILEGES;

PrivilegeCountは、Privileges配列のエントリ数をあらかじめ設定しておく必要があるみたいです。
Privilegesは、LUID_AND_ATTRIBUTES構造体の配列です。ANYSIZE_ARRAYはwinnt.hで1が設定されています。

LUID_AND_ATTRIBUTES構造体は以下です。
MSDN-LUID_AND_ATTRIBUTES

typedef struct _LUID_AND_ATTRIBUTES {
  LUID Luid;
  DWORD Attributes;
} LUID_AND_ATTRIBUTES, *PLUID_AND_ATTRIBUTES;

Luidは、LUID構造体です。
Atrributesは、そのLuidの属性を指定するみたいです。

LUID構造体の仕様は以下です。
MSDN-LUID

typedef struct _LUID {
  DWORD LowPart;
  LONG HighPart;
} LUID, *PLUID;

LowPartは下位ビットで、HighPartは上位ビットらしいです。

Luidを取得するために使用するWindows API関数は「LookupPrivilegeValue」です。
仕様は以下です。
LookupPrivilegeValue

BOOL LookupPrivilegeValue(
  LPCTSTR lpSystemName,
// システムを指定する文字列のアドレス
  LPCTSTR lpName, // 特権を指定する文字列のアドレス
  PLUID lpLuid // ローカル一意識別子のアドレス
);

デバッグ特権を取得するプロセスのトークンを取得する必要があります。
そのために使用するWindows API関数は「OpenProcessToken」です。

MSDN-OpenProcessToken

BOOL OpenProcessToken(
  HANDLE ProcessHandle, // プロセスのハンドル
  DWORD DesiredAccess, // プロセスに対して希望するアクセス権
  PHANDLE TokenHandle // 開かれたアクセストークンのハンドルへのポインタ
);

ProcessHandleは特権を取得するプロセスのハンドルです。
DesiredAceessは希望するアクセス権のフラグを指定します。
そして、TokenHandleには、そのプロセスのトークンが格納されます。

では、デバッグ特権を取得してみます。

defines.py
class LUID(Structure):
    _fields_ = [
        ("LowPart",  DWORD),
        ("HighPart", LONG),
    ]

class LUID_AND_ATTRIBUTES(Structure):
    _fields_ = [
        ("Luid",       LUID),
        ("Attributes", DWORD),
    ]

class TOKEN_PRIVILEGES(Structure):
    _fields_ = [
        ("PrivilegeCount", DWORD),
        ("Privileges",     LUID_AND_ATTRIBUTES),
    ]
test_debug_privilege.py
from ctypes  import *
from ctypes  import wintypes
from defines import *

kernel32 = windll.kernel32
advapi32 = windll.advapi32

kernel32.GetCurrentProcess.argtypes = []
kernel32.GetCurrentProcess.restype  = wintypes.HANDLE

kernel32.OpenProcessToken.argtypes  = (wintypes.HANDLE,
                                      DWORD64,
                                      POINTER(wintypes.HANDLE))
kernel32.OpenProcessToken.restype   = wintypes.BOOL

# advapi32.LookupPrivilegeValueW.argtypes = (
#                                           wintypes.LPWSTR,
#                                           wintypes.LPWSTR,
#                                           POINTER(LUID))
# advapi32.LookupPrivilegeValueW.restype  = wintypes.BOOL

def get_token():
    token  = wintypes.HANDLE()
    handle = kernel32.GetCurrentProcess()
    if not kernel32.OpenProcessToken(handle, TOKEN_ALL_ACCESS, token):
        return False
    return token

def get_luid():
    luid = LUID()
    if not advapi32.LookupPrivilegeValueW(None, "seDebugPrivilege", byref(luid)):
        return False
    return luid

def enable_privilege(token, luid):
    l_and_a            = LUID_AND_ATTRIBUTES()
    l_and_a.Luid       = luid
    l_and_a.Attributes = SE_PRIVILEGE_ENABLED
    tp                 = TOKEN_PRIVILEGES()
    tp.PrivilegeCount  = 1
    tp.Privileges[0]   = l_and_a
    if not advapi32.AdjustTokenPrivileges(token, False, tp, 0, 0, 0):
        return False
    return True


def set_debug_privilege():
    token = get_token()
    if not token:
        print(WinError(GetLastError()))
        exit()
    luid = get_luid()
    if not luid:
        print(WinError(GetLastError()))
        exit()
    enable_privilege(token, luid)
    print(WinError(GetLastError()))

if __name__ == "__main__":
    set_debug_privilege()

GetCurrentProcess関数で、現在のプロセスハンドルを返してくれます。
このとき、64bitでもctyepsではintを返すので正しい型を指定してあげる必要があるみたいです。
同様にOpenProcessTokenも型を指定してあげる必要がありました。
ちなみに、コマンドプロンプトでユーザ実行の場合は特権が正しく得られませんが、管理者実行の場合は特権が正しく得られます。

各エラー時参考になったページ:
https://stackoverflow.com/questions/11669335/how-to-get-privateusage-memory-value-from-python-on-win7x64
https://svn.python.org/projects/python/branches/pep-0384/Lib/test/symlink_support.py

そして、デバッグ特権を有効にして、レジスタ値を取得してみたところ、結果は同じでした。
やはり、RIPやRSPなどが取れないままでした。引き続き原因を探っていきたいと思います。
また、分かる方がおられましたらコメントいただけると嬉しいです。

(Update:2018/05/30) 正しく取得できました。
詳しくは本編(レジスタ値の取得・変更「GetThreadContext」・「SetThreadContext」)参照。

今後はこのデバッグ特権は有効にして進めたいと思います。

以上、番外編でした!

6
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
0