solly 发表于 2019-11-19 00:50

160 个 Crackme 之 066 -- figugegl.2 算法分析和注册机实现

有一段时间没有发贴了,这次发一个非常简单的,萌新可以来看看。
160 个 CrackMe 之 066 -- figugegl.2 是一个 LCC-Win32 编译的 CrackMe,LCC 是一个小巧的 C 语言编译环境,编写简单的 C 程序比较方便,开发环境的体积很小,并且包括了资源编辑器。


先看看 CrackMe 的信息:

节的信息如下:

可见,该 CrackMe 没有加壳,编译器是 LCC Win32。


直接用OD载入,显示如下:

前面是 SEH 处理代码,后面一个内部过程调用,这个调用相当于 C 语言的 main() 函数。


跟随进入 main() 函数,如下图所示:

开始部分就是命令行参数处理,继续向下看,如下图所示:

在调用 KERNEL32.GetModuleHandleA() 后,就是 Windows 程序的入口函数了,即 WinMain() 函数。


再跟随这个 WinMain() 函数,如下图所示:

由上图可见,通过以下代码,生成一个对话框应用程序:
00401298|.6A 00         push    0                              ; /lParam = NULL
0040129A|.68 4F144000   push    0040144F                         ; |DlgProc = figugegl.0040144F
0040129F|.6A 00         push    0                              ; |hOwner = NULL
004012A1|.6A 64         push    64                               ; |pTemplate = 64
004012A3|.57            push    edi                              ; |hInst
004012A4|.E8 F7020000   call    <jmp.&USER32.DialogBoxParamA>    ; \DialogBoxParamA
可以看到,DlgProc = 0x0040144F,通过 OD 的右键菜单“跟随立即数”,就可来到这个 DlgProc 了,如下图所示:

可以看到处理 WM_CLOSE, WM_INITDIALOG, WM_COMMAND 消息的代码,我们主要关心其处理 WM_COMMAND 代码,如图所示的所选代码部分,就是一个 Case 跳转,其参数 EAX = 0 ~ 3,共4种情况。
静态分析就到这里,我们先运行 CrackMe 看看,直接 F9,弹出 CrackMe 界面如下:

先输入假码注册,如上图所示,点“CHECK”。来到前面静态分析的 Case 跳转处,如下图所示:

可以看到,分成4个部分,在数据区,可以看到用到的 4 个地址。点击“CHECK”按钮时,EAX = 0x00000001,这就表示 CHECK 按钮的 ControlID 为 0x00000001。
上图显示,“CHECK”的处理代码如下:
004014A1   .FF75 08         push    dword ptr
004014A4   .E8 FCFEFFFF       call    004013A5                         ;注册验证函数
004014A9   .59                pop   ecx
004014AA   .31C0            xor   eax, eax
004014AC   .40                inc   eax
004014AD   .EB 56             jmp   short 00401505

其中,只有一个 call 004013A5 调用,接着直接返回 eax=1。跟随进入 call 调用,如下图所示:

这个过程就是注册码的算法代码,其中算法在上图所选择的代码内,算法也很简单。主要代码如下:
004013C2|.89C6            mov   esi, eax                         ;名字的长度值
004013C4|.EB 06             jmp   short 004013CC
004013C6|>C64435 EB 20      /mov   byte ptr , 20       ;在name后添加 0x20,空格
004013CB|.46                |inc   esi
004013CC|>83FE 14            cmp   esi, 14                         ;len < 20
004013CF|.^ 7C F5             \jl      short 004013C6                  ;长度<20,添加空格
004013D1|.31F6            xor   esi, esi                         ;int i=0
004013D3|>0FB67C35 EB       /movzx   edi, byte ptr       ;name
004013D8|.0FB65435 F5       |movzx   edx, byte ptr        ;name
004013DD|.89F8            |mov   eax, edi
004013DF|.31D0            |xor   eax, edx                        ;xor值 = name^name
004013E1|.B9 0A000000       |mov   ecx, 0A                         ;10
004013E6|.99                |cdq
004013E7|.F7F9            |idiv    ecx
004013E9|.83C2 30         |add   edx, 30                         ;dl = (xor值 % 10) + '0'
004013EC|.885435 D6         |mov   byte ptr , dl       ;serial = dl
004013F0|.46                |inc   esi
004013F1|.39CE            |cmp   esi, ecx                        ;i<10
004013F3|.^ 7C DE             \jl      short 004013D3                  ;循环

分析如下:
1、通过 GetDlgItemTextA() 取得界面上输入的用户名;
2、如果用户名长度不够20个字符,则在后面添加空格字符,直到长度达到20个字符;
3、取名字第1到10字符,依次与名字的第11到20字符异或运算;
4、将异或运算后的结果与10求余,并加上0x30,实际上就是生成一个'0'~'9'范围的数字字符。
5、将前面生成的10个数字字符组成序列号(但这个数字并不一定是真正的序列号,看下面説明)。
6、CrackMe 并没有用上面生成的序列号与界面上输入的序列号进行字符串比较,而是先转换成整数再进行比较的。如上图所示,是通过库函数 strtoul() 函数转换的,不过,前面代码生成的序列号有10位数字,当大于最大无符号整数范围(0xFFFFFFFF,也就是 4294967295)时,会返回最大无符号整数(0xFFFFFFFF)。这个时候,我们输入的就不能是前面1~5步生成的序列号了,而是一个常数:4294967295。
如下图所示:

前5步生成的序列号是:8359659000,明显这个数字已超出 4294967295 了,即溢出了。

通过 strtoul()转换后,EAX 返回的是 0xFFFFFFFF,也就是 4294967295 了,明显已经是不正确的值了。
转换计算的序列号以后, CrackMe 通过调用 GetDlgItemInt() 函数,从界面取回输入的序列号,然后与转换后的序列号比较,如下图所示:

比较后,根据结果会跳转到下面显示正确与否的提示。

如上图所示,两个序列号不相等,结果是失败,显示信息如下:

我们根据前面的分析,重新输入新的序列号:4294967295。如下图所示:

再点“CHECK”,来到序列号比较处,如下图所示:


这次是一样的了,都是 0xFFFFFFFF,即 4294967295 了。F9 继续,这次提示如下了:

表明这次序列号输入正确。
前面测试的是一个异常的序列号,下面我们看看正常的序列号,如下图所示,先把 name 改一下:

点击“CHECK”,再次来到序列号比较的代码处:

可以看到,这次转换正常了,没有溢出了,序列号是 3966900000,小于 4294967295,转换正常了。
F9 运行,返回界面。输入正确的序列号,如下图所示:

重新点击“CHECK”,再一次来到序列号对比的代码处:

可以看到,这次是相等的了,F9后,提示如下,表明序列号正确:

分析完毕!!!
下面代码是根据前面分析的算法写出的注册机:
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int getSN(char * name);

int main(int argc, char** argv) {
    char name = {0};
    printf("Input your name: ");
    gets(name);

        getSN(name);
        return 0;
}

int getSN(char * name) {
    int n=strlen(name);
        for (int i=n; i<20; i++) {
      name = 0x20;    /// padding
    }
               
        unsigned long long check = 0;
        for (int i=0; i<10; i++) {
                check = check * 10 + (name ^ name) % 10;
        }
        if(check>>32) {
          printf("Serial: 4294967295\n"); /// overflow, Max: 0xFFFFFFFF
        } else {
                printf("Serial: %lu\n", check); /// normal case
        }
      
    return 0;
}
使用 Dev-C++ 调试通过。

solly 发表于 2019-11-19 10:16

w780628d 发表于 2019-11-19 06:52
这个真看不懂。

这个 Crackme 已是相当的简单了。如果不写贴,不写注册机,三~五分钟不到就可找到码了。

solly 发表于 2019-11-19 10:19

hfxiang 发表于 2019-11-19 10:09
这个好像是太专业了,学习起来有困难

这个就是一个裸*奔的结构清晰的货,已是很简单的了。。。

w780628d 发表于 2019-11-19 06:52

gunyao 发表于 2019-11-19 09:24

学习一下,给楼主点个赞{:1_921:}

hfxiang 发表于 2019-11-19 10:09

这个好像是太专业了,学习起来有困难

孤竹 发表于 2019-11-19 11:12

虽然我看不懂,评分支持一下

笑海的星星 发表于 2019-11-19 14:09

有看没有懂,最基础的还没看懂

Dabingpeng 发表于 2019-11-19 18:22

支持一下

霖僡 发表于 2019-11-19 20:29

高手简单,初学难!赞!
页: [1] 2
查看完整版本: 160 个 Crackme 之 066 -- figugegl.2 算法分析和注册机实现