freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

Brute Ratel C4 1.2.2 Badger Shellcode详细剖析
2023-02-04 17:50:44
所属地 浙江省

1.前言

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泄露版在少部分人中流传。
image

Brute Ratel C4的开发者NinjaParanoid在discord群组中追踪了Brc4泄露的经过。
image

MdSec是一家来自于英国的安全公司,主要业务是渗透测试对手模拟,MdSec开发了Nighthawk C2用于售卖,毫无疑问MdSec的Nighthawk和Brute Ratel C4属于竞品关系。
image

可以看到介绍新的Nighthawk Licensing费用为每位用户每年为7500英镑或者10000美元,而且必须至少购买3个用户许可证。
image

2.样本IOCs

名称: bruteratel-1.2.2-pwn3rzs-cyberarsenal.7z
大小: 82818265 字节 (78.9 MiB)
MD5:756bf6d0e21d9e8247a08352cd38dffd
SHA1: 10ae61b605a51a71dc87abb9c28d1e2567d9b32e
SHA256: d5b0c42ef9642dce715b252a07fc07ad9917bfdc13bd699d517b78210cc6ec60

3.恶意代码分析

泄露的Brc4 1.2.2版本目录结构如下:
image

bruteratel团队服务器端有x64和arm64两个版本,我们使用file命令查看服务端通过Go BuildId可以判断是使用golang编写
image

commander-runme是客户端图形化界面,commander-runme是指向/lib64/commander的快捷方式。
image

commander是使用Qt编写的图形界面客户端。
image

启动团队服务端-a指定用户名,-p指定密码,-h指定服务端口,-sc指定cert.pem证书,-sk指定key.pem私钥
image

之后我们启动客户端连接,Brc4只有HTTP Listener和DOH Listener两种Listener
image

这里我们创建一个HTTP Listener,这里我本地测试直接使用了ip地址,端口和User-Agent使用默认的配置,添加了3个URl,Sleep Obfuscation使用了默认的APC方式,睡眠时间默认,Common Auth认证密码我这里选择了随机生成,如果选择One Time Auth认证密码上线一次过后当前Auth认证密码将会被移除也就是只支持上线一次,如果勾选Die if C2 is inaccessible选项如果连接C2失败将会自动退出。
image

我们打开Payload Profiler选项Add Payload Profile,还可以添加TCP和SMB的Payload,这两种Payload并不可以直接单独上线和bruteratel服务器端通信而是需要通过已上线的badger进行转发到bruteratel服务器,使用这两种Payload场景一般在同一个域中进行内网横向移动时使用。
image

要使用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进行转发的。
image

接下来我们生成payload,这里我将首先分析x64架构的Default下的Shellcode RtlExitUserThread
image

3.1 badger_x64_rtl.bin

3.1.1 badger shellcode load

接下来我们使用如下的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, immpush reg组合在栈上初始化了大量数据。
image

首先在栈上初始化了0x12C字节的base64编码的Brc4配置文件,配置文件大小根据Listeners的配置而变化
image

接着初始化在栈上的0x39410字节的加密数据
image

接下来会将栈上的加密数据和base64配置文件数据都复制到申请的堆空间上
image

MemMoveAllocHeap函数的主要逻辑获取ntdll基址后通过ror13hash获取RtlAllocateHeap函数地址,申请堆空间将数据复制到堆空间。
image

函数GetNdllBaseAddress,获取_PEB_LDR_DATA结构地址后暴力搜索到MZSignature标志,然后对比如果AddressOfNewExeHeader-0x40小于0x3BF并且NtSignature标志为PE就找到ntdll.dll基址了。
image

通过ror13hash获取RtlAllocateHeap函数地址并申请堆空间
image

将0x39410字节的加密数据复制到堆空间
image

配置文件数据也同样复制到堆空间
image

接下来进入主函数
image

通过PEB+0xBC地址的NtGlobalFlang字段进行反调试,如果进程是由调试器创建的话将会设置(FLG_HEAP_ENABLE_TAIL_CHECK (0x10) | FLG_HEAP_ENABLE_FREE_CHECK (0x20) |
FLG_HEAP_VALIDATE_PARAMETERS (0x40))标志也就是0x70,调式器附加并不会设置此标志。
image

获取到ntdll基址后通过ror13hash获取RtlFreeHeap函数地址,并且获取了ZwProtectVirtualMemory函数syscall id和syscall地址。
image

CheckinlineHookAndGetSyscallId函数检测会检测函数头部字节是否为0xCC(int3软件断点),还会检测函数第1个和第4个字节是否为0xE9(jmp)检测函数是否被inlineHook,如果当前函数检测到inlineHook还会继续检测上一个Ntxxxx函数。
image

如果检测通过会通过对比opcode特征获取syscall id,Windows的syscall id需要使用一个WORD类型(2字节)存储所以这里要将第5第6字节合为一个WORD类型(2字节)。
image

我们查看NT10 (Windows 10/11)的syscall id table,可以看到同一个函数在不同的系统版本号下syscall id可能会发生变化,并且不同函数的syscall id占用空间大小不同所以需要一个WORD(2字节)进行存储。
image

搜索Ntxxxx函数的opcode特征0x0F05(syscall)以及0xC3(ret)特征获取获取syscall地址
image

接着获取了LdrGetDllHandleEx和LdrGetProcedureAddress函数地址
image

GetDllAddrOrGetDllFullPathByHash函数根据参数3传入的标志判断获取dll基址还是获取UNICODE_STRING FullDllName字段返回
image

根据参数3来判断,如果参数3为0则返回dll基址,如果参数3为1则返回UNICODE_STRING FullDllName字段
image

接着调用LdrGetDllHandleEx函数加载Kernel32.dll并获取基址,然后获取ZwFlushInstructionCache函数地址syscall id和syscall地址
image

接下来会调用Rc4Decrypt函数使用rc4算法解密0x39410字节大小的加密数据
image

Rc4Decrypt函数首先通过ror13hash获取了Exit Method函数RtlExitUserThread,GetProcAddress、LoadLibraryA的函数地址
image

rc4的密钥在加密数据的尾部的8字节也就是*@$/}lu},此rc4密钥为随机生成
image

我们打开CyberChef使用rc4密钥*@$/}lu}解密从内存中dump下来的加密数据后发现为一个被抹掉MZSignature标志的的PE文件
image

将dump出来的PE文件使用Exeinfo查看是一个使用Mingw编译的x64 dll
image

我们查看x64dbg中解密后内存中的数据和我们使用CyberChef解密的相同
image

在rc4解密后PE文件尾部的8字节密钥被加密,此时尾部16字节的头8字节为被解密的Brc4配置文件的rc4密钥
image

我们使用rc4密钥f?zi\)*<解密Base64编码的Brc4的配置文件后,可以看到解密的Brc4配置文件内容
image

接下来调用ReflectiveDLLInjection函数通过反射式注入调用解密的badger core dll
image

将badger shellcode load的虚拟内存首地址0x4D0000内存权限改为PAGE_READWRITE
image

使用processhacker工具查看内存我们看到0x4D0000虚拟内存权限已经被改为RW权限了
image

将堆中的badger core dll前0x400字节复制到0x4D0000地址的RW权限内存中,这里开始复用了我们shellcode load申请的虚拟内存用于反射式注入badger core dll
image

将badger core dll所有section复制到RW内存
image

填充导入表到RW内存
image

修复重定位数据
image

将Base64编码的配置文件的rc4密钥复制到申请的堆空间
image

修改各个区段的内存权限
image

释放用于存放解密后的badger core dll的堆内存
image

调用ZwFlushInstructionCache刷新代码缓存
image

然后通过call rax调用badger core dll main
image

3.1.2 badger core dll

badger core dll的main函数处,调用FreeConsole函数隐藏控制台窗口,初始化了一些用于存储dll基址的全局变量
image

GetSomeDllFunAddress函数获取了ntdll.dll、kernel32.dll、kernelbase.dll、advapi32.dll、crypt32.dll、ws2_32.dll中需要用到的函数地址。
image

通过ror13hash获取ntdll.dll中的函数地址
image

GetWs2_32DllFunAddress函数中使用rc4密钥bYXJm/3#M?:XyMBF解密ws2_32.dll字符串通过GetDllBaseAddress函数获取dll基址并通过ror13hash获取函数地址。
image

GetDllBaseAddress函数会先调用GetDllAddrOrFullPathByHash函数获取dll基址,如果获取失败将会调用RtlRegisterWait函数通过线程池的工作线程执行LoadLibraryA函数,将参数3的CALLBACKFUNC设置为LoadLibraryA函数地址,参数4设置为要加载的对应dll字符串,之后通过WaitForSingleObject函数等待事件对象执行,当LoadLibraryA函数将对应dll加载到当前进程内存再调用GetDllAddrOrFullPathByHash函数获取加载到内存中的对应dll基址。
image

使用RtlRegisterWait函数加载dll是通过线程池的工作线程执行LoadLibraryA函数所以可以达到隐藏调用堆栈的效果,可以防止EDR/AV通过回溯LoadLibraryA调用栈的方式来判断是否为恶意软件调用。
image

之后通过SyscallZwCreateThreadEx函数创建线程,SyscallNtGetContextThread函数获取线程CONTEXT,SyscallNtSetContextThread函数设置线程执行函数为ThreadMain函数地址,SyscallNtResumeThread函数恢复线程执行。
image

接下来我们看ThreadMain函数函数
image

首先调用了initKeyAndFormatInfo函数
image

initBrc4EncryptAlgorithmArray函数初始化了Brc4的自定义加密算法使用到的9个数组,每个数组共有256个元素,初始化方式就是将数组内所有元素减1,关于Brc4的自定义加密算法可以去我的github查看我逆向还原的算法。
image

我们查看未初始化的ArrayBox1
image

初始化完毕的ArrayBox1每位元素减1
image

接下来初始化了一些格式化字符用于在commander界面中显示上线的badger基本信息
image

解码Base64后使用rc4密钥f?zi\)*<解密Brc4配置文件
image

配置文件是通过|符号(0x7C)进行分隔,所以会通0x7C判断并逐字段解析配置文件
image

解析完之后个别字段会调用AsciiToHexadecimal函数将解析的配置文件字段从Ascii转为十六进制
image

接下来GetSystemInfo函数获取当前系统基本信息供上线包使用
image

WSAStartup函数初始化WinSock版本2.2
image

接着查询了0x511000内存地址的属性
image

我们查看现在的内存,0x511000这块虚拟内存权限为RX,这块内存是之前用于运行反射式注入badger core dll的代码。
image

将0x511000内存属性更改为PAGE_READWRITE并将内存清零
image

我们查看下现在的内存结构0x511000内存属性被更改为PAGE_READWRITE并且内存被清零
image

接下来会先发送上线包到C2
image

首先通过ror13 hash方式获取Wininet.dll的导出函数地址
image

格式化上线包
image

使用Brc4自定义的加密算法加密上线包
image

加密完后使用Base64进行编码上线包
image

接着会将加密后的上线包发送给C2
image

首先设置User-Agent
image

设置域名以及端口
image

设置请求方式和请求路径
image

发送POST请求
image

查询C2任务队列是否有任务
image

任务队列存在任务则读取C2服务器返回数据
image

base64解码
image

使用Brc4自定义加密算法解密,这里返回的数据用于心跳包,b-xx为当前Listeners上线的badger数量从b-0开始累计,\\后面的为b-cookie,这两个数据用于确定当前上线badger的唯一性
image

之后调用initCallbackFun函数初始化Brc4的内置后利用命令函数调用表,使用2个数组按分别按照顺序一一对应存储函数调用地址和函数调用ID
image

这是Brc4内置的后利用命令和对应的函数调用ID,除了help、cls、title这3个用于C2 Commander图形化界面的命令外,一共有131个命令用于和Badger交互,有2个命令的函数调用ID是相同的

调用ID命令
0x3C9Fpwd
0xD53Farp
0x4FFEuserinfo
0x391lockws
0x609lsdr
0xA01uptime
0xB06idletime
0x703exit_process
0x605revtoken
0x105dumpclip
0xC144drivers
0x2919list_downloads
0xA217get_parent
0x1719set_debug
0x4318tasks
0x5921get_child
0x6154psclean
0x9C41screenshot
0xBA9Dlist_tcppivot
0xA63Cclear_parent
0x3BA8clear_child
0x71C6get_argument
0x93D6clear_argument
0xE3CBdcenum
0x8289get_malloc
0x8146get_threadex
0xF616ipstats
0x4A9Edll_block
0xB3E4dll_unblock
0x3793get_wmiconfig
0x2698reset_wmiconfig
0x44B7token_vault
0xED33vault_clear
0x803exit_thread
0x4395get_killdate
0x4934shadowcloak
0xB339netstat
0xD41Aroutes
0xBE9Alocal_sessions
0x38B7dnscache
0xB6A3getenv
0x5248sysinfo
0x6135windowlist
0x73E8applist
0xD9A3crisis_monitor
0xD359socks_start、socks_profile_start
0xD959socks_stop
0x2DA1keylogger
0x2129sleep
0x1139cd
0xA905cp
0x9B84mv
0xE993rm
0x3F61mkdir
0x8F40rmdir
0xA32ls
0xA959net
0xF584runas
0xF999make_token
0xE9B0run
0xEBC0kill
0xBED0shellspawn
0x9DE0ps
0x8AFAset_parent
0x6BAEget_system
0x6F39system_exec
0xF3D9psreflect
0x3FD4loadr、mimikatz
0x2C74download
0x6C36reg
0xC929set_child
0xB458scquery
0xE2EApsimport
0x13A1upload
0x699Apivot_tcp
0x73E6set_argument
0x3C4Dpivot_smb
0xFE37psexec
0x97E9sccreate
0xFA73scdelete
0x3B3Escdivert
0x5962set_malloc
0x5761set_threadex
0xC662psgrep
0xE591portscan
0x9881dcsync
0x4953netshares
0x4355set_wmiconfig
0x5213wmiquery
0x81E7grab_token
0xF856impersonate
0xCB46vault_remove
0x4932coffexec
0x6492list_modules
0x2133memhunt
0x7348suspended_run
0x8491set_killdate
0x8044sharpinline
0x3456scstart
0xB98Equery_session
0x7579sentinel
0xB99Apasspol
0xB69Aschtquery
0x29B3sharescan
0xE4A9shinject_ex
0xD8F3ps_ex
0xB6BFswitch_profile
0xB3A9timeloop
0xE19Apreview
0xA657lookup
0xA5F1memdump
0xD163addpriv
0xE53Afileinfo
0xB1D3wmiexec
0xF83Elstree
0xE4B9kerberoast
0xB93Aicmp_ping
0xDA9Cphish_creds
0x34AEstart_address
0xCDE4threads
0xE1BAphantom_thread
0xF2EDstop_task
0xD2E5obfsleep
0x4A83socks_profile
0x3BD8memhook
0xA23Bsamdump
0xE3D2sharpreflect
0xA7D9set_coffargs
0xD8A9clear_coffargs

在C2 Commander图形化界面输入help命令可以获得所有内置命令,但是并没有0xF83E对应的命令,通过逆向brute-ratel-linx64也就是C2的TeamServer端发现对应的命令为lstree,此命令可能还在开发中并不能使用
image

通过C2返回的唯一ID数据格式化心跳包
image

使用Brc4自定义加密算法加密心跳包
image

使用base64编码
image

发送心跳包到C2
image

之后进入睡眠混淆函数首先使用SystemFunction036函数随机生成16字节伪随机数用于加密堆空间数据的rc4密钥
image

Rc4CryptDecryptHeapData函数使用随机生成的rc4密钥将所有使用的堆空间数据加密
image

根据配置文件选择的睡眠混淆方式选择对应的睡眠混淆函数,我们配置的是APC方式
image

创建纤程执行睡眠混淆函数
image

切换到纤程执行
image

首先为ROP链分配CONTEXT结构体(0x4D0)大小的堆空间,并且使用SystemFunction036生成16字节的伪随机数用于加密睡眠中badger core dll内存的rc4密钥
image

接着调用SetProcessValidCallTargets函数关闭以下几个ROP链用到的函数的CFG(Control Flow Guard)保护,CFG保护在Windows 8.1 UPDATE (KB3000850)补丁后开始包含,可以防止在程序中间接执行任意代码,在VS编译器中开启/guard:cf标志可以启用
image

在自己编写的load程序中一般在编译中是不会开启CFG保护的,但是考虑到一般要把shellcode注入到系统进程空间运行,而系统进程编译时基本都是开启了CFG保护的,所以为了兼容性考虑所以要把ROP链中用到的函数的CFG保护关闭。
image

创建一个Event对象,创建了一个挂起的线程并将函数TpReleaseCleanupGroupMembers+0x450地址设置为线程起始地址,填充ROP链CONTEXT结构的ContextFlags为CONTEXT_FULL(0x10000B)
image

获取创建的挂起线程的CONTEXT并复制到ROP链的CONTEXT
image

然后使用NtWaitForWorkViaWorkerFactory、BaseThreadInitThunk、RtlUserThreadStart函数构造了一个虚假线程调用堆栈上下文
image

接着开始填充ROP链函数ZwWaitForSingleObject、ZwProtectVirtualMemory、SystemFunction032、ZwGetContextThread的CONTEXT结构上下文,ZwTestAlert函数用于立即执行线程APC队列中挂起的APC回调
image

填充ROP链函数ZwSetContextThread、WaitForSingleObjectEx、SystemFunction032、ZwGetContextThread的CONTEXT结构上下文
image

使用NtQueueApcThread函数创建APC队列,将ZwContinue函数作为回调函数用于执行插入APC队列中的ROP链CONTEXT,使用ZwAlertResumeThread函数恢复线程执行,然后用rc4算法加密了最后一块堆内存,NtSignalAndWaitForSingleObject函数通过通知事件在保持不可警报的同时等待当前进程
image

ZwContinue函数的原型如下,参数一为CONTEXT结构体指针
image

ROP链开始执行此时RIP为ZwContinue函数,RCX为ZwWaitForSingleObject函数CONTEXT结构,ZwWaitForSingleObject等待当前进程对象作为不可警报。
image

NtProtectVirtualMemory将当前的badger core dll内存权限修改为PAGE_READWRITE
image

SystemFunction032通过rc4算法使用之前随机生成的16个字节伪随机数加密badger core dll内存
image

ZwGetContextThread获取当前执行ROP链线程的CONTEXT结构上下文
image

ZwSetContextThread设置虚假调用堆栈到ROP链线程
image

WaitForSingleObjectEx通过配置文件设置的睡眠时间进行睡眠等待
image

查看睡眠中的badger core dll,内存权限被改为RW并且全部被加密
image

查看欺骗线程调用堆栈都有一个固定的函数偏移,RtlUserThreadStart+0x21、BaseThreadInitThunk+0x14、TpReleaseCleanupGroupMembers+0x747、ZwWaitForWorkViaWorkerFactory+0x14
image

睡眠完成后之后调用了SystemFunction032函数通过rc4算法解密badger core dll内存
image

NtProtectVirtualMemory函数将badger core dll的内存权限改为PAGE_EXECUTE_READ
image

ZwSetContextThread恢复ROP链线程的CONTEXT结构上下文
image

RtlExitUserThread退出当前线程结束ROP链
image

结束后释放ROP链的堆空间并关闭句柄然后使用SwitchToFiber函数切换Fiber
image

Brc4支持3种睡眠混淆方式,我们使用obfsleep命令可以切换睡眠混淆方式,obfsleep 0就是刚刚分析的APC方式,obfslep 1和obfsleep 2对应Poling-0和Poling-1,接下来我们分析Poling方式的睡眠混淆,首先还是为ROP链分配CONTEXT结构体(0x4D0)字节大小的堆空间,并且使用SystemFunction036函数生成16字节的伪随机数用于加密睡眠中badger core dll内存的rc4密钥。
image

然后关闭ROP链用到的函数的CFG保护,并且创建一个Event对象,这里通过IsPoling标志判断了如果我们使用obfslep 1则会使用RtlCreateTimer函数,如果obfsleep 2则使用RtlRegisterWait函数,回调函数RtlCaptureContext用于获取RtlCreateTimer或RtlRegisterWait函数CONTEXT上下文结构。
image

然后将RtlCaptureContext回调获取的CONTEXT结构上下文复制到ROP链的CONTEXT
image

通过NtWaitForWorkViaWorkerFactory、BaseThreadInitThunk、RtlUserThreadStart函数构造了一个虚假线程调用堆栈CONTEXT结构上下文
image

然后填充ROP链函数VirtualProtect、SystemFunction032、ZwGetContextThread、ZwSetContextThread、WaitForSingleObject的CONTEXT结构的上下文结构,RSP-8用于调整RtlCaptureContext回调函数调用时造成的的偏移量。
image

填充ROP链函数ZwSetContextThread、SystemFunction032、VirtualProtect、NtSetEvent的CONTEXT结构上下文。
image

通过IsPooling标志判断选择RtlCreateTimer函数创建计时器队列或RtlRegisterWait函数注册等待句柄以创建ROP链,ZwContinue作为回调函数执行ROP链的CONTEXT,参数5用于调整ROP链调用之间的时间间隔,然后rc4算法加密最后一块堆空间数据,通过WaitForSingleObject等待Event对象。
image

VirtualProtect函数将badger core dll内存权限改为PAGE_READWRITE权限
image

SystemFunction032通过rc4算法使用之前随机生成的16个字节伪随机数加密badger core dll内存
image

ZwGetContextThread获取当前执行ROP链线程的CONTEXT结构上下文
image

ZwSetContextThread设置虚假调用线程堆栈到ROP链线程
image

通过WaitForSingleObject通过配置文件设置的睡眠时间进行睡眠等待
image

ZwSetContextThread恢复执行当前ROP链线程的CONTEXT结构上下文
image

SystemFunction032函数使用rc4算法解密badger core dll内存
image

VirtualProtect函数将badger core dll的内存权限改为PAGE_EXECUTE_READ
image

ZwSetEvent函数设置Event对象状态为Signaled
image

结束后释放ROP链内存,关闭句柄,切换回Fiber
image

接下来分析Brc4的后利用命令调用,我们使用mkdir testdir命令测试,在发送完心跳包到C2后会查询任务队列是否有任务,如果有则任务则解析,首先第一层为base64编码,第二层然后经过Brc4自定义加密算法解密,第三层还是base64编码解码后就为实际的命令调用数据
image

我们查看解密后的数据,0x3f61为mkdir命令的函数调用ID,0x20为分隔符,testdir是我们mkdir命令的参数
image

接下来将函数调用ID和参数分别复制到堆空间中
image

创建任务线程执行命令
image

进入TastThread将调用CheckCurrentCallbackFunid函数检查当前函数调用ID是否在当前函数调用ID表下标
image

函数调用ID占2字节,所以分别比对当前函数调用ID和当前下标的函数调用表ID如果2个字节都相同则函数返回TRUE对比成功
image

如果当前函数调用ID不在当前函数调用ID表下标则会将下标加1,并将函数调用ID表数组地址加3,循环比对函数调用ID表中下一个函数调用ID
image

如果ID比对成功则进行调用,通过call qword ptr [rax+rsi*8]进行调用,rax为函数调用地址表数组中的首地址,rsi为我们当前函数调用ID在函数调用ID表中的下标
image

进入mkdir函数,使用CreateDirectoryA函数创建testdir文件
image

如果文件夹创建成功则格式化返回信息字符串
image

将格式化完成的返回消息进行base64编码然后加密等待将数据发送给C2
image

我们查看COMMAND界面返回的信息
image

3.2 badger_x64_stealth_rtl.bin

上小节我们分析了Default下的Exit Method:RtlExitUserThread的shellcode以及badger core dll payload,接下来我们分析Stealth下的Exit Method:RtlExitUserThread的shellcode,我们主要分析Stealth的不同点和Default相同的代码就不再分析,Stealth相比于Default的shellcode主要增加了FixDllMemoryHook函数
image

获取kernelbase.dll的UNICODE_STRING FullDllName
image

获取ntdll.dll的UNICODE_STRING FullDllName
image

之后调用ReadDiskDllFixMemoryHook函数,首先将\??\和FullDllName拼接
image

NtOpenFile函数打开dll文件句柄
image

打开文件成功则调用NtReadFile函数读取dll到申请的堆中
image

之后通过解析PE文件获得disk dll和memory dll的.text section地址,并将memory dll .text section内存权限改为PAGE_EXECUTE_READWRITE权限
image

将disk dll的.text section覆盖memory dll的.text section
image

将memory dll的.text section内存权限改为PAGE_EXECUTE_READ后调用GetDiskDllRdataSectionInfo函数
image

函数GetDiskDllRdataSectionInfo通过解析PE文件获取disk dll和memory dll的.rdata section地址
image

将memory dll .rdata section内存权限改为PAGE_READWRITE
image

将disk dll .rdata section覆盖memory dll .rdata section
image

之后分别对kernel32.dll和kernelbase.dll调用ReadDiskDllFixMemoryHook进行UnHook
image

之后调用ZwFlushInstructionCache刷新代码缓存
image

Stealth相比于Defualt的shellcode主要就是多了一个FixDllMemoryHook函数,主要就是读取ntdll.dll、kernel32.dll、kernelbase.dll在硬盘中的.text section和.rdata section覆盖内存中可能被AV或EDR等安全软件hook的.text section和.rdata section
image

3.3 stage_x64_rtl.bin

使用ida分析stage shellcode起始的特征和badger shellcode的特征相同
image

在栈上初始化了0xC4大小的加密数据,尾部的8字节为rc4密钥
image

使用CyberChef进行解密发现为配置文件
image

stage和stealth的payload一样都有FixDllMemoryHook函数用于对ntdll.dll、kernel32.dll、kernelbase.dll进行Unhook
image

使用rc4算法密钥bYXJm/3#M?:XyMBF解密wininet.dll字符串然后调用GetDllBaseAddressRtlRegisterWait函数加载dll获取基址
image

首先创建Event对象
image

RtlRegisterWait函数通过线程池的工作线程调用LoadLibraryA函数,函数参数3为LoadLibraryA函数地址,参数4为wininet.dll字符串
image

然后WaitForSingleObject等待Event对象
image

调用GetDllBaseAddressOrGetDllFullPath函数获取加载到内存中的winnet.dll基址
image

之后通过ror13hash获取winnet.dll中需要用到的函数地址
image

使用rc4算法密钥bYXJm/3#M?:XyMBF解密crypt.dll字符串然后并加载获取dll基址
image

之后通过ror13hash获取CryptBinaryToStringA函数地址
image

使用rc4算法密钥>s?un&>)解密配置文件和我们刚刚使用CyberChef解密的相同,此rc4密钥为随机生成
image

之后调用ParsingStageConfiguration函数逐个解析配置文件字段
image

解析完配置文件后调用HttpGetBadgerShellcode函数获取下一阶段的Badger Shellcode
image

通过配置文件auth密钥格式化认证包
image

使用rc4算法密钥>s?un&>)加密认证包
image

使用base64编码加密后的认证包
image

调用InternetOpenA函数设置User-Agent
image

InternetConnectA设置域名和端口
image

HttpOpenRequestA设置请求方式和path
image

HttpAddRequestHeadersA将rc4算法加密密钥附加到HTTP请求头
image

HttpSendRequestA函数将指定的请求发送到C2服务器
image

InternetQueryDataAvailable函数查询C2服务器返回的数据大小
image

InternetReadFile读取C2服务器返回的数据
image

使用rc4算法密钥>s?un&>)解密C2服务器返回的数据
image

使用ZwAllocateVirtualMemory函数申请PAGE_READWRITE权限的虚拟内存
image

将Badger Shellcode复制到新申请的虚拟内存中
image

调用ZwProtectVirtualMemory将Badger Shellcode内存权限改为PAGE_EXECUTE_READ
image

调用Badger Shellcode,由于此前已经分析过Badger Shellcode所以这里不在继续分析
image

使用BinDiff对比发现stage获取的shellcode为badger_x64_stealth_ret
image

3.4 badger_x64.dll

使用Exeinfo查看badger_x64.dll可以看到是用MinGW-w64编译的x64 dll,并且有一个大的.data section
image

接下来分析dll main,主要就是通过ror13hash获取了ntdll基址然后获取NtProtectVirtualMemory、ZwAllocateVirtualMemroy、ZwWaitForSingleObject、ZwCreateThreadEx函数的syscall id和syacall地址
image

ZwAllocateVirtualMemory给shellcode申请PAGE_READWRITE权限的虚拟内存,将.data section的shellcode复制到申请的虚拟内存,然后将.data section的shellcode清零,然后将shellcode虚拟内存权限改为PAGE_EXECUTEREAD
image

我们可以看到.data section的Badger Shellcode没有进行任何加密
image

之后ZwCreateThreadEx创建线程执行Badger Shellcode,分析到这里可得出结论badger_x64.dll其实就是一个用于加载Badger Shellcode的加载器
image

使用BinDiff对比badger_x64.dll使用的shellcode为badger_x64_ret
image

3.5 badger_x64_svc.exe

使用Exeinfo查看badger_x64_svc.exe也使用MinGW-w64编译的64位程序并且也有一个大的.data section
image

这里获取ntdll.dll基址不是通过ror13hash而是直接通过暴力搜索的方式
image

接着通过ror13hash获取NtProtectVirtualMemory、ZwAllocateVirtualMemroy、ZwWaitForSingleObject、ZwCreateThreadEx的函数syscall id和syscall address
image

接下来还是相同的操作将.data section的Badger Shellcode复制到申请的虚拟内存,然后清空.data section的shellcode,将虚拟内存权限改为PAGE_EXECUTEREAD然后调用ZwCreateThreadEx创建线程执行
image

使用BinDiff对比发现badger_x64_svc.exe使用的shellcode也是badger_x64_ret
image

3.6 badger_x64_stealth_svc.exe

使用Exeinfo查看badger_x64_stealth_svc.exe是使用MinGW-w64编译的64位程序同样有大的.data section,根据此特征可知主要逻辑也是加载.data section的shellcode执行
image

badger_x64_stealth_svc的主要逻辑和badger_x64_svc相同不在阐述。
image

经过BinDiff对比badger_x64_stealth_svc使用的shellcode为badger_x64_stealth_ret
image

4.总结

本篇文章详细分析了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团队服务器。

# shellcode # 逆向分析 # C2 # Brute Ratel C4 # BRc4
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录