MD5消息摘要算法
前言:MD5消息摘要算法的学习笔记
#define _CRT_SECURE_NO_WARNINGS
#include "md5.h"
#include <stdio.h>
#include <windows.h>
int main()
{
const char* encodeData = "admin"; // 原始数据
unsigned char md5Buffer[16] = { 0 };
MD5_CTX ctx;
MD5Init(&ctx);
MD5Update(&ctx, encodeData, lstrlen(encodeData));
MD5Final(md5Buffer, &ctx);
char szBuffer[33] = { 0 };
char szTmp[3] = { 0 };
for (int i = 0; i < 16; i++)
{
sprintf(szTmp, "%02X", md5Buffer[i]);
strcat(szBuffer, szTmp);
}
printf("%s", szBuffer);
// admin -> 21232F297A57A5A743894A0E4A801FC3
return 0;
}
编译运行如下图所示
IDA识别如下
__int64 __cdecl main_0()
{
int v0; // eax
int v1; // edx
__int64 v2; // ST04_8
int i; // [esp+D0h] [ebp-C8h]
char v5; // [esp+DCh] [ebp-BCh]
__int16 v6; // [esp+DDh] [ebp-BBh]
char v7; // [esp+E8h] [ebp-B0h]
int v8; // [esp+E9h] [ebp-AFh]
int v9; // [esp+EDh] [ebp-ABh]
int v10; // [esp+F1h] [ebp-A7h]
int v11; // [esp+F5h] [ebp-A3h]
int v12; // [esp+F9h] [ebp-9Fh]
int v13; // [esp+FDh] [ebp-9Bh]
int v14; // [esp+101h] [ebp-97h]
int v15; // [esp+105h] [ebp-93h]
char v16; // [esp+114h] [ebp-84h]
char v17; // [esp+174h] [ebp-24h]
int v18; // [esp+175h] [ebp-23h]
int v19; // [esp+179h] [ebp-1Fh]
int v20; // [esp+17Dh] [ebp-1Bh]
__int16 v21; // [esp+181h] [ebp-17h]
char v22; // [esp+183h] [ebp-15h]
LPCSTR lpString; // [esp+18Ch] [ebp-Ch]
lpString = "admin";
v17 = 0;
v18 = 0;
v19 = 0;
v20 = 0;
v21 = 0;
v22 = 0;
sub_413780(&v16);
v0 = lstrlenA(lpString);
sub_4138D9(&v16, lpString, v0);
sub_413CA8(&v17, &v16);
v7 = 0;
v8 = 0;
v9 = 0;
v10 = 0;
v11 = 0;
v12 = 0;
v13 = 0;
v14 = 0;
v15 = 0;
v5 = 0;
v6 = 0;
for ( i = 0; i < 16; ++i )
{
j__sprintf(&v5, "%02X", (unsigned __int8)*(&v17 + i));
j__strcat(&v7, &v5);
}
j__printf("%s", &v7);
HIDWORD(v2) = v1;
LODWORD(v2) = 0;
return v2;
}
MD5Init初始化变量
首先是初始化变量,默认MD5算法中初始化变量分别是0x67452301,0xefcdab89,0x98badcfe,0x10325476
typedef struct {
UINT4 state[4]; /* state (ABCD) */
UINT4 count[2]; /* number of bits, modulo 2^64 (lsb first) */
unsigned char buffer[64]; /* input buffer */
} MD5_CTX;
void MD5Init (context) MD5_CTX* context; /* context */
{
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;
}
MD5Update数据处理
MD5Update的函数定义如下所示
void MD5Update (context, input, inputLen)
MD5_CTX *context; /* context */
unsigned char *input; /* input block */
unsigned int inputLen; /* length of input block */
{
unsigned int i, index, partLen;
/* Compute number of bytes mod 64 */
index = (unsigned int)((context->count[0] >> 3) & 0x3F);
/* Update number of bits */
if ((context->count[0] += ((UINT4)inputLen << 3)) < ((UINT4)inputLen << 3))
context->count[1]++;
context->count[1] += ((UINT4)inputLen >> 29); // count[1] 填充 input >> 29
partLen = 64 - index; // partLen = 64
/* Transform as many times as possible.*/
if (inputLen >= partLen) {
MD5_memcpy((POINTER)&context->buffer[index], (POINTER)input, partLen);
MD5Transform(context->state, context->buffer);
for (i = partLen; i + 63 < inputLen; i += 64)
MD5Transform(context->state, &input[i]);
index = 0;
}
else
i = 0;
/* Buffer remaining input */
MD5_memcpy((POINTER)&context->buffer[index], (POINTER)&input[i], inputLen-i);
}
MD5Transform
这里主要学习MD5Transform函数,这里继续跟到MD5Transform函数中,如下图所示
首先它是先进行Decode函数,它的作用是实现将要加密的字符串进行decode操作,实际上就是将对应的字符转换为字节数存储到数组中
接下来,首先需要知道的是FF,GG,HH,II 函数,F,G,H,I 四个辅助函数
接着就是FF,GG,HH,II 函数,FF调用F,GG调用G,HH调用H,II调用I中会将初始化的四个常数和一个随机数再加上一个32位的常数作为参数进行调用运算,其中还加上ROTATE_LEFT左移运算
#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32-(n))))
比如下面的运算过程FF调用F函数所示,可以看到FF函数就是将初始化的四个常数和一个随机数和一个32位的常数作为参数传入
每次改变的都是传入的第一个参数,这个参数也是一开始MD5_INIT初始化的其中的一个常数之一
#define FF(a, b, c, d, x, s, ac) { \
(a) += F ((b), (c), (d)) + (x) + (UINT4)(ac); \
(a) = ROTATE_LEFT ((a), (s)); \
(a) += (b); \
}
其中FF,GG,HH,II 函数中会调用F,G,H,I 四个辅助函数,这四个辅助函数的作用就是将MD5_INIT中初始化的常量其中的三个作为参数代入,进行相关的异与或运算
接着其中F函数有三个传参,它其中就会进行相关的与、或、异或的操作
#define F(x, y, z) (((x) & (y)) | ((~x) & (z)))
总共存在四个辅助函数,各个辅助函数的过程如下执行,其中的X,Y,Z分别就是MD5_INIT初始化的常量
在MD5加密的源码中的过程,如下图所示
其中FF GG HH II这四个函数总共会经过64轮,然后最终的常量会再次叠加到之前MD5_INIT初始化的变量中去
// 最后对应改完的a,b,c,d都再次被叠加到对应的初始化对应的常量中去
state[0] += a;
state[1] += b;
state[2] += c;
state[3] += d;
MD5Final
接着继续来讲MD5Final的学习过程,在MD5Final中就是来生成最终的128位
首先先调用的是Encode方法,其实跟上面的Decode方法一样
MD5Final的函数定义如下所示
void MD5Final (digest, context)
unsigned char digest[16]; /* message digest */
MD5_CTX *context; /* context */
{
unsigned char bits[8];
unsigned int index, padLen;
/* Save number of bits */
Encode (bits, context->count, 8);
/* Pad out to 56 mod 64.*/
index = (unsigned int)((context->count[0] >> 3) & 0x3f);
padLen = (index < 56) ? (56 - index) : (120 - index);
MD5Update (context, PADDING, padLen);
/* Append length (before padding) */
MD5Update (context, bits, 8);
/* Store state in digest */
Encode (digest, context->state, 16);
/* Zeroize sensitive information.*/
MD5_memset ((POINTER)context, 0, sizeof (*context));
}
关于在逆向中MD5算法的特征
所以这里md5算法的特征就有两种,第一种就是如下图所示的四个常数
在动态调试器中碰到的则如下
第二种就是通过识别其中的64个加法常数,如下图所示
在动态调试器中碰到的则如下
MD5逆向题目
题目界面如下所示
最后判断是否正确的流程则是如果你输入的Name通过MD5加密的值然后经过一次变形,如果这个变形之后的值是跟输入的序列号的值相等的话那么此时就会显示Success的提示
分析过程如下:
首先会将输入的序列号进行保存到堆栈中,然后CALL 004012B0还会进行相关的MD5的常量初始化操作
004011C4 |. 894C24 14 mov dword ptr ss:[esp+0x14],ecx ; 四个字节的序列号
004011C8 |. 894424 10 mov dword ptr ss:[esp+0x10],eax ; 四个字节的序列号
004011CC |. 8B4424 6F mov eax,dword ptr ss:[esp+0x6F]
004011D0 |. 8D8C24 280100>lea ecx,dword ptr ss:[esp+0x128] ; 用来存储MD5初始化的四个常数
004011D7 |. 895424 18 mov dword ptr ss:[esp+0x18],edx ; 四个字节的序列号
004011DB |. 51 push ecx
004011DC |. 894424 20 mov dword ptr ss:[esp+0x20],eax ; 四个字节的序列号
004011E0 |. E8 CB000000 call MD5KeyGe.004012B0 ; md5_init
接着第二次进行到MD5_UPDATE的函数的时候,它会将第二次的字符串拼接到第一次存储的字符串的后面
接着最后会将整体的字符串testwww.pediy.com
,也就是第一次字符串和第二次字符串拼接的整体进行MD5加密,可以看到下面有16个字节,也就是MD5加密
最后通过Name和序列号的字符串进行MD5加密的16个字节再进行一次变形,通过码表来对应的转换成对应的字符串
具体的过程就是取出16个字节中的一个字节然后将其与0x16之后得到一个对应的索引值,然后在23456789ABCDEFGHJKLMNPQRSTUVWXYZ
中取出对应的索引值来作为真正的密文,一共循环的次数为16次
00401236 |> /8A8C04 800100>/mov cl,byte ptr ss:[esp+eax+0x180] ; cl = 上面的一个字节
0040123D |. |83E1 1F |and ecx,0x1F ; cl & 1F
00401240 |. |40 |inc eax ; 累加计数,直到16
00401241 |. |83F8 10 |cmp eax,0x10 ; 比较是不是已经16个字节数量了
00401244 |. |8A540C 3C |mov dl,byte ptr ss:[esp+ecx+0x3C] ; 下一个存储的地址
00401248 |. |889404 0F0300>|mov byte ptr ss:[esp+eax+0x30F],dl
0040124F |.^\7C E5 \jl short MD5KeyGe.00401236
这里的话testwww.pediy.com
最后得到的结果就是VZSD-47VL-NZUQ-U2Q6
,结果如下图所示