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使用。
想到现在的编译器都开了基址随机化,那么使用字符串之类的,肯定是用的偏移。这样子无论加载到哪里都能正确找到,而不是写死一个地址。
随便找个例子看看
140002000 - 14000122B = DD 05,确实是相对的。
看到text 和 rdata是相连的,但是内存展开后区段会对齐哦,不直接提取。