用C编写和编译Shellcode

admin 2024年7月11日14:06:34评论6 views字数 7888阅读26分17秒阅读模式
这是一个小型试验,用于熟悉用 C 编写和编译 shellcode 的过程,只是对论文的个人看法 从 C 项目到汇编,再到 hasherezade 为 vxunderground 编写的 shellcode - 去看看它,深入了解这个过程中涉及的所有微妙之处,这些笔记不会涉及。

为了完成本实验,我们将把一个简单的 C 程序(在上述论文中由 hasherezade 提供)转换为 shellcode,并通过手动将其注入记事本内的 RWX 内存位置来执行它。

概述

以下是在 C 语言中编写和编译 shellcode 的工作原理的快速概述:

  1. Shellcode 是用 C 语言编写的
  2. C 代码被编译为汇编指令列表
  3. 清理程序集指令并删除外部依赖项
  4. 程序集链接到二进制文件
  5. Shellcode 是从二进制文件中提取的
  6. 现在可以通过利用代码注入技术来注入/执行此 shellcode
1. 准备开发环境

首先,让我们启动 VS 2019 的开发人员命令提示符,它将设置编译和链接本实验中使用的 C 代码所需的开发环境:

用C编写和编译Shellcode

就我而言,所述控制台位于此处:

C:Program Files (x86)Microsoft Visual Studio2019CommunityCommon7ToolsVsDevCmd.bat

让我们这样开始:

cmd /k "C:Program Files (x86)Microsoft Visual Studio2019CommunityCommon7ToolsVsDev

用C编写和编译Shellcode

2. 生成程序集列表

下面是构成我们将要转换为 shellcode 的程序的两个 C 文件:

  • c-shellcode.cpp- 弹出消息框的程序
  • peb-lookup.h- 所需的头文件,其中包含用于解析 和 的地址的函数c-shellcode.cppLoadLibraryAGetProcAddress
#include <Windows.h>#include "peb-lookup.h"// It's worth noting that strings can be defined nside the .text section:#pragma code_seg(".text")__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;}#pragma once#include <Windows.h>#ifndef __NTDLL_H__#ifndef TO_LOWERCASE#define TO_LOWERCASE(out, c1) (out = (c1 <= 'Z' && c1 >= 'A') ? c1 = (c1 - 'A') + 'a': c1)#endiftypedef 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 modulestypedef 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;#endif //__NTDLL_H__inline LPVOID get_module_by_name(WCHAR* module_name){    PPEB peb = NULL;#if defined(_WIN64)    peb = (PPEB)__readgsqword(0x60);#else    peb = (PPEB)__readfsdword(0x30);#endif    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

用C编写和编译Shellcode

3. MASM宏

现在我们的 C 代码已经转换为汇编 ,我们需要稍微清理一下文件,这样我们就可以将其链接到.exe而不会出错,并避免 shellcode 崩溃。具体来说,我们需要:c-shellcode.asm

  1. 从外部库中删除依赖项
  2. 对齐堆栈
  3. 修复一个简单的语法问题

3.1 删除外部库

首先,我们需要注释掉或删除将此模块与库链接的指令,并且:libcmtoldnames

用C编写和编译Shellcode

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 callerAlignRSP ENDP

下面显示了它在 :c-shellcode.asm

用C编写和编译Shellcode

3.3 删除PDATA和XDATA段

删除或注释掉和细分,如下所示:PDATAXDATA

用C编写和编译Shellcode

3.4 修复语法问题

我们需要将行更改为:mov rax, QWORD PTR gs:96mov rax, QWORD PTR gs:[96]

用C编写和编译Shellcode

4. 链接到 EXE

我们现在已经准备好链接内部的程序集列表以获得可执行文件:

c-shellcode.asmc-shellcode.exe "C:Program Files (x86)Microsoft Visual Studio2019CommunityVCToolsMSVC14.26.28801binHostx64x64ml64.exe" c-shellcode.asm /link /entry:AlignRSP

用C编写和编译Shellcode

5. 测试EXE

我们现在可以检查它是否符合预期 - 弹出一个消息框:c-shellcode.exe

用C编写和编译Shellcode

6. 复制 Shellcode

一旦我们有了二进制文件,我们就可以提取 shellcode 并使用任何代码注入技术执行它,但为了这个实验,我们将它作为十六进制值列表复制出来,并简单地将它们粘贴到notepad.exe内的 RWX 内存插槽中。c-shellcode.exe

让我们从该部分复制出 shellcode,在我们的例子中,它从 0x200 开始到原始文件中:.text

用C编写和编译Shellcode

如果您想知道我们是如何找到 shellcode 位置的,请查看以下部分 - 您也可以从那里提取:.text

用C编写和编译Shellcode

7. 测试 Shellcode

复制 shellcode 后,让我们将其粘贴到记事本中的 RWX 内存区域(您可以将任何内存位置设置为具有 xdbg64 的 RWX 权限),将 RIP 设置为该位置并恢复该位置的代码执行。如果我们正确地执行了前面的所有步骤,我们应该看到我们的 shellcode 执行并弹出消息框:

用C编写和编译Shellcode

用C编写和编译Shellcode

用C编写和编译Shellcode

 

原文始发于微信公众号(白帽子社区团队):用C编写和编译Shellcode

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年7月11日14:06:34
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   用C编写和编译Shellcodehttp://cn-sec.com/archives/2942171.html

发表评论

匿名网友 填写信息