2024VCTF-WP

一.ezre

​ 1.先查一手壳,64位无壳

image-20240316231022612

​ 2.找到主函数一顿分析发现有爆红

image-20240316231540215

跟进一下,然后让ida重新分析一手

image-20240316231636804

​ 3.重新分析之后这块儿参数都没了,不知道是不是ida的问题,然后跟进sub_1248函数瞅一眼,应该是base64编码,然后最后码表从s中取

image-20240316231913899

​ 4.回头抽了一眼密文最后有个等号,确定应该就是base64,然后直接用在线平台自定义base64解出来,得到一串看不见的字符,一直怀疑是不是哪儿搞错了,然后怀着怀疑的态度,接着往上逆回去。

image-20240316232306247

​ 5.这块儿俩函数都用到了这个参数,但是跟进去看的时候瞅不见,看来得动态一下子

image-20240316232636074

image-20240316232647386

​ 6.服务起来之后随便输入几个数

image-20240316232954454

​ 7.先把这个变量取出来

image-20240316233058348

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
unsigned char ida_chars[] =
{
0x54, 0x0D, 0x08, 0x60, 0x14, 0x2C, 0x41, 0x2A, 0x30, 0x2E,
0x1C, 0x66, 0x1B, 0x47, 0x32, 0x74, 0x65, 0x05, 0x68, 0x7E,
0x23, 0x24, 0x52, 0x5C, 0x48, 0x71, 0x11, 0x21, 0x25, 0x04,
0x3E, 0x4D, 0x5B, 0x4C, 0x17, 0x29, 0x78, 0x45, 0x00, 0x3C,
0x7B, 0x6B, 0x6A, 0x5A, 0x50, 0x61, 0x19, 0x15, 0x73, 0x7D,
0x75, 0x43, 0x3D, 0x3A, 0x70, 0x16, 0x77, 0x0C, 0x67, 0x51,
0x6F, 0x03, 0x6D, 0x58, 0x4E, 0x37, 0x12, 0x2D, 0x4A, 0x1A,
0x4F, 0x5F, 0x4B, 0x7C, 0x55, 0x0F, 0x1D, 0x0E, 0x31, 0x6E,
0x79, 0x1E, 0x22, 0x36, 0x69, 0x7A, 0x28, 0x26, 0x53, 0x56,
0x0B, 0x63, 0x5E, 0x64, 0x72, 0x3B, 0x5D, 0x0A, 0x42, 0x01,
0x2F, 0x13, 0x09, 0x46, 0x3F, 0x6C, 0x7F, 0x44, 0x1F, 0x34,
0x18, 0x57, 0x20, 0x39, 0x38, 0x02, 0x76, 0x10, 0x59, 0x49,
0x07, 0x27, 0x40, 0x2B, 0x35, 0x33, 0x62, 0x06
};

​ 8.再看题,好像明了了,第一个函数应该是假的,用的假字符串,所以下一步直接看含有输入的sub_6301026996B3这个函数就行

image-20240316233355707

​ 9.一开始一直没看出来这个函数是啥加密,然后最后总结了一下,对于输入的明文进行加密的好像只有最后一步的异或,选择直接把函数dump出来,赌一把是不是对称的

image-20240316233639271

​ 10.贴出脚本,enc为秘钥,然后input数组里是最后密文经过自定义base64编码之前的密文16进制数,整个函数逻辑都是dump题中的加密过程。

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
#include <stdio.h>

unsigned int HTDWORD(unsigned long long x) {
return (unsigned int)((x >> 32) & 0xFFFFFFFF);
}

unsigned long long sub_6301026996B3(char enc[], char input[], unsigned long long inputLen) {
unsigned long long result;
char v5;
int v6 = 0;
int v7 = 0;
unsigned long long i;

for (i = 0; ; ++i) {
result = i;
if (i >= inputLen)
break;
v6 = (v6 + 1) % 128;
unsigned long long v3 = enc[v6] + v7;
v7 = (((HTDWORD(v3) >> 25) + enc[v6] + v7) & 0x7F) - (HTDWORD(v3) >> 25);
v5 = enc[v6];
enc[v6] = enc[v7];
enc[v7] = v5;
input[i] ^= enc[(enc[v6] + enc[v7]) & 0x7F];
}
return result;
}

int main() {
unsigned long long inputLen = 32;
char enc[] = {
0x54, 0x0D, 0x08, 0x60, 0x14, 0x2C, 0x41, 0x2A, 0x30, 0x2E,
0x1C, 0x66, 0x1B, 0x47, 0x32, 0x74, 0x65, 0x05, 0x68, 0x7E,
0x23, 0x24, 0x52, 0x5C, 0x48, 0x71, 0x11, 0x21, 0x25, 0x04,
0x3E, 0x4D, 0x5B, 0x4C, 0x17, 0x29, 0x78, 0x45, 0x00, 0x3C,
0x7B, 0x6B, 0x6A, 0x5A, 0x50, 0x61, 0x19, 0x15, 0x73, 0x7D,
0x75, 0x43, 0x3D, 0x3A, 0x70, 0x16, 0x77, 0x0C, 0x67, 0x51,
0x6F, 0x03, 0x6D, 0x58, 0x4E, 0x37, 0x12, 0x2D, 0x4A, 0x1A,
0x4F, 0x5F, 0x4B, 0x7C, 0x55, 0x0F, 0x1D, 0x0E, 0x31, 0x6E,
0x79, 0x1E, 0x22, 0x36, 0x69, 0x7A, 0x28, 0x26, 0x53, 0x56,
0x0B, 0x63, 0x5E, 0x64, 0x72, 0x3B, 0x5D, 0x0A, 0x42, 0x01,
0x2F, 0x13, 0x09, 0x46, 0x3F, 0x6C, 0x7F, 0x44, 0x1F, 0x34,
0x18, 0x57, 0x20, 0x39, 0x38, 0x02, 0x76, 0x10, 0x59, 0x49,
0x07, 0x27, 0x40, 0x2B, 0x35, 0x33, 0x62, 0x06
};
char input[] = {
0x0F,0x3C,0x41,0x75,0x72,0x42,0x53,0x06,0x5D,0x4C,0x32,0x1D,
0x2A,0x5C,0x49,0x26,0x22,0x4B,0x69,0x22
};

sub_56453ADC16B3(enc, input, inputLen);


for (int i = 0; i < inputLen; ++i) {
printf("%c", input[i]);
}
return 0;
}

​ 11.然后就直接出了

image-20240316234551169

二.ezvm(已复现)

​ 又是喜闻乐见的vm,elf文件有个upx壳,一开始用工具脱有报错,可能是upx标识啥的被改了,但是用010看又没找到啥问题,最后想动调一下,试试在ida里手脱,结果在ubuntu里用readelf命令查看发现是个动态链接库文件.so之类的,不太会动调这类文件,最后壳也没脱掉,诶,ctf,诶,vm。

​ 接后续复现,按着大佬的wp还有官方的wp才得以解出,该说不说这个魔改的upx确实难弄。一开始脱壳是这样的,果然没这么简单。

image-20240320213552081

​ 用010eidtor编辑器改一下该elf文件最后的4个字节就行,然后 -d 脱壳成功

image-20240320214142629

image-20240320214247631

vm的初始化函数如下:

image-20240320235001666

算是小型虚拟机吧,只有8种操作:

image-20240320235122957

vm运行起来的函数如下:(没改结构体,凑活看了只能)继续跟进到vm_step函数,此函数用于判断每一次执行指令时候取到第几个操作码

image-20240320235251951

0~7之间选取一种对应的操作指令码,然后对应到相应的指令函数中去

image-20240321000131807

思路基本清晰了,下面直接分析8个指令函数就行,然后对照指令写出模拟执行的脚本如下:

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
codes = [0xF0, 0xE0, 0x02, 0x00, 0xF0, 0xE0, 0x00, 0xE0, 0x02, 0xF0, 
0xE1, 0xE0, 0x02, 0xE0, 0x02, 0xF0, 0xE0, 0x01, 0x10, 0xF2,
0xE0, 0x00, 0xE0, 0x01, 0xF1, 0xE0, 0x00, 0x20, 0x02, 0xF0,
0xE0, 0x00, 0xE1, 0xE0, 0x00, 0xF0, 0xE0, 0x01, 0xE0, 0x02,
0xF1, 0xE0, 0x01, 0x00, 0x01, 0xF0, 0xE1, 0xE0, 0x01, 0xE0,
0x00, 0xF3, 0xE0, 0x02, 0xF6, 0xE0, 0x02, 0x00, 0x01, 0xF7,
0x04, 0xF0, 0xE0, 0x02, 0x00, 0xF0, 0xE0, 0x03, 0x00, 0xF0,
0xE0, 0x00, 0xE1, 0xE0, 0x02, 0xF1, 0xE0, 0x03, 0xE0, 0x00,
0xF0, 0xE0, 0x00, 0xE1, 0x02, 0xF1, 0xE0, 0x00, 0x00, 0x01,
0xF0, 0xE0, 0x00, 0xE1, 0xE0, 0x00, 0xF1, 0xE0, 0x03, 0xE0,
0x00, 0xF2, 0xE0, 0x03, 0x00, 0x01, 0xF0, 0xE0, 0x00, 0xE1,
0xE0, 0x02, 0xF0, 0xE0, 0x01, 0xE1, 0xE0, 0x03, 0xF0, 0xE1,
0xE0, 0x03, 0xE0, 0x00, 0xF0, 0xE1, 0xE0, 0x02, 0xE0, 0x01,
0xF3, 0xE0, 0x02, 0xF6, 0xE0, 0x02, 0x00, 0x01, 0xF7, 0x45,
0xF0, 0xE0, 0x02, 0x00, 0xF0, 0xE0, 0x03, 0x00, 0xF3, 0xE0,
0x02, 0xF2, 0xE0, 0x02, 0x00, 0x01, 0xF0, 0xE0, 0x00, 0xE1,
0xE0, 0x02, 0xF1, 0xE0, 0x03, 0xE0, 0x00, 0xF2, 0xE0, 0x03,
0x00, 0x01, 0xF0, 0xE0, 0x00, 0xE1, 0xE0, 0x02, 0xF0, 0xE0,
0x01, 0xE1, 0xE0, 0x03, 0xF0, 0xE1, 0xE0, 0x03, 0xE0, 0x00,
0xF0, 0xE1, 0xE0, 0x02, 0xE0, 0x01, 0xF1, 0xE0, 0x00, 0xE0,
0x01, 0xF2, 0xE0, 0x00, 0x00, 0x01, 0xF0, 0xE0, 0x00, 0xE1,
0xE0, 0x00, 0xF0, 0xE0, 0x01, 0xE0, 0x02, 0xF4, 0xE0, 0x01,
0xF1, 0xE0, 0x01, 0x00, 0x02, 0xF0, 0xE0, 0x01, 0xE1, 0xE0,
0x01, 0xF5, 0xE0, 0x00, 0xE0, 0x01, 0xF1, 0xE0, 0x00, 0xE0,
0x02, 0xF0, 0xE0, 0x01, 0xE0, 0x02, 0xF4, 0xE0, 0x01, 0xF1,
0xE0, 0x01, 0x00, 0x02, 0xF0, 0xE1, 0xE0, 0x01, 0xE0, 0x00,
0xF0, 0xE0, 0x01, 0xE0, 0x02, 0xF4, 0xE0, 0x01, 0xF6, 0xE0,
0x01, 0x20, 0x00, 0xF7, 0x94, 0xF8, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]


# for i in range(len(codes)):
# print(((codes[i]+0x10)),end=' ')

op=0
flag=0
while op < len(codes):
optemp = (codes[op]+0x10) & 0xff #用 & 0xff 运算符时,将整数值限制在 0 到 255 的范围内
if(optemp >=8):
break
print("0x%04x:" % (op),end=' ')
if optemp == 0: #进入fun0
step=1
while(codes[op+step]<0xf0):
step+=1
if(step==4):
print('a%d = 0x%x' % (codes[op+2],codes[op+3]))
elif(step==5):
print("a%d = a%d" % (codes[op+2],codes[op+4]))
else:
assert step == 6
if(codes[op+1]==0xE0):
if(codes[op+4]==0xE0):
print("a%d = [a%d]" % (codes[op+2],codes[op+5]))
else:
print("a%d = [0x%x]" % (codes[op+2],(codes[op+5] << 8) + codes[op+4]))
else:
assert codes[op+1] == 0xE1
print("[a%d] = a%d" % (codes[op+3],codes[op+5]))
op+=step
elif(optemp== 1): #进入fun1
if(codes[op+3]==0xE0):
print("a%d += a%d" % (codes[op+2],codes[op+4]))
else:
print("a%d += 0x%x" % (codes[op+2],(codes[op+4]<<8) + codes[op+3]))
op+=5
elif(optemp == 2): #进入fun2
if(codes[op+3]==0xE0):
print("a%d %%= a%d" % (codes[op+2],codes[op+4]))
else:
print("a%d %%= 0x%x" % (codes[op+2],(codes[op+4]<<8) + codes[op+3]))
op+=5
elif(optemp == 3): #fun3
print("a%d++" % codes[op+2])
op+=3
elif(optemp == 4): #fun4
print("a%d--"% codes[op+2])
op+=3
elif(optemp == 5): #fun5
print("a%d ^= a%d" % (codes[op+2],codes[op+4]))
op+=5
elif(optemp == 6): #fun6
if(codes[op+2] < ((codes[op+4]<<8) + codes[op+3])):
flag=1
print("flag = %d" % flag)
op+=5
elif(optemp == 7):
if(flag==1):
print("goto 0x%04x" % codes[op+1])
flag=0
op+=2
else:
print(666) #检测错误异常

运行出来的结果如下:给出了一部分提示注释,用于方便与后面的解密脚本对应

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
0x0000: a2 = 0x0
0x0004: a0 = a2
0x0009: [a2] = a2
0x000f: a1 = 0x10
0x0013: a0 %= a1
0x0018: a0 += 0x220
0x001d: a0 = [a0]
0x0023: a1 = a2
0x0028: a1 += 0x100
0x002d: [a1] = a0
0x0033: a2++
0x0036: flag = 1
0x003b: goto 0x0004
0x003d: a2 = 0x0 //(a2=i)
0x0041: a3 = 0x0 //(a3=j)
0x0045: a0 = [a2]
0x004b: a3 += a0
0x0050: a0 = a2
0x0055: a0 += 0x100
0x005a: a0 = [a0]
0x0060: a3 += a0
0x0065: a3 %= 0x100
0x006a: a0 = [a2]
0x0070: a1 = [a3]
0x0076: [a3] = a0
0x007c: [a2] = a1
0x0082: a2++
0x0085: flag = 1
0x008a: goto 0x0045
0x008c: a2 = 0x0 (a2=i)
0x0090: a3 = 0x0 (a3=j)
0x0094: a2++
0x0097: a2 %= 0x100
0x009c: a0 = [a2]
0x00a2: a3 += a0
0x00a7: a3 %= 0x100
0x00ac: a0 = [a2]
0x00b2: a1 = [a3] //交换值
0x00b8: [a3] = a0
0x00be: [a2] = a1
0x00c4: a0 += a1
0x00c9: a0 %= 0x100
0x00ce: a0 = [a0]
0x00d4: a1 = a2
0x00d9: a1--
0x00dc: a1 += 0x200 //密文的位置偏移
0x00e1: a1 = [a1]
0x00e7: a0 ^= a1
0x00ec: a0 += a2 ***此处与循环变量a2有一个累加的魔改***
0x00f1: a1 = a2
0x00f6: a1--
0x00f9: a1 += 0x200
0x00fe: [a1] = a0
0x0104: a1 = a2
0x0109: a1--
0x010c: flag = 1
0x0111: goto 0x0094

前面两部分循环分析完基本感觉就是RC4加密,然后最后打星号部分即是有一个与循环变量a2有一个累加的过程

最后改一下经典的RC4解密模板就行了,贴出脚本如下:

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
key = [0x54, 0x68, 0x69, 0x73, 0x5F, 0x31, 0x73, 0x5F, 0x66, 0x31, 
0x6C, 0x4C, 0x6C, 0x6C, 0x61, 0x67] # 秘钥 = This_1s_f1lLllag

table = [0x56, 0x54, 0xD9, 0xB5, 0xF3, 0xB1, 0xFD, 0x67, 0x15, 0xEE,
0xB0, 0x68, 0xB7, 0x2B, 0x4A, 0x64, 0x10, 0x27, 0x52, 0xDE,
0x43, 0x26, 0x0F, 0x2A, 0x41, 0x30, 0x75, 0x30, 0x98, 0x9E,
0x79, 0x5E] # 密文 = VT\x1FF36\xB5\x1FF36\xB1\xFDg\x15\x1FF36\xB0h\xB7+Jd\x10\x27R\x1FF36C&\x0F*A0u0\x98\x9Ey^

# 初始化 S 盒和密钥调度算法所需的 T 盒
S = list(range(256))
T = [key[i % len(key)] for i in range(256)]

j = 0
for i in range(256):
j = (j + S[i] + T[i]) % 256
S[i], S[j] = S[j], S[i]


j = 0
for i in range(32):
a = (i + 1) % 256
j = (j + S[a]) % 256
S[a], S[j] = S[j], S[a]
k = S[(S[a] + S[j]) % 256]
print(chr(((table[i]-a) ^ k) & 0xff),end='') #对明文字符进行异或运算还有一个减去循环变量 a 的操作

#flag{711df52879efbcb8964b6056d926ea35}