这可能是一个非常简单的问题,因为我不太习惯 OllyDBG 反汇编程序的语法。
以下汇编语句是否执行此操作:
MOV EAX, DWORD PTR [ESI + 14]
大致翻译成这样的C代码:
eax = *(esi + 0x14);
我是否正确理解了语法还是我误解了这一点?
这可能是一个非常简单的问题,因为我不太习惯 OllyDBG 反汇编程序的语法。
以下汇编语句是否执行此操作:
MOV EAX, DWORD PTR [ESI + 14]
大致翻译成这样的C代码:
eax = *(esi + 0x14);
我是否正确理解了语法还是我误解了这一点?
@DCoder肯定已经回答了这个问题,所以这里只是一些笔记,或者,至少它开始是一个简短的笔记,最后变成了一个怪物。
OllyDbgMASM
默认使用(有一些扩展)。换句话说:
operation target, source
其他语法在(取决于版本)下可用:
例如IDEAL
,HLA
和AT&T
。
对于反汇编代码的外观,还有很多其他选项。点击周围。这些变化很容易找到合适的。
数字总是十六进制,但没有任何符号,如0x
或h
(为了紧凑,我猜 - 看起来更干净(恕我直言))。在其他地方,就像下面反汇编的详细说明一样,可以看到例如以 10 为基数的数字——然后在末尾用一个点表示。例如 0x10 (16.)
(在这里我大步走了……)
(谈论英特尔)
首先像那些在表x86asm.net和沙堆是明确与汇编代码工作的宝贵资产。但是,还应该具有:
来自Intel® 64 and IA-32 Architectures Software Developer Manuals。
有很多优秀的部分和制度是如何缝合在一起的描述,以及如何操作会影响整体系统的寄存器,标志,堆栈等读取例如6.2 STACKS
,3.4 BASIC PROGRAM EXECUTION REGISTERS
,CHAPTER 4 DATA TYPES
从“开发商”卷。
如前所述,x86amd 和 Sandpile 是很好的资源,但是当您对说明感到疑惑时,手册也是一个不错的选择;“指令集参考 AZ”。
你的整行可能是这样的:
00406ED6 8B46 14 MOV EAX,DWORD PTR DS:[ESI+14]
; or
00406ED6 8B46 14 MOV EAX,DWORD PTR [ESI+14]
(取决于选项和始终显示默认段。)
在这种情况下,我们可以将二进制文件拆分为:
8B46 14
| | |
| | +---> Displacement
| +------> ModR/M
+--------> Opcode
请注意,操作码和其他字段之前也可以有前缀。详细看说明书。例如 AZ 手册中的“第 2 章指令格式”。
找到MOV
操作,你会看到:
Opcode Instruction Op/En 64-bit Compat Description
…
8B /r MOV r32,r/m32 RM Valid Valid Move r/m32 to r32.
| |
| +---> source
+-------> destination
…
指令操作数编码
Op/En Operand1 Operand2 Operand3 Operand4
RM ModRM:reg (w) ModRM:r/m (r) NA NA
有关代码的详细信息,请阅读“3.1 解释指令参考页”。
总之MOV - mov表说:
8B : Opcode.
/r : ModR/M byte follows opcode that contains register and r/m operand.
r32 : One of the doubleword general-purpose registers.
r/m32: Doubleword general-purpose register or memory operand.
RM : Code for "Instruction Operand Encoding"-table.
该指令操作数编码表说:
reg : Operand 1 is defined by the reg bits in the ModR/M byte.
(w) : Value is written.
r/m : Operand 2 is Mod+R/M bits of ModR/M byte.
(r) : Value is read.
行。现在我要深入这里,却无法阻止自己。(通常会发现了解构建块有助于理解该过程。)
ModR/M 字节是0x46
二进制形式的:
7,6 5,4,3 2,1,0 (Bit number)
0x46: 01 000 110
| | |
| | +---> R/M
| +-----------> REG/OpExt
+------------------> Mod
000
REG 字段的值转换为EAX
ESI+disp8
(参考“2.1.5 ModR/M 和 SIB 字节的寻址模式编码”表 2-2,在 AZ 参考中)。
铂。2. 告诉我们一个 8 位值,8 位位移字节,跟在 ModR/M 字节之后,应该添加到 的值上ESI
。相比之下,如果有 32 位位移或寄存器操作码+ModR/M 的将是:
32-bit displacement General-purpose register
+-----> MOV r32,r/m32 +-----> MOV r32,r/m32
| |
8Bh 86h 8Bh C1h
| +--> EAX | +--> EAX
| | | |
+---> 10 000 110 b +---> 11 000 001 b
| | | |
+---+---+ +---+---+
| |
v v
ESI + disp32 ECX
因为我们有一个disp8
下一个字节是一个 1 字节的值,应该添加到 ESI 的值。在这种情况下0x14
。
请注意,此字节已签名,因此例如0xfe
将意味着ESI - 0x02
.
ESI 是指向 DS 指向的段中数据的指针。
段选择器由三个值组成:
15 - 3 2 1 - 0 (Bits)
|-------------|-----------------|---------------------------|
| Index | Table Indicator | Requested Privilege Level |
+-------------+-----------------+---------------------------+
所以说 selector = 0x0023 我们有:
0x23 0000000000100 0 11 b
| | |
| | +----> RPL : 3 = User land, (0 is kernel)
| +-------> TI : 0 = GDT (GDT or LDT)
+---------------> Index: 4 Multiplied by 8 and added to TI
段寄存器(CS、DS、SS、ES、FS 和 GS)设计用于保存代码、堆栈或数据的选择器。这是为了降低复杂性并提高效率。
这些寄存器中的每一个还有一个隐藏部分,即“影子寄存器”或“描述符缓存”,用于保存基地址、段限制和访问控制信息。当段选择器加载到段寄存器的可见部分时,这些值由处理器自动加载。
| Segment Selector | Shadow Register |
+------------------+--------------------------+
| Idx | TI | RPL | BASE | Seg Lim | Access | CS, SS, DS, ES, FS, GS
+------------------+--------------------------+
BASE 地址是线性地址。ES、DS 和 SS 不用于 64 位模式。
从段地址 ESI+disp8 读取 32 位值。例子:
ESI = 0x005056A0
Dump of DS segment:
0 1 2 3 4 5 6 7 8 9 a b c d e f
005056A0 00 00 00 00 9C 8F 41 7E 4C 1F 42 00 C0 1E 42 00 ....œA~LB.ÀB.
005056B0 E0 1F 42 00 70 20 42 00 48 21 42 00 4A A8 42 7E àB.p B.H!B.J¨B~
ESI + 0x14 = 0x005056B4 => 70 20 42 00 …
EAX = (DWORD)70 20 42 00 = 00 42 20 70 (4333680.)
你的例子的一个问题是它esi
是一个整数(严格来说)。但是,该值可以是段地址之一。然后你必须考虑到每个段都有一个基地址,(偏移量)——如:
seg = malloc(4096);
seg[0]
|
+---> at base address, e.g. 0x505000
+----------------+
| |
| |
…
505000 | | seg[00 - 0f]
505010 | | seg[10 - 1f]
505020 | | seg[20 - 2f]
…
在这种情况下,由于是 ESI,该段将是 DS 指向的段。
要在 C 中模拟这一点,您需要通用寄存器的变量,但您还需要创建段(从哪里读取/写入数据。)大致这样的代码可能是这样的:
void dword_m2r(uint32_t *x, struct segment *seg, uint32_t offset)
{
*x = *((uint32_t*)(seg->data + (offset - seg->base)));
}
dword_m2r(&eax, &ds, esi + 0x14);
地点struct segment
和地点ds
:
struct segment {
u8 *data;
u32 base;
u32 size;
u32 eip;
};
struct segment ds;
ds.base = 0x00505000;
ds.size = 0x3000;
ds.data = malloc(ds.size);
ds.eip = 0x00;
为了进一步发展这个概念,你可以创建另一个struct
带有寄存器的,使用寄存器的定义或变量,添加默认段等。
对于基于英特尔的架构,这可能是朝着这个方向发展的(作为一个不太好的开始):
#include <stdint.h>
#define u64 uint64_t
#define u32 uint32_t
#define u16 uint16_t
#define u8 uint8_t
union gen_reg {
u64 r64;
u32 r32;
u16 r16;
u8 l8;
};
struct CPU {
union gen_reg accumulator;
u8 *ah;
union gen_reg counter;
u8 *ch;
…
struct segment s_stack;
struct segment s_code;
struct segment s_data;
…
u32 eflags;
u32 eip;
…
};
#define RAX CPU.accumulator.rax
#define EAX CPU.accumulator.eax
#define AX CPU.accumulator.ax
#define AH *((u8*)&AX + 1)
#define AL CPU.accumulator.al
…
/* and then some variant of */
ESI = 0x00505123;
dword_m2r(&EAX, &DS, ESI + 0x14);
对于更紧凑的方式,放弃 ptr 以H
注册等,请查看例如virtualbox的代码库。注意:大多数编译器需要某种形式的 pack 指令来防止填充结构中的位——这样,例如 AH 和 AL 确实与 AX 的正确字节对齐。
的DWORD PTR [expression]
语法的意思是“取的值expression
,把它解释为一个地址,并且访问图4(的大小DWORD
)字节开始与该地址”。但是汇编数据类型与 C 的数据类型有很大不同,因此可以通过这种方式访问许多 C 类型。
你提供的指令基本上等同于C代码:
typedef dword_t ...;
dword_t eax = *(dword_t *)((char *)esi + 0x14);
此指令可用于访问4个连续字节不管那些字节的C类型是-在上面的行,则可以(在32位系统)定义dword_t
为int
,float
,void *
或另一种类型的适当的尺寸,并且它会仍然以相同的方式工作,它只是从一个地方传输到另一个地方的位和字节。使用相当智能的编译器,这甚至可以用于一步读取整个结构或数组,只要它们的长度足够小。
但是,如果您愿意的话,以后可以将其用作指针吗?
正如我所说,仅从这个上下文中就不可能说出这些字节的原始 C 类型是什么。您必须查看使用此值的其他地方并寻找特定类型的指标。如果您看到它在[eax]
或 类似的表达式中使用 - 它可能是一个指针。如果在更复杂的表达式中使用它,例如[eax + ecx]
,则两者之一是指针,另一个是该指针的数组索引/字节位移,但不知道从该行中哪个是哪个,需要更多上下文。