Skype逆向之旅
from:
- Skype逆向之旅: 創世紀 http://www.oklabs.net/skype-reverse-engineering-genesis/
- Skype逆向之旅:分析篇 http://www.oklabs.net/skype-reverse-engineering-the-long-journey/
0x00 背景
我想...在顯示分析之前先交代一下背景資訊。
But,我得先為我糟糕的英語先道個歉。
在2k6的時候,我在一個法國公司主持一些逆向工程專案,建立一套轉有的技術體系。但是這些技術當中最好的/最大的收穫便是逆向Skype協議獲得的經驗。
一年後,由於該公司的戰略調整該專案被中止。我離開我的工作,我希望有一天能夠繼續完成它。雖然在幾年後,都沒有怎麼接觸過該專案..
我決定今天釋出該項工作,即使它還比較原始。但是它可能會幫助任何其它想學習該協議或滿足其好奇心的人。我覺得這比在出租屋的房子裡睡上一年覺還美好。
在我離開的時候,逆向工程完成了:能夠連線到Skype的分散式網路,認證使用者,獲取聯絡人列表,管理使用者的存在和接收(響應)文字聊天。
在這裡我分享我在該專案中遇到的一些困難,一些工作筆記,逆向過程中的一些工具(包括我自己開發的),併釋出一個概念型客戶端(名為FakeSkype)和它的事件驅動(已停止開發,名為Epyks)。
我說過,這個專案開始於2k6年,因此,使用的是Skype V2.5客戶端協議。
這個專案使用的是Windows XP 32位系統下。
下面是我收集的一些比較好的論文。
- Silver Needle In The Skype : Philippe Biondi & Fabrice Desclaux
- Castle In The Skype : Fabrice Desclaux
- Vanilla Skype Part I & Vanilla Skype Part II : Fabrice Desclaux Kostya Kortchinsky
- Skype, Guide for Network Administrators : Skype Inc.
- Malicious Crypto : Frederic Raynal
首先,我們應該知道從網路流量逆向工程Skype協議在一些國家是合法的,而檢查/反彙編二進位制檔案在Skype的許可協議條款中是禁止的。但是針對檔案格式和協議的互操作性有合法的先例。此外,一些國家特別許可那些透過逆向工程而重寫的再工程程式(去除複製保護)。
0x01 簡要
首先,Skype的網路和現有的網路完全不同,其主要區別在於它的架構上。Skype客戶端是基於peer-to-peer網路模型而不是基於經典的client-server結構的VoIP客戶端。這就意味著每個Spype客戶端涉及到使用者間通訊不涉及到集中式的架構。每個Skype客戶端同時作為服務端和客戶端使用。在一個P2P網路當中,每個客戶端被設計成一個“節點”。
Server Based Network
P2P Network
Skype有其特定的P2P架構,因為它必須適用所使用的網路情況。舉個例子,與Kazaa被設計成檔案共享的P2P網路不同,Skype網路被最佳化到可以傳輸實時資料。Skype協議實現了使用者安全認證,動態聯絡人列表管理和確保隱私。
Skype的使用者目錄完全分佈在網路中節點之間。這意味著該網路可以很容易得到擴容,且不需要一個複雜昂貴的集中式架構。
Skype還透過其它Skype節點路由通訊網路以減緩NAT和防火牆穿越的壓力。然而,這會對那些直接連到Internet的使用者造成額外的負擔。因為他們的計算機和網路頻寬還用於路由其他使用者的通訊。
0x02 逆向工程
正如前面所說,Skype客戶端實現的Skype通訊協議帶有多重的保護,以防止逆向工程。為了完全研究該協議,我的第一個目標就是清除二進位制檔案上的每個保護,讓我可以從內部觀察該協議的工作流程。有許多研究人員試圖研究/逆向工程Skype協議而開發第三方客戶端,但由於沒能夠進一步充分地解剖這個協議,導致現在還沒有成功過的案例。這裡有一個Skype協議分析的知識庫:http://www1.cs.columbia.edu/~salman/skype/。
最完整的論文來自EADS-CCR實驗室的兩個研究人員,分別是Phillipe Biondi和Fabrice Desclaux。他們公開的研究成果:“Silver Needle in the Skype”帶來了繞過Skype主要保護的曙光,同時也透露著該項工作的複雜性。在此基礎上我找到了一種方法來繞過這些保護措施。
作為一個閉源程式,分析之前需要把二進位制檔案翻譯成x86會變程式碼。在此我使用的是眾所周知的“OllyDbg(v1.10)”與“Phant0m”外掛。這個工具可以載入客戶端程式並觀察它的彙編程式碼,還可以透過設定斷點單步執行彙編指令。我還使用另一個出名的工具“IDA(v5.00)”。協議分析方面我主要用“OSpy(v1.9.0.0)”。這個工具允許我在二進位制檔案執行過程中設定指定的hook,以便收集傳送到網路的重要資料。
以上就是分析時所需要的工具,總體上,我遇到了以下幾種保護:
二進位制檔案的敏感程式碼使用一個硬編碼的金鑰進行加密以避免程式碼被靜態分析。Skype客戶端(SC)開始處有一個程式負責執行過程中解密敏感程式碼和加密部分程式碼。之後,解密器處理剛加密的程式碼,並擦除另一部分程式碼。後者的作用是為了防止解密的程式碼儲存在計算機記憶體中。我當時設計了一個工具,用於從計算機儲存器中完整提取出SC程式在執行過程中解密出來的程式碼,一旦解密程式解密完成,便把解密程式碼覆蓋到儲存在硬碟上的SC二進位制檔案中原有的加密程式碼。並防止解密程式在解密完成後的進行程式碼擦除。該程式還會修改匯入表,該表包含了程式所使用的每一個匯入函式。我設計的那個工具也設法把匯入表的修改操作同步到磁碟上的二進位制檔案上。
SC Binary Behavior
Main lines of the tool to restore encrypted & scrambled code (dirty) /* Skype Sucker */ /* Author : Ouanilo MEDEGAN */ STARTUPINFO si; PROCESS_INFORMATION pi; HANDLE hProcess = 0; char *Buffer; char *BigBuffer; long Size; FILE *result; result = fopen("Skype.exe", "rb"); fseek(result, 0, SEEK_END); Size = ftell(result); fseek(result, 0, SEEK_SET); BigBuffer = malloc(Size); fread(BigBuffer, Size, 1, result); fclose(result); Buffer = malloc(0x49F000); ZeroMemory(Buffer, 0x49F000); ZeroMemory(&si, sizeof(si)); si.cb = sizeof(si); hProcess = OpenProcess(PROCESS_VM_READ,FALSE, 0xF0C); ReadProcessMemory(hProcess, (LPVOID)0x01FB0000, Buffer, 0x49F000,NULL); result = fopen("Skype.exe", "wb"); memcpy(BigBuffer + 3429792, Buffer, 0x49F000); fwrite(BigBuffer, Size, 1, result); fclose(result); CloseHandle(hProcess); system("PAUSE"); return 0;
SC程式也執行一些程式以在執行時探測偵錯程式,並在探測到偵錯程式的時候停止執行程式。我沒有使用隱藏偵錯程式的補丁,因為OllyDbg(奇怪)是不在SC的偵錯程式黑名單中。事實上,它只是停止一個偵錯程式,就是老且臭名昭著的“SoftICE”,並且使用各種加密字串技巧進行檢測。
OllyDbg View
現在來看一下SC最有效的保護機制。實際上程式碼裡包含了約300處多型完整性代理(polymorphic integrity agents)。它們的功能是:隨機執行,並負責檢查並校驗各部分程式碼的完整性,防止在敏感程式碼上打補丁,甚至可以防止在程式碼執行過程中設定斷點,就如在偵錯程式中插入的軟體斷點時,在計算機記憶體中改變其程式執行時的映象程式碼。繞過這些代理的主要困難是,它們都來互不相同,如大小,檢查方式和檢查標準(例如,檢查的結果可以用來確定程式碼的下一個執行路徑,或者是另一個代理的地址)。然而,經過深入的分析,發現它們存在相同的設計思想,它們分別是:
- 初始化地址
- 迴圈
- 查詢
- 測試/計算
Skype Checksum agents generic patterns
MOV R32, CONST ;Pattern #1 ANY 8 MOV R32, R32 ANY 8 MOV R32, [R32+CONST] JMP OFFSET XOR R32, CONST ;Pattern #2 ANY 8 ANY 8 ANY 8 MOV R32, [R32+CONST] JMP OFFSET XOR R32, R32 ;Pattern #3 ANY 8 MOV R32, [R32+CONST] ANY 8 DEC R32 JNZ OFFSET XOR R32, R32 ;Pattern #4 ANY 8 MOV R32, CONST ANY 8 DEC R32 JNZ OFFSET
意識到這一點,我想我能夠使用OllyDbg透過一定的模式找出總體上幾乎所有代理。一旦得到它們所有特徵點,便使用一個調教好的x86模擬器(名為“x86emu”的IDA外掛)以等待每個特徵點代理的執行結果。然後,我還設計了一個OD外掛(使用外掛開發工具包)給SC二進位制的代理打上完整的補丁,讓它們在最終測試之前直接設定好預期的值。我想我已經能夠修復二進位制程式碼,而且可以在程式碼的每個部分上設定斷點,並且還獲得一個不需要執行數百個控制點的更快的SC二進位制檔案。
Checksum Agent View Exemple#1
Checksum Agent View Exemple#2
Tuned x86 emulator window after execution on spotted agents
Skype Checksum agents Patcher Plugin
/* Skype Checksum Agents Patcher Plugin */ /* Author : Ouanilo MEDEGAN */ // VERY IMPORTANT NOTICE: COMPILE THIS DLL WITH BYTE ALIGNMENT OF STRUCTURES // AND UNSIGNED CHAR! #include "plugin.h" HINSTANCE hinst; // DLL instance HWND hwmain; // Handle of main OllyDbg window typedef struct patchs_s { unsigned int wtp; unsigned int end; char *code; } patchs; int checks[] = { 0x74ffdb, 0x754220, 0x7566d5, 0x758331, 0x75915e, 0x7602df, 0x760477, 0x7706ed, 0x770bf9, 0x770ff3, 0x771fa5, 0x7729b4, 0x772a5f, 0x77ad1c, 0x77baef, 0x77d053, 0x77ec72, 0x780dcd, 0x781652, 0x782810, 0x783412, 0x783d58, 0x78566c, 0x785a82, 0x786142, 0x7881b7, 0x788302, 0x7907f5, 0x7a2b6e, 0x7a2f1d, 0x7a34d5, 0x7a36ca, 0x7a8cfc, 0x7a9784, 0x7aa229, 0x7ac1b6, 0x7aede1, 0x7af68d, 0x7b22ee, 0x7b319e, 0x7b6d55, 0x7b6f55, 0x7b7711, 0x7b7b19, 0x7b8dda, 0x7b93c0, 0x7bc108, 0x7bdeef, 0x7be304, 0x7bee1d, 0x7befaf, 0x7bf322, 0x7c2b0e, 0x7c427d, 0x7c4c73, 0x7c5d71, 0x7c71f0, 0x7c7f23, 0x7c8e97, 0x7cbc3d, 0x7d75ca, 0x7d8388, 0x7da2ce, 0x7db581, 0x7dcac3, 0x7deed3, 0x7e502c, 0x7e5e8d, 0x7e696c, 0x7e866b, 0x7eb1a9, 0x7eb76d, 0x7ed530, 0x7f5a45, 0x7f9b84, 0x7f9e99, 0x7fa29e, 0x7fd27d, 0x7fd846, 0x7fe497, 0x7fec0a, 0x7ff609, 0x7ffdf1, 0x7ffef1, 0x800128, 0x800deb, 0x8014d3, 0x802276, 0x802893, 0x803709, 0x804925, 0x804eef, 0x805005, 0x8058fa, 0x8066e4, 0x806c60, 0x806d26, 0x806dd4, 0x806ec4, 0x807702, 0x808bab, 0x809287, 0x809aee, 0x809d70, 0x80af22, 0x80bc37, 0x80bf48, 0x80c627, 0x80c854, 0x80c994, 0x80cad7, 0x80cc87, 0x80d0c7, 0x80d1c7, 0x80d3f9, 0x812457, 0x813316, 0x813c0f, 0x814547, 0x815a1b, 0x846e01, 0x8497cb, 0x849cb2, 0x84a148, 0x84d824, 0x84da14, 0x84ef26, 0x854042, 0x85489a, 0x8552bf, 0x861ac8, 0x861f18, 0x86329a, 0x863c8a, 0x864185, 0x8670c7, 0x867d00, 0x868400, 0x868919, 0x869075, 0x869e2f, 0x86a514, 0x86d1de, 0x8779db, 0x877a5e, 0x877f2b, 0x87831f, 0x87b74f, 0x88426a, 0x89131c, 0x892293, 0x892500, 0x89453b, 0x8986f3, 0x898828, 0x899162, 0x89b328, 0x89e972, 0x8a3455, 0x8a62c7, 0x8a71b6, 0x8a78cd, 0x8a8627, 0x8ac0a0, 0x8acdba, 0x8b1383, 0x8b1dcc, 0x8b42a7, 0x8b48fb, 0x8b5c31, 0x8ba8eb, 0x8bab10, 0x8bbfa7, 0x8be61a, 0x8c3ff0, 0x8c90b4, 0x8cb8dc, 0x8cbaa2, 0x8cc105, 0x8ce0ef, 0x8d01df, 0x8d0a41, 0x8d1a85, 0x8d26c7, 0x8d4731, 0x8d4efc, 0x8d5707, 0x8dc0d1, 0x8dcb50, 0x8e0dd6, 0x8e14c4, 0x8e227d, 0x8e2564, 0x8e3722, 0x8e4ede, 0x8e4fb7, 0x8e6b3f, 0x8e77b7, 0x8e8a34, 0x8e9935, 0x8ea9ba, 0x8ebfc5, 0x8ee0c2, 0x8ee2b4, 0x8ee718, 0x8ef236, 0x8ef86e, 0x8efd29, 0x8f01b6, 0x8f0b49, 0x8f11a1, 0x905450, 0x924cde, 0x9261D5, 0x928aec, 0x928e78, 0x93ae8e, 0x93d1e0, 0x93f37e, 0x940266, 0x940577, 0x940a3c, 0x940ba3, 0x946a4d, 0x9484b4, 0x0 }; patchs ptab[] = { { 0x74fff9, 0x750009, "MOV ECX, 0c445513e" }, { 0x75423e, 0x754251, "MOV EDI, 07a51760" }, { 0x7566ed, 0x7566fd, "MOV ECX, 07f26c1cd" }, { 0x75834e, 0x75835d, "MOV EDX, 08143833c" }, { 0x75917b, 0x75918a, "MOV EDI, 0ac10c50d" }, { 0x7602fd, 0x76030d, "MOV EDI, 07b43df78" }, { 0x760491, 0x7604a2, "MOV EAX, 013c45465" }, { 0x770707, 0x770717, "MOV EDX, 02d86e921" }, { 0x770c19, 0x770c2c, "MOV ECX, 023d836c7" }, { 0x77100d, 0x77101f, "MOV ESI, 0b7d7ee34" }, { 0x771fc5, 0x771fd6, "MOV EAX, 08ffdfc46" }, { 0x7729d4, 0x7729e7, "MOV EAX, 0aa2552d6" }, { 0x772a7d, 0x772a8d, "MOV EDX, 0dd02dff5" }, { 0x77ad3a, 0x77ad4b, "MOV EBX, 01f1e9f7b" }, { 0x77bb0c, 0x77bb1b, "MOV EAX, 056ea89cf" }, { 0x77d073, 0x77d07f, "MOV ESI, 07d4e5bf" }, { 0x77ec8d, 0x77eca0, "MOV ECX, 09c78fcfe" }, { 0x780de7, 0x780df7, "MOV EDI, 02ca58b26" }, { 0x78166f, 0x781682, "MOV EAX, 02d18848f" }, { 0x78282b, 0x78283e, "MOV ECX, 0d2d3a1f5" }, { 0x78342d, 0x78343d, "MOV EDI, 0aa0d1086" }, { 0x783d76, 0x783d86, "MOV EDI, 054d99c86" }, { 0x785687, 0x785696, "MOV EDI, 09c9fcf4b" }, { 0x785aa0, 0x785ab3, "MOV ECX, 03a77aa74" }, { 0x786162, 0x786173, "MOV EDI, 024367f84" }, { 0x7881d4, 0x7881e5, "MOV EAX, 0764de354" }, { 0x78831d, 0x78832f, "MOV EBX, 0c0aeb096" }, { 0x790812, 0x790822, "MOV EBX, 0e7393655" }, { 0x7a2b8c, 0x7a2b9c, "MOV ESI, 02cbbbb5a" }, { 0x7a2f41, 0x7a2f4e, "MOV EBX, 0995c72e3" }, { 0x7a34f5, 0x7a3505, "MOV EAX, 01fe3bec1" }, { 0x7a36e2, 0x7a36f5, "MOV EDI, 0d4027302" }, { 0x7a8d19, 0x7a8d29, "MOV EBX, 0518f95ba" }, { 0x7a97a8, 0x7a97b8, "MOV EDI, 0c0bad4af" }, { 0x7aa24a, 0x7aa25a, "MOV EDX, 086760316" }, { 0x7ac1ce, 0x7ac1dc, "MOV EDI, 0e6503aaa" }, { 0x7aedfb, 0x7aee0e, "MOV EAX, 02091582b" }, { 0x7af6aa, 0x7af6b9, "MOV ESI, 0aab731b2" }, { 0x7b2309, 0x7b2319, "MOV ECX, 0336b4c77" }, { 0x7b31c1, 0x7b31d0, "MOV EAX, 0a9dc7b7e" }, { 0x7b6d75, 0x7b6d85, "MOV EAX, 033156dcb" }, { 0x7b6f6f, 0x7b6f7f, "MOV EDI, 068c30e6" }, { 0x7b7731, 0x7b7744, "MOV EAX, 03a739a6c" }, { 0x7b7b36, 0x7b7b49, "MOV EBX, 0dd5eb0ac" }, { 0x7b8df4, 0x7b8e06, "MOV ECX, 0cda6236b" }, { 0x7b93e1, 0x7b93f1, "MOV EDX, 0909f211e" }, { 0x7bc122, 0x7bc12f, "MOV EDX, 0423c1dd2" }, { 0x7bdf0a, 0x7bdf1a, "MOV EDX, 0dd8ff70f" }, { 0x7be324, 0x7be334, "MOV EAX, 09cfc6204" }, { 0x7bee37, 0x7bee47, "MOV EAX, 08033cb72" }, { 0x7befd0, 0x7befe0, "MOV EBX, 0dd57229d" }, { 0x7bf33d, 0x7bf34b, "MOV EDI, 0303d25eb" }, { 0x7c2b2b, 0x7c2b3a, "MOV ESI, 0e5ea18bb" }, { 0x7c4298, 0x7c42a9, "MOV EDI, 0f9cead47" }, { 0x7c4c8e, 0x7c4c9f, "MOV ESI, 09ccb01a2" }, { 0x7c5d8b, 0x7c5d9c, "MOV ECX, 09d859997" }, { 0x7c720e, 0x7c721e, "MOV EBX, 0d0a84b9f" }, { 0x7c7f40, 0x7c7f4f, "MOV EDI, 0d3e4f2c7" }, { 0x7c8eb4, 0x7c8ec6, "MOV ECX, 0427b5851" }, { 0x7cbc5a, 0x7cbc6d, "MOV EAX, 02d194eb0" }, { 0x7d75e4, 0x7d75f4, "MOV EBX, 0b024bef9" }, { 0x7d83a8, 0x7d83b9, "MOV EBX, 0547f095b" }, { 0x7da2ef, 0x7da2ff, "MOV ESI, 0e5ce5b73" }, { 0x7db59e, 0x7db5b0, "MOV ECX, 099b9f3a8" }, { 0x7dcae3, 0x7dcaf1, "MOV EAX, 0b77e8d82" }, { 0x7deef0, 0x7def03, "MOV EAX, 0e734dc4e" }, { 0x7e5044, 0x7e5057, "MOV EDX, 07febc729" }, { 0x7e5eaa, 0x7e5eb7, "MOV EDI, 0ac34b755" }, { 0x7e698a, 0x7e699d, "MOV ECX, 030143199" }, { 0x7e8688, 0x7e8699, "MOV EAX, 0149e7219" }, { 0x7eb1c6, 0x7eb1d8, "MOV EAX, 0929f271f" }, { 0x7eb78e, 0x7eb79e, "MOV EDX, 08b216d4a" }, { 0x7ed54b, 0x7ed55e, "MOV ESI, 08017bf81" }, { 0x7f5a62, 0x7f5a71, "MOV EAX, 099f2503f" }, { 0x7f9ba4, 0x7f9bb5, "MOV EAX, 05175280f" }, { 0x7f9eb3, 0x7f9ec3, "MOV EDI, 0a955c470" }, { 0x7fa2bb, 0x7fa2c8, "MOV EAX, 02dfc1afa" }, { 0x7fd298, 0x7fd2a7, "MOV ECX, 01f5d41fc" }, { 0x7fd863, 0x7fd872, "MOV ESI, 0e0812fb9" }, { 0x7fe4b2, 0x7fe4c0, "MOV EDI, 099e325dd" }, { 0x7fec24, 0x7fec35, "MOV EBX, 054b340c3" }, { 0x7ff629, 0x7ff63a, "MOV EDX, 0b45090bf" }, { 0x7ffe0f, 0x7ffe1d, "MOV ECX, 0e5bc5382" }, { 0x7fff12, 0x7fff22, "MOV ECX, 08ac85e98" }, { 0x800146, 0x800157, "MOV ESI, 090201620" }, { 0x800e05, 0x800e17, "MOV ECX, 0e27686f6" }, { 0x8014ee, 0x801500, "MOV ECX, 0d2c7b21d" }, { 0x802290, 0x80229e, "MOV EAX, 0ec8ae7f8" }, { 0x8028b1, 0x8028c3, "MOV ESI, 054b9f847" }, { 0x803729, 0x803737, "MOV EBX, 0cddb39d5" }, { 0x804943, 0x804956, "MOV ESI, 013b8244d" }, { 0x804f0c, 0x804f1c, "MOV EAX, 06c9aae5b" }, { 0x805022, 0x805032, "MOV ESI, 055070ae1" }, { 0x805918, 0x80592b, "MOV EBX, 030124195" }, { 0x806705, 0x806714, "MOV EBX, 0147affd2" }, { 0x806c7e, 0x806c8c, "MOV EDX, 06e536285" }, { 0x806d43, 0x806d53, "MOV ECX, 07a9b5d13" }, { 0x806df1, 0x806e00, "MOV EAX, 054a8a4b4" }, { 0x806ee8, 0x806ef6, "MOV EBX, 02e42c699" }, { 0x80771d, 0x807730, "MOV EBX, 0e638907b" }, { 0x808bce, 0x808be1, "MOV EDX, 0f9e77f79" }, { 0x8092a8, 0x8092b6, "MOV EDX, 06efe8faf" }, { 0x809b0b, 0x809b1b, "MOV EDX, 034f306d1" }, { 0x809d8a, 0x809d9d, "MOV EAX, 0acf4b4d5" }, { 0x80af39, 0x80af46, "MOV EDI, 071094df2" }, { 0x80bc57, 0x80bc68, "MOV EBX, 0b4e5d2d2" }, { 0x80bf65, 0x80bf72, "MOV ESI, 076f6bf82" }, { 0x80c644, 0x80c656, "MOV EDI, 0e3df1200" }, { 0x80c874, 0x80c887, "MOV EAX, 090c5b6fe" }, { 0x80c9af, 0x80c9be, "MOV EBX, 026599582" }, { 0x80caf5, 0x80cb02, "MOV EDX, 030a145e7" }, { 0x80cca8, 0x80ccb8, "MOV ESI, 06bc5d1d1" }, { 0x80d0e8, 0x80d0f8, "MOV ESI, 055c8cecf" }, { 0x80d1e5, 0x80d1f4, "MOV EDI, 0ffb2b709" }, { 0x80d419, 0x80d42b, "MOV ESI, 052956b56" }, { 0x812478, 0x81248a, "MOV ESI, 0c51f8d42" }, { 0x813333, 0x813344, "MOV ECX, 018b4b2ca" }, { 0x813c2f, 0x813c3e, "MOV EDI, 0fb585983" }, { 0x814568, 0x814577, "MOV EBX, 0a393b14" }, { 0x815a39, 0x815a49, "MOV ECX, 01552376d" }, { 0x846e1c, 0x846e29, "MOV EDX, 0895a8f12" }, { 0x8497e9, 0x8497f9, "MOV ESI, 03c8b3592" }, { 0x849ccc, 0x849cdf, "MOV EAX, 03fbb1f8a" }, { 0x84a165, 0x84a172, "MOV EBX, 0e036d6b9" }, { 0x84d842, 0x84d855, "MOV EDX, 021242c95" }, { 0x84da31, 0x84da42, "MOV EAX, 0fedb8372" }, { 0x84ef44, 0x84ef54, "MOV EBX, 0ae164cd1" }, { 0x85405f, 0x85406f, "MOV EBX, 09a52989" }, { 0x8548b4, 0x8548c7, "MOV EAX, 0ac0639e0" }, { 0x8552d9, 0x8552ea, "MOV EAX, 091c4f5b9" }, { 0x861ae8, 0x861afa, "MOV ESI, 093840a1c" }, { 0x861f36, 0x861f45, "MOV ESI, 0419edd7b" }, { 0x8632b1, 0x8632c1, "MOV EBX, 085ab7503" }, { 0x863ca5, 0x863cb8, "MOV EDX, 07dbfb5b" }, { 0x8641a6, 0x8641b6, "MOV ECX, 0e173e1e9" }, { 0x8670e5, 0x8670f8, "MOV ECX, 0e0b9151f" }, { 0x867d1e, 0x867d2e, "MOV EBX, 09d26fa68" }, { 0x86841d, 0x86842a, "MOV ESI, 012ec7977" }, { 0x868939, 0x86894b, "MOV EDX, 068904b7c" }, { 0x869090, 0x86909e, "MOV EDX, 0f1c6291c" }, { 0x869e49, 0x869e5c, "MOV EAX, 0f1d2405f" }, { 0x86a52b, 0x86a53d, "MOV ESI, 06193d267" }, { 0x86d1f8, 0x86d206, "MOV EAX, 0ebf84584" }, { 0x8779f8, 0x877a07, "MOV ESI, 0f70a3528" }, { 0x877a7c, 0x877a8a, "MOV EDX, 0e550009b" }, { 0x877f4e, 0x877f5d, "MOV EAX, 0cd8400bd" }, { 0x87833c, 0x87834f, "MOV EBX, 0b44b3cde" }, { 0x87b769, 0x87b777, "MOV EDX, 04db966e" }, { 0x884288, 0x884298, "MOV EDX, 01553793c" }, { 0x89133a, 0x89134a, "MOV ESI, 02a7014c9" }, { 0x8922ab, 0x8922be, "MOV EBX, 0c38e0a6" }, { 0x89251d, 0x89252f, "MOV EBX, 0c98a0cfb" }, { 0x894556, 0x894565, "MOV EBX, 086018a98" }, { 0x898714, 0x898724, "MOV EDX, 0376c710b" }, { 0x898845, 0x898853, "MOV ECX, 0f3b765ab" }, { 0x89917f, 0x89918c, "MOV EAX, 0d6e5d43b" }, { 0x89b342, 0x89b354, "MOV EDX, 059941057" }, { 0x89e98d, 0x89e99d, "MOV ESI, 0494ffa1f" }, { 0x8a3476, 0x8a3487, "MOV EDI, 0d2b785" }, { 0x8a62ea, 0x8a62fd, "MOV EBX, 08bd76a45" }, { 0x8a71ce, 0x8a71dc, "MOV EDX, 0a210fda6" }, { 0x8a78eb, 0x8a78fb, "MOV ECX, 0540c5ef9" }, { 0x8a8645, 0x8a8655, "MOV EDX, 0627f206c" }, { 0x8ac0bd, 0x8ac0cc, "MOV ESI, 08e981340" }, { 0x8acdd5, 0x8acde5, "MOV EDI, 0febe5a7c" }, { 0x8b13a4, 0x8b13b4, "MOV ECX, 013ce0803" }, { 0x8b1de9, 0x8b1dfb, "MOV ECX, 04ea685c7" }, { 0x8b42c4, 0x8b42d5, "MOV EDX, 0476254ba" }, { 0x8b4919, 0x8b4928, "MOV EBX, 0f84be5e4" }, { 0x8b5c49, 0x8b5c59, "MOV EDX, 0609bbc80" }, { 0x8ba905, 0x8ba917, "MOV EDI, 05d22fa5c" }, { 0x8bab30, 0x8bab42, "MOV EAX, 05be049f2" }, { 0x8bbfc1, 0x8bbfd1, "MOV EBX, 011ac3604" }, { 0x8be635, 0x8be645, "MOV EDX, 016c52865" }, { 0x8c400e, 0x8c401d, "MOV ECX, 0ed9cd2db" }, { 0x8c90cb, 0x8c90db, "MOV ECX, 027208727" }, { 0x8cb8fa, 0x8cb90b, "MOV EDI, 0e4a5842d" }, { 0x8cbabd, 0x8cbad0, "MOV EBX, 0bed28bd1" }, { 0x8cc120, 0x8cc130, "MOV EBX, 0d4f92d37" }, { 0x8ce109, 0x8ce119, "MOV ECX, 0c3b20a27" }, { 0x8d01f7, 0x8d0208, "MOV EDX, 0238a774f" }, { 0x8d0a5f, 0x8d0a6e, "MOV EDX, 02d698aea" }, { 0x8d1a9c, 0x8d1aa9, "MOV EAX, 074ec115a" }, { 0x8d26e1, 0x8d26f0, "MOV ECX, 078b53a47" }, { 0x8d4752, 0x8d4762, "MOV EBX, 0e8dcb02c" }, { 0x8d4f17, 0x8d4f26, "MOV ESI, 0733cb8e2" }, { 0x8d5721, 0x8d5731, "MOV EAX, 07351160b" }, { 0x8dc0ee, 0x8dc0fd, "MOV EDX, 02337f2eb" }, { 0x8dcb7e, 0x8dcb8c, "MOV ECX, 055899497" }, { 0x8e0df1, 0x8e0e01, "MOV ESI, 0911efc6" }, { 0x8e14e2, 0x8e14f2, "MOV EBX, 0918e6486" }, { 0x8e2297, 0x8e22a8, "MOV EDX, 0ed078080" }, { 0x8e257f, 0x8e258d, "MOV EDI, 0d79f6837" }, { 0x8e3745, 0x8e3756, "MOV EAX, 04ff61b79" }, { 0x8e4efb, 0x8e4f0c, "MOV ESI, 0951858ca" }, { 0x8e4fd1, 0x8e4fe1, "MOV EAX, 0373fa366" }, { 0x8e6b59, 0x8e6b6c, "MOV EAX, 0a4e1d4fa" }, { 0x8e77d1, 0x8e77df, "MOV EDI, 0df1e173d" }, { 0x8e8a4e, 0x8e8a5d, "MOV EDI, 08e10e996" }, { 0x8e9953, 0x8e9963, "MOV ECX, 0777fadde" }, { 0x8ea9d7, 0x8ea9e9, "MOV ECX, 0f3eadf10" }, { 0x8ebfe3, 0x8ebff3, "MOV ESI, 050667a1a" }, { 0x8ee0dc, 0x8ee0ef, "MOV EAX, 01f3501a3" }, { 0x8ee2d5, 0x8ee2e6, "MOV EDX, 055aad44f" }, { 0x8ee738, 0x8ee747, "MOV ESI, 0b2fbff36" }, { 0x8ef253, 0x8ef266, "MOV EAX, 06ad8ec8d" }, { 0x8ef88f, 0x8ef8a1, "MOV ESI, 025480f72" }, { 0x8efd44, 0x8efd55, "MOV EDX, 0a4c2184c" }, { 0x8f01d3, 0x8f01e3, "MOV EDX, 07442d802" }, { 0x8f0b6d, 0x8f0b7b, "MOV EDX, 03a326175" }, { 0x8f11b9, 0x8f11c8, "MOV EBX, 04a8ffb13" }, { 0x90546e, 0x90547e, "MOV EDI, 09c89e0af" }, { 0x924cff, 0x924d0f, "MOV ESI, 092e232d3" }, { 0x9261f2, 0x926204, "MOV EDI, 0189bc8ff" }, { 0x928b10, 0x928b21, "MOV EBX, 07a13a572" }, { 0x928e93, 0x928ea6, "MOV EDX, 0c026ebb8" }, { 0x93aea9, 0x93aeb9, "MOV EDI, 01131e7ed" }, { 0x93d201, 0x93d20f, "MOV EBX, 064a2d76a" }, { 0x93f39b, 0x93f3ae, "MOV ESI, 0bbe3e8c3" }, { 0x940284, 0x940294, "MOV ESI, 0a22eb245" }, { 0x94058e, 0x94059d, "MOV EBX, 02d33939c" }, { 0x940a5c, 0x940a6c, "MOV EAX, 0456de1a1" }, { 0x940bbb, 0x940bce, "MOV ESI, 0deffe75a" }, { 0x946a64, 0x946a72, "MOV ESI, 0aa8e9c06" }, { 0x9484d4, 0x9484e5, "MOV EDX, 08e7e081c" }, { 0, 0, NULL } }; void *build_ext_model(char *cmd_sequence) { t_extmodel *diamod; t_extmodel *pmod; char diaasm[ARGLEN]; char cmd[ARGLEN]; int n, nseq, errpos, offset, i, j, k, m, good, validcount; char *pa; char errtext[512]; char t[512]; t_extmodel model; m = 0; diamod = malloc(sizeof(t_extmodel) * NSEQ * NMODELS); memset(t, 0, 512); memset(errtext, 0, 512); memset(diaasm, 0, ARGLEN); if (cmd_sequence == NULL) return NULL; n = strlen(cmd_sequence); if (n == 0) return NULL; memcpy(diaasm, cmd_sequence, n); diaasm[sizeof(diaasm) - 1] = '\0'; memset(diamod, 0, sizeof(t_extmodel) * NSEQ * NMODELS); pa = diaasm; errtext[0] = '\0'; for (nseq = 0; *pa != '\0'; nseq++) { Addtolist(0, 1, "treat : nseq = %d, where = %s", nseq, pa); errpos = 0; offset = pa - diaasm; if (nseq >= NSEQ) { sprintf(errtext, "Sequence is limited to %i commands", NSEQ); break; } n = 0; while (n < TEXTLEN && *pa != '\r' && *pa != '\n' && *pa != '\0') cmd[n++] = *pa++; if (n >= TEXTLEN) { errpos = TEXTLEN - 1; strcpy(errtext, "Command is too long"); break; } while (*pa == '\r' || *pa == '\n') pa++; cmd[n] = '\0'; for (i = 0; cmd[i] != '\0' && i < (TEXTLEN - 4); i++) { if (cmd[i] != ' ' && cmd[i] != '\t') break; } if (memicmp(cmd + i, "ANY", 3) == 0 && (cmd[i + 3] == ' ' || cmd[i + 3] == '\t' || cmd[i + 3] == '\0' || cmd[i + 3] == ';')) { i += 3; while (cmd[i] == ' ' || cmd[i] == '\t') i++; validcount = 0; for (n = 0; ; i++) { if (cmd[i] >= '0' && cmd[i] <= '9') n = n * 16 + cmd[i] - '0'; else if (cmd[i] >= 'A' && cmd[i] <= 'F') n = n * 16 + cmd[i] - 'A' + 10; else if (cmd[i] >= 'a' && cmd[i] <= 'f') n = n * 16 + cmd[i] - 'a' + 10; else break; validcount++; } while (cmd[i] == ' ' || cmd[i] == '\t') i++; if (cmd[i] != '\0' && cmd[i] != ';') { errpos = i; strcpy(errtext, "Syntax error"); break; } if (validcount == 0) n = 1; if (n 8) { errpos = i; strcpy(errtext, "ANY count is limited to 1..8"); break; } if (nseq == 0) { strcpy(errtext, "'ANY' can't be the first command in sequence"); break; } diamod[nseq * NMODELS].isany = n; diamod[nseq * NMODELS].cmdoffset = offset; continue; } i = 0; for (j = 0; i < NMODELS; j++) { Addtolist(0, 1, "building : i = %d, j = %d, errtext = %s", i, j, errtext); good = 0; for (k = 0; k < 4 && i < NMODELS; k++) { if (i >= NMODELS) break; memset(&model, 0, sizeof(model)); n = Assemble(cmd, 0, (t_asmmodel *)&model, j | 0x80000000, k, (i == 0 ? errtext : t)); if (n > 0) { good = 1; for (m = 0, pmod = diamod + nseq * NMODELS; m < i; pmod++, m++) { if (n == pmod->length && memcmp(model.code, pmod->code, n) == 0 && memcmp(model.mask, pmod->mask, n) == 0 && memcmp(model.ramask, pmod->ramask, n) == 0 && memcmp(model.rbmask, pmod->rbmask, n) == 0) break; } if (m >= i) { diamod[nseq * NMODELS + i] = model; diamod[nseq * NMODELS + i].cmdoffset = offset; i++; } } else if (i == 0) errpos = -n; } if (good == 0) break; } Addtolist(0, 1, "end of building : errtext = %s", errtext); if (errtext[0] != '\0') break; Addtolist(0, 1, "end of building end end"); } if (nseq == 0 && errtext[0] == '\0') strcpy(errtext, "Empty sequence"); if (diamod[(nseq - 1) * NMODELS].isany != 0) { nseq--; strcpy(errtext, "'ANY' can't be the last command in sequence"); } if (errtext[0] != '\0') { MessageBox(hwmain, errtext, "SkyCks Plugin", MB_OK | MB_ICONERROR); } errpos = errpos + nseq + m; if (errpos) return (diamod); return (diamod); } BOOL WINAPI DllEntryPoint(HINSTANCE hi, DWORD reason, LPVOID reserved) { if (reason == DLL_PROCESS_ATTACH) hinst = hi; return 1; } extc int _export cdecl ODBG_Plugindata(char shortname[32]) { strcpy(shortname, "Skycks"); return PLUGIN_VERSION; } extc int _export cdecl ODBG_Plugininit(int ollydbgversion, HWND hw, ulong *features) { if (ollydbgversion < PLUGIN_VERSION) return -1; hwmain = hw; Addtolist(0, 0, "Skype CheckSum Agents Patcher Plugin"); Addtolist(0, -1, " Copyright (C) 2006 Ouanilo MEDEGAN"); return 0; } extc void _export cdecl ODBG_Pluginmainloop(DEBUG_EVENT *debugevent) { } extc int _export cdecl ODBG_Pluginmenu(int origin, char data[4096], void *item) { switch (origin) { case PM_MAIN: strcpy(data, "0 &Spot Them All|1 &Update Comments|2 &Dump Comments|3 &Clean Comments|4 &About"); return 1; default: break; } return 0; } extc void _export cdecl ODBG_Pluginaction(int origin, int action, void *item) { t_dump *cpu; ulong base; char name[TEXTLEN]; char errtext[TEXTLEN]; char nop = 0x90; int res; char *buffer; char saddr[9]; char names[20]; FILE *file; int i, j, k; t_asmmodel model; if (origin == PM_MAIN) { cpu = (t_dump *)Plugingetvalue(VAL_CPUDASM); switch (action) { case 0: i = 0; Dumpbackup(cpu, BKUP_SAVEDATA); while (ptab[i].wtp) { errtext[0] = 0; Assemble(ptab[i].code, ptab[i].wtp, &model, 0, 0, errtext); if (errtext[0]) Addtolist(0, 1, "error : %s with %s", errtext, ptab[i].code); else Addtolist(0, -1, "success at %p with %s", ptab[i].wtp, ptab[i].code); Writememory(model.code, ptab[i].wtp, model.length, MM_RESTORE); j = k = 0; j = ptab[i].end - (ptab[i].wtp + model.length); while (j) { Writememory(&nop, ptab[i].wtp + model.length + k, 1, MM_RESTORE); j--; k++; } //Setbreakpoint(ptab[i].wtp, TY_ACTIVE, 0); i++; } Dumpbackup(cpu, BKUP_DELETE); Dumpbackup(cpu, BKUP_LOADCOPY); break; case 1: i = 0; while (checks[i]) { memset(names, 0, 20); sprintf(names, "Chk #%d", i); Quickinsertname(checks[i], NM_COMMENT, names); i++; } Mergequicknames(); break; case 2: buffer = malloc(4096 * 4); memset(buffer, 0, 4096 * 4); strcat(buffer, "int checks[] = {"); //Findallsequences(cpu, build_ext_model("XOR R32, R32\r\nANY 8\r\nJMP OFFSET\r\nANY 8\r\nSUB R32, CONST\r\nANY 8\r\nDEC R32"), 0, "Skype CheckSum Layers"); memset(name, 0, TEXTLEN); res = 0; base = (ulong)Plugingetvalue(VAL_MAINBASE); res = Findname(base, NM_COMMENT, name); Addtolist(0, -1, "base = %p, res = %d, name = %s", base, res, name); memset(name, 0, TEXTLEN); memset(saddr, 0, 9); while ((res = Findnextname(name)) != 0) { if (strstr(name, "Chk #")) { Addtolist(0, 0, "found : @", name, res); sprintf(saddr, "%#8x", res); strcat(buffer, saddr); strcat(buffer, ",\r\n "); } memset(name, 0, TEXTLEN); memset(saddr, 0, 9); } sprintf(saddr, "%#8x", 0); strcat(buffer, saddr); strcat(buffer, ",\r\n "); strcat(buffer, "}"); file = fopen("tab.txt", "wb"); fwrite(buffer, strlen(buffer), 1, file); fclose(file); free(buffer); MessageBox(0, "Job Done", "Dump", MB_OK); break; case 3: /*memset(name, 0, TEXTLEN); res = 0; base = (ulong)Plugingetvalue(VAL_MAINBASE); res = Findname(base, NM_COMMENT, name); memset(name, 0, TEXTLEN); while ((res = Findnextname(name)) != 0) { if (strstr(name, "Chk #")) { Addtolist(0, 0, "found : @", name, res); Insertname(res, NM_COMMENT, 0); } memset(name, 0, TEXTLEN); memset(saddr, 0, 9); }*/ base = (ulong)Plugingetvalue(VAL_MAINBASE); Deletenamerange(base, 0xbeefff, NM_COMMENT); break; case 4: MessageBox(hwmain, "Skype CheckSum Agents Patcher Plugin\n" "Copyright (C) 2006 Ouanilo MEDEGAN", "SkyCks Plugin", MB_OK | MB_ICONINFORMATION); break; default: break; } } } extc void _export cdecl ODBG_Pluginreset(void) { } extc int _export cdecl ODBG_Pluginclose(void) { return 0; } extc void _export cdecl ODBG_Plugindestroy(void) { }
*Checksum Agent After Automatic Patch*
![](http://www.oklabs.net/wp-content/uploads/2012/06/after_patch.jpg)
SC程式還定期執行檢查是否在除錯狀態。除錯執行比正常執行通常慢上許多。當探測到的時候,程式會給偵錯程式設定陷阱,如改變執行時的關鍵值,或者直接讓它執行到一個錯誤的路徑從而導致程式直接跑飛。這個保護使用OllyDbg的Phant0m外掛hook住GetTickCount就過了。
程式有幾個例程執行完整性檢查,打上補丁讓我們繼續。
Integrity Check Exemple #1
Integrity Check Exemple #2
Integrity Check Exemple #3
SC程式的敏感程式碼處有大量的花指令混淆正常的閱讀。但是這一保護只是減慢了分析程式,並迫使我更加仔細閱讀彙編程式碼。但在程式碼的某些部分使用一些特殊的技巧,使得不能跟隨執行。在這些執行點上,等待是唯一的解決方案。
例:RC4金鑰的擴充套件使用基於“異常重定向(exceptions redirections)”的種子例程生成。程式碼異常有意呼叫並設定處理例程,後續的程式碼在其它地址上。所有執行在Ring 3級別的除錯都會丟失這些處理例程。為了解決這個問題,我已經設計了一個工具,讓它執行在虛擬機器上,並需要一個執行著的Skype客戶端附加上,在注入程式碼中接收種子的擴充套件需求和應答金鑰生成器。
Skype Key Server
/* Skype Key Server */ /* Author : Ouanilo MEDEGAN */ #define RC4_KLEN 88 HANDLE hProcess = 0; LPVOID RemoteAddr, KeyAddr; int Size; void __declspec(naked) InjectedCode() { __asm { jmp BeginOCode //KeyAddr: INT 3 INT 3 INT 3 INT 3 KeyAddrGet: _emit 0xE8 _emit 0x00 _emit 0x00 _emit 0x00 _emit 0x00 pop eax sub eax, 0x09 mov eax, dword ptr[eax] ret //Seed: INT 3 INT 3 INT 3 INT 3 SeedGet: _emit 0xE8 _emit 0x00 _emit 0x00 _emit 0x00 _emit 0x00 pop eax sub eax, 0x09 mov eax, dword ptr[eax] ret BeginOCode : call KeyAddrGet mov ecx, eax call SeedGet mov edx, eax mov eax, 0x0075D470 call eax //mov eax, 0x7C80C058 //ON COMMON MACHINE : ExitThread Address //mov eax, 0x77E54A8F //ON "NEUF" MACHINE mov eax, 0x401304 call eax DEC ECX //I DEC ESI //N DEC EDX //J INC EBP //E INC EBX //C PUSH ESP //T DEC ECX //I DEC EDI //O DEC ESI //N POP EDI //_ INC EBP //E DEC ESI //N INC ESP //D } } int SizeOfCode() { int Size; char *Proc; char Buffer[14] = { 0 }; Size = 0; Proc = (char *)InjectedCode; do { memcpy(Buffer, Proc, 13); Size++; Proc++; } while (strcmp(Buffer, "INJECTION_END")); return (Size - 1); } DWORD GetSkypeProcessHandle() { HANDLE hProcessSnap; PROCESSENTRY32 PE32; DWORD SkypeProcess; SkypeProcess = -1; hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if (hProcessSnap == INVALID_HANDLE_VALUE) { printf("Error : CreateToolhelp32Snapshot (of processes) failed..\n"); return (-1); } PE32.dwSize = sizeof(PROCESSENTRY32); if (!Process32First(hProcessSnap, &PE32)) { printf("Error : Process32First failed..\n"); CloseHandle(hProcessSnap); return (-1); } do { if (strcmp("Skype.exe", PE32.szExeFile) == 0) { SkypeProcess = PE32.th32ProcessID; break; } } while (Process32Next(hProcessSnap, &PE32)); CloseHandle(hProcessSnap); return (SkypeProcess); } int Seed2Key(unsigned char *Key, unsigned int seed) { /* FIXME */ /* For the moment based on Skype.exe process in memory. Pick the proc Appart !*/ DWORD NbWritten, ThID; HANDLE hThread; unsigned char *CodeBuffer; if (!WriteProcessMemory(hProcess, KeyAddr, (LPCVOID)Key, RC4_KLEN, (SIZE_T *)&NbWritten)) { printf("Skype Process WriteProcessMemory (Key) failed.. Aborting..\n"); return (0); } CodeBuffer = (unsigned char *)malloc(Size); memcpy(CodeBuffer, (void *)InjectedCode, Size); memcpy(CodeBuffer + 2, (void *)&KeyAddr, 4); memcpy(CodeBuffer + 18, (void *)&seed, 4); if (!WriteProcessMemory(hProcess, RemoteAddr, (LPCVOID)CodeBuffer, Size, (SIZE_T *)&NbWritten)) { printf("Skype Process WriteProcessMemory (Code) failed.. Aborting..\n"); return (0); } free(CodeBuffer); hThread = CreateRemoteThread(hProcess, NULL, NULL, (LPTHREAD_START_ROUTINE)RemoteAddr, NULL, NULL, (LPDWORD)&ThID); if (!hThread) { printf("Skype Process CreateRemoteThread failed.. Aborting..\n"); return (0); } WaitForSingleObject(hThread, INFINITE); if (!ReadProcessMemory(hProcess, KeyAddr, (LPVOID)Key, RC4_KLEN, (SIZE_T *)&NbWritten)) { printf("Skype Process ReadProcessMemory (Key) failed.. Aborting..\n"); return (0); } return (1); } int InitProc() { STARTUPINFO Si; PROCESS_INFORMATION Pi; ZeroMemory(&Si, sizeof(Si)); ZeroMemory(&Pi, sizeof(Pi)); Si.cb = sizeof(Si); //CREATE_SUSPENDED /*if(!CreateProcessA("SkypeKeyServer.exe", "SkypeKeyServer.exe", NULL, NULL, FALSE, NULL, NULL, NULL, (LPSTARTUPINFOA)&Si, &Pi)) { printf("Error creating process..\n"); return (0); } hProcess = Pi.hProcess;*/ hProcess = OpenProcess(PROCESS_ALL_ACCESS, NULL, GetSkypeProcessHandle()); if (!hProcess) { printf("Failed Opening process..\n"); return (0); } KeyAddr = VirtualAllocEx(hProcess, NULL, RC4_KLEN, MEM_COMMIT, PAGE_EXECUTE_READWRITE); if (!KeyAddr) { printf("Skype Process VirtualAllocEx (Key) failed.. Aborting..\n"); return (0); } Size = SizeOfCode(); RemoteAddr = VirtualAllocEx(hProcess, NULL, Size, MEM_COMMIT, PAGE_EXECUTE_READWRITE); if (!RemoteAddr) { printf("Skype Process VirtualAllocEx (Code) failed.. Aborting..\n"); return (0); } return (1); } void EndProc() { VirtualFreeEx(hProcess, KeyAddr, 0, MEM_RELEASE); VirtualFreeEx(hProcess, RemoteAddr, 0, MEM_RELEASE); CloseHandle(hProcess); } int main(int argc, char* argv[]) { WORD wVersionRequested; WSADATA wsaData; int err, SelRes, Res, CbSz, i; SOCKET Sock; sockaddr_in LocalBind, ClientBind; fd_set Sockets; unsigned char Key[RC4_KLEN] = { 0 }; unsigned int Seed; wVersionRequested = MAKEWORD(2, 2); err = WSAStartup(wVersionRequested, &wsaData); if (err != 0) { printf("Unable to start WSA Lib\n"); return (0xBADF00D); } if (!InitProc()) return 0; printf("SkypeKeyServer Started..\n"); Sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if (Sock == INVALID_SOCKET) { printf("Could not create socket..\n"); WSACleanup(); exit(0); } ZeroMemory((char *)&LocalBind, sizeof(LocalBind)); LocalBind.sin_family = AF_INET; LocalBind.sin_addr.s_addr = htonl(INADDR_ANY); LocalBind.sin_port = htons(33033); bind(Sock, (struct sockaddr *)&LocalBind, sizeof(LocalBind)); while (1) { FD_ZERO(&Sockets); FD_SET(Sock, &Sockets); CbSz = sizeof(struct sockaddr_in); SelRes = select(FD_SETSIZE, &Sockets, NULL, NULL, NULL); if (SelRes) { Res = recvfrom(Sock, (char *)&Seed, 0x04, 0, (SOCKADDR *)&ClientBind, &CbSz); if (Res != 0x04) ZeroMemory((char *)&Key[0], RC4_KLEN); else { for (i = 0; i < 0x14; i++) *(unsigned int *)(Key + 4 * i) = Seed; Seed2Key(Key, Seed); } Res = sendto(Sock, (char *)&Key[0], RC4_KLEN, 0, (SOCKADDR *)&ClientBind, CbSz); } } EndProc(); closesocket(Sock); WSACleanup(); return 0; }
到了這裡,我就可以獲得一個幾乎沒有保護的二進位制檔案。接下來就可以真正的開始研究協議了。
這裡是我研究過程中的OllyDbg udd檔案(包含標籤,有意義的斷點,除錯註釋等):skype_binary_ollydbg_udd.zip(程式路徑: C:\Program Files\Skype\Phone\Skype.exe)。
0x03 Skype協議分析
3.1 方法
在分析的第一步,是儲存各種諸如首次連線沒有快取資料,重連,錯誤的驗證資料(使用者名稱/密碼),成功的連線,聊天初始化等等,所有的資料的傳送都是透過Skype網路完成。這裡已經有調教好的OSpy設定,帶有網路功能入口點hook和收集傳送到這些功能的引數(引數包括髮送到/接收自網路的資料)。
在以前的分析中我注意到一個關鍵點。二進位制檔案充斥著大量動態生成的除錯字串。但是在二進位制檔案中它們並未顯示出來(當然,它們僅用於內部除錯)。我不得不在二進位制檔案打上幾個補丁,讓程式管理那些除錯字串,並用一個特定的函式來接收生成的字串。在這一點上,我已經調教好“OSpy”讓它收集生成的字串和填補其產生的報告,以同步網路資料傳送和接收那些有意義的字串。。最後,我能得到一個更易於理解的報告,如下圖所示(生成的字串帶有“SD”標記):
oSpy Skype Debug Hook
/* oSpy Skype Debug Hook */
/* Author : Ouanilo MEDEGAN */
#include "stdafx.h"
#include "hooking.h"
#include "logging.h"
void skype_log(char *str)
{
if ((str) && (strstr(str, "UI-[") == NULL))
{
str[strlen(str) - 1] = 0;
message_logger_log_message("Sdbg", NULL, MESSAGE_CTX_INFO, "%s", str);
}
}
void
hook_skype()
{
void *func_addr;
void *var1_addr;
void *var2_addr;
int oldProtect, NbW;
if (cur_process_is("Skype.exe"))
{
func_addr = (void *)0xB9DFC8;
var1_addr = (void *)0xB9E150;
var2_addr = (void *)0xB9E154;
}
else
return ;
VirtualProtect(func_addr, 4, PAGE_READWRITE, (PDWORD)&oldProtect);
*(DWORD *)func_addr = (DWORD)skype_log; //Override built-in & empty logging function
VirtualProtect(func_addr, 4, oldProtect, (PDWORD)&oldProtect);
VirtualProtect(var1_addr, 8, PAGE_READWRITE, (PDWORD)&oldProtect);
*(DWORD *)var1_addr = (DWORD)0x01; //Set logging to YES
*(DWORD *)var2_addr = (DWORD)0x00; //Dont Log Hour ! (~ 0x01)
VirtualProtect(var1_addr, 8, oldProtect, (PDWORD)&oldProtect);
}
加上這樣的詳細報告,我就能夠確定幾乎沒有什麼網路資料包是偽造的,而且還能幫助理解接收/應答對等網路的資料包。實際上,這些報告還能夠讓我快速定位部分有趣的彙編碼,如透過單步除錯以便理解如何資料包是如何構建。報告還幫助我瞭解Skype會話的不同階段,從斷開狀態,初始化通訊會話,首次進入P2P網路,在網路上登入認證本節點,動態聯絡人列表的獲取,根據要求響應P2P協議。
3.2 協議的特性
在繼續之前,我發現了一些Skype協議機制,它的一些技術tip我在這裡再重申一遍:
Skype網路是基於P2P網路模型,Skype客戶端同時作為客戶端和伺服器使用。每個SC在網路表現為一個“node”。然而,那些頻寬好,CPU棒的客戶端可能會被晉升為“超級node(SN)”。超級node在Skype網路上作為普通node的介面,幫助普通node路由請求和提供網路資訊。也有特殊的超級node用於防止直接連線這種特殊的網路情況,每個SC二進位制檔案都包含適當的超級node和普通node行為的程式碼。
每個節點在SC的網路都由一個“NodeID”唯一標識確定,它是一個平臺特定的值,如硬碟序列號生成,或開發系統的序列。
在Skype網路中也存在一些集中式的網路架構:
LoginServer: 該服務用於驗證客戶端的帳號密碼,讓客戶端可以成為Skype網路上一員。
EventServer: 用於動態快取每個登入成功的使用者資訊,如聯絡人列表,聊天記錄等等...
Skype Entities
在首次連結當中,SC只使用硬編碼在載入程式上的超級node IP地址等資訊,並在首次連結之後收集並儲存網路資料和使用者資訊(當前版本儲存到“shared.xml”檔案)。
為了進行網路通訊,SC同時使用UDP和TCP協議。傳輸過程的訊息的每一位都經過加密。一個訊息有多鍾加密演算法可以選擇。但主要還是用“RC4(128 bit)”演算法,雖然AES(Advanced Encryption Standard)和RSA也可以使用。請參考Skype網路管理員指南獲得更詳細的加密細節Skype Reverse Engineering : Genesis。
Skype協議是一個二進位制協議。這意味著,該協議不像HTTP或FTP協議具備較好的可讀性。該協議使用數字化的功能ID,引數型別和引數ID。它有點類似於微軟的RPC(Remote procedure call)協議。這些使得該協議更加難以理解。此外,讓該協議更加難以理解的是,傳輸的資料採用的是自己的壓縮演算法。
SC在一個完整的會話當中有以下幾個狀態:
- “Disconnected” 離線狀態。
- “Registering” 註冊狀態(向Skype網路廣播它的存在)。
- “Registered” 就緒狀態(準備跟其它使用者互動)。
- “Initializing text/file/voice/video session” 初始化狀態(與其它使用者協商建立通訊會話)。
- “Session in progress” 通訊狀態(正與其它使用者通訊中)。
3.3 理解協議機制
經過6個月的研究,我已經完全理解了SC客戶端怎麼從“Disconnected”狀態切換到“Registered”狀態,以及它在“Registered”狀態如何工作,最後如何執行文字/檔案/聲音/影片會話初始化。
現在讓我們總體描述一下SC客戶端是怎麼從“Disconnected”狀態到“Registered”狀態:使用者從輸入使用者名稱和密碼之後發起連線的整個過程。
SC客戶端首先必須和一個超級node建立連線,並將該超級node作為它的父節點。這個超級node在整個會話期間作為Skype網路介面。具體的實現方法是,SC客戶端執行主機掃描:傳送UDP探測包給已知的超級node(快取或載入程式設定的),直到它收到一個確定的探測應答。然後SC將嘗試與該超級node建立一個TCP連線。如果失敗,SC會繼續執行主機掃描探測其它已知的超級node。當超級node傳送一個探測拒絕應答,SC客戶端也會繼續執行主機掃描。
一旦SC客戶端成功和超級node建立一個TCP連線,它便向超級node傳送一個“client accept”請求。如果收到拒絕應答則繼續執行主機掃描。收到確定應答則意味著主機掃描完成且有一個父節點可以作為它的Skype網路介面。
Host Scan Sample
Successful Parent Node Connection
下一步,SC進行使用者驗證。它將與一個已知的LoginServer建立TCP連線,併傳送一個包含眾多hash演算法和加密原語(即256位AES長金鑰,1536位RSA長金鑰,SHA,MD5和RC4 hash)的資料包。這個強大的加密方案用於保護使用者訪問(使用者名稱/密碼)時不被釣魚。只有具備“Skype Technologies S.A.”的LoginServer才能使用RSA的公鑰/私鑰解密傳送過來的資料包。資料包的加密部分使用“Skype Technologies S.A.”釋出的公鑰,並硬編碼在SC二進位制檔案中。客戶端可以放心地認為只有具有“Skype Technologies S.A.”的私鑰才能解密資料包。
注意,在傳送資料包到LoginServer之前,SC已經生成一對用於會話期間的RSA公鑰/私鑰(每個長為1024位)。當該訪問是有效時,也就是說如果使用者輸入的帳號密碼是正確的。LoginServer將給SC傳送一個包含驗證使用者名稱,驗證有效期,公鑰的資料結構。該結構簽署“Skype Technologies S.A.”的私鑰以確保它的安全性。透過硬編碼在SC二進位制檔案的“Skype Technologies S.A.”公鑰就可以對該結構體進行解密。這個資料結構證明使用者已經透過LoginServer身份驗證。我們稱這個資料結構為“Signed Credentials”。
Successful Authentication
一旦透過驗證,SC就向Skype網路廣播:我的已經上線了哈。具體的實現方法是,SC客戶端傳送一個包含本node的位置資訊(IP:Port,父節點的IP:Port,node ID等等...)的資料包。這裡的使用者私鑰涉及使用RC4和RSA,資料包還包含之前已獲得的使用者Signed Credentials,這個方案的優點是確保傳送者的安全性。資料的加密部分透過包含在驗證透過的Signed Credentials的公鑰解密出來。也就是說只有透過使用者登入驗證的使用者才能傳送資料。
節點位置資訊的資料包包含(它也可以包含其他資訊,如使用者的真實姓名,地理位置,等等)身份驗證,User's DirBlob。User's DirBlob的地址被填充為父節點併傳送到指定的超級node,接著向Skype網路上傳送廣播讓其它使用者可以發現它的存在。到這裡,SC處於Registered狀態。
在這個狀態,SC客戶端從EventServer上獲取使用者聯絡人列表並檢查每個存在的聯絡人。它的實現方案是:SC客戶端與一個已知的EventServer建立TCP連線,然後跟LoginServer進行身份驗證,接著請求下載該使用者列表包含的每個聯絡人對應的hash列表。獲得hash列表後如果發現沒有匹配的hash,SC客戶端會向EventServer傳送“hash details request”命令請求EventServer回覆Contact's DirBlob。這個回覆包作為代替之前的User's DirBlob。它包含node位置資訊,詳細的登入資訊,真是姓名,地理資訊等等...
現在,無論SC客戶端在Skype網路槽上是否上線都會從取得的聯絡人列表搜尋每個聯絡人。完成之後便向指定的超級node(這個超級node應該在之前就收到SC客戶端傳送的Contact DirBlob廣播)傳送“contact search”命令。然後超級node會回覆含有它這個節點的位置資訊的Contact DirBlob。超級節點會儲存廣播的節點位置DirBlob72個小時,如果一個聯絡人在72小時內多次登入,SC客戶端搜尋的時候會得到更多的Contact DirBlob應答。它傳送一個ping命令到接收過DirBlob的每個節點並挑選一個已知確切的位置的聯絡節點作為應答。
這一步之後SC便處於“Registered”狀態,並且聯絡人列表也更新到最新狀態。
3.4 Registered狀態
Incoming Ping Example
一旦處於Registered狀態,SC客戶端便在監聽它的網路介面。也就是說,他的父節點可以傳送線上聯絡人會話初始化請求。SC客戶端可以自接收父節點的進入ping,好友搜尋傳送的使用者位置驗證,網路配置測試和傳入申請會話初始化。
Incoming Session Proposition Example
3.5 會話初始化
為了與另一個使用者建立通訊連線,兩個SC客戶端之間需要進行協商作為通訊的channel。channel定義一個安全的AES流作為資料交換使用,無論是否直接或透過超級節點中繼通訊。
Session Initialization Sample #1
Session Initialization Sample #2
3.6 會話處理
如果你閱讀到這裡,我就當作你已經看懂我之前都在說些什麼,接下來讓程式碼代替我講訴後續的分析。
0x04 附錄
抱歉,以下都是法語的。
有關Skype v2.5協議的相關資料(包含使用者驗證和聯絡列表獲取)請點選Skype (v2.5) Protocol Analysis檢視(法語)。
有關Skype v2.5協議的主要資料結構請點選Skype (v2.5) Protocol Data Structures檢視。
有關Skype分析專案的筆記請點選Skype (v2.5) Analysis Project Misc Notes檢視。
相關文章
- iOS逆向之旅 — 總綱2018-10-16iOS
- iOS逆向之旅(進階篇) — 工具(LLDB)2018-10-26iOSLLDB
- iOS逆向之旅(進階篇) — HOOK(Logos)2018-10-26iOSHookGo
- iOS逆向之旅(進階篇) — HOOK(FishHook)2018-10-26iOSHook
- iOS逆向之旅(進階篇) — 程式碼注入2018-10-26iOS
- iOS逆向之旅(基礎篇) — Macho檔案2018-10-26iOSMac
- iOS逆向之旅(進階篇) — HOOK(Method Swizzling)2018-10-26iOSHook
- 記一次Android逆向之旅(入門向)2020-04-07Android
- iOS逆向之旅(進階篇) — 工具(class-dump)2018-10-26iOS
- iOS逆向之旅(進階篇) — 重簽名APP(二)2018-10-26iOSAPP
- iOS逆向之旅(進階篇) — 重簽名APP(一)2018-10-26iOSAPP
- Android逆向之旅---Hook神器家族的Frida工具使用詳解2018-07-02AndroidHook
- iOS逆向之旅(基礎篇) — 彙編(一)— 彙編基礎2018-10-25iOS
- Android逆向之旅--瘋狂兔子無敵跑跑 內購破解教程2018-05-28Android
- iOS逆向之旅(基礎篇) — 彙編(五) — 彙編下的Block2018-10-25iOSBloC
- iOS逆向之旅(基礎篇) — 彙編(四) — 彙編下的函式2018-10-25iOS函式
- iOS逆向之旅(基礎篇) — 彙編(二) — 彙編下的 IF語句2018-10-25iOS
- Android逆向之旅---手遊「狂野飆車極速版」內購破解教程2018-04-09Android
- Android逆向之旅--免Root實現微信訊息同步原理解析2018-06-22Android
- Android逆向之旅---爆破資訊類應用「最右」的防抓包策略2018-04-25Android
- iOS逆向之旅(基礎篇) — 彙編(三) — 彙編下的 Switch語句2018-10-25iOS
- win10系統下如何阻止Skype廣告2020-01-17Win10
- Android逆向之旅---破解某支付軟體防Xposed等框架Hook功能檢測機制2018-05-15Android框架Hook
- Android逆向之旅---靜態方式分析破解視訊編輯應用「Vue」水印問題2018-05-03AndroidVue
- win10系統skype訊息不推送怎麼辦_win10系統skype不傳送訊息如何解決2020-04-30Win10
- 逆向通達信 x 逆向微信 x 逆向Qt2024-07-01QT
- 羽夏逆向——逆向基礎2021-11-19
- Skype 支援端對端加密,但沒有預設啟用2018-08-23加密
- Android逆向之旅--微信封了抖音分享功能,而我要把短視訊成功分享到朋友圈!...2018-07-30Android
- Android逆向之旅--破解過濾掉某音短視訊的廣告和視訊水印問題2018-06-04Android
- Android 逆向(四) - adb常用逆向命令2024-03-20Android
- windows10系統怎麼解除安裝Skype for Business應用2020-02-19Windows
- 【JS逆向百例】cebupacificair 航空逆向分析2024-11-18JSAI
- 逆向工程核心原理(1)逆向基礎2023-03-16
- iOS逆向之旅(基礎篇) — App的簽名機制【Xcode是如何將App安裝到手機的】2018-10-26iOSAPPXCode
- 逆向32024-11-15
- 逆向logmein2024-09-11
- SMC逆向2024-05-27