TLS回调函数

练习:HelloTls.exe

  1. 运行练习程序,弹出一个消息框,单击确定按钮后,程序终止运行。
    在这里插入图片描述
  2. 在OllyDbg调试器中打开并运行HelloTls.exe文件。
    在这里插入图片描述
  3. 消息对话框中显示的内容于程序运行时显示的内容不同,单击确定按钮,HelloTls.exe进程随机终止。
    在这里插入图片描述
  4. 原因在于,程序运行EP代码前先调用了TLS回调函数,而该回调函数中含有反调试代码,使程序在被调试时弹出“Debugger Detected!“消息对话框。

TLS

  1. TLS是各线程的独立的数据存储空间,使用TLS技术可在线程内部独立使用或修改进程的全局数据或静态数据,就像对待自身的局部变量一样。
  2. IMAGE_DATA_DIRECTORY[9]
  • 若在编程中启用了TLS功能,PE头文件中就会设置TLS表项目。
    在这里插入图片描述
  • IMAGE_TLS_DIRECTORY结构体位于RVA 1A0.
    在这里插入图片描述
  • IMAGE_TLS_DIRECTORY结构体有2种版本,分别为32位版本与64位版本,使用PEView工具查看IMAGE_TLS_DIRECTORY结构体。
    在这里插入图片描述
  • 比较重要的成员为AddressOfCallbacks,该值指向含有TLS回调函数地址数组,这意味着可用向同一程序注册多个TLS回调函数。(数组以NULL值结束。)
    在这里插入图片描述
  • 该数组实际存储的就是TLS回调函数的地址,进程启动运行时,(执行EP代码前)系统会逐一调用存储在该数组该数组种的函数。

TLS回调函数

  1. TLS回调函数是指,没当创建/终止进程的线程时会自动调用执行的函数,创建进程的主线程时也会自动调用回调函数,且其调用执行先于EP代码,反调试技术利用的就是TLS回调函数的这一特征。
  2. IMAGE_TLS_CALLBACK
  • TLS回调函数的定义如下:
(NTAPI *PIMAGE_TLS_CALLBACK)(
	PVOID DllHandle,
	DWORD Reason,
	PVOID Reserved
);
  • 仔细观察TLS回调函数的定义可用发现,它于DllMain()函数的定义类似:
BOOL WINAPI DllMain(
	__in HINSTANCE hinstDLL,
	__in DWORD fdwReason,
	__in LPVOID lpReserved
);
  • 它们的参数顺序与含义都是一样的,其中,参数DllHandle为模块句柄(即加载地址),参数Reason表示调用TLS回调函数的原因,具体原因有4种。
#define DLL_PROCESS_ATTACH 1
#define DLL_THREAD_ATTACH 2
#define DLL_THREAD_DETACH 3
#define DLL_PROCESS_DETACH 0

TlsTest.exe

  1. TlsTest.exe程序的源代码。
#include <windows.h>

#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);
}

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

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

    print_console("ThreadProc() end\n");

    return 0;
}

int main(void)
{
    HANDLE hThread = NULL;

    print_console("main() start\n");

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

    print_console("main() end\n");

    return 0;
}
  • TlsTest.cpp源代码注册了2个TLS回调函数(TLS_CALLBACK1、TLS_CALLBACK2),它们也非常简单,只是将DllHandle与Reason这2个参数的值输出到控制台,然后终止退出。
  • main()函数也非常,创建用户线程(ThreadProc)后终止,main()与ThreadProc()内部分别将函数开始/终止日志输出到控制台。
    在这里插入图片描述
  1. DLL_PROCESS_ATTACH
  • 进程的主线程调用main()函数前,已经注册了TLS回调函数(TLS_CALLBACK1、TLS_CALLBACK2)会先被调用执行,此时Reason的值为1(DLL_PROCESS_ATTACH)。
  1. DLL_THREAD_ATTACH
  • 所有TLS回调函数完成调用后,main()函数开始调用执行,创建用户线程(ThreadProc)前,TLS回调函数会被再次调用执行,此时Reason=2(DLL_THREAD_ATTACH)。
  1. DLL_THREAD_DETACH
  • TLS回调函数全部执行完毕后,ThreadProc()线程函数开始调用执行,其执行完毕后Reason=3(DLL_THREAD_DETACH),TLS回调函数被调用执行。
  1. DLL_PROCESS_DETACH
  • ThreadProc()线程函数执行完毕后,一直等待线程终止的main()函数(主线程)也会终止,此时Reason=0(DLL_PROCESS_DETACH),TLS回调函数最后依次被调用执行。

调试TLS回调函数

  1. 修改OllyDbg(2.0)选项就可以调试TLS回调函数。
    在这里插入图片描述
  2. 重启应用程序,程序会暂停在TLS回调函数,
    在这里插入图片描述

手工添加TLS回调函数

  1. 本节的目标是直接修改Hello.exe文件(PE文件),为其添加TLS回调函数。
  2. 修改前的原程序。
  • 修改前的原程序为Hello.exe,它非常简单,运行时弹出一个消息框,然后终止退出。
    在这里插入图片描述
  1. 首先要确定IMAGE_TLS_DIRECTORY结构体与TLS回调函数放到文件的哪个位置。
  • 添加到节区末尾的空白区域。
  • 增加最后一个节区的大小。
  • 在最后添加新节区。
  1. 这里采用第二个方法,即增加最后一个节区的大小。
    在这里插入图片描述
  • 最后一个节区(.rsrc)的Pointer to Raw Data = 9000,Size of Raw Data = 200,所以PE头中的定义的文件整体大小为9200,考虑到要添加的代码与数据的大小,我们将最后一个节区大小增加到200(文件的大小增加到9400)。
    在这里插入图片描述
  1. 编辑PE文件头
  • 分别修改.rcrs节区头中Size of Raw Data与Characteristics的值,即Size of Raw Data = 400、Characteristics = E0000060.
    在这里插入图片描述
  • Characteristics=E0000060.
    在这里插入图片描述
  • 在原有属性的基础上新增了IMAGE_SCN_CNT_CODE|IMAGE_SCN_MEM_EXECUTE|IMAGE_SCN_MEM_WRITE属性。
  1. IMAGE_DATA_DIRECTORY[9]
  • 接下来要设置TLS表(IMAGE_NT_HEADERS-IMAGE_OPTIONAL_HEADER-IMAGE_DATA_DIRECTORY[9])的值,拓展区域的起始地址为9200(文件偏移),在PEView中查看该地址为C200(RVA地址),我们将从该地址处创建IMAGE_TLS_DIRECTORY结构体,因此修改PE文件头的IMAGE_DATA_DIRECTORY[9]。
    在这里插入图片描述
  • 修改后用PEView工具查看。
    在这里插入图片描述
  1. 设置IMAGE_TLS_DIRECTORY结构体
  • 只要把TLS回调函数注册到其中即可,编辑设置IMAGE_TLS_DIRECTORY结构体。
    在这里插入图片描述
  • 在文件偏移9200(RVA C200)地址处创建了IMAGE_TLS_DIRECTORY结构体,AddressOfCallbacks成员的值为VA 40C224(文件偏移9224),它是Array of TLS Callback Function的起始地址。只要把TLS回调函数的地址(40C230)放入该数组,即可成功注册TLS回调函数。
  • 先向TLS回调函数写入C2 0C00 - RETN 0C命令,即在TLS回调函数中不执行任何操作。
  1. 编写TLS回调函数
  • 利用OllyDbg的汇编功能,从40C230地址处开始编写反调试代码。
    在这里插入图片描述
  • 编写好TLS回调函数后,将修改的代码全部选中,鼠标右键Edit->Copy to executable->右键->save file.
  • ESP + 8 存储的是Reason参数的值,若值为1(DLL_PROCESS_ATTACH)时,检查PEB.BeingDebugged成员,若处于调试状态,则弹出消息框(MessageBoxA)后终止并退出进程。
  1. 使用OllyDbg打开我们添加了回调函数的程序,下图表明手工添加TLS回调函数成功。
    在这里插入图片描述
  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
vmp2.06全过程分析: 1. VMP从TLS回调函数开始,在回调函数检查OEP处的第一个字节是否为0xCC,如果是的话表示被调试 // // 下面比较内存数[0104409A]的一个字节是否为0.. // 010375AC C9 pop r14 010375AB 58 C0C49CBB push BB9CC4C0 010375A6 40 add dword 010375A5 F1 pop r4 010375A4 E1 pop r8 010375A3 F5 pop r3 010375A2 E5 pop r7 010375A1 ED pop r5 010375A0 FD pop r1 0103759F D1 pop r12 0103759E F9 pop r2 0103759D 01 pop r0 0103759C C5 pop r15 0103759B D5 pop r11 0103759A DD pop r9 01037599 F1 pop r4 ; 到这里的伪指令是保存原寄存器环境到VM寄存器 (对应进入VM时候的入栈寄存器) 01037598 0C 00 push word 00 ; cmp byte ptr [0104409A], 00 01037596 C8 push r14 ; jz 01037595 94 9A400401 push 0104409A 01037590 51 add dword 0103758F DD pop r9 0103758E 06 read word mem byte 0103758D 21 push esp 0103758C 1E read word mem byte 0103758B 4B nand byte 0103758A CD pop r13 01037589 57 add byte 01037588 DD pop r9 01037587 21 push esp 01037586 92 read word mem byte 01037585 31 nand byte 01037584 D1 pop r12 01037583 95 10 pop word (byte)r4 01037581 DC push r9 ; r9 --> 减法的EFLAGS部分1 01037580 DC push r9 0103757F 5D nand dword 0103757E E9 pop r6 0103757D 20 EAF7 push dword F7EA 0103757A 77 nand dword 01037579 F1 pop r4 01037578 D0 push r12 ; r12 --> 减法的EFLAGS部分2 01037577 D0 push r12 01037576 97 nand dword 01037575 CD pop r13 01037574 88 1508 push dword 0815 01037571 5D nand dword 01037570 F9 pop r2 0103756F 40 add dword 0103756E D9 pop r10 ; r4 = nand( nand(r9,r9), nand(0815,0815) ) + nand( nand(r12,r12), nand(F7EA,F7EA)) 0103756D F1 pop r4 ; r4存储前面比较需要的减法操作得到的EFLAGS.. 0103756C 58 EC217180 push 807121EC ; VM堆栈中存储两个加密后的跳转和不跳转的地址 01037567 1B 70817680 push 80768170 01037562 21 push esp 01037561 1C 04 push word 04 0103755F F0 push r4 ; r4 and 40 = nand( nand(r4,r4), nand(40,40) ) ==> ZF标志位 0103755E 28 push esp 0103755D 4D read dword mem dword 0103755C 97 nand dword 0103755B DD pop r9 0103755A 50 BF push dword BF ; FFFFFFBF = nand(40,40) 01037558 5D nand dword 01037557 F9 pop r2 01037556 91 shr dword ; ZF标志位右移4位, 如果ZF等于1的话,40 shr 4 = 4 否则等于0 01037555 CD pop r13 01037554 51 add dword ; 这里ZF=1的话,就是加4, 否则加0 01037553 DD pop r9 01037552 71 read dword mem dword 01037551 DD pop r9 ; 分支地址存储在r9 01037550 E9 pop r6 ; 退掉前面入栈的两个分支地址 807121EC / 80768170 0103754F E9 pop r6 0103754E DC push r9 ; xor r9, 8172ADCB ==> 一次xor解码跳转地址 0103754D BA push esp 0103754C EA read dword mem dword 0103754B D9 pop r10 0103754A D8 push r10 01037549 5D nand dword 01037548 E9 pop r6 01037547 94 34528D7E push 7E8D5234 01037542 5D nand dword 01037541 DD pop r9 01037540 58 CBAD7281 push 8172ADCB 0103753B D8 push r10 0103753A 97 nand dword 01037539 E9 pop r6 01037538 5D nand dword 01037537 F9 pop r2 01037536 CD pop r13 ; r13存储跳转的目的地址.. 01037535 E0 push r8 ; 入栈之前的原寄存器环境 01037534 D4 push r11 01037533 E4 push r7 01037532 D8 push r10 01037531 00 push r0 01037530 C4 push r15 0103752F EC push r5 0103752E F4 push r3 0103752D D4 push r11 0103752C FC push r1 0103752B F0 push r4 0103752A 94 403B6344 push 44633B40 01037525 E0 push r8 01037524 51 add dword 01037523 E9 pop r6 01037522 C8 push r14 01037521 CC push r13 01037520 29 jmp // // 下面的伪指令是否读取OEP地址,然后读取OEP处的一个字节。。和0xCC进行比较.. (比较是减法实现.. JNZ xxx) // 01038C26 F9 pop r2 01038C25 1B C0C49CBB push BB9CC4C0 01038C20 51 add dword 01038C1F ED pop r5 01038C1E E5 pop r7 01038C1D DD pop r9 01038C1C E9 pop r6 01038C1B D5 pop r11 01038C1A 01 pop r0 01038C19 FD pop r1 01038C18 C9 pop r14 01038C17 C5 pop r15 01038C16 D1 pop r12 01038C15 D0 push r12 ; xor r12, 8172ADCB r12 = 807121EC 01038C14 07 push esp 01038C13 EA read dword mem dword 01038C12 5D nand dword 01038C11 E1 pop r8 01038C10 1B 34528D7E push 7E8D5234 01038C0B 97 nand dword 01038C0A F1 pop r4 01038C09 D0 push r12 01038C08 1B CBAD7281 push 8172ADCB 01038C03 5D nand dword 01038C02 CD pop r13 01038C01 5D nand dword 01038C00 ED pop r5 01038BFF F1 pop r4 ; r4 = 01038C27 01038BFE CD pop r13 01038BFD E1 pop r8 01038BFC ED pop r5 01038BFB DC push r9 01038BFA E8 push r6 01038BF9 C8 push r14 01038BF8 00 push r0 01038BF7 FC push r1 01038BF6 C4 push r15 01038BF5 D4 push r11 01038BF4 58 9A400401 push 0104409A 01038BEF F8 push r2 01038BEE 40 add dword 01038BED D9 pop r10 01038BEC D9 pop r10 01038BEB F8 push r2 ; r2 = 0 01038BEA 1B 00000001 push 01000000 01038BE5 40 add dword 01038BE4 F5 pop r3 01038BE3 E1 pop r8 01038BE2 E0 push r8 01038BE1 0D 3C push dword 3C 01038BDF 40 add dword 01038BDE F5 pop r3 ; [SP] = 0100003C 01038BDD FB read dword mem dword ; 读取 l_newOffset = 0xE0 01038BDC F5 pop r3 01038BDB E0 push r8 01038BDA F4 push r3 01038BD9 40 add dword 01038BD8 E9 pop r6 01038BD7 C9 pop r14 01038BD6 C8 push r14 01038BD5 0D 28 push dword 28 ; +28 ==> OEP的RVA 01038BD3 40 add dword 01038BD2 D5 pop r11 01038BD1 FB read dword mem dword 01038BD0 ED pop r5 01038BCF EC push r5 01038BCE E0 push r8 01038BCD 40 add dword 01038BCC D5 pop r11 01038BCB C9 pop r14 ; r14 = [SP] = 0105B5C0 (EP地址) 01038BCA 1C CC push word CC 01038BC8 C8 push r14 01038BC7 1F read word mem byte 01038BC6 21 push esp 01038BC5 1E read word mem byte 01038BC4 6E nand byte 01038BC3 F5 pop r3 01038BC2 B8 add byte 01038BC1 E9 pop r6 01038BC0 21 push esp 01038BBF F2 read word mem byte 01038BBE 31 nand byte 01038BBD ED pop r5 01038BBC BF 24 pop word (byte)r9 ; 执行减法 sub byte ptr [0105B5C0], CC 01038BBA E8 push r6 ; 合并减法操作的ELFAGS 01038BB9 E8 push r6 01038BB8 97 nand dword 01038BB7 DD pop r9 01038BB6 20 EAF7 push dword F7EA 01038BB3 5D nand dword 01038BB2 F1 pop r4 01038BB1 EC push r5 01038BB0 EC push r5 01038BAF 77 nand dword 01038BAE F5 pop r3 01038BAD 20 1508 push dword 0815 01038BAA 5D nand dword 01038BA9 D5 pop r11 01038BA8 40 add dword 01038BA7 F5 pop r3 01038BA6 F1 pop r4 ; r4 == 合并后的EFLAGS 01038BA5 94 6A67A116 push 16A1676A ; 入栈两个跳转地址 01038BA0 1B 8F67A116 push 16A1678F 01038B9B 07 push esp ; 理论上这四句是连在一起的.. 01038B9A 0C 04 push word 04 01038B98 F0 push r4 01038B97 50 BF push dword BF ; and( not(r4), 40) = nand(r4, nand(40,40)) ==> 注意这里没有nand(r4,r4) 表示 JNE.. 01038B95 5D nand dword 01038B94 D5 pop r11 01038B93 91 shr dword ; JNE成立,所以and(not(r4),40)的结果等于0x40.. 右移4位 = 0x4 01038B92 D5 pop r11 01038B91 40 add dword ; 加法.. 指向跳转加密后的地址数值 01038B90 E9 pop r6 01038B8F EA read dword mem dword ; 01038B8E F5 pop r3 ; r3 => 加密后的跳转地址 01038B8D DD pop r9 01038B8C DD pop r9 01038B8B F4 push r3 01038B8A E9 pop r6 01038B89 E8 push r6 ; r6 = r3 => 加密后的跳转地址 01038B88 07 push esp 01038B87 4D read dword mem dword 01038B86 5D nand dword 01038B85 D1 pop r12 01038B84 1B D6075DE8 push E85D07D6 01038B7F 5D nand dword 01038B7E D1 pop r12 01038B7D E8 push r6 01038B7C 1B 29F8A217 push 17A2F829 01038B77 5D nand dword 01038B76 DD pop r9 01038B75 77 nand dword 01038B74 D5 pop r11 ; xor r6, 17A2F829 01038B73 D5 pop r11 ; r11 ==> 解密后的跳转地址 01038B72 F8 push r2 01038B71 C4 push r15 01038B70 D8 push r10 01038B6F 00 push r0 01038B6E C8 push r14 01038B6D F0 push r4 01038B6C FC push r1 01038B6B CC push r13 01038B6A F0 push r4 01038B69 E0 push r8 01038B68 C4 push r15 01038B67 E4 push r7 01038B66 58 403B6344 push 44633B40 01038B61 40 add dword 01038B60 D1 pop r12 01038B5F F8 push r2 01038B5E D4 push r11 ; 01038B5D 29 jmp // // 下面是TLS回调函数返回到系统模块.. // 01039F42 E9 pop r6 01039F41 1B C0C49CBB push BB9CC4C0 01039F3C 51 add dword 01039F3B D5 pop r11 01039F3A D9 pop r10 01039F39 F5 pop r3 01039F38 E1 pop r8 01039F37 D1 pop r12 01039F36 ED pop r5 01039F35 D5 pop r11 01039F34 07 push esp 01039F33 4D read dword mem dword 01039F32 C9 pop r14 01039F31 07 push esp 01039F30 4D read dword mem dword 01039F2F 5D nand dword 01039F2E FD pop r1 01039F2D 1B D6075DE8 push E85D07D6 01039F28 5D nand dword 01039F27 DD pop r9 01039F26 1B 29F8A217 push 17A2F829 01039F21 C8 push r14 01039F20 5D nand dword 01039F1F FD pop r1 01039F1E 5D nand dword 01039F1D CD pop r13 01039F1C DD pop r9 01039F1B CD pop r13 01039F1A F1 pop r4 01039F19 F9 pop r2 01039F18 01 pop r0 01039F17 FD pop r1 01039F16 E5 pop r7 01039F15 E1 pop r8 01039F14 FD pop r1 01039F13 F5 pop r3 01039F12 D5 pop r11 01039F11 C5 pop r15 01039F10 F1 pop r4 01039F0F F0 push r4 01039F0E 21 push esp 01039F0D 4D read dword mem dword 01039F0C 5D nand dword 01039F0B D1 pop r12 01039F0A 20 FF08 push dword 08FF 01039F07 5D nand dword 01039F06 F9 pop r2 ; and r4, FFFFF700 01039F05 1D popfd ; 设置到eflags =====> 最后退出EFLAGS还是被覆盖,所以这里不知道干啥用? 01039F04 07 push esp ; 下面写入返回地址, 这里返回到系统模块 TLS返回 01039F03 71 read dword mem dword 01039F02 50 14 push dword 14 01039F00 07 push esp 01039EFF 51 add dword 01039EFE F9 pop r2 01039EFD 8E write dword mem 01039EFC D1 pop r12 01039EFB D1 pop r12 01039EFA 01 pop r0 01039EF9 E4 push r7 01039EF8 D4 push r11 01039EF7 E0 push r8 01039EF6 DC push r9 01039EF5 F0 push r4 01039EF4 C4 push r15 01039EF3 FC push r1 01039EF2 EC push r5 01039EF1 F4 push r3 01039EF0 C4 push r15 01039EEF E8 push r6 01039EEE 12 exitVM

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值