1. 程式人生 > 實用技巧 >jarvisoj-軟體密碼破解-2(CFF_100_1)

jarvisoj-軟體密碼破解-2(CFF_100_1)

下載附件後發現是個win32下的可執行檔案,拖入IDA後,F5檢視主函式但出現了錯誤,將main區域的彙編程式碼全部選擇,右鍵點選create funcion 即可解決,但還是錯誤,原因說是sp的錯誤,這時點選option的general,將裡面的Stack pointer打勾,發現mian函式最後return 的值為-4,將其改為0即可順利反編譯出主函式!

主函式的內容為下:

int __usercall wmain@<eax>(int a1@<edi>, int a2, int a3)
{
  unsigned int v3; // kr00_4
  __int16 *v5; //
eax int v6; // esi __int16 v7; // cx char v8; // al unsigned __int16 v9[512]; // [esp+Ch] [ebp-504h] void (__cdecl *MultiByteStr[64])(int); // [esp+40Ch] [ebp-104h] if ( a2 != 1 )//有引數 { OutputDebugStringW(*(LPCWSTR *)(a3 + 4)); memset(MultiByteStr, 0, 0x100u); v5 = *(__int16 **)(a3 + 4); v6
= (int)(v5 + 1); do { v7 = *v5; ++v5; } while ( v7 ); WideCharToMultiByte(0, 0, *(LPCWSTR *)(a3 + 4), -1, (LPSTR)MultiByteStr, ((signed int)v5 - v6) >> 1, 0, 0); __debugbreak(); // inc 3斷點!!!! v8 = *off_40FEC0; MultiByteStr[0](a1); JUMPOUT(dword_401150[
0]); }
//沒有引數 printf(
"%s\n", off_40FEC0); wprintf(L"please input your password:\n"); wscanf(L"%s", v9); v3 = wcslen(v9); if ( v3 > 0x10 || !v3 || sub_401180((char *)v9) == -1 ) return 0; wprintf(L"{FLAG:%s}\n", v9); return 0; }

可以清晰的知道,當我們輸入的密碼<=16且不為空,sub_401180((char *)v9) != -1,就會把我們的flag字串輸出!這裡要注意的是,主函式的最開始對輸入的引數進行了判斷,引數會影響程式的執行方向,而且還有一個很明顯的inc3斷點!!!

進入關鍵函式sub_401180()函式檢視,如下:

  1 int __thiscall sub_401180(char *this)
  2 {
  3   char *v1; // ebx
  4   LPWSTR v2; // eax
  5   LPWSTR v3; // edx
  6   WCHAR v4; // cx
  7   unsigned int v5; // eax
  8   __int16 *v6; // edi
  9   WCHAR v7; // cx
 10   __int16 *v8; // edi
 11   __int16 v9; // ax
 12   char *v10; // eax
 13   __int16 v11; // cx
 14   unsigned int v12; // eax
 15   __int16 *v13; // edi
 16   __int16 v14; // cx
 17   int result; // eax
 18   struct _PROCESS_INFORMATION ProcessInformation; // [esp+10h] [ebp-11A8h]
 19   SIZE_T NumberOfBytesRead; // [esp+20h] [ebp-1198h]
 20   SIZE_T NumberOfBytesWritten; // [esp+24h] [ebp-1194h]
 21   struct _STARTUPINFOW StartupInfo; // [esp+28h] [ebp-1190h]
 22   struct _DEBUG_EVENT DebugEvent; // [esp+70h] [ebp-1148h]
 23   CONTEXT Context; // [esp+D0h] [ebp-10E8h]
 24   int v22; // [esp+3A0h] [ebp-E18h]
 25   int v23; // [esp+3A4h] [ebp-E14h]
 26   int v24; // [esp+3A8h] [ebp-E10h]
 27   char v25; // [esp+3ACh] [ebp-E0Ch]
 28   int Buffer; // [esp+3B0h] [ebp-E08h]
 29   int v27; // [esp+3B4h] [ebp-E04h]
 30   int v28; // [esp+3B8h] [ebp-E00h]
 31   int v29; // [esp+3BCh] [ebp-DFCh]
 32   __int16 v30; // [esp+7AEh] [ebp-A0Ah]
 33   WCHAR CommandLine; // [esp+7B0h] [ebp-A08h]
 34 
 35   ProcessInformation.hProcess = 0;
 36   ProcessInformation.hThread = 0;
 37   ProcessInformation.dwProcessId = 0;
 38   ProcessInformation.dwThreadId = 0;
 39   v1 = this;
 40   memset(&StartupInfo, 0, 0x44u);
 41   memset(&DebugEvent, 0, 0x60u);
 42   StartupInfo.cb = 68;
 43   memset(&CommandLine, 0, 0xA00u);
 44   v2 = GetCommandLineW();
 45   v3 = v2;
 46   do
 47   {
 48     v4 = *v2;
 49     ++v2;
 50   }
 51   while ( v4 );
 52   v5 = (char *)v2 - (char *)v3;
 53   v6 = &v30;
 54   do
 55   {
 56     v7 = v6[1];
 57     ++v6;
 58   }
 59   while ( v7 );
 60   qmemcpy(v6, v3, v5);
 61   v8 = &v30;
 62   do
 63   {
 64     v9 = v8[1];
 65     ++v8;
 66   }
 67   while ( v9 );
 68   *(_DWORD *)v8 = 32;
 69   v10 = v1;
 70   do
 71   {
 72     v11 = *(_WORD *)v10;
 73     v10 += 2;
 74   }
 75   while ( v11 );
 76   v12 = v10 - v1;
 77   v13 = &v30;
 78   do
 79   {
 80     v14 = v13[1];
 81     ++v13;
 82   }
 83   while ( v14 );
 84   qmemcpy(v13, v1, v12);
    //此函式為建立一個子執行緒,並進入除錯狀態
85 CreateProcessW(0, &CommandLine, 0, 0, 0, 1u, 0, 0, &StartupInfo, &ProcessInformation);
    //此函式通知被除錯程式繼續執行
86 ContinueDebugEvent(ProcessInformation.dwProcessId, ProcessInformation.dwThreadId, 0x10002u);
    //此函式等待被除錯事件(時間無限),直到DebugEvent.dwDebugEventCode == 8(OUTPUT_DEBUG_STRING_EVENT)產生即呼叫了outputdebugstring
87 WaitForDebugEvent(&DebugEvent, 0xFFFFFFFF); 88 while ( DebugEvent.dwDebugEventCode != 8 ) 89 { 90 ContinueDebugEvent(ProcessInformation.dwProcessId, ProcessInformation.dwThreadId, 0x10002u); 91 WaitForDebugEvent(&DebugEvent, 0xFFFFFFFF); 92 } 93 LOWORD(Buffer) = 0;
    //此函式讀取程序記憶體,並把資料儲存至Buffer中
94 ReadProcessMemory( 95 ProcessInformation.hProcess, 96 DebugEvent.u.CreateThread.hThread, 97 &Buffer, 98 DebugEvent.u.DebugString.nDebugStringLength, 99 &NumberOfBytesRead);
    //被除錯程式繼續執行
100 ContinueDebugEvent(ProcessInformation.dwProcessId, ProcessInformation.dwThreadId, 0x10002u);
    //等待除錯事件
101 WaitForDebugEvent(&DebugEvent, 0xFFFFFFFF); 102 Context.ContextFlags = 65537;
    //讀取程序記憶體,並把資料儲存至Buffer內
103 ReadProcessMemory( 104 ProcessInformation.hProcess, 105 DebugEvent.u.CreateThread.hThread, 106 &Buffer, 107 DebugEvent.u.DebugString.nDebugStringLength, 108 &NumberOfBytesRead);
    //得到中斷執行緒的上下文資訊,此時Context.Eip就是發生中斷處的下一個地址
109 GetThreadContext(ProcessInformation.hThread, &Context); 110 v22 = 0x148A4690; 111 v23 = 0xF14300E; 112 v24 = 0x75C83B41; 113 v25 = 0xF5u;
    //在發生中斷處,將v22~v25寫入程序記憶體
114 WriteProcessMemory(ProcessInformation.hProcess, (LPVOID)(Context.Eip - 1), &v22, 0xDu, &NumberOfBytesWritten);
    //被除錯程式繼續執行
115 ContinueDebugEvent(ProcessInformation.dwProcessId, ProcessInformation.dwThreadId, 0x10002u);
    //等待除錯事件
116 WaitForDebugEvent(&DebugEvent, 0xFFFFFFFF); 117 LOWORD(Buffer) = 0;
  //讀取程序記憶體,並將資料儲存至Buffer內
118 ReadProcessMemory( 119 ProcessInformation.hProcess, 120 DebugEvent.u.CreateThread.hThread, 121 &Buffer, 122 DebugEvent.u.DebugString.nDebugStringLength, 123 &NumberOfBytesRead);
    //最後的判斷
124 if ( Buffer != 0x2B5C5C25 || v27 != 0x36195D2F || v28 != 0x7672642C ) 125 result = -1; 126 else 127 result = -(v29 != 0x524E6680); 128 return result; 129 }

發現裡面是一些windows api,相應的解釋,註釋已經給出。大概的操作就是在主程序(父程序)中開啟一個子程序,並進入除錯狀態,利用子程序對執行的程序記憶體進行一些寫入資料和讀取記憶體操作。由於對子程式進行了除錯的判斷,因此父程序很難接收到事件的訊息,所以動態除錯很困難(但也可以!我最開始動態除錯,但我疏忽了以為只有12個位元組,所以只弄出了前12個(>o<),當然也可以全部弄出來,不過很麻煩!)。回到函式中,有幾個關鍵的點:

①對程序的記憶體進行了寫入資料的操作

②寫入資料的地址在中斷地址處

③取出程序記憶體的的資料,並比較資料

所以,我們要知道這個中斷點在哪裡,才能知道是對那個區域進行了寫入操作,恰好主函式中有一個明顯的inc 3中斷操作,利用ollydebug開啟,找到相應位置,如下

因此我們需要把v22~v25的資料內容寫入,注意是小端順序(在執行 除錯到這裡的時候,中途會直接終止,不過問題也不大,可以直接jmp這裡,寫入後的資料如下:

繼續往下執行可以知道,把我們輸入的資料與'elcome to CFF test!'進行異或運算,再把結果都+1,得出的資料通過呼叫outputdebugstring,使得waitfordebugevent函式接受到該事件,讀取異或後的結果,與給出的資料進行比較。

到這裡,程式的邏輯已經清楚了!,只需寫出解密指令碼即可

如下:

b=[0x25,0x5c,0x5c,0x2b,0x2f,0x5d,0x19,0x36,0x2c,0x64,0x72,0x76,0x80,0x66,0x4e,0x52]
c='elcome to CFF test!'
print(len(c))
for i in range(16):
    x=chr(ord(c[i])^(b[i]-1))
    print(x,end='')

最後的執行結果:

結果即是flag!

解這道題可是花了我好長時間,一開始看writeup各種看不懂,(還沒學到家呀!),查資料,這才慢慢弄懂了,但其實還有一些地方不太懂,就比如有引數才會執行的inc 3指令,程式是怎麼get到這個地址的,還是有點沒明白,希望有大佬可以告訴我,自己以後如果知道了,會來更的!關於裡面的一些api函式,強烈建議大家看這個部落格https://blog.csdn.net/xiammy/article/details/1675793,你們會收穫到驚喜的!