1.前言 在非托管的C++里面,用户态下,Main函数入口是毫无疑问的首先运行。那么如果想要在Main入口前运行一些函数,应该怎么做呢?本篇看下
2.概括 其实PE里面提供了一个TLS回调函数,它是一个全局的可以存储全局变量,全局方法的RVA数据段。可以利用这个特点,来在Main之前运行方法。也可以利用这个特点,进行反调试机制。比如在TLS回调里面一旦发现调试,就直接退出程序。 这里搞一个小例子看下,以下x86环境下:
#include <windows.h>
#include<stdio.h>
//x86
#pragma comment(linker, "/INCLUDE:__tls_used")
//x64
//#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);
}
//x86
#pragma data_seg(".CRT$XLX")
PIMAGE_TLS_CALLBACK pTLS_CALLBACKs[] = { TLS_CALLBACK1, TLS_CALLBACK2, 0 };
#pragma data_seg()
//x64
//#pragma const_seg (".CRT$XLB")
//const
//PIMAGE_TLS_CALLBACK pTLS_CALLBACKs[] = { TLS_CALLBACK1, TLS_CALLBACK2, 0 };
//#pragma const_seg ()
DWORD WINAPI ThreadProc(LPVOID lParam)
{
char szMsg[20] = { 0, };
wsprintfA(szMsg, "ThreadProc() start\n");
print_console(szMsg);
char szMsg1[20] = { 0, };
wsprintfA(szMsg1, "ThreadProc() end\n");
print_console(szMsg1);
return 0;
}
int main(void)
{
HANDLE hThread = NULL;
char szMsg[20] = { 0, };
wsprintfA(szMsg, "main() start\n");
print_console(szMsg);
hThread = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);
WaitForSingleObject(hThread, 60 * 1000);
CloseHandle(hThread);
char szMsg1[20] = { 0, };
wsprintfA(szMsg1, "main() end\n");
print_console(szMsg1);
getchar();
return 0;
}
运行结果:
TLS_CALLBACK1() : DllHandle = F80000, Reason = 1
TLS_CALLBACK2() : DllHandle = F80000, Reason = 1
main() start
TLS_CALLBACK1() : DllHandle = F80000, Reason = 2
TLS_CALLBACK2() : DllHandle = F80000, Reason = 2
ThreadProc() start
ThreadProc() end
TLS_CALLBACK1() : DllHandle = F80000, Reason = 3
TLS_CALLBACK2() : DllHandle = F80000, Reason = 3
main() end
可以看到在main函数之前,运行了TLS_CALLBACK1和TLS_CALLBACK2函数。
比如可以在TLS_CALLBACK1函数里面添加如下检测是否调试的代码:
void NTAPI TLS_CALLBACK1(PVOID DllHandle, DWORD Reason, PVOID Reserved)
{
if (IsDebuggerPresent() == 1)
{
exit(1);
}
char szMsg[80] = { 0, };
wsprintfA(szMsg, "TLS_CALLBACK1() : DllHandle = %X, Reason = %d\n", DllHandle, Reason);
print_console(szMsg);
}
如果调试当前程序,则IsDebuggerPresent()==1,直接退出当前程序。这是最简单的反调试思路。