免杀基础–windows shellcode编写入门
0x00 介绍
在计算机安全中,shellcode是一小段代码,可以用于软件漏洞利用的载荷。被称为"shellcode"是因为它通常启动一个命令终端,攻击者可以通过这个终端控制受害的计算机,但是所有执行类似任务的代码片段都可以称作shellcode。……Shellcode通常是以机器码形式编写的。 -----维基百科
作为一名红队人员,最常接触的shellcode生成器可能是CobalStrike或者msfvenom。其中msfvenom可以生成很多种shellcode,可有时我们想为了实现自定义功能的shellcode或者为了增强shellcode的免杀能力,需要编写自己的shellcode。
0x01 Shellcode特点
在编写自己的shellcode时,我们必须需要注意shellcode的一些限制:
- 不能使用字符串的直接偏移
- 不能确定函数的地址(如printf)
由于这些限制,导致我们写shellcode不可能像高级编程语言那样轻松直接的调用一个API、读写字符串。这里面关键是要解决API和数据地址问题。
0x02 编写shellcode
- 针对API地址,采取如下步骤:
1)从PEB获取kernel32.dll 基地址; 2)定位 GetProcAddress函数的地址; 3)使用GetProcAddress确定 LoadLibrary函数的地址; 4)然后使用 LoadLibrary加载DLL文件(例如user32.dll); 5)使用 GetProcAddress查找某个函数的地址(例如MessageBox); 6)指定函数参数; 7)调用函数。
- 针对代码要使用的字符串,采取字符数组形式将数组保存在栈中。
0x03 关键代码
获取API函数地址的函数(兼容x86和x64):
//************************************************************
// 函数名称: GetProcAddressWithHash
// 函数说明: 通过哈希获取函数地址
// 参 数: dwModuleFunctionHash 函数加模块的哈希值
// 返 回 值: HMODULE 函数地址
//************************************************************
HMODULE GetProcAddressWithHash(DWORD dwModuleFunctionHash)
{
PPEB PebAddress;
PMY_PEB_LDR_DATA pLdr;
PMY_LDR_DATA_TABLE_ENTRY pDataTableEntry;
PVOID pModuleBase;
PIMAGE_NT_HEADERS pNTHeader;
DWORD dwExportDirRVA;
PIMAGE_EXPORT_DIRECTORY pExportDir;
PLIST_ENTRY pNextModule;
DWORD dwNumFunctions;
USHORT usOrdinalTableIndex;
PDWORD pdwFunctionNameBase;
PCSTR pFunctionName;
UNICODE_STRING BaseDllName;
DWORD dwModuleHash;
DWORD dwFunctionHash;
PCSTR pTempChar;
DWORD i;
#if defined(_WIN64)
PebAddress = (PPEB)__readgsqword(0x60);
#elif defined(_M_ARM)
PebAddress = (PPEB)((ULONG_PTR)_MoveFromCoprocessor(15, 0, 13, 0, 2) + 0);
__emit(0x00006B1B);
#else
PebAddress = (PPEB)__readfsdword(0x30);
#endif
pLdr = (PMY_PEB_LDR_DATA)PebAddress->Ldr;
pNextModule = pLdr->InLoadOrderModuleList.Flink;
pDataTableEntry = (PMY_LDR_DATA_TABLE_ENTRY)pNextModule;
while (pDataTableEntry->DllBase != NULL)
{
dwModuleHash = 0;
pModuleBase = pDataTableEntry->DllBase;
BaseDllName = pDataTableEntry->BaseDllName;
pNTHeader = (PIMAGE_NT_HEADERS)((ULONG_PTR)pModuleBase + ((PIMAGE_DOS_HEADER)pModuleBase)->e_lfanew);
dwExportDirRVA = pNTHeader->OptionalHeader.DataDirectory[0].VirtualAddress;
//获取下一个模块地址
pDataTableEntry = (PMY_LDR_DATA_TABLE_ENTRY)pDataTableEntry->InLoadOrderLinks.Flink;
// 如果当前模块不导出任何函数,则转到下一个模块 加载模块入口
if (dwExportDirRVA == 0)
{
continue;
}
//计算模块哈希值
for (i = 0; i < BaseDllName.MaximumLength; i++)
{
pTempChar = ((PCSTR)BaseDllName.Buffer + i);
dwModuleHash = ROTR32(dwModuleHash, 13);
if (*pTempChar >= 0x61)
{
dwModuleHash += *pTempChar - 0x20;
}
else
{
dwModuleHash += *pTempChar;
}
}
pExportDir = (PIMAGE_EXPORT_DIRECTORY)((ULONG_PTR)pModuleBase + dwExportDirRVA);
dwNumFunctions = pExportDir->NumberOfNames;
pdwFunctionNameBase = (PDWORD)((PCHAR)pModuleBase + pExportDir->AddressOfNames);
for (i = 0; i < dwNumFunctions; i++)
{
dwFunctionHash = 0;
pFunctionName = (PCSTR)(*pdwFunctionNameBase + (ULONG_PTR)pModuleBase);
pdwFunctionNameBase++;
pTempChar = pFunctionName;
//计算函数名hash
do
{
dwFunctionHash = ROTR32(dwFunctionHash, 13);
dwFunctionHash += *pTempChar;
pTempChar++;
} while (*(pTempChar - 1) != 0);
//与模块hash相加
dwFunctionHash += dwModuleHash;
if (dwFunctionHash == dwModuleFunctionHash)
{
usOrdinalTableIndex = *(PUSHORT)(((ULONG_PTR)pModuleBase + pExportDir->AddressOfNameOrdinals) + (2 * i));
return (HMODULE)((ULONG_PTR)pModuleBase + *(PDWORD)(((ULONG_PTR)pModuleBase + pExportDir->AddressOfFunctions) + (4 * usOrdinalTableIndex)));
}
}
}
return NULL;
}
使用GetProcAddressWithHash函数,获取所有要使用的API函数地址(以MessageBoxA为例)
//************************************************************
// 函数名称: Initfunctions
// 函数说明: 动态获取所有的函数地址
// 参 数: Pfunctions pfn 保存有所有API函数地址的结构体
// 返 回 值: void
//************************************************************
void Initfunctions(Pfunctions pfn)
{
//获取LoadLibraryA函数地址
pfn->fnLoadLibraryA = (pfnLoadLibraryA)GetProcAddressWithHash(HASH_LoadLibraryA);
//将user32.dll加载到当前进程中,此处就是对字符串的处理:字符串数组形式
char szUser32[] = { 'u', 's', 'e', 'r', '3', '2', '.', 'd', 'l', 'l', 0 };
pfn->fnLoadLibraryA(szUser32);
//获取MessageBoxA函数地址
pfn->fnMessageBoxA = (pfnMessageBoxA)GetProcAddressWithHash(HASH_MessageBoxA);
}
真正的shellcode功能代码
Functions fn;
//动态获取所有需要的函数指针
Initfunctions(&fn);
//调用MessageBox
char szText[] = { 'H', 'e', 'l', 'l', 'o', ' ', 'B', 'a', 'd', ' ', 'B', 'o', 'y', 0 };
char szCaption[] = { 'N', 'o', 't','e', 0 };
fn.fnMessageBoxA(NULL, szText, szCaption, MB_OK);
生成shellcode 数据文件代码
void CreateShellCode()
{
HANDLE hFile = CreateFileA("ShellCode.bin", GENERIC_ALL, 0, NULL, CREATE_ALWAYS, 0, NULL);
if (hFile==INVALID_HANDLE_VALUE)
{
MessageBoxA(NULL, "CreateFileA Error", "Error", MB_ERR_INVALID_CHARS);
return;
}
DWORD dwSize = (DWORD)ShellCodeEnd - (DWORD)ShellCodeStart;
DWORD dwWrite = 0;;
WriteFile(hFile, ShellCodeStart, dwSize, &dwWrite,NULL);
CloseHandle(hFile);
}
通过以上代码,生成的shellcode.bin就是最终的需要的shellcode。
编译选项(VS2013+):如果生成的shellcode验证不通过,关闭VS编译优化。
验证shellcode
typedef VOID(*PCALLL)();
int main(int argc, char* argv[])
{
//打开要执行的ShellCode文件
HANDLE hFile = CreateFileA("ShellCode.bin", GENERIC_READ, 0, NULL, OPEN_ALWAYS, 0, NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
printf("CreateFile Error");
return -1;
}
DWORD dwSize = 0;
//获取ShellCode的总大小
dwSize = GetFileSize(hFile, NULL);
//申请一块可读可写可执行的内存
LPVOID lpAddress = VirtualAlloc(NULL, dwSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if (lpAddress == NULL)
{
printf("VirtualAlloc Error");
CloseHandle(hFile);
return -1;
}
//将文件读取到申请的内存中
DWORD dwRead = 0;
ReadFile(hFile, lpAddress, dwSize, &dwRead, 0);
//执行ShellCode for x86
//__asm
//{
// call lpAddress;
//}
//执行ShellCode
PCALLL p = (PCALLL)lpAddress;
p();
return 0;
}
演示:
参考:
https://github.com/TonyChen56/ShellCodeFrame