第十六章-序列号生成算法分析-Part1

本章,我们分析的CrackMe与之前的不同之处在于序列号是基于名称变化的,也就是说我们将讨论序列号生成算法。

1、查看CrackMe程序信息

先运行一下CrackMe程序:
在这里插入图片描述

有File、Help两个下拉框,选项有Exit、Register、About:
在这里插入图片描述

再用PEiD查看信息:
在这里插入图片描述

这是一个由汇编代码编写的Win32 GUI程序。
例如:Delphi是使用Pascal语言,Masm/Tasm是使用8086汇编语言编写程序的

2、使用OllyDbg加载CrackMe程序

使用OllyDbg加载CrackMe程序:
在这里插入图片描述

程序停在了入口处。
EP代码非常短,这是因为CrackMe程序是使用汇编语言编写出来的可执行文件。
  在使用VC++、VC、Delphi等开发工具编写程序时,除了自己编写的代码外,还有一部分启动函数是由编译器添加的,经过反编译后,代码看上去就变得非常复杂。但是如果直接使用汇编语言编写程序,汇编代码会直接变为反汇编代码。观察上图中的代码可以看到,main()直接出现在EP中,简洁又直观,充分证明了这是一个直接用汇编语言编写的程序。
接下来查看它使用了哪些常见的Windwos API函数,鼠标右键,查找->当前模块中的名称(标签):
在这里插入图片描述

当前模块中的名称有:
在这里插入图片描述

我们看到了很多上面常见的功能:
USER32.GetDlgItemTextA:获取用户输入的文本;
USER32.EndDialogKERNEL32.ExitProcess:退出、结束进程;
USER32.MessageBoxA:弹出新的对话框。

3、GetDlgItemTextA函数设置一个内存断点

接下来我们给GetDlgItemTextA函数设置一个内存断点:
在这里插入图片描述

运行程序,选择Register功能,输入姓名和序列号:
在这里插入图片描述

点击OK按钮,程序就会停在刚刚设置的断点处:
在这里插入图片描述

  GetDlgItemTextA函数的参数中Buffer = CRACKME.0040218E,指向的缓冲区存放着用户输入的文本,起始地址为40218E,在Buffer参数上面单击鼠标右键选择-Follow in Dump来在数据窗口中定位到用户名缓冲区:
在这里插入图片描述

  里面就是我们输入的用户名“Test”, 之后程序会根据我们的输入生成一个正确的序列号,来和我们输入的输入的序列号进行比较
我们继续调试,执行到调用获取输入的Name的GetDlgItemTextA函数的位置:
在这里插入图片描述

  我们可以看到获取输入的Name的GetDlgItemTextA函数,接下来还有一个获取输入的Name的GetDlgItemTextA函数
我们再点击运行程序,它又停在了GetDlgItemTextA函数的断点处:
在这里插入图片描述

  这次GetDlgItemTextA函数的参数中Buffer = CRACKME.0040217E,指向的缓冲区存放着用户输入的序列号,起始地址为40217E,在Buffer参数上面单击鼠标右键选择-Follow in Dump来在数据窗口中定位到序列号缓冲区:
在这里插入图片描述

  里面就是我们输入的序列号“123456”,之后我们照样执行到GetDlgItemTextA函数返回,回到自己的代码空间调用GetDlgItemTextA函数的地方:
在这里插入图片描述

我们继续单步调试,步过EndDialog函数之后:
在这里插入图片描述

Register对话框就会退出
  现在缓冲区CRACKME.0040217E里面存放了我们输入的错误序列号,从程序的角度出发,程序就会取用户输入的错误的序列号与根据名称生成的正确序列号进行比较,所以我们可以对错误序列号设置内存访问断点,看看程序的哪些地方使用了。
  我们拖选中错误的序列号,单击鼠标右键选择-Breakpoint-Memory,on access,然后运行起来:
在这里插入图片描述

4、读取输入的序列号的位置

之后当程序访问读取缓冲区CRACKME.0040217E里我们输入的错误序列号时,就会停下来:
在这里插入图片描述

我们可以看到程序尝试从缓冲区中读取错误序列号的第一个字节,并且将保存到BL寄存器中,我们按F7键单步调试:
在这里插入图片描述

因为bl是39不为0,所以根据test把两个bl相与之后置标志位,je不会跳转;这里je只会在bl为0、ZF=1时跳转。
之后就会继续执行

004013EA  |.  80EB 30       |sub bl,0x30

就会得到错误序列号第一个字符的值‘1’
在这里插入图片描述

之后执行

004013ED  |.  0FAFF8        |imul edi,eax

al在循环开始被初始化为0xA,edi在

004013DA  |.  33FF          xor edi,edi

被初始化成了0,所以这里imul edi,eax之后edi依旧是0:
在这里插入图片描述

之后

004013F0  |.  03FB          |add edi,ebx

相加到edi的结果为1:
在这里插入图片描述

然后下面

004013F2  |.  46            |inc esi            ;  CRACKME.0040217E

  ESI递增1,然后跳转到循环开始处,读取错误序列号的下一个字符,只要读取的输入的字符bl为空就会跳出循环。
  所以这个循环的逻辑就是读取输入的字符串的每个字符,把字符的ASCII值减去30得到它的16进制值,然后逐个乘以eax(0xA,就相当于16进制的进位),再累加到edi寄存器保存:
在这里插入图片描述

我们直接在跳出循环后的地址:

004013F5  |> \81F7 34120000 xor edi,0x1234

  设一个断点,然后运行程序,就会跳出循环并且停在CRACKME.004013F5了,不过我们开始在缓冲区CRACKME.0040217E的错误序列号设置了一个内存断点,所以每次循环读取

004013E4  |.  8A1E          |mov bl,byte ptr ds:[esi]

的时候,程序依旧会停下来,我们为了方便可以去掉这个内存断点:
在这里插入图片描述

我们拖选中错误的序列号0040217E,单击鼠标右键选择-Breakpoint-删除内存断点:
在这里插入图片描述

5、输入的序列号的值

然后运行起来,这次就会停在CRACKME.004013F5了:
在这里插入图片描述

这时候由错误序列号最终得到的edi是0x0001E240,10进制形式就是123456
在这里插入图片描述

之后执行edi异或0x1234

004013F5  |> \81F7 34120000 xor edi,0x1234

在这里插入图片描述

得到edi的值为0x0001F074,之后

004013FB  |.  8BDF          mov ebx,edi

把返回值保存在ebx寄存器中,然后返回:
在这里插入图片描述

之后执行

0040123D   .  83C4 04       add esp,0x4

栈顶指针加4个字节:
在这里插入图片描述

6、输入的序列号计算的值保存在eax寄存器

然后

00401240   .  58            pop eax

把栈顶的值0x00005738出栈,并保存在eax寄存器
在这里插入图片描述

  之后比较错误序列号得到的值ebx(0x0001F074)和eax(0x00005738),根据是否相等来决定跳转到弹出正确的对话框还是错误的对话框
在这里插入图片描述

所以我们的目的是让ebx(错误序列号得到的)和eax(正确序列号)相等,

7、程序的计算逻辑

  ebx的值(0x1F074)是由我们输入的错误序列号字符串“123456”,逐个字符转换成16进制之后累乘以0xA,再相加的结果edi(0x123456),再和0x1234异或得到的0x1F074
  所以我们想让eax和ebx相等,就也把eax异或0x1234
0x00005738 xor 0x1234 = 0x450c(17676)
  也就是说我们输入序列号“123456”就会得到ebx=0x1F074
如果我们输入“17676”,就可以得到ebx=0x5738,此时就会ebx=eax,通过验证跳转到弹出正确的对话框。
  所以用户名的“Test”对应的序列号就是“17676”:
在这里插入图片描述在这里插入图片描述

  到这里我们就明白了程序逻辑,通过用户名“Test”计算得到eax=0x5738,再把序列号“17676”转换之后异或得到ebx=0x5738,当eax=ebx的时候,就说明这是一对正确的序列号,注册成功。

8、计算输入的用户名

  其实同样的方法,我们也可以在输入的Name字符串的缓冲区“Test”下一个内存断点,就能找到程序读取输入的Name字符串的位置,然后就可以知道由“Test”如何计算得到eax=0x5738:
在这里插入图片描述

运行程序,停在读取缓冲区0040218E处:
在这里插入图片描述

9、验证输入的用户名字符串

我们可以看到依然是从用户名缓冲区中逐个字符读取,然后

00401385  |.  84C0          |test al,al

判断字符是否为空,为空则跳出循环,不为空则继续执行

00401389  |.  3C 41         |cmp al,0x41

比较字符和0x41(‘A’)的大小,之后

0040138B  |. /72 1F         |jb short CRACKME.004013AC

JB无符号小于则跳转当CF标志位为1的时候才会跳转。也就是字符小于0x41(‘A’)就跳出循环。接着

0040138D  |.  3C 5A         |cmp al,0x5A
0040138F  |.  73 03         |jnb short CRACKME.00401394

就是大于0x5A(‘Z’)就跳出循环到CRACKME.004013D2

00401394  |> \E8 39000000   |call CRACKME.004013D2

CRACKME.004013D2函数就是:
在这里插入图片描述

  当字符大于‘A’时就减小0x20,就是把它从小写字母转换成大写字母,让他处在A-Z的范围中,可以看到执行完这个循环之后,我么你输入的用户名字符串“Test”已经变成了“TEST”
所以这个循环就是判断用户名字符是否为空且转换成A-Z的范围之内的大写字符。
全部字符通过循环之后就到了:

0040139D  |.  E8 20000000   call CRACKME.004013C2

调用CRACKME.004013C2:
在这里插入图片描述

10、输入的用户名的值

同样的,接下来的这个循环也是从Name缓冲区中逐个读取字符给bl,之后把值累加在edi寄存器:
在这里插入图片描述

最后返回时edi的值为0x54+0x45+0x53+0x54=0x140
返回到

004013A2  |.  81F7 78560000 xor edi,0x5678

在这里插入图片描述

将edi异或0x5678,得到0x00005738
在这里插入图片描述

然后把edi的值保存到eax寄存器,之后跳转到返回
在这里插入图片描述

这就是eax寄存器的值0x5738的由来:
在这里插入图片描述

11、完整的用户名和序列号的比较逻辑

所以完整的用户名和序列号的比较逻辑就是:
  通过用户名“Test”转换成字符串“TEST”,然后逐个字符累加得到edi的值0x140,在异或0x5678计算得到的值保存在eax=0x5738;再把序列号“17676”转换之后,异或得到ebx=0x5738,当eax=ebx的时候,就说明这是一对正确的序列号,注册成功。

12、用户名和序列号的注册机

由此我们可以写一个注册机:
serial.c:

#include<stdio.h>
int main()
{
    //用户名
    char name[10];
    //用户名的值
    int edi = 0;
    //序列号
    int serial = 0;
    printf("请输入你的用户名:\n");
    scanf("%s", name);
    //判断用户名字符是否为空且转换成A-Z的范围之内的大写字符
    int i = 0;
    for (; name[i]; i++) {
        //字符小于‘A’
        if (name[i] < 65) {
            printf("输入的用户名中有不是大小写字母的非法字符!\n");
            break;
        }
        //字符大于‘Z’
        if (name[i] > 90) {
            name[i] = name[i] - 32;
        }
        edi = edi + name[i];
    }
    serial = edi ^ 0x1234 ^ 0x5678;
    printf("计算得到的序列号是:%d", serial);
    return 0;
}

运行截图:
在这里插入图片描述

用户名:~~~
序列号:17750
输入到软件:
在这里插入图片描述在这里插入图片描述

注册成功!说明我们的注册机是成功的!

  • 5
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值