首页
社区
课程
招聘
[原创]HW行动 rdpscan后门简单分析
2019-7-27 16:24 6492

[原创]HW行动 rdpscan后门简单分析

2019-7-27 16:24
6492

笔者目前是初学者,该文章的分析的每一步都是经过实践与参考他人分析报告而输出。

分析时间花了三个星期,文中如有错误,欢迎各位指正。


样本概况

文件名称 :rdpscan.exe

文件大小 :85504 byte

文件类型 :PE32 executable for MS Windows (console) Intel 80386 32-bit

MD5:d0840aeb2642d718f325a07a4b7f6751

SHA1:ae77ca712670176650b01d30e6fe4801e90fafe2

SHA256:9966826ada8b1f366a9e7b9b1e7c430a2a49dda60eb7025c7481295e3ab7f9e4


文件名称 :ssleay32.dll

文件大小 :67416 byte

文件类型 :data

MD5:27b0374083f46693df15c0e3fffad070

SHA1:0eb87babebc32e526f6128fc4be3686c034c4185

SHA256:948e2def7339cf87071913148705bbba3cd9a8279c6157251036d6fac59983d5


拿到文件后,是个压缩包,进行解压后,发现如下文件。




本地静态分析

有个exe文件,查下壳。


无壳,发现是VS2013编写的控制台程序。


在线分析

VT检测

rdpscan.exe 因为之前国内厂商已经检测,并且已经添加特征库,所以在线分析会被检测出。

https://www.virustotal.com/gui/file/9966826ada8b1f366a9e7b9b1e7c430a2a49dda60eb7025c7481295e3ab7f9e4/detection


得到的特征为木马后门


ssleay32.dll(shellcode) 由于关键信息数据已加密,静态分析时则无法检测。

https://www.virustotal.com/gui/file/948e2def7339cf87071913148705bbba3cd9a8279c6157251036d6fac59983d5/detection



在线云沙箱检测

微步云沙箱检测如下,结果是该文件为安全:

这与自己在本地虚拟机运行时一致,当第一次拿到样本时,当晚就在虚拟机里实际运行,直接提示缺失组件,无法成功运行。

因为后门编译为debug版本,没有找到对应的运行库,无法运行,导致沙箱无法对其进行恶意性行为检测。

https://s.threatbook.cn/report/file/0b5a6f745a47e2cb83c167e1f53f92db38f21e07a73f8600fc58dbc73cd927a7/?env=win7_sp1_enx64_office2013


如果要实际运行,本地需要安装VS2015以上版本后才会自带VC运行库。

直接在网上搜索VCRUNTIME140.dll缺失,会找到一些解决文章, 但是经过实践,都失败了,无法安装运行库。

最后只能退而求其次,安装了VS2015,因为会自带所需运行库。

附一个视频,安装VCRUNTIME140.dll。还未实验,不知道是否能行。

https://www.youtube.com/watch?v=AqxebQEFU64

该rdpscan扫描工具参考了之前网上公布的工具源码,所以按照之前网上的分析步骤,先去源项目的github查下源码。

https://github.com/robertdavidgraham/rdpscan/blob/master/src/main.c

经过仔细对比,在471-190行之间的代码被加入了一个判断,如果条件成立就跳转执行恶意代码。

/* See if we have a single IPv4 range, in which case we expand

* this into a larger range */
if (cfg.list_filename == NULL && cfg.target_count == 1 && is_ipv4_range(cfg.targets[0])) {
char *string = cfg.targets[0];
unsigned index = 0;
struct Range range;
struct RangeList list = {0};
size_t i;
uint64_t count;
struct BlackRock br;

/* Free the old list and reset it to NULL */
free(cfg.targets);
cfg.targets = 0;
cfg.target_count = 0;

/* Parse the range */
range = range_parse_ipv4(string, &index, (unsigned)strlen(string));
if (!range_is_valid(range)) {
fprintf(stderr, "[-] %s: invalid range\n", string);
exit(1);
}

IDA打开rdpscan.exe翻译成伪代码后如图,goto LABEL_20为跳转:


sub_405040为触发函数,执行完后,如果返回值为0,则直接跳转。

逻辑是只要在命令行里正常输入IP地址后,一回车执行就会设置返回值为0。

接着来到LABEL_20进行分析

  if ( !dword_426EE0 )

        dword_426EE0 = (int)sub_4050A0();

暂时没啥用,后面没发现出现。

CreateMutex函数的作用是找出当前系统是否已经存在指定进程的实例,如果没有则创建一个互斥体。

然后得到调用GetLastError得到的错误码,如果执行成功,就直接进入到如下部分:

if ( v24 != 183 )
        {
          v26 = GetModuleHandleA("KERNEL32.DLL");
          v27 = GetProcAddress(v26, "VirtualAlloc");
          v28 = GetModuleHandleA("KERNEL32.DLL");
          v29 = GetProcAddress(v28, "CreateThread");
          v30 = (void *)((int (__stdcall *)(_DWORD, signed int, signed int, signed int))v27)(0, 0x100000, 4096, 64);
          if ( sub_405940(v30) )
            ((void (__stdcall *)(_DWORD, _DWORD, void *, _DWORD, _DWORD, _DWORD))v29)(0, 0, v30, 0, 0, 0);
        }

先创建一块虚拟内存空间,接着调用CreateThread新建一个线程。这里有个比较奇怪的函数sub_405940,跟进看看。


sub_405940函数的作用是读取ssleay32.dll文件内容,放入之前创建的虚拟内存空间中,之后通过CreateThread新开线程执行,那可以猜测ssleay32.dll为需要执行的后门代码。

IDA打开ssleay32.dll看看



IDA静态分析与ollydbg动态分析时一致,

现在动态分析下,刚分析时需要下的断点

00405D73  |.  FFD7          call edi       ;  kernel32.CreateThread

之后创建了新线程后,该线程就执行了ssleay32.dll内容。shellcode前面是通过FS寄存器找到kernel32.dll基址,之后找到

00C9F45C   75F11222  kernel32.GetProcAddress

00C9F454   75F148D7  kernel32.LoadLibraryA

00C9F450   75F11826  kernel32.VirtualAlloc

这三个函数地址, 此时的基址为0x01F20000,但偏移是不变的。



其中用到的混淆方法是通过kernel32.dll基址加偏移获取到API函数名称时,取出每个字符然后经过循环算术处理后最后与先前设定的硬编码的值进行比较,如果正确,那么此时就是需要使用到的API函数,避免了直接使用硬编码的API名称进行比较来获取到需要的API函数。


接着检测了这三个API函数是否存在软件断点(与0xCC进行比较),如果存在断点就直接跳转到01F3074E(此时的基址为0x01F20000,但偏移是不变的。)然后退出函数。


调用VirustualAlloc创建一段内存空间,并将一段硬编码的内容(加密的代码指令)复制进来,之后再从其余硬编码内容里复制一段内容作为解密时用到的密钥,最后复制进加密的字符串内容,等待后续解密使用。


之后根据密钥对已复制的内容进行解密,解密完毕后,对密钥部分进行清零。

从加密的字符串数据里,解密出KERNEL32.dll,调用LoadLibraryA加载。

之后从加密的字符内容里,解密出KERNEL32.dll里需要用到的API函数名称,调用GetProcAddress获取地址。


函数如下:

00C9F43C   00DB9C00  ASCII "HeapFree"

00C9F43C   00DB9C00  ASCII "InitializeCriticalSection"

00C9F428   00DB9C00  ASCII "LocalAlloc"

00C9F428   00DB9C00  ASCII "LocalFree"

00C9F428   00DB9C00  ASCII "SetUnhandledExceptionFilter"

00C9F428   00DB9C00  ASCII "TerminateThread"

00C9F428   00DB9C00  ASCII "GetCurrentThread"

00C9F428   00DB9C00  ASCII "GetCurrentProcessId"

00C9F428   00DB9C00  ASCII "CreateFileMappingA"

00C9F428   00DB9C00  ASCII "MapViewOfFile"

00C9F428   00DB9C00  ASCII "UnmapViewOfFile"

00C9F428   00DB9C00  ASCII "EnterCriticalSection"

00C9F428   00DB9C00  ASCII "LeaveCriticalSection"

...

00C9F43C   00DB9C00  ASCII "ExitProcess"


接着通过LoadLibraryA加载解密后的字符串USER32.dll

之后从加密的字符内容里,解密出USER32.dll里需要用到的API函数名称,调用GetProcAddress获取地址。


函数如下:

0211FB30  |02239C00  ASCII "GetForegroundWindow"

0211FB44   02239C00  ASCII "EnumChildWindows"

0211FB30   02239C00  ASCII "wsprintfA"

0211FB30   02239C00  ASCII "EnableWindow"

0211FB30   02239C00  ASCII "GetClassNameA"


接着通过LoadLibraryA加载解密后的字符串ADVAPI32.dll

之后从加密的字符内容里,解密出ADVAPI32.dll里需要用到的API函数名称,调用GetProcAddress获取地址。


函数如下:

0211FB30   02239C00  ASCII "RegCreateKeyExW"

0211FB44   02239C00  ASCII "RegQueryValueExW"

0211FB30   02239C00  ASCII "FreeSid"

0211FB44   02239C00  ASCII "SetSecurityDescriptorDacl"

0211FB30   02239C00  ASCII "InitializeSecurityDescriptor"

0211FB30   02239C00  ASCII "SetEntriesInAclW"

0211FB30   02239C00  ASCII "AllocateAndInitializeSid"

0211FB44   02239C00  ASCII "RegOpenKeyExW"

0211FB30   02239C00  ASCII "RegSetValueExW"

0211FB30   02239C00  ASCII "RegDeleteValueW"

0211FB30   02239C00  ASCII "RegNotifyChangeKeyValue"

0211FB30   02239C00  ASCII "RegEnumValueA"

0211FB30   02239C00  ASCII "RegCloseKey"


接着通过LoadLibraryA加载解密后的字符串WS2_32.dll

之后从加密的字符内容里,解密出WS2_32.dll里需要用到的API函数名称,调用GetProcAddress获取地址。

这是一个与网络操作相关的库,猜测有网络通信功能。


前面需要用到的API函数准备完毕后,之后流程进入call edi(0x0212166A,偏移166A)在此之前已经对其进行了解密,跟进后进行分析。



edi里只调用了一个函数,偏移12A3,继续跟进。



进入12A3后,call 021211C2,内部调用RtlAllocateHeap函数生成了一段堆空间,这里使用了隐藏API函数的方法,如下:

API函数调用方式



后面两个函数判断是否申请成功,申请成功后继续往下执行。



call 021212D2 得到当前进程的默认堆的句柄,并申请一段堆空间。

call 02121096 解密字符串获取到“memcpy”,获取到msvcrt.dll地址,最后获取到函数memcpy的api地址,将一段地址的0x10200长度内容复制到之前申请的堆空间里。



调用WideCharToMultiByte将ascii码转换为Unicode,这里有些函数的作用是对ascii码与Unicode进行互转,中间转换可以不考虑,只关心最后结果就好。

运行到这里,则开始使用之前申请的堆空间的地址作为参数来创建第一个线程(偏移1B4F)创建完毕后CloseHandle线程句柄。





第二个线程(偏移1C31)跟进后发现会利用CreateFileW函数检查"\\.\Regmon"、"\\.\FileMon"、"\\.\ProcmonDebugLogger"、"\\.\NTICE" 等工具是否存在,如果发现对CreateFile下了0xCC断点或者存在执行后发现以上文件就会调用 eax=75F2D822 (kernel32.TerminateProcess) 退出进程。



之后如果错误码不为0,则调用CloseHandle。进入第三个线程准备中。

第三个线程(偏移1E63)后面调用Sleep函数暂时无法跟进线程里,使用WaitForSingleObject函数可以转到第三个线程地址。

接着调用了user32 getforegroundwindow,而GetForegroundWindow获取一个前台窗口的句柄(用户当前工作的窗口)

之后使用eax=76900EAC (user32.EnumChildWindows)枚举以下窗口类名,判断是否存在。 


0295FE14   0031C3B8  UNICODE "ACPUASM"

0295FE34   0031C3F8  UNICODE "AOPOASM"

0295FE54   00320658  \String2 = "AOPUASM"

0295FE54   00320670  \String2 = "ACPOASM"

0295FE34   0032C060  UNICODE "WinDbgFrameClass"


接着延时0x3E8毫秒(也就是1秒的时间)



但第四个进程(偏移2062)调用call esi创建的线程跟进后存在反调试,会检测kernelbase.IsDebuggerPresent函数的开头第一个字节是否为0x64。



利用IsDebuggerPresent 和 GetTickCount函数来检测调试器,其中使用GetTickCount获取时间,如果时间相差1秒就退出进程。




遇到的第一个难题,在阅读腾讯御见发布的分析报告中,发现截图里的指令,我在使用IDA打开ssleay32.dll中始终无法找到,由于对工具的不熟练,就这么困扰了几天。

之后分析发现,对于对于动态加载的shellcode,可以在运行过程中使用LordPE dump指定的内存,然后使用IDA打开进行分析,稍稍发现了一些线索,不过这种方法不太好,最好的方法是在ollydbg中如果运行到需要分析的部分时,右键选择【复制】【复制数据到文件】即可,之后IDA打开选择以32位汇编进行显示就会显示当前实际存在内存中的汇编指令,就这么本地进行分析,这个问题就这么解决了。

然后接着开始分析,偏移2171处为第五个线程,通过使用OpenMutex搜索互斥量来判断Wireshark是否存在,存在就终止进程。



ASCII "Wireshark-is-running-{9CA78EEA-EA4D-4490-9240-FC01FCEF464B}"




上面这几个线程创建完毕后,来到生成魔法字符串的部分,生成的内容为Poison Ivy C++,同样是从加密的字符串里进行解密,之后进行拼接。



通过搜索发现这是一款非常经典的远控木马特征字符串,之后通过call ebx进入木马下一部分来执行不同功能的shellcode。

call ebx 按F7跟进去,如图:

第一个call 02302A98(偏移2A98),创建了一块堆空间,将之前的ssleay32.dll内容给复制进来,之后EIP指向该处执行。


流程如下:




这里相当于执行完call ebx后就又重新执行了之前所有的shellcode内容

前三个call 02302A9B shellcode模块(偏移为2A9B)主要目的是使用之前的shellcode环境准备部分再次新开地址空间来运行,执行另外的恶意行为。



后三个call 02302A9B shellcode模块(偏移为2A9B)里则包含了对配置信息的解密读取并创建svchost.exe进程并注入shellcode。

call 02302A9B 为相关shellcode模块(偏移为2A9B),这里共6个shellcode子模块。

在这些子模块中要找到木马的配置项,难度有些大,在动态调试中,发现都会创建新线程加载之前的准备环境内容,由于目前动态调试过于复杂,只好虚拟机里实际运行后门看看。



安装好火绒安全,主要是使用已变成内置组件的火绒剑。

当直接运行起来后,在进程列表里可以查看到新创建了svchost.exe进程。




开启监控,命令行一运行rdpscan扫描目标,立马就有了数据被记录。

由于之前的分析已经得知ssleay32.dll为shellcode,需要被加载的,所以这里重点关注。



看到已经对这个文件进行了打开操作,双击看看具体内容。



发现访问了可疑IP,以及新发现了svchost.exe进程,说明这是后门创建的。


在【进程】选项里找到3516进程ID的进程看看,右键选择【进程信息】。


查看线程,发现被注入了shellcode,同时在运行中。


查看TCP/IP


现在的问题是如何查找该木马的C2域名与地址?因为上面火绒剑仅仅记录了指向的IP。

这里的话可以先猜测,比如搜索“http”,但有时不一定能搜到。由于之前已看了相关的分析报告,目前后门已经运行中,所以这里就能确定相关的关键字。




很明显,hw.html是可疑的,于是找第一个出现的字符串虚拟地址,在ollydbg附加刚刚发现的svchost.exe成功查看到木马的配置如下:


该后门暂时分析到这里,文中如有错误,欢迎指正。



[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法

上传的附件:
收藏
点赞3
打赏
分享
最新回复 (3)
雪    币: 2428
活跃值: (2864)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
boursonjane 2019-7-27 17:08
2
0
好不容易把后门传过去要运行了, 结果在目标机器上弹出"丢失xxx.dll", 哈哈哈哈哈
雪    币: 2095
活跃值: (344)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
icesnowwise 2019-7-28 09:03
3
0
bjtwokeight 好不容易把后门传过去要运行了, 结果在目标机器上弹出"丢失xxx.dll", 哈哈哈哈哈
我觉得安全人员电脑里面装个VS应该很正常吧,问题不大,楼主是虚拟机没那么多库。
雪    币: 233
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
白鱼danger 2019-8-9 16:10
4
0
还真是狠角色,防不胜防啊
游客
登录 | 注册 方可回帖
返回