名詞注釋
System breakpoint:系統斷點,OllyDbg用CreateProcessA加載DEBUG_ONLY_THIS_PROCESS參數執行,程序運行之后會觸發一個INT13,在系統空間里。
Entry point of main module:主模塊的入口點,即文件的入口點。
WinMain:程序的WinMain()函數入口點
OD的設置中-選項-事件中設置
OD快捷鍵熟悉
1、F2 下斷點,
2、Alt+b 打開斷點編輯器,可編輯所有下過的斷點
3、空格鍵 可快速切換斷點狀態。
4、Ctrl+F9.當位於某個CALL中,這時想返回到調用這個CALL的地方時,可以按“Ctrl+F9”快捷鍵執行返回功能。這樣OD就會停在遇到的第一個返回命令(如RET、RETF或IRET)。
5、Alt+F9 如果跟進系統DLL提供的API函數中,此時想返回到應用程序領空里,可以按快捷鍵“Alt+F9”執行返回到用戶代碼命令。
6、Ctrl+G 跳轉到API、地址的位置
逆向之猜
逆向與開發的知識是成正比關系,只有對開發特別熟悉,逆向一個程序才能猜測到該用哪個關鍵的API才能快速定位到程序的數據處理。
通過PEID查看程序特征,根據程序語言用IDE生成特征或者熟悉開發的API函數。就可以更方便地讓我們定位到獲取edit值的函數。
【GetDlgItemText 函數】 用於獲取對話框中指定控件的標題或文本。
‘使用OD的快捷鍵【Ctrl+G】跳轉到API的位置下斷點。
F2設置斷點,當【GetDlgItemTextA】這個函數被調用OD就會中斷
【ALT+B】快捷鍵可以打開斷點窗口查看,在斷點位置按【空格鍵】可以激活與禁用斷點。
按快捷鍵【Ctrl+F9】可以回到調用函數的尾部ret處。
算法逆向
F7跟進004011E5地址內的函數,進入子程序call 00401340,特別值得注意的是00401359是跳轉到4013680的,相關匯編代碼注釋如下:
加密函數匯編注釋如下:
00401340 push ebp ; ebp入棧
00401341 mov ebp,dword ptr ss:[esp+0xC] ; 將用戶名移動到ebp中
00401345 push esi ; esi入棧
00401346 push edi ; edi入棧
00401347 mov edi,dword ptr ss:[esp+0x18] ; 將參數從堆棧中傳給edi(用戶名長度值) edi = 5
0040134B mov ecx,0x3 ; ecx = 3
00401350 xor esi,esi ; esi 清 0
00401352 xor eax,eax ; eax 清0
00401354 cmp edi,ecx
00401356 jle XTraceMe.00401379 ; edi<ecx條件成立時跳轉,i<len
00401358 push ebx
00401359 /cmp eax,0x7 ; 比較eax與7的值
0040135C jle XTraceMe.00401360 ; 當等於7,ZF=1短跳轉
0040135E |xor eax,eax
00401360 |xor edx,edx ; edx清0
00401362 |xor ebx,ebx
00401364 |mov dl,byte ptr ds:[ecx+ebp] ; 地址低8位的一個字節,dl是存儲一個字節的寄存器,ecx = 3, [ecx+ebp] = d
00401367 |mov bl,byte ptr ds:[eax+0x405030] ; 00405030 0C 0A 13 09 0C 0B 0A 08
0040136D |imul edx,ebx ; edx * ebx 賦值給 edx, dl為高8位,存儲1字節數
00401370 |add esi,edx ; edx+esi,把值賦予給esi ,esi = 4B0 + 3F2
00401372 |inc ecx ; 遞增指令,ecx 由 3 -> 4 -> 5
00401373 |inc eax ; eax = 1,eax++
00401374 |cmp ecx,edi
00401376 \jl XTraceMe.00401359 ; ecx 是否大於等於5(用戶名長度), jl指令大於不等於滿足時跳轉
00401378 pop ebx
00401379 push esi ; /<%ld>
0040137A push TraceMe.00405078 ; |Format = "%ld"
0040137F push ebp ; |s
00401380 call dword ptr ds:[<&USER32.wsprintfA>] ; \wsprintfA
00401386 mov eax,dword ptr ss:[esp+0x1C]
0040138A add esp,0xC
0040138D push ebp ; /String2
0040138E push eax ; |String1
0040138F call dword ptr ds:[<&KERNEL32.lstrcmpA>] ; \lstrcmpA
程序原先輸入的用戶名:abcde、序列號:123456。在堆棧窗口看到的是d、e,也就是
name[3]=‘d’,
name[4]=‘e’
根據00401367 地址處判斷,密碼數組索引第3位之后的值逐位取出與固定地址的值比對。然后在0040137A 會輸出密鑰的值2201。
仔細逆推一遍:
edx = 64 * 0C = 4B0
edx = 65 * 0A = 3F2
4B0 + 3F2 = 8A2
8A2對應的十進制為2210
逆向結論:
abcde轉換為十六進制從索引值第3位開始逐位取值,64:d、65:e ,然后與00405030 0C 0A 13 09 0C 0B 0A 08對應的值進行相乘然后累加。得出的8A2轉換成十進制2210就是密碼的值。
把加密函數取出來就成了算號注冊機,下面是加密函數反匯編轉換來的C代碼:
#include "stdafx.h"
#include <string.h>
//char name[65] = "abcdexxxx";
char name[65];
char table[8] = { 0xC ,0xA ,0x13 ,0x09 ,0x0C ,0x0B ,0x0A ,0x08 };
int main()
{
printf(" 輸入key:\n ");
scanf_s("%s",name,65);
//會用到一個固定地址的值
//會用到姓名里的后兩位
//eax = i
int user_len = strlen(name);
int key_code= 0; //esi
int count_ecx = 3; //esi
int eax = 0; //eax
for (; count_ecx<user_len;)
{
if (eax>7)
eax = 0;
int ebx = 0;
int edx = 0;
edx = name[count_ecx];
ebx = table[eax];
ebx = edx * ebx;
key_code += ebx;
count_ecx++;
eax++;
}
printf("key_code: %d", key_code);
return 0;
}
暴力破解
在定位到GetDlgItemText這個API處,F8單步步過向下跟隨到有test判斷的地方,注意觀察數據堆棧區的位置。
匯編指令注釋如下:
0040119C mov esi,dword ptr ss:[esp+0x100] ; Case 3F5 of switch 0040115E
004011A3 mov edi,dword ptr ds:[<&USER32.GetDlgIte>; user32.GetDlgItemTextA
004011A9 push ebx
004011AA lea eax,dword ptr ss:[esp+0x4C]
004011AE push 0x51 ; /Count = 51 (81.)
004011B0 push eax ; |Buffer
004011B1 push 0x6E ; |ControlID = 6E (110.)
004011B3 push esi ; |hWnd
004011B4 call edi ; \GetDlgItemTextA
004011B6 lea ecx,dword ptr ss:[esp+0x9C]
004011BD push 0x65 ; /最大字符數
004011BF push ecx ; |文本緩沖區指針
004011C0 push 0x3E8 ; |控件標識
004011C5 push esi ; |對話框句柄
004011C6 mov ebx,eax ; |將用戶名的長度轉到ebx中
004011C8 call edi ; \GetDlgItemTextA
004011CA mov al,byte ptr ss:[esp+0x4C] ; 將用戶名的第一個字節給al
004011CE test al,al ; 檢查有沒有輸入用戶名
004011D0 je XTraceMe.00401248 ; 如果沒有輸入用戶名跳走,告知輸入的字符太少,zf=0跳轉
004011D2 cmp ebx,0x5
004011D5 jl XTraceMe.00401248 ; 如果用戶名不大於5那么就跳轉到錯誤提示處
004011D7 lea edx,dword ptr ss:[esp+0x4C] ; 用戶名地址放到edx中
004011DB push ebx ; 用戶名長度
004011DC lea eax,dword ptr ss:[esp+0xA0] ; 密碼地址放到eax
004011E3 push edx ; 用戶名地址入棧
004011E4 push eax ; 密碼地址入棧
004011E5 call TraceMe.00401340 ; 調用函數,相當於a("123456",abcde,5)
004011EA mov edi,dword ptr ds:[<&USER32.GetDlgIte>; user32.GetDlgItem
004011F0 add esp,0xC ; 平衡堆棧
004011F3 test eax,eax ; 函數返回值都是在eax里面的,eax=0注冊失敗,eax=1注冊成功
004011F5 nop ; zf標志位為0,滿足條件時跳轉
第一個test指令首先對比用戶名是否大於5,不大於5就跳轉到彈出錯誤提示的地方。否則繼續執行。F8單步步過進入【GetDlgItemTextA】后面的調用查看相關的代碼。004011E5地址處call調用一個函數,並且在之前push了三個參數。
第二處test指令下面那條je跳轉指令用nop填充掉。
然后右鍵【復制到可執行文件】-【所有修改】
選擇全部復制
選擇【保存文件】,暴力破解就完成了
成功后輸入任何密碼都提示成功彈框,截圖如下:
樣本引用
《加密與解密》這本書里的附帶小程序TraceMe.exe。