Reversing/Hooking

[Hooking] 4. openGL&SDL Hooking

이전엔 기껏 디버거로 다 찾아놨더니 D11이라 물먹었었다. D9를 쓸만한 바이너리를 찾아서 후킹을 시도해보려고 했다.

특정 라이브러리를 사용하는지 확인해보려면

  1. IDA로 import 혹은 Name 심볼중에 라이브러리 심볼 있는지 찾기
  2. x64 디버거에서 로드 심볼중 해당 DLL 로드하는지 찾기

두가지 정도로 확인이 가능할 것 같은데, 테스트 빌드했던 언리얼 바이너리는 2는 해당되지만 1은 해당되지 않아 어셈블리 코드 탐색에 문제가 있었다.


openGL Hooking - Assault Cube

https://guidedhacking.com/threads/opengl-swapbuffers-hooking-for-drawings-and-etc.10943/

이친구 오랜만이다. 클라이언트 실행하고 디버거를 attach 해봤다.

그랬더니 D3D 대신 gdi가 나온다. gdi도 d3d처럼 동일하게 vtable을 사용해서 후킹할 수 있지 않을까?

혹여나 싶어서 IDA에서 import 모듈 대상으로 검색을 진행해봤더니

그렇다. GDI도 아니고 OPENGL을 사용하는걸로 보인다.

이렇게 된 김에 openGL을 D3D 후킹하듯이 진행을 해볼까?

이전에 dll 인젝션을 진행해서 ESP를 위한 GUI를 그릴땐 API를 후킹해서 그린다기보단, 기존 창 위에 오버레이를 띄우고 라이브러리를 이용해 새롭게 그림을 그리는 느낌에 가까웠다.

이번엔 그냥 API후킹을 Detours 방식으로 해본다는 느낌으로 접근해보자.

기존에 D3D에 후킹을 어떻게 하려고 했냐면, D3D에서 씬을 한번 그리는 작업이 종료될 때 마다 호출되는 EndScene 함수를 후킹하기 위해 Vtable에 접근하여 실제 주소를 찾고, 해당 주소를 변조하는 식으로 후킹을 하려고 했다.

이걸 openGL로 고대로 옮겨오면, openGL에서는 한 장면을 그리는 작업을 종료할 때 glutSwapBuffers 라는 함수를 사용한다. 해당 함수를 후킹하는 작업이 EndScene을 후킹하는 작업과 동일한 셈.

따라서 SwapBuffers에 대해 후킹을 진행해보자.

Swapbuffer 디버깅

  • SDL - Simple Directmedia Layer : 크로스플랫폼 멀티미디어 라이브러리. 3D 랜더를 위해 OpenlGL을 사용한다.

OpenGL의 Swapbuffer와 SDL의 Swapbuffer는 기능적으로 동일해보이고, import 하는 라이브러리만 다를 뿐이라고 이해하면 되나?

swapbuffer 함수가 어떤게 쓰이는지 잠깐 체크해보자

두개의 swapbuffer 모두에 브레이크를 걸고 진행하니, 둘 모두에서 브레이크가 걸리는게 확인된다.

디버거를 따라 진행해보니 , 결국은 SDL_GL_SwapBuffers 함수는 내부적으로 wglSwapbuffers 함수를 호출하는게 확인된다. 그 루틴을 좀 찾아가보면

0x6814F4E0 주소에는 winDIB라는 함수의 주소값이 담겨있고,

해당 주소영역 아래쪽으로 opengl32 함수들의 함수 포인터가 담겨있는게 심볼을 통해 확인된다.

이때 edx+88위치의 함수를 호출하는데

그냥 심볼이 빠져 있을 뿐, swapbuffer로 점프하는 구문이 들어있는 코드가 나온다.

그럼 저 위치도 일종의 테이블로 활용하여, 다른 opengl 함수들을 후킹할 때 사용할 수 있는것 아닌가? 다른 함수들의 주소들이 들어있으니 이를 활용할 수 있어보인다.

일단 계속 swapbuffer를 찾아보면, 0x476e67이라는 고정 주소 위치에서 해당 함수를 호출하는 구문을 찾을 수 있었다.

저 함수는 SDL.dll안에 들어있으므로, sdl.dll에서 Swapbuffer를 찾아 디버깅 해보면

아까 어셈에서 봤던것과 동일한 내용이 보인다. 특정 주소값 위치를 기준으로 하여 함수를 호출하는 동작이다.

헥스로 보면 0x6814f4e0 라는 주소값이 고정으로 박혀있는게 보인다.

메모리에서 swapbuffer 함수의 주소를 찾아오려면 SDL.dll의 0x6814f4e0에 접근하여 포인터를 따라가면 되는 것.

메모리를 읽어오는 프로그램을 작성하여 이론과 동일하게 함수 주소를 받아올 수 있는지 체크해보자.

  • 예제 문서에서는 GetProcAddress 함수를 이용해 함수의 주소를 바로 받아오는 식으로 진행했지만, 본 실습시에는 이러한 방법 대신 포인터를 따라가면서 주소값을 받아오는 식으로 진행하려고 한다.
  • D3D 후킹에서 GetProcAddress함수를 이용해 EndScene 의 주소를 받아오지 못하는 이유는 EndScene은 가상함수(virtual function)으로서 동작하는데, 가상함수는 dll에서 export 되지 않아 GetProcAddress 함수가 심볼 이름으로 받아오는게 불가능하기 때문.

순서는 다음과 같다

  1. 0x6814f4e0 위치의 포인터 참조
  2. 참조한 포인터 주소 + 0x88위치에 있는 Swapbuffers 함수 주소 가져오기
  3. openGL32.dll 모듈에서 해당 함수에 직접 접근하여 Detour 훅 진행

후킹대상 주소 찾기

Detour 후킹 코드는 다음과 같이 구성된다.

void hooked_function() {
    //후킹시 실행될 내용 
}
void trampoline() {
    // trampoline 에 들어가야 할 어셈블리 주요 내용
  // 후킹함수 실행 - 메모리 정리 - 원본함수 점프 순으로 구성 
    // call hooked_funtion
  // 스택, 레지스터 재 정렬을 위한 코드
  // jmp  origin_function+5  -> 원본함수에서 트램폴린으로 점프한 다음 구문부터 재시작 
}
void Detour() {
    // 필요한 핸들과 origin_function의 주소값 얻어오는 동작 진행 
  // 원본함수 origin_function에서 trampoline으로 점프하는 구문으로 조작하는 동작 진행 
}

이후에 원본함수 origin_function이 실행되면, 공격자가 조작한 대로 origin에서 trampoline으로 점프하여 동작이 실행된다.

하나씩 차근차근 진행해보자.

  • 나중에 인젝터도 GUI로 하나 만들어야겠다. 쓸때마다 코드 수정해서 컴파일하기 귀찮음.
// dllmain.cpp : DLL 애플리케이션의 진입점을 정의합니다.
#pragma once
#include <windows.h>
#include <stdio.h>
//#include "hook.cpp"

namespace hooker {
    void hook() {
        FILE* pFile = nullptr;
        HMODULE dhandle = GetModuleHandle(L"opengl32.dll");
        int* taddr = (int*)0x6814f4e0;

        if (AllocConsole()) {

            freopen_s(&pFile, "CONIN$", "rb", stdin);
            freopen_s(&pFile, "CONOUT$", "wb", stdout);
            freopen_s(&pFile, "CONOUT$", "wb", stderr);

            printf("base : %08x \n", taddr);
            printf("follow address : %08x \n", *taddr);
        }
    }

};

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH: {
        MessageBox(nullptr, L"injection success", L"dll injection", MB_OK);
        hooker::hook();
    }
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

메모리 읽어오는게 잘 되는지 확인하기 위해 테스트 dll을 작성해 인젝션을 진행한다.

제대로 따라간다. 이제 함수 주소까지 접근진행하자.

namespace hooker {
    void hook() {
        FILE* pFile = nullptr;
        int* taddr = (int*)0x6814f4e0;
        int** faddr =(int **) *taddr ;
        int offset = 0x88 / sizeof(int);
        if (AllocConsole()) {

            freopen_s(&pFile, "CONIN$", "rb", stdin);
            freopen_s(&pFile, "CONOUT$", "wb", stdout);
            freopen_s(&pFile, "CONOUT$", "wb", stderr);

            printf("base : %08x \n", taddr);
            printf("follow address : %08x \n", *taddr);
            printf("follow pointer: %08x \n", faddr+offset);
            printf("function pointer : %08x \n", *(faddr + offset));
        }
    }

};

주소값을 제대로 찾아오는게 확인된다.

후킹 함수 제작

이제 후킹 동작을 구성할 차례.

  1. 트램펄린 함수
  2. 원본 어셈블리 코드 조작 함수
  3. 커스텀 후킹 함수

순서대로 세가지를 만들자.

우선, 트램펄린 동작을 구성하기 위해 점프하는 위치에서 최소 5바이트 만큼의 어셈블리를 똑같이 만들어줘야 한다.

4바이트 메모리 주소를 가지고 점프를 진행하기 때문에 5바이트만큼 어셈블리가 필요한 것이고, 원본 코드에서 실행할 동작을 트램폴린이 대신 실행한다고 생각하면 된다.

push ebp
mov ebp,esp
sub esp,8

위의 경우는 5바이트로 명령어가 떨어지질 않아 총 6바이트를 트램폴린에서 구성시키고, 원본 코드자리에는 nop코드를 넣어 6바이트를 맞춰주자.

void Tramp() {
        __asm {
            PUSHFD //save all register and flags
            PUSHAD
            CALL hookf //call custom function 
            POPAD
            POPFD
            PUSH EBP    //origin function's asm
            MOV EBP,ESP
            SUB ESP,8
            JMP [origin_addr]     //back to origin 
        }
    }

다시 원본함수로 돌아갈 때 생길수 있는 문제를 미연에 방지하고자 레지스터와 플래그들을 미리 저장했다가 다시 쓰게끔 만들자.

이제 원본 코드에서 트램폴린으로 점프하게 만드는 어셈블리를 덮어씌워야 한다.

void modifier(DWORD origin,DWORD newfunc,int size) {
        DWORD Protect = 0;
        DWORD newOffset = newfunc - origin +1 ; //make offset 
        VirtualProtect((LPVOID)origin, size, PAGE_EXECUTE_READWRITE, &Protect); //change page permission

        printf("origin addr : %08x \n", origin);
        printf("tramp addr : %08x \n", newfunc);
        origin_addr = origin + size;
        *((LPBYTE)origin + 0) = 0xE9;       //overwrite - JMP
        origin += 1;
        *((LPDWORD)origin + 0) = newOffset; //ADDR 4byte
        origin += 4;
        *((LPBYTE)origin) = 0x90;       //NOP


    }

원본 어셈블리코드를 jmp~ 문으로 덮어 씌웠고, 트램폴린 함수로 점프까지 잘 되는게 확인된다.

여기서 에러가 발생하길래 원인을 찾아봤더니, +1056 위치로 점프해야 하는걸 +1055로 가고있더라. newOffset을 연산할 때 바이트를 적절하게 맞춰줘야 정확하게 동작하여 에러가 발생하지 않는다.

이렇게 Detours 후킹을 해봤다. 이 다음부턴 원하는 대로 동작을 만들어서 넣으면 될듯.

openGL 라이브러리 Detours 후킹 성공.

이전에 만든 ESP를 오버레이 방식이 아닌 이 방식으로도 한번 해볼까..