C语言代码逆向的实战十篇(原创)
以前一直仰慕会找0day,病毒分析的大牛,便开始学习反汇编,现在稍微接触脱壳破解,发现说教程的人似乎对反汇编也不是那么的熟悉,我认为算法分析就应该是将整个注册码换算的流程翻译成自己能认识的语言,至少了然于心吧,而不是来到一个call,eax返回了一个注册码的地址,便注释成“获取注册码的函数”,这又何来的分析呢。于是把前些天做的反汇编笔记发出来分享下。一共十篇,开始是debug版,后来是release的。白痴的CRACKME和白痴的反汇编分析MFC写的垃圾CRACKME
void CCrackMeDlg::OnButton1()
{
MessageBox("flag!!!");
CWnd *h1;
CString s1;
char *a;
int r;
int c=IDC_EDIT1;
h1=GetDlgItem(IDC_EDIT1);
if(h1!=0)
{
h1->GetWindowText(s1);
a=s1.GetBuffer(6);
r=strcmp(a,"123456");
if(r==0)
MessageBox("yes");
else
MessageBox("no");
}
}
OD反出来的代码
0040147D 90 NOP
0040147E 90 NOP
0040147F 90 NOP
00401480 . 6A FF PUSH -1
00401482 . 68 581A4000 PUSH CrackMe.00401A58 ;SE handler installation
00401487 . 64:A1 00000000 MOV EAX,DWORD PTR FS:
0040148D . 50 PUSH EAX
0040148E . 64:8925 000000>MOV DWORD PTR FS:,ESP
00401495 . 51 PUSH ECX
00401496 . 57 PUSH EDI
00401497 . 6A 00 PUSH 0
00401499 . 6A 00 PUSH 0
0040149B . 8BF9 MOV EDI,ECX
0040149D . 68 30304000 PUSH CrackMe.00403030 ;ASCII "flag!!!"(我特意在这里弹个信息框,我为了提示自己按钮事件的代码是从这里开始的!)
004014A2 . E8 E3020000 CALL <JMP.&MFC42.#4224> ;MessageBox("flag!!!");
004014A7 . 8D4C24 04 LEA ECX,DWORD PTR SS:
004014AB . E8 AA020000 CALL <JMP.&MFC42.#540>
004014B0 . 68 E8030000 PUSH 3E8 ;push 编辑框ID
004014B5 . 8BCF MOV ECX,EDI
004014B7 . C74424 14 0000>MOV DWORD PTR SS:,0
004014BF . E8 C0020000 CALL <JMP.&MFC42.#3092> ;h1=GetDlgItem(IDC_EDIT1);//取得编辑框句柄
004014C4 . 85C0 TEST EAX,EAX
004014C6 . 74 68 JE SHORT CrackMe.00401530 ;如果h1为0则跳到00401530
004014C8 . 53 PUSH EBX ;h1不为0就从这里开始往下走
004014C9 . 8D4C24 08 LEA ECX,DWORD PTR SS: ;将一个局部变量给ecx以便把ecx压入堆栈
004014CD . 56 PUSH ESI
004014CE . 51 PUSH ECX
004014CF . 8BC8 MOV ECX,EAX
004014D1 . E8 A8020000 CALL <JMP.&MFC42.#3874> ;h1->GetWindowText(s1);//得到字符串
004014D6 . 6A 06 PUSH 6
004014D8 . 8D4C24 10 LEA ECX,DWORD PTR SS:
004014DC . E8 97020000 CALL <JMP.&MFC42.#2915> ;a=s1.GetBuffer(6);//转为char
004014E1 . BE 28304000 MOV ESI,CrackMe.00403028 ;ASCII "123456"
004014E6 > 8A10 MOV DL,BYTE PTR DS: ;从这里开始应该是把C的库函数strcmp给弄过来了
004014E8 . 8A1E MOV BL,BYTE PTR DS: ;有时间看看人家的strcmp是怎么实现的!
004014EA . 8ACA MOV CL,DL
004014EC . 3AD3 CMP DL,BL
004014EE . 75 1E JNZ SHORT CrackMe.0040150E
004014F0 . 84C9 TEST CL,CL
004014F2 . 74 16 JE SHORT CrackMe.0040150A
004014F4 . 8A50 01 MOV DL,BYTE PTR DS:
004014F7 . 8A5E 01 MOV BL,BYTE PTR DS:
004014FA . 8ACA MOV CL,DL
004014FC . 3AD3 CMP DL,BL
004014FE . 75 0E JNZ SHORT CrackMe.0040150E
00401500 . 83C0 02 ADD EAX,2
00401503 . 83C6 02 ADD ESI,2
00401506 . 84C9 TEST CL,CL
00401508 .^75 DC JNZ SHORT CrackMe.004014E6
0040150A > 33C0 XOR EAX,EAX
0040150C . EB 05 JMP SHORT CrackMe.00401513
0040150E > 1BC0 SBB EAX,EAX
00401510 . 83D8 FF SBB EAX,-1
00401513 > 5E POP ESI
00401514 . 5B POP EBX
00401515 . 85C0 TEST EAX,EAX
00401517 . 6A 00 PUSH 0
00401519 . 6A 00 PUSH 0
0040151B . 75 07 JNZ SHORT CrackMe.00401524 ;上面应该是把C的库函数strcmp给弄过来了,在这里(不为0跳到no)
0040151D . 68 24304000 PUSH CrackMe.00403024 ;ASCII "yes"
00401522 . EB 05 JMP SHORT CrackMe.00401529
00401524 > 68 20304000 PUSH CrackMe.00403020 ;ASCII "no"
00401529 > 8BCF MOV ECX,EDI
0040152B . E8 5A020000 CALL <JMP.&MFC42.#4224> ;真会省的!只用了一个MessageBox!要知道我们用C++是写了两次啊!
00401530 > 8D4C24 04 LEA ECX,DWORD PTR SS: ;在遥远的上面的JE跳到这里来,然后就不关我们事啦
00401534 . C74424 10 FFFF>MOV DWORD PTR SS:,-1
0040153C . E8 0D020000 CALL <JMP.&MFC42.#800>
00401541 . 8B4C24 08 MOV ECX,DWORD PTR SS:
00401545 . 5F POP EDI
00401546 . 64:890D 000000>MOV DWORD PTR FS:,ECX
0040154D . 83C4 10 ADD ESP,10
00401550 . C3 RETN ;看,在上面再处理一下后事,这个事件就退出了
00401551 90 NOP
00401552 90 NOP
00401553 90 NOP
每学一种新技术都有新的HELLO WORLD!
带你阅读VC生成的经典反汇编代码,提高反汇编阅读能力没事在百度逛了下,看见有这样的方法提高反汇编能力!
刚写的关于如何提高反汇编代码阅读能力的帖子,顺便把一些call啊,堆栈平衡啊,局部变量访问啊这些东西结合实例说了一下
把自己写好的“hello world”程序源码加载到VC中,给main函数下个断点(F9下断点)
然后按F5运行至断点,VC把程序断下来的时候在断点代码处 右键菜单go to disassembly就可以看汇编代码了!
而且还是一行一行,源码对照反汇编解释给你看的。。
写一个小程序
#include <stdio.h>
void main(int argc[],char *argv[])
{
int a,b;
a=1;
b=2;
a=a+b;
}
然后按照我刚才说的方法看汇编代码。。
用VC看汇编代码的好处就是能对照着看,坏处就是,如果你想看指定内存地址或者寄存器的数据的话,得自己在监视窗口中添加,很麻烦,调试程序时通常要眼观八方,注意数据寄存器,内存地址,和堆栈发生的变化,所以明显VC翻译汇编代码的功能着实有点好处,但比起OD来还是有点麻烦,那还是用回OD吧,程序载入时先执行的其实不是main函数,而是编译器生成的一些初始化代码,这些代码暂且不讨论吧,直接来到main函数吧,在OD中怎么到达main函数呢,在这里介绍一个技巧:
将程序改写成如下:
#include <stdio.h>
void main(int argc[],char *argv[])
{
char *a="1234567";
printf("%s",a);
然后将程序载入OD,右键-查看-所有参考文本串,在弹出的窗口中招字符串“12345”,然后双击这个字符串,OD就会把你带到main函数的附近了,到了main函数附近之后,就找push ebp吧(程序的经典开头代码)
下面把完整代码贴一遍(注意蓝色为main函数中代码 红色部分为一个校验堆栈平衡的一个函数。)
00401010 >55 push ebp ; 保存原来的ebp
00401011 8BEC mov ebp,esp ; 使得ebp=esp
00401013 83EC 44 sub esp,44 ; 预留44h个字节的空间
上面一句的详细解释:上面预留的44h个字节空间即68个字节的空间,即预留17个变量的空间(每个变量4个字节),其中我们的程序定义了一个变量--即指向常量字符串的指针a
00401016 53 push ebx
00401017 56 push esi
00401018 57 push edi
;上面三句是因为后面要用到这三个寄存器 所以为了程序执行完后不改变寄存器值 而保存他们,程序执行完后恢复
00401019 8D7D BC lea edi,dword ptr ss:;将edi设置为栈中一块连续区域的首地址
0040101C B9 11000000 mov ecx,11 ; 11h=17,为什么要给ecx赋予17呢,我们往下看
00401021 B8 CCCCCCCC mov eax,CCCCCCCC ;为什么要给eax赋予8个c呢,我们往下看
00401026 F3:AB rep stos dword ptr es:
现在开始解释上面三句,我先讲第三句吧,第三句的功能就是:将eax寄存器的值复制给由es:指向的内存区域,复制多少次呢?
ecx次!就是17次,就是将17个eax放到以es:为首地址的内存区域中,这样做没别的意思,就是初始化变量而已。
00401028 C745 FC 1C00420>mov dword ptr ss:,CPPlearn.>; 把字符串常量的指针给ss:也就是我们定义的指针变量a
0040102F 8B45 FC mov eax,dword ptr ss: ; 再将指a赋值给eax
00401032 50 push eax
00401033 68 B0074200 push CPPlearn.004207B0 ; ASCII "%s"
00401038 E8 53000000 call CPPlearn.printf
0040103D 83C4 08 add esp,8 ; 压栈调用函数,然后由主程序恢复堆栈平衡 压栈调用函数,你懂的!
00401040 5F pop edi
00401041 5E pop esi
00401042 5B pop ebx ; 犯罪现场重现
00401043 83C4 44 add esp,44 ; 释放掉占44h字节大小的在main中的所有变量
00401046 3BEC cmp ebp,esp,在这里如果因为一些意外而使得恢复之后的esp不等于原来的esp(原来的esp保存在ebp)的话,将会出现一些问题,继续往下看
00401048 E8 C3000000 call CPPlearn._chkesp
{
00401110 > /75 01 jnz short CPPlearn.00401113 ; 当ebp与esp相等时,属于正常情况,所以就不跳转了直接 返回主程序,其实我觉得这个判断应该由主程序来做,那就不用进call了,进call要浪费点时间。这里为了跟踪代码,我故意将Z标志位改成0(即比较结果不为0)
所以他的jnz就能实现跳转了。(跳到下面的401112去了)
00401112 |C3 retn
00401113 \55 push ebp ; 又一个子程序,犯罪现场保存恢复这些垃圾话我就不说了
00401114 8BEC mov ebp,esp
00401116 83EC 00 sub esp,0
00401119 50 push eax
0040111A 52 push edx
0040111B 53 push ebx
0040111C 56 push esi
0040111D 57 push edi
0040111E 68 54004200 push CPPlearn.00420054 ; ASCII "The value of ESP was not properly saved across a function call.This is usually a result of calling a function declared with one calling convention with a function pointer declared with a different calling convention. "
00401123 68 50004200 push CPPlearn.00420050
00401128 6A 2A push 2A
0040112A 68 40004200 push CPPlearn.00420040 ; ASCII "i386\chkesp.c"
0040112F 6A 01 push 1
00401131 E8 CA140000 call CPPlearn._CrtDbgReport//在这里会弹出一个警告的对话框,里面有调试按钮,忽略按钮 等等
00401136 83C4 14 add esp,14
00401139 83F8 01 cmp eax,1 ; 在这里我点击了ignore按钮,所以跳过了下面的int 3指令
0040113C 75 01 jnz short CPPlearn.0040113F
0040113E CC int3
0040113F 5F pop edi ; 然后一直走着走着就回去了~~
00401140 5E pop esi
00401141 5B pop ebx
00401142 5A pop edx
00401143 58 pop eax
00401144 8BE5 mov esp,ebp
00401146 5D pop ebp
00401147 C3 retn
}
0040104D 8BE5 mov esp,ebp ; 程序执行完了,esp自然要恢复了
0040104F 5D pop ebp ; 把原来的ebp要回来
00401050 C3 retn
挺不错的。确实是一种学习的方法 分析一段C语言算法后,我懂得了汇编用于写程序的用途的抛弃的必然性
C语言一个简单算法的代码 没神马意义,就一个两层嵌套循环对一个二维数组的操作而已。
(本文中有QQ空间的图,如果图挂了 请到我qq空间看,不好意思 http://user.qzone.qq.com/315102821/blog/1334651812)
http://b152.photo.store.qq.com/psb?/V11E7sIg3gcp5I/3tIAWQXek7oqIdRpRwvcCwLtJnhvchiZAAYQ6hGtaxE!/b/YXQfp1qFkQAAYgztmlpLjgAA看汇编代码,几张图是连着的。
http://b154.photo.store.qq.com/psb?/V11E7sIg3gcp5I/VyVjSWpQMk1ae3pLP6Mm1amYRzu7Ixr8z4YR9CDfPX8!/b/YbpD1Vs5JwAAYr6m0FueIAAA
http://b154.photo.store.qq.com/psb?/V11E7sIg3gcp5I/.1.52WaSyMZx6l4KfmFBrnYeKHml71Lu*VKe1evYn4I!/b/Ydcvz1uuJgAAYsuYzVuHJgAA
http://b154.photo.store.qq.com/psb?/V11E7sIg3gcp5I/bL4yQ837sP4wTsyy4yCfpQFD.D*hpMwDOPv1K7nDyqI!/b/YVor0ltPKAAAYh1K1Vv8IAAA
循环语句那一段经典代码我就不解释了。。参考<<天书夜读 从汇编语言到windows内核>>吧主要分析地址为411A6E到411AC1这里面的汇编语句吧(其实这里面就等于c=a * b + a * b + a * b;) 注意一下的话可以发现,mov 寄存器,dword ptr 这一句,经常出现,出现了四次,我以每次这条语句出现的地方作为一部分来讲解吧
看着这条表达式吧方便参考:c=a * b + a * b + a * b;
-------------------------------------第一部分-------------------------------------
eax=i*0Ch; 结构体元素偏移地址=第几个结构体*结构体元素大小ecx=a;
esi=b;
edx=j;eax=ecx+eax; 结构元素地址=数组首址+结构体元素偏移地址
eax=eax*(esi+edx*4);
;esi是数组b的首址,edx是j,j=0 to 2,0=0*4(第0个下标元素)
4=1*4(第1个下标元素)
8=2*4(第2个下标元素)
因为一维下标总是0所以才可以用这种方式寻址数组
-------------------------------------第一部分--------------------------------------------------------------------------第二部分-------------------------------------
ecx=i*0Ch; 结构体元素偏移地址=第几个结构体*结构体元素大小edx=a;
edi=b;
esi=j;ecx=edx+ecx+4; 结构元素地址=数组首址+结构体元素偏移地址+4个字节(不加4字节就是第0个下标元素,加了自然就是第一个了)
ecx=ecx*(edi+esi*4+0ch); ecx求出来了,现在要累乘,结果还是放在ecx。edi为b首址,esi为j j*4字节就是:第j个元素所在字节位置,
然后再加了0ch是因为一维坐标始终为1,即一维坐标为0时造成的3个int元素
我们还得加上去,3*4字节=12字节=0ch字节,所以要加0ch
add eax,ecx; 已经有两个表达式计算出来了,所以先将它们加起来吧
-------------------------------------第二部分--------------------------------------------------------------------------第三部分-------------------------------------
edx=i*0Ch; 结构体元素偏移地址=第几个结构体*结构体元素大小ecx=a;
edi=b;
esi=j;edx=edi+edx+8; b的首址+结构体元素偏移地址+第2个下标元素(即第3个数组元素,所以+8字节)
edx=edx*(edi+(esi*4)+18h) edx照乘,被乘数为b的首址+一维下标为2造成的18h个字节的占用+j*4字节。add eax,edx 最后结果在eax中了-------------------------------------第三部分--------------------------------------------------------------------------第四部分-------------------------------------
ecx=i*0Ch; 结构体元素偏移地址=第几个结构体*结构体元素大小ecx=ecx+cedx=j
ecx+edx*4=eax; 这句当然是把结果给c了
-------------------------------------第四部分-------------------------------------
哇咔咔看看 判断素数
在百度搜了个C语言常用算法.docx。便拿起最简单的一个小算法来练习下反汇编了
程序源代码:#include<stdio.h>#include<stdlib.h> int sushu(int m); void main(){ int m,k; printf("please input a number:\n"); scanf("%d",&m); k=sushu(m); if(k==m) printf("该数是素数\n"); else printf("该数不是素数\n"); system("pause");} int sushu(int m){int i;for(i=2;i*i<=m;i++) if(m%i==0) return 0;return m;} 逆向思路:用OD打开程序 F8单步走,走到一个Call 卡主,因为程序在正式运行了,要求输入了在那个call下断点重新加载程序 F9运行到断点,然后F7跟进CALL。Call里面是主程序内容,这里主要分析sushu函数,再走几步找到sushu函数进去,汇编代码:00401000 56 push esi
00401001 8B7424 08 mov esi,dword ptr ss: ; esi作传进来的参数
00401005 83FE 04 cmp esi,4 ; m<=i*i
00401008 B9 02000000 mov ecx,2 ; i=2
0040100D 7C 14 jl short asm.00401023 ; 小于4的话直接跳过 返回素数本身
0040100F 90 nop
00401010 8BC6 mov eax,esi ; 参数暂时给eax
00401012 99 cdq ; 将eax的符号位扩展到edx
00401013 F7F9 idiv ecx ; 合并后除以ecx 商放在eax 余数放在edx
00401015 85D2 test edx,edx
00401017 74 0E je short asm.00401027 ; 如果等于0,即没有余数 就判断它不是素数
00401019 41 inc ecx ; i++;
0040101A 8BC1 mov eax,ecx
0040101C 0FAFC1 imul eax,ecx ; 综合两句得 i*i
0040101F 3BC6 cmp eax,esi
00401021^ 7E ED jle short asm.00401010 ; i*i小于等于4则循环继续
00401023 8BC6 mov eax,esi
00401025 5E pop esi
00401026 C3 retn
00401027 33C0 xor eax,eax
00401029 5E pop esi
0040102A C3 retn ---------------------------------------多多练习! 随机生成一百个数 个位为x的数有y个 求x和y这次笔记难度加大一点了!有比较多的跳转和循环,还包含数组寻址~~~
笔记还是整理漂亮一点吧,以后可能要拿来做教程。
C源代码,,VC编译无压力过:
#include <stdio.h>
#include <stdlib.h>
void main()
{
int a,x,i,p;
for(i=0;i<=10;i++)
x=0;
for(i=1;i<=100;i++)
{
a=rand() % 100;
printf("%4d",a);
if(i%10==0)
printf("\n");
}
for(i=1;i<=100;i++)
{ p=a%10;
if(p==0) p=10;
x=x+1;
}
for(i=1;i<=10;i++)
{ p=i;
if(i==10) p=0;
printf("%d,%d\n",p,x);
}
printf("\n");
system("pause");
}
(之前从VS2008复制过来的时候代码有高亮的,现在换了VC6就木有了。。)
进入正题!
照样F8到程序跑飞,断下Call F7跟进代码如下
00401000 81EC C0010000 sub esp,1C0 ; 1c0=448,448=101*4+11*4
00401006 B9 0B000000 mov ecx,0B ; ecx=11
0040100B 33C0 xor eax,eax ; eax=0
0040100D 56 push esi
0040100E 57 push edi
0040100F 8D7C24 08 lea edi,dword ptr ss: ; 访问缓冲区的地址
00401013 BE 01000000 mov esi,1 ; esi=1
00401018 F3:AB rep stos dword ptr es: ; 将eax往es:放11次
0040101A 8D7C24 38 lea edi,dword ptr ss: ; 访问数组x的末尾+1也就是数组a的起始
0040101E E8 74010000 call asm.00401197 ; rand
00401023 99 cdq ; edx变成eax的符号位
00401024 B9 64000000 mov ecx,64 ; ecx=100
00401029 F7F9 idiv ecx ; eax除以ecx,商在eax 余数在edx
0040102B 52 push edx
0040102C 68 44804000 push asm.00408044 ; ASCII "%4d"
00401031 8917 mov dword ptr ds:,edx ; 把余数转存到ds:??噢噢.. ds:就是相当于a了
00401033 E8 2E010000 call asm.00401166
00401038 8BC6 mov eax,esi
0040103A B9 0A000000 mov ecx,0A
0040103F 99 cdq
00401040 F7F9 idiv ecx ; eax除以10
00401042 83C4 08 add esp,8 ; 我靠,401033call的堆栈平衡的语句还跑那么远来执行
00401045 85D2 test edx,edx
00401047 75 0D jnz short asm.00401056
00401049 68 40804000 push asm.00408040
0040104E E8 13010000 call asm.00401166
00401053 83C4 04 add esp,4 ; 还是堆栈平衡,这次像样点。
00401056 46 inc esi
00401057 83C7 04 add edi,4
0040105A 83FE 64 cmp esi,64
0040105D ^ 7E BF jle short asm.0040101E
0040105F 8D4C24 38 lea ecx,dword ptr ss: ; 数组a的地址放到ecx
00401063 BE 64000000 mov esi,64 ; esi=100
00401068 8B01 mov eax,dword ptr ds: ; 循环起始
0040106A BF 0A000000 mov edi,0A ; edi=10
0040106F 99 cdq
00401070 F7FF idiv edi
00401072 85D2 test edx,edx
00401074 75 02 jnz short asm.00401078 ; if eax/10没有余数则跳转
00401076 8BD7 mov edx,edi
00401078 8B7C94 08 mov edi,dword ptr ss: ; 这两条啊 +了8,是因为之前还push了两个寄存器 还记得吗?就是最前面的那两个push
0040107C 8D4494 08 lea eax,dword ptr ss: ; 从上面三句看出 edx是作为变量p了
00401080 47 inc edi
00401081 83C1 04 add ecx,4 ; 把a数组的下标往后挪
00401084 4E dec esi
00401085 8938 mov dword ptr ds:,edi
00401087 ^ 75 DF jnz short asm.00401068 ; 循环结束
00401089 BE 01000000 mov esi,1 ; esi是循环变量咯
0040108E 83FE 0A cmp esi,0A
00401091 8BC6 mov eax,esi
00401093 75 02 jnz short asm.00401097
00401095 33C0 xor eax,eax
00401097 8B54B4 08 mov edx,dword ptr ss:
0040109B 52 push edx
0040109C 50 push eax
0040109D 68 38804000 push asm.00408038 ; ASCII "%d,%d
"
004010A2 E8 BF000000 call asm.00401166
004010A7 83C4 0C add esp,0C
004010AA 46 inc esi
004010AB 83FE 0A cmp esi,0A
004010AE ^ 7E DE jle short asm.0040108E
004010B0 68 40804000 push asm.00408040
004010B5 E8 AC000000 call asm.00401166
004010BA 68 30804000 push asm.00408030 ; ASCII "pause"
004010BF E8 0C000000 call asm.004010D0
004010C4 83C4 08 add esp,8
004010C7 5F pop edi
004010C8 5E pop esi
004010C9 81C4 C0010000 add esp,1C0
004010CF C3 retn
这次逆向的这个算法总结了一些经验 分享一下
1遇到跳转跨度较大的,例如跨了20条语句左右的,那大概就是一个C语言里的for while do这些循环了。
2认清循环之后要找准循环变量再进行后续分析
3 很多时候程序会以ecx作为循环变量,如果ecx用作其他用途的话 也会选择其他寄存器做循环变量
4 在程序里面有些局部变量编译器会直接用寄存器把他替换掉,所以要认清哪个寄存器把这个变量替换掉了
5
00401000 81EC C0010000 sub esp,1C0 ; 1c0=448,448=101*4+11*4
00401006 B9 0B000000 mov ecx,0B ; ecx=11
0040100B 33C0 xor eax,eax ; eax=0
0040100D 56 push esi
0040100E 57 push edi
0040100F 8D7C24 08 lea edi,dword ptr ss: ; 访问缓冲区的地址
这里的最后一条语句的意思我开始以为是访问CALL语句的下一条指令的地址呢,原来是因为在程序开头用sub esp,1c0开辟了一段缓冲区,
然后push了两个寄存器,所以访问这段缓冲区自然就是esp+8了
6
程序开始时分配的这一段缓冲区是一下子分配给了一个或者一个以上的数组和变量了,这里的缓冲区包含数组a和数组x
原来后声明的变量还排在缓冲区的前面。。也就是,这里的缓冲区数组x的11个元素排前面 接着才是数组a
从这里可以看出:0040101A 8D7C24 38 lea edi,dword ptr ss: ; 访问数组x的末尾+1也就是数组a的起始
esp+38,38h也就是56D,就是56了,56减去之前的两个寄存器占的8个字节,也就是48了,每个int变量占4个字节,那么这里就是有12个变量了,什么?12???,x不是11个元素吗?怎么成12了,呵呵因为第12已经是接下来的数组的第一个元素了!!
冒泡算法的分析
冒泡算法,以前学C的时候看到这名字就觉得深奥不敢看。。。。
C源代码:
main()
{
int i,j,temp;
int a;
for(i=0;i<5;i++)
scanf ("%d,",&a); //输入5个数
for(i=0;i<=4;i++)
{
for (j=0;j<5-i;j++)
if (a>a)
{
temp=a;
a=a;
a=temp;
}
}
for(i=0;i<5;i++)
printf("%5d,",a );
printf("\n");
}
老做法!F8单步到跑飞CALL断下重新加载进Call分析!(好一个从句!)
00401000 83EC 14 sub esp,14 ; 14h=20,20=4*5
00401003 56 push esi
00401004 57 push edi ; 保存现场
00401005 8D7424 08 lea esi,dword ptr ss: ; 访问sub esp,14空出的缓冲区的首址
00401009 BF 05000000 mov edi,5 ; edi=5
0040100E 56 push esi ; 把缓冲区用来接收数字(输入循环的开始)
0040100F 68 3C804000 push asm.0040803C ; ASCII "%d,"
00401014 E8 98000000 call asm.004010B1 ; scanf
00401019 83C4 08 add esp,8 ; 恢复两个单位的堆栈(以后我们就称4个字节为一个单位吧)
0040101C 83C6 04 add esi,4 ; 指向缓冲区的指针加一个单位,即指向下一个元素
0040101F 4F dec edi ; edi--;
00401020 ^ 75 EC jnz short asm.0040100E ; edi为0就退出循环(即初始化5个元素)(输入循环的结束)
00401022 BF 05000000 mov edi,5 ; edi=5
00401027 85FF test edi,edi ; 大循环的开始(嵌套) 两个正整数相与,结果不为0所以Z为0直到edi减小为0时或者本来就为0时这个操作才会导致Z=1 由于大循环结束处有cmp edi,1和jge跳转所以这里的jle循环算是废的了
00401029 7E 1A jle short asm.00401045 ; 把它nop掉试试 ……结果运行也没错呵呵
0040102B 8D4424 0C lea eax,dword ptr ss: ; 缓冲区的第二个变量放在eax,也就是冒泡中所谓的a了
0040102F 8BF7 mov esi,edi ; 第一次循环的时候i和j相同都是5 只是在小循环里面j就是(esi)才递减了
00401031 8B48 FC mov ecx,dword ptr ds: ; 前一个变量(循环开始)
00401034 8B10 mov edx,dword ptr ds: ; 后一个变量
00401036 3BCA cmp ecx,edx ; 比较一下
00401038 7E 05 jle short asm.0040103F ; 如果大于则调位(小于则不调位)
0040103A 8950 FC mov dword ptr ds:,edx
0040103D 8908 mov dword ptr ds:,ecx ; 上面两句是调位代码
0040103F 83C0 04 add eax,4 ; j++;
00401042 4E dec esi ; 这里跟j++其实也差不多,只不过esi是用于计数,而没有用来寻址而已
00401043 ^ 75 EC jnz short asm.00401031 ; 循环结束
00401045 4F dec edi
00401046 83FF 01 cmp edi,1
00401049 ^ 7D DC jge short asm.00401027 ; 大循环的结束(嵌套)
0040104B 8D7424 08 lea esi,dword ptr ss: ; 拿第一个数组元素
0040104F BF 05000000 mov edi,5 ; 输出5次吧
00401054 8B06 mov eax,dword ptr ds: ; 输出循环的开始
00401056 50 push eax
00401057 68 34804000 push asm.00408034 ; ASCII "%5d,"
0040105C E8 1F000000 call asm.00401080
00401061 83C4 08 add esp,8
00401064 83C6 04 add esi,4
00401067 4F dec edi
00401068 ^ 75 EA jnz short asm.00401054 ; 输出循环的结束
0040106A 68 30804000 push asm.00408030
0040106F E8 0C000000 call asm.00401080 ; printf
00401074 83C4 04 add esp,4 ; 最后输出换行符
00401077 5F pop edi ; 上面这些跟开头差不多了,只不过一个输入 一个输出罢了
00401078 5E pop esi
00401079 83C4 14 add esp,14
0040107C C3 retn
逆向一些在百度找到的简单的C语言算法
今天好不容易在网管中心的一台垃圾机子上装好VC和OD。。
在百度找到一些简单,适合用来反汇编的算法: http://blog.csdn.net/yanfan0916/article/details/6450472
程序源码:
#include <stdio.h>
#include <stdlib.h>
void main()
{
int i,j,k;
printf("\n");
for(i=1;i<5;i++)
for(j=1;j<5;j++)
for(k=1;k<5;k++)
{
if(i!=k&&i!=j&&j!=k)
printf("%d,%d,%d/n",i,j,k);
}
}
OD载入,一直F8走,发现竟然没有停住直接结束了程序,原来因为本C程序中未有要求用户输入的函数,所以
我们要自己注意OD中代码调用了哪个call之后屏幕开始输出数据了。
00401140|.E8 BBFEFFFF call 00401000
走到这里的时候发现已经有东西输出了,于是断下 重新加载程序 运行到此处再F7单步!
OD反汇编代码:
00401000/$53 push ebx
00401001|.56 push esi
00401002|.57 push edi ;看来就是把这三个寄存器当做i j k了
00401003|.68 3C704000 push 0040703C
00401008|.E8 53000000 call 00401060 ;输出空行
0040100D|.83C4 04 add esp, 4 ;恢复堆栈(C函数)
00401010|.BB 01000000 mov ebx, 1 ;i
00401015|>BF 01000000 /mov edi, 1 ;j
0040101A|>BE 01000000 |/mov esi, 1 ;k
0040101F|>3BDE ||/cmp ebx, esi ;cmp i,k
00401021|.74 18 |||je short 0040103B ;不符合条件i!=k则跳转,进入本层循环的下一次循环或因循环变量不小于5而退出本层
00401023|.3BDF |||cmp ebx, edi
00401025|.74 14 |||je short 0040103B ;不符合条件i!=j则跳转,进入本层循环的下一次循环或因循环变量不小于5而退出本层
00401027|.3BFE |||cmp edi, esi
00401029|.74 10 |||je short 0040103B ;不符合条件j!=k则跳转,进入本层循环的下一次循环或因循环变量不小于5而退出本层
0040102B|.56 |||push esi
0040102C|.57 |||push edi
0040102D|.53 |||push ebx
0040102E|.68 30704000 |||push 00407030 ;ASCII "%d,%d,%d/n"
00401033|.E8 28000000 |||call 00401060 ;如果都符合就输出吧
00401038|.83C4 10 |||add esp, 10 ;恢复堆栈
0040103B|>46 |||inc esi ;k++
0040103C|.83FE 05 |||cmp esi, 5
0040103F|.^ 7C DE ||\jl short 0040101F ;k<5则跳转
00401041|.47 ||inc edi ;j++
00401042|.83FF 05 ||cmp edi, 5
00401045|.^ 7C D3 |\jl short 0040101A ;j<5则跳转
00401047|.43 |inc ebx
00401048|.83FB 05 |cmp ebx, 5
0040104B|.^ 7C C8 \jl short 00401015 ;i<5则跳转
0040104D|.5F pop edi ;恢复现场
0040104E|.5E pop esi
0040104F|.5B pop ebx
00401050\.C3 retn
OD的那些跳转的连线还真给我们省事,一眼就能看出是三层循环。