前言

上一篇中,分析到了 json 内容错误这里,
接下来就需要具体去探索为何会错误了。

错在哪里

回到 Engintime OS Lab ,在结束进程相关函数(ExitProcess TerminateProcess 等)上面下断点,
可以看出并不是程序主动退出,那么可能的原因是异常无法处理而退出的,通过对退出时的栈信息进行判断可以证明这一点。

可惜的是,我用的这个版本的 od 无论怎么关闭忽略异常都不会接管,所以暂时换了vs的调试器。

0.png

另外,此程序并未启用随机基址,故这个地址是不变的。

0x00633E32

分析异常成因

IDA 查看之
1.png

可以得到以下信息

  1. 异常位于数据段
  2. 在异常代码片段之前是正常代码片段
  3. 从 CODE XREF(交叉引用来看) 是从 sub_634B40+435↓p 处正常调用到此处

od动态看一下
3.png

4.png
在函数 sub_460320 调用前后,异常内容发生了变换,
初步猜测,函数 sub_460320 会改变即将执行的内容即解密。

为了看的清晰一些,使用了 IDAHex-Rays Decompiler 插件,俗称 F5插件。
5.png
得到如下结论

  1. retaddr 即返回地址是参数的一部分
  2. retaddr 的前4个字节不为 0x48404840 时,进行解密
  3. 解密完成后 前8个字节为 0x48404840 0x48404840

这是典型的 SMC加密 方式。

SMC加密

SMC是一种局部代码加密技术,通过对一段代码进行加密来达到增加逆向工程难度或者免杀的目的。

具体的内容,网上一搜到处都是。
参考

这种加密有个特点,就是静态key,一旦keydump出来了,这个软件就能被共享出去。

通常情况下,这个key,必须要授权才能得到,也有例外情况。

接下来重点就是看 sub_5E6550 的行为
6.png
通过动态观察其参数,又能得到如下信息

retaddr 的4-8字节中存放有待解密长度,解密块从长度之后开始计数
最后一个参数中存放着先前clientauthorizedcode返回的json中的 [0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5]。

7.png

通过对比置换表等特征,可以确定是 3des 加密,
我并未具体分析sub_5E6550如何实现,
只是根据行为进行推断,这点会影响后面破解器的编写。

3DES

3DES(或称为Triple DES)是三重数据加密算法(TDEA,Triple Data Encryption Algorithm)块密码的通称。

参考

由于是用3des 做的 SMC加密,老实说比较没辙,就算知道
明文 密钥长度 密文
还是无法求得密钥,那么就避免不了需要穷举了(最少我不知道如何求解)。

通过对能成功运行的 CP Lab 对比分析,还原了异常代码片段
2.png

由于程序多处进行了加密,故还原一处并无大用,但是可以写出一个破解器。

暴力破解器

这个暴力破解器有两种写法

  1. 提取出加密数据,外部自行使用 3des库,同时需要还原解密逻辑
  2. 直接注入程序,调用程序原有的解密程序

由于我不想在这个东西上浪费时间,通常来说,暴力破解是非常耗时间的,这里我只给出第二种写法的验证算法,这种写法的好处在于,我不用管它内部是怎么样加密变换的,只需要知道

加密块 + 加密密钥 + 解密入口 = 解密块

// 经过修改的验证算法,调用原程序的解密算法,并修改 retaddr 为指定的加密块起始地址
int  sub_460320(char* key, char* start)
{
    int v1; // ebx
    char* v2; // esi
    int v3; // [esp+10h] [ebp-2Ch]
    char* v4; // [esp+28h] [ebp-14h]
    DWORD* v5; // [esp+2Ch] [ebp-10h]
    int v6; // [esp+38h] [ebp-4h]
    void* retaddr = start; // [esp+40h] [ebp+4h]
    int retv = 0;

    v4 = (char*)key;
    v5 = (DWORD*)retaddr;
    v1 = *(DWORD*)retaddr;
    if (*(DWORD*)retaddr != 0x48404840)
    {
        v2 = (char*)*((DWORD*)retaddr + 1); // 168
        __asm {
            lea ecx, v3
            mov eax, 0x005E5CF0
            call eax
        }

        v6 = 0;
        int cret = (int)retaddr + 8; // 31
        __asm {
            lea ecx, v3
            push v4
            push v2
            push cret
            push v2
            push cret
            mov eax, 0x005E6550
            call eax
            mov retv, eax
        }
        *v5 = 0x48404840;
        v5[1] = 0x48404840;
        v5 = (DWORD*)((char*)retaddr + v1 + 8);
        *v5 = 0x48404840;
        v5[1] = 0x48404840;
        __asm {
            lea ecx, v3
            mov eax, 0x005E5D00
            call eax
        }
        return retv;
    }
    return -1;
}

#define COPYSIZE (0x178)
char memencode[COPYSIZE] = { 0 };

void verify(char* key, int keylen) {
    memcpy(memencode, (char*)0x00633E29, COPYSIZE);

    sub_460320(key, memencode);

    // 输出解密结果
    // Write2Con("[+] ---- Key ----\r\n");
    // Write2Con(PrintBuffer(key, keylen));

    // Write2Con("[+] ---- decode ----\r\n");
    // Write2Con(PrintBuffer(memencode, 0x50));

}

需要注意,以上代码并不是真正的破解器,只是简单验证算法,
其效率低下,如果感兴趣,可以根据逻辑自行优化。

破解思路

方案一:
通过其他渠道获取到正确的 3dskey

方案二:
将已知可用(已解密)的exe以dll方式加载到目标进程内存,
hook解密函数,根据特征码,匹配到指定已解密函数,并跳转到已解密函数继续执行,
需要在解密函数到达时动态提取特征码并匹配。
难点在于特征的动态提取。

总结

通过逐层分析,已经对该系列软件的验证方式得到了一个较为具体的理解。

现在知道了,该系列的部分不需要正确的 3dskey 能正常工作,部分则需要,以及这个key的作用。
下一篇中,将写出一个较为通用的工具,也算是对整个逆向过程的总结。