前阵子应要求解密一个Access数据库文件。
已知的是数据库密码存放在数据库 mdb 文件中,偏移量为 0x42处 。数据库文件的版本信息存放在 mdb 文件偏移量为 0x14
处的一字节 , 值为0 代表 Access 97的数据库文件,值为1代表Access 2k 及 Access 03 的数据库文件。
以上就是我得到的信息。分析思路:既然已经知道了数据库密码存放的方式以及偏移量,说明在校验输入的密码是否正确的过程中一定会对存储的密码数据进行访问。只需要对 ReadFile 读取进来的数据,偏移量为 0x42处的一字节下硬件访问断点,不断的跟踪数据就可以得到密码验证过程了。
好了,有了思路后,开始分析。数据库文件得到后,双击打开,提示输入密码。OD 附加到 Access 97 ,在 ReadFile
下断点,输入密码点击确定后,在ReadFile断下了,获得了缓冲区地址参数后,Ctrl+F9 运行到返回,得到读取的数据内容,
并在0x42处下一字节的硬件访问断点。 F9 继续运行,断下,发现在msjet35.dll 模块的 4047647 函数内 断下,暂且把这个函数
称为 Decrypt_S1 (short for Decrypt Stage 1) , 根据IDA的静态分析,并动态调试追踪函数的参数, 分析得到这个函数共有
4个参数,其中两个是用 栈传递的,还有两个是用寄存器传递的。
.text:04047851 push 7Eh
.text:04047853 lea eax, [ebp+18h]
.text:04047856 lea ecx, [esp+224h+init_key]
.text:0404785D push eax
.text:0404785E lea esi, [ebp+42h]
.text:04047861 call Decrypt_S1
第一个参数是读取的数据库文件的 +0x18 偏移地址,第二个参数是解密次数,第三个参数ecx是前面调用函数初始化
的一个解密数据 init_key,第四个参数是数据库文件的 +0x42 偏移地址。
其中未知的是第三个参数的值是否是变化的,我进行多次的调试分析,得到结论是:解密任何数据库用的解密数据
init_key 是一样的。 解密函数 Decrypt_S1 的算法非常简单,就是异或而已。以下是我重写的解密过程代码:
void Decrypt_s1_97(char * buf,int offset)
{
unsigned char *esi=init_key+6;
char *edi=buf+offset;
unsigned char bl=0,dl=0,dh=0;
bl=init_key[4];
dl=init_key[5];
bl++;
unsigned char al=0;
int ebp=0x7e; //解密长度
unsigned char cl=esi[bl];
unsigned char ch;
int max=0;
do{
dl=dl+cl;
ch=*edi; //ch 由源文件读取
edi++;
al= esi[dl] ; //经过多次调试发现,代码没错……错的是没有吧 init_key 初始化完整……
esi[dl ]=cl;
esi[bl ]=al ;
al+=cl;
bl++;
ch ^= esi[al]; //al的值没错,错的是 在al偏移位置的值 ……气死了,我复制更长的 init_key 结果就能得到密码了。。。
--ebp;
*(edi-1)=ch;
cl=esi[(int)bl]; //cl 从init_key 读取
}while(ebp);
init_key[4]=0;
init_key[5]=dl;
}
代码写得少,比较丑,见谅~
################################################################################
好了,有了前面的分析思路,对于 Access 2k 以及 Access 03 也是一样的分析方法,只是 2k 跟 03 的数据库多了
一个 Decrypt_s2 函数而已,这个函数也很简单,只是调用 floor() 函数,用返回值再对 Decrypt_s1 得到的密码在进行
一轮的异或解密而已。
void Decrypt_s2(char * buf,int offset)
{
double x= *((double *) &buf[0x72]); //数据类型为 double 8字节 ,float 4字节
factor=floor(x); //floor(double x)
int *target=(int*)(buf+offset);
for(int i=0;i<10;i++)
*(target+i)^=factor;
}
ok ,以上就是我对Access 数据库 97 ~ 03 的密码分析过程,第一次写这种文章,写的很烂,见谅哈哈。
下面附上我写的解密源码,Dev C++可以直接编译运行:
#include<stdio.h>
#include<stdlib.h>
#include<memory.h>
#include<math.h>
#define readSize 0x1000
unsigned char init_key[]={
0xC7, 0xDA, 0x39, 0x6B, 0x00, 0x00, 0x4E, 0xA2, 0xDD, 0x43, 0x16, 0xD0, 0x34, 0xBE, 0x26, 0x60,
0x9B, 0x11, 0x56, 0xAE, 0x12, 0x8C, 0xF6, 0x22, 0x7C, 0xCB, 0x4D, 0xCD, 0x8D, 0xF1, 0x5E, 0x27,
0x52, 0x1D, 0x24, 0x3E, 0x72, 0x3C, 0xE3, 0xFD, 0xC8, 0x00, 0xAA, 0x46, 0xAD, 0x38, 0x89, 0x5D,
0x6D, 0x85, 0x78, 0x71, 0xE6, 0x80, 0x77, 0x82, 0xCC, 0x53, 0x09, 0xDB, 0x79, 0x69, 0x6F, 0x73,
0x50, 0x9E, 0x49, 0x5A, 0x42, 0x23, 0x4C, 0x55, 0xF2, 0xEB, 0xD4, 0x15, 0x98, 0x47, 0x33, 0x1E,
0x1F, 0xC4, 0xF0, 0x35, 0x1A, 0xA8, 0x4A, 0x7B, 0x18, 0x10, 0xEE, 0x7D, 0xE4, 0x40, 0x0A, 0x6B,
0x61, 0x9A, 0x66, 0x70, 0x93, 0xE2, 0x58, 0x01, 0x19, 0xB8, 0x83, 0xBD, 0xBF, 0x04, 0xF4, 0x2C,
0xDA, 0x59, 0x3A, 0xEF, 0x97, 0xAB, 0x5F, 0x03, 0x84, 0x48, 0xCE, 0x37, 0xFA, 0xCA, 0x8E, 0x9C,
0xE9, 0xCF, 0x8F, 0x02, 0xF8, 0x5B, 0x20, 0xA3, 0xD5, 0xFB, 0xE0, 0xE5, 0xA4, 0x17, 0x2B, 0xD2,
0x06, 0x68, 0x5C, 0xB1, 0x6C, 0xFC, 0x2D, 0xE1, 0x25, 0x3F, 0xF5, 0x2A, 0x88, 0x28, 0xB7, 0xB9,
0x0B, 0x1C, 0x32, 0x7E, 0x29, 0xFF, 0x92, 0xDC, 0x07, 0xC5, 0x90, 0xC6, 0xB0, 0xC3, 0x8A, 0xB5,
0x08, 0xB2, 0xE8, 0x75, 0x31, 0xA1, 0x57, 0xC1, 0x30, 0xC9, 0x91, 0xD3, 0xBA, 0x0C, 0x6A, 0x36,
0xB3, 0x54, 0x6E, 0x63, 0xA6, 0x44, 0x1B, 0xC0, 0x2E, 0x45, 0x7F, 0x99, 0x7A, 0x4F, 0x39, 0xC2,
0xAF, 0xA0, 0xED, 0xD9, 0x3D, 0xEA, 0x14, 0x21, 0xE7, 0xAC, 0xB4, 0xF3, 0x51, 0x9F, 0xD1, 0xD7,
0x8B, 0xFE, 0x76, 0xA7, 0xBB, 0x9D, 0x0E, 0xA5, 0x81, 0x64, 0x95, 0xF9, 0x2F, 0x62, 0x94, 0x05,
0x0F, 0x87, 0x4B, 0xD6, 0xC7, 0xA9, 0x74, 0x96, 0x41, 0x13, 0xD8, 0xF7, 0x65, 0xB6, 0xBC, 0xEC,
0x86, 0xDF, 0x3B, 0xDE, 0x67, 0x0D, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
int factor=0xa926; //第二次解密密钥 ,不同密码值不同。。。我靠
//--------------------------03 破解函数
void Decrypt_s1(char * buf,int offset) ;
void Decrypt_s2(char * buf,int offset);
void Decrypt_03(char * buf); //包装函数
void show(char *buf) ; //输出密码
//---------------------------97 破解函数
void Decrypt_s1_97(char * buf,int offset) ;
void show_97(char *buf) ;
void Decrypt_97(char * buf); //包装函数
void show(char * buf); //输出密码
int main(int argc,char **argv)
{
FILE *fp;
if(argc !=2 )
{
printf("Access 97 ~ 2003 password recover\nUsage:%s dbFileName.mdb\n",argv[0]);
return 0;
}
if((fp=fopen(argv[1],"r")) == 0)
{
printf("Could not open file %s\n",argv[1]);
return 0;
}
char *buf=(char*)malloc(readSize*sizeof(char));
memset(buf,0,readSize);
fread(buf,readSize,1,fp);
fclose(fp);
if(buf[0x14] == 0) //97 版本
Decrypt_97(buf);
else if(buf[0x14] == 1) //03 版本
Decrypt_03(buf);
free(buf);
}
void Decrypt_s1(char * buf,int offset)
{
unsigned char *esi=init_key+6;
char *edi=buf+offset;
unsigned char bl=0,dl=0,dh=0;
bl=init_key[4];
dl=init_key[5];
bl++;
unsigned char al=0;
int ebp=0x80;
unsigned char cl=esi[bl];
unsigned char ch;
int max=0;
do{
dl=dl+cl;
ch=*edi; //ch 由源文件读取
edi++;
al= esi[dl] ; //经过多次调试发现,代码没错……错的是没有吧 init_key 初始化完整……
esi[dl ]=cl;
esi[bl ]=al ;
al+=cl;
bl++;
ch ^= esi[al]; //al的值没错,错的是 在al偏移位置的值 ……气死了,我复制更长的 init_key 结果就能得到密码了。。。
--ebp;
*(edi-1)=ch;
cl=esi[(int)bl]; //cl 从init_key 读取
}while(ebp);
init_key[4]=0;
init_key[5]=dl;
}
void Decrypt_s2(char * buf,int offset)
{
double x= *((double *) &buf[0x72]); //数据类型为 double 8字节 ,float 4字节
factor=floor(x); //floor(double x)
int *target=(int*)(buf+offset);
for(int i=0;i<10;i++)
*(target+i)^=factor;
}
void show(char *buf)
{
printf("password is :\n" );
char tmp[21];
memset(tmp,0,21);
for(int i=0;i<20 ;i++)
{
if(buf[0x42+i*2] !=0 )
tmp[i]=buf[0x42+i*2];
else
break;
}
printf("%s",tmp);
}
void Decrypt_s1_97(char * buf,int offset)
{
unsigned char *esi=init_key+6;
char *edi=buf+offset;
unsigned char bl=0,dl=0,dh=0;
bl=init_key[4];
dl=init_key[5];
bl++;
unsigned char al=0;
int ebp=0x7e; //解密长度
unsigned char cl=esi[bl];
unsigned char ch;
int max=0;
do{
dl=dl+cl;
ch=*edi; //ch 由源文件读取
edi++;
al= esi[dl] ; //经过多次调试发现,代码没错……错的是没有吧 init_key 初始化完整……
esi[dl ]=cl;
esi[bl ]=al ;
al+=cl;
bl++;
ch ^= esi[al]; //al的值没错,错的是 在al偏移位置的值 ……气死了,我复制更长的 init_key 结果就能得到密码了。。。
--ebp;
*(edi-1)=ch;
cl=esi[(int)bl]; //cl 从init_key 读取
}while(ebp);
init_key[4]=0;
init_key[5]=dl;
}
void show_97(char *buf)
{
printf("password is :\n" );
char tmp[21];
memset(tmp,0,21);
for(int i=0;i<20 ;i++)
{
if(buf[i+0x42] !=0 )
//printf("%c",buf[0x42+i*2]);
tmp[i]=buf[0x42+i];
else
break;
}
printf("%s",tmp);
}
void Decrypt_97(char * buf)
{
Decrypt_s1_97(buf,0x18);
show_97(buf);
}
void Decrypt_03(char * buf)
{
//先用 init_key key 解密 0x18 偏移起始的 0x80 字节
Decrypt_s1(buf,0x18);
//第二次用 factor 解密 0x42 偏移起始的 0x2a 字节的密码
Decrypt_s2(buf,0x42);
show(buf);
}