数据类型和变量如何转换为机器码?

逆向工程 拆卸 部件 x86 机器码
2021-06-18 16:18:31

我知道每个 x86 指令都有一个操作码,但我不知道数据类型和变量如何转换为机器代码。所以我试图弄清楚。我写了一个简单的 C 程序并将其转换为汇编:

#include<stdio.h>

int variable1=1;
int variable2=1;

char variable3='A';
char variable4='A';

char name[20]="naveen prakash";


int main(int argc, char const *argv[])
{

char g[4]="hell";
int local_variable1=1;
int local_variable2=2;
int local_variable3;

variable1=0;
variable2=1;

 variable3='B';
 variable4='A';

 printf("HeLLO wORLD\n");
 getch();

}

上述程序的汇编(使用编译gcc -S hello.c):

.file   "hello.c"
.globl _variable1
    .data
    .align 4
_variable1:
    .long   1
.globl _variable2
    .align 4
_variable2:
    .long   1
.globl _variable3
_variable3:
    .byte   65
.globl _variable4
_variable4:
    .byte   65
.globl _name
_name:
    .ascii "naveen prakash\0"
    .space 5
    .def    ___main;    .scl    2;  .type   32; .endef
    .section .rdata,"dr"
LC1:
    .ascii "HeLLO wORLD\0"
LC0:
    .ascii "hell\0"
    .text
.globl _main
    .def    _main;  .scl    2;  .type   32; .endef
_main:
    pushl   %ebp
    movl    %esp, %ebp
    andl    $-16, %esp
    subl    $32, %esp
    call    ___main
    movl    LC0, %eax
    movl    %eax, 16(%esp)
    movl    $1, 20(%esp)
    movl    $2, 24(%esp)
    movl    $0, _variable1
    movl    $1, _variable2
    movb    $66, _variable3
    movb    $65, _variable4
    movl    $LC1, (%esp)
    call    _puts
    call    _getch
    leave
    ret
    .def    _puts;  .scl    2;  .type   32; .endef
    .def    _getch; .scl    2;  .type   32; .endef

我只能找出全局变量而找不到局部变量。然后我创建了上述程序的可执行文件并使用IDA,ollyDBG,PE Explorer并试图找到变量的位置,但我找不到。

当它转换成机器语言时,变量和数据类型声明会发生什么变化?

我读到那个.data段包含初始化数据,然后我试图找到我的初始化数据,但我找不到任何东西。我有一个文本“naveen prakash”,但在可执行文件的任何地方都找不到它。我使用十六进制编辑器查看二进制文件。

所以我的问题是,变量和数据类型如何在机器代码中表示,我们如何使用调试器找到它?

2个回答

这是 Nordwald 评论的扩展:

汇编程序中没有变量,因为它们是一个高级概念。有不同长度的位域,根据操作进行解释。这些可以驻留在堆栈上(本地)或某个内存位置(全局)。

HLL 与机器代码

当它转换成机器语言时,变量和数据类型声明会发生什么变化?

当用高级语言编写的代码通过编译翻译成机器语言时,变量和数据类型声明信息会丢失,因为机器语言中不存在变量和数据类型的概念。这是反编译具有挑战性的部分原因。

高级编程语言具有数据抽象,例如缓冲区、结构和局部变量,它们都可以帮助程序员和程序分析以可扩展的方式推理程序。在编译过程中,这些抽象被移除,因为代码被翻译成对寄存器和一个全局寻址内存区域的操作。1

高级语言是独立于机器的。为了让在 HLL 中编写的代码由其 CPU 在机器上执行,它需要被翻译成与目标机器的 CPU 指令集架构兼容的形式。指令集的示例是 x86、MIPS 和 ARM。我们可以将指令集视为特定于处理器的机器语言的表示。机器语言只处理表示常量、内存位置、CPU 寄存器和指令等信息的二进制值。

可执行二进制组织

然而,仅仅执行 HLL 到目标代码的翻译是不够的。信息必须以这样的方式组织在一个文件中,以便它可以被程序加载器映射到内存中并成为一个正在运行的进程。这就是二进制格式和系统应用程序二进制接口 (ABI) 的用武之地。

在进行二进制分析时,需要考虑以下几点:

  • 编译器工具链
  • 系统架构
  • 运行环境
  • 二进制格式

我相信gcc默认情况下会编写 ELF 二进制文件。ELF 二进制文件的结构使得内存分配给静态分配的变量,例如该.data部分中的全局变量诸如“hello world”之类的字符串文字存储在该.rodata部分中并通过指针访问。

当然,如果您正在分析的二进制文件是 PE 而不是 ELF 二进制文件,这无关紧要。无论哪种方式,您都应该查阅适合您系统的 ABI 文档。

无论如何,函数局部变量的空间将在虚拟内存中的运行时堆栈上分配,而不是在二进制文件中。这是静态内存分配和基于堆栈的分配之间的区别:

运行时子例程的每个实例在堆栈上都有自己的框架(也称为活动记录),包含参数和返回值、局部变量、临时变量和簿记信息。传递给后续例程的参数位于框架的顶部,在那里被调用者可以很容易地找到它们。其余信息的组织取决于实现:它因一种语言、机器和编译器而异。2

这将反映在编译器生成的代码中,因为是编译器负责生成管理运行时堆栈的代码。

也可以看看:

如何找出elf32-i386反汇编中的方法参数大小和类型?


1. TIE:二进制程序中类型的原理逆向工程

2. Scott, Michael L. 编程语言语用学。第 3 版。第 117 页

程序集使用内存中的指针表示变量。更好的问题是“变量在内存中如何表示的?”