返回
顶部

references:

要破解的二进制文件和注册机

下断点的方法和crackhead一样,通过搜索所有引入的函数,从中找出可以用来下断点的函数

此处为GetDlgItemTextA函数

我们在该函数出现的所有位置下断点,然后在cycle程序的输入框中随便输点东西,触发断点即可

断点触发之后,我们就不需要这些断点了,在函数调用的下一行下断点即可

下面我们开始捋代码

004010A6  |. 6A 11          PUSH 11                                  ; /Count = 11 (17.)
004010A8  |. 68 71214000    PUSH cycle.00402171                      ; |Buffer = cycle.00402171
004010AD  |. 68 E9030000    PUSH 3E9                                 ; |ControlID = 3E9 (1001.)
004010B2  |. FF75 08        PUSH DWORD PTR SS:[EBP+8]                ; |hWnd
004010B5  |. E8 0F020000    CALL <JMP.&USER32.GetDlgItemTextA>       ; \GetDlgItemTextA
004010BA  |. 0BC0           OR EAX,EAX
004010BC  |. 74 61          JE SHORT cycle.0040111F
004010BE  |. 6A 11          PUSH 11                                  ; /Count = 11 (17.)
004010C0  |. 68 60214000    PUSH cycle.00402160                      ; |Buffer = cycle.00402160
004010C5  |. 68 E8030000    PUSH 3E8                                 ; |ControlID = 3E8 (1000.)
004010CA  |. FF75 08        PUSH DWORD PTR SS:[EBP+8]                ; |hWnd
004010CD  |. E8 F7010000    CALL <JMP.&USER32.GetDlgItemTextA>       ; \GetDlgItemTextA
004010D2  |. 0BC0           OR EAX,EAX
004010D4  |. 74 49          JE SHORT cycle.0040111F

GetDlgItemTextA函数被调用了两次,返回值存储在EAX中,为获取到的字符串的长度,第三个参数为传出参数,用于存储获取到的字符串,两次调用中分别传入了指针0x0040217100402160

根据调试结果,第一次调用获取到的是Serial,第二次调用获取到的是Name

只要我们输入不为空(即EAX值不为0),那么上面代码中的JE SHORT cycle.0040111F就不会执行

接着往下看

004010D6  |. B9 10000000    MOV ECX,10
004010DB  |. 2BC8           SUB ECX,EAX
004010DD  |. BE 60214000    MOV ESI,cycle.00402160                   ;  ASCII "1231231231231231"
004010E2  |. 8BFE           MOV EDI,ESI
004010E4  |. 03F8           ADD EDI,EAX
004010E6  |. FC             CLD
004010E7  |. F3:A4          REP MOVS BYTE PTR ES:[EDI],BYTE PTR DS:[>

这段代码对用户名进行了拷贝,CLD复位DDF标志寄存器,因此在执行REP MOVS时ESI和EDI每次拷贝完成后都会增加,由于此处为REP MOVS BYTE,因此每次拷贝一个字节,ESI和EDI的增加梯度也为1字节

拷贝的次数由ECX-EAX的结果决定,即(16-用户名长度),其实就是将用户名的长度扩展到了16位

004010E9  |. 33C9           XOR ECX,ECX
004010EB  |. BE 71214000    MOV ESI,cycle.00402171                   ;  ASCII "1234567890000000"
004010F0  |> 41             /INC ECX
004010F1  |. AC             |LODS BYTE PTR DS:[ESI]
004010F2  |. 0AC0           |OR AL,AL
004010F4  |. 74 0A          |JE SHORT cycle.00401100
004010F6  |. 3C 7E          |CMP AL,7E
004010F8  |. 7F 06          |JG SHORT cycle.00401100
004010FA  |. 3C 30          |CMP AL,30
004010FC  |. 72 02          |JB SHORT cycle.00401100
004010FE  |.^EB F0          \JMP SHORT cycle.004010F0
00401100  |> 83F9 11        CMP ECX,11
00401103  |. 75 1A          JNZ SHORT cycle.0040111F

清空ECX,然后将ESI指向Serial

下面开始循环,其中有一条指令大家可能没见过

LODS指令,Load String Operand

比如此处的LODS BYTE PTR DS:[ESI],会将ESI所指向的内容加载到EAX寄存器中,至于是加载到EAX、AX、AL由指令控制,此处由于我们是LODS BYTE,因此内存中的内容会被加载到AL中

因此上面代码中的循环就干了一件事,就是检测Serial中的字符是否位于0x300x7E之间,也就是ASCII码表中的48~126之间

最后判断了一下字符串长度是否为16,因为ECX会多加一次,也就是说比实际长度多1,根据CMP ECX, 11,我们可以确定Serial的长度必须为16位,因为如果无法满足这个条件,程序就会直接跳到RET指令,那就没得玩儿了,你可以自己试一下,如果Serial的长度不是16位,当你点击Check按钮的时候,程序是没有任何反应的

接着看

00401105  |. E8 E7000000    CALL cycle.004011F1
0040110A  |. B9 01FF0000    MOV ECX,0FF01
0040110F  |. 51             PUSH ECX
00401110  |. E8 7B000000    CALL cycle.00401190
00401115  |. 83F9 01        CMP ECX,1
00401118  |. 74 06          JE SHORT cycle.00401120
0040111A  |> E8 47000000    CALL cycle.00401166
0040111F  |> C3             RETN
00401120  |> A1 68214000    MOV EAX,DWORD PTR DS:[402168]

我们需要确保JE SHORT cycle.00401120执行,因为如果在这里没有跳转的话,那么不可避免的就一定会执行RETN,程序结束,又没得玩了

RETN上面那个CALL只是输出一下错误提示而已

因此我们要保证在前两个CALL执行完毕之后,ECX的值为1

这里我贴一下这两个函数的汇编代码:

cycle.004011f1

004011F1  /$ A1 60214000    MOV EAX,DWORD PTR DS:[402160]
004011F6  |. 8B1D 64214000  MOV EBX,DWORD PTR DS:[402164]
004011FC  |. 3305 71214000  XOR EAX,DWORD PTR DS:[402171]
00401202  |. 331D 75214000  XOR EBX,DWORD PTR DS:[402175]
00401208  |. 25 0F1F3F7F    AND EAX,7F3F1F0F
0040120D  |. 81E3 00010307  AND EBX,7030100
00401213  |. 33C9           XOR ECX,ECX
00401215  |> 8BF0           /MOV ESI,EAX
00401217  |. 8BFB           |MOV EDI,EBX
00401219  |. D3E6           |SHL ESI,CL
0040121B  |. D3E7           |SHL EDI,CL
0040121D  |. 81E6 80808080  |AND ESI,80808080
00401223  |. 81E7 80808080  |AND EDI,80808080
00401229  |. 8BD6           |MOV EDX,ESI
0040122B  |. C0EE 07        |SHR DH,7
0040122E  |. 66:C1E2 07     |SHL DX,7
00401232  |. C1EA 08        |SHR EDX,8
00401235  |. C0EE 07        |SHR DH,7
00401238  |. 66:C1E2 07     |SHL DX,7
0040123C  |. C1EA 08        |SHR EDX,8
0040123F  |. C0EE 07        |SHR DH,7
00401242  |. 66:D1EA        |SHR DX,1
00401245  |. 8BF2           |MOV ESI,EDX
00401247  |. 8BD7           |MOV EDX,EDI
00401249  |. C0EE 07        |SHR DH,7
0040124C  |. 66:C1E2 07     |SHL DX,7
00401250  |. C1EA 08        |SHR EDX,8
00401253  |. C0EE 07        |SHR DH,7
00401256  |. 66:C1E2 07     |SHL DX,7
0040125A  |. C1EA 08        |SHR EDX,8
0040125D  |. C0EE 07        |SHR DH,7
00401260  |. 66:C1EA 05     |SHR DX,5
00401264  |. 8BFA           |MOV EDI,EDX
00401266  |. 33FE           |XOR EDI,ESI
00401268  |. 8BD7           |MOV EDX,EDI
0040126A  |. 81E2 FF000000  |AND EDX,0FF
00401270  |. 51             |PUSH ECX
00401271  |. 52             |PUSH EDX
00401272  |. BA 08000000    |MOV EDX,8
00401277  |. 91             |XCHG EAX,ECX
00401278  |. 83F8 03        |CMP EAX,3
0040127B  |. 7F 0F          |JG SHORT cycle.0040128C
0040127D  |. F6E2           |MUL DL
0040127F  |. 5A             |POP EDX
00401280  |. 83C0 08        |ADD EAX,8
00401283  |. 91             |XCHG EAX,ECX
00401284  |. D3C0           |ROL EAX,CL
00401286  |. 33C2           |XOR EAX,EDX
00401288  |. D3C8           |ROR EAX,CL
0040128A  |. EB 0D          |JMP SHORT cycle.00401299
0040128C  |> 83E8 03        |SUB EAX,3
0040128F  |. F6E2           |MUL DL
00401291  |. 5A             |POP EDX
00401292  |. 91             |XCHG EAX,ECX
00401293  |. D3C3           |ROL EBX,CL
00401295  |. 33DA           |XOR EBX,EDX
00401297  |. D3CB           |ROR EBX,CL
00401299  |> 59             |POP ECX
0040129A  |. 41             |INC ECX
0040129B  |. 83F9 08        |CMP ECX,8
0040129E  |.^0F85 71FFFFFF  \JNZ cycle.00401215
004012A4  \. C3             RETN

cycle.00401190

00401190  /$ 5F             POP EDI
00401191  |. 59             POP ECX
00401192  |. 57             PUSH EDI
00401193  |. 81F9 80000000  CMP ECX,80
00401199  |. 7E 55          JLE SHORT cycle.004011F0
0040119B  |. 51             PUSH ECX
0040119C  |. 8BF1           MOV ESI,ECX
0040119E  |. 81E1 FF000000  AND ECX,0FF
004011A4  |. 8BF8           MOV EDI,EAX
004011A6  |. 83F9 08        CMP ECX,8
004011A9  |. 7E 05          JLE SHORT cycle.004011B0
004011AB  |. 8BFB           MOV EDI,EBX
004011AD  |. C1E9 04        SHR ECX,4
004011B0  |> C1C7 08        /ROL EDI,8
004011B3  |. D1E9           |SHR ECX,1
004011B5  |.^75 F9          \JNZ SHORT cycle.004011B0
004011B7  |. C1EE 08        SHR ESI,8
004011BA  |. 23FE           AND EDI,ESI
004011BC  |. 81E7 FF000000  AND EDI,0FF
004011C2  |. 59             POP ECX
004011C3  |> BE 80000000    MOV ESI,80
004011C8  |> 85FE           /TEST ESI,EDI
004011CA  |. 74 20          |JE SHORT cycle.004011EC
004011CC  |. 33FE           |XOR EDI,ESI
004011CE  |. 57             |PUSH EDI
004011CF  |. 81E1 00FF0000  |AND ECX,0FF00
004011D5  |. 87CE           |XCHG ESI,ECX
004011D7  |. 32E9           |XOR CH,CL
004011D9  |. 33F1           |XOR ESI,ECX
004011DB  |. 87F1           |XCHG ECX,ESI
004011DD  |. 51             |PUSH ECX
004011DE  |. FF05 82214000  |INC DWORD PTR DS:[402182]
004011E4  |. E8 A7FFFFFF    |CALL cycle.00401190
004011E9  |. 5F             |POP EDI
004011EA  |.^EB D7          |JMP SHORT cycle.004011C3
004011EC  |> D1EE           |SHR ESI,1
004011EE  |.^75 D8          \JNZ SHORT cycle.004011C8
004011F0  \> C3             RETN

指令都认识,但是这两个函数具体做了什么,我是不知道的,肯定是某种算法,但是变成机器码之后,几乎面目全非,根本无法辨识

我也是盯着这两段代码看了好久,什么思路都没有,最后就干脆用C语言把这个代码逻辑给实现了

https://144.one/s/u14s4

在这段代码中,我固定了用户名的前8位以及序列号的前4位,然后爆破序列号的4~7位

根据代码逻辑,EBX的初始化过程如下:

ebx = name[4-7] ^ serial[4-7] & 0x07030100

由于很多bit位都在与操作中被丢弃了,所以serial[4-7]并没有太多可能的值

但是我的这个代码并没有得到合法的Name和Serial,不过我发现了一些规律

在第一个函数cycle.004011f1运行完毕之后,EAX的值从未变过,EBX的值呈现周期性循环,循环周期为4

经过第二个函数cycle.00401190的运算之后,ecx的值总是同一个

后来我就突发奇想,如果我们控制EAX的值,使对应掩码0x7F3F1F0F所有的保留位全部为1,会发生什么?于是得出如下关系式

name[0-3] ^ serial[0-3] = 0x7F3F1F0F
serial[0-3] = name[0-3] ^ 0x7F3F1F0F

上面表达式中的0x7F3F1F0F可以有多个值,只要每个字节的高4bit的值x分别满足0<= x <10<= x <20<= x <40<= x <8即可,具体逻辑可以看代码

但是这样会限制serial[3]的值不能超过0x50,后来放松了第一个字节的限制,使其满足0<= x <2,发现这样也能生成有效的序列号,

然后我就随便找了一组,**JEUUUJ

结果发现,居然找出了满足ecx=1serial[4-7]

虽然我不知道这究竟是为什么,但事实就是只要我们满足上面那个表达式,那么经过最多64次尝试,就能找到一组合法的用户名和序列号

在通过ecx==1的检测之后,还有一个检测,但是就比较简单了,具体分析请查看下面的注册机源代码

注册机运行效果:

image-20220513115712303

下面是注册机代码,仍然使用codeblocks编译,你可以取消____DEBUG宏的注释以观察程序运行过程中各变量的变化:

// Author:  12138
// URL:     http://144.34.164.217
//          https://144.one
// Date:    2022/05/12 04:00 AM
// Desc:    CrackMe.cycle serial generator

#include   <windows.h>
#include   <stdio.h>
#include   <string.h>
#include   <time.h>

// #define ____DEBUG

#ifdef ____DEBUG
#define FUCK(_format, _var) printf(_format, _var);
#define CUM(_str) printf(_str);
#else
#define FUCK(_format, _var);
#define CUM(_str);
#endif

typedef unsigned int _ui;

// 这个计数器应该是一个全局变量
_ui _TODO_counter = 0xFEDCBA98;

char generate_random(char lower, char upper) {
   return (rand() % (upper - lower + 1)) + lower;
}

_ui string_to_hex(char* _string, int _begin) {
    char _24_31[3]={0};
    char _16_23[3]={0};
    char _8_15[3]={0};
    char _0_7[3]={0};

    sprintf(_24_31, "%02X", _string[_begin]);
    sprintf(_16_23, "%02X", _string[_begin+1]);
    sprintf(_8_15, "%02X", _string[_begin+2]);
    sprintf(_0_7, "%02X", _string[_begin+3]);

    char hex_string[8+1] = {0};
    strcpy(hex_string, _0_7);
    strcat(hex_string, _8_15);
    strcat(hex_string, _16_23);
    strcat(hex_string, _24_31);
    _ui _ret_val = (_ui)strtoul(hex_string, NULL, 16);
    return _ret_val;
}

int hex_to_string(_ui _hex_value, char* _ret_string) {
    _ui _s_15 = _hex_value & 0xFF000000;
    _s_15 = _s_15 >> 24;
    _ui _s_14 = _hex_value & 0x00FF0000;
    _s_14 = _s_14 >> 16;
    _ui _s_13 = _hex_value & 0x0000FF00;
    _s_13 = _s_13 >> 8;
    _ui _s_12 = _hex_value & 0x000000FF;
    if (_s_12 < 0x30 || _s_12 > 0x7E || _s_13 < 0x30 || _s_13 > 0x7E || _s_14 < 0x30 || _s_14> 0x7E || _s_15 < 0x30 || _s_15 > 0x7E) {
        return 1;
    }
    char hex_string[4+1] = {0};
    char _tmp_val[2] = {0};
    sprintf(_tmp_val, "%c", _s_12);
    strcpy(hex_string, _tmp_val);
    sprintf(_tmp_val, "%c", _s_13);
    strcat(hex_string, _tmp_val);
    sprintf(_tmp_val, "%c", _s_14);
    strcat(hex_string, _tmp_val);
    sprintf(_tmp_val, "%c", _s_15);
    strcat(hex_string, _tmp_val);
    strcpy(_ret_string, hex_string);
    return 0;
}

_ui _401190(_ui eax, _ui ebx, _ui ecx) {
    if(ecx > 0x80) {
        _ui _tmp_val = ecx;
        _ui esi = ecx;
        ecx = ecx & 0xFF;
        _ui edi = eax;
        if(ecx > 0x08) {
            edi = ebx;
            ecx = ecx >> 4;
        }
        while (ecx != 0) {
            edi = _rotl(edi, 8);
            ecx = ecx >> 1;
        }
        esi = esi >> 8;
        edi = edi & esi;
        edi = edi & 0xFF;
        ecx = _tmp_val;
        esi = 0x80;
        while (1) {
            if((esi & edi) == 0) {
                esi = esi >> 1;
                // 只有esi和edi与运算的结果等于0且esi右移1位等于0的时候,这个循环才会被打破
                if(esi == 0)
                    break;
                else
                    continue;
            }
            edi = edi ^ esi;
            _tmp_val = edi;
            ecx = ecx & 0xFF00;
            _ui _exch_val = ecx;
            ecx = esi;
            esi = _exch_val;
            // 对CH和CL进行异或运算,并将结果反映到ECX上
            _ui ch = ecx & 0xFF00;
            ch =  ch >> 8;
            _ui cl = ecx & 0xFF;
            ch = ch ^ cl;
            ch = ch << 8;
            ch = ch & 0xFF00;
            ecx = ecx & 0xFFFF00FF;
            ecx = ecx | ch;
            esi = esi ^ ecx;
            _exch_val = ecx;
            ecx = esi;
            esi = _exch_val;
            _TODO_counter += 1;
            FUCK("[*] _TODO counter: 0x%x\n", _TODO_counter);
            ecx = _401190(eax, ebx, ecx);
            edi = _tmp_val;
            esi = 0x80;
            continue;
        }
        return ecx;
    }
    else
        return ecx;
}

int register_func(char* extend_user_name, char* serial) {

    printf("[*] current Serial: %s\n", serial);
    // 获取用户名的0~3位并转换成整型数
    _ui eax = string_to_hex(extend_user_name, 0);
    FUCK("[*] eax: %x\n", eax);

    // 获取用户名的4~7位并转换成整型数
    _ui ebx = string_to_hex(extend_user_name, 4);
    FUCK("[*] ebx: %x\n", ebx);

    // 获取序列号的0~3位并转换成整型数
    _ui _xor_eax = string_to_hex(serial, 0);
    FUCK("[*] the oprand xor with eax: %x\n", _xor_eax);

    // 获取序列号的4~7位并转换成整型数
    _ui _xor_ebx = string_to_hex(serial, 4);
    FUCK("[*] the oprand xor with ebx: %x\n", _xor_ebx);

    eax = eax ^ _xor_eax;
    ebx = ebx ^ _xor_ebx;
    eax = eax & 0x7F3F1F0F;
    ebx = ebx & 0x07030100;
    printf("[*] eax is ready for loop: %x\n", eax);
    printf("[*] ebx is ready for loop: %x\n",ebx);

    // 下面开始循环
    int loop_counter = 0, ecx = 0;
    while(loop_counter < 8) {
        FUCK("\n_________________OO<--%d-->OO_________________\n", loop_counter);
        ecx = loop_counter;
        _ui esi = eax;
        _ui edi = ebx;
        esi = esi << ecx;
        edi = edi << ecx;
        // 这一步其实就是获取到esi和edi每个字节中的最高位
        esi = esi & 0x80808080;
        edi = edi & 0x80808080;
        _ui edx = esi;
        // 下面这一堆移位操作其实是把每个字节中的最高位移动到了DL的高四位中
        _ui _d = edx & 0x80000000;
        _d = _d >> 31;
        _ui _c = edx & 0x00800000;
        _c = _c >> 23;
        _ui _b = edx & 0x00008000;
        _b = _b >> 15;
        _ui _a = edx & 0x00000080;
        _a = _a >> 7;

        char _DL_H4_0_3[3] = {0};
        sprintf(_DL_H4_0_3, "%X", _d*8+_c*4+_b*2+_a);
        char* hex_string = (char*)malloc(2+1);
        strcpy(hex_string, _DL_H4_0_3);
        edx = (_ui)strtoul(hex_string, NULL, 16);
        free(hex_string);
        edx = edx << 4;
        esi = edx;
        FUCK("[*] esi: 0x%x\n", esi);
        /*
        我们分别用ABCD代表一个32位数中每个字节的最高位
        |-----------------| |--------DX-------|
        |                 | |---DH--| |--DL---|
        D000 0000 C000 0000 B000 0000 A000 0000         DH  >> 7
        D000 0000 C000 0000 0000 000B A000 0000         DX  << 7
        D000 0000 C000 0000 BA00 0000 0000 0000         EDX >> 8
        0000 0000 D000 0000 C000 0000 BA00 0000         DH  >> 7
        0000 0000 D000 0000 0000 000C BA00 0000         DX  << 7
        0000 0000 D000 0000 CBA0 0000 0000 0000         EDX >> 8
        0000 0000 0000 0000 D000 0000 CBA0 0000         DH  >> 7
        0000 0000 0000 0000 0000 000D CBA0 0000         DX  >> 1
        0000 0000 0000 0000 0000 0000 DCBA 0000
        */
        edx = edi;
        // 下面这一堆移位操作其实是把每个字节中的最高位移动到了DL的低四位中

        /*
        我们分别用ABCD代表一个32位数中每个字节的最高位
        |-----------------| |--------DX-------|
        |                 | |---DH--| |--DL---|
        D000 0000 C000 0000 B000 0000 A000 0000         DH  >> 7
        D000 0000 C000 0000 0000 000B A000 0000         DX  << 7
        D000 0000 C000 0000 BA00 0000 0000 0000         EDX >> 8
        0000 0000 D000 0000 C000 0000 BA00 0000         DH  >> 7
        0000 0000 D000 0000 0000 000C BA00 0000         DX  << 7
        0000 0000 D000 0000 CBA0 0000 0000 0000         EDX >> 8
        0000 0000 0000 0000 D000 0000 CBA0 0000         DH  >> 7
        0000 0000 0000 0000 0000 000D CBA0 0000         DX  >> 5
        0000 0000 0000 0000 0000 0000 0000 DCBA
        */
        _d = edx & 0x80000000;
        _d = _d >> 31;
        _c = edx & 0x00800000;
        _c = _c >> 23;
        _b = edx & 0x00008000;
        _b = _b >> 15;
        _a = edx & 0x00000080;
        _a = _a >> 7;

        char _DL_L4_0_3[3] = {0};
        sprintf(_DL_L4_0_3, "%02X", _d*8+_c*4+_b*2+_a);
        hex_string = (char*)malloc(2+1);
        strcpy(hex_string, _DL_L4_0_3);
        edx = (_ui)strtoul(hex_string, NULL, 16);
        free(hex_string);
        edi = edx;
        FUCK("[*] edi: 0x%x\n", edi);
        edi = edi ^ esi;
        edx = edi;
        edx = edx & 0xFF;
        _ui _tmp_val = ecx;
        ecx = eax;
        eax = _tmp_val;
        FUCK("[*] edx: 0x%x\n", edx);
        if (eax > 3) {
            eax = eax - 3;
            eax = eax * 8;
            _tmp_val = eax;
            eax = ecx;
            ecx = _tmp_val;
            ebx = _rotl(ebx, ecx);
            ebx = ebx ^ edx;
            ebx = _rotr(ebx, ecx);
        } else {
            eax = eax * 8 + 8;
            _tmp_val = eax;
            eax = ecx;
            ecx = _tmp_val;
            eax = _rotl(eax, ecx);
            eax = eax ^ edx;
            eax = _rotr(eax, ecx);
        }
        FUCK("[*] eax: 0x%x\n",eax);
        FUCK("[*] ebx: 0x%x\n", ebx);
        loop_counter += 1;
    }
    printf("[*] after loop, eax: 0x%X\n", eax);
    printf("[*] after loop, ebx: 0x%X\n", ebx);


    // 下面是函数cycle.401190
    ecx = 0xFF01;
    // 这里需要对一个内存(0x402182)的值进行一个加法运算
    // 我暂时还不清楚这个内存的值是否固定,因此先留一个TODO,经调试,这里的初始值应该是98BADCFE
    // 转换成整型数就是FEDCBA98
    // 该函数并不是简单地循环,是一个递归函数,因此需要单独定义
    ecx = _401190(eax, ebx, ecx);
    FUCK("[*] ecx: 0x%X\n", ecx);
    return ecx;
}


int main_call() {
    _TODO_counter = 0xFEDCBA98;
    char user_name[16+1] = {0};
    printf("[*] give me a max-16-len string, 'q' to exit:\n\t");
    scanf("%16s", user_name);
    FUCK("%s\n",  user_name);
    if(1==strlen(user_name) && 'q'==user_name[0]) return 'q';
    // 将用户名扩充至16位
    int user_name_length = strlen(user_name);
    printf("[*] user name length: %d\n", user_name_length);
    char* extend_user_name = (char*)malloc(16+1);
    strcpy(extend_user_name, user_name);
    int i = 0;
    for(; i<16-user_name_length; i++) {
        char _tmp_val[2] = {0};
        sprintf(_tmp_val, "%c", user_name[i%user_name_length]);
        strcat(extend_user_name, _tmp_val);
    }
    printf("[*] user name after extended: %s\n", extend_user_name);
    // 对于掩码0x7F3F1F0F
    // 0x7F和username[3]进行异或运算
    // 如果username[3]的1和3bit置位,那么serial[3]的1和3bit一定会复位
    // 则serial[3]一定会小于0x30,这样就无法通过字符范围的检测了[0x30, 0x7E]
//        if(180 <= extend_user_name[3]) {
//            printf("[-] after xor with mask(0x7F), the value would less than 0x30, \n\tusername[3]'s ascii value should not be greater than 0x50\n");
//            continue;
//        }
    // 首先获取到注册码的前4位
    // __n_0_3 ^ __s_0_3 == 0x7F3F1F0F
    _ui _n_0_3 = string_to_hex(extend_user_name, 0);
    FUCK("0x%X\n", _n_0_3);
    char _s_0_3[4+1] = {0};
    _ui _hex_xor_res = 0;
    i=0;
    int j = 0;
    int k = 0;
    int l = 0;
    for(; i<8; i++) {
        j=0;
        for(; j<4; j++) {
            k=0;
            for(; k<2; k++) {
                l=0;
                for(; l<2; l++) {
                    memset(_s_0_3, 0, sizeof(_s_0_3));
                    char _tmp_char[2+1] = {0};
                    char hex_string[4+1] = {0};
                    sprintf(_tmp_char, "%02X", (4*l+3)*16+0x0F);
                    strcpy(hex_string, _tmp_char);
                    sprintf(_tmp_char, "%02X", (4*k+3)*16+0x0F);
                    strcat(hex_string, _tmp_char);
                    sprintf(_tmp_char, "%02X", (2*j+1)*16+0x0F);
                    strcat(hex_string, _tmp_char);
                    sprintf(_tmp_char, "%02X", i*16+0x0F);
                    strcat(hex_string, _tmp_char);

                    FUCK("hex_string = %s\n", hex_string);
                    // _hex_xor_res = 0x0F0F0F0F;
                    _hex_xor_res = (_ui)strtoul(hex_string, NULL, 16);
                    FUCK("hex_xor_res = 0x%X\n", _hex_xor_res);
                    FUCK("_n_0_3----%x\n", _n_0_3 ^ _hex_xor_res);
                    if(hex_to_string(_n_0_3 ^ _hex_xor_res, _s_0_3)==0) {
                        FUCK("_s_0_3----%s\n", _s_0_3);
                        goto __GOTO_ONCE;
                    }
                }
            }
        }
    }

    __GOTO_ONCE:
    FUCK("%s\n", _s_0_3);

    // 为了通过注册码长度检测,这里先给序列号填充为16位,其实后八位暂时是用不着的
    char* _s_8_15 = "89abcdef";
    // 由于掩码0x07030100最后会和EBX进行与运算
    // 所以最后EBX的四个字节,从高到低依次拥有3、2、1、0个有效bit位
    // 也就是说,无论_n_4_7和_s_4_7是什么样的,EBX只有2^(3+2+1+0)=64种可能的值
    // 下面这个组合可以覆盖到所有的可能性
    // 这样一来,我们就可以获取到注册码的4~7位了
    char* _s_4 = "@ABCDEFG";
    char* _s_5 = "@ABC";
    char* _s_6 = "@A";
    char* _s_7 = "A";
    i=0;
    for(; i<8; i++) {
        j=0;
        for(; j<4;j++) {
            k=0;
            for(; k<2; k++) {
                char* hex_string = (char*)malloc(16+1);
                strcpy(hex_string, _s_0_3);
                char _s_4_7[4+1] = {0};
                char _tmp_val[2] = {0};
                sprintf(_tmp_val, "%c", _s_4[i]);
                strcpy(_s_4_7, _tmp_val);
                sprintf(_tmp_val, "%c", _s_5[j]);
                strcat(_s_4_7, _tmp_val);
                sprintf(_tmp_val, "%c", _s_6[k]);
                strcat(_s_4_7, _tmp_val);
                sprintf(_tmp_val, "%c", _s_7[0]);
                strcat(_s_4_7, _tmp_val);
                strcat(hex_string, _s_4_7);
                strcat(hex_string, _s_8_15);
                if(register_func(extend_user_name, hex_string) == 1) {
                    free(hex_string);
                    printf("[+] half success!!! come on!!!\n\n");
                    // 实现剩下的运算
                    char _24_31[3]={0};
                    char _16_23[3]={0};
                    char _8_15[3]={0};
                    char _0_7[3]={0};

                    // 获取用户名的8~11并转换成整型数
                    _ui eax = string_to_hex(extend_user_name, 8);
                    FUCK("[*] eax: %x\n", eax);

                    // 获取用户名的12~15并转换成整型数
                    _ui ebx = string_to_hex(extend_user_name, 12);
                    FUCK("[*] ebx: %x\n", ebx);

                    eax = eax ^ ebx;
                    eax = eax ^ _TODO_counter;
                    eax = eax | 0x40404040;
                    eax = eax & 0x77777777;
                    while(1) {
                        char _eax_bin_format[32+1] = {0};
                        itoa(eax, _eax_bin_format, 2);
                        char __eax_bin_format[32+1] = {0};
                        sprintf(__eax_bin_format, "%032s", _eax_bin_format);
                        int index=0;
                        CUM("[+] this is the binary format of serial_8_11 ^ serial_12_15 result:\n\t");
                        for(; index<32; index++) {
                            FUCK("%c", __eax_bin_format[index]);
                            if((index+1)%4 == 0 && (index+1)!=32) {
                                CUM(" ");
                            }
                            if((index+1)%8 == 0 && (index+1)!=32) {
                                CUM("%% ");
                            }
                        }
                        CUM("\n");

                        char _s_8_11[4+1] = {0};
                        sprintf(_tmp_val, "%c", generate_random(0x30, 0x7E));
                        FUCK("random char %s", _tmp_val);
                        strcpy(_s_8_11, _tmp_val);
                        sprintf(_tmp_val, "%c", generate_random(0x30, 0x7E));
                        FUCK("random char %s", _tmp_val);
                        strcat(_s_8_11, _tmp_val);
                        sprintf(_tmp_val, "%c", generate_random(0x30, 0x7E));
                        FUCK("random char %s", _tmp_val);
                        strcat(_s_8_11, _tmp_val);
                        sprintf(_tmp_val, "%c", generate_random(0x30, 0x7E));
                        FUCK("random char %s", _tmp_val);
                        strcat(_s_8_11, _tmp_val);
                        printf("[*] generating random 4-len string: %s\n", _s_8_11);
//                        char _DEBUG_s_8_11[4+1] = "}3d7";
//                        strcpy(_s_8_11, _DEBUG_s_8_11);
                        _ui _s_8_11_hex_value = string_to_hex(_s_8_11, 0);
                        // 根据汇编代码
                        // eax = eax ^ s_8_11
                        // eax = eax ^ s_12_15
                        // eax == 0必须成立
                        // 那么可以推导出 eax ^ s_8_11 == s_12_15
                        FUCK("_s_8_11_hex_value: 0x%X\n", _s_8_11_hex_value);
                        FUCK("eax: 0x%X\n", eax);
                        _ui _s_12_15_hex_value = eax ^ _s_8_11_hex_value;
                        FUCK("_s_12_15_hex_value: 0x%X\n", _s_12_15_hex_value);
                        hex_string = (char*)malloc(4+1);
                        if(1==hex_to_string(_s_12_15_hex_value, hex_string)) {
                            free(hex_string);
                            continue;
                        }

                        char serial_number[16+1] = {0};
                        strcpy(serial_number, _s_0_3);
                        strcat(serial_number, _s_4_7);
                        strcat(serial_number, _s_8_11);
                        strcat(serial_number, hex_string);
                        free(hex_string);

                        printf("[+] Name:\n\t%s\n", user_name);
                        printf("[+] Serial:\n\t%s\n", serial_number);
                        char c = 0;
                        while ((c = getchar()) != EOF && c != '\n');
                        return 0;
                    }
                }
                free(hex_string);
            }
        }
    }
    return 0;
}

int main(int argc, char** argv) {
    while(1)
        if('q' == main_call()) break;
    return 0;
}