AVALON Reverse Engineering CuTeEvil Crackme 2 算法分析后续

CuTeEvil Crackme 2 算法分析后续

环境配置

系统 : winxp 32bit
程序 : CuTeEvil Crackme 2
要求 : 找出注册解决方案
使用工具 :ida pro & od

静态分析

关键代码段在消息响应函数里:

          if ( GetDlgItemTextA(::hWnd, 1002, username, 32) )
          {
            if ( GetDlgItemTextA(::hWnd, 1004, password_403250, 32) )
            {
              len_name = lstrlenA(username);
              if ( len_name < 0x20 )
              {
                for ( i = 32 - len_name; i; --i )
                  *(&byte_40326F[len_name] + i) = 0x30;
              }
              qmemcpy(dword_403270, username, len_name);// make username long string
              V0 = dword_403270[0];
              V1 = dword_403270[1];
              sum = 0;
              for ( word_403203 = 32; word_403203; --word_403203 )// tea like encrypt
              {
                sum += 0x4F1BBCDC;
                V0 += *&aUsername[4] + (sum ^ (V1 >> 5)) + (V1 ^ *aUsername) + 16 * V1;
                V1 += *&aUsername[12] + (sum ^ (V0 >> 5)) + (V0 ^ *&aUsername[8]) + 16 * V0;
              }
              dword_403290 = V0;
              dword_403294 = V1;
              for ( index = 0; index != 8; ++index )
              {
                res_value = *(&dword_403290 + index);
                value = byte_403205[index];
                if ( byte_40320F[index] == 0x30 )
                  final_value = value + res_value;
                else
                  final_value = res_value - value;
                if ( final_value != aCutedevil[index] )
                  return 0;
              }
              _V0 = dword_403270[2];
              _V1 = dword_403270[3];
              SUM = 0;
              for ( word_403203 = 32; word_403203; --word_403203 )// tea like encrypt
              {
                SUM -= 0x5432EDCC;
                _V0 += *&aBad_cracker[4] + (SUM ^ (_V1 >> 5)) + (_V1 ^ *aBad_cracker) + 16 * _V1;
                _V1 += *&aBad_cracker[12] + (SUM ^ (_V0 >> 5)) + (_V0 ^ *&aBad_cracker[8]) + 16 * _V0;
              }
              dword_4032A0 = _V0;
              dword_4032A4 = _V1;
              if ( _V0 == 0xE0u
                && byte_4032A8
                 + BYTE3(dword_4032A4)
                 + BYTE2(dword_4032A4)
                 + BYTE1(dword_4032A4)
                 + dword_4032A4
                 + BYTE3(dword_4032A0)
                 + BYTE2(dword_4032A0)
                 + BYTE1(dword_4032A0)
                 + dword_4032A0 == 0xEFu )      // compare plaintext equals hardcode data
              {
                usp2 = &username_str_part2;
                password = password_403250;
                index_j = 16;
                xor_data = 3;
                while ( index_j )
                {
                  if ( *usp2 == 0x30 )
                  {
                    if ( index_j >= 12 )        // part2 len must > 4
                      return 0;
                    break;
                  }
                  if ( (*password ^ *usp2) != xor_data )
                    return 0;
                  ++password;
                  ++usp2;
                  ++xor_data;
                  --index_j;
                }
                MessageBoxA(::hWnd, Congratulations_Text, Caption, 0);
              }
            }

可以看出程序用魔改tea加密算法的方式实现了两处加密算法,之后才是与密码密切相关的检查代码:

if ( (*password ^ *usp2) != xor_data )
 if ( index_j >= 12 )        // username part2 len must > 4

最关键的就是要把加解密算法写出。

第一段username

编写代码提取密文:

'''
plain_text = [0x43, 0x75, 0x54, 0x65, 0x64, 0x45, 0x76, 0x69]
table      = [0x2D, 0x27, 0xA1, 0x25, 0x5C, 0x6B, 0x27, 0x5D]

plain_text[0] = plain_text[0] - table[0]
plain_text[1] = plain_text[1] + table[1]
plain_text[2] = plain_text[2] - table[2]
plain_text[3] = plain_text[3] + table[3]
plain_text[4] = plain_text[4] + table[4]
plain_text[5] = plain_text[5] - table[5]
plain_text[6] = plain_text[6] - table[6]
plain_text[7] = plain_text[7] - table[7]
'''

plain_text = [0x43, 0x75, 0x54, 0x65, 0x64, 0x45, 0x76, 0x69]
table      = [0x2D, 0x27, 0xA1, 0x25, 0x5C, 0x6B, 0x27, 0x5D]

plain_text[0] = plain_text[0] + table[0]
plain_text[1] = plain_text[1] - table[1]
plain_text[2] = plain_text[2] + table[2]
plain_text[3] = plain_text[3] - table[3]
plain_text[4] = plain_text[4] - table[4]
plain_text[5] = plain_text[5] + table[5]
plain_text[6] = plain_text[6] + table[6]
plain_text[7] = plain_text[7] + table[7]

for ch in plain_text:
    print hex(ch),



输出为:

0x70 0x4e 0xf5 0x40 0x8 0xb0 0x9d 0xc6

再解密:

#include <stdio.h>
#include <stdint.h>

//加密函数
void encrypt (uint32_t* v, uint32_t* k) {
    uint32_t v0=v[0], v1=v[1], sum=0, i;           /* set up */
    //uint32_t delta=0x9e3779b9;                     /* a key schedule constant */
    uint32_t delta=0x4F1BBCDC;                     /* a key schedule constant */
    uint32_t k0=k[0], k1=k[1], k2=k[2], k3=k[3];   /* cache key */
    for (i=0; i < 32; i++) {                       /* basic cycle start */
/*
        sum += delta;
        v0 += ((v1<<4) + k0) ^ (v1 + sum) ^ ((v1>>5) + k1);
        v1 += ((v0<<4) + k2) ^ (v0 + sum) ^ ((v0>>5) + k3);
*/
        sum += delta;
        v0 += k1 + (sum^(v1>>5)) + (v1^k0) + (v1<<4);
        v1 += k3 + (sum^(v0>>5)) + (v0^k2) + (v0<<4);
    }                                              /* end cycle */
    v[0]=v0; v[1]=v1;
    printf("sum: %x\n",sum);
}
//解密函数
void decrypt (uint32_t* v, uint32_t* k) {
    uint32_t v0=v[0], v1=v[1], sum=0xe3779b80, i;  /* set up */
    //uint32_t delta=0x9e3779b9;                     /* a key schedule constant */
    uint32_t delta=0x4F1BBCDC;                     /* a key schedule constant */
    uint32_t k0=k[0], k1=k[1], k2=k[2], k3=k[3];   /* cache key */
    for (i=0; i<32; i++) {                         /* basic cycle start */
/*
        v1 -= ((v0<<4) + k2) ^ (v0 + sum) ^ ((v0>>5) + k3);
        v0 -= ((v1<<4) + k0) ^ (v1 + sum) ^ ((v1>>5) + k1);
        sum -= delta;
*/
        v1 -= k3 + (sum^(v0>>5)) + (v0^k2) + (v0<<4);
        v0 -= k1 + (sum^(v1>>5)) + (v1^k0) + (v1<<4);
        sum -= delta;
    }                                              /* end cycle */
    v[0]=v0; v[1]=v1;
}

int main()
{
    uint32_t v[2]={0x40f54e70,0xc69db008},k[4]={1919251285,1701667150,1684095488,1634878303};
    // v为要加密的数据是两个32位无符号整数
    // k为加密解密密钥,为4个32位无符号整数,即密钥长度为128位
//    printf("加密前原始数据:%u %u\n",v[0],v[1]);
//    encrypt(v, k);
    printf("加密后的数据:%x %x\n",v[0],v[1]);
    decrypt(v, k);
    printf("解密后的数据:%x %x\n",v[0],v[1]);
    return 0;
}

结果为:

➜  playground ./test
加密后的数据:40f54e70 c69db008
解密后的数据:63617243 2064656b

即第一段username为Cracked

第二段username

可以看到约束条件:

              if ( _V0 == 0xE0u
                && byte_4032A8
                 + BYTE3(dword_4032A4)
                 + BYTE2(dword_4032A4)
                 + BYTE1(dword_4032A4)
                 + dword_4032A4
                 + BYTE3(dword_4032A0)
                 + BYTE2(dword_4032A0)
                 + BYTE1(dword_4032A0)
                 + dword_4032A0 == 0xEFu ) 

所以构造一段这样的数据就可以通过检测:

e0 02 02 02 02 02 02 03

在动态调试中确认细节时,请注意加密算法与之前不同的一点:

004031F7  42 61 64 5F 43 72 61 63 6B 65 72 00 20 00 2D 27  Bad_Cracker. .-'

0040127A   .  66:C705 03324>mov word ptr ds:[0x403203],0x20

用来计算的index的内存在key的范围内,也就是说每一轮计算的k3是不同的。解密算法如下:

#include <stdio.h>
#include <stdint.h>

//加密函数
void encrypt (uint32_t* v, uint32_t* k) {
    uint32_t v0=v[0], v1=v[1], sum=0, i;           /* set up */
    //uint32_t delta=0x9e3779b9;                     /* a key schedule constant */
    uint32_t delta=0xABCD1234;                     /* a key schedule constant */
    uint32_t k0=k[0], k1=k[1], k2=k[2], k3=k[3];   /* cache key */
    for (i=0; i < 32; i++) {                       /* basic cycle start */
/*
        sum += delta;
        v0 += ((v1<<4) + k0) ^ (v1 + sum) ^ ((v1>>5) + k1);
        v1 += ((v0<<4) + k2) ^ (v0 + sum) ^ ((v0>>5) + k3);
*/
        sum += delta;
        k3 = k[3] + 0x20 - i;
        v0 += k1 + (sum^(v1>>5)) + (v1^k0) + (v1<<4);
        v1 += k3 + (sum^(v0>>5)) + (v0^k2) + (v0<<4);
        printf("v0 v1:%x %x\n",v0,v1);
    }                                              /* end cycle */
    v[0]=v0; v[1]=v1;
    printf("sum: %x\n",sum);
}
//解密函数
void decrypt (uint32_t* v, uint32_t* k) {
    uint32_t v0=v[0], v1=v[1], sum=0x79a24680, i;  /* set up */
    //uint32_t delta=0x9e3779b9;                     /* a key schedule constant */
    uint32_t delta=0xABCD1234;                     /* a key schedule constant */
    uint32_t k0=k[0], k1=k[1], k2=k[2], k3=k[3];   /* cache key */
    for (i=0; i<32; i++) {                         /* basic cycle start */
/*
        v1 -= ((v0<<4) + k2) ^ (v0 + sum) ^ ((v0>>5) + k3);
        v0 -= ((v1<<4) + k0) ^ (v1 + sum) ^ ((v1>>5) + k1);
        sum -= delta;
*/
        k3 = k[3] + i + 1;
        v1 -= k3 + (sum^(v0>>5)) + (v0^k2) + (v0<<4);
        v0 -= k1 + (sum^(v1>>5)) + (v1^k0) + (v1<<4);
        sum -= delta;
        printf("v0 v1:%x %x\n",v0,v1);
    }                                              /* end cycle */
    v[0]=v0; v[1]=v1;
}

int main()
{
    uint32_t v[2]={0x20202e0, 0x3020202},k[4]={0x5F646142, 0x63617243, 0x0072656B, 0x272D0000};
    // v为要加密的数据是两个32位无符号整数
    // k为加密解密密钥,为4个32位无符号整数,即密钥长度为128位
//    printf("加密前原始数据:%x %x\n",v[0],v[1]);
//    encrypt(v, k);
    printf("加密后的数据:%x %x\n",v[0],v[1]);
    decrypt(v, k);
    printf("解密后的数据:%x %x\n",v[0],v[1]);
    return 0;
}

输出如下:

➜  playground ./test2
加密后的数据:20202e0 3020202
v0 v1:cc29e7e2 3f9215df
v0 v1:e253608a bdb699c1
v0 v1:86a87c4b 6a17d221
v0 v1:3abce1ed df7aa847
v0 v1:52ee068a 5f8ea2d
v0 v1:a2f833dd 4117825e
v0 v1:3a399c42 d018d4fb
v0 v1:71744480 42f0bb4
v0 v1:cf7e9e08 3c058afc
v0 v1:fb7e763b e4ca58b3
v0 v1:57174dd5 46657ddf
v0 v1:af0ceb31 40f3b39b
v0 v1:8ed2747f ffc66db
v0 v1:e28217ce a8b9434a
v0 v1:fa55ad5c 62fafca0
v0 v1:e97368e9 2cfec67b
v0 v1:94e0e5b1 c9ff7251
v0 v1:7e5c29ab aa8e8034
v0 v1:4777703a b8ac791c
v0 v1:665ba044 17b108f2
v0 v1:4c0d0bb4 15f237fc
v0 v1:8791d396 7dc56d66
v0 v1:3f467a2d a359445e
v0 v1:a05dad8c 3cc0f22b
v0 v1:50cbe740 141ed39f
v0 v1:c3042df3 df5cbd04
v0 v1:8806960a c3a048ca
v0 v1:19912759 345384f9
v0 v1:72ed0f1b aa380b30
v0 v1:40b60690 e0aa500e
v0 v1:c1c137d7 17b8ee98
v0 v1:2af433d2 650202c2
解密后的数据:2af433d2 650202c2

注册成功

剩下的xor代码也很简单:

>>> name = "dreamcracker"
>>> len(name)
12
>>> password = ""
>>> xor_data = 3
>>> for ch in name:
...     res = ord(ch) ^ xor_data
...     password += chr(res)
...     xor_data = xor_data + 1
... 
>>> print password
gv`gjk{khgh|

给出一段可以通过注册的数据:

00403270  43 72 61 63 6B 65 64 20 D2 33 F4 2A C2 02 02 65  Cracked ???e
00403280  64 72 65 61 6D 63 72 61 63 6B 65 72              dreamcracker

password : gv`gjk{khgh|

参考链接

  1. http://dreamcracker.today/2019/12/09/%e7%bb%8f%e5%85%b8%e7%ae%97%e6%b3%95%e6%95%b0%e7%bb%84%e6%b1%82%e5%92%8c-duelists-crackme-3-%e5%90%8e%e7%bb%ad/ 经典算法数组求和 – Duelist’s Crackme #3 后续