本文主要是针对ReBeacon项目的兼容合并之前二开时留下的记录。主要是将原生Beacon替换为WBGlil大佬的ReBeacon。本人是比较菜的菜鸡,过程有一些错误时间问题就不改了。大佬们多多包容。
一、Stageless构造分析
图:Stage与Stageless关系
可能有些漏打r所以Stageless是普遍理解的Stage而Stager是Stage,看图
当一般木马样本执行Stager(Shellcode),该Stager大小一般在1k左右,功能是自动通过Hash查找API地址,并且下载最终Stageless,该Stageless是包含beacon核心的一段shellcode通过call调用直接去执行这一组shellcode,并且通过反射DLL注入技术将DLL注入到内存空间中,并且执行DllMain进入后门主体。
首先分析功能,CobaltStrike会通过协议来调用对应协议的处理函数:
“exportBeaconStage<协议>”HTTP和HTTPS是使用同一个函数
图:stageless管理函数
通过分析发现一个payload对应不同的导出BeaconStage函数。
图:exportBeaconStageHTTP函数
该函数根据处理器架构去从两个文件中选取一个进行读取,并且构造参数。
图:DLL文件选取
并且调用exportBeaconStage函数进行参数定义,该参数包含profile连接地址等信息。
如图所示,包含URI派生进程 水印等等信息,并且使用beacon_obfuscate进行异或加密。然后查找AAAABBBBCCCC进行替换为已经异或加密的参数信息
图:加密替换
图:参数构造
通过逆向分析,AAAABBBBCCCC就是对应着DLL内部异或操作指令并且引用地址
图:异或操作
这个时候这个DLL,直接通过rundll32,可以进行上线了。但是DLL不能直接作为Shellcode进行加载,需要构造成反射DLL注入的shellcode才行。但是通过逆向发现ReflectiveLoader的指令为:int 3(中断3,通过这个指令可以在调试模式产生断点效果)和 nop(跳过/忽略)并且没有多余的导出表函数,但是一般反射dll地址通过ReflectiveLoader,但是该函数明显无效.
图: ReflectiveLoader函数
通过逆向CobaltStrike分析发现CobaltStrike通过查找cc90909090为定位点(跟AAAABBBB同理)进行替换,通过符号匹配定位ReflectiveLoader在文件偏移,以这个偏移进行查找。这个设计很巧妙,可以实现模块化替换代码数据,当然地址问题比较麻烦。
图:查找cc90偏移
替换内容为BeaconLoader,HV、MVF、VA是根据profile中allocator参数控制,是申请内存方式,个人经验这里建议使用VA,因为堆管理机制问题内存扫描概率很少会扫描到该地址。
图:allocator参数
这些.o文件其实是coff文件对象文件,cobaltstrike会提取这些coff文件对应的代码段,一般不出意外是已经实现好地址寻址功能这里随机拿一个进行分析。
我们分析VirtualAlloc对应BeaconLoader.VA.x64.o,通过伪代码和对应指令调用堆栈寄存器RSP+DllMain偏移的函数,这里推断基本是DLLMain函数地址。并且发现该coff只有两个导出表。
功能方面,先查找对应的依赖库地址,并且定位Kernel32EAT和动态函数地址,最后申请内存,然后复制句柄和段和进程导入表等等,最后修改内存重定向表和打内存补丁,最后运行DllMain。当然每一个函数地址内部样子这里不多分析,其实是hash定位得到对应API地址,然后执行对应流程如写入内存,申请内存空间等等。
图:BeaconLoader.VA 核心流程
图:BeaconLoader
这样ReflectiveLoader被替换为对应内存申请方式的二进制代码。最后将处理好的DLL文件进行最后一步处理,加入寻找ReflectiveLoader函数的代码为头部,即可生成完成。
图:MZ头
当然这个MZ头不是PE文件的MZ头,而且汇编pop出栈指令
二、Stager分析
Stager其实就是普遍理解的Shellcode,这组shellcode一般大小为1k,一般功能是通过hash定位API地址,然后再调用API地址。
以下为定位API地址代码片段:
图:stager头部
图:执行成功片段
该定位片段执行完毕会通过图中jmpeax是进入跳转api内部,因为调研这个定位时就是通过call调用方式,如果再call调用会影响堆栈导致部分指针参数无法引用。
开始分析首先Shellcode通过fs寄存器的0x30偏移处获得TEB中的PEB指针,并且获取Ldr表
图:TEB结构体
图:Ldr与PEB
并且遍历里面链表,然后获取InMemoryOrderModuleList和ShutdownInProgress并且进行查询得到kernel32.dll的基地址然后获得导入表地址。
流程:
TEB->PEB->Ldr->InMemoryOrderLoadList->currentProgram->ntdll->kernel32.BaseDll
然后通过遍历并且通过lodsb指令计算对应函数名称hash,然后得到API地址,最后恢复堆栈平衡,跳转到获取到的地址执行API功能。
l0x0726774C——LoadLibraryA
l0xA779563A——InternetOpenA
l0xC69F8957——InternetConnectA
l0x3B2E55EB—–HttpOpenRequestA
l0x7B18062D—–HttpSendRequestA
l0x315E2145—–GetDesktopWindow
l0x0BE057B7—–InternetErrorDlg
l0xE553A458—–VirtualAlloc
l0xE2899612—–InternetReadFile
这是这段shellcode调用的所有函数
1.LoadLibraryA加载wininet.dll
2.InternetOpenA->InternetConnectA->HttpOpenRequestA->HttpSendRequestA 创建连接发起请求
3.GetDesktopWindow->InternetErrorDlg 查找报错
4.VirtualAlloc开辟内存
5.InternetReadFile读入完整shellcode
可以看到这段shellcode其实也只是一个远程下载的功能,当然整个shellcode一环扣一环感觉连接的很紧凑
三、Stageless 生成支持
原生ReBeacon_Src是带有rawdata数据的,替换为:AAABBBBCCCC为开头即可需要长度4096,我们称为标志数据,上面分析已经有对应的分析。注意入口函数异或算法的key也需一同修改。
图:标志数据
入口算法,因为很多人需要绕过Beacon_Eye,所以会对服务端key进行修改实现绕过eye目地。
图:入口算法(0x61)
接着添加一个ReflectiveLoader导出函数,需要大量int 3 和nop指令作为标志代码,因为服务端会对其进行替换覆盖为正确的加载代码。(长度一直延下去 一个中断3七个nop)
图:导出函数。
接着对项目进行编译,使用CrackSleeve进行加密为beacon.x64.dll,替换二开环境的sleeve目录对应文件,接着生成stageless即可实现上线。
图:Stageless 成功上线
四、对象文件加载修复
ReBeacon_Srcx项目中对BOF功能仅停留在x86(32位)上兼容,所以对于32位BOF 可以做到完美加载,但是x64才是实战环境下的主要架构。
图:问题代码点
通过代码分析,可以发现该流程为判断类型是否6和20,并且修改对应段的偏移数据,还是比较复杂的算法。但是在x64下这段代码变得无效,所以需要添加见人64位的bof内存补丁功能。我们通过IDA Pro
图:x64内存补丁伪代码
接下来通过宏定义,判断WIN32和x64架构,编译不同代码即可。类型小于5以下那么就跳出,如果是那么读取段数据对应的重定向值是否小于0xffffffff如果是那么修改pcode+offset对应的偏移值。
图:内存补丁新代码
接着使用自写system命令测试bof功能,成功加载。
图:成功执行代码
(API地址因为某些机制干扰 有10%功能性运行失败)
五、portscan获取列表失败
在cobaltstrike渗透过程,portscan功能,对应的网段地址列表有时候无法获取。
图:扫描窗口
这个对cobaltstrike的使用会造成不少没必要对麻烦,通过打开时命令会显确定问题在于beacon问题。
通过逆向发现,程序会最终发送一个interfaces.x64.o的bof对象文件到目标运行,获取对应的网段列表
图:网段操作
即然bof发送了,那么运行后应该会返回地址信息给teamserver那么只能重写这个对象文件了。 通过IDA分析该对象文件原理,发现通过Iphlpap.dll内部的GetIfEntry和GetIpAddrTable实现并且通过beaconformatprintf构造返回信息,而且发现其申请内存长度为792,WindowsSDK编译可能根据编译器参数忽略一些参数等等会导致长度不一样所以需要进行修改改为动态获得参数。
图:分析信息
根据该逻辑并且进一步完善,我们得到以下代码。直接忽略掉LoadLibrary 的基本操作,直接进入重点代码。
图:核心代码
图:修复成功
六、Hashdump异常修复 (其它功能错误也是使用该方法修复)
Hashdump导出时,报error:2错误,一般cs使用命名管道连接功能模块。如果错误代码为2,那么代表没有找到路径可以推断功能没有被执行。
图:异常代码2
打开spawn函数,将getDLLContent返回值进行导出。通过IDA打开发现为一个32位程序(因为使用32位的寄存器与32位地址)。那么一个64位程序为什么使用32位动态连接库进行注入??
图:汇编代码
那么既然确定错误点了,但是spawn其实没有问题,因为截屏缺没有问题,截屏也是使用spawn进行执行。那么分析一下这个DLL是怎么调取的。这个GetDLLContent其实就是资源读取通过getDLLName获取读取目标名。然后这个Hashdump是继承了Job,所以上一级函数是直接调用Hashdump的spawn对象,然后var2就是架构名。看看调用语句。
图:DLL名字构造
发现使用is64函数,但是这个函数返回值却是false估计没被引用所以就算是x64因为false所以先取了x86返回值。那么我们直接参考截图功能使用BeaconEntry的arch函数即可获得架构类型,截图注释代码。
图:修复代码
然后成功执行hashdump
图:Hashdump
原文始发于微信公众号(猫头鹰安全团队):Cobalt Strike – ReBeacon 合并记录
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论