羽夏逆向指引——破解第一個程序


寫在前面

  此系列是本人一個字一個字碼出來的,包括示例和實驗截圖。可能有錯誤或者不全面的地方,如有錯誤,歡迎批評指正,本教程將會長期更新。 如有好的建議,歡迎反饋。碼字不易,如果本篇文章有幫助你的,如有閑錢,可以打賞支持我的創作。如想轉載,請把我的轉載信息附在文章后面,並聲明我的個人信息和本人博客地址即可,但必須事先通知我

你如果是從中間插過來看的,請仔細閱讀 羽夏逆向指引——序 ,方便學習本教程。

小白鼠

  本次示例使用C++,也就是編譯型語言,為了方便查看匯編代碼和編碼故使用VS 2022,其他IDE即可,代碼如下:

#include <iostream>
using namespace std;

int main(int argc, char* argv[])
{
    int x = 0;

    cout << "請輸入密鑰:" << endl;
    cin >> x;
    if (x == 1234)
    {
        cout << "成功,By.寂靜的羽夏,CNBLOG Only!!!" << endl;
    }
    else
    {
        cout << "失敗,By.寂靜的羽夏,CNBLOG Only!!!" << endl;
    }

    system("pause");
    return 0;
}

  是不是代碼很簡單?判斷你輸入的是不是1234,是的話就是成功,反之失敗。這是最簡單的校驗示例了。

分析

  代碼的流程十分簡單,我們可以用下面的流程圖進行表示:

graph TD; A("程序開始")-->B("顯示信息,提示輸入")-->C[/"獲取輸入"/]-->D{"判斷是否和 1234 相等"}; D--YES-->E("輸出正確信息")-->F("程序結束"); D-.NO.->G("輸出錯誤信息")-->F;

  由於是初次分析編譯型程序,我們直接在IDE先看看它的反匯編:

#include <iostream>
using namespace std;

int main(int argc, char* argv[])
{
00E42550  push        ebp
00E42551  mov         ebp,esp
00E42553  sub         esp,0D0h
00E42559  push        ebx
00E4255A  push        esi
00E4255B  push        edi
00E4255C  lea         edi,[ebp-10h]
00E4255F  mov         ecx,4
00E42564  mov         eax,0CCCCCCCCh
00E42569  rep stos    dword ptr es:[edi]
00E4256B  mov         eax,dword ptr [__security_cookie (0E4C004h)]
00E42570  xor         eax,ebp
00E42572  mov         dword ptr [ebp-4],eax
00E42575  mov         ecx,offset _3226632D_ConsoleApplication3@cpp (0E4F066h)
00E4257A  call        @__CheckForDebuggerJustMyCode@4 (0E41384h)
    int x = 0;
00E4257F  mov         dword ptr [x],0

    cout << "請輸入密鑰:" << endl;
00E42586  mov         esi,esp
00E42588  push        offset std::endl<char,std::char_traits<char> > (0E4103Ch)
00E4258D  push        offset string "\xc7\xeb\xca\xe4\xc8\xeb\xc3\xdc\xd4\xbf\xa3\xba" (0E49B30h)
00E42592  mov         eax,dword ptr [__imp_std::cout (0E4D0DCh)]
00E42597  push        eax
00E42598  call        std::operator<<<std::char_traits<char> > (0E411A9h)
00E4259D  add         esp,8
00E425A0  mov         ecx,eax
00E425A2  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (0E4D0A8h)]
00E425A8  cmp         esi,esp
00E425AA  call        __RTC_CheckEsp (0E4128Fh)
    cin >> x;
00E425AF  mov         esi,esp
00E425B1  lea         eax,[x]
00E425B4  push        eax
00E425B5  mov         ecx,dword ptr [__imp_std::cin (0E4D098h)]
00E425BB  call        dword ptr [__imp_std::basic_istream<char,std::char_traits<char> >::operator>> (0E4D09Ch)]
00E425C1  cmp         esi,esp
00E425C3  call        __RTC_CheckEsp (0E4128Fh)
    if (x == 1234)
00E425C8  cmp         dword ptr [x],4D2h
00E425CF  jne         __$EncStackInitStart+0A0h (0E425FCh)
    {
        cout << "成功,By.寂靜的羽夏,CNBLOG Only!!!" << endl;
00E425D1  mov         esi,esp
00E425D3  push        offset std::endl<char,std::char_traits<char> > (0E4103Ch)
00E425D8  push        offset string "\xb3\xc9\xb9\xa6\xa3\xacBy.\xbc\xc5\xbe\xb2\xb5\xc4\xd3\xf0\xcf\xc4\xa3\xacCNBLOG Onl@"... (0E49B64h)
00E425DD  mov         eax,dword ptr [__imp_std::cout (0E4D0DCh)]
00E425E2  push        eax
00E425E3  call        std::operator<<<std::char_traits<char> > (0E411A9h)
00E425E8  add         esp,8
00E425EB  mov         ecx,eax
00E425ED  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (0E4D0A8h)]
00E425F3  cmp         esi,esp
00E425F5  call        __RTC_CheckEsp (0E4128Fh)
    }
00E425FA  jmp         __$EncStackInitStart+0C9h (0E42625h)
    else
    {
        cout << "失敗,By.寂靜的羽夏,CNBLOG Only!!!" << endl;
00E425FC  mov         esi,esp
00E425FE  push        offset std::endl<char,std::char_traits<char> > (0E4103Ch)  
00E42603  push        offset string "\xca\xa7\xb0\xdc\xa3\xacBy.\xbc\xc5\xbe\xb2\xb5\xc4\xd3\xf0\xcf\xc4\xa3\xacCNBLOG Onl@"... (0E49D40h)
00E42608  mov         eax,dword ptr [__imp_std::cout (0E4D0DCh)]
00E4260D  push        eax
00E4260E  call        std::operator<<<std::char_traits<char> > (0E411A9h)
00E42613  add         esp,8
00E42616  mov         ecx,eax
00E42618  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (0E4D0A8h)]
00E4261E  cmp         esi,esp
00E42620  call        __RTC_CheckEsp (0E4128Fh)
    }

    system("pause");
00E42625  mov         esi,esp
00E42627  push        offset string "pause" (0E49B40h)
00E4262C  call        dword ptr [__imp__system (0E4D1D4h)]
00E42632  add         esp,4
00E42635  cmp         esi,esp
00E42637  call        __RTC_CheckEsp (0E4128Fh)
    return 0;
00E4263C  xor         eax,eax
}

  看不懂匯編的意思的,請自學匯編。看不懂整體匯編語言的作用,請學習我的教程 羽夏看C語言 即可看懂。
  輸出信息最關鍵的當然是判斷了,也就是下面這個部分:

    if (x == 1234)
00E425C8  cmp         dword ptr [x],4D2h
00E425CF  jne         __$EncStackInitStart+0A0h (0E425FCh)

  如果我把jne給干掉,無論我輸入什么,都會輸出正確。也就是我們把判斷失敗的鏈條給砍斷了,只能走這條路。

graph TD; A("程序開始")-->B("顯示信息,提示輸入")-->C[/"獲取輸入"/]-->D{"判斷是否和 1234 相等"}; D--->E("輸出正確信息")-->F("程序結束"); G("輸出錯誤信息")-->F("程序結束");

  上面只改匯編代碼的,通過修改匯編以修改代碼執行流程實現自己的目的,就是我們所謂的暴力破解,也就是patch,俗稱打補丁。如果我逆向分析得到密鑰就是1234,直接輸入,這東西也就是所謂的key。當然,現實中驗證不可能這么簡單,它們往往是通過某種算法,通過獲取計算機獨特的信息生成注冊碼(如果我們能夠知道算法是什么,根據算法來寫出算key的軟件,俗稱注冊機),然后根據校驗函數進行校驗,大大增大了分析難度。這就是說為什么爆破比注冊機簡單很多。
  但是爆破也不是那么輕松的,如果在校驗函數中生成了大量的中間結果,如果它再拿來這東西校驗的話,被破解的難度就會提高一個層次。發現被破解,直接退出程序,這往往是付費軟件防破解的一個手段。
  當然,我們破解軟件的時候不可能是拿到別人的源碼按照本篇文章開頭那樣進行分析。我們來真槍實戰一下。假設這個軟件是我們未知的,如何分析這個程序。

小試牛刀

  本小節是按照常規套路進行分析,分析不同類型的軟件可能流程不太一樣,比如分析病毒你肯定不能直接在真機上跑,有些病毒還有反虛擬機的模塊,甚至變形多態;編譯型代碼軟件分析和解釋型代碼軟件分析也不太一樣;只是分支操作有所不同。接下來是編譯型軟件的常規操作:

  查殼,看看是啥軟件編寫的:

  直接拖到IDA瞅一瞅,如果沒報錯,說明軟件可以直接分析。如果報錯,說明軟件被加殼或者加密。當然這軟件沒有加殼,所以正常,由於軟件短小精悍,我們很快定位到主函數部分,下面是它的流程:

  看到匯編代碼后,IDA會有一些注釋,看下圖:

  這個明明是字符串,但IDA沒有識別出來。這個是經常會遇到的情況。有時候IDA識別不出Unicode字符串,甚至識別錯誤以為是函數偏移,不要僅依賴這個軟件。那么我們如何處理呢?把光標放到上面字符串的首地址,按下ALT + A,將會彈出下面的窗體:

  這個就是將數據轉化為字符串,由於這個是普通的ASCII字符串,直接選C-style即可。

  轉換成功后結果如下:

  聽說IDA有一個F5的功能,的確,它是IDA的一個插件。在匯編窗口內按一下會調用Hex-ray這個插件翻譯成偽C代碼。但是不要過度依賴這個插件,因為翻譯出來的東西很多是不准確的,甚至是錯誤的。比如函數調用的參數個數、多個變量其實就是一個變量、類型不對等等。這些東西都需要依賴自身的開發經驗和分析進行調整。經過分析和重命名后,如下所示:

  通過分析,我們既得到了Key,也分析透了函數流程。

暴力破解

  IDA其實可以patch軟件的,怎么搞自行學習,比較麻煩,一旦patch就會導致原來的分析流程變化,自認為不太適用,如果用於去除花指令的話另當別論。在動態調試器里修改是最快捷的,最舒服的方式。如下的動圖進行演示:

結語

  學習本篇指引,如果IDA不會使用的話,請自行找教程進行練習。本系列教程只是個指引,故不提供IDA使用教程。IDA有如下基本操作:函數和變量的重命名、跳轉、類型轉化、搜索字符串、查找引用是最常用的操作了,請自行學習。

下一篇

  羽夏逆向指引——補丁


免責聲明!

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



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