通过调用栈对软件爆破,以后不用总是搜索字符串了
基础知识栈
栈是操作系统在运行时自动初始化的一块区域,是数据暂时存储的的动态内存区域。它的大小在Windows操作系统下由PE文件结构中PE文件头中IMAGEOPTIONALHEADER结构中SizeOfStackReserve字段所定义。在OD中,要想顺利分析软件的功能,在单步需要关注的便是寄存器和栈空间。下面,我们来看一下软件是怎么利用栈的。函数的调用过程
我们用一个充满函数的小软件来研究函数的调用,源代码如下:复制代码隐藏代码includeiostreamincludecstdioincludecstdlibincludewindows。husingnamespacestd;intfunction(inta,intb);这是第一个函数,揭示控制台应用程序函数调用过程intWINAPIwinfunction(inta,intb);winapi调用方式intcdeclcfunction(inta,intb);c函数方式intstdcallcppfunction(inta,intb);c函数方式intfastcallffunction(inta,intb);寄存器函数方式intstdcallstdfunction(inta,intb);其实就是WINAPI调用方式intmyfunction(inta,intb);递归调用演示intmain(){inta;intb;printf(定位汇编代码);scanf(d,a);scanf(d,b);scanf方便找汇编代码winfunction(a,b);cfunction(a,b);cppfunction(a,b);ffunction(a,b);stdfunction(a,b);myfunction(a,b);return0;}intfunction(inta,intb){intcab;加法运算最简单returnc;}intWINAPIwinfunction(inta,intb){intcab;加法运算最简单returnc;}intcdeclcfunction(inta,intb){intcab;加法运算最简单returnc;}intstdcallcppfunction(inta,intb){intcab;加法运算最简单returnc;}intfastcallffunction(inta,intb){intcab;加法运算最简单returnc;}intstdcallstdfunction(inta,intb){intcab;加法运算最简单returnc;}intmyfunction(inta,intb){a;b;myfunction(a,b);}
将编译出的软件用OD载入,通过API定位至调用语段:
简单分析一下,易得:复制代码隐藏代码0040159A8B55F0movedx,dwordptrss:〔ebp0x10〕;将scanf的结果放入EDX(b)0040159D8B45F4moveax,dwordptrss:〔ebp0xC〕;将scanf的结果放入EAX(a)004015A089542404movdwordptrss:〔esp0x4〕,edx;将EDX内容压入栈,就是我输入的b(2)004015A4890424movdwordptrss:〔esp〕,eax;将EAX内容压入栈,就是我输入的a(1)004015A7E881000000call未命名1。0040162D;WINAPI调用方式004015AC83EC08subesp,0x8;恢复堆栈004015AF8B55F0movedx,dwordptrss:〔ebp0x10〕;将scanf的结果放入EDX(b)004015B28B45F4moveax,dwordptrss:〔ebp0xC〕;将scanf的结果放入EAX(a)004015B589542404movdwordptrss:〔esp0x4〕,edx;将EDX内容压入栈,就是我输入的b(2)004015B9890424movdwordptrss:〔esp〕,eax;将EAX内容压入栈,就是我输入的a(1)004015BCE884000000call未命名1。00401645;c函数方式004015C18B55F0movedx,dwordptrss:〔ebp0x10〕;将scanf的结果放入EDX(b)004015C48B45F4moveax,dwordptrss:〔ebp0xC〕;将scanf的结果放入EAX(a)004015C789542404movdwordptrss:〔esp0x4〕,edx;将EDX内容压入栈,就是我输入的b(2)004015CB890424movdwordptrss:〔esp〕,eax;将EAX内容压入栈,就是我输入的a(1)004015CEE888000000call未命名1。0040165B;c函数方式004015D383EC08subesp,0x8;恢复堆栈004015D68B55F0movedx,dwordptrss:〔ebp0x10〕;将scanf的结果放入EDX(b)004015D98B45F4moveax,dwordptrss:〔ebp0xC〕;将scanf的结果放入EAX(a)004015DC89C1movecx,eax;将EAX寄存器的值放入ECX004015DEE890000000call未命名1。00401673;寄存器方式004015E38B55F0movedx,dwordptrss:〔ebp0x10〕;将scanf的结果放入EDX(b)004015E68B45F4moveax,dwordptrss:〔ebp0xC〕;将scanf的结果放入EAX(a)004015E989542404movdwordptrss:〔esp0x4〕,edx;将EDX内容压入栈,就是我输入的b(2)004015ED890424movdwordptrss:〔esp〕,eax;将EAX内容压入栈,就是我输入的a(1)004015F0E89A000000call未命名1。0040168F;stdcall调用方式004015F583EC08subesp,0x8;恢复堆栈004015F88B55F0movedx,dwordptrss:〔ebp0x10〕;将scanf的结果放入EDX(b)004015FB8B45F4moveax,dwordptrss:〔ebp0xC〕;将scanf的结果放入EAX(a)004015FE89542404movdwordptrss:〔esp0x4〕,edx;将EDX内容压入栈,就是我输入的b(2)00401602890424movdwordptrss:〔esp〕,eax;将EAX内容压入栈,就是我输入的a(1)00401605E89D000000call未命名1。004016A7;递归调用
然后我们随意跟进一个函数,如图(我选择的是C函数方式)
简单分析,易得:复制代码隐藏代码0040164555pushebp;ebp入栈保护现场0040164689E5movebp,esp;保存栈指针0040164883EC10subesp,0x10;设置栈指针0040164B8B5508movedx,dwordptrss:〔ebp0x8〕;从栈中读入edx(a)0040164E8B450Cmoveax,dwordptrss:〔ebp0xC〕;从栈中读入eax(b)0040165101D0addeax,edx;将eax和ebx相加,读入eax004016538945FCmovdwordptrss:〔ebp0x4〕,eax;保存eax的值入栈准备引用(c)004016568B45FCmoveax,dwordptrss:〔ebp0x4〕;将c作为返回值放入eax00401659C9leave;恢复栈0040165AC3retn;函数返回
综上所述,我们可以得到以下几点:
1。大多数调用协定都将栈作为参数传递的途径
2。在进入一个函数时执行call指令就是做了两步:
(1)将下一行指令地址压入栈
(2)跳转到call后的地址
3。将eax作为返回值
4。return指令实际做的事:
(1)跳转到栈顶的那个地址
(2)栈顶中的地址出栈
所以,函数为了返回必定要用栈,分析栈也就可以知道函数是从哪调用的。正式开始调用栈
栈不止可以用于函数调用,临时数据的存储都是用栈,但是,栈中为了函数调用的部分称为调用栈,用调用栈可以分析函数是从哪里调用的。OD对调用栈的支持
快捷键(AltK)
如图便是OD的调用栈窗口
第一行是Main函数里的函数
第二行是Main函数调用栈找地址法原理
通过调用栈,我们可以知道函数是从哪里调用的,便可以知道是哪里触发了函数,便可轻易找到是哪里判断了注册码是正确还是错误的。实战调用栈法
我又写了一个小程序,源码如下(验证部分和我上次发帖的一样):复制代码隐藏代码includeiostreamincludecstdioincludecstdlibincludestringincludewindows。husingnamespacestd;intcheck(stringusername,stringsn);检测注册码正确性intregiste();注册界面inthelp();帮助界面intmain(){cout1。注册endl2。帮助endl;intchoose;cinchoose;if(choose1){registe();}elseif(choose2){help();}else{cout没有这个选项!endl;system(pause);}return0;}inthelp(){cout这只是个帮助endl;system(pause);return0;}intregiste(){stringusername,sn;cout请输入用户名:endl;cinusername;cout请输入序列号:endl;cinsn;check(username,sn);system(pause);return0;}intcheck(stringuser,stringsn){for(inti0;iuser。length();i){for(intj0;jsn。length();j){if(user〔i〕1!sn〔i〕){MessageBox(NULL,序列号错误,失败,MBOK);return0;}}}MessageBox(NULL,序列号正确,成功,MBOK);return1;}
OD载入,运行
在弹出错误框后,暂停,打开调用栈
可以看出,从我的exe调用的而非系统dll调用的最上层的调用来自:
未命名1。004018B2,其中未命名1为我的程序名。
进入004018B2
看到上面将序列号错误压入了栈,所以我们判断这个call显示了这个弹窗,所以我们想要跳过这个call,而它上面就有一个je跳转,我们改为jmp看一下。
至此,软件爆破完成!感兴趣的同学可以尝试分析一下剩余的语句的功能,对自己的能力提升是有帮助的。结语
调用栈破解法很常用,学会了它,你就不用总是搜索字符串了。现在,自己找个CrackMe或编译我提供的源代码试一试吧,祝你好运!