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
分析,因为动态分析书上并没有得出什么结论,我们也不浪费什么时间了,直接开搞
我们根据书中的步骤
我们开始分析dll
这个文件
书上的分析方法是直接只列出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
进行比较
这个值换算成十进制之后就是4294967295d
,然后我们会发现这么大的一个值,在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
也就是393216d
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
文件就分析完了
下面我们分析EXE
文件
我们先从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
,我们可以将这个两个字符串颠倒一下,就可以了
本文完