实战|记一次攻防样本——shellcode分析(下)

举报
亿人安全 发表于 2024/10/31 23:20:08 2024/10/31
【摘要】 sub19456其逻辑实现如下:如果a4为4,也就是之前隐藏在shellcode中的文件格式pe的pe头其Characteristics**IMAGE_FILE_BYTES_REVERSED_HI**值为0,那么这里就会调用VirtualProtect函数将,现在加载到内存的pe的可执行段权限修改为可读可执行,这里攻击者这么做的目的也非常明显,因为上面我们分析sub_19396的时候,如何a...

图片

sub19456其逻辑实现如下:

图片

如果a4为4,也就是之前隐藏在shellcode中的文件格式pe的pe头其Characteristics**IMAGE_FILE_BYTES_REVERSED_HI**值为0,那么这里就会调用VirtualProtect函数将,现在加载到内存的pe的可执行段权限修改为可读可执行,这里攻击者这么做的目的也非常明显,因为上面我们分析sub_19396的时候,如何a4==4,其将申请的空间的权限,利用VirtualProtect将其修改成可读可写了,没有执行权限,那么这里攻击者经过上面一堆操作,差不多就是手动的把文件格式pe以内存格式(也就是我们常说的拉升)加载到了内存的一个地址,并且做了一些还原,对可执行段代码进行解密,对导入函数表解密还原,对重定向表进行修复等相关操作;做完这些操作自然就是要开始运行了,所以给可执行节区的内存空间要加个可执行权限;如下图:a1+0x28,其实就是v13里面的第六个数组,v13[5],也就是VirtualPortect;

图片

图片

然后回到主逻辑sub_188E6,对v13里面的进行清空,如下图:

图片

一个条件判断,获取PE_head_Characteristic的值,判断第13位是否为1,这个位一般是用来标志映像是系统文件的;如果该标志位为1,v2等于pehead的文件可选头里面的SizeOfInitializedData的值,反之,v2等于pehead的文件头里面的TimeDateStamp和PointerToSymbolTable的组合部分值,前者的后两个字节+后者的前两个字节,不难看出这v2的值应该也是受攻击者的配置项影响的:

图片

图片

调用DLL主函数初始化

然后继续看,拿到这个v2用来做什么了,v10=v5+v2,v5是映射到内存pe的基址,那么这里应该是基址加相对偏移拿到的拿到一个绝对地址;

最后直接把v10强行转化成指针函数执行了,传入了三个函数,v5,1,a1分别的实际含义是:映射到内存的恶意pe的基址、1、最先传入的参数(没有:0);

图片

最后返回的rax,就是v10的值(这个大概率就是一个内置好的函数地址,攻击者通过是可以通过配置更改,调用的函数的,这里应该是有两个选择,因为v2的取值有两种可能,是看攻击者怎么配置的):

图片

简单总结sub_188e6这部分逻辑,就是找到隐藏在shellcode中的恶意pe文件,该pe文件被做了很多手脚,于是做了很多操作还原,并申请空间,将其拉伸到内存中,中间做了一些免杀手段,最后调用拉伸pe中的某个函数的地址; 返回的地址就是调用的地址;

到这,我们就需要动态调试了,可以直接把开辟的用来加载恶意pe的空间,直接dump下来,我们再分析对应调用的函数实现;

如下图是最后调用代码段中v10对应的函数的时候,开辟空间里面的相关内容,

图片

图片

拿到这个v10 call调用的地址:

图片

ida里面直接找到对应偏移的函数:sub_20b48(v10 对应的函数):

图片

分析逻辑,上来一个条件判断第二个参数是否为1,是则调用sub_282d0,(上面我们分析引导加载的函数最后调用v10传入的第二个参数就是1):

图片

里面越界内存分别调用的函数是GetSystemTimeAsFileTime、GetCurrentThreadId 、GetCurrentProcessId 、QueryPerformanceCounter

图片

图片

图片

这里逻辑大致就是通过GetSystemTimeAsFileTime获取当前时间戳,异或线程ID和进程ID得到一个值v2;然后QueryPerformanceCounter获取另一个时间戳v4,通过位移运算以及和v2进行一些异或运算得到v0,对v0做了一个判断是否等于指定值:0x2B992DDFA232,等于就赋值0x2B992DDFA233,最后使用v0给qw_47ef0赋值,并返回;(乍一看感觉是攻击者在做什么校验,其实不然,这里是在计算64位的security_cookie,就是我们编译的时候开GS生成的代码)

分析到这,我们不难发现其实这个v10call,也就是sub_20B48其实就是恶意加载的pe文件(dll)的dllmain或dllentrypoint,我们看其逻辑和参数和dllmain都是一样的,这里的第二个参数就是fdwReason,为1一般是首次加载dll触发,再加上上面的security_cookies计算;参考cobaltstrike,反射调用引导函数修复加载dll之后也是直接跳去dll主逻辑;

所以我们基本可以把重点定位到如下函数sub_209e8

图片

sub_209e8实现逻辑如下,接收两个参数,这里我们可以看到a2为1的时候,先调用的是上面那次,此时传入的v6是被加载恶意pe的基址,a2是1

图片

case1情况下,先是两个条件判断,sub_20d88sub_249B0:

图片

第一个if,sub_20d88实现如下,可以看到调用了一个函数,然后根据函数是否调用成功返回eax,函数如果返回是0,sub_20d88也返回0,反之返回1;此外还把函数的返回值存到qw_4c930:

图片

调用的函数是:GetProcessHeap,这里是获取进程的堆句柄;

图片

图片

然后sub_249B0这个判断,其实现如下,没有接收参数:

图片

先调用sub_1f1a8函数,该函数实现如下:先调用函数RtlEncodePointer获取返回值,然后作为参数调入下面几个函数

图片

图片

RtlEncodePointer不是windows公开的api,看名称像是获取一个“被编码/被加密”指针;

在如下的几个函数里面,传入刚刚RtlEncodePointer的返回,这些函数都是赋值函数,把返回值赋值给一些指定处;

图片

如:sub_20de8

图片

最后调用sub_2312c并返回,其实现如下:

图片

如上图,sub_2312c也是一个赋值函数,这个函数先调用GetModuleHandle()拿到kernel32.dll的基址,然后调用GetPorcAddress,获取上述的函数的地址并保存到指定处(qw_525a0),存的时候还做了一个异或加密,key是:2b992ddfa232;最后返回GetCurrentPackageId的地址;

简单总结:sub_1F1A8,就是做了很多初始化的赋值操作,获取函数地址值等;

然后我们回到sub_249B0,一个判断,判断条件sub_22f14,做赋值操作,将sub_23070函数结果返回dw_479c8,然后调用另一个函数sub_24a30:

图片

这些函数的逻辑有些晦涩难懂,不太好分析;

四、恍然大悟跳出来

到这其实我们可以看出,shellcode中的引导加载后调用v10(dllmain),这里就是在做初始化;关键call相关逻辑大概率是初始化做完之后:

跳回shellcode开始处,我们可以看到sub_188e6返回的rax,接下来被调用了,所以sub_188e6里面自己调用一次是在做初始化;(这和cs一模一样)

图片

此时传入的三个参数:shellcode中隐藏pe开始位置、4、一个类似特征码的56a2b5f0

图片

此时再调用dllmian ,我们来到sub_183e0函数:

图片

其实现如下,三个参数和dllmian传入的一样;a2等于4;

图片

带入a2=4,来到如下逻辑:

图片

五、终是CobaltStrike没跑了

到这差不多我们就可以定性了,就是cobaltstrike,如下是之前之前笔者22年一文章里面分析的 ;Cobaltstrike4.0——记一次上头的powershell上线分析,可以看到上图逻辑和之前笔者分析的一样的:

图片

按照cs逻辑,这里后续主要的逻辑在:sub_BA74()里面;

回连c2、接收指令

图片

图片

图片

指令解析执行流:

图片

图片

到这shellcode基本内容差不多就这些了,其实主要就是过了一遍反射加载函数和相关逻辑实现;

0x03 该shellcode利用的技术点

抛开上面的shellcode不谈,其是cobaltstrike的“shellcode”(严格意义说就是stage,只不过我们切入的时候是把他当shellcode切入),那么我们简单总结下分析出来的cs的反射加载函数里面兼容使用一些技术:

1、shellcode头部插入垃圾代码;
2、shellcode去call pop特征;
3、shellcode中隐藏pe文件(dll),去dos头特征、去pe头特征;
4、利用反射加载的dll里面的微软弃用字段来存储一些配置项(如密钥、是否开启内存权限管控等);
5、利用CreateFileMapping+MapViewOfFile来替换Virtual相关的内存空间申请;
6、反射加载函数里面找到要加载的pe之后,完全抹除pe文件的dos头、pe头特征;
7、反射加载的pe文件的导入表中的导入模块名称、导入函数名单以及可执行节区代码都是被加密的,反射加载函数加载的时候动态解密加载

通过分析我们也间接的学习了cobaltstrike对一些配置项落地实现的原理;
通过对比对应的关键profile配置项如下(这里我们根据分析出来的逻辑回去对比cs的配置profile里面的配置项,注意不是说上面的shellcode都做了)

stage{  
    set userwx “true”  # 避免申请rwx权限内存  
    set allocator "MapViewOFFile" #利用MapViewOfFile来申请匿名映射内存空间,用于加载dll  
    set cleanup “true”   

    set magic\_mz\_x64 "YA" # 修改反射加载dll的头部特征,dos头和pe头  
    set magic\_pe "JQ"  

    set obuscate "true" # 混淆导入表  

    set stomppe "true" # 消除加载内存后的pe头中的dos头和pe头特征  

    transform-x64{  
    \`   prepend "xxxxxx"   #上述shellcode头部的多余字段内容  
    }  
}

最后我们回过头来,直接使用脚本提取下这个beacon的配置:

图片

这个样本的上线方式和之前的文章核对下,没有问题,可以看到如下图两个标记的地方,可以明显看到回连的c2,以及其使用了域名前置的手段;

图片

0x04 总结

其实攻防中,红队使用的主流c2框架基本还都是CobaltStrike,因为cs 可以生成跨平台payload、可选远控形式的隧道多、强大的后渗透能力、互联网上有丰富的现成功能插件、和其他远控工具的兼容及其优秀的团队协作设计;所以很多红队都是使用cs;

但是享受其优点的同时也要接纳其缺点,比如会被检测设备重点照顾,你可能会说那cs不是可以修改profile来修改流量侧和端侧的特征吗,但是防守方法做检测的同学会想不到吗,所以在一定程度上你修改了profile的样本还是会被检测到(这个需要大量测试,因为不同厂商的检测点可能不尽相同,甚至对相同特征不同厂商的检测点也可能不同,你煞费苦心调制的profile可能因为没有做一个点,直接被秒了,如上文,拿几年前Didier Stevens师傅写的配置提取样本都可以直接把这个beacon秒了,那厂商端侧检测就更容易了,当然肯定是有一堆能绕过检测的配置能满足实际的攻防需求,因为实际攻防场景里我们不是要过所有安全设备,而是知道了目标环境之后,把目标环境里面的杀软以及相关安全设备过了就行); 那么在这种场景下,我们不妨思考下,红队如何破圈呢;这是一个值得思考的问题!

笔者才疏学浅,文章如有错误欢迎指出;

一方面是通过修改profiles

亿人安全
知其黑,守其白。手握利剑,心系安全。主要研究方向包括:Web、内网、红蓝对抗、代码审计、安卓逆向、CTF。
181篇原创内容
公众号
#应急响应 · 目录
上一篇应急响应——让Linux下的隐藏手段(Rootkit)无所遁形

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。