TLS回调函数: TLS(Thread Local Storage, 线程局部存储)回调函数,TLS 回调函数的调用运行要先于 EP 代码的执行,并且每次创建或结束线程都会再次调用,故常用于反调试。
可以在节区表发现IMAGE_TLS_DIRECTORY,找到Address of Callbacks(注意回调函数一般不止一个 )
题解:
32位无壳
首先是一个虚假的逻辑验证。
在函数栏搜索找到callback函数,但是$+5,没有直接反编译,找到主要回调函数
这里有一个小花指令,最好retn会跳到sub_4019BF,后面还会有一处
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 void __usercall sub_4019BF (int a1@<ebp>) { void *v1; if ( *(_DWORD *)(a1 + 12 ) == 1 ) { memset ((void *)(a1 - 284 ), 0 , 0x50 u); sub_401930 ((void *)(a1 - 284 )); *(_BYTE *)(a1 - 1 ) = 0 ; *(_BYTE *)(a1 - 1 ) = NtCurrentPeb ()->BeingDebugged; 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 ; sub_4018C0 (a1 - 32 ); hObject = CreateFileMappingA (0 , 0 , 4u , 0 , 0x1000 u, (LPCSTR)(a1 - 32 )); *(_DWORD *)dword_404448 = MapViewOfFile (hObject, 0xF001F u, 0 , 0 , 0x1000 u); *(_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 *)sub_4018C0 (a1 - 116 ); sub_401930 (v1); *(_BYTE *)(a1 - 8 ) = 90 ; *(_BYTE *)(a1 - 7 ) = 12 ; *(_BYTE *)(a1 - 6 ) = 0 ; sub_4018C0 (a1 - 8 ); sub_401130 ((char *)(a1 - 8 ), dword_404448[0 ]); } 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 ; sub_4018C0 (a1 - 24 ); sub_401410 (); memset ((void *)(a1 - 204 ), 0 , 0x44 u); *(_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, 0x14 u); if ( !*(_DWORD *)(a1 - 120 ) ) { sub_4018C0 (a1 - 44 ); sub_401930 ((void *)(a1 - 44 )); LABEL_12: CloseHandle (hObject); return ; } } else if ( dword_404440 == -2 ) { sub_4018C0 (a1 - 88 ); sub_401930 ((void *)(a1 - 88 )); goto LABEL_12; } sub_4018C0 (a1 - 16 ); sub_401930 ((void *)(a1 - 16 )); goto LABEL_12; } }
这个函数有两大部分
这个a1 + 12就是对应着运行前与退出,对应DLL_PROCESS_ATTACH和DLL_PROCESS_DETACH
所以总结一下!a1 + 12 == 1是线程运行之前执行
a1 + 12 == 0 是线程退出后运行
首先会遇到一个PEB反调试,我们直接修改ZF标志位绕过。
然后就是一个异或,将原始数据inputflag混肴。
紧接着读取我们的输入,
输入之后F9进入第二部分
获取WriteFile API的地址
将WriteFile API地址改成自写函数的地址(也就是Hook函数)
也就是之后我们调用WriteFile函数地址就会调用Hook函数(Hook函数里执行完Hook代码就会脱钩)
GetModuleHandleA得到WiteFile函数的句柄,GetProcAddress函数获取WriteFile函数的地址存入dword_4043DC,并在sub_4016C0中将这个值修改为sub_401650的地址值。
随后在第二个TLS函数会调用ExitProcess(),也就是退出进程,那么退出进程又会调用TLS函数链,于是又回到了第一个TLS函数,不过这次进的是退出线程的函数块,分析主要函数
这里创建了一个文件,也就是tmp文件,但是对名字也进行了混肴,然后往里面写入进程。
我们可以发现这里的writefile变成了我们相要执行的函数
把子进程的两个值改成6,实践上是xxtea在加密过程中的右移轮次。
接下来分析一下tmp文件。
其中
先判断是否被调试,然后对delta进行异或操作。
hObject = CreateFileMappingA(0, 0, 4u, 0, 0x1000u, Name_FLAG);
创建名字为FLAG的文件映射对象,用于进程间通信。*(_DWORD *)input = MapViewOfFile(hObject, 0xF001Fu, 0, 0, 0x1000u);
存了内存映射文件,便于后面的共享内存。 简而言之,就是创建一个名为FLAG的文件映射对象,把input指向的地址设置成一块共享的内存,这样就可以在子进程里对input这块内存进行修改,实现加密。
一个魔改的XXTEA加密,z右移5改成了右移6位
这里一处函数需要注意
WaitForDebugEvent表明父进程调用子进程是以调试方式打开,也就是说父进程调试子进程,所以实际上子进程的反调试是不能绕过的,正常执行程序应该会进入子进程的if语句,对key和delta进行一些魔改运算。 0xC0000005,这是一个异常代码,代表EXCEPTION_ACCESS_VIOLATION
,内存访问异常,也即子进程中触发的异常。
也就是说,子进程被父进程调试,当子进程发生异常,应当交由调试者(父进程)处理。所以当子进程中触发异常,程序就会流回父进程,判断是否为内存访问异常,如果是,就对eip和eax做出相应修改。
再回去看子进程触发异常的地方,eip+5之后正好跳过了会触发异常的地方。而eax里存储的实际上是delta经过一通魔改运算之后的结果。也就是说,delta不仅在子进程里被修改,还会在父进程里异或一次,也就是还原成之前最初的模样
前一半的(修改的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 #include <stdio.h> #include <stdint.h> #define DELTA 0x1c925d64 #define MX (((z>>6^y<<2) + (y> >3^z<<4)) ^ ((sum^y) + (key[(p&3)^e] ^ z))) uint32_t k[4 ]= {0x12 ,0x90 ,0x56 ,0x78 };void xxtea (uint32_t * v, int n, uint32_t * key) { unsigned int sum,z,p,y; int e, rounds; rounds = 6 + 52 /n; sum = rounds * DELTA; y = v[0 ]; do { e = (sum >> 2 ) & 3 ; for (p=n-1 ; p>0 ; p--) { z = v[p-1 ]; y = v[p] -= MX; } z = v[n-1 ]; y = v[0 ] -= MX; sum -= DELTA; } while (--rounds); } int main () { uint32_t v[5 ] = {0x6b7ce328 ,0x4841d5dd ,0x963784dc ,0xef8a3226 ,0x776b226 }; int n = 5 ; xxtea (v, n, k); int i; for (i=0 ;i<5 ;i++) { printf ("%c" ,v[i]&0xff ); printf ("%c" ,v[i]>>8 &0xff ); printf ("%c" ,v[i]>>16 &0xff ); printf ("%c" ,v[i]>>24 &0xff ); } return 0 ; }
后面那半就比较好出了,没有改任何Delta数或XXTEA算法
miniLctf{cbda59ff59e3e90c91c02e9b40b78b}
参考:miniL2022 WP 今晚恰烤lq! - 云之君 (yunzh1jun.com)