首页
社区
课程
招聘
[原创]MD5算法分析及逆向详解
发表于: 2014-10-21 17:11 28813

[原创]MD5算法分析及逆向详解

2014-10-21 17:11
28813
题外话:最近在看加密与解密,看到加密算法部分,感觉对于初次接触的新手还是有些难度的。故写下该篇文章,算作一个引导吧,新手飘过,老鸟勿笑。

基本原理:
      MD5的典型应用是对一段信息(Message)产生信息摘要(Message-Digest),以防止被篡改。比如,在UNIX下有很多软件在下载的时候都有一个文件名相同,文件扩展名为.md5的文件,在这个文件中通常只有一行文本,大致结构如:
  MD5 (tanajiya.tar.gz) = 0ca175b9c0f726a831d895e269332461
这就是tanajiya.tar.gz文件的数字签名。MD5将整个文件当作一个大文本信息,通过其不可逆的字符串变换算法,产生了这个唯一的MD5信息摘要。

算法过程:
      对MD5算法简要的叙述可以为:MD5以512位分组来处理输入的信息,且每一分组又被划分为16个32位子分组,经过了一系列的处理后,算法的输出由四个32位分组组成,将这四个32位分组级联后将生成一个128位散列值。
  在MD5算法中,首先需要对信息进行填充,使其位长对512求余的结果等于448。因此,信息的位长(Bits Length)将被扩展至N*512+448,即N*64+56个字节(Bytes),N为一个正整数。填充的方法如下,在信息的后面填充一个1和无数个0,直到满足上面的条件时才停止用0对信息的填充。然后,在在这个结果后面附加一个以64位二进制表示的填充前信息长度。经过这两步的处理,现在的信息的位长=N*512+448+64=(N+1)*512,即长度恰好是512的整数倍。这样做的原因是为满足后面处理中对信息长度的要求。注意即使消息长度已经满足该公式的要求(位长对512求余的结果等于448),仍然需要填充。
  MD5中有四个32位被称作链接变量(Chaining Variable)的整数参数,他们分别为:A=0x01234567,B=0x89abcdef,C=0xfedcba98,D=0x76543210。
  当设置好这四个链接变量后,就开始进入算法的四轮循环运算。循环的次数是信息中512位信息分组的数目。
  将上面四个链接变量复制到另外四个变量中:A到a,B到b,C到c,D到d。
  主循环有四轮(MD4只有三轮),每轮循环都很相似。第一轮进行16次操作。每次操作对a、b、c和d中的其中三个作一次非线性函数运算,然后将所得结果加上第四个变量,文本的一个子分组和一个常数。再将所得结果向右环移一个不定的数,并加上a、b、c或d中之一。最后用该结果取代a、b、c或d中之一。
  以一下是每次操作中用到的四个非线性函数(每轮一个)。
  F(X,Y,Z) =(X&Y)|((~X)&Z)
  G(X,Y,Z) =(X&Z)|(Y&(~Z))
  H(X,Y,Z) =X^Y^Z
  I(X,Y,Z)=Y^(X|(~Z))
  这四个函数的说明:如果X、Y和Z的对应位是独立和均匀的,那么结果的每一位也应是独立和均匀的。
  F是一个逐位运算的函数。即,如果X,那么Y,否则Z。函数H是逐位奇偶操作符。
  假设Mj表示消息的第j个子分组(从0到15),<<
  FF(a, b, c, d, Mj, s, ti)表示 a = b + ((a + F(b, c, d) + Mj + ti) << s)
  GG(a, b, c, d, Mj, s, ti)表示 a = b + ((a + G(b, c, d) + Mj + ti) << s)
  HH(a, b, c, d, Mj, s, ti)表示 a = b + ((a + H(b, c, d) + Mj + ti) << s)
  II(a, b, c, d, Mj, s, ti)表示 a = b + ((a + I(b, c, d) + Mj + ti) << s)
  
这四轮(64步)是:
  第一轮
  FF(a, b, c, d, M0, 7, 0xd76aa478)
  FF(d, a, b, c, M1, 12, 0xe8c7b756)
  FF(c, d, a, b, M2, 17, 0x242070db)
  FF(b, c, d, a, M3, 22, 0xc1bdceee)
  FF(a, b, c, d, M4, 7, 0xf57c0faf)
  FF(d, a, b, c, M5, 12, 0x4787c62a)
  FF(c, d, a, b, M6, 17, 0xa8304613)
  FF(b, c, d, a, M7, 22, 0xfd469501)
  FF(a, b, c, d, M8, 7, 0x698098d8)
  FF(d, a, b, c, M9, 12, 0x8b44f7af)
  FF(c, d, a, b, M10, 17, 0xffff5bb1)
  FF(b, c, d, a, M11, 22, 0x895cd7be)
  FF(a, b, c, d, M12, 7, 0x6b901122)
  FF(d, a, b, c, M13, 12, 0xfd987193)
  FF(c, d, a, b, M14, 17, 0xa679438e)
  FF(b, c, d, a, M15, 22, 0x49b40821)
  第二轮
  GG(a, b, c, d, M1, 5, 0xf61e2562)
  GG(d, a, b, c, M6, 9, 0xc040b340)
  GG(c, d, a, b, M11, 14, 0x265e5a51)
  GG(b, c, d, a, M0, 20, 0xe9b6c7aa)
  GG(a, b, c, d, M5, 5, 0xd62f105d)
  GG(d, a, b, c, M10, 9, 0x02441453)
  GG(c, d, a, b, M15, 14, 0xd8a1e681)
  GG(b, c, d, a, M4, 20, 0xe7d3fbc8)
  GG(a, b, c, d, M9, 5, 0x21e1cde6)
  GG(d, a, b, c, M14, 9, 0xc33707d6)
  GG(c, d, a, b, M3, 14, 0xf4d50d87)
  GG(b, c, d, a, M8, 20, 0x455a14ed)
  GG(a, b, c, d, M13, 5, 0xa9e3e905)
  GG(d, a, b, c, M2, 9, 0xfcefa3f8)
  GG(c, d, a, b, M7, 14, 0x676f02d9)
  GG(b, c, d, a, M12, 20, 0x8d2a4c8a)
  第三轮
  HH(a, b, c, d, M5, 4, 0xfffa3942)
  HH(d, a, b, c, M8, 11, 0x8771f681)
  HH(c, d, a, b, M11, 16, 0x6d9d6122)
  HH(b, c, d, a, M14, 23, 0xfde5380c)
  HH(a, b, c, d, M1, 4, 0xa4beea44)
  HH(d, a, b, c, M4, 11, 0x4bdecfa9)
  HH(c, d, a, b, M7, 16, 0xf6bb4b60)
  HH(b, c, d, a, M10, 23, 0xbebfbc70)
  HH(a, b, c, d, M13, 4, 0x289b7ec6)
  HH(d, a, b, c, M0, 11, 0xeaa127fa)
  HH(c, d, a, b, M3, 16, 0xd4ef3085)
  HH(b, c, d, a, M6, 23, 0x04881d05)
  HH(a, b, c, d, M9, 4, 0xd9d4d039)
  HH(d, a, b, c, M12, 11, 0xe6db99e5)
  HH(c, d, a, b, M15, 16, 0x1fa27cf8)
  HH(b, c, d, a, M2, 23, 0xc4ac5665)
  第四轮
  II(a, b, c, d, M0, 6, 0xf4292244)
  II(d, a, b, c, M7, 10, 0x432aff97)
  II(c, d, a, b, M14, 15, 0xab9423a7)
  II(b, c, d, a, M5, 21, 0xfc93a039)
  II(a, b, c, d, M12, 6, 0x655b59c3)
  II(d, a, b, c, M3, 10, 0x8f0ccc92)
  II(c, d, a, b, M10, 15, 0xffeff47d)
  II(b, c, d, a, M1, 21, 0x85845dd1)
  II(a, b, c, d, M8, 6, 0x6fa87e4f)
  II(d, a, b, c, M15, 10, 0xfe2ce6e0)
  II(c, d, a, b, M6, 15, 0xa3014314)
  II(b, c, d, a, M13, 21, 0x4e0811a1)
  II(a, b, c, d, M4, 6, 0xf7537e82)
  II(d, a, b, c, M11, 10, 0xbd3af235)
  II(c, d, a, b, M2, 15, 0x2ad7d2bb)
  II(b, c, d, a, M9, 21, 0xeb86d391)
  常数ti可以如下选择:
  在第i步中,ti是4294967296*abs(sin(i))的整数部分,i的单位是弧度。(4294967296等于2的32次方)
  所有这些完成之后,将A、B、C、D分别加上a、b、c、d。然后用下一分组数据继续运行算法,最后的输出是A、B、C和D的级联。
  当你按照我上面所说的方法实现MD5算法以后,你可以用以下几个信息对你做出来的程序作一个简单的测试,看看程序有没有错误。
Test:
  MD5 ("") = d41d8cd98f00b204e9800998ecf8427e
  MD5 ("a") = 0cc175b9c0f1b6a831c399e269772661
上面是原理及计算过程的分析。
C语言版的算法分析如下(完整版请看附件):

/*初始化md5的结构*/
void MD5Init (MD5_CTX *context)
{
/*将当前的有效信息的长度设成0,这个很简单,还没有有效信息,长度当然是0了*/
  context->count[0] = context->count[1] = 0;
  /* Load magic initialization constants.*/
  /*初始化变量,算法要求这样 */

  context->state[0] = 0x67452301;
  context->state[1] = 0xefcdab89;
  context->state[2] = 0x98badcfe;
  context->state[3] = 0x10325476;
}

/*将与加密的信息传递给md5结构,可以多次调用
context:初始化过了的md5结构
input:欲加密的信息,可以任意长
inputLen:指定input的长度*/

void MD5Update(MD5_CTX *context, unsigned char * input, unsigned int  inputLen)
{
unsigned int i, index, partLen;
/* Compute number of bytes mod 64 */
/*计算已有信息的bits长度的字节数的模64, 64bytes=512bits。
用于判断已有信息加上当前传过来的信息的总长度能不能达到512bits,
如果能够达到则对凑够的512bits进行一次处理*/
/*计算已有信息模64后的字节数*/

index = (unsigned int)((context->count[0] >> 3) & 0x3F);
/* Update number of bits *//*更新已有信息的bits长度,当已有长度加上新输入信息的长度
如果小于新输入的信息长度,说明高位进位*/

if((context->count[0] += ((UINT4)inputLen << 3)) < ((UINT4)inputLen << 3))
  context->count[1]++;
context->count[1] += ((UINT4)inputLen >> 29); //输入的高位字节数
/*计算已有的字节数长度还差多少字节可以凑成64字节的整倍数*/
partLen = 64 - index;
/* Transform as many times as possible.*/
/*如果当前输入的字节数 大于 已有字节数长度补足64字节整倍数所差的字节数*/

if(inputLen >= partLen)
    {
  /*用当前输入的内容把context->buffer的内容补足512bits*/
  R_memcpy((POINTER)&context->buffer[index], (POINTER)input, partLen);
  /*用基本函数对填充满的512bits(已经保存到context->buffer中) 做一次转换,转换结果保存到context->state中*/
  MD5Transform(context->state, context->buffer);
/*对当前输入的剩余字节(满足512bits)做转换,转换结果保存到context->state中*/
  for(i = partLen; i + 63 < inputLen; i += 64)/*改为i+64<=inputlen更容易理解*/
   MD5Transform(context->state, &input[i]);
        index = 0;
    }
else
    i = 0;
/* Buffer remaining input */
/*将输入缓冲区中不足512bits的剩余内容填充到context->buffer中,留待以后再作处理*/

R_memcpy((POINTER)&context->buffer[index], (POINTER)&input[i], inputLen-i);
}

/*获取加密 的最终结果
digest:保存最终的加密串
context:你前面初始化并填入了信息的md5结构*/

void MD5Final (unsigned char digest[16],MD5_CTX *context)
{
unsigned char bits[8];
unsigned int index, padLen;
/*将要被转换的信息转换成8bits的字符串*/
Encode(bits, context->count, 8);

/* 计算已有的bits长度的字节数的模64, 64bytes=512bits*/
index = (unsigned int)((context->count[0] >> 3) & 0x3f);
/*计算需要填充的字节数,padLen的取值范围在1-64之间*/
padLen = (index < 56) ? (56 - index) : (120 - index); //56字节等于448bits
/*这一次函数调用至多导致一次MD5Transform调用,因为这一次填充后的大小为:48~64+48字节数*/
MD5Update(context, PADDING, padLen);

/*补上原始信息的bits长度(bits长度固定的用64bits表示),这一次能够恰巧凑够512bits,不会多也不会少*/
MD5Update(context, bits, 8);

/*将最终的结果保存到digest中,将4个32bits转换成16个8bits*/
Encode(digest, context->state, 16);
/* Zeroize sensitive information. */
R_memset((POINTER)context, 0, sizeof(*context));
}

/*
对512bits信息(即block缓冲区)进行一次处理,每次处理包括四轮
state[4]:md5结构中的state[4],用于保存对512bits信息加密的中间结果或者最终结果
block[64]:欲加密的512bits信息
*/

static void MD5Transform (UINT4 state[4], unsigned char block[64])
{
UINT4 a = state[0], b = state[1], c = state[2], d = state[3], x[16];
Decode(x, block, 64);
/* Round 1 */
FF(a, b, c, d, x[ 0], S11, 0xd76aa478); /* 1 */
FF(d, a, b, c, x[ 1], S12, 0xe8c7b756); /* 2 */
FF(c, d, a, b, x[ 2], S13, 0x242070db); /* 3 */
FF(b, c, d, a, x[ 3], S14, 0xc1bdceee); /* 4 */
FF(a, b, c, d, x[ 4], S11, 0xf57c0faf); /* 5 */
FF(d, a, b, c, x[ 5], S12, 0x4787c62a); /* 6 */
FF(c, d, a, b, x[ 6], S13, 0xa8304613); /* 7 */
FF(b, c, d, a, x[ 7], S14, 0xfd469501); /* 8 */
FF(a, b, c, d, x[ 8], S11, 0x698098d8); /* 9 */
FF(d, a, b, c, x[ 9], S12, 0x8b44f7af); /* 10 */
FF(c, d, a, b, x[10], S13, 0xffff5bb1); /* 11 */
FF(b, c, d, a, x[11], S14, 0x895cd7be); /* 12 */
FF(a, b, c, d, x[12], S11, 0x6b901122); /* 13 */
FF(d, a, b, c, x[13], S12, 0xfd987193); /* 14 */
FF(c, d, a, b, x[14], S13, 0xa679438e); /* 15 */
FF(b, c, d, a, x[15], S14, 0x49b40821); /* 16 */
/* Round 2 */
GG(a, b, c, d, x[ 1], S21, 0xf61e2562); /* 17 */
GG(d, a, b, c, x[ 6], S22, 0xc040b340); /* 18 */
GG(c, d, a, b, x[11], S23, 0x265e5a51); /* 19 */
GG(b, c, d, a, x[ 0], S24, 0xe9b6c7aa); /* 20 */
GG(a, b, c, d, x[ 5], S21, 0xd62f105d); /* 21 */
GG(d, a, b, c, x[10], S22,  0x2441453); /* 22 */
GG(c, d, a, b, x[15], S23, 0xd8a1e681); /* 23 */
GG(b, c, d, a, x[ 4], S24, 0xe7d3fbc8); /* 24 */
GG(a, b, c, d, x[ 9], S21, 0x21e1cde6); /* 25 */
GG(d, a, b, c, x[14], S22, 0xc33707d6); /* 26 */
GG(c, d, a, b, x[ 3], S23, 0xf4d50d87); /* 27 */
GG(b, c, d, a, x[ 8], S24, 0x455a14ed); /* 28 */
GG(a, b, c, d, x[13], S21, 0xa9e3e905); /* 29 */
GG(d, a, b, c, x[ 2], S22, 0xfcefa3f8); /* 30 */
GG(c, d, a, b, x[ 7], S23, 0x676f02d9); /* 31 */
GG(b, c, d, a, x[12], S24, 0x8d2a4c8a); /* 32 */
/* Round 3 */
HH(a, b, c, d, x[ 5], S31, 0xfffa3942); /* 33 */
HH(d, a, b, c, x[ 8], S32, 0x8771f681); /* 34 */
HH(c, d, a, b, x[11], S33, 0x6d9d6122); /* 35 */
HH(b, c, d, a, x[14], S34, 0xfde5380c); /* 36 */
HH(a, b, c, d, x[ 1], S31, 0xa4beea44); /* 37 */
HH(d, a, b, c, x[ 4], S32, 0x4bdecfa9); /* 38 */
HH(c, d, a, b, x[ 7], S33, 0xf6bb4b60); /* 39 */
HH(b, c, d, a, x[10], S34, 0xbebfbc70); /* 40 */
HH(a, b, c, d, x[13], S31, 0x289b7ec6); /* 41 */
HH(d, a, b, c, x[ 0], S32, 0xeaa127fa); /* 42 */
HH(c, d, a, b, x[ 3], S33, 0xd4ef3085); /* 43 */
HH(b, c, d, a, x[ 6], S34,  0x4881d05); /* 44 */
HH(a, b, c, d, x[ 9], S31, 0xd9d4d039); /* 45 */
HH(d, a, b, c, x[12], S32, 0xe6db99e5); /* 46 */
HH(c, d, a, b, x[15], S33, 0x1fa27cf8); /* 47 */
HH(b, c, d, a, x[ 2], S34, 0xc4ac5665); /* 48 */
/* Round 4 */
II(a, b, c, d, x[ 0], S41, 0xf4292244); /* 49 */
II(d, a, b, c, x[ 7], S42, 0x432aff97); /* 50 */
II(c, d, a, b, x[14], S43, 0xab9423a7); /* 51 */
II(b, c, d, a, x[ 5], S44, 0xfc93a039); /* 52 */
II(a, b, c, d, x[12], S41, 0x655b59c3); /* 53 */
II(d, a, b, c, x[ 3], S42, 0x8f0ccc92); /* 54 */
II(c, d, a, b, x[10], S43, 0xffeff47d); /* 55 */
II(b, c, d, a, x[ 1], S44, 0x85845dd1); /* 56 */
II(a, b, c, d, x[ 8], S41, 0x6fa87e4f); /* 57 */
II(d, a, b, c, x[15], S42, 0xfe2ce6e0); /* 58 */
II(c, d, a, b, x[ 6], S43, 0xa3014314); /* 59 */
II(b, c, d, a, x[13], S44, 0x4e0811a1); /* 60 */
II(a, b, c, d, x[ 4], S41, 0xf7537e82); /* 61 */
II(d, a, b, c, x[11], S42, 0xbd3af235); /* 62 */
II(c, d, a, b, x[ 2], S43, 0x2ad7d2bb); /* 63 */
II(b, c, d, a, x[ 9], S44, 0xeb86d391); /* 64 */
state[0] += a;
state[1] += b;
state[2] += c;
state[3] += d;
/* Zeroize sensitive information. */
R_memset((POINTER)x, 0, sizeof(x));
}

/*字节转换函数
output:用于输出的字符缓冲区
input:欲转换的四字节的整数形式的数组
len:output缓冲区的长度,要求是4的整数倍
*/

static void Encode(unsigned char *output, UINT4 *input,unsigned int  len)
{
unsigned int i, j;
for(i = 0, j = 0; j < len; i++, j += 4) {
  output[j] = (unsigned char)(input[i] & 0xff);
  output[j+1] = (unsigned char)((input[i] >> 8) & 0xff);
  output[j+2] = (unsigned char)((input[i] >> 16) & 0xff);
  output[j+3] = (unsigned char)((input[i] >> 24) & 0xff);
}
}

/*与上面相反的转换函数
output:保存转换出的整数
input:欲转换的字符缓冲区
len:输入的字符缓冲区的长度,要求是4的整数倍
*/

static void Decode(UINT4 *output, unsigned char *input,unsigned int  len)
{
unsigned int i, j;
for(i = 0, j = 0; j < len; i++, j += 4)
  output[i] = ((UINT4)input[j]) | (((UINT4)input[j+1]) << 8) |
   (((UINT4)input[j+2]) << 16) | (((UINT4)input[j+3]) << 24);
}

调用函数如下
BOOL CheckSerial(HWND hWnd)
{
        MD5_CTX context;
        long dtLength,lsLength;
        int i;
        TCHAR szName[MAXINPUTLEN]={0};
        TCHAR szHash[MAXINPUTLEN]={0};
        TCHAR szBuffer[MAXINPUTLEN]={0};
        TCHAR szSerial[MAXINPUTLEN]={0};
        TCHAR szTeam[]="www.pediy.com";
        TCHAR szSNtemp[20]={0};
        TCHAR szBase32[]="23456789ABCDEFGHJKLMNPQRSTUVWXYZ";
        unsigned int tcTemp=0;

        dtLength=GetDlgItemText(hWnd, IDC_Name, szName, sizeof(szName)/sizeof(TCHAR)+1);  
        if (dtLength==0)
        {
                SetDlgItemText(hWnd, IDC_Serial, "Wrong Serial!");
                return FALSE;
        }
        lsLength=GetDlgItemText(hWnd,IDC_Serial,szSerial,sizeof(szSerial)/sizeof(TCHAR)+1);
        if (lsLength!=19)
        {
                SetDlgItemText(hWnd,IDC_Serial,"Wrong Serial!");
                return FALSE;
        }
        if ((szSerial[4]!='-')||(szSerial[9]!='-')||(szSerial[14]!='-'))
        {
                SetDlgItemText(hWnd,IDC_Serial,"Wrong Serial!");
                return FALSE;
        }
        memcpy(szSNtemp,szSerial,4);
        memcpy((szSNtemp+4),(szSerial+5),4);
        memcpy((szSNtemp+8),(szSerial+10),4);
        memcpy((szSNtemp+12),(szSerial+15),4);
        MD5Init(&context);
        MD5Update(&context,szName,dtLength);
        MD5Update(&context,szTeam,lstrlen(szTeam));
        MD5Final(szHash, &context);
               
        for(i=0;i<16;i++)
        {
                tcTemp=(unsigned int)szHash[i]%32;
                szBuffer[i]=szBase32[tcTemp];
        }
        if (lstrcmp(szSNtemp,szBuffer)==0)
        {
                SetDlgItemText(hWnd,IDC_Serial,"Success!");
                return TRUE;
        }
        else
        {
                SetDlgItemText(hWnd,IDC_Serial,"Wrong Serial!");
                return FALSE;
        }
        return TRUE;
}

逆向过程分析如下:
在GetDlgItemTextA处下断点
运行后输入用户名:testt, 序列号: 01234567890ABCDEFG后运行来到断点处


跟进MD5Init(&context);如下:

然后调用void MD5Update (context, input, inputLen)
用户名地址

Context结构地址

函数调用

函数跟进
注意此时堆栈如下:
                     esp+10h        context
                     esp+14h        context (name)
                     esp+18h        inputLen

函数体如下:




下面对字符串"www.pediy.com" 做同样的处理,如下:

然后进入到MD5Final(szHash, &context)中


函数体分析如下:


然后再往下分析就特别简单了,可以字节分析。

附件: 单向散列算法分析及逆向详解.pdf

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

上传的附件:
收藏
免费 0
支持
分享
最新回复 (7)
雪    币: 341
活跃值: (143)
能力值: ( LV7,RANK:110 )
在线值:
发帖
回帖
粉丝
2
看一看 瞧一瞧
2014-10-21 17:24
0
雪    币: 8719
活跃值: (2085)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
不错,值得学习!
2014-10-21 17:24
0
雪    币: 459
活跃值: (398)
能力值: ( LV8,RANK:120 )
在线值:
发帖
回帖
粉丝
4
看看,学习
2014-10-21 17:33
0
雪    币: 3
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
看看,以为是MD5可逆了
2014-10-21 20:58
0
雪    币: 101
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
不错。。。
2014-10-21 23:51
0
雪    币: 56
活跃值: (34)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
7
不错啊,谢谢分享~
2014-10-22 08:37
0
雪    币: 517
活跃值: (35)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
8
这东西嘛,跟踪麻烦,穷举麻烦,爆破确实很容易的。用作软件注册验证,基本上只能堵住幼儿园肄业生。
2014-10-22 16:29
0
游客
登录 | 注册 方可回帖
返回
//