ctfshow_reverse

ctfshow_reverse

主要是写一些逆向的题目试一试

reverse1

拿到一个附件之后,放到 ida 中查看:

。。。

直接就有 flag 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int __fastcall main(int argc, const char **argv, const char **envp)
{
char s2[104]; // [rsp+10h] [rbp-70h] BYREF
unsigned __int64 v5; // [rsp+78h] [rbp-8h]

v5 = __readfsqword(0x28u);
puts("plz input the key:");
__isoc99_scanf("%s", s2);
if ( !strcmp("flag{7ujm8ikhy6}", s2) )
puts("flag{7ujm8ikhy6}");
else
puts("key error");
return 0;
}

flag{7ujm8ikhy6}

reverse2

附件包含一个 勒索病毒.exeenflag.txt

看了一眼 enflag ,是乱码的,应该是被加密的。

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
do
{
sub_401037(asc_406BA0, v4);
sub_401037((char *)&byte_406BFC, v5);
sub_401037(asc_406C48, v6);
sub_401037((char *)&byte_406CA8, v7);
sub_401037((char *)&byte_406D0C, v8);
sub_401037(a1, v9);
sub_401073("%d", (char)v10);
if ( *(_DWORD *)v10 == 1 )
{
v13 = fopen("flag.txt", "r");
if ( !v13 )
{
sub_401037((char *)&byte_406D48, v4);
getchar();
exit(0);
}
v12 = fopen("enflag.txt", "w");
if ( !v12 )
{
sub_401037((char *)&byte_406D70, v4);
getchar();
exit(0);
}
sub_401037(asc_406D84, v4);
sub_401073("%s", (char)Str);
sub_401069(Str, Str1);
sub_401028((int)Str, (int)v15, (int)v14, (int)v13, (int)v12);
}
else if ( *(_DWORD *)v10 == 2 )
{
v11 = 0;
}
else
{
sub_401037(asc_406D98, v4);
}
}
while ( v11 );
return 0;

应该是先在显示上打印提示字符,然后获取输入。

并且对 flag.txt 进行加密之后拿到 enflag.txt 。初步想法是拿到密钥之后根据算法反推原来的 flag,

看看主要的加密函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
char __cdecl sub_401A70(char *Str, char *Str1)
{
char v3; // [esp+0h] [ebp-E4h]
signed int i; // [esp+D0h] [ebp-14h]
signed int v5; // [esp+DCh] [ebp-8h]

__CheckForDebuggerJustMyCode(byte_40B027);
v5 = strlen(Str);
for ( i = 0; i < v5; ++i )
Str1[i] += Str[i] ^ 0x1F;
if ( !strcmp(Str1, "DH~mqqvqxB^||zll@Jq~jkwpmvez{") )
sub_401037((char *)&byte_406B80, v3);
else
sub_401037("Error!\n", v3);
return *Str1;
}

从这里可以知道密钥的处理方法是明文异或(XOR) 0x1F 得到密文

1
DH~mqqvqxB^||zll@Jq~jkwpmvez{

啧,有段字符被 ~ 包着了,md 格式识别成划去的字符了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int __cdecl sub_4014E0(char *Str, int a2, int a3, FILE *Stream, FILE *a5)
{
char v6; // [esp+0h] [ebp-D8h]
size_t v7; // [esp+D0h] [ebp-8h]

__CheckForDebuggerJustMyCode(byte_40B027);
v7 = strlen(Str);
sub_4010F0(a2, Str, v7);
sub_4010C8(a3);
sub_40116D(a3, a2);
sub_4010EB(a3, Stream, a5);
fclose(Stream);
fclose(a5);
return sub_401037(asc_406B34, v6);
}

根据这个结构,ai直接看出是典型的 RC4 加密,而 RC4 是对称加密,于是我们不需要这么麻烦的再写解密脚本,直接使用原来的程序帮我们解密即可。

下面是破解密钥的脚本:

1
2
3
4
5
cipher = "DH~mqqvqxB^||zll@Jq~jkwpmvez{"
key = ""
for c in cipher:
key += chr(ord(c) ^ 0x1F)
print(key)

运行后得到:[Warnning]Access_Unauthorized

然后复制一份 enflag.txt ,改名为 flag.txt

运行原程序:

encrypto

此时打开 enflag.txt ,得到 flag :

flag{RC4&->ENc0d3F1le}

reverse3

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
  v21 = __readfsqword(0x28u);
v7 = 80;
v8 = 64227;
v9 = 226312059;
v10 = -1540056586;
v11 = 5;
v12 = 16;
v13 = 3833;
v5 = 0;
puts("plz input the key:");
__isoc99_scanf("%s", s);
v3 = strlen(s);
strncpy(dest, v19, v3 - 6);
dest[strlen(s) - 6] = 0;
__isoc99_sscanf(dest, "%x", &v5);
v17[0] = v7;
v17[1] = v8;
v17[2] = v9;
v17[3] = v10;
v17[4] = (v11 << 12) + v12;
v17[5] = v13;
v17[6] = v5;
v16 = 0;
for ( i = 0; i <= 6; ++i )
{
for ( v16 += (unsigned int)v17[i]; v16 > 0xFFFF; v16 = v15 + (unsigned int)(unsigned __int16)v16 )
{
v14 = (unsigned __int16)v16;
v15 = v16 >> 16;
}
}
if ( v16 == 0xFFFF )
puts("OK");
else
puts("Error");
return 0;
}

前面的一段是标准的处理 flag 的格式,拿到 flag 里面的东西。

具体的算法是网络校验和,把 v17 的元素加到 v16 里面,只要 v16 大于 0xFFFF ,就把高 16 位和低 16 位拆开,然后加在一起折叠回去。

下面是拿 v5 的脚本:

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
# 把 C 里面的有符号负数强转为 32 位无符号整数
def to_uint32(n):
return n & 0xFFFFFFFF

# 写死的常量数组
v17_constants = [
80,
64227,
226312059,
to_uint32(-1540056586),
(5 << 12) + 16,
3833
]

v16 = 0

# 模拟那个折叠相加的校验和逻辑
for val in v17_constants:
v16 += val
while v16 > 0xFFFF:
v16 = (v16 & 0xFFFF) + (v16 >> 16)

# 我们要让 v16 + v5 的折叠结果为 0xFFFF
# 所以 target_v5 就是 0xFFFF 减去前面常量的折叠和
target_v5 = 0xFFFF - v16


print(f"算出来的 v5 (Hex) 是: {hex(target_v5)}")

算出来的 v5 (Hex) 是: 0x1a9f

flag{1a9f}

题目提示了 4 位即可

reverse4

1
2
3
4
5
6
7
8
9
int __fastcall __noreturn main(int argc, const char **argv, const char **envp)
{
qword_140004618 = (__int64)malloc(0x10u);
qword_140004620 = qword_140004618;
*(_QWORD *)(qword_140004618 + 8) = 0;
sub_140001020((char *)&Format);
sub_140001080("%lld");
((void (__fastcall __noreturn *)())sub_1400010E0)();
}

初步判断是输入东西,然后通过 sub_1400010E0

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
void __fastcall __noreturn sub_1400010E0(char *a1, __int64 a2)
{
int v2; // r9d
__int64 v3; // r8
char *v4; // r10
char v5; // al
__int64 v6; // rbx
unsigned __int8 v7; // cl
char v8; // [rsp+1Fh] [rbp-3F9h]
char v9; // [rsp+20h] [rbp-3F8h] BYREF

v2 = 0;
v3 = (__int64)a1;
if ( a1 )
{
v4 = &v9;
do
{
++v4;
++v2;
a1 = &a4890572163qwe[-26 * (v3 / 26)];
v5 = a1[v3];
v3 /= 26;
a2 = v3;
*(v4 - 1) = v5;
}
while ( v3 );
}
v6 = v2;
while ( v6 )
{
v7 = *(&v8 + v6--);
sub_1400011E0(v7 ^ 7u, a2, v3);
}
sub_140001220(a1, a2, v3);
}

这段:

1
2
a1 = &a4890572163qwe[-26 * (v3 / 26)];
v5 = a1[v3];

是 ida 反汇编取余,本质上是 v5 = table[ v3 % 26 ]

这里实际上是一个 26 进制转换,但是 26 进制的表被改了。

实际上是 )(*&^%489$!057@#><:2163qwe

也就是余 1 ,对应的是 )

然后

1
2
v7 = *(&v8 + v6--);
sub_1400011E0(v7 ^ 7u, a2, v3);

倒序提取余数 ( 因为取余数的时候低位在前面 ) ,然后异或 7 。

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
void __noreturn sub_140001220()
{
__int64 v0; // r9
int v1; // ecx
__int64 v2; // rdx
char v3; // al
int v4; // r8d
__int64 v5; // r9
char v6; // cl
int v7; // eax

v0 = qword_140004620;
v1 = 0;
v2 = 0;
while ( 1 )
{
v3 = *(_BYTE *)v0;
v4 = v1 + 1;
v5 = *(_QWORD *)(v0 + 8);
if ( v3 != aV4pY59[v2] )
v4 = v1;
qword_140004620 = v5;
if ( !v5 )
break;
v6 = *(_BYTE *)v5;
v7 = v4 + 1;
v0 = *(_QWORD *)(v5 + 8);
if ( v6 != aV4pY59[v2 + 1] )
v7 = v4;
qword_140004620 = v0;
if ( v0 )
{
v2 += 2;
v1 = v7;
if ( v2 < 14 )
continue;
}
goto LABEL_11;
}
v7 = v4;
LABEL_11:
if ( v7 == 14 )
sub_1400012E0();
sub_1400012B0();
}

这里是遍历链表然后与硬编码字符串 aV4pY59 比较

1
.rdata:00000001400032F8 aV4pY59         db '/..v4p$$!>Y59-',0   ; DATA XREF: sub_140001220+B↑o

所以比对的密文是:/..v4p$$!>Y59-

以下是解密脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
TABLE = ")(*&^%489$!057@#><:2163qwe"
target_cipher = "/..v4p$$!>Y59-"

def decrypt(cipher):
num = 0
for char in cipher:
original_char = chr(ord(char) ^ 7)
digit = TABLE.index(original_char)
num = num * 26 + digit
return num

flag_num = decrypt(target_cipher)
print(f"目标密文: {target_cipher}")
print(f"解密出的最终 Flag: {flag_num}")

得到数字:

2484524302484524302

运行源程序输入后验证正确。

reverse5