逆向一个crackme(4)

(注意:本文的代码都是有格式的,但是网页加载比较慢,如果看每格式的代码比较累,可以稍作等待,等网页加载完毕即可显示格式。。。)
这是160个crackme的第23个

 
crackme23
在做这个crackme之前,c/c++或者masm/tasm写的程序我都是直接od载入调试的,但是这个程序的算法昨天折腾了大半天还是没搞明白。无奈,今天第一次(基本上算第一次吧。。)用ida静态的来分析之,忽然就豁然开朗,ida的图形界面方式简直碉堡了。。

首先打开程序

要求输入name和serial,如果正确的话status里就会显示成功

peid载入发现是masm/tasm汇编写的

程序比较小,爆破就不说了,比较简单,下面用ida载入,看看程序流程

sub_401023是main函数,进去,找到窗口过程

进去之后,发现程序是调用了SetTimer定期检测两个输入框

来到WM_TIMER的地方,这里是关键

(注意:因为我分析过,所以函数名之类我已经改过了)

这里有两个全局变量:

dword ptr [403166]  不妨设为a

dword ptr [403167]  不妨设为b

事实上待会还会涉及到一个全局变量dword ptr [403168]  不妨设为c

通过分析call switcha之后的代码可以发现,当a=16,b!=16时注册成功

好,来看关键函数switcha()

不知道为什么ida没有分析完整,第一次用ida,我也不知道怎么弄。。

事实上这里的retn的作用是jmp。并不是真的返回。我们用od分析。

注意下面这几句

00401459 |. 8925 A0314000 mov dword ptr ds:[0x4031A0],esp
0040145F |. 8D25 52314000 lea esp,dword ptr ds:[0x403152] ;
00401465 |. 0FBE05 66314000 movsx eax,byte ptr ds:[0x403166]
0040146C |. 03E0 add esp,eax

把esp保存之后将esp指向内存地址0×403152,然后再add变量a,这样之后retn,那么显然程序会跳到0×403152+a内的值所指向的地方接着执行,相当于jmp dword ptr [0x403152+eax]。(原因在于retn的本质是pop eip;jmp eip)

接着我们该做的事当然是看看0×403152这个地方存了什么东东

我们发现其实是5个地址值。

那么可以判断这个函数其实是根据变量a的值的不同跳转到不同分支去执行,这不就是switch语句吗。

具体来看这5个地址处的指令

 

;跳转0
0040146F  mov esp,dword ptr ds:[0x4031A0]
00401475  push 0x0                                 ; /IsSigned = FALSE
00401477  lea eax,dword ptr ss:[ebp-0x4]           ; |
0040147A  push eax                                 ; |pSuccess
0040147B  push 0x64                                ; |ControlID = 64 (100.)
0040147D  push dword ptr ds:[0x403170]             ; |hWnd = NULL
00401483  call <jmp.&USER32.GetDlgItemInt>         ; \GetDlgItemInt
00401488  mov dword ptr ds:[0x403188],eax          ;  序列号存入变量0x403188,不妨设为p
0040148D  cmp dword ptr ss:[ebp-0x4],0x0           ;  读取是否成功
00401491  je XChafe_1.0040149A
00401493  add byte ptr ds:[0x403166],0x4           ;  a+=4
0040149A  leave
0040149B  retn


;跳转1
00401063  mov esp,dword ptr ds:[0x4031A0]
00401069  push 0x14                                ; /Count = 14 (20.)
0040106B  push Chafe_1.0040318C                    ; |用户名存入0x40318c,不妨设为u
00401070  push dword ptr ds:[0x403174]             ; |hWnd = NULL
00401076  call <jmp.&USER32.GetWindowTextA>        ; \GetWindowTextA
0040107B  mov ecx,0x14                             ;  函数读取20个字符
00401080  sub ecx,eax
00401082  lea edi,dword ptr ds:[eax+0x40318C]
00401088  mov byte ptr ds:[edi],0x0
0040108B  inc edi
0040108C  dec ecx                                  ;  这个循环是把读取的name后的空间填0
0040108D  jnz XChafe_1.00401088                    ;  这样做是因为实际上算法用到了19个byte
0040108F  test eax,eax
00401091  je XChafe_1.004010A3
00401093  add byte ptr ds:[0x403166],0x4           ;  如果成功读取则a+=4
0040109A  mov byte ptr ds:[0x403168],0x0           ;  c=0
004010A1  jmp XChafe_1.004010A9
004010A3  mov byte ptr ds:[0x403166],ah            ;  没有输入用户名则a=0
004010A9  leave
004010AA  retn

;跳转2
00401361  lea edi,dword ptr ds:[0x40318C]
00401367  movsx eax,byte ptr ds:[0x403168]
0040136E  add edi,eax                              ;  u1
00401370  inc byte ptr ds:[0x403168]               ;  c++
00401376  mov eax,dword ptr ds:[0x403188]          ;  eax=p
0040137B  mov esp,dword ptr ds:[0x4031A0]          ;  恢复堆栈
00401381  inc eax                                  ;  p++
00401382  inc dword ptr ds:[0x403188]
00401388  xor eax,dword ptr ds:[edi]               ;  p^=((u1<<24)+(u1<<16)+(u1<<8)+u1)
0040138A  mov dword ptr ds:[0x403188],eax
0040138F  cmp byte ptr ds:[0x403168],0x10          ;  c==16?
00401396  jnz XChafe_1.0040139F
00401398  add byte ptr ds:[0x403166],0x4
0040139F  leave
004013A0  retn

;跳转3
0040149C  mov eax,dword ptr ds:[0x403188]
004014A1  add eax,0x9112478                        ;  p+0x9112478==0?
004014A6  test eax,eax
004014A8  jnz XChafe_1.004014B3
004014AA  add byte ptr ds:[0x403166],0x4
004014B1  jmp XChafe_1.004014BA
004014B3  mov byte ptr ds:[0x403166],0x0
004014BA  mov esp,dword ptr ds:[0x4031A0]
004014C0  leave
004014C1  retn

;跳转4
004014BA  mov esp,dword ptr ds:[0x4031A0]
004014C0  leave
004014C1  retn

这五段代码联系起来看,这个switch就好理解了,用伪代码写写看

 

switch(a):
	case 0:
		if(输入了序列号p) a+=4;
		break;
	case 4:
		if(输入了用户名u[]) a+=4,b=0;
		else a=0;
		break;
	case 8:
		p+=1;
		p^=((u[C+3]<<24)+(u[C+2]<<16)+(u[C+1]<<8)+u[C]);//这里写成小写c会导致显示错误,wp的bug??
		c+=1;
		if(c==16) a+=4;
		break;
	case 12:
		if(p+0x9112478==0) a+=4;
		else a=0;
		break;
	case 16:
		break;

可以发现,这几个case是上面的成功了下次WM_TIMER来的时候就会执行下面的,所以作者写的时候目测是用if语句(因为是masm,所以应该是.if  .endif,win32汇编。。可能记错了语法,别打我。。)
又由于timer相当于while(1)
于是,用正常点的代码来叙述,如下

 

while(1)
{
	if(输入了序列号p)
	{
		if(输入了用户名u[])
		{
			for(c=0;c<16;c++)
			{
				p+=1;
				p^=((u[C+3] <<24)+(u[C+2] <<16)+(u[C+1] <<8)+u[C]);			
			}
			if(p==(uint)(-0x9112478)) 显示注册成功;
		}


	}
	显示注册失败;
}

大功告成,算法逆过来很简单,下面是注册机

 

#include<stdio.h>
#include<string.h>
typedef unsigned int uint;
typedef unsigned char uchar;
uchar s[20];
const uint xh=0x9112478;
int main()
{
    int i;
    while(~scanf("%s",s))
    {
        uint p=-xh;
        for(i=15;i>=0;i--)
        {
            p^=(uint)((s[i+3]<<24)+(s[i+2]<<16)+(s[i+1]<<8)+s[i]);
            p-=1;
        }
        printf("%u\n",p);

    }

    return 0;
}

3 thoughts on “逆向一个crackme(4)

发表评论

电子邮件地址不会被公开。 必填项已用 * 标注

*

您可以使用这些 HTML 标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>