首页 > 其他分享 >TLS回调函数

TLS回调函数

时间:2023-01-23 20:23:20浏览次数:37  
标签:TLS 回调 函数 NULL PVOID Reason pragma main

TLS回调函数

TLS回调函数是个啥

TLS全称Thread Local Storage,中文“线程局部存储”。
TLS是各线程的独立的数据存储空间,使用TLS技术可在线程内部独立使用或修改进程的全局数据或静态数据,就像对待自身的局部变量一样。
Buuuuuuuuut,我们今天要来看一下TLS其他的用处。

TLS的特性与利用

TLS的基本样式

TLS的代码实现基本样式如下:

#include <windows.h>
#include <iostream>
#pragma comment(linker, "/INCLUDE:__tls_used")//x64模式下写_tls_used

VOID NTAPI TLS_CALLBACK(PVOID DllHandle, DWORD Reason, PVOID Reserved)
{
        //在这里写你想做的事情
}

//它能跑,你就别管它在干啥
#pragma data_seg(".CRT$XLX")
PIMAGE_TLS_CALLBACK tlsCalls[] = { TLS_CALLBACK, 0 };
#pragma data_seg()

int main()
{

        return 0;
}

每一句都是什么意思呢?不知道,背下来就好了。
P.S.需要在x86的模式下生成.exe文件。我测试的时候在x64模式下程序会直接无视TLS。

TLS被调用的原因

TLS会因为以下4种原因被调用:

即进程、线程的启动前和结束后。
这种特性决定了它经常被用于反调试,你的程序会在main()函数之前就被莫名其妙地中止了。

测试你的TLS回调函数

运行下面的代码,看看你的输出

#include <windows.h>
#include <iostream>
#pragma comment(linker, "/INCLUDE:__tls_used")

VOID NTAPI TLS_CALLBACK(PVOID DllHandle, DWORD Reason, PVOID Reserved)
{
	if (Reason == DLL_PROCESS_ATTACH)
	{
		printf("running TLSCallBack before main()\n");
	}
	if (Reason == DLL_PROCESS_DETACH)
	{
		printf("running TLSCallBack after main()\n");
	}
	if (Reason == DLL_THREAD_ATTACH)
	{
		printf("running TLSCallBack before thread\n");
	}
	if (Reason == DLL_THREAD_DETACH)
	{
		printf("running TLSCallBack after thread\n");
	}
}

#pragma data_seg(".CRT$XLX")
PIMAGE_TLS_CALLBACK tlsCalls[] = { TLS_CALLBACK, 0 };
#pragma data_seg()

VOID my_thread()
{
	printf("thread begin\n");
	printf("thread end\n");
}

int main()
{
	printf("main() begin\n");
	HANDLE hThread = CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)my_thread, NULL, NULL, NULL);
	WaitForSingleObject(hThread, 1000);
	printf("main() end\n");
	return 0;
}

理论上应该会输出4遍running TLSCallBack balabala。然鹅,我这里只能输出成这样:

利用TLS藏东西

我们在写代码的时候总有一些事情是不想让别人发现的,比如srand()之类的,或者把真正的加密函数藏起来。我们就可以把它放在TLS中运行。如果你的TLS是可以在main()之后运行的。那么你可以藏成这样:

#include <windows.h>
#include <iostream>
#pragma comment(linker, "/INCLUDE:__tls_used")

VOID NTAPI TLS_CALLBACK0(PVOID DllHandle, DWORD Reason, PVOID Reserved)
{
	if (Reason == DLL_PROCESS_ATTACH)
	{
		//first half of encryption code
	}
	if (Reason == DLL_PROCESS_DETACH)
	{
		//last half of encryption code
                exit(0);
	}
}

VOID NTAPI TLS_CALLBACK1(PVOID DllHandle, DWORD Reason, PVOID Reserved)
{
	//change something that will be used in the last half of encryption code
	ExitProcess(-1);
}

#pragma data_seg(".CRT$XLX")
PIMAGE_TLS_CALLBACK tlsCalls[] = { TLS_CALLBACK0, TLS_CALLBACK1, 0 };
#pragma data_seg()

int main()
{
	//fake encryption code
	return 0;
}

这样程序在执行完TLS_CALLBACK1后会因为ExitProcess(-1)而结束进程,即跳过整个main()函数去执行TLS_CALLBACK0。
如果你像我一样,main()函数后面无法运行TLS,那么你可以藏成这样:

#include <windows.h>
#include <iostream>
#pragma comment(linker, "/INCLUDE:__tls_used")

VOID NTAPI TLS_CALLBACK0(PVOID DllHandle, DWORD Reason, PVOID Reserved)
{
	if (Reason == DLL_PROCESS_ATTACH)
	{
		//first half of encryption code
	}
}

VOID NTAPI TLS_CALLBACK1(PVOID DllHandle, DWORD Reason, PVOID Reserved)
{
	if (Reason == DLL_THREAD_ATTACH)
	{
		//last half of encryption code
	}
	exit(0);
}

#pragma data_seg(".CRT$XLX")
PIMAGE_TLS_CALLBACK tlsCalls[] = { TLS_CALLBACK0, TLS_CALLBACK1, 0 };
#pragma data_seg()

void my_thread()
{
	//fake encryption code
}

int main()
{
	HANDLE hThread = CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)my_thread, NULL, NULL, NULL);
	return 0;
}

由于exit(0)的存在,整个函数不会执行线程中的任何一行代码。

邪恶的想法

上述写法已经可以让我们神不知鬼不觉地绕过展示出来的代码而去执行藏起来的东西。如果再在真正的加密函数中用上Windows系统编程特有的try-except模块,那么你将创造出一个IDA里处处都是假代码的、丧心病狂的、恶心至极的题目。hiahiahiahia......

TLS的防治

识别TLS的存在

我们可以在main()函数的第一行代码/创建线程的代码处下个断点,然后动调,发现程序退出了/输出了或者进行操作了都是TLS存在的特征。

解决TLS的问题

想多了,我不会。不过好消息是IDA会。
在IDA的函数窗口搜索TLS就会显示出IDA帮你分析出来的TLS回调函数:

然后我们就可以去里面慢慢读了。

结束语

怎么做恶心的题不太会,但怎么出恶心的题倒是会了......
阿巴阿巴阿巴......

标签:TLS,回调,函数,NULL,PVOID,Reason,pragma,main
From: https://www.cnblogs.com/Clovershrub/p/17065294.html

相关文章

  • 冒泡排序函数(算法)
    比较相邻的元素。如果第一个比第二个大,就交换他们两个。对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。针对所有的元素重......
  • Day06 - 匿名函数和文件操作
    1.匿名函数lambdadef函数名(参数列表): 函数体'''匿名函数'''#万物皆对象#对象就会有内存地址,就会有一个引用#通过这个引用就可以找到该对象并使用它d......
  • Day05 - 内置函数和参数
    0.列表推导式格式:列表变量=[表达式for变量inrange(10)]表达式中需要使用后面的变量'''列表推导式创建一个具有一百个数字的列表'''#c_l=[]#f......
  • python一个函数简单接收命令行参数
    需要使用sys和getopt库defarg(_,__):#接收命令行参数importsysimportgetopt'''参数:_:短参数str,列如:-f-g-p__:长参数list,列如:['file','......
  • Day02函数和条件表达
    0.格式化字符串'''格式化字符串'''print(1)print(1,2,3,4)a=1b=2.1123c='hello's='a=%db=%fc=%s'%(a,b,c)s+='--world'print(s)......
  • 3.Prometheus计算函数
    1.Prometheus监控cpu构思2.函数rate()3.函数irate()4.函数rate()及irate()区别5.函数increase()6.函数sum()7.函数by8.topk()9.count()1.Prometheus监控cpu构思%......
  • 聚合函数
    聚合函数(多行处理函数)当行处理函数:接受一个参数,返回一个结果多行处理函数:接受多个参数,返回一个结果什么是聚集函数聚集函数作用于一组数据,并对一组数据返回一个值常用......
  • 数据库基础篇(函数,约束)
    函数字符串函数数值函数日期函数流程函数字符串函数常用函数:函数功能CONCAT(s1,s2,…,sn)字符串拼接,将s1,s2,…,sn拼接成一个字符串LOWER(str)将字符串全部转为小写UP......
  • 软件的基本是要处理好”算法“及其基础(三)delphi 10.4.1字符串工具单元及其函数
     //:关于字符串的定义: _RawByteStr=RawByteString; {$IFDEFNEXTGEN}   UTF8String=type_AnsiString(65001);   RawByteString=type_AnsiString($f......
  • 读函数式编程思维笔记04_语言与范式_模式与重用
    1. 语言的分类1.1. 静态类型1.1.1. 要求我们事先指定变量和函数的类型1.2. 动态类型1.2.1. 允许推迟指定类型1.3. 强类型1.3.1. 变量“知道”自己的类型1.3......