本
文
摘
要
前言:
我已经来到看雪两年多了,只发过一篇贴子,还不是技术贴。两年来,我虽然很少在线,但偶尔逛逛也能发现一些有用的文章,解决了我许多疑惑。我今天就把这一周以来研究抖音签名算法的主要心得分享给大家。初探:
抖音的签名算法在libcms.so中,在JNI_Onload中动态注册jni函数。算法用ollvm混淆了,主要是流程平坦化,流程混淆和运算替换as,cp算法:
这部分我参考了https://bbs.pediy.com/thread-226931.htm,通过调试对比,很快的就把最新版的as,cp算法弄出来了。所以这部分没什么心得。mas算法:
这部分我没有找到任何参考文章,是我一步一步调试分析出来得。先上代码:#include <stdio.h> #include <stdlib.h> #include <memory.h> 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
转载请注明转自看雪论坛。
更多抖音签名设计(如何突破抖音算法)相关信息请关注本站,本文仅仅做为展示!