浅析shellcode

作者:正在思考ing 发布:2026-07-17 20:16 收录:2026-09-07 16:51 2 次阅读 约 2687 字
摘要:shellcode编写日记
推荐理由:本文涵盖「shellcode」、「GetProcAddress」、「LoadLibraryA」等多个主题,重点关注 shellcode。

前言

好久没有更新文章了,不管期末咋样,也是进入暑假了。这几天看了一些关于编写shellcode的内容,自己也来实操一下😁😁😁

目标

编写shellcode,通过加载器,实现一个弹窗的功能,和世界打个招呼

**合法研究,严禁越界;技术向善,从我做起**

前期分析

在shellcode制作过程中,我们需要重点关注GetProcAddress以及LoadLibraryA这两个API。但这两个API已经被杀软重点标记了,所以我们尝试其它方法调用这两个API。

下面在Windbg中推演过程:

随机运行一个程序,开始调试,运行!process 0 0 <具体程序>,进入该进程上下文

.process /i ffffe688ce695080,切换到该进程的上下文

查看当前进程!process

我们找到主线程的Teb(线程环境模块)的地址,查看其具体结构

我们找到PEB(进程环境模块)的地址,查看其具体结构

找到Ldr(Loader Data(加载器数据))的地址,查看其结构

找到这里的InLoadOrderModuleList(加载模块列表)字段,这个字段是一个双向链表,其中记录了当前进程加载的模块列表。kernel32.dll一般情况下会在第三个模块,顺着这个双向链表往下查看。

找到了kernel32.dll的基址,开始解析其文件结构

通过解析其DOS头,定位到e_lfanew字段,这个字段记录的NT头的RVA,查看NT头结构

这里我们找到了NT可选头的位置,继续查看其结构

找到目录表,查看其第一个结构

我们对kernel32.dll的目录表进行分析

第一个结构体中记录的就是导出表的RVA,分析其内容

这里 存在四个重要的字段:

  1. NumberOfFunctions:导出表中函数的数量
  2. AddressOfFunctions:具体函数RVA的RVA
  3. AddressOfNames:函数名字的RVA
  4. AddressOfNameOrdinals:函数索引的RVA

那我们可以遍历AddressOfNames,找到关键函数的索引,在AddressOfFunctions中找到具体函数的RVA,即可调用具体函数。

复盘

我们的调用链为:teb -> peb -> Ldr -> InLoadOrderModuleList ->kernel32基址 -> 导出表 -> LoadLibraryA/GetProcAddress地址

具体代码实现

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

int main() {
    ULONG_PTR peb = __readgsqword(0x60);
    ULONG_PTR ldr = *(ULONG_PTR*)(peb + 0x18);  
    ULONG_PTR head  = ldr + 0x10;
    ULONG_PTR entry = *(ULONG_PTR*)(head);  
    ULONG_PTR kernelBase = 0;
    while (entry != head) {
        USHORT nameLen = *(USHORT*)(entry + 0x58);
        if (nameLen == 24) {           
            PWSTR name = *(PWSTR*)(entry + 0x60);
            // 大小写不敏感比较: (c | 0x20) 将大写 ASCII 转小写
            if ((name[0]  | 0x20) == L'k' &&
                (name[1]  | 0x20) == L'e' &&
                (name[2]  | 0x20) == L'r' &&
                (name[3]  | 0x20) == L'n' &&
                (name[4]  | 0x20) == L'e' &&
                (name[5]  | 0x20) == L'l' &&
                (name[6]          ) == L'3' &&  // 数字不变
                (name[7]          ) == L'2' &&
                (name[8]          ) == L'.' &&  // 点号不变
                (name[9]  | 0x20) == L'd' &&
                (name[10] | 0x20) == L'l' &&
                (name[11] | 0x20) == L'l') {
                kernelBase = *(ULONG_PTR*)(entry + 0x30); // DllBase
                break;
            }
        }
        entry = *(ULONG_PTR*)(entry);         // Flink → 下一个节点

    }
    if (!kernelBase) {
        printf("[-] kernel32.dll not found\n");
        return1;
    }
    printf("[+] kernel32.dll base: 0x%p\n", (PVOID)kernelBase);
    ULONG e_lfanew = *(ULONG*)(kernelBase + 0x3C);
    ULONG exportRVA   = *(ULONG*)(kernelBase + e_lfanew + 0x18 + 0x70);
    ULONG_PTR expBase = kernelBase + exportRVA;
    ULONG numNames  = *(ULONG*)(expBase + 0x18);  // NumberOfNames
    ULONG funcsRVA  = *(ULONG*)(expBase + 0x1C);  // AddressOfFunctions
    ULONG namesRVA  = *(ULONG*)(expBase + 0x20);  // AddressOfNames
    ULONG ordsRVA   = *(ULONG*)(expBase + 0x24);  // AddressOfNameOrdinals
    ULONG_PTR funcs = kernelBase + funcsRVA;       // DWORD 数组: 函数 RVA
    ULONG_PTR names = kernelBase + namesRVA;       // DWORD 数组: 名称 RVA
    ULONG_PTR ords  = kernelBase + ordsRVA;        // WORD  数组: 序号
    ULONG_PTR pGetProcAddress = 0;
    ULONG_PTR pLoadLibraryA   = 0;
    for (ULONG i = 0; i < numNames; i++) {
        PCHAR  fn  = (PCHAR)(kernelBase + *(ULONG*)(names + i * 4));
        USHORT ord = *(USHORT*)(ords + i * 2);
        ULONG  rva = *(ULONG*)(funcs + ord * 4);
        if (fn[0]=='G' && fn[1]=='e' && fn[2]=='t' && fn[3]=='P' &&
            fn[4]=='r' && fn[5]=='o' && fn[6]=='c' && fn[7]=='A' &&
            fn[8]=='d' && fn[9]=='d' && fn[10]=='r' && fn[11]=='e' &&
            fn[12]=='s' && fn[13]=='s' && fn[14]=='\0') {
            pGetProcAddress = kernelBase + rva;
        }
        if (fn[0]=='L' && fn[1]=='o' && fn[2]=='a' && fn[3]=='d' &&
            fn[4]=='L' && fn[5]=='i' && fn[6]=='b' && fn[7]=='r' &&
            fn[8]=='a' && fn[9]=='r' && fn[10]=='y' && fn[11]=='A' &&
            fn[12]=='\0') {
            pLoadLibraryA = kernelBase + rva;
        }
    }
    printf("[+] GetProcAddress: 0x%p\n", (PVOID)pGetProcAddress);
    printf("[+] LoadLibraryA:   0x%p\n", (PVOID)pLoadLibraryA);
    if (!pGetProcAddress || !pLoadLibraryA) {
        printf("[-] Failed to resolve functions\n");
        return1;

    }

    typedef HMODULE (WINAPI *fnLoadLibraryA_t)(LPCSTR);
    typedef FARPROC (WINAPI *fnGetProcAddress_t)(HMODULE, LPCSTR);
    typedef int     (WINAPI *fnMessageBoxA_t)(HWND, LPCSTR, LPCSTR, UINT);
    fnLoadLibraryA   fnLoadLibraryA   = (fnLoadLibraryA)pLoadLibraryA;
    fnGetProcAddress fnGetProcAddress = (fnGetProcAddress)pGetProcAddress;
    HMODULE hUser32 = fnLoadLibraryA("user32.dll");
    if (!hUser32) {
        printf("[-] Failed to load user32.dll\n");
        return1;
    }
    printf("[+] user32.dll base: 0x%p\n", hUser32);
    fnMessageBoxA_t fnMessageBoxA = (fnMessageBoxA_t)fnGetProcAddress(hUser32, "MessageBoxA");
    if (!fnMessageBoxA) {
        printf("[-] Failed to resolve MessageBoxA\n");
        return1;
    }
    printf("[+] MessageBoxA: 0x%p\n", fnMessageBoxA);
    fnMessageBoxA(NULL"hello world""Message", MB_OK);
    return0;
}

提取shellcode

上述过程中制作的是一个完整的PE文件,但shellcode只是一段字节码,所以我们需要对编译过程做出更改。主要是一下两点:

  1. 该变程序入口点,正常程序启动是通过_start函数会做CRT初始化,再调用main函数,但shellcode是没有这些初始化过程,所以需要更换程序入口点。

  2. 为了方便提取shellcode,我们需要合并段

其它还包括:关Spectre缓解,关闭安全保护... 具体步骤如下:

代码也要做出如下修改:

#include <windows.h>
#include <intrin.h>
#pragma code_seg("shell")
#pragma comment(linker,"/entry:shellcode")
int shellcode() {
    ULONG_PTR peb = __readgsqword(0x60);
    ULONG_PTR ldr = *(ULONG_PTR*)(peb + 0x18);
    ULONG_PTR head = ldr + 0x10;
    ULONG_PTR entry = *(ULONG_PTR*)(head);   // Flink → 第一个 LDR_DATA_TABLE_ENTRY
    ULONG_PTR kernelBase = 0;

    while (entry != head) {
        WORD nameLen = *(WORD*)(entry + 0x58);
        if (nameLen == 24) {           
            PWSTR name = *(PWSTR*)(entry + 0x60);
            if ((name[0] | 0x20) == L'k' &&
                (name[1] | 0x20) == L'e' &&
                (name[2] | 0x20) == L'r' &&
                (name[3] | 0x20) == L'n' &&
                (name[4] | 0x20) == L'e' &&
                (name[5] | 0x20) == L'l' &&
                (name[6]) == L'3' &&  // 数字不变
                (name[7]) == L'2' &&
                (name[8]) == L'.' &&  // 点号不变
                (name[9] | 0x20) == L'd' &&
                (name[10] | 0x20) == L'l' &&
                (name[11] | 0x20) == L'l') {
                kernelBase = *(ULONG_PTR*)(entry + 0x30); // DllBase
                break;
            }
        }
        entry = *(ULONG_PTR*)(entry);         // Flink → 下一个节点
    }
    if (!kernelBase) {
        return1;
    }
    ULONG e_lfanew = *(ULONG*)(kernelBase + 0x3C);
    ULONG exportRVA = *(ULONG*)(kernelBase + e_lfanew + 0x18 + 0x70);
    ULONG_PTR expBase = kernelBase + exportRVA;
    ULONG numNames = *(ULONG*)(expBase + 0x18);  // NumberOfNames
    ULONG funcsRVA = *(ULONG*)(expBase + 0x1C);  // AddressOfFunctions
    ULONG namesRVA = *(ULONG*)(expBase + 0x20);  // AddressOfNames
    ULONG ordsRVA = *(ULONG*)(expBase + 0x24);  // AddressOfNameOrdinals

    ULONG_PTR funcs = kernelBase + funcsRVA;       // DWORD 数组: 函数 RVA
    ULONG_PTR names = kernelBase + namesRVA;       // DWORD 数组: 名称 RVA
    ULONG_PTR ords = kernelBase + ordsRVA;        // WORD  数组: 序号

    ULONG_PTR pGetProcAddress = 0;
    ULONG_PTR pLoadLibraryA = 0;

    for (ULONG i = 0; i < numNames; i++) {
        PCHAR  fn = (PCHAR)(kernelBase + *(ULONG*)(names + i * 4));
        WORD ord = *(WORD*)(ords + i * 2);
        ULONG  rva = *(ULONG*)(funcs + ord * 4);

        if (fn[0] == 'G' && fn[1] == 'e' && fn[2] == 't' && fn[3] == 'P' &&
            fn[4] == 'r' && fn[5] == 'o' && fn[6] == 'c' && fn[7] == 'A' &&
            fn[8] == 'd' && fn[9] == 'd' && fn[10] == 'r' && fn[11] == 'e' &&
            fn[12] == 's' && fn[13] == 's' && fn[14] == '\0') {
            pGetProcAddress = kernelBase + rva;
        }

        if (fn[0] == 'L' && fn[1] == 'o' && fn[2] == 'a' && fn[3] == 'd' &&
            fn[4] == 'L' && fn[5] == 'i' && fn[6] == 'b' && fn[7] == 'r' &&
            fn[8] == 'a' && fn[9] == 'r' && fn[10] == 'y' && fn[11] == 'A' &&
            fn[12] == '\0') {
            pLoadLibraryA = kernelBase + rva;
        }
    }

    if (!pGetProcAddress || !pLoadLibraryA) {

        return1;
    }

    typedef HMODULE(WINAPI* fnLoadLibraryA_t)(LPCSTR);
    typedef FARPROC(WINAPI* fnGetProcAddress_t)(HMODULE, LPCSTR);
    typedef int     (WINAPI* fnMessageBoxA_t)(HWND, LPCSTR, LPCSTR, UINT);

    fnLoadLibraryA_t   fnLoadLibraryA = (fnLoadLibraryA_t)pLoadLibraryA;
    fnGetProcAddress_t fnGetProcAddress = (fnGetProcAddress_t)pGetProcAddress;

    char module_user32dll[] = { 'u','s','e','r','3','2','.','d','l','l'},
        func_MessageBoxA[] = { 'M','e','s','s','a','g','e','B','o','x','A' },
        content[] = { 'h','e','l','l','o',' ','w','o','r','l','d' },
        Tip[] = { 'M','e','s','s','a','g','e' };
    HMODULE hUser32 = fnLoadLibraryA(module_user32dll);
    if (!hUser32) {

        return1;
    }
    fnMessageBoxA_t fnMessageBoxA = (fnMessageBoxA_t)fnGetProcAddress(hUser32, func_MessageBoxA);
    if (!fnMessageBoxA) {

        return1;
    }
    fnMessageBoxA(NULL, content, Tip, MB_OK);

    return0;
}

编译,查看文件的段分布

这里的shell段就是我们的shellcode

注意:代码中所有的字符串都应该像Tip[] = { 'M','e','s','s','a','g','e' };这样存储,否则字符串会和代码存储到不同位置(虽然仍然在同一段)

这部分就是我们的shellcode,放入Loader中运行

#include<Windows.h>
#include<iostream>
/* length: 894 bytes */
unsignedchar buf[] = "\x48\x89\x5C\x24\x10\x48\x89\x74\x24\x18\x55\x57\x41\x56\x48\x8B\xEC\x48\x83\xEC\x50\x65\x48\x8B\x04\x25\x60\x00\x00\x00\x4C\x8B\x40\x18\x49\x83\xC0\x10\x49\x8B\x10\x49\x3B\xD0\x0F\x84\xB1\x00\x00\x00\x66\x83\x7A\x58\x18\x0F\x85\x9A\x00\x00\x00\x48\x8B\x4A\x60\x0F\xB7\x01\x66\x83\xC8\x20\x66\x83\xF8\x6B\x0F\x85\x85\x00\x00\x00\x0F\xB7\x41\x02\x66\x83\xC8\x20\x66\x83\xF8\x65\x75\x77\x0F\xB7\x41\x04\x66\x83\xC8\x20\x66\x83\xF8\x72\x75\x69\x0F\xB7\x41\x06\x66\x83\xC8\x20\x66\x83\xF8\x6E\x75\x5B\x0F\xB7\x41\x08\x66\x83\xC8\x20\x66\x83\xF8\x65\x75\x4D\x0F\xB7\x41\x0A\x66\x83\xC8\x20\x66\x83\xF8\x6C\x75\x3F\x66\x83\x79\x0C\x33\x75\x38\x66\x83\x79\x0E\x32\x75\x31\x66\x83\x79\x10\x2E\x75\x2A\x0F\xB7\x41\x12\x66\x83\xC8\x20\x66\x83\xF8\x64\x75\x1C\x0F\xB7\x41\x14\x66\x83\xC8\x20\x66\x83\xF8\x6C\x75\x0E\x0F\xB7\x41\x16\x66\x83\xC8\x20\x66\x83\xF8\x6C\x74\x27\x48\x8B\x12\x49\x3B\xD0\x0F\x85\x4F\xFF\xFF\xFF\xB8\x01\x00\x00\x00\x48\x8B\x5C\x24\x78\x48\x8B\xB4\x24\x80\x00\x00\x00\x48\x83\xC4\x50\x41\x5E\x5F\x5D\xC3\x4C\x8B\x4A\x30\x4D\x85\xC9\x74\xDC\x41\x8B\x41\x3C\x33\xDB\x45\x33\xD2\x45\x33\xC0\x42\x8B\x8C\x08\x88\x00\x00\x00\x42\x8B\x7C\x09\x1C\x42\x8B\x74\x09\x20\x49\x03\xF9\x46\x8B\x74\x09\x24\x49\x03\xF1\x46\x8B\x5C\x09\x18\x4D\x03\xF1\x45\x85\xDB\x74\xA6\x0F\x1F\x00\x43\x8D\x0C\x00\x42\x0F\xB7\x0C\x31\x42\x8D\x04\x85\x00\x00\x00\x00\x8B\x04\x30\x8B\x14\x8F\x42\x0F\xB6\x0C\x08\x80\xF9\x47\x0F\x85\xA2\x00\x00\x00\x42\x80\x7C\x08\x01\x65\x0F\x85\xFE\x00\x00\x00\x42\x80\x7C\x08\x02\x74\x0F\x85\xF2\x00\x00\x00\x42\x80\x7C\x08\x03\x50\x0F\x85\xE6\x00\x00\x00\x42\x80\x7C\x08\x04\x72\x0F\x85\xDA\x00\x00\x00\x42\x80\x7C\x08\x05\x6F\x0F\x85\xCE\x00\x00\x00\x42\x80\x7C\x08\x06\x63\x0F\x85\xC2\x00\x00\x00\x42\x80\x7C\x08\x07\x41\x0F\x85\xB6\x00\x00\x00\x42\x80\x7C\x08\x08\x64\x0F\x85\xAA\x00\x00\x00\x42\x80\x7C\x08\x09\x64\x0F\x85\x9E\x00\x00\x00\x42\x80\x7C\x08\x0A\x72\x0F\x85\x92\x00\x00\x00\x42\x80\x7C\x08\x0B\x65\x0F\x85\x86\x00\x00\x00\x42\x80\x7C\x08\x0C\x73\x75\x7E\x42\x80\x7C\x08\x0D\x73\x75\x76\x42\x80\x7C\x08\x0E\x00\x75\x6E\x49\x8D\x1C\x11\xEB\x68\x80\xF9\x4C\x75\x63\x42\x80\x7C\x08\x01\x6F\x75\x5B\x42\x80\x7C\x08\x02\x61\x75\x53\x42\x80\x7C\x08\x03\x64\x75\x4B\x42\x38\x4C\x08\x04\x75\x44\x42\x80\x7C\x08\x05\x69\x75\x3C\x42\x80\x7C\x08\x06\x62\x75\x34\x42\x80\x7C\x08\x07\x72\x75\x2C\x42\x80\x7C\x08\x08\x61\x75\x24\x42\x80\x7C\x08\x09\x72\x75\x1C\x42\x80\x7C\x08\x0A\x79\x75\x14\x42\x80\x7C\x08\x0B\x41\x75\x0C\x42\x80\x7C\x08\x0C\x00\x75\x04\x4D\x8D\x14\x11\x41\xFF\xC0\x45\x3B\xC3\x0F\x82\xC5\xFE\xFF\xFF\x48\x85\xDB\x0F\x84\x5F\xFE\xFF\xFF\x4D\x85\xD2\x0F\x84\x56\xFE\xFF\xFF\x48\x8D\x4D\xD0\xC7\x45\xD0\x75\x73\x65\x72\xC7\x45\xD4\x33\x32\x2E\x64\x66\xC7\x45\xD8\x6C\x6C\xC7\x45\xE0\x4D\x65\x73\x73\xC7\x45\xE4\x61\x67\x65\x42\x66\xC7\x45\xE8\x6F\x78\xC6\x45\xEA\x41\xC7\x45\xF0\x68\x65\x6C\x6C\xC7\x45\xF4\x6F\x20\x77\x6F\x66\xC7\x45\xF8\x72\x6C\xC6\x45\xFA\x64\xC7\x45\x20\x4D\x65\x73\x73\x66\xC7\x45\x24\x61\x67\xC6\x45\x26\x65\x41\xFF\xD2\x48\x85\xC0\x0F\x84\xF1\xFD\xFF\xFF\x48\x8D\x55\xE0\x48\x8B\xC8\xFF\xD3\x48\x85\xC0\x0F\x84\xDF\xFD\xFF\xFF\x45\x33\xC9\x4C\x8D\x45\x20\x48\x8D\x55\xF0\x33\xC9\xFF\xD0\x33\xC0\xE9\xCE\xFD\xFF\xFF\x00";
int main()
{
// VirtualAlloc(起始地址,大小,分配类型,内存页的初始保护属性)
 LPVOID addr = VirtualAlloc(NULLsizeof(buf), MEM_COMMIT, PAGE_READWRITE);
memcpy(addr, buf, sizeof(buf));
 DWORD old;
// VirtualProtect(初始地址,大小,内存页的初始保护属性,旧属性)
 VirtualProtect(addr, sizeof(buf), PAGE_EXECUTE, &old);
// CrateThread(安全属性,线程栈大小,线程函数的起始地址,传递给线程函数的参数,创建标志,指向接收线程 ID 的变量的指针)
 HANDLE hthread = CreateThread(NULL0, (LPTHREAD_START_ROUTINE)addr, NULL0NULL);
 WaitForSingleObject(hthread, -1);
}

转载声明:本文转载自原发布平台 (作者:正在思考ing), 原文标题《浅析shellcode》, 查看原文。 版权归原作者及原发布平台所有,本站仅作收录与展示,未对正文内容作实质性修改; 若涉及侵权请联系本站处理。