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

路過剛好看到這個和 DLL 有關的 repository
就順手玩看看做個紀錄
DllShimmer
DllShimmer 可以透過 DLL 的內部資料,產生一個 CPP 的樣板檔案,這樣我們可以在這個樣板檔案上插入任何我們想要執行的程式碼,這在觀察 DLL 行為和 Debug 上都蠻有實用價值的。
Prerequisite
要插入 function 到 DLL 前,我們必須先準備一個 DLL 來當作目標
這裡使用之前在介紹製作 DLL 檔案時的範例:
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 之間插入額外的程式碼
Build
原始的程式會 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 裡面的資訊來建立 codeoutput_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
Execute
製作好了之後,當然就是要實際來用用看啦 ~
這裡繼續沿用之前的測試腳本:
#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
Conclusion
雖然這邊只有示範用 Dynamic Link 的方式,但其實作者有提供 Static Link 的方式,所以有興趣的都可以去試試看。另外目前還只有支援整數的參數傳遞,如果是浮點數的話可能會有一些問題,參數數量上限是 12 個,如果想要更詳細的可以參考作者的 Github。
Reference
如果你覺得這篇文章有用 可以考慮贊助飲料給大貓咪