20220311-西湖论剑-IoTSecPartWriteUp

Wow, IoT! Just make some ez tasks.

Toolkits provided

开发板(海特实验室自研)*4(板子上有两颗CPU,标有AVR的题目是由不同于Linux的处理器执行的),TF卡,TF读卡器,USB-ISP转接器,USB-TTL转接器,FPC及FPC连接器*2,杜邦线*4,排线,micro-USB连接线*若干,还有开发板的与原理图,比赛现场提供电烙铁

image-20220318115057039

TASK1-Linux IoT

tqmm

题目只给了个ip地址,尝试扫描端口,发现开了下面四个端口。nc 9999,提示MQTT通信,并要求公布一串base64编码

image-20220311200713489

image-20220311200816422

不太懂,网上搜一波,发现最后只要作为服务端发送主题和这串base64编码,就可以在客户端收到flag

image-20220311201112434

TASK2-AVR MCU IoT(Aka 找串口

no more time

提供附件内容如下,F_CPU即AVR单片机CPU的时钟频率,其逻辑是若PD2为0则自减1万赫兹,否则不变

最直接的想法,连接板子,和程序交互,满足上述逻辑,便能得到某些东西。用串口调试工具连接UART调试串口,但提示no tf card,如下图所示。而这块板子是有两块CPU的,很明显该题目的程序没有运行起来,或者串口找的不对

1
2
3
4
5
6
7
8
9
这题直接输出flag。AVR时钟F_CPU存在下面逻辑:
for(;;){
if(!PD2){
F_CPU-=10000;
}
else{
F_CPU=F_CPU;
}
}

image-20220318113906959

根据开发板各元器件介绍,以及原理图,找比如RXD和TXD等这些输入输出串口。首先可以看到下图一右上方两个便是开发板上的micro口,分别是OTG和UART调试串口,结合下图一其他信息可以说有程序就是通过UART与外界交互的。接着观察微控制单元ATMEGA328P-MU如下图二,可以看到PD0、PD1分别对应RXD和TXD,可能是另外一个程序的输入输出,然后就可以看到附件中提到的PD2。综上,正确的思路应该是连接MCU的串口,!PD2也就是短接PD2使之逻辑上为1,从而获得flag

image-20220318135115527

image-20220318110923175

操作如下,为了方便先溜进隔壁的电子实验室给FPC连接器焊上排针,注意正反(给大家看看我焊的球🤣

然后按照原理图,用杜邦线连接FPC与USB-TTL转接器对应的接口,如下图二,其中PD2连PC6或PD7也没什么问题,接地就行

接着用FPC连接开发板MCU的串口以及FPC连接器,注意蓝条在上

最后就是连电脑用串口工具查看了,如下图三所示,注意:插,电源最后插;拔,接地最后拔(来自学长的教诲,好像串口也不可以乱插拔来着

image-20220318141810312

image-20220318150445998

image-20220318144730164

手动多次短接PD2引脚将flag打印出来,如下图所示

一些细节问题

  • 如果是乱码,说明波特率不对,换个波特率多试几次,这里和MCU的串口是9600,和UART是115200

  • 一般来说RXD连TXD,TXD连RXD,但好像也有反过来情况,不行的话尝试两者反接试试

  • 为什么还要连UART这边呢?USB-TTL上虽然写着电源,但这里并没什么软用,所以连UART是为了给开发板供电的。学长说为了保护MCU,TTL这里的电源最好也连上

image-20220318145057919

base64

比赛中的hint

  1. 逆向AVR固件
  2. radare2、simavr、avr-gdb
  3. 自定义base64字母表

需要的工具

image-20220420213431480


复现了上一题,我们就知道怎么和开发板上已有的程序进行交互了,同样是连PD0和PD1引脚看输出

在Ubuntu里,连接好设备后dev路径下会有两个tty,ttyACM0对应UART调试串口,ttyUSB0对应MCU的调试串口

image-20220318153935196

连接好,打开串口工具,并设置好参数,连接上之后点击开发板上的MCU-RST按钮,就可以和该题程序交互了。如下图所示,是一个变表base64编码,编码后的flag为

1
UoH+U/D/U92lgdLnWMZIR/nOVo2JU9VKOi==

image-20220318153237172

这里不仅仅是输出一个加密后的flag,我们也可以使用这个变表的base64进行编码,所以如果MCU里面程序可以接收字节流的话,其实是可以直接打印变化后的表的。只要发送下面这串

1
2
3
4
5
6
7
space = b''
for i in range(len(c) // 8):
t = hex(int(c[i * 8:(i + 1) * 8], 2))[2:]
x = len(t)
space += bytes.fromhex(t.rjust(x + (x % 2), '0'))
print(space)
# b'\x00\x10\x83\x10Q\x87 \x92\x8b0\xd3\x8fA\x14\x93QU\x97a\x96\x9bq\xd7\x9f\x82\x18\xa3\x92Y\xa7\xa2\x9a\xab\xb2\xdb\xaf\xc3\x1c\xb3\xd3]\xb7\xe3\x9e\xbb\xf3\xdf\xbf'

但尝试发送十六进制数据,失败收尾,毕竟还要看里面程序接受的是不是字节数组


这道题可以直接提取固件(没有锁死熔丝位啥的

1
$ avrdude -F -c usbasp -p atmega328p -U flash:r:base64.hex

(这个我虚拟机提不出来,换了fuck@Scr1pt的mac才提出来

提完固件,分析思路就和一般的二进制题差不多了,主要是相应工具的使用。对于iot的题目,用到的是题目最开始所列的那些工具

静态分析

strings

首先使用strings命令参看程序中硬编码的字符串

其中并没有变化后的base64表,推测其应该是程序运行时按照某顺序生成的

image-20220425083338925

radare2

使用radare2进行分析,radare2是逆向分析工具,和ida类似,但是是开源的

加载程序,-a参数设置程序架构

image-20220425084042957

a命令
  • aaaaa

    分析全部二进制代码,radare2为我们划分程序并命名

    image-20220425084626503

  • afl

    列出所有函数

    其中位于0x00000068处的entry0即我们现在所处的位置,就是整个程序的入口

    image-20220425084745753

p命令
  • pdf

    打出反汇编后的代码(太长了,不好看,不能f5反编译就折磨

  • pd @ <function name>

    查看某一函数的反汇编,比如entry0如下

    可以看到入口函数,call了一些函数,也可以用p命令查看

    image-20220425085427596

image-20220425085742912

之类的静态分析的常规操作

但纯汇编实在是太不友好。BTW上面这些用ida同样可以完成

image-20220425091213847

接着,结合re的一般流程,当然可以想到通过动调的方式来获取base64变表

动态分析

qemu-system-avr

模拟固件

1
$ qemu-system-avr -M uno -bios base64.hex -nographic

image-20220425090133113

使用ctrl a c命令进入qemu monitor模式,使用如下命令查看qemu模拟器中的内存映射

1
$ info mtree

image-20220425090319861

可以看到sram静态存储器的范围在0x0000000000800100-0x00000000008008ff之间

使用memsave指令将这段内存dump下来

1
$ memsave 0x800100 2048 1.bin

使用010editor查看,就能找到base64变表了

image-20220425091358734

总的来说,和fuck@Scr1pt的那道病毒题很像,壳难脱,也是dump内存来得快

最后解密就好了

1
2
3
4
5
6
7
8
9
10
import base64
import string

cipher = "UoH+U/D/U92lgdLnWMZIR/nOVo2JU9VKOi=="

space2 = "a0b1c2d3e4f5g6h7i8j9ZYXWVUTSRQPON+klmnopqrABCDEFGHIJKLM/stuvwxyz"
space1 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"

print (base64.b64decode(cipher.translate(str.maketrans(space2,space1))))
# flag{we1c0me_e2sy_base64}

滴滴答

附件提示如下

1
2
3
4
5
6
7
单片机实现摩斯加密过程,已知在处理高汉明重量时消耗的时间较长,串口输出为计数器Count日志,请输出明文。t = 1000000 * Count / 16000000)

data < -(k0, k1, k2...k6); r < -("_", ".")
for i < - 0 to 6
MOSI_enc(r, data[i])
delay 350ms
end

连接板子后的输出结果如下图所示

提示说处理高汉明重量时消耗的时间较长,这里科普一下

  • 汉明距离:两个等长的字符串对应位置上的字符不同的个数
  • 汉明重量:特殊的汉明距离,即一个字符串中非零的字符个数

然后说是hangming(“.”) = 4,hangming(“_”) = 6,但其实就算不知道,也就只有两种情况,都试一下就行

image-20220318205355423

用输出的Count计算t,转成图表更加直观

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import matplotlib.pyplot as plt
import numpy as np

c = [0x186eb4, 0x3c07e0, 0x780a7e, 0xb40cc0, 0x12d57b4, 0x1517299, 0x1cbfe4a, 0x1ef1979, 0x22c9dbe, 0x2682234,
0x2a5a699, 0x2e12b30, 0x373202b, 0x3b0a472, 0x3d4bf5c, 0x4114394, 0x4355e97, 0x4c75405, 0x503d87f, 0x5405c92,
0x57ce0e3, 0x5b96552, 0x5f6e974, 0x688de76, 0x6c46308, 0x701e73e, 0x77a7310, 0x79f8e07]

for i in range(len(c)):
c[i] = 1000000 * c[i] / 16000000

print(c)
x = np.array(c)
y = np.array([0] * len(c))
plt.scatter(x, y, color='red', s=10)
plt.show()

image-20220420204323278

其中间隔最大的显然就是间隔符,间隔中等和间隔最小的要么是_.,得到如下

1
.-- . .---- -.-. ----- -- .
1
2
3
4
5
6
7
8
9
10
11
12
13
14
from exploit.Morse import morse

c = '.-- . .---- -.-. ----- -- .'
morse(c, ' ')
print()
c = list(c)
for i in range(len(c)):
if c[i] == '.':
c[i] = '-'
elif c[i] == '-':
c[i] = '.'
morse(''.join(c), ' ')
# WE1C0ME
# DT6 5IT

(不要傻的(是本人)拿右边的进度做文章,然后得到了shit

1
2
3
4
c = [0, 3, 7, 11, 14, 18, 22, 25, 29, 33, 37, 40, 44, 48, 51, 55, 59, 62, 66, 70, 74, 77, 81, 85, 88, 92, 96, 100]
for i in range(len(c) - 1):
print(c[i + 1] - c[i], end=' ')
# 3 4 4 3 4 4 3 4 4 4 3 4 4 3 4 4 3 4 4 4 3 4 4 3 4 4 4

Reference

  1. 海特实验室wp
  2. u神wp-no more time
  3. ChaMd5团队wp
  4. https://www.waveshare.net/w/upload/DVD_AVR8_CN/data/product/USB_AVR_ISP_XPII_PG-2-3.htm

something else