首页
社区
课程
招聘
[旧帖] [原创]练习TraceMe.exe,做算号器成功,列出汇编分析和几点疑问。 0.00雪花
发表于: 2013-8-24 17:37 2068

[旧帖] [原创]练习TraceMe.exe,做算号器成功,列出汇编分析和几点疑问。 0.00雪花

2013-8-24 17:37
2068
呃……通过实战,终于体验到了汇编基础差的痛苦之处!
我用的是 中文OllyICE
(如有机会,求转个正。)
第一步,找CALL,即算号过程。
1.直接用OllyICE打开 trace.exe。根据书上的教程示例查找USER32.GetDlgItemTextA函数的“输入函数参考”,在反汇编窗口中跟随这个“参考”并下断点。
这里一直无法理解“输入函数参考”的中文含意,这个在英文原版中的含义是不是“输入函数的引用处”或者“涉及到了这个输入函数的地方”?或许是中文翻译的失误吧。
2.下完断,Ctrl+F2重新运行OD,接着F9执行traceme.exe,会出现验证对话框,随便填并确定,我们的断点会拦到它的(如图1)。(如果提示输入长度不够,请重试)。
图1:

图1中 004011A3这一行就是断到的地方,因为输入函数GetDlgItemTextA的地址被引用并交给了edi,再往下看,CALL了两次 edi,根据API手册,traceme.exe 把我们输入的用户名的密码读入了变量。为什么是存入esp+4C和esp+9C这两个地址呢?我猜是对话框对象被创建的时候系统就那么分配的。。不晓得猜测正确不
3.接下来,连续两个判断,是看输入的用户名是否符合格式。再接下来,哎呀~连接几个压栈这是要CALL啊,CALL完了00401340还有个 add esp,0C, 这是C调用的栈平衡嘛。栈平衡后面紧跟着就是test eax,eax ,这是判断函数返回值是否为零啊,如图2。
图2:

我们现在是乱输入的密码,返回值肯定不对,先不去管那个疑似算号函数的CALL,先跟下去,跟下去发现一连串的对话框相关API调用后。traceme弹了个窗,说我密码不对。。
4.我们重新开始调试,到0040113这一行暂停下,把下面的je short 440122E 改成 nop(双击汇编代码区填NOP),接下来继续F8下去,会弹出个恭喜框。这下我们可以确定00401340肯定就是算号函数了。只是不明白,为什么算号函数出来第一时间不平衡栈,而是把GetDlgItem的地址引用给edi?难道编译器就是这么无脑整的?
第二步,算号函数。
Ctrl+F2重新开始,F9重新执行,这次在CALL 00401340的时候跟进去。
如图3

由于OD对API函数有一定的注释,因此我们很容易发现00401379行之后是格式化一个字符串再与另一个字符串比较的操作。通过跟踪发现 0040138E这一行eax装的是我们输入的密码,那么格式化的ebp就应该是算号器算的喽~~
00401395、00401397两行中
neg eax
sbb eax,eax
结合下面的inc eax,是为了确定函数的返回值是0 or 1,
如果有朋友对这组指令的意义不了解,请参考http://www.cnblogs.com/awpatp/archive/2009/11/06/1597488.html,讲得非常详细。
具体的算号过程《加密与解密第三版 》注释的很详细,算法是简单的循环取用户名中的某一数+数据表中的某一数。
下面一行中,ecx被赋值为3,
0040134B  |   mov     ecx, 3
00401359  ~ 00401376是个循环,在循环中ecx只有 inc加操作和cmp比较操作,结合下面的指令就明白了为什么用户名要大于4个字符:
00401364   |mov     dl, byte ptr [ecx+ebp]
ebp是用户名首字符加上ecx,算号器只从第4个字符开始算。
经过算号器验证,用户名分别输入,12345,00045,11145,44445,他们的密码都是1154.

00401367这一行中 eax+405030是提取源数据表用于加法,在数据窗口中查找地址405030并把数据复制出来就可以做算号器了。
只是发现00401354这一行的比较有点多余,在00401347这一行,edi=用户名长度,而用户名长度不够在算号函数之外已经有这样的判错了,能进入算号函数用户名长度一定不会比3小!我将00401354,00401356两行改成NOP,暂时没有发现任何问题,这又是编译器没优化的原因吗?

========下面是算号器 C 实现=====
// win32_test_traceme.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include "string.h"

char table[]={0x0C,0x0A,0x13,0x09,0x0C,0x0B,0x0A,0x08};
char buffer[100];

int _tmain(int argc, _TCHAR* argv[])
{
        int i,j,len;
        long cod=0,cod1=0,cod2=0;
        printf("因偷懒无判错,所以请输入大于4个字符的用户名!:\n");
        gets(buffer);
        len=strlen(buffer);
        for(i=3,j=0;i<len&&j<8;i++,j++)
        {
                cod1=(long)buffer[i];
                cod2=(long)table[j];
                cod1=cod1*cod2;
                cod=cod+cod1;

        }
        printf("你需要的密码为:%ld\n",cod);
        getchar();

        return 0;
}

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

上传的附件:
收藏
免费 0
支持
分享
最新回复 (5)
雪    币: 41
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
貌视很复杂,学习了。
2013-8-25 19:23
0
雪    币: 145
活跃值: (85)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
3
期待高人指点
2013-8-25 19:30
0
雪    币: 238
活跃值: (55)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
4
BOOL CALLBACK MainDlg (HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{              
   
    TCHAR cName[MAXINPUTLEN]={0};   
    TCHAR cCode[100]={0};
    。。。
   if (cName[0] == 0||len<5)   
            {   
                lstrcpy(szBuffer,szEnchar);   
                SetFocus (GetDlgItem(hDlg,IDC_TXT0));   
            }   
   
            else   
            {   
                if(GenRegCode(cCode, cName ,len))   //此处调用序列号计算的子程序   
                {   lstrcpy(szBuffer,szSucc);   
                    EnableWindow(GetDlgItem(hDlg,IDC_TXT0),FALSE);   
                    EnableWindow(GetDlgItem(hDlg,IDC_TXT1),FALSE);   
                }   
                else   
                    lstrcpy(szBuffer,szFail);   
            SetFocus (GetDlgItem(hDlg,IDC_TXT1));   
            }   
            MessageBeep (MB_OK);   
            DialogBox (hInst, MAKEINTRESOURCE (IDD_CHECK), hDlg, CheckDlgProc ) ;
            。。。
}

BOOL GenRegCode( TCHAR  *rCode, TCHAR  *name ,int len)   
{   
    int i,j;   
    unsigned long code=0;   
   
    for(i=3,j=0;i<len;i++,j++)   
    {
        if(j>7) j=0;   
        code+=((BYTE)name[i])*Table[j];
    }   
   
    wsprintf(name,TEXT("%ld"),code);   
    if(lstrcmp(rCode, name)==0)      //比较真假序列号,这里为了省事,直接比较了   
        return TRUE;   
    else   
        return FALSE;   
   
}  
为什么是存入esp+4C和esp+9C这两个地址呢?我猜是对话框对象被创建的时候系统就那么分配的。。不晓得猜测正确不

esp或ebp的加或减说明是局部变量或者函数参数,看代码知道是cCode, cName这两个局部变量传到调用GenRegCode(cCode, cName ,len)之后的实参。用esp+4C而不用ebp+8或其他是因为编译器做了这样的选择,代码中并没有玄机。
只是不明白,为什么算号函数出来第一时间不平衡栈,而是把GetDlgItem的地址引用给edi?难道编译器就是这么无脑整的?

我猜是编译器为了配合CPU的乱序执行而做的优化。
只是发现00401354这一行的比较有点多余,在00401347这一行,edi=用户名长度,而用户名长度不够在算号函数之外已经有这样的判错了,能进入算号函数用户名长度一定不会比3小!我将00401354,00401356两行改成NOP,暂时没有发现任何问题,这又是编译器没优化的原因吗?

看源代码,作者在函数外部的判断是界面部分,用来产生提示;内部判断是算法部分。不应该靠人来保证参数的正确,无论何时进行参数检查是必要的。
2013-8-25 23:27
0
雪    币: 34
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
[QUOTE=sierra;1214501]BOOL CALLBACK MainDlg (HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{              
   
    TCHAR cName[MAXINPUTLEN]={0};   
    TCHAR c...[/QUOTE]

多谢你的回复,
看来我的几个猜测没错了。尤其是最后一个,很有道理。

J>7这个判断是我忽视了,谢谢你的指正。  如果输入和用户名大于11个数我的算号器就错了。呵呵。
2013-8-26 10:18
0
雪    币: 34
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
不复杂啊,这一断代码我基本上都懂了,你有什么不明白了问我。
2013-8-26 10:19
0
游客
登录 | 注册 方可回帖
返回
//