为什么反汇编数据会成为指令?

时间:2016-04-24 23:55:24

标签: assembly x86 tasm

我需要一些帮助才能理解当下发生的事情 这段代码"发生":" jmp开始"。 我只了解.com文件可以是64kb,因此您希望将所有内容放在一个段中。如果要放置变量,则需要jmp。但是当我搜索它时,许多指南只是在评论中说jmp Begin只是跳过数据而没有别的。这是我的问题: 在这一刻究竟发生了什么:

enter image description here

它似乎运行此

        mov     al, a
        mov     bl, b
        sub     al, bl

但我无法理解为什么它在turbo调试器中看起来像这样。 当我改变Result的起始值时?对于大于0的东西,它会改变为其他东西,当我将其更改为例如90时,它看起来完全正常。我对装配完全不熟悉,我似乎根本无法掌握它。这是我的全部代码:

            .MODEL TINY

Code        SEGMENT

            ORG    100h
            ASSUME CS:Code, DS:Code

Start:
                jmp     Begin
a               EQU     20
b               EQU     10
c               EQU     100
d               EQU     5
Result          DB      ?


Begin:

            mov     al, a
            mov     bl, b
            sub     al, bl
            mov     bl, c
            mul     bl
            mov     bl, d
            div     bl              
            mov     Result, al
            mov     ah, 4ch
            int     21h

Code        ENDS
            END             Start

2 个答案:

答案 0 :(得分:3)

我试着给你一个解释。

问题在于,在过去(今天部分仍然如此),处理器并没有区分内存中的代码和数据字节。这意味着.com文件中的任何字节都可以用作代码和数据。调试器不知道哪些字节将作为代码执行,哪些字节将用作数据。在棘手的情况下,一个字节实际上可以用作代码和数据......你的程序可以在内存中创建有效代码的数据,你可以跳转到它来执行它。

在许多(但不是全部)情况下,调试器实际上可以找出什么是代码以及什么是数据,但是这种代码分析会变得非常复杂,因此大多数调试器/反汇编器根本就没有这样的代码流分析器。出于这个原因,他们只是在文件/内存中选择一个偏移量(这通常是当前的指令指针),并从这个偏移量开始,它们将一系列连续的字节解码为串行指令,而不遵循任何jmp指令直到调试器的屏幕完全填满了足够数量的反汇编线。愚蠢的反汇编程序/调试程序并不关心反汇编的字节是否实际用作程序中的指令或数据,它们将它们视为指令。

如果您正在调试程序并且调试器在断点处停止,那么它将获取当前指令指针并从该偏移量开始再次执行愚蠢的反汇编,并使用原语"填充调试器屏幕"方法

连续字节的这种串行反汇编是一种在大多数情况下都能工作的简单方法。如果您对非jmp指令进行串行解码,那么几乎可以确定处理器将按此顺序执行它们。但是,一旦到达并解码jmp指令,就无法确保以下字节作为代码有效。然而,您可以尝试将它们解码为希望没有数据混合到代码中间的指令(是的,在大多数情况下,在jmp(或类似的控制流指令)之后没有数据,这是为什么调试器会给你一个哑的反汇编作为"可能有用的预测" )。事实上,大多数代码通常都是条件跳转并在它们之后反汇编代码,因为代码对调试器来说非常有用。在跳转指令之后将数据放在代码中间是非常罕见的,我们可以将其视为边缘情况。

假设您有一个简单的.com程序,它只是跳过一些数据然后以int 20h存在:

    jmp start
    db  90h
start:
    int 20h

反汇编程序可能会通过从偏移量0000开始反汇编来告诉您类似以下内容:

--> 0000   eb 01        jmp short 0003
    0002   90           nop
    0003   cd 20        int 20h

很酷,这看起来与我们的asm源代码完全一样......现在让我们稍微改变一下程序:让我们改变数据...

    jmp start
    db  cdh
start:
    int 20h

现在反汇编程序会告诉你:

--> 0000   eb 01        jmp short 0003
    0002   cd cd        int cdh
    0004   20 ...... whatever...

问题是一些指令由超过1个字节组成,调试器并不关心字节是否代表您的代码或数据。在上面的例子中,如果反汇编程序将字节从偏移0000到程序结束(包括数据)串行反汇编,那么你的1字节数据将被反汇编成2字节指令("窃取"你的第一个字节)实际代码)所以调试器试图反汇编的下一条指令将来自偏移的0004而不是0003,你的jmp通常会跳转。在第一个例子中,我们没有遇到这样的问题,因为在反汇编程序的数据部分后,数据被反汇编成1字节指令而意外下一条反汇编调试器的指令是偏移0003,这正是您jmp的目标。

然而,在这种情况下调试器向您显示的是幸运的是,当程序执行时会发生什么。通过执行一条指令,程序实际上会跳转到偏移0003并且调试器会再次进行愚蠢的反汇编,但这次是从偏移的0003开始,它位于前一次错误反汇编中的指令中间...

让我们说你调试第二个示例程序,然后逐个执行其中的所有指令。当您使用指令指针== 0000启动程序时,调试器会显示:

--> 0000   eb 01        jmp short 0003
    0002   cd cd        int cdh
    0004   20 ...... whatever...

然而,当您触发"步骤"命令执行一条指令,指令指针(IP)变为0003,调试器执行"哑的反汇编"再次从偏移0003直到调试器屏幕填满,你会看到:

--> 0003   cd 20      int 20h
    0005   ...... whatever...

结论:如果您有愚蠢的反汇编程序,并且将数据混合到代码的中间(数据周围有jmp),则愚蠢的反汇编程序会将您的数据视为代码,这可能导致&#34 ;次要"你遇到的问题。

带流量分析的高级反汇编程序(如Ida Pro)将按照跳转指令进行反汇编。在偏移0000处反汇编jmp后,它会发现下一条反汇编指令是0003处jmp的目标,它会在下一步中反汇编int 20h。它会将偏移量为0002的db cdh字节标记为数据。

补充说明:

正如您已经注意到(过时的)8086指令集中的指令可以是1-6字节长的任何位置,但jmpcall可以以字节粒度跳转到内存中的任何位置。指令的长度通常可以从指令的前1或2个字节确定。然而字节"粘在一起"仅当处理器以其特殊IP(指令指针寄存器)指向指令的第一个字节并尝试执行给定偏移处的字节时,才进入指令。让我们看一个棘手的例子:你在偏移0000的内存中有字节eb ff 26 05 00 03 00,你可以一步一步地执行它。

--> 0000   eb ff        jmp short 0001
    0002   26 05 00 03  es: add ax, 300h
    0006   00 ...... whatever...

处理器指令指针(IP)指向偏移量0000,因此它对一条指令进行解码,并将那些字节粘在一起,并将其组合成一条指令"在执行时。 (处理器在0000执行指令解码。)由于第一个字节是eb,它知道指令长度是2个字节。调试器也知道这一点,所以它为你解码指令,并根据不正确的假设生成一些额外的错误反汇编,在某些时候处理器将在偏移0002处执行指令,然后在偏移0006处执行...等等。将会看到这不是真的,处理器会将字节组合成完全不同偏移的指令。

正如您所看到的那样棘手的字节代码包含一个jmp,它跳转到位于执行jmp指令本身中间的0001的偏移量!然而,这根本不是问题。处理器并不关心它并愉快地跳转到偏移0001,因此下一步它将尝试解码指令(或者#34;将字节粘在一起)。让我们看看处理器在0001处找到什么样的指令:

--> 0001   ff 26 05 00  jmp word ptr [5]
    0005   03 00        add ax, word ptr [bx+si]

如您所见,我们在0001处有下一条指令,并且调试器在偏移0005处向我们显示了一些垃圾反汇编,基于错误的假设,处理器将在某个时刻达到该偏移...

0001处的指令告诉处理器从偏移0005中拾取一个字并将其解释为跳转到那里的偏移量。如您所见,word ptr [5]的值为3(作为小端16位值),因此处理器将3放入其IP寄存器(跳转到0003)。让我们看看它在偏移0003处发现了什么:

--> 0003   05 00 03     add ax, 300h

以调试器的方式显示我棘手的字节代码eb ff 26 05 00 03 00的反汇编是很困难的,因为处理器执行的实际指令位于重叠的存储区中。首先,处理器执行字节0000-0001,然后是0001-0004,最后是0003-0005。

在一些较新的risc体系结构中,指令的长度是固定的,它们必须位于对齐的内存区域,并且不可能跳转到任何地方,因此调试器的工作比x86更容易。< / p>

答案 1 :(得分:1)

任何字节序列都可以解码为x86指令。 x86机器代码使用大部分编码空间,因此只有少数位模式不是有效指令。您有时会看到(bad)或类似的迹象表明字节不代表有效的x86指令。

如果你有一些字节,你不希望CPU作为代码执行,只需确保执行永远不会到达它们。 jmp超过非代码块,并在执行进入非代码之前进行exit系统调用。 (或永远循环)。

有些反汇编程序试图明白它们将代码拆解为哪些字节,但由于x86代码可以计算寄存器中的地址并跳转到它,因此并不总是可以确定将跳转哪些地址到。