|
|
1
Peter Cordes
6 年前
要解决这个问题,有三个原则需要理解。
-
汇编程序必须能够推断出正确的长度。
虽然Intel的语法没有使用像
AT&T syntax
汇编程序仍然需要找到操作数大小的方法。
模棱两可的指令
mov [var], 1
写为
movl $1, var
在AT&中;T语法,如果存储的大小为32位(请注意后缀
l
),因此很容易判断立即数操作数的大小。
接受Intel语法的汇编程序需要一种方法来推断此大小,有四种广泛使用的选项:
-
它是从另一个操作数推断出来的。
例如,当涉及寄存器时就是这种情况。
E、 g。
mov [var], dx
是一个16位存储。
-
这是明确规定的。
mov WORD [var], dx
MASM语法汇编程序需要
PTR
在大小之后,因为它们的大小说明符只允许在内存操作数上使用,而不允许在即时操作数或其他任何地方使用。
这是我喜欢的形式,因为它清晰、突出,而且不太容易出错(
mov WORD [var], edx
无效)。
-
它是根据上下文推断出来的。
var db 0
mov [var], 1 ; MASM/TASM only. associate sizes with labels
MASM语法汇编程序可以推断
var
声明为
db
它的大小是8位,存储也是8位(默认情况下)。
这是我不喜欢的形式,因为它使代码更难阅读(汇编的一个好处是指令语义的“局部性”),并且混合了类型之类的高级概念和存储大小之类的低级概念。这就是为什么
NASM's syntax doesn't support magical / non-local size association
。
-
情况就是这样
push
、分支和所有指令,其中操作数大小取决于内存模型或代码大小。
某些指令可以覆盖使用的实际大小,但默认值是明智的选择。(例如:。
push word 123
vs.
push 123
)
简而言之
必须
这是汇编程序告诉大小的一种方式,否则它将拒绝代码。(或者一些低质量的汇编程序,如emu8086,对于不明确的情况,有一个默认的操作数大小。)
如果您正在查看已反汇编的代码,反汇编程序通常会采取安全措施,并始终明确说明大小。
如果没有,则必须手动检查操作码,如果反汇编程序不显示操作码,则是时候更改它了。
反汇编程序很容易找到操作数的大小,因为它要反汇编的二进制代码与CPU执行的二进制代码相同,并且指令操作码对操作数大小进行编码。
-
C语言故意对C类型如何映射到位的数量不严格
试图从反汇编中推断变量的类型并非徒劳,但也必须考虑平台,而不仅仅是架构。
讨论了使用的主要模型
here
:
Datatype LP64 ILP64 LLP64 ILP32 LP32
char 8 8 8 8 8
short 16 16 16 16 16
_int32 32
int 32 64 32 32 16
long 64 64 32 32 32
long long 64 [64]
pointer 64 64 64 32 32
x86\u 64上的Windows使用LLP64。x86-64上的其他操作系统通常使用x86-64 System V ABI,这是一种LP64型号。
-
程序集没有类型,程序员可以利用它
Even compilers can exploit that
。
在链接a的情况下
bar
类型的变量
long long
(64位)与1进行OR运算,
clang
通过仅对低位字节进行ORing操作来保留REX前缀。如果立即使用两个dword加载或一个qword加载重新加载变量,则会导致存储转发暂停,因此这可能不是一个好的选择,尤其是在32位模式下
or dword [bar], 1
大小相同,可能会作为两个32位的半部分重新加载。
如果一个人不小心地查看反汇编代码,他们可以推断
酒吧
为8位。
这种部分访问变量或对象的技巧很常见。
为了正确猜测变量的大小,需要一些专业知识。
例如,结构成员通常是填充的,因此它们之间有未使用的空间,这可能会欺骗没有经验的用户,使其认为每个成员都比实际大小大。
烟囱有精确的对齐要求
may make widen the parameters size
。
经验法则是,编译器通常倾向于保持堆栈16字节对齐,并自然对齐所有变量。
Multiple narrow variables are packed into a single dword
。通过堆栈传递函数参数时,每个参数都被填充为32或64位,但这不适用于堆栈上的局部变量布局。
最后回答你的问题
是的,从第一段代码中可以假定
arg_0
是16位宽。
请注意,由于它是传递到堆栈上的函数arg,因此它实际上是32位的,但不使用高16位。
如果a
mov ecx, [ebp+arg_0]
出现在代码中的时间要晚于您对
arg\u 0
,它肯定至少是32位的。
它不太可能是64位的(64位类型在32位代码中很少见,我们可以打赌),所以我们可以断定它是32位的。
显然,第一个代码片段是那些只使用变量的一部分的技巧之一。
这就是你如何处理一个var大小的反向工程,你做一个猜测,验证它与代码的其余部分一致,如果不一致,重新访问它,重复。
随着时间的推移,你会做出几乎不需要修改的猜测。
|