Thread Local Storage는 스레드마다 독립적으로 가지는 저장 공간이다. 스레드 내에서도 프로세스의 글로벌 데이터와 정적 static 데이터를 로컬 데이터처럼 독립적으로 취급하고자 할 때 사용하게 된다.
콜백함수는 특정 동작시 자동으로 호출되는 함수였다.
프로세스의 스레드가 생성 혹은 종료될 때마다 자동으로 호출되는 함수를 TLS 콜백이다.
이 TLS 콜백은 스레드의 생성, 종료 총 2번 동안 호출된다.
근데 중요한 것은 프로세스의 제일 Main Thread를 실행할 때도 TLS 콜백 함수가 먼저 호출된다는 것이다.
즉 EP코드가 실행되기 전에 TLS 콜백이 먼저 호출된다는 특징이, 개발자들의 안티 디버깅 기법으로 사용된다고 한다.
EP보다 먼저 실행되면 왜 안티디버깅인지는 차차 알아보자
ㄴ 알아냈다.
만약 이 TLS 콜백 함수의 코드에다가 디버깅을 방지하려는 코드를 집어넣으면, 디버거로 이 프로세스를 열어보려로 했을 때 TLS 콜백이 호출되고, 그 콜백함수에 존재하는 안티 디버깅 코드가 실행되면서 디버깅을 차단시켜버릴 수 있다.
그냥 실행할 때는 안티디버깅 코드가 의미가 없으니까 넘어가게 되면서 일반 사용은 별 문제가 없을 것이고.
실습에 주어진 HelloTls.exe를 그냥 클릭해서 실행하면 이런 메시지박스가 나온다.

근데 만약에 디버거에 올려놓고 실행하면 다른 메시지박스가 나온다.
TLS 기능을 사용하도록 했다면, PE헤더에서 TLS Table이 세팅된다.
이건 NT헤더 중에서 IMAGE_OPTIONAL 헤더에 가면 IMAGE_DATA_DIRECTORY[0]~[15] 있었지. 그 중에서 [9]에 해당한다.
제일 밑에 보이져?
저 주소에 가면 IMAGE_TLS_DIRECTORY가 나온다. 이 디렉토리는 보통 .rdata 섹션에 가면 찾을 수 있다.
typedef struct _IMAGE_TLS_DIRECT0RY64 {
ULONGLONG StartAdd ressOfRawData;
ULONGLONG EndAdd ressOfRawData;
ULONGLONG AddressOfIndex; // PDWORD
ULONGLONG AddressOfCallBacks; // PIMAGE_TLS_CALLBACK *;
DWORD SizeOfZeroFill;
DWORD Characteristics;
} IMAGE_TLS_DIRECT0RY64;
typedef IMAGE_TLS_DIRECT0RY64 * PIMAGE_TLS_DIRECT0RY64;
typedef struct _IMAGE_TLS_DIRECT0RY32 {
DWORD StartAddressOfRawData;
DWORD EndAddressOfRawData; //
DWORD AddressOfIndex; // PDWORD
DWORD AddressOfCallBacks // PIMAGE_TLS_CALLBACK * 』
DWORD SizeOfZeroFill;
DWORD Characteristics;
} IMAGE_TLS_IDIRECT0RY32;
typedef IMAGE_TLS_DIRECT0RY32 * PIMAGE TLS DIRECT0RY32;
#ifdef _WIN64
typedef IMAGE_TLS_DIRECT0RY64 IMAGE_TLS_DIRECTORY;
typedef PIMAGE_TLS_DIRECT0RY64 PIMAGE_TLS_DIRECTORY;
#else
typedef IMAGE_TLS_DIRECT0RY32 IMAGE_TLS_DIRECTORY;
typedef PIMAGE_TLS_DIRECT0RY32 PIMAGE_TLS_DIRECTORY;
#endif
너무 빡세다
암튼 중요한 멤버는 Address of Call Backs이다. TLS 콜백 함수의 주소들이 VA값으로 배열을 이루고 있다. 그니까 한 프로세스에 TLS 콜백을 여러개 등록할 수 있고, 그 배열의 끝은 8바이트 NULL로 채운다.
typedef VOID
(NTAPI *PIMAGE_TLS_CALLBACK) (
PVOID DllHandle,
DWORD Reason,
PVOID Reserved
);
저 Reason은 tls콜백함수가 호출된 이유를 말한다.
스레드 생성/종료/프로세스 생성/종료
#define DLL_PROCESS,ATTACH 1
#define DLL_THREAD_AtTACH 2
#define DLL_THREAD_DETA어 3
#define DLL_PROCES引DETA어 0
암튼 이 정의는 예전의 DllMain 정의랑 비슷하다.
BOOL WINAPI DUMain(
__in HINSTANCE hinstDLL,
__in DWORD fdwReason,
__in LPVOID IpvReserved
TlsTest라는 코드의 일부를 보자
두개의 콜백 함수가 있고 각각 Dll Handle하고 Reason을 출력해준다.

메인에서는 스레드를 생성하는게 다다.
프로세스가 이제 시작되어 메인함수가 시작되려고 할 때 TLS 콜백들을 호출한다.
Reason은 1이겠지.
TLS 콜백 함수들이 다 끝나면 메인이 실행된다.그리고 메인을 따라서 사용자 스레드가 생성되면 Reason2로 TLS 콜백들이 호출된다.
TLS 콜백이 끝나면 ThreadProc( ) 스레드 함수가 실행될 것이고, 그 스레드 함수가 끝나면 reason3로 TLS 콜백이 호출된다.
메인함수마저 종료되면 마지막으로 Reason 0으로 TLS 콜백이 호출된다.
콜백 함수는 2개니까 총 8번의 호출이 있다
얘를 디버깅할 때는 디버거에서 브레이크 포인트를 TLS콜백에 멈추도록 설정을 변경하거나, ntdll.dll모듈 내부의 System Startup에서 브레이크를 걸도록 설정해야 한다.
뭐가 됐든, 처음 멈추는 bp가 EP코드여서는 tls 콜백을 디버깅 할 수 없지.
이제 이 tls 콜백 기능을 직접 추가하는 것을 보자.
Hello.exe를 HelloTls.exe 정도로 바꿔보자
IMAGE_TLS_DIRECTORY와 TLS 콜백 함수를 파일의 어디에 넣을 지 오프셋을 정해야 한다. 프로세스 아니고 파일이다.
- 섹션 끝 빈 영역
- 마지막 섹션 크기 확장
- 마지막에 새로운 섹션 추가
아래로 갈수록 난이도가 높다. 두번째 정도를 볼까
마지막 섹션이 .rsrc 섹션임을 PE View로 확인햇다면 마지막 섹션의 헤더를 보자.
File Alignment의 배수만큼 섹션을 추가해도 된다.
그럼 변경되는 것이
- 전체 파일 사이즈
- 마지막 섹션 사이즈( 마지막 섹션 헤더의 RawDataSize)
그리고 추가로 콜백 함수를 만들려면 메모리 EXECUTE 속성을 추가해야 해서
마지막 섹션 헤더의 Characteristics를 E0000060으로 해야한다.
CODE + INITALIZED DATA+MEM_EXECUTE+MEM_READ+MEM_WRITE
TLS Table도 설정해야 한다. NT헤더의 IMAGE_OPTIOANL HEADER에서 [9]인덱스 데이터 디렉토리 가서 TLS Table에 콜백함수가 있을 RVA를 적어야 한다.
TLS 함수가 있을 파일 오프셋을 방금 우리가 만들었지.
마지막 섹션에 새로 추가한 File Alignment의 시작
그걸 RVA로 변경하면 된다. PE View가 다 바꿔준다.
Size도 변경해야 하는데 IMAGE_TLS_DIRECTORY 구조체의 크기인 0x18 (24바이트)라고 적는다.
이 24바이트 구조체를 세팅해야지. 이렇게 바꾸래

아마 교재에서 임의로 만든게 아닐까
저 Address of Call back에 아까 TLS함수들의 주소가 나열되어 있다고 했지. 그 배열의 offset으로 가서 TLS 콜백 함수 주소를 적어주면 되고 마지막에 null 8바이트로 끝나는지 확인하면 된다.
콜백함수가 호출될 수 있는 구조를 다 만들어놨으니 콜백함수를 프로그래밍하면 끝이다.
TLS 콜백함수가 있는 주소에 가서 Assemble 기능으로 편집하면 된다.