본문 바로가기
Security

안티 디버깅 – Thread ID

by 올엠 2020. 11. 1.
반응형

안티 디버깅과 프로세스 보호는 약간 차이가 있다. 안티 디버깅은 디버깅을 하지 못하도록 하여 프로그램의 실행 내용을 분석하지 못하도록 방해하는 행위로, 프로세스 보호와도 혼용하기도 하지만, 프로세스 보호는 후킹과 같은 외부 프로세스에서 해당 프로그램의 데이터를 조작하지 못하도록 방지하는 기능이라 할 수 있다. 쉽게 얘기하면 정당한 경로로 함수를 호출했는지를 확인하는, 안티 디버깅의 2차적 보안 기능인 셈이다. 안티 디버깅은 디버깅을 차단하는 기능으로 이것만으로는 후킹과 같은 인젝션 기능을 막지 못한다. 따라서 안티 디버깅과는 별개로 생각하면 된다(물론 이 역시 디버깅으로 분석하여 뚫을 수 있다. 따라서 디버깅 행위에 대한 보호가 먼저 이루어져야 한다).

이 기능은 후킹시 프로세스를 보호하고자 한다면, 어느 지점을 보호해야 하는지에 대해 알아 볼 수 있다. 여러분이 만든 프로그램이 후킹으로 인해 외부에서 내부 중요 함수가 호출되는 것을 막을 수 있는 방법이 어떤 것이 있는지 알아보도록 하자.

우리는 앞서 후킹에 대해서 배우는데, 우리가 알고 있는 후킹 방법 중 다른 스레드에서 함수를 호출한 경우 이를 탐지하여 실행을 막는 기능에 대해 알아보자. 먼저 현재 스레드 ID를 기억하였다가 이를 이용해 활용하는 프로그램을 만들어 보자.

threaddetected.cpp

#include <stdio.h>
#include <windows.h>
#include <conio.h>

// 실행 초기 현재 실행하는 프로그램의 스레드ID를 기억한다.

DWORD g_dwThreadID = GetCurrentThreadId();

void Check()
{
    // Check함수를 해당 스레이드와 다른 스레드ID로 실행하는지 확인한다.
    DWORD c_dwThreadID = GetCurrentThreadId();
    if (c_dwThreadID != g_dwThreadID)
    {

        printf(“* Another thread ID call detected\n”);
        printf(“* Current thread ID is
               : % d \n”, c_dwThreadID);

        printf(“Original thread ID is
               : % d \n”, g_dwThreadID);

        exit(0xF230BE05);
    }
    else
        printf(“safe\n”);
}

void main()
{
    while (true)
    {
        Sleep(1000);
        Check();
    }
    return;
}

이제 후킹 Dll을 만들어 외부에서 Check 함수를 호출해 보자. 이를 위해서는 먼저 Threaddetected 프로그램 Ollydbg로 역분석해서, 해당 프로그램이 사용하는 주소를 알아보도록 하자.

 

얼핏 보면 0x4110D2가 Check 함수를 호출하는 것 같다. 하지만 이를 Step into(F7키)를 이용해 확인해 보면 JMP 명령을 통해 실제 함수 위치가 어디인지 알 수 있다.

이제 Check함수가 호출하는 메모리 주소가 0x411390이라는 것을 확인하였다. 이 Check 함수를 외부에서 호출할 수 있는 Dll을 만들어 인젝션을 하여야 한다.

로더는 후킹 글에서 만들었으므로 해당 로더를 이용하고,여기서는 Callhook라는 Dll을 만들어보자.

callhook.cpp

#include <windows.h>
DWORD WINAPI MyThread(LPVOID);
DWORD g_threadID;
HMODULE g_hModule;

// 사전에 조사한 함수의 메모리 주소를 이용하여 호출을 대신한다.
// 함수 호출 규약에서 배웠듯이 프로그램 Check 프로그램과 동일한 호출 규약을 이용하여 호출하도록 하자.
void __cdecl Callhook()
{
    typedef void(__cdecl * pCallAddress)();
    // 조금 전 조사한 메모리 주소를 입력한다. 아래 주소는 운영체제마다 바뀔 수 있다.
    pCallAddress Check = (pCallAddress)(0x00411390);
    Check();
}

INT APIENTRY DllMain(HMODULE hDLL, DWORD Reason, LPVOID Reserved)
{
    Switch(Reason)
    {
    case DLL_PROCESS_ATTACH:
        g_hModule = hDLL;
        DisableThreadLibraryCalls(hDLL);
        CreateThread(NULL, NULL, &MyThread, NULL, NULL, &g_threadID);
        break;
    case DLL_THREAD_DETACH:
        break;
    }
    return TRUE;
}

DWORD WINAPI MyThread(LPVOID)
{
    while (true)
    {

        // F1키를 누루면 CallHook()을 이용하여 Check()함수를 호출 한다.
        if (GetAsyncKeyState(VK_F1) & 1)
        {
            Callhook();
        }
        // 프로그램에 무리를 주지 않기 위해 삽입한다.
        Sleep(100);
    }
    FreeLibraryAndExitThread(g_hModule, 0);
    return 0;
}

위 Dll을 작성한 후 빌드하고 먼저 Threaddetected프로그램을 실행하자.

그리고 Threaddetected 프로그램의 PID를 작업관리자를 이용해 확인하고, InjectionDll을 이용하여 우리가 작성한 Dll을 인젝션 시키도록 하자. 필자 머신의 후킹할 프로그램 PID는 2192이다.

injectiondll –i 2192 Callhook.dll

정상적으로 인젝션 되었다면 아래와 같은 성공 문구가 나타난다.

 

이제 실습 준비가 마무리되었다. F1키를 누르면 우리가 만든 Callhook.dll에 의해서 Check 함수가 호출되고, Threaddetect 프로그램은 사전에 구해놓은 스레드 ID와 비교 후 값이 맞지 않으면 다른 스레드 ID에서 실행한 것으로 판단하고 프로그램을 아래와 같이 종료하게 된다.

반응형