TLS (Thread Local Storage)
- 스레드별로 가지는 독립된(고유한) 데이터 저장 공간
- 각각의 스레드는 고유한 스택을 갖기 때문에 스택 변수(지역 변수)는 스레드 별로 고유하다. 그러나 정적 변수와 전역 변수의 경우에는 프로세스 내의 모든 스레드에 의해서 공유된다 (= 공유 메모리 영역에 놓인다)
- 정적(static) 변수, 전역(global) 변수를 각각의 스레드에게 독립적으로 만들어 주고 싶을 때 사용한다
- 같은 문장(context)을 실행하고 있지만 실제로는 스레드 별로 다른 주소 공간을 상대로 작업하게 됨
TLS 콜백 함수
- 프로세스의 스레드가 생성/종료될 때마다 자동으로 호출되는 콜백 함수
- 쓰레드 전용으로 쓰이는 'TLS'와 특정 이벤트에 의해 호출되는 'Callback 함수'의 특성이 합쳐졌다고 볼 수 있다
- 프로세스의 메인 스레드가 생성될 때에도 콜백 함수가 호출되므로, EP 코드보다 먼저 호출된다
- 안티 디버깅 기법으로 사용된다
typedef VOID (NTAPI *PIMAGE_TLS_CALLBACK)(PVOID DllHandle, DWORD dwReason, PVOID Reserved);
* NTAPI (Native API) : Windows NT kernel-mode 함수들의 호출 규약을 나타내기 위한 매크로, WINAPI, __stdcall 매크로와 유사하다
* DllHandle: TLS 콜백 함수를 포함하는 DLL의 핸들
* dwReason: TLS 콜백 함수가 호출된 이유, DLL_PROCESS_ATTACH(1), DLL_PROCESS_DETACH(0), DLL_THREAD_ATTACH(2), DLL_THREAD_DETACH(3) 4가지의 값을 가질 수 있음
HelloTLS.exe 분석
- TLS 기능을 사용하도록 프로그래밍 시, IMAGE_NT_HEADER.DataDirectory[9]이 세팅됨
- RAW=7910 (RVA=9310)에는 IMAGE_TLS_DIRECTORY 구조체가 존재한다
* AddressOfCallBacks 멤버변수에는 TLS 콜백함수 주소 배열의 VA가 담겨있다
- VA=408114, RVA=8114, RAW=6714에 가보면 TLS 콜백 함수 주소 배열이 존재한다
* TLS 콜백함수가 0x401000 하나 등록되어 있다
- VA=0x401000 함수를 Ollydbg로 봐보면 아래와 같다
cf) kernel32!IsDebuggerPresent API 내부 구현은 아래와 같다. FS:[18]로 TEB를 구하고, [EAX+30]으로 PEB를 구하고, [EAX+2]로 BeingDebugged 멤버의 값을 확인한다
TlsTest.exe
#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("ThreadProc() start\n");
print_console("ThreadProc() end\n");
return 0;
}
int main(void)
{
HANDLE hThread = NULL;
print_console("main() start\n");
hThread = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);
WaitForSingleObject(hThread, 60*1000);
CloseHandle(hThread);
print_console("main() end\n");
return 0;
}
- #pragma comment(linker, "/INCLUDE:__tls_used")를 통해 링커로 하여금 "__tls_used" 심볼을 포함시키도록 하며
- #pragma data_seg(".CRT$XLX") 를 통해 .CRT$XLX 섹션에 (this section name is convention for Microsoft Visual Stuido compiler&linker when working with TLS callbacks) 콜백 함수의 배열을 올바르게 추가한다
- TLS Callback은 메인스레드보다 먼저 호출되므로 printf()보다 WriteConsole() Low-Level API를 사용하자
TLS 콜백함수 분석
- System startup breakpoint (in ntdll.dll)에서 멈추도록 하여 TLS Callback 주소에 bp 걸고 달리거나
- 아예 TLS callback option에 체크하여 분석
HXD를 이용한 TLS 콜백 함수 추가
1. 마지막 섹션(.rsrc라고 하자)의 크기를 0x200 확장시킴 // File Alignment 고려
2. .rsrc의 섹션 헤더 수정
- Size of Raw Data + 0x200
- Characteristics에 MEM_SCN_CNT_CODE(0x20), MEM_SCN_CNT_EXECUTE/WRITE(0xA0000000) 추가
3. IMAGE_OPTIONAL_HEADER.DataDirectory[9]의 RVA, Size 세팅
4. .rsrc의 확장시킨 영역에 IMAGE_TLS_DIRECTORY 구조체(size 0x18) 세팅, TLS 함수주소 배열 세팅, TLS 함수 코드 세팅
'security > 리버싱핵심원리' 카테고리의 다른 글
SEH [Structured Exception Handling] (0) | 2023.04.07 |
---|---|
TEB & PEB 정리 (0) | 2023.04.05 |
CreateRemoteThread()를 이용한 DLL Injetcion의 한계 (0) | 2023.04.04 |
ASLR 제거 (0) | 2023.04.04 |
API 코드 패치를 이용한 API hooking - notepad.exe 프로세스 은폐하기 (2) (0) | 2023.04.03 |