您的位置:首页 > 编程语言

恶意代码分析实战 Lab 7-3 习题笔记

2017-09-30 14:06 1681 查看

Lab 7-3

问题

1.这个程序如何完成持久化驻留,来确保在计算机被重启后它能继续运行?

解答: 我们还是先开始按静态分析来开始我们的分析(这里同时要分析
.dll
.exe




我们会发现这里有一个
CreateFileA
CopyFileA
这两个函数,说明会创建一个文件和复制一个文件,这个创建文件可能会是什么日志之类的,复制文件可能是把病毒复制到某个地方

然后是
FindFirstFileA
FindNextFileA
这两个函数,说明这个函数会在系统中查找什么文件

然后
CreateFileMappingA
MapViewOfFile
告诉我们,这个程序会打卡一个文件,然后将它映射到内存中

但是在导入表中我们并没有发现
LoadLibrary
或者
GetProcAddress
,说明这个函数并没有在运行的时候加载这个
DLL


然后看看字符串



这里指示了一个路径
C:\\windows\\system32\\kerne132.dll
,然后这里的确会很迷惑人,书中不说我都没看见~

.data:0040304C 00000021 C C:\\windows\\system32\\kerne132.dll


这里注意是
132.dll
,为了方便解释,最后那三个是数字
132


我们再来分析一下这个
dll
文件

还是先从导入表开始看起



这个
dll
文件会创建和打开一个互斥变量,也就是这个函数
CreateMutexA
OpenMutexA


然后还会创建进程
CreateProcessA
这个函数

最后还会调用
Slee
函数来休眠

其他函数没什么价值,我们分析分析字符串



这里有个
exec
这个字符串,有可能这个程序是个后门程序,只是猜测,然后还有个
127.26.152.13
这个
ip
,看到这个
ip
就很有可能是个后门木马了

然后我们继续书中的介绍,来找一下导出函数



标黄那里就是导出函数所在的地方,先点选
LAB07-03.DLL
,然后再查看黄色标注的这里,如果有导出函数,这里就会显示,但是这里并没有

然后便是开始
IDA
分析,因为动态分析书上并没有得出什么结论,我们也不浪费什么时间了,直接开搞

我们根据书中的步骤

[b]我们开始分析
dll
这个文件
[/b]

书上的分析方法是直接只列出
call
语句的代码,然后这样分析,我们还是老规矩,毕竟也不是很长的代码,我们还是先一条一条分析下去看看



一开始是调用了这个
__alloca_probe
这个函数,这个函数是用来在空间中分配栈空间的函数,然后这个函数的入参是
11F8h
也就是
4600d
,然后我们继续往下看,
IDA
fdwReason
的值赋值给了
eax
,这里的
[esp+11F8h+fdwReason]
这里说明已经将
[esp+11F8h]
这个地方分配出去了

最后我们将返回值和
1
比较大小,如果不等于
1
呢,则下面的
jnz
跳转执行,
jnz
跳转之后就马上执行了返回,所以这个代码是希望这个
eax
也就是
fdwReason
是等于
1


然后我们继续分析主干



这里我们是先将这个
byte_10026054
赋值给
al
,我们来看看这个
byte_10026054
是个啥



db
是申请一个字节然后,后面的
0
代表了存储的数据

所以这里是将
al
置为
0
的意思

然后呢将
al
也就是
0
存入
[esp+1208h+buf]
的位置之后,将
eax
置为
0
,然后就是用
OpenMutexA
打开了一个叫
SADFHUHF
的互斥量,然后查看调用结果,如果结果
eax
0
了,
jnz
不跳转,反之如果不为
0
了,
jnz
跳转



MSDN
里面写明了这个
OpenMutexA
的返回值,逻辑上归纳一下就是,如果调用失败,返回
NULL
,在计算机中也就是
0
jnz
不会跳转,继续执行代码,反之如果调用成功,则
jnz
跳转,跳转之后我们顺着箭头可以看到是结束执行了

所以这是判断当前系统中是否有相同程序的作用,一个系统中只能运行一个这个程序



然后如果
OpenMutexA
调用失败,执行上面这段代码,也就是没跳转之后执行的代码

这里是调用
CreateMutexA
来创建一个叫
SADFHUHF
互斥量,然后在调用
WSAStartup
这个函数,这个函数是干什么的呢



这个函数是
Windows异步套接字启动命令
,从
MSDN
中我们可以分析这个函数 入参有哪些





IDA
的标注中我们可以看到,这个函数是这样的入参模式

wVersionRequested
的值是
202h
,而
lpWSAData
的值是
ecx
,我们继续分析这个入参会做什么



这里说,这个
wVersionRequested


调用者可以使用的最高版本的Windows Sockets规范。高位字节指定次要版本号;低位字节指定主版本号

那根据这个入参
202h
换算成二进制就是
‭0000,0010,0000,0010‬
,分成高字节和低字节之后就是
(00000010, 00000010)
也是就
(2.2)


所以这里指定的套接字版本是
2.2


而这个
lpWSAData


指向WSADATA数据结构的指针,用于接收Windows Sockets实现的详细信息

这里的返回值是这样的



如果成功了,返回
0


逻辑上总结一下就是,
WSAStartup
之后,返回值经过那个
test
之后,如果成功,返回
0
,然后
jnz
不跳转,反之如果不成功了,跳转结束程序

然后我们走主干,假设调用成功了,程序就会来到这里执行这个,这里有几个不明确的值,但是
IDA
已经标注出来这个什么类型的



这里我们可以根据以前的分析方法,右键选择这个值所代表的类型,然后把他替换成能看懂的代码



下面就是我替换之后的代码



这里是初始话了一个
TCP
INET
连接,然后将返回值赋值给
esi
,之后和
0,FFFF,FFFFh
进行比较

这个值换算成十进制之后就是
‭4294967295‬d
,然后我们会发现这么大的一个值,在
MSDN
Windows Sockets Error Codes
根本没有

于是我们联想到有符号数,这个值有可能是个负值,根据以前我们介绍过的计算方法,先将这个赋值减
1
,然后计算反码,然后在将非符号位转换成十进制

就是这样
FFFF,FFFF
-
1
=
FFFF,FFFE


然后除了符号位之外,全部取反码,最后就是

1000,0000,0000,0000,0000,0000,0000,0001


最后换算成十进制就是
-1d


如果返回值大于
-1d
的话,
jnz
跳转,
jz
不跳转

所以逻辑上归纳一下就是,如果返回值大于
-1d
的话,函数继续执行,不跳转

反之返回值小于等于
-1d
的话,
jz
跳转,之后就是做一些清理工作就退出程序了

假设函数没有跳转,之后便会执行这些函数



这里主要是执行了这个函数
connect




这里
IDA
已经帮我们标注参数出来了

s
的值是
esi


name
的值是
edx


namelen
的值是
10h


这里的
namelen
好理解,
10h
换算成十进制也是
10d


其他的
s
esi
代表的是刚刚我们
WSAStartup
初始话之后保存在
esi
栈中的套接字,这个我们不用管他太多

然后就是
edx
的值,从上面一直从上面看下来,我们会发现,其实
edx
指向的就是
127.26.152.13:80
,当然,这个时候已经不是
127.26.152.13:80
这个值了,经过
hton
一系列变化之后已经从主机(Host)序转换成网络(Network)序了,,注意调用
hton
之前
push
进栈的
50h
,这个是端口号,然后网络序是计算机在网络上通信使用的底层编码,我们知道这个值代表了这个
IP
和端口就够了

然后这个
connect
函数的返回值是这样的



这个图片翻译过来就是如果没有错误,就返回
0


最后这个判断和上个代码片是一样的

逻辑上就是如果返回值大于
-1d
的话(也就是返回值是
0
),函数继续执行,
jz
不跳转

反之返回值小于等于
-1d
的话,
jz
跳转,之后就是做一些清理工作就退出程序了

然后依旧假设程序返回值是
0
,然后继续分析主干代码



之后便会来到上面这个图片这里的位置

这里是将
ebp
存储
strncmp
函数的位置,其实就是指向
strncmp
函数的一个指针

ebx
也是同样的道理,是指向
CreateProcessA
的指针

然后这里没有跳转,继续往下执行,下面就是



注意这里的
or
运算,运算规则在下

0∨0=0
0∨1=1
1∨0=1
1∨1=1


可以看出来,只有有一个
1
,最后的结算结果就是
1
,而我们运算的第二个因子是
FFFF,FFFFh
,所以分析可知,这个
or
运算的目的是将
ecx
全部置
1


然后将
eax
全部置
0


之后最主要的就是调用了
send
这个函数,这里最主要的是将
buf
里面的值
hello
发送出去



然后我们可以从图片中看出,这个函数的一些入参,这里的
flags
一般置为
0


然后我们看一下返回值



如果没出错的话,返回的是发送的字节数,这个置一般大于
0


在代码片段的最后,有个判断的地方



这里的结构和上面的一样的,如果返回值大于
-1d
的话,函数继续执行,
jz
不跳转

反之返回值小于等于
-1d
的话,
jz
跳转,这里
jz
跳转也是跳转结束

逻辑上就是如果
send
函数出错的话,跳转函数结束

然后我们依旧假设
send
没有报错,我们就会来到这里



注意这里调用了
shutdown
函数,这个
shutdown
函数的入参是
esi
1
,我们查一下这个
shutdown
函数的一些说明



然后依旧是替换成人类可读的代码



这个
SD_SEND
的解释是这样的



这个意思就是关闭这个
socket
连接的意思

然后也是比较返回值,如果调用失败,跳转结束函数

如果函数调用成功,则执行这个



然后我们会发现这个

这里没什么技术含量,就是
recv
一个数据,然后存入
buf


最后判断一个返回值
eax
的值,
test
的运算和
and
运算一样,区别是不会保存结果

我们在
Lab 4
的时候完整分析了
test
会影响的标志位,然后最后会的出下面结论

其中一定的是

指令执行后
CF=0
OF=0


然后
JLE
跳转的条件是
ZF=1 or SF<>OF


主要分析这三个标志位
ZF
SF
OF
,因为也只有这三个标志位才会影响
JLE
的跳转

我们把
eax
分为这么三种情况

eax=0
eax>0
eax<0


如果
eax
=
0
and
之后,结果为
0
,所以
ZF
=
1
,然后不论
eax
等于多少,
OF
=
0
,然后就是
SF
,如果运算结果是正数,则
SF
=
0
,反之
SF
=
1
0
是正数,所以
SF
=
0


分析一圈之后就是

如果
eax
=
0
的话

ZF=1
OF=1
SF=0


根据
Lab 4
我们讲的跳转条件



eax
=
0
的时候,
JLE
会跳转

如果
eax
>
0
的话,按上面逻辑分析

ZF=0
OF=0
SF=0


这个
JLE
不会跳转

如果
eax
<
0
的话,我们可以得出一下结论

ZF=0
OF=0
SF=1


这里
OF
<>
SF
,所以
JLE
也会跳转

所以总结一下就是,当
eax
<=
0
的时候,
JLE
会跳转,那什么时候会返回小于等于
0
的值



recv
MSDN
说明里面说明了,这个函数是返回接受的字节数,如果数据已经传输完毕,然后没有接受到数据(
eax
=
0
),或者报错的时候(
eax
<
0
),
JLE
就会跳转

这时候函数就会跳会这个代码片段以前的地方重复执行



然后有会重新发送一个
hello
出去,然后关闭连接,接收一个回执,如果接收失败又跳回去发送
hello


这个代码片段如果用C语言来写的话,是这样的

iResult = send( ConnectSocket, sendbuf, (int)strlen(sendbuf), 0 );
// 以下是发送失败跳转处理函数
if (iResult == SOCKET_ERROR) {
//printf("send failed: %d\n", WSAGetLastError());
closesocket(ConnectSocket);
WSACleanup();
return 1;
}

//printf("Bytes Sent: %ld\n", iResult);

// shutdown the connection since no more data will be sent
// 关闭连接
iResult = shutdown(ConnectSocket, SD_SEND);
if (iResult == SOCKET_ERROR) {
//printf("shutdown failed: %d\n", WSAGetLastError());
closesocket(ConnectSocket);
WSACleanup();
return 1;
}

// 循环发送
// 接受数据失败跳转
do {
iResult = recv(ConnectSocket, recvbuf, recvbuflen, 0);
if ( iResult > 0 )
//printf("Bytes received: %d\n", iResult);
do_something(iResult);
else if ( iResult == 0 )
printf("Connection closed\n");
else
printf("recv failed: %d\n", WSAGetLastError());
} while( iResult > 0 );


然后我们依旧假设我们接受数据成功了,接下来就会处理这个代码片段



我们看看
MSDN
里面对
strncmp
的定义



根据以前的知识,可以从汇编代码中得出一下C伪代码

const char string1[] = "sleep";
const char *string2 = buf; //buf是从recv获得的数据
size_t count = 5;

strncmp(string1, string2, count);


strncmp
的话会比较前
count
个字符串相当或不相等,我们这里的
count
是等于
5
,所以也就是
sleep
这个字符串的长度,它会比较接受的字符串是不是
sleep


然后
test
一下
eax
的值是不是为
0
,然后如果不为
0
了的话,
jnz
跳转

逻辑上归纳一下就是,如果接收的字符串是
sleep
的话,函数跳转到左边的地方执行,也就是调用
sleep
函数来是进程休眠,休眠的时间是
6,0000h
也就是
‭393216‬d
ms,也就是
393.216
s,同时也是
6.053
min,差不多就是6分钟

执行完这些后,这个代码片段跳到一开始发送
hello
那里开始执行



如果接受的参数不是
sleep
的话,执行左边的这串,这里是判断发送的字符串是不是
exec
,如果是的话,跳转右边的红线



我们先看如果接收到不是
exec
的情况,也就是左边这个,因为这个短一点

这里是一个比较



比较这个
buf
的大小和
71h
71h
的话等于
113d
,如果
buf
=
71h
的话,
ZF=1
,那么
jz
跳转,绿线,跳转之后就是结束程序

如果不相等的话,红线跳转,也就是下面的地方



这里会休眠一下,然后休眠完之后会跳转到一开始发送
hello
那个地方,这里这个代码片段的作用应该是判断缓冲区是否大于某个值的

我们回到右边,如果接受的字符串是
exec
,那么将执行下面这些函数



我们注意到这里的一个调用函数就是
CreateProcessA
这个函数,我们去看看
MSDN
的定义



然后根据汇编代码,我们可以整理出这些入参的具体值是多少~

lpApplicationName=0
lpCommandLine=edx
lpProcessAttributes=0
lpThreadAttributes=0
bInheritHandles=1
dwCreationFlags=8000000h
lpEnvironment=0
lpCurrentDirectory=0
lpStartupInfo=ecx
lpProcessInformation=eax


然后我们剔除没有意义的初始值为
0
的参数只剩下以下的参数

lpCommandLine=edx
bInheritHandles=1
dwCreationFlags=8000000h
lpStartupInfo=ecx
lpProcessInformation=eax


然后吧其中的寄存器换成具体的变量就是

lpCommandLine=CommandLine
bInheritHandles=1
dwCreationFlags=8000000h
lpStartupInfo=StartupInfo
lpProcessInformation=ProcessInformation


然后这里最重要的就是
CommandLine
这个参数,表明了我们要为什么可执行文件创建一个教程来运行,但是我们点开这个
CommandLine
的时候会发现这个在栈中的数据并没有表明具体是什么值



这里书中说的怎么判断
CommandLine
是什么的方法有点笼统,我也是有点看的头大

我的分析方法是,先把
CommandLine
建一个数组,然后就会发现,后面接的是
db 4091 dup(?)
这个东西

我们知道这个
db
double byte
的缩写,然后在计算机中,没有3/2个字节的说法,这里我们整理出了
4091
个字节的数组,联想到
2^12
=
4096
,而
4096
4091
差了
5
个字节,刚好就是前面我们的
strncmp
count
的值加上一个空格的值,也就是,这个
4091
4096
减去了
exec
和一个空格之后剩下的部分

然后也就是服务器发送来的字符串我们假设会是这样的
exec C:\\Windows\someshell.exe


然后程序将
exec
+
(空格)
剔除之后剩下的部分就是那个
CommandLine
的部分,这个取决有服务器发送,是个不定值,无法从代码中看出来,所以这里的意思就是为这个
C:\\Windows\someshell.exe
专门创建一个进程来运行它,这个可执行文件一般是事先就上传到服务器的病毒木马之类的

然后这个代码片段运行完之后,又返回到发送
hello
那里继续循环执行,然后整个
DLL
文件就分析完了

[b]下面我们分析
EXE
文件
[/b]

我们先从
main
的地方开始



这里我们看到有个
argc
这个东西,这是给这个可执行文件传入的参数的个数

这里代码将这个
argc
2
进行了比较之后,有个跳转

一般
argv
argv
是存储传入参数的数组,
argc
是入参的个数)的第一个参数是这个函数的名字,第二个开始就是用户输入的参数

如果
eax
=
2
的话,
ZF=1
jnz
不会跳转,继续往下执行,如果不等于
2
的话,函数跳转左边

左边我们看一下会是什么,左边是结束函数,这里说明这个
exe
文件执行的时候需要在后面跟一个参数,我们继续

OK,我们假设我们入参是
2
了,那往下将执行这些函数



然后这里有个作者写的提示字符串,我们可以不管,主要是这个第一行代码和第三行代码需要我们注意一下

这里将
eax
指向了
argv
(注意区分一开始的
argc
)的开始地址,然后第三行这里又将
eax
这个指针向后移动了
4
个位,注意,这里是
4
个位,不是字节,根据计算机尝试,
4
位等于一个字节,也就是这时候
eax
指向的是
argv[1]
这个地方,也是就入参的具体值

然后继续往下执行



我们会看到上面这个代码,我们通过上一个图片的分析知道,这个
eax
其实存储的
argv[1]
的地址,然后现在
dl
又通过这个指针的指针指向这个
argv[1]
esi
存储了那句作者的提示信息,这里比较了这么两个指针指向的值是否相同

这有点像口令验证登录,口令就是这个
WARNING_THIS_WILL_DESTROY_YOUR_MACHINE


如果相同了之后,
ZF
=
1
,然后代码继续往下执行,如果不相同了,跳转
loc_401488
这个地方,这个地方并不是结束函数

逻辑上归纳下就是如果
dl
bl
指向的值相同(也就是第一个字符),就往红线往下执行,如果不相同的话,则跳转

我们先按红色这跟线往下走看



这里用
test
来检测这个
cl
也就是指向
argv[1]
的第一个字符是不是为
0
cl
在上一个图片的时候被赋值为
dl


这里的
argv[1]
可以看出字符串数组,用指针操作字符串数组

这么两个图片的意思和逻辑就是

如果入参的第一个字符相同了,那看看是不是为
0
,如果是的话,
jz
跳转,走绿色那条线,如果不是的话,继续走红线



红线走下来就是将
dl
指向了字符串数组的第二个字符,然后再比较,最后直到比较完所有的字符串

如果不相同了,跳转来这里



然后直接将
eax
置为
0


如果相等的话,跳转来这里



然后这里的
sbb
是借位减法的意思,也就是
eax-eax-CF
得到的值再保存在
eax


这个
CF
是上一步操作影响的标志位结果,我们可以看看这个代码片段都是哪里来的



会影响
CF
标志位的地方就这么两个,都用黄色标注出来了,因为
cmp
指令其实就是
sub
指令的变种,只是
cmp
没有保存结果而已

如果我们的
dl
<
bl
的话,就会存在借位,如果存在借位的话,那

sbb eax, eax


就会等于

sub eax, eax
sub eax, CF


这时候的
CF
=
1


最后的结果就是
eax
=
-1d
=
0FFFFFFFFh


第二个
sbb
也是一样,前一个操作
0-1
的情况,肯定存在借位,所以这里的

sbb eax, 0FFFFFFFFh


就等于

sub eax, 0FFFFFFFFh
sub eax, CF


最后结果也是
0FFFFFFFFh


如果前面不存在借位的话,也就是
dl
>=
bl
的情况的话

最后的
eax
=
1


所以这就出了三个
eax
的值

if(eax=0){
输入字符串=预存字符串;
}
else if(eax=-1){
dl<bl;
}
else if(eax=1){
dl>=bl;
}


然后我们下一步就是这里的代码片段



这里我们判断
eax
的值,如果为
0
也就相等的情况下,那么
ZF
=
1
,走红线,反之,走绿线(绿线就是跳转结束了)

然后我们还是假设走了红线



红线下一个代码片段有点长,我们先一个
call
一个
call
的看

这里我们先看这个函数调用了
CreateFileA
,这个函数的入参基本就是
eax
3
1
,从上面我们分析可以得出此时
eax
=
0


然后这个函数的具体参数就不贴出来了,网络吃了shi,我们公司一群傻b管理网络。。。出口400M带宽只买了个100M的路由。。。呵呵



这个函数主要就是在
C:\\Windows\\System32\\Kernel32.dll
这里创建或者打开一个这个文件



然后下一个函数调用是这样的



这里的函数
CreateFileMappingA
是将我们上面创建的文件
C:\\Windows\\System32\\Kernel32.dll
映射到内存中的作用

我们只要注意这个最后压入栈的参数,是我们上面创建的文件就
ok




然后我们继续下去,这里调用了
MapViewOfFile
这个函数,这个函数的作用是将上面创建的那个内存映射最后映射到进程中

这里的
hFileMappingObject
=
eax
,然后这里的
dwDesiredAccess
是等于
4
,这个我们可以查一下具体是那个参数代表了
4




最后我们可以得到这个参数的名字,在
MSDN
中的解释是这样的



这里指这个文件是只读的意思

继续往下



这里是创建或打开了一个叫
Lab07-03.dll
的文件,这个文件是已经存在我们这个分析目录的了,所以这里应该是打开这个文件

下面这里的代码是这样的



这里将我们调用
CreateFileA
的返回值进行比较,这里的十六进制
0FFFFFFFF
代表的是十进制的
-1


CreateFileA
返回值如果成功,则是句柄,如果失败则是
INVALID_HANDLE_VALUE


(暂时写到这里,我先去破一下公司限速玛德傻逼)

(2017-11-7)公司服务器被搞瘫了。。。。继续写文章



如果上面那个调用失败,则直接调用
exit
然后结束进程

然后如果成功了,则调用
CreateFileMappingA
来将这个文件映射到内存中



然后又调用
MapViewOfFile
来将这个文件映射到内存中的指定位置

然后就是进行一些内存的操作,然后我们按照书中的步骤,一直往下拉,然后可以看到这些



这里开始调用
CloseHandle
来关闭我们打开的两个文件

然后我们可以看到这里还调用了
CopyFileA
来将
Lab07-03.dll
复制到
C:\\windows\\system32\\kerne132.dll
这个地方

这里注意这个目录的最后是
xxx132.dll
不是
xxxl32.dll
,这个注意看到就明白这个函数的作用了

然后我们继续往下



这里调用了
sub_4011E0
这个函数,然后调用之前是
push
了一个参数入栈,
IDA
显示这个参数的
C:\\*


然后我们进入这个函数看看,然后我们可以发现这些东西



这个函数的第一个入参被
IDA
标注为了
lpFileName
,说明这个入参很有可能就是一个文件的名字

然后函数继续调用
FindFirstFileA




我们可以从
IDA
的注释中发现,这个
lpFileName
其实就是
C:\\*




然后这里将这个函数的返回值于
0FFFFFFFFh
也就是
-1d
比较,一般函数调用发生错误都是返回
-1
或者
1
,然后成功一般返回
0
,调用失败就跳转函数结束了

然后下面一个主线



这里我们已经把数字
10h
替换成了标准变量的模式

如果
dwFileAttributes
不是
File_ATTRIBUTE_DERECTORY
的话

test
指令为逻辑与运算,也就是如果
dwFileAttributes
也是
10h
的话,逻辑与的结果也是
10h
,结果不为
0
,所以
ZF
=
0
jz
不跳转,继续往下执行,如果
dwFileAttributes
不为
10h
的话,也是往下执行,但是当我们的
dwFileAttributes
00h
或者
01h
的话,我们就会直接
jz
跳转

01h
windwos SDK
中代表了
FILE_ATTRIBUTE_READONLY
,也就是只读模式

如果不跳转,直接往下执行的话,是这样的



然后这里开始将
.
移动到
esi


然后再将
cFileName
的地址放到
eax
中,其实这是
eax
就是一个指向
cFileName
的指针了

下一步



我们来看看这个代码片段

这里将
eax
指向的值给了
dl
又给了
cl
,其实也就是上面的
cFileName
参数

然后把
eai
的值给了
bl
,其实也就是上面的
.
这个字符串

这里的意思应该是比较找到的
cFileName
.
是不是相等的

如果
cFileName
.
相等,结果则为
0
ZF
=
1
,然后
jnz
不跳转

如果不想等了,
ZF
=
0
jnz
跳转

这里跳转位置
loc_401255
可以理解成退出函数,因为在
loc_401255
这个位置,其实做的是一些函数的清理工作

然后主线继续



这里检测一下
cFileName
为不为
0


0
的话也是跳转结束

然后继续主线



然后我们可以看到这样的操作,上面我们就已经说过了
eax
是指针,然后
eax+1
的意思就是指针往后偏移一个位置,
esi
也是同样的操作

eax+1
之后,
dl
其实就是指向了
cFileName
字符串数组的第二个字符

这里我们不能确定就是
esi+1
之后,会指向了什么地方

但是我们安装数据段的分析

esi
此时是指向了
.data.00403040
的地方,往后偏移一个位置,也就是
.data.00403044
,也就是
C:\*
的位置



这时候是将
cFileName
这个参数的第二个字符以后的数据和
C:\*
比较



如果这两个参数相同,则继续往下执行而不跳转

然后这个继续主线



这时候就是将这个指针往后偏
2
个位置,
esi
最后就会偏向这里了



一个叫
NewFileName
的数据,内容是
C:\windows\system32\kerne132.dll


然后如果相同,继续跳转回去继续比较

然后我们注意这里的
sbb
,上面有两根绿色的线,都是函数比较大小失败的跳转



sbb
其实就是
sub - CF
的意思,
CF
由上一步影响

因为是
cmp
之后,如果
cmp
的第一个函数比第二个函数小,那么
CF
=
1
,所以最后
eax
会变成
-1
,然后后门因为
eax
=
eax
,所以
CF
=
0
,最后
eax
就会变成
0


最后这个函数可以看到是个递归函数,然后分析到这里,基本可以确定这个函数是在
C:\\
下面查找
.exe
文件

然后我们为了查明这个函数找到
.exe
文件之后会做什么,我们要分析
sub_4010a0
这个函数,为什么呢



因为这里代码找到了
.exe
文件之后,就会调用这个函数,所以我们要查明这个函数是做什么的,这里有个参数被
push
入了栈,就是
ebp
了,这个参数具体是什么我们可以看
IDA
的注释,
IDA
已经表明这个是
lpFileName


也就是我们一开始传入的参数
lpFileName


然后我们进入
sub_4010a0
这个函数看看,是什么东西

这个函数的第一个调用是这样的



这个会创建一个名字为
lpFileName
值的文件或者打开这个文件,这时候的
lpFileName
的值其实是来自
C:\\*
下面任意个
.exe
文件,那这里因为这个文件存在了,所以
CreateFileA
其实是打开它

下一个调用



这里大概就是将这个映射到内存中(一般是方便进程间共享数据的)



然后这里就调用
MapViewOfFile
来获取共享的内存地址

我们也不要放过小尾巴



这里我们可以看到,函数判断了调用的
MapViewOfFile
的返回值,这个函数人如果成功是返回一个地址,共享文件在内存中的地址,失败返回
NULL
也就是
0


如果
esi
也就是返回值
eax
=
0
的话,则
jz
跳转,也就是会跳到
loc_4011D5
的地方



这个地方是函数结束的地方

也就是调用失败就跳转结束了,然后我们继续主线分析



这里出现了一个我们以前没遇到的函数
IsBadReadPtr
,这个函数按照
MSDN
的解释就是检查调用这个函数的进程谁都对这个内存有读取权限

也就是测试这个
.exe
文件是否有读的权限

如果没有也是跳转结束

对于
IsBadReadPtr
我们有两个参数,一个是
lp
=
ebp
,一个是
ucb
=
4
ebp
代表了要判断的内存的起始地址是哪里,然后
ucb
代表了这块内存的大小,这里指明了是
4


然后函数继续往下执行



这里比较了
ebp+0
这个地址上值和
4550h
的大小,
ebp
我们说过,是栈基底地址,也就是这个函数栈开始的第一个地址(注意是这个函数,上个函数的值我们在这个函数无法调用)

这里如果满足要求了,不跳转,继续往下执行



这里也是继续往后比较这个内存可不可读



然后最后我们可以看到,如果这个比较都符合了,会来这调用
stricmp
来比较这个
kernel32.dll


然后便是下面这些晦涩难懂的代码



注意第三行的
repne scasb
repne
是重复前缀的意思,也就是一直重复后面这个
scasb
,而
scasb
是检索目标字符串直到字符串最后的
\0
,然后这个指令是用
ecx
来计数的,
ecx
是计数寄存器的意思,这个我们汇编原理的时候大概提了一下

然后下面的
not ecx
就是得到检索的次数,这里我们再明显的说一下这个
repne scasb
的个个参数,第一次见到这个函数会让人比较蒙

第一行的
edi
是要检索的字符串

然后第二行的
ecx
是要循环的次数,这里设置成了
-1
说明不限次数

然后还有个检索内容是在
eax
中,而这个
eax
是上一步影响的



如果
eax
0
的话,
jz
跳转,
jnz
不跳转,不跳转的话就是我们上面那个代码片段,所以这里,如果
_stricmp
成功了返回
0
,就会跳转到这里

然后这时候,
eax
=
0
,也就是我们要查找的内容是
0
,准确的说,应该是
\0
,也就是字符串结尾的那个字符

所以这个会将
edi
指向的字符长度返回,然后这里的
dword_403010
其实是
kerne132.dll
,注意这里是
1(数字)32.dll


这个代码真难分析。。。我分析到这里的感觉

这个代码的意思就是在整个文件系统中寻找以
.exe
结尾的文件,然后在
.exe
文件内容中找到
kernel32.dll
的位置,然后替换成
kerne132.dll
,然后将这个
kerne132.dll
深深的植入系统中,如果你删了
kerne132.dll
这个文件,系统还运行不了

然后我们试试用动态分析看看,记得做好系统的快照

然后我们一开始分析就知道了,要运行这个病毒的话首先就是要输入一个参数

比如这样



然后执行就ok了

我们在
Process Monitor
里面设置
filter
之后,就可以看到这个代码在整个文件系统里面找
exe
文件



然后你就会在发现多了一个文件出来



名字是相当相似的

这个函数如果你分析到这里,像我的话,还是比较懵逼,到底这个函数替换完之后会做什么(因为时间隔太长了我都记不得以前分析的结果了)?

其实在现实生活中,肯定是做一些后门或者勒索病毒之类的活

分析基本到这里

2.这个恶意代码的两个明显的基于主机特征是什么?

解答: 就是使用了一个叫
kerne132.dll
的文件,和一个叫
SADFHUHF
的互斥量

3.这个程序的目的是什么?

解答: 目的就是创建一个很难删除的后门程序,如果你单单删除了
kerne132.dll
的话,整个系统就会崩了,然后会连接一个远程的主机,一个用来执行命令,一个用来睡眠

4.一旦这个恶意代码被安装,你如何移除它?

解答: 这是比较关键的一个问题,按照书上的说法是

这个程序很难被删除,因为它感染了整个文件系统的
.exe
文件,最好的方法是从备份系统中恢复或者留下这个恶意
kerne132.dll
文件并修改它,或者复制
kernel32.dll
kerne132.dll


要是我来解决的话,我的想法就是直接修改程序中的字符串,这个可以通过
OD
来做,也就是原程序或者叫病毒是查找
kernel32.dll
并替换成
kerne132.dll
,我们可以将这个两个字符串颠倒一下,就可以了

本文完
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  代码分析 病毒