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,需要被加载的,所以这里重点关注。
看到已经对这个文件进行了打开操作,双击看看具体内容。