OllyDBG 的反汇编语法和 C 等价物

逆向工程 拆卸 部件 x86 ollydbg
2021-06-13 20:43:06

这可能是一个非常简单的问题,因为我不太习惯 OllyDBG 反汇编程序的语法。

以下汇编语句是否执行此操作:

MOV EAX, DWORD PTR [ESI + 14]

大致翻译成这样的C代码:

eax = *(esi + 0x14);

我是否正确理解了语法还是我误解了这一点?

2个回答

@DCoder肯定已经回答了这个问题,所以这里只是一些笔记,或者,至少它开始是一个简短的笔记,最后变成了一个怪物


OllyDbgMASM默认使用(有一些扩展)。换句话说:

operation target, source

其他语法在(取决于版本)下可用:

  • 选项->调试选项->Disasm
  • 选项->代码

例如IDEALHLAAT&T

对于反汇编代码的外观,还有很多其他选项。点击周围。这些变化很容易找到合适的

数字总是十六进制,但没有任何符号,如0xh(为了紧凑,我猜 - 看起来更干净(恕我直言))。在其他地方,就像下面反汇编的详细说明一样,可以看到例如以 10 为基数的数字——然后在末尾用一个点表示。例如 0x10 (16.)


(在这里我大步走了……)

在阅读代码时

(谈论英特尔)

首先像那些在表x86asm.net沙堆是明确与汇编代码工作的宝贵资产。但是,还应该具有:

  • 英特尔® 64 位和 IA-32 架构软件开发人员手册,第 1 卷:基本架构。
  • 英特尔® 64 位和 IA-32 架构软件开发人员手册,第 2 卷(2A、2B 和 2C):指令集参考,AZ。
  • ……等等(还有一些收藏卷。)

来自Intel® 64 and IA-32 Architectures Software Developer Manuals

有很多优秀的部分和制度是如何缝合在一起的描述,以及如何操作会影响整体系统的寄存器,标志,堆栈等读取例如6.2 STACKS3.4 BASIC PROGRAM EXECUTION REGISTERSCHAPTER 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操作,你会看到:

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
  1. 000REG 字段的值转换为EAX
  2. Mod+R/M 转换为 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
  • GDT = 全局描述符表
  • LDT = 本地描述符表

段寄存器(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.)

在 C 中模拟

你的例子的一个问题是它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_tintfloatvoid *或另一种类型的适当的尺寸,并且它会仍然以相同的方式工作,它只是从一个地方传输到另一个地方的位和字节。使用相当智能的编译器,这甚至可以用于一步读取整个结构或数组,只要它们的长度足够小。

但是,如果您愿意的话,以后可以将其用作指针吗?

正如我所说,仅从这个上下文中就不可能说出这些字节的原始 C 类型是什么。您必须查看使用此值的其他地方并寻找特定类型的指标。如果您看到它在[eax]或 类似的表达式中使用 - 它可能是一个指针。如果在更复杂的表达式中使用它,例如[eax + ecx],则两者之一是指针,另一个是该指针的数组索引/字节位移,但不知道从该行中哪个是哪个,需要更多上下文。