• Linux动态库*.so函数名修改


    在某些学习或者特殊需求的情况下要对linux下动态库*.so文件内部的函数名进行修改。

            比如一个函数ADD(int a,int b);修改为Add(int a,int b);

    通过这篇文章你将了解到在linux下动态库函数名寻址的规则,截止2024年3月linux动态库的寻址规则已经出现多种,这里不会一一介绍。这篇文章仅提供规则,并不提供修改函数名的相关代码和执行文件。如果有需要请留言沟通。

    开发环境:Ubuntu20、gcc、g++、IDAPro(Windows下安装即可,版本不限)。

    必备知识:ELF文件标准(linux下执行文件头)、IDApro的使用方法。

    建议了解:linux 动态库加载流程(自行寻找资源,后续可能给出链接)参考文档(P73):https://paper.seebug.org/papers/Archive/refs/elf/Understanding_ELF.pdf

    正文开始:

            编译简单的动态库和调用代码。

            以下为测试代码:其中仅有两个函数分别是ADD和MINUS的动态库代码;使用main.cpp链接api.so对这两个函数进行调用,并执行。最终再修改main.cpp对ADD的调用修改为Add,同时不编译动态库,仅对动态库的二进制文件进行修改的情况下,完成调用。

    注:下列代码仅为参考,并未规范处理。编译命令见main.cpp。

    1. //myAPI.h
    2. //int ADD(int a, int b);
    3. //int MINUS(int a, int b);
    4. #ifdef __cplusplus
    5. extern "C" {
    6. #endif
    7. // int Add(int a, int b);
    8. int ADD(int a, int b);
    9. int MINUS(int a, int b);
    10. #ifdef __cplusplus
    11. }
    12. #endif
    1. //myAPI.cpp
    2. #include "api.h"
    3. // int aaa(int a, int b){
    4. int ADD(int a, int b){
    5. return a + b;
    6. }
    7. // int aaa(int a, int b){
    8. // return a + b;
    9. // }
    10. int MINUS(int a, int b){
    11. // int c = ADD(a,b);
    12. int c = b;
    13. return a - c;
    14. }
    1. //main.cpp
    2. #include "api.h"
    3. #include
    4. // #include
    5. // 使用hash表做编译,不写使用ELF GUN hash 或者两者兼顾
    6. // g++ -shared -fPIC -o libapi.so api.cpp -Wl,--hash-style=sysv /// -Wl,--retain-symbols-file=retain-symbols.txt
    7. // 链接动态库生成执行文件
    8. // g++ -o main main.cpp api.h -L. -lapi
    9. // 添加动态库路径到环境变量中
    10. // export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/.
    11. int main(){
    12. std::cout << "1 + 1 = " << ADD(1, 1) << std::endl;
    13. std::cout << "1 - 1 = " << MINUS(1, 1) << std::endl;
    14. return 0;
    15. }

    通过上述步骤你将获得libapi.so和main两个可执行文件。并且执行main时,运行正常。

    打开IDAPro,将libapi.so使用IAD进行逆向,能够查看二进制文件即可。

    1. idx name hash_val
    2. 3 __cxa_finalize 0BEA6495 00000002
    3. 4 _Z5MINUSii 01E5E459 00000001
    4. 5 _ITM_registerTMCloneTable 0B7268A5 00000001
    5. 6 _ITM_deregisterTMCloneTable 012F7225 00000001
    6. 7 _Z3ADDii 0D758CB9 00000002
    7. 8 __gmon_start__ 0F4D007F 00000000
    8. ida pro 提供数据如下:
    9. ; ELF Hash Table
    10. elf_hash_nbucket DCD 3
    11. elf_hash_nchain DCD 9
    12. elf_hash_bucket DCD 8, 6, 7
    13. elf_hash_chain DCD 0, 0, 0, 0, 0, 4, 5, 3, 0
    14. ; ELF Symbol Table
    15. 0 Elf64_Sym <0>
    16. 1 Elf64_Sym 3, 0, 7, .init_proc, 0>
    17. 2 Elf64_Sym 3, 0, 0x12, __dso_handle, 0>
    18. 3 Elf64_Sym 0x20, 0, 0, dword_0, 0> ; "__cxa_finalize"
    19. 4 Elf64_Sym 0x12, 0, 9, _Z5MINUSii, 0x28> ; "_Z5MINUSii" ...
    20. 5 Elf64_Sym 0x20, 0, 0, dword_0, 0> ; "_ITM_registerTMCloneTable"
    21. 6 Elf64_Sym 0x20, 0, 0, dword_0, 0> ; "_ITM_deregisterTMCloneTable"
    22. 7 Elf64_Sym 0x12, 0, 9, _Z3ADDii, 0x20> ; "_Z3ADDii" ...
    23. 8 Elf64_Sym 0x20, 0, 0, dword_0, 0> ; "__gmon_start__"
    24. ; ELF String Table
    25. byte_300 DCB 0 ; DATA XREF: LOAD:0000000000000240↑o
    26. ; LOAD:0000000000000258↑o ...
    27. aGmonStart DCB "__gmon_start__",0 ; DATA XREF: LOAD:00000000000002E8↑o
    28. aItmDeregistert DCB "_ITM_deregisterTMCloneTable",0
    29. ; DATA XREF: LOAD:00000000000002B8↑o
    30. aItmRegistertmc DCB "_ITM_registerTMCloneTable",0
    31. ; DATA XREF: LOAD:00000000000002A0↑o
    32. aCxaFinalize DCB "__cxa_finalize",0 ; DATA XREF: LOAD:0000000000000270↑o
    33. aZ3addii DCB "_Z3ADDii",0 ; DATA XREF: LOAD:00000000000002D0↑o
    34. aZ5minusii DCB "_Z5MINUSii",0 ; DATA XREF: LOAD:0000000000000288↑o
    35. DCB 0, 0, 0, 0, 0, 0, 0

    规则:

                规则:idx是直接读取 ELF Symbol Table 里面的 内容,顺序和 ELF String Table 里面的不同
                        hash_val 通过特定的方法计算得出。
                        elf_hash_nbucket 这个值编译器根据方 案计算出的,一般(总符号数/4 + 1) 在附近选择一个素数,能 够使数据更加离散。
                        elf_hash_nchain  这个值是符号的个数 。
                        elf_hash_bucket DCD 8, 6, 7
                            根据下表计算出的hash_val从最下面开始,第8个 0 ,接着第6个 1, 第7个是2。
                        elf_hash_chain  DCD 0, 0, 0, 0, 0, 4, 5, 3, 0
                            根据下表计算出:

                                         在计算的hash=0有 8,
                                             计算的hash=1有 6,5,4
                                             计算的hash=2有 7,3
                                因此:elf_hash_bucket[0] = 8;elf_hash_bucket[1] = 6;elf_hash_bucket[2] = 7;
                                        elf_hash_chain[8] = 0
                                        elf_hash_chain[6] = 5; elf_hash_chain[5] = 4; elf_hash_chain[4] = 0
                                        elf_hash_chain[7] = 3;

                    对橙色部分的内容进行解释:

                     hash为0的只有8,结尾默认0,故hash_chain[8]=0。

                     hash为1的有6,5,4 结尾默认0,故hash_chain[6]=5,hash_chain[5]=4,hash_chain[4]=0

                     同理,hash为2的如上。

    至此,你已经了解了--hash-style=sysv使用传统ELF编译动态库,hash表的生成规则了,其本质是一种快捷的链式结构。

    使用十六进制编辑器,将hash表对应的函数ADD->Add,然后调整ELF hash链表。

    1. 3 __cxa_finalize 0BEA6495 00000002
    2. 4 _Z5MINUSii 01E5E459 00000001
    3. 5 _ITM_registerTMCloneTable 0B7268A5 00000001
    4. 6 _ITM_deregisterTMCloneTable 012F7225 00000001
    5. 7 _Z3Addii 0D77ACB9 00000000
    6. 8 __gmon_start__ 0F4D007F 00000000
    7. hash_val = 0: 8,7
    8. hash_val = 1: 6,5,4
    9. hash_val = 2: 3
    10. elf_hash_bucket DCD 8, 6, 3
    11. elf_hash_chain DCD 0, 0, 0, 0, 0, 4, 5, 0, 7

    注:使用IDA获取这个表的时候,有对应值在二进制文件的偏移,可以根据偏移直接修改。这一过程修改了hash table 和 string table两部分,上面首为7的行,已经和最开始的不一致了,是通过elf_hash(string)获得到的。这个函数的实现,资源较多。稍后进行链接给出。

    调整完毕,再将main.cpp和api.h两个文件的ADD->Add。如下:

    1. //myAPI.h
    2. //int ADD(int a, int b);
    3. //int MINUS(int a, int b);
    4. #ifdef __cplusplus
    5. extern "C" {
    6. #endif
    7. // int Add(int a, int b);
    8. int ADD(int a, int b);
    9. int MINUS(int a, int b);
    10. #ifdef __cplusplus
    11. }
    12. #endif
    13. //----------------------------------
    14. //main.cpp
    15. #include "api.h"
    16. #include
    17. // #include
    18. // 使用hash表做编译,不写使用ELF GUN hash 或者两者兼顾
    19. // g++ -shared -fPIC -o libapi.so api.cpp -Wl,--hash-style=sysv /// -Wl,--retain-symbols-file=retain-symbols.txt
    20. // 链接动态库生成执行文件
    21. // g++ -o main main.cpp api.h -L. -lapi
    22. // 添加动态库路径到环境变量中
    23. // export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/.
    24. int main(){
    25. std::cout << "1 + 1 = " << Add(1, 1) << std::endl;
    26. std::cout << "1 - 1 = " << MINUS(1, 1) << std::endl;
    27. return 0;
    28. }

    此时,使用修改后的libapi.so和上述两个文件,编译,发现Add可以被正常调用。

    结语:此案例中ADD->Add是函数名长度,所以可以避免下面执行的代码不做偏移修改,如果函数名长度不一致,可能导致整个动态库文件出现较大的问题。如果有兴趣,可以查找相关的资料并提交到GNU,说不定你也是对开源社区做贡献的小可爱了。这个案例对有传统ELF hash表有效,其他标准的hash表也是类似的调整方法,了解其运作原理就可以很快出结果。

    参考文档(P73):https://paper.seebug.org/papers/Archive/refs/elf/Understanding_ELF.pdf

  • 相关阅读:
    2_1 计算机组成与体系结构
    网信办部署开展清朗专项行动,严打色情等违法信息外链
    shell脚本基础
    剑指offer——JZ32 从上往下打印二叉树 解题思路与具体代码【C++】
    浅谈构造函数【Javascript】
    Spring系列篇一《Spring底层的核心原理解析》
    操作系统 day10(调度的概念、层次、七状态模型)
    JAVA毕业设计科技项目在线评审系统计算机源码+lw文档+系统+调试部署+数据库
    PE文件-C++-MFC-IDA-逆向分析-x32dbg
    测试架构需要具备哪些能力
  • 原文地址:https://blog.csdn.net/weixin_40751444/article/details/136743131