您的位置:首页 > 其它

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

2016-08-10 15:42 155 查看
这是我做的第一个比较复杂的破解,还写了注册机(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()
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息