elfloader 是一个超级简单的 ELF 文件加载器,它能够生成 ELF 的扁平内存表示。
将其与 Rust 搭配使用,现在你就可以用一种合适、安全、高级的语言编写你的 shellcode。任何 LLVM 可以定位的目标架构都可以使用,包括针对真正奇特平台和 ABI 的自定义目标规格。尽情享受在 32 位系统上使用 u64、边界检查数组、内存分配的 drop 处理等功能吧 :)
它只是将所有 LOAD 段串联在一起,如果存在间隙则使用零填充,形成一个大扁平文件。
该文件包含 .bss 段的零初始化,因此可以直接用作 shellcode 负载。
如果你不想浪费时间编写复杂的链接器脚本,这或许是一个很棒的方法。
该工具不处理任何重定位,你需要自己确保原始 ELF 的基址是你想要的地址。
要使用此工具,只需:
Usage: elfloader [--perms] [--binary] [--base=<addr>] <input ELF> <output>
--binary - 不输出 FELF,输出没有元数据的原始加载映像
--perms - 创建包含权限数据的 FELF0002,覆盖 --binary
--base=<addr> - 强制输出起始地址为 `<addr>`,如果需要,从基址到第一个 LOAD 段起始位置之间填充零。
`<addr>` 默认为十六进制,可以使用 `0d`、`0b`、`0x` 或 `0o` 前缀覆盖。
警告:这并不会以 base 为基址进行重定位,它只是让输出起始于 `<addr>`(添加零字节,使得输出映像可以加载到 `<addr>` 而非原始 ELF 基址)
<input ELF> - 输入 ELF 文件的路径
<output> - 输出文件的路径
要安装此工具,运行:
cargo install --path .
现在你就可以在 shell 中的任何位置使用 elfloader 了!
该项目的开发过程在以下视频中直播:
https://www.youtube.com/watch?v=x0V-CEmXQCQ
在 example_small_program 中有一个示例,只需运行 make 或 nmake,即可生成一个 8 字节的 example.bin。
pleb@gamey ~/elfloader/example_small_program $ make
cargo build --release
Finished release [optimized] target(s) in 0.03s
elfloader --binary target/aarch64-unknown-none/release/example_small_program example.bin
pleb@gamey ~/elfloader/example_small_program $ ls -l ./example.bin
-rw-r--r-- 1 pleb pleb 8 Nov 8 12:27 ./example.bin
pleb@gamey ~/elfloader/example_small_program $ objdump -d target/aarch64-unknown-none/release/example_small_program
target/aarch64-unknown-none/release/example_small_program: file format elf64-littleaarch64
Disassembly of section .text:
00000000133700b0 <_start>:
133700b0: 8b000020 add x0, x1, x0
133700b4: d65f03c0 ret
现在你可以用 Rust 编写你的 shellcode,不必担心是否生成了 .data、.rodata、.bss 等。这个工具会为你处理所有这一切!
还有一个包含 .bss 和 .rodata 的示例
pleb@gamey ~/elfloader/example_program_with_data $ make
cargo build --release
Finished release [optimized] target(s) in 0.04s
elfloader --binary target/aarch64-unknown-none/release/example_program_with_data example.bin
pleb@gamey ~/elfloader/example_program_with_data $ ls -l ./example.bin
-rw-r--r-- 1 pleb pleb 29 Nov 8 12:39 ./example.bin
pleb@gamey ~/elfloader/example_program_with_data $ objdump -d target/aarch64-unknown-none/release/example_program_with_data
target/aarch64-unknown-none/release/example_program_with_data: file format elf64-littleaarch64
Disassembly of section .text:
0000000013370124 <_start>:
13370124: 90000000 adrp x0, 13370000 <_start-0x124>
13370128: 90000008 adrp x8, 13370000 <_start-0x124>
1337012c: 52800029 mov w9, #0x1 // #1
13370130: 91048000 add x0, x0, #0x120
13370134: 3904f109 strb w9, [x8, #316]
13370138: d65f03c0 ret
pleb@gamey ~/elfloader/example_program_with_data $ readelf -l target/aarch64-unknown-none/release/example_program_with_data
Elf file type is EXEC (Executable file)
Entry point 0x13370124
There are 4 program headers, starting at offset 64
Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
LOAD 0x0000000000000120 0x0000000013370120 0x0000000013370120
0x0000000000000004 0x0000000000000004 R 0x1
LOAD 0x0000000000000124 0x0000000013370124 0x0000000013370124
0x0000000000000018 0x0000000000000018 R E 0x4
LOAD 0x000000000000013c 0x000000001337013c 0x000000001337013c
0x0000000000000000 0x0000000000000001 RW 0x4
GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 RW 0x0
Section to Segment mapping:
Segment Sections...
00 .rodata
01 .text
02 .bss
03
该工具只关心 LOAD 段。它从 ELF 头部确定字节序(小端 vs 大端)和位数(32 vs 64),然后根据程序头部的虚拟地址(加载位置)、文件大小(已初始化字节数)和内存大小(实际内存区域大小)创建一个扁平映像。字节根据偏移量和文件大小从文件中初始化,然后用零扩展到内存大小(如果内存大小小于文件大小则截断)。
这些 LOAD 段随后被串联在一起,间隙用零字节填充。
这样设计是为了极其简单,并且与 ELF 输入无关。它可以是可执行文件、目标文件、共享对象、核心转储等,并不在意。它只会给你内存的扁平表示,仅此而已。
这使得你可以将任何 ELF 转换为 shellcode,或者转换为一种更简单的文件格式,便于在难以访问的区域(如嵌入式设备)中加载。就个人而言,我是为我的 MIPS NT 4.0 加载器开发了这个工具,它允许我运行 Rust 代码。
该工具默认生成 FELF 文件格式。这是一种 Falk ELF 格式。它是一个简单的文件格式:
FELF0001 - 魔术头部
entry - 64 位小端整数,表示入口点地址
base - 64 位小端整数,表示加载映像的基地址
<image> - 文件的其余部分是原始映像,需要加载到 `base` 并从 `entry` 跳入执行
该工具默认生成 FELF 文件格式。这是一种带有权限的 Falk ELF 格式。它是一个简单的带权限的文件格式:
FELF0002 - 魔术头部
entry - 64 位小端整数,表示入口点地址
base - 64 位小端整数,表示加载映像的基地址
<image> - 文件的其余部分是原始映像,需要加载到 `base` 并从 `entry` 跳入执行
<perms> - 权限,与 <image> 的字节对应,每个字节包含以下按位或运算的标志:
0x01 - 可执行,0x02 - 可写,0x04 - 可读
填充字节将为 0x00,因此没有任何访问权限