2018 HCTF 线上赛

HCTF线上赛 3道RE的writeup

这是我在De1ta打得第一场比赛。结束前我做出了Lucky star和seven两道题,结束后十分钟做出了polish duck。spiral是好几天后才问了pizza做出来的。我还是太菜了,比赛又背锅,要努力学习!

比赛时间:2018年11月9日 20:00——2018年11月11日 20:00

题目,脚本及ida数据库下载:下载地址

Lucky star

找main函数,发现是smc过的,找找其他地方 0040155D和004015CA 调用了了一个反调试函数,把参数0x11改成0 00403148有一段进程名的字符串,全改成0 0040251b调用了另一个反调试函数,把下方的00402521的jz改成jmp 全部改完打个补丁,程序就能正常运行了。 在调用main函数的地方00401C20设下断点,开始调试,断下的时候main函数就解析好了 先播放了一段音频,中间还有sleep,大概一分钟,不相等可以直接把那一段的cmp jz改成cmp jmp

004015E0是加密函数 加密算法如下: 先把输入进行base64加密 a2位base64加密完的 它的base64大小写是反的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
v14=0
v19 = strlen(a2);
if ( v19 > 0 )
{
do
{
v16 = 6;
do
{
v17 = rand() % 4;
v18 = v16;
v16 -= 2;
result = (_BYTE)v17 << v18;
a2[v14] ^= result;
}
while ( v16 > -2 );
++v14;
}
while ( v14 < v19 );
}

可以看到,他对每一个字符异或了四个随机数(先对随机数进行了一些操作),在异或的地方设下条件记录断点,记录下异或的数据,然后反向解密就行了。

解密:

1
2
3
4
5
6
secret=[0x49,0xE6,0x57,0xBD,0x3A,0x47,0x11,0x4C,0x95,0xBC,0xEE,0x32,0x72,0xA0,0xF0,0xDE,0xAC,0xF2,0x83,0x56,0x83,0x49,0x6E,0xA9,0xA6,0xC5,0x67,0x3C,0xCA,0xC8,0xCC,0x05]
xor=[0,0,8,0,128,0,0,1,0,48,8,1,128,0,12,1,64,0,0,0,0,0,8,1,64,0,0,2,0,16,4,0,192,16,0,0,192,48,0,2,128,16,8,0,64,32,4,2,0,48,0,3,192,16,4,2,192,0,8,1,128,48,0,2,192,0,0,1,128,16,4,1,128,48,4,2,0,16,12,2,192,0,4,3,0,32,12,1,0,16,12,0,192,32,12,3,192,16,0,2,128,48,0,2,64,16,12,3,64,32,4,2,128,0,12,0,128,48,8,1,192,48,0,1,0,48,8,0,0,16,4,0,0,0,8,0,128,0,12,3,192,0,12,2,192,32,8,1,64,48,12,3,0,0,12,1,0,0,4,1]
a=''
for i in range(len(secret)):
a+=chr(secret[i]^xor[4*i]^xor[4*i+1]^xor[4*i+2]^xor[4*i+3])
print(a)

解密后: Agn0zNSXENvTAv9lmg5HDdrFtw8ZFq== 它的base64加密大小写是反的,手动换一下= = aGN0ZnsxenVtaV9LMG5hdDRfTW8zfQ==

hctf{1zumi_K0nat4_Mo3}

这里不知道为什么少了},可能是base64的问题?

seven

驱动逆向 sub_140001000中kbdclass应该跟键盘有关,但不知道怎么输入的 sub_1400012F0应该是解密函数了 中间有一段代码:

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
if ( *v6 == 0x11 )                        // up
{
if ( v5 & 0xFFFFFFF0 )
{
v5 -= 0x10;
goto LABEL_13;
}
v5 += 0xD0;
dword_1400030E4 = v5;
}
if ( v8 != 0x1F ) // down
goto LABEL_14;
if ( (v5 & 0xFFFFFFF0) == 0xD0 )
v5 -= 0xD0;
else
v5 += 0x10;
LABEL_13:
dword_1400030E4 = v5;
LABEL_14:
if ( v8 == 0x1E ) // left
{
if ( v5 & 0xF )
--v5;
else
v5 += 0xF;
dword_1400030E4 = v5;
}
if ( v8 == 0x20 ) // right
{
if ( (v5 & 0xF) == 15 )
v5 -= 15;
else
++v5;
dword_1400030E4 = v5;
}
v9 = aO[v5];
if ( v9 == '*' )
{
v10 = "-1s\n";
}
else
{
if ( v9 != '7' )
{
LABEL_29:
aO[v5] = 'o';
goto LABEL_30;
}
v10 = "The input is the flag!\n";
}

aO为字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
****************
o..............*
**************.*
************...*
***********..***
**********..****
*********..*****
********..******
*******..*******
******..********
*****..*********
****..**********
****7***********

要把这个o走到7的位置 0x11 0x1F 0x1E 0x20是硬件扫描码 wsad,对应写出移动的顺序就行了

flag:hctf{ddddddddddddddssaasasasasasasasasas}

PolishDuck

找到了字符串 Arduino leonardo,是单片机的型号。

做法是瞎猜的,没想到能做出来= = 这是个hex文件,首先不知道它机器的芯片,先用ida直接打开,能看到解析后的内容,也可以用相关工具解析成bin再用ida打开。发现0180地址出有一串字符串Arduino LLC Arduino Leonardo 这是它机器的型号,查找资料知道是avr架构的ATmega32u ida打开的时候,processer type 选Atmel AVR,打开后选ATmega32_L,可以反汇编了。 在0D40地址看到

1
notepad.exe 44646 + ( 64094 + (71825 * ( ( 15873 + ( 21793 * ( 7234 + ( 17649 * ( ( 2155 + ( 74767 * ( 35392 + ( 88216 * ( 83920 + ( 16270 + ( 20151 * ( 5268 + ( 90693 * ( 82773 + ( 716 + 27377 * ( 44329 + ( 49366 * ( ( ( 38790 + ( 70247 * ( 97233 + ( 18347 + ( 22117 * ( ( ( 72576 + ( ( 47541 + ( 46975 + ( 53769 * ( 94005 + ( ( 72914 + ( 5137 + ( 87544 * 71583 + ( 20370 + ( 37968 * ( 17478 + ( ( 40532 + ( 10089 + ( 13332 * ( ( 24170 + ( 46845 * ( 16048 + 23142 * ( 31895 + ( 62386 * ( 12179 ( 94552 + ( ( ( 52918 + ( 91580 + ( ( ( 38412 + ( 91537 * ( 70 + ( 98594 * ( ( 35275 + ( 62912 * ( 4755 + ( 16737 * ( 27595 + ( ( 43551 + ( 64482 * 3550 ) ) - 21031 ) ) ) ) ) ) - 57553 ) ) - 89883 ) - 38900 ) ) ) - 19517 ) - 79082 ) ) ) ) ) ) ) ) - 70643 ) ) 55350 ) ) ) ) ) - 40301 ) ) ) ) - 83065 ) ) ) ) ) - 52460 ) ) - 49428 ) - 94686 ) ) ) ) ) ) - 1653 ) - 65217 ) ) ) - 43827 ) 66562 ) )

不是一个完整的式子,不能计算,想起他方向。 这不是一个 完整的字符串,而是很多个字符串(中间有),大致猜一下可能是选取其中某些字符串拼接起来,就能计算了 从开头开始跟踪,跟踪到sub_9A8,应该是解析相关的函数了。 往下找,找到可疑的地方0A6C开始:

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
ROM:0A6C                 ldi     r22, 0xF4
ROM:0A6D ldi r23, 1
ROM:0A6E ldi r24, 0
ROM:0A6F ldi r25, 0
ROM:0A70 call sub_8B6
ROM:0A72 ldi r24, 0x40 ; '@'
ROM:0A73 ldi r25, 1
ROM:0A74 call sub_88D
ROM:0A76 ldi r22, 0xF4
ROM:0A77 ldi r23, 1
ROM:0A78 ldi r24, 0
ROM:0A79 ldi r25, 0
ROM:0A7A call sub_8B6
ROM:0A7C ldi r24, 0x4C ; 'L'
ROM:0A7D ldi r25, 1
ROM:0A7E call sub_88D
ROM:0A80 ldi r22, 0xF4
ROM:0A81 ldi r23, 1
ROM:0A82 ldi r24, 0
ROM:0A83 ldi r25, 0
ROM:0A84 call sub_8B6
ROM:0A86 ldi r24, 0x53 ; 'S'
ROM:0A87 ldi r25, 1
ROM:0A88 call sub_88D
ROM:0A8A ldi r22, 0xF4
ROM:0A8B ldi r23, 1
ROM:0A8C ldi r24, 0
ROM:0A8D ldi r25, 0
ROM:0A8E call sub_8B6
ROM:0A90 ldi r24, 0x62 ; 'b'
ROM:0A91 ldi r25, 1
ROM:0A92 call sub_88D

有很多结构十分相似的部分,把不一样的地方都提取出来,是一堆地址

1
140  14C  153  162  177  18B  1A9  1C8  1D3  1EB  1FE  25E  207 21C 227 246 261 270 28B 298 2A3 2B1 25C 2BA 2C5 2D0 2D7 2F2 307 310 25E 327 346 3DC 34D 364 373 38F 3A6 3B3 3BF 3D0 3DF 3EF 400 44B 413 42C 43B 44F 452 490 45F 46C 47D 48E 497 49E 4B5 4CB 445 445 4D6 44D 44D 494 4E5 44F

这些地址并不指向任何东西,但如过把140对应之前字符串的开头notepad.exe,14C刚好能对应第二个字符串开头44646,以此类推,而某些地址可能指向的不是某一字符串的开头,这样猜测整合完应该能得到完整算术式 试着把他们一一对应过去

1
2
3
4
5
6
7
8
9
unsigned char secret[] = { 0x6E, 0x6F, 0x74, 0x65, 0x70, 0x61, 0x64, 0x2E, 0x65, 0x78,0x65, 0x00, 0x34, 0x34, 0x36, 0x34, 0x36, 0x20, 0x00, 0x2B,0x20, 0x28, 0x20, 0x36, 0x34, 0x30, 0x39, 0x34, 0x20, 0x2B,0x20, 0x28, 0x20, 0x00, 0x37, 0x31, 0x38, 0x32, 0x35, 0x20,0x2A, 0x20, 0x28, 0x20, 0x28, 0x20, 0x31, 0x35, 0x38, 0x37,0x33, 0x20, 0x2B, 0x20, 0x00, 0x28, 0x20, 0x32, 0x31, 0x37,0x39, 0x33, 0x20, 0x2A, 0x20, 0x28, 0x20, 0x37, 0x32, 0x33,0x34, 0x20, 0x2B, 0x20, 0x00, 0x28, 0x20, 0x31, 0x37, 0x36,0x34, 0x39, 0x20, 0x2A, 0x20, 0x28, 0x20, 0x28, 0x20, 0x32,0x31, 0x35, 0x35, 0x20, 0x2B, 0x20, 0x28, 0x20, 0x37, 0x34,0x37, 0x36, 0x37, 0x20, 0x00, 0x2A, 0x20, 0x28, 0x20, 0x33,0x35, 0x33, 0x39, 0x32, 0x20, 0x2B, 0x20, 0x28, 0x20, 0x38,0x38, 0x32, 0x31, 0x36, 0x20, 0x2A, 0x20, 0x28, 0x20, 0x38,0x33, 0x39, 0x32, 0x30, 0x20, 0x00, 0x2B, 0x20, 0x28, 0x20,0x31, 0x36, 0x32, 0x37, 0x30, 0x20, 0x00, 0x2B, 0x20, 0x28,0x20, 0x32, 0x30, 0x31, 0x35, 0x31, 0x20, 0x2A, 0x20, 0x28,0x20, 0x35, 0x32, 0x36, 0x38, 0x20, 0x2B, 0x20, 0x28, 0x20,0x00, 0x39, 0x30, 0x36, 0x39, 0x33, 0x20, 0x2A, 0x20, 0x28,0x20, 0x38, 0x32, 0x37, 0x37, 0x33, 0x20, 0x2B, 0x20, 0x00,0x28, 0x20, 0x37, 0x31, 0x36, 0x20, 0x2B, 0x20, 0x00, 0x32,0x37, 0x33, 0x37, 0x37, 0x20, 0x2A, 0x20, 0x28, 0x20, 0x34,0x34, 0x33, 0x32, 0x39, 0x20, 0x2B, 0x20, 0x28, 0x20, 0x00,0x34, 0x39, 0x33, 0x36, 0x36, 0x20, 0x2A, 0x20, 0x28, 0x20,0x00, 0x28, 0x20, 0x28, 0x20, 0x33, 0x38, 0x37, 0x39, 0x30,0x20, 0x2B, 0x20, 0x28, 0x20, 0x37, 0x30, 0x32, 0x34, 0x37,0x20, 0x2A, 0x20, 0x28, 0x20, 0x39, 0x37, 0x32, 0x33, 0x33,0x20, 0x00, 0x2B, 0x20, 0x28, 0x20, 0x31, 0x38, 0x33, 0x34,0x37, 0x20, 0x2B, 0x20, 0x28, 0x20, 0x32, 0x32, 0x31, 0x31,0x37, 0x20, 0x2A, 0x20, 0x28, 0x20, 0x28, 0x20, 0x00, 0x28,0x20, 0x37, 0x32, 0x35, 0x37, 0x36, 0x20, 0x2B, 0x20, 0x28,0x20, 0x28, 0x20, 0x00, 0x34, 0x37, 0x35, 0x34, 0x31, 0x20,0x2B, 0x20, 0x28, 0x20, 0x34, 0x36, 0x39, 0x37, 0x35, 0x20,0x2B, 0x20, 0x28, 0x20, 0x35, 0x33, 0x37, 0x36, 0x39, 0x20,0x00, 0x2A, 0x20, 0x28, 0x20, 0x39, 0x34, 0x30, 0x30, 0x35,0x20, 0x2B, 0x20, 0x00, 0x28, 0x20, 0x28, 0x20, 0x37, 0x32,0x39, 0x31, 0x34, 0x20, 0x00, 0x2B, 0x20, 0x28, 0x20, 0x35,0x31, 0x33, 0x37, 0x20, 0x2B, 0x20, 0x28, 0x20, 0x00, 0x38,0x37, 0x35, 0x34, 0x34, 0x20, 0x2A, 0x20, 0x00, 0x37, 0x31,0x35, 0x38, 0x33, 0x20, 0x2B, 0x20, 0x28, 0x20, 0x00, 0x32,0x30, 0x33, 0x37, 0x30, 0x20, 0x2B, 0x20, 0x28, 0x20, 0x00,0x33, 0x37, 0x39, 0x36, 0x38, 0x20, 0x00, 0x2A, 0x20, 0x28,0x20, 0x31, 0x37, 0x34, 0x37, 0x38, 0x20, 0x2B, 0x20, 0x28,0x20, 0x28, 0x20, 0x34, 0x30, 0x35, 0x33, 0x32, 0x20, 0x2B,0x20, 0x28, 0x20, 0x00, 0x31, 0x30, 0x30, 0x38, 0x39, 0x20,0x2B, 0x20, 0x28, 0x20, 0x31, 0x33, 0x33, 0x33, 0x32, 0x20,0x2A, 0x20, 0x28, 0x20, 0x00, 0x28, 0x20, 0x32, 0x34, 0x31,0x37, 0x30, 0x20, 0x00, 0x2B, 0x20, 0x28, 0x20, 0x34, 0x36,0x38, 0x34, 0x35, 0x20, 0x2A, 0x20, 0x28, 0x20, 0x31, 0x36,0x30, 0x34, 0x38, 0x20, 0x2B, 0x20, 0x00, 0x32, 0x33, 0x31,0x34, 0x32, 0x20, 0x2A, 0x20, 0x28, 0x20, 0x33, 0x31, 0x38,0x39, 0x35, 0x20, 0x2B, 0x20, 0x28, 0x20, 0x36, 0x32, 0x33,0x38, 0x36, 0x20, 0x2A, 0x20, 0x28, 0x20, 0x00, 0x31, 0x32,0x31, 0x37, 0x39, 0x20, 0x00, 0x28, 0x20, 0x39, 0x34, 0x35,0x35, 0x32, 0x20, 0x2B, 0x20, 0x28, 0x20, 0x28, 0x20, 0x28,0x20, 0x35, 0x32, 0x39, 0x31, 0x38, 0x20, 0x00, 0x2B, 0x20,0x28, 0x20, 0x39, 0x31, 0x35, 0x38, 0x30, 0x20, 0x2B, 0x20,0x28, 0x20, 0x00, 0x28, 0x20, 0x28, 0x20, 0x33, 0x38, 0x34,0x31, 0x32, 0x20, 0x2B, 0x20, 0x28, 0x20, 0x39, 0x31, 0x35,0x33, 0x37, 0x20, 0x2A, 0x20, 0x28, 0x20, 0x37, 0x30, 0x20,0x00, 0x2B, 0x20, 0x28, 0x20, 0x39, 0x38, 0x35, 0x39, 0x34,0x20, 0x2A, 0x20, 0x28, 0x20, 0x28, 0x20, 0x33, 0x35, 0x32,0x37, 0x35, 0x20, 0x00, 0x2B, 0x20, 0x28, 0x20, 0x36, 0x32,0x39, 0x31, 0x32, 0x20, 0x2A, 0x20, 0x00, 0x28, 0x20, 0x34,0x37, 0x35, 0x35, 0x20, 0x2B, 0x20, 0x28, 0x20, 0x00, 0x31,0x36, 0x37, 0x33, 0x37, 0x20, 0x2A, 0x20, 0x28, 0x20, 0x32,0x37, 0x35, 0x39, 0x35, 0x20, 0x00, 0x2B, 0x20, 0x28, 0x20,0x28, 0x20, 0x34, 0x33, 0x35, 0x35, 0x31, 0x20, 0x2B, 0x20,0x00, 0x28, 0x20, 0x36, 0x34, 0x34, 0x38, 0x32, 0x20, 0x2A,0x20, 0x33, 0x35, 0x35, 0x30, 0x20, 0x00, 0x29, 0x20, 0x29,0x20, 0x2D, 0x20, 0x32, 0x31, 0x30, 0x33, 0x31, 0x20, 0x29,0x20, 0x29, 0x20, 0x00, 0x29, 0x20, 0x29, 0x20, 0x29, 0x20,0x29, 0x20, 0x2D, 0x20, 0x35, 0x37, 0x35, 0x35, 0x33, 0x20,0x29, 0x20, 0x00, 0x29, 0x20, 0x2D, 0x20, 0x38, 0x39, 0x38,0x38, 0x33, 0x20, 0x29, 0x20, 0x2D, 0x20, 0x33, 0x38, 0x39,0x30, 0x30, 0x20, 0x29, 0x20, 0x29, 0x20, 0x00, 0x29, 0x20,0x2D, 0x20, 0x31, 0x39, 0x35, 0x31, 0x37, 0x20, 0x29, 0x20,0x2D, 0x20, 0x00, 0x37, 0x39, 0x30, 0x38, 0x32, 0x20, 0x29,0x20, 0x29, 0x20, 0x29, 0x20, 0x29, 0x20, 0x29, 0x20, 0x29,0x20, 0x29, 0x20, 0x29, 0x20, 0x00, 0x2D, 0x20, 0x37, 0x30,0x36, 0x34, 0x33, 0x20, 0x29, 0x20, 0x29, 0x20, 0x00, 0x35,0x35, 0x33, 0x35, 0x30, 0x20, 0x29, 0x20, 0x29, 0x20, 0x29,0x20, 0x00, 0x29, 0x20, 0x29, 0x20, 0x2D, 0x20, 0x34, 0x30,0x33, 0x30, 0x31, 0x20, 0x29, 0x20, 0x29, 0x20, 0x00, 0x29,0x20, 0x29, 0x20, 0x2D, 0x20, 0x38, 0x33, 0x30, 0x36, 0x35,0x20, 0x29, 0x20, 0x29, 0x20, 0x00, 0x29, 0x20, 0x29, 0x20,0x29, 0x20, 0x2D, 0x20, 0x00, 0x35, 0x32, 0x34, 0x36, 0x30,0x20, 0x00, 0x29, 0x20, 0x29, 0x20, 0x2D, 0x20, 0x34, 0x39,0x34, 0x32, 0x38, 0x20, 0x29, 0x20, 0x2D, 0x20, 0x39, 0x34,0x36, 0x38, 0x36, 0x20, 0x00, 0x29, 0x20, 0x29, 0x20, 0x29,0x20, 0x29, 0x20, 0x29, 0x20, 0x29, 0x20, 0x2D, 0x20, 0x31,0x36, 0x35, 0x33, 0x20, 0x29, 0x20, 0x00, 0x2D, 0x20, 0x36,0x35, 0x32, 0x31, 0x37, 0x20, 0x29, 0x20, 0x00, 0x29, 0x20,0x29, 0x20, 0x2D, 0x20, 0x34, 0x33, 0x38, 0x32, 0x37, 0x20,0x29, 0x20, 0x00, 0x36, 0x36, 0x35, 0x36, 0x32, 0x20, 0x29,0x20, 0x29, 0x20, 0x00 };
unsigned char *p;
int address[] = { 0x140 ,0x14C ,0x153 ,0x162 ,0x177 ,0x18B ,0x1A9 ,0x1C8 ,0x1D3 ,0x1EB ,0x1FE ,0x25E ,0x207,0x21C,0x227,0x246,0x261,0x270,0x28B,0x298,0x2A3,0x2B1,0x25C,0x2BA,0x2C5,0x2D0,0x2D7,0x2F2,0x307,0x310,0x25E,0x327,0x346,0x3DC,0x34D,0x364,0x373,0x38F,0x3A6,0x3B3,0x3BF,0x3D0,0x3DF,0x3EF,0x400,0x44B,0x413,0x42C,0x43B,0x44F,0x452,0x490,0x45F,0x46C,0x47D,0x48E,0x497,0x49E,0x4B5,0x4CB,0x445,0x445,0x4D6,0x44D,0x44D,0x494,0x4E5,0x44F };
int i;
for (i = 0; i < 68; i++)
{
p = &secret[0] + address[i]-0x140;
cout << p;
}

得到完整的算术表达式,用python算出flag

1
2
3
a=44646 + ( 64094 + ( 71825 * ( ( 15873 + ( 21793 * ( 7234 + ( 17649 * ( ( 2155 + ( 74767 * ( 35392 + ( 88216 * ( 83920 + ( 16270 + ( 20151 * ( 5268 + ( 90693 * ( 82773 + ( 716 + ( 27377 * ( 44329 + ( 49366 * ( ( ( 38790 + ( 70247 * ( 97233 + ( 18347 + ( 22117 * ( ( ( 72576 + ( ( 47541 + ( 46975 + ( 53769 * ( 94005 + ( ( 72914 + ( 5137 + ( 87544 * ( ( 71583 + ( 20370 + ( 37968 * ( 17478 + ( ( 40532 + ( 10089 + ( 13332 * ( ( 24170 + ( 46845 * ( 16048 + ( 23142 * ( 31895 + ( 62386 * ( 12179 + ( 94552 + ( ( ( 52918 + ( 91580 + ( ( ( 38412 + ( 91537 * ( 70 + ( 98594 * ( ( 35275 + ( 62912 * ( 4755 + ( 16737 * ( 27595 + ( ( 43551 + ( 64482 * 3550 ) ) - 21031 ) ) ) ) ) ) - 57553 ) ) ) ) ) - 89883 ) - 38900 ) ) ) - 19517 ) - 79082 ) ) ) ) ) ) ) ) ) - 70643 ) ) ) ) - 55350 ) ) ) ) ) - 40301 ) ) ) ) - 83065 ) ) ) ) ) - 52460 ) ) - 49428 ) - 94686 ) ) ) ) ) ) - 1653 ) - 65217 ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) - 43827 ) ) ) ) ) - 66562 ) ) ) 
s=hex(a)[2:-1].decode('hex')
print(s)

flag:hctf{P0l1sh_Duck_Tast3s_D3l1ci0us_D0_U_Th1nk?}

Spiral

part A

IDA打开,定位到main函数。flag由命令行参数传入。sub_122AD87把flag的格式去掉,剩下的部分长度为73。

sub_1229E1E把flag分成前46和后27两部分。

sub_122F430校验前46,算法比较简单。取每一个字符的低3bit和除符号位的高4bit,根据低3bit对高4bit加/异或,然后把低3bit和操作后的高4bit跟常量对比。低3bit已经给出了,就很好算了,直接求解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#flag1
const=[0x07,0xE7,0x07,0xE4,0x01,0x19,0x03,0x50,0x07,0xE4,0x01,0x20,0x06,0xB7,0x07,0xE4,0x01,0x22,0x00,0x28,0x00,0x2A,0x02,0x54,0x07,0xE4,0x01,0x1F,0x02,0x50,0x05,0xF2,0x04,0xCC,0x07,0xE4,0x00,0x28,0x06,0xB3,0x05,0xF8,0x07,0xE4,0x00,0x28,0x06,0xB2,0x07,0xE4,0x04,0xC0,0x00,0x2F,0x05,0xF8,0x07,0xE4,0x04,0xC0,0x00,0x28,0x05,0xF0,0x07,0xE3,0x00,0x2B,0x04,0xC4,0x05,0xF6,0x03,0x4C,0x04,0xC0,0x07,0xE4,0x05,0xF6,0x06,0xB3,0x01,0x19,0x07,0xE3,0x05,0xF7,0x01,0x1F,0x07,0xE4]
flag1=''
for i in range(46):
if const[2*i]==0:
const[2*i+1]-=0x22
elif const[2*i]==1:
const[2*i+1]-=0x13
elif const[2*i]==2:
const[2*i+1]-=0x46
elif const[2*i]==3:
const[2*i+1]-=0x42
elif const[2*i]==4:
const[2*i+1]^=0xCA
elif const[2*i]==5:
const[2*i+1]^=0xFE
elif const[2*i]==6:
const[2*i+1]^=0xBE
elif const[2*i]==7:
const[2*i+1]^=0xEF
flag1+=chr(const[2*i+1]<<3|const[2*i])
print(flag1)

G_1s_iN_y0@r_aRe4_0n5_0f_Th5_T0ugHtEST_En1gMa_

part B

sub_12283E8在当前磁盘根目录下写了一个文件Spiral_core.sys,连带输入的后27位也写进去。剩下的部分都在这个sys里完成。后面一个函数没怎么看懂,直接去看sys了。

打开源程序,输一个假flag,就能在根目录看到新建的文件了。这是一个驱动程序,DriverEntry是入口函数,主要是在sub_403310。前面几个函数都在读取一些寄存器的值,申请一些内存,不太清楚实在做什么。到了sub_401596发现了vmxon这个指令。查找资料后发现应该跟VMX技术有关,这是一种硬件虚拟化技术,具体怎么实现不太清楚。后面到了sub_402B60中有大量vmwrite等指令,其实这里不用搞懂他在做什么。我当时死扛在这里太久,却没丝毫头绪。后来问了问pizza才知道这里都不需要看,最重要的在中间的一句

1
vmwrite(0x6C16, &loc_401738);

IDA并不能很好的识别401738的数据,手动P或者C一下发现这是一串代码。开头的call没什么用,第二个call sub_402880才是真正核心的地方。

这里面有一个switch,每个case里面又有switch。

回到sub_402B60,最后vmlaunch之后,后续的cpuid,invd,以及返回后下一个函数sub_4030B0里面的vmcall,这些函数已经不是它本身的内容了,全部被虚拟到了 sub_402880里面。cpuid,invd,vmcall对应着这里面这些switch。具体怎么对应可以进入里面看参数。sub_4019A0有0xDEADBEEF,那就对应着cpuid。用汇编看sub_401690可以看到cpuid的参数DEADBEEF是通过eax传递的,,那接下来的invd和rdmsr的参数也是用eax(rdmsr上面有两行,eax才是它的参数)。

sub_4019A0里先调用了一个函数sub_402690,用来解密一个常量数组A。

接下来就要根据sub_4019A0,解析所有之前的这些指令。

大致分为四种:

cpuid:对操作码解密

invd:打乱操作码

rdmsr:打乱常量数组A

vmcall:虚拟机操作。

没错了,之前的VMX技术"重定义"了这些函数后,形成了一个虚拟机,校验flag过程在这个虚拟机内实现。

之前一串的vmcall就是虚拟机的opcode,我们要把这些opcode解析出来。

这些opcode都是在对常量数组操作,这个数组被之前的调用sub_402690解密后,再通过两个rdmsr,之后就全是vmcall对它的操作了,我们先把vmcall之前的A算出来。

第一个解密函数这里要注意,dword_40502C实际上是数组A地址-1,所以对应A的下标要-1。

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
A=[0x7,0x0CE,0x59,0x23,0x9,0x5,0x3,0x1,0x6,0x2,0x6,0x5,0x7D,0x56,0x0F0,0x28,0x4,0x59,0x4D,0x4D,0x4B,0x53,0x9,0x1,0x0F,0x57,0x8,0x0D3,0x38,0x6F,0x299,0x0E1,0x36,0x2,0x76,0x357,0x6A,0x0AA,0x374,0x1A4,0x5D,0x56,0x57,0x7,0x7F,0x8,0x0A8,0x0B0,0x9,0x32,0x2,0x6,0x463,0x469,0x5,0x0C6,0x2,0x25,0x68,0x33,0x32,0x67,0x1,0x71,0x1,0x507,0x63,0x8,0x6,0x0A3,0x5F5,0x6,0x31,0x3B8,0x65,0x200,0x28,0x57,0x1,0xA5,0x09]

v5 = A[40]
for i in range(4):
A[8*i+40] = A[8*i+40-1]
for j in range(2*i+1):
A[3-i+9*(i+4-j)] = A[3-i+9*(i+4-(j+1))]
for k in range(2*i+2):
A[k+9*(3-i)+3-i] = A[10*(3-i)+k+1]
for l in range(2*i+2):
A[9*(l+3-i)+i+5] = A[9*(3-i+l+1)+i+5]
for m in range(2*i+2):
A[9*(i+5)+i+5-m] = A[9*(i+5)+i+5-(m+1)]
A[72] = v5

# rdmsr(0x174)
v5 = A[80]
v6 = A[8]
for i in range(1,9)[::-1]:
A[10 * i] = A[9 * (i - 1) + i - 1]
A[0] = v5
for j in range(1,9):
A[8 * j] = A[8 * j + 8]
A[8 * (j+1)] = v6

# rdmsr(0x176)
v2 = A[76]
v3 = A[36]
for k in range(1,9)[::-1]:
A[9 * k + 4] = A[9 * (k - 1) + 4]
A[4] = v2
for l in range(8):
A[l + 36] = A[l + 37]
A[44] = v3
print(A)

之后解析vmcall。由于每一段vmcall之前都有一个invd,所以要先按他的方式打乱。

最开始是一个cpuid异或操作码:

1
2
3
4
op=[0xA3,0xF9,0x77,0xA6,0xC1,0xC7,0x4E,0xD1,0x51,0xFF,0x93,0xC8,0x45,0x95,0xF5,0xF2,0x78,0xE6,0x69,0xC6,0x90,0xCD,0x40,0x96,0xF0,0xFE,0x78,0xE3,0x64,0xC7]
# cpuid(0xDEADBEEF)
for i in range(10):
op[i]^=op[i+20]

接下来打乱,我把三段的操作码分开了。

同样的,解析invd的时候要注意,它有些不是从op开头的数组起点,而是从op前一位dword_405374或前两位作为数组起点的,只需对应op-1就行。

invd(4437) 的后面有个循环比较迷,只有一次,相当于没有循环,但是出循环后计数器还是加了,所以计数器是1不是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
	
# invd(0x4437)
v3 = op[7]
for k in range(3):
op[k + 7] = op[6 - k]
if ( k == 2 ):
op[6 - k] = op[3]
else:
op[6 - k] = op[k + 8]
op[3] = op[1]
op[1] = op[2]
op[1] = v3

# invd(0x4433)
for i in range(5):
v0 = op[2 * i]
op[2 * i] = op[2 * i + 1]
op[2 * i + 1] = v0
op0=op[0:10]

# invd(0x4434)
v5 = op[0]
for j in range(9):
op[j] = op[j + 1]
op[9] = v5
op1=op[0:10]

# invd(4437)
v3 = op[7]
for k in range(3):
op[k + 7] = op[6 - k]
if ( k == 2 ):
op[6 - k] = op[3]
else:
op[6 - k] = op[k + 8]
op[3] = op[1]
op[1] = op[2]
op[1] = v3
op2=op[0:10]
opa=[op0,op1,op2]

然后就可以解析vmcode了。根据sub_401C40,最高两位对比操作码,决定何种操作,次两位定位一个数组A中的位置,再过来两位是否为CC决定输入字符串是否倒序,最后两位定位输入字符串的一个字符。

解析代码:

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
# get the operation
for i in range(3):
op=opa[i]
for opcode in O[i]:
v1 = opcode >> 24
v2 = ((opcode>>16) & 0xF) + 9 * ((opcode>>20)&0xF)
v3 = (opcode&0xff)
if ( ((opcode>>8)&0xff) == 0xCC ):
t=opcode&0xff
t1=v3-1
t2=v3+1
t3=v3-2
t4=v3+2
else:
t=26-v3
t1=26-(v3-1)
t2=26-(v3+1)
t3=26-(v3-2)
t4=26-(v3+2)

if ( v1 == op[0] ):
print("A[%d]=x[%d]"%(v2,t))
elif ( v1 == op[1] ):
print("A[%d]=(A[%d]+x[%d])&0xFF"%(v2,v2,t))
elif ( v1 == op[2] ):
print("A[%d]=(A[%d]-x[%d])&0xFF"%(v2,v2,t))
elif ( v1 == op[3] ):
print("A[%d]=(A[%d]/x[%d])&0xFF"%(v2,v2,t))
elif ( v1 == op[4] ):
print("A[%d]=(A[%d]*x[%d])&0xFF"%(v2,v2,t))
elif ( v1 == op[5] ):
print("A[%d]=(A[%d]^x[%d])&0xFF"%(v2,v2,t))
elif ( v1 == op[6] ):
print("A[%d]=(A[%d]^(x[%d]+x[%d]-x[%d]))&0xFF"%(v2,v2,t1,t,t2))
elif ( v1 == op[7] ):
print("A[%d]=(A[%d]^(x[%d]<<4))&0xFF"%(v2,v2,t))
elif ( v1 == op[8] ):
print("A[%d]=(A[%d]|x[%d])&0xFF"%(v2,v2,t))
elif ( v1 == op[9] ):
print("A[%d]=(A[%d]^(x[%d]^x[%d]^x[%d]+x[%d]-x[%d]))&0xFF"%(v2,v2,t2,t1,t3,t,t4))
print()

最后一个vmcall是校验过程sub_401960。这个校验很奇怪,虽然循环是9个,但每次调用的函数的参数都是1,怀疑出了问题。校验函数的内容就是根据一个table把A转成一个9*9矩阵每一行都是1到9的范围内且不相同。(很像数独,虽然只有行)

有了算法和校验,算法这么复杂,逆估计是逆不动了,z3启动!

A是最开始算出来的,那一串操作是刚刚解析的,至于那个while(1):只是因为想在ide里面把它作为代码块缩略= =

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
A=[165,89,35,9,512,3,1,6,87,7,206,125,86,5,40,4,2,8,2,6,5,9,240,15,86,118,855,77,77,75,83,1,225,87,7,127,56,111,665,54,2,6,1123,1129,211,106,170,884,198,176,420,50,103,1,8,168,113,2,9,104,50,1525,6,5,93,1,1287,37,8,6,51,9,89,49,952,101,99,40,87,1,163]
table=[[0x0,0x1,0x2,0x3,0x12,0x13,0x14,0x23,0x24],[0x4,0x5,0x6,0x7,0x8,0x15,0x17,0x27,0x37],[0x10,0x20,0x30,0x31,0x40,0x50,0x51,0x52,0x60],[0x11,0x21,0x22,0x32,0x33,0x34,0x35,0x41,0x42],[0x16,0x25,0x26,0x36,0x43,0x44,0x45,0x46,0x54],[0x18,0x28,0x38,0x48,0x58,0x67,0x68,0x78,0x88],[0x47,0x55,0x56,0x57,0x65,0x66,0x76,0x77,0x87],[0x53,0x62,0x63,0x64,0x72,0x74,0x75,0x85,0x86],[0x61,0x70,0x71,0x73,0x80,0x81,0x82,0x83,0x84]]
x=[BitVec('x%d'%i,16)for i in range(27)]
s=Solver()

for i in range(27):
s.add(And(x[i]>=0x20,x[i]<=0x7F))
while 1:
A[12]=(A[12]^x[23])&0xFF
A[1]=(A[1]-x[1])&0xFF
A[29]=(A[29]^(x[18]+x[17]-x[16]))&0xFF
A[0]=(A[0]+x[0])&0xFF
A[14]=(A[14]^x[4])&0xFF
A[26]=(A[26]^(x[19]<<4))&0xFF
A[25]=(A[25]^x[6])&0xFF
A[11]=(A[11]-x[3])&0xFF
A[24]=(A[24]^x[20])&0xFF
A[8]=(A[8]-x[25])&0xFF
A[10]=(A[10]+x[24])&0xFF
A[23]=(A[23]^(x[4]+x[5]-x[6]))&0xFF
A[27]=(A[27]^(x[8]^x[6]^x[5]+x[7]-x[9]))&0xFF
A[22]=(A[22]/x[21])&0xFF
A[2]=(A[2]-x[26])&0xFF
A[21]=(A[21]^(x[21]^x[23]^x[24]+x[22]-x[20]))&0xFF
A[28]=(A[28]-x[18])&0xFF
A[4]=(A[4]/x[2])&0xFF

A[48]=(A[48]/x[9])&0xFF
A[32]=(A[32]+x[9])&0xFF
A[38]=(A[38]/x[10])&0xFF
A[35]=(A[35]^(x[16]+x[15]-x[14]))&0xFF
A[45]=(A[45]-x[13])&0xFF
A[37]=(A[37]^x[13])&0xFF
A[50]=(A[50]/x[15])&0xFF
A[52]=(A[52]-x[16])&0xFF
A[36]=(A[36]-x[14])&0xFF
A[43]=(A[43]^(x[11]<<4))&0xFF
A[42]=(A[42]^(x[11]<<4))&0xFF
A[39]=(A[39]-x[12])&0xFF
A[33]=(A[33]^x[16])&0xFF
A[47]=(A[47]^(x[14]<<4))&0xFF
A[49]=(A[49]+x[8])&0xFF
A[46]=(A[46]+x[10])&0xFF
A[44]=(A[44]+x[12])&0xFF
A[30]=(A[30]^x[8])&0xFF

A[72]=(A[72]^(x[24]^x[22]^x[21]+x[23]-x[25]))&0xFF
A[67]=(A[67]-x[4])&0xFF
A[64]=(A[64]^x[20])&0xFF
A[75]=(A[75]^(x[24]+x[25]-x[26]))&0xFF
A[74]=(A[74]/x[3])&0xFF
A[61]=(A[61]^(x[5]<<4))&0xFF
A[80]=(A[80]+x[0])&0xFF
A[59]=(A[59]^(x[17]+x[18]-x[19]))&0xFF
A[78]=(A[78]^x[1])&0xFF
A[77]=(A[77]-x[26])&0xFF
A[73]=(A[73]^x[24])&0xFF
A[56]=(A[56]^x[6])&0xFF
A[66]=(A[66]^(x[21]<<4))&0xFF
A[51]=(A[51]-x[7])&0xFF
A[55]=(A[55]+x[17])&0xFF
A[60]=(A[60]^x[19])&0xFF
A[76]=(A[76]^(x[3]+x[2]-x[1]))&0xFF
A[70]=(A[70]-x[22])&0xFF
break

for n in range(9):
t=[0,0,0,0,0,0,0,0,0]
for i in range(9):
t[i]=A[(table[n][i]&0xF)+9*((table[n][i]>>4)&0xF)]
s.add(And(t[i]>=1,t[i]<=9))
for j in range(8):
for k in range(j+1,9):
s.add(t[j]!=t[k])

assert(s.check()==sat)
m=s.model()
flag2=''
for i in x:
flag2+=chr(m[i].as_long())
flag="hctf{"+flag1+flag2+"}"
print(flag)

历尽千辛,终于解出,鼓掌撒花!