操:API斷點(diǎn)定位與補(bǔ)丁保存全解析)
x64dbg彈窗破解這套流程我前前后后折騰了大半年踩過的坑比吃過的鹽都多。今天把這套完整的過程梳理一遍把那些文檔里壓根不會寫的細(xì)節(jié)和報錯一并說出來給后面接手同類需求的朋友省點(diǎn)時間。需要先聲明一點(diǎn)這套流程只適合用在你自己寫的演示程序、CTF比賽靶場、或者你擁有明確授權(quán)的軟件調(diào)試上。逆向分析本身是安全研究的基礎(chǔ)技能但用在未經(jīng)授權(quán)的商業(yè)軟件上就是另一回事了分寸自己要把握好。1. 項(xiàng)目目標(biāo)與整體思路拆解1.1 目標(biāo)分析彈窗的“底座”是什么拿到一個“想去掉彈窗”的任務(wù)第一步不是急著開調(diào)試器而是搞清楚這個彈窗到底是什么類型。彈窗只是表象它的底層大體分三類MessageBox系列最樸素的模態(tài)彈窗調(diào)用MessageBoxA/MessageBoxW/MessageBoxEx顯示一段文字加一個按鈕一般在網(wǎng)絡(luò)驗(yàn)證、試用期提醒里見得最多。DialogBox系列基于Win32資源對話框的彈窗用DialogBoxParamA/CreateDialogParam這類API創(chuàng)建常見于自定義的消息窗體外觀更復(fù)雜可以帶輸入框、列表、圖片。自繪窗口程序自己創(chuàng)建窗口類自己處理WM_PAINT、WM_CREATE等消息不走系統(tǒng)的標(biāo)準(zhǔn)彈窗API。這類最麻煩字符串搜索往往搜不到關(guān)鍵內(nèi)容需要從窗口創(chuàng)建流程逐層往下查。我在這次項(xiàng)目里用的演示程序是一個叫demo_check.exe的試運(yùn)行工具每次啟動時都會彈出一個“試用期已到期請續(xù)費(fèi)”的提示框點(diǎn)“確定”后程序還會繼續(xù)運(yùn)行只是每次打開都很煩。這就屬于典型的MessageBoxA 型NAG彈窗也就是常說的“煩人窗口”。這種彈窗的破解思路是所有彈窗類型里最標(biāo)準(zhǔn)的——定位調(diào)用點(diǎn)、分析邏輯、拆掉調(diào)用或逆轉(zhuǎn)跳轉(zhuǎn)。1.2 為什么首選x64dbg市面上調(diào)試器不少OllyDbg在32位時代幾乎是標(biāo)配但面對現(xiàn)在越來越多的64位程序它的老版本顯得力不從心。x64dbg雖然是OllyDbg的后繼者但它是獨(dú)立架構(gòu)的既能調(diào)試32位也能調(diào)試64位進(jìn)程界面、快捷鍵、腳本系統(tǒng)、插件體系都更適合現(xiàn)代逆向。我對x64dbg的偏愛主要是三個理由一是它的可視化能力做得好內(nèi)存布局、?;厮荨⒓拇嫫鳡顟B(tài)、調(diào)用棧都是實(shí)時刷新的對著一堆匯編代碼翻來翻去的時候非常有用。二是它的插件生態(tài)相當(dāng)成熟像ScyllaHide處理反調(diào)試、腳本插件跑自動化流程這些在彈窗破解里都可能用到。三是它自帶數(shù)據(jù)庫和標(biāo)簽管理你在分析過程中做的改動、命名、注釋、斷點(diǎn)狀態(tài)都能保存下來下一次打開還能接著上一次的工作繼續(xù)干。當(dāng)然任何調(diào)試器本身只是工具核心還是你對匯編指令、調(diào)用約定、PE結(jié)構(gòu)、Windows消息機(jī)制這些底層知識的掌握程度。調(diào)試器好上手真正拉開差距的是底層知識的熟練度。1.3 破解策略的選擇針對彈窗功能破壞點(diǎn)一般有四個可選方向攔截API調(diào)用在彈窗API上下斷點(diǎn)程序一調(diào)就斷然后讓它直接跳過彈窗邏輯返回繞過展示。修改調(diào)用點(diǎn)把彈窗API所在的那幾條指令直接NOP掉或改成跳轉(zhuǎn)程序找不到彈窗入口自然就不會彈。改寫條件跳轉(zhuǎn)有些彈窗外圍會有判斷邏輯比如“如果過期了就彈窗”把這種判斷跳轉(zhuǎn)直接逆轉(zhuǎn)或NOP讓程序認(rèn)為還沒到期也就不會彈。修改資源/字符串把提示文本改掉或者刪掉對話框資源但這只能治標(biāo)彈窗邏輯還在破解痕跡太明顯。我這次走的方案是先定位調(diào)用點(diǎn)觀察它的調(diào)用邏輯把決定彈不彈窗的關(guān)鍵跳轉(zhuǎn)改成無條件跳過。這樣做的好處是改動最小——不破壞程序的正常流程不會引發(fā)運(yùn)行時崩潰也方便驗(yàn)證效果。2. 調(diào)試前的準(zhǔn)備工作和靜態(tài)偵察2.1 先把程序“體檢”一遍拿到程序直接上調(diào)試器是一種常見的錯誤做法。調(diào)試器加載目標(biāo)程序后如果程序自身有反調(diào)試機(jī)制一檢測到調(diào)試環(huán)境就會主動退出那時你連程序日志都看不到排查起來特別被動。所以事先做靜態(tài)檢查是必須的。我習(xí)慣先用查殼工具比如Die或PEiD快速看一下程序是否加殼。如果檢測結(jié)果顯示“Borland C”、“Microsoft Visual C”這類正常編譯特征說明沒加殼可以直接上x64dbg。如果看到UPX、ASPack或者VMProtect、Themida這類特征那就得先考慮脫殼或應(yīng)對虛擬化保護(hù)否則動態(tài)調(diào)試會非常痛苦。這步花不了多少時間但能避免后面大量無效操作。查殼工具提示“保護(hù)器: 無”或者“編譯器: Microsoft Visual C/C”基本就能放心往下走了。2.2 用“字符串搜索”找到彈窗的入口線索解決了殼的疑慮下一步就是把程序跑起來看一下有沒有導(dǎo)入函數(shù)能直接指向彈窗API。打開x64dbg的“符號”窗口過濾MessageBox如果能在導(dǎo)入表里看到MessageBoxA或者M(jìn)essageBoxW事情就已經(jīng)成功了一半。不過更直接的辦法永遠(yuǎn)是搜索字符串。我這次在x64dbg里按下CtrlB打開搜索面板搜索字符串“試用期”結(jié)果在兩個位置有發(fā)現(xiàn)一個是.rdata段的靜態(tài)文本另一個是調(diào)用函數(shù)附近的局部字符串。雙擊跳轉(zhuǎn)到數(shù)據(jù)窗口按CtrlR查找所有引用該字符串的指令地址程序里處在彈窗調(diào)用點(diǎn)附近的位置就浮出水面了。這里有個細(xì)節(jié)要記住如果搜不到檢查一下編碼環(huán)境。現(xiàn)在的程序大部分用寬字符Unicode在x64dbg的搜索框里默認(rèn)按ASCII搜中文搜不到很正常。這時候要切換成UTF-16或Unicode編碼再搜一次。說白了就是把搜索條件設(shè)置成跟程序編碼匹配這是新手第一道坎。2.3 從導(dǎo)入表反推彈窗API字符串搜索不一定每次都靈。有些程序會把字符串加密或者動態(tài)拼接比如“試”和“用期”拼在一起之后再做展示這樣你搜“試用期”就沒有任何結(jié)果。這時候就得換思路直接在導(dǎo)入表里查MessageBoxA/MessageBoxW/DialogBoxParam對函數(shù)下斷點(diǎn)讓它運(yùn)行時自己撞到斷點(diǎn)上。具體操作是在x64dbg底部命令框輸入bp MessageBoxA然后按下F9運(yùn)行。程序啟動后彈窗出現(xiàn)x64dbg會立刻斷在MessageBoxA的入口。你往調(diào)用棧里看就能找到是誰調(diào)用了這個API。不管字符串怎么藏API調(diào)用的行為是暴露在外的這是動態(tài)分析最底層的邏輯。3. 動態(tài)調(diào)試全過程逐段拆解3.1 系統(tǒng)斷點(diǎn)與模塊加載時機(jī)x64dbg加載demo_check.exe后會先停在系統(tǒng)斷點(diǎn)System Breakpoint就是程序還沒開始執(zhí)行入口點(diǎn)之前的位置。這時候程序主模塊其實(shí)已經(jīng)映射進(jìn)內(nèi)存了但還沒有進(jìn)入入口函數(shù)。很多新手在這一步就開始敲指令卻發(fā)現(xiàn)斷點(diǎn)下不上原因往往是模塊還沒完成初始化。正確做法是先讓程序跑起來停在入口點(diǎn)或第一次彈窗的位置再下針對API的斷點(diǎn)。拿到系統(tǒng)斷點(diǎn)后按F9直接運(yùn)行程序會一直跑直到撞上你下的API斷點(diǎn)或主模塊入口。注意在x64dbg里設(shè)置斷點(diǎn)以后程序不會自動停在入口得像上面說的用“運(yùn)行到入口點(diǎn)”功能。雖然現(xiàn)在版本里常常自帶一個入口斷點(diǎn)選項(xiàng)但你最好養(yǎng)成手動管理斷點(diǎn)的習(xí)慣后續(xù)破解會更順手。3.2 命中彈窗API后的?;厮菀坏┏绦驈棿俺霈F(xiàn)MessageBoxA斷點(diǎn)命中整個調(diào)試器畫面會跳到系統(tǒng)DLL通常是user32.dll的代碼區(qū)域。這時候先別細(xì)看這坨代碼重點(diǎn)在調(diào)用棧Call Stack窗口。調(diào)用棧會列出從當(dāng)前函數(shù)往上追溯的知識鏈像這樣000000000014F920 00007FF612345678 demo_check0xABCD 000000000014F928 00007FF612345900 demo_check0x1234 000000000014F930 00007FF612346010 demo_check0xA567第一行是當(dāng)前代碼放置的位置第二行是調(diào)用者以此類推。雙擊第二行或者按Ctrl↑直接跳轉(zhuǎn)到調(diào)用MessageBoxA的那條指令也就是彈窗邏輯的核心位置。在x64下函數(shù)調(diào)用參數(shù)的前四個放在rcx、rdx、r8、r9里MessageBoxA的函數(shù)簽名是int MessageBoxA( HWND hWnd, // rcx LPCSTR lpText, // rdx 文本內(nèi)容 LPCSTR lpCaption, // r8 窗口標(biāo)題 UINT uType // r9 按鈕/圖標(biāo)類型 );所以你看rdx寄存器里的值指向的字符串就是彈窗顯示的正文r8指向的是標(biāo)題。這種對應(yīng)關(guān)系在識別彈窗邏輯時非常好用一眼就能看出程序在哪里組織這些參數(shù)、從哪里取得文本內(nèi)容、哪里是分支判斷。3.3 彈窗邏輯的定位與NOP處理跳到調(diào)用點(diǎn)之后看到的典型匯編序列大概是這樣00401234 mov r9d, 0 ; uType 0040123B lea rdx, [rip0x7FFF] ; lpText 00401242 lea r8, [rip0x7FF5] ; lpCaption 00401249 xor ecx, ecx ; hWnd 0040124B call MessageBoxA ; 彈窗! 00401250 test eax, eax這就是一個標(biāo)準(zhǔn)彈窗調(diào)用點(diǎn)。要把它“摘掉”我一般會最優(yōu)先把call MessageBoxA這一條指令NOP掉。在x64dbg里光標(biāo)定位到這條指令選中后按空格或者右鍵“二進(jìn)制”-“用NOP填充”指令就變成了6個字節(jié)的0x90。但NOP掉調(diào)用以后程序邏輯會有個問題之前如果彈窗的返回值eax會被后續(xù)代碼用到現(xiàn)在eax里的值變得不確定有概率引起程序行為異常。所以更穩(wěn)妥的做法是看彈窗前后有沒有條件跳轉(zhuǎn)找到?jīng)Q定“要不要彈”的那條分支直接逆轉(zhuǎn)或讓它直接跳過。比如常見的結(jié)構(gòu)是00401210 cmp byte ptr [rip0x10A0], 0 00401217 je short 00401240 ; 如果某個標(biāo)志位為0跳過彈窗 ... 00401240 ...如果你把je改成jmp就是無條件跳轉(zhuǎn)到“跳過彈窗”的那條路徑條件是死是活都不影響了。但我這次的實(shí)際場景有點(diǎn)特殊程序里的彈窗代碼雖然入口有判斷但判斷標(biāo)志位在程序運(yùn)行過程中寫死了直接改跳轉(zhuǎn)反而可能導(dǎo)致后續(xù)代碼找不到本應(yīng)存在的彈窗流程。經(jīng)過反復(fù)測試我最后選擇了“NOP調(diào)用手動修復(fù)eax”的組合方式效果最穩(wěn)定。3.4 保存補(bǔ)丁與文件轉(zhuǎn)儲調(diào)試器里的修改是停在內(nèi)存層面的程序退出以后一切都還原了。要讓補(bǔ)丁長期生效得把它寫回文件。具體操作在x64dbg里改好指令后右鍵選擇“復(fù)制到可執(zhí)行文件”彈出新窗口再右鍵“保存文件”就能導(dǎo)出一個打上補(bǔ)丁的新exe。注意有些程序有自我校驗(yàn)或者數(shù)字簽名直接改動文件會讓程序檢測到異常啟動時會報錯。這種情況就得加一個繞過校驗(yàn)的邏輯比如找到校驗(yàn)函數(shù)入口直接讓它永遠(yuǎn)返回“未修改”或者用內(nèi)存補(bǔ)丁的方式在運(yùn)行時動態(tài)patch不直接改動磁盤文件。我這次做的demo_check.exe沒有校驗(yàn)所以直接保存到新文件就行。如果有校驗(yàn)我的經(jīng)驗(yàn)是做一個外部加載補(bǔ)丁啟動時隨調(diào)試器或DLL注入強(qiáng)制修改內(nèi)存這樣磁盤文件保持原樣校驗(yàn)自然也不會被觸發(fā)。4. 文件補(bǔ)丁的兩種保存方式與驗(yàn)證4.1 直接把補(bǔ)丁寫回PE文件上一節(jié)提到的“復(fù)制到可執(zhí)行文件”就是最直接的寫回方式。x64dbg會把當(dāng)前被修改過的這一段指令以及我們編輯后的字節(jié)連同重定位信息一起寫進(jìn)新的PE文件。這里有個坑修改后的文件大小不能變。你把指令改成0x90字節(jié)數(shù)沒變寫回沒問題。但如果你要插入一條原來沒有的跳轉(zhuǎn)指令占用了新的空間可能就需要擴(kuò)大節(jié)區(qū)或者借用文件頭的空隙而不是簡簡單單覆蓋幾個字節(jié)。所以NOP填充是最省事的這也是為什么很多人喜歡用NOP而不是寫一個跳轉(zhuǎn)到別處的指令。另外別忘了保存文件前確認(rèn)當(dāng)前修改的地址是在主模塊里不是DLL里。如果你是在user32.dll的地址上改東西那就麻煩了寫回文件的時候會把整個DLL的字節(jié)也帶上完全沒有意義。4.2 用Scylla或工具做轉(zhuǎn)儲與修復(fù)如果程序加了殼或者有加殼保護(hù)直接修改原始PE文件通常無效因?yàn)檎嬲拇a在運(yùn)行時才解密磁盤上的字節(jié)可能是密文。這種情況下就需要“轉(zhuǎn)儲”內(nèi)存中已解密的代碼然后修復(fù)導(dǎo)入表。x64dbg里一般搭配Scylla插件來做這件事。在當(dāng)前進(jìn)程狀態(tài)正確的地方比如OEP也就是原始入口點(diǎn)用Scylla抓取當(dāng)前進(jìn)程的映像它會自動修復(fù)IAT導(dǎo)入地址表并重新生成一個瘦身版的exe。這個文件就是把運(yùn)行時已經(jīng)展開的代碼保存下來后續(xù)可以直接往上面做靜態(tài)補(bǔ)丁。需要注意轉(zhuǎn)儲出來的文件和你加殼前的原始程序在結(jié)構(gòu)上不完全一致有的程序跑起來會缺這個缺那個。所以如果是加殼程序建議先把殼脫掉再做彈窗破解整個鏈路會順暢得多。4.3 運(yùn)行驗(yàn)證與回歸測試補(bǔ)丁做完不能彈窗消失就收工我習(xí)慣做三輪驗(yàn)證第一輪雙擊補(bǔ)丁后的exe確認(rèn)不彈窗、程序主界面正常出現(xiàn)、主要按鈕功能可用。第二輪把補(bǔ)丁后的文件丟到x64dbg里再跑一遍看程序啟動有沒有異常、有沒有觸發(fā)別的自我保護(hù)。第三輪多開幾次程序確認(rèn)補(bǔ)丁效果穩(wěn)定不會出現(xiàn)偶發(fā)崩潰。有的程序在不同啟動路徑下行為不同比如命令行參數(shù)不同、資源加載順序不同反復(fù)開關(guān)幾次才能暴露問題。我在驗(yàn)證過程中遇到過一種比較隱蔽的狀況彈窗補(bǔ)丁生效了但程序后續(xù)的試用期邏輯也在走比如定時檢查到“試用已到期”于是主窗口標(biāo)題或者功能按鈕被禁用。這時候光改彈窗沒解決根本問題得回到代碼里找那個決定“是否過期”的標(biāo)志位或者比較運(yùn)算一并處理掉。5. 常見錯誤與排查技巧實(shí)錄5.1 字符串搜索定位失敗的排查清單這是彈窗破解里出現(xiàn)頻率最高的一個坑我在自己的實(shí)操里總結(jié)了一套排查順序現(xiàn)象可能原因處理辦法搜“試用期”沒有任何結(jié)果編碼不對ASCII搜中文切換UTF-16/Unicode編碼再搜字符串搜索有結(jié)果但“引用”列表為空字符串是動態(tài)拼接的沒有被直接引用對附近代碼下斷點(diǎn)觀察引用點(diǎn)很多不清楚哪一處是彈窗同一段字符串被多處復(fù)用用bp MessageBoxA走動態(tài)斷點(diǎn)定位字符串在資源文件里不在內(nèi)存中程序調(diào)用LoadString加載在LoadStringW上下斷點(diǎn)看參數(shù)定位字符串搜索只是一個切入點(diǎn)它更像是地圖上的一個標(biāo)記。圖上的標(biāo)記可能指到好幾個岔路口真正決定走哪條路還是要靠動態(tài)調(diào)試。5.2 斷點(diǎn)命不中的場景有時候你明明下了bp MessageBoxA程序也彈了窗但調(diào)試器就是不斷。遇到這種情況我馬上查兩件事第一程序彈的是不是系統(tǒng)自繪窗口根本沒走M(jìn)essageBoxA。比如有些程序用TaskDialog接口或者用WPF那套渲染那API就完全不一樣。這種情況需要在創(chuàng)建窗口、顯示窗口相關(guān)的API上斷點(diǎn)比如CreateWindowExW。第二程序是不是在彈窗之前先做了反調(diào)試檢測檢測到x64dbg就拒絕執(zhí)行彈窗流程改走了靜默路徑。這時畫面表現(xiàn)為程序消失或卡住斷點(diǎn)永遠(yuǎn)等不來命中。處理辦法是先加載ScyllaHide插件開啟常用反調(diào)試?yán)@過選項(xiàng)比如隱藏調(diào)試器進(jìn)程名、處理IsDebuggerPresent、NtQueryInformationProcess這些API的返回值。5.3 反調(diào)試帶來的“假死”和“自殺”反調(diào)試是彈窗破解里最容易讓人心態(tài)炸裂的話題。程序檢測調(diào)試器的方式五花八門常見的有調(diào)用IsDebuggerPresent或CheckRemoteDebuggerPresent返回真就直接退出。通過NtQueryInformationProcess查詢調(diào)試端口。檢測特定窗口、特定注冊表鍵或者運(yùn)行時間隨機(jī)數(shù)校驗(yàn)。利用WH_DEBUG鉤子偵測是否有外部消息注入。對付這些手段ScyllaHide可以在R3層面把大部分調(diào)用結(jié)果和諧掉。啟動x64dbg前先保證ScyllaHide插件已啟用然后勾選“隱藏PEB”、“繞過NtQueryInformationProcess”、“抑制斷點(diǎn)檢測”等選項(xiàng)。如果你用的程序比較硬核還有驅(qū)動級反調(diào)試普通方式就無能為力了只能考慮內(nèi)核調(diào)試或者手動patch反調(diào)試函數(shù)本身。不過那種場景已經(jīng)超出“破解彈窗”的范疇屬于更專業(yè)的對抗范疇這里不多展開。5.4 運(yùn)行時崩潰與指令邊界問題補(bǔ)丁改完程序要么正常要么秒退或崩潰。秒退先自定義查看事件日志和程序本身的行為模式但九成九是因?yàn)槟愀腻e了指令邊界。比如你原本想覆蓋一條6字節(jié)的指令但你的NOP操作不小心多覆蓋了一個字節(jié)把后面那條指令的頭部也碰掉了整個解碼流程錯位程序一跑就炸。還有一種常見錯誤是你以為改的是調(diào)用點(diǎn)但實(shí)際上改的是跳轉(zhuǎn)表或者函數(shù)開頭破壞了函數(shù)棧幀對齊。x64下棧幀不對齊的直接后果就是后續(xù)調(diào)用系統(tǒng)API時崩潰。所以每次改動之前先觀察這條指令的長度看反匯編窗口右側(cè)的字節(jié)長度顯示再決定NOP多少字節(jié)。改完以后最好從頭再“單步走”一遍這條分支看到確實(shí)按照預(yù)期跳過了再做保存。5.5 文件校驗(yàn)與簽名還原的處理前面提過很多程序有自校驗(yàn)。這類程序在修改文件后啟動時會計(jì)算自身文件的哈希和內(nèi)置值比對發(fā)現(xiàn)不一致就直接拒絕運(yùn)行。你的補(bǔ)丁做得再完美也白搭。實(shí)際處理的辦法就兩種一種是讓校驗(yàn)永遠(yuǎn)“成立”找到校驗(yàn)函數(shù)返回的地方把最終比較的結(jié)果改寫成“一致”。比如程序在算文件哈希后和一個常量做比較你可以把cmp后的跳轉(zhuǎn)改成永遠(yuǎn)不會跳走的路徑或者直接讓jne變成jmp。另一種就是在內(nèi)存里打補(bǔ)丁。啟動時程序先運(yùn)行原版文件你用一個外部工具比如在系統(tǒng)啟動加載一個DLL注入器在程序運(yùn)行后將補(bǔ)丁寫進(jìn)內(nèi)存。這種方案麻煩點(diǎn)但繞過一切磁盤校驗(yàn)因?yàn)槲募緛砭褪窃娴?。我自己在做某公司安全開發(fā)中心的授權(quán)驗(yàn)證程序時遇到的就是自校驗(yàn)彈窗的組合。最后用x64dbg跑起來找到校驗(yàn)函數(shù)逆轉(zhuǎn)其中對“文件被修改”的判斷之后才順利把彈窗邏輯摘掉。6. 事后優(yōu)化與腳本化補(bǔ)丁方案6.1 用腳本自動打補(bǔ)丁如果你經(jīng)常要處理類似的彈窗破解任務(wù)每次都手動打開x64dbg、定位、改指令、保存文件效率太低。x64dbg支持腳本命令可以把步驟沉淀下來。比如先記錄要修改的地址、原始字節(jié)、NOP填充的范圍寫成一個腳本StartRun SetBreakpoint 401234 Run ...實(shí)際上x64dbg官方腳本語法主要是基于命令行的你可以在命令行窗口直接執(zhí)行匯編指令和斷點(diǎn)操作。寫過一段時間腳本之后你會發(fā)現(xiàn)自己對程序結(jié)構(gòu)理解得更深從“用鼠標(biāo)點(diǎn)來點(diǎn)去”進(jìn)階到“用命令精準(zhǔn)控制”。我這里分享一個我常用的二分法先寫腳本暴力遍歷MessageBoxW斷點(diǎn)只要一命中就自動記錄調(diào)用棧、寄存器快照然后繼續(xù)運(yùn)行。多次運(yùn)行以后把這些記錄做對比彈窗路徑和正常路徑之間的差異就一目了然。6.2 動態(tài)補(bǔ)丁加載器的思路除了靜態(tài)修改文件也可以做一個補(bǔ)丁加載器在程序啟動的時候通過導(dǎo)入表劫持或者DLL注入在DllMain里直接改寫目標(biāo)函數(shù)的指令。這樣你甚至不用改原始exe所有破解邏輯都留在外部DLL里。這個方案的好處是維護(hù)起來靈活不污染原始文件壞處是啟動依賴外部文件容易被殺毒軟件攔截。如果你只是做安全研究驗(yàn)證這種方式更優(yōu)雅也更接近真實(shí)攻防場景里“內(nèi)存馬”的思路。不過要提醒一句做這種加載器需要你對PE加載流程、DLL注入原理有扎實(shí)的掌握否則很容易出現(xiàn)“DLL加載但補(bǔ)丁沒生效”的尷尬局面。我見過很多半吊子的動態(tài)補(bǔ)丁器就是因?yàn)镈LL加載時機(jī)晚于彈窗邏輯執(zhí)行彈窗已經(jīng)彈完了補(bǔ)丁才寫進(jìn)去等于白忙活。6.3 補(bǔ)丁的穩(wěn)定性驗(yàn)證思路補(bǔ)丁做完不是結(jié)束穩(wěn)定性驗(yàn)證是重頭戲。我會準(zhǔn)備一個清單程序連續(xù)啟動20次不出現(xiàn)偶發(fā)崩潰或彈窗殘留。在程序內(nèi)部跳轉(zhuǎn)、切換頁面、保存數(shù)據(jù)等操作后確認(rèn)功能不與原版有差異。如果有網(wǎng)絡(luò)功能確認(rèn)補(bǔ)丁后網(wǎng)絡(luò)請求正常不影響登錄、同步等模塊。當(dāng)前系統(tǒng)環(huán)境之外再扔到虛擬機(jī)里測一遍排除環(huán)境因素導(dǎo)致的假陽性結(jié)果。我踩過最深的一個坑是補(bǔ)丁后程序能正常啟動但一進(jìn)主界面的“關(guān)于”窗口就崩潰。后來排查發(fā)現(xiàn)是因?yàn)槲野岩惶幈緫?yīng)保留NOP的call指令直接改沒了而程序在后續(xù)流程里還會讀取那次調(diào)用的返回值eax返回值為空指針導(dǎo)致后續(xù)解引用失敗。這給我留下的教訓(xùn)是能不破壞調(diào)用棧、寄存器約束的補(bǔ)丁方式就絕不隨意NOP調(diào)用。優(yōu)先改跳轉(zhuǎn)條件其次NOP調(diào)用最后才考慮改寫指令序列。7. 寫在最后一些更深的體會這套流程走下來我最大的感受是破解一個彈窗本身不難真正難的是在不破壞程序原有邏輯的前提下干凈地移除或繞過彈窗。這要求你把匯編層面的數(shù)據(jù)流、控制流都看清了再動手去動它。如果前面哪個環(huán)節(jié)還不太熟練我給一個循序漸進(jìn)的練習(xí)建議不需要一上來就死磕復(fù)雜的加殼程序先拿自己寫的演示程序練手反復(fù)做“定位彈窗API、讀取調(diào)用參數(shù)、修改跳轉(zhuǎn)條件、保存補(bǔ)丁、驗(yàn)證回歸”這幾件事直到不再依賴搜索字符串能直接從調(diào)用棧推理出彈窗入口這一套技術(shù)就算真正掌握了。后面有時間的話我會再寫一篇關(guān)于如何用腳本批量掃描程序內(nèi)的彈窗模式、以及如何處理多彈窗和嵌套彈窗的文章那時候需要的工具和思路會更豐富一些。