crackme TBM

环境

看雪论坛的附件,其中序列号的第一题
IDA PRO 破解版 Version 6.8.150423 (64-bit)
含有下文注释的ida pro数据文件

过程

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

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

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

//其中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

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

那么此时再进入sub_401305看看

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

crackemeTBM6.png

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

// 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

12处是循环体,注解如下

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

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

crackemeTBM11.png

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

//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

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

注册机

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

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里面还有层转换,比较简单 注解如下

//注释中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;
}