首页
社区
课程
招聘
[原创]看雪CTF2016 第27题分析-27-上帝溜猪
发表于: 2016-12-26 11:32 6122

[原创]看雪CTF2016 第27题分析-27-上帝溜猪

2016-12-26 11:32
6122

此题计算是比较简单的,主要是用输入的SN各字节对一个KEY各字节依次XOR,再用XOR后的KEY各字节依次乘以0x5e并对Code进行XOR,亮点在于解密后的Code未知,之前不能知道正确的代码是什么。不过此题也有一些问题,主要有4:
   
1.SN各字节对KEY的各字节都要做XOR运算,实际上仅相当于所有SN各位XOR得到一个C,再用C对KEY各字节XOR,也就是说11字节SN做XOR之后得到一个字节C,这样的对应关系是多对一,这样必然会多解。

2.(KEY[i] XOR C) * 0x5E各字节也都要同Code各字节XOR,这也有问题,同上,也只相当于(KEY[i] XOR C) * 0x5E所有字节(长度20)XOR后得到另一个C2再同Code各字节XOR运算,而这个对应关系也是多对一,这也导致必定多解。

3.对解密后的Code没有做有效的验证,也没有捕获异常,导致输入不正确的SN程序就会崩溃。

4.程序的验证代码在输入字符时进行,当输入SN长度不少于11时触发,不过有个问题就是验证正确的不一致性:当手动一个个输入字符时输到第11个就触发验证,不会再有11个字符以上的情况发生,而采用复制粘贴时一次可以粘贴多个字符,这将导致验证正确的不一致性,也就是说同一个注册码一个一个输入可能是错的,不过一次粘贴进去可能是对的(也可能反之)。比如注册码:00000000000N就在一次性粘贴进去是对的,一个个输是错的,相反的有注册码:00000000aQNN在一次性粘贴进去是错的,一个个的输入是正确的(其实只有11位有效,后面一位还没输入就验证了,是多出来的)。

OD中验证代码如下分析见注释:

00401570   .  6A FF                      push    -1
00401572   .  68 D81B4000                push    00401BD8                               ; Entry point
00401577   .  64:A1 00000000             mov     eax, fs:[0]
0040157D   .  50                         push    eax
0040157E   .  64:8925 00000000           mov     fs:[0], esp
00401585   .  83EC 70                    sub     esp, 70
00401588   .  55                         push    ebp
00401589   .  56                         push    esi
0040158A   .  57                         push    edi
0040158B   .  8BF1                       mov     esi, ecx
0040158D   .  6A 01                      push    1                                      ; /Arg1 = 1
0040158F   .  897424 18                  mov     ss:[esp+18], esi                       ; |
00401593   .  E8 5E030000                call    <jmp.&MFC42.#6334>                     ; \MFC42.#6334
00401598   .  8D4C24 0C                  lea     ecx, [esp+0C]
0040159C   .  E8 19030000                call    <jmp.&MFC42.#540>                      ; Jump to MFC42.#307
004015A1   .  8B46 60                    mov     eax, ds:[esi+60]
004015A4   .  8D56 60                    lea     edx, [esi+60]
004015A7   .  C78424 84000000 00000000   mov     dword ptr ss:[esp+84], 0
004015B2   .  8B68 F8                    mov     ebp, ds:[eax-8]
004015B5   .  83FD 0B                    cmp     ebp, 0B                                ; //SN长度不少于11,其实一个个字符输入就是11个,复制粘贴可以多于11个
004015B8   .  0F8C D8000000              jl      00401696
004015BE   .  B9 18000000                mov     ecx, 18
004015C3   .  33C0                       xor     eax, eax
004015C5   .  8D7C24 19                  lea     edi, [esp+19]
004015C9   .  C64424 18 00               mov     byte ptr ss:[esp+18], 0
004015CE   .  F3:AB                      rep stos dword ptr es:[edi]
004015D0   .  66:AB                      stos    word ptr es:[edi]
004015D2   .  6A 00                      push    0                                      ; /Arg1 = 0
004015D4   .  8BCA                       mov     ecx, edx                               ; |
004015D6   .  AA                         stos    byte ptr es:[edi]                      ; |
004015D7   .  E8 14030000                call    <jmp.&MFC42.#2915>                     ; \MFC42.#2915, //取SN
004015DC   .  8BCD                       mov     ecx, ebp
004015DE   .  8BF0                       mov     esi, eax
004015E0   .  8BD1                       mov     edx, ecx
004015E2   .  8D7C24 18                  lea     edi, [esp+18]
004015E6   .  C1E9 02                    shr     ecx, 2
004015E9   .  F3:A5                      rep movs dword ptr es:[edi], dword ptr ds:[esi ; //复制SN到BUFFER
004015EB   .  8BCA                       mov     ecx, edx
004015ED   .  33D2                       xor     edx, edx
004015EF   .  83E1 03                    and     ecx, 00000003
004015F2   .  85ED                       test    ebp, ebp
004015F4   .  F3:A4                      rep movs byte ptr es:[edi], byte ptr ds:[esi]
004015F6   .  7E 17                      jle     short 0040160F
004015F8   >  8A4C14 18                  mov     cl, ss:[edx+esp+18]
004015FC   .  33C0                       xor     eax, eax
004015FE   >  3088 20304000              xor     ds:[eax+403020], cl                    ; //对0x403020的KEY用SN依次XOR,其实仅相当于SN所以位XOR得到一个C,用C对KEY来XOR
00401604   .  40                         inc     eax                                    ; //因为所有SN各位XOR只得到一个C,这一点在程序的设计上有问题,必定多解
00401605   .  83F8 14                    cmp     eax, 14
00401608   .^ 7C F4                      jl      short 004015FE                         ; //KEY共20字节,依次XOR
0040160A   .  42                         inc     edx
0040160B   .  3BD5                       cmp     edx, ebp                               ; //SN各字节依次XOR
0040160D   .^ 7C E9                      jl      short 004015F8
0040160F   >  C74424 10 00000000         mov     dword ptr ss:[esp+10], 0
00401617   .  FF15 08204000              call    ds:[<&KERNEL32.GetCurrentProcessId>]   ; [KERNEL32.GetCurrentProcessId
0040161D   .  50                         push    eax                                    ; /ProcessID
0040161E   .  6A 00                      push    0                                      ; |InheritHandle = FALSE
00401620   .  68 FF0F1F00                push    1F0FFF                                 ; |Access = PROCESS_ALL_ACCESS
00401625   .  FF15 04204000              call    ds:[<&KERNEL32.OpenProcess>]           ; \KERNEL32.OpenProcess
0040162B   .  8BF8                       mov     edi, eax
0040162D   .  8D4424 10                  lea     eax, [esp+10]
00401631   .  50                         push    eax                                    ; /pBytesWritten
00401632   .  8D4C24 1C                  lea     ecx, [esp+1C]                          ; |
00401636   .  6A 2C                      push    2C                                     ; |Size = 44., //Code总长度为0x2c
00401638   .  51                         push    ecx                                    ; |Buffer
00401639   .  68 40154000                push    00401540                               ; |BaseAddress = CrackMe2.401540, Entry point
0040163E   .  57                         push    edi                                    ; |hProcess
0040163F   .  FF15 14204000              call    ds:[<&KERNEL32.ReadProcessMemory>]     ; \KERNEL32.ReadProcessMemory, //读加密的Code代码到BUFFER
00401645   .  85C0                       test    eax, eax
00401647   .  74 4D                      je      short 00401696
00401649   .  33F6                       xor     esi, esi
0040164B   >  8A86 20304000              mov     al, ds:[esi+403020]                    ; //Code[j] ^= (KEY[i] ^ C) * 0x5e
00401651   .  B2 5E                      mov     dl, 5E
00401653   .  33C9                       xor     ecx, ecx
00401655   .  F6EA                       imul    dl
00401657   >  8A540C 18                  mov     dl, ss:[ecx+esp+18]
0040165B   .  32D0                       xor     dl, al
0040165D   .  88540C 18                  mov     ss:[ecx+esp+18], dl
00401661   .  41                         inc     ecx
00401662   .  83F9 2C                    cmp     ecx, 2C                                ; //对所有Code(总长度为0x2c)依次XOR
00401665   .^ 7C F0                      jl      short 00401657
00401667   .  46                         inc     esi
00401668   .  83FE 14                    cmp     esi, 14                                ; //对所有KEY(总长度为20)依次XOR
0040166B   .^ 7C DE                      jl      short 0040164B
0040166D   .  8D4424 10                  lea     eax, [esp+10]
00401671   .  8D4C24 18                  lea     ecx, [esp+18]
00401675   .  50                         push    eax                                    ; /pBytesWritten
00401676   .  6A 2C                      push    2C                                     ; |Size = 44.
00401678   .  51                         push    ecx                                    ; |Buffer
00401679   .  68 40154000                push    00401540                               ; |BaseAddress = CrackMe2.401540, Entry point
0040167E   .  57                         push    edi                                    ; |hProcess
0040167F   .  FF15 00204000              call    ds:[<&KERNEL32.WriteProcessMemory>]    ; \KERNEL32.WriteProcessMemory, //回写解密后的Code
00401685   .  85C0                       test    eax, eax
00401687   .  74 0D                      je      short 00401696
00401689   .  8B5424 14                  mov     edx, ss:[esp+14]
0040168D   .  52                         push    edx
0040168E   .  E8 ADFEFFFF                call    00401540                               ; //调用解密后的Code
00401693   .  83C4 04                    add     esp, 4
00401696   >  8D4C24 0C                  lea     ecx, [esp+0C]
0040169A   .  C78424 84000000 FFFFFFFF   mov     dword ptr ss:[esp+84], -1
004016A5   .  E8 26010000                call    <jmp.&MFC42.#800>                      ; [MFC42.#800
004016AA   .  8B4C24 7C                  mov     ecx, ss:[esp+7C]
004016AE   .  5F                         pop     edi
004016AF   .  5E                         pop     esi
004016B0   .  5D                         pop     ebp
004016B1   .  64:890D 00000000           mov     fs:[0], ecx
004016B8   .  83C4 7C                    add     esp, 7C
004016BB   .  C3                         retn

加密的提示注册成功的函数:

00401540      AE                         scas    byte ptr es:[edi]
00401541      C5ACF0 F484C4AC            lds     ebp, ds:[esi*8+eax+ACC484F4]           ; Modification of segment register
00401548      F0:F4                      lock hlt                                       ; LOCK prefix is not allowed
0040154A      84C4                       test    ah, al
0040154C      AE                         scas    byte ptr es:[edi]
0040154D      C43B                       les     edi, ds:[ebx]                          ; Modification of segment register
0040154F      D130                       sal     dword ptr ds:[eax], 1                  ; Undocumented instruction or encoding
00401551      E5 84                      in      eax, 84                                ; I/O command
00401553      C44F 88                    les     ecx, ds:[edi-78]                       ; Modification of segment register
00401556      E0 C0                      loopnz  short 00401518
00401558      AC                         lods    byte ptr ds:[esi]
00401559      2C C7                      sub     al, 0C7
0040155B      C4C4                       les     eax, esp                               ; Illegal use of register
0040155D      2C 4C                      sub     al, 4C
0040155F      C7C4 C4AEC44F              mov     esp, 4FC4AEC4                          ; Suspicious use of stack pointer
00401565      0C 2C                      or      al, 2C
00401567      BD C7C4C407                mov     ebp, 7C4C4C7

由以上可以看出验证过程:

设输入注册码为SN

常量KEY在0x403020为:

BYTE KEY[20] = {0xCC,0xAA,0xBD,0xDD,0xCB,0xBA,0xB2,0x92,0xAF,0xBA,0xB4,0xB9,0xB0,0xAC,0xCB,0xBA,0xCE,0xD0,0xDF,0xDD};

常量Code在0x401540为:

BYTE Code[0x2c] = {0xAE,0xC5,0xAC,0xF0,0xF4,0x84,0xC4,0xAC,0xF0,0xF4,0x84,0xC4,0xAE,0xC4,0x3B,0xD1,0x30,0xE5,0x84,0xC4,0x4F,0x88,0xE0,0xC0,0xAC,0x2C,0xC7,0xC4,0xC4,0x2C,0x4C,0xC7,0xC4,0xC4,0xAE,0xC4,0x4F,0x0C,0x2C,0xBD,0xC7,0xC4,0xC4,0x07};

int lenSN = strlen(SN);
int i,j;
for(i = 0; i < lenSN; i++)
{
        for(j = 0; j < sizeof(KEY); j++){
                KEY[j] ^= SN[i];
        }
}
for(i = 0; i < sizeof(KEY); i++){
        for(j = 0; j < sizeof(Code); j++){
                Code[j] ^= KEY[i] * 0x5e;
        }
}
Code();

由上面的分析可以简化运算:
BYTE C = 0;
for(i = 0; i < lenSN; i++){
        C ^= SN[i];
}

BYTE C2 = 0;
for(i = 0; i < sizeof(KEY); i++){
        C2 ^= (KEY[i] ^ C) * 0x5e;
}

for(i = 0; i < sizeof(Code); i++){
        Code[i] ^= C2;
}
Code();

由上面简化后的分析,不难想到此题的解法:

1.首先要找到一个字节常量C2,用其对Code解密后的Code将是正常的代码,能正确运行,并能显示注册成功的信息,当然,代码的内容是什么我们之前是不可知的怎么显示成功信息我们也不用太关心,因为代码解密只需要一个字节的数据C2,一个字节也就只有255种情况(不会为0,为0时代码不会变),写段代码枚举一下就行,不过当然需要人肉(人眼观察用C2解密后的代码是否是比较正常的代码):

BYTE Code2[0x2c];
for(BYTE C2 = 1 ; C2; C2++){
        for(j = 0; j < sizeof(Code); j++){
                Code2[j] = Code[j] ^ C2;
        }
        printf("%2c",C2);        //在这儿看反汇编的Code解密后的结果Code2,要是看上去是乱七八糟的代码就继续。。。
}
运行以上程序,并观察结果,当C2为0xc4时,能得到看上去正常的代码(这是偶输入正确注册码时抓的代码,上面的Code2地址当然不是这个,不过代码看上去比较正常的就这一个,0xc4后面的偶就没试了):
00401540      6A 01                      push    1
00401542      68 34304000                push    offset 00403034                //'成功'
00401547      68 34304000                push    offset 00403034                //'成功'
0040154C      6A 00                      push    0
0040154E      FF15 F4214000              call    ds:[<&USER32.MessageBoxA>]
00401554      8B4C24 04                  mov     ecx, ss:[esp+4]
00401558      68 E8030000                push    3E8
0040155D      E8 88030000                call    <jmp.&MFC42.#3092>                     ; Jump to MFC42.#3092
00401562      6A 00                      push    0
00401564      8BC8                       mov     ecx, eax
00401566      E8 79030000                call    <jmp.&MFC42.#2642>                     ; Jump to MFC42.#2642
0040156B      C3                         retn

当然对于求上面的C2还有捷径可以走的:因为解密后的代码为一个函数,一般情况下函数最后应该是一条返回指令,在8086指令集中一般是这两条:
        1). 一字节指令:
                0xc3                 RETN
        2). 三字节指令:
                0xc2 xx xx        RETN xxxx
考察未解密的那段代码的最后三个字节0xc4, 0xc4, 0x07,对以上二种情况对比:
        1).如果是一字节RETN指令,最后一字节XOR一个常数字节C2要得到0xc3,这个C2就是:
                C2 = 0xc3 ^ 0x07 = 0xc4
        2).如果是三字节RETN指令,最后三个字节XOR一个常数字节C2要得到 0xc2 xx xx,这个C2就是:
                C2 = 0xc2 ^ 0xc4 = 0x06
                最后三字节都XOR C 将是:
                0xc2 0xc2 0x1        RETN 0x1c2        //看上去不太对哦,要弹出0x1c2个字节,能有这么多参数嘛?
这就得到了解密的常量字节C2=0xc4,当然函数最后也可能不是RETN指令,也可能RETN不在最后一个字节,这种方法就不一定正确了,正确与否只需要用它XOR所有的未解密代码,观察解密后的代码是否是乱码就知道了.
       

2.由之前的分析,要得到正确的C2=0xc4,就要去找前面那个C,这个也简单:

for(BYTE C = 1; C; C++){
        BYTE C2 = 0;
        for(j = 0; j < sizeof(KEY); j++){
                C2 ^= (KEY[j] ^ C) * 0x5e;
        }
        if(C2 == 0xc4){        //0xc4是上一步得到的
                printf("0x%x", C);
        }
}

这样可以得到可用的C不只一个:
0x1e
0x3e
0x5e
0x7e
0x9e
0xbe
0xde
0xfe

以上这些都行,也就是说对于SN(不少于11位)各位XOR后的值为以上几个的所有SN都应该是能提示注册成功的SN(当然,长过11的SN有复制粘贴与一个个输入的差别)。

3.计算SN,SN各位XOR之后的值在C的列表中{0x1e,0x3e,0x5e,0x7e,0x9e,0xbe,0xde,0xfe}就行,这个要是写个枚举过程可以找出无数个,不过这儿手工找一两个就行了:

因为C是SN各位XOR来的,所以每相同的二个SN字节XOR后为0,先来几个双数个相同的,后面的XOR凑到上面的数就行了:

我选二个
00000000000N        12位的,因为前10个相同XOR结果为0,而 '0' ^ 'N' = 0x30 ^ 0x4e = 0x7e 在上面的C的列表中
00000000aQN         11位的,因为前8个相同XOR结果为0,而'a' ^ 'Q' ^ 'N' = 0x61 ^ 0x51 ^ 0x4e = 0x7e 在上面的C的列表中

不过正如前面我说提到的程序验证正确的不一致性,00000000000N 这个注册码只能复制粘贴,不能一个一个的输入!


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

收藏
免费 1
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回