首页
论坛
专栏
课程

[原创]SO文件格式及linker机制学习总结(1)

ThomasKing
6
2015-2-2 16:38 41013
学习SO文件格式和linker已有很长一段时间,现实现SO文件的抽取以及相应的加载器,目的在于学习总结,并对之前帖子未涉及到的知识进行补充。当然,为了让总结更有意思,先从文件格式的定义上入手,自定义一个简单的文件格式。为了保持平台上的兼容性和实现的简单性,在对SO抽取的过程中,保持与指令集相关的相对结构,不涉及与平台相关的指令集重定位等。后文在此格式基础上,实现一个类似linker的加载器,对抽取后的SO文件进行加载。限于水平,难免会有错误和疏漏之处,请各位大大斧正。

另外,限于篇幅,后续文章将会介绍如何内存加载SO文件以及融合加载器的实现,目的在于将加载器与抽取后的文件相融合,实现无缝加载和替换......
-------------------------------------------------
附件为测试源码,解压密码:ThomasKing.2015.02.01

[推荐]十年磨一剑!《加密与解密(第4版)》上市发行

上传的附件:
最新回复 (41)
熊猫正正 9 2015-2-2 17:01
2

0

谢谢分享,学习
jimdlf 2015-2-2 17:04
3

0

MARK下 学习下
fanfu 2015-2-2 17:54
4

0

好帖,这个必得顶,跟着
ThomasKing的步伐走
Fido 2015-2-2 19:52
5

0

学习...感谢楼主分享
mingxuan三千 2015-2-2 22:37
6

0

感谢分享
JackJoker 2015-2-3 00:03
7

0

多谢分享,学习
OnlyEnd 2015-2-4 18:10
8

0

王总碉堡了!!!  下下来学习学习
flyworm 2015-2-5 11:44
9

0

感谢分享
sendwave 2015-2-5 19:22
10

0

请教大神:我今天直接测试demo失败了,于是到“SO文件抽取”里面重新编译和运行,结果Mini_elf_generate崩溃了。
目录里面的txt说:需要从特定手机复制依赖库。请问这里是不是说将目标手机中的对应依赖so文件复制到Mini_elf_generate编译环境(Linux)中?还是说复制到Android手机Demo运行的目录下?

还望大神赐教,多谢!
ThomasKing 6 2015-2-5 23:09
11

0

额,小菜。。。
把手机的/system/lib目录下的依赖库复制出来
sendwave 2015-2-6 07:24
12

0

这个我清楚,我想问复制到哪里去?是复制到运行环境,还是复制到编译环境?
ThomasKing 6 2015-2-6 10:11
13

0

这个都可以,只要保证二者统一即可。
xiaomayu 2015-2-6 14:01
14

0

大神,你好,我在Ubuntu12.0.4下编译so文件抽取,但是异常退出了,我跟日志跟进了Mini_elf_generate.c文件,异常在下面几行代码,请大神指点迷津。

     memcpy(f_hash, new_hash, (2 + new_nbuckets + new_nchains) * 4);
      f_rel = (Elf32_Rel*)(f_hash + 2 + new_nbuckets + new_nchains);
      memcpy(f_rel, new_rel, sizeof(Elf32_Rel) * new_rel_count);
      f_pltrel = (Elf32_MiniPltRel*)((unsigned)f_rel + sizeof(Elf32_Rel) * new_rel_count);
      memcpy(f_pltrel, tRel, sizeof(Elf32_MiniPltRel) * tRel_count);
      plt_offset = (unsigned)(si->plt_rel + si->plt_rel_count) - si->base;
      memcpy(base + plt_offset, (void*)si->base + plt_offset, si->size - plt_offset);
ThomasKing 6 2015-2-13 19:34
15

0

额,小菜。。。。 你注意下内存,应该没什么问题。 我刚试了。
handsome枫雪 2015-2-17 09:52
16

0

感谢LZ分享~~~~~~~~~~~
wangfeitw 2015-3-9 10:11
17

0

谢谢,学习中
tljabc 2015-3-29 21:03
18

0

感谢分享
chitcrazy 2015-4-24 11:14
19

0

密码对吗,怎么打不开呢
SnowNight 2015-9-3 20:09
20

0

谢谢分享
dalerkd 1 2015-9-3 20:13
21

0

这得mark,谢谢分享
zhengweird 2016-4-14 13:49
22

0

感谢楼主分享,冒昧问一句映射内存为啥要2次mmap呢?
ugene 2016-5-6 18:17
23

0

学习一下
gugubupt 2016-5-25 17:40
24

0

楼主提到的soinfo的next赋值然后加入solist中,我发现并不能加进去啊。
然后so文件成功映射到内存中,可以找到函数地址,然而进行调用也是失败的。
我怀疑是因为没有加入solist的原因。
楼主可以解答下吗?
大王叫我挖坟 3 2016-5-26 21:20
25

0

你问的问题太对啦,我也崩溃啊,是复制到哪里啊
大王叫我挖坟 3 2016-5-27 09:38
26

0

仔细看了代码,在函数load_NeededLibray里面需要载入libfoo.so依赖的so文件啊,所以呢那些so需要复制到Mini_elf_generate编译的同目录就行了,跟libfoo.so放在一个目录,,,,但是还是崩溃,找到函数了崩溃,正在调试中
qqsunqiang 2016-5-27 10:03
27

0

谢谢楼主的分享。
大王叫我挖坟 3 2016-5-27 18:14
28

0

哈哈成功了,请注意啊,如果你用自己的手机做实验,就要把自己手机的那五个so拷贝到跟Mini_elf_generate同一个目录啊,因为作者大大,在运行Mini_elf_generate的时候就需要那5个so里面的函数重定位把函数地址重新写到libfoo.so的rel结构里面去啊,记得必须是自己手机的不能用,作者的因为,函数地址不一样啊,libfoo.so调用那些函数找不到地址会崩溃的!!!!!!!!!!!!!!!!!!thomasking偶像大大,总结一下,做个记录希望帮助别人
7.rel.plt节和rel.dyn节
//#define ELF32_R_SYM(x) ((x) >> 8)
//#define ELF32_R_TYPE(x) ((x) & 0xff)
//#define R_ARM_JUMP_SLOT        22        /* Create PLT entry */
//#define R_ARM_ABS32        2        /* Direct 32 bit  */
//#define R_ARM_GLOB_DAT        21        /* Create GOT entry */     
#define R_ARM_RELATIVE                23        /* Adjust by program base */

typedef struct elf32_rel {
  Elf32_Addr        r_offset;//重定位结束后的地址,如果是自己的函数或者自己的变量就存放自己的地址如
,thomasking大大那个linker连接器,是解析非R_ARM_RELATIVE把别的库里面的函数地址就写到了
这个r_offset里面了
.fini_array:00003EA8 20 0C 00 00                 DCD sub_C20
data:00004004 AC 22 00 00 off_4004        DCD aGetstring          ; DATA XREF: JNI_OnLoad+34o

同时
Relocation section '.rel.dyn' at offset 0xaf8 contains 12 entries:
Offset     Info    Type            Sym.Value  Sym. Name
00003ea8  00000017 R_ARM_RELATIVE
00004004  00000017 R_ARM_RELATIVE

//包含两个信息,
//用unsigned sym = ELF32_R_SYM(rel->r_info)就是.dynsym节的索引,第几项,
//用ELF32_R_TYPE(r_info)就是类型,如R_ARM_JUMP_SLOT
  Elf32_Word        r_info;  //uint32_t
} Elf32_Rel;

rel.plt节是重定位的函数,也就是so调用的外部函数,来得到外部函数的地址,定位完毕,会把地址存在got表里面

找到函数重定位之后的地址的办法,也是got hook的办法
计算函数hash值,然后根据.dynstr+.hash+dynsym得出,此函数是.dynsym节内容的第几项(symidx )

.rel.plt段中
ELF32_R_SYM(rel.r_info) == symidx && ELF32_R_TYPE(rel.r_info) == R_ARM_JUMP_SLOT

.rel.dyn段中
ELF32_R_SYM(rel.r_info) == symidx &&(ELF32_R_TYPE(rel.r_info) == R_ARM_ABS32
ELF32_R_SYM(rel.r_info) == symidx && ELF32_R_TYPE(rel.r_info) == R_ARM_GLOB_DAT)

就找到了,那个rel.r_offset就是地址啦,替换掉他就是传说中的got hook了
(1)R_ARM_ABS32          .rel.dyn节中 是调用别的so的函数,用函数指针来调用,而且是全局函数指针
(2)通过全局函数指针的方式调用外部函数,它的重定位类型是R_ARM_ABS32,并且位于节区**
整个重定位过程是先位到.got,再从.got定位到.date

(2)R_ARM_GLOB_DAT   .rel.dyn节中 是调用别的so的函数,用函数指针来调用,而且是局部函数指针
过局部函数指针方式调用外部函数它的重定位类型是R_ARM_GLOB_DAT,并

(3)R_ARM_JUMP_SLOT   .rel.plt节中 是调用别的so的函数,纯正调用函数
就是另外一个so里面的函数,直接调用外部函数,它的重定位类型是R_ARM_JUMP_SLOT,并且位于.re.plt节区,
其Offset指向最终调用函数地址的地址(也就是函数指针的指针)**。整个过程是先到.plt,再到.got,
最后才定位到真正的函数地址。

第四种,R_ARM_RELATIV=0x17也是rel.dyn中  自己so里面的函数指针啊,字符串指针啊,等等反正自己的
奇怪为什么got的hook不用这种,这样就可以hook自己的程序字符串?函数指针感觉也没啥用哦!
,在进行重定位时候,就是填写so自己的base,这种用于函数自己的指针,如函数指针啊,char *等等
如果是这个则
unsigned sym = ELF32_R_SYM(rel->r_info);  sym=0;
si->symtab[sym].st_shndx==0;是dynsym节的第一个全00那个
大王叫我挖坟 3 2016-5-27 19:04
29

0

关于重定位我的观点,大神请绕道,给跟我一样的菜鸟看的
我打个比方,你现在写个so叫mytest.so,里面打印log了,需要调用_android_log_print函数是吧,怎么调用了,这个函数在liblog.so里面,这就涉及重定位了,是linker帮助我们完成的过程如下
1.linker加载mytest.so到内存,然后根据dynamic的类型遍历其段结构,得到.hash,.dynstr,dynsym,rel.plt和.got结构
填充其值到soinfo结构体里面,如rel.plt是的首地址和大小soinfo->plt_rel和soinfo->plt_rel_count 这两个里面

2.for循环soinfo->plt_rel_count得到每一项需要重定位的信息,他们的结构是
typedef struct elf32_rel {
Elf32_Addr        r_offset;
        Elf32_Word        r_info;  //uint32_t
} Elf32_Rel;

然后用ELF32_R_SYM(rel->r_info)作为索引(比如值是sym=6),ELF32_R_SYM只是一个宏,#define ELF32_R_SYM(x) ((x) >> 8),,作为索引,到自己的.dynsym节找第几项,那就是第6项,funcname=si->strtab + si->symtab[sym].st_name得到要重定位的函数名,,,注意现在得到名字了,

3、在别的so里面找,在liblog.so里面找,hashval = elfhash(funcname)得到哈希值,然后如果
for(i = bucket[hashval % nbucket]; i != 0; i = chain[i]){
strcmp(strtab + symtab[i].st_name, name)
我们得到symtab[i].st_value//这是个地址,就是我们要找的函数在liblog.so里面的地址了
}
(就表示找到了,注意这里strtab 和symtab可是liblog.so里面的哟,其实linker会遍历很多so,然后把他们存放在soinfo数组里面,看linker.c源码知道最大是128个)
好我们得到这个地址的值了,把这个值填充到si->r_offset里面,哈哈就完成重定位了,
关于跟GOT的关系
比如,si->offset=0x3FE8,我们得到的地址是0x123456,那么重定位完毕后
03FE8: 56 43 21 ;别看ida瞎扯淡,那个401c不是值

还跟plt有关系

.rel.dyn跟这个的区别是

.rel.dyn是调用别的函数的函数指针,和自己的指针
Rel.plt是直接调用别的函数的函数指针罗

//-----------对于rel.dyn的got是

如果是自己的函数指针如,aNativeStringRe则直接填写地址ida给你分析出来了,是224c,等下重定位就加上自己的base就行了,对于调用别人so用函数指针来调用,如__gnu_Unwind_Find_exidx_ptr就跟那个rel.plt一样了
十一回武汉 2016-6-16 14:54
30

0

大神的代码我已经成功跑通过了,可是在抽取我自己写的一个so的时候遇到了些问题。这里是我的so的两个PT_LOAD段的信息:
p_offset  0h         p_offset  0x2ea4h
p_vaddr  0h         p_vaddr  0x3ea4h
p_filesz  7993       p_filesz   380
p_memsz 7993         p_memsz  380

按照大神的 load_segment中的方法进行映射,第一个PT_LOAD段的起始位置应该是
[si->base,si->base+0x2000h), 第二个PT_LOAD段起始位置是[si->base+0x3000h,si->base+0x5000h). 观察地址后发现两个LOAD段是不连续的,中间断了1000h,于是我的so抽取就失败了。而我以前成功抽取的so中,两个LOAD段的地址计算出来正好是连续的。
我想知道各位大神有没有遇到过这样的问题,应该怎么处理这种so?
十一回武汉 2016-6-17 16:00
31

0

我抽取的so执行时找不到dlopen,dlsym等函数,请问这有什么解决办法
不知世事 1 2016-7-14 15:12
32

0

感谢你,最近也在研究thomasking大大的一些列so教程,可否留个联系方式
njhu 2016-9-2 11:48
33

0

请问大家在测试的时候有出现过这个问题吗?

”0 failed to map segment from 'libfoo.so' @ 0x017bf000 (0x000022cb). p_vaddr=0x00000000 p_offset=0x00000000“

errno: 12 (Cannot allocate memory)。
njhu 2016-9-21 14:34
34

0

请问这个问题怎么解决呢?
OnlyForU 2016-9-21 15:27
35

0

感谢楼主!
不知世事 1 2016-9-22 10:12
36

0

你好,请教你个问题,最近在研究这个,在编译大神的Demo时,我的步骤是这样的:
1.把手机中的五个.so文件替换过来;
2.编译没问题,在执行时一直弹出段错误的提示,百度一发说是指针所对应的地址是无效地址,没有物理内存对应该地址,不知道什么原因?求解决万分感谢。。
ylmylm杨 2016-9-22 18:15
37

0

请教大神:我抽离完so以后,程序执行的时候,抽离简单的自己写的so,可以正常运行,抽离一些第三方的有JNI_onLoad方法的so,执行完加载程序的JNI_onload方法,就报错了,在 JNI_onload的}的时候报错
ylmylm杨 2016-9-27 18:24
38

0

运行的时候,JNI_Onload结束报错
不知世事 1 2016-10-20 15:38
39

0

你好,请问大神,这个问题最后怎么解决的
zhoujiamur 1 2016-11-18 20:32
40

0

先顶一下,后面慢慢看
toToC 2017-5-14 17:55
41

0

正向进行类抽取一直没有学习过,今天仔细的学习了一波,感谢楼主的分享
liumengde 2018-1-24 21:13
42

0

这个加固方案不通用啊  只能自己手机使用
返回