【ARMpwn图文教程】从零开始带你玩转ARM架构下的Pwn,别再被栈溢出搞懵了!
嘿,兄弟们!今天咱们来聊一个很多人一听就头大的话题——ARMpwn图文教程,说实话,我自己刚开始接触ARM pwn的时候,那叫一个痛苦啊,寄存器名字记不住、指令集跟x86完全不一样、连个像样的教程都难找……但是别慌,今天我就把这些坑一个一个给你填平了!

为啥要学ARM pwn?
你可能会问:“x86的pwn我还没玩明白呢,学ARM干啥?” 唉,兄弟,这你就不懂了吧,现在IoT设备、路由器、手机、甚至很多嵌入式设备,全是ARM架构的,你想想,一个智能门锁、一个摄像头,里面跑的都是ARM的二进制,你要是不会ARM pwn,那不就等于少了一大块战场吗?而且CTF比赛里ARM pwn的题目也越来越多了,不学真的亏大了!
环境搭建,别一上来就卡住
首先咱们得把环境搞起来,我推荐用 qemu-user 来跑ARM的二进制,配合 gdb-multiarch 调试,Ubuntu下一条命令搞定:
sudo apt install qemu-user qemu-user-static gdb-multiarch gcc-arm-linux-gnueabi
对了,如果你要跑静态编译的ARM程序,记得用 qemu-arm ./binary 就行,动态的话稍微麻烦点,得指定 -L /usr/arm-linux-gnueabi,我第一次搞的时候忘了加这个参数,结果报了一堆找不到库的错误,折腾了半小时才发现……真的蠢哭了😂
ARM pwn的核心:寄存器和调用约定
好,接下来是重头戏,ARM和x86最大的区别就是——参数不放在栈上,而是放在寄存器里! 这个一定要记住,不然你连函数怎么传参的都搞不清楚。
ARM 32位下,前四个参数分别放在 r0、r1、r2、r3 里,超出的才压栈,函数返回值放在 r0,还有个特别的寄存器叫 lr(link register),也就是r14,它保存着函数返回地址,这一点跟x86把返回地址压栈完全不一样!
所以在ARM pwn里,你要控制PC(程序计数器,相当于x86的EIP),通常就是想办法覆盖 lr 或者直接劫持PC,栈溢出的思路还是一样的,只是细节变了。
一个简单的栈溢出例子
假设有这么一段ARM代码:
void vulnerable() {
char buf[64];
gets(buf);
}
编译成ARM之后,栈布局大概是这样的:buf占64字节,然后可能有一些padding,接着保存的lr就在附近,你只要输入足够长的字符,就能覆盖到lr,从而控制返回地址。
用gdb-multiarch调试的时候,先 qemu-arm -g 1234 ./vuln,然后gdb里 target remote localhost:1234 就能连上了,下个断点在vulnerable函数返回的地方,看看lr被覆盖成啥了,心里就有数了。
ROP才是ARM pwn的灵魂
光控制PC还不够,你得能执行任意代码啊,这时候就得靠 ROP(Return-Oriented Programming) 了,ARM里找gadget的工具推荐用 ROPgadget 或者 ropper,用法跟x86差不多:
ROPgadget --binary ./vuln --only "pop|bx"
不过要注意,ARM里常见的gadget是 pop {r0, pc} 这种,一次能弹好几个寄存器,特别爽,而且ARM有Thumb模式,指令是2字节对齐的,有时候地址要加1切换过去,这个坑我踩过好几次,调了半天才发现是Thumb的问题……
实战小技巧,都是我踩过的坑
- 字节序问题:ARM默认是小端,写payload的时候别忘了用
p32()打包地址。 - cache刷新:有时候你写shellcode到栈上,执行不了,可能是指令缓存没刷新,可以用
__clear_cache或者直接ROP算了。 - libc版本:ARM的libc跟x86不通用,别想着直接拿x86的偏移量套,老老实实用题目给的libc。
最后说两句
好啦,这篇 ARMpwn图文教程 就先写到这儿,其实ARM pwn没你想的那么可怕,核心思路跟x86是一样的,就是把寄存器搞明白、把gadget找对,剩下的就是多练,我当初也是从一脸懵逼到慢慢上手,相信我,你也可以的!
有啥问题欢迎留言交流,咱们一起进步!下次我再写一篇关于ARM64 pwn的文章,记得来看哦~