本文章將講解如何使用惡意的 Golang 來實現 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_NAME=要轉發的dll文件名.FUNCTION_NAME")
列出導出函數
下面我們采用 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
感興趣的可以查看。
參考資料
- [1] PE知識復習之PE的導出表
- [2] DLL Proxying
- [3] /EXPORT (Exports a Function)
- [4] Windows Privilege Escalation - DLL Proxying
- [5] DLL Hijacking using DLL Proxying technique
- [6] DLL之def和exp文件作用
- [7] mingw環境中使用dlltool工具來生成動態庫的步驟
- [8] Specifying the DEF file when compiling a DLL with Clang
- [9] issues - cmd/link: support msvc object files
- [10] gcc Options for Linking
- [11] RUSTGO: CALLING RUST FROM GO WITH NEAR-ZERO OVERHEAD
- [12] Go Execution Modes
- [13] go tool link
- [14] DllMain entry point
- [15] DllMain簡介和DLL編寫說明
- [16] Call Go function from C function
- [17] github.com/NaniteFactory/dllmain
- [18] How to implement DllMain entry point in Go