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

2021-12-23  本文已影响0人  doinb1517

全局和局部变量

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

代码清单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

小结

上一篇 下一篇

猜你喜欢

热点阅读