【文章标题】: 算法分析入门教程实战篇及应用篇
【文章作者】: NBA2005
【作者邮箱】: NBA2008PE@126.com
【作者QQ号】: 382309369
【软件名称】: CRACK ME 2007之Splish
【软件大小】: 232KB
【下载地址】: PEDIY CRACK ME 2007 序列号 爱在天涯之Splish
【加壳方式】: 无
【保护方式】: 无
【编写语言】: MASM32 / TASM32
【使用工具】: OD
【操作平台】: WINXP
【软件介绍】: 供我写算法教程的好例子
【作者声明】: 围绕算法分析的相关知识点进行介绍,浅薄之处望海涵!!
--------------------------------------------------------------------------------
【详细过程】
要上网,踏新浪
找资料,上百度
当黑客,乘黑鹰
学破解,来看雪
算法分析是道坎
破解知识都沾边
要想一直打此过
文武全才是方向
曾经夸下海口,要写一篇关于算法分析入门教程的文章。新春佳节之际,祝各位破友心情愉快,破解顺利,破解技术节节高。顺便在看雪论坛上又浏览了一圈,发现算法分析的文章确实都很好,但似乎没有一位对算法分析进行系统、全面的阐述的,对初学算法分析的人的实战困惑解答很少。倒是发现了一位好同志laomms的好文章自效验,读来思路清晰,简洁易懂,内容前卫却又深入浅出,同时作者还细心地配了附件供练习、体会。(这样的好同志居然没有看雪勋章,令人叫屈)。看雪论坛之藏龙卧虎是破解界公认的。CRACK ME的难度也是业内公认的。感谢好同志laomms,你的好文激发了我的创作热情。但真正开始写破文时,又觉得困难重重,自己的能力有限啊。
PEDIY CRACK ME 2007出来了,这凝聚着看雪论坛上诸多默默无闻的高手的心血结晶。但,你会用它吗?我的理解是CRACK ME是给你自己独立做的,不是用来欣赏的。要学好破解的最好方法就是不断地自己独立地破解而不是在论坛上焦急地等待别人的关照。只有不断地实践,才能体会破解书中散落的种种技术细节问题。很多菜鸟也懂这个道理,但心里有一种急功近利和畏难心理在做怪,总是迫不急待地看破文答案。这不是一个好习惯。其实开始学破解时,好几天没有进展是很平常的。这是好事。百分之九十的人都会这样。为什么呢?每位破解高手都有他的强项和弱项。多数人遇到的障碍,往往是他的弱项。你想提高自己,弥补弱项,就以它为目标钻研下去。很简单的道理,强项都不是天生的。要想既快又扎实地学好破解,必须养成一个好习惯。
什么,这不是算法分析吗?你怎么竟扯好习惯?没错。要学好算法分析,必须要有一个好习惯。否则事倍功半。
学破解的好习惯(也是做CRACK ME 的好习惯):
1.坚持独立完成破解(CRACK ME)。贵在坚持。开始时很吃力,成果也不明显。没关系。万事开头难,坚持。
2.独立地查找相关资料并研读,弥补自己的弱项。案头常备基础破解教材查询是绝大多数破解高手的好习惯。很奇怪,竟没有一个高手强调这一点,尽管他就是这么做的。我的案头放的是段钢著的《家蜜和街蜜II》--看雪老大的风流韵事。绝大多数破解的基础问题,在上面都能找到答案。不需要在网上无头的苍蝇般乱搜索。建议先看一遍段钢著的《加密和解密II》*,再来做CRACK ME。因为CRACK ME是综合考试,不过是开卷考试啊。
3.打开记事本,记录破解过程。这也是许多高手的好习惯,有助于整理破解的思路。
4.破解成功后,对照相应的破文,印证和加深对破解的理解。
5.复习相关的破解知识,总结心得和收获。
6.如果三天以上还做不出来,可以看看前辈的破文,对照段钢著的《加密和解密II》复习相关的破解知识点。并就书中和CRACK ME中的难点请教高手指点。
7.坚持、坚持、再坚持。
8.没有相应的破文,无人回答的CRACK ME问题,不妨暂时放入破解悬案库中。以后等功力提高了再看看。或者借鉴同类型的破文,寻找破解的思路、灵感。
*PUSH 001:段钢著的《加密和解密II》。玩笑一下,才能更好地集中注意力欣赏。呵呵,希望老大不要象Petnt大侠那样放在心上。
好了,我先进行一下测试,你有没有达到学算法分析的水平。这是残酷的淘汰赛,加油。2008年NBA2005算法分析实战淘汰赛正式开张,看以下程序段代码:
00404444:CALL对这个CRACK ME Splish你会爆破吗?
00404466:CMP EAX,0
00404468:JE 00404666 CALL不会
00404486:CALL 继续下文
......
CALL 不会:
00404666:CALL 你数学成绩好吗?
00404688:CMP EAX,0
00404848:JE 00409999 CALL放弃破解梦 寻找能发挥你才华的领域
00404868:CALL 复习爆破知识再来
.......
CALL对这个CRACK ME Splish你会爆破吗?
地球人都知道,要学算法分析,先会爆破。为什么?许多高手遇到这些菜鸟的问题还真蒙了,书上
说的啊。这个结论的理论基础是什么?为什么数学好的人学破解有天分?许多破解书上都没有答案。
我个人的答案:破解是以程序为目标的。而程序的关键就是程序的流程。相对应的,破解的最最基础
就是对程序流程的理解是否透彻。通常情况下,会爆破意味着对程序流程的理解比较透彻,而算法分
析的基石就是程序流程。有数学天分的人,往往逻辑性强。程序流程的要求就是强调逻辑性。只有自
己独立地做CRACK ME,才会对这些基本的问题进行思考,才会学到许多实用的知识。
好,算法分析入门教程第二个测试来了,这回不是淘汰赛了。你会用各种破解工具吗?破解工具的
熟练使用,有助于我们集中精神算法分析。不会不要紧,可以边看边学。建议你先看看相关的工具使
用教程。
算法分析入门教程实战篇第一课:断点篇
看过了段钢著的《加密和解密II》和CCDEBUGER及其他高手的OLLYDBG入门系列,我们总结一下断点的常用手段:
1.根据注册成功与否的提示字符串查找字符串。这是最普通和最常用的。OD是右键选查找,有
OD自带和插件两种字符串的查找方法。破解老手都知道,现在大多数软件的字符串查找后的内容是动态的。
Ultra String Reference
Address Disassembly Text String
00401000 push 0 (initial cpu selection)
004010B5 mov dword ptr [ebp-8], 00403000 ourwindow
0040110B push 0040300A splish, splash
00401110 push 00403000 ourwindow
004011B1 push 00403086 hard coded:
004011B6 push 00403060 static
004011DC push 00403092 name:
004011E1 push 00403060 static
00401207 push 00403098 serial:
0040120C push 00403060 static
00401237 push 0040305B edit
0040126A push 0040305B edit
0040129D push 0040305B edit
004012D9 push 00403020 check hardcoded
004012DE push 00403019 button
0040130F push 00403030 name/serial check
00401314 push 00403019 button
004013BF push 0040300A splish, splash
004013C4 push 0040138E congratulations, you got the hard coded serial
004013D4 push 0040300A splish, splash
004013D9 push 00403067 sorry, please try again.
00401433 push 0040300A splish, splash
00401438 push 004030D9 your mission is to disable the splash screen, find the hardcoded serial\n\nand to keygen the name/serial part. this level is to make sure you understand\n\nhow to use your tools and what is going on in the program. these are three very\n\nbasic and e ...
004014DD mov dword ptr [ebp-8], 0040147F splash_class
00401528 push 0040300A splish, splash
0040152D push 0040147F splash_class
00401680 push 0040300A splish, splash
00401685 push 004030A0 please enter your name.
00401695 push 0040300A splish, splash
0040169A push 004030B8 please enter your serial number.
004016CF push 0040300A splish, splash
004016D4 push 00403042 good job, now keygen it.
004016E4 push 0040300A splish, splash
004016E9 push 00403067 sorry, please try again.
在下面认为有价值的字符串分别设断,有经验了就可选其中最有把握的设断。
check hardcoded
Splish splash
congratulations, you got the hard coded serial
good job, now keygen it.
Sorry try again.
2.根据函数设断。CTRL+N列出该软件的函数表。
名称位于 Splish
地址 区段 类型 ( 名称 注释
00402070 .rdata 输入 ( USER32.BeginPaint
00402004 .rdata 输入 ( GDI32.CreatePatternBrush
00402060 .rdata 输入 ( USER32.CreateWindowExA
00402068 .rdata 输入 ( USER32.DefWindowProcA
00402064 .rdata 输入 ( USER32.DispatchMessageA
0040203C .rdata 输入 ( USER32.EndPaint
00402018 .rdata 输入 ( KERNEL32.ExitProcess
0040205C .rdata 输入 ( USER32.GetClientRect
00402014 .rdata 输入 ( KERNEL32.GetCommandLineA
00402058 .rdata 输入 ( USER32.GetMessageA
00402010 .rdata 输入 ( KERNEL32.GetModuleHandleA
00402034 .rdata 输入 ( USER32.GetSystemMetrics
0040200C .rdata 输入 ( KERNEL32.GetTickCount
00402024 .rdata 输入 ( USER32.GetWindowTextA
00402020 .rdata 输入 ( USER32.LoadBitmapA
00402038 .rdata 输入 ( USER32.LoadCursorA
00402028 .rdata 输入 ( USER32.LoadIconA
0040202C .rdata 输入 ( USER32.LoadMenuA
00402030 .rdata 输入 ( USER32.MessageBoxA
0040206C .rdata 输入 ( USER32.PostQuitMessage
00402074 .rdata 输入 ( USER32.RegisterClassExA
00402040 .rdata 输入 ( USER32.SendMessageA
00402000 .rdata 输入 ( GDI32.SetBkMode
00402044 .rdata 输入 ( USER32.SetFocus
00402048 .rdata 输入 ( USER32.SetMenu
0040204C .rdata 输入 ( USER32.ShowWindow
00402050 .rdata 输入 ( USER32.TranslateMessage
00402054 .rdata 输入 ( USER32.UpdateWindow
00401000 .text 输出 <模块入口点> (initial cpu selection)
我认为通常破解软件重要的有:
00402018 .rdata 输入 ( KERNEL32.ExitProcess
00402058 .rdata 输入 ( USER32.GetMessageA
0040200C .rdata 输入 ( KERNEL32.GetTickCount
00402024 .rdata 输入 ( USER32.GetWindowTextA
00402030 .rdata 输入 ( USER32.MessageBoxA
爱在天涯的断点下在 GetWindowTextA ( bp GetWindowTextA ) 。我对CRACK ME的理解是给我们做破解的练习题。既然是练习就应该多种方法尝试。
3.内存和硬件断点,是算法分析的好伙伴。后面我将会进一步的阐述。
4.无提示的断点法。这是许多初学者的难点,一般的教材上也没有详细的论述。许多
破解高手只是反复地实验,很少总结回顾。这不是好习惯。我自己的总结经验就是
断点的设定应根据程序的相应事件进行着手,这是设断总的思路原则。具体类型有:
A.Windows的窗口消息,见第一卷 OllyDBG 入门系列 第五篇 消息断点及 RUN 跟踪
B.相应的事件。如我遇到的一价值三千多的教育软件光盘,不是光盘就无声退出,没有
任何提示。在设断的效率问题上,我自己总不满意,成了我心中的悬案。看了laomms
(常见自校检分析实例),忽然有所启发:退出本身就是一个事件,设断退出函数,
然后逆向追踪。试验几个破解的悬案,果然效率大大地高。他山之玉,可以攻石。特取
laomms(常见自校检分析实例)中的例子说明,在此感谢laomms的好文赐我灵感。啥?
侵权?你知道不,laomms是个女的,身材超赞的。还不懂?情人节快到了,明白了吗?
我俩分啥彼此。
下例子取自laomms(常见自校检分析实例):http://bbs.pediy.com/showthread.php?threadid=28298
我们就是要从退出函数入手。软件退出一般都是调用ExitProcess、PostQuitMessage之类的,我们用OD载入sample2-change.EXE,从输入表中我们可以看出软件是调用ExitProcess退出的。于是在OD中下断BP ExitProcess,F9运行,断下后看堆栈信息:
0012FEB8 004015B5 /CALL 到 ExitProcess 来自 sample2-.004015AF //从这里我们可以看出ExitProcess的调用地方是在004015AF
0012FEBC 00000000 \ExitCode = 0
0012FEC0 20DFA6E6
在OD中CTRL+G,输入004015AF:
004015AF |. FF15 AC514200 CALL DWORD PTR DS:[<&KERNEL32.ExitProces>; \就在这里,向上找这个子CALL的首部
004015B5 |> 8BE5 MOV ESP,EBP
004015B7 |. 5D POP EBP
004015B8 \. C3 RETN
=======================================
004014E0 /$ 55 PUSH EBP ; 找到这里,注意信息栏的内容
004014E1 |. 8BEC MOV EBP,ESP
004014E3 |. 51 PUSH ECX
004014E4 |. 833D F8354200>CMP DWORD PTR DS:[4235F8],1
004014EB |. 75 11 JNZ SHORT sample2-.004014FE
信息栏的内容:
Local Calls from 0040146B, 0040148B, 004014A9, 004014C9
也就是说有四个地方调用ExitProcess退出,因为程序的退出按钮和关闭的叉号也是调用ExitProcess函数的,一般都会在前面几个,我们在内容上右击,“前往CALL来自0040146B”
0040146B |. E8 70000000 CALL sample2-.004014E0 ; \到这里,同样查找首部
00401470 |. 83C4 0C ADD ESP,0C
00401473 |. 5D POP EBP
00401474 \. C3 RETN
============
00401460 /$ 55 PUSH EBP ; 在这里,信息栏提示:Local Calls from 00401072, <ModuleEntryPoint>+11A
00401461 |. 8BEC MOV EBP,ESP
00401463 |. 6A 00 PUSH 0 ; /Arg3 = 00000000
00401465 |. 6A 00 PUSH 0 ; |Arg2 = 00000000
在Local Calls from 00401072上右击,前往CALL来自00401072:
00401048 |. E8 BDFFFFFF CALL sample2-.0040100A
0040104D |. 85C0 TEST EAX,EAX
0040104F |. 74 1F JE SHORT sample2-.00401070 ; 是从这里跳过去的,NOP掉
00401051 |. 8BF4 MOV ESI,ESP
00401053 |. 6A 30 PUSH 30 ; /Style = MB_OK|MB_ICONEXCLAMATION|MB_APPLMODAL
00401055 |. 68 28004200 PUSH sample2-.00420028 ; |Title = "提示"
0040105A |. 68 1C004200 PUSH sample2-.0042001C ; |Text = "正常运行!"
0040105F |. 6A 00 PUSH 0 ; |hOwner = NULL
00401061 |. FF15 B4524200 CALL DWORD PTR DS:[<&USER32.MessageBoxA>>; \MessageBoxA
00401067 |. 3BF4 CMP ESI,ESP
00401069 |. E8 82050000 CALL sample2-.004015F0
0040106E |. EB 07 JMP SHORT sample2-.00401077
00401070 |> 6A 00 PUSH 0
00401072 |. E8 E9030000 CALL sample2-.00401460 ; 就是这里了,最终会调用ExitProcess,向上看是从哪里跳过来
00401077 |> 33C0 XOR EAX,EAX
可以看出,00401072处的CALL最终呼叫ExitProcess退出,所以只要使0040104F处的跳转失效即可,将0040104F的跳转NOP掉后保存,测试运行正常。
C.EXEscope等资源修改软件查看相应资源和ID号,进行设断。
D.动态调试下,在相应的子程序里查找字符串。我上面提过:现在大多数软件的字符串查找后的内容是动态的。
E.万能断点法bpx hmemcpy。效率差了些,将就吧。
要成名,先断背
学算法,先学断
韩红和管彤的断背之说,闹的是沸沸扬扬。为了感谢laomms,我这整了个小花边。无他,想
让laomms出名,只好让他当了会女的。废话,不当女的,我不就和他断背了吗?呵呵,搞点小花絮
轻松一下。哎呀,我这断点论是越说话越多。算法分析啊,注意重点。哥们我偏说断。设断时还要
考虑编程语言的特点。我就取易语言说说自己的心得,其他语言以后大家破解时注意积累:
易语言的下断方法常用的有如下几种:
1.有注册失败提示的,CTRL+N断MessageBoxA函数。
2.无注册失败提示的,易语言的万能断点GetWindowTextA试试。
3.还不行,用通用万用断点。命令:“bpx hmemcpy“。
4.算法分析中,常用的断点法:Alt+M打开内存镜像,找到易语言的标志物“.ecode”段,F2断了它。
动态调试中查看字符串。
无提示的断法详细示范:
《阳小子的第一个易语言编写的EM,希望新手可以找回自信!高手飘!》此论坛上找最新的帖子。
建议自己先尝试能否断下来,找到关键,不会断的再看下文。会断的就挥挥手,不耽误你新年的
娱乐。
************不会断的再看下文************
ALT+M打开内存镜象,于易语言的标志.ecode段F2设断,运行程序,断下。右键查找字符串: Ultra String Reference
Address Disassembly Text String
0041A861 push 0040C0EC !
0041A903 push 0040C0EE m
0041A999 push 0040C0F0 y
0041AA3B push 0040C0F2 x
0041AADD push 0040C0F4 c
0041AB7F push 0040C0EE m
0041AC21 push 0040C0F6 e
0041AC92 push 0040C0F8 believed oneself you are best!\n\n cracker by:
0041ACBE push 0040C12D 恭喜
0041AD07 push 0040C132 已注册
0041AF45 push 0040F4BD handle
0041B07F mov ebp, esp (initial cpu selection)
哈哈,找到了:
0041ACBE push 0040C12D 恭喜
0041AD07 push 0040C132 已注册
练练逆向追踪吧!
这就是动态调试的原理来查找字符串,结合了内存资源段断点法
和字符串查找两种方法。大家可细细领略体会高手阳小子的浪漫
情怀。
算法分析是道坎
破解知识都沾边
要想一直打此过
文武全才是方向
算法分析入门教程实战篇第二课:追出注册码。
这一课很简单,因为重点和难点都在第一课断点上了,而且看雪论坛上的文章是好的不的了。我就不和人家的石头上碰了,哥们我天生就是孬种。为什么老说哥们?怕大家怀疑我是女的呀。这里我再强调一下程序流程,
循环渐多迷人眼,
程序流程指方向,
分清主次破四方,
程序流程念心中。
附算法分析入门教程实战篇基础知识之一:程序流程。
判断明码注册码的一般程序流程:
段1:赋值注册码。
段2:检测注册码的对错,即控制程序流程的语句。特征是条件判断语句。
段3:分道扬镳。对和错两个子程序。
附业余版本的查找注册码:
1.确定提示信息的位置。多数是错误的信息。设断方法文中已经详细叙述了。
2.向上查找关键的跳转。粗粗过一遍程序的大体结构,把握程序的流程和大体结构。
3.再向上寻找关键的CALL 、内存地址或堆栈窗口,结合与你输入的假用户名和假码相关的程序流 程、汇编语言知识的理解综合研判。对大多数程序而言,注册码(特别是明码)往往在关键的跳转不远处的内存地址或堆栈窗口、寄存器窗口中。暗码分析要看懂大致的算法。 哎呀,太抽象了。我们不是学画画的。我也不是。说练就练,WHO怕WHO。就拿CRACK ME 2007之Splish来剖析吧。OD载入,试运行。了解CM作者的题目要求是做CRACK ME的基本常识。点HELP---ABOUT,作者有三个要求:
1.禁止Splash。
2.找到硬件序列号。
3.对Name/Serial部分做出注册机。
第一题是考处理NAG窗口的知识点。如果你不知道如何下手,拿起《加密和解密II》看看,复习基本的相关知识。
字符串查找,发现Splish Splash字符都指向同样的语句:
00401680 |. 68 0A304000 push 0040300A ; |splish, splash
很简单,将这些push 0040300A统统NOP掉。对是对了,还不能拿满分。没说禁止Splish啊。
0040300A里的字符splish, splash是啥时有的?固定的还是动态的?试验一下,重来CTRL+F2,停在:
00401000 >/$ 6A 00 push 0 ; /(initial cpu selection)
00401002 |. E8 83070000 call <jmp.&KERNEL32.GetModuleHandleA> ; \GetModuleHandleA
右击内存窗口,转到0040300A,一看,是固定值。分别在0040300A内存处设内存读、写断点和
硬件读、写断点,各自运行,都没断下来。看来,是程序初始化时的赋值。只好运行Winhex,查找文本Splish, Splash
在100A-1017地址处。将2C 53 70 6C 61 73 68,Splash全部赋值为20 20 20 20 20 20 20。为啥不赋值为零?
你试试看,显示......。另存为TEMPSplish.EXE。试运行,哈哈,“, Splash”没了。(*002)记住字符的ASCII值是算法分析的基础。
*PUSH002:对字符的ASCII值一定要熟悉,深入分析非密码学算法的关键。很多算法是在字符的ASCII值上做文章的。连字符在序列号中经常用到,应熟记。空 格--> 20在NAG窗口中常用,应背熟熟。
附算法分析入门教程实战篇基础知识之二:常用ASCII表
数 字类:
数 字 0 1 2 3 4 5 6 7 8 9
十六进制 30 31 32 33 34 35 36 37 38 39
十 进制 48 49 50 51 52 53 54 55 56 57
大写字母:
字 母 A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
十六进制 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F 50 51 52 53 54 55 56 57 58 59 5A
十 进制 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
小写字母:
小写字母 a b c d e f g h i j k l m n o p q r s t u v w x y z
十六进制 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 70 71 72 73 74 75 76 77 78 79 7A
十 进制 97 98 99 100101102103104105106107108109110111112113114115116117118119120121122
特殊字符:
字 符 ! " # $ % & ' ( ) * + , - . / : ; < = > ? @ [ \ ] ^ _ ` { | } ~
十六进制 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F 3A 3B 3C 3D 3E 3F 40 5B 5C 5D 5E 5F 60 7B 7C 7D 7E
十 进制 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 58 59 60 61 62 63 64 91 92 93 94 95 96123124125126
使用频率高的:
数 字:'0'~'9' --> 30~39
大写字母:'A'~'Z' --> 41~5A
小写字母:'a'~'z' --> 61~7A
特别字符:空 格' '--> 20
连字符'-'--> 2D
哇塞,这么多,记不住啊。我也不全记得,常用的记住了。再不行,在OD的命令行里用?指令啊:? 61,ASCII a。多方便啊。试试? 空格。ASCII后面怎么是空的。看来不太常用的少见的字符还是要记?NO,NO,NO,我连放了三个洋屁:段钢著的《加密和解密II》开头就是ASCII表,经常查阅吧。现在你开始体会我为什么床头放这书了吧。
我说过,CRACK ME是给你练习各种破解技术的。哥们我就自己给找个乐,能不能将Splish, Splash换成别的字符?说干就干,就换成WWW.PEDIY.COM吧。运行Winhex,替换文本Splish, Splash为WWW.PEDIY.COM,等等,提示说少一个字符。小CASE,输入WWW.PEDIY.COMM,替换完成后,将1017处4D改为20,另存为TEMPSplish.EXE。试运行,哈哈,WWW.PEDIY.COM。
说这么多有什么用?一是强调常用ASCII表的重要性。二是现身说法,不要为了做CRACK ME而做。记住,我们是为了提高自己的破解技术来做CRACK ME 的。
第二题是找到硬件序列号。这是一个明码比较的题目。很简单,利用出错字符串提示找到关键设断。这许多朋友都会。我这就整点新招,我们是练破解技术的,不是比谁快。双击TEMPSplish.EXE,检测硬件序列号。有错误提示。不许动,打开OD,附加TEMPSplish.EXE,F12暂停,然后CTAL+F9,直到错误提示的确定之类的按钮有效,点它,继续回到ODCTAL+F9。
许多朋友对找不到字符串的软件很头痛,比如许多的NAG窗口。F12暂停,然后CTAL+F9是个好的组合。直到回到主程序领空:
004013E5 |> \61 popad
004013E6 |. EB 73 jmp short 0040145B
004013E8 |> 66:83F8 02 cmp ax, 2
004013EC |. 75 1B jnz short 00401409
向上看:
004013BD |> \6A 00 push 0 ; /Style = MB_OK|MB_APPLMODAL
004013BF 68 0A304000 push 0040300A ; www.pediy.com
004013C4 |. 68 8E134000 push 0040138E ; |congratulations, you got the hard coded serial
004013C9 |. 6A 00 push 0 ; |hOwner = NULL
004013CB |. E8 78030000 call <jmp.&USER32.MessageBoxA> ; \MessageBoxA
004013D0 |. EB 13 jmp short 004013E5
004013D2 |> 6A 00 push 0 ; /Style = MB_OK|MB_APPLMODAL
004013D4 |. 68 0A304000 push 0040300A ; |www.pediy.com
004013D9 |. 68 67304000 push 00403067 ; |sorry, please try again.
004013DE |. 6A 00 push 0 ; |hOwner = NULL
004013E0 |. E8 63030000 call <jmp.&USER32.MessageBoxA> ; \MessageBoxA
004013E5 |> 61 popad
004013E6 |. EB 73 jmp short 0040145B
004013E8 |> 66:83F8 02 cmp ax, 2
许多新手不是不会算法分析,而是常常困在77******之类的地址里,只能空叹英雄无妞可泡啊。这死地方哪来的MM泡啊。如何返回程序领空,是许多新手学算法分析时常常困惑的盲点。偏偏很少有高手强调这个知识点。一分钱能憋死英雄汉。相信许多人都有这样的感慨。CRACK ME论坛上有一篇“关于Hackgbeta.exe ”,其实就是这个知识点。我就将当时的回答复制如下:http://bbs.pediy.com/showthread.php?t=59077。我懒啊,懒人懒办法。
Quote:
Originally Posted by sexyhuli
我说NBA2005兄,你的那个MessageBox怎么是在主程序里?
我的是在krnln.fnr模块里啊...
我的也是在krnln.fnr模块里啊
77E133B3 > 55 push ebp
77E133B4 8BEC mov ebp, esp
77E133B6 51 push ecx
77E133B7 833D 583BE477 0>cmp dword ptr [77E43B58], 0
77E133BE 74 29 je short 77E133E9
77E133C0 64:A1 18000000 mov eax, dword ptr fs:[18]
77E133C6 8B40 24 mov eax, dword ptr [eax+24]
77E133C9 8945 FC mov dword ptr [ebp-4], eax
77E133CC B8 00000000 mov eax, 0
77E133D1 B9 2835E477 mov ecx, 77E43528
EAX 00002010
ECX 0040C1CB 001Hackg.0040C1CB
EDX 0040C1C4 001Hackg.0040C1C4
EBX 100E357C krnln.100E357C
ESP 0012F8B4
EBP 0012F97C
ESI 00001000
EDI 80000004
CTRL+F9:,出现P.S提示后,断下:
10062176 5F pop edi ; 018B0240
10062177 83F8 03 cmp eax, 3
1006217A 5E pop esi
1006217B 75 0F jnz short 1006218C
1006217D 8B4C24 68 mov ecx, dword ptr [esp+68]
10062181 B8 02000000 mov eax, 2
10062186 8901 mov dword ptr [ecx], eax
10062188 83C4 64 add esp, 64
1006218B C3 retn
CTRL+F9反复,直到:
004A797C 83C4 28 add esp, 28
004A797F 68 04000080 push 80000004
004A7984 6A 00 push 0
004A7986 68 E9C14000 push 0040C1E9 ; ASCII "P.S..."
004A798B 68 01030080 push 80000301
004A7990 6A 00 push 0
004A7992 68 00000000 push 0
返回程序领空是基本的技能,即如何返回程序主干。
结论:F12暂停,然后CTAL+F9是个好的组合,回到主程序领空不成问题。原来这么简单。
返回主程序领空的一般方法:
1.CTAL+F9。
2.ALT+F9。
3.内存代码段设断法。
我示范一下,双击TEMPSplish.EXE,检测硬件序列号。有错误提示。不许动,打开OD,附加TEMPSplish.EXE,ALT+M打开内存窗口,右击代码段,F2设断,点窗口选CPU,F9运行。点错误提示的确定之类的按钮,怎么样?断在同样的地方。
许多新手看高手的算法分析,往往有看编程的感觉。我们是逆向工程师啊?看雪祭坛,锻成钢。怎么琢磨都象走错门了。怎么逆向追踪?同样是CRACK ME论坛上的一篇“关于Hackgbeta.exe ”,我回答“messageboxa 设断,往上追踪源头:”就有人不乐意。废话,我就是不知道如何追踪源头才问你呢。算法分析的文章我都能倒背如流,就是不会逆向追踪。
这是什么论坛啊?别牢骚了,没看见论坛坛主的名字吗?
我自逍遥又风流,
气你气你就气你。
情人节时忙买花,
送花莫要写错名。
天,你这哪是算法分析,整个一看雪论坛名人整盅游戏。轻松一下,休息休息。
其实,我上面特意选了偶像laomms(常见自校检分析实例)中的退出事件的逆向追踪,很经典,希望新手
仔细琢磨钻研。我这就这简单的CRACE ME再演示一遍,对照阅读理解。
现在的软件很少有对和错的提示是邻居的了。好,我就看错误提示信息逆向追踪:
004013D2 |> 6A 00 push 0 ; /Style = MB_OK|MB_APPLMODAL
004013D4 |. 68 0A304000 push 0040300A ; |www.pediy.com
004013D9 |. 68 67304000 push 00403067 ; |sorry, please try again.
004013DE |. 6A 00 push 0 ; |hOwner = NULL
004013E0 |. E8 63030000 call <jmp.&USER32.MessageBoxA> ; \MessageBoxA
004013E5 |> 61 popad
004013E6 |. EB 73 jmp short 0040145B
点此程序段开头 004013D2 |> 6A 00 push 0
信息窗口提示:
跳转来自 00401386
有一条红线来自00401386 |. /75 4A |jnz short 004013D2
是控制程序流程的语句,这可是注册码的重要特征啊。你说是就是啊?我还说偶像laomms是个武林高手呢。
如何确定这是关键的控制程序流程的语句?
实验爆破啊。JNZ改为JZ。运行。哈哈,正确的提示。回顾上面我的程序流程的知识:
判断明码注册码的一般程序流程:
段1:赋值注册码。
段2:检测注册码的对错,即控制程序流程的语句。特征是条件判断语句。
段3:分道扬镳。对和错两个子程序。
段2和段3都全了。向上找段1:
0040136F |. 8D05 53134000 lea eax, dword ptr [401353]
00401375 |. 8D1D 15324000 lea ebx, dword ptr [403215]
0040137B |> 8038 00 /cmp byte ptr [eax], 0
0040137E |. 74 0C |je short 0040138C
00401380 |. 8A08 |mov cl, byte ptr [eax]
00401382 |. 8A13 |mov dl, byte ptr [ebx]
00401384 |. 38D1 |cmp cl, dl
00401386 |. 75 4A |jnz short 004013D2
经典的赋值语句EAX和EBX。设断
0040136F |. 8D05 53134000 lea eax, dword ptr [401353]
重新运行。断下,信息窗口提示:
地址=00401353, (ASCII "HardCoded")
eax=00000000
我们什么也没有输入,猜HardCoded就是注册码。F8单步,
寄存器窗口变化:
EAX 00401353 ASCII "HardCoded"
ECX 0012FF34
EDX B6BEF9D8
EBX 005A2A10
ESP 0012FCD4
EBP 0012FCF4
ESI 00000111
EDI 005F01F0
EIP 00401375 TEMPSpli.00401375
其中EAX一行变红,ASCII HardCoded分外醒目。
再F8后,信息窗口:
ds:[00401353]=48 ('H')
跳转来自 0040138A
寄存器窗口变化:
EAX 00401353 ASCII "HardCoded"
ECX 0012FF34
EDX B6BEF9D8
EBX 00403215 TEMPSpli.00403215
ESP 0012FCD4
EBP 0012FCF4
ESI 00000111
EDI 005F01F0
EIP 0040137B TEMPSpli.0040137B
其中EBX变红,403215是地址。
向上看:
0040135F |. 68 153240>push 00403215 ; |Buffer = TEMPSpli.00403215
00401364 |. FF35 9034>push dword ptr [403490] ; |hWnd = 001E026A (class='Edit',parent=001F0200)
0040136A |. E8 BB0300>call <jmp.&USER32.GetWindowText>; \GetWindowTextA
00403215是获取对话框内的信息。应该是假机器码。设断0040135F |. 68 153240>push 00403215,重运行。
输入5201314,我爱你一生一世。断下,右键点内存窗口,转到00403215,还全是零啊。别急,F8过CALL,再看,我爱你一生一世。看寄存器:
EAX 00000007
ECX 0012FF34
EDX B6A329D8
EBX 0059B6F8
ESP 0012FCD4
EBP 0012FCF4
ESI 00000111
EDI 005C7720
EIP 0040136F TEMPSpli.0040136F
这个CALL什么用途?EAX=7,正好是我们输入假码的位数。这个CALL将我们输入的假码写入内存403215处,并统计出它的位数赋值给EAX。你怀疑?不会是巧合吧?我的爱天下唯一,不信我证明给你看。破解算法分析最常用的方法就是对比法。
我再输入334420,生生世世爱你,六位。还不信,我可要割腕了。
我再输入1234567890,
每个数字都刻你的名字,
天天破解敲着你,
证明我有多想你。
许多朋友对CALL的含义不知道如何快速地理解它的用途。这可是算法分析的关键。还有许多朋友不知道如何看寄存器窗口和内存窗口、信息窗口,这里唠叨了一大堆帮助他们理解。会的朋友请黑眼睛飘过去。其中多次运用了相应的断点法,加深理解。
其实看这几个窗口的核心就是我们输入的假用户名和假码,结合程序流程的关键控制流程语句细细研读,不放心的可以变换用户名和注册假码多用用对比法。
注册码在哪?EAX里啊。
假码在哪?EBX里啊。
输入注册码HardCoded,成功了。等等,还有注册机呢。
我不会写注册机啊。天,我有恐高症。
没关系,利用程序自身写注册机。国外在这方面研究很多的。这是算法分析啊。我问你,我们为什么研究算法分析?
注册码找到了,研究算法分析有什么用?
算法分析入门教程第三课应用篇:算法分析的作用
1.象杀毒软件样方便、高效。现在的注册码许多都对应机器码,一机一码。下面我就不举例了,大家都是比我聪明的高智商,只有我这傻瓜在这象蜗牛样爬格子。论坛上讲得太多了,小孩子都能倒背。
2.发现软件的BUG,尽量调试弥补。
3.也可以打补丁。
4.利用软件自身做注册机。这是一个方向,终点是调试、完善软件。许多人可能认为不如注册机自己编写方便。但我说过,这是一个方向,终点是调试、完善软件,不是单单的注册码。我们还在摸索中,通过
利用软件自身做注册机的练习,我们初步体会简单的完善软件的感觉。就象我刚学破解,很长时间没有进展。因为我没经验,走了不少弯路。我自己在这方面摸索了一阵,所以看了laomms文章如获至宝,许多的悬案都有了眉目和方向。真心地再次感谢laomms。自己英文不好,理解上不如laomms深刻。这方面laomms在看雪论坛上写了许多相关的文章,大家有兴趣的可以在看雪论坛输入laomms查询,下载laomms文中的附件练习。
http://www.google.cn/search?q=laomms&btnG=%BC%EC%CB%F7&client=pub-1585618761901261&ie=GB2312&oe
=GB2312&hl=zh-CN&domains=http%3A%2F%2Fwww.pediy.com&sitesearch=http%3A%2F%2Fwww.pediy.com
CRACK ME的练习并不在数量的多少,而在于质量,在于不断的练习、总结和积累经验。
首先,我们来整理下程序流程,你可以当作文章的段落大意更好理解:
段1:读取假码,获得位数。
段2:真码和假码分别赋值EAX和EBX,再在CL和DL中分别取单字符比较。这个比较的循环以假码位数为
循环结束的判断条件。这是控制程序流程的关键段落。
段3:正确的提示。
段4:错误的提示。
我们要动手的就是对错误的提示段4进行整改,将它改头换面成注册机。
004013D4 |. 68 0A3040>push 0040300A ; |www.pediy.com
004013D9 |. 68 673040>push 00403067 ; |sorry, please try again.
将错误提示换成真的注册码,将标题www.pediy.com换成
004013C4 |. 68 8E1340>push 0040138E ; |congratulations, you got the hard coded serial
你如果觉得注册码的表达不准确,也可改成自己的句子。方法同上:将标题splish, splash改成www.pediy.com。
不输入任何假码,结果如下:
004013D2 |> \6A 00 push 0 ; /Style = MB_OK|MB_APPLMODAL
004013D4 |. 68 8E134000 push 0040138E ; |Title = "Congratulations, you got the hard coded serial"
004013D9 |. 50 push eax ; |Text
004013DA |. 90 nop ; |
004013DB |. 90 nop ; |
004013DC |. 90 nop ; |
004013DD |. 90 nop ; |
004013DE |. 6A 00 push 0 ; |hOwner = NULL
成功了吗?没有,这里有个BUG,EAX里的值是动态的。你可以分别输入两位、三位、四位....假码,关公的脸啊,真是
个大BUG。没关系,回顾一下,关键是EAX,将EAX换成00401353:
004013D2 |> \6A 00 push 0 ; /Style = MB_OK|MB_APPLMODAL
004013D4 |. 68 8E134000 push 0040138E ; |Title = "Congratulations, you got the hard coded serial"
004013D9 68 53134000 push 00401353 ; ASCII "HardCoded"
004013DE |. 6A 00 push 0 ; |hOwner = NULL
004013E0 |. E8 63030000 call <jmp.&USER32.MessageBoxA> ; \MessageBoxA
记得这句:0040136F |. 8D05 53134000 lea eax, dword ptr [401353]
将右键复制到可执行文件,选全部修改,关掉新出的窗口,选Y,另存为001TEMPSplish.EXE。原来这么简单。
等等,还有个小BUG,你输入错的机器码才会出现修改过的注册机。那万一买彩票中了大奖,输入的是正确的机器码呢?不是看不到你辛苦做的注册机了吗?
哈哈哈,既然你运气这么好,还要看我的注册机干什么?我的注册机作用是啥?给你看的?你脑袋里灌水了?破解就是这么有趣。在这唠叨一句,破解很容易走火入魔。编程的高手里,水平越高,行为举止在一般人眼里都觉得怪异。破解也很类似。
围棋高手钱宇平就是典型的用脑过度,在决赛前住进精神病院,将世界冠军从指间溜过。思来令人扼腕,惋惜连连。奉劝爱好破解的朋友注意心理健康的调节,有自己的业余爱好,不要做破解呆子。所幸许多朋友象我一样,喜欢打游戏和体育运动。我认识一位水平比我高的不得了的高手,说天才丝毫不为过,可惜他在精神病院做标本。让人一声叹息,往事堪回首。
再来练练如何补丁。这太EASY了。对照着段钢著的《加密和解密II》书照做吧。我就不做看不起朋友智慧的事了。
算法分析入门教程第四课实战及应用篇:汇编语言和BUG的研判。
论坛上经常有朋友问下面类似的问题:
算法分析需要懂汇编语言吗?
写文章需要认识字吗?
高手是不是都很牛,一看汇编语句就明明白白我的心?
看小说是不是所有的字都认识?研究生英语考试的阅读理解生词多不多,是不是全能看懂?
呵呵,外交家的标准格式,我也会太极推手啊。
我又不是周星弛,需要懂汇编语言吗、不需要吗的唠叨个没完。
我想我的意思表达还是满准确的。
写了上面的这么多内容,我都累了。我就简单地讲下算法分析的思路:
1.首先我再次强调算法分析的程序流程,这是基础啊:
判断暗码注册码算法分析的一般程序流程:
段1:赋值假用户名和假码。
段2:用户名和注册码算法段
段3:检测注册码的对错,即控制程序流程的语句。特征是条件判断语句。
段4:分道扬镳。对和错两个子程序。
在算法分析中,程序流程的理解能力的高低,决定了算法分析的效率和精确度。可以毫不夸张地说,算法分析的水平差距就在程序流程的把握能力上。这是我再三强调的原因。而写注册机,则更让我们加深对程序流程的理解。许多高手也知道这道理,但怕吓跑了新手。
2.你可以将要破解的程序理解成你要做的阅读理解题目。议论文和叙述文的文体不同,文章结构也不同。考试既考你的综合理解能力,也考你根据文章中心思想和段落大意、上下文猜测生词和冷僻词汇的能力。我们进行算法分析,首先要了解他的程序流程,即段落大意和文章布局。干啥呀,确定算法分析的坐标。同样地,我们先逆向追踪到关键处,粗粗地跟一遍附近的主程序大段落,不要在CALL猜的对不对的问题上浪费时间(初学算法分析可以跟进去研究),这样便于从主程序大段落的全局角度来把握这部分的程序流程,目标是迅速确定确定算法分析的大致范围甚至具体的方位。做阅读理解不就是这样的嘛。
3.对于汇编语言的理解问题,我的理解是提问题的朋友是以W32dasm静态汇编的角度来理解汇编语言的。其实猜测生词和冷僻词汇也一样,我们很难在没有上下文的情况下猜出词意。但这不代表我连上下文也看不懂,更不代表没有学单词的必要。
学汇编语言是算法分析的基础。但不意味着我们只有完全掌握了汇编语言后才能来搞算法分析。学于算法分析,用于算法分析,这是可行的。符合西方实用的教育原则:发现问题,解决问题。
*PUSH003:我国的教育原则是解决问题,发现问题则几乎被忽略。体现在破解上,就是绝大多数人从书上照搬,从不多问几个为什么,对菜鸟的一针见血的问题以一句书上说的敷衍了之。破解的目的就是为了注册码,其它的也懒得去管。体现在市场中,我们的研究多数停留在象牙塔,而很少象国外同仁迅速转化成生产力。这有沉重的历史性原因。可喜的是,目前的国家领导人已经开始着手处理这方面的问题。看雪老大也已经行动起来了。在现代社会中,发现问题的人才比解决问题的人才重要得多。体现在破解中,发现程序BUG的人才太少了。CRACK ME论坛上的一篇“关于Hackgbeta.exe ”中的CRACK ME软件“crackmegame游戏一下”也有一个小BUG,你看出来了吗?我是很长时间才发现。刚想上网吹吹牛,发现阳小子一眼就看出来了。
一山还比一山高,
不是菜鸟我是谁?
望尽破解路遥遥,
我不勤奋待何日?
横批:不服不行。
4.怎样象猜冷僻词样猜不太理解的汇编语言?要点如下:
A.熟悉算法分析大段落的文章结构模板。
B.充分利用动态调试的特点。对吃不准的CALL,宁愿更换用户名和注册码试验,采用对比法鉴定自己的猜想。
在算法分析里,现在的软件子循环象子公司一样多,子循环内套着子循环。晕死你。
C.在关键的地址处可以采用内存断点法或硬件断点法跟踪算法流程。
D.经常对堆栈窗口、寄存器窗口和信息窗口、内存窗口进行查看,特别留意与假用户名和假注册码相关的汇编语言。
5.如何正确地理解算法分析?个人的体会是,一边看汇编和各结果显示的窗口的红色变化,根据自己的理解一边计算器模拟。
在程序的结果显示窗口中相互验证。开始时,很吃力是正常的。除非你是计算机专业出身的。边破解,边学汇编。我经常破解完一个软件,就回头对汇编语言进行恶补。万事开头难。
6.算法分析大段落的文章结构模板:
A.读取假用户名和假码。
B.获取位数。判断是否为空。以前经常看到有CRACK ME的作者出洋相,什么都不输入,直接点注册,居然成功。所以这往
往是我们确定算法分析的开始的依据。为什么直接点注册,居然成功?要善于发现问题。
C.对用户名和注册码的限制条件判断。
D.对用户名和(或)注册码的处理段,可以是逻辑运算和(或)各种字符的转换。这里可分几个小段分别处理。
E.对处理过的用户名和(或)注册码进行保藏,地址可以是内存、堆栈、注册表或文件。
F.调用处理过的用户名和(或)注册码,进行比较。
G.消除比较后的用户名和(或)注册码进行安全处理,你可以理解为毁尸灭迹。
上述的结构前后顺序可能不完全一样,许多语句或段落也不一定全具备。你心中有了这个模板,猜CALL的含义会轻松很多。
结合这个CRACK ME分段如下:
B段:
004015E7 |. >push 20 ; /Count = 20 (32.)
004015E9 |. >push 00403242 ; |Buffer = 001TEMPS.00403242
004015EE |. >push dword ptr [ebp+C] ; |hWnd
004015F1 |. >call <jmp.&USER32.GetWindowTextA> ; \GetWindowTextA
004015F6 |. >test eax, eax
004015F8 |. >je 00401693
004015FE |. >mov dword ptr [403467], eax
00401603 |. >push 0B ; /Count = B (11.)
00401605 |. >push 00403236 ; |Buffer = 001TEMPS.00403236
0040160A |. >push dword ptr [ebp+8] ; |hWnd
0040160D |. >call <jmp.&USER32.GetWindowTextA> ; \GetWindowTextA
00401612 |. >test eax, eax
00401614 |. >je short 0040167E
00401616 |. >mov dword ptr [403463], eax
用户名A段,寄存器初始化:
0040161B |. >xor ecx, ecx
0040161D |. >xor ebx, ebx
0040161F |. >xor edx, edx
00401621 |. >lea esi, dword ptr [403236]
00401627 |. >lea edi, dword ptr [403258]
0040162D |. >mov ecx, 0A
用户名D段,算法分析核心循环段:
00401632 |> />/movsx eax, byte ptr [esi+ebx]
00401636 |. |>|cdq
00401637 |. |>|idiv ecx
00401639 |. |>|xor edx, ebx
0040163B |. |>|add edx, 2
0040163E |. |>|cmp dl, 0A
00401641 |. |>|jl short 00401646
00401643 |. |>|sub dl, 0A
00401646 |> |>|mov byte ptr [edi+ebx], dl
00401649 |. |>|inc ebx
0040164A |. |>|cmp ebx, dword ptr [403463]
00401650 |.^\>\jnz short 00401632
假码A段,寄存器初始化:
00401652 |. >xor ecx, ecx
00401654 |. >xor ebx, ebx
00401656 |. >xor edx, edx
00401658 |. >lea esi, dword ptr [403242]
0040165E |. >lea edi, dword ptr [40324D]
00401664 |. >mov ecx, 0A
F段:
00401658 |. >lea esi, dword ptr [403242]
0040165E |. >lea edi, dword ptr [40324D]
00401664 |. >mov ecx, 0A
00401669 |> >/movsx eax, byte ptr [esi+ebx]
0040166D |. >|cdq
0040166E |. >|idiv ecx
00401670 |. >|mov byte ptr [edi+ebx], dl
00401673 |. >|inc ebx
00401674 |. >|cmp ebx, dword ptr [403467]
0040167A |.^ >\jnz short 00401669
0040167C |. >jmp short 004016A8
其中E段表现为:
00401646 |> \>|mov byte ptr [edi+ebx], dl
存储地址是内存 00403258。初学者可在算法分析前设内存断点研究。
00401670 |. >|mov byte ptr [edi+ebx], dl
存储地址是内存 0040324D。初学者可在算法分析前设内存断点研究。
不错,少了C段和G段。这可是个BUG啊。
C段.对用户名和注册码的限制条件判断。
加上注册码的取余数判断,造成了注册码的多样性、简单化。只要是除以0A的余数与用户名处理后的对应字符相同就可以,
答案就不是唯一的。而注册码的安全性是以它的超低猜中几率为表现的。
我举个例子:
用户名:1
注册码:尝试数字0123456789,3是正确的注册码。
尝试小写字母,e、o和y是正确的注册码。
查看ASCII,十进制个位是1的特殊符号有=和[。大写字母G和Q是正确的注册码。
呵呵,学破解搞算法分析的,这么累,原来这样也行。具体的算法公式你应该会写出来了吧?
最后,写几个常见的汇编语句组合:
1.大写和小写的相互转换:你呀的穿上小皮袄我就认不出你是乌龟了。
大写 -> 小写: 加20H
小写 -> 大写: 减20H
2.数值的十进制和16进制转化、字符直接变数字和数字直接变字符。你就是脱了小皮袄我照样认出你是小乌龟。
3.NEC往往和OR一起结合,完成对字符串长度的取值。
4.左移->乘
SHL EAX,2
等价于EAX = EAX * (2^2)
右移->除
SHR EAX,3
等价于EAX = EAX / (2^3)
5。奇偶测试
test EAX,80000001
je/jne xxxxxxxx
6.对标志位值进行测试
test EAX,EAX
je/jne xxxxxxxx
7.对某位进行测试
test EAX,4
je/jne xxxxxxxx
8.根据姓名个字符依次循环取值。
9.用户名或注册码的限制条件举例子:
0040137E MOV ESI,[ESP+04] <--输入的名字
:00401382 PUSH ESI <-- 进栈
:00401383 MOV AL,[ESI] <-- AL = 姓名的第1个字母
:00401385 TEST AL,AL <-- 判断是否等于0.
:00401387 JZ 0040139C <-- 等于0就跳
:00401389 CMP AL,41 <-- 将它与41 或 'A'进行比较
:0040138B JB 004013AC <-- 小于'A'就跳
:0040138D CMP AL,5A <-- 将它与5A 或 'Z'进行比较.
:0040138F JNB 00401394 <-- 不小于'Z'就跳.
:00401391 INC ESI <-- 姓名的计数器.
:00401392 JMP 00401383 <-- 重复测试
:00401394 CALL 004013D2 <-- 大于 'Z'跳到这里.
:00401399 INC ESI <-- 姓名的计数器
:0040139A JMP 00401383 <-- 重复测试.
上面这段代码检查输入姓名中的字母是否介于A和Z之间,如果是小写字母就执行00401394处的CALL,
它包含经典的 SUB AL,20大写化程式。到这里我们的名字完全成了大写。
具体的例子大家自己在不断练习中逐渐总结吧。我累了。
本来还想写注册机的体会,但有《算法注册机编写扫盲BY我要[DFCG]》,看雪论坛上有laomms的六篇系列注册机教程,我这个菜鸟最后还是删去了浅薄空乏的注册机体会和SMC技术的探讨、与算法分析关系不大的汉化技术。
既生NBA2005,何生laomms?
附算法分析入门教程实战篇基础知识之三:汇编语言集合 BY转自PYG官方论坛飘云阁 初学者乐园。
汇编语言的准备知识--给初次接触汇编者
汇编语言和CPU以及内存,端口等硬件知识是连在一起的. 这也是为什么汇编语言没有通用性的原因. 下面简单讲讲基本知识(针对INTEL x86及其兼容机)
============================
x86汇编语言的指令,其操作对象是CPU上的寄存器,系统内存,或者立即数. 有些指令表面上没有操作数, 或者看上去缺少操作数, 其实该指令有内定的操作对象, 比如push指令, 一定是对SS:ESP指定的内存操作, 而cdq的操作对象一定是eax / edx.
在汇编语言中,寄存器用名字来访问. CPU 寄存器有好几类, 分别有不同的用处:
1. 通用寄存器:
EAX,EBX,ECX,EDX,ESI,EDI,EBP,ESP(这个虽然通用,但很少被用做除了堆栈指针外的用途)
这些32位可以被用作多种用途,但每一个都有"专长". EAX 是"累加器"(accumulator), 它是很多加法乘法指令的缺省寄存器. EBX 是"基地址"(base)寄存器, 在内存寻址时存放基地址. ECX 是计数器(counter), 是重复(REP)前缀指令和LOOP指令的内定计数器. EDX是...(忘了..哈哈)但它总是被用来放整数除法产生的余数. 这4个寄存器的低16位可以被单独访问,分别用AX,BX,CX和DX. AX又可以单独访问低8位(AL)和高8位(AH), BX,CX,DX也类似. 函数的返回值经常被放在EAX中.
ESI/EDI分别叫做"源/目标索引寄存器"(source/destination index),因为在很多字符串操作指令中, DS:ESI指向源串,而ES:EDI指向目标串.
EBP是"基址指针"(BASE POINTER), 它最经常被用作高级语言函数调用的"框架指针"(frame pointer). 在破解的时候,经常可以看见一个标准的函数起始代码:
push ebp ;保存当前ebp
mov ebp,esp ;EBP设为当前堆栈指针
sub esp, xxx ;预留xxx字节给函数临时变量.
...
这样一来,EBP 构成了该函数的一个框架, 在EBP上方分别是原来的EBP, 返回地址和参数. EBP下方则是临时变量. 函数返回时作 mov esp,ebp/pop ebp/ret 即可.
ESP 专门用作堆栈指针.
2. 段寄存器:
CS(Code Segment,代码段) 指定当前执行的代码段. EIP (Instruction pointer, 指令指针)则指向该段中一个具体的指令. CS:EIP指向哪个指令, CPU 就执行它. 一般只能用jmp, ret, jnz, call 等指令来改变程序流程,而不能直接对它们赋值.
DS(DATA SEGMENT, 数据段) 指定一个数据段. 注意:在当前的计算机系统中, 代码和数据没有本质差别, 都是一串二进制数, 区别只在于你如何用它. 例如, CS 制定的段总是被用作代码, 一般不能通过CS指定的地址去修改该段. 然而,你可以为同一个段申请一个数据段描述符"别名"而通过DS来访问/修改. 自修改代码的程序常如此做.
ES,FS,GS 是辅助的段寄存器, 指定附加的数据段.
SS(STACK SEGMENT)指定当前堆栈段. ESP 则指出该段中当前的堆栈顶. 所有push/pop 系列指令都只对SS:ESP指出的地址进行操作.
3. 标志寄存器(EFLAGS):
该寄存器有32位,组合了各个系统标志. EFLAGS一般不作为整体访问, 而只对单一的标志位感兴趣. 常用的标志有:
进位标志C(CARRY), 在加法产生进位或减法有借位时置1, 否则为0.
零标志Z(ZERO), 若运算结果为0则置1, 否则为0
符号位S(SIGN), 若运算结果的最高位置1, 则该位也置1.
溢出标志O(OVERFLOW), 若(带符号)运算结果超出可表示范围, 则置1.
JXX 系列指令就是根据这些标志来决定是否要跳转, 从而实现条件分枝. 要注意,很多JXX 指令是等价的, 对应相同的机器码. 例如, JE 和JZ 是一样的,都是当Z=1是跳转. 只有JMP 是无条件跳转. JXX 指令分为两组, 分别用于无符号操作和带符号操作. JXX 后面的"XX" 有如下字母:
无符号操作: 带符号操作:
A = "ABOVE", 表示"高于" G = "GREATER", 表示"大于"
B = "BELOW", 表示"低于" L = "LESS", 表示"小于"
C = "CARRY", 表示"进位"或"借位" O = "OVERFLOW", 表示"溢出"
S = "SIGN", 表示"负"
通用符号:
E = "EQUAL" 表示"等于", 等价于Z (ZERO)
N = "NOT" 表示"非", 即标志没有置位. 如JNZ "如果Z没有置位则跳转"
Z = "ZERO", 与E同.
如果仔细想一想,就会发现 JA = JNBE, JAE = JNB, JBE = JNA, JG = JNLE, JGE= JNL, JL= JNGE, ....
4. 端口
端口是直接和外部设备通讯的地方。外设接入系统后,系统就会把外设的数据接口映射到特定的端口地址空间,这样,从该端口读入数据就是从外设读入数据,而向外设写入数据就是向端口写入数据。当然这一切都必须遵循外设的工作方式。端口的地址空间与内存地址空间无关,系统总共提供对64K个8位端口的访问,编号0-65535. 相邻的8位端口可以组成成一个16位端口,相邻的16位端口可以组成一个32位端口。端口输入输出由指令IN,OUT,INS和OUTS实现,具体可参考汇编语言书籍。
tianxj 2008-1-30 21:41
汇编指令的操作数可以是内存中的数据, 如何让程序从内存中正确取得所需要的数据就是对内存的寻址.
INTEL 的CPU 可以工作在两种寻址模式:实模式和保护模式. 前者已经过时,就不讲了, WINDOWS 现在是32位保护模式的系统, PE 文件就基本是运行在一个32位线性地址空间, 所以这里就只介绍32位线性空间的寻址方式.
其实线性地址的概念是很直观的, 就想象一系列字节排成一长队,第一个字节编号为0, 第二个编号位1, .... 一直到4294967295(十六进制FFFFFFFF,这是32位二进制数所能表达的最大值了). 这已经有4GB的容量! 足够容纳一个程序所有的代码和数据. 当然, 这并不表示你的机器有那么多内存. 物理内存的管理和分配是很复杂的内容, 初学者不必在意, 总之, 从程序本身的角度看, 就好象是在那么大的内存中.
在INTEL系统中, 内存地址总是由"段选择符:有效地址"的方式给出.段选择符(SELECTOR)存放在某一个段寄存器中, 有效地址则可由不同的方式给出. 段选择符通过检索段描述符确定段的起始地址, 长度(又称段限制), 粒度, 存取权限, 访问性质等. 先不用深究这些, 只要知道段选择符可以确定段的性质就行了. 一旦由选择符确定了段, 有效地址相对于段的基地址开始算. 比如由选择符1A7选择的数据段, 其基地址是400000, 把1A7 装入DS中, 就确定使用该数据段. DS:0 就指向线性地址400000. DS:1F5278 就指向线性地址5E5278. 我们在一般情况下, 看不到也不需要看到段的起始地址, 只需要关心在该段中的有效地址就行了. 在32位系统中, 有效地址也是由32位数字表示, 就是说, 只要有一个段就足以涵盖4GB线性地址空间, 为什么还要有不同的段选择符呢? 正如前面所说的, 这是为了对数据进行不同性质的访问. 非法的访问将产生异常中断, 而这正是保护模式的核心内容, 是构造优先级和多任务系统的基础. 这里有涉及到很多深层的东西, 初学者先可不必理会.
有效地址的计算方式是: 基址+间址*比例因子+偏移量. 这些量都是指段内的相对于段起始地址的量度, 和段的起始地址没有关系. 比如, 基址=100000, 间址=400, 比例因子=4, 偏移量=20000, 则有效地址为:
100000+400*4+20000=100000+1000+20000=121000. 对应的线性地址是400000+121000=521000. (注意, 都是十六进制数).
基址可以放在任何32位通用寄存器中, 间址也可以放在除ESP外的任何一个通用寄存器中. 比例因子可以是1, 2, 4 或8. 偏移量是立即数. 如: [EBP+EDX*8+200]就是一个有效的有效地址表达式. 当然, 多数情况下用不着这么复杂, 间址,比例因子和偏移量不一定要出现.
内存的基本单位是字节(BYTE). 每个字节是8个二进制位, 所以每个字节能表示的最大的数是11111111, 即十进制的255. 一般来说, 用十六进制比较方便, 因为每4个二进制位刚好等于1个十六进制位, 11111111b = 0xFF. 内存中的字节是连续存放的, 两个字节构成一个字(WORD), 两个字构成一个双字(DWORD). 在INTEL架构中, 采用small endian格式, 即在内存中,高位字节在低位字节后面. 举例说明:十六进制数803E7D0C, 每两位是一个字节, 在内存中的形式是: 0C 7D 3E 80. 在32位寄存器中则是正常形式,如在EAX就是803E7D0C. 当我们的形式地址指向这个数的时候,实际上是指向第一个字节,即0C. 我们可以指定访问长度是字节, 字或者双字. 假设DS:[EDX]指向第一个字节0C:
mov AL, byte ptr DS:[EDX] ;把字节0C存入AL
mov AX, word ptr DS:[EDX] ;把字7D0C存入AX
mov EAX, dword ptr DS:[EDX] ;把双字803E7D0C存入EAX
在段的属性中,有一个就是缺省访问宽度.如果缺省访问宽度为双字(在32位系统中经常如此),那么要进行字节或字的访问,就必须用byte/word ptr显式地指明.
缺省段选择:如果指令中只有作为段内偏移的有效地址,而没有指明在哪一个段里的时候,有如下规则:
如果用ebp和esp作为基址或间址,则认为是在SS确定的段中;
其他情况,都认为是在DS确定的段中。
如果想打破这个规则,就必须使用段超越前缀。举例如下:
mov eax, dword ptr [edx] ;缺省使用DS,把DS:[EDX]指向的双字送入eax
mov ebx, dword ptr ES:[EDX] ;使用ES:段超越前缀,把ES:[EDX]指向的双字送入ebx
堆栈:
堆栈是一种数据结构,严格地应该叫做“栈”。“堆”是另一种类似但不同的结构。SS 和 ESP 是INTEL对栈这种数据结构的硬件支持。push/pop指令是专门针对栈结构的特定操作。SS指定一个段为栈段,ESP则指出当前的栈顶。push xxx 指令作如下操作:
把ESP的值减去4;
把xxx存入SS:[ESP]指向的内存单元。
这样,esp的值减小了4,并且SS:[ESP]指向新压入的xxx. 所以栈是“倒着长”的,从高地址向低地址方向扩展。pop yyy 指令做相反的操作,把SS:[ESP]指向的双字送到yyy指定的寄存器或内存单元,然后把esp的值加上4。这时,认为该值已被弹出,不再在栈上了,因为它虽然还暂时存在在原来的栈顶位置,但下一个push操作就会把它覆盖。因此,在栈段中地址低于esp的内存单元中的数据均被认为是未定义的。
最后,有一个要注意的事实是,汇编语言是面向机器的,指令和机器码基本上是一一对应的,所以它们的实现取决于硬件.有些看似合理的指令实际上是不存在的,比如:
mov DS:[edx], ds:[ecx] ;内存单元之间不能直接传送
mov DS, 1A7 ;段寄存器不能直接由立即数赋值
mov EIP, 3D4E7 ;不能对指令指针直接操作.
tianxj 2008-1-30 21:42
“汇编语言”作为一门语言,对应于高级语言的编译器,我们需要一个“汇编器”来把汇编语言原文件汇编成机器可执行的代码。高级的汇编器如MASM, TASM等等为我们写汇编程序提供了很多类似于高级语言的特征,比如结构化、抽象等。在这样的环境中编写的汇编程序,有很大一部分是面向汇编器的伪指令,已经类同于高级语言。现在的汇编环境已经如此高级,即使全部用汇编语言来编写windows的应用程序也是可行的,但这不是汇编语言的长处。汇编语言的长处在于编写高效且需要对机器硬件精确控制的程序。而且我想这里的人学习汇编的目的多半是为了在破解时看懂反汇编代码,很少有人真的要拿汇编语言编程序吧?(汗......)
好了,言归正传。大多数汇编语言书都是面向汇编语言编程的,我的帖是面向机器和反汇编的,希望能起到相辅相成的作用。有了前面两篇的基础,汇编语言书上对大多数指令的介绍应该能够看懂、理解了。这里再讲一讲一些常见而操作比较复杂的指令。我这里讲的都是机器的硬指令,不针对任何汇编器。
无条件转移指令jmp:
这种跳转指令有三种方式:短(short),近(near)和远(far)。短是指要跳至的目标地址与当前地址前后相差不超过128字节。近是指跳转的目标地址与当前地址在用一个段内,即CS的值不变,只改变EIP的值。远指跳到另一个代码段去执行,CS/EIP都要改变。短和近在编码上有所不同,在汇编指令中一般很少显式指定,只要写 jmp 目标地址,几乎任何汇编器都会根据目标地址的距离采用适当的编码。远转移在32位系统中很少见到,原因前面已经讲过,由于有足够的线性空间,一个程序很少需要两个代码段,就连用到的系统模块也被映射到同一个地址空间。
jmp的操作数自然是目标地址,这个指令支持直接寻址和间接寻址。间接寻址又可分为寄存器间接寻址和内存间接寻址。举例如下(32位系统):
jmp 8E347D60 ;直接寻址段内跳转
jmp EBX ;寄存器间接寻址:只能段内跳转
jmp dword ptr [EBX] ;内存间接寻址,段内跳转
jmp dword ptr [00903DEC] ;同上
jmp fward ptr [00903DF0] ;内存间接寻址,段间跳转
解释:
在32位系统中,完整目标地址由16位段选择子和32位偏移量组成。因为寄存器的宽度是32位,因此寄存器间接寻址只能给出32位偏移量,所以只能是段内近转移。在内存间接寻址时,指令后面是方括号内的有效地址,在这个地址上存放跳转的目标地址。比如,在[00903DEC]处有如下数据:7C 82 59 00 A7 01 85 65 9F 01
内存字节是连续存放的,如何确定取多少作为目标地址呢?dword ptr 指明该有效地址指明的是双字,所以取
0059827C作段内跳转。反之,fward ptr 指明后面的有效地址是指向48位完全地址,所以取19F:658501A7 做远跳转。
注意:在保护模式下,如果段间转移涉及优先级的变化,则有一系列复杂的保护检查,现在可不加理会。将来等各位功力提升以后可以自己去学习。
条件转移指令jxx:只能作段内转移,且只支持直接寻址。
=========================================
调用指令CALL:
Call的寻址方式与jmp基本相同,但为了从子程序返回,该指令在跳转以前会把紧接着它的下一条指令的地址压进堆栈。如果是段内调用(目标地址是32位偏移量),则压入的也只是一个偏移量。如果是段间调用(目标地址是48位全地址),则也压入下一条指令的完全地址。同样,如果段间转移涉及优先级的变化,则有一系列复杂的保护检查。
与之对应retn/retf指令则从子程序返回。它从堆栈上取得返回地址(是call指令压进去的)并跳到该地址执行。retn取32位偏移量作段内返回,retf取48位全地址作段间返回。retn/f 还可以跟一个立即数作为操作数,该数实际上是从堆栈上传给子程序的参数的个数(以字计)返回后自动把堆栈指针esp加上指定的数*2,从而丢弃堆栈中的参数。这里具体的细节留待下一篇讲述。
虽然call和ret设计为一起工作,但它们之间没有必然的联系。就是说,如果你直接用push指令向堆栈中压入一个数,然后执行ret,他同样会把你压入的数作为返回地址,而跳到那里去执行。这种非正常的流程转移可以被用作反跟踪手段。
==========================================
中断指令INT n
在保护模式下,这个指令必定会被操作系统截获。在一般的PE程序中,这个指令已经不太见到了,而在DOS时代,中断是调用操作系统和BIOS的重要途径。现在的程序可以文质彬彬地用名字来调用windows功能,如 call user32!getwindowtexta。从程序角度看,INT指令把当前的标志寄存器先压入堆栈,然后把下一条指令的完全地址也压入堆栈,最后根据操作数n来检索“中断描述符表”,试图转移到相应的中断服务程序去执行。通常,中断服务程序都是操作系统的核心代码,必然会涉及到优先级转换和保护性检查、堆栈切换等等,细节可以看一些高级的教程。
与之相应的中断返回指令IRET做相反的操作。它从堆栈上取得返回地址,并用来设置CS:EIP,然后从堆栈中弹出标志寄存器。注意,堆栈上的标志寄存器值可能已经被中断服务程序所改变,通常是进位标志C, 用来表示功能是否正常完成。同样的,IRET也不一定非要和INT指令对应,你可以自己在堆栈上压入标志和地址,然后执行IRET来实现流程转移。实际上,多任务操作系统常用此伎俩来实现任务转换。
广义的中断是一个很大的话题,有兴趣可以去查阅系统设计的书籍。
============================================
装入全指针指令LDS,LES,LFS,LGS,LSS
这些指令有两个操作数。第一个是一个通用寄存器,第二个操作数是一个有效地址。指令从该地址取得48位全指针,将选择符装入相应的段寄存器,而将32位偏移量装入指定的通用寄存器。注意在内存中,指针的存放形式总是32位偏移量在前面,16位选择符在后面。装入指针以后,就可以用DS:[ESI]这样的形式来访问指针指向的数据了。
============================================
字符串操作指令
这里包括CMPS,SCAS,LODS,STOS,MOVS,INS和OUTS等。这些指令有一个共同的特点,就是没有显式的操作数,而由硬件规定使用DS:[ESI]指向源字符串,用ES:[EDI]指向目的字符串,用AL/AX/EAX做暂存。这是硬件规定的,所以在使用这些指令之前一定要设好相应的指针。
这里每一个指令都有3种宽度形式,如CMPSB(字节比较)、CMPSW(字比较)、CMPSD(双字比较)等。
CMPSB:比较源字符串和目标字符串的第一个字符。若相等则Z标志置1。若不等则Z标志置0。指令执行完后,ESI 和EDI都自动加1,指向源/目标串的下一个字符。如果用CMPSW,则比较一个字,ESI/EDI自动加2以指向下一个字。
如果用CMPSD,则比较一个双字,ESI/EDI自动加4以指向下一个双字。(在这一点上这些指令都一样,不再赘述)
SCAB/W/D 把AL/AX/EAX中的数值与目标串中的一个字符/字/双字比较。
LODSB/W/D 把源字符串中的一个字符/字/双字送入AL/AX/EAX
STOSB/W/D 把AL/AX/EAX中的直送入目标字符串中
MOVSB/W/D 把源字符串中的字符/字/双字复制到目标字符串
INSB/W/D 从指定的端口读入字符/字/双字到目标字符串中,端口号码由DX寄存器指定。
OUTSB/W/D 把源字符串中的字符/字/双字送到指定的端口,端口号码由DX寄存器指定。
串操作指令经常和重复前缀REP和循环指令LOOP结合使用以完成对整个字符串的操作。而REP前缀和LOOP指令都有硬件规定用ECX做循环计数器。举例:
LDS ESI,SRC_STR_PTR
LES EDI,DST_STR_PTR
MOV ECX,200
REP MOVSD
上面的代码从SRC_STR拷贝200个双字到DST_STR. 细节是:REP前缀先检查ECX是否为0,若否则执行一次MOVSD,ECX自动减1,然后执行第二轮检查、执行......直到发现ECX=0便不再执行MOVSD,结束重复而执行下面的指令。
LDS ESI,SRC_STR_PTR
MOV ECX,100
LOOP1:
LODSW
.... (deal with value in AX)
LOOP LOOP1
.....
从SRC_STR处理100个字。同样,LOOP指令先判断ECX是否为零,来决定是否循环。每循环一轮ECX自动减1。
REP和LOOP 都可以加上条件,变成REPZ/REPNZ 和 LOOPZ/LOOPNZ. 这是除了ECX外,还用检查零标志Z. REPZ 和LOOPZ在Z为1时继续循环,否则退出循环,即使ECX不为0。REPNZ/LOOPNZ则相反。
tianxj 2008-1-30 21:42
高级语言程序的汇编解析
在高级语言中,如C和PASCAL等等,我们不再直接对硬件资源进行操作,而是面向于问题的解决,这主要体现在数据抽象化和程序的结构化。例如我们用变量名来存取数据,而不再关心这个数据究竟在内存的什么地方。这样,对硬件资源的使用方式完全交给了编译器去处理。不过,一些基本的规则还是存在的,而且大多数编译器都遵循一些规范,这使得我们在阅读反汇编代码的时候日子好过一点。这里主要讲讲汇编代码中一些和高级语言对应的地方。
1. 普通变量。通常声明的变量是存放在内存中的。编译器把变量名和一个内存地址联系起来(这里要注意的是,所谓的“确定的地址”是对编译器而言在编译阶段算出的一个临时的地址。在连接成可执行文件并加载到内存中执行的时候要进行重定位等一系列调整,才生成一个实时的内存地址,不过这并不影响程序的逻辑,所以先不必太在意这些细节,只要知道所有的函数名字和变量名字都对应一个内存的地址就行了),所以变量名在汇编代码中就表现为一个有效地址,就是放在方括号中的操作数。例如,在C文件中声明:
int my_age;
这个整型的变量就存在一个特定的内存位置。语句 my_age= 32; 在反汇编代码中可能表现为:
mov word ptr [007E85DA], 20
所以在方括号中的有效地址对应的是变量名。又如:
char my_name[11] = "lianzi2000";
这样的说明也确定了一个地址,对应于my_name. 假设地址是007E85DC,则内存中[007E85DC]='l',[007E85DD]='i', etc. 对my_name的访问也就是对这地址处的数据访问。
指针变量其本身也同样对应一个地址,因为它本身也是一个变量。如:
char *your_name;
这时也确定变量"your_name"对应一个内存地址,假设为007E85F0. 语句your_name=my_name;很可能表现为:
mov [007E85F0], 007E85DC ;your_name的内容是my_name的地址。
2. 寄存器变量
在C和C++中允许说明寄存器变量。register int i; 指明i是寄存器存放的整型变量。通常,编译器都把寄存器变量放在esi和edi中。寄存器是在cpu内部的结构,对它的访问要比内存快得多,所以把频繁使用的变量放在寄存器中可以提高程序执行速度。
3. 数组
不管是多少维的数组,在内存中总是把所有的元素都连续存放,所以在内存中总是一维的。例如,int i_array[2][3]; 在内存确定了一个地址,从该地址开始的12个字节用来存贮该数组的元素。所以变量名i_array对应着该数组的起始地址,也即是指向数组的第一个元素。存放的顺序一般是i_array[0][0],[0][1],[0][2],[1][0],[1][1],[1][2] 即最右边的下标变化最快。当需要访问某个元素时,程序就会从多维索引值换算成一维索引,如访问i_array[1][1],换算成内存中的一维索引值就是1*3+1=4.这种换算可能在编译的时候就可以确定,也可能要到运行时才可以确定。无论如何,如果我们把i_array对应的地址装入一个通用寄存器作为基址,则对数组元素的访问就是一个计算有效地址的问题:
; i_array[1][1]=0x16
lea ebx,xxxxxxxx ;i_array 对应的地址装入ebx
mov edx,04 ;访问i_array[1][1],编译时就已经确定
mov word ptr [ebx+edx*2], 16 ;
当然,取决于不同的编译器和程序上下文,具体实现可能不同,但这种基本的形式是确定的。从这里也可以看到比例因子的作用(还记得比例因子的取值为1,2,4或8吗?),因为在目前的系统中简单变量总是占据1,2,4或者8个字节的长度,所以比例因子的存在为在内存中的查表操作提供了极大方便。
4. 结构和对象
结构和对象的成员在内存中也都连续存放,但有时为了在字边界或双字边界对齐,可能有些微调整,所以要确定对象的大小应该用sizeof操作符而不应该把成员的大小相加来计算。当我们声明一个结构变量或初始化一个对象时,这个结构变量和对象的名字也对应一个内存地址。举例说明:
struct tag_info_struct
{
int age;
int sex;
float height;
float weight;
} marry;
变量marry就对应一个内存地址。在这个地址开始,有足够多的字节(sizeof(marry))容纳所有的成员。每一个成员则对应一个相对于这个地址的偏移量。这里假设此结构中所有的成员都连续存放,则age的相对地址为0,sex为2, height 为4,weight为8。
; marry.sex=0;
lea ebx,xxxxxxxx ;marry 对应的内存地址
mov word ptr [ebx+2], 0
......
对象的情况基本相同。注意成员函数具体的实现在代码段中,在对象中存放的是一个指向该函数的指针。
5. 函数调用
一个函数在被定义时,也确定一个内存地址对应于函数名字。如:
long comb(int m, int n)
{
long temp;
.....
return temp;
}
这样,函数comb就对应一个内存地址。对它的调用表现为:
CALL xxxxxxxx ;comb对应的地址。这个函数需要两个整型参数,就通过堆栈来传递:
;lresult=comb(2,3);
push 3
push 2
call xxxxxxxx
mov dword ptr [yyyyyyyy], eax ;yyyyyyyy是长整型变量lresult的地址
这里请注意两点。第一,在C语言中,参数的压栈顺序是和参数顺序相反的,即后面的参数先压栈,所以先执行push 3. 第二,在我们讨论的32位系统中,如果不指明参数类型,缺省的情况就是压入32位双字。因此,两个push指令总共压入了两个双字,即8个字节的数据。然后执行call指令。call 指令又把返回地址,即下一条指令(mov dword ptr....)的32位地址压入,然后跳转到xxxxxxxx去执行。
在comb子程序入口处(xxxxxxxx),堆栈的状态是这样的:
03000000 (请回忆small endian 格式)
02000000
yyyyyyyy <--ESP 指向返回地址
前面讲过,子程序的标准起始代码是这样的:
push ebp ;保存原先的ebp
mov ebp, esp;建立框架指针
sub esp, XXX;给临时变量预留空间
.....
执行push ebp之后,堆栈如下:
03000000
02000000
yyyyyyyy
old ebp <---- esp 指向原来的ebp
执行mov ebp,esp之后,ebp 和esp 都指向原来的ebp. 然后sub esp, xxx 给临时变量留空间。这里,只有一个临时变量temp,是一个长整数,需要4个字节,所以xxx=4。这样就建立了这个子程序的框架:
03000000
02000000
yyyyyyyy
old ebp <---- 当前ebp指向这里
temp
所以子程序可以用[ebp+8]取得第一参数(m),用[ebp+C]来取得第二参数(n),以此类推。临时变量则都在ebp下面,如这里的temp就对应于[ebp-4].
子程序执行到最后,要返回temp的值:
mov eax,[ebp-04]
然后执行相反的操作以撤销框架:
mov esp,ebp ;这时esp 和ebp都指向old ebp,临时变量已经被撤销
pop ebp ;撤销框架指针,恢复原ebp.
这是esp指向返回地址。紧接的retn指令返回主程序:
retn 4
该指令从堆栈弹出返回地址装入EIP,从而返回到主程序去执行call后面的指令。同时调整esp(esp=esp+4*2),从而撤销参数,使堆栈恢复到调用子程序以前的状态,这就是堆栈的平衡。调用子程序前后总是应该维持堆栈的平衡。从这里也可以看到,临时变量temp已经随着子程序的返回而消失,所以试图返回一个指向临时变量的指针是非法的。
为了更好地支持高级语言,INTEL还提供了指令Enter 和Leave 来自动完成框架的建立和撤销。Enter 接受两个操作数,第一个指明给临时变量预留的字节数,第二个是子程序嵌套调用层数,一般都为0。enter xxx,0 相当于:
push ebp
mov ebp,esp
sub esp,xxx
leave 则相当于:
mov esp,ebp
pop ebp
=============================================================
Saver 2005-2-17 20:49
8088 汇编速查手册
一、数据传输指令
───────────────────────────────────────
它们在存贮器和寄存器、寄存器和输入输出端口之间传送数据.
1. 通用数据传送指令.
MOV 传送字或字节.
MOVSX 先符号扩展,再传送.
MOVZX 先零扩展,再传送.
PUSH 把字压入堆栈.
POP 把字弹出堆栈.
PUSHA 把AX,CX,DX,BX,SP,BP,SI,DI依次压入堆栈.
POPA 把DI,SI,BP,SP,BX,DX,CX,AX依次弹出堆栈.
PUSHAD 把EAX,ECX,EDX,EBX,ESP,EBP,ESI,EDI依次压入堆栈.
POPAD 把EDI,ESI,EBP,ESP,EBX,EDX,ECX,EAX依次弹出堆栈.
BSWAP 交换32位寄存器里字节的顺序
XCHG 交换字或字节.( 至少有一个操作数为寄存器,段寄存器不可作为操作数)
CMPXCHG 比较并交换操作数.( 第二个操作数必须为累加器AL/AX/EAX )
XADD 先交换再累加.( 结果在第一个操作数里 )
XLAT 字节查表转换.
── BX 指向一张 256 字节的表的起点, AL 为表的索引值 (0-255,即
0-FFH); 返回 AL 为查表结果. ( [BX+AL]->AL )
2. 输入输出端口传送指令.
IN I/O端口输入. ( 语法: IN 累加器, {端口号│DX} )
OUT I/O端口输出. ( 语法: OUT {端口号│DX},累加器 )
输入输出端口由立即方式指定时, 其范围是 0-255; 由寄存器 DX 指定时,
其范围是 0-65535.
3. 目的地址传送指令.
LEA 装入有效地址.
例: LEA DX,string ;把偏移地址存到DX.
LDS 传送目标指针,把指针内容装入DS.
例: LDS SI,string ;把段地址:偏移地址存到DS:SI.
LES 传送目标指针,把指针内容装入ES.
例: LES DI,string ;把段地址:偏移地址存到ES:DI.
LFS 传送目标指针,把指针内容装入FS.
例: LFS DI,string ;把段地址:偏移地址存到FS:DI.
LGS 传送目标指针,把指针内容装入GS.
例: LGS DI,string ;把段地址:偏移地址存到GS:DI.
LSS 传送目标指针,把指针内容装入SS.
例: LSS DI,string ;把段地址:偏移地址存到SS:DI.
4. 标志传送指令.
LAHF 标志寄存器传送,把标志装入AH.
SAHF 标志寄存器传送,把AH内容装入标志寄存器.
PUSHF 标志入栈.
POPF 标志出栈.
PUSHD 32位标志入栈.
POPD 32位标志出栈.
二、算术运算指令
───────────────────────────────────────
ADD 加法.
ADC 带进位加法.
INC 加 1.
AAA 加法的ASCII码调整.
DAA 加法的十进制调整.
SUB 减法.
SBB 带借位减法.
DEC 减 1.
NEC 求反(以 0 减之).
CMP 比较.(两操作数作减法,仅修改标志位,不回送结果).
AAS 减法的ASCII码调整.
DAS 减法的十进制调整.
MUL 无符号乘法.
IMUL 整数乘法.
以上两条,结果回送AH和AL(字节运算),或DX和AX(字运算),
AAM 乘法的ASCII码调整.
DIV 无符号除法.
IDIV 整数除法.
以上两条,结果回送:
商回送AL,余数回送AH, (字节运算);
或 商回送AX,余数回送DX, (字运算).
AAD 除法的ASCII码调整.
CBW 字节转换为字. (把AL中字节的符号扩展到AH中去)
CWD 字转换为双字. (把AX中的字的符号扩展到DX中去)
CWDE 字转换为双字. (把AX中的字符号扩展到EAX中去)
CDQ 双字扩展. (把EAX中的字的符号扩展到EDX中去)
三、逻辑运算指令
───────────────────────────────────────
AND 与运算.
OR 或运算.
XOR 异或运算.
NOT 取反.
TEST 测试.(两操作数作与运算,仅修改标志位,不回送结果).
SHL 逻辑左移.
SAL 算术左移.(=SHL)
SHR 逻辑右移.
SAR 算术右移.(=SHR)
ROL 循环左移.
ROR 循环右移.
RCL 通过进位的循环左移.
RCR 通过进位的循环右移.
以上八种移位指令,其移位次数可达255次.
移位一次时, 可直接用操作码. 如 SHL AX,1.
移位>1次时, 则由寄存器CL给出移位次数.
如 MOV CL,04
SHL AX,CL
四、串指令
───────────────────────────────────────
DS:SI 源串段寄存器 :源串变址.
ES:DI 目标串段寄存器:目标串变址.
CX 重复次数计数器.
AL/AX 扫描值.
D标志 0表示重复操作中SI和DI应自动增量; 1表示应自动减量.
Z标志 用来控制扫描或比较操作的结束.
MOVS 串传送.
( MOVSB 传送字符. MOVSW 传送字. MOVSD 传送双字. )
CMPS 串比较.
( CMPSB 比较字符. CMPSW 比较字. )
SCAS 串扫描.
把AL或AX的内容与目标串作比较,比较结果反映在标志位.
LODS 装入串.
把源串中的元素(字或字节)逐一装入AL或AX中.
( LODSB 传送字符. LODSW 传送字. LODSD 传送双字. )
STOS 保存串.
是LODS的逆过程.
REP 当CX/ECX<>0时重复.
REPE/REPZ 当ZF=1或比较结果相等,且CX/ECX<>0时重复.
REPNE/REPNZ 当ZF=0或比较结果不相等,且CX/ECX<>0时重复.
REPC 当CF=1且CX/ECX<>0时重复.
REPNC 当CF=0且CX/ECX<>0时重复.
五、程序转移指令
───────────────────────────────────────
1>无条件转移指令 (长转移)
JMP 无条件转移指令
CALL 过程调用
RET/RETF过程返回.
2>条件转移指令 (短转移,-128到+127的距离内)
( 当且仅当(SF XOR OF)=1时,OP1<OP2 )
JA/JNBE 不小于或不等于时转移.
JAE/JNB 大于或等于转移.
JB/JNAE 小于转移.
JBE/JNA 小于或等于转移.
以上四条,测试无符号整数运算的结果(标志C和Z).
JG/JNLE 大于转移.
JGE/JNL 大于或等于转移.
JL/JNGE 小于转移.
JLE/JNG 小于或等于转移.
以上四条,测试带符号整数运算的结果(标志S,O和Z).
JE/JZ 等于转移.
JNE/JNZ 不等于时转移.
JC 有进位时转移.
JNC 无进位时转移.
JNO 不溢出时转移.
JNP/JPO 奇偶性为奇数时转移.
JNS 符号位为 "0" 时转移.
JO 溢出转移.
JP/JPE 奇偶性为偶数时转移.
JS 符号位为 "1" 时转移.
3>循环控制指令(短转移)
LOOP CX不为零时循环.
LOOPE/LOOPZ CX不为零且标志Z=1时循环.
LOOPNE/LOOPNZ CX不为零且标志Z=0时循环.
JCXZ CX为零时转移.
JECXZ ECX为零时转移.
4>中断指令
INT 中断指令
INTO 溢出中断
IRET 中断返回
5>处理器控制指令
HLT 处理器暂停, 直到出现中断或复位信号才继续.
WAIT 当芯片引线TEST为高电平时使CPU进入等待状态.
ESC 转换到外处理器.
LOCK 封锁总线.
NOP 空操作.
STC 置进位标志位.
CLC 清进位标志位.
CMC 进位标志取反.
STD 置方向标志位.
CLD 清方向标志位.
STI 置中断允许位.
CLI 清中断允许位.
六、伪指令
───────────────────────────────────────
DW 定义字(2字节).
PROC 定义过程.
ENDP 过程结束.
SEGMENT 定义段.
ASSUME 建立段寄存器寻址.
ENDS 段结束.
END 程序结束.
汇编常用命令、指令一览 (应该对新手有点用处)
汇编常用命令、指令一览
(作者:wutoyou)
--------------------------------------------------------------------------------
MOV(MOVe) 传送指令P28
PUSH 入栈指令P32
POP 出栈指令P33
XCHG(eXCHanG) 交换指令P34
XLAT(TRANSLATE) 换码指令P34
LEA (Load Effective Address) 有效地址送寄存器指令P35
LDS(Load DS with pointer) 指针送寄存器和DS指令P35
LES(Load ES with pointer) 指针送寄存器和ES指令P35
LAHF(Load AH with Flags) 标志位送AH指令P36
SAHF(Store AH into Flgs) AH送标志寄存器指令P36
PUSHF(PUSH the Flags) 标志进栈指令P36
POPF(POP the Flags) 标志出栈指令P37
ADD 加法指令P38
ADC 带进位加法指令P39
INC 加1指令P39
SUB(SUBtract) 不带借位的减法指令P40
SBB(SuVtrach with borrow) 带借位的减法指令P40
DEC(DECrement) 减1指领P41
NEG(NEGate) 求补指令P41
CMP(CoMPare) 比较指令P42
MUL(unsinged MULtiple) 无符号数乘法指令P46
IMUL(sIgned MUL tiple) 有符号数乘法指令P46
DIV(unsigned DIVide) 无符号数除法指令P48
IDIV(sIgned DIVide) 有符号数除法指令P48
CBW(Count Byte to Word) 字节转换为字指令P50
CWD(Count Word to Doble word) 字转换为双字指令P50
DAA 压缩的BCD码加法十进制调整指令P53
DAS 压缩的BCD码减法十进制调整指令P53
AAA 非压缩的BCD码加法十进制调整指令P54
AAS 非压缩的BCD码加法十进制调整指令P54
AND 逻辑与指令P54
OR 逻辑或指令P55
XOR 逻辑异或指令P56
NOT 逻辑非指令P56
TEST 测试指令P57
SHL(SHift logical Letf) 逻辑左移指令P57
SHR(SHift logical Right) 逻辑右移指令P57
ROL(Rotate Left ) 循环左移指令P58
ROR(Rotate Right) 循环右移指令P58
RCL(Rotate Left through Carry) 带进位循环左移P58
RCR(Rotate Right through Carry) 带进位循环左移P58
MOVS(MOVe String) 串传送指令P58
STOS(STOre into String) 存入串指令P60
LODS(LOad from string) 从串取指令P60
REP(REPeat) 重复操作前缀P61
CLD(CLear Direction flag) 清除方向标志指令P61
STD(SeT Direction flag) 设置方向标志指令P61
CMPS(CoMPare String) 串比较指令P62
SCAS(SCAn String) 串扫描指令P63
REPE/REPZ(REPeat while Equal/Zero)相等/为零时重复操作前缀P63
REPNE/REPNZ(REPeat while Not Equal/Zero)不相等/不为零进重复前缀
IN(INput) 输入指令P65
OUT(OUTput) 输出指令P65
JMP(JuMP) 无条件转移指令P66
JZ,JNZ,JS,JNS,JO,JNO,JP,JNP,JB,JNB,JBE,JNBE,JL,JNL,JLE,JNLE,JCXZ 条件转移指令P67
LOOP 循环指令P70
LOOPZ/LOOPE 为零/相等时循环指令P70
LOOPNZ/LOOPNE 不为零/不相等时循环指令P70
CALL 子程序调用指令P71
RET(RETun) 子程序返回指令P72
CLC(CLear Carry) 进位位置0指令P77
CMC(CoMplement Carry) 进位位求反指令P77
SRC(SeT Carry) 进位位置1指令P77
NOP(No OPeretion) 无操作指令P77
HLT(HaLT) 停机指令P77
OFFSET 返回偏移地址P85
SEG 返回段地址P85
EQU(=) 等值语句P90
PURGE 解除语句P91
DUP 操作数字段用复制操作符P93
SEGMENT,ENDS 段定义指令P95
ASSUME 段地址分配指令P95
ORG 起始偏移地址设置指令P96
$ 地址计数器的当前值P97
PROC,ENDP 过程定义语句P97
NAME,TITLE,END 程序开始结束语句P98
MACRO,ENDM 宏定义指令P99
--------------------------------------------------------------------------------
段内直接短跳转 JMP SHORT OPR
段内直接近转移 JMP NEAR PTR OPR
段内间接转移 JMP WORD PTR OPR
段间直接转移 JMP FAR PTR OPR
段间间接转移 JMP DWORD PTR OPR
JZ OPR //结果为零转移
JNZ OPR //结果不为零转移
JS OPR //结果为负转移
JNS OPR //结果为正转移
JO OPR //溢出转移
JNO OPR //不溢出转移
JP OPR //结果为偶转移
JNP OPR //结果为奇转移
JC OPR //有进位转移
JNC OPR //无进位转移
要上网,踏新浪
找资料,上百度
当黑客,乘黑鹰
学破解,来看雪
算法分析是道坎
破解知识都沾边
要想一直打此过
文武全才是方向
--------------------------------------------------------------------------------
【经验总结】
要上网,踏新浪
找资料,上百度
当黑客,乘黑鹰
学破解,来看雪
算法分析是道坎
破解知识都沾边
要想一直打此过
文武全才是方向
学破解的好习惯(也是做CRACK ME 的好习惯):
坚持独立完成破解(CRACK ME)。贵在坚持。
案头常备基础破解教材查询。
记事本,记录破解过程。
了解CM作者的题目要求是做CRACK ME的基本常识。
不要为了做CRACK ME而做。记住,我们是为了提高自己的破解技术来做CRACK ME 的。
CRACK ME的练习并不在数量的多少,而在于质量,在于不断的练习、总结和积累经验。
要学好破解的最好方法就是不断地自己独立地破解而不
是在论坛上焦急地等待别人的关照。只有不断地实践,才能体会破解书中散落的种种技术细节问题。很多菜鸟也懂这个道理,但心里有一种急功近利和畏难心理在做怪,总是迫不急待地看破文答案。这不是一个好习惯。其实开始学破解时,好几天没有进展是很平常的。这是好事。百分之九十的人都会这样。为什么呢?每位破解高手都有他的强项和弱项。多数人遇到的障碍,往往是他的弱项。你想提高自己,弥补弱项,就以它为目标钻研下去。
很简单的道理,强项都不是天生的。
有没有达到学算法分析水平的测试。
我自己的总结经验就是
断点的设定应根据程序的相应事件进行着手,这是设断总的思路原则。
对许多无提示的软件,在动态调试中查看字符串是常用手段。
设断时还要考虑编程语言的特点。
易语言的下断常用方法。
破解的最最基础:
就是对程序流程的理解是否透彻。通常情况下,会爆破意味着对程序流程的理解比较透彻,而算法分
析的基石就是程序流程。有数学天分的人,往往逻辑性强。
循环渐多迷人眼,
程序流程指方向,
分清主次破四方,
程序流程念心中。
判断明码注册码的一般程序流程:
段1:赋值注册码。
段2:检测注册码的对错,即控制程序流程的语句。特征是条件判断语句。
段3:分道扬镳。对和错两个子程序。
判断暗码注册码算法分析的一般程序流程:
段1:赋值假用户名和假码。
段2:用户名和注册码算法段
段3:检测注册码的对错,即控制程序流程的语句。特征是条件判断语句。
段4:分道扬镳。对和错两个子程序。
目的是迅速确定算法分析的大致范围甚至具体的方位。
附算法分析入门教程实战篇基础知识之一:程序流程。
算法分析入门教程实战篇基础知识之二:常用ASCII表。在OD的命令行里用?指令助记忆。
汇编资料书查询少见字符串。
算法分析入门教程实战篇基础知识之三:汇编语言集合 转自PYG官方论坛飘云阁 初学者乐园。
新手常见的调试问题处理方法有------
--返回主程序领空的一般方法:
1.CTAL+F9。
2.ALT+F9。
3.内存代码段设断法。
--逆向追踪的两个例子,体会逆向工程的大意。
--许多朋友对CALL的含义不知道如何快速地理解它的用途。这可是算法分析的关键。示范了 如何看寄存器窗口和内存窗口、信息窗口,唠叨了一大堆帮助他们理解,其中多次运用了相应的断点法,加深理解。其实看这几个窗口的核心就是我们输入的假用户名和假码两内奸,结合程序流程的关键控制流程语句细细研读,不放心的可以变换用户名和注册假码多用用对比法。
---许多的NAG窗口。F12暂停,然后CTAL+F9是个好的组合。
算法分析应用价值具体例子:利用程序自身写注册机具体过程和思路。
推荐laomms的许多相关的系列文章。这方面laomms在看雪论坛上写了许多相关的文章,
大家有兴趣的可以在看雪论坛输入laomms查询,下载laomms文中的附件练习。
算法分析的作用
1.象杀毒软件样方便、高效。现在的注册码许多都对应机器码,一机一码。下面我就不举例了,大家都是比我聪明的
高智商,只有我这傻瓜在这象蜗牛样爬格子。论坛上讲得太多了,小孩子都能倒背。
2.发现软件的BUG,尽量调试弥补。
3.也可以打补丁。
4.利用软件自身做注册机。这是一个方向,终点是调试、完善软件。
在这唠叨一句,破解很容易走火入魔。奉劝爱
好破解的朋友注意心理健康的调节,有自己的业余爱好,不要做破解呆子。
算法分析需要懂汇编语言吗?
写文章需要认识字吗?
高手是不是都很牛,一看汇编语句就明明白白我的心?
看小说是不是所有的字都认识?研究生英语考试的阅读理解生词多不多,是不是全能看懂?
其实猜测生词和冷僻
词汇也一样,我们很难在没有上下文的情况下猜出词意。但这不代表我连上下文也看不懂,更不代表没有学单词的必要。
学汇编语言是算法分析的基础。但不意味着我们只有完全掌握了汇编语言后才能来搞算法分析。学于算法分析,用于算法
分析,这是可行的。
怎样象猜冷僻词样猜不太理解的汇编语言?要点如下:
A.熟悉算法分析大段落的文章结构模板。
B.充分利用动态调试的特点。对吃不准的CALL,宁愿更换用户名和注册码试验,采用对比法鉴定自己的猜想。
在算法分析里,现在的软件子循环象子公司一样多,子循环内套着子循环。晕死你。
C.在关键的地址处可以采用内存断点法或硬件断点法跟踪算法流程。
D.经常对堆栈窗口、寄存器窗口和信息窗口、内存窗口进行查看,特别留意与假用户名和假注册码相关的汇编语言。
算法分析大段落的文章结构模板:
A.读取假用户名和假码。
B.获取位数。判断是否为空。以前经常看到有CRACK ME的作者出洋相,什么都不输入,直接点注册,居然成功。所以这往
往是我们确定算法分析的开始的依据。为什么直接点注册,居然成功?要善于发现问题。
C.对用户名和注册码的限制条件判断。
D.对用户名和(或)注册码的处理段,可以是逻辑运算和(或)各种字符的转换。这里可分几个小段分别处理。
E.对处理过的用户名和(或)注册码进行保藏,地址可以是内存、堆栈、注册表或文件。
F.调用处理过的用户名和(或)注册码,进行比较。
G.消除比较后的用户名和(或)注册码进行安全处理,你可以理解为毁尸灭迹。
上述的结构前后顺序可能不完全一样,许多语句或段落也不一定全具备。你心中有了这个模板,猜CALL的含义会轻松很多。
几个常见的汇编语句组合。
*PUSH002:对字符的ASCII值一定要熟悉,深入分析非密码学算法的关键。很多算法是在字符的ASCII值
上做文章的。连字符在序列号中经常用到,应熟记。空 格--> 20在NAG窗口中常用,应背熟。
BUG和修改字符串的简单探讨。
《算法注册机编写扫盲BY我要[DFCG]》,看雪论坛上有laomms的六篇系列注册机教程、CCDEBUGER及
其他高手的OLLYDBG入门系列。
看雪论坛名人整盅游戏系列,穿插调节文章的节奏--
--看雪论坛之藏龙卧虎是破解界公认的。CRACK ME的难度也是业内公认的。
--我的案头放的是段钢著的《家蜜和街蜜II》--看雪老大的风流韵事。
--在此感谢laomms的好文赐我灵感。啥?
侵权?你知道不,laomms是个女的,身材超赞的。还不懂?情人节快到了,明白了吗?
我俩分啥彼此。
要成名,先断背
学算法,先学断
韩红和管彤的断背之说,闹的是沸沸扬扬。为了感谢laomms,我这整了个小花边。无他,想
让laomms出名,只好让他当了会女的。废话,不当女的,我不就和他断背了吗?呵呵,搞点小花絮
轻松一下。
哥们我天生就是孬种。为什么老说哥们?怕大家怀疑我是女的呀。
---这是什么论坛啊?别牢骚了,没看见论坛坛主的名字吗?
我自逍遥又风流,
气你气你就气你。
情人节时忙买花,
送花莫要写错名。
天,你这哪是算法分析,整个一看雪论坛名人整盅游戏。轻松一下,休息休息。
---是控制程序流程的语句,这可是注册码的重要特征啊。你说是就是啊?我还说偶像laomms是个武林高手呢。
---大家都是比我聪明的高智商,只有我这傻瓜在这象蜗牛样爬格子。论坛上讲得太多了,
小孩子都能倒背。
---等等,还有个小BUG,你输入错的机器码才会出现修改过的注册机。那万一买彩票中了大奖,
输入的是正确的机器码呢?
不是看不到你辛苦做的注册机了吗?
---“crackmegame游戏一下”也有一个小BUG,你看出来了吗?我是很长时间才发现。刚想上网吹吹牛,发现阳小子一眼就看出来了。
一山还比一山高,
不是菜鸟我是谁?
望尽破解路遥遥,
我不勤奋待何日?
横批:不服不行。
---大写和小写的相互转换:你呀的穿上小皮袄我就认不出你是乌龟了。
---数值的十进制和16进制转化、字符直接变数字和数字直接变字符。你就是脱了小皮袄我照样认出你是小乌龟。
要上网,踏新浪
找资料,上百度
当黑客,乘黑鹰
学破解,来看雪
算法分析是道坎
破解知识都沾边
要想一直打此过
文武全才是方向
--------------------------------------------------------------------------------
【版权声明】: 本文原创于看雪技术论坛和NBA2005, 转载请注明作者并保持文章的完整, 谢谢!
2008年02月12日 上午 12:15:15常用,应背熟。
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)