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
0x008845F44
0x008845F88
0x008845FCC
0x00884600用户名长度10
0x008846040xF14 这里是否大于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

测试效果

保存后进行测试:
输入注册信息
这里写图片描述
查看注册信息
这里写图片描述
可以看到输入的注册信息出现在了关于中。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值