从 EXE 中提取解密令牌

逆向工程 视窗 C++ 加密 快手 散列函数
2021-07-01 08:46:35

编辑

正如一些人指出的那样,这个 exe 文件在某些​​分析仪上被归类为“病毒”。我可以向你保证,这个 exe 来自一个非常值得信赖的来源,实际上是一个大难题的一部分(其中包含更多这样的问题,与 RE、密码学等有关)

非常欢迎你检查我的堆栈交换配置文件,看看我真的没有“这个人向人们发送病毒并声称这是一个难题”类型转换:-)


我已经使用 OllyDbg 这个exe(原始版本,一个简单的谜题)调试了一段时间,但我仍然无法找到它期望的输入。

它能做什么

执行后,它会要求您输入解密的令牌。然后程序会告诉你令牌是否正确。

我所知道的:

  1. exe 计算目标散列并在您运行它后将其存储在内存中(此散列不会随执行而改变
  2. 一旦执行,它就会调用 IsDebuggerPresent - 如果是,则更改上述结果。我在我的 exe 版本上修补了这个。
  3. 它等待用户输入
  4. 它计算用户输入的哈希值(我认为是加盐),并将其与在步骤 1 中计算的哈希值进行比较

目标是找出在第 3 步中应将哪个输入提供给 exe

我试过的

基本上,由于哈希是一个 32 字节的字符串,我想这可能是普通的 MD4/MD5。我所做的是给程序一个已知的纯文本——“1111”,并提取它计算的哈希值。然后我获取了哈希并尝试在其上运行 MD4/MD5 破解程序,给它一个已知的候选者 - “1111”。我没有这样做,这否定了 MD4/MD5 理论(可能?)

下一步

由于 RE 对我来说是新手,下一个合乎逻辑的步骤是尝试找出 exe 用来计算步骤 1 中的目标哈希的字符串。我在调试步骤 1 时确实在内存中找到了一些东西 - 一个带有的价值

"Habh;)86!!'a/$r3})RX;8{>T"

(注意 \t 和 \n),但是在步骤 3 中将此输入提供给 exe 时,结果不正确。

想法将不胜感激!

2个回答

既然是crackme,我不想剧透解决方案,尽管我尝试给出一些提示,这可能会对您有所帮助。

散列函数可能是使用盐的 MD5、修改后的 IV 或散列函数在您尝试时以不同的方式使用。但是,您既不必反转散列函数,也不必找出确切的算法。这是因为,crackme 计算原始令牌和用户提供的令牌的哈希值。所以,我认为最简单的方法是,如果您尝试在哈希函数的开头捕获令牌,因为此时它应该是明文。

您尝试作为令牌的字符串以某种方式用于令牌解密。这个挑战比你在执行过程中的任何时候在内存中找到解决方案标记都要棘手。

此程序的令牌长 57 个字符,并使用 200KB 查找表进行加密。密文作为 57 个双字索引嵌入应用程序中。反调试是作为对 IsDebuggerAttached 的简单检查实现的,它在 57 个好的或 57 个坏的索引之间进行选择

当应用程序启动时,令牌被解密,然后一次散列一个字符。我对查找表的操作感兴趣,而不是反转或检查散列。

索引表的代码由应用程序在单独的线程中运行。线程过程从 0x4037A0 开始,但是它包含一些无效的操作码来混淆反汇编程序。通过在执行之前填充 NOP 来修补无效代码(参见:0x403998)

查表功能需要 3 次索引操作来解析编码的关键字符。每个双字索引都以 0xc800 为模,以确保它位于 200KB 表内。最终的索引操作包括基于索引奇偶校验的偏移量。

对应查表的C程序是:

unsigned int lookup(unsigned char *cipher_table, unsigned int cipher_index)
{
    unsigned int parity_check = 0, offset = 0;
    cipher_index = *((unsigned int *)(cipher_table + (4 * (cipher_index % 0xc800))));
    cipher_index = *((unsigned int *)(cipher_table + (4 * (cipher_index % 0xc800))));
    if (cipher_index != 0) {
        parity_check = cipher_index;
        do {
            if (!parity(parity_check & 0xFF)) offset = ~offset;
            parity_check = parity_check >> 8;
        } while (parity_check > 0);
    }
    cipher_index = *((unsigned int *)(cipher_table + (4 * ((cipher_index % 0xc800) + (offset & 1)))));

    return ((~cipher_index & 0xFE000000) | cipher_index & 0x01FFFFFF) ^ 0xFE000000;
}

下面是一个完整的 C 密钥解密例程。它需要“1.exe”二进制文件,因为它从中读取了 200KB 的查找表。

#include <stdio.h>
#include <stdlib.h>

const unsigned char xor_data[57] = {
    0xD1, 0x18, 0x52, 0xF3, 0x4B, 0x29, 0xB5, 0xE6,
    0x2E, 0x60, 0x10, 0x1B, 0x59, 0x3E, 0x4D, 0xC1,
    0x27, 0xAB, 0xA4, 0x82, 0x7F, 0x5B, 0x07, 0xA4,
    0xE3, 0x1C, 0xE3, 0x93, 0x74, 0xBB, 0x3A, 0x62,
    0x39, 0xE0, 0xDE, 0xE3, 0x85, 0x7A, 0x05, 0x29,
    0x4F, 0xF1, 0x70, 0x1C, 0x70, 0x5E, 0xB3, 0xBF,
    0x0A, 0x74, 0x97, 0x49, 0xFB, 0xE4, 0xC8, 0xF6,
    0x5F 
};

const unsigned int cipher_index[57] = {
    0xd12479b8, 0x02802dd4, 0x9390b528, 0x6d13be10,
    0x38637098, 0xadb0df76, 0x492afd8d, 0x6cda8589,
    0x3f327fb3, 0xb559ed2c, 0x0d379569, 0xee50590a,
    0xeffc3faf, 0xb183ff7c, 0x4942fe7a, 0x3f9f0383,
    0xe5e8796e, 0x4acdb7e3, 0x6f7778ac, 0x9cfbd58d,
    0x5d58a9d4, 0xb53ee0e8, 0x4f0bc8f7, 0x6ddcf35a,
    0x0b32d6f3, 0xec181a05, 0xebf6aab4, 0x727beb1b,
    0xd19b1f42, 0x0cd03ae5, 0xc901c555, 0x9e6123ed,
    0x4805bbd3, 0x0b4e3b5f, 0xe74991a5, 0x16e56a67,
    0xcdbf9035, 0x0c3fb795, 0x4e46ed21, 0x3098a99c,
    0x0be2219a, 0x89008aba, 0xd1e8cb77, 0x8d0a0f39,
    0xbf93eaae, 0xdd1f5179, 0x27aa29da, 0x0687579f,
    0xe4961b86, 0x00047b96, 0xdf66fc9d, 0xe4ff21b6,
    0x056c231b, 0xb94a2217, 0x2e385b22, 0xd9315588,
    0x975aec60
};

unsigned int lookup(unsigned char *cipher_table, unsigned int cipher_index);

int main()
{
    // read lookup table from 1.exe
    unsigned char *cipher_table = (unsigned char*)malloc(0x32000);
    if (cipher_table == NULL) {
        printf("Failed to allocate 200K lookup table\r\n");
        return -1;
    }
    FILE *file = NULL;
    if (fopen_s(&file, "1.exe", "r") != 0) {
        printf("Failed to open 1.exe\r\n");
        return -1;
    }
    fseek(file, 0x6430, 0);
    fread(cipher_table, 4, 0xc800, file);
    fclose(file);

    // Look up 57 key characters via indexes
    for (int i = 0; i < 57; i++) {
        char key = (char)((lookup(cipher_table, cipher_index[i]) >> ((i & 3) * 8)) ^ (xor_data[i]));
        printf("%c", key);
    }
    printf("\r\n");
    free(cipher_table);

    return 0;
}

// true = even; false=odd
bool parity(unsigned char check)
{
    check = (check & 0x55) + ((check >> 1) & 0x55);
    check = (check & 0x33) + ((check >> 2) & 0x33);

    return (((check & 0x0F) + ((check >> 4) & 0x0F)) % 2);
}

unsigned int lookup(unsigned char *cipher_table, unsigned int cipher_index)
{
    unsigned int parity_check = 0, offset = 0;
    cipher_index = *((unsigned int *)(cipher_table + (4 * (cipher_index % 0xc800))));
    cipher_index = *((unsigned int *)(cipher_table + (4 * (cipher_index % 0xc800))));
    if (cipher_index != 0) {
        parity_check = cipher_index;
        do {
            if (!parity(parity_check & 0xFF)) offset = ~offset;
            parity_check = parity_check >> 8;
        } while (parity_check > 0);
    }
    cipher_index = *((unsigned int *)(cipher_table + (4 * ((cipher_index % 0xc800) + (offset & 1)))));

    return ((~cipher_index & 0xFE000000) | cipher_index & 0x01FFFFFF) ^ 0xFE000000;
}

令牌是(剧透):

<****TKN!34C38983E67F17EE146C25B8838E428D8B0AE58!TKN****>