npointer-public-misc-1

空指针MISC第一场公开赛

模拟信号部分挺有意思,之后的固件打包什么的就很无聊

首先是阅读生成模拟信号的代码:

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
def main():
# For simplification, we only use the physical layer in this challenge, some encoding process is removed as well.
# So you only need to consider the modulate process instead of stuggling with complex message structure.
# Write your own receiver to get the firmware. Enjoy :)
with open('firmware.img', 'rb') as f:
data = f.read()
#assert hashlib.md5(data).hexdigest() == '85b3ddc0a5b1aa36bcc8397f59616934'
hint = b"A message to let you know that you have successfully synchronized."
message = hint + data
tx = Sender(device=1)
tx.start()
tx.send(message)

@classmethod
def _payload2signal(klass, payload):
payload = np.frombuffer(payload, dtype=np.uint8)
payload_header = np.frombuffer(rs_codec.encode(
bytes([(len(payload) >> 8) & 0xff, len(payload) & 0xff])), dtype=np.uint8)
crc = convi2b(binascii.crc32(bytes(payload)) & 0xffffffff, 4)
data = np.concatenate((payload_header, payload, crc))
# print(data)
modulated_data = np.concatenate(
[klass._modulate(klass._encode(b)) for b in data])
modulated_data = np.concatenate(
(PREAMBLE, modulated_data, np.zeros(8)))
# print('after modulation:', len(payload), len(modulated_data))
return np.array(modulated_data, dtype=np.float32)

@staticmethod
def _modulate(encoded):
# return LUT_MOD_5[encoded]
return LUT_MOD[encoded]

可以看到在固件的前面加上了字符串的hint,然后被_payload2signal处理。加上payload_headercrc之后利用LUT_MOD进行替换然后末尾再加8个0,最后写入wav文件。在从文件读出的时候发现数据偏差挺大的,幸好LUT_MOD表示8个比特,每个比特用6个信号标识。

1
2
3
4
5
6
7
8
FRAMECNT = 6
SAMPLESIZE = FRAMECNT
DURATION = 0.000125

SIG_HI = np.sin(2 * np.pi * FREQ_HI * np.linspace(0,
DURATION, FRAMECNT, endpoint=False))
SIG_LO = -1 * np.sin(2 * np.pi * FREQ_HI * np.linspace(0,
DURATION, FRAMECNT, endpoint=False))

对于360度的圆周分两半,30度为步进,生成6个值,存在很明显的正负关系,这个级别的哪怕有误差也能识别。所以从后向前恢复,根据hint找到开头,再用md5哈希暴力出尾部即可。

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
import soundfile as sf
import binascii
import numpy as np
from constants import LUT_MOD, PREAMBLE, SAMPLERATE
import hashlib

def convi2b(x, bts):
return np.array([(x >> (8*(bts-i-1))) & 0xff for i in range(bts)], dtype=np.uint8)

data, samplerate = sf.read('./misc.wav')

def is_hi(x):
return x[1] > 0 and x[2] > 0 and x[4] < 0 and x[5] < 0

def rev(block48):
tmp = []
for i in range(8):
tmp.append(block48[i*6:(i+1)*6])
tmp = list(map(lambda x: 1 if is_hi(x) else 0, tmp))
tmp = int('0b'+"".join(list(map(str,tmp))),2)
return tmp

i = data.size - 8
das = []

while i > 48:
tmp = data[i-48:i]
das.append(rev(tmp))
i -= 48

das = das[::-1]
das = bytes([*das])
indicate = b"A message to let you know that you have successfully synchronized."
das = das[das.find(indicate)+len(indicate):]

'''
print(das[:-4])
print(len(das))
'''
while len(das) > 0:
print(len(das))
if hashlib.md5(das).hexdigest() == '85b3ddc0a5b1aa36bcc8397f59616934':
break
das = das[:-1]

print(das)

with open('firmware2.img','wb') as f:
f.write(das)

之后得到被打包过的固件,查找到这个工具。成功解压就可以了。

之后按照XML脱离出ELF文件和none文件

1
2
3
4
5
6
7
8
9
10
11
12
13
<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
<Firmware version="1.00" name="CTF Firmware">
<Chunk compressed="true">
<Start-offset>00000000</Start-offset>>
<End-offset>00000B80</End-offset>
<Algorithm>gzip</Algorithm>
</Chunk>
<Chunk compressed="false">
<Start-offset>00000B81</Start-offset>
<End-offset>00000C5B</End-offset>
<Algorithm>none</Algorithm>
</Chunk>
</Firmware>

开始逆向:

这是main函数,检查0xdeadbeaf之后读到3次检查开始执行

利用RC4解密之后调用loadChunk,看看明文就行了。函数,变量全改好名了,RC4也非常标准,就这样。