일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- 개발하기
- 윈도우 커널 드라이버
- HackCTF
- 시스템해킹
- Windows Kernel Driver
- ucrtbase.dll
- IAT Hooking
- 윈도우 커널 디버깅
- packet capture
- Windows Kernel Debug
- C언어 패킷캡쳐
- hacking
- 해킹
- apphelp.dll
- Windows
- 개발 환경 준비
- arudino
- Msvcrt.dll
- pcap packet capture
- Network Byte Order
- 포너블
- vcruntime.dll
- 네트워크 바이트 오더
- pcap packet
- 윈도우 커널
- vcruntime140.dll
- pwnable
- Windows Kernel
- 바이트 오더
- windows kernel debugging
- Today
- Total
미친해커
[WoW] WoW 프로세스의 커널 함수 호출 과정 본문
How are ntdll.dll and WoW ntdll.dll different?
ntdll.dll은 커널 함수가 정의 되어 있는 DLL이다. 유저 모드의 커널 함수들은 syscall을 통해 커널 함수를 호출할 수 있다. 하지만 운영체제(커널)은 64Bit이며 WoW 프로세스는 32Bit로 동작한다. 그렇기에 32Bit 프로세스는 syscall 하기 전 64Bit 모드로 전환할 필요가 있다.
위 이미지와 같이 WoW ntdll.dll의 경우 syscall number를 eax 레지스터에 지정하고 syscall
이 아닌 call edx
로 어떠한 함수를 호출하는 것을 확인할 수 있다.
Which function is invoked using edx register?
call edx
는 ntdll!Wow64SystemServiceCall
함수를 호출한다.
해당 함수는 ntdll!Wow64Transition
를 참조해 다른 함수로 점프한다.
ntdll!Wow64SystemServiceCall
에서 wow64cpu!KiFastSystemCall
로 넘어가게 된다.
아쉽게도 IDA Pro에서는 jmp far 어셈블리가 해석되지 않는다. 아무래도 Opcode 0xEA 어셈블리가 지원되지 않는 모양이다.
jmp far 0033:XXXXXXXX
는 아래에 있는 jmp qword ptr ds:[r15+0xF8]
으로 이동하게 된다.
여기부터는 64Bit 모드로 동작하게 된다. 계속해서 r15 레지스터에 어떤 값이 들어가 있는지 확인해야 한다.
What is the value of the r15 register?
r15가 0x77893620이고 해당 주소는 wow64cpu!TurboThunkDispatch
의 주소라는 것을 Windbg를 통해 알 수 있었다.
Where is the address to jump (jmp qword ptr ds:[r15+0xF8])
r15(0x77893620) + 0xF8 = 0x77893718
0x77893718에는 wow64cpu!CpupReturnFromSimulatedCode
의 주소가 작성되어 있다.
이렇게 64Bit 모드로 전환하고 점프(호출)하는 함수를 알아낼 수 있었다.
wow64cpu!CpupReturnFromSimulatedCode
다음 함수의 어셈블리를 먼저 확인해보자
wow64cpu!CpupReturnFromSimulatedCode
, wow64cpu!TurboDispatchJumpAddressStart
, wow64cpu!ServiceNoTurbo
함수가 있는 것을 확인할 수 있다.
wow64cpu!CpupReturnFromSimulatedCode
와 wow64cpu!TurboDispatchJumpAddressStart
는 하나의 함수 처럼 이어져 있다.
wow64cpu!TurboDispatchJumpAddressStart
에서 jmp qword ptr ds:[r15+rcx*8]
을 하게 되는데 디버깅 결과, wow64cpu!ServiceNoTurbo
로 이동하는 것을 확인했다.
wow64cpu!ServiceNoTurbo
는 wow64cpu!TurboDispatchJumpAddressEnd
라고도 불린다.
다음은 호출은 call qword ptr ds:[wow64cpu!_imp_Wow64SystemServiceEx]
이다.
wow64cpu!_imp_Wow64SystemServiceEx
심볼 네이밍을 보면 Wow64SystemServiceEx
를 wow64cpu.dll
이 import 하기 때문에 IAT에 등록되어 있음을 알 수 있다.
Wow64SystemServiceEx
는 wow64.dll
에 존재하는 것을 확인 했고 해당 함수를 IDA Pro로 확인해보았다.
전체 코드는 아래 더보기를 클릭하면 나온다.
__int64 __fastcall Wow64SystemServiceEx(unsigned int SyscallNumber, __int64 a2)
{
struct _TEB *v3; // rdi
__int64 v4; // r8
__int64 v5; // rdx
struct _TEB *v6; // rbx
__int64 v7; // rax
__int64 (__fastcall *v8)(__int64); // rsi
_QWORD *v9; // rsi
__int64 *v10; // rdi
__int64 *v11; // rax
__int64 v12; // rcx
_QWORD *v14; // rcx
unsigned int v15; // [rsp+20h] [rbp-898h]
PVOID v16; // [rsp+38h] [rbp-880h] BYREF
int v17; // [rsp+40h] [rbp-878h]
char v18[8]; // [rsp+48h] [rbp-870h] BYREF
__int64 v19; // [rsp+50h] [rbp-868h]
unsigned int v20; // [rsp+58h] [rbp-860h]
unsigned int v21; // [rsp+5Ch] [rbp-85Ch]
unsigned int v22; // [rsp+60h] [rbp-858h]
char v23; // [rsp+64h] [rbp-854h]
PVOID v24; // [rsp+70h] [rbp-848h] BYREF
__int64 v25[259]; // [rsp+78h] [rbp-840h] BYREF
__int64 v26; // [rsp+890h] [rbp-28h] BYREF
v16 = NtCurrentTeb()->TlsSlots[12];
v3 = 0i64;
v17 = 0;
NtCurrentTeb()->TlsSlots[12] = &v16;
v4 = (SyscallNumber >> 12) & 3;
v5 = SyscallNumber & 0xFFF;
if ( (unsigned int)v5 > LODWORD(ServiceTables[3 * v4 + 1]) )
{
v15 = 0xC000001C;
}
else
{
v6 = NtCurrentTeb();
v7 = (int)v6->SpareUlong0;
if ( (_DWORD)v7 )
{
if ( (int)v7 < 0 )
v3 = v6;
else
v3 = (struct _TEB *)((char *)v6 + v7);
}
v24 = v6->TlsSlots[3];
v25[1] = (__int64)v25;
v25[0] = (__int64)v25;
v25[2] = (__int64)&v26;
v6->TlsSlots[3] = &v24;
v8 = *(__int64 (__fastcall **)(__int64))(*(_QWORD *)&ServiceTables[3 * v4] + 8 * v5);
v20 = (SyscallNumber >> 12) & 3;
v21 = SyscallNumber & 0xFFF;
v6->LastErrorValue = HIDWORD(v3->NtTib.Self);
if ( pfnWow64LogSystemService )
{
v19 = a2;
v23 = 0;
Wow64LogSystemServiceWrapper(v18);
v15 = v8(a2);
v23 = 1;
v22 = v15;
Wow64LogSystemServiceWrapper(v18);
}
else if ( v8 == whNtSetTimerEx )
{
v15 = whNtSetTimerEx(a2);
}
else if ( v8 == whNtCallbackReturn )
{
v15 = whNtCallbackReturn(a2);
}
else if ( v8 == whNtQueryVirtualMemory )
{
v15 = whNtQueryVirtualMemory(a2);
}
else if ( v8 == whNtOpenKeyEx )
{
v15 = whNtOpenKeyEx(a2);
}
else if ( v8 == whNtQueryValueKey )
{
v15 = whNtQueryValueKey(a2);
}
else
{
v15 = v8(a2);
}
HIDWORD(v3->NtTib.Self) = v6->LastErrorValue;
v9 = v6->TlsSlots[3];
v10 = v9 + 1;
v11 = (__int64 *)v9[1];
v12 = *v11;
if ( (_QWORD *)v11[1] != v9 + 1 || *(__int64 **)(v12 + 8) != v11 )
__fastfail(3u);
*v10 = v12;
for ( *(_QWORD *)(v12 + 8) = v10; v11 != v10; v14[1] = v10 )
{
RtlFreeHeap(NtCurrentPeb()->ProcessHeap, 0, v11);
v11 = (__int64 *)*v10;
v14 = *(_QWORD **)*v10;
if ( *(__int64 **)(*v10 + 8) != v10 || (__int64 *)v14[1] != v11 )
__fastfail(3u);
*v10 = (__int64)v14;
}
v6->TlsSlots[3] = (PVOID)*v9;
}
NtCurrentTeb()->TlsSlots[12] = v16;
if ( *(_DWORD *)(Wow64InfoPtr + 8) && (v17 & 3) == 0 && (v17 & 4) == 0 )
Wow64SetupForInstrumentationReturn();
return v15;
}
__int128 ServiceTables[];
__int64 __fastcall Wow64SystemServiceEx(unsigned int SyscallNumber, __int64 a2)
{
.....
v4 = (SyscallNumber >> 12) & 3; // Syscall Number가 정상이라면 0
v5 = SyscallNumber & 0xFFF; // Syscall Number가 정상이라면 0
/*
LODWORD(ServiceTables[3 * v4 + 1])의 값이 정상적인 Syscall Number를
인자로 받은 호출이라면 0x1000이 된다. 그러기에 반드시 else 빠지는 것이
정상적인 진행이다.
*/
if ( (unsigned int)v5 > LODWORD(ServiceTables[3 * v4 + 1]) )
{
v15 = 0xC000001C;
}
else
{
....
//whNtXXXXX Syscall Number에 맞는 함수 주소를 가져옴
v8 = *(__int64 (__fastcall **)(__int64))(*(_QWORD *)&ServiceTables[3 * v4] + 8 * v5);
v20 = (SyscallNumber >> 12) & 3;
v21 = SyscallNumber & 0xFFF;
v6->LastErrorValue = HIDWORD(v3->NtTib.Self);
if ( pfnWow64LogSystemService )
{
v19 = a2;
v23 = 0;
Wow64LogSystemServiceWrapper(v18);
v15 = v8(a2);
v23 = 1;
v22 = v15;
Wow64LogSystemServiceWrapper(v18);
}
else if ( v8 == whNtSetTimerEx )
{
....
else
{
v15 = v8(a2); // whNtxxxxxx 함수 호출
}
....
}
분석해본 결과 v8
에 현재 호출한 커널 함수 이름의 wh(ex: whNtxxxxxxx)를 붙인 함수의 주소를 저장한다.
예를 들어 NtOpenProcess
를 호출했다면, v8
에는 whNtOpenProcess
의 주소가 담긴다. 그리고 해당 함수들이 v15 = v8(a2)
에서 호출된다.
일부 커널 함수들은 v15 = v8(a2)
라인에서 실행하지 않고 if문으로 실행하는 루틴이 빠져있다. 왜 인지는 모르겠다.
What is the whNtxxxxxxx function in wow64.dll?
whNtxxxxxxxx
함수는 실제 Ntxxxxxxx
함수를 호출하기 전에 호출되는 함수이다.
NTSTATUS __fastcall whNtOpenProcess(unsigned int *a1)
{
_DWORD *v1; // rdi
ACCESS_MASK v2; // er14
int *v3; // rsi
void **v4; // rbx
struct _CLIENT_ID *v5; // r9
NTSTATUS result; // eax
__int64 v7; // [rsp+0h] [rbp-68h] BYREF
int v8; // [rsp+20h] [rbp-48h]
__int64 v9; // [rsp+28h] [rbp-40h] BYREF
POBJECT_ATTRIBUTES ObjectAttributes; // [rsp+30h] [rbp-38h] BYREF
__int64 *v11; // [rsp+38h] [rbp-30h]
struct _CLIENT_ID ClientId; // [rsp+40h] [rbp-28h] BYREF
v11 = &v7;
v1 = (_DWORD *)*a1;
v2 = a1[1];
v3 = (int *)a1[3];
v9 = 0i64;
v4 = (void **)((unsigned __int64)&v9 & -(__int64)((_DWORD)v1 != 0));
v8 = Wow64ShallowThunkAllocObjectAttributes32TO64_FNC((_DWORD *)a1[2], (__int64 *)&ObjectAttributes);
if ( v8 < 0 )
{
local_unwind_0(v11, &loc_18000BADF);
}
else if ( (_DWORD)v3 )
{
v5 = &ClientId;
ClientId.UniqueThread = (HANDLE)v3[1];
ClientId.UniqueProcess = (HANDLE)*v3;
goto LABEL_4;
}
v5 = 0i64;
LABEL_4:
result = NtOpenProcess(v4, v2, ObjectAttributes, v5);
if ( (_DWORD)v1 )
*v1 = *(_DWORD *)v4;
return result;
}
나도 아직 정확히 모든 분석을 끝내지는 못하였지만 a1은 Ntxxxxxxx 함수의 인자가 담겨있는 스택의 주소인 것으로 추정된다.
x86 ntdll.dll에서 호출된 커널함수의 인자는 x86 아키텍쳐에 맞춰 정렬되어 있기 때문에 해당 구조(체)를 x64 아키텍쳐 맞춰서 변환하는 작업을 하는 것으로 보인다.
결론적으로 whNtxxxxxxx
함수는 x86 아키텍쳐에 맞춰진 인자들을 실제 커널 함수(Ntxxxxxxx)를 호출하기 전에 x64 아키텍쳐에 맞춰 세팅하고 커널 함수를 호출하는 것으로 확인된다.
WoW 프로세스의 커널 함수 호출 과정
1. Ntxxxxxxxxx
(ntdll.dll x86)
2. Wow64SystemServiceCall
-> Wow64Transition
참조 (wow64cpu.dll)
3. KiFastSystemCall
(wow64cpu.dll)
4. CpupReturnFromSimulatedCode
(wow64cpu.dll)
5. TurboDispatchJumpAddressStart
(wow64cpu.dll)
6. TurboDispatchJumpAddressEnd(ServiceNoTurbo)
(wow64cpu.dll)
7. Wow64SystemServiceEx
(wow64.dll)
8. whNtxxxxxxxxx
(wow64.dll)
9. Ntxxxxxxxxx
(ntdll.dll x64)
'Windows > Heaven's Gate (Windows on Windows x64)' 카테고리의 다른 글
[WoW] Windbg를 이용해 WoW 프로세스 디버깅하기 (0) | 2022.06.13 |
---|---|
[WoW] Far와 Near 어셈블리 명령의 차이점 (0) | 2022.06.01 |
[WoW] 32Bit 모드에서 64Bit 모드로 전환하기 (0) | 2022.05.31 |
[WoW] 32Bit 응용 프로그램에서의 Kernel 함수 호출 (0) | 2022.05.04 |
[WoW] WoW64(Windows on Windows 64-Bit)란? (0) | 2022.05.03 |