|
作者: yongmin [yongmin] 论坛用户 | 登录 |
作者:鹭影依凌 转贴自:一蓑烟雨 声明: 1.以下内容都是个人在学习中的一些心得体会,写给新手的,高手飘过 2.文章难免有疏漏之处,欢迎各位兄弟批评指正 3.本文原创于UnPaKcN,如转载,请保持文章完整性 有了第一篇的基础知识学习 下面我们以AuRRa公司的龙头产品Media-WorkShop为实例来进行第二篇 学习目标 1.培养良好的"破解书写习惯" 2.学会判断程序段代码的功能 3.学会逆向分析和爆破 一、培养良好的破解习惯 1.在指令右侧注释栏填写必要的跟踪注释 应该着重注释的内容 (1).寄存器的值(数值/字符串) (2).运算表达式 便于完成将来对算法的总结 (3).CALL功能 确定哪些CALL是关键CALL,哪些是无大意义的CALL 比如: A.经过一个CALL后,字符串中的小写字母全部变为大写字母 这样的CALL,显然没有必要跟进去 B.又如 CALL XXXXXXXX TEST EAX,EAX JE xxxxxxxx //跳向注册成功 这样的CALL一般来说是关键CALL(算法CALL) 很有必要跟进去探个究竟 (4).代码段的功能 确定代码段的功能,以便从全局的角度对整个注册流程进行分析 2.调试过程中,不同阶段,观察侧重点不同 A.首次调试 通常需要着重观察反汇编窗口和信息提示窗口 B.区段集中调试时期 应多注意寄存器和堆栈窗口 C.某条特殊指令 集中跟随数据窗口 3.一气呵成,又不拘泥 所谓一气呵成,就是指已经有了思路,那么就要一鼓作气,将其进行到底 不然,开工一半,中途搁置 过几天再拿起来...晕,又得从头分析 不拘泥 不是每个软件都是软柿子,不是每个软件设计者都不懂得ANTI-DBG 要知道,现在的程序员的软件保护意识越来越强的!! 暂时没有思路就先放一放,说生活中的某个细节会突然触发你的灵感 如果只是为了破解而破解,那么建议你放弃CRACKER这条路 学习破解只是一种很好的学习别人程序设计思路的方式 并在学习过程中体会到自身提高的乐趣 二、过程分析笔记 !!!阅读建议: 由于本篇是完全面向新手的文章,读者可以先下载注释笔记,然后浏览下 然后边调试边看以下分析内容,效果会更好 切记:一定要自己亲手调试 在本例中 我们用提示字符串进行注册代码段的定位 提示信息:"invalid username or registration code" 向上看代码,一般是在程序段的段首下断点 关于"段首"的概念 可以这样理解:在向上代码段中出现第一个retn的下方 一般是push esp,push -1,push A,B格式的 本例中,我们在地址0045BDB0处F2下断 流程大致应该是这样的: 1.运行程序,输入试练码,程序被断下 2.进行首次跟踪 主要完成些重要数据的记录(旁白注释) 3.将程序细分段,确定各个分段的作用 4.进行细跟踪 搞定算法 实例讲解: 输入试练码: ID:luying10 SN:9876543210abcdef 点击确定,程序被OD断下 注意观察代码窗口、信息提示窗口、(寄存器窗口只要注意字符串类型数据就好) 并随时记录有用信息 ;====================================================================| 0045BDB0 . 6A FF push -1 ; //断在这 0045BDB2 . 68 983A4700 push 00473A98 ; 0045BDB7 . 64:A1 0000000>mov eax, dword ptr fs:[0] 0045BDBD . 50 push eax 0045BDBE . 64:8925 00000>mov dword ptr fs:[0], esp 0045BDC5 . 51 push ecx 0045BDC6 . 53 push ebx 0045BDC7 . 56 push esi 0045BDC8 . 8BF1 mov esi, ecx 0045BDCA . 57 push edi 0045BDCB . 8D4C24 0C lea ecx, dword ptr [esp+C] 0045BDCF . E8 D4BE0000 call <jmp.&MFC42.#540> 0045BDD4 . 6A 01 push 1 0045BDD6 . 8BCE mov ecx, esi 0045BDD8 . C74424 1C 000>mov dword ptr [esp+1C], 0 0045BDE0 . E8 43C30000 call <jmp.&MFC42.#6334> 0045BDE5 . E8 6CC20000 call <jmp.&MFC42.#1168> 0045BDEA . 8B48 04 mov ecx, dword ptr [eax+4] 0045BDED . E8 78C30000 call <jmp.&MFC42.#1669> ; 0045BDF2 . 68 E8030000 push 3E8 ; /Timeout = 1000. ms 0045BDF7 . FF15 3C524700 call dword ptr [<&KERNEL32.Sleep>] ; \Sleep 0045BDFD . E8 54C20000 call <jmp.&MFC42.#1168> 0045BE02 . 8B48 04 mov ecx, dword ptr [eax+4] 在经过地址0045BDF7的CALL时候,程序停顿了一小会儿 我们看英文注释也大概猜到这段代码的作用了----加入延时验证,防止暴力攻击 ;--------------------------------------------------------------------| 0045BE05 . E8 5AC30000 call <jmp.&MFC42.#2652> ; 0045BE0A . 8B46 64 mov eax, dword ptr [esi+64] ; 0045BE0D . 8B4E 60 mov ecx, dword ptr [esi+60] ; 程序运行到0045BE0A这行时候 信息提示窗口提示dword ptr [esi+64] = "9876543210abcdef" [esi+64]中保存的是序列号 这不正是我们最开始天的序列号嘛 运行本行代码后,发现EAX变成了序列号 同样,运行0045BE0D这行,ECX被赋予了用户名 ;--------------------------------------------------------------------| 0045BE10 . 8D5E 64 lea ebx, dword ptr [esi+64] ; 0045BE13 . 8D7E 60 lea edi, dword ptr [esi+60] ; 这两行只有值,没有所谓的字符串的东东 我们学过LEA是装载有效地址的指令 [esi+64]中保存的是序列号 那么,执行完lea ebx, dword ptr [esi+64]后,EBX中保存的就应该是序列号的地址 同样,EDI中保存用户名的地址 ;--------------------------------------------------------------------| 0045BE16 . 50 push eax ; 序列号压栈 0045BE17 . 51 push ecx ; 用户名压栈 压栈操作,不用多解释,下面是一个CALL,我们首次跟踪,先F8过 ;--------------------------------------------------------------------- 0045BE18 . E8 83430000 call 004601A0 ; 0045BE1D . 83C4 08 add esp, 8 0045BE20 . 85C0 test eax, eax 0045BE22 . 75 1F jnz short 0045BE43 ; 运行了0045BE18这行的CALL后,EAX变为0 继续F8,来到跳转,跳转指向是绿色,跳转未实现 下面英文提示invalid username or registration code 显然再继续下去是提示注册失败咯 ;--------------------------------------------------------------------| 0045BE24 . 6A 40 push 40 0045BE26 . 68 9C7C4900 push 00497C9C ; sorry 0045BE2B . 68 707C4900 push 00497C70 ; invalid username or registration code sorry 0045BE30 . 8BCE mov ecx, esi 0045BE32 . E8 EDC30000 call <jmp.&MFC42.#4224> ; 提示注册失败,点击确定按钮,继续F8 ;--------------------------------------------------------------------| 0045BE37 . C705 082C4E00>mov dword ptr [4E2C08], 0 ; 0045BE41 . EB 76 jmp short 0045BEB9 ; //跳走 程序跳走 ;跳到这里来 0045BEB9 > 8BCE mov ecx, esi ; ->来到这 0045BEBB . E8 74C20000 call <jmp.&MFC42.#4853> 0045BEC0 . 8D4C24 0C lea ecx, dword ptr [esp+C] 0045BEC4 . C74424 18 FFF>mov dword ptr [esp+18], -1 0045BECC . E8 8FBD0000 call <jmp.&MFC42.#800> 0045BED1 . 8B4C24 10 mov ecx, dword ptr [esp+10] 0045BED5 . 5F pop edi 0045BED6 . 5E pop esi 0045BED7 . 5B pop ebx 0045BED8 . 64:890D 00000>mov dword ptr fs:[0], ecx 0045BEDF . 83C4 10 add esp, 10 0045BEE2 . C3 retn ; //结束 ;====================================================================| 首轮跟踪结束,总结下: 1.关键跳转0045BE22 2.关键CALL->004601A0 3.标志位EAX EAX = 0 注册失败 EAX = 1 注册成功 4.可疑处,地址:0045BE7 mov dword ptr [4E2C08], 0 两个研究方向 1.关键CALL->004601A0 2.可疑的赋值 首先研究关键CALL ;====================================================================| ;在地址0045BE18处跟进关键CALL->004601A0 ;--------------------------------------------------------------------| 004601A0 /$ 53 push ebx ; OD下方显示:本地调用来自 00428B46, 00429225, 0044BB64, 0045120C, 00455368, 0045BE18, 0045E824 004601A1 |. 55 push ebp 注意:从这里我们可疑看到,程序有多处代码调用了CALL=>004601A0 这也验证了为了程序只修改0014BE22处的关键跳转不能达到完全爆破的目的 ;--------------------------------------------------------------------| 004601A2 |. 8B6C24 0C mov ebp, dword ptr [esp+C] ; EBP = [esp+C] = 用户名 004601A6 |. 56 push esi 004601A7 |. 57 push edi 004601A8 |. BE 84014A00 mov esi, 004A0184 ; ESI = 4A0184 004601AD |. 8BC5 mov eax, ebp ; EAX = 用户名 004601AF |> 8A10 /mov dl, byte ptr [eax] ; 用户名的第1个字符 004601B1 |. 8A1E |mov bl, byte ptr [esi] 004601B3 |. 8ACA |mov cl, dl 004601B5 |. 3AD3 |cmp dl, bl 004601B7 |. 75 1E |jnz short 004601D7 ; 跳转实现//跳出循环体(跳至EAX置零) ;---------------------------- 004601B9 |. 84C9 |test cl, cl 004601BB |. 74 16 |je short 004601D3 ; 004601BD |. 8A50 01 |mov dl, byte ptr [eax+1] 004601C0 |. 8A5E 01 |mov bl, byte ptr [esi+1] 004601C3 |. 8ACA |mov cl, dl 004601C5 |. 3AD3 |cmp dl, bl 004601C7 |. 75 0E |jnz short 004601D7 004601C9 |. 83C0 02 |add eax, 2 004601CC |. 83C6 02 |add esi, 2 004601CF |. 84C9 |test cl, cl 004601D1 |.^ 75 DC \jnz short 004601AF ; //循环 004601D3 |> 33C0 xor eax, eax ; 004601D5 |. EB 05 jmp short 004601DC ; 程序跳出了循环体,条件是用户名不为空 我们可以看看如果程序从004601D5这跳会怎样 首先跳至004601DC 因为EAX = 0,所以又跳至00460242 接着EAX置零后返回 而我们的主程序在EAX=0的时候是注册失败的 ;---------------------------- 004601D7 |> 1BC0 sbb eax, eax ; 004601D9 |. 83D8 FF sbb eax, -1 ; 004601DC |> 85C0 test eax, eax 004601DE |. 74 62 je short 00460242 ; EAX=1,跳转未实现 综上,我们可疑得出,上段代码是检查用户名是否为空 ;--------------------------------------------------------------------| 004601E0 |. 8B7C24 18 mov edi, dword ptr [esp+18] ; EDI = [esp+18] = 序列号 004601E4 |. BE 84014A00 mov esi, 004A0184 ; ESI = 4A0184 004601E9 |. 8BC7 mov eax, edi ; 004601EB |> 8A10 /mov dl, byte ptr [eax] ; 序列号的第i个字符 004601ED |. 8A1E |mov bl, byte ptr [esi] 004601EF |. 8ACA |mov cl, dl 004601F1 |. 3AD3 |cmp dl, bl 004601F3 |. 75 1E |jnz short 00460213 ; 跳转实现//跳出循环体(跳至EAX置零) 004601F5 |. 84C9 |test cl, cl 004601F7 |. 74 16 |je short 0046020F ; 004601F9 |. 8A50 01 |mov dl, byte ptr [eax+1] 004601FC |. 8A5E 01 |mov bl, byte ptr [esi+1] 004601FF |. 8ACA |mov cl, dl 00460201 |. 3AD3 |cmp dl, bl 00460203 |. 75 0E |jnz short 00460213 00460205 |. 83C0 02 |add eax, 2 00460208 |. 83C6 02 |add esi, 2 0046020B |. 84C9 |test cl, cl 0046020D |.^ 75 DC \jnz short 004601EB ; //循环 0046020F |> 33C0 xor eax, eax ; 00460211 |. EB 05 jmp short 00460218 ; 程序跳出了循环体,条件是序列号不为空 我们可以看看如果程序从00460211这跳会怎样 首先跳至00460218 因为EAX = 0,所以又跳至00460242 接着EAX置零后返回 而我们的主程序在EAX=0的时候是注册失败的 00460213 |> 1BC0 sbb eax, eax ; 00460215 |. 83D8 FF sbb eax, -1 ; 00460218 |> 85C0 test eax, eax ; 0046021A |. 74 26 je short 00460242 ; EAX=1,跳转未实现 综上,我们可疑得出,上段代码是检查序列号是否为空 ;--------------------------------------------------------------------| 0046021C |. 57 push edi ; 0046021D |. 55 push ebp ; 0046021E |. E8 6DF9FFFF call 0045FB90 ; 00460223 |. 83C4 08 add esp, 8 00460226 |. 85C0 test eax, eax ; 00460228 |. 75 0E jnz short 00460238 ; EDI保存序列号 EBP保存用户名 EAX = 0 跳转未实现,继续 有一个CALL->0045FB90 ,下次跟入 假设跳转实现,程序跳至00460238 EAX = 1,返回-》注册成功 ;--------------------------------------------------------------------| 0046022A |. 57 push edi ; 0046022B |. 55 push ebp ; 0046022C |. E8 8FFBFFFF call 0045FDC0 ; 00460231 |. 83C4 08 add esp, 8 00460234 |. 85C0 test eax, eax ; 00460236 |. 74 0A je short 00460242 ; EDI保存序列号 EBP保存用户名 EAX = 0 跳转实现 EAX = 0,返回,注册失败 假设跳转未实现 EAX = 1,返回,注册成功 ;--------------------------------------------------------------------| 00460238 |> 5F pop edi 00460239 |. 5E pop esi 0046023A |. 5D pop ebp 0046023B |. B8 01000000 mov eax, 1 ; 00460240 |. 5B pop ebx 00460241 |. C3 retn ; 标志位赋值:EAX = 1 //返回 ;--------------------------------------------------------------------| 00460242 |> 5F pop edi ; 跳转来自 004601DE, 0046021A, 00460236 00460243 |. 5E pop esi 00460244 |. 5D pop ebp 00460245 |. 33C0 xor eax, eax ; 00460247 |. 5B pop ebx 00460248 \. C3 retn ; EAX置零//返回 ;====================================================================| 跟进call 0045FB90 ,只有一处调用 跟进call 0045FDC0 ,只有一处调用 我们可以爆破了 在第二层中想办法让EAX的返回值为1 思路. 1.修改关键跳转 地址00460236处jnz改为jmp 2.修改赋值语句 地址00460245处xor eax,eax修改为inc eax(nop填充) 哈哈,这么简单就搞定`````` 还有个可疑点呢,别忘了噢~~~ 我们在运行到0045BE20 . 85C0 test eax, eax 这行时候,修改下EAX的值,将其由0置为1 使下面的跳转实现 ;--------------------------------------------------------------------| 0045BE43 > 8B07 mov eax, dword ptr [edi] ; 0045BE45 . 8D4C24 0C lea ecx, dword ptr [esp+C] 0045BE49 . 50 push eax ; 0045BE4A . 68 547C4900 push 00497C54 ; license to:%s 0045BE4F . 51 push ecx 0045BE50 . E8 07C20000 call <jmp.&MFC42.#2818> 0045BE55 . 8B5424 18 mov edx, dword ptr [esp+18] ; 提示窗口信息(ASCII "License To:luying10 ") ;--------------------------- 0045BE59 . 83C4 0C add esp, 0C 0045BE5C . 8BCE mov ecx, esi 0045BE5E . 6A 40 push 40 0045BE60 . 68 487C4900 push 00497C48 ; thank you 0045BE65 . 52 push edx ; 提示窗口信息(ASCII "License To:luying10 ") 0045BE66 . E8 B9C30000 call <jmp.&MFC42.#4224> ; 提示注册成功 ;--------------------------- 0045BE6B . 57 push edi 0045BE6C . B9 04034A00 mov ecx, 004A0304 ; ASCII "�p?" 0045BE71 . E8 3EBE0000 call <jmp.&MFC42.#858> 0045BE76 . 53 push ebx 0045BE77 . B9 08034A00 mov ecx, 004A0308 ; ASCII "乇?" 0045BE7C . E8 33BE0000 call <jmp.&MFC42.#858> 0045BE81 . C705 082C4E00>mov dword ptr [4E2C08], 1 ; 这块很可疑噢~ mov dword ptr [4E2C08], 1 ;--------------------------- 0045BE8B . 8B3F mov edi, dword ptr [edi] ; 0045BE8D . 57 push edi ; 0045BE8E . 68 A0F24800 push 0048F2A0 ; username 0045BE93 . 68 94F24800 push 0048F294 ; register 0045BE98 . B9 F4014A00 mov ecx, 004A01F4 0045BE9D . E8 5EC7FEFF call 00448600 ; EDI为用户名 0045BEA2 . 8B1B mov ebx, dword ptr [ebx] ; 0045BEA4 . B9 F4014A00 mov ecx, 004A01F4 0045BEA9 . 53 push ebx ; 0045BEAA . 68 84F24800 push 0048F284 ; registercode 0045BEAF . 68 94F24800 push 0048F294 ; register 0045BEB4 . E8 47C7FEFF call 00448600 ; EBX为序列号 ;--------------------------- 0045BEB9 > 8BCE mov ecx, esi ; 0045BEBB . E8 74C20000 call <jmp.&MFC42.#4853> 0045BEC0 . 8D4C24 0C lea ecx, dword ptr [esp+C] 0045BEC4 . C74424 18 FFF>mov dword ptr [esp+18], -1 0045BECC . E8 8FBD0000 call <jmp.&MFC42.#800> 0045BED1 . 8B4C24 10 mov ecx, dword ptr [esp+10] 0045BED5 . 5F pop edi 0045BED6 . 5E pop esi 0045BED7 . 5B pop ebx 0045BED8 . 64:890D 00000>mov dword ptr fs:[0], ecx 0045BEDF . 83C4 10 add esp, 10 0045BEE2 . C3 retn ; //结束 ;====================================================================| 注册失败mov dword ptr [4E2C08], 0 注册成功mov dword ptr [4E2C08], 1 分析,将注册失败后[4E2C08]也赋值为1 但在整个程序中,有很多地方调用关键CALL 并且两段是不相干的代码 修改地址赋值不能改变关键CALL 修改关键CALL后该地址赋值就相应随之发生变化 结论:修改此处,不能达到完美爆破的目的 只修改关键CALL就可以达到完美爆破的目的 通过本文,主要是让兄弟们学会如何给程序加注释,和确定程序代码段的功能 两点说明: 1.恰当的书写注释 注释不是越多越好,太多的注释反而影响程序的阅读 用最少的文字表达概括语句含义 2.注册流程分析的意义 明确区段代码的作用,为以后定位注册关键代码段做铺垫,减少不必要的代码分析 Now,check it yourself```Come On,U'll be the best!! 鹭影依凌[2007.12.15] |
地主 发表时间: 07-12-17 10:40 |
|
20CN网络安全小组版权所有
Copyright © 2000-2010 20CN Security Group. All Rights Reserved.
论坛程序编写:NetDemon
粤ICP备05087286号