:從Base64到RSA混合加密)
搞惡意代碼分析這些年我最深的感受就是一個樣本能不能被真正看懂一半看行為另一半看通信。攻擊組織的技術迭代最明顯的往往不是漏洞利用那些花活而是通信加密這套基礎設施的升級。最近我在復盤一個業(yè)內常被稱為蔓靈花的攻擊組織時明顯感覺到它的武器庫又一次更新了早年間那種拿Base64包一層就敢上線的時代早就過去取而代之的是AES加RSA混合加密、再包一層魔改TLS的組合。這篇文章把我梳理出來的加密通信演進路徑、逆向還原方法和完整實操環(huán)境搭建過程都寫出來。無論你是剛進入惡意代碼分析方向的新人還是已經(jīng)被加密C2流量折磨過一陣子的分析人員里面提到的定位算法、提取密鑰、解密驗證的套路都可以直接拿來用。我會盡量把工具選型背后的理由講清楚也會把容易踩的坑單拎出來說省得你繞遠路。1. 為什么攻擊武器都在升級加密通信1.1 C2通信在惡意軟件里到底扮演什么角色C2是Command and Control的縮寫說白了就是木馬回連的指揮鏈路。樣本被投放到目標機器后會定期向攻擊者控制的服務器發(fā)送心跳同時接收指令、回傳文件整套遠程控制都靠這條通道維持。你可以把它想象成工蜂回巢命令從這里下發(fā)數(shù)據(jù)從這里運走。通道一斷即使樣本駐留得再深也只是個癱瘓的潛伏者。所以攻擊者對C2通信的加密投入從來都是不遺余力的。早期不少攻擊武器走的是明文HTTP上線后直接在日志里留下請求URL、上傳內容安全審計人員掃一眼流量就能看出問題。如今的主流攻擊組織基本都完成了幾輪升級最常見的做法是把指令放在加密字段里用對稱加密保護數(shù)據(jù)內容再用非對稱加密或者TLS保證密鑰傳輸環(huán)節(jié)不會被中間人直接拿走。從防御側來看加密通信反而是分析工作的重要突破口。攻擊者為了加密必須把算法、密鑰、初始化向量這些邏輯寫進樣本里。這些東西只要有一處能被提取整條C2鏈路就能被還原。換句話說加密不是分析工作的終點而是一個關鍵切入點。1.2 武器庫升級是被防御側倒逼出來的攻擊者升級加密通信不是為了炫技更多是防御側壓力導致的必然選擇。近些年威脅情報共享機制越來越成熟一個樣本的特征被公開后全球的安全設備能在很短時間內同步識別規(guī)則。再加上沙箱動態(tài)分析、流量側機器學習建模、JA3指紋追蹤這些手段的普及早期那種“編碼代替加密”的偽裝方式幾乎全部失效。蔓靈花這類組織的應對方式很有代表性。它們的加密通信經(jīng)歷了從簡單編碼到對稱加密、再到非對稱加密加自定義TLS的完整演進。每次升級都不是推倒重來而是在舊方案基礎上疊加新保護這也是為什么老樣本和新樣本經(jīng)常能看出明顯的同源特征。對于分析人員來說理解這條演進路線等于拿到了一個從易到難的解密梯度從最弱的舊樣本入手逐步順藤摸瓜找到新樣本的分析思路。值得強調的是加密升級是有成本的。攻擊者要維護私鑰、要保證算法在各版本W(wǎng)indows上穩(wěn)定運行、還要讓加密流量看起來不像惡意流量。這些成本會真實反映在樣本的代碼結構和執(zhí)行邏輯里也是我們逆向時可以利用的線索。2. 武器庫里的加密通信演進圖譜2.1 第一代明文HTTP與Base64偽加密最早一批樣本的通信方式簡單得有些可愛。樣本上線后向C2域名發(fā)起HTTP請求參數(shù)經(jīng)過Base64編碼就當作加密。有些甚至只是自定義了Base64字符表換了個排序就當成安全方案。這種“偽加密”的識別成本極低。分析時可以先用熵值看數(shù)據(jù)特征Base64的字符集集中在大小寫字母、數(shù)字和“/”分布沒那么隨機再看請求參數(shù)的結構Base64編碼后經(jīng)常以“”結尾做Padding這個特征在流量里相當顯眼。如果樣本用了自定義字符表比對標準Base64字符集就能定位到替換表的位置。拆解這類樣本最大的價值在于建立信心。我建議新手從這類樣本練手先用Python寫個Base64解碼腳本觀察URL參數(shù)的還原結果再對照樣本代碼確認編碼邏輯。這個過程能幫你熟悉從樣本到流量的完整分析鏈條后面遇到真正復雜的加密方案時就知道每一層該從哪里下手。2.2 第二代XOR與自定義混淆真正意義上的加密通信很多組織是從XOR開始入門的。XOR運算本身很輕量既不需要額外依賴也不會帶來明顯的性能開銷非常適合集成到木馬模塊里。常見的做法是單字節(jié)XOR或者多字節(jié)XOR密鑰要么硬編碼在樣本中要么從計算機名、時間戳等運行環(huán)境信息里臨時派生。XOR通信的一個顯著特征是加密前后數(shù)據(jù)長度完全一致密文的熵相對較高但又不像AES輸出那樣均勻隨機。當攻擊者使用多字節(jié)密鑰輪轉加密時可以通過已知明文攻擊來破解比如心跳包固定包含“hello”或者特定的會話標識用明密文對就能反推出密鑰流。蔓靈花早期樣本里的XOR方案有一個值得注意的細節(jié)部分版本會在加密前先做Base64或自定義編碼再執(zhí)行XOR。這種多層混淆表面看增加了分析難度實際上每一層都有自己的特征反而更容易暴露算法的執(zhí)行順序。分析時只要順著樣本的字符串處理函數(shù)逐層剝開就能還原出完整的編碼鏈路。2.3 第三代AES這類對稱加密與動態(tài)密鑰隨著檢測能力提升攻擊者很快意識到XOR太容易被統(tǒng)計特征識別開始轉向AES、RC4、ChaCha20這類標準對稱加密算法。最常見的是AES-CBC模式密鑰和IV的生成方式五花八門有的直接硬編碼在數(shù)據(jù)段里有的由文件屬性、卷序列號、系統(tǒng)版本組合后經(jīng)過MD5或SHA1派生還有的會在每次通信前重新生成密鑰再通過其他通道傳遞。分析這一代加密通信核心思路是先確定加密算法再找密鑰和IV。算法層相對容易識別AES的S盒、密鑰擴展邏輯、輪常量在反匯編代碼里都有明顯特征困難在于密鑰的提取。硬編碼密鑰直接用二進制搜索就能定位派生密鑰則需要動態(tài)調試在哈希函數(shù)或派生邏輯的調用點上打斷點觀察寄存器里的參數(shù)值。這里要特別提醒一點AES-CBC解密時IV錯誤只會影響第一個分組后續(xù)分組照樣能解出來。分析時如果發(fā)現(xiàn)解密結果前半部分亂碼、后半部分卻出現(xiàn)了正常ASCII字符基本可以斷定是IV找錯了不用急著推翻整個算法判定。這個細節(jié)能幫你省下大量調試時間。2.4 第四代RSA混合加密與魔改TLS當前威脅等級最高的一類方案是RSA與對稱加密的混合體系。樣本內置RSA公鑰運行時隨機生成AES會話密鑰再用RSA公鑰加密該會話密鑰后隨數(shù)據(jù)包發(fā)送。服務器側持私鑰解出AES密鑰后續(xù)大量數(shù)據(jù)走AES加解密。這種設計的巧妙之處在于即使分析人員完整逆向出樣本邏輯手頭沒有私鑰也無法直接解出RSA保護的會話密鑰傳統(tǒng)意義上的“解密C2流量”在這里就失效了。針對這種方案分析思路需要轉換不去硬解RSA而是走進程內提取路線。在樣本完成解密、即將使用AES密鑰的瞬間從內存中把密鑰dump出來或者用Frida hook加密API直接攔截解密后的數(shù)據(jù)內容。這就相當于繞開了鎖直接看鎖后面已經(jīng)打開的文件柜。同期的TLS層也在快速演變。組織開始使用自定義證書字段、亂序的CipherSuite、固定時鐘偏移等方式生成獨特的TLS指紋試圖規(guī)避基于JA3的檢測。還有一些方案借助CDN中轉隱藏真實C2讓流量在攻擊者和服務器之間多跳幾層進一步拉高溯源門檻。這部分內容對分析環(huán)境的要求更高但思路依然是那句老話攻不破的協(xié)議就去盯實現(xiàn)協(xié)議的程序。3. 逆向還原加密通信的完整思路3.1 靜態(tài)定位先找算法再找密鑰拿到一個加密通信樣本我習慣先做靜態(tài)分析把算法和密鑰候選位置標出來。第一步看導入表如果樣本導入了Advapi32.dll里的CryptEncrypt、CryptDecrypt系列函數(shù)大概率走的是Windows Crypto API如果導入的是WS2_32.dll里的send、recv同時又有大量內存操作可能是自研的私有加密實現(xiàn)如果樣本使用WinINet或WinHTTP庫通信層基本就是HTTP或HTTPS的封裝。第二步在反匯編代碼里搜索加密特征常量。AES的S盒初始化數(shù)據(jù)、TEA算法的0x9E3779B9魔數(shù)、MD5/SHA系列的初始哈希值都是一搜一個準的信號。Ghidra和IDA Pro都有字節(jié)模式搜索功能直接把S盒的前幾字節(jié)輸入進去能快速定位到算法所在的函數(shù)。字符串表同樣值得過一遍。樣本里經(jīng)常殘留錯誤提示、調試日志或者固定的握手標志比如“begin_session”“encrypt_key”這類字符串它們會把算法調用關系交代得一清二楚。靜態(tài)分析的目標不是立刻寫出解密腳本而是把可疑函數(shù)、密鑰引用點、加密調用點這三類位置在代碼里標出來給動態(tài)調試畫一張地圖。3.2 動態(tài)提取斷點、Hook與內存搜索靜態(tài)分析經(jīng)??ㄔ诿荑€生成環(huán)節(jié)密鑰可能被多次變換、白化處理光靠讀代碼根本繞不清楚。這時必須上動態(tài)調試。我常用的方式是先在關鍵的API上下斷點比如send、recv、CryptEncrypt、CryptDecrypt、SSL_write、SSL_read。樣本調用這些函數(shù)時內存里必然存在明密文轉換現(xiàn)場看函數(shù)參數(shù)就能拿到數(shù)據(jù)緩沖區(qū)的地址和長度。Frida是個效率很高的選擇。用腳本attach到樣本進程hook住發(fā)送函數(shù)在調用前后分別dump緩沖區(qū)調用前是明文調用后是密文兩組數(shù)據(jù)一對比加密邏輯基本就清楚了。下面這個例子是hook常見發(fā)送函數(shù)的關鍵思路var sendPtr Module.findExportByName(ws2_32.dll, send); Interceptor.attach(sendPtr, { onEnter: function(args) { this.buf args[1]; this.len args[2].toInt32(); }, onLeave: function(retval) { var data Memory.readByteArray(this.buf, this.len); console.log(hexdump(data, { offset: 0, length: this.len, header: true })); } });如果樣本有反調試機制動態(tài)調試前需要先處理。常見的檢查點是PEB里的BeingDebugged標記、NtQueryInformationProcess查詢、時間差檢測、線程名枚舉等。在調試器里把這些檢測點繞過之后再繼續(xù)hook否則樣本會直接退出給你留一個空蕩蕩的進程列表。3.3 解密驗證從樣本到PCAP的閉環(huán)拿到算法和密鑰后最后一步是拿真實的流量驗證。先用Wireshark導出一段樣本通信的PCAP文件從里面取出加密載荷再根據(jù)逆向出的算法編寫解密腳本。這個環(huán)節(jié)我最常用Python配合pycryptodome庫可以覆蓋大部分對稱加密場景。以典型的AES-CBC為例解密腳本的骨架長這樣from Crypto.Cipher import AES from Crypto.Util.Padding import unpad import base64 key bytes.fromhex(SAMPLE_KEY_HEX) iv bytes.fromhex(SAMPLE_IV_HEX) enc_b64 SAMPLE_CIPHERTEXT_BASE64 ciphertext base64.b64decode(enc_b64) cipher AES.new(key, AES.MODE_CBC, iv) plaintext unpad(cipher.decrypt(ciphertext), 16) print(plaintext)注意AES-CBC的密鑰長度決定算法分級128位對應16字節(jié)、192位對應24字節(jié)、256位對應32字節(jié)。如果腳本解密出一屏亂碼先檢查密鑰長度是不是搞錯了再檢查是否跳過前8字節(jié)的會話標識、是否漏掉Base64解碼步驟。驗證成功后的流量日志非常寶貴它能直接告訴你攻擊者的指令格式、心跳間隔、回傳文件的分塊方式。把這些信息整理成檢測規(guī)則比單純提取IOC要實用得多因為你理解的是通信協(xié)議的語義而不是某個固化的哈希值。4. 從零搭建一套惡意流量解密分析環(huán)境4.1 需要準備的虛擬機與工具清單建議用一臺Windows虛擬機專門運行惡意樣本另一臺Linux虛擬機兼職DNS模擬和C2服務器模擬。物理機或宿主機承擔抓包工作。網(wǎng)絡結構要完全隔離不能讓樣本真的連到互聯(lián)網(wǎng)。這種三機結構能確保分析全程可控也能防止樣本通過局域網(wǎng)橫向擴散。工具方面我整理了一份常用清單用途工具說明靜態(tài)分析IDA Pro / Ghidra函數(shù)定位、算法還原動態(tài)調試x64dbg / Frida斷點、Hook、內存dump流量捕獲WiresharkPCAP采集、特征分析域名模擬ApateDNS / INetSim偽造DNS應答、模擬HTTP服務自動化監(jiān)控Procmon注冊表、文件、進程行為監(jiān)控腳本處理Python pycryptodome數(shù)據(jù)清洗、加解密驗證調試建議先快照再運行。每次分析前給虛擬機做一個干凈快照樣本一旦釋放惡意行為或者觸發(fā)自毀邏輯恢復快照就能快速重來。這個習慣看起來不起眼但在反復調試時能節(jié)省大量時間。4.2 把C2通信完整地騙到本機要讓樣本正常聯(lián)網(wǎng)最可控的辦法是偽造C2域名。在Windows虛擬機的hosts文件里把樣本內置的C2域名指向Linux模擬機的IP然后在Linux上用INetSim監(jiān)聽對端口并把響應內容調整成樣本預期的格式。樣本解析到域名后會發(fā)現(xiàn)連上了“服務器”就會按照預設的心跳間隔持續(xù)回連。抓包時要重點記錄幾個位置TCP握手的第一個數(shù)據(jù)包、TLS ClientHello、樣本主動發(fā)送的POST或GET請求。很多樣本在上線成功后會立刻收取一個密鑰或配置這個配置數(shù)據(jù)往往也是加密的解密后能看到攻擊者針對該主機下發(fā)任務的格式。Wireshark里可以用下面這組過濾條件快速篩選可疑流量tcp.port 80 || tcp.port 443 || tls.handshake.type 1跑一段時間后把流量導出為PCAP結合前面逆向出的密鑰做解密驗證。我遇到最多的現(xiàn)象是樣本心跳包本身是固定結構解密后能直接看到命令類型、會話編號和目標ID。把這個結構畫成字段偏移表后續(xù)遇到同源樣本時可以自動匹配。4.3 解密并還原惡意指令內容解密驗證時建議把樣本運行時間控制在三分鐘以內這樣既能看到完整的心跳周期又不至于讓樣本持續(xù)上傳本地文件。如果解密后看到指令是下載并執(zhí)行某個文件或者調整自身持久化配置說明你的解密鏈路已經(jīng)打通了。還原指令內容還有一個容易被忽視的作用為檢測規(guī)則提供語義依據(jù)。比如指令格式里固定包含一個魔數(shù)頭那么流量檢測設備就可以在不解密全部數(shù)據(jù)的情況下通過檢測魔數(shù)頭出現(xiàn)的位置和頻率來預判屬于特定家族。我自己做檢測規(guī)則時非常依賴這一步還原出來的結構。5. 加密通信分析的常見坑與排查對照5.1 新手最容易踩的四個坑第一個坑是不關反調試就硬調。部分樣本使用雙進程保護調試器附加后子進程退出還有的在關鍵函數(shù)里插入大量垃圾指令干擾定位。建議動態(tài)調試前先用x64dbg的ScyllaHide插件統(tǒng)一清理PEB標記、斷點檢測等常見反調試項。省得花半天追一個永不觸發(fā)的中斷。第二個坑是把編碼當成加密。Base64、Hex、URL編碼都只是編碼變換過程可逆且不需要密鑰。如果分析時急著搜索“密鑰”很容易被這些干擾項帶偏。判斷標準很簡單變換過程是否需要密鑰參與沒有密鑰也能還原的就是編碼不是加密。第三個坑是高估了RSA私鑰獲取的可能。遇到RSA混合加密方案不要浪費時間去爆破私鑰那是數(shù)學上的不可能任務。直接轉向進程內存在解密后、使用前的窗口期下鉤子通常幾分鐘就能拿到實際使用的會話密鑰。第四個坑是忽略Padding和分塊規(guī)則。CBC模式的數(shù)據(jù)在發(fā)送前通常要做Padding最后一塊可能不足16字節(jié)有些樣本還會自定義數(shù)據(jù)格式在密文前追加固定長度的頭部信息。直接對整個載荷做解密前幾字節(jié)解出來是長度字段或魔數(shù)容易被誤判為解密失敗。正確的做法是先把頭部字段解析清楚定位到有效密文起點再執(zhí)行解密。5.2 加密流量分析排查速查表現(xiàn)場現(xiàn)象可能原因排查方向特征字符串找到斷點卻不命中算法實現(xiàn)是純函數(shù)、無外部API調用在函數(shù)調用處打斷點回溯調用者按AES-CBC解密全是亂碼密鑰長度錯誤或IV提取位置錯誤校驗密鑰字節(jié)數(shù)核對Padding機制內存里搜不到密鑰密鑰在函數(shù)執(zhí)行前臨時生成hook哈希函數(shù)dump派生輸入流量像HTTPS但證書異常魔改TLS或內置固定證書對比證書鏈、檢測TLS指紋偏差域名解析成功但連不上模擬服務端不支持對應協(xié)議用INetSim擴展模塊按協(xié)議族監(jiān)聽解密后內容前半部分正常后半部分亂碼IV錯誤僅影響首分組保留后續(xù)明文反推有效IV這套速查表是我在實際分析中反復驗證過的??吹絹y碼別慌張先確定問題在密鑰、IV還是數(shù)據(jù)切分再逐層排除。加密通信分析的本質就是借助樣本自身留下的線索把攻擊者精心搭建的加密鏈路反向拆開。每次拆解成功你手里的檢測能力和威脅追蹤能力都會實實在在往前進一步。最后分享一個我自己的經(jīng)驗在分析加密通信時可以順手統(tǒng)計心跳包的固定長度變化。即使在無法快速解密的場景下包的長度分布、發(fā)送間隔也能暴露攻擊者的指令下發(fā)節(jié)奏和受控主機的活躍狀態(tài)。把這些元數(shù)據(jù)特征記下來后續(xù)不管是追蹤同源樣本還是做全流量回溯都經(jīng)常能派上大用場。