android逆向奇技淫巧二十八:x音MD5使用分析
1、MD5是业界非常成熟的hash算法了,原理不再赘述,这里介绍一下可以魔改的地方!
(1)MD5默认输出是128bit,怎么改变这个长度了?MD5的结果是由4个32bit的ABCD组成的,原始取值如下:
unsigned int A = 0x67452301; unsigned int B = 0xEFCDAB89; unsigned int C = 0x98BADCFE; unsigned int D = 0x10325476;
大家有没有发现啥规律了?16进制数从0开始分别是0123456789abcdef,直接按照4byte的长度截取使用了(注意MD5用的是小端,顺序刚好是反过来的),这不也是很明显的特征数么?所以这里完全可以换成其他没规律的数字;如果要想改变结果的长度,分别改变ABCD长度就行了;由此同样要改变明文Mi的分组长度和Ki的长度!
(2)上图只有Mi是明文,F和Ki出现的目的就是为了尽可能混淆或扩散明文和hash值,让明文和hash值之间找不到任何规律!所以F函数和Ki也是可以魔改的!原F函数如下:
round 1: F(x,y,z) = (x & y) | (~x & z) //X为真就是Y;X为假就是Z round 2: G(x,y,z) = (x & z) | (y & ~z) : round 3: H(x,y,z) = x ^ y ^ z round 4: I(x,y,z) = y ^ ( x | ~z)
完全可以替换成其他的函数,也可以调换顺序!同理,Ki原始值的计算方式是2^32 * |sin i |,而后取其整数部分, 原始的取值如下:
const unsigned int k[] = { 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501, 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8, 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a, 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1, 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391};
这么多固定值的数,不是很容易被找到么?所以这里的三角函数sin完全可以换成cos、tg、ctg等其他函数。经过这些魔改后,特征数完全改变,想要查找就不那么容易了!
2、之前用findcrypt在x音的libmetasec.so中找到了base64的码表,进而找到了X-Tyhon、X-Ladon、X-Argus生成代码的附近,但是还有X-Gorgon了,看着很像MD5类的算法,但是一直没找到,今天换个新插件试试:signsrch!居然找到了部分特征数,结果如下:
(1)先看第一个,使用的地方在偏移0x6A5D4这里,来到代码这里后直接F5看看,还真有新发现:
这不就是MD5的ABCD四个数么?继续进入里面的函数,在0x69D9C函数里面开始大量计算hash值了, 这里明显用到K表:
注意,原始值是10进制表示的,按H后转成16进制,但是F5识别成了补码,需要在数字上点击右键->invert sign才能看到正确的数字;
继续往下扒,在0x6A4F4偏移处开始用0x80填充了,如下:
3、至此,可以明确x音肯定用了MD5算法,具体传入了哪些参数了?这个只能动态调试看看了! MD5的ABCD四个数都放栈上了,并且函数传入的是一串疑似URL内容的字符串!
经过一系列骚操作后,栈上的ABCD变成这样了:这个结果后续会被用于X-Gorgon的构造(当然肯定不是简单粗暴地直接拿过来用)!
同样的代码位置,还处理过这类“密文”数据:
处理完的结果还是放栈上原ABCD的位置:
其他遇到过计算MD5的字符串:
这里把cronet、quic协议的版本都拿来求hash值了:
堆内存居然偶遇了X-Gorgon,难道是malloc申请的内存使用后忘记free了?
这里的1128后续会被用来生成X-Ladon字段:
总结:
1、算法模块特征:
- 进行大量的循环运算
- 依赖运算相关的汇编指令,诸如XOR、AND、OR、NOT等(70%)
- 加解密中信息熵的变化
2、MD5的ABCD、K表内部的数据都是明文存储,很容易被找到,为啥对这些数据加密?为啥不通过某些公式计算出来,而是直接放明文了?我个人猜测:这类使用频率非常高的hash算法,如果把ABCD或K表加密,或用公式现场计算,效率肯定受影响,会降低用户体验的!其他某些地方加了OLLVM和VMP,已经导致client端计算效率降低了,如果再在这里加固,效率怕是直接跌倒地板,用户都跑光了,要安全有啥用了?
3、MD5除了F函数,在和明文运算的时候为啥不用异或了?
(1)如果用异或,需要IV或key,接收端验证的时候也要这个,该怎么得到这两个了?除非提前通过其他方式获取,比如DH算法交换双方的公钥后生成key,然后HMAC算法使用key和明文一起生成hash值,所以HMAC的本质相当于用key加盐了!
(2)异或有个特性,满足“交换律”:如果A^B=C,那么A^C=B或则B^C=A;一旦key泄漏,结合得到的hash值,可能反推出明文,就不安全了!
4、附上MD5的源码,为了方便理解,加了很多注释;
(1)MD5.h文件
#ifndef MD5_H #define MD5_H typedef struct { /* 存储原始信息的bits数长度(不包括填充的bits),最长为 2^64 bits。如果消息长度大于2^64,则只使用其低64位的值,即(消息长度 对 2^64取模) */ unsigned int count[2]; unsigned int state[4]; unsigned char buffer[64];/*每次处理64byte*/ }MD5_CTX; //F,G,H,I四个非线性变换函数 #define F(x,y,z) ((x & y) | (~x & z)) #define G(x,y,z) ((x & z) | (y & ~z)) #define H(x,y,z) (x^y^z) #define I(x,y,z) (y ^ (x | ~z)) //x循环左移n位的操作 #define ROTATE_LEFT(x,n) ((x << n) | (x >> (32-n))) //FF,GG,HH,II是四轮循环变换分别用到的变换函数 #define FF(a,b,c,d,x,s,ac) \ { \ a += F(b,c,d) + x + ac; \ a = ROTATE_LEFT(a,s); \ a += b; \ } #define GG(a,b,c,d,x,s,ac) \ { \ a += G(b,c,d) + x + ac; \ a = ROTATE_LEFT(a,s); \ a += b; \ } #define HH(a,b,c,d,x,s,ac) \ { \ a += H(b,c,d) + x + ac; \ a = ROTATE_LEFT(a,s); \ a += b; \ } #define II(a,b,c,d,x,s,ac) \ { \ a += I(b,c,d) + x + ac; \ a = ROTATE_LEFT(a,s); \ a += b; \ } void MD5Init(MD5_CTX *context); void MD5Update(MD5_CTX *context,unsigned char *input,unsigned int inputlen); void MD5Final(MD5_CTX *context,unsigned char digest[16]); void MD5Transform(unsigned int state[4],unsigned char block[64]); void MD5Encode(unsigned char *output,unsigned int *input,unsigned int len); void MD5Decode(unsigned int *output,unsigned char *input,unsigned int len); #endif
(2)MD5.c文件
#include <memory.h> #include "md5.h" /*填充位从1开始,就是0x80*/ unsigned char PADDING[]={0x80,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; void MD5Init(MD5_CTX *context) { context->count[0] = 0; context->count[1] = 0; /*ABCD, 分别是大端序和小端序*/ context->state[0] = 0x67452301; context->state[1] = 0xEFCDAB89; context->state[2] = 0x98BADCFE; context->state[3] = 0x10325476; } void MD5Update(MD5_CTX *context,unsigned char *input,unsigned int inputlen) { unsigned int i = 0,index = 0,partlen = 0; /* 计算[已处理数据长度(byte) mod 64] count表示的是bit,>>3就是除以8转成byte &0x3F就是只要最低的8bit数字,也就是控制在0~63之间,也就是mod 64了 */ index = (context->count[0] >> 3) & 0x3F; partlen = 64 - index; //筹够512bit还差多少? /*保存输入原文的bit信息;原文不超过2^64,所以用64bit=8byte来表示长度;而int只有4byte,所以需要2个int*/ context->count[0] += inputlen << 3; if(context->count[0] < (inputlen << 3))//处理加法进位溢出的情况。 context->count[1]++; context->count[1] += inputlen >> 29; /* 如果当前输入的字节数 大于 已有字节数长度补足64字节整倍数所差的字节数 */ if(inputlen >= partlen) { /* 用当前输入的内容把 context->buffer 的内容补足 512bits */ memcpy(&context->buffer[index],input,partlen); /* 用基本函数对填充满的512bits(已经保存到context->buffer中) 做一次转换,转换结果保存到context->state中 */ MD5Transform(context->state,context->buffer); for(i = partlen;i+64 <= inputlen;i+=64)//从补缺的偏移继续处理 /* 对当前输入的剩余字节做转换(如果剩余的字节大于512bits的话), 转换结果保存到context->state中 */ MD5Transform(context->state,&input[i]); index = 0; } else { i = 0; }/*将输入缓冲区中的不足填充满512bits的剩余内容填充到context->buffer中,留待以后再作处理;这就是context中单独设置buffer的根本原因*/ memcpy(&context->buffer[index],&input[i],inputlen-i); } //获取MD5码(由digest返回),顺便清除context数据 void MD5Final(MD5_CTX *context,unsigned char digest[16]) { unsigned int index = 0,padlen = 0; unsigned char bits[8]; //先mod 64byte(也即是512bit),看看超出来多少byte index = (context->count[0] >> 3) & 0x3F; /*为什么是56和120,还有8byte了?8byte=64bit,用来表示原文长度的*/ padlen = (index < 56)?(56-index):(120-index); //记录数据长度 MD5Encode(bits,context->count,8); //追加数据长度信息:先padding,在求hash值 MD5Update(context,PADDING,padlen); //最后8byte(也就是明文长度)的hash值 MD5Update(context,bits,8); //获取MD5码。其实就是将ABCD四个32位整数以16进制方式级联 MD5Encode(digest,context->state,16); } //将无符号整数转为字节类型数组 void MD5Encode(unsigned char *output,unsigned int *input,unsigned int len) { unsigned int i = 0,j = 0; while(j < len) { output[j] = input[i] & 0xFF; output[j+1] = (input[i] >> 8) & 0xFF; output[j+2] = (input[i] >> 16) & 0xFF; output[j+3] = (input[i] >> 24) & 0xFF; i++; j+=4; } } //将字节类型数组转为无符号整数 void MD5Decode(unsigned int *output,unsigned char *input,unsigned int len) { unsigned int i = 0,j = 0; while(j < len) { //典型的大端序: input的地址越高,在int的权位越高,所以越要左移 output[i] = (input[j]) | (input[j+1] << 8) | (input[j+2] << 16) | (input[j+3] << 24); i++; j+=4; } } void MD5Transform(unsigned int state[4],unsigned char block[64]) { unsigned int a = state[0]; unsigned int b = state[1]; unsigned int c = state[2]; unsigned int d = state[3]; unsigned int x[64]; //将64字节的一组数据进一步划分为16个子分组 MD5Decode(x,block,64); //后面这一串是K表 FF(a, b, c, d, x[ 0], 7, 0xd76aa478); /* 1 */ FF(d, a, b, c, x[ 1], 12, 0xe8c7b756); /* 2 */ FF(c, d, a, b, x[ 2], 17, 0x242070db); /* 3 */ FF(b, c, d, a, x[ 3], 22, 0xc1bdceee); /* 4 */ FF(a, b, c, d, x[ 4], 7, 0xf57c0faf); /* 5 */ FF(d, a, b, c, x[ 5], 12, 0x4787c62a); /* 6 */ FF(c, d, a, b, x[ 6], 17, 0xa8304613); /* 7 */ FF(b, c, d, a, x[ 7], 22, 0xfd469501); /* 8 */ FF(a, b, c, d, x[ 8], 7, 0x698098d8); /* 9 */ FF(d, a, b, c, x[ 9], 12, 0x8b44f7af); /* 10 */ FF(c, d, a, b, x[10], 17, 0xffff5bb1); /* 11 */ FF(b, c, d, a, x[11], 22, 0x895cd7be); /* 12 */ FF(a, b, c, d, x[12], 7, 0x6b901122); /* 13 */ FF(d, a, b, c, x[13], 12, 0xfd987193); /* 14 */ FF(c, d, a, b, x[14], 17, 0xa679438e); /* 15 */ FF(b, c, d, a, x[15], 22, 0x49b40821); /* 16 */ /* Round 2 */ GG(a, b, c, d, x[ 1], 5, 0xf61e2562); /* 17 */ GG(d, a, b, c, x[ 6], 9, 0xc040b340); /* 18 */ GG(c, d, a, b, x[11], 14, 0x265e5a51); /* 19 */ GG(b, c, d, a, x[ 0], 20, 0xe9b6c7aa); /* 20 */ GG(a, b, c, d, x[ 5], 5, 0xd62f105d); /* 21 */ GG(d, a, b, c, x[10], 9, 0x2441453); /* 22 */ GG(c, d, a, b, x[15], 14, 0xd8a1e681); /* 23 */ GG(b, c, d, a, x[ 4], 20, 0xe7d3fbc8); /* 24 */ GG(a, b, c, d, x[ 9], 5, 0x21e1cde6); /* 25 */ GG(d, a, b, c, x[14], 9, 0xc33707d6); /* 26 */ GG(c, d, a, b, x[ 3], 14, 0xf4d50d87); /* 27 */ GG(b, c, d, a, x[ 8], 20, 0x455a14ed); /* 28 */ GG(a, b, c, d, x[13], 5, 0xa9e3e905); /* 29 */ GG(d, a, b, c, x[ 2], 9, 0xfcefa3f8); /* 30 */ GG(c, d, a, b, x[ 7], 14, 0x676f02d9); /* 31 */ GG(b, c, d, a, x[12], 20, 0x8d2a4c8a); /* 32 */ /* Round 3 */ HH(a, b, c, d, x[ 5], 4, 0xfffa3942); /* 33 */ HH(d, a, b, c, x[ 8], 11, 0x8771f681); /* 34 */ HH(c, d, a, b, x[11], 16, 0x6d9d6122); /* 35 */ HH(b, c, d, a, x[14], 23, 0xfde5380c); /* 36 */ HH(a, b, c, d, x[ 1], 4, 0xa4beea44); /* 37 */ HH(d, a, b, c, x[ 4], 11, 0x4bdecfa9); /* 38 */ HH(c, d, a, b, x[ 7], 16, 0xf6bb4b60); /* 39 */ HH(b, c, d, a, x[10], 23, 0xbebfbc70); /* 40 */ HH(a, b, c, d, x[13], 4, 0x289b7ec6); /* 41 */ HH(d, a, b, c, x[ 0], 11, 0xeaa127fa); /* 42 */ HH(c, d, a, b, x[ 3], 16, 0xd4ef3085); /* 43 */ HH(b, c, d, a, x[ 6], 23, 0x4881d05); /* 44 */ HH(a, b, c, d, x[ 9], 4, 0xd9d4d039); /* 45 */ HH(d, a, b, c, x[12], 11, 0xe6db99e5); /* 46 */ HH(c, d, a, b, x[15], 16, 0x1fa27cf8); /* 47 */ HH(b, c, d, a, x[ 2], 23, 0xc4ac5665); /* 48 */ /* Round 4 */ II(a, b, c, d, x[ 0], 6, 0xf4292244); /* 49 */ II(d, a, b, c, x[ 7], 10, 0x432aff97); /* 50 */ II(c, d, a, b, x[14], 15, 0xab9423a7); /* 51 */ II(b, c, d, a, x[ 5], 21, 0xfc93a039); /* 52 */ II(a, b, c, d, x[12], 6, 0x655b59c3); /* 53 */ II(d, a, b, c, x[ 3], 10, 0x8f0ccc92); /* 54 */ II(c, d, a, b, x[10], 15, 0xffeff47d); /* 55 */ II(b, c, d, a, x[ 1], 21, 0x85845dd1); /* 56 */ II(a, b, c, d, x[ 8], 6, 0x6fa87e4f); /* 57 */ II(d, a, b, c, x[15], 10, 0xfe2ce6e0); /* 58 */ II(c, d, a, b, x[ 6], 15, 0xa3014314); /* 59 */ II(b, c, d, a, x[13], 21, 0x4e0811a1); /* 60 */ II(a, b, c, d, x[ 4], 6, 0xf7537e82); /* 61 */ II(d, a, b, c, x[11], 10, 0xbd3af235); /* 62 */ II(c, d, a, b, x[ 2], 15, 0x2ad7d2bb); /* 63 */ II(b, c, d, a, x[ 9], 21, 0xeb86d391); /* 64 */ state[0] += a; state[1] += b; state[2] += c; state[3] += d; } void main( void ) { int read_len; int i ; char temp[8]={0}; //unsigned char digest[16]; //存放结果 char hexbuf[128]="12334567"; unsigned char decrypt[16]={0}; unsigned char decrypt32[64]={0}; MD5_CTX md5c; MD5Init(&md5c); //初始化 read_len = strlen(hexbuf); MD5Update(&md5c,(unsigned char *)hexbuf,read_len); MD5Final(&md5c,decrypt); printf("md5 decrypt:%s\n",decrypt); strcpy((char *)decrypt32,""); for(i=0;i<16;i++) { sprintf(temp,"%02x",decrypt[i]); strcat((char *)decrypt32,temp); } printf("md5 decrypt32:%s\n",decrypt32); return; }
参考:
1、https://cloud.tencent.com/developer/article/1806329 MD5 C源码