利用TLS反调试
今天说一下利用TLS提供的静态绑定回调函数来反调试
原理
首先说一下tls为什么可以反调试
一般我们调试时候是断点在oep(pe文件的程序入口点)上的,而tls回调函数会在加载可执行程序之前调用
首先简单描述一下程序的加载过程
- 创建process对应的内核对象
- 读取pe文件
- 拉伸到内存中
- 加载模块修复各种表如iat表
- 创建线程
- 将线程context的eip指向oep
- 程序执行
上面就是简述的程序加载过程,如果有一种机制可以在指向oep之前判断是否有人在我的process中设置dr系列寄存器(调试寄存器)那么就可以直接退出进程,以此来达到反调试的目的
tls是什么
TLS主要是为了解决多线程中变量的同步问题
进程中的全局变量和函数内定义的静态(static)变量,是每个线程都可以访问的共享变量。只要有任何一个线程修改了共享变量,其他所有线程中的共享变量也会同步被修改
乍看之下,这种方式使得数据交换十分的便捷,无需额外的通信就可以实现数据之间的交换。但是当共享变量要修改前,需要对该变量上锁,其他要访问该共享变量的线程必须等共享变量修改完成释放锁后才能继续执行。这期间线程等待所耗费的资源就是所谓的开销。关于同步异步、并发并行、互斥信号量等基础知识这里不再赘述
TLS全称Thread Local Storage,即线程局部存储。TLS是一种方法,通过这种方法,给定多线程进程中的每个线程可以分配位置来存储特定于线程的数据。通过TLS API (TlsAlloc)支持动态绑定(运行时)特定于线程的数据。Win32和Microsoft c++编译器现在除了现有的API实现外,还支持静态绑定(加载时)每个线程数据。
这边我们只说静态绑定
程序可以提供一个或多个TLS回调函数,以支持TLS数据对象的附加初始化和终止
如果有多个回调函数,则按其地址在数组中出现的顺序调用每个函数。空指针终止数组。空列表是完全有效的(不支持回调),在这种情况下,回调数组只有一个成员时最后要跟一个0,类似于pe文件结构中的一些表结束的方式
下面是代码:
#include <Windows.h>
#include <stdio.h>
#ifdef _WIN64 //64位
#pragma comment (linker, "/INCLUDE:_tls_used")
#pragma comment (linker, "/INCLUDE:tls_callback_func")
#else //32位
#pragma comment (linker, "/INCLUDE:__tls_used")
#pragma comment (linker, "/INCLUDE:_tls_callback_func")
#endif
void __stdcall tls_callback(PVOID handle, DWORD type, PVOID data) {
BOOL res;
CheckRemoteDebuggerPresent(GetCurrentProcess(), &res);
if (res)
ExitProcess(0);
}
#ifdef _WIN64 //64位
#pragma const_seg(".CRT$XLF")
EXTERN_C const
#else
#pragma data_seg(".CRT$XLF") //32位
EXTERN_C
#endif
PIMAGE_TLS_CALLBACK tls_callback_func[] = { tls_callback,0 };
#ifdef _WIN64 //64位
#pragma const_seg()
#else
#pragma data_seg() //32位
#endif //_WIN64
int main() {
printf("staring...\n");
system("pause");
return 0;
}
我们在tls函数中判断如果有人调试自己就直接退出
下面来看一下效果
这是正常执行的时候
这是放入od的时候
进程会直接退出导致无法调试