September 18, 2022

XCTF 分站赛复现

再摸鱼要被制裁了……

*CTF 2022

参考:
zsky学长的博客
PZ师傅的博客

simplefs

复现感觉也8难,为什么比赛的时候脑子就跟被糊了一样……

实际上instruction.txt已经给出提示了,但是我当时没太理解这是什么意思,所以直接忽略了,导致一直在错误的方向被折磨……

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
__int64 __fastcall enc(__int64 a1, int a2)
{
int i; // [rsp+10h] [rbp-10h]
int v4; // [rsp+14h] [rbp-Ch]
_BYTE *v5; // [rsp+18h] [rbp-8h]

v4 = sub_559D6FFEF0A3();
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;
}

程序运行参数设置为imagetest.flag 500,IDA远程去调。

我直接断在这个函数进不去,在外面有几句判断的跳转,把外面三句跳转全nop掉,然后成功了……调出来v4的值是0xDEEDBEEF,然后从低到高依次取1字节来循环移位和异或。
*CTF按这个逻辑加密一下,出来的密文是0,0xd2,0xfc,0xd8,在imagetest.flag里匹配一下这几个值,找到密文在偏移0x49000处,然后写脚本复原一下就可以了。
因为懒得写解密就直接用原来的加密程序的屑……

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

h_v='00D2FCD8A2DABA9E9C26F8F6B4CE3CCC9688983482DE80368AD8C0F038AE'
enc = binascii.unhexlify(h_v)
flag=''
for i in enc:
for j in range(0xff):
v5=j
v5 = ((v5 >> 1) | (v5 << 7))&0xff
v5 ^= 0xef
v5 = ((v5 >> 2) | (v5 << 6))&0xff
v5 ^= 0xbe
v5 = ((v5 >> 3) | (v5<<5))&0xff
v5 ^= 0xed
v5 = ((v5 >> 4) | (v5<<4))&0xff
v5 ^= 0xde
v5 = ((v5 >> 5) | (v5<<3))&0xff
if v5==i:
flag+=chr(j)
print(flag)

Jump

setjump和longjump

非局部跳转语句: 在栈上跳过若干调用帧,返回到当前函数调用路径上的某一个函数中。

与goto语句不同,setjmp允许跨函数调用,但是goto不允许。

int setjmp(jmp_buf env);:从longjmp调用返回longjump的val值,直接调用返回0。
void longjmp(jmp_buf env,int val);:返回到setjmp,其中env是setjmp的env,val是setjmp的返回值。
使用第二个参数的原因是一个setjump可以有多个longjmp。

setjmp被调用时,其jmp_buf结构的内存应该未被释放。

感觉好像跟异常处理那个很像,可以直接跳过栈帧🐏了所有局部变量

主函数:

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
int __cdecl main(int argc, const char **argv, const char **envp)
{
__int64 v3; // rdx
__int64 v4; // rcx
__int64 v5; // r8
__int64 v6; // r9
__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
int v24; // edx
int v25; // ecx
int v26; // er8
int v27; // er9
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]
signed int v35; // [rsp+Ch] [rbp-4h]
int v36; // [rsp+Ch] [rbp-4h]

gets(input);
v33 = setjump((__int64)&env1, (__int64)argv, v3, v4, v5, v6, v28);
if ( !v33 )
sub_402689();
if ( v33 == 1 )
return 1;
qword_4C9400 = sub_428A40(dword_4C613C + 1, 1uLL);
if ( !(unsigned int)setjump((__int64)&env1, 1LL, v8, v9, v10, v11, v29) )
longjmp((__int64)&off_4C9460, 1u);
squa = malloc(8LL * dword_4C613C);
v34 = setjump((__int64)&env1, 1LL, v12, v13, v14, v15, v30);
if ( !v34 )
longjmp((__int64)&off_4C9460, 1u);
if ( v34 <= dword_4C613C )
longjmp((__int64)&off_4C9460, v34 + 1);
sub_401F62(squa);
v35 = setjump((__int64)&env1, 1LL, v16, v17, v18, v19, v31);
if ( !v35 )
sub_402826();
if ( v35 <= dword_4C613C )
longjmp((__int64)&unk_4C9540, v35);
free(squa);
free(qword_4C9400);
v36 = setjump((__int64)&env1, 1LL, v20, v21, v22, v23, v32);
if ( !v36 )
longjmp((__int64)&unk_4C9540, dword_4C613C + 1);
if ( v36 == 1 )
printf((unsigned int)"*CTF{%s}\n", (unsigned int)byte_4C9620, v24, v25, v26, v27);
return 0;
}

首先是gets,然后是一堆抽象的setjump和longjmp,能看的函数就sub_402689、sub_401F62和sub_402826

sub_402689:

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

if ( j_strchr_ifunc((unsigned __int64)input, 2) || j_strchr_ifunc((unsigned __int64)input, 3) )
longjmp(&env1, 1LL, 0LL);
if ( !(unsigned int)setjump((__int64)&off_4C9460, 3LL, v0, v1, v2, v3, v8) )
longjmp(&env1, 2LL, 1LL);
_sprintf(qword_4C9400, (__int64)"%c%s%c", 2LL, (const char *)input, 3LL);
v10 = setjump((__int64)&off_4C9460, (__int64)"%c%s%c", v4, v5, v6, v7, v9);
if ( !v10 )
longjmp(&env1, 1LL, 2LL);
if ( v10 > 0 )
{
qword_4C9660 = sub_428A40(dword_4C613C + 1, 1LL);
j_strcat_ifunc_0(qword_4C9660, qword_4C9400 + v10 - 1LL);
if ( v10 > 1 )
j_strcat_ifunc_1(qword_4C9660, qword_4C9400, v10 - 1);
*(_QWORD *)(squa + 8LL * v10 - 8) = qword_4C9660;
}
longjmp(&env1, (unsigned int)v10, 3LL);
}

给输入前面加2后面加3然后sprintf到qword_4C9400,后面的if把qword_4C9400层层移位放到qword_4C9660指向的一堆数组里形成一个矩阵。

sub_401F62是一坨又臭又长的函数……不过是对矩阵做操作,可以先动调跳过,观察前后数据来推测一下函数行为。
执行sub_401F62后,使用一个IDAPython脚本来打印出qword_4C9660指向的矩阵:

1
2
3
4
5
6
addr = [0x8530F0,0x853720,0x853120,0x853150,0x853180,0x8531B0,0x8531E0,0x853210,0x853240,0x853270,0x8532A0,0x8532D0,0x8534E0,0x8536F0,0x853540,0x853360,0x853570,0x8535A0,0x8535D0,0x853450,0x853600,0x853630,0x853660,0x853480,0x8534B0,0x853300,0x853390,0x853510,0x8533C0,0x853420,0x853330,0x8536C0,0x8533F0,0x853690,0x853750]
for i in range(34):
tmp = b""
for j in range(34):
tmp += get_bytes(addr[i] + j, 1)
print(tmp)

我的输入是0123456789qwertyuiopasdfghjklzxc,打印结果:

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
b'\x020123456789qwertyuiopasdfghjklzxc\x03'
b'\x03\x020123456789qwertyuiopasdfghjklzxc'
b'0123456789qwertyuiopasdfghjklzxc\x03\x02'
b'123456789qwertyuiopasdfghjklzxc\x03\x020'
b'23456789qwertyuiopasdfghjklzxc\x03\x0201'
b'3456789qwertyuiopasdfghjklzxc\x03\x02012'
b'456789qwertyuiopasdfghjklzxc\x03\x020123'
b'56789qwertyuiopasdfghjklzxc\x03\x0201234'
b'6789qwertyuiopasdfghjklzxc\x03\x02012345'
b'789qwertyuiopasdfghjklzxc\x03\x020123456'
b'89qwertyuiopasdfghjklzxc\x03\x0201234567'
b'9qwertyuiopasdfghjklzxc\x03\x02012345678'
b'asdfghjklzxc\x03\x020123456789qwertyuiop'
b'c\x03\x020123456789qwertyuiopasdfghjklzx'
b'dfghjklzxc\x03\x020123456789qwertyuiopas'
b'ertyuiopasdfghjklzxc\x03\x020123456789qw'
b'fghjklzxc\x03\x020123456789qwertyuiopasd'
b'ghjklzxc\x03\x020123456789qwertyuiopasdf'
b'hjklzxc\x03\x020123456789qwertyuiopasdfg'
b'iopasdfghjklzxc\x03\x020123456789qwertyu'
b'jklzxc\x03\x020123456789qwertyuiopasdfgh'
b'klzxc\x03\x020123456789qwertyuiopasdfghj'
b'lzxc\x03\x020123456789qwertyuiopasdfghjk'
b'opasdfghjklzxc\x03\x020123456789qwertyui'
b'pasdfghjklzxc\x03\x020123456789qwertyuio'
b'qwertyuiopasdfghjklzxc\x03\x020123456789'
b'rtyuiopasdfghjklzxc\x03\x020123456789qwe'
b'sdfghjklzxc\x03\x020123456789qwertyuiopa'
b'tyuiopasdfghjklzxc\x03\x020123456789qwer'
b'uiopasdfghjklzxc\x03\x020123456789qwerty'
b'wertyuiopasdfghjklzxc\x03\x020123456789q'
b'xc\x03\x020123456789qwertyuiopasdfghjklz'
b'yuiopasdfghjklzxc\x03\x020123456789qwert'
b'zxc\x03\x020123456789qwertyuiopasdfghjkl'

可以观察到是按照每个字符串的第一个字符(字典序)来排序。

sub_402826:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void __fastcall __noreturn 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 )
longjmp((__int64)&env1, 1u);
if ( v7 <= LEN )
{
byte_4C9420[v7 - 1] = *(_BYTE *)(LEN - 1LL + *(_QWORD *)(8LL * v7 - 8 + squa));
free(*(_QWORD *)(8LL * v7 - 8 + squa));
longjmp((__int64)&env1, v7 + 1);
}
if ( !(unsigned int)strcmp(byte_4C9420, enc, LEN) )
longjmp((__int64)&env1, 1u);
longjmp((__int64)&env1, 2u);
}

取了矩阵的每一列的最后一个字符存到byte_4C9420,然后跟密文比较。
那么现在获取到的密文就是矩阵最后一列,排序之后的密文就是矩阵第一列。
这两列密文索引值相同的字符一定是挨着的,因为最初是层层移位的。
现在已知flag第一个字符一定是\x02(程序里加的),那么就依次去最后一列找当前字符的索引值,索引到第一列字符,就是当前的flag字符。
比如第0位字符是\x02,那么就去最后一列找\x02的索引值,然后第一列相应的这个字符就是挨着\x02的字符(即第1位字符),以此类推。

1
2
3
4
5
6
7
8
9
10
11
12
enc = [0x03, 0x6A, 0x6D, 0x47, 0x6E, 0x5F, 0x3D, 0x75, 0x61, 0x53, 
0x5A, 0x4C, 0x76, 0x4E, 0x34, 0x77, 0x46, 0x78, 0x45, 0x36,
0x52, 0x2B, 0x70, 0x02, 0x44, 0x32, 0x71, 0x56, 0x31, 0x43,
0x42, 0x54, 0x63, 0x6B]

enc_sort = sorted(enc)
flag = chr(2)

for i in range(len(enc)):
flag += chr(enc_sort[enc.index(ord(flag[i]))])

print(flag)

不知道为什么有时候调到longjump会直接退出程序 = =
太玄学了,调得想砸电脑

NaCl

氯化钠氯化钠!
比赛的时候 一直想着氯化钠 没看这题,因为在我的WSL1上直接segment fault了 = =
不过今日我早已换了WSL2!它成功运行了!!!来试试!(兴奋地搓搓手

新技能get:IDA trace
来逝一逝

断点下在加密函数,断下之后开始trace,然后走完一轮加密,把trace全部copy出来分析。
(我的输入:01234567890123456789012345

有加密的话直接去找cmp,用循环的话肯定会用到这个指令,然后就可以把代码段分成一段一段的循环,这样方便理解逻辑。
可以对比着动态调试去理解,需要的数据直接去栈上抄。

然后做苦力嗯分析就好了

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
0000004F	SFI:sub_80807C0+E5	jle     loc_80807E0	
0000004F SFI:sub_80807C0:loc_80807E0 mov eax, [r15+30h]
0000004F SFI:sub_80807C0+24 mov [r15+14h], eax
0000004F SFI:sub_80807C0+28 mov eax, [r15+30h]
0000004F SFI:sub_80807C0+2C mov esi, 1
0000004F SFI:sub_80807C0+31 mov edi, eax RDI=0000000030313233
0000004F SFI:sub_80807C0+33 nop word ptr [rax+rax+00000000h]
0000004F SFI:sub_80807C0+3E xchg ax, ax
0000004F SFI:sub_80807C0+40 lea r15, [r15-8] R15=000000007FFF7F08
0000004F SFI:sub_80807C0+44 lea r12, loc_8080820 R12=0000000008080820
0000004F SFI:sub_80807C0+4B mov [r15], r12
0000004F SFI:sub_80807C0+4E jmp loc_80800C0
0000004F SFI:sub_80807C0:loc_80800C0 endbr64
0000004F SFI:sub_80807C0-6FC mov [r15-4], edi
0000004F SFI:sub_80807C0-6F8 mov [r15-8], esi
0000004F SFI:sub_80807C0-6F4 mov eax, [r15-8] RAX=0000000000000001
0000004F SFI:sub_80807C0-6F0 mov edx, [r15-4] RDX=0000000030313233
0000004F SFI:sub_80807C0-6EC mov ecx, eax RCX=0000000000000001
0000004F SFI:sub_80807C0-6EA rol edx, cl RDX=0000000060626466 CF=0

rol_1 -> eax ebx edx

0000004F SFI:sub_80807C0-6E8 mov eax, edx RAX=0000000060626466
0000004F SFI:sub_80807C0-6E6 nop word ptr [rax+rax+00h]
0000004F SFI:sub_80807C0-6E0 lea r15, [r15+8] R15=000000007FFF7F10
0000004F SFI:sub_80807C0-6DC mov edi, [r15-8] RDI=0000000008080820
0000004F SFI:sub_80807C0-6D8 and edi, 0FFFFFFE0h AF=0 SF=0
0000004F SFI:sub_80807C0-6D5 lea rdi, [r13+rdi+0]
0000004F SFI:sub_80807C0-6D0 jmp rdi
0000004F SFI:sub_80807C0:loc_8080820 mov ebx, eax RBX=0000000060626466
0000004F SFI:sub_80807C0+62 mov eax, [r15+30h] RAX=0000000030313233
0000004F SFI:sub_80807C0+66 mov esi, 8 RSI=0000000000000008
0000004F SFI:sub_80807C0+6B mov edi, eax RDI=0000000030313233
0000004F SFI:sub_80807C0+6D lea r15, [r15-8] R15=000000007FFF7F08
0000004F SFI:sub_80807C0+71 lea r12, loc_8080840 R12=0000000008080840
0000004F SFI:sub_80807C0+78 mov [r15], r12
0000004F SFI:sub_80807C0+7B jmp loc_80800C0
0000004F SFI:sub_80807C0:loc_80800C0 endbr64
0000004F SFI:sub_80807C0-6FC mov [r15-4], edi
0000004F SFI:sub_80807C0-6F8 mov [r15-8], esi
0000004F SFI:sub_80807C0-6F4 mov eax, [r15-8] RAX=0000000000000008
0000004F SFI:sub_80807C0-6F0 mov edx, [r15-4] RDX=0000000030313233
0000004F SFI:sub_80807C0-6EC mov ecx, eax RCX=0000000000000008
0000004F SFI:sub_80807C0-6EA rol edx, cl RDX=0000000031323330

rol_8 -> eax edx

0000004F SFI:sub_80807C0-6E8 mov eax, edx RAX=0000000031323330
0000004F SFI:sub_80807C0-6E6 nop word ptr [rax+rax+00h]
0000004F SFI:sub_80807C0-6E0 lea r15, [r15+8] R15=000000007FFF7F10
0000004F SFI:sub_80807C0-6DC mov edi, [r15-8] RDI=0000000008080840
0000004F SFI:sub_80807C0-6D8 and edi, 0FFFFFFE0h
0000004F SFI:sub_80807C0-6D5 lea rdi, [r13+rdi+0]
0000004F SFI:sub_80807C0-6D0 jmp rdi
0000004F SFI:sub_80807C0:loc_8080840 and ebx, eax RBX=0000000020222020

ebx = rol_8 & rol_1

0000004F SFI:sub_80807C0+82 mov eax, [r15+30h] RAX=0000000030313233
0000004F SFI:sub_80807C0+86 mov esi, 2 RSI=0000000000000002
0000004F SFI:sub_80807C0+8B mov edi, eax RDI=0000000030313233
0000004F SFI:sub_80807C0+8D lea r15, [r15-8] R15=000000007FFF7F08
0000004F SFI:sub_80807C0+91 lea r12, loc_8080860 R12=0000000008080860
0000004F SFI:sub_80807C0+98 mov [r15], r12
0000004F SFI:sub_80807C0+9B jmp loc_80800C0
0000004F SFI:sub_80807C0:loc_80800C0 endbr64
0000004F SFI:sub_80807C0-6FC mov [r15-4], edi
0000004F SFI:sub_80807C0-6F8 mov [r15-8], esi
0000004F SFI:sub_80807C0-6F4 mov eax, [r15-8] RAX=0000000000000002
0000004F SFI:sub_80807C0-6F0 mov edx, [r15-4] RDX=0000000030313233
0000004F SFI:sub_80807C0-6EC mov ecx, eax RCX=0000000000000002
0000004F SFI:sub_80807C0-6EA rol edx, cl RDX=00000000C0C4C8CC

rol_2 -> edx eax

0000004F SFI:sub_80807C0-6E8 mov eax, edx RAX=00000000C0C4C8CC
0000004F SFI:sub_80807C0-6E6 nop word ptr [rax+rax+00h]
0000004F SFI:sub_80807C0-6E0 lea r15, [r15+8] R15=000000007FFF7F10
0000004F SFI:sub_80807C0-6DC mov edi, [r15-8] RDI=0000000008080860
0000004F SFI:sub_80807C0-6D8 and edi, 0FFFFFFE0h PF=1
0000004F SFI:sub_80807C0-6D5 lea rdi, [r13+rdi+0]
0000004F SFI:sub_80807C0-6D0 jmp rdi
0000004F SFI:sub_80807C0:loc_8080860 xor eax, ebx RAX=00000000E0E6E8EC PF=0 SF=1

eax = rol_2(input[0]) ^ (rol_8(input[0]) & rol_1(input[0]))

0000004F SFI:sub_80807C0+A2 xor eax, [r15+34h] RAX=00000000D4D3DEDB PF=1

eax ^= input[1]

0000004F SFI:sub_80807C0+A6 mov edx, eax RDX=00000000D4D3DEDB
0000004F SFI:sub_80807C0+A8 mov eax, [r15+2Ch] RAX=0000000000000000
0000004F SFI:sub_80807C0+AC cdqe
0000004F SFI:sub_80807C0+AE lea rcx, ds:0[rax*4] RCX=0000000000000000
0000004F SFI:sub_80807C0+B6 mov rax, [r15+20h] RAX=00000000080AFB60
0000004F SFI:sub_80807C0+BA add rax, rcx SF=0
0000004F SFI:sub_80807C0+BD nop dword ptr [rax]
0000004F SFI:sub_80807C0+C0 mov eax, eax
0000004F SFI:sub_80807C0+C2 mov eax, [r13+rax+0] RAX=0000000004050607

eax ^= key[0]

0000004F SFI:sub_80807C0+C7 xor eax, edx RAX=00000000D0D6D8DC PF=0 SF=1
0000004F SFI:sub_80807C0+C9 mov [r15+30h], eax ; input[1]
0000004F SFI:sub_80807C0+CD mov eax, [r15+14h] RAX=0000000030313233
0000004F SFI:sub_80807C0+D1 mov [r15+34h], eax ; input[0]

tmp = input[0]
input[0] = rol_2(input[0]) ^ (rol_8(input[0]) & rol_1(input[0])) ^ input[1] ^ key[0]
input[1] = tmp

0000004F SFI:sub_80807C0+D5 inc dword ptr [r15+2Ch] SF=0
0000004F SFI:sub_80807C0+D9 nop dword ptr [rax+00000000h]

后面xxtea就不贴了,手搓解密脚本即可。

解密脚本不贴了,因为刚开始出了点问题,后来跟zsky学长的脚本一一对比,到最后已经基本上是逐句复制粘贴………………

那么所以某个傻子的脚本到底是哪里出了问题呢?
其实就是这一句v[1] = rol(v[0],2) ^ (rol(v[0],8) & rol(v[0],1)) ^ tmp ^ key[43 - k];
rol的定义我是这样写的:#define rol(w,r) (w<<r)|(w>>(32-r)

宏定义一定要带括号!!!
宏定义一定要带括号!!!
宏定义一定要带括号!!!

这样是不是好像没什么问题?但是不像函数一样是返回一个值,宏定义是不管这些直接替换的,所以说rol这玩意被替换掉之后是不带括号的,而或运算的优先级很低,会被&和^抢占,这样解密就会出问题………………

你是怎么发现的?

关于我是怎么发现的………………因为XXTEA解密都是祖传脚本,那么出问题的只有可能是这里的代码了。
我试着调换了一下异或的顺序,它的结果居然改变了!!!

好吧,这铁运算优先级的锅了……就因为这个sb错误又浪费好久😭😭😭

或者PZ师傅那个思路也挺好的嗯,我只是想试一试trace

ACTF2022

dropper

UPX脱壳不能运行……就脱壳静态看,带壳动态调。这题还是有点邪门的,把ALSR扬了之后居然不能跑,估计是有校验什么的……那就带着调吧,反正影响不大。

主逻辑在sub_140019470,sub_1400113D9有一个异或,还原出傀儡进程的代码。
静态看找了半天没看到还原前的数据在哪,就直接上手调了(别问我为什么用IDA调,因为我是懒🐶

带壳程序载入IDA,有代码的地方翻到最底下,有个大跳jmp near ptr byte_140011591,在这下断然后直接跑,jmp之后就直接进入OEP了。

然后就对比着静态的函数逻辑,走到sub_1400113D9,看参数就能找到数据,然后使用IDAPython脚本dump数据:

1
2
3
4
5
6
import idaapi

data = idaapi.dbg_read_memory(0x7FF7508681C0,0x25400)
w = open("D:\\ctf\\tmp\\data.txt",'wb')
w.write(data)
w.close()

异或还原:

1
2
3
4
5
6
7
r = open("D:\\ctf\\tmp\\data.txt",'rb')
w = open("D:\\ctf\\tmp\\new.exe",'wb')

s = r.read()
data = [i^0x73 for i in s]

w.write(bytes(data))

别问我为什么不直接提取异或之后的数据……调了两次跑飞了,eip跳来跳去单步也步不动,不知道是什么bug🥲,搞得烦了直接抓瞎dump数据。

代码很长就不全部贴了,只贴关键部分 C++出题真不是人啊……

sub_7FF6E770D080:

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
sub_7FF6E7701032(std::cin, (__int64)input);//输入
strlen = strlen_0(input);
copy_input = strcopy((__int64)input);//拷贝
base64_input = base64(copy_input, (unsigned int)strlen);//base64
j_UA_Server_removeCallback_0_0((__int64)input, base64_input);
v17 = v16;
strlen = sub_7FF6E7701136((__int64)v16, (__int64)input);
sub_7FF6E7701244((__int64)v11, strlen);
v19 = j_operator_new_unsigned___int64_(2016i64);
if ( v19 )
{
v21 = &v20;
strlen = not((__int64)v22, (__int64)&unk_7FF6E771F300, 360i64);
v31 = strlen;
v29 |= 1u;
v4 = strcopy(strlen);
rev_enc = gen_int((__int64)v21, v4);
v32 = sub_7FF6E7701433(v19, (__int64)rev_enc);// 4个一组 逆序存
}
else
{
v32 = 0i64;
}
v18 = v32;
v12 = (void (__fastcall ***)(_QWORD, __int64))v32;
if ( (v29 & 1) != 0 )
{
v29 &= 0xFFFFFFFE;
clear((__int64)v22);
}
sub_7FF6E7701226((__int64)v12);
v24 = j_operator_new_unsigned___int64_(2004i64);
if ( v24 )
strlen = (__int64)cpy_(v24, (__int64)v11);
else
strlen = 0i64;
v23 = strlen;
v13 = strlen;
(**v12)(v12, strlen);
sub(v13, (__int64)v14, (__int64)(v12 + 1));
if ( (unsigned __int8)sub_7FF6E7701014((__int64)v14) )
{
strlen = not((__int64)v25, (__int64)&unk_7FF6E771F910, 4i64);
v31 = strlen;
v6 = sub_7FF6E7701735(std::cout, strlen);
std::ostream::operator<<(v6, sub_7FF6E770105A);
clear((__int64)v25);
}

有用的是sub_7FF6E7701244,进去之后到sub_7FF6E770BC50,其中主要的一个循环:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
for ( v10[0] = 0; ; ++v10[0] )
{
v16 = v10[0];
v4 = strlen_0(a2);
if ( v16 >= v4 )
break;
To_10000(num_128, 128i64);
pow1_3_to_2(num_128, v12, v10);//a2 = pow(a1,a3)
v5 = (char *)sub_7FF6E7701118(a2, v10[0]);
To_10000(v13, (unsigned int)*v5);
mul((__int64)v13, (__int64)v14, (__int64)v12);
v6 = add((__int64)v9, (__int64)v15, (__int64)v14);
cpy((__int64)v9, v6);
}
cpy_(a1, (__int64)v9);

pow1_3_to_2自己梳理一一下逻辑就会发现是个pow函数。这个for循环做的事情大概就是

1
2
3
4
5
num = 128 #0x80
n = 0
for i in range(len(flag_base64)):
t = pow(num,i)
n += t * flag_base64[i]

sub_7FF6E7701433转换了程序里的一组字符串,逆序4个一组(也就是万进制)去存。
sub_7FF6E7701226触发一个除零异常(7FF6E770C09F),触发异常后跳转到7FF6E770C0B9,替换虚表,把加密函数替换成sub_7FF6E770187F。此函数里就是做了一些加减乘的操作。

虽然逻辑不难,但是真的调到自闭啊啊啊啊啊啊啊啊啊啊啊这代码是真的难看啊嵌的一套又一套……
不知道为什么我把异常pass to application之后就直接异常退出了,不会转到异常处理……又是每天一个玄学问题

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
box = [8433, 7593, 342, 2871, 1984, 1642, 9440, 3394, 8311, 2028, 7079, 8305, 248, 657, 986, 5500, 7924, 9497, 3109, 8290, 8787, 1600, 2271, 7732, 8512, 3986, 923, 4719, 9219, 3685, 496, 6248, 365, 1718, 8724, 5635, 6437, 5806, 4816, 6193, 396, 3063, 3735, 206, 1564, 912, 6633, 8869, 5633, 6686, 5073, 3516, 4477, 8799, 8818, 123, 9190, 1695, 723, 7151, 998, 6100, 8836, 952, 593, 5702, 374, 2078, 9411, 7813, 4247, 4708, 2612, 6715, 4071, 9894, 8003, 6194, 8622, 572, 1218, 9605, 6119, 5597, 9744, 7046, 3370, 1814, 7205, 8345]
key = []
key.append([9388, 278, 1268, 2916, 7619, 6986, 434, 8142, 3713, 9643, 347, 9517, 684, 3959, 8949, 6627, 7251, 2918, 4540, 6458])
key.append([3526, 2132, 5621, 9575, 2298, 3616, 2055, 4103, 6348, 7812, 7953, 5076, 1898, 5217, 2831, 8048, 6973, 4104, 3410, 1178])
key.append([6793, 3650, 250, 4109, 5341, 7164, 9947, 6850, 7328, 1517, 2100, 5823, 1796, 8109, 9725, 4418, 7918, 7776, 851, 5544])
key.append([3607, 1798, 3103, 361, 8776, 2045, 5992, 8020, 5492, 9304, 884, 7531, 2328, 3791, 8477, 7574, 7147, 5891, 7047, 1786])
key.append([2787, 1695, 495, 7189, 4984, 8401, 8477, 8821, 1524, 9333, 3347, 2287, 3600, 1748, 8538, 1238, 8239, 7065, 7302, 753])
key.append([1664, 212, 1655, 7713, 8717, 2355, 2419, 6471, 3425, 9343, 7457, 8098, 5638, 1968, 6185, 5824, 9929, 9356, 3226, 8079])
key.append([9599, 857, 6193, 8631, 2984, 4037, 2980, 9442, 4673, 3411, 3202, 4672, 8769, 4438, 4458, 1523, 8917, 2266, 5283, 1438])
key.append([5749, 2729, 3467, 3377, 5922, 1736, 5403, 6104, 8175, 5668, 8967, 3257, 1340, 560, 7850, 8145, 4013, 7728, 9029, 5507])
key.append([465, 1390, 2723, 8764, 2468, 1737, 274, 6519, 9490, 2912, 2074, 3846, 4905, 4522, 9220, 3671, 286, 4572, 9332, 7111])
key.append([8894, 7959, 1416, 7040, 5241, 5871, 2250, 3438, 5007, 4180, 8698, 258, 5030, 405, 721, 9620, 4969, 9524, 5573, 5770])

# ((x+key[0])*key[1] - key[2] + key[3]) * key[4] - key[5] + key[6] - key[7] + key[8] - key[9] - box

def to_W(lst):
num = 0
for i in lst[::-1]:
num *= 10000
num += i
return num

w_key = [to_W(i) for i in key]
box = to_W(box)

flag = ((box + w_key[9] - w_key[8] + w_key[7] - w_key[6] + w_key[5])//w_key[4] - w_key[3] + w_key[2])//w_key[1] - w_key[0]
f = ''

while flag > 0:
i = flag % 0x80
f += chr(i)
flag//= 0x80

import base64
print(base64.b64decode(f.encode()))
#b'ACTF{dr0pp3r_1s_v3ry_int3r3st1ng_1d7a90a63039831c7fcaa53b766d5b2d!!!!!}'

关于本文

本文作者 云之君, 许可由 CC BY-NC 4.0.