미친해커

[WoW] WoW 프로세스의 커널 함수 호출 과정 본문

Windows/Heaven's Gate (Windows on Windows x64)

[WoW] WoW 프로세스의 커널 함수 호출 과정

미친해커 2022. 6. 14. 11:09
반응형

How are ntdll.dll and WoW ntdll.dll different?

ntdll.dll은 커널 함수가 정의 되어 있는 DLL이다. 유저 모드의 커널 함수들은 syscall을 통해 커널 함수를 호출할 수 있다. 하지만 운영체제(커널)은 64Bit이며 WoW 프로세스는 32Bit로 동작한다. 그렇기에 32Bit 프로세스는 syscall 하기 전 64Bit 모드로 전환할 필요가 있다.

<좌> WoW ntdll.dll <우> 64Bit ntdll.dll

위 이미지와 같이 WoW ntdll.dll의 경우 syscall number를 eax 레지스터에 지정하고 syscall이 아닌 call edx로 어떠한 함수를 호출하는 것을 확인할 수 있다.

Which function is invoked using edx register?

call edxntdll!Wow64SystemServiceCall함수를 호출한다.

 

해당 함수는 ntdll!Wow64Transition를 참조해 다른 함수로 점프한다.

 

ntdll!Wow64SystemServiceCall에서 wow64cpu!KiFastSystemCall로 넘어가게 된다.

<좌> IDA Pro <우> Cheat Engine x64

아쉽게도 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 레지스터가 가르키는 주소로 이동해 값을 확인

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!CpupReturnFromSimulatedCodewow64cpu!TurboDispatchJumpAddressStart는 하나의 함수 처럼 이어져 있다.

 

wow64cpu!TurboDispatchJumpAddressStart에서 jmp qword ptr ds:[r15+rcx*8]을 하게 되는데 디버깅 결과, wow64cpu!ServiceNoTurbo로 이동하는 것을 확인했다.

 

wow64cpu!ServiceNoTurbowow64cpu!TurboDispatchJumpAddressEnd라고도 불린다.

 

다음은 호출은 call qword ptr ds:[wow64cpu!_imp_Wow64SystemServiceEx]이다.

wow64cpu!_imp_Wow64SystemServiceEx

심볼 네이밍을 보면 Wow64SystemServiceExwow64cpu.dll이 import 하기 때문에 IAT에 등록되어 있음을 알 수 있다.

 

Wow64SystemServiceExwow64.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문으로 실행하는 루틴이 빠져있다. 왜 인지는 모르겠다.

 

일부 커널 함수들은 따로 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)

반응형
Comments