返回介绍

14.2 基于对 so 中的函数加密实现 so 加固

发布于 2024-10-10 22:32:19 字数 6700 浏览 0 评论 0 收藏 0

上一节介绍了对 so 中指定的 section 进行加密来实现对 so 加固方案。延续之前的内容来介绍一下如何对函数进行加密来实现加固。

14.2.1 技术原理

本节内容和上一节有很多类似的地方,这里就不做太多的解释了。和上一节内容唯一的不同点就是如何找到指定的函数的偏移地址和大小。

那么先来了解一下 so 中函数的表现形式:在 so 文件中,每个函数的结构描述是存放在.dynsym 段中的。每个函数的名称保存在.dynstr 段中的,类似于之前说过的每个 section 的名称都保存在.shstrtab 段中,所以在上一节中找到指定 section 的时候,就是通过每个 section 的 sh_name 字段到.shstrtab 中寻找名字即可,而且知道.shstrtab 这个 section 在头文件中是有一个 index 的,就是在所有段列表中的索引值,所以很好定位.shstrtab。

但是在本节内容中可能遇到一个问题,就是不能按照这种方式去查找指定函数名了,如下所示:

可能有的人意识到一个方法,就是可以通过 section 的 type 来获取.dynsym 和.dynstr。看到上图中.dynsym 类型是:DYNSYM,.dynstr 类型是 STRTAB,但是这种方法是不行的,因为这个 type 不是唯一的,也就说不同的 section,type 可能相同,没办法区分,比如.shstrtab 和.dynstr 的 type 都是 STRTAB.其实从这里就知道这两个段的区别了:.shstrtab 值存储段的名称,.dynstr 是存储 so 中的所有符号名称。

那么该怎么办呢?这时候再去看一下 so 文件格式说明(参见第 4 章)。看到有一个.hash 段,在上图中也可以看到的:由 Elf32_Word 对象组成的哈希表支持符号表访问。下面的例子有助于解释哈希表组织,如图 14-8 所示。

图 14-8 符号表索引

bucket 数组包含 nbucket 个项目,chain 数组包含 nchain 个项目,下标都是从 0 开始。bucket 和 chain 中都保存符号表索引。chain 表项和符号表存在对应。符号表项的数目应该和 nchain 相等,所以符号表的索引也可用来选取 chain 表项。哈希函数能够接受符号名并且返回一个可以用来计算 bucket 的索引。因此,如果哈希函数针对某个名字返回了数值 X,则 bucket[X%nbucket]给出了一个索引 y,该索引可用于符号表,也可用于 chain 表。如果符号表项不是所需要的,那么 chain[y]则给出了具有相同哈希值的下一个符号表项。可以沿着 chain 链一直搜索,直到所选中的符号表项包含了所需要的符号,或者 chain 项中包含值 STN_UNDEF。

上面的描述感觉有点复杂,其实说的简单点就是:用目标函数名在用 hash 函数得到一个 hash 值,然后再做一些计算就可以得到这个函数在.dynsym 段中这个函数对应的条目了。关于这个 hash 函数是公用的,在 Android 中的 bonic/linker.c 源码中也是可以找到的:

那么只要得到.hash 段即可,但是怎么获取到这个 section 中呢?so 中并没有对这个 section 进行数据结构的描述,有人可能想到了在上图中看到.hash 段的 type 是 HASH,那么再通过这个 type 来获取?但是之前说了,这个 type 不是唯一的,通过它来获取 section 是不靠谱的?那么该怎么办呢?这时候就要看一下程序头信息了,如下所示:

程序头信息是最后 so 被加载到内存中的映像描述,这里看到有一个.dynamic 段。再看看 so 文件的装载视图和链接视图,如图 14-9 所示。

在之前也说过,so 被加载到内存之后,就没有 section 了,对应的是 segment,也就是程序头中描述的结构,而且一个 segment 可以包含多个 section,相同的 section 可以被包含到不同的 segment 中。.dynamic 段一般用于动态链接,所以.dynsym 和.dynstr,.hash 肯定包含在这里。可以解析了程序头信息之后,通过 type 获取到.dynamic 程序头信息,然后获取到 segment 的偏移地址和大小,再进行解析成 elf32_dyn 结构。下面代码就是程序头的 type 类型和 dyn 结构描述,可以在 elf.h 中找到:

图 14-9 so 文件的装载视图和链接视图

这里的三个字段含义如下:

·d_tag:标示,标示这个 dyn 是什么类型,是.dynsym 还是.dynstr 等。

·d_val:section 的大小。

·d_ptr:section 的偏移地址。

细心的读者可能会发现一个问题,就是在这里寻找.dynamic 也是通过类型的,然后再找到对应的 section。这种方式和之前说的通过 type 来寻找 section 有两个不同之处:

·在程序头信息中,type 标示.dynamic 段是唯一的,所以可以通过 type 来进行寻找。

·看到上面的链接视图和装载视图发现,这种通过程序头中的信息来查找.dysym 等 section 靠谱点,因为当 so 被加载到内存中,就不存在了 section 了,只有 segment 了。

14.2.2 实现方案

本节用一个案例来展示 so 加固方法,即编写 native 程序直接返回字符串给 Java 层进行展示。需要做的是对 Java_com_example_shelldemo2_MainActivity_getString 函数进行加密。加密和解密都是基于装载视图实现。注意,被加密函数如果用 static 声明,那么函数是不会出现在.dynsym 中,是无法在装载视图中通过函数名找到进行解密的。当然,也可以采用取巧方式,类似上节,把地址和长度信息写入 so 头中实现。Java_com_example_shelldemo2_MainActivity_getString 需要被调用,那么一定是能在.dynsym 找到的。

1.加密流程

1)读取文件头,获取 e_phoff、e_phentsize 和 e_phnum 信息。

2)通过 Elf32_Phdr 中的 p_type 字段,找到 DYNAMIC。从下图可以看出,其实 DYNAMIC 就是.dynamic section。从 p_offset 和 p_filesz 字段得到文件中的起始位置和长度。

3)遍历.dynamic,找到.dynsym、.dynstr、.hash section 文件中的偏移和.dynstr 的大小。在我的测试环境下,fedora 14 和 windows7 Cygwin x64 中 elf.h 定义.hash 的 d_tag 标示是:DT_GNU_HASH;而安卓源码中的是:DT_HASH。

4)根据函数名称,计算 hash 值。

5)根据 hash 值,找到下标 hash%nbuckets 的 bucket;根据 bucket 中的值,读取.dynsym 中对应索引的 Elf32_Sym 符号;从符号的 st_name 索引找到在.dynstr 中对应的字符串与函数名进行比较。若不等,则根据 chain[hash%nbuckets]找下一个 Elf32_Sym 符号,直到找到或者 chain 终止为止。这里叙述得有些复杂,直接上代码表示:

6)找到函数对应的 Elf32_Sym 符号后,即可根据 st_value 和 st_size 字段找到函数的位置和大小。

7)后面的步骤就和上节相同了,这里就不赘述。

2.解密流程

解密流程为加密逆过程,大体相同,只有一些细微的区别,具体如下:

·找到 so 文件在内存中的起始地址。

·通过 so 文件头找到 Phdr;从 Phdr 找到 PT_DYNAMIC 后,需取 p_vaddr 和 p_filesz 字段,并非 p_offset,这里需要注意。

·对内存区域数据的解密,也需要注意读写权限问题。

14.2.3 代码实现

1.native 程序

代码如下:

代码逻辑和前一节的加密 section 中的代码类似,只是在寻找函数的地方有点不同,在加密的代码中说明一下。

2.加密程序

代码如下:

解密程序需要说明一下。

1)定位到.dynamic 的 segment,解析成 elf32_dyn 结构信息:

这里有一个解析 elf32_dyn 结构:

需要注意的是,elf32_dyn 中用到了联合体 union 结构,Java 中是不存在这个类型的,所以需要了解这个联合体的含义,虽然是三个字段,但是大小是 8 个字节,而不是 12 字节。dyn.d_val 和 dyn.d_val 是在一个联合体中的。

2)计算目标函数的 hash 值,得到函数的偏移值和大小:

逻辑有点绕,了解原理即可。其中 nbucket 和 nchain、bucket[i]和 chain[i]都是 4 个字节,它们的值就是目标函数在.dynsym 中的位置。

上面对 so 中的函数加密成功了,那么下面来验证加密,直接使用 IDA 进行查看,如图 14-10 所示。

可以看到,加密的函数内容已经面目全非了,看不到信息了。比较加密前的文件,如图 14-11 所示。

提示:案例下载地址为 http://download.csdn.net/detail/jiangwei0910410003/9289009

3.测试 Android 项目

用加密之后的 so 文件来测试一下:

图 14-10 IDA 查看 so 文件

图 14-11 加密前 so 的内容

运行结果如图 14-12 所示。

图 14-12 运行结果

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文