看雪-课程-加密与解密基础
July 13, 2020 8:36 PM
学习看雪的加密与解密基础
1.概述
0 预备知识
1.汇编语言 《王爽汇编》
2.C语言
3.Win32编程,API方式的Windows程序设计。Charles Petzold所著的《Windows程序设计》以C语言为讲解平台
1.1起始
关键词:加密、解密、逆向工程、静态分析、动态分析
从理论上说,几乎没有破解不了的保护。对软件的保护仅仅靠技术是不够的,最终要靠人们的知识产权意识和法制观念的进步以及生活水平的提高。如果一种保护技术的强度强到足以让破解者在软件的生命周期内无法将其完全破解,这种保护技术就可以说是非常成功的。软件保护方式的设计应在一开始就作为软件开发的一部分来考虑,列入开发计划和开发成本中,并在保护强度、成本、易用性之间进行折衷考虑,选择一个合适的平衡点。
1.2软件解密技术
通过研究解密技术可以掌握一些Windows系统底层知识.
作为一个合格的程序员,上要能上到需求分析,设计抽象,设计模式等,下要能下到系统核心,熟悉整个系统的底层结构。
学习解密建议掌握:
- 汇编语言一定得学好;
- 至少掌握一门高级语言;
- 握Win32编程,WinSDK程序设计就是API方式的Windows程序设计,学习Windows API将使您更深入地了解Windows工作方式。此类书籍有Charles Petzold著的《Windows程序设计》(以C来讲解)。
- 有精力,再看看《Windows核心编程》这类书,相信会使你内力大增的。
- 有了这些基础,再参考《加密与解密》(第二版)这本书,你将会扣开加解密的大门。
解密技术涉及到了很多的方面,这里只是简单窥探一下破解者的操作手法――静态分析技术与动态分析技术。
1.3 静态分析介绍
高级语言编写的程序有两种形式:
- 一种编译成机器语言在CPU上执行,如Visual C ++、PASCAL等。由于机器语言与汇编语言几乎是一一对应的,因此可将机器语言转化成汇编语言,这个过程称为反汇编(Disassembler)。
- 另一种高级语言是一边解释一边执行的,称之为解释性语言。如Visual Basic 3.0/4.0、Visual FoxPro等,这类语言编译的程序可以被还原成高级语言的原始结构,这个过程称为反编译(Decompiler)。
++静态分析,即从反汇编出来的程序清单上分析程序流程,了解关注模块完成的功能++
1.反汇编
编译执行的语言因为直接同CPU的指令集打交道,具有很强的指令依赖性和系统依赖性,但编译后的程序执行效率要比解释语言高得多
反汇编工具,如W32Dasm、IDA pro等,将二进制的机器语言反汇编成汇编代码,在汇编代码级别上来分析程序的功能
2.反编译
解释性语言在编译期间,它的程序代码不被直接转换成计算机识别的二进制代码,而是只生成一种中间代码,目的不是让计算机直接去执行,而只是为了进行先期语法检查。这些代码被放置到内存的“数据段”中,运行时由一个解释器再动态解释被存放于“数据段”的伪代码。因此,可以看到很多伪编译的程序都带有一个很大的DLL之类的文件,例如Visual Basic、Visual FoxPro、Power Builder、Java等。解释执行的语言由于其解释器不需要直接同机器码打交道,所以实现起来较为简单,而且便于在不同的平台上面移植。解释语言编译的代码实际上是“变形的源代码”,只要能理解其对应机制,就能做出反编译器来。这类语言编译的程序可以被还原成高级语言的原始结构,这个过程称为反编译(Decompiler)。
最新的Microsoft Visual Studio.net也是一种解释语言,只是将名称换成“中间语言(Microsoft Intermediate Language)”。如果用W32Dasm反汇编一此类程序,将会看到莫名其妙的代码,因为它不再是传统意义上的汇编代码了,只有用P-code反编译器才能得到正确的代码,以从语言助记符上理解程序功能。如支持VB5/VB6 P-code的反编译器Exdec、WKTVBDE、VBDE,对付最新microsoft .net的反编译程序LDASM(.NET FRAMEWORK SDK中提供)等。
从静态上可以了解各个模块的功能以及整个软件的编程思路,但是,并不可能真正地了解软件中各个模块的技术细节
1.4动态分析介绍
对于被分析的软件来说,静态分析只是工作的第一步,动态跟踪是分析软件的关键所在。所谓动态跟踪主要是指利用Visual C++自带的调试器,或系统级调试器SoftICE或TRW2000等,一步一步跟踪分析。
由于Win32程序内存寻址使用的是相对简单的平坦寻址模式,并且Win32程序大量调用系统提供的API,而Win32平台上的调试器如SoftICE等恰好有针对API设置断点的强大功能,因而这些特点都给动态跟踪破解带来极大的方便。因为程序的作者用的是高级语言,Windows又是提倡“透明 ”,不希望程序员知道底层的操作 , 而只提供给他们高层的接口, 而相当多的高级函数调用某个一定的底层函数,所以解密者经常在底层函数上下断点。所以在Windows中,只要Windows的函数被使用,想对任何寻找蛛丝马迹的人隐藏什么东西是比较困难的。
为什么要对软件进行动态分析呢?这主要是因为:
1、许多软件在整体上完成的功能,一般要分解成若干模块来完成,而且后一模块在执行时,往往需要使用其前一模块处理的结果,这一结果我们把它叫中间结果。如果我们只对软件本身进行静态地分析,一般是很难分析出这些中间结果的。而只有通过跟踪执行前一模块,才能看到这些结果。另外,在程序的运行过程中,往往会在某一地方出现许多分支和转移,不同的分支和转移往往需要不同的条件,而这些条件一般是由运行该分支之前的程序来产生的。如果想知道程序运行到该分支的地方时,到底走向哪一分支,不进行动态地跟踪和分析是不得而知的。
2、有许多软件在运行时,其最初执行的一段程序往往需要对该软件的后面各个模块进行一些初始始化工作,而没有依赖系统的重定位。
3、有许多加密程序为了阻止非法跟踪和阅读,对执行代码的大部分内容进行了加密变换,而只有很短的一段程序是明文。加密程序运行时,采用了逐块解密,逐块执行和方法,首先运行最初的一段明文程序,该程序在运行过程中,不仅要完成阻止跟踪的任务,而且还要负责对下一块密码进行解密。显然仅对该软件的密码部分进行反汇编,不对该软件动态跟踪分析,是根本不可能进行解密的。
由于上述原因,在对软件静态分析不行的条件下,就要进行动态分析了。
1.5逆向工程
逆向工程(Reverse Engineering)定义为:"the process of analyzing a subject system to identify the system's components and their interrelationships and create representations of the system in another form or at a higher level of abstraction." (Source: Chikofsky and Cross)
如果认为:
“源代码 → 编译器 → 可执行程序”的过程是“顺向工程”的话;
“可执行程序 → 反编译器或人工反编译 → 源代码”的过程就是逆向工程。
将逆向工程包括的内容可以分为3类:
1.软件使用限制的去除,或软件功能的添加
(1) 按照计算机类别,可以分为个人微型计算机、小型机、中型机、大型机等;
(2) 按照操作平台或处理器类型,可以分为windows、MAC、UNIX,x86,risc等
(3) 按照限制类型,可以分为软件使用时间限制,软件功能模块限制、软件运行条件限制(软件狗等)、软件注册限制等
(4) 可以是软件功能限制的去除,也可以是软件功能的添加。
2.软件源代码的再获得或二进制代码水平的Debug
(1) 按照计算机类别,可以分为个人微型计算机、小型机、中型机、大型机等;
(2) 按照操作平台,可以分为windows、MAC、UNIX等
(3) 按照软件层次,可以分为普通应用层软件与操作系统源等。
3.硬件的复制、模拟
坦白地讲,现在的逆向工程,真实目的就是为了再利用。据此,个人可以学习别人的编程技术及技巧,公司可以窥探别人的商业软件秘密,或开发与之兼容的软件;(二进制代码层面的)Debug自然也是其中重要的目的之一。
据说,著名的杀毒软件AVP代码写的实在太有条理,因此很容易被分析后“再利用”。有心人不仅可以将其病毒特征库改头换面后再推出,也可以利用逆向工程得到AVP某些模块的源代码,加入自己开发的产品中。
在《看雪论坛精华5》(www.pediy.com)中,收录了一篇文章,讲述如何利用逆向工程知识为一个多媒体播放软件去除“静音”bug的过程。
《Windows图形编程》(Feng Yuan著,机械工业出版社)一书中,作者花费了2个章节,约100多页的文字来介绍如何探索windows系统的GDI/DirectDraw内部数据结构,以及如何自制一个apispy来帮助研究。利用类似的方法,你可以自行探索操作系统的奥秘,而不需要别人的讲解。不过,由此产生的副作用是系统上的很多bug被发现和利用,最有名的就是“堆栈溢出攻击”。
利用逆向工程技术还可以使我们对一些软件进行改造。《程序员杂志》2003.07有文章《编程扩展“记事本”功能》,介绍的就是一例。作者是采用CreateRemoteThread函数远程钩入目标程序进程内,达到其目的。而另一些人可能更喜欢直接修改目标程序的一些二进制代码,从而利用附加的Dll(动态运行库)。后面一种方法叫做SMC(Self Modifying Code)。我看到有人这样为Netscape添加按钮执行“密码保护”功能;有人为filemon添加了“打开最近文档”功能......但最著名的,还是改造W32Dasm――一个著名的反汇编工具活动。通过协作,目前的W32Dasm已经能支持中文字符串,能识别VB程序的引入表,能直接对反汇编程序patch,能添加注释等等,而这一切都是在没有源代码情况下,直接在二进制代码层面完成。
++著名的反编译器提供商datarescue出品的IDA Pro,提供对30多个家族、几十种处理器的支持,它支持windows平台上几乎所有的编译器++
成为一个“逆向工程”大师,应该具有如下特征:
1.永远保存好奇心,崇尚自由。这能促使探索;也能抵抗商业利欲的侵袭;有了它,枯燥的代码世界才有了生气。
2.勤奋与毅力:“让我们搞清楚作为一名Cracker最需要具备的基本条件,其实那并不是扎实的汇编功底和编程基础。你可以完全不懂这些,CRACKING的秘诀就是勤奋+执着!记住并能做到这两点,你一样可以变得优秀。”
3.精通至少一门编程语言,不仅仅是Coding,更重要的是编程思想。RAD工具也许是容易学的,但你要明白隐藏在它背后的机制。
4.扎实的汇编功底和系统编程的知识。
5.能发现“美丽”。是的,你能在枯燥的二进制代码中见到美,那是数学和对称的美丽。卓越的编译器优化能力,简洁而又高效的代码,都能使你领略到她的存在。
逆向工程的终极网站woodmann上列出那些大师的名字:Matt Pietrek,Jeffrey Richter,John Robbins ......他们的专著是每个程序员都应该阅读的:《Windows95系统程式设计打奥秘》、《Windows核心编程》、《应用程序调试技术》......
总之,逆向工程应是一门优雅的艺术,而不是低层次者手中粗陋的工具;逆向工程的目的是学习与再利用;它的精神是“自由”。
2 文本字符
2.1 ASCII字符集
++美国信息交换标准码(ASCII)是一个7位的编码标准,包括26个小写字母、26个大写字母、10个数字、32个符号、33个控制代码及空格,总共128个代码。++
由于计算机通常用“字节”(byte)这个8位的存储单位来进行信息交换,因此不同的计算机厂商对ASCII进行了扩充,增加了128个附加字符,它们的值在127以上的部分是不统一的。例如ANSI、Symbol、OEM等字符集,其中ANSI是系统预设的标准文字存储格式。
Unicode是ASCII字符编码的一个扩展,只不过在Windows中用2个字节对其进行编码,也称为“宽字符集”(Widechars)。Unicode是一种双字节编码机制的字符集,使用0~65535之间的双字节无符号整数对每个字符进行编码。在Unicode中,所有的字符都是16位,包括所有的7位ASCII码都被扩充为16位(注意,高位扩充的是零)。例如,字符串“pediy”的ASCII码是:
70h 65h 64h 69h 79h
其Unicode码的十六进制是:
0070h 0065h 0064h 0069h 0079h
Intel处理器在内存中,一个字存入存储器要占有相继的2个字节,这个字存放时就按Little-Endian方式存入,即低位字节存入低地址,高位字节存入高地址.
2.2 字节存储顺序
多字节数据是按怎样的顺序存放的呢?
实际情况和CPU有关,微处理机中的存放顺序有正序(Big-Endian)和逆序(Little-Endian)之分。
- 常见的Intel体系芯片使用的编码方式属于Little-Endian类;
- 某些RISC架构的CPU,如IBM的Power-PC等属于Big-Endian类。
两种编码区别:
- Big-Endian 高位字节存入低地址,低位字节存入高地址,依次排列;
- Little-Endian 低位字节存入低地址,高位字节存入高地址,反序排列。
3 Windows操作系统
3.1 Win32 API函数
API的英文全称为“Application Programming Interface”(应用程序编程接口)
在Windows程序设计领域发展的初期,Windows程序员能够使用的编程工具唯有API函数。这些函数提供应用程序运行所需要的窗口管理、图形设备接口、内存管理等各项服务功能。这些功能以函数库的形式组织在一起,形成了Windows应用程序编程接口(API),简称“Win API”。Win API子系统负责将API调用转换成Windows操作系统的系统服务调用。所以,可以认为API函数是构筑整个Windows框架的基石,它的下面是Windows的操作系统核心,而它的上面则是Windows应用程序
如下图所示。对应用程序开发人员而言,所看到的Windows操作系统实际上就是Win API,操作系统的其他部分对开发人员来说是完全透明的。
用于16位版本Windows的API(Windows 1.0到Windows 3.1)称作“Win16”,用于32位版本Windows的API(Windows 9x/NT/2000/XP/2003)称作“Win32”。64位版本Windows API的名称和功能基本没有变化,还是Win32的函数名,只不过是用64位代码实现的。API函数调用在从Win16到Win32的转变中保持兼容,并在数量和功能上不断增强——Windows 1.0支持不到450个函数调用,现在已有几千个函数。
所有32位版本的Windows都支持Win16 API(以确保和旧的应用程序兼容)和Win32 API(以运行新的应用程序)。非常有趣的是,Windows NT/2000/XP与Windows 9x的工作方式不同。在Windows NT/2000/XP中,Win16函数调用通过一个转换层被转化为Win32函数调用,然后被操作系统处理。在Windows 9x中,该操作正好相反:Win32函数调用通过转换层转换为Win16位函数调用,再由操作系统处理。
Windows运转的核心是一个称作“动态链接”的概念。Windows提供了应用程序可利用的丰富的函数调用,这些函数采用动态链接库(即DLL)实现。在Windows 9x中,通常位于WINDOWSSYSTEM子目录中。在Windows NT/2000/XP中,通常位于系统安装目录的SYSTEM和SYSTEM32子目录中。
在早期,Windows的主要部分只需要在3个动态链接库中实现,代表了Windows的3个主要子系统,分别叫作Kernel、User和GDI。
- Kernel(由16位的KRNL386.EXE和32位的KERNEL32.DLL实现):操作系统核心功能服务,包括进程与线程控制、内存管理、文件访问等。
- User(由16位的USER.EXE和32位的USER32.DLL实现):负责处理用户接口,包括键盘和鼠标输入、窗口和菜单管理等。
- GDI(由16位的GDI.EXE和32位的GDI32.DLL实现):图形设备接口,允许程序在屏幕和打印机上显示文本和图形。
- 除了上述模块以外,Windows还提供了其他DLL以支持另外一些功能,包括++对象安全性、注册表操作(ADVAPI32.DLL)、通用控件(COMCTL32.DLL)、公共对话框(COMDLG32.DLL)、用户界面外壳(SHELL32.DLL)、图形引擎(DIBENG.DLL)和网络(NETAPI32.DLL)++。
虽然Win API是一个基于C语言的接口,但是Win API中的函数可以由用不同语言编写的程序调用,只要在调用时遵循调用的规范即可。
Unicode影响着计算机工业的每个部分,对操作系统和编程语言的影响最大。NT系统是使用Unicode标准字符集重新开发的,其系统核心完全是用Unicode函数工作的。如果希望调用任何一个Windows函数并给它传递一个ANSI字符串,系统首先要将字符串转换成Unicode,然后将Unicode传递给操作系统。相反,如果希望函数返回ANSI字符,系统就会首先将Unicode字符串转换成ANSI字符串,然后将结果返回给应用程序。也就是说,在NT架构下,Win32 API能接受Unicode和ASCII两种字符集,而其内核则只能使用Unicode。所有这些操作对用户来说都是透明的,但这些字符串的转换需要占用系统资源。
++在Win32 API函数字符集中,“A”表示ANSI,“W”表示Widechars(即Unicode)。前者就是通常使用的单字节方式,后者是宽字节方式,以方便处理双字节字符。++用字符串作参数的每个Win32函数在操作系统中都有这两种方式的版本。例如,编程时使用MessageBox函数,而在USER32.DLL中却没有32位MessageBox函数的入口点。实际上,有两个入口点,一个名为“MessageBoxA”(ANSI版),另一个名为“MessageBoxW”(宽字符版)。幸运的是,程序员通常不必关心这个问题,代码中只需要使用MessageBox,开发工具中的编译模块就会根据设置来决定是采用MessageBoxA还是MessageBoxW。
这个试验结果表明,MessageBoxExA函数其实是一个替换翻译层,用于分配内存,并将ANSI字符串转换成Unicode字符串,系统最终调用Unicode版的MessageBoxExW函数执行。当MessageBoxExW返回时,它便释放内存缓存。在这个过程中,系统必须执行这些额外的转换操作,因此,ANSI版的应用程序需要更多的内存,占用更多的CPU资源。而Unicode版的程序在NT架构下的执行效率就高多了。
由于Win32程序大量调用系统提供的API函数,而Win32平台上的调试器(如OllyDbg等)恰好有针对API函数设置断点的强大功能,因此掌握常用的API函数具体用法会给程序的跟踪调试带来极大的方便,详细的Win32 API参考文档可以从MSDN中获得。建议读者掌握一定的Win32编程知识(如阅读《Windows程序设计》一书),这会对合理选择API函数有很大的帮助。
3.2 WOW64
WOW64(Windows-on-Windows 64-bit)是64位Windows操作系统的子系统,可以使大多数32位应用程序在不作修改的情况下运行在64位版本上。
64位版本的Windows,除了带有64位操作系统应有的系统文件,还带有32位操作系统应有的系统文件。++Windows的64位系统文件都放在一个叫作“System32”的文件夹中,WindowsSystem32文件夹中包含原生的64位映像文件。为了兼容32位操作系统,还增加了WindowsSysWOW64文件夹,其中是32位的系统文件。++
64位应用程序会加载System32目录下64位的kernel32.dll、user32.dll和ntdll.dll。32位应用程序加载时,WOW64建立32位ntdll.dll所要求的启动环境,将CPU模式切换至32位,并开始执行32位加载器,就如同该进程运行在原生的32位系统之上。WOW64会对32位ntdll.dll的调用重定向ntdll.dll(64位),而不是发出原生的32位系统调用指令。WOW64转换到原生的64位模式,并捕获与系统调用有关的参数,发出对应的原生64位系统调用。当原生的系统调用返回时,WOW64将任何输出参数在返回至32位模式之前从64位转换成32位格式。
++WOW64不支持16位应用程序的执行(32位Windows支持16位应用程序的执行),也不支持加载32位内核模式的设备驱动程序。WOW64进程只能加载32位DLL,不能加载原生的64位DLL。类似的,原生的64位进程不能加载32位的DLL。++
3.3 WIndows消息机制
Windows是一个消息(Message)驱动式系统。Windows消息提供应用程序与应用程序之间、应用程序与Windows系统之间进行通信的手段。应用程序想要实现的功能由消息来触发,并且靠对消息的响应和处理来完成。
Windows系统中有两种消息队列:
- 一种是系统消息队列;
- 另一种是应用程序消息队列。
计算机的所有输入设备由Windows监控。当一个事件发生时,Windows先将输入的消息放入系统消息队列中,再将输入的消息复制到相应的应用程序队列中,应用程序中的消息循环从它的消息队列中检索每个消息并发送给相应的窗口函数。一个事件的发生,到达处理它的窗口函数必须经历上述过程。值得注意的是消息的非抢先性,即不论事件的急与缓,总是按到达的先后排队(一些系统消息除外),这就使一些外部实时事件可能得不到及时的处理。
由于Windows本身是由消息驱动的,所以调试程序时跟踪一个消息会得到相当底层的答案。
常用的Windows消息函数:
(1)SendMessage函数
调用一个窗口的窗口函数,将一条消息发给那个窗口。除非消息处理完毕,否则该函数不会返回。
返回值:由具体的消息决定。如消息投递成功,则返回“TRUE”(非零)。
LRESULT SendMessage(
HWND hWnd, // 目的窗口的句柄
UINT Msg, // 消息标识符
WPARAM wParam, // 消息的WPARAM域
LPARAM lParam // 消息的LPARAM域
);
(2)WM_COMMAND消息
当用户从菜单或按钮中选择一条命令或者一个控件时发送给它的父窗口,或者当一个快捷键被释放时发送。Visual C++的WINUSER.H文件里定义了WM_COMMAND消息对应的十六进制数是0111h。
返回值:如果应用程序处理这条消息,则返回值为零。
WM_COMMAND
wNotifyCode = HIWORD(wParam); // 通告代码
wID = LOWORD(wParam); // 菜单条目、控件或快捷键的标识符
hwndCtl = (HWND) lParam; // 控件句柄
(3)WM_DESTROY消息
当一个窗口被销毁时发送。WM_DESTROY消息的十六进制数是02h。
这条消息无参数。
返回值:如果应用程序处理这条消息,则返回值为零。
3.4 虚拟内存
默认情况下,32位Windows操作系统的地址空间在4GB以内。Win32的平坦内存模式使每个进程拥有自己的虚拟空间。对32位进程来说,这个地址空间是4GB,因为32位指针可以拥有00000000h~FFFFFFFFh之间的任何一个值。此时,程序的代码和数据都放在同一地址空间中,即不必区分代码段和数据段。
虚拟内存(Virtual Memory)不是真正的内存,它通过映射(Map)的方法使可用的虚拟地址(Virtual Address)达到4GB,每个应用程序可以被分配2GB的虚拟地址,剩下的2GB留给操作系统自用。在Windows NT中,应用程序甚至可有3GB的虚拟地址。Windows是一个分时的多任务操作系统,CPU时间被分成一个个的时间片后分配给不同的程序。在一个时间片里,与这个程序执行无关的内容不会映射到线性地址中。因此,每个程序都有自己的4GB寻址空间,互不干扰。在物理内存中,操作系统和系统DLL代码需要供每个应用程序调用,所以在所有的时间必须映射。用户的EXE程序只在自己所属的时间片内被映射,而用户DLL则有选择地被映射。
简单地说,虚拟内存的实现方法和过程如下。
① 当一个应用程序被启动时,操作系统就创建一个新进程,并给每个进程分配2GB的虚拟地址(不是内存,只是地址)。
② 虚拟内存管理器将应用程序的代码映射到那个应用程序的虚拟地址中的某个位置,并把当前所需要的代码读取到物理地址中(注意:虚拟地址与应用程序代码在物理内存中的位置是没有关系的)。
③ 如果使用动态链接库DLL,DLL也会被映射到进程的虚拟地址空间中,在需要的时候才被读入物理内存中。
④ 其他项目(例如数据、堆栈等)的空间是从物理内存中分配的,并被映射到虚拟地址空间中。
⑤ 应用程序通过使用它的虚拟地址空间中的地址开始执行,然后虚拟内存管理器把每次的内存访问映射到物理位置。
看不明白上面的步骤也不要紧,但要明白以下几点。
- 应用程序是不会直接访问物理地址的。
- 虚拟内存管理器通过虚拟地址的访问请求来控制所有的物理地址访问。
- 每个应用程序都有相互独立的4GB寻址空间,不同应用程序的地址空间是隔离的。
- DLL程序没有自己的“私有”空间,它们总是被映射到其他应用程序的地址空间中,作为其他应用程序的一部分运行。因为如果它不和其他程序处于同一个地址空间,应用程序就无法调用它。
使用虚拟内存的好处是:简化了内存的管理,并可弥补物理内存的不足;可以防止多任务环境下各个应用程序之间的冲突。
++64位Windows操作系统提供16TB的有效寻址空间,其中有一半可用于用户模式的应用程序。++