0x00 前言与背景
学习beacon生成和调试分析和检测beacon的过程中的一些记录。
0x01 二次开发CobaltStrike
我这是在网上找到的一个由鸡哥反编译的一个CobaltStrike4.1版本,所以不在需要重新反编译了。如下是工作目录。decomplie_src
是存储着CS反编译的代码,lib目录
存储着cobaltstrike4.1.jar文件,作为库文件。out
作为编译输出的目录,src
存放着所需要的源码文件,也就是decomplie_src
源码的子集。这里需要使用的工具是IDEA。
首先新建一个项目,设置好项目名和路径之后,在Project下面新建两个目录,decomplie_src
和lib
目录。
把反编译之后的decomplie_src目录里面的所有java文件复制到项目中的decomplie_src
,并把cobaltstrike4.1.jar文件放到项目中的lib
目录。
对项目进行设置,添加模块依赖,在路径File-->Project Structure-->Modules-->Dependencies
设置SDK。然后在Dependencies这个页面点一下"+"号,添加jar包,完成后点应用。
进入Artifacts-->JAR-->From modules with dependencies
,设置一个MAIN CLASS为aggressor.Aggressor
,这个值可以在MANIFEST.MF
文件中查看。完成之后就是这样的结果。
然后就是将decomplie_src
复制到src
中,这里直接将网上编译的那个版本的src目录复制到本项目的src
目录中,如果有其他需求可以自己从decomplie_src
复制添加即可。
Build -->Build Artifacts -->Build
进行编译。在out目录就可以看到生成的.jar文件。
在Run --> Profile-->Edit...
编辑配置,选择“+”号,在JAR Application添加一个配置文件。在Path to jar
选择编译生成的jar包路径。在VM Optionals
填入-XX:+AggressiveHeap -XX:+UseParallelGC
以后每次要运行或者调试的时候,都可以Run-->Profile-->Run(debug)
最后就完成了。
0x02 beacon生成原理
beacon生成主要有3个步骤,首先是将C2Patch的相关数据patch到beacon中,便于beacon使用,第二步,处理beacon的PE数据和添加引导shellcode,这段shellcode是引导执行ReflectiveLoader。第三步将beacon加密,并和一些数据patch到loader中。
在beacon生成这块,主要涉及到4个java文件。BeaconPayload.java主要是对beacon进行C2Profile相关的处理,主要涉及的函数为exportBeaconStage
。MalleablePE.java,主要用于对beacon进行一些PE相关的处理,主要涉及的函数为process
函数。而BeaconLoader.java主要是patch beacon的开头那段shellcode。主要涉及到patchDOSHeader
函数。BaseArtifactUtils主要是将加密beacon数据和一些加密beacon的key之类的数据patch到loader模板上,
CobaltStrike服务端,通过()生成beacon,经过分析,将断点分别下在WindowsExecutableStageDialog.dialogAction
和WindowsExecutableStageDialog.dialogResult
这两个函数处。通过 dialogAction函数,选择需要保存的文件的内容或者格式。然后调用SafeDialogs.saveFile
函数,弹出保存文件的选择框。
接着调用dialogResult
函数,dialogResult函数是生成beacon的主要流程,参数的参数var1表示保存beacon的路径。
依次获取架构类型(x86或者x64),然后监听器这里选的是reverse_http。
然后调用ScListener.export
函数,export函数的作用是通过不同的监听器,选择不同的Stage。本例中是调用eaconPayload.exportBeaconStageHTTP函数。exportBeaconStageHTTP的参数分别是(var1)端口,(var2)链接地址,var3,var4分别是false,var5是架构。通过不同的架构选择不同的BeaconStage数据。
在exportBeaconStage
函数中,首先调用SleevedResource.readResource
加载指定的beacon原始文件
然后读取C2Profile中的uri等C2Profile信息。我曾经根据这些默认的C2Profile的URI信息,捞到过一批有价值的CobaltStrike样本。
在解析完C2Profile数据之后,就会按照一定的顺序将这些C2Profile的参数拼装起来,并patch到beacon中,以供beacon使用。
在beacon\BeaconConstants.java
这个文件中,可以看到C2Profile数据拼接序号的含义。在之前分析exportBeaconStageHTTP参数的时候,var1为端口。可以看到这里第二个处理的就是端口。而beacon\BeaconConstants.java
文件的第二项的内容也是端口。
拼接C2Profile数据的是和,CobaltStrike使用addShort
,addInt
,addData
,addString
四个函数添加数据。其中前三个为基本函数,addString
底层是通过addData
实现的。以addData为例子,通过分析参数,可知依次添加了index,type,length,和value。且如果是short类型,type为1,如果是int类型,type为2,如果是Data或者string类型呢,type就为3了。
然后将这些数据转化为byte类型,然后将其与46进行进行异或运算。
将读取的原始beacon文件的byte转化为string类型,并定位其中的“AAAABBBBCCCCDDDDEEEEFFFF”字符串。然后用C2Profile数据替换掉。
当C2Profile的数据patch到beacon之后呢,开始处理PE数据,CobaltStrike在处理PE数据的时候分为两部,首先会对PE数据进行预处理,也就是从C2Profile中读取关于PE处理的相关数据,例如image_size,compile_time等等,然后对这些PE的基础数据进行处理。
在对PE数据进行预处理之后呢,根据所选择的架构不同,patch DOS头,也就是前面那段shellcode。首先调用findReflectiveLoader函数,通过导出表获取ReflectiveLoader
导出函数地址。然后将该地址填充到shellcode中,其实分析过这种stage类型的beacon会发现,这种payload本质就是一个PE文件,只不过PE头被修改成一段可以执行的shellcode,这个shellcode的目的就是引导至ReflectiveLoader函数。
构造合适的Loader,在_patchArtifact
函数中,首先读取指定的Loader的模板,该模板位于resources/目录下面,然后生成一组随机的byte数组,并用这些数组对beacon进行异或加密,这也是为什么每个生成的beacon都是不一样的原因。
然后依次保存1024个A所在地址偏移+16这个地址,beacon的长度,异或加密的随机数组,GetModuleHandleA,GetProcAddress函数地址(如果可以的话),保存加密之后的beacon数据,将上述数据替换到那1024个A处。然后将byte[]写入指定文件即完成beacon的生成。
0x03 beacon分析调试
根据上面分析,cobalt strike 的stage模式从loader通过CreateThread执行beacon。经过一小段shellcode之后,执行ReflectiveLoader
,在ReflectiveLoader
处理完PE数据之后,其实是跳转到了DllEntryPoint函数。此处将重点分析beacon对于C2Profile的解析过程。
很显然,在beacon中,第一个函数的作用是定位当前EIP,第二个函数即就是ReflectiveLoader
,经过对PE的一些处理之后,最后会执行DllEntryPoint
.
在dwReason为1的时候初始化C2Profile数据,在Sub_339762_Parse_C2Profile
函数中,很显然,可以看到将C2Profile进行了解密。在内存中,大概还是以 index,type,length,value的顺序进行分布。
然后读取整个解密数据,解析出type和value,只将这两项目保存到刚刚malloc的内存中。并且将type和value从偏移为8的地址处开始保存。而这一部分数据的分布顺序就是一些检测工具,例如BeaconEye的检测原理。
其实,往下面翻一番是能找到所链接的server的。
0x04 BeaconEye原理分析
BeaconEye使用C#开发,通过使用yara规则,检测beacon解析之后的C2Profile数据分布实现的。因为libyaraNET库没有x86版本,所以BeaconEye务必编译成X64版本,才可以通过。
以x86的yara规则举例子,yara第一段为全0,因为C2Profile数据是从+0x08处开始复制的,所以前八个字节没有数据为全0,剩下的数据就很简单了,根据解析C2Profile的变量类型来看,前六个数据类型分别是short,short,int,int,short,short对应的type分别是01,01,02,02,01,01。这样就是检测的原理。