首页
课程
问答
CTF
社区
招聘
峰会
发现
排行榜
知识库
工具下载
看雪20年
看雪商城
证书查询
登录
注册
首页
社区
课程
招聘
发现
问答
CTF
排行榜
知识库
工具下载
峰会
看雪商城
证书查询
社区
逆向工程
发新帖
24
13
[原创] SoFixer导入表问题修复及ELF解析简记
发表于: 2024-6-20 20:01
11349
[原创] SoFixer导入表问题修复及ELF解析简记
Denny Chen
2024-6-20 20:01
11349
## 一. 简介 在多次使用 F8 大佬的 <a href="elink@871K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6r3z5p5I4q4c8W2c8Q4x3V1k6e0L8@1k6A6P5r3g2J5">SoFixer</a> 时,碰到导入符号对不上的问题(作者已知的问题),所以就学习了下这个工具的源码并做了简单的修复 <a href="elink@e7dK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6o6K9r3g2F1P5h3q4F1k6$3#2A6L8X3M7&6i4K6u0r3f1$3!0r3K9i4S2W2M7R3`.`.">github链接</a>。这里简单记录一下修复过程及学习过程中的对elf文件格式新增的体会。 修复的过程主要针对 ElfRebuilder.cpp 中的 relocate 函数。 ```c++ switch (type) { // I don't known other so info, if i want to fix it, I must dump other so file case R_386_RELATIVE: case R_ARM_RELATIVE: *prel = *prel - dump_base; break; case 0x401: case 0x402:{ auto syminfo = si.symtab[sym]; if (syminfo.st_value != 0) { *prel = syminfo.st_value; } else { auto load_size = si.max_load - si.min_load; if (mImports.size() == 0){ *prel = load_size + external_pointer; external_pointer += sizeof(*prel);//修复前都是从这里修改重定位内容 }else{ //修复后是从这里修改重定位内容 const char* symname = si.strtab + syminfo.st_name; int nIndex = getIndexOfImports(symname); if (nIndex != -1){ *prel = load_size + nIndex*sizeof(*prel); } //printf("type:0x%x offset:0x%x -- symname:%s nIndex:%d\r\n", type, rel->r_offset, symname, nIndex); } } break; } default: break; } ``` 从打印的属性和符号名来看,0x401也有出现导入符号相关类型,所以这里将 case 0x401 也加上了,不过不加的话,在对应的偏移出好像也能解析出对应符号。IDA中解析导入表时,是按照导入表顺序,逐个创建函数声明(每个占用8字节),SoFixer 中修改导入表时,是按照读到的重定位表中导入符号顺序,逐个加8字节( sizeof(*prel) == 8 )递增赋值。读到的重定位表中导入符号顺序和导入表的顺序大部分相同,但也会有些出路,所以当导入符号数量大的时候,就会对应不上。 这里修复的方法是先将导入符号名按顺序保存到 vector 中,修复时,根据重定位处读到的对应的符号名到 vector 中取数组索引,这样就能保证和导入符号的顺序完全一致。 ``` int ElfRebuilder::GetIndexOfImports(std::string stringSymName){ int nIndex = 0; for (auto& it : mImports){ std::string strImport = it; if (strImport == stringSymName){ return nIndex; } nIndex++; } return -1; } //将导入表的符号按顺序保存在 std::vector<std::string> mImports; 中,以便后面获得导入符号序号 void ElfRebuilder::SaveImportsymNames(){ Elf_Sym* symtab = si.symtab; const char* strtab = si.strtab; int nIndex = 0; bool start = false; while (true){ Elf_Sym sym = symtab[nIndex]; if (sym.st_name == 0 && !start){ nIndex++; continue; } start = true; if (sym.st_name != 0 && sym.st_value!=0){ //开始进到非导入表的符号,退出 break; } const char* symname = strtab + sym.st_name; mImports.push_back(symname); // FLOGD("NO:%d %s \r\n", nIndex, symname); nIndex++; } } ``` 简单修复后,测试修复包含2000多个导入函数的so时,导入函数都能对上号。 ## 二. ELF 文件解析 elf 文件格式绝对是老生常谈的话题了,这里主要记录一下导入表,重定位表,plt,got表,以及它们在IDA中的解析。 对于文件格式的解析,相信大多数人一开始会时用010Editor和对应文件格式模板查看文件的结构。Elf格式的模块能很好地解析出节表对应的内容,但不能直接解析动态段的内容,所以动态段中的内容学习及认识起来没有节表简单直观。 为方便学习和查看,可以将动态段中内容一次性读出来,在内存中或者直接打印出来查看。SoFixer 中 ReadSoInfo 函数和 <a href="elink@fbfK9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8X3q4G2M7%4m8^5M7X3g2X3i4K6u0W2j5$3!0E0i4K6u0r3j5h3&6V1M7X3!0A6k6q4)9J5k6o6p5H3i4K6u0W2x3q4)9J5k6e0m8Q4y4h3k6J5y4o6N6Q4x3V1k6^5M7X3g2X3i4K6u0r3j5X3W2G2L8X3W2U0i4K6u0r3L8r3W2F1K9$3g2J5i4K6u0r3L8r3W2F1K9$3g2J5i4K6u0W2j5%4m8H3">linker.cpp</a> 的 prelink_image 函数有将动态段内容读取并存储到 soinfo 变量中,可以参考一下。如果是直接查看,可以直接使用 readelf -d xxx.so 和 objdump -x xxx.so 获得动态段内容,也可以在ida中跳转到动态段的偏移处查看动态段的信息。 ### 1. 导入表 so 文件中所有符号都存储在动态符号表,IDA中按照符号属性,将符号划分在导入和导出表。动态符号表自第三项开始,所有 sym_value(代码结构体中是字段st_value)为0的符号是导入符号,IDA会将这些符号按照动态符号表中顺序逐个加到导入表中,如下:  导入符号在so文件中并不存在对应的函数体或数据,所以IDA专门模拟了一段外部段(extern)来记录这些符号的名称及地址,如下:  这些导入函数的地址是按照导入表顺序逐个排列的,每个占用8字节。那这个extern段的首地址是怎么来的呢?IDA会按照节中的 s_addr 为需要空间的段分配一段空间(有的不用空间来展示的,就不用,如:shstrtab节),分配完最后一个后,紧接着便是extern的空间。SoFixer中修复so后,data节之后正好是 si.max_load - si.min_load,所以导入表extern首地址从这里开始。 ### 2. plt/got 表 plt节全名是 Procedure Linkage Table,是程序代码和got表符号调用的过渡程序,可用于延迟绑定got表符号,但其实延迟绑定的机制在Android中应该是很少用,看到很多应用的so的 FLAGS 都是 BIND_NOW,也就是加载so时就绑定符号,不会延迟。 plt节中函数会通过got表上函数符号地址所在分页首地址(adrp指令的作用),加上对应偏移获取对应的函数地址。从本地文件和内存中代码对比来看,plt 表不存在重定位的情况,代码上可以理解为adrp获取分页首地址是根据符号自动获取,然后取相对偏移处内容,类似取相对偏移处内容。如下,机器码除了大小尾不同外,其它都一样:  从截图中也可以看出,plt表中连续的两个函数之间也只是加的偏移不一样,分别取got表中不同偏移处的函数地址进行调用。 got表中存储着符号地址,毫无疑问,这里的地址肯定是会被重写或者重定位的。直接010打开elf文件,可以发现,got表的前0x18字节都是0,后面存储的都是plt节对应的首地址,如下:  但是IDA中,跳到对应got表偏移处,发现对应的机器码并不是这个,如下:  如果将got表改掉,让ida识别不到真实的got表,那么ida中对应的偏移处的内容和elf文件中的内容又是一样的,如下:  这说明,IDA在识别到got表之后,有对其中的内容做修改,改成用户方便查看的方式,例如导入函数,本地函数等的地址加到got表上,就类似so动态加载时,linker为got上内容重定位或重写。解析重定位表就能获取got表对应偏移,符号名,重定位地址等的信息,IDA应该也是解析了重定位表,然后实现对got表处的内容重写。 > 如果got表没有或者找不到,但got对应地址处的内容有修改成对应符号的地址,则IDA会按照正常的代码去解析它的符号。F8大佬的SoFixer中就是这么做的,将所有重定位表对应地址的内容改写了(其中自然也包括got表符号的重定位),所以即使got表没修复,got表对应位置还是能正常显示函数符号。 ### 3. 重定位表 通过一个demo,分别通过节和动态段查看如下: ```bash # 节表 readelf -S libnative-lib2.so | grep rel [ 8] .rela.dyn RELA 0000000000006858 00006858 [ 9] .rela.plt RELA 000000000000e5c8 0000e5c8 # [18] .data.rel.ro PROGBITS 0000000000032d30 00031d30 这是数据节,不属于重定位表 # 动态段 readelf -d libnative-lib2.so | grep REL 0x0000000000000002 (PLTRELSZ) 1992 (bytes) #JMPREL 重定位表的大小 0x0000000000000014 (PLTREL) RELA #指定了 PLTREL 的类型是 RELA 0x0000000000000017 (JMPREL) 0xe5c8 #对应节表中 .rela.plt 节 0x0000000000000007 (RELA) 0x6858 #对应节表中 .rela.dyn 节 0x0000000000000008 (RELASZ) 32112 (bytes) #RELA 重定位表的大小, 1338 个单元 0x0000000000000009 (RELAENT) 24 (bytes) #RELA 中每个元素占的字节大小 0x000000006ffffff9 (RELACOUNT) 886 #Elf64_Rela中r_addend非零的单元的数量 ``` 从地址上看.rela.dyn 节对应 RELA,.rela.plt 节对应 JMPREL,但其实两段重定位表也是首尾连接的,内存上是连成一块的(0x6858 + 32112 == 0xe5c8)。 重定位表的数据结构类型有Elf_Rel和Elf_Rela,对应REL和RELA两中类型的重定位表,后者相对前者多了个字段 r_addend,以64位为例,如下: ``` typedef struct { Elf64_Addr r_offset; /* Address */ Elf64_Xword r_info; /* Relocation type and symbol index */ Elf64_Sxword r_addend; /* Addend */ } Elf64_Rela; ``` r_offset 字段是在elf文件偏移,重定位修改的就是这个偏移处的内容,r_info 字段可以转换成重定位类型和符号索引,符号索引配合动态符号表可以找到对应符号,进而找到符号名和符号在文件中偏移位置(如果是导入符号,该值为0,如果是非导入的函数,偏移位置就是函数地址),有了r_offset和r_info之后,就能定位和修改符号信息了,IDA中修改got表部分的内容应该是根据这个改的。r_addend 字段记录了需要额外的的加数,一些重定位类型需要一个额外的加数来完成重定位,如TLS(Thread-Local Storage)模型中的一些情况(搜自chatgpt)。 可以在IDA中定位到重定位表的偏移,查看重定位表的情况,如下,每一单元(行)中分别是Elf_Rela中对应三个字段:  ## 三. 小节 笔者能力有限,如果有写的不对的地方,还请多批评指正!
登录后可查看完整内容
冰与火的战歌:Windows内核攻防实战高级班!从零到实战,融合AI与Windows内核攻防全技术栈,打造具备自动化能力的内核开发高手。
最后于
2024-6-20 20:25 被Denny Chen编辑 ,原因:
#其他内容
收藏
・
24
点赞
・
13
打赏
分享
分享到微信
分享到QQ
分享到微博
赞赏记录
参与人
雪币
留言
时间
Rusty19
你的帖子非常有用,感谢分享!
2026-4-11 15:25
x0rrrrr
这个讨论对我很有帮助,谢谢!
2026-3-18 22:51
NshIdE
你的分享对大家帮助很大,非常感谢!
2026-3-9 19:45
wx_funcrever
你的帖子非常有用,感谢分享!
2025-12-26 15:58
东方玻璃
+2
谢谢你的细致分析,受益匪浅!
2025-12-24 14:12
孤独的街
你的帖子非常有用,感谢分享!
2025-11-11 23:37
mb_biynvvpu
期待更多优质内容的分享,论坛有你更精彩!
2025-3-28 13:30
exit0
+1
谢谢你的细致分析,受益匪浅!
2024-12-15 04:17
Cr0ssx2
非常支持你的观点!
2024-10-22 16:13
sinker_
感谢你的贡献,论坛因你而更加精彩!
2024-10-6 04:22
Ram98
感谢你分享这么好的资源!
2024-6-21 09:47
秋狝
为你点赞~
2024-6-20 23:41
pexillove
+1
期待更多优质内容的分享,论坛有你更精彩!
2024-6-20 20:12
查看更多
赞赏
×
1 雪花
5 雪花
10 雪花
20 雪花
50 雪花
80 雪花
100 雪花
150 雪花
200 雪花
支付方式:
微信支付
赞赏留言:
快捷留言
感谢分享~
精品文章~
原创内容~
精彩转帖~
助人为乐~
感谢分享~
最新回复
(
4
)
jikjgygjhg
雪 币:
10
能力值:
( LV1,RANK:0 )
在线值:
发帖
1
回帖
6
粉丝
2
关注
私信
jikjgygjhg
2
楼
最后于
2025-2-13 14:16 被jikjgygjhg编辑 ,原因:
2025-2-13 11:57
0
mb_tjojeozj
雪 币:
720
能力值:
( LV1,RANK:0 )
在线值:
发帖
0
回帖
3
粉丝
0
关注
私信
mb_tjojeozj
3
楼
感谢大佬,能否提供下 编译后的可执行文件
2025-3-26 00:09
1
cs_jackw
雪 币:
0
能力值:
( LV1,RANK:0 )
在线值:
发帖
0
回帖
16
粉丝
0
关注
私信
cs_jackw
4
楼
为啥我用你这个还是会有很多符号找不到?我对elf这些不是很了解,有没有建议 我该从哪里入手去完善?
2025-10-15 09:38
0
Denny Chen
雪 币:
225
活跃值:
(30)
能力值:
( LV2,RANK:10 )
在线值:
发帖
2
回帖
6
粉丝
12
关注
私信
Denny Chen
5
楼
github地址迁到:
e61K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6o6h3f1#2Q4x3X3c8x3j5h3u0Q4x3V1k6e0L8@1k6A6P5r3g2J5
2026-9-1 11:55
0
游客
登录
|
注册
方可回帖
回帖
表情
雪币赚取及消费
高级回复
返回
Denny Chen
2
发帖
6
回帖
10
RANK
关注
私信
他的文章
[原创] SoFixer导入表问题修复及ELF解析简记
11349
[分享][原创]一次针对反fart的加壳apk的脱壳经历
21618
关于我们
联系我们
企业服务
看雪公众号
专注于PC、移动、智能设备安全研究及逆向工程的开发者社区
看原图
赞赏
×
雪币:
+
留言:
快捷留言
为你点赞!
返回
顶部