1.前言
Brute Ratel C4是类似于Cobalt Strike的商业红队框架,每年的License收费为$2500,首个版本Brute Ratel C4 v0.2 (Prometheus)于2021年2月9日发布,截至发稿最新发布版本v1.1(Stoffel's Escape)与2022年7月20日发布,相比与Cobalt Strike的知名度BRc4显然要低调的多,但是BRc4使用的技术和功能却丝毫不亚于CS,本次分析的样本使用的最终payload就是BRc4框架。
2.样本运行流程图
3.样本IOCs
名称: Roshan_CV.iso
大小: 4839424 字节 (4726 KiB)
MD5: a7df3462a6dce565064cfe408557c4df
SHA1: 6b91bfc761fe958c8ac04dd403db284ccc3a530e
SHA256: 1fc7b0e1054d54ce8f1de0cc95976081c7a85c7926c03172a3ddaa672690042c
名称: OneDrive.Update
大小: 277064 字节 (270 KiB)
MD5: d6eae771c6cb12e17028fa35e4fba295
SHA1: 70767d3f7ff7736063ee76c0d9c0e90ce895332b
SHA256: b5d1d3c1aec2f2ef06e7d0b7996bc45df4744934bd66266a6ebb02d70e35236e
名称: OneDriveUpdater.exe
大小: 4200864 字节 (4102 KiB)
MD5: 792e95b64b9cf45ac8bc10d4d0f077c2
SHA1: e50af7ee7e0a323d8aa60b6d9b3d39ab33b004f5
SHA256: 60e64dd2c6d2ac6fe9b498fadac81bc34a725de5d893e7df8b2728d8dc5b192d
名称: version.dll
大小: 259584 字节 (253 KiB)
MD5: 76fa734236daa023444dec26863401dc
SHA1: a68a1b7ca1d1ddd005fd10382bf6cd0b86e74e86
SHA256: ea2876e9175410b6f6719f80ee44b9553960758c7d0f7bed73c0fe9a78d8e669
名称: vresion.dll
大小: 31496 字节 (30 KiB)
MD5: 17c1e1099b65051bb6dec71fea37315b
SHA1: 8ed26469afbd53da7749ef9c6ab8c7f010e9bb1e
SHA256: e549d528fee40208df2dd911c2d96b29d02df7bef9b30c93285f4a2f3e1ad5b0
名称: Roshan-Bandara_CV_Dialog
大小: 3149 字节 (3.07 KiB)
MD5: 82eb32d941b6dd63957a37453c9b0333
SHA1: f27a374044c73a6a5b2d926bb9468daf570b8bfc
SHA256: 4466017fd40c4b5fa9923928b7be3acf03825b4dd8c5fcf088992aee398b8c2d
4.恶意代码分析
恶意样本是个iso文件,双击挂载iso,一共有5个文件,除了Roshan-Bandara_CV_Dialog
这个快捷方式之外其他的文件都是隐藏文件(我开启了显示隐藏文件所以这里全部显示了)
我们查看Roshan-Bandara_CV_Dialog快捷方式指向的目标%windir%/system32/cmd.exe /c start OneDriveUpdater.exe
,主要就是调用cmd命令执行了隐藏文件OneDriveUpdater.exe,cmd.exe /c
命令指定执行完后会自动关闭控制台窗口
我们接着看这个快捷方式的图标文件,发现其图标文件指向%ProgramFiles%\Microsoft Office\root\Office16\WINWORD.EXE
,由于本机没有装Office 2016所以此快捷方式的图标就无法显示,由此可见此次攻击样本十分有针对性
其中的vresion.dll和OneDriveUpdater.exe为白文件都有Microsoft的有效数字签名
其中的version.dll链接器版本14.29是使用1929 (Visual Studio 2019 Version 16.10 + 16.11)编译的64位DLL,可以看到编译时间戳为2022-5-17 16:35:50
version.dll和vresion.dll的导出函数完全相同
OneDrive.Update是一个被加密的二进制文件,猜测可能是shellcode或者pe文件
使用ida64打开OneDriveUpdater.exe分析,我们可以看到OneDriveUpdater.exe一共调用了version.dll中的3个导出函数
接下来我们使用ida64分析version.dll,查看version.dll的导出函数时发现并没有相关的实现,而是使用了导出函数转发技术将函数转发到白文件vresion.dll的实现
4.1 stage0:version.dll代码分析
接下来我们分析version.dll的DllMain函数,只调用了一个sub_180002090函数
sub_180002090函数使用了WTSEnumerateProcessesA函数枚举进程
循环查找RuntimeBroker.exe
进程
读取加密文件OneDrive.Update
到内存
控制台窗口打印字符串"Please wait..."
先调用了NtDelayExecution
函数休眠了几秒,接着调用NtOpenProcess
函数以PROCESS_ALLACCESS
权限打开RuntimeBroker.exe
进程
此样本的NtOpenProcess函数实现,这些汇编代码特征让我想到了SysWhispers3项目
这是我使用SysWhispers3项目生成的NtOpenProcesss的syscall调用,发现除了SysWhispers3使用函数Hash方式之外汇编代码特征完全一致,可以判断此样本使用了SysWhispers3项目
接下来我们详细分析下首先SW3_GetSyscallEGG_180001D20
函数参数为ntop
主要逻辑是通过gs:[60]
获取PEB,然后通过偏移找到PEB->Ldr->InMemoryOrderModuleList
链表,并通过访问LDR_DATA_TABLE_ENTRY->FullDllName
字段使用_stricmp函数判断找到ntdll.dll
接下来通过解析ntdll.dll的PE结构获取导出函数
第一个导出函数名称
memcmp函数对比直到找到导出函数NtOpenProcess
最终获取到导出函数地址
接下来通过搜索函数NtOpenProcess的OPCODE特征找到syscall(0F05)的地址(EGG)
接下来分析SW3_GetSyscallNumber_180002000
函数,主要逻辑还是通过OPCODE特征搜索找到syscall id,关于syscall id对应的函数在不同windows版本下不同可以在j00ru的博客查询
此时RAX为NtOpenProcess函数的syscall id:26,r15为syscall的地址
通过syscall调用NtOpenProcess函数
接下来使用硬编码的xor密钥jikoewarfkmzsdlhfnuiwaejrpaw
解密OneDrive.Update
,解密大小为0x493E0
调用了NtCreateSection
函数创建0x493E0
大小的PAGE_EXECUTE_READWRITE
内存权限,SECTION_MAP_READ | SECTION_MAP_WRITE | SECTION_MAP_EXECUTE
访问属性的section
接着调用NtMapViewOfSection
函数将section映射到本进程虚拟内存
并调用NtMapViewOfSection
函数将section映射到RuntimeBroker.exe
进程虚拟内存
使用memmove函数将解密后的shellcode复制到本进程section共享内存空间,此时section内存会同步映射到远程进程RuntimeBroker.exe
进程虚拟内存
然后调用NtCreateThreadEx函数在RuntimeBroker.exe进程中创建远程线程执行注入的shellcode
我们使用processhack工具查看RuntimeBroker.exe
进程内存可以看到shellcode已经注入成功
4.2 stage1:OneDrive.Update代码分析
为了方便调试我将stage0解密后的shellcode从内存中dump下来并使用自写的shellcode loader加载调试分析,shellcode开头有巨量的mov
和push
执行的组合,通过压入栈中的数据可以判断大致是在初始化数据和函数调用要用到的参数等等
在偏移0x43750处才开始主要逻辑
可以看到刚刚压入栈中的众多数据包含了一个PE文件并且此PE文件的Dos头的MZSignature(0x5A4D)被抹除防止EDR进行内存扫描查杀,通过访问0x3C位置AddressOfNewExeHeader开始解析此PE文件
最终获取导出函数的RVA:0x92E0
接下来的逻辑还是熟悉的通过PEB-Ldr->InMemoryOrderModuleList
获取ntdll.dll的基址
然后调用FindHashFunAddress_5137AE函数通过函数Hash获取对应函数的地址
通过解析ntdll.dll的PE结构获取导出函数名称表
并对导出函数名进行ror13 Hash然后对比目标函数Hash直到找到需要的函数
获取到需要的导出函数地址后会调用CheckFuninlineHook_51375B函数进行检查
首先检查函数头是否被下了int3软件断点(0xCC),然后通过检测OPCODE编码E9(jmp)判断函数是否被EDR给inlineHook,如果函数正常则通过偏移获取函数的syscall id
接着将参数压栈,通过syscall调用ZwAllocateVirtualMemory
申请PAGE_READWRITE
权限的内存
接着又申请了一块内存PAGE_READWRITE
权限的内存
申请完内存后调用了CopyPeToAllocAddress_513815函数
此函数将栈中的PE文件(0x3240)大小的内容以每次QWORD大小复制到申请的虚拟内存中
PE文件复制完成
接着调用 MemSetPeMemoryFeatures_513805函数将内存中PE文件Dos头除了AddressOfNewExeHeader字段以外的内存置0,防止EDR扫描内存查杀
又调用了MemSetPeMemoryFeatures_513805函数将内存中PE文件DosStub部分的内存置0
接着调用CopyBase64ToAllocAddress_513820函数
将0x148字节大小的Base64编码数据复制到申请的第二块虚拟内存中
复制完内存后还是通过调用FindHashFunAddress_5137AE函数通过函数Hash获取ZwProtectVirtualMemory函数地址
调用ZwProtectVirtualMemory将存放PE文件那块内存属性改为PAGE_EXECUTE_READ
属性
获取了ZwCreateThreadEx的syscall id
调用ZwCreateThreadEx执行的函数为函数开头获取的导出函数
调用ZwWaitForSingleObject函数等待线程执行完毕
4.3 stage2:ReflectiveDLLInjection代码分析
stage1创建的线程执行的函数入口,搜索内存找到Nt头的Signature(PE)地址,通过kernel32.dll的hash获取基址
访问PEB->Ldr->InMemoryOrderModuleList
链表并对FullDllName进行ror13 Hash
对比Hash正确
获得kernel32.dll基址
函数GetNtdllAddress_66b2c0通过PEB加偏移获取ntdll.dll的基址
接下来调用GetFunAddressByHash_668050函数通过Hash获取6个对应函数的地址
通过解析kernel32.dll的PE结构导出表,对导出函数进行Hash对比找到对应的函数
成功获取LoadLibraryA函数地址
获取完需要的函数地址之后,对于ntdll的导出函数进行inlineHook检测并动态获取对应的syscall id
检测函数是否被下int3软件断点,并检测函数头前7个字节OPCODE是否和函数固定特征相同,最后通过固定偏移获取syscall id
通过BRc4官网的功能介绍可知检测EDR的HOOK是BRc4的主要功能之一
此时rax为动态获取的syscall id
通过此函数获取了4个ntdll的导出函数syscall id
syscall(间接系统调用)也是BRc4官网介绍的主要功能特征
调用NtSetInformationProcess函数并设置参数PROCESS_INFOMATION_CLASS
为0x28
通过syscall id调用NtSetInformationProcess函数
调用NtAllocateVirtualMemory函数申请了PAGE_READWRITE
权限的内存
syacall id调用NtAllocateVirtualMemory函数
接下来将PE头0x400字节复制到了新申请的内存,通过前面获取的函数地址和此复制操作有分析过类似样本的应该可以大致推断这是ReflectiveDLLInjection(反射式注入),主要逻辑就是模拟系统的PE Loader将PE文件复制到可执行内存中并修复IAT处理重定位数据然后直接调用内存中的函数执行,虽然这是10年前发布的技术但metasploit和cobalt strike等等框架还是普遍有使用ReflectiveDLLInjection技术,此方式可以避免文件落地以减少EDR查杀
接着将全部section逐个复制到新内存
复制下一个section
复制完全部secsion后开始获取IAT函数地址填充修复IAT
循环直到修复完IAT
修复重定位数据
调用NtFlushInstructionCache函数刷新缓存
调用NtProtectVirtualMemory修改各个section的内存权限
所有section被更改的内存权限
我们使用processhack工具查看内存中各个区段的权限
之后将PE头0x200字节内存置0,防止EDR扫描内存特征查杀
最后调用dllmain
4.4 stage3:DllPayload代码分析
我们将内存中的PE文件dump出来,可以看到此PE文件为MinGW-w64编译的x64 DLL
编译时间戳为2022-3-26 4:51:36
,一共有11个sections
有一个无名导出函数为ReflectiveDLLInjection
接下来我们分析dllmain的两个函数sub_7A1500和sub_7A1FB0
sub_7A1500函数通过多个时间戳函数和进程线程ID异或获得12位的唯一ID
sub_7A1FB0函数主要检测确保内存中PE文件的MZSignature(MZ)
和NtHeaderSignature(PE00)
被抹除
接下来分析主要的核心函数,通过Hash获取VirtualFree函数释放了不需要的两处内存
SendRecvDataC2Server_61F89060函数就是主要的加密解密和收发数据到C2服务器
GetAllNeedFunAddressbyHash_61F83A30获取了全部需要用到的函数地址,首先获取了kernel32.dll中需要的函数(由于篇幅限制只截取了部分获取dll导出函数的截图)
获取ntdll.dll中需要用到的函数
除了获取kelnel32.dll和kernelbase.dll和ntdll.dll基址使用了通过PEB加Hash方式,获取其它DLL都使用RC4密钥bYXJm/3#M?:XyMBF
解密dll字符串并使用LoadLibraryA加载
获取通过syscall方式调用的ntdll.dll的导出函数syscall id
最后获取了wininet.dll中的导出函数
接下来DecryptBase64ReqHeader_61F88DC0函数主要解密了base64参数
initDecBox9_61F8B100函数初始化了Brute Ratel C4自定义加密算法需要用的9个box[256],初始化方法将box中每位元素减1
此为其中一个box
base64解码
使用了CryptStringToBinaryA函数解码base64,可以看到解码后的数据还是乱码
之后使用rc4算法和硬编码的密钥bYXJm/3#M?:XyMBF
解密后的数据可以看到有C2的ip地址User-Agent以及一些配置信息
对于解密的User-AgentMozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36
和Brute Ratel C4官网的Documentation的Listener Profile配置教程中User-Agent完全一致,可以判断此样本就是Brute Ratel C4生成的payload
base64解码配置数据头的eyJjb29raWUiOiI=
base64解码In0=
接下来GetSysInfo_61F99FC0函数主要是调用函数获取了用户名进程PID系统版本和位数等等信息
获取的当前进程的绝对路径
并使用base64编码
获取完数据初始化winsocket版本为2.2
函数EncryptDataAndConnectC2_61F8C760首先将将获取到的的系统信息格式化
格式化后的数据
这里使用了Brute Ratel C4自定义的加密算法密钥2Q73HI7Q0OD5BRN7
加密了数据
关于Brute Ratel C4自定义的加密算法官网有进行说明,此加密用于加密和BRc4服务器之间的网络数据,这层加密是在ssl层之下
对于Brute Ratel C4的自定义加密算法其实就是一个比较复杂的xor加密,这里使用我逆向还原的c++代码,关于完整的加密解密算法源码感兴趣的可以去我的github,首先InitXorKeyBox函数根据key2Q73HI7Q0OD5BRN7
生成160字节xorkey,函数首先获取key的后4字节传入replaceFourByteKey函数
replaceFourByteKey函数首先将4字节的key进行ror 8,然后以第1个字节数据当作g_Box1的数组下标取出值异或g_Box2[g_replaceIndex],g_replaceIndex默认值为1每次调用replaceFourByteKey函数值+1,这是第1个字节的处理方式
2到4字节处理方式是直接将数据当作g_Box1的数组下标取出值直接进行替换
replaceFourByteKey函数处理完的4字节数据会和key的前4字节异或生成第二组xorkey的前4字节
第二组xorkey后面每4字节都是通过前4字节的key异或前一组xorkey的后4字节,这里33 ^ 48
生成的就是第二组xorkey的第5个字节,剩下的以此类推
通过以上流程一共176字节11组xorkey生成完毕
接下来这是EncryptData函数的主要逻辑也就是全部的加密过程,每次加密16字节的明文数据
首先调用MyXor函数使用第一组xorkey逐字节和明文数据进行异或
接着replaceBoxData函数将数据作为g_Box1的数组下标进行替换
接着调用ByteOutOfOrder函数将数据乱序
boxXorData函数一共是处理了16字节,我这里就贴了前4个字节的图,因为加密方式都相同主要就是数据作为g_box3和g_box4数组下标取出的值进行异或
再次调用MyXor函数对数据进行异或,本次使用的就是第二组xorkey,这里循环9次前面的3个函数流程相同,MyXor函数一共使用9组xorkey进行异或加密
最后一层加密除了没有调用boxXorData函数其他的调用顺序和9次循环加密里的逻辑相同,至此16字节的明文数据加密完毕,开始加密下16字节的明文数据
通过以上加密算法加密后的数据进行base64编码
使用InternetOpenW函数设置User-Agent为Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36
InternetConnectW函数设置C2的ip地址174.129.157.251
和端口443
HttpOpenRequestW函数涉资请求类型为POST并且请求路径为/content.html
InternetSetOptionW设置Internet选项为INTERNET_OPTION_SECURITY_FLAGS
HttpAddRequestHeadersW函数参数HTTP_ADDREQ_FLAG_ADD
向C2发送POST请求
COFFLoader(BOFs)功能部分代码
C-Sharp内存加载部分代码
由此分析对比官网介绍的C-Sharp、BOFs内存加载功能
ETW patch功能使用的方案是直接将EtwEventWrite函数头改写C3(ret)让函数直接返回,由于没有动态调试我通过自己写的ror13Hash小工具确认函数Hash
AMSI patch的方案是将AmsiScanBuffer函数头改写为B8 57 00 07 80 C3
(mov eax,0x80070057;ret)
我们反汇编amsi.dll可知如果AmsiScanBuffer函数判断传入参数错误的话会跳过扫描分支过程直接将返回值0x80070057存放eax并返回,所以这里的patch方案也是直接将0x80070057存放eax并直接ret返回
5.总结
Brute Ratel C4是一款非常优秀的商业红队C2框架,使用了众多用于规避和检测EDR的技术,在最新版的BRc4 v1.1(Stoffel's Escape)的更新日志中作者宣布已经对核心程序进行了重写以隐藏内存的的几处痕迹,并且不再更新试用许可证,新申请试用许可证只会获得 v1.0.x 版本的最新更新。作者称此举是为了防止试用用户将最新的payloads上传到Virus Total,可见BRc4作者为了对抗EDR厂商获取最新的payloads分析以进行针对性查杀的努力,BRc4作者的总结很到位这是一场EDR和BRc4之间的猫捉老鼠游戏。