所以基本上我要做的就是在分析任务期间将数据与内存地址区分为汇编代码。
这是一个我很难处理的例子。
假设我们在.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寄存器的类型,我可以区分这两种情况。
我是以正确的方式吗?
有人能给我一些帮助吗?
谢谢!
答案 0 :(得分:3)
看起来你是在正确的轨道上 - 通常,指针和恰好看起来像内存地址的数字之间的区别在于指针将在某处被解除引用。显然你只能在它发生的时候观察它,所以你将不得不分析该值的生命周期代码,看它是如何使用的。
如果某个值最终出现在一个寄存器中,然后该寄存器用作存储器操作的基址寄存器,那么<em>就是一个指针。除非另有证明,否则其他任何东西都是一个看起来像指针的数字。可能有一些快捷方式,比如将它作为参数传递给你知道带有指针的函数(如果你可以假设代码首先是正确的那样)。
复杂化的原因在于,可以加载该值,将其添加到另一个值,推入堆栈,传递,隐藏在另一个变量等中,最后通过程序的完全不同部分重新加载和取消引用
对于更多想法,我建议查看OS程序加载器的功能,因为这通常需要检测和修复指针,特别是对于可重定位代码。
答案 1 :(得分:3)
“type”的一个视图是应用于值的一组操作。
因此,理解寄存器(或存储器位置)中值的“类型”的方法是确定程序应用于它的操作。应用于寄存器的每个操作建议值可以是一组可能的类型,例如“类型约束”。
如果在一个操作中使用一个寄存器来确定一个地址,这反过来会导致内存提取(x86 LEA指令“形成一个地址”,但不会导致内存提取!),那么它就是某种指针。什么样的内存提取提示指针的类型;如果是字节提取,则它可能是“指向char的指针”,如果它是向浮点单元提取值,则它可能是“指向double的指针”。因此,使用寄存器的方式建立了一些类型约束(例如,“可以是类型T”)。
如果寄存器被添加到另一个或添加到,则它可以是指针(例如,指针算术)或数字(整数或自然)。如果寄存器被多显或分割,则它可能不是指针。
但是这些分析仅限于您可以通过直接检查使用寄存器值的少数指令来确定的内容(例如,那些可以通过特定寄存器值“到达”的指令)。
但是,许多机器操作只是通过寄存器复制值。您真正想要做的是数据流分析寄存器值来自哪里以及它去往何处。流入,流入或流出寄存器的值的所有运算符都应用于建立类型约束。对类型的更好表征是(数据)流过寄存器的值的类型约束的交集。 (您必须担心是否发生了不可见的强制:指向字符串的指针,可以“无形地转换”为指向许多体系结构上的第一个字符的指针,而不需要任何特定的机器指令。)
所以你的类型推理过程需要对整个程序进行数据流分析(并且由于某些数据流取决于值的类型,这可能是迭代的),估计每个值的类型的交集,然后考虑是否可能发生隐含转换。 (您可以在脑海中进行推理过程,但如果您必须在大型程序中执行此操作,则需要使用工具来管理大量数据。)
一般来说,你不能完美地做到这一点;人们可以很容易地将类型推断转化为图灵停止问题:
if Turing(x) then op1(register1) else op2(register1) endif
[那么,寄存器是仅在op1中使用还是仅在op2中使用?]因此,您可以用一粒盐来估算类型。