天天看点

Crackmes.de上borismilner的一个crackme

borismilner提供的几个​​crackme​​归档在"1-very_easy_for_newbies"下:

Crackmes.de上borismilner的一个crackme

level0-level3还算简单,是名副其实的"crackme for newbies",但是相比起来level-4.exe可能超过"newbie"的能力,因为作者在CRM中用了简单的SMC(self-modify code)。且不论它是不是真的"very easy",看我在此献个丑把这题解了。

    先查壳,这个exe倒是不带壳(不要以为这个级别的CRM不带壳,那真是太小看作者了!),运行程序,随便输入一串字符,程序对此返回"NOT A GOOD JOB":

Crackmes.de上borismilner的一个crackme

以此为切入口,在IDA中"Strings"窗口中搜索该字符串,然而,并没有任何收获:

Crackmes.de上borismilner的一个crackme

那就换"Password"试试运气,输入密码的地方应该离打印"Password"不远:

Crackmes.de上borismilner的一个crackme
Crackmes.de上borismilner的一个crackme

对Password引用的代码片的附近又调用了scanf,之后又对输入的字符串与字符串"Mario"进行了逐一比对。嗯,第一反应觉得"Mario"就是应该就是password,重新运行一次程序,输入"Mario"试试,依旧提示失败:

Crackmes.de上borismilner的一个crackme

看来需要好好分析一下程序,先调试一下程序,看看输入"Mario"后,是什么原因导致程序输出"NOT A GOOD JOB"。

.text:0040140C cmp     ds:inputStr, 'M'
.text:00401413 jz      short loc_40148B
.text:00401415 cmp     ds:inputStr+1, 'a'
.text:0040141C jz      short loc_40148B
.text:0040141E cmp     ds:inputStr+2, 'r'
.text:00401425 jz      short loc_40148B
.text:00401427 cmp     ds:inputStr+3, 'i'
.text:0040142E jz      short loc_40148B
.text:00401430 cmp     ds:inputStr+4, 'o'
.text:00401437 jz      short loc_40148B      

在0x40140C处下断点,即程序输入字符串后首次进行比较的位置。输入"Mario"后,程序会跳转到0x0040148B处,这段代码片好像会往屏幕上输出一段火星文,先运行看下结果:

.text:0040148B mov     eax, offset aIhsF@hhcMhe        ; "\rIHS'F'@HHC'MHE'&\r"
.text:00401490 mov     ecx, 10h
.text:00401495
.text:00401495 loc_401495:                             ; CODE XREF: sub_4013E0+B9j
.text:00401495                                         ; sub_4013E0+CCj
.text:00401495 xor     byte ptr [eax+ecx], 7
.text:00401499 loop    loc_401495
.text:0040149B xor     byte ptr [eax+ecx], 7
.text:0040149F push    eax                             ; Format
.text:004014A0 call    printf
.text:004014A5 add     esp, 4      

当程序执行完printf,屏幕上输出的并不是什么火星文,取而代之的依然是熟悉而又不和谐的"NOT A GOOD JOB"字样!再回过去看下IDA中0x0040148B处引用的火星文,已经变成了"NOT A GOOD JOB",如图:

Crackmes.de上borismilner的一个crackme

这种变化源自0x00401495-0x00401499之间的xor循环,这段xor循环将数据段的字符串进行异或解码,解码前:

.data:0040902A 00000013 C \rIHS'F'@HHC'MHE'&\r      

解码后:

.data:0040902A aIhsF@hhcMhe db 0Dh,'NOT A GOOD JOB !',0Dh,0 ; DATA XREF: sub_4013E0+A4o
.data:0040902A                                         ; sub_4013E0:loc_40148Bo      

嗯,看来密码不能是"Mario",作者误导我呐!换一个密码,避开所有坑,继续往下调试必然会进入这个代码片:

.text:0040144C loc_40144C:                             ; CODE XREF: sub_4013E0+6Aj
.text:0040144C mov     eax, offset loc_409000          ; smc
.text:00401451 mov     ebx, offset loc_40145B
.text:00401456 jmp     loc_409000      

jmp指令使程序跳转到数据段执行(请注意下面代码片的段前缀.data,我调试了很久才注意到这是数据段):

.data:00409000 xor     byte ptr [eax+4], 0F7h
.data:00409004 jl      short near ptr Format+15h ;<-注意这条指令,执行完后面的xor 
                                             ;byte ptr [eax+4],7后会变为mov ecx,[esp+arg_0]
.data:00409006 and     al, 4
.data:00409008 xor     byte ptr [eax+4], 7
.data:0040900C or      ecx, 0F0F0h
.data:00409012 jmp     ebx      

此时,eax指向0x00409000,所以指令xor byte ptr [eax+4],0F7h其实就是修改0x409004处的数据,同时这处还是一条指令,最终0x00409004被修改为一条mov指令,如下图:

Crackmes.de上borismilner的一个crackme

至于[esp+arg_0]中,按照调试的结果,存储的是程序的参数数量,即int argc。

int main(int argc,char* argv[]);      

最后,程序指令jmp ebx跳转到代码段0x0040145B:

Crackmes.de上borismilner的一个crackme

这段代码相对比较容易,它将ecx中存储的参数的数量通过循环移位,移动到eax的最高位,然后使eax同0x10000000相加。如果相加的结果造成符号位溢出,则会使0x401474处的jno指令不发生跳转,然后再做一次SMC解码,得到正确的显示结果:"GOOD JOB"

Crackmes.de上borismilner的一个crackme
.text:00401460 mov     byte ptr loc_409000, 10h
...
.text:00401476 sub     byte ptr loc_409000, 7