系列往期:
[原创]默默无闻·恶意代码分析Lab1
[原创] 默默无闻·恶意代码分析Lab5
[原创]默默无闻·恶意代码分析Lab6
[原创]默默无闻·恶意代码分析第七章
[原创] 默默无闻·恶意代码分析Lab7
工具说明
参照书籍:《恶意代码分析实战》;
文件来源:文件来源:官网随书文件、或者附件中(文件密码:apebro
);;
使用工具:WinHex、CFF、StudyPE+、Exeinfo PE、Resource Hacker、Depends Walker、OD、IDA、MSDN;
摘要
本章节(OllyDbg)顺承第八章(动态调试),所以本章节以OllyDbg
工具为主,IDA Pro
工具为辅的形式进行分析;
由于OllyDbg
显示的更紧凑,但是IDA Pro
显示的更好理解;所以我们整体截图一般使用OllyDbg
视图,细节截图我们一般使用IDA Pro
视图;
Lab9-1
用OllyDbg
和IDA Pro
分析恶意代码文件Lab09-01.exe,回答下列问题。在第三章中,我们使用基础的静态和动态分析技术,已经对这个恶意代码做了初步分析;
Lab9-1分析
1、首先查壳;
无壳;
2、拖进IDA Pro
查看导入函数;
太多,就不截图了。但是我们可以看见很多特性函数,比如:
综上判断这个程序可能出现的操作涉及注册表以及注册服务工作,目的是进行网络传输一些数据;
3、shift+f12
快捷键查看字符串;
command.com
和cmd.exe
这种敏感指令;
.com
、.bat
和.cmd
这种有趣的文件后缀;
http/1.0\r\n\r\n
和GET
这种网络关键字;
- 一个网址
http://www.practicalmalwareanalysis.com
;
%SYSTEMOOT%\\SYSTEM32\\
敏感路径;
更加确认了有网络行为,并且有远程指令的嫌疑;
4、分析行为;
- 使用
IDA Pro
查看main()
入口,再使用OllyDbg
跟踪;
- 再使用
OllyDbg
打开Lab09-01.exe
文件;
很奇怪,这里却是0x00403896
开始,是不是很奇怪?这里我们看一下右下角的调用栈:
是的,这个状态还不是真正的Lab09-01.exe
的main()
入口,还处在内核初始化环节;
- 所以,我们使用
F8
(step-over),一路跟到call 0x00102AF0
指令;
如果发现跟的太快跑飞了程序,我们可以使用Ctrl+F12
组合键重新开始;
- 我们使用
F7
(step-into)进入Lab09-01.exe
;
这就是进入了;
首先,我们可以看到,再0x00402AFD
处是一个CMP [ebp+argc] , 1
指令(关于这个指令分析看往期帖子),这个指令是判断main()
函数参数是否等于1,不等于1
则跳到loc_402B1D
处;
- 因为我们没有添加参数,所以进入
sub_401000
处;
此处OllyDbg
显示的更紧凑一些;
此处,我们可以清晰的看到,这个函数中使用了注册表相关的函数(使用MSDN查看详情);
1 2 3 | RegOpenKeyExA(...); / / 打开指定的注册表键;
RegQueryValueExa(...); / / 检索与打开的注册表项关联的指定值名的类型和数据;
CloseHandle(...); / / 关闭句柄;
|
可以看见,在打开注册表键函数中,使用的是HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft \XPS
键;
因为我电脑的注册表项不存在,所以RegOpenKeyExA
返回0x2
;
注意,失败并不是返回0
,详情查阅MSDN,这里是使用跟OD
的方式;
- 继续往下走判断
test eax,eax
,很显然,并不符合;所以只能跳转loc_401066
;
退回0x00402B08
处;
因为在返回前执行了xor eax,eax
,所以eax
处是0
,执行test eax,eax
后,执行跳转loc_402B13
,执行call sub_402410
处;
首先看第一个GetModuleFileNameA
函数,获取当前可执行文件的路径;
因为是栈内空间,所以只有执行了,我们才能知道是啥,直接OD看内存;
使用方法是先找到ebp地址
,再减去208
;
要说明的是,这个程序中使用了ebp
获取参数,所以我们有理由相信这是debug
版编译的程序;
如果是esp
存取参数,同理寻址;
- 继续向下运行,是一个
GetShortPathNameA
函数(更多细节查看MSDN),这里简单理解是截取路径字符串;
1 | GetShortPathName(...); / / 获取指定输入路径的短路径形式。
|
- 继续往下是一连串的位操作,直接使用
OD
跳过细节,看最近跳转函数ShellExecuteA
前;
很显然,这是一个通过构造的字符串加上cmd.exe
程序删除Lab09-01.exe
(也就是删除自我)的程序;
- 下面,我们就要给
Lab09-01.exe
传入参数,使它能够在运行,至少有两种方式;
- 是给定足够参数,满足
0x402AFD
处的检查;
- 修改检查注册表项的代码路径;
直接修改路径很难判断是否会对后续运行造成影响,所以先使用给定参数的方式;
- 那么问题来了,我们应该输入什么参数呢?还是得去
IDA
看看反汇编;
从main()
函数往下看,我们会看到_mbscmp
函数,这是一个字符串对比函数;
在_mbscmp
函数的上面,我们可以找到固定的字符串(这还是相对简单的);
我们可以双击进去,就看到字符串是什么了;
由此可见,目前找到的这个程序的参数有四个,分别是-in
、-re
、-c
和-cc
;
先分析-in
此时假设已经有一个参数传进去了;
此时我们发现程序跳转到了sub_402510
:
我们同时使用OD
动态跟踪;
- Debug->Arguments,然后在出现的对话框中输入
-in
;
- 此时,我们在
OD
中跟到call sub_402510
,暂时不知道这个函数功能;
我们来观察一下传入的参数;1 | ![image - 20210413201158399 ](https: / / imagesubmit001.oss - cn - beijing.aliyuncs.com / img / image - 20210413201158399.png )
|
此时我们有两个选择,一个是直接跳过(step-out),但是令人担心的是会不会跑飞;
还有一个就是进去分析一波;
- 我们可以点进去看一下,
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 | int __cdecl sub_402510( int a1)
{
int result; / / eax@ 2
char v2; / / [sp + 4h ] [bp - 4h ]@ 5
char v3; / / [sp + 4h ] [bp - 4h ]@ 7
if ( strlen((const char * )a1) = = 4 )
{
if ( * (_BYTE * )a1 = = 97 ) / / a
{
v2 = * (_BYTE * )(a1 + 1 ) - * (_BYTE * )a1; / / b
if ( v2 = = 1 )
{
v3 = 99 * v2;
if ( v3 = = * (_BYTE * )(a1 + 2 ) ) / / c
result = (char)(v3 + 1 ) = = * (_BYTE * )(a1 + 3 ); / / d
else
result = 0 ;
}
else
{
result = 0 ;
}
}
else
{
result = 0 ;
}
}
else
{
result = 0 ;
}
return result;
}
|
很显然,这是一堆位操作,我们根据函数可以很简单的判断出这是一个密码验证函数:
1 2 3 4 | * (_BYTE * )a1 = a;
* (_BYTE * )(a1 + 1 ) = b;
* (_BYTE * )(a1 + 2 ) = c;
* (_BYTE * )(a1 + 3 ) = d;
|
最终得到密码是abcd
;
在这里,我们选择的是直接跳过(step-out);
很显然,因为我们传入的并不是abcd
,所以返回值是0
;
我们发现因为返回值eax
是0
,所以并不跳转,而是直接运行了sub_402410
(这是自我删除函数);
- 所以为了能够通过检查,我们直接在
OD
中的eax
寄存器处双击修改成0x00000001
;
然后就成功跳转0x00402B3F
;
这里要提一句的是,修改要在test eax,eax
语句之前,因为jnz
判断是根据标志寄存器;
我们可以看到,函数的返回值eax
;
我们可以通过OD
来查看传入_mbscmp
函数的参数(这个是IDA
经过特征数据库识别的,虽然我不清楚识别的依据,但是在OD
中没有显示出函数名,我就觉得大概率还不是原型函数);
还是正常跳过查看返回值;
我们发现eax
返回的是0x01300000
,有点奇怪,但是不打紧,我们继续往下跟;
OD
继续往下跟,跳转到了sub_4025B0(IDA)
也就是0x004025B0(OD)
;
我们可以看到,这里获取了当前文件路径,并且截取了路径名,这大概就是sub_4025B0(IDA)
函数的功能;
通过跳转,我们可以看见一个熟悉的字段lpServiceName
,所以我们可以猜测sub_402600
是一个服务相关的函数;
这里我猜测Lab09-01
就是之前截取的字符串;
首先是__alloca_probe
应该是一个注册内存相关的函数,我们暂时不用管;
sub_4025B0
根据上面的经验是一个获取文件路径并且截取的函数;
经过跳转,跳到了loc_402632
函数;
在这个函数中,我们发现,有%SYSTEMROOT%\system32\
字符串;
- 再往下就是一堆字符拼接操作,我们直接使用OD跳过,至
0x004026CC
处;
我们发现OpenSCManagerA
函数传入的三个参数都是立即数,根据第七章中的知识总结[[原创]默默无闻·恶意代码分析第七章 ],我们知道它返回一个服务控制管理器句柄,所有要和服务交互的代码会调用这个函数;
所以我们对于这种已经确定的官方API可以跳过(但是我们可能需要知道传入的参数);
下面是一个OpenServiceA
函数,这是打开一个服务,我们需要看一下栈,看看要打开的是什么服务;
是这个程序;
我们发现,直接跳过了跳转,来到了ChangeServiceConfig
函数,这个功能用于修改服务的配置参数,我们来看一眼传参;
再往下,我们就发现跳出了sub_402600
函数;
- 继续往下跟,我们发现从
jmp short loc_402BC2
跳转到jmp loc_402D76
,直至跳出main
函数结束;
到此我们可以判断-in
参数是安装服务功能;
这里有一个细节,貌似我们并没有看见CreateService
函数呀,怎么就创建服务了呢?
我们再返回IDA
查看,在OpenService
函数的打开函数下方,有一个判断:
也就是说,如果没有打开,我们就进入loc_40277D
函数进行创建服务;
那为什么我没有进入这个函数呢?主要是因为这个程序我反复的单步太多遍,导致已经有一个叫做Lab09-01
的服务了;
这也就是为啥,每次都能打开的原因了;
至此,对于-in
参数的功能分析到此结束;
再分析-re
- 先将
OD
中调试->参数,修改传入参数,然后再Ctrl+F2
重启服务;
- 单步至
call 00402AF0
进入主程序开始跟;
依旧是一个三步走然后进行密码检验,细节看上面对-in
的分析;
- 找到了
sub_402900
函数,用IDA
打开看细节,除了上一个看到的,又看到了DeleteService
,所以我们初步判断这是一个服务删除功能;
在之前的位置已经看不到Lab09-01
服务了;
到此,对于-re
参数功能的分析已经结束,我们知道这是删除服务功能;
分析-c
参数;
在此之前,为了我们后续的分析,我们要先将服务安装上去;
切不可在退出OD
、IDA
后用命令行安装,别忘了,它还有自我删除的行为;
- 找到
00402AF0
使用F7
步入,至sub_402510
密码校验结束;
使用OD
可以看到,这里大概就是-c
参数开始的程序段了;
在这里,我们可以看到又四个地址跳转,我们需要一个一个的来看;
- 首先
mbscmp
函数我们传入的值应该两个都是-c
;
返回值为:
所以0x00402CD9
并不跳转;
下一个是cmp [ebp+argc],7
,这里是判断参数是否为七个,
这里轻微的反汇编一下就是:
1 2 3 4 5 6 | if (argc ! = 7 ){
loc_402CCF();
}
else {
sub_401070(argv[ 2 ],argv[ 3 ],argv[ 4 ],argv[ 5 ]);
}
|
首先,我们要过去看看loc_402CCF
是什么;
根据上面的经验,即将跳转的是sub_402410
函数是自我删除函数,这显然是不正常的;
- 所以,我们需要再去看看
sub_401070
函数都做了什么;
貌似是有些功能,那么我们先给OD
添加上足够数量的参数;
在这里,有小伙伴问了,这-c
、q
、w
、e
、r
、abcd
这不是六个么
这里要说一下main()
函数的参数判断了:
1 | int main( int argc,const char * * argv);
|
如果我们使用命令行输入Lab09-01.exe -c abcd
;
那么参数的对应值应该是argc=3,argv={"Lab09-01.exe","-c","abcd"}
;
你明白了吗?(<-__<-)
- 重新跟至
00402C4F
处;
- 开始单步,就可以顺利的单步下去,直到
sub_401070
;
F7
进入sub_401070
函数;
首先是一连串的字符串拼接,直接F5
看过程;
1 2 3 4 5 6 7 8 9 10 11 | memset(&Data, 0 , 0x1000u );
v7 = 0 ;
v8 = (char * )&Data;
strcpy((char * )&Data, a1);
v8 + = strlen(a1) + 1 ;
strcpy(v8, a2);
v8 + = strlen(a2) + 1 ;
strcpy(v8, a3);
v8 + = strlen(a3) + 1 ;
strcpy(v8, a4);
v8 + = strlen(a4) + 1 ;
|
再继续往下看RegCreateKeyExA
函数,在以前的经验中,我们直到这个函数是用来创建指定的键,如果该键已经存在于注册表中,函数将打开它;
我们通过IDA
可以看见,SOFTWARE\\Microsoft \\XPS
字符串被传进参数;
因为我使用了52pojie
官网上的OD
,插件比较多,所以可以直接在栈内看到清晰的参数;
- 不跟
RegCreateKeyExA
函数细节,直接F8
往下走;
我们可以看到,这里有一个RegSetValueExA
函数,用来设置注册表键值,我们需要关注一下,传入的参数是什么;
两者相差无几;
最后一路F8
跳出sub_401070
函数
紧接着jmp short loc_402CD4
跳到jmp loc_402D76
跳出程序执行;
- 打开注册表
HKEY_LOCAL_MACHINE\SOFTWARE|Microsoft\XPS
可以看到:
而我们输入的参数qwer
就是键值的数值;
到此,对于-c
参数功能的分析已经结束,我们知道这是设置注册表配置键;
分析-cc
参数;
- 先将
OD
中调试->参数,修改传入参数,然后再Ctrl+F2
重启服务;
- 找到
00402AF0
使用F7
步入,至sub_402510
密码校验结束;
- 继续一路跟下去,跳到
loc_402BC7
;
- 进入
loc_402CD9
,我们发现这里匹配的就是参数-cc
了,到这里我们就要一步一步来了;
- 继续向下走到
call 00401280
,在上面的两次跳转没有跳出去,那这个应该就是功能函数了,所以进去看看;
感觉貌似跟-c
没有什么区别,我们使用OD
单步一下看看;
- 我们发现跳转到
loc_401309
函数,概览一下,貌似又是字符串拼接;
快速通过后,直接看函数返回后的状态;
- 传入的参数很显然就是拼接后的字符串,我们再看
offset aKSHSPSPerS ; "k:%s h:%s p:%s per:%s\n"
,这个很像printf()
函数的格式化;
- 我们进入
sub_402E7E
看看细节;
果然跟我们之前看的printf
函数很像;
我们用命令行输出一下确认看看;
到此,对于-cc
参数功能的分析已经结束,我们知道这是设置注册表配置键打印出来;
对于无参分析
还记得开头我们分析到了sub_401000
,在其中有一个判断是否能找到注册表项;
所以从loc_401066
跳出sub_401000
;
F5
反汇编看一下;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | signed int sub_402360()
{
int v1; / / eax@ 5
char v2; / / [sp + 0h ] [bp - 1000h ]@ 1
char v3; / / [sp + 400h ] [bp - C00h]@ 1
char name; / / [sp + 800h ] [bp - 800h ]@ 1
char v5; / / [sp + C00h] [bp - 400h ]@ 1
while ( 1 )
{
if ( sub_401280(&v3, 1024 , &name, 1024 , &v2, 1024 , &v5) )
return 1 ;
atoi(&v2);
if ( sub_402020(&name) )
break ;
v1 = atoi(&v5);
Sleep( 1000 * v1);
}
return 1 ;
}
|
可以看见,这是一个死循环;
其中通过sub_401280
和sub_402020
还有一个在反汇编中没有体现的loc_402408
跳出循环;
此处,我们主要把前两个函数理清楚;
sub_401280
:
首先第一个注意到的函数是RegOpenKeyExA
它的功能是打开指定的注册表键;
- 因为正常打开,所以返回了
0
;
- 我也挺奇怪的,查了MSDN后发现;
1 | / / 如果函数失败,返回值为WINERROR.H中定义的非零错误代码。
|
所以返回0
反而是正常的;
使用OD
继续往下跟就是RegQueryValueExA
函数,它的功能是检索与打开的注册表项关联的指定值名的类型和数据;
- 同样跟上面的函数一样,如果失败则返回非
0
错误代码;
- 所以因为获取成功,所以返回了
0
;
再往下跳转到了loc_401309
处,依旧是我们喜闻乐见的字符串操作,直接跳过看代码;
1 2 3 4 5 6 7 8 9 | v12 = &Data;
strcpy(a1, (const char * )&Data);
v12 + = strlen(a1) + 1 ;
strcpy(a3, (const char * )v12);
v12 + = strlen(a3) + 1 ;
strcpy(a5, (const char * )v12);
v12 + = strlen(a5) + 1 ;
strcpy(a7, (const char * )v12);
v12 + = strlen(a7) + 1 ;
|
我们可以判断,就是把注册表中的键值依次取出;
sub_402020
:
首先遇见的是sub_401E60
处函数,进去瞅瞅;
跳过第一个__alloca_probe
函数就看到sub_401420
,F7
进去看一下;
- 我们就看到一个熟悉的
sub_401280
函数,我们知道它是获取注册表键值的;
- 往下跳转到
loc_401468
,直至跳出;
跳转到loc_401E9B
;
行至sub_401470
函数,F7
进去看一下;
- 又是一个熟悉的
sub_401280
,,我们知道它是获取注册表键值的;
- 往下跳转到
loc_4014C0
,直至跳出;
跳转至loc_401EB8
;
行至sub_401D80
函数,F7
进去看一下;
先是_time
函数获取系统时间,因为下面用到了rand
函数,所以这个应该是获取随机数种子;
sub_4030E0
函数是给0x0040C180
处赋值0x6077BBD4
;
跳转至loc_401DB1
;行至sub_401D10
函数,F7
进去看一下;
1 2 3 4 5 6 7 8 9 10 11 12 13 | char sub_401D10()
{
char v1; / / [sp + 0h ] [bp - 4h ]@ 1
do
{
v1 = rand();
if ( v1 > 127 )
v1 - = 127 ;
}
while ( (v1 < 48 || v1 > 57 ) && (v1 < 97 || v1 > 122 ) && (v1 < 65 || v1 > 90 ) );
return v1;
}
|
很显然,判断条件就是随机出来的v1
(不是数字&&不是小写字母&&不是大写字母);
因为是随机的,所以看看后面是干啥用的,F8
直至跳出;
所以我们仍然使用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 | int __cdecl sub_401D80(char * a1)
{
time_t v1; / / eax@ 1
signed int i; / / [sp + 0h ] [bp - 8h ]@ 1
signed int j; / / [sp + 0h ] [bp - 8h ]@ 4
signed int k; / / [sp + 0h ] [bp - 8h ]@ 7
char * v6; / / [sp + 4h ] [bp - 4h ]@ 1
char * v7; / / [sp + 4h ] [bp - 4h ]@ 4
char * v8; / / [sp + 4h ] [bp - 4h ]@ 7
v1 = time( 0 );
sub_4030E0(v1);
v6 = a1;
for ( i = 0 ; i < 4 ; + + i )
* v6 + + = sub_401D10();
* v6 = 47 ;
v7 = v6 + 1 ;
for ( j = 0 ; j < 4 ; + + j )
* v7 + + = sub_401D10();
* v7 = 46 ;
v8 = v7 + 1 ;
for ( k = 0 ; k < 3 ; + + k )
* v8 + + = sub_401D10();
* v8 = 0 ;
return 0 ;
}
|
这里我们发现,这就是一个无聊的延时函数,所以我们直接使用F8
直至跳出;
跳转至loc_401ED7
;
行至sub_401AF0
函数,F7
跳入;
步进到sub_401640
,F7
跳入;
我们发现这是一个网络连接功能函数;
1 2 3 4 5 6 7 | WSAStartup(...);
gethostbyname(...);
WSACleanup(...);
socket(...);
htons(...);
connect(...);
closesocket(...);
|
既然知道函数功能了,我们就直接F8
跳出sub_401640
;
这里我们要说一下的是,因为我们电脑是断网环境,所以网络连接是失败的,下一步应该是直接跳出函数了;
此处,我们假设联网成功,使用静态分析;
跳转loc_401B35
,可以看到是一个Get
头;
然后协议是HTTP/1.0\r\n\r\n
;
-
然后是一个发送指令,根据上面参数的传递,是[ebp+buf]
,这个就是函数传入的参数,也就是我们之前获取的注册表配置信息;
如果发送成功,返回的应该是发送的字节数,所以失败就退出,否则跳转loc_401C18
;
在loc_401C18
函数中,出现了recv
函数,应该是从网络上获取了数据;
在这里,有一个判断回收长度是否小于等于0
,如果是,则跳转loc_401CAD
;
话说两头,左右我们都得看一下,我们直接使用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 | do{
v10 = recv(s, &v6, 512 , 0 );
if (v10 < = 0 ){
if (v10){
sub_401740(&s);
return 1 ;
}
continue ;
}
if ((unsigned int )(v10 + v8) > * (_DWORD * )a5){
sub_401740(&s);
return 1 ;
}
qmemcpy(&a4[v8], &v6, v10);
v8 + = v10;
if (strstr(a4, asc_40C068))
break ;
} while (v10 > 0 );
if (sub_401740(&s)){
result = 1 ;
}
else {
* (_DWORD * )a5 = v8;
result = 0 ;
}
|
这里我们可以看见,重点在于sub_401740
函数是做什么的,找到它,进去看一下;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | signed int __cdecl sub_401740(SOCKET * a1)
{
signed int result; / / eax@ 2
if ( shutdown( * a1, 1 ) = = - 1 )
{
closesocket( * a1);
WSACleanup();
result = 1 ;
}
else
{
closesocket( * a1);
WSACleanup();
result = 0 ;
}
return result;
}
|
很显然哦,shutdown
函数功能是关闭功能禁止在套接字上发送或接收;
F8
跳出sub_401740
;
行至跳转loc_401CFC
跳出sub_401AF0
函数;
假设获得了相应字符串,跳转loc_401F10
;
我们可以看见这是一个字符串匹配函数strstr
,以及即将跳转的loc_401F3D
函数;
再往下,就是sub_401E60
的结束了;
纵观sub_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 35 36 37 | int _cdecl sub_401E60(cahr * a1,signed int a2){
signed int result;
if (sub_401420(...)){ / / 获取注册表键并截取
result = 1 ;
}
else if (sub_401470(...)){ / / 获取注册表键
result = 1 ;
}
else if (sub_401D80(...)){ / / 无聊的延时
resutl = 1 ;
}
else if (sub_401AF0(...)){ / / 网络Get和recv
result = 1 ;
}
else {
if (a = strstr(...)){ / / 字符串匹配
if (b = strstr(...)){
if (b - a + 1 < = a2){
memcpy(a1 , ...); / / 复制
a1[...] = 0 ;
result = 0 ;
}
else {
result = 1 ;
}
}
else {
result = 1 ;
}
}
else {
result = 1 ;
}
}
return result;
}
|
以上就是sub_401E60
函数的功能,最主要的是从网络获取字符串;
如果sub_401E60
网络获取字符串成功,那么就进入loc_40204C
;
一路往下总揽一下,发现又是一个if - else if
结构,直接使用结构代码看情况;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | if (!strncmp(..., "SLEEP" ,...)){
...;
}
else if (!strncmp(..., "UPLOAD" ,...)){
...;
}
else if (!strncmp(..., "DOWNLOAD" ,...)){
...;
}
else if (!strncmp(..., "CMD" ,...)){
...;
}
else {
strncmp(..., "NOTHING" ,...);
}
|
很显然,这就是一个后门功能;
Lab9-1分析总结
到此,应该算是大体理清楚了,看了一眼书上答案,有点迷,感觉自己还是漏了点细节;
还有就是关于我们查看字符串,我们可以使用xrof
或者x
键进行交叉引用查询,然后一路向上反向操作,这样可以有目的性的查看网络或者其它功能;
问题:
1、如何让这个恶意代码安装自身;
-in
选项;
2、这个恶意代码的命令行选项是什么?它要求的密码是什么?
有四个命令选项,一个密码;密码是abcd
,四个选项分别是-in
、-re
、-c
、-cc
;
3、如何利用OllyDbg
永久修补这个恶意代码,使其不需要指定的命令行密码?
方法很多,重点是查看密码检查方式;
4、这个恶意代码基于系统的特征是什么?
使用了注册表项,以及安装服务;
5、这个恶意代码通过网络命令执行哪些不同操作?
五个SLEEP
、UPLOAD
、DOWNLOAD
、CMD
和NOTHING
;
6、这个恶意代码是否有网络特征?
一个网址www.practicalmalwareanalysis.com
和HTTP/1.0 GET
请求;
Lab9-2
1、用OllyDbg
分析恶意代码文件Lab09-02.exe,回答下列问题;
Lab9-2分析
1、首先查壳;
无壳,下一步;
2、检查导入表;
首先导入动态库有两个WS2_32
和KERNEL32
,可以看出主要还是网络操作,和一些敏感系统函数;
1 2 3 4 5 6 7 8 | connect(); / / WS2_32 网络连接;
gethostbyname(); / / WS2_32 从主机数据库中检索与主机名对应的主机信息;
WaitForSingleObject(); / / KERNEL32 多线程函数;
CreateProcessA(); / / KERNEL32 创建进程;
loadLibrary(); / / KERNEL32 动态库加载;
TerminateProcess(); / / KERNEL32 终止所有线程;
GetCurrentProcess(); / / KERNEL32 获取当前进程句柄;
WriteFile(); / / KERNEL32 写操作;
|
3、检查字符串
貌似暂时没看到什么有意思的敏感字符串;
4、分析行为;
- 先用
IDA
打开,找到main()
函数地址是0x00401128
,用OD
打开程序,找到相应点,设置软中断,一路跳到相应位置,开始分析;
首先看到一连串的立即数赋值,这个就很奇怪了,怀疑是硬编码,我们可以用OD
单步,然后找到内存位置,查看是什么;
这是两个字符串,一个是1qaz2wsx3edc
、一个是ocl.exe
;
再往下是一个又是一个字符串;
在这里,我们看见一个比较有意思的事情,这里是一个ComandLine[4]
的字符串数组,指向的的是另外一个字符串组;
而且我们看见了cmd
这个敏感命令;
继续OD
往下单步到GetModuleFileName
函数;
这是传参,直接跳过;
- 往下看是两个
__strrchr
和__strcmp
两个函数;
1 2 | strrchr(); / / 查找一个字符串在另一个字符串中 末次 出现的位置,并返回从字符串中的这个 / / 位置起,一直到字符串结束的所有字符;
strcmp(); / / 字符串匹配,没啥好说的;
|
按照这样的逻辑,可以得到反汇编框架:
1 2 3 4 5 6 7 8 | char * strexe = "olc.exe"
GetModuleFileNameA(...);
char * strchr(...);
if (!strcmp(strexe,strchr)){
while ( 1 ){
...
}
}
|
目前知道的是通过文件路径获取文件名,如果是ocl.exe
,则进入while(1)
的死循环;
而我们因为断网问题,所以需要在检查跳转处修改判断值;
修改了strcmp
的返回值eax
,就可以进入loc_0040124C
;
下面就是依次的调用WSAStartup
、WSASocket
,一路往下;
继续往下,又发现一个返回值检查,一个一个的改感觉有点麻烦,先整理一下静态反汇编框架;
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 | while ( 1 ){
if (WSAStartup(...))
return 1 ;
if (WSASocketA(...) = = - 1 )
break ;
char * name = (char * )sub_401089(...);
if (gethostbyname(name)){ / / 从主机数据库中检索与主机名对应的主机信息。
...
data[] = htons( 0x270 ); / / 0x270 是端口号: 9999
...
if (connect(...) = = - 1 ){
closesocket(...);
WSACleanup();
Sleep( 0x7530 ); / / 30 秒
}
else {
sub_401000(...);
closesocket(...);
WSACleanup();
Sleep( 0x7530 ); / / 30 秒
}
}
else {
closesocket(...);
WSACleanup();
Sleep( 0x7530 ); / / 30 秒
}
}
|
这样一看就很清晰了,我们只需要把重心放在sub_401089
获取了什么字符串,和如果网络链接成功sub_401000
做了什么;
进入sub_401089
是一堆位操作,功力不够,逆这个有点浪费时间,我直接用OD
看结果了;
所以在内存区找到[ebp-0x108]
的位置;
获得了解密字符串www.practicalmalwareanalysis.com
;
再进入sub_401000
看看到底做了啥;
还是老样子,先看函数框架;
1 2 3 4 5 6 7 8 9 | int _cdecl sub_401000( int a1, int a2, int a3, int a4 ,void * a5){
memset(&struct1, 0 , 0x44 );
...
memset(&struct2, 0 , 0x10 );
...
CreateProcessA(.., ComLine,..,&struct1,&struct2); / / ComLine是cmd
WaitForSingleObject(...);
return 0 ;
}
|
由此可见,两个memset
是设置两个结构体清零,IDA
中有识别的命名,这个有提示意义;
在CreateProcessA
运行之前对结构体进行了赋值;
Lab9-2分析总结
这个程序相较于第一个程序还是非常简单的。用书上答案来讲,这个程序创建了多个反向shell
;
还有关于一些关于CreateProcess
中参数的细节,在此就不再赘述,因为病毒分析对于程序功能更加关注;
问题:
1、在二进制文件中,你看到的静态字符串是什么?
找到的cmd
字符串;
2、当你运行这个二进制文件时,会发生什么?
死循环,网络连接成功后,多线程创建反向shell;
3、怎样让恶意代码的攻击负荷(payload)获得运行?
将程序命名为ocl.exe
;
4、在地址0x00401133
处发生了什么?
有两个字符串,一个用于最后解析出网址,一个用于解析出ocl.exe
,使程序实现运行;
5、传递给子例程(函数)0x00401089
的参数是什么?
选中一路网上找,可以发现,分别是1qaz2wsx3edc
和一个数据缓冲区;
6、恶意代码使用的域名是什么?
practicalmalwareanalysis.com
;
7、恶意代码使用什么编码函数来混淆域名?
使用字符串异或加密;
8、恶意代码在0x0040106E
处调用CreateProcessA
函数的意义是什么?
通过绑定一个套接字与命令行shell来创建反向shell;
Lab9-3
使用OllyDbg
和IDA Pro
分析恶意代码文件Lab09-03.exe。这个恶意代码加载3个自带的DLL(DLL1.dll
、DLL2.dll
、DLL3.dll
),它们在编译时请求相同的内存加载位置。因此,在OllyDbg
中对照IDA Pro
浏览这些DLL可以发现,相同代码可能会出现在不同的内存位置。这个实验的目的是让你在使用OllyDbg
看代码时可以轻松的在IDA Pro
里找到它对应的位置;
Lab9-2分析
因为题意说明,这并不是重在分析的题目,所以根据问题走;
问题:
1、Lab09-03.exe导入了哪些DLL?
使用OD
打开Lab09-03.exe
,点击View->memory查看;
或者直接使用Dependency Walker
;
导入表包括了DLL2
、DLL1
、netapi32
、kernel32
;
对照答案后,我发现还有动态加载的dll
这就找到了标准答案了;
2、DLL1.dll
、DLL2.dll
、DLL3.dll
要求的基地址是多少?
查看基地址可以直接拖WinHex
,这对于PE结构不熟悉的小伙伴不太友好,所以我是直接拖CFF
的;
3、当使用OllyDbg
调试Lab09-03.exe时,为DLL1.dll
、DLL2.dll
、DLL3.dll
分配的基地址是什么?
这个在OD
中,View->Memory,这里有一个问题是要先单步到加载DLL3.dll
;
4、当Lab09-03.exe调用DLL1.dll
中的一个导入函数时,这个导入函数都做了些什么?
使用IDA
打开DLL1.dll
,找到Exports
窗口,找到DLL1Print
,双击找到函数;
至于sub_10001038
,经过验证是一个打印字符串函数;
综上所述,是一个打印函数;
5、当Lab09-03.exe调用WriteFile
的函数时,它写入的文件名是什么?
所以我们还是得先去看看DLL2ReturnJ
返回了什么;
双击进去;
我们会发现这是一个未定义数据空间,我们摁x
查看交叉引用;
只有这个是赋值操作,双击进去;
找到了,所以返回的文件名是temp.text
;
6、当Lab09-03.exe使用NetScheduleJobAdd
创建一个job时,从哪里获取第二个参数的数据?
首先在Lab09-03.exe
找到NetScheduleJobAdd
;
往上找,谁交叉引用了[ebp+Buffer]
了;
去DLL3.dll
找到DLLGetStructure
瞅一瞅;
查看交叉引用;
点进去查看;
所以可以看见,ptr [eax], 36EE80h
(实际汇编是不允许内存直接存取立即数的,这里只是为了清晰直观),意思是在以buff
位置的值为地址存放0x36EE80
的偏移 ,并不是就是存放0x36EE80
;
这里要说一下的是,因为DLL3.dll
加载时如果发生了重定位,这个偏移是会动的;
7、在运行或调试Lab09-03.exe时,你会看到Lab09-03.exe打印处三块神秘数据。DLL1.dll
、的神秘数据,DLL2.dll
的神秘数据,、DLL3.dll
的神秘数据分别是什么?
都是以下的步骤:
1、先把dll
拖进IDA
;
2、导出函数(Exports
)找到打印函数;
3、双击进去,找到字符串;
4、摁x
找到赋值语句,向上找赋值源;
直接给出答案:
DLL1.dll
;
所以是当前进程标识;
DLL2.dll
;
是文件句柄;
DLL3.dll
;
找这个的时候遇到点以外,没有找到本来想找的mov
,但是转念一想,函数可以,所以找了push
,找到了;
MultiByteToWideChar
的功能是将一个字符串映射为宽字符(Unicode)字符串;
根据MSDN,可以看见最后是把ping www.malwareanalysisbook.com
的位置赋给了WideCharStr
;
8、如何将DLL2.dll
加载到IDA Pro
中,使得它与OllyDbg
使用的加载地址匹配?
不匹配,但是可以手动调整加载到IDA Pro
中的地址;
Lab9-3分析总结
这个题目相对简单,需要注意的就是关于PE中的重定位问题;
至于找东西,有两种思路:
1、分析就是自上向下看过程;
2、做题就是自下往上找调用,找变动;
总结
这一篇写了四五天了,期间是各种突发情况,眼看月底近了,真的是心急如焚,希望能抓紧写完,抓住银四的尾巴找到工作吧;
这三题,第一题写到很细,分析的有点因小失大,拘泥于字眼之间了;
最后用两句诗句与君共勉吧;
行路难,行路难,多歧路,今安在!
长风破浪会有时,直挂云帆济沧海。
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
最后于 2021-4-17 00:18
被平头猿小哥编辑
,原因: