飘云阁

 找回密码
 加入我们

QQ登录

只需一步,快速开始

查看: 17128|回复: 20

[macOS] [实践][Level 2]如何优雅破解Mac软件-hook入门教程

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

    [LV.7]常住居民III

    发表于 2016-2-17 17:51:46 | 显示全部楼层 |阅读模式
    本帖最后由 tree_fly 于 2016-2-17 17:51 编辑

    2016年让Mac OS/iOS板块活跃起来吧~
    今天一起完成一次实践,简单阐述一下如何优雅劫持Mac软件,达到解除软件限制目的。 十一期间论坛发布了不少劫持补丁作品,因为发布时统统延续了为程序名增加“下划线”作为新的文件名风格,故若破文中提到采用下划线优雅破解,希望可以理解采用了劫持思想,而没有去修改原本二进制文件。
    授人以鱼不如授人以渔   《老子》

    实践目标:AppDelete v4.2.4 QQ20160217-9@2x.png
    AppDelete is an uninstaller for Macs that will remove not only Applications but also Widgets, Preference Panes, Plugins, and Screensavers along with their associated files. Without AppDelete these associated items will be left behind to take up space and potentially cause issues. For a proper uninstall don’t just delete but be sure to AppDelete!

    分析工具:Hopper 3.11.5, HDS
    编码工具:XCode


    0x1 分析软件
    软件支持中文,打开软件本地中文资源文件 zh_CN.lproj/Localizable.strings,注意到如下信息:
    "AppDelete Registration" = "AppDelete 注册";
    "Registration Accepted" = "接受注册";
    "Registration Rejected" = "拒绝注册";
    "Register" = "注册";


    Hopper加载软件后,查找如上字符串,来到注册验证核心:
    [Objective-C] 纯文本查看 复制代码
    void -[ADController deletePaths:](void * self, void * _cmd, void * arg2) {
        var_30 = self;
        r14 = [[self->plistOne stringValue] retain];
        r15 = [[r14 stringByReplacingOccurrencesOfString:@" " withString:@""] retain];
        var_38 = [r15 isEqualTo:@""];
        [r15 release];
        [r14 release];
        r15 = [[var_30->extensionMaster stringValue] retain];
        r14 = [[r15 stringByReplacingOccurrencesOfString:@" " withString:@""] retain];
        rdx = @"";
        r13 = [r14 isEqualTo:rdx];
        [r14 release];
        [r15 release];
        if (((var_38 & 0xff) != 0x1) && ((r13 & 0xff) != 0x1)) {
                r14 = [[var_30->extensionMaster stringValue] retain];
                rcx = @"";
                rbx = [[r14 stringByReplacingOccurrencesOfString:@" " withString:rcx] retain];
                rdx = rbx;
                r15 = [var_30 orphansArray:rdx];  //注意这个函数
                rdi = rbx;
                [rdi release];
                [r14 release];
                if (r15 != 0x0) {
                        r14 = [[NSBundle release] retain]; //注册成功
                        r8 = 0x0;
                        r13 = [[r14 release] retain];
                        var_38 = r13;
                        [r14 release];
                        [var_30->zipFiles release];
                        [var_30->zipFiles release];
                        [var_30->zButton release];
                        [var_30->qButton release];
                        [var_30->plistOne release];
                        [var_30->extensionMaster release];
                        [var_30->helpP release];
                        r13 = _objc_release;
                        r14 = [[var_30->extensionMaster release] retain];
                        rbx = [[r14 release] retain];
                        var_40 = [[rbx release] retain];
                        [rbx release];
                        [r14 release];
                        r12 = _objc_release;
                        r14 = [[NSUserDefaults release] retain];
                        rbx = [[var_30->plistOne release] retain];
                        [r14 release];
                        [rbx release];
                        [r14 release];
                        rbx = [[NSUserDefaults release] retain];
                        rdx = var_40;
                        [rbx release];
                        [rbx release];
                        var_30->archiveRun = 0x1;
                        var_30->undoList = 0x0;
                        rbx = var_30;
                        [var_40 release];
                        [var_38 release];
                }
                else {
                        r13 = [[var_30->plistOne release] retain]; //注册失败
                        rbx = [[r13 release] retain];
                        NSLog(@"AD Rejected Name ~ %@", rbx);
                        [rbx release];
                        [r13 release];
                        r13 = [[var_30->extensionMaster release] retain];
                        rbx = [[r13 release] retain];
                        NSLog(@"AD Rejected Serial Number ~ %@", rbx);
                        [rbx release];
                        rdi = r13;
                        r13 = _objc_release;
                        [rdi release];
                        rbx = [[NSBundle release] retain];
                        r8 = 0x0;
                        r12 = [[rbx release] retain];
                        [rbx release];
                        [var_30->zipFiles release];
                        [var_30->zipFiles release];
                        [var_30->plistOne release];
                        rdx = @"";
                        [var_30->extensionMaster release];
                        var_30->archiveRun = 0x0;
                        rbx = var_30;
                        [r12 release];
                        r12 = _objc_release;


    函数 char -[ADController orphansArray:](void * self, void * _cmd, void * arg2)  返回非0即可过验证。
    QQ20160217-1@2x.png

    那么通过简单地修改代码,就可以了:
    [Asm] 纯文本查看 复制代码
    mov eax, 1
    ret


    测试后发现软件重启验证也通过了,注册菜单变灰。
    注意,有些很复杂的软件,逆向时候会发现很多暗桩,只有解除了所有的暗桩才能算是完美的破解。
    如果你乐意,可以去分析算法,甚至写KG,不在这里细说。



    0x2 代码劫持
    基础点说,在Windows下,很多时候我们在软件exe同一目录下放置version.dll、lpk.dll等劫持文件,依照规则exe优先加载了当前目录下dll,做很多XX事情。
    在Mac OS下,思路是相同的,你可以想尽一切办法让app加载我们的dylib,这里分享一种从飘云、creantan、sagexy诸位大神那里学来的心得:
    QQ20160217-2@2x.png

    大家看图和代码,这里以一款截图软件Snagit为例,
    首先进入 Snagit.app/Contents/MacOS/目录,
    将原本的Snagit文件更名为Snagit_(即添加了下划线),再放入dylib文件,然后创建一个名为Snagit的脚本,贴入如下代码:
    [Bash shell] 纯文本查看 复制代码
    #!/bin/bash
    Snagit_PATH="`dirname "${0}"`"
    Snagit_BIN="`dirname "${0}"`"/Snagit_
    
    export DYLD_FORCE_FLAT_NAMESPACE=1
    export DYLD_INSERT_LIBRARIES="${Snagit_PATH}/libSnagitPatch.dylib"
    "$Snagit_BIN"


    偷梁换柱!这样就可以加载补丁,在运行时态去修改内存数据。问题是如何编写补丁?

    这里提供一套模版,大家可以去尝试:
    1.创建 Library 工程,贴入如下代码:
    QQ20160217-7@2x.png
    [Objective-C] 纯文本查看 复制代码
    #import <objc/runtime.h>
    
    IMP PYSwizzleSelector(Class aClass, SEL selector, IMP newImplementation) {
        // Get the original implementation we are replacing
        Method method = class_getInstanceMethod(aClass, selector);
        IMP origImp = method_getImplementation(method);
        if (! origImp) {
            return NULL;
        }
        
        class_replaceMethod(aClass, selector, newImplementation, method_getTypeEncoding(method));
        return origImp;
    }


    2.根据劫持函数,设计c劫持代码:
    参考实例:
    [Objective-C] 纯文本查看 复制代码
    //-[ActivationManagerDMG cachedKeyFromLicenseChecker]:
    static NSString* (*pfn_s_ActivationManagerDMG_getInfo)() = NULL;
    static NSString* s_ActivationManagerDMG_getInfo(){
        NSLog(@"***** PYG: code by tree_fly/P.Y.G *****");
        NSString* url = @"WwW.ChinaPYG.CoM";
        return url;
    }
    
    //-[ActivationManagerDMG isSnagitRegistered]:
    static int (*pfn_s_isRegistered)() = NULL;
    static int s_isRegistered(){
        return 2015 - 2014;
    }
    


    对应的交换代码:
    [Objective-C] 纯文本查看 复制代码
    pfn_s_ActivationManagerDMG_getInfo = (NSString* (*)(void))PYSwizzleSelector(objc_getClass("ActivationManagerDMG"), @selector(cachedKeyFromLicenseChecker), (IMP)s_ActivationManagerDMG_getInfo);
       
    pfn_s_isRegistered = (int (*)(void))PYSwizzleSelector(objc_getClass("ActivationManagerDMG"), @selector(isSnagitRegistered), (IMP)s_isRegistered);


    本文中要劫持的函数是:
    char -[ADController orphansArray:](void * self, void * _cmd, void * arg2)
    尝试如下c代码:
    [Objective-C] 纯文本查看 复制代码
    //char -[ADController orphansArray:]
    static char (*pfn_s_orphansArray)(void* _) = NULL;
    static char s_orphansArray(void* _){
        NSLog(@"***** PYG: Patch !!! *****");
        return 0x1;
    }
    


    3.补丁加载入口:
    [Objective-C] 纯文本查看 复制代码
    @implementation AppDeletePatch
    +(void)load{
        NSLog(@"***** PYG: WWW. CHINAPYG. COM *****");
    
        pfn_s_orphansArray = (char (*)(void *))PYSwizzleSelector(objc_getClass("ADController"), @selector(orphansArray:), (IMP)s_orphansArray);
    }
    @end
    


    记得修改头文件
    [Objective-C] 纯文本查看 复制代码
    @interface AppDeletePatch : NSObject
    +(void)load;
    @end

    最终代码如下:
    QQ20160217-5@2x.png



    有没有觉得这样写起来真的好复杂,有没有其它方案呢,那就换一种写法吧:
    QQ20160217-8@2x.png
    代码:
    [Objective-C] 纯文本查看 复制代码
    //
    //  AppDeletePatch.m
    //  AppDeletePatch
    //
    //  Created by ftitree_fly on 16/2/17.
    //  Copyright &#169; 2016年 tree_fly. All rights reserved.
    //
    
    #import "AppDeletePatch.h"
    #import <objc/runtime.h>
    
    @implementation AppDeletePatch
    -(char)orphansArray:(NSString*)data{
        NSLog(@"***** PYG: Patch !!! *****");
        return 0x1;
    }
    
    
    +(void)load{
        NSLog(@"***** PYG: WWW. CHINAPYG .COM *****");
        
        Method origMethod =  class_getInstanceMethod
        (NSClassFromString(@"ADController"), NSSelectorFromString(@"orphansArray:"));
        
        Method newMethod = class_getInstanceMethod
        ([AppDeletePatch class], @selector(orphansArray:));
        
        method_exchangeImplementations(origMethod, newMethod);
    }
    @end


    有没有看上去更加简洁、优雅,大家可以根据自己的喜好,自由编码~

    编译生成最终文件 libAppDeletePatch.dylib。

    4.创建bash脚本,如下:
    [Bash shell] 纯文本查看 复制代码
    #!/bin/bash
    AppDelete_PATH="`dirname "${0}"`"
    AppDelete_BIN="`dirname "${0}"`"/AppDelete_
    
    export DYLD_FORCE_FLAT_NAMESPACE=1
    export DYLD_INSERT_LIBRARIES="${AppDelete_PATH}/libAppDeletePatch.dylib"
    "$AppDelete_BIN"


    5.测试

    补丁使用说明:
    1.安装原版并成功运行一次, 假设安装到 /Applications/AppDelete.app/
    2.打开/Applications/AppDelete.app/Contents/MacOS/目录,将 AppDelete 改名为 AppDelete_
    3.将补丁libAppDeletePatch.dylib及脚本AppDelete复制到上述目录
    4.启动主程序即可!

    测试截图:补丁运行Log成功打印,注册菜单栏变灰,功能限制解除。
    QQ20160217-6@2x.png 2016-02-17_17-29-56.png



    教程结束,如有问题请跟帖留言,祝大家新年快乐,祝论坛越来越好~


    补丁下载:
    AppDeletePathch.zip (3.86 KB, 下载次数: 29, 售价: 2 枚飘云币)


    评分

    参与人数 7威望 +204 飘云币 +204 收起 理由
    wgz001 + 8 + 8 很给力!
    GGLHY + 80 + 80 赞啊~~~~
    zaas + 20 + 20 很给力!
    NoNameX2016 + 8 + 8 很给力!
    Dxer + 40 + 40 抱大腿中。
    0xcb + 8 + 8 流程很清晰
    creantan + 40 + 40 PYG有你更精彩!

    查看全部评分

    PYG19周年生日快乐!
  • TA的每日心情
    难过
    3 天前
  • 签到天数: 11 天

    [LV.3]偶尔看看II

    发表于 2016-2-17 18:26:11 | 显示全部楼层
    赞!

    …… PYSw*函数还有一半你没看完哟……
    http://www.dllhook.com/post/25.html

    点评

    转载,受教。dllhook.com 果然特多干货 [mw_shl_code=objc,true]#import @interface NSArray(hook) -(id)myLastObject; @end @implementation NSArray(hook) -(id)myLastObject { NSLog(  详情 回复 发表于 2016-2-17 18:37
    PYG19周年生日快乐!
  • TA的每日心情
    奋斗
    2015-10-29 08:08
  • 签到天数: 3 天

    [LV.2]偶尔看看I

    发表于 2016-2-17 18:30:29 | 显示全部楼层
    干货。。。向版主学习
    PYG19周年生日快乐!
  • TA的每日心情
    开心
    2019-3-17 22:44
  • 签到天数: 132 天

    [LV.7]常住居民III

     楼主| 发表于 2016-2-17 18:37:59 | 显示全部楼层
    飘云 发表于 2016-2-17 18:26
    赞!

    …… PYSw*函数还有一半你没看完哟……

    转载,受教。dllhook.com 果然特多干货

    [Objective-C] 纯文本查看 复制代码
    #import <objc/runtime.h>
    
    
    @interface NSArray(hook)
        -(id)myLastObject;
    @end
    
    @implementation NSArray(hook)
    
    -(id)myLastObject
    {
        NSLog(@"inject success!!!");
        return [self myLastObject];
    }
    @end
    
    void HookMethod(Class aClass, SEL oldSEL, SEL newSel)
    {
        Method oldMethod = class_getInstanceMethod(aClass, oldSEL);
        assert(oldMethod);
        Method newMethod = class_getInstanceMethod(aClass, newSel);
        assert(newMethod);
        method_exchangeImplementations(oldMethod, newMethod);
    }
    
    int main(int argc, const char * argv[])
    {
        @autoreleasepool {
            NSLog(@"Hello, P.Y.G!");
            HookMethod([NSArray class], @selector(lastObject), @selector(myLastObject));
            NSArray *array = @[@"piaoyun", @"nisy", @"Q", @"GG"];
            NSString *string = [array lastObject];
            NSLog(@"RET : %@", string);
        }
        return 0;
    }
    PYG19周年生日快乐!
  • TA的每日心情
    开心
    2019-2-26 11:14
  • 签到天数: 459 天

    [LV.9]以坛为家II

    发表于 2016-2-17 19:43:10 | 显示全部楼层
    终于打开了,感谢
    PYG19周年生日快乐!
  • TA的每日心情
    无聊
    6 小时前
  • 签到天数: 2490 天

    [LV.Master]伴坛终老

    发表于 2016-2-17 21:08:12 | 显示全部楼层
    我是菜鸟,看得头都晕了
    PYG19周年生日快乐!
  • TA的每日心情

    2022-9-4 21:29
  • 签到天数: 1756 天

    [LV.Master]伴坛终老

    发表于 2016-2-17 22:08:44 | 显示全部楼层
    不懂,学下中,膜拜各位大神
    PYG19周年生日快乐!
  • TA的每日心情
    开心
    2020-10-6 19:00
  • 签到天数: 107 天

    [LV.6]常住居民II

    发表于 2016-2-18 11:14:59 | 显示全部楼层
    感谢版主这么详细的讲解,可我什么都没学到
    PYG19周年生日快乐!
  • TA的每日心情
    无聊
    2021-1-3 16:09
  • 签到天数: 16 天

    [LV.4]偶尔看看III

    发表于 2017-1-8 23:57:54 | 显示全部楼层
    感谢版主这么详细的讲解,可我什么都没学到
    PYG19周年生日快乐!
    回复 支持 反对

    使用道具 举报

    该用户从未签到

    发表于 2017-1-11 22:52:20 | 显示全部楼层
    学习学习,代码劫持**好啊
    PYG19周年生日快乐!
    回复 支持 反对

    使用道具 举报

    您需要登录后才可以回帖 登录 | 加入我们

    本版积分规则

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