Reversing/Hooking

[Hooking ] 3. D3D Hooking : 이론

D3D 후킹을 진행할 때는 DirectX에서 장면의 랜더를 종료할떄 사용하는 함수인 EndScene 함수를 주로 후킹한다. 해당 함수의 주소를 Vtable을 이용해 후킹을 진행하는 모양.

Vtable은 선언된 가상함수들의 주소를 담는 테이블이라고 했다. Vtable의 시작주소만 알고있다면 오프셋 계산을 통해 해당 바이너리에서 사용되는 모든 가상함수들의 주소를 얻어낼 수 있는것.

D3D 후킹은 다음과 같은 순서로 진행된다.

  1. dll injection을 통해 목표 프로세스에서 dll 메인을 실행
  2. dll 메인에서 후킹을 위해 만든 함수가 호출되며 d3d9.dll의 vTable 시작주소 탐색
  3. 알아낸 vTable 시작주소에서 오프셋 연산을 진행해 Endscene 함수의 실제 주소를 찾아냄
  4. 코드 패치 (후킹) 진행

후킹이 완료되면 EndScene 함수가 호출될 때 마다 hook 함수가 먼저 실행되고 그다음 EndScene 함수가 호출될 것이다.

순서에 맞게 진행하려면 몇가지 준비물들이 필요하다.

  1. vtable 시작주소 탐색을 위한 코드 패턴
  2. Endscene 함수의 오프셋

각각 나눠서 진행해보자.

Vtable 시작주소 얻어오기

대부분 자료에서 Vtable시작주소를 얻어오기 위해 어셈블리 코드 패턴 탐색을 진행한다. 이때 주로 사용하는 코드 탐색 패턴은 아래와 같다

"\xC7\x06\x00\x00\x00\x00\x89\x86\x00\x00\x00\x00\x89\x86", "xx????xx????xx"

앞은 어셈블리 헥스코드고, 뒤는 적용될 마스크인데, 둘을 합하면 "\xC7\x06\x??\x??\x??\x??\x89\x86\x??\x??\x??\x??\x89\x86" 인 코드 패턴을 찾아내라는 소리.

어떻게 저런 패턴이 나올 수 있었는지 부터 시작한다.

이전 문서에서 vtable은 클래스별로 생성된다고 했다. EndScene 함수가 어떤 클래스에 속하는지부터 알아내야 주소를 찾아내기 용이할듯 하다.

https://docs.microsoft.com/en-us/windows/win32/api/d3d9/nf-d3d9-idirect3ddevice9-endscene

MS에서 제공하는 문서 내용대로면, EndScene 함수는 IDirect3DDevice9라는 클래스의 하위 메소드로서 기능하고 있다. (위 MS 문서에서는 인터페이스로 통칭한다. )

그러므로 d3d9.h 에 정의된 IDirect3DDevice9 클래스가 선언되는 시점을 찾아 vtable을 구해오는 코드 루틴을 찾아볼 수 있겠다.

The IDirect3DDevice9 interface is obtained by calling the IDirect3D9::CreateDevice method.

IDirect3D9::CreateDevice 메소드를 통해 IDirect3DDevice9 인터페이스를 얻어올 수 있다고 한다.

HRESULT CreateDevice(
  UINT                  Adapter,
  D3DDEVTYPE            DeviceType,
  HWND                  hFocusWindow,
  DWORD                 BehaviorFlags,
  D3DPRESENT_PARAMETERS *pPresentationParameters,
  IDirect3DDevice9      **ppReturnedDeviceInterface
);

CreateDevice를 통해서만 IDirect3DDevice9 인터페이스가 반환된다는 의미이므로, CreatDevice 함수 실행 시점에 클래스가 선언되고, vftable 참조가 일어난다고 해석할 수 있겠다.

6번째 인자인 ppReturnedDeviceInterface로 인터페이스가 반환될 포인터가 전달된다고 하는데, 함수 내부로 진입해 해당 인자로 반환될 포인터가 전달되는 시점을 찾으면 될것같다.

CreateDevice 함수가 호출되는 시점으로 찾아가보자.

x64디버거에서 기호 - d3d9.dll 우클릭 - 디스어셈블러에서 따라가기 - CPU 탭으로 넘어가 어셈블리 코드에 우클릭 - 다음을 찾기 - 현재모듈 - 모듈간 호출

Createdevice 검색하고 어셈을 따라간다.

그럼 요런게 나오는데

오프셋을 계산하면 341D4 가 나온다.

IDA로 연 바이너리에서 CreateDevice를 찾아보면 총 세개가 나오는데, D3D11이네?

여태 D3D9으로 잡고 진행중이었는데, 갑자기 머리가 멍해진다. 다른 정보들로 검색해봐도 이 바이너리에서 d3d9을 사용하는 흔적이 안보이네.

다른 바이너리를 찾아서 다시 진행해본다.

  • OpenGL & SDL Hooking 이후 아래 부분 진행하였다.

CreateDevice 함수 분석 - Vtable 가져오는 코드 찾기 

reversing.kr 의 FPS 문제를 대상으로 dll 에서 EndScene 함수를 찾고 Vtable 가져오는 부분까지 가보자.  

디버거로 열어 심볼을 보면 저렇게 두개의 3d3 dll이 보인다.

CreateDevice 함수를 찾았고, IDA에서 따라가 내부를 확인해보자

7번째 인자인 a7으로IDirect3DDevice9이 전달되는게 확인된다.

인자로 전달된 값은 v33에 복사되고, 함수 시작과 동시에 포인터 자체는 0으로 초기화시킨다.

V33 변수는 V28.LowPart를 복사하는데, 이는 IDirect3DDevice9 구조체 포인터 자료형을 가지는걸로 보인다. V28변수에 값이 어떻게 들어가는지를 보면

CEnum::CreateDeviceImpl 함수의 8번째 인자로 전달되어 쓰인다. 해당 함수 내부에서 값이 어떻게 정해지는지 볼 수 있을것 같다.

v48 변수에 전달된 인자가 넘겨지는게 확인되었고, IDirect3DDevice9 구조체 포인터 자료형을 가지는 v25 변수가 복사되는게 보인다.

v25는 v24에서 값을 가져오는데, v24는 CD3DHal::CD3DHal 함수의 실행 리턴값을 받아온다. 해당 함수 내부로 들어가자

이전 vtable 디버깅때 봤던 vftable 심볼로부터 값을 가져와 v1에 저장하고, v1을 반환하는걸 확인할 수 있다.해당 부분이 vtable로부터 가상함수 주소를 받아오는 부분이라고 생각하면 될듯.

즉, vtable 주소를 가져오려면 해당 코드부분에 접근해야 한다는 결론이 나온다.

C7 06 38 26 00 10                 mov     dword ptr [esi], offset ??_7CD3DHal@@6B@ ; const CD3DHal::`vftable'
89 86 58 32 00 00                 mov     [esi+3258h], eax
89 86 50 32 00 00                 mov     [esi+3250h], eax

헥스랑 어셈코드를 같이보면 위와 같다. 여기서 C7 06 ~ 으로 시작하는 부분이 패턴 매칭에 사용되는 시작부분이다.

A102 부터 시작되는 3줄의 18바이트 어셈에서 MOV시에 사용하는 명령어 2바이트는 일정하지만, 목표 대상이 되는 주소 4바이트는 메모리에 올라갈 때 마다 바뀔 것이다. 고로 주소값에 해당하는 부분들을 ??로 마스킹 처리해줘야 패턴 탐지에 사용할 수 있다.

C7 06 ?? ?? ?? ?? 
89 86 ?? ?? ?? ??
89 86 ?? ?? ?? ??

이제 위 패턴과 오프셋 주소를 가지고 실제 메모리에 올라간 코드를 찾아 갈 것이다.

컨트롤+B 로 패턴 탐지 기능을 사용해보자.

CreateDevice → CEnum::CreateDeviceImpl → CD3DHal::CD3DHal 까지 따라왔다.

디버거상에선 제대로 찾아 오는게 보인다. 주소는 63500000 + C00 + 59502

아까 IDA에서 봤던 주소부분엔 38 26 53 63 으로 주소값이 코드영역에 박혀있는게 보이므로, 해당 부분을 찾아가는 코드를 작성하면 어떤 주소값이 전달되는지도 확인이 가능할 것이다.

해당 부분에 브레이크를 걸고, 가져온 vtable주소를 확인 후 EndScene 함수까지 연결되는지 보자.

ESI 레지스터가 가리키는 메모리 영역에 주소값이 복사되었고 , 해당 주소값을 따라가면 또 다른 포인터들이 잔뜩 저장된게 확인된다. 저 함수들이 각각 어떤 함수인지는 미리 정의된 Vtable Index 순서에 따라 정해지는데, 이 index 순서는 d3d9.h에 정의된 순서라고 한다.

https://github.com/apitrace/dxsdk/blob/master/Include/d3d9.h

42 #define IDirect3DDevice9_BeginScene(p) (p)->lpVtbl->BeginScene(p)
43 #define IDirect3DDevice9_EndScene(p) (p)->lpVtbl->EndScene(p)
44 #define IDirect3DDevice9_Clear(p,a,b,c,d,e,f) (p)->lpVtbl->Clear(p,a,b,c,d,e,f)

EndScene 함수는 43번째로 정의되어 있으므로, 43번째 vtable 인덱스를 보면 될듯.

제대로 EndScene을 찾아온게 맞다면 매 실행마다 브레이크가 걸려야한다. 근처에 있는 다른함수들도 브레이크를 걸어주고 실행을 돌려봤는데, 계속 여기서만 걸리는걸 보니 잘 찾아온게 맞는듯!

EndScene을 찾아오는 이유는 해당 코드 주소 위치에 코드패치를 진행하고 후킹을 걸기 위해서였다.

이제 해당 코드위치를 찾아가 주소값을 얻고, vtable 인덱스를 계산하여 Endscene 함수를 패치하는 코드를 작성해보자.

굉장한 삽질..


참고 문서

https://ajlab.tistory.com/entry/Direct3D-Hooking-분석-해봤습니다