RE January 03, 2020

桂林CTF逆向题解

Words count 58k Reading time 53 mins. Read count 0

一、逆向

1、re

第一题,拖进ida发现是打不开的,怀疑题目加壳了,elf文件加壳的话,拖进虚拟机,直接打命令:

strings re

看到:

UPX的壳直接脱了,upx -d 文件名

看到函数名被隐藏了,但是很简单,直接shift +f12查找字符串,input flag,双击定位到那里,查看引用,ctrl + x,就可以定位到主逻辑了,F5,就是上面的界面了,接着分析一波,看到correct,直接进去:

可以知道是除法,但是少了a1[6]的检验,出题粗心吧?!直接脚本搞出来,a1[6] = 66(不能为空,可见字符即可)

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

a = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
a[0] = 166163712 / 1629056
a[1] = 731332800 / 6771600
a[2] = 357245568 / 3682944
a[3] = 1074393000 / 10431000
a[4] = 489211344 / 3977328
a[5] = 518971936 / 5138336
a[6] = 66
a[7] = 406741500 / 7532250
a[8] = 294236496 / 5551632
a[9] = 177305856 / 3409728
a[10] = 650683500 / 13013670
a[11] = 298351053 / 6088797
a[12] = 386348487 / 7884663
a[13] = 438258597 / 8944053
a[14] = 249527520 / 5198490
a[15] = 445362764 / 4544518
a[16] = 981182160 /10115280
a[17] = 174988800 / 3645600
a[18] = 493042704 / 9667504
a[19] = 257493600 / 5364450
a[20] = 767478780 / 13464540
a[21] = 312840624 / 5488432
a[22] = 1404511500 / 14479500
a[23] = 316139670 / 6451830
a[24] = 619005024 / 6252576
a[25] = 372641472 / 7763364
a[26] = 373693320 / 7327320
a[27] = 498266640 / 8741520
a[28] = 452465676 / 8871876
a[29] = 208422720 / 4086720
a[30] = 515592000 / 9374400
a[31] = 719890500 / 5759124
flag = ""
for i in a:
flag += chr(i)
print flag

2、encrypt

查壳,发现没有壳,然后ida分析:

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
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
int length; // eax
int leng; // eax
char v6; // [rsp+4h] [rbp-93Ch]
int i; // [rsp+8h] [rbp-938h]
int v8; // [rsp+Ch] [rbp-934h]
char v9; // [rsp+10h] [rbp-930h]
char v10[8]; // [rsp+420h] [rbp-520h]
char input; // [rsp+430h] [rbp-510h]
char v12[1032]; // [rsp+530h] [rbp-410h]
unsigned __int64 v13; // [rsp+938h] [rbp-8h]

v13 = __readfsqword(0x28u);
v10[0] = 16;
v10[1] = 32;
v10[2] = 48;
v10[3] = 48;
v10[4] = 32;
v10[5] = 32;
v10[6] = 16;
v10[7] = 64;
memset(&input, 0, 0x100uLL);
v8 = strlen(&input);
memset(v12, 0, 0x400uLL);
printf("please input your flag:", a2, v12);
scanf("%s", &input);
memset(&v9, 0, 0x408uLL);
sub_4006B6(&v9, v10, 8);
length = strlen(&input);
xor(&v9, &input, length);
leng = strlen(&input);
base64_gai(&input, leng, v12, &v6);
for ( i = 0; i <= 50; ++i )
{
if ( v12[i] != byte_602080[i] )
{
puts("Wrong");
return 0LL;
}
}
puts("Good");
return 0LL;
}

程序一共就两关,第一关xor加密,第二关魔改base64:

先看第一关:

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
_DWORD *__fastcall sub_4007DB(_DWORD *a1, __int64 input, int length)
{
int v3; // ST28_4
int v4; // ST2C_4
_DWORD *result; // rax
int i; // [rsp+18h] [rbp-1Ch]
int v7; // [rsp+1Ch] [rbp-18h]
int v8; // [rsp+20h] [rbp-14h]
_DWORD *v9; // [rsp+2Ch] [rbp-8h]

v7 = *a1;
v8 = a1[1];
v9 = a1 + 2;
for ( i = 0; i < length; ++i )
{
v7 = (v7 + 1);
v3 = v9[v7];
v8 = (v8 + v3);
v4 = v9[v8];
v9[v7] = v4;
v9[v8] = v3;
*(i + input) ^= LOBYTE(v9[(v3 + v4)]);
}
*a1 = v7;
result = a1;
a1[1] = v8;
return result;
}

这里拿输入进行了亦或,但是v9的值不知道,嘿嘿,gdb动态查看寄存器内容,可直接提取,但是不知道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
72
_DWORD *__fastcall base64_2(__int64 input, int a2, const char *a3, _DWORD *a4)
{
int v4; // eax
int v5; // eax
char v6; // al
int v7; // eax
char v8; // al
char j_2; // ST2F_1
int v10; // eax
int v11; // ST30_4
int v12; // eax
int v13; // eax
int v14; // edx
_DWORD *result; // rax
_DWORD *v16; // [rsp+0h] [rbp-40h]
char j; // [rsp+2Dh] [rbp-13h]
char j_1; // [rsp+2Eh] [rbp-12h]
int v19; // [rsp+30h] [rbp-10h]
int v20; // [rsp+34h] [rbp-Ch]

v16 = a4;
v19 = 0;
v20 = 0;
while ( v20 < a2 )
{
v4 = v20++;
j = *(v4 + input);
if ( v20 >= a2 )
{
v6 = 0;
}
else
{
v5 = v20++;
v6 = *(v5 + input);
}
j_1 = v6;
if ( v20 >= a2 )
{
v8 = 0;
}
else
{
v7 = v20++;
v8 = *(v7 + input);
}
j_2 = v8;
v10 = v19;
v11 = v19 + 1;
a3[v10] = ((j >> 2) & 0x3F) + 61;
v12 = v11++;
a3[v12] = ((((j_1 & 0xFF) >> 4) | 16 * j) & 0x3F) + 61;
a3[v11] = ((((j_2 & 0xFF) >> 6) | 4 * j_1) & 0x3F) + 61;
v13 = v11 + 1;
v19 = v11 + 2;
a3[v13] = (j_2 & 0x3F) + 61;
}
if ( a2 % 3 == 1 )
{
a3[--v19] = 61;
}
else if ( a2 % 3 != 2 )
{
goto LABEL_15;
}
a3[v19 - 1] = 61;
LABEL_15:
v14 = strlen(a3);
result = v16;
*v16 = v14;
return result;
}

一看base64,但是仔细看看会发现是魔改base64,不同之处在于,首先没有表,然后是移位后的结果进行了与运算(0x3F),接着加61,于是倒着逆回去:先减去61,再base64_魔改解密:

1
2
3
4
5
for i in range(0,len(code),4):
a1 = ((code[i]<<2) | (code[i+1]>>4))&0xff
a2 = (((code[i+1]&0x3f)<<4) | (code[i+2]>>2))&0xff
a3 = (((code[i+2]&0x3f)<<6) | code[i+3])&0xff
res += [a1,a2,a3]

字符都是在0-0xff范围内的,所以最终的结果要&0xff。

1
2
3
4
5
6
7
8
9
10
for ( i = 0; i <= 50; ++i )
{
if ( v12[i] != byte_602080[i] )
{
puts("Wrong");
return 0LL;
}
}
puts("Good");
return 0LL;

最后是一个校验,byte_602080就是校验值,ida提取下:

1
2
.data:0000000000602080 byte_602080     db 5Ah                  ; DATA XREF: main+177↑r
.data:0000000000602081 aTzztrdFqpVvlYn db '`TzzTrD|fQP[_VVL|yneURyUmFklVJgLasJroZpHRxIUlH\vZE=',0

我们可以知道,0x5A = ‘Z’,所以字符串就出来了:

s = ‘Z`TzzTrD|fQP[_VVL|yneURyUmFklVJgLasJroZpHRxIUlH\vZE=’

这里因为\要转义,这是一个易错点。

好了,有了字符串就可以逆向了:

先解出base64那一块的密文:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#coding=utf8
s = 'Z`TzzTrD|fQP[_VVL|yneURyUmFklVJgLasJroZpHRxIUlH\\vZE='

print len(s)
code = []
for i in s:
if i!=' ':
code.append(ord(i) - 61)
else:
code.append(0)
res = []
for i in range(0,len(code),4):
a1 = ((code[i]<<2) | (code[i+1]>>4))&0xff
a2 = (((code[i+1]&0x3f)<<4) | (code[i+2]>>2))&0xff
a3 = (((code[i+2]&0x3f)<<6) | code[i+3])&0xff
res += [a1,a2,a3]
print res
print len(res)

接着发现是39位的输入,那么就可以gdb动态查看栈了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
.text:000000000040089C ; 21:     *(i + a2) ^= *(4LL * (v3 + v4) + v9);
.text:000000000040089C mov eax, [rbp+var_1C]
.text:000000000040089F movsxd rdx, eax
.text:00000000004008A2 mov rax, [rbp+var_30]
.text:00000000004008A6 add rax, rdx
.text:00000000004008A9 mov edx, [rbp+var_1C]
.text:00000000004008AC movsxd rcx, edx
.text:00000000004008AF mov rdx, [rbp+var_30]
.text:00000000004008B3 add rdx, rcx
.text:00000000004008B6 movzx esi, byte ptr [rdx]
.text:00000000004008B9 mov edx, [rbp+var_10]
.text:00000000004008BC mov ecx, edx
.text:00000000004008BE mov edx, [rbp+var_C]
.text:00000000004008C1 add edx, ecx
.text:00000000004008C3 movzx edx, dl
.text:00000000004008C6 lea rcx, ds:0[rdx*4]
.text:00000000004008CE mov rdx, [rbp+var_8]
.text:00000000004008D2 add rdx, rcx
.text:00000000004008D5 mov edx, [rdx]
.text:00000000004008D7 xor edx, esi//下断点!查看edx的内容
.text:00000000004008D9 mov [rax], dl
.text:00000000004008DB add [rbp+var_1C], 1
.text:00000000004008DF jmp loc_400810

经过39次xor,得到edx内容为:

0x10,0x59,0x9c,0x92,0x6,0x22,0xcf,0xa5,0x72,0x1e,0x45,0x6a,0x6,0xcb,0x8,0xc3,0xe4,0x49,0x5a,0x63,0xc,0xdf,0xf6,0x5f,0x8,0x28,0xbd,0xe2,0x10,0x15,0x1f,0x6e,0xaa,0x5a,0xca,0xec,0x80,0xaf,0x9b

接着就可以亦或逆向了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#coding=utf8
s = 'Z`TzzTrD|fQP[_VVL|yneURyUmFklVJgLasJroZpHRxIUlH\\vZE='

print len(s)
code = []
for i in s:
if i!=' ':
code.append(ord(i) - 61)
else:
code.append(0)
res = []
for i in range(0,len(code),4):
a1 = ((code[i]<<2) | (code[i+1]>>4))&0xff
a2 = (((code[i+1]&0x3f)<<4) | (code[i+2]>>2))&0xff
a3 = (((code[i+2]&0x3f)<<6) | code[i+3])&0xff
res += [a1,a2,a3]
print res
print len(res)
c = [0x10,0x59,0x9c,0x92,0x6,0x22,0xcf,0xa5,0x72,0x1e,0x45,0x6a,0x6,0xcb,0x8,0xc3,0xe4,0x49,0x5a,0x63,0xc,0xdf,0xf6,0x5f,0x8,0x28,0xbd,0xe2,0x10,0x15,0x1f,0x6e,0xaa,0x5a,0xca,0xec,0x80,0xaf,0x9b]
print len(c)
m = ''
for i in range(39):
m += chr(c[i]^res[i])
print m
1
2
3
4
5
6
v1ct0r@ubuntu:~/Desktop/CTF House/GUICTF/逆向题的题目和答案(1)/re2$ python encrypt.py 
52
[118, 53, 253, 245, 125, 71, 254, 149, 19, 122, 38, 89, 63, 255, 49, 161, 133, 124, 99, 2, 110, 189, 147, 106, 62, 77, 141, 215, 39, 115, 45, 94, 204, 98, 242, 223, 229, 210, 0]
39
39
flag{e10adc3949ba59abbe56e057f20f883e}�

flag就出来了~

3、number_game

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
unsigned __int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
_QWORD *v3; // ST08_8
__int64 input; // [rsp+10h] [rbp-30h]
__int16 v6; // [rsp+18h] [rbp-28h]
__int64 v7; // [rsp+20h] [rbp-20h]
__int16 v8; // [rsp+28h] [rbp-18h]
char i; // [rsp+2Ah] [rbp-16h]
unsigned __int64 v10; // [rsp+38h] [rbp-8h]

v10 = __readfsqword(0x28u);
input = 0LL;
v6 = 0;
v7 = 0LL;
v8 = 0;
i = 0;
__isoc99_scanf("%s", &input, a3);
if ( sub_4006D6(&input) ) // 0-3的输入,一共输入10次
{
v3 = sub_400758(&input, 0, 10); // 存储到v3的过程
sub_400807(v3, &v7); // 转存到v7
i = 0;
sub_400881(&v7); // 存到data
if ( sub_400917() )
{
puts("TQL!");
printf("flag{", &v7);
printf("%s", &input);
puts("}");
}
else
{
puts("your are cxk!!");
}
}
return __readfsqword(0x28u) ^ v10;
}

分析逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
_QWORD *__fastcall sub_400758(__int64 input, int i, int _10)
{
_QWORD *v4; // rax
_QWORD *v5; // ST28_8
int v6; // [rsp+0h] [rbp-30h]
char v7; // [rsp+1Fh] [rbp-11h]

v6 = _10;
v7 = *(i + input);
if ( v7 == 32 || v7 == 10 || i >= _10 )
return 0LL;
v4 = malloc(0x18uLL);
v5 = v4;
*v4 = v7;
v4[1] = sub_400758(input, 2 * i + 1, v6);
v5[2] = sub_400758(input, 2 * (i + 1), v6);
return v5;
}

这里我们知道,通过申请堆块来存储数据,然后堆块的申请有一定的规律,每一次递归回来,从i处开始,所以可得下表:

那么回溯回去时,可以得到查找顺序为:7、3、8、1、9、4、0、5、2、6,那么逆向时对应着0、1、2、3、4、5、6、7、8、9填回去即可,接着看第二关:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
__int64 sub_400917()
{
unsigned int v1; // [rsp+0h] [rbp-10h]
signed int i; // [rsp+4h] [rbp-Ch]
signed int j; // [rsp+8h] [rbp-8h]
int k; // [rsp+Ch] [rbp-4h]

v1 = 1;
for ( i = 0; i <= 4; ++i )
{
for ( j = 0; j <= 4; ++j )
{
for ( k = j + 1; k <= 4; ++k )
{
if ( *(&unk_601060 + 5 * i + j) == *(&unk_601060 + 5 * i + k) )
v1 = 0;
if ( *(&unk_601060 + 5 * j + i) == *(&unk_601060 + 5 * k + i) )
v1 = 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
.data:0000000000601060 unk_601060      db  31h ; 1
.data:0000000000601061 db 34h ; 4
.data:0000000000601062 byte_601062 db 23h ; DATA XREF: sub_400881+F↑w
.data:0000000000601063 db 32h ; 2
.data:0000000000601064 db 33h ; 3
.data:0000000000601065 db 33h ; 3
.data:0000000000601066 db 30h ; 0
.data:0000000000601067 byte_601067 db 23h ; DATA XREF: sub_400881+1D↑w
.data:0000000000601068 db 31h ; 1
.data:0000000000601069 byte_601069 db 23h ; DATA XREF: sub_400881+2B↑w
.data:000000000060106A db 30h ; 0
.data:000000000060106B byte_60106B db 23h ; DATA XREF: sub_400881+39↑w
.data:000000000060106C db 32h ; 2
.data:000000000060106D db 33h ; 3
.data:000000000060106E byte_60106E db 23h ; DATA XREF: sub_400881+47↑w
.data:000000000060106F byte_60106F db 23h ; DATA XREF: sub_400881+55↑w
.data:0000000000601070 db 33h ; 3
.data:0000000000601071 byte_601071 db 23h ; DATA XREF: sub_400881+63↑w
.data:0000000000601072 byte_601072 db 23h ; DATA XREF: sub_400881+71↑w
.data:0000000000601073 db 30h ; 0
.data:0000000000601074 db 34h ; 4
.data:0000000000601075 db 32h ; 2
.data:0000000000601076 byte_601076 db 23h ; DATA XREF: sub_400881+7F↑w
.data:0000000000601077 byte_601077 db 23h ; DATA XREF: sub_400881+8D↑w
.data:0000000000601078 db 31h ; 1
.data:0000000000601078 _data ends

提取出来,画一张数独的图,很快就可以知道,

1
2
3
4
5
1 4 x 2 3
3 0 x 1 x
0 x 2 3 x
x 3 x x 0
4 2 x x 1

空缺的是0421421430,拿到了,在再根据之前的顺序:

1
2
3
4
5
0、1、2、3、4、5、6、7、8、9
7、3、8、1、9、4、0、5、2、6
0、4、2、1、4、2、1、4、3、0
得到:
1、1、3、4、2、4、0、0、2、4

到此,复现完了桂林CTF的Re~

0%