【吾爱破解精华帖】一个crackme的分析

(出处: 吾爱破解论坛)
拿到crackmePEID的深度扫描发现是aspack2.x的壳,用Abstersiver把壳脱掉。运行脱壳后的程序,随便输入几组用户名和注册码,发现并没有任何提示信息。由此可以推测只有输入正确的nameserial程序才会给出提示信息。用OD加载起来程序,大概浏览一下代码,找到两处调用MessageBox的地方,很容易判断上面那个MessageBox是和验证注册码相关的。因为它上面有一个跳转,那个跳转是要跳转到返回的,所以这个跳转很关键,而决定要不要跳转的判断条件是eax的值,而eax的值是call 00401509的返回值。所以00401509这个call很关键,我们在这个地址处下一个断点重点分析一下。

下面就是00401509这个CALL的详细分析。Ctrl+F2重新启动被调试程序,F9让程序运行起来,我先输入一组nameserialnamenvluoyan,serialwhaththefuck ,点击程序的check按钮,发现程序被断在了00401509



总结一下就是获取用户输入的name并对name的长度进行判断。


总结一下就是对name的长度进行运算,确保name的长度必须在一个范围之内,即0x0190<=((0x02bc-5*(0x30-0x48/Lname))*0x6b)-0xcf6c<=0x2300解一下这个不等式。这个不等式我之后直接用16进制计算,发现得不到正确的结果,不知道为什么。



我们输入的nvluoyan8位,正好在[39]之间。

name的长度验证之后就进行了对serial的处理了。处理serial的函数需要三个参数,第一个是hwnd,和算法没关系,第二个是name的长度Lname,第三个是用户输入的用户名name

然后进入00401305这个call的内部来看看。


没什么重要的信息,就是一些字符串初始化操作,字符串的作用都在注释中写清楚了,就不多做解释了。


然后获取用户输入的Serial,把Serial存放到UserSerial中。


获取用户输入的Serial的第一个字符,用0x11cfSerial的第一个字符取余,检测结果是不是0x17,如果是则继续流程,否则返回。可见这个crackmeSerial的第一个字符是有要求的,我们可以写一个小程序来输出一下,很简单的遍历输出即可。


输出结果是:


所以用户输入的Serial的第一个字符必须是$ * 6 8 ? H Q T l ~ 10个中的其中一个。为了让流程继续下去,我们修改一下输入的Serial,从whatthefuck变为6hatthefuck


然后是取得用户的name,并把name中的每个字符按顺序加起来,最后的结果保存在sum中。00401305这个CALL有两个重要的参数一个是用户输入的name,一个是name的长度Lname,在这里用到了!


这部分就是就是关键了,详细的在注释中都说明了。


然后对SerialFirst数组格式化,就是在最前面加一个大写字母T,把结果存放到SerialFormatFirst数组中。



然后是对SerialFirst数组的第二次格式化,结果存放到SerialFormatSecond[256]中。



然后就是一个重要的call了,这个call有三个参数,其中两个是根据name生成并格式化过的Serial和它的长度,另外一个是用户输入的Serial

程序并没有直接比较SerialFormatSecondUserSerial中的每个字符,而是取得SerialFormatSecond中的每个字符,经过一定运算得到一个字符,如果这个字符和UserSerial中相应的某个字符比较是否相等,所以注册机就很好写了。值得注意的一点是,有一个代码是idiv ecx,是对0xA取余。取余这个操作很有意思,它的结果总是小于自己,对0x0A取余,那么结果一定小于0x0A,想一想为什么要对0x0A取余呢?因为它的结果一定是在00x0x0A-1这个范围里面,再加上0x30,那么这个范围就变成了0x30~0x3A-1,对应的ASCII码就是数字0到数字9,这样就保证用户的注册码只能包含数字来了。注册机源码本来想用python写的,可是对于位操作不太熟悉,所以就用C语言写了一个。
[C] 纯文本查看 复制代码
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
#include <iostream>
#include "string.h"
#include "stdio.h"
#include "stdlib.h"
 
 
int main()
{
    char name[256] = {0};
    char MagicNum[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    char GenSerial[256] = {0};
    char FormatSerial[256 * 2] = { 0 };
    int Lname =0;
    int LMagicNum = strlen(MagicNum);
    int LSerial = 0;
    int sum = 0;
    char cname = 0;
    char cMagicNum=0;
    char cGenSerial = 0;
 
    int index = 0;
    int xor1 = 0;
    int xor2 = 0;
    int sum2 = 0;
    int FormatChar = 0;
    int index2 = 0;
 
    char CharComputSerial = 0;
     
    int exit;
   
 
    printf("please input your name :");
    scanf_s("%s",name,256);
     
    printf("\n");
    printf("*******************************************************\n");
    Lname = strlen(name);
 
 
    //用户名长度必须大于等于3小于等于9
    if (Lname < 3 || Lname>9)
 
    {
        printf("the length of user name shall range 3 to 9");
        return 0;
    }
 
 
   
 
 
    //return 1;
 
    for (int i = 0; i <= Lname; i++)
    {
        sum = sum + name[i];
    }
 
  //不支持ASCII码之外的字符 0x21~0x7E
  //写一个循环检测name中的每个字符是否在0x21到0x7e中当然可行
  //但我想用另外一种方法,既然name的长度为3位到9位,而且要保证
  //每个字符都属于键盘输入的可见ASCII码,那么name中所有的字符的和
  //一定有一个最小值,也一定有一个最大值
  //最小值就是0x21*3,当name长度是3位的时候
  //最大值就是0x7e*9,当name长度是9位的时候
 
    if (sum < 0x21 * 3 || sum>0x7e * 9)
    {
        printf("illegle user name");
        return 0;
    }
 
 
    
 
    do {
         
         
        cname = name[index];
    
        cMagicNum=MagicNum[3 * index - 1];
        if (index == 0)
        {
            cMagicNum = 0x0;
        }
        xor1 = cname ^ cMagicNum;
     
 
        xor2 = (sum * index - sum) ^ 0xffffffff;
 
  
 
        sum2 = xor1 + xor2 + 0x014d;
 
     
        cGenSerial = (((sum2 + Lname * (index + 3) * cname) % 0x0A) + 0x30)&0xff;
 
    
        cGenSerial = ((((unsigned int)cGenSerial^ 0x0ADAC)  * (index + 2)) % 0x0A + 0x030) & 0xff;
           
         
        
 
        GenSerial[index] = cGenSerial;
        index++;
    } while (index < Lname);
 
     
 
     
 
    FormatChar = ((Lname * sum) % 0x64)+0x30;
 
 
    sprintf_s(FormatSerial, "%c%s-%d", 'T', GenSerial,FormatChar);
 
 
    LSerial = strlen(FormatSerial);
    
 
 
 
    index2 = 1;
 
 
    do
    {
       CharComputSerial= FormatSerial[index2];
       CharComputSerial ^= 0x20;
       CharComputSerial %= 0x0A;
       CharComputSerial += 0x30;
       FormatSerial[index2] = CharComputSerial;
 
       index2++;
 
    } while(index2<LSerial);
 
 
    printf("your name:%s\n",name);
    printf("your code:%s\n",FormatSerial);
    printf("*******************************************************\n");
 
    scanf_s("%d",&exit);
 
    return 1;
 
 
}





crackme6.zip

(5.2 KB, 下载次数: 25)



posted @ 2021-11-01 23:30  说0不是1  阅读(504)  评论(0)    收藏  举报