第四篇:逆向之堆排序

本文通过反汇编代码分析了一个C语言实现的堆排序算法。首先展示了主函数的基本框架,然后详细解释了堆排序函数的每一步操作,包括如何找到最大数并进行交换,以及如何递归调整堆。最后,作者提醒在逆向过程中注意寄存器的变化,以理解流程控制和功能实现,强调有目的性的分析能提高效率。
摘要由CSDN通过智能技术生成
版本:Release
优化选项:O2
调试工具:OD
源码:
//堆排序
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#define MAX_SIZE 10  
#define SWAP(x,y,t) ((t)=(x),(x)=(y),(y)=(t))

void adjust(int a[],int rootn,int n)//调整堆
{
	int temp,parent,child;
	temp=a[rootn];
	parent=rootn;
	child=2*parent;
	while(child<=n)
	{
		if(child<n&&a[child]<a[child+1])
			child++;
		if(temp>a[child])
			break;
		a[parent]=a[child];
		parent=child;
		child*=2;
	}
	a[parent]=temp;
}

void heapsort(int a[],int n)
{
	int i,temp;
	for(i=n/2;i>0;i--)
		adjust(a,i,n);//自下而上逐层建堆
	for(i=n-1;i>0;i--)
	{
		SWAP(a[1],a[i+1],temp);//取堆顶点最大元素
		adjust(a,1,i);//调整堆
	}
}

int main()
{
	int i;
	int a[MAX_SIZE+1];//={0,5,8,4,6,2,15,12,58,12,10};
	srand((unsigned)time(0));
	for(i=1;i<=MAX_SIZE;i++)
		a[i]=rand()%100;
	for(i=1;i<=MAX_SIZE;i++)
		printf("%4d",a[i]);
	putchar('\n');
	heapsort(a,MAX_SIZE);
	for(i=1;i<=MAX_SIZE;i++)
		printf("%4d",a[i]);
	system("pause");
	return 0;
}

首先把主函数架子给反出来,比较简单
012610A0 >/$  83EC 2C       sub     esp, 2C
012610A3  |.  56            push    esi
012610A4  |.  57            push    edi
012610A5  |.  6A 00         push    0
012610A7  |.  FF15 A0202601 call    dword ptr [<&MSVCR90._time64>]   ;  MSVCR90._time64
012610AD  |.  50            push    eax                              ; /seed
012610AE  |.  FF15 A8202601 call    dword ptr [<&MSVCR90.srand>]     ; \srand
012610B4  |.  8B3D AC202601 mov     edi, dword ptr [<&MSVCR90.rand>] ;  MSVCR90.rand
012610BA  |.  83C4 08       add     esp, 8
012610BD  |.  BE 01000000   mov     esi, 1
012610C2  |>  FFD7          /call    edi
012610C4  |.  99            |cdq
012610C5  |.  B9 64000000   |mov     ecx, 64                         ;  100取余
012610CA  |.  F7F9          |idiv    ecx
012610CC  |.  46            |inc     esi
012610CD  |.  83FE 0A       |cmp     esi, 0A                         ;  分配10个元素
012610D0  |.  8954B4 04     |mov     dword ptr [esp+esi*4+4], edx    ;  第一次到这里其实esi==2,也就是说随机数存储在分配数组中下标1开始
012610D4  |.^ 7E EC         \jle     short 012610C2
012610D6  |.  8B3D B0202601 mov     edi, dword ptr [<&MSVCR90.printf>;  MSVCR90.printf
012610DC  |.  BE 01000000   mov     esi, 1
012610E1  |>  8B54B4 08     /mov     edx, dword ptr [esp+esi*4+8]    ;  这个循环是输出随机取得的数
012610E5  |.  52            |push    edx
012610E6  |.  68 04212601   |push    01262104                        ;  ASCII "%4d"
012610EB  |.  FFD7          |call    edi
012610ED  |.  46            |inc     esi
012610EE  |.  83C4 08       |add     esp, 8
012610F1  |.  83FE 0A       |cmp     esi, 0A                         ;  依旧是循环10次
012610F4  |.^ 7E EB         \jle     short 012610E1
012610F6  |.  6A 0A         push    0A                               ; /c = 0A  (Line Feed)
012610F8  |.  FF15 A4202601 call    dword ptr [<&MSVCR90.putchar>]   ; \putchar
012610FE  |.  83C4 04       add     esp, 4
01261101  |.  8D4424 08     lea     eax, dword ptr [esp+8]           ;   取分配数组的地址
01261105  |.  E8 F6FEFFFF   call    heapsort                         ;  排序函数是我们要主要分析的
0126110A  |.  BE 01000000   mov     esi, 1
0126110F  |.  90            nop
01261110  |>  8B44B4 08     /mov     eax, dword ptr [esp+esi*4+8]    ;  看过前面几个循环应该不会陌生了吧,一个模式,输出排序后的数
01261114  |.  50            |push    eax
01261115  |.  68 04212601   |push    01262104                        ;  ASCII "%4d"
0126111A  |.  FFD7          |call    edi
0126111C  |.  46            |inc     esi
0126111D  |.  83C4 08       |add     esp, 8
01261120  |.  83FE 0A       |cmp     esi, 0A
01261123  |.^ 7E EB         \jle     short 01261110
01261125  |.  68 08212601   push    01262108                         ; /command = "pause"
0126112A  |.  FF15 B4202601 call    dword ptr [<&MSVCR90.system>]    ; \system
01261130  |.  83C4 04       add     esp, 4
01261133  |.  5F            pop     edi
01261134  |.  33C0          xor     eax, eax
01261136  |.  5E            pop     esi
01261137  |.  83C4 2C       add     esp, 2C
0126113A  \.  C3            retn


嗯。下面分析我们的排序函数,这才是重头戏嘛
先大概浏览了下主要的跳转,发现函数调用直接插进来优化掉了没有了函数调用,本来还想从子函数入手的,既然如此就直接上吧,虽然会有点复杂
01261000 >/$  53            push    ebx
01261001  |.  55            push    ebp
01261002  |.  56            push    esi
01261003  |.  57            push    edi
01261004  |.  BF 05000000   mov     edi, 5                           ;  姑且认为edi是数组下标吧,令edi=n
01261009  |.  8DA424 000000>lea     esp, dword ptr [esp]
01261010  |>  83FF 05       /cmp     edi, 5
01261013  |.  8B1CB8        |mov     ebx, dword ptr [eax+edi*4]
01261016  |.  8BF7          |mov     esi, edi
01261018  |.  8D0C3F        |lea     ecx, dword ptr [edi+edi]        ;  ecx=2n
0126101B  |.  7F 22         |jg      short 0126103F                  ;  粗略来看下面这些跳转是为了找出a[n],a[2n],a[2n+1]中较大的数
0126101D  |.  83F9 0A       |cmp     ecx, 0A                         ;  if (2n<10)
01261020  |>  7D 0A         |/jge     short 0126102C
01261022  |.  8B1488        ||mov     edx, dword ptr [eax+ecx*4]
01261025  |.  3B5488 04     ||cmp     edx, dword ptr [eax+ecx*4+4]   ;  if(a[2n]<a[2n+1])
01261029  |.  7D 01         ||jge     short 0126102C
0126102B  |.  41            ||inc     ecx
0126102C  |>  8B1488        ||mov     edx, dword ptr [eax+ecx*4]
0126102F  |.  3BDA          ||cmp     ebx, edx                       ;  if (a[n]<=a[2n+1])
01261031  |.  7F 0C         ||jg      short 0126103F
01261033  |.  8914B0        ||mov     dword ptr [eax+esi*4], edx     ;  a[n]=a[较大那个]
01261036  |.  8BF1          ||mov     esi, ecx                       ;  esi=ecx,这里ecx是最大数的下标
01261038  |.  03C9          ||add     ecx, ecx                       ;  ecx=2*ecx,继续向下搜索调整
0126103A  |.  83F9 0A       ||cmp     ecx, 0A
0126103D  |.^ 7E E1         |\jle     short 01261020
0126103F  |>  4F            |dec     edi                             ;  n--
01261040  |.  891CB0        |mov     dword ptr [eax+esi*4], ebx      ;  到这里,在看下上面涉及到了ebx的代码,观察下就可以发现a[n]==ebx,而这些代码的目的应该是找到最大数的位置,然后将a[n]替换进去
01261043  |.  85FF          |test    edi, edi                        ;  n>0则继续循环
01261045  |.^ 7F C9         \jg      short 01261010
01261047  |.  BA 09000000   mov     edx, 9
0126104C  |.  8D68 28       lea     ebp, dword ptr [eax+28]          ;  a[10]
0126104F  |.  90            nop
01261050  |>  8B75 00       /mov     esi, dword ptr [ebp]
01261053  |.  8B48 04       |mov     ecx, dword ptr [eax+4]          ;  SWAP a[1],a[10]
01261056  |.  8970 04       |mov     dword ptr [eax+4], esi          ;  a[10]
01261059  |.  894D 00       |mov     dword ptr [ebp], ecx
0126105C  |.  8B58 04       |mov     ebx, dword ptr [eax+4]
0126105F  |.  B9 02000000   |mov     ecx, 2
01261064  |.  3BCA          |cmp     ecx, edx                        ;  我们假设edx就是n吧,这样的话if(2<n)
01261066  |.  BE 01000000   |mov     esi, 1
0126106B  |.  7F 1E         |jg      short 0126108B
0126106D  |>  7D 0A         |/jge     short 01261079                 ;  下面这段循环和上面一个套路,只是优化后略有不同(明明是一个函数,妹的)
0126106F  |.  8B3C88        ||mov     edi, dword ptr [eax+ecx*4]
01261072  |.  3B7C88 04     ||cmp     edi, dword ptr [eax+ecx*4+4]
01261076  |.  7D 01         ||jge     short 01261079
01261078  |.  41            ||inc     ecx
01261079  |>  8B3C88        ||mov     edi, dword ptr [eax+ecx*4]
0126107C  |.  3BDF          ||cmp     ebx, edi
0126107E  |.  7F 0B         ||jg      short 0126108B
01261080  |.  893CB0        ||mov     dword ptr [eax+esi*4], edi
01261083  |.  8BF1          ||mov     esi, ecx
01261085  |.  03C9          ||add     ecx, ecx
01261087  |.  3BCA          ||cmp     ecx, edx
01261089  |.^ 7E E2         |\jle     short 0126106D
0126108B  |>  4A            |dec     edx                             ;  n--
0126108C  |.  83ED 04       |sub     ebp, 4
0126108F  |.  891CB0        |mov     dword ptr [eax+esi*4], ebx
01261092  |.  85D2          |test    edx, edx
01261094  |.^ 7F BA         \jg      short 01261050
01261096  |.  5F            pop     edi
01261097  |.  5E            pop     esi
01261098  |.  5D            pop     ebp
01261099  |.  5B            pop     ebx
0126109A  \.  C3            retn

总结:
逆向的时候在进入一个很多跳转的指令中时,最好大概看下循环中一些常用到的寄存器,然后可以将寄存器记在纸上,而不是向无头苍蝇一样直接冲进指令堆中,结果常常是找不着北,这样应该会有助于分析流程控制条件和实现的功能等
虽然一条条指令分析并记在纸上简单又准确我也常这样做,不过我想如果在遇到一些大,复杂的跳转循环之类的指令应该会很麻烦吧,我是这么认为。。总之有一定目的性的分析应该会更好些
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值