shellcode 编写过程中,一般全局变量,字符串等都会特殊处理,例如字符串可能会直接存放在栈上,然后提取函数段。

例如一个常见的shellcode 提取:

(随便搜来的,微改)

#include <iostream>
#include <windows.h>
#include <winternl.h>
#include <stdint.h>
#include "funtions.h"

void start() {
    FUNCS funcs;
    init(&funcs);
    testfunc(&funcs);
}

void init(FUNCS *funcs)
{
    PEB* pProcessEnvironmentBlock = (PEB*)__readgsqword(0x60);
    LDR_DATA_TABLE_ENTRY* pKernel32TableEntry = CONTAINING_RECORD(pProcessEnvironmentBlock->Ldr->InMemoryOrderModuleList.Flink->Flink->Flink, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks);

    // We've ended up at the base address of `kernel32.dll`.
    IMAGE_DOS_HEADER* pDosHeader = (IMAGE_DOS_HEADER*)pKernel32TableEntry->DllBase;

    // In order to get the exported functions we need to go to the NT PE header.
    IMAGE_NT_HEADERS* pNtHeader = (IMAGE_NT_HEADERS*)((size_t)pDosHeader + pDosHeader->e_lfanew);

    // From the NtHeader we can extract the virtual address of the export directory of this module.
    IMAGE_EXPORT_DIRECTORY* pExports = (IMAGE_EXPORT_DIRECTORY*)((size_t)pDosHeader + pNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);

    // The exports directory contains both a list of function _names_ of this module and the associated _addresses_ of the functions.
    const int32_t* pNameOffsets = (const int32_t*)((size_t)pDosHeader + pExports->AddressOfNames);

    // We will use this struct to store strings.
    // We are using a struct to make sure strings don't end up in another section of the executable where we wouldn't be able to address them in a different process.
    struct
    {
        uint64_t text0, text1;
    } x;

    // We're now looking for the `GetProcAddress` function. Since there's no other function starting with `GetProcA` we'll just find that instead.
    x.text0 = 0x41636F7250746547; // `GetProcA`

    int32_t i = 0;

    // We're just extracting the first 8 bytes of the strings and compare them to `GetProcA`. We'll find it eventually.
    while (*(uint64_t*)((size_t)pDosHeader + pNameOffsets[i]) != x.text0)
        ++i;

    const int16_t* pFunctionNameOrdinalOffsets = (const int16_t*)((size_t)pDosHeader + pExports->AddressOfNameOrdinals);
    const int32_t* pFunctionOffsets = (const int32_t*)((size_t)pDosHeader + pExports->AddressOfFunctions);

    // Now resolve the index in `pFunctionOffsets` from `pFunctionNameOrdinalOffsets` to get the address of the desired function in memory.

    funcs->pGetProcAddress = (GetProcAddressFunc)(const void*)((size_t)pDosHeader + pFunctionOffsets[pFunctionNameOrdinalOffsets[i]]);
    funcs->kernel32Dll = (HMODULE)pDosHeader;

    // Get `LoadLibraryA`.
    x.text0 = 0x7262694C64616F4C; // `LoadLibr`
    x.text1 = 0x0000000041797261; // `aryA\\0\\0\\0\\0`

    funcs->pLoadLibraryA = (LoadLibraryAFunc)funcs->pGetProcAddress(funcs->kernel32Dll, (const char*)&x.text0);

}

void testfunc(FUNCS* funcs) {
    // Load `user32.dll`.
    struct
    {
        uint64_t text0, text1;
    } x;
    x.text0 = 0x642E323372657375; // `user32.d`
    x.text1 = 0x0000000000006C6C; // `ll\\0\\0\\0\\0\\0\\0`
    HMODULE user32Dll = funcs->pLoadLibraryA((const char*)&x.text0);

    // Get `MessageBoxA`.
    x.text0 = 0x426567617373654D; // `MessageB`
    x.text1 = 0x000000000041786F; // `oxA\\0\\0\\0\\0\\0`

    MessageBoxAFunc pMessageBoxA = (MessageBoxAFunc)funcs->pGetProcAddress(user32Dll, (const char*)&x.text0);
    // Display a message box.
    x.text0 = 0x616C206174736148; // `Hasta la`
    x.text1 = 0x0021617473697620; // ` vista!\\0`
    pMessageBoxA(NULL, (const char*)&x.text0, (const char*)&x.text1 + 7, MB_OK);

    // Load `ExitProcess` from `kernel32.dll`.
    x.text0 = 0x636F725074697845; // `ExitProc`
    x.text1 = 0x0000000000737365; // `ess\\0\\0\\0\\0\\0`

    ExitProcessFunc pExitProcess = (ExitProcessFunc)funcs->pGetProcAddress(funcs->kernel32Dll, (const char*)&x.text0);

    // Kill the current process with exit code -1.
    pExitProcess((uint32_t)-1);
}

void sc_end() {};

void main()
{
    
    size_t sc_size = (size_t)sc_end - (size_t)start;
    printf("shellcode size: %zd  \\n", sc_size);
    char* buf = (char*)malloc(sc_size);
    if (!buf) {
        printf("malloc failed \\n");
        return 0;
    }

    memcpy(buf, (void*)start, sc_size);

    FILE* fp = fopen("func.bin", "wb");
    fwrite(buf, 1, sc_size, fp);
    fclose(fp);
    printf("Extract ok!");
    getchar();
    return 0;
   
}
// funtions.h
#pragma once
typedef FARPROC(*GetProcAddressFunc)(HMODULE, const char*);
typedef HMODULE(*LoadLibraryAFunc)(const char*);
typedef int32_t(*MessageBoxAFunc)(HWND, const char*, const char*, uint32_t);
typedef void(*ExitProcessFunc)(uint32_t);

struct FUNCS
{
    GetProcAddressFunc pGetProcAddress;
    LoadLibraryAFunc pLoadLibraryA;
    HMODULE kernel32Dll;
};

/////funtions///
void init(FUNCS* funcs);
void testfunc(FUNCS* funcs);

可以看到字符串全部存在栈上,拼接起来,因为小端还要反转,看着很难受。

得想个办法让字符串之类的能和平常一样存放,还能提取shellcode使用。

想到现在的编译器都开了基址随机化,那么使用字符串之类的,肯定是用的偏移。这样子无论加载到哪里都能正确找到,而不是写死一个地址。

https://s3-us-west-2.amazonaws.com/secure.notion-static.com/1e750574-df50-45bb-9b4d-c4e9cf024f89/Untitled.png

随便找个例子看看

https://s3-us-west-2.amazonaws.com/secure.notion-static.com/96437766-a729-4d96-b285-359bbef7e3a6/Untitled.png

https://s3-us-west-2.amazonaws.com/secure.notion-static.com/7ac996bc-5add-4a34-9464-e969a34f9ac2/Untitled.png

140002000 - 14000122B = DD 05,确实是相对的。

看到text 和 rdata是相连的,但是内存展开后区段会对齐哦,不直接提取。

https://s3-us-west-2.amazonaws.com/secure.notion-static.com/b36dc375-6d6a-4a40-ae54-370700b9cad2/Untitled.png

https://s3-us-west-2.amazonaws.com/secure.notion-static.com/3dd163fc-bb79-4b09-adac-27263e6409f1/Untitled.png