作者:Crazyman_Army
原文來自:https://bbs.ichunqiu.com/thread-43469-1-1.html
0x00知識回顧 (由於筆者省事,沒開XP虛擬機,而且沒關閉ASLR,所以每次重載的內存地址會不一樣)
在第一章的內容中,筆者已經講了OllyDbg(簡稱OD)的界面介紹以及基礎的操作,普及了常用的匯編指令
上次課在底下的附件中,我留下了一個演示樣例,大家載入OD后搜索到字符串點擊跟蹤到反匯編窗口的時候會發現與第一章所講的程序的框架不太一樣.
尤其可見多出了很多的call指令,call指令在匯編中就是調用子程序,下面放出代碼,各位同學可以比對一下與上篇文件所貼出代碼的不同
代碼如下:
#include <windows.h>
#include <tlhelp32.h>
#include <stdio.h>
BOOL getProcess(const char *procressName);
BOOL getProcess(const char *procressName)
{
char pName[MAX_PATH];
strcpy(pName, procressName);
CharLowerBuff(pName, MAX_PATH);
PROCESSENTRY32 currentProcess;
currentProcess.dwSize = sizeof(currentProcess);
HANDLE hProcess = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hProcess == INVALID_HANDLE_VALUE)
{
printf("CreateToolhelp32Snapshot()調用失敗!\n");
return FALSE;
}
_asm NOP;
BOOL bMore = Process32First(hProcess, ¤tProcess);
while (bMore)
{
CharLowerBuff(currentProcess.szExeFile, MAX_PATH);
if (strcmp(currentProcess.szExeFile, pName) == 0)
{
CloseHandle(hProcess);
return TRUE;
}
bMore = Process32Next(hProcess, ¤tProcess);
}
CloseHandle(hProcess);
return FALSE;
}
int main()
{
char* process = "explorer.exe";
if (getProcess(process))
{
printf("發現explorer.exe\n");
system("pause");
exit(0);
}
else
{
printf("沒有發現explorer.exe\n");
printf("Cracke Success!");
}
getchar();
return 0;
}
從代碼角度來看,很明顯,這次筆者講判斷進程封裝成一個返回布爾值的函數,所以才會在核心判斷部分出現如此多的調用,那這當然不能像上次一樣改一步簡單的跳轉就解決問題,如果改得不對就會造成程序的崩潰。
下面筆者通過三種方法來繞過這個檢驗explorer.exe進程的小程序
方法一:修改判斷進程的變量名稱
在只能搜索中,我們看到了explorer.exe這個進程的字符串,我們用鼠標選中后,雙擊,在反匯編窗口中跟隨
反匯編窗口顯示的如下:
用鼠標選中這條代碼右鍵 -> 數據窗口中跟隨–> 內存地址
數據窗口隨之發生了變化,在數據窗口上選中一部分數據右鍵
右鍵 –> 復制到可執行文件
點擊放大鍵放大該窗口
然后就可以胡亂修改explorer.exe這個字符串
筆者這里就隨便修改了
修改如下:
在窗口上右鍵,保存文件
文件保存成功
點擊運行
第一種方法破解成功
方法二:確認過眼神,你不是我們的人
回到判斷的核心代碼,這里面是jnz來進行進程的判斷
那jnz 的全拼寫是 jump not zero 那其的反義詞即為 jump
Zero,即為jz 或者 je (jump equal),那這樣原來的判斷explorer.exe就改為了不判斷explorer.exe,這樣就繞過了檢測進程.
下面我們來實際操作,用鼠標選中后
雙擊
將jnz short 00291158改為 jz short 00291158或者 je short 00291158,將使用nop填充勾掉
點擊匯編
然后再保存文件,保存后運行
第二種破解方法成功
方法三:條件改強制
如下圖,這個jnz short 00101158是如果zf=0時就會跳轉
而jnz所在代碼的地址為00101137,我們嘗試讓其無條件跳轉到00101137下面一個地址,也就是00101139(下圖紅框鎖標注的區域)
雙擊地址為00101137的那條代碼,如下圖所示 將jnz short 00101158改為jmp short 00101139,點擊匯編
點擊運行按鈕
程序運行截圖:
很顯然破解成功了,那具體是怎么個原理呢,我們這里留個懸念,等用od能分析一個簡單程序后,我們回過頭來解釋這個問題
00x01 用OllyDbg分析一個簡單的程序
下面,筆者將帶領同學去用OllyDbg來分析一個簡單的程序
筆者為了方便寫了一個簡單的判斷輸入數字的程序,代碼如下:
載入OD
反匯編窗口上右鍵中文搜搜引擎 -> 智能搜索
跟蹤后的代碼可以看到:
在有程序保護的情況下,你可能無法搜索到字符串,這里先不討論,筆者將帶領同學一步一步分析這個簡單的程序
這里面push ebp movebp,esp push ecx可以簡單的理解為源碼中的int main()后的{ (這里為了方便於理解)
push Text.013E3E34,push的意思自然是壓棧
這里就不得不要說說C語言中的printf
下面是printf的函數聲明
int printf(const char *format, ...);
那在默認的情況下,printf函數應該有一個const char*型的參數,而這一行就是把“Please Input Flag Number:”這個字符串指針當成參數1,壓入棧中
mov [local.1],0x0
那究竟這個[local.1]是何方神聖呢?
我們選中這段代碼
右鍵 -> 分析 –> 從模塊中刪除分析
此時[local.1]就露出了他真面目[ebp-4]
第一章的時候我們講到了,mov(move)數據傳送指令
mov a,b 把b的值傳給a
當然mov也可以對內存中的數據進行賦值
這里面 mov [ebp-4],0x0 就是把0賦值給內存中所設置的變量,其中[]操作符可以引用內存中的數據
類似於源碼中
call指令就是調用,那就是調用printf函數輸出”PleaseInput Flag Number”這個字符串指針
lea eax,dword ptrss:[ebp-0x4]
LEA指令是一個計算機指令,可以將有效地址傳送到指定的的寄存器。
按道理來說就把變量1的地址傳遞給了eax寄存器
push eax
push Text.00EE3E50
現在的eax其實被傳遞了變量1的地址
往下面一瞥會發現scanf函數
下面是scanf函數的定義
int scanf(const char * restrict format,...);
一般代碼中都是scanf(“格式說明符”,&變量地址)
這里面格式說明符是函數的參數1,變量地址是函數的參數2
而這個變量地址就類似於圖中的push eax-> 參數2
而格式說明符就類似於圖中的pushText.00EE3E50 ->參數1
那為何是先把參數2推入,再把參數1推入呢?
比如 int Text(a,b,c)這個函數Text參數為a,b,c
在Windows調用規定中
這個函數在匯編中會
Push c
Push b
Push a
Call Text函數的地址
在此你應該能發現參數啊a,b,c卻被反向以c,b,a的順序壓入棧中,然后再callText函數的地址
Call指令當然就是調用scanf函數讀取輸入flag的值
Add esp,0xc
由於_cdcel調用約定,故需要進行堆棧平衡
cmp dword ptrss:[ebp-0x4],0x2F59
cmp是比較指令 而這里面比較的兩個對象是變量1與0x2F9(12121)
故cmp指令可以影響標志寄存器
Jnz short Text.009E1036
Jnz跳轉指令,由於前面的cmp指令
若變量1-12121=0 Z位=1 jnz不跳轉
若變量1-12121!=0 Z位=0 jnz跳轉到009E1036
那這個程序的大致流程就是輸入變量1的數以后與12121做比較,如果兩個數相等,Z位=1 ,jnz不跳轉,輸入Right!;如果兩個數不相等,Z位=0,jnz跳轉到009E1036上,輸出Error!
然后剩下的代碼分析和前面所講的差不多,大家可以自己寫一下注釋
00x02總結
本篇我們介紹如何用OD來分析一個簡單的程序,同學可以在事后自己用OD跑一下,這樣會理解的更加深刻
下一章 如何用OD(OllyDbg)動態調試程序
點擊“閱讀原文”即可下載本次文章所用到的幾個程序的例子哦~