Snow 发表于 2012-5-27 11:50

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!

Snow 发表于 2012-5-27 11:53

带你阅读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




602291051 发表于 2012-5-27 11:53

wohoohoo 发表于 2012-5-27 11:55

挺不错的。确实是一种学习的方法

Snow 发表于 2012-5-27 11:58

分析一段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了
-------------------------------------第四部分-------------------------------------

Cser2 发表于 2012-5-27 11:59

哇咔咔看看

Snow 发表于 2012-5-27 12:00

判断素数
在百度搜了个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 ---------------------------------------多多练习!

Snow 发表于 2012-5-27 12:01

随机生成一百个数 个位为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已经是接下来的数组的第一个元素了!!

Snow 发表于 2012-5-27 12:02

冒泡算法的分析
冒泡算法,以前学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

Snow 发表于 2012-5-27 12:03

逆向一些在百度找到的简单的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的那些跳转的连线还真给我们省事,一眼就能看出是三层循环。

页: [1] 2 3
查看完整版本: C语言代码逆向的实战十篇(原创)