吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 6311|回复: 24
收起左侧

[原创] 通过调用栈对软件爆破,以后不用总是搜索字符串了

  [复制链接]
Lifetimer 发表于 2020-1-26 19:23
本帖最后由 Lifetimer 于 2020-1-26 19:28 编辑

基础知识


栈是操作系统在运行时自动初始化的一块区域,是数据暂时存储的的动态内存区域。它的大小在Windows操作系统下由PE文件结构中PE文件头中IMAGE_OPTIONAL_HEADER结构中SizeOfStackReserve字段所定义。在OD中,要想顺利分析软件的功能,在单步需要关注的便是寄存器和栈空间。下面,我们来看一下软件是怎么利用栈的。

函数的调用过程

我们用一个充满函数的小软件来研究函数的调用,源代码如下:

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<windows.h>
using namespace std;
int function(int a,int b);//这是第一个函数,揭示控制台应用程序函数调用过程
int WINAPI winfunction(int a,int b);//winapi调用方式 
int _cdecl cfunction(int a,int b);//c函数方式
int _stdcall cppfunction(int a,int b);//c++函数方式
int _fastcall ffunction(int a,int b);//寄存器函数方式 
int _stdcall stdfunction(int a,int b);//其实就是WINAPI调用方式 
int myfunction(int a,int b);//递归调用演示 
int main(){
        int a;int b;
    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);
        return 0;
}
int function(int a,int b){
        int c = a + b;//加法运算最简单 
        return c;
}
int WINAPI winfunction(int a,int b){
        int c = a + b;//加法运算最简单 
        return c;
}
int _cdecl cfunction(int a,int b){
        int c = a + b;//加法运算最简单 
        return c;
}
int _stdcall cppfunction(int a,int b){
        int c = a + b;//加法运算最简单 
        return c;
}
int _fastcall ffunction(int a,int b){
        int c = a + b;//加法运算最简单 
        return c;
}
int _stdcall stdfunction(int a,int b){
        int c = a + b;//加法运算最简单 
        return c;
}
int myfunction(int a,int b){
        a++;
    b++;
    myfunction(a,b);
}

将编译出的软件用OD载入,通过API定位至调用语段:
QQ截图20200126171238.png
简单分析一下,易得:

0040159A    8B55 F0         mov edx,dword ptr ss:[ebp-0x10]      ; 将scanf的结果放入EDX(b)
0040159D    8B45 F4         mov eax,dword ptr ss:[ebp-0xC]       ; 将scanf的结果放入EAX(a)
004015A0    895424 04       mov dword ptr ss:[esp+0x4],edx       ; 将EDX内容压入栈,就是我输入的b(=2)
004015A4    890424          mov dword ptr ss:[esp],eax           ; 将EAX内容压入栈,就是我输入的a(=1)
004015A7    E8 81000000     call 未命名1.0040162D                   ; WINAPI调用方式
004015AC    83EC 08         sub esp,0x8                          ; 恢复堆栈
004015AF    8B55 F0         mov edx,dword ptr ss:[ebp-0x10]      ; 将scanf的结果放入EDX(b)
004015B2    8B45 F4         mov eax,dword ptr ss:[ebp-0xC]       ; 将scanf的结果放入EAX(a)
004015B5    895424 04       mov dword ptr ss:[esp+0x4],edx       ; 将EDX内容压入栈,就是我输入的b(=2)
004015B9    890424          mov dword ptr ss:[esp],eax           ; 将EAX内容压入栈,就是我输入的a(=1)
004015BC    E8 84000000     call 未命名1.00401645                   ; c函数方式
004015C1    8B55 F0         mov edx,dword ptr ss:[ebp-0x10]      ; 将scanf的结果放入EDX(b)
004015C4    8B45 F4         mov eax,dword ptr ss:[ebp-0xC]       ; 将scanf的结果放入EAX(a)
004015C7    895424 04       mov dword ptr ss:[esp+0x4],edx       ; 将EDX内容压入栈,就是我输入的b(=2)
004015CB    890424          mov dword ptr ss:[esp],eax           ; 将EAX内容压入栈,就是我输入的a(=1)
004015CE    E8 88000000     call 未命名1.0040165B                   ; c++函数方式
004015D3    83EC 08         sub esp,0x8                          ; 恢复堆栈
004015D6    8B55 F0         mov edx,dword ptr ss:[ebp-0x10]      ; 将scanf的结果放入EDX(b)
004015D9    8B45 F4         mov eax,dword ptr ss:[ebp-0xC]       ; 将scanf的结果放入EAX(a)
004015DC    89C1            mov ecx,eax                          ; 将EAX寄存器的值放入ECX
004015DE    E8 90000000     call 未命名1.00401673                   ; 寄存器方式
004015E3    8B55 F0         mov edx,dword ptr ss:[ebp-0x10]      ; 将scanf的结果放入EDX(b)
004015E6    8B45 F4         mov eax,dword ptr ss:[ebp-0xC]       ; 将scanf的结果放入EAX(a)
004015E9    895424 04       mov dword ptr ss:[esp+0x4],edx       ; 将EDX内容压入栈,就是我输入的b(=2)
004015ED    890424          mov dword ptr ss:[esp],eax           ; 将EAX内容压入栈,就是我输入的a(=1)
004015F0    E8 9A000000     call 未命名1.0040168F                   ; stdcall调用方式
004015F5    83EC 08         sub esp,0x8                          ; 恢复堆栈
004015F8    8B55 F0         mov edx,dword ptr ss:[ebp-0x10]      ; 将scanf的结果放入EDX(b)
004015FB    8B45 F4         mov eax,dword ptr ss:[ebp-0xC]       ; 将scanf的结果放入EAX(a)
004015FE    895424 04       mov dword ptr ss:[esp+0x4],edx       ; 将EDX内容压入栈,就是我输入的b(=2)
00401602    890424          mov dword ptr ss:[esp],eax           ; 将EAX内容压入栈,就是我输入的a(=1)
00401605    E8 9D000000     call 未命名1.004016A7                   ; 递归调用

然后我们随意跟进一个函数,如图(我选择的是C函数方式)
QQ截图20200126171805.png
QQ截图20200126172300.png
简单分析,易得:

00401645    55              push ebp                             ; ebp入栈保护现场
00401646    89E5            mov ebp,esp                          ; 保存栈指针
00401648    83EC 10         sub esp,0x10                         ; 设置栈指针
0040164B    8B55 08         mov edx,dword ptr ss:[ebp+0x8]       ; 从栈中读入edx(a)
0040164E    8B45 0C         mov eax,dword ptr ss:[ebp+0xC]       ; 从栈中读入eax(b)
00401651    01D0            add eax,edx                          ; 将eax和ebx相加,读入eax
00401653    8945 FC         mov dword ptr ss:[ebp-0x4],eax       ; 保存eax的值入栈准备引用(c)
00401656    8B45 FC         mov eax,dword ptr ss:[ebp-0x4]       ; 将c作为返回值放入eax
00401659    C9              leave                                ; 恢复栈
0040165A    C3              retn                                 ; 函数返回

综上所述,我们可以得到以下几点:
1.大多数调用协定都将栈作为参数传递的途径
2.在进入一个函数时执行call指令就是做了两步:
(1)将下一行指令地址压入栈
(2)跳转到call后的地址
3.将eax作为返回值
4.return指令实际做的事:
(1)跳转到栈顶的那个地址
(2)栈顶中的地址出栈

所以,函数为了返回必定要用栈,分析栈也就可以知道函数是从哪调用的。

正式开始


调用栈

栈不止可以用于函数调用,临时数据的存储都是用栈,但是,栈中为了函数调用的部分称为调用栈,用调用栈可以分析函数是从哪里调用的。

OD对调用栈的支持

快捷键(Alt+K)
如图便是OD的调用栈窗口
QQ截图20200126174259.png
第一行是Main函数里的函数
第二行是Main函数

调用栈找地址法原理

通过调用栈,我们可以知道函数是从哪里调用的,便可以知道是哪里触发了函数,便可轻易找到是哪里判断了注册码是正确还是错误的。

实战调用栈法

我又写了一个小程序,源码如下(验证部分和我上次发帖的一样):

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<string>
#include<windows.h>
using namespace std;
int check(string username,string sn);//检测注册码正确性 
int registe();//注册界面 
int help();//帮助界面 
int main(){
        cout<<"1.注册"<<endl<<"2.帮助"<<endl;
        int choose;
        cin>>choose;
        if(choose==1){
                registe();
        } 
        else if(choose==2){
                help();
        }
        else{
                cout<<"没有这个选项!"<<endl;
                system("pause");
        }
        return 0;
}
int help(){
        cout<<"这只是个帮助"<<endl;
        system("pause");
        return 0;
}
int registe(){
        string username,sn;
    cout<<"请输入用户名:"<<endl;
    cin>>username;
    cout<<"请输入序列号:"<<endl;
    cin>>sn;
    check(username,sn);
    system("pause");
    return 0;
}
int check(string user,string sn){
    for(int i=0;i<user.length();i++){
        for(int j=0;j<sn.length();j++){
            if(user[i]-1!=sn[i]){
                MessageBox(NULL,"序列号错误","失败",MB_OK);
                return 0;
            }
        }
    }
    MessageBox(NULL,"序列号正确","成功",MB_OK);
    return 1;
}

OD载入,运行
QQ截图20200126175951.png
在弹出错误框后,暂停,打开调用栈
QQ截图20200126180024.png
QQ截图20200126180145.png
可以看出,从我的exe调用的而非系统dll调用的最上层的调用来自:
未命名1.004018B2,其中未命名1为我的程序名。
进入004018B2
QQ截图20200126180610.png
看到上面将序列号错误压入了栈,所以我们判断这个call显示了这个弹窗,所以我们想要跳过这个call,而它上面就有一个je跳转,我们改为jmp看一下。
QQ截图20200126183839.png
至此,软件爆破完成!感兴趣的同学可以尝试分析一下剩余的语句的功能,对自己的能力提升是有帮助的。

结语

调用栈破解法很常用,学会了它,你就不用总是搜索字符串了。现在,自己找个CrackMe或编译我提供的源代码试一试吧,祝你好运!

免费评分

参与人数 19威望 +1 吾爱币 +19 热心值 +19 收起 理由
红颜世家、 + 1 + 1 谢谢@Thanks!
redABC + 1 + 1 我很赞同!
朱朱你堕落了 + 1 用心讨论,共获提升!
san4san + 1 + 1 热心回复!
西北狼 + 1 + 1 用心讨论,共获提升!
生有涯知无涯 + 1 谢谢@Thanks!
蓝蓝的小刺客 + 1 谢谢@Thanks!
你坏 + 1 + 1 我很赞同!
镇北看雪 + 1 我很赞同!
清风听不见声音 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
qaz003 + 1 + 1 思路清析。。
gaosld + 1 + 1 用心讨论,共获提升!
liphily + 1 + 1 总有人以为自己写的够详细,我就能看懂了似的
FleTime + 1 用心讨论,共获提升!
女萝岩 + 1 + 1 我很赞同!
逍遥枷锁 + 1 + 1 谢谢@Thanks!
笙若 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
智小白 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
Hmily + 1 + 7 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

青春作伴 发表于 2020-1-26 21:19
玩这个 太伤脑,多年不玩,感觉轻松多了
 楼主| Lifetimer 发表于 2020-1-28 12:02
生有涯知无涯 发表于 2020-1-27 19:51
根据图1和 #define WINAPI _stdcall;(参考链接:https://www.cnblogs.com/songyaqi/p/12075105.html https ...

对,你不说我还没发现,可能是我的编译器出了问题,我用的是devcpp5.11开发环境自带的那个g++编译器,但是可以确保的是,#define WINAPI _stdcall这句话没有问题,你看那个stdcall调用协议也平衡了栈,反倒是那个c函数协定(_cdecl),本应由调用者平衡栈,但它在子程序处才平衡。改天我用visual Studio2019再编译一次试试。还有一种可能,我用g++是以release模式编译的,有可能有影响。不过还是感谢你的提醒,谢谢!
ma4907758 发表于 2020-1-26 20:02
xjywb520 发表于 2020-1-26 20:29
好文章,虽然不太懂,但是看着挺让人有学习的欲望的
lq15212686349 发表于 2020-1-27 00:57
先收藏再说
weizhuqiang 发表于 2020-1-27 10:22
收藏了,谢谢分享
x2005y 发表于 2020-1-27 10:27
谢谢分享,楼主好人一个
li_wyman 发表于 2020-1-27 10:43
学习了,讲的很清楚
doropio 发表于 2020-1-27 13:25
青春作伴 发表于 2020-1-26 21:19
玩这个 太伤脑,多年不玩,感觉轻松多了

哈哈哈哈哈哈真实
生有涯知无涯 发表于 2020-1-27 19:51
本帖最后由 生有涯知无涯 于 2020-1-27 19:53 编辑

根据图1和 #define WINAPI _stdcall;(参考链接:https://www.cnblogs.com/songyaqi/p/12075105.html https://www.cnblogs.com/yenyuloong/p/9626658.html)
图1.jpg
可知,WINAPI即为_stdcall,既被调用者清理栈,但图2中显示的并不符合此规则,正常情况下应该没有这句“sub esp,0x8”。这该如何解释?
图2.png
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-15 17:41

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表