0%

XCTF2022

*CTF

RE

Simple File System

给了一个file文件,一个png文件,一个elf文件

我们对elf文件进行分析

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
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
__int64 __fastcall sub_5615516A1488(int a1, const char **a2)
{
__int64 v2; // rbp
__int64 v4; // rsi
int *v5; // rax
char *v6; // rax
unsigned int v7; // eax
unsigned int v8; // eax
int i; // [rsp-38h] [rbp-1048h]
int j; // [rsp-34h] [rbp-1044h]
int v12; // [rsp-30h] [rbp-1040h]
int v13; // [rsp-2Ch] [rbp-103Ch]
unsigned int v14; // [rsp-28h] [rbp-1038h]
unsigned int v15; // [rsp-28h] [rbp-1038h]
unsigned int v16; // [rsp-28h] [rbp-1038h]
unsigned int v17; // [rsp-28h] [rbp-1038h]
unsigned int v18; // [rsp-28h] [rbp-1038h]
unsigned int v19; // [rsp-28h] [rbp-1038h]
unsigned int v20; // [rsp-28h] [rbp-1038h]
unsigned int v21; // [rsp-28h] [rbp-1038h]
int v22; // [rsp-24h] [rbp-1034h]
int v23; // [rsp-20h] [rbp-1030h]
time_t v24; // [rsp-18h] [rbp-1028h] BYREF
char v25[16]; // [rsp-10h] [rbp-1020h] BYREF
char v26[1024]; // [rsp+3F0h] [rbp-C20h] BYREF
char v27[1024]; // [rsp+7F0h] [rbp-820h] BYREF
char v28[1032]; // [rsp+BF0h] [rbp-420h] BYREF
unsigned __int64 v29; // [rsp+FF8h] [rbp-18h]
__int64 v30; // [rsp+1000h] [rbp-10h]

v30 = v2;
v29 = __readfsqword(0x28u);
if ( a1 != 3 )
{
printf("use: %s <diskfile> <nblocks>\n", *a2);
return 1LL;
}
v4 = (unsigned int)atoi(a2[2]);
if ( !(unsigned int)sub_5615516A3BEC(a2[1], v4) )
{
v5 = __errno_location();
v6 = strerror(*v5);
printf("couldn't initialize %s: %s\n", a2[1], v6);
return 1LL;
}
v7 = sub_5615516A3C9E();
printf("opened emulated disk image %s with %d blocks\n", a2[1], v7);
while ( 1 )
{
printf(" simplefs> ");
fflush(stdout);
if ( !fgets(v25, 1024, stdin) )
break;
if ( v25[0] != 10 )
{
v25[strlen(v25) - 1] = 0;
v12 = __isoc99_sscanf(v25, "%s %s %s", v26, v27, v28);
if ( v12 )
{
if ( !strcmp(v26, "format") )
{
if ( v12 == 1 )
{
if ( (unsigned int)sub_5615516A2357() )
puts("disk formatted.");
else
puts("format failed!");
}
else
{
puts("use: format");
}
}
else if ( !strcmp(v26, "mount") )
{
if ( v12 == 1 )
{
if ( (unsigned int)sub_5615516A27C2() )
puts("disk mounted.");
else
puts("mount failed!");
}
else
{
puts("use: mount");
}
}
else if ( !strcmp(v26, "debug") )
{
if ( v12 == 1 )
sub_5615516A24BB();
else
puts("use: debug");
}
else if ( !strcmp(v26, "getsize") )
{
if ( v12 == 2 )
{
v14 = atoi(v27);
v13 = sub_5615516A3014(v14);
if ( v13 < 0 )
puts("getsize failed!");
else
printf("inode %d has size %d\n", v14, (unsigned int)v13);
}
else
{
puts("use: getsize <inumber>");
}
}
else if ( !strcmp(v26, "create") )
{
if ( v12 == 1 )
sub_5615516A216A();
else
puts("use: create");
}
else if ( !strcmp(v26, "delete") )
{
if ( v12 == 2 )
{
v15 = atoi(v27);
if ( (unsigned int)sub_5615516A2DD1(v15) )
printf("inode %d deleted.\n", v15);
else
puts("delete failed!");
}
else
{
puts("use: delete <inumber>");
}
}
else if ( !strcmp(v26, "cat") )
{
if ( v12 == 2 )
{
v16 = atoi(v27);
if ( !(unsigned int)sub_5615516A2017(v16, "/dev/stdout") )
puts("cat failed!");
}
else
{
puts("use: cat <inumber>");
}
}
else if ( !strcmp(v26, "captureflag") )
{
puts("Are you kidding?");
}
else if ( !strcmp(v26, "plantflag") )
{
v8 = time(&v24);
srand(v8);
v22 = rand() % 100;
v23 = rand() % 100;
for ( i = 0; i < v22; ++i )
{
v17 = sub_5615516A216A();
if ( (unsigned int)sub_5615516A1E16("flag", v17, 2) )//在这
printf("copied file %s to inode %d\n", v27, v17);
else
puts("copy failed!");
}
v18 = sub_5615516A216A();
if ( (unsigned int)sub_5615516A1E16("flag", v18, 1) )
printf("plant flag to inode %d!\n", v18);
else
puts("copy failed!");
for ( j = 0; j < v23; ++j )
{
v19 = sub_5615516A216A();
if ( (unsigned int)sub_5615516A1E16("flag", v19, 2) )
printf("copied file %s to inode %d\n", v27, v19);
else
puts("copy failed!");
}
}
else if ( !strcmp(v26, "copyin") )
{
if ( v12 == 3 )
{
v20 = atoi(v28);
if ( !(unsigned int)sub_5615516A1E16(v27, v20, 0) )
goto LABEL_69;
printf("copied file %s to inode %d\n", v27, v20);
}
else
{
puts("use: copyin <filename> <inumber>");
}
}
else if ( !strcmp(v26, "copyout") )
{
if ( v12 == 3 )
{
v21 = atoi(v27);
if ( (unsigned int)sub_5615516A2017(v21, v28) )
printf("copied inode %d to file %s\n", v21, v28);
else
LABEL_69:
puts("copy failed!");
}
else
{
puts("use: copyout <inumber> <filename>");
}
}
else if ( !strcmp(v26, "help") )
{
puts("Commands are:");
puts(" format");
puts(" mount");
puts(" debug");
puts(" create");
puts(" delete <inode>");
puts(" cat <inode>");
puts(" captureflag");
puts(" plantflag");
puts(" copyin <file> <inode>");
puts(" copyout <inode> <file>");
puts(" help");
puts(" quit");
puts(" exit");
}
else
{
if ( !strcmp(v26, "quit") || !strcmp(v26, "exit") )
break;
printf("unknown command: %s\n", v26);
puts("type 'help' for a list of commands.");
}
}
}
}
puts("closing emulated disk.");
sub_5615516A3E6B();
return 0LL;
}

其实就是跟一个终端一样,我们发现还有一个记事本,记录了一些操作:

1
2
3
4
5
6
7
8
9
10
11
12
# instructions

I implemented a very simple file system and buried my flag in it.

The image file are initiated as follows:
./simplefs image.flag 500
simplefs> format
simplefs> mount
simplefs> plantflag
simplefs> exit

And you cold run "help" to explore other commands.

那么我们很自然的怀疑到plantflag里有东西,我们查看一下,定位到关键函数

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
// bad sp value at call has been detected, the output may be wrong!
__int64 __fastcall sub_5615516A1E16(const char *a1, unsigned int a2, int a3)
{
int *v3; // rax
char *v4; // rax
unsigned int v7; // [rsp+1Ch] [rbp-4024h]
unsigned int v8; // [rsp+20h] [rbp-4020h]
int v9; // [rsp+24h] [rbp-401Ch]
FILE *stream; // [rsp+28h] [rbp-4018h]
char ptr[16]; // [rsp+30h] [rbp-4010h] BYREF
char v12; // [rsp+40h] [rbp-4000h] BYREF
__int64 v13[512]; // [rsp+3040h] [rbp-1000h] BYREF

while ( v13 != (__int64 *)&v12 )
;
v13[511] = __readfsqword(0x28u);
v7 = 0;
stream = fopen(a1, "r");
if ( stream )
{
while ( 1 )
{
v8 = fread(ptr, 1uLL, 0x4000uLL, stream);
if ( (int)v8 <= 0 )
break;
if ( a3 == 1 )
{
sub_5615516A21B2(ptr, v8);
}
else if ( a3 == 2 )
{
sub_5615516A2305(ptr, v8);
}
v9 = sub_5615516A3585(a2, ptr, v8, v7);
if ( v9 < 0 )
{
printf("ERROR: fs_write return invalid result %d\n", (unsigned int)v9);
break;
}
v7 += v9;
if ( v9 != v8 )
{
printf("WARNING: fs_write only wrote %d bytes, not %d bytes\n", (unsigned int)v9, v8);
break;
}
}
printf("%d bytes copied\n", v7);
fclose(stream);
return 1LL;
}
else
{
v3 = __errno_location();
v4 = strerror(*v3);
printf("couldn't open %s: %s\n", a1, v4);
return 0LL;
}
}

一个函数一个函数看,发现a3 = 1时,里面的函数可逆,我们查看一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
__int64 __fastcall sub_5615516A21B2(__int64 a1, int a2)
{
int i; // [rsp+10h] [rbp-10h]
int v4; // [rsp+14h] [rbp-Ch]
_BYTE *v5; // [rsp+18h] [rbp-8h]

v4 = sub_5615516A30A3();
for ( i = 0; i < a2; ++i )
{
v5 = (_BYTE *)(i + a1);
*v5 = (*v5 >> 1) | (*v5 << 7);
*v5 ^= v4;
*v5 = ((unsigned __int8)*v5 >> 2) | (*v5 << 6);
*v5 ^= BYTE1(v4);
*v5 = ((unsigned __int8)*v5 >> 3) | (32 * *v5);
*v5 ^= BYTE2(v4);
*v5 = ((unsigned __int8)*v5 >> 4) | (16 * *v5);
*v5 ^= HIBYTE(v4);
*v5 = (*v5 >> 5) | (8 * *v5);
}
return 0LL;
}

现在有几个问题是,没找到原来的数据或者对比的数据,(这个数据哦经过我的调试应该是从图片的文件中读取的)还不知道v4是啥,v4这个值的函数太恶心了,所以我们动调一下,把所有文件都扔到目录下:

image-20220418233932982

先输入:

1
./simplefs image.flag 500

然后进行attach一下,程序就动起来了,最好在sub_5615516A21B2函数进行断点。

然后按照指示的命令调下去,然后点F9,进入断点

image-20220418234244165

调试出v4的值,然后我们其中能动调出v5的每个值,前三个0x00和0xD2和0xFC,那么我们直接去flag图片里找就行了,当然我们可以直接动态调试出原来的flag,可以不用写脚本。

image-20220418235157590

当然还是写一下逆向的脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
a = [0x00,0xD2,0xFC,0xD8,0xA2,0xDA,0xBA,0x9E,0x9C,0x26,0xF8,0xF6,0xB4,0xCE,0x3C,0xCC,0x96,0x88,0x98,0x34,0x82,0xDE,0x80,0x36,0x8a,0xd8,0xc0,0xf0,0x38,0xae,0x40]
flag = ""
for i in range(len(a)):
v1 = a[i]
v1 = ((v1<< 5) | (v1 >> 3))&0xff
v1 ^= 0xde
v1 = ((v1 << 4) | (v1 >> 4))&0xff
v1 ^= 0xed
v1 = ((v1 << 3) | (v1 >> 5))&0xff
v1 ^= 0xbe
v1 = ((v1 << 2) | (v1 >> 6))&0xff
v1 ^= 0xef
v1 = ((v1 << 1) | (v1 >> 7))&0xff
flag += chr(v1)
print(flag)
#*CTF{Gwed9VQpM4Lanf0kEj1oFJR6}

NaCl

这个题看一早上,汇编没学好,出不来,近些天上课正在看汇编,等我汇编看的差不多了,就开始整一下这个题的汇编。

image-20220421184638338

函数逻辑很清晰,但这个函数里的内容就不敢苟同了,很杂很乱:

image-20220421184911788

我只能说不太好看懂,经过动态调试后,发现其到38行后进入下面的汇编:

image-20220419151306919

然后会进入一个巨大的循环,里面进行着加密,经过43次循环后,进入下一个汇编,这个汇编是xtea加密,这个xtea改了两处,第一是循环轮次,第二是delta。

参考了PZ师傅的解题思路,我们可以采用idapython去掉花指令的方法来生成可视的代码,从而避免看汇编。

不能反汇编的原因是因为,在这些汇编的后面是很奇怪的,他们有着统一的格式

1
2
3
4
5
6
7
nop
lea r15, [r15+8]
mov edi, [r15-8]
and edi, 0xffffffe0h
lea rdi, [r13+rdi+0]
jmp rdi
其实这样看来,很像是retn的做法,而这些指令的操作对象全部是堆栈,通过调试发现,r13一直为0r15的操作像是栈的储存,即这个是用r15表示rspr14代表rbp
1
2
3
4
5
6
7
还有地方似乎更像是call指令
nop
lea r15, [r15-8]
lea r12, loc_8080980
mov [r15], r12
jmp loc_8080720
其实这个就是要跳转到那个函数

那么我们要用idapython修改的地方已经出来了,我们只需要将像上面第一个的改成retn,第二个改成call指令就行了。

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
import idc
start = 0x807FEC0
end = 0x8080AD1

address = [0 for i in range(5)]
callTarget = ["lea", "lea", "mov", "jmp"]
retnTarget = ["lea", "mov", "and", "lea", "jmp"]


def nop(s, e):
while (s < e):
patch_byte(s, 0x90)
s += 1

def turnCall(s, e):
nop(s, e)
patch_byte(e, 0xE8)
huaStart = next_head(e)
huaEnd = next_head(huaStart)
nop(huaStart, huaEnd)

def turnRetn(s, e):
nop(s, e)
patch_byte(e, 0x90)
patch_byte(e + 1, 0xC3)

p = start
while p < end:
address[0] = p
address[1] = next_head(p)#下一个指令
address[2] = next_head(address[1])
address[3] = next_head(address[2])
address[4] = next_head(address[3])

for i in range(0, 4):
if print_insn_mnem(address[i]) != callTarget[i]:
break
else:
turnCall(address[0], address[3])
p = next_head(next_head(address[3]))
continue

for i in range(0, 5):
if print_insn_mnem(address[i]) != retnTarget[i]:
break
else:
turnRetn(address[0], address[4])
p = next_head(next_head(address[4]))
continue

p = next_head(p)

然后我们点edit–patch program–最后一项,重进ida刷新,就会发现进入一片有反汇编的世界!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
__int64 __fastcall sub_8080900(__int64 a1)
{
__int64 v1; // r13
int v2; // er15
struct_v3 *v3; // r15

v3 = (struct_v3 *)(v1 + (unsigned int)(v2 - 40));
v3->input = a1;
v3->count = 0;
for ( v3->count = 0; v3->count <= 3; ++v3->count )
{
v3->t = 8 * v3->count + v3->input;//取8位,flag长32位
v3->qword10 = sub_8080720(v3->t);//第一个加密
sub_8080100(1 << (LOBYTE(v3->count) + 1), (__int64)&v3->qword10);// 循环轮次2,4,8,16
sub_807FEC0((char *)&unk_80AFC80 + 8 * v3->count, &v3->qword10, 8LL);
}
sub_807FF20(&byte_80AFC60, &unk_80AFC80, 32LL);
return 4096LL;
}

我学习到了比如像*(v3+36),*(v3+18),这样的,我们其实可以将其定义成一个结构体右键–creat new struct即可。

先进入第一个函数:

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
__int64 __fastcall sub_8080720(__int64 a1)
{
__int64 v1; // rbx
__int64 v2; // r13
__int64 v3; // r15
_QWORD *v4; // r15
struct_v5 *v5; // r15

v4 = (_QWORD *)(v3 - 8);
*v4 = v1;
v5 = (struct_v5 *)(v2 + (unsigned int)((_DWORD)v4 - 56));
v5->qword0 = a1;
v5->xorkey = sub_8080360();
v5->qword18 = *(_QWORD *)(v2 + (unsigned int)v5->qword0);
v5->HIGHER = little(HIDWORD(v5->qword18)); // 小端序
v5->LOWER = little(v5->qword18);
for ( v5->int2C = 0; v5->int2C <= 43; ++v5->int2C )
{
v5->dword14 = v5->LOWER;
ROL(v5->LOWER, 1);
ROL(v5->LOWER, 8);
v5->LOWER = v5->HIGHER ^ ROL(v5->LOWER, 2) ^ *(_DWORD *)(v2 + 4 * v5->int2C + (unsigned int)v5->xorkey);
v5->HIGHER = v5->dword14;
}
v5->qword18 = 0LL;
v5->qword18 = ((v5->qword18 | v5->LOWER) << 32) | v5->HIGHER;
return v5->qword18;
}

这个是先将8个字节又分为2组,每组4字节并将其倒置,然后对其低位左移,然后进行异或,这个异或很复杂,我们可以通过动调把xorkey调试出来。然后再看第二个加密

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
__int64 __fastcall sub_8080100(int a1, __int64 a2)
{
__int64 v2; // r13
mylist *v3; // r15
__int64 result; // rax

v3[-1].count = a1;
*(_QWORD *)&v3[-1].t0 = a2;
*(_QWORD *)&v3[-1].t6 = &dword_80AFB40; // key
v3[-1].t10 = *(_DWORD *)(v2 + (unsigned int)*(_QWORD *)&v3[-1].t0);
v3[-1].t9 = *(_DWORD *)(v2 + (unsigned int)*(_QWORD *)&v3[-1].t0 + 4);
v3[-1].t8 = 0;
v3[-1].t5 = 271733878; // delata
for ( v3[-1].t11 = 0; v3[-1].t11 < (unsigned int)v3[-1].count; ++v3[-1].t11 )
{
v3[-1].t10 += ((((unsigned int)v3[-1].t9 >> 5) ^ (16 * v3[-1].t9)) + v3[-1].t9) ^ (*(_DWORD *)(v2
+ 4 * (v3[-1].t8 & 3)
+ (unsigned int)*(_QWORD *)&v3[-1].t6)
+ v3[-1].t8);
v3[-1].t8 += v3[-1].t5;
v3[-1].t9 += ((((unsigned int)v3[-1].t10 >> 5) ^ (16 * v3[-1].t10)) + v3[-1].t10) ^ (*(_DWORD *)(v2 + 4 * (((unsigned int)v3[-1].t8 >> 11) & 3) + (unsigned int)*(_QWORD *)&v3[-1].t6)
+ v3[-1].t8);
}
*(_DWORD *)(v2 + (unsigned int)*(_QWORD *)&v3[-1].t0) = v3[-1].t10;
result = (unsigned int)v3[-1].t9;
*(_DWORD *)(v2 + (unsigned int)*(_QWORD *)&v3[-1].t0 + 4) = result;
return result;
}

其中我们还是可以通过右键–convert to struct把v3转换成内含12个int的结构体,不难发现这是个xtea加密,传入参数第一个是循环轮次,这个轮次是2,4,8,16,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
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
#include <stdio.h>
#include <stdint.h>

#define SHL(x, n) ( ((x) & 0xFFFFFFFF) << n )
#define ROTL(x, n) ( SHL((x), n) | ((x) >> (32 - n)) )

unsigned int xorKey[44] = {
0x04050607, 0x00010203, 0x0C0D0E0F, 0x08090A0B, 0xCD3FE81B, 0xD7C45477, 0x9F3E9236, 0x0107F187,
0xF993CB81, 0xBF74166C, 0xDA198427, 0x1A05ABFF, 0x9307E5E4, 0xCB8B0E45, 0x306DF7F5, 0xAD300197,
0xAA86B056, 0x449263BA, 0x3FA4401B, 0x1E41F917, 0xC6CB1E7D, 0x18EB0D7A, 0xD4EC4800, 0xB486F92B,
0x8737F9F3, 0x765E3D25, 0xDB3D3537, 0xEE44552B, 0x11D0C94C, 0x9B605BCB, 0x903B98B3, 0x24C2EEA3,
0x896E10A2, 0x2247F0C0, 0xB84E5CAA, 0x8D2C04F0, 0x3BC7842C, 0x1A50D606, 0x49A1917C, 0x7E1CB50C,
0xFC27B826, 0x5FDDDFBC, 0xDE0FC404, 0xB2B30907
};

void decipher(unsigned int num_rounds, uint32_t v[2], uint32_t const key[4]) {
unsigned int i;
uint32_t v0=v[0], v1=v[1], delta = 0x10325476, sum=delta*num_rounds;
for (i=0; i < num_rounds; i++) {
v1 -= (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + key[(sum >> 11) & 3]);
sum -= delta;
v0 -= (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + key[(sum & 3)]);
}
v[0]=v0; v[1]=v1;
}
void XorRol(uint32_t v[2])
{
uint32_t encLow = v[1];
uint32_t encHigh = v[0];
uint32_t orgLow, orgHigh, v6, v7, v8;
int i;

for ( i = 43; i >= 0; i-- )
{
orgLow = encHigh;
v6 = ROTL(orgLow, 1);
v7 = ROTL(orgLow, 8) & v6;
v8 = v7 ^ ROTL(orgLow, 2);
orgHigh = encLow ^ xorKey[i] ^ v8;

encHigh = orgHigh;
encLow = orgLow;
}
v[0] = orgLow; v[1] = orgHigh;
}

int main(){
uint32_t v[] = { 0xFDF5C266, 0x7A328286, 0xCE944004, 0x5DE08ADC, 0xA6E4BD0A, 0x16CAADDC, 0x13CD6F0C, 0x1A75D936 };
uint32_t k[4] = { 0x03020100, 0x07060504, 0x0B0A0908, 0x0F0E0D0C };
int i, j;
uint32_t teaData[8];
for ( i = 0; i <= 3; i++ )
{
decipher(1 << (i + 1), v + i * 2, k);
printf("0x%X, 0x%X, ", v[i * 2], v[i * 2 + 1]);
teaData[i * 2] = v[i * 2];
teaData[i * 2 + 1] = v[i * 2 + 1];
}
puts("\n");

for ( i = 0; i <= 3; i++ )
{
XorRol(teaData + i * 2);
}

puts("\n");

unsigned char * t = (unsigned char *)&teaData;
for ( i = 0; i < 32; i += 4 )
printf("%c%c%c%c", t[i + 3], t[i + 2], t[i + 1], t[i]);

return 0;
}


*CTF{mM7pJIobsCTQPO6R0g-L8kFExhYuivBN}

Jump

属实是把这题整明白了,看了一下午一直在想为什么这个会一直为0,原来是有setjump和longjump函数。

1
2
int setjmp(jmp_buf env);
void longjmp(jmp_buf env, int value);

众所周知,env一般代表的是上下文环境,在这也是如此,什么时候会用到这类函数捏。我们知道goto只能用于函数的局部跳转,跨函数是不行的,那么我们采用setjump和longjump就很好的解决了这个问题,这两个函数保存当前上下文,比如保存现有堆栈环境,在嵌套函数中进行随心所欲的跳转。

setjump返回值永远是0,这就说明了为什么在题中ifelse语句中,永远只进else语句。当先调用了setjump后,再调用longjump,那么返回回值由value参数确定,然后setjump也要返回非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
__int64 __fastcall sub_40293E(__int64 a1, __int64 a2)
{
__int64 v2; // rdx
__int64 v3; // rcx
__int64 v4; // r8
__int64 v5; // r9
__int64 v7; // rsi
__int64 v8; // rdx
__int64 v9; // rcx
__int64 v10; // r8
__int64 v11; // r9
__int64 v12; // rdx
__int64 v13; // rcx
__int64 v14; // r8
__int64 v15; // r9
__int64 v16; // rdx
__int64 v17; // rcx
__int64 v18; // r8
__int64 v19; // r9
__int64 v20; // rdx
__int64 v21; // rcx
__int64 v22; // r8
__int64 v23; // r9
__int64 v24; // rdx
__int64 v25; // rcx
__int64 v26; // r8
__int64 v27; // r9
char v28; // [rsp+0h] [rbp-10h]
char v29; // [rsp+0h] [rbp-10h]
char v30; // [rsp+0h] [rbp-10h]
char v31; // [rsp+0h] [rbp-10h]
char v32; // [rsp+0h] [rbp-10h]
int v33; // [rsp+Ch] [rbp-4h]
int v34; // [rsp+Ch] [rbp-4h]
int v35; // [rsp+Ch] [rbp-4h]
int v36; // [rsp+Ch] [rbp-4h]

scanf(input);
v33 = setjump((__int64)&unk_4C9680, a2, v2, v3, v4, v5, v28);//v33=0
if ( v33 )
{
if ( v33 == 1 )
return 1LL;
}
else
{
de();//是一个添加函数
}
v7 = 1LL;
qword_4C9400 = longjump(dword_4C613C + 1, 1uLL);
if ( !(unsigned int)setjump((__int64)&unk_4C9680, 1LL, v8, v9, v10, v11, v29) )
{
v7 = 1LL;
((void (__fastcall *)())((char *)&sub_401D44 + 1))();
}
qword_4C9408 = sub_427140(8LL * dword_4C613C);
v34 = setjump((__int64)&unk_4C9680, 1LL, v12, v13, v14, v15, v30);
if ( v34 )
{
if ( v34 <= dword_4C613C ) // 循环0x23次
{
v7 = (unsigned int)(v34 + 1);
((void (__fastcall *)())((char *)&sub_401D44 + 1))();
}
}
else
{
v7 = 1LL;
((void (__fastcall *)())((char *)&sub_401D44 + 1))();
}
sub_401F62((char *)qword_4C9408); // 对字符排序
v35 = setjump((__int64)&unk_4C9680, v7, v16, v17, v18, v19, v31);
if ( v35 )
{
if ( v35 <= dword_4C613C )
{
v7 = (unsigned int)v35;
((void (__fastcall *)())((char *)&sub_401D44 + 1))();
}
}
else
{
sub_402826((__int64)&unk_4C9680, v7, v20, v21, v22, v23);// 对比处
}
sub_427730(qword_4C9408);
sub_427730(qword_4C9400);
v36 = setjump((__int64)&unk_4C9680, v7, v24, v25, v26, v27, v32);
if ( !v36 )
((void (__fastcall *)())((char *)&sub_401D44 + 1))();
if ( v36 == 1 )
sub_411920((__int64)"*CTF{%s}\n", (const char *)input);
return 0LL;
}

我们先看de函数:

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
void de()
{
__int64 v0; // rsi
__int64 v1; // rdx
__int64 v2; // rcx
__int64 v3; // r8
__int64 v4; // r9
int v5; // er9
__int64 v6; // rdx
__int64 v7; // rcx
__int64 v8; // r8
__int64 v9; // r9
char v10; // [rsp+0h] [rbp-10h]
char v11; // [rsp+0h] [rbp-10h]
int v12; // [rsp+Ch] [rbp-4h]

if ( sub_4011A0() || (v0 = 3LL, sub_4011A0()) )
{
v0 = 1LL;
((void (__fastcall *)())((char *)&sub_401D44 + 1))();
}
if ( !(unsigned int)setjump((__int64)&unk_4C9460, v0, v1, v2, v3, v4, v10) )
((void (__fastcall *)())((char *)&sub_401D44 + 1))();
sprintf(qword_4C9400, (unsigned int)"%c%s%c", 2, (unsigned int)input, 3, v5);// 在输入前添加2,输入后添加3
v12 = setjump((__int64)&unk_4C9460, (__int64)"%c%s%c", v6, v7, v8, v9, v11);
if ( !v12 )
((void (__fastcall *)())((char *)&sub_401D44 + 1))();
if ( v12 > 0 )
{
qword_4C9660 = longjump(dword_4C613C + 1, 1uLL);
sub_401020(qword_4C9660, qword_4C9400 + v12 - 1LL);
if ( v12 > 1 )
sub_401100();
*(_QWORD *)(qword_4C9408 + 8LL * v12 - 8) = qword_4C9660;
}
((void (__fastcall *)())((char *)&sub_401D44 + 1))();
}

我们首先要盯着input里的变化,这个函数只在我们输入的字符串前增加2,字符串后加3

我是先看到了对比函数,所以先查看一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void __fastcall sub_402826(__int64 a1, __int64 a2, __int64 a3, __int64 a4, __int64 a5, __int64 a6)
{
char v6; // [rsp+0h] [rbp-10h]
int v7; // [rsp+Ch] [rbp-4h]

v7 = setjump((__int64)&unk_4C9540, a2, a3, a4, a5, a6, v6);
if ( v7 )
{
if ( v7 <= dword_4C613C )
{
byte_4C9420[v7 - 1] = *(_BYTE *)(dword_4C613C - 1LL + *(_QWORD *)(8LL * v7 - 8 + qword_4C9408));// 取每个字符串的最后一位
sub_427730(*(_QWORD *)(8LL * v7 - 8 + qword_4C9408));
((void (__fastcall *)())((char *)&sub_401D44 + 1))();
}
}
else
{
((void (__fastcall *)())((char *)&sub_401D44 + 1))();
}
sub_401DC1(byte_4C9420, &unk_4C6100, (unsigned int)dword_4C613C);// 对比处
((void (__fastcall *)())((char *)&sub_401D44 + 1))();
}

对比处藏着我们需要逆向的字符串,然后我们在byte_4C9420发现我们输入的字符串变化了,但变化我们不清楚,返回主函数,继续找,找到sub_401F62,其参数是经过de函数后的字符串,我们进去查看:

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
unsigned __int64 __fastcall sub_401F62(char *a1)
{
int i; // eax
char *v2; // rax
unsigned __int64 result; // rax
char v4; // [rsp+13h] [rbp-49Dh]
char *v5; // [rsp+18h] [rbp-498h]
char *v6; // [rsp+20h] [rbp-490h]
char **v7; // [rsp+28h] [rbp-488h]
char *v8; // [rsp+30h] [rbp-480h]
char *v9; // [rsp+38h] [rbp-478h]
char *v10; // [rsp+40h] [rbp-470h]
char *v11; // [rsp+48h] [rbp-468h]
char *k; // [rsp+48h] [rbp-468h]
char *v13; // [rsp+48h] [rbp-468h]
char *j; // [rsp+50h] [rbp-460h]
char *v15; // [rsp+50h] [rbp-460h]
char *v16; // [rsp+58h] [rbp-458h]
char *m; // [rsp+60h] [rbp-450h]
char *v18; // [rsp+68h] [rbp-448h]
char *v19; // [rsp+98h] [rbp-418h]
__int64 v20[2]; // [rsp+A0h] [rbp-410h] BYREF
__int64 v21; // [rsp+B0h] [rbp-400h] BYREF
unsigned __int64 v22; // [rsp+4A8h] [rbp-8h]

v22 = __readfsqword(0x28u);
v5 = a1;
v6 = a1 + 264;
v20[0] = 0LL;
v20[1] = 0LL;
v7 = (char **)&v21;
while ( v7 > (char **)v20 )
{
v10 = &v5[8 * (((v6 - v5) / 8) >> 1)];
if ( (int)sub_401EB7(v10, v5) < 0 )
sub_401EF6(v10, v5, 8);
if ( (int)sub_401EB7(v6, v10) < 0 )
{
sub_401EF6(v10, v6, 8); // 循环右移1位
if ( (int)sub_401EB7(v10, v5) < 0 )
sub_401EF6(v10, v5, 8);
}
v8 = v5 + 8;
v9 = v6 - 8;
for ( i = sub_401EB7((_QWORD *)v5 + 1, v10); ; i = sub_401EB7(v8, v10) )
{
if ( i < 0 )
{
v8 += 8;
continue;
}
while ( (int)sub_401EB7(v10, v9) < 0 )
v9 -= 8;
if ( v8 >= v9 )
break;
sub_401EF6(v8, v9, 8);
if ( v10 == v8 )
{
v10 = v9;
}
else if ( v10 == v9 )
{
v10 = v8;
}
v8 += 8;
v9 -= 8;
LABEL_22:
if ( v8 > v9 )
goto LABEL_23;
}
if ( v8 != v9 )
goto LABEL_22;
v8 += 8;
v9 -= 8;
LABEL_23:
if ( (unsigned __int64)(v9 - v5) > 0x20 )
{
if ( (unsigned __int64)(v6 - v8) > 0x20 )
{
if ( v9 - v5 <= v6 - v8 )
{
*v7 = v8;
v7[1] = v6;
v7 += 2;
v6 = v9;
}
else
{
*v7 = v5;
v7[1] = v9;
v7 += 2;
v5 = v8;
}
}
else
{
v6 = v9;
}
}
else if ( (unsigned __int64)(v6 - v8) > 0x20 )
{
v5 = v8;
}
else
{
v7 -= 2;
v5 = *v7;
v6 = v7[1];
}
}
v11 = a1;
v2 = a1 + 32;
if ( a1 + 264 <= a1 + 32 )
v2 = a1 + 264;
v19 = v2;
for ( j = a1 + 8; j <= v19; j += 8 )
{
if ( (int)sub_401EB7(j, v11) < 0 )
v11 = j;
}
if ( v11 != a1 )
sub_401EF6(v11, a1, 8);
v15 = a1 + 8;
while ( 1 )
{
v15 += 8;
if ( v15 > a1 + 264 )
break;
for ( k = v15 - 8; (int)sub_401EB7(v15, k) < 0; k -= 8 )
;
v13 = k + 8;
if ( v13 != v15 )
{
v16 = v15 + 8;
while ( --v16 >= v15 )
{
v4 = *v16;
v18 = v16;
for ( m = v16; ; m = v18 )
{
v18 -= 8;
if ( v18 < v13 )
break;
*m = *v18;
}
*m = v4;
}
}
}
result = __readfsqword(0x28u) ^ v22;
if ( result )
sub_459C10();
return result;
}

这个排序函数很长,很难搞,我们先动调搞一下,发现经过sub_401EF6(v10, v6, 8); 函数时会让字符串循环左移一位,然后我们直接略过这个循环函数,直接看循环后的结果,发现在藏字符串的地方存了许多长字符串,那么我们经过观察,能发现几个规律,这个字符串的第一个字符是经过从小到大排列的,而且第一个字符和最后一个字符是永远绑定在一起的,而我们要解密的字符串经过:

1
byte_4C9420[v7 - 1] = *(_BYTE *)(dword_4C613C - 1LL + *(_QWORD *)(8LL * v7 - 8 + qword_4C9408));

也就是取字符串最后一位连起来并且首位是2,末位是3,所以很简单了,直接手算就出了。

1
2
3
4
\x02\x03+1246=BCDEFGLNRSTVZ_acjkmnpquvwx   #第一位字符,按升序排列
\x03jmGn_=uaSZLvN4wFxE6R+p\x02D2qV1CBTck #对比时的字符串,也就是每个字符串最后一位

\x02cwNG1paBu=6Vn2kxSCqm+_4LETvFRZDj\x03