0%

WriteUp:TCTF2020 flash

题目描述

TCTF2020 的 flash 题目。

题目本体见这里

一开始看到名字我还以为是 Adobe 那个 flash 逆向,结果下载下来一看 run.sh

1
2
3
4
#! /bin/sh


qemu-system-mips -M mips -bios ./flash -nographic -m 16M -monitor /dev/null 2>/dev/null

哦,得嘞,原来是闪存那个 flash。

题目的关键当然是这个 flash 文件了,一开始我总以为它是 qemu 的镜像文件,但是无奈用了各种工具检查都显示是纯数据文件,最后我终于注意到了 -bios 参数,实际上意思就是从这个文件第一行开始执行,所以直接拖进IDA就可以了。

环境配置

首先是安装 qemu-system-mips,直接 apt install qemu-system-mips 即可。

然后为了能够调试这个文件,我们需要目标架构包含 mips 的 gdb,通常情况下预编译的 gdb 是只有 i386 和 x86_64 的,所以我们还需要手动编译一下,这里我选择的是 gdb9.2。

1
2
3
4
5
6
wget -c "http://ftp.gnu.org/gnu/gdb/gdb-9.2.tar.gz"
tar xf gdb-9.2.tar.gz
cd gdb-9.2
mkdir build
./configure --target=mips --with-python=/usr/bin/python3
make -j8

这里选定了 Python 路径是因为我需要用到 pwndbg。

编译完成后注意在运行的时候需要指定 data-directory

1
2
cd gdb
./gdb --data-directory ./data-directory/

然后输入 set architecture 再按 tab 就可以看到包含了各种 mips 的架构。

1
2
3
4
5
6
7
8
9
10
11
12
pwndbg> set architecture 
auto mips:4010 mips:5500 mips:isa32 mips:loongson_2e
mips mips:4100 mips:5900 mips:isa32r2 mips:loongson_2f
mips:10000 mips:4111 mips:6000 mips:isa32r3 mips:micromips
mips:12000 mips:4120 mips:7000 mips:isa32r5 mips:mips5
mips:14000 mips:4300 mips:8000 mips:isa32r6 mips:octeon
mips:16 mips:4400 mips:9000 mips:isa64 mips:octeon+
mips:16000 mips:4600 mips:gs264e mips:isa64r2 mips:octeon2
mips:3000 mips:4650 mips:gs464 mips:isa64r3 mips:octeon3
mips:3900 mips:5000 mips:gs464e mips:isa64r5 mips:sb1
mips:4000 mips:5400 mips:interaptiv-mr2 mips:isa64r6 mips:xlr
pwndbg> set architecture

最后在 run.sh 加一个 -s,这样在程序启动的时候就会连带启动 gdbserver 了,接下来只需要在 gdb 里连接即可。

1
pwndbg> target remote 127.0.0.1:1234

到这里环境配置就完成了。

内存布局

首先第一个难点是拖进IDA后我们并不知道内存布局,但是从第一行代码可以猜测基址是 0xFC00000。

1
2
3
ROM:0FC00000                 .text # ROM
ROM:0FC00000 j loc_FC01680
ROM:0FC00004 nop

IDA rebase之后跟随跳转可以看到这样一行。

1
li      $t0, 0xBFC01550

因此大胆猜测实际基址是 0xBFC00000,设置之后可以看到 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
ROM:BFC015E4 sub_BFC015E4:                            # CODE XREF: sub_BFC00000+16C0↓p
ROM:BFC015E4
ROM:BFC015E4 var_18 = -0x18
ROM:BFC015E4 addr80000000 = -0x14
ROM:BFC015E4 var_10 = -0x10
ROM:BFC015E4 var_8 = -8
ROM:BFC015E4 var_4 = -4
ROM:BFC015E4
ROM:BFC015E4 addiu $sp, -0x28
ROM:BFC015E8 sw $ra, 0x28+var_4($sp)
ROM:BFC015EC sw $fp, 0x28+var_8($sp)
ROM:BFC015F0 move $fp, $sp
ROM:BFC015F4 lui $v0, 0xBFC1
ROM:BFC015F8 sw $v0, 0x28+var_18($fp)
ROM:BFC015FC lui $v0, 0x8000
ROM:BFC01600 sw $v0, 0x28+addr80000000($fp)
ROM:BFC01604 sw $zero, 0x28+var_10($fp)
ROM:BFC01608 b loc_BFC0163C
ROM:BFC0160C nop
ROM:BFC01610 # ---------------------------------------------------------------------------
ROM:BFC01610
ROM:BFC01610 loc_BFC01610: # CODE XREF: sub_BFC015E4+60↓j
ROM:BFC01610 lw $v1, 0x28+var_18($fp)
ROM:BFC01614 addiu $v0, $v1, 4
ROM:BFC01618 sw $v0, 0x28+var_18($fp)
ROM:BFC0161C lw $v0, 0x28+addr80000000($fp)
ROM:BFC01620 addiu $a0, $v0, 4
ROM:BFC01624 sw $a0, 0x28+addr80000000($fp)
ROM:BFC01628 lw $v1, 0($v1)
ROM:BFC0162C sw $v1, 0($v0)
ROM:BFC01630 lw $v0, 0x28+var_10($fp)
ROM:BFC01634 addiu $v0, 1
ROM:BFC01638 sw $v0, 0x28+var_10($fp)
ROM:BFC0163C
ROM:BFC0163C loc_BFC0163C: # CODE XREF: sub_BFC015E4+24↑j
ROM:BFC0163C lw $v0, 0x28+var_10($fp)
ROM:BFC01640 slti $v0, 0x1000
ROM:BFC01644 bnez $v0, loc_BFC01610
ROM:BFC01648 nop
ROM:BFC0164C li $v0, 0x80000500
ROM:BFC01654 jalr $v0
ROM:BFC01658 nop
ROM:BFC0165C nop
ROM:BFC01660 move $sp, $fp
ROM:BFC01664 lw $ra, 0x28+var_4($sp)
ROM:BFC01668 lw $fp, 0x28+var_8($sp)
ROM:BFC0166C addiu $sp, 0x28
ROM:BFC01670 jr $ra
ROM:BFC01674 nop
ROM:BFC01674 # End of function sub_BFC015E4

刚才说过,这个 flash 文件是作为 bios 运行的,因此这里需要先把用户态的代码加载到指定位置。不难看出这里是把 0xBFC10000 - 0xBFC14000 这 0x4000 字节的代码加载到了 0x80000000 的位置然后跳转到 0x80000500 开始执行,因此接下来我选择把原文件拆成两份分别加载到 IDA 中。

到这里文件的大致内存布局就非常清晰了。

中断

跳转到 0x80000500 后第一件事是设置了 Compare 和 Count 寄存器。

1
2
3
4
5
6
7
ROM:80000564 sub_80000564:                            # CODE XREF: ROM:80000500↑p
ROM:80000564 # ROM:80002668↓p
ROM:80000564 li $t0, 0x1000
ROM:80000568 mtc0 $t0, Compare # Timer Compare
ROM:8000056C mtc0 $zero, Count # Timer Count
ROM:80000570 jr $ra
ROM:80000574 nop

这里根据 google,mips 处理器会不断增加 Count 寄存器的计数,当 Count 等于 Compare 的时候会触发一个中断。

接下来程序在加载了 Welcome 字符串后就原地死循环了。

1
2
3
4
5
6
7
8
9
10
11
ROM:80000D34 loc_80000D34:                            # CODE XREF: ROM:80000518↑j
ROM:80000D34 addiu $sp, -0x20
ROM:80000D38 sw $ra, 0x1C($sp)
ROM:80000D3C sw $fp, 0x18($sp)
ROM:80000D40 move $fp, $sp
ROM:80000D44 lui $v0, 0x8000
ROM:80000D48 addiu $a0, $v0, (aWelcomeToFlagM - 0x80000000) # "Welcome to Flag Machine"
ROM:80000D4C mtc0 $zero, Count # Timer Count
ROM:80000D50
ROM:80000D50 loc_80000D50: # CODE XREF: ROM:loc_80000D50↓j
ROM:80000D50 b loc_80000D50

这里就非常有意思了,程序的控制流进入了死循环,但是我们明明看到字符串被正常输出了出来,是怎么办到的呢?我一开始也完全不理解,后来联想到上面的 Count 和 Compare 寄存器我突然有了答案:是通过中断的方式来改变控制流。

那另一个问题就来了,根据 x86 的知识,应该有一张中断表来保存不同中断子程序的入口,那么 mips 上中断子程序的入口在哪呢?经过我一阵 google 后,我终于找到了原来 mips 的中断处理入口固定为 0x80000180,用 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
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
ROM:80000180 interrupt:
ROM:80000180
ROM:80000180 arg_0 = 0
ROM:80000180 arg_4 = 4
ROM:80000180 arg_8 = 8
ROM:80000180 arg_C = 0xC
ROM:80000180 arg_10 = 0x10
ROM:80000180 arg_14 = 0x14
ROM:80000180 arg_18 = 0x18
ROM:80000180 arg_1C = 0x1C
ROM:80000180 arg_20 = 0x20
ROM:80000180 arg_24 = 0x24
ROM:80000180 arg_28 = 0x28
ROM:80000180 arg_2C = 0x2C
ROM:80000180 arg_30 = 0x30
ROM:80000180 arg_34 = 0x34
ROM:80000180 arg_38 = 0x38
ROM:80000180 arg_3C = 0x3C
ROM:80000180 arg_40 = 0x40
ROM:80000180 arg_44 = 0x44
ROM:80000180 arg_48 = 0x48
ROM:80000180 arg_4C = 0x4C
ROM:80000180 arg_50 = 0x50
ROM:80000180 arg_54 = 0x54
ROM:80000180 arg_58 = 0x58
ROM:80000180 arg_5C = 0x5C
ROM:80000180 arg_60 = 0x60
ROM:80000180 arg_6C = 0x6C
ROM:80000180 arg_70 = 0x70
ROM:80000180 arg_74 = 0x74
ROM:80000180 arg_78 = 0x78
ROM:80000180 arg_7C = 0x7C
ROM:80000180 arg_80 = 0x80
ROM:80000180 arg_84 = 0x84
ROM:80000180
ROM:80000180 move $k1, $sp
ROM:80000184 li $sp, 0x8043FF6C
ROM:8000018C sw $at, arg_0($sp)
ROM:80000190 sw $v0, arg_4($sp)
ROM:80000194 sw $v1, arg_8($sp)
ROM:80000198 sw $a0, arg_C($sp)
ROM:8000019C sw $a1, arg_10($sp)
ROM:800001A0 sw $a2, arg_14($sp)
ROM:800001A4 sw $a3, arg_18($sp)
ROM:800001A8 sw $t0, arg_1C($sp)
ROM:800001AC sw $t1, arg_20($sp)
ROM:800001B0 sw $t2, arg_24($sp)
ROM:800001B4 sw $t3, arg_28($sp)
ROM:800001B8 sw $t4, arg_2C($sp)
ROM:800001BC sw $t5, arg_30($sp)
ROM:800001C0 sw $t6, arg_34($sp)
ROM:800001C4 sw $t7, arg_38($sp)
ROM:800001C8 sw $s0, arg_3C($sp)
ROM:800001CC sw $s1, arg_40($sp)
ROM:800001D0 sw $s2, arg_44($sp)
ROM:800001D4 sw $s3, arg_48($sp)
ROM:800001D8 sw $s4, arg_4C($sp)
ROM:800001DC sw $s5, arg_50($sp)
ROM:800001E0 sw $s6, arg_54($sp)
ROM:800001E4 sw $s7, arg_58($sp)
ROM:800001E8 sw $t8, arg_5C($sp)
ROM:800001EC sw $t9, arg_60($sp)
ROM:800001F0 sw $gp, arg_6C($sp)
ROM:800001F4 sw $k1, arg_70($sp)
ROM:800001F8 sw $fp, arg_74($sp)
ROM:800001FC sw $ra, arg_78($sp)
ROM:80000200 mfc0 $t0, EPC # Exception Program Counter
ROM:80000204 sw $t0, arg_7C($sp)
ROM:80000208 mfc0 $t0, ErrorEPC # Error Exception Program Counter
ROM:8000020C sw $t0, arg_80($sp)
ROM:80000210 mfc0 $t0, SR # Status register
ROM:80000214 sw $t0, arg_84($sp)
ROM:80000218 addi $a0, $sp, 0
ROM:8000021C lw $k0, off_80003D20
ROM:80000224 jalr $k0
ROM:80000228 nop
ROM:8000022C nop
ROM:80000230 lw $t0, arg_7C($sp)
ROM:80000234 mtc0 $t0, EPC # Exception Program Counter
ROM:80000238 lw $t0, arg_80($sp)
ROM:8000023C mtc0 $t0, ErrorEPC # Error Exception Program Counter
ROM:80000240 lw $t0, arg_84($sp)
ROM:80000244 mtc0 $t0, SR # Status register
ROM:80000248 lw $v0, arg_4($sp)
ROM:8000024C lw $v1, arg_8($sp)
ROM:80000250 lw $a0, arg_C($sp)
ROM:80000254 lw $a1, arg_10($sp)
ROM:80000258 lw $a2, arg_14($sp)
ROM:8000025C lw $a3, arg_18($sp)
ROM:80000260 lw $t0, arg_1C($sp)
ROM:80000264 lw $t1, arg_20($sp)
ROM:80000268 lw $t2, arg_24($sp)
ROM:8000026C lw $t3, arg_28($sp)
ROM:80000270 lw $t4, arg_2C($sp)
ROM:80000274 lw $t5, arg_30($sp)
ROM:80000278 lw $t6, arg_34($sp)
ROM:8000027C lw $t7, arg_38($sp)
ROM:80000280 lw $s0, arg_3C($sp)
ROM:80000284 lw $s1, arg_40($sp)
ROM:80000288 lw $s2, arg_44($sp)
ROM:8000028C lw $s3, arg_48($sp)
ROM:80000290 lw $s4, arg_4C($sp)
ROM:80000294 lw $s5, arg_50($sp)
ROM:80000298 lw $s6, arg_54($sp)
ROM:8000029C lw $s7, arg_58($sp)
ROM:800002A0 lw $t8, arg_5C($sp)
ROM:800002A4 lw $t9, arg_60($sp)
ROM:800002A8 lw $gp, arg_6C($sp)
ROM:800002AC lw $k1, arg_70($sp)
ROM:800002B0 lw $fp, arg_74($sp)
ROM:800002B4 lw $ra, arg_78($sp)
ROM:800002B8 move $sp, $k1
ROM:800002BC eret
ROM:800002BC # End of function interrupt

先保存所有寄存器,再恢复所有寄存器,明显是中断子程序了。

到这里疑问其实只解决了一半,因为通常的中断在完成后回立即返回原来的上下文,也就是原来的位置,如果没有特殊处理的话中断之后仍然会回到死循环的位置,所以这里的中断子程序应该有一些特殊的处理。又是一番 google,原来 mips 在发生中断的时候会把返回地址保存在 EPC 中,而上面的中断子程序中 EPC 的值是存在栈上的,也就是说只要修改了栈上的值,结束中断后返回的地址也会发生变化(其实就类似线程调度的底层实现)。想到这个地方基本上就可以说豁然开朗了,把断点分别设置在 jalr $k0 前后,成功观察到 arg_7C($sp) 值发生了变化,所以我们的推测应该是正确的,接下来就是把这里地址变换的逻辑逆出来了。

这里跟踪 jalr $k0,发现最后关键是调用了一个 syscall 又回到了内核态,继续跟踪可以看到逻辑还算比较清晰,在第一次进入的时候初始化地址映射,然后根据特定的算法计算映射后的地址。我没有太多的耐心逆向具体的算法,我选择直接把地址映射表 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
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
import struct

def pp(l):
for i in l:
print(hex(i))

addr2000 = [
0x800287c0, 0x80028004, 0xbfc01550
]

addr2800 = []

bs = open("28000.dump", "rb+").read()

for i in range(len(bs)//4):
addr2800.append(struct.unpack(">I", bs[4*i:4*(i+1)])[0])

#https://stackoverflow.com/questions/27506474/how-to-byte-swap-a-32-bit-integer-in-python
def swap32(i):
return struct.unpack("<I", struct.pack(">I", i))[0]

addr = 0x80000610

x = 0xD3ABC0DE

epc_xor = addr ^ x

xored_list = [0x7EC5AB53, 0xFEE9AB53, 0x6EC5AB53, 0xEAE9AB53, 0x12C5AB53, 0x96E9AB53, 0x32C5AB53, 0x82E9AB53, 0xCEC6AB53, 0xAEE9AB53, 0xFEC6AB53, 0x5AE9AB53, 0xEEC6AB53, 0x46E9AB53, 0x96C6AB53, 0x72E9AB53, 0xBEC6AB53, 0x1EE9AB53, 0xAEC6AB53, 0x0AE9AB53, 0x4EC6AB53, 0x36E9AB53, 0x62C6AB53, 0x22E9AB53, 0x12C6AB53, 0xCEEAAB53, 0x3EC6AB53, 0xFAEAAB53, 0xDEC7AB53, 0xE6EAAB53, 0xE6C7AB53, 0x92EAAB53, 0x96C7AB53, 0xBEEAAB53, 0x52C7AB53, 0xAAEAAB53, 0x16C7AB53, 0x56EAAB53, 0xCAC8AB53, 0x42EAAB53, 0xEAC8AB53, 0x6EEAAB53, 0xA6C8AB53, 0x1AEAAB53, 0x46C8AB53, 0x06EAAB53, 0x7AC8AB53, 0x32EAAB53, 0x6AC8AB53, 0xDEEBAB53, 0x06C8AB53, 0xCAEBAB53, 0x3AC8AB53, 0xF6EBAB53, 0x2EC8AB53, 0xE2EBAB53, 0xDEC9AB53, 0x8EEBAB53, 0xFAC9AB53, 0xBAEBAB53, 0xEEC9AB53, 0xA6EBAB53, 0xE2C9AB53, 0x52EBAB53, 0x92C9AB53, 0x7EEBAB53, 0xA6C9AB53, 0x6AEBAB53, 0x5AC9AB53, 0x16EBAB53, 0x66C9AB53, 0x02EBAB53, 0x1AC9AB53, 0x2EEBAB53, 0x0EC9AB53, 0xDAECAB53, 0x3EC9AB53, 0xC6ECAB53, 0xD6CAAB53, 0xF2ECAB53, 0xCACAAB53, 0x9EECAB53, 0xFECAAB53, 0x8AECAB53, 0xEECAAB53, 0xB6ECAB53, 0x82CAAB53, 0xA2ECAB53, 0xB6CAAB53, 0x4EECAB53, 0x7ACAAB53, 0x7AECAB53, 0x66CAAB53, 0x66ECAB53, 0x2ACAAB53, 0x12ECAB53, 0xEECBAB53, 0x3EECAB53, 0x9ACBAB53, 0x2AECAB53, 0x5ECBAB53, 0xD6EDAB53, 0x7ECBAB53, 0xC2EDAB53, 0x16CBAB53, 0xEEEDAB53, 0xD2CCAB53, 0x9AEDAB53, 0xC6CCAB53, 0x86EDAB53, 0xEACCAB53, 0xB2EDAB53, 0x9ECCAB53, 0x5EEDAB53, 0x92CCAB53, 0x4AEDAB53, 0xBACCAB53, 0x76EDAB53, 0xAECCAB53, 0x62EDAB53, 0xA2CCAB53, 0x0EEDAB53, 0x4ECCAB53, 0x3AEDAB53, 0x7ACCAB53, 0x26EDAB53, 0x16CCAB53, 0xD2EEAB53, 0xF2CDAB53, 0xFEEEAB53, 0x8ECDAB53, 0xEAEEAB53, 0xBACDAB53, 0x96EEAB53, 0xAACDAB53, 0x82EEAB53, 0x52CDAB53, 0xAEEEAB53, 0x66CDAB53, 0x5AEEAB53, 0x1ACDAB53, 0x46EEAB53, 0x36CDAB53, 0x72EEAB53, 0xC6CEAB53, 0x1EEEAB53, 0x8ECEAB53, 0x0AEEAB53, 0x82CEAB53, 0x36EEAB53, 0xAECEAB53, 0x22EEAB53, 0x5ACEAB53, 0xCEEFAB53, 0x4ECEAB53, 0xFAEFAB53, 0x7ECEAB53, 0xE6EFAB53, 0x66CEAB53, 0x92EFAB53, 0x06CEAB53, 0xBEEFAB53, 0xD6CFAB53, 0xAAEFAB53, 0xE2CFAB53, 0x56EFAB53, 0xA6CFAB53, 0x42EFAB53, 0x12CFAB53, 0x6EEFAB53, 0xCAD0AB53, 0x1AEFAB53, 0x46D0AB53, 0x06EFAB53, 0x06D0AB53, 0x32EFAB53, 0x26D0AB53, 0xDEF0AB53, 0xC6D1AB53, 0xCAF0AB53, 0xF2D1AB53, 0xF6F0AB53, 0x8ED1AB53, 0xE2F0AB53, 0xA6D1AB53, 0x8EF0AB53, 0x46D1AB53, 0xBAF0AB53, 0x62D1AB53, 0xA6F0AB53, 0x02D1AB53, 0x52F0AB53, 0xDAD2AB53, 0x7EF0AB53, 0xE6D2AB53, 0x6AF0AB53, 0x96D2AB53, 0x16F0AB53, 0xA6D2AB53, 0x02F0AB53, 0x6AD2AB53, 0x2EF0AB53, 0x02D2AB53, 0xDAF1AB53, 0xCED3AB53, 0xC6F1AB53, 0xFED3AB53, 0xF2F1AB53, 0x92D3AB53, 0x9EF1AB53, 0x5AD3AB53, 0x8AF1AB53, 0x1AD3AB53, 0xB6F1AB53, 0x0AD3AB53, 0xA2F1AB53, 0x36D3AB53, 0x4EF1AB53, 0xD2D4AB53, 0x7AF1AB53, 0xFED4AB53, 0x66F1AB53, 0x9AD4AB53, 0x12F1AB53, 0xAAD4AB53, 0x3EF1AB53, 0x7AD4AB53, 0x2AF1AB53, 0x6ED4AB53, 0xD6F2AB53, 0x16D4AB53, 0xC2F2AB53, 0x3AD4AB53, 0xEEF2AB53, 0xCAD5AB53, 0x9AF2AB53, 0xEAD5AB53, 0x86F2AB53, 0xA2D5AB53, 0xB2F2AB53, 0x42D5AB53, 0x5EF2AB53, 0x0ED5AB53, 0x4AF2AB53, 0x02D5AB53, 0x76F2AB53, 0x2AD5AB53, 0x62F2AB53, 0xC6D6AB53, 0x0EF2AB53, 0xBAD6AB53, 0x3AF2AB53, 0x5AD6AB53, 0x26F2AB53, 0x46D6AB53, 0xD2F3AB53, 0x72D6AB53, 0xFEF3AB53, 0x32D6AB53, 0xEAF3AB53, 0x22D6AB53, 0x96F3AB53, 0xCAD7AB53, 0x82F3AB53, 0xEAD7AB53, 0xAEF3AB53, 0x9AD7AB53, 0x5AF3AB53, 0x8AD7AB53, 0x46F3AB53, 0xB6D7AB53, 0x72F3AB53, 0xA2D7AB53, 0x1EF3AB53, 0x46D7AB53, 0x0AF3AB53, 0x6ED7AB53, 0x36F3AB53, 0x06D7AB53, 0x22F3AB53, 0x3AD7AB53, 0xCEF4AB53, 0xDED8AB53, 0xFAF4AB53, 0xCAD8AB53, 0xE6F4AB53, 0xEAD8AB53, 0x92F4AB53, 0x8AD8AB53, 0xBEF4AB53, 0xBED8AB53, 0xAAF4AB53, 0x5ED8AB53, 0x56F4AB53, 0x52D8AB53, 0x42F4AB53, 0x76D8AB53, 0x6EF4AB53, 0x1ED8AB53, 0x1AF4AB53, 0x0AD8AB53, 0x06F4AB53, 0x36D8AB53, 0x32F4AB53, 0xDAD9AB53, 0xDEF5AB53, 0xFED9AB53, 0xCAF5AB53, 0xE2D9AB53, 0xF6F5AB53, 0x96D9AB53, 0xE2F5AB53, 0x86D9AB53, 0x8EF5AB53, 0xB6D9AB53, 0xBAF5AB53, 0xA6D9AB53, 0xA6F5AB53, 0x4ED9AB53, 0x52F5AB53, 0x42D9AB53, 0x7EF5AB53, 0x6AD9AB53, 0x6AF5AB53, 0x02D9AB53, 0x16F5AB53, 0x32D9AB53, 0x02F5AB53, 0xD6DAAB53, 0x2EF5AB53, 0xCADAAB53, 0xDAF6AB53, 0xF2DAAB53, 0xC6F6AB53, 0x96DAAB53, 0xF2F6AB53, 0x8ADAAB53, 0x9EF6AB53, 0xB2DAAB53, 0x8AF6AB53, 0x56DAAB53, 0xB6F6AB53, 0x7EDAAB53, 0xA2F6AB53, 0x3EDAAB53, 0x4EF6AB53, 0x26DAAB53, 0x7AF6AB53, 0xCEDBAB53, 0x66F6AB53, 0xE2DBAB53, 0x12F6AB53, 0x8ADBAB53, 0x3EF6AB53, 0x46DBAB53, 0x2AF6AB53, 0x7ADBAB53, 0xD6F7AB53, 0x62DBAB53, 0xC2F7AB53, 0x16DBAB53, 0xEEF7AB53, 0x3EDBAB53, 0x9AF7AB53, 0x26DBAB53, 0x86F7AB53, 0xFEDCAB53, 0xB2F7AB53, 0xEEDCAB53, 0x5EF7AB53, 0x86DCAB53, 0x4AF7AB53, 0x46DCAB53, 0x76F7AB53, 0x66DCAB53, 0x62F7AB53, 0x26DCAB53, 0x0EF7AB53, 0xC2DDAB53, 0x3AF7AB53, 0xE2DDAB53, 0x26F7AB53, 0x82DDAB53, 0xD2F8AB53, 0xAEDDAB53, 0xFEF8AB53, 0x5EDDAB53, 0xEAF8AB53, 0x7EDDAB53, 0x96F8AB53, 0x12DDAB53, 0x82F8AB53, 0x06DDAB53, 0xAEF8AB53, 0xDADEAB53, 0x5AF8AB53, 0xC2DEAB53, 0x46F8AB53, 0xF6DEAB53, 0x72F8AB53, 0x8ADEAB53, 0x1EF8AB53, 0x42DEAB53, 0x0AF8AB53, 0x66DEAB53, 0x36F8AB53, 0x06DEAB53, 0x22F8AB53, 0xD2DFAB53, 0xCEF9AB53, 0xFEDFAB53, 0xFAF9AB53, 0x8EDFAB53, 0xE6F9AB53, 0xAADFAB53, 0x92F9AB53, 0x4ADFAB53, 0xBEF9AB53, 0x66DFAB53, 0xAAF9AB53, 0x3EDFAB53, 0x56F9AB53, 0xD6E0AB53, 0x42F9AB53, 0xC2E0AB53, 0x6EF9AB53, 0x9EE0AB53, 0x1AF9AB53, 0xA6E0AB53, 0x06F9AB53, 0x62E0AB53, 0x32F9AB53, 0x06E0AB53, 0xDEFAAB53, 0xD6E1AB53, 0xCAFAAB53, 0x9AE1AB53, 0xF6FAAB53, 0xBEE1AB53, 0xE2FAAB53, 0x4AE1AB53, 0x8EFAAB53, 0x6EE1AB53, 0xBAFAAB53, 0x0AE1AB53, 0xA6FAAB53, 0xCAE2AB53, 0x52FAAB53, 0xF6E2AB53, 0x7EFAAB53, 0xE6E2AB53, 0x6AFAAB53, 0x96E2AB53, 0x16FAAB53, 0x86E2AB53, 0x02FAAB53, 0x56E2AB53, 0x2EFAAB53, 0x62E2AB53, 0xDAFBAB53, 0x3EE2AB53, 0xC6FBAB53, 0xEAE3AB53, 0xF2FBAB53, 0x8AE3AB53, 0x9EFBAB53, 0xA2E3AB53, 0x8AFBAB53, 0x52E3AB53, 0xB6FBAB53, 0x7EE3AB53, 0xA2FBAB53, 0x6AE3AB53, 0x4EFBAB53, 0x06E3AB53, 0x7AFBAB53, 0x32E3AB53, 0x66FBAB53, 0xDEE4AB53, 0x12FBAB53, 0xFAE4AB53, 0x3EFBAB53, 0xE6E4AB53, 0x2AFBAB53, 0x92E4AB53, 0xD6FCAB53, 0xAEE4AB53, 0xC2FCAB53, 0x5EE4AB53, 0xEEFCAB53, 0x42E4AB53, 0x9AFCAB53, 0x12E4AB53, 0x86FCAB53, 0x02E4AB53, 0xB2FCAB53, 0xDEE5AB53, 0x5EFCAB53, 0xEAE5AB53, 0x4AFCAB53, 0x9EE5AB53, 0x76FCAB53, 0xAAE5AB53, 0x62FCAB53, 0x5EE5AB53, 0x0EFCAB53, 0x4EE5AB53, 0x3AFCAB53, 0x3EE5AB53, 0x26FCAB53, 0xDEE6AB53, 0xD2FDAB53, 0x00000000, 0x00000000, 0x2B8A463B, 0x009F369D]
orig_list = []
for i in xored_list:
orig_list.append(swap32(i))

def norm(epc, addr):
if epc == addr:
return 0
elif epc < addr:
return -1
else:
return 1
base = 0x80028000
base2 = 0xbfc20000
def convert(addr):
first = addr2000[0]
tmp1 = 0
tmp2 = 0
while addr2000[1] != first:
tt = addr2800[(first-base)//4]
r = norm(addr, orig_list[(tt-base2)//4])
if r == 1:
first = addr2800[(first+0xC-base)//4]
if first == addr2000[1]:
break
else:
if tmp1 !=0:
break
elif r==-1:
first = addr2800[(first+8-base)//4]
if first == addr2000[1]:
break
else:
if tmp1 !=0:
break
elif r==0:
tmp1 = 1
tmp2 = addr2800[(first - base)//4]
if first == addr2000[1]:
break
else:
if tmp1 !=0:
break

return tmp2

def get_address(inaddr):
xored = inaddr ^ x
outaddr = xored
r1 = convert(xored)
if r1 != 0:
outaddr = orig_list[(r1 + 4 - base2)//4]
return outaddr ^ x

mp = {}
mpr = {}

for i in range(0x8000):
addr = 0x80000000 + i
outaddr = get_address(addr)
mp[addr] = outaddr
mpr[outaddr] = addr
#print(f"{hex(addr)} => {hex(outaddr)}")

print(hex(mp[0x80000D50]))

这里 2800.dump 就是内存中 0x80002800 开始长度为 0x1000 的内存。

有了这个程序之后,我们就可以来还原程序中的控制流了,比如刚才 loc_80000D50 处的死循环实际上经过一次中断后会到达 0x80002e34 的位置开始下一段逻辑。整个程序基本上都是这样由无数个小片段来构成的,稍微花点功夫就可以人工把原控制流拼接起来。

定位验证逻辑

之后的难点是怎么定位输入验证逻辑。这里我的方法是首先在内存里搜索输入的字符串,比如 flagPPPPP,可以搜索 0x5050 (PP的ASCII)

1
2
3
4
5
pwndbg> find/h 0x80000000, 0x80010000, 0x5050
0x8000880a
0x8000880b
0x8000880c
3 patterns found.

实际上如果从 0x80008000 开始 dump 可以看到之前的输入,比如

1
2
3
4
5
6
7
8
pwndbg> hexdump 0x80008000 0x1000
+0000 0x80008000 00 00 18 04 31 32 33 34 35 36 00 33 34 35 36 7d │....│1234│56.3│456}│
+0010 0x80008010 0a 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 │....│....│....│....│
+0020 0x80008020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 │....│....│....│....│
...
+0800 0x80008800 00 00 00 00 66 6c 61 67 50 7f 50 50 50 50 0a 00 │....│flag│P.PP│PP..│
+0810 0x80008810 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 │....│....│....│....│
...

也就是说第一次输入是在 0x80008004,第二次是在 0x80008804,然后以此类推。接下来重启程序,在输入 flag 之前在 0x80008004 下一个读取断点,输入一个字符会立即触发,简单跟踪逻辑可以发现是遇到 \n 的话就停止处理进入验证逻辑,接下来就是跟踪 flag 如何被验证的了。

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
61
62
63
64
65
66
67
68
69
70
ROM:8000081C ImportantStart:                          # CODE XREF: ROM:80002EE8↓p
ROM:8000081C
ROM:8000081C var_38 = -0x38
ROM:8000081C var_8 = -8
ROM:8000081C var_4 = -4
ROM:8000081C
ROM:8000081C addiu $sp, -0x48
ROM:80000820 sw $ra, 0x48+var_4($sp)
ROM:80000824 sw $fp, 0x48+var_8($sp)
ROM:80000828 move $fp, $sp
ROM:8000082C sw $zero, 0x48+var_38($fp) # 0x800026ef
ROM:80000830 mtc0 $zero, Count # Timer Count
ROM:80000834
ROM:80000834 loc_80000834: # CODE XREF: ImportantStart:loc_80000834↓j
ROM:80000834 b loc_80000834 # next: 0x80002ab0

ROM:80002AB0 b loc_80000C9C
ROM:80002AB4 nop

ROM:80000C9C loc_80000C9C: # CODE XREF: ROM:80002AB0↓j
ROM:80000C9C # ROM:80002B28↓j ...
ROM:80000C9C lw $v0, 0x10($fp)
ROM:80000CA0 mtc0 $zero, Count # Timer Count
ROM:80000CA4
ROM:80000CA4 loc_80000CA4: # CODE XREF: ROM:loc_80000CA4↓j
ROM:80000CA4 b loc_80000CA4 # next: 0x80002df8

ROM:80002DF8 beqz $v0, loc_8000083C
ROM:80002DFC nop
ROM:8000083C loc_8000083C: # CODE XREF: ROM:80002DF8↓j
ROM:8000083C la $v0, word_80003D24
ROM:80000844 lw $v0, 8($v0) # flag code 0x80003d40
ROM:80000848 lhu $v0, 0($v0)
ROM:8000084C sh $v0, 0x14($fp)
ROM:80000850 la $v0, word_80003D24
ROM:80000858 lw $v0, 8($v0)
ROM:8000085C addiu $v1, $v0, 2
ROM:80000860 la $v0, word_80003D24
ROM:80000868 sw $v1, 8($v0)
ROM:8000086C lhu $v0, 0x14($fp)
ROM:80000870 sltiu $v1, $v0, 0xE
ROM:80000874 mtc0 $zero, Count # Timer Count
ROM:80000878
ROM:80000878 loc_80000878: # CODE XREF: ROM:loc_80000878↓j
ROM:80000878 b loc_80000878 # next: 0x80002ac4

ROM:80002AC4 beqz $v1, loc_80000C98
ROM:80002AC8 nop
ROM:80002ACC b loc_8000087C
ROM:80002AD0 nop
ROM:8000087C loc_8000087C: # CODE XREF: ROM:80002ACC↓j
ROM:8000087C nop
ROM:80000880 sll $v1, $v0, 2 # v0 selection
ROM:80000884 la $v0, dword_80002690
ROM:8000088C addu $v0, $v1, $v0
ROM:80000890 lw $v0, 0($v0)
ROM:80000894 mtc0 $zero, Count # Timer Count
ROM:80000898
ROM:80000898 loc_80000898: # CODE XREF: ROM:loc_80000898↓j
ROM:80000898 b loc_80000898 # next: 0x80002ad8

ROM:80002AD8 jr $v0
ROM:80002ADC nop
ROM:80002E00 b goflagdivide
ROM:80002E04 nop

ROM:80002690 dword_80002690: .word 0x800008A0, 0x800008EC, 0x80000938, 0x80000980, 0x800009CC
ROM:80002690 # DATA XREF: ROM:80000884↑o
ROM:80002690 .word 0x80000A1C, 0x80000A70, 0x80000AFC, 0x80000B88, 0x80000BD0
ROM:80002690 .word 0x80000C20, 0x80000C48, 0x80000C6C, 0x80000C84

这里最后的 jr $v0 实际上是从 0x80002690 的 14 个子程序中选择了一个跳转,我卡了一段时间才反应过来是虚拟机,继续观察每个子程序的逻辑可以观察到这个虚拟机的信息:

  • 0x80003d38 是 PC 计数器,虚拟机代码区起点为 0x80003d40
  • 栈区为 0x80018000-0x80020000,没有寄存器,0x80003d24 存储验证结果,0x80003d3c 是用户输入(不包含flag{}
  • 0x80000710 处的函数弹出栈顶的值。
  • 0x80000794 处的函数把值压入栈顶。

简单的逆向后可以编写脚本生成虚拟机伪代码。

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
import struct

orig = [0x10260080, 0x00000000, 0x00000000, 0x00000000, 0x00800180, 0x00000280, 0x00800180, 0x00000000, 0x1D090900, 0x00000900, 0x05000A00, 0x14000600, 0x01000900, 0x01000A00, 0x08000B00, 0x01000900, 0xE0FF0600, 0x11000900, 0x09000200, 0x030048B2, 0xA9720900, 0x07000500, 0x09004401, 0x02001100, 0x48B20900, 0x09000300, 0x05007E09, 0x2E010700, 0x11000900, 0x09000200, 0x030048B2, 0x60550900, 0x07000500, 0x09001801, 0x02001100, 0x48B20900, 0x09000300, 0x0500A14C, 0x02010700, 0x11000900, 0x09000200, 0x030048B2, 0x37000900, 0x07000500, 0x0900EC00, 0x02001100, 0x48B20900, 0x09000300, 0x050071AA, 0xD6000700, 0x11000900, 0x09000200, 0x030048B2, 0x2C120900, 0x07000500, 0x0900C000, 0x02001100, 0x48B20900, 0x09000300, 0x05003645, 0xAA000700, 0x11000900, 0x09000200, 0x030048B2, 0xE8110900, 0x07000500, 0x09009400, 0x02001100, 0x48B20900, 0x09000300, 0x05004712, 0x7E000700, 0x11000900, 0x09000200, 0x030048B2, 0xC7760900, 0x07000500, 0x09006800, 0x02001100, 0x48B20900, 0x09000300, 0x05006D09, 0x52000700, 0x11000900, 0x09000200, 0x030048B2, 0x2C120900, 0x07000500, 0x09003C00, 0x02001100, 0x48B20900, 0x09000300, 0x0500CB87, 0x26000700, 0x11000900, 0x09000200, 0x030048B2, 0xE4090900, 0x07000500, 0x09001000, 0x07001D09, 0x09000800, 0x0B000000, 0x09000D00, 0x0B000100, 0xDDE40D00, 0x6C6C7CAC, 0xD8B41BC8, 0xBAD7365B, 0x183F502C, 0xA1D248EF, 0xFF5A2F91, 0xA868CC67, 0xE48978A1, 0x54B968BF, 0x0065E01D, 0x0A0E4574, 0x6AE27932, 0x3B337A5A, 0x70A5812A, 0x66178002, 0xF7A600A2, 0x2097FD67, 0x69BD7DB9, 0xC3BC4229, 0xCBDB5CB8, 0x491B2CCB, 0xFD93B6D1, 0xD9735472, 0xC953C3EB, 0x44A8341C, 0x21F987F1, 0x163FECE8, 0x969EF4C6, 0x5422BDAA, 0x769F2DB0, 0x3A384F0E, 0xCE22439D, 0x1C845620, 0xE22838CE, 0x59E20579, 0x1FED8C37, 0x9CA5E63B, 0xC1D78E1E, 0x0B9DA21F, 0xADAAA050, 0x3B3FF509, 0xE5F0A239, 0x74ED3875, 0xF428491E, 0x045988C6, 0xCE79945F, 0x306D11E0, 0x8B5A4388, 0x0D2A4365, 0x06155428, 0x47D87921, 0x933CB9DB, 0x1AE5B43A, 0xEB9C3573, 0xB0A3CBB8, 0xA9E07C20, 0x376A7E60, 0x3AC87999, 0xFF22732E, 0x5AECA38D, 0x9080C504, 0xA63CA03B, 0x4A2D3659, 0x7CE34674, 0x6F42F442, 0x180E34CC, 0xBE6771C2, 0x8E93EE6D, 0xA00E7012, 0x98BFC4C1, 0x78A3A1FC, 0xF17FA9DD, 0xD85B22F0, 0x096CBB37, 0x01329A96, 0xF456E6BA, 0xB2696C61, 0xA0F3B059]

addr3d20 = [0 for i in range(len(orig))]

addr82000 = [0 for i in range(0x1000)]

addrflag = [0 for i in range(0x10)]

for i in range(0x10):
addrflag[i] = ord('a') + i

baseflag = 0x80008004

base2 = 0x80018000

base = 0x80003d20

fp = [0 for i in range(100)]


#https://stackoverflow.com/questions/27506474/how-to-byte-swap-a-32-bit-integer-in-python
def swap32(i):
return struct.unpack("<I", struct.pack(">I", i))[0]

def pp(l):
for i in l:
print(hex(i))

def pp2(l):
print(" ".join(map(hex, l)))

for i in range(len(addr3d20)):
addr3d20[i] = swap32(orig[i])

def sub_read():
global ptr
ptr -= 1
return area[ptr]

def write_add(a0):
global ptr, area
area[ptr] = a0
ptr += 1

def ss(v):
v = v + (v>>31)
return ((v>>1)<<1)

idx = 0x80003d40
ptr = 0
area = [0 for i in range(0x200)]

plus2 = set([
6,7,9
])

flag = "flag{123456789123456789}"
divide_flag = 9 # 0x80003d24 origin (len+1)/2
flagptr = 0
mdd= 0x3bbc9
cmds = {
0x9 : "push",
0xa : "push divide flag",
0x5 : "cmp",
0x6 : "jneqz",
0x7 : "jeqz",
0x1 : "sub",
0x0 : "add",
0x2 : "mul",
0x3 : "mod",
0x4 : "cmpless",
0x8 : "push flag",
0xB : "pop divide flag",
0xC : "subptr",
0xD : "end"
}

while True:
current_addr = idx
second = None
if (idx - base) % 4 == 0:
wd = addr3d20[(idx - base)//4]
else:
wd = ((addr3d20[(idx - base)//4] & 0xFFFF) << 16 ) + ((addr3d20[(idx - base)//4 + 1] & 0xFFFF0000) >> 16)
first = (wd & 0xFFFF000) >> 16
idx += 2
if first in plus2:
second = wd & 0xFFFF
if (first == 0x6 or first == 0x7) and second > 0x1000:
second = second - 0x10000
print(f"{hex(current_addr - 0x80003d40)} {hex(first)} {cmds[first]} {hex(second)}")
idx += 2
else:
print(f"{hex(current_addr - 0x80003d40)} {hex(first)} {cmds[first]}")
if first == 0xD:
break
elif first < 0xD:
if first == 0x9:
write_add(second)
elif first == 0xa:
write_add(divide_flag)
elif first == 0x5:
r = sub_read() ^ sub_read()
if r < 1:
r = 0
else:
r = 1
write_add(r)
elif first == 0x6:
if sub_read() == 0:
continue
#idx += ss(second)
elif first == 0x7:
if sub_read() != 0:
continue
#idx += ss(second)
elif first == 0x1:
r = sub_read() - sub_read()
write_add(r)

最后可以得到关键验证逻辑的伪代码

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
0x0 0x9 push 0x91d
0x4 0x9 push 0x0
0x8 0xa push divide flag
0xa 0x5 cmp
0xc 0x6 jneqz 0x14 => 0x24
0x10 0x9 push 0x1
0x14 0xa push divide flag
0x16 0x1 sub
0x18 0xb pop divide flag
0x1a 0x8 push flag
0x1c 0x9 push 0x1
0x20 0x6 jneqz -0x20 => 0x4
0x24 0x9 push 0x11
0x28 0x2 mul # mul last two bytes of flag
0x2a 0x9 push 0xb248
0x2e 0x3 mod # result % 0xb248
0x30 0x9 push 0x72a9
0x34 0x5 cmp
0x36 0x7 jeqz 0x144 => 0x17C fail
0x3a 0x9 push 0x11
0x3e 0x2 mul
0x40 0x9 push 0xb248
0x44 0x3 mod
0x46 0x9 push 0x97e
0x4a 0x5 cmp
0x4c 0x7 jeqz 0x12e
0x50 0x9 push 0x11
0x54 0x2 mul
0x56 0x9 push 0xb248
0x5a 0x3 mod
0x5c 0x9 push 0x5560
0x60 0x5 cmp
0x62 0x7 jeqz 0x118
0x66 0x9 push 0x11
0x6a 0x2 mul
0x6c 0x9 push 0xb248
0x70 0x3 mod
0x72 0x9 push 0x4ca1
0x76 0x5 cmp
0x78 0x7 jeqz 0x102
0x7c 0x9 push 0x11
0x80 0x2 mul
0x82 0x9 push 0xb248
0x86 0x3 mod
0x88 0x9 push 0x37
0x8c 0x5 cmp
0x8e 0x7 jeqz 0xec
0x92 0x9 push 0x11
0x96 0x2 mul
0x98 0x9 push 0xb248
0x9c 0x3 mod
0x9e 0x9 push 0xaa71
0xa2 0x5 cmp
0xa4 0x7 jeqz 0xd6
0xa8 0x9 push 0x11
0xac 0x2 mul
0xae 0x9 push 0xb248
0xb2 0x3 mod
0xb4 0x9 push 0x122c
0xb8 0x5 cmp
0xba 0x7 jeqz 0xc0
0xbe 0x9 push 0x11
0xc2 0x2 mul
0xc4 0x9 push 0xb248
0xc8 0x3 mod
0xca 0x9 push 0x4536
0xce 0x5 cmp
0xd0 0x7 jeqz 0xaa
0xd4 0x9 push 0x11
0xd8 0x2 mul
0xda 0x9 push 0xb248
0xde 0x3 mod
0xe0 0x9 push 0x11e8
0xe4 0x5 cmp
0xe6 0x7 jeqz 0x94
0xea 0x9 push 0x11
0xee 0x2 mul
0xf0 0x9 push 0xb248
0xf4 0x3 mod
0xf6 0x9 push 0x1247
0xfa 0x5 cmp
0xfc 0x7 jeqz 0x7e
0x100 0x9 push 0x11
0x104 0x2 mul
0x106 0x9 push 0xb248
0x10a 0x3 mod
0x10c 0x9 push 0x76c7
0x110 0x5 cmp
0x112 0x7 jeqz 0x68
0x116 0x9 push 0x11
0x11a 0x2 mul
0x11c 0x9 push 0xb248
0x120 0x3 mod
0x122 0x9 push 0x96d
0x126 0x5 cmp
0x128 0x7 jeqz 0x52
0x12c 0x9 push 0x11
0x130 0x2 mul
0x132 0x9 push 0xb248
0x136 0x3 mod
0x138 0x9 push 0x122c
0x13c 0x5 cmp
0x13e 0x7 jeqz 0x3c
0x142 0x9 push 0x11
0x146 0x2 mul
0x148 0x9 push 0xb248
0x14c 0x3 mod
0x14e 0x9 push 0x87cb
0x152 0x5 cmp
0x154 0x7 jeqz 0x26
0x158 0x9 push 0x11
0x15c 0x2 mul
0x15e 0x9 push 0xb248
0x162 0x3 mod
0x164 0x9 push 0x9e4
0x168 0x5 cmp
0x16a 0x7 jeqz 0x10
0x16e 0x9 push 0x91d
0x172 0x7 jeqz 0x8
0x176 0x9 push 0x0
0x17a 0xb pop divide flag
0x17c 0xd end

逻辑非常直白,读入所有用户输入后,倒着每两个字节和 0x11 相乘然后对 0x0b248 取模看是否等于特定的数值,写一个脚本直接算出 flag。

1
2
3
4
5
6
7
8
9
toequal = [0x72a9, 0x97e, 0x5560, 0x4ca1, 0x37, 0xaa71, 0x122c, 0x4536, 0x11e8, 0x1247, 0x76c7, 0x96d, 0x122c, 0x87cb, 0x9e4]
fl = ""
for eq in toequal:
for i in range(0xFFFF):
if i * 0x11 % 0xb248 == eq:
print(f"last two bytes: {struct.pack('>H', i)}")
fl = struct.pack('>H', i).decode("utf-8") + fl
break
print(fl)

得到 flag 为 flag{it's_time_to_pwn_this_machine!}

后记

一瓶可乐一包零食,一个逆向做一天。

不得不说这次 TCTF 2020 第一道逆向题就给我带来了非常大的惊喜浪费了我一天半的时间,帮我复习了一大波 mips 知识还有操作系统的基础知识,让我见识到了程序还能这样执行。如果有可能,我倒是更想知道这道题是怎么出的。 :)

上文中的脚本和IDA数据库均已经打包,见这里

Reference