2017HCTF第一题WP

来源:互联网 发布:python隐马尔可夫模型 编辑:程序博客网 时间:2024/06/08 12:35

0x0 初探
先用PEID查看一下CM的信息
这里写图片描述
这里写图片描述
从图中可以发现,程序使用Debug的编译方式编译且有一个TLS回调函数。
因为程序有ASLR,不方便分析,因此我用FFI去掉后再继续分析。
这里写图片描述
1x0 分析TLS回调函数
OD载入,断在TLS入口处。
这里写图片描述
一共有四个函数,其中前两个我们需要关注一下。因为这两个函数是反调试的函数。
1x1 分析函数 0041141A
CM先创建一个进程快照
这里写图片描述
然后就开始枚举进程,取出进程名,如果进程名字符合如下进程名中的其中一个,就直接报错退出。

ollyice.exe
ollydbg.exe
peid.exe
ida.exe
idaq.exe

对应的检测代码如下:

004129D1    8D85 CCFDFFFF   lea     eax, dword ptr [ebp-0x234]004129D7    50              push    eax004129D8    8B8D C0FDFFFF   mov     ecx, dword ptr [ebp-0x240]004129DE    51              push    ecx004129DF    E8 7EE8FFFF     call    <kernel32!Process32FirstW>004129E4    8985 B4FDFFFF   mov     dword ptr [ebp-0x24C], eax004129EA    83BD B4FDFFFF 0>/cmp     dword ptr [ebp-0x24C], 0x0004129F1    0F84 94010000   |je      00412B8B004129F7    8BF4            |mov     esi, esp004129F9    8D85 F0FDFFFF   |lea     eax, dword ptr [ebp-0x210]004129FF    50              |push    eax00412A00    FF15 A0C14100   |call    dword ptr [<&ucrtbased.wcslen>]                 ; ucrtbase.wcslen00412A06    83C4 04         |add     esp, 0x400412A09    3BF4            |cmp     esi, esp00412A0B    E8 6CE7FFFF     |call    <CheckEsp>00412A10    83C0 01         |add     eax, 0x100412A13    8BF4            |mov     esi, esp00412A15    50              |push    eax00412A16    8D8D F0FDFFFF   |lea     ecx, dword ptr [ebp-0x210]00412A1C    51              |push    ecx00412A1D    FF15 9CC14100   |call    dword ptr [<&ucrtbased._wcslwr_s>]              ; ucrtbase._wcslwr_s00412A23    83C4 08         |add     esp, 0x800412A26    3BF4            |cmp     esi, esp00412A28    E8 4FE7FFFF     |call    <CheckEsp>00412A2D    8BF4            |mov     esi, esp00412A2F    68 848B4100     |push    00418B84                                        ; UNICODE "ollyice.exe"00412A34    8D85 F0FDFFFF   |lea     eax, dword ptr [ebp-0x210]00412A3A    50              |push    eax00412A3B    FF15 A4C14100   |call    dword ptr [<&ucrtbased.wcscmp>]                 ; ucrtbase.wcscmp00412A41    83C4 08         |add     esp, 0x800412A44    3BF4            |cmp     esi, esp00412A46    E8 31E7FFFF     |call    <CheckEsp>00412A4B    85C0            |test    eax, eax00412A4D    75 1E           |jnz     short 00412A6D00412A4F    68 A08B4100     |push    00418BA0                                        ; ///////WARNING///////\n00412A54    E8 9EE9FFFF     |call    <printf>00412A59    83C4 04         |add     esp, 0x400412A5C    8BF4            |mov     esi, esp00412A5E    6A 00           |push    0x000412A60    FF15 94C14100   |call    dword ptr [<&ucrtbased.exit>]                   ; ucrtbase.exit00412A66    3BF4            |cmp     esi, esp00412A68    E8 0FE7FFFF     |call    <CheckEsp>00412A6D    8BF4            |mov     esi, esp00412A6F    68 BC8B4100     |push    00418BBC                                        ; UNICODE "ollydbg.exe"00412A74    8D85 F0FDFFFF   |lea     eax, dword ptr [ebp-0x210]00412A7A    50              |push    eax00412A7B    FF15 A4C14100   |call    dword ptr [<&ucrtbased.wcscmp>]                 ; ucrtbase.wcscmp00412A81    83C4 08         |add     esp, 0x800412A84    3BF4            |cmp     esi, esp00412A86    E8 F1E6FFFF     |call    <CheckEsp>00412A8B    85C0            |test    eax, eax00412A8D    75 1E           |jnz     short 00412AAD00412A8F    68 D88B4100     |push    00418BD8                                        ; ///////\nWARNING\n///////\n00412A94    E8 5EE9FFFF     |call    <printf>00412A99    83C4 04         |add     esp, 0x400412A9C    8BF4            |mov     esi, esp00412A9E    6A 00           |push    0x000412AA0    FF15 94C14100   |call    dword ptr [<&ucrtbased.exit>]                   ; ucrtbase.exit00412AA6    3BF4            |cmp     esi, esp00412AA8    E8 CFE6FFFF     |call    <CheckEsp>00412AAD    8BF4            |mov     esi, esp00412AAF    68 F48B4100     |push    00418BF4                                        ; UNICODE "peid.exe"00412AB4    8D85 F0FDFFFF   |lea     eax, dword ptr [ebp-0x210]00412ABA    50              |push    eax00412ABB    FF15 A4C14100   |call    dword ptr [<&ucrtbased.wcscmp>]                 ; ucrtbase.wcscmp00412AC1    83C4 08         |add     esp, 0x800412AC4    3BF4            |cmp     esi, esp00412AC6    E8 B1E6FFFF     |call    <CheckEsp>00412ACB    85C0            |test    eax, eax00412ACD    75 1E           |jnz     short 00412AED00412ACF    68 D88B4100     |push    00418BD8                                        ; ///////\nWARNING\n///////\n00412AD4    E8 1EE9FFFF     |call    <printf>00412AD9    83C4 04         |add     esp, 0x400412ADC    8BF4            |mov     esi, esp00412ADE    6A 00           |push    0x000412AE0    FF15 94C14100   |call    dword ptr [<&ucrtbased.exit>]                   ; ucrtbase.exit00412AE6    3BF4            |cmp     esi, esp00412AE8    E8 8FE6FFFF     |call    <CheckEsp>00412AED    8BF4            |mov     esi, esp00412AEF    68 108C4100     |push    00418C10                                        ; UNICODE "ida.exe"00412AF4    8D85 F0FDFFFF   |lea     eax, dword ptr [ebp-0x210]00412AFA    50              |push    eax00412AFB    FF15 A4C14100   |call    dword ptr [<&ucrtbased.wcscmp>]                 ; ucrtbase.wcscmp00412B01    83C4 08         |add     esp, 0x800412B04    3BF4            |cmp     esi, esp00412B06    E8 71E6FFFF     |call    <CheckEsp>00412B0B    85C0            |test    eax, eax00412B0D    75 1E           |jnz     short 00412B2D00412B0F    68 D88B4100     |push    00418BD8                                        ; ///////\nWARNING\n///////\n00412B14    E8 DEE8FFFF     |call    <printf>00412B19    83C4 04         |add     esp, 0x400412B1C    8BF4            |mov     esi, esp00412B1E    6A 00           |push    0x000412B20    FF15 94C14100   |call    dword ptr [<&ucrtbased.exit>]                   ; ucrtbase.exit00412B26    3BF4            |cmp     esi, esp00412B28    E8 4FE6FFFF     |call    <CheckEsp>00412B2D    8BF4            |mov     esi, esp00412B2F    68 208C4100     |push    00418C20                                        ; UNICODE "idaq.exe"00412B34    8D85 F0FDFFFF   |lea     eax, dword ptr [ebp-0x210]00412B3A    50              |push    eax00412B3B    FF15 A4C14100   |call    dword ptr [<&ucrtbased.wcscmp>]                 ; ucrtbase.wcscmp00412B41    83C4 08         |add     esp, 0x800412B44    3BF4            |cmp     esi, esp00412B46    E8 31E6FFFF     |call    <CheckEsp>00412B4B    85C0            |test    eax, eax00412B4D    75 1E           |jnz     short 00412B6D00412B4F    68 D88B4100     |push    00418BD8                                        ; ///////\nWARNING\n///////\n00412B54    E8 9EE8FFFF     |call    <printf>00412B59    83C4 04         |add     esp, 0x400412B5C    8BF4            |mov     esi, esp00412B5E    6A 00           |push    0x000412B60    FF15 94C14100   |call    dword ptr [<&ucrtbased.exit>]                   ; ucrtbase.exit00412B66    3BF4            |cmp     esi, esp00412B68    E8 0FE6FFFF     |call    <CheckEsp>00412B6D    8D85 CCFDFFFF   |lea     eax, dword ptr [ebp-0x234]00412B73    50              |push    eax00412B74    8B8D C0FDFFFF   |mov     ecx, dword ptr [ebp-0x240]00412B7A    51              |push    ecx00412B7B    E8 A1E6FFFF     |call    0041122100412B80    8985 B4FDFFFF   |mov     dword ptr [ebp-0x24C], eax00412B86  ^ E9 5FFEFFFF     \jmp     004129EA

1x2 分析函数 00411186
这段函数其实就是IsDebuggerPresent。检测了PEB的BeingDebugged,若BeingDebugged为0则未被调试,否则被调试。
函数代码如下:
这里写图片描述
1x3 一次性全部拆解
这两个检测函数都位于TLS回调函数内,因此删掉即可。
这里写图片描述
这样系统就不会去执行TLS回调函数了,而是直接执行入口点处的代码了。
2x0 分析第一个输入
OD来到main函数00412E20处,单步调试一阵发现第一个算法函数 00411316
因为这个函数的代码很长,所以使用IDA来分析。
IDA来到00411316处,使用F5插件查看反编译好后的C代码。
首先将注册码的顺序颠倒(123456789 -> 987654321)
这里写图片描述
然后将循环次数作为密匙,加密颠倒后的Key
这里写图片描述
最后和一串密文比较,若全部相等则通过第一个Key的验证。
这里写图片描述
v8处的数据如下:
这里写图片描述
计算注册码的办法很简单,写一个逆运算即可。
代码如下:
这里写图片描述
运行以后得到结果:M.KATSURAGI
3x0 分析第二个输入
第二个输入就有点复杂了,其中还夹杂着反调试(ntdll!NtQueryInformationProcess查询DebugPort)。x86用StrongOD插件,x64用xjun师傅的SharpOD插件都可以绕过(我是Windows7 x64)
首先程序判断输入的Key是否是0x23个字节,如果不是则失败。
这里写图片描述
构造一个key,使大小为0x23即可通过。我构造的是0123456789ABCDEFGHIJKLMNOPQRSTUVWXY
继续分析,来到第一个计算的函数(一共有四个),我先将这个计算的函数和将要出现的计算的函数分别标记,到最后一起分析。
第一个计算的函数我命名为First。
这里写图片描述
加密以后,CM做了一些操作以后(包括调试器的检测)来到第二个计算的函数,我命名为Second。
这里写图片描述
然后执行第三个计算的函数,我命名为Third
这里写图片描述
然后执行第四个计算的函数,我命名为Fourth
这里写图片描述
然后将First计算的数据的第八个字节开始用Second计算的七个字节替换,第十五个字节开始用Third计算的七个字节替换,第二十二个字节开始用Fourth的计算的七个字节替换。
这里写图片描述
最后和内置好的一串数据比较,如果相等就成功。
这里写图片描述
其中,内置好的正确数据如下:
这里写图片描述
3x1 四个计算函数之First的分析
First函数比较简单,将Key的每个字节和0x76异或。
这里写图片描述
3x2 四个计算函数之Second的分析
代码如下:
这里写图片描述
3x3 四个计算函数之Third的分析
这个和Second进行的运算差不多。代码如下:
这里写图片描述
3x4 四个计算函数之Fourth的分析
代码如下:
这里写图片描述
3x5 计算第二个注册码
这个注册码一共经历了四次变换(First Second Third Fourth)
其中,First运算可逆,Second Third Fourth都不是可逆的函数。
观察一下计算后的数据,容易发现注册码被分成了如下的五个部分:
这里写图片描述
其中有高亮的(第二个到第四个)都是被替换后的,而没有高亮的(第一个和最后一个)都是不被替换的,因此没有高亮的部分可以直接用First运算来还原。
而有高亮的部分我没有找到好的办法去还原,只有枚举了。
总体的计算思路如下:
1.先不考虑没有高亮的部分(第一个和最后一个),枚举第二个,用Second运算来枚举,尝试一个字节一个字节枚举,考虑到注册码都是可显示字符,scanf又有遇到空格截断的特性,因此从0x21开始枚举。若Second返回的的数据是正确的,则开始第二个字节的枚举。
2.枚举第三个,用Third运算来枚举,尝试一个字节一个字节枚举,若Third返回的的数据是正确的,则开始第二个字节的枚举。
3.枚举第四个,用Fourth运算来枚举,尝试一个字节一个字节枚举,若Fourth返回的的数据是正确的,则开始第二个字节的枚举。
4.这步中的数据应该是正确的Key经过First运算后的数据了,因为First运算是xor运算,故将这部分的数据再用First运算一次即可得到Key。
计算代码如下:

#include <stdio.h>#include <stdlib.h>void First(unsigned char *Output,unsigned char *Input,int size)//Key的第一次变换{    for(int i = 0;i < size;i++)        Output[i] = Input[i] ^ 0x76;}void Second(unsigned char *Output,unsigned char *Input,int size)//Key的第二次变换{    if(size > 7)        return;    for(int i = 0;i < size;i++)    {        int tmp;        int tmp1;        Output[i] = Input[i] ^ 0xAD;        tmp = (Output[i] & 0xAA) >> 1;        tmp1 = 2 * Output[i] & 0xAA;        Output[i] = tmp1 | tmp;    }}void Third(unsigned char *Output,unsigned char *Input,int size)//Key的第三次变换{    if(size > 7)        return;    for(int i = 0;i < size;i++)    {        int tmp;        int tmp1;        Output[i] = Input[i] ^ 0xBE;        tmp = (Output[i] & 0xCC) >> 2;        tmp1 = 4 * Output[i] & 0xCC;        Output[i] = tmp1 | tmp;    }}void Fourth(unsigned char *Output,unsigned char *Input,int size)//Key的第四次变换{    if(size > 7)        return;    for(int i = 0;i < size;i++)    {        int tmp;        int tmp1;        Output[i] = Input[i] ^ 0xEF;        tmp = (Output[i] & 0xF0) >> 4;        tmp1 = 16 * Output[i] & 0xF0;        Output[i] = tmp1 | tmp;    }}int main(void){    int j;    unsigned char ch;    unsigned char Data1[] = //第二个key    {        0x1E,0x15,0x02,0x10,0x0D,0x48,0x48,        0x6F,0xDD,0xDD,0x48,0x64,0x63,0xD7,        0x2E,0x2C,0xFE,0x6A,0x6D,0x2A,0xF2,        0x6F,0x9A,0x4D,0x8B,0x4B,0x9A,0xAA,        0x41,0x42,0x42,0x42,0x13,0x14,0x0B    };    /*         计算第二个Key         这部分的Key一共有35个字节,若每七个字节是一块则一共有五块.    */    j = 0;//第二块    ch = 0x21;    while(true)    {        unsigned char a;        if(j == 7)            break;        Second(&a,&ch,1);//第二次变换        if(a != Data1[j + 7])        {            ch++;            continue;        }        Data1[j + 7] = ch;        ch = 0x21;        j++;    }    j = 0;//第三块    ch = 0x21;    while(true)    {        unsigned char a;        if(j == 7)            break;        Third(&a,&ch,1);//第三次变换        if(a != Data1[j + 14])        {            ch++;            continue;        }        Data1[j + 14] = ch;        ch = 0x21;        j++;    }    j = 0;//第四块    ch = 0x21;    while(true)    {        unsigned char a;        if(j == 7)            break;        Fourth(&a,&ch,1);//第四次变换        if(a != Data1[j + 21])        {            ch++;            continue;        }        Data1[j + 21] = ch;        ch = 0x21;        j++;    }    First(Data1,Data1,sizeof(Data1));//第一次变换    /* Output:hctf{>>D55_CH0CK3R_B0o0M!-037444eb} */    printf("%0.35s\n",Data1);    system("pause");    return 0;}

运行以后得到结果:hctf{>>D55_CH0CK3R_B0o0M!-037444eb}
4x0 最后一个输入
这个就很简单了,问你输入Y或者N,若输入Y则成功(不区分大小写),否则失败。
这里写图片描述
4x1 完整的计算代码
将上述代码写到一起,然后再加上输出最后一个注册码的代码即可。
代码如下:

#include <stdio.h>#include <stdlib.h>void First(unsigned char *Output,unsigned char *Input,int size)//Key的第一次变换{    for(int i = 0;i < size;i++)        Output[i] = Input[i] ^ 0x76;}void Second(unsigned char *Output,unsigned char *Input,int size)//Key的第二次变换{    if(size > 7)        return;    for(int i = 0;i < size;i++)    {        int tmp;        int tmp1;        Output[i] = Input[i] ^ 0xAD;        tmp = (Output[i] & 0xAA) >> 1;        tmp1 = 2 * Output[i] & 0xAA;        Output[i] = tmp1 | tmp;    }}void Third(unsigned char *Output,unsigned char *Input,int size)//Key的第三次变换{    if(size > 7)        return;    for(int i = 0;i < size;i++)    {        int tmp;        int tmp1;        Output[i] = Input[i] ^ 0xBE;        tmp = (Output[i] & 0xCC) >> 2;        tmp1 = 4 * Output[i] & 0xCC;        Output[i] = tmp1 | tmp;    }}void Fourth(unsigned char *Output,unsigned char *Input,int size)//Key的第四次变换{    if(size > 7)        return;    for(int i = 0;i < size;i++)    {        int tmp;        int tmp1;        Output[i] = Input[i] ^ 0xEF;        tmp = (Output[i] & 0xF0) >> 4;        tmp1 = 16 * Output[i] & 0xF0;        Output[i] = tmp1 | tmp;    }}int main(void){    int j;    unsigned char ch;    unsigned char Data[] = {0xA4,0xA9,0xAA,0xBE,0xBC,0xB9,0xB3,0xA9,0xBE,0xD8,0xBE};//第一个key    unsigned char Data1[] = //第二个key    {        0x1E,0x15,0x02,0x10,0x0D,0x48,0x48,        0x6F,0xDD,0xDD,0x48,0x64,0x63,0xD7,        0x2E,0x2C,0xFE,0x6A,0x6D,0x2A,0xF2,        0x6F,0x9A,0x4D,0x8B,0x4B,0x9A,0xAA,        0x41,0x42,0x42,0x42,0x13,0x14,0x0B    };    /* 计算第一个key */    for(int i = 0;i < sizeof(Data);i++)        Data[i] = (((((i ^ 0x76) - 52) ^ 0x80) + 43) ^ Data[i]);    for(int i = 0;i < sizeof(Data) / 2;i++)    {        Data[i] ^= Data[sizeof(Data) - i - 1];        Data[sizeof(Data) - i - 1] ^= Data[i];        Data[i] ^= Data[sizeof(Data) - i - 1];    }    /* Output:M.KATSURAGI */    printf("%0.11s\n",Data);    /*         计算第二个Key         这部分的Key一共有35个字节,若每七个字节是一块则一共有五块.    */    j = 0;//第二块    ch = 0x21;    while(true)    {        unsigned char a;        if(j == 7)            break;        Second(&a,&ch,1);//第二次变换        if(a != Data1[j + 7])        {            ch++;            continue;        }        Data1[j + 7] = ch;        ch = 0x21;        j++;    }    j = 0;//第三块    ch = 0x21;    while(true)    {        unsigned char a;        if(j == 7)            break;        Third(&a,&ch,1);//第三次变换        if(a != Data1[j + 14])        {            ch++;            continue;        }        Data1[j + 14] = ch;        ch = 0x21;        j++;    }    j = 0;//第四块    ch = 0x21;    while(true)    {        unsigned char a;        if(j == 7)            break;        Fourth(&a,&ch,1);//第四次变换        if(a != Data1[j + 21])        {            ch++;            continue;        }        Data1[j + 21] = ch;        ch = 0x21;        j++;    }    First(Data1,Data1,sizeof(Data1));//第一次变换    /* Output:hctf{>>D55_CH0CK3R_B0o0M!-037444eb} */    printf("%0.35s\n",Data1);    /*         计算第三个key        Output:y    */    puts("y");    system("pause");    return 0;}

运行以后得到完整的Key:
这里写图片描述
用原版CM注册,得到结果如下:
这里写图片描述
全文完。