May 18, 2022

miniLCTF2022-TWIN

考点:

简述考点

TLS回调函数

这个逆向技巧其实只要记住一句话:执行于线程开始与结束

创建或终止线程时,TLS回调函数都会自动调用执行

稍加调试不难发现主逻辑在main函数,可以确定有TLS回调函数或去看看表

image-20220518182041328

去IMAGE_TLS_DIRECTORY,找到Address of Callbacks(注意回调函数不止一个

于是发现了401990即是我们的回调函数(其实IDA已经识别出来了,不过可以了解一下原理)

image-20220518182226517

PEB反调试

这题只是调用了一下,并且!设置了回调函数,这个函数里实现了一个Hook(稍后再聊)

image-20220518184155684

主进程与子进程的异常处理

经过一个函数会创建一个文件tmp,而在tmp里预留了一个异常,专门是用来让主进程接收然后修改某些值

而这个函数就是用来接收异常0xC0000005,并修改了指定值

image-20220518185114824

花指令与XXTEA

花指令其实只要跟一遍就会很清楚,如果未解析在函数开头按P就好

image-20220518190134731

这题的加密就是XXTEA了,看篇文章即可很快识别与理解

有趣的是执行流,TLS跳来跳去与主进程子进程修改了XXTEA的某些值

当然硬做也行,不过未免无趣了些(错过了好多有趣的逆向知识!

文章参考

逆向工程核心原理第45章

云之君的Write up

现在就正式以我的视角来认识一遍这道题!

0x00 日常查壳

无壳32位

image-20220518190830145

0x01 开始上当

点进main函数,太天真了,解密就知道上当了

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
int __cdecl main(int argc, const char **argv, const char **envp)
{
char v4[100]; // [esp+0h] [ebp-90h] BYREF
char v5[33]; // [esp+64h] [ebp-2Ch]
char v6[7]; // [esp+85h] [ebp-Bh] BYREF
int i; // [esp+8Ch] [ebp-4h]

v5[0] = 38;
v5[1] = 17;
v5[2] = 8;
v5[3] = 35;
v5[4] = 26;
v5[5] = 8;
v5[6] = 28;
v5[7] = 39;
v5[8] = 3;
v5[9] = 25;
v5[10] = 26;
v5[11] = 43;
v5[12] = 10;
v5[13] = 29;
v5[14] = 4;
v5[15] = 30;
v5[16] = 8;
v5[17] = 49;
v5[18] = 25;
v5[19] = 4;
v5[20] = 2;
v5[21] = 25;
v5[22] = 54;
v5[23] = 1;
v5[24] = 20;
v5[25] = 57;
v5[26] = 4;
v5[27] = 59;
v5[28] = 5;
v5[29] = 3;
v5[30] = 10;
v5[31] = 5;
v5[32] = 0;
qmemcpy(v6, "81=<{xy", sizeof(v6));
memset(v4, 0, sizeof(v4));
sub_401050("Please input your flag: ");
sub_4010F0("%s", v4);
for ( i = 0; i < 40 && (i ^ v4[i] ^ 0x7F) == v5[i]; ++i )
;
if ( i == 40 )
sub_401050("correct\n");
else
sub_401050("wrong\n");
return 0;
}

0x02 TLS回调函数

于是稍加尝试与翻翻函数看看字符串什么,就会看到IDA标识出的一个TlsCallback_0,很明显主要逻辑是在那

首先就是要弄懂函数的执行流程

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
159
// positive sp value has been detected, the output may be wrong!
void __usercall sub_4019BF(int a1@<ebp>)
{
char *v1; // eax

if ( *(_DWORD *)(a1 + 12) == 1 )
{
memset((void *)(a1 - 284), 0, 0x50u);
puts((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;
Xor_0x7F((const char *)(a1 - 32));
hObject = CreateFileMappingA(0, 0, 4u, 0, 0x1000u, (LPCSTR)(a1 - 32));
*(_DWORD *)input = 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 = (char *)Xor_0x7F((const char *)(a1 - 116));
puts(v1);
*(_BYTE *)(a1 - 8) = 90;
*(_BYTE *)(a1 - 7) = 12;
*(_BYTE *)(a1 - 6) = 0;
Xor_0x7F((const char *)(a1 - 8));
scanf((char *)(a1 - 8), *(_DWORD *)input, 41);
}
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((const char *)(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 *)input + 20), 5, (int)&enc2);
*(_DWORD *)(a1 - 120) = memcmp((const void *)(*(_DWORD *)input + 20), &unk_40402C, 0x14u);
if ( !*(_DWORD *)(a1 - 120) )
{
Xor_0x7F((const char *)(a1 - 44));
puts((void *)(a1 - 44));
LABEL_12:
CloseHandle(hObject);
return;
}
}
else if ( dword_404440 == -2 )
{
Xor_0x7F((const char *)(a1 - 88));
puts((void *)(a1 - 88));
goto LABEL_12;
}
Xor_0x7F((const char *)(a1 - 16));
puts((void *)(a1 - 16));
goto LABEL_12;
}
}

第一个TLS函数

执行几句会发现遇到一个PEB的反调试,ZF标志位直接改了过去即可,要不然就少了个TLS函数(注意这是第二个TLS函数,第一个TLS执行完后会执行这个)

image-20220518193047504

随后继续跟随,就是经过混淆的字符串,然后经过异或0x7F再输出,于是一个scanf接收我们的input

image-20220518193312139

第二个TLS函数

随后的a1 + 12并不会进入,于是就会执行完第一个TLS函数ret走,由于TLS的机制会执行到第二个TLS函数

所以我们可以在第二个TLS函数开头下个断点,一个F9就会到这

简述该函数

image-20220518193646460

随后在第二个TLS函数会调用ExitProcess(),也就是退出进程,那么退出进程又会调用TLS函数链

再返TLS链

于是又回到了第一个TLS函数,不过这次进的是退出线程的函数块

image-20220518194314494

在这里就是创建子进程tmp文件,并且可以发现WriteFile API的地址修改成了Hook函数

image-20220518194400449

这时候已经可以在同目录文件夹找到tmp文件,拉进IDA

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int __cdecl main(int argc, const char **argv, const char **envp)
{
void *v3; // ecx
int result; // eax

sub_401400(v3); // 有个反调试直接ZF改了即可
if ( IsDebuggerPresent_0() ) // 这里是调试是我们要进去的,因为主进程是作为tmp的调试器
{
DELTA ^= 0x90909090;
key[1] = 144;
}
DELTA = sub_401210(DELTA); // 改变了DELTA,并触发异常去主进程又改了DELTA
sub_401390(&aMinilctfCbda59); // 获取flag
sub_401240(&aMinilctfCbda59, 5, key);
if ( !memcmp(&aMinilctfCbda59, &unk_404018, 0x14u) )
result = 1;
else
result = -1;
return result;
}

关键的一个点就是触发0xC0000005异常,和主函数对应,又异或了个值0x1B207

(想动调两个文件,前些日子成功过,今天一直不成功如果有师傅会调请务必告诉我!)

image-20220518200339284

而tmp函数里的加密逻辑其实也就是XXTEA,关键的是从>> 5改成了>> 6(如果看了下XXTEA源码相信没什么问题)

image-20220518200707412

0x03 GetFlag

于是再注意一下DELTA数的变换前一半flag就出来了

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

void btea(uint32_t *v, int n, uint32_t const key[4])
{
uint32_t y, z, sum;
unsigned p, rounds, e;
if (n > 1) /* Coding Part */
{
rounds = 6 + 52/n;
sum = 0;
z = v[n-1];
do
{
sum += DELTA;
e = (sum >> 2) & 3;
for (p=0; p<n-1; p++)
{
y = v[p+1];
z = v[p] += MX;
}
y = v[0];
z = v[n-1] += MX;
}
while (--rounds);
}
else if (n < -1) /* Decoding Part */
{
n = -n;
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[]= {0x6B7CE328, 0x4841D5DD, 0x963784DC, 0xEF8A3226, 0x0776B226};
//uint32_t v[]= {0x9021A921, 0xF53B3060, 0x8E88A84E, 0x43635AD5, 0xAC119239};
uint32_t const k[4]= {0x12,0x90,0x56,0x78};
int n = 5, i, j;
printf("%x\n", ((0x9E3779B9 ^ 0x12345678 ^ 0x90909090 ^ 0x7B) + 12345) ^ 0x1B207); //加的优先级比异或高
printf("%x\n", 0x1C93EF63 ^ 0x1B207u);
btea(v, -n, k);

// for ( i = 0; i < 5; i++ )
// printf("%x ", v[i]);
unsigned char * p = (unsigned char *)v;
for ( i = 0; i < 5; i++ )
for ( j = 0; j < 4; j++ )
printf("%c", p[i * 4 + j]);

return 0;
}
// miniLctf{cbda59ff59e3e90c91c02e9b40b78b}

而后面那半就比较好出了,没有改任何Delta数或XXTEA算法

image-20220518200933891

GetFlag!

image-20220518201030541

DASCTF X SU
🍬
HFCTF2022
🍪

About this Post

This post is written by P.Z, licensed under CC BY-NC 4.0.