目錄

廣告 AD

DllShimmer:建立 DLL 代理,在函式呼叫中嵌入你的程式碼

路過剛好看到這個和 DLL 有關的 repository

就順手玩看看做個紀錄

廣告 AD

DllShimmer 可以透過 DLL 的內部資料,產生一個 CPP 的樣板檔案,這樣我們可以在這個樣板檔案上插入任何我們想要執行的程式碼,這在觀察 DLL 行為和 Debug 上都蠻有實用價值的。


要插入 function 到 DLL 前,我們必須先準備一個 DLL 來當作目標

這裡使用之前在介紹製作 DLL 檔案時的範例:

C++:如何打造一個動態連結函式庫

C++:如何打造一個動態連結函式庫

為 C++ 開發者設計,詳細說明如何建置跨平台的動態連結函式庫 (DLL)。您將學會利用 CMake 管理複雜的編譯與連結,並理解如何處理符號可見性等問題,打造高效且可重用的程式碼模組。

閱讀全文

裡面將自製的 backpack class 的實作隱藏起來,並包裝成一個 DLL 檔案

而 Shared Library 的 function 宣告如下:

BackpackHandle *create_backpack(const size_t max_size);
void destroy_backpack(BackpackHandle *handle);
MyLibErrorCode add_item(const BackpackHandle *handle, const void *buf, size_t size);
MyLibErrorCode remove_last_item(const BackpackHandle *handle);
MyLibErrorCode get_item_size(const BackpackHandle *handle, const size_t idx, size_t *size);
MyLibErrorCode get_item(const BackpackHandle *handle, const size_t idx, void *buf);
MyLibErrorCode get_size(const BackpackHandle *handle, size_t *size);
const char *get_error_string(MyLibErrorCode code);

這些接下來我們會透過 DllShimmer 來在這些 function 之間插入額外的程式碼


原始的程式會 import 一個 DLL,並使用裡面的 function,類似圖片上方 Program 使用 DLL

我們如果需要插入額外的 function,需要透過 DllShimmer 來幫助我們建立一個新的 DLL (圖片下方綠色方框)

這樣我們新的程式碼可以放在新的 DLL 裡面,程式變成跟新的 DLL 做互動,然後再透過新的 DLL 跟原本的 DLL 互動,接下來就來看看怎麼製作新的 DLL 吧 ~

我們先下載預先編譯好的 DllShimmer 程式下來 ~

檔案可以到 Github (Github release link) 下載

由於我是 Windows OS,所以是下載圖中紅色框框的版本,其他 OS 可以自己下載其他的版本 ~

接著透過 DllShimmer 幫我們產生和原本的 DLL 一模一樣介面的程式碼出來

  • dll_file_path: 目標 DLL 檔案的位置,DllShimmer 需要讀取 DLL 裡面的資訊來建立 code
  • output_path: 透過分析目標 DLL 後,新產生的檔案要放置的路徑,記得要確保路徑是存在的
  • path_to_find_original_dll_file: 與 dll_file_path 不同,因為在程式運行時,新的 DLL (圖中綠色) 要去找到原本的 DLL (圖中藍色),而這變數就代表著程式運行當中,要去哪裡找到這原本 DLL,路徑可以是絕對的也可以是相對的。
./DllShimmer.exe -i <dll_file_path> -o <output_path> -x <path_to_find_original_dll_file>

Example:

這邊 dll_file_path 選擇了我之前的範例 libmylib.dll,然後因為我之後會把原本的 DLL 改名為 “libmylib_orig.dll”,所以 path_to_find_original_dll_file 會改成這樣。

$ ./DllShimmer.exe -i libmylib.dll -o dll -x "libmylib_orig.dll"

▓█████▄  ██▓     ██▓
▒██▀ ██▌▓██▒    ▓██▒                 By @Print3M
░██   █▌▒██░    ▒██░            (print3m.github.io)
░▓█▄   ▌▒██░    ▒██░
░▒████▓ ░██████▒░██████▒            Documentation:
 ▒▒▓  ▒ ░ ▒░▓  ░░ ▒░▓  ░     github.com/Print3M/DllShimmer
 ░ ▒  ▒ ░ ░ ▒  ░░ ░ ▒  ░
 ░ ░  ░   ░ ░     ░ ░                   1.1.1
   ░        ░  ░    ░  ░
  ██████  ██░ ██  ██▓ ███▄ ▄███▓ ███▄ ▄███▓▓█████  ██▀███
▒██    ▒ ▓██░ ██▒▓██▒▓██▒▀█▀ ██▒▓██▒▀█▀ ██▒▓█   ▀ ▓██ ▒ ██▒
░ ▓██▄   ▒██▀▀██░▒██▒▓██    ▓██░▓██    ▓██░▒███   ▓██ ░▄█ ▒
  ▒   ██▒░▓█ ░██ ░██░▒██    ▒██ ▒██    ▒██ ▒▓█  ▄ ▒██▀▀█▄
▒██████▒▒░▓█▒░██▓░██░▒██▒   ░██▒▒██▒   ░██▒░▒████▒░██▓ ▒██▒
▒ ▒▓▒ ▒ ░ ▒ ░░▒░▒░▓  ░ ▒░   ░  ░░ ▒░   ░  ░░░ ▒░ ░░ ▒▓ ░▒▓░
░ ░▒  ░ ░ ▒ ░▒░ ░ ▒ ░░  ░      ░░  ░      ░ ░ ░  ░  ░▒ ░ ▒░
░  ░  ░   ░  ░░ ░ ▒ ░░      ░   ░      ░      ░     ░░   ░
      ░   ░  ░  ░ ░         ░          ░      ░  ░   ░

[+] 'libmylib.dll.cpp' file created
[+] 'dllshimmer.h' file created
[+] 'libmylib.dll.def' file created
[+] 'compile.sh' file created

Success! What to do next?

  1. Jump into the './' directory.
  2. Add your backdoor to the 'libmylib.dll.cpp' file.
  3. Compile project using the 'compile.sh' script.

你會看到多出了很多的檔案,整個路徑的檔案有:

.
├── dll/
│   ├── compile.sh
│   ├── dllshimmer.h
│   ├── libmylib.dll.cpp
│   └── libmylib.dll.def
├── DllShimmer.exe
└── libmylib.dll

接著我們可以依照程式輸出的指示操作,先來改產生出的 CPP 檔案 (libmylib.dll.cpp)

CPP 檔案中會有許多我們 export 出的 function,裡面可以看到:

// Put your code here...

我們可以開始將註解替換成我們的程式碼了

// add_item
extern "C" UINT64 add_itemFwd(PARAMS) {
    #ifdef DEBUG
        dbgf("add_item called");
    #endif
    
    // Put your code here...

    return PROXY_FUNCTION("add_item");
}

這邊我把傳入的參數印出來,當作是想要 Debug 看看 DLL 這邊都收到甚麼參數

// Generated by DllShimmer (github.com/Print3M/DllShimmer)
// 
// Author: Print3M (print3m.github.io/)
#include "dllshimmer.h"
#include <windows.h>
#include <stdio.h>

// add_item
extern "C" UINT64 add_itemFwd(PARAMS) {
    #ifdef DEBUG
        dbgf("add_item called");
    #endif
    
    printf("Call Function [Add Item]: handle = %p, buffer = %p, size = %zu\n", a1, a2, a3);

    return PROXY_FUNCTION("add_item");
}

// create_backpack
extern "C" UINT64 create_backpackFwd(PARAMS) {
    #ifdef DEBUG
        dbgf("create_backpack called");
    #endif
    
    printf("Call Function [Create Backpack]: max size = %zu\n", a1);

    return PROXY_FUNCTION("create_backpack");
}

// destroy_backpack
extern "C" UINT64 destroy_backpackFwd(PARAMS) {
    #ifdef DEBUG
        dbgf("destroy_backpack called");
    #endif
    
    printf("Call Function [Destroy Backpack]: handle = %p\n", a1);

    return PROXY_FUNCTION("destroy_backpack");
}

// get_error_string
extern "C" UINT64 get_error_stringFwd(PARAMS) {
    #ifdef DEBUG
        dbgf("get_error_string called");
    #endif
    
    printf("Call Function [Get Error String]: code = %zu\n", a1);

    return PROXY_FUNCTION("get_error_string");
}

// get_item
extern "C" UINT64 get_itemFwd(PARAMS) {
    #ifdef DEBUG
        dbgf("get_item called");
    #endif
    
    printf("Call Function [Get Item]: handle = %p, index = %zu, buffer = %p\n", a1, a2, a3);

    return PROXY_FUNCTION("get_item");
}

// get_item_size
extern "C" UINT64 get_item_sizeFwd(PARAMS) {
    #ifdef DEBUG
        dbgf("get_item_size called");
    #endif
    
    printf("Call Function [Get Item Size]: handle = %p, index = %zu, size buffer = %p\n", a1, a2, a3);

    return PROXY_FUNCTION("get_item_size");
}

// get_size
extern "C" UINT64 get_sizeFwd(PARAMS) {
    #ifdef DEBUG
        dbgf("get_size called");
    #endif
    
    printf("Call Function [Get Size]: handle = %p, size buffer = %p\n", a1, a2);

    return PROXY_FUNCTION("get_size");
}

// remove_last_item
extern "C" UINT64 remove_last_itemFwd(PARAMS) {
    #ifdef DEBUG
        dbgf("remove_last_item called");
    #endif
    
    printf("Call Function [Remove Last Item]: handle = %p\n", a1);

    return PROXY_FUNCTION("remove_last_item");
}


BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) { 

    switch (fdwReason) {
    case DLL_PROCESS_ATTACH: {
        #ifdef DEBUG
            dbgf("DLL_PROCESS_ATTACH");
        #endif
    }
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }

    return TRUE;
}

最後可以透過 compile.sh 來建立新的 DLL,我是使用 Windows 測試的,DllShimmer 使用的 compiler 是 x86_64-w64-mingw32-g++,所以要記得安裝 MinGW ~

source compile.sh

完成後會看到以下訊息,並多了一個 libmylib.dll 的檔案 ~

$ source compile.sh
[+] Proxy DLL compiled successfully.

全部檔案:

.
├── dll/
│   ├── compile.sh
│   ├── dllshimmer.h
│   ├── libmylib.dll.cpp
│   ├── libmylib.dll.def
│   └── libmylib.dll
├── DllShimmer.exe
└── libmylib.dll

製作好了之後,當然就是要實際來用用看啦 ~

這裡繼續沿用之前的測試腳本:

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

#include "mylib.h"

#define CHECK(call)                                                                             \
    do                                                                                          \
    {                                                                                           \
        MyLibErrorCode err = (call);                                                            \
        if (err != MYLIB_SUCCESS)                                                               \
        {                                                                                       \
            fprintf(stderr, "Error at %s:%d: %s\n", __FILE__, __LINE__, get_error_string(err)); \
            exit(EXIT_FAILURE);                                                                 \
        }                                                                                       \
    } while (0)

int main()
{
    BackpackHandle *handle = create_backpack(1000); // 1000 bytes
    if (handle == NULL)
    {
        fprintf(stderr, "Create Failed\n");
        exit(EXIT_FAILURE);
    }

    int a = 10;
    long long b = 100000000000LL;
    char c = 'c';

    printf("Item 0: %d\n", a);
    printf("Item 1: %lld\n", b);
    printf("Item 2: %c\n", c);

    CHECK(add_item(handle, &a, sizeof(a)));
    CHECK(add_item(handle, &b, sizeof(b)));
    CHECK(add_item(handle, &c, sizeof(c)));

    for (int i = 0; i < 3; ++i)
    {
        size_t s;
        CHECK(get_item_size(handle, i, &s));
        printf("Item %d size: %zu\n", i, s);
    }

    int a_get;
    long long b_get;
    char c_get;

    CHECK(get_item(handle, 0, &a_get));
    CHECK(get_item(handle, 1, &b_get));
    CHECK(get_item(handle, 2, &c_get));

    printf("[Get] Item 0: %d\n", a_get);
    printf("[Get] Item 1: %lld\n", b_get);
    printf("[Get] Item 2: %c\n", c_get);

    for (int i = 0; i < 3; ++i)
    {
        size_t s;
        CHECK(get_size(handle, &s));
        printf("Total size: %zu\n", s);
        remove_last_item(handle);
    }

    destroy_backpack(handle);
}

在編譯之前,我們要記得把 DLL 的名稱稍微修改一下:

  • 原本的 DLL (藍色方框):改名為 libmylib_orig.dll,因為前面我們有設定之後程式是透過這個名稱來找原本的 DLL 的。
  • 新的 DLL (綠色方框):改名為 libmylib.dll,因為要充當原本的 DLL,所以名稱要與原本的一樣。
.
├── dll/
│   ├── compile.sh
│   ├── dllshimmer.h
│   ├── libmylib.dll.cpp
│   ├── libmylib.dll.def
│   └── libmylib.dll
├── text/
│   ├── test.c
│   ├── mylib.h
│   ├── libmylib_orig.dll  # 原本的 DLL
│   └── libmylib.dll  # 新的 DLL
├── DllShimmer.exe
└── libmylib.dll

編譯的時候記得 link 我們新產生出的 DLL,Shared Library 的 Header 我們就沿用舊的就可以了,等於只替換掉 DLL 的部分。

x86_64-w64-mingw32-g++ test.c -o test.exe -l mylib

最後執行起來,可以看到有執行我們插入的 Code,有 Call Function 的參數述職的輸出 ~

那到這邊就大功告成啦 ~

Call Function [Create Backpack]: max size = 1000
Item 0: 10
Item 1: 100000000000
Item 2: c
Call Function [Add Item]: handle = 000001fb19651fd0, buffer = 0000005a065ff84c, size = 4
Call Function [Add Item]: handle = 000001fb19651fd0, buffer = 0000005a065ff840, size = 8
Call Function [Add Item]: handle = 000001fb19651fd0, buffer = 0000005a065ff83f, size = 1
Call Function [Get Item Size]: handle = 000001fb19651fd0, index = 0, size buffer = 0000005a065ff820
Item 0 size: 4
Call Function [Get Item Size]: handle = 000001fb19651fd0, index = 1, size buffer = 0000005a065ff820
Item 1 size: 8
Call Function [Get Item Size]: handle = 000001fb19651fd0, index = 2, size buffer = 0000005a065ff820
Item 2 size: 1
Call Function [Get Item]: handle = 000001fb19651fd0, index = 0, buffer = 0000005a065ff838
Call Function [Get Item]: handle = 000001fb19651fd0, index = 1, buffer = 0000005a065ff830
Call Function [Get Item]: handle = 000001fb19651fd0, index = 2, buffer = 0000005a065ff82f
[Get] Item 0: 10
[Get] Item 1: 100000000000
[Get] Item 2: c
Call Function [Get Size]: handle = 000001fb19651fd0, size buffer = 0000005a065ff818
Total size: 3
Call Function [Remove Last Item]: handle = 000001fb19651fd0
Call Function [Get Size]: handle = 000001fb19651fd0, size buffer = 0000005a065ff818
Total size: 2
Call Function [Remove Last Item]: handle = 000001fb19651fd0
Call Function [Get Size]: handle = 000001fb19651fd0, size buffer = 0000005a065ff818
Total size: 1
Call Function [Remove Last Item]: handle = 000001fb19651fd0
Call Function [Destroy Backpack]: handle = 000001fb19651fd0

雖然這邊只有示範用 Dynamic Link 的方式,但其實作者有提供 Static Link 的方式,所以有興趣的都可以去試試看。另外目前還只有支援整數的參數傳遞,如果是浮點數的話可能會有一些問題,參數數量上限是 12 個,如果想要更詳細的可以參考作者的 Github。



廣告 AD