抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

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; // eax

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;
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, 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 *)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, 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) )
{
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)

评论