书接上文,笔者发的一篇对某红队钓鱼样本分析的文章:《记一次(反虚拟+反监测+域名前置)钓鱼样本分析及思考》 本文主要针对上文中样本使用的shellcode展开分析,非常详细的记录了笔者分析该shellcode过程;以及对其使用的相关技术进行分析拆解;
0x01 背景
书接上文,笔者发的一篇对某红队钓鱼样本分析的文章:记一次(反虚拟+反监测+域名前置)钓鱼样本分析及思考;
本文主要针对上文中样本使用的shellcode展开分析,非常详细的记录了笔者分析该shellcode过程;以及对其使用的相关技术进行分析拆解;
0x02 分析
对于shellcode我们首先可以通过一些模拟器来查看其内部的大致函数调用情况,然后有针对性的开展分析;
一、自动化模拟分析探虚实
笔者一般使用speakeasy这款工具,https://github.com/mandiant/speakeasy
模拟运行的结果如下图:
如上,可以看到这个样本前面做的一些动作,拿到几个关键函数的调用地址,其中比较明显特征的有:CreatFileMappingA、MapViewOfFile,后面也调用了这两个函数,结合我们dump下来的shellcode文件大小(3百多kb),不难看出里面应该是藏了一个pe文件,这里的逻辑是把藏于其中的pe文件从文件格式映射成内存格式,并且还调用了VirtualProtect来修改内存权限属性,应该是要修改相关数据;
在大致知道了这个情况之后,我们可以做一些尝试,比如直接去dump下来的shellcode文件里面找,是否存在相关pe文件:
二、妙计上心头直捣黄龙
我们在dump下来的shellcode文件里面查可执行文件:
上图是我们查看shellcode中pe文件dos头的情况,可以发现有一个4d5a开头的地方,但是0x3c偏移,以及后面的pe头都没有,所以大概率不是;也有可能是:是,但是其他数据被加密了,需要动态解密还原出来,然后才去拉伸;所以这里我们尝试走捷径失败;
三、老老实实正常分析
那没办法就直接怼dump的文件把:
如下,可以看到,上来第一部分就是调用sub_188e6(这个地址是内置的一个相对地址+获取的运行时绝对地址拿到的和call $+5,pop 操作类似),然后下面的第二部分就是传入几个参数,调用第一部分返回的rax函数,r8d传入的像是特征码;(和CobaltStrike有点像,但是前面头不符合,并且没有出现pe文件头的特征)
我们跟进sub_188e6,直接ida f5看逻辑(一般来说分析shellcode的时候是没有比较逐字节扣的,能f5直接f5即可,但是有些做了编码壳的shellcode还是需要先简单分析壳逻辑,动态调试脱壳后再f5即可;例如:之前笔者分析的一个带编码壳的shellcode 中的shellcode)
如下图,上来第一部分对一个v13数组变量进行构造赋值操作,然后第二部分调用sub_18c66对v9变量进行赋值:
找PE
跟进sub_18c66,其实现如下:
简单转化下出现数据的编码:
上图,我们可以直观的看出,做了一个递减的循环,寻找当i对应地址的WORD为YA的时候,并且其0x3c偏移处的值在0x40-0x400之间,并且i+(0x3c偏移处地址的值)的地址对应值的WORD为0x4a51(JQ)的时候,i的值;
i的起始取值来自sub_18b66,如下,该函数就是返回函数的返回值;
所以我们简单总结就知道了v9的赋值函数sub_18c66,其实就是从函数的返回地址开始,往前找,找到一个符合上述分析条件的地址;并且我们稍加留意可以看到条件当中出现了0x3c这个敏感偏移;这不就是回溯找PE文件位置吗,只不过这里攻击者做了特征隐藏,DOS头的MZ到这里变成了AY,PE头的PE到这里变成了QJ;(难怪刚刚我们上面查pe文件的时候没找到)
按照这个逻辑我们再次查看shellcode的二进制文件,如下图可以看到就是在刚开始的地方;(结合上面我们直接分析的开头代码,这里有点像反射dll加载,但是又不全是,因为做了一些改良,往前面头部加了一些lj代码)
然后我们回到sub_188E6的主逻辑上;
如下:先是对v13数组前两个元素做一个条件判断(这个条件肯定是成立的,上面的赋值就是直接这样赋值的,取低32位,比较也成立,所以这里就是一个恒真式),接着调用sub_18cf6传入v13变量地址;
peb找函数地址
跟入sub_18cf6函数:
其实现如下:
__int64 __fastcall sub_18CF6(_QWORD *a1)
{
int v1; // eax
__int64 result; // rax
__int16 v3; // [rsp+0h] [rbp-68h]
unsigned __int16 v4; // [rsp+0h] [rbp-68h]
__int64 v5; // [rsp+8h] [rbp-60h]
int v6; // [rsp+10h] [rbp-58h]
unsigned int *v7; // [rsp+18h] [rbp-50h]
int v8; // [rsp+20h] [rbp-48h]
int v9; // [rsp+20h] [rbp-48h]
__int64 *i; // [rsp+28h] [rbp-40h]
unsigned int *v11; // [rsp+30h] [rbp-38h]
unsigned int *v12; // [rsp+38h] [rbp-30h]
unsigned __int8 *xx_address; // [rsp+40h] [rbp-28h]
_BYTE *v14; // [rsp+48h] [rbp-20h]
unsigned __int16 *v15; // [rsp+50h] [rbp-18h]
for ( i = *(__int64 **)(*(_QWORD *)(__readgsqword(0x60u) + 0x18) + 0x20i64); i; i = (__int64 *)*i )
{
xx_address = (unsigned __int8 *)i[10];
v3 = *((_WORD *)i + 0x24);
v8 = 0;
do
{
v9 = __ROR4__(v8, 13);
if ( *xx_address < 97u )
v1 = *xx_address;
else
v1 = *xx_address - 0x20;
v8 = v1 + v9;
++xx_address;
–v3;
}
while ( v3 );
if ( v8 == 0x6A4ABC5B )
break;
}
v5 = i[4];
v11 = (unsigned int *)(*(unsigned int *)(*(int *)(v5 + 0x3C) + v5 + 0x88) + v5);
v12 = (unsigned int *)(v11[8] + v5);
v15 = (unsigned __int16 *)(v11[9] + v5);
v4 = 6;
while ( 1 )
{
result = v4;
if ( !v4 )
break;
v14 = (_BYTE *)(*v12