Sublime Text是一款強大的文本編輯器,在不注冊的情況下也可以使用,但標題欄的未注冊字樣與時不時彈出的nag窗口有時也讓人感覺很不爽,于是嘗試對其注冊過程進行分析與破解。
截至寫本篇文章時,Sublime Text的最新穩定版本為3143版本。由于之前我們破解過其舊版本,所以對其注冊機制還算比較了解(已將之前破解Sublime Text 3126版本時寫的筆記整理成了博文,請見《Sublime Text 3126 Win32版本暴力破解過程》)。如果有心的話,大家不妨對比兩個版本的注冊機制和流程,看看從3126更新到3143版本這一年多的時間里,作者為了增大破解的難度做出了哪些努力。畢竟在攻與防的較量中,大家都在進步,我們在提高自己二進制代碼分析能力的同時,其實也有幸見證了軟件作者的成長。
一、第一次嘗試
在上個版本的Sublime中,我們很快就查到了注冊相關的邏輯,然后打開了局面。針對新版本,我們當然希望能夠故伎重演,就看作者給不給我們這個機會了。
首先隨意輸入一串注冊碼,點擊確定后彈出錯誤提示框,此時中斷程序,查看調用棧如下圖。 根據MessageBoxW的調用者地址即可回溯到Wrapper函數頭部。 從調用棧中可以看到再上幀的返回地址為0x00449817,為了保險,我們在Wrapper函數的頭部下斷后,重新輸入一次錯誤的注冊碼看看: 試圖繼續回溯時發現不是第一現場(ecx為0,而主調方是用ecx尋址的) 很明顯,這里把ecx清掉了。于是用IDA的交叉引用功能查到了調用源。emmmm,是JMP過去的,并且在之前清了ecx的值,作者也變猥瑣了。 繼續回溯: 分析發現ecx像是一個結構指針,其中第二個位置放的就是函數指針,第三個位置放的是傳入的參數。于是利用函數指針地址下條件斷點。顯示從ecx開始,[ECX+4]==0x0044F4E0,在下面代碼中注釋部分標注了下斷點的方法,由于ecx的值是通過esi取到的,于是[ESI+4]==0x0044F4E0,然后esi又是通過eax取到的,于是又更進下[[EAX+8]+4]==0x0044F4E0。果然這些條件斷點都被命中了。
.text: 00589 A1D loc_589A1D:
.text: 00589 A1D
8 B
70 08 mov esi, [eax+
8 ]
.text: 00589 A20
8 D
48 10 lea ecx, [eax+
10 h]
.text: 00589 A23
50 push eax
.text: 00589 A24 E8 B7
62 00 00 call sub_58FCE0
.text: 00589 A29
83 2 D F8
30 8 A+
sub dword_8A30F8,
10 h
.text: 00589 A30
8 B
06 mov eax, [esi]
.text: 00589 A32
59 pop ecx
.text: 00589 A33
8 B CE
mov ecx, esi
.text: 00589 A35 FF
50 04 call dword ptr [eax+
4 ]
.text: 00589 A38
8 B
06 mov eax, [esi]
.text: 00589 A3A
8 B CE
mov ecx, esi
.text: 00589 A3C
6 A
01 push 1
.text: 00589 A3E FF
10 call dword ptr [eax]
.text: 00589 A40 E8
3 D
6 B
00 00 call sub_59058
OD中在這句上下斷:
00589 A1D >
|> 8B70 08 mov esi,dword ptr ds:[eax+0x8]
再來一次,斷下后Ctrl+A分析一下程序,程序就顯示出了跳轉的來源,這里十分不巧,有兩點,都下條件斷判定一下: 再來一次,嗯,是從下面跳上來的: 此時eax中的值是關鍵,當前為0x03DCA1C0,應該是堆棧的地址。那是什么時候取出來的呢? 哇,全局變量來了,咱們終于落地了。只要有了全局變量,一切都好商量。 哈哈,全局就只有一處可以設值的,其他三處都是改寫的。 大膽猜測,現在所在的函數就是從消息到消息響應函數的映射函數,而目標函數就是負責查出二者映射關系的函數。 結合IDA發現這些函數是通過消息循環被調用的:
WPARAM sub_58BA83()
{
signed int v0; _DWORD *v1;
bool v2;
int v3; _DWORD *v4;
bool v5; MSG Msg;
char v8;
char v9;
char v10;
char v11;
int v12; _DWORD *v13; WPARAM v14;
int v15; v14 = -
1 ;MsgHandler();
LABEL_2:Msg.hwnd =
0 ;
memset (&Msg.message,
0 ,
0x18 u);v0 =
0 ;
while (
1 ){
if ( !PeekMessageW(&Msg,
0 ,
0 ,
0 ,
1u ) ){sub_58EFBC(&v13);v15 =
0 ;
while (
1 ){v1 = (_DWORD *)sub_58EF7A(&v11);v2 = v13 != (_DWORD *)*v1;sub_452FC0(&v11);
if ( !v2 )
break ;sub_589E11(*v13);sub_452FA3(&v9);sub_452FC0(&v9);}v15 = -
1 ;sub_452FC0(&v13);sub_590582();
if ( v0 ==
2 )MsgHandler();
if ( !PeekMessageW(&Msg,
0 ,
0 ,
0 ,
0 ) ){sub_58EFBC(&v12);v15 =
1 ;
while (
1 ){v4 = (_DWORD *)sub_58EF7A(&v10);v5 = v12 != *v4;sub_452FC0(&v10);
if ( !v5 )
break ;v3 = *(_DWORD *)(*(_DWORD *)v12 +
4 );
if ( v3 )(*(
void (**)(
void ))(*(_DWORD *)v3 +
24 ))();sub_452FA3(&v8);sub_452FC0(&v8);}v15 = -
1 ;sub_452FC0(&v12);sub_590582();MsgWaitForMultipleObjectsEx(
0 ,
0 ,
0xFFFFFFFF ,
0x1CFF u,
6u );}
goto LABEL_2;}
if ( Msg.message ==
18 )
break ;
switch ( Msg.message ){
case 0x7E9 u:
if ( v0 ){
if ( v0 ==
1 )v0 =
2 ;}
else {MsgHandler();v0 =
1 ;}
break ;
case 0x7EA u:v14 = Msg.wParam;
break ;
case 0x7EB u:
return v14;
default :
if ( (Msg.message ==
256 || Msg.message ==
257 || Msg.message ==
260 || Msg.message ==
261 )&& sub_589BFD(Msg.hwnd) ){
if ( sub_58A100(Msg.hwnd, Msg.message, Msg.wParam, Msg.lParam) !=
0 )TranslateMessage(&Msg);}
else {TranslateMessage(&Msg);DispatchMessageW(&Msg);}sub_590582();
break ;}}PostQuitMessage(Msg.wParam);
return v14;
}
可以看到,Sublime的作者自定義了0x7E9消息,在其中完成了錯誤對話框的顯示。并且經過分析,該消息至少還有如下兩種觸發方式: 1. 作者設置的定時器會調用MsgHandler函數 2. 該函數還負責處理其他窗口消息,如窗口獲得焦點等等
因此,在該函數處下斷或在SendMessage/PostMessage等函數上下斷都會有較大的工作量,意味著我們要從眾多干擾信息中找出真正關注的目標信息。并且即便找到了發送該消息的真身,也可能并不意味著我們找到了注冊相關的邏輯。因為作者完全可以在二者之間再采用一些猥瑣的方法增加熵值。基于上述分析,我決定放棄這種思路。
第二次嘗試
Sublime的未注冊版本中有一個nag窗口,是在用戶保存文件到一定次數時觸發的。依然采用下斷MessageBoxW的方式,配合棧回溯和IDA靜態分析,結果又來到了上面的0x7E9消息處理函數中。 這里就不再截圖了,因為大部分圖都和上面類似。
新思路
在思路二中,我們其實想利用未注冊版Sublime的一些提示信息,如保存時的nag窗口、關于窗口中的未注冊等等、標題欄的未注冊字樣。既然nag窗口走不通,我們就試試后面的吧。 搜索字符串: 放到IDA里看看流程圖: 可以看到一個典型的分支結構。并且判斷的條件是將一個結構的第一字節是否為0,如果為0則進入了未注冊分支;且該結構為全局結構。因此可對其下硬件訪問斷點:
hr 0x008845E8
先暫時不忙啟用該斷點,而是在注冊對話框中輸入了key后啟用該斷點,然后確認,程序斷在如下位置: 回溯即找到0x45075D的函數,依然是IDA結合OD的方法:
.text: 0045075 D B8 EC
97 76 00 mov eax, offset loc_7697EC
.text: 00450762 E8
69 1 B
31 00 call __EH_prolog
.text: 00450767 81 EC
8 C
00 00 +
sub esp,
8 Ch
.text: 0045076 D
53 push ebx
.text: 0045076 E
56 push esi
.text: 0045076 F
57 push edi
.text: 00450770 8 B F9
mov edi, ecx
.text: 00450772 8 D
8 D
68 FF FF+lea ecx, [ebp+lpszKeyUnicode]
.text: 00450778 51 push ecx
.text: 00450779 8 B
87 E0
01 00 +
mov eax, [edi+
1E0 h]
.text: 0045077 F
8 B
80 B4
00 00 +
mov eax, [eax+
0 B4h]
.text: 00450785 8 B
88 80 03 00 +
mov ecx, [eax+
380 h]
.text: 0045078 B E8
3 F
9 D
04 00 call GetKey
.text: 00450790 33 DB xor ebx, ebx
.text: 00450792 8 D
4 D D4 lea ecx, [ebp+lpszKey]
.text: 00450795 8 B D0
mov edx, eax
.text: 00450797
.text: 00450797 89 5 D FC
mov [ebp+var_4], ebx
.text: 0045079 A E8 C6
28 07 00 call ConvertKeyToASCII
.text: 0045079 F
53 push ebx
.text: 004507 A0
6 A
01 push 1
.text: 004507 A2
8 D
8 D
68 FF FF+lea ecx, [ebp+lpszKeyUnicode]
.text: 004507 A2 FF
.text: 004507 A8
.text: 004507 A8 C6
45 FC
02 mov byte ptr [ebp+var_4],
2
.text: 004507 AC E8
0 C
77 FC FF
call FreeString
.text: 004507 B1
8 B
8 F D8
01 00 +
mov ecx, [edi+
1 D8h]
.text: 004507 B7
6 A
0 F
push 0 Fh
.text: 004507 B9
58 pop eax
.text: 004507 BA
89 45 9 C
mov [ebp+var_64], eax
.text: 004507 BD
89 45 B4
mov [ebp+var_4C], eax
.text: 004507 C0
8 D
45 80 lea eax, [ebp+pOrgRegInfo]
.text: 004507 C3
50 push eax
.text: 004507 C4
66 89 5 D
80 mov [ebp+pOrgRegInfo], bx
.text: 004507 C8
89 5 D
84 mov [ebp+var_7C], ebx
.text: 004507 CB
89 5 D
98 mov [ebp+var_68], ebx
.text: 004507 CE
88 5 D
88 mov [ebp+var_78], bl
.text: 004507 D1
89 5 D B0
mov [ebp+var_50], ebx
.text: 004507 D4
88 5 D A0
mov [ebp+var_60], bl
.text: 004507 D7 E8
60 EF FF FF
call CopyRegInfo
.text: 004507 DC
53 push ebx
.text: 004507 DD
6 A
01 push 1
.text: 004507 DF
8 D
4 D A0 lea ecx, [ebp+var_60]
.text: 004507E2 E8 E2
56 FB FF
call sub_405EC9
.text: 004507E7 53 push ebx
.text: 004507E8 6 A
01 push 1
.text: 004507 EA
8 D
4 D
88 lea ecx, [ebp+var_78]
.text: 004507 ED E8 D7
56 FB FF
call sub_405EC9
.text: 004507 F2
39 5 D E4 cmp [ebp+var_1C], ebx
.text: 004507 F5
0 F
84 C4
01 00 +jz loc_4509BF
.text: 004507 FB
8 B
97 D8
01 00 +
mov edx, [edi+
1 D8h]
.text: 00450801 8 D
4 D D4 lea ecx, [ebp+lpszKey]
.text: 00450804 8 D
42 01 lea eax, [edx+
1 ]
.text: 00450807 50 push eax
.text: 00450808 8 D
45 EC lea eax, [ebp+lpParameter]
.text: 0045080 B
50 push eax
.text: 0045080 C
8 D
42 04 lea eax, [edx+
4 ]
.text: 0045080 F
83 C2
08 add edx,
8
.text: 00450812 50 push eax
.text: 00450813 E8 F6 F1 FF FF
call CheckKey
.text: 00450818 8 B
8 F D8
01 00 +
mov ecx, [edi+
1 D8h]
.text: 0045081 E
83 C4
0 C
add esp,
0 Ch
.text: 00450821 8 B F0
mov esi, eax
.text: 00450823 83 FE
01 cmp esi,
1
.text: 00450826 0 F
94 C2 setz dl
.text: 00450829 88 11 mov [ecx], dl
.text: 0045082 B
8 B
8 F D8
01 00 +
mov ecx, [edi+
1 D8h]
.text: 00450831 38 19 cmp [ecx], bl
.text: 00450833 74 13 jz short loc_450848
其中,CheckKey函數在注冊碼正確時應該返回1,緊接著就是對該返回值的判斷: 可以看到,注冊失敗返回的是2。考慮到程序中可能還有其他地方調用注冊驗證函數,我們修改其頭部代碼,讓其直接返回1即可: 下面是原驗證函數的入口: 修改以后變成了這樣:
此外,通過IDA的靜態分析結合OD動態調試,在注冊流程下面還開啟了一個線程向服務器報告注冊情況: 對應的線程是由下面這段代碼開啟的。
.text: 00450938
.text: 00450938 loc_450938:
.text: 00450938 53 push ebx
.text: 00450939 53 push ebx
.text: 0045093 A FF
75 EC
push [ebp+lpParameter]
.text: 0045093 D
68 73 F7
44 00 push offset sub_44F773
.text: 00450942 53 push ebx
.text: 00450943 53 push ebx
.text: 00450944 FF
15 1 C
61 78 +
call ds:CreateThread
.text: 0045094 A
50 push eax
.text: 0045094 B FF
15 3 C
63 78 +
call ds:CloseHandle
.text: 00450951 8 B
87 D8
01 00 +
mov eax, [edi+
1 D8h]
.text: 00450957 6 A
0 C
push 0 Ch
.text: 00450959 80 78 01 00 cmp byte ptr [eax+
1 ],
0
.text: 0045095 D
74 08 jz short loc_450967
在OD中查看如下: 本著不打擾作者也不被作者打擾的考量將其干掉:
首先干掉線程函數,直接使用頭部ret大法即可。注意這里是__stdcall調用約定,線程函數傳入一個lpParameter,因此應該為ret4.
下面再把負責CreateThread的那片代碼也干掉:
雖然將傳參、壓棧過程全部用0x90填充了,但是由于CreateThread的某些參數(如函數入口)在可執行程序加載時涉及到重定位,因此還是直接用一個短跳轉略過這一片比較好。至此,sublime破解完成。隨意輸入序列號即可完成注冊。
在啟動過程中,Sublime也會發送注冊信息到驗證服務器:
于是將其啟動時連網進行版本檢查的代碼也干掉。還是在InternetOpenW處下斷點,然后回溯到調用處:0x005A750D,進而回溯到函數頭部,使用ret大法。
讓程序啟動即為破解版
對于懶人來說,連手動注冊這一步都希望能省掉。根據前面的分析,我們知道程序是否注冊是由一個全局結構來控制的。因此在啟動過程中對該全局地址下硬件訪問斷點。看該地址中的值何時被讀取: 在OD中設置硬件訪問斷點:hr 0x008845E8,然后重新運行程序。 很快斷點命中: 可以看出,此時全局結構中注冊標志為0,而這里取出了該標志與0判斷。因此我們將其改為 or byte ptr [eax], 1 這樣無論如何執行完該語句后,該結構中的值都為1了。再次運行程序,直接就成為了已注冊版。至此破解完畢。
總結
以上是生活随笔 為你收集整理的Sublime Text 3143 Win32版本暴力破解过程 的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔 網站內容還不錯,歡迎將生活随笔 推薦給好友。