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源码

posted @ 2022-05-18 12:02  第七子007  阅读(1432)  评论(2编辑  收藏  举报