010Editor逆向分析

主要内容:

010Editor介绍

16进制编辑器:16进制修改、文本修改、模板解析各种文件格式、对比文件

010暴力破解分析

1、找到注册的窗口

2、测试注册窗口的反应

3、根据反应做出下一步分析

猜测API,API下断点动态调试

敏感字符串,程序中搜索

4、动态分析,定位关键跳转,修改代码

5、动态分析,定位关键CALL,修改代码

010算法分析

关键函数

一步步分析,找到访问用户名和密码的代码

边分析边重写C语言

测试

1样本概况

1.1 应用程序信息

应用程序名称:010Editor 8.0.1

MD5值:7D5D95F0E3DC24AC30133A596D9904AD

SHA1值:4D7089541DB5BFF56CB87F41ECAC2AC3844B2BCA

简单功能介绍:16进制修改、解析各种文件、对比文件

1.2 分析环境及工具

系统环境:win7 32位

工具:Ollydbg、IDA、010Editor、PEiD、Exeinfo、Vistual Studio

1.3 分析目标

1、010Editor暴力破解分析

2、010Editor算法分析

3、010Editor网络验证分析

2.具体分析过程

2.1 分析过程

2.1.1 010Editor暴力破解分析

使用Exeinfo先看一下程序信息,发现该程序是Qt框架下写的,连接器版本是12.0,对应的编译器是VS2013

该程序是收费的,运行之后需要填写用户名和密码,当我们点击Check License时会弹出一个窗口:

因此我们有两种思路进行分析,第一种是搜索创建窗口的函数,第二种是搜索弹出窗口中的字符串,这里第一种进行分析。

使用OD进行分析,在主模块中右键,查找当前模块的名称,发现很多Qt5的函数,输入Create查找,也没有定位到API

使用IDA对程序进行分析,查看其导入表,使用的是Qt的库,对Qt不熟悉,也没有发现创建窗口的函数,因此继续使用OD进行分析。

在模块窗口中找到user32.dll这个模块,这个模块和窗口API有关,进入该模块后,按:Ctrl+n,然后搜索和创建窗口有关的API,如:CreateWindowExA/W、MessageBox、DialogBox、CreateDialog等,Qt程序底层调用的创建窗口的API是CreateWindowA/W,我们搜索该API下断点,然后运行测试:

发现其在CreateWindowExW处断了下来,我们在K窗口中看栈回溯,对主模块进行分析,找到下面几个调用函数:

每个都点进去往上追踪,看有没有关键信息,其中018CB29点进去之后,发现了有用的字符串信息:

其中一个是无效的用户和密码,就是我们输入时弹出的字符串,我们点击该行,会提示我们是从哪个地方跳过来的,我们右键转到该地址:

我们对上面两个对EDI判断的地方下断点运行分析,发现语句执行了,说明如果EBX不等于0x93,EDI不等于0xED、0x20C,就会跳转到刚才提示失败的地方

继续向上追踪,看EBX和EDI分别表示什么意思:

追踪上去发现,如果EDI不等于0xDB就跳转到刚才的地方,对上面的代码下断点运行之后,发现没有断下来,说明上面这一段代码没有执行,EDI的值是上面跳转过来的。

CMP语句的跳转来自两个地方,是两个相邻的地方,我们追踪过去:

发现是判断EBX的值,如果EBX等于0xE7就跳转到刚才的位置,而EBX又是EAX赋值得到的,所以就是判断EAX是否等于0xE7。而又已经知道程序是VS2013的程序,则ECX是函数的返回值,所以我们找到了关键函数。就是当CALL1函数执行后,返回值如果等于0xE7,下面就会跳转到判断EDI的地方。如果EDI不等于0xDB,就会跳转提示错误。继续往下找,看能不能找到提示正确的地方。

在这里发现了提示正确的字符串,又发现其在关键跳转的下面。说明EDI等于0xDB才能继续往下执行提示正确,而EDI又来自上面的EAX,当CALL2函数执行后,返回值EAX等于0xDB,才能继续往下执行提示正确。进入该函数进行分析,发现里面又调用了一次CALL1函数:

分析之后发现当CALL1函数返回值是0x2D的时候会跳转到EAX等于0xDB,说明要想CALL2的返回值等于0xDB,CALL1的返回值必须是0x2D。

暴力破解的话,我们只需要将EDI是否等于0xDB下面的跳转nop掉,然后保存就行,再打开输入就可以通过验证了。

如果想要运行程序之后,直接进入而不需要我们再点Check License,我们要让CALL2的返回值是0xDB,进入函数进行修改,修改后之直接返回:

修改后保存,再打开就是直接进入了。

也可以用弹出窗口中的字符串,来进行搜索。右键查找字符串,右键输入查找的文本也能找到关键位置:

2.1.2 010Editor算法分析   

破解完程序之后,我们需要对关键函数进行分析,来写一个注册机,自动生成序列号。进入关键函数:0040A826。因为是C++程序,需ECX传递this指针,我们需要关注ECX的值,数据窗口中跟随

每个都右键跟随进去看看,数据窗口跟随:0x05153DF0,发现了我们输入的用户名:

数据窗口跟随:0x05129678,发现了我们输入的密码:

发现了带有isEmpty、String的字符串,猜测是判断用户名和密码是否为空的函数:

进去函数进行分析,确定是判断用户名和密码是否为空的函数:

继续往下分析,EAX被赋值为:0x0012D08C,数据窗口跟随:0x05010C3F0,再数据窗口跟随:

发现了输入的密码:

继续向下分析,跟随ECX,ECX+4上保存的是用户名的信息:

下一个函数将带有密码信息的EAX传进去了,不知道其具体目的,进去之后发现过程很长。先数据窗口跟随EAX,然后运行程序,看其数据是怎么变化:

运行前:

运行后:

根据结果显示,上面函数的作用应该是将密码字符串转为了16进制的字节数据。继续向下分析,下面函数传入了一个ASCII是999的值,根据名字猜测是字符串操作,但是具体作用还不知道:

运行之后,返回值是0,没有发现其他变化,先不管其作用,继续往下分析:

发现ESI指向了0123456789ABCDEF,然后是循环。这一块是将999传入进行了操作,没有涉及到用户名和密码,先不去分析其作用,继续向下分析:

从这里开始应该是算法的地方,一步步对算法进行分析:

K[3]如果不等于9C、FC、AC,就会跳转到EAX=0xE7,返回时就会出错。所以K[3]应该是9C、FC、AC的其中一个。如果密码是个数组,初步判断K[0]和K[6]有联系,K[1]和K[7]、K[2]和K[5]有联系。其中K[0]^K[6]后传入了函数,进入函数进行分析:

该函数对K[0]和K[6]进行了一系列的操作,然后返回EAX,再继续分析下一个函数:

经过分析得知:ECX不能为0,即:((K[0]^K[6])^0x18+0x3D)^0xA7不等于0,EAX也不能为0,且小于等于0x3E8。而EAX的值又取决于上面函数的返回值,函数中分析后可知,EAX的值取决于函数中EAX除以ECX的余数,如果余数EDX不为0,则EAX被清空为0,因此,要想EAX不为0,EAX处于ECX的余数要为0。由上面的分析可以得出密码数组中一些元素的关系,可以根据上面的算法编写代码进行测试。

继续向下分析:

运行到这里之后向下跳转到:013BDD88

有一个函数由toUtf8String的字样,应该是和字符串有关的操作,跟随push进去的EAX,看数据的变化,运行前:

该函数运行后:

所以该函数作用是将用户名字符串转化为ASCII。下面又有一个函数,看名字应该是和数组有关的,运行之后看返回值:

运行之后发现其返回值是用户名,继续向下分析,有一个函数传入了四个参数,具体作用不知道,先运行看数据变化:

运行之后,EAX不确定是什么值,可能是哈希值。继续向下分析:

当0x00407644函数的返回值即:((K[0]^K[6])^0x18+0x3D)^0xA7大于等于9的时候才会跳转到成功的地方。经过分析得知:0x00402E50这个函数对ASCII码版的用户名字符串进行了一个计算,计算后返回的结果,分别与密码的K[4]、K[5]、K[6]、K[7]进行了比较,都相等的话,才能成功。

至于函数对用户名计算的这个函数,可以一步步进行分析,也可以Ctrl+X复制函数的地址,然后再IDA中按G键找到该地址:

按F5进行分析:

OD中将0x2E64148上的数据复制下来,放到代码中:

再将IDA中的代码复制下来,放到VS代码中,将参数进行修改

运行之后,得到密码:

2.1.3 010Editor网络验证分析

生成的序列号过一段时间后,再次注册会将序列号发给官网进行验证,如果官网没有保存这个序列号就会失效,我们需要让其通过网络验证。

使用OD继续分析:

运行之后,发现函数返回了0x113,进入0x00409C9B处的函数分析:

发现里面有判断,如果[ESI+0x2C]上的值不为0,就会返回0x113,我们可以将跳转改为无条件跳转:JMP short 0x013BE4F6,然后保存,继续往下运行观察:

此时,没有跳转,继续向下执行,进行网络验证,修改代码,让其一直返回1:

保存之后,就成功绕过了网络验证。

2.2 测试代码(如有代码验证贴出关键代码

用户名加密函数:

参数1:用户名、参数2:BL等于FC, 就是0,不等就是1、参数3:1、参数4:0x40083C8的返回值,<=0x3E8

int __cdecl EncodeUsername(const char* pUsername, int a2, char a3, unsigned __int16 a4)

{

    const char* v4; // edx@1

    signed int v5; // esi@1

    signed int v6; // edi@1

    unsigned __int8 v7; // bl@2

    int v8; // eax@3

    int v9; // ecx@3

    int v10; // ecx@4

    int result; // eax@4

    int v12; // ecx@5

    unsigned __int8 v13; // [sp+8h] [bp-10h]@2

    unsigned __int8 v14; // [sp+Ch] [bp-Ch]@2

    unsigned __int8 v15; // [sp+10h] [bp-8h]@2

    int v16; // [sp+14h] [bp-4h]@1

    v4 = pUsername;

    v16 = 0;

    v5 = strlen(pUsername);

    v6 = 0;

    if (v5 <= 0)

    {

        result = 0;

    }

    else

    {

        v13 = 0;

        v14 = 0;

        v7 = 15 * a4;

        v15 = 17 * a3;

        do

        {

            v8 = toupper(v4[v6]);

            v9 = v16 + g_EcodeArray[v8];

            if (a2)

            {

                v10 = g_EcodeArray[v7]

                    + g_EcodeArray[v15]

                    + g_EcodeArray[(unsigned __int8)(v8 + 47)] * (g_EcodeArray[(unsigned __int8)(v8 + 13)] ^ v9);

                result = g_EcodeArray[v14] + v10;

                v16 = g_EcodeArray[v14] + v10;

            }

            else

            {

                v12 = g_EcodeArray[v7]

                    + g_EcodeArray[v15]

                    + g_EcodeArray[(unsigned __int8)(v8 + 23)] * (g_EcodeArray[(unsigned __int8)(v8 + 63)] ^ v9);

                result = g_EcodeArray[v13] + v12;

                v16 = g_EcodeArray[v13] + v12;

            }

            v14 += 19;

            ++v6;

            v15 += 9;

            v7 += 13;

            v13 += 7;

            v4 = pUsername;

        } while (v6 < v5);

    }

    return result;

}

    主函数:

int main()

{

    srand(time(NULL));

    //0、初始化

    int dwRet = rand() % 0x3E8;

    byte K[10] = { 0x11,0x22,0x33,0x9C,0x55,0x66,0x77,0x88,0x99,0xaa };

    //1、用户名,经过函数加密,由返回值确定K[4]、K[5]、K[6]、K[7]

    char szName[100] = { 0 };

    printf("please input name: ");

    scanf_s("%s", szName, 100);

    DWORD dwKey = EncodeUsername(szName, 1, 0, dwRet);

    K[4] = dwKey & 0xFF;

    K[5] = dwKey >> 8 & 0xFF;

    K[6] = dwKey >> 16 & 0xFF;

    K[7] = dwKey >> 24 & 0xFF;

    //2、穷举

    while (true)

    {

        byte K0 = rand() % 0xFF;

        byte K6 = K[6];

        //AL=((K[0]^K[6])^0x18+0x3D)^0xA7

        //ECX=(((K[0]^K[6])^0x18+0x3D)^0xA7)&0xFF

        byte al = ((K0 ^ K6) ^ 0x18 + 0x3D) ^ 0xA7;

        if (al >= 9)

        {

            K[0] = K0;

            break;

        }

    }

    while (true)

    {

        byte K1 = rand() % 0xFF;

        byte K2 = rand() % 0xFF;

        byte K5 = K[5];

        byte K7 = K[7];

        //ESI=(((K[1]^K[7])&0xFF)*0x100+((K[2]^K[5])&0xFF))&0xFFFF

        //EAX=(((ESI^0x7892+0x4D30)^0x3421)&0xFFFF)

        //余数为0,返回商;且返回的商小于等于0x3E8

        DWORD ESI = (((K1 ^ K7) & 0xFF) * 0x100 + ((K2 ^ K5) & 0xFF)) & 0xFFFF;

        DWORD EAX = (((ESI ^ 0x7892) + 0x4D30) ^ 0x3421) & 0xFFFF;

        int a = EAX % 0xB;

        if (EAX%0xB==0 && EAX/0xB== dwRet)

        {

            K[1] = K1;

            K[2] = K2;

            K[5] = K5;

            K[7] = K7;

            break;

        }

    }

    printf("%02X%02X-%02X%02X-%02X%02X-%02X%02X-%02X%02X", K[0], K[1], K[2], K[3], K[4], K[5], K[6], K[7], K[8], K[9]);

    getchar();

    return 0;

}

3.总结

在对010Editor分析过程中,发现其先对用户名和密码判断是否为空,然后对密码字符串转成16进制字节数据,对数据进行验证。再将用户名转成ASCII字符串,通过一个函数返回一个值,这个值会和密码的K[4]、K[5]、K[6]、K[7]有关系。编写注册机代码思路是:先指定用户名,用加密函数算出一个值,然后根据这个值确定K[4]、K[5]、K[6]、K[7],再用分析出来的的算法将剩下不确定的计算出来。

在对算法进行逆向分析的过程,要一步步跟踪解析算法,需要有耐心,并且要多写注释,便于思路的梳理。另外也可以配合IDA的使用,自动分析一些函数。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值