iOS逆向工程——非越狱调试

其实iOS的逆向分析业界已经十分成熟了,网上也有许多有趣的尝试(一步一步实现iOS微信自动抢红包(非越狱)。本文着重于如何在非越狱机器上进行调试,出于学习及总结的目的,记录于此。

本文以破解游戏梦幻西游为例,逐步讲解整个调试流程。

->工具

完成整个过程需要准备以下几个必要工具:

1、iOSOpenDev,用于允许在Xcode上创建Dylib工程;

2、yololib,用于为二进制文件注入Dylib;

3、class-dump,用于导出 decrypted 的二进制文件的头文件;

4、CaptainHook,这是一个头文件,引入工程后,我们可以通过宏快速地 Hook 任意方法;

5、一台越狱机器,安装 cycriptopenssh

非必要工具:

1、dumpdecrypted,用于为AppStore包的二进制文件破壳;

注:dumpdecrypted 之所以非必要,是因为一般情况下可以从越狱市场下到已经 decrypted 后的app!如果下到的包不是 decrypted 的,请自行百度 decrypted。

->定位

首先我们要解决的问题是,在哪里注入我们的调试入口。这个定位过程必须要有越狱机器的配合。我们会通过在越狱机器(已安装应用)上运行 cycript 确定应用视图层级。

在MAC命令行运行:

    ->ssh root@192.168.10.11 #你的越狱手机的IP,默认越狱机器密码为 alpine
    ->ps -e #关掉其它应用,从进程列表中找到应用对应的进程号,一般路径带了一串MD5值的就是了
    ->cycript -p 2231 #进行cycript

当 cycript 运行后,就可以通过命令行运行objc代码:

    cy#[[UIApplication sharedApplication].keyWindow recursiveDescription]

这里用到了私有方法 recursiveDescription ,会返回整个层级的描述。

<UIWindow: 0x15cd9de50; frame = (0 0; 667 375); autoresize = W+H; gestureRecognizers = <NSArray: 0x15e08c260>; layer = <UIWindowLayer: 0x15cd79f50>>
   | <CCEAGLView: 0x15e08d2a0; frame = (0 0; 667 375); autoresize = W+H; gestureRecognizers = <NSArray: 0x15cdc2290>; layer = <CAEAGLLayer: 0x15e08c8f0>>

到这,我们定位了主View对应的类,现在我们需要写点代码Hook这个类。

->编写Dylib

通过 iOSOpenDev 创建一个Dylib工程,把主工程文件由.m 改为.mm,引入头文件 CaptainHook.h,把主工程文件.mm替换为以下内容:

#import "CaptainHook.h"

__attribute__((constructor)) static void entry() {
    [[BQInjectToolHelper sharedInstance] injectToolsToMY];
}

static BQInjectToolsNavigationController *sharedMainNav = nil;

CHDeclareClass(CCEAGLView);

CHMethod(7, id, CCEAGLView, initWithFrame, CGRect, frame, pixelFormat, id, arg2, depthFormat, unsigned int, arg3, preserveBackbuffer, _Bool, arg4, sharegroup, id, arg5, multiSampling, _Bool, arg6, numberOfSamples, unsigned int, arg7)
{
    //调用原来的AsyncOnAddMsg:MsgWrap:方法
    id v = CHSuper(7, CCEAGLView, initWithFrame, frame, pixelFormat, arg2, depthFormat, arg3, preserveBackbuffer, arg4, sharegroup, arg5, multiSampling, arg6, numberOfSamples, arg7);

    if (sharedMainNav == nil) {
        BQInjectToolsMainViewController *mainVc = [[BQInjectToolsMainViewController alloc] init];
        BQInjectToolsNavigationController *mainNav = [[BQInjectToolsNavigationController alloc] initWithRootViewController:mainVc];
        sharedMainNav = mainNav;
    }

    [sharedMainNav injectToSuperView:v];
    BQLog(@"BQ Inject initWithFrame:pixelFormat:depthFormat:preserveBackbuffer:sharegroup:multiSampling:numberOfSamples: success!");
    return v;
}

@implementation BQInjectToolHelper

+ (BQInjectToolHelper *)sharedInstance
{
    static BQInjectToolHelper *sharedInsatnce = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInsatnce = [[BQInjectToolHelper alloc] init];
    });
    return sharedInsatnce;
}

- (void)injectToolsToMY
{
    CHLoadLateClass(CCEAGLView);
    CHClassHook(0, CCEAGLView, init);
    CHClassHook(1, CCEAGLView, initWithFrame);
    CHClassHook(2, CCEAGLView, initWithFrame, pixelFormat);
    CHClassHook(3, CCEAGLView, initWithFrame, pixelFormat, depthFormat);
    CHClassHook(7, CCEAGLView, initWithFrame, pixelFormat, depthFormat, preserveBackbuffer, sharegroup, multiSampling, numberOfSamples);

    BQLog(@"BQ Injected!");
}

@end

@implementation BQInjectToolsNavigationController

- (void)injectToSuperView:(UIView *)view
{
    if (view == nil || self.view.superview == view) return;

    [self.view removeFromSuperview];

    self.view.frame = CGRectMake((view.frame.size.width - _injectToolsContentSize.width) / 2, -_injectToolsContentSize.height, _injectToolsContentSize.width, _injectToolsContentSize.height);
    [view addSubview:self.view];

    [self addGestureRecognizerToView:view];
}

@end

这里做了几件事:

1、申明了CCEAGLView 类,并Hook了方法 -(id)initWithFrame:(struct CGRect)arg1 pixelFormat:(id)arg2 depthFormat:(unsigned int)arg3 preserveBackbuffer:(_Bool)arg4 sharegroup:(id)arg5 multiSampling:(_Bool)arg6 numberOfSamples:(unsigned int)arg7 方法;

2、在补始化CCEAGLView 时,为该View添加子View以及Pan手势响应;

后续我们就可以通过手势,呼出我们注入的页面了。

这里有个小技巧,在cycript下,输入 [[CCEAGLView alloc] init ,并按两下 tab 键,cycript会为你联想方法,你可以把方法列表里的方法全Hook了(事实上我就是这么做的)

编写完代码后运行,这里注意保证生成的dylib的目标架构包含(armv7&arm64)。

->注入

在命令行运行:

    ->yololib MY.app/MY libJDYTest.dylib #注意文件的相对路径,dylib文件不能带前置目录

如果你的应用包含多个架构支持,那么yololib会分别为你的每一个架构注入dylib。运行后如下:

    Reading binary: libJDYTest.dylib

    2016-06-08 21:26:50.944 yololib[47766:1342053] FAT binary!
    2016-06-08 21:26:50.944 yololib[47766:1342053] Injecting to arch 9
    2016-06-08 21:26:50.944 yololib[47766:1342053] Patching mach_header..
    2016-06-08 21:26:50.945 yololib[47766:1342053] Attaching dylib..

    2016-06-08 21:26:50.945 yololib[47766:1342053] Injecting to arch 11
    2016-06-08 21:26:50.945 yololib[47766:1342053] Patching mach_header..
    2016-06-08 21:26:50.945 yololib[47766:1342053] Attaching dylib..

    2016-06-08 21:26:50.945 yololib[47766:1342053] Injecting to arch 0
    2016-06-08 21:26:50.945 yololib[47766:1342053] 64bit arch wow
    2016-06-08 21:26:50.946 yololib[47766:1342053] dylib size wow 56
    2016-06-08 21:26:50.946 yololib[47766:1342053] mach.ncmds 23
    2016-06-08 21:26:50.946 yololib[47766:1342053] mach.ncmds 24
    2016-06-08 21:26:50.946 yololib[47766:1342053] Patching mach_header..
    2016-06-08 21:26:50.946 yololib[47766:1342053] Attaching dylib..

    2016-06-08 21:26:50.946 yololib[47766:1342053] size 54
    2016-06-08 21:26:50.946 yololib[47766:1342053] complete!

看到上面的提示就表示注入完成了。注入的地址是 @executable_path/libJDYTest.dylib ,因此最终要把dylib放到和app可执行文件相同的目录下。

->重签名

重签名需要以下三个东西:

1、libJDYTest.dylib,这个是我们已经注入到二进制的动态库;

2、配对的证书和.mobileprovision文件;

3、Entitlements.plist文件;

1我们已经生成。2的获取方式如下:

1、从 https://developer.apple.com 申请开发者证书,证书名如下iPhone Developer: YourName (YYYYYYYYY),记下证书名YYYYYYYYY;

[2]、创建一个新应用工程,在工程 Build-Setting 中,把 Code signing 的Profile和证书指定为开发者证书;

[3]、运行起新工程,在Product里点app右键,找到app的目录,在app目录下有.mobileprovision文件(第三步其实可以不必做,这么做是为了保险起见);

Entitlement.pist 是用于申明应用权限的文件,会在签名中使用到,3的获取方式如下:

1、在 https://developer.apple.com 查找到证书的TeamID;

2、按如下模板生成 Entitlement.plist;

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>application-identifier</key>
  <string>XXXXXXXXXX.as.your.wish.name</string>
  <key>com.apple.developer.team-identifier</key>
  <string>XXXXXXXXXX</string>
  <key>get-task-allow</key>
  <true/>
  <key>keychain-access-groups</key>
  <array>
      <string>XXXXXXXXXX.as.your.wish.name</string>
  </array>
</dict>
</plist>

以上文件准备好后,运行如下命令:

    ->cp libJDYTest.dylib MY.app/
    ->cp embedded.mobileprovision MY.app/

    ->rm -rf MY.app/_CodeSignature  # 如果你的应用有ext或都watch,需要对Watch和ext中的_CodeSignature同样删了,或者干脆把Watch和ext删了

    ->codesign -f -s YYYYYYYYY MY.app/libJDYTest.dylib
    ->codesign -f -s YYYYYYYYY --entitlements Entitlements.plist MY.app
    ->xcrun -sdk iphoneos PackageApplication -v MY.app -o $(pwd)/MYFinish.ipa

运行完后,MYFinish.ipa就是我们的最终成果,用itools装到非越狱的iphone上吧。

这里签名需要注意以下两点:

1、签名的开发者证书,在钥匙串中的 属性->信任->使用此证书时 ,必须要选择<系统默认>否则签名不成功;

2、Apple Worldwide Developer Relations CA - G2证书同上;

验证是否正确答名成功使用以下命令:

    ->codesign -vvvvv MYFinish.app/libJDYTest.dylib 

    MYFinish.app/libJDYTest.dylib: valid on disk
    MYFinish.app/libJDYTest.dylib: satisfies its Designated Requirement

->调试

MYFinish.ipa 安装好后,后续重复以上步骤就可以在自己的面板上开发功能了。当然我们可以把上面的操作写成脚本,并保存为 fastPack.sh,方便快速的签名和打包,代码如下。

## 1、move yololib to /usr/local/bin/ by your self;
## 2、copy app dylib mobileprovision plist to the same path with this shell;
## 3、the all name will not support any space;
##
##                                                              ——Zhiqiang.bzq
##                                                                  2016.06.04

## Functions ##
function makeFinishName() {
    # appname=$1

    # name=${appname%.*}
    # ext=${appname##*.}
    # finishname="${name}Finish.$ext"
    echo $1;
}

function checkArgument() {
    if [ $# != 5 ]; then
        echo "use like this : $0 appname.app libname.dylib embedded.mobileprovision entitlement.plist cername"
        exit 1
    fi
    appname=$1
    libname=$2
    profile=$3
    entitlement=$4

    echo "checking arguments..."

    if [ ! -d $appname ]; then
    echo "app $appname no exist"
    exit 2
    fi

    if [ ! -f $libname ]; then
    echo "dylib $libname no exist"
    exit 3
    fi

    if [ ! -f $profile ]; then
    echo "profile $profile no exist"
    exit 4
    fi

    if [ ! -f $entitlement ]; then
    echo "entitlement $entitlement no exist"
    exit 5
    fi
}

function doCopy() {
    echo "copying file..."

    finishname=$(makeFinishName $1)

    # if [ -d $finishname ]; then
    #   rm -rf $finishname
    # fi

    # cp -rf $1 $finishname
    cp $2 "${finishname}/"
    cp $3 "${finishname}/"
}

function doInjectAtPath() {
    echo "injecting..."

    finishname=$(makeFinishName $1)
    name=${1%.*}

    yololib "${finishname}/${name}" "$2"
}

function findExtFilesAtPath () {
    ext=$1
    path=$2

    result=$(find $2/. -name "*.$1")
    echo $result
}

function removeCodeSignAtPath () {
    rm -rf "$1/_CodeSignature"
}

function doCodeSign() {
    echo "code signning..."

    finishname=$(makeFinishName $1)

    removeCodeSignAtPath "${finishname}/_CodeSignature"

    # find appex extension
    appeses=$(findExtFilesAtPath "appex" "${finishname}/")
    for appex in $appeses; do
        removeCodeSignAtPath $appex
        codesign -f -s $5 $appex
    done

    # for sub app
    apps=$(findExtFilesAtPath "app" "${finishname}/")
    for app in $apps; do
        removeCodeSignAtPath $app
        codesign -f -s $5 $app
    done

    # resign dylib
    dylibs=$(findExtFilesAtPath "dylib" "${finishname}/")
    for dylib in $dylibs; do
        codesign -f -s $5 $dylib
    done

    # sign the whole app by entitlement
    codesign -f -s $5 --entitlements $4 $finishname
}

function doGenerateIPA() {
    echo "packing ipa..."

    finishname=$(makeFinishName $1)
    name=${finishname%.*}
    path=$(pwd)

    xcrun -sdk iphoneos PackageApplication -v $finishname -o ${path}/${name}.ipa
}

function main() {
    checkArgument $*
    doCopy $*
    doInjectAtPath $*
    doCodeSign $*
    # doGenerateIPA $*
    echo "done!"
}

## Script ##
main $*

但这样远远不够,至少无法快速的进行断点调试。打开我们之前为了获取.mobileprofile文件创建的工程,添加新的Target,并改为MY(和目标可执行文件名相同即可)。

在MY的Build Phases中添加Run Script,Run Script添加如下脚本:

    lib_path="${BUILT_PRODUCTS_DIR}/lib${TARGET_NAME}.dylib"
    local_workspace="/Users/zhiqiangbao/Desktop/HackMH"

    rm -rf "${BUILT_PRODUCTS_DIR}/MY.app"
    cp -rf "${local_workspace}/MY.app" "${BUILT_PRODUCTS_DIR}/"
    chmod 777 "${BUILT_PRODUCTS_DIR}/MY.app/MY" #注意,这一步必须

    cp -rf "${local_workspace}/embedded.mobileprovision" "${BUILT_PRODUCTS_DIR}/"
    cp -rf "${local_workspace}/Entitlements.plist" "${BUILT_PRODUCTS_DIR}/"
    cp -rf "${local_workspace}/fastPack.sh" "${BUILT_PRODUCTS_DIR}/"

    cd $BUILT_PRODUCTS_DIR
    ./fastPack.sh MY.app libJDYTest.dylib embedded.mobileprovision  Entitlements.plist "XXXXXXXX"
    cd -

这里做了个小trick,在Xcode为新工程生成完app后,我们用自己签名过的app替换了它。所以最终被安装上手机的是被我们cracked的应用。

CMD+R运行工程试试,哈哈,大功告成。PS:游戏内的原生Log也可以看哦。

附图:



另附上开发工具dylib的git地址,别忘了改Run Script中的地址信息哦!

梦幻西游开发工具:git@gitlab.alibaba-inc.com:zhiqiang.bzq/MY_Cracked.git

用以下命令初始化:

    ->git submodule init
    ->git submodule update

如有不正之处,欢迎指正~!^ ^

时间: 2024-09-29 05:12:33

iOS逆向工程——非越狱调试的相关文章

[iOS逆向工程] 在汇编语言调试中获取当前实例句柄

在分析Safari行为的时候想到要用objective-c的特性随时可以语义化的查看一下UIView的各种状态,比如在UIView方法内部设了个断点,想看一下当前视图结构.只要得到当前实例的句柄就可以了.查了一些资料,记录一下. 函数参数的传递 iOS Simulator里应用是跑在32bits模式下的(在Activity Monitor可以看到),依据<<Mac OS Debug Magic>>里关于Intel 32bits参数传递的定义: Table 2 : Accessing

iOS逆向工程使用LLDB的USB连接调试第三方App_IOS

LLDB是Low Level Debugger的简称,在iOS开发的调试中LLDB是经常使用的,LLDB是Xcode内置的动态调试工具.使用LLDB可以动态的调试你的应用程序,如果你不做其他的额外处理,因为debugserver缺少task_for_pid权限,所以你只能使用LLDB来调试你自己的App.那么本篇博客中就要使用LLDB来调试从AppStore下载安装的App,并且结合着Hopper来分析第三方App内部的结构.LLDB与Hopper的结合,会让你看到不一样的东西,本篇博客就会和你

IOS 实现微信自动抢红包(非越狱IPhone)_IOS

iOS微信自动抢红包(非越狱)       现在微信红包很流行,尤其在微信群里发红包,如果没有及时抢红包,根本抢不到,于是就想是不是能写个插件之类的实现自动抢红包呢,经过在网上查询资料,实现了该功能,如下: 微信红包 前言:最近笔者在研究iOS逆向工程,顺便拿微信来练手,在非越狱手机上实现了微信自动抢红包的功能. 题外话:此教程是一篇严肃的学术探讨类文章,仅仅用于学习研究,也请读者不要用于商业或其他非法途径上,笔者一概不负责哟~~ 好了,接下来可以进入正题了! 此教程所需要的工具/文件 yolo

微信双开是定时炸弹?关于非越狱iOS上微信分身高危插件ImgNaix的分析

微信双开是定时炸弹? 关于非越狱iOS上微信分身高危插件ImgNaix的分析 作者:蒸米@阿里聚安全 序言 微信作为手机上的第一大应用,有着上亿的用户.并且很多人都不只拥有一个微信帐号,有的微信账号是用于商业的,有的是用于私人的.可惜的是官方版的微信并不支持多开的功能,并且频繁更换微信账号也是一件非常麻烦的事,于是大家纷纷在寻找能够在手机上登陆多个微信账号的方法,相对于iOS,Android上早就有了很成熟的产品,比如360 OS的微信双开和LBE的双开大师就可以满足很多用户多开的需求. 但是在

一步一步实现iOS微信自动抢红包(非越狱)

题外话:此教程是一篇严肃的学术探讨类文章,仅仅用于学习研究,也请读者不要用于商业或其他非法途径上,笔者一概不负责哟~~ 好了,接下来可以进入正题了! 此教程所需要的工具/文件 yololib class-dump dumpdecrypted iOSOpenDev iTools OpenSSH(Cydia) iFile(Cydia) Cycript(Cydia) Command Line Tools Xcode 苹果开发者证书或企业证书 一台越狱的iPhone 是的,想要实现在非越狱iPhone上

iOS逆向工程之给App脱壳

本篇博客以微信为例,给微信脱壳."砸壳"在iOS逆向工程中是经常做的一件事情,,因为从AppStore直接下载安装的App是加壳的,其实就是经过加密的,这个"砸壳"的过程就是一个解密的过程.未砸壳的App是无法在Class-dump.Hopper等工具中使用的.所以我们要将App进行砸壳处理.在Android开发中也存在App加壳的处理,比如什么360加固了,其实就是给App加一层壳. 本篇博客我们就以WeChat(微信)为例,从AppStore上下载微信并安装,然

你的iOS 8需要越狱吗?

在苹果备受关注的大事件当中,除了在6月初召开的WWDC2014之外,我想当属在6.24的时候,国内盘古团队正式放出了iOS7.1.1的完美越狱工具了.但是,和以往不同,不管是本次的盘古,还是在去年年末,evad3r团队的太极助手关注度却呈现出了高开低走的态势.在iPhone步入iOS7时代之后,可能,我们大家都已经明显感觉到身边朋友对于越狱行为的需求似乎已经下降了很多.那么越狱行为逐渐失宠的原因是什么呢?在本篇文章中,就让我们就一起来聊聊和越狱有关的那些事儿. 越狱(Jailbreak)是什么?

iOS逆向工程使用dumpdecrypted工具给App脱壳_IOS

本篇以微信为例,给微信脱壳."砸壳"在iOS逆向工程中是经常做的一件事情,,因为从AppStore直接下载安装的App是加壳的,其实就是经过加密的,这个"砸壳"的过程就是一个解密的过程.未砸壳的App是无法在Class-dump.Hopper等工具中使用的.所以我们要将App进行砸壳处理.在Android开发中也存在App加壳的处理,比如什么360加固了,其实就是给App加一层壳. 本篇我们就以WeChat(微信)为例,从AppStore上下载微信并安装,然后使用d

iOS 7完美越狱图解教程

今天晚上,越狱梦之队evad3rs搞了个突然袭击,悄无声息的放出了iOS 7完美越狱工具,支持所有可 升级至iOS 7的iPhone.iPod touch.iPad.和iPad mini,版本方面则支持iOS 7到7.0.4中的任何版本 .下面我们为大家带来完美越狱教程. 越狱前注意事项如下: 1.使用evasi0n工具之前请使用iTunes或者iCloud备份你的iOS设备,这样在越狱碰到意外时,可确 保不会丢失你的数据. 2.在使用evasi0n之前,请关闭密码锁,否则可能会导致一些意外发生