2018/11/19-hctf-LuckyStar

题目链接:https://pan.baidu.com/s/1wv1T4an04YaHTE5uo1Zvmw
提取码:k228 

打开程序后会播放一段音乐,音乐结束后会让输入key,输入错误会显示“Maybe next year”。

首先程序使用了Tls回调函数进行反调试部分。

 

IDA反编译,发现不少反调试,若检测到调试则输出“LuckyStar!”,然退出程序。

 

有个二重do-while循环会检测进程名并和一个字符串数组比较,字符串数组保存的是六个常见的调试器的名称,若相等,则检测到调试,输出“LuckyStar”并退出程序。

 

对于这个反调试我们选择用winhex搜索这些字符串,然后修改掉即可。

程序在调用dword_407380这个函数时候会崩溃掉,我们用od把它给nop掉即可。

 

 

接着调用srand函数,0x61616161作为种子,然后以rand()%0x2018arr数组中寻值,然后和0x401780地址的值开始亦或,进行自解密。

 

Tls函数结束后,就会执行自解密的代码部分。

当我们nop掉反调试后,程序界面如图,

 

并会放一段音乐,当音乐结束后会让输入。

对于反编译自解密的部分,我们选择当程序正在执行自解密部分的时候,使用ODattach功能附加到程序,然后右击选择“使用OD脱壳调试进程”功能进行脱壳。

这里我们并不是要真的脱壳,而是因为此时程序自解密部分已经解密了,我们那此时的进程内存dump下来,然后使用IDA即可找到自解密部分并能够反编译。

现在来分析播放完音乐才能输入的功能,我们发现创建了一个新线程。

 

点进StartAddress看看

 

可以看到调用了PlaySoundA函数来播放音乐,当音乐放完会赋值dword_40737C1

然后只有当值为1时,才能跳出下面的while循环,然后执行输入和验证部分。

我们使用ODPlaySoundA函数给nop掉,然后就不必等待播放音乐了。

 

然后我们发现下面有第二段自解密,并且在获取输入后,输入会作为参数传进自解密部分。

 

然而当我们使用OD按下f9会发现程序崩溃,进行调试发现是自第二段函数的自解密的数据是错误的,然后不用OD调试则程序能正常运行,而第二段自解密的正确又只于rand()函数有关,自解密不对说明rand()是错的,即srand的种子被改变了。也就是说——程序在某个地方有着隐藏的反调试我们很难发现,如果检测到调试,srand()函数的种子就会被改变导致rand()生成值错误。

那么既然它改变了srand()的种子,我们就在srand()函数内部的第一行处设断点,当停下来时候找堆栈中的返回地址并查看调用srand()的部分,当看种子在什么时候被改变了。

 

这里我们发现有两个种子,我们进行调试一下,发现使用od时使用的种子是0x10001165,而另一个种子是0x68637466,换成ascii码就是“hctf”。我们直接修改指令使种子为0x68637466即可。

然后使用OD调试运行即可自解密正确,然后获取输入进行校验。

接着我们同样使用OD附加上程序已经自解密完第二段函数的状态,然后使用OD脱壳功能,再使用IDA反编译第二个自解密函数。

我们来分析第二段反编译的自解密函数。

首先前面的一堆伪代码可以根据一个byte4033C8数组中的编码表轻松看出来是base64加密,不过要注意的是编码表被改了。

 

也就是先对传入的输入进行了base64加密,接着又对加密后的输入依据rand()生成值进行了运算,思路比较简单,不再过多分析。

 

接着跳出第二段自解密的函数后就是将运算后得到的数据与内存中的数据进行比较了。

 

可以找到第二段数据为

 

这样的表示不太准确,我们用OD调试可以得到比较的数据为

{0x49, 0xE6, 0x57, 0xBD, 0x3A, 0x47, 0x11, 0x4C, 0x95, 0xBC, 0xEE, 0x32, 0x72, 0xA0, 0xF0 ,0xDE,  

0xAC, 0xF2, 0x83, 0x56, 0x83, 0x49, 0x6E, 0xA9, 0xA6, 0xC5, 0x67, 0x3C, 0xCA, 0xC8, 0xCC, 0x5}  

现在我们已经理清了程序流程,先对输入进行了编码表变化了的base64加密,然后再进行运算,最后再和目标数据进行对比,然后我们只需要写出脚本即可。

#include<stdio.h>
#include<time.h>
int main(void)
{
    int i,j;
    int arr[] = {0x49, 0xE6, 0x57, 0xBD, 0x3A, 0x47, 0x11, 0x4C, 0x95, 0xBC, 0xEE, 0x32, 0x72, 0xA0, 0xF0 ,0xDE,  
0xAC, 0xF2, 0x83, 0x56, 0x83, 0x49, 0x6E, 0xA9, 0xA6, 0xC5, 0x67, 0x3C, 0xCA, 0xC8, 0xCC, 0x5};

    srand(0x68637466);
    for(i = 0; i < 384; i++)
    {
        rand();
    }

    for(i = 0; i < arr[i]; i++)
    {
        for(j = 6; j > -2; j -=2)
        {
            arr[i] ^= rand()%4<<j;
        }
        printf("%c",arr[i]);
    }
    printf("\n");
    return 0;
}

可以得到base64加密后的字符串为“Agn0zNSXENvTAv9lmg5HDdrFtw8ZFq=”。

然后写脚本进行解密,注意编码表问题。

//密文:Agn0zNSXENvTAv9lmg5HDdrFtw8ZFq=
//编码表:bcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/

#include <stdio.h> 
const char base[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/"; 
char* base64_encode(const char* data, int data_len); 
char *base64_decode(const char* data, int data_len); 
static char find_pos(char ch); 
int main(int argc, char* argv[]) 
{
    char *enc = "Agn0zNSXENvTAv9lmg5HDdrFtw8ZFq=";
    int len = strlen(enc); 
    char *dec = base64_decode(enc, len); 
    printf("encoded : %s\n", enc); 
    printf("decoded : %s\n", dec); 

    return 0; 
} 

/* */ 
static char find_pos(char ch)   
{ 
    char *ptr = (char*)strrchr(base, ch);//the last position (the only) in base[] 
    return (ptr - base); 
} 
/* */ 

/* */ 
char *base64_decode(const char *data, int data_len) 
{ 
    int ret_len = (data_len / 4) * 3; 
    int equal_count = 0; 
    char *ret = NULL; 
    char *f = NULL; 
    int tmp = 0; 
    int temp = 0; 
    char need[3]; 
    int prepare = 0; 
    int i = 0; 
    if (*(data + data_len - 1) == '=') 
    { 
        equal_count += 1; 
    } 
    if (*(data + data_len - 2) == '=') 
    { 
        equal_count += 1; 
    } 
    if (*(data + data_len - 3) == '=') 
    {//seems impossible 
        equal_count += 1; 
    } 
    switch (equal_count) 
    { 
    case 0: 
        ret_len += 4;//3 + 1 [1 for NULL] 
        break; 
    case 1: 
        ret_len += 4;//Ceil((6*3)/8)+1 
        break; 
    case 2: 
        ret_len += 3;//Ceil((6*2)/8)+1 
        break; 
    case 3: 
        ret_len += 2;//Ceil((6*1)/8)+1 
        break; 
    } 
    ret = (char *)malloc(ret_len); 
    if (ret == NULL) 
    { 
        printf("No enough memory.\n"); 
        exit(0); 
    } 
    memset(ret, 0, ret_len); 
    f = ret; 
    while (tmp < (data_len - equal_count)) 
    { 
        temp = 0; 
        prepare = 0; 
        memset(need, 0, 4); 
        while (temp < 4) 
        { 
            if (tmp >= (data_len - equal_count)) 
            { 
                break; 
            } 
            prepare = (prepare << 6) | (find_pos(data[tmp])); 
            temp++; 
            tmp++; 
        } 
        prepare = prepare << ((4-temp) * 6); 
        for (i=0; i<3 ;i++ ) 
        { 
            if (i == temp) 
            { 
                break; 
            } 
            *f = (char)((prepare>>((2-i)*8)) & 0xFF); 
            f++; 
        } 
    } 
    *f = '\0'; 
    return ret; 
}

运行即可得到flag。

猜你喜欢

转载自www.cnblogs.com/Fingerprint/p/9986591.html