Packet & .new
Packet : {...}
花括号表示一instruction packet ,在 packet内,多条指令逻辑上并发,但有上限,硬件允许受限的依赖前递。
.new: 在packet标识需要用到的前递
rX.new:引用本 packet 内刚产生的 rX。
p0.new:引用本 packet 内刚产生的谓词 p0。
在 Hexagon 里,谓词 (predicate) 就是布尔寄存器,专门用来做“条件执行” 和 “条件跳转”。
比如
{ if (p0.new) jump:nt loc_2552C |
p0.new就是后面cmp获得的谓词。
注意packet里的两个指令是没有先后顺序的。
寄存器与寄存器对
- 通用寄存器:
r0-r31 - 谓词寄存器:
p0 - ? - 特殊寄存器:
sp(栈指针) 、lr(返回地址) 、pc、ugp(user global pointer) - 寄存器对:
rY:X表示64-bit值 (低 32 位通常在右侧寄存器rX)
内存访问指令
load/store
memw(addr): 32-bitmemh(addr): 16-bitmemb(addr): 8-bitmemub(addr): unsigned 8-bitmemd(addr): 64-bit load
通过等号可以很轻松的区分是load还是store
post-increment
$rY = mem^*(rA++ #imm)$
用C语言的形式来理解 非常像
rY = *rA++ |
r2 = memub(r1++ #1):读 *(uint8_t*)r1,然后 r1++
##imm / @pcrel(大立即数/重定位)
add(pc, ##sym@pcrel):得到符号地址(PC 相对重定位),类似 “取全局地址/常量池地址”。memw(r0 + ##-0x101EC):带大立即数偏移的寻址(常见于访问 GOT/全局区/固定偏移表)。
算数与逻辑
加减
add(rA , rB)/add(rA , #imm)sub(rA,rB)/sub(rA, #imm)neg(rX):rX = -rX
位运算
and \ or \ xor \not
形式和加减差不多
移位
asl(rX, #n): 算术左移lsr(rX, #n): 逻辑右移
其他
addasl(rA, rB, $n)
r13 = addasl(r1, r0, #1) ⇒ r13 = r1 + (r0 << 1)
mpyi(rX , #imm) : 乘立即数
比较、谓词与条件执行
比较生成谓词
p0 = cmp.eq(rA, rB/#imm):相等p0 = cmp.gt(rA, rB/#imm):有符号大于p0 = cmp.gtu(rA, rB/#imm):无符号大于
5.2 谓词化执行(Predication)
if (p0) insnif (!p0) insnif (p0.new):使用同 packet 内刚生成的 p0
位测试/置位/清位
tstbit(rX, #b):测试位 b(返回谓词/用于 p0)setbit(rX, #b):设置位 bclrbit(rX, #b):清除位 bbitsclr(rX, #mask_or_bit): 测试位是否为0
控制流
跳转
jump label:无条件跳转if (p0) jump:t label:条件跳转:t/:nt:分支形式/预测提示。对语义可先忽略,当作 jump。
6.2 调用
call func:直接调用(目标在指令里)callr rX:间接调用(函数指针)- 例:
r0 = memw(r17++ #4); callr r0:遍历函数指针表并调用
- 例:
6.3 返回/栈帧
allocframe(#imm):建立栈帧deallocframe:撤销栈帧dealloc_return:撤栈并返回(组合指令)jumpr lr:跳回返回地址(return)
硬件零开销循环
loop0(target, rN):设置 loop0,重复执行从当前位置到:endloop0的区间rN次,回跳目标为target:endloop0:循环结束边界标记(不是普通指令)
{ loop0(loc_219DC, r10) ... } |
通常相当于 for (i = 0; i < r10; i++) { ... }
原子/锁:memw_locked
r0 = memw_locked(addr):带 reservation 的 load(类似 load-linked)memw_locked(addr, p0) = rX:条件 store(类似 store-conditional),成功与否反映在谓词p0(或由汇编约定给出)
reservation: 是指 CPU 在执行一条“锁定加载” 时,建立的仅对于该线程有效的监视状态。如果立刻尝试“条件写回”同一个位置,并且在此期间没人改过它,就允许这次写回成功
trap0(#1)
用于进入内核 类似int8
lilac-ctf Gateway
这个题目就用到了 hexagon 架构。
交互逻辑中有输入的就只有输入ip。
尝试输入一个很长的内容
❯ ./qemu-hexagon pwn |
出现了bus error 应该是越界了
对于这种陌生的架构 IDA中可以通过定位字符串的方式定位函数。
找 “172.16.0.1:7777|Location Lookup Service”
很快就找到了 在 sub_20FD0:
重命名为register

可以猜测出在hexagon中应该是通过r0在传参数
这里面用了三个关键函数
有一个显然时puts
还有 剩下两个
可以用strace来跟踪系统调用进行猜测
这里我最初的判断是错误的 我猜测第一个是readinput
第二个是parse
后来看 xref 发现 几乎每次put后都跟着readinput 说明它应该不是readinput 可能是刷新缓冲区的操作。
那么后面那个就是readinput
前面一个指令r0 = add(fp, #-0x68) 中看,这样推测也更为合理。
然后就需要考虑hexagon的栈结构。搜索后发现和x86差不多。
可以写脚本先输入(0x6D)字符验证。
[*] Got EOF while reading in interactive |
果然直接bus error了
而如果是0x69 0x6a这样还能稍微撑一会 应该是栈被迁移的特征
show的时候会少显示一个字符 猜测是回车前的字符自动截断变成\x00
然后就是找gadget的事情了,这里我直接搬一个网上wp找的gedget
0x217e4: |
.text:000214F4 { r0 = r16 } |
栈的地址未知 可以通过栈迁移的方式 。
从程序的交互行为中可以推测出 应该是存储了输出到某个值
应该是存储到了bss段 这个逆向我实在看不懂
https://rocketma.dev/2026/01/27/gateway/ 提到了动调 有这个应该好调很多。
目前来看应该是存到了bss 开头位置
然后打ROP
