d3CTF2023

d3CTF2023

AntCTF x D^3CTF 2023 题目复现

d3sky

image-20230503015455343

32位打开,加了TLS反调试

image-20230503015530528

image-20230503015559974

利用除零异常将密钥改为YunZh1JunAlkaid

然后进行rc4加密

主函数的逻辑比较简单的虚拟机

image-20230503015802739

以v9为偏移然后读取三个字符后进行rc4加密

将加密后的数据作为操作码进行执行

然后再加密打乱Sbox

首先提取出指令码进行解密

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
arr = [0x009E, 0x0028, 0x00F5, 0x0075, 0x0073, 0x0073, 0x0030, 0x007E, 0x0048, 0x0048, 0x00F2, 0x002F, 0x003D, 0x00EC,
0x0001, 0x0026, 0x003E, 0x00CD, 0x0082, 0x00AD, 0x00B1, 0x00D1, 0x0036, 0x00D2, 0x00B4, 0x00E5, 0x00E8, 0x004C,
0x003D, 0x000C, 0x0073, 0x00FD, 0x0059, 0x00A7, 0x0048, 0x0093, 0x00FD, 0x0006, 0x00E0, 0x0044, 0x0048, 0x0071,
0x0094, 0x004A, 0x008E, 0x00A4, 0x0036, 0x0091, 0x0023, 0x00EE, 0x0068, 0x00C1, 0x005D, 0x000B, 0x004D, 0x001A,
0x0074, 0x0083, 0x0051, 0x0052, 0x00EE, 0x00FE, 0x0011, 0x00A2, 0x00A1, 0x0064, 0x00BD, 0x0098, 0x004D, 0x00B9,
0x0097, 0x0045, 0x00E6, 0x00F7]

box = [0] * 256

op = [...]


def rc4_intit():
k = "YunZh1JunAlkaid"
key = []
for i in k:
key.append(ord(i))

for i in range(256):
box[i] = i

v6 = 0
for i in range(256):
v8 = box[i]
v6 = (v6 + v8 + key[i % len(key)]) & 0xff
box[i] = box[v6]
box[v6] = v8

v5 = 0
v6 = 0
for i in range(len(arr)):
v5 = (v5 + 1) % 256
v7 = box[v5]
v6 = (v6 + v7) % 256
v8 = box[v6]
box[v5] = v8
box[v6] = v7
arr[i] ^= (box[(v7 + v8) % 256])


def rc4(st, end):
v5 = 0
v6 = 0
for i in range(st, end):
v5 = (v5 + 1) % 256
v7 = box[v5]
v6 = (v6 + v7) % 256
v8 = box[v6]
box[v5] = v8
box[v6] = v7
op[i] ^= (box[(v7 + v8) % 256])


if __name__ == '__main__':
rc4_intit()
for i in range(0, len(op)-3, 3):
rc4(i, i + 3)
print(hex(op[i]), hex(op[i + 1]), hex(op[i + 2]))
if op[i + 2] == 0x13:
print("\n判断:")

rc4(i, i + 3)

看出指令码非常的有规律

image-20230503020112776

虽然这个虚拟机只有与非运算,但是众所周知,与非门是一种通用的逻辑门,因为任何布尔运算都可以用与非来实现

A ^ B = !A * B + A * !B = !(AA) * B + A * !(B*B) = !( !( !(AA) * B ) * !( A * !(B*B) ) )

然后动调获取密文

写脚本

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

enc = [0x24, 0x0B, 0x6D, 0x0F, 0x03, 0x32, 0x42, 0x1D,
0x2B, 0x43, 0x78, 0x43, 0x73, 0x30, 0x2B, 0x4E,
0x63, 0x48, 0x77, 0x2E, 0x32, 0x39, 0x1A, 0x12,
0x71, 0x7A, 0x42, 0x17, 0x45, 0x72, 0x56, 0x0C,
0x5C, 0x4A, 0x62, 0x53, 0x33]

s = Solver()
x = [BitVec('x[%d]' % i, 8) for i in range(37)]

for i in range(37):
s.add(enc[i] == x[i % 37] ^ x[(i + 1) % 37] ^ x[(i + 2) % 37] ^ x[(i + 3) % 37])
s.add(x[36] == 0x7e)

if s.check() == sat:
result = s.model()
for each in x:
print(chr(result[each].as_long()), end="")
else:
print("no")

flag{A_Sin91e_InS7rUcti0N_ViRTua1_M4chin3~}

d3rc4

64位

image-20230505141636756

只是rc4加密,直接动调提取异或的keystream

然而解密后是假flag

1
2
3
4
5
6
7
8
9
10
11
12
13
fake = [0xDB, 0xB6, 0x2A, 0x04, 0xC7, 0xB9, 0x68, 0xE0, 0xBD, 0x3E, 0x04, 0x6F, 0xD3, 0x38, 0x10, 0x6B, 0x4A, 0xE5,
0x61, 0xA7, 0x24, 0x0D, 0xFC, 0x73, 0xAA, 0x71, 0xF8, 0x10, 0x0D, 0x7D, 0x55, 0x6E, 0x43]
enc = [0xb8, 0x86, 0x44, 0x63, 0xb5, 0xd8, 0x1c, 0x95,
0xd1, 0x7e, 0x70, 0x5e, 0xbc, 0x56, 0x63, 0x34,
0x28, 0x90, 0x15, 0xf8, 0x4d, 0x52, 0x9d, 0x1e,
0xf5, 0x1f, 0xc8, 0x64, 0x52, 0x1b, 0x64, 0x0f,
0x24, 0x93, 0x40, 0x5d, 0x84, 0x93, 0x7d, 0x92,
0x0a]

for i in range(len(fake)):
x = fake[i] ^ enc[i]
print(chr(x), end="")

c0ngratul@t1ons_but_i_am_n0t_f1ag

调试以及查看函数表可知

在main函数之前有init函数,负责对key以及输出的初始化

image-20230505142246727

在main函数之后,还有fini对输入做进一步的校验

image-20230505142357182

image-20230505142422977

大概分析了一下他fork调用的流程

总的来说,利用管道对key进行修改。打开一个子进程然后修改key

子进程又打开子进程。

因为有wait函数,当前进程会等待其子进程结束后再继续运行

利用gdb进行调试

首先在fork出下断

image-20230505142947162

这里会进行创建子进程,因此如果想要跟踪子进程,就得将gdb的跟踪模式从主进程切换到子进程

1
set follow-fork-mode child

切换到子进程后在校验处下断

image-20230505143227640

对应ida里面的

image-20230505143300500

image-20230505143338667

提取rdx里面的值,写脚本

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
flag = [0xF7, 0x5F, 0xE7, 0xB0, 0x9A, 0xB4, 0xE0, 0xE7, 0x9E, 0x05, 0xFE, 0xD8, 0x35, 0x5C, 0x72, 0xE0, 0x86, 0xDE,
0x73, 0x9F, 0x9A, 0xF6, 0x0D, 0xDC, 0xC8, 0x4F, 0xC2, 0xA4, 0x7A, 0xB5, 0xE3, 0xCD, 0x60, 0x9D, 0x04, 0x1F]

enc = [0xb8, 0x86, 0x44, 0x63, 0xb5, 0xd8, 0x1c, 0x95,
0xd1, 0x7e, 0x70, 0x5e, 0xbc, 0x56, 0x63, 0x34,
0x28, 0x90, 0x15, 0xf8, 0x4d, 0x52, 0x9d, 0x1e,
0xf5, 0x1f, 0xc8, 0x64, 0x52, 0x1b, 0x64, 0x0f,
0x24, 0x93, 0x40, 0x5d, 0x84, 0x93, 0x7d, 0x92,
0x0a]

ks = [0x35, 0x4b, 0xa0, 0x60, 0x08, 0x50, 0xa5, 0xf1,
0x33, 0x97, 0xb2, 0x13, 0xcb, 0x4c, 0x0d, 0xcf,
0xa3, 0x7c, 0x57, 0x53, 0xe2, 0xa9, 0x65, 0x4e,
0x0e, 0xc7, 0x7a, 0x0f, 0xfd, 0xb5, 0x9e, 0xb4,
0x33, 0xf9, 0x61, 0xd3]

for i in range(0, len(flag), 2):
flag[i + 1] = flag[i] - (flag[i + 1] ^ ks[i + 1])
flag[i] = (flag[i] ^ ks[i]) - flag[i + 1]

for i in range(len(flag)):
flag[i] ^= enc[i]

print()
for i in flag:
print(chr(i & 0xff), end="")

getting_primes_with_pipes_is_awesome

d3recover

附件是两个文件ver1无符号而ver2有符号

利用bindiff恢复符号

找到可疑的函数

image-20230511211205029

看起来很复杂但是很多都是对函数异常时的处理,只要关注其调用的python函数即可

image-20230511211306925

简单化简一下逻辑

1
2
3
4
5
6
7
8
9
for(int i = 0; i < 32; i++) {
input[i] ^= 0x23;
}

for (int i = 0; i <= 29; i++) {
int x1 = input[i];
int x2 = input[i+2];
input[i] = (x1 + x2) ^ 0x54;
}

然后就是猜了,看了别的师傅的wp,可以通过调试来调试出最后做比较的字符

但是我找不到。

留意到有一串疑似经过base64加密的字符串

image-20230511222518281

长度也对的上,将其base64解码后再解密

image-20230511222556078

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

int input[32];

int flag[] = {
0xd3,0xc7,0xce,0xca,0x3f,0x84,0xdb,0xb3,0xb6,0xb9,0x80,0xea,0xd0,0xcd,0x72,0xfc,
0xd8,0x30,0x95,0xdb,0xe2,0xd8,0x92,0x08,0xc1,0xc6,0xc5,0xf4,0x07,0xec,0x02,0x5e
};

int main()
{
for (int i = 0; i <= 29; i++) {
flag[i] ^= 0x54;
}

for (int i = 29; i >= 0; i--) {
flag[i] -= flag[i+2];
}

for (int i = 0; i < 32; i++) {
printf("%c", flag[i]^0x23);
}
return 0;
}

flag{y0U_RE_Ma5t3r_0f_R3vocery!}

d3syscall

ida64

image-20230506000957959

点开函数,发现全是自己定义的Linux系统调用

image-20230506001045316

找到注册的函数下断,然后将生成的my_module文件丢进ida

image-20230506002136378

my_module中定义了每个系统调用号对应的函数,发现是个虚拟机

image-20230506002253557

最后写脚本

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

flag = [0xB0800699CB89CC89, 0x4764FD523FA00B19,
0x396A7E6DF099D700, 0xB115D56BCDEAF50A,
0x2521513C985791F4, 0xB03C06AF93AD0BE]

for i in range(0, 6, 2):
s = Solver()
reg = [BitVec('reg[%d]' % i, 64) for i in range(2)]

ans1 = reg[1] + (((reg[0] << 3) + 0x51e7647e) ^ ((reg[0] * 3) + 0x0E0B4140A) ^ (reg[0] + 0x0E6978F27))
ans2 = reg[0] + (((ans1 << 6) + 0x53A35337) ^ ((ans1 * 5) + 0x9840294D) ^ (ans1 - 0x5EAE4751))

s.add(flag[i] == ans1 & 0xffffffffffffffff)
s.add(flag[i + 1] == ans2 & 0xffffffffffffffff)

if s.check() == z3.sat:
result = s.model()
for each in reg:
print(hex(result[each].as_long()), end=", ")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>

using namespace std;

int main()
{
unsigned long long flag[] = {
0x65637b6674633364, 0x322d343939623966, 0x343438342d373435, 0x30612d643063612d, 0x3630383537623739, 0x7d3061};
for(int i = 0; i < 6; i++) {
unsigned long long x = flag[i];
for(int j = 0; j < 8; j++) {
printf("%c", x&0xff);
x >>= 8;
}
}
return 0;
}

d3ctf{cef9b994-2547-4844-ac0d-a097b75806a0}

d3hell

To be continue……