【问题标题】:Is there any way to analyze the "type" of register in x86 assembly source code?有没有办法分析x86汇编源代码中寄存器的“类型”?
【发布时间】:2014-01-05 15:28:39
【问题描述】:

所以基本上我要做的就是在我对汇编代码的分析任务中区分数据和内存地址。

这是一个我很难处理的例子。

假设我们在 .data 部分声明了一个变量 val

0x08048054    01 00 00 00

这里是反汇编ELF文件的一行汇编代码。

mov    $0x08048054,  %eax

所以这可能是变量val的间接引用,像这样:

    mov    $0x8048054,%eax
    mov    %edx,0x4(%esp)
    mov    %eax,(%esp)
    call printf

然后我将 $0x8048054 转换成变量名 val 像这样:

    mov    val,%eax
    mov    %edx,0x4(%esp)
    mov    %eax,(%esp)
    call printf

但还有另一种情况,0x8048054 只是在一次计算中用作数字

    mov    $0x8048054,%eax
    add     0x8(%ebp), %eax

这可能等于(我知道我们在实际代码中几乎看不到这一点,但这是可能的)

    b = 0x8048054 + argc;

在这种情况下,我不应该将 $0x8048054 重新写入 val

所以我的想法是,如果我能弄清楚 %eax 寄存器的类型,我大概可以区分这两种情况。

  • 对于第一种情况,%eax的类型是指针
  • 第二个,类型为整数

我的方法对吗?

谁能帮帮我?

谢谢!

【问题讨论】:

  • 检查内存段,如/proc/self/maps 所示。如果它没有指向有效的数据部分,那么它绝对不是指针。如果它指向一个代码部分,它可能是一个函数指针 - 但它们永远不会被取消引用。

标签: c linux assembly x86 disassembly


【解决方案1】:

“类型”的一个视图是应用于值的一组操作。

因此,了解寄存器(或内存位置)中值的“类型”的方法是确定程序对其应用的操作。应用于寄存器的每个操作都建议了一组可能的值类型,例如“类型约束”。

如果在操作中使用寄存器来确定地址,这反过来会导致内存提取(x86 LEA 指令“形成地址”,但不会导致内存提取!),那么它是某种的指针。什么样的内存获取提示指针的类型;如果是字节提取,它可能是“指向 char 的指针”,如果它是对浮点单元的值的提取,它可能是“指向双精度数的指针”。因此,寄存器的使用方式建立了一些类型约束(例如,“可能是类型 T”)。

如果寄存器与另一个相加或相加,它可能是一个指针(例如,指针算术)或一个数字(整数或自然数)。如果寄存器被乘或除,它可能不是指针。

但这些分析仅限于您可以通过直接检查使用寄存器值的少数指令来确定的内容(例如,可以通过特定寄存器值“到达”的那些指令)。

但是,许多机器操作只是复制值,通常是通过寄存器。您真正想要做的是数据流分析,了解寄存器值的来源和去向。对流入、流入或流出寄存器的值的所有运算符都应用于建立类型约束。类型的更好表征是(数据)流经寄存器的值的类型约束的交集。 (您必须担心是否发生了不可见的强制:指向字符串的指针,可以在许多架构上“不可见地转换”为指向其第一个字符的指针,而无需任何特定的机器指令)。

所以你的类型推断过程需要对整个程序进行数据流分析(而且由于部分数据流依赖于值的类型,这可能是迭代的),估计每个值的类型的交集,然后考虑是否可能发生隐含转换。 (您可能会在脑海中执行此推理过程,但如果您必须在大型程序上执行此操作,您将真的需要工具来管理庞大的数据量)。

一般来说,你不能完美地做到这一点;可以很容易地将类型推断转化为图灵停止问题:

if Turing(x) then op1(register1) else op2(register1) endif

[所以,寄存器总是只在 op1 中使用还是只在 op2 中使用?] 所以你对类型的估计持保留态度。

【讨论】:

    【解决方案2】:

    看起来您走在正确的轨道上 - 通常,指针和恰好看起来像内存地址的数字之间的区别在于指针将在某处被取消引用。显然,您只能在它发生时观察到这一点,因此您将不得不在该值的生命周期内分析代码以了解它是如何使用的。

    如果一个值最终在一个寄存器中,然后用作内存操作的基址寄存器,它是一个指针。其他任何东西都是一个看起来像指针的数字除非另有证明。可能会有一些捷径,例如将其作为参数传递给您知道需要指针的函数(如果您可以首先假设代码是正确的)。

    复杂性在于,该值可能会被加载、添加到另一个值、推入堆栈、传递、隐藏在另一个变量中等,最终由程序的完全不同的部分重新加载和取消引用.

    要获得更多想法,我建议查看 OS 程序加载器的作用,因为这通常需要检测和修复指针,尤其是对于可重定位代码。

    【讨论】:

    • 据我所知,OS loader 没有任何有用的逻辑。它只是从编译器/链接器接收所有指针的列表。
    • @anatolyg 无论哪种方式,你都会得到一些信息,上面写着“这些东西是指针”,这为你节省了一堆静态代码分析。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-11-21
    • 2015-02-24
    • 1970-01-01
    • 2013-04-15
    • 2017-10-20
    相关资源
    最近更新 更多