Sublime Text 3143 Win32版本破解後續——排除暗樁與完美註冊

本文是對上一篇記錄《Sublime Text 3143 Win32版本暴力破解過程》的補充之一。

整個破解工作到目前爲止,似乎一切都很順利,隨便輸入一個序列號就能完成註冊了。然而今早起牀刷牙時猛然想起一個細節:Sublime的作者在3126版本中就已經有了對註冊函數進行驗證的環節,故意傳入了一個全0的參數,此時作爲異常情況,註冊函數應該返回代表註冊失敗的值,當初我們第一次破解3126版本的時候就因爲沒發現這點而折戟沉沙。

排查遇坑

一個本就在不斷對抗破解,手段越來越惡劣、套路越來越猥瑣的作者,會在新版本放棄這種方式嗎?我不相信這種奇蹟會發生。於是在IDA中找到註冊函數:0x0044FA0E,Ctrl+X查找交叉引用,可以看到如下地方調用了該函數:

後記1-交叉引用

一共五處調用,先看第一處:

後記2-IDA看到的異常

嗨呀,看到上面一串全0字符串頓時有種不妙的感覺,嚇得我趕緊打開了OD。在目標位置下斷後運行程序:註冊函數兩次被斷下,第一次傳入了我們之前輸入的key,而第二次,就來到了這裏:

4-1.第二次命中

ecx中傳入的是一個字符串對象,其中第一個字段即爲字符串指針:

4-1-2.第二次命中時的ecx值

來看看是何方神聖:

4-1-3.第二次命中時傳入的字符串

哇,社會是真的險惡。這種驗證碼就算是閉着眼睛也知道是有問題的。調用註冊碼校驗函數完成後,果不其然,作者開始將返回值和1匹配(在Sublime 3143版本中,註冊函數返回1表示註冊成功),果然城裏人套路深啊!

作者的套路1

接着分析,如果我們不問青紅皁白地將註冊函數修改爲無論合適都返回註冊成功,那就中了作者的套路了,下面這段代碼就會執行:

.text:00426DAE 6A 0C          push    0Ch             ; size_t
.text:00426DB0 E8 6E DF 2F 00 call    ??2@YAPAXI@Z    ; 對象大小爲12
.text:00426DB5 8B 4D 6C       mov     ecx, [ebp+6Ch]  ; ecx指向全局註冊結構
.text:00426DB8 C7 04 24 00 53+mov     [esp+0FCh+var_FC], 75300h
.text:00426DBF 50             push    eax
.text:00426DC0 89 48 08       mov     [eax+8], ecx    ; 新對象第三個成員爲全局對象大小
.text:00426DC3 B9 04 31 8A 00 mov     ecx, offset dword_8A3104
.text:00426DC8 89 45 60       mov     [ebp+60h], eax  ; 新對象保存起來了
.text:00426DCB C7 00 EC E7 7E+mov     dword ptr [eax], offset ??_7callback_work_item@@6B@ ; const callback_work_item::`vftable'
.text:00426DD1 C7 40 04 63 6C+mov     dword ptr [eax+4], offset sub_426C63
.text:00426DD8 E8 5E 68 17 00 call    sub_59D63B      ; 壓棧兩個參數,頂到底分別是:新創建對象的地址,0x75300,0xC
.text:00426DD8                                        ; ecx指向全局地址,疑似爲消息響應

這裏構造的就是一個Handler(我們在第一次嘗試中分析過,其中第三個成員即是參數(全局註冊結構),其中第二個成員即是處理函數),看看其內容:

.text:00426C63                sub_426C63 proc near
.text:00426C63
.text:00426C63                arg_0= dword ptr  4
.text:00426C63
.text:00426C63 8B 44 24 04    mov     eax, [esp+arg_0]
.text:00426C67 C6 00 00       mov     byte ptr [eax], 0
.text:00426C6A C3             retn
.text:00426C6A                sub_426C63

很明顯,被觸發後,該函數會清空全局註冊結構中的已註冊標誌。那麼能不能按照Sublime 3126版本那樣,在註冊函數中判斷edx的值是否爲0或者通過堆棧壓入的參數是否爲0來區分是作者故意測試還是真的在校驗註冊碼呢?

作者的套路2

第三次斷點命中時情況如下:

5-1-1.第三次命中

這時,ECX中字符串地址爲我們之前輸入的驗證碼,EDX爲0,傳入的其他參數情況也是0:

5-1-2

但此時,註冊函數仍然識別出了是正確的註冊碼,應返回1,並且結構偏移18處的整數應不爲0,否則又會註冊一個清空全局註冊標誌的handler上去。

5-1-3

將計就計

偏移18位置的整數是什麼呢?我們並不清楚。但既然放在了全局結構中,想必是與註冊信息有關的。於是對該字段下硬件訪問斷點,然後隨意輸入一串註冊信息:

6

點擊註冊後,斷在了上述位置。此時edi值爲0,正將0寫入esi+0x10位置,也就是0x00884600位置。在IDA中分析一下:

void __thiscall sub_405EC9(int g_pstRegInfo, char pUnknown, size_t nLength)
{
  int _pstRegInfo; // esi
  void *v4; // ebx
  bool bTrue; // cf

  _pstRegInfo = g_pstRegInfo;
  if ( pUnknown && *(g_pstRegInfo + 0x14) >= 0x10u )
  {
    v4 = *g_pstRegInfo;
    if ( nLength )
      memmove_0(g_pstRegInfo, v4, nLength);
    std::_Deallocate(v4, *(_pstRegInfo + 20) + 1, 1u);
  }
  *(_pstRegInfo + 0x14) = 0xF;
  bTrue = *(_pstRegInfo + 0x14) < 0x10u;
  *(_pstRegInfo + 0x10) = nLength;
  if ( !bTrue )
    _pstRegInfo = *_pstRegInfo;
  *(_pstRegInfo + nLength) = 0;
}

通過這段我們知道了一個信息:

0x008845E8 一字節註冊信息
0x008845EC
0x008845F0 從這開始是pRegInfo,存放用戶名 0
0x008845F4 4
0x008845F8 8
0x008845FC C
0x00884600 用戶名長度 10
0x00884604 0xF 14 這裏是否大於16很重要

如果0x00884604位置大於等於16,則從0x008845F0位置取出地址,拷貝nLength位到0x008845F0,並且釋放掉原來的內容,然後將0x00884604的值設爲0xF,並將0x00884600的值設置爲nLength,然後將從0x008845F0開始的,第nLength位置爲0。

儘管目前仍然有點迷糊,但很明顯,這裏進行的是字符串操作。如果串長大於等於15,則取其前nLength位複製到0x008845F0,並添加結尾的NULL標識。同時將0x00884600設置爲長度。我高度懷疑這裏是在進行註冊用戶名的提取。如果用戶名很長,那麼0x008845F0存放的指向用戶名的指針,否則就將用戶名直接放在0x008845F0,其最大的容量爲15(預留1位存放結尾的0標誌),然後緊接着0x00884600放其長度。這樣就解釋的通了!

這個猜測可以立即加以驗證,手動修改0x008845F0處的內容如下:

7

然後查看註冊信息:

8

哈哈,我們的猜想得到了證實。下面就可以動手改註冊函數的代碼了。

修改註冊函數,實現完美破解

接下來對註冊函數進行修改,實現所謂完美破解,即註冊時輸入用戶名即完成破解,且輸入的用戶名可以在關於窗口查看:

由於程序包含重定位信息,爲了不影響ASLR機制的正常工作,有三點需要注意:

  1. 在代碼中不出現絕對地址,要麼需要自己根據某個全局地址(這裏其實是有條件的,因爲註冊信息就是全局存放的)算出偏移,手動修正;否則就得加重定位表項

  2. 如果要調用外部API,則要麼直接採用call到IAT的方法(E8+相對偏移),前提是sublime已經導入了該函數

  3. 也乾脆直接想辦法找kernel32基址,然後解析其導出表,只要知道LoadLibrary的地址就行,當然,順帶獲取到GetProcAddress的地址就更方便了。但這種方法有被殺軟誤報的風險。

咱們儘量偷懶,註冊函數有幾點需要滿足的:

  1. 正常註冊情況下,應該返回1

  2. 如果是作者故意傳入的那組錯誤的註冊碼,則應該返回2

  3. 正常情況下,獲取用戶輸入,將用戶輸入的前15位作爲註冊用戶名填入(不滿15位的,取相應位數即可)

下面開始修改:

ecx中指向的是用戶輸入的key,edx如果不爲0的話則指向全局註冊信息結構。那麼用戶輸入的key附近有沒有存放其大小呢?如果有的話,咱們就不用自己寫彙編去求了。本着“大海撈針,總不死心”的方針,在周圍轉轉,還真有收穫!

10

注意到偏移0x10位置就是字符串的長度。此外,在輸入key的過程中,還發現如果輸入的key長度小於等於0xF,則直接存在ECX指向的緩衝區中,否則將其指針存入ECX指向的結構中。

9-輸入用戶名爲15時,直接通過ecx傳入

9-輸入用戶名爲16時

有了上面的信息,咱們就可以來寫一個完美版的註冊函數了。

0044FA0E    55              push ebp                        
0044FA0F    53              push ebx
0044FA10    56              push esi
0044FA11    57              push edi
0044FA12    B8 FB967600     mov eax,sublime_.007696FB       ;這裏有重定位信息,該語句保留不動
0044FA17    8BEC            mov ebp,esp
0044FA19    83EC 38         sub esp,0x38
0044FA1C    33C0            xor eax,eax
0044FA1E    40              inc eax                       ;默認情況下設置返回值爲1(註冊成功)
0044FA1F    85D2            test edx,edx                    ;是否傳入了全局註冊信息結構
0044FA21    74 1F           je short sublime_.0044FA42          ;沒傳入,作者在挖坑,單獨處理
0044FA23    8BFA            mov edi,edx                     ;傳入了,將註冊信息複製爲用戶名
0044FA25    8079 10 0F      cmp byte ptr ds:[ecx+0x10],0xF    ;比較用戶輸入是否大於150044FA29    7F 08           jg short sublime_.0044FA33      ;大於15位,則[ECX]中爲指針
0044FA2B    8BF1            mov esi,ecx                     ;小於15位,直接存在ecx指向的緩衝區中
0044FA2D    0FB649 10       movzx ecx,byte ptr ds:[ecx+0x10] ;待拷貝長度設置爲輸入字符串長度
0044FA31    EB 07           jmp short sublime_.0044FA3A
0044FA33    8B31            mov esi,dword ptr ds:[ecx]       ;大於15位,取指針爲字符串
0044FA35    B9 0F000000     mov ecx,0xF                     ;只取150044FA3A    FC              cld                             ;通用過程,拷貝字符串到註冊信息中
0044FA3B    894A 10         mov dword ptr ds:[edx+0x10],ecx    
0044FA3E    F3:A4           rep movs byte ptr es:[edi],byte ptr ds:[esi]
0044FA40    EB 2A           jmp short sublime_.0044FA6C         ;拷貝完後,跳到退出處
0044FA42    8079 10 4F      cmp byte ptr ds:[ecx+0x10],0x4F     ;從上面判斷edx處跳來,判斷是不是作者構造的非法key(該key長度爲0x4F0044FA46    75 24           jnz short sublime_.0044FA6C         ;不是則調到退出處
0044FA48    8B39            mov edi,dword ptr ds:[ecx]          ;開始判斷是否是作者故意傳入的非法key
;非法key偏0x26的位置爲一串0,每串0x20個,然後是一個0xA,這樣一共重複90044FA4A    8D7F 26         lea edi,dword ptr ds:[edi+0x26]     ;從第一組0開始
0044FA4D    50              push eax                             ;暫存返回值
0044FA4E    B0 30           mov al,0x30                     ;找'0'
0044FA50    B9 29010000     mov ecx,0x129                   ;最多找0x129次(到達末尾了)
0044FA55    8BF7            mov esi,edi                     ;暫存edi到esi
0044FA57    F3:AE           repe scas byte ptr es:[edi]       ;掃描目標串,直到ecx爲0或找到非'0'字符
0044FA59    57              push edi                          ;暫存edi
0044FA5A    2BFE            sub edi,esi                     ;求'0'串長,
0044FA5C    83FF 21         cmp edi,0x21                    ;'0'串長若是0x20(向後挪了一格)
0044FA5F    5F              pop edi                         ;恢復edi的值
0044FA60    75 03           jnz short sublime_.0044FA65       ;'0'串長若是0x20(向後挪了一格)
0044FA62    42              inc edx                         ;循環計數器自增,能跳到這個分支edx定爲0
0044FA63  ^ EB F0           jmp short sublime_.0044FA55     ;跳回去繼續執行(模擬了一個for循環)
0044FA65    83FA 09         cmp edx,0x9                     ;循環結束後,判斷是不是有90
0044FA68    58              pop eax                         ;恢復返回值
0044FA69    75 01           jnz short sublime_.0044FA6C         ;如果不是,則是恰巧0x4F的串,返回1
0044FA6B    40              inc eax                     ;否則就是作者構造的用於釣魚的串,返回2
0044FA6C    8BE5            mov esp,ebp
0044FA6E    5F              pop edi
0044FA6F    5E              pop esi
0044FA70    5B              pop ebx
0044FA71    5D              pop ebp
0044FA72    C3              retn

測試效果

保存後進行測試:
輸入註冊信息
這裏寫圖片描述
查看註冊信息
這裏寫圖片描述
可以看到輸入的註冊信息出現在了關於中。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章