来自Wikipedia


反汇编不是一门精确的科学:在具有
可变宽度指令的CISC平台上,或者在存在自修改
代码,单个程序可能具有两个或多个合理的分解。确定在程序运行期间实际上会遇到哪些指令,将减少到已证明无法解决的暂停问题。



为什么在CISC上反汇编不是一门精确的科学?
我知道重新汇编反汇编的代码可能会对同一条指令产生不同但相似的操作码,但是因为它们相似,所以不应影响生成的程序。而且,如果这不是一门精确的科学知识,例如assemble(disassemble(opcode))!=操作码,那么CPU如何确定哪种方式来解释操作码流?
在旁注中,这是否暗示着在RISC平台上拆卸精确科学?


评论

一个更抽象的原因可能是编译不是1对1。每个高级语句都不会编译为唯一的一组操作码。就像在数学中一样,您具有域和范围。编译会将域映射到范围。如果域比范围大得多,则编译不是1到1。因此,没有反函数或重新编译/反汇编函数(一般来说,解决方案并非来自于必要,而是您可以选择)。

#1 楼

回答第一个问题。最大的问题是您无法真正将数据与代码分开。基本上有两种散布方法:


线性扫描直到最后,而无需跟随跳转或以任何方式对已分解的代码进行推理。例如,SoftICE和Windbg使用线性扫描。这可能是个问题,因为您不知道何时停止。您不知道指令在哪里结束,数据在哪里开始。为此,您必须依赖可执行格式的元数据,例如节大小。另一方面,递归遍历算法会考虑跳转和调用,而反汇编器会跟随跳转,仅反汇编将实际执行的代码。例如,IDA和OllyDBG使用此方法。它的好处是,它本质上知道什么代码和什么数据。但这显然有其缺点。首先,通过纯递归遍历,并非所有代码都会被分解。例如,在运行时通过计算其地址而未直接引用的函数将不会被发现。再次,引擎有一些启发法来绕过此问题。

例如,一个程序跳回了几条指令,但它并没有跳到先前已分解并执行的指令的开头,而是跳到了中间。反汇编程序应如何决定显示哪一个?如果这是编码人员的意图,则两者都可能有效。递归遍历分解器可能会显示第一个分解的版本,并且只是标记跳转到函数的中间。但是,如果您不详细检查字节,则很难说出跳转后实际上将执行什么操作。

还有许多其他示例。

这两种方法的另一个大问题是自我修改代码。只是不能静态完成。

CPU自身在执行代码时没有任何这些问题,换句话说,它是动态的。

要回答我不认为的第二个问题这仅是CISC体系结构的问题,其中一些也可以应用于RISC。

评论


实际上,他们应该说冯·诺依曼体系结构(允许自我修改),而不是CISC。

–俄罗斯
13年8月4日在19:36

@Ruslan:与自我修改无关。关键是可变宽度指令允许模棱两可的解释。例如。您可能会将JMP插入指令的中间,从而导致读取单独的有效指令序列。这对于固定宽度的指令集是不可能的,而固定宽度的指令集始终是RISC(几乎是?)。

– BlueRaja-Danny Pflughoeft
13年8月4日在20:30



“自修改代码不是唯一的问题”-嗯,这甚至​​不是问题。在固定宽度的体系结构上,反汇编自修改代码应该没有问题。当然,反汇编程序无法知道运行时修改后的汇编外观(这是一个无法确定的问题),但这不在反汇编二进制文件的范围之内。

– BlueRaja-Danny Pflughoeft
13年8月4日在21:56



OllyDBG使用线性扫描吗?

–viv
13年8月9日在8:28

我非常确定可以,但是现在找不到该声明的参考。我可能将它与SoftICE混合在一起。在答案中更正。谢谢!

–0xea
2013年8月9日在16:47



#2 楼

除了前面的答案中描述的问题之外,这是程序可以进行多个反汇编的另一种方式。考虑具有以下逻辑的代码(我对语法屠杀表示歉意):



buf = ...一些字符串...
val =已读()if(val == 7){
mprotect(buf,...,PROT_EXEC); / *使buf可执行* /
goto buf; / *执行buf * /
} else {
print(buf);
}



在这种情况下,buf是否为代码(因此应反汇编)或数据(因此应反汇编)取决于输入。因此,该程序具有多个可能的反汇编。请注意,这是因为代码从根本上无法与数据区分开,并且与RISC与CISC无关。

本文对如何将代码伪装成看似合理的数据进行了令人愉快的讨论:


J. Mason,S。Small,F。Monrose,G。MacManus。英文Shellcode。第16届ACM计算机和通信安全(CCS)会议论文集,伊利诺伊州芝加哥。 2009年11月。[PDF]


#3 楼

我认为此评论旨在描述的事实是,在CISC上,以x86为例,这里有几种可能的反汇编的合理表示。但是我认为这也可以部分地归结为典型的RISC实现,在这种情况下-汇编程序可以提供类似CISC的助记符,由不同的基本(RISC)操作码组合表示。

例如,rep前缀在许多操作码上是没有意义的,但在过去已被使用,以使反向工程师更难使用。但是,对于最终要看拆卸的人来说,哪个代表更好?一个带有rep前缀的代码或一个没有rep前缀的代码(这更多地反映了CPU的功能)。

如果反汇编程序忠于其发现的内容,则应包括nop前缀。另一方面,反汇编程序的工作是使机器代码对人类可读。因此,加倍努力并确保删除多余的前缀是很明智的。类似地,代表nop(无操作)的多字节操作码可以被完全剥离或以与其他字符不同的方式表示(例如IDA使用对齐字节),或者分别表示为jne(用于1和5字节版本)因此,从我的角度来看,反汇编机器代码是一门精确的科学,因为对于每个操作码而言,都有一个精确的助记符(在这里,请忽略jnz / q4312079q对偶性)。否则,正如您所指出的那样,CPU将如何处理并弄清楚该怎么办?但是,为了代表人类逆向工程人员,有时(我敢说吗?)对它进行了后期处理,以提高可读性和理解力。那就是拆卸器和拆卸器变化的地方。


另一个问题是,为了遵循所有可能的代码路径,必须使用试探法(这是好的反汇编程序的显着特征之一)。否则,很难将数据与代码区分开。但是据我所知,“ CISC”不能因其特质而被选中,因此我认为这并不是发表此评论的主要原因。

#4 楼

反汇编中的关键问题在于将二进制可执行文件中的代码与数据精确分离。为此,您将需要进行静态分析,以精确确定间接跳转(跳转到运行时计算出的地址)的目标。间接跳转的例子有很多,包括C ++中的切换目标计算和vtable。

这种类型的静态分析(如果存在)将能够解决已知无法确定的停止问题。因此,这种静态分析将不存在。基于此,拆卸工具需要依靠试探法和/或近似法来确定间接跳跃目标。有关此问题的更多详细信息,请参见“ ARM拆卸的可靠性”中对类似问题的解答。如果二进制文件被混淆和/或具有自修改代码,则代码/数据分离的任务将变得更加困难。

已从二进制文件中提取了代码和数据的近似值。然后需要解决将语义附加到它们的问题,这也是困难的。例如,当通过指令访问时,struct { int i; int j;}看起来类似于int arr [2]。因此,在不依赖于诸如调试符号之类的外部信息的情况下,很难在执行的代码上附加独特的语义。

评论


可以从结构中区分出较大的数组,这是因为其元素的访问方式,甚至可能与arr [2]一样小。

–杂件
2015年4月14日在21:16

@Jongware同意。这只是一个简单的示例,它说明了在反汇编中具有多种有效含义的想法。

–科多卡
15年4月15日在7:55

#5 楼

初步说明

首先,我们需要就二进制程序的正确反汇编是什么达成一致。我会提出以下定义:


正确地反汇编二进制程序将提供该程序可能需要执行的所有输入的所有可能指令集。 >

另一种陈述方式是说,我们在程序可能接受的每个输入上公开程序所有可能执行的指令。

停止问题

在这里,我们已经可以在图灵机上与停机问题相提并论,可以将其定义为以下内容(维基百科):


停机问题是通过对任意计算机程序的描述和输入来确定程序将结束运行还是继续永远运行。图灵,这意味着,即使我们可以使用程序自动处理一定数量的病例,某些病理性病例也总是会从程序中逸出,无法确定是不是机器/程序将在给定的输入上停止。

当然,这种病理情况是无限多的(因此,没有希望一一列举出来)作为特殊情况)。

返回到我们的反汇编问题!

由于停止问题,确实无法确定程序的所有可能路径!

的确,解决停顿问题的双重方法是可访问性问题,您想知道是否有输入可以使您到达程序的特定点。而且,知道程序是否可以到达内存中的特定位置并将其解释为一条指令(即指令指针在某个时候取该地址的值)是可访问性问题。



但是,在现实世界中吗?

我知道,我知道,这只是数学……不是事实……大多数混淆(自愿或非自愿)都可以解决并自动从二进制代码中删除...

这主要是因为做这些混淆的人不习惯无法解决的问题...

想象一下,您在程序中插入了无法解决的问题的计算,或者甚至是足够困难和复杂的计算,

举个例子,让我们看一下Collat​​z序列(维基百科),该序列被推测总是在1点后结束。但是,其背后的算术问题是如此复杂,以至于大约一个世纪以来就一直存在这种猜想……这是一个完美的不透明谓词!当然,可能存在这种猜想的证明,但是这个问题非常复杂,足以开始在其上进行构建,并使计算机探索程序的状态空间感到困惑。如今,有关强混淆的当前研究方向...我们几乎已经用过以前使用过的小技巧,人们开始在有更好基础的问题上进行研究。即使在软件混淆方面我们仍然想念等效于Shannon(信息理论之父)的东西,以与密码学进行比较。

最后一句话

因此,我们看到了拆卸问题与停止问题密切相关。而且,使用高度复杂的问题可能是现代软件混淆的下一步。

我要说的最后一个事实是,如果我们必须坚持拆卸技术的最新水平,那么当前的拆卸工具可能远远落后于我们可以做的事情。当我看到当前的史前工具是多么的时候,我总是在痛苦中哭泣……但是将所有现代技术付诸实践将需要大量的开发和维护工作,以致似乎没有人愿意为此做准备(但这只是我的本能。愚见)。