這次爆破的是某編輯類軟件,版本是32位綠色版本:V4.3.1
1、OD打開后發現了VMP0段,這里下個內存訪問斷點:
又來到這里了,非常明顯的VMP入口特征:
一大堆push指令又開始保存物理寄存器;同時讓esi指向虛擬指令集;和上面一篇文章分析的混淆手法一模一樣,個人猜測用的VMP也是3.5.0版本的;
分配虛擬棧空間:
、
這里就不再重復分析整個VMP過程了,感興趣的小伙伴建議看看之前的VMP系列介紹;為了快速定位關鍵位置,來到注冊地方,隨便輸入一個辨識度較高的注冊碼:
同時記得繼續在內存視圖給VMP0段下斷點后點擊確認按鈕,斷到這里了,繼續F7:
單步F7走着,同時在棧中回溯,找到了自己輸入的sn,這個地址就很重要了,果斷取消VMP0段的斷點,對這個地址下讀的斷點:
這里也下訪問斷點;繼續回溯棧,在棧下方凡是這個exe本身的調用都下斷點:
這里有messageBox,棧里面也有sn和“該注冊碼不正確!”的關鍵提示,說明這里已經走到了注冊錯誤的分支,需要回溯棧,看看在哪調用了這個方法(這里的返回地址是0x5E01E9)!
這里的地址是0x3E6000,text段的基址是0x381000,偏移=0x3E6000-0x381000=0x65000,記住這個偏移,后續可能有用;
繼續F7,執行完messbaox彈窗后,居然回到了某個VM的入口:正常情況下,ret會回到call的下一行,但這里回到了push(或則說vm的入口),說明ret的的地址因該是人為故意加上的,也說明這段代碼是關鍵代碼,作者故意不想讓逆向人員破解,真是此地無銀三百兩啊!
上面這個地方會不會是驗證碼檢測的入口了?我沒仔細分析,不過既然斷到這里了,就有可能是,不管那么多了,先NOP這些代碼試試,結果直接崩掉,說明不能這么粗暴,重新來!
上面是爆破軟件的傳統思路:通過字符串找到各種關鍵提示(sn、注冊不正確之類的)的內存,通過訪問斷點定位到關鍵代碼,然后逐步往上回溯找到關鍵的JCC指令,改變JCC指令的跳轉方向達到爆破的目的;由於被VMP加了很多混淆指令,直接這樣簡單粗暴找JCC難度不小,這條路暫時放棄,得換個思路和打法!
2、既然3E6000是彈窗的,那么只要找出是哪個函數調用了這個函數距離JCC指令就更進一步了,上面就是這種思路。但ret后發現是VM的入口,並不是我們傳統意義上的函數調用。既然動態分析行不通,那就靜態查找試試;打開IDA,默認的base是401000,函數偏移是0x65000,絕對地址是466000,正好是目標函數:
為了方便,可以把base改成exe運行的base,也就是0x380000,能在3E6000這里直接看到函數了:
通過function calls能找到所有調用這個函數的函數:注意,這里的call用的是相對地址,不是絕對地址,所以直接用硬編碼找是不行的!
一共有24個,如果挨個看代碼難度非常大,只能繼續接着動態調試:每個地方都下斷點,然后繼續操作注冊的流程,滿以為能找到調用點,結果一個都沒斷下來,說明注冊失敗彈窗的函數不是這么調用了,要么是jmp到這里,要么是call 寄存器這種間接調用;
3、靜態分析也沒找到關鍵的調用點,繼續動態分析;VMP最大的特點就是混淆:明明一個簡單的指令,非要用復雜的多行指令替代,那么這次就trace一下,看看從VM入口一直到彈窗,這中間究竟執行了哪些代碼!
(1)既然5E01E9是關鍵代碼,那么先trace一下,看看都有哪些代碼執行過!先在0x5E01E9下個斷點,然后開啟trace功能,接着上面輸入sn驗證的操作重新做一遍,run trace界面就能看到指令執行的記錄了,如下(這里順便吐槽一下log to file的功能,只能看到地址和寄存器的值,看不到執行的指令,WTF.......):
接下來就是個體力活了:挨個找寄存器里面的值等於00000040的行(后續會解釋原因),挨個下斷點,比如下面這樣:
這里不得不吐槽一下OD的trace功能不好用:OD內部沒法搜索關鍵詞,保存到文件后又看不到執行的指令,很不方便,果斷棄坑,換成x32dbg;trace的步驟:
- 在內存布局那找到VMP0段下個一次性的訪問斷點,然后操作注冊的流程,正常情況下會斷到VM入口。這時單步步進,進入VM
- 在“跟蹤”頁面右鍵選擇“啟動追蹤”,最后再在菜單欄選擇“追蹤->自動步進”,或則直接CTRL+F7
此時x32dbg會自動開始單步步進,直到注冊界面彈框;整個過程我花了一上午+中午,超過5小時,終於看到彈框;在跟蹤界面查到一共執行了0x3F617=259607行代碼;
(2)這么多行代碼,該怎么分析才能找到關鍵的JCC指令?在“跟蹤”界面,右鍵選擇搜索->常數,表達式這里輸入00000040(后面會詳細解釋為啥是這個數),點擊確定;
和0x40相關的指令有近100條,逐條篩選and指令(后續會詳細解釋為什么要重點查找and指令)。
可以看到除最后一條,所有的指令都是and eax,ecx; 第1、2條分別執行一次,第3條執行了30次;
接下來就是純體力活了:
- 選中某條,右鍵 復制->索引;(虛擬機不好截圖,用手機拍的,讀者請多擔待)
- 回到跟蹤窗口,ctrl+g后粘貼剛才復制的索引,定位到那行and代碼;然后 右鍵->信息 查看寄存器的值
對於eax=0x40、eflags=0x246或0xFFFFxxxxx開始的值,都下個斷點(其實一共只有3個,也不用挨個檢查,直接下斷點也行);
這三個and eax,ecx都有個共同點:之前都執行了not eax和not ecx,原理后續再介紹!3個斷點下來后,繼續操作注冊的流程,3個斷點都成功斷下,然后挨個過濾,把執行完and eax,ecx后eax=0x0的選出來,人為把eax改成0x40或0x246;
除此以外,由於影響eax的是ecx,所以把ecx=FFFFFDB9(或者是~276,因為剛好讓ZF位=0,和eax與后也會讓ZF=0)找出來,挨個下斷點查看:
關鍵的兩個and指令夾雜在jmp中間,如果不是trace,根本找不到這些關鍵點:
凡是eax不等於0x40的全都改成0x40
終於,在改了好多次eax=0x40后,成功爆破!
輸入注冊碼購買之類的也沒了!
4、(1)VMP的萬用門
學過邏輯電路的朋友們都知道有一種門電路,叫與非門(俗稱萬用門),表示為: Nand(a,b) = ~a & ~b,就是兩個數取反后再與;這是一個很普通的表達式,為啥要專門拿出來介紹?用名字就能看出來:萬用門!匯編里面最基本的4種邏輯運算,都能用萬用門表示,推導過程如下:
- Not(a) = ~a = ~a & ~a = Nand(a,a)
- Or(a,b) = a | b = ~(~a & ~b) = Nand(Nand(a,b),Nand(a,b))
- And(a,b) = a & b = ~~a & ~~b = Nand(Nand(a,a),Nand(b,b))
- Xor(a,b) = (~a & b) | (a & ~b) = (0 | (a & ~b)) | (0 | (b & ~a)) = (a & (~a | ~b)) | (b & (~a | ~b)) = (~a | ~b) & (a | b) = ~(a & b) | ~(~a & ~b) = Nand(And(a,b),Nand(a,b)) =Nand(Nand(Nand(a,a),Nand(b,b)),Nand(a,b))
這里感覺就有點饒了: ~a表示a取反,用Nand(a,a)表示時是~a&~a,表達式里面又嵌套了取反,感覺有點像盜夢空間............
再VMP 3.5.0版本中,大量使用了Nand運算來表示其他的各種邏輯,真實地隱藏了原本的各種邏輯運算,有效地加大了逆向分析的難度!所以上面
(2)指令模擬
(2.1)JCC跳轉要依賴efalgs的標志位,而標志位又收到sub/cmp等指令的影響,如果逆向人員順着sub/cmp等指令找JCC,會很容易暴露關鍵的JCC指令(我第一次就是用這種思路分析的),但找了很久都沒找到關鍵的JCC指令,原因就是sub、cmp這種指令被混淆和模擬,請看下面的推導過程:
- -a = ~a+1 => ~a = -a -1
- ~(~a+b) = ~(-a-1+b) = -(-a-1+b)-1 = a-b => a-b = Not(NotT(a)+b)
//異或,這里相當於取反 // 請確保,n不是0,就是1 // 0 -> 1 1 -> 0 public static int flip(int n) { return n ^ 1; } // 如果n是非負數,返回1 // 如果n是負數,返回0 public static int sign(int n) { // (n >> 31) & 1: 非負數結果是0,負數結果是1 // >>> >> //通過flip反轉: 非負數結果是1,負數結果是0 return flip((n >> 31) & 1); } //整個局部變量,只有c是自然數,其他取值都是0或1; public static int getMax2(int a, int b) { //這里任然可能溢出 int c = a - b; //分別求出三個值的符號狀態已判斷正負 int sa = sign(a); int sb = sign(b); int sc = sign(c); //a和b的符號一樣嗎?異或試試了 int difSab = sa ^ sb; int sameSab = flip(difSab);//a和b符號相同就是1,否則是0,這樣才符合常識和直覺 // 返回a的條件 returnA == 1 應該返回a; // returnA == 0,不應該返回a // difSab和sameSab是互斥的; // a和b的符號不同,並且a非負,返回a,也就是difSab * sa=1; // 或則a和b符號相同,此時a-b絕對不會溢出,並且c符號是正的(說明a>b),返回a;中間的+相當於或, +前后是互斥的 int returnA = difSab * sa + sameSab * sc; // a和b只能返回1個 int returnB = flip(returnA); return a * returnA + b * returnB; }
public static int add(int a, int b){ int sum = a; while(b!=0){ sum = a^b; b = (a&b)<<1; a = sum; } return sum; } public static int negNum(int a){ return add(~a,1); } public static int minus(int a,int b){ return add(a,negNum(b)); } public static int multi(int a,int b){ int rest = 0; while(b!=0){ if((b&1)!=0){//最右位是1,需要相加 rest = add(rest,a); } a=a<<1; //配合b逐位檢查是否為1 b=b>>>1;//b逐位檢查是否為1,如果是1就要加a } return rest; } public static boolean isNeg(int a){ return a<0; } /* * a/b就是不停地循環a-b,直到a-b<0; * */ public static int div(int a, int b){ //先轉成正數 int x = isNeg(a)?negNum(a):a; int y = isNeg(b)?negNum(b):b; int rest = 0; for(int i =31;i>-1;i=minus(i,1)){ if((x>>i)>=y){ rest = rest | (1<<i); x = minus(x,y<<i); } } return isNeg(a)^isNeg(b)?negNum(rest):rest; } public static int divide(int a, int b){ if(b==0){ System.out.println("divisor is 0"); return 0; } if(a == Integer.MIN_VALUE&&b==Integer.MIN_VALUE){ return 1; }else if(b==Integer.MIN_VALUE){ return 0; }else if(a == Integer.MIN_VALUE){ int rest = div(add(a,1),b); return add(rest,div(minus(a,multi(rest,b)),b)); }else{ return div(a,b); } }
前面做的CMP、sub等指令,結果都會反饋到eflags的ZF位,不考慮其他位,當eflags=0x40時,ZF=1,JCC指令才會根據實際情況跳轉, 所以要想辦法改變eflags的ZF位。很不幸的是:x86匯編指並未提供可以直接修改ZF位的指令(只有STC、CTC、CMC、bt等少數指令可以修改CF位),這個該怎么修改ZF位了?
VM的一大特點:寄存器都是虛擬的,存放在棧中,所以VM的eflags是可以隨便改的!那么vm的eflags值是怎么計算得到的了? 計算方式如下:
- eflags = and( eflags1, 0x815) + and( eflags2, not(0x815)) ,其中eflag1和eflags2都是Nand(sn,sn)+隨機數得到的,不過這兩個數不重要,只要eflags的ZF=0就行;下面標紅的這段就是前半段and(eflags1,0x815),eflags1原值0x286,保存在ecx;0x815在eax,and eax,ecx后把結果保存在eax=0x4,然后寫入epb+4的位置(也即是虛擬eflags的位置)
-
zf = and(0x40, eflags) ZF取決於原eflags的值。具體到匯編代碼層面,用的還是and eax, ecx;,所以上面要重點對這行代碼下斷點調試;eflags保存在ecx(應該是0x246或0xFFFFxxxx形式),0x40保存在eax,所以斷點可以根據這兩個條件篩選;執行完后的結果保存在eax,然后寫回棧上面的VM_CONTEXT中的eflags位置,這樣代碼執行完后如果eax=0,要手動改成0x40;
參考: 1、https://www.52pojie.cn/thread-1304279-1-2.html 爆破vm代碼關鍵點之某文本編輯輯xxxxEdit4.3.1(4480)的分析與爆破
2、https://bbs.pediy.com/thread-224732.htm 談談vmp的爆破
3、https://www.52pojie.cn/thread-1036956-1-1.html VMP學習筆記之萬用門
4、https://www.bilibili.com/video/BV1444y187AC?p=6 a和b比較大小、加減乘除的實現