Reversing/Hooking

[Hooking] 8. D3D Hooking : dx9, dx11 후킹 실습

DirectX 후킹 방식과 과정에 대해 다시 정리해보면,

  • EndScene 함수 후킹
    • 한번의 랜더링이 끝날 때 마다 EndScene 함수가 호출됨
    • 해당 함수를 후킹하면 매 렌더 프레임마다 원하는 동작 수행 가능
    • 주로 이를 통해 화면 위에 그림을 그릴 수 있음.
    • 따라서 EndScene 함수의 주소가 필요함.
  • VTable 시작주소 탐색
    • EndScene 함수는 D3d9.dll의 VTable 공간에 담겨있음.
    • CreateDevice 함수 어셈블리 코드 패턴 탐색을 진행하여 VTable 시작주소를 얻어올 수 있음.
    • VTable 찾아 인덱스 번호 참조하여 EndScene 함수 시작주소 가져올 수 있음.
  • Detour 후킹
    • 원본함수의 실행코드 시작부분을 패치하여 코드 흐름을 바꾸는 Inline 후킹의 한 종류
    1. 원본함수의 코드를 Inline 패치
    2. 트램폴린 코드로 점프
    3. 후킹 함수 실행
    4. 트램폴린 코드로 복귀 - 인자, 스택, 메모리 정리등을 수행
    5. 다시 원본함수로 복귀 - 없었던 일인 것 처럼.
    • detour 함수 내에 트램폴린 구성하는 어셈블리와 코드 패치 기능 존재.
    • 후킹함수 별도로 선언
  • DirectX 함수의 실행
    • 윈도우 핸들 , D3D 객체, 인터페이스를 가져와 d3d 디바이스 생성
    • 생성한 디바이스를 인자로 함수 실행 → 해당 D3D 디바이스에 그림이 그려짐.
  • Dummy Device Method
    • 기존에 생성된 디바이스의 디바이스 오브젝트를 그대로 사용하기 위함.
    • D3D vTable은 프로세스 핸들 기준으로 생성됨
    • 더미 디바이스 생성시 해당 프로세스의 vTable이 새로 생성한 디바이스에 공유됨
    • 해당 디바이스의 vTable 복사하여 가져오면 모든 함수 포인터 갖게됨

(요약본도 길다)

이렇게 하면 해당 프로세스의 모든 D3D 함수를 사용할 수 있게된다.


EndScene Hooking - Dummy Device Method

  • 프로젝트 세팅 : 프로젝트 - 속성 - 구성속성 - 링커 - 입력 - d3d9.lib 추가

Dummy Device를 이용한 EndScene 후킹부터 재개한다.

  1. D3D 인스턴스 및 dummy device 생성
  2. 생성된 d3d device의 ptable 복사
  3. 복사한 ptable에서 EndScene 함수 주소 가져옴
  4. EndScene 함수에 후킹 진행

위와 같은 순서로 동작한다.

우선 3번까지만 만들어 EndScene 함수의 주소를 제대로 가져오는지부터 확인한다.

DWORD HookMain() {
    FILE* pFile = nullptr;
    if (AllocConsole()) {
        freopen_s(&pFile, "CONIN$", "rb", stdin);
        freopen_s(&pFile, "CONOUT$", "wb", stdout);
        freopen_s(&pFile, "CONOUT$", "wb", stderr);
    }
    else
    {
        return 0;
    }

    //dummy device hooking
    d3dhelper::GetD3D9Device(dtable, sizeof(dtable));
    DWORD64 pEndScene = (DWORD64)dtable[42];

    cout << "pEndScene : " << setbase(16) << pEndScene << endl;

    return 0;
}
~~~
case DLL_PROCESS_ATTACH:
        CloseHandle(CreateThread(nullptr, 0, (LPTHREAD_START_ROUTINE)HookMain, hModule, 0, nullptr));

이때 후킹 함수는 CreateThread 함수 통해 실행시킨 후 CloseHandle 실행, 단 한번만 돌게 만든다.

코드 빌드 후 DLL 인젝션 진행하여 함수 주소가 제대로 나오는지 확인하면

pEndScene : 7ff9934ba7e0

해당 위치를 디버거에서 따라가보면

D3D9.dll+A7E0 40 53                                   push    rbx
D3D9.dll+A7E2 48 83 EC 40                             sub     rsp, 40h
D3D9.dll+A7E6 48 C7 44 24 28 FE FF FF+                mov     [rsp+48h+var_20], 0FFFFFFFFFFFFFFFEh
D3D9.dll+A7EF 48 8B D9                                mov     rbx, rcx

위와같은 위치를 찾아가며 , 해당 위치는 CD3DBase::EndScene 임이 확인된다.

이제 EndScene 함수에 후킹을 걸어 화면 위에 오버레이를 그려볼건데, 다음 순서로 Detours 후킹이 진행된다.

1. 원본함수 실행시 후킹함수로 점프
2. 후킹함수 동작 종료시 트램폴린 코드로 점프 
3. 트램폴린 코드실행 - 원본함수 동작 일부 수행
4. 원본함수로 복귀 - 없었던 일인 것 처럼.

위 동작대로 수행하기 위해선 아래 코드의 작성이 필요하다

  1. 원본함수 동작 덮어씌우는 코드
    • 목표함수의 주소값을 직접적으로 덮어씌운다.
    • 바이트 코드 남아도는 만큼은 0x90으로 채워준다.
  2. 트램폴린 동작 생성하는 코드
    • 트램폴린에는 목표함수로 점프하는 코드가 포함되어있다.
    • 트램폴린 생성 후 트램폴린 코드의 주소를 반환한다.
    • 이때 점프할 위치는 (원본함수 + 덮어씌운 길이 ) 위치
  3. 목표함수 코드
    • 동작이 완료되면 트램폴린 코드로 점프한다
    • 트램폴린 코드는 원본함수 동작일부 + 원본함수로 점프 하는 동작을 수행한다.

테스트에 사용한 프로그램

https://github.com/joeyGumer/Triangle-example-directX9

 

GitHub - joeyGumer/Triangle-example-directX9: trying to learn how to directx

trying to learn how to directx. Contribute to joeyGumer/Triangle-example-directX9 development by creating an account on GitHub.

github.com

  • X64 Debug 로 빌드하여 테스트 진행하였다.
  • 후킹된 EndScene 함수
00007FF9934BA7E0 | 48:B8 E0109591F97F0000          | mov rax,<d3dhooking2.long __cdecl hook::hEndScene(struct IDirect3DDevice9 * __ptr64)>      |
00007FF9934BA7EA | FFE0                            | jmp rax                                                                                    |
  • 후킹 함수 내부
00007FF991951108 | 5B                              | pop rbx                                                                                    |
00007FF991951109 | 48:FF25 30450000                | jmp qword ptr ds:[<long (__cdecl* __ptr64 oEndScene)(struct IDirect3DDevice9 * __ptr64)>]  | HookHeader.h:85
  • 트램폴린 영역
0000023451350000 | 40:53                           | push rbx                                                                                   |
0000023451350002 | 48:83EC 40                      | sub rsp,40                                                                                 |
0000023451350006 | 48:C74424 28 FEFFFFFF           | mov qword ptr ss:[rsp+28],FFFFFFFFFFFFFFFE                                                 |
000002345135000F | 48:8BD9                         | mov rbx,rcx                                                                                |
0000023451350012 | 50                              | push rax                                                                                   |
0000023451350013 | 48:B8 F2A74B93F97F0000          | mov rax,d3d9.7FF9934BA7F2                                                                  |
000002345135001D | 48:870424                       | xchg qword ptr ss:[rsp],rax                                                                | [rsp]:render_frame+F5
0000023451350021 | C3                              | ret                                                                                        |
  • 트램폴린 함수 ret 시점 RSP
000000F0E88FF4D8  00007FF9934BA7F2  d3d9.00007FF9934BA7F2
  • 점프할 위치는 원본 EndScene 함수
00007FF9934BA7F1 | 90                              | nop                                                                                        |
00007FF9934BA7F2 | 48:8BC1                         | mov rax,rcx                                                                                |
00007FF9934BA7F5 | 4C:8D41 08                      | lea r8,qword ptr ds:[rcx+8]                                                                |
00007FF9934BA7F9 | 48:F7D8                         | neg rax                                                                                    |

제대로 후킹이 걸리는걸 확인했으니 , 화면위에 그림을 올려보자.

D3d9 Dummy Device 후킹 완료.


Dx11의 경우

DirectX11의 경우 9와 다른점이 몇가지 있다.

  1. 더미디바이스 생성을 위해 CreateDevice 함수 대신 D3D11CreateDeviceAndSwapChain 함수 사용
  2. swapchain vTable을 이용
  3. EndScene 함수 대신 Present 함수 후킹
  4. d311.lib 추가
  • 경우에 따라서 d3dcompiler.lib 도 필요함.

그 이유는

  1. D3D11CreateDevice 함수는 DeviceInterface를 반환하지 않아 이를 통해 vTable을 바로 받아오는 동작이 불가하다.
  2. d311은 화면을 한번 그리는 동작이 종료될 때 IDXGISwapChain::Present 함수가 실행된다.
  3. Present 함수 포인터는 SwapChain vTable에 존재한다. (인덱스 8번)
  4. 따라서 D3D11CreateDeviceAndSwapChain 함수를 실행해 vTable을 가지고 있는SwapChain 포인터를 받아와 사용한다.

따라서 d3d9 을 사용하는 부분 코드를 수정해야한다.

  • CreateDevice 함수가 0x887A0004 로 인해 실패하는 경우, 두번째 인자인 Driver Type을 바꿔가면서 함수를 실행해보자.
  • D3D_DRIVER_TYPE_REFERENCE → D3D_DRIVER_TYPE_HARDWARE 로 바꿔서 실행 성공함.

Present 함수는 dxgi.dll 에 CDXGISwapChain::Present로 선언되어있다.

.text:0000000180002A40 48 89 5C 24 10                          mov     [rsp-8+arg_8], rbx
.text:0000000180002A45 48 89 74 24 20                          mov     [rsp-8+arg_18], rsi
.text:0000000180002A4A 55                                      push    rbp
.text:0000000180002A4B 57                                      push    rdi
.text:0000000180002A4C 41 56                                   push    r14
.text:0000000180002A4E 48 8D 6C 24 90                          lea     rbp, [rsp-70h]
.text:0000000180002A53 48 81 EC 70 01 00 00                    sub     rsp, 170h

앞에서부터 12바이트 만큼 후킹 걸고 진행해보면

매 Scene 랜더마다 후킹 함수가 호출되는게 확인된다.

D3d11 후킹 완료.


Github

https://github.com/synod2/d3d9_Endscene_DummyDevice_X64

 

GitHub - synod2/d3d9_Endscene_DummyDevice_X64: X64 d3d9 Endscene hooking using DummyDevice method

X64 d3d9 Endscene hooking using DummyDevice method - GitHub - synod2/d3d9_Endscene_DummyDevice_X64: X64 d3d9 Endscene hooking using DummyDevice method

github.com

https://github.com/synod2/d3d11_Present_DummyDevice_X64

 

GitHub - synod2/d3d11_Present_DummyDevice_X64: X64 d3d11 Present hooking using DummyDevice method

X64 d3d11 Present hooking using DummyDevice method - GitHub - synod2/d3d11_Present_DummyDevice_X64: X64 d3d11 Present hooking using DummyDevice method

github.com


참고 문서

https://guidedhacking.com/threads/d3d9-endscene-hook-template-using-dummy-device.14008/

https://docs.microsoft.com/en-us/windows/win32/api/dxgi/nf-dxgi-idxgiswapchain-present

 

IDXGISwapChain::Present (dxgi.h) - Win32 apps

Presents a rendered image to the user.

docs.microsoft.com

https://guidedhacking.com/threads/d3d11-vtable-indexes.3813/

https://github.com/guided-hacking/GH_D3D11_Hook

 

GitHub - guided-hacking/GH_D3D11_Hook: Barebones D3D11 hook.

Barebones D3D11 hook. Contribute to guided-hacking/GH_D3D11_Hook development by creating an account on GitHub.

github.com