【文章標題】: 關於時間限制的拆除
【文章作者】: kanghtta
--------------------------------------------------------------------------------
【詳細過程】
廢話
大家好,好久不見了! 前段時間朋友讓我CRACK一個國產軟體,和軟體的作者交流了一下,由於征得他的同意,就開始破了
VB寫的,運行5分鐘就自動退出,由於對反編譯的p-code不太熟悉,就把它爆破了!
可這兩天又從網上下了一個,一開始就遇上用易語言寫的殼,好不容易把這層烏龜蓋那了,又遇上異常處理,附加資料,
反跟蹤,這些內容都不會阿,怎麼辦啊,學阿! 所以crackme就做的少了點。不過也打算潛水段時間來把作業系統的,記憶體管理,進程管理
還有檔,和I/o等知識好好深入一下,自我感覺破解的過程中如果你對這些知識有一個深度的掌握,更能讓你得心應手。
正文:
今天寫的是關於時間限制的拆除,我用的是段大哥出的 《加密與解密》第二版,首先聲明,本文大部分內容來源於這本書中,
只不過是當作小弟的學習筆記寫出來;也算是對所學知識的一種總結和鞏固;
理論:
1:)時間限制
當我們運行某個沒註冊的軟體時,可能會發現它會給你一個提示,意味著你使用的是未註冊版本;大一點的軟體一般讓你用
30天,卡巴 的試用 也算是一種時間限制吧! 它在你使用了30天后,會提示你重新註冊,不過有個辦法可以繞過這個限制,
就是找到相關的註冊表鍵值,刪了就可以! 其它小一點的軟體,可能會讓你運行幾分鐘後,就將程式殺了!
總的來說,時間限制有兩類:
第一種: 限制你每次運行的時間,比如:每次你只能使用目的程式幾分鐘,或幾個小時;
第二種:不限制你每次運行的時間,但是有個時間段的限制, 如上面說的卡巴;
2:)關於時間限制的保護方式
程式在第一次運行的時候,或在安裝的時候,取得當前系統日期,並且將它記錄在系統中的某個地方;程式在每次運行時
都要取系統時間,並將其和記錄下的時間進行比較,當其差值大於某個約定的時間後就停止運行,並提示用戶註冊;
3:)計時器
有時間限制的程式,自然有一個計時器統計程式運行的時間。
那麼什麼是計時器以及如何使用它?
一個內在的例行程式,只要指定的時間間隔過去,系統就發送一個WM_TIMER消息到對應的訊息佇列中;它在程式運行時
不可見的;
下面是羅雲兵的 win32 程式設計書中有關計時器的一部分:
Windows 的計時器是一種輸入裝置,它週期性地在指定的間隔時間通知應用程式。它可以用向指定視窗發送 WM_TIMER 消息或者調用指定的過程來執行使用者的程式。計時器的應用主要包括下面一些地方:
時鐘程式 - 顯然,這是計時器最直接的應用。
多工 - 如果程式有大量的資料處理,除了用多執行緒的辦法,還可以用計時器,在每一個計時器消息中處理一小塊內容。
定時顯示程式的狀況 - 計時器就相當於 Dos 程式設計中的自己掛接在 int 1ch 上面的要定時處理的程式,它可以定時顯示程式運行的情況,如發送了多少內容,接收了多到內容等等。
在遊戲程式中使用計時器可以消除在不同處理器下用延時來保持速度一致所造成的誤差。
用於資料流程處理 - 在音訊、視頻的播放中,需要隔一段時間處理一段資料。
總的來說,在 Dos 下實現精確定時的唯一方法是在 int 1ch 時鐘中斷中處理常式,但你使用起來必須遵守很多的規範,而在 Windows 的計時器中,你可以用 SetTimer 函數分配不止一個的計時器,比如說,在你的文本編輯程式中,你可以使用一個間隔1秒的計時器來在狀態列中顯示時鐘,同時分配一個10分鐘的計時器來實現定時存檔的功能。計時器實際上是 Windows 對時鐘中斷的一種擴展,它的本質還是基於時鐘中斷的,所以你實際上無法把計時器的間隔設置到55毫秒以下,另外,計時器的精度也是以55毫秒為倍數的,比如說,你設置了一個1秒的計時器,它實際上是在每989毫秒的時候發生的。和在 Dos 下使用時鐘中斷,windows 的計時器還有下面一些要點:
在 Dos 中,你的程式隨時可能被 int 1ch 打斷,而在Windows 中,Windows 通過 WM_TIMER 消息把計時器消息放入正常的訊息佇列中,所以你不必擔心你的程式在別的處理中被計時器打斷。
不可能有同時兩條以上的 WM_TIMER 消息,如果在一個還在訊息佇列中,視窗再得到一條 WM_TIMER 消息,兩條消息會被合併為一條,所以在程式比較忙的時候可能會丟失 WM_TIMER 消息。
WM_TIMER 消息的級別是很低的,程式只有在訊息佇列中沒有其他消息的情況下,才會接收 WM_TIMER 消息,你可以通過下馬方法驗證:在一個設置了計時器的視窗上按住標題列移動視窗,你會發現計時器停止了工作,當你鬆開滑鼠後,在這個過程中丟失的 WM_TIMER 消息並沒有被補上,所以如果你設計一個時鐘程式,你不能使用計時器消息來計數,而必須在消息中每次獲取正確的系統時間。
講了這麼多計時器的特點,下面是計時器相關的API,你會發現除了在使用中要注意的這些特性,計時器的API真是又少又簡單:
建立計時器
SetTimer(
HWND hWnd, // handle of window for timer messages
UINT nIDEvent, // timer identifier
UINT uElapse, // time-out value
TIMERPROC lpTimerFunc // address of timer procedure
);
hWnd 是 windows 發送 WM_TIMER 的視窗,nIDEvent 是計時器的編號,在 WM_TIMER 中出現在 wParam 參數中,用來區分在多個計時器的情況下,這條消息是由哪個計時器產生的。uElapse 是計時器間隔的毫秒數,如果你要設置一個1秒的計時器,這個值就是1000,lpTimerFunc 是處理計時器消息的過程,如果這個參數不是 NULL,windows 在到時間後會調用lpTimerFunc 指定的過程,調用的參數是 CALLBACK TimerProc(hwnd,WM_TIMER,iTimerID,dwTime),iTimerID 是計時器 ID,dwTime 是系統時間;如果 lpTimerFunc 參數是 NULL,Windows 會把 WM_TIMER 消息放入消息迴圈中,消息的 hWnd 是第一個參數中指定的 hWnd,也就是說向這個視窗發送了 WM_TIMER 消息。
另外,如果你的程式沒有視窗,你也可以用這種辦法建立計時器:invoke SetTimer,NULL,NULL,uElapse,TimerProc,函數會返回一個系統定義的 TimerID供你在 KillTimer 中使用。
取消計時器
KillTimer(
HWND hWnd, // handle of window that installed timer
UINT uIDEvent // timer identifier
);
取消計時器只需對應 SetTimer 時的 hWnd 和 uIDEvent 調用 KillTimer 函數就行了。
4: ) 獲取時間的API函數
取得時間的API函數一般有:
一:GetSystemTime
The GetSystemTime function retrieves the current system date and time. The system time is expressed in Coordinated Universal Time (UTC).
GetSysTemTime函數獲取當前系統日期和時間.系統時間基於格林威治時間表示.
VOID GetSystemTime(
LPSYSTEMTIME lpSystemTime // address of system time structure
);
Parameters
參數: lpSystemTime
Pointer to a SYSTEMTIME structure to receive the current system date and time.
指向一個返回當前系統日期和時間的SYSTEMTIME結構指標;
Return Values: 函數無返回值;
二:GetLocalTime
The GetLocalTime function retrieves the current local date and time.
GetLocalTime函數返回本地當前的日期和時間;
VOID GetLocalTime(
LPSYSTEMTIME lpSystemTime // address of system time structure; 參數同上
);
三:GetFileTime
GetFileTime
函數返回檔被創建或是最後一次被存取和修改的日期和時間;
BOOL GetFileTime(
HANDLE hFile, // handle to the file 檔案控制代碼
LPFILETIME lpCreationTime, // address of creation time 創建檔的FILETIME結構體位址
LPFILETIME lpLastAccessTime, // address of last access time 最後存取[訪問]檔的FILETIME結構體位址
LPFILETIME lpLastWriteTime // address of last write time 最近寫入時間的FILETIME結構體位址
);
四: 軟體作者也可能用高階語言封裝的類來作業系統時間,但封裝的類同樣要調用這幾個函數;
還有一種方法就是讀取需要頻繁訪問的系統檔的最後修改日期,在利用FileTimeToSystemTime()將其轉換為系統時間格式;
從而取得當前系統時間;
注意:如果你打算在你的軟體中打算用時間保護,那麼你的軟體必須要能防RegMon FIleMOn之類的監視軟體,
否則,拆除這種時間保護將會變得相當容易;
實踐練手:
首先照本宣科,由於時間保護的軟體我機子裡庫存不多,就用下看雪老大光碟裡的:timer.exe
1: 運行下程式看看,有兩個文字方塊:
第一個: 展示版本本將運行20秒,20秒後它將自己關閉,你需要重新運行它;
第二個: 這是一個演示時間限制的crackme版本
2: 用PEid查看
什麼都沒找到 * 嘿嘿,,,
3:OD載入看看,ctrl+N 看看有沒有我們上面所說的那些函數;
名稱位於 Timer
地址 區段 類型 ( 名稱 注釋
00402024 .rdata 輸入 ( USER32.DestroyWindow ;銷毀窗口
00402018 .rdata 輸入 ( USER32.DialogBoxParamA :對話方塊創建
00402010 .rdata 輸入 ( USER32.EndDialog : 結束對話方塊
00402004 .rdata 輸入 ( KERNEL32.ExitProcess
0040201C .rdata 輸入 ( USER32.GetDlgItem
00402000 .rdata 輸入 ( KERNEL32.GetModuleHandleA
00402028 .rdata 輸入 ( USER32.KillTimer :關閉計時器
0040202C .rdata 輸入 ( USER32.LoadIconA
0040200C .rdata 輸入 ( USER32.PostMessageA
00402014 .rdata 輸入 ( USER32.SendMessageA
00402030 .rdata 輸入 ( USER32.SetTimer :創建計時器
00402020 .rdata 輸入 ( USER32.wsprintfA
00401000 .text 輸出 <模組入口點>
4: 程式用setTimer創建了一個計時器,在命令視窗中: bpx SetTimer 對此函數下斷,
或者在00402030 .rdata 輸入 ( USER32.SetTimer 這行上F2 也可以;
F9運行程式,程式被斷下來了;
004010BC . /0F85 86000000 jnz 00401148
004010C2 . |8B7424 08 mov esi, dword ptr [esp+8] ; Case 110 (WM_INITDIALOG) of switch 004010A5
004010C6 . |6A 00 push 0 ; /Timerproc = NULL
004010C8 . |68 E8030000 push 3E8 ; |Timeout = 1000. ms
004010CD . |6A 01 push 1 ; |TimerID = 1
004010CF . |56 push esi ; |hWnd
004010D0 . |FF15 30204000 call dword ptr [<&USER32.SetTimer>] ; \SetTimer
004010D6 . |A1 04304000 mov eax, dword ptr [403004]
如何拆除世間限制呢:我們在來看看SetTimer函數的定義:
SetTimer
The SetTimer function creates a timer with the specified time-out value.
SetTimer函數創建用指定的時間間隔值創建一個計時器
UINT SetTimer(
HWND hWnd, // handle of window for timer messages ; Timer消息發往視窗控制碼
UINT nIDEvent, // timer identifier ;計時器標識
UINT uElapse, // time-out value : 以毫秒為單位的時間間隔值,每隔這個時間,計時器就發送計時器消息給指定的視窗
TIMERPROC lpTimerFunc // address of timer procedure 如果此值為NULL,系統發送一Wm_timer消息給應有程式訊息佇列;
);
方法一: 用JMP 跳過
1:) 用Hiew 打開Timer.exe,回車來到代碼模式
2:) F5鍵後輸入虛擬位址 .004010C6
3: ) F3進入編輯模式後,回車鍵輸入: jmp 10D6 後ESC鍵退出,F9 保存;
4: ) 運行程式,那個計數的視窗裡再也沒有數值跳動了;
方法二:改UINT uElapse, // time-out value 這個參數的值
注意: 參數調用約定的堆疊順序,還有就是UINT是16位不帶正負號的整數,注意修改後的值不要超出表示範圍,否則出現上溢;
unsigned [int] 表示數的範圍 : 0-4294967295 應該有好幾天了吧; 嘿嘿...
1:) 用Hiew 打開Timer.exe,回車來到代碼模式
2:) F5鍵後輸入虛擬位址 .004010C8
3: ) F3進入編輯模式後,回車鍵輸入: PHSH 71266300 後ESC鍵退出,F9 保存;
4: ) 運行程式,那個計數的視窗裡再也沒有數值跳動了;
方法三: 利用WM_TIMER , 這時你需要查找 VC標頭檔WINUSER.H 的消息定義 得到: #define WM_TIMER 0x0113
在W32Dasm裡查找113字串 ,在find text 對話方塊中輸入 : 113
:00401175 3D13010000 cmp eax, 00000113 每隔一秒出現一次
:0040117A 75CC jne 00401148
:0040117C A108304000 mov eax, dword ptr [00403008]
:00401181 83F813 cmp eax, 00000013 是否超過20秒,13為16進制數
:00401184 7FB1 jg 00401137 比較 ,超時跳走就完蛋,
:00401186 40 inc eax
:00401187 8D4C240C lea ecx, dword ptr [esp+0C]
:0040118B 50 push eax
:00401184 7FB1 jg 00401137
我們將此處用NOP代替,直接爆了它;
1:) 用Hiew 打開Timer.exe,回車來到代碼模式
2:) F5鍵後輸入虛擬位址 .00401184
3: ) F3進入編輯模式後,回車鍵輸入: 9090 後ESC鍵退出,F9 保存;
4: ) 運行程式,那個計數的視窗裡計數數值超過20了,但程式還沒有退出;
好了,今天就到這了,吃飯去了,今天下了很大雨哦; 我喜歡這天氣; 祝大家玩得愉快;
2011年5月12日 星期四
關於時間限制的拆除
虛擬桌面技術的初步探討
【文章標題】: 虛擬桌面技術的初步探討
【文章作者】: newjueqi
【編寫語言】:VC++6.0
【操作平臺】: XP-SP2
【作者聲明】: 這幾天研究了虛擬桌面技術,感覺到這是一種非常有意思的技術,這篇就當成是學習筆記吧!本人只是感興趣,沒有其它目的,失誤之處敬請給位大俠原諒!
什麼是桌面?
每一個運行著Window NT 的系統中都有一個Window 工作站物件,這個物件是安全物件的第一層,是所有使用者安全物件的繼承之源,每一個Window 工作站物件可以擁有一些桌面物件,每一個桌面都擁有一個視窗鏈。視窗鏈裡存放著顯示在所屬桌面的各種視窗。Window NT 用了兩個桌面視窗物件,一個是用來處理登陸介面、遮罩、鎖住工作站等,一個是我們登陸之後進來操作的視窗了。
Window NT通過"explorer.exe"進程來管理這個桌面物件。這就是為什麼我們在工作管理員裡殺掉"explorer.exe",我們的桌面就會消失的原因。
什麼是虛擬桌面?
虛擬桌面是一種可以在電腦原來桌面基礎上再創造一個新的桌面出來,在新的桌面上可以進行日常的操作。
虛擬桌面的用途?
(1) 本人覺得這門技術最重要的用途就是可以把任何有UI介面的軟體變成一個後臺軟體(即看不到任何介面,包括啟動介面)
(2) 可以時工作時是一個桌面,娛樂時是一個桌面(大家可以去下載網站上搜索一下這類軟體的用途,上面的功能描述非常有意思)
虛擬桌面的實現方法
在windows中,要創建一下新的桌面可用到API:CreateDesktop(),函式宣告如下
HDESK CreateDesktop(
LPCTSTR lpszDesktop, // 新桌面的名稱
LPCTSTR lpszDevice, // 為NULL
LPDEVMODE pDevmode, // 為NULL
DWORD dwFlags, // 指定應用程式在桌面的相容方式
ACCESS_MASK dwDesiredAccess, // 指定新桌面的許可權
LPSECURITY_ATTRIBUTES lpsa //指定控制碼是否能被繼承
);
返回值是新創建的桌面的控制碼。
那麼新建了一個桌面後,怎麼在這個新的桌面上運行程式呢?先不要著急,我們先來回顧一下創建進程的函數CreateProcess(),在這個函數的參數中StartupInfo中有 lpDesktop這麼一個屬性,如果這個屬性為NULL則在當前的桌面創建執行緒,如果指定了桌面的名稱,則進程將會在指定的桌面上啟動,所以想在創建的新桌面裡初始化一些程式,只要把lpDesktop參數指定為新桌面的名稱即可。
另外也有一個簡單的方法可把新的執行緒掛在新創建的桌面下,就是使用API函數SetThreadDesktop(),聲明如下:
BOOL SetThreadDesktop(
HDESK hDesktop // 指向指定的桌面控制碼
);
但使用這個函數要注意一點,根據MSDN的說法:The SetThreadDesktop function will fail if the calling thread has any windows or hooks on its current desktop (unless the hDesktop parameter is a handle to the current desktop) 意思就是除非要指定的桌面控制碼是當前的桌面,不然的話這個函數的調用會失敗如果當前執行緒擁有任何的視窗(即UI介面).
怎麼實現不同桌面之間的切換呢?
要在不同的桌面之間切換,可用API函數SwitchDesktop,聲明如下:
BOOL SwitchDesktop(
HDESK hDesktop // 桌面的控制碼
);
另外也可通過點擊“切換”按鈕實現桌面的切換。
但又引申出一個新的問題,必須要知道各個桌面的控制碼,獲取桌面的控制碼可通過API函數GetThreadDesktop,函數的聲明如下:
HDESK GetThreadDesktop(
DWORD dwThreadId //執行緒的ID);
返回值就是指定執行緒所在的桌面了。
而且我們必須要認清的是創建新桌面的執行緒啟動是在舊的桌面上,所以可以用下面的語句輕鬆獲得當前桌面的控制碼:
GetThreadDesktop(GetCurrentThreadId());
返回值就是舊的桌面控制碼。
怎麼關閉新創建的桌面?
這個問題其實也不用我們擔心,微軟已經替我們想好了^-^ , 用CloseDesktop函數可輕鬆實現這個功能,函式宣告如下:
BOOL CloseDesktop(
HDESK hDesktop // 指定要關閉的桌面的控制碼
);
下面貼一段代碼的例子,是在新創建的桌面上運行計算器(calc.exe)實現計算器的後臺運行
#include <windows.h>
HINSTANCE hInst; //當前的實例控制碼
HWND hWnd; //窗口控制碼
HDESK hvirtualDesk; //新創建的虛擬桌面控制碼
PROCESS_INFORMATION pi; //計算器進程資訊
//消息迴圈
LRESULT CALLBACK WinProc(
HWND hwnd, // handle to window
UINT uMsg, // message identifier
WPARAM wParam, // first message parameter
LPARAM lParam // second message parameter
)
{
switch(uMsg)
{
case WM_CLOSE:
TerminateProcess( pi.hProcess, 1 );
CloseDesktop( hvirtualDesk );
DestroyWindow(hwnd);
PostQuitMessage(0);
break;
case WM_DESTROY:
CloseDesktop( hvirtualDesk );
PostQuitMessage(0);
break;
case WM_HOTKEY:
if ( 0x0001 == wParam ) //為退出桌面熱鍵Alt+Q
{
SendMessage(hwnd,WM_CLOSE,0,0);
}
break;
default:
return DefWindowProc(hwnd,uMsg,wParam,lParam);
}
return 0;
}
//創建虛擬桌面
void CrateVirtualDesk()
{
//把新創建的虛擬桌面控制碼存放在hvirtualDesk
hvirtualDesk=CreateDesktop( "newdesk",
NULL,
NULL,
DF_ALLOWOTHERACCOUNTHOOK,
GENERIC_ALL,
NULL);
}
//在虛擬桌面上運行一個計算器的實例
void RunCalc()
{
STARTUPINFO si;
ZeroMemory( &si, sizeof(si) );
si.cb = sizeof(si);
si.lpDesktop = "newdesk";
ZeroMemory( &pi, sizeof(pi) );
if( !CreateProcess( NULL,
"calc",
NULL,
NULL,
FALSE,
0,
NULL,
NULL,
&si,
&pi ) )
{
MessageBox(NULL,"運行計算器失敗","Error",0);
ExitProcess(1);
}
}
int WINAPI WinMain(
HINSTANCE hInstance, // handle to current instance
HINSTANCE hPrevInstance, // handle to previous instance
LPSTR lpCmdLine, // command line
int nCmdShow // show state
)
{
WNDCLASS wndcls;
MSG msg;
hInst=hInstance;
ZeroMemory( &wndcls,sizeof(wndcls) );
wndcls.cbClsExtra=0;
wndcls.cbWndExtra=0;
wndcls.hbrBackground=(HBRUSH)GetStockObject(BLACK_BRUSH);
wndcls.hInstance=hInstance;
wndcls.lpfnWndProc=WinProc;
wndcls.lpszClassName="hello";
wndcls.lpszMenuName=NULL;
wndcls.style=CS_HREDRAW | CS_VREDRAW;
RegisterClass(&wndcls);
hWnd=CreateWindow( "hello","hello",WS_OVERLAPPEDWINDOW,
300,300,100,100,NULL,NULL,hInstance,NULL );
//註冊所需的熱鍵,Alt+Q為退出創建的虛擬桌面
if( !RegisterHotKey( hWnd,0x0001,MOD_ALT ,'Q' ) )
{
//處理退出進程
return TRUE;
}
//創建虛擬桌面
CrateVirtualDesk();
//在虛擬桌面上運行一個計算器的實例
RunCalc();
ShowWindow( hWnd,SW_SHOWNORMAL );
UpdateWindow( hWnd );
while( GetMessage( &msg,NULL,0,0 ) )
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
2011年5月10日 星期二
Sandboxie 註冊演演算法分析
Sandboxie 註冊演演算法分析
【文章作者】: uuk
【軟體名稱】: Sandboxie 3.49.06
【下載地址】: http://www.sandboxie.com/index.php
【加殼方式】: 無
【保護方式】: 序號
【編寫語言】: Microsoft Visual C++ 7.0
【使用工具】: PEID,IDA Pro with Hex-Rays plugin,Syser
【操作平臺】: XP SP3,
【軟體介紹】: Sandboxie 允許你在沙盤環境中運行流覽器或其他程式,因此運行所產生的變化可以隨後刪除。可用來消除上網、運行程式的痕跡,也可用來還原我的最愛、主頁、註冊表等。即 使在沙盤進程中下載的檔,也會隨著沙盤的清空而刪除。此軟體在系統託盤中運行,如果想啟動一個沙盤進程,請通過託盤圖示(而不要用原方式)啟動流覽器或 相應程式。
【作者聲明】: 菜鳥學習演算法,失誤之處敬請諸位大俠賜教!
Sandboxie自3.48版本開始採用新的啟動方式——聯網啟動,但也為不能上網的電腦提供了離線啟動方式。能離線啟動說明註冊的驗證是在本地進行的,方便我們進行演演算法分析。官方離線啟動網址:http://www.sandboxie.com/index.php?OfflineActivation。在之前我已經通過官方的離線啟動網頁得到一個離線啟動碼Key(RF0UD7XH6KREU2TX……E4YJ1PT8RW8PO)。
通過跟蹤啟動過程,啟動管理程式License.exe調用SbieDll.dll的SbieApi_ActivateLicense,SbieApi_ActivateLicense通過IRP_MJ_DEVICE_CONTROL與驅動SbieDrv.sys通信,最終驗證過程在驅動SbieDrv.sys裡。通過IoControlCode定位到函數sub_20744(重命名為ActivateLicense),查看ActivateLicense的交叉引用,發現在驅動載入過程中也被調用了,因此可以確認驗證過程就是sub_20744。ActivateLicense反彙編代碼如下:
.text:00020744 push ebp
.text:00020745 mov ebp, esp
.text:00020747 sub esp, 28h
.text:0002074A push esi
.text:0002074B push 24h ; 36 = 24h
.text:0002074D push [ebp+pKey] ; ActivationKey(UNICODE)
.text:00020750 xor esi, esi
.text:00020752 push pPagedPool_tzuk ; PagedPool("tzuk")
.text:00020758 mov [ebp+var_18], esi
.text:0002075B mov [ebp+var_14], esi
.text:0002075E call sub_1C1C8 ; mpz_set_str (mpz_t rop, char *str, int base)
.text:0002075E ; return eax = rop
仔細分析sub_1C1C8,會發現它是將字串Key當作36進制數,轉換成16進制的大數(有點像GMP庫裡的mpz_set_str(Key, ActivationKey, 36))。由於是大數操作,所以sub_1C1C8裡有大量的記憶體操作,剛開始分析時就讓我暈頭轉向,同時又是大數運算,比較複雜。我花了不少時間才搞明白它在做什麼,也知道程式中是如何存儲大數的。大數結構如下所示:
struct BigNum{unsigned int size; unsigned int num[size];}
大數在記憶體中按從低到高的位址存儲從低到高的數。知道了大數的結構,以及記憶體申請和釋放操作,後面的分析就會輕鬆許多。
.text:00020763 cmp eax, esi ; 大數 Key = eax
.text:00020765 mov [ebp+pKey], eax
.text:00020768 jnz short loc_2077E
.text:0002077E ; ---------------------------------------------------------------------------
.text:0002077E
.text:0002077E loc_2077E: ; CODE XREF: ActivateLicense+24 j
.text:0002077E push ebx
.text:0002077F push edi
.text:00020780 push offset MOD
.text:00020785 push offset EXP
.text:0002078A push eax
.text:0002078B push pPagedPool_tzuk
.text:00020791 call sub_1C09E ; mpz_powm (mpz_t rop, mpz_t base, mpz_t exp, mpz_t mod)
.text:00020791 ; return eax = rop
大致分析sub_1C09E,排除記憶體操作後,發現裡面有一些除法運算,初步確定是在做大數除法,但是在自己用大數計算器驗證時發現結果總是不對,於是以為在sub_1C09E裡有其他小動作。花了好幾個晚上,仔仔細細的研究了裡面不確定的CALL,以及運算過程,最後確定sub_1C09E裡面做的就是大數除法和求模運算。既然是除法,那為什麼測試時結果不對呢?我又去分析了下測試過程,發現我寫的一個字串反轉程式少做一次對換,導致中間的兩個字元沒被反轉(大數在記憶體裡是從小到大存儲,而計算器中是從大到小,所以要反轉)。這樣浪費了幾天時間,但也讓我明白了大數除法的演算法。確定了sub_1C09E裡面的大數運算過程,寫出數學表達示,化簡後發現是類似mpz_powm(Reg, Key, EXP, MOD)的函數。在sub_1C09E之後,註冊資訊就以明碼放在記憶體中,後面就是一些資料的校驗了。想要寫出註冊機就得能反求Reg = Key^EXP mod MOD,我求不出來,於是到論壇上求助http://bbs.pediy.com/showthread.php?t=123926,lingyu大俠告知這是RSA2048加密演算法,在這要感謝大俠們的指點!
下麵是資料校驗過程:
.text:00020796 mov ebx, eax ; ebx = Reg = eax
.text:00020798 mov eax, [ebx]
.text:0002079A cmp eax, 4 ; Reg 長度size大於4
.text:0002079D push 12h
.text:0002079F pop esi
.text:000207A0 jbe loc_20A39
.text:000207A6 mov cl, [ebx+4]
.text:000207A9 and cl, 0Fh
.text:000207AC cmp cl, 1 ; Reg 最低位為1
.text:000207AF push 13h
.text:000207B1 pop esi
.text:000207B2 jnz loc_20A39
.text:000207B8 push 14h
.text:000207BA pop esi
.text:000207BB lea eax, ds:0FFFFFFF4h[eax*4] ; size*4-0Ch
.text:000207C2 push eax
.text:000207C3 lea edi, [ebx+10h]
.text:000207C6 push edi
.text:000207C7 call sub_1B4B8 ; CheckSum 資料校驗
sub_1B4B8是對註冊資訊Reg進行校驗,返回兩個校驗值,裡面有兩個CALL(sub_1B392、sub_1B418),看Hex-Rays plugin生成的C代碼,很容易就能自己寫出個checksum生成函數,代碼在附件裡有。也不知道是有現成的函數,還是作者杜撰的。
.text:000207CC cmp eax, [ebx+8] ; 比較校驗值1與[ebx+8]
.text:000207CF jnz loc_20A39
.text:000207D5 cmp edx, [ebx+0Ch] ; 比較校驗值2與[ebx+0Ch]
.text:000207D8 jnz loc_20A39
.text:000207DE cmp dword ptr [edi], 80000001h ; [ebx+10h]
.text:000207E4 push 21h
.text:000207E6 pop esi
.text:000207E7 jnz loc_20A39
.text:000207ED cmp dword ptr [ebx+14h], 1 ; [ebx+14h] == 1
.text:000207F1 push 22h
.text:000207F3 pop esi
.text:000207F4 jnz loc_20A39
再後面還有到期時間與當前系統時間的比較,軟體版本的比較,系統碼的比較,在IDA裡很容易就能看得懂,這裡就不詳細說明瞭。註冊資訊的結構如下所示:
關於註冊機:RSA2048是很難反求的,只能通過替換公開金鑰的方法進行破解(看雪論壇精華裡看到的)。由於是替換驅動SbieDrv.sys裡的公開金鑰,替換後還得修正檔的CheckSum,不然驅動不能載入。附件裡是我寫的一個簡單的Patch和Keygen。
同時有個疑問:SbieDrv.sys打過補丁後,其數位簽章就變成無效了,既然RSA可以通過替換公開金鑰進行攻擊,那檔的數位簽章可不可以偽造?