恶意代码分析实战 Lab 9-1 习题笔记
2017-11-13 17:48
344 查看
Lab 9-1
问题
1.如何让这个恶意代码安装自身?
解答: 这个既然开始了动态调试的部分,我们就只用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函数的返回值是
然后就开始退出这个函数了
然后就是分离出这个值之后返回
2.这个恶意代码的命令行选项是什么?它要求的密码是什么?
解答: 这个代码分析太长时间了哈哈哈,密码是abcd
3.如何利用OllyDbg永久修补这个恶意代码,使其不需要指定的命令行密码?
解答: 修改特定的地址上的代码,然后不跳转就ok4.这个恶意代码基于系统的特征是什么?
解答: 恶意代码创建了一个注册表项,然后一个名为XYZ的服务
5.这个恶意代码通过网络执行了哪些不同操作?
解答:SLEEP,
UPLOAD,
DOWNLOAD,
CMD,
NOTHING之类的指令
6.这个恶意代码是否有网络特征?
解答: 有,对对应网址的资源有个一个GET请求
本文完
相关文章推荐
- 恶意代码分析实战 Lab 4 习题笔记
- 恶意代码分析实战 Lab 1-4 习题笔记
- 恶意代码分析实战 Lab 9-2 习题笔记
- 恶意代码分析实战 Lab 3-2 习题笔记
- 恶意代码分析实战 Lab 6-2 习题笔记
- 恶意代码分析实战 Lab 7-1 习题笔记
- 恶意代码分析实战 Lab 10-1 习题笔记
- 恶意代码分析实战 Lab 7-3 习题笔记
- 恶意代码分析实战 Lab 3-4 习题笔记
- 恶意代码分析实战 Lab 1-3 习题笔记
- 恶意代码分析实战 Lab 6-3 习题笔记
- 恶意代码分析实战 Lab 1-2 习题笔记
- 恶意代码分析实战 Lab 3-3 习题笔记
- 恶意代码分析实战 Lab 3-1 习题笔记
- 恶意代码分析实战 Lab 6-4 习题笔记
- 恶意代码分析实战 Lab 5-1 习题笔记
- 恶意代码分析实战 Lab 10-2 习题笔记
- 恶意代码分析实战 Lab 6-1 习题笔记
- 恶意代码分析实战 Lab 7-2 习题笔记