找回密码
 立即注册

QQ登录

只需一步,快速开始

1401软件论坛总版规【新人必看】如何正确发布收费帖子加入vip系统学习仅需498元免费获得1401软件安全论坛vip开通本站VIP会员请联系QQ 564853771
论坛纪念优盘Beyond EXE超强加密器V1.61401论坛破解工具包 2017-9-12[破解班vip]入门篇[破解班vip]基础篇
[破解班vip]脱壳篇[破解班vip]实战篇[破解班vip]网络验证篇[破解班vip]零基础HOOK教程[破解班vip]零基础易语言入门
[破解班vip]零基础c语言入门[破解班vip]零基础Delphi编程入门[破解班vip]寒假培训课程共7课[破解班vip]破解提高篇[破解班vip]易语言培训课程
[破解班vip]核心技术培训篇[破解班vip]pe格式与pe操作[破解班vip]2022全新封包-山寨-爆破全系列[原创课程]c++单文档框架课程[逆向班vip]汇编语句与反汇编基础
[逆向班vip]全自动扫雷辅助[逆向班vip]手机模拟按键[逆向班vip]植物大战僵尸辅助广告位招租...付费破解软件 +Q 564853771
查看: 3164|回复: 0

破解Hopper Disassembler v3.7.8 for mac的艰难历程

[复制链接]
  • TA的每日心情

    2023-5-7 22:27
  • 签到天数: 340 天

    [LV.8]以坛为家I

    343

    主题

    454

    回帖

    2098

    积分

    武林新贵

    UID
    7
    元宝
    232
    威望
    815
    贡献
    64
    信誉值
    0
    精华
    10
    在线时间
    189 小时
    注册时间
    2014-1-16
    最后登录
    2023-5-7
    违规
    0
    积分
    2098

    优秀版主热心会员金点子奖突出贡献最佳新人活跃会员宣传达人吾爱富翁论坛元老管理团队已有小成灌水之王

    发表于 2015-3-11 14:18:45 | 显示全部楼层 |阅读模式
    Ȧ
    Hopper Disassembler是一个很牛的逆向分析工具,虽然我个人觉得比不上IDA,但是聊胜于无,感觉字符处理得不错。
    这个工具在2.x时代是很容西xx的,但是到了3.x翅膀就硬了。
    我这里xx的是最新的3.7.8,在这个版本中自我保护手段是很多的,主要是:1.无法用gdb lldb载入分析,一附加就出错退出。2、Section偏移量篡改。3、加密了__TEXT里的代码,字符串,还有objc相关的信息。这样的结果就是,无法进行动态分析,无法载入静态分析。
    以下Hopper Disassembler简称hd
    想破解它,首先就要修复其Section。
    关于Mach-O的结构,不懂的百度一下吧。还有就是用010 Editor来编辑简直好用到不行
    要修复Section,我们先分析以下内存布局,根据其布局来修复
    这是otool -l输出的__TEXT Segment的信息
    代码:
    Load command 1
          cmd LC_SEGMENT_64
      cmdsize 1032
      segname __TEXT
      vmaddr 0x0000000100000000
      vmsize 0x00000000003d1000
      fileoff 0
    filesize 4001792
      maxprot 0x00000007
    initprot 0x00000007
      nsects 12
        flags 0x0
    主要看vmaddr 和fileoff,vmaddr是这个二进制文件加载到内存中的位置。fileoff是加载到内存里的数据在文件中得偏移量,这里是0.也就是说从头加载。
    为什么要看这里呢,这是因为根据分析发现hd主要是对Section的offet进行修改,修改为任意值。Section中得offset和fileoff类似,也是指在文件中得偏移量。静态分析工具需要这个偏移量来定位一些静态信息,如果将其修改,静态分析就无法继续。还有就是修改Section对运行时是没有影响的。这样我们就得知了fileoff和vmaddr。
    我们再看一下__TEXT Segment的__text Section,以下是otool -l输出的__text的信息。
    代码:
    Section
      sectname __text
      segname __TEXT
          addr 0x0000000100006fa0
          size 0x00000000003022ea
        offset 28576
        align 2^4 (16)
        reloff 0
        nreloc 0
        flags 0x80000400
    reserved1 0
    reserved2 0
    addr同样是这个Section在内存中的位置,offset是在文件中的偏移量。可以这么算
    offset = fileoff + vmadr - addr
    将offset回填就行了。
    按照上面说的方法,逐一修正所有的Section就ok了。还要注意的地方就是各个Segment的大小filesize和vmsize是否相等。
    至此Section的修复就算完成了。
    第二步,就是恢复被加密的Section的内容。
    因为无法附加调试,vm_read的动态基址获取很麻烦,于是我这里选择指定DYLD_INSERT_LIBRARIES环境变量的方式注入一个dylib,在dylib的构造方法里启动一个线程,在线程中等待运行时Section的解密完成。一旦发现解密完成之后就把解密后的内容dump到硬盘上。
    代码:
    /*
            export DYLD_FORCE_FLAT_NAMESPACE=1
            export DYLD_INSERT_LIBRARIES=~/xd.dylib
    */

    #include <stdio.h>
    #include <unistd.h>
    #include <errno.h>
    #include <stdlib.h>
    #include "pthread.h"
    #include <sys/types.h>
    #include <sys/ptrace.h>
    #include <sys/sysctl.h>
    #include <mach/mach.h>
    #include <mach/mach_init.h>
    #include <mach/mach_vm.h>
    mach_vm_address_t getBasicAddress(mach_port_t task){
            mach_vm_size_t region_size = 0;
            mach_vm_address_t region = NULL;
            int ret = 0;
              /* Get region boundaries */
    #if defined(_MAC64) || defined(__LP64__)
            vm_region_basic_info_data_64_t info;
            mach_msg_type_number_t info_count = VM_REGION_BASIC_INFO_COUNT_64;
            vm_region_flavor_t flavor = VM_REGION_BASIC_INFO_64;
            if ((ret = mach_vm_region(mach_task_self(), &region, &region_size, flavor, (vm_region_info_t)&info,  
                    (mach_msg_type_number_t*)&info_count, (mach_port_t*)&task)) != KERN_SUCCESS)
            {
                    printf("mach_vm_region() message %s!\n",mach_error_string(ret));
                    return NULL;
            }
    #else
            vm_region_basic_info_data_t info;
            mach_msg_type_number_t info_count = VM_REGION_BASIC_INFO_COUNT;
            vm_region_flavor_t flavor = VM_REGION_BASIC_INFO;
            if ((ret = vm_region(mach_task_self(), &region, &region_size, flavor, (vm_region_info_t)&info,  
                    (mach_msg_type_number_t*)&info_count, (mach_port_t*)&task)) != KERN_SUCCESS)
            {
                    printf("vm_region() message %s!\n",mach_error_string(ret));
                    return NULL;
            }
    #endif
            return region;
    }
    vm_size_t readRemotoMemory(char *buf,vm_size_t len,mach_port_t task,vm_address_t address)
    {
            vm_size_t outSize = 0;
            int ret = vm_read_overwrite(task,address,len,(vm_address_t)buf,&outSize);
            if (ret != 0)
            {
                    printf("vm_read_overwrite() message %s!\n",mach_error_string(ret));
                    return 0;
            }
            return outSize;
    }
    //int main(int argc, char const *argv[])
    void* handler(void *p)
    {
            //int pid = 16057;
            int pid = getpid();
            char buffer[512];
            mach_vm_address_t address = 0;
            mach_port_t task = 0;
            int waitTime = 15;
            while(waitTime){
                    sleep(1);
                    printf("thread waiting! %d\n",waitTime);
                    waitTime --;
            }
            int ret = task_for_pid(mach_task_self(),pid,&task);
            if (ret != 0)
            {         
                    printf("task_for_pid() message %s!\n",mach_error_string(ret));
                    return NULL;
            }
            address = getBasicAddress(task);
            printf("pid    : %d\n",pid);
            printf("task    : %x\n", task);
            printf("address : %llx\n", address);
            if (address == 0)
            {
                    printf("getBasicAddress() faild!\n");
                    return NULL;
            }
            uint32_t writeSize = 0;
            FILE *fp = fopen("dump.bin","wb");
            readRemotoMemory(buffer,512,task,address);
            printf("%x\n",*(uint*)buffer);
            while (writeSize <= 0x1F8E){
                    readRemotoMemory(buffer,512,task,address);
                    //printf("%x\n",*(uint*)buffer);
                    address += 512;
                    writeSize += writeSize;
                    fwrite(buffer,512,1,fp);
            }
            return NULL;
    }
    void __attribute__((constructor)) init()
    {
            int err;
            pthread_t ntid;
        err = pthread_create(&ntid, NULL, handler, NULL);
        if (err != 0)
        {
                printf("can't create thread: %s\n", strerror(err));
                return ;
        }
    }
    这是我此次使用的dump的代码。将其编译成dylib,插入DYLD_INSERT_LIBRARIES后启动hd就行了。详细做法请看其代码。
    dump出来的文件,其实就是一个解密后的二进制镜像。里面包含有解密了得Section。其文件结构也是一个mach-o文件。
    这里我用010 Editor神器将dump出来的Section逐一复制粘贴到原本里hd里。我做的时候只还原了__TEXT Segment的Section,__Data Segment的没有理会。而且这样子修复后的hd,是可以运行的,只不过显示主界面大约一秒钟之后就闪退了。而且其反调试功能还在工作,还是无法动态调试。
    这样就算解密完成了,经过这样的处理,就能顺利地加载到IDA分析了。
    本人比较懒,有快捷的方法我也不啰嗦。因为要完全还原hd的可执行文件太麻烦,于是我这里使用运行时内存补丁来将其破解。
    经过ida的分析,找到几处关键点checkRegistrationLicense,和checkRegistrationToken,然后用这里(http://malokch.xicp.net/?post=25)的工具生成补丁。
    补丁的加载方式还是DYLD_INSERT_LIBRARIES插入dylib,~~太懒了没办法,补丁代码如下
    代码:
    /*
            export DYLD_FORCE_FLAT_NAMESPACE=1
            export DYLD_INSERT_LIBRARIES=~/chd.dylib
    */

    #include <stdio.h>
    #include <unistd.h>
    #include <errno.h>
    #include <stdlib.h>
    #include "pthread.h"
    #include <sys/types.h>
    #include <sys/ptrace.h>
    #include <sys/sysctl.h>
    #include <mach/mach.h>
    #include <mach/mach_init.h>
    #include <mach/mach_vm.h>
    #include "libkern/OSCacheControl.h"
    mach_vm_address_t getBasicAddress(int pid){
            mach_vm_size_t region_size = 0;
            mach_vm_address_t region = 0;
            mach_port_t task = 0;
            int ret = 0;
            ret = task_for_pid(mach_task_self(),pid,&task);
            if (ret != 0)
            {         
                    printf("task_for_pid() message %s!\n",mach_error_string(ret));
                    return 0;
            }
              /* Get region boundaries */
    #if defined(_MAC64) || defined(__LP64__)
            vm_region_basic_info_data_64_t info;
            mach_msg_type_number_t info_count = VM_REGION_BASIC_INFO_COUNT_64;
            vm_region_flavor_t flavor = VM_REGION_BASIC_INFO_64;
            if ((ret = mach_vm_region(mach_task_self(), &region, &region_size, flavor, (vm_region_info_t)&info,  
                    (mach_msg_type_number_t*)&info_count, (mach_port_t*)&task)) != KERN_SUCCESS)
            {
                    printf("mach_vm_region() message %s!\n",mach_error_string(ret));
                    return 0;
            }
    #else
            vm_region_basic_info_data_t info;
            mach_msg_type_number_t info_count = VM_REGION_BASIC_INFO_COUNT;
            vm_region_flavor_t flavor = VM_REGION_BASIC_INFO;
            if ((ret = vm_region(mach_task_self(), &region, &region_size, flavor, (vm_region_info_t)&info,  
                    (mach_msg_type_number_t*)&info_count, (mach_port_t*)&task)) != KERN_SUCCESS)
            {
                    printf("vm_region() message %s!\n",mach_error_string(ret));
                    return NULL;
            }
    #endif
            return region;
    }
    vm_size_t readRemotoMemory(char *buf,vm_size_t len,int pid,vm_address_t address)
    {
            vm_size_t outSize = 0;
            mach_port_t task = 0;
            int ret = task_for_pid(mach_task_self(),pid,&task);
            if (ret != 0)
            {         
                    printf("task_for_pid() message %s!\n",mach_error_string(ret));
                    return 0;
            }
            ret = vm_read_overwrite(task,address,len,(vm_address_t)buf,&outSize);
            if (ret != 0)
            {
                    printf("vm_read_overwrite() message %s!\n",mach_error_string(ret));
                    return 0;
            }
            return outSize;
    }
    //
    int FakeCode(char *addr, char code)
    {
            mach_port_t task;
            mach_vm_size_t region_size = 0;
            mach_vm_address_t region = (vm_address_t)addr;
      /* Get region boundaries */
    #if defined(_MAC64) || defined(__LP64__)
            vm_region_basic_info_data_64_t info;
            mach_msg_type_number_t info_count = VM_REGION_BASIC_INFO_COUNT_64;
            vm_region_flavor_t flavor = VM_REGION_BASIC_INFO_64;
            if (mach_vm_region(mach_task_self(), &region, &region_size, flavor, (vm_region_info_t)&info, (mach_msg_type_number_t*)&info_count, (mach_port_t*)&task) != 0)
            {
                    return 0;
            }
    #else
            vm_region_basic_info_data_t info;
            mach_msg_type_number_t info_count = VM_REGION_BASIC_INFO_COUNT;
            vm_region_flavor_t flavor = VM_REGION_BASIC_INFO;
            if (vm_region(mach_task_self(), &region, &region_size, flavor, (vm_region_info_t)&info, (mach_msg_type_number_t*)&info_count, (mach_port_t*)&task) != 0)
            {
                    return 0;
            }
    #endif
      /* Change memory protections to rw- */
            if (vm_protect(mach_task_self(), region, region_size, 0, VM_PROT_READ | VM_PROT_WRITE | VM_PROT_COPY) != KERN_SUCCESS)
            {
                    //_LineLog();
                    return 0;
            }
      /* Actually perform the write */
            *addr = code;
      /* Flush CPU data cache to save write to RAM */
            sys_dcache_flush(addr, sizeof(code));
      /* Invalidate instruction cache to make the CPU read patched instructions from RAM */
            sys_icache_invalidate(addr, sizeof(code));
      /* Change memory protections back to r-x */
            vm_protect(mach_task_self(), region, region_size, 0, VM_PROT_EXECUTE | VM_PROT_READ);
            return 1;
    }

    //int main(int argc, char const *argv[])
    void* handler(void *p)
    {
            //int pid = 16057;
            int pid = getpid();
            char buffer[512];
            mach_vm_address_t address = 0;
            address = getBasicAddress(pid);
            //printf("Target pid    : %d\n",pid);
            //printf("Base address  : %llx\n", address);
            if (address == 0)
            {
                    printf("getBasicAddress() faild!\n");
                    return NULL;
            }
            //Demo
            char *demo = (char*)address + 0x329a9e;
            demo[0] = ' ';
            demo[1] = ' ';
            demo[2] = ' ';
            demo[3] = ' ';
            //Demo version
            char *dv = (char*)address + 0x329E8C;
            dv[0] = 'F';
            dv[1] = 'u';
            dv[2] = 'l';
            dv[3] = 'l';
            //Waiting for decode __text
            sleep(1);
            //isreg
            //*(uint16_t*)(address + 0x58b65)        = 0x9090;
            //*(uint16_t*)(address + 0x58b65 + 2) = 0x9090;
            //*(uint16_t*)(address + 0x58b65 + 4) = 0x9090;
            //checkRegistrationLicense:
            //xor ebx,ebx    =>    mov    $1,%bl
            //xor edi,edi    =>    inc    %edi
            *(uint32_t*)(address + 0xb9b7) = 0xc7ff01b3;
            //checkRegistrationToken
            // xor r14d,r14d => inc r14d
            *(uint8_t*)(address + 0xb974)          = 0x41;
            *(uint8_t*)(address + 0xb974 + 1) = 0xff;
            *(uint8_t*)(address + 0xb974 + 1) = 0xc6;
            return NULL;
    }
    void __attribute__((constructor)) init()
    {
            int err;
            pthread_t ntid;
        err = pthread_create(&ntid, NULL, handler, NULL);
        if (err != 0)
        {
                printf("can't create thread: %s\n", strerror(err));
                return ;
        }
    }
    这样子破解可能还不完全,我也没发现哪里不能用,不过能用就是了。没有了注册窗口,没有了调试面板的限制,没有Demo的水印等。
    最后打包的时候,将MacOS下的Hopper Disassembler v3重命名为Hopper Disassembler v3_,然后新建一个shell脚本,名字叫Hopper Disassembler v3,脚本代码如下
    代码:
    #!/bin/bash
    HD_PATH="`dirname "${0}"`"
    HD_BIN="`dirname "${0}"`"/Hopper\ Disassembler\ v3_
    export DYLD_INSERT_LIBRARIES="${HD_PATH}/chd.dylib"
    "$HD_BIN"
    我这里的补丁叫chd.dylib,将其放到MacOS下就OK了。简直完美。
    若有不对之处敬请指正。

    评分

    参与人数 1威望 +3 贡献 +3 元宝 +3 收起 理由
    帝天 + 3 + 3 + 3 看起来好牛逼的样子

    查看全部评分

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|Archiver|手机版|小黑屋|1401软件安全 ( ICP备16034480号 )

    GMT+8, 2024-5-2 11:31 , Processed in 0.161814 second(s), 26 queries , Gzip On.

    Powered by Discuz! X3.5

    Copyright © 2001-2020, Tencent Cloud.

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