ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [리버싱 핵심원리 study] 45장 TLS 콜백 함수
    Reverse Engineering 2021. 2. 13. 13:45

    TLS 콜백 함수는 EP 코드보다 먼저 실행되어 안티 디버깅 기법으로 활용되기도 한다.

     

    1. 실습 #1 - HelloTls.exe

    예제 파일인 HelloTls.exe를 실행시켜보자.

    HelloTls.exe

    위와 같이 메시지 박스를 출력하는 간단한 프로그램이다. 이번에는 OllyDbg로 HelloTls.exe를 열어보자.

     

    HelloTls.exe

    그러자 위와 같은 메시지 박스가 나타나며 확인 버튼을 누르면 그대로 프로세스가 종료된다. 일반적인 프로그램과 동작이 다른 이유는 EP 코드보다 먼저 실행되는 TLS 콜백 함수에서 Anti-Debugging 코드가 실행되었기 때문이다. 해당 원리를 이해하지 못하면 디버깅을 진행할 수가 없다.

     

    2. TLS

    TLS란 Thread Local Storage의 준말로 스레드별로 독립된 데이터 저장 공간을 가리킨다. TLS 기능을 사용하도록 프로그래밍하면 다음과 같이 PE 헤더의 IMAGE_DATA_DIRECTORY[9]에 TLS 항목이 세팅된다.

     

    IMAGE_DATA_DIRECTORY[9] (TLS Table)

    위에 나타난 RVA 9310 주소에는 다음과 같은 IMAGE_TLS_DIRECTORY 구조체가 있다.

     

    typedef struct _IMAGE_TLS_DIRECTORY64 {
    	ULONGLONG StartAddressOfRawData;
        ULONGLONG EndAddressOfRawData;
        ULONGLONG AddressOfIndex;
        ULONGLONG AddressOfCallBacks;
        DWORD SizeOfZeroFill;
        DWORD Characteristics;
    } IMAGE_TLS_DIRECTORY64;
    typedef IMAGE_TLS_DIRECTORY64* PIMAGE_TLS_DIRECTORY64;
    
    typedef struct _IMAGE_TLS_DIRECTORY32 {
    	DWORD StartAddressOfRawData;
        DWORD EndAddressOfRawData;
        DWORD AddressOfIndex;
        DWORD AddressOfCallBacks;
        DWORD SizeOfZeroFill;
        DWORD Characteristics;
    } IMAGE_TLS_DIRECTORY32;
    typedef IMAGE_TLS_DIRECTORY32* PIMAGE_TLS_DIRECTORY32;

     

    PEView를 통해 HelloTls.exe의 IMAGE_TLS_DIRECTORY 구조체 멤버 값을 확인해보자.

    PEView : IMAGE_TLS_DIRECTORY

    이 중 눈여겨 보아야 할 멤버는 AddressOfCallbacks로 TLS 콜백 함수 주소 배열을 나타낸다. (배열의 끝은 NULL) 프로세스가 시작되면 함수 주소 배열에 저장된 함수를 하나씩 호출하게 된다.

     

    3. TLS 콜백 함수

    TLS 콜백 함수를 책에서는 다음과 같이 정리하고 있다.

     

    "TLS 콜백 함수란 프로세스의 스레드가 생성/종료될 때마다 자동으로 호출되는 콜백 함수입니다. 흥미로운 점은 메인 스레드가 생성될 때도 이 콜백 함수가 호출됩니다. 그것도 EP 코드보다 더 먼저 호출됩니다. 바로 이 특징이 안티 디버깅 기법으로 사용됩니다."

     

    TLS 콜백 함수의 정의는 다음과 같다.

    typedef void
    (NTAPI *PIMAGE_TLS_CALLBACK) {
    	PVOID DllHandle,
        DWORD Reason,
        PVOID Reserved
    );

     

    이는 DllMain()의 함수의 형태와 비슷하다. DllHandle은 모듈의 핸들을 나타내며 Reason은 TLS 콜백 함수가 호출된 이유를 나타내며 아래와 같은 값들을 가질 수 있다.

    #define DLL_PROCESS_ATTACH 1
    #define DLL_THREAD_ATTACH 2
    #define DLL_THREAD_ATTACH 3
    #define DLL_PROCESS_DETACH 0

     

    4. 실습 #2 - TlsTest.exe

    예제 파일인 TlsTest.exe를 통해 TLS 콜백 함수를 등록하는 실습을 진행해보자.

     

    - TlsTest.cpp

    #include <Windows.h>
    
    #pragma comment(linker, "/INCLUDE:__tls_used")
    
    void print_console(char* szMsg)
    {
    	HANDLE hStdout = GetStdHandle(STD_OUTPUT_HANDLE);
    	WriteConsoleA(hStdout, szMsg, strlen(szMsg), NULL, NULL);
    }
    
    void NTAPI TLS_CALLBACK1(PVOID DllHandle, DWORD Reason, PVOID Reserved)
    {
    	char szMsg[80] = { 0, };
    	wsprintfA(szMsg, "TLS_CALLBACK1() : DllHandle = %X, Reason = %d\n", DllHandle, Reason);
    	print_console(szMsg);
    }
    
    void NTAPI TLS_CALLBACK2(PVOID DllHandle, DWORD Reason, PVOID Reserved)
    {
    	char szMsg[80] = { 0, };
    	wsprintfA(szMsg, "TLS_CALLBACK2() : DllHandle = %X, Reason = %d\n", DllHandle, Reason);
    	print_console(szMsg);
    }
    
    #pragma data_seg(".CRT$XLX")
    PIMAGE_TLS_CALLBACK pTLS_CALLBACKs[] = { TLS_CALLBACK1, TLS_CALLBACK2, 0 };
    #pragma data_seg()
    
    DWORD WINAPI ThreadProc(LPVOID lParam)
    {
    	print_console((char*)"ThreadProc() start\n");
    	print_console((char*)"ThreadProc() end\n");
    
    	return 0;
    }
    
    int main(void)
    {
    	HANDLE hThread = NULL;
    
    	print_console((char*)"main() start\n");
    
    	hThread = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);
    	WaitForSingleObject(hThread, 60 * 1000);
    	CloseHandle(hThread);
    
    	print_console((char*)"main() end\n");
    
    	return 0;
    }

    소스코드에서 두 개의 TLS 콜백 함수 TLS_CALLBACK1, TLS_CALLBACK2를 등록하고 DllHandle, Reason 값을 출력하도록 하였다. TlsTest.exe를 실행하면 다음과 같은 결과가 나타난다.

     

    TlsTest.exe 실행화면

    - DLL_PROCESS_ATTACH (Reason = 1)

    main 스레드가 호출되기 전에 등록된 TLS 콜백함수들이 호출된다.

     

    - DLL_THREAD_ATTACH (Reason = 2)

    TLS 콜백 함수들이 종료된 후, main 함수가 실행된다. main 함수 내부 CreateThread를 이용해 스레드 생성을 하는데 이때 마찬가지로 TLS 콜백 함수들이 호출된다.

     

    - DLL_THREAD_DETACH (Reason = 3)

    ThreadProc() 함수가 종료된 후, TLS 콜백 함수들이 호출된다.

     

    - DLL_PROCESS_DETACH (Reason = 0)

    main() 함수가 종료되면서 마지막으로 TLS 콜백 함수들이 호출된다.

     

    5. TLS 콜백 디버깅

    일반적인 방법으로 OllyDbg로 프로세스를 열게되면 TLS 함수 내에 안티 디버깅 코드가 존재할 수 있어서, System breakpoint에서 멈추도록 옵션을 조정해주면 된다. OllyDbg2의 경우 TLS callback에서 멈추는 기능도 있어서 바로 TLS 콜백 함수부터 디버깅을 진행하도록 옵션을 설정할 수 있다.

     

    6. Comment

    Win32 API 공부를 하던 도중 TLS 관련된 내용도 있어 어느정도 수월하게 실습을 진행할 수 있었다. 마지막 부분에서는 직접 수작업으로 TLS 기능을 추가하는 내용도 있었는데 PEB 관련된  내용이 있어 해당 부분을 공부한 후 다시 정리해봐야겠다. 

    반응형

    댓글

Designed by Tistory.