记一次头铁的病毒样本分析过程
样本
运行效果:
点击确定后就无任何反应。
二 静态分析
1、程序信息
MD5 fdd9fd0249d48d8c6d991741c67fcfeb
SHA-1 ff0181242825b5bb8cac1d4d17e8377352e3aa55
SHA-256 6a9bdabc4599618513de5c963972929de9322c486e84e101e177c0868e7c5fb7
File size 1.34 MB (1408512 bytes)
其中较为引人注目的就是winnet.dll、shell32.dll、advapi32.dll,这三者分别是对网络,运行程序,注册表进行操作的动态库,说明程序存在运行外部程序和联网操作。
字符串的话,通过Strings.exe查询后没发现任何敏感字符串,这里就不贴出来了。
2、在线查毒
3、进程行为监控:
通过火绒的监控发现其对注册表进行了大量操作,不过并没什么影响。
且运行了一个名为CrashReporter.exe的程序。
途中创建了一些文件:
首先是CrashReporter.exe,跳到目标目录后发现其文件大小与样本大小相同,故猜测此样本运行后将自身转移到新路径后伪装绿色运行,其次是txt,打开后发现内容为:
ABC
AAC
AAD
BBC
BAC
BAD
暂时不知什么意思,然后是WeiXinCR01,内容为:
D2 C2 7E AD 52 06 C4 93
这里根据后面的调试,发现内容含义为Crc32计算C盘的序列号的结果。
4、网络行为
HttpDebuger无任何发现。
三 动静态分析
首先分析原样本,突破口可以从给MessageBoxA下断后返回拿到关键点。
这里直接返回到call的位置,直接计算call地址的相对地址,然后扔进IDA进行分析。
这个样本在静态分析时之所以没有看到敏感字符串是因为他的字符串都是以字符数组初始化的。
在分析的时候可以通过先转义这些字符数组的内容,再去分析这个地方执行了什么。比如这里的字符数组为SetEvent error,那么说明调用SetEvent失败。并且他这个样本调用函数的方式是通过一个类似shellcode的方法获取,然后把函数存放放到一个全局变量里。
我们来到调用了MessageBoxA后,直接到函数头部进行一直交叉引用,最后来到了WinMain的最初调用处。
这个过程中会发现整个代码中存在很多这种形式的代码。
这些代码看似是在获取TEB和PEB操作,但是经过简单分析后,发现这些其实就是垃圾代码,只是为了通过增大代码量,用来增加分析时间。
1、StartMainWork分析
在这个StartMainWork函数执行前,会执行几个数据初始化的函数,包括有crc32和crc64表的生成,动态库函数的获取,还有样本之后所需要用到的数据。这里crc32和64生成的函数很好识别,因为其算法固定,且带有常量值,下面是样本生成crc的关键代码。
for ( i = 0; i < 256; ++i ) // 开始生成crc32表 { crcValue = i; for ( j = 0; j < 8; ++j ) { ......垃圾代码........ if ( crcValue & 1 ) crcValue = (crcValue >> 1) ^ 0xEDB88320;// 搜索这个值,可以知道这里是CRC算法的常量值 else crcValue = crcValue >> 1; g_crc32Table[i] = crcValue; } }
for ( jj = 0; jj < 256; ++jj ) // 开始生成crc64表 { v45 = jj; for ( kk = 0; kk < 8; ++kk ) { ......垃圾代码........ if ( v45 & 1 ) { LODWORD(v0) = (v45 >> 1) ^ 0xAC4BC9B5; // 根据这个常量值,搜索后发现是crc64的常量值 HIDWORD(v0) = (v45 >> 1 >> 32) ^ 0x95AC9329; v45 = v0; } else { v45 >>= 1; } g_crc64Table_Low[2 * jj] = v45; g_crc64Table_Hight[2 * jj] = HIDWORD(v45);
然后比较有意思的就是动态库函数的获取了,该样本采取了类似shellcode的方式获取,通过Ldr来遍历自身的dll模块,然后通过都PE文件的解析寻找函数地址,并添加到全局函数表中。
该方式在《恶意代码分析实战》一书中第十九章的shellcode符号解析有讲解,这里就不详细解释了,以下是关键代码:
peb = readPEB(); *functionTable = peb; if ( peb ) { // 通过动态调试得知这里获取的是Kernel32.dll地址,参数二为字符串Kernel32.dll通过crc32计算后的结果 ModuleBase = getModuleBaseByCrcValue(*functionTable, 0xF7784A01); functionTable[1] = ModuleBase; if ( KernelBase ) { // 直接从目录项里获取参数三对应的函数地址 procCreateFileA = getProcAddrByDirArray(functionTable[1], -1, (int)&strCreateFileA, 0); ....往下的也是一样 } ............ }
这里贴出它加载的所有函数:
$ ==> 757E3090 kernel32.CreateFileA $+4 757DDF70 kernel32.GetLastError $+8 757D83E0 kernel32.GetSystemDirectoryA $+C 757E22D0 kernel32.ExpandEnvironmentStringsA $+10 757E0BE0 kernel32.GetStartupInfoA $+14 757F2DA0 kernel32.CreateProcessA $+18 757E2E40 kernel32.CloseHandle $+1C 757E3030 kernel32.WaitForSingleObject $+20 757E3420 kernel32.ReadFile $+24 757D89C0 kernel32.lstrcat $+28 757E2DF0 kernel32.GetCurrentProcessId $+2C 757DDE70 kernel32.GetCurrentThreadId $+30 757E2300 kernel32.GetTickCount $+34 757E3510 kernel32.WriteFile $+38 757F2D80 kernel32.CreatePipe $+3C 75130000 "MZ?" $+40 75155840 user32.wsprintfA $+44 757C0000 "MZ?" $+48 757E0B30 kernel32.LoadLibraryA $+4C 757E0A40 kernel32.FreeLibrary $+50 757E0D90 kernel32.GetModuleFileNameA $+54 757DE010 kernel32.lstrlenW $+58 757DDF00 kernel32.SetLastError $+5C 7581F6A0 kernel32.lstrcpyW $+60 757E03A0 kernel32.lstrlenA $+64 757D8320 kernel32.lstrcpyA $+68 757DF440 kernel32.lstrcmp $+6C 757DF320 kernel32.VirtualAlloc $+70 757DF420 kernel32.VirtualFree $+74 757E0860 kernel32.GetModuleFileNameW $+78 757E3090 kernel32.CreateFileA $+7C 757E32C0 kernel32.GetFileSize $+80 757E3420 kernel32.ReadFile $+84 757E2E40 kernel32.CloseHandle $+88 757D88F0 kernel32.GetEnvironmentVariableA $+8C 757D89C0 kernel32.lstrcat $+90 757E3510 kernel32.WriteFile $+94 757DDF70 kernel32.GetLastError $+98 757E0E60 kernel32.Sleep $+9C 757E3030 kernel32.WaitForSingleObject $+A0 757E2E90 kernel32.CreateEventA $+A4 757E0E70 kernel32.CreateThread $+A8 757E2FE0 kernel32.SetEvent $+AC 757D92B0 kernel32.OutputDebugStringA $+B0 757E2300 kernel32.GetTickCount $+B4 757D2EB0 kernel32.FindResourceA $+B8 757E03E0 kernel32.SizeofResource $+BC 757DE7A0 kernel32.LoadResource $+C0 757DF2A0 kernel32.LockResource $+C4 757F4B10 kernel32.SetCurrentDirectoryA $+C8 7581CD60 kernel32.WinExec $+CC 757DF4B0 kernel32.GetProcAddress $+D0 757E2DF0 kernel32.GetCurrentProcessId $+D4 757DDE70 kernel32.GetCurrentThreadId $+D8 757F2D80 kernel32.CreatePipe $+DC 757E3010 kernel32.WaitForMultipleObjects $+E0 757E2FD0 kernel32.ResetEvent $+E4 757E0AF0 kernel32.ProcessIdToSessionId $+E8 757E1E10 kernel32.GetNativeSystemInfo $+EC 757E0640 kernel32.IsWow64Process $+F0 757E2DE0 kernel32.GetCurrentProcess $+F4 757F2DA0 kernel32.CreateProcessA $+F8 757D9870 kernel32.TerminateProcess $+FC 757F5000 kernel32.VirtualAllocEx $+100 757F5250 kernel32.WriteProcessMemory $+104 757F2E40 kernel32.CreateRemoteThread $+108 757E0590 kernel32.OpenProcess $+10C 757F4B70 kernel32.SetEnvironmentVariableA $+110 757F3550 kernel32.GetEnvironmentStrings $+114 757E3390 kernel32.GetVolumeInformationA $+118 757D8FE0 kernel32.GetComputerNameA $+11C 757E16C0 kernel32.GetVersionExA $+120 757E0BE0 kernel32.GetStartupInfoA $+124 757D83E0 kernel32.GetSystemDirectoryA $+128 757E34C0 kernel32.SetFilePointerEx $+12C 757E32D0 kernel32.GetFileSizeEx $+130 757E32E0 kernel32.GetFileTime $+134 757E34D0 kernel32.SetFileTime $+138 757E22D0 kernel32.ExpandEnvironmentStringsA $+13C 757E30C0 kernel32.DeleteFileA $+140 757E34B0 kernel32.SetFilePointer $+144 757E09C0 kernel32.GetModuleHandleA $+148 757E0DB0 kernel32.GetModuleHandleW $+14C 757E07C0 kernel32.GetEnvironmentVariableW $+150 757E1E40 kernel32.GetCommandLineA $+154 757D9420 kernel32.GetExitCodeProcess $+158 757E1850 kernel32.ResumeThread $+15C 757DF2F0 kernel32.GetSystemTimeAsFileTime $+160 757E2ED0 kernel32.CreateMutexA $+164 757D8AC0 kernel32.CopyFileA $+168 757E3060 kernel32.CreateDirectoryA $+16C 757E2040 kernel32.GetExitCodeThread $+170 757DF490 kernel32.LocalFree $+174 757DDF50 kernel32.WideCharToMultiByte $+178 75130000 "MZ?" $+17C 75155840 user32.wsprintfA $+180 76EE6640 ntdll.NtdllDefWindowProc_A $+184 7514F150 user32.RegisterClassA $+188 75155580 user32.CreateWindowExA $+18C 7516F7E0 user32.ShowWindow $+190 75161970 user32.UpdateWindow $+194 75150B90 user32.GetMessageA $+198 75167660 user32.TranslateMessage $+19C 75154EC0 user32.DispatchMessageA $+1A0 75159350 user32.CharUpperA $+1A4 751AD860 user32.MessageBoxA $+1A8 751527A0 user32.GetCursorPos $+1AC 751B7170 user32.FindWindowExA $+1B0 75167AF0 user32.GetWindowThreadProcessId $+1B4 75161ED0 user32.GetWindowRect $+1B8 751660B0 user32.ScreenToClient $+1BC 75162150 user32.GetClientRect $+1C0 75154080 user32.SetWindowTextA $+1C4 7516F040 user32.MoveWindow $+1C8 751525B0 user32.GetAsyncKeyState $+1CC 76580000 "MZ?" $+1D0 7659F620 advapi32.SetServiceStatus $+1D4 7659F8A0 advapi32.RegisterServiceCtrlHandlerW $+1D8 7659DF20 advapi32.OpenProcessToken $+1DC 765A3910 advapi32.LookupPrivilegeValueA $+1E0 7659EF80 advapi32.AdjustTokenPrivileges $+1E4 765B2990 advapi32.CreateProcessAsUserA $+1E8 7659E7E0 advapi32.InitializeSecurityDescriptor $+1EC 7659E7C0 advapi32.InitializeAcl $+1F0 765C45D0 advapi32.LookupAccountNameA $+1F4 7659E660 advapi32.AddAccessAllowedAce $+1F8 7659E640 advapi32.SetSecurityDescriptorDacl $+1FC 765A2840 advapi32.GetUserNameA $+200 75530000 "MZ?" $+204 755A59A0 msvcrt.sscanf $+208 755B94D0 msvcrt.memset $+20C 755B8CF0 msvcrt.memcpy $+210 755A4CB0 msvcrt.printf $+214 75596C30 msvcrt._beginthreadex $+218 755B9EF0 msvcrt.strtok $+21C 755B9E60 msvcrt.strstr $+220 755BF140 msvcrt._time64 $+224 7558C650 msvcrt.rand $+228 7558BAB0 "j\fh垜]u鐷\x11" $+22C 755774F0 msvcrt.malloc $+230 7558C680 msvcrt.srand $+234 75577310 msvcrt.free $+238 76E60000 "MZ?" $+23C 76EBE1D0 ntdll.RtlInitializeCriticalSection $+240 76E9E8E0 ntdll.RtlEnterCriticalSection $+244 76E9DE00 ntdll.RtlLeaveCriticalSection $+248 76EAF940 ntdll.RtlDeleteCriticalSection $+24C 746A0000 "MZ?" $+250 746A4FD0 wtsapi32.WTSEnumerateSessionsA $+254 746A53A0 wtsapi32.WTSQuerySessionInformationA $+258 746A24A0 wtsapi32.WTSFreeMemory $+25C 746A1930 wtsapi32.QueryUserToken $+260 75C60000 "MZ?" $+264 75DBA520 shell32.CommandLineToArgvW $+268 75E97CF0 shell32.ShellExecuteA $+26C 76250000 "MZ?" $+270 7626A1C0 shlwapi.PathFileExistsA $+274 73B20000 "MZ?" $+278 73DC1380 wininet.InternetOpenA $+27C 73DD8640 wininet.InternetQueryOptionA $+280 73DDA4B0 wininet.InternetSetOptionA $+284 73E27000 wininet.InternetConnectA $+288 73DD7D50 wininet.InternetReadFile $+28C 73DE2950 wininet.InternetCloseHandle $+290 73EC41F0 wininet.HttpOpenRequestA $+294 73DF00F0 wininet.HttpAddRequestHeadersA $+298 73E32B20 wininet.HttpSendRequestA $+29C 73DD9EE0 wininet.HttpQueryInfoA $+2A0 6F780000 "MZ?" $+2A4 6F7D19B0 dnsapi.DnsGetCacheDataTable $+2A8 6F7A1DE0 dnsapi.DnsFree $+2AC 6F793530 dnsapi.DnsQuery_W
最后初始化的是一个样本之后用到的数据,包括有创建文件的路径,自拷贝的路径等等。该函数是直接赋值,这里直接贴出内容。
$ ==> 00000201 $+4 00000066 $+8 00000002 $+C 00000065 $+10 00000001 $+14 0123A601 $+18 02D91248 "D:\\Tencent\\WeChat\\v7.0D:\\Tencent\\WeChat\\v7.0%ProgramData%\\Tencent\\WeChat" $+1C 02D92448 "CrashReporter.exe" $+20 02D92478 "%PUBLIC%\\unlimit.flag" $+24 76ED9800 "鶅}\x08" $+28 00000000 $+2C 02D924A8 "%PUBLIC%\\Documents" $+30 02D924D8 "new.doc" $+34 76EA0100 ntdll.76EA0100 $+38 02D924F8 "%ProgramData%\\WeiXinCR01" $+3C 02D92530 "%ProgramData%\\WeiXinCR02" $+40 00000001 $+44 00000014 $+48 00000064 $+4C 02D92884 $+50 02D928A8 "Microsoft Word" $+54 02D928B7 $+58 00000064 $+5C 0000000A $+60 01200200 $+64 02D925E8 "127.0.0..0.0.127192.168.172.16.0.0.0.0255.255.255.255" $+68 02D92640 ".testtest." $+6C 00000000 $+70 00000000 $+74 00000001 $+78 00000301 $+7C 0001D4C0 $+80 00000000 $+84 00000BB8 $+88 00026000 $+8C 00000000 $+90 02D92668 "WeiXinCrashReporter" $+94 02D92698 "https://start.firefoxchina.cnhttps://www.msn.cn/zh-cn" $+98 02D928BC "http://39.99.57.67/grief-seed/ribbon-mummy.dathttp://39.99.57.67/grief-seed/marmaid-conductor.dathttp://39.99.57.67/grief-seed/candle-knight.dathttp://39.99.57.67/grief-seed/gramphone-witch.dat" $+9C 00001388 $+A0 00007530 $+A4 00000001 $+A8 02D92982 "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.104 Safari/537.36 Edg/89.0.774.76" $+AC 02D92750 "###@@@WeiXin0099" $+B0 00000000 $+B4 00000000 $+B8 00000003 $+BC 02D92A06 $+C0 02D92A09 "0d8d26376410b051421adee91163c5a54a5d5095d47cdf05ae5351de7ba0a153382c6d5778c21726" $+C4 00EFF320 $+C8 00000FDC $+CC 00000000 $+D0 01200000 $+D4 00001F18 $+D8 00DEF000 $+DC 01200000 $+E0 00000000 $+E4 01200000 $+E8 00EFF390 $+EC 00001F18 $+F0 00001F18 $+F4 00001F18 $+F8 00DEF000 $+FC 00000000 $+100 00000000
这里可以看到存在协议头,域名和IP列表,但是网络工具并没有监测到,说明样本没有触发。
StartMainWork函数头首先是一个调用ExpandEnvironmentStringsA函数,来获取环境变量的值。
通过前面他初始化的字符串得知这里的环境值为 %PUBLIC%\unlimit.flag,因为这个文件一直不存在,所以紧接着的PathFileExistsA返回0,不进入代码块。
然后会对本层函数传进来的参数进行判断,因为打开程序时默认不带有命令参数,所以CmdLine为NULL。又因为上面判断flag文件不存在,所以对应的标志位也是为0。此处判断不成立。往下走会判断dataTable + 0x14位置的值是否为0。通过查看dataTable发现这个位置的值恒等于1,所以此处永远是执行的状态。
进行自我复制的操作。
因为v73在上面的if没有成立,所以此处的值为默认的1,故此处调用的后两个参数为1,0。然后整个WinMain的执行周期结束。明显关键函数为**CopySelf_And_DoWork**,接下来进入分析。
2、CopySelf_And_DoWork分析
进入函数后首先会调用一个popMsgBox_DnsCache,这个函数的是跳出一个信息框然后进行DNS一些查询操作。这里注意的是ida在这里显示的是+0xE的作为参数一,可是在我调试的时候发现这里实际上是0x38,具体原因我也不清楚。
现在继续往下看看还会执行什么。
这里会执行两个函数,通过动态调试得知这里的参数内容如下:
pathTable = "D:\\Tencent\\WeChat\\v7.0D:\\Tencent\\WeChat\\v7.0%ProgramData%\\Tencent\\WeChat"
ExistPath = 上面一个函数创建new出来的地址,用于此处函数的out_buffer。
经过内部分析,这个函数作为为对参数一的路径以\r分割,然后逐一判断其是否存在,存在则返回到ExistPath。参数三的作用则是,传入0就直接判断路径是否存在,不创建。
传入1就是如果路径不存在则创建。因为本人虚拟机只有C盘,故只有第三项路径存在C:\ProgramData\\Tencent\\WeChat。如果所有路径都不存在,则本层函数直接返回,反之执行下列代码。
图片可能有点乱,大概的流程就是:
if ( GetModuleFileNameA(0, ¤tPath, 259) )// 获取当前运行时的路径 { if ( lstrcmpA_0(&_crashReportPath, &_currentPath) )// 比较两个文件运行的路径 { // 这里是路径不相同时的处理 if (CopyFileA(¤tPath, &CrashReportPath, 0) )// 将自身拷贝到目标路径 { if ( PathFileExistsA(&CrashReportPath) ) { wsprintf(&v226, &v395, &CrashReportPath); //运行拷贝后的程序 if ( CreateProcessA(0, &v226, 0, 0, 0, 0, 0, 0, &lpStartupInfo, &lpProcessInformation) )// shellCmd { CloseHandle_0(lpProcessInformation); CloseHandle_0(v10); } } } } else { CrashReportMain((int)dataTable, a2, ExistFlag);// CrashReport.exe 执行的函数 } }
然后本层函数也执行完毕。现在就开始分析重中之中,CrashReportMain。
3、CrashReportMain分析
首先在x32dbg进行头部下断,查看一下该函数的参数。
参数1 = dataTable
参数2 = 1
参数3 = 0
函数进来后依旧是popMsgBox_DnsCache函数的调用,这个函数基本上不影响样本功能,所以略过。
到了这里虽然里面有一个sub_511EB0函数,但是这个条件没有成立,0x24的位置始终为0。这里先继续往下走。
这里的话是判断互斥体名称是否存在,存在的话创建一个互斥体。
往下继续分析,会看到这里会对上面的错误码进行判断,而且判断的文本为Error : A downloader instance is already,由此可知,这个当前分析的这个CrashReporter样本可能是一个下载器。
往下执行会执行到这个条件,因为这个sub_4C7B40内部存在sleep函数,单步跳过时会直接卡住。如果返回为true,则会执行下面的sub_5176C0,然后本层函数直接结束掉。这里就直接从sub_4C7B40开始分析。
4、sub_4C7B40分析
首先执行第一个函数,CheckMachineIsMatch_FileExist,这个函数会检测是否存在文件C:\ProgramData\WeiXinCR02文件,该文件存放着C盘序列号的CRC32值。
如果文件不存在则函数返回0,如果存在则获取C盘的序列号并且计算出CRC32值,然后与文件里的值对比,是否一致。这里由于文件是不存在的,所以返回的为0,下面0x40是1,条件成立直接执行下面的延迟函数。
safeSleep通过isMatch来计算需要延迟的时间,如果isMatch == true,则延迟2分钟,反之延迟4分钟。
这里因为为了分析直接先nop掉这个函数执行,并且让返回值为1。
包括这里isMatch也是0,所以这里也需要改。
紧接着执行这个位置,函数直接返回1。
由于没有文件WeiXinCR02,所有在函数尾部,创建了该文件,并写入了C盘序列号的CRC32值,最后也返回1。这个函数整体大概是通过判断文件WeiXinCR02的存在来进行样本休眠,具体的作用可能为了躲过某些检测。
5、sub_5176C0分析
进入头部直接是一个urlList的初始化:
往下走会执行一个Requst函数,会对每一个url进行一个get请求和读取源码,之后会执行一个sub_5071A0,这里没分析出具体是干嘛。看到一些加密或解密的函数,估计是在读取网页源码的时候对内容进行解密然后形成shellcode?
网页操作执行完后,就是文件下载了,这里通过查看数据表发现这里的字符串都是以url\filename的形式存在,然后下面只有一个函数在执行,所以这里基本上可以确定下面的函数是下载这些文件,在这里本人自己去访问一下这些地址,发现已经无法访问了,所以无法下载,这估计也是为什么该样本运行时只有一个信息框弹出,就没有其它反应了,这里进入downloader进行分析。
6、downloader分析
函数头的字符串提示更加确定这是一个下载函数。
往下走后会看到样本使用了strtok进行文本分割,然后传入startDownLoad开始下载,下载同时会记录下载的个数,当所有下载完成后,本层函数返回下载的总数。这里注意下上面CrashReporter函数中我少了一个函数没分析,那个函数其实就是这个函数。
7、startDownLoad分析
这里定义了一个结构体变量,newMem_8,结构体内容如下:
struct unkown { int* urlField; int* dataTable };
之后会根据参数3进行启动不同的下载线程。
8、sub_50C610分析
因为文件已经无法下载了,所以sub_5071A0本人没法继续跟入分析,因为此函数里,基本都是对下载的文件进行image,section校验,且对部分数据进行处理。由于没拿到原文件,所以本人无从下手。
9、openDoc_download分析
首先是对保存路径初始化。
然后是对文件路径判断是否存在,如果存在则打开改文档。
否则进行下载。
下载完成后创建路径,然后再去打开文档。同样,由于文档当前也是无法下载的,导致没法进一步分析。
至此,该样本分析基本完了,且据目前分析结果来看,该样本也就是个下载器。
四 总结
长达一周的分析,终于结束了。因为该样本添加了很多的垃圾代码,导致分析的时候需要翻来翻去,有些关键代码又夹在里面,分析的时候甚是头疼,并且还有很多没用的条件,看起来觉得它会执行,但是动态调试的时候就是不会执行。
又因为该样本的下载文件地址全部失效,把样本跑起来后整个虚拟机啥问题都没有,监视工具也压根就没啥反应,压根就是一个正常的程序,这更增加了分析难度。刚刚开始接手这个样本时,运行起来和正常程序没什么区别,完全没有找到有关恶意代码的地方,最后被迫无奈,直接从WinMain手撕。整个样本,共106个函数,天天24小时肝。
由于压根就没看到有什么恶意代码,使得产生自我怀疑,甚至产生了放弃的念头,但是想到已经分析那么多了,还不如坚持下去。最后把70多个函数全部给分析了,然后已经看出了个大概,其余的就没分析,只看了个流程。
