How to study C && ASM Code(4)

本文通过实例解析了C语言中if、for、do-while和switch-case等控制结构如何转换为汇编语言。从基本的if语句到复杂的循环结构,详细介绍了每种控制结构对应的汇编指令及其执行流程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


                    ==bbs.cciss.cn.==
                            
                    How to study C && ASM Code(4)

|=---------------=[ 控制语句的逆向 ]=------------------------------=|
|=-----------------------------------------------------------------=|
|=---------------=[ 7all<7all7_at_163.com> ]=----------------------=|
|=-----------------------------------------------------------------=|
|=---------------=[ 版权所有:www.cciss.cn ]=-----------------------=|

--[ Joke
  在分析完最后一组数据包后,工作终于有了大的进展.先自己祝贺自己一下,不知道
是上帝看我太笨,还是上帝嫌我太无知.总是我山穷水尽的时候,给我一点胜利的曙光.
于是要我本来绷的很紧的神经可以稍微的缓和下.大概在1个月以前就想着继续写,等
了一个多月,唯有今天晚上最值得庆贺,于是爬起来写点蹩脚的东西.

--[ 前言
  C的控制语句有判断语句,循环语句等,汇编与之相关的指令有jmp(无条件跳转),jz(条
件跳转),jnz(条件跳转)等.而我们本文档想说明的正是C的控制语句与汇编的有条件跳转
无条件跳转指令之间的关系.具体的指令建议大家参考相关的资料,下面的内容我们直接
从C->ASM的过程来分析下相关的内容.文档不妥之处,希望大家给予指正.
 
--[ 判断语句 
  下面我们根据简单的代码来拟向下这些C代码的汇编代码,这次不说废话了,直接从code开始.
不断的code才可以不断的进步,否则缺少了调试程序的环节,写代码就失去了很多的意义.
Example: lesson4.c
[code]
#include <stdio.h>
int main()
{
 int i = 1;
 if (i != 1) {
  printf("i != 1/n/n");
 }
 else{
  printf("i == 1/n/n");
 }
 return 0;
}
[/code]
因为只是简单的测试代码,我们直接设置判断条件,该程序的执行流程很简单,判断i是否等于
1,等于1则打印i == 1,然后执行到return 0,程序退出.
我们首先看下lesson4.c在VC6下面的反汇编代码,断点位置为int i = 1;
反汇编代码如下:
[code]
3:    int main()
4:    {
00401010   push        ebp
00401011   mov         ebp,esp
00401013   sub         esp,44h
00401016   push        ebx
00401017   push        esi
00401018   push        edi
00401019   lea         edi,[ebp-44h]
0040101C   mov         ecx,11h
00401021   mov         eax,0CCCCCCCCh
00401026   rep stos    dword ptr [edi]     //前面为建立堆栈框架,在前面文章有提到:)
5:        int i = 1;
/*
ebp-4地址为声明的int型变量i的地址,这里实现了对i变量的赋值:)
在前面文章也有所提及,这里不再具体说明.
*/
00401028   mov         dword ptr [ebp-4],1
6:        if (i != 1) {
/*
使用cmp指令比较ebp-4及变量i的值是否等于1
*/
0040102F   cmp         dword ptr [ebp-4],1
/*
如果等于1,则je指令成功跳转到0x00401044地址
je指令为条件跳转指令,符合则跳转.
*/ 
00401033   je          main+34h (00401044)
/*
下面内容为调用pritf函数,调用不成功信息
*/
7:            printf("i != 1/n/n");
00401035   push        offset string "i != 1/n/n" (00422024)
0040103A   call        printf (00401080)
0040103F   add         esp,4
8:        }
9:        else{
/*
如果i!=1,则跳转到程序执行结束处.
*/
00401042   jmp         main+41h (00401051)
/*
调用printf函数,打印成功信息
*/
10:           printf("i == 1/n/n");
00401044   push        offset string "i == 1/n/n" (00422fb4)
00401049   call        printf (00401080)
0040104E   add         esp,4
11:       }
12:       return 0;
00401051   xor         eax,eax
13:   }

[/code]
  通过上面的分析,应该已经大体熟悉了该程序在拟向后的一些运行流程.下面我简单
描述下上面拟向后的代码的简单流程.
A: mov dword ptr [ebp-4],1 /*这里的ebp-4为变量i分配的内存地址*/
B: cmp dword ptr [ebp-4],1 /*比较ebp-4是否等于1,根据结果判断是否执行下面的跳转语句*/
C: je  main+34h (00401044) /*若条件成立,则跳转到0x00401044地址处,并打印成功信息*/
D: 若上面条件不成立则执行到打印错误信息的printf函数处,打印错误信息.
E: 执行到程序结尾,退出程序.

  下面我们根据上面的简单流程与分析,对该代码进行拟向,并最终实现与C代码相同的功能.
最终代码如下所示:
lesson4.c(汇编版本)
[code]
#include <stdio.h>

int main()
{
 /*int i = 1;
 if (i != 1) {
  printf("i != 1/n/n");
 }
 else{
  printf("i == 1/n/n");
 }*/
 /*
 下面为嵌入式汇编代码
 */
 int i; //声明变量i
 /*
 声明指针类型字符串变量
 注意这里不能声明为数组类型,原因自己考虑下:)
 */
 static char *str1 = "i != 1/n";
 static char *str2 = "i == 1/n";
 __asm{
  pushad;
  mov  i, 0x01;  /*对i赋值为1*/
  cmp  i, 0x01;  /*比较i是否等于1*/
  jz   lab_success; /*成功则跳转到打印成功信息处*/
  push str1; /*printf函数的参数入栈*/
  call printf;
  jmp  lab_error; /*打印完失败信息后跳转到程序执行结束处*/
lab_success: /*成功信息标记*/
  push str2; /*printf函数的参数入栈*/
  call printf;
lab_error: /*错误信息标记*/
  //xor eax,eax; /*可以添加一条随意的指令,如nop,也可不写*/
 }
 /*
 这里需要重点解释下,细心的朋友可能发现了,我们前面的代码是return 0,
 这里为什么变为exit(0)呢?
 答案:为了保持程序的堆栈平衡.
 使用return 0时,在程序退出时会调用_chkesp函数,举例代码如下:
 cmp         ebp,esp
  call        __chkesp (00401100)
  调用__chkesp是检测ebp与esp,因为我们前面在潜入汇编时对程序本来的堆栈
  有所影响,所以在执行到__chkesp函数时,程序会报错.
  而exit函数与return返回的不同之处正在于exit不会检查堆栈是否平衡而直接退出.
  在我们保证我们前面代码运行正常的情况下,如此解决这个问题算是一个折中的办法.
 */
 exit(0);
}
[/code]
  当面上面的例子只是个简单的判断语句的拟向过程,具体的情况要复杂很多,不过了解
其原理后,在慢慢熟悉相关技术后,应该对以后拟向大的代码有些许帮助:)

--] 循环语句
  Code,继续从Code开始.下面一个简单的for循环语句的C代码.
Example: lesson41.c
[code]
#include <stdio.h>
int main()
{
 int i;
 for (i = 0; i < 3; i++) {
  printf("loop testing/n");
 }
 exit(0);
}
[/code]
上面的C程序很简单,声明一个整型i变量,i从等于0到小于3这个条件来循环打印三遍.
我们在int i处下断点,利用VC6得到的拟向结果如下:
[code]
3:    int main()
4:    {
00401010   push        ebp
00401011   mov         ebp,esp
00401013   sub         esp,44h
00401016   push        ebx
00401017   push        esi
00401018   push        edi
00401019   lea         edi,[ebp-44h]
0040101C   mov         ecx,11h
00401021   mov         eax,0CCCCCCCCh
00401026   rep stos    dword ptr [edi] /*建立堆栈框架*/
5:        int i;
6:        for (i = 0; i < 3; i++) {
/*
ebp-4为变量i的内存地址,这里对i进行变量初始化,及i=0;
也即 mov dword ptr [ebp-4],0
*/
00401028   mov         dword ptr [ebp-4],0
/*
jmp无条件跳转到0x0040103a,
0x0040103a正好为i<3的判断条件处.
*/
0040102F   jmp         main+2Ah (0040103a)
/*
mov eax,dword ptr [ebp-4]
使eax等于变量i的值,以eax为循环计数器,以次做为判断条件
*/
00401031   mov         eax,dword ptr [ebp-4]
/*
add eax,1
add指令的用法即:eax=eax+1
这里eax会不断的加1直到满足循环条件为止.
*/
00401034   add         eax,1
/*
因为ebp-4及变量i的内存地址,
这里mov dword ptr [ebp-4],eax
即为i++的过程.
*/
00401037   mov         dword ptr [ebp-4],eax
/*
比较是否小于3
*/
0040103A   cmp         dword ptr [ebp-4],3
/*
jge指令也为有条件跳转指令,其用法为大于等于则跳转
如果条件满足,则跳转到exit函数,退出程序
*/
0040103E   jge         main+3Fh (0040104f)
7:            printf("loop testing/n");
/*
调用printf打印测试字符串
*/
00401040   push        offset string "loop test/n/n" (0042201c)
00401045   call        printf (00401070)
0040104A   add         esp,4
8:        }
/*
跳转到条件判断处,继续循环执行,直到条件满足
*/
0040104D   jmp         main+21h (00401031)
9:        exit(0);
0040104F   push        0
00401051   call        exit (00402d50)
00401056   add         esp,4
10:   }
[/code]
通过上面对拟向后汇编代码的部分解释,我相信大家都对该过程有所熟悉,下面我
也对其流程进行简单的描述.
A: mov dword ptr [ebp-4],0 /*变量赋值的过程,这里也可以把ebp-4比喻为一个变量*/
B: jmp main+2Ah (0040103a) /*0x0040103a正是判断是否满足条件的语句处,对应i<3*/
C: cmp dword ptr [ebp-4],3 /*无条件跳转到这里,比较ebp-4与3的大小关系*/
D: jge main+3Fh (0040104f) /*若大于等于3,则跳转到0x0040104f处,程序退出*/
E: 若步骤D的条件不成立,则执行下面的步骤
F: printf("loop testing/n")/*打印测试字符串*/
G: jmp main+21h (00401031) /*无条件跳转到0x00401031,直到条件满足*/
H: mov eax,dword ptr [ebp-4]/*把eax做为内部循环操作的一个标记*/
I: 对于H步骤而言,在Intel的文档内对各个寄存器的作用有相关的描述,大家可以参考
   Intel官方网站的资料,实际情况还需要具体分析.
J: add eax,1 /*对eax加1,实现自增运算*/
K: mov dword ptr [ebp-4],eax /*赋值eax给ebp-4,对ebp-4与3对比大小*/
L: cmp dword ptr [ebp-4],3 /*比较条件,看步骤D是否成功跳转*/

下面来看针对上面的步骤进行的拟向代码:
[code]
#include <stdio.h>

int main()
{
/* int i;
 for (i = 0; i < 3; i++) {
  printf("loop testing/n");
 }*/
 int i;
 static char *str = "loop test/n/n";
 __asm{
  mov  i,0x00;  /*变量i赋值*/
  jmp  loopfun; /*跳转到判断处*/
addcount:
  mov  eax,i;   /*使用eax做为循环计数器*/
  add  eax,1;   /*eax=eax+1*/
  mov  i,eax;   /*i=eax,使eax可以循环加1*/
loopfun:
  cmp  eax,3;   /*比较eax与3的大小,以此循环*/
  jge  quit;    /*若eax大于等于3,则退出*/
  push str;
  call printf;
  jmp  addcount; /*跳转到循环计数器处,直到条件成立*/
quit:
  nop; /*也可无指令:)*/
 }
 exit(0);
}
[/code]

下面继续看一个循环的C代码:)
[code]
#include <stdio.h>

int main()
{
 int i = 0;
 do {
  printf("do while loop/n/n");
  i = i + 1;
 } while(i < 3);
 exit(0);
}
[/code]
反汇编代码
[code]
5:        int i = 0;
00401028   mov         dword ptr [ebp-4],0
6:        do {
7:            printf("do while loop/n/n");
0040102F   push        offset string "do while loop/n/n" (0042201c)
00401034   call        printf (00401070)
00401039   add         esp,4
8:            i = i + 1;
0040103C   mov         eax,dword ptr [ebp-4]
0040103F   add         eax,1
00401042   mov         dword ptr [ebp-4],eax
9:        } while(i < 3);
00401045   cmp         dword ptr [ebp-4],3
00401049   jl          main+1Fh (0040102f) /*jl指令为小于则跳转,属于短跳转指令*/
10:       exit(0);
0040104B   push        0
0040104D   call        exit (00402d50)
[/code]

拟向代码
[code]
#include <stdio.h>
int main()
{
/* int i = 0;
 do {
  printf("do while loop/n/n");
  i = i + 1;
 } while(i < 3);*/
 int i;
 static char *str = "do while loop/n/n";
 __asm{
  mov  i,0x00;
loopcount:
  push str;
  call printf;
  mov  eax,i;
  add  eax,1;
  mov  i,eax;
  cmp  i,3;
  jl   loopcount;
 }
 exit(0);
}
[/code]
由于前面对for循环进行了详细的说明,这里不再进行详细的说明,好像do while循环比for
循环的代码简单了很多:)考虑代码性能的话...hmmm...

--] switch case选择语句
  拟向switch是很有意思的事情,对switch的拟向就好像在喝一杯咖啡,越喝越感觉到咖啡的
香气:)下面我们开始,code...继续从code开始.
Example; lesson43.c
[code]
#include <stdio.h>
int main()
{
 int i = 0;
 switch(i) {
 case 0:
  printf("i = 0/n/n");
  break;
 case 1:
  printf("i = 1/n/n");
  break;
 case 2:
  printf("i = 2/n/n");
  break;
 default:
  printf("default/n/n");
 }
 exit(0);
}
[/code]

反编译...继续拿VC反编译,代码如下:
[code]
5:        int i = 0;
/*ebp-4=i,对变量赋值*/
00401028   mov         dword ptr [ebp-4],0
6:        switch(i) {
/*首先以eax做为一个计数器*/
0040102F   mov         eax,dword ptr [ebp-4]
/*
重新开辟一内存地址用于存放case的结果
我觉得这里可以理解为重新建立一个变量i的copy
*/
00401032   mov         dword ptr [ebp-8],eax
/*以ebp-8处做为case选择的条件对比*/
00401035   cmp         dword ptr [ebp-8],0
/*je指令为等于则跳转*/
00401039   je          main+39h (00401049)
0040103B   cmp         dword ptr [ebp-8],1
0040103F   je          main+48h (00401058)
00401041   cmp         dword ptr [ebp-8],2
00401045   je          main+57h (00401067)
/*上面判断完case条件后,若都不满足,则jmp到default处*/
00401047   jmp         main+66h (00401076)
7:        case 0:
8:            printf("i = 0/n/n");
00401049   push        offset string "i = 0/n/n" (00422fc4)
0040104E   call        printf (004010a0)
00401053   add         esp,4
9:            break;
00401056   jmp         main+73h (00401083)
10:       case 1:
11:           printf("i = 1/n/n");
00401058   push        offset string "i = 1/n/n" (00422034)
0040105D   call        printf (004010a0)
00401062   add         esp,4
12:           break;
00401065   jmp         main+73h (00401083)
13:       case 2:
14:           printf("i = 2/n/n");
00401067   push        offset string "i = 2/n/n" (00422028)
0040106C   call        printf (004010a0)
00401071   add         esp,4
15:           break;
00401074   jmp         main+73h (00401083)
16:       default:
17:           printf("default/n/n");
00401076   push        offset string "default/n/n" (0042201c)
0040107B   call        printf (004010a0)
00401080   add         esp,4
18:       }
19:       exit(0);
00401083   push        0
00401085   call        exit (00402d80)
[/code]
有了上面对判断语句,循环语句的详细分析说明,我相信大家对switch语句的
汇编代码应该没有太多的问题存在了,如果有问题可以mail给我,大家一起讨
论下相关内容:)下面我直接给出拟向的代码.
[code]
#include <stdio.h>

int main()
{
/*int i = 0;
switch(i) {
case 0:
printf("i = 0/n/n");
break;
case 1:
printf("i = 1/n/n");
break;
case 2:
printf("i = 2/n/n");
break;
default:
printf("default/n/n");
}*/
 int i;
 static char *str1 = "i = 0/n/n";
 static char *str2 = "i = 1/n/n";
 static char *str3 = "i = 2/n/n";
 static char *str4 = "default/n/n";
 __asm{
  mov  i,0x00;
  mov  eax,i;
  cmp  eax,0x00;
  je   lab1;
  cmp  eax,0x01;
  je   lab2;
  cmp  eax,0x02;
  je   lab3;
  jmp  lab4;
lab1:
  push str1;
  call printf;
  jmp  labfinish;
lab2:
  push str2;
  call printf;
  jmp  labfinish;
lab3:
  push str3;
  call printf;
  jmp  labfinish;
lab4:
  push str4;
  call printf;
  jmp  labfinish;
labfinish:
  nop;
 }
 
 exit(0);
}
[/code]

--] 总结
  不知不觉三个小时过去了,希望这三个小时对大家有帮助:)对于文中不足之处,请指正.

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值