浅谈iOS越狱App开发

这是一篇科普性质的文章, 内容主要包括:

  • 基本背景知识
  • 开发环境
  • Root权限
  • launchd
  • bypass iPhone_NA/sandbox/codesign
  • SSH Tips
  • Cydia Hosting

如果你对这些Topic已经足够了解, 剩下的也不用看了:)

我最早接触越狱大概是在iPhoneOS(4.0之后才改名成iOS)3.1.3时代, 印象比较深的是@comexSpirit,经典的一键完美越狱; geohot还很活跃, 一己之力搞出Limera1n; 中文社区则有著名的GFWInterpreter...

咳咳, 不怀旧了, 开面是正文:

iOS和OS X一脉相承, kernel都来至Darwin OS). iOS6的kernel是Darwin Kernel 13.0.0, iOS7是Darwin Kernel 14.0.0(来源:维基百科)

这是Mavericks用的kernel版本, 13.0.2.

  
% uname -v Darwin Kernel Version 13.0.2: Sun Sep 29 19:38:57 PDT 2013; root:xnu-2422.75.4~1/RELEASE_X86_64

它们主流的开发语言都是Objective-C, 大部分Framework和Library也是通用的, 如Foundation/CoreFoundation, 就算不通用, 它们的设计也基本一致, 如UIKit/AppKit, 还有Chameleon这种致力于把UIKit搬到Mac的项目.

iOS下可用的Framework列表, 在/System/Library/Frameworks下可以找到.

文件系统目录结构一致, 默认用户是mobile; 越狱后的iOS才能算完整的Unix-like系统, 你可以装shell, terminal, 开ssh, 启后台进程, 绕过sanbox, 使用root权限, 使用动态库等等, Mac/Unix的知识全都可以用上.

正统的iOS App开发直接看Apple的iOS App Programming Guide即可.

Updates关于动态库, SO上这个回答的很不错

一般而言, 你需要一台Mac进行正儿八经的开发, iDevice不是必需的. 但如果是开发越狱App, 你需要一台越狱后的iDevice, Mac反而不是必需的.

主要的开发环境有两种:

On device toolchain

最早的toolchain都是自己build的, 大神们的toolchain一般也不公开, 现在情况好一些, 直接装Bigboss提供的即可.

这种做法好处是兼容性好, 测试方便, 比如你要编译个python的什么库;

缺点也有不少, 由于不能自定义固件了, 系统区的空间比较少, 编译速度慢, 如果升级iOS并重新越狱的话又得再装一遍环境.

我第一个SBSetting toggle就是用iphone-gcc编译的~

对比表格来自iPhone Dev Wiki

iphone-gcc LLVM+Clang for iOS
iOS SDK tools version 2.0 6.1
Maximum iOS SDK supported 3.2 7.0
Supported ARCHS arm, armv4t, armv6 armv4t, armv6, armv7, armv7f
Compiler Version GCC 4.0 LLVM 3.3
CC Tools Version 286 839
Cydia Repo Hosted On Telesphoreo BigBoss
Last Updated On July, 2008 October, 2013
Maintainer saurik coolstar
Default Target arm-apple-darwin9 armv7-apple-darwin11
ARCH built for arm armv7
C++ version supported C++98 (from 1998) C++11 (from 2011)
Obj. C Version supported 2.0 2.0
Obj. C Blocks (4.0 SDK+) Supported? No Yes
Obj. C ARC (5.0 SDK+) Supported? No Yes
Obj. C Literals+Autosynthesize (5.1 SDK+) Supported? No Yes

Cross compile

交叉编译的话, 主要就是Theos了, 它是跨平台的一套Makefile, 依赖perl, 在iOS也可以用.

在Theos之前, 你需要手写Makefile, 麻烦不说还容易出错, 还需要自己设置各种环境变量/Flag. 比如最这个Makefile

Theos的nic命令可以生成很多类型的模板, 主流的都包括了:

% theos/bin/nic.pl
NIC 2.0 - New Instance Creator
------------------------------
[1.] iphone/application
[2.] iphone/flipswitch_switch
[3.] iphone/library
[4.] iphone/preference_bundle
[5.] iphone/tool
[6.] iphone/tweak

简单的解释一下:

  • application

    正常的App, 在Mac下其实意义不大, 因为你可以直接用XCode

  • tool

    non-GUI工具, 比如写个helper或者daemon什么的.

  • library

    dylib或者Bundle

  • flipswitch_switch

    flipswtich的模板, ControlCenter插件.

  • preference_bundle

    Preference插件, 依赖Preference Loader, 插在Settings.app里.

  • tweak

    MobileSubstrate插件, 现在叫CydiaSubstrate. 一般意义上的tweak大部分都是这类.

SBSettings Toggle/Preference Bundle/NotificationCenter Widget/FlipSwtich 这些插件都是动态库. 关于动态库, 推荐Apple的这篇:Dynamic Library Programming Topics, 和这篇:Code Loading Programming Topics

CydiaSubstrate则是一个代码注入平台, 动态加载并注入dylib, 一般会和classdump/method swizzling配合, 具体可以看看它的文档, iPhone Dev Wiki上的介绍也不错.

更多的一些Tweak例子可以参考Ryan Petrich's Tweak Week.

Updates: 创建Tweak的Tweak: Flex 2
Updates: 还可以试试iOSOpenDev, 我最早装过它的公测版, 当时有bug没用起来, 现在看上去可用多了.

关于Root privilege

如何像Cydia那样以Root权限运行呢?

和传统做法基本一致:

  1. 给你的executable加上setuid/setgid bits, 比如说在postinst脚本里加上: chmod 6755 /path/to/executable
  2. 程序启动的时候调用setuid(0)
  3. iOS不允许直接以root权限运行, 所以还要一个exec trick.

关于什么是setuid/setgid, 请直接man chmod并搜索set-user-ID-on-execution

具体可以看GoAgent iOS的这几个文件:postinst, main.m, goagent, Makefile

还有种做法是利用launchd来执行需要root权限的操作. 从权限隔离的角度而言, 这种做法更好, 就是有点绕.

关于launchd/launchctl

launchd是iOS/OS X的关键程序, 类似于init/upstart, 用来管理所有的进程. 你在Activity Monitor随便找个进程, 它的直接或者间接父进程都是launchd.

 photo active_monitor_view_hierarchically_zpsbe835ca2.png

launchd托管了很多job, 它们可以是进程, 可以是Timer; launchd还可以监控文件, socket等等. 功能相当强大, 更多可以查看手册:man lauchd.plist.

launchctl是launchd的命令行管理工具, 这次iOS7完美越狱也用到了(Shebang Trick).

launchd/launchctl是开源的, 如果你想通过写代码来控制, 可以参考launchctl的实现.

顺带提一句, 现在GoAgent的启动/停止, 也是通过launchd的两个job实现的.

bypass iPhone_NA

有时候你会看到这样的宏:

OSX_AVAILABLE_STARTING(MAC_10_1,__IPHONE_NA);

它说明这个API仅在10.1之后的OS X下可用, iOS上不可用, 直接调用它编译器会报错.

如何绕过呢?

我们是可以动态加载lib的, 利用dlopen/dlsym(上面的动态库介绍有详细介绍)即可:

void* libHandle = dlopen("/System/Library/Frameworks/SystemConfiguration.framework/SystemConfiguration", RTLD_LAZY);

CFStringRef(*_SCDynamicStoreKeyCreateProxies)(CFAllocatorRef) = dlsym(libHandle, "SCDynamicStoreKeyCreateProxies");

//调用_SCDynamicStoreKeyCreateProxies

还有一种做法是AppProxyCap这种, 链接SystemConfiguration.framework之后直接取地址:

typedef CFDictionaryRef (*SCDynamicStoreCopyProxies) (SCDynamicStoreRef store);
static SCDynamicStoreCopyProxies origin_SCDynamicStoreCopyProxies;

origin_SCDynamicStoreCopyProxies = &SCDynamicStoreCopyProxies;

这种做法也可以用来判断OS版本, 比如某个API是5.0之后加入的, 那么之前的版本取地址将会得到空值.

Updates: 还可以把这个宏给undef掉~

bypass sandbox

直接安装到/Applications即可.

bypass codesign

直接看Saurik的这篇文章即可:Bypassing iPhone Code Signatures, 有证书的话还是签下名吧.

SSH Tips

  • 记得把默认的alpine密码改掉, iOS下也是有病毒的:ikee
  • 没有WiFi的情况下, 可以考虑ssh over usb, 具体可以看这里

  • 开启ssh公钥认证, 可以节省很多时间, 比如Theos的make install和scp就不用输密码了.

  • 顺带提下, 我的vim-config也基本支持iOS, 主要是.vimrc/.bashrc

Cydia Repo Hosting

Cydia的source/repo基本上是Debian的APT repo, 只需要提供:

  1. Release, repo描述文件
  2. Packages|Pacakges.gz/bz2, repo的package清单
  3. *.deb, 实际package文件

Release文件几乎不用改, 只要准备好deb文件, 然后用dpkg-scanpackage命令生成Packages就可以了, 比如update_packages.sh:

#!/bin/bash
dpkg-scanpackages . > Packages && gzip -c Packages > Packages.gz

之后可以通过rsync同步到服务器上, 跑个文件下载的web服务就可以了, 比如当初学node写的serve_cydia.js, 依赖node-static:

#!/usr/bin/env node

var node_static = require('node-static');
var http = require('http');
var url = require('url');

var cydia_files = new(node_static.Server)('./files');

http.createServer(function(request, response) { request.addListener('end', function() { if (request.url.search('cydia') === -1) { console.error('non cydia request:' + request.url +' from ' + request.connection.remoteAddress) return } cydia_files.serve(request, response, function(error, result){ if (error) { console.error('' + request.connection.remoteAddress + ' ' + request.method + ' ' + request.url + ' ' + error.status + ' ' + error.message); response.writeHead(error.status, error.message); response.end(); } else { console.log('' + request.connection.remoteAddress + ' ' + request.method + ' ' + request.url); } }); }); }).listen(80)

不想自己hosting的话, 可以提交给Bigboss或者是尝试下myrepospace.

一些参考资源

--END


Published: January 25 2014

blog comments powered by Disqus