首页
社区
课程
招聘
[翻译]Reverse Engineering Code with IDA Pro(第一、二章)
发表于: 2008-6-4 11:07 21258

[翻译]Reverse Engineering Code with IDA Pro(第一、二章)

2008-6-4 11:07
21258

因为对逆向不熟,来这儿正好看到arhat组织翻译这本书,因为以为arhat还在考虑找哪些人翻译,就在有空的时候开始翻译(希望下次arhat也顺便通知一下没入选的 )。
虽然翻译组开始了他们的工作,不过我的初译已经完成,根据arhat的建议,也放上来有兴趣的人看一下。第一次翻译,且对逆向不熟,所以见谅了。
没完成的工作:
1。对自己不能确定的译句,在注释中列出原文,并给出自己这么译的原因。
2。原书中的图
3。对原文的意思扩展。因为本书是针对初学者,所以有些话我觉得值得详细解释一下(老鸟无视)。

Reverse Engineering Code With IDA Pro
第一章
1.引言
信息安全专家的舞台在最近这些年戏剧性地发生了变化。我们的任务不再是防范好奇的年青人对关键的组织资产的不受欢迎的探查;我们,作为一个社区,现在要面对的是抵御渗透攻击,这此攻击以商业或地缘政治上的利益为目的,由有组织化或以国家为背景的罪犯发起,因而残酷、技术上复杂。
程序和协议中安全漏洞的盛行,Internet尺度和复杂性的持续增长,四处存储的信息的敏感性,给我们的下一代对手创造了丰富的目标环境。这些罪恶群体正在使用改善了的、多种形式的软件(这些软件被特意设计成能避开IDS,IPS及AV检测引擎的检测),提供完整的对受害者计算机远程控制和窃听功能。为了理解和预测这些恶意软件程序的影响,几种可有效使用的技巧之一就是借助于像Data Rescue及Zynamics这种公司的工业级工具进行的高级逆向工程。
本书展现逆向工程界的前沿技术。本书作者在他们各自领域都很突出,我相信你和你的公司都能在此发现大量信息,能帮助你对前瞻性的计算机安全前沿技术有所准备。
非常感谢Lauren Vogt,Ted lpsen,Dan Kaminsky,Jason Larsen,Walter Pearce,Justin Ferguson,Luis Miras和Syngress公司的其它员工,是他们让本书的出版成为可能。
Joshua J. Pennell
IOActive,综合信息安全服务公司 创始人及首席执行官
2.代码调试器概览
迟早你将想完全了解一个可执行文件的全部细节。比如,你也许会希望知道:
它调用的精确内存地址
它写入的精确内存区域
它正在读哪个区域
它正在利用哪个寄存器
通过反汇编,调试器将帮助你逆向一个你没有源代码的文件。这对你分析一个恶意软件很有用,因你几乎不可能得到它的源码。本节的内容不是深入教你如何使用调试器,而是简单的向你展示,调试器可以为你所用。调试器是非常有用的工具,需要花较长的时间学习使用它们的全部功能。
调试器中的佼佼者之一,也是本书的焦点--交互式反汇编器(IDA Pro),可以从DataRescue公司得到。IDA Pro应该是你在企业环境下的首选调试器。和它提供的特性相比,它不算贵,值得它那象征性的收费。
注:DataRescue在他们的网站 http://www.datarescue.com/idabase/index.html 提供了一个演示版本(译者注:此网页已失效,新地址为http://www.hex-rays.com/idapro/idadowndemo.htm)。这个版本有一些限制,如仅仅能工作在几种文件类型和处理器类型,有时间限制,且只能作为一个Windows GUI应用运行等。
IDA Pro远不止是一个简单的调试器。它是一个可编程的、交互式的反汇编器和调试器。用IDA Pro,你可以逆向现有的所有类型的可执行文件和应用程序。IDA Pro能处理各种计算机系统上的文件,比如手柄类机器如Xbox、Playstation、Nintendo到Macintosh计算机系统,到PDA平台,Windows、UNIX以及另外更多的系统。图1.1显示了当你首次运行IDA Pro时的启动画面。注意,所有的文件类型和TABS,对你反汇编的文件类型,这些选项能帮你进行合适分析。
图1.1 IDA Pro启动过程中的反汇编数据库选择项

在图1.2中,IDA Pro已经载入并且正在反汇编一个文件名为instantmsgrs.exe的WooBot变体。从图1.2中,我们能看到instanmsgrs.exe已经用一个叫作Molebox的可执行的加壳器加了壳。你也能清楚的看到它正在进行的内存调用和调用的Windows DLLs文件。这些信息对避免病毒或恶意软件的爆发非常宝贵,特别是当你为了修复你的系统需要编写一个定制的清除程序时。
总结
IDA是Windows下最流行的调试工具之一。首先,IDA Pro是一个反汇编器,这时,它显示二进制文件(一个可执行文件或者一个动态链接库[DLL])的汇编代码。它的一些高级特性努力使理解汇编代码变得更容易。其次,它也是一个调试器,这种情况下,它允许用户单步调试二进制文件,以确定被实际执行的指令和执行顺序。通过本书,你能学习所有的这些特性。IDA Pro在恶意软件分析、软件脆弱性研究及其它一些地方中被广泛应用。IDA Pro可以在 www.datarescue.com 网站购买。

第二章 汇编和逆向工程基础
本章解答 汇编和IA-32处理器
二进制文件的栈、堆及其它部分
IA-32指令集温习及参考
1.引言
本章我们将介绍基本的术语,对汇编和Intel体系结构提供一个简要的介绍,同时覆盖其它一些概念,帮助你理解相关主题。本书集中于32位的Intel平台上Windows和Linux操作系统下的汇编指令。读者至少应该熟悉IA-32汇编指令(虽然本章在一定程度上覆盖了体系结构和指令集的介绍),同时对C/C++有坚实的理解。本章主要是简单地给那些对本书主题不熟悉或有一点生疏的读者一个入手点,对那些作者会提及的、认为必要的知识点提供一个基本的参考点。
2.汇编与IA-32处理器
与计算机通信,汇编是一个有趣的方法;Donald Knuth曾经说过“科学是我们理解得足够好能够解释计算机,艺术是我们做的其它事。”对我来说,这个事实在汇编语言程序设计中是最普遍的,因为当你写汇编时,在很多地方,你都会发现你自己经常做一些像滥用指令之类的事,而这些指令在设计上并不是这样用(比如,把LEA指令用来做其它事,而不是用来做指针算法)。不过什么又是精确的汇编呢?汇编意指指令助词符,它和处理器的指令集有一个一对一的对应关系,也就是说,在你的代码和处理器之间没有更多的抽象层:所写即所得(尽管对于这点,在某些平台上可能有一些例外,它们的汇编器导出伪指令,并且把他们翻译成多个处理器指令--但先前的说法通常是正确的)。
比如,假设我们有下面的单行C代码:
return 0;
我们最终得到以下编译产生的汇编代码:
leave
xor eax, eax
ret
注意:
许多汇编器有不同的语法,上面的汇编代码语法(实际上是本书主要使用的汇编语法)是Intel语法。另一个在Unix世界流行、大量使用的语法是AT&T语法,它看上去有一点不同。
IDA选择Intel语法是因为它的反汇编器使用此语法,并且实际上Intel语法是一个更流行的语法。这意味着当你使用Intel语法时,会有更多的书籍、白皮书和人群能解答你的问题。
AT&T语法和Intel语法的差别不在本书讨论范围之内,不过下面的例子可以让你知道它们之间看上去有什么不同:
Intel:
leave
xor eax, eax
ret
AT&T:
leave
xorl %eax, %eax
ret
注意,无论如何,AT&T语法仍然在许多场合使用,并且是Unix世界的标准语法(尽管在运行Unix和类Unix操作系统的基于IA-32的计算机上,这正在慢慢发生变化)。因此,花一点额外的时间学习一下AT&T语法并不是坏事,尤其是你打算在不同的Unix或其它平台上做很多工作时。
如果你现在不能完全确认这些意味着什么,那也不必担心;我只是希望让你对汇编不再陌生。只要知道先前的是汇编代码,它代表C语言中的return 0就好。但这不是处理器看到的,汇编是人类可读代码的最后一层。当你的编译器编译、汇编代码时,它输出我们所熟悉的操作码,这是指令的二进制表示,或者说对于执行单个指令所必须的开关序列。操作码通常用16进制表示,因为比起二进制,16进制更易读。先前指令的操作码为:
0xC9                (leave)
0x31, 0xc0        (xor eax,eax)
0xC9                  (ret)
如我们所见,这里有三个基本的抽象层,随着计算技术的提高,我们增加了越来越多的抽象层,如Java和  .NET 应用程序使用的虚拟机。但最终,所有的东西都是汇编,是发送到处理器的1和0的序列。对操作码更完整的讨论请见 Intel 64 and IA-32 Architechures Software Developer's Manual 卷 2A, 第 2.1.2节.
工具与陷井
几乎每一个人在走进神奇的数字世界的过程中,都会遇到一个术语shellcode,它使我们感到恐慌。我们看到这些神秘的十六进制字符数组,不能确定它们是什么意思。任何测试过exploit的人都可能遇到过它,即使你很早就开始你的职业生涯,你可能仍不能完全理解它。
不过放心吧,这些认为它很神秘的想法是过于复杂了。Shellcode仅仅是一个操作码序列,通常存储于C字符数组。术语shellcode起源于一个事实:这些操作码序列实际上是执行一个shell的指令,如/bin/sh、cmd.exe。在这里,如果我们考虑我们先前的例子:
return 0;
对于这个C语言指令的shellcode,我们能简单地用操作码表示这个指令,也就是 0xC9,0x31,0xC0,0xC9;假如我们把它放到C程序里,它看上去可能是这样的:
unsigned char shellcode [] = "\xc9\x31\xc0\xc9";
现在你知道了这些,你可能会感觉把shellcode看得比它实际上更复杂显得有一些愚蠢,不过你不必这么想。我觉得每一个人在他们前进的道路上都会有这样的过程--至少我是这样的。
那现在我们对汇编指令是什么有了一些了解,但如何使用它们?一个带参数(也就是通常所说的操作数)的指令根据指令的不同,参数可以是一个常量、内存变量或者寄存器。常量很简单:它们在源代码中静态定义。比如,一个代码片段使用如下指令:
mov eax, 0x1234
那么16进制数0x1234就是常量。常量是相当直观的,对它们,只要知道是常数,不会变化,通常直接编码进指令中就可以了。然而,这儿有一个有趣的话题,如果考虑我们先前提到的在C语言中返回0的那个例子,机敏的读者可能会注意到被编译器产生的汇编指令没有包含一个常量,虽然在源代码中包含一个。那是编译器优化的结果,编译器认识到拷贝操作比执行一个异或操作大得多。
下面,我们说说寄存器。寄存器有点像C/C++中的变量。通用寄存器可以包含整数,偏移量,中间值,指针或其它任何能用32位比特表示出来的事物。基本上,它是一个预分配的变量,物理上它存在于处理器中,对处理器,它总是可见的。它们与变量相比,还是有一点点细微的差别,那就是它们总是被一遍又一遍的重复使用,而在C 或者C++程序中,变量一般是为一个单一的目的使用的,从不会为了其它目的再次使用它。
在IA-32体系结构中,有8个32位的通用寄存器,6个16位的段寄存器,一个32位的指令指针寄存器,1个32位的状态寄存器,5个控制寄存器,3个内存管理寄存器,8个调试寄存器等等。多数情况下你只会用到通用寄存器、指针寄存器、段寄存器和状态寄存器。假如你和操作系统驱动等打交道,你很可能遇到其它寄存器。在这儿我们会涉及通用寄存器、指针寄存器、状态寄存器和段寄存器。至于其它寄存器,你知道它们存在就可以了,对好奇的读者,也鼓励你参考Intel文档查阅更多的细节。
8个32位通用寄存器是:EAX、EBX、ECX、EDX、ESI、EDI、EBP和ESP。这些寄存器可以被使用在你认为合适的地方,不过有几个值得注意的例外。比如,许多指令分配指定的寄存器给一定的参数(或者说,操作数)。作为一个具体的例子,许多字符串指令使用ECX作计数器,ESI作源指针、EDI作目的指针。此外,在确定的内存模型中,有些指令以指定的段作为默认基地址(这些在本书都会简短地谈到)。最后,其它一些寄存器的使用总是对应固某种类型的操作。比如,EBP和ESP寄存器在许多堆栈操作中被使用,它们的值包含的地址如果不在当前进程的地址空间中的话,常常会导致应用程序的崩溃。IA-32体系结构几乎是完全向后兼容8086处理器的,这在它们使用的寄存器中反映出来。所有的通用寄存器都能以取寄存器的全部32位或者低16位方式使用,EAX、EBX、ECX和EDX寄存器还能访问它们的高或者低8位。完成这些功能是通过使用在图2.1中的名字来反应的。比如,你可以在指令中使用AL,而不是EAX以实现访问EAX寄存器的低8位;访问EBP的低16位,你用BP代替EBP即可;访问EDX低16位的第二字节,你用DH代替EDX即可。除了这些通用寄存器,还有指针寄存器EIP。EIP指向要被处理器执行的下一个指令,言外之意,就是所有基于应用程序的攻击的目标都是控制这个寄存器。和通用寄存器不同,EIP不能被直接修改。显然你也就不能通过给EIP赋值的方法执行一个指令,你必须执行一系列能导致间接修改EIP的操作,比如入栈操作后的RET指令。如果你觉得最后一句话有一点奇怪,你不是十分理解,那也不必担心,上面提到的指令和堆栈段的细节都会在后面章节中覆盖。现在,只要知道,你不能直接修改指令指针的值。
除了EIP寄存器和通用寄存器,还有6个16位寄存器:代码寄存器(CS),数据寄存器(DS),栈寄存器(SS),附加寄存器(ES)及FS和GS寄存器,后两个寄存器是用于其它目的的寄存器。段寄存器是一个指向段选择子的指针,这些指针通常用作偏移量的基地址;比如,考虑下面的指令:
mov DS:[eax], ebx
        在上面指令中,EBX寄存器的内容被拷入数据段寄存器指定的内存,其偏移量由EAX指定。也可以把这说成DS的地址加上EAX的内容。段选择子是用来标识段的16位的标识符,也就是说:段选择子并不是直接指向段,段描述符定义段。因此,段寄存器指向段选择子,段选择子被用来标识段描述符(最多8192个)中的一个,而段描述符则用来标识段。还迷惑么?
        段选择子结构相对简单。(16位中的)第3至15位被用作指向描述符表项的索引(三个内存管理寄存器之一)。第2位精确指定是哪一个描述符表,最低两位指定请求优先级(环0至环3的优先级将在本章后面讨论)。段描述符,对操作系统设计是有趣且相当重要的,但本书并不覆盖此内容,以确保本书所述内容与本书主题相关。同样的,我们总是鼓励有兴趣的读者去参考Intel开发者手册。最后,在各种相关的寄存器中,我们还有EFLAGS寄存器,它包含一组不同的标识,以指出先前指令、状态等(如当前优先级、是否有开中断)的操作情况。在我们更好的掌握了那些使用它的指令前,EFLAGS寄存器对我们实际上意义不大,在本章后面将会有EFLAGS寄存器的完整描述。
我们已经讨论了常量和寄存器操作数,也是时候讨论内存操作数了。内存操作数要慎重一点对待,虽然在本书的这一阶段,对它们的描述是相当有限的。总体上,内存操作数就是高级语言程序员眼中的变量。也就是说,当你在一个像C或者C++这样的语言中声明一个变量时,它通常是存在于内存中,因此常常是一个内存操作数。最常见的是通过指针访问它们,对这些指针解引用时,或者会导致值被装入寄存器或者被直接从内存访问。概念本身是相当简单的,但真正的理解正在发生什么需要内存布局方面的更深入一点的知识,而内存布局又依赖于内存模型、所处的工作模式和优先级。这里对进入后面几章提供了一个很好的引导,在后面几章,会涉及工作模式。
在IA-32体系中,有三个基本的工作模式,一个伪模式。它们是保护模式、实地址模式、系统管理模式,伪模式是保护模式下面的一个子模式,被称作虚拟8086模式。在有趣的简述之后,只有保护模式将详细讨论。这几种工作模式的最大区别是它们改变了哪些指令和哪些系统特性可见。比如,实模式意味着向后兼容,在实模式,只有实地址模式的内存模型被支持。这儿需要注意的主要地方是(除非你正在倒退到古老的DOS或类似的应用),当你重启或给一台IA-32体系的PC上电时,它就自然地处于实模式。系统管理模式在Intel的80386之后出现,用于实现类似电源管理和系统硬件控制功能。基本上,这种模式下会停止所有其它的操作,并切换到一个新的地址空间。通常讲,几乎你所有的使用和遇到的情况都是在保护模式下的。
保护模式在80286处理器中引入并在80386中得到改进,它代表了Intel体系的一个巨大的提高。一个关键的问题是先前的处理器仅支持一种工作模式,没有内在的,在指令和内存级别上的硬件增强保护。这不仅让恶作剧的操作员做他们想做的任何事,而且使一个有缺陷的应用程序能够摧毁整个系统,因此这是一个影响系统稳定性和安全性的问题。先前的处理器的另一个问题是640KB限制;这是保护模式所解决的另一个问题。此外,保护模式还有其它优点,比如在硬件级支持的多任务和对中断处理的修改。实际上,286和386代表了个人计算机的重大进步。
在早些时候,比如在像8086/80186这样的处理器,或者今天,在现代处理器的实地址模式中,段寄存器代表了一个线性地址的高16位,而在保护模式,段选择子只是一个描述符表项的索引。更进一步,如前所述,早期CPU没有对内存的保护和对指令的限制;而在保护模式,有四个被称为环的优先级。这些环用0~3来表示范围,数字越小,优先级越高。环0通常被操作系统使用,环3一般是应用程序运行的地方。这保护了操作系统的数据结构和对象,以免被不可靠的或恶作剧的应用程序破坏,并限制了应用程序可以运行的指令(如果应用程序能切换自己的优先级有什么好处呢?)。在IA-32体系结构中,有三个地方可以看到优先级:CS寄存器的低2位是当前优先级(CPL),一个段描述符的低2位是描述符优先级(DPL),一个段选择子的低2位是请求者优先级(RPL)。CPL是当前执行代码的优先级,DPL是该描述符的优先级,明显的,RPL应该是创建那个段的代码的优先级。
优先级限制了对系统数据里更多可信组件的访问;比如,一个环3的应用程序不能访问一个环2下的数据。无论如何,环2下的组件是能访问环3部分的数据的。这就是Windows或者Linux内核能读取你的数据,你却不能随意从中读取数据的原因。优先级分离的另一个功能是它在执行控制转换时进行核查。执行从当前段到另一个段的转换请求会触发一个核查,确保CPL是和段的DPL一致的。执行的间接转换通过像调用门这样的事件发生(后面将会简单地介绍到此内容)。最后,优先级限制对某些指令的访问,这些指令或者从根本上改变操作系统的环境,或者执行那些通常为操作系统保留的操作(比如从一个串口读或写)。
保护模式有三种不同地内存模型:平坦型、分段型及实地址型。实地址模型用在系统启动时和为了保持向后兼容性;不过,你不大可能碰到这种情况,所以在本章我们不讨论它。平坦内存模型,如它的名字所示,它是平坦的(见图2.2)!这意味着基本上,系统内存以单一连续的地址空间出现,在许多实例中,地址从0到4294967296。这个地址空间被称为线性地址空间,里面的任何一个地址被称为线性地址。在这个内存模型中,所有的段--代码、堆栈、数据等等都在同一地址空间。不论你第一反应倾向于什么,这种模型几乎用于所有现代操作系统,并且很有可能你家里的计算机的操作系统正在使用这种模型。虽然看上去这种模型会导致灾难和不可靠;不过,它几乎都是同分页一起使用,这里我们简单讨论一下分页。
保护模式中的平坦内存模型的不同之处仅在于,段限制被设置成确保为只有那些实际存在的地址范围才能被访问。这和其它的模型有区别,其它模型的全部地址空间被使用,在地址空间中可能有未用内存的间隙。
另一个内存模型是分段内存模型。它被用于早期的操作系统,并且看上去现在又开始复出,因为在某些领域,它意味着快速(由于能跳过重定位操作)和安全。尽管如此,这个内存模型的应用仍然很稀有,它是否能完全复出仍然有待观察,虽然有点不大可能。在这里谈到它是因为假如你在Linux下做很多逆向工程或者exploit开发,你遇到它的可能性会显著增加。
在一个分段内在模型中,系统内存被划分为被称之为段的很多部分。(见图2.3)这些段相互独立、隔离。现在让我们比较一下分段和平坦模型,它们之间真正的区别是站在操作系统或应用程序的角度,它们的表示不同。在两种模型中,数据仍然以线性的形式被存储,但数据视角发生了改变。不同于使用一个线性地址访问数据,分段模型使用了逻辑地址(就是通常所说的远指针)。逻辑地址是一个地址组合,包括一个基地址和偏移量,基地址被存储在段选择子中。这两者一起对应段中的一个地址,这个地址反过来映射了一个线性地址空间。这种方式完成了一个更高程度的分段,这是被处理器强制的,以保证一个进程不会进入另一个进程的地址空间(鉴于此,也希望在一个平坦内存模型中,通过操作系统能达到同样的效果)。因而,在处理器的地址空间中,基地址加上偏移量等于一个线性地址。此外,还可以使用多段模型,且保留单一分段模型的特征,除非个应用都有自己的段集和段描述符。当前,作者不打算考虑使用此种模型的操作系统,因此没有对它如何工作的更多叙述。
图2.3 分段内存模型

破坏与防护
分段安全
如前面提到的,分段内存模型最近在不同社区得到一些发展,特别是grsecurity(http://grsecurity.com/)和Pax(http://pax.grsecurity.net/),这是第三方的Linux内核补丁,与纯正的内核比,它们提供更高的安全性。grsecurity的首席开发者,Brad Spengler,论证了平坦内存模型下会由产品导致不安全,这个论证使用的程序被认为是第一个可exploit的Linux内核 NULL指针解引用,详细情况可参考下述网址:http://marc.info/?1=dailydave&m=117294179528847&w=2。此外,PaX的匿名作者已经实现了一个被称为UDEREF的特性,此特性会试图停止在内核中由用户空间提供的指针的意外解引用(因此是一个潜在的可exploit的情况)。关于它如何工作已经被写成文档,鼓励有兴趣的读者简单地阅读一下相关报道以更好地理解带UDEREF修补的平坦内存模型安全上的含义。在本书写作的时候,可以在下述网址找到相关内容:http://grsecurity.net/~spender/uderef.txt。
作为现代操作系统常常使用比物理内存所能提供的更大的地址空间的一个结果,一些地址分配方法分配地址时并不要求将所有数据载入物理内存。完成这种地址分配的方法就是分页和虚拟内存,这是现代计算技术的一个基本原则,它常常被那些有面向操作背景的人误解。人们能理解在只有1GB或2GB的物理内存时,一个应用程序也能使用4GB的内存,但却又不真正知道这是如何工作的,这种情况并不鲜见。
简言之,分页利用了在任一时刻只有当前必须的数据需要被存储在物理内存中的事实;它存储必须的数据在物理内存中,其余的存储在磁盘上。从磁盘载入数据,或写数据回磁盘的过程被称为交换数据,这就是为什么Windows产品通常有交换文件,Linux产品通常有交换分区的原因。当分页功能没有启用时,线性地址(不论它是否由一个远指针组成)与物理地址一一对应,在它们之间没有转换,也没有生成地址的(译注:作者意思应该是这里面不会有一个线性地址到物理地址的生成过程)产生。不管怎样,当分页功能启用时,应用程序使用的所有指针是虚拟地址(这是为什么在保护模式的平坦内存模型下两个应用程序使用分页访问相同的地址并不会相互破坏的原因),这些虚拟地址同物理内存地址没有一一对应关系。
当使用分页后,处理器将物理内存分割成4KB,2MB或4MB的页。当一个地址被转换成线性地址,再通过分页机制被查询时,假如此地址当前并不存在于物理内存中,一个页错误异常将发生,指示操作系统把指定的页装入内存,然后重新执行刚才产生页错误的指令。
转换虚拟地址到物理地址的过程依据使用的页面大小而不同,但基本途径是一样的。一个线性地址被分割成两个或者三个部分。首先,页目录基寄存器(PDBR)或控制寄存器3(CR3)被用来定位页目录。在其中,线性空间中的位22到31被用来作为页目录中的偏移量,指示在页目录中应该使用哪个的页表。(见图2.4)一旦页表被定位,位12到21被用来定位页表项(PTE),页表项指出内存中被使用的页。最后线性地址中的位0到11用来定位请求数据在页中的偏移。使用别的分页过程也差不多,除了一个间接层被省略了;目录项直接指向页,页表和PTE完全被去除了。页目录项(PTE)的内容和PTE对我们而言并不重要。如果什么时候它对你变得重要了,或者你只是好奇,请参考处理器文档。
图2.4 4KB地址转换
既然我们对指令和操作数、内存模型、工作模式等有一个合适的了解,我们就可以继续前进了。本章后面和贯穿全书用到的许多术语已经被定义了,因此你在全书学习中如果觉得有些不太能理解的内容可以回到本节再研读一下。
3.二进制可执行文件的栈、堆及其它
在前面一节,我们说到段、段寄存器、段描述符和段选择子,但我们实际上没有探讨它们包含的内容。理解这些不同的部分对理解一个二进制文件的布局是相当重要的。本节我们将尽可能讨论这些概念,尽管在像堆等一些地方,如果不针对一个具体的实现,要深入了解它是如何工作几乎不可能;在这些地方,我们提供一个通用的高层次上的视角,详尽的细节将作为一个练习留给读者。
戒示:
读者要注意在这儿定义的区和段并不明确表示或暗指一个分段内存模型。在所有的内存模型中,应用程序和操作系统被分成不同的区;一个应用程序是非常乐意对实现细节不敏感的。此外,在此节,术语段和区可被互换使用。
早前我们讨论过段寄存器,特别是CS,DS和SS段寄存器,但我们没有告诉你代码、数据和堆栈是什么。在传统设计中,一个应用程序有几个不同的基本段(许多特定于实现)。最基本的是代码段(或者文本段或简单地写成 .text),数据段(常简写为 .data),符号块(BSS/ .BSS),栈段和堆段。作为一个例子,下面的C代码将有助于阐述这几个段的区别:
unsigned int variable_zero;
unsigned int variable_one = 0x1234;
int
main(void)
{
        void * variable_two = malloc (0x1234) ;
        [...]
}
在这个代码示例中,定义了三个变量和一个函数。第一个变量被命名为variable_zero,是一个未初始化的全局变量。在本例中,C编译器将在生成的二进制代码中为其分配空间并以0填充。在二进制代码中它存在于BSS段。变量variable_one是另一个全局变量,在这儿,它被初始化成0x1234。本例中,编译器将在二进制代码中为该变量预先分配空间并且将值存储在数据段中。再下面,可以看到函数main。Main明显是一个函数,因此存在于代码段中。在main后,我们看到命名为variable_two的变量,它给我带来一个有趣的窘境:我们有一个指针及此指针指向的一块内存,指针的作用域在main中。指针自身对于函数是本地变量,是动态分配且存在于栈上的,它的生命周期同它所在的函数一样,同时,malloc()函数返回的指针存在于堆上,是动态分配的,有一个全局作用域,有一个“使用直到用free()函数释放它”的生命期望。很多时候还有其它段,比如,当程序被GNU编译套件(GCC)编译时,在源文件中声明的只读的常量字符串最终常常在一个以 .rodata名字命令的段中,或者在代码段中。
栈是需要理解的一个更重要的段,它在应用程序的运行中扮演了一个关键角色。那些有正规计算机科学背景的人无疑都会知道一个栈是什么,它如何工作。栈是一种简单的数据结构,基本上,栈数据向上层叠,其元素以一种先进先出的方式被增加和删除。当你往栈中增加一个元素,你推它入栈,当你从栈中移除一个元素,你将它弹出栈。(见图2.5)在大多数的计算平台上,由于两个理由,栈是非常重要的。一是所有自动或本地变量存储在栈上;也就是说,当一个函数调用时,它声明的任何本地变量分配的空间都在栈上(除开static变量或其它类似的变量)。这通常通过增加或减少当前栈指针的值来实现。栈指针是ESP寄存器,它通常指向栈寄存器的顶部,或更准确地说,SS:ESP指向栈段的顶部。栈的顶部是当前在使用的栈地址的最低部分--最低是因为在IA-32体系中,栈是向下增长的。栈顶通常是边界,不过不是绝对边界,而是当前栈帧的边界,EBP指针指向这个地址。
一个栈帧是正在执行函数的栈的当前视图。当处理器进入一个新的过程时,几个步骤(也就是通常所说的过程序言)会发生。(见图2.6)。过程序言如下:例程首先将调用语句的下一指令的地址入栈;接着,当前栈基址(EBP)被存入栈;然后ESP寄存器内容被拷入EBP寄存器;最后,将ESP寄存器的值减,以便为那个函数中的变量分配空间。当函数被调用时,调用参数以反向顺序入栈(也就是说最后一个参数最先入栈)。与序言对应的汇编代码如下:
push ebp
mov ebp, esp
sub esp, 0x1234
图2.6 栈帧
有点奇怪的是这里我们没看到对返回地址、或是调用子程序后我们将继续执行地址的保存。比如,假设下面的C代码:
A( );
B( );
当在函数A( )中,返回地址将是指令B( )的地址。要理解它,还真有一点点棘手,特别是因为你不曾看到那些保存地址的指令,其实它是在调用指令中隐含了,在本章后面我们再讨论这个问题。除了过程序言,还有一个过程结尾。过程结尾基本上恢复过程序言做过的所有操作。这包括增栈指针,以释放分配的本地变量,调用leave和ret指令,以移除保存的帧指针和返回地址,并返回到调用函数的执行流。等等,对栈的总结如下:
  ■ IA-32体系下栈是向下增长,既往低地址增长
  ■ 栈中的元素是以后进先出的顺序被添加和移除
  ■ 生命周期限于的本地作用域的变量在栈中
  ■ 每个函数都有栈帧(除非被编译器特地省略了),栈帧里包含本地变量
  ■ 在每个函数的栈帧之前有一个存储的帧指针,返回地址和传递给子程序的参数
  ■ 栈帧在过程序言中建立,在过程结尾中被销毁
堆是另一个重要的数据结构,不是因为处理器有什么特性依赖于它,而是由于要大量使用它。堆其实就是一个内存区,用来动态分配需要存储于当前栈帧之外的变量;作为这个特性的一个结果,许多对象及应用中大量的数据存储于堆上。堆通常是随机映射的结果,或者,在更经典的例子中,它是一个数据段的动态扩展(尽管DS鲜有指向堆)。在这方面来说,处理器通常很无知,这些堆的细节对处理器隐藏了。此外,用户使用堆的情况,操作系统只知道很少一点;当有堆的使用请求时,可能的话,处理器简单地给应用分配更多内存,要不就失败。通常libc或其它相似的库使用自己定义的语义提供了堆操作。
堆会向操作系统请求一块相对较大的内存区(尤其是在初始化的时候),并且根据应用的请求,分配内存区中较小块的内存。典型地,这些内存块有内联的元数据指出块的大小和块的其它属性,比如之前一块内存块的大小等。
对已分配好的内存块,通过增加其大小,可以合并下一个内存块,减小其大小,可以找到先前一个块。比如,在图2.7中,你可以看到由Glibc分配的块。这个例子中,指针mem指出由malloc()或类似函数返回的分配的块的起始地址,同时,指针chunk标出块的实际开始地址。我们可以看到这儿有一些元数据,包括前面一块内存块的大小、本内存块的大小,指明不同状态条件的标识。这种块最常在Linux被使用,在其它多数操作系统和动态内存分配方法的具体实现中也使用相似的块(当然,有一些关键的区别)。因为从操作系统获得大块内存或扩展数据段的大小是相对昂贵的操作,通常要维护一个排序缓存。这个缓存是某种形式的指针链表,其中的指针指向被释放的内存块。这个链表的维护一般比较复杂,包括合并相邻的空闲块以减少碎片,为了在有分配的请求时以尽可能最有效的方法定位候选块,这个链表可能还包括各种各样的以大小或其它特性排了序的链表。
图2.7 Glibc分配的块
使用和先前相似的例子,在图2.8中,你可以看到用Glibc释放内存块的图示。这个例子中,指针mem指出API用户得到的返回地址经常在哪儿,指针chunk指向实际使用的数据结构开始地址。最大的差别是在被用作用户数据的地方,存储有两个指针,分别指向链表中空闲下一个的内存块和前一个内存块。这明显表明,与被分配了的块按块的大小操作来不同,内存空闲块直接由链表操作。这个结构化的链表的具体细节显然是和Glibc有关,但它所表达的概念是足够通用的,适用于绝大多数实现。在你的应用程序的生命周期中,分配和释放内存请求非常频繁地发生,最初从操作系统获得的内存中的块被释放,返还给空闲链表,然后,可能有更多的分配请求,它们又从空闲链表上得到这些块,如此反复,直到所有内存耗尽,或者应用程序终止。
在前一节,我们讨论了一个二进制文件布局中最普通的区,包括它们的一些功能,同时对栈段和堆段进行了一次深入旅行,在一定深度上讲到了它们如何工作。这应该足够给你提供一个基础,以帮助你继续理解相应内容。当然,再一次地,鼓励有兴趣的读者去参考其它与相关主题的资料。
4.IA-32指令集温习及索引
        前一节,我们简单谈到指令和操作数,但集中于IA-32的体系结构设计,然后深入到二进制的可执行内存的普遍布局、它们的目的和用法。本节主要给你提供一个经常使用的指令的参考,告诉你它们的用法和操作数。如果现在你已经阅读过Intel开发者手册,本节可能是多余的,你可以跳过本节,直接到下一章。本节,在表2.1中的术语将被使用。
表2.1 使用的术语
术语                                        意义
Reg32                                        任何32位的寄存器
Reg16                                        任何16位的寄存器
Reg8                                        任何8位的寄存器
Mem32                                        32位的内存操作数
Mem16                                        16位的内存操作数
Mem8                                        8位的内存操作数
Sreg                                                段寄存器
Memoffs8                                8位内存偏移量
Memoffs16                                16位的内存偏移量
Memoffs32                                32位的内存偏移量
Imm8                                        8位立即数(常量)
Imm16                                        16位立即数
Imm32                                        32位立即数
Ptr16:16                                        操作数的绝对地址
Ptr16:32                                        操作数的绝对地址
Mem16:16                                由mem16:16给出的绝对间接地址
Mem16:32                                由mem16:32给出的绝对间接地址
Rel8                                                8位相对移位
Rel16                                        16位相对移位
Rel32                                        32位相对移位
寄存器名                                被介绍过的任何寄存器的名字

        我们将要介绍的第一个指令是MOV,这是一个最基本的指令,它拷贝一个操作数到另一个操作数中。它的格式和操作数如表2.2所示:
表2.2 MOV指令
目的操作数                                源操作数
reg8/mem8                                 reg8
reg16/mem16                         reg16
reg32/mem32                         reg32
reg8                                         reg8/mem8
reg16                                         reg16/mem16
reg32                                         reg32/mem32
reg16/mem16                         Sreg
Sreg                                         reg16/mem16
AL                                                 memoffs8
AX                                                 memoffs16
EAX                                         memoffs32
memoffs8                                 AL
memoffs16                                 AX
memoffs32                                EAX
reg8                                         imm8
reg16                                         imm16
reg32                                         imm32
reg8/mem8                                 imm8
reg16/mem16                         imm16
reg32/mem32                        imm32

        mov指令拷贝源操作数到目的操作数,且只能用在移动某些类型的操作数上;比如,它不能被用在代码段,不能被用来修改EIP寄存器。假如一个目的操作数是Sreg类型,那它必须指向一个有效的段选择子。下面要介绍的指令是不同的位操作指令,如and,异或。
        And是另一个相对简单的指令。它在目的操作数上执行同源操作数的每位与,将结果保存在目的操作数中。它支持表2.3中列出的操作数。位与比较两个操作数的二进制表示,如果两个都是1,则输出1,否则输出0。
表2.3 and指令
目的操作数                                源操作数
reg8/mem8                                 reg8
reg16/mem16                         reg16
reg32/mem32                         reg32
reg8                                         reg8/mem8
reg16                                         reg16/mem16
reg32                                         reg32/mem32
AL                                                 imm8
AX                                                 imm16
EAX                                         imm32
reg8                                         imm8
reg16                                         imm16
reg32                                         imm32
reg8/mem8                                 imm8
reg16/mem16                         imm16
reg32/mem32                         imm32
        接下来是NOT指令,这也是一个比较简单的系念;它在单个操作数上执行位非操作,允许的操作数如表2.4。它就简单地对操作数的1和0取反。
表2.4 not指令
目的操作数                                源操作数
reg8/mem8                                 N/A
reg16/mem16                         N/A
reg32/mem32                         N/A
        稳步前进吧,我们说说执行位或的or指令。它可用的参数如表2.5。一个位或意思是(当然,还是很简单)按位比较两个操作数,当两位同时为0时,输出0,否则输出1。
表2.5 or指令
源操作数                                目的操作数
reg8/mem8                                 reg8
reg16/mem16                         reg16
reg32/mem32                         reg32
reg8                                         reg8/mem8
reg16                                         reg16/mem16
reg32                                         reg32/mem32
AL                                                 imm8
AX                                                 imm16
EAX                                         imm32
reg8                                         imm8
reg16                                         imm16
reg32                                         imm32
reg8/mem8                                 imm8
reg16/mem16                         imm16
reg32/mem32                         imm32

        下面我们介绍异或指令,也就是XOR。它在它的操作数上执行位的异或,可用的操作数如表2.6所示。异或指令比较源和目的操作数,结果存储在目的操作数中。其输出是两位比较,相同为0,不同为0。
表2.6 xor指令
源操作数                                目的操作数
reg8/mem8                                 reg8
reg16/mem16                         reg16
reg32/mem32                         reg32
reg8                                         reg8/mem8
reg16                                         reg16/mem16
reg32                                         reg32/mem32
AL                                                imm8
AX                                                 imm16
EAX                                         imm32
reg8                                         imm8
reg16                                         imm16
reg32                                         imm32
reg8/mem8                                 imm8
reg16/mem16                        imm16
reg32/mem32                         imm32
        test指令通常用来检查一个具体的状态,根据结果修改控制流(见表2.7)。test在第一个和第二个操作数上指令执行一个位与操作,根据结果,将EFLAGS标识置位。然后丢弃其计算结果。
Cmp指令比较两个操作数,这个比较是通过目的操作数和源操作数相减执行的,执行后根据结果置位EFLAGS寄存器(见表2.8)。它使用方式同test相似,常用作比较用户输入值和子程序返回值。如果操作数是即数,它符号位会被扩展以匹配其它操作数的大小。
表2.8 cmp指令
源操作数                                目的操作数
reg8/mem8                                 reg8
reg16/mem16                         reg16
reg32/mem32                         reg32
reg8                                         reg8/mem8
reg16                                         reg16/mem16
reg32                                         reg32/mem32
AL                                                 imm8
AX                                                 imm16
EAX                                         imm32
reg8                                         imm8
reg16                                         imm16
reg32                                         imm32
reg8/mem8                                 imm8
reg16/mem16                         imm16
reg32/mem32                         imm32
表2.7 test指令
源操作数                                目的操作数
reg8/mem8                                 reg8
reg16/mem16                         reg16
reg32/mem32                         reg32
AL                                                 imm8
AX                                                 imm16
EAX                                         imm32
reg8                                         imm8
reg16                                         imm16
reg32                                         imm32
reg8/mem8                                 imm8
reg16/mem16                         imm16
reg32/mem32                         imm32

装载有效地址指令,也就是lea,计算源操作数指定的地址,存储在目的操作数中(见表2.9)。它也可不修改源操作数的值,用在多个寄存器进行算术计算。
表2.9 lea指令
源操作数                                目的操作数
reg8                                         mem8
reg16                                         mem16
reg32                                         mem32
        jmp指令将控制转移至其操作数指定的地址。这个执行可以执行4种不同的跳转:近跳转、短跳转、远跳转和任务切换(见表2.10)。近跳转是发生在当前代码段内的跳转。短跳转是与当前地址相距-128        ~127地址内的跳转。远跳转是将控制转移到地址空间内具有与当前代码段相同优先级的段内。最后,任务切换跳转到不同的任务中的一条指令上。
表2.10 jmp指令
源操作数                                目的操作数
rel8                                         N/A
rel16                                         N/A
rel32                                         N/A
reg16/mem16                         N/A
reg32/mem32                         N/A
ptr16:16                                 N/A
ptr16:32                                 N/A
mem16:16                                 N/A
mem16:32                                 N/A
        jcc指令不是一个特定的指令,而是一系列条件跳转指令。根据使用的指令不同,条件也不同,但经常和test、cmp指令一起使用。表2.11显示了jcc使用的目的操作数。在表2.12中,你可以发现条件跳转列表和检查一个条件为真或假的标识。不必担心不明白这些标识,EFLAGS寄存器的说明将在指令说明后给出。
表2.11 jcc指令
源操作数                                目的操作数
rel8                                         N/A
rel16                                         N/A
rel32                                         N/A
表2.12 条件跳转指令
指令                                EFLAGS条件                                描述
ja                                         CF = 0 && ZF = 0 Jump if above
jae                                         CF = 0 Jump if above or equal
jb                                         CF = 1 Jump if below
jbe                                         CF = 1 || ZF = 1 Jump if below or equal
jc                                         CF = 1 Jump if carry
jcxz                                 CX = 0 Jump if CX is zero
jecxz                                 ECX = 0 Jump is ECX is zero
je                                         ZF = 1 Jump if equal
jg                                         ZF = 0 && SF = OF Jump if greater than
jge                                         SF = OF Jump if greater than or equal to
jl                                         SF != OF Jump if less than
jle                                         ZF = 1 || SF != OF Jump if less than or equal to
jna                                         CF = 1 || ZF = 1 Jump if not above
jnae                                 CF = 1 Jump if not above or equal
jnb                                         CF = 0 Jump if not below
jnbe                                 CF = 0 && ZF = 0 Jump if not below or equal
jnc                                         CF = 0 Jump if not carry
jne                                         ZF = 0 Jump not equal
jng                                         ZF = 1 || SF != OF Jump not greater
jnge                                 SF != OF Jump not greater or equal
jnl                                         SF = OF Jump not less
jnle                                 ZF = 0 && SF = OF Jump not less or equal
jno                                         OF = 0 Jump if not overfl ow
jnp                                         PF = 0 Jump not parity
jns                                         SF = 0 Jump not signed
jnz                                         ZF = 0 Jump not zero
jo                                         OF = 1 Jump if overfl ow
p                                         PF = 1 Jump if parity
jpe                                         PF = 1 Jump if parity even
jpo                                         PF = 0 Jump if parity odd
js                                         SF = 1 Jump if signed
jz                                         ZF = 1 Jump if zero
        从表2.12你可以看到非常多的条件跳转寄存器,它们都依赖于EFLAGS寄存器的的不同状态,现在我们就对这个先前没描述的内容探讨一番。EFLAGS寄存器是32位寄存器,包含一组状态和系统标识及一个控制标识。每个标识都由寄存器的一位表示,从位0到31,分别代表如下标识:
CF 进位标识,指明在算术操作中,寄存器的最高有效位是否有进位或借位。这个标识位用于无符号数算术。
        PF        奇偶位,当结果的最低有效位个数为偶数个是置位
        AF        调整位,如果操作结果有从第3位上借位或进位时置位
        ZF        零标志位,操作结果为0时置位
        SF        符号位,设为与结果最高有效位一样(带符号数的最高有效位是符号位)
        TF        陷井位,当允许单步调试时,置位
        IF        中断使能位,当标记的中断使能时置位,当清除中断标记时复位
        DF        检测位,用于字符串操作中决定指令增还是减
        OF        如果执行有符号数算术运算时有溢出则置位
        IOPL(位12,13)        I/O优先级位,指出当前运行任务的当前I/O优先级
        NF        嵌套任务位,如果当前任务是与先前执行的任务关联时置位
        恢复标志        控制处理器对调试异常的响应
        VM        虚拟-8086标志,置位时启用虚拟8086模式
        AC        对齐检查位,置位以启用内存引用的对齐检查
        VIF 虚拟中断位,是IF标志位的虚拟映像,同VIP标志一起使用
        VIP        虚拟中断位,用来决定中断是否被挂起
        ID        标记位,用来决定CPU是否支持CPUID指令
        位22到31当前被保留。
        Call指令更像一个正式的跳转指令,它按本文前面所述的方式设置栈,当被执行的函数完成后,允许处理器在调用点恢复执行。
表2.13 call指令
源操作数                                        目的操作数
rel16                                                 N/A
rel32                                                 N/A
reg16/mem16                                 N/A
reg32/mem32                                 N/A
ptr16:16                                         N/A
ptr16:32                                         N/A
mem16:16                                         N/A
mem16:32                                         N/A
        ret指令与call相反。它将元数据存储在栈上,弹出它并返回到那个地址(见表2.14)。可选的立即数指定执行返回后,弹出多少个字节的栈数据。
表2.14 ret指令
源操作数                                目的操作数
N/A                                         N/A
imm16                                         N/A
        就像你看到的,现在我们已经熟悉很多指令了,它们中的绝大多数有很多不同的操作数,这又导致同一个指令会有不同的形式(因此操作码也不同)。这不是一个完整的指令参考――实际上,它只涉及皮毛。尽管如此,我们希望对很多平常使用的指令有点基础,并让你熟悉它们。如果读者不熟悉指令集的话,强烈鼓励你们查阅Intel开发者手册,特别是卷3A和3B。
总结
现在你至少熟悉了汇编语言和Intel体系。在一定程度上知道内存模型和工作模式如何工作,还获得了适当的基本知识,在此基础上可以继续理解其它内容。尽管如此,你可能还是在暗自想,“的确,我理解了一些汇编知识,但,逆向工程是什么?”好的,逆向工程是一个宽泛的定义,对不同的人,代表不同的含义。通常,它是把一个对人来说不可读或不可分析的应用程序变为可读或可分析的过程。有的人这么做是为了得到丢失的源代码,其它人这么做是为了复制专属产品,另一些人逆向恶意软件,以知道它们在做什么,最后,还有一些人是为了发现软件里面的漏洞或者脆弱点。
从本书的写作目的出发,可简单总结为,逆向工程是处理计算机可读的二进制文件,使用操作码来产生汇编语句,然后理解这些汇编语句,以帮助你完成你想达到的目的。从这个意义上,也可以说是,对逆向工程师,没有闭源软件。处理器看到每一个指令,逆向工程师亦是如此。


[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

上传的附件:
收藏
免费 7
支持
分享
最新回复 (20)
雪    币: 208
活跃值: (10)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
2
术语:
Malware 恶意软件
Packer 加壳器  见http://bbs.pediy.com/showthread.php?t=46907&page=2&highlight=packer
Binary files  二进制文件
Assemble  有时候译成汇编代码,视上下文
Opcode  (operation code) 操作码
Operation mode 工作模式
Exploit 不译
Stack frame 栈帧(stack frame is created by  assembly code,and it's a an area of memory that temporarily holds the arguments to the function as well as any variables that are defined local to the function.When the procedure is called, the stack frame is pushed onto stack.)栈帧是汇编代创建的一个临时内存区别,它与一个函数相关,存储函数参数或其它函数中的本地变量。当过程被调用时,栈帧就被弹入栈内。
   --我自己的理解,栈帧就是当一个函数或过程调用时,与些过程或函数相关的所有存储在栈上的东西的集合(也就是一帧),比如函数参数,本地变量等等。上面的英文不是关于栈帧的英文权威解释,只是我在查询这个词意思是搜到的,但我觉得很确切。
Procedure prologue 过程序言
Procedure epilogue 过程结尾
2008-6-4 11:09
0
雪    币: 1844
活跃值: (35)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
3
顶,继续啊,期待你的下文
2008-6-4 11:38
0
雪    币: 201
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
感谢!为我们提供了参考!
2008-6-4 13:02
0
雪    币: 208
活跃值: (10)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
5
我从几个地方下载了本书电子片,都有乱码,不知道是什么原因。

At any rate, this chapter serves as an introduction to the physical layout of the Ý les, and
details aspects of the Ý les that a reverse engineer would Ý nd interesting and/or important.
Both of these Ý le formats have open documentation and, in places where readers Ý nd this
chapter lacking, they are strongly encouraged to read the speciÝ cations themselves
2008-6-4 14:57
0
雪    币: 2108
活跃值: (208)
能力值: (RANK:1260 )
在线值:
发帖
回帖
粉丝
6
[QUOTE=;]...[/QUOTE]
首先向sagaeon致歉,是我的工作没做好。
但我还是辩白一下:
1.我发起活动时,出版社还没有最终定下来是否由翻译不组来翻译
2.在这个过程中,有些朋友已经开始翻译了
3.等出版社定下来后,我又要和各位有意翻译的朋友联系确认(这个过程比较麻烦,因为大家都不是一直在线,一直处在不确定的状态)

所以导致了偏差。

我确认翻译人员的标准是:
1.有意向翻译,最好翻译过东西
2.已经开始翻译了(不想让朋友们劳而无获,这也是我对不住sagaeon的地方)
3.在联系过程中率先确认的。

再次向sagaeon致歉。
2008-6-4 19:16
0
雪    币: 1844
活跃值: (35)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
7
唯有继续期待出版了
2008-6-5 01:41
0
雪    币: 98729
活跃值: (201034)
能力值: (RANK:10 )
在线值:
发帖
回帖
粉丝
8
Sagaeon: Test it,Thanks.
2008-6-5 01:50
0
雪    币: 98729
活跃值: (201034)
能力值: (RANK:10 )
在线值:
发帖
回帖
粉丝
9
Again,For the translation of these words, learning..
2008-6-5 01:53
0
雪    币: 208
活跃值: (10)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
10
晕。arhat版,这个弄复杂了,我只是随口说说,就像你说的一样,这里面是因为组织大家有个过程,和你没关系。现在该我道歉了,完全是误会哈。
另外,我的确是初次接触这方面的内容,包括翻译和逆向,所以自身还有待提高,希望在论坛里慢慢提高
2008-6-5 09:05
0
雪    币: 208
活跃值: (10)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
11
arhat已经组织翻译了,我们大家一起静候佳音吧
2008-6-5 09:07
0
雪    币: 208
活跃值: (10)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
12
是不是栈帧的翻译不对啊。。。。。
2008-6-5 09:10
0
雪    币: 201
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
13
这个我看过了“Ý”指的是“fi”  还有几处"itís" 中的"í"是" ' "  也就是"it's".
2008-6-5 11:46
0
雪    币: 208
活跃值: (10)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
14
嗯,是这样,谢谢
另外,上面上传了word附件。
2008-6-5 14:59
0
雪    币: 34
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
15
有没有附书 源代码 ?
2008-7-31 11:40
0
雪    币: 101
活跃值: (88)
能力值: ( LV2,RANK:140 )
在线值:
发帖
回帖
粉丝
16
这个,不是打击大家, 我粗略的过了一遍这本书, 写的其实不咋好, 对IDA怎么用介绍得非常少。 属于那种入门者看了也不咋明白,懂得人也用不大上的那一类。

还没翻译的章节最好就不要再花时间和精力翻译了。
2008-9-29 15:11
0
雪    币: 204
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
17
唯有继续期待出版了!!
2008-10-15 15:14
0
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
18
感谢提供了参考
2008-12-5 13:22
0
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
jis
19
可以理解...支持......继续努力....
2009-3-11 02:31
0
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
20
十分感谢楼主
2009-3-11 10:53
0
雪    币: 561
活跃值: (124)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
21
支持下  
2009-3-12 13:56
0
游客
登录 | 注册 方可回帖
返回
//