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