侧边栏壁纸
  • 累计撰写 38 篇文章
  • 累计创建 23 个标签
  • 累计收到 10 条评论

目 录CONTENT

文章目录

【Reverse】TLS回调函数的理解 以及【miniLCTF2022】TWIN 复现

Fup1p1
2022-11-23 / 0 评论 / 0 点赞 / 674 阅读 / 7,163 字 / 正在检测是否收录...

TLS回调函数

0x01 TLS介绍

TLS(Thread Local Storage,线程局部储存),主要用于给线程独立的传值,由于线程不拥有进程的资源,所以几个同一进程的几个线程需要独立赋值时的需要通过TLS技术。每个线程创建时都会分配一个index所以,这个索引index是全局变量,线程根据index来获取其他线程传过来的返回值。TLS有一个特点,就是它通常在程序EP前就要运行,所以起始TLS才是个程序真正的开始。利用这一特点,可以用来进行的程序的反调试。

一句话介绍:执行于进程或线程的创建与终止,早于EP(Entry Point)
那么我们怎么才能知道一个程序是否含有TLS回调函数呢?

0x02 PE TLS Table

编程中启用了TLS功能,PE头文件中就会设置TLS表
QQ截图20221123192604
然后找到TLS Table
QQ截图20221123192713
再找到TLS函数开始的地址
QQ截图20221123192930
即0x401990
在IDA中验证一下(IDA是能帮你识别出TLS函数的)
QQ截图20221123193030

0x03 TLS函数调用顺序

假设main函数里写了创建用户线程(ThreadProc)

0x01 DLL_PROCESS_ATTACH 进程启动 Reason=1

进程的主线程调用main()函数前,已经注册的TLS回调函数(TLS_CALLBACK1、TLS_CALLBACK2)会先被调用执行。

0x02 DLL_THREAD_ATTACH 线程启动 Reason=2

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

0x03 DLL_THREAD_DETACH 线程结束 Reason=3

TLS回调函数全部执行完毕后,ThreadProc()线程函数开始调用执行。其执行完毕后Reason=3(DLL_THREAD_DETACH),TLS回调函数被调用执行。

0x04 DLL_PROCESS_DETACH 进程结束 Reason=0

ThreadProc()线程函数执行完毕后,一直在等待线程终止的main()函数(主线程)也会终止。此时Reason=0(DLL_PROCESS__DETACH),TLS回调函数最后一次被调用执行。

【miniLCTF2022】TWIN 复现

0x01 陷阱

main函数是纯纯骗你的,解密出来
QQ截图20221123201636
后面你能发现一件惊奇的事情,那就是main函数根本就不会被执行!

0x02 找到TLS函数开始的地址

IDA已经帮你解析出了TLS函数,虽然是空的(出题人加了点❀)
也可以像文章开头一样,在PE中就找到了TLS的开始地址。
QQ截图20221123201921
Snipaste_2022-11-23_20-25-06
花指令清不清无所谓。
这个TLS函数中,你能清晰的发现DLL_PROCESS_ATTACH和DLL_PROCESS_DETACH(我已加注释)

void __usercall sub_4019BF(int a1@<ebp>)
{
  void *v1; // eax


// Reason==1即DLL_PROCESS_ATTACH
  if ( *(_DWORD *)(a1 + 12) == 1 )    
  {
    memset((void *)(a1 - 284), 0, 0x50u);
    sub_401930((void *)(a1 - 284));
    *(_BYTE *)(a1 - 1) = 0;
    *(_BYTE *)(a1 - 1) = NtCurrentPeb()->BeingDebugged;  //反调试,得过,设置ZF==1
    if ( !*(_BYTE *)(a1 - 1) )
      return;
    *(_BYTE *)(a1 - 32) = 57;
    *(_BYTE *)(a1 - 31) = 51;
    *(_BYTE *)(a1 - 30) = 62;
    *(_BYTE *)(a1 - 29) = 56;
    *(_BYTE *)(a1 - 28) = 0;
    Xor_0x7f(a1 - 32);
    hObject = CreateFileMappingA(0, 0, 4u, 0, 0x1000u, (LPCSTR)(a1 - 32));
    *(_DWORD *)dword_404448 = MapViewOfFile(hObject, 0xF001Fu, 0, 0, 0x1000u);
    *(_BYTE *)(a1 - 116) = 47;
    *(_BYTE *)(a1 - 115) = 19;
    *(_BYTE *)(a1 - 114) = 26;
    *(_BYTE *)(a1 - 113) = 30;
    *(_BYTE *)(a1 - 112) = 12;
    *(_BYTE *)(a1 - 111) = 26;
    *(_BYTE *)(a1 - 110) = 95;
    *(_BYTE *)(a1 - 109) = 22;
    *(_BYTE *)(a1 - 108) = 17;
    *(_BYTE *)(a1 - 107) = 15;
    *(_BYTE *)(a1 - 106) = 10;
    *(_BYTE *)(a1 - 105) = 11;
    *(_BYTE *)(a1 - 104) = 95;
    *(_BYTE *)(a1 - 103) = 6;
    *(_BYTE *)(a1 - 102) = 16;
    *(_BYTE *)(a1 - 101) = 10;
    *(_BYTE *)(a1 - 100) = 13;
    *(_BYTE *)(a1 - 99) = 95;
    *(_BYTE *)(a1 - 98) = 25;
    *(_BYTE *)(a1 - 97) = 19;
    *(_BYTE *)(a1 - 96) = 30;
    *(_BYTE *)(a1 - 95) = 24;
    *(_BYTE *)(a1 - 94) = 69;
    *(_BYTE *)(a1 - 93) = 95;
    *(_BYTE *)(a1 - 92) = 0;
    v1 = (void *)Xor_0x7f(a1 - 116);
    sub_401930(v1);
    *(_BYTE *)(a1 - 8) = 90;
    *(_BYTE *)(a1 - 7) = 12;
    *(_BYTE *)(a1 - 6) = 0;
    Xor_0x7f(a1 - 8);
    sub_401130((char *)(a1 - 8), dword_404448[0]);
  }
  
  
  // Reason==0 即 DLL_PROCESS_DETACH
  
  if ( !*(_DWORD *)(a1 + 12) ) 
  {
    *(_BYTE *)(a1 - 24) = 81;
    *(_BYTE *)(a1 - 23) = 80;
    *(_BYTE *)(a1 - 22) = 11;
    *(_BYTE *)(a1 - 21) = 18;
    *(_BYTE *)(a1 - 20) = 15;
    *(_BYTE *)(a1 - 19) = 0;
    Xor_0x7f(a1 - 24);
    CreatTmp();
    memset((void *)(a1 - 204), 0, 0x44u);
    *(_DWORD *)(a1 - 204) = 68;
    CreateProcessA(
      (LPCSTR)(a1 - 24),
      0,
      0,
      0,
      0,
      3u,
      0,
      0,
      (LPSTARTUPINFOA)(a1 - 204),
      (LPPROCESS_INFORMATION)(a1 - 136));
    *(_BYTE *)(a1 - 44) = 28;
    *(_BYTE *)(a1 - 43) = 16;
    *(_BYTE *)(a1 - 42) = 13;
    *(_BYTE *)(a1 - 41) = 13;
    *(_BYTE *)(a1 - 40) = 26;
    *(_BYTE *)(a1 - 39) = 28;
    *(_BYTE *)(a1 - 38) = 11;
    *(_BYTE *)(a1 - 37) = 117;
    *(_BYTE *)(a1 - 36) = 0;
    *(_BYTE *)(a1 - 16) = 8;
    *(_BYTE *)(a1 - 15) = 13;
    *(_BYTE *)(a1 - 14) = 16;
    *(_BYTE *)(a1 - 13) = 17;
    *(_BYTE *)(a1 - 12) = 24;
    *(_BYTE *)(a1 - 11) = 117;
    *(_BYTE *)(a1 - 10) = 0;
    *(_BYTE *)(a1 - 88) = 47;
    *(_BYTE *)(a1 - 87) = 19;
    *(_BYTE *)(a1 - 86) = 26;
    *(_BYTE *)(a1 - 85) = 30;
    *(_BYTE *)(a1 - 84) = 12;
    *(_BYTE *)(a1 - 83) = 26;
    *(_BYTE *)(a1 - 82) = 95;
    *(_BYTE *)(a1 - 81) = 28;
    *(_BYTE *)(a1 - 80) = 19;
    *(_BYTE *)(a1 - 79) = 16;
    *(_BYTE *)(a1 - 78) = 12;
    *(_BYTE *)(a1 - 77) = 26;
    *(_BYTE *)(a1 - 76) = 95;
    *(_BYTE *)(a1 - 75) = 11;
    *(_BYTE *)(a1 - 74) = 23;
    *(_BYTE *)(a1 - 73) = 26;
    *(_BYTE *)(a1 - 72) = 95;
    *(_BYTE *)(a1 - 71) = 27;
    *(_BYTE *)(a1 - 70) = 26;
    *(_BYTE *)(a1 - 69) = 29;
    *(_BYTE *)(a1 - 68) = 10;
    *(_BYTE *)(a1 - 67) = 24;
    *(_BYTE *)(a1 - 66) = 24;
    *(_BYTE *)(a1 - 65) = 26;
    *(_BYTE *)(a1 - 64) = 13;
    *(_BYTE *)(a1 - 63) = 95;
    *(_BYTE *)(a1 - 62) = 30;
    *(_BYTE *)(a1 - 61) = 17;
    *(_BYTE *)(a1 - 60) = 27;
    *(_BYTE *)(a1 - 59) = 95;
    *(_BYTE *)(a1 - 58) = 11;
    *(_BYTE *)(a1 - 57) = 13;
    *(_BYTE *)(a1 - 56) = 6;
    *(_BYTE *)(a1 - 55) = 95;
    *(_BYTE *)(a1 - 54) = 30;
    *(_BYTE *)(a1 - 53) = 24;
    *(_BYTE *)(a1 - 52) = 30;
    *(_BYTE *)(a1 - 51) = 22;
    *(_BYTE *)(a1 - 50) = 17;
    *(_BYTE *)(a1 - 49) = 117;
    *(_BYTE *)(a1 - 48) = 0;
    sub_401510(a1 - 24, a1 - 136);
    if ( dword_404440 == 1 )
    {
      sub_4012C0(*(_DWORD *)dword_404448 + 20, 5, &unk_40405C);
      *(_DWORD *)(a1 - 120) = memcmp((const void *)(*(_DWORD *)dword_404448 + 20), &unk_40402C, 0x14u);
      if ( !*(_DWORD *)(a1 - 120) )
      {
        Xor_0x7f(a1 - 44);
        sub_401930((void *)(a1 - 44));
LABEL_12:
        CloseHandle(hObject);
        return;
      }
    }
    else if ( dword_404440 == -2 )
    {
      Xor_0x7f(a1 - 88);
      sub_401930((void *)(a1 - 88));
      goto LABEL_12;
    }
    Xor_0x7f(a1 - 16);
    sub_401930((void *)(a1 - 16));
    goto LABEL_12;
  }
}

Snipaste_2022-11-24_08-56-51
如果不过PEB反调试的话,将不会设置第二个TLS函数,那么后面会得不到正确的flag。

hObject = CreateFileMappingA(0, 0, 4u, 0, 0x1000u, Name_FLAG);创建名字为FLAG的文件映射对象,用于进程间通信。
*(_DWORD *)input = MapViewOfFile(hObject, 0xF001Fu, 0, 0, 0x1000u);存了内存映射文件,便于后面的共享内存。
简而言之,就是创建一个名为FLAG的文件映射对象,把input指向的地址设置成一块共享的内存,这样就可以在子进程里对input这块内存进行修改,实现加密。
来自云之君的Blog

0x03 第一个TLS函数

在TLS函数开始地址处下个断点,开始跟进。
Snipaste_2022-11-24_09-21-21
过掉反调试
Snipaste_2022-11-24_09-07-53
发现其设置了第二个TLS函数
Snipaste_2022-11-24_09-08-25
继续跟进,发现字符串混淆,即对字符串异或0x7f还原
Snipaste_2022-11-24_09-08-57
第一个TLS的末尾,开始输入字符,随便输入12345678
Snipaste_2022-11-24_09-09-29
第一个TLS函数算是结束了,根据TLS函数的机制,他将会执行第二个TLS函数。

0x04 进入第二个TLS

Snipaste_2022-11-24_09-11-32
此时,就可以去第二个TLS开始地址下断点,直接F9跳过去。
Snipaste_2022-11-24_09-12-39
我们就能得到第二个TLS函数
Snipaste_2022-11-24_09-15-26
获取WriteFile 的地址
Snipaste_2022-11-24_09-18-17
HOOK WriteFile地址,到目标函数(其实就是Sub_0x401650,之后可以知道是修改tmp文件中XXTEA的z的右移值)
Snipaste_2022-11-24_09-19-53
第二个TLS函数就结束了

0x05 进程结束,进入PROCESS_DETACH

进程结束,根据TLS函数的机制,接下来返回到DLL_PROCESS_DETACH
Snipaste_2022-11-24_12-49-30
进入CreatTmp函数
Snipaste_2022-11-24_13-41-56
创建子进程tmp文件,可以在同目录下发现已经创建好的tmp文件。
跟进WriteFile函数中,发现了之前HOOK的函数
Snipaste_2022-11-24_13-45-41
继续向下调试,能发现很关键的函数
Snipaste_2022-11-24_14-59-24

0x06 主进程调试子进程

此时我们要关注Tmp子进程,进入tmp文件,其包含了一个XXTEA,以及一些对Delta修改的指令。
Snipaste_2022-11-25_02-23-54
tmp文件中还检测了是否有调试器
Snipaste_2022-11-24_13-51-35
我们将IDA的根目录进行修改,改成adi.exe 😋,就能过了。
Snipaste_2022-11-24_15-11-49
我们重新调试,在关键点XOR处下断点,就能得到最终的Delta值。
Snipaste_2022-11-25_03-13-38

0x07 Cat Flag

第一段flag

已知XXTEA的key和密文(注意key[1]已被赋值为144)
Snipaste_2022-11-25_03-06-43

#include <stdio.h>
#include <stdlib.h>
#define delta 0x1C925D64
 
int main()
{
    unsigned int v[5] = { 0x6B7CE328, 0x4841D5DD, 0x963784DC, 0xEF8A3226, 0x776B226};
    unsigned int key[4] = {0x12,144,0x56,0x78};
    unsigned int sum = 0;
    unsigned int y,z,p,rounds,e;
    int n = 5;
    int i = 0;
    rounds = 6 + 52/n;
    y = v[0];
    sum = rounds*delta;
     do
     {
        e = sum >> 2 & 3;
        for(p=n-1;p>0;p--)
        {
            z = v[p-1];
            v[p] -= ((((z>>6)^(y<<2))+((y>>3)^(z<<4))) ^ ((key[(p&3)^e]^z)+(y ^ sum)));
            y = v[p];
        }
        z = v[n-1];
        v[0] -= ((((y<<2)^(z>>6))+((z<<4)^(y>>3))))^ ((key[(p^e)&3]^z)+(y ^ sum));
        y = v[0];
        sum = sum-delta;
     }while(--rounds);
 
    for(i=0;i<n;i++)
    {
        printf("%c%c%c%c",*((char*)&v[i]+0),*((char*)&v[i]+1),*((char*)&v[i]+2),*((char*)&v[i]+3));
    }
    return 0;
}
//miniLctf{cbda59ff59e

第二段flag

第二个XXTEA函数(sub_4012C0)
Snipaste_2022-11-25_02-58-20

#include <stdio.h>
#include <stdlib.h>
#define delta 0x9E3779B9
 
int main()
{
    unsigned int v[5] = {0x9021A921, 0xF53B3060, 0x8E88A84E, 0x43635AD5, 0xAC119239};
    unsigned int key[4] = {0x12,0x34,0x56,0x78};
    unsigned int sum = 0;
    unsigned int y,z,p,rounds,e;
    int n = 5;
    int i = 0;
    rounds = 6 + 52/n;
    y = v[0];
    sum = rounds*delta;
     do
     {
        e = sum >> 2 & 3;
        for(p=n-1;p>0;p--)
        {
            z = v[p-1];
            v[p] -= ((((z>>5)^(y<<2))+((y>>3)^(z<<4))) ^ ((key[(p&3)^e]^z)+(y ^ sum)));
            y = v[p];
        }
        z = v[n-1];
        v[0] -= ((((y<<2)^(z>>5))+((z<<4)^(y>>3))))^ ((key[(p^e)&3]^z)+(y ^ sum));
        y = v[0];
        sum = sum-delta;
     }while(--rounds);
 
    for(i=0;i<n;i++)
    {
        printf("%c%c%c%c",*((char*)&v[i]+0),*((char*)&v[i]+1),*((char*)&v[i]+2),*((char*)&v[i]+3));
    }
    return 0;
    //3e90c91c02e9b40b78b}
}

两段flag组合即为最后的flag

0x08 后话

这题涉及的知识点挺多的,最后还是要感谢P.Z云之君等大佬的博客和视频,帮助我一步步了解这题的运行过程。感谢这些师傅们,Thanks♪(・ω・)ノ。ORZ。
这题遇到的麻烦也不少:调试时,不知道何原因,tmp子进程一直创建失败,在Process_Exployer软件中也看不到tmp进程的踪影。导致调试时一直卡在WaitForDebugEvent处。但是放到win10虚拟机中,问题又解决了。搞不懂都。

0x09总结知识点

· 理清TLS函数的调用过程,TLS>Main->TLS(这道题Main根本都没执行,加解密与flag的判断,全在TLS函数中)
· 主进程调试子进程,以及其接受异常处理事件,这题秀我一脸了属于是(原来可以酱紫玩)。
· PEB反调试,并不是看到PEB检测就无脑修改ZF标志符,主进程调试子进程,所以子进程中的PEB检测反而不需要过,盲目的过反调试会带来后果。
· HOOK函数,此题HOOK函数修改tmp文件中XXTEA的z的右移位数
· 字符串的混淆

0

评论区