为了完成本实验,我们将把一个简单的 C 程序(在上述论文中由 hasherezade 提供)转换为 shellcode,并通过手动将其注入记事本内的 RWX 内存位置来执行它。
概述
以下是在 C 语言中编写和编译 shellcode 的工作原理的快速概述:
- Shellcode 是用 C 语言编写的
- C 代码被编译为汇编指令列表
- 清理程序集指令并删除外部依赖项
- 程序集链接到二进制文件
- Shellcode 是从二进制文件中提取的
- 现在可以通过利用代码注入技术来注入/执行此 shellcode
1. 准备开发环境
首先,让我们启动 VS 2019 的开发人员命令提示符,它将设置编译和链接本实验中使用的 C 代码所需的开发环境:
就我而言,所述控制台位于此处:
C:Program Files (x86)Microsoft Visual Studio2019CommunityCommon7ToolsVsDevCmd.bat
让我们这样开始:
cmd /k "C:Program Files (x86)Microsoft Visual Studio2019CommunityCommon7ToolsVsDev
2. 生成程序集列表
下面是构成我们将要转换为 shellcode 的程序的两个 C 文件:
- c-shellcode.cpp- 弹出消息框的程序
- peb-lookup.h- 所需的头文件,其中包含用于解析 和 的地址的函数c-shellcode.cppLoadLibraryAGetProcAddress
// It's worth noting that strings can be defined nside the .text section:
__declspec(allocate(".text"))
wchar_t kernel32_str[] = L"kernel32.dll";
__declspec(allocate(".text"))
char load_lib_str[] = "LoadLibraryA";
int main()
{
// Stack based strings for libraries and functions the shellcode needs
wchar_t kernel32_dll_name[] = { 'k','e','r','n','e','l','3','2','.','d','l','l', 0 };
char load_lib_name[] = { 'L','o','a','d','L','i','b','r','a','r','y','A',0 };
char get_proc_name[] = { 'G','e','t','P','r','o','c','A','d','d','r','e','s','s', 0 };
char user32_dll_name[] = { 'u','s','e','r','3','2','.','d','l','l', 0 };
char message_box_name[] = { 'M','e','s','s','a','g','e','B','o','x','W', 0 };
// stack based strings to be passed to the messagebox win api
wchar_t msg_content[] = { 'H','e','l','l','o', ' ', 'W','o','r','l','d','!', 0 };
wchar_t msg_title[] = { 'D','e','m','o','!', 0 };
// resolve kernel32 image base
LPVOID base = get_module_by_name((const LPWSTR)kernel32_dll_name);
if (!base) {
return 1;
}
// resolve loadlibraryA() address
LPVOID load_lib = get_func_by_name((HMODULE)base, (LPSTR)load_lib_name);
if (!load_lib) {
return 2;
}
// resolve getprocaddress() address
LPVOID get_proc = get_func_by_name((HMODULE)base, (LPSTR)get_proc_name);
if (!get_proc) {
return 3;
}
// loadlibrarya and getprocaddress function definitions
HMODULE(WINAPI * _LoadLibraryA)(LPCSTR lpLibFileName) = (HMODULE(WINAPI*)(LPCSTR))load_lib;
FARPROC(WINAPI * _GetProcAddress)(HMODULE hModule, LPCSTR lpProcName)
= (FARPROC(WINAPI*)(HMODULE, LPCSTR)) get_proc;
// load user32.dll
LPVOID u32_dll = _LoadLibraryA(user32_dll_name);
// messageboxw function definition
int (WINAPI * _MessageBoxW)(
_In_opt_ HWND hWnd,
_In_opt_ LPCWSTR lpText,
_In_opt_ LPCWSTR lpCaption,
_In_ UINT uType) = (int (WINAPI*)(
_In_opt_ HWND,
_In_opt_ LPCWSTR,
_In_opt_ LPCWSTR,
_In_ UINT)) _GetProcAddress((HMODULE)u32_dll, message_box_name);
if (_MessageBoxW == NULL) return 4;
// invoke the message box winapi
_MessageBoxW(0, msg_content, msg_title, MB_OK);
return 0;
}
typedef struct _UNICODE_STRING
{
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
} UNICODE_STRING, * PUNICODE_STRING;
typedef struct _PEB_LDR_DATA
{
ULONG Length;
BOOLEAN Initialized;
HANDLE SsHandle;
LIST_ENTRY InLoadOrderModuleList;
LIST_ENTRY InMemoryOrderModuleList;
LIST_ENTRY InInitializationOrderModuleList;
PVOID EntryInProgress;
} PEB_LDR_DATA, * PPEB_LDR_DATA;
//here we don't want to use any functions imported form extenal modules
typedef struct _LDR_DATA_TABLE_ENTRY {
LIST_ENTRY InLoadOrderModuleList;
LIST_ENTRY InMemoryOrderModuleList;
LIST_ENTRY InInitializationOrderModuleList;
void* BaseAddress;
void* EntryPoint;
ULONG SizeOfImage;
UNICODE_STRING FullDllName;
UNICODE_STRING BaseDllName;
ULONG Flags;
SHORT LoadCount;
SHORT TlsIndex;
HANDLE SectionHandle;
ULONG CheckSum;
ULONG TimeDateStamp;
} LDR_DATA_TABLE_ENTRY, * PLDR_DATA_TABLE_ENTRY;
typedef struct _PEB
{
BOOLEAN InheritedAddressSpace;
BOOLEAN ReadImageFileExecOptions;
BOOLEAN BeingDebugged;
BOOLEAN SpareBool;
HANDLE Mutant;
PVOID ImageBaseAddress;
PPEB_LDR_DATA Ldr;
// [...] this is a fragment, more elements follow here
} PEB, * PPEB;
inline LPVOID get_module_by_name(WCHAR* module_name)
{
PPEB peb = NULL;
peb = (PPEB)__readgsqword(0x60);
peb = (PPEB)__readfsdword(0x30);
PPEB_LDR_DATA ldr = peb->Ldr;
LIST_ENTRY list = ldr->InLoadOrderModuleList;
PLDR_DATA_TABLE_ENTRY Flink = *((PLDR_DATA_TABLE_ENTRY*)(&list));
PLDR_DATA_TABLE_ENTRY curr_module = Flink;
while (curr_module != NULL && curr_module->BaseAddress != NULL) {
if (curr_module->BaseDllName.Buffer == NULL) continue;
WCHAR* curr_name = curr_module->BaseDllName.Buffer;
size_t i = 0;
for (i = 0; module_name[i] != 0 && curr_name[i] != 0; i++) {
WCHAR c1, c2;
TO_LOWERCASE(c1, module_name[i]);
TO_LOWERCASE(c2, curr_name[i]);
if (c1 != c2) break;
}
if (module_name[i] == 0 && curr_name[i] == 0) {
//found
return curr_module->BaseAddress;
}
// not found, try next:
curr_module = (PLDR_DATA_TABLE_ENTRY)curr_module->InLoadOrderModuleList.Flink;
}
return NULL;
}
inline LPVOID get_func_by_name(LPVOID module, char* func_name)
{
IMAGE_DOS_HEADER* idh = (IMAGE_DOS_HEADER*)module;
if (idh->e_magic != IMAGE_DOS_SIGNATURE) {
return NULL;
}
IMAGE_NT_HEADERS* nt_headers = (IMAGE_NT_HEADERS*)((BYTE*)module + idh->e_lfanew);
IMAGE_DATA_DIRECTORY* exportsDir = &(nt_headers->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]);
if (exportsDir->VirtualAddress == NULL) {
return NULL;
}
DWORD expAddr = exportsDir->VirtualAddress;
IMAGE_EXPORT_DIRECTORY* exp = (IMAGE_EXPORT_DIRECTORY*)(expAddr + (ULONG_PTR)module);
SIZE_T namesCount = exp->NumberOfNames;
DWORD funcsListRVA = exp->AddressOfFunctions;
DWORD funcNamesListRVA = exp->AddressOfNames;
DWORD namesOrdsListRVA = exp->AddressOfNameOrdinals;
//go through names:
for (SIZE_T i = 0; i < namesCount; i++) {
DWORD* nameRVA = (DWORD*)(funcNamesListRVA + (BYTE*)module + i * sizeof(DWORD));
WORD* nameIndex = (WORD*)(namesOrdsListRVA + (BYTE*)module + i * sizeof(WORD));
DWORD* funcRVA = (DWORD*)(funcsListRVA + (BYTE*)module + (*nameIndex) * sizeof(DWORD));
LPSTR curr_name = (LPSTR)(*nameRVA + (BYTE*)module);
size_t k = 0;
for (k = 0; func_name[k] != 0 && curr_name[k] != 0; k++) {
if (func_name[k] != curr_name[k]) break;
}
if (func_name[k] == 0 && curr_name[k] == 0) {
//found
return (BYTE*)module + (*funcRVA);
}
}
return NULL;
}
现在,我们可以将 C 代码转换为汇编指令,如下所示:
c-shellcode.cpp C:Program Files (x86)Microsoft Visual Studio2019CommunityVCToolsMSVC14.26.28801binHostx64x64cl.exe" /c /FA /GS- c-shellcode.cpp
开关指示编译器:
/c- 防止自动调用 LINK
/FA- 创建一个列表文件,其中包含提供的 C 代码的汇编程序代码
/GS-- 关闭对某些缓冲区溢出的检测
下面显示了我们如何编译成:
c-shellcode.cppc-shellcode.asm
3. MASM宏
现在我们的 C 代码已经转换为汇编 ,我们需要稍微清理一下文件,这样我们就可以将其链接到.exe而不会出错,并避免 shellcode 崩溃。具体来说,我们需要:c-shellcode.asm
- 从外部库中删除依赖项
- 对齐堆栈
- 修复一个简单的语法问题
3.1 删除外部库
首先,我们需要注释掉或删除将此模块与库链接的指令,并且:libcmtoldnames
3.2 修复堆栈对齐
在我们的第一段的顶部添加程序:AlignRSP_TEXTc-shellcode.asm
; https://github.com/mattifestation/PIC_Bindshell/blob/master/PIC_Bindshell/AdjustStack.asm
; AlignRSP is a simple call stub that ensures that the stack is 16-byte aligned prior
; to calling the entry point of the payload. This is necessary because 64-bit functions
; in Windows assume that they were called with 16-byte stack alignment. When amd64
; shellcode is executed, you can't be assured that you stack is 16-byte aligned. For example,
; if your shellcode lands with 8-byte stack alignment, any call to a Win32 function will likely
; crash upon calling any ASM instruction that utilizes XMM registers (which require 16-byte)
; alignment.
AlignRSP PROC
push rsi ; Preserve RSI since we're stomping on it
mov rsi, rsp ; Save the value of RSP so it can be restored
and rsp, 0FFFFFFFFFFFFFFF0h ; Align RSP to 16 bytes
sub rsp, 020h ; Allocate homing space for ExecutePayload
call main ; Call the entry point of the payload
mov rsp, rsi ; Restore the original value of RSP
pop rsi ; Restore RSI
ret ; Return to caller
AlignRSP ENDP
下面显示了它在 :c-shellcode.asm
3.3 删除PDATA和XDATA段
删除或注释掉和细分,如下所示:PDATAXDATA
3.4 修复语法问题
我们需要将行更改为:mov rax, QWORD PTR gs:96mov rax, QWORD PTR gs:[96]
我们现在已经准备好链接内部的程序集列表以获得可执行文件:
c-shellcode.asmc-shellcode.exe "C:Program Files (x86)Microsoft Visual Studio2019CommunityVCToolsMSVC14.26.28801binHostx64x64ml64.exe" c-shellcode.asm /link /entry:AlignRSP
5. 测试EXE
我们现在可以检查它是否符合预期 - 弹出一个消息框:c-shellcode.exe
6. 复制 Shellcode
一旦我们有了二进制文件,我们就可以提取 shellcode 并使用任何代码注入技术执行它,但为了这个实验,我们将它作为十六进制值列表复制出来,并简单地将它们粘贴到notepad.exe内的 RWX 内存插槽中。c-shellcode.exe
让我们从该部分复制出 shellcode,在我们的例子中,它从 0x200 开始到原始文件中:.text
如果您想知道我们是如何找到 shellcode 位置的,请查看以下部分 - 您也可以从那里提取:.text
7. 测试 Shellcode
复制 shellcode 后,让我们将其粘贴到记事本中的 RWX 内存区域(您可以将任何内存位置设置为具有 xdbg64 的 RWX 权限),将 RIP 设置为该位置并恢复该位置的代码执行。如果我们正确地执行了前面的所有步骤,我们应该看到我们的 shellcode 执行并弹出消息框:
原文始发于微信公众号(白帽子社区团队):用C编写和编译Shellcode
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论