• 我们在哪一颗星上见过 ,以至如此相互思念 ;我们在哪一颗星上相互思念过,以至如此相互深爱
  • 我们在哪一颗星上分别 ,以至如此相互辉映 ;我们在哪一颗星上入睡 ,以至如此唤醒黎明
  • 认识世界 克服困难 洞悉所有 贴近生活 寻找珍爱 感受彼此

恶意代码分析实战:第六章 识别汇编中的C代码结构

恶意代码分析实战 云涯 4年前 (2020-06-15) 1880次浏览

再次强调,分析恶意代码的目标是理解一个程序总体功能,而不是分析每一条指令,不要在细节上陷入困境,将精力集中在程序整体上是如何工作的,而不是它们是如何做每一件特定的事情的。

1 全局与局部变量

全局变量可以被一个程序中任意函数访问和使用。局部变量只能在它被定义的函数中访问。在C中全局变量和局部变量声明方式是相似的,但是在汇编中看起来完全不同。

定义全局变量x与y

int x=1;
int y=2;
void main(){
  x=x+y;
printf("Total=%d\n",x);}

定义局部变量x与y

void main(){
  int x=1;
  int y=2;
  x=x+y;
  printf("Total=%d\n",x);}

在汇编中,全局变量通过内存地址引用,局部变量通过栈地址引用

全局变量汇编代码:此时1存储在内存dword_40CF60,2存储在内存dword_40C0000。全局变量x就通过dword_40CF60来标记。

mov eax, dword_40CF60

add eax, dword_40C000
mov dword_40CF60, eax

mov ecx, dword_40CF60

push ecx
push offset aTotalD ; "total = %d\n"
call printf

局部变量汇编代码:局部变量以函数为入口,x就会位于栈上一个相对ebp的常量偏移处。ebp-4是一个基于栈的局部变量,只在被定义的那个函数中被引用。

mov dword ptr [ebp-4], 1
mov dword ptr [ebp-8], 2
mov eax, [ebp-4]
add eax, [ebp-8]
mov [ebp-4], eax
mov ecx, [ebp-4]
push ecx
push offset aTotalD ; "total = %d\n"
call printf

局部变量IDA汇编代码:x被假名var_4标记,y被假名var_8标记了

mov[ebp+var_4], 0
mov[ebp+var_8], 1
moveax, [ebp+var_4]
addeax, [ebp+var_8]
mov[ebp+var_4], eax
movecx, [ebp+var_4]
push ecx
push offset aTotalD ; "total = %d\n"
call printf

2 反汇编算数操作

int a = 0;
int b = 1;
a = a + 11;
a = a - b;
a--;
b++;
b = a % 3;

对应汇编操作:IDA 将a标记为var_4,b标记为var_8。首先,var_4和var_8分别被初始化为0和1。a被移到eax中,然后0x0b被加给eax,从而a增加了11,b被a减。最后五条指令实现了取模。当执行div或idiv指令时,用edx:eax除操作数并将结果保存在eax中,余数保存在edx中。这就是为什么edx被移动到var_8中的原因。

mov [ebp+var_4], 0
mov [ebp+var_8], 1
mov eax, [ebp+var_4]
add eax, OBh
mov [ebp+var_4], eax
mov ecx, [ebp+var_4]
sub ecx, [ebp+var_8]
mov [ebp+var 4], ecx
move dx, [ebp+var_4]
sub edx, 1 
mov [ebp+var_4], edx
mov eax, [ebp+var_8]
add eax, 1
mov [ebp+var_8], eax
mov eax, [ebp+var_4]
cdq
mov ecx, 3
idiv ecx

3 识别if语句

找到IF的关键点是IF/ELSE。

3.1 IDA图形化可以看到if语句

3.2 识别嵌套IF语句

 

在汇编中识别嵌套IF如下:

4 识别循环

4.1 找到for循环

for循环时一个C编程使用的基本循环机制. 

for循环总共有四个组件: 初始化 , 比较 , 执行指令 , 以及递增或递减

在汇编中,for循环可以通过定位四个组件-初始化, 比较, 执行指令, 递增或递减;

图形化更直观

4.2 找到while循环

while循环频繁被恶意代码作者使用, 来通过循环知道一个条件是否满足, 比如接收到一个数据包或命令. 

while循环的汇编你看起来和for循环类似, 但是它们更容易理解,.

和for唯一区别是缺少一个递增或者递减. 

5 理解函数约定

调用约定决定了函数调用发生的方式. 这些约定包含了参数被放到栈上或寄存器的次序, 以及是由调用者还是被调函数,负责在函数执行完成时的清理站.

调用约定的使用依赖于编译器, 以及其他因素.

函数常见三个调用约定: cdecl, stdcall, fastcall

5.1 cdecl — 从右到左

cdecl是常用约定之一, 参数是从右到左按序被压入栈, 当函数完成时由调用者清理栈, 并且将返回值保存在EAX中.

test(a,b,c)依次入栈就是c, b, a

函数完成时, 怎add esp, 12 由调用者清理栈

 

5.2 stdcall — 从右到左

stdcall是被调用者在函数完成时清理栈, 其他和cdecl非常类似

stdcall是windows API 的标准调用约定. 任何调用这些API的代码都不需要清理栈, 因为清理栈的责任是由实现API函数代码的DLL程序所承担

因此上图的add esp 12就没有了

 

5.3 fastcall — 前两个ECX,EDX,后续从右到左

规定将前两个参数由寄存器ecx和edx来传递,其余参数还是通过堆栈传递(从右到左)

 

5.4 压栈与移动

左侧是cdecl调用约定, 右侧是GCC编译的stdcall调用约定. 即使同一段代码,不同编译器编译后调用约定也不同.

6 分析switch语句

switch语句被程序员用来做一个基于字符或整数的决策, 比如后门选择控制指令

6.1 if样式

6.2 跳转表

对于一个庞大的switch, 使用跳转表来及逆行跳转.

7 反汇编数组

数组是通过一个基地址作为起始点来进行访问的

8 识别结构体

结构体通过一个作为起始指针的基地址来访问.

要判定附近的数据字节类型是同一个结构体的组成部分, 还是只是凑巧相互挨着是比较困难的

这依赖于这个结构体的上下文.

 

9 分析链表遍历

链表是包含一个数据记录序列的数据结构,并且每一个记录都包括一个对序列中下一个序列的引用。

对应的汇编代码如下:

链表的特征就是包含指向下一个节点的指针。

10 小结

在恶意代码分析中,永恒不变的任务:从细节进行抽象。不要在底层细节中停滞,而是建立起能够识别代码在高层次上做什么的能力。

 

11 实验

11.1 lab-1

1. main函数调用唯一子过程中发现的主要代码结构是什么?

是if结构

2. 0x40105F子过程是什么?

是printf

很奇怪是,无论IDA Pro商业版还是免费版都不一定总能识别printf函数,其中一种方法是找到调用子例程前被压入栈的参数。这里发现都是字符串,以及结尾都有一个\n换行符。推断是printf

3. 恶意代码功能是什么?

检测网络是否连通

11.2 lab-2

在main函数中跟到sub_401040, 发现如下的buffer解析成了var_20*等

然后从210-11结束,发现一共是512字节,因此将buffer修改为数组buffer [512], 将Array size修改为512.

11.3 lab-3

包含一个switch和跳转表

11.4 lab-4

以上的综合性代码

 

 


云涯历险记 , 版权所有丨如未注明 , 均为原创丨本网站采用BY-NC-SA协议进行授权
转载请注明原文链接:恶意代码分析实战:第六章 识别汇编中的C代码结构
喜欢 (0)