Twitterに書ききれないこと

イベントや技術的なことを記したい・・・

Windows Anti-Debug Reference まとめ その1

SymantecのページにWindowsのアンチデバッグ技術について、いい感じにまとまったページがある。勉強のために、そのページを訳してまとめたり、追加で調べた。間違いがないように注意しているけれど間違っているかも・・・

www.symantec.com

Exploiting memory discrepancies

1. kernel32!IsDebuggerPresent

プロセスがデバッグされていると、IsDebuggerPresentは1を返す。そうでない場合は0を返す。このAPIは、PEB.BeingDebugged(PEB構造体のオフセット0x002の位置)を読み取っているだけである。PEB.BeingDebuggedを0に設定すれば簡単に回避できる。

call IsDebuggerPresent
test eax, eax
jne @DebuggerDetected
2. PEB!IsDebugged

PEB関連のアンチデバッグの話にPEB・TEBの構造の説明を追加しておく。 PEB構造体は、32bit環境ではfs:[30h]に配置されている。fs:[0h]はTEB構造体の先頭を指している。 64bit環境では、fsではなくgsを用いてgs:[60h]となる。

TEB構造体は以下のようになっている。

typedef struct _TEB
{                                                                 /* win32/win64 */
    NT_TIB                       Tib;                               /* 000/0000 */
    PVOID                        EnvironmentPointer;                /* 01c/0038 */
    CLIENT_ID                    ClientId;                          /* 020/0040 */
    PVOID                        ActiveRpcHandle;                   /* 028/0050 */
    PVOID                        ThreadLocalStoragePointer;         /* 02c/0058 */
    PPEB                         Peb;                               /* 030/0060 */
...

NT_TIB構造体は以下のようになっている。fs:[18h]/gs:[30h]とすることでNT_TIBのアドレスを取得することができる。

struct _NT_TIB {              //  x86  /  x64
  void *ExceptionList;        // 0x000 / 0x000
  void *StackBase;            // 0x004 / 0x008
  void *StackLimit;           // 0x008 / 0x010
  void *SubSystemTib;         // 0x00c / 0x018
  union {
    void *FiberData;          // 0x010 / 0x020
    uint32_t Version;         // 0x010 / 0x020
  };
  void *ArbitraryUserPointer; // 0x014 / 0x028
  struct _NT_TIB *Self;       // 0x018 / 0x030
};

よってPEBのアドレスを取得するコードは次のように書ける。

mov    eax, large fs:30h

        or

mov    eax, large fs:18h
mov    eax, [eax+30]   

ここから本題 前述の通りPEBのオフセット0x002バイトにはBeingDebuggedがある。 IsDebuggerPresentとは、APIを使うか直接値を参照するかの違い。 プロセスがデバッグされているとき、システムによって1が設定されている。 このバイトを変更してもプログラムの実行過程にも影響することがないので、0にすることでデバッグ検知を回避する。

mov eax, fs:[30h]
mov eax, byte [eax+2]
test eax, eax
jne @DebuggerDetected
3. PEB!NtGlobalFlags

プロセスが作成されると、システムがいくつかのフラグを設定する。フラグはプログラムがコールするAPIがどのように動作するかを定義している。

NtGlobalFlagsは、デバッグされていない場合は0、デバッグされている場合は0x70となる。 また、プロセスがデバッグされている場合は、NTDLLでヒープ操作ルーチンを制御するいくつかのフラグが設定される。 0x70となるフラグの組み合わせは以下の3つである。

  • FLG_HEAP_ENABLE_TAIL_CHECK (0x10)
  • FLG_HEAP_ENABLE_FREE_CHECK (0x20)
  • FLG_HEAP_VALIDATE_PARAMETERS (0x40)

これらのフラグはPEBのオフセット0x068から読み取ることができる。 PEB構造体は以下のようになっている。

typedef struct _PEB
{                                                                 /* win32/win64 */
    BOOLEAN                      InheritedAddressSpace;             /* 000/000 */
    BOOLEAN                      ReadImageFileExecOptions;          /* 001/001 */
    BOOLEAN                      BeingDebugged;                     /* 002/002 */
    BOOLEAN                      SpareBool;                         /* 003/003 */
    HANDLE                       Mutant;                            /* 004/008 */
    HMODULE                      ImageBaseAddress;                  /* 008/010 */
    PPEB_LDR_DATA                LdrData;                           /* 00c/018 */
    RTL_USER_PROCESS_PARAMETERS *ProcessParameters;                 /* 010/020 */
    PVOID                        SubSystemData;                     /* 014/028 */
    HANDLE                       ProcessHeap;                       /* 018/030 */
    PRTL_CRITICAL_SECTION        FastPebLock;                       /* 01c/038 */
    PVOID /*PPEBLOCKROUTINE*/    FastPebLockRoutine;                /* 020/040 */
    PVOID /*PPEBLOCKROUTINE*/    FastPebUnlockRoutine;              /* 024/048 */
    ULONG                        EnvironmentUpdateCount;            /* 028/050 */
    PVOID                        KernelCallbackTable;               /* 02c/058 */
    ULONG                        Reserved[2];                       /* 030/060 */
    PVOID /*PPEB_FREE_BLOCK*/    FreeList;                          /* 038/068 */
    ULONG                        TlsExpansionCounter;               /* 03c/070 */
    PRTL_BITMAP                  TlsBitmap;                         /* 040/078 */
    ULONG                        TlsBitmapBits[2];                  /* 044/080 */
    PVOID                        ReadOnlySharedMemoryBase;          /* 04c/088 */
    PVOID                        ReadOnlySharedMemoryHeap;          /* 050/090 */
    PVOID                       *ReadOnlyStaticServerData;          /* 054/098 */
    PVOID                        AnsiCodePageData;                  /* 058/0a0 */
    PVOID                        OemCodePageData;                   /* 05c/0a8 */
    PVOID                        UnicodeCaseTableData;              /* 060/0b0 */
    ULONG                        NumberOfProcessors;                /* 064/0b8 */
    ULONG                        NtGlobalFlag;                      /* 068/0bc */

このアンチデバッグはNtGlobalFlagsフィールドをリセットすることで回避することができる。

mov eax, fs:[30h]
mov eax, [eax+68h]
and eax, 0x70
test eax, eax
jne @DebuggerDetected
4. Heap flags

前述のように、NtGlobalFlagsはヒープ·ルーチンがどのように動作するかを表す。 ヒープが生成されるとNtGlobalFlagsの値がセットされる。 ヒープ生成時にはいくつかのフラグがセットされる。

Heap flagsを使ったアンチデバッグ方法も基本的にBeingDebuggedとNtGlobalFlagsと同じである。

PEB構造体のProcessHeapから対応する構造体のアドレスを取得し,Flags、ForceFlagsの値をチェックすることでデバッグを検知する。ヒープ生成時Flagsは0x02(HEAP_GROWABLE)、ForceFlagsは0になっている。デバッグされていると0x50000062と0x40000060になる。

32bitだとHEAP構造体は以下のようになっている

ntdll!_HEAP
   +0x000 Entry            : _HEAP_ENTRY
   +0x008 Signature : Uint4B
   +0x00c Flags            : Uint4B
   +0x010 ForceFlags       : Uint4B
・・・

64bitだとオフセットが変わっているので注意。

ntdll!_HEAP
   +0x000 Segment          : _HEAP_SEGMENT
   +0x000 Entry            : _HEAP_ENTRY
   +0x010 SegmentSignature : Uint4B
   +0x014 SegmentFlags     : Uint4B
   +0x018 SegmentListEntry : _LIST_ENTRY
   +0x028 Heap             : Ptr64 _HEAP
   +0x030 BaseAddress      : Ptr64 Void
   +0x038 NumberOfPages    : Uint4B
   +0x040 FirstEntry       : Ptr64 _HEAP_ENTRY
   +0x048 LastValidEntry   : Ptr64 _HEAP_ENTRY
   +0x050 NumberOfUnCommittedPages : Uint4B
   +0x054 NumberOfUnCommittedRanges : Uint4B
   +0x058 SegmentAllocatorBackTraceIndex : Uint2B
   +0x05a Reserved         : Uint2B
   +0x060 UCRSegmentList   : _LIST_ENTRY
   +0x070 Flags            : Uint4B
   +0x074 ForceFlags       : Uint4B

上記を踏まえて、Flags、ForceFlagsの値をチェックしたアンチデバッグは以下のように書ける。

mov eax, fs:[30h]
mov eax, [eax+18h] ;process heap
mov eax, [eax+0ch] 
cmp eax, 2
jne @DebuggerDetected

    or

mov eax, fs:[30h]
mov eax, [eax+18h] ;process heap
mov eax, [eax+10h] ;heap flags
test eax, eax
jne @DebuggerDetected
5. Vista anti-debug (no name)

これはデバッグされていないプログラムのメモリダンプを、比較することによって見つかったWindows Vistaのアンチデバッグ方法である。

プロセスがデバッグされている場合、TEBのオフセット0xBFCでシステムDLLを参照するUnicode文字列へのポインタが含まれる。また文字列はこのポインタをたどる。(したがって、TEBのオフセット0xC00に位置する)

プロセスがデバッグされていない場合は、ポインタがNULLに設定され、文字列が存在しない。

call GetVersion
cmp al, 6
jne @NotVista
push offset _seh
push dword fs:[0]
mov fs:[0], esp
mov eax, fs:[18h] ; teb
add eax, 0BFCh
mov ebx, [eax] ; pointer to a unicode string
test ebx, ebx ; (ntdll.dll, gdi32.dll,...)
je @DebuggerNotFound
sub ebx, eax ; the unicode string follows the
sub ebx, 4 ; pointer
jne @DebuggerNotFound
;debugger detected if it reaches this point
;...

今回はここまで。

参考文献

wine/winternl.h at master · wine-mirror/wine · GitHub

デバッガ検出技術:PEB.NtGlobalFlag, Heap Flags - Log.i53