天天看點

《C++ 黑客程式設計揭秘與防範(第2版)》——6.7 打造一個密碼顯示器

本節書摘來自異步社群出版社《c++ 黑客程式設計揭秘與防範(第2版)》一書中的第6章,第6.7節,作者:冀雲,更多章節内容可以通路雲栖社群“異步社群”公衆号檢視。

c++ 黑客程式設計揭秘與防範(第2版)

關于系統提供的調試api函數已經學習了不少,而且基本上常用到的函數都已學過。下面用調試api編寫一個能夠顯示密碼的程式。讀者别以為這裡寫的程式什麼密碼都能顯示,這是不可能的。下面針對前面的crackme來編寫一個顯示密碼的程式。

在編寫關于crackme的密碼顯示程式以前需要準備兩項工作,第一項工作是知道要在什麼地方合理地下斷點,第二項工作是從哪裡能讀取到密碼。帶着這兩個問題重新來思考一下。在這裡的程式中,要對兩個字元串進行比較,而比較的函數是strcmp(),該函數有兩個參數,分别是輸入的密碼和真正的密碼。也就是說,在調用strcmp()函數的位置下斷點,通過檢視它的參數是可以擷取到正确的密碼的。在調用strcmp()函數的位置設定int3斷點,也就是将0xcc機器碼寫入這個位址。用od看一下調用strcmp()函數的位址,如圖6-75所示。

《C++ 黑客程式設計揭秘與防範(第2版)》——6.7 打造一個密碼顯示器

圖6-75 調用strcmp()函數的位址

從圖6-75中可以看出,調用strcmp()函數的位址為00401e9e。有了這個位址,隻要找到該函數的兩個參數,就可以找到輸入的錯誤的密碼及正确的密碼。從圖6-75中可以看出,正确的密碼的起始位址儲存在edx中,錯誤的密碼的起始位址儲存在ecx中。隻要在00401e9e位址處下斷點,并通過線程環境讀取edx和ecx寄存器值就可以得到兩個密碼的起始位址。

進行準備的工作已經做好了,下面來寫一個控制台的程式。先定義兩個常量,一個是用來設定斷點的位址,另一個是int3指令的機器碼。定義如下:

  // 啟動資訊

  startupinfo si = { 0 };

  si.cb = sizeof(startupinfo);

  getstartupinfo(&si);

  // 程序資訊

  process_information pi = { 0 };

  // 建立被調試程序

  bool bret = createprocess(pszfilename,

            null,

            false,

            debug_process | debug_only_this_process,

            &si,

            π);

  if ( bret == false )

  {

    printf("createprocess error rn");

    return -1;

  }<code>`</code>

然後進入調試循環,要處理兩個調試事件,一個是createprocess_debug_event,另一個是exception_debug_event下的exception_breakpoint。處理create process_debug_event的代碼如下:

// 産生異常時的調試事件

case exception_debug_event:

{

  // 判斷異常類型

  switch ( de.u.exception.exceptionrecord.exceptioncode )

    // int3類型的異常

  case exception_breakpoint:

    {

      // 擷取線程環境

      context.contextflags = context_full;

      getthreadcontext(pi.hthread, &amp;context);

      // 判斷是否斷在設定的斷點位置處

      if ( (bp_va + 1) == context.eip )

      {

        // 讀取正确的密碼

        readprocessmemory(pi.hprocess,

            (lpvoid)context.edx,

            (lpvoid)pszpassword,

            maxbyte,

            &amp;dwreadwritenum);

        // 讀取錯誤密碼

            (lpvoid)context.ecx,

            (lpvoid)pszerrorpass,

        printf("你輸入的密碼是: %s rn", pszerrorpass);

        printf("正确的密碼是: %s rn", pszpassword);

        //指令執行了int3而被中斷

        // int3的機器指令長度為1位元組

        // 是以需要将eip減一來修正eip

        // eip是指令指針寄存器

        // 其中儲存着下條要執行指令的位址

        context.eip --;

        // 修正原來該位址的機器碼

        writeprocessmemory(pi.hprocess,

              (lpvoid)bp_va,

              (lpvoid)&amp;boldbyte,

              sizeof(byte),

              &amp;dwreadwritenum);

        // 設定目前的線程環境

        setthreadcontext(pi.hthread, &amp;context);

      }

      break;

    }

  }

}<code>`</code>

對于調試事件的處理,應該放到調試循環中。上面的代碼給出的是對調試事件的處理,再來看一下調試循環的大體代碼: