釘釘邀請上臺功能分析
因為釘釘課堂不上臺成員與上臺成員延遲差距太大,而當人數過多時挨個邀請太費時間,又有部分成員有網絡波動等原因導致需要反復邀請。所以為了解放老師上課的雙手,這個產品就誕生了出來。
一、分析邀請call
1、首先分析釘釘的一鍵邀請是如何發送到服務器的。
2、首先x32dbg附加tblive.exe進程對所有發包函數進行下斷,sendto,send,WSASend,WSASendTo。
3、經過反復測試,點擊邀請按鈕沒有任何一個函數被斷下。
4、而我們也知道tblive進程是由DingTalk主進程創建出來的,那么他們可能存在管道通訊由主進程進行發包。
5、所以附加DingTalk進程,并重復上述操作,在WSASend函數斷了下來。

6、那么回溯到函數外層查看傳參的數據。

7、休息時思考了一下為什么tblive進程中對發包函數下斷無法斷下邀請上臺功能,而在釘釘主進程卻可以。那么就驗證了我們上面分析的子進程和父進程直接是含有通訊,假設教師創建課堂的時候課堂會保存一個類這個類包含了課堂id,臺下學生鏈表等必要信息。當一個學員點開視頻則加入臺下學生鏈表中,當教師點擊臺下學生邀請上臺時。那么tblive就會組裝消息通過子父進程通訊給釘釘主進程, 由主進程和服務器進行通訊完成該功能。那么子父進程的通訊常見的也就幾種,通過測試可以發現釘釘采用的管道通訊。
8、分析釘釘的子父進程通訊,首先下斷WriteFile的時候發現斷點一直來,看來一眼線程模塊,發現有很多線程,那么我作為開發角度一定會把io相關操作使用多線程避免阻塞主線程從而影響上課的網速。所以我這里首先暫停所有線程保留主線程,同時在WriteFile函數下斷,點擊邀請按鈕查看WriteFile函數是否斷下成功,結果也是顯而易見的,并不能斷下來。后續通過折半查找法,定位到了釘釘的消息隊列處理線程。

9、只保留主線程和消息隊列線程,再次給WriteFile函數下斷并且點擊邀請按鈕,可以看到相關的重要信息。

10、通過這個信息,我們可以很確定的判斷出我們的假想成立。那么現在需要的工作就是通過回溯找到關鍵call。
11、從該信息我們可以得知,需要完成這個功能至少需要classroom_id和uids。通過棧回溯,發現了一處代碼使用了HeapFree函數,于是在HeapFree上下斷再次點擊邀請按鈕。

12、再次將棧窗口滾動查看,于是我們看到了uids和classroom_id分開存放的位置,直覺告訴我距離關鍵call已經很近了。

13、我們向上回溯后很清晰的看到了他所傳入的兩個call,一個為uids一個為classroom_id。

14、通過測試,我們發現這個call是可以用來邀請人員的,而他的id是一個結構體,包含了uids的長度,否則在組包的時候會被截斷。通過函數的頭和尾部,不難看出這是一個類對象的成員函數,而他的外部call只是給ecx進行了一次賦值,沒有做多余的操作,而這個ecx恰巧是被邀請人的對象指針。通過替換ecx指針為不同人的話也可以辦到邀請不同的人。他要比內部call更為簡單,所以我們準備采用這個call作為邀請call。

二、分析創建成員指針
1、那么距離自動邀請,還差一個成員指針從何而來。通過分析釘釘的行為,我們得知一個信息就是,每當臺下人員有所變動后所有的成員指針都會被釋放掉并且重新new一片空間。所以只要我們找到了哪里對他們進行生成的函數,我們就可以通過hook得到最新的成員指針地址,所以對tblive的malloc函數下斷。
2、而malloc函數一直來斷點很影響我們的判斷,而我看到了malloc的eax他有模塊地址的信息,而我們也確切直到成員的函數指針在模塊classroom.dll下面,所以對齊斷點設置條件斷點當eax的值在classroom.dll的范圍內的時候再斷下來。

3、我們現在將臺下成員進行變動一下,malloc被斷下來了,通過觀察堆棧,看看是否有相關的敏感信息,F9了幾下我們就看到了棧中的classroom_id和uids了。所以這個時候取消malloc的斷點開始向上回溯,停到疑似創建成員指針的位置后下斷重新變動臺下成員,經過驗證這個call則為創建成員指針。

4、那么已知條件已經具備,附上我的代碼。通過劫持注入的方式可以當釘釘創建tblive的時候我的dll也就被加載了,同時也將附帶上自動邀請的功能,可以讓老師不用頻繁的拉人上臺了。注意劫持的dll是air2.dll,將原dll名稱改為air2Org.dll即可。
三、源代碼
#include "pch.h" #include <Windows.h>#pragma comment(linker,"/EXPORT:?air_roi_create@@YAHPAPAXABV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@Z=air2Org.?air_roi_create@@YAHPAPAXABV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@Z")#pragma comment(linker,"/EXPORT:?air_roi_destroy@@YAHPAX@Z=air2Org.?air_roi_destroy@@YAHPAX@Z")#pragma comment(linker,"/EXPORT:?air_roi_detect@@YAHPAXPBEHHHW4air_pixel_format@@PAEHH@Z=air2Org.?air_roi_detect@@YAHPAXPBEHHHW4air_pixel_format@@PAEHH@Z")#pragma comment(linker,"/EXPORT:air_dl_segment_create=air2Org.air_dl_segment_create")#pragma comment(linker,"/EXPORT:air_dl_segment_destroy=air2Org.air_dl_segment_destroy")#pragma comment(linker,"/EXPORT:air_dl_segment_detect=air2Org.air_dl_segment_detect")#pragma comment(linker,"/EXPORT:air_dl_segment_get_insize=air2Org.air_dl_segment_get_insize") // 創建成員指針偏移const DWORD g_dwCreateMemberOffset = 0x1E1523;const DWORD g_dwOrginCallOffset = 0x36BB;// 邀請call偏移const DWORD g_dwInviteCallOffset = 0x1974c0; #define HOOK_LEN 5VOID SetHook(DWORD_PTR dwHookAddr, LPVOID dwPfnAddr, BYTE* btBackCode){ // 準備HOOK BYTE btJmp[HOOK_LEN] = { 0xE9 ,0,0,0,0 }; *(DWORD*)&btJmp[1] = (DWORD)dwPfnAddr - dwHookAddr - HOOK_LEN; // 獲取自身進程句柄 HANDLE hProcess = GetCurrentProcess(); // 備份數據 if (!ReadProcessMemory(hProcess, (LPVOID)dwHookAddr, btBackCode, HOOK_LEN, NULL)) { MessageBox(NULL, "HOOK失敗", NULL, NULL); return; } // 開始hook if (!WriteProcessMemory(hProcess, (LPVOID)dwHookAddr, btJmp, HOOK_LEN, NULL)) { MessageBox(NULL, "HOOK失敗", NULL, NULL); return; }} VOID UnSetHook(DWORD_PTR dwHookAddr, BYTE* btBackCode){ // 獲取自身進程句柄 HANDLE hProcess = GetCurrentProcess(); // 卸載hook if (!WriteProcessMemory(hProcess, (LPVOID)dwHookAddr, btBackCode, HOOK_LEN, NULL)) { MessageBox(NULL, "卸載HOOK失敗", NULL, NULL); return; }} // 邀請原數據DWORD g_dwCreateMemberOrgCallAddr = NULL;DWORD g_dwCreateMemberAddr = NULL;BYTE g_btCreateMemberCode[5]{};DWORD g_dwCreateMemberCallback = NULL;DWORD g_dwInvateCallAddr = NULL;DWORD g_dwMemberPoint = NULL;void AutoInviteMember(){ __asm { mov ecx, g_dwMemberPoint call g_dwInvateCallAddr } return;} _declspec(naked) void HookCreateMemberFunc(){ __asm { pushad pushf mov eax, dword ptr ss:[ebp - 0x48] mov [g_dwMemberPoint], eax } AutoInviteMember(); __asm { popf popad call g_dwCreateMemberOrgCallAddr jmp g_dwCreateMemberCallback }} // 入口函數BOOL WINAPI DllMain(HMODULE hModule, DWORD dwReason, PVOID pvReserved){ if (dwReason == DLL_PROCESS_ATTACH) { // 初始化數據 DWORD dwClassroomHandle = (DWORD)GetModuleHandle("classroom.dll"); g_dwInvateCallAddr = dwClassroomHandle + g_dwInviteCallOffset; g_dwCreateMemberAddr = dwClassroomHandle + g_dwCreateMemberOffset; g_dwCreateMemberCallback = g_dwCreateMemberAddr + HOOK_LEN; g_dwCreateMemberOrgCallAddr = dwClassroomHandle + g_dwOrginCallOffset; // HOOK加入成員生成指針部分 SetHook(g_dwCreateMemberAddr, HookCreateMemberFunc, g_btCreateMemberCode); } else if (dwReason == DLL_PROCESS_DETACH) { UnSetHook(g_dwCreateMemberAddr, g_btCreateMemberCode); } return TRUE;}