今天是04年最后一天了,回首往昔,感概万千。
发一篇以前整理的文章,附件包含pdf格式的文章、
对象程序,程序为应一个朋友而破的一个东西。
注:文章仅为交流,有任何错误请指正。由于CRC32
存在非常多的碰撞,我这里得出的密码并不能使程序
正常往下执行,可能是还需要其它文章,或可能是
我得的密码不多,由于时间不多,没有细看。
本文仅为学习CRC32作的一个教程。
http://bbs.pediy.com/upload/file/2004/12/CRC32.part1.rar_617.rar
http://bbs.pediy.com/upload/file/2004/12/CRC32.part2.rar_632.rar
by lordor
04.12.31
密码游戏:实例解说暴力破解CRC32 密码的思路及实现
By lordor
前置知识:调试器ollydbg 的简单使用,汇编基本知识,VC 编程的基本使用。对象:电脑爱好者、软件破解爱好者、逆向工程爱好者工具:ollyDbg1.1 英文版,VC.NET 、PEID0.92 、Hex WorkShop4.23 目的:找到密码,激活灰色按键。
说明:请打开附件带的程序PKProc_98.exe ,运行这个程序时要输入密码才能激活确定按键,现在我们就要找到密码,本文将介绍怎么找到程序的处理加密的过程,怎么用VC 编写穷举程序进行密码的破解。
一、分析程序密码验证过程先用PEID 检测一下程序编写语言,显示:Install Stub 32-bit -> InstallShield [Overlay] ,这样可以用OllyDbg 直接反汇编,也可以动态跟踪。
这个程序的反汇编语句比较好读,流程清晰,有兴趣的朋友可以动态跟踪它的加载过程。这里我们用API 断点法定位关键代码。由于程序是要密码正确才能激活确定按键,我们可以用EnableWindow 下断。用OllyDbg 载入程序,在命令行下输入bp EnableWindow( 注意字母大小写),然后F9 直接让程序运行起来,在密码框中附件输入一个字符,马上会中断在ollydbg 中,按一下Alt+F9 返回到程序的代码中,往上看就是程序的验证算法了。我们看一下这些代码
100071EE CALL DWORD PTR DS:[<&USER32.GetDlgItemTe>; \GetDlgItemTextA
100071F4 LEA ECX,DWORD PTR SS:[EBP-200] //取得输入字符
100071FA PUSH ECX ; /String
100071FB CALL DWORD PTR DS:[<&KERNEL32.lstrlenA>] //求字符长度
10007201 LEA EDX,DWORD PTR DS:[EAX+1] //字符长度加1
10007204 LEA ECX,DWORD PTR SS:[EBP-200] //输入字符串地址入ECX
1000720A CALL ginstall.10002F73 //关键验证函数,得检验值入EAX
1000720F MOV ECX,DWORD PTR DS:[1000BCA8] //1000BCA8 处的值0EE2AF34 赋给ECX
10007215 SUB ECX,EAX //执行减操作,这里改变CF 标志
10007217 CMP ECX,1 //比较是否为1
1000721A MOV ECX,3EF
1000721F SBB EAX,EAX //相当于:EAX=EAX-(EAX+1)
10007221 NEG EAX //相当于:EAX=-EAX
10007223 PUSH EAX //压入EAX
10007224 CALL ginstall.10006497 //取得窗口HWND 值
10007229 PUSH EAX ; |hWnd //压入HWND
1000722A CALL DWORD PTR DS:[<&USER32.EnableWindow>; //这里执行否要激活
10007230 POP ESI
大家可以看到这个过程比较清晰,也就是先取得输入的字符串,通过CALL ginstall.10002F73 求这串字符串的CRC32( 后面我们会说),然后与[1000BCA8] 处的值比较,如果相等,则会执行EAX 值是1 还是0,这个值就是EnableWindow 的是否激活参数。我们再进CALL ginstall.10002F73 看看这个CRC32 算法,过程也不长,先记住执行这个CALL 传入的两个参数,即EDX 为字符串的长度,ECX 为字符的地址:
10002F73 PUSH ESI //保存
10002F74 MOV EAX,-1 //EAX=FFFFFFFF
10002F79 PUSH EDI //保存
10002F7A MOV ESI,EDX //字符串长度入ESI
10002F7C ADD EDX,EAX //EDX=EDX+EAX,长度减1
10002F7E TEST ESI,ESI //看ESI 是否为0,即是否有字符
10002F80 JE SHORT ginstall.10002FA6 //为0 则直接跳走
10002F82 /MOVZX ESI,BYTE PTR DS:[ECX] //取一位字符入ESI
10002F85 |MOVZX EDI,AX //EDI=AX
10002F88 |SHR EAX,8 //EAX 右移8 位
10002F8B |XOR ESI,EDI //EDI 与一位字符作异或运算
10002F8D |AND ESI,0FF //ESI 取低8 位
10002F93 |INC ECX //字符地址加一
10002F94 |MOV ESI,DWORD PTR DS:[ESI*4+1000EBE0] //以1000EBE0 为基址,读入4 位字节
10002F9B |XOR ESI,EAX //ESI=ESI XOR EAX
10002F9D |MOV EAX,ESI //保存ESI 到EAX 中
10002F9F |MOV ESI,EDX //ESI=EDX
10002FA1 |DEC EDX //长度减一
10002FA2 |TEST ESI,ESI //长度是否为0
10002FA4 \JNZ SHORT ginstall.10002F82 //字符长度不为0,则继续循环
10002FA6 NOT EAX //执行NOT 操作
10002FA8 POP EDI
10002FA9 POP ESI
10002FAA RETN 这个Call 是一个变形的CRC32,1000EBE0 处存放的是CRC32 用到的256 个4 个字节长的表,其表值与标准的CRC32 表值不一样,在后面我也看到,作者修改的这个表值使得CRC32 算法安全性大大降低。更详细的CRC32 算法说明请大家参考相关的资料。
二、分离出主要验证关键函数从上面的分析,我们已经清楚程序的验证过程了。由于CRC32 属于散列值,我们没方法据CRC32 的值求得原先的值,所以必须穷举所有可能的密码串的CRC32 值,比较是否与预定的值相等。为了更可靠的穷举,我们要从以上分析的代码中生成我们的关键函数:求CRC32 算法。
首先可以把这段汇编代码拷贝出来,只要在VC 中嵌入汇编代码就可以使用了。其次因为1000EBE0 处存放着表,所以必须要把1000EBE0 处的内容拷贝出来,我是用IsDebuggerPresent 插件中的DUMP 工具保存下来的。为什是400 的长度?因为256*4=1024 个字节,转换为十六进制则为400。然后用Hex WorkShop 打开dump 出来的文件,先择所有的字节,再点击“EDIT/Copy As/C Source”,再在VC 的文件中粘贴,就会生成适合VC 使用的数据了。最后构成关键代码如下:
//功能:求输入字符串的crc32值
//参数:name为字符串地址,len为字符串的长度
//返回:CRC32值
DWORD GetCrc(char* name,size_t len) {
//edx=字符串的长度
//ecx=字符串地址
DWORD dwcrc;
__asm{
mov edx,len;
mov ecx,name[0];
//PUSH ESI;
MOV EAX,-1;
//PUSH EDI;
MOV ESI,EDX;
ADD EDX,EAX;
TEST ESI,ESI;
JE SHORT _10002FA6;
_10002F82:
MOVZX ESI,BYTE PTR DS:[ECX];
MOVZX EDI,AX;
SHR EAX,8;
XOR ESI,EDI;
AND ESI,0xFF;
INC ECX;
MOV ESI,DWORD PTR DS:[ESI*4+basetable];
XOR ESI,EAX;
MOV EAX,ESI;
MOV ESI,EDX;
DEC EDX;
TEST ESI,ESI;
JNZ SHORT _10002F82;
_10002FA6:
NOT EAX ;==>必须等于 eax=0EE2AF34
mov dwcrc,eax;
//POP EDI;
//POP ESI;
}
if(dwcrc==0x0EE2AF34) {
char buff[256];
wsprintf(buff,"Find the Key:%s",name);
MessageBoxA(NULL,buff,"Succeed!",MB_OK|MB_TOPMOST);
}
return dwcrc;
}
说明:其中basetable 定义为数组:unsigned char basetable[1024] ,内容就是上面拷贝出来的数据;0x0EE2AF34
为程序中正确的密码的CRC32值。
三、设计我们的穷举程序
有了前面的分析,后面的工作就简单了,现在我们来穷举长度为10 位以内的密码,编写代码如下。下
面的代码只是为了方面说明,没有采用其它算法,也没有作算法及代码的优化。
void CbrutforceDlg::OnBnClickedBegin() {
// TODO: Add your control notification handler code here
char BaseCode[]="0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; //可见的字符36
size_t BaseCodeLen=strlen(BaseCode); //长度
char PassWord[256]; //定义测试的密码串,并初始化为0
memset(PassWord,0x0,256);
size_t a1,a2,a3,a4,a5,a6,a7,a8,a9,a10; //定义密码下标位
for(a1=0;a1<BaseCodeLen;a1++) //这里穷举10位的密码
{
//求长度为1位的密码
PassWord[0]=BaseCode[a1];
PassWord[1]=0x0;
GetCrc(PassWord,2);
for(a2=0;a2<BaseCodeLen;a2++) {
//求长度为2位的密码
PassWord[1]=BaseCode[a2];
PassWord[2]=0x0;
GetCrc(PassWord,3);
for(a3=0;a3<BaseCodeLen;a3++) {
//求长度为3位的密码
PassWord[2]=BaseCode[a3];
PassWord[3]=0x0;
GetCrc(PassWord,4);
for(a4=0;a4<BaseCodeLen;a4++) {
//求长度为4位的密码
PassWord[3]=BaseCode[a4];
PassWord[4]=0x0;
GetCrc(PassWord,5);
for(a5=0;a5<BaseCodeLen;a5++) {
//求长度为5位的密码
PassWord[4]=BaseCode[a5];
PassWord[5]=0x0;
GetCrc(PassWord,6);
for(a6=0;a6<BaseCodeLen;a6++) {
//求长度为6位的密码
PassWord[5]=BaseCode[a6];
PassWord[6]=0x0;
GetCrc(PassWord,7);
for(a7=0;a7<BaseCodeLen;a7++) {
//求长度为7位的密码
PassWord[6]=BaseCode[a7];
PassWord[7]=0x0;
GetCrc(PassWord,8);
for(a8=0;a8<BaseCodeLen;a8++) {
//求长度为8位的密码
PassWord[7]=BaseCode[a8];
PassWord[8]=0x0;
GetCrc(PassWord,9);
for(a9=0;a9<BaseCodeLen;a9++) {
//求长度为9位的密码
PassWord[8]=BaseCode[a9];
PassWord[9]=0x0;
GetCrc(PassWord,10);
for(a10=0;a10<BaseCodeLen;a10++) {
//求长度为10位的密码
PassWord[9]=BaseCode[a10];
PassWord[10]=0x0;
GetCrc(PassWord,11);
}
}
}
}
}
}
}
}
}
}
AfxMessageBox("Finish!");
}
算法说明:定义所有的可见字符、测试的密码串及密码串的下标,过程就是通过循环来穷举。详细的请看源代码。
现在开始编译一下程序,运行程序就开始进行密码穷举了。在我的机器AMD Athlon 1.8G+512M DDR333 的机器下,十几分钟就会找到密码。按确定后还可以继续查找下一个密码,由于这个算法存在的漏洞,半个小时内会找到好几个。注意:由于穷举很耗系统资源,运行程序后,CPU 会占用100%。看看成功的画面.
总结:对于这个分析的过程来说,首先要找到处理密码的地方,并找出加密判断的地方,如本例找到CRC32 加密后值,再后模拟它的过程,对密码进行暴力破解。这种方法可能推广使用到其它地方,如WIPZIP/WINRAR 加密码,Word 文档加密码,还有如Md5 的暴力破解等场合。对于要破解的密码来说,关键是要找到要处理密码的函数,以及判断密码是否正确的地方,而穷举过程来说,关键是对穷举算法的设计,达到缩短穷举算法的时间。
不过对于穷举来说,目前还没有一个很好的思路来设计一个穷举算法。如果大家有好的建议请发邮件给我
关于我:网名:lordor
Mail: lordor@163.com
QQ:88378557
介绍:自由撰稿人。喜欢系统底层、密码学、程序逆向分析及软件加密。目前兴趣方向:嵌入式操作系统、移动操作平台等
于04.12.31
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)