bad mouse

题目给了一个usb接口的小电路板,插上之后发现是一个模拟的鼠标,这个鼠标挨个会画出flag的字符,然而画字符的速度是递减的,显然在比赛结束前是画不完的…

首先把题目给的固件转化为二进制形式,工具见hex2bin,之后用ida打开,处理器选择Atmel AVR,下面的型号选ATmega32或者其它的型号,可以将这个固件反汇编出来.

反汇编的代码中,开头处有明显一部分数据,这一段数据都介于0x3f到0x7f之间,很有可能是存放鼠标画出的图案的bitmap,经过一系列的变换后,对照鼠标画出的图案,可以正确构造出这个图像.

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
from PIL import Image

import binascii
import base64

a = '403F42614463554148694A6B4C4B4E4D5051614F5C53565558576059656B676D696F7141646379454C774E79507B4D5D706F72717473767578777A797C7B7E7D484F51724C43464548474E69555B575D595F58715453565558575A595C5B5E5D605F667D6C456E477049716D6C6B7D6B78517A537C557E7778774175447D463F4841464544434D415049524B544D5251504F594D5C555E57605961555C5B6D5B606362696473756368676A496C4B7D4B4070427274737A774059426B44654543403F42435245476748614A4F4C4B4E4959515B535955595158576179656B676D696F674164636C6571777379757B7D4D706F7E77774B76557B4F463F7C7B444949514A634D554C5148475067545D565F586159735453655360676269646B657D605F62636465756368696A6B6C6B746D793F7B417D43455578777A75457D473F4541457D444346415149534B514D5149504F59715C635E6560675E795C5B655568736A736C756D4568676A65756D776F7571756D74737C534049424B444D455F403F513F4C534E55505751694C4B524F58715A435C7D5D5B58576151646F666F6871694164636C717179724B757D7479706F784F7C457E474049415B7C7B445B48514A534C554D6748474A49545B5D7E584F5251545359755A6F5C5F6B59605D605F6267647B7545683F6A6F6C6B754D783F7A417C437A5578777A597C5B4D794061426344434C4551575359555B5D6D504F586F5C655E676069617B5C5B6D5B686F6A716C736D4568677047747D763F7841795374734676487849577C5B7E5D403F'
b = binascii.a2b_hex(a)
arr = [0]*64
for i in b:
arr[i - 0x3f] += 1
b = list(b)
for i in range(len(b)//2):
b[i*2], b[i*2+1] = b[i*2+1], b[i*2]
d = [(b[i] - ((i % 0x40)+0x3f)) % 0x40 for i in range(len(b))]
c = [(d[2*i+1] << 6)+d[2*i] for i in range(len(d)//2)]
c = c[:288]
size = 4
blk = Image.new('RGB', (size, size), (0, 0, 0))
img = Image.new('RGB', (6*48*size, 12*size), (255, 255, 255))
for i in range(len(c)):
for j in range(12):
if (c[i] & 1) == 1:
img.paste(blk, (i*size, j*size, (i+1)*size, (j+1)*size))
c[i] >>= 1
img.save('ms.png')
# SECCON{379eaX85bTa99c695b36855i4Ycfa5b5}

四.3

这个题目类型和线上赛的时候一样,给出了程序运行过程中所有分支语句的选择情况,要求给出一个能产生相同结果的输入,只不过程序变得更加复杂了.

这个程序是一个x86模拟器,可以执行一部分的x86指令.找到主程序中的while循环的位置后,就可以把题目中给的trace结果分成很多段,每段对应一条模拟的x86指令.

对x86的指令编码稍有了解的话就可以知道第一个字节是Opcode,第二个是ModR/M,x86指令有不带参数的,有带2个寄存器为参数的,有后面跟着一个立即数作参数的,若是不带参数的,则不会触发ModR/M字节的解析,而带2个寄存器为参数的指令,其ModR/M字节的解析相对简单,只需提取出寄存器的序号即可.找到ModR/M字节的解析函数,根据该函数中的分支语句的触发情况就可以推出模拟的x86指令的类型.

最终的结果如下所示:

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
 0:   b8 04 00 00 00          mov    eax,0x4
5: b9 00 00 00 00 mov ecx,0x0
a: 39 c1 cmp ecx,eax
c: 7f 00 jg 0xe
e: 01 d8 add eax,ebx
10: 90 nop
11: 90 nop
12: 39 c1 cmp ecx,eax
14: 7f 00 jg 0x16
16: 01 d8 add eax,ebx
18: 90 nop
19: 90 nop
1a: 39 c1 cmp ecx,eax
1c: 7f 00 jg 0x1e
1e: 01 d8 add eax,ebx
20: 90 nop
21: 90 nop
22: 39 c1 cmp ecx,eax
24: 7f 00 jg 0x26
26: 01 c1 add ecx,eax
28: 90 nop
29: 90 nop
2a: 39 c1 cmp ecx,eax
2c: 7f 00 jg 0x2e
2e: 01 c1 add ecx,eax
30: 90 nop
31: 90 nop
32: 39 c1 cmp ecx,eax
34: 7f 00 jg 0x36
36: f4 hlt

另外程序运行过程中对flags寄存器也有模拟,而其中每个位的置位和清除过程的分支选择不同,因此需要分析出每次运算指令之后的flags寄存器中CF、OF、SF的情况,最终拼凑出一个正确的取值.