IDA Pro 中无法识别的程序

逆向工程 艾达
2021-06-24 17:09:50

对于爱好,我只是想反转和旧的 DOS 游戏可执行文件。

我对倒车很陌生,所以我很难理解 IDA Pro 的一些内容。我让程序选择最适合的策略来反汇编可执行文件,它似乎工作正常,但有些程序无法识别(标记为红色),如图所示:

在此处输入图片说明

现在我看到我可以明确地将它标记为一个函数,但是我无法找到任何外部参照(也是因为我猜如果是这样的话,IDA 已经将它们变成了过程)。

所以我想知道这些函数的确切目的是什么,它们是通过动态生成地址来调用的还是什么?就像CALL对寄存器内容的指令。

一个附带问题:我看到二进制文件已分为 4 个段类别:

  • 代码(明确意图)
  • 数据(明确意图)
  • 存根
  • 覆盖

虽然前两个来自 C/C++ 环境非常清楚,但我正试图准确了解其他两个是什么。

如果我理解正确的话,存根段只是通往远离当前执行段的过程的桥梁,而覆盖段是在运行时在同一块内存上替换的段,以使程序可以使用比可寻址空间多得多的代码. 以前有没有关于这种内存管理的好教程?

2个回答

可执行文件中未使用的函数

对此有多种解释。

正如Itsbriany所说,混淆,但我认为这在旧的DOS可执行文件中并不是什么大事。

不需要的库函数。例如,如果您的代码链接到数学库(那些旧处理器通常没有用于浮点的数学协处理器),您通常会获得所有数学仿真。您的程序可能sqrt用于距离计算,但从不使用sin,cos和类似的三角函数,但它们以某种方式联系在一起。

函数指针。如果您的原始 C 程序有类似的内容, int (*funcs())[]={func1, func2, func3}; int result=(func[index])(); 并且这些是,唯一引用,那么 ida 将不会将它们识别为正在使用。但是,您会在其中找到数据外部参照。func1func2func3

C++ 对象方法,虽然当 C++ 开始被使用时,32 位处理器已经很普遍,您可以使用 dos 扩展程序将程序编译为 32 位二进制文​​件。如果方法从不直接调用,只能通过类的 vtable 调用,则与函数指针相同。

原始源代码中的调试函数永远不会在完成的二进制文件中调用。如果程序员做了类似的事情

#ifdef DEBUG
some_function(parm1, parm2, another_parm);
#endif

忘了把some_function#ifdef为好,然后完成二进制文件将仍然有some_function,但它没有呼叫。

不止一名程序员在代码的独立部分工作。一个程序员可能已经被分配到的读/写一个配置文件中的任务,他执行read_config()write_config()后来有人决定游戏应该只用read_config(),配置部分应该在单独的设置程序中完成。如果两个函数都在同一个源文件中,它们将被编译成 - 并从 - 一个单一的目标文件中链接。

旧 dos 程序中的叠加

我不知道有什么好的教程,但这里有关于叠加层的基本信息:

您需要识别运行时不需要彼此的代码部分。例如,您的程序可能有一组函数来导航世界地图,一组不同的函数来导航地牢,还有第三组函数来与店主互动。

你会编译这些分开,所以你得到4个文件:main_game.exeworld.ovldungeon.ovl,和shopkeeper.ovl(糟糕,文件名有 8 个字符的限制。)main_game.exe将被加载到低内存中,并计算要加载的覆盖层的一个段 - 全部在同一地址。最简单的方法是制作一个只有一个变量的段,它最后加载 - 该变量的地址将是加载覆盖的地址。让我们命名它overlay_start

为了将主 .exe 连接到覆盖层,每个覆盖层在开始时都会有一个跳转表。例如,在 world.ovl 中:

0000 jmp show_world_map
0004 jmp draw_player_on_world_map
0008 jmp draw_enemies_on_world_map
....

在 dungeon.ovl 中:

0000 jmp show_dungeon_map
....

现在你需要一堆函数来链接到你的主程序来调用这些覆盖函数,它们看起来基本相同:

extern int (*overlay_start())[];

void show_world_map() { load_overlay("world.ovl"); overlay_start[0](); }
void draw_player_on_world_map() { load_overlay("world.ovl"); overlay_start[1](); }    
void draw_enemies_on_world_map() { load_overlay("world.ovl"); overlay_start[2](); }

void show_dungeon_map() { load_overlay("dungeon.ovl"); overlay_start[0](); }
....

void load_overlay(char *filename) {
    /* omitting all the error checking, loops for more than 64KB, etc.) */
    FILE *fp=fopen(filename, "rb");
    fread(overlay_start, 1, SOME_LARGE_NUMBER, fp);
    fclose(fp);
}

(为了保持简洁,我在这个例子中省略了函数参数)。

根据您的编译器,其中大部分可以自动生成。这些功能,可能包括load_overlay功能,通常是进入该stub部分的内容。这只是一堆在链接时让链接器满意的函数,main_game.exe确保加载了正确的叠加层,调用预期的函数,然后返回。

您正在查看的代码可能是打包的,因为它有存根

软件供应商和恶意软件作者打包他们的可执行文件以增加逆向工程的难度,因为大多数二进制文件将显示为“压缩”数据(非机器指令)。只有最开始的指令应该是显而易见的,并且可能不会出现在 .text 段中。

如果是这种情况,则二进制文件将在执行期间自行解包。这个解包过程将“解压缩”“压缩”的数据并显示更多有效的机器指令,这些指令将在以后执行。解包存根还可以设置全局变量或动态分配内存,这些内存可能会保存指向您正在查看的该过程的函数指针。

此外,.stub段中的代码应该包含许多数学指令,例如xor reg1、reg2(其中 reg1 和 reg2 不同)、shrshlrorrol等……有很多跳转。

同样,您没有看到该函数的任何外部参照,因为 IDA 找不到在反汇编中调用的子例程,因为它可能被全局变量引用或在解包过程中通过堆上的动态分配引用。

该可执行文件的作者也有可能忘记调用该过程。