解答: 这个既然开始了动态调试的部分,我们就只用OD
来进行操作了,因为这个版本的恶意代码都是针对XP
的,所以我打算是把OD
装到XP
那台运行代码的机器上
这里我们还是跟这书上的步骤走走,先摸清摸清一下套路
我们先打开OllyDbg
,我这个是从官网下载的,原版的1.x
的东西,然后大概是这样
这里稍微说一下这个OllyDbg
调整字体的方式
如果你用是吾爱破解版的话,字体应该没问题了(吾爱的插件比较多,懒得自己装插件的同学可以下一个)
我们打开这里,然后点第一个这个Appearance
这个东西
就会打开一个这个界面,然后Height adjust
这里可以调一下行高这个东西(第一个标黄的地方)
然后就是这个Change
这个地方,这里可以调字体,把字体调成你喜欢的就行了
然后我们回归正题,继续按照书中的步骤一步一步调试
一般打开OD
的时候,停在地方就是函数main
开始的地方
我们可以看到我们停到了这里这个地方
这里对我们来说并不是什么重要的东西,我们可以按F8(step-over)
往后慢慢一步一步走,其实这时候按F7(step-into)
是最保险的,因为你不知道什么时候会调用到函数,然后如果你想回退到开始执行的地方,可以按Ctrl+F2
来回溯到函数一开始执行的地方
然后我们边按边对照这IDA
的汇编代码看,不然光看OD
的代码会把人看傻的
我们先来到第一个call
的地方
这里OD
是标识了调用的是kernel32.GetVersion
的函数
到现在这些函数都是程序执行前的初始化,这里是获取导入库的地址的
然后我们继续往下,忽略那些有的没的函数,来到第二个调用CALL
这里,这里具体是什么我们可以进去看看
进去我们可以发现这里其实也是代码的初始化过程
这里是创建Heap
也就堆,我们忽略,跳出来之后继续往下
然后我们来到这个CALL
的地方,这个也是初始化的东西,但是我们注意到下面这个CALL
的函数是GetCommandLineA
,这个函数是获得用户输入的函数,说明这个程序需要用户提供一个参数
然后我们继续往下会发现
在3915
这个地方,然后这里有个CALL
,我们按F7
进去看看
进去发现这个也是初始化的过程
调用了GetEnvironmentStrings
这个函数来获取环境,这个函数为当前进程返回当前系统的环境变量
然后这个函数在里面一直比较什么,最后返回的eax
是这样的
EAX 00900768 ASCII "=::=::\"
这个对我们没什么用处
下面就是这三个函数的CALL
我们进去第一个调用405E61
处,然后我们会发现这个函数其实就是调用模块的
我们退出,然后进入第二个调用
这也是获得模块文件名字的调用
这是第三个调用,我们可以发现
这里获得了当前进程的ID
,这其实也是程序执行过程中,初始化的过程
然后我们继续往下分析
这里有三个push
,是Lab09-01.00402AF0
调用的三个入参,右边的注释已经帮我们标出来了
后面我们分析会发现,其实这个调用Lab09-01.00402AF0
就是main
函数
然后我们就进入这个函数看看
然后进入就找到第一个调用,熟悉我的人都知道我喜欢找CALL
来分析代码
这是进入的第一个CALL
,我们注意到这个CALL
后面的一行的代码是
CMP DWORD PTR SS:[EBP+8], 1
这时候我们可以看看IDA
了
IDA
里面,标黄那个CALL
下面的CMP
是不是很眼熟
我们看上面的定义,argc
=8
,转换一下这个语句就是[ebp+8]
这就和OD
对应上了
其实上面那个OD
的第一个调用,就是__alloca_probe
这个函数,这个函数是分配栈空间用的
于是我们就大概知道这是在比较你的有没有输入参数,如有有参数的话,argc
就不会等于1
,如果没有就是1
现在我们没有输入任何参数,然后[ebp+argc]
其实=1
,所以cmp
相减之后,结果为0
,于是ZF=1
,则jnz
不跳转
我们可以会到OD
里面一步一步看看有没有跳转
的确,OD
里面并没有跳转,继续往下执行了
然后可能这时候有同学会问,这时候如果只看OD
的数据,怎么知道DWORD PTR SS:[EBP+8]
就是argc
呢,如果你想这种硬分析的话,我们可以回到进入main
函数之前
这里在吊用Lab09-01.00402AF0
之前push
入栈了三个参数,这个函数就是我们标记为main
的函数,学过C
语言的同学都知道main
函数的一般三个入参
int main(int argc, char **argv, char **envp)
然后我们会发现这个main
函数的三个入参都被OD
标注了类型,然后还要我们清楚的一点就是这个每个参数的大小问题,不清楚的可以看看OD
的标注,这里我们可以在Arg1
和Arg2
这里看出来每个参数的大小是多少
Arg1
的地址是DS:[40EB84]
,然后Arg2
的地址是DS:[40EB80]
,从这里我们可以看出这个代码中,每个参数是占4
个字节的大小
然后这里有个小的skill,在汇编函数调用中,我们压入这三个函数完之后,并不是马上就调用了这个函数,而是还要压入函数的返回地址
我们按照param3
,param2
,param1
的次序依次在栈中压入参数,对应我们这里就是Arg3
,Arg2
,Arg1
的次序
然后在压参数结束之后,我们还要压入返回地址
最后,准备调用函数的时候,再将ebp
入栈,将esp
的指赋值给ebp
然后这时到了程序转移到被调用函数中执行,这个期间
地址ebp+0
指向的是外面那个函数的ebp
指针,ebp+4
指向的是RET返回地址
,ebp+8
就是我们的第一个入参,也就是这里的Arg1
所以这里为啥EBP+8
对应argc
就是这个道理
然后因为我们现在调试的时候并没有输入任何的入参,所以这里我们的argc
是等于1
的
所以我们这里并不会跳转
而是继续往下执行,然后从00402B03
开始的话,就到了IDA
的这里
这里的Lab09-01.00401000
对应的就是sub_401000
,然后我们看看这个函数是干什么的
这里调用了RegOpenKeyExA
这个函数,然后OD
已经帮我们标注好了这个入参的个个值,这里比IDA
人性得很多
这里是打开一个注册表的键,位置是HKEY_LOCAL_MACHINE
这个地方,然后具体位置是在SOFTWARE\Microsoft \XPS
这里
然后调用完之后就是测试这个调用是否成功了
这里test
了一下返回值,一般返回值是
成功就返回0
,假设调用成功了,and
之后,结果为0
,ZF
=1
,那么je
跳转
但是很不幸的是,我们OD
调试的时候这个函数是返回失败的
函数的返回值在EAX
中存储,这时候是2
然后就是函数把eax
置为0
之后返回了,于是我们大概知道这个函数的一半是干什么的了
就是检测系统中是否存在那个注册表键,如果没有的话,就退出这个函数,并返回0
,那如果成功呢,我们这里手动修改了一下eax
看看
我们将这个ZF
标志位手动置1
然后执行
这里我们到了这个地方
这里调用了一个注册表查询的函数,将名为Configuration
的值查出来
这里调用完成之后我们手动将返回值也置为0
,因为这时候这个键都不存在,哪里会查询成功
然后就是一些返回值比较函数的作用,注意这里的ebp-4
的位置,这里一般是属于临时空间
这里的JE
其实和JZ
是一样的,如果相等就跳转
这里其实如果调用失败之后,也会返回0
如果两个函数都调用成功,就返回1
然后这里我们为了节约分析时间,分析过后,其实这里的sub_401000
如果调用失败,jz
跳转之后,就会去执行一个Lab09-01.00402410
的函数
其实也就是IDA
中的sub_402410
然后在这调用的最后,是这个
这里用ShellExecuteA
打开了cmd.exe
,这应该是这个恶意程序的一种伪造,如果用户没有输入任何的参数,或者没有发现那个注册表项,再或者查询注册表失败,就来执行这个ShellExecuteA
通过cmd.exe
来运行一些命令
关键现在我们要找出这个命令是什么,然后我们现在回到这个函数的开始,开始我们的分析
这里是开头的地方,一样的先是做了一些栈的初始化,将ebp
压栈备份,然后把ebp
指向esp
然后做完这些后把esp
相减208
,将esp
往低地址的地方偏移了208
个地址,4个地址存储一个字节,有52
个字节的存储空间
这里稍微讲一下这个怎么看一个栈空间存储多少字节,一个EAX
多少字节这个问题
就是上图画圈圈这么一个空间占多少字节,你可能会说,这里只要看旁边的地址差是多少就知道了,对,但是并不是所有的栈都是图片中这样相差4
我们先看看一个栈空间可以存储多少的字节
这是地址空间的OD
里面的stack
这里的用4
个地址空间的空间,存储了8
个十六进制的数字,
然后一个字节等于2
个十六进制字符,这里有8
个,所以相当于4
个字节,也就是一个地址指向一个字节,每个栈空间是4
的地址差,为什么要用上面的208
/4
得出可以存储多少个数据节,就是这么来的
一个栈的空间,刚好可以存储进去一个EAX
或者EBX
(因为也是8
位十六进制的,4
字节)
我们继续回到汇编代码中
这里一共分配了52
个空间了,然后下面push
了四个参数进入栈中,这四个参数并不是占这些分配的空间,他们是在esp
的基础上往低地址的地方堆砌,注意,这里是从esp
开始,而不是ebp
然后最后通过
LEA EAX, DWORD PTR SS:[EBP-208]
将通过sub esp, 208
后的esp
值返回给eax
中
在IDA
中已经帮我们把这个变量标注为了Filename
这个名称,然后这个代码片段到最后的就是删除那个可执行程序本身(刚刚写了一下午的分析,,,一关全没了,,,所以这里简写了。。。)
其实之类这些各种比较字符串长度的操作,主要是为了在赋值字符串的时候,不覆盖原来的字符串,所以才要求计算偏移也就是字符串的长度,这点看懂了这部分基本没啥问题了,最后那个用cmd.exe
执行的指令,有个入参就是这个拼接后的字符串
如果我们手动修改这个cmp
的结果值
让这个程序以为我们输入了一个参数,然后看看
然后这里将我们的argv
(也就是ebp+8
)赋值给eax
,然后就是把argv
(也就是ebp+c
)赋值给了ecx
,然后这个代码
MOV EDX, DWORD PTR DS:[ECX+EAX*4-4]
的作用就是取argv
里面的最后一个参数的值,放入edx
中又放入eax
中
然后就开始调用这个Lab09-01.00402510
的这个函数
也就是IDA
中的sub_402510
这个
进来之后
前面的两句是函数栈的初始化
在2515
处的地方,将我们的获取到的argv
的最后一个函数(ebp+8
)赋值给了edi
这个地方
下一句将ecx
置为-1
然后repne scas edi
这个其实是计算edi
指向的字符串的长度
然后not ecx
是全部长度,包括字符串最后的结束符\0
然后下面的ecx + -1
就是去除这个结束符之后的长度
最后将这个长度和4
进行比较,这里剧透一下,如果长度不是4
,函数就直接返回0
了
我们这里肯定不能让他就这么返回了,改~
这里的意思就是将那个字符的第一个字符,通过指针赋值给了cl
,然后又给了edx
然后最后和61
比较,61
其实是a
的ASCII
编码
然后我们这里也是改~
让他一直执行下去
这是将那个字符串的第二个字符(eax+1
)赋值给了cl
,然后这里的代码算是比较复杂的了,主要集中在这里
SUB AL, BYTE PTR DS:[EDX]
al
是字符串的第二个字符,[edx]
是字符串的第一个字符,如果能想清这里,这里就理解了
这里的意思是,如果字符串的第二个字符,比第一个字符大1
(也就是第二个字符如果是b
),就跳转继续,否则和上面一样,直接返回0
(这里说的字符串,是指main
入参的argv
最后一个参数)
然后下面就是这个代码
这里的[ebp-4]
是字符串的上面字符串相减之后的值,是上一个代码片段遗传下来的
这里的IMUL DL
意思就是将dl
和al
相乘,然后将结果放在ax
中(因为ax
的一半就是al
,而eax
的1/4
就是al)
因为al
上一代码片段曾经计算过了,是0x01h
这个值,所以最后和dl
相乘之后,就还是dl
的值,也就是63
这里注意考虑[ebp-4]
的时候,和上下文结合起来看
这段代码的意思就是将第三个字符和c
(也就是63
)比较
这里其实是要注意这个MOVSX
这个指令,这个指令是带符合操作的
然后下一个字符
这里的al
这时候是63
,上一步计算后的结果最后还存在al
中,于是al
=1
之后就是64
也就是ASCII
的d
了
然后这些计算都通过之后,就会将eax
赋值为1
然后返回了
这里的代码有点变态,因为各个参数之间不是独立的,后面的比较参数是根据前面的计算结果来的,如果写出伪C代码的话,大概如下
int sub_402510(const char *argv_last_string)
{
char *ptmp = NULL;
len_argv_string = strlen(argv_last_string);
if (len_argv_string != 4)
{
return 0;
}
ptmp = &argv_last_string[0];
int a_value = 61;
if (*ptmp != a_value)
{
return 0;
}
int ntmp = (int)(*(ptmp+1)) - a_value;
if (ntmp != 1)
{
return 0;
}
int c_value = 63 * ntmp;
if (*(ptmp + 2) != c_value)
{
return 0;
}
int d_value = c_value + 1;
if (*(ptmp + 3) != d_value)
{
return 0;
}
return 1;
}
大概就是上面这个样子的一个函数,很变态,不是直接比较a
,b
,c
,d
,而是根据前一个计算的结果推倒后面的要比较的字符串
然后函数出来之后就是一个检测返回值是否是1
然后下面的地方是对应IDA
的这里
这个argv
如果你输入的是abcd
话就是abcd
,如果没输入就是这个可执行文件的绝对路径
然后我们这里看见这个函数__mbscmp
,这个函数在MSDN
中的解释就是
比较字符串的东西,这个函数其实和strcmp
差不多
所以这里2B56
地址的这个函数其实就是__mbscmp
,我们这里可以标记一下
然后我们看看他push
进去的参数有哪些
第一个push
进去的是byte_40C170
,这个好像是个字符串的,第二个push
进去的是eax
很可惜的就是IDA
中并没有很明确的标注这个参数,IDA
这里的2Dh
并没有翻译成ASCII
,2Dh
就是-
这里第一个push
的参数我们知道了,是-in
,然后我们看看第二个push
的参数是什么
这里将[EBP-1820]
位置的值赋值给了eax
而这个[EBP-1820]
是从edx
来的
而EDX
是从[ECX+4]
来的,这里注意一下就是[ECX+4]
这里读数据的时候要从倒着读,这里的[ECX=4]
=00900AE4
内存中是380B9000
但是在栈中是00900B38
这样
然后现在我们也知道了第二个参数是abcd
,这里是将abcd
这个字符串和-in
进行比较
这里因为我们现在是不等于的,所以返回值不会为0
,这里我们返回了1
test
是and
逻辑的运算,and
之后还是1
,ZF
=0
,JNZ
就会跳转
就会跳右边这条绿线
然后这里的结构其实和上面这里是一样的
也是比较这个入参和-re
是否相同,我们这里依旧不相同
我们还是顺着绿色这个线继续走
下面的这里还是一个test
比较之后JNZ
跳转
这里是比较是否和-c
相同,我们依旧不相同,然后我们继续往下
然后我们这里走的是左边这条绿色的线
这里和上面一样,是比较-cc
选项的
我们依旧不等于,然后继续走jnz
绿线
到这里我们就会调用这个sub_402410
的函数
然后这个函数我们上面曾经遇到过,就是没输入任何密码的时候调用的那个删除自身函数
这条线走完之后我们去看看那些个分支是干什么的
一开始是如果我们输入了参数abcd
之后,第一个比较的是-in
选项,我们进去看看是什么
如果当时输入了-in
这个参数,就会跳到这里,对应IDA
就是
这里的[EBP+8]
其实就是调用这个函数时的输入也就是argc
这个参数
这里会比较参数的个数是否为3
,如果相等的话,ZF
=1
,就走红线,如果不相等就走绿线
我们先看红线是啥
相等之后就会调用这些函数
对应IDA
也就是这里
这里我们可以结合这IDA
来看,IDA
已经表明了这个ecx
是将ServiceName
赋值给它
我们可以看看这个值是多少
IDA
中标识是一段地址空间,我们这时候就可以看OD
的了
OD
里面标识是[EBP-404]
的地址上的值,计算出来就是0012FB7C
也就是
这里就相当于sub_4025B0(0012FB7C, 400)
这样的函数调用,然后我们进去这个函数看看
这里我们注意到有个调用是GetModuleFileNameA
这个函数,这个函数会将返回值放在OD
标注的PathBuffer
这里,所以我们查看返回值要查看这个地址上的值是多少
这里的PathBuffer
是=0012E344
我们查看一下就是这样的
也就是我们当前这个可执行文件的绝对路径
然后就是一个test
这里如果成功是返回这个绝对路径的长度,这里test
之后,ZF
=0
,所以jnz
会跳转过去
跳转过来就是这样的函数
这里有个调用函数
在IDA
中标注的是
也就是__splitpath
这个函数
这个函数在MSDN
里面貌似查不到,我们直接看执行结果是什么
最后我们可以得出调用顺序是这样的__splitpath(0012E344, 0, 0, 0012FB7C, 0)
其中0012E344
这个地址是存着上一部返回的绝对路径
这个函数执行之后的寄存器变化
可以看出来这个函数的返回值EAX
貌似是个地址,或者说就是个指针,但是这个指针指向的地址都是乱码,这个不知道是什么东西
然后我们看看传进去的两个入参,发现,函数执行之后,0012FB7C
原本没有值的,现在存着这个感觉是像被分割之后的字符串
值是Lab09-01
,注意我们要看\00
字符串结束符,所以我们大概知道了这个函数就将那个绝对路径分割成为Lab09-01
这个字符串
然后这个函数如果调用成功返回的是0
,失败返回1
然后我们从函数中返回之后就是来到了这里,这有个test
,我们成功了,所以返回值是0
,然后test
之后ZF
=0
,JE
跳转
如果这里不是返回0
,那么整个这个函数会跳转之后结束并返回-1
跳转之后来到这里
在OD
中是这样的
这里有调用一个函数,还是按上面那种写法就是这样的sub_402600(edx)
,我们可以执行OD
来看看这个EDX
的值是多少
我们可以大概看出来,这个EDX
其实就是刚刚上面那个函数处理后的返回结果Lab09-01
函数进来之后的是这样的(这个函数很大)
对应IDA
中就是这样的
现在我是处于判断-in
成功之后那条线进入的一个函数
这里的call __alloca_probe
就是函数初始化栈的东西,这个我们可以不用管
这个写简单点就是sub_4025B0(EAX, 400)
(这个函数就是那个__split什么那个函数)
这里函数就有这么几个入参,执行看看
然后这时候的EAX
返回值就是0
了
也就是这里
这里的eax
=0
,然后test
之后ZF
=1
,然后JZ
就绿线跳转了,红线是结束这个函数并返回1
绿线之后就是来到这里
对应IDA
的这里
这里有个字符串我们注意到%SYSTEMROOT%\\system32\\
这里显示是的计算[EDI]
的字符长度也就是上面那个字符串的长度
这么一串指令到最后的
都是为了调用OpenSCManagerA
这个函数,这个函数是在指定的计算机上建立与服务控制管理器的连接,并打开指定的服务控制管理器数据库。
前两个参数为0
说明这个服务控制器是在本地上
这时候的寄存器
可以看出上面那个操作就是把这个字符串拼接成EDX
所示的样子
然后这里将返回值放在[EBP-404]
里面
然后和0
比较,如果返回值EAX
等于0
,ZF
=1
,JNZ
不跳转,继续执行就是结束并返回1
然后我们这里并不等于0
,于是跳转
也就是来到这里
OD
的这里
这里的第一个push
入栈的eax
在IDA
中标注是lpServiceName
,然后第二个push
入栈的ecx
是hSCManager
这里的eax
的值是Lab09-01
,也就是要打开的服务的名称,如果调用成功,返回一个指针
然后我们这里是第一次调用这个函数,所以这个函数会返回0
作为返回值
和0
执行cmp
之后ZF
=1
,则JE
跳转
执行之后就会跳转到绿色的线那里,如果调用成功了,就重新配置一下这个服务的参数
然后这个函数就是创建一个叫Lab09-01
的服务
这里调用成功之后,返回值肯定不为0
,所以和0
执行cmp
之后,ZF
=0
,之后JNZ
肯定就会跳转了
跳转之后的绿线就是执行一写关闭操作,来关闭打开的Handle
们
之后就是调用了
这里的入参我们可在在OD
里面查看
这里的入参edx
等于
然后我们看看调用后的结果,存放在ecx
指向的地址
这里把%SYSTEMROOT%
这个变量替换成了环境变量然后就成了0012DF44
这个地址上的字符串
调用成功之后继续往下走
我们直接看看这个入参和结果
这是执行之后的返回值,存在eax
指向的地址
然后执行成功之后继续往下
这里两个入参,edx
和ecx
lpNewFileName
的值是ecx
,然后lpExistingFileName
也就是edx
也是上面我们刚刚的返回值
这里要将这个可执行文件赋值到system32
下面
执行成功之后就会跳到这里
我们看看这个函数的入参是多少
也就是这个system32
下面的可执行文件
进入这个函数之后,我们第一个要执行的函数是这个
这个函数在MSDN
里面的解释就是检索系统目录的路径。 系统目录包含系统文件,如动态链接库和驱动程序。
我们看看返回值,返回值eax
是字符长度,真正的字符被赋值到了lpbuf
里面
lpBuffer
的值是
这是返回值
如果函数返回成功了,就会跳转到绿线继续执行,否则返回1
调用sub_4014E0
这个函数的时候,入参是ecx
和eax
然后就调用了sub_4014E0
进入这个函数之后是这样的
我们还是按照先看入参然后看返回值的做法看看
这个函数的入参是eax
,这个的值是
这也就是创建这个文件叫C:\WINDOWS\system32\kernel32.dll
这里我们执行后会发现返回值不是0
,然后就是和0
就行cmp
,ZF
=0
,则JNZ
跳转,走绿线
也就是这里
我们还是用OD
来看参数,这个函数的意思就是检索文件或目录创建,上次访问和上次修改的日期和时间。
我们看看参数
hfile
的值是5c
也就是上面那个函数的返回值
然后这个函数会把返回值放到lpCreationTime
和lpLastAccessTime
、lpLastWriteTime
这里
我们看看CreationTime
的返回值好像是乱码的东西
调用成功之后就会调用这个
这里先关闭上面那个Handle
然后就是CreateFileA
入参ecx
的值
然后设置这个文件的时间和kernel32.dll
的一样
然后就是关闭这个Handle
然后返回
这个函数成功返回0
,失败返回1
然后接着上面一层这个函数也会返回,从这里返回之后就会来到这里
这里开始调用sub_401070
这个函数
这里入参有个网址,我们进去这个函数看看
这个函数的一连串的指令之后是这个函数
这里看函数名是创建一个注册表的键值
是在HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft \\XPS
这里创建
然后设置一个键叫Configuration
然后就是在关闭之后返回
然后上面一层的函数也会返回,这里成功返回0
,失败返回1
现在我们知道了这个-in
参数会干什么了,这个参数会让函数安装自己为一个服务,然后把Lab09-01.exe
复制到system32
下面
我们试试下一个参数-re
这个参数
这里我们将入参设置为-re
然后来到OD
的这里
然后这里相同之后,ZF
=1
,JNZ
不会跳转,走红线
然后这里会比较我们的入参是不是三个
我们的入参就是三个,所以这里走红线
然后就会来到这里
然后这里有个函数sub_4025B0
这个东东
进去看看就会发现这个
我们进去看看
GetModuleFileNameA
函数的返回值是
然后就开始退出这个函数了
然后就是分离出这个值之后返回
解答: 这个代码分析太长时间了哈哈哈,密码是abcd
解答: 修改特定的地址上的代码,然后不跳转就ok
解答: 恶意代码创建了一个注册表项,然后一个名为XYZ
的服务
解答: SLEEP
, UPLOAD
, DOWNLOAD
, CMD
,NOTHING
之类的指令
解答: 有,对对应网址的资源有个一个GET
请求
本文完