2024长城杯初赛逆向

比赛吐槽

线上比赛还得队员在一起开摄像头,逆天比赛

题目不给分类

3h 20t?开玩笑吧

逆向部分题目质量不错

Tea

简单的tea,输入flag每8字节加密一次,一共加密5次,密钥固定

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
int __fastcall main(int argc, const char **argv, const char **envp)
{
char Str[64]; // [rsp+20h] [rbp-70h] BYREF
char v5[39]; // [rsp+60h] [rbp-30h]
char v6[3]; // [rsp+87h] [rbp-9h] BYREF
int i; // [rsp+8Ch] [rbp-4h]

_main();
v5[0] = -119;
v5[1] = -48;
v5[2] = -121;
v5[3] = 54;
v5[4] = -55;
v5[5] = 69;
v5[6] = -39;
v5[7] = -48;
v5[8] = 113;
v5[9] = 59;
v5[10] = 54;
v5[11] = -109;
v5[12] = 24;
v5[13] = -65;
v5[14] = 1;
v5[15] = 99;
v5[16] = -87;
v5[17] = 54;
v5[18] = 126;
v5[19] = -9;
v5[20] = -1;
v5[21] = 32;
v5[22] = 25;
v5[23] = -126;
v5[24] = -51;
v5[25] = 119;
v5[26] = 123;
v5[27] = -118;
v5[28] = 18;
v5[29] = 48;
v5[30] = 34;
v5[31] = 80;
v5[32] = -106;
v5[33] = -87;
v5[34] = -53;
v5[35] = 92;
v5[36] = 43;
v5[37] = 33;
v5[38] = -109;
qmemcpy(v6, "ta}", sizeof(v6));
printf("plz input your flag:");
scanf("%42s", Str);
if ( strlen(Str) != 42 )
{
printf("wrong length");
exit(0);
}
for ( i = 0; i <= 39; i += 8 )
encrypt(&Str[i], &key);
for ( i = 0; i <= 41; ++i )
{
if ( Str[i] != v5[i] )
{
printf("error");
exit(0);
}
}
printf("win");
return 0;
}

密钥key

tea算法

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

v6 = *a1;
v5 = a1[1];
v4 = 0;
for ( i = 0; i <= 0x1F; ++i )
{
v4 -= 0x61C88647;
v6 += (v5 + v4) ^ (*a2 + 16 * v5) ^ ((v5 >> 5) + a2[1]);
v5 += (v6 + v4) ^ (a2[2] + 16 * v6) ^ ((v6 >> 5) + a2[3]);
}
*a1 = v6;
result = a1 + 1;
a1[1] = v5;
return result;
}

直接逆就行

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
#!/usr/bin/env python

ans = [
0x3687D089, 0xD0D945C9, 0x93363B71, 0x6301BF18, 0xF77E36A9, 0x821920FF,
0x8A7B77CD, 0x50223012, 0x5CCBA996, 0x7493212B, 0x00007D61
]

key = [0x12345678, 0x0BADF00D, 0x05201314, 0x87654321]

for i in range(0, 10, 2):
v6 = ans[i]
v5 = ans[i+1]
sum = 0xc6ef3720
for _ in range(0x20):
v5 -= (v6 + sum) ^ (key[2] + 16 * v6) ^ ((v6 >> 5) + key[3])
v5 = (v5 + 0x100000000) & 0xFFFFFFFF
v6 -= (v5 + sum) ^ (key[0] + 16 * v5) ^ ((v5 >> 5) + key[1])
v6 = (v6 + 0x100000000) & 0xFFFFFFFF
sum = (sum + 0x61C88647) & 0xFFFFFFFF
ans[i] = v6
ans[i+1] = v5

flag = b''
for d in ans:
flag += d.to_bytes(4, 'little')

print(flag)

Vm

虚拟机题,附件里面连符号都没去,很好分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int __fastcall main(int argc, const char **argv, const char **envp)
{
int v3; // eax
char s[142]; // [rsp+0h] [rbp-90h] BYREF
__int16 v6; // [rsp+8Eh] [rbp-2h]

vm_init(&vm, argv, envp);
vm_load(&vm, &ctf_rom, (unsigned int)ctf_rom_len);
printf("Enter the flag: ");
fgets(s, 128, stdin);
s[strlen(s) - 1] = 0;
v3 = strlen(s);
vm_fill_input((__int64)&vm, s, v3);
v6 = vm_run(&vm);
if ( v6 )
puts("Incorrect flag!");
else
puts("Correct flag!");
return v6;
}

init_vm里面初始化了虚拟机PC,栈还有flag堆

1
2
3
4
5
6
7
8
9
10
_QWORD *__fastcall vm_init(_QWORD *a1)
{
_QWORD *result; // rax

a1[0x1000] = (char *)a1 + 4919; // PC
a1[0x1041] = a1 + 0x1001; // SP = &BP
result = a1;
a1[0x1052] = a1 + 0x1042; // &flag
return result;
}

vm_load把rom加载到PC处

vm_fill_input把输入flag复制到虚拟机的flag堆里面

主要分析虚拟机主函数vm_run

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
          while ( 1 )
{
byte = vm_read_byte((__int64)a1);
if ( byte != 0xA2 )
break;
v9 = vm_pop((__int64)a1);
v8 = v9 * vm_pop((__int64)a1);
vm_push((__int64)a1, v8);
}
if ( byte > 0xA2u )
break;
if ( byte == 0x52 )
{
v7 = vm_read_short((__int64)a1);
if ( !(unsigned __int16)vm_pop((__int64)a1) )
a1[0x1000] = (char *)a1 + v7;
}
else if ( byte > 0x52u )
{
if ( byte == 0x66 )
{
v5 = vm_pop((__int64)a1) + 1;
vm_push((__int64)a1, v5);
}
else
{
if ( byte != 0x9B )
goto LABEL_31;
vm_pop((__int64)a1);
}
}
else
{
if ( byte != 3 )
goto LABEL_31;
v11 = vm_pop((__int64)a1);
v10 = vm_pop((__int64)a1) ^ v11;
vm_push((__int64)a1, v10);
}
}
if ( byte != 0xCE )
break;
v6 = vm_pop((__int64)a1);
vm_push((__int64)a1, v6);
vm_push((__int64)a1, v6);
}
if ( byte > 0xCEu )
break;
if ( byte == 0xA4 )
{
v12 = vm_read_short((__int64)a1);
vm_push((__int64)a1, v12);
}
else
{
if ( byte != 0xB1 )
goto LABEL_31;
if ( (__int64)(a1[0x1052] - (_QWORD)(a1 + 0x1042)) > 0x7F )
exit(6);
v3 = (unsigned __int8 *)a1[0x1052];
a1[0x1052] = v3 + 1;
vm_push((__int64)a1, *v3);
}
}
if ( byte != 0xDF )
break;
v4 = vm_read_short((__int64)a1);
if ( a1 + 0x1001 == (_QWORD *)a1[0x1041] )
a1[0x1000] = (char *)a1 + v4;
}
if ( byte != 0xE1 )
LABEL_31:
exit(2);
return (unsigned __int16)vm_read_short((__int64)a1);

分析虚拟机指令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
"""
vm[0x8000] PC
vm[0x8008] BP
vm[0x8208] SP
vm[0x8290] &flag
vm[0x8210] flag

0xA2 栈顶两个数相乘
0x52 根据栈顶判断 跳转
0x66 栈顶数加一
0x9B pop栈顶
0x03 栈顶两个数异或
0xCE 重复栈顶的数
0xA4 push 立即数
0xB1 push 1字节flag到栈
0xDF 栈空跳转
0xE1 return
_ exit
"""

把rom导出,解析一下rom代码

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
pc = 0
while (pc < len(rom)):
print(f"[{pc}]")
op = rom[pc]
pc += 1
match (op):
case 0xA2:
print("pop ax")
print("pop bx")
print("mul ax, bx")
print("push ax")
case 0x52:
l = rom[pc]
h = rom[pc+1]
i = (h << 8) | l
i -= 4919
pc += 2
print("pop ax")
print("test ax, ax")
print(f"movz pc, {i}")
case 0x66:
print("pop ax")
print("inc ax")
print("push ax")
case 0x9B:
print("pop ax")
case 0x03:
print("pop ax")
print("pop bx")
print("xor ax, bx")
print("push ax")
case 0xCE:
print("pop ax")
print("push ax")
print("push ax")
case 0xA4:
l = rom[pc]
h = rom[pc+1]
i = (h << 8) | l
pc += 2
print(f"push {hex(i)}")
case 0xB1:
print("mov ax, *flag++")
print("push ax")
case 0xDF:
l = rom[pc]
h = rom[pc+1]
i = (h << 8) | l
i -= 4919
pc += 2
print("test sp, bp")
print(f"movz pc, {i}")
case 0xE1:
print("ret rom[pc]")
case _:
print("error")
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
[0]
push 0x8205
[3]
push 0x102
[6]
push 0xa541
[9]
push 0xe6
[12]
push 0xa02d
[15]
push 0xdf
[18]
push 0xcb16
[21]
push 0x81
[24]
push 0xbc8f
[27]
push 0xa6
[30]
push 0xc0f6
[33]
push 0xa3
[36]
push 0xb06d
[39]
push 0xd2
[42]
push 0x9da4
[45]
push 0xe7
[48]
push 0xd2b9
[51]
push 0x7a
[54]
push 0xb47b
[57]
push 0xb3
[60]
push 0xcaf3
[63]
push 0x8c
[66]
push 0xc24c
[69]
push 0x87
[72]
push 0xeec7
[75]
push 0x26
[78]
push 0x8b53
[81]
push 0x106
[84]
push 0x9141
[87]
push 0x10e
[90]
push 0xb7a1
[93]
push 0x9d
[96]
push 0xd3d6
[99]
push 0x77
[102]
push 0xae54
[105]
push 0xcf
[108]
push 0x992d
[111]
push 0xf6
[114]
push 0xbaae
[117]
push 0xa9
[120]
push 0xa767
[123]
push 0xd2
[126]
push 0xa631
[129]
push 0xf2
[132]
push 0xeea1
[135]
push 0x26
[138]
push 0x87e4
[141]
push 0x115
[144]
push 0xf24a
[147]
push 0x1d
[150]
push 0xc382
[153]
push 0xa3
[156]
push 0x9021
[159]
push 0x102
[162]
push 0xb94b
[165]
push 0xb5
[168]
push 0xcba0
[171]
push 0x6d
[174]
push 0x867d
[177]
push 0x12e
[180]
push 0xa570
[183]
push 0xef
[186]
push 0xc7e3
[189]
push 0x85
[192]
push 0xf0db
[195]
push 0x26
[198]
mov ax, *flag++
push ax
[199]
pop ax
push ax
push ax
[200]
pop ax
test ax, ax
movz pc, 212
[203]
pop ax
pop bx
mul ax, bx
push ax
[204]
pop ax
pop bx
xor ax, bx
push ax
[205]
pop ax
inc ax
push ax
[206]
pop ax
test ax, ax
movz pc, 198
[209]
ret rom[pc]
[210]
error
[211]
error
[212]
pop ax
[213]
test sp, bp
movz pc, 219
[216]
ret rom[pc]
[217]
error
[218]
error
[219]
ret rom[pc]
[220]
error
[221]
error

逻辑很简单,先压32*2个数到栈里面,然后依次从flag取一个字节,先检查是否为空字符,是空字符直接返回,否则跟栈里的数先做乘法,再做异或,最后+1检查是否为0,为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
ls = [
0x8205, 0x102,
0xa541, 0xe6,
0xa02d, 0xdf,
0xcb16, 0x81,
0xbc8f, 0xa6,
0xc0f6, 0xa3,
0xb06d, 0xd2,
0x9da4, 0xe7,
0xd2b9, 0x7a,
0xb47b, 0xb3,
0xcaf3, 0x8c,
0xc24c, 0x87,
0xeec7, 0x26,
0x8b53, 0x106,
0x9141, 0x10e,
0xb7a1, 0x9d,
0xd3d6, 0x77,
0xae54, 0xcf,
0x992d, 0xf6,
0xbaae, 0xa9,
0xa767, 0xd2,
0xa631, 0xf2,
0xeea1, 0x26,
0x87e4, 0x115,
0xf24a, 0x1d,
0xc382, 0xa3,
0x9021, 0x102,
0xb94b, 0xb5,
0xcba0, 0x6d,
0x867d, 0x12e,
0xa570, 0xef,
0xc7e3, 0x85,
0xf0db, 0x26
]

for i in range(len(ls)-2, -1, -2):
a = ls[i]
b = ls[i+1]
# (b * f) ^ a = 0xFFFF
f = ((0xFFFF ^ a) // b) & 0xFF
print(chr(f), end='')

Time_Machine

这题比赛中没打完,挺有意思的父子进程题目

这道题ida的反编译很烂,看反编译分析干扰项太多

没有符号,先字符串定位

提示输入Enter The Flag:\n跟提示flag正确Correct Flag :)\n不在同一个地方

输入的地方结尾有个函数指针,下断点但是断不下来,交叉引用找入口

这里就是主函数,根据环境变量qazqweedccxz决定进入的分支,当该环境变量存在的时,进入输入分支,看看另一个分支

设置环境变量qazqweedccxz=1,然后开一个子进程并调试,子进程进输入分支,难怪断不下来,重新在main分支的地方下断点,手动进子进程分支,看看结尾出函数指针在干嘛

从输入的flag里取一个字节,也没发现有什么校验逻辑,但注意到ud2指令(0F 0B),运行到该指令会触发异常,由于父进程在调试子进程,所有分析父进程的操作逻辑

EXCEPTION_DEBUG_EVENT = 1

当调试出现异常时进入前面看到的输出Correct Flag :)\n的函数

断点调试一下

先是获取寄存器信息

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
typedef struct _CONTEXT {
DWORD64 P1Home;
DWORD64 P2Home;
DWORD64 P3Home;
DWORD64 P4Home;
DWORD64 P5Home;
DWORD64 P6Home;
DWORD ContextFlags;
DWORD MxCsr;
WORD SegCs;
WORD SegDs;
WORD SegEs;
WORD SegFs;
WORD SegGs;
WORD SegSs;
DWORD EFlags;
DWORD64 Dr0;
DWORD64 Dr1;
DWORD64 Dr2;
DWORD64 Dr3;
DWORD64 Dr6;
DWORD64 Dr7;
DWORD64 Rax;
DWORD64 Rcx;
DWORD64 Rdx;
DWORD64 Rbx;
DWORD64 Rsp;
DWORD64 Rbp;
DWORD64 Rsi;
DWORD64 Rdi;
DWORD64 R8;
DWORD64 R9;
DWORD64 R10;
DWORD64 R11;
DWORD64 R12;
DWORD64 R13;
DWORD64 R14;
DWORD64 R15;
DWORD64 Rip;
union {
XMM_SAVE_AREA32 FltSave;
NEON128 Q[16];
ULONGLONG D[32];
struct {
M128A Header[2];
M128A Legacy[8];
M128A Xmm0;
M128A Xmm1;
M128A Xmm2;
M128A Xmm3;
M128A Xmm4;
M128A Xmm5;
M128A Xmm6;
M128A Xmm7;
M128A Xmm8;
M128A Xmm9;
M128A Xmm10;
M128A Xmm11;
M128A Xmm12;
M128A Xmm13;
M128A Xmm14;
M128A Xmm15;
} DUMMYSTRUCTNAME;
DWORD S[32];
} DUMMYUNIONNAME;
M128A VectorRegister[26];
DWORD64 VectorControl;
DWORD64 DebugControl;
DWORD64 LastBranchToRip;
DWORD64 LastBranchFromRip;
DWORD64 LastExceptionToRip;
DWORD64 LastExceptionFromRip;
} CONTEXT, *PCONTEXT;

然后是读两个字节的内存

计算一下这里的偏移,lpContext+0xF8,刚好是RIP的偏移,即从读两字节出错的指令

接着检查这两个字节是否为0F 0B,很巧,刚好是子进程中的ud2,很明显这里在捕获子进程在ud2指令出错

第一次异常不是ud2,直接退出了,F9接着调,中间很像flag校验的地方,先忽略,看后面写内存的地方

往RIP写两个字节0x90,把出错的ud2直接nop掉了

父进程调试子进程思路基本明确,子进程靠错误指令ud2触发异常,父进程nop掉异常指令恢复子进程运行

接下来分析中间的校验部分

取r12b,进行加密操作后结果与r11d校验,最后检查r13是否为1,两个校验都通过,计数器+1,计数器等于0x1C时输出正确

分析一下加密函数

因为输入的只有1个字节,中间只会case 1

直接把1字节的256中结果全算出来,后面直接查表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
m = {}
for c in range(256):
l1 = 1
l2 = ((c + l1) << 10) ^ (c + l1)
l1 = (l2 >> 1) + l2
l3 = (((8 * l1) ^ l1) >> 5) + ((8 * l1) ^ l1)
l3 &= 0xFFFFFFFF
l4 = (((16 * l3) ^ l3) >> 17) + ((16 * l3) ^ l3)
l4 &= 0xFFFFFFFF
r = (l4 << 25) ^ l4
r &= 0xFFFFFFFF
r = (r >> 6) + r
r &= 0xFFFFFFFF
print(hex(c), hex(r))
m[r] = c

回到子进程的奇怪代码

现在就知道子进程在干嘛了,先从flag取一个字节到r12b,再计算r11d,给r13赋值,前面分析父进程知道只有在r13==1时才进行校验,用idapython解析一下,在r13==1时查表r11d得到r12b,即得到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
import idc

m = {}
for c in range(256):
l1 = 1
l2 = ((c + l1) << 10) ^ (c + l1)
l1 = (l2 >> 1) + l2
l3 = (((8 * l1) ^ l1) >> 5) + ((8 * l1) ^ l1)
l3 &= 0xFFFFFFFF
l4 = (((16 * l3) ^ l3) >> 17) + ((16 * l3) ^ l3)
l4 &= 0xFFFFFFFF
r = (l4 << 25) ^ l4
r &= 0xFFFFFFFF
r = (r >> 6) + r
r &= 0xFFFFFFFF
print(hex(c), hex(r))
m[r] = c

start = 0x7FF670FF3088
end = start + 0x286F

ea = start
while (ea < end):
# mov r12b, [rcx+i]
ins_len = idc.create_insn(ea)
ins = idc.generate_disasm_line(ea, 0)
if (ins == "retn"):
break
ea += ins_len
# mov r11, k
ins_len = idc.create_insn(ea)
ins = idc.generate_disasm_line(ea, 0)
k1 = idc.print_operand(ea, 1)
k1 = int("0x"+k1[:-1], 16)
ea += ins_len
# xor r11, k
ins_len = idc.create_insn(ea)
ins = idc.generate_disasm_line(ea, 0)
k2 = idc.print_operand(ea, 1)
k2 = int("0x"+k2[:-1], 16)
ea += ins_len
# ror r11, k
ins_len = idc.create_insn(ea)
ins = idc.generate_disasm_line(ea, 0)
k3 = idc.print_operand(ea, 1)
k3 = int("0x"+k3[:-1], 16)
ea += ins_len
r11 = (((k1 ^ k2) >> k3) | ((k1 ^ k2) << (64 - k3))) & 0xFFFFFFFFFFFFFFFF
# mov r13, 0/1
ins_len = idc.create_insn(ea)
ins = idc.generate_disasm_line(ea, 0)
r13 = idc.print_operand(ea, 1)
r13 = int(r13)
ea += ins_len
# ud2
ins_len = idc.create_insn(ea)
ins = idc.generate_disasm_line(ea, 0)
ea += ins_len
if (r13 == 1):
print(chr(m[r11]), end='')


2024长城杯初赛逆向
https://blog.noxke.icu/2024/03/30/ctf_wp/长城杯2024初赛逆向/
作者
noxke
发布于
2024年3月30日
许可协议