我试图反汇编ELF文件,它是在armv7a(Android)上执行的共享对象文件。我看到一个奇怪的街区。看来程序计数器寄存器PC设置为0。我错过了什么还是做错了什么?


该过程进入ARM模式下的0x1708。下面是我从ELF文件中反汇编的奇怪的asm代码块。 。值为0x170c(此文件存储在little-endian中)。然后继续。

; section: .plt
; function: function_1708 at 0x1708 -- 0x1718
0x1708:   04 e0 2d e5       str lr, [sp, #-4]!
0x170c:   04 e0 9f e5       ldr lr, [pc, #4]
0x1710:   0e e0 8f e0       add lr, pc, lr
0x1714:   08 f0 be e5       ldr pc, [lr, #8]!
; data inside code section at 0x1718 -- 0x171c
0x1718:   b4 77 00 00                                        |.w..            |


而0x8ed4位于LR部分。在0x1718处为零。我从0x77b4追溯到这个奇怪的块,因此在执行该块之前不应修改任何数据。

我做错了什么,或者这是ARM体系结构的特定行为吗? />

评论

您是否熟悉其他汇编语言的GOT / PLT? (通常,x86更为人所知)。因为实际上您的问题与GOT / PLT行为密切相关,特别是与ARM体系结构有关。

不。我是逆向工程的新手。我正在搜索相关信息。有什么好读的书吗?

好吧,那我就写一个完整的答案。

请注意,这并非微不足道!如果您一开始不太了解,那是完全正常的。尝试获取调试器并逐步执行程序以更好地理解(并保持信心!)。

#1 楼

实际上,您描述的行为来自GOT / PLT部分的通常行为。它们用于在运行时动态地将程序调用链接到共享库函数。
实际上,共享库可以在进程内存中的任何位置加载,无法静态预测其弹出位置。因此,GOT / PLT在运行时动态加载每个库函数的地址,并将其缓存以备将来使用。
其工作方式非常简单,PLT(过程链接表)只是一堆小代码小工具(对于程序中调用的每个库函数,一个函数,再加上位于PLT节开头的一个通用代码)。库函数的地址。
首先,由于尚未解析函数地址,因此GOT都设置为零。整个程序中执行的GOT填充都将被执行并调用新函数(某些功能可能很少被调用,而GOT中的地址可能很少被填充)。 PLT(请参见下图)并执行专门为此功能编写的代码(每个代码小工具都有其自己的偏移量,以便从GOT获取正确的地址)。但是,起初,GOT仅包含零,因此您需要对其进行初始化。
要对其进行初始化,请跳到PLT的最开始,找到程序要调用的库函数的地址。您执行此代码,然后在GOT中写入该函数的地址。

一旦缓存了该函数的地址,如果您调用了函数,然后直接跳转到它(如下图所示)。

对于ARM,您有两种类型的GOT / PLT模式:

直接PLT
间接PLT

在您的情况下,这是直接PLT(但是它似乎是很旧的代码,我怀疑一个旧的编译器左右,因为新的编译器最好使用ip代替lr)。 >请注意,PLT是静态部分,因此0x170c将始终保持不变(因此,pc将始终在0x170c处加载相同的值)。
参考文献


Eli Bendersky在共享库中的位置独立代码(PIC)。


Raspberry Pi中的ARM汇编程序–第27章,由Roger FerrerIbáñez编写。 />
用于Cortex-M和Cortex-R的ARM FDPIC工具集,内核和库。



评论


因此,在我的情况下,function_1708是PLT [0],这是调用解析器的代码,对吗?链接器如何知道将其内存地址填充到共享库的正确地址的位置?就我而言,0x8ed4不是.got中的第一个地址。第一个是0x8eac。填充解析器地址的策略取决于ELF文件的格式,运行的操作系统或体系结构或CPU?

– IvanaGyro
18-09-18在7:41

在程序每次运行的开始,链接器都会设置共享库在PLT中的地址,并且程序所使用的每个函数在当前共享库中都具有一个偏移量。这样,如果您知道共享库在内存中的地址以及特定函数的偏移量,则可以获取虚拟内存中该函数的地址。多亏了GOT(可缓存结果),您只需计算一次。

–恐怖
18-09-18在8:02



我想我问错了问题。但是,我在某些ELF规范中得到了答案。与@Igor Skochinsky♦回答的相同,链接器选择放置解析器地址的位置在ELF规范中定义。该定义取决于文件ELF的类型。无论如何,您对我有很大帮助。谢谢!

– IvanaGyro
18-09-18在16:26

#2 楼

虽然另一个答案没有错,但实际上并没有涵盖真正的问题:调用零地址不会导致崩溃的原因是什么? ,但实际上前几个GOT条目是特殊的/保留的,并由动态加载器/链接器(有时也称为解释器)用于其自身目的:


当前模块的符号,它是标签列表(GOT[0]条目)的开始,其中包含正确解析动态符号所需的各种信息。请参阅ELF规范。
在运行时使用指向_DYNAMIC结构的指针填充Elf32_Dyn,该结构包含该进程中存在的所有动态图像的列表。此列表位于动态链接器中(在Linux系统上为GOT[1],在Android系统上为link_map二进制文件)。 br />
您引用的代码段是在首次调用外部符号时调用的解析程序存根(您可以看到,所有GOT条目均已初始化为其地址ld.so)。它获取linker并跳转到它,它应该最终进入动态链接器,该链接器将查找符号,修补GOT条目并作为最后一步跳转到该函数。尚无(AFAIK)官方文档,仅是ELF ABI规范,glibc源代码和各种博客文章的随机片段。我建议您在调试器中单步执行此代码以查看实际发生的情况,并将其与源代码进行匹配。除了来自@perror的链接之外,我还找到了这篇文章,其中解释了一些保留的GOT条目(尽管对于x64 Linux而非ARM Android)。

评论


我试图找到ELF的官方规格。根据维基百科,有很多不同的规范。该组织开发的ELF,即UNIX系统实验室,已经不存在了。第一个规范由AT&T发布。我很好奇为什么这么多公司发布其ELF规范或将ELF规范包含在其ABI规范中,以及历史背景是什么。挺有趣的。

– IvanaGyro
18-09-18在16:14

@IgorSkochinsky:我不知道这一切!实际上,我假设IvenCJ7所做的分析是静态完成的,因此GOT最初设置为零,然后设置为PLT的开始。我现在看到我很可能错了。很好的解释伊戈尔(再次)和良好的链接,谢谢!

–恐怖
18-09-18在16:45