VMP加殼(三):VMP殼爆破實戰-破解某編輯類軟件&指令替換原理


  這次爆破的是某編輯類軟件,版本是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這種指令被混淆和模擬,請看下面的推導過程:

      cmp指令本質上是減法,只不過結果不會寫回操作數,所以模擬減法就很重要了!下面是減法的模擬過程:
  •       -a = ~a+1  => ~a = -a -1
  •       ~(~a+b) = ~(-a-1+b) = -(-a-1+b)-1 = a-b  => a-b = Not(NotT(a)+b)
        a-b最終可以由Not(NotT(a)+b)來表示,而Not(a)又可以用Nand(a,a)來表示,這就導致了 VMP中not eax; not ecx; and eax,ecx代碼大量出現,在trace的時候某些代碼甚至執行了上千次
 
     (2.2)a和b比大小,大家第一想到的就是if(a>b),或則cmp a,b,這里可以用另一種方式替代:下面的函數也能返回a和b較大的數,但是沒有任何大於符號,編譯成匯編后會讓逆向人員更加難以理解!這么做的本質是: 把互斥條件的if else用+號替換
    //異或,這里相當於取反
    // 請確保,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;
    }
     (2.3)同理,加減乘除都可以“迂回”實現,而不是直接用+、-、*、/  這些明顯的運算符號,比如:
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);
        }
    }
     (3)eflags位模擬

       前面做的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比較大小、加減乘除的實現


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM