第45章:TLS回调函数

摘要:
8114-8000+6600=6714。Reason参数指示调用TLS回调函数的原因:AddressOfCallBacks字段添加回调函数。voidprint_ console(char*szMsg){HANDLEhStdout=GetStdHandle(STD_OUTPUT_HANDLE);print_console(szMsg);

TLS(Thread Local Storage,线程局部存储)回调函数(Callback Function)常用于反调试。

TLS 回调函数的调用运行要先于 EP 代码的执行。它是各线程独立的数据存储空间,可修改进程的全局/静态数据。

若在编程中启用了 TLS,PE 头文件中会设置 TLS 项目,即:IMAGE_TLS_Directory

第45章:TLS回调函数第1张

其中比较重要的成员是:AddressOfCallBacks  它指向回调函数数组地址

自己找一下试试:

第45章:TLS回调函数第2张

同时获取到相应的节区信息,计算: 9310 - 8000 + 6600 = 7910 .

第45章:TLS回调函数第3张

第四个元素即为 AddressOfCallBacks .

408114 明显超出了文件的大小,因此判断是加上了 ImageBase (即 VA ),在节区头查看 ImageBase 大小为 400000 .

因此 RVA 为 8114 ,8114 - 8000 + 6600 = 6714 .

第45章:TLS回调函数第4张

第45章:TLS回调函数第5张

第45章:TLS回调函数第6张

参数 Reason 表示调用 TLS 回调函数的原因:

第45章:TLS回调函数第7张

TLS设计的本意,是为了解决多线程程序中变量同步的问题,是 Thread Local Storage 的缩写,意为线程本地存储。线程本身有独立于其他线程的栈空间,因此线程中的局部变量不用考虑同步问题。多线程同步问题在于对全局变量的访问,TLS在操作系统的支持下,通过把全局变量打包到一个特殊的节,当每次创建线程时把这个节中的数据当做副本,拷贝到进程空闲的地址空间中。以后线程可以像访问局部变量一样访问该异于其他线程的全局变量的副本,而不用加同步控制。

第二个程序的代码( TlsTest.cpp ):

#include <windows.h>

#pragma comment(linker, "/INCLUDE:__tls_used")// 通过#pragma comment(Linker,"INCLUDE:_tls_used")显示的申明crt库中定义的_tls_used变量
                            // 以便向_tls_used!AddressOfCallBacks域添加回调函数。                              
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
", 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
", DllHandle, Reason);
    print_console(szMsg);
}

#pragma data_seg(".CRT$XLX")   //CRT 表明使用 C RunTime 机制,X 表示标识名随机,L 表示 TLS callback section, X 为 B-Y 之间的任意一个字母
    PIMAGE_TLS_CALLBACK pTLS_CALLBACKs[] = { TLS_CALLBACK1, TLS_CALLBACK2, 0 }; // 注意,此处将自定义函数放入了 TLS 表中。
#pragma data_seg()

DWORD WINAPI ThreadProc(LPVOID lParam)
{
    print_console("ThreadProc() start
");

    print_console("ThreadProc() end
");

    return 0;
}

int main(void)
{
    HANDLE hThread = NULL;

    print_console("main() start
");

    hThread = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);
    WaitForSingleObject(hThread, 60*1000);
    CloseHandle(hThread);

    print_console("main() end
");

    return 0;
}

程序运行截图:

第45章:TLS回调函数第8张

① DLL_Process_ATTACH  在主线程调用 Main()函数前,已经注册的两个函数会被调用执行。

② DLL_Thread_ATTACH   在 TLS 函数完成后,main()函数开始执行。在创建用户线程前(若有),TLS 回调函数会再次被执行。

③ DLL_Thread_Detach     TLS 函数执行完后,线程函数开始调用执行,线程函数结束后(若有)再次调用 TLS 函数。

④DLL_Process_Detach     main()函数结束后,再次调用 TLS 函数。

若不在主函数中调用 CreateThread 函数,则只会执行两次 TLS 。 

使用 win xp sp3 进行实际调试:

使用 OD ,将断点设置为 TLS 断点,程序会断在此处:

第45章:TLS回调函数第9张

第45章:TLS回调函数第10张

对比代码,并观察程序名可知,程序被断在了 TLS_CallBack1 处。继续运行程序至返回:

第45章:TLS回调函数第11张

第45章:TLS回调函数第12张

可以看到,通过间接调用,实现了 TLS CallBack1 的调用。继续执行程序:

第45章:TLS回调函数第13张

第45章:TLS回调函数第14张

可以看到,函数执行到了第一次执行的位置,表明会循环执行,直至函数执行完:

第45章:TLS回调函数第15张

第45章:TLS回调函数第16张

手工添加 TLS 回调函数

①将 TLS 结构体及 TLS 回调函数放在最后一个节区 or 其它节区的空白地区 or 添加新节区。

第45章:TLS回调函数第17张

属性增加了  Write (方便在调试时编写代码),Execute(可执行),CNT_CODE(区段包含代码)。

②设置 TLS 表

第45章:TLS回调函数第18张

③设置 IMAGE_TLS_DIRECTORY 结构体

StartAddressOfRawData:tls模板在内存中的起始VA,模板是用于创建线程时初始化TLS数据的;
EndAddressOfRawDataL:tls模板在内存中的结束VA;
AddressOfIndex:存储TLS索引的位置;

这三个值都可以指向 NULL 区域。

第四个值 CallBack 指向一个数组,以 四个字节的 NULL 结尾。数组每四个字节指向一个地址,存储着函数。

第45章:TLS回调函数第19张

有意思的是,在使用 StudyPe+ x86 修改后,在 win xp sp3 上无法运行,但是在 Win 10 上可以。经查证:

第45章:TLS回调函数第20张

使用软件进行修改时,增加最后一个节区 200 字节时,会自动将 VirtualSize 增大 1000,就算自己修改后回去后,可选头中的 ImageSize 还是自动增大 1000。

因此如果自己改变了 VirtualSize ,那么会造成 ImageSize 比实际大 1000,在 xp 上无法运行。

免责声明:文章转载自《第45章:TLS回调函数》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇GBK、GB2312、iso-8859-1之间的区别iptables常用命令及应用下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

sqlalchemy 数据库操作

1、简介 一种ORM 2、安装 pip3 install -i https://pypi.douban.com/simple sqlalchemy 3、连接数据库 from sqlalchemy importcreate_engine engine =create_engine( "mysql+pymysql://root:密码@1...

Linux 系统编程 学习:11-线程:线程同步

Linux 系统编程 学习:11-线程:线程同步 背景 上一讲 我们介绍了线程的属性 有关设置。这一讲我们来看线程之间是如何同步的。 额外安装有关的man手册: sudo apt-get install manpages-posix-dev -y 情景导入 我们都知道引入线程在合理的范围内可以加快提高程序的效率。但我们先来看看如果多线程同时访问一个临...

Bash 破壳漏洞Shellshock (CVE-2014-6271)复现分析

漏洞简介 GNU Bash 4.3及之前版本在评估某些构造的环境变量时存在安全漏洞,向环境变量值内的函数定义后添加多余的字符串会触发此漏洞,攻击者可利用此漏洞改变或绕过环境限制,以执行Shell命令。某些服务和应用允许未经身份验证的远程攻击者提供环境变量以利用此漏洞。此漏洞源于在调用Bash Shell之前可以用构造的值创建环境变量。这些变量可以包含代码,...

MFC中Carray的使用

CArray   需要包含的头文件 <afxtempl.h>   CArray类支持与C arrays相似的数组,但是必要时可以动态压缩并扩展。数组索引从0开始。可以决定是固定数组上界还是允许当添加元素时扩展当前的边界。内存对上界是连续地分配空间,甚至一些元素可为空。   和C arrays一样,CArray索引元素的访问时间是不变的,与数组大...

zabbix3.0.4 部署之七 (zabbix3.0.4 邮件报警) &amp;amp; 微信报警

[root@sv-zabbix ~]# cat /usr/local/zabbix/share/zabbix/alertscripts/sendEmail.sh #!/bin/bash#SMTP_server='smtp.mxhichina.com'    # SMTP服务器#username='11'     # 用户名#password='11'   ...

数据库并发处理

为什么要有锁? 我们都是知道,数据库中锁的设计是解决多用户同时访问共享资源时的并发问题。在访问共享资源时,锁定义了用户访问的规则。根据加锁的范围,MySQL 中的锁可大致分成全局锁,表级锁和行锁三类。在本篇文章中,会依次介绍三种类型的锁。在阅读本篇文章后,应该掌握如下的内容: 为什么要在备份时使用全局锁? 为什么推荐使用 InnoDB 作为引擎进行备份?...