首页
社区
课程
招聘
[旧帖] [原创]CrackMe入门——序列号保护机制 0.00雪花
发表于: 2015-10-9 15:37 1972

[旧帖] [原创]CrackMe入门——序列号保护机制 0.00雪花

2015-10-9 15:37
1972
发此贴的目的主要是总结一下最近学习的东西,如果能结识相同爱好的朋友再好不过了
本人也是新手,如有错误之处欢迎诸位大侠赐教!
-----------------------------------------------------------------------------------------------
序列号保护机制大致有四种

    [*]以用户名等信息作为自变量,通过函数F变换之后得到注册码
      序列号 = F(用户名)

      破解方法:只需要把F 函数的实现代码从软件中提取出来

    [*]通过注册码验证用户名的正确性
      用户名 = F‾¹(序列号)

      三种破解方法:

      1)由于F‾¹ 的实现代码是包含在软件中的,所以可以通过F‾¹ 来找出其逆变换即F 函数,从而写出正确的注册机

      2)给定一个用户名,利用穷举法找到一个满足公式的序列号

      3)给定一个序列号,利用F‾¹(序列号)公式得出一个用户名,从而得到一个正确的用户名/序列号对

    [*]通过对等函数检查注册码
      F1(用户名)= F2(序列号)

      是上一个方法的升级版,破解方法也类似

    [*]同时采用用户名和序列号作为自变量,即采用二元函数
      特定值 = F3(用户名,序列号)


接下来我会用一个实例来具体分析

首先运行程序,单击菜单 Help,会弹出一个注册框,随便输入一组数据,点击OK,会弹出一个提示对话框 “Incorrect!, Try Again”
大致了解了程序的运行流程,下一步要做的就是找到程序中关键的代码段,有几种方法,下面一一来尝试。

    [*]在OD里利用 API 断点来定位关键代码段:
      如何下API 断点:
      在用户代码的反汇编窗口,使用"Ctrl+N"快捷键打开应用程序的输入表,找到要下断点的API函数,在这个函数上按Enter键或右键菜单执行"Find references to import(查找输入函数参考)"命令或按Enter键打开调用此函数的参考代码窗口,即可找到调用此API的相关地址,在反汇编窗口中定位到此地址即可下断点

    在单击菜单Help后,程序弹出了一个模态对话框,等待用户的输入,先去输入表中看看程序调用了哪些创建对话框的函数



    发现了DialogBoxParamA函数,然后在反汇编窗口中找到调用此函数的地址,在参考窗口中发现有三处调用了此函数(其实只有两处)



    依次分析,很容易可以发现第二个才是我们要找的



    连窗口处理过程的地址都有了,在它的下一条指令(CMP EAX,0)处下好断点
      函数说明:
      DialogBoxParamA函数会阻塞,直到窗口处理过程中调用了EndDialog函数后,才会返回,并且EndDialog函数的参数会当作DialogBoxParamA函数的返回值

    在上一步中获取到了窗口处理过程的函数地址,然后可以直接在反汇编窗口中定位到窗口处理过程函数,进行具体的分析;这里为了练习API断点,就不采用这种方法了。
    继续分析程序的执行流程,在点击OK按钮后,程序要读取EDIT控件中的内容,然后才能对输入的名称和注册码进行处理,所以可以通过对调用读取控件内容的API函数处下断点,打开程序的输入表,发现了GetDlgItemTextA函数



    在反汇编窗口中找到调用GetDlgItemTextA函数的地址



    在004012C4处下断点,然后在OD中调试程序,输入一组测试数据,可以发现上面的GetDlgItemTextA函数是将输入的Name读取到0040218E处,长度为11个字节,下面的GetDlgItemTextA函数是将输入的Serial读取到0040217E处,长度也为11个字节,接着程序调用EndDialog函数结束窗口过程函数。程序的执行流程到了上一步操作的断点处,对此处的反汇编代码进行初略的分析,很容易定位出关键代码段是如下被标出的两个函数



    [*]在OD里利用字符串来定位关键代码段:
    在我们注册失败后,程序弹出了一个提示框



    先去查找Incorrect!,Try Again 字符串,在反汇编窗口单击鼠标右键,选择“字符串参考/查找ASCII”



    发现有两处,只能逐个分析,双击就可跳转到反汇编窗口中去,我们最终要定位到的是下面这里,这段代码首先调用MessageBeep函数播放一段系统音乐,然后调用MessageBoxA函数弹出失败对话框



    接着在反汇编窗口中寻找哪里调用了00401362处的代码(使用IDA的交叉引用参考很方便就能找到)



    经过分析,我们也能够定位出两个关键函数的地址

    [*]在OD里利用消息断点加内存断点定位关键代码段:
      如何下消息断点:
           单击菜单"View/Windows(查看/窗口)"可以列出窗口相关参数,比如按钮、对应的ID、以及句柄等,在相应的条目上单击右键,执行"Message breakpoint on ClassProc(在ClassProc上设置消息断点)",再选择要断的消息,此断点会断点系统底层代码,可以在用户程序.text区段下一个内存访问一次性断点

    在OD里运行调试程序,点击菜单Help,弹出注册对话框,然后切换到OD用户反汇编代码区,点击W图标或者菜单“查看/窗口”,会列出当前程序中所用到的资源信息



    在此窗口中可以看到控件的句柄、风格、类、ID等等,给On按钮下一个消息断点,由于我们要捕捉的是点击消息,所以选择断在WM_LBUTTONUP消息上



    接着输入一组测试数据,点击OK按钮,程序断在了系统底层代码,按Alt+M快捷键打开内存映射窗口,在用户代码代码区段下一个内存访问一次性断点,按F9让程序继续执行,程序会断在对话框的窗口过程函数入口处



    在窗口中得到的控件的ID在此处可以派上用场了,窗口处理过程的第三个参数传进来的是此消息所属的控件ID,这里OK按钮的控件ID是3EAh,所以可以定位OK按钮的点击消息响应代码段从004012B5处开始,很明显这里是读取用户输入,可以在此处下一个断点,然后让程序继续执行,直到读取完毕,程序将输入的用户名存放在0040218E处,将输入的注册码存放在0040217E处,在内存窗口中定位到其中一个地址,下一个内存访问断点,这里我选择的是0040218E,按F9继续运行,程序会断在此处



    ESI寄存器里存放的值就是我们所下断点的地址了,按Alt+K快捷键打开堆栈窗口,查看调用堆栈



    发现此函数的返回地址是00401232,在反汇编窗口中定位到00401232地址处,进行分析仍然可以定位出两个关键函数的地址



    [*]在OD里利用消息断点加RUN跟踪定位关键代码段:
    下消息断点的步骤和上面一种方法是一样的,这里我们不通过内存断点,改用RUN跟踪的方法定位关键代码段,RUN跟踪的作用是将程序执行过的指令记录下来,所以使用RUN跟踪的关键点是如何缩小要跟踪的范围,这里我们就结合消息断点来使用。
    在OK按钮上下好消息断点,输入一组测试数据,点击OK,程序断在了系统代码区,然后开始设置RUN跟踪
    第一步:点击菜单“选项/调试设置/跟踪”中设置相关配置,注意缓冲区大小,如果执行的指令太多,缓冲区满了的话,会自动丢弃前面老的记录



    第二步:点击菜单“调试/打开或清除RUN跟踪”
    第三步:在用户代码反汇编窗口中鼠标右键,在弹出菜单中选择“RUN跟踪/添加所有函数过程的入口”
    RUN跟踪设置好之后,按F9继续运行程序,会弹出提示对话框,不要点确定,点击OD菜单中的“查看/RUN跟踪”,这个窗口中显示的就是打开RUN跟踪后,到当前程序执行过的指令



    可以看到程序执行的最后一条指令是地址00401378处,CALL <JMP.&USER32.MessageBoxA>指令,双击跳转到反汇编窗口,可以得知此处就是弹出失败对话框的代码,继续分析程序中哪里跳转到了00401362处,最终我们仍然可以定位到此处



    可以在此处下一个断点,然后输入一组测试数据,查看0040218E和0040217E处存放的数据,便可得知两个关键函数,0040137E是对输入的名称进行处理,004013D8是对输入的注册码进行处理。


利用上面几种方法可以定位出两个关键函数,接下来要做的就是逆向出这两个函数的功能,写出注册机。首先分析0040137E



刚开始看反汇编的代码时候,单个指令很容易能看懂,但是组合在一起的时候,就不知道这段代码是做什么的了,只能多看、多练、多总结了。这段代码对名称字符串进行了一些简单的运算,并将运算结果保存在EAX寄存器中返回。
上述反汇编代码用C语言表示如下:
DWORD DealName(BYTE* name)
{
  BYTE *p = name;
  BYTE ch;
  while (true)
  {
    ch = *p;
    if (ch == 0)
    {
      break;
    }
    else if (ch < 'A')
    {
      MessageBox(NULL, "Incorrect!,Try Again", "Error!", MB_OK | MB_ICONEXCLAMATION | MB_APPLMODAL);
      return 0;
    }
    else if (ch >= 'z')
    {
      ch -= 0x20;
      *p = ch;
    }
    ++p;
  }

  p = name;
  DWORD num = 0;
  while (true)
  {
    ch = *p;
    if (ch == 0)
    {
      break;
    }
    else
    {
      num += ch;
      p++;
    }
  }
  num ^= 0x5678;
  return num;
}

接着再看看另一个关键函数004013D8,反汇编代码如下:



这段代码对输入的注册码字符串进行了一些简单的运算,并将运算结果保存在EBX寄存器中返回。用C语言表示如下:
DWORD DealSerial(BYTE* code)
{
  DWORD  i;
  DWORD  num = 0;
  for (i = 0; code[i] != 0; ++i)
  {
    num = num * 0x0A + code[i] - '0';
  }
  num ^= 0x1234;
  return num;
}

再看这两个函数执行完后,程序的处理逻辑



结合前面可以知道,EAX的值是对名称处理后的结果,EBX的值是对注册码处理后的结果,只要EAX等于EBX就能注册成功。这个就是典型的最开始提到的第三种序列号保护机制:通过对等函数检查注册码,F1(用户名)= F2(序列号)。
现在只要求出DealName的逆函数或者DealSerial的逆函数就可以写出注册机了,结合上下文可以将DealSerial函数理解成:将字符串转换成数字,然后将转换后的数字与0x1234异或,所以要让EAX和EBX的值相等,只要将EAX的值与0x1234进行异或,得到的值就是要求的注册码,很容易就能写出注册机,关键函数:
DWORD Cracker(DWORD num)
{
  num ^= 0x1234;
  return num;
}


参考资料:加密与解密

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

上传的附件:
收藏
免费 0
支持
分享
最新回复 (3)
雪    币: 35
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
学习一下,还是看不懂。纯粹练练头脑,不至于痴呆。
2015-10-10 08:13
0
雪    币: 1
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
学习一下,感谢分享
2015-10-10 09:19
0
雪    币: 0
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
学习一下,感谢分享
2015-10-10 22:06
0
游客
登录 | 注册 方可回帖
返回
//