> ## Content Index
> Fetch the complete content index at: https://blog.hacking.pub/llms.txt
> Use this file to discover other available public pages before exploring further.

# crackme TBM
- URL: https://blog.hacking.pub/2017/06/26/crackme-tbm/
- Published: 2017-06-26T15:59:21.000Z
- Updated: 2017-06-26T16:17:37.000Z
- Author: chainhelen
- Tags: 逆向, 逆向工程, crackme, 练手, 看雪

#### 环境

看雪论坛的附件，其中序列号的[第一题](http://pan.baidu.com/s/1bo61h5H?ref=blog.hacking.pub)  
IDA PRO 破解版 Version 6.8.150423 (64-bit)  
[含有下文注释的ida pro数据文件](http://imgqi.hacking.pub/crackeme%20TBM/TBM.zip?ref=blog.hacking.pub)

#### 过程

(跳过UPX脱壳过程)  
首先在界面中输入Name，和Serial，发现错误的情况并不会有弹窗  
拖入IDA，可见`Functions View` 导入表中含有`GetDlgItemTextA`，那么在此处DLL设下断点  
输入`Name: woshi; Serial : 1234567`运行触发  
触发点断后，返回上一层`CR-F7` 或者 `Run until Return`

```no
seg019:00401539 call    GetDlgItemTextA
seg019:0040153E mov     ebx, eax
seg019:00401540 or      ebx, ebx                        ; 验证Name填入不可为空
seg019:00401542 jnz     short loc_401548
seg019:00401544 xor     eax, eax
seg019:00401546 jmp     short loc_401598                ; 如果为空，结束. 可以把Loc_401598重命名成 Loc_End_Function

```

按空格键，显示流程图样式

![crackemeTBM.png](https://blog.hacking.pub/qiniu/crackeme%20TBM/crackemeTBM3.png)

看图中代码，就是代码继续执行下来的流程，其中部分补充解释如下

```
//其中N.len 代表 Name.length
//(h)代表16进制，(d)代表10进制  

```

当输入名字长度满足

```
190(h) <= (2BC(h) - ( (30(h) - 48(h)) / N.len ) * 5 ) * 6B(h)  <= 2300(h) 

```

代码进入`loc_40157E`, 如下图

![crackemeTBM.png](https://blog.hacking.pub/qiniu/crackeme%20TBM/crackemeTBM5.png)

这里注意我标记了`1 , 2 , 3`  
`1处`代码末端判断决定流程走不走`2处`，但是最终都会走向`3处`  
`2处`代码很明显，是将`edx = 1`  
以此反推`1处`末端代码要想走向`2处`，必须`eax != 0`，也就是函数`sub_401305`执行结果

那么此时再进入`sub_401305`看看

`sub_401305`的前面部分的代码异常复杂，全是申请内存，初始化，跳过，直接看第一个判断跳转的地方

![crackemeTBM6.png](https://blog.hacking.pub/qiniu/crackeme%20TBM/crackemeTBM6.png)

`4处`末端代码很明显是取`Serial`值判空，很明显期望走向`5处`代码  
`5处代码`注释如下

```no
// serial[0] 代表serial的首尾字符ASSIC数值

seg020:004013BC mov     eax, 11CFh                      ; eax = 11CF(h)
seg020:004013C1 movzx   ecx, [ebp+String]               ; ecx = Serial[0]
seg020:004013C8 cdq
seg020:004013C9 idiv    ecx                             ; eax = 11CF(h) / serial[0]
seg020:004013C9                                         ; edx = 11CF(h) % serial[0]
seg020:004013CB cmp     edx, 17h
seg020:004013CE jz      short loc_4013D7
seg020:004013D0 xor     eax, eax
seg020:004013D2 jmp     loc_401504 

```

意思就是需满足`11CF(h) % serial[0] >= 17(h)`，否则 `5处`红色又是直接走向终结

这里需要计算一下，先前Serial输入的是 `1234567`，那么计算如下

```
11CF(h) % (1的ASSIC) = 11CF(h) % 31(h) = 4559(d) % 49(d) = 2(d) = 2(h) < 17(h)

```

所以首位是`1`是不可以的，可以尝试如下构造(也可以直接写个程序遍历ASSIC)

```
//此处^代表乘方，不是异或
11CF(h) - 17(h) = 11B8(h) = 4536(d) = 4536 = 2^3 * 3^4 * 7  
// 任意取因子 
2^3 * 3^2 = 72(d) = 48(h) 
// 48(h) 刚好为可见字符`H`

```

#### 重新输入

通过上面分析，这里修改Serial输入为`H234567`，重新断点  
流程到`6处`，这里其实是个循环，`6处`是初始条件，`7处`是循环体，`8处`是判断条件  
`ebx`从零开始，直到大于等于`Name.length`，本质上遍历求`name assic`的和，结果存在栈上`[ebp + __Name_Assic_Sum]`

求和结算后，进入`9处`初始化`ebx`,此处又是个大循环，依然遍历`Name`

![crackemeTBM8.png](https://blog.hacking.pub/qiniu/crackeme%20TBM/crackemeTBM8.png)

`12处`是循环体，注解如下

```no
seg020:004013F2 mov     edx, [ebp+arg_8]
seg020:004013F5 movsx   edi, byte ptr [edx+ebx]         ; edi = Name[i]
seg020:004013F9 mov     esi, [ebp+__Name_ASSIC_SUM]     ; esi = NSum
seg020:004013FC mov     ecx, ebx
seg020:004013FE shl     ecx, 2
seg020:00401401 mov     edx, ebx
seg020:00401403 inc     edx
seg020:00401404 sub     ecx, edx
seg020:00401406 movzx   ecx, [ebp+ecx+var_11F]          ; ecx = [ABCD...][3 * i - 1]
seg020:0040140E mov     edx, edi
seg020:00401410 xor     edx, ecx                        ; edx = N[i] xor [ABCD...][3 * i - 1]
seg020:00401412 mov     ecx, esi
seg020:00401414 imul    ecx, ebx                        ; ecx = NSum * i
seg020:00401417 sub     ecx, esi                        ; ecx = NSum * i - NSum
seg020:00401419 mov     esi, ecx                        ; esi = NSum * i - NSum
seg020:0040141B xor     esi, 0FFFFFFFFh                 ; esi = (NSum * i - NSum) xor 0FFFFFFFF(h)
seg020:0040141E lea     esi, [edx+esi+14Dh]             ; esi = (N[i] xor [ABCD...][3 * i - 1]) + ((NSum * i - NSum) xor 0FFFFFFFF(h)) + 14D(h)
seg020:00401425 mov     ecx, [ebp+__NAME_Length]        ; ecx = NLen
seg020:00401428 mov     edx, ebx                        ; edx = i
seg020:0040142A add     edx, 3                          ; edx = i + 3
seg020:0040142D imul    ecx, edx                        ; ecx = NLen * (i + 3)
seg020:00401430 imul    ecx, edi                        ; ecx = N[i] * NLen * (i + 3)
seg020:00401433 mov     eax, esi                        ; eax = (N[i] xor [ABCD...][3 * i - 1]) + ((NSum * i - NSum) xor 0FFFFFFFF(h)) + 14D(h)
seg020:00401435 add     eax, ecx                        ; eax = (N[i] xor [ABCD...][3 * i - 1]) + ((NSum * i - NSum) xor 0FFFFFFFF(h)) + 14D(h) + (N[i] * NLen * (i + 3))
seg020:00401437 mov     ecx, 0Ah                        ; ecx = 0A(h)
seg020:0040143C xor     edx, edx                        ; edx = 0
seg020:0040143E div     ecx                             ; eax = ( N[i] xor [ABCD...][3 * i - 1] + ((NSum * i - NSum) xor 0FFFFFFFF(h)) + 14D(h) + N[i] * NLen * (i + 3) ) / 0A(h)
seg020:0040143E                                         ; edx = ( N[i] xor [ABCD...][3 * i - 1] + ((NSum * i - NSum) xor 0FFFFFFFF(h)) + 14D(h) + N[i] * NLen * (i + 3) ) % 0A(h)
seg020:00401440 add     edx, 30h                        ; edx = ( N[i] xor [ABCD...][3 * i - 1] + ((NSum * i - NSum) xor 0FFFFFFFF(h)) + 14D(h) + N[i] * NLen * (i + 3) ) % 0A(h) + 30(h)
seg020:00401443 mov     [ebp+ebx+var_104], dl           ; 取 edx的低8位，记为Low
seg020:0040144A movzx   edi, [ebp+ebx+var_104]          ; edi = low
seg020:00401452 xor     edi, 0ADACh                     ; edi = low xor 0ADAC(h)
seg020:00401458 mov     esi, ebx                        ; esi = i
seg020:0040145A add     esi, 2                          ; esi = i + 2
seg020:0040145D mov     eax, edi                        ; eax = low xor 0ADAC(h)
seg020:0040145F imul    eax, esi                        ; eax = (low xor 0ADAC(h)) * (i + 2)
seg020:00401462 mov     ecx, 0Ah                        ; ecx = 0A(h)
seg020:00401467 cdq
seg020:00401468 idiv    ecx                             ; eax = ((low xor 0ADAC(h)) * (i + 2)) / 0A(h)
seg020:00401468                                         ; edx = ((low xor 0ADAC(h)) * (i + 2)) % 0A(h)
seg020:0040146A add     edx, 30h                        ; edx = ((low xor 0ADAC(h)) * (i + 2)) % 0A(h) + 30(h)
seg020:0040146D mov     [ebp+ebx+var_104], dl           ; 取edx的低8位，记为Low_Low
seg020:00401474 inc     ebx                             ; ebx = i + 1  ;即循环次数+1

```

流程继续到`11处`代码，该处是将求出结果进行格式化，先跳过过程直接看求出最终结果是什么

![crackemeTBM9.png](https://blog.hacking.pub/qiniu/crackeme%20TBM/crackemeTBM9.png)

ecx保存着数据地址 ，结果是`T86856-118`，流程继续走到`13处`是一个循环

![crackemeTBM11.png](https://blog.hacking.pub/qiniu/crackeme%20TBM/crackemeTBM11.png)

循环变量是`eax`，起始值语句`or eax 0FFFFFFFF(h)`本质上就是赋值了`-1`  
（为什么不用`mov`，因为这样机器码会少）  
循环直到不为零，很显然`13处`是求上叙`T86856-118`的长度，保存在`eax`寄存器  
流程继续往下走到`14处`，这里已经可以猜测出  
`T86856-118`就是对应的`Name： whoshi`的序列号，`14处`代码会把生成的序列号跟输入的序列号作比较

```no
//14处代码
seg021:004014DE push    eax
seg021:004014DF lea     eax, [ebp+String]
seg021:004014E5 push    eax
seg021:004014E6 lea     eax, [ebp+var_21F]
seg021:004014EC push    eax
seg021:004014ED call    sub_4012C2    //这里就是比较函数
seg021:004014F2 add     esp, 0Ch     //释放栈内存
seg021:004014F5 cmp     eax, 0     //这里可以看出eax保存了比较结果
seg021:004014F8 jnz     short loc_401501

```

![crackemeTBM12.png](https://blog.hacking.pub/qiniu/crackeme%20TBM/crackemeTBM12.png)

`14处`代码有两处出口 => `15处`、`16处` \=> `17处`  
`15处`和`16处`的差异仅仅在于`eax` 一个取值为0，一个取值为1  
回想起前面`2处`的结果，也就明白了，就是把结果`eax = 1`一层一层往上面传  
那么这里下断点，修改寄存器`eax`的值为1，答案可以弹出了

#### 注册机

序列号计算分为两部分，例如`T86856-118`  
第一部分`86856`的计算方式在`12处`代码，上面已经给出注释了  
第二部分`118`的计算部分在格式的地方，即`14处`的部分代码，以下是注解

```no
seg020:0040149A mov     edi, [ebp+__NAME_Length]        ; edi = N.len
seg020:0040149D mov     eax, edi                        ; eax = N.len
seg020:0040149F imul    eax, [ebp+__Name_ASSIC_SUM]     ; eax = N.len * N_Assic_Sum
seg020:004014A3 mov     ecx, 64h                        ; ecx = 64(h)
seg020:004014A8 cdq
seg020:004014A9 idiv    ecx                             ; eax = N.len * N_Assic_Sum / 64(h)
seg020:004014A9                                         ; edx = N.len * N_Assic_Sum % 64(h)
seg020:004014AB mov     edi, edx                        ; edi = N.len * N_Assic_Sum % 64(h)
seg020:004014AD add     edi, 30h                        ; edi = N.len * N_Assic_Sum % 64(h) + 30(h) 
seg020:004014B0 push    edi

//此处edi即为第二部分

```

但是仅仅这两部分求出来的字符串并不是对的，在比较函数sub\_4012C2里面还有层转换，比较简单 注解如下

```no
//注释中R就是上面求得的字符串，注意 循环变量 i 的是从 1 开始取值的，也就是说第一个字符并非一定是它代码中写死的'T'，利用我们上面求法，第一个字符是`H`也是可以的

seg011:004012D3 movsx   edi, byte ptr [edx+esi] ; edi = R[i]
seg011:004012D7 mov     eax, edi        ; eax = R[i]
seg011:004012D9 xor     eax, 20h        ; eax = R[i] xor 20(h)
seg011:004012DC mov     ecx, 0Ah        ; ecx = 0A(h)
seg011:004012E1 cdq
seg011:004012E2 idiv    ecx             ; eax = (R[i] xor 20(h)) / 0A(h)
seg011:004012E2                         ; edx = (R[i] xor 20(h)) % 0A(h)
seg011:004012E4 mov     edi, edx
seg011:004012E6 add     edi, 30h        ; edx = (R[i] xor 20(h)) % 0A(h) + 30(h)

```

注册机代码

```
#include <string.h>
#include <stdio.h>

#define MAXN 10000

char name[MAXN + 1];
char Serial[MAXN + 1];
char resFirst[MAXN + 1], fLen;
int resSecond;
char res[MAXN + 1];

//[ABCD..]
int getAlpha(int index) {
    if(index < 0 || index >= 24) {
        return 0;
    }
    return 'A' + index;
}

int main() {
    while(gets(name)) {
        int NLen = (int)strlen(name);

        if(0 == NLen || '\n' == name[0]) {
            break;
        }

        int NSum = 0;
        fLen = 0;
        for(int i = 0;i < NLen;i++) {
            NSum += name[i];
        }

        //the first part of result
        for(int i = 0;i < NLen;i++) {
            int tmp = 0;

            //(N[i] xor [ABC..][3 * i - 1])
            tmp += name[i] ^ getAlpha(3 * i - 1);

            //((Sum * i - Sum) xor  FFFFFFFF) + (14D(h))
            tmp += ((NSum * i - NSum) ^ 0xFFFFFFFF) + 0x14d;

            //(NLen * (i + 3) * N[i])
            tmp += NLen * (i + 3) * name[i];

            //low
            int low = tmp % 0xa + 0x30;

            //res
            int curRes = (low ^ 0xadac) * (i + 2) % 0xa + 0x30;

            //restore the first part of result
            resFirst[fLen++] = curRes;;
        }
        resFirst[fLen++] = '\0';

        //the second part of result
        resSecond = NLen * NSum % 0x64 + 0x30;

        //join
        sprintf(res, "T%s-%d\0", resFirst, resSecond);

        //change
        int RLen = strlen(res);
        for(int i = 1;i < RLen;i++) {
            res[i] = (res[i] ^ 0x20) % 0xa + 0x30;
        }
        printf("%s\n",res);
    }
    return 0;
}

```