UNIX 編程技巧——如何 hook 一個共享庫

有時程序員需要完成這類任務:

假如你有一個二進制版的系統,例如現在流行的android,你需要爲這個系統開發一個軟件。

這個軟件牽涉到系統行爲,因此需要對系統做修改。然而你並沒有這個系統的所有源碼

( Nexus S的源碼不一定與 android 官方版本一模一樣),或者是你只有這個系統的頭文件。

當你需要修改部分系統行爲的時候,你不可能用源碼重新編譯一個共享庫來替換系統文件。


此時,就需要利用 dlfcn.h 運行時動態鏈接庫來實現我們的目標。利用 dlopen(),dlsym(),dlerror(),dlclose()這

四個函數,可以實現類似 Windows 平臺 Api Hook 機制的效果。


步驟如下:

1、首先得先將原共享庫改名。假如系統中有一個共享庫libwuxiao.so,那麼就需要

      把它改成其他名字,比如,改爲 libwuxiao_orig.so。

假如這個共享庫的源碼如下:

#include <stdio.h>
void func_a(int a, int b)
{
    printf("func_a called a=%d b=%d\n",a,b);
}
void func_b(char* string)
{
    printf("func_b called string=%s\n",string);
}
int func_c()
{
    return 123;
}

    編譯一下, gcc -shared -fPIC -o libwuxiao.so wuxiao.c

可以寫一個可執行程序來測試一下這個共享庫。之後,你可以看到

我們完全不修改這個可執行文件,讓這個可執行文件執行我們增加的代碼。

#include <stdio.h>

extern void func_a(int a, int b);
extern void func_b(char* string);
extern int func_c();

int main(int argc, char* argv)
{
    func_a(55,66);
    func_b("hello,wuxiao");
    int b = func_c();
    printf("after func(), b=%d\n",b);
}

 gcc -o wuxiao_test main.c -lwuxiao -L/path/to/libwuxiao.so

2、然後有兩類編程方法實現wrapper:

      a) 先調用dlopen(),返回libwuxiao_orig.so的句柄,再從句柄中查找想要的函數

#include <stdio.h>
#include <dlfcn.h>

static void* libwuxiao_handle = NULL;

__attribute__((constructor)) _init_wuxiao()
{
    libwu_handle = dlopen("libwuxiao_orig.so",RTLD_NOW);  // 注意:已經改名字了
}

__attribute__((destructor)) void _fini_wuxiao()
{
    dlclose(libwu_handle);
}

int func_c()
{
    int (*func_c_ptr)();
    func_c_ptr = dlsym(libwuxiao_handle,"func_c");
    if ( func_c_ptr != NULL )
    {
        printf(" add your hook code here\n");
        return func_c_ptr();
    }
    return -1;

}

       鏈接方法:  gcc -shared -fPIC -o libwuxiao.so -ldl -lwuxiao_orig -L/path/to/libwuxiao_orig.so

(編譯完上面的僞裝共享庫之後,執行一下 main_test,看看增加的代碼有沒有被執行。)


       上面代碼中,原共享庫的 func_c 就是我們想要修改的函數。

       這裏寫了一個相同的 func_c,它查找到原函數的地址,先執行 hook 代碼,然後執行原 func_c 函數,

       因而不會影響原函數的執行,只不過多執行了我們的代碼。

       隨後 “僞裝”成爲libwuxiao.so,系統在執行程序的時候,就會查找僞造的 libwuxiao.so,

       而僞造的libwuxiao.so又會去加載真正的 libwuxiao_orig.so。

       這樣達到了我們的目標:

           1、保證原libwuxiao.so中的所有函數不會丟失

           2、在調用原libwuxiao.so中的函數的時候能夠執行我自己的代碼。


       b) 這裏的方法更簡單,原理與上面的相同,都是通過在僞造的libwuxiao.so中鏈接原libwuxiao_orig.so,

            然後再定義同型函數,查找原函數地址,然後調用原函數的方法。這裏的方法僅對GNU版本的系統有效。

#include <stdio.h>
#include <sys/types.h>
#include <dlfcn.h>

int func_c()
{
    int (*func_c_ptr)();
    
    // find func_c() in another shared object(with the same name)
    dlerror();
    func_c_ptr = dlsym(RTLD_DEFAULT,"func_c");
    while ( func_c_ptr == func_c )
    {
        if ( func_c_ptr == NULL ) {
            printf("Error(wrapper-2): cannot find func_c()\n");
            break;
        }
        
        dlerror();
        func_c_ptr = dlsym(RTLD_NEXT,"func_c");
    }
    
    if ( func_c_ptr != NULL )
    {
        printf("wuxiao is very powerful the second time\n");
        return func_c_ptr();
    }
    return -1;
}

編譯: gcc -shared -fPIC -o libwuxiao.so -ldl -lwuxiao_orig -L. -D_GNU_SOURCE

   這種方法雖然更簡單,但只對GNU有效,如果你的代碼要放在非GNU的系統上執行,那還是用a)中的方法吧。

總結:

    1、這裏實現的wrapper的侷限性就在於,你必須知道原來的共享庫中的函數原型,但是你不必知道所有函數的原型。

    你只需要知道其中你需要的函數原型即可。

    2、如果你遇到一個共享庫,裏面的符號是強符號(不能再定義一個同名函數了),則這裏的方法不再有用了。

發佈了252 篇原創文章 · 獲贊 31 · 訪問量 89萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章