간혹 악성 드라이버를 분석할 일이 있는데 이 녀석의 시작점인 DriverEntry부터 보고 싶은 경우 어찌할 바를 몰라 고민하는 경우가 있었습니다.
그러다 생각한 것이 커널 내부함수인 IopLoadDriver로 부터 따라가서 들어가는 방법인데요. 노가다가 좀 심합니다. 그래도 정리해 보면 좋을 것 같아서 포스팅해 봅니다. (누가 좀 효율적인 방법 좀 알면 알려주세요. -_-a)
XP SP2 기준이구요 예제로 Null 드라이버를 로드해 봤습니다.
먼저 IopLoadDriver에 브레이크 포인트를 겁니다.
kd> bp nt!IopLoadDriver
kd> bl
0 e 805a5ba3 0001 (0001) nt!IopLoadDriver
g로 실행하고 나서 OsrLoader 같은 것으로 Null 드라이버를 로드합니다. 그러면 드라이버를 로드하는 과정에서 브레이크 포인트가 걸립니다.
Breakpoint 0 hit
nt!IopLoadDriver:
805a5ba3 8bff mov edi,edi
kd> k
ChildEBP RetAddr
f9e93d4c 805a627c nt!IopLoadDriver
f9e93d74 804e626b nt!IopLoadUnloadDriver+0x45
f9e93dac 8057f16b nt!ExpWorkerThread+0x100
f9e93ddc 804fa27a nt!PspSystemThreadStartup+0x34
00000000 00000000 nt!KiThreadStartup+0x16
콜스택을 보면 시스템에서 드라이버를 로드하는 과정임을 볼 수 있습니다. IopLoadDriver 시작위치에서 멈췄네요.
이제 새로운 명령어인 pc(Step to Next Call)을 사용해 볼껀데요.
이 명령을 사용하면 현재 실행주소로부터 다음 call을 만날 때까지 쭉~ 실행됩니다.
다음 call에서 멈추지요. 자... 이제 pc를 수행합니다.
kd> pc
nt!IopLoadDriver+0x4b:
805a5bee e836b0fcff call nt!NtQueryKey (80570c29)
NtQueryKey를 호출하는 부분에서 멈췄네요.
앞뒤의 코드를 모두 보이면 아래와 같습니다.
805a5bde 66895d9a mov word ptr [ebp-66h],bx
805a5be2 895d9c mov dword ptr [ebp-64h],ebx
805a5be5 899d78ffffff mov dword ptr [ebp-88h],ebx
805a5beb 895da8 mov dword ptr [ebp-58h],ebx
805a5bee e836b0fcff call nt!NtQueryKey (80570c29)
805a5bf3 3d05000080 cmp eax,80000005h
805a5bf8 740b je nt!IopLoadDriver+0x6a (805a5c05)
805a5bfa 3d230000c0 cmp eax,0C0000023h
805a5bff 0f856d330400 jne nt!IopLoadDriver+0x5e (805e8f72)
코드에 특이사항이 없으므로 다시 pc를 실행하여 다음 call까지 실행하게 합니다.
kd> pc
nt!IopLoadDriver+0x78:
805a5c13 e8ac6ffaff call nt!ExAllocatePoolWithTag (8054cbc4)
메모리를 할당하는 부분이네요. 원하는 부분이 아니므로 또 pc를 합니다.
엔터만 쳐서 이전 명령을 그대로 수행하는 WinDbg의 기능을 사용해 보죠.
kd>
nt!IopLoadDriver+0x99:
805a5c34 e8f0affcff call nt!NtQueryKey (80570c29)
kd>
nt!IopLoadDriver+0xca:
805a5c65 e85a6ffaff call nt!ExAllocatePoolWithTag (8054cbc4)
kd>
nt!IopLoadDriver+0x114:
805a5caf e8cd46f5ff call nt!RtlAppendUnicodeToString (804fa381)
kd>
nt!IopLoadDriver+0x11f:
805a5cba e8d75af6ff call nt!HeadlessKernelAddLogEntry (8050b796)
kd>
nt!IopLoadDriver+0x1e6:
805a5cd8 e8c956f3ff call nt!ExAcquireResourceSharedLite (804db3a6)
kd>
nt!IopLoadDriver+0x1fd:
805a5cf5 e8ac26f6ff call nt!RtlEqualString (805083a6)
kd>
nt!IopLoadDriver+0x1fd:
805a5cf5 e8ac26f6ff call nt!RtlEqualString (805083a6)
관심있는 부분까지 계속 진행해야 하는데 이 쯤에서 RtlEqualString이 반복됩니다.
루프인 것 같아서 뒤부분의 코드를 살펴 봅니다.
805a5cf5 e8ac26f6ff call nt!RtlEqualString (805083a6)805a5cfa 84c0 test al,al
805a5cfc 0f8531330400 jne nt!IopLoadDriver+0x23a (805e9033)
805a5d02 8b3f mov edi,dword ptr [edi]
805a5d04 ebdd jmp nt!IopLoadDriver+0x208 (805a5ce3)
805a5d06 2e005300 add byte ptr cs:[ebx],dl
805a5d0a 59 pop ecx
805a5d0b 005300 add byte ptr [ebx],dl
805a5d0e 0000 add byte ptr [eax],al
805a5d10 0000 add byte ptr [eax],al
805a5d12 8bce mov ecx,esi
805a5d14 e83779f3ff call nt!ExReleaseResourceLite (804dd650)
루프 바깥 부분에 브레이크 포인트를 걸어주고 그냥 go를 해버려서 루프를 빠져나가려고 합니다.
루프 바깥에 브레이크 포인트를 걸 지점을 찾아야 하는데 앞에서 call 한 흐름을 보면 다행히 RtlEqualString이 반복되기 직전에
ExAcquireResourceSharedLite를 호출한 것이 보이네요.
그러므로 ExReleaseResourceLite에 걸어주면 적당해 보입니다.
kd> bp 805a5d14
kd> bl
0 e 805a5ba3 0001 (0001) nt!IopLoadDriver
1 e 805a5d14 0001 (0001) nt!IopLoadDriver+0x212
kd> g
Breakpoint 1 hit
nt!IopLoadDriver+0x212:
805a5d14 e83779f3ff call nt!ExReleaseResourceLite (804dd650)
루프를 빠져나오면서 브레이크 포인트가 걸렸습니다.
이제부터는 다시 call을 확인하면서 진행합니다.
kd> pc
nt!IopLoadDriver+0x222:
805a5d24 e8dcfbffff call nt!IopBuildFullDriverPath (805a5905)
kd>
nt!IopLoadDriver+0x31a:
805a5d3e e806fdffff call nt!IopGetDriverNameFromKeyNode (805a5a49)
kd>
nt!IopLoadDriver+0x36b:
805a5d8f e8dff4ffff call nt!MmLoadSystemImage (805a5273)
kd>
nt!IopLoadDriver+0x428:
805a5da2 e82e4af5ff call nt!RtlImageNtHeader (804fa7d5)
kd>
nt!IopLoadDriver+0x43a:
805a5db4 e829200000 call nt!IopPrepareDriverLoading (805a7de2)
kd>
nt!IopLoadDriver+0x47f:
805a5df0 e8590bfcff call nt!ObCreateObject (8056694e)
kd>
nt!IopLoadDriver+0x4c7:
805a5e3c e89449f5ff call nt!RtlImageNtHeader (804fa7d5)
kd>
nt!IopLoadDriver+0x4fb:
805a5e70 e8ae05fcff call nt!ObInsertObject (80566423)
kd>
nt!IopLoadDriver+0x536:
805a5ea0 e84301fcff call nt!ObReferenceObjectByHandle (80565fe8)
kd>
nt!IopLoadDriver+0x547:
805a5eb1 e8032ffcff call nt!NtClose (80568db9)
kd>
nt!IopLoadDriver+0x563:
805a5ecd e8f26cfaff call nt!ExAllocatePoolWithTag (8054cbc4)
kd>
nt!IopLoadDriver+0x5b1:
805a5f1b e8a46cfaff call nt!ExAllocatePoolWithTag (8054cbc4)
kd>
nt!IopLoadDriver+0x5e8:
805a5f3d e8fed7fdff call nt!NtQueryObject (80583740)
kd>
nt!IopLoadDriver+0x61e:
805a5f59 e8666cfaff call nt!ExAllocatePoolWithTag (8054cbc4)
kd>
nt!IopLoadDriver+0x669:
805a5fa4 ff572c call dword ptr [edi+2Ch]
뭔가 로드되고 있는 것처럼 보이지 않나요? ^^
마지막 call이 제가 찾던 부분입니다. 어떤 함수주소를 호출하는게 보이죠?
저는 이것이 로드될 드라이버의 DriverEntry이기를 바라고 있죠.
앞쪽 코드를 보면 push 두번이 보이므로 2개의 파라미터를 가진다는 것을 알 수 있습니다.
805a5f9a 8b7d80 mov edi,dword ptr [ebp-80h]
805a5f9d ffb570ffffff push dword ptr [ebp-90h]
805a5fa3 57 push edi
805a5fa4 ff572c call dword ptr [edi+2Ch] ds:0023:81593884={Null!DriverEntry (fa13159a)}
805a5fa7 3bc3 cmp eax,ebx
그런데 DriverEntry가 2개의 파라미터를 가지자나요.
NTSTATUS
DriverEntry(
__in struct _DRIVER_OBJECT *DriverObject,
__in PUNICODE_STRING RegistryPath
)
그래서 현재 스택에 push된 파라미터를 확인해 보니 DriverObject와 RegistryPath가 확인됩니다.
kd> dd esp
f9e9bc84 814a9330 814fe000 00000000 f657bcf4
kd> !drvobj 814a9330
Driver object (814a9330) is for:
\Driver\Null
Driver Extension List: (id , addr)
Device Object list:
kd> dS 814fe000
814fe008 "\REGISTRY\MACHINE\SYSTEM\Control"
814fe048 "Set001\Services\Null"
DriverEntry 진입점을 찾았네요.
그런데 역시 노가다는 심하네요. ^^