在C++中使用HOOK修改sleep函數(shù)的方法
HOOK是什么
Hook(鉤子)是一種編程機制,它允許開發(fā)者在程序執(zhí)行的特定點插入自定義代碼,從而攔截、處理或修改原有的函數(shù)調(diào)用、消息傳遞或系統(tǒng)事件。
通俗地說,Hook就像是給程序安裝了一個“監(jiān)聽器”或“攔截器”。當(dāng)目標(biāo)函數(shù)被調(diào)用時,控制權(quán)會先轉(zhuǎn)移到你的Hook代碼,你可以在執(zhí)行原有操作之前或之后插入自定義邏輯,甚至完全替換原有行為。例如游戲外 掛通過hook來對游戲運行時用到的函數(shù)或其他API進行修改來實現(xiàn)外 掛的功能。同時,hook也常與協(xié)程搭配使用,修改系統(tǒng)函數(shù)來為類似sleep等阻塞線程的函數(shù)添加協(xié)程的功能。
如何使用hook來修改sleep函數(shù)
下面的代碼是最簡單的hook的實現(xiàn)
#include <iostream>
#include <unistd.h>
extern "C" unsigned int sleep(unsigned int seconds)
{
std::cout << "我們成功修改了系統(tǒng)提供的sleep函數(shù)!" << std::endl;
return 0;
}
void test1()
{
std::cout << "使用sleep函數(shù)睡2s"<< std::endl;
sleep(2);
std::cout << "sleep函數(shù)睡完了"<< std::endl;
}
調(diào)用函數(shù)test1(),程序運行結(jié)果如下:
使用sleep函數(shù)睡2s 我們成功修改了系統(tǒng)提供的sleep函數(shù)! sleep函數(shù)睡完了
在上面的代碼中,我們僅做了兩件事
- 實現(xiàn)一個
sleep函數(shù),與unistd.h中的sleep函數(shù)簽名一致 - 使用
extern "C"告訴C++編譯器"按C語言的方式處理這個函數(shù)"
定義一個相同簽名的sleep為什么鏈接過程中不會產(chǎn)生重定義問題呢?在鏈接器鏈接過程中,函數(shù)符號有類似強弱符號之分,在動態(tài)庫中的函數(shù)會被新目標(biāo)文件的函數(shù)替換,因此此處程序運行時會運行我們重新寫的sleep函數(shù)而不是unistd.h內(nèi)的。
至于加extern "C"的作用,在c++編譯過程中,為了區(qū)分不同的重載函數(shù),編譯器會給同名函數(shù)加入隨即字符進行區(qū)分,我們的目的是重寫sleep函數(shù),因此要確保函數(shù)名與unistd.h中相同,通過加入extern "C"來做到這一點
對上述代碼的改進
上述代碼存在很大的缺陷,最主要的是它失去了sleep函數(shù)最基本的功能。通常我們利用hook修改函數(shù)時,我們需要維持其原有功能。我們不可能真的去實現(xiàn)一個完整的sleep,但我們可以獲得原sleep的函數(shù)指針
在不同的平臺有不同的獲取庫函數(shù)指針的方法,下面時在linux平臺來獲取sleep函數(shù)指針的例子:
獲取sleep函數(shù)指針
linux為獲取庫函數(shù)指針提供了特定的函數(shù)dlsym,定義在<dlfcn.h>中。其函數(shù)簽名為:
void *dlsym(void *restrict handle, const char *restrict symbol);
dlsym返回值是函數(shù)指針,其第一個參數(shù)是指定查找的庫,第二個參數(shù)傳入函數(shù)名稱。
在hook場景中,handle參數(shù)常取RTLD_NEXT,表示跳過當(dāng)前庫查找其他庫。也就是說,當(dāng)symbol傳入"sleep"時,dlsym跳過當(dāng)前庫我們定義的sleep,找到了unistd.h定義的sleep函數(shù),并返回其函數(shù)指針。
代碼改進
有了上邊提供的函數(shù),我們可以保存原有sleep函數(shù)并給他加點"小料",代碼如下:
#include <iostream>
#include <unistd.h>
#include <dlfcn.h>
using sleep_fun_type = unsigned int (*)(unsigned int seconds);
sleep_fun_type original_sleep = NULL;
extern "C" unsigned int sleep(unsigned int seconds)
{
std::cout << "我們成功修改了系統(tǒng)提供的sleep函數(shù)!" << std::endl;
return original_sleep(seconds); // <-----這里調(diào)用我們保存下來的原始的sleep
}
void test1()
{
original_sleep = (sleep_fun_type)dlsym(RTLD_NEXT, "sleep"); // <-----這里獲得了unistd中的sleep
std::cout << "使用sleep函數(shù)睡2s"<< std::endl;
sleep(2); // <-----這里調(diào)用我們自己寫的sleep
std::cout << "sleep函數(shù)睡完了"<< std::endl;
}
代碼相較于開始,只做了一點改進,即保存原始sleep函數(shù),并在我們自己定義的sleep函數(shù)中調(diào)用保存的原始sleep函數(shù)。
需注意的是,編譯時應(yīng)加上-ldl選項鏈接動態(tài)庫
總結(jié)
上述代碼仍有許多不完善的地方,實際過程中要檢查dlsym返回值是否為NULL等問題,同時代碼對初始化并不規(guī)范,可以使用下面的初始化方法(gcc編譯器),也可以使用其他更兼容的方法進行初始化。
__attribute__((constructor)) void init_hook() // gcc編譯器提供,main函數(shù)運行前,庫和內(nèi)存初始化完成后運行
{
// 在main函數(shù)執(zhí)行前先初始化—original_sleep。
original_sleep = (SleepFunc)dlsym(RTLD_NEXT,"sleep");
}
最后,需要注意的是,如果采用上述方法重新定義sleep,會使所有庫運行的sleep函數(shù)都改變成我們自己定義的sleep,如果返回值與原sleep存在差異,可能導(dǎo)致一些其他的隱含問題。
以上就是在C++中使用HOOK修改sleep函數(shù)的方法的詳細(xì)內(nèi)容,更多關(guān)于C++ HOOK修改sleep函數(shù)的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
C++代碼改造為UTF-8編碼問題的總結(jié)(最新推薦)
本文總結(jié)了如何將C++程序代碼改造為UTF-8編碼,包括操作系統(tǒng)、編譯器和終端等各方面的設(shè)置,在實際操作中,可以通過漸進式更新的方式,只在新的代碼項目中使用UTF-8編碼,避免大規(guī)模修改舊代碼,感興趣的朋友一起看看吧2025-02-02
求斐波那契(Fibonacci)數(shù)列通項的七種實現(xiàn)方法
本篇文章是對求斐波那契(Fibonacci)數(shù)列通項的七種實現(xiàn)方法進行了詳細(xì)的分析介紹,需要的朋友參考下2013-05-05
Qt實現(xiàn)XML與JSON數(shù)據(jù)解析全攻略
XML(可擴展標(biāo)記語言)和JSON(JavaScript對象表示法)是兩種最常用的數(shù)據(jù)格式,分別適用于不同的場景,本文將詳細(xì)介紹如何利用Qt庫來高效地處理XML和JSON數(shù)據(jù),感興趣的可以了解下2025-04-04
C語言中隊列的結(jié)構(gòu)和函數(shù)接口的使用示例
隊列只允許一端進行插入數(shù)據(jù)操作,在另一端進行刪除數(shù)據(jù)操作的特殊線性表,隊列具有先進先出FIFO的性質(zhì);隊列可用數(shù)組和鏈表 的方法實現(xiàn),使用鏈表的結(jié)構(gòu)實現(xiàn)更優(yōu)一些,因為如果使用數(shù)組節(jié),出隊列時刪去首元素需要將整個數(shù)組前移,效率比較低2023-02-02

