160个crackme 024详细题解(动态指令)

0x4012DD处开始向后的三条指令是关键

004012DD  lods dword ptr ds:[esi];  [0x4011ec处开始,每次向后取4个字节(高端存取法),进行异或运算],直到执行到向后偏移4 * 0x3E个字节

  1. 004012DD   >  AD            lods dword ptr ds:[esi]                  ;  [0x4011ec处开始,每次向后取4个字节(高端存取法),进行异或运算],直到执行到向后偏移4 * 0x3E个字节  
  2. 004012DE   . |33D8          xor ebx,eax  
  3. 004012E0   . |49            dec ecx  

 

注意在0x4011ec到向后偏移4 * 0x3E处不能有任何断点,不然某些地方数据会变成0xCC, 0xCC对应的汇编指令为int3,是专门用来调试的中断指令。当CPU执行到int3指令时,会触发异常代码为EXCEPTION_BREAKPOINT的异常,这样OD就能够接收到这个异常,然后进行相应的处理,这也是CC断点也叫int3断点的原因。

 

因此为了获取数据,我们直接从exe文件中读取

  1. const int count = 0x3E;  
  2. int size = count * 4;  
  3. FILE *f = fopen("Chafe.2.exe","rb");  
  4. fseek(f,0x5ec,0);//0x4011ec在文件中的绝对偏移为0x5EC  
  5. unsigned long *buffer = new unsigned long[0x3E];  
  6. fread(buffer,4,count,f);  
  7. fclose(f);  

接下来,我们模拟这个异或运算

  1. unsigned long x = 0;  
  2. for (int i=0;i<count;i++) {  
  3.     //对于倒数第2组和第三组的数据,是我们需要逆推的数据,这里先跳过  
  4.     //由于高位存取法   
  5.     //所以倒数第二组数据为0xD833ADXX  
  6.     //所以倒数第三组数据为0xXXXXXX04   
  7.     if (i == count - 3 || i == count - 2) continue;  
  8.     x ^= buffer[i];  
  9. }  

异或运算可以分字节运算,所以,为了求解XX,我们先把已知的字节做运算

  1. //x ^= 0xNNNNNN04  
  2. x ^= 0x00000004;  
  3. //x ^= 0xD833ADNN  
  4. x ^= 0xD833AD00;  

然后由于最终结果要等于0xAFFCCFFB,才能注册成功

因此我们再与0xAFFCCFFB做异或运算

  1. x ^= 0xAFFCCFFB; 

现在,x的结果为0x5426eb58

对应起来,倒数第二组的末尾字节为0x58,

倒数第三组的前三个字节为0x54eb26

但是,由于4个数据一组,

  1. 004012BB xor dword ptr ds:[0x4012D9],eax ;  [0x4012d9] ^= ans  
  2. 004012C1 shr eax,0x10  ;  ans = ans >> 0x10  
  3. 004012C4 sub word ptr ds:[0x4012D9],ax  ;  [0x4012d9] -= ans 以上相当于修改了[0x4012d9]处的指令  

程序是修改0x4012d9处的数据,4字节数据,即0x4012d9 ,0x4012da,0x4012db,0x4012dc处的数据,而根据逆推,它们分别存储着数据0xEB 0x26 0x54 0x58,由于高位存取法,该处数据实际为0x585426EB

即我们需要把运算结果的最后一字节放到最开头,我们使用位移实现

  1. //做位调换   
  2. unsigned long t = x;  
  3. x = (t & 0xFF) << 24;  
  4. x = x + (t >> 0x8);  

现在,我们得出,要想注册成功,0x4012D9的数据必须为0x585426EB

更为关键的是0x585426EB对应的指令正好有一条jmp 0x00401301,因此这样就能跳转到注册成功的地方了,这种动态改变指令的算法很巧妙,值得我们学习

我们来分析,程序是如何计算这里的数据的

  1. 004012A3   .  A1 0B304000   mov eax,dword ptr ds:[0x40300B]          ;  unsigned long ans = 0x58455443  
  2. 004012A8   .  BB 6C314000   mov ebx,Chafe_2.0040316C                 ;  for (int i=0; i<16; i++) {  
  3. 004012AD   >  0303          add eax,dword ptr ds:[ebx]               ;     ans += *((unsigned long *)p++);  
  4. 004012AF   .  43            inc ebx  
  5. 004012B0   .  81FB 7C314000 cmp ebx,Chafe_2.0040317C  
  6. 004012B6   .^ 75 F5         jnz XChafe_2.004012AD                    ;  }  

首先是对用户名进行计算,循环16次,每一次用户名字符串指针向后移到一个字节,并把当前指针指向的地址处的数据向后取出4个字节,转成数字,即向后取4个字符,获取ascii码,由高到低组合成4字节数据

接下来是关键的计算

  1. 004012B8   .  5B            pop ebx                                  ;  获取输入的serial,存入ebx  
  2. 004012B9   .  03C3          add eax,ebx                              ;  ans += serial  
  3. 004012BB   .  3105 D9124000 xor dword ptr ds:[0x4012D9],eax          ;  [0x4012d9] ^= ans  
  4. 004012C1   .  C1E8 10       shr eax,0x10                             ;  ans = ans >> 0x10  
  5. 004012C4   .  66:2905 D9124>sub word ptr ds:[0x4012D9],ax            ;  [0x4012d9] -= ans 以上相当于修改了[0x4012d9]处的指令  

这里就是修改0x4012D9处的数据了

首先写出运算过程

ans += serial;

         unsigned long x = 0x584554; //0x4012D9的初始内容

         x ^= ans;

         ans = ans >> 0x10;

         x -= ans & 0xFF;

x == 0x585426EB

由于异或运算可以分字节单独运算,于是我们写出方程

0x0058 ^ ans_h = 0x5854

ans_l ^ 0x4554  –  ans_h = 0x26EB

解得ans_h = 0x580C

ans_l = 3BA3

所以 ans = 0x580C3BA3

  1. unsigned long fl = 0x00584554;   
  2. unsigned long key = fl ^ x;  
  3. t = (key >> 0x10) + (x & 0xFFFF);  
  4. t ^= (fl & 0xFFFF);  
  5. key = (key & 0xFFFF0000) + t;  

 

name的运算结果+serial必须为0x580C3BA3才能注册成功

所以serial = key – (name的运算结果)

综上,我们写出注册机

  1. #include <iostream>  
  2. #include <cstdio>  
  3. #include <cstring>  
  4.   
  5. using namespace std;  
  6.   
  7. int main() {  
  8.     const int count = 0x3E;  
  9.     int size = count * 4;  
  10.     FILE *f = fopen("Chafe.2.exe","rb");  
  11.     fseek(f,0x5ec,0);//0x4011ec在文件中的绝对偏移为0x5EC  
  12.     unsigned long *buffer = new unsigned long[0x3E];  
  13.     fread(buffer,4,count,f);  
  14.     fclose(f);  
  15.     unsigned long x = 0;  
  16.     for (int i=0;i<count;i++) {  
  17.         //对于倒数第2组和第三组的数据,是我们需要逆推的数据,这里先跳过  
  18.         //由于高位存取法   
  19.         //所以倒数第二组数据为0xD833ADXX  
  20.         //所以倒数第三组数据为0xXXXXXX04   
  21.         if (i == count - 3 || i == count - 2) continue;  
  22.         x ^= buffer[i];  
  23.     }  
  24.     //cout << "异或" << x << endl;  
  25.     x ^= 0xAFFCCFFB;  
  26.     //x ^= 0xNNNNNN04  
  27.     x ^= 0x00000004;  
  28.     //x ^= 0xD833ADNN  
  29.     x ^= 0xD833AD00;  
  30.       
  31.     //做位调换   
  32.     unsigned long t = x;  
  33.     x = (t & 0xFF) << 24;  
  34.     x = x + (t >> 0x8);  
  35.       
  36.     //0x585426EB  
  37.     cout << hex << "0x" << x << endl;  
  38.     unsigned long fl = 0x00584554;   
  39.     unsigned long key = fl ^ x;  
  40.       
  41.     t = (key >> 0x10) + (x & 0xFFFF);  
  42.     t ^= (fl & 0xFFFF);  
  43.     key = (key & 0xFFFF0000) + t;  
  44.       
  45.     char name[50];  
  46.     memset(name,0,50);  
  47.     cout << "请输入用户名:";  
  48.     cin >> name;  
  49.     int len = strlen(name);  
  50.     char *p = name;  
  51.     unsigned long ans = 0x58455443;  
  52.     for (int i=0; i<16; i++) {  
  53.         ans += *((unsigned long *)p++);  
  54.     }  
  55.     cout << dec << key - ans << endl;  
  56.     return 0;  
  57. }  

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值