LightningSystem

LightningSystem

祥云杯

其它逆向题都很简单,就不水篇幅了,就写一道题。

这道题比赛的时候没整出来,原因是写 vm 的反汇编器的时候写错了一条指令,没有及时发现,导致最后一步没有解出flag,非常遗憾。。又让我想起了国赛 little_evil 也是同样的情况。。

总的来说是一道非常有意思的题目,题目给出了 stm32 固件代码以及电路图。

w25q16 是 flash 芯片,可以存储数据,类似于硬盘,可以在 github 找到 stm32 驱动 w25q16 的代码。

驱动代码如下

1
2
3
https://github.com/yhyuan/STM32-Examples/blob/master/15-M3-SPI%20(W25X16-W25Q16)/%E2%89%A5%C3%83%E2%80%93%C3%9A/USER/spi_flash.c

https://github.com/yhyuan/STM32-Examples/blob/master/15-M3-SPI%20(W25X16-W25Q16)/%E2%89%A5%C3%83%E2%80%93%C3%9A/USER/main.c

对比一下这道题固件中的代码,发现惊人的相似,几乎一摸一样,因此顺手就把符号恢复了。

stm32 与 w25q16 靠的是 spi 总线协议通信,很久很久以前学单片机的时候学过,但是现在只记得一个名词。。不过不影响做题。

这道题还给了 spi 通信的逻辑分析仪抓取的时序图,可以用工具进行分析,设置参数如下

然后逻辑分析软件就正常识别了 spi 总线上传输的数据

通过逆向得知有两次 ID 读取,正好与这里的数据对应, 然后固件程序调用

1
SPI_FLASH_BufferRead(v4, 0, 512);

从 flash 读取 0 扇区 512 字节到内存.
读取的 512 字节的数据直接从逻辑分析软件里面提取

1
2
3
4
5
6
7
8
9
10
11
12
13
import binascii
import ctypes
data = """0xDE 0xFF/0xC8 0xFF/0x03 0xFF/0x00 0xFF/0xAA 0xFF/0x03 0xFF/0x01 0xFF/0xB0 0xFF/0x01 0xFF/0x00 0xFF/0x05 0xFF/0x00 0xFF/0x01 0xFF/0x04 0xFF/0x00 0xFF/0x01 0xFF/0x04 0xFF/0x04 0xFF/0x00 0xFF/0x00 0xFF/0xF3 0xFF/0x03 0xFF/0x00 0xFF/0x02 0xFF/0x03 0xFF/0x01 0xFF/0x2C 0xFF/0x02 0xFF/0x00 0xFF/0x05 0xFF/0x00 0xFF/0x01 0xFF/0x04 0xFF/0x00 0xFF/0x01 0xFF/0x04 0xFF/0x04 0xFF/0x00 0xFF/0x00 0xFF/0xF3 0xFF/0x03 0xFF/0x00 0xFF/0x02 0xFF/0x03 0xFF/0x01 0xFF/0x2C 0xFF/0x06 0xFF/0x2C 0xFF/0x09 0xFF/0x07 0xFF/0x2E 0xFF/0x00 0xFF/0x08 0xFF/0x2C 0xFF/0x2E 0xFF/0x05 0xFF/0x00 0xFF/0x01 0xFF/0x04 0xFF/0x00 0xFF/0x01 0xFF/0x04 0xFF/0x04 0xFF/0x00 0xFF/0x00 0xFF/0xEC 0xFF/0x03 0xFF/0x00 0xFF/0x00 0xFF/0x03 0xFF/0x01 0xFF/0x15 0xFF/0x09 0xFF/0x34 0xFF/0x00 0xFF/0x0A 0xFF/0x34 0xFF/0x02 0xFF/0x05 0xFF/0x34 0xFF/0x02 0xFF/0x07 0xFF/0x2E 0xFF/0x34 0xFF/0x0B 0xFF/0x2E 0xFF/0x7E 0xFF/0x05 0xFF/0x34 0xFF/0x01 0xFF/0x07 0xFF/0x30 0xFF/0x34 0xFF/0x0B 0xFF/0x30 0xFF/0x7E 0xFF/0x0C 0xFF/0x30 0xFF/0xF9 0xFF/0x09 0xFF/0x33 0xFF/0x00 0xFF/0x03 0xFF/0x32 0xFF/0x00 0xFF/0x0D 0xFF/0x32 0xFF/0x4D 0xFF/0x0E 0xFF/0x32 0xFF/0x07 0xFF/0x08 0xFF/0x32 0xFF/0x2C 0xFF/0x08 0xFF/0x32 0xFF/0x2E 0xFF/0x08 0xFF/0x32 0xFF/0x30 0xFF/0x09 0xFF/0x35 0xFF/0x00 0xFF/0x0A 0xFF/0x35 0xFF/0x02 0xFF/0x05 0xFF/0x35 0xFF/0x36 0xFF/0x0F 0xFF/0x35 0xFF/0x32 0xFF/0x05 0xFF/0x00 0xFF/0x01 0xFF/0x04 0xFF/0x00 0xFF/0x01 0xFF/0x04 0xFF/0x04 0xFF/0x00 0xFF/0x00 0xFF/0xB9 0xFF/0x03 0xFF/0x00 0xFF/0x36 0xFF/0x03 0xFF/0x01 0xFF/0x60 0xFF/0x10 0xFF/0x2E 0xFF/0x00 0xFF/0x09 0xFF/0x34 0xFF/0x00 0xFF/0x05 0xFF/0x34 0xFF/0x4A 0xFF/0x10 0xFF/0x2F 0xFF/0x34 0xFF/0x04 0xFF/0x2E 0xFF/0x2F 0xFF/0x04 0xFF/0x04 0xFF/0x00 0xFF/0x00 0xFF/0x22 0xFF/0x05 0xFF/0x00 0xFF/0x01 0xFF/0x04 0xFF/0x00 0xFF/0x01 0xFF/0x04 0xFF/0x04 0xFF/0x00 0xFF/0x00 0xFF/0xE1 0xFF/0x03 0xFF/0x00 0xFF/0xB1 0xFF/0x03 0xFF/0x01 0xFF/0xBA 0xFF/0x01 0xFF/0x00 0xFF/0x05 0xFF/0x00 0xFF/0x01 0xFF/0x04 0xFF/0x00 0xFF/0x01 0xFF/0x04 0xFF/0x04 0xFF/0x00 0xFF/0x00 0xFF/0xF3 0xFF/0x04 0xFF/0x00 0xFF/0x00 0xFF/0x13 0xFF/0x03 0xFF/0x00 0xFF/0xBB 0xFF/0x03 0xFF/0x01 0xFF/0xC7 0xFF/0x01 0xFF/0x00 0xFF/0x05 0xFF/0x00 0xFF/0x01 0xFF/0x04 0xFF/0x00 0xFF/0x01 0xFF/0x04 0xFF/0x04 0xFF/0x00 0xFF/0x00 0xFF/0xF3 0xFF/0xFF 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x4D 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x00 0xFF/0x20 0xFF/0x12 0xFF/0x67 0xFF/0x0F 0xFF/0xDB 0xFF/0xF6 0xFF/0x0A 0xFF/0x0F 0xFF/0x39 0xFF/0xF6 0xFF/0xC9 0xFF/0xF5 0xFF/0xC1 0xFF/0xF2 0xFF/0xA3 0xFF/0x0D 0xFF/0xD0 0xFF/0xF5 0xFF/0x01 0xFF/0x0C 0xFF/0x6F 0xFF/0x0E 0xFF/0x39 0xFF/0xF2 0xFF/0x80 0xFF/0xF5 0xFF/0xE4 0xFF/0x0C 0xFF/0xD7 0xFF/0xF8 0xFF/0x68 0xFF/0x0C 0xFF/0x96 0xFF/0xF5 0xFF/0xA5 0xFF/0x0F 0xFF/0x9F 0xFF/0x0F 0xFF/0x31 0xFF/0xF9 0xFF/0x2E 0xFF/0x1B 0xFF/0x07 0xFF/0x69 0xFF/0x6E 0xFF/0x70 0xFF/0x75 0xFF/0x74 0xFF/0x3A 0xFF/0x00 0xFF/0x47 0xFF/0x6F 0xFF/0x6F 0xFF/0x64 0xFF/0x20 0xFF/0x6A 0xFF/0x6F 0xFF/0x62 0xFF/0x21 0xFF/0x00 0xFF/0x57 0xFF/0x6F 0xFF/0x72 0xFF/0x6B 0xFF/0x20 0xFF/0x68 0xFF/0x61 0xFF/0x72 0xFF/0x64 0xFF/0x65 0xFF/0x72 0xFF/0x21 0xFF/0x00 0xFF/0xFF 0xFF/0xFF 0xFF/0xFF 0xFF/0xFF 0xFF/0xFF 0xFF/0xFF 0xFF/0xFF 0xFF/0xFF 0xFF/0xFF 0xFF/0xFF 0xFF/0xFF 0xFF/0xFF 0xFF/0xFF 0xFF/0xFF 0xFF/0xFF 0xFF/0xFF 0xFF/0xFF 0xFF/0xFF 0xFF/0xFF 0xFF/0xFF 0xFF/0xFF 0xFF/0xFF 0xFF/0xFF 0xFF/0xFF 0xFF/0xFF 0xFF/0xFF 0xFF/0xFF 0xFF/0xFF 0xFF/0xFF 0xFF/0xFF 0xFF/0xFF 0xFF/0xFF 0xFF/0xFF 0xFF/0xFF 0xFF/0xFF 0xFF/0xFF 0xFF/0xFF 0xFF/0xFF 0xFF/0xFF 0xFF/0xFF 0xFF/0xFF 0xFF/0xFF 0xFF/0xFF 0xFF/0xFF 0xFF/0xFF 0xFF/0xFF 0xFF/0xFF 0xFF/0xFF 0xFF/0xFF 0xFF/0xFF 0xFF/0xFF 0xFF/0xFF 0xFF/0xFF 0xFF/0xFF 0xFF/0xFF 0xFF/0xFF 0xFF/0xFF 0xFF/0xFF 0xFF/0xFF 0xFF/0xFF 0xFF/0xFF 0xFF/0xFF 0xFF/0xFF 0xFF/0xFF 0xFF/0xFF 0xFF/0xFF 0xFF/0xFF 0xFF/0xFF 0xFF/0xFF 0xFF/0xFF 0xFF/0xFF 0xFF/0xFF 0xFF/0xFF 0xFF/0xFF 0xFF/0xFF 0xFF/0xFF 0xFF/0xFF 0xFF/0xFF 0xFF/0xFF 0xFF/0xFF 0xFF/0xFF 0xFF/0xFF 0xFF/0xFF 0xFF/0xFF 0xFF/0xFF 0xFF/0xFF 0xFF/0xFF 0xFF/0xFF"""
data = data.split(" ")
data2 = ''
for n in data:
if '/' in n:
n2 = n.split("/")[1]
else:
n2 = n
data2 += n2[2:]

data_code = binascii.a2b_hex(data2)

读入 512 字节的数据作为 bytecode 执行vm

vm 的结构体看起来很乱,其实很简单如下

Vm 的 case1 与 case2 函数看起很复杂,其实是操作串口输入输出数据,类似于 getchar, putchar。

编写 反汇编器如下

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
def gen(pc, ins_text):
return "_0x%x: %s" % (pc, ins_text)

def decompiler(data):
pc = 0
while pc < len(data):
opcode = data[pc]
op1 = data[pc + 1]
op2 = data[pc + 2]
op3 = data[pc + 3]
asm_text = gen(pc, "Undefine")
if opcode == 0:
asm_text = gen(pc, "nop")
pc += 1
elif opcode == 1:
asm_text = gen(pc, "putchar(mem[mem[0x%x]]);" % op1)
pc += 2
elif opcode == 2:
asm_text = gen(pc, "mem[mem[0x%x]] = getchar();" % op1)
pc += 2
elif opcode == 3:
asm_text = gen(pc, "mem[0x%x] = 0x%x;" % (op1, op2))
pc += 3
elif opcode == 4:
asm_text = gen(pc, "if(mem[0x%x] == mem[0x%x]) goto _0x%x" % (op1, op2, pc + ctypes.c_int8(op3).value + 4))
pc += 4
elif opcode == 5:
asm_text = gen(pc, "mem[0x%x] += 0x%x" % (op1, op2))
pc += 3
elif opcode == 6:
asm_text = gen(pc, "mem2[0x%x] = ror(mem2[0x%x], 0x%x)" % (op1, op1, op2))
pc += 3
elif opcode == 7:
asm_text = gen(pc, "mem2[0x%x] = mem[mem[0x%x]]" % (op1, op2))
pc += 3
elif opcode == 8:
asm_text = gen(pc, "mem2[0x%x] += mem2[0x%x]" % (op1, op2))
pc += 3
elif opcode == 9:
asm_text = gen(pc, "mem[0x%x] = mem[0x%x]" % (op1, op2))
pc += 3
elif opcode == 10:
asm_text = gen(pc, "mem[0x%x] *= 0x%x" % (op1, op2))
pc += 3
elif opcode == 11:
asm_text = gen(pc, "mem2[0x%x] -= mem2[0x%x]" % (op1, op2))
pc += 3
elif opcode == 12:
if ctypes.c_int8(op2).value < 0:
asm_text = gen(pc, "mem2[0x%x] << 0x%x" % (op1, 0x100 - op2)) #!!负数 7
else:
asm_text = gen(pc, "mem2[0x%x] >> 0x%x" % (op1, op2)) #!!负数
pc += 3
elif opcode == 13:
asm_text = gen(pc, "mem2[0x%x] ^= 0x%x" % (op1, op2))
pc += 3
elif opcode == 14:
asm_text = gen(pc, "mem2[0x%x] *= 0x%x" % (op1, op2))
pc += 3
elif opcode == 15:
asm_text = gen(pc, "mem2[mem[0x%x]] = mem2[0x%x]" % (op1, op2))
pc += 3
elif opcode == 16:
asm_text = gen(pc, "mem[0x%x] = mem[mem[0x%x]]" % (op1, op2))
pc += 3
elif opcode == 0xff:
return
else:
print("Undefine: 0x%x opcode:%d" % (pc, opcode))
raise
print(asm_text)

decompiler(data_code[2: ])
print("data offset:", hex(data_code[0] + 2))
mem = data_code[data_code[0] + 2: ]
#print(mem)
print(binascii.b2a_hex(mem[126:126 + 2]))
print(binascii.b2a_hex(mem[128:]))
print(binascii.b2a_hex(mem[128:128 + 42]))
print(mem[128:])
data_cmp = mem[128:128 + 42]
cmp_list = []
for i in range(21):
gg = (data_cmp[i * 2] << 8) | data_cmp[2 * i + 1]
cmp_list.append(gg)
print(cmp_list)

mem2 表示已word形式访问内存。

利用C语言重编译,并用IDA反汇编结果如下

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
mem[0] = 0xAA;
mem[1] = 0xB0;
do
{
putchar(mem[mem[0]]);
++mem[0];
}
while ( mem[0] != mem[1] );
mem[0] = 2;
mem[1] = 44;
do
mem[mem[0]++] = getchar();
while ( mem[0] != mem[1] );
mem[0] = 2;
mem[1] = 44;
do
{
mem2[44] = sub_41137F(mem2[44], 9);
mem2[46] = mem[mem[0]];
mem2[44] += mem2[46];
++mem[0];
}
while ( mem[0] != mem[1] );
mem[0] = 0;
mem[1] = 21;
do
{
mem[52] = mem[0];
mem[52] *= 2;
mem[52] += 2;
mem2[46] = mem[mem[52]];
mem2[46] -= mem2[126];
mem2[48] = mem[++mem[52]];
mem2[48] -= mem2[126];
mem2[48] <<= 7;
mem[51] = mem[0];
mem[50] = 0;
mem2[50] ^= 0x4Du;
mem2[50] *= 7;
mem2[50] += mem2[44];
mem2[50] += mem2[46];
mem2[50] += mem2[48];
mem[53] = mem[0];
mem[53] *= 2;
mem[53] += 54;
mem2[mem[53]] = mem2[50];
++mem[0];
}
while ( mem[0] != mem[1] );
mem[0] = 54;
mem[1] = 96;
do
{
mem[46] = mem[mem[0]];
mem[52] = mem[0];
mem[52] += 74;
mem[47] = mem[mem[52]];
if ( mem[46] != mem[47] )
{
mem[0] = -69;
mem[1] = -57;
do
{
putchar(mem[mem[0]]);
++mem[0];
}
while ( mem[0] != mem[1] );
goto LABEL_17;
}
++mem[0];
}
while ( mem[0] != mem[1] );
mem[0] = -79;
mem[1] = -70;
do
{
putchar(mem[mem[0]]);
++mem[0];
}
while ( mem[0] != mem[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
mem[0] = 0;
mem[1] = 21; // mem[2:2+42] 是输入的flag
do
{
mem[52] = mem[0];
mem[52] *= 2;
mem[52] += 2;
mem2[46] = mem[mem[52]];
mem2[46] -= mem2[126];
mem2[48] = mem[++mem[52]];
mem2[48] -= mem2[126];
mem2[48] <<= 7;
mem[51] = mem[0];
mem[50] = 0;
mem2[50] ^= 0x4Du;
mem2[50] *= 7;
mem2[50] += mem2[44]; // 这个值需要爆破 0 - 0xffff
mem2[50] += mem2[46];
mem2[50] += mem2[48];
mem[53] = mem[0];
mem[53] *= 2;
mem[53] += 54;
mem2[mem[53]] = mem2[50]; // 存到 mem[53:53+42]
++mem[0];
}
while ( mem[0] != mem[1] );

就是一个简单的加密变化, 最后与内存中标准数据比较

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
mem[0] = 54;
mem[1] = 96;
do
{
mem[46] = mem[mem[0]];
mem[52] = mem[0];
mem[52] += 74;
mem[47] = mem[mem[52]];
if ( mem[46] != mem[47] )
{
mem[0] = -69;
mem[1] = -57;
do
{
putchar(mem[mem[0]]);
++mem[0];
}
while ( mem[0] != mem[1] );
goto LABEL_17;
}
++mem[0];
}
while ( mem[0] != mem[1] );

最后只需要爆破 mem2[44] 两个字节数据即可得到flag

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

bool test(unsigned short key, char *flag) {
unsigned short target[] = {4711, 4059, 62986, 3897, 63177, 62913, 62115, 3536, 62721, 3183, 3641, 62080, 62948, 3287, 63592, 3222, 62885, 3999, 3889, 63790, 6919};
for (unsigned short i = 0; i < 21; i++) {
unsigned tmp = target[i];
tmp -= key;
tmp -= (i ^ 0x4D) * 7;
flag[2 * i + 1] = ( (tmp >> 7 ) & 0x7f) + 0x20;
flag[2 * i] = (tmp & 0x7f) + 0x20;
}
return flag[0] == 'f';
}
int main() {
char flag[100] = {0};
for(int i = 0; i <= 0xffff; i++) {
if(test(i, flag)) {
printf("%x %s\n", i, flag);
}
}
return 0;
}

比赛的时候犯了一个什么错误呢?调试到比赛结束都没发现

下面是比赛的时候 vm 反编译器的生成结果

非常白痴的问题,应该写成 mem2[0x30] <<= 0x7 少了一个“=”,重编译之后,这一句代码直接被优化掉了,ida 没有显示出来。


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!