references:
耍一耍二进制
如果需要密码,就是1
根据注册时弹出的错误信息,搜索错误字符串定位到程序代码
两个错误提示,两段同样的代码,我们分别在其调用者下断点
实际上是JNZ指令条件跳转过来的
两条跳转指令的上一条指令是同一个函数
下断点跟入该函数
对该函数进行分析
首先前三行是压栈操作,保护寄存器的内容
00403B2C /$ 53 PUSH EBX
00403B2D |. 56 PUSH ESI
00403B2E |. 57 PUSH EDI
接下来的三行我并不理解他是什么意思,因为从字面上来看,他是对两个偏移量进行了CMP操作,而EAX寄存器是指向我们输入的用户名字符串的指针,EDX寄存器是指向程序硬编码的字符串的指针,我不明白比较这两个指针的意义何在
00403B2F |. 89C6 MOV ESI,EAX
00403B31 |. 89D7 MOV EDI,EDX
00403B33 |. 39D0 CMP EAX,EDX
如果着两个指针一样,就会跳转
00403B35 |. 0F84 8F000000 JE CrackMe3.00403BCA
太离谱了,这两个指针要在什么样的情况下才会相等????
不过前两行我还是能理解的,就是将地址分别转移到了ESI和EDI寄存器
直接ctrl+G定位到指定内存地址00403BCA
:
可以看到是直接返回了,而此时ZF标志寄存器肯定会置位(1)
那么JNZ就不会成立,错误消息就不会弹出了
我们继续往下看,因为EAX和EDX并不相等
看看这两个寄存器是否为空(空地址全0)
不过除非我们直接没给用户名,不然这两个TEST指令都不会成立
接着看下面是三行
从结果来看是把两个字符串的长度放到了EAX和EDX寄存器中,但是为啥DS:[ESI-4]
和DS:[EDI-4]
就是字符串的长度,我也不清楚,我也没有源代码,鬼知道他是怎么写的,如果大家有知道的,欢迎留言,不胜感激
注意SUB指令不同于其他的指令,SUB var1, var2
的意思是var1-var2
,不能照搬MOV的规则
然后他把两个字符串的长度相减,此处结果为0,因为Unregistered...
和Registered User
的长度是一样的,长度都是0x0F
如果我们输入的字符串长比硬编码的字符串长,直接跳走
就是把ADD EDX, EAX
指令给跳过了,如果没有跳过,说明我们输入的字符串长度和硬编码的字符串长度相等或者更短,那么该指令就会执行,将两者的差值和硬编码的字符串长度相加,那么此时EDX的值将和EAX原来的值相等
我们可以实验一下,将用户名的长度设置为1
可以看到,当我们输入的用户名长度较小时,会产生一个负数,但是最后和EDX相加之后,EDX就会变成EAX之前的值
等效于:
EDX+(EAX-EDX)=EAX
接着往下看
保存EDX的值,然后将EDX右移两位,如果EDX小于4,那么右移两位之后,结果肯定为0,ZF就会置位
就会跳转到CrackMe3.00403B7B
这里之所以要右移两位,是因为下面的代码将会对我们输入的用户名和硬编码的用户名进行循环比较,每次比较4个字节,也就是4个字符串,这里由于是1,所以进不去循环,我们使用默认的用户名来进行调试
这个就是循环体,以EDX作为循环次数
前三行:
00403B55 |> 8B0E /MOV ECX,DWORD PTR DS:[ESI]
00403B57 |. 8B1F |MOV EBX,DWORD PTR DS:[EDI]
00403B59 |. 39D9 |CMP ECX,EBX
ESI和EDI分别为我们输入的字符串和硬编码字符串的指针,这三行指令分别取出了两个字符串的前4个字符到ECX和EBX中,并对两者进行比较
如果不相等,则直接跳出循环
跳转到如下位置:
00403BB5 |> 5A POP EDX
00403BB6 |. 38D9 CMP CL,BL
00403BB8 |. 75 10 JNZ SHORT CrackMe3.00403BCA
00403BBA |. 38FD CMP CH,BH
00403BBC |. 75 0C JNZ SHORT CrackMe3.00403BCA
00403BBE |. C1E9 10 SHR ECX,10
00403BC1 |. C1EB 10 SHR EBX,10
00403BC4 |. 38D9 CMP CL,BL
00403BC6 |. 75 02 JNZ SHORT CrackMe3.00403BCA
00403BC8 |. 38FD CMP CH,BH
00403BCA |> 5F POP EDI
00403BCB |. 5E POP ESI
00403BCC |. 5B POP EBX
00403BCD \. C3 RETN
恢复EDX的值,然后对ECX和EBX寄存器的24~31bit进行比较,不同则直接返回,否则继续比较16~23biit,不同则直接返回,否则将ECX和EBX分别右移16位,重复以上操作
我们是因为EBX和ECX不相等跳到这里来的,所以这里的判断也会全部失败,最后ZF标志位处于复位状态返回,弹出错误信息
回到循环代码
00403B5D |. 4A |DEC EDX
00403B5E |. 74 15 |JE SHORT CrackMe3.00403B75
前四个字符串一样,EDX减1,如果EDX变成0了,说明已经比较完了,跳转
00403B75 |> 83C6 04 ADD ESI,4
00403B78 |. 83C7 04 ADD EDI,4
00403B7B |> 5A POP EDX
00403B7C |. 83E2 03 AND EDX,3
00403B7F |. 74 22 JE SHORT CrackMe3.00403BA3
两个指针分别向后偏移4位,然后恢复EDX的值,和3进行与操作,其实就是判断EDX的最后两个bit是否全为0,如果不为0,说明字符串没有被完全比较,还余下了几个,其实这个与运算的结果就是进入循环前进行的右移2位(除以4)的余数
如果EDX被4整除了,就跳转,也就是说,字符串比较已经完成
00403BA3 |> 01C0 ADD EAX,EAX
00403BA5 |. EB 23 JMP SHORT CrackMe3.00403BCA
这里其实就是判断一下EAX的值是否为0,为0说明我们输入的用户名和硬编码的用户名长度一致,因为前面有一个SUB EAX, EDX
的操作
如果没有被4整除,也就是说还有余数,那么久继续比较
00403B81 |. 8B0E MOV ECX,DWORD PTR DS:[ESI]
00403B83 |. 8B1F MOV EBX,DWORD PTR DS:[EDI]
00403B85 |. 38D9 CMP CL,BL
00403B87 |. 75 41 JNZ SHORT CrackMe3.00403BCA
00403B89 |. 4A DEC EDX
00403B8A |. 74 17 JE SHORT CrackMe3.00403BA3
00403B8C |. 38FD CMP CH,BH
00403B8E |. 75 3A JNZ SHORT CrackMe3.00403BCA
00403B90 |. 4A DEC EDX
00403B91 |. 74 10 JE SHORT CrackMe3.00403BA3
可以看到,先比较剩余的第一个字符,不相等直接返回,如果相等,再判断字符串是不是比较完了
如果上面都没有跳转,再比较剩余的第二个字符,同上
如果都没有跳转,执行下面的代码
00403B93 |. 81E3 0000FF00 AND EBX,0FF0000
00403B99 |. 81E1 0000FF00 AND ECX,0FF0000
00403B9F |. 39D9 CMP ECX,EBX
00403BA1 |. 75 27 JNZ SHORT CrackMe3.00403BCA
00403BA3 |> 01C0 ADD EAX,EAX
00403BA5 |. EB 23 JMP SHORT CrackMe3.00403BCA
通过与操作提取出第3个字符进行比较,其实和上面的逻辑是一样的,只不过不用跳CrackMe3.00403BA3
了,因为要跳转的代码跟它挨着,这一处的代码逻辑,上面已经讲过了,不再赘述
回到循环代码
00403B60 |. 8B4E 04 |MOV ECX,DWORD PTR DS:[ESI+4]
00403B63 |. 8B5F 04 |MOV EBX,DWORD PTR DS:[EDI+4]
00403B66 |. 39D9 |CMP ECX,EBX
00403B68 |. 75 4B |JNZ SHORT CrackMe3.00403BB5
00403B6A |. 83C6 08 |ADD ESI,8
00403B6D |. 83C7 08 |ADD EDI,8
00403B70 |. 4A |DEC EDX
00403B71 |.^75 E2 \JNZ SHORT CrackMe3.00403B55
代码逻辑和上面就是一样的了,再往后偏移4个字节,比较第5~8个字符,如果还没有比较完,就继续循环
这一个函数,到这里我们就算分析完了,因此我们只需要确保用户名和硬编码的字符串一致就行了,而硬编码的字符串我们是可以直接在调试器中看到的,就是EDX指向的那块内存
现在我们已经成功通过第一个检测,来到第二个监测点
不过由于这两个检测点使用的是同一个函数,只是参数不同而已,所以我们只需要将序列号改为硬编码的序列号即可
简简单单,完事儿!!!