wzhenghit 发表于 2020-8-16 10:27

Windows逆向学习笔记——破解Source Insight 4


## 前言
这里po jie的是当前最新版本Source Insight 4.0.118,总结po jie过程有以下三个部分:
1. 逆向Serial Number序列号验证算法,构造可用序列号
2. 逆向分析验证license签名过程,自签名license文件,并使用自己的公钥替换程序中原始公钥
3. 程序运行起来后,会单独开启一个线程在线检查Serial Number是否有效,需要修改二进制文件过掉此检查

除此之外,笔者也看了一些大佬的文章,提到有黑名单检查,因为没遇到这个逻辑,程序暂时也能用,所以这里的破解中没有涉及,后面如果遇到再研究也不迟。

文中用到的所有代码可点击右侧超链接,在 (https://github.com/jungz7/Learn-Windows/tree/master/LearnCrack/CrackSI4)上查看

## 环境准备
IDA6.8、x32dbg
[官网](https://www.sourceinsight.com/) 下载sourceinsight40118-setup.exe
<br>

## 0、简单po jie:修改两字节
因为本文主要是研究自签名license并替换程序中公钥的po jie方法,比较麻烦,纯粹是为了学习目的。如果对这种方法不感兴趣,这部分内容就提供另一种简单的po jie方法:修改 sourceinsight4.exe 二进制文件中的两个字节完成po jie
- 第一处是 *VA=005141A8*,*FA=1135A8*,将 74 改为 EB,原因可参考 *第 2.2 节*
- 第二处是 *VA=00513470*,*FA=112870*,将 83 改为 C3,原因可参考 *第3 节*
最后,复制本文 *2.3节* 中 *si4.lic*的内容,并保存为*C:\ProgramData\Source Insight\4.0\si4.lic*文件即可po jie程序
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200815121747931.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl8zODUyNjE4MA==,size_16,color_FFFFFF,t_70#pic_center)
<br>


## 1、验证序列号的过程——逆向分析
这部分内容分4个小节:
1.1 节 讲述如何定位到验证序列号的代码
1.2 节 中逆向还原验证序列号格式的函数,并按照格式构造出正式版序列号
1.3 节 中逆向还原本地校验序列号的函数,构造出可通过本地校验的序列号
1.4 节 本地验证序列号

### 1.1 定位“Serial Number错误”弹窗代码
安装程序后,打开sourceinsight4.exe程序会看到选择license文件的界面,如下图:
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200813114712705.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl8zODUyNjE4MA==,size_16,color_FFFFFF,t_70#pic_center)
选择第一项“输入serial number”,点击“Next >”继续,显示输入Serial Number的对话框,并且提示 Serial Number 的格式“S4XX-XXXX-XXXX-XXXX”。按照格式输入一个,点击"Next>",会弹出“无效的Serial Number”警告,如下图:
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200813115255344.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl8zODUyNjE4MA==,size_16,color_FFFFFF,t_70#pic_center)
在IDA中搜索弹窗警告中的文本“The serial number ...”,找到引用此字符串的地址是 *.text:00513A69*。

分析*.text:00513A69*此位置上下文代码,可以看出 *.text:00513A45 call sub_510B50* 是Serial Number的验证函数,若验证失败(返回结果0),则会再判断是否是3.x版本的Serail Number,不是则弹出上图中的警告;若验证成功则跳转到 *loc_513ACB* 继续判断验证结果。代码如下所示:
```c
.text:00513A25               mov   eax, dword_673488
.text:00513A2A               push    1               ; int
.text:00513A2C               lea   ecx,
.text:00513A32               push    ecx             ; int
.text:00513A33               lea   edx,
.text:00513A39               push    edx             ; int
.text:00513A3A               add   eax, 608h
.text:00513A3F               push    eax             ; int
.text:00513A40               lea   eax,
.text:00513A44               push    eax             ; char *
.text:00513A45               call    sub_510B50      ; 验证Serial Number是否正确
.text:00513A4A               add   esp, 34h
.text:00513A4D               test    eax, eax      ; eax = 0, 弹出错误窗口并返回
.text:00513A4D                                       ; eax!= 0, 继续验证Serial Number格式
.text:00513A4F               jnz   short loc_513ACB
.text:00513A51               lea   ecx,
.text:00513A55               push    ecx
.text:00513A56               call    sub_561CB0
.text:00513A5B               add   esp, 4
.text:00513A5E               test    eax, eax
.text:00513A60               jz      short loc_513A69
.text:00513A62               push    offset aTheSerialNumbe ; "The serial number you entered is for ve"...
.text:00513A67               jmp   short loc_513A6E
.text:00513A69 ; ---------------------------------------------------------------------------
.text:00513A69
.text:00513A69 loc_513A69:                           ; CODE XREF: sub_5139C0+A0j
.text:00513A69               push    offset aTheSerialNum_0 ; "The serial number you entered is not co"...
.text:00513A6E
.text:00513A6E loc_513A6E:                           ; CODE XREF: sub_5139C0+A7j
.text:00513A6E               call    sub_40AC20
.text:00513A73               add   esp, 4
...                            ...
.text:00513ACA               retn
.text:00513ACB ; ---------------------------------------------------------------------------
.text:00513ACB
.text:00513ACB loc_513ACB:                           ; CODE XREF: sub_5139C0+8Fj
...                            ...
.text:00513AE1               cmp   , edx
.text:00513AE7               jz      short loc_513B0D
.text:00513AE9               push    offset aTheSerialNum_1 ; "The serial number you entered is for a "...
.text:00513AEE               call    sub_40AC20
...                            ...
.text:00513B0C               retn
```
<br>

### 1.2 Serial Number验证函数 分析
详细分析上面步骤中提到的 *sub_510B50* 处的序列号验证函数,总结出Serial Number的验证规则如下:
SerailNumber字符串长度必须为19(16个字符加上3个分隔符)
SerailNumber必须等'S'
SerailNumber是'0'-'9'
SerailNumber等于'T'表示Trial license试用许可,等于'B'表示Beta license测试许可,等于'S'表示Standard license标准许可,等于'U'表示Upgrade license升级许可
SerailNumber等于 'G'、'V'、'R' 其中之一
SerailNumber等于 'R'、'G'、'D'、'F' 其中之一
SerialNumber的前12个字符经过函数 *.text:00510C6B call sub_510320* 转换后得到4个字符,与SerialNumber的最后4个字符必须相同
代码如下所示:
```c
.text:00510B50 ; int __cdecl sub_510B50(char *, int, int, int, int)
.text:00510B50 sub_510B50      proc near               ; CODE XREF: .text:005129C4p
.text:00510B50                                       ; sub_5139C0+85p
.text:00510B50
.text:00510B50 ary4char      = dword ptr -18h
.text:00510B50 szSNTemp      = byte ptr -14h
.text:00510B50 ptr_szSN      = dword ptr4
.text:00510B50                              
.text:00510B50 ptr_nSNFlag= dword ptr8
.text:00510B50 ptr_nLicTypeFlag= dword ptr0Ch
.text:00510B50 ptr_nVersionFlag= dword ptr10h
.text:00510B50 arg_10          = dword ptr14h
.text:00510B50
.text:00510B50               sub   esp, 18h
.text:00510B53               push    esi
.text:00510B54               mov   esi,
.text:00510B58               push    esi             ; char *
.text:00510B59               call    __strupr
.text:00510B5E               push    esi             ; char *
.text:00510B5F               call    _strlen
.text:00510B64               add   esp, 8
.text:00510B67               cmp   eax, 13h      ; strlen(ptr_szSN) == 13h
.text:00510B6A               jnz   loc_510C86
.text:00510B70               mov   al, '-'
.text:00510B72               cmp   , al   ; ptr_szSN = '-'
.text:00510B75               jnz   loc_510C86
.text:00510B7B               cmp   , al   ; ptr_szSN = '-'
.text:00510B7E               jnz   loc_510C86
.text:00510B84               cmp   , al   ; ptr_szSN = '-'
.text:00510B87               jnz   loc_510C86
.text:00510B8D               cmp   byte ptr , 'S' ; ptr_szSN = 'S'
.text:00510B90               jnz   loc_510C86
.text:00510B96               mov   ecx,
.text:00510B9A               test    ecx, ecx
.text:00510B9C               jz      short loc_510BB5
.text:00510B9E               mov   al,
.text:00510BA1               cmp   al, 'R'         ; ptr_szSN == 'R'
.text:00510BA3               jz      short loc_510BB5
.text:00510BA5               cmp   al, 'G'         ; ptr_szSN == 'G'
.text:00510BA7               jz      short loc_510BB5
.text:00510BA9               cmp   al, 'D'         ; ptr_szSN == 'D'
.text:00510BAB               jz      short loc_510BB5
.text:00510BAD               cmp   al, 'F'         ; ptr_szSN == 'F'
.text:00510BAF               jnz   loc_510C86
.text:00510BB5
.text:00510BB5 loc_510BB5:                           ; CODE XREF: sub_510B50+4Cj
.text:00510BB5                                       ; sub_510B50+53j ...
.text:00510BB5               mov   al,
.text:00510BB8               cmp   al, '0'         ; ptr_szSN >= '0'
.text:00510BBA               jl      loc_510C86
.text:00510BC0               cmp   al, '9'         ; ptr_szSN <= '9'
.text:00510BC2               jg      loc_510C86
.text:00510BC8               mov   edx,
.text:00510BCC               movsx   eax, al
.text:00510BCF               sub   eax, '0'
.text:00510BD2               mov   , eax
.text:00510BD4               mov   al,
.text:00510BD7               cmp   al, 'T'         ; ptr_szSN == 'T', Trial license
.text:00510BD9               jnz   short IF_BEGIN
.text:00510BDB               mov   eax,
.text:00510BDF               mov   dword ptr , 1
.text:00510BE5               jmp   short ELSE_END
.text:00510BE7 ; ---------------------------------------------------------------------------
.text:00510BE7
.text:00510BE7 IF_BEGIN:                               ; CODE XREF: sub_510B50+89j
.text:00510BE7               cmp   al, 'B'         ; ptr_szSN == 'B', Bete license, cannot be used with the release version
.text:00510BE9               jnz   short ELSE_IF
.text:00510BEB               mov   edx,
.text:00510BEF               mov   dword ptr , 3
.text:00510BF5               jmp   short ELSE_END
.text:00510BF7 ; ---------------------------------------------------------------------------
.text:00510BF7
.text:00510BF7 ELSE_IF:                              ; CODE XREF: sub_510B50+99j
.text:00510BF7               cmp   al, 'S'         ; ptr_szSN == 'S', Standard license
.text:00510BF9               jnz   short ELSE_IF_
.text:00510BFB               mov   eax,
.text:00510BFF               mov   dword ptr , 0
.text:00510C05               jmp   short ELSE_END
.text:00510C07 ; ---------------------------------------------------------------------------
.text:00510C07
.text:00510C07 ELSE_IF_:                               ; CODE XREF: sub_510B50+A9j
.text:00510C07               cmp   al, 'U'         ; ptr_szSN == 'U', Upgrade License
.text:00510C09               jnz   short loc_510A56
.text:00510C0B               mov   edx,
.text:00510C0F               mov   dword ptr , 0
.text:00510C15
.text:00510C15 ELSE_END:                               ; CODE XREF: sub_510B50+95j
.text:00510C15                                       ; SI_ValidateSNFmt+A5j ...
.text:00510C15               mov   al,
.text:00510C18               cmp   al, 'G'         ; ptr_szSN == 'G'
.text:00510C1A               jnz   short IF_BEGIN2
.text:00510C1C               mov   eax, Flag]
.text:00510C20               mov   dword ptr , 1
.text:00510C26               jmp   short ELSE_END2
.text:00510C28 ; ---------------------------------------------------------------------------
.text:00510C28
.text:00510C28 IF_BEGIN2:                              ; CODE XREF: sub_510B50+CAj
.text:00510C28               cmp   al, 'V'         ; ptr_szSN == 'V'
.text:00510C2A               jnz   short ELSE_IF2
.text:00510C2C               mov   edx, Flag]
.text:00510C30               mov   dword ptr , 2
.text:00510C36               jmp   short ELSE_END2
.text:00510C38 ; ---------------------------------------------------------------------------
.text:00510C38
.text:00510C38 ELSE_IF2:                               ; CODE XREF: sub_510B50+DAj
.text:00510C38               cmp   al, 'R'         ; ptr_szSN == 'R'
.text:00510C3A               jnz   short loc_510C86
.text:00510C3C               mov   eax, Flag]
.text:00510C40               mov   dword ptr , 0
.text:00510C46
.text:00510C46 ELSE_END2:                              ; CODE XREF: sub_510B50+D6j
.text:00510C46                                       ; sub_510B50+E6j
.text:00510C46               test    ecx, ecx
.text:00510C48               jz      short loc_510C7C
.text:00510C4A               lea   ecx,
.text:00510C4E               push    esi             ; char *
.text:00510C4F               push    ecx             ; char *
.text:00510C50               call    _strcpy
.text:00510C55               lea   edx,
.text:00510C59               push    edx
.text:00510C5A               push    offset ary256Chars
.text:00510C5F               lea   eax,
.text:00510C63               push    0Fh
.text:00510C65               push    eax
.text:00510C66               mov   , 0
.text:00510C6B               call    sub_510320      ; 通过SerialNumber前12个字符计算出最后4个字符,并与输入的SerialNumber最后4个字符做比较
.text:00510C70               mov   ecx,
.text:00510C73               add   esp, 18h
.text:00510C76               cmp   ecx, ; 比较计算出的最后4个字符是否与输入的SerialNumber最后4个字符相同
.text:00510C7A               jnz   short loc_510C86
.text:00510C7C
.text:00510C7C loc_510C7C:                           ; CODE XREF: sub_510B50+F8j
.text:00510C7C               mov   eax, 1
.text:00510C81               pop   esi
.text:00510C82               add   esp, 18h
.text:00510C85               retn
.text:00510C86 ; ---------------------------------------------------------------------------
.text:00510C86
.text:00510C86 loc_510C86:                           ; CODE XREF: sub_510B50+1Aj
.text:00510C86                                       ; sub_510B50+25j ...
.text:00510C86               xor   eax, eax
.text:00510C88               pop   esi
.text:00510C89               add   esp, 18h
.text:00510C8C               retn
.text:00510C8C sub_510B50      endp
```
<br>

### 1.3 构造可用的Serial Number
步骤1.2中提到Serial Number的验证规则之一是:序列号的前12个字符经过函数 *sub_510320* 转换后得到4个字符,与序列号的最后4个字符相同。
以下便是是 *sub_510320* 汇编代码分析以及据此还原的C代码,将此函数命名为 *Get4charBySNPre12char*
```c
.text:00510320 sub_510320      proc near               ; CODE XREF: sub_510390+73p
.text:00510320                                       ; sub_510B50+11Bp ...
.text:00510320
.text:00510320 ptr_szSN      = dword ptr4
.text:00510320 nSNLen          = dword ptr8
.text:00510320 ary256char      = dword ptr0Ch
.text:00510320 ptr_4charsResult= dword ptr10h
.text:00510320
.text:00510320               push    ebx
.text:00510321               mov   ebx,
.text:00510325               push    ebp
.text:00510326               mov   ebp,
.text:0051032A               push    esi
.text:0051032B               push    edi
.text:0051032C               mov   edi,
.text:00510330               xor   esi, esi
.text:00510332
.text:00510332 DO_BEGIN:                               ; CODE XREF: sub_510320+5Ej
.text:00510332               movsx   eax, byte ptr
.text:00510336               add   eax, esi
.text:00510338               and   eax, 0FFh
.text:0051033D               mov   cl,
.text:00510340               mov   eax, 1
.text:00510345               cmp   ebx, eax
.text:00510347               jbe   short loc_510361
.text:00510349               lea   esp,
.text:00510350
.text:00510350 _DO_BEGIN:                              ; CODE XREF: sub_510320+3Fj
.text:00510350               movsx   edx, byte ptr
.text:00510354               movzx   ecx, cl
.text:00510357               xor   edx, ecx
.text:00510359               mov   cl,
.text:0051035C               inc   eax
.text:0051035D               cmp   eax, ebx
.text:0051035F               jb      short _DO_BEGIN
.text:00510361
.text:00510361 _DO_END:                              ; CODE XREF: sub_510320+27j
.text:00510361               movzx   eax, cl
.text:00510364               cdq
.text:00510365               mov   ecx, 1Ah
.text:0051036A               idiv    ecx
.text:0051036C               mov   eax,
.text:00510370               inc   esi
.text:00510371               mov   dl, byte ptr ds:sz26Chars ; "KV96GMJYH7QF5TCW4U3XZPRSDN"
.text:00510377               mov   , dl
.text:0051037B               cmp   esi, 4
.text:0051037E               jb      short DO_BEGIN
.text:00510380               pop   edi
.text:00510381               pop   esi
.text:00510382               pop   ebp
.text:00510383               pop   ebx
.text:00510384               retn
.text:00510384 sub_510320      endp

//还原为C代码
char g_sz26Chars[] = { "KV96GMJYH7QF5TCW4U3XZPRSDN" };
void Get4charBySNPre12char(char* szSN, int nSNLen, char* ary256Chars, char* pResult) {
    for (int i = 0; i < 4; i++) {
      char cl = ary256Chars[(szSN + i) & 0xFF];
      for (int j = 1; j < nSNLen; j++) {
            cl = ary256Chars ^ (unsigned char)cl];
      }
      *(pResult + i) = g_sz26Chars[((unsigned char)cl % 0x1A)];
    }
}

//构造一个可用的Serial Number
char g_ary256Chars[] = { 0x23, 0xDD, 0x78... }; //这里是一个256大小的char数组
void main() {
    char szSN[] = { "S4SG-KRGM-YD7Q-XXXX" };
    Get4charBySNPre12char(szSN, 15, g_ary256Chars, &szSN);
    printf("%s\r\n", szSN); //S4SG-KRGM-YD7Q-RCFY
}
```
因为 *Get4charBySNPre12char* 函数是通过SerialNumber的前12个字符生成后4个字符,所以可以将构造的前12个字符传入此函数,计算出一个可用的SerialNumber的后4个字符,代码如上,最终得到一个Standard正式版序列号:**S4SG-KRGM-YD7Q-RCFY**
<br>

### 1.4 验证Serial Number
将构造的序列号输入到序列号验证窗口中,点击“Next >”,验证通过,弹出“个人信息窗口”。
输入姓名、组织、邮箱信息,再点击“Next >”,弹窗“信息确认窗口”。
再点击“Next >”,先提示“正在激活许可”,接着又弹出了一个错误窗口,如下图:
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200813232924119.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl8zODUyNjE4MA==,size_16,color_FFFFFF,t_70#pic_center)
通过定位字符串找到处理上述流程的函数 *sub_514740*,该函数首先检查是否联网,
如果联网了,则会通过网络校验SerailNumer,验证失败就会弹出上面的错误信息;
如果没有联网,则会跳转到生成临时license文件的函数 *sub_513780* ,该函数会在 *C:\ProgramData\Source Insight\4.0* 目录生成 *si4.lic* 文件,允许程序单次运行,相当于一次性许可证,重启程序之后,这个许可也会失效。

据此猜测,既然生成了许可证文件,那么程序启动时就应该会去读 *si4.lic* 文件进行验证,打开监控软件便可看到对该文件进行了操作,如下图所示。至此,完成SerialNumber验证的分析,并构造出可用的序列号,接下来就分析 *si4.lic* 文件的加载及验证签名过程。
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200814000603408.png#pic_center)
<br>

## 2、验证RSA签名的过程——逆向分析
这部分内容分3个小节:
2.1 节 根据读文件的API跟踪程序调用流程,还原出程序启动验证RSA签名的流程
2.2 节 讲述最简单的po jie方法,只需修改两个字节就可以po jie 程序
2.3 节 讲述自己签名license文件的po jie方法,相比 *2.2 节* 的方法要麻烦很多,但是学到了RSA签名校验的知识以及CryptoAPI编程

### 2.1 验证RSA签名的流程
在 *1.4 节* 分析的最后提到,程序启动后会加载 *si4.lic* 许可证文件,并且会调用 *CreateFileMapping*API将文件内容映射到内存,所以对这个函数下API断点,就可以定位到读许可证的代码。而且读完许可证之后,就是验证许可证签名的代码。

程序中只有一处调用了这个API,结合动态调试,总结出调用关系如下所示:
```c
.text:0045B460WinMain
.text:0045B6CD      callsub_515000
.text:00515032            |- callsub_514ED0
.text:00514EF3                     |- callsub_417E40                           检查C:\ProgramData\Source Insight\4.0\si4.lic文件是否存在
.text:00514F03                     |- callsub_5140E0                           校验lic文件的函数,如果si4.lic文件存在,则执行校验
.text:005140FD                              |- callsub_5127A0                  加载si4.lic,校验字段值,保存校验结果。成功返回0xC8
.text:005127EA                              |      |- callsub_511150      
.text:00511156                              |      |      |- callsub_45A770 加载si4.lic文件
.text:0045A779                              |      |               |- callsub_45A290
.text:0045A2E7                              |      |                        |- callsub_41B290   
.text:0041B2B2                              |      |                                 |- callsub_4567F0
.text:00456898                              |      |                                 |      |- callds:CreateFileMappingW
.text:0041B2D9                              |      |                                 |- callds:MapViewOfFile
.text:00512806                              |      |- callsub_510570      校验并存储<Type>值,与"Trial"、"Beta"、"Standard"相比较,设置校验结果
.text:00512875                              |      |- callsub_510570      校验并存储<LicensedUser>值,不存在则结束,返回失败标志
.text:005128FA                              |      |- callsub_510570      校验并存储<Serial>值,不存在则结束,返回失败标志
.text:00512927                              |      |- callsub_510570      校验并存储<ActId>值,如果值等于"Deferred",设置标志位,表明si4.lic是断网激活时生成的一次性许可证
.text:00514102                              |- cmp   eax, 0C8h                判断 sub_5127A0 返回值,失败则弹出错误窗口
.text:00514133                              |- mov   eax,                判断如果是“一次性许可证”,则函数返回False
.text:0051419B                              |- callsub_512CF0               加载si4.lic,校验签名。成功返回0xC8
.text:00512D39                                       |- callsub_45A770      加载si4.lic文件                              
.text:00512D59                                       |- callsub_457D80      遍历si4.lic中的数据,查找<Signature>标签
.text:00512D6B                                       |- callsub_458520      找到<Signature>后,获取它的值
.text:00512DEB                                       |- callsub_402E00      Base64解码Signature字符串值,得到二进制签名数据
.text:00512E08                                       |- callsub_510640      CryptoAPI校验签名。成功返回0xC8

//还原成伪代码
InfoObj *g_infoObj;
int WinMain()
{
    g_infoObj->sub_515000();
}

//校验si4.lic中签名的函数
int sub_512CF0()
{
    sub_45A770();       //加载si4.lic文件                              
    sub_457D80();       //遍历si4.lic中的数据,查找<Signature>键值对
    sub_458520();       //从<Signature>键值对中,获取Value值
    sub_402E00();       //Base64解码Signature值,得到二进制签名数据
    sub_510640();       //使用CryptoAPI校验 RSA签名,校验成功返回C8
}
      
class InfoObj{
    int mLicenseType;   //offset:+0    许可证类型:Trial/Beta/Standard
   
    //InfoObj的构造函数
    sub_512BF0() {
    }
   
    sub_515000() {
      this.sub_514ED0();
    }
   
    sub_514ED0() {
      
      //检查C:\ProgramData\Source Insight\4.0\si4.lic文件是否存在
      boolean success = sub_417E40();   
      if(success)
      {
            //如果si4.lic文件存在,则执行校验
            this.sub_5140E0();            
      }
    }
   
    sub_5140E0() {
      
      //加载si4.lic,校验字段值,保存校验结果
      int loadLicResult = this.sub_5127A0();
      if(loadLicResult != 0xC8) {
            //弹出错误窗口
            return false;
      }
      
      //检查是否是一次性许可
      if(mLicenseType == 2) {
            //弹出错误窗口
            return false;
      }
      
      //加载si4.lic,校验签名
      if(sub_512CF0() != 0xC8) {
            //弹出错误窗口
            return false;
      }      
      
    }
   
    //读si4.lic文件,校验并保存字段值
    sub_5127A0(){
      sub_511150();         //加载si4.lic文件,这个函数里调用了 sub_45A770 CreateFileMappingW API
      sub_510570("Type");   //获取<Type>值,与"Trial"、"Beta"、"Standard"相比较,设置校验结果
      sub_510570("Serial");   //获取<Serial>值,不存在则结束,返回失败标志
      sub_510570("ActId");    //获取<ActId>值,如果值等于"Deferred",设置mLicenseType=2,表明si4.lic是断网激活时生成的一次性许可证
      ...
    }
};

```
调用关系看起来还是有些复杂,这里强调重要的三个函数作用,其中后两个函数需要还原成C语言代码, 以便验证自己签名的数据是否正确:

- *sub_5127A0* 函数,是校验 *si4.lic* 文件中字段的函数。如果使用之前生成的一次性许可,调用这个函数会校验失败,后续也不会走校验签名的逻辑。所以,需要注册一个试用许可,以便通过该函数的校验。
- *sub_402E00* 函数,Base64解码函数,将 *si4.lic* 文件中 *<Signature\>* 签名字符串,转为0x100字节大小的二进制签名数据。
- *sub_510640* 函数,调用CryptoAPI,校验二进制签名数据。
<br>

### 2.2 简单的po jie
在 *2.1 节* 的调用流程中提到,*sub_512CF0* 函数是签名校验的入口函数,如果此函数返回值等于0C8h,则表示校验通过;如果返回值不等于0C8h,则会弹出验证错误窗口,汇编代码如下面所示。

在*.text:005141A8*处检查校验结果,成功就跳转到 *loc_5141D4*,失败则会执行错误流程。
所以,可以将此处的比较跳转 *jz* 改为无条件跳转 *jmp*,修改二进制就是将 74 改为 EB,这样就过掉了校验签名。

```c
.text:0051419B E8 50 EB+                call    sub_512CF0
.text:005141A0 83 C4 08               add   esp, 8
.text:005141A3 3D C8 00+                cmp   eax, 0C8h
.text:005141A8 74 2A                  jz      short loc_5141D4
.text:005141AA
.text:005141AA          loc_5141AA:                           ; CODE XREF: .text:00514179j
.text:005141AA                                                ; .text:0051418Bj
.text:005141AA 83 BC 24+                cmp   dword ptr , 0
.text:005141B2 74 0D                  jz      short loc_5141C1
.text:005141B4 50                     push    eax
.text:005141B5 8B CE                  mov   ecx, esi
.text:005141B7 E8 D4 CB+                call    sub_510D90
.text:005141BC E8 2F E9+                call    sub_412AF0
.text:005141C1
.text:005141C1          loc_5141C1:                           ; CODE XREF: .text:005141B2j
.text:005141C1 8B CE                  mov   ecx, esi
.text:005141C3 E8 08 CF+                call    sub_5110D0
.text:005141C8 33 C0                  xor   eax, eax
.text:005141CA 5E                     pop   esi
.text:005141CB 81 C4 00+                add   esp, 100h
.text:005141D1 C2 04 00               retn    4
.text:005141D4          ; ---------------------------------------------------------------------------
.text:005141D4
.text:005141D4          loc_5141D4:                           ; CODE XREF: .text:00514184j
.text:005141D4                                                ; .text:005141A8j
```
<br>

### 2.3 自签名license文件po jie
上面分析调用流程时,已经提到 :
*sub_402E00* 函数,会把 *si4.lic* 文件中的字符串签名解码为0x100字节大小的二进制签名数据,命名该函数为 **Base64Decode**;
*sub_510640* 函数,会用程序中自带的2048位非对称加密的公钥,以 *si4.lic*文件中 *Signature* 标签之前的数据作为校验数据,与二进制签名数据作比对,命名该函数为 **VerifySignature**

先粗略描述一下服务器签名以及本地验证签名的过程:
- 首先,服务器有一对非对称密钥,导出PEM格式公钥后,写死在程序中,在后面 *VerifySignature* 函数中可以看到这个字符串公钥。私钥是存储在服务器上,且要严密保存的。
- 在 *si4.lic* 文件中,*Signature* 标签之前的所有内容(还要去掉所有的 \r\n\t和空格),就是待签名的数据。服务器使用私钥对数据签名后,生成0x100字节大小的签名数据,这部分数据经过 Base64编码,变成字符串数据,也就是 *si4.lic* 文件中 *Signature* 标签中的字符串。
- 程序启动后,会加载 *si4.lic* 文件,先调用 *Base64Decode* 函数将文件中的 Signature 字符串签名转为二进制签名,再把二进制签名和 *si4.lic* 中原始的待签名的数据传给 *VerifySignature* 函数,该函数内调用 *CryptVerifySignatureW* API校验签名是否匹配。

*si4.lic* 文件内容如下:
```xml
<!--

        Source Insight 4.x License File

        DO NOT EDIT THIS FILE.Doing so will render it unusable.

        This license was created for:
       
                xxx
                51asm
                xxx@xxx.com
       
-->
<SourceInsightLicense>
        <Header
                Value="1"
        />
        <LicenseProperties
                LicensedUser="51asm"
                ActId="9930152826"
                HWID="ZM6QWPSW-MNFUHVF5"
                Serial="S4SG-KRGM-YD7Q-RCFY"
                Organization=""
                Email="xxx@xxx.com"
                Type="Standard"
                Version="4"
                MinorVersion="0"
                Date="2020-08-15"
                Expiration="2030-08-15"
        />

        <Signature
                Value="TMNEF2MnPkl3+gLskqe8+X1Yq6ZiqFBMddeWJWC9ttGxxSBRHDMX9QCJowo5ffHqURy5/dhSJ5rDsqxMAK5h30WXcjJcP8D2Cc0P0igilnKX9gFoX/FaBMnDbQMTD6bq4UbV6lxiFXxmTVW8/Xt1rA1b7nMzdp1apkAquyPQizglC471Jo1JMeehEuVeLAe38cENDDQVQtV28u9AGyTaCrA6IIOIsJSrWeldmW8VHbTkmD4bt87OdTDt/oN3+sDcV8idMm02eGCX2/HQ/Ef0ozGQK1BoljDEJtGXlhzzKputxWK/O36WPPrE5iuiFNmfrqewH3NMhoEWszhFxapuHg=="
        />
</SourceInsightLicense>
```


#### 2.2.1 Base64Decode函数
这个是还原 *sub_402E00* 汇编代码的函数,该函数将 *si4.lic* 文件中的 Signature字符串签名数据解码为二进制签名数据。

-char\* szSignature   传入参数, *si4.lic* 文件 *Signature* 标签中的字符串签名数据
-BYTE\* ptr_pbSignature   传出参数,存放解码后的二进制签名数据的缓冲区
-DWORD\* ptr_dwSigLen   传出参数,保存二进制签名数据的大小

```c
//.text:00512DEB               call    sub_402E00
int Base64Decode(char* szSignature, BYTE* ptr_pbSignature, DWORD* ptr_dwSigLen)
{
    int i = 0;
    int j = 0;
    int edi = 0;
    int edx = 0;
    while (true)
    {
      unsigned int ecx = 0;
      if ((unsigned int)(szSignature - 'A') <= 0x19)       // char = 'A' ~ 'Z'
      {                                                       //ecx =0~19h
            ecx = (unsigned int)szSignature - 'A';
            if (ecx < 0)
            {
                break;
            }
      }
      else if ((unsigned int)(szSignature - 'a') <= 0x19)// char ='a' ~ 'z'
      {                                                       //ecx =1Ah ~ 33h
            ecx = (unsigned int)szSignature - 'G';
            if (ecx < 0)
            {
                break;
            }
      }
      else if ((unsigned int)(szSignature - '0') <= 9)   // char = '0' ~ '9'
      {                                                       //ecx = 34h ~ 3Dh
            ecx = (unsigned int)szSignature + 4;
            if (ecx < 0)
            {
                break;
            }
      }
      else if (szSignature == '+')                         // char = 2Bh
      {                                                       //ecx = 3Eh
            ecx = '>';
      }
      else if (szSignature == '/')                         // char = 2Fh
      {                                                       //ecx = 3Fh
            ecx = '?';
      }
      else
      {
            break;
      }

      edi <<= 6;
      edi |= ecx;
      edx += 6;
      i++;
      edi &= 0xFFFF;
      if ((edx & 0xFFFF) >= 8)
      {
            edx += 0xFFF8;
            unsigned short di = edi & 0xFFFF;
            unsigned char dl = edx & 0xFF;
            BYTE bt = (di >> dl) & 0xFF;
            ptr_pbSignature = bt;
      }
    }

    while (szSignature == '=')
    {
      i++;
    }
    *ptr_dwSigLen = j;
    return i;
}
```
<br>

#### 2.2.2 VerifySignature函数
这个是还原 *sub_510640* 汇编代码的函数,该函数完成签名校验。

- BYTE\* pbData   待签名字符串数据,来源自 *si4.lic* 文件中 *Signature* 标签之前的所有内容(去掉所有的 \r\n\t和空格)
- DWORD dwDataLen    待签名字符串数据的长度
- BYTE\* pbSignature    私钥签名的数据,*2.2.1 节 Base64Decode* 函数的传出参数
- DWORD dwSigLen   私钥签名的数据长度,*2.2.1 节 Base64Decode* 函数的传出参数

```c

//.text:00512E08               call    sub_510640
DWORD VerifySignature(BYTE* pbData, DWORD dwDataLen, BYTE* pbSignature, DWORD dwSigLen)
{
    DWORD cbPublicKey = 0;
    //PEM格式公钥,作为CryptStringToBinaryA函数的第一个参数
    //.text:00510658               push    offset pszString ; "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgk"..
    LPCSTR publicKey = g_pszPublicKey;

    BYTE bBinary = { 0 };
    DWORD cbBinary = sizeof(bBinary);
    if (!CryptStringToBinaryA((LPCSTR)publicKey, cbPublicKey, CRYPT_STRING_BASE64HEADER, bBinary, &cbBinary, NULL, NULL))
      return 0x1D8;

    PCERT_PUBLIC_KEY_INFO pvStructInfo = NULL;
    DWORD cbStructInfo = 0;
    if (!CryptDecodeObjectEx(X509_ASN_ENCODING, X509_PUBLIC_KEY_INFO, bBinary, cbBinary,
      CRYPT_DECODE_ALLOC_FLAG, NULL, &pvStructInfo, &cbStructInfo))
      return 0x1D8;

    HCRYPTPROV hProv = NULL;
    if (!CryptAcquireContextW(&hProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))
      return 0x1D9;

    HCRYPTKEY hKey = NULL;
    if (!CryptImportPublicKeyInfo(hProv, X509_ASN_ENCODING, pvStructInfo, &hKey))
      return 0x1DA;

    LocalFree(pvStructInfo);

    HCRYPTHASH hHash = NULL;
    if (!CryptCreateHash(hProv, CALG_SHA1, NULL, 0, &hHash))
      return 0x1DA;

    if (!CryptHashData(hHash, pbData, dwDataLen, 0))
      return 0x1DB;

    BOOL success = CryptVerifySignatureW(hHash, pbSignature, dwSigLen, hKey, NULL, 0);

    CryptDestroyHash(hHash);
    CryptReleaseContext(hProv, 0);

    if (success)
      return 0xC8;

    return 0x1CE;
}
```
<br>

#### 2.2.3 自签名 si4.lic 文件
自签名许可证分为两个部分,
第一部分:签名数据
1. 先导出公钥,并手工替换掉sourceinsight4.exe中的公钥
2. 签名 *si4.lic* 中的待签名数据,得到二进制签名数据
3. 转换二进制签名数据为字符串,并拷贝到 *si4.lic* 文件的 *<Signature\>* 标签中

第二部分:校验签名
调用上面的 *Base64Decode* 和 *VerifySignature* 函数,校验我们自己签名的数据是否正确。

代码如下,完整代码可点击右侧超链接,在 (https://github.com/jungz7/Learn-Windows/tree/master/LearnCrack/CrackSI4)上查看

```c
//待签名数据
char* sig4_data = "<!--\
SourceInsight4.xLicenseFile\
DONOTEDITTHISFILE.Doingsowillrenderitunusable.\
Thislicensewascreatedfor:\
xxx\
51asm\
xxx@xxx.com\
-->\
<SourceInsightLicense>\
<Header\
Value=\"1\"\
/>\
<LicenseProperties\
LicensedUser=\"51asm\"\
ActId=\"9930152826\"\
HWID=\"ZM6QWPSW-MNFUHVF5\"\
Serial=\"S4SG-KRGM-YD7Q-RCFY\"\
Organization=\"\"\
Email=\"xxx@xxx.com\"\
Type=\"Standard\"\
Version=\"4\"\
MinorVersion=\"0\"\
Date=\"2020-08-15\"\
Expiration=\"2030-08-15\"\
/>";

void main()
{
    // ----------------------------------------------------
    // 第一部分:签名 Sign

    TestSignVerify edc;        //TestSignVerify是封装的CryptoAPI,用来做签名和校验
    edc.InitializeProviderForSigner(NULL, PROV_RSA_FULL);
    // 1. 导出公钥
    edc.ExportX509PEMPublicKey("public.key");
    // 用导出的字符串公钥 替换掉程序中的原始公钥,用WinHex打开程序后,直接搜索"BEGIN PUBLIC KEY"就可以定位到公钥字符串的位置
    // 注意Windows平台导出的公钥中,换行是 0D 0A,而程序中提供的公钥是 0A

    // 2. 签名数据
    BYTE* bSig = NULL;
    DWORD szSigLen = 0;
    if (!edc.SignMessage(
      CALG_SHA1,
      (BYTE*)sig4_data,   // 待签名字符串数据,来源自si4.lic 文件中 <Signature>标签之前的所有内容(去掉所有的 \r\n\t和空格)
      strlen(sig4_data),// 待签名字符串数据长度
      (BYTE**)&bSig,      // 传出参数,二进制签名数据
      &szSigLen))         // 传出参数,二进制签名数据大小
      return;
   
    // 3. Base64编码二进制签名,转为字符串签名
    char szSig = { 0 };
    Base64Encode(bSig, szSig);
    printf("%s\r\n", szSig);
    // 这里需要把szSig字符串拷贝到 si4.lic文件的<Signature>标签中

    // ----------------------------------------------------
    // 第二部分:校验 Verify

    // 1. Base64解码字符串签名,转为二进制签名
    BYTE bSigBuff = { 0 };
    DWORD dwSigLen = 0;
    int nConvertLen = Base64Decode(szSig, bSigBuff, &dwSigLen);

    // 2. 校验签名
    DWORD dwRet = VerifySignature(
      (BYTE*)sig4_data,
      strlen(sig4_data),
      bSigBuff,
      dwSigLen);

    if (dwRet == 0xC8)
      printf("success\r\n");
    else
      printf("failed\r\n");

    return;
}
```
<br>

## 3. 过掉在线检查序列号
完成步骤2的po jie之后,启动程序,可以正常运行。但是程序运行2分钟左右之后,会再次弹出需要激活的窗口,所以猜测:程序启动先验证本地许可证,之后又联网验证,验证失败则会再弹出需要激活窗口。

基于此猜测,在导入表中查找网络发包相关API,看到有*HttpSendRequestW* API,在调试器中下API断点,成功断下后,看到 *lpOptional* 参数中包含有Serial Number,如下图所示:


分析调用关系可知,每次程序启动,都会在 *sub_514290* 函数中创建在线检查序列号的线程,该线程的启动函数地址是 *sub_513470*。所以,可以将该函数入口处代码修改为 *ret*,直接结束该线程。参考下面的代码,就是修改 *.text:00513470* 地址处的 83 改为C3。
```c
//该函数中创建在线检查序列号的线程
.text:00514290 sub_514290      proc near               ; CODE XREF: sub_464B00+A6p
...                            ...
.text:005142F2               push    esi             ; int
.text:005142F3               push    0               ; dwStackSize
.text:005142F5               push    offset sub_513470 ; 在线检查序列号的线程函数地址
.text:005142FA               call    __beginthread

//在线检查的线程函数
.text:00513470          sub_513470      proc near               ; DATA XREF: sub_514290+65o
.text:00513470 83 EC 18               sub   esp, 18h
...                                     ...
.text:0051347D 68 C0 D4+                push    1D4C0h          ; dwMilliseconds
.text:00513482 FF D5                  call    ebp ; Sleep   ; sleep 两分钟
...                                     ...
.text:005135B6 E8 15 FB+                call    sub_5130D0      ; 此函数中调用发包的API
.text:00513109                                  |-- callsub_511250
.text:0051156E                                          |-- callsub_425150
.text:004252AD                                                      |-- callds:HttpSendRequestW
```

wzhenghit 发表于 2020-8-19 19:53

溪语的小窝吖 发表于 2020-8-16 14:34
如何能进行系统的学习,害

给我的学校做一下宣传:我是在科锐学的逆向,要是对这块感兴趣可以了解一下 www.51asm.com

lumiaz7 发表于 2020-8-16 10:39

牛逼 思路清晰

SriChen 发表于 2020-8-16 10:39

厉害厉害               

m-10306 发表于 2020-8-16 10:42

厉害思路清晰过程也很详细 学习了

gblgbl 发表于 2020-8-16 10:57

非常有学习价值的一篇逆向分析的技术帖,辛勤啦!感谢分享心得。{:1_893:}

TazBbB9Wat81 发表于 2020-8-16 11:00

好笔记!收藏以后参考学习

承蒙厚爱丶 发表于 2020-8-16 11:11

收藏以后参考学习

吾爱vision 发表于 2020-8-16 11:16

厉害厉害

奋斗的小骚年 发表于 2020-8-16 11:18

厉害厉害,收藏了。

不苦小和尚 发表于 2020-8-16 11:30

不错不错,厉害,学习了
页: [1] 2 3 4 5 6 7 8 9 10
查看完整版本: Windows逆向学习笔记——破解Source Insight 4