[2019红帽杯]easyRE

Linux可执行文件, 直接拖进IDA, F5查看伪代码, 但是有些复杂抓不住关键, 打开String窗口改由字符串入手.

image-20220128151231261

跟到字符串所在位置, 交叉引用确定程序语句再F5看伪代码, 直接找到关键点

image-20220128151403433

分析程序

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
__int64 sub_4009C6()
{
__int64 result; // rax
int i; // [rsp+Ch] [rbp-114h]
__int64 v2; // [rsp+10h] [rbp-110h]
__int64 v3; // [rsp+18h] [rbp-108h]
__int64 v4; // [rsp+20h] [rbp-100h]
__int64 v5; // [rsp+28h] [rbp-F8h]
__int64 v6; // [rsp+30h] [rbp-F0h]
__int64 v7; // [rsp+38h] [rbp-E8h]
__int64 v8; // [rsp+40h] [rbp-E0h]
__int64 v9; // [rsp+48h] [rbp-D8h]
__int64 v10; // [rsp+50h] [rbp-D0h]
__int64 v11; // [rsp+58h] [rbp-C8h]
char v12[13]; // [rsp+60h] [rbp-C0h] BYREF
char v13[4]; // [rsp+6Dh] [rbp-B3h] BYREF
char v14[19]; // [rsp+71h] [rbp-AFh] BYREF
char v15[32]; // [rsp+90h] [rbp-90h] BYREF
int v16; // [rsp+B0h] [rbp-70h]
char v17; // [rsp+B4h] [rbp-6Ch]
char v18[72]; // [rsp+C0h] [rbp-60h] BYREF
unsigned __int64 v19; // [rsp+108h] [rbp-18h]

v19 = __readfsqword(0x28u);
qmemcpy(v12, "Iodl>Qnb(ocy", 12);
v12[12] = 127;
qmemcpy(v13, "y.i", 3);
v13[3] = 127;
qmemcpy(v14, "d`3w}wek9{iy=~yL@EC", sizeof(v14));
//上面这几行代码将v12~v14填充为"Iodl>Qnb(ocy\x7fy.i\x7fd`3w}wek9{iy=~yL@EC"
//这一长度为36的字符, 与后续v15的比较做准备
memset(v15, 0, sizeof(v15));
v16 = 0;
v17 = 0;
sub_4406E0(0LL, v15, 37LL);
//根据经验,sub_4406E0()应该是某种输入函数
//感觉类似scanf_s(),后面的37应该是限制长度
v17 = 0;
if ( sub_424BA0(v15) == 36 )
//可见v15的长度应该为36,但是v15数组长度只有32,这说明v16的值被输入函数越界覆盖
{
for ( i = 0; i < (unsigned __int64)sub_424BA0(v15); ++i )
//从这里可以猜出sub_424BA0()就是strlen()
{
if ( (unsigned __int8)(v15[i] ^ i) != v12[i] )
//将输入字符串与下标异或后与v12对比.已知v12很容易推出v15的值
{
result = 4294967294LL;
goto LABEL_13;
}
}
sub_410CC0("continue!");
memset(v18, 0, 0x40uLL);
v18[64] = 0;
sub_4406E0(0LL, v18, 64LL);
v18[39] = 0;
if ( sub_424BA0(v18) == 39 )
//v18长度应为39
{
v2 = sub_400E44(v18);
v3 = sub_400E44(v2);
v4 = sub_400E44(v3);
v5 = sub_400E44(v4);
v6 = sub_400E44(v5);
v7 = sub_400E44(v6);
v8 = sub_400E44(v7);
v9 = sub_400E44(v8);
v10 = sub_400E44(v9);
v11 = sub_400E44(v10);
//进到sub_400E44()里可以看到它用了base64码表,应该是base64编码函数
//上面对v18进行了10次base64编码
if ( !(unsigned int)sub_400360(v11, off_6CC090) )
//sub_400360()应该是strcmp()
//对比编码10次后的值是否与off_6CC090这个字符串相等
//将off_6CC090解码10次就可以得到v18
{
sub_410CC0("You found me!!!");
sub_410CC0("bye bye~");
}
result = 0LL;
}
else
{
result = 4294967293LL;
}
}
else
{
result = 0xFFFFFFFFLL;
}
LABEL_13:
if ( __readfsqword(0x28u) != v19 )
sub_444020();
return result;
}

得到v15

1
2
3
4
5
6
7
string = "Iodl>Qnb(ocy\x7fy.i\x7fd`3w}wek9{iy=~yL@EC"

for i in range(0, len(string)):
print(chr(i ^ ord(string[i])), end="")

#Info:The first four chars are `flag`
#一个提示, 但是不知道它指的前4个字符是谁的前4个

得到v18

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import base64

string = """
Vm0wd2VHUXhTWGhpUm1SWVYwZDRWVll3Wkc5WFJsbDNXa1pPVlUxV2NIcFhhMk0\
xVmpKS1NHVkdXbFpOYmtKVVZtcEtTMUl5VGtsaVJtUk9ZV3hhZVZadGVHdFRNVT\
VYVW01T2FGSnRVbGhhVjNoaFZWWmtWMXBFVWxSTmJFcElWbTAxVDJGV1NuTlhia\
0pXWWxob1dGUnJXbXRXTVZaeVdrWm9hVlpyV1hwV1IzaGhXVmRHVjFOdVVsWmlh\
MHBZV1ZSR1lWZEdVbFZTYlhSWFRWWndNRlZ0TVc5VWJGcFZWbXR3VjJKSFVYZFd\
ha1pXWlZaT2NtRkhhRk5pVjJoWVYxZDBhMVV3TlhOalJscFlZbGhTY1ZsclduZG\
xiR1J5VmxSR1ZXSlZjRWhaTUZKaFZqSktWVkZZYUZkV1JWcFlWV3BHYTFkWFRrZ\
FRiV3hvVFVoQ1dsWXhaRFJpTWtsM1RVaG9hbEpYYUhOVmJUVkRZekZhY1ZKcmRG\
Tk5Wa3A2VjJ0U1ExWlhTbFpqUldoYVRVWndkbFpxUmtwbGJVWklZVVprYUdFeGN\
HOVhXSEJIWkRGS2RGSnJhR2hTYXpWdlZGVm9RMlJzV25STldHUlZUVlpXTlZadE\
5VOVdiVXBJVld4c1dtSllUWGhXTUZwell6RmFkRkpzVWxOaVNFSktWa1phVTFFe\
FduUlRhMlJxVWxad1YxWnRlRXRXTVZaSFVsUnNVVlZVTURrPQ==
"""

for i in range(0, 10):
string = base64.b64decode(string)
print(string.decode())

#https://bbs.pediy.com/thread-254172.htm
#怎么是个网站????我的flag呢?!

打开网站, 很好, 上当了...

image-20220128165228452
搞到这里了却发现是个假的, 好尴尬😅

看了别人的WP才知道答案原来从我眼前溜过了

off_6CC090这个字符串附近有点东西

image-20220128230344597
image-20220128230404288

交叉引用找过去

image-20220128230447817

分析一下

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
unsigned __int64 sub_400D35()
{
unsigned __int64 result; // rax
unsigned int v1; // [rsp+Ch] [rbp-24h]
int i; // [rsp+10h] [rbp-20h]
int j; // [rsp+14h] [rbp-1Ch]
unsigned int v4; // [rsp+24h] [rbp-Ch]
unsigned __int64 v5; // [rsp+28h] [rbp-8h]

v5 = __readfsqword(0x28u);
v1 = sub_43FD20(0LL) - qword_6CEE38;
for ( i = 0; i <= 1233; ++i )
{
sub_40F790(v1);
sub_40FE60();
sub_40FE60();
v1 = sub_40FE60() ^ 0x98765432;
}
//上面这一坨不是很懂
v4 = v1;
if ( ((unsigned __int8)v1 ^ byte_6CC0A0[0]) == 102 && (HIBYTE(v4) ^ (unsigned __int8)byte_6CC0A3) == 103 )
//这个判断无关紧要,直接看下面
{
for ( j = 0; j <= 24; ++j )
sub_410E90((unsigned __int8)(byte_6CC0A0[j] ^ *((_BYTE *)&v4 + j % 4)));
//关键点应该就在这里了,byte_6CC0A0这个字符串的内容已知,异或的密钥只有4位,
//结合前面给出的提示,前面4位是"flag",我们就可以得出密钥,从而解密出flag
}
result = __readfsqword(0x28u) ^ v5;
if ( result )
sub_444020();
return result;
}

解密代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <cstdio>

using namespace std;

int main()
{
char ciphertext[25] =
{
64, 53, 32, 86, 93, 24, 34, 69, 23, 47,
36, 110, 98, 60, 39, 84, 72, 108, 36, 110,
114, 60, 50, 69, 91
};
char hint[4] = { 'f','l','a','g' };
char key[4] = { 0 };
for (int i = 0; i < 4; i++)
for (int j = -128; j <= 127; j++)
if ((ciphertext[i] ^ j) == hint[i])
key[i] = j;
for (int i = 0; i < 25; i++)
putchar(ciphertext[i] ^ key[i % 4]);
}
//flag{Act1ve_Defen5e_Test}