女萝岩 发表于 2020-1-21 02:50

一个crackme的分析

本帖最后由 女萝岩 于 2020-1-21 03:06 编辑

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

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



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


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



我们输入的nvluoyan是8位,正好在,9]之间。

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

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


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


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


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


输出结果是:


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


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


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


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



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



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

程序并没有直接比较SerialFormatSecond和UserSerial中的每个字符,而是取得SerialFormatSecond中的每个字符,经过一定运算得到一个字符,如果这个字符和UserSerial中相应的某个字符比较是否相等,所以注册机就很好写了。值得注意的一点是,有一个代码是idiv ecx,是对0xA取余。取余这个操作很有意思,它的结果总是小于自己,对0x0A取余,那么结果一定小于0x0A,想一想为什么要对0x0A取余呢?因为它的结果一定是在0到0x0x0A-1这个范围里面,再加上0x30,那么这个范围就变成了0x30~0x3A-1,对应的ASCII码就是数字0到数字9,这样就保证用户的注册码只能包含数字来了。注册机源码本来想用python写的,可是对于位操作不太熟悉,所以就用C语言写了一个。
#include <iostream>
#include "string.h"
#include "stdio.h"
#include "stdlib.h"


int main()
{
    char name = {0};
    char MagicNum[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    char GenSerial = {0};
    char FormatSerial = { 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;
    }

//不支持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;
   
      cMagicNum=MagicNum;
      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 = 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;
       CharComputSerial ^= 0x20;
       CharComputSerial %= 0x0A;
       CharComputSerial += 0x30;
       FormatSerial = 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;


}






女萝岩 发表于 2020-1-21 03:16

每天下班之后分析一点,弄了差不多一个星期吧,语言表达能力很差,各位多指点。

chenjingyes 发表于 2020-1-22 09:45

byh3025 发表于 2020-1-22 08:24
大神是通过什么找到弹窗的call的呢?

我记得有个叫什么xspy还是什么的东西可以抓按钮事件,前一阵子这个论坛看过相关文章:lol

weikun444 发表于 2020-1-21 12:21

楼主辛苦了。学习了

netspirit 发表于 2020-1-21 12:42

感觉好厉害..........

52896009 发表于 2020-1-21 17:29

好厉害..........

小小学生 发表于 2020-1-21 21:54

高手在民间。很低调啊。希望多出原创作品交流交流

byh3025 发表于 2020-1-22 08:24

大神是通过什么找到弹窗的call的呢?

chenjingyes 发表于 2020-1-22 09:15

不错的题目 可以练练手:lol

hua111 发表于 2020-1-22 10:06

页: [1] 2
查看完整版本: 一个crackme的分析