《PEDIY CrackMe 2007》学习笔记(一) - 序列号 - aalloverred - Taliesin‘s KGM1Tal

这是我做的第一个比较复杂的破解,还写了注册机(python版),从昨天晚上一直到现在,估计有10个小时...磕磕绊绊,不过总算弄出来了

大家可以通过链接:http://pan.baidu.com/s/1miMwZig 密码:7vl2,下载软件、我自己写的文档和参考资料,以及两个注册机

界面图

注册机有纯python和exe版,前者需要python环境,后者只能在64位机上使用(这个BUG我还没法决解ORZ)

分析如下

crackme1
	1.API函数,消息断点,查看参考字符串都断不下,说明被处理过
		原理
			00401519   $  BF 96124000   mov edi,KGM1Tal.00401296                ; 入口地址
			0040151E   .  B9 00010000   mov ecx,0x100							
			00401523   .  B0 99         mov al,0x99
			00401525   .  34 55         xor al,0x55
			00401527   .  F2:AE         repne scas byte ptr es:[edi]			;检查自入口地址开始1K的地方是否有0xCC断点,有一个都不行
			00401529   .  85C9          test ecx,ecx
			0040152B   .  74 06         je short KGM1Tal.00401533				;没有断点,ecx为0,跳转实现,检测完毕,调到retn返回
			0040152D   .  5E            pop esi                                 
			0040152E   .  33F6          xor esi,esi                             
			00401530   .  57            push edi                                ; KGM1Tal.00401396,跳到MessageBox,通知用户错误
			00401531   .^ EB C2         jmp short KGM1Tal.004014F5
			00401533   >  C3            retn
		解决方法
			在这段代码执行之前的断点全能断下(可以在检测的区域内),软件在执行过断点后,就把数据恢复了(加断点改写成0xCC)
			了解这样的代码机制,有助于单步分析程序
			程序一般在入口和API函数设置多个防断点
		知识补充1
			1、即:repnz scasb(32位地址操作)。扫描es:edi指向的一系列字节数据,扫描长度由ecx指定,当遇到与al中的数据相等时停止扫描。
			2、最经典的求字符串长度的代码,strlen()在VC优化编译模式是这段代码。
			3、得到的字符串最后存放在ecx中。

			前缀指令-重复
				任何一个串操作指令,都可以在前面加一个重复前缀,以实现串操作的重复执行,重复次数隐含在cx寄存器中
				rep
					rep前缀用在movs、stos、lods指令前,每次执行一次指令,cx减1;直到cx=0,重复执行结束
				repz 
					也可以表把为repe,用在cmps、scas指令前,每执行一次串指令cx减1,并判断ZF标志是否为0
				//只要cx=0或ZF=0,则重复执行结束
				repnz 
					也可以表达为repne,用在cmps、scas指令前,每执行一次串操作指令cx减1,并判断ZF标志是否为1,只要cx=0 或ZF=1,则重复执行结束。

			串扫描指令-scas
				scasb      
					字节串扫描:al-es:[edi],edi←edi+/-1
				scasw         
					字串扫描:ax-es:[edi],edi←edi+/-2
				串扫描指令scas将附加段中的字节或字内容与AL/AX寄存器内容进行比较,根据比较的结果设置标志,每次比较后修改edi寄存器的值,使之指向下一个元素。
		知识补充2	
			修改寄存器edi的值:如果标志DF为0,则 inc edi;如果DF为1,则 dec edi
			
			cmp等比较语句,实际上是做差,相等 ZF = 1,不等 ZF = 1 
			
			ZF标志位,运算结果为0,ZF = 1, 运算结果非零, ZF = 0
			
			test ecx, ecx
				//实际上是and运算,不保存结果,作用,ecx = 0 时,设置ZF为1,实现跳转

	2.难点——汇编代码分析
		经验
			要段段清,关键是搞清代码段的功能(尤其分析循环),记录全局变量值的含义
			要有相应代码编写经验才能彻底看懂汇编代码,比如MASM32和C/C++
			注册机要边分析边写,先用python写,再改成其他语言
		本程序的密码机制
			通过用户名逐位生成序列号,与输入的序列号比较
			套路
				检测位数(10)
				检测是否为空
				检测是否在一个范围内(A-Z)
				将各个字母的ASCII值相加,取最低字节,再对某数取余数,取商
				得到索引在内置字典中查值
	3.破解分析
		利用PEid查看,没有加壳,WASM32编写
		打开软件尝试输入,发现有错误提示,可以考虑API函数断点(GetDialogItem),消息断点,查找字符串设置断点
			在OllyDBG中添加软件断点,尝试后发现以上都断不住...很蒙...看了资料得知作者设置了断点检测
			后来想起来,利用硬件断点就可以断下来了,或许以后可以直接硬件断
		思路受阻,看了资料中的思路,开始从对话框的回调函数入手,主要是WM_INITDIALOG, WM_COMMAND, WM_CLOSE的处理(WASM编程内容)
			00401230  /.  55            push ebp								 ;
			00401231  |.  8BEC          mov ebp,esp								 ;以上两句是子函数执行前的堆栈调整
			00401233  |.  817D 0C 10010>cmp [arg.2],0x110						 ; WM_INITDIALOG 0x0110
			0040123A  |.  75 1E         jnz short KGM1Tal.0040125A               ; 相等不跳转,执行初始化代码  
			0040123C  |.  68 057F0000   push 0x7F05                              ; /RsrcName = IDI_WINLOGO
			00401241  |.  6A 00         push 0x0                                 ; |hInst = NULL
			00401243  |.  E8 5A030000   call 
   
   
    
                 ; \LoadIconA
			00401248  |.  50            push eax                                 ; /lParam = 0x0
			00401249  |.  6A 01         push 0x1                                 ; |wParam = 0x1
			0040124B  |.  68 80000000   push 0x80                                ; |Message = WM_SETICON
			00401250  |.  FF75 08       push [arg.1]                             ; |hWnd = 0x401000
			00401253  |.  E8 56030000   call 
    
    
     
               ; \SendMessageA
			00401258  |.  EB 36         jmp short KGM1Tal.00401290				 ;	消息处理结束
			0040125A  |>  817D 0C 11010>cmp [arg.2],0x111                        ;  WM_COMMAND 0x111
			00401261  |.  75 1D         jnz short KGM1Tal.00401280               ;  跳转则完蛋
			00401263  |.  817D 10 E9030>cmp [arg.3],0x3E9
			0040126A  |.  75 24         jnz short KGM1Tal.00401290               ;  跳转则完蛋
			0040126C  |.  E8 A8020000   call KGM1Tal.00401519                    ;  检测是否有断点(程序入口)
			00401271  |.  E8 33020000   call KGM1Tal.004014A9                    ;  第二次检测断点(API函数)
			00401276  |.  FF75 08       push [arg.1]                             ;  KGM1Tal.
     
     
      
      
			00401279  |.  E8 18000000   call KGM1Tal.00401296                    ;  关键
			0040127E  |.  EB 10         jmp short KGM1Tal.00401290               ;  消息处理结束
			00401280  |>  837D 0C 10    cmp [arg.2],0x10                         ;  WM_CLOSE 0x10
 			00401284  |.  75 0A         jnz short KGM1Tal.00401290				 ;	相等不跳转,执行结束代码
			00401286  |.  6A 00         push 0x0                                 ; /Result = 0x0
			00401288  |.  FF75 08       push [arg.1]                             ; |hWnd = 00401000
			0040128B  |.  E8 06030000   call 
      
      
       
                    ; \EndDialog
			00401290  |>  33C0          xor eax,eax
			00401292  |.  C9            leave
			00401293  \.  C2 1000       retn 0x10
			分析完两个检测断点(具体知识点见1)后,只剩下0x00401279处的代码
		0x00401279处的代码,跳转到0x00401296
			00401296   $  55            push ebp
			......
			004012AE   .  FF35 3C304000 push dword ptr ds:[0x40303C]             ; /Count = 1E (30.)
			004012B4   .  68 00304000   push KGM1Tal.00403000                    ; |Buffer = KGM1Tal.00403000
			004012B9   .  68 EC030000   push 0x3EC                               ; |ControlID = 3EC (1004.)
			004012BE   .  FF75 08       push dword ptr ss:[ebp+0x8]              ; |hWnd = 00401000
			004012C1   .  E8 D6020000   call 
       
       
         ; \GetDlgItemTextA 004012C6 . FF35 40304000 push dword ptr ds:[0x403040] ; /Count = 14 (20.) 004012CC . 68 23304000 push KGM1Tal.00403023 ; |Buffer = KGM1Tal.00403023 004012D1 . 68 ED030000 push 0x3ED ; |ControlID = 3ED (1005.) 004012D6 . FF75 08 push dword ptr ss:[ebp+0x8] ; |hWnd = 00401000 004012D9 . E8 BE020000 call 
        
          ; \GetDlgItemTextA 004012DE . E8 4F000000 call KGM1Tal.00401332 ; 初步检测 004012E3 . 68 53304000 push KGM1Tal.00403053 ; ASCII "ZWATRQLCGHPSXYENVBJDFKMU" 004012E8 . E8 C9000000 call KGM1Tal.004013B6 ; 关键1 004012ED . E8 DC010000 call KGM1Tal.004014CE ; 关键2 004012F2 . 6A 00 push 0x0 ; /Result = 0x0 004012F4 . FF75 08 push dword ptr ss:[ebp+0x8] ; |hWnd = 00401000 004012F7 . E8 9A020000 call 
         
           ; \EndDialog 004012FC . EB 26 jmp short KGM1Tal.00401324 ; 跳转到返回,消息处理结束 这段代码中有三个重要的代码段,都要进入分析 初步检测 00401332 $ 33C0 xor eax,eax ; eax清零 00401334 . B9 00000000 mov ecx,0x0 ; ecx清零 00401339 . BE 23304000 mov esi,KGM1Tal.00403023 ; 这个地址存储着序列号的第一个字符 0040133E . 8A06 mov al,byte ptr ds:[esi] ; 取序列号第一个字符 00401340 . EB 10 jmp short KGM1Tal.00401352 ; 无条件跳转,可能有循环 00401342 > 0FB6C0 movzx eax,al 00401345 . 80B8 50314000>cmp byte ptr ds:[eax+0x403150],0x2 ; 从0x403150开始存了一系列数,分析知只有序列号为A-Z,下面跳转才不会实现 0040134C . 75 0A jnz short KGM1Tal.00401358 ; 跳转实现,完蛋 0040134E . 41 inc ecx 0040134F . 8A0431 mov al,byte ptr ds:[ecx+esi] ; 取序列号下一位 00401352 > 3C 00 cmp al,0x0 ; 跟0比较,序列号为空则下面跳转不能执行了 00401354 .^ 77 EC ja short KGM1Tal.00401342 ; 跳转又跳回此处有循环,al = 0结束循环,序列号为空直接完蛋 00401356 . EB 07 jmp short KGM1Tal.0040135F ; 这条语句必须执行 => 序列号由A-Z组成 00401358 > C605 44304000>mov byte ptr ds:[0x403044],0x40 ; 经错误尝试,这条代码一旦执行,就完蛋了 0040135F > BE 00304000 mov esi,KGM1Tal.00403000 ; 指向用户名 00401364 . 33C9 xor ecx,ecx 00401366 . B8 01000000 mov eax,0x1 0040136B . 33D2 xor edx,edx ; ntdll.KiFastSystemCallRet 0040136D . C705 45304000>mov dword ptr ds:[0x403045],0x0 00401377 > B9 00000000 mov ecx,0x0 ; 循环开始 0040137C . 8A0C32 mov cl,byte ptr ds:[edx+esi] ; 取用户名第一个字符 0040137F . 80F9 00 cmp cl,0x0 00401382 . 74 09 je short KGM1Tal.0040138D ; 空则结束循环 00401384 . 42 inc edx ; ntdll.KiFastSystemCallRet 00401385 . 000D 45304000 add byte ptr ds:[0x403045],cl ; 加到0x403045这!!!,记住它的含义,用户名字符值和取最低字节 0040138B .^ EB EA jmp short KGM1Tal.00401377 0040138D > A1 45304000 mov eax,dword ptr ds:[0x403045] ; 开始做除法运算,除数是ecx,被除数是eax 00401392 . B9 18000000 mov ecx,0x18 00401397 . 99 cdq 00401398 . F7F9 idiv ecx 0040139A . 8815 4F304000 mov byte ptr ds:[0x40304F],dl ; 余数存起来0x40304F,记住它的含义 004013A0 . 8A0D 44304000 mov cl,byte ptr ds:[0x403044] ; 以下两条代码印证对序列号A—Z的检测,把循环和判断分开了,起到迷惑作用 004013A6 . 80F9 40 cmp cl,0x40 004013A9 . 75 05 jnz short KGM1Tal.004013B0 ; 不跳就错误 004013AB . E9 45010000 jmp KGM1Tal.004014F5 ; 上面不跳,这个跳,提示输入错误 004013B0 > E9 CB000000 jmp KGM1Tal.00401480 ; 跳转去检测是否有软件断点,指出第二个字符是E。内部代码略 004013B5 . C3 retn 这段代码检测序列号的组成,对用户名做了一些处理,作者把很多事情分开写了 关键1 004013B6 $ 55 push ebp 004013B7 . 8BEC mov ebp,esp 004013B9 . 68 23304000 push KGM1Tal.00403023 ; ASCII "KEJTMPWUDM" 004013BE . E8 7D010000 call KGM1Tal.00401540 ; 检查位数,返回值必须是0xA => 10位数,内部代码略 004013C3 . 83F8 0A cmp eax,0xA 004013C6 . 0F85 29010000 jnz KGM1Tal.004014F5 ; 跳转就完蛋 004013CC . BE 23304000 mov esi,KGM1Tal.00403023 ; ASCII "KEJTMPWUDM" 004013D1 . B8 00000000 mov eax,0x0 004013D6 . BB 00000000 mov ebx,0x0 004013DB . 33C9 xor ecx,ecx 004013DD . EB 06 jmp short KGM1Tal.004013E5 ; 循环开始 004013DF > 8A0C30 mov cl,byte ptr ds:[eax+esi] ; 取序列号第一个字符 004013E2 . 03D9 add ebx,ecx ; 加到ebx上 004013E4 . 40 inc eax 004013E5 > 83F8 09 cmp eax,0x9 ; 可知,把前9位加起来 004013E8 .^ 72 F5 jb short KGM1Tal.004013DF 004013EA . 8BC3 mov eax,ebx ; 准备做除法 004013EC . B9 09000000 mov ecx,0x9 004013F1 . 99 cdq 004013F2 . F7F9 idiv ecx 004013F4 . A3 4A304000 mov dword ptr ds:[0x40304A],eax ; 序列号所有字符加起来除以9,商保存在0x40304A,要记住它 004013F9 . 8B7D 08 mov edi,dword ptr ss:[ebp+0x8] ; 指向内置字典ZWATRQQLCGHPSXYENVBJDFKMU(24位) 004013FC . 8A15 4F304000 mov dl,byte ptr ds:[0x40304F] ; 取用户名经运算得到的余数 00401402 . 8AC2 mov al,dl ; 与序列号经运算得到的商的低字节相加得到一个索引 00401404 . 3C 18 cmp al,0x18 00401406 . 76 02 jbe short KGM1Tal.0040140A 00401408 . 2C 18 sub al,0x18 0040140A > A2 4E304000 mov byte ptr ds:[0x40304E],al ; 以上语句保证了这个索引在24以内,并保存之 0040140F . 33C0 xor eax,eax 00401411 . A0 4E304000 mov al,byte ptr ds:[0x40304E] ; 再次取出索引 00401416 . 8A2438 mov ah,byte ptr ds:[eax+edi] ; 从字典中取得参考值 00401419 . 8A36 mov dh,byte ptr ds:[esi] ; 从输入序列号中取得第一个字符 0040141B . 38F4 cmp ah,dh ; 比较第一个字母 0040141D . 0F85 D2000000 jnz KGM1Tal.004014F5 ; 不等就完蛋 00401423 . 80EE 41 sub dh,0x41 ; 处理第一个字符 00401426 . 8AF2 mov dh,dl ; 又被赋其他值,上一条语句无用 00401428 . B4 00 mov ah,0x0 0040142A . A2 4E304000 mov byte ptr ds:[0x40304E],al ; 保存索引 0040142F . 33C0 xor eax,eax 00401431 . A0 4E304000 mov al,byte ptr ds:[0x40304E] ; 取出索引 00401436 . 02C2 add al,dl ; 把余数加到索引上,其实这两个值现在是一样的,之后索引就变了 00401438 . 3C 18 cmp al,0x18 0040143A . 76 02 jbe short KGM1Tal.0040143E 0040143C . 2C 18 sub al,0x18 0040143E > B9 02000000 mov ecx,0x2 00401443 . 8A2438 mov ah,byte ptr ds:[eax+edi] ; 从字典中取得参考值 00401446 . 8A3431 mov dh,byte ptr ds:[ecx+esi] ; 从输入序列号中取得第三个字符 00401449 . 38F4 cmp ah,dh ; 比较第三个字母 0040144B . 0F85 A4000000 jnz KGM1Tal.004014F5 ; 不等就完蛋 00401451 . EB 24 jmp short KGM1Tal.00401477 ; 循环开始,以下检测序列号的方法就统一了 00401453 > A2 4E304000 mov byte ptr ds:[0x40304E],al ; 保存索引 00401458 . 33C0 xor eax,eax 0040145A . A0 4E304000 mov al,byte ptr ds:[0x40304E] ; 取出索引 0040145F . 80EE 41 sub dh,0x41 ; 第三个字符减0x41 00401462 . 8AD6 mov dl,dh 00401464 . 41 inc ecx 00401465 . 02C2 add al,dl ; 把差值加到索引上 00401467 . 3C 18 cmp al,0x18 00401469 . 76 02 jbe short KGM1Tal.0040146D 0040146B . 2C 18 sub al,0x18 0040146D > 8A2438 mov ah,byte ptr ds:[eax+edi] ; 从字典中取得参考值 00401470 . 8A3431 mov dh,byte ptr ds:[ecx+esi] ; 从输入序列号中取得下一个字符 00401473 . 38F4 cmp ah,dh 00401475 . 75 7E jnz short KGM1Tal.004014F5 ; 不等就完蛋 00401477 > 83F9 08 cmp ecx,0x8 ; 循环7次,还差一个,至此大半分析都结束了 0040147A .^ 72 D7 jb short KGM1Tal.00401453 0040147C . C9 leave 0040147D . C2 0400 retn 0x4 这段代码验证了序列号前9位的正确性 关键2 004014CE /$ BE 23304000 mov esi,KGM1Tal.00403023 ; ASCII "KEJTMPWUDM" 004014D3 |. A1 4A304000 mov eax,dword ptr ds:[0x40304A] ; 取出商低字节 004014D8 |. 8A5E 09 mov bl,byte ptr ds:[esi+0x9] ; 取序列号最后一个字符 004014DB |. 38D8 cmp al,bl 004014DD |. 75 16 jnz short KGM1Tal.004014F5 ; 不等就完蛋,成功! 004014DF |. B8 6C304000 mov eax,KGM1Tal.0040306C ; ASCII "Great Job!" 004014E4 |. 8BD8 mov ebx,eax 004014E6 |. 83C3 0B add ebx,0xB 004014E9 |. 6A 00 push 0x0 ; /Style = MB_OK|MB_APPLMODAL 004014EB |. 50 push eax ; |Title = 00004413 ??? 004014EC |. 53 push ebx ; |Text = 000002BB ??? 004014ED |. 6A 00 push 0x0 ; |hOwner = NULL 004014EF |. E8 B4000000 call 
          
            ; \MessageBoxA 004014F4 |. C3 retn 这段代码说明了序列号最后一位的来历 4.注册机代码 python版(用python一边破解一边写很快) def main(): refer_list = ['Z', 'W', 'A', 'T', 'R', 'Q', 'L', 'C', 'G', 'H', 'P', 'S', \ 'X', 'Y', 'E', 'N', 'V', 'B', 'J', 'D', 'F', 'K', 'M', 'U'] name = raw_input('Enter your name - ') name_1ist = list(name) code_str = '' code_list = list(code_str) sum_name = 0 sum_serial = 0 # 1st code for item in name_1ist: sum_name += ord(item) remainder = sum_name % 256 % 24 offset = remainder code_list += refer_list[offset] # 2nd code code_list += ['E'] # 3rd code offset = (offset + remainder)%24 code_list += refer_list[offset] # 4th to 9th for i in range(2, 8): offset = (ord(code_list[i]) - 0x41 + offset)%24 code_list += refer_list[offset] # 10th for item in code_list: sum_serial += ord(item) code_list += chr(sum_serial/9%256) # result code_str = ''.join(code_list) print 'your code is ', code_str if raw_input('press any key to exit...'): pass if __name__ == '__main__': main() 
           
          
         
       
      
      
     
     
    
    
   
   

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值