2011年5月12日 星期四

關於時間限制的拆除

【文章標題】: 關於時間限制的拆除

【文章作者】: 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了,但程式還沒有退出;

  

  

   
好了,今天就到這了,吃飯去了,今天下了很大雨哦; 我喜歡這天氣; 祝大家玩得愉快; 


虛擬桌面技術的初步探討

【文章標題】: 虛擬桌面技術的初步探討  

【文章作者】:  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裡很容易就能看得懂,這裡就不詳細說明瞭。註冊資訊的結構如下所示:


 4.jpg  


關於註冊機:RSA2048是很難反求的,只能通過替換公開金鑰的方法進行破解(看雪論壇精華裡看到的)。由於是替換驅動SbieDrv.sys裡的公開金鑰,替換後還得修正檔的CheckSum,不然驅動不能載入。附件裡是我寫的一個簡單的Patch和Keygen。

    
同時有個疑問:SbieDrv.sys打過補丁後,其數位簽章就變成無效了,既然RSA可以通過替換公開金鑰進行攻擊,那檔的數位簽章可不可以偽造?