【问题标题】:C/C++ exposure of functions / methods vs JavaC/C++ 暴露函数/方法 vs Java
【发布时间】:2018-08-08 16:59:45
【问题描述】:

Minecraft Modding 的世界让我对 Java 和 C/C++ 库之间的机制差异感到好奇,以允许在外部调用库中的方法/函数。

我的理解是,Minecraft Modding 的出现是由于能够对 Java 进行反编译/反射,以便对可以从库中调用的类和方法进行逆向工程。我相信 Java 类规范包含大量关于类结构的元数据,允许以非预期方式使用代码。

有一些混淆工具试图让 Java 逆向工程变得更加困难,但总体而言,这似乎很难防止。

我没有 C/C++ 方面的知识深度,不知道在那里可以做到什么程度。

对于 C/C++ 代码,提前本地编译。最终结果是特定于该平台的机器代码组合。 C/C++ 具有外部化函数的概念,以便它们可以从库或可执行文件外部公开。一些库也有入口点。

通常在连接到外部函数时,会有一个头文件列出库中可用于编写代码的函数。

我认为需要一种机制将公开的函数映射到库/可执行机器代码程序集中的地址,以便在正确的位置进行函数调用。

通常将函数调用与地址连接在一起是链接器的工作。链接器仍然需要以某种方式知道在哪里可以找到这些函数。

这让我想知道是否从根本上可以调用非导出函数。如果是这样,这是否需要能够定位它们的地址并理解它们的参数格式?

据我了解,C/C++ 中的函数调用通常是通过将参数分配给简单函数的寄存器或更复杂函数的参数数组来完成的。

我不知道在本机代码中调用非公共 API 的做法是否普遍 或者如果这样做的固有困难使本机代码在这种使用中非常安全。

【问题讨论】:

  • 请注意,没有“C/C++”之类的语言。虽然存在两种 非常 不同的语言 C 和 C++。
  • 这是对的,但 C++ 以 C 为基础构建的方式旨在允许它仍然与它互操作并保持兼容性级别。 C++ 运行时建立在 C 功能之上,例如在使用“new”创建对象或删除它们时会在后台调用 alloc 和 free。
  • @CarlJokl,您的假设非常简单。 C++ 允许运算符和函数重载,例如,你要避免的东西,而且根本不明显......只是举一个例子说明它们是多么不同的语言。
  • 语言的差异并不是重点。 C++ 可以直接调用 C 函数,而无需通过兼容层。例如,C++ 程序可以使用普通 C 中公开的 OpenGL 函数。C 程序可以调用以 C++ 实现的 Direct3D(尽管更混乱)。这些都不需要像 JNI 或 PInvoke 这样的本机 ABI 翻译层。 Objective-C 也可以直接调用 C,但是是一种非常不同的语言风格。所以我说的是语言的直接互操作性,而不是特定的语法。
  • 等价的分组可能是 Java、Scala、Groovy、Clojure 等,它们的语法非常不同,但由于在 Java 虚拟机上运行,​​它们能够相互调用。等价物也将是 .Net 系列,其中 C# .net 和 VB .net 可以调用公共库,即使语法非常不同,它在 CLR 上运行并且具有公共语言接口,因此语言可以相对直接地相互调用。跨度>

标签: java c++ c reverse-engineering libraries


【解决方案1】:

首先,有一些工具(质量和功能各不相同)可以将编译后的机器代码逆向工程回原始语言 [或另一种语言,就此而言]。这样做最大的问题是C、C++等语言,结构中成员的名字是没有名字的,经常会变得“扁平化”,所以原来是这样的:

 struct user
 {
    std::string name;
    int age;
    int score;
 };

会变成:

 struct s0
 {
     char *f0;
     char *f1;
     int f2;
     int f3;
 };

[当然注意std::string可能有十几种不同的实现方式,“两个指针”只是一种可能的变体]

当然,如果有描述库如何工作的头文件,您可以使用其中的数据结构来获得更好的类型信息。同样,如果文件中有调试信息,则可以使用它以更好的方式形成数据结构和变量名。但是想要将这些东西保密的人(通常)不会使用调试符号发布代码,而只会发布实际必要的部分来调用公共功能。

但是,如果您了解这些是如何使用的 [或阅读一些显示“用户”的代码,您就可以知道姓名、年龄和分数是什么。

了解什么是数组以及什么是单独的字段也很困难。是哪个:

 struct
 {
    int x, y, z;
 };

 int arr[3];

几年前,我开始玩耐心纸牌游戏(类似于“纸牌”)。为此,我需要一种在屏幕上显示卡片的方法。所以我想“好吧,Windows 上现有的 Solitaire 有一个,我敢打赌我可以弄清楚如何使用它”,事实上,我做到了。我可以随心所欲地画梅花后或黑桃二。我从未完成实际的游戏部分,但我确实设法从非公共共享库中加载了绘图功能。无论如何都不是火箭科学(有些人为具有数千个函数和非常复杂的数据结构的商业游戏这样做 - 这有两三个你需要调用的函数),但我也没有花太多时间在它上面,如果我没记错的话,需要几个小时,从提出想法到做出“有效”的东西。

但是对于您问题的第二部分,插件接口(例如 Photoshop 的过滤器插件或视频编辑器中的转换)通常被实现为“共享库”(又名“动态链接库”,DLL)。

操作系统中有一些函数可以将共享库加载到内存中,并通过函数名称查询函数。这些函数的接口(通常)是预定义的,因此可以使用头文件中的函数指针原型来形成实际调用。

只要共享库的编译器和应用程序代码使用相同的 ABI(应用程序二进制接口),当涉及到参数如何从调用者传递到函数时,一切都应该解决 - 这不像编译器只是随机使用它喜欢的任何寄存器,参数以明确定义的顺序传递,哪个寄存器用于给定处理器架构的 ABI 规范定义的内容。 [如果你必须知道数据结构的内容,情况会变得更加复杂,并且这种结构有不同的版本——比如有人有一个包含两个指针(开始和结束)的std::string,无论出于何种原因,设计改为一个指针和一个长度——应用程序代码和共享库都需要用相同版本的std::string编译,否则会发生坏事!]

可以调用非公共 API 函数,但通过调用按名称查找函数的查询将无法发现它们 - 您必须找出其他方法 - 例如通过知道“这个函数是从函数 XYZ 开始的 132 字节”,当然,你也不会有函数原型。

当然还有一个额外的复杂性,即 Java 字节码可移植到许多不同的处理器架构上,机器代码只能在定义的一组处理器上运行 - x86 的代码适用于 Intel 和 AMD 处理器(可能还有一些其他处理器),代码ARM 处理器在使用 ARM 指令集开发的芯片中工作,依此类推。您必须为给定进程编译 C 或 C++ 代码。

【讨论】:

  • 感谢您的详细解答。这与我讨论的关于调用 Minecraft 世界生成算法的工具有关,以便尝试确定某些稀有生成的兴趣点的位置。到目前为止,我只看到工具为 Java 版本执行此操作。我质疑用原生(“基岩”)版本做同样的事情是否可行。并非不可能,但肯定要困难得多。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2023-03-12
  • 1970-01-01
  • 1970-01-01
  • 2016-12-21
  • 1970-01-01
  • 2010-12-05
  • 2016-08-17
相关资源
最近更新 更多