吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 10754|回复: 53
上一主题 下一主题
收起左侧

[Android 原创] ELF 导入表/导出表 加固原理分析与实现

  [复制链接]
跳转到指定楼层
楼主
fnv1c 发表于 2021-8-6 01:07 回帖奖励
本帖最后由 fnv1c 于 2021-8-6 10:12 编辑

0x01 前言

ELF格式是被广泛应用的可执行文件、共享库格式。然而ELF文件的加固技术相对于PE文件而言,仍然较为落后。近年来,随着安卓系统的推广,承载native so的ELF格式也日趋流行,因而ELF文件加固技术有了较大提升。本文将讨论ELF动态库的导入/导出表加密的原理与实现(ELF加固的关键环节)。
理解此文可能需要的前置知识:ELF基本概念,ELF动态链接基本概念。
注:本文阐述的导入/导出对象为函数,实验平台为X64 Linux。

0x02 剖析符号动态解析(延迟绑定)

众所周知,很多可执行文件都用到了外部库提供的函数。那么函数又是如何被可执行文件找到,如何被调用的呢?这就涉及到ELF动态链接的知识了。ELF对外部函数的处理默认采用了延迟绑定技术,也就是说当且仅当用到此函数时,才会解析此函数地址(也有例外,例如开启FULL RELRO时,启动时即解析所有符号,本文不讨论此情况)。
这样做的好处是显然的,一方面缩短了应用程序启动时间(不需要在启动时解析所有导入的符号),另一方面不会造成过多的运行时开销。
导入函数的延迟绑定主要又两个表实现,一个是PLT表,一个是GOT表。PLT表负责处理函数解析与调用,GOT表存储解析后函数地址。下面演示延迟绑定的流程。
例如调用函数abc时 主程序:abc() 被编译为 call abc@plt (也就是说,plt段实际上存储的是与延迟绑定相关的指令。)
随后进入 abc@plt : jmp *(abc@got); push 123; jmp resolve_sym;
首先进行了间接跳转,跳到了GOT表中abc对应项目存储的地址。
如果abc已经被解析了,那么就会跳到abc函数的真实地址。
如果abc没有被解析 ,GOT中abc对应地址实际为abc@plt+6 也就是jmp指令后push 123;jmp resolve_sym对应的地址。 这对延迟绑定的实现至关重要。
resolve_sym会将GOT[1] (本ELF的link_map)压栈,并调用GOT[2] (_dl_runtime_resolve),对用到的符号进行首次解析。实际参数为_dl_runtime_resolve(GOT[1] (link_map),123);
动态链接器会通过link_map和123这个数字找到需要的符号,并解析调用,同时改写abc对应的GOT项目,使其指向abc函数真实地址。  

想一想:为什么使用PLT和GOT两个表,这样还引入了一次间接跳转,为什么不用性能更高的方法呢  

0x03 符号动态解析的静态分析

不管你是否看懂0x02的内容,相信你都不明白为什么_dl_runtime_resolve(GOT[1] (link_map),123);能成功地找到abc并且调用他。当然,123只是一个序号(reloc_index),需要配合ELF中的其他数据完成对符号的解析。
直接IDA分析比纸上谈兵要容易理解地多。下面以对printf函数的调用为例


link_map是包括ELF基址,dynamic段地址等信息的结构。

struct link_map  {    ElfW(Addr) l_addr;                /* ELF基址  */    

char *l_name;                     /* SONAME  */    

ElfW(Dyn) *l_ld;                  /* Dynamic段地址  */    

struct link_map *l_next, *l_prev; /* link_map链表,包含所有加载的动态库的link_map  */  

};

符号动态解析主要与.dynamic段的strtab,symtab,jmprel有关。  reloc_index指导动态链接器寻找本ELF的.dynamic段的jmprel节,找到其中的第reloc_index(6)条,其中记录了printf的got表偏移,printf函数对应的symtab_index(5),和相关符号信息。






随后动态链接器找到symtab,第5条即printf的符号信息,可以看到其记录了"printf"在strtab的位置,动态链接器获得符号名,进行解析。  

0x04 导入表加密之.dynamic部分加密

我们知道,.dynamic在动态库符号解析中,发挥着重要的作用。导入表加密的第一思路一定是在.dynamic段做文章。我们可以将关键的导入函数在.dynamic段的strtab节中对应的符号名加密。并且在函数被调用前,将strtab节中对应字符串解密,得益于延迟绑定特性,我们仍然能查找到正确的符号。但是对导入的静态分析却完全损坏了。  我们编写test.so,在constructor中使用printf函数。编译保存,用ida的patch功能将strtab节对应字符串"printf"改为"114514",保存。执行test.so,发现报错,找不到符号"114514"。



然后编写decrypt_import函数,通过dlinfo获取link_map,从而得到.dynamic段地址,寻找strtab节,将114514替换回"printf",之后再调用printf,发现一切正常。

静态分析可以发现,ida已经将114514识别为一个外部函数,imports中找不到printf。
  

0x05 导入表加密之GOT劫持

上文的加密方法十分巧妙,但也有脆弱性。首先,关键函数调用前,.dynamic中内容已经被解密,且不会再恢复加密状态。其次,GOT表中会存储真实函数地址,动态调试可以恢复真实导入表。那么,如何规避这两点问题?  我们知道,GOT表存储的是函数真实地址,若没有被解析,则指向函数解析的相关代码。如果我们劫持GOT表地址,指向我们定义的函数,会如何?
程序会忽略掉延迟绑定的全套流程,直接跳到我们的自定义函数。我们可以根据这一特性实现导入表加密。
编写test.so,在constructor中使用puts函数。编译保存。通过ida的patch功能将puts函数对应的symtab项修改,改成__stk_chk_failed的sym项,保存。
再编写fix_got函数,修改puts@got为puts_proxy。在puts_proxy中解析puts函数地址并调用。  



尝试运行,成功。

打开ida,逆向constructor,发现完全看不到使用puts的痕迹,imports也无puts。用到puts的函数的反编译结果也会出错。




0x06 导出表加密

之前我一直错误地认为ELF和PE格式一样,无法加密导出表,直到遇到了这个奇怪壳
如果你已经对elf的符号查找机制掌握透彻,也能想当然地得出导出表加密的方案。即对.dynamic动手脚。
上文分析的奇怪壳子用的是.dynamic重建,本文讨论.dynamic加密。
实际上,导出表查找也依赖dynamic段的symtab节,先通过hash链表找到可能的symtab_index,再依次查找,如果找到那么完成。我们可以先加密symtab节中的重要符号,然后在动态库被加载后解密symtab节,这样就实现了运行时可加载,但静态分析找不到的导出表加密。
编写test.so,hide_me为隐藏关键函数,编译,用ida修改symtab,将其对应的strtab的"hide_me"改为"114514",编写fix_export函数,解密strtab的对应内容。


编写load.c,导入test.so并且通过dlsym查找hide_me并调用。尝试运行,成功。

静态分析软件显示test.so的exports中没有hide_me,只有114514

0x07 结语

之前CSAPP的动态链接部分看得人一知半解,动手实现才发现其中奥秘。也算是“ 纸上得来终觉浅,绝知此事要躬行。”

0x08 参考链接

如果你想了解更多,不妨看看下面内容
dl-resolve
.dynamic
符号表hash
符号表hash
FULL RELRO

图片.png (33.25 KB, 下载次数: 1)

图片.png

play.zip

3.14 KB, 阅读权限: 10, 下载次数: 46, 下载积分: 吾爱币 -1 CB

文中用到的代码片断

免费评分

参与人数 31吾爱币 +28 热心值 +29 收起 理由
怦然心动 + 1 + 1 谢谢@Thanks!
DS_FLY100 + 1 + 1 谢谢@Thanks!
菜鸟也想飞 + 1 + 1 谢谢@Thanks!
h4rry + 1 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
苏打水vip + 1 我很赞同!
cozudayo + 1 已经处理,感谢您对吾爱破解论坛的支持!
独行风云 + 1 + 1 热心回复!
victos + 1 + 1 谢谢@Thanks!
fengbolee + 2 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
a3586597 + 1 热心回复!
DancingLight + 1 + 1 用心讨论,共获提升!
GuXing + 1 我很赞同!
it_harry + 1 + 1 谢谢@Thanks!
不爱everyone + 1 用心讨论,共获提升!
theStyx + 2 + 1 谢谢@Thanks!
呜呜啊哎 + 1 + 1 谢谢@Thanks!
404undefined + 1 + 1 热心回复!
KylinYang + 1 + 1 我很赞同!
ZiZ6bvD9H + 1 + 1 谢谢@Thanks!
舒默哦 + 1 + 1 谢谢@Thanks!
lllliiii + 1 + 1 我很赞同!
GuiXiaoQi + 1 我很赞同!
努力加载中 + 1 + 1 谢谢@Thanks!
cxrstc + 1 谢谢@Thanks!
xmmyzht + 1 + 1 谢谢@Thanks!
heartfilia + 1 我很赞同!
zhefox + 1 + 1 我很赞同!
sam喵喵 + 1 谢谢@Thanks!
xiahhhr + 1 + 1 谢谢@Thanks!
XhyEax + 3 + 1 谢谢@Thanks!
woyucheng + 1 + 1 谢谢@Thanks!

查看全部评分

本帖被以下淘专辑推荐:

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

推荐
 楼主| fnv1c 发表于 2021-8-20 18:04 |楼主
dogydogyfly 发表于 2021-8-20 16:50
嗯 感谢解答
我想知道 为什么一定需要indirect jmp 这一步 这相当于还是通过代码段到plt再到got 只是时 ...

我网上查了下资料,没找到答案。我个人觉得这个和linker的实现相关。
可重定位目标文件调用外部函数的时候是call blabla(e8 00 00 00 00),再声明一个elf重定位项,链接的时候把00改成适当的offset。如果你链接的目标文件里有blabla的定义,链接器就直接把00改成到那个函数的偏移,如果没有,再改成到plt表的偏移。假设直接代码里indirect jmp,就没办法处理前者的情况了。
也就是说当你把c文件编译成可重定位目标文件时,编译器把没定义的函数都一视同仁了,他也不知道是外部函数,还是链接阶段你会给出定义了这个函数的目标文件,所以就这么处理了
推荐
 楼主| fnv1c 发表于 2021-8-18 14:32 |楼主
本帖最后由 fnv1c 于 2021-8-18 14:33 编辑

dogydogyfly 发表于 2021-8-17 19:38
有一点不太懂
为什么延迟绑定这块一定需要plt
直接用got缓存地址不可以吗。。。

ELF加载到内存之后执行之前,一般是不会解析导入的符号的,got表默认指向的就是plt表与延迟绑定相关的部分。直到用到时才解析所以才叫延迟绑定。
如果开了FULL RELRO,倒是会提前解析所有符号地址放进got表,但也要走一次plt表的indirect jmp
沙发
菠萝蜜 发表于 2021-8-6 08:03
3#
xiahhhr 发表于 2021-8-6 08:29
感谢分享!!!
4#
kuagnkuangkuang 发表于 2021-8-6 08:29
感谢分享教程
5#
艾莉希雅 发表于 2021-8-6 09:05
这段代码怎么有味道啊(恼)
6#
CNEggplant 发表于 2021-8-6 09:26
感谢楼主分享!
7#
xiaoniba2016 发表于 2021-8-6 10:08
感谢分享教程
8#
kimay5211 发表于 2021-8-6 14:54
感谢分享教程
9#
绫音 发表于 2021-8-6 15:00
二进制加密吗
10#
sam喵喵 发表于 2021-8-6 17:41
大佬最好能搞个完整代码演示,必须支持,热心奉上
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-15 09:48

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表