{"id":13481018,"url":"https://github.com/esrrhs/hookso","last_synced_at":"2025-05-16T18:10:52.773Z","repository":{"id":81850242,"uuid":"258518752","full_name":"esrrhs/hookso","owner":"esrrhs","description":"linux动态链接库的注入修改查找工具 A tool for injection, modification and search of linux dynamic link library","archived":false,"fork":false,"pushed_at":"2025-03-27T10:25:38.000Z","size":95,"stargazers_count":245,"open_issues_count":1,"forks_count":92,"subscribers_count":12,"default_branch":"master","last_synced_at":"2025-04-02T06:08:31.923Z","etag":null,"topics":["dynamic-libraries","hacking","injection","linux","modification"],"latest_commit_sha":null,"homepage":"","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/esrrhs.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2020-04-24T13:23:16.000Z","updated_at":"2025-03-27T10:25:41.000Z","dependencies_parsed_at":"2023-09-06T05:15:18.628Z","dependency_job_id":null,"html_url":"https://github.com/esrrhs/hookso","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/esrrhs%2Fhookso","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/esrrhs%2Fhookso/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/esrrhs%2Fhookso/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/esrrhs%2Fhookso/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/esrrhs","download_url":"https://codeload.github.com/esrrhs/hookso/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247999864,"owners_count":21031046,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dynamic-libraries","hacking","injection","linux","modification"],"created_at":"2024-07-31T17:00:47.731Z","updated_at":"2025-04-09T08:12:50.152Z","avatar_url":"https://github.com/esrrhs.png","language":"C++","readme":"# hookso\n\n[\u003cimg src=\"https://img.shields.io/github/license/esrrhs/hookso\"\u003e](https://github.com/esrrhs/hookso)\n[\u003cimg src=\"https://img.shields.io/github/languages/top/esrrhs/hookso\"\u003e](https://github.com/esrrhs/hookso)\n[\u003cimg src=\"https://img.shields.io/github/actions/workflow/status/esrrhs/hookso/ccpp.yml?branch=master\"\u003e](https://github.com/esrrhs/hookso/actions)\n\nhookso是一个linux动态链接库的注入修改查找工具，用来修改其他进程的动态链接库行为。\n\n[Readme EN](./README_EN.md)\n\n# 功能\n* 让某个进程执行系统调用\n* 让某个进程执行.so的某个函数\n* 给某个进程挂接新的.so\n* 卸载某个进程的.so\n* 把旧.so的函数或某个地址替换为新.so的函数\n* 复原.so的函数或某个地址的替换\n* 查找.so的函数地址\n* 查看.so的函数参数，或某个地址的函数参数\n* 当执行.so的某个函数时或某个地址的函数时，触发执行新的函数\n\n# 编译\ngit clone代码，运行脚本，生成hookso以及测试程序\n```\n# ./build.sh  \n# cd test \u0026\u0026 ./build.sh \n```\n\n# 示例\n* 启动test目录下的测试程序\n\n先看下测试代码，代码很简单，test.cpp不停的调用libtest.so的libtest函数\n\n```\nint n = 0;\nwhile (1) {\n    if (libtest(n++)) {\n        break;\n    }\n    sleep(1);\n}\n```\n而libtest.so的libtest函数只是打印到标准输出。\n\n注意这里使用了几种不同的方式调用puts，原因是不同的写法，会导致puts在elf中的位置不太一样，这里使用多种写法，使得后面的查找替换都能够覆盖到。具体可以```readelf -r libtest.so```查看细节。\n```\ntypedef int (*PutsFunc)(const char *s);\n\nPutsFunc f = \u0026puts;\n\nextern \"C\" bool libtest(int n) {\n    char buff[128] = {0};\n    snprintf(buff, sizeof(buff), \"libtest %d\", n);\n    if (n % 3 == 0) {\n        puts(buff);\n    } else if (n % 3 == 1) {\n        f(buff);\n    } else {\n        PutsFunc ff = \u0026puts;\n        ff(buff);\n    }\n    return false;\n}\n```\n这时候，test是没有加载libtestnew.so的，后面会用hookso来注入，libtestnew.cpp的代码如下\n```\nextern \"C\" bool libtestnew(int n) {\n    char buff[128] = {0};\n    snprintf(buff, sizeof(buff), \"libtestnew %d\", n);\n    puts(buff);\n    return false;\n}\n\nextern \"C\" bool putsnew(const char *str) {\n    char buff[128] = {0};\n    snprintf(buff, sizeof(buff), \"putsnew %s\", str);\n    puts(buff);\n    return false;\n}\n```\nlibtestnew.cpp定义了两个函数，一个用来替换libtest.so的puts函数，一个用来替换libtest.so的libtest函数\n\n现在我们开始编译并运行它\n```\n# cd test\n# ./build.sh\n# ./test\nlibtest 1\nlibtest 2\n...\nlibtest 10\n```\n程序开始运行，可以看到，不停的打印到输出，假设test的pid是11234\n\n* 示例1：让test在屏幕上打印一句话\n```\n# ./hookso syscall 11234 1 i=1 s=\"haha\" i=4\n4\n```\n注意这里的输出4，表示是系统调用的返回值。然后观察test的输出，可以看到haha输出\n```\nlibtest 12699\nlibtest 12700\nhahalibtest 12701\nlibtest 12702\nlibtest 12703\n```\n这里的几个参数说明：1是系统调用的号码，1表示的是write，i=1意思是一个int类型值为1的参数，s=\"haha\"则为字符串内容为haha\n\n所以这里等价于C语言调用了write(1, \"haha\", 4)，也就是在标准输出打印一句话\n\n* 示例2：让test调用libtest.so的libtest函数\n```\n# ./hookso call 11234 libtest.so libtest i=1234\n0\n```\n这里的参数和返回值，和示例1 syscall同理。然后观察test的输出，可以看到输出\n```\nlibtest 12713\nlibtest 12714\nlibtest 12715\nlibtest 1234\nlibtest 12716\nlibtest 12717\n```\nlibtest 1234则为我们插入的一次调用输出结果\n\n* 示例3：让test加载libtestnew.so\n```\n# ./hookso dlopen 11234 ./test/libtestnew.so \n13388992\n```\n注意这里的输出13388992，表示是dlopen的handle，这个handle后面卸载so会用到。然后查看系统/proc/11234/maps\n```\n# cat /proc/11234/maps \n00400000-00401000 r-xp 00000000 fc:01 678978                             /home/project/hookso/test/test\n00600000-00601000 r--p 00000000 fc:01 678978                             /home/project/hookso/test/test\n00601000-00602000 rw-p 00001000 fc:01 678978                             /home/project/hookso/test/test\n01044000-01076000 rw-p 00000000 00:00 0                                  [heap]\n7fb351aa9000-7fb351aaa000 r-xp 00000000 fc:01 678977                     /home/project/hookso/test/libtestnew.so\n7fb351aaa000-7fb351ca9000 ---p 00001000 fc:01 678977                     /home/project/hookso/test/libtestnew.so\n7fb351ca9000-7fb351caa000 r--p 00000000 fc:01 678977                     /home/project/hookso/test/libtestnew.so\n7fb351caa000-7fb351cab000 rw-p 00001000 fc:01 678977                     /home/project/hookso/test/libtestnew.so\n```\n可以看到libtestnew.so已经成功加载\n\n* 示例4：让test卸载libtestnew.so\n```\n# ./hookso dlclose 11234 13388992\n13388992\n```\n这个13388992是示例3 dlopen返回的handle值(多次dlopen的值是一样，并且dlopen多次就得dlclose多次才能真正卸载掉)。然后查看系统/proc/11234/maps\n```\n# cat /proc/16992/maps \n00400000-00401000 r-xp 00000000 fc:01 678978                             /home/project/hookso/test/test\n00600000-00601000 r--p 00000000 fc:01 678978                             /home/project/hookso/test/test\n00601000-00602000 rw-p 00001000 fc:01 678978                             /home/project/hookso/test/test\n01044000-01076000 rw-p 00000000 00:00 0                                  [heap]\n7fb3525ab000-7fb352765000 r-xp 00000000 fc:01 25054                      /usr/lib64/libc-2.17.so\n7fb352765000-7fb352964000 ---p 001ba000 fc:01 25054                      /usr/lib64/libc-2.17.so\n7fb352964000-7fb352968000 r--p 001b9000 fc:01 25054                      /usr/lib64/libc-2.17.so\n7fb352968000-7fb35296a000 rw-p 001bd000 fc:01 25054                      /usr/lib64/libc-2.17.so\n```\n可以看到已经没用libtestnew.so了\n\n* 示例5：让test加载libtestnew.so，执行libtestnew，然后卸载libtestnew.so\n```\n# ./hookso dlcall 11234 ./test/libtestnew.so libtestnew i=1234\n0\n```\n同理，这里的输出0为函数返回值。然后观察test的输出，可以看到libtestnew.so的libtestnew函数输出\n```\nlibtest 151\nlibtest 152\nlibtest 153\nlibtestnew 1234\nlibtest 154\nlibtest 155\n```\nlibtestnew 1234就是libtestnew.so的函数libtestnew输出，dlcall相当于执行了前面的dlopen、call、dlclose三步操作\n\n* 示例6：让test加载libtestnew.so，并把libtest.so的puts函数调用，修改为调用libtestnew.so的putsnew\n```\n# ./hookso replace 11234 libtest.so puts ./test/libtestnew.so putsnew\n13388992    140573454638880\n```\n注意这里的输出结果13388992表示handle，140573454638880表示替换之前的旧值，后面我们复原会用到。然后观察test的输出，可以看到已经调用到了libtestnew.so的putsnew方法\n```\nlibtest 3313\nlibtest 3314\nlibtest 3315\nlibtest 3316\nlibtest 3317\nputsnew libtest 3318\nputsnew libtest 3319\nputsnew libtest 3320\n```\n现在开始，libtest.so内部调用puts函数，就变成了调用libtestnew.so的putsnew函数了，libtest.so之外调用puts函数，还是以前的没有变\n\n* 示例7：让test的libtest.so的puts函数，恢复到之前，这里的140573454638880就是之前示例6 replace输出的backup旧值\n```\n# ./hookso setfunc 11234 libtest.so puts 140573454638880\n140573442652001\n```\n注意这里的setfunc也会输出旧值140573442652001，方便下次再还原。然后观察test的输出，可以看到又重新回到了puts方法\n```\nputsnew libtest 44\nputsnew libtest 45\nputsnew libtest 46\nlibtest 47\nlibtest 48\nlibtest 49\n```\n注意这时候libnewtest.so仍然在内存中，如果不需要可以用dlclose卸载它，这里不再赘述\n\n* 示例8：让test加载libtestnew.so，并把libtest.so的libtest函数，跳转到libtestnew的libtestnew，这个和示例6的区别是libtest是libtest.so内部实现的函数，puts是libtest.so调用的外部函数\n```\n# ./hookso replace 2936 libtest.so libtest ./test/libtestnew.so libtestnew\n13388992    10442863786053945429\n```\n这里的输出和示例6同理。然后观察test的输出，可以看到调用了libtestnew.so的libtestnew函数\n```\nlibtest 31714\nlibtest 31715\nlibtest 31716\nlibtest 31717\nlibtest 31718\nlibtestnew 31719\nlibtestnew 31720\nlibtestnew 31721\nlibtestnew 31722\nlibtestnew 31723\n```\n现在整个进程所有调用libtest的地方，都跳转到了libtestnew函数\n\n* 示例9：让test的libtest.so的libtest函数，恢复到之前，这里的10442863786053945429就是之前示例8 replace输出的替换旧值\n```\n# ./hookso setfunc 11234 libtest.so libtest 10442863786053945429\n1092601523177\n```\n然后观察test的输出，可以看到又回到了libtest.so的libtest函数\n```\nlibtestnew 26\nlibtestnew 27\nlibtestnew 28\nlibtestnew 29\nlibtest 30\nlibtest 31\nlibtest 32\n```\n\n* 示例10：查找test的libtest.so的libtest函数地址\n```\n# ./hookso find 11234 libtest.so libtest\n0x7fd9cfb91668  140573469644392\n```\n0x7fd9cfb91668即为地址，140573469644392是地址转成了uint64_t的值\n\n* 示例11：查看libtest.so的libtest的传参值\n```\n# ./hookso arg 11234 libtest.so libtest 1\n35\n# ./hookso arg 11234 libtest.so libtest 1\n36\n```\n最后一个参数1表示第1个参数，因为test是在循环+1，所以每次传入libtest函数的参数都在变化\n\n* 示例12：当执行libtest.so的libtest时，执行syscall，在屏幕上输出haha\n```\n# ./hookso trigger 11234 libtest.so libtest syscall 1 i=1 s=\"haha\" i=4\n4\n```\n然后观察test的输出，可以看到调用的输出\n```\nlibtest 521\nlibtest 522\nhahalibtest 523\nlibtest 524\n```\n\n* 示例13：当执行libtest.so的libtest时，执行call，用相同的参数调用一次libtest函数\n```\n# ./hookso trigger 11234 libtest.so libtest call libtest.so libtest @1\n0\n```\n然后观察test的输出，可以看到输出了两次818\n```\nlibtest 816\nlibtest 817\nlibtest 818\nlibtest 818\nlibtest 819\nlibtest 820\n```\n\n* 示例14：当执行libtest.so的libtest时，执行dlcall，用相同的参数调用一次libtestnew.so的libtestnew函数\n```\n# ./hookso trigger 11234 libtest.so libtest dlcall ./test/libtestnew.so libtestnew @1\n0\n```\n然后观察test的输出，可以看到输出了libtestnew的结果\n```\nlibtest 972\nlibtest 973\nlibtestnew 974\nlibtest 974\nlibtest 975\n```\n\n* 示例15：当执行libtest.so的libtest时，执行dlopen，注入libtestnew.so\n```\n# ./hookso trigger 11234 libtest.so libtest dlopen ./test/libtestnew.so  \n15367360\n```\n\n* 示例16：当执行libtest.so的libtest时，执行dlclose，卸载libtestnew.so\n```\n# ./hookso trigger 11234 libtest.so libtest dlclose 15367360\n15367360\n```\n\n* 示例17：查看某个地址的函数传参值，例如通过find得到的地址，或者其他途径得到的地址\n```\n# ./hookso argp 11234 140573469644392 1\n35\n# ./hookso argp 11234 140573469644392 1\n36\n```\n最后一个参数1表示第1个参数，因为test是在循环+1，所以每次传入libtest函数的参数都在变化\n\n* 示例18：当执行某个地址的函数时，执行syscall，在屏幕上输出haha\n```\n# ./hookso triggerp 11234 140573469644392 syscall 1 i=1 s=\"haha\" i=4\n4\n```\n其他triggerp的参数，与trigger相同，不再赘述\n\n* 示例19：通过其他方式（如gdb）获得libtest.so的libtest函数地址，修改其跳转到libtestnew.so的libtestnew\n```\n# gdb -p 11234 -ex \"p (long)libtest\" --batch | grep \"$1 = \" | awk '{print $3}'\n4196064\n# ./hookso replacep 11234 4196064 ./test/libtestnew.so libtestnew\n23030976        6295592 140220482557656\n```\n这里的输出分别代表handle、地址、地址的旧值，然后观察test的输出，可以看到输出了libtestnew的结果\n```\nlibtest 8\nlibtest 9\nlibtest 10\nlibtestnew 11\nlibtestnew 12\nlibtestnew 13\nlibtestnew 14\n```\n* 示例20：使用replacep输出的旧值，还原replacep的修改\n```\n# ./hookso setfuncp 11234 6295592 140220482557656\n139906556569240\n```\n然后观察test的输出，可以看到输出已经还原\n```\nlibtestnew 32\nlibtestnew 33\nlibtestnew 34\nlibtestnew 35\nlibtest 36\nlibtest 37\nlibtest 38\n```\n* 示例21：通过其他方式（如gdb）获得test的mysleep函数地址，修改其跳转到libtestnew.so的mysleepnew\n```\n# gdb -p 11234 -ex \"p (long)mysleep\" --batch | grep \"$1 = \" | awk '{print $3}'\n4196356\n# ./hookso replacep 11234 4196356 ./test/libtestnew.so mysleepnew\n23030976        4196356 1923701360725\n```\n这里类似示例19，不过这里是把test的原生低地址函数mysleep跳转到so中的高地址函数mysleepnew，内部实现机制不太相同。观察test的输出结果\n```\nlibtest 28\nlibtest 29\nlibtest 30\nlibtest 31\nmysleepnew\nlibtest 32\nmysleepnew\nlibtest 33\nmysleepnew\nlibtest 34\n```\n可以看到mysleepnew已经生效\n\n# 用法\n```\nhookso: type pid params\n\neg:\n\ndo syscall: \n# ./hookso syscall pid syscall-number i=int-param1 s=\"string-param2\" \n\ncall .so function: \n# ./hookso call pid target-so target-func i=int-param1 s=\"string-param2\" \n\ndlopen .so: \n# ./hookso dlopen pid target-so-path \n\ndlclose .so: \n# ./hookso dlclose pid handle \n\nopen .so and call function and close: \n# ./hookso dlcall pid target-so-path target-func i=int-param1 s=\"string-param2\" \n\nreplace src.so old-function to target.so new-function: \n# ./hookso replace pid src-so src-func target-so-path target-func \n\nreplace target-function-addr to target.so new-function: \n# ./hookso replacep pid func-addr target-so-path target-func \n\nset target.so target-function new value : \n# ./hookso setfunc pid target-so target-func value \n\nset target-function-addr new value : \n# ./hookso setfuncp pid func-addr value \n\nfind target.so target-function : \n# ./hookso find pid target-so target-func \n\nget target.so target-function call argument: \n# ./hookso arg pid target-so target-func arg-index \n\nget target-function-addr call argument: \n# ./hookso argp pid func-addr arg-index \n\nbefore call target.so target-function, do syscall/call/dlcall/dlopen/dlclose with params: \n# ./hookso trigger pid target-so target-func syscall syscall-number @1 i=int-param2 s=\"string-param3\" \n# ./hookso trigger pid target-so target-func call trigger-target-so trigger-target-func @1 i=int-param2 s=\"string-param3\" \n# ./hookso trigger pid target-so target-func dlcall trigger-target-so trigger-target-func @1 i=int-param2 s=\"string-param3\" \n# ./hookso trigger pid target-so target-func dlopen target-so-path\n# ./hookso trigger pid target-so target-func dlclose handle\n\nbefore call target-function-addr, do syscall/call/dlcall/dlopen/dlclose with params: \n# ./hookso triggerp pid func-addr syscall syscall-number @1 i=int-param2 s=\"string-param3\" \n# ./hookso triggerp pid func-addr call trigger-target-so trigger-target-func @1 i=int-param2 s=\"string-param3\" \n# ./hookso triggerp pid func-addr dlcall trigger-target-so trigger-target-func @1 i=int-param2 s=\"string-param3\" \n# ./hookso triggerp pid func-addr dlopen target-so-path\n# ./hookso triggerp pid func-addr dlclose handle\n```\n\n# QA\n##### 为什么就一个3k行+的main.cpp?\n因为东西简单，减少无谓的封装，增加可读性\n##### 这东西实际有什么作用？\n如同瑞士军刀一样，用处很多。可以用来热更新，或者监控某些函数行为，或者开启调试\n##### 函数调用有什么限制？\nsyscall、call、dlcall只支持最大6个参数的函数调用，并且参数只能支持整形、字符  \nreplace不受限制，但是必须确保新的函数和旧函数，参数一致，不然会core掉\n##### 有些so的函数会报错？\n某些so太大无法被全部load进内存，导致无法解析，运行失败，如\n```\n# ./hookso find 11234 libstdc++.so.6.0.28 __dynamic_cast                 \n[ERROR][2020.4.28,14:26:55,161]main.cpp:172,remote_process_read: remote_process_read fail 0x7fc375714760 5 Input/output error\n```\n把so参数修改成文件路径，这样就会从文件读取so信息\n```\n# ./hookso find 11234 /usr/local/lib64/libstdc++.so.6.0.28 __dynamic_cast\n0x7fc37475cea0   140477449227936\n```\n可以看到，find命令已成功执行，对于其他的命令如call、dlopen、replace同理\n\n# 应用\n[Lua 代码覆盖率工具 cLua](https://github.com/esrrhs/cLua)\n\n[Lua 性能分析工具 pLua](https://github.com/esrrhs/pLua)\n\n[Lua 调试工具 dLua](https://github.com/esrrhs/dlua)\n\n[Lua 监控工具 wLua](https://github.com/esrrhs/wLua)\n","funding_links":[],"categories":["C++"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fesrrhs%2Fhookso","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fesrrhs%2Fhookso","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fesrrhs%2Fhookso/lists"}