一例cobalt Strike payload 反射式dll注入的分析
QakBot(Qbot)与cobalt Strike恶意流量样本分析 | Demon (ggsec.cn)这篇博客中末尾提到了一个cobastrick的payload,这是一段shellcode,主要功能是的解密出一个dll,采用反射式注入的方式启动这个dll。本文主要是分析这段payload,有助于理解reflectiveinject的原理。
反射式注入 reflectiveInject的介绍可参考 反射式dll注入(ReflectiveDLLInjection)_随心动,随风行的博客-CSDN博客_reflectivedllinjection
github上有相应的实现
https://github.com/stephenfewer/ReflectiveDLLInjection
样本信息
MD5: 7c01dd99cee0668afc9b32308b8132f5
SHA1: dd1d89906e903caab123586f9b3369326200aeef
SHA256: 09ffa300c2343d68f7c9cc1a7e8f136c5c99cabd5e69a7326efabfece7aff59d
SHA512: dec9e255cb79c13e40539a024e2ffc290249810f7d51a418e62bf60a37d4cbf546e4662f4559c78a4832e637f71a91cbffbbb5042ba3823e24532c334c8a37ef
CRC32: 124f37e5
分析环境
-
win7 x64 虚拟机
-
IDA Pro6.8
-
OD
-
vs2010
静态分析
对样本进行进行静态分析,主要功能是在内存中解密并执行一段shellcode,将跳转执行,如下图所示
由上面的分析可知,加密的shellcode大小为0x00033000,在文件中的偏移为0x42,基于上图中的分析,可对这段shellcode进行解密。下面是提取并解密shellcode的代码
#include <stdio.h>
#include <windows.h>
int main(int argc,char** argv){
FILE* file = fopen("shellcode.bin","rb");
DWORD sc_size = 0x00033000;
DWORD offset = 0x42;
fseek(file,offset,SEEK_SET);
char* buf = (char*)malloc(sc_size);
memset(buf,0,sc_size);
size_t readbytes = 0;
while(readbytes < sc_size){
size_t r = fread(buf + readbytes,1,sc_size-readbytes,file);
if(r < 0) break;
readbytes += r;
}
fclose(file);
if (sc_size == readbytes){
//解密
DWORD key = 0x04DBA13B;
DWORD *_buf = (DWORD*)buf;
DWORD _sc_size = sc_size / sizeof(DWORD);
for(int i = 0;i< _sc_size;i++){
_buf[i] ^= key;
key ^= _buf[i];
}
//导出
FILE* outfile = fopen("shellcode1.bin","wb");
fwrite(buf,1,sc_size,outfile);
fclose(outfile);
}
free(buf);
return 0;
}
下面对shellcode1.bin进行分析,这个文件比较特殊的地方是即是一段shellcode,又是一个合法的dll,先使用IDA用binary file模式打开
进入了sub_8157看一下
上面是以binary方式打开的,下面以pe形式打开定位到这个函数。
0x8157是文件偏移,我们需要转化成VA,使用LordPE的位置计算机计算出VA为0x10008D57
用OD使用pe模式打开shellcode1.bin,定位到函数 0x10008D57,发现这是dll的一个导出函数,函数名为ReflectiveLoader,功能为一个以shellcode的方式来加加载dll。
下面我们来分析一个这个函数
首先找到PE的起始位置
下面这段是shellcode的通用操作,通过PEB找到kernel32.dll在内存中的基址,可参考[原创]PEB结构:获取模块kernel32基址技术及原理分析-软件逆向-看雪论坛-安全社区|安全招聘|bbs.pediy.com
LDR_DATA_TABLE_ENTRY 结构体可参考Vergilius Project | _LDR_DATA_TABLE_ENTRY
从kernerl32.dll的导出表中找到下列几个api的地址
LoadLibraryA
GetProcAddress
VirtualAlloc
VirtualProtect
LoadLibraryExA
GetModuleHandleA
下面这段代码复现了上面通过PEB获取api地址的过程,并导出api的hash表
#include <stdio.h>
#include <windows.h>
#include <winternl.h>
//计算哈希值
#define ROTR32(value, shift) (((DWORD) value >> (BYTE) shift) | ((DWORD) value << (32 - (BYTE) shift)))
//重新定义PEB结构。winternl.h中的结构定义是不完整的。
typedef struct _MY_PEB_LDR_DATA {
ULONG Length;
BOOL Initialized;
PVOID SsHandle;
LIST_ENTRY InLoadOrderModuleList;
LIST_ENTRY InMemoryOrderModuleList;
LIST_ENTRY InInitializationOrderModuleList;
} MY_PEB_LDR_DATA, *PMY_PEB_LDR_DATA;
typedef struct _MY_LDR_DATA_TABLE_ENTRY
{
LIST_ENTRY InLoadOrderLinks;
LIST_ENTRY InMemoryOrderLinks;
LIST_ENTRY InInitializationOrderLinks;
PVOID DllBase;
PVOID EntryPoint;
ULONG SizeOfImage;
UNICODE_STRING FullDllName;
UNICODE_STRING BaseDllName;
} MY_LDR_DATA_TABLE_ENTRY, *PMY_LDR_DATA_TABLE_ENTRY;
#define ROTR32(value, shift) (((DWORD) value >> (BYTE) shift) | ((DWORD) val
ue << (32 - (BYTE) shift)))
//导出api的hash值
void ExportProcAddressHash()
{
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;
PebAddress = (PPEB)__readfsdword(0x30);
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;
}
//计算dll名的哈希值
for (int i = 0; i < BaseDllName.Length; 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 (int 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);
printf("%ws 0x%.8X %s 0x%.8X\n",BaseDllName.Buffer,dwModuleHash,pFunctionName,dwFunctionHash);
}
}
}
导出的结果如下图所示,得到的hash与样本分析的一致。
使用VirtualAlloc分配一段内存
拷贝PE头部到分配的内存
根据节表将各个节拷贝到内存相应位置
根据导入表,加载相应的动态库,更新IAT,页面还有解密dll名称和函数名称的操作
地址重定位
将.text节属性修改为可执行,定位dll的入口点,执行DllMain(selfHanlde,DLL_PROCESS_ATTCH,.),最后返回DllEntryPoint的地址
在汇编语言中如何获取当前指令的地址,可以用这种方法,在这个样本中,两次用到了这个技术。
call $+5 ;将当前指令的下一条指令的地址入栈
pop eax ;将栈顶的值给eax,获取当前指令的地址
动态分析
因为样本是一段shellcode,无法自己运行,需要加载,手动完成一个shellcode加载器,开发环境是vs2010,代码如下 :
#include <stdio.h>
#include <windows.h>
int LoadShellCode(int argc,char** argv){
//打开shellcode文件
FILE* file = fopen("shellcode.bin","rb");
//读取文件大小
fseek(file,0,SEEK_END);
size_t filesize = ftell(file);
rewind(file);
//分配内存,注意页面属性为PAGE_EXECUTE_READWRITE
char* buf = (char*)VirtualAlloc(NULL,filesize,MEM_COMMIT,PAGE_EXECUTE_READWRITE);
memset(buf,0,filesize);
//读取文件内容
size_t readbytes = 0;
while(readbytes < filesize){
size_t r = fread(buf + readbytes,1,filesize-readbytes,file);
if(r < 0) break;
readbytes += r;
}
//跳转到shellcode起始位置执行
if(readbytes == filesize){
_asm{
jmp buf
}
}
//关闭文件,释放内存
fclose(file);
VirtualFree(buf,0,MEM_RELEASE);
return 0;
}
编译生成LoadShellcode.exe,使用IDA打开,找到上面jmp buf语句的VA为0x004114DA
使用OD调试LoadShellcode.exe,在0x004114DA处中断
F7单步进入shellcode,用IDA以binary模式打开shellcode.bin文件,反汇编后的代码与OD中的一致,说明已经成功的跳转到shellcode的起始位置
解密出来一个PE文件,地址为0x00230042 大小为0x00033000
使用pchunter将这个PE导出来
其实这个dll是一个corbastrike payload,我们直接动态调试可获取其CC服务器
检索这个域名 amajai-technologies.work,就可知道这是一个QakBot家族的恶意样本。
VT上的搜索结果
参考资料
QakBot(Qbot)与cobalt Strike恶意流量样本分析 | Demon (ggsec.cn)
反射式dll注入(ReflectiveDLLInjection)_随心动,随风行的博客-CSDN博客_reflectivedllinjection
[原创]PEB结构:获取模块kernel32基址技术及原理分析-软件逆向-看雪论坛-安全社区|安全招聘|bbs.pediy.com
Vergilius Project | _LDR_DATA_TABLE_ENTRY
https://github.com/stephenfewer/ReflectiveDLLInjection