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

查看 159|回复 10
作者:wzhenghit   
前言
这里po jie的是当前最新版本Source Insight 4.0.118,总结po jie过程有以下三个部分:
[ol]
  • 逆向Serial Number序列号验证算法,构造可用序列号
  • 逆向分析验证license签名过程,自签名license文件,并使用自己的公钥替换程序中原始公钥
  • 程序运行起来后,会单独开启一个线程在线检查Serial Number是否有效,需要修改二进制文件过掉此检查
    [/ol]
    除此之外,笔者也看了一些大佬的文章,提到有黑名单检查,因为没遇到这个逻辑,程序暂时也能用,所以这里的破解中没有涉及,后面如果遇到再研究也不迟。
    文中用到的所有代码可点击右侧超链接,在 Github上查看
    环境准备
    IDA6.8、x32dbg
    官网 下载sourceinsight40118-setup.exe
    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程序


    1、验证序列号的过程——逆向分析
    这部分内容分4个小节:
    1.1 节 讲述如何定位到验证序列号的代码
    1.2 节 中逆向还原验证序列号格式的函数,并按照格式构造出正式版序列号
    1.3 节 中逆向还原本地校验序列号的函数,构造出可通过本地校验的序列号
    1.4 节 本地验证序列号
    1.1 定位“Serial Number错误”弹窗代码
    安装程序后,打开sourceinsight4.exe程序会看到选择license文件的界面,如下图:

    选择第一项“输入serial number”,点击“Next >”继续,显示输入Serial Number的对话框,并且提示 Serial Number 的格式“S4XX-XXXX-XXXX-XXXX”。按照格式输入一个,点击"Next>",会弹出“无效的Serial Number”警告,如下图:

    在IDA中搜索弹窗警告中的文本“The serial number ...”,找到引用此字符串的地址是 .text:00513A69。
    分析.text:00513A69此位置上下文代码,可以看出 .text:00513A45 call sub_510B50 是Serial Number的验证函数,若验证失败(返回结果0),则会再判断是否是3.x版本的Serail Number,不是则弹出上图中的警告;若验证成功则跳转到 loc_513ACB 继续判断验证结果。代码如下所示:
    .text:00513A25                 mov     eax, dword_673488
    .text:00513A2A                 push    1               ; int
    .text:00513A2C                 lea     ecx, [eax+604h]
    .text:00513A32                 push    ecx             ; int
    .text:00513A33                 lea     edx, [eax+60Ch]
    .text:00513A39                 push    edx             ; int
    .text:00513A3A                 add     eax, 608h
    .text:00513A3F                 push    eax             ; int
    .text:00513A40                 lea     eax, [esp+158h+MultiByteStr]
    .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, [esp+128h+MultiByteStr]
    .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     [eax+604h], 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
    1.2 Serial Number验证函数 分析
    详细分析上面步骤中提到的 sub_510B50 处的序列号验证函数,总结出Serial Number的验证规则如下:
    SerailNumber字符串长度必须为19(16个字符加上3个分隔符)
    SerailNumber[0]必须等'S'
    SerailNumber[1]是'0'-'9'
    SerailNumber[2]等于'T'表示Trial license试用许可,等于'B'表示Beta license测试许可,等于'S'表示Standard license标准许可,等于'U'表示Upgrade license升级许可
    SerailNumber[3]等于 'G'、'V'、'R' 其中之一
    SerailNumber[6]等于 'R'、'G'、'D'、'F' 其中之一
    SerialNumber的前12个字符经过函数 .text:00510C6B call sub_510320 转换后得到4个字符,与SerialNumber的最后4个字符必须相同
    代码如下所示:
    .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 ptr  4
    .text:00510B50                              
    .text:00510B50 ptr_nSN[3]Flag  = dword ptr  8
    .text:00510B50 ptr_nLicTypeFlag= dword ptr  0Ch
    .text:00510B50 ptr_nVersionFlag= dword ptr  10h
    .text:00510B50 arg_10          = dword ptr  14h
    .text:00510B50
    .text:00510B50                 sub     esp, 18h
    .text:00510B53                 push    esi
    .text:00510B54                 mov     esi, [esp+1Ch+ptr_szSN]
    .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     [esi+4], al     ; ptr_szSN[4] = '-'
    .text:00510B75                 jnz     loc_510C86
    .text:00510B7B                 cmp     [esi+9], al     ; ptr_szSN[9] = '-'
    .text:00510B7E                 jnz     loc_510C86
    .text:00510B84                 cmp     [esi+0Eh], al   ; ptr_szSN[14] = '-'
    .text:00510B87                 jnz     loc_510C86
    .text:00510B8D                 cmp     byte ptr [esi], 'S' ; ptr_szSN[0] = 'S'
    .text:00510B90                 jnz     loc_510C86
    .text:00510B96                 mov     ecx, [esp+1Ch+arg_10]
    .text:00510B9A                 test    ecx, ecx
    .text:00510B9C                 jz      short loc_510BB5
    .text:00510B9E                 mov     al, [esi+6]
    .text:00510BA1                 cmp     al, 'R'         ; ptr_szSN[6] == 'R'
    .text:00510BA3                 jz      short loc_510BB5
    .text:00510BA5                 cmp     al, 'G'         ; ptr_szSN[6] == 'G'
    .text:00510BA7                 jz      short loc_510BB5
    .text:00510BA9                 cmp     al, 'D'         ; ptr_szSN[6] == 'D'
    .text:00510BAB                 jz      short loc_510BB5
    .text:00510BAD                 cmp     al, 'F'         ; ptr_szSN[6] == '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, [esi+1]
    .text:00510BB8                 cmp     al, '0'         ; ptr_szSN[1] >= '0'
    .text:00510BBA                 jl      loc_510C86
    .text:00510BC0                 cmp     al, '9'         ; ptr_szSN[1]
    1.3 构造可用的Serial Number
    步骤1.2中提到Serial Number的验证规则之一是:序列号的前12个字符经过函数 sub_510320 转换后得到4个字符,与序列号的最后4个字符相同。
    以下便是是 sub_510320 汇编代码分析以及据此还原的C代码,将此函数命名为 Get4charBySNPre12char
    .text:00510320 sub_510320      proc near               ; CODE XREF: sub_510390+73p
    .text:00510320                                         ; sub_510B50+11Bp ...
    .text:00510320
    .text:00510320 ptr_szSN        = dword ptr  4
    .text:00510320 nSNLen          = dword ptr  8
    .text:00510320 ary256char      = dword ptr  0Ch
    .text:00510320 ptr_4charsResult= dword ptr  10h
    .text:00510320
    .text:00510320                 push    ebx
    .text:00510321                 mov     ebx, [esp+4+nSNLen]
    .text:00510325                 push    ebp
    .text:00510326                 mov     ebp, [esp+8+ptr_szSN]
    .text:0051032A                 push    esi
    .text:0051032B                 push    edi
    .text:0051032C                 mov     edi, [esp+10h+ary256char]
    .text:00510330                 xor     esi, esi
    .text:00510332
    .text:00510332 DO_BEGIN:                               ; CODE XREF: sub_510320+5Ej
    .text:00510332                 movsx   eax, byte ptr [ebp+0]
    .text:00510336                 add     eax, esi
    .text:00510338                 and     eax, 0FFh
    .text:0051033D                 mov     cl, [eax+edi]
    .text:00510340                 mov     eax, 1
    .text:00510345                 cmp     ebx, eax
    .text:00510347                 jbe     short loc_510361
    .text:00510349                 lea     esp, [esp+0]
    .text:00510350
    .text:00510350 _DO_BEGIN:                              ; CODE XREF: sub_510320+3Fj
    .text:00510350                 movsx   edx, byte ptr [eax+ebp]
    .text:00510354                 movzx   ecx, cl
    .text:00510357                 xor     edx, ecx
    .text:00510359                 mov     cl, [edx+edi]
    .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, [esp+10h+ptr_4charsResult]
    .text:00510370                 inc     esi
    .text:00510371                 mov     dl, byte ptr ds:sz26Chars[edx] ; "KV96GMJYH7QF5TCW4U3XZPRSDN"
    .text:00510377                 mov     [esi+eax-1], 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
    因为 Get4charBySNPre12char 函数是通过SerialNumber的前12个字符生成后4个字符,所以可以将构造的前12个字符传入此函数,计算出一个可用的SerialNumber的后4个字符,代码如上,最终得到一个Standard正式版序列号:S4SG-KRGM-YD7Q-RCFY
    1.4 验证Serial Number
    将构造的序列号输入到序列号验证窗口中,点击“Next >”,验证通过,弹出“个人信息窗口”。
    输入姓名、组织、邮箱信息,再点击“Next >”,弹窗“信息确认窗口”。
    再点击“Next >”,先提示“正在激活许可”,接着又弹出了一个错误窗口,如下图:

    通过定位字符串找到处理上述流程的函数 sub_514740,该函数首先检查是否联网,
    如果联网了,则会通过网络校验SerailNumer,验证失败就会弹出上面的错误信息;
    如果没有联网,则会跳转到生成临时license文件的函数 sub_513780 ,该函数会在 C:\ProgramData\Source Insight\4.0 目录生成 si4.lic 文件,允许程序单次运行,相当于一次性许可证,重启程序之后,这个许可也会失效。
    据此猜测,既然生成了许可证文件,那么程序启动时就应该会去读 si4.lic 文件进行验证,打开监控软件便可看到对该文件进行了操作,如下图所示。至此,完成SerialNumber验证的分析,并构造出可用的序列号,接下来就分析 si4.lic 文件的加载及验证签名过程。

    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,结合动态调试,总结出调用关系如下所示:
    .text:0045B460  WinMain
    .text:0045B6CD      call  sub_515000
    .text:00515032            |- call  sub_514ED0
    .text:00514EF3                     |- call  sub_417E40                           检查C:\ProgramData\Source Insight\4.0\si4.lic文件是否存在
    .text:00514F03                     |- call  sub_5140E0                           校验lic文件的函数,如果si4.lic文件存在,则执行校验
    .text:005140FD                              |- call  sub_5127A0                  加载si4.lic,校验字段值,保存校验结果。成功返回0xC8
    .text:005127EA                              |        |- call  sub_511150      
    .text:00511156                              |        |        |- call  sub_45A770 加载si4.lic文件
    .text:0045A779                              |        |                 |- call  sub_45A290
    .text:0045A2E7                              |        |                          |- call  sub_41B290   
    .text:0041B2B2                              |        |                                   |- call  sub_4567F0
    .text:00456898                              |        |                                   |        |- call  ds:CreateFileMappingW
    .text:0041B2D9                              |        |                                   |- call  ds:MapViewOfFile  
    .text:00512806                              |        |- call  sub_510570      校验并存储值,与"Trial"、"Beta"、"Standard"相比较,设置校验结果
    .text:00512875                              |        |- call  sub_510570      校验并存储值,不存在则结束,返回失败标志
    .text:005128FA                              |        |- call  sub_510570      校验并存储值,不存在则结束,返回失败标志
    .text:00512927                              |        |- call  sub_510570      校验并存储值,如果值等于"Deferred",设置标志位,表明si4.lic是断网激活时生成的一次性许可证
    .text:00514102                              |- cmp   eax, 0C8h                判断 sub_5127A0 返回值,失败则弹出错误窗口
    .text:00514133                              |- mov   eax, [esi]               判断如果是“一次性许可证”,则函数返回False
    .text:0051419B                              |- call  sub_512CF0               加载si4.lic,校验签名。成功返回0xC8
    .text:00512D39                                       |- call  sub_45A770      加载si4.lic文件                              
    .text:00512D59                                       |- call  sub_457D80      遍历si4.lic中的数据,查找标签
    .text:00512D6B                                       |- call  sub_458520      找到后,获取它的值
    .text:00512DEB                                       |- call  sub_402E00      Base64解码Signature字符串值,得到二进制签名数据
    .text:00512E08                                       |- call  sub_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中的数据,查找键值对
        sub_458520();       //从键值对中,获取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");     //获取值,与"Trial"、"Beta"、"Standard"相比较,设置校验结果
            sub_510570("Serial");   //获取值,不存在则结束,返回失败标志
            sub_510570("ActId");    //获取值,如果值等于"Deferred",设置mLicenseType=2,表明si4.lic是断网激活时生成的一次性许可证
            ...
        }
    };
    调用关系看起来还是有些复杂,这里强调重要的三个函数作用,其中后两个函数需要还原成C语言代码, 以便验证自己签名的数据是否正确:
  • sub_5127A0 函数,是校验 si4.lic 文件中字段的函数。如果使用之前生成的一次性许可,调用这个函数会校验失败,后续也不会走校验签名的逻辑。所以,需要注册一个试用许可,以便通过该函数的校验。
  • sub_402E00 函数,Base64解码函数,将 si4.lic 文件中  签名字符串,转为0x100字节大小的二进制签名数据。
  • sub_510640 函数,调用CryptoAPI,校验二进制签名数据。

    2.2 简单的po jie
    在 2.1 节 的调用流程中提到,sub_512CF0 函数是签名校验的入口函数,如果此函数返回值等于0C8h,则表示校验通过;如果返回值不等于0C8h,则会弹出验证错误窗口,汇编代码如下面所示。
    在.text:005141A8处检查校验结果,成功就跳转到 loc_5141D4,失败则会执行错误流程。
    所以,可以将此处的比较跳转 jz 改为无条件跳转 jmp,修改二进制就是将 74 改为 EB,这样就过掉了校验签名。
    .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 [esp+108h], 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
    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 文件内容如下:
       
       
       
    2.2.1 Base64Decode函数
    这个是还原 sub_402E00 汇编代码的函数,该函数将 si4.lic 文件中的 Signature字符串签名数据解码为二进制签名数据。
  • char* szSignature   传入参数, si4.lic 文件 Signature 标签中的字符串签名数据
  • BYTE* ptr_pbSignature   传出参数,存放解码后的二进制签名数据的缓冲区
  • DWORD* ptr_dwSigLen   传出参数,保存二进制签名数据的大小

    //.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') ';
            }
            else if (szSignature == '/')                         // char = 2Fh
            {                                                       //  ecx = 3Fh
                ecx = '?';
            }
            else
            {
                break;
            }
            edi = 8)
            {
                edx += 0xFFF8;
                unsigned short di = edi & 0xFFFF;
                unsigned char dl = edx & 0xFF;
                BYTE bt = (di >> dl) & 0xFF;
                ptr_pbSignature[j++] = bt;
            }
        }
        while (szSignature == '=')
        {
            i++;
        }
        *ptr_dwSigLen = j;
        return i;
    }
    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 函数的传出参数

    //.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[0x800] = { 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;
    }
    2.2.3 自签名 si4.lic 文件
    自签名许可证分为两个部分,
    第一部分:签名数据
    [ol]
  • 先导出公钥,并手工替换掉sourceinsight4.exe中的公钥
  • 签名 si4.lic 中的待签名数据,得到二进制签名数据
  • 转换二进制签名数据为字符串,并拷贝到 si4.lic 文件的  标签中
    [/ol]
    第二部分:校验签名
    调用上面的 Base64Decode 和 VerifySignature 函数,校验我们自己签名的数据是否正确。
    代码如下,完整代码可点击右侧超链接,在 Github上查看
    //待签名数据
    char* sig4_data = "\
    \
    \
    ";
    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 文件中 标签之前的所有内容(去掉所有的 \r\n\t和空格)
            strlen(sig4_data),  // 待签名字符串数据长度
            (BYTE**)&bSig,      // 传出参数,二进制签名数据
            &szSigLen))         // 传出参数,二进制签名数据大小
            return;
        // 3. Base64编码二进制签名,转为字符串签名
        char szSig[345] = { 0 };
        Base64Encode(bSig, szSig);
        printf("%s\r\n", szSig);
        // 这里需要把szSig字符串拷贝到 si4.lic文件的标签中
        // ----------------------------------------------------
        // 第二部分:校验 Verify
        // 1. Base64解码字符串签名,转为二进制签名
        BYTE bSigBuff[0x2000] = { 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;
    }
    3. 过掉在线检查序列号
    完成步骤2的po jie之后,启动程序,可以正常运行。但是程序运行2分钟左右之后,会再次弹出需要激活的窗口,所以猜测:程序启动先验证本地许可证,之后又联网验证,验证失败则会再弹出需要激活窗口。
    基于此猜测,在导入表中查找网络发包相关API,看到有  HttpSendRequestW API,在调试器中下API断点,成功断下后,看到 lpOptional 参数中包含有Serial Number,如下图所示:
    分析调用关系可知,每次程序启动,都会在 sub_514290 函数中创建在线检查序列号的线程,该线程的启动函数地址是 sub_513470。所以,可以将该函数入口处代码修改为 ret,直接结束该线程。参考下面的代码,就是修改 .text:00513470 地址处的 83 改为C3。
    //该函数中创建在线检查序列号的线程
    .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                                  |-- call  sub_511250
    .text:0051156E                                            |-- call  sub_425150
    .text:004252AD                                                      |-- call  ds:HttpSendRequestW

    函数, 文件

  • wzhenghit
    OP
      


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

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

    牛逼 思路清晰
    SriChen   

    厉害厉害               
    m-10306   

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

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

    好笔记!收藏以后参考学习
    承蒙厚爱丶   

    收藏以后参考学习
    吾爱vision   

    厉害厉害
    奋斗的小骚年   

    厉害厉害,收藏了。
    您需要登录后才可以回帖 登录 | 立即注册