Android进阶

修改so导出函数名称

2017-09-14  本文已影响638人  赵海洋

最近有一个需求是将某so(无源码)的某些函数名称改掉。搜遍网络没有直接的解决方案,最后是综合多个地方的资料和建议及类似代码,自己弄出了个不通用的解决方案。

首先,so没有源码,所以不可能直接修改导出函数,一开始的思路就是下面这两种解决方案:

  1. 通过ida将so反编译成源码,然后再手动整理成可编译的代码,再去改。
  2. so是ELF格式的,直接以二进制方式修改这个ELF文件应该可以达到目的。

以上两种方法中,1最彻底,实行完之后代码就是可维护可更新的了,以后加什么新功能或其它改动都可以。但我使用ida及Hex-ray插件生成c伪代码后看了看代码就放弃了。一个264KB的so,反编译出来了795KB的代码,这你敢信。。。 反编译出来的代码中大量的sub_xxx函数,还结合各种汇编代码,各种跳转,除了调用其它so以及自身的导出函数的名称是可见的,其它一概不可见。预计恢复难度较高,至少在一周的工作时间,所以暂时搁置了。

然后就研究第2种修改ELF格式的方法,首先查找了一些资料如下:

以及elf分析的相关工具:

也找到同样尝试修改导出函数名称的人,还有半成品代码:

然后花了点时间熟悉了makefile及linux编译(没有使用Android Studio+NDK来实现,因为毕竟是工具类),再在github上一个elf工具集的基础上增加了个redefine工具。

看到这里,我假设你已经读了上面一些ELF相关资料,所以下面的解释对ELF相关术语不做详述。

一开始很顺利,找到类型为DYNSYM的section,然后找到它对应的类型为STRTAB的section,然后直接取出整个section,遍历里面的所有字符串(都是以\0结尾,和c字符串一样),然后将字符串修改成新的字符串,目前只支持将长字符串替换成短的,然后把\0前未替换的部分前移(如果替换前后一样长就不用前移了),在后面多余的地方补0。

替换完了后,使用dlsym加载发现找不到符号,然后就发现了* ELF格式可执行文件,更改符号名称要注意的地方,所以又改进了代码,加上了rehash的步骤。

然后测试修改用ndk生成的一个hellojni的示例后成功。但是在实际使用中发现了一个奇葩的问题,即比如原导出函数Java_com_example _test_TestJni_mprotect,被我替换成Java_com_abcdef_test_TestJni_mprotect,在加载时提示dlopen failed: cannot locate symbol "protect" referenced by "/data/app/com.xxxx.xxxx-2/lib/arm/libmxxxx_xxx.so"

dlopen failed: cannot locate symbol "protect" referenced by "/data/app/com.xxxx.xxxx-2/lib/arm/libmxxxx_xxx.so"

使用ida反编译修改后的so发现,有一个import function名为mprotect,在修改后变成了protect,再使用readelf查看section信息:

Section Headers:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] .note.gnu.build-i NOTE            00000114 000114 000024 00   A  0   0  4
  [ 2] .hash             HASH            00000138 000138 002210 04   A  3   0  4
  [ 3] .dynsym           DYNSYM          00002348 002348 0047b0 10   A  4   3  4
  [ 4] .dynstr           STRTAB          00006af8 006af8 012751 00   A  0   0  1
  [ 5] .gnu.version      VERSYM          0001924a 01924a 0008f6 02   A  3   0  2
  [ 6] .gnu.version_r    VERNEED         00019b40 019b40 000040 00   A  4   2  4
  [ 7] .rel.dyn          REL             00019b80 019b80 000250 08   A  3   0  4
  [ 8] .rel.plt          REL             00019dd0 019dd0 000260 08  AI  3   9  4
  [ 9] .plt              PROGBITS        0001a030 01a030 0003a4 04  AX  0   0  4
  [10] .text             PROGBITS        0001a3d4 01a3d4 0180e0 00  AX  0   0  4
  [11] .rodata           PROGBITS        000324b4 0324b4 0021ac 00   A  0   0  4
  [12] .ARM.extab        PROGBITS        00034660 034660 003d24 00   A  0   0  4
  [13] .ARM.exidx        ARM_EXIDX       00038384 038384 0025a0 00  AL 10   0  4
  [14] .init_array       INIT_ARRAY      00040c74 040c74 00000c 00  WA  0   0  4
  [15] .fini_array       FINI_ARRAY      00040c80 040c80 000008 00  WA  0   0  4
  [16] .data.rel.ro      PROGBITS        00040c88 040c88 0000b8 00  WA  0   0  8
  [17] .dynamic          DYNAMIC         00040d40 040d40 000128 08  WA  4   0  4
  [18] .got              PROGBITS        00040e68 040e68 000194 04  WA  0   0  4
  [19] .data             PROGBITS        00041000 041000 000070 00  WA  0   0  4
  [20] .bss              NOBITS          00041070 041070 0029a4 00  WA  0   0  4
  [21] .comment          PROGBITS        00000000 041070 000027 01  MS  0   0  1
  [22] .ARM.attributes   ARM_ATTRIBUTES  00000000 041097 000031 00      0   0  1
  [23] .shstrtab         STRTAB          00000000 0410c8 0000dd 00      0   0  1

其中 [ 3] .dynsym[17] .dynamic都引用的是索引为4的[ 4] .dynstr STRTAB。那么问题很明显了,应该是导入表和导出表共用了字符表,并且共用了同一个字符串,只不过索引不同。在修改函数名称时,因为是将example替换成了abcdef后,将后面的_test_TestJni_mprotect前移了1位,导致mprotect这个符号引用的name指向了protect。

那么如果想解决这个问题,还得在修改字符表后,枚举所有引用了同一个字符表的section,并查看它里面的每一个符号引用的字符串是某是被修改并移位过的,也要修改它的st_name,然后保存。

已经在这个问题上折腾了几天的我不想再继续倒腾了,所以就调整了下替换字符串的长度,把abcdef换成了abcdefg,这样的话所有符号的st_name索引都不会变化了。

如果以后有机会,会把未完成的功能做完:

  1. 支持短字符替换成长字符。
  2. 替换字符串并移位后检查所有引用同一字符串的符号,修改其st_name
  3. 测试对于不同平台上的的so的兼容性
  4. 支持GCC编译出来的

最后附上代码:https://github.com/k1988/ELFkickers/tree/master/redefine

上一篇下一篇

猜你喜欢

热点阅读