7

アプリケーションの dlsym への呼び出しをインターセプトしたい。dlsym をプリロードし、 dlsym 自体を使用して実際のアドレスを取得するように .so 内で宣言しようとしましたが、明らかな理由でうまくいきませんでした。

プロセスのメモリ マップを取得し、libelf を使用して、読み込まれた libdl.so 内の dlsym の実際の場所を見つけるよりも簡単な方法はありますか?

4

2 に答える 2

9

警告:

これを行おうとするすべての人に、はっきりと警告しなければなりません。共有ライブラリをフックするという一般的な前提には、dlsymいくつかの重大な欠点があります。最大の問題は、dlsymglibc が内部的にスタックの巻き戻し技術を使用して、どのロード済みモジュールから関数が呼び出されたかを調べる場合の元の実装です。傍受している共有ライブラリdlsymが元のアプリケーションに代わって元のライブラリを呼び出すと、現在のモジュールが最初に呼び出したモジュールではなく、フック ライブラリになるため、などを使用したルックアップが中断されます。RTLD_NEXT

これを正しい方法で実装することは可能かもしれませんが、より多くの作業が必要です。試したことはdlinfoありませんが、linket マップの連鎖リストを取得するために使用すると、すべてのモジュールを個別にウォークスルーし、dlsym各モジュールを個別に実行して、正しい動作を得ることができると思いRTLD_NEXTます。そのためには、呼び出し元のアドレスを取得する必要があります。これは、古いbacktrace(3)関数ファミリーを介して取得する場合があります。

2013年からの私の古い答え

コメンターと同じ hdante の回答の問題に出くわしました__libc_dlsym()。直接呼び出すと、segfault でクラッシュします。いくつかの glibc ソースを読んだ後、回避策として次のハックを思い付きました。

extern void *_dl_sym(void *, const char *, void *);
extern void *dlsym(void *handle, const char *name)
{
    /* my target binary is even asking for dlsym() via dlsym()... */
    if (!strcmp(name,"dlsym")) 
        return (void*)dlsym;
    return _dl_sym(handle, name, dlsym);
}

この「ソリューション」では、次の 2 つの点に注意してください。

  1. このコードは、 によって内部的に行われるロックをバイパスする(__libc_)dlsym()ため、このスレッドセーフにするために、ロックを追加する必要があります。
  2. の 3 番目の引数は呼び出し元のアドレスです。glibc_dl_sym()はスタックの巻き戻しによってこの値を再構築するようですが、関数自体のアドレスを使用するだけです。呼び出し元のアドレスは、呼び出し元のリンク マップを見つけるために内部的に使用されますRTLD_NEXT(また、3 番目の引数として NULL を使用すると、使用時にエラーで呼び出しが失敗しますRTLD_NEXT)。ただし、glibc の unwindind 機能については調べていないため、上記のコードが正しいことを 100% 確実に実行するとは限りません。

これまでに示したソリューションには、いくつかの重大な欠点があります。状況によって_dl_sym()は、意図したものとはまったく異なる動作をすることです。dlsym()たとえば、存在しないシンボルを解決しようとすると、NULL が返されるだけでなく、プログラムが終了します。これを回避する_dl_sym()には、元のポインターを取得して、dlsym()それを他のすべてに使用することができます (「標準」LD_PRELOADフック アプローチのように、フックdlsymをまったく使用しません)。

extern void *_dl_sym(void *, const char *, void *);
extern void *dlsym(void *handle, const char *name)
{
    static void * (*real_dlsym)(void *, const char *)=NULL;
    if (real_dlsym == NULL)
        real_dlsym=_dl_sym(RTLD_NEXT, "dlsym", dlsym);
    /* my target binary is even asking for dlsym() via dlsym()... */
    if (!strcmp(name,"dlsym")) 
        return (void*)dlsym;
    return real_dlsym(handle,name);
}

2021 年の更新 / glibc-2.34

glibc 2.34 以降、関数_dl_sym()は公開されなくなりました。私が提案できる別のアプローチはdlvsym()、代わりに使用することです。これは、正式には glibc API と ABI の一部です。dlsym唯一の欠点は、シンボルを要求するために正確なバージョンが必要になることです。幸いなことに、これは glibc ABI の一部でもありますが、残念ながら、アーキテクチャごとに異なります。ただし、grep 'GLIBC_.*\bdlsym\b' -r sysdepsglibc ソースのルート フォルダーにある a は、必要なものを教えてくれます。

[...]
sysdeps/unix/sysv/linux/i386/libc.abilist:GLIBC_2.0 dlsym F
sysdeps/unix/sysv/linux/i386/libc.abilist:GLIBC_2.34 dlsym F
[...]
sysdeps/unix/sysv/linux/x86_64/64/libc.abilist:GLIBC_2.2.5 dlsym F
sysdeps/unix/sysv/linux/x86_64/64/libc.abilist:GLIBC_2.34 dlsym F

glibc-2.34 では、実際にはこの関数の新しいバージョンが導入されましたが、後方互換性のために古いバージョンがまだ保持されています。

x86_64 の場合、次を使用できます。

real_dlsym=dlvsym(RTLD_NEXT, "dlsym", "GLIBC_2.2.5");

また、最新バージョンと、同じプロセス内の別のインターセプターの 1 つを取得したい場合は、そのバージョンを使用して、バージョン管理されていないクエリを再度実行できます。

real_dlsym=real_dlsym(RTLD_NEXT, "dlsym");

dlsym実際に と の両方を共有オブジェクトにフックする必要がある場合dlvsym、このアプローチはもちろん機能しません。

dlsym()更新:両方をdlvsym()同時にフックする

好奇心から、両方の glibc シンボル クエリ メソッドをフックする方法を考え、libdl. dlopen()この考え方は、インターセプタ ライブラリがフラグを使用して実行時にこのライブラリを動的にロードできるというものです。RTLD_LOCAL | RTLD_DEEPBINDこれにより、このオブジェクトの別のリンカ スコープが作成され、 も含まlibdldlsymますdlvsym。インターセプター ライブラリで。ここでの問題は、インターセプター ライブラリがラッパー ライブラリ内の関数を直接呼び出すことができないことdlsymです。これは、元の問題である を使用できないためです。

ただし、共有ライブラリには初期化関数を含めることができます。これは、リンカーがdlopen()戻る前に呼び出すものです。ラッパー ライブラリの初期化関数からインターセプタ ライブラリにいくつかの情報を渡す必要があるだけです。どちらも同じプロセスにあるため、環境ブロックを使用できます。

これは私が思いついたコードです:

dlsym_wrapper.h:

#ifndef DLSYM_WRAPPER_H
#define DLSYM_WRAPPER_H

#define DLSYM_WRAPPER_ENVNAME "DLSYM_WRAPPER_ORIG_FPTR"
#define DLSYM_WRAPPER_NAME "dlsym_wrapper.so"
typedef void* (*DLSYM_PROC_T)(void*, const char*);

#endif

dlsym_wrapper.c、にコンパイルdlsym_wrapper.so:

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

#include "dlsym_wrapper.h"

__attribute__((constructor))
static void dlsym_wrapper_init()
{
    if (getenv(DLSYM_WRAPPER_ENVNAME) == NULL) {
        /* big enough to hold our pointer as hex string, plus a NUL-terminator */
        char buf[sizeof(DLSYM_PROC_T)*2 + 3];
        DLSYM_PROC_T dlsym_ptr=dlsym;
        if (snprintf(buf, sizeof(buf), "%p", dlsym_ptr) < (int)sizeof(buf)) {
            buf[sizeof(buf)-1] = 0;
            if (setenv(DLSYM_WRAPPER_ENVNAME, buf, 1)) {
                // error, setenv failed ...
            }
        } else {
            // error, writing pointer hex string failed ...
        }
    } else {
        // error: environment variable already set ...
    }
}

そして、オリジナルへのポインタを取得するためのインターセプタ ライブラリ内の 1 つの関数dlsym()(ミューテックスによって保護され、1 回だけ呼び出される必要があります):

static void *dlsym_wrapper_get_dlsym
{
    char dlsym_wrapper_name = DLSYM_WRAPPER_NAME;
    void *wrapper;
    const char * ptr_str;
    void *res = NULL;
    void *ptr = NULL;

    if (getenv(DLSYM_WRAPPER_ENVNAME)) {
        // error: already defined, shoudn't be...
    }
    wrapper = dlopen(dlsym_wrapper_name, RTLD_LAZY | RTLD_LOCAL | RTLD_DEEPBIND | RTLD_NOLOAD);
    if (wrapper) {
        // error: dlsym_wrapper.so already loaded ...
        // it is important that we load it by ourselves to a sepearte linker scope
    }
    wrapper = dlopen(dlsym_wrapper_name, RTLD_LAZY | RTLD_LOCAL | RTLD_DEEPBIND);
    if (!wrapper) {
        // error: dlsym_wrapper.so can't be loaded
    }

    ptr_str = getenv(DLSYM_WRAPPER_ENVNAME);
    if (!ptr_str) {
        // error: dlsym_wrapper.so failed...
    }
    if (sscanf(ptr_str, "%p", &ptr) == 1) {
        if (ptr) {
            // success!
            res = ptr;
        } else {
            // error: got invalid pointer ...
        }
    } else {
        // error: failed to parse pointer...
    }

    // this is a bit evil: close the wrapper. we can be sure
    // that libdl still is used, as this mosule uses it (dlopen)
    dlclose(wrapper);

    return res;
}

dlsym_wrapper.soもちろん、これはそれがライブラリ検索パスにあることを前提としています。LD_PRELOADただし、完全なパスを使用してインターセプター ライブラリを挿入し、まったく変更LD_LIBRARY_PATHしない方がよい場合もあります。これを行うには、追加dladdr(dlsym_wrapper_get_dlsym,...)してインジェクター ライブラリ自体のパスを検索し、それを使用してラッパー ライブラリを検索することもできます。

于 2013-09-16T09:52:01.560 に答える
-1

http://www.linuxforu.com/2011/08/lets-hook-a-library-function/

テキストから:

フックで __libc_dlsym (ハンドル、シンボル) を呼び出す必要がある場合は、それ自体が dlsym() を呼び出す関数に注意してください。

extern void *__libc_dlsym (void *, const char *);
void *dlsym(void *handle, const char *symbol)
{
    printf("Ha Ha...dlsym() Hooked\n");
    void* result = __libc_dlsym(handle, symbol); /* now, this will call dlsym() library function */
    return result;
}
于 2013-03-24T13:38:58.473 に答える