Linuxでメモリから動的ライブラリをロードする

クロスプラットフォームプロジェクトの1つでは、ダウンロードする前にプラグインのデジタル署名を検証する機能が必要でした。 ファイルを作成するためのオプションはどれも安全ではありません。静的検証が既に実行されているため、署名検証とダウンロードの間にファイルを置き換えることができ、ダウンロード後に署名を検証することはできません。 したがって、ファイルを作成せずにプラグインをダウンロードする必要があります。



ld.soはライブラリに静的にリンクされているため、open、mmap、およびその他の関数をインターセプトすることはできません。ローダーによってロードされた実行可能ファイルは「劣っています」(libdlの関数をインターセプトしても)それらの文字はdlsymからは見えません。 したがって、システムコールのインターセプトのみが残ります。



ブートローダーエントリポイント:

void *dlopen_memory(void *base, size_t size, void *(*custom_dlopen)(const char *filename, void *arg), void *arg);
      
      





パラメータ:



戻り値はdlopenと同じです。ライブラリハンドルまたはNULL、およびdlerror()によるエラーの説明。



次のように機能します。

  1. 擬似ランダムライブラリ名が生成されます。 読みやすいものが必要な場合は、これだけが必要です-追加で渡します。 ファイル名パラメーター。
  2. 空のSIGQUITシグナルハンドラがインストールされ、古いものが保存されます。 この信号は、ダウンロードが完了すると後で使用されます。
  3. 実際にロードする子プロセスが作成されます。
  4. 子プロセスの準備完了フラグの保留中の設定。
  5. ライブラリは、dlopenまたはcustom_dlopenを介してロードされています。
  6. SIGQUITは独自のプロセスに送信されます。
  7. 子プロセスの予想される完了。
  8. SIGQUITハンドラーが復元されます。


子プロセスは次のアクションを実行します。

  1. 親の追跡を開始します。 これはいくつかの興味深い結果につながります。たとえば、彼は彼の親の親になります。
  2. 親プロセスに準備完了フラグを設定します。
  3. 親プロセスが実行されており、停止が要求されていない間:

    1. 親プロセスは、システムコールに入る前に実行されます。 システムコールがmmap擬似ファイルではない場合、プロセスはコールを終了する前でも実行されます。
    2. 子プロセスのレジスタが取得されます。
    3. この呼び出しが親プロセスに対してSIGQUIT呼び出しを行う場合、トレースは停止します。
    4. この呼び出しがライブラリ疑似ファイルのオープンである場合、出力記述子は、疑似ファイルでの操作を識別するために使用される特別な記述子に置き換えられます。
    5. この呼び出しが疑似ファイルからの読み取りである場合、ライブラリイメージから親プロセスのバッファーへの読み取りが実行され、呼び出しは正常に完了します。
    6. この呼び出しがファイル属性の取得である場合、擬似ファイルに関する情報が生成され、親プロセスのバッファーに配置されます(その中では、サイズのみが意味をなします)。
    7. この呼び出しがファイルのクローズである場合、呼び出しは正常に完了します。
    8. この呼び出しが擬似ファイルのメモリへのマッピングである場合、次のアクションが実行されます。

      1. MAP_ANONYMOUSフラグが設定されます(ファイルにマップされていないメモリ領域が要求されます)。
      2. mmap呼び出しが進行中です。
      3. 親プロセスで作成された領域に、擬似ファイルの要求された部分がコピーされます。
  4. プロセスは親から切断され、終了します。


このコードは移植性がなく、Linuxおよび32ビットモードのIA-32アーキテクチャのプロセッサでのみ動作します。 異なるアーキテクチャのシステムでは、システムコールのエミュレーションを実装する必要があります(#if /#endでラップ)、異なる語長のシステムでは、peekstringプロシージャを変更する必要もあります。 ptraceまたはwaitpidの呼び出しが失敗すると、親プロセスと子プロセスの両方の作業が終了します。 別の動作が必要な場合は、failラベルの背後にあるハンドラーを書き換えます。



コードは制限なしで無料で使用できます。

 #include <sys/mman.h> #include <sys/ptrace.h> #include <string.h> #include <stdlib.h> #include <assert.h> #include <time.h> #include <stdio.h> #include <sched.h> #include <unistd.h> #include <signal.h> #include <sys/wait.h> #include <sys/user.h> #include <sys/syscall.h> #include <dlfcn.h> #include <errno.h> #include <limits.h> #include <fcntl.h> #include <sys/stat.h> #define min(a, b) ((a) < (b) ? (a) : (b)) static void generate_name(char *name, size_t length) { assert(length > 5); strcpy(name + length - 4, ".so"); for(unsigned int i = 1; i < length - 4; i++) name[i] = rand() % ('Z' - 'A' + 1) + 'A'; name[0] = '/'; } static void quit_handler(int sig) { (void) sig; } static int peekstring(pid_t pid, void *base, char *dest, size_t length) { unsigned int word; unsigned int offset = 0; do { word = ptrace(PTRACE_PEEKDATA, pid, base + offset, NULL); memcpy(dest + offset, &word, sizeof(unsigned int)); offset += sizeof(unsigned int); } while((word & 0xFF) && (word & 0xFF00) && (word & 0xFF0000) && (word & 0xFF000000) && offset < length); dest[length - 1] = 0; return 0; } static int pokedata(pid_t pid, void *address, const void *data, size_t length) { length = (length + sizeof(unsigned int) - 1) & ~(sizeof(unsigned int) - 1); const unsigned int *src = data; for(unsigned int offset = 0; offset < length; offset += sizeof(unsigned int)) { if(ptrace(PTRACE_POKEDATA, pid, address + offset, (void *) *src++) == -1) return -1; } return 0; } void *dlopen_memory(void *base, size_t size, void *(*custom_dlopen)(const char *filename, void *arg), void *arg) { char fakename[16]; int ready = 0; unsigned int offset = 0; generate_name(fakename, sizeof(fakename)); struct sigaction old_handler, new_handler = { .sa_handler = quit_handler, .sa_flags = 0, .sa_restorer = NULL }; sigfillset(&new_handler.sa_mask); if(sigaction(SIGQUIT, &new_handler, &old_handler) == -1) return NULL; pid_t child = fork(); if(child == -1) { sigaction(SIGQUIT, &old_handler, NULL); return NULL; } else if(child == 0) { pid_t parent = getppid(); if(ptrace(PTRACE_ATTACH, parent, NULL, NULL) == -1) { kill(parent, SIGKILL); _exit(1); } ready = 1; if(ptrace(PTRACE_POKEDATA, parent, &ready, (void *)ready) == -1) goto fail; int status; char path[PATH_MAX]; int handle = getdtablesize(); do { struct user_regs_struct regs; for(int i = 0; i < 2; i++) { if(ptrace(PTRACE_SYSCALL, parent, NULL, NULL) == -1) goto fail; if(waitpid(parent, &status, 0) == -1) goto fail; if(!WIFSTOPPED(status)) goto outer_break; if(ptrace(PTRACE_GETREGS, parent, NULL, ®s) == -1) goto fail; if(regs.orig_eax == SYS_mmap2 && regs.edi == handle) break; } if(regs.orig_eax == SYS_kill && regs.ebx == parent && regs.ecx == SIGQUIT) { break; } else if(regs.orig_eax == SYS_open) { if(peekstring(parent, (void *) regs.ebx, path, PATH_MAX) == -1) goto fail; if(strcmp(path, fakename) != 0) continue; regs.eax = handle; offset = 0; } else if(regs.orig_eax == SYS_read && regs.ebx == handle) { unsigned int bytes = min((unsigned int) regs.edx, size - offset); if(pokedata(parent, (void *) regs.ecx, base + offset, bytes) == -1) goto fail; offset += bytes; regs.eax = bytes; } else if(regs.orig_eax == SYS_close && regs.ebx == handle) { regs.eax = 0; } else if(regs.orig_eax == SYS_fstat64 && regs.ebx == handle) { struct stat statbuf = { .st_dev = 0, .st_ino = 1, .st_mode = 0444, .st_nlink = 1, .st_uid = 0, .st_gid = 0, .st_rdev = 0, .st_size = size, .st_blksize = 512, .st_blocks = (size + 511) & ~511, .st_atim = { 0, 0 }, .st_mtim = { 0, 0 }, .st_ctim = { 0, 0 } }; if(pokedata(parent, (void *) regs.ecx, &statbuf, sizeof(struct stat)) == -1) goto fail; regs.eax = 0; } else if(regs.orig_eax == SYS_mmap2 && regs.edi == handle) { regs.esi |= MAP_ANONYMOUS; if(ptrace(PTRACE_SETREGS, parent, NULL, ®s) == -1) goto fail; if(ptrace(PTRACE_SYSCALL, parent, NULL, NULL) == -1) goto fail; if(waitpid(parent, &status, 0) == -1) goto fail; if(!WIFSTOPPED(status)) break; if(ptrace(PTRACE_GETREGS, parent, NULL, ®s) == -1) goto fail; unsigned int offset = regs.ebp * 4096; unsigned int bytes = min(size - offset, (unsigned int) regs.ecx); if(pokedata(parent, (void *) regs.eax, base + offset, bytes) < 0) { regs.eax = -errno; if(ptrace(PTRACE_SETREGS, parent, NULL, ®s) == -1) goto fail; } continue; } else if(regs.orig_eax == -1) break; if(ptrace(PTRACE_SETREGS, parent, NULL, ®s) == -1) goto fail; } while(1); outer_break: ptrace(PTRACE_DETACH, parent, NULL, NULL); _exit(0); fail: ptrace(PTRACE_KILL, parent, NULL, NULL); _exit(1); } while(ready == 0) sched_yield(); void *handle; if(custom_dlopen) handle = custom_dlopen(fakename, arg); else handle = dlopen(fakename, RTLD_NOW); kill(getpid(), SIGQUIT); waitpid(child, NULL, 0); sigaction(SIGQUIT, &old_handler, NULL); return handle; }
      
      






All Articles