首页
社区
课程
招聘
[原创]一个有趣的crackMe分析
2012-10-12 22:12 9191

[原创]一个有趣的crackMe分析

2012-10-12 22:12
9191
先说明这是我在CrackMe2007文档中碰到的,在文档不定技术\其妙的跳转下,觉得这个crackMe特别有趣,不过那篇文章没分析完全,只是最近都在弄这文档下的,都是N年前的东西了,会不会过时了啊

首先这个crackMe是asm写的,所以代码比较好看,OD随便一体调,就找到了关键代码处
00401229   .  6A 28         push    28                               ; /Count = 28 (40.)
0040122B   .  68 68344000   push    00403468                         ; |Buffer = SV_Keyge.00403468
00401230   .  68 EB030000   push    3EB                              ; |ControlID = 3EB (1003.)
00401235   .  FF75 08       push    dword ptr [ebp+8]                ; |hWnd
00401238   .  E8 0B010000   call    <jmp.&user32.GetDlgItemTextA>    ; \GetDlgItemTextA
0040123D   .  6A 28         push    28                               ; /Count = 28 (40.)
0040123F   .  68 68304000   push    00403068                         ; |Buffer = SV_Keyge.00403068
00401244   .  68 EC030000   push    3EC                              ; |ControlID = 3EC (1004.)
00401249   .  FF75 08       push    dword ptr [ebp+8]                ; |hWnd
0040124C   .  E8 F7000000   call    <jmp.&user32.GetDlgItemTextA>    ; \GetDlgItemTextA
00401251   .  68 68304000   push    00403068                         ; /String = ""
00401256   .  E8 53010000   call    <jmp.&kernel32.lstrlenA>         ; \lstrlenA
0040125B   .  83F8 10       cmp     eax, 10
0040125E   .  75 64         jnz     short 004012C4
00401260   .  E8 D3FDFFFF   call    00401038  //关键调用

可以看出程序要求注册码为16个字节,跟进最后那个调用
00401038  /$  8D1D 68344000 lea     ebx, dword ptr [403468]          ;  用户名
0040103E  |.  8D35 68304000 lea     esi, dword ptr [403068]          ;  注册码
00401044  |.  8D3D 68324000 lea     edi, dword ptr [403268]          ;  关键缓存
0040104A  |.  EB 2A         jmp     short 00401076
0040104C  |>  6A 03         /push    3                               ; /n = 3
0040104E  |.  56            |push    esi                             ; |String2
0040104F  |.  68 68364000   |push    00403668                        ; |String1 = SV_Keyge.00403668
00401054  |.  E8 4F030000   |call    <jmp.&kernel32.lstrcpynA>       ; \lstrcpynA
00401059  |.  68 68364000   |push    00403668
0040105E  |.  E8 51030000   |call    004013B4                                 ;关键调用
00401063  |.  3203          |xor     al, byte ptr [ebx]
00401065  |.  8807          |mov     byte ptr [edi], al
00401067  |.  46            |inc     esi
00401068  |.  46            |inc     esi
00401069  |.  43            |inc     ebx
0040106A  |.  803B 00       |cmp     byte ptr [ebx], 0
0040106D  |.  75 06         |jnz     short 00401075
0040106F  |.  8D1D 68344000 |lea     ebx, dword ptr [403468]
00401075  |>  47            |inc     edi
00401076  |>  803E 00        cmp     byte ptr [esi], 0
00401079  |.^ 75 D1         \jnz     short 0040104C
0040107B  \.  C3            retn

看看上面这段代码的循环,可以发现它的作用是每次循环取注册码2个字节然后call 4013b4一个不知名的调用,先不管它,从后续的代码来看这个调用将返回值以字节和用户名进行字节异或,然后存入那个未知缓存中,一直循环8次,因此可以推测注册码以每2个字节为一组进行一些运算再和用户名异或得出8个字节在目标缓存中,这里同时也知道了用户名应该为8个字节。解析完毕,跟进call 4013b4
004013B4  /$  55            push    ebp
004013B5  |.  8BEC          mov     ebp, esp
004013B7  |.  53            push    ebx
004013B8  |.  56            push    esi
004013B9  |.  57            push    edi
004013BA  |.  8B7D 08       mov     edi, dword ptr [ebp+8]
004013BD  |.  8B75 08       mov     esi, dword ptr [ebp+8]
004013C0  |>  8A07          /mov     al, byte ptr [edi]
004013C2  |.  47            |inc     edi
004013C3  |.  0AC0          |or      al, al
004013C5  |.^ 75 F9         \jnz     short 004013C0
004013C7  |.  2BF7          sub     esi, edi                         ;  大概自己计算下,可以得出这条指令执行后esi==-3
004013C9  |.  33DB          xor     ebx, ebx
004013CB  |.  03FE          add     edi, esi                         ;  这里将循环增长后的edi还原
004013CD  |.  33D2          xor     edx, edx
004013CF  |.  F7D6          not     esi                              ;  取反后esi==2
004013D1  |.  EB 23         jmp     short 004013F6
004013D3  |>  8A07          /mov     al, byte ptr [edi]
004013D5  |.  3C 41         |cmp     al, 41
004013D7  |.  72 0C         |jb      short 004013E5
004013D9  |.  2C 57         |sub     al, 57                          ;  al>='A'
004013DB  |.  80D2 00       |adc     dl, 0                          
004013DE  |.  C0E2 05       |shl     dl, 5
004013E1  |.  02C2          |add     al, dl
004013E3  |.  EB 02         |jmp     short 004013E7
004013E5  |>  2C 30         |sub     al, 30                          ;  al-='0'
004013E7  |>  8D4E FF       |lea     ecx, dword ptr [esi-1]          ;  这里第一次ecx=1,第二次ecx=0,可以直接计算出下面的移位数
004013EA  |.  83E0 0F       |and     eax, 0F                         ;  保留低4位
004013ED  |.  C1E1 02       |shl     ecx, 2
004013F0  |.  D3E0          |shl     eax, cl
004013F2  |.  03D8          |add     ebx, eax                        ;  最后结果都相加保存于ebx
004013F4  |.  47            |inc     edi
004013F5  |.  4E            |dec     esi
004013F6  |>  0BF6           or      esi, esi
004013F8  |.^ 75 D9         \jnz     short 004013D3
004013FA  |.  8BC3          mov     eax, ebx                         ;  计算结果给eax,返回
004013FC  |.  5F            pop     edi
004013FD  |.  5E            pop     esi
004013FE  |.  5B            pop     ebx
004013FF  |.  C9            leave
00401400  \.  C2 0400       retn    4

到这里这个关键调用分析完了,继续向下分析,然后好戏上场
00401265   .  8D3D 68324000 lea     edi, dword ptr [403268]
0040126B   .  FFD7          call    edi
0040126D   .  6A 00         push    0                                ; /Style = MB_OK|MB_APPLMODAL
0040126F   .  68 3B304000   push    0040303B                  ; |Title = "Then  ?"
00401274   .  68 1C304000   push    0040301C                 ; |Text = "Try again"
00401279   .  6A 00         push    0                                ; |hOwner = NULL
0040127B   .  E8 E6000000   call    <jmp.&user32.MessageBoxA>        ; \MessageBoxA

发现失败弹窗在这里,并且如果还记得403268这个地址的话就会知道,这是程序刚刚根据注册码和用户名计算出来数据啊,怎么被当成代码执行了!这里肯定产生异常,直接F9的话肯定跑飞没救了,先不运行它,回溯上去,发现一段异常安装代码
004011F1   .  64:FF35 00000>push    dword ptr fs:[0]
004011F8   .  8F45 EC       pop     dword ptr [ebp-14]
004011FB   .  C745 F0 00104>mov     dword ptr [ebp-10], 00401000
00401202   .  C745 F4 82124>mov     dword ptr [ebp-C], 00401282
00401209   .  8D45 EC       lea     eax, dword ptr [ebp-14]
0040120C   .  64:A3 0000000>mov     dword ptr fs:[0], eax
00401212   .  8965 F8       mov     dword ptr [ebp-8], esp
00401215   .  896D FC       mov     dword ptr [ebp-4], ebp

这里的安装方式不太习惯,仔细看看可以知道,异常堆栈中保存了异常处理地址外加一个别的地址,然后esp,ebp都保存在内,于是我们找到异常处理例程,断下,让程序飞
00401000  /.  55            push    ebp
00401001  |.  8BEC          mov     ebp, esp
00401003  |.  52            push    edx
00401004  |.  8B55 08       mov     edx, dword ptr [ebp+8]           ;  lpExceptionRecode
00401007  |.  FF32          push    dword ptr [edx]                         ;  异常码
00401009  |.  8F05 90364000 pop     dword ptr [403690]
0040100F  |.  8B55 0C       mov     edx, dword ptr [ebp+C]           ;  lpSeh
00401012  |.  8B45 10       mov     eax, dword ptr [ebp+10]          ;  lpContext
00401015  |.  FF72 08       push    dword ptr [edx+8]                ;  safe_place
00401018  |.  8F80 B8000000 pop     dword ptr [eax+B8]               ;  eip
0040101E  |.  FF72 0C       push    dword ptr [edx+C]                ;  还原esp
00401021  |.  8F80 C4000000 pop     dword ptr [eax+C4]               ;  esp
00401027  |.  FF72 10       push    dword ptr [edx+10]               ;  ebp
0040102A  |.  8F80 B4000000 pop     dword ptr [eax+B4]               ;  ebp
00401030  |.  B8 00000000   mov     eax, 0
00401035  |.  5A            pop     edx
00401036  |.  C9            leave
00401037  \.  C3            retn

从这里看出,异常处理保存了异常码然后恢复堆栈指针,然后跑到safe_place即401282处继续执行
继续断下
00401282   .  8D3D 68324000 lea     edi, dword ptr [403268]  ;这里是前面计算出的8字节结果的地址
00401288   .  83C7 04       add     edi, 4         ;转到后4个字节
0040128B   .  8B07          mov     eax, dword ptr [edi]
0040128D   .  3B05 90364000 cmp     eax, dword ptr [403690]   ;这个地址是前面存储异常代码的内存,居然用它来和后4个字节比较,有点难以理解它的企图
00401293      75 1C         jnz     short 004012B1
00401295   .  E8 60000000   call    004012FA                       //关键调用,跟进
0040129A   .  68 E9030000   push    3E9                              ; /ControlID = 3E9 (1001.)
0040129F   .  FF75 08       push    dword ptr [ebp+8]                ; |hWnd
004012A2   .  E8 9B000000   call    <jmp.&user32.GetDlgItem>         ; \GetDlgItem
004012A7   .  6A 00         push    0                                ; /Enable = FALSE
004012A9   .  50            push    eax                              ; |hWnd
004012AA   .  E8 8D000000   call    <jmp.&user32.EnableWindow>       ; \EnableWindow
004012AF   .  EB 13         jmp     short 004012C4
004012B1   >  6A 00         push    0                                ; /Style = MB_OK|MB_APPLMODAL
004012B3   .  68 3B304000   push    0040303B                         ; |Title = "Then  ?"
004012B8   .  68 1C304000   push    0040301C                         ; |Text = "Try again"
004012BD   .  6A 00         push    0                                ; |hOwner = NULL
004012BF   .  E8 A2000000   call    <jmp.&user32.MessageBoxA>        ; \MessageBoxA
004012C4   >  FF75 EC       push    dword ptr [ebp-14]
004012C7   .  64:8F05 00000>pop     dword ptr fs:[0]

其实这里我们又发现了一个失败弹窗,顿时凌乱了,上下翻找,所有代码里居然没有正确弹窗,OD字符串查找也没发现正确字符创(其实IDA中可以找到注册成功字符串...)
先不管了,继续跟进一个调用call 4012fa
004012FA  /$  33D2          xor     edx, edx
004012FC  |.  33C9          xor     ecx, ecx
004012FE  |>  02D0          /add     dl, al
00401300  |.  41            |inc     ecx
00401301  |.  C1C8 08       |ror     eax, 8
00401304  |.  83F9 04       |cmp     ecx, 4
00401307  |.^ 75 F5         \jnz     short 004012FE
00401309  |.  80FA 54       cmp     dl, 54                           ;  上面这些代码的目的就是将结果后四个字节逐字节相加并判断是否等于0x54
0040130C  |.  75 1B         jnz     short 00401329
0040130E  |.  8D3D 46304000 lea     edi, dword ptr [403046]
00401314  |.  33DB          xor     ebx, ebx
00401316  |>  3107          /xor     dword ptr [edi], eax
00401318  |.  83C7 04       |add     edi, 4
0040131B  |.  43            |inc     ebx
0040131C  |.  83FB 07       |cmp     ebx, 7
0040131F  |.^ 75 F5         \jnz     short 00401316                  ;  上面总计对403046开始处28个字节进行了些令人不解的运算(和异常代码异或)
00401321  |.  8D3D 46304000 lea     edi, dword ptr [403046]
00401327  |.  FFD7          call    edi                              ;  不管怎么说,它又开始对数据进行调用....
00401329  \>  C3            retn


然后.....就没有然后了,这个调用之后的下面就是些一般处理然后退出了
总结下已知信息,除了我们知道的一些计算过程就是:
(暂且称那8个字节计算结果缓存为buf)
执行403268产生的异常代码号应该和buf+4后4个字节相等(推测)
异常代码号逐字节相加应该等于0x54
对于前4字节没有更多的信息了,于是我们想到去windows头文件找SEH的异常代码号吧,果然发现了个相加0x54的异常   
#define STATUS_INTEGER_DIVIDE_BY_ZERO    ((DWORD   )0xC0000094L)
#define EXCEPTION_INT_DIVIDE_BY_ZERO     STATUS_INTEGER_DIVIDE_BY_ZERO
 

无疑是除0异常了,于是我们尝试着用这个异常去执行最后那个令人不解的数据异或,然后查看结果数据,既然是要执行它,我们就用汇编查看
00403046    6A 00           push    0
00403048    68 3B304000     push    0040303B                         ; ASCII "Then  ?"
0040304D    68 26304000     push    00403026                         ; ASCII "Yeah that's right !!"
00403052    6A 00           push    0
00403054    E8 0DE3FFFF     call    <jmp.&user32.MessageBoxA>
00403059    C3              retn

找到了!原来这28个字节的正确变换结果就是提示注册成功,难怪前面都找不到了

这个异常号是由buf8个字节的执行产生的后果,首先后4个字节由于上面发现已经被确定为0xc00000094,就是说还剩4个字节来让我们产生除0异常,于是自己想各种4字节内产生除0异常的汇编代码吧,我是直接xor eax,eax,div eax,一汇编刚好4个字节,OK了
就是说计算用户名注册码的结果应该为如下序列:
33 c0 f7 f0 94 00 00 c0   
当然也可以识别的序列,只要前4字节产生除0异常

总的来说这个crackMe就是要求你的用户名密码经过计算后产生的计算结果前4字节产生除0异常,而后四个字节就是相关的异常号,需要你的用户名和密码计算来产生异常并且验证异常号,否则就陷入无限异常和程序终止,很有趣的CrackMe
分析完毕,附上CrackMe和注册机
crack.rar

注册机由随机生成的注册码,然后反算用户名(反过来的话就不是那么好算了),附上代码
#include <iostream>
#include <ctime>

int main()
{
	char user[20] = { 0 };
	char code[20]= { 0 };
	char exc[] = { 0x33,0xc0,0xf7,0xf0, 0x94,0x00,0x00,0xc0 }; 
	char k;

	srand(time(0));
	for (int i=0; i < 8; i++)
	{
		unsigned char dl;
		//循环直到找到的是有效ascii输入字符而非无法输入或不可见的字符
		while(1)
		{
			dl=0;
			k=0;
			code[2*i]=rand()%2?rand()%26+65:rand()%10+48;
			code[2*i+1]=rand()%2?rand()%26+65:rand()%10+48;

			char c=code[2*i];
			if (c>='A')
			{
				int x=(c>='W'?0:1);
				dl=(dl+x)*32;
				c=c-'W'+dl;
			}
			else c-='0';
			c&=0xf;
			c<<=4;
			k+=c;

			c=code[2*i+1];
			if (c>='A')
			{
				int x=(c>='W'?0:1);
				dl=(dl+x)*32;
				c=c-'W'+dl;
			}
			else c-='0';
			c&=0xf;
			k+=c;

			user[i] = k^exc[i];
			if (user[i]>0&&isalnum(user[i]) )
				break;
		}
	}	
	printf("user: %s\ncode: %s",user,code);
	getchar();
	return 0;
}

一个有效的序列号:  vknxY4Vk      45AB9988SD3456AB

[培训]二进制漏洞攻防(第3期);满10人开班;模糊测试与工具使用二次开发;网络协议漏洞挖掘;Linux内核漏洞挖掘与利用;AOSP漏洞挖掘与利用;代码审计。

上传的附件:
收藏
点赞0
打赏
分享
最新回复 (5)
雪    币: 191
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
lzdxbksmc 2012-10-15 12:13
2
0
支持一下。。
雪    币: 49
活跃值: (40)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
xuqiqw 1 2012-10-15 20:15
3
0
转正第一帖...还好有人来了
雪    币: 84
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
janecd 2013-3-28 15:39
4
0
学习学习了。。
雪    币: 131
活跃值: (303)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
学者learner 2013-3-29 23:53
5
0
恩,比较详细,值得学习
雪    币: 48
活跃值: (462)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
紫川 2013-4-13 11:16
6
0
赞一个,佩服楼主
游客
登录 | 注册 方可回帖
返回