Brute Ratel C4 1.2.2 Badger Shellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

2.样本IOCs

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

3.恶意代码分析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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, imm,push reg组合在栈上初始化了大量数据。

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

接下来进入主函数

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

填充导入表到RW内存

BruteRatelC41.2.2BadgerShellcode详细剖析

修复重定位数据

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

修改各个区段的内存权限

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

调用ZwFlushInstructionCache刷新代码缓存

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

3.1.2 badger core dll

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

接下来我们看ThreadMain函数函数

BruteRatelC41.2.2BadgerShellcode详细剖析

首先调用了initKeyAndFormatInfo函数

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

我们查看未初始化的ArrayBox1

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

WSAStartup函数初始化WinSock版本2.2

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

格式化上线包

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

首先设置User-Agent

BruteRatelC41.2.2BadgerShellcode详细剖析

设置域名以及端口

BruteRatelC41.2.2BadgerShellcode详细剖析

设置请求方式和请求路径

BruteRatelC41.2.2BadgerShellcode详细剖析

发送POST请求

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

base64解码

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

这是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,此命令可能还在开发中并不能使用

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

使用base64编码

BruteRatelC41.2.2BadgerShellcode详细剖析

发送心跳包到C2

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

切换到纤程执行

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

RtlExitUserThread退出当前线程结束ROP链

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

创建任务线程执行命令

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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函数

BruteRatelC41.2.2BadgerShellcode详细剖析

获取kernelbase.dll的UNICODE_STRING FullDllName

BruteRatelC41.2.2BadgerShellcode详细剖析

获取ntdll.dll的UNICODE_STRING FullDllName

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

NtOpenFile函数打开dll文件句柄

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

之后调用ZwFlushInstructionCache刷新代码缓存

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

3.3 stage_x64_rtl.bin

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

首先创建Event对象

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

然后WaitForSingleObject等待Event对象

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

调用InternetOpenA函数设置User-Agent

BruteRatelC41.2.2BadgerShellcode详细剖析

InternetConnectA设置域名和端口

BruteRatelC41.2.2BadgerShellcode详细剖析

HttpOpenRequestA设置请求方式和path

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

3.4 badger_x64.dll

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

3.5 badger_x64_svc*ex.e**

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

使用BinDiff对比发现badger_x64_svc*ex.e**使用的shellcode也是badger_x64_ret

BruteRatelC41.2.2BadgerShellcode详细剖析

3.6 badger_x64_stealth_svc*ex.e**

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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

BruteRatelC41.2.2BadgerShellcode详细剖析

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团队服务器。

from https://www.freebuf.com/articles/system/356597.html