CTF Re&&Pwn

抖音签名算法的逆向杂谈

2018-08-22  本文已影响163人  看雪学院

前言:

我已经来到看雪两年多了,只发过一篇贴子,还不是技术贴。

两年来,我虽然很少在线,但偶尔逛逛也能发现一些有用的文章,解决了我许多疑惑。我今天就把这一周以来研究抖音签名算法的主要心得分享给大家。

初探:

抖音的签名算法在libcms.so中,在JNI_Onload中动态注册jni函数。

算法用ollvm混淆了,主要是流程平坦化,流程混淆和运算替换

as,cp算法:

这部分我参考了https://bbs.pediy.com/thread-226931.htm,通过调试对比,很快的就把最新版的as,cp算法弄出来了。所以这部分没什么心得。

mas算法

这部分我没有找到任何参考文章,是我一步一步调试分析出来得。

先上代码:

#include

#include

#include

unsigned char ARRAY[0x100];

unsigned int KEY[0x20];

unsigned int init = 0;

void dump_mem(const char *mem, int len)

{

    for (int i = 0; i < len; ++i)

    {

        unsigned char c = *(unsigned char *)(mem + i);

        const char *fmt = c < 0x10 ? "0x0%X" : "0x%X";

        printf(fmt, c);

        if ((i + 1) % 16 == 0 && (i + 1) < len)

        {

            printf("\n");

        }

        else

        {

            printf(" ");

        }

    }

    printf("\n");

}

void dump_memToStr(char *str, const char *mem, int len)

{

    for (int i = 0; i < len; ++i)

    {

        unsigned char c = *(unsigned char *)(mem + i);

        const char *fmt = c < 0x10 ? "0%x" : "%x";

        sprintf(str + i * 2, fmt, c);

    }

}

int LoadData(const char *fn, void *mem)

{

    FILE *fp = fopen(fn, "rb");

    int len = 0;

    fseek(fp, 0, SEEK_END);

    len = ftell(fp);

    rewind(fp);

    int rSzie = fread(mem, len, 1, fp);

    fclose(fp);

    return rSzie;

}

/*

* -----------------------------------------------------------------------------------------------------------------------

* 单个整数处理

*-------------------------------------------------------------------------------------------------------------------------

*/

unsigned int encrypt_dword_0(unsigned int d)

{

    unsigned char b[4] = {0};

    for (int i = 0; i < 4; ++i)

    {

        b[i] = *(unsigned char *)((unsigned char *)&d + i);

    }

    unsigned int ret = b[0];

    for (int i = 0; i < 3; ++i)

    {

        ret = (ret << 4) + b[i + 1];

    }

    return ret;

}

unsigned int encrypt_dword_1(unsigned int d)

{

    unsigned int pre = 0;

    unsigned char b[4] = {0};

    for (int i = 0; i < 4; ++i)

    {

        b[i] = *(unsigned char *)((unsigned char *)&d + i);

    }

    unsigned int v0, v1, v2, v3, v4;

    for (int index = 0; index < 4; ++index)

    {

        switch (index & 0x1)

        {

        case 0:

            v0 = pre << 7;

            v1 = b[index] ^ v0;

            v2 = pre >> 3;

            v3 = v1 ^ v2;

            pre = pre ^ v3;

            break;

        case 1:

            v0 = pre << 0xB;

            v1 = b[index] ^ v0;

            v2 = pre >> 5;

            v3 = v1 ^ v2;

            v4 = v3 ^ 0xFFFFFFFF;

            pre = pre ^ v4;

            break;

        }

    }

    unsigned ret = pre & 0x7FFFFFFF;

    return ret;

}

unsigned int encrypt_dword_2(unsigned int d)

{

    unsigned int pre = 0x4E67C6A7;

    unsigned char b[4] = {0};

    for (int i = 0; i < 4; ++i)

    {

        b[i] = *(unsigned char *)((unsigned char *)&d + i);

    }

    //printf("b0 = %#x, b1 = %#x, b2 = %#x, b3 =%#x\n",b[0],b[1],b[2],b[3]);

    for (int index = 0; index < 4; ++index)

    {

        unsigned int v0 = pre << 5;

        unsigned int v1 = v0 + b[index];

        unsigned int v2 = pre >> 2;

        unsigned int v3 = v1 + v2;

        //printf("v0 = %#x, v1 = %#x, v2 = %#x, v3 = %#x",v0,v1,v2,v3);

        pre = pre ^ v3;

        // printf("pre = %#X\n\n\n",pre);

    }

    unsigned int ret = pre;

    return ret;

}

unsigned int encrypt_dword_3(unsigned int d)

{

    unsigned int v1;

    int v2;

    unsigned int v3;

    unsigned int v4;

    v1 = *((unsigned char *)ARRAY + (d >> 24));

    v2 = *((unsigned char *)ARRAY + (unsigned char)d);

    v3 = (*((unsigned char *)ARRAY + ((d >> 16) & 0xFF)) << 16) | (v1 << 24);

    v4 = v3 | (*((unsigned char *)ARRAY + ((unsigned short)d >> 8)) << 8);

    return (((v4 | v2) << 10) | (v3 >> 22)) ^ ((v4 >> 8) | (v2 << 24)) ^ (v4 | v2) ^ (4 * (v4 | v2) | (v1 >> 6)) ^ (((v4 | v2) << 18) | (v4 >> 14));

}

/*

* -----------------------------------------------------------------------------------------------------------------------

* 处理索引1-16的数据

*-------------------------------------------------------------------------------------------------------------------------

*/

unsigned int rEndian(unsigned int d)

{

    unsigned char b[4] = {0};

    for (int i = 0; i < 4; ++i)

    {

        b[i] = *(unsigned char *)((unsigned char *)&d + i);

    }

    return (b[0] << 24) | (b[1] << 16) | (b[2] << 8) | b[3];

}

unsigned char rBit(unsigned char c)

{

    unsigned char tmp = 0;

    unsigned char bit[8] = {0};

    for (int i = 0; i < 8; ++i)

    {

        bit[i] = (c >> i) & 0x1;

    }

    //dump_mem((const char*)&bit, 8);

    for (int i = 0; i < 8; ++i)

    {

        tmp |= (bit[i] << (7 - i)) & 0xFF;

    }

    return tmp;

}

unsigned int rBit_eachByte(unsigned int d)

{

    unsigned char *C = (unsigned char *)&d;

    unsigned int ret = 0;

    for (int index = 0; index < 4; ++index)

    {

        ret |= rBit(C[index]) << (8 * index);

    }

    return ret;

}

void encrypt_dwordArr_0(unsigned int *D)

{

    unsigned int tD[4] = {0};

    for (int i = 0; i < 4; ++i)

    {

        tD[i] = rEndian(D[i]);

    }

    for (int i = 0; i < 32; ++i)

    {

        tD[i % 4] = encrypt_dword_3(tD[(i + 1) % 4] ^ tD[(i + 2) % 4] ^ tD[(i + 3) % 4] ^ KEY[i]) ^ tD[i % 4];

    }

    for (int i = 0; i < 4; ++i)

    {

        D[i] = rEndian(tD[3 - i]);

    }

}

void encrypt_dwordArr_1(unsigned int *D)

{

    for (int i = 0; i < 4; ++i)

    {

        D[i] = rBit_eachByte(D[i]) ^ rBit_eachByte(encrypt_dword_0(D[(i + 1) % 4])) ^ rBit_eachByte(encrypt_dword_1(D[(i + 2) %4])) ^ rBit_eachByte(encrypt_dword_2(D[(i + 3) % 4]));

    }

}

void encrypt_subBytes(unsigned char *data)

{

    encrypt_dwordArr_0((unsigned int *)data);

    //dump_mem((const char*)data, 16);

    encrypt_dwordArr_1((unsigned int *)data);

    //dump_mem((const char*)data, 16);

}

/*

* -----------------------------------------------------------------------------------------------------------------------

* 根据as计算mas

*-------------------------------------------------------------------------------------------------------------------------

*/

unsigned char rFourBit(unsigned char c)

{

    return ((c & 0xF) << 4) | ((c >> 4) & 0xF);

}

const char *cal_mas_0(const char *as)

{

    unsigned char *mas = (unsigned char *)malloc(27);

    memset(mas, 0, 27);

    mas[0] = 1;

    *(unsigned short *)(mas + 1) = 0x8760; // =(unsigned short)mas

    *(unsigned short *)(mas + 3) = 0x0220; // ???,和getuid有关

    memcpy(mas + 5, as, 22);

    for (int i = 1; i < 27; ++i)

    {

        mas[i] = rBit(mas[i]);

    }

    unsigned char *mas_copy = (unsigned char *)malloc(27);

    memcpy(mas_copy, mas, 27);

    for (int i = 1; i < 17; ++i)

    {

        mas[i] = mas_copy[1 + 16 - i];

    }

    encrypt_subBytes(mas + 1);

    for (int i = 17; i < 27; ++i)

    {

        mas[i] = mas_copy[17 + 26 - i];

    }

    free(mas_copy);

    return mas;

}

const char *cal_mas_1(const char *mas)

{

    unsigned char *mas_str = (unsigned char *)malloc(27 * 2 + 1);

    memset(mas_str, 0, 27 * 2 + 1);

    dump_memToStr(mas_str, (const char *)mas, 27);

    //printf("%s\n",mas_str);

    return mas_str;

}

const char *cal_mas(const char *as)

{

    if (!init)

    {

        LoadData("ARRAY.dat", ARRAY);

        LoadData("KEY.dat", KEY);

        init = 1;

    }

    return cal_mas_1(cal_mas_0(as));

}

ollvm对这些算法的保护是很有用的,比如像encrypt_dword_0,encrypt_dword_1,encrypt_dword_2这些算法,如果不加ollvm的情况下,估计一天就能全搞定了,但是加上后,可能就要3天了。

刚开始接触ollvm,看到一大堆分支,有点不知所措。不过经过多次调试后,便发现其实ollvm其实是纸老虎。以encrypt_dword_1为例:

分支,单元块虽多,但是有用的也只有5块(其实就只有黄色的三块是重要的)。

那要怎么才能定位到这几块主要的地方呢?

动态调试?这是不可能的,十分耗时间。

正确方法是视觉排除法加直觉。

放大关键部分 

结合两张图,可以看出主要块上面的都是一些凭感觉就可以排除,因为他们太小,基本没什么有用运算。通过视觉就可以排除掉60%以上的块了,然后再辅助直觉就可以排除80%的块,然后就可以把剩下的块标注出来,最后剩下的块中基本都至少包含一种有效运算如异或,移位,求和之类的。

再通过下断点调试,这里不是单步调试,而是直接f9。然后观察这些关键运算的操作数。粗略f9跑几遍,有些出现特别多,而操作数又无明显意义(比如运算都是是一个地址加一个常数)的运算,基本可以确定不是关键的。

在剩下的块中,我们要保证输入数据的每一步变化都在这些运算之中,要保证数据变化在掌控之中。最好截图保存运算前后的数据。

如果数据变化在掌控之中,根据截图就可以总结出算法。如:

如果数据变化不在掌控之中,我们可以先大胆猜测一些中间运算,然后验证下,如果能解决最好,不能解决,那就需要在其它可疑块上下断点。

总之,就是要保证数据的来源和去处在掌控之中。

总结:

逆向ollvm的最好方法是视觉和直觉排除法。

写到后面发现全文没什么有用的东西,就当作闲聊吧。

最后放上一张验证算法正确的图:

原文作者:青史无疆(看雪ID)

原文链接:https://bbs.pediy.com/thread-246360.htm

转载请注明转自看雪论坛。

上一篇下一篇

猜你喜欢

热点阅读