読者です 読者をやめる 読者になる 読者になる

Twitterに書ききれないこと

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

Windows Anti-Debug Reference まとめ その2

前回からの続き

Windows Anti-Debug Reference まとめ その1 - Twitterに書ききれないこと

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

www.symantec.com

Exploiting system discrepancies

1. NtQueryInformationProcess

NTDLL!NtQueryInformationProcessはZwQueryInformationProcessシステムコールのラッパーである。 プロトタイプは以下のようになっている。

NTSTATUS WINAPI NtQueryInformationProcess(
  _In_      HANDLE           ProcessHandle,
  _In_      PROCESSINFOCLASS ProcessInformationClass,
  _Out_     PVOID            ProcessInformation,
  _In_      ULONG            ProcessInformationLength,
  _Out_opt_ PULONG           ReturnLength
);

第二引数のProcessInformationClassに7をセットする。ProcessInformationClassが7の場合、ProcessDebugPortを意味する。この値はプロセスのデバッガのポート番号で,0以外の値の場合は、プロセスがリング3デバッガで実行されていることを示す。

NtQueryInformationProcessを呼び出したとき、デバッグされているとProcessInformationは-1となる。このアンチデバッグは強力で簡単に回避する方法はない。 しかし、プログラムがトレースされている場合は、システムコールのリターン時にProcessInformationを変更することができる。

別の解決策として、ZwNtQueryInformationProcessシステムコールをフックするシステムドライバを使用する方法がある。 NtQueryInformationProcessの回避は、多くのアンチデバッグ技術を回避する(例えば、CheckRemoteDebuggerPresentやUnhandledExceptionFilterなど)。

push 0
push 4
push offset isdebugged
push 7 ;ProcessDebugPort
push -1
call NtQueryInformationProcess
test eax, eax
jne @ExitError
cmp isdebugged, 0
jne @DebuggerDetected
2. kernel32!CheckRemoteDebuggerPresent

このAPIは、プロセスのハンドルデバッグフラグであるpbDebuggerPresentの2つの引数を取る。 プロセスがデバッグされているとpbDebuggerPresentはTRUE、そうでない場合はFALSEとなる。

BOOL WINAPI CheckRemoteDebuggerPresent(
  _In_    HANDLE hProcess,
  _Inout_ PBOOL  pbDebuggerPresent
);

内部的には、NtQueryInformationProcessと同様でProcessInformationClassにProcessDebugPortを設定する。

push offset isdebugged
push -1
call CheckRemoteDebuggerPresent
test eax, eax
jne @DebuggerDetected
3. UnhandledExceptionFilter

Windows XP SP>=2・Windows 2003・Windows Vistaで例外が発生したとき、通常はOSが以下のように例外を処理する。

  • いずれかの場合に、プロセスごとのベクトル化例外処理に制御を渡す。

  • 例外が処理されない場合、例外を生成したスレッドのFS[0]が指し示すSEHハンドラの先頭に制御を渡す。 FS[0]はTEB->NT_TIB.ExceptionListを指す。これは、EXCEPTION_REGISTRATION_RECORD構造体のリスト構造の先頭を指し示している。Windowsではこのリストに例外処理を追加することで例外処理を行う。この連鎖的に呼び出される例外処理のリストをSEHチェーンと呼ぶ。

  • いずれかのSEHハンドラも当てはまらず、例外が処理されていない場合は、kernel32!UnhandledExceptionFilterを呼び出す。

  • この関数は、デバッグされていない場合、ユーザ定義された(kernel32!SetUnhandledExceptionFilterでセットした)フィルタ関数を呼び出す。 デバッグされていた場合、プロセスを終了する。

push @not_debugged
call SetUnhandledExceptionFilter
xor eax, eax
mov eax, dword [eax] ; trigger exception
;program terminated if debugged
;...
@not_debugged:
;process the exception
;continue the execution
4. UnhandledExceptionFilter

NTDLL!NtSetInformationThreadはZwSetInformationThreadシステムコールのラッパーとなっている。プロトタイプは以下のようになっている

NTSTATUS ZwSetInformationThread(
  _In_ HANDLE          ThreadHandle,
  _In_ THREADINFOCLASS ThreadInformationClass,
  _In_ PVOID           ThreadInformation,
  _In_ ULONG           ThreadInformationLength
);

ThreadInformationClassは以下のようになっている。 この関数はスレッドの優先度を設定する関数だが、0x11をセットして呼び出された場合、スレッドはデバッガから切り離される。 ZwQueryInformationProcessを呼び出した場合も同様となる。

typedef enum _THREAD_INFORMATION_CLASS {
ThreadBasicInformation,                0x00 
ThreadTimes,                           0x01
ThreadPriority,                        0x02
ThreadBasePriority,                    0x03
ThreadAffinityMask,                    0x04
ThreadImpersonationToken,              0x05
ThreadDescriptorTableEntry,            0x06
ThreadEnableAlignmentFaultFixup,       0x07
ThreadEventPair,                       0x08
ThreadQuerySetWin32StartAddress,       0x09
ThreadZeroTlsCell,                     0x0A
ThreadPerformanceCount,                0x0B
ThreadAmILastThread,                   0x0C
ThreadIdealProcessor,                  0x0D
ThreadPriorityBoost,                   0x0E
ThreadSetTlsArrayAddress,              0x0F
ThreadIsIoPending,                     0x10
ThreadHideFromDebugger                 0x11
} THREAD_INFORMATION_CLASS, *PTHREAD_INFORMATION_CLASS;

このアンチデバッグを回避するためには、いずれかのメソッドが呼び出される前に、ZwSetInformationThreadの引数を変更する必要がある。 またはカーネルドライバを使用して、直接システムコールをフックすることで回避する。

push 0
push 0
push 11h ;ThreadHideFromDebugger
push -2
call NtSetInformationThread
;thread detached if debugged
5. kernel32!CloseHandle and NtClose

ZwCloseシステムコールを使用するAPIは、デバッガを検出することができる。

プロセスがデバッグされている場合、ZwCloseシステムコールに無効なハンドルを渡して呼び出すと、STATUS_INVALID_HANDLE(0xC0000008)を例外として生成する。

カーネルから直接利用できる情報(システムコールを含む)に依存しているすべてのアンチデバッグと同様に、 このアンチデバッグを回避する方法は、呼び出し前かカーネルフックの設定前にリング3のシステムコールデータを変更するしかない。

このアンチデバッグは、非常に強力なものであるが、マルウェアでは広く使用されていないようである。

push offset @not_debugged
push dword fs:[0]
mov fs:[0], esp
push 1234h ;invalid handle
call CloseHandle
; if fall here, process is debugged
;...
@not_debugged:
6. Self-debugging

プロセスは、自分自身をデバッグしようとすることで、デバッグされていることを検知できる。 新しいプロセスを作成し、親プロセスにkernel32!DebugActiveProcess(pid)を呼び出すことで検知できる。

次に、このAPIはZwDebugActiveProcessをラップしたntdll!DbgUiDebugActiveProcessを呼び出す。

プロセスがデバッグされている場合は、システムコールは失敗する。

親プロセスのPIDの取得は、toolhelp32 APIを用いて行うことができることに注意しなければならない。 (PROCESSENTRY32構造内のth32ParentProcessIDフィールド)

7. Kernel-mode timers

kernel32!QueryPerformanceCounterは効率的なアンチデバッグである。 このAPIは、ZwQueryPerformanceCounterをラップしたntdll!NtQueryPerformanceCounterを呼び出す。 また、このアンチトレーストリックを回避する簡単な方法はない。

基本的な考えは単純で、通常の実行時と比べてデバッグしている場合は実行時間が遅くなるので、それを検出するというものである。

8. User-mode timers

kernel32!GetTickCountは、システムが起動してからの経過時間をミリ秒単位で返すAPIである。 興味深いのは、この作業の実行ために、カーネル関連のサービスを利用していないということである。

ユーザモードプロセスは、ユーザアドレス空間マッピングされ、このカウンタを保持している。 8GBのユーザモード空間の場合、値は次のようになる。

D [0x7FFE0000] * D[0x7FFE0004]/(^242)

こちらも7.と同じ考えである。他にもtimeGetTimeなどが使える。

9. kernel32!OutputDebugStringA

このアンチデバッグは、非常にオリジナルである。 (元記事の筆者は、ReCrypt v0.80でパックされたファイルで一度だけ見た)

仕組みは、有効なASCII文字列を引数に、OutputDebugStringAを呼び出すというものである。

通常の状態では戻り値は1であるが、プログラムがデバッガで実行されている場合、戻り値は引数として渡された文字列のアドレスになる。 これを利用してアンチデバッグを実装することができる。

また、OllyDbgでのみ使えるアンチデバッグ方法もある。 OllyDbgでは、OutputDebugStringに正常でない文字列を引数として渡すとクラッシュしてしまうバグがある。 しかし、解析にはこのバグを修正したOllyDbgが使われることがほとんどである。

xor eax, eax
push offset szHello
call OutputDebugStringA
cmp eax, 1
jne @DebuggerDetected
10. Ctrl-C

コンソールプログラムが、デバッグされていなければシグナルハンドラが直接呼び出される。 対して、デバッグされているとき、例外としてEXCEPTION_CTL_Cが投げられる。

push offset exhandler
push 1
call RtlAddVectoredExceptionHandler
push 1
push sighandler
call SetConsoleCtrlHandler
push 0
push CTRL_C_EVENT
call GenerateConsoleCtrlEvent
push 10000
call Sleep
push 0
call ExitProcess
exhandler:
;check if EXCEPTION_CTL_C, if it is,
;debugger detected, should exit process
;...
sighandler:
;continue

今回はここまで

参考文献

http://www.ffri.jp/assets/files/research/research_papers/SEH_Overwrite.pdf

http://wizardbible.org/33/33.txt

ZwSetInformationThread - Change Start Address - Sysinternals Forums