破解这个软件时,感觉比较有意思,写下总结,欢饮提出自己的见解,相互学习。
载入运行,并输入name:duan password:123456,如图,显示Wrong。
peid检查没有壳,并得到里面含有base64,DES,MD5算法;正好可以锻炼下才学的密码学。
OD载入直接运行后会直接结束,说明有反调试,检测到了调试器,重新载入看看能不能找到刚显示的字符串,
并没有找到,关键信息隐藏了,不过显示了很多逆向工具的名称,说明程序检测了这些调试器是否运行,还有“A——+/”的字符串这确实是base64的特点。分别到ollydbg.exe和OllyDbg的调用处。后来用IDA才发现,程序的每次载入的内存都不一样,所以这里的地址仅作参考。
00361AD0 /$ 55 push ebp ; 反调试1
00361AD1 |. 8BEC mov ebp,esp
…………
00361AF2 |. E8 4F240000 call <jmp.&KERNEL32.CreateToolhelp32Snap>; \CreateToolhelp32Snapshot
00361AF7 |. 8BF8 mov edi,eax
00361AF9 |. 8D85 D4FEFFFF lea eax,[local.75]
00361AFF |. 50 push eax ; /lppe
00361B00 |. 57 push edi ; |hSnapshot
00361B01 |. E8 3A240000 call <jmp.&KERNEL32.Process32First> ; \Process32First
…………
00361B21 |. B9 DC523600 |mov ecx,crackme6.003652DC ; ollyice.exe
…………
00361B57 |. 0F84 CC000000 |je crackme6.00361C29
00361B5D |. B9 D0523600 |mov ecx,crackme6.003652D0 ; olydbg.exe
00361B62 |. 8D85 F8FEFFFF |lea eax,[local.66]
…………
00361B8F |. 0F84 94000000 |je crackme6.00361C29
00361B95 |. B9 C4523600 |mov ecx,crackme6.003652C4 ; peid.exe
00361B9A |. 8D85 F8FEFFFF |lea eax,[local.66]
…………
00361BC7 |. 74 60 |je Xcrackme6.00361C29
00361BC9 |. B9 B8523600 |mov ecx,crackme6.003652B8 ; lda.exe
…………
00361BFB |. 74 2C |je Xcrackme6.00361C29
00361BFD |. 8D95 D4FEFFFF |lea edx,[local.75]
00361C03 |. 52 |push edx ; /lppe
00361C04 |. 57 |push edi ; |hSnapshot
00361C05 |. E8 30230000 |call <jmp.&KERNEL32.Process32Next> ; \Process32Next
00361C0A |. 85C0 |test eax,eax
00361C0C |.^ 0F85 03FFFFFF \jnz crackme6.00361B15
00361C12 |. 5E pop esi
00361C13 |> 57 push edi ; /hObject
00361C14 |. FF15 08503600 call dword ptr ds:[<&KERNEL32.CloseHandl>; \CloseHandle
…………
00361C28 |. C3 retn
遍历进程看是否有如上调试器运行有则结束,看来不光检测OD。
00361920 /$ 56 push esi ; 反调试2
…………
0036192A |. 68 98523600 push crackme6.00365298 ; |OllyDbg
0036192F |. FFD6 call esi ; \FindWindowA
…………
00361943 |. 68 90523600 push crackme6.00365290 ; |1212121
…………
00361956 |. 68 88523600 push crackme6.00365288 ; |icu_dbg
0036195B |. FFD6 call esi ; \FindWindowA
…………
00361967 |. 6A 00 push 0x0 ; /Title = NULL
00361969 |. 68 80523600 push crackme6.00365280 ; |pe--diy
…………
0036197A |. 6A 00 push 0x0 ; /Title = NULL
0036197C |. 68 78523600 push crackme6.00365278 ; |odbydyk
…………
003619A0 |. 6A 00 push 0x0 ; /Title = NULL
003619A2 |. 68 5C523600 push crackme6.0036525C ; |TIdaWindow
…………
003619B5 |. 68 54523600 push crackme6.00365254 ; |TESTDBG
…………
003619D9 |. 6A 00 push 0x0 ; /Title = NULL
003619DB |. 68 48523600 push crackme6.00365248 ; |Shadow
…………
00361A01 |. 68 34523600 push crackme6.00365234 ; |PEiD v0.94
…………
00361A14 \. C3 retn
第二处检测有没有上述的窗口打开 有则结束,这两处,直接用十六进制编辑器把字符串改一下,就可以通过。
在这两个函数入口设断即可断下,在入口函数处即可发现三次函数调用。
…………
00181E40 . 00E8 add al,ch
00181E42 . E8 89FCFFFF call crackme6.00181AD0 ;反调试1 上面
00181E47 . E8 D4FAFFFF call crackme6.00181920 ;反调试2 上面
00181E4C . E8 EFFDFFFF call crackme6.00181C40
第三个函数也是反调试:
00181C40 |$ 55 push ebp
00181C41 |. 8BEC mov ebp,esp
00181C43 |. 51 push ecx
00181C44 |. C745 FC 00000>mov [local.1],0x0
00181C4B |. 64:A1 3000000>mov eax,dword ptr fs:[0x30] ; 反调试3
00181C51 |. 3E:0FB640 02 movzx eax,byte ptr ds:[eax+0x2] ;判断peb+0x2处的BeingDebugged
00181C56 |. 8945 FC mov [local.1],eax
IDA载入,能直接来到main处,虽然程序运行时地址是动态的,但可以计算偏移,在OD中找到main的入口。设断。程序先运行完三个反调试函数,再到main。
用IDA发现里面调用CreateThread函数创建子线程,
.text:00402966 push ecx
.text:00402967 push edx
.text:00402968 call Print_401EE0 ;打印输出
.text:0040296D add esp, 8
.text:00402970 mov ecx, eax
.text:00402972 call ds:??6?$basic_ostream@DU?$char_traits@D@std@@@std@@QAEAAV01@P6AAAV01@AAV01@@Z@Z ; std::basic_ostream<char,std::char_traits<char>>::operator<<(std::basic_ostream<char,std::char_traits<char>> & (*)(std::basic_ostream<char,std::char_traits<char>> &))
.text:00402978 push ebx ; lpThreadId
.text:00402979 push ebx ; dwCreationFlags
.text:0040297A push ebx ; lpParameter
.text:0040297B push offset StartAddress ; lpStartAddress
.text:00402980 push ebx ; dwStackSize
.text:00402981 push ebx ; lpThreadAttributes
.text:00402982 call ds:CreateThread
.text:00402988
.text:00402988 loc_402988: ; CODE XREF: _main+E1j
.text:00402988 call Fan1_401AD0 ; 遍历进程看是否有od,ice,IDA,peid运行
.text:0040298D call Fan2_401920 ; 查看是否有逆向工具
.text:00402992 mov [ebp+var_1C], ebx
.text:00402995 mov eax, large fs:30h
可以看出主线程在创建线程后,一直循环调用反调试函数1,2来检测。在子线程处设断,当停在子线程入口处时,将主线程挂起。
……
00182538 |. 64:A3 0000000>mov dword ptr fs:[0],eax
0018253E |. 8B0D 8C501800 mov ecx,dword ptr ds:[<&MSVCP100.std::ci>; MSVCP100.std::cin
00182544 |. 8D45 C4 lea eax,[local.15]
00182547 |. 50 push eax
00182548 |. 51 push ecx
00182549 |. E8 E2FBFFFF call crackme6.00182130 ; 输入
0018254E |. 83C4 08 add esp,0x8
00182551 |. E8 1AF7FFFF call crackme6.00181C70 ; 有检测,进去改一下跳转不要让它结束。
00182556 |. 6A 08 push 0x8
继续往下:
00182583 |. 8D70 01 lea esi,dword ptr ds:[eax+0x1]
00182586 |> 8A10 /mov dl,byte ptr ds:[eax]
00182588 |. 40 |inc eax
00182589 |. 3AD3 |cmp dl,bl
0018258B |.^ 75 F9 \jnz Xcrackme6.00182586 ;循环计算name长度
0018258D |. 2BC6 sub eax,esi
0018258F |. 50 push eax ; size
00182590 |. 8D45 C4 lea eax,[local.15]
00182593 |. 50 push eax ; buff
00182594 |. E8 67EFFFFF call crackme6.00181500 ; 转成ASICC
00182599 |. 8D8D 3CFFFFFF lea ecx,[local.49]
0018259F |. 51 push ecx
001825A0 |. E8 6B0D0000 call crackme6.00183310 ; 有转换 MD5
跟踪参数返回的结果,这样可以省去分析代码的时间,查看结果为 5d676628e56b33c125c3e2f04656afdd,就是上述三种算法的一种,在网上在线MD5解密后为duan,说明就是MD5加密了。接下来看:
001825A5 |. 83C4 20 add esp,0x20
001825A8 |. 8BC8 mov ecx,eax
001825AA |. 895D FC mov [local.1],ebx
001825AD |. E8 1EFFFFFF call crackme6.001824D0 ; 有转换返回的结果为25c3e2f0 刚好是上面MD5加密后的值的一部分
001825B2 |. 3978 14 cmp dword ptr ds:[eax+0x14],edi
001825B5 |. /72 02 jb Xcrackme6.001825B9
001825B7 |. |8B00 mov eax,dword ptr ds:[eax]
001825B9 |> \50 push eax ; |src
001825BA |. 8D55 E4 lea edx,[local.7] ; |
001825BD |. 52 push edx ; |dest
001825BE |. FF15 D8501800 call dword ptr ds:[<&MSVCR100.strncpy>] ; \strncpy 将有name变换得来的key1=25c3e2f0换个位置
001825C4 |. 83C4 0C add esp,0xC
001825C7 |. 39BD 6CFFFFFF cmp [local.37],edi
接着往下面回来到第二个输入调用;
00182667 |. 8D45 9C lea eax,[local.25]
0018266A |. 50 push eax
0018266B |. 51 push ecx
0018266C |. E8 BFFAFFFF call crackme6.00182130 ; scanf 输入123456
00182671 |. 83C4 08 add esp,0x8
00182674 E8 A7F3FFFF call crackme6.00181A20 ; 禁止鼠标键盘
这个函数跟进去会调用BlockInput函数禁止鼠标,来阻止下断点之类,所以nop掉,不让其调用。
接下来,调用了四次xxxxxx600函数 应该是base64,没发现des的特征,观察参数的变化。
001826B0 |. 50 push eax
001826B1 |. 8D8D 3CFFFFFF lea ecx,[local.49]
001826B7 |. 51 push ecx
001826B8 |. C745 FC 01000>mov [local.1],0x1
001826BF |. E8 3CEFFFFF call crackme6.00181600 ;参数1为返回数据的地址
由123456得到的返回值由上,不是个字符串,继续试了试下面的三个函数结果返回的数据为0,然后得到最后的0数据用MD5加密,得到的数据就是定值,无论输入什么都是0的MD5值,这样password就失去了作用,显然password输入有问题。用iDA分析该函数也比较麻烦,最后还是将123456在网上用base64工具编码解码试试,结果发现编码的值显然不是上面这个,不过解码后都不是正常的字符串,若是解码,那就要解码4次,于是用1在网上编码四次得到VkZaRk9WQlJQVDA9,作为password输入。这次调用一次后得到的字符串正常。
001826C4 |. 50 push eax
001826C5 |. 8D95 CCFEFFFF lea edx,[local.77]
001826CB |. 52 push edx
001826CC |. C645 FC 02 mov byte ptr ss:[ebp-0x4],0x2
001826D0 |. E8 2BEFFFFF call crackme6.00181600
001826D5 |. 50 push eax
001826D6 |. 8D85 20FFFFFF lea eax,[local.56]
001826DC |. 50 push eax
001826DD |. C645 FC 03 mov byte ptr ss:[ebp-0x4],0x3
001826E1 |. E8 1AEFFFFF call crackme6.00181600
001826E6 |. 50 push eax
001826E7 |. 8D8D E8FEFFFF lea ecx,[local.70]
001826ED |. 51 push ecx
001826EE |. C645 FC 04 mov byte ptr ss:[ebp-0x4],0x4
001826F2 |. E8 09EFFFFF call crackme6.00181600
001826F7 |. 83C4 20 add esp,0x20
四次调用完后,字符串依次为VFZFOVBRPT0=,TVE9PQ=,MQ==,1,说明此函数就是base64解码函数。
一直往下会来到类似name处理的代码:
0009280C |. 2BC6 sub eax,esi
0009280E |. 50 push eax
0009280F |. 8D95 74FFFFFF lea edx,[local.35]
00092815 |. 52 push edx
00092816 |. E8 E5ECFFFF call crackme6.00091500 ; 密码长度1
0009281B |. 8D85 04FFFFFF lea eax,[local.63]
00092821 |. 50 push eax
00092822 |. E8 E90A0000 call crackme6.00093310 ;MD5加密1的MD5值为:c4ca4238a0b923820dcc509a6f75849b
00092827 |. 83C4 20 add esp,0x20
0009282A |. 8BC8 mov ecx,eax
0009282C |. C745 FC 05000>mov [local.1],0x5
00092833 |. E8 98FCFFFF call crackme6.000924D0 ;去MD5值中的某几位key2=a0b92382
00092838 |. 3978 14 cmp dword ptr ds:[eax+0x14],edi
0009283B |. 72 02 jb Xcrackme6.0009283F
0009283D |. 8B00 mov eax,dword ptr ds:[eax]
0009283F |> 50 push eax ; |src
00092840 |. 8D4D D8 lea ecx,[local.10] ; |
00092843 |. 51 push ecx ; |dest
00092844 |. FF15 D8500900 call dword ptr ds:[<&MSVCR100.strncpy>] ; \strncpy
接着到最后:
000928AD |. 889D 04FFFFFF mov byte ptr ss:[ebp-0xFC],bl
000928B3 |. 885D E0 mov byte ptr ss:[ebp-0x20],bl
000928B6 |. E8 35FAFFFF call crackme6.000922F0 ; 跟进最后一个函数,肯定会去判断参数就是key1 key2
000928BB |. 83C4 08 add esp,0x8
000928BE |. 33C0 xor eax,eax
分别是由name和password得到的,最后这个函数会进行验算,跟进,用IDA看了下
很明显后半段有个分支,两种不同的结果,最终都会打印,那就有个正确一个错误,这里测试明显是错的,所以OD这里设断后,改标志位,执行相反的流程,结果如下,没有Wrong而是Congratulation
还是好好分析下验证算法,函数开始时申请了内存。然后有两个关键的函数调用,如下:
00212343 |> \6A 00 push 0x0
00212345 |. 56 push esi
00212346 |. 8BCF mov ecx,edi
00212348 |. C745 FC FFFFF>mov [local.1],-0x1
0021234F |. E8 8C150000 call crackme6.002138E0 ; 变换key1
先根据参数的变化判断一下,参数有esi为key1,还有edi放的是上面申请的内存地址,在数据窗口监视,调用过后的结果为
全是0和1,有点分组密码的特征,那就可能是上面说的des,那key1有可能为密钥,key2可能就是文本啦,在网上查下,
结果如上,
00212354 |. 6A 00 push 0x0
00212356 |. 53 push ebx
00212357 |. 8BCF mov ecx,edi
00212359 |. E8 B2190000 call crackme6.00213D10 ; 变换key2 des加密
第二个函数参数为ebx即key2,和edi即申请的内存,还是观察返回结果,用IDA分析和结合后面的调用结果返回在[EDI+0x600]处
得到的结果仍是二进制,用计算器将第一行转换为十六进制发现为464e,刚好和网上的计算一样,说明就是des加密,key1密钥,key2文本。
接着往下看:
0021235E |. A1 0C532100 mov eax,dword ptr ds:[0x21530C]
00212363 |. 8B0D 10532100 mov ecx,dword ptr ds:[0x215310]
00212369 |. 8B15 14532100 mov edx,dword ptr ds:[0x215314]
0021236F |. 8945 D4 mov [local.11],eax
00212372 |. A1 18532100 mov eax,dword ptr ds:[0x215318]
00212377 |. 894D D8 mov [local.10],ecx
0021237A |. 8A0D 1C532100 mov cl,byte ptr ds:[0x21531C]
00212380 |. 8945 E0 mov [local.8],eax
00212383 |. 8D45 D4 lea eax,[local.11]
00212386 |. 8955 DC mov [local.9],edx
00212389 |. 884D E4 mov byte ptr ss:[ebp-0x1C],cl
0021238C |. 8D50 01 lea edx,dword ptr ds:[eax+0x1]
0021238F |. 90 nop
00212390 |> 8A08 /mov cl,byte ptr ds:[eax]
00212392 |. 40 |inc eax
00212393 |. 84C9 |test cl,cl
00212395 |.^ 75 F9 \jnz Xcrackme6.00212390 ; 长度
00212397 |. 2BC2 sub eax,edx
00212399 |. 8BC8 mov ecx,eax
0021239B |. 33C0 xor eax,eax
0021239D |. 85C9 test ecx,ecx
0021239F |. 7E 15 jle Xcrackme6.002123B6
002123A1 |> 8A5405 D4 /mov dl,byte ptr ss:[ebp+eax-0x2C]
002123A5 |. 80F2 03 |xor dl,0x3
002123A8 |. 885405 C0 |mov byte ptr ss:[ebp+eax-0x40],dl
002123AC |. C64405 C1 00 |mov byte ptr ss:[ebp+eax-0x3F],0x0
002123B1 |. 40 |inc eax
002123B2 |. 3BC1 |cmp eax,ecx
002123B4 |.^ 7C EB \jl Xcrackme6.002123A1
002123B6 |> 8BCF mov ecx,edi
这段代码读取内存固定信息B13:5GG0EF47A7F,经过与3异或后为A2096DD3FE74B4EC,
002123B6 |> \8BCF mov ecx,edi
002123B8 |. 8D75 C0 lea esi,[local.16]
002123BB |. E8 F0140000 call crackme6.002138B0 ; 关键生成,就是将des生成的上面的二进制转换为十六进制464E765999867BBF
接下来就是直接比对,就是上面爆破的地方。相等就成功。
所以,要将key1作为密钥加密key2得到A2096DD3FE74B4EC才行。
题目要求输入XDCSC2012作为name,同时得到的key1为d4021e38
那么这个key1就可以作为密钥解密A2096DD3FE74B4EC
在网上解得为8018faaf,这是password经过MD5加密后的一部分,写程序取password从一个字符开始,字符为字母数字,一次MD5加密,取5到8个字节与8018faaf相等成功。
BOOL MD5Hash()
{
MD5_CTX context;
long i;
int j,k,m;
char szName[MAXINPUTLEN]={"QWERTYUIOPASDFGHJKLZXCVBNM1234567890qwertyuiopasdfghjklzxcvbnm"};
char szHash[MAXINPUTLEN]={0};
char szBuffer[MAXINPUTLEN]={0};
char duan[3]={0};
char buff[] = {"8018FAAF"};
for (i=1;i<=2;i++)
{
for (j=0;j<62;j++)
{
duan[0] = szName[j];
if (i==1)
{
MD5Init(&context);
MD5Update(&context, duan, i);
MD5Final(szHash, &context);
for(k=0; k < 16; k++) // 将szHash[]中的16进制转换成字符形式显示
wsprintf(&szBuffer[k*2], "%02X", *(byte*)(szHash+k));
if (strncmp(buff,(szBuffer+8),sizeof(buff)-1) == 0)
{
printf("%s\n",duan);
return;
}
}
if (i==2)
{
for (m=0;m<62;m++)
{
duan[1] = szName[m];
MD5Init(&context);
MD5Update(&context, duan, i);
MD5Final(szHash, &context);
for(k=0; k < 16; k++) // 将szHash[]中的16进制转换成字符形式显示
wsprintf(&szBuffer[k*2], "%02X", *(byte*)(szHash+k));
if (strncmp(buff,(szBuffer+8),8) == 0)
{
printf("%s\n",duan);
return;
}
}
}
}
}
return TRUE;
}
则password为VkdwT1VsQlJQVDA9
总结:
该程序用遍历进程,查看窗口反调试,查看BingDebugged反调试,并禁止鼠标反调试,创建子线程验证,主线程检测反调试,name用MD5加密取部分作为des的密钥,password进行4四次base64解码后得到的字符串用MD5加密取部分作为des的文本,des加密后的得到的字符串与固定字符串比较,相等则成功,没有调用一般的比较函数来防止跟踪,还是比较有意思的,爆破简单,分析算法对我来说还是有点麻烦。
上传图片有点麻烦啊 对能有耐心看完的表示感谢
crackme6.zip
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)