[英]What happens if x86 jmp jumps to an address between two other consecutive valid ones?
在 x86 程序集的例程中,如果代码包含指向两个有效地址之间的有效地址的跳转,会发生什么情况? 这是一个人工示例:
0x0001: mov ...
0x0005: add ...
0x0009: jmp 0x0003
此外,我如何在本地机器或在线上进行类似的实验? 我检查了在线 x86 编辑器,如https://defuse.ca/online-x86-assembler.htm#disassembly ,但它不允许我放置像“0x0001”这样的指令地址。
没有“有效”或“无效”地址之类的东西。 每个地址都可以跳转到,如果对应的页面被映射,则执行。
那么当你在指令之间跳转时会发生什么? 好吧,处理器不知道您打算从哪里开始和结束指令。 它只是执行它看到的字节。 此代码将与您期望的不同,因为 CPU 尝试将其他指令的中间部分解析为操作码。
您的具体示例不足以让我说出指令的结果。 也许您可以提供一个完整的示例(包括机器代码),以便我可以给出更好的解释。
CPU 将在目标地址处开始解码指令。
您在反汇编中看到的指令流(当使用像objdump
这样的工具时)只是对程序可执行字节的一种解释,假设给定的起点。
碰巧“跳到指令的中间”是一种混淆技术,有时被恶意软件用来隐藏线性扫描反汇编程序(如objdump
)的程序语义。 更复杂的反汇编程序将尝试跟随这些“未对齐”的跳转,但这可能是不可能的,这取决于静态/动态确定什么可以/不能确定。
Linn 和 Debray的论文“对可执行代码进行混淆以提高对静态反汇编的抵抗力”更详细地讨论了这一点。
请参阅第 3.2 节“垃圾插入”。 您描述的场景是他们所说的“部分或完全重叠指令”,即对字节流的不同解释可以为重叠地址范围提供不同的汇编指令。
我最近在 codegolf 的“在 x86/x64 机器代码中打高尔夫球的技巧”中添加了一个关于跳过指令的技巧。 您会发现这些是有意应用到先前指令的一部分。 而且不仅仅是为了混淆。 这是该答案的全文:
跳过指令是与一个或多个后续操作码组合的操作码片段。 后续操作码可用于与前置跳过指令不同的入口点。 使用跳过指令代替无条件短跳转可以节省代码空间,速度更快,并设置诸如NC
(无进位)之类的附带状态。
我的示例都是针对 16 位实/虚拟 86 模式的,但是这些技术中有很多可以类似地用于 16 位保护模式,或者 32 位或 64 位模式。
11:跳过指令
常量 __TEST_IMM8、__TEST_IMM16 和 __TEST_OFS16_IMM8 被定义为这些指令的相应字节串。 它们可用于跳过适合以下 1、2 或 3 个字节的后续指令。 但是,请注意,它们修改了标志寄存器,包括始终设置 NC。 16 位偏移量加 16 位立即测试指令不包括在这些用途中,因为它可能访问段中偏移量 0FFFFh 处的字。 此外,所提供的 __TEST_OFS16_IMM8 应仅用于 86M,以避免访问超出段限制的数据。 在使用这些常量之一的 db 指令之后,应在括号中列出跳过的指令。
86 模式在 lmacros1.mac 323cc150061e (2021-08-29 21:45:54 +0200) 中定义:
%define __TEST_IMM8 0A8h ; changes flags, NC
%define __TEST_IMM16 0A9h ; changes flags, NC
; Longer NOPs require two bytes, like a short jump does.
; However they execute faster than unconditional jumps.
; This one reads random data in the stack segment.
; (Search for better ones.)
%define __TEST_OFS16_IMM8 0F6h,86h ; changes flags, NC
16 位模式下的0F6h,86h
操作码为test byte [bp + disp16], imm8
指令。 我相信我实际上并没有在任何地方使用这个。 (实际上,堆栈内存访问实际上可能比无条件短跳转慢。)
0A8h
是test al, imm8
在任何模式下的操作码。 0A9h
操作码在 32 位和 64 位模式下更改为test eax, imm32
形式的指令。
ldosboot boot32.asm 07f4ba0ef8cd (2021-09-10 22:45:32 +0200) 中的两个用例:
首先,为一个公共函数链接两个不同的入口点,这两个入口点都需要初始化一个字节大小的寄存器。 mov al, X
指令各占 2 个字节,因此可以使用__TEST_IMM16
跳过一个这样的指令。 (如果有两个以上的入口点,则可以重复此模式。)
error_fsiboot:
mov al,'I'
db __TEST_IMM16 ; (skip mov)
read_sector.err:
mov al, 'R' ; Disk 'R'ead error
error:
其次,某个入口点需要两个字节的额外拆卸,但可以与后续代码部分的失败案例共享。
mov bx, [VAR(para_per_sector)]
sub word [VAR(paras_left)], bx
jbe @F ; read enough -->
loop @BB
pop bx
pop cx
call clust_next
jnc next_load_cluster
inc ax
inc ax
test al, 8 ; set in 0FFF_FFF8h--0FFF_FFFFh,
; clear in 0, 1, and 0FFF_FFF7h
jz fsiboot_error_badchain
db __TEST_IMM16
@@:
pop bx
pop cx
call check_enough
jmp near word [VAR(fsiboot_table.success)]
这是inicomp lz4.asm 4d568330924c (2021-09-03 16:59:42 +0200)中的一个用例,我们依赖于test al, X
指令清除进位标志:
.success:
db __TEST_IMM8 ; (NC)
.error:
stc
retn
最后,这里是DOSLFN Version 0.41c (11/2012) 中一个非常相似的跳过指令的用法。 他们使用mov cx, imm16
而不是test ax, imm16
mov cx, imm16
状态标志没有影响,但会破坏cx
寄存器。 (操作码0B9h
在非 16 位模式下是mov ecx, imm32
,并写入完整的ecx
或rcx
寄存器。)
;THROW-Geschichten... [english: THROW stories...]
SetErr18:
mov al,18
db 0B9h ;mov cx,nnnn
SetErr5:
mov al,5
db 0B9h ;mov cx,nnnn
SetErr3:
mov al,3
db 0B9h ;mov cx,nnnn
SetErr2:
mov al,2
SetError:
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.