分析
这道题目的目的是使用ROP调用system函数,并设置参数为字符串/bin/cat flags
的地址。总的来说,就是要了解armel的函数传参方式,然后调用相应的gadget。
分析pwnme函数,局部变量s的大小是36,但是可以传入0x60个字节,因此存在缓冲区溢出。
1 | int pwnme() |
这个地方再说一下armel下的函数栈帧开辟和释放流程吧:
在函数初始化阶段,先将R11和LR两个寄存器保存到栈上,然后将SP+4的大小保存到R11上,最后SP自减0x20来开辟了栈帧。
R11也叫做FP(Frame Pointer)寄存器,类似于x86下的EBP寄存器,用于和SP一起界定函数栈栈的边界。FP寄存器就是指向栈底,SP寄存器就是指向栈顶。
pwnme函数是在main函数中通过BL指令跳转执行,BL指令会将其下一条指令作为pwnme函数的返回地址保存到LR寄存器中。因此在pwnme函数中保存到栈上的LR寄存器值就是需要通过缓冲区溢出进行修改的关键数据。
1 | .text:00010570 PUSH {R11,LR} |
在pwnme函数的结尾,会将R11+4的值赋值给SP,来做栈平衡。函数返回地址的跳转是直接通过POP指令将栈上的函数返回地址恢复到PC中。
1 | .text:000105C0 SUB SP, R11, #4 |
和mipsel架构不同,armel的SP寄存器在函数的生命周期里面都是变化的,因此如果要定位、计算栈上变量的位置,可以用FP寄存器来计算。pwnme函数的局部变量s起始地址为fp-0x24,返回地址保存在fp,因此需要0x24个字节填充后,覆盖返回地址。
利用
经过观察,发现armel下的split不像mipsel,有UsefulGadgets可以直接使用。因此,需要自己去找gadgets了。
调用system函数需要知道system函数地址和传入的字符串,二者都在程序中。那么难点就是如何去传参。
- 需要从栈上将字符串设置到寄存器R0
- 需要从栈上将system函数的地址设置到PC寄存器上
使用ROPGadget没有找到直接从栈上POP数据到R0的指令,因此考虑先将字符串地址从栈上取出到另外一个寄存器,然后再mov到R0上。只找到如下的一条:
1 | # ROPgadget --binary split_armv5 | grep "mov" | grep "r0" |
那么继续找可以从栈上恢复数据到R3寄存器的指令,挺多的,找了如下的两条做参考:
1 | # ROPgadget --binary split_armv5 | grep "pop" | grep "r3" |
ROP编写如下:
1 | rop = b"A" * 0x24 |
简单补充一下armel下批量POP的话,是先POP到序号小的寄存器。
EXP
1 | from pwn import * |
利用结果如下:
1 | [*] '/home/utest/rop_practice/armv5/split_armv5/split_armv5' |