飘云阁(PYG官方)

 找回密码
 加入论坛

QQ登录

只需一步,快速开始

扫一扫,访问微社区

查看: 617|回复: 20

[macOS] 手把手教你破解 MacOS 系列之 Bartender 3 - 操作实战5步走

[复制链接]
  • TA的每日心情
    开心
    2019-3-17 22:44
  • 签到天数: 132 天

    [LV.7]常住居民III

    发表于 2019-9-19 02:33:54 | 显示全部楼层 |阅读模式
    本帖最后由 tree_fly 于 2019-9-19 09:36 编辑

    手把手教你破解MacOS系列之Bartender 3
    逆向分析、敏捷劫持、追溯爆破、算法推算、公钥替换、注册机


    【破文作者】tree_fly/P.Y.G
    【作者邮箱】itreefly@hotmail.com
    【作者主页】itreefly.com
    【破解平台】MacOS
    【破解声明】请勿商用;本文仅做研究所用。





    一、开始
      大家好,我是tree_fly。欢迎来到飘云阁。今天来分析一款macOS平台的软件(这是一款管理菜单栏图标的软件 Bartender 3 v3.0.64),尝试从不同的角度慢慢切入,尽量阐述清晰,偏于操作实战,易于上手,最重要的是一起享受沉浸逆向的愉快时光。


    Bartender 3 官网:www.macbartender.com/



    二、收集资料


    先简单分析一些基础的信息,比如注册界面及功能限制的内容





    • 采用用户名及注册码验证模式
    • 没有网络验证
    • 4周时间全功能试用
    • 试用期结束后功能失效,弹过期购买窗口




    三、逆向实战

    收集完一些基本资料后,启动神器[Hopper V4],载入`/Applications/Bartender 3.app/Contents/MacOS/Bartender 3`分析。根据一些关键词如`license`,快速切入到注册验证相关的函数。
    Hopper V4是Hopper Disassembler V4的简称,官网:https://www.hopperapp.com/ ,v4.0.8是一个[特别的版本](https://chinapyg.com/thread-87967-1-1.html)




    依据经验,`[Class* isLicensed]`这样的Bool返回值函数是要重点关注的。

    **操作实战 1**

    说做就做,先注入到程序,让所有`[Class* isLicensed]`返回true,测试一下,看看好用不好用。

    这次我们换个花样玩,不在Hopper内手动修改函数开头的汇编代码:

    [Asm] 纯文本查看 复制代码
    /* return true */
    mov rax, 0x1
    ret


    采用敏捷调试方案 **Frida**。

    正常运行Bartender 3,打开iTerm(或系统的终端),输入注入代码:

    [Bash shell] 纯文本查看 复制代码
    ~ frida-trace -m "-[*Bartender* *icense*]" "Bartender 3"


    操作无误的情况下,终端会显示如下信息:
    [Bash shell] 纯文本查看 复制代码
    Instrumenting functions...
    -[Bartender_3.AboutPreferencesController isLicensed]: Auto-generated handler at "/Users/lg/__handlers__/__Bartender_3.AboutPreferencesCo_07d0e87a.js"
    -[Bartender_3.LicensePreferencesController licenseBartenderWithSender:]: Auto-generated handler at "/Users/lg/__handlers__/__Bartender_3.LicensePreferences_b42e61ac.js"
    -[Bartender_3.LicensePreferencesController licenseSegmentClickedWithSender:]: Auto-generated handler at "/Users/lg/__handlers__/__Bartender_3.LicensePreferences_c4914f22.js"
    -[Bartender_3.LicensePreferencesController isLicensed]: Auto-generated handler at "/Users/lg/__handlers__/__Bartender_3.LicensePreferences_aa4237d6.js"
    -[Bartender_3.AppDelegate showPreferencesLicenseView:]: Auto-generated handler at "/Users/lg/__handlers__/__Bartender_3.AppDelegate_showPr_ad1fa37a.js"
    -[Bartender_3.AppDelegate isLicensed]: Auto-generated handler at "/Users/lg/__handlers__/__Bartender_3.AppDelegate_isLicensed_.js"
    Started tracing 6 functions. Press Ctrl+C to stop.


    最后一行提示共计有6个函数被hook,并且已经在当前工作目录下创建了“__handlers __”文件夹,其中包含了对应的6个js文件。

    按下**Ctrl+C**中断,仔细查看终端提示的信息,其中有3个`isLicensed`函数被击中,分别是:

    [Objective-C] 纯文本查看 复制代码
    -[Bartender_3.AboutPreferencesController isLicensed]
    -[Bartender_3.LicensePreferencesController isLicensed]
    -[Bartender_3.AppDelegate isLicensed]


    接下来要做的是逐一修改对应的js文件,比如打开`-[Bartender_3.AppDelegate isLicensed]`对应的`Bartender_3.AppDelegate_isLicensed_.js`,其内容是:

    [JavaScript] 纯文本查看 复制代码
    {
      onEnter: function (log, args, state) {
        log('-[Bartender_3.AppDelegate isLicensed]');
      },
      onLeave: function (log, retval, state) {
      }
    }


    修改onLeave函数内容,仅仅加入一行代码:`retval.replace(1);`。代码意思就是在函数运行结束时修改返回值为1(true)。如下:

    [JavaScript] 纯文本查看 复制代码
    {
      onEnter: function (log, args, state) {
        log('-[Bartender_3.AppDelegate isLicensed]');
      },
      onLeave: function (log, retval, state) {
        retval.replace(1);  /* Just add one line code. */
      }
    }


    其他2个函数的hook代码修改同上操作,完成了所有的js文件修改后,再次输入注入代码:

    [Bash shell] 纯文本查看 复制代码
    ~ frida-trace -m "-[*Bartender* *icense*]" "Bartender 3"


    此时!测试Bartender 3,发现没有了试用提醒,没有试用倒计时,也没有注册码输入框了,活脱脱的已激活状态。恭喜你!你已经完成了操作实战 1的内容。

    然而!不要高兴的太早,面前的假象容易迷惑了双眼。4周的试用时间还没有到呢,怎么知道不会有暗桩呢~

    为了交点智商税,手动修改下系统时间为4周后,果不其然,功能失效了。不要泄气,至少目前知道了是该进一步分析`isLicensed`的伪代码了。


    **操作实战 2**

    有了操作实战 1的经验积累,接下来逐一分析`isLicensed`函数的细节。

    挑选其中一个查看一下伪代码:

    [C] 纯文本查看 复制代码
    /* @class _TtC11Bartender_311AppDelegate */
    -(char)isLicensed {
        rax = sub_100068310();
        rax = ObjectiveC._convertBoolToObjCBool(rax & 0xff);
        rax = sign_extend_64(rax);
        return rax;
    }


    切换到Hopper的汇编代码界面,双击并进入子函数`sub_100068310`, 右键点击汇编代码第一行,选择`References to 0x100068310`查看交叉引用信息。



    细心的你是否发现除了之前打交道的3个`isLicensed`函数,还有3处也在调用注册验证函数`sub_10068310`。所以明白了单靠操作实战 1为什么不会有好下场了吧。

    好了,继续阅读`sub_10068310`的伪代码吧。*为了逻辑看上去更清晰,以下展示的代码修剪了一些干扰。*

    [C] 纯文本查看 复制代码
    int sub_100068310() {
        r13 = Swift.String() -> __C.NSString(0x8000000000000000 | "license2HoldersName", 0x13);
        rbx = Swift.String() -> __C.NSString(0xe800000000000000, 0x3265736e6563696c);
        r13 = sub_100068b50(var_30, r14, r15, r12, 0xea00000000003272);
        rbx = 0x1;
        if ((r13 & 0x1) == 0x0) {
                rbx = sub_100068f30(var_30, r15, r12, r14);
        }
        rax = rbx;
        return rax;
    }


    `0x3265736e6563696c`转为字符串是`2esnecil`,字节高低转位就是`license2`。

    代码在读取配置文件,使用**AppDelete**快速查看配置文件的路径。



    打开配置文件`com.surteesstudiOS.Bartender.plist`,这里记录了很多信息,但是对分析没有什么帮助,都不是关键点。



    关键的是代码中的2个函数:`sub_100068b50` 和 `sub_100068f30`,从逻辑上看任一函数返回true都能完成验证。

    简单看了下两个函数的伪代码,都很长,很长,很长,真是眼花缭乱,有没有什么分析技巧呢?额,反复调试、反复分析代码就是了。

    静态分析

    静态分析,是相对于动态调试而言。对于新手,可以尝试倒序阅读伪代码,更容易理清代码的逻辑关系。比如`sub_10068b50`,仅有1个`return`,其结尾的代码是:

    [C] 纯文本查看 复制代码
                }
        }
        else {
                rbx = 0x0;
        }
        rax = rbx;
        return rax;
    }


    这里的else提示false,继续向上看关注`rbx`寄存器值:

    [C] 纯文本查看 复制代码
                   if (r13 != 0x0) {
                         rbx = sub_1000627c0(r12, r14, r15, var_50, r13);
                   }
                    else {
                         rbx = 0x0;
                   }
             }
        }
        else {
                rbx = 0x0;
        }
        rax = rbx;
        return rax;
    }


    所以`sub_1000627c0`的返回值很重要,假想返回`0x1`就好了。继续向上,理清逻辑关系。*为了逻辑看上去更清晰,以下展示的代码修剪了一些干扰。*

    [C] 纯文本查看 复制代码
    int sub_100068b50(int arg0, int arg1, int arg2, int arg3, int arg4) {
        if ((r14 != 0x0) && (rdx != 0x0)) {
                rbx = sub_100062dc0(r13, rbx, rdx, rcx, r8);
                if (rbx != 0x0) {
                    r13 = sub_1000627c0(var_38, var_40, var_58, var_30, rbx);
                    if ((r13 & 0x1) != 0x0) {
                            rbx = 0x1;
                    }
                }
                else {
                    if (r13 != 0x0) {
                        rbx = sub_1000627c0(r12, r14, r15, var_50, r13);
                    }
                    else {
                        rbx = 0x0;
                    }
                }
        }
        else {
                rbx = 0x0;
        }
        rax = rbx;
        return rax;
    }


    纵览这段代码,只要`sub_1000627c0`返回`0x1`,一切就都好了。如果进一步细看`sub_1000627c0`,其实已经无限接近注册码算法了,算法后面我们再分析,气氛似乎看起来有些微妙了。

    deep layer  |      func call layer
    -------------------------
      4             |       注册码算法层
      3             |      sub_1000627c0
      2             |      sub_10068b50
      1             |        isLicensed

    经过操作实战 2的分析,已经从`isLicesed`的第一层,逐步分析到了第三层了。恭喜你,少年!练成九阴真经指日可待!

    问题来了,怎样让`sub_1000627c0`返回`true`呢?



    **操作实战 3**

    经过操作实战 2的分析,接下来要注入函数`sub_1000627c0`并返回`true`。继续采用敏捷调试方案 **Frida**。

    正常运行Bartender 3,打开iTerm(或系统的终端),输入注入代码:

    [Bash shell] 纯文本查看 复制代码
    ~ frida-trace -i "sub_1000627c0" "Bartender 3"


    提示注入失败,看来参数`-i`对于无符号名的函数不起作用。

    再仔细阅读一下注释内容,好像也没有其他的选项可供使用:

    [Bash shell] 纯文本查看 复制代码
    ~ frida-trace -h
    Usage: frida-trace [options] target
    
    Options:
      --version             show program's version number and exit
      -h, --help            show this help message and exit
      -I MODULE, --include-module=MODULE
                            include MODULE
      -X MODULE, --exclude-module=MODULE
                            exclude MODULE
      -i FUNCTION, --include=FUNCTION
                            include FUNCTION
      -x FUNCTION, --exclude=FUNCTION
                            exclude FUNCTION
      -a MODULE!OFFSET, --add=MODULE!OFFSET
                            add MODULE!OFFSET
      -T, --include-imports
                            include program's imports
      -t MODULE, --include-module-imports=MODULE
                            include MODULE imports
      -m OBJC_METHOD, --include-objc-method=OBJC_METHOD
                            include OBJC_METHOD
      -M OBJC_METHOD, --exclude-objc-method=OBJC_METHOD
                            exclude OBJC_METHOD
      -s DEBUG_SYMBOL, --include-debug-symbol=DEBUG_SYMBOL
                            include DEBUG_SYMBOL
      -q, --quiet           do not format output messages
      -o OUTPUT, --output=OUTPUT


    其实`sub_xxxxxxx`只是Hopper这样的逆向分析软件显示的函数名,内存中就不存在这样的函数符号名,仅提示函数的偏移地址,而且开启`ASLR`情况下,每次加载的地址都是随机地址。说这么多就是告诉你**这句代码不起作用**。

    对于无符号的函数,怎样用Frida来hook呢?

    应该要找到程序加载的基地址`module address`,根据地址偏移量`offset`,才能定位到内存中的真实地址`target address`。



    所以,需要一些编程,但不是那么复杂,一起来看一下。

    注入器的代码 inject.py

    [JavaScript] 纯文本查看 复制代码
    import frida
    import sys
    import codecs
    
    def on_message(message, data):
        print("[{}] => {}".format(message, data))
    
    def main(target_process):
        session = frida.attach(target_process)
    
        with codecs.open(sys.argv[2], 'r', 'utf-8') as f:
            source = f.read()
    
        script = session.create_script(source)
        script.on("message", on_message)
        script.load()
        print("[!] Ctrl+D or Ctrl+Z to detach from instrumented program.\n\n")
        sys.stdin.read()
        session.detach()
    
    
    if __name__ == "__main__":
        main(sys.argv[1])
    



    需要注入的代码 Bartender3.js

    [JavaScript] 纯文本查看 复制代码
    function get_rva(module, offset) {
        var base_addr = Module.findBaseAddress(module);
        if (base_addr == null)
            base_addr = enum_to_find_module(module);
        console.log(module + ' addr: ' + base_addr);
        var target_addr = base_addr.add(offset);
     
        return target_addr;
    }
     
    var target_addr = get_rva("Bartender 3", 0x627c0);
    console.log("sub_1000627c0 addr: " + target_addr);
    
    Interceptor.attach(target_addr, {
        onEnter: function(args) {
        },
        onLeave: function(retval) {
            console.log("sub_1000627c0 return:" + retval + " replaced: 0x1");
            retval.replace(0x1);
        },
    });


    接下来,验证奇迹的时刻到了。

    正常运行Bartender 3,打开iTerm(或系统的终端),输入以下命令:

    [Bash shell] 纯文本查看 复制代码
    ~ python3 inject.py "Bartender 3" Bartender3.js




    **Surprise!!!** 试用提示消失了,软件为已激活状态,再调整系统时间至4周后,软件依然提示激活状态,并且功能测试正常,这次不是伪破解,是完美爆破!

    所以经过以上Frida的调试,最佳的Patch的方案就是修改`sub_1000627c0`函数开头的汇编代码,多么熟悉的味道。

    [Asm] 纯文本查看 复制代码
    /* return true */
    mov rax, 0x1
    ret


    恭喜你,少年!你已完成操作实战 3的所有项目!并且学会了Frida的其中两种Hooking Functions方法。是时候进入下一层了。



    **操作实战 4**

    还有什么比攻破软件注册码验证机制并且写出注册机更兴奋的呢?来吧,少年,攻与防的比赛还没有结束,未来属于你的,继续读下去。

    是时候分析`sub_1000627c0`了。还是那么长、那么长、那么长的代码,硬着头皮上吧,负责任的告诉你很快一些特殊的字符串会出现在你的眼前。

    动态调试

    是时候好好展示你的动态调试技术了,提前关闭已经运行的Bartender 3,打开Hopper调试器,清除所有的断点,点击运行按钮(第一个小图标),稍等片刻。



    程序运行后,打开注册码输入框,输入用户名:tree_fly,以及任意密码:AAAABBBBCCCCDDDDEEEE。在点击注册按钮之前,在`sub_100627c0`函数头下个断点,点击注册按钮后程序断在`sub_100627c0`。

    继续按下F6,或者上图的第5个小图标,逐行Step Over运行汇编代码。

    很快第一个特殊字符串`SecDecodeTransformCreate`出现了:

    SecDecodeTransformCreate(**_kSecBase32Encoding, &var_60);

    关于Security Transforms API可以参考官方文档: [Security Transforms Programming Guide-Signing and Verifying](https://developer.apple.com/libr ... ansformsBasics.html)

    看上去是进行Base32解密,莫不会是解密注册码?快速向下定位到属性配置API看看:`SecTransformSetAttribute`。

    在`0000000100062919 call imp___stubs__SecTransformSetAttribute`这一行下断点,直接点击运行按钮,将RIP指向此行。
    [Asm] 纯文本查看 复制代码
    000000010006290b  lea  rcx, qword [rbp+var_60]     ; argument "error" for SecTransformSetAttribute
    000000010006290f  mov  rdi, qword [rbp+var_48]     ; argument "transformRef" for SecTransformSetAttribute
    0000000100062913  mov  rsi, r14                    ; argument "key" for SecTransformSetAttribute
    0000000100062916  mov  rdx, rbx                    ; argument "value" for SecTransformSetAttribute
    0000000100062919  call imp___stubs__SecTransformSetAttribute  ; SecTransformSetAttribute


    如果你不是很熟悉MacOS x64的寄存器传值规则,细心的Hopper已经帮你注释出来了。

    没错,\$rbx存储着即将Base32解密的value,看下就知道,切换到Debugger Console标签,输入以下调试命令:

    [Asm] 纯文本查看 复制代码
    po $rdx
    <41414141 42424242 43434343 44444444 45454545>
    
    x $rdx
    0x1019a2010: 81 49 28 8e ff ff 1d 02 14 00 00 00 00 00 00 00  .I(.............
    0x1019a2020: 41 41 41 41 42 42 42 42 43 43 43 43 44 44 44 44  AAAABBBBCCCCDDDD
    
    x -c0x40 $rdx
    0x1019a2010: 81 49 28 8e ff ff 1d 02 14 00 00 00 00 00 00 00  .I(.............
    0x1019a2020: 41 41 41 41 42 42 42 42 43 43 43 43 44 44 44 44  AAAABBBBCCCCDDDD
    0x1019a2030: 45 45 45 45 00 00 00 00 00 00 00 00 00 00 00 00  EEEE............
    0x1019a2040: 31 00 00 00 00 00 00 00 1d 00 00 00 00 00 00 00  1...............

    在lldb中,x是 memorry read的缩写,-c 是显示字节长度,默认是0x20。

    以上读取到的正是刚才输入的注册码,看来**是对注册码进行Base32解密**。

    为了向正确的道路越走越近,伪造一串Base32加密字符串作为注册码:

    Base32EncString("hello, tree_fly") = NBSWY3DPFQQHI4TFMVPWM3DZ

    重新来过,这次注册码的输入框内容输入新的加密字符串,激活断点,继续向下。

    很快一段奇怪的代码出现了,只见加解密执行API:`SecTransformExecute`,未见加解密属性配置API:`SecTransformSetAttribute`,以及具体的加解密方案。

    [C] 纯文本查看 复制代码
        rax = sub_100063140(var_70, r14, rdx, var_50, r13, r15, rbx);
        if (0x0 == 0x0) goto loc_100062b41;
    
    loc_100062cc8:
        rax = rbx;
        return rax;
    
    loc_100062b41:
        rax = SecTransformExecute(rdi, &var_60);
        r15 = rax;
        if (r15 == 0x0) goto loc_100062cd9;


    仔细查看代码,这个7个参数的子函数`sub_100063140`要进入看一看。

    进入`sub_100063140`后,很快看到了 `SecVerifyTransformCreate` 和 `_kSecDigestSHA1`,到这里一切都明朗了。

    *咳咳咳,敲黑板*,聪明的你,看到这些信息,脑海里是不是已经浮现了具体的加解密算法。

    没错,就是RSA的数字签名算法(Sign&Verify)。

    既然考虑RSA算法,两个问题思考下:

    待加密字符串是什么及采用哪种hash方式?

    公布的公钥是什么?(为什么不提私钥,想什么呢,少年。)

    **待加密的字符串及加密方式**

    快速定位到 `SecVerifyTransformCreate` 其后的第一个`SecTransformSetAttribute`,在`000000010006320c         call       imp___stubs__SecTransformSetAttribute`这一行下断点,继续运行程序,成功断在此行。

    [Asm] 纯文本查看 复制代码
    00000001000631fc mov rbx, rax
    00000001000631ff lea rcx, qword [rbp+var_28]  ; argument "error" 
    0000000100063203 mov rdi, r14                 ; argument "transformRef" 
    0000000100063206 mov rsi, r13                 ; argument "key"
    0000000100063209 mov rdx, rbx                 ; argument "value"
    000000010006320c call imp___stubs__SecTransformSetAttribute       ; SecTransformSetAttribute


    输入调试代码:

    [Asm] 纯文本查看 复制代码
    po $rdx
    <42617274 656e6465 72322c74 7265655f 666c79>
    
    x -c0x40 $rdx
    0x108fc27c0: 81 49 28 8e ff ff 1d 02 13 00 00 00 00 00 00 00  .I(.............
    0x108fc27d0: 42 61 72 74 65 6e 64 65 72 32 2c 74 72 65 65 5f  Bartender2,tree_
    0x108fc27e0: 66 6c 79 00 00 00 00 00 00 00 00 00 00 00 00 00  fly.............
    0x108fc27f0: ff ff ff ff 00 00 01 00 00 00 00 00 00 00 00 00  ................


    所以待加密的字符串是:“Bartender2,tree_fly”,正是字符串“Bartender2,”与用户名拼接。

    在反复的调试过程中,容易发现这个加密字符串还是有变化的。
    [Asm] 纯文本查看 复制代码
    x -c0x40 $rdx
    0x100ebef30: 81 49 28 8e ff ff 1d 02 1a 00 00 00 00 00 00 00  .I(.............
    0x100ebef40: 42 61 72 74 65 6e 64 65 72 32 55 70 67 72 61 64  Bartender2Upgrad
    0x100ebef50: 65 2c 74 72 65 65 5f 66 6c 79 00 00 00 00 00 00  e,tree_fly......
    0x100ebef60: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................


    一个新的值出现:“Bartender2Upgrade,tree_fly”。

    这也解释了操作实战 2中的那句话。

    关键的2个函数:`sub_100068b50` 和 `sub_100068f30`,从逻辑上看任一函数返回true都能完成验证。

    所以,从软件作者设计注册码的角度看,其实分两类,但是,这不重要了,任选其一即可。

    关于文本的hash方式,已经很明显了,就是SHA1。

    **公布的公钥**

    公钥的藏身之处要多得去了,有的存储为文件,有的以原文PEM字符串形式存储在软件中,有的还要进行解密才能获取到真正的公钥。

    搜关键词**SecItemImport**,在下面这行下断点:

    [Asm] 纯文本查看 复制代码
    ; ================ B E G I N N I N G   O F   P R O C E D U R E ================
    ; Variables:
    ;    outItems: void *, 16
    ;    importKeychain: int, 8
    
     imp___stubs__SecItemImport:        // SecItemImport
    00000001000a62e8  jmp  qword [_SecItemImport_ptr]


    断下后,按照调用栈逆向寻找,定位到以下位置:

    [Asm] 纯文本查看 复制代码
    0000000100062f29  lea  rdx, qword [rbp+var_50]     ; argument "inputFormat" for SecItemImport
    0000000100062f2d  lea  rcx, qword [rbp+var_48]     ; argument "itemType" for SecItemImport
    0000000100062f31  lea  r9, qword [rbp+var_B8]      ; argument "keyParams" for SecItemImport
    0000000100062f38  mov  esi, 0x0                    ; argument "fileNameOrExtension" for SecItemImport
    0000000100062f3d  xor  r8d, r8d                    ; argument "flags" for SecItemImport
    0000000100062f40  mov  rdi, r13                    ; argument "importedData" for SecItemImport
    0000000100062f43  push rax                         ; argument "outItems" for SecItemImport
    0000000100062f44  push 0x0                         ; argument "importKeychain" for SecItemImport
    0000000100062f46  call imp___stubs__SecItemImport  ; SecItemImport


    参考Hopper的函数参数注释,importedData对应的\$rdi存储的是公钥数据,打印一下:

    [Asm] 纯文本查看 复制代码
    po $r13
    <2d2d2d2d 2d424547 494e2050 55424c49 43204b45 592d2d2d 2d2d0a4d 4948774d 49476f42 67637168 6b6a4f4f 4151424d 49476341 6b45416b 61346f73 3865494d 59375469 6d58696e 51673136 5453754e 456c794d 70744c4e 6b6a680a 70474363 51763331 56446d73 36637630 52397248 6d2f4c69 4c624741 6c495146 36624c55 4f48706f 556a5333 56743947 6b514956 414d394a 7353556c 455a4461 0a415562 43467649 50706c52 3266314e 74416b41 5249652f 35415242 41583252 6676715a 52753465 37737556 65714f64 58614534 4e787064 512b2b48 57473246 790a3035 614d4e56 48367351 55755962 6133725a 78695472 3079716d 31565946 45354450 5a5a3849 36444130 4d41416b 42304b67 7245394a 6a47656c 34653566 58630a7a 6e6c7269 77314f38 48422b2b 316b476a 32793632 635a336b 76663669 76654132 6e5a786b 4e533956 4c443775 30393154 5a737464 4854456f 6b393577 4177430a 64674e7a 0a2d2d2d 2d2d454e 44205055 424c4943 204b4559 2d2d2d2d 2d0a>
    
    x -c0x200 $r13
    0x101998270: 31a3df8dffff1d008414000001000000  1...............
    0x101998280: 7e010000000000007e01000000000000  ~.......~.......
    0x101998290: 00000000000000000000000000000000  ................
    0x1019982a0: 2d2d2d2d2d424547494e205055424c49  -----BEGIN PUBLI
    0x1019982b0: 43204b45592d2d2d2d2d0a4d4948774d  C KEY-----.MIHwM
    0x1019982c0: 49476f42676371686b6a4f4f4151424d  IGoBgcqhkjOOAQBM
    0x1019982d0: 494763416b45416b61346f733865494d  IGcAkEAka4os8eIM
    0x1019982e0: 593754696d58696e516731365453754e  Y7TimXinQg16TSuN
    0x1019982f0: 456c794d70744c4e6b6a680a70474363  ElyMptLNkjh.pGCc
    0x101998300: 5176333156446d733663763052397248  Qv31VDms6cv0R9rH
    0x101998310: 6d2f4c694c6247416c49514636624c55  m/LiLbGAlIQF6bLU
    0x101998320: 4f48706f556a5333567439476b514956  OHpoUjS3Vt9GkQIV
    0x101998330: 414d394a7353556c455a44610a415562  AM9JsSUlEZDa.AUb
    0x101998340: 4346764950706c523266314e74416b41  CFvIPplR2f1NtAkA
    0x101998350: 5249652f35415242415832526676715a  RIe/5ARBAX2RfvqZ
    0x101998360: 527534653773755665714f6458614534  Ru4e7suVeqOdXaE4
    0x101998370: 4e787064512b2b4857473246790a3035  NxpdQ++HWG2Fy.05
    0x101998380: 614d4e5648367351557559626133725a  aMNVH6sQUuYba3rZ
    0x101998390: 786954723079716d3156594645354450  xiTr0yqm1VYFE5DP
    0x1019983a0: 5a5a3849364441304d41416b42304b67  ZZ8I6DA0MAAkB0Kg
    0x1019983b0: 7245394a6a47656c3465356658630a7a  rE9JjGel4e5fXc.z
    0x1019983c0: 6e6c726977314f3848422b2b316b476a  nlriw1O8HB++1kGj
    0x1019983d0: 32793632635a336b7666366976654132  2y62cZ3kvf6iveA2
    0x1019983e0: 6e5a786b4e5339564c44377530393154  nZxkNS9VLD7u091T
    0x1019983f0: 5a7374644854456f6b3935774177430a  ZstdHTEok95wAwC.
    0x101998400: 64674e7a0a2d2d2d2d2d454e44205055  dgNz.-----END PU
    0x101998410: 424c4943204b45592d2d2d2d2d0a0000  BLIC KEY-----...
    


    调试到这里,已经从内存中找到了软件的公钥,如何存储的呢?使用字符串“BEGIN PUBLIC”搜索看看,是不是以原文字符串存储在软件中。



    果真如此,那么Patch公钥不在话下了。

    通过操作实战 4,我们分析出了RSA数字签名算法,被加密的文本,加密文本的hash方式SHA1,及公钥内容。有了这些信息,离注册机还远吗?



    **实战操作 5**

    来到这里的都是好汉了,少年,你很棒!

    分析公钥长度

    从公布的PEM格式的公钥,谁能看出来对应私钥长度是多少位的请举手。

    [Plain Text] 纯文本查看 复制代码
    -----BEGIN PUBLIC KEY-----
    MIHwMIGoBgcqhkjOOAQBMIGcAkEAka4os8eIMY7TimXinQg16TSuNElyMptLNkjh
    pGCcQv31VDms6cv0R9rHm/LiLbGAlIQF6bLUOHpoUjS3Vt9GkQIVAM9JsSUlEZDa
    AUbCFvIPplR2f1NtAkARIe/5ARBAX2RfvqZRu4e7suVeqOdXaE4NxpdQ++HWG2Fy
    05aMNVH6sQUuYba3rZxiTr0yqm1VYFE5DPZZ8I6DA0MAAkB0KgrE9JjGel4e5fXc
    znlriw1O8HB++1kGj2y62cZ3kvf6iveA2nZxkNS9VLD7u091TZstdHTEok95wAwC
    dgNz
    -----END PUBLIC KEY-----


    从经验来看,推测其长度介于1024-2048之间。起初我测试了一下用1024位私钥对应的公钥Patch掉作者的公钥,发现有长度检测,注册失败。所以制造一个长度相同的公钥迫在眉睫。

    等等,为什么要Patch公钥呢?看张图吧,看图说话。



    继续回到密钥长度问题,首先我尝试用openssl来分析,但是提示错误信息:
    [Bash shell] 纯文本查看 复制代码
    ~ openssl rsa -pubin -text -in public.pem
    4415460972:error:06FFF07F:digital envelope routines:CRYPTO_internal:expecting an rsa key:/BuildRoot/Library/Caches/com.apple.xbs/Sources/libressl/libressl-22.250.1/libressl-2.6/crypto/evp/p_lib.c:295:



    然后使用以下2个命令,指定密钥长度位于 1024-2048之间,采用二分法反复验证,以期公钥的长度相同。

    [Bash shell] 纯文本查看 复制代码
    openssl genrsa -out pri.pem 1024
    openssl rsa -in pri.pem -pubout > pub1024.pem



    最终发现长度在1678时恰如其分(不是密钥长度的唯一值)。少年,长度都帮你算好了,创建自己的密钥吧。

    [Bash shell] 纯文本查看 复制代码
    openssl genrsa -out pri.1678.pem 1678
    openssl rsa -in pri.1678.pem -pubout > pub.1678.pem





    如何手动Patch公钥

    有很多的字节编辑软件,推荐010 Editor,选中字符串后原位贴入即可。16进制字节粘贴的方法选择Edit->Paste From->Paste from Hex Text。注意仔细检查替换的正确性。





    用Swift 写注册机

    论坛有Go写的RSA算法注册机,这里换Swift 5。
    [Bash shell] 纯文本查看 复制代码
    mkdir Bartender3KeyMaker
    cd Bartender3KeyMaker
    swift package init --type executable



    以上创建了Bartender3KeyMaker文件夹以及同名的Swift项目。

    打开Package.swift,添加2个dependencies:

    [Swift] 纯文本查看 复制代码
    // swift-tools-version:5.1
    // The swift-tools-version declares the minimum version of Swift required to build this package.
    
    import PackageDescription
    
    let package = Package(
        name: "Bartender3KeyMaker",
        dependencies: [
            .package(url: "https://github.com/IBM-Swift/BlueRSA", from: "1.0.0"),
            .package(url: "https://github.com/norio-nomura/Base32", from: "0.5.4"),
        ],
        targets: [
    
            .target(
                name: "Bartender3KeyMaker",
                dependencies: ["CryptorRSA", "Base32"]),
            .testTarget(
                name: "Bartender3KeyMakerTests",
                dependencies: ["Bartender3KeyMaker"]),
        ]
    )




    修改main.swift:

    [Swift] 纯文本查看 复制代码
    import CryptorRSA
    import Base32
    
    if #available(OSX 10.12, *) {
        
        print("*** Bartender 3 KeyMaker ***")
        
        let publicKeyPEM = """
    -----BEGIN PUBLIC KEY-----
    MIHwMA0GCSqGSIb3DQEBAQUAA4HeADCB2gKB0i4NHp5du5mNFuKif70Ra4Au7d3s
    5id3pgD5X7IO6oRtDvIqWYVFON2iY01T48hUxN8BCHpbt575PAhT0cV2mUeeElNW
    QxhhXo2VcP98wlbzvTM4+jnwytK7kqNQINjyuxJucm9/Ak7VuIrZpvAR72UHN2dz
    FGKEie4liTy4u7/rYAqlWTjp5GvPgkk9Fspdisjm8MSxpv8q+bO6cY3sUXfN8lHI
    t3HLOOuyEYnhBJ2429xrtveKEAogxagLexmucAyo3J7CYR/D6sPwjJF/SoHykwID
    AQAB
    -----END PUBLIC KEY-----
    """
        
        let privateKeyPEM = """
    -----BEGIN RSA PRIVATE KEY-----
    MIIDyQIBAAKB0i4NHp5du5mNFuKif70Ra4Au7d3s5id3pgD5X7IO6oRtDvIqWYVF
    ON2iY01T48hUxN8BCHpbt575PAhT0cV2mUeeElNWQxhhXo2VcP98wlbzvTM4+jnw
    ytK7kqNQINjyuxJucm9/Ak7VuIrZpvAR72UHN2dzFGKEie4liTy4u7/rYAqlWTjp
    5GvPgkk9Fspdisjm8MSxpv8q+bO6cY3sUXfN8lHIt3HLOOuyEYnhBJ2429xrtveK
    EAogxagLexmucAyo3J7CYR/D6sPwjJF/SoHykwIDAQABAoHSEeoqqiL+swpvB7V9
    ifi34ELhaD8bfekO3Dwm3SbuVpvyf4S4FJ9MMvRUOyXSbAGGINbPDIKXmTGOCBNL
    fMzZbkHxERhyu45NcTjcn5dSJu9lAAM/XMDutjIgJoYqcRtkaRQsUnGPXUnIztPZ
    S8mTiyUkOUwAyjHn5XwEam5mDj42gkrbIZk/S0THnQOdrbSZnnwcq8jmvS3g0xup
    ryjOhKwe2174khAP2bD1emEM5UyygJEcsJV91NTSlqKrPbVPre5DdibGX73aWOCw
    6GMv2l9RAml4eKeKZ9DIr98IJbXBIDjQaTM1HxT3ekAyWm+eiBo0qXsfvZCEOJkS
    KgTzkiQ1GSh9y/N9qmixgCLKiqqsli4vHnMgwsOMnO7Lf913ELFvA7/DgOpLTOTB
    lu3KLaXH+lnx6Obvsdk+6ysCaWHbutRcHcezwrQ6cO77r9IYp9xf8J6RRMqXwOGp
    Gn1Qy2i5pV5KYm9wL1Mmkxr+zymTeJ964EuItPl2a/NMx7ln0UQNBU7oGG5rK1Sc
    xUYWjWJGPyXA/eF80UFDryM/5nG+nA1Nb1vCOQJpFbYQ47Wv/+sKM+qv5d1Lv+ul
    qeYvHiavGSQJR7XZmzIMGX1NZTbaB1cBS3BEDDm7fWhbOoOSmKKyInR5K99o9V70
    eqv/GAFUW+JwZDvi7lHrpm0+TFHQTD9KHYy6et7YhOtnaz1PHLK/AmlDf1/6oh8Y
    Y/FkhvrmnEvFyqPd6X76oJCmfM3Z2N4gmd3zujlKNFx5KRQ7clv9Psx9jO6icgrL
    jtvlRb1n8AnC5Mz+90w2BPj1EI6uqgOYOG4E3xcnX1q+cW2Uaq8ezTCSPDs/Ia4x
    yGECaTg3UbgNnQGhmrwbrtqIt+/aVjiE7UMDCl+svCvaaHHZCY1J/aRqu6JAnURU
    I5qqQ46zTzJR+i9A9jryT1S11sIxuVr5hDmKC+2JA/9ogrwbYuHnCZrCx2P53Bhm
    kDd2Ypk3F7cQTVgQTA==
    -----END RSA PRIVATE KEY-----
    """
    
        let publicKey = try! CryptorRSA.createPublicKey(withPEM: publicKeyPEM)
        let privateKey = try! CryptorRSA.createPrivateKey(withPEM: privateKeyPEM)
    
        print("Please input your name: ", separator: "", terminator: "")
        let userName = readLine() ?? "tree_fly"
        
        let data = "Bartender2,\(userName)".data(using: String.Encoding.utf8)!
        let plainText = CryptorRSA.createPlaintext(with: data)
    
        let signature = try plainText.signed(with: privateKey, algorithm: .sha1)!
        let verification = try plainText.verify(with: publicKey, signature: signature, algorithm: .sha1)
        print("Key Verification: ", verification)
    
        let signBase32String = signature.data.base32EncodedString
        
        print("\nName:", userName)
        print("SN:", signBase32String)
        
    } else {
        print("ERROR(OSX NEED 10.12+)")
    }



    然后运行程序

    [Bash shell] 纯文本查看 复制代码
    swift run



    一切顺利情况下,即可正确计算注册码:




    或者:

    [Plain Text] 纯文本查看 复制代码
    Name: [url=http://www.chinapyg.com]www.chinapyg.com[/url]
    SN: BR7K5KAGMS4PIM2KNJOVCLP4XYHWNL6VM7C6XF2DJYZFJPR7CBJ5YUCOQBMQHDFXN6EMI6YT4P4VZ4XL543G733FXYGNRDGKKHPPXKZG2LCZX433BY7BAAQNVJZ54GBNJS6DG5ASX5D3A6LQ24FBKX5NX2A4XOR2NLMY32FOL7PVDN6GZKAAUPBTX3Z65TYBEPMYW76T4XDTPUONUNLXAUNMTPVT37G73C5HNX2QUVC5XOQCUCTTJVXDINP4NYP73FAQTBGWDNZY6LREV6QUKIA3ZYLI26NXR5XLPA4VGBNYK7Z36SXPTUHGUHSRQTMAGGAKJRFX4UNKEMPV





    四、结束语

    本文详细介绍了操作实战5步走,少年,出来走一走

    时间仓促,纰漏难免,旨在拓宽思路,抛砖引玉,希望能给读者带来一些启发和实战操作的引导。


    感谢您的阅读,欢迎留言。



    格式整齐、排版清秀的PDF在文末,献上你的PYB吧 ^_^


    tree_fly/P.Y.G


    2019-09-18



    参考资料








    PDF文档下载:


    本帖子中包含更多资源

    您需要 登录 才可以下载或查看,没有帐号?加入论坛

    x

    评分

    参与人数 7威望 +18 飘云币 +18 收起 理由
    0xcb + 1 + 1 赞一个,这个帖子很给力!
    Dweling + 1 + 1 吃水不忘打井人,给个评分懂感恩!
    dryzh + 5 + 5 哇塞Frida高阶
    Master.lu + 1 + 1 PYG有你更精彩!
    small-q + 8 + 8 赞一个,这个帖子很给力!
    分享之源 + 1 + 1 感谢分享!
    不破不立 + 1 + 1 PYG有你更精彩!

    查看全部评分

  • TA的每日心情
    开心
    昨天 07:45
  • 签到天数: 702 天

    [LV.9]以坛为家II

    发表于 2019-9-19 06:53:55 | 显示全部楼层
    坐沙发前排学习
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    开心
    昨天 07:37
  • 签到天数: 960 天

    [LV.10]以坛为家III

    发表于 2019-9-19 07:23:28 | 显示全部楼层
    飞树表哥666,回家跟着学习,mac环境还用不习惯.
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    开心
    昨天 08:48
  • 签到天数: 46 天

    [LV.5]常住居民I

    发表于 2019-9-19 08:32:37 | 显示全部楼层
    搬个小板凳坐一会
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    郁闷
    3 天前
  • 签到天数: 1106 天

    [LV.10]以坛为家III

    发表于 2019-9-19 09:18:05 | 显示全部楼层
    得先安装一个黑苹果才能跟着操作
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    奋斗
    2016-1-13 12:25
  • 签到天数: 3 天

    [LV.2]偶尔看看I

    发表于 2019-9-19 09:42:29 | 显示全部楼层
    感谢吊大的飞树,火速前来学习
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    奋斗
    昨天 10:07
  • 签到天数: 291 天

    [LV.8]以坛为家I

    发表于 2019-9-19 10:08:48 | 显示全部楼层
    厉害厉害,值得学习。
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    开心
    2019-9-30 09:30
  • 签到天数: 15 天

    [LV.4]偶尔看看III

    发表于 2019-9-19 12:59:09 | 显示全部楼层
    精品文章,由浅入深,赞。
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    擦汗
    昨天 09:46
  • 签到天数: 83 天

    [LV.6]常住居民II

    发表于 2019-9-19 13:31:44 | 显示全部楼层
    这个必须要收藏
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    奋斗
    2015-10-29 08:08
  • 签到天数: 3 天

    [LV.2]偶尔看看I

    发表于 2019-9-19 14:40:35 | 显示全部楼层
    6到∞大,感谢飞树博士的图文教程
    回复 支持 反对

    使用道具 举报

    您需要登录后才可以回帖 登录 | 加入论坛

    本版积分规则

    关闭

    站长推荐上一条 /1 下一条

    快速回复 返回顶部 返回列表