Shellcode是一段可以直接执行的机器码,通常用于漏洞利用或攻击中。它们通常是极小的程序,能直接在目标内存中运行。Rust作为一种系统编程语言,可以用来编写高效、安全的Shellcode。以下是如何用Rust构建Shellcode的详细步骤。
1. 什么是Shellcode?
Shellcode是一种直接执行的二进制代码,通常通过漏洞注入到目标系统中。它的名字来源于其常见用途,即启动一个shell(命令行)以供攻击者控制。
例如,以下是一个简单的Shellcode,它打印“hello world”:
echo '488d35140000006a01586a0c5a4889c70f056a3c5831ff0f05ebfe68656c6c6f20776f726c640a' | xxd -r -p > shellcode.bin
通过反汇编可以看出,它包含了一个write系统调用来输出“hello world”字符串。
2. 可执行文件的结构
所有的可执行文件都分为多个部分(section),如.text(存储代码)、.data(存储数据)等。Rust编译器会自动生成这些部分,但我们需要了解它们的作用以便更好地控制Shellcode的生成。
3. Rust的编译过程
Rust的编译过程分为以下几个阶段:
1. 解析和宏展开:将源码解析为抽象语法树(AST)。
2. 分析:包括类型检查、借用检查等,生成中间表示(MIR)。
3. 优化与代码生成:通过LLVM生成目标机器码。
4. 链接:将生成的对象文件链接为最终的可执行文件。
4. 使用#![no_std]
默认情况下,Rust依赖标准库(std),但Shellcode通常运行在没有操作系统支持的环境中,因此我们需要使用#![no_std]来禁用标准库。
以下是一个最小的#![no_std]程序:
#![no_std]
#![no_main]
#![feature(start)]#[start]
fnstart(_argc: isize, _argv: *const *constu8) ->isize {0
}#[panic_handler]
fnpanic(_: &core::panic::PanicInfo) -> ! {loop {}
}
需要在.cargo/config.toml中配置编译参数:
[build]
rustflags = ["-C", "link-arg=-nostdlib", "-C", "link-arg=-static"]
5.在Rust中使用汇编
Rust支持内联汇编(asm!),可以直接嵌入机器码。以下是一个简单的例子,使用syscall系统调用打印“hello world”:
#![feature(asm)]const SYS_WRITE: usize = 1;
const STDOUT: usize = 1;
static MESSAGE: &str = "hello world\n";unsafefnsyscall3(scnum: usize, arg1: usize, arg2: usize, arg3: usize) ->usize {letret: usize;asm!("syscall",in("rax") scnum,in("rdi") arg1,in("rsi") arg2,in("rdx") arg3,lateout("rax") ret,options(nostack),);ret
}fnmain() {unsafe {syscall3(SYS_WRITE, STDOUT, MESSAGE.as_ptr() asusize, MESSAGE.len());}
}
6. 使用Never类型
Rust中的“Never”类型(!)表示永远不会返回的函数。对于Shellcode,这非常有用,因为Shellcode通常不会返回任何值。例如:
#[no_mangle]
fn _start() -> ! {loop {}
}
7.执行Shellcode
在Linux上,可以通过以下两种方式执行Shellcode:
嵌入到.text段 通过#[link_section = ".text"]将Shellcode嵌入到.text段:
#[link_section = ".text"]
static SHELLCODE: [u8; 16] = *include_bytes!("shellcode.bin");fn main() {let exec_shellcode: extern "C" fn() -> ! =unsafe { std::mem::transmute(&SHELLCODE as *const _ as *const ()) };exec_shellcode();
}
8. 使用mmap设置可执行内存
使用mmap将内存标记为可执行:
use mmap::{MapOption::*, MemoryMap};
use std::mem;static SHELLCODE: &[u8] = include_bytes!("shellcode.bin");fnmain() {letmap = MemoryMap::new(SHELLCODE.len(), &[MapReadable, MapWritable, MapExecutable]).unwrap();unsafe {std::ptr::copy(SHELLCODE.as_ptr(), map.data(), SHELLCODE.len());letexec_shellcode: extern"C"fn() -> ! = mem::transmute(map.data());exec_shellcode();}
}
9. Hello World Shellcode
以下是一个完整的Hello World Shellcode示例:
#![no_std]
#![no_main]
#![feature(asm)]const SYS_WRITE: usize = 1;
const SYS_EXIT: usize = 60;
const STDOUT: usize = 1;
static MESSAGE: &str = "hello world\n";#[no_mangle]
fn_start() -> ! {unsafe {syscall3(SYS_WRITE, STDOUT, MESSAGE.as_ptr() asusize, MESSAGE.len());syscall1(SYS_EXIT, 0);}loop {}
}unsafefnsyscall1(scnum: usize, arg1: usize) ->usize {letret: usize;asm!("syscall",in("rax") scnum,in("rdi") arg1,lateout("rax") ret,options(nostack),);ret
}unsafefnsyscall3(scnum: usize, arg1: usize, arg2: usize, arg3: usize) ->usize {letret: usize;asm!("syscall",in("rax") scnum,in("rdi") arg1,in("rsi") arg2,in("rdx") arg3,lateout("rax") ret,options(nostack),);ret
}
10. 生成反向TCP Shellcode
以下是一个更复杂的Shellcode示例,用于建立反向TCP连接并启动一个shell:
const SYS_SOCKET: usize = 41;
const SYS_CONNECT: usize = 42;
const SYS_DUP2: usize = 33;
const SYS_EXECVE: usize = 59;#[no_mangle]
fn _start() -> ! {// 创建socket、连接服务器、重定向输入输出、启动shellloop {}
}
通过这些示例,我们可以看到Rust在构建Shellcode时的强大能力,同时也能更好地理解Shellcode的工作原理。如果有任何疑问,可以随时提问!
原创 梦兽编程