references:
如果要密码,就是1
这回情况和上次那个程序稍有不同,没有任何提示字符串,我们只能在系统API函数调用处下端点
windows GUI程序的常用于下断点的API函数
使用OllyDBG加载程序之后,Ctrl+N获取当前程序引入的所有库函数
其中有一个GetWinTextA
,这个函数用于获取指定窗口中的字符串,可以用来下断点,选中这个函数右键下断点即可
可以看到,与该函数相关的指令都会被下断点,这两个断点对我们来说都没啥用,我们直接删除掉即可,然后在00401323
的下一个位置下断点,也就是00401328
,这个才是CrackHead程序的代码
我们跟入这个call即可
该函数的代码:
004013D2 /$ 56 PUSH ESI
004013D3 |. 33C0 XOR EAX,EAX
004013D5 |. 8D35 C4334000 LEA ESI,DWORD PTR DS:[4033C4]
004013DB |. 33C9 XOR ECX,ECX
004013DD |. 33D2 XOR EDX,EDX
004013DF |. 8A06 MOV AL,BYTE PTR DS:[ESI]
004013E1 |. 46 INC ESI
004013E2 |. 3C 2D CMP AL,2D
004013E4 |. 75 08 JNZ SHORT CrackHea.004013EE
004013E6 |. BA FFFFFFFF MOV EDX,-1
004013EB |. 8A06 MOV AL,BYTE PTR DS:[ESI]
004013ED |. 46 INC ESI
004013EE |> EB 0B JMP SHORT CrackHea.004013FB
004013F0 |> 2C 30 /SUB AL,30
004013F2 |. 8D0C89 |LEA ECX,DWORD PTR DS:[ECX+ECX*4]
004013F5 |. 8D0C48 |LEA ECX,DWORD PTR DS:[EAX+ECX*2]
004013F8 |. 8A06 |MOV AL,BYTE PTR DS:[ESI]
004013FA |. 46 |INC ESI
004013FB |> 0AC0 OR AL,AL
004013FD |.^75 F1 \JNZ SHORT CrackHea.004013F0
004013FF |. 8D040A LEA EAX,DWORD PTR DS:[EDX+ECX]
00401402 |. 33C2 XOR EAX,EDX
00401404 |. 5E POP ESI
00401405 |. 81F6 53757A79 XOR ESI,797A7553
0040140B \. C3 RETN
第三行中4033C4
是序列号字符串的地址,是由之前的函数调用GetWindowTextA
获取的
004013D5 |. 8D35 C4334000 LEA ESI,DWORD PTR DS:[4033C4]
现在ESI指向序列号
004013DF |. 8A06 MOV AL,BYTE PTR DS:[ESI]
004013E1 |. 46 INC ESI
004013E2 |. 3C 2D CMP AL,2D
004013E4 |. 75 08 JNZ SHORT CrackHea.004013EE
获取第一个序列号,然后ESI往后偏移1,如果第一个字符不是-
(ascii为2D),则跳转
004013EE |> EB 0B JMP SHORT CrackHea.004013FB
接着跳
004013FB |> 0AC0 OR AL,AL
004013FD |.^75 F1 \JNZ SHORT CrackHea.004013F0
AL不为0就跳
004013F0 |> 2C 30 /SUB AL,30
004013F2 |. 8D0C89 |LEA ECX,DWORD PTR DS:[ECX+ECX*4]
004013F5 |. 8D0C48 |LEA ECX,DWORD PTR DS:[EAX+ECX*2]
004013F8 |. 8A06 |MOV AL,BYTE PTR DS:[ESI]
004013FA |. 46 |INC ESI
004013FB |> 0AC0 OR AL,AL
004013FD |.^75 F1 \JNZ SHORT CrackHea.004013F0
先减去0x30,下面两个LEA指令相当于ECX=ECX*10+EAX
,不过在这个循环里,ECX并没有什么作用,整个循环就是对序列号进行遍历,直到读完(最后的结束标志符0x00)
004013FF |. 8D040A LEA EAX,DWORD PTR DS:[EDX+ECX]
00401402 |. 33C2 XOR EAX,EDX
00401404 |. 5E POP ESI
00401405 |. 81F6 53757A79 XOR ESI,797A7553
0040140B \. C3 RETN
循环完成后,执行了四条指令,就返回了,现在我并不清楚这四条指令的作用
函数返回后,对EAX和ESI寄存器进行了比较,如果两者不相等,就会跳走,继续执行程序会发现没有任何反应,这显然不是我们想要的,我们不希望程序跳走,我们想让他执行下面的那条JMP指令,显然这个JMP跳到的地方有更多的东西
因此,我们需要确保在函数执行完成后,EAX和ESI的值是相等的
通过调试器可以看到,ESI的初始值是0,且函数的第一行代码就对ESI的值进行了保存
在函数返回的时候,取出ESI的值和0x797A7553
进行异或,那么无论如何,函数返回的时候ESI的值一定会变成0x797A7553
,现在我们的问题就是如何控制AX也是这个值
在函数一开始的地方
004013E4 |. 75 08 JNZ SHORT CrackHea.004013EE
这条指令是否执行只会影响EDX的值,如果这条指令没执行,那么EDX的值就会变成0xffffffff
但是,我并没有办法输入-
,所以这条指令肯定会执行,也就是说EDX的值肯定是0,那么在最后和EAX进行异或运算的时候,EDX是完全没有影响的,异或完之后,EAX的值是不会变的
现在就是要想办法,怎么把EAX的值弄成0x797A7553
现在的问题就是要循环多少次,每次EAX的值应该是多少,才能在循环结束之后,ECX的值为0x797A7553
,因为在EDX为0的情况下,下面这两条指令完成后,其实就是MOV EAX, ECX
004013FF |. 8D040A LEA EAX,DWORD PTR DS:[EDX+ECX]
00401402 |. 33C2 XOR EAX,EDX
刚测试了一下,输入框允许的最大长度为21,EAX的值一共存在10种可能(0~9)
也就是说最多存在10^21
个组合,这太大了,显然是爆破不出来的
仔细观察一下代码,可以发现下面这个表达式:
ECX=ECX*10+EAX
而由于EAX每次在运算之前会减去0x30,因此EAX的值就是我们在程序中输入的序列号种的字符
0x797A7553
的10进制形式为2038068563,而ECX的初始值为0,第一次循环,ECX的值就是EAX的值,第二次循环,ECX的值是之前的EAX的值乘以10加上当前EAX的值,很明显,我们只需要将序列号设置为2038068563
即可
弹出的对话框提示我们编写一个序列号生成器,而且程序提示说每台电脑上的序列号都不一样
刚才把这个程序放到了其他机器上,输入这个序列号,结果还是这个对话框
又换了一台机器,还是这个样子,不管了,既然他这么说了,我就看一下
很明显0x797A7553
就是硬编码,唯一可能会影响到ESI值的因素就是在调用检验函数之前的代码
00401310 |. 8B35 9C334000 MOV ESI,DWORD PTR DS:[40339C]
00401316 |. 6A 28 PUSH 28 ; /Count = 28 (40.)
00401318 |. 68 C4334000 PUSH CrackHea.004033C4 ; |Buffer = CrackHea.004033C4
0040131D |. FF35 90314000 PUSH DWORD PTR DS:[403190] ; |hWnd = 00070812 (class='Edit',parent=00090814)
00401323 |. E8 4C010000 CALL <JMP.&USER32.GetWindowTextA> ; \GetWindowTextA
00401328 |. E8 A5000000 CALL CrackHea.004013D2
就是上面代码中的第一行,这个0x40339C
也是硬编码,但是这块内存中的值可能会在不同的机器上有不同的值,这里触及到我的知识盲区了,就到这儿吧
告辞!!