我最近在做一个关于shellcode入门和开发的专题课👩🏻💻,主要面向对网络安全技术感兴趣的小伙伴。这是视频版内容对应的文字版材料,内容里面的每一个环境我都亲自测试实操过的记录,有需要的小伙伴可以参考🫡
我的个人主页:https://imbyter.com
一、C语言方式编写shellcode
1. 新建0.createshellcode.cpp文件:用于生成整个项目的shellcode文件,便于其他项目加载执行shellcode。
#include "a.start.h"
#include "z.end.h"
#include "shellcode.h"
#pragma optimize("", off )
#pragma comment(linker,"/entry:EntryMain")
int EntryMain()
{
// 获取shellcode片段大小
DWORD dwShellcodeSize = (DWORD)ShellCodeEnd - (DWORD)ShellCodeStart;
DWORD dwWriten = 0;
HANDLE hFile = NULL;
// 创建文件,用于保存最后的shellcode
hFile = CreateFileA("shellcode.bin", GENERIC_WRITE | GENERIC_READ, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
if (hFile == INVALID_HANDLE_VALUE)
{
return -1;
}
// 将shellcode写入到文件
if (WriteFile(hFile, ShellCodeStart, dwShellcodeSize, &dwWriten, NULL) == FALSE)
{
CloseHandle(hFile);
return -1;
}
CloseHandle(hFile);
return 0;
}
2. 新建a.start.h、z.end.h,以及对应的a.start.cpp、z.end.cpp,分别用于标记shellcode的开始和结束。
a.start.h:
#pragma once
// 用于标记shellcode开始
void ShellCodeStart();
a.start.cpp:
#include "a.start.h"
#include "shellcode.h"
// 用于标记shellcode开始
void ShellCodeStart()
{
// shellcode执行的主要功能
ShellcodeMain();
}
z.end.h:
#pragma once
// 用于标记shellcode结束
void ShellCodeEnd();
z.end.cpp:
// 次函数仅用来标记shellcode结尾
void ShellCodeEnd()
{
}
3. 新建shellcode.h,以及对应的shellcode.cpp,用于编写shellcode执行的主要功能代码。
shellcode.h:
#pragma once
#include <windows.h>
#include <Winternl.h>
HMODULE GetKernel32BaseAddress();
FARPROC _GetPorcAddress();
int ShellcodeMain();
// 创建文件
int DoCreateFile();
// 弹框提示
int DoMessageBox();
shellcode.cpp:
#include "shellcode.h"
// shellcode主要执行的功能
int ShellcodeMain()
{
// 创建文件
DoCreateFile();
// 弹框提示
DoMessageBox();
// 其他功能...
return 0;
}
// 功能:创建文件 D:\1.txt
int DoCreateFile()
{
// 获取GetPorcAddress函数地址
typedef FARPROC(WINAPI* FN_GetProcAddress)(__in HMODULE hModule, __in LPCSTR lpProcName);
FN_GetProcAddress fn_GetProcAddress = (FN_GetProcAddress)_GetPorcAddress();
if (fn_GetProcAddress)
{
// 获取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(GetKernel32BaseAddress(), szLoadLibraryA);
if (fn_LoadLibraryA)
{
// 获取CreateFileA函数地址
char szCreateFileA[] = { 'C','r','e','a','t','e','F','i','l','e','A',0 };
typedef HANDLE(WINAPI* FN_CreateFileA)(
_In_ LPCSTR lpFileName,
_In_ DWORD dwDesiredAccess,
_In_ DWORD dwShareMode,
_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes,
_In_ DWORD dwCreationDisposition,
_In_ DWORD dwFlagsAndAttributes,
_In_opt_ HANDLE hTemplateFile
);
FN_CreateFileA fn_CreateFileA = (FN_CreateFileA)fn_GetProcAddress(GetKernel32BaseAddress(), szCreateFileA);
// 执行CreateFileA
char szFilePath[] = { 'D',':','\\','1','.','t','x','t',0 };
fn_CreateFileA(szFilePath, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL);
}
}
return 0;
}
// 功能:弹框提示
int DoMessageBox()
{
// 获取GetPorcAddress函数地址
typedef FARPROC(WINAPI* FN_GetProcAddress)(__in HMODULE hModule, __in LPCSTR lpProcName);
FN_GetProcAddress fn_GetProcAddress = (FN_GetProcAddress)_GetPorcAddress();
if (fn_GetProcAddress)
{
// 获取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(GetKernel32BaseAddress(), szLoadLibraryA);
if (fn_LoadLibraryA)
{
// 获取MessageBoxA函数地址
char szUser32[] = { 'U','s','e','r','3','2','.','d','l','l',0 };
char szMessageBoxA[] = { 'M','e','s','s','a','g','e','B','o','x','A',0 };
typedef int (WINAPI* FN_MessageBoxA)(__in_opt HWND hWnd, __in_opt LPCSTR lpText, __in_opt LPCSTR lpCaption, __in UINT uType);
FN_MessageBoxA fn_MessageBoxA = (FN_MessageBoxA)fn_GetProcAddress(fn_LoadLibraryA(szUser32), szMessageBoxA);
// 执行MessageBoxA
char szCaption[] = { 't','i','t','l','e',0 };
char szText[] = { 'H','e','l','l','o',' ','W','o','r','l','d', 0 };
fn_MessageBoxA(0, szText, szCaption, MB_OK | MB_ICONINFORMATION);
}
}
return 0;
}
// 获取kernel32基址
HMODULE GetKernel32BaseAddress()
{
HMODULE hKernel32 = NULL;
// 用户保存模块名
WCHAR wszModuleName[MAX_PATH];
#ifdef _WIN64 // 64位PEB偏移为0x60
PPEB lpPeb = (PPEB)__readgsqword(0x60);
#else // 32位PEB偏移为0x30
PPEB lpPeb = (PPEB)__readfsdword(0x30);
#endif
PLIST_ENTRY pListHead = &lpPeb->Ldr->InMemoryOrderModuleList;
PLIST_ENTRY pListData = pListHead->Flink;
// 遍历所有模块
while (pListData != pListHead)
{
PLDR_DATA_TABLE_ENTRY pLDRData = CONTAINING_RECORD(pListData, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks);
DWORD dwLen = pLDRData->FullDllName.Length / 2;
if (dwLen > 12) // 12 是"kernel32.dll"的长度,获取到的完整路径肯定要比模块名长
{
// 从获取到的模块完整路径中提取模块名
for (size_t i = 0; i < 12; i++)
{
wszModuleName[11 - i] = pLDRData->FullDllName.Buffer[dwLen - 1 - i];
}
// 最终要获取的目标模块名("kernel32.dll"),逐个字节比较,包含大小写。
if ((wszModuleName[0] == 'k' || wszModuleName[0] == 'K') &&
(wszModuleName[1] == 'e' || wszModuleName[1] == 'E') &&
(wszModuleName[2] == 'r' || wszModuleName[2] == 'R') &&
(wszModuleName[3] == 'n' || wszModuleName[3] == 'N') &&
(wszModuleName[4] == 'e' || wszModuleName[4] == 'E') &&
(wszModuleName[5] == 'l' || wszModuleName[5] == 'L') &&
(wszModuleName[6] == '3') &&
(wszModuleName[7] == '2') &&
(wszModuleName[8] == '.') &&
(wszModuleName[9] == 'd' || wszModuleName[9] == 'D') &&
(wszModuleName[10] == 'l' || wszModuleName[10] == 'L') &&
(wszModuleName[11] == 'l' || wszModuleName[11] == 'L'))
{
hKernel32 = (HMODULE)pLDRData->DllBase;
break;
}
}
pListData = pListData->Flink;
}
return hKernel32;
}
// 获取GetPorcAddress函数地址
FARPROC _GetPorcAddress()
{
// 保存最终结果
FARPROC pGetPorcAddress = NULL;
// kernel32基址
HMODULE hKernel32 = GetKernel32BaseAddress();
if (!hKernel32)
{
return NULL;
}
PIMAGE_DOS_HEADER lpDosHeader = (PIMAGE_DOS_HEADER)hKernel32;
PIMAGE_NT_HEADERS lpNTHeader = (PIMAGE_NT_HEADERS)((unsigned char*)hKernel32 + lpDosHeader->e_lfanew);
// 模块有效性验证
if (!lpNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size)
{
return NULL;
}
if (!lpNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress)
{
return NULL;
}
// 通过导出表中的导出函数名,定位"GetProcAddress"的位置
PIMAGE_EXPORT_DIRECTORY lpExports = (PIMAGE_EXPORT_DIRECTORY)((unsigned char*)hKernel32 + lpNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
PDWORD lpdwFunName = (PDWORD)((unsigned char*)hKernel32 + lpExports->AddressOfNames);
PWORD lpdwOrd = (PWORD)((unsigned char*)hKernel32 + lpExports->AddressOfNameOrdinals);
PDWORD lpdwFunAddr = (PDWORD)((unsigned char*)hKernel32 + lpExports->AddressOfFunctions);
for (DWORD dwLoop = 0; dwLoop <= lpExports->NumberOfNames - 1; dwLoop++)
{
char* pFunName = (char*)(lpdwFunName[dwLoop] + (unsigned char*)hKernel32);
// 比较函数名
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'
)
{
pGetPorcAddress = (FARPROC)(lpdwFunAddr[lpdwOrd[dwLoop]] + (unsigned char*)hKernel32);
break;
}
}
return pGetPorcAddress;
}
最后的框架结构如下图:
后续开发所有功能都可以在遵循shellcode编写原则的基础上,以新的.h头文件.cpp源文件进行扩展。
二、C++类方式编写shellcode
也可以使用C++类的方式进行编写,比如将shellcode.cpp改为CDoShellcode类的方式实现:
shellcode.h:
#pragma once
#include <windows.h>
#include <Winternl.h>
class CDoShellcode
{
private:
HMODULE GetKernel32BaseAddress();
FARPROC _GetPorcAddress();
public:
// 创建文件
int DoCreateFile();
// 弹框提示
int DoMessageBox();
};
shellcode.cpp:
#include "shellcode.h"
// 功能:创建文件 D:\1.txt
int CDoShellcode::DoCreateFile()
{
// 获取GetPorcAddress函数地址
typedef FARPROC(WINAPI* FN_GetProcAddress)(__in HMODULE hModule, __in LPCSTR lpProcName);
FN_GetProcAddress fn_GetProcAddress = (FN_GetProcAddress)_GetPorcAddress();
if (fn_GetProcAddress)
{
// 获取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(GetKernel32BaseAddress(), szLoadLibraryA);
if (fn_LoadLibraryA)
{
// 获取CreateFileA函数地址
char szCreateFileA[] = { 'C','r','e','a','t','e','F','i','l','e','A',0 };
typedef HANDLE(WINAPI* FN_CreateFileA)(
_In_ LPCSTR lpFileName,
_In_ DWORD dwDesiredAccess,
_In_ DWORD dwShareMode,
_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes,
_In_ DWORD dwCreationDisposition,
_In_ DWORD dwFlagsAndAttributes,
_In_opt_ HANDLE hTemplateFile
);
FN_CreateFileA fn_CreateFileA = (FN_CreateFileA)fn_GetProcAddress(GetKernel32BaseAddress(), szCreateFileA);
// 执行CreateFileA
char szFilePath[] = { 'D',':','\\','1','.','t','x','t',0 };
fn_CreateFileA(szFilePath, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL);
}
}
return 0;
}
// 功能:弹框提示
int CDoShellcode::DoMessageBox()
{
// 获取GetPorcAddress函数地址
typedef FARPROC(WINAPI* FN_GetProcAddress)(__in HMODULE hModule, __in LPCSTR lpProcName);
FN_GetProcAddress fn_GetProcAddress = (FN_GetProcAddress)_GetPorcAddress();
if (fn_GetProcAddress)
{
// 获取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(GetKernel32BaseAddress(), szLoadLibraryA);
if (fn_LoadLibraryA)
{
// 获取MessageBoxA函数地址
char szUser32[] = { 'U','s','e','r','3','2','.','d','l','l',0 };
char szMessageBoxA[] = { 'M','e','s','s','a','g','e','B','o','x','A',0 };
typedef int (WINAPI* FN_MessageBoxA)(__in_opt HWND hWnd, __in_opt LPCSTR lpText, __in_opt LPCSTR lpCaption, __in UINT uType);
FN_MessageBoxA fn_MessageBoxA = (FN_MessageBoxA)fn_GetProcAddress(fn_LoadLibraryA(szUser32), szMessageBoxA);
// 执行MessageBoxA
char szCaption[] = { 't','i','t','l','e',0 };
char szText[] = { 'H','e','l','l','o',' ','W','o','r','l','d', 0 };
fn_MessageBoxA(0, szText, szCaption, MB_OK | MB_ICONINFORMATION);
}
}
return 0;
}
// 获取kernel32基址
HMODULE CDoShellcode::GetKernel32BaseAddress()
{
HMODULE hKernel32 = NULL;
// 用户保存模块名
WCHAR wszModuleName[MAX_PATH];
#ifdef _WIN64 // 64位PEB偏移为0x60
PPEB lpPeb = (PPEB)__readgsqword(0x60);
#else // 32位PEB偏移为0x30
PPEB lpPeb = (PPEB)__readfsdword(0x30);
#endif
PLIST_ENTRY pListHead = &lpPeb->Ldr->InMemoryOrderModuleList;
PLIST_ENTRY pListData = pListHead->Flink;
// 遍历所有模块
while (pListData != pListHead)
{
PLDR_DATA_TABLE_ENTRY pLDRData = CONTAINING_RECORD(pListData, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks);
DWORD dwLen = pLDRData->FullDllName.Length / 2;
if (dwLen > 12) // 12 是"kernel32.dll"的长度,获取到的完整路径肯定要比模块名长
{
// 从获取到的模块完整路径中提取模块名
for (size_t i = 0; i < 12; i++)
{
wszModuleName[11 - i] = pLDRData->FullDllName.Buffer[dwLen - 1 - i];
}
// 最终要获取的目标模块名("kernel32.dll"),逐个字节比较,包含大小写。
if ((wszModuleName[0] == 'k' || wszModuleName[0] == 'K') &&
(wszModuleName[1] == 'e' || wszModuleName[1] == 'E') &&
(wszModuleName[2] == 'r' || wszModuleName[2] == 'R') &&
(wszModuleName[3] == 'n' || wszModuleName[3] == 'N') &&
(wszModuleName[4] == 'e' || wszModuleName[4] == 'E') &&
(wszModuleName[5] == 'l' || wszModuleName[5] == 'L') &&
(wszModuleName[6] == '3') &&
(wszModuleName[7] == '2') &&
(wszModuleName[8] == '.') &&
(wszModuleName[9] == 'd' || wszModuleName[9] == 'D') &&
(wszModuleName[10] == 'l' || wszModuleName[10] == 'L') &&
(wszModuleName[11] == 'l' || wszModuleName[11] == 'L'))
{
hKernel32 = (HMODULE)pLDRData->DllBase;
break;
}
}
pListData = pListData->Flink;
}
return hKernel32;
}
// 获取GetPorcAddress函数地址
FARPROC CDoShellcode::_GetPorcAddress()
{
// 保存最终结果
FARPROC pGetPorcAddress = NULL;
// kernel32基址
HMODULE hKernel32 = GetKernel32BaseAddress();
if (!hKernel32)
{
return NULL;
}
PIMAGE_DOS_HEADER lpDosHeader = (PIMAGE_DOS_HEADER)hKernel32;
PIMAGE_NT_HEADERS lpNTHeader = (PIMAGE_NT_HEADERS)((unsigned char*)hKernel32 + lpDosHeader->e_lfanew);
// 模块有效性验证
if (!lpNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size)
{
return NULL;
}
if (!lpNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress)
{
return NULL;
}
// 通过导出表中的导出函数名,定位"GetProcAddress"的位置
PIMAGE_EXPORT_DIRECTORY lpExports = (PIMAGE_EXPORT_DIRECTORY)((unsigned char*)hKernel32 + lpNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
PDWORD lpdwFunName = (PDWORD)((unsigned char*)hKernel32 + lpExports->AddressOfNames);
PWORD lpdwOrd = (PWORD)((unsigned char*)hKernel32 + lpExports->AddressOfNameOrdinals);
PDWORD lpdwFunAddr = (PDWORD)((unsigned char*)hKernel32 + lpExports->AddressOfFunctions);
for (DWORD dwLoop = 0; dwLoop <= lpExports->NumberOfNames - 1; dwLoop++)
{
char* pFunName = (char*)(lpdwFunName[dwLoop] + (unsigned char*)hKernel32);
// 比较函数名
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'
)
{
pGetPorcAddress = (FARPROC)(lpdwFunAddr[lpdwOrd[dwLoop]] + (unsigned char*)hKernel32);
break;
}
}
return pGetPorcAddress;
}
然后将a.start.cpp中的ShellCodeStart函数改为:
void ShellCodeStart()
{
CDoShellcode shellcode;
// 创建文件
shellcode.DoCreateFile();
// 弹框提示
shellcode.DoMessageBox();
// 其他功能...
}
这样即可实现类的方式执行shellcode功能。
测试:运行以上代码对应生成的exe文件,会在当前路径下生成shellcode.bin文件,该文件就是我们最后得到的shellcode文件。使用编写shellcode加载器加载执行shellcode.bin测试效果。
如果有任何问题,可以在我们的知识社群中提问和沟通交流:
一个人走得再快,不如一群人走得更远!🤜🤛