encryption & decryption

encryption & decryption - 版权申明

Info iconThis preview shows page 1. Sign up to view the full content.

View Full Document Right Arrow Icon
This is the end of the preview. Sign up to access the rest of the document.

Unformatted text preview: 版权申明 本电子版包含了《加密与解密》 (第二版)的前 2 章,本电子版允许免费传 播。未经许可,任何人不得以任何方式复制或抄袭本电子版之部分或全部内容 用于商业目的。作者对本电子版保留所有权力。 本电子版的发布己征得电子工业出版社的同意,由于出版合同的限制,本 电子版不提供整本书全部内容,欲获取其它章节内容的读者,请购买纸版书籍。 本电子版仅为草稿,未经出版社校稿,与纸版书籍在文字描述、版式上稍 有区别。 电子版制作:段钢 网上预定:华储网 技术支持:看雪学院(http://www.pediy.com) ・2・ 加密与解密 加密与解密(第二版) 段 钢 编著 Publishing House of Electronics Industry 北京・BEIJING 加密与解密 ・3・ 内容简介 软件保护工作是维护软件开发人员利益的必要手段, 是软件开发过程中的重要环节。 本书是看雪论坛 密界一流好手的呕心之作,讲述 Windows 环境下的软件保护技术及相关解决方案。通过解析大量的实例 来展示软件调试的最深处。 加密与解密技术在相辅相成中不断发展,既没有无坚不摧的矛,也不会有坚不可摧的盾。一个不熟悉 敌手的战士不会有太多的胜利机会,不熟悉解密技术的软件开发者,也很难开发出更加先进的保护技术。 在本书中, 我们试图从软件加密和解密两方面对当今流行的软件保护技术进行分析, 希望读者看过本书后, 能够对当今流行的各种软件保护技术及破解技术有所了解。 这本书内容丰富,不论是加解密爱好者还是软件编程者都应该好好看看。自出版以来,得到读者的热 烈欢迎,是致力于加密与解密研究的专业或非专业人员一本难得的好书。 图书在版编目(CIP)数据 加密与解密(第二版)/段钢编著.-北京:电子工业出版社,2003.4 书 名:加密与解密 编 著 者:段 钢 责任编辑:郭 立 毛兆余 特约编辑: 印 刷 者: 装 订 者: 出版发行:电子工业出版社 URL:http://www.phei.com.cn 北京市海淀区万寿路 173 信箱 经 销:各地新华书店 开 本:787×1092 版 次:2003 年 4 月第 1 版 印 数: 邮编:100036 册 1/16 印张: 定 字数: 千字 2003 年 4 月第 1 次印刷 价: 49.00 元(含光盘) 凡购买电子工业出版社的图书,如有缺页、倒页、脱页问题者,请向购买书店调换。 若书店售缺,请与本社发行部联系调换,电话: 010)68279077。 ( ・4・ 加密与解密 前 言 自计算机诞生之日起,其技术的发展可谓日新月异,各种新技术、新思路不断涌现。 个人计算机操作系统也经历了 DOS, Windows 3.x, Windows 9x, Windows 2000 及 Windows XP 的历程。各种应用软件从最初的几个、几十个字节发展到现在的动辄几张光盘,成千 上万的共享软件和商业软件越来越庞大,技术内涵也日趋复杂。一款优秀的软件,其技术 秘密往往成为他人窃取的重点。作为软件开发人员,为了保护自己辛辛苦苦开发的软件不 轻易被他人“借鉴” ,有必要对软件保护(加密)和破解(解密)技术进行研究。但是,软 件保护和破解方面的资料目前比较匮乏,许多软件开发人员不得不自行摸索,导致在重复 劳动中走了不少弯路,耗费了大量的时间和精力。随着软件以共享方式在网络上发布这种 方式的流行,软件保护和数据加密技术的迫切性被越来越突出地表现了出来。 软件加解密的发展历史 我们通常是随着操作系统的不断升级来划分相应的软件,所以划分软件加解密的发展 历史就是操作系统发展的历史。 1.DOS 时期 这个时代的软件主要是正式版和功能不全的 Demo 版以及一些磁盘防拷贝保护,很少 有所谓的共享软件(Shareware) 。所以 DOS 时代所谓的解密通常是去掉软件中的某些限制 或跳过原版磁盘检查,然后通过广大业余的 BBS 提供下载。但值得一提的是,这个时代的 软件由于 16 位操作系统很容易接触到系统底层的原因,而导致个别软件保护方式异常强 悍, 又因为在 16 位平台上很难区分系统领空和程序领空而导致天然的破解跟踪困难, 所以 这个时代的软件保护机制两极分化很严重。 2.Windows 95 早期 当 Windows 95 出现的时候,很多人不适应这个平台,它上面的加解密资料奇缺,对 许多人来说就像做了一场恶梦。这段时期共享软件渐渐地盛行起来,采用序列号保护的共 享软件越来越多。 由于当时许多程序员对刚刚出现的 Windows 95 不了解, 感觉有些手足无 措,编制的软件在加密部分都比较脆弱,所以那时候的序列号加密方案特别脆弱。 3.Windows 95 末期 其实这段时期应该是 Windows 95 和 Windows 98 共存的时期。在这个时期,程序员已 经对 Windows 9x 这个系统了如指掌,一些需要较高编程技巧、与系统核心等底层联系紧 密的软件纷纷出笼。这个时期,共享软件大多还是采用序列号加密方式,但其序列号通常 经过复杂的计算,所以很难像早期的软件一样,随随便便就能被解密。 4.Windows 2000/XP 时期 这段时期就是 Windows 9x 和 Windows 2000/XP 共存的时期。这时,各种软件的加密 加密与解密 ・5・ 外壳泛滥,特别是各种专用加壳软件的出现,大大地提高了软件的保护质量;同时,解密 技术也不断提高,各种新式的解密工具随即出现。此时的序列号加密越来越多地采用密码 学中不可逆的加密算法,使得解密的过程越来越像高等数学的研究。软件解密者要想得到 正确的序列号就必须对各种成熟加密算法有很深的了解, 或者找出软件加密算法的漏洞 (像 WinRAR。CloneCD 等软件的 Keygen 就是利用 ECC 椭圆算法的加密漏洞而编制出来的) 。 关于本书 2000 年初,作者想找一些研究加解密的朋友交流一下,但十分令人遗憾的是,那时国 内这方面的技术资料很缺乏,不成系统,大家交流的也有限,因此就建了一个主页“看雪 学院” 与大家共同探讨加密与解密的知识。 , 这个主页是当时国内惟一一个从技术角度研究 加解密的站点,并在广大网友的支持下,健康地成长了起来了。后来,主页提供的软件调 试论坛成了国内知名的加解密技术论坛,吸引了密界众多高手。大家以知识共享的精神, 无私地将自己所知的技术奉献出来了, 至今为止原创了 2500 余篇文章, 极大地推动了国内 加解密技术的发展。 这是一本很难写的书,因为当时这是一个全新的领域。从 Windows 95 面世以来的 6 年内,市面上没有一本这方面的书,网上也很缺乏相关资料。为了填补国内 Windows 平台 上加密与解密书籍的空白, 作者与软件调试论坛的密界一流好手努力合作, 克服种种困难, 于 2001 年 9 月推出了国内第一本全面介绍 Windows 平台下软件加密与解密技术的书籍, 这就是本书的第一版《加密与解密——软件保护技术及完全解决方案》 。 在第一版中,我们试图从软件加密和解密这两个方面对当今流行的软件保护技术进行 了分析。希望读者看过本书之后,能够对各种流行的软件保护与破解技术有所了解。 第一版一面世就得到了广大读者的喜爱和认可, 获得了 2002 年全国优秀畅销书奖 (科 技类) 在全国很多计算机专业书店获得了名利前茅的销售业绩, ! 而且一年来在著名的华储 网销售排行中都被排在前几名内。次年,本书在台湾发行了繁体版,得到了台湾读者的热 烈欢迎。 为了跟上技术发展的步伐,作者花费了 6 个月时间做准备,6 个月时间进行写作,汇 集了国内顶尖软件调试论坛(看雪论坛)的众多密界一流好手,以本书第一版为基础,更 新了第一版的大部分内容, 最后完成了本书的第二版 《加密与解密》 这本 500 多页的图书, 。 几乎囊括了 Windows 下软件保护的绝大多数内容,从基本的跟踪调试到深层的拆解脱壳、 从浅显的分析注册到商用的软件保护,其跨度之广、内容之深,国内至今尚无同类出版物 能与之比肩。 内容导读 第二版是在一版基础上写成的,删除了第一版中的过时内容,补充了许多新技术。全 书有一半的内容与第一版不同,结构更加合理。补充和加强了 Windows 与 Unicode、代码 逆向分析、IDA 详细操作、SoftICE 符号调试技术、OllDbg 操作、密码学算法应用、VB 的 Pcode 跟踪、增加 PE 文件的功能、SEH 技术、脱壳技术等。 什么是 API?什么是 Unicode?什么是逆向分析?Winodws 9x 与 Windows 2000/XP 上 ・6・ 加密与解密 的加解密究竟有什么不同?只有了解这些基础知识,在加密与解密过程中才能有的放矢地 处理各种问题。本书的基础篇(第 1 章“基础知识”和第 2 章“代码分析技术” 将系统 ) 地解答这些问题。 在进行软件解密的过程中,一个首要问题是对被解密的软件进行分析,这部分就是静 态分析与动态分析技术。本书以极大的篇幅讲述了这两种分析技巧,包括逆向工程必备工 具 IDA 的详细操作,最新 SoftICE 和 OllyDbg 的操作等。这些内容可以在第 3 章“静态分 析技术”与第 4 章“动态分析技术”中找到。 一些软件作者对软件保护方案的策划与实施很不以为然,他们往往自以为保护在解密 者眼中不堪一击。希望本部分能让这些软件作者了解一些软件攻击的方法,以便更好地保 护自己的作品。在这个年代,研究加解密不掌握点密码学的知识是不可思意的。第二版详 细讲解了 MD5, SHA, CRC, RSA, ElGamal 等算法在软件保护方面的应用,并且光盘上提供 了实例的源码!这些内容可以在第 5 章“软件保护及其弱点”与第 6 章“加密算法”中找 到。 现在所使用的语言无非是两种:一种是解释执行的语言,另一种就是编译后才能够执 行的语言。解释语言的最大弱点之一就是能被反编译,因此其保护的重点应放在如何防止 反编译上。这些内容可以在第 7 章“反编译语言”中找到。 PE 是 Windows 上可执行文件的格式,熟知 PE 文件将有助于对操作系统的深刻理解。 如果你知道 EXE 和 DLL 里面的奥秘,就成为一名知识更加渊博的程序员。本书用大量篇 幅,图文并貌地详细讲解了 PE 格式(第 8 章“PE 文件格式”。 ) 在掌握 PE 格式后,就可随心所意地对 PE 文件做“手术” ,进行二次开发,如增加菜 单、按钮等功能。这部分将带你走进另一个计算机的世界里去(第 9 章“增加 PE 文件功 能”。 ) SEH 的出现已非一日,但有关 SEH 的知识资料却不是很多。SEH 不仅可以简化程序 的错误处理,使程序更加健壮,还被广泛应用于反跟踪和加密中。本书从解密角度讲述了 SEH 的机理,同时讲述了其他各种反跟踪技术,如 Anti-Debug、花指令等。软件作者可以 将这些技术应用到自己的软件中去,以加强软件的反跟踪能力(第 10 章“反跟踪技术”。 ) 现在,越来越多的软件都采用了加壳保护。当在软件分析和汉化过程中,脱壳是必不 可少的一步。第 11 章“加壳与脱壳”详细介绍了各种壳的脱壳技巧,读者可以在自己的软 件中运用这些壳中的先进反跟踪技术。 第 12 章“补丁技术”介绍了文件补丁和内存补丁技术,同时重点讲解了 SMC 技术在 补丁方面的应用。学习补丁一件很有意思的事情。 商用软件保护技术实际就是对商业软件加密的技术,真正有价值的商业软件一般都会 采用这些技术来保护。第 13 章“商用软件保护技术”讲解了常见的商业保护技术,如软件 狗,Vbox,SalesAgent,Flexlm 等保护,而且对这些保护的优缺点进行了分析。 对读者的要求 本书适合以下读者。 对软件加密与解密感兴趣的读者 加密与解密 ・7・ 对软件保护感兴趣的软件开发人员 对逆向工程感兴趣的读者 对调试技术感兴趣的读者 使用本书需要具备以下知识。 汇编基础知识。此类书籍市面上很多,如《IBM PC 汇编语言程序设计》等。 应了解 C 语言。了解 C 语言的某些知识是有帮助的,但不是必须了解。 Win32 编程。不管研究加密与解密,还是编程,都必须了解 Win32 编程。Win32 编 程就是 API 方式的 Windows 程序设计,学习 Windows API 能使您更深入地了解 Windows 工作方式。此类书籍有 Charles Petzold 所著的《Windows 程序设计》 ,该书 堪称经典之作,它以 C 语言为讲解平台。另一本书是罗云彬所编著的《Windows 环 境下 32 位汇编语言程序设计》 ,它以 Win32 汇编为讲解平台。 到此为止,作者将不再假设您已经具有任何加解密的经验了。 致谢 感谢我的母校同济大学,她的“同舟共济、自强不息”的同济精神一直指导着我的工 作和学习! 感谢电子工业出版社计算机事业部对本书的大力支持! 同时也要感谢那些共同参与第一版组稿的软件调试论坛的众多密界一流好手,是他们 的参与才让此书得以完成。这次的第二版改动较大,参考引用了如下朋友在第一版中参与 的文章: 1. Blowfish http://www.shieldsoftware.com/) ( 参与的 “软件保护技术” Anti-Debug” JAVA 、 “ 、 “ 程序反编译” ; 2.DREAMtHEATE 参与的“Windows 消息机制” ; 3.DDXia[CCG]参与的“远程调试技术”“补丁技术” , ; 4.Passion 参与的“FileMon 的使用”“TimeLOCK 保护” 、 ; 5.Ljtt 参与的“花指令” ; 6.Arbiter 参与的“FrogsICE 使用简介”“CRC 原理篇” 、 ; 7.Ajj(http://ajj.126.com/)参与的“IceDump 和 NticeDump 的使用” ; 8.Fisheep(fisheep@sohu.com)参与的“VBOX4.3”“SalesAgent 保护技术”“FlexGen 、 、 工具用法”“利用 FlexLm SDK 解密”“浮点指令小结” 、 、 ; 9.吴朝相(http://www.souxin.com/)参与的“常用断点设置技巧”及“认识壳” ; 10.mr.wei 参与的“DeDe 用法” ; 11. (http://www.zoudan.com) 邹丹 的论文 “关于 Windows 95 下的可执行文件的加密研究” ; 12.TiANWEi(http://winice.yeah.net)参与的“SoftICE 指令手册” 。 在第二版的编写过程中特别感谢: 1.Hume(http://humeasm.yeah.net/)提供的“指令优化一文” ; 2. 老罗的缤纷天地 http://www.luocong.com/) ( 提供的 CRC32 实践篇” “奇妙的 Base64 “ 与 编码”两篇文章及实例; ・8・ 加密与解密 3.夜月提供“Blowfish 算法解密”一文; 4.娃娃(王凌迪)参与的“MD5 算法”资料; 5.Blowfish 的“ReVirgin 使用指南”“挫败隐藏在 SEH handler 中的保护” , 。 同时也要感谢 Sun Bird,Hying,Spring,pll621,Ajj,小楼,Ljtt,Hume,Arbiter, Aming,Cooljiang,洋白菜,WinDos2K,小牧童等软件调试论坛的众多朋友的支持和帮助! 论坛网友的一言一行都已融进了本书的文字里,实在无法一一列举。特别感谢 CCG 团体 给与的技术支持! 关于配套光盘 本书所有实例及源码均在配套光盘里提供, 大部分实例是使用 Microsoft Visual C++ 6.0 开发和测试的。 由于版权问题,配套光盘仅提供书中提到的免费软件或共享软件。如果从学习角度需 要使用那些有版权的软件,建议用搜索引擎查找如 www.google.com) 。 光盘提供的软件经过多方面检查测试,绝无病毒。但一些加解密工具采用了某些病毒 技术,因此部分代码与某些病毒的特征码类似,会造成查毒软件的误报。 请勿将光盘的文件做成虚拟光驱,并跟踪调试虚拟光驱上的实例,以免出现一些无法 解释的错误。建议将文件拷贝到硬盘,并去除只读属性再调试。 反馈信息 我们非常希望能够了解读者对本书的看法。 如果有什么问题或有自己的调试实战故事, 欢迎发到作者主页的论坛里,我乐意回答朋友们提出的任何合理的问题,因为当我努力回 答这些问题时,也会从中获益匪浅。 作者主页 http://www.pediy.com 段钢 2003 年 3 月 加密与解密 目 ・9・ 录 第 1 章 基础知识··········································································································································· (1) 1.1 文本编码方式·································································································································· (1) 1.2 Windows API 函数 ······················································································································· (2) 1.2.1 Win API 简介 ·························································································································· (2) 1.2.2 什么是句柄 ······························································································································ (4) 1.2.3 常用 WIN32 API 函数 ············································································································ (4) 1.3 Windows 与 Unicode···················································································································· (7) 1.3.1 1.3.2 1.4 1.5 Windows 9x 与 Unicode········································································································· (7) Windows 2000/XP 与 Unicode ······························································································ (9) Windows 消息机制 ······················································································································· (9) Windows 注册表 ·························································································································· (12) 1.5.1 注册表的逻辑结构 ················································································································ (12) 1.5.2 注册表相关函 ···················································································································· (13) 1.5.3 注册表分析软件 ···················································································································· (15) 1.6 保护模式简介································································································································ (17) 1.6.1 虚拟内存 ································································································································ (18) 1.6.2 保护模式的权限级别 ············································································································ (19) 第 2 章 代码分析技术 ······························································································································· (21) 2.1 认识 PE 格式································································································································· (21) 2.1.1 PE 格式 ·································································································································· (21) 2.1.2 文件偏移地址与虚拟地址转换···························································································· (23) 2.2 代码指令 ········································································································································· (25) 2.2.1 转移指令机器码的计算 ········································································································ (25) 2.2.2 条件设置指令 ························································································································ (28) 2.2.3 指令修改技巧 ························································································································ (29) 2.2.4 浮点指令 ································································································································ (30) 2.3 逆向分析技术······························································································································· (33) 2.3.1 函数 ········································································································································ (33) 2.3.2 循环 ········································································································································ (37) 2.3.3 控制语句 ································································································································ (38) 2.3.4 全局变量 ································································································································ (38) 2.3.5 字串初始化 ···························································································································· (39) 第 3 章 静态分析技术 ······························································································································· (40) 3.1 文件类型分析································································································································ (40) 3.1.1 FileInfo 工具·························································································································· (40) 3.1.2 PEiD 工具 ······························································································································ (41) ・10・ 3.2 加密与解密 资源··················································································································································· (42) 3.2.1 资源黑客的使用 ···················································································································· (42) 3.2.2 3.3 eXeScope 的使用··················································································································· (44) W32Dasm 使用介绍 ··················································································································· (44) 3.3.1 准备工作 ································································································································ (44) 3.3.2 操作步骤 ······························································································································· (46) 3.3.3 代码清单的阅读 ···················································································································· (52) 3.4 IDA Pro 使用简介 ······················································································································· (57) 3.4.1 IDA 文件 ································································································································ (58) 3.4.2 IDA 配置文件 ························································································································ (58) 3.4.3 IDA 菜单选项配置················································································································ (60) 3.4.4 打开文件 ································································································································ (62) 3.4.5 IDA 主窗口界面···················································································································· (63) 3.4.6 注释 ········································································································································ (64) 3.4.7 交叉参考 ································································································································ (64) 3.4.8 查找字符串 ···························································································································· (65) 3.4.9 参考重命名 ···························································································································· (65) 3.4.10 标签的用法 ·························································································································· (66) 3.4.11 进制的转换 ·························································································································· (67) 3.4.12 3.4.13 数组(Arrays) ·················································································································· (68) 3.4.14 结构体(Structures) ········································································································ (68) 3.4.15 枚举类型(Enumerated Types) ····················································································· (70) 3.4.16 堆栈变量 ······························································································································ (71) 3.4.17 IDC 脚本 ······························································································································ (72) 3.4.18 FLIRT ·································································································································· (77) 3.4.19 插件 ······································································································································ (78) 3.4.20 输出 ······································································································································ (79) 3.4.21 3.5 手工识别代码和数据 ·········································································································· (67) 小结 ······································································································································ (80) 文件编辑工具································································································································ (80) 3.5.1 Hiew 使用 ······························································································································ (80) 3.5.2 3.5.3 3.6 HexWorkshop 使用 ·············································································································· (85) WinHex 使用 ························································································································· (87) 静态分析技术应用实例············································································································· (88) 3.6.1 解密初步 ································································································································ (88) 3.6.2 逆向工程初步 ························································································································ (89) 第 4 章 动态分析技术 ······························································································································· (92) 4.1 SoftICE 调试器 ···························································································································· (92) 4.1.1 安装 ········································································································································ (92) 加密与解密 ・11・ 4.1.2 调试窗口简介 ························································································································ (99) 4.1.3 窗口操作 ······························································································································ (102) 4.1.4 SoftICE 配置 ······················································································································· (105) 4.1.5 SoftICE 常用命令简介 ······································································································· (111) 4.1.6 SoftICE 调试技术 ··············································································································· (115) 4.1.7 SoftICE 的符号调试技术 ··································································································· (123) 4.1.8 断点 ······································································································································ (132) 4.1.9 4.1.10 4.2 SoftICE 远程调试 ··············································································································· (139) IceDump 和 NticeDump 的使用 ····················································································· (144) TRW2000 调试器 ······················································································································ (150) 4.2.1 安装 ······································································································································ (150) 4.2.2 配置 ······································································································································ (150) 4.2.3 输出信息(Export)的装载······························································································ (151) 4.2.4 TRW2000 操作···················································································································· (151) 4.2.5 条件断点 ······························································································································ (153) 4.2.6 符号调试 ······························································································································ (154) 4.3 OllyDbg 调试器·························································································································· (154) 4.3.1 OllyDbg 界面······················································································································· (154) 4.3.2 基本操作 ······························································································································ (156) 4.3.3 实例 ······································································································································ (157) 4.4 常见问题小结······························································································································ (158) 第 5 章 软件保护技术及其弱点 ·········································································································· (160) 5.1 序列号保护方式 ························································································································· (160) 5.1.1 序列号保护机制 ·················································································································· (160) 5.1.2 如何攻击序列号保护 ·········································································································· (162) 5.1.3 字符处理代码分析 ············································································································· (164) 5.1.4 注册机制作 ·························································································································· (167) 5.1.5 浮点数 ·································································································································· (172) 5.2 5.3 警告(Nag)窗口 ····················································································································· (175) 时间限制 ······································································································································· (177) 5.3.1 计时器 ·································································································································· (177) 5.3.2 时间限制 ······························································································································ (179) 5.3.3 拆解时间限制保护 ·············································································································· (180) 5.4 菜单功能限制······························································································································ (182) 5.4.1 相关函数 ······························································································································ (182) 5.4.2 拆解菜单限制保护 ·············································································································· (183) 5.5 Key File 保护 ······························································································································ (183) 5.5.1 相关 API 函数 ····················································································································· (183) 5.5.2 拆解 Key File 的一般思路 ································································································· (185) ・12・ 加密与解密 5.5.3 文件监视工具 FileMon ······································································································ (185) 5.5.4 拆解 KeyFile 保护·············································································································· (187) 5.6 CD-Check ····································································································································· (192) 5.6.1 相关函数 ······························································································································ (192) 5.6.3 拆解光盘保护 ······················································································································ (194) 5.7 只运行一个实例 ························································································································· (194) 4.7.1 实现方案 ······························································································································ (195) 5.7.2 实例 ······································································································································ (196) 5.8 常用断点设置技巧 ···················································································································· (196) 5.9 关于软件保护的一般性建议 ································································································· (197) 第 6 章 加密算法······································································································································· (199) 6.1 单向散列算法······························································································································ (199) 6.1.1 6.1.2 SHA 算法 ····························································································································· (201) 6.1.3 6.2 MD5 算法 ···························································································································· (199) CRC 算法 ···························································································································· (203) 公开密钥算法······························································································································ (206) 6.2.1 6.2.2 ElGamal 算法 ······················································································································ (212) 6.2.3 6.3 RSA 算法 ····························································································································· (206) DSA 算法 ····························································································································· (217) 对称算法 ······································································································································· (218) 6.3.1 6.4 Blowfish 算法 ······················································································································ (218) 其他算法 ······································································································································· (221) 6.4.1 Base64 编码 ························································································································· (221) 6.4.2 Crypto API ·························································································································· (222) 6.5 小结················································································································································· (223) 第 7 章 反编译语言 ·································································································································· (225) 7.1 Visual Basic 程序 ······················································································································· (225) 7.1.1 Visual Basic 字符编码方式 ································································································ (225) 7.1.2 VB3 和 VB4 反编译 ············································································································ (226) 7.1.3 动态分析 VB3 和 VB4 程序······························································································· (226) 7.1.4 动态分析 VB5 和 VB6 程序······························································································· (229) 7.1.5 SmartCheck 调试工具 ······································································································· (236) 7.1.5 伪编译(P-code) ·············································································································· (241) 7.2 Delphi/ C++ Builder 程序 ······································································································ (250) 7.2.1 认识 Delphi·························································································································· (250) 7.2.2 DeDe 反编译器 ···················································································································· (250) 7.2.3 断点 ······································································································································ (256) 7.3 Java 程序 ······································································································································ (257) 7.3.1 JVM 指令系统 ···················································································································· (258) 加密与解密 ・13・ 7.3.2 7.3.3 JVM 堆栈结构 ···················································································································· (258) 7.3.4 JVM 碎片回收堆 ················································································································ (258) 7.3.4 7.4 JVM 寄存器 ························································································································ (258) JVM 存储区 ························································································································ (259) InstallShield 反编译 ················································································································· (262) 7.4.1 安装文件构成 ······················································································································ (262) 7.4.2 脚本语言反编译 ·················································································································· (262) 7.4.3 IS 解密 ································································································································ (263) 第 8 章 PE 文件格式 ······························································································································· (265) 8.1 PE 文件结构 ································································································································ (265) 8.1.1 PE 的基本概念 ···················································································································· (265) 8.1.2 DOS 插桩程序 ····················································································································· (267) 8.1.3 PE 文件头(IMAGE_NT _HEADERS) ······································································· (268) 8.1.4 块表(The Section Table) ······························································································· (274) 8.1.5 各种块(Sections)的描述 ································································································ (276) 8.1.6 输入表(Import Table) ··································································································· (277) 8.1.7 绑定输入(Bound import)······························································································ (284) 8.1.8 输出表(Export Table) ··································································································· (285) 8.1.8 基址重定位表(Base ReloCation Table) ······································································· (287) 8.1.9 资源 ······································································································································ (290) 8.1.10 PE 格式小结 ······················································································································ (292) 8.2 PE 编辑工具 ································································································································ (292) 8.2.1 LordPE 使用简介 ··············································································································· (292) 8.2.2 PEditor 使用简介 ·············································································································· (295) 第 9 章 增加 PE 文件功能 ···················································································································· (296) 9.1 数据对齐 ······································································································································· (296) 9.2 增加区块(Section) ··············································································································· (296) 9.2.1 手工构造区块 ······················································································································ (297) 9.2.2 工具辅助构造区块 ·············································································································· (298) 9.3 增加输入函数······························································································································ (298) 9.3.1 手工增加 ······························································································································ (298) 9.3.2 工具辅助 ······························································································································ (299) 9.4 9.5 9.6 增加 DLL 文件 ··························································································································· (300) 窗口函数 ······································································································································· (301) 增加菜单功能······························································································································ (304) 9.6.1 扩充 WndProc····················································································································· (305) 9.6.2 Exit 菜单 ······························································································································ (305) 9.6.2 Open 菜单 ···························································································································· (306) 9.6.3 Save 菜单 ····························································································································· (309) ・14・ 9.7 加密与解密 用 DLL 增加功能 ······················································································································ (313) 9.7.1 创建 DLL 文件 ···················································································································· (313) 9.7.2 调用 DLL 函数 ···················································································································· (313) 9.8 修复基址重定位表 ···················································································································· (314) 9.9 增加输出函数······························································································································ (317) 第 10 章 反跟踪技术 ······························································································································· (319) 10.1 结构化异常处理 ······················································································································ (319) 10.1.1 异常列表 ···························································································································· (319) 10.1.2 异常信息 ···························································································································· (322) 10.1.4 系统异常调试程序 ············································································································ (325) 10.1.5 10.2 10.3 10.4 SEH 异常处理 ··················································································································· (320) 10.1.3 异常处理回调函数 ············································································································ (327) 反调试技术 ································································································································ (330) 花指令 ········································································································································· (339) 反-反调试技术·························································································································· (344) 10.4.1 10.4.2 FrogsICE ··························································································································· (344) 10.4.3 10.5 SuperBPM ························································································································· (344) 在 Windows 2000/XP 下隐藏 SoftICE ··········································································· (348) 反跟踪实例 ································································································································ (349) 10.5.1 Anti-SoftICE ····················································································································· (349) 10.5.2 Anti-Spy····························································································································· (350) 10.5.3 Anti-DeDe.························································································································· (351) 第 11 章 加壳与脱壳································································································································ (353) 11.1 认识壳 ·········································································································································· (353) 11.1.1 壳的概念 ···························································································································· (353) 11.1.2 壳的加载过程 ···················································································································· (354) 11.2 加壳工具 ····································································································································· (355) 11.2.1 ASPacK ······························································································································ (356) 11.2.2 UPX ··································································································································· (357) 11.2.3 PECompact························································································································ (357) 11.2.4 ASProtect··························································································································· (358) 11.2.5 tElock ································································································································· (359) 11.2.6 幻影 ···································································································································· (359) 11.3 专用脱壳软件 ··························································································································· (359) 11.3.1 ASPack······························································································································· (360) 11.3.2 UPX ···································································································································· (360) 11.3.3 ASProtect··························································································································· (361) 11.4 通用脱壳软件 ··························································································································· (361) 11.4.1 ProcDump 使用简介 ········································································································ (362) 加密与解密 ・15・ 11.4.2 File Scanner 使用简介······································································································ (368) 11.5 手动脱壳 ····································································································································· (369) 11.5.1 查找入口点 ························································································································ (369) 11.5.2 抓取内存映像文件 ············································································································ (372) 11.5.3 重建输入表 ······················································································································· (375) 11.5.4 ImportREC 使用指南 ······································································································ (379) 11.5.5 Revirgin 使用指南 ············································································································ (385) 11.5.6 重建可编辑资源 ················································································································ (389) 11.6 压缩保护的壳 ··························································································································· (389) 11.6.1 11.6.2 PECompact 的壳··············································································································· (394) 11.6.3 PE-PaCK 的壳 ·················································································································· (397) 11.6.4 11.7 ASPack 的壳 ······················································································································ (389) Petite 的壳·························································································································· (401) 加密保护的壳 ··························································································································· (403) 11.7.1 11.7.2 11.8 ASProtect 加密保护·········································································································· (403) tElock 加密保护 ················································································································ (416) DLL 文件···································································································································· (423) 11.8.1 ASPack 的壳 ······················································································································ (424) 11.8.2 PECompact 的壳··············································································································· (427) 11.8.3 UPX 的壳 ··························································································································· (431) 11.8.4 ASProtect 的壳 ·················································································································· (434) 11.8.5 tElock 的壳 ························································································································ (435) 11.9 脱壳小结 ····································································································································· (437) 第 12 章 补丁技术 ···································································································································· (439) 12.1 补丁原理 ····································································································································· (439) 12.1.1 12.1.2 12.2 文件补丁 ···························································································································· (439) 内存补丁 ···························································································································· (441) 补丁工具 ····································································································································· (443) 12.2.1 12.3 文件补丁工具 ···················································································································· (443) 12.2.2 内存补丁工具 ···················································································································· (445) SMC 补丁技术 ························································································································· (445) 12.3.1 单层 SMC 技术 ················································································································· (446) 12.3.2 多层 SMC 技术 ················································································································· (447) 12.3.3 SMC 函数 ·························································································································· (449) 第 13 章 商用软件保护技术 ················································································································· (451) 13.1 软件狗(Dongles) ················································································································ (451) 13.1.1 软件狗介绍 ························································································································ (451) 13.1.2 软件狗厂商 ························································································································ (452) 13.1.3 软件狗的弱点 ···················································································································· (453) ・16・ 13.2 加密与解密 Vbox 保护技术 ························································································································· (454) 13.2.1 13.2.2 Vbox 4.2 版本 ···················································································································· (456) 13.2.3 13.3 Vbox 4.03 版本 ·················································································································· (454) Vbox 4.3 版本 ···················································································································· (458) SalesAgent 保护技术 ············································································································· (459) 13.3.1 13.3.2 13.4 13.5 13.6 从“现在购买(BUY NOW) ”入手 ·············································································· (459) 暴力去除 SalesAgent 的保护 ··························································································· (461) SoftSENTRY 保护技术 ········································································································ (462) TimeLOCK 保护技术 ··········································································································· (464) Flexlm 保护 ······························································································································· (466) 13.7.1 License 文件格式 ·············································································································· (466) 13.7.2 设置环境变量 ···················································································································· (468) 13.7.3 Flexlm Server ···················································································································· (469) 13.7.4 FlexGen 工具用法 ············································································································ (470) 13.7.5 利用 FlexLm SDK 解密 ··································································································· (472) 附录 A 浮点指令 ······································································································································· (477) 附录 B SoftICE 操作手册 ····················································································································· (480) 附录 C TRW2000 手册 ·························································································································· (517) 参考文献 ························································································································································· (520) ・17・ 加密与解密 第 1章 基础知识 研究软件加密与解密,必须要了解一些 Windows 系统的基础知识。比如不同版本的 Windows 对 ASCII 码与 Unicode 码支持情况,什么是 API 函数,注册表结构等知识。掌 握这些后,在加密与解密过程中才能有的放失地处理各种问题。 1.1 文本编码方式 美国信息交换标准码(ASCII:American Standard Code for Information Interchange) 起始于 50 年代后期,并最终在 1967 年定案。现代的 ASCII 是一个七位的编码标准,包 括 26 个小写字母、26 个大写字母、10 个数字、32 个符号、33 个控制代码和一个空格, 总共 128 个代码。由于计算机通常用“字节(byte)”这个八位的存储单位来进行信息交换, 因此不同的计算机厂家对 ASCII 进行了扩充,增加了 128 个附加的字符来补充 ASCII, 它们的值在 127 以上的部分是不统一的。 例如 ANSI、 Symbol、 OEM 等字符集, 其中 ANSI 是系统预设的标准文字存储格式。表 1-1 列出了用十六进制(Hex)与十进制数(Dec)表 示的部分常用字符的 ASCII 值。 Unicode 是 ASCII 字符编码的一个扩展。Unicode 据称是使用“宽字符集” ,所以本书 把宽字符(Widechars)和 Unicode 作为同义语。为了将成千上万的文字统一到同一个编 码机制之下,不管是东方文字还是西方文字,在 Unicode 中一律用两个字节来表示。也就 是说,Unicode 是一种双字节编码机制的字符集,使用 0~65 535 之间的双字节无符号整 数对每个字符进行编码。Unicode 中,所有的字符都是 16 位,包括所有的 7 位 ASCII 码 都被扩充为 16 位 (注意,高位扩充的是零\x0)。如英文字串“pediy” ,它的 ASCII 码是: 0x70 0x65 0x64 0x69 0x79 其 Unicode 码的十六进制是: 0x0070 0x0065 0x0064 0x0069 0x0079 Intel 处理器在内存中将以如图 1.1 形式存放。存放时,低位字节存入低地址,高位字 节存入高地址,也就是说是以相反的次序存入的。 pediy ASCII 码 70 65 64 69 79 p Unicode 码 e d i y 70 00 65 00 64 00 69 00 79 00 图 1.1 内存中的 ASCII 码与 Unicode 码 ・18・ 加密与解密 表 1-1 常用字符的 ASCII 值 Hex Dec 字符 Hex Dec 字符 Hex Dec 字符 Hex Dec 字符 00H 0D NUL 34H 52D 4 4DH 77D M 66H 102D f 07H 7D BEL 35H 53D 5 4EH 78D N 67H 103D g 0AH 10D LF 36H 54D 6 4FH 79D O 68H 104D h 0CH 12D FF 37H 55D 7 50H 80D P 69H 105D i 0DH 13D CR 38H 56D 8 51H 81D Q 6AH 106D j 20H 32D SP 39H 57D 9 52H 82D R 6BH 107D k 21H 33D ! 3AH 58D : 53H 83D S 6CH 108D l 22H 34D " 3BH 59D ; 54H 84D T 6DH 109D m 23H 35D # 3CH 60D < 55H 85D U 6EH 110D n 24H 36D $ 3DH 61D = 56H 86D V 6FH 111D o 25H 37D % 3EH 62D > 57H 87D W 70H 112D p 26H 38D & 3FH 63D ? 58H 88D X 71H 113D q 27H 39D ' 40H 64D @ 59H 89D Y 72H 114D r 28H 40D ( 41H 65D A 5AH 90D Z 73H 115D s 29H 41D ) 42H 66D B 5BH 91D [ 74H 116D t 2AH 42D * 43H 67D C 5CH 92D \ 75H 117D u 2BH 43D + 44H 68D D 5DH 93D 76H 118D v 2CH 44D , 45H 69D E 5EH 94D ↑ 77H 119D w 2DH 45D - 46H 70D F 5FH 95D ← 78H 120D x 2EH 46D . 47H 71D G 60H 96D ` 79H 121D y 2FH 47D / 48H 72D H 61H 97D a 7AH 122D z 30H 48D 0 49H 73D I 62H 98D b 7BH 123D { 31H 49D 1 4AH 74D J 63H 99D c 7CH 124D | 32H 50D 2 4BH 75D K 64H 100D d 7DH 125D } 33H 51D 3 4CH 76D L 65H 101D e 7EH 126D ~ 1.2 Windows API 函数 现在很多讲程序设计的书都是基于 MFC 库和 OWL 库的 Windows 设计,对 Windows 实现的细节鲜有讨论,而调试程序是和系统底层打交道, 所以很有必要掌握一些 API 函数的 知识。 1.2.1 Win API 简介 对于一个初学者来说,API 函数也许是一个时常耳闻却感觉有些神秘的东西。API 的 英文全称为 Application Programming Interface(应用程序编程接口) 。 对这个定义的理解,需要追溯到操作系统的发展历史上。当 Windows 操作系统开始占 据主导地位的时候,开发 Windows 平台下的应用程序成为人们的需要。而在 Windows 程 序设计领域处于发展的初期,Windows 程序员所能使用的编程工具惟有 API 函数。这些函 加密与解密 ・19・ 数提供应用程序运行所需要的窗口管理、图形 Windows Windows 设备接口、 内存管理等各项服务功能。 这些功能 应用程序 应用程序 以函数库的形式组织在一起,形成了 Windows 应用程序编程接口(API) ,简称 Win API。Win Win API API 子系统负责将 API 调用转换成 Windows 操 子系统 作系统的系统服务调用。所以可以认为 API 函 用户层 数是构筑整个 Windows 框架的基石,在它的下 面是 Windows 的操作系统核心,而它的上面则 核心层 系统服务 是 Windows 应用程序,如图 1.2 所示。对于应 硬件层 用程序开发人员而言,所看到的 Windows 操作 系统实际上就是 Win API, 操作系统的其他部分 图 1.2 Windows 应用程序与操作系统的关系 对开发人员来说是完全透明的。 用于 16 位版本 Windows 的 API(Windows 1.0 到 Windows 3.1)现在被称作 Win16。 用于 32 位版本 Windows 的 API(Windows9x、Windows NT、Windows 2000 和 Windows XP)现在被称作 Win32。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 中通常位于 在 \WINDOWS\SYSTEM 子目录中,在 Windows NT/2000/XP 通常位于系统安装目录里的 \SYSTEM 和\SYSTEM32 子目录中。 在早期,Windows 的主要部分只需要在三个动态链接库实现。这代表了 Windows 的 三个主要子系统,它们被称作 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)等。 注意:Widnows 9x 是一个 16 位与 32 位的混合体,因此系统将 Kernel、User 和 GDI 等链 接库的 16 位与 32 位版本的一起加载到系统内存里。Windows NT/2000/XP 是一个 ・20・ 加密与解密 纯 32 位操作系统,并没有加载 krnl386.exe 等 16 位链接库到内存中(也许为了兼容 特殊的 16 位程序,才保留在 System32 目录中)。 Win 32 API 是一个基于 C 语言的接口,但是 Win32 API 中的函数可以被不同语言编写 的程序调用,只要在调用时遵循调用的规范即可。 1.2.2 什么是句柄 句柄(Handle)在 Windows 中使用非常频繁,它是 Windows 用来标识被应用程序建立 或使用对象的一个惟一的整数值 (通常为 32 位) Windows 要使用各种各样的句柄来标识诸 。 如应用程序实例、窗口、图标、菜单、输出设备、文件等对象。程序通过调用 Windows 函 数获取句柄,然后在其他 Windows 函数中使用这个句柄,以引用它代表的对象。句柄的实 际值对程序来说无关紧要的,这个值是被 Windows 模块内部用来引用相应对象的。 当一个进程被初始化时,系统要为它分配一个句柄表,句柄值是放入进程的句柄表中的 索引。当调试一个应用程序并且观察内核对象句柄的实际值时,会看到一些较小的值,如 1, 2 等。 请记住, 句柄的含义并没有记入文档资料, 并且可能随时变更。 实际上在 Windows 2000 中,返回的值用于标识放入进程的句柄表的该对象的字节数,而不是索引号本身。因此,在 Windows 的不同版本下调试程序时,就不要为句柄值的表达形式不同而疑惑了。 1.2.3 常用 WIN32 API 函数 由于 Win32 程序大量调用系统提供的 API 函数, Win32 平台上的调试器, SoftICE 而 如 等,恰好有针对 API 函数设置断点的强大功能,因而掌握常用的 API 函数具体用法会给跟 踪调试程序带来极大的方便。本节将把常用的 WIN32 API 函数介绍一下,其它相关的 API 函数将掺杂到各章节中讲解。详细的 Win32 API 参考文档可以从 MSDN 中获得。 API 函数是区分字符集的:A 表示 ANSI;W 表示 Widechars,即 Unicode。前者就是 通常使用的单字节方式。后者是宽字节方式,以方便处理双字节字符。用字符串作参数的每 个 Win32 函数都在操作系统中都有两种方式的版本。例如:编程时使用 MessageBox 函数, 而在 USER32.DLL 中,没有 32 位 MessageBox 函数的入口点。实际上,有两个入口点,一 个名为 MessageBoxA(ANSI 版) ,另一个名为 MessageBoxW(宽字符版) 。幸运的是,程 序员通常不必关心这个问题,程序中只需要使用 Messagebox,开发工具中的编译模块会根 据设置决定是采用 MessageBoxA 还是 MessageBoxW。 1.常用 API 函数详解 1)Hmemcpy 函数 void hmemcpy( void _huge *hpvDest, // 目的数据地址 const void _huge *hpvSource, // 源数据地址 long cbCopyn // 数据大小(字节) ); 返回值:如果成功就返回目的数据地址;失败则返回零。 加密与解密 ・21・ 这是个 Win16 API 函数,位于 16 位的 krnl386.exe 链接库里;但一般的编程书籍上很少 提到,原因它是系统底层的东西,没有特殊需要,一般不直接调用。它执行的操作很简单, 只是将内存中的一块数据拷贝到另一个地方。 注意:上文己说过,在 Windows 9x 中,Win32 函数调用通过转换层转换为 Win16 函数调 用,所以 Windows 9x 底层频繁地调用 Hmemcpy 这个 16 位的函数来拷贝数据。由 于这个特性,它常被解密者作为断点拦截数据,从而有个别称: “万能断点” 。在 Windows NT/2000 系统上相关的函数是 Memcpy,但在 Windows NT/2000 上不同于 在 Windows 9x 上,应用程序很少再调用 Memcpy 来处理数据,用此函数设置断点 基本上什么也拦不住。 2)GetWindowText 函数 此函数在 USER32.DLL 用户模块中,它的作用是取得一个窗体的标题文字,或者一 个控件的内容。函数原型: int GetWindowText( HWND hWnd, // 窗口或文本控件句柄 LPTSTR lpString, // 缓冲区地址 int nMaxCount // 复制的最大字符数 ); 返回值:如果成功就返回文本长度;失败则返回零值。 ANSI 版是 GetWindowTextA,Unicode 版是 GetWindowTextW。 3)GetDlgItem 函数 此函数在 USER32.DLL 用户模块中,它的作用是获取指定对话框的句柄。函数原型: HWND GetDlgItem( HWND hDlg, // 对话框句柄 int nIDDlgItem // 控件标识 ); 返回值:如果成功就返回对话框的句柄;失败则返回零。 4)GetDlgItemText 函数 此函数在 USER32.DLL 用户模块中,它的作用是获取对话框文本。函数原型: UINT GetDlgItemText( HWND hDlg, // 对话框句柄 int nIDDlgItem, // 控件标识(ID 号) LPTSTR lpString, // 文本缓冲区指针 int nMaxCount // 字符缓冲区的长度 ); 返回值:如果成功就返回文本长度;失败则返回零。 ANSI 版是 GetDlgItemTextA,Unicode 版是 GetDlgItemTextW。 注意:在 Windows 9x 下 GetDlgItemTextA(W)函数是独立存在的。而在 Windows 2000/XP ・22・ 加密与解密 下,GetDlgItemTexA(W)函数其实是调用 GetWindowTextA(W)来实现的,具体形式 如下: int GetWindowTextA(W)(GetDlgItem(hDlg,nIDDlgItem),lpString,nMaxCount) 5)GetDlgItemInt 函数 此函数在 USER32.DLL 用户模块中,它的作用是获取对话框整数值。函数原型: UINT GetDlgItemInt( HWND hDlg, // 对话框句柄 int nIDDlgItem, // 控件标识 BOOL *lpTranslated, // 接收成功/失败指示的指针 BOOL bSigned // 指定是有符号数还是无符号数 ); 返回值:如果成功,lpTranslated 被设置为 TRUE,返回文本对应的整数值;如果失败,lpTranslated 被设置为 FALSE,返回值为零。 6)MessageBoxEx 函数 此函数是在 USER32.DLL 用户模块中,创建和显示信息框。函数原型: int MessageBoxEx( HWND hWnd, // 父窗口句柄 LPCTSTR lpText, // 消息框文本地址 LPCTSTR lpCaption, // 消息框标题地址 UINT uType, // 消息框样式 WORD wLanguageId // 语言标识 ); ANSI 版是 MessageBoxExA,Unicode 版是 MessageBoxExW。 7)MessageBox 函数 此函数是在 USER32.DLL 用户模块中,创建和显示信息框。函数原型: int MessageBox( HWND hWnd, // 父窗口句柄 LPCTSTR lpText, // 消息框文本地址 LPCTSTR lpCaption, // 消息框标题地址 UINT uType // 消息框样式 ); ANSI 版是 MessageBoxA,Unicode 版是 MessageBoxW。 注意:在 Windows 9x/2000/XP 下,MessageBoxA(W)函数其实是调用 MessageBoxEx 来实 现的,具体形式如下:int MessageBoxEx(hWnd,lpText, lpCaption,uType,0)。 ・23・ 加密与解密 1.3 Windows 与 Unicode Unicode 影响到计算机工业的每个部分,对操作系统和编程语言的影响最大。在这一节 详细讨论 Windows 操作系统的各版本对 Unicode 支持情况,为以后在不同系统平台上调试 软件打好理论基础。 1.3.1 Windows 9x 与 Unicode Windows 9x 操作系统不是一种全新的操作系统。它继承了 16 位 Windows 操作系统的 特性, 它不是用来处理 Unicode 的。 如果要增加对 Unicode 的支持, 其工作量相当大。 Windows 9x 几乎都是使用 ANSI 字符串来进行所有的内部操作的。 但 Windows 9x 里还是有少量函数具有支持 Unicode 能力, 如下面 Win9x_Unicode.exe 程序: #include <windows.h> #include <tchar.h> int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { MessageBox(NULL, TEXT ("Windows 9x 支持 Unicode?"), TEXT ("Hello"), 0) ; return 0 ; } 在 VC 编译器中, 设置好 UNICODE 标识符, Unicode 方式编译, 按 生成 Unicode 程序。 可以用第三章的反汇编工具 W32Dasm 查看其汇编代码: :00401000 6A00 push 00000000 :00401002 6858504000 push 00405058 :00401007 6830504000 push 00405030 :0040100C 6A00 push 00000000 * Reference To: USER32.MessageBoxW, Ord:01C3h 0040100E FF1594404000 Call dword ptr [00404094] :00401014 33C0 xor eax, eax :00401016 C21000 ret 0010 很明显程序中存在一个 Unicode 版本的 Messagebox 函数, 这时在 Windows 9x 执行这个程序,结果程序正常运行,见图 1.3。 这个实例演示了部分 Unicode 函数能在 Windows 9x 上运 行的很好。 现在来看看 Windows 98 里 MessageBoxW 函数内部 结构: 图 1.3 Wins 9x 支持 Unicode ・24・ 加密与解密 int MessageBoxW( MessageBoxExW{ // 调用 MessageBoxExW( )函数 WideCharToMultiByte( ); // 取得 Unicode 字符串"Windows 9x 支持 Unicode?"的长度 GlobalAlloc( ); // 按字符长度分配内存 WideCharToMultiByte( ); // 将字符串"Windows 9x 支持 Unicode?"转换成 ANSI 字符串 WideCharToMultiByte( ); // 取得 Unicode 字符串"Hello"长度 GlobalAlloc( ); // 分配内存 WideCharToMultiByte( ); // 将 Unicode 字符串"Hello"转换成 ANSI 字符串 MessageBoxExA( ); // 最终还是调用 ANSI 版的 MessageBoxExA 函数显示窗口 GlobalFree( ); // 释放内存 GlobalFree( ); // 释放内存 } ); 原来 MessageBoxW 函数将 Unicode 字符串转换成 ANSI 字符串,最终调用 ANSI 版的 MessageBoxExA 函数来显示窗口。 这 个 实 例 涉 及 到 Unicode 与 ANSI 之 间 转 换 字 符 串 的 操 作 , 程 序 调 用 了 WideCharToMultiByte 函数进行转换。 这个函数用于将宽字符串转换成等价的多字节字符 串,如下所示: int WideCharToMultiByte( UINT CodePage, // 标识与新转换的字符串相关的代码页 DWORD dwFlags, // 设定用于转换的其它控件,通常设置为 0 LPCWSTR lpWideCharStr, // 要转换的字符串的内存地址 int cchWideChar, // 该字符串长度(字节) LPSTR lpMultiByteStr, // 转换后多字节版本的字符串的内存地址 int cchMultiByte, // 转换后多字节版本字符串的长度(字节) LPCSTR lpDefaultChar, // 通常为 NULL LPBOOL lpUsedDefaultChar // 通常为 NULL ); 如果传递-1 作为 WideCharToMultiByte 函数的 cchWideChar 参数,那么该函数用于 确定源字符串的长度。 如果传递 0 给 cchMultiByte 参数, 那么该函数将不执行字符串的转换, 而是返回使转换取得成功所需要的缓存值。一般来说通过下列步骤将 Unicode 字符串转换成 ANSI 字符串: 1) 调用 WideCharToMultiByte 函数,为 lpMultiByteSt 参数传递 NULL,为 cchMultiByte 参数传递 0,以得到所需要的缓存值; 2) 分配足够的内存块,准备用于存放转换后的 ANSI 字符串; 3) 再调用 WideCharToMultiByte 函数进行转换, 这次将缓存地址作为 lpMultiByteStr,参数, 并传递第一次调用 WideCharToMultiByte 返回的缓存大小,作为 cchMultiByte 参数; 4) 使用转换后的字符串; 加密与解密 ・25・ 5) 释放 ANSI 字符串占用的内存块。 如要将 ANSI 字符串转换成 Unicode 字符串可调用 MultiByteToWideChar 函数来实现。 Windows 9x 系 统 上 还 有 其 他 几 个 比 较 常 用 的 Unicode 函 数 , 如 lstrlenW 、 FindResourceW、GetCommandLine、ExtTextOut、TextOutW、MultiByteToWideChar 等。 Windows 9x 上其它成百上千的函数只提供了接受 Unicode 参数的进入点,但是这些函 数并不将 Unicode 字符串转换成 ANSI 字符串,它们只返回运行失败的消息。这些函数只有 ANSI 版本的才能正确运行。所以,如要为 Windows 9x 系统开发软件,只能用 ANSI 版函数 开发应用程序,Unicode 版本的程序在 Windows 9x 下无法正常运行。 1.3.2 Windows 2000/XP 与 Unicode Windows 2000 是使用 Unicode 从头进行开发的,其系统核心完全是用 Unicode 函数工 作的。如果调用任何一个 Windows 函数并给它传递一个 ANSI 字符串,那么系统首先要将 字符串转换成 Unicode,然后再将 Unicode 传递给操作系统。相反,如果希望函数返回 ANSI 函数,系统就会首先将 Unicode 字符串转换成 ANSI 字符串,然后将结果返回给应用程序。 所有这些操作对用户来说都是透明的,但进行这些字符串的转换需要占用系统资源。 在这以一实例讲述 Windows 2000 是如何将 ANSI 版函数转换成 Unicode 版函数的,源 码与 Win9x_Unicode 实例一样,编译时不设置 UNICODE 标识符,按 ANSI 方式编译。 现在来看看 Windows 2000 里 MessageBoxA 函数内部结构: int MessageBoxA( MessageBoxExA{ // 调用 MessageBoxExA 函数 MBToWCSEx( ) // 将 MessageBoxA 消息框主体文字转换成 Unicode 字符串 MBToWCSEx( ) // 将 MessageBoxA 消息框标题栏上的文字转换成 Unicode 字符串 MessageBoxExW( ) // 调用 MessageBoxExW 函数 HeapFree( ) // 释放内存 } ); 这个试验结果表明 MessageBoxExA 函数其实是一个替换翻译层,用于分配内存,并将 将 ANSI 字符串转换成 Unicode 字符串,系统最终是调用 Unicode 版的 MessageBoxExW 函 数执行。当 MessageBoxExW 返回时,它便释放内存缓存。在这个过程中,系统必须执行这 些额外的转换操作,因此 ANSI 版的应用程序需要更多的内存和占用更多的 CPU 资源。而 Unicode 版的程序在 Windows 2000/XP 下执行效率就高多了。 Windows 2000/XP 既支持 Unicode, 也支持 ANSI, 所有新的和未过时的函数在 Windows 2000/XP 中都同时拥有 ANSI 和 Unicode 两个版本。 1.4 Windows 消息机制 Windows 是一个消息(Message)驱动式系统,Windows 消息提供应用程序与应用程序 ・26・ 加密与解密 之间、应用程序与 Windows 系统之间进行通信的手段。应用程序想要实现的功能由消息来 触发,并且靠对消息的响应和处理来完成。 Windows 系统中有两种消息队列,一种是系统消息队列,另一种是应用程序消息队列。 计算机的所有输入设备由 Windows 监控,当一个事件发生时,Windows 先将输入的消息放 入系统消息队列中,再将输入的消息拷贝到相应的应用程序队列中,应用程序中的消息循环 从它的消息队列中检索每个消息并且发送给相应的窗口函数中。一个事件的发生,到达处理 它的窗口函数必须经历上述过程。值得注意的是消息的非抢先性,即不论事件的急与缓,总 是按到达的先后排队(一些系统消息除外) 这就使得一些外部实时事件可能得不到及时的 处理。 由于 Windows 本身是由消息驱动的,所以调试程序时跟踪一个消息会得到相当底层的 答案。在这将常用 Window 消息函数列出,以方便需要时参考。 1)SendMessage 函数 调用一个窗口的窗口函数,将一条消息发给那个窗口。除非消息处理完毕,否则该函 数不会返回。 LRESULT SendMessage( HWND hWnd, // 目的窗口的句柄 UINT Msg, // 消息标识符 WPARAM wParam, // 消息的 WPARAM 域 LPARAM lParam // 消息的 LPARAM 域 ); 返回值:由具体的消息决定 2)PostMessage 函数 将一条消息投递到指定窗口的消息队列。投递的消息会在 Windows 事件处理过程中 得到处理。在那个时候,会随同投递的消息调用指定窗口的窗口函数。特别适合那些不需 要立即处理的窗口消息的发送。 BOOL PostMessage( HWND hWnd, // 目的窗口的句柄 UINT Msg, // 消息标识符 WPARAM wParam, // 消息的 WPARAM 域 LPARAM lParam // 消息的 LPARAM 域 ); 返回值:如消息投递成功,则返回 TRUE(非零) 。 3)WM_COMMAND 消息 是用户从菜单或按钮中选择一条命令或者一个控件发送给它的父窗口,或者当一个快 捷键被解释时发送。 WINUSER.H 文件里定义了 WM_COMMAND 消息对应的十六进制是 0x0111。 WM_COMMAND 加密与解密 wNotifyCode = HIWORD(wParam); // 通告代码 wID = LOWORD(wParam); ・27・ // 菜单条目、控件或快捷键的标识符 // 控件句柄 hwndCtl = (HWND) lParam; 返回值:如果应用程序处理这条消息,返回值为零。 4)WM_DESTROY 消息 当一个窗口被破坏时发送。WM_DESTROY 消息的十六进制是 0x0002。 这条消息无参数 返回值:如果应用程序处理这条消息,返回值为零。 5)WM_GETTEXT 消息 应用程序发送一条 WM_GETTEXT 消息,拷贝一个对应窗口的文本到一个呼叫程序提 供的缓冲区。WM_GETTEXT 消息的十六进制是 0x000D。 WM_GETTEXT wParam = (WPARAM) cchTextMax; lParam = (LPARAM) lpszText; 返回值:被拷贝的字符数 // 需要拷贝的字符数 // 接收文本的缓冲区地址 6)WM_LBUTTONDOWN 消息 当光标在一个窗口的客户区并且用户按下鼠标左键时,WM_LBUTTONDOWN 消息 被发送。如果鼠标动作没被捕获,这条消息被发送给光标下的窗口;否则,被发送给已经 捕获鼠标动作的窗口。WM_LBUTTONDOWN 消息的十六进制是 0x0201。 WM_LBUTTONDBLCLK fwKeys = wParam; // key 旗标 xPos = LOWORD(lParam); // 光标的水平位置 // 光标的垂直位置 yPos = HIWORD(lParam); 返回值:如果应用程序处理这条消息,返回值为零。 7)WM_LBUTTONUP 消息 当光标在一个窗口的客户区并且用户释放鼠标左键时,WM_LBUTTONUP 消息被发 送。如果鼠标动作没被捕获,这条消息被发送给光标下的窗口;否则,被发送给已经捕获 鼠标动作的窗口。WM_LBUTTONUP 消息的十六进制是 0x0202。 WM_LBUTTONUP fwKeys = wParam; // key 旗标 xPos = LOWORD(lParam); // 光标的水平位置 // 光标的垂直位置 yPos = HIWORD(lParam); 返回值:如果应用程序处理这条消息,返回值为零。 8)WM_QUIT 消息 当应用程序呼叫 PostQuitMessage 函数时,生成消息 WM_QUIT。WM_QUIT 消息的十 六进制是 0x0012 ・28・ 加密与解密 WM_QUIT nExitCode = (int) wParam; 返回值:这条消息没有返回值。 // 退出代码 1.5 Windows 注册表 从 Windows 95 开始,Microsoft 在 Windows 中引入了注册表(Registry)的概念(实 际上原来在 Windows NT 中已有此概念) 。注册表是 Windows 的核心数据库,表中存放各 种参数,直接控制 Windows 的启动、硬件驱动程序的装载、应用程序的各种状态信息和 数据等。 1.5.1 注册表的逻辑结构 注册表被组织成子树及其项、子项、值项的分层结构,就像磁盘文件系统的目录结构 一样,如图 1.4 所示。每个键都包含组特定的信息,每个键的键名都是和它所包含的信 息相关联的。 子目录树 注册表项 项目名称 数据类型 值 子项 活动子项 活动子项中的项目 子项 图 1.4 Windows 注册表 注册表的根主键不可以被删除,也不能添加新的根主键,它们的结构含义如下: 1. HKEY_CLASSES_ROOT 简称 HKCR,这个主键包括文件扩展名和 COM 组件类的注册信息。 2. HKEY_CURRENT_USER 简称 HKCU,这个主键包含了与当前登录用户相关的软件配置和参数。 ・29・ 加密与解密 3. HKEY_LOCAL_MACHINE 简称 HKLM,这个主键存放的是用来控制系统和软件的设置。 其中 HKLM\SOFTWARE 子键存储了一些 Windows 系统级软件配置信息。另外,还存 储了第三方应用程序的系统级设置,如应用程序中文件和目录路径,以及有关使用许可证和 期限的信息。 4. HKEY_USERS 简称 HKU,它包含关于动态加载的用户配置文件和默认的配置文件的信息。这包含同 时出现在 HKEY_CURRENT_USER 中的信息。 5. HKEY_CURRENT_CONFIG 包含在启动时由本地计算机系统使用的硬件配置文件的相关信息。 另 外, HKEY_DYN_DATA 是 Windows 9x 注 册表的子目录树,不被 Windows 2000/XP 使用。 HKEY_LOCAL_MACHINE 和 HKEY_CURRENT_USER 是注册表中两 个最重要的部分,一般应用程序在这存储配置信息。 每个注册表项或子项都可以包含称为值项的数据。有些值项存储每个用户的特殊信 息,而其他值项则存储应用于计算机所有用户的信息。值项包括三部分:值的名称、值的 数据类型和值本身。表 1-2 列出当前由系统定义和使用的数据类型。 表 1-2 注册表的数据类型 数据类型 REG_BINARY REG_DWORD REG_EXPAND_SZ REG_MULTI_SZ REG_SZ REG_FULL_RESOURC E_DESCRIPTOR 1.5.2 说 明 未处理的二进制数据。多数硬件组件信息都以二进制数据存储,而以十六进 制格式显示在注册表编辑器中。 数据由 4 字节长的数表示。 许多设备驱动程序和服务的参数是这种类型并在 注册表编辑器中以二进制、十六进制或十进制的格式显示。 长度可变的数据串。 该数据类型包含在程序或服务使用该数据时确定的变量。 多个字符串。其中包含格式可被用户读取的列表或多值的值通常为该类型。 项用空格、逗号或其他标记分开。 固定长度的文本串。 设计用来存储硬件元件或驱动程序的资源列表的一列嵌套数组。 注册表相关函数 1. RegOpenKeyEx 函数 此函数在 ADVAPI32.DLL 用户模块中,它的作用是打开子健。函数原型: LONG RegOpenKeyEx( HKEY hKey, // 要打开的主键句柄或标准项名 LPCTSTR lpSubKey, // 要打开的子键名地址 DWORD ulOptions, // 保留,必须为 0 REGSAM samDesired, // 存取掩码 ・30・ 加密与解密 PHKEY phkResult // 存放打开子键句柄的地址 ); 返回值:如果成功就返回 0(ERROR_SUCCESS) ;失败则返回非零错误代码 2. RegQueryValueEx 函数 此函数在 ADVAPI32.DLL 用户模块中,它的作用是获取一个项的设置值。函数原型: LONG RegQueryValueEx( HKEY hKey, // 需要查找的主键的句柄或标准项名 LPTSTR lpValueName, // 需要查找的子键名地址 LPDWORD lpReserved, // 保留,必须为 0 LPDWORD lpType, // 存放子键类型的缓冲区地址 LPBYTE lpData, // 存放返回结果的缓冲区地址 LPDWORD lpcbData // 存放返回结果字节长度的缓冲区地址 ); 返回值:如果成功就返回 0(ERROR_SUCCESS) ;失败则返回非零错误代码 3. RegSetValueEx 函数 此函数在 ADVAPI32.DLL 用户模块中,它的作用是设置指定项的值。函数原型: LONG RegSetValueEx( HKEY hKey, // 需要设置键值的主键句柄或一个标准项名 LPCTSTR lpValueName, // 需要设置的子键名地址 DWORD Reserved, // 保留,必须为 0 DWORD dwType, // 键值类型 CONST BYTE *lpData, // 所设置的数据地址 DWORD cbData // 所设置的数据字节长度 ); 返回值:如果成功就返回 0(ERROR_SUCCESS) ;失败则返回非零错误代码 了解程序是如何操作注册表的将有助于加深对注册表的认识, 在跟踪分析注册表操作时 更加得心应手。 用 Win32 API 操作 Windows 注册表的基本步骤: (1)用 RegOpenKey( )或 RegOpenKeyEx( )打开想要操作的主键,获得一个句柄。 (2)将句柄传递给 RegQueryValueEx( )、RegSetValueEx( )等函数来读、写相应的键值。 (3)操作完毕之后用 RegCloseKey( )关闭先前获得的句柄。 1.5.3 注册表分析软件 注册表分析软件有两大类,一类是注册表读写监视软件,另一类是注册表比较软件。 本节工具位于光盘\tools\spy tools\。 加密与解密 ・31・ 1.注册表“监视员”Regmon Regmon(Registry Monitor)是一款出色的注册表数据库实时监视软件,它将与注册表 数据库相关的一切操作(如读取、修改、出错信息等)全部记录下来以供用户参考,并允许 用户对记录的信息进行保存、过滤、查找等处理。 在 Windows NT/2000/XP 系统下, 必须以管理员的身份进入系统才能正常运行 Regmon。 缺省情况下 Regmon 会对注册表数据库的所有读取、修改、错误信息等内容进行监视,这样 就淹没在一大堆垃圾信息中。这时,可以设置其 Filter(过滤)功能,按“Ctrl+L”打开过 滤器,如图 1.5 所示。 图 1.5 Regmon 过滤设置 所谓过滤器,其实就是一组条件,这组条件限制 Regmon 什么该显示,什么不该显示。 过滤器各部分含义如下: 包 含( Include) “*”是通配符,代表所有文件。可设置具体文件名,多个文件用 : 英文的分号“ ;”隔开,例如: Regtest;notepad” “ 。监视指定注册表的根键名,如: HKLM\等。监视指定程序操作注册表指定的键值,如:regtest*reg。 排 除( Exclude ) :规定记录结果不包含的字符串。如:在“包含”中设置记事本程 序“ notepad” ,而在“排除”里设置“ HKCU\” ,显示的结果将把对注册表 HKCU\ 下的读取忽略。 加 亮( Highlight) :当符合条件时,该文件访问记录将被高亮显示。 图 1.6 Regmon 主界面 ・32・ 加密与解密 按图 1.5 设置过滤器后,打开 Regmon 程序菜单“选项 /捕促事件” 。运行记事本程序 与光盘 \chap01 提供的 Regtest 程序,对注册表读写结果如图 1.6。如需查看指定条目在注 册表中的显示情况,用鼠标双击此行就可。例如双击“HKLM\SOFTWARE\pediy\reguser” 这一行,就会自动运行 Windows 的 Regedit.exe 程序,并打开相应的项目。 2.注册表照相机 Regsnap RegSnap 是一款分析 Windows 注册表及系统配置文件更改的工具。Regsnap 的原理非 常简单,在不同的时间段对注册表“拍照” ,然后比较“拍照”结果,进而分析出注册表及 与系统文件的变化。 1) 首先运行 RegSnap 进行第一次快照,在菜单栏中选择“New” ,这时弹出图 1.7 对话框。 图 1.7 RegSnap 快照窗口 快照全部(Snap All) :除了记录注册表的数据外,还会记录下 Windows 的系统配置文 件的情况。 只包含注册表(Registry only) :仅记录系统注册表的情况。若选择后者,运行的效率当 然会高些。 远端 PC 注册表(Registry from remote) :仅对注册 后的用户开放,主要功能是选择记录局域网上的某 台终端的注册表情况。 2) 第二步运行光盘提供的读写注册表的小程序 Regtest (图 1.8) ,将数据写进注册表。再用 RegSnap 进行 第二次快照。结束后,点击“比较(Compare) ”命 令菜单打开比较快照窗口。 Regsnap 在比较两个注册 表快照文件后,会输出一个比较报告: 图 1.8 ***新增的键 HKEY_LOCAL_MACHINE\SOFTWARE\pediy\reguser Value: String: "http:\\www.pediy.com" -------------Total positions: 1 演示对注册表读写操作 ・33・ 加密与解密 3、注册表比较工具 RegShot RegShot 是个小巧的注册表静态比较工具,它能快 速地帮助您发现注册表的变化。RegShot 是一款国产免 费软件,作者是 TiANWEi。 1)首先,在运行软件前用 RegShot 对注册表做一次拍 照,按下“摄取 1”按钮,如图 1.9。 2) 运行结束后,对注册表再做一次拍照,按下“摄取 2”按钮。 3)按 RegShot 的“比较”按钮进行比较。 在这用前面的 Regtest 软件做个演示(图 1.8) 。先 用 RegShot 对注册表做一次拍照,用 Regtest 向注册表 写入键值,再用 RegShot 对注册表做一次拍照,比较两 次结果如下: 图 1.9 小巧的 RegShot 增加键:1 HKEY_LOCAL_MACHINE\SOFTWARE\pediy ---------------------------------增加值:1 HKEY_LOCAL_MACHINE\SOFTWARE\pediy\reguser: 68 74 74 70 3A 5C 5C 77 77 77 2E 70 65 64 69 79 2E 63 6F 6D 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 1.6 保护模式简介 学过 8088/8086 汇编语言的读者,一定熟悉 AX、BX、CX、DX、SI、DI、SP、BP 这 些 16 位的寄存器; 80386 中, 在 这些寄存器被扩展到了 32 位, EAX、 即 EBX、 ECX、 EDX、 ESI、EDI、ESP 和 EBP,段寄存器增加了两个 FS 和 GS。 一般来说,80x86(80386 及其以后的各代 CPU)可在实模式、保护模式和虚拟 86 模式 三种模式下运转。实模式就是古老的 MS-DOS 的运行环境,只能利用这些 32 位寄存器的前 16 位,而后面的 16 位就浪费了。当前流行的 Windows 操作系统运行在保护模式下,在保 护模式下程序可以利用更多的内存,可以实现多任务系统 。在 Widnows 9x 下,如果开一个 MS-DOS 窗口,即运行一个 DOS 应用程序,那么该程序就运行在虚拟 86 模式下。 1.6.1 虚拟内存 在保护模式下,CPU 的寻址方式与实模式不同。在实模式下的寻址方式是“段基址+段 偏移” ,段的缺省大小为 64KB,所有段是可读、写的,唯有代码段是可执行的,段的特权级 为 0。而在保护方式下内存是“线性”的,因为这时段寄存器的意义不同,它里面放的不再 是段基地址, 而是存放着段选择子, 这个值是不直接参与寻址的, 只是全局描述符表 Global ( Descriptor Table, GDT)或本地描述符表(Local Descriptor Table, LDT)的一个指针,不同 段的有不同的属性(读、写、执行、特权级等) ,如图 1.10 所示。尽管如此,在继续看保护 ・34・ 加密与解密 模式内存结构时, 仍请记住段/偏移量的概念, 不妨把段寄存器看作对于保护模式中的选择子 的一个模拟。 段寄存器 内存 段选择子 偏移量 段描述符寄存器 基地址 属性 操作数 偏 界限 移 量 0~4GB-1 0~3 特权级 0~4GB-1 基地址 00000000H 图 1.10 保护模式的寻址操作 Win32 的平坦内存模式使每个进程有赋予它自己的虚拟空间,对于 32 位进程来说,这 个地址空间是 4GB, 因为 32 位指针可以拥有从 0x00000000~0xFFFFFFFF 之间的任何一个 值。此时程序的代码和数据都放在同一地址空间中,即不必区分代码段和数据段。程序员也 不需了解段寄存器 CS、DS、ES 等的具体内容了。 虚拟内存(Virtual Memory)不是真正的内存,它通过映射(Map)的方法,使可用的 虚拟地址(Virtual Address)达到 4GB,每个应用程序可以被分配 2GB 的虚拟地址,剩下 的 2GB 留给操作系统自己用。在 Windows NT 中,应用程序甚至可有 3GB 的虚拟地址。简 单地说,虚拟内存的实现方法和过程是: 1) 当一个应用程序被启动时,操作系统就创建一个新进程,并给每个进程分配 2GB 的 虚拟地址(不是内存,只是地址) ; 2) 虚拟内存管理器(Virtual Memory Manager)将应用程序的代码映射到那个应用程序 的虚拟地址中的某个位置并把当前所需要的代码读取到物理地址中(注意:虚拟地 址和应用程序代码在物理内存中的位置是没有关系的) ; 3) 如果使用动态链接库(Dynamic-Link Library,即 DLL) DLL 也被映射到进程的虚 , 拟地址空间,在需要的时候才被读入物理内存; 4) 其他项目(例如数据、堆栈等)的空间是从物理内存中分配的,并被映射到虚拟地址 空间中; 5) 应用程序通过使用它的虚拟地址空间中的地址开始执行,然后虚拟内存管理器把每次 的内存访问映射到物理位置。 如果看不明白上面的步骤也不要紧,但要明白以下几点: 1) 应用程序是不会直接访问物理地址的; 2) 虚拟内存管理器通过虚拟地址的访问请求,控制所有的物理地址访问; 3) 每个应用程序都有相互独立的 4GB 的寻址空间,不同应用程序的地址空间是隔离的; 4) DLL 程序没有自己的“私有”空间,它们总是被映射到其它应用程序的地址空间中, 作为其它应用程序的一部分运行。 使用虚拟内存的好处是:简化了内存的管理,并可弥补物理内存的不足;可以防止多任 务境下各个应用程序之间的冲突。 ・35・ 加密与解密 1.6.2 保护模式的权限级别 在保护模式下,所有的应用程序都有权限级别(Privilege Level,简写为 PL) ,这个权 限级别按优先次序分为四等:0、1、2 和 3, 其中 3 特权级别最低,0 特权级别最高。特权 级环如图 1.11 所示。 0 特权级别 如果应用程序拥有第 0 级的权限 (也就是 1 特权级别 说其 PL=0, 或者说它是运行在 Ring 0 的应用 2 特权级别 程序) ,它就可以执行所有的指令并访问所有 3 特权级别 数据;如果应用程序拥有的权限级别是第 3 级,它能执行的指令是有限的,能访问的数据 图 1.11 特权级环 也是有限的。 操作系统核心层是运行在 Ring 0 级的, 而 Win32 子系统(如动态链接库 KERNEL32.DLL 、USER32.DLL 和 GDI32.DLL 等)是运行在 Ring3 级的,以提供与应用程序接口。 图 1.12 是 Windows 2000/XP 体系结构简图,核心态工作在 Ring 0 级,用户态工作在 Ring 3 级。 HAL 是一个可加载的核心模块 HAL.dll,它为运行在 Windows 2000/XP 上的硬 件平台提供低级接口。Windows 2000/XP 的执行体是 NTOSKRNL.EXE 的上层(内核是其 下层) 。用户层导出并且可以调用的函数接口在 NTDLL.DLL 中,通过 Win32API 或一些其 他的环境子系统对他们进行访问。 Win32 应用程 服务进程 Win32 子系统动态链接库 Ntdll.dll 用户态(Ring3) 内存、进程、线程、IO 等管理 核心体 核心态(Ring0) 设备驱动程序 硬件抽象层(HAL) 图 1.12 Windows 2000/XP 特权级简图 另外用户的应用程序也是运行在 Ring 3 级的(就是用 Visual C++、Borland C++、 Visual Basic、Delphi、Borland C++ Builder 等 SDK 工具开发的应用程序) ,也就是说 享有的权限是最低的——换言之,受到保护模式的“保护” 。该类应用程序没有权限去破 坏操作系统,只能规矩地使用 Win32API 接口函数与系统打交道。如想控制系统,就必须 取得 0 特级权,如后面接触到的调试工具 SoftICE 就是工作 0 特级权上的。 ・36・ 加密与解密 第2章 代码分析技术 在进行软件汉化、解密和计算机病毒分析工作中,一个首要问题是对被汉化、解密的 软件进行代码分析,这就必须掌握一定的代码分析能力。 2.1 认识 PE 格式 在 Win16 平台上(如 Windows 3.x) ,可执行文件是 NE 格式,在 Win32 平台上(包 括 Windows 95/98/ME/NT/2000/XP/CE) ,可执行文件是 PE(Portable Executable)格式。 本节只介绍一下 PE 文件的基本知识,具体内容参考 PE 格式一章。 2.1.1 PE 格式 PE 文件使用的是一个平面地址空间,所有代码和数据都被合并在一起,组成一个很 大的结构。文件的内容被分割为不同的区块(Section,又称区段、节等) ,块中包含代码 或数据,各个块按页边界来对齐,块没有大小限制,是一个连续结构。每个块都有它自己 在内存中的一套属性,如:这个块是否包含代码、是否只读或可读/写等。 每 一个区块都有不同的名字,这个名字是用来表示区块的功能。例如,一个区块 叫.rdata 表明它是一个只读区块。常见的块有.text、.rdata、.data、.idata、.rsrc 等。各种 块含义如下: .text:是在编译或汇编结束时产生的一种块,它的内容全是指令代码; .rdata:是运行期只读数据; .data:是初始化的数据块; .idata:包含其他外来 DLL 的函数及数据信息,即输入表; .rsrc:包含模块的全部资源,如图标、菜单、位图等等。 注意:使用区块名字只是方便人们,而对操作系统来说是无关紧要的,因此可将上面块名 改成其它毫无意义的字符,不会影响 PE 文件执行。 PE 文件非常好的一个地方就是在磁盘上的数据结构与在内存中的结构是一致的,如 图 2.1 所示。装载一个可执行文件到内存中,主要就是将一个 PE 文件的某一部分映射到 地址空间中。这样,PE 文件的数据结构在磁盘和内存中是一样的。 认识 PE 文件不是作为单一内存映射文件被装入内存是很重要的。Windows 加载器遍 历 PE 文件并决定文件的哪一部分被映射,这种映射方式是将文件较高的偏移位置映射到 较高的内存地址中。当磁盘文件一旦被装入内存中,其某项的偏移地址可能区别于原始的 偏移位置。 ・37・ 加密与解密 映射到内存中 磁盘中的 PE 文件 文件尾 .rsrc 块 不能被映射数据 .rsrc 块 文 其它块 其它块 件 .data 块 偏 移 .data 块 地 址 .text 块 .text 块 块表 块表 PE 头 文件头:0 某一虚拟地址(VA) PE 头 Dos 头部 Dos 头部 相对虚拟地址(RVA) 基地址 ImageBase) ( 图 2.1 PE 文件结构 PE 相关名词解释如下: 1.入口点(Entry Point) PE 文件执行时的入口点(Entry Point) 也就是说程序在执行时的第一行代码的地 。 址应该就是这个值。 2.文件偏移地址(File Offset) PE 文件在磁盘上储存时,各数据的地址称文件偏移地址(File Offset)或物理地址 (RAW Offset) 。文件偏移地址(File Offset)是从 PE 文件第一个字节开始计数,起始值 为 0。用十六进制工具(例如 Hex Workshop、WinHex 等)打开文件显示的地址就是文件 偏移地址。 3.虚拟地址(Virtual Address,VA) 由于 Windows 程序是运行在 386 保护模式下,所以程序访问存储器所使用的逻辑地 址称为虚拟地址(Virtual Address,VA) ,又称之为内存地址(Memory Offset) 。与实地 址模式下的分段地址类似,虚拟地址也可写成“段:偏移量”的形式,这里的段是指段选择 子。如: 0167: 00401000”就是这种表示方法,其中: “ 0167:这是段选择子,其数据保存在 CS 段选择器里。同一程序在不同系统环境下此值 可能不同,一般也不需要关心此值。 00401000:此处表示内存的虚拟地址(Virtual Address,VA) ,一般来说,同一程序的 同一条指令在不同系统环境下此值相同。 4.基地址(ImageBase) 文件执行时将被映像到指定内存地址中, 这个初始内存地址称为基地址 ImageBase) ( 。 这个值是由 PE 文件本身设定。按照默认设置,Visual C++建立的 EXE 文件基地址是 ・38・ 加密与解密 0x00400000,DLL 文件基地址是 0x10000000。但是,可以在创建应用程序的 EXE 文件时 改变这个地址,方法是在链接应用时使用链接程序的/BASE 选项。 5.相对虚拟地址 相对虚拟地址 Relative Virtual Address, ( RVA) 是内存中相对于 PE 文件装入地址 (基 地址)的偏移量。它们之间关系如下: 相对虚拟地址(RVA)= 虚拟地址(VA)- 基地址(ImageBase) 例如,某个 EXE 文件从地址 0x400000 处装入,并且它的代码区块开始于 0x401000, 代码区块的 RVA 将是: (RVA)0x1000 =(虚拟地址)0x401000 - (基地址)0x400000 2.1.2 文件偏移地址与虚拟地址转换 PE 文件被映射到内存后,同一数据相对于文件头的偏移量在内存中和磁盘文件中可 能是不同的,这样就存在着文件偏移地址与虚拟地址转换问题。 用任何一款 PE 编辑工具(如 LordPE,光盘\tools\PE_tools\Editor\LordPE)来查看 实例 PE_Offset.exe 文件的块表结构。点击 LordPE 的“PE Editor”打开 PE_Offset 文件, 如图 2.2 所示。 入口点 基地址 区块编辑 地址转换器 图 2.2 LordPE 中的 PE 编辑器 LordPE 功能非常强大,其具体用法在后面章节专门介绍。若要查看区块的信息,点 击“Sections”按钮来打开区块编辑器(图 2.3) 。 块名 虚拟地址 图 2.3 虚拟大小 物理地址 物理大小 区块(Section)数据 图 2.3 中显示出 PE_Offset 文件在磁盘与内存中各区块的地址、大小等信息。虚拟地 址和虚拟大小是指该区块在内存中的地址和大小。物理地址和物理大小是指该区块在磁盘 文件中的地址和大小。 文件区块有两种对齐值,一种是磁盘文件中的,另一种是内存中的。PE 文件被映射 ・39・ 加密与解密 到内存时,区块总是至少以一个页边界为开始。也就是说,每个区块的第一个字节对应于 某个内存页。在 x86 系统中,每个内存页的大小是 4K,也就是 0x1000 字节,所以在 x86 系统中,PE 文件区块的内存对齐值一般等于 0x1000,每个区块按 0x1000 之倍数内存偏移 位置开始。而磁盘对齐值一般取 0x200,每个区块按 0x200 之倍数的文件偏移位置开始。 例如本例文件的.text 区块在磁盘文件中的起始偏移位置是 0x400,当 PE 文件被装入 内存后,将放在装入地址之上的 0x1000 位置处。同样,.rdata 区块在磁盘文件的 0x800 偏移处,PE 文件被装入内存后,将放在装入地址之上的 0x2000 字节处。 另外,磁盘文件中每个区块的大小必定等于磁盘对齐值的整数倍,而区块的实际代码 或数据的大小不一定刚好是这么多,所以在不足的地方一般以 0x00 来填充,这就是区块 间的间隙。当然也可以创建一个在文件中的区块起始位置与在内存中的装入地址相同的 PE 文件,这会使执行文件变得更大,但是可以加速文件的装入速度。 映射到内存中 .data 块 磁盘文件中 .rdata 块 0x402000 不能映射的数据 间隙 .data 块 文 件 .text 块 .rdata 块 移 0x800 地 址 .text 块 0x400 (文件偏移)add1 起始地址 0x0 虚 0x401000 0xC00 偏 0x403000 块表 拟 地 ∆k 址 PE 头 块表 PE 头 DOS 头部 add2(虚拟偏移) DOS 头部 0x400000 基地址 图 2.4 应用程序加载映射示意图 在图 2.4 中,磁盘文件中.text 块起始端与文件头偏移量为 add1,映射到内存后,.text 起始端与文件头(基地址)的偏移量为 add2。这里 add1 的值就是文件偏移地址(File Offset) add2 的值就是相对虚拟地址(RVA) , ,假设它们的差值为 ∆k,则文件偏移地址 与虚拟地址关系如下: File Offset = RVA - ∆k File Offset = VA – ImageBase - ∆k 式中: File Offset:文件偏移地址 ImageBase:基地址 RVA:相对虚拟地址 VA:虚拟地址 在同一区块中, 各地址的偏移量是相等的, 可用上面的公式对此区块中任一 File Offset 与 VA 进行转换。但请不要误解在整个文件里,File Offset 与 VA 的差值是 ∆k。因为在内 ・40・ 加密与解密 存中各区块是以一个页边界为开始的, 所以不同区块在磁盘与内存中的差值不一样。 2-1 表 是 PE_Offset 文件各区块在磁盘与内存起始地址值。 表 2-1 各区块在磁盘与内存起始地址差值 区块 虚拟偏移量(Virtual Offset)=RVA 文件偏移量 Raw Offset) ( 差值 ∆k .text 0x1000 0x400 0xC00 .rdata 0x2000 0x800 0x1800 .data 0x3000 0xC00 0x2400 例 如,此实例中某一虚拟地址( VA) = 0x401112 ,要求计算它的文件偏移地址( File Offset) 0x401112 在.text 块中,此时 ∆k = 0xC00 。 File Offset = VA - ImageBase - ∆k = 0x401112 - 0x400000 - 0xC00 = 0x512 再来看看虚拟地址 0x4020D2 的转换: 0x4020D2 是在.rdata 块中,此时 ∆k=0x1800 File Offset = VA – ImageBase –∆k = 0x4020D2 - 0x400000 - 0x1800 = 0x8D2 在实际操作时,建议使用 RVA-Offset 之类的转换工具。LordPE 工具也有这个转换功 能,点击图 2.2 的“FLC”按钮打开“文件位址计算器(File Location Calculator),见图 ” 2.5。 计算 虚拟地址 相对虚拟地址 文件偏移地址 16 进制编辑器 图 2.5 偏移地址转换器 在 VA 域中输入填入要转换的虚拟地址 0x401112,点击“DO”按钮,转换后的文件 偏移地址(File Offset)为 0x512。 2.2 代码指令 在软件分析过程中,经常需要计算转移指令机器码或修改指定的代码,虽有许多工具可 以做这些事,但掌握其原理和技巧很有必要的。 2.2.1 转移指令机器码的计算 根据转移的距离,转移转指令有以下类型: 短转移(Short jump) :无条件转移和条件转移的机器码都是两个字节。转移范围是在 ・41・ 加密与解密 -128 到+127 字节。 长转移(Long jump) :无条件转移的机器码是 5 个字节,条件转移的机器码是 6 字节。 这是因为条件转移要用 2 字节表示其转移类型(如 je、jg 和 jns 等) ,其它 4 个字节表 示转移偏移量。在无条件转移仅用一个字节就可表示其转移类型(jmp),其它 4 个字节 表示转移偏移量。 子程序调用指令(call) call 指令调用有两类,一类是平常经常接触的,类似于长转移 : (Long jump) 另一类其调用的参数涉及到寄存器、 。 堆栈等值, 比较复杂。 call dword 如: ptr [eax * edx + 2]。 表 2-2 列出了常用转移指令机器码,通过该表就可根据转移偏移量计算出转移指令的 机器码。 表 2-2 转移指令的条件与机器码 转移类别 标志位 含 义 短转移机器码 长转移机器码 CALL - call 调用指令 E8xxxxxxxx E8xxxxxxxx JMP - 无条件转移 EBxx E9xxxxxxxx JO OF=1 溢出 70xx 0F80xxxxxxxx JNO OF=0 无溢出 71xx 0F81xxxxxxxx JB/JC/JNAE CF=1 低于/进位/不高于等于 72xx 0F82xxxxxxxx JAE/JNB/JNC CF=0 高于等于/不低于/无进位 73xx 0F83xxxxxxxx JE/JZ ZF=1 相等/等于零 74xx 0F84xxxxxxxx JNE/JNZ ZF=0 不相等/不等于零 75xx 0F85xxxxxxxx JBE/JNA CF=1 或 ZF=1 低于等于/不高于 76xx 0F86xxxxxxxx JA/JNBE CF=0 且 ZF=0 高于/不低于等于 77xx 0F87xxxxxxxx JS SF=1 符号为负 78xx 0F88xxxxxxxx JNS SF=0 符号为正 79xx 0F89xxxxxxxx JP/JPE PF=1 “1”的个数为偶 7Axx 0F8Axxxxxxxx JNP/JPO PF=0 “1”的个数为奇 7Bxx 0F8Bxxxxxxxx JL/JNGE SF≠OF 小于/不大于等于 7Cxx 0F8Cxxxxxxxx JGE/JNL SF=OF 大于等于/不小于 7Dxx 0F8Dxxxxxxxx JLE/JNG ZF≠OF 或 ZF=1 小于等于/不大于 7Exx 0F8Exxxxxxxx JG/JNLE SF=OF 且 ZF=0 大于/不小于等于 7Fxx 0F8Fxxxxxxxx 两个因素制约转移指令机器码,一是表 2-2 中列出 的转移类型,另一个是转移的位移量。 1)短转移指令机器码计算实例 例如,代码段有一条无条件转移指令如下: … :401000 jmp 401005 … :00401000 EB :00401001 03 :00401002 … :00401003 … :00401004 … :00401005 33 :00401006 c0 … 图 2.6 短转移举例 jmp 指令 位移量=3 xor 指令 ・42・ 加密与解密 :401005 xor eax,eax … 无条件短转移的机器码形式为 EBxx,其中 EB00~EB7F 是向后转移,EB80~EBFF 是向前转移。图 2.6 表示了该转移指令的机器语言,以及用位移 EB + 位移量 量来表示转向地址的方法。由图可见,位移量为 0x3, CPU 执 行完 jmp 401005” “ 指令后的 EIP 值为 0x401002, 然后执行:(EIP) 机器码 E B 0 3 ← (EIP)+ 位移量,执行后就跳转到 401005 地址处。即: jmp “ 图 2.7 机器码组合形式 401005”指令机器码形式是 EB 03(图 2.7) 。 也就是说转移指令机器码形式是: 位移量 = 目的地址-起始地址-跳转指令本身的长度 转移指令机器码 =“转移类别机器码”+“位移量” 2)长转移指令机器码计算实例 :00401000 E9 例如,代码段有一条无条件转移指令如下: 93 … jmp 指令 13 :401000 jmp 402398 00001393 00 (双字存储) … 00 :402398 xor eax,eax … … 无条件长转移指令的机器码是 5 个字节,机 位移量=1393 器码是 E9。 根据上面公式, 此例转移的位移量为: 0x00402398-0x00401000-0x5=0x00001393 :00402398 33 xor 指令 由图 2.8 可见,0x00001393 在内存中以双字 c0 :00402399 存储(32 位) ,存放时,低位字节存入低地址, … 高位字节存入高地址, 也就是说 00 00 13 93 以相 图 2.8 长转移举例 反的次序存入,形成了 93 13 00 00 存储形式。 转移指令机器码 =“转移类别机器码” + “位移量”=“E9”+“93 13 00 00” = E9 93 13 00 00 上面两个实例演示转移指令向后转移(由低地址到高地址) ,如果是向前转移(由高 低地址到低地址) ,计算方法一样: 例如,代码段有一条无条件转移指令向前转移,如下: … :401000 xor eax,eax … :402398 jmp 401000 … 位移量=0x401000-0x402398-5=0xFFFFEC63(取后 32 位) 转移机器码=“E9”+“63 EC FF FF”= E9 63 EC FF FF 实 际操作时可以使用相关的机器码计算工具,如 图 2.9 机器码计算工具 ・43・ 加密与解密 oPcodeR(光盘\tools\ code\ Op-Code) ,这款工具可以计算出各种转移的机器码(图 2.9) 。 2.2.2 条件设置指令 条件设置指令的形式是:SETcc r/m8 (r/m8——表示 8 位寄存器) 条件设置指令根据处理器定义的 16 种条件 cc,设置所指定的寄存器。在当条件满足时, 目标操作数会被置 1,否则置 0。这 16 种条件与条件转移指令 Jcc 中的条件是一样的,如表 2-3 所示。 表 2-3 条件设置指令 机器码 伪码指令 目标置 1 时的意义 标志位 0F 90 SETO r/m8 溢出 OF=1 0F 91 SETNO r/m8 未溢出 OF=0 0F 92 SETC/SETB/SETNAE r/m8 进位/低于/不高于等于 CF=1 0F 93 SETNC/SETAE/SETNB 无进位/高于等于/不低于 CF=0 0F 94 SETE/SETZ r/m8 相等/等于零 ZF=1 0F 95 SETNE/SETNZ 不相等/不等于零 ZF=0 0F 96 SETBE/SETNA r/m8 低于等于/不高于 CF=1 或 ZF=1 0F 97 SETNBE/SETA r/m8 不低于等于/高于 CF=0 且 ZF=0 0F 98 SETS r/m8 符号为负 SF=1 0F 99 SETNS r/m8 符号为正 SF=0 0F 9A SETP/SETPE r/m8 “1”的个数为偶 PF=1 0F 9B SETNP/SETPO r/m8 “1”的个数为奇 PF=0 0F 9C SETL/SETNGE r/m8 小于/不大于等于 SF≠OF 0F 9D SETGE/SETNL r/m8 大于等于/不小于 SF=OF 0F 9E SETLE/SETNG r/m8 小于等于/不大于 ZF=1 或 SF≠OF 0F 9F SETG/SETNLE 大于/不小于等于 SF=OF 且 ZF=0 r/m8 r/m8 r/m8 条件设置指令可以用来消除程序中的转移指令。 C 语言里, 在 常会见到执行以下功能的 语句: x = (a < b)? c1:c2; 如用汇编来实现,其代码如下: cmp a, b jge L1 mov ebx, c1 jmp L2 mov ebx, L1: c2 L2: 下面将使用条件设置指令重新编码: ・44・ 加密与解密 xor ebx,ebx cmp a, b setge bl dec ebx and ebx,(c1-c2) add ebx,c2 ; a≥b,则 bl 置 1;否则置 0 也可用条件传输指令 cmov, fcmov 去除程序中的转移指令, 但是它们仅被 Pentium Pro 以后的处理器支持。实现同样功能的代码如下: mov ebx, c2 cmp a, b cmovl ebx, c1 2.2.3 指令修改技巧 在软件分析过程中,为了优化原程序或在一定空间里增添代码,这就需要一定的指令 修改技巧。表 2-4 列出了常用指令修改技巧,在以后的实践操作中经常用到。 表 2-4 常用指令修改技巧 功 能 指 令 机器码 指令长度(bytes) 1 58 1 inc eax 40 1 48 1 eb00 2 mov eax, 00000000h B8 00 00 00 00 5 push 0 6A 00 2 pop eax 58 1 sub eax, eax/ xor eax, eax 2B C0 /33 C0 2 cmp eax, 00000000h 83 F8 00 3 je _label_ 74xx/0F84xxxxxxxx 2/6 or eax, eax/ test eax, eax 0B C0 /85 C0 2 je _label_ 74xx/0F84xxxxxxxx 2/6 xchg eax, ecx 91 1 jecxz _label_ 测试寄存器是否为 50 jmp xx 测试寄存器是否为零 1 dec eax 寄存器清零 90 pop eax 替换 2 个字节 nop push eax 替换 1 个字节 E3xx 2 cmp eax, 0ffffffffh 83 F8 FF 3 je _label_ 74xx/0F84xxxxxxxx 2/6 ・45・ 加密与解密 74xx/0F84xxxxxxxx 2/6 48 1 B8 FF FF FF FF 5 xor eax, eax/ sub eax, eax 33 C0 /2B C0 2 dec eax 48 1 stc F9 1 sbb eax, eax 2B C0 2 jmp _label_ EBxx/E9xxxxxxxx 2/5 push _label_ 68 xx xx xx xx 5 ret jmp 指令 1 mov eax, 0ffffffffh 0FFFFFFFFh 40 dec eax 置寄存器为 inc eax je _label_ 0FFFFFFFFh C3 1 很多指令针对 eax 作了优化,要尽可能多地使用 eax。例如:xchg eax, ecx 只要 1 字节, 而用其它寄存器则是 2 字节。 2.2.4 浮点指令 一般的汇编书里对浮点指令介绍的很少,因此本节简单地介绍一下浮点数。 1.浮点数据格式 在计算机中表达实数要采用浮点数格式,Intel 80x87 遵循 IEEE 浮点格式标准。IEEE 浮点数的格式由符号位 S、指数部分 E 和尾数部分 M 三部分组成(图 2.10) 。 D31 D30 符号 指数 有效数字 浮点数据格式 符号 D23 D22 8 位指数 D0 23 位有效数字 单精度浮点数据格式 图 2.10 浮点数据格式 符号(Sign) :表示数据的正负,在最高有效位。负数的符号 1,正数的符号 0。 指数(Exponent) :或称阶码,表示数据以 2 为底的幂。指数采用偏移码表示,恒为 整数。例如单精度格式 8 位指数的偏移基数为 127,除去全 0、全 1 两个编码外,其 余的 1~254 编码表示阶码数值 -126 ~ +127。 有效数字(Significand) :表示数据的有效数字,反映数据的精度。 单精度浮点数据格式是 32 位,符号位 1 位,E=8 位,M=23 位。 扩展单精度格式,符号位 1 位,E>=11 位,M31 位。 双精度浮点数据格式是 64 位,符号位 1 位,E=11 位,M=52 位。 扩展精度浮点数据格式是 80 位,符号位 1 位,E>=15 位,M>63 位。 1)把浮点格式数据转换成实数表达式 设单精度浮点数的符号位是 s,指数是 e,有效数字是 x。则该浮点数的值用十进制表 示为:= (-1) s× (1 + x) ×2(e - 127) 例:49E48E68 h=0100 1001 1110 0100 1000 1110 0110 1000 b ・46・ 加密与解密 将它分成符号、指数和有效数字 3 部分: 49E48E68 h=0 100 1001 1 110 0100 1000 1110 0110 1000 b 符号位是 0,即 s = 0 指数部分是 100 1001 1,读成十进制就是 147,即 e = 147。 有效数字部分是 110 0100 1000 1110 0110 1000,也就是二进制的纯小数 0.110 0100 1000 1110 0110 1000 , 其 十 进 制 形 式 为 0.78559589385986328125 , 即 x = 0.78559589385986328125。 这样,该浮点数的十进制表示 = (-1) s× (1 + x) ×2(e - 127) = (-1)0 ×(1+ 0.78559589385986328125) × 2 (147-127) = 1872333 2)把实数转换成浮点格式 6 例:100.25 = 0110 0100.01 b = 1.10010001 b×2 符号位 = 0 指数部分是 = 127+6= 133 = 10000101 b 有效数字部分 = 10010001000000000000000 b 这样 100.25 表示成单精度浮点数为: 0 10000101 10010001000000000000000 b = 42C88000 h 2.浮点寄存器 组成浮点执行环境的寄存器主要是 8 个通用数据寄存器和几个专用寄存器,它们是状 态寄存器、控制寄存器和标记寄存器等。 1)浮点数据寄存器 浮点处理单元有 8 个浮点数据寄存器(FPU) ,编号为 ST0ST7。每个浮点寄存器都 是 80 位,以扩展精度格式存储数据。 8 个浮点寄存器组成首尾相接的堆栈,不采用随机 存取,而是按照“后进先出”的堆栈原则工作,并且首尾循环,所以浮点寄存器常被称为 浮点数据栈。 2)浮点状态寄存器 浮点状态寄存器表明 FPU 当前的各种操作状态,每条浮点指令都对它进行修改以反 映执行结果,其作用与整数处理单元的标志寄存器 EFLAGS 相当,如图 2.11 所示。 15 14 13~11 B C3 TOP 10 9 8 7 6 5 4 3 2 1 0 C2 C1 C0 ES SF PE UE OE ZE DE IE 图 2.11 浮点状态寄存器 C3~C0 是 4 位条件码标志,其中 C1 表示数据寄存器栈出现上溢或下溢;C3/C2/C0 是保存浮点比较指令的结果。 3.浮点操作 在这介绍几个常用的浮点指令,其它指令参考附录。 ・47・ 加密与解密 1)取数指令 取数指令从存储器或浮点数据寄存器取得数据,压入寄存器栈顶 st(0)。 “压栈”的操 作是使栈顶指针减 1,数据进入新的栈顶 st(0),原来的 st(0)成为现在的 st(1)、原 st(1)为现 在的 st(2)…。数据进入寄存器栈前由浮点处理单元自动转换成扩展精度浮点数。 FLD m32r/m64r/m80r/ st(i) :取存储器或 st(i)中的浮点数,压入栈顶 st(0) FILD m16i/m32i/m64i :取存储器的整数,压入栈顶 st(0) 2)存数指令 该组指令除执行存数功能外,还要弹出(pop)栈顶。出栈的操作是将栈顶 st(0)清空, 并使栈顶指针加 1,使原来的 st(1)成为现在的 st(0)、原 st(2)为现在的 st(1)……。出栈指令 的助记符都是用 P 结尾。 FSTP m32r/m64r/m80r/ st(i) :st(0)按浮点格式存入存储器或 st(i),然后出栈 FISTP m16i/m32i/m64i :st(0)按整数格式存入存储器,然后出栈 3)比较指令 浮点比较指令比较栈顶数据与指顶的源操作数,比较结果通过浮点状态寄存器反映, 见表 2-5。 表 2-5 比较指令的结果 比较结果 C3 C2 C0 st(0) > 源操作数 0 0 0 st(0)< 源操作数 0 0 1 st(0) = 源操作数 1 0 0 不可排序 1 1 1 FCOM :浮点数比较,st(0)和 st(1)比较 FCOMP :浮点数比较,st(0)和 st(1)比较,并出栈 FICOM m16i/m32i :整数比较,st(0)与 m16i/m32i 比较 在比较结果中, “不可排序”是指两个浮点格式数不能按照相对值进行比较排序的一 种关系。由于浮点指令没有转移指令,所以需将比较结果 C3/C2/C0 转换到整数状态寄存 器中,然后利用整数转移指令进行跳转。具体过程如下: 首先用“FSTSW AX”指令将浮点状态字存入整数寄存器 AX; 再通过“SAHF”指令将条件码转送到整数状态寄存器的低 4 位。SAHF 指令将包含 C3/C2/C0 的高字节送入整数状态字的低字节,正好对应 ZF/PF/CF; 最后利用 jxx 指令进行条件判断转移(用无符号条件转移指令) 。 汇编形式如下: FCOM ; 比较指令 FSTSW AX ; 浮点状态寄存器送 AX SAHF ; AX 高字节转送到整数标志寄存器的低字节 JB … ; 小于,转移 ・48・ 加密与解密 2.3 逆向分析技术 逆向分析技术是指通过分析反汇编代码,理解其代码功能,如各接口的数据结构等, 然后用高级语言重新描述这段代码,逆向推出原软件的思路。这是一个很重要的技能,需 要扎实的编程功底和汇编知识。这方面能力培养是自己用高级语言写些程序,然后反汇编 对着分析。 逆向分析的一个好习惯是给代码加注释,对某段代码功能、局部变量、全局变量、输 入参数和返回值做些简要说明,即可理清自己的思路,也方便与他人交流共同研究。 2.3.1 函数 程序都是由不同功能的函数 (子程序) 组成, 因此将分析的重点放在函数上是明智的, 这样可以将注意力集中在某一段代码上。一个函数有如下几部分:函数名、入口参数、返 回值、函数功能。函数名很重要因为许多高级语言编译的程序可以从其函数名理解它的 功能。参数的功能确定是有一定的难度的,这要分析清楚参数接口的数据结构,还要结合 函数代码来实现。 1. 调用约定 高级语言中,子程序(函数、过程,或类似概念的东西)依赖于堆栈来传递的。也就 是说,调用者把要传递给子程序的参数压入堆栈,子程序从堆栈取出相应的值再使用,调 用者或者被调用者必须有一方把堆栈指针修正到调用前的状态。调用约定,是指调用例程 时参数的传递顺序和约定平衡堆栈的程序。常用的调用约定见表 2-6。 表 2-6 调用约定 约定类型 命名约定 参数传递顺序 C SysCall StdCall Pascal Basic Fortran 名字前加下划线 名字大写 名字大写 名字大写 从右到左 从右到左 从左到右 从左到右 从左到右 是 是 名字前加下划线 从右到左 调用者平衡堆栈 是 充许使用 VARARG 是 注:VARARG 表示参数的个数可以是不确定的, StdCall 如果使用 VARARG 参数类型 ,是调用程序平衡堆栈,否则是被调 用程序平衡堆栈。 Stdcall 调用约定是 Win32 API 函数采用的约定方式,它是“标准调用(Standard CALL) ”之意,它采用 C 调用约定的入栈顺序和 Pascal 调用约定的调整栈指针方式,即 函数入口参数按从右到左的顺序入栈,并由被调用的函数在返回前清理传送参数的内存 栈。在 Win32 API 中,惟一一个特殊的 API 是 wsprintf,它是 C 约定的,需要调用者 平衡堆栈。 例如:调用函数 test1(Par1, Par2, Par3: integer),按 C、Pascal、Stdcall 三种调用约 定,汇编代码如下: ・49・ 加密与解密 C 调用约定 Pascal 调用约定 Stdcall 调用约定 push par3 ;参数按右到左传递 push par1 ;参数按左到右传递 push par3 ;参数按右到左传递 push par2 push par2 push par2 push par1 push par3 push par1 call test1 call test1;函数内平衡堆栈 call test1;函数内平衡堆栈 add esp 0C ;平衡堆栈 test1 函数返回值放在 eax 中。 2. 参数传递与局部变量 无论 C、C++、BASIC、Pascal 等高级语言的函数(子程序)执行过程基本都是一致 的: 1) 调用者将函数(子程序)执行完成时应返回的地址、参数压入堆栈; 2) 子程序使用 EBP 指针+偏移量对堆栈中的参数寻址,并取出、完成操作; 3) 子程序使用 RET 或 RETF 指令返回。 此时, CPU 将 EIP 置为堆栈中保存的地址, 并继续予以执行。 堆栈在整个过程中发挥着非常重要的作用。堆栈是一个“先进后出”的区域,堆栈只 有一个出口, 即当前栈顶, 堆栈操作的对象只能是字操作数 =4 个字节) 例如: Stdcall ( 。 按 约定调用函数 test2(Par1, Par2)(有 2 个参数) ,其堆栈建立情况如下: push ebp ; 保护现场原先的 EBP 指针 mov ebp, esp ; 设置新的 EBP 指针,指向栈顶 sub esp, xxxx ; 在堆栈中留出点空间放局部变量 … add esp, xxxx ; 释放局部变量占用的堆栈 pop ebp ; 恢复现场的 ebp 指针 ret 8 ; 返回(相当于 ret; add esp,8)(ret 后的值是参数个数×0x4) 此外,还有一组指令,即 ENTER 和 LEAVE,它们可以帮助进行堆栈的维护。Enter 语 句的作用就是 push ebp/mov ebp,esp/sub esp,xxx,Leave 则完成 add esp,xxx/pop ebp 的功 能。所以上面的程序可以改成: enter xxxx,0 ; 0 表示创建 xxxx 空间放局部变量 … leave ; 恢复现场 ret 8 ; 返回(0x4×0x2 = 0x8) 再来分析一下函数如何传递参数, 因为 ESP 是堆栈指针, 所以一般使用 EBP 来存取堆 栈(图 2.12) 。 ・50・ 加密与解密 … 起始堆栈 K-04h 参数 2 EBP+Ch K-08h 参数 1 EBP+8h K-0Ch 返回地址 EBP+4h K K-10h 保存的 EBP EBP+0h K-14h 局部变量 1 EBP-4h K-18h 局部变量 2 EBP-8h ESP … 图 2.12 当前 ESP 指针 函数堆栈创建图 1) 此例函数中有两个参数,假设执行函数前堆栈指针的 ESP 为 K; 2) 根据 Stdcall 调用约定,先将参数 Par2 压进栈,此时 ESP 为 K-04h; 3) 再将 Par1 压进栈,此时 ESP 为 K-8h; 4) 参数进栈结束后, 程序开始执行 call 指令, call 指令把返回地址压入堆栈, 这时候 ESP 为 K-0Ch; 5) 这 时已经在子程序中了,可以开始使用 EBP 来 存取参数了,但为了在返回时恢复 EBP 的值,用“push ebp”保存 EBP 的值,这时 ESP 为 K-10h; 6) 再执行一句 “mov ebp,esp” ,这时候 [ebp + 8] 就是参数 1,[ebp + c]就是参数 2; 7) sub esp, 8, 在堆栈中定义局部变量, 局部变量 1、 对应的地址分别是 [ebp-4]、 2 [ebp-8]。 局部变量的范围从它的定义到它定义所在的代码块的结束为止,也就是说当函数调用 结束后局部变量便消失。 处理完毕后,就可以开始用 ebp 存取参数和局部变量了。 例如 MessageBoxA 函数参数内部情况,其函数原型是: int MessageBoxA(HWND hWnd,LPCTSTR lpText, LPCTSTR lpCaption,UINT uType)。 不同版本的操作系统下,MessageBoxA 函数内部结构的大致框架如下: Windows 98 第二版 Windows 2000 Service Pack 2 Exported fn(): MessageBoxA - Ord:01B7h Exported fn(): MessageBoxA - Ord:01C4h :BFF541BA push ebp ; ebp 入栈 :77E175D5 push ebp ; ebp 入栈 :BFF541BB mov ebp, esp ; ebp 指向栈顶 :77E175D6 mov ebp, esp ; ebp 指向栈顶 …… …… :BFF541CB call MessageBoxExA :77E175F4 call MessageBoxExA :BFF541D0 pop ebp ; ebp 出栈 :77E175F9 leave ; 释放堆栈框架 :BFF541D1 ret 0010 ; 清理栈中参数 :77E175FA ret 0010 ; 清理栈中参数 3. 函数返回值 如果函数有返回值,它的值放在 eax 或在某个参数里返回。逆向分析中需要了解这个 返回值与参数的关系。例如下面这段 Pascal 程序: ・51・ 加密与解密 function MyAdd1(x, y :integer) : integer; // 函数声明 var erg: integer; // 局部变量 begin erg := x + y; // 计算 myadd1 := erg; // 返回值 end; 这是一个普通的函数,将两个整数相加。这个函数有两个参数,并用了一个局部变量临 时保存结果。其汇编实现代码如下: Pascal 主程序 MyAdd1 函数 push ebx ; 参数 1 push ebp ; 保存 ebp push ecx ; 参数 2 mov ebp, esp ; 设置新的 EBP 指针 call MyAdd ; 调用函数 sub esp, 4 ; 局部变量空间 ; 堆栈在 MyAdd 函数里平衡 mov ebx, [ebp+0C] ; 取得第一个参数 mov ..., eax ; 返回值在 eax 中 mov ecx, [ebp+8] ; 取得第二个参数 add ebx, ecx ; 相加 mov [ebp-4], ebx ; 将结果放到局部变量中 mov eax, [ebp-4] ; 将局部变量指针返回到 eax leave ; 恢复现场 add esp, 8 ; 平衡堆栈 ret Pascal 的过程和函数不同点在于: 函数能返回计算结果, 即有一个返回值, 而过程没有。 不过实际上函数和过程差别不大,因为可以调用函数完成一系列操作,跳过其返回值。也可 以通过过程的参数传递计算结果,具体如下: procedure MyAdd2(var res:integer; x, y :integer) // 声明过程 begin res := x + y; // 计算 end; // 没有返回值 第一个参数 res 可以参与运算,但结果不保留,过程的返回值通过 res 参数返回。 Pascal 主程序 MyAdd2 函数 push ebx ; res 参数入栈 push ebp ; 保存 ebp push edx ; x 参数 mov ebp, esp ; 设置新的 EBP 指针 push ecx ; y 参数 call MyAdd2 ; 调用函数 mov eax, [ebp+0c] ; 取得第二个参数 x mov ..., res ; 结果在 res 中返回 mov ecx, [ebp+08] ; 取得第二个参数 y ; 无局部变量 add eax, ecx ; 相加 mov ebx, [ebp+10] ; 将参数 res 地址放进 ebx ・52・ 加密与解密 mov [ebx], eax ; 将结果放进 res leave add esp, 0C ; 平衡堆栈 ret 2.3.2 循环 识别循环是一件困难的事,这需要经验的积累。如确定某段代码是循环,就可分析其 计数器,一般是用 ecx 寄存器做计数器,也有用其它方法来控制循环的,如 test eax, eax 等。下面是一段最简单的循环代码: xor ecx, ecx ; ecx 清零 :00440000 inc ecx ; 计数 cmp ecx, 05 ; 循环 4 次 jbe 00440000 ; 重复 … 上面汇编代码,用高级语言 C 来描述有以下三种形式: 方案一 while (i < 5) { 方案二 for (i=0; i<5; i++) { i=0 repeat still to come } 方案三 … i++; } … until (i >= 5 ) 再来看一段较复杂的循环,如这段 C 程序: for (i = 0; i < 1024; i++) { …… }; 其汇编代码如下: ... mov dword ptr [ebp-164h], 0 ; 初始化[ebp-164h] jmp short loc_41453D ; 从 loc_41453D 处开始 mov ecx, [ebp-164h] ; 将[EBP-164h] 指向的数据放进 ecx inc ecx ; 加1 mov [ebp-164h], ecx ; 保存在[EBP-164h]里 loc_41452E: ・53・ 加密与解密 loc_41453D: cmp dword ptr [ebp-164h], 400h ; [ebp-164h]>1024 ?(注意 10 进制与 16 进制) jnb short loc_414567 ; 如果大于,跳转 short loc_41452E ; 返回,循环 ... jmp loc_414567: ... 程序在这里是利用[EBP-164h]作为计数器, 2.3.3 控制语句 1. If Then Else 语句 高级语言的“If Then Else”语句在汇编中形式比较简单,关键是掌握各转移指令的含 义。例如: cmp byte ptr [00531640], bl ; 比较 00531640 指向的字节与 bl 是否相同 jne 0045FE49 2. Case 语句 多分支语句,在汇编代码下也有多种形式,不像高级语言那样容易理解,例如: 反汇编代码 mov eax, edi // 此时 eax=k sub eax, 00000002 // eax=k-0x2 高级语句 switch(k) { je 00401165 case '0x2 ':……;break; sub eax, 0000000E // 比较(0xE=k-0x2) case '0x10':……;break; // k=0xE+0x2=0x10 jne 0040114E …… …… default :……; } 2.3.4 全局变量 全局变量作用域整个程序,一直是存在的,它放在全局变量的内存区;而局部变量则 是存在于函数的堆栈区,当函数调用结束后便消失。在大多数程序,常数一般放在全局变 量中,如一些注册版标记、测试版标记等。 程序中存取全局变量有如下几种方式: 1) 直接模式 mov eax, dword prt [00543380]:直接调用全局变量。 2) 间接模式 ・54・ 加密与解密 mov eax, [esi + 4EC] | | 基址 偏移量 在逆向分析中,注释很重要的,假设 4EC 是一个注册标记,可以这样注释: mov eax, [esi + flg_Regged_ 4EC] 如果此时在程序中搜索偏移量 4EC,可能会在程序其它处多次调用它,如: mov eax, dword ptr [esi+flg_Regged_000004EC] cmp eax, ebx lea edi, dword ptr [esi+flg_Regged_000004EC] 2.3.5 字串初始化 当看到如下一段汇编代码,就知它在初始化了: mov [esp+3Ch+var_A], 4 ; 用 ESP+X 相对寻址 mov [esp+3Ch+var_9], 6 mov [esp+3Ch+var_7], dl mov [esp+3Ch+var_6], 10h mov [esp+3Ch+var_3], 4 mov [esp+3Ch+var_2], 10h 这些初始化的字符,可能是一些计算用的数据表,也可能是一段字符串。 ...
View Full Document

Ask a homework question - tutors are online