Algorithm, Secret key and Protocol
最近在對基于區塊鏈構建的信任社會(未來社會形態)非常感興趣,區塊技術去中心化的特性,讓沒有金融機構成為了可能(包括央行,以及各種商業銀行)。
除了在數字貨幣領域大放異彩外,在包括供應鏈,網絡購物,公平合約等方面的應用也非常廣泛。其中智能合約的特性十分的吸引我。
不過我今天并不想討論區塊技術,因為區塊技術建立在密碼學的技術之上,通過最近兩周對密碼學以及數論基礎的研究,分享一些知識,主要關于如何攻擊一個宣稱安全的密碼系統。
在密碼學中,算法,秘鑰以及協議構成了一個完整的密碼系統。
算法是密碼系統的基礎,它是將一組或多組輸入信息轉化成一組或多組輸出信息的數學方法,算法的輸出會被秘鑰影響,秘鑰不同,輸出也不同。因此,加密同樣的信息,A用秘鑰Ka加密與B用秘鑰Kb加密輸出的信息是完全不同的。
秘鑰是密碼系統的核心,奧秘在于算法存在一個最優解(最優解有很多種解釋,例如超遞增序列,因式分解兩素數之和等),這個秘密就是秘鑰。如果一個人想要解密別人的密文,在不知道秘鑰的情況下,他只能嘗試解數學難題,這對于普通人擁有的資源來說,直到宇宙毀滅也無法得出答案。
有了算法和秘鑰,沒有協議就不是一個密碼系統。協議是指由多方參與的,確定要去完成一些事情。如果沒有多方參與,或者沒有去完成一些目標就不叫協議。協議有很多種,最明顯比如秘鑰交換協議(例如A和B要在一個加密的通道中傳遞信息,在這之前A和B要協商一個秘鑰并交換,使得A可以加密消息發送給B并確保B可以解密信息)。
在一個安全的密碼系統中,必須是算法,秘鑰,協議都安全,如果其中一個是不安全的,那么整個系統就是不安全的。
大多數流行的公開算法,已經被廣泛的證明是安全的,它們經歷過很多理論數學家的分析。秘鑰的安全性由秘鑰長度,以及你是否妥當的保管你的秘鑰決定。如果你做的不錯,那么秘鑰也是安全的。
協議卻不是如此了,協議很脆弱。舉個例子,A將重要的信息加密發送給B,只有B有秘鑰可以解密這些信息,C在網路上監聽到了A發送給B的秘密,但是C沒有秘鑰無法解密信息,怎么辦。上面說過,秘鑰是這個數學難題的最優解,如果沒有秘鑰,C就只能嘗試解數學難題,恐怕C這一生都無法解開,但是C的老板要求他必須在72小時內解密信息,不然C所在的公司就要遭受重大損失。于是C決定鋌而走險,他綁架了B,并對B進行嚴刑拷打,B扛不住,交出了他的秘鑰,C拿著B的秘鑰解密了秘密,并干掉了B。
上面的例子說明了協議在整個密碼系統中是一個易攻擊的薄弱環節,這樣的攻擊類型很多(賄賂,美色誘惑,威脅等)。
我們來嘗試把這種攻擊帶入到軟件破解上來,同樣的結論也是攻擊協議,而非攻擊算法和秘鑰。同樣來舉個例子,軟件D采用RSA公開秘鑰算法來對用戶的信息以及軟件產品序列號摘要進行加密,并發送給軟件D公司的注冊服務器,軟件公司D的計算機收到用戶A發送給它的加密信息,用自己的私鑰解密并把用戶信息與數據庫中的信息進行對比,如果序列號不在D公司的數據庫中說明這個產品的不是D公司銷售的產品,如果序列號在D公司的數據庫中,并且A用戶信息不在數據庫中,那么A是第一次注冊,將A的信息與序列號綁定在一起保存在數據庫中,此序列號將不能再給其他人使用。如果序列號和A都在數據庫中,并且序列號與A綁定,那么D公司用私鑰加密一段許可信息發送給A,A每次使用D軟件的時候驗證這段信息,如果是D公司發送的許可信息允許使用,如果不是不允許登陸。
看起來很不錯,RSA是安全的公開密鑰算法,并且秘鑰被D公司保存起來,除非你能賄賂D公司的員工獲得秘鑰,或者攻破D公司的網路,否則秘鑰也是安全的。因此攻擊算法和秘鑰是不現實的。那么,我們可以從協議的角度入手,看看有沒有弱點。
先簡單說一下RSA算法:
用上面的公式加密,假如消息是2,C = 27?% 33 = 29,密文就是29。解密的話就是M = 293?% 33 = 2。
回到剛才的軟件破解問題上,軟件產品中有e和n,如果能夠分解n我們就能夠構造d,也就知道了D公司的秘鑰,由于分解n是一個數學難題,我們不知道最優解很難解出答案。我們采用另一種方法,破壞協議,首先我們自己選擇2個大素數,構造另一個n,并分別求出對應的e和d。然后我們架設一個服務器,用來模仿D公司的驗證服務器。
我們侵入D公司的軟件,用我們自己的n和e替換D公司的n和e,這時軟件D會用我們的公開秘鑰加密用戶信息以及序列號,這段信息是無法送給D公司驗證的,因為D公司沒有我們的保密秘鑰,無法解密信息,因此這樣還無法完成軟件的驗證。
下一步,我們使軟件D將加密好的信息發送給我們剛才架設的模仿D公司的服務器,這臺機器擁有我們的保密秘鑰,它成功的解密信息,并直接構造一個驗證通過的數據塊,并用保密秘鑰加密發送給D軟件。D軟件用我們的公開秘鑰解密信息,驗證一切正常,并將這段信息保存到計算機中。每次D軟件啟動時,重新檢查是否存在驗證通過信息塊,并用我們設給它的公開秘鑰解密,解密成功后通過驗證,啟動D軟件。只要通過一次驗證,D軟件就無需再次與D公司服務器進行交互,因此完成了破解。
再次說明,算法,秘鑰,協議是一個密碼系統的組成部分,任何一個方面不安全,整個系統就是不安全的。當下,很多廠商對算法和秘鑰很關注,在算法的選擇與秘鑰的長度上下足了功夫。但他們的系統并不像他們想象的那么安全,因為他們忽視了協議。
好了,今天就聊到這,歡迎@我的email與我交流。
TedZhang2891@gmail.com
Posted onAugust 25, 2016 CategoriesCryptography TagsCryptography Leave a commenton Algorithm, Secret key and ProtocolRansomware Locky Analysis
?Locky的變種非常的多,這個樣本來自下面的Url,是最新的一種變種。?
這是程序在剛開始執行時與釋放了Image并替換了之后的對比,很明顯發生了進程替換,因此進行分析之前有必要把它內部釋放出來的image提取出來,分析這個image才能搞清楚它是如何做加密的。
?
?Locky存在一個未知的殼,IDA并不能檢測出這個殼,因為它的導入表等信息并沒有被破壞。它在運行起來后
,執行相當大量的垃圾代碼干擾調試,依據是在調試的過程中它進行相當多的寄存器操作但6個通用寄存器的值始終是0。
這這些垃圾代碼中隱藏著它獲取Kernel32.dll的Addr的邏輯,它會獲取Kernel32的BaseAddress并通過偏移計算出API的地址。
它會調用VirtualAlloc分配一塊可寫可執行的內存塊,大小是61BB,可執行意味著會釋放代碼到這塊內存區中,應該著重分析。(0x404587)
TextSegment中存在一部分未被IDA識別的func,位置在(0x402940)。這部分代碼的作用是將一些數據(在TextSegment中)釋放到剛才通過VirtualAlloc分配的內存中去。
GenerateShellCodes(PVOID?lpShellCodes,?BYTE* pDataInTextSegment,?DWORD?dwLen,?DWORD?dwHEX);
其中bl寄存器用來作為臨時存儲一個字節的寄存器,ebx寄存器用作計數器,它從函數參數中獲得Data的Length,然后遞減,如果等于0就跳出循環,esi是指向VirtualAlloc返回的內存地址。處理完畢后,會在指定的位置寫入一堆數據,這些數據還需要進行異或處理才能變成真正可以被執行的代碼。
以下是沒有被處理的數據,只是單純的填充了緩沖區。
它的解這部分Code的算法如下:
其中dwHEX是這個處理函數的第四個參數。解完了以后的數據如下:
下面已經完成了部分ShellCodes的釋放,接下來將跳轉到ShellCode去執行代碼。
這段ShellCode的目的釋放一個被壓縮的PE文件,并加載這個PE文件,整個過程沒有釋放任何文件出來,無法被監控軟件發現,釋放PE文件使用了非常精巧的方式加載到當前進程中,并完成了進程替換。
被釋放的ShellCode本身還有很多代碼沒有釋放出來,在ShellCode的Offset=247處有一個func用于釋放ShellCode中的代碼(釋放不太嚴謹,應該是通過一些異或操作把原來不是代碼的數據轉換成代碼)。
這個操作會將ShellCode頭部的一些代碼釋放出來。在后面經過調試發現,這個func會被反復的多次調用,用于釋放存在ShellCode中的數據(轉換成可以被執行的CPU代碼)。因此這里應該是釋放ShellCode自身代碼的邏輯。
sub_1D1020用來獲得Kernel32的BaseAddr:
GetModuleHandle
?
?
再被釋放的ShellCode+1020處有一個Func(SC_GetModuleAddr)它用來根據傳入的HardCode得到對應的Module的基地址,例如Kernel32.dll Advapi32.dll等。
LPVOID SC_GetModuleAddr(DWORD dwHEX);
對應的在ShellCode+1122處有一個Func(SC_GetFuncAddr)它用來根據傳入的HardCode得到對應的func的基地址,例如Kernel32的GetProcAddress等。
PFUNC?SC_GetFuncAddr(DWORD?dwHEX,?LPVOID?lpModuleBaseAddr);
如下圖:
ShellCode會調用這段代碼GetProcAddress,并傳入Module的基地址以及要獲取的函數名。這里函數的名字被嵌入到了ShellCode中,被解釋稱代碼了,需要重新解析成字符串。注冊服務,隱藏自己的行為。
后面的邏輯大致符合如下描述:
例如:用同樣的方法獲得了EnumServicesStatusExA的函數地址。
這里調用GlobalAlloc(ShellCode+001D0686)在堆上分配內存分配的內存用0初始化,大小是0x4214。(可能是一個結構)因為被釋放掉了。
這里又使用API分配了一塊內存,大小是1799C。頁屬性是可讀寫不可執行。這個函數的參數很值得注意,居然是當前進程的基地址。
這里從當前的進程中釋放了一堆壓縮數據到剛才分配的內存中,然后又繼續調用VirtualAlloc分配了一塊大小1AE00的內存,頁屬性依然是可讀寫不可以執行,這塊內存用于解壓縮剛才釋放的壓縮數據。
程序接下來調用了RtlDecompressBuffer這個Undocument的API。用于釋放壓縮的數據到指定的Buffer中。釋放出來的文件是一個PE Image。(ShellCode+318E)
把這個PE dump出來之后可以從導入表中發現一堆Crypt相關的API,它就是真正加密用戶數據的PE。
到這里后面的分析都與Ransomware沒有關系了,只要能拿到內部的加密數據的文件進行分析就可以了。下面是這段ShellCode作為加載器還有一些什么具體行為的分析。
這里釋放了壓縮數據所占用的內存。
這里打開了通過獲取當前殼進程的ImagePath,然后調用CreateFile打開文件并計算文件的Size。
計算出來的文件大小是31000,然后調用VirtualAlloc分配相同大小的空間。下面是VirtualAlloc的參數。可讀可寫不可以執行。
?
然后調用ReadFile從當前Image的文件中讀取數據,一次讀取完畢。
然后關閉文件。
現在內存中有2份PE文件了,一份原始的殼的Image,還有一個被脫殼后真正做加密工作的Image。
上面是原始殼的PE數據
上面是被脫殼后的PE數據
判斷它最后會還原回去,不然沒有必要保存一份在內存中。這樣可以不用釋放文件,監控軟件監控不到,非常精明的設計。
這里調用VirtualProtect來對0x400000位置的當前Image設置可寫權限,要開始覆蓋當前的數據了。
開始將Image的內容清空:
然后從debug021:001D17E9開始的一大段代碼用于填充新數據到當前的Image中去。好大的一段代碼,因為內存中的PE的Offset與加載到內存后的Offset不一樣,所以需要小心的分段加載PE數據,這里的代碼類似一個進程加載器。
寫完后的數據已經與上面釋放出來的PE一樣了(對照0x200000地址數據),這里發生了進程替換,它肯定還要找到新的PE的EntryPoint開始新的調用。
調用RtlZeroMemory把釋放出來的在內存中的做加密的PEImage擦除掉。
然后調用VirtualFree釋放掉內存(清理現場)。
?
現在調用VirtualProtect把在0x400000位置的新的PEImage頁屬性設為只讀。
PEHeader,第一個頁面。繼續設置代碼段為可執行可讀。
到這里,新的PE已經加載完畢了,應該可以準備執行了。
這里會把一些API用到的都取出來保存在棧上,后面用。有一些,不過我最關注的是CreateThread,然后它會清理ShellCode,清理犯罪現場。通過使用下面的API調用。清理的過程分幾個階段,頭部的ShellCode還保留,只是把除了頭部以外的都清理成0.
可以看到從F6之后都是0了。
然后調用VirtualFree把這整段Code刪除掉。到這里ShellCode的使命已經完成,簡單來說它的目的就是將內部的一個PE釋放出來,并替換當前進程,并把當前進程的數據緩存到內存中。
然后,程序會執行CreateThread,執行新的PE代碼。
405152就是新的進程的EntryPoint,可以通過對提取的PEImage的分析佐證。
至此,脫殼完成并且成功的分析到了這個殼的運作機制。
Continue reading?Ransomware Locky Analysis
Posted onAugust 7, 2016 CategoriesTech Blog TagsAssembly,?C++,?Reversing Leave a commenton Ransomware Locky AnalysisRansomware Cerber Analysis
Cerber是一個可執行程序,它的感染后行為沒有CryptXXX這么隱蔽,可以說分析它的行為并不困難,但是它對內部數據的保護比CryptXXX做的好。例如:?我可以寫一個簡單反向算法就可以將CryptXXX中的所有加密數據提取出來,但是對于Cerber它顯然在對內部數據的保護上下足了功夫,但這并沒有阻擋我提取出它所有的內部數據。下面會詳細的分析它如何保護內部數據的邏輯。
還需要了解的是,Cerber是C語言實現的,并且它并沒有使用微軟的運行庫,換句話說它沒有采用微軟的編譯器,很有可能是Intel的編譯器或者是Cross-Compiler的gcc,我不確定。能夠肯定的是2點,沒有采用微軟編譯器,沒有使用C++標準庫。
2016-7-8
更新一下,我原本通過分析CryptXXX,感覺CryptXXX的邏輯設計的非常巧妙,它的感染方式被分散到很多個Export函數中。現在通過最新對Cerber的分析,發現Cerber也是設計的非常巧妙,它去做一些具體的操作時會調用其它命令行進程隱蔽自己。可以看出作者對windows系統的理解程度非常之深,應該是個高級玩家。
CryptXXX有各種設計精美的Blackmail,它們是Html,Bmp,Txt,有2個web服務器節點,與web的交互非常頻繁,相互做backup。維護這些都需要人員和精力,它很像是一個團隊在做。
Cerber更像是一個人在做,它的勒索頁面很簡單也不美觀,它也沒有與web交互,應該是沒有精力維護web站點,或者更好的隱藏自己。但作者應該是個windows資深玩家。
為什么這么講,因為我發現cerber會給自己提權,也會去check UAC當前的狀態,還用了很多windows的內置變量(%xxxx%)這種形式的,我感覺作者以前就是做病毒的。
Global Data Structure
| Named | ImageOffset | Size | Description |
| g_lpModuleFullPath | 0x41B760 | 4 | Cerber所在的當前目錄加文件名 |
| g_lpModulePath | 0x41B440 | 4 | Cerber所在的目錄 |
| g_pAddrContainMeta | 0x41B43C | 0x124 | 一個數據結構包含Meta信息 |
| g_hEvent | 0x41B438 | 4 | 全局Event,手動Set初始無信號 |
| g_szCerCoProtMutex | 0x41B648 | 4 | 全局Mutex的name |
| g_hHeap | 0x419A50 | 4 | 全局對象,私有堆句柄 |
| g_dwCurrentPID | 0x419A54 | 4 | 當前進程ID |
| g_dwImmMap | 0x419A3C | 0x10 | 一組常量,用于計算字符串 |
| g_bEncryptDone | 0x41BF61 | 4 | 加密是否完成標志 |
| g_hCryptProv | 0x41A168 | 4 | cryptographic service provider |
| g_szModuleFileName | 0x41B970 | 4 | 當前進程的文件名 |
| g_tedGlobalMeta | 0x41A0F8 | ? | 全局Meta數據結構 |
| g_bMultiThread | 0x41A12C | 4 | 全局標志,是否多線程加密 |
| g_dwMaxBlockSize | 0x41A14C | 4 | 最大塊的大小 |
| g_dwMaxBlocks | 0x41A12D | 4 | 最大塊數量 |
| g_dqMinFileSize | 0x41A138 | 8 | 最小文件大小 |
| g_dwRsaKeySize | 0x41A154 | 4 | RsaKey的大小 |
| g_Cerber_key_place | 0x41BEB8 | 4 | ? |
| g_pJsonObject | 0x41A134 | 4 | 全局json對象 |
| g_pfnIsWow64Process | 0x41BFFC | ? | API |
| g_InitEnvpPrograms | 0x41BFF8 | 4 | 判斷以下4個全局變量是否初始化 |
| g_envpPCPFile | 0x419AA4 | 4 | %CommonProgramFiles% |
| g_envpPCPW6432 | 0x419AA8 | 4 | %CommonProgramW6432% |
| g_envpProgramFile | 0x419AAC | 4 | %ProgramFiles% |
| g_envpProgramW6432 | 0x419AB0 | 4 | %ProgramW6432% |
| g_dwStopReason | 0x41952C | 4 | 程序不執行的原因 |
| g_bStartNoParam | 0x41BF60 | 4 | 標志是否沒有命令行參數 |
| g_lpNewExtension | 0x41BC98 | 4 | 加密后新的擴展名 |
| g_szWatchdogMutex | 0x41BB78 | 4 | Watchdog的mutex |
| g_byteDebug | 0x41A12E | 4 | 全局的調試標記,打開可以在DbgView中收到調試信息 |
| g_byteNetWork | 0x41A12F | 4 | 設置為1加密網絡文件 |
| g_AvgReadBytes | 0x41A150 | 4 | 平均每個塊的Bytes數量 |
Meta數據機構
包含一個初始化的臨界區變量,一個當前進程PID,當前進程的TID。
Export Function
| funcName | ImageOffset | Description |
| Start | 0x406BED | EP 沒有什么特別,PE的主入口 |
| RSA Key |
| LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUF2a3R5NXFocUV5ZFI5MDc2RmV2cAowdU1QN0laTm1zMUFBN0dQUVVUaE1XYllpRVlJaEJLY1QwL253WXJCcTBPZ3Y3OUsxdHRhMDRFSFRyWGdjQXAvCk9KZ0JoejlONThhZXdkNHlaQm0yY29lYURHdmNHUkFjOWU3Mk9iRlEvVE1FL0lvN0xaNXFYRFd6RGFmSThMQTgKSlFtU3owTCsvRytMUFRXZzdrUE9wSlQ3V1NrUmI5VDh3NVFnWlJKdXZ2aEVySE04M2tPM0VMVEgrU29FSTUzcAo0RU5Wd2ZOTkVwT3BucE9PU0tRb2J0SXc1NkNzUUZyaGFjMHNRbE9qZWsvbXVWbHV4amlFbWMwZnN6azJXTFNuCnFyeWlNeXphSTVEV0JEallLWEExdHAyaC95Z2JrWWRGWVJiQUVxd3RMeFQyd01mV1BRSTVPa2hUYTl0WnFEMEgKblFJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg== |
解密Cerber的內部數據非常麻煩,它用了很多層技術防止提取出它的內部數據,這些數據雖然可以在調試時動態的跑出來但是對于靜態分析代碼效率太低下。因此我實現了這個解密算法。
| The assembly list was implemented by me |
| ?decrypt |
| The file be generated automatically |
| ?GenerateCodes |
下面是解密后與解密前的數據,解密之前的數據我并沒有 dump出來,因為我累計在這部分已經花去了超過24個小時(包括編寫匯編算法,抽取腳本,分析等),我不想再花更多的時間完善它(后來強迫癥犯了,提供了一個diff版方便看代碼)。
| Encrypt | Decrypt |
| ?encrypt | ?decrypt |
通過分析decrypt.txt文件,可以發現cerber隱藏的很多細節,包括我并沒有發現的細節。因此解密內部數據這項工作應該更早的展開。
通過閱讀解密后的數據,可以了解它使用了那些技術。挑選一些主要的寫在下面。
| select * from %s | 難道有內部數據庫,這是不可能的,有可能是應用了WMI。 |
| FirewallProduct | 這個好像不言自明 |
| AntiVirusProduct | |
| cerber_startup_fake_service | Fake? |
| wireshark.exe | 看起來會反調試反監控,但是事實上好像沒有work |
| dumpcap.exe | |
| ollydbg.exe | |
| SOFTWARE\Oracle\VirtualBox | 看起來是虛擬機檢查,不過貌似也沒有work |
| VBoxMouse.sys | |
| VMWARE | |
| SOFTWARE\VMware, Inc.\VMware Tools | |
| CERBER_WATCHDOG_PROTECTION_MUTEX | 這些串放到這里讓人一看就知道他的行為,如果我寫我會Debug和Release分別放置不同的值 |
| CERBER_CORE_PROTECTION_MUTEX | |
| CERBER_STATISTICS_PROTECTION_MUTEX | |
| bcdedit.exe
/set {default} recoveryenabled no | 這個…?很賤 |
| Software\Microsoft\Windows\CurrentVersion\Run | 看起來要自啟動 |
| AutoRun | ? |
| ? | ? |
這是一個Json格式文件,被解密后釋放出來。是一個配置文件
| The file type of JSON which Extracted from decrypt |
| ?config |
這個Json后面會釋放出3個勒索文件出來
| ?help_cerber注意,這個包沒有病毒,但是在Windows10上會被Windows Defender警告,因為它檢測到模式匹配 |
主程序行為詳細分析。
先看一下下面鏈接的代碼:
start
Cerber程序被執行后,會先解密一個內部數據并計算一個四字節的值存放到全局變量中。然后通過當前進程的Pid創建一個全局唯一的Mutex命名字符串并用它構造命名的Mutex對象(MSCTF.Shared.MUTEX.575906ab)。
緊接著將當前全路徑提取出來,同時將文件名去掉只保留路徑,并把這兩個值保存到全局變量中去。
接著,開始嘗試啟動新的cerber進程。這個過程比較隱晦,它先枚舉系統所有的窗口站,并查找有沒有交互式窗口,如果找到了,將這個交互式窗口鏈接到當前進程,然后注冊并啟動一個Windows Service,這個服務只做一件事情,就是不停的嘗試再啟動一個Cerber進程。如果啟動服務沒有成功,那么就直接創建一個Cerber進程,這個過程也很麻煩,它會先獲取當前活動的SessionID,并進而獲得用戶的Token,用用戶的身份去創建一個新的Cerber,無論兩種方法只要創建成功,那就不會返回,進程退出。
TED_CreateCerberAgain
CB_CreateThreadOnSCM
TR_CreateProcessUtillSucess
TED_CreateProcess
wrap_CreateProcessAsUser
這是程序的Self-Spawn流程。
?
?
?
解密內部數據流程
在進入真正搞破壞之前,程序還有些準備工作要做,其中就包括從自身的資源文件中提取一個Json文件出來,并用一個JsonParser生成一個Json對象出來。這里有可能使用了第三方的Json解析庫,我不確定是Jsonc還是Jansson。因為我已經把Json內部的一些方法分析出來了,才判斷出那是在構造一個Json對象,因此如果早一點介入調查第三方庫會效率更高一些。
Ok,回到初始化JsonObject的部分,首先程序會把自己Image中的資源段文件讀取出來,然后通過自定義的算法計算出Json文件的串并Hold在內存中。下面我來分析一下這個過程。
首先,它會用一個神奇的算法解密出制定的字符串,這個神奇的算法被普遍應用于解密字符串在這個程序中(我已經實現了這個算法,1000行匯編代碼)。
前面我說過cerber對內部數據的保護比CryptXXX做的更好就是由于它的解密內部數據的算法需要固定3個值才能解密出對應的數據,而CryptXXX的算法是固定,只要有輸入就可以解密內部數據。
PCTSTR __cdecl TED_MakeRealTString(BYTE *byteUnknown, int nResSize, DWORD dwHex, char bUseWStr);
TED_MakeRealTString
上面是解密內部數據的func原型,其中需要固定的數據有保存在rdata中的字節,字節長度,一組解密用的key(4字節16進制數據)。
這個解密函數非常復雜,首先如果是第一次調用需要要進行一下初始化,工作包括將內部數據的鏈表頭指向空,初始化meta信息,meta信息中包括了臨界區變量,進程PID,TID。
然后,根據傳入的rdata中的字節和字節長度計算一個CRC,然后遍歷鏈表,如果匹配到了相同的CRC,就把當前鏈表中的item取出來,返回保存在item中WStr或者Str,這個是根據上面原型的第四個參數指定的。
如果通過CRC沒有找到數據,那說明數據還沒有被緩存,執行計算過程。首先為鏈表分配一個新的節點,并把CRC放到新的item里面,然后開始執行計算。
計算的過程也很復雜,首先在棧上構造一個ASCII碼表,然后把輸入的字節解釋稱0-255的整數數組,并用一個會自動循環并且最大值不會超過輸入字節長度的索引值不停的循環的取出輸入數據中的值并當成整形與ASCII碼表中整數計算出一個可變的數值,這個數值會與ASCII碼表中的數據交換,經過這樣循環的處理,會因為輸入的不同得出不同的結果。(復雜代碼用語言描述太難了,還是看代碼吧)
TED_CalculateSpecialString
然后,它用被交換,或者說是擾亂過的ASCII碼表與輸入的加密后的數據做異或操作(一個復雜的異或操作,加減偏移什么的),計算出Real的數據出來。
我這里講的是它解密出了一塊Json的數據,下一步還要解析這個Json數據把它轉換成內部的數據結構。
鏈表在這里的作用是緩存內部數據。
構造Json Object流程
在生成Json對象之前首先要了解Json的基本數據類型,json共有5種基本數據類型,它們是邏輯值,字符串,數值,數組和對象。其它信息如果想了解就去google一下吧。
程序首先構造了一個json對象:
TED_GetJsonMem
然后把這個對象和json串一起送入Parser中,Parser根據輸入的Json會去填充這個JsonObject。這像是某一種第三方Json Parse庫,因此我沒有必要繼續分析它的作用。看一下就知道是在解析Json。
JSON_Parse
看的出來,這個JSON_Parse被多個函數調用,但我們這里只需要關注Start這條線就可以了。
因為他上面調用它的生成Json對象的方法是一個通用方法,程序的其它位置會調用它來生成其它的Json對象。
?
?
TED_GenerateJsonObject
TODO:JsonStructure
JsonObject被初始化完畢后,在程序中提取配置信息等操作就由相應的操作函數來完成。這個對象是在內存中分配出來的,因此在全局數據區是發現不到它的。這個對象會再接下來的程序生命期中被多次使用到。
配置全局變量
接下來程序需要初始化一些全局數據成員,通過從Json中提取出key對應的value。這些全局變量是加密用戶數據所需要指定的配置信息,它們包括:多線程加密標志,最大塊尺寸,最大塊數量,最小文件尺寸,RSAkey的size。
?
接下來,程序要進行一些加密前的檢查工作,它先要去檢查一下指定的注冊表位置,第一次執行時這個位置是空的,因此這段code會直接返回,還不會開始加密用戶的數據。
“Printers\Defaults\{89873163-8DC1-7563-E594-6622BDBB1978}”這個注冊表位置會被檢查,如果不是第一次運行,在這個位置會有幾堆數據,這些數據包含是一個被序列化的Json對象,一組RsaKey,那么程序的就會解析這個Json對象,解析后的Json對象指針會被放到g_pJsonObject(看最開頭有列表)這個位置,并且程序會提取出RasKey并計算一個hash并放到全局變量中去,完成這些工作后程序返回。
上面這個過程就是加快操作的功能,如果從注冊表中提取不到Rsa信息,那么就從全局的json對象中構造,首先從json對象中提取一個用來加密的Key,這個key實際上可以從資源文件中提取出來(最上面有列出)。然后把這些信息寫到注冊表中,讓下一次操作可以從注冊表中提取數據。整個的流程如下:
TED_GetCerberKeyFromRegister
完整的加密配置準備工作的流程如下:
TED_PrepareEncrypt
CoreProtectionMutex
接下來程序初始化了一個全局mutex,這個mutex被Cerber當作Core Protection Mutex,然后創建了一個。
然后還會創建一個全局的Event。
GlobalMeta
然后程序開始分配一些內存,為創建全局的數據結構,這個結構包含一個meta信息,一組句柄,還有標記句柄數據中保存了多少句柄的數值。Meta信息是另一個數據結構,包含當前進程Pid,線程的tid,以及一個臨界區對象。
struct tedStruData {
int dwCount;
tedGlobalMeta meta;
HANDLE* pObjHandle[64];
};
緊接著,他會創建一個線程,并把這條線程加入到全局數據結構的句柄數組中去。這條線程啟動了之后會創建一個隱藏的窗口,這個窗口的窗口過程接收END_SESSION消息,當收到這條消息后,會對全局Event設置信號,這個事件是為了保證系統中其它的Cerber進程能夠有效的退出。
CB_WindowProc
接下來,程序處理一下命令行參數,因為程序是命令行應用,第一次執行時不帶參數,當啟動后,會釋放程序內的加密數據,這些數據包含了很多命令行參數。當cerber spawn一堆cerber的時候它們都會帶一些特殊的參數進來,在這里,程序的行為將發生變化。它的核心邏輯就在這里了,分析這部分代碼還是很有收獲的,提權功能,關閉UAC功能,反AV產品功能,反虛擬機功能,判斷是不是Admin功能,在64位操作系統上禁用文件系統重定向等在這里被大量使用,也導致了程序在不同的平臺上行為不一樣,學習到很多病毒的做法。也正是因為分析了這部分代碼,我才感覺作者在windows領域浸淫已久,很牛逼。這些技術CryptXXX并沒有出現。
?TED_ProcessCommand
ProcessCommand里面的內容非常多,一一展開很費心力,我們先跳過這里,看看后面做了什么,最后在回過頭看這部分。
2016-7-12 back to here
這個Func被重命名為TED_ProcessCommand,因為程序會根據不同的執行流選擇做具體的操作,而具體的操作都是通過創建自身的一個副本,并提供一些不同的參數從而實現了同一個程序的不同行為,因此代碼中需要有一個解析并處理Command Argv的地方,這個地方就是ProcessCommand函數。
這個函數被調用后,首先嘗試啟動調試權限,然后會判斷當前的啟動路徑是不是一個特殊的路徑(C:\Users\Administrator\AppData\Roaming\{64E3EBE0-DAE1-314A-731D-B7440DC4E389})在我機器上如上述路徑,如果不是,就把程序拷貝到這里面去,并創建一個Process再次執行,當前Process退出。
如果是的話,接下來會去判斷傳入的參數數量,進入不同的if塊中,這里有3種選擇,沒有參數,有2個參數,以及超過3個參數的條件。
沒有參數的情況是直接執行本程序,這時候他會去簡單當前是不是Admin的權限,如果是它嘗試用管理員的身份重新啟動cerber并帶上-eval {dwCurrentPid}參數,但是它在這么做之前會先檢查一下UAC的狀態,如果UAC沒有工作,那么它會這么做。如果UAC正在工作中,它會用cmd去啟動當前的Process的鏡像文件。{cmd /d /c start c:\tmp\cerber.exe –eval pid}.?這個原因導致調試困難。
如果程序啟動后被傳入了2個參數,這里有好幾個可選項(eval,shadow,watchdog,stat,antiav)。如果shadow,那么將刪除卷快照,并將啟動恢復設置為no。
TED_PC_shadow
如果參數是watchdog,程序先初始化watchdog的專用mutex,先啟動一個watchdog創建線程,創建watchdog process,然后這個線程會每隔1秒監控watchdog process是否始終活動,如果超過4秒watchdog process仍然活躍,那么久殺了這個process再創建一個,這么做是不是為了防止watchdog process被輕易的發現?
TED_PC_watchdog
TR_Watchdog
如果不是第一次運行,那么watchdog可能已經被創建了,程序打開watchdog的全局mutex,如果發現mutex的句柄不存在,說明程序可能出現了問題,例如被調試等,那么程序會繼續檢查Core Protection Mutex在不在,如果也不存在,那么殺死所有在系統中的cerber進程,隱蔽它的行為。
如果參數是antiav,它會檢查是不是具有管理員權限,然后執行AntiAVProduct函數,內部邏輯異常復雜,涉及到權限,SID,Boot等設置,放代碼讀下不解釋了。
TED_AnitAv
如果參數是stat,那么它開始執行真正的加密工作,在開始加密之前,需要進行一些準備工作。首先把json中的encrypt對象中的數據全部load到內存中,包括一組擴展名列表,然后打開注冊表Printers\Defaults\{89873163-8DC1-7563-E594-6622BDBB1978},在這個位置把RsaKey取出來,如果取不到,它會先從內存中把Key給弄進去,下次就會取到Key了。
然后,進入Encrypt函數,開始執行真正的加密工作。這里有個debug標志,如果把這個標志打開,會把一些調試信息寫到文件中,可能得到一些有效的信息。如果json文件中指定多線程加密,那么就會根據指定的數量創建多條線程進行加密。
TED_Encrypt
TR_DoEncrypt
TED_EncryptData主要加密函數,入參是路徑+文件名,加密的核心算法就在這個func里面
在加密之前還有一寫邏輯用來枚舉所有驅動器,網絡驅動器的,把需要加密的磁盤遞歸的加入一個鏈表中(這個鏈表數據結構很復雜,我很想弄出來,不過由于太耗費時間放棄了)。
TED_EnumDevice?枚舉所有可以訪問并有寫權限的驅動器
TED_RecursionFolder?遞歸的將所有文件加入鏈表
退出部分
經過上面的邏輯后,程序會拋出很多的Process,函數返回后用戶的數據也已經被加密了,剩下的部分是準備推出部分,這部分不是特別重要,但是它卻展示了程序內部數據結構的構成,以及如何管理大量的句柄。
這次先放代碼,看后分析
TED_PrepareLeave?主調
TED_WaitAllObject?具體
主調函數先進入臨界區,然后調用waitAllObject去等待所有的句柄相應。句柄中大量的process和thread。WaitAllObject函數已經被我分析好了,這部分代碼很有意思,理解它需要有一些領域知識,如果沒有寫過高并發服務器的話,估計很難看得懂這部分代碼(指未翻譯之前的代碼)。
首先有一個背景知識,就是一個WaitForMutliObject句柄組上限是64個,超過這個數量就沒法管理了(不信可以去google)。那么如果需要在wait中管理超過64個句柄怎么辦,有一個辦法,類似虛擬地址映射(頁表,頁目錄),可以創建一些管理線程,這些線程每一個管理另外64個句柄,這樣可以無限放大(二級,三級這樣)。有了這些知識再看代碼就清晰了,可以讀一下我翻譯過的代碼。
繼續往下,都是一些釋放操作沒什么好說的。最后如果加密完成,它要通知其他系統中的cerber進程退出,這些進程可能是上面wait不到的。反正要退出了,這里也比較暴力,枚舉所有cerber然后調用Terminate。后面還有一個force的做backup,是通過cmd.exe調用taskkill來殺。最后在自殺。放一個小片段吧。
SR_start
?
Posted onJuly 15, 2016 CategoriesTech Blog Tagsmalware virus reversing Leave a commenton Ransomware Cerber AnalysisRansomware CryptXXX Analysis
Global Data Structure
| Named | ImageOffset | Description |
| pConfig | 0x4259A0 | 全局配置信息,窗口句柄,全局標志位,當前進程是否是Svchost本身等。 |
| szMutexCrypt | pConfig+0x100 | 全局互斥對象的名稱, |
| szModuleFullPath | pConfig+0x121 | Crypt.dll的當前完整路徑 (C:\Tmp\Crypt_Patched.dll) |
| dwOSVer | pConfig+0x26B | 操作系統的主版本號和次要版本號 |
| szModuleDirectory | pConfig+0x663 | Crypt.dll的當前所在目錄 |
| dwPlatform | pConfig+0x26C | 64 or 32 代表不同的平臺 |
| dwWebServerID | pConfig+0x26A | 0x13524C90 or 0x40BBAA5D |
| ? | pConfig+0x563 | ? |
| ? | pConfig+0x8A5 | ? |
| szWebserverIp | pConfig+0x26A | “144.76.82.19” or “93.170.187.64” |
| lpPubKeyString | 0x4272B8 .BSS | 運行時釋放到這里 |
| dwRandomSeed | 0x423008 .data | 隨機數種子,每次運行都不一樣 |
Export Function
| funcName | ImageOffset | Description |
| DllEntryPoint | 0x422028 | 這個入口會根據當前執行的進程名稱來判斷是第一次被調用(rundll32.exe),還是之后的偽裝調用(svchost.exe)。如果是第一次調用,做一些初始化的工作,將rundll.exe從系統目錄拷貝到當前module目錄中并重命名為svchost.exe,然后用偽裝的svchost加載crypt.dll并調用MS111。這回導致新進程再去load crypt.dll進而反復啟動新的進程,這是系統中出現一大堆svchost.exe的原因。
0x41FFFF位置打patch 90 81 C4 28 00 00 00讓它無法啟動好多的進程方便調試。只要不是svchost啟動的,暫時就不會干不好的事情。 |
| MS111 | 0x421E1C | 首先判斷一下是否已經在入口中執行了偽裝svchost加載模塊的流程,如果沒有創建一個新的進程調用MS112。如果執行了,先等6分鐘,然后啟動2條線程TR_ProcessWindow和TR_PerformCheck,然后進入消息循環等待進程退出的消息。 |
| MS112(Encrypt) | 0x4213C0 | 首先從全局信息中取出互斥對象的名稱,然后打開這個互斥對象。根據后面的分析這個互斥對象應該是每個Process都是唯一的,沒有確認。成功創建了Mutex后先組裝一個神秘的字符串沒有調,然后根據一個標志來決定是否創建一個Process去執行MS113. 然后檢查全局數據結構,取出一個特殊文件的路徑,判斷這個文件存在不存在。 |
| MS113(FillConfig) | 0x421384 | 這個函數只做一件主要的事情,就是去webserver上面下載一個危險的dll。
https://www.proofpoint.com/tw/threat-insight/post/cryptxxx-ransomware-learns-samba-other-new-tricks-with-version3100 |
| MS114 | 0x421E0C | Set一個全局標志后,調用MS112. MS112會根據是否設置了這個全局標志來決定是否調用MS113. |
| MS115(Deamon) | 0x421254 | 每隔200ms檢查一下系統中是否存在WerFault.exe(CrashReport),如果存在就殺掉這個進程。同時檢查當前進程的父進程是否已經退出了,如果退出了就用系統中保存的偽裝的svchost加載crypt.dll并調用MS112,然后退出。 |
?
| RSA Key |
| —–BEGIN CERTIFICATE—–
BgIAAACkAABSU0ExAAQAAAEAAQBfFTOUbZiP6u9PppNyTSXM+Y5W9pEcKe68HJYq dLYpXL+XCzXTUgsSRJ1iNmXqrhUEqz3hOi93Bw53U28gvnJTHRboA32xzli688MQ eJz7kis1d2G+o8bz+VHO/7qsX+jlBLkP86a6+MYvvhZW+Z0HcsZbMjn6/yCgbhF8 BhpvuQ== —–END CERTIFICATE— |
?
寫了個程序把image中的所有加密字符串解密出來。
| Encrypt | Decrypt |
| ?EncryptString | DecryptString |
?
這是一個html,被解密后釋放出來。是一個勒索頁面
| The blackmail html which Extracted from decrypt |
| ?blackmail |
DllEntryPoint
?DllEntryPoint
MS111?主要邏輯
MS111
TR_ProcessWindow:遍歷所有的隱藏窗口,并且不屬于系統,并查找窗口所屬的進程名,如果match到了一個特殊的進程名,就給他發一個消息讓他恢復出來。
TR_ProcessWindow
TR_PerformCheck:創建一個Process開始調用MS112
TR_PerformCheck
如果不是Svchost.exe創建新的Process開始調用MS112,進程退出。
執行的Commandline如下:
C:\Users\ADMINI~1\Desktop\_00310~1\svchost.exe C:\Users\ADMINI~1\Desktop\_00310~1\_00310000_pe.dll, MS112
MS112?主要邏輯
MS112
根據最新的分析,這個Func是用來加密用戶文件,并顯示勒索信息的。
首先從全局信息中取出互斥對象的名稱,然后打開這個互斥對象。根據后面的分析這個互斥對象應該是每個Process都是唯一的,沒有確認。成功創建了Mutex后先組裝一個神秘的字符串,根據是否已經獲得了全局數據結構來決定是否創建一個Process去執行MS113,MS113會去webserver請求全局數據結構.
然后檢查全局數據結構,取出一個特殊文件的路徑,判斷這個文件存在不存在。
如果存在,就把文件中的內容讀取到一個Buffer中,這個Buffer是有結構的,它的0x20C偏移是0x3E8這是一個Marker。
如果這個文件不存在,那么請求webserver給它發送一個這份文件內容的數據。它會初始化一個請求數據結構,其中前4個字節是請求的command,最后四個字節是marker。中間的內容根據全局數據結構填充。
請求的數據使用Https安全協議加密,它有兩個webserver(”144.76.82.19″ or “93.170.187.64”),當一個請求失敗時會嘗試第二個webserver。
首先發送校驗包,用于服務器確認通信合規。先發送4個字節包含通信包長度的頭包,然后繼續發送包含實際驗證數據的包。
TED_SendAuthenticationPackage
實際上在發送這兩個包之前還發送了一個52字節的包,
發送完驗證包后,開始接受返回數據。協議相同,先接受4個字節的包,確認要返回的實際數據的長度。然后分配一個帶結構的Buffer,用來接收返回的數據,如果返回的數據太多,就一次只接收8k的數據。接收成功后把這些數據壓縮保存到Buffer中。
接收的第一個包是返回的校驗包,與先前發送的校驗包大小一致。校驗包的前4個字節是命令,第4-8個字節大概是一個唯一碼,并把這個唯一碼寫到全局數據結構中去。然后開始接收真正的數據。并把265個字節寫到全局數據結構中。
TED_RequestForSpecialInfo
然后,接著從全局結構中取回Buffer構造一個新的數據結構,數據結構的前265字節是剛才請求回來的數據(這個數據在后面分析是一個PublicKey),接著4個字節是一個flag,最后四個字節是0x3E8。中間是0。數據結構的大小是0x210。通過從全局數據結構中取一個字符串,并使用上面的Buffer創建這個特殊文件。
SR_WriteSpecialFile
接下來開始解密很多的本地加密字符串,然后組裝在一起。后面分析到這實際上是在組裝一個勒索的html文件。用瀏覽器加載這個html。
中間會刪除一些文件,同時創建一個Process并向這個Process注入一段代碼執行寫文件的操作。
TED_CreateAWriteFileProcessThenExit
最后它會創建一個Windows窗口,這個窗口的尺寸等同于客戶的屏幕大小,并將信息(大概是勒索信息)打印到窗口上。
這部分會根據是否有全局的窗口句柄來判斷是否將信息打印到用戶屏幕上去,或者是寫入到一個Bmp文件中去。
SR_PrintScreenAndSave2File
之后會等待WM_QUIT消息,隨后釋放Mutex退出進程。這個Mutex應該是為了防止重入,進而在用戶屏幕上繪制太多的信息。
0x41A77C應該是打印在用戶屏幕上面的勒索信息,這個function涉及了大量的加密字符串的解密工作。
MS113主要邏輯
這個導出函數的主要邏輯是去Webserver上面下載數據并生成一個邪惡的dll。它首先會去嘗試連接”144.76.82.19″的443端口SSL,如果這個server訪問失敗還有一個后備server地址是 “93.170.187.64”。
連接并校驗成功后會先發送一個請求文件大小的包,根據請求到的文件大小創建一個緩沖區,并接收數據,完畢后會把這個數據寫成一個image文件存放到本地磁盤上。
TED_DownloadStillerx_dll
下面是卡巴斯基對這個dll的分析介紹
StillerX
In order to further monetize the infections, CryptXXX downloads a DLL which acts as a credential stealing module. Internally referenced as “stiller.dll”, “stillerx.dll” and “stillerzzz.dll”, this DLL works as a plugin, but can also be used as a standalone stealer. The stealer, like the ransomware, is written in Delphi, and uses the object-oriented capabilities offered by the language. Its relatively large size on disk (around 1.2mb) is due to the static linking of several third party libraries such as DCPcrypt used for retrieving and decrypting locally stored credentials.
https://www.proofpoint.com/tw/threat-insight/post/cryptxxx-ransomware-learns-samba-other-new-tricks-with-version3100
我嘗試下載這個dll但是失敗了,我不知道是不是webserver端的程序修改了還是什么其他原因。當我請求文件大小的時候webserver返回了一個比代碼中size還要大的尺寸,以至于程序總是沒有進行實際的請求工作就退出了。
我Patch了個size,提供了一個更大的size,第一次請求實際的數據時能夠得到數據,但是第二次繼續請求的時候沒有任何數據到達。因此實際獲取的數據長度與請求得到的文件長度不同,程序沒有繼續構建這個dll出來。Webserver邏輯變更也是有可能的,所以我無法釋放出這個文件加以分析。
MS115主要邏輯
這個導出函數的工作就是monitor WerFault.exe,不停的殺這個process。然后判斷它的父進程是否已經退出了,如果退出的話創建一個Process用偽裝的svchost去loadCrypt然后調用MS112。看起來是一個守護進程。
MS115_TerminateWerfault
加密用戶數據的邏輯
這個邏輯主要在MS112中,從下面這個圖可以看出來它會用隨機算法build一個隨機串出來,這個隨機串用來給文件加密,這樣它就不需要調用其它加密的API,保證了它的加密速度。Build這個隨機串在生成勒索信息和真正加密用戶文件中都有用到。當然它還會每隔8191個字節加密前64個字節用非對稱RC4加密。
加密的邏輯是先將文件讀到一個緩沖區中,然后進入一個循環中,循環每次增加8255個字節。在這個循環中拷貝緩沖區中的前64個字節到一個臨時buffer中,然后用非對稱的PublicKey進行RSA加密,然后將這個buffer拷貝到另外一個緩沖區中,由于加密后數據會變大,因此拷貝到另外的緩沖區中選擇的size的大小是128字節。接下來的8191字節采用一個數學算法,利用隨機生成的44字節的串對這部分數據進行加密,速度很快。完成后將這8191個字節拷貝到另外的緩沖區中。8191+64=8255
如果這一切完成了,它會寫個0x3E8到文件末尾。還會把使用到的隨機串用PublicKey加密了。
這塊邏輯比較復雜,如果要準確的分析還需要動態調試驗證一下,這里我還沒有去做。把代碼貼出來,大家自己感受吧。
TED_EncryptUserFile
TED_EncryptBytesByRC4每隔8255加密64字節頭,強加密
TED_EncryptBytesByAlgorithm? 剩下的8191采用隨機串用數學算法加密,速度快,沒有看到AES對稱加密算法,和AESkey。
?
Random隨機數生成一個64位的key的算法分析
這個算法的邏輯簡單說就是,用兩個Random函數生成2個隨機數,一個是0-3用于選擇使用哪一個字符數據,另一個0-44用戶定位數組中哪一個字符。這樣就組裝除了一個64位的Key字符數組。放代碼看的清楚。
TED_BuildKeyWithRandom
種子算法:
SR_System__Randomize
偽隨機算法:
SR_Random
Other
?
由于惡意軟件是delphi寫的,因此了解一下delphi使用的字符串的結構對分析代碼有些幫助。
AnsiString的內部結構
被感染后會將rundll32.exe從windows目錄中拷貝過來并改名為svchost,然后都用svchost啟動,起到偽裝迷惑的作用。
然后創建一個Process,進程的Commandline如下:
C:\Users\ADMINI~1\Desktop\_00310~1\svchost.exe C:\Users\ADMINI~1\Desktop\_00310~1\_00310000_pe.dll, MS111
?
這個過程是在Dll load的時候做的,所以它只要一load就會創建一個Process再去load自己,這樣就會存在很多個Process在系統中。為了調試方便,可以打patch,讓它不要去創建進程。
?
由于會寫一個bmp文件出來,這個bmp文件的內容也是勒索信息,被用于屏幕保護。所以需要知道bmp的結構才能分析對應的代碼。
原文地址:?http://ec2-52-196-167-189.ap-northeast-1.compute.amazonaws.com/wordpress/index.php/category/tech-blog/
總結
以上是生活随笔為你收集整理的Algorithm, Secret key and Protocol的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Android平台监听系统截屏方案预研及
- 下一篇: Ransomware Locky Ana