美文网首页
恶意代码分析实战 第六章 识别汇编中的C代码结构

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

作者: doinb1517 | 来源:发表于2021-12-23 15:03 被阅读0次

全局和局部变量

全局变量可以被一个程序中的任意函数访问和使用
局部变量只能在它被定义的函数中访问
在反汇编代码中:
全局变量通过内存地址引用
局部变量通过栈地址引用

代码清单6-1:两个全局变量

#include<stdio.h>

int x = 1;
int y = 2;

void main() {
    x = x + y;
    printf("total=%d", x);
}

代码清单6-2:两个局部变量

#include<stdio.h>
void main(){
    int x = 1;
    int y = 2;
    x = x+y;
    printf("total=%d", x);
}

使用gcc编译为64位程序,使用IDA打开(也可以使用gcc -S 查看汇编代码)


demo1.png

此部分代码为未经标记的汇编代码。


demo22.png

这部分代码是经过IDA标记的代码。

demo2.png

反汇编算数操作

以下的C代码进行了加发,减法,自增自减,去模五种运算

C语言代码

suanshu1.jpg

汇编代码

suanshu2.jpg

这里需要解释一下cdq的作用,cdq其实多出现在除法运算之前,这里使用除法来进行取模运算。

CDQ 是一个让很多人感到困惑的指令。这个指令把 EAX 的第 31 bit 复制到 EDX 的每一个 bit 上。 它大多出现在除法运算之前。它实际的作用只是把EDX的所有位都设成EAX最高位的值。也就是说,当EAX <80000000, EDX 为00000000;当EAX >= 80000000, EDX 则为FFFFFFFF。

例如 :
         假设 EAX 是 FFFFFFFB (-5) ,它的第 31 bit (最左边) 是 1,
         执行 CDQ 后, CDQ 把第 31 bit 复制至 EDX 所有 bit
         EDX 变成 FFFFFFFF
        这时候, EDX:EAX 变成 FFFFFFFF FFFFFFFB ,它是一个 64 bit 的大型数字,数值依旧是 -5。

备注:
 EDX:EAX,这里表示EDX,EAX连用表示64位数

在使用div或者idiv指令时,是在用edx:eax除操作数并将结果保存在eax中,余数保存在edx中,最后把余数赋值给b。

void main(){
    int a = 0;
    int b = 1;
    a = a + 11;
    a = a - b;
    a--;
    b++;
    b = a % 3;
}
suanshu22.png

识别if语句

对于一个if语句必定有一个条件跳转
不是所有的条件跳转都对应一个if语句

C语言代码

if1.jpg

汇编代码

if2.jpg

基于cmp的结果进行判断是否继续执行还是跳转到40102B

用IDA Pro图像化分析函数

if3.jpg if4.png
no为红色箭头
yes为绿色箭头
蓝色表示下一个执行块

识别嵌套的if语句

C语言代码

if5.jpg

汇编代码


if6.jpg if7.jpg

1,2,3处发生了三次跳转。

doubleif.png

这类多重if嵌套还是直接看IDA图像比较方便。

识别循环

找到for循环

for循环的四个组件

  • 初始化
  • 比较条件‘
  • 执行代码
  • 递增递减

C语言代码

# include<stdio.h>
void main(){
    int i;
    for(i=0;i<100;i++){
        printf("i equals %d.\n", i);
    }
}

汇编

for1.jpg

IDA Pro图形化


for2.jpg for3.png

当比较语句为false的时候,执行循环四步,有一个很明显的闭环。红的箭头部分是跳出循环,可以进行下一步操作,本示例程序中,没有进行下一步动作,仅清理栈并返回。

找到while循环

C语言代码

while1.jpg

汇编代码


while2.jpg

停止代码的时候应该关注1处的条件跳转。

理解函数的调用约定

函数调用在汇编代码中的表现可能不一样,调用约定决定了函数调用发生的方式。这些约定包含了参数被放在栈上或者寄存器中的顺序,以及是由调用者或者被调函数负责在函数执行完毕后清理栈。

一个函数调用的伪代码
call1.jpg

最常见的三个调用约定:cdecl,stdcall,fastcall,下面讨论他们的关键区别。

1. cdecl

call2.jpg
在cdecl约定中:
参数从右到左按序被压入栈
当函数完成时由调用者清理栈。
将返回值保存在EAX中。

2.stdcall

stdcall主要约定了栈的清理是由被调函数来执行的。stdcall是Windows API的标准调用约定。任何调用这些API的代码都不需要清理栈,清理栈由实现API函数代码的DLL程序所承担

3.fastcall

在fastcall中,前面的一些参数被传到寄存器(典型的是前两个),备用的寄存器是EDX和ECX。如果需要,剩下的参数再以从右到左的次序被加载到栈上。
使用fastcall比其他约定更高效,因为代码不需要涉及过多的栈操作

4.压栈与移动

C代码

mov1.jpg

adder函数汇编代码

move2.jpg
即使是同一种编译器,在调用约定方面也可能存在差异性,这依赖于各种选项和设置
move3.jpg

分析switch语句

if语句通常以两种方式被编译:

使用if样式和跳转表

IF样式

C代码

switch1.jpg

反汇编代码

switch2.jpg
switch3.jpg

采用三对比较跳转,最后一句无条件跳转

IDA Pro图形化

switch4.jpg

整个图线看上去就像是多层嵌套的IF,在if判断为False的情况下执行下一个判断。

switch5.png

跳转表

上面三个case的switch会被编译器编译为if,else结构,但是如果case的数量线性增加,或者刚好我们需要的case在最后一个,那么算法的时间复杂度不就变成了O(n)吗,其实并不是这样,当case的数量增加,编译器会使用跳转表优化代码,降低算法的时间复杂度。

C语言代码

#include<stdio.h>
void main(){
    int i=2;
    switch(i)
    {
        case 1:
        printf("1");
        break;
        case 2:
        printf("2");
        break;
        case 3:
        printf("3");
        break;
        case 4:
        printf("4");
        break;
        case 5:
        printf("5");
        break;
        default:
        break;
    }
}
jump0.jpg
jump1.jpg jump2.jpg
jump3-1.png

[rbp+var_4]存储的是case 一共有六种case(从0到5)所以是和case-1做比较

jump3-2.png

lea rdx, ds:0[rax*4]这个指令中是rax*4加上跳转表的基址来确定要跳转到哪一个case块,乘以四是因为跳转表中每一项是一个4字节大小的地址。

jump3-3.png

IDA Pro上有标记这是跳转表。

反汇编数组

数组a是局部定义的,数组b是全局定义的

C代码

string1.jpg

汇编代码

string2.jpg string3.png

eax是索 引,每个元素的大小是4,数据的基址加上偏移来访问正确的数组元素。

string4.png

识别结构体

C代码

struct1.jpg

main函数反汇编代码

struct2.jpg

test函数反汇编代码

struct3.jpg struct4.jpg

分析链表遍历

C代码

list1.jpg list2.jpg

汇编代码

list3.jpg list4.jpg

小结

  • 从细节中抽象
  • 不要过度死磕汇编,从全局角度看待问题

相关文章

网友评论

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

      本文链接:https://www.haomeiwen.com/subject/oubhqrtx.html