首页
社区
课程
招聘
[原创]恶意代码分析入门系列之 - 基础知识
2020-9-1 11:48 4952

[原创]恶意代码分析入门系列之 - 基础知识

2020-9-1 11:48
4952

0x00 前言


在第一小节完成了环境的搭建,成功搭建好了一个可用于恶意样本分析的虚拟机环境,在这小节,先介绍一些关于恶意样本的相关知识,也算是为恶意样本分析做准备。


首先,我没有写详细的汇编教程,论坛中有非常多的汇编教程,读者可以多多利用论坛的搜索功能,找到一些相关的知识点。大多数情况来说,遇到的问题之前都有人在论坛中求问过,只是措辞和询问的方式不一样,大家遇到问题的时候可以先利用论坛的搜索功能或结合google进行搜索。


为了不让之后的内容过于抽象,在本小节中介绍基础的汇编知识。



0x00 汇编基础内容

寄存器相关

    简单来说,寄存器是CPU内部用于存放数据的小型存储区域,寄存器是内存与CPU能够交互的一个关键。

    以x86为例,常见的有8个通用寄存器。

    EAX    eax是累加器,在做加法和乘法运算的时候,eax是默认寄存器    

    EBX    ebx是基地址寄存器,通常被用于寻址    

    EXC    ecx是计数器,通常用于循环计数    

    EDX    edx通常用于存放除法运算产生的余数    

    ESI     esi是源寄存器,通常在字符串操作中使用

    EDI    edi与esi对应,edi表示目标寄存器    

    EBP    ebp是基址指针,通常用于指向栈底    

    ESP    esp是堆栈指针,与ebp对应,通常指向栈顶。

    还有一些特殊的寄存器,刚开始的时候不用掌握那么多,后面遇到再去学习就行。


    eax ebx exc edx 这四个寄存器,除了本身自带的一些功能,他们还常常被用于一些运算。开发人员可以直接使用一些汇编指令去操作这几个寄存器实现一些简单的功能,需要注意的是,eax寄存器还有一个存放函数返回值的功能。在大多数情况下,一个函数调用完成之后,返回值都会保存在EAX寄存器中


    后面的esi和edi通常会配合数据段寄存器DS来使用,通常情况下,我们不会动esi和edi的值。


    esp和ebp分别用于指向栈顶和栈底,是两个特别关键的寄存器。esp和ebp涉及到操作系统中一个最基础的概念"堆栈"。


    

    说是堆栈,其实是指栈,当我们描述堆的时候都会直接说堆。

    栈的原理其实很简单,从C语言的角度来看,栈是一种数据结构,先入后出。从计算机系统来看,栈是一个具有先入后出属性的动态内存区域。程序可以将数据压入栈中,也可以将数据从栈顶弹出。可以说我们所见到的函数调用,基本上都是跟栈挂钩的。
    在一个函数开头,经常可以看到如下的语句来开辟新的堆栈:

    push ebp

    mov ebp,esp

  esp 40h


    这里首先保存当前的ebp,然后通过mov将ebp和esp指向同一个位置,再通过sub指令更改esp的指向,这样就为当前函数开辟出了一个新的栈空间。

    sub esp 40h里面的40h,是编译器预留给当前函数的空间大小。


    

     然后在函数结尾,通常会使用

    mov esp,ebp
    pop ebp
    ret

    作为返回,退出当前函数。


    在汇编代码中,ebp常见的有两种使用方式,分别是:

    ebp + xx

    ebp - xx

    根据windows内存空间的生长方向我们可以知道,ebp + xx 是取参数的值,ebp -xx 是取局部变量的值。

    有时候也会使用esp的加减来定位变量,但是这种情况比较少见。

    

    

    举个

    我们在对一个函数调用的时候,就会将一些关键信息入栈。

    最开始入栈的是函数的参数,在C语言中,函数的参数入栈顺序是自右向左依次入栈。

    最后入栈的是函数的返回地址,这个地址很重要,关系着函数执行完成之后该回到哪里继续执行。

    比如我有个函数fun1(int a, char b, int c )

    我在main函数中调用了fun1函数,

    那么参数入栈的时候

    最先入栈的是变量c

    其次入栈的是变量b

    接着入栈的是变量a

    最后入栈的是main函数调用这个fun1函数之后的地址


    

    汇编指令相关

    

    先介绍几个常见的指令:

    mov

    add

    sub

    push

    pop

    call

    rete


    最常见的汇编指令是mov,mov指令有两个操作数,格式如下:

    mov 操作数1,操作数2

    

    mov指令的功能是将操作数2的内容移动到操作数1,这里的操作数1必须是寄存器,操作数2可以是地址、常量、寄存器。例如:

    mov eax,31 是把0x31赋值给eaxmov eax,ebx 是把ebx的值赋值给eax

    那么可不可以用 mov 0x00401000,31 来表示将0x31放入到0x00401000这个地址呢,答案是不行的。

    因为如果第一个操作数变成了地址,编译器并不能保证这个地址一定存在,而数据是从内存中读入到CPU中的,要让CPU能够正常操作,所以其中一个操作数必须为寄存器。


    

    add和sub是一组运算指令,分别对应加减运算。

    这两个指令和mov类似,使用方法都是

    add  操作数1,操作数2sub  操作数1,操作数2功能是将操作数1和操作数2进行add或者sub操作,然后结果保存在操作数1的位置所以很明显,操作数1也必须是一个寄存器。比如

    add eax,0x10是将eax中的值和0x10相加,然后结果保存在eax中add eax,ebx 是将eax中的值和exb相加然后保存在eax中。sub指令也是同理。比如我要利用eax进行运算可以这样写:

    mov eax,0x10
    add eax,0xA
    mov ebx,0x05
    add eax,ebx



    还有一组常用的汇编指令是push和pop

    和之前的汇编指令不同,push和pop只有一个操作数。

    比如

    push eax

    push ebx

    push 0x10

    pop edi

    pop ebx

    pop bex

    ......


    push指令的功能是将跟在push指令后的操作数入栈。

    当一条push指令执行之后,ebp的值不变,esp的值会减去入栈类型的长度。

    比如假设当前的栈如下:


    



栈底ebp指向的地址是0x0012FF80

栈顶esp指向的地址是0x0012FF70

现在执行push 0x10,那么堆栈的变化如下


与之对应的,pop指令执行之后,ebp还是不变,esp的值加上pop出的类型的长度。

关于汇编的这部分,笔者去年写了一点点在公众号上,有兴趣的读者可以看看https://mp.weixin.qq.com/s/8ZdX7cOLp18FMtE5vBgTkA

由于图片转过来不清晰,又有水印,这里就不继续讲汇编了,讲点后面的内容。



0x01 恶意样本分类

按文件类型分类

主要分为

PE文件(Windows平台可执行文件,如exe和dll文件)

ELF文件(Linux平台下的可执行文件)
office文档文件(注意2007版本是个分界点,07版本之前的文档文件本质是二进制文件,之后本质是压缩包)

ps1文件(powershell脚本)

js文件(JavaScript脚本)

VBS文件(vbs脚本)

bat文件(windows批处理文件)

chm文件(微软帮助文档)

lnk文件(链接文件)

hwp文件(韩国office,类似于我国的wps)

...

目前比较常见的好像就是这些,在Windows平台下,PE文件是攻击主流,其他的所有恶意样本都统称非PE,通常情况下,现在的样本的流行使用非PE加载PE的方式来攻击,原因是非PE的查杀相对PE来讲较为困难,攻击者可以通过非PE文件在用户的计算机上设置计划任务等操作,来下载后续的PE文件到本地并执行。



按行为来分

主要分为

  1. Downloader(下载者)  此类木马的主要功能就是下载后续的攻击文件到本地继续执行,此类样本的特点就是小,通常来说,代码也比较简单,就是通过访问一个链接,下载文件保存到本地,然后通过一定的方法启动这个文件。
  2. Dropper(释放器),此类木马的主要功能是释放并执行后续的攻击文件。就攻击者可能会将恶意的程序加密成为一段数据,然后放到Dropper中,由Dropper解密释放并执行这个文件,这样做的目的是可以起到一定的免杀效果。
  3. 窃密木马,此类木马的主要功能是收集本地主机的一些机密信息并打包上传,比如收集各大浏览器的缓存目录、隐私目录等,拿到用户保存的密码信息。或是收集本地主机的一些文档文件,最后加密上传到攻击者的邮箱服务器、FTP服务器等。
  4. 远控木马,远控木马拥有对本地主机的控制权,可以随时通过下发控制指令实现不同的功能。属于在分析中很常见的木马,目前主流的远控木马大多都是由C#编写,并且很多是开源的商业马,在github都可以找到源码实现.
  5. Ransom(勒索病毒)
  6. Adware(广告类恶意软件),此类软件通常不像木马那样有侵犯用户隐私的危害,大多数是用于推广广告、篡改用户浏览器主页等。
  7. 感染型木马,感染型木马较为特殊,遇见的也比较少,可以等后面找一个实际的感染型木马分析。




0x02 恶意样本分析基本框架流程

总的来说,样本分析是有一个固定套路的。

通常来说流程如下:

  1. 文件查壳(看文件是否有壳,有壳则需要脱壳)
  2. 行为检测(通过行为检测工具,跑出样本的大致行为,通过此步骤,可以对样本的基本功能有个概要的了解,从而确定分析方向,比如在行为检测的时候,监控到样本运行后会访问某个域名,然后我们可以对这个域名进行查询,看该域名是否已经有公开的情报,是否被归类到了某个家族,确定这些信息之后,我们大概可以了解到本次分析的样本的基本类型,比如是下载者、是窃密的、还是远控的。)
  3. 静态分析(静态分析分为导入表分析、字符串分析、代码分析、在导入表分析和字符串分析的时候,可以结合行为检测得到的结果)
  4. 动态调试(通常来讲,阅读IDA的代码,是分析样本最快的情况,但有时候,一些较为复杂的解密运算、或是样本开辟了新内存等功能,需要使用调试器来协助分析)
  5. 关联分析(在样本的基本功能分析完成之后,我们需要对样本做关联分析,通常来讲,大部分的样本都是有通信行为的,在关联分析的时候,通常就是以请求的域名、ip等信息进行关联)
  6. 溯源分析(更深层次的对攻击者进行一个挖掘)
  7. 总结分析报告



0x03 WindowsAPI基础

个人的一点感觉,学习逆向,开发技能可以不用掌握多少,但是开发思想是一定要有的。

就一个功能,我们可以不用了解它的具体代码实现,但是我们需要了解它的实现思路。

我在没事儿的时候,就经常会想,如果我要写一个远控木马,我应该具备哪些功能,这些功能要如何实现才能更好的提升攻击效率,以及不被检测到。如果我是攻击者,我想要攻击一个企业我需要做哪些准备,需要收集什么信息。想多了之后,在分析木马的时候往往会有意外之喜。 



如何辨别程序中的API

目前来说,写木马的主流语言还是C和C++,虽然VB和C#的木马越来越多,但想要完全取代应该还有一段时间。其中VB的样本大多数都是Downloader,也就是作为攻击中的前置部分,用于下载后续的木马到本地执行。C#的样本多为远控。说回C和C++,相信大家大学第一门编程课都是C语言,写第一个程序基本上长这样:


#include<stdio.h>

int main()

{

printf("hello\n");

return 0;

}



在上面的代码中,虽然只会输出一个hello ,但其实我们已经用到了一个系统函数printf,该函数声明在stdio.h中

我们可以找到stdio.h这个文件,查看printf函数的具体定义:

image.png




所以,其实如果我们不需要使用stdio.h中的函数,在C语言开头的那行#include<stdio.h>其实是可以不用写的:

image.png



但是这个时候我们就会发现,通过纯C语言,我们可以做的东西其实很少。哪怕最简单的接收用户输入,然后输出也需要引用系统的库文件。


第二个例子,假如我们想要用原生C语言实现字符串比较的功能,我们可能会这样写:

#include<stdio.h>
int main()
{
	char *str1 = "this is first strings";
	char *str2 = "this is second strings";
	for(int i=0;str1[i]!='\0';i++)
		if(str1[i]!=str2[i])
			return 0;
	printf("str1 eq str2\n");

	return 0;
}


这样可以直接比较str1和str2是否相等,但如果我们还需要统计如果不相等的情况下哪个字符串更大(长)

我们则还需要在其中加入字符串统计的功能

#include<stdio.h>
int main()
{
	char *str1 = "this is first strings";
	char *str2 = "this is second strings";
	bool isEq = true;
	for(int i=0;str1[i]!='\0';i++)
	{
		if(str1[i]!=str2[i])
		{
			isEq = false;
		}
		if(str1[i+1] == '\0' && str2[i+1] != '\0')
		{
			printf("str2 big than str1\n");
			break;
		}
			
		if(str1[i+1] != '\0' && str2[i+1] == '\0')
		{
			printf("str1 big than str2\n");
			break;
		}
			
	}
	if(isEq)
		printf("str1 eq str2\n");

	return 0;
}


我们也可以调用windowsAPI strcmp实现刚才的功能

#include<stdio.h>
#include<string.h>
int main()
{
	char *str1 = "this is first strings";
	char *str2 = "this is second strings";
	if(!strcmp(str1,str2))
		printf("str1 eq str2 \n");
	else
		strcmp(str1,str2)>0?printf("str1 big then str2\n"):printf("str2 big then str1\n");
	return 0;
}




通过调用strcmp,我们只需要检查该函数的返回值,就能很好的得到对应的结果。

这只是比较简单的例子,Windows封装了超级多的API以供我们在vc和vc++中直接调用,几乎囊括了所有我们在编程中所需要的操作,如文件操作、进程/线程操作、网络请求、注册表操作、加密解密、消息传递等。

攻击者在编写恶意代码的时候,通常也会直接调用这些API函数,所以我们在本节中,需要先对WindowsAPI有个大概的了解。


想要在程序中辨别使用的API也非常简单,我们在通过IDA对程序进行反汇编之后,程序调用的Windows API在IDA中默认是红色显示:

image.png



一般来说,sub_开头的,是IDA自动识别并命名的函数,sub_后面的地址是函数的起始地址。

红色显示的函数,就是Windows API函数




如何学习API


个人的建议是一边分析,一边记录,然后多翻,多看。

有一定编程能力的,可以自己尝试写一些恶意的操作,如文件夹遍历,收集本地信息上传这样的功能。在编写的时候就需要调用很多WindowsAPI函数,在这个过程中,就可以很好的掌握这些API的参数、返回值等信息。

如果对编程不是很熟悉的,可以直接分析样本,遇到里面用的API,就去查询它的功能、参数、返回值等。

所有的WindowsAPI都可以在微软的官方手册上找到对应的注解,

以CreateFile函数为例,在Google搜索一般第一条就是微软的官方注解:

image.png



在百度上搜索的话需要自己找一下:

image.png



在官网中,我们可以看到该函数的使用场景、调用语法、参数详解:(也可以看百度百科)

image.png



经过官网的资料我们可以知道该函数可以用于打开或创建控制台、通信资源、目录、磁盘驱动器、文件、邮槽、管道等对象并返回可访问的句柄。

且参数1lpFileName就是我们需要打开的文件名。


那么我们在分析木马的时候遇到该函数调用,我们就可以根据参数1(最后一个push的参数)的内容,确定攻击者打开/创建的文件名称。


我们可以把平时在分析样本时遇到的API都记录到一个excel中,并做好对应的注解,加上自己对这个API的理解,定期的去更新、翻阅这个excel。


{ char *str1 = "this is first strings"; char *str2 = "this is second strings"; bool isEq = true; for(int i=0;str1[i]!='\0';i++) { if(str1[i]!=str2[i]) { isEq = false; } if(str1[i+1] == '\0' && str2[i+1] != '\0') { printf("str2 big than str1\n"); break; } if(str1[i+1] != '\0' && str2[i+1] == '\0') { printf("str1 big than str2\n"); break; } } if(isEq) printf("str1 eq str2\n"); return 0; }

#include<stdio.h> int main() { char *str1 = "this is first strings"; char *str2 = "this is second strings"; bool isEq = true; for(int i=0;str1[i]!='\0';i++) { if(str1[i]!=str2[i]) { isEq = false; } if(str1[i+1] == '\0' && str2[i+1] != '\0') { printf("str2 big than str1\n"); break; } if(str1[i+1] != '\0' && str2[i+1] == '\0') { printf("str1 big than str2\n"); break; } } if(isEq) printf("str1 eq str2\n"); return 0; }


[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法

最后于 2020-9-1 12:00 被jux1a编辑 ,原因:
收藏
点赞4
打赏
分享
最新回复 (2)
雪    币: 622
活跃值: (1211)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
绝望的皮卡丘 2020-9-1 11:59
2
0
态度不错,可以多看下别人的分析报告
雪    币: 9763
活跃值: (3925)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
Willarcap 2020-9-1 12:51
3
0
支持一下。另外,最懂微软的还是微软,如果上不了Google,为啥不用必应国际版搜一下呢,切记切到国际版
游客
登录 | 注册 方可回帖
返回