Bugku Reverse Bingo

注意
本文最后更新于 2024-02-12,文中内容可能已过时。

听说BugKu上线了,从昨天下午做到了今天下午,正好一天时间,最后是做了87题,实在是做不动了,冲分就暂时告一段落,前面的题目还是比较水的。这里讲解一下花了我大概一个小时才做出来的题目,bingoranklist.png 题目是一个解锁包,解压得到一张图片。 bingo.png 刚开始的时候没看分类,以为是MISC题,然后找了半天隐写内容,都没做找到,但是这个文件这么大,肯定是有问题。 然后又仔细找了一下,结果发现了这样一段内容, 图片截图.png 看上去内容好像是EXE程序中才会有的,于是看了一下分类,好家伙,原来是re题。 百度了一下png文件尾 PNG (png),    文件头:89504E47  文件尾:AE 42 60 82 搜索 AE 42 60 82 mz.png 这不就是熟悉的MZ文件头吗,用010 Editor提取出这一段内容。 并重命名为bingo.exe error.png 结果报错了,随便找了一个exe文件,比对文件内容是否确实,发生少了PE文件头标识。 PE缺失.png 补上这一段内容。 ok.png 数据补上后直接打开运行,发现可以显示黑框,但是运行后直接退出,于是打开IDA分析一下 ida.png ida打开后似乎认不出来文件的其他内容,动态调试后发现这一段内容会出现异常的情况。

1
2
3
4
5
6
7
8
9
pusha
mov     ecx, 3E000h
mov     ebx, 1000h
mov     ebx, 400000h
add     ebx, edx
xor     byte ptr [ebx], 22h
inc     ebx
popa
jmp     loc_408BE0

原因是edx的内容也是400xxxh,相加之后到了800xxx,超出了范围。 这里不知道是作者预期还是怎么的,反正应该是这个解密函数出现了问题。 但是看到这里应该就是一个xor解密(xor 0x22),所以直接在外部解密吧。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#define MAXKEY 5000
#define MAXFILE 1000
#include <cstdio>
#include <cstring>
using namespace std;
int main()
{
    char xor_key[MAXKEY], file_dir[MAXFILE];
    char* buf;
    //printf("xor key: ");
    //scanf("%s", xor_key);
    xor_key[0] = 0x22;
    xor_key[1] = 0;
    printf("file: ");
    scanf("%s", file_dir);
    FILE* fp = fopen(file_dir, "rb");
    strcat(file_dir, ".xor");
    FILE* fpw = fopen(file_dir, "wb+");
    if (fp && fpw)
    {
        fseek(fp, 0, SEEK_END);
        size_t size = ftell(fp);
        fseek(fp, 0, SEEK_SET);
        buf = new char[size];
        fread(buf, sizeof(char), size, fp);
        for (size_t i = 0, keySize = strlen(xor_key); i < size; i++)
            buf[i] ^= xor_key[i % keySize];
        fwrite(buf, sizeof(char), size, fpw);
    }
    if (fp) fclose(fp);
    if (fpw) fclose(fpw);
    return 0;
}

解密文件后,得到文件bingo.exe.xor,观察文件信息,发现实际上只有**.text**段进行了异或加密,其他内容都没有加密。 从文件中大量的0x22内容也可以看出来(因为0x00 ^ 0x22 = 0x22) cmp.png 从0xCC就可以知道,应该是解密对了,因为0xCC对应的是INT3断点,也就是当段未初始化的时候,vs debug模式下会赋值的内容。vs中的烫烫烫也是这么来的。 替换.text段的内容。 替换后.png 替换后得到的exe程序,直接运行当然还是不可以的,但是可以放到ida中解析各个函数了。 但是没有任何的符号信息,难以阅读。所以我还是打算调整程序让其可以正常运行。

打开ida后,定位到start处 patch.png 直接用Keypatch(ida插件)进行修改,让其直接跳到程序真正的入口点(jmp sub_408BE0)。 patch.png 修改后进行保存 canrun.png 发现程序以及可以成功运行。 重新载入后发现,接下来发现程序的符号信息就有了。 main.png 可以看出,程序对输入内容进行加密后与程序中off_443DC0(zaciWjV!Xm[_XSqeThmegndq)进行比对。 encode.png 这里的加密方法就是对你输入值(c)进行平方,然后再加上一个参数(b),最后解出来a。 满足关系式: a^2 + b^2 = c^2。 本来以为直接解密就好了,没想到这里还有一个函数_strrev(v6); 他的作用是把字符串信息倒置,所以最后显示的顺序也会变换。

由于这里sqrt还有个精度问题,我这里就不进行逆运算了,也就是 c = sqrt(a^2 + b^2) 直接编写程序爆破c的内容。

解密程序

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <cstdio>
#include <algorithm>
#include <string.h>
using namespace std;

int main()
{
    char s[] = "zaciWjV!Xm[_XSqeThmegndq";
    char e[] = "                        ";
    char* v6 = (char*)operator new(strlen(s) + 1);
    memset(v6, 0, strlen(s) + 1);

    for (int i = 0; i < strlen(s); i++)
    {
        v6[i] = 'a' + i;
        _strrev(v6);
    }
    for (int i = 0; i < strlen(s); i++) e[v6[i] - 'a'] = s[i];
    printf("%s\n", v6);
    printf("%s\n", e);
    int a2 = 0x34;
    for (int i = 0; i < strlen(s); ++i)
    {
        for (char t = 1; t < 0xFF; t++)
        {
            int v2 = (signed __int64)pow((double)a2, 2.0);
            signed int v3 = (unsigned __int64)(signed __int64)pow((double)t, 2.0);
            v3 -= v2;
            v6[i] = (signed __int64)(sqrt((double)v3) + 0.5);
            if (v6[i] == e[i])
            {
                printf("%c", t);
                break;
            }
        }
        --a2;
    }
    return 0;
}

运行后可以得到: flag.png

flag{woc_6p_tql_moshifu},看到这个flag之后还是觉得挺开心的。

0%