Brute Ratel C4 1.2.2版本被破解并公开泄露到互联网上,Brute Ratel C4的开发者NinjaParanoid在Twitter上发文称是MdSec将Brc4上传到VT(VirusTotal)然后被俄罗斯的Molecules组织破解导致Brc4 1.2.2 Scandinavian Defense在互联网上流传,并且Brc4 1.2.5泄露版在少部分人中流传。
Brute Ratel C4的开发者NinjaParanoid在discord群组中追踪了Brc4泄露的经过。
MdSec是一家来自于英国的安全公司,主要业务是渗透测试对手模拟,MdSec开发了Nighthawk C2用于售卖,毫无疑问MdSec的Nighthawk和Brute Ratel C4属于竞品关系。
可以看到介绍新的Nighthawk Licensing费用为每位用户每年为7500英镑或者10000美元,而且必须至少购买3个用户许可证。
名称: bruteratel-1.2.2-pwn3rzs-cyberarsenal.7z
大小: 82818265 字节 (78.9 MiB)
MD5:756bf6d0e21d9e8247a08352cd38dffd
SHA1: 10ae61b605a51a71dc87abb9c28d1e2567d9b32e
SHA256: d5b0c42ef9642dce715b252a07fc07ad9917bfdc13bd699d517b78210cc6ec60
泄露的Brc4 1.2.2版本目录结构如下:
bruteratel团队服务器端有x64和arm64两个版本,我们使用file命令查看服务端通过Go BuildId可以判断是使用golang编写
commander-runme是客户端图形化界面,commander-runme是指向/lib64/commander的快捷方式。
commander是使用Qt编写的图形界面客户端。
启动团队服务端-a指定用户名,-p指定密码,-h指定服务端口,-sc指定cert.pem证书,-sk指定key.pem私钥
之后我们启动客户端连接,Brc4只有HTTP Listener和DOH Listener两种Listener
这里我们创建一个HTTP Listener,这里我本地测试直接使用了ip地址,端口和User-Agent使用默认的配置,添加了3个URl,Sleep Obfuscation使用了默认的APC方式,睡眠时间默认,Common Auth认证密码我这里选择了随机生成,如果选择One Time Auth认证密码上线一次过后当前Auth认证密码将会被移除也就是只支持上线一次,如果勾选Die if C2 is inaccessible选项如果连接C2失败将会自动退出。
我们打开Payload Profiler选项Add Payload Profile,还可以添加TCP和SMB的Payload,这两种Payload并不可以直接单独上线和bruteratel服务器端通信而是需要通过已上线的badger进行转发到bruteratel服务器,使用这两种Payload场景一般在同一个域中进行内网横向移动时使用。
要使用TCP或者SMB的Payload需要域内有一个已经通过HTTP或者DOH上线的badger主机,然后使用pivot_tcp命令在上线的badger主机上创建tcp端口监听配合TCP的Payload连接,使用pivot_smb命令通过命名管道连接到SMB的Payload,在TCP或者SMB的badger主机执行任何命令都会发送至已上线的HTTP或者DOH的badger主机转发到bruteratel团队服务器,我们可以看到b-21是SMB的badger而b-22是TCP的badger,这2种badger都是通过b-20的HTTP badger进行转发的。
接下来我们生成payload,这里我将首先分析x64架构的Default下的Shellcode RtlExitUserThread
接下来我们使用如下的shellcode load代码加载shellcode进行分析
DWORD dwOldProtect = 0;
OVERLAPPED ol = { 0 };
HANDLE hFile = CreateFileW(L"shellcode.bin", GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
int fileSize = GetFileSize(hFile, NULL);
LPVOID lpShellCode = VirtualAlloc(NULL, fileSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
ReadFileEx(hFile, lpShellCode, fileSize, &ol, NULL);
CloseHandle(hFile);
VirtualProtect(lpShellCode, fileSize, PAGE_EXECUTE_READ, &dwOldProtect);
((void(*)())lpShellCode)();
WaitForSingleObject((HANDLE)-1, -1);
使用ida分析,这是shellcode起始的特征,在保存了寄存器环境后,通过大量的mov reg, imm
,push reg
组合在栈上初始化了大量数据。
首先在栈上初始化了0x12C字节的base64编码的Brc4配置文件,配置文件大小根据Listeners的配置而变化
接着初始化在栈上的0x39410字节的加密数据
接下来会将栈上的加密数据和base64配置文件数据都复制到申请的堆空间上
MemMoveAllocHeap函数的主要逻辑获取ntdll基址后通过ror13hash获取RtlAllocateHeap函数地址,申请堆空间将数据复制到堆空间。
函数GetNdllBaseAddress,获取_PEB_LDR_DATA结构地址后暴力搜索到MZSignature标志,然后对比如果AddressOfNewExeHeader-0x40小于0x3BF并且NtSignature标志为PE就找到ntdll.dll基址了。
通过ror13hash获取RtlAllocateHeap函数地址并申请堆空间
将0x39410字节的加密数据复制到堆空间
配置文件数据也同样复制到堆空间
接下来进入主函数
通过PEB+0xBC地址的NtGlobalFlang字段进行反调试,如果进程是由调试器创建的话将会设置(FLG_HEAP_ENABLE_TAIL_CHECK (0x10) | FLG_HEAP_ENABLE_FREE_CHECK (0x20) |
FLG_HEAP_VALIDATE_PARAMETERS (0x40))标志也就是0x70,调式器附加并不会设置此标志。
获取到ntdll基址后通过ror13hash获取RtlFreeHeap函数地址,并且获取了ZwProtectVirtualMemory函数syscall id和syscall地址。
CheckinlineHookAndGetSyscallId函数检测会检测函数头部字节是否为0xCC(int3软件断点),还会检测函数第1个和第4个字节是否为0xE9(jmp)检测函数是否被inlineHook,如果当前函数检测到inlineHook还会继续检测上一个Ntxxxx函数。
如果检测通过会通过对比opcode特征获取syscall id,Windows的syscall id需要使用一个WORD类型(2字节)存储所以这里要将第5第6字节合为一个WORD类型(2字节)。
我们查看NT10 (Windows 10/11)的syscall id table,可以看到同一个函数在不同的系统版本号下syscall id可能会发生变化,并且不同函数的syscall id占用空间大小不同所以需要一个WORD(2字节)进行存储。
搜索Ntxxxx函数的opcode特征0x0F05(syscall)以及0xC3(ret)特征获取获取syscall地址
接着获取了LdrGetDllHandleEx和LdrGetProcedureAddress函数地址
GetDllAddrOrGetDllFullPathByHash函数根据参数3传入的标志判断获取dll基址还是获取UNICODE_STRING FullDllName字段返回
根据参数3来判断,如果参数3为0则返回dll基址,如果参数3为1则返回UNICODE_STRING FullDllName字段
接着调用LdrGetDllHandleEx函数加载Kernel32.dll并获取基址,然后获取ZwFlushInstructionCache函数地址syscall id和syscall地址
接下来会调用Rc4Decrypt函数使用rc4算法解密0x39410字节大小的加密数据
Rc4Decrypt函数首先通过ror13hash获取了Exit Method函数RtlExitUserThread,GetProcAddress、LoadLibraryA的函数地址
rc4的密钥在加密数据的尾部的8字节也就是*@$/}lu}
,此rc4密钥为随机生成
我们打开CyberChef使用rc4密钥*@$/}lu}
解密从内存中dump下来的加密数据后发现为一个被抹掉MZSignature标志的的PE文件
将dump出来的PE文件使用Exeinfo查看是一个使用Mingw编译的x64 dll
我们查看x64dbg中解密后内存中的数据和我们使用CyberChef解密的相同
在rc4解密后PE文件尾部的8字节密钥被加密,此时尾部16字节的头8字节为被解密的Brc4配置文件的rc4密钥
我们使用rc4密钥f?zi\)*<
解密Base64编码的Brc4的配置文件后,可以看到解密的Brc4配置文件内容
接下来调用ReflectiveDLLInjection函数通过反射式注入调用解密的badger core dll
将badger shellcode load的虚拟内存首地址0x4D0000内存权限改为PAGE_READWRITE
使用processhacker工具查看内存我们看到0x4D0000虚拟内存权限已经被改为RW权限了
将堆中的badger core dll前0x400字节复制到0x4D0000地址的RW权限内存中,这里开始复用了我们shellcode load申请的虚拟内存用于反射式注入badger core dll
将badger core dll所有section复制到RW内存
填充导入表到RW内存
修复重定位数据
将Base64编码的配置文件的rc4密钥复制到申请的堆空间
修改各个区段的内存权限
释放用于存放解密后的badger core dll的堆内存
调用ZwFlushInstructionCache刷新代码缓存
然后通过call rax调用badger core dll main
badger core dll的main函数处,调用FreeConsole函数隐藏控制台窗口,初始化了一些用于存储dll基址的全局变量
GetSomeDllFunAddress函数获取了ntdll.dll、kernel32.dll、kernelbase.dll、advapi32.dll、crypt32.dll、ws2_32.dll中需要用到的函数地址。
通过ror13hash获取ntdll.dll中的函数地址
GetWs2_32DllFunAddress函数中使用rc4密钥bYXJm/3#M?:XyMBF
解密ws2_32.dll字符串通过GetDllBaseAddress函数获取dll基址并通过ror13hash获取函数地址。
GetDllBaseAddress函数会先调用GetDllAddrOrFullPathByHash函数获取dll基址,如果获取失败将会调用RtlRegisterWait函数通过线程池的工作线程执行LoadLibraryA函数,将参数3的CALLBACKFUNC设置为LoadLibraryA函数地址,参数4设置为要加载的对应dll字符串,之后通过WaitForSingleObject函数等待事件对象执行,当LoadLibraryA函数将对应dll加载到当前进程内存再调用GetDllAddrOrFullPathByHash函数获取加载到内存中的对应dll基址。
使用RtlRegisterWait函数加载dll是通过线程池的工作线程执行LoadLibraryA函数所以可以达到隐藏调用堆栈的效果,可以防止EDR/AV通过回溯LoadLibraryA调用栈的方式来判断是否为恶意软件调用。
之后通过SyscallZwCreateThreadEx函数创建线程,SyscallNtGetContextThread函数获取线程CONTEXT,SyscallNtSetContextThread函数设置线程执行函数为ThreadMain函数地址,SyscallNtResumeThread函数恢复线程执行。
接下来我们看ThreadMain函数函数
首先调用了initKeyAndFormatInfo函数
initBrc4EncryptAlgorithmArray函数初始化了Brc4的自定义加密算法使用到的9个数组,每个数组共有256个元素,初始化方式就是将数组内所有元素减1,关于Brc4的自定义加密算法可以去我的github查看我逆向还原的算法。
我们查看未初始化的ArrayBox1
初始化完毕的ArrayBox1每位元素减1
接下来初始化了一些格式化字符用于在commander界面中显示上线的badger基本信息
解码Base64后使用rc4密钥f?zi\)*<
解密Brc4配置文件
配置文件是通过|
符号(0x7C)进行分隔,所以会通0x7C判断并逐字段解析配置文件
解析完之后个别字段会调用AsciiToHexadecimal函数将解析的配置文件字段从Ascii转为十六进制
接下来GetSystemInfo函数获取当前系统基本信息供上线包使用
WSAStartup函数初始化WinSock版本2.2
接着查询了0x511000内存地址的属性
我们查看现在的内存,0x511000这块虚拟内存权限为RX,这块内存是之前用于运行反射式注入badger core dll的代码。
将0x511000内存属性更改为PAGE_READWRITE并将内存清零
我们查看下现在的内存结构0x511000内存属性被更改为PAGE_READWRITE并且内存被清零
接下来会先发送上线包到C2
首先通过ror13 hash方式获取Wininet.dll的导出函数地址
格式化上线包
使用Brc4自定义的加密算法加密上线包
加密完后使用Base64进行编码上线包
接着会将加密后的上线包发送给C2
首先设置User-Agent
设置域名以及端口
设置请求方式和请求路径
发送POST请求
查询C2任务队列是否有任务
任务队列存在任务则读取C2服务器返回数据
base64解码
使用Brc4自定义加密算法解密,这里返回的数据用于心跳包,b-xx为当前Listeners上线的badger数量从b-0开始累计,\\
后面的为b-cookie,这两个数据用于确定当前上线badger的唯一性
之后调用initCallbackFun函数初始化Brc4的内置后利用命令函数调用表,使用2个数组按分别按照顺序一一对应存储函数调用地址和函数调用ID
这是Brc4内置的后利用命令和对应的函数调用ID,除了help、cls、title这3个用于C2 Commander图形化界面的命令外,一共有131个命令用于和Badger交互,有2个命令的函数调用ID是相同的
调用ID | 命令 |
---|---|
0x3C9F | pwd |
0xD53F | arp |
0x4FFE | userinfo |
0x391 | lockws |
0x609 | lsdr |
0xA01 | uptime |
0xB06 | idletime |
0x703 | exit_process |
0x605 | revtoken |
0x105 | dumpclip |
0xC144 | drivers |
0x2919 | list_downloads |
0xA217 | get_parent |
0x1719 | set_debug |
0x4318 | tasks |
0x5921 | get_child |
0x6154 | psclean |
0x9C41 | screenshot |
0xBA9D | list_tcppivot |
0xA63C | clear_parent |
0x3BA8 | clear_child |
0x71C6 | get_argument |
0x93D6 | clear_argument |
0xE3CB | dcenum |
0x8289 | get_malloc |
0x8146 | get_threadex |
0xF616 | ipstats |
0x4A9E | dll_block |
0xB3E4 | dll_unblock |
0x3793 | get_wmiconfig |
0x2698 | reset_wmiconfig |
0x44B7 | token_vault |
0xED33 | vault_clear |
0x803 | exit_thread |
0x4395 | get_killdate |
0x4934 | shadowcloak |
0xB339 | netstat |
0xD41A | routes |
0xBE9A | local_sessions |
0x38B7 | dnscache |
0xB6A3 | getenv |
0x5248 | sysinfo |
0x6135 | windowlist |
0x73E8 | applist |
0xD9A3 | crisis_monitor |
0xD359 | socks_start、socks_profile_start |
0xD959 | socks_stop |
0x2DA1 | keylogger |
0x2129 | sleep |
0x1139 | cd |
0xA905 | cp |
0x9B84 | mv |
0xE993 | rm |
0x3F61 | mkdir |
0x8F40 | rmdir |
0xA32 | ls |
0xA959 | net |
0xF584 | runas |
0xF999 | make_token |
0xE9B0 | run |
0xEBC0 | kill |
0xBED0 | shellspawn |
0x9DE0 | ps |
0x8AFA | set_parent |
0x6BAE | get_system |
0x6F39 | system_exec |
0xF3D9 | psreflect |
0x3FD4 | loadr、mimikatz |
0x2C74 | download |
0x6C36 | reg |
0xC929 | set_child |
0xB458 | scquery |
0xE2EA | psimport |
0x13A1 | upload |
0x699A | pivot_tcp |
0x73E6 | set_argument |
0x3C4D | pivot_smb |
0xFE37 | psexec |
0x97E9 | sccreate |
0xFA73 | scdelete |
0x3B3E | scdivert |
0x5962 | set_malloc |
0x5761 | set_threadex |
0xC662 | psgrep |
0xE591 | portscan |
0x9881 | dcsync |
0x4953 | netshares |
0x4355 | set_wmiconfig |
0x5213 | wmiquery |
0x81E7 | grab_token |
0xF856 | impersonate |
0xCB46 | vault_remove |
0x4932 | coffexec |
0x6492 | list_modules |
0x2133 | memhunt |
0x7348 | suspended_run |
0x8491 | set_killdate |
0x8044 | sharpinline |
0x3456 | scstart |
0xB98E | query_session |
0x7579 | sentinel |
0xB99A | passpol |
0xB69A | schtquery |
0x29B3 | sharescan |
0xE4A9 | shinject_ex |
0xD8F3 | ps_ex |
0xB6BF | switch_profile |
0xB3A9 | timeloop |
0xE19A | preview |
0xA657 | lookup |
0xA5F1 | memdump |
0xD163 | addpriv |
0xE53A | fileinfo |
0xB1D3 | wmiexec |
0xF83E | lstree |
0xE4B9 | kerberoast |
0xB93A | icmp_ping |
0xDA9C | phish_creds |
0x34AE | start_address |
0xCDE4 | threads |
0xE1BA | phantom_thread |
0xF2ED | stop_task |
0xD2E5 | obfsleep |
0x4A83 | socks_profile |
0x3BD8 | memhook |
0xA23B | samdump |
0xE3D2 | sharpreflect |
0xA7D9 | set_coffargs |
0xD8A9 | clear_coffargs |
在C2 Commander图形化界面输入help命令可以获得所有内置命令,但是并没有0xF83E对应的命令,通过逆向brute-ratel-linx64也就是C2的TeamServer端发现对应的命令为lstree,此命令可能还在开发中并不能使用
通过C2返回的唯一ID数据格式化心跳包
使用Brc4自定义加密算法加密心跳包
使用base64编码
发送心跳包到C2
之后进入睡眠混淆函数首先使用SystemFunction036函数随机生成16字节伪随机数用于加密堆空间数据的rc4密钥
Rc4CryptDecryptHeapData函数使用随机生成的rc4密钥将所有使用的堆空间数据加密
根据配置文件选择的睡眠混淆方式选择对应的睡眠混淆函数,我们配置的是APC方式
创建纤程执行睡眠混淆函数
切换到纤程执行
首先为ROP链分配CONTEXT结构体(0x4D0)大小的堆空间,并且使用SystemFunction036生成16字节的伪随机数用于加密睡眠中badger core dll内存的rc4密钥
接着调用SetProcessValidCallTargets函数关闭以下几个ROP链用到的函数的CFG(Control Flow Guard)保护,CFG保护在Windows 8.1 UPDATE (KB3000850)补丁后开始包含,可以防止在程序中间接执行任意代码,在VS编译器中开启/guard:cf标志可以启用
在自己编写的load程序中一般在编译中是不会开启CFG保护的,但是考虑到一般要把shellcode注入到系统进程空间运行,而系统进程编译时基本都是开启了CFG保护的,所以为了兼容性考虑所以要把ROP链中用到的函数的CFG保护关闭。
创建一个Event对象,创建了一个挂起的线程并将函数TpReleaseCleanupGroupMembers+0x450地址设置为线程起始地址,填充ROP链CONTEXT结构的ContextFlags为CONTEXT_FULL(0x10000B)
获取创建的挂起线程的CONTEXT并复制到ROP链的CONTEXT
然后使用NtWaitForWorkViaWorkerFactory、BaseThreadInitThunk、RtlUserThreadStart函数构造了一个虚假线程调用堆栈上下文
接着开始填充ROP链函数ZwWaitForSingleObject、ZwProtectVirtualMemory、SystemFunction032、ZwGetContextThread的CONTEXT结构上下文,ZwTestAlert函数用于立即执行线程APC队列中挂起的APC回调
填充ROP链函数ZwSetContextThread、WaitForSingleObjectEx、SystemFunction032、ZwGetContextThread的CONTEXT结构上下文
使用NtQueueApcThread函数创建APC队列,将ZwContinue函数作为回调函数用于执行插入APC队列中的ROP链CONTEXT,使用ZwAlertResumeThread函数恢复线程执行,然后用rc4算法加密了最后一块堆内存,NtSignalAndWaitForSingleObject函数通过通知事件在保持不可警报的同时等待当前进程
ZwContinue函数的原型如下,参数一为CONTEXT结构体指针
ROP链开始执行此时RIP为ZwContinue函数,RCX为ZwWaitForSingleObject函数CONTEXT结构,ZwWaitForSingleObject等待当前进程对象作为不可警报。
NtProtectVirtualMemory将当前的badger core dll内存权限修改为PAGE_READWRITE
SystemFunction032通过rc4算法使用之前随机生成的16个字节伪随机数加密badger core dll内存
ZwGetContextThread获取当前执行ROP链线程的CONTEXT结构上下文
ZwSetContextThread设置虚假调用堆栈到ROP链线程
WaitForSingleObjectEx通过配置文件设置的睡眠时间进行睡眠等待
查看睡眠中的badger core dll,内存权限被改为RW并且全部被加密
查看欺骗线程调用堆栈都有一个固定的函数偏移,RtlUserThreadStart+0x21、BaseThreadInitThunk+0x14、TpReleaseCleanupGroupMembers+0x747、ZwWaitForWorkViaWorkerFactory+0x14
睡眠完成后之后调用了SystemFunction032函数通过rc4算法解密badger core dll内存
NtProtectVirtualMemory函数将badger core dll的内存权限改为PAGE_EXECUTE_READ
ZwSetContextThread恢复ROP链线程的CONTEXT结构上下文
RtlExitUserThread退出当前线程结束ROP链
结束后释放ROP链的堆空间并关闭句柄然后使用SwitchToFiber函数切换Fiber
Brc4支持3种睡眠混淆方式,我们使用obfsleep命令可以切换睡眠混淆方式,obfsleep 0就是刚刚分析的APC方式,obfslep 1和obfsleep 2对应Poling-0和Poling-1,接下来我们分析Poling方式的睡眠混淆,首先还是为ROP链分配CONTEXT结构体(0x4D0)字节大小的堆空间,并且使用SystemFunction036函数生成16字节的伪随机数用于加密睡眠中badger core dll内存的rc4密钥。
然后关闭ROP链用到的函数的CFG保护,并且创建一个Event对象,这里通过IsPoling标志判断了如果我们使用obfslep 1则会使用RtlCreateTimer函数,如果obfsleep 2则使用RtlRegisterWait函数,回调函数RtlCaptureContext用于获取RtlCreateTimer或RtlRegisterWait函数CONTEXT上下文结构。
然后将RtlCaptureContext回调获取的CONTEXT结构上下文复制到ROP链的CONTEXT
通过NtWaitForWorkViaWorkerFactory、BaseThreadInitThunk、RtlUserThreadStart函数构造了一个虚假线程调用堆栈CONTEXT结构上下文
然后填充ROP链函数VirtualProtect、SystemFunction032、ZwGetContextThread、ZwSetContextThread、WaitForSingleObject的CONTEXT结构的上下文结构,RSP-8用于调整RtlCaptureContext回调函数调用时造成的的偏移量。
填充ROP链函数ZwSetContextThread、SystemFunction032、VirtualProtect、NtSetEvent的CONTEXT结构上下文。
通过IsPooling标志判断选择RtlCreateTimer函数创建计时器队列或RtlRegisterWait函数注册等待句柄以创建ROP链,ZwContinue作为回调函数执行ROP链的CONTEXT,参数5用于调整ROP链调用之间的时间间隔,然后rc4算法加密最后一块堆空间数据,通过WaitForSingleObject等待Event对象。
VirtualProtect函数将badger core dll内存权限改为PAGE_READWRITE权限
SystemFunction032通过rc4算法使用之前随机生成的16个字节伪随机数加密badger core dll内存
ZwGetContextThread获取当前执行ROP链线程的CONTEXT结构上下文
ZwSetContextThread设置虚假调用线程堆栈到ROP链线程
通过WaitForSingleObject通过配置文件设置的睡眠时间进行睡眠等待
ZwSetContextThread恢复执行当前ROP链线程的CONTEXT结构上下文
SystemFunction032函数使用rc4算法解密badger core dll内存
VirtualProtect函数将badger core dll的内存权限改为PAGE_EXECUTE_READ
ZwSetEvent函数设置Event对象状态为Signaled
结束后释放ROP链内存,关闭句柄,切换回Fiber
接下来分析Brc4的后利用命令调用,我们使用mkdir testdir命令测试,在发送完心跳包到C2后会查询任务队列是否有任务,如果有则任务则解析,首先第一层为base64编码,第二层然后经过Brc4自定义加密算法解密,第三层还是base64编码解码后就为实际的命令调用数据
我们查看解密后的数据,0x3f61为mkdir命令的函数调用ID,0x20为分隔符,testdir是我们mkdir命令的参数
接下来将函数调用ID和参数分别复制到堆空间中
创建任务线程执行命令
进入TastThread将调用CheckCurrentCallbackFunid函数检查当前函数调用ID是否在当前函数调用ID表下标
函数调用ID占2字节,所以分别比对当前函数调用ID和当前下标的函数调用表ID如果2个字节都相同则函数返回TRUE对比成功
如果当前函数调用ID不在当前函数调用ID表下标则会将下标加1,并将函数调用ID表数组地址加3,循环比对函数调用ID表中下一个函数调用ID
如果ID比对成功则进行调用,通过call qword ptr [rax+rsi*8]
进行调用,rax为函数调用地址表数组中的首地址,rsi为我们当前函数调用ID在函数调用ID表中的下标
进入mkdir函数,使用CreateDirectoryA函数创建testdir文件
如果文件夹创建成功则格式化返回信息字符串
将格式化完成的返回消息进行base64编码然后加密等待将数据发送给C2
我们查看COMMAND界面返回的信息
上小节我们分析了Default下的Exit Method:RtlExitUserThread的shellcode以及badger core dll payload,接下来我们分析Stealth下的Exit Method:RtlExitUserThread的shellcode,我们主要分析Stealth的不同点和Default相同的代码就不再分析,Stealth相比于Default的shellcode主要增加了FixDllMemoryHook函数
获取kernelbase.dll的UNICODE_STRING FullDllName
获取ntdll.dll的UNICODE_STRING FullDllName
之后调用ReadDiskDllFixMemoryHook函数,首先将\??\
和FullDllName拼接
NtOpenFile函数打开dll文件句柄
打开文件成功则调用NtReadFile函数读取dll到申请的堆中
之后通过解析PE文件获得disk dll和memory dll的.text section地址,并将memory dll .text section内存权限改为PAGE_EXECUTE_READWRITE权限
将disk dll的.text section覆盖memory dll的.text section
将memory dll的.text section内存权限改为PAGE_EXECUTE_READ后调用GetDiskDllRdataSectionInfo函数
函数GetDiskDllRdataSectionInfo通过解析PE文件获取disk dll和memory dll的.rdata section地址
将memory dll .rdata section内存权限改为PAGE_READWRITE
将disk dll .rdata section覆盖memory dll .rdata section
之后分别对kernel32.dll和kernelbase.dll调用ReadDiskDllFixMemoryHook进行UnHook
之后调用ZwFlushInstructionCache刷新代码缓存
Stealth相比于Defualt的shellcode主要就是多了一个FixDllMemoryHook函数,主要就是读取ntdll.dll、kernel32.dll、kernelbase.dll在硬盘中的.text section和.rdata section覆盖内存中可能被AV或EDR等安全软件hook的.text section和.rdata section
使用ida分析stage shellcode起始的特征和badger shellcode的特征相同
在栈上初始化了0xC4大小的加密数据,尾部的8字节为rc4密钥
使用CyberChef进行解密发现为配置文件
stage和stealth的payload一样都有FixDllMemoryHook函数用于对ntdll.dll、kernel32.dll、kernelbase.dll进行Unhook
使用rc4算法密钥bYXJm/3#M?:XyMBF
解密wininet.dll字符串然后调用GetDllBaseAddressRtlRegisterWait函数加载dll获取基址
首先创建Event对象
RtlRegisterWait函数通过线程池的工作线程调用LoadLibraryA函数,函数参数3为LoadLibraryA函数地址,参数4为wininet.dll字符串
然后WaitForSingleObject等待Event对象
调用GetDllBaseAddressOrGetDllFullPath函数获取加载到内存中的winnet.dll基址
之后通过ror13hash获取winnet.dll中需要用到的函数地址
使用rc4算法密钥bYXJm/3#M?:XyMBF
解密crypt.dll字符串然后并加载获取dll基址
之后通过ror13hash获取CryptBinaryToStringA函数地址
使用rc4算法密钥>s?un&>)
解密配置文件和我们刚刚使用CyberChef解密的相同,此rc4密钥为随机生成
之后调用ParsingStageConfiguration函数逐个解析配置文件字段
解析完配置文件后调用HttpGetBadgerShellcode函数获取下一阶段的Badger Shellcode
通过配置文件auth密钥格式化认证包
使用rc4算法密钥>s?un&>)
加密认证包
使用base64编码加密后的认证包
调用InternetOpenA函数设置User-Agent
InternetConnectA设置域名和端口
HttpOpenRequestA设置请求方式和path
HttpAddRequestHeadersA将rc4算法加密密钥附加到HTTP请求头
HttpSendRequestA函数将指定的请求发送到C2服务器
InternetQueryDataAvailable函数查询C2服务器返回的数据大小
InternetReadFile读取C2服务器返回的数据
使用rc4算法密钥>s?un&>)
解密C2服务器返回的数据
使用ZwAllocateVirtualMemory函数申请PAGE_READWRITE权限的虚拟内存
将Badger Shellcode复制到新申请的虚拟内存中
调用ZwProtectVirtualMemory将Badger Shellcode内存权限改为PAGE_EXECUTE_READ
调用Badger Shellcode,由于此前已经分析过Badger Shellcode所以这里不在继续分析
使用BinDiff对比发现stage获取的shellcode为badger_x64_stealth_ret
使用Exeinfo查看badger_x64.dll可以看到是用MinGW-w64编译的x64 dll,并且有一个大的.data section
接下来分析dll main,主要就是通过ror13hash获取了ntdll基址然后获取NtProtectVirtualMemory、ZwAllocateVirtualMemroy、ZwWaitForSingleObject、ZwCreateThreadEx函数的syscall id和syacall地址
ZwAllocateVirtualMemory给shellcode申请PAGE_READWRITE权限的虚拟内存,将.data section的shellcode复制到申请的虚拟内存,然后将.data section的shellcode清零,然后将shellcode虚拟内存权限改为PAGE_EXECUTEREAD
我们可以看到.data section的Badger Shellcode没有进行任何加密
之后ZwCreateThreadEx创建线程执行Badger Shellcode,分析到这里可得出结论badger_x64.dll其实就是一个用于加载Badger Shellcode的加载器
使用BinDiff对比badger_x64.dll使用的shellcode为badger_x64_ret
使用Exeinfo查看badger_x64_svc.exe也使用MinGW-w64编译的64位程序并且也有一个大的.data section
这里获取ntdll.dll基址不是通过ror13hash而是直接通过暴力搜索的方式
接着通过ror13hash获取NtProtectVirtualMemory、ZwAllocateVirtualMemroy、ZwWaitForSingleObject、ZwCreateThreadEx的函数syscall id和syscall address
接下来还是相同的操作将.data section的Badger Shellcode复制到申请的虚拟内存,然后清空.data section的shellcode,将虚拟内存权限改为PAGE_EXECUTEREAD然后调用ZwCreateThreadEx创建线程执行
使用BinDiff对比发现badger_x64_svc.exe使用的shellcode也是badger_x64_ret
使用Exeinfo查看badger_x64_stealth_svc.exe是使用MinGW-w64编译的64位程序同样有大的.data section,根据此特征可知主要逻辑也是加载.data section的shellcode执行
badger_x64_stealth_svc的主要逻辑和badger_x64_svc相同不在阐述。
经过BinDiff对比badger_x64_stealth_svc使用的shellcode为badger_x64_stealth_ret
本篇文章详细分析了Brute Ratel C4 Scandinavian Defense 1.2.2泄露版HTTP Listener的x64架构的所有Badger Payload,截至发稿前Brc4已经更新到了1.4 Resurgence版本开发者对bruteratel团队服务器和shellcode核心进行了改进,比如删除了一直使用的ror13哈希算法,将配置文件的存储方式从base64编码改为二进制,删除了badger core dll中用于解密dll字符串的rc4密钥bYXJm/3#M?:XyMBF
,将badger core dll中使用的所有命令输出字符串删除将由bruteratel服务器直接进行格式化输出等等,由于篇幅原因下一篇文章将带来检测运行和睡眠中的badger core dll和bruteratel团队服务器。