省流
DodgeBox使用傀儡模块加载payload,具体步骤如下(忽略了很多细节):
-
• 检查payload的有效性
-
• 在system32下查找代码段足够大的模块作为目标
-
• 复制目标模块到指定路径并重命名(下称傀儡模块),优先复制到%systemroot%,如果没有权限,就复制到%programdata%下
-
• 将展开后的payload头和代码段写入傀儡模块
-
• 查找傀儡模块镜像大小的空闲内存
-
• 对傀儡模块进行映射,使用NtCreateSection和ZwMapViewOfSection对模块进行映射
-
• 将展开后的payload剩余部分写入映射区域
-
• 调用payload入口函数
-
校验payload
-
• 标志位检查
-
• 架构检查
-
• 重要字段判定
-
• 通过属性判断是否是一个可执行的(0x2)dll模块(0x2000)
-
• 文件头中的可选头大小是否等于实际的可选头大小,这个字段在后面会经常用到
-
• 文件大小参数检查
下面是在校验所有节的文件偏移确实小于文件大小,否则给定的文件大小有误。
注意红框内代码,在获取原始文件中节偏移和节大小时并没有使用IMAGE_SECTION_HEADER结构体,而是通过入口点偏移地址加上可选头大小来获得指向原始文件节大小位置,再来获取节偏移,初看会有点懵,需要自己去查表,实际上像这样的小手段还有很多。
查找目标
查找目录
bool find_satisfied_sysdll(wchar_t* _out_name,
wchar_t* _out_path,
DWORD payload_text_end_rva,
DWORD size_except_virtual_text)
查找条件
block_dll[0] = (__int64)L"advapi32.dll";
block_dll[2] = (__int64)L"bcrypt.dll";
block_dll[3] = (__int64)L"bcryptprimitives.dll";
block_dll[4] = (__int64)L"cfgmgr32.dll";
block_dll[5] = (__int64)L"combase.dll";
block_dll[6] = (__int64)L"cryptbase.dll";
block_dll[7] = (__int64)L"cryptsp.dll";
block_dll[8] = (__int64)L"dhcpcsvc.dll";
...
(text_end_virtual - entry_rva - 0x10) > payload_text_end_rva &&
(nt_header->OptionalHeader.SizeOfImage - text_end_virtual) >= size_except_virtual_text
感染目标副本
拷贝副本
寻找空闲内存
-
• 以(kernel32基址 - 傀儡模块映射内存大小)(需要按照0x100000对齐)为起点,每次递减0x100000查找可用内存区域映射傀儡模块,找到后就能确定payload的新基址,计算公式如下:payload_new_base = new_image_base + src_data_rva - payload_data_rva;
payload手动展开
-
• 在内存中展开payload -
• payload_image_handle_iat:手动加载导入模块,修复导入表,同时抹掉导入表中的模块名和函数名 -
• payload_image_handle_reloc:手动对payload进行重定位 -
• payload_image_remove_table -
• 抹掉payload的导入表和导入目录 -
• 抹掉pyaload的重定位表和重定位目录 -
• 抹掉调试表和调试目录 -
• paylod_image_remove_other:抹掉文件头中的Machine,TimeDateStamp,Characteristics,可选头中Subsystem,除了.lrsrc之外的节名
感染傀儡模块磁盘文件:
-
• 修改可选头中DllCharacteristics,去掉随机基址 -
• 重新设置加载基址 -
• 抹掉重定位和TLS -
• 找到入口点的FOA并进行PATCH,写入0xC300000001C0C748也就是
mov eax,1
return
-
• 将payload的【头和代码段】写入文件
阶段性总结
为了防止搞混,我通过一张图来总结一下:
目前payload已经在内存中展开并进行导入表,重定位表等修复,以及抹除了某些信息,同时目标文件也已经被抹掉了一些字段,并被写入了payload的代码部分,截止到目前目标文件还没有被映射到内存中
映射傀儡模块
感染傀儡模块内存
如果上述流程出问题了怎么办?
总结:
我暂且用傀儡模块来称呼这一技术实现,从原理上来说我想大多数人听我这么一说感觉并不是十分的复杂,与我们平时所见或者是对比傀儡进程来说无非是将操作步骤进行了拆分,一部分在文件,一部分在内存。但是从代码实现角度来说,APT41的实现是完整且细腻的,文章实际上只是进行了大纲的总结提炼,还有很多的代码细节保证了稳定和健壮性,以及保证了自身的隐蔽性。
原文始发于微信公众号(无名之):APT41 DodgeBox 出来混呢,求的就是一个”稳“字
- 左青龙
- 微信扫一扫
- 右白虎
- 微信扫一扫
评论