LKM-Rootkit Summary
<!--kg-card-begin: markdown--><h1 id="">引</h1>
<p>LKM 是 Linux Kernel Module 的缩写，所以这里 LKM-Rootkit 的含义，是&quot;基于Linux内核模块实现的 Rootkit&quot;。由于前段时间分析了skidmap的细节，所以想到针对性的梳理一下这方面的内容。在集中精力读了一些开源代码后，整理一些手册性质的文字记录下来，供他人日后查阅。</p>
<h1 id="">开源项目参考</h1>
<p>本节项目主要来源为 <a href="https://github.com/milabs/awesome-linux-rootkits">awesome-linux-rootkits</a> 中提到的 LKM 相关的项目。</p>
<p>Rootkit 作为一个隐藏工具，其主要设计目标是为了隐藏攻击痕迹，本节将对开源LKM进行一个汇总梳理。每个项目中，可能包含多个实现细节，通过编号（SK-X.X）对应到知识点汇总章节中。</p>
<h2 id="rooty">rooty</h2>
<p><strong>项目地址</strong>: <a href="https://github.com/jermeyyy/rooty">https://github.com/jermeyyy/rooty</a></p>
<p><strong>模块自身隐藏</strong>： SK-5.1 加 SK-5.2 实现内核模块的隐藏；</p>
<p><strong>本地交互</strong>： 利用 SK-1.3 向内核模块发送控制指令；利用 SK-3.1 筛选 ioctl 命令；</p>
<p><strong>远程后门交互</strong>： 参考 SK-1.2;</p>
<p><strong>获取root权限</strong>：参考 SK-9.1 ；</p>
<p><strong>SHELL后门</strong>： 功能实现参考 SK-10.2 启动了一个 sshd 任务处理shell 交互；</p>
<p><strong>VNC后门</strong>：参考 SK-10.2 启动了一个 vnc 服务。</p>
<p><strong>文件/目录隐藏</strong>：参考 SK-3.1 和 SK-4.1 更改根目录(&quot;/&quot;)的迭代器;</p>
<p><strong>进程隐藏</strong>： 参考 SK-3.1 和 SK-7.3;</p>
<p><strong>网络会话隐藏</strong>：参考 SK-6.1； HOOK 参考 SK-3.1；</p>
<p><strong>键盘记录</strong>：利用 SK-8.1 实现键盘记录，并将结果记录到一个指定文件。</p>
<h2 id="reptile">Reptile</h2>
<p><strong>项目地址</strong>: <a href="https://github.com/f0rb1dd3n/Reptile">https://github.com/f0rb1dd3n/Reptile</a></p>
<p><strong>本地交互</strong>: 利用 SK-1.3 向内核发送控制指令；利用 SK-3.1 筛选 ioctl 命令；</p>
<p><strong>远程后门交互</strong>： 参考 SK-1.4；</p>
<p><strong>模块自身隐藏</strong>： 参考 SK-5.1；</p>
<p><strong>获取root权限</strong>：参考 SK-9.1；</p>
<p><strong>网络会话隐藏</strong>：参考 SK-6.1；HOOK参考 SK-3.4；</p>
<p><strong>进程隐藏</strong>：参考 SK-7.1；HOOK参考 SK-3.4；</p>
<p><strong>目录隐藏</strong>：参考 SK-4.2；HOOK参考 SK-3.4；</p>
<p><strong>文件隐藏</strong>：参考 SK-4.3；HOOK参考 SK-3.4；</p>
<h2 id="rkduck">rkduck</h2>
<p><strong>项目地址</strong>：<a href="https://github.com/QuokkaLight/rkduck">https://github.com/QuokkaLight/rkduck</a></p>
<p><strong>本地交互</strong>：rkduck提供了一个控制程序，user-kernel的通讯方式参考 SK-1.1;</p>
<p><strong>SHELL后门</strong>：交互协议参考 SK-1.2； 功能实现参考 SK-10.1;</p>
<p><strong>文件/目录隐藏</strong>： 利用 SK-3.1 和 SK-4.1 更改根目录(&quot;/&quot;)迭代器;</p>
<p><strong>进程隐藏</strong>： 参考 SK-3.1 和 SK-7.3;</p>
<p><strong>SSH后门</strong>：参考 SK-10.1 植入公钥到 <code>/root/.ssh/authorized_keys</code> 文件;</p>
<p><strong>键盘记录</strong>： 利用 SK-8.1实现键盘记录，并利用 SK-10.1 执行 <code>scp</code> 命令 将记录结果回传攻击者;</p>
<h2 id="lkmrootkit">lkm-rootkit</h2>
<p><strong>项目地址</strong>： <a href="https://github.com/croemheld/lkm-rootkit">https://github.com/croemheld/lkm-rootkit</a></p>
<p><strong>模块自身隐藏</strong>： 参考 SK-5.1 和 SK-5.3;</p>
<p><strong>远程后门交互</strong>： 参考 SK-10.2 启动一个UDP监听服务线程，接受从网络发来的控制指令；</p>
<p><strong>文件/目录隐藏</strong>： 参考 SK-2.3 找到 syscall_table 位置，参考 SK-4.4 隐藏文件；</p>
<p><strong>键盘记录</strong>： 参考 SK-8.2 ，记录结果会通过远程交互的UDP会话发回去；</p>
<p><strong>进程隐藏</strong>：参考 SK-8.1；HOOK 参考 SK-3.1；</p>
<p><strong>网络会话隐藏</strong>： 参考 SK-6.1; HOOK参考 SK-3.1；</p>
<p><strong>权限提升</strong>： 参考 SK-9.1;</p>
<h2 id="suterusu">suterusu</h2>
<p><strong>项目地址</strong>：<a href="https://github.com/mncoppola/suterusu">https://github.com/mncoppola/suterusu</a></p>
<p><strong>模块自身隐藏</strong>：参考 SK-5.1 和 SK-5.2；</p>
<p><strong>本地交互</strong>： 参考 SK-1.3；</p>
<p><strong>远程后门交互</strong>：参考 SK-1.2；</p>
<p><strong>SHELL后门</strong>： 交互参考 SK-1.2； 功能实现参考 SK-10.1；并发执行参考 SK-10.5；</p>
<p><strong>键盘记录</strong>：参考 SK-8.1；在这个位置还用到了 wake_up_interruptible 中断相关的接口，感兴趣的小伙伴们可以在这里深挖一下，它也是一个在 Linux 内核中比较重要的 API。这里就不展开了；</p>
<p><strong>文件/目录隐藏</strong>：参考 SK-4.1; 下钩参考 SK-3.1</p>
<p><strong>进程隐藏</strong>：参考 SK-7.4；下钩参考 SK-3.1</p>
<p><strong>网络会话隐藏</strong>： 参考 SK-6.1； 下钩参考 SK-3.1</p>
<h2 id="arprootkit">ARP-Rootkit</h2>
<p><strong>项目地址</strong>：<a href="https://github.com/romeroperezabel/ARP-RootKit">https://github.com/romeroperezabel/ARP-RootKit</a></p>
<p>PS： 截止本文编写时，该项目已经从github下架，无法找到，希望读者中有人能够贡献一波，供我等分析。</p>
<h2 id="rootkit">rootkit</h2>
<p><strong>项目地址</strong>：<a href="https://github.com/nurupo/rootkit">https://github.com/nurupo/rootkit</a></p>
<p><strong>本地交互</strong>：项目中存在 client 程序，通过 SK-1.5 的方式配置内核模块；下钩参考 SK-3.1;</p>
<p><strong>模块自身隐藏</strong>： 参考 SK-5.1 和 SK-5.3；下钩参考 SK-3.1；</p>
<p><strong>进程隐藏</strong>： 参考 SK-5.3; 下钩参考 SK-3.1;</p>
<p><strong>文件/目录隐藏</strong>： 参考 SK-4.1；</p>
<h2 id="diamorphine">Diamorphine</h2>
<p><strong>项目地址</strong>： <a href="https://github.com/m0nad/Diamorphine">https://github.com/m0nad/Diamorphine</a></p>
<p><strong>本地交互</strong>：参考 SK-1.6；</p>
<p><strong>模块自身隐藏</strong>： 参考 SK-5.1;</p>
<p><strong>文件/目录隐藏</strong>： 参考 SK-4.4； HOOK 参考 SK-3.3；获取系统调用表参考 SK-2.1，在较新系统版本中参考 SK-10.4；</p>
<p><strong>权限提升</strong>：参考 SK-9.1；</p>
<h2 id="rootkit2">rootkit （2）</h2>
<p><strong>项目地址</strong>： <a href="https://github.com/ivyl/rootkit">https://github.com/ivyl/rootkit</a></p>
<p><strong>本地交互</strong>： 参考 SK-1.5；</p>
<p><strong>模块自身隐藏</strong>：参考 SK-5.1 / SK-5.2 / SK-5.4；</p>
<p><strong>文件/目录隐藏</strong>： 参考 SK-4.5;</p>
<p><strong>进程隐藏</strong>：参考 SK-7.4；</p>
<p><strong>权限提升</strong>： 参考 SK-9.1；</p>
<h2 id="randkit">randkit</h2>
<p><strong>项目地址</strong>: <a href="https://github.com/vrasneur/randkit">https://github.com/vrasneur/randkit</a></p>
<p><strong>文件/目录隐藏</strong>： 参考 SK-4.4</p>
<h2 id="adoreng">adore-ng</h2>
<p><strong>项目地址</strong>：<a href="https://github.com/trimpsyw/adore-ng">https://github.com/trimpsyw/adore-ng</a></p>
<p><strong>文件隐藏</strong>：高内核版本参考 SK-4.1； 低内核版本参考 SK-4.5；</p>
<p><strong>本地交互</strong>：参考 SK-1.7；</p>
<p><strong>权限提升</strong>：参考 SK-9.1；</p>
<p><strong>进程隐藏</strong>：参考 SK-7.4；</p>
<h2 id="the_colonel">the_colonel</h2>
<p><strong>项目地址</strong>：<a href="https://github.com/bones-codes/the_colonel">https://github.com/bones-codes/the_colonel</a></p>
<p><strong>本地交互</strong>：参考 SK-1.5；</p>
<p><strong>文件隐藏</strong>：参考 SK-4.5；</p>
<p><strong>进程隐藏</strong>：参考 SK-7.4；</p>
<p><strong>权限提升</strong>：参考 SK-9.1；</p>
<p><strong>键盘记录</strong>：参考 SK-8.3；</p>
<h2 id="enyelkm">enyelkm</h2>
<p><strong>项目地址</strong>： <a href="https://github.com/David-Reguera-Garcia-Dreg/enyelkm">https://github.com/David-Reguera-Garcia-Dreg/enyelkm</a></p>
<p><strong>本地交互</strong>： 参考 SK-1.8；</p>
<p><strong>网络隐藏</strong>：参考 SK-6.1；</p>
<p><strong>文件隐藏</strong>： 参考 SK-4.4； HOOK 参考 SK-3.3；</p>
<p><strong>权限提升</strong>：参考 SK-9.1；</p>
<p><strong>shell后门</strong>：参考 SK-1.9；会筛选处理 ICMP 和 TCP4 协议的报文；</p>
<h2 id="subversive">subversive</h2>
<p><strong>项目地址</strong>：<a href="https://github.com/falk3n/subversive">https://github.com/falk3n/subversive</a></p>
<p><strong>本地交互</strong>： 参考 SK-1.10； HOOK 参考 SK-3.5;</p>
<p><strong>文件隐藏</strong>： 参考 SK-4.4； HOOK 参考 SK-3.5；</p>
<p><strong>权限提升</strong>： 参考 SK-9.1； HOOK 参考 SK-3.5；</p>
<p><strong>进程隐藏</strong>： 参考 SK-7.5； HOOK 参考 SK-3.5;</p>
<h2 id="lkmrootkit2">lkm-rootkit (2)</h2>
<p><strong>项目地址</strong>: <a href="https://github.com/jiayy/lkm-rootkit">https://github.com/jiayy/lkm-rootkit</a></p>
<p><strong>模块自身隐藏</strong>：参考 SK-5.1 和 SK-5.2；</p>
<p><strong>本地交互</strong>： 参考 SK-1.3；</p>
<p><strong>远程后门交互</strong>：参考 SK-1.2；</p>
<p><strong>SHELL后门</strong>： 交互参考 SK-1.2； 功能实现参考 SK-10.1；并发执行参考 SK-10.5；</p>
<p><strong>键盘记录</strong>：参考 SK-8.1；在这个位置还用到了 wake_up_interruptible 中断相关的接口，感兴趣的小伙伴们可以在这里深挖一下，它也是一个在 Linux 内核中比较重要的 API。这里就不展开了；</p>
<p><strong>文件/目录隐藏</strong>：参考 SK-4.1; 下钩参考 SK-3.1</p>
<p><strong>进程隐藏</strong>：参考 SK-7.4；下钩参考 SK-3.1</p>
<p><strong>网络会话隐藏</strong>： 参考 SK-6.1; HOOK参考 SK-3.1；</p>
<h2 id="wukong">wukong</h2>
<p><strong>项目地址</strong>: <a href="https://github.com/hanj4096/wukong">https://github.com/hanj4096/wukong</a></p>
<p><strong>模块自身隐藏</strong>: 参考 SK-5.1 和 SK-5.2;</p>
<p><strong>本地交互</strong>： 参考 SK-1.3；</p>
<p><strong>文件/目录隐藏</strong>： 参考 SK-4.5; HOOK 参考 SK-3.1；</p>
<p><strong>进程隐藏</strong>： 参考 SK-7.3； HOOK 参考 SK-3.1；</p>
<p><strong>网络会话隐藏</strong>： 参考 SK-6.1；</p>
<h2 id="kernel_rootkit">Kernel_Rootkit</h2>
<p><strong>项目地址</strong>： <a href="https://github.com/varshapaidi/Kernel_Rootkit">https://github.com/varshapaidi/Kernel_Rootkit</a></p>
<p><strong>模块自身隐藏</strong>： 参考 SK-5.5；</p>
<p><strong>文件/目录隐藏</strong>： 参考 SK-4.6；</p>
<p><strong>进程隐藏</strong>： 参考 SK-7.6；</p>
<p><strong>网络会话隐藏</strong>： 参考 SK-6.2；</p>
<p>PS： 这个项目似乎是个半成品，其中有大量无用代码，看逻辑似乎是想通过劫持内存分配或内存映射的逻辑实现一些未知功能。</p>
<h2 id="brootus">brootus</h2>
<p><strong>项目地址</strong>： <a href="https://github.com/dsmatter/brootus">https://github.com/dsmatter/brootus</a></p>
<p><strong>模块自身隐藏</strong>：参考 SK-5.1 和 SK-5.2；</p>
<p><strong>文件/目录隐藏</strong>：参考 SK-4.4； HOOK 参考 SK-3.3；</p>
<p><strong>进程隐藏</strong>： 参考 SK-7.4;</p>
<p><strong>网络会话隐藏</strong>： 参考 SK-6.1；</p>
<p>PS： 这个项目似乎是个半成品，其中有一部分函数有调用过程，却没有实现。</p>
<h2 id="sutekh">Sutekh</h2>
<p><strong>项目地址</strong>: <a href="https://github.com/PinkP4nther/Sutekh">https://github.com/PinkP4nther/Sutekh</a></p>
<p><strong>权限提升</strong>： 参考 SK-9.1</p>
<h2 id="lilyofthevalley">LilyOfTheValley</h2>
<p><strong>项目地址</strong>：<a href="https://github.com/En14c/LilyOfTheValley">https://github.com/En14c/LilyOfTheValley</a></p>
<p><strong>模块自身隐藏</strong>：参考 SK-5.1 和 SK-5.2；</p>
<p><strong>文件/目录隐藏</strong>：参考 SK-4.1； HOOK 参考 SK-3.1；</p>
<p><strong>进程隐藏</strong>：参考  SK-7.4； HOOK 参考 SK-3.1；</p>
<p><strong>权限提升</strong>：参考 SK-9.1；</p>
<h2 id="researchrootkit">research-rootkit</h2>
<p><strong>项目地址</strong>：<a href="https://github.com/NoviceLive/research-rootkit">https://github.com/NoviceLive/research-rootkit</a></p>
<p>PS: 该项目是个学习性质的项目，并非一个完整的 <code>rootkit</code> 所以这里不展开了。</p>
<h2 id="outofsightoutofmindrootkit">Out-of-Sight-Out-of-Mind-Rootkit</h2>
<p><strong>项目地址</strong>：<a href="https://github.com/NinnOgTonic/Out-of-Sight-Out-of-Mind-Rootkit">https://github.com/NinnOgTonic/Out-of-Sight-Out-of-Mind-Rootkit</a></p>
<p><strong>模块自身隐藏</strong>： 参考 SK-5.1 和 SK-5.2；</p>
<p><strong>网络交互</strong>：参考 SK-1.4； HOOK 参考 SK-3.2；</p>
<p><strong>文件/目录隐藏</strong>： 参考 SK-4.4； HOOK 参考 SK-3.1；</p>
<p>PS：项目目前是半成品学习性质项目，有PDF介绍，但功能并未完全实现。目前这里只整理一部分已经完整实现的功能。</p>
<h1 id="lkmrootkit">LKM-Rootkit 知识点汇总</h1>
<p>本节将以知识点汇总的形式介绍各种常见功能的技能点，并为每一个知识点，给出一个编号（SK-X.X），进而标定出相关项目用到了哪种知识点。</p>
<h2 id="">配置交互</h2>
<p>由于 LKM 工作在内核层，编译安装后，很难更改配置。所以一般都需要维护一个通讯接口，供用户侧随时更新配置信息。整理后发现，常见的通讯接口大致可以归类为下几种情况：</p>
<p><strong>SK-1.1</strong> 通过 Netlink 交互（本地）</p>
<p>Netlink 是标准 socket 提供的一种双向通讯协议，可用于本地配置内核模块，<br>
通过<code>socket(PF_NETLINK, SOCK_RAW, NETLINK_USER)</code> 可完成用户侧的初始化。其中NETLINK_USER为双方约定好的私有编号。</p>
<p>内核层的初始化代码截图如下所示：</p>
<p><img src="__GHOST_URL__/content/images/2021/01/netlink_kernel_create.png" alt="netlink_kernel_create" loading="lazy"></p>
<p>netlink_kernel_create 这个接口，在不同的内核版本声明不同，当需要做内核适配时，要注意这个接口的声明情况。</p>
<p><strong>SK-1.2</strong> 通过筛选 ICMP报文 交互（网络）</p>
<p>这是一种可以从网络环境同内核模块交互的方法。下图是一个 ICMP 对接shell后门的实现片段：</p>
<p><img src="__GHOST_URL__/content/images/2021/01/icmp_sendmsg.png" alt="icmp_sendmsg" loading="lazy"></p>
<p><strong>SK-1.3</strong> 通过 ioctl 进行交互（本地）</p>
<p>ioctl 是一种针对 I/O 设备定义的交互方式，在 LKM-Rootkit 中也经常遇到，通过约定一个自定义的协议号，可以实现本地的 Rootkit 交互。下图是一段内核中过滤特定协议号的处理逻辑，供参考：</p>
<p><img src="__GHOST_URL__/content/images/2021/01/ioctl_config.png" alt="ioctl_config" loading="lazy"></p>
<p><strong>SK-1.4</strong> 通过筛选 IP报文 实现交互（网络）A</p>
<p>这是一种通过筛选 IP 报文，实现内核交互的方法，在 Linux 中，对 IPv4报文 的处理，和一个名为  ip_rcv 的函数有关，通过更改这个函数的处理逻辑，可以完成定制的交互逻辑，下图是被用于替换原函数执行逻辑的代码片段，供参考：</p>
<p><img src="__GHOST_URL__/content/images/2021/01/khook_ip_rcv.png" alt="khook_ip_rcv" loading="lazy"></p>
<p>可以看出，代码覆盖了 TCP/UDP/ICMP 三种协议的处理机制，实现同内核的交互。</p>
<p><strong>SK-1.5</strong> 文件读写（本地）</p>
<p>在内核中，通过对指定文件读写函数的劫持，可以实现用户和内核间的交互，下图是一个为 <code>/proc</code> 目录文件的读写处理函数打 HOOK 的代码片段：</p>
<p><img src="__GHOST_URL__/content/images/2021/01/local_file_rw_hook.png" alt="local_file_rw_hook" loading="lazy"></p>
<p><strong>SK-1.6</strong>  劫持 kill signal（本地）</p>
<p>有一些内核模块功能很简单，只有单向配置的需求，此时会考虑使用劫持 signal 信号的方案，在 <code>int kill(pid_t pid, int sig);</code> 函数中，第二个参数就是 signal，它使用起来足够简单，只要对 <code>sys_kill</code> 的处理逻辑增加一个 HOOK 点，就可以筛选出特殊的 signal 标记，很容易实现，所以在一些 Rootkit 中会选择这样的方式向内核发送同步指令：</p>
<p><img src="__GHOST_URL__/content/images/2021/01/hack_kill_sig.png" alt="hack_kill_sig" loading="lazy"></p>
<p><strong>SK-1.7</strong> 劫持 lookup（本地）</p>
<p>在 <code>inode_operations</code> 中，有一个 lookup 函数指针，用于查阅相关目录文件。在部分 rootkit 中，会通过改写这个指针中函数的处理逻辑，获知用户正在查阅的文件名，通过对文件名编码，就可以得到用户想要实现的指令了，下图是一个利用该方法解析指令内容的方法：</p>
<p><img src="__GHOST_URL__/content/images/2021/01/fake_lookup.png" alt="fake_lookup" loading="lazy"></p>
<p><strong>SK-1.8</strong> 中断处理过程劫持（本地）</p>
<p>通过重写内核中的中断处理逻辑，对于正常程序的调用找到原有逻辑执行，筛选出特殊的调用请求。如此可以完成用户和内核模块之间的交互，下图是一个生成新中断处理的逻辑：</p>
<p><img src="__GHOST_URL__/content/images/2021/01/new_idt.png" alt="new_idt" loading="lazy"></p>
<p><strong>SK-1.9</strong> 筛选 IP报文 实现交互（网络） B</p>
<p>内核在处理收到的网络报文时，会依次交给不同的解析器进行试图解析。解析器可以通过 <code>dev_add_pack</code> 进行添加，这样就可以筛选出符合需求的特殊报文，从而得到操作指令，下图是一段调用 <code>dev_add_pack</code> 的截图：</p>
<p><img src="__GHOST_URL__/content/images/2021/01/dev_add_pack_eth.png" alt="dev_add_pack_eth" loading="lazy"></p>
<p><strong>SK-1.10</strong> 利用 sys_uname（本地）</p>
<p><code>uname(struct utsname *buf)</code> 函数是一个获取机器名的 API，传入参数是一段空内存，执行返回后，这段内存则被填充上当前机器的相关信息。如果在内核中将处理逻辑改写，就可以利用传入的这个空内存向内核模块传递数据了，同时又不会影响该 API 原有的功能。下图是一个被改写后的 <code>uname</code> 处理逻辑：</p>
<p><img src="__GHOST_URL__/content/images/2021/01/uname_transmit.png" alt="uname_transmit" loading="lazy"></p>
<h2 id="syscall_table">syscall_table的获取</h2>
<p>在 Linux内核 中，syscall_table(系统调用表) 是一个用来将&quot;系统中断号&quot;映射到&quot;对应功能函数（地址）&quot;的表格。通过修改这个表格中的内容，就可以完成对某些特定功能函数的HOOK操作，进而修改替换函数的原有执行逻辑，最终完成&quot;关键信息的隐藏&quot;。</p>
<p>所以，获取 syscall_table 内存地址是一个非常重要的技能点，它直接决定着 Rootkit 的效果。通过阅读开源代码，大致总结出以下几种获取方式。</p>
<p><strong>SK-2.1</strong> 暴力搜索</p>
<p>在 sys_close 是Linux 内核提供的一个函数，它是 syscall_table 中第 &quot;__NR_close&quot; 个元素的值，其中 __NR_close 的值通过内核宏定义，不同的编译环境中，这个值有区别。下图是一个使用该方法的代码截图：</p>
<p><img src="__GHOST_URL__/content/images/2021/01/find_syscall_table_violence.png" alt="find_syscall_table_violence" loading="lazy"></p>
<p><strong>SK-2.2</strong> 通过 IDT 搜索</p>
<p>Linux 内核中，syscall_table 是挂在中断描述符表结构下的，所以只要找到中断描述符表，即可确定 syscall_table 的大致范围，同时 syscall_table 中保存的数据又都是系统地址指针，所以此种方法，可相对准确的定位到 syscall_table 结构的位置，下图是具体的代码截图，供参考：</p>
<p><img src="__GHOST_URL__/content/images/2021/01/find_syscall_tbl_from_idtr.png" alt="find_syscall_tbl_from_idtr" loading="lazy"></p>
<p><strong>SK-2.3</strong> 通过 MSR_LSTAR 定位</p>
<p>在Linux 内核中(arch/x86/include/asm/msr-index.h)，定义了一个 MSR_LSTAR 宏，这是一个和系统调用表执行有关的寄存器，它保存着一个内存地址，实际的 syscall_table 地址将出现在这个地址的后面，确定了大致范围后，即可参考 SK-2.2 的方式，搜索到 syscall_table 地址了，下图是此种方案的代码片段截图：</p>
<p><img src="__GHOST_URL__/content/images/2021/01/syscall_MSR_LSTAR.png" alt="syscall_MSR_LSTAR" loading="lazy"></p>
<p><strong>SK-2.4</strong> 通过 kallsyms 获取</p>
<p>在 SK-10.3 中提到了 利用 <code>kallsyms_on_each_symbol</code> 获取内核符号的知识点，其实这个接口也可以被用于获取 <code>syscall_table</code>，在内核中，其符号名为 <code>sys_call_table</code>，一段代码样例如下所示：</p>
<p><img src="__GHOST_URL__/content/images/2021/01/kallsym_find_syscall.png" alt="kallsym_find_syscall" loading="lazy"></p>
<h2 id="hook">内核钩子（HOOK）</h2>
<p>想在 Linux 内核中隐藏信息，首先要有办法把自己的代码插入关键位置并执行。这就需要在内核中的特定位置下钩子。本小节整理的就是下钩子的几种技巧。</p>
<p><strong>SK-3.1</strong> Hijack钩子</p>
<p>这是一种直接修改内核代码的钩子，它会将一组控制执行顺序的代码直接插入给定的内存地址，进而让自己的代码得到执行。下图是项目中实际的代码截图：</p>
<p><img src="__GHOST_URL__/content/images/2021/01/hijack_hook_push_ret-1.png" alt="hijack_hook_push_ret-1" loading="lazy"></p>
<p>其中第10行是 <code>push xxx; ret</code> 代码的 hex 数据模版，第11行则是将自己的函数地址填充到模版中，进而达到一个 <code>push new; ret</code>的效果。与之类似的还可能会遇到 <code>mov eax，xxx ; jmp eax </code> 类型的 hex 模版。</p>
<p><strong>SK-3.2</strong> nf_hook 钩子</p>
<p>nf 是 netfilter 的缩写，可以从内核层截流网络报文，完成网络交互。下图是利用 nf_hook 实现icmp协议后门的截图：</p>
<p><img src="__GHOST_URL__/content/images/2021/01/nf_hook_backdoor.png" alt="nf_hook_backdoor" loading="lazy"></p>
<p><strong>SK-3.3</strong> syscall劫持</p>
<p>在 Linux 系统中，syscall_table 中包含了很多系统相关的重要函数指针，针对性的修改这里的指针内容，可以实现插入自定义函数逻辑的效果，下图是一个劫持 <code>getdents64</code> 的代码片段，它先保存了原始的函数（<code>getdents64</code>）地址，然后又在 syscall_table 里放了一个自己实现的 <code>fake_getdents64</code> 函数地址：</p>
<p><img src="__GHOST_URL__/content/images/2021/01/patch_syscall_table.png" alt="patch_syscall_table" loading="lazy"></p>
<p><strong>SK-3.4</strong> KHOOK 框架</p>
<p>在 Reptile 项目中，用了一个名为 KHOOK 的钩子框架，它是对 SK-3.1 方案的扩展，其在易用性和稳定性中下了一些功夫。在符号查找中，参考了 SK-10.3 的方案。所以这里把它单独提出来作为一个 HOOK 知识点，该项目具体的编码，比较复杂，这里就不展开说了，感兴趣的小伙伴们可以去参考：<br>
<a href="https://github.com/milabs/khook">https://github.com/milabs/khook</a> 项目，项目中有严谨的描述文档和全部代码，各位可以自行查阅。</p>
<p><strong>SK-3.5</strong> 调试模式</p>
<p>IA-32 从 386 开始，引入了调试寄存器和硬件断点，这意味着在 ring0 层，内核代码的任意位置都可以被设置硬件断点，从而实现对内核代码调试的能力。这种调试能力也为 rootkit 开发提供了一种新的控制流劫持的思路，在 <a href="https://github.com/falk3n/subversive">https://github.com/falk3n/subversive</a> 这一款 rootkit 中，就是通过此种方式实现 HOOK 的，它先对 syscall_table 的内存地址下了硬件断点，随后当其他代码访问到 syscall_table 结构时，调试代码都会被执行。下图是一段设置内存断点的代码截图，红框圈定的是它在每个CPU上依次下断的调用逻辑：</p>
<p><img src="__GHOST_URL__/content/images/2021/01/cpu_hardware_debug-1.png" alt="cpu_hardware_debug-1" loading="lazy"></p>
<h2 id="">文件/目录隐藏</h2>
<p><strong>SK-4.1</strong> 目录文件迭代器</p>
<p>VFS中存在一个名为 <code>file_operations</code> 的接口抽象，其中存在一个目录文件迭代器。如果对这个迭代器的执行逻辑做一下修改，即可完成对指定文件的隐藏，在Rootkit中一般通过修改根目录迭代器的方式隐藏文件/目录。下图是一个获取迭代器函数的代码片段：</p>
<p><img src="__GHOST_URL__/content/images/2021/01/vfs_get_iterate.png" alt="vfs_get_iterate" loading="lazy"></p>
<p><strong>SK-4.2</strong> 目录隐藏（Reptile）</p>
<p>在 Reptile 中，目录隐藏功能的实现比较特殊，它通过对一组函数打 HOOK 的方式，从而实现一个隐藏目录无法给他人查阅的能力，这些函数有：<br>
<code>fillonedir</code>/<code>filldir</code>/<code>filldir64</code>/<code>compat_fillonedir</code>/<code>compat_filldir</code>/<code>compat_filldir64</code>/<code>__d_lookup</code></p>
<p><strong>SK-4.3</strong> 文件隐藏（Reptile）</p>
<p>在 Reptile 中，文件隐藏是一个临时实现，它是通过对 vfs_read 打 HOOK 实现的。在注释中作者提到: <code>这不是最佳实现，但它有效，在将来可能会改变</code>。下图是这段函数头的截图：</p>
<p><img src="__GHOST_URL__/content/images/2021/01/reptile_hide_file.png" alt="reptile_hide_file" loading="lazy"></p>
<p><strong>SK-4.4</strong> 下钩 getdents 函数</p>
<p>在 Linux 中 <code>getdents</code> 和 <code>getdents64</code> 是显示目录/文件相关的函数，通过改动这两个函数的执行逻辑，可以实现是否显示一个指定文件的效果。下图是一段用于隐藏文件的代码片段截图，可以看到当 file_hiding 开关打开时，会触发一个特殊的逻辑，以便于不显示受保护的文件：</p>
<p><img src="__GHOST_URL__/content/images/2021/01/fake_getdents.png" alt="fake_getdents" loading="lazy"></p>
<p><strong>SK-4.5</strong> 劫持 readdir</p>
<p>在 SK-4.1 中提到的 <code>file_operations</code> 接口抽象中，还有一个 readdir 的函数指针，通过修改这个指针，也能达到文件隐藏的效果，下图是一个 Rootkit 中更改这个指针的代码片段：</p>
<p><img src="__GHOST_URL__/content/images/2021/01/fake_fs_readdir.png" alt="fake_fs_readdir" loading="lazy"></p>
<p><strong>SK-4.6</strong> 替换 file_operations</p>
<p>在 SK-4.1 和 SK-4.5 中看到的是，替换 <code>file_operations</code> 成员成员指针的情况，而如果直接替换这个成员本身，自然也能实现相同的效果，这里就不贴图了。</p>
<h2 id="">模块自身的隐藏</h2>
<p><strong>SK-5.1</strong>: 从模块链表删除自身</p>
<p>在Linux内核中，每一个 <code>struct module</code> 通过一个链表结构(struct list_head)，链接在一起。所有和模块遍历有关的操作，都需要依赖这个链表，如果从这个链表中将自身摘出，则模块自身便无法通过遍历的方式被找到了，下图是一个摘除自身的代码片段，以供参考：</p>
<p><img src="__GHOST_URL__/content/images/2021/01/hide_module_from_lsmod.png" alt="hide_module_from_lsmod" loading="lazy"></p>
<p><strong>SK-5.2</strong>：从文件系统移除模块自身</p>
<p>当模块成功安装后，用户可以从 <code>/sys/modules</code> 看到新模块的一些基本信息。通过对 <code>kobject_del</code> 方法可以移除对应的模块目录，所以在一些 LKM 中也会使用这种方式隐藏自身，下图是一个参考代码：</p>
<p><img src="__GHOST_URL__/content/images/2021/01/kobject_module_del.png" alt="kobject_module_del" loading="lazy"></p>
<p><strong>SK-5.3</strong>: 从内核文件树移除模块</p>
<p>在 Linux内核中，有一个内核文件系统，它是通过红黑树的结构组织起来的。这个文件系统运行后将会对应 <code>/proc</code>，<code>/sys</code> 等虚拟文件目录。如果将当前模块对应的结构从这棵树中摘除，也可以达到隐藏 <code>/sys/modules</code> 目录内容的效果。这个方法同 SK-5.2 的区别在于，前者是将内核结构整体从内核删除，而本节是仅删除挂载关系，内核结构还在内存中，下图中红框，就是具体实现的代码片段：</p>
<p><img src="__GHOST_URL__/content/images/2021/01/module_erase_rbtree.png" alt="module_erase_rbtree" loading="lazy"></p>
<p><strong>SK-5.4</strong> ：内核模块链表脱钩</p>
<p>除 <code>struct module</code> 结构以外，内核中还有一个 <code>module_kobject.kobj</code> 也是和模块一一对应的，且这个结构也是个通过链表连载一起的结构体。有些 Rootkit 会选择在这里脱钩，实现内核模块的隐藏：</p>
<p><img src="__GHOST_URL__/content/images/2021/01/list_del_kkobj_kobj.png" alt="list_del_kkobj_kobj" loading="lazy"></p>
<p><strong>SK-5.5</strong> 替换 file_operations</p>
<p>同 SK-4.6 一样，只是替换目标换成了 <code>/sys/modules</code> 目录对应的 file_operations 结构。</p>
<h2 id="">网络会话的隐藏</h2>
<p><strong>SK-6.1</strong> 通过 seq_operations 隐藏网络会话</p>
<p>在 Linux 内核中，网络会话也是一种文件，通过 file 结构可以找到一个名为 <code>seq_operations.show</code> 的函数指针，该函数指针用于生成网络会话相关的信息供展示，通过修改对应的函数，可以实现网络会话对用户显示的隐藏。</p>
<p>下图是获取 <code>tcp4_seq_show</code> 相关函数地址的代码片段，供参考：</p>
<p><img src="__GHOST_URL__/content/images/2021/01/call_get_seq_show.png" alt="call_get_seq_show" loading="lazy"></p>
<p><img src="__GHOST_URL__/content/images/2021/01/process_get_seq_show.png" alt="process_get_seq_show" loading="lazy"></p>
<p>如此操作后，就可以通过过滤地址或端口，对特定的网络会话进行屏蔽了。</p>
<p>除 <code>tcp4_seq_show</code> 外，还可能出现 <code>udp4_seq_show</code>/<code>tcp6_seq_show</code>/<code>udp6_seq_show</code> 等几种情况。</p>
<p><strong>SK-6.2</strong> 替换 seq_operations</p>
<p>在 SK-6.1 中，是通过 seq_operations 的成员实现会话流控制，如果直接替换 一个新伪造的 seq_operations 到对应位置，也能实现相同的效果，这里就不截图了。</p>
<h2 id="">进程隐藏</h2>
<p><strong>SK-7.1</strong> 进程隐藏（Reptile）</p>
<p>在 Reptile 中隐藏进程这个功能的实现，有些特殊，它是通过&quot;给一组函数打hook&quot;的方式，最终完成进程隐藏的，在这个过程中，被打 HOOK的函数 有： <code>audit_alloc</code>/<code>copy_creds</code>/<code>exit_creds</code>/<code>find_task_by_vpid</code>/<code>vfs_statx</code>/<code>sys_kill</code>/<code>__x64_sys_kill</code>/<code>tgid_iter</code>/<code>load_elf_binary</code> 。</p>
<p>当一个进程(task)被设置为隐藏后，Reptile 会为其 task_struct.flags 设置一个标记 0x80000000，有趣的是，这个标记在 Linux内核 中代表着 PF_SUSPEND_TASK，是一个进程无法冻结的标记，这会让那些正常设置这个标记的进程也出现隐身效果。</p>
<p><strong>SK-7.2</strong> 进程隐藏（tasks链表摘除)</p>
<p>在 Linux 中，process/thread 会对应到一个名为 struct task_struct 的结构体，在这个结构体中有一个 tasks 链表成员，它作为一个侵入式的双向链表会将当前 task 同其他 task 链接到一起。有些 Rootkit 会将被保护的 task 从这个链表中摘除，进而无法遍历访问。脱钩方法也很简单，只需要在模块中增加一行代码 <code>list_del_init(&amp;current-&gt;tasks);</code> 即可。在一部分 rootkit 中，会考虑自行编码脱钩的逻辑代码，下图就是一个展开实现的样例，它们背后的实现思路都是相同的的：</p>
<p><img src="__GHOST_URL__/content/images/2021/01/tasks_list_del.png" alt="tasks_list_del" loading="lazy"></p>
<p><strong>SK-7.3</strong> 隐藏 &quot;/proc&quot; 目录信息 A</p>
<p>在 SK-4.1 中，提到了文件目录迭代器的使用。其实 <code>/proc</code> 目录中的内容，也可以通过目录文件迭代器实现目录内容的隐藏，下图是一个获取 <code>/proc迭代器</code> 的代码片段：</p>
<p><img src="__GHOST_URL__/content/images/2021/01/get_proc_iterate.png" alt="get_proc_iterate" loading="lazy"></p>
<p><strong>SK-7.4</strong> 隐藏 &quot;/proc&quot; 目录信息 B</p>
<p>在 SK-4.5 中 提到了通过更改 filldir/readdir 指针隐藏文件的方法，该方法也适用于 <code>/proc</code> 目录下的文件。这里就不贴代码图了。</p>
<p><strong>SK-7.5</strong> 通过 syscall hook 隐藏</p>
<p>在 syscall 调用中，有一系列和进程操作相关的函数，如果让这些函数统统对需要隐藏的进程 pid 执行失败，如此 ring3 层便无法查阅到进程的有关信息了，这些函数的列表如下：</p>
<pre><code>getpgid
getsid
setpgid
kill
tgkill
tkill
sched_setscheduler
sched_setparam
sched_getscheduler
sched_getparam
sched_setaffinity
sched_getaffinity
sched_rr_get_interval
wait4
waitid
rt_tgsigqueueinfo
rt_sigqueueinfo
prlimit64
ptrace
migrate_pages
move_pages
get_robust_list
perf_event_open
</code></pre>
<p><strong>SK-7.6</strong> 替换 file_operations</p>
<p>同 SK-4.6 类似，只是替换目标为 <code>/proc</code> 目录对应的 <code>file_operations</code> 结构.</p>
<h2 id="">键盘记录</h2>
<p><strong>SK-8.1</strong> 键盘记录器</p>
<p>内核提供了<code>register_keyboard_notifier</code> 和 <code>unregister_keyboard_notifier</code> 两个方法，用于增加或卸载键盘监视器，下图是使用该机制实现键盘记录功能的代码片段：</p>
<p><img src="__GHOST_URL__/content/images/2021/01/keylogger.png" alt="keylogger" loading="lazy"></p>
<p><strong>SK-8.2</strong> 键盘记录（<code>/dev/ttyS0</code>读写劫持）</p>
<p>在 Linux 中，  <code>/dev/ttyS0</code> 是一个字符设备（虚拟），可以简单的将之理解为是标准输入，通过重写它的 read 函数，可以捕获用户从键盘输入的内容，下图是一个劫持的代码样例：</p>
<p><img src="__GHOST_URL__/content/images/2021/01/network_keylogger.png" alt="network_keylogger" loading="lazy"></p>
<p><strong>SK-8.3</strong> <code>/dev/input/event</code> 监听</p>
<p>这是一个和输入有关的设备文件，通过监听该文件的变化情况，可以得到用户正在输入的内容。</p>
<p>下图是选自 <a href="https://github.com/bones-codes/the_colonel/blob/devx/lkm/col_kl.c">https://github.com/bones-codes/the_colonel/blob/devx/lkm/col_kl.c</a> 的代码片段，该代码没有合并到 master 分支，似乎是个未完成的版本，仅供参考：</p>
<p><img src="__GHOST_URL__/content/images/2021/01/listen_keyboard_device.png" alt="listen_keyboard_device" loading="lazy"></p>
<h2 id="">权限提升</h2>
<p><strong>SK-9.1</strong> 本地权限获取</p>
<p>对于 Rootkit 来说，有时需要为普通进程赋予管理员权限。这就涉及到一个名为 cred 的结构体，另一方面，当前进程的 cred 可以通过 <code>prepare_creds</code> 可以获取。下图就是一段通过该方式提权的代码段：</p>
<p><img src="__GHOST_URL__/content/images/2021/01/cred_root_me.png" alt="cred_root_me" loading="lazy"></p>
<h2 id="">其他技术点</h2>
<p><strong>SK-10.1</strong> 执行系统命令</p>
<p>内核提供了 <code>call_usermodehelper</code> 函数，用于在用户态运行一个可执行文件。下图是使用该函数，实现shell后门的代码片段：</p>
<p><img src="__GHOST_URL__/content/images/2021/01/run_shell_command.png" alt="run_shell_command" loading="lazy"></p>
<p><strong>SK-10.2</strong> 从内核启动一个线程</p>
<p>内核提供了 <code>kernel_create</code> 函数，用于从内核态启动一个用户态线程。配置好 <code>subprocess_info</code> 实例后，即可完成启动。</p>
<p><img src="__GHOST_URL__/content/images/2021/01/create_thread_from_kernel.png" alt="create_thread_from_kernel" loading="lazy"></p>
<p><strong>SK-10.3</strong>： 内核符号地址获取 A</p>
<p>Linux 内核提供了一个名为 <code>kallsyms_on_each_symbol</code> 的 API，用来获取各种符号在内存中的地址。但该 API 需要 Linux内核 在编译时打开 <code>CONFIG_KALLSYMS</code> 宏，好在一些常见的Linux发行版中，默认都支持了这个宏，所以通配性还过得去。下图是基于该API抽象后的获取指定符号地址的代码片段：</p>
<p><img src="__GHOST_URL__/content/images/2021/01/kallsyms_find_sym.png" alt="kallsyms_find_sym" loading="lazy"></p>
<p><strong>SK-10.4</strong>：内核符号地址获取 B</p>
<p>在 SK-10.3 中提到了通过 <code>kallsyms_on_each_symbol</code> 获取符号地址的方法，其实，与这个API相对的还有另一个名为 <code>kallsyms_lookup_name</code> 的孪生API也可以获取一部分符号地址，这里就不贴截图了，感兴趣的小伙伴们自行找源码探索一下。</p>
<p><strong>SK-10.5</strong>： 通过工作队列执行任务分发</p>
<p>在 Linux内核中，有一个工作队列(workqueue)的机制，可以将特定工作（以函数为单位）分发到不同的CPU中去执行，产生一个内核并发执行任务的效果。编码者将“任务提交”和“任务执行”分开实现，分别对应到 <code>投递 workqueue</code> 和 <code>消费 workqueue</code> ，Linux内核就会可以辅助完成任务的分发和执行，是一个极为方便使用的 API。</p>
<p>在使用上，它需要先通过 <code>create_workqueue</code> 初始化一个新队列，当有新工作需要执行时，再通过 <code>queue_work</code> 方法向队列提交这个 work。下图是使用该方法的一段代码截图，供参考：</p>
<p><img src="__GHOST_URL__/content/images/2021/01/how_to_use_workqueue.png" alt="how_to_use_workqueue" loading="lazy"></p>
<h1 id="">参考资料</h1>
<p><a href="https://wiki.osdev.org/Inline_Assembly/Examples#RDMSR">https://wiki.osdev.org/Inline_Assembly/Examples#RDMSR</a></p>
<!--kg-card-end: markdown-->
引


LKM 是 Linux Kernel Module 的缩写，所以这里 LKM-Rootkit 的含义，是"基于Linux内核模块实现的 Rootkit"。由于前段时间分析了skidmap的细节，所以想到针对性的梳理一下这方面的内容。在集中精力读了一些开源代码后，整理一些手册性质的文字记录下来，供他人日后查阅。



开源项目参考


本节项目主要来源为 awesome-linux-rootkits 中提到的 LKM 相关的项目。


Rootkit 作为一个隐藏工具，其主要设计目标是为了隐藏攻击痕迹，本节将对开源LKM进行一个汇总梳理。每个项目中，可能包含多个实现细节，通过编号（SK-X.X）对应到知识点汇总章节中。



rooty


项目地址: https://github.com/jermeyyy/rooty


模块自身隐藏： SK-5.1 加 SK-5.2 实现内核模块的隐藏；


本地交互： 利用 SK-1.3 向内核模块发送控制指令；利用 SK-3.1 筛选 ioctl 命令；


远程后门交互： 参考 SK-1.2;


获取root权限：参考 SK-9.1 ；


SHELL后门： 功能实现参考 SK-10.2 启动了一个 sshd 任务处理shell 交互；


VNC后门：参考 SK-10.2 启动了一个 vnc 服务。


文件/目录隐藏：参考 SK-3.1 和 SK-4.1 更改根目录("/")的迭代器;


进程隐藏： 参考 SK-3.1 和 SK-7.3;


网络会话隐藏：参考 SK-6.1； HOOK 参考 SK-3.1；


键盘记录：利用 SK-8.1 实现键盘记录，并将结果记录到一个指定文件。



Reptile


项目地址: https://github.com/f0rb1dd3n/Reptile


本地交互: 利用 SK-1.3 向内核发送控制指令；利用 SK-3.1 筛选 ioctl 命令；


远程后门交互： 参考 SK-1.4；


模块自身隐藏： 参考 SK-5.1；


获取root权限：参考 SK-9.1；


网络会话隐藏：参考 SK-6.1；HOOK参考 SK-3.4；


进程隐藏：参考 SK-7.1；HOOK参考 SK-3.4；


目录隐藏：参考 SK-4.2；HOOK参考 SK-3.4；


文件隐藏：参考 SK-4.3；HOOK参考 SK-3.4；



rkduck


项目地址：https://github.com/QuokkaLight/rkduck


本地交互：rkduck提供了一个控制程序，user-kernel的通讯方式参考 SK-1.1;


SHELL后门：交互协议参考 SK-1.2； 功能实现参考 SK-10.1;


文件/目录隐藏： 利用 SK-3.1 和 SK-4.1 更改根目录("/")迭代器;


进程隐藏： 参考 SK-3.1 和 SK-7.3;


SSH后门：参考 SK-10.1 植入公钥到 /root/.ssh/authorized_keys 文件;


键盘记录： 利用 SK-8.1实现键盘记录，并利用 SK-10.1 执行 scp 命令 将记录结果回传攻击者;



lkm-rootkit


项目地址： https://github.com/croemheld/lkm-rootkit


模块自身隐藏： 参考 SK-5.1 和 SK-5.3;


远程后门交互： 参考 SK-10.2 启动一个UDP监听服务线程，接受从网络发来的控制指令；


文件/目录隐藏： 参考 SK-2.3 找到 syscall_table 位置，参考 SK-4.4 隐藏文件；


键盘记录： 参考 SK-8.2 ，记录结果会通过远程交互的UDP会话发回去；


进程隐藏：参考 SK-8.1；HOOK 参考 SK-3.1；


网络会话隐藏： 参考 SK-6.1; HOOK参考 SK-3.1；


权限提升： 参考 SK-9.1;



suterusu


项目地址：https://github.com/mncoppola/suterusu


模块自身隐藏：参考 SK-5.1 和 SK-5.2；


本地交互： 参考 SK-1.3；


远程后门交互：参考 SK-1.2；


SHELL后门： 交互参考 SK-1.2； 功能实现参考 SK-10.1；并发执行参考 SK-10.5；


键盘记录：参考 SK-8.1；在这个位置还用到了 wake_up_interruptible 中断相关的接口，感兴趣的小伙伴们可以在这里深挖一下，它也是一个在 Linux 内核中比较重要的 API。这里就不展开了；


文件/目录隐藏：参考 SK-4.1; 下钩参考 SK-3.1


进程隐藏：参考 SK-7.4；下钩参考 SK-3.1


网络会话隐藏： 参考 SK-6.1； 下钩参考 SK-3.1



ARP-Rootkit


项目地址：https://github.com/romeroperezabel/ARP-RootKit


PS： 截止本文编写时，该项目已经从github下架，无法找到，希望读者中有人能够贡献一波，供我等分析。



rootkit


项目地址：https://github.com/nurupo/rootkit


本地交互：项目中存在 client 程序，通过 SK-1.5 的方式配置内核模块；下钩参考 SK-3.1;


模块自身隐藏： 参考 SK-5.1 和 SK-5.3；下钩参考 SK-3.1；


进程隐藏： 参考 SK-5.3; 下钩参考 SK-3.1;


文件/目录隐藏： 参考 SK-4.1；



Diamorphine


项目地址： https://github.com/m0nad/Diamorphine


本地交互：参考 SK-1.6；


模块自身隐藏： 参考 SK-5.1;


文件/目录隐藏： 参考 SK-4.4； HOOK 参考 SK-3.3；获取系统调用表参考 SK-2.1，在较新系统版本中参考 SK-10.4；


权限提升：参考 SK-9.1；



rootkit （2）


项目地址： https://github.com/ivyl/rootkit


本地交互： 参考 SK-1.5；


模块自身隐藏：参考 SK-5.1 / SK-5.2 / SK-5.4；


文件/目录隐藏： 参考 SK-4.5;


进程隐藏：参考 SK-7.4；


权限提升： 参考 SK-9.1；



randkit


项目地址: https://github.com/vrasneur/randkit


文件/目录隐藏： 参考 SK-4.4



adore-ng


项目地址：https://github.com/trimpsyw/adore-ng


文件隐藏：高内核版本参考 SK-4.1； 低内核版本参考 SK-4.5；


本地交互：参考 SK-1.7；


权限提升：参考 SK-9.1；


进程隐藏：参考 SK-7.4；



the_colonel


项目地址：https://github.com/bones-codes/the_colonel


本地交互：参考 SK-1.5；


文件隐藏：参考 SK-4.5；


进程隐藏：参考 SK-7.4；


权限提升：参考 SK-9.1；


键盘记录：参考 SK-8.3；



enyelkm


项目地址： https://github.com/David-Reguera-Garcia-Dreg/enyelkm


本地交互： 参考 SK-1.8；


网络隐藏：参考 SK-6.1；


文件隐藏： 参考 SK-4.4； HOOK 参考 SK-3.3；


权限提升：参考 SK-9.1；


shell后门：参考 SK-1.9；会筛选处理 ICMP 和 TCP4 协议的报文；



subversive


项目地址：https://github.com/falk3n/subversive


本地交互： 参考 SK-1.10； HOOK 参考 SK-3.5;


文件隐藏： 参考 SK-4.4； HOOK 参考 SK-3.5；


权限提升： 参考 SK-9.1； HOOK 参考 SK-3.5；


进程隐藏： 参考 SK-7.5； HOOK 参考 SK-3.5;



lkm-rootkit (2)


项目地址: https://github.com/jiayy/lkm-rootkit


模块自身隐藏：参考 SK-5.1 和 SK-5.2；


本地交互： 参考 SK-1.3；


远程后门交互：参考 SK-1.2；


SHELL后门： 交互参考 SK-1.2； 功能实现参考 SK-10.1；并发执行参考 SK-10.5；


键盘记录：参考 SK-8.1；在这个位置还用到了 wake_up_interruptible 中断相关的接口，感兴趣的小伙伴们可以在这里深挖一下，它也是一个在 Linux 内核中比较重要的 API。这里就不展开了；


文件/目录隐藏：参考 SK-4.1; 下钩参考 SK-3.1


进程隐藏：参考 SK-7.4；下钩参考 SK-3.1


网络会话隐藏： 参考 SK-6.1; HOOK参考 SK-3.1；



wukong


项目地址: https://github.com/hanj4096/wukong


模块自身隐藏: 参考 SK-5.1 和 SK-5.2;


本地交互： 参考 SK-1.3；


文件/目录隐藏： 参考 SK-4.5; HOOK 参考 SK-3.1；


进程隐藏： 参考 SK-7.3； HOOK 参考 SK-3.1；


网络会话隐藏： 参考 SK-6.1；



Kernel_Rootkit


项目地址： https://github.com/varshapaidi/Kernel_Rootkit


模块自身隐藏： 参考 SK-5.5；


文件/目录隐藏： 参考 SK-4.6；


进程隐藏： 参考 SK-7.6；


网络会话隐藏： 参考 SK-6.2；


PS： 这个项目似乎是个半成品，其中有大量无用代码，看逻辑似乎是想通过劫持内存分配或内存映射的逻辑实现一些未知功能。



brootus


项目地址： https://github.com/dsmatter/brootus


模块自身隐藏：参考 SK-5.1 和 SK-5.2；


文件/目录隐藏：参考 SK-4.4； HOOK 参考 SK-3.3；


进程隐藏： 参考 SK-7.4;


网络会话隐藏： 参考 SK-6.1；


PS： 这个项目似乎是个半成品，其中有一部分函数有调用过程，却没有实现。



Sutekh


项目地址: https://github.com/PinkP4nther/Sutekh


权限提升： 参考 SK-9.1



LilyOfTheValley


项目地址：https://github.com/En14c/LilyOfTheValley


模块自身隐藏：参考 SK-5.1 和 SK-5.2；


文件/目录隐藏：参考 SK-4.1； HOOK 参考 SK-3.1；


进程隐藏：参考 SK-7.4； HOOK 参考 SK-3.1；


权限提升：参考 SK-9.1；



research-rootkit


项目地址：https://github.com/NoviceLive/research-rootkit


PS: 该项目是个学习性质的项目，并非一个完整的 rootkit 所以这里不展开了。



Out-of-Sight-Out-of-Mind-Rootkit


项目地址：https://github.com/NinnOgTonic/Out-of-Sight-Out-of-Mind-Rootkit


模块自身隐藏： 参考 SK-5.1 和 SK-5.2；


网络交互：参考 SK-1.4； HOOK 参考 SK-3.2；


文件/目录隐藏： 参考 SK-4.4； HOOK 参考 SK-3.1；


PS：项目目前是半成品学习性质项目，有PDF介绍，但功能并未完全实现。目前这里只整理一部分已经完整实现的功能。



LKM-Rootkit 知识点汇总


本节将以知识点汇总的形式介绍各种常见功能的技能点，并为每一个知识点，给出一个编号（SK-X.X），进而标定出相关项目用到了哪种知识点。



配置交互


由于 LKM 工作在内核层，编译安装后，很难更改配置。所以一般都需要维护一个通讯接口，供用户侧随时更新配置信息。整理后发现，常见的通讯接口大致可以归类为下几种情况：


SK-1.1 通过 Netlink 交互（本地）


Netlink 是标准 socket 提供的一种双向通讯协议，可用于本地配置内核模块，

通过socket(PF_NETLINK, SOCK_RAW, NETLINK_USER) 可完成用户侧的初始化。其中NETLINK_USER为双方约定好的私有编号。


内核层的初始化代码截图如下所示：





netlink_kernel_create 这个接口，在不同的内核版本声明不同，当需要做内核适配时，要注意这个接口的声明情况。


SK-1.2 通过筛选 ICMP报文 交互（网络）


这是一种可以从网络环境同内核模块交互的方法。下图是一个 ICMP 对接shell后门的实现片段：





SK-1.3 通过 ioctl 进行交互（本地）


ioctl 是一种针对 I/O 设备定义的交互方式，在 LKM-Rootkit 中也经常遇到，通过约定一个自定义的协议号，可以实现本地的 Rootkit 交互。下图是一段内核中过滤特定协议号的处理逻辑，供参考：





SK-1.4 通过筛选 IP报文 实现交互（网络）A


这是一种通过筛选 IP 报文，实现内核交互的方法，在 Linux 中，对 IPv4报文 的处理，和一个名为 ip_rcv 的函数有关，通过更改这个函数的处理逻辑，可以完成定制的交互逻辑，下图是被用于替换原函数执行逻辑的代码片段，供参考：





可以看出，代码覆盖了 TCP/UDP/ICMP 三种协议的处理机制，实现同内核的交互。


SK-1.5 文件读写（本地）


在内核中，通过对指定文件读写函数的劫持，可以实现用户和内核间的交互，下图是一个为 /proc 目录文件的读写处理函数打 HOOK 的代码片段：





SK-1.6 劫持 kill signal（本地）


有一些内核模块功能很简单，只有单向配置的需求，此时会考虑使用劫持 signal 信号的方案，在 int kill(pid_t pid, int sig); 函数中，第二个参数就是 signal，它使用起来足够简单，只要对 sys_kill 的处理逻辑增加一个 HOOK 点，就可以筛选出特殊的 signal 标记，很容易实现，所以在一些 Rootkit 中会选择这样的方式向内核发送同步指令：





SK-1.7 劫持 lookup（本地）


在 inode_operations 中，有一个 lookup 函数指针，用于查阅相关目录文件。在部分 rootkit 中，会通过改写这个指针中函数的处理逻辑，获知用户正在查阅的文件名，通过对文件名编码，就可以得到用户想要实现的指令了，下图是一个利用该方法解析指令内容的方法：





SK-1.8 中断处理过程劫持（本地）


通过重写内核中的中断处理逻辑，对于正常程序的调用找到原有逻辑执行，筛选出特殊的调用请求。如此可以完成用户和内核模块之间的交互，下图是一个生成新中断处理的逻辑：





SK-1.9 筛选 IP报文 实现交互（网络） B


内核在处理收到的网络报文时，会依次交给不同的解析器进行试图解析。解析器可以通过 dev_add_pack 进行添加，这样就可以筛选出符合需求的特殊报文，从而得到操作指令，下图是一段调用 dev_add_pack 的截图：





SK-1.10 利用 sys_uname（本地）


uname(struct utsname *buf) 函数是一个获取机器名的 API，传入参数是一段空内存，执行返回后，这段内存则被填充上当前机器的相关信息。如果在内核中将处理逻辑改写，就可以利用传入的这个空内存向内核模块传递数据了，同时又不会影响该 API 原有的功能。下图是一个被改写后的 uname 处理逻辑：






syscall_table的获取


在 Linux内核 中，syscall_table(系统调用表) 是一个用来将"系统中断号"映射到"对应功能函数（地址）"的表格。通过修改这个表格中的内容，就可以完成对某些特定功能函数的HOOK操作，进而修改替换函数的原有执行逻辑，最终完成"关键信息的隐藏"。


所以，获取 syscall_table 内存地址是一个非常重要的技能点，它直接决定着 Rootkit 的效果。通过阅读开源代码，大致总结出以下几种获取方式。


SK-2.1 暴力搜索


在 sys_close 是Linux 内核提供的一个函数，它是 syscall_table 中第 "__NR_close" 个元素的值，其中 __NR_close 的值通过内核宏定义，不同的编译环境中，这个值有区别。下图是一个使用该方法的代码截图：





SK-2.2 通过 IDT 搜索


Linux 内核中，syscall_table 是挂在中断描述符表结构下的，所以只要找到中断描述符表，即可确定 syscall_table 的大致范围，同时 syscall_table 中保存的数据又都是系统地址指针，所以此种方法，可相对准确的定位到 syscall_table 结构的位置，下图是具体的代码截图，供参考：





SK-2.3 通过 MSR_LSTAR 定位


在Linux 内核中(arch/x86/include/asm/msr-index.h)，定义了一个 MSR_LSTAR 宏，这是一个和系统调用表执行有关的寄存器，它保存着一个内存地址，实际的 syscall_table 地址将出现在这个地址的后面，确定了大致范围后，即可参考 SK-2.2 的方式，搜索到 syscall_table 地址了，下图是此种方案的代码片段截图：





SK-2.4 通过 kallsyms 获取


在 SK-10.3 中提到了 利用 kallsyms_on_each_symbol 获取内核符号的知识点，其实这个接口也可以被用于获取 syscall_table，在内核中，其符号名为 sys_call_table，一段代码样例如下所示：






内核钩子（HOOK）


想在 Linux 内核中隐藏信息，首先要有办法把自己的代码插入关键位置并执行。这就需要在内核中的特定位置下钩子。本小节整理的就是下钩子的几种技巧。


SK-3.1 Hijack钩子


这是一种直接修改内核代码的钩子，它会将一组控制执行顺序的代码直接插入给定的内存地址，进而让自己的代码得到执行。下图是项目中实际的代码截图：





其中第10行是 push xxx; ret 代码的 hex 数据模版，第11行则是将自己的函数地址填充到模版中，进而达到一个 push new; ret的效果。与之类似的还可能会遇到 mov eax，xxx ; jmp eax 类型的 hex 模版。


SK-3.2 nf_hook 钩子


nf 是 netfilter 的缩写，可以从内核层截流网络报文，完成网络交互。下图是利用 nf_hook 实现icmp协议后门的截图：





SK-3.3 syscall劫持


在 Linux 系统中，syscall_table 中包含了很多系统相关的重要函数指针，针对性的修改这里的指针内容，可以实现插入自定义函数逻辑的效果，下图是一个劫持 getdents64 的代码片段，它先保存了原始的函数（getdents64）地址，然后又在 syscall_table 里放了一个自己实现的 fake_getdents64 函数地址：





SK-3.4 KHOOK 框架


在 Reptile 项目中，用了一个名为 KHOOK 的钩子框架，它是对 SK-3.1 方案的扩展，其在易用性和稳定性中下了一些功夫。在符号查找中，参考了 SK-10.3 的方案。所以这里把它单独提出来作为一个 HOOK 知识点，该项目具体的编码，比较复杂，这里就不展开说了，感兴趣的小伙伴们可以去参考：

https://github.com/milabs/khook 项目，项目中有严谨的描述文档和全部代码，各位可以自行查阅。


SK-3.5 调试模式


IA-32 从 386 开始，引入了调试寄存器和硬件断点，这意味着在 ring0 层，内核代码的任意位置都可以被设置硬件断点，从而实现对内核代码调试的能力。这种调试能力也为 rootkit 开发提供了一种新的控制流劫持的思路，在 https://github.com/falk3n/subversive 这一款 rootkit 中，就是通过此种方式实现 HOOK 的，它先对 syscall_table 的内存地址下了硬件断点，随后当其他代码访问到 syscall_table 结构时，调试代码都会被执行。下图是一段设置内存断点的代码截图，红框圈定的是它在每个CPU上依次下断的调用逻辑：






文件/目录隐藏


SK-4.1 目录文件迭代器


VFS中存在一个名为 file_operations 的接口抽象，其中存在一个目录文件迭代器。如果对这个迭代器的执行逻辑做一下修改，即可完成对指定文件的隐藏，在Rootkit中一般通过修改根目录迭代器的方式隐藏文件/目录。下图是一个获取迭代器函数的代码片段：





SK-4.2 目录隐藏（Reptile）


在 Reptile 中，目录隐藏功能的实现比较特殊，它通过对一组函数打 HOOK 的方式，从而实现一个隐藏目录无法给他人查阅的能力，这些函数有：

fillonedir/filldir/filldir64/compat_fillonedir/compat_filldir/compat_filldir64/__d_lookup


SK-4.3 文件隐藏（Reptile）


在 Reptile 中，文件隐藏是一个临时实现，它是通过对 vfs_read 打 HOOK 实现的。在注释中作者提到: 这不是最佳实现，但它有效，在将来可能会改变。下图是这段函数头的截图：





SK-4.4 下钩 getdents 函数


在 Linux 中 getdents 和 getdents64 是显示目录/文件相关的函数，通过改动这两个函数的执行逻辑，可以实现是否显示一个指定文件的效果。下图是一段用于隐藏文件的代码片段截图，可以看到当 file_hiding 开关打开时，会触发一个特殊的逻辑，以便于不显示受保护的文件：





SK-4.5 劫持 readdir


在 SK-4.1 中提到的 file_operations 接口抽象中，还有一个 readdir 的函数指针，通过修改这个指针，也能达到文件隐藏的效果，下图是一个 Rootkit 中更改这个指针的代码片段：





SK-4.6 替换 file_operations


在 SK-4.1 和 SK-4.5 中看到的是，替换 file_operations 成员成员指针的情况，而如果直接替换这个成员本身，自然也能实现相同的效果，这里就不贴图了。



模块自身的隐藏


SK-5.1: 从模块链表删除自身


在Linux内核中，每一个 struct module 通过一个链表结构(struct list_head)，链接在一起。所有和模块遍历有关的操作，都需要依赖这个链表，如果从这个链表中将自身摘出，则模块自身便无法通过遍历的方式被找到了，下图是一个摘除自身的代码片段，以供参考：





SK-5.2：从文件系统移除模块自身


当模块成功安装后，用户可以从 /sys/modules 看到新模块的一些基本信息。通过对 kobject_del 方法可以移除对应的模块目录，所以在一些 LKM 中也会使用这种方式隐藏自身，下图是一个参考代码：





SK-5.3: 从内核文件树移除模块


在 Linux内核中，有一个内核文件系统，它是通过红黑树的结构组织起来的。这个文件系统运行后将会对应 /proc，/sys 等虚拟文件目录。如果将当前模块对应的结构从这棵树中摘除，也可以达到隐藏 /sys/modules 目录内容的效果。这个方法同 SK-5.2 的区别在于，前者是将内核结构整体从内核删除，而本节是仅删除挂载关系，内核结构还在内存中，下图中红框，就是具体实现的代码片段：





SK-5.4 ：内核模块链表脱钩


除 struct module 结构以外，内核中还有一个 module_kobject.kobj 也是和模块一一对应的，且这个结构也是个通过链表连载一起的结构体。有些 Rootkit 会选择在这里脱钩，实现内核模块的隐藏：





SK-5.5 替换 file_operations


同 SK-4.6 一样，只是替换目标换成了 /sys/modules 目录对应的 file_operations 结构。



网络会话的隐藏


SK-6.1 通过 seq_operations 隐藏网络会话


在 Linux 内核中，网络会话也是一种文件，通过 file 结构可以找到一个名为 seq_operations.show 的函数指针，该函数指针用于生成网络会话相关的信息供展示，通过修改对应的函数，可以实现网络会话对用户显示的隐藏。


下图是获取 tcp4_seq_show 相关函数地址的代码片段，供参考：








如此操作后，就可以通过过滤地址或端口，对特定的网络会话进行屏蔽了。


除 tcp4_seq_show 外，还可能出现 udp4_seq_show/tcp6_seq_show/udp6_seq_show 等几种情况。


SK-6.2 替换 seq_operations


在 SK-6.1 中，是通过 seq_operations 的成员实现会话流控制，如果直接替换 一个新伪造的 seq_operations 到对应位置，也能实现相同的效果，这里就不截图了。



进程隐藏


SK-7.1 进程隐藏（Reptile）


在 Reptile 中隐藏进程这个功能的实现，有些特殊，它是通过"给一组函数打hook"的方式，最终完成进程隐藏的，在这个过程中，被打 HOOK的函数 有： audit_alloc/copy_creds/exit_creds/find_task_by_vpid/vfs_statx/sys_kill/__x64_sys_kill/tgid_iter/load_elf_binary 。


当一个进程(task)被设置为隐藏后，Reptile 会为其 task_struct.flags 设置一个标记 0x80000000，有趣的是，这个标记在 Linux内核 中代表着 PF_SUSPEND_TASK，是一个进程无法冻结的标记，这会让那些正常设置这个标记的进程也出现隐身效果。


SK-7.2 进程隐藏（tasks链表摘除)


在 Linux 中，process/thread 会对应到一个名为 struct task_struct 的结构体，在这个结构体中有一个 tasks 链表成员，它作为一个侵入式的双向链表会将当前 task 同其他 task 链接到一起。有些 Rootkit 会将被保护的 task 从这个链表中摘除，进而无法遍历访问。脱钩方法也很简单，只需要在模块中增加一行代码 list_del_init(&current->tasks); 即可。在一部分 rootkit 中，会考虑自行编码脱钩的逻辑代码，下图就是一个展开实现的样例，它们背后的实现思路都是相同的的：





SK-7.3 隐藏 "/proc" 目录信息 A


在 SK-4.1 中，提到了文件目录迭代器的使用。其实 /proc 目录中的内容，也可以通过目录文件迭代器实现目录内容的隐藏，下图是一个获取 /proc迭代器 的代码片段：





SK-7.4 隐藏 "/proc" 目录信息 B


在 SK-4.5 中 提到了通过更改 filldir/readdir 指针隐藏文件的方法，该方法也适用于 /proc 目录下的文件。这里就不贴代码图了。


SK-7.5 通过 syscall hook 隐藏


在 syscall 调用中，有一系列和进程操作相关的函数，如果让这些函数统统对需要隐藏的进程 pid 执行失败，如此 ring3 层便无法查阅到进程的有关信息了，这些函数的列表如下：


getpgid
getsid
setpgid
kill
tgkill
tkill
sched_setscheduler
sched_setparam
sched_getscheduler
sched_getparam
sched_setaffinity
sched_getaffinity
sched_rr_get_interval
wait4
waitid
rt_tgsigqueueinfo
rt_sigqueueinfo
prlimit64
ptrace
migrate_pages
move_pages
get_robust_list
perf_event_open



SK-7.6 替换 file_operations


同 SK-4.6 类似，只是替换目标为 /proc 目录对应的 file_operations 结构.



键盘记录


SK-8.1 键盘记录器


内核提供了register_keyboard_notifier 和 unregister_keyboard_notifier 两个方法，用于增加或卸载键盘监视器，下图是使用该机制实现键盘记录功能的代码片段：





SK-8.2 键盘记录（/dev/ttyS0读写劫持）


在 Linux 中， /dev/ttyS0 是一个字符设备（虚拟），可以简单的将之理解为是标准输入，通过重写它的 read 函数，可以捕获用户从键盘输入的内容，下图是一个劫持的代码样例：





SK-8.3 /dev/input/event 监听


这是一个和输入有关的设备文件，通过监听该文件的变化情况，可以得到用户正在输入的内容。


下图是选自 https://github.com/bones-codes/the_colonel/blob/devx/lkm/col_kl.c 的代码片段，该代码没有合并到 master 分支，似乎是个未完成的版本，仅供参考：






权限提升


SK-9.1 本地权限获取


对于 Rootkit 来说，有时需要为普通进程赋予管理员权限。这就涉及到一个名为 cred 的结构体，另一方面，当前进程的 cred 可以通过 prepare_creds 可以获取。下图就是一段通过该方式提权的代码段：






其他技术点


SK-10.1 执行系统命令


内核提供了 call_usermodehelper 函数，用于在用户态运行一个可执行文件。下图是使用该函数，实现shell后门的代码片段：





SK-10.2 从内核启动一个线程


内核提供了 kernel_create 函数，用于从内核态启动一个用户态线程。配置好 subprocess_info 实例后，即可完成启动。





SK-10.3： 内核符号地址获取 A


Linux 内核提供了一个名为 kallsyms_on_each_symbol 的 API，用来获取各种符号在内存中的地址。但该 API 需要 Linux内核 在编译时打开 CONFIG_KALLSYMS 宏，好在一些常见的Linux发行版中，默认都支持了这个宏，所以通配性还过得去。下图是基于该API抽象后的获取指定符号地址的代码片段：





SK-10.4：内核符号地址获取 B


在 SK-10.3 中提到了通过 kallsyms_on_each_symbol 获取符号地址的方法，其实，与这个API相对的还有另一个名为 kallsyms_lookup_name 的孪生API也可以获取一部分符号地址，这里就不贴截图了，感兴趣的小伙伴们自行找源码探索一下。


SK-10.5： 通过工作队列执行任务分发


在 Linux内核中，有一个工作队列(workqueue)的机制，可以将特定工作（以函数为单位）分发到不同的CPU中去执行，产生一个内核并发执行任务的效果。编码者将“任务提交”和“任务执行”分开实现，分别对应到 投递 workqueue 和 消费 workqueue ，Linux内核就会可以辅助完成任务的分发和执行，是一个极为方便使用的 API。


在使用上，它需要先通过 create_workqueue 初始化一个新队列，当有新工作需要执行时，再通过 queue_work 方法向队列提交这个 work。下图是使用该方法的一段代码截图，供参考：






参考资料


https://wiki.osdev.org/Inline_Assembly/Examples#RDMSR

{"version":"0.3.1","atoms":[],"cards":[["markdown",{"markdown":"# 引\n\nLKM 是 Linux Kernel Module 的缩写，所以这里 LKM-Rootkit 的含义，是\"基于Linux内核模块实现的 Rootkit\"。由于前段时间分析了skidmap的细节，所以想到针对性的梳理一下这方面的内容。在集中精力读了一些开源代码后，整理一些手册性质的文字记录下来，供他人日后查阅。\n\n# 开源项目参考\n\n本节项目主要来源为 [awesome-linux-rootkits](https://github.com/milabs/awesome-linux-rootkits) 中提到的 LKM 相关的项目。\n\nRootkit 作为一个隐藏工具，其主要设计目标是为了隐藏攻击痕迹，本节将对开源LKM进行一个汇总梳理。每个项目中，可能包含多个实现细节，通过编号（SK-X.X）对应到知识点汇总章节中。\n\n## rooty\n\n**项目地址**: https://github.com/jermeyyy/rooty\n\n**模块自身隐藏**： SK-5.1 加 SK-5.2 实现内核模块的隐藏；\n\n**本地交互**： 利用 SK-1.3 向内核模块发送控制指令；利用 SK-3.1 筛选 ioctl 命令；\n\n**远程后门交互**： 参考 SK-1.2;\n\n**获取root权限**：参考 SK-9.1 ；\n\n**SHELL后门**： 功能实现参考 SK-10.2 启动了一个 sshd 任务处理shell 交互；\n\n**VNC后门**：参考 SK-10.2 启动了一个 vnc 服务。\n\n**文件/目录隐藏**：参考 SK-3.1 和 SK-4.1 更改根目录(\"/\")的迭代器;\n\n**进程隐藏**： 参考 SK-3.1 和 SK-7.3;\n\n**网络会话隐藏**：参考 SK-6.1； HOOK 参考 SK-3.1；\n\n**键盘记录**：利用 SK-8.1 实现键盘记录，并将结果记录到一个指定文件。\n\n## Reptile\n\n**项目地址**: https://github.com/f0rb1dd3n/Reptile\n\n**本地交互**: 利用 SK-1.3 向内核发送控制指令；利用 SK-3.1 筛选 ioctl 命令；\n\n**远程后门交互**： 参考 SK-1.4；\n\n**模块自身隐藏**： 参考 SK-5.1；\n\n**获取root权限**：参考 SK-9.1；\n\n**网络会话隐藏**：参考 SK-6.1；HOOK参考 SK-3.4；\n\n**进程隐藏**：参考 SK-7.1；HOOK参考 SK-3.4；\n\n**目录隐藏**：参考 SK-4.2；HOOK参考 SK-3.4；\n\n**文件隐藏**：参考 SK-4.3；HOOK参考 SK-3.4；\n\n\n## rkduck\n\n**项目地址**：https://github.com/QuokkaLight/rkduck\n\n**本地交互**：rkduck提供了一个控制程序，user-kernel的通讯方式参考 SK-1.1;\n\n**SHELL后门**：交互协议参考 SK-1.2； 功能实现参考 SK-10.1;\n\n**文件/目录隐藏**： 利用 SK-3.1 和 SK-4.1 更改根目录(\"/\")迭代器;\n\n**进程隐藏**： 参考 SK-3.1 和 SK-7.3;\n\n**SSH后门**：参考 SK-10.1 植入公钥到 `/root/.ssh/authorized_keys` 文件;\n\n**键盘记录**： 利用 SK-8.1实现键盘记录，并利用 SK-10.1 执行 `scp` 命令 将记录结果回传攻击者;\n\n## lkm-rootkit\n\n**项目地址**： https://github.com/croemheld/lkm-rootkit\n\n**模块自身隐藏**： 参考 SK-5.1 和 SK-5.3;\n\n**远程后门交互**： 参考 SK-10.2 启动一个UDP监听服务线程，接受从网络发来的控制指令；\n\n**文件/目录隐藏**： 参考 SK-2.3 找到 syscall_table 位置，参考 SK-4.4 隐藏文件；\n\n**键盘记录**： 参考 SK-8.2 ，记录结果会通过远程交互的UDP会话发回去；\n\n**进程隐藏**：参考 SK-8.1；HOOK 参考 SK-3.1；\n\n**网络会话隐藏**： 参考 SK-6.1; HOOK参考 SK-3.1；\n\n**权限提升**： 参考 SK-9.1;\n\n\n## suterusu\n\n**项目地址**：https://github.com/mncoppola/suterusu\n\n**模块自身隐藏**：参考 SK-5.1 和 SK-5.2；\n\n**本地交互**： 参考 SK-1.3；\n\n**远程后门交互**：参考 SK-1.2；\n\n**SHELL后门**： 交互参考 SK-1.2； 功能实现参考 SK-10.1；并发执行参考 SK-10.5；\n\n**键盘记录**：参考 SK-8.1；在这个位置还用到了 wake_up_interruptible 中断相关的接口，感兴趣的小伙伴们可以在这里深挖一下，它也是一个在 Linux 内核中比较重要的 API。这里就不展开了；\n\n**文件/目录隐藏**：参考 SK-4.1; 下钩参考 SK-3.1\n\n**进程隐藏**：参考 SK-7.4；下钩参考 SK-3.1\n\n**网络会话隐藏**： 参考 SK-6.1； 下钩参考 SK-3.1\n\n\n## ARP-Rootkit\n\n**项目地址**：https://github.com/romeroperezabel/ARP-RootKit\n\nPS： 截止本文编写时，该项目已经从github下架，无法找到，希望读者中有人能够贡献一波，供我等分析。\n\n## rootkit\n\n**项目地址**：https://github.com/nurupo/rootkit\n\n**本地交互**：项目中存在 client 程序，通过 SK-1.5 的方式配置内核模块；下钩参考 SK-3.1;\n\n**模块自身隐藏**： 参考 SK-5.1 和 SK-5.3；下钩参考 SK-3.1；\n\n**进程隐藏**： 参考 SK-5.3; 下钩参考 SK-3.1;\n\n**文件/目录隐藏**： 参考 SK-4.1；\n\n## Diamorphine\n\n**项目地址**： https://github.com/m0nad/Diamorphine\n\n**本地交互**：参考 SK-1.6；\n\n**模块自身隐藏**： 参考 SK-5.1;\n\n**文件/目录隐藏**： 参考 SK-4.4； HOOK 参考 SK-3.3；获取系统调用表参考 SK-2.1，在较新系统版本中参考 SK-10.4；\n\n**权限提升**：参考 SK-9.1；\n\n## rootkit （2）\n\n**项目地址**： https://github.com/ivyl/rootkit\n\n**本地交互**： 参考 SK-1.5；\n\n**模块自身隐藏**：参考 SK-5.1 / SK-5.2 / SK-5.4；\n\n**文件/目录隐藏**： 参考 SK-4.5;\n\n**进程隐藏**：参考 SK-7.4；\n\n**权限提升**： 参考 SK-9.1；\n\n## randkit\n\n**项目地址**: https://github.com/vrasneur/randkit\n\n**文件/目录隐藏**： 参考 SK-4.4\n\n## adore-ng\n\n**项目地址**：https://github.com/trimpsyw/adore-ng\n\n**文件隐藏**：高内核版本参考 SK-4.1； 低内核版本参考 SK-4.5；\n\n**本地交互**：参考 SK-1.7；\n\n**权限提升**：参考 SK-9.1；\n\n**进程隐藏**：参考 SK-7.4；\n\n\n## the_colonel\n\n**项目地址**：https://github.com/bones-codes/the_colonel\n\n**本地交互**：参考 SK-1.5；\n\n**文件隐藏**：参考 SK-4.5；\n\n**进程隐藏**：参考 SK-7.4；\n\n**权限提升**：参考 SK-9.1；\n\n**键盘记录**：参考 SK-8.3； \n\n## enyelkm\n\n**项目地址**： https://github.com/David-Reguera-Garcia-Dreg/enyelkm\n\n**本地交互**： 参考 SK-1.8；\n\n**网络隐藏**：参考 SK-6.1；\n\n**文件隐藏**： 参考 SK-4.4； HOOK 参考 SK-3.3；\n\n**权限提升**：参考 SK-9.1； \n\n**shell后门**：参考 SK-1.9；会筛选处理 ICMP 和 TCP4 协议的报文；\n\n## subversive\n\n**项目地址**：https://github.com/falk3n/subversive\n\n**本地交互**： 参考 SK-1.10； HOOK 参考 SK-3.5;\n\n**文件隐藏**： 参考 SK-4.4； HOOK 参考 SK-3.5；\n\n**权限提升**： 参考 SK-9.1； HOOK 参考 SK-3.5；\n\n**进程隐藏**： 参考 SK-7.5； HOOK 参考 SK-3.5;\n\n\n## lkm-rootkit (2)\n\n**项目地址**: https://github.com/jiayy/lkm-rootkit\n\n**模块自身隐藏**：参考 SK-5.1 和 SK-5.2；\n\n**本地交互**： 参考 SK-1.3；\n\n**远程后门交互**：参考 SK-1.2；\n\n**SHELL后门**： 交互参考 SK-1.2； 功能实现参考 SK-10.1；并发执行参考 SK-10.5；\n\n**键盘记录**：参考 SK-8.1；在这个位置还用到了 wake_up_interruptible 中断相关的接口，感兴趣的小伙伴们可以在这里深挖一下，它也是一个在 Linux 内核中比较重要的 API。这里就不展开了；\n\n**文件/目录隐藏**：参考 SK-4.1; 下钩参考 SK-3.1\n\n**进程隐藏**：参考 SK-7.4；下钩参考 SK-3.1\n\n**网络会话隐藏**： 参考 SK-6.1; HOOK参考 SK-3.1；\n\n## wukong\n\n**项目地址**: https://github.com/hanj4096/wukong\n\n**模块自身隐藏**: 参考 SK-5.1 和 SK-5.2;\n\n**本地交互**： 参考 SK-1.3；\n\n**文件/目录隐藏**： 参考 SK-4.5; HOOK 参考 SK-3.1；\n\n**进程隐藏**： 参考 SK-7.3； HOOK 参考 SK-3.1；\n\n**网络会话隐藏**： 参考 SK-6.1；\n\n## Kernel_Rootkit\n\n**项目地址**： https://github.com/varshapaidi/Kernel_Rootkit\n\n**模块自身隐藏**： 参考 SK-5.5；\n\n**文件/目录隐藏**： 参考 SK-4.6；\n\n**进程隐藏**： 参考 SK-7.6；\n\n**网络会话隐藏**： 参考 SK-6.2；\n\nPS： 这个项目似乎是个半成品，其中有大量无用代码，看逻辑似乎是想通过劫持内存分配或内存映射的逻辑实现一些未知功能。\n\n## brootus\n\n**项目地址**： https://github.com/dsmatter/brootus\n\n**模块自身隐藏**：参考 SK-5.1 和 SK-5.2；\n\n**文件/目录隐藏**：参考 SK-4.4； HOOK 参考 SK-3.3；\n\n**进程隐藏**： 参考 SK-7.4;\n\n**网络会话隐藏**： 参考 SK-6.1；\n\nPS： 这个项目似乎是个半成品，其中有一部分函数有调用过程，却没有实现。\n\n\n## Sutekh\n\n**项目地址**: https://github.com/PinkP4nther/Sutekh\n\n**权限提升**： 参考 SK-9.1\n\n## LilyOfTheValley\n\n**项目地址**：https://github.com/En14c/LilyOfTheValley\n\n**模块自身隐藏**：参考 SK-5.1 和 SK-5.2；\n\n**文件/目录隐藏**：参考 SK-4.1； HOOK 参考 SK-3.1；\n\n**进程隐藏**：参考  SK-7.4； HOOK 参考 SK-3.1；\n\n**权限提升**：参考 SK-9.1；\n\n## research-rootkit\n\n**项目地址**：https://github.com/NoviceLive/research-rootkit\n\nPS: 该项目是个学习性质的项目，并非一个完整的 `rootkit` 所以这里不展开了。\n\n## Out-of-Sight-Out-of-Mind-Rootkit\n\n**项目地址**：https://github.com/NinnOgTonic/Out-of-Sight-Out-of-Mind-Rootkit\n\n**模块自身隐藏**： 参考 SK-5.1 和 SK-5.2；\n\n**网络交互**：参考 SK-1.4； HOOK 参考 SK-3.2；\n\n**文件/目录隐藏**： 参考 SK-4.4； HOOK 参考 SK-3.1；\n\nPS：项目目前是半成品学习性质项目，有PDF介绍，但功能并未完全实现。目前这里只整理一部分已经完整实现的功能。\n\n# LKM-Rootkit 知识点汇总\n\n本节将以知识点汇总的形式介绍各种常见功能的技能点，并为每一个知识点，给出一个编号（SK-X.X），进而标定出相关项目用到了哪种知识点。\n\n## 配置交互\n\n由于 LKM 工作在内核层，编译安装后，很难更改配置。所以一般都需要维护一个通讯接口，供用户侧随时更新配置信息。整理后发现，常见的通讯接口大致可以归类为下几种情况：\n\n**SK-1.1** 通过 Netlink 交互（本地）\n\nNetlink 是标准 socket 提供的一种双向通讯协议，可用于本地配置内核模块，\n通过`socket(PF_NETLINK, SOCK_RAW, NETLINK_USER)` 可完成用户侧的初始化。其中NETLINK_USER为双方约定好的私有编号。\n\n内核层的初始化代码截图如下所示：\n\n![netlink_kernel_create](__GHOST_URL__/content/images/2021/01/netlink_kernel_create.png)\n\nnetlink_kernel_create 这个接口，在不同的内核版本声明不同，当需要做内核适配时，要注意这个接口的声明情况。\n\n**SK-1.2** 通过筛选 ICMP报文 交互（网络）\n\n这是一种可以从网络环境同内核模块交互的方法。下图是一个 ICMP 对接shell后门的实现片段：\n\n![icmp_sendmsg](__GHOST_URL__/content/images/2021/01/icmp_sendmsg.png)\n\n**SK-1.3** 通过 ioctl 进行交互（本地）\n\nioctl 是一种针对 I/O 设备定义的交互方式，在 LKM-Rootkit 中也经常遇到，通过约定一个自定义的协议号，可以实现本地的 Rootkit 交互。下图是一段内核中过滤特定协议号的处理逻辑，供参考：\n\n![ioctl_config](__GHOST_URL__/content/images/2021/01/ioctl_config.png)\n\n**SK-1.4** 通过筛选 IP报文 实现交互（网络）A\n\n这是一种通过筛选 IP 报文，实现内核交互的方法，在 Linux 中，对 IPv4报文 的处理，和一个名为  ip_rcv 的函数有关，通过更改这个函数的处理逻辑，可以完成定制的交互逻辑，下图是被用于替换原函数执行逻辑的代码片段，供参考：\n\n![khook_ip_rcv](__GHOST_URL__/content/images/2021/01/khook_ip_rcv.png)\n\n可以看出，代码覆盖了 TCP/UDP/ICMP 三种协议的处理机制，实现同内核的交互。\n\n**SK-1.5** 文件读写（本地）\n\n在内核中，通过对指定文件读写函数的劫持，可以实现用户和内核间的交互，下图是一个为 `/proc` 目录文件的读写处理函数打 HOOK 的代码片段：\n\n![local_file_rw_hook](__GHOST_URL__/content/images/2021/01/local_file_rw_hook.png)\n\n**SK-1.6**  劫持 kill signal（本地）\n\n有一些内核模块功能很简单，只有单向配置的需求，此时会考虑使用劫持 signal 信号的方案，在 `int kill(pid_t pid, int sig);` 函数中，第二个参数就是 signal，它使用起来足够简单，只要对 `sys_kill` 的处理逻辑增加一个 HOOK 点，就可以筛选出特殊的 signal 标记，很容易实现，所以在一些 Rootkit 中会选择这样的方式向内核发送同步指令：\n\n![hack_kill_sig](__GHOST_URL__/content/images/2021/01/hack_kill_sig.png)\n\n**SK-1.7** 劫持 lookup（本地）\n\n在 `inode_operations` 中，有一个 lookup 函数指针，用于查阅相关目录文件。在部分 rootkit 中，会通过改写这个指针中函数的处理逻辑，获知用户正在查阅的文件名，通过对文件名编码，就可以得到用户想要实现的指令了，下图是一个利用该方法解析指令内容的方法：\n\n![fake_lookup](__GHOST_URL__/content/images/2021/01/fake_lookup.png)\n\n**SK-1.8** 中断处理过程劫持（本地）\n\n通过重写内核中的中断处理逻辑，对于正常程序的调用找到原有逻辑执行，筛选出特殊的调用请求。如此可以完成用户和内核模块之间的交互，下图是一个生成新中断处理的逻辑：\n\n![new_idt](__GHOST_URL__/content/images/2021/01/new_idt.png)\n\n\n**SK-1.9** 筛选 IP报文 实现交互（网络） B\n\n内核在处理收到的网络报文时，会依次交给不同的解析器进行试图解析。解析器可以通过 `dev_add_pack` 进行添加，这样就可以筛选出符合需求的特殊报文，从而得到操作指令，下图是一段调用 `dev_add_pack` 的截图：\n\n![dev_add_pack_eth](__GHOST_URL__/content/images/2021/01/dev_add_pack_eth.png)\n\n**SK-1.10** 利用 sys_uname（本地）\n\n`uname(struct utsname *buf)` 函数是一个获取机器名的 API，传入参数是一段空内存，执行返回后，这段内存则被填充上当前机器的相关信息。如果在内核中将处理逻辑改写，就可以利用传入的这个空内存向内核模块传递数据了，同时又不会影响该 API 原有的功能。下图是一个被改写后的 `uname` 处理逻辑：\n\n![uname_transmit](__GHOST_URL__/content/images/2021/01/uname_transmit.png)\n\n## syscall_table的获取\n\n在 Linux内核 中，syscall_table(系统调用表) 是一个用来将\"系统中断号\"映射到\"对应功能函数（地址）\"的表格。通过修改这个表格中的内容，就可以完成对某些特定功能函数的HOOK操作，进而修改替换函数的原有执行逻辑，最终完成\"关键信息的隐藏\"。\n\n所以，获取 syscall_table 内存地址是一个非常重要的技能点，它直接决定着 Rootkit 的效果。通过阅读开源代码，大致总结出以下几种获取方式。\n\n**SK-2.1** 暴力搜索\n\n在 sys_close 是Linux 内核提供的一个函数，它是 syscall_table 中第 \"\\_\\_NR\\_close\" 个元素的值，其中 \\_\\_NR\\_close 的值通过内核宏定义，不同的编译环境中，这个值有区别。下图是一个使用该方法的代码截图：\n\n![find_syscall_table_violence](__GHOST_URL__/content/images/2021/01/find_syscall_table_violence.png)\n\n**SK-2.2** 通过 IDT 搜索\n\nLinux 内核中，syscall_table 是挂在中断描述符表结构下的，所以只要找到中断描述符表，即可确定 syscall_table 的大致范围，同时 syscall_table 中保存的数据又都是系统地址指针，所以此种方法，可相对准确的定位到 syscall_table 结构的位置，下图是具体的代码截图，供参考：\n\n![find_syscall_tbl_from_idtr](__GHOST_URL__/content/images/2021/01/find_syscall_tbl_from_idtr.png)\n\n**SK-2.3** 通过 MSR_LSTAR 定位\n\n在Linux 内核中(arch/x86/include/asm/msr-index.h)，定义了一个 MSR_LSTAR 宏，这是一个和系统调用表执行有关的寄存器，它保存着一个内存地址，实际的 syscall_table 地址将出现在这个地址的后面，确定了大致范围后，即可参考 SK-2.2 的方式，搜索到 syscall_table 地址了，下图是此种方案的代码片段截图：\n\n![syscall_MSR_LSTAR](__GHOST_URL__/content/images/2021/01/syscall_MSR_LSTAR.png)\n\n**SK-2.4** 通过 kallsyms 获取\n\n在 SK-10.3 中提到了 利用 `kallsyms_on_each_symbol` 获取内核符号的知识点，其实这个接口也可以被用于获取 `syscall_table`，在内核中，其符号名为 `sys_call_table`，一段代码样例如下所示：\n\n![kallsym_find_syscall](__GHOST_URL__/content/images/2021/01/kallsym_find_syscall.png)\n\n\n\n## 内核钩子（HOOK）\n\n想在 Linux 内核中隐藏信息，首先要有办法把自己的代码插入关键位置并执行。这就需要在内核中的特定位置下钩子。本小节整理的就是下钩子的几种技巧。\n\n**SK-3.1** Hijack钩子\n\n这是一种直接修改内核代码的钩子，它会将一组控制执行顺序的代码直接插入给定的内存地址，进而让自己的代码得到执行。下图是项目中实际的代码截图：\n\n![hijack_hook_push_ret-1](__GHOST_URL__/content/images/2021/01/hijack_hook_push_ret-1.png)\n\n其中第10行是 `push xxx; ret` 代码的 hex 数据模版，第11行则是将自己的函数地址填充到模版中，进而达到一个 `push new; ret`的效果。与之类似的还可能会遇到 `mov eax，xxx ; jmp eax ` 类型的 hex 模版。\n\n**SK-3.2** nf_hook 钩子\n\nnf 是 netfilter 的缩写，可以从内核层截流网络报文，完成网络交互。下图是利用 nf_hook 实现icmp协议后门的截图：\n\n![nf_hook_backdoor](__GHOST_URL__/content/images/2021/01/nf_hook_backdoor.png)\n\n**SK-3.3** syscall劫持\n\n在 Linux 系统中，syscall_table 中包含了很多系统相关的重要函数指针，针对性的修改这里的指针内容，可以实现插入自定义函数逻辑的效果，下图是一个劫持 `getdents64` 的代码片段，它先保存了原始的函数（`getdents64`）地址，然后又在 syscall_table 里放了一个自己实现的 `fake_getdents64` 函数地址：\n\n![patch_syscall_table](__GHOST_URL__/content/images/2021/01/patch_syscall_table.png)\n\n**SK-3.4** KHOOK 框架\n\n在 Reptile 项目中，用了一个名为 KHOOK 的钩子框架，它是对 SK-3.1 方案的扩展，其在易用性和稳定性中下了一些功夫。在符号查找中，参考了 SK-10.3 的方案。所以这里把它单独提出来作为一个 HOOK 知识点，该项目具体的编码，比较复杂，这里就不展开说了，感兴趣的小伙伴们可以去参考：\nhttps://github.com/milabs/khook 项目，项目中有严谨的描述文档和全部代码，各位可以自行查阅。\n\n\n**SK-3.5** 调试模式\n\nIA-32 从 386 开始，引入了调试寄存器和硬件断点，这意味着在 ring0 层，内核代码的任意位置都可以被设置硬件断点，从而实现对内核代码调试的能力。这种调试能力也为 rootkit 开发提供了一种新的控制流劫持的思路，在 https://github.com/falk3n/subversive 这一款 rootkit 中，就是通过此种方式实现 HOOK 的，它先对 syscall_table 的内存地址下了硬件断点，随后当其他代码访问到 syscall_table 结构时，调试代码都会被执行。下图是一段设置内存断点的代码截图，红框圈定的是它在每个CPU上依次下断的调用逻辑：\n\n![cpu_hardware_debug-1](__GHOST_URL__/content/images/2021/01/cpu_hardware_debug-1.png)\n\n\n## 文件/目录隐藏\n\n**SK-4.1** 目录文件迭代器\n\nVFS中存在一个名为 `file_operations` 的接口抽象，其中存在一个目录文件迭代器。如果对这个迭代器的执行逻辑做一下修改，即可完成对指定文件的隐藏，在Rootkit中一般通过修改根目录迭代器的方式隐藏文件/目录。下图是一个获取迭代器函数的代码片段：\n\n![vfs_get_iterate](__GHOST_URL__/content/images/2021/01/vfs_get_iterate.png)\n\n**SK-4.2** 目录隐藏（Reptile）\n\n在 Reptile 中，目录隐藏功能的实现比较特殊，它通过对一组函数打 HOOK 的方式，从而实现一个隐藏目录无法给他人查阅的能力，这些函数有：\n`fillonedir`/`filldir`/`filldir64`/`compat_fillonedir`/`compat_filldir`/`compat_filldir64`/`__d_lookup`\n\n**SK-4.3** 文件隐藏（Reptile）\n\n在 Reptile 中，文件隐藏是一个临时实现，它是通过对 vfs_read 打 HOOK 实现的。在注释中作者提到: `这不是最佳实现，但它有效，在将来可能会改变`。下图是这段函数头的截图：\n\n![reptile_hide_file](__GHOST_URL__/content/images/2021/01/reptile_hide_file.png)\n\n**SK-4.4** 下钩 getdents 函数\n\n在 Linux 中 `getdents` 和 `getdents64` 是显示目录/文件相关的函数，通过改动这两个函数的执行逻辑，可以实现是否显示一个指定文件的效果。下图是一段用于隐藏文件的代码片段截图，可以看到当 file_hiding 开关打开时，会触发一个特殊的逻辑，以便于不显示受保护的文件：\n\n![fake_getdents](__GHOST_URL__/content/images/2021/01/fake_getdents.png)\n\n**SK-4.5** 劫持 readdir\n\n在 SK-4.1 中提到的 `file_operations` 接口抽象中，还有一个 readdir 的函数指针，通过修改这个指针，也能达到文件隐藏的效果，下图是一个 Rootkit 中更改这个指针的代码片段：\n\n![fake_fs_readdir](__GHOST_URL__/content/images/2021/01/fake_fs_readdir.png)\n\n**SK-4.6** 替换 file_operations\n\n在 SK-4.1 和 SK-4.5 中看到的是，替换 `file_operations` 成员成员指针的情况，而如果直接替换这个成员本身，自然也能实现相同的效果，这里就不贴图了。\n\n\n## 模块自身的隐藏\n\n**SK-5.1**: 从模块链表删除自身\n\n在Linux内核中，每一个 `struct module` 通过一个链表结构(struct list_head)，链接在一起。所有和模块遍历有关的操作，都需要依赖这个链表，如果从这个链表中将自身摘出，则模块自身便无法通过遍历的方式被找到了，下图是一个摘除自身的代码片段，以供参考：\n\n![hide_module_from_lsmod](__GHOST_URL__/content/images/2021/01/hide_module_from_lsmod.png)\n\n**SK-5.2**：从文件系统移除模块自身\n\n当模块成功安装后，用户可以从 `/sys/modules` 看到新模块的一些基本信息。通过对 `kobject_del` 方法可以移除对应的模块目录，所以在一些 LKM 中也会使用这种方式隐藏自身，下图是一个参考代码：\n\n![kobject_module_del](__GHOST_URL__/content/images/2021/01/kobject_module_del.png)\n\n**SK-5.3**: 从内核文件树移除模块\n\n在 Linux内核中，有一个内核文件系统，它是通过红黑树的结构组织起来的。这个文件系统运行后将会对应 `/proc`，`/sys` 等虚拟文件目录。如果将当前模块对应的结构从这棵树中摘除，也可以达到隐藏 `/sys/modules` 目录内容的效果。这个方法同 SK-5.2 的区别在于，前者是将内核结构整体从内核删除，而本节是仅删除挂载关系，内核结构还在内存中，下图中红框，就是具体实现的代码片段：\n\n![module_erase_rbtree](__GHOST_URL__/content/images/2021/01/module_erase_rbtree.png)\n\n**SK-5.4** ：内核模块链表脱钩\n\n除 `struct module` 结构以外，内核中还有一个 `module_kobject.kobj` 也是和模块一一对应的，且这个结构也是个通过链表连载一起的结构体。有些 Rootkit 会选择在这里脱钩，实现内核模块的隐藏：\n\n![list_del_kkobj_kobj](__GHOST_URL__/content/images/2021/01/list_del_kkobj_kobj.png)\n\n**SK-5.5** 替换 file_operations\n\n同 SK-4.6 一样，只是替换目标换成了 `/sys/modules` 目录对应的 file_operations 结构。\n\n## 网络会话的隐藏\n\n**SK-6.1** 通过 seq_operations 隐藏网络会话\n\n在 Linux 内核中，网络会话也是一种文件，通过 file 结构可以找到一个名为 `seq_operations.show` 的函数指针，该函数指针用于生成网络会话相关的信息供展示，通过修改对应的函数，可以实现网络会话对用户显示的隐藏。\n\n下图是获取 `tcp4_seq_show` 相关函数地址的代码片段，供参考：\n\n![call_get_seq_show](__GHOST_URL__/content/images/2021/01/call_get_seq_show.png)\n\n![process_get_seq_show](__GHOST_URL__/content/images/2021/01/process_get_seq_show.png)\n\n如此操作后，就可以通过过滤地址或端口，对特定的网络会话进行屏蔽了。\n\n除 `tcp4_seq_show` 外，还可能出现 `udp4_seq_show`/`tcp6_seq_show`/`udp6_seq_show` 等几种情况。\n\n**SK-6.2** 替换 seq_operations\n\n在 SK-6.1 中，是通过 seq_operations 的成员实现会话流控制，如果直接替换 一个新伪造的 seq_operations 到对应位置，也能实现相同的效果，这里就不截图了。\n\n## 进程隐藏\n\n**SK-7.1** 进程隐藏（Reptile）\n\n在 Reptile 中隐藏进程这个功能的实现，有些特殊，它是通过\"给一组函数打hook\"的方式，最终完成进程隐藏的，在这个过程中，被打 HOOK的函数 有： `audit_alloc`/`copy_creds`/`exit_creds`/`find_task_by_vpid`/`vfs_statx`/`sys_kill`/`__x64_sys_kill`/`tgid_iter`/`load_elf_binary` 。\n\n当一个进程(task)被设置为隐藏后，Reptile 会为其 task_struct.flags 设置一个标记 0x80000000，有趣的是，这个标记在 Linux内核 中代表着 PF_SUSPEND_TASK，是一个进程无法冻结的标记，这会让那些正常设置这个标记的进程也出现隐身效果。\n\n**SK-7.2** 进程隐藏（tasks链表摘除)\n\n在 Linux 中，process/thread 会对应到一个名为 struct task_struct 的结构体，在这个结构体中有一个 tasks 链表成员，它作为一个侵入式的双向链表会将当前 task 同其他 task 链接到一起。有些 Rootkit 会将被保护的 task 从这个链表中摘除，进而无法遍历访问。脱钩方法也很简单，只需要在模块中增加一行代码 `list_del_init(&current->tasks);` 即可。在一部分 rootkit 中，会考虑自行编码脱钩的逻辑代码，下图就是一个展开实现的样例，它们背后的实现思路都是相同的的：\n\n![tasks_list_del](__GHOST_URL__/content/images/2021/01/tasks_list_del.png)\n\n**SK-7.3** 隐藏 \"/proc\" 目录信息 A\n\n在 SK-4.1 中，提到了文件目录迭代器的使用。其实 `/proc` 目录中的内容，也可以通过目录文件迭代器实现目录内容的隐藏，下图是一个获取 `/proc迭代器` 的代码片段：\n\n![get_proc_iterate](__GHOST_URL__/content/images/2021/01/get_proc_iterate.png)\n\n**SK-7.4** 隐藏 \"/proc\" 目录信息 B\n\n在 SK-4.5 中 提到了通过更改 filldir/readdir 指针隐藏文件的方法，该方法也适用于 `/proc` 目录下的文件。这里就不贴代码图了。\n\n\n**SK-7.5** 通过 syscall hook 隐藏\n\n在 syscall 调用中，有一系列和进程操作相关的函数，如果让这些函数统统对需要隐藏的进程 pid 执行失败，如此 ring3 层便无法查阅到进程的有关信息了，这些函数的列表如下：\n\n```\ngetpgid\ngetsid\nsetpgid\nkill\ntgkill\ntkill\nsched_setscheduler\nsched_setparam\nsched_getscheduler\nsched_getparam\nsched_setaffinity\nsched_getaffinity\nsched_rr_get_interval\nwait4\nwaitid\nrt_tgsigqueueinfo\nrt_sigqueueinfo\nprlimit64\nptrace\nmigrate_pages\nmove_pages\nget_robust_list\nperf_event_open\n```\n\n\n**SK-7.6** 替换 file_operations \n\n同 SK-4.6 类似，只是替换目标为 `/proc` 目录对应的 `file_operations` 结构.\n\n## 键盘记录\n\n**SK-8.1** 键盘记录器\n\n内核提供了`register_keyboard_notifier` 和 `unregister_keyboard_notifier` 两个方法，用于增加或卸载键盘监视器，下图是使用该机制实现键盘记录功能的代码片段：\n\n![keylogger](__GHOST_URL__/content/images/2021/01/keylogger.png)\n\n**SK-8.2** 键盘记录（`/dev/ttyS0`读写劫持）\n\n在 Linux 中，  `/dev/ttyS0` 是一个字符设备（虚拟），可以简单的将之理解为是标准输入，通过重写它的 read 函数，可以捕获用户从键盘输入的内容，下图是一个劫持的代码样例：\n\n![network_keylogger](__GHOST_URL__/content/images/2021/01/network_keylogger.png)\n\n**SK-8.3** `/dev/input/event` 监听\n\n这是一个和输入有关的设备文件，通过监听该文件的变化情况，可以得到用户正在输入的内容。\n\n下图是选自 https://github.com/bones-codes/the_colonel/blob/devx/lkm/col_kl.c 的代码片段，该代码没有合并到 master 分支，似乎是个未完成的版本，仅供参考：\n\n![listen_keyboard_device](__GHOST_URL__/content/images/2021/01/listen_keyboard_device.png)\n\n## 权限提升\n\n**SK-9.1** 本地权限获取\n\n对于 Rootkit 来说，有时需要为普通进程赋予管理员权限。这就涉及到一个名为 cred 的结构体，另一方面，当前进程的 cred 可以通过 `prepare_creds` 可以获取。下图就是一段通过该方式提权的代码段：\n\n![cred_root_me](__GHOST_URL__/content/images/2021/01/cred_root_me.png)\n\n## 其他技术点\n\n**SK-10.1** 执行系统命令\n\n内核提供了 `call_usermodehelper` 函数，用于在用户态运行一个可执行文件。下图是使用该函数，实现shell后门的代码片段：\n\n![run_shell_command](__GHOST_URL__/content/images/2021/01/run_shell_command.png)\n\n**SK-10.2** 从内核启动一个线程\n\n内核提供了 `kernel_create` 函数，用于从内核态启动一个用户态线程。配置好 `subprocess_info` 实例后，即可完成启动。\n\n![create_thread_from_kernel](__GHOST_URL__/content/images/2021/01/create_thread_from_kernel.png)\n\n**SK-10.3**： 内核符号地址获取 A\n\nLinux 内核提供了一个名为 `kallsyms_on_each_symbol` 的 API，用来获取各种符号在内存中的地址。但该 API 需要 Linux内核 在编译时打开 `CONFIG_KALLSYMS` 宏，好在一些常见的Linux发行版中，默认都支持了这个宏，所以通配性还过得去。下图是基于该API抽象后的获取指定符号地址的代码片段：\n\n![kallsyms_find_sym](__GHOST_URL__/content/images/2021/01/kallsyms_find_sym.png)\n\n**SK-10.4**：内核符号地址获取 B\n\n在 SK-10.3 中提到了通过 `kallsyms_on_each_symbol` 获取符号地址的方法，其实，与这个API相对的还有另一个名为 `kallsyms_lookup_name` 的孪生API也可以获取一部分符号地址，这里就不贴截图了，感兴趣的小伙伴们自行找源码探索一下。\n\n**SK-10.5**： 通过工作队列执行任务分发\n\n在 Linux内核中，有一个工作队列(workqueue)的机制，可以将特定工作（以函数为单位）分发到不同的CPU中去执行，产生一个内核并发执行任务的效果。编码者将“任务提交”和“任务执行”分开实现，分别对应到 `投递 workqueue` 和 `消费 workqueue` ，Linux内核就会可以辅助完成任务的分发和执行，是一个极为方便使用的 API。\n\n在使用上，它需要先通过 `create_workqueue` 初始化一个新队列，当有新工作需要执行时，再通过 `queue_work` 方法向队列提交这个 work。下图是使用该方法的一段代码截图，供参考：\n\n![how_to_use_workqueue](__GHOST_URL__/content/images/2021/01/how_to_use_workqueue.png)\n\n\n\n\n\n# 参考资料\n\nhttps://wiki.osdev.org/Inline_Assembly/Examples#RDMSR"}]],"markups":[],"sections":[[10,0],[1,"p",[]]],"ghostVersion":"3.0"}