Quantcast
Channel: Essence Sharing | 干货分享 - iOSRE
Viewing all 301 articles
Browse latest View live

B站直播间特效表情文件的获取

$
0
0

@tbag wrote:

小破站今年的跨年晚会很是不错,在跨年夜总算给本阿宅一点点精神慰藉。在用手机看直播的时候,大家刷的礼物特效不停出现,我比较钟爱其中的“打call”,想着加到聊天软件里做个表情,以后兄弟们分享文章的时候可以商业互吹一番。搜了一圈没有发现现成的,今天空下来,随即手撸了一下找到表情文件,简单记录一下过程。

准备

老套路,越狱iPhone,AppStore里下载B站客户端,打开 FLEXible 开关,搬出 FLEX 看看这个特效表情是个啥子东西。设置好后,进入客户端,在首页找到 直播 入口,然后里面选个人气高的直播间进去,耗费巨资手刷一个打call表情,待显示时候,查看FLEX的Views结构如下:

可疑的类为 BBLiveBaseSVGAAnimationViewSVGAPlayer , 我们知道SVG是可以作为矢量图像的,点进去看一下,发现里面有个 SVGAVideoEntity ,进一步查看,确认这个就是表情文件了,里面的images字典就是是图片帧各个部分的元素的碎图。

到此,我们定位到了特效文件的显示对象。

获取

按惯例,先广撒网一下,Google一下 SVGAPlayer ,居然有意外收获: SVGAPlayer 是YY的UED团队出品的一个特效方案,类似于airbnb的Lottie,可以将AE或Flash动画导出到客户端所用。网站http://svga.io/中有详细介绍,并且含有一个svga文件的在线预览页面

根据其iOS端使用手册,验证 SVGAParser 类 确实也在B站客户端中有引用,hook一下它的几个parseWith函数,运行后顺利拿到初始化特效文件的日志:

SVGAParser parseWithData, <789c4cba 05505c5b d3358cbb bbbb0577 ... b6d054d7 e309d1ff 01424fd4 86>, cacheKey 14EAD217D8A29CA4B7320F1CCF549584

789c4cba开头的应该为动画的Data数据,数据有点大,这里截断显示了。

根据cacheKey在app的存储目录下搜索一番, find . -name "*14EAD217D8A29CA4B7320F1CCF549584*" 无结果,说明没这个文件,不用灰心,换grep再撸一遍, grep -rn "14EAD217D8A29CA4B7320F1CCF549584" ./ ,这次有意外收获:
(多余的目录前缀已删掉)

Binary file ./Library/Caches/live/animation/manifest.sqlite-wal matches

匹配了两个sqlite的wal文件。顺带说一嘴这个是什么?WAL是 Write-Ahead Logging 的意思,是实现原子提交和回滚的一种机制。How WAL Works

检查一下 animation 文件夹下还有什么? data manifest.sqlite manifest.sqlite-shm manifest.sqlite-wal trash ,在data文件夹下找到一堆类似md5命名的文件,大小为几百K,拷贝到Mac上 file一下:

file ./Library/Caches/live/animation/data/b84008c716944e598887d43b3ff89514
./Library/Caches/live/animation/data/b84008c716944e598887d43b3ff89514: Apple binary property list

很显然了,是plist文件。改后缀名为plist后打开查看:

将数据部分提取出来保存为文件:
/usr/libexec/PlistBuddy -c 'Print :"$objects:1"' b84008c716944e598887d43b3ff89514.plist > 1.svga

用svga文件在线预览工具 打开刚才保存的 1.svga,播放成功。

吼,剩下的就是跑shell脚本从这些plist文件中提取一下svga的过程了,最后,终于找到了心爱的打call表情,最后的最后,无奈录屏转为GIF了…

提取的一些svga文件记录

56

Posts: 3

Participants: 2

Read full topic


IOS13.2.3获取微信小游戏/小程序资源文件+代码

$
0
0

@makdbb wrote:

有时候看别的小程序/小游戏有些功能不知道如何实现,想参考下,但是没有源码,或者看到一些游戏的资源做的不错,想拿下来改改。怎么办?
今天就来看看怎么拿到资源文件和代码(虽然代码是加密过的),不过一般js的加密也就是混淆压缩,做了变量名替换,资源文件就没有压缩的了。
(之前网上也有很多人写过怎么弄,但是实际上很多都不可用了,所以自己也记录下。)

  1. 先试玩一下觉得可以的小游戏或者小程序,让微信先把包下下来。
    一般情况下开发微信小游戏/小程序的时候,会把一个包上传到微信服务器,但是这个包有一定的大小限制,一般都在几MB,但是微信给了一些动态加载资源的方式,自己可以从自己的服务器或者cdn上面去拉取新的资源包来更新自己的小程序/小游戏,这个就俗称热更新。当然代码中也会去加载一些动态的资源。

简单总结下一共有三种资源需要获取:

  • 基础包中资源(微信服务器拉到微信本地)
  • 热更新加载包中的资源(从开发者服务器拉到微信本地)
  • 动态资源(从开发者服务器拉到微信本地)
2. ssh到越狱手机获取
1ssh root@localhost -p 2222
3. 进入cydia调试微信
1cyrun -b com.tencent.xin -e
4. 找到微信沙盒Library目录
1[[NSFileManager defaultManager] URLsForDirectory:NSLibraryDirectory inDomans:NSUserDomainMask][0]2我的手机是这个目录:3file:///var/mobile/Containers/Data/Application/38BF9699-C8DE-4570-95CF-09876BE91D41/Library/
5. 退出cydia,进入沙盒Library目录find一下wxapkg文件
1cd /var/mobile/Containers/Data/Application/38BF9699-C8DE-4570-95CF-09876BE91D41/Library/; find -name "*.wxapkg";
6. 退出手机,scp把wxapkg拉回来。
1scp -P2222 root@localhost:/var/mobile/Containers/Data/Application/38BF9699-C8DE-4570-95CF-09876BE91D41/Library/WechatPrivate/9454ac8b05a1983e8255ea94d91cc7b2/WeApp/LocalCache/release/wxb92d4d650d51eda8/117.wxapkg .
7. 拉回来的是一个wxapkg文件,其实也只是一种文件格式。git上找个工具解析出来就行。
1php ./parse.php 117.wxapkg

我看git上有用php写的也有用go写的,我本地有php所以就用php了,自己也可以用go来弄个下。
给一个git连接:https://github.com/Clarence-pan/unpack-wxapkg
源码有兴趣可以看看。其实也就是按照固定格式解析二进制文件。
最终获得的文件有这些:

2. ssh到越狱手机获取
1ssh root@localhost -p 2222
3. 进入cydia调试微信
1cyrun -b com.tencent.xin -e
4. 找到微信沙盒Library目录
1[[NSFileManager defaultManager] URLsForDirectory:NSLibraryDirectory inDomans:NSUserDomainMask][0]2我的手机是这个目录:3file:///var/mobile/Containers/Data/Application/38BF9699-C8DE-4570-95CF-09876BE91D41/Library/
5. 退出cydia,进入沙盒Library目录find一下wxapkg文件
1cd /var/mobile/Containers/Data/Application/38BF9699-C8DE-4570-95CF-09876BE91D41/Library/; find -name "*.wxapkg";
6. 退出手机,scp把wxapkg拉回来。
1scp -P2222 root@localhost:/var/mobile/Containers/Data/Application/38BF9699-C8DE-4570-95CF-09876BE91D41/Library/WechatPrivate/9454ac8b05a1983e8255ea94d91cc7b2/WeApp/LocalCache/release/wxb92d4d650d51eda8/117.wxapkg .
7. 拉回来的是一个wxapkg文件,其实也只是一种文件格式。git上找个工具解析出来就行。
1php ./parse.php 117.wxapkg

我看git上有用php写的也有用go写的,我本地有php所以就用php了,自己也可以用go来弄个下。
给一个git连接:https://github.com/Clarence-pan/unpack-wxapkg
源码有兴趣可以看看。其实也就是按照固定格式解析二进制文件。
最终获得的文件有这些:
1578503504247

为了避免麻烦,每次还得上手机,我写了一个脚本,连上手机直接拉,拉完直接抽文件,哈哈,请允许我卖弄一下。(记得把parse.php放到脚本同目录下)代码如下:

 #########################################################################
 # File Name: get_wxapkg.sh
 # Author: 小马的爸爸
 # mail: 我还没有伊妹儿
 # Created Time: 2020-01-09
 #########################################################################
 #!/bin/bash
 wxapkgs=`ssh root@localhost -p2222 "find /var/mobile/Containers/Data/Application/ -name '*.wxapkg'"`
 for wxapkg in $wxapkgs;
do

    echo "COPYING $wxapkgs"
    scp -P2222 root@localhost:/$wxapkg .
    php ./parse.php ./${wxapkg##*/}
done

最后,我今天编辑文章用的一个在线的markdown编辑器,share一下~
最后希望能和大家多多交流,有兴趣可以加我微信一起学习讨论哈。nicholas_mcc

Posts: 4

Participants: 2

Read full topic

求 iOS 13.2越狱方案

微信集赞/评论插件分析及开发

$
0
0

@4ch12dy wrote:

开始

为什么要做这个集赞的插件呢?起因是上周去参加了某个会议,有一个集赞60领玩偶的活动,但是想到平时一条朋友圈也就几个赞,而且又不想找人点赞,领不到,很气。回去以后想着能不能写一个集赞的插件,在需要的时候直接输入想要的赞、评论数量,我发的朋友圈就能有多少赞。这样再有这样的活动岂不美哉。准备开干!

理性分析

再开始之前,先理性分析一波:如果想要集赞,这里有两种思路,一个是直接在view层去更改,但是这样得去处理界面的一些细节,一旦不注意,很容易崩溃。还有种思路是更改datasource或者说赞评论的模型。可以想到,最初一条朋友圈肯定是从服务器拿到数据并封装成对应的模型。一般来说,越改底层的数据或者说源头的数据,那么稳定性和真实性就更高。这里我的想法就是既不改view层,也不改源头层,就改封装好的模型那一层应该就很符合要求。接下来主要讲一下怎么我去实现这个需求的分析过程,因为本身功能不是很复杂,大佬随便看看就行。

准备条件

  • 一台mac
  • 一台越狱的iOS设备
  • ida/Hooper/theos
  • flex/issh/xia0LLDB
  • 其他常见逆向工具等

对于一贯喜欢上调试器分析得我,所以写了iSSHxia0LLDB两个工具,在这两个工具的辅助下整个插件用了2小时就完成了逆向分析和代码实现。

逆向分析

逆向赞和评论的数据模型

一切从界面入手,这里分析界面我一般喜欢用flex,在微信的朋友圈界面,用flex很容易发现当前界面的控制器为WCTimeLineViewController而且界面是一个UITableView

将wechat执行文件拖入Hooper(wechat文件太大,ida分析会很卡)找到UITableView的代理方法:
-[WCTimeLineViewController tableView:cellForRowAtIndexPath:]

这里面肯定会根据模型去设置cell数据

  r2 = [r28 section];
  var_70 = r25;
  r24 = [r25 calcDataItemIndex:r2];
  r25 = [[MMServiceCenter defaultCenter] retain];
  r2 = [WCFacade class];
  var_78 = r28;
  r0 = [r25 getService:r2];
  r0 = [r0 retain];
  r24 = [[r0 getTimelineDataItemOfIndex:r24] retain];
  [r0 release];
  [r25 release];
  r19 = [[MMServiceCenter defaultCenter] retain];
  r0 = [r19 getService:[WCFacade class]];
  r0 = [r0 retain];
  r25 = [[r0 getLayerIdForDataItem:r24] retain];
  [r0 release];
  [r19 release];
  r19 = [[MMServiceCenter defaultCenter] retain];
  r0 = [r19 getService:[WCFacade class]];
  r0 = [r0 retain];
  r20 = r0;
  r0 = [r0 getShowTip:r24 layerId:r25];
  r29 = r29;
  r26 = [r0 retain];
  [r20 release];
  [r19 release];

整理下来就是

[[MMServiceCenter defaultCenter] getService:[WCFacade class]]会得到一个WCFacade对象,然后通过

[WCFacade getTimelineDataItemOfIndex:]就能得到cell的数据

看到这里,上调试器!看下都是什么数据…

将设备用数据线连接上电脑(这里我推荐用数据线的方式,wifi延时太高,影响心情),手机上打开微信

直接输入issh debug -a wechat就能挂上微信

xia0 ~ $ issh debug -a wechat
[I]:iproxy process for 2222 port alive, pid=1382
[I]:++++++++++++++++++ Nice to Work :) +++++++++++++++++++++
[I]:iOSRE dir exist
[I]:iproxy process for 1234 port alive, pid=1395
[I]:Run ps -e | grep debugserver | grep -v grep; [[ 0 == 0 ]] && (killall -9 debugserver 2> /dev/null)
[I]:/iOSRE/tools/debugserver file exist, Start debug...
[I]:Run /iOSRE/tools/debugserver 127.0.0.1:1234 -a wechat

打开另一个终端进行调试(我的lldb已经安装了xia0LLDB脚本)

xia0 ~ $ lldb
"xutil" command installed -> xutil
"choose" command installed -> choose
"xbr" command installed --> xbr -[UIView initWithFrame:]
"sbt" command installed -> sbt
// 连接到远端
(lldb) pcc

这里有两种方法:

一种是用xbr "-[WCTimeLineViewController tableView:cellForRowAtIndexPath:]"下断点去查看;

// 对-[WCTimeLineViewController tableView:cellForRowAtIndexPath:]方法下断点
(lldb) xbr "-[WCTimeLineViewController tableView:cellForRowAtIndexPath:]"
(lldb) c

第二种是由于lldb支持choose命令,可以直接拿到WCFacade对象。既然如此选choose

(lldb) choose WCFacade
<__NSArrayM 0x2823d9860>(
<WCFacade: 0x139e1c030>
)

调用其getTimelineDataItemOfIndex:方法就能拿到第一条朋友圈的数据

(lldb) po [0x139e1c030 getTimelineDataItemOfIndex:0]
Class name: WCDataItem, addr: 0x13e2871d0
tid: 13121667995275040000
username: wxid_6913ohfkk7xxxx
createtime: 1564224719
commentUsers: (
)
contentObj: <WCContentItem: 0x2801f5500>

输入ivars 0x13e2871d0就能拿到对象的所有属性值

(lldb) ivars 0x13e2871d0
<WCDataItem: 0x13e2871d0>:
in WCDataItem:
	cid (int): 0
	tid (NSString*): @"13121667995275040000"
	type (int): 0
	flag (int): 0
	username (NSString*): @"wxid_6913ohfkk7xxxx"
	nickname (NSString*): @"xia0"
	createtime (int): 1564224719
	locationInfo (WCLocationInfo*): <WCLocationInfo: 0x2801f7800>
	likeFlag (BOOL): NO
	likeCount (int): 0
	likeUsers (NSMutableArray*): <__NSArrayM: 0x286152d90>
	commentCount (int): 0
	commentUsers (NSMutableArray*): <__NSArrayM: 0x2861539c0>
	contentObj (WCContentItem*): <WCContentItem: 0x2801f5500>
	appInfo (WCAppInfo*): <WCAppInfo: 0x287404080>
	contentDesc (NSString*): @"test"

由于属性太多,这里我就只显示了一些比较关心的数据,可以看到这就是我发的一条内容为test的朋友圈。

里面我们还发现了likeUserscommentUsers的字段,冷静思考就知道应该就是对应的赞和评论列表。我先给自己点个赞看下里面的数据。

likeCount (int): 1
likeUsers (NSMutableArray*): <__NSArrayM: 0x283e37060>

发现赞的数量变为1了,在看下里面的内容

(lldb) po 0x283e37060
<__NSArrayM 0x283e37060>(
Class name: WCUserComment
username: wxid_6913ohfkk7xxxx
nickname: xia0
content:
source: 0
type: 1
createTime: 1564225007
isLocalAdded: 0
commentID: (null)
comment64ID: (null)
refCommentID: (null)
refComment64ID: (null)
refUserName:
bDeleted: 0
)

正是我自己的微信号。同理可以得到评论

commentCount (int): 1
commentUsers (NSMutableArray*): <__NSArrayM: 0x283e349c0>
	
(lldb) po 0x283e349c0
<__NSArrayM 0x283e349c0>(
Class name: WCUserComment
username: wxid_6913ohfkk7xxxx
nickname: xia0
content: 评论测试
source: 0
type: 2
createTime: 1564225144
isLocalAdded: 1
commentID: (null)
comment64ID: (null)
refCommentID: (null)
refComment64ID: (null)
refUserName: (null)
bDeleted: 0
)

到这里我们还可以发现赞和评论都是一个类(模型),只是里面的类型字段不同。现在我们其实已经拿到了我们想要的数据模型了。但是还有一个问题在于我们应该什么时候去更改这些数据呢?也就是我们说的hook点。

最好的hook可以想到是每次刷新数据的时候,这样我们的数据就是最新的。

寻找HOOK点

先想一下,刷新数据的时候,当拿到新的数据肯定会封装为一个WCDataItem对象,那么我们可以对WCDataItem里面的方法下断点,然后打印调用链不就反向得到了刷新的函数了吗?

但是逆向和调试过微信的人都知道,当你使用bt命令的时候只能得到一堆无符号的调用栈像下面这样

(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 4.1
  * frame #0: 0x000000010857d4d0 WeChat`_mcwxh_dydx33_8to8(_VDecStruct*, unsigned char*, unsigned char*, unsigned int, unsigned int, unsigned int, unsigned int) + 30312888
    frame #1: 0x0000000194fcc638 Foundation`_decodeObjectBinary + 2004
    frame #2: 0x0000000194fcbb6c Foundation`_decodeObject + 340
    frame #3: 0x0000000194ed24fc Foundation`-[NSKeyedUnarchiver decodeObjectForKey:] + 228
    frame #4: 0x0000000194f2a09c Foundation`+[NSKeyedUnarchiver unarchiveObjectWithData:] + 92
    frame #5: 0x0000000105a88404 WeChat`int fmt::internal::CharTraits<char>::format_float<long double>(char*, unsigned long, char const*, unsigned int, int, long double) + 2432992
    frame #6: 0x0000000108da9ea8 WeChat`_mcwxh_dydx33_8to8(_VDecStruct*, unsigned char*, unsigned char*, unsigned int, unsigned int, unsigned int, unsigned int) + 38884240
    frame #7: 0x0000000108daa890 WeChat`_mcwxh_dydx33_8to8(_VDecStruct*, unsigned char*, unsigned char*, unsigned int, unsigned int, unsigned int, unsigned int) + 38886776
    frame #8: 0x0000000108dad178 WeChat`_mcwxh_dydx33_8to8(_VDecStruct*, unsigned char*, unsigned char*, unsigned int, unsigned int, unsigned int, unsigned int) + 38897248
    frame #9: 0x0000000108717394 WeChat`_mcwxh_dydx33_8to8(_VDecStruct*, unsigned char*, unsigned char*, unsigned int, unsigned int, unsigned int, unsigned int) + 31991932

但是用我写的sbt命令就能恢复oc符号,下面我们对-[WCDataItem setCid:]下断点,然后得到调用栈

(lldb) sbt
==========================================xia0LLDB==========================================
  BlockSymbolFile    Not Set The Block Symbol Json File, Try 'sbt -f'
============================================================================================
  frame #0: [file:0x103c094d0 mem:0x10857d4d0] WeChat`-[WCDataItem setCid:] + 0
  frame #1: [file:0x18193c638 mem:0x194fcc638] Foundation`_decodeObjectBinary + 2004
  frame #2: [file:0x18193bb6c mem:0x194fcbb6c] Foundation`_decodeObject + 340
  frame #3: [file:0x1818424fc mem:0x194ed24fc] Foundation`-[NSKeyedUnarchiver decodeObjectForKey:] + 228
  frame #4: [file:0x18189a09c mem:0x194f2a09c] Foundation`+[NSKeyedUnarchiver unarchiveObjectWithData:] + 92
  frame #5: [file:0x101114404 mem:0x105a88404] WeChat`+[CUtility SafeUnarchiveFromData:] + 64
  frame #6: [file:0x104435ea8 mem:0x108da9ea8] WeChat`-[WCAdvertiseDataHelper hasCommented:] + 116
  frame #7: [file:0x104436890 mem:0x108daa890] WeChat`-[WCAdvertiseDataHelper IsAdvertiseDataValid:] + 48
  frame #8: [file:0x104439178 mem:0x108dad178] WeChat`-[WCAdvertiseDataHelper getAdvertiseDataByCurMinTime:MaxTime:] + 552
  frame #9: [file:0x103da3394 mem:0x108717394] WeChat`Maybe c function? Distance:3348 >= 2500 # Symbol:-[WCTimelineMgr tryRemoveCachesOfLikeUserWithNewTimelineList:] + 3348
  frame #10: [file:0x206a0 mem:0x1131f06a0] WeChat`-[WCTimelineMgr onDataUpdated:andData:andAdData:withChangedTime:] + 233
  frame #11: [file:0x103db00c8 mem:0x1087240c8] WeChat`Maybe c function? Distance:9732 >= 2500 # Symbol:-[WCTimelineDataProvider responseForSnsTimeLineResponse:Event:] + 9732
  frame #12: [file:0x103db0398 mem:0x108724398] WeChat`-[WCTimelineDataProvider MessageReturn:Event:] + 112
  frame #13: [file:0x1033923c0 mem:0x107d063c0] WeChat`-[CAppObserverCenter NotifyFromMainCtrl:Event:] + 336
  frame #14: [file:0x104c292f8 mem:0x10959d2f8] WeChat`-[CMainControll TimerCheckEvent] + 728
  frame #15: [file:0x1800a3604 mem:0x193733604] libobjc.A.dylib`-[NSObject performSelector:withObject:] + 68
  frame #16: [file:0x101cb1fa8 mem:0x106625fa8] WeChat`-[MMNoRetainTimerTarget onNoRetainTimer:] + 84
  frame #17: [file:0x1819750bc mem:0x1950050bc] Foundation`__NSFireTimer + 88
  frame #18: [file:0x180e3d0a4 mem:0x1944cd0a4] CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__ + 32
  frame #19: [file:0x180e3cdd0 mem:0x1944ccdd0] CoreFoundation`__CFRunLoopDoTimer + 884
  frame #20: [file:0x180e3c5c4 mem:0x1944cc5c4] CoreFoundation`__CFRunLoopDoTimers + 252
  frame #21: [file:0x180e37284 mem:0x1944c7284] CoreFoundation`__CFRunLoopRun + 1832
  frame #22: [file:0x180e36844 mem:0x1944c6844] CoreFoundation`CFRunLoopRunSpecific + 452
  frame #23: [file:0x1830e5be8 mem:0x196775be8] GraphicsServices`GSEventRunModal + 104
  frame #24: [file:0x1ae78431c mem:0x1c1e1431c] UIKitCore`UIApplicationMain + 216
  frame #25: [file:0x100152b04 mem:0x104ac6b04] WeChat`main + 1387268
  frame #26: [file:0x1808ec020 mem:0x193f7c020] libdyld.dylib`start + 4

可以看到调用栈的符号已经恢复了,能够清晰的看出调用的过程

其中里面有个很明显的方法

-[WCTimelineMgr onDataUpdated:andData:andAdData:withChangedTime:]

看名字就知道,这个应该就是我们需要的hook点。

整理思路

整理一下目前的情况,首先拿到了赞和评论的模型,然后找到了hook点。下一步就是写代码去实现集赞的功能。

大概的代码逻辑应该如下

  • 在hook点的时候拿到原始的朋友圈数据,并过滤出自己的那条朋友圈
  • 取出自己朋友圈的赞和评论数据备用
  • 随机从通讯录好友里面选择数量去构造赞和评论对象,并放入原朋友圈赞和评论列表里面

下面就是写代码实现就可以了。还有个情况是在你进入自己的朋友圈详情界面的时候,也就是看到点赞的人都是头像的界面。也需要做类似的操作才能实现集赞的功能。

这里分析的过程和上面类似,我选择的hook点为:
-[WCCommentDetailViewControllerFB setDataItem:]

代码实现

具体的代码实现这里就不再去分析了,我把代码开源到了这里fkwechatzan

Bigboss源直接搜索fkwechatzan即可安装

完成效果

  • 集赞助手设置界面

  • 朋友圈详情界面

  • 赞和评论

一点总结

本文详细介绍了使用issh和xia0LLDB去完成一个集赞功能的逆向分析过程,这个功能本身不是很复杂,这里仅仅我在逆向过程中的一些理解和分析。每个人的逆向分析过程可能都不尽相同,我提供一个完整的分析步骤,而不是完全的去靠猜测,虽然逆向有时候猜测就能有一些意外惊喜,不过不确定性也同样会花费大量时间。

最后,妈妈再也不用担心没有人赞我的朋友圈了~

下次集赞领礼品的活动我要定了!

Posts: 34

Participants: 17

Read full topic

论坛月经贴:砸壳——支持 Windows,无需 SSH 的砸壳他不香吗

$
0
0

@ChiChou wrote:

各种砸壳失败的帖子实在是太蛋疼了,有坚持用 old school 的 DYLD_ 模块注入的,有 python 环境折腾不对的……

来试试这个?

  1. Windows 需要专门安装 iTunes 以支持 USB 连接 iOS。Store 版本的 iTunes 似乎不稳定,最好在官网下独立安装包版的
  2. 下载最新的 Node.js LTS(长期支持版,也就是左边那个安装包)https://nodejs.org/

使用 Latest release 可能导致找不到匹配的二进制包

  1. npm install -g bagbak 安装砸壳命令工具。如有异常最好上代理
  2. USB 连接越狱并装了 frida 的 iOS 设备
  3. bagbak [App 名字或者 bundle id]

搞定

录屏演示:http://t.cn/A6vUKQnt

以上适用于 macOS 和 Windows。Linux 暂未真机测试,求小白鼠

一些提醒:

  1. 如果不知道目标应用的 bundle id,可以输入 bagbak -l 列出全部 App 的信息
  2. bagbak -H 参数可以使用另一种非 USB 的方式,即 tcp 连接。不建议这么做,这样相当于开了一个没有密码的 SSH 在内网
  3. 在 Windows 下砸壳会丢失原始安装包的许多文件属性(修改时间、可执行属性等),这是由于 NTFS 和 iOS 的文件系统不兼容导致的。如果使用 Windows 下砸壳再压缩成 ipa,可能会无法重打包安装——但是想想,重打包这件事本来就离不开 mac 的工具链。

项目地址和 Bug 反馈:https://github.com/ChiChou/bagbak

Posts: 1

Participants: 1

Read full topic

写个小玩意,查看App可执行文件路径。

$
0
0

@nu11 wrote:

Ios12 app目录怎么一一对应的? 这个帖子里问怎么对应的。中间一串字母烦得很。
其实就是个命令行工具,用nic.pl 新建
[10.] iphone/tool
名字叫 lsapp
修改文件main.mm:

#import <objc/runtime.h>
#include <dlfcn.h>
int main(int argc, char **argv, char **envp) {
	dlopen("/System/Library/PrivateFrameworks/ScreenshotServices.framework/ScreenshotServices", RTLD_LAZY); 
    Class LSApplicationWorkspace_class = objc_getClass("LSApplicationWorkspace");
    NSObject* workspace = [LSApplicationWorkspace_class performSelector:@selector(defaultWorkspace)];
    NSArray*apps = [workspace performSelector:@selector(allApplications)]; // LSApplicationProxy
    for (id obj in apps) { //obj is LSApplicationProxy
        NSString * applicationIdentifier = [obj performSelector:@selector(applicationIdentifier)];
        NSString * localizedName = [obj performSelector:@selector(localizedName)];
        NSString * canonicalExecutablePath = [obj performSelector:@selector(canonicalExecutablePath)];
        printf("%s(%s)===>%s\n",localizedName.UTF8String,applicationIdentifier.UTF8String,canonicalExecutablePath.UTF8String);
    }
	return 0;
}

make clean && make && make package && make install安装到bin目录,直接
lsapp运行即可

运行截图:

Posts: 2

Participants: 2

Read full topic

Frida日志收集查看工具 FridaNSLogger

$
0
0

@tbag wrote:

FridaNSLogger

FridaNSLogger可以在Frida中将日志信息通过socket连接发送至Mac端查看。
Mac端日志查看工具 FridaNSLoggerViewer 基于 NSLogger 修改实现。

项目地址

特点

  • 可以在Frida TypeScript代码中直接发送日志消息;
  • 支持 string 和 binary 类型日志消息;
  • 支持简单的断线重连;
  • 完备的Mac端日志查看器FridaNSLoggerViewer(支持日志分级,过滤,保存等);

快速使用

  1. 在Mac端启动日志查看器FridaNSLoggerViewer,默认监听 127.0.0.1:50010 ,并获取该Mac系统内网IP(比如192.168.2.10)

  2. 在Frida TypeScript工程中引用:

import { Logger } from "./logger";
import { swapInt64 } from "./logger";

// 连接到局域网内的FridaNSLoggerViewer,注意修改IP。
// 如果Frida脚本
const logger = new Logger('192.168.2.10', 50010);
logger.logStr('helloworld'); //发送string类型日志

const testS64 = new Int64('0x0102030405060708');
const testBuf = Memory.alloc(8).writeS64( swapInt64(testS64) ).readByteArray(8);
logger.logBinary(testBuf as ArrayBuffer); //发送binary类型日志

FridaNSLoggerViewer 效果如下图:

原理

Frida脚步内作为client,利用Frida的 SocketConnection 接口,将日志编码后发送;
FridaNSLoggerViewer作为socket服务端,可监听局域网内多个client发来的连接。NSLogger原有实现需要加密后的socket数据,FridaNSLoggerViewer对其修改,去掉了加密,支持 raw tcp packet.

新加入的client默认第一条消息发送设备信息,包含Frida版本,系统版本等信息。后续每条日志打包为一个LogMessage发送。

NSLogger接收的单个二进制数据包格式为:

uint32_t    totalSize        //(total size for the whole message excluding this 4-byte count)
uint16_t    partCount        //(number of parts below)
[repeat partCount times]:
    uint8_t        partKey        //the part key
    uint8_t        partType    //(string, binary, image, int16, int32, int64)
    uint32_t    partSize    //(only for string, binary and image types, others are implicit)
    .. `partSize' data bytes

举例:
一个LogMessage的数据包拆分如下:

00000073 //totalSize,占4byte。数值为整个包的字节数减去4,即后续部分长度
000a //0xa=10 parts,2byte,有多少个parts
0104 00000000 5e13fedb //01=PART_KEY_TIMESTAMP_S, 04=PART_TYPE_INT64
0304 00000000 00011402 //03=PART_KEY_TIMESTAMP_US
0400 00000008 54687265 61642036  //PART_KEY_THREAD_ID   
0003 00000003 // PART_KEY_MESSAGE_TYPE  PART_TYPE_INT32 
1500 00000001 31 //0x15=21,PART_KEY_CLIENT_VERSION
1400 0000000f 4e534c6f 67676572 54657374 417070 // 0x14=20,PART_KEY_CLIENT_NAME 
1900 00000008 6950686f 6e652058 //0x19=25=PART_KEY_UNIQUEID
1700 00000004 31322e32 //0x17=23=PART_KEY_OS_VERSION
1600 00000003 694f53 //0x16=22=PART_KEY_OS_NAME
1800 00000006 6950686f6e65 //0x18=24=PART_KEY_CLIENT_MODEL

(完)

Posts: 1

Participants: 1

Read full topic

SpringBoard tweak 双击图标启动debugserver

$
0
0

@nu11 wrote:

0x00 懒是第一生产力

狗神的书上和帖子里都有写如何配置debugserver。配置完之后用起来还是有些麻烦,至少要开两个终端窗口,一个手机端的开启debugserver,另外一个开启lldb。在手机端的shell,需要先ssh登录,然后各种ls+grep找到要调试的app,然后敲debugserver xxx 把各种参数配置好。 https://github.com/4ch12dy/issh 对上述操作有封装和优化,但是还是需要敲命令找App,运行debugServer。所以做个tweak提升一下生产力。双击应用图标,一键启动debugserver。 代码zip包和运行截图在本文末尾。

我的开发环境是iOS13.3,但是并没有用到特殊版本的API,低版本手机应该也OK。

0x01 通过图标找到应用执行路径

从界面找逻辑,逆向发现SpringBoard的图标是SBIconView。并且有一个叫属性 applicationBundleIdentifierForShortcuts 返回的是图标对应的App的bundle id。通过bundle id构造LSApplicationProxy对象,并且拿到canonicalExecutablePath属性,也就是应用的可执行文件路径。

Class LSApplicationProxy_class = objc_getClass("LSApplicationProxy");
NSObject* proxyObj = [LSApplicationProxy_class performSelector:@selector(applicationProxyForIdentifier:) withObject:bundle];
NSString * canonicalExecutablePath = [proxyObj performSelector:@selector(canonicalExecutablePath)];

0x02 寻找注入点添加扩展

接续看SBIconView,图标上有两个手势对象:

  • 单击,用来启动App。
  • 长按,进入编辑状态,执行删除和排列图标等操作。

所以,我们来给图标交互加个双击扩展。

%hook SBIconView

- (void)didMoveToWindow
{
	%orig;
	UITapGestureRecognizer *doubleTap = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(handleDoubleClick:)];
	[doubleTap setNumberOfTapsRequired:2];
	[self addGestureRecognizer:doubleTap];
	NSArray * ges = self.gestureRecognizers;
	for(UITapGestureRecognizer * each in ges){
		if([each isKindOfClass:[UITapGestureRecognizer class]]){
			[each requireGestureRecognizerToFail: doubleTap];
		}
	}
}

这里额外说一句 [each requireGestureRecognizerToFail: doubleTap] 添加了双击手势指挥,由于iOS内部维护了手势的状态机,我们单击操作发生的时候,其实产生了两种Possible State。第一种是识别为单击,然后结束。第二种是识别为双击的第一下并等待第二下的发生,然后根据两次点击之时间间隔阈值来判断是不是合法的双击。所以我们手动加个约束,相当于指定了识别的优先级,只有双击失败了,才继续执行单击回调。这种操作会带来一点几乎无感的瑕疵:单击等待双击识别失败的延迟,延迟的值就是双击识别执行的阈值(大约零点几秒)。

0x03 debugserver启动和关闭

debugserver是一个二进制文件,狗神的教程里有如何重签,issh把这些过程给简化了。先看一下debugserver的权限:
-rwxr-xr-x 1 root admin 9876848 Jan 19 11:28 /iOSRE/tools/debugserver*

再来看一下SpringBoard的权限:
-rwxr-xr-x 1 root wheel 71264 Dec 5 13:15 SpringBoard*

属主用户都是root,没毛病。找个函数调用一下:

  1. system函数
  2. posix_spawn函数
  3. NSTask ,面向对象方便管理,异步执行,不会block UI,就用它了。

代码

task = [[NSTask alloc]init];
[task setLaunchPath:bin_serverpath];
[task setArguments:args];
[task launch];

每次server在launch之前,要把之前的task结束掉。

- (void)interrupt; // Not always possible. Sends SIGINT.

- (void)terminate; // Not always possible. Sends SIGTERM.

NSTask头文件里竟然告诉我 Not always possible。事实上我调用的时候,还真的不怎么possible,实际测试第一次server正常启动,后续由于没成功关闭,所以第二次就没法启动了。所以还是换种方式关闭吧。简单粗暴的 kill 函数:

NSTask * task = [TaskManager sharedManager].runningTask;
if(task){
    kill(task.processIdentifier,SIGKILL);
    task = nil;
}

0x04 添加UI交互

直接用Alert,又有按钮又有输入框,不过UIAlertView已经被废弃掉了,需要用UIAlertController。由于弹出Controller需要父Controller,通过View找到当前的Controller,做正向的应该都写过这段代码吧。。

@implementation UIView(find)
-(UIViewController*)findViewController
{
    UIResponder* target= self;
    while (target) {
        target = target.nextResponder;
        if ([target isKindOfClass:[UIViewController class]]) {
            break;
        }
    }
    return (UIViewController*)target;
}
@end

0x05 优化一下用户体验
输入框里的ip和debugserver的path,每个人都不一样,所以在第一次输入完成之后,把这些值用NSUserDefault持久化存储起来,下次直接读取填充。

0x06后记

之前看坛子里一些帖子讨论用Root身份运行App的帖子,学习完帖子里的技巧,增强对操作系统的理解以及实践之后。如果真的想RootApp运行,其实SpringBoard本身就是一个RootApp,我们吧SpringBoard当做RootViewController,很容易把一些系统工具做出界面,从而提升生产力。比如砸壳,重签,拷贝App等。

上代码
tap2debug.zip (53.5 KB)

Posts: 5

Participants: 4

Read full topic


记一次桌面歌词插件开发

$
0
0

@Lakr233 wrote:

晚上没睡觉 过几天随缘更新创作心路历程

设置里面的那个选项目前还没有实现,所以就是可以理解为开箱即用没有设置

已知问题:由于网易云不知道干了什么会导致启动的时候把第二首歌的歌词载入缓存导致识别错误,虽然通过代码已经尽我所能的避免这个问题了但是如果遇到了这个问题的话,切一下歌曲吧,我不知道怎么修太菜了

附上屏幕截图一张

// 就知道没人用传错包了都没人发现
wiki.qaq.NMLrc_1.1_iphoneos-arm.deb (173.8 KB)

记一次桌面歌词插件开发

这个坑,其实挺早就想着是不是改做了,今天来记录一下实现的方法。首先是思路。我们hook网易设置歌词的方法,推送数据给SpringBoard,再在SpringBoard上面绘制歌词窗口。

那么首先是获取ipa用于分析。使用Frida-iOS-dump可以轻松的提取砸壳完成的网易云音乐。由于我们开发的插件并不需要很多调试内容,选择MonkeyDev创建logos语法的hook项目即可。创建MonkeyApp调试App会带来很多额外的问题,比如不能登录之类的App自身限制。

砸壳以后,找到位于Payload内的Executable文件放入Hopper开始分析。与此同时,我们可以使用FLEX插件来仔细看看网易云歌词的界面实现。盲猜是一个TableView。FLEXing插件位于BigBoss源内,通过长按状态栏实现调用。点击Select的小箭头再点击歌词界面,可以看到用于显示歌词的UILabel。打开Views选项,便能看到一个叫做NMLyricCell的Class。这个名字是我们在Hopper/IDA中需要重点关注的对象。

打开Hopper,检查这个对象。我们发现了一个方法,名字叫做setLyric。这个名字既然在这里,那么很大的可能开发人员在其他地方也会使用这个名字。比如说在更新歌词的时候会Call一个Wrapper,他的名字极有可能也是setLyric。不妨搜索看看?

果然,这是一个通用的方法名。

这里有两个地方引起了我的注意,第一个是类名字:NMLyricObject,第二个还是类的名字:NMPlayerManager。第二个类名引起我的注意是因为一个叫做setLyricsArray的方法。很有可能,setLyricsArray是NMPlayerManager类自动生成的Setter。不过这个无所谓了,反正都是要hook哒!

接下来我猜在播放器这个shared类里面会有一个歌词高亮的wrapper,比如setLyricIndex之类的。在搜索“NMPlayerManager index”之后,果然找到了一个方法:

-[NMPlayerManager setHighlightedLyricIndex:]

同时还有 -[NMPlayerManager highlightedLyricIndex]。这说明HighlightedLyricIndex很有可能是NMPlayerManager的一个属性。

于是我们有了思路:

  • Hook setHighlightedLyricIndex 来获取应该显示的歌词的位置
  • Hook NMPlayerManager 任意方法来缓存一个self指针
  • 通过obj-C runtime和缓存的self获取歌词内容
  • 通过特定方法将数据发送给SpringBoard
  • 在SpringBoard上绘制窗口更新歌词
  • 当歌词超过6秒没有更新,隐藏绘制

上代码!

这里,由于网易云初始化的缓存包含两首歌的歌词,就很**。通过上述代码可以规避这个问题,但是觉得很不安全。如果有更好的点子欢迎留言。其中注意,setLyricsArray会设置一个NMLyricObject的Array,并非直接储存NSString。这样一来我们需要使用obj-C runtime去取这个歌词的内容。你可以缓存这个内容,也可以通过NMPlayerManager的属性取出这个Array。

// 导出水果私有方法 objc_retainAutoreleaseReturnValue
// 真的不知道为什么不是 objc_retainAutoReleaseReturnValue
OBJC_EXPORT id objc_retainAutoreleaseReturnValue(id obj) __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0);

// 获取歌词数组 
    SEL _sel1 = NSSelectorFromString(@"lyricsArray");
    id _voucher1 = objc_msgSend(manager, _sel1); // NMPlayerManager*
    NSArray *lrcOA = objc_retainAutoreleaseReturnValue(_voucher1);
    id targetObject = [lrcOA objectAtIndex: index];
// 获取原歌词
    SEL _sel2 = NSSelectorFromString(@"lyric");
    id _voucher2 = objc_msgSend(targetObject, _sel2);
    NSString *_lrc = objc_retainAutoreleaseReturnValue(_voucher2);
// 获取翻译词
    SEL _sel3 = NSSelectorFromString(@"translatedLyric");
    id _voucher3 = objc_msgSend(targetObject, _sel3);
    NSString *_lrctrnd = objc_retainAutoreleaseReturnValue(_voucher3);

接下来就需要把歌词数据发送给SpringBoard。由于进程间通讯非常麻烦,UIPasteBoard不知道为啥被block了获取不到数据,我决定使用GCDWebSocket进行操作。:man_shrugging:

这个就挺好玩,去SpringBoard开一个服务器😂

不过在这之前,我们先把这个桌面歌词的绘制处理一下。

接下来开端口,处理更新。划重点,UI更新必须在主线程,不然会崩掉SpringBoard。

那我们respond写什么呢?嘿嘿

        return [GCDWebServerDataResponse responseWithHTML:@"花Q"];

至此,插件编写已经完成了。如果遇到undefinedSymbols的话去添加一下框架,也可以动态查找。下面这张图是手动链接需要的Framework,有一些是GCDWebServer需要使用的。

项目地址: https://lab.qaq.wiki/Lakr233/neteasemusiclrcs

此致,全体起立!

2019年冬 -> 2020年春
https://www.qaq.wiki/?p=621

Posts: 4

Participants: 4

Read full topic

iOS 越狱史及技术简析 - iPhone OS 2 (iOS 2)

$
0
0

@geniusumi wrote:

iOS 越狱史及技术简析 - iOS1
GitHub 仓库

前言

在整理这篇文章的时候,我一直在犹豫要不要写上这个版本更新的安全机制,因为实际上真正的越狱利用部分与他们并没有关系,而因为是刚刚加入系统中,后续对他们的收尾工作并不复杂,但思索再三,还是将他们写下来了,原因无他:只有一步一步追溯苹果的更新脚步,才能理解在后续版本中他们做的安全措施。

回顾

在上一篇文章中,我们已经提到了苹果对 iOS 1 的安全保护正在一步一步进化中。截止 iOS 1.1.5,从上至下层级排列,iOS 的安全措施如下

名称 出现版本 说明
Secure Boot iOS 1.0 启动链的每个环节都会负责验证下一阶段的签名
fsroot 只读 iOS 1.0 系统盘 /dev/disk0s1 默认是只读的
AFC Restriction iOS 1.0 iPhone 通过 AFC 服务与电脑进行数据交换,这个服务默认只允许宿主机访问/Media目录
固件加密 iOS 1.1 IMG2格式的系统固件通过Key 0x837被加密
mobile 用户 iOS 1.1.3 在此之前,所用应用程序均使用root权限运行

应该说,这个阶段的越狱并没有什么难度。越狱社区正处于萌芽状态,尽管并没有原生的中文支持,威锋社区已经通过修改内核文件的方法让 iPhone 成功支持了中文输入法与中文短信的显示。

随后,2008 年 7 月 1 日, 苹果推出了 iOS 2 更新,同时公布了 iPhone 3G。 iOS 2 预装在 iPhone 3G 上,iPhone 1代也可以升级到 iOS 2。

代码签名与沙盒

这次更新之后,App Store 开始内置在 iPhone 内,这意味着用户可以自己下载并安装应用程序了。相对应的,iOS 新增了以下安全机制:

  • 强制代码签名
    • 所有可执行程序、库、甚至内存中的代码,都必须经过苹果签名才能运行
  • 应用程序沙盒
    • 应用程序默认情况下运行在沙盒中,限制其所能访问的资源(包括但不限于系统调用,文件等)

上述两个安全机制都被注册到 TrustedBSD 中的 Mandatory Access Control 扩展(MACF)里。TrustedBSD 是一个内核扩展程序的集合,从 Mac OS X 10.5(2007 年 10 月 26 日发布)开始,苹果将整个 TrustedBSD 都移植到了 XNU 内核中,所以 Mac OS X 和 iOS 也自然拥有了 MACF。

MACF 的架构大致如下:

    ^
    |
    |
 User Space
+------------------------------------------------------------+
|Ker|nel Space                                               |
|   |     +----------+            +-----------------------+  |
|   |     |          |            |                       |  |
|   |     |  MACF    |------------>     +-----------+     |  |
|   v     |          |            |     |  Policy 1 |     |  |
|         +----------+            |     +----+------+     |  |
|    Kernel   |  Access Event     |          |            |  |
|             v                   |          v            |  |
|    +--------+---------------+   |     +----+------+     |  |
|    |  Kernel Implmentation  |   |     |  Policy 2 |     |  |
|    +------------------------+   |     +----+------+     |  |
|    |    Kernel Object       |   |          |            |  |
|    +------------------------+   |          |            |  |
|             |                   |          |            |  |
|             |                   |          |            |  |
|    +--------v---------------+   |     +----v------+     |  |
|    |  File System, Network  |   |     |  Policy N |     |  |
|    +------------------------+   |     +-----------+     |  |
|                                 |                       |  |
|                                 +-----------------------+  |
|                                                            |
+------------------------------------------------------------+

安全策略可以编译时静态链接到内核,也可以在引导时加载,甚至在运行时动态加载。在主体访问客体时,MACF 会调用所有的安全策略,只有当所有的安全策略均表示同意,MACF 才会授权这次访问。

    +--------------------+      +-------------------+
    | User mode process  |      |  User Mode Daemon |
    +---------|-^--------+      +---------^---------+
+-------------|-|--------------------------------------------------+
              | |                         |
    +---------v-+--------+                |
    | sysent/mach_trap_tb|                |
    +---------|-^--------+           +----+---+
              | |                    | Policy |
    +---------v-+--------+     +----->        |
    |  syscall/trap #n   |     |     | Module |
    +---------|-^--------+     |     +--------+
              | |              |
    +---------v-+--------+     |
    |     M  A  C  F     |-----
    +--------------------+

在具体实现中,每次 MACF 均会检查是否有策略 hook 了 sycall/mach trap,如果是,那么就会拉起这个策略,由该策略判断允许还是阻止继续执行。

代码签名 (AppleMobileFileIntegrity)

从上述 MACF 架构中我们知道,要想在 iOS 实现代码签名机制,就需要自己提供一个验证代码签名的策略,然后注册相关 hook,这个工作由AppleMobileFileIntegrity.kext完成。它既不防止权限提升也不阻止未授权访问资源,它的工作仅限于保证文件的完整性(Integrity)和认证性(Authentication)。

由于代码签名验证的逻辑非常复杂,一般情况下不适合在内核态运行,所以 AMFI 由两部分组成,用户态的daemon - /usr/libexec/amfid 和内核态的AppleMobileFileIntegrity.kext组成。后者会在内核一初始化完成就注册到 MACF 的策略,如果初始化注册时出错,就会导致 kernel panic。而在初始化完成后想 unload 这个 kext,则会报错 – “Cannot unload AMFI - policy is not dynamic”,然后接着给你来个 panic。与之相反,前者可以说是 AMFI 的阿克琉斯之踵,几乎在之后版本的所有越狱中都会拿这个下手绕过代码签名。

沙盒

与 AMFI 相同,沙盒也由一个用户态/usr/libexec/sandboxd和一个kextSandbox.kext组成。在初始版本中(Sandbox<=165, iOS 1 ~ iOS 4),沙盒机制是黑名单制的,这就意味着这个机制很容易被通过“白加黑”的手段绕过。所以苹果在 iOS 5 的时候重做了这个机制,重命名为"App Sandbox",引入了容器(Container)的概念。

正文

Pwnage

** 设备版本 ** : iPhone, iPod Touch, iPhone 3G (全版本)
** 利用软件 ** : Pwnage Tool, QuickPwn, WinPwn

第一个 bootrom 级别的漏洞。

首先,让我们回顾一下当时 iPhone 的两条启动链:

  • 正常启动,bootrom 检查 LLB 签名并加载 LLB, LLB 检查 iBoot 签名并加载 iBoot, iBoot 检查内核签名并加载设备树和内核,内核完成最后的启动。
  • DFU 模式下,bootrom 验证 WTF 的签名并加载 WTF (What’s The Firmware),WTF 加载 iBSS 签名并读取 iBSS,iBSS 加载设备树、 ramdisk 和内核,验证内核签名。

看起来很美,问题出在哪呢?让我们一条一条梳理:

  • 在 iOS 1 时代,所有系统关键文件(如iBoot/iBEC/iBSS)都被用8900格式进行打包,里面含有签名与文件本身的 img2 数据。从 iOS 2 开始,苹果不再采用 8900 格式,但 WTF 映像文件除外
  • 早期设备中,系统与用户文件储存在 NAND ,引导相关文件存放在 NOR
  • 将文件写入 NOR 需要通过 ramdisk 中的 AppleImage3NORAccess.kext,这个 kext 需要验证文件的签名

因为第三点,苹果认为没必要去验证 NOR 中文件的签名,因为他们已经被 kext 所代劳了,所以 bootrom 在正常启动时不会验证 LLB 的签名,然而,bootrom 还是会验证 WTF 的签名。由于 WTF 仍是 8900 格式,pwnage 通过构造特定的 8900 签名,导致 bootrom 在签名验证时栈溢出,从而永远返回验证成功来绕过 bootrom 的签名验证。接下来的步骤,由于可信启动链的破坏,就比较简单了。

首先,构造一个固件文件:

  • 修改 WTF 签名,并将 WTF 中对下一阶段的签名验证关闭
  • 修改 iBSS,iBEC, iBoot, LLB,关闭所有签名验证
  • 关闭 ramdisk 中文件完整性校验,修改fstab
  • 关闭代码签名(AMFI.kext),关闭AppleImage3NORAccess.kext的代码签名验证
  • 给设备树打补丁,让 AppleImage3NORAccess.kext 能获取到 Key 0x837,并用这个 Key 加密文件并写入 NOR 中

最后模拟 iTunes 让 iPhone 进入 DFU 模式后加载自制 WTF 文件就可以完成后续操作了,由于大同小异(增加afc2,安装包管理软件等),这里不再赘述。

值得注意的两点是:第一,Cydia.app 和 Installer.app 一起,在这里被正式加入越狱预装应用的行列;第二,没有找到有关绕过沙盒的资料,所以猜想这个机制应该一越狱就被关掉了,而且没有保护机制。

ARM7 Go

** 设备版本 ** : iPod Touch 2
** 利用软件 ** : QuickPwn, RedSn0w Lite

iBoot 级别的漏洞。苹果在 iPod Touch 2 的第一个 iBoot 的版本里忘记把调试代码删了,留下了arm7_goarm7_stop,前者可以从任意地址执行未签名代码。

于是我们还是照葫芦画瓢,DIY 固件文件:

  • 修改 WTF 签名,并将 WTF 中对下一阶段的签名验证关闭
  • 修改 iBSS,iBEC, LLB,关闭所有签名验证
  • 修改 fsroot 的 fstab 和 Services.plist,新增afc2,把 cydia 加进去
  • 对于 iBoot:第一步,关闭签名验证。 第二,在上一篇文章中我们已经提到,iBoot 在更新后不再处理大部分boot-args,除非设备处于调试模式,所以我们要把判断调试模式的语句打个补丁,让更多的flag可用。处理器内部有一个安全机制保证只有一部分内存段才能执行指令,所以最后一步是移除掉可执行指令内存的限制。

之后,等到设备进入 DFU 模式,RedSn0w Lite 会向设备发送最重要的一段命令:

arm7_stop
mw 0x9000000 0xe59f3014
mw 0x9000004 0xe3a02a02
mw 0x9000008 0xe1c320b0
mw 0x900000c 0xe3e02000
mw 0x9000010 0xe2833c9d
mw 0x9000014 0xe58326c0
mw 0x9000018 0xeafffffe
mw 0x900001c 0x2200f300
arm7_go
arm7_stop

mw $1,$2 会将 $2 写入到 $1 所在的地址中,反汇编,我们得到这样一小段指令

asm 
; e5 9f 30 14 e3 a0 2a 02 e1 c3 20 b0 e3 e0 20 00 e2 83 3c 9d e5 83 26 c0 ea ff ff fe 22 00 f3 00 
; Segment type: Pure code 
                    AREA ROM, CODE, READWRITE, ALIGN=0 
                    ; ORG 0x9000000 
                    CODE32 
                    LDR R3, =0x2200F300 
                    MOV R2, #0x2000 
                    STRH R2, [R3] 
                    MOV R2, #0xFFFFFFFF 
                    ADD R3, R3, #0x9D00 
                    STR R2, [R3,#0x6C0] 
loc_9000018 ; CODE XREF: ROM:loc_9000018 
                    B loc_9000018 
; --------------------------------------------------------------------------- 
dword_900001C DCD 0x2200F300 ; DATA XREF: ROM:09000000 
; ROM ends 
END

这段代码做了一点微小的工作:首先,将 0x2000 写入 0x2200F300,关闭签名检测;其次,将 0xFFFFFFFF 写入 0x220196C0,这个地方是flag的值。如果flag=-1,那么 iBoot 会认为设备处于调试模式,关闭所有其他的限制。

之后,我们将事先准备好的固件文件一个一个发送过去,然后,设置boot-path,从我们准备好的内核直接启动!

bat

echo "[xx] Sending patched iBSS 2.2.1..."
sudo ./iRecovery -f ${files}/iBSS221pwn.dfu 
echo "[xx] Go into recovery mode..."
sudo ./iRecovery -s << EOF
go
/exit
EOF

# REPLUG HERE?
## echo PLEASE UNPLUG/REPLUG THE IPOD
## read A
sleep 5

echo "[xx] Sending semi-tethered iBoot 2.2.1.."
sudo ./iRecovery -f ${files}/iBoot221semi.img3

echo "[xx] Go into recovery mode..."
sudo ./iRecovery -s << EOF
go
/exit
EOF

sleep 5
echo "[xx] Configuring environment"
sudo ./iRecovery -s << EOF
setenv boot-path /System/Library/Caches/com.apple.kernelcaches/kernelcache.s5l8720p
fsboot
/exit
EOF

sleep 5
echo "[xx] Here we go!"
sudo ./iRecovery -s << EOF
go
/exit
EOF

完整脚本
需要注意的一点是,由于我们修改了 iBoot 文件,在重启之后 bootrom 会校验 iBoot 签名,显然这会导致失败,回落到 DFU。所以每次我们重启设备,都需要插上电脑继续进行引导。

总结

至此,我们已经看完了 iOS 2 时代所有的漏洞利用和更新的安全机制。两位门神 AMFI 和 Sandbox 的加入也开始让 iOS 的安全走上了正轨,我们所耳熟能详的 Cydia 作者 Saurik 已经发布了这个灰色小盒子的第一个版本,获得了社区的认可。此时 AMFI 和 Sandbox 还非常地脆弱,可以看到,工具甚至都没有直接利用他们的漏洞,这就好比苹果更新了两把锁,结果外面的人砸开门之后拿了一把电锯进来,完全无视,一路碾压。那要怎么办呢?聪明的读者应该想到了:不仅加固门,甚至你进门了,一根针也带不进来。

应该说,只有第一个漏洞是正常开拓了一个新的时代的。由此,越狱社区开始重视 iOS 的 bootrom 相关安全研究。

后记

这篇东西说长也不算长,但坑了很久,一个原因是因为 Dota 太好玩了,另外一个原因是由于笔者水平不足,难免需要多查一些资料保证准确性,同时也在思考文章的架构需要怎么写才能更合理。如果你有什么意见,欢迎向我提出。

Posts: 2

Participants: 2

Read full topic

一张图看懂UAF系统漏洞

$
0
0

@Lakr233 wrote:

看得懂就看得懂,看不懂就看不懂。我解释不来,你也学不会。哈哈哈哈哈哈今天我也要做个标题党。

下面开始上课。两个结构体,一个包含另一个的指针。

struct vipInfo {
    int level;  // 等级
    int time;   // 剩余天数
};

struct vipObject {
    int fd;
    bool activated;
    struct vipInfo* info;
};

在学习了内核信息安全编程思想以后,王五决定使用类似句柄的方式对veryImportantPerson进行查表储存和有限管理。因此他创建了一张嘉宾表还有几个管理嘉宾的函数。其中,创建读取还有反激活可以在用户态调用。

struct vipObject* vipObjectList[VIPMAXCOUNT];

int createVipObject();
int readVipLevel(int fd);
void setVipLevel(int fd, int level);
void setVipTime(int fd, int time);
void deactivateVip(int fd);
void reactiveVip(int fd);
void releaseVipObject(int fd);

都是假设【狗头.hevc】

但是粗心的张三(嗯不是王五么)在写deactivateVip的时候写了一个内核信息不安全的代码。聪明的你一定知道发生了什么。在free完成以后,这个vipObject仍然对info持有一个被释放的指针。我们也许可以叫他悬垂指针。

void deactivateVip(int fd) {
    if (vipObjectList[fd] == nullptr)
        return;
    vipObjectList[fd]->activated = false;
    // 如果到期了或者压根没时间那就给他释放一下吧 应该没人会这么做
    if (vipObjectList[fd]->info != nullptr && vipObjectList[fd]->info->time < 1)
        free(vipObjectList[fd]->info);
}

指向曾经存在的对象,但该对象已经不再存在了。// 说的好像我有过一样

在这样的情况下,如果我们立刻申请分配一块与vipInfo大小相等的内存,在系统最佳优化神队友的操作下,我们很有可能会重新分配到同一块内存,也就是刚释放的内存。这有什么用呢不就是在自己这里写来写去么?别急,这是个演示。如果说这段代码存在于内核,那么内核用他来写来写去不就是个100w的漏洞了么?上代码!

int fuck = createVipObject();
    while (true) {
        deactivateVip(fuck);
        printf("UAF pointer: 0x%p - ", (void*)vipObjectList[fuck]->info);
        struct vipInfo* fakeInfo = (struct vipInfo*)malloc(sizeof(vipInfo));
        printf("new pointer: 0x%p\n", (void*)fakeInfo);
        fakeInfo->level = 999; fakeInfo->time = 2333;
        if (readVipLevel(fuck) == 999)
            break;
        free(fakeInfo);
        reactiveVip(fuck);
    }

由于我们的代码在自己这里,单一线程单一进程同一内存空间所以很容易的,就能分配到相同的内存。

    // round 1 -> UAF pointer: 0x0x1007abd00 - new pointer: 0x0x1007abd00

至此,这就是一个UAF漏洞的简单实现。至于内核漏洞,如何在内核地址段中分配内存,如何在ipc地址段中分配内存,如何诱导系统作出我们想要的举动来分配我们想要的内存,这些是难题,也是成为大神路上还要修炼的点点滴滴。

2020年春

后记是一张图,标题党的实际行动:

还要感谢 “Soulghost 高级页面仔” 的公众号文章 能把事情讲得这么清楚。这位同城的老哥有空出来聚聚?

https://www.qaq.wiki/?p=641

Posts: 3

Participants: 2

Read full topic

尝试让这个世界对懒人更友好一点 [VS插件分享]

$
0
0

@Lakr233 wrote:

由于厌倦了繁琐的终端操作重制ssh主机指纹还有各种奇葩的问题操作
由于看到🐟总的插件创意非常棒(但是那是private的

我觉得自己写一个vs插件会是很棒的选择。
两天时间从头开始学习了py和js/ts,现学现卖,还请包含诸多代码问题和格式丑。
插件功能全部都以GUI的形式展现,没有配置选项,会保存设备设置。
插件有一些依赖需要自己安装,具体请看readme,需要使用下面的命令安装最新版的依赖。

brew uninstall --ignore-dependencies libimobiledevice usbmuxd
brew install -v --HEAD --build-from-source usbmuxd libimobiledevice

发布地址和issue tracker:https://github.com/Co2333/iOSreExtension
内测地址(不包含issue tracker因为我没开注册):https://lab.qaq.wiki/Lakr233/iOSreExtension

由于vsc发布时出于安全原因会过滤掉一些东西导致包不完整故暂不考虑上架vsc,如果有哥们比较有经验的话可以论坛私信咯!

pr/issue/requirement are welcome

最近几天会集成设备远程桌面还有插件项目模版创建,如果有资深图像大佬欢迎带着代码联系(没错我就是想白嫖)。

2020年春

Posts: 1

Participants: 1

Read full topic

lldb快速打印Objective-C方法中block参数的签名

$
0
0

@everettjf wrote:

iOS逆向时经常会遇到参数为block类型,本文介绍一个lldb script,可快速打印出Objective-C方法中block参数的类型。

class-dump出的头文件中经常包含如下方法签名:

- (void)doSomethingWithCompletionHandler:(CDUnknownBlockType)arg1;

CDUnknownBlockType 就是block类型的参数。当我们要调用这个方法,就需要知道这个参数类型。

网上搜了下,发现一篇好文章,文章讲解了使用lldb命令找到参数类型的方法。连接如下:

http://www.swiftyper.com/2016/12/16/debuging-objective-c-blocks-in-lldb/

但每次都需要lldb逐个命令的敲打,很是麻烦。于是又搜到一个lldb脚本,

然而年久失修,不怎么能工作。

那就尝试修复这个脚本吧!

-> debugging .
-> debugging ..
-> debugging ...
-> ....
-> fixed, yeah :)

安装

安装lldb script

cd ~
git clone git@github.com:everettjf/zlldb.git

然后在 ~/.lldbinit 文件中添加下行内容:

command script import ~/zlldb/main.py

image

使用

例如我们有如下block方法

@interface Hello : NSObject
@end
@implementation Hello
+ (void)say:(NSString*)text callback:(void(^)(NSString *text,int x, NSString *y, double z, BOOL m))callback {
}
@end

调用如下:

[Hello say:@"world" callback:^(NSString *text, int x, NSString *y, double z, BOOL m) {
    }];

那么我们断点到这个调用行:

如果是逆向工作的话,没有代码,那可以断点到 objc_msgSend这行,如下:

block是第二个参数,那么打印出$arg4

image

然后执行命令 zblock 0x100588080 (block的地址传给 zblock命令),然后block的参数就出来啦。

image

根据每一行的 type encoding 对照苹果的文档即可知道block的参数都有什么。

https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html

代码

由于Xcode11内置的lldb script开始默认Python3版本,facebook的chisel还有些支持问题(可能现在解决了)。zlldb就是把我自己常用的几个命令放到了这里,支持Python3(也就是最新版Xcode)。除了zblock外,还有几个简单的命令,大家可以参考README。

参考


Enjoy :slight_smile:

广告和招人,见这篇文章哈 https://mp.weixin.qq.com/s/NPZOr4Y9w9gwwX9VxJcHlA

Posts: 2

Participants: 1

Read full topic

rejecting incoming connection from XXX 或者Failed to connect port的问题解决

$
0
0

@makdbb wrote:

之前尝试动态调试的时候还wifi连接过一次手机,这次又出现问题了。
不过这次细细看了下出错信息mac端和iphone端都看了下。
iphone端显示的是:

error: rejecting incoming connection from ::ffff:192.168.2.104 (expecting ::1)

mac端显示的是:

process connect connect://192.168.2.103:1234

看了iphone端的显示是ipv6的地址,因此怀疑是否是因为lldb不支持ipv6?或者ipv6用在这里会有什么问题?
先不管这些,可能是因为ipv6的问题。那就简单了。
自己指定下mac的ip试试。

debugserver 192.168.2.104:1234 -a Aweme // 此处是mac的ip

然后跑起来。
mac上继续连接:

process connect connect://192.168.2.103:1234
* thread #1, queue = 'com.apple.main-thread', stop reason = signal SIGSTOP
    frame #0: 0x0000000198abc634 libsystem_kernel.dylib`mach_msg_trap + 8
libsystem_kernel.dylib`mach_msg_trap:
->  0x198abc634 <+8>: ret    

libsystem_kernel.dylib`mach_msg_overwrite_trap:
    0x198abc638 <+0>: mov    x16, #-0x20
    0x198abc63c <+4>: svc    #0x80
    0x198abc640 <+8>: ret    
Target 0: (Aweme) stopped.
(lldb) c   
(lldb) image list -o -f 
[  0] 0x00000000049f8000 /private/var/containers/Bundle/Application/36554A1C-10D0-41BF-920F-FEB48C354632/Aweme.app/Aweme(0x00000001049f8000)
[  1] 0x000000010a0b0000 /Library/Caches/cy-7TD2dP.dylib(0x000000010a0b0000)

boom 好了 hooray :slight_smile:
嘿嘿 大家可以试试,有什么问题可和我微信探讨交流 nicholas_mcc

Posts: 1

Participants: 1

Read full topic

如何优雅的在LLDB里dumpdecrypted

$
0
0

@4ch12dy wrote:

开始

之前写过一篇文章介绍了在LLDB中的脱壳工具,原理是将dumpdecrypted移植成了LLDB脚本。该脱壳的场景主要是针对一些无法正常启动的app进行脱壳,例如,检测到越狱直接闪退、不明crash等。因为现在的脱壳工具前提必须是app至少能正常启动。但是在LLDB中脱壳那篇文章关于脱壳流程其实很繁琐,所以这里将进行改进,用一种优雅的方式实现,本文默认讨论是针对启动就crash的场景。

问题?

由于在LLDB中脱壳的特殊性,LLDB以后台模式启动App后,能拿到最早的执行点,所以App代码还没执行,自然是不会crash的,但是这个时机是没有办法进行脱壳的,因为很多基础模块都没有加载到进程,而我写的脱壳代码是需要执行环境(直接进行内存dump的话是不需要)。所以才有了之前那篇文章设置断点的方式,主要就是为了能够在基础模块加载完后,app代码还未执行之前进行dump。也因为如此,写了一个能对mod_init_func下断点的命令,但这种方式一直都不优雅,很麻烦,而且+[Class load]的执行时机在mod_init_func之前,如果把检测的代码放到那里,这时候程序以及退出了。

逆向分析

其实对于dump的时机问题,最开始就在思考怎么容易的去拿到一个点。但之前在dyld源码中查找过相关api,不过没有找打合适的点。最近分析一个app正是用的+[Class load]方式进行的检测,导致脱壳费了一点精力,所以想完善一下。开始分析:

首先+[Class load]的调用链如下

(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 3.1
  * frame #0: 0x0000000100fd67f0 TestAPP`+[OCClassDemo load](self=OCClassDemo, _cmd="load") at OCClassDemo.m:20:5
    frame #1: 0x00000001b6c2cecc libobjc.A.dylib`load_images + 736
    frame #2: 0x00000001011ea0d4 dyld`dyld::notifySingle(dyld_image_states, ImageLoader const*, ImageLoader::InitializerTimingList*) + 448
    frame #3: 0x00000001011f958c dyld`ImageLoader::recursiveInitialization(ImageLoader::LinkContext const&, unsigned int, char const*, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 524
    frame #4: 0x00000001011f8308 dyld`ImageLoader::processInitializers(ImageLoader::LinkContext const&, unsigned int, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 184
    frame #5: 0x00000001011f83d0 dyld`ImageLoader::runInitializers(ImageLoader::LinkContext const&, ImageLoader::InitializerTimingList&) + 92
    frame #6: 0x00000001011ea420 dyld`dyld::initializeMainExecutable() + 216
    frame #7: 0x00000001011eedb4 dyld`dyld::_main(macho_header const*, unsigned long, int, char const**, char const**, char const**, unsigned long*) + 4616
    frame #8: 0x00000001011e9208 dyld`dyldbootstrap::start(dyld3::MachOLoaded const*, int, char const**, dyld3::MachOLoaded const*, unsigned long*) + 396
    frame #9: 0x00000001011e9038 dyld`_dyld_start + 56

可以看到是dyld在进行递归初始化的时候发出,然后调用libobjc.A.dylibload_images函数,最后才执行到App自身的+[OCClassDemo load]代码。由于objc代码开源的,所以去看下load_images这个函数

/***********************************************************************
* load_images
* Process +load in the given images which are being mapped in by dyld.
* Calls ABI-agnostic code after taking ABI-specific locks.
*
* Locking: write-locks runtimeLock and loadMethodLock
**********************************************************************/
const char *
load_images(enum dyld_image_states state, uint32_t infoCount,
            const struct dyld_image_info infoList[])
{
    bool found;

    // Return without taking locks if there are no +load methods here.
    found = false;
    for (uint32_t i = 0; i < infoCount; i++) {
        if (hasLoadMethods((const headerType *)infoList[i].imageLoadAddress)) {
            found = true;
            break;
        }
    }
    if (!found) return nil;

    recursive_mutex_locker_t lock(loadMethodLock);

    // Discover load methods
    {
        rwlock_writer_t lock2(runtimeLock);
        found = load_images_nolock(state, infoCount, infoList);
    }

    // Call +load methods (without runtimeLock - re-entrant)
    if (found) {
        call_load_methods();
    }

    return nil;
}

注释上也写了,这个函数其实主要就是去处理+load方法的,这样说来我们直接在这个方法下断点可以吗?事实上是可以的,但是进程会加载很多模块,在调试器中去需要频繁的进行check是否当前就是app的模块。那这个点其实也是不优雅的。

柳暗花明

在调试的时候由于一个偶然的bug,调试器断在了TweakInject.dylib这里面的一个函数。而且此时app的+load代码并没有执行,而且看名字应该是加载tweak的dylib用的,然后就去google 了一下这个,发现是有开源代码的

看了下源码发现确实就是用来加载/Library/MobileSubstrate/DynamicLibraries下的dylib的,规则就是根据plist的字段。而且现在像libobjc.A.dylib这样的基础模块以及初始化完成。所以是能够执行OC代码,那这个时机应该就是最佳的dump时机。所以我对CoreFoundation 里面的CFBundleGetMainBundle函数下断点。堆栈如下

* frame #0: 0x00000001fd2d18ac CoreFoundation`CFBundleGetMainBundle
    frame #1: 0x00000001fdbfeadc Foundation`+[NSBundle mainBundle] + 112
    frame #2: 0x00000001012b6ee0 TweakInject.dylib`___lldb_unnamed_symbol1$$TweakInject.dylib + 96
    frame #3: 0x000000010146f56c dyld`ImageLoaderMachO::doModInitFunctions(ImageLoader::LinkContext const&) + 424
    frame #4: 0x000000010146f7ac dyld`ImageLoaderMachO::doInitialization(ImageLoader::LinkContext const&) + 40
    frame #5: 0x0000000101469f64 dyld`ImageLoader::recursiveInitialization(ImageLoader::LinkContext const&, unsigned int, char const*, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 512

这时候执行dumpdecrypted发现就没问题了,顺利脱壳。后面在封装成一个命令,这样只要调试器挂上以后,执行命令自动下断点,触发以后自动执行脱壳命令,这样就要优雅很多。

现在xia0LLDB中dumpdecrypted已经更新,在以后台模式启动app,lldb挂上去以后,直接执行dumpdecrypted -X即可

更多思考

本文来说,这里应该就完了,已经找到了一个比较优雅的dump点。不过我却陷入了思考,这个TweakInject.dylib凭啥就能优先初始化执行代码,能够保证在基础模块初始化之后,其他模块之前。正常来说肯定是做不到的,除非对dyld进行修改,使其优先初始化这个dylib。所以是怎么做到这得呢?搜索了一番没有发现是什么资料,随后我又对unc0ver进行了测试(因为我用的Chimera越狱),之前我就说过这两个越狱其实差异很大,导致我想看下unc0ver中这里是怎么处理的,同样对CFBundleGetMainBundle下断点,堆栈如下

(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
  * frame #0: 0x00000001b6f4a510 CoreFoundation`CFBundleGetMainBundle
    frame #1: 0x000000010d27bd14 SubstrateLoader.dylib`___lldb_unnamed_symbol1$$SubstrateLoader.dylib + 124
    frame #2: 0x000000010d37e18c cy-c5bKjj.dylib`ImageLoaderMachO::doModInitFunctions(ImageLoader::LinkContext const&) + 428
    frame #3: 0x000000010d37e560 cy-c5bKjj.dylib`ImageLoaderMachO::doInitialization(ImageLoader::LinkContext const&) + 52
    frame #4: 0x000000010d3795a4 cy-c5bKjj.dylib`ImageLoader::recursiveInitialization(ImageLoader::LinkContext const&, unsigned int, char const*, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 548
    frame #5: 0x000000010d378308 cy-c5bKjj.dylib`ImageLoader::processInitializers(ImageLoader::LinkContext const&, unsigned int, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 184
    frame #6: 0x000000010d3783d0 cy-c5bKjj.dylib`ImageLoader::runInitializers(ImageLoader::LinkContext const&, ImageLoader::InitializerTimingList&) + 92
    frame #7: 0x000000010d36d894 cy-c5bKjj.dylib`dyld::runInitializers(ImageLoader*) + 88
    frame #8: 0x000000010d3743b8 cy-c5bKjj.dylib`dlopen_internal + 1064
    frame #9: 0x00000001b6d03560 libdyld.dylib`dlopen + 172
    frame #10: 0x000000010d37e18c cy-c5bKjj.dylib`ImageLoaderMachO::doModInitFunctions(ImageLoader::LinkContext const&) + 428
    frame #11: 0x000000010d37e560 cy-c5bKjj.dylib`ImageLoaderMachO::doInitialization(ImageLoader::LinkContext const&) + 52
    frame #12: 0x000000010d3795a4 cy-c5bKjj.dylib`ImageLoader::recursiveInitialization(ImageLoader::LinkContext const&, unsigned int, char const*, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 548
    frame #13: 0x000000010d378308 cy-c5bKjj.dylib`ImageLoader::processInitializers(ImageLoader::LinkContext const&, unsigned int, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 184
    frame #14: 0x000000010d3783d0 cy-c5bKjj.dylib`ImageLoader::runInitializers(ImageLoader::LinkContext const&, ImageLoader::InitializerTimingList&) + 92
    frame #15: 0x000000010d36a3d0 cy-c5bKjj.dylib`dyld::initializeMainExecutable() + 136
    frame #16: 0x000000010d36edb4 cy-c5bKjj.dylib`dyld::_main(macho_header const*, unsigned long, int, char const**, char const**, char const**, unsigned long*) + 4616
    frame #17: 0x000000010d369208 cy-c5bKjj.dylib`dyldbootstrap::start(dyld3::MachOLoaded const*, int, char const**, dyld3::MachOLoaded const*, unsigned long*) + 396
    frame #18: 0x000000010d369038 cy-c5bKjj.dylib`_dyld_start + 56

这里可以发现并不是TweakInject.dylib,而是SubstrateLoader.dylib从名字上来看应该也是用作加载tweak的dylib的。另外有个注意的地方在于,调用者并不是dyld,而是cy-c5bKjj.dylib完整的路径是/Library/Caches/cy-c5bKjj.dylib,然而去文件中发现并不存在。

更新(2020-02-28)

找到原因了,本来写了一段,不过又删除了。因为后面涉及到的知识点太多了,如果只是用这一个节去简单说下原理,那无疑会遗漏很多细节,于是我准备再写一篇,详细介绍这背后的所有知识点。主要涉及到以下内容

  • jailbreak post exploit patching
  • tweak loader
  • dyld how to load and init image
  • DYLD_INSERT_LIBRARIES details
  • +[Class load] and mod_init_func

分析在这里

最后

这里目前还有几个疑问没解决,后面找到了答案再来更新。

参考

Posts: 1

Participants: 1

Read full topic


Step into Jailbreak and dyld

$
0
0

@4ch12dy wrote:

本文以coolstar系越狱为基础讨论

背景

本文由如何优雅的在LLDB里dumpdecrypted中tweak loader优先初始化的一个思考而来,里面提出了一个疑问在于,tweak loader到底何时加载到进程以及何时被初始化的。而在去探索这个答案的过程中,发现背后涉及到的却是和整个越狱相关,正好借此去深入了解越狱的相关细节。

Tweak loader

我最开始搜到的源码是:https://github.com/chr1s0x1/TweakInject

代码本身很简单,就是去/Library/MobileSubstrate/DynamicLibraries下面去搜索,通过plist文件里面的字段决定是否将dylib加载到当前进程中,就和平时开发tweak去hook那些app的过程一样。不过我感到疑问的地方在于那本身这个tweak loader dylib又是谁以及何时被加载到进程中的呢。这里不难想到应该就是越狱后支持hook环境相关,所以我又去翻阅相关越狱源码。

Electra

我研究的越狱源码是:https://github.com/coolstar/electra

至于为什么会选择Electra,而且是早期的一个版本?有以下几点原因:

  • coolstar由于在开发Electra后saurik没有提供substrate的支持,所以后面自己实现了完整的hook环境,这里面很多东西都是重新研究实现的,保留了很多当时的曲折过程,而这些恰好很便于去学习。
  • 代码开源,而且早期一切都是以实用为主,代码都比较有代表性。

pspawn_payload

回到之前的问题,tweak loader是谁负责加载的呢?最直接相关的代码在:

代码里面的SBInject.dylib就是tweak loader,如下

#define SBINJECT_PAYLOAD_DYLIB "/usr/lib/SBInject.dylib"

这个模块叫做pspawn_payload,你在coolstar系越狱里进程模块里面就会有这个模块名。

下面是该模块初始化的代码

__attribute__ ((constructor))
static void ctor(void) {
    if (getpid() == 1) {
        current_process = PROCESS_LAUNCHD;
        pthread_t thd;
        pthread_create(&thd, NULL, thd_func, NULL);
    } else {
        current_process = PROCESS_XPCPROXY;
        rebind_pspawns();
    }
}

代码就是如果当前进程不是launchd,就hook pspawns函数。pspawns正是创建一个新进程的函数,所以hook以后会在创建前注入dylib。下面简单列举里面几个关键地方的代码

int fake_posix_spawn_common(pid_t * pid, const char* path, const posix_spawn_file_actions_t *file_actions, posix_spawnattr_t *attrp, char const* argv[], const char* envp[], pspawn_t old) {
	
	...
	if (strcmp(path, "/usr/libexec/amfid") == 0) {
        DEBUGLOG("Starting amfid -- special handling");
        inject_me = AMFID_PAYLOAD_DYLIB;
    } else {
        inject_me = SBINJECT_PAYLOAD_DYLIB;
    }
    ...

    int envcount = 0;
    
    if (envp != NULL){
        DEBUGLOG("Env: ");
        const char** currentenv = envp;
        while (*currentenv != NULL){
            DEBUGLOG("\t%s", *currentenv);
            if (strstr(*currentenv, "DYLD_INSERT_LIBRARIES") == NULL) {
                envcount++;
            }
            currentenv++;
        }
    }
    
    char const** newenvp = malloc((envcount+2) * sizeof(char **));
    int j = 0;
    for (int i = 0; i < envcount; i++){
        if (strstr(envp[j], "DYLD_INSERT_LIBRARIES") != NULL){
            continue;
        }
        newenvp[i] = envp[j];
        j++;
    }
    
    char *envp_inject = malloc(strlen("DYLD_INSERT_LIBRARIES=") + strlen(inject_me) + 1);
    
    envp_inject[0] = '\0';
    strcat(envp_inject, "DYLD_INSERT_LIBRARIES=");
    strcat(envp_inject, inject_me);
    
    newenvp[j] = envp_inject;
    newenvp[j+1] = NULL;

    ...

    origret = old(pid, path, file_actions, newattrp, argv, newenvp);
    
}

这里我分析下几个关键的地方,第一是如果当前要创建进程为/usr/libexec/amfid(也就是验证代码签名的进程),那么就注入AMFID_PAYLOAD_DYLIB模块,这个模块主要就是去patch验证签名的地方,这样才能执行任意代码;如果是其他进程,那么就注入SBINJECT_PAYLOAD_DYLIB模块(也就是tweak loader)。第二个地方是介绍了注入的实现方式,原理就是设置当前的环境变量,在调用原函数前,将当前DYLD_INSERT_LIBRARIES这个环境变量里面增加一个dylib路径,这样在进程穿件后就会去加载tweak loader。到这里,我们明白了tweak loader原来就是在这里被注入进去的。但同时又引出了一个问题,pspawn_payload模块本身又是谁加载的呢?这不又回到了之前的问题,接着分析。

the fun part

这里的名字并不是我随便起的,因为在Electra里面就是就是。相关的代码路径在

https://github.com/coolstar/electra/blob/02858b14dac30c9ba868bd3024529e9ae6592e67/electra/the%20fun%20part/fun.c

这个文件里面的代码会做Post exploit patching所有事情,即在漏洞完成利用以后的所有事,这里当然就是指越狱环境。和其他越狱一样,主要就下面几个工作

  • 初始化jailbreakd

  • setuid(0):将当前进程设为root进程

  • Remap tfp0

  • Remount / as rw :重新挂起根目录,实现任意文件读写

  • Prepare bootstrap binary:准备预装的一些基本的二进制文件,包括sshd,gnu命令等

  • setup hook enviroment : 支持hook环境

  • launch some daemon:加载一些守护进程

  • respring :重启Springboard

这里的最后一步如果完成,也就是平时看到的那样,代表越狱成功了。

回我之前的问题,这些步骤里面我们关心的地方就是hook环境的支持,直接相关代码在

if (enable_tweaks){
    const char* args_launchd[] = {BinaryLocation, itoa(1), "/bootstrap/pspawn_payload.dylib", NULL};
    rv = posix_spawn(&pd, BinaryLocation, NULL, NULL, (char **)&args_launchd, NULL);
    waitpid(pd, NULL, 0);
    
    const char* args_recache[] = {"/bootstrap/usr/bin/recache", "--no-respring", NULL};
    rv = posix_spawn(&pd, "/bootstrap/usr/bin/recache", NULL, NULL, (char **)&args_recache, NULL);
    waitpid(pd, NULL, 0);
}

也就是说如果设置了支持tweak的话,就会将pspawn_payload模块注入到1号进程,而1号进程就是launchd进程。另外你可能想问,那这里的注入又是怎么实现的呢?不可能又是hook posix_spawn注入环境变量吧,这不就产生鸡生蛋和蛋生鸡的问题了吗?事实上这里的注入并不是通过hook posix_spawn,注入的实现在

https://github.com/coolstar/electra/tree/02858b14dac30c9ba868bd3024529e9ae6592e67/basebinaries/inject_criticald

注入的原理就是通过task_for_pid来实现的,如果你的设备是用的coolstar系越狱(Electra或者Chimera)的话,你会在越狱目录下存在这个可执行文件,下面是Chimera越狱的信息

xia0:/chimera root# ls -la
total 1100
drwxr-xr-x  8 root wheel    256 Feb 26 13:19 ./
drwxr-xr-x 28 root wheel    896 Sep 16 17:44 ../
-rwxr-xr-x  1 root wheel 168736 Sep 17 10:21 inject_criticald*
-rwxr-xr-x  1 root wheel 207920 Sep 17 10:21 jailbreakd*
-rwxr-xr-x  1 root wheel 133840 Sep 17 10:21 jailbreakd_client*
-rwxr-xr-x  1 root wheel 167296 Sep 17 10:21 libjailbreak.dylib*
-rwxr-xr-x  1 root wheel 236896 Sep 17 10:21 pspawn_payload-stg2.dylib*
-rwxr-xr-x  1 root wheel 202640 Sep 17 10:21 pspawn_payload.dylib*
xia0:/chimera root# ./inject_criticald 
Usage: inject_criticald <pid> <dylib>

inject_criticald这个命令可以直接对进程进行注入dylib。到这里,关于tweak loader的加载问题已经得到了解决,整个加载的过程可以说就是越狱的整个过程。现在tweak loader由谁加载的问题解决了,但是何时被加载和初始化的问题还没解决?接下来就和越狱本身不相关了,要从DYLD_INSERT_LIBRARIES说起

DYLD_INSERT_LIBRARIES && dyld

下面就抛开越狱,进入DYLD_INSERT_LIBRARIES和dyld的实现细节里面,由于dyld本事是开源的,所以从源码开始分析。直接搜索DYLD_INSERT_LIBRARIES最可疑的地方就在这里

// In order for register_func_for_add_image() callbacks to to be called bottom up,
// we need to maintain a list of root images. The main executable is usally the
// first root. Any images dynamically added are also roots (unless already loaded).
// If DYLD_INSERT_LIBRARIES is used, those libraries are first.
static void addRootImage(ImageLoader* image)
{
	//dyld::log("addRootImage(%p, %s)\n", image, image->getPath());
	// add to list of roots
	sImageRoots.push_back(image);
}

注释里面说到了一句,If DYLD_INSERT_LIBRARIES is used, those libraries are first.

也就是说,如果DYLD_INSERT_LIBRARIES环境变量注入的模块会被优先处理。本身这个函数的话是在下面函数中调用

void link(ImageLoader* image, bool forceLazysBound, bool neverUnload, const ImageLoader::RPathChain& loaderRPaths)
{
	// add to list of known images.  This did not happen at creation time for bundles
	if ( image->isBundle() && !image->isLinked() )
		addImage(image);

	// we detect root images as those not linked in yet 
	if ( !image->isLinked() )
		addRootImage(image);
	
	// process images
	try {
		image->link(gLinkContext, forceLazysBound, false, neverUnload, loaderRPaths);
	}
	catch (const char* msg) {
		garbageCollectImages();
		throw;
	}
}

接下来一个重要的函数就是initializeMainExecutable()

void initializeMainExecutable()
{
	// record that we've reached this step
	gLinkContext.startedInitializingMainExecutable = true;

	// run initialzers for any inserted dylibs
	ImageLoader::InitializerTimingList initializerTimes[sAllImages.size()];
	initializerTimes[0].count = 0;
	const size_t rootCount = sImageRoots.size();
	if ( rootCount > 1 ) {
		for(size_t i=1; i < rootCount; ++i) {
			sImageRoots[i]->runInitializers(gLinkContext, initializerTimes[0]);
		}
	}
	
	// run initializers for main executable and everything it brings up 
	sMainExecutable->runInitializers(gLinkContext, initializerTimes[0]);
	
	// register cxa_atexit() handler to run static terminators in all loaded images when this process exits
	if ( gLibSystemHelpers != NULL ) 
		(*gLibSystemHelpers->cxa_atexit)(&runAllStaticTerminators, NULL, NULL);

	// dump info if requested
	if ( sEnv.DYLD_PRINT_STATISTICS )
		ImageLoaderMachO::printStatistics((unsigned int)sAllImages.size(), initializerTimes[0]);
}

这里可以看到会去先初始化sImageRoots,然后才初始化sMainExecutable。当然后面就是一个递归的初始化过程,即是说如果初始化的模块依赖其他模块,那么又先初始化依赖的模块。在递归初始化函数之中有个地方

void ImageLoader::recursiveInitialization(const LinkContext& context, mach_port_t this_thread,
										  InitializerTimingList& timingInfo, UninitedUpwards& uninitUps)
{
	...
	// let objc know we are about to initialize this image
	uint64_t t1 = mach_absolute_time();
	fState = dyld_image_state_dependents_initialized;
	oldState = fState;
	context.notifySingle(dyld_image_state_dependents_initialized, this);
	
	// initialize this image
	bool hasInitializers = this->doInitialization(context);

	// let anyone know we finished initializing this image
	fState = dyld_image_state_initialized;
	oldState = fState;
	context.notifySingle(dyld_image_state_initialized, this);

	...
}

从这里可以看出,+load函数确实会比mod_init_func优先执行。

最后总结一下,由于tweak loader是通过DYLD_INSERT_LIBRARIES注入的,所以会优先初始化,只有这样才能实现加载tweak模块的功能。到这里tweak loader何时被初始化的疑问也得到了解决,后面会用实验去验证这个分析。

实验

前面分析了这么多,那实际情况到底是不是这样的呢。首先我们还是对CFBundleGetMainBundle、App里面的+loadmod_init_func下断点,首先断下来的是:

(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 4.1
  * frame #0: 0x00000001fd2d18ac CoreFoundation`CFBundleGetMainBundle
    frame #1: 0x00000001fdbfeadc Foundation`+[NSBundle mainBundle] + 112
    frame #2: 0x00000001024a2ee0 TweakInject.dylib`___lldb_unnamed_symbol1$$TweakInject.dylib + 96
    frame #3: 0x000000010256f56c dyld`ImageLoaderMachO::doModInitFunctions(ImageLoader::LinkContext const&) + 424
    frame #4: 0x000000010256f7ac dyld`ImageLoaderMachO::doInitialization(ImageLoader::LinkContext const&) + 40
    frame #5: 0x0000000102569f64 dyld`ImageLoader::recursiveInitialization(ImageLoader::LinkContext const&, unsigned int, char const*, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 512
    frame #6: 0x0000000102568dd8 dyld`ImageLoader::processInitializers(ImageLoader::LinkContext const&, unsigned int, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 152
    frame #7: 0x0000000102568e98 dyld`ImageLoader::runInitializers(ImageLoader::LinkContext const&, ImageLoader::InitializerTimingList&) + 88
    frame #8: 0x00000001025567d4 dyld`dyld::initializeMainExecutable() + 188
    frame #9: 0x000000010255b88c dyld`dyld::_main(macho_header const*, unsigned long, int, char const**, char const**, char const**, unsigned long*) + 4708
    frame #10: 0x0000000102555044 dyld`_dyld_start + 68

从调用链来看initializeMainExecutable后就是先初始化TweakInject.dylib,这时候app自身代码还没执行。接下来断下来是

(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 3.1
  * frame #0: 0x000000010244e7f0 TestAPP`+[OCClassDemo load](self=OCClassDemo, _cmd="load") at OCClassDemo.m:20:5
    frame #1: 0x00000001fc476a24 libobjc.A.dylib`call_load_methods + 188
    frame #2: 0x00000001fc477d94 libobjc.A.dylib`load_images + 148
    frame #3: 0x00000001025564c4 dyld`dyld::notifySingle(dyld_image_states, ImageLoader const*, ImageLoader::InitializerTimingList*) + 488
    frame #4: 0x0000000102569f40 dyld`ImageLoader::recursiveInitialization(ImageLoader::LinkContext const&, unsigned int, char const*, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 476
    frame #5: 0x0000000102568dd8 dyld`ImageLoader::processInitializers(ImageLoader::LinkContext const&, unsigned int, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 152
    frame #6: 0x0000000102568e98 dyld`ImageLoader::runInitializers(ImageLoader::LinkContext const&, ImageLoader::InitializerTimingList&) + 88
    frame #7: 0x00000001025567f8 dyld`dyld::initializeMainExecutable() + 224
    frame #8: 0x000000010255b88c dyld`dyld::_main(macho_header const*, unsigned long, int, char const**, char const**, char const**, unsigned long*) + 4708
    frame #9: 0x0000000102555044 dyld`_dyld_start + 68

从调用链看,这时候开始初始化主模块,而且正是+load函数,所以+load就是app最早执行代码的地方。而且是通知libobjc.A.dylib去完成的。这里可以回到dyld的源码中:

static void notifySingle(dyld_image_states state, const ImageLoader* image)
{
	//dyld::log("notifySingle(state=%d, image=%s)\n", state, image->getPath());
	std::vector<dyld_image_state_change_handler>* handlers = stateToHandlers(state, sSingleHandlers);
	if ( handlers != NULL ) {
		dyld_image_info info;
		info.imageLoadAddress	= image->machHeader();
		info.imageFilePath		= image->getRealPath();
		info.imageFileModDate	= image->lastModified();
		for (std::vector<dyld_image_state_change_handler>::iterator it = handlers->begin(); it != handlers->end(); ++it) {
			const char* result = (*it)(state, 1, &info);
			if ( (result != NULL) && (state == dyld_image_state_mapped) ) {
				//fprintf(stderr, "  image rejected by handler=%p\n", *it);
				// make copy of thrown string so that later catch clauses can free it
				const char* str = strdup(result);
				throw str;
			}
		}
	}
	...
}

notifySingle函数会循环调用所有注册通知的处理模块

// Callback that provides a bottom-up array of images
// For dyld_image_state_[dependents_]mapped state only, returning non-NULL will cause dyld to abort loading all those images
// and append the returned string to its load failure error message. dyld does not free the string, so
// it should be a literal string or a static buffer
//
typedef const char* (*dyld_image_state_change_handler)(enum dyld_image_states state, uint32_t infoCount, const struct dyld_image_info info[]);

这里就是由libobjc.A.dylib去处理的+load函数。最后断下来的就是:

(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
  * frame #0: 0x000000010242bd20 TestAPP`temp_init at temp.c:98:5
    frame #1: 0x000000010256f56c dyld`ImageLoaderMachO::doModInitFunctions(ImageLoader::LinkContext const&) + 424
    frame #2: 0x000000010256f7ac dyld`ImageLoaderMachO::doInitialization(ImageLoader::LinkContext const&) + 40
    frame #3: 0x0000000102569f64 dyld`ImageLoader::recursiveInitialization(ImageLoader::LinkContext const&, unsigned int, char const*, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 512
    frame #4: 0x0000000102568dd8 dyld`ImageLoader::processInitializers(ImageLoader::LinkContext const&, unsigned int, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 152
    frame #5: 0x0000000102568e98 dyld`ImageLoader::runInitializers(ImageLoader::LinkContext const&, ImageLoader::InitializerTimingList&) + 88
    frame #6: 0x00000001025567f8 dyld`dyld::initializeMainExecutable() + 224
    frame #7: 0x000000010255b88c dyld`dyld::_main(macho_header const*, unsigned long, int, char const**, char const**, char const**, unsigned long*) + 4708
    frame #8: 0x0000000102555044 dyld`_dyld_start + 68

这里看出就是mod_init_func了,当然在dyld中调用这个函数的地方就是

bool ImageLoaderMachO::doInitialization(const LinkContext& context)
{
	CRSetCrashLogMessage2(this->getPath());

	// mach-o has -init and static initializers
	doImageInit(context);
	doModInitFunctions(context);
	
	CRSetCrashLogMessage2(NULL);
	
	return (fHasDashInit || fHasInitializers);
}

其中doModInitFunctions就会解析load command找到_DATA,_mod_init_func列表进行初始化调用。

最后

对于coolstar系的越狱简单的分析就到这里,unc0ver越狱的话整体流程应该差不多,但在substrate层应该实现差异挺大的,后面有时间的话再分析下相关实现了。

参考

Posts: 1

Participants: 1

Read full topic

手把手教你搭建cydia私有源

$
0
0

@makdbb wrote:

手把手教你搭建cydia私有源

 出于两个目的想要把一些deb包打包做成私有源

  1. 万能的墙导致有时候下载别的源太慢或者必须要科学上网。
  2. 手机上装的deb包有时候,比如不小心手机重启了(虽然现在我还没遇到过),不完美越狱就比较尴尬了。因此尽可能是将手机上现在已经安装的所有的包导出然后制作成私有源,即使下次重启啥都没了重新安装也不用科学上网,也不用那么麻烦了。
     可能已经有其他平台或者软件能够解决以上问题了,但是本着学习和不断探索的精神,还是造一次轮子吧。

1. 打包deb

 发布越狱程序的时候需要打包deb包,然后自己上传到cydia源内就可以通过cydia来安装了。

1.1 配置文件

 创建一个目录 bundleid为名字的文件夹(比如com.echosdaddy.test),在 com.echosdaddy.test 目录下新建 DEBIAN 和 Applications 这两个目录,然后在 DEBIAN 下新建一个文本文件 control。

 control 文件就是打包用的配置文件,编辑文件如下:

Package: com.echosdaddy.test
Name: echosdaddy
Version: 0.1
Description: this is a description
Section: Jailbreak
Depends: firmware (>= 8.0)
Priority: optional
Architecture: iphoneos-arm
Author: echosdaddy
Homepage: https://lingo.bichonfrise.cn
Icon: file:///Applications/test.app/Icon.png
Maintainer: echosdaddy

 下面是注释说明,只是为了让大家明白各个配置都是干嘛的。

Package: com.echosdaddy.test // bundleid
Name: echosdaddy  // 软件名称
Version: 0.1 // 版本号
Description: this is a description  // 描述信息
Section: Jailbreak // 这里就对应的是源内全部软件下面的分类了,cydia会自动分类
Depends: firmware (>= 8.0) // 软件依赖以","分割,如果有版本要求可参考左侧例子
Priority: optional
Architecture: iphoneos-arm
Author: echosdaddy // 作者信息
Homepage: https://lingo.bichonfrise.cn // 作者主页,这里其实就可以做成像雷锋源那样做个导航
Icon: file:///Applications/test.app/Icon.png  // 软件图标,会在cydia中显示
Maintainer: echosdaddy //维护人,多个维护人以","分割

 找到你用 Xcode 编译的应用,复制到 Applications 目录,记得要把 .DS_Store 文件删除,不然可能安装不成功,使用 ls -al 查看文件确认一下。

1.2 打包

 到com.echosdaddy.test上面一级目录,然后执行打包命令:

dpkg-deb -b com.echosdaddy.test com.echosdaddy.test.deb

  dpkg-deb 是theos里面的一个程序,如果没设置环境变量,记得使用绝对路径或者相对路径都可以。

  如果你想解包别的deb咋弄呢?

dpkg -x a.deb a

  解包之后你就可以修改control文件啥的了。

2. 扫描打包上传

 到这里其实就已经有自己的deb包了,那如何做成cydia,能够自己在cydia中设置一下源就可以直接自己选择下载安装了呢?

 首先得自己制作cydia格式的包文件,包含以下几个文件:

  1. deb 包
  2. Packages文件
  3. Release文件
  4. 图标文件(可选)

 图标文件自己整个60*60的图标png格式放到Packages同级目录下即可,cydia会自动识别。名字必须是“CydiaIcon.png”

 接下来就是打包了把所有的deb都打包在一起:

dpkg-scanpackages *.deb > Packages

  Packages 文件际上就是 control 文件的一个集合,打开 Packages 查看一下格式:

Package: net.echosdaddy.test
Version: 0.1
Architecture: iphoneos-arm
Maintainer: EchosDaddy
Depends: grep, sed
Filename: /Users/bichonfrise/deb/com.sull.backtodeb_0.9b6-1_iphoneos-arm.deb
Size: 76352
MD5sum: 3666ea71c418c28db81b694b8768d14b
SHA1: 6e6899cdd2ba2b67529c474e0490ab145eb4fbf1
SHA256: 5b4489d419f3245e6d45fba4ac67c1dd55ca3b4b16784f12744f63b3c84edcdf
Section: EchosDaddy
Priority: optional
Homepage: https://bichonfrise.cn
Description: 这是一个EchosDaddy程序
Author: EchosDaddy
Name: 应用测试

  把你想打包的deb都放到当前目录下就行了。这样就可以生成一个Packages文件,接下来用bzip2压缩下就行了

bzip2 Packages

  最后写一个Release文件这一步就完事了,其实就是介绍一下自己的软件源。

Origin: EchosDaddy 软件源™
Label: EchosDaddy
Suite: stable
Version: 1.7
Codename: EchosDaddy
Architectures: iphoneos-arm
Components: main
Description: EchosDaddy 软件源

  然后上传到自己的webserver下或者空间下就行了,一共以下几类文件:

  1. deb包
  2. Packages.bz2
  3. Release
  4. 图标文件(可选)

  然后自己就可以通过cydia设置自己的源来使用了。

  • 这里需要自己有个webserver或者空间都可以,我看别人也有用git搞的,我不熟悉就不搞那个了。

我自己搞了一个可以参观下:http://lingo.bichonfrise.cn/cydia

3. cydia内跳转

 在看雷锋源的时候发现可以走webhtml跳转到cydia内包安装页面,咋做的呢?charles抓包看了下其实就是urlschema。自己设置bundleid就行了。记得这里只有用charles抓包手机请求才能看到,因为做了user-agent判断,当然自己重写user-agent也行。

<a href="cydia://package/com.abcydia.tone.nlch" target="_new">
  <img class="icon" src="icons/Ringtones.png" />
    <div>
      <div>
        <label>
          <p>逆流成河 - 金南玲  铃声</p>
        </label>
      </div>
    </div>
</a>

4. 可持续发展设想

  这里没啥说的,就是走平台化思路,两个思路:

  1. 开放给大家自己可以备份自己的源
  2. 大家可以自行发布自己的deb包,可以设置为收费或(免费+google adsence广告)模式。

最后的最后,欢迎大家与我多多交流,vx号:nicholas_mcc

参考资料:

【saurik教程】http://www.saurik.com/id/7

【打包deb】https://www.exchen.net/ios-hacker-将-app-打包成-deb-发布安装.html

【制作cydia源】https://www.exchen.net/ios-hacker-制作自己的-cydia-源.html

【图标相关】 https://www.cnblogs.com/mengshu-lbq/archive/2013/01/16/2862681.html

Posts: 3

Participants: 3

Read full topic

Gcc中的编译器堆栈保护技术浅析

$
0
0

@makdbb wrote:

最近在看ios下的arm汇编代码的时候看到有个stack_chkguard 和stack_chk_fail,记录下,以下代码均来自linux64下gcc编译器编译的二进制文件,代码已经备注的很详细了。

gcc中的编译器选项可以设置是否开启栈保护:-fstack-protector -fstack-protector -fno-stack-protector gcc 4版本中默认已经启用的stack-protector

c源代码如下:

#include <stdio.h>
int main(int argc, char* argv[]){
	int i;
	char a[64];
	i = 0;
	a[0] = 'a';
	return 0;
}
1_nosp 为强制关掉stack_protector的反汇编代码。
(gdb) file 1_nosp
Reading symbols from 1_nosp...(no debugging symbols found)...done.
(gdb) disas main
Dump of assembler code for function main:
   0x00000000004004d6 <+0>:     push   %rbp // 保存栈基址
   0x00000000004004d7 <+1>:     mov    %rsp,%rbp // 设置新的栈指针
   0x00000000004004da <+4>:     mov    %edi,-0x54(%rbp) // argc
   0x00000000004004dd <+7>:     mov    %rsi,-0x60(%rbp) // argv
   0x00000000004004e1 <+11>:    movl   $0x0,-0x4(%rbp) // 设置i的值
   0x00000000004004e8 <+18>:    movb   $0x61,-0x50(%rbp) // 设置a[0] = 'a'
   0x00000000004004ec <+22>:    mov    $0x0,%eax // 设置返回值
   0x00000000004004f1 <+27>:    pop    %rbp  // 弹出栈基址
   0x00000000004004f2 <+28>:    retq
End of assembler dump.
1_sp 为默认开启stack_protector的反汇编代码。
(gdb) file 1_sp
Reading symbols from 1_sp...(no debugging symbols found)...done.
(gdb) disas main
Dump of assembler code for function main:
   0x0000000000400546 <+0>:     push   %rbp // 保存栈基址
   0x0000000000400547 <+1>:     mov    %rsp,%rbp // 设置新的栈指针
   0x000000000040054a <+4>:     sub    $0x70,%rsp // 开辟新的栈空间
   0x000000000040054e <+8>:     mov    %edi,-0x64(%rbp) // argc
   0x0000000000400551 <+11>:    mov    %rsi,-0x70(%rbp) // argv
   0x0000000000400555 <+15>:    mov    %fs:0x28,%rax // canary 值
   0x000000000040055e <+24>:    mov    %rax,-0x8(%rbp) // 这里是canary
   0x0000000000400562 <+28>:    xor    %eax,%eax // 清空eax
   0x0000000000400564 <+30>:    movl   $0x0,-0x54(%rbp) // 设置i的值
   0x000000000040056b <+37>:    movb   $0x61,-0x50(%rbp) // 设置a[0] = 'a'
   0x000000000040056f <+41>:    mov    $0x0,%eax // 设置返回值
   0x0000000000400574 <+46>:    mov    -0x8(%rbp),%rdx //
   0x0000000000400578 <+50>:    xor    %fs:0x28,%rdx // 检测canary值
   0x0000000000400581 <+59>:    je     0x400588 <main+66> // 如果为0则直接返回 否则调到保护代码区。
   0x0000000000400583 <+61>:    callq  0x400420 <__stack_chk_fail@plt>
   0x0000000000400588 <+66>:    leaveq
   0x0000000000400589 <+67>:    retq
End of assembler dump.
(gdb) disas __stack_chk_fail
Dump of assembler code for function __stack_chk_fail@plt:
   0x0000000000400420 <+0>:     jmpq   *0x200bf2(%rip)        # 0x601018
   0x0000000000400426 <+6>:     pushq  $0x0
   0x000000000040042b <+11>:    jmpq   0x400410
End of assembler dump.

等于保存了两份一份在%fs:0x28处,另外一份在-0x8(%rbp)处,最终检测canary值是否被修改

当参数少于7个时, 参数从左到右放入寄存器: rdi, rsi, rdx, rcx, r8, r9。当参数为7个以上时, 前 6 个与前面一样, 但后面的依次从 “右向左” 放入栈中,即和32位汇编一样。xor %fs:0x28,%rdx,如果没有被修改直接返回,如果被修改了就跳到_stack_chk_fail中去。

linux64位汇编参数传递方式:参数个数大于 7 个的时候 H(a, b, c, d, e, f, g, h); a->%rdi, b->%rsi, c->%rdx, d->%rcx, e->%r8, f->%r9 h->8(%esp) g->(%esp) call H

最后的最后,欢迎大家与我多多交流,vx号:nicholas_mcc

Posts: 4

Participants: 2

Read full topic

一个基于GoogleAdscence的刷量系统的设计与实现

$
0
0

@makdbb wrote:

之前在测试googleAdscence的时候发现,在正常的情况下,googleAdsence刷量的收入大概会在2美金左右。如果加上抹机以及代理切换等手段,最终产生的收益可能会更高。但是这里涉及到的问题最终使策略对抗的问题。单机能够能够持续性的刷出2美金左右的收益。这里仅提出这个问题,并不去解决,原因点有三个:第一,这个是核心生产力和竞争力。第二,这个是需要大量的测试模型和规则来去验证获得最终的可行规则。第三,最终可行规则本身可能会变,因此需要不停的去重复第二步,不停的更新最终可行规则,策略对抗就是这么麻烦。其本质都是人与机器的区分。

 此文章仅是一个入门,依靠此文章中讲解的技术和方式可获得很少量的收益,想要获得更大量的收益需要自行进行探索和研究。

1. 设计篇

 adscence 刷量本质还是在于如何去解放双手。不用人去刷pv/uv,机器来去操作即可。顺着这个思路去走即可。

 那么下一个问题就应该是如何让adsence发现不了你是机器人或者机器。这个不做深入讨论,有兴趣可做交流讨论。

  设计上,你可以有一个网站,或者站群或者多个站群,这是前提条件。那还需要的就是一个可以自动刷广告的机器,这里会以ios正常开发中的WKWebView为例来实现。

  此类模式中最常见的还有刷移动端广告的喝水app,跑步app,轻游戏等app。均为名义上的跑步,喝水等,实为刷广告为app主赚钱。

2. 实现篇

  实现上基本的你得有一个网站,一个googleadsence账号,并为网站设置googleadsence广告。

  另外,得自己做一个app来打开网站去加载谷歌广告,并不定时的点击。

  本着以下设计原则来简单讲解下简单的刷广告规则:

  1. 如何创建这样一个可以自动加载网站的应用。

  这里用WKWebView来简单实现一个app可以自动加载页面。

  1. 如何设定策略,让一个自动拉取网站的页面,页面之间的关联性如何保证。

  这里需要注意的第一个是ip,第二个是ua(user-agent),第三个是Referer。因此,大型的刷量系统必然会设计到很多的ip代理/vpn。第二ua好说很好解决。第三referer就是要保证页面之间的关联性。需要设置合理的referer。

  1. 如何自动点击广告,规则如何。

 这里需要测算的,当然你下设置固定的值,实时观察,待每日广告收益更新的时候可以取适当的调整。

 简单从代码层面讲一下如何去做app:

 这里是设置一个状态信息,以便实时观察到正在跑什么:

m_statusLabel  = [[UILabel alloc] initWithFrame:CGRectMake(self.view.frame.size.width/4, 50, self.view.frame.size.width/2, 30)];
    [self.view addSubview:m_statusLabel];
    m_statusLabel.backgroundColor = [UIColor blackColor];
    [m_statusLabel setTextColor:[UIColor whiteColor]];
    [m_statusLabel setAdjustsFontSizeToFitWidth:true];
    [m_statusLabel setTextAlignment:NSTextAlignmentCenter];

这里是一些其他的方法,大家参考下:

-(void)startRequestWithUA:(NSString*)userAgent lastUrl:(NSString*) lastUrl url:(NSString*)url viewTimes:(NSNumber*)viewTimes{
    m_WebView.customUserAgent = userAgent;
    NSLog(@"url=%@,last_url=%@,view_times=%@", url, lastUrl, viewTimes);
    // 请求网址
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url]];
    [request setValue:lastUrl forHTTPHeaderField:@"Referer"];
    [m_WebView loadRequest:request];
    m_WebView.UIDelegate = self;
    m_WebView.navigationDelegate = self;
    
    m_reloadTimes = [viewTimes intValue];
    
    // 开启定时器跑任务
    int randomTime = [Utility GetRandomTime];
    m_Timer = [NSTimer scheduledTimerWithTimeInterval:randomTime target:self selector:@selector(Timered:) userInfo:nil repeats:NO];
    
    
    // 添加控件
    NSLog(@"-- web view reloaded.");
    NSString *msgText  = [NSString stringWithFormat:@"浏览器加载完成, 已加载%d次,%d秒后再次加载!", m_reloadTimes,randomTime];
    m_statusLabel.text = msgText;
}
/* 页面加载完成 */
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation{
    NSLog(@"load finished-----------------");
    // 得延迟执行
    int randomTime = [Utility GetRandomNum:4] + 1;
    [NSTimer scheduledTimerWithTimeInterval:randomTime target:self selector:@selector(scrollView:) userInfo:nil repeats:NO];
}
/* 在发送请求之前,决定是否跳转 */
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler{
    if (navigationAction.targetFrame == nil) {
        [webView loadRequest:navigationAction.request];
    }
    //允许跳转
    decisionHandler(WKNavigationActionPolicyAllow);
    //不允许跳转
    //decisionHandler(WKNavigationActionPolicyCancel);
}
-(WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures
{
    NSLog(@"createWebViewWithConfiguration");
    if (!navigationAction.targetFrame.isMainFrame) {
        [webView loadRequest:navigationAction.request];
    }
    return nil;
}
- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 配置WKWebView
    WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
    m_WebView = [[WKWebView alloc] initWithFrame:self.view.frame configuration:config];
    [self.view addSubview:m_WebView];
    [self initCtrl];
    
    LoadConfigApi* loadConfigApi = [[LoadConfigApi alloc] initWithToken:TEST_TOKEN netType:TEST_NET_TYPE];
    [loadConfigApi startWithCompletionBlockWithSuccess:^(YTKBaseRequest *request) {
        NSLog(@"request succeed with string %@",request.responseString);
        NSString * ua =[[request.responseObject objectForKey:@"data"] objectForKey:@"ua"];
        NSString * url =[[request.responseObject objectForKey:@"data"] objectForKey:@"url"];
        NSString * lastUrl =[[request.responseObject objectForKey:@"data"] objectForKey:@"last_url"];
        NSNumber* viewTimes =[[request.responseObject objectForKey:@"data"] objectForKey:@"view_times"];
        
        [self startRequestWithUA:ua lastUrl:lastUrl url:url viewTimes:viewTimes];
    } failure:^(YTKBaseRequest *request) {
        // 你可以直接在这里使用 self
        NSLog(@"request failed with response string %@", request.responseString);
    }];
}
-(void)clickView:(NSTimer *)timer{
//    NSInteger pointId = [PTFakeMetaTouch fakeTouchId:[PTFakeMetaTouch getAvailablePointId] AtPoint:CGPointMake(30,30) withTouchPhase:UITouchPhaseBegan];
//    [PTFakeMetaTouch fakeTouchId:pointId AtPoint:CGPointMake(30,30) withTouchPhase:UITouchPhaseEnded];
    NSString *jsString = @"document.elementFromPoint(100, 100).click();";
    [m_WebView evaluateJavaScript:jsString completionHandler:^(id object, NSError * _Nullable error) {
        NSLog(@"obj:%@---error:%@", object, error);
    }];
    
}
-(void)scrollView:(NSTimer *)timer{
    [m_WebView.scrollView setContentOffset:CGPointMake(0, [Utility GetRandomNum:5]*100 + 500) animated:YES];
    [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(clickView:) userInfo:nil repeats:NO];
}
- (void)Timered:(NSTimer*)timer {
    
    LoadConfigApi* loadConfigApi = [[LoadConfigApi alloc] initWithToken:TEST_TOKEN netType:TEST_NET_TYPE];
    [loadConfigApi startWithCompletionBlockWithSuccess:^(YTKBaseRequest *request) {
        NSLog(@"request succeed with string %@",request.responseString);
        NSString * ua =[[request.responseObject objectForKey:@"data"] objectForKey:@"ua"];
        NSString * url =[[request.responseObject objectForKey:@"data"] objectForKey:@"url"];
        NSString * lastUrl =[[request.responseObject objectForKey:@"data"] objectForKey:@"last_url"];
        NSNumber* viewTimes =[[request.responseObject objectForKey:@"data"] objectForKey:@"view_times"];
        [self startRequestWithUA:ua lastUrl:lastUrl url:url viewTimes:viewTimes];
    } failure:^(YTKBaseRequest *request) {
        // 你可以直接在这里使用 self
        NSLog(@"request failed with response string %@", request.responseString);
    }];
}

 大家对完整代码有兴趣的话可以加我微信nicholas_mcc,完整工程可以分享的。

Posts: 1

Participants: 1

Read full topic

通过 IDA Script 自动化解密 unc0ver-431 的字符串异或混淆

$
0
0

@Soulghost wrote:

背景

到目前为止 iOS 13 的越狱方案中,只有 tfp0 予以公开,后续步骤均为闭源jakeajamesjelbrekLib 最近刚刚增加了 iOS 13 的支持)。在研究 iOS 13 从 tfp0 到 jailbreak 的过程中发现了诸多困难,iOS 12 的 setuid 提权以及劫持虚函数的 kexec 都有所变化。考虑到从 XNU Source 和 kernelcache 中寻找线索需要消耗大量时间,我决定去逆一波 unc0ver 找线索(抄作业)。

然而 unc0ver 为了防止黑恶势力利用,对控制流和字符串都做了混淆,混的亲妈都不认识了 (╯°□°)╯︵ ┻━┻。

寻找 kexec 的线索

为了找到 unc0ver 处理 iOS 13 kexec 的代码,我从 IOConnectTrap6 的 XREF 找到了许多调用点,打算从这些调用点去寻找 init_kexec 的代码,但是发现字符串都做了异或处理,为了找线索就不得不还原这些字符串:

简单看一眼代码可知是最基本的异或混淆,在编译期通过异或修改了静态字符串的值,在函数开始时再重新异或一次来还原:

那么解决方案也就简单了,把这个异或操作重新运算到这些字符串的值上面即可。

自动化解密

思路

要实现自动化解密,一个简单的方法是对执行运行时异或解密的代码进行静态分析,并模拟寄存器状态和内存的值,我们观察一段相关代码:

__text:000000010007A928                 ADRP            X8, #aA_4@PAGE ; "A�h�������is�a�̙��|���.�����=�������pT"...
__text:000000010007A92C                 ADD             X8, X8, #aA_4@PAGEOFF ; "A�h�������is�a�̙��|���.�����=�������pT"...
__text:000000010007A930                 ADRP            X9, #asc_10019DDF0@PAGE ; "\x1B��B� Uu�����\x1B�B������� �����aI�"...
__text:000000010007A934                 ADD             X9, X9, #asc_10019DDF0@PAGEOFF ; "\x1B��B� Uu�����\x1B�B������� �����aI�"...
__text:000000010007A938                 ADRP            X10, #byte_10019DDB0@PAGE
__text:000000010007A93C                 ADD             X10, X10, #byte_10019DDB0@PAGEOFF
__text:000000010007A940                 ADRP            X11, #byte_10019DD70@PAGE
__text:000000010007A944                 ADD             X11, X11, #byte_10019DD70@PAGEOFF
__text:000000010007A948                 ADRP            X12, #str_IOGraphicsAccelerator2@PAGE
__text:000000010007A94C                 ADD             X12, X12, #str_IOGraphicsAccelerator2@PAGEOFF
__text:000000010007A950                 LDRB            W13, [X12]
__text:000000010007A954                 MOV             W14, #0x64
__text:000000010007A958                 EOR             W13, W13, W14
__text:000000010007A95C                 AND             W13, W13, #0xFF
__text:000000010007A960                 STRB            W13, [X12]

可以发现这里的代码非常简单,只用到了少量指令,要模拟这些指令是非常简单的。

实现

状态模拟

我目前只分析了 sub_10007A8F8,其中只用到了以下这些指令:

handlers = {
    'ADRP': handle_adrp,
    'ADD': handle_add,
    'LDRB': handle_ldrb,
    'STRB': handle_strb,
    'MOV': handle_mov,
    'EOR': handle_eor,
    'AND': handle_and,
    'STUR': handle_stur,
    'LDUR': handle_ldur
}

其中要注意的是 ST*RLD*R 可能会操作 stack,这时候我们需要特殊判断并且模拟栈内存,我采用了一个很偷懒的模拟办法,那就是检测 [x29, var_xx] 的表达式,然后读写一个模拟 memory 的 dict,其中 key=xx。

寄存器只需要处理 X 和 W,这里用 X 来存储;栈上内存用 dict 来存储:

x = [None] * 31
for i in range(31):
    x[i] = RegX(i)

x[29].writeX(0)

# simulator stack memory
memory = {}

静态分析

通过 IDAPython 提供的 API 从特定地址读取 asm 然后分析即可:

def u0_xorpatch(startAddr, endAddr):
    if endAddr < startAddr:
        raise Exception('invalid input params')
    print('xorpath from {} to {}'.format(hex(startAddr), hex(endAddr)))
    cursor = startAddr
    for _ in range(1 + (endAddr - startAddr) // 4):
        mnem = GetMnem(cursor)
        print('[*] {} {}'.format(hex(cursor), GetDisasm(cursor)))
        if mnem in handlers:
            handlers[mnem](cursor)
        else:
            print('[-] Warn: unresolved mnem ' + mnem + ' at ' + str(hex(cursor)))
            raise Exception('unresolved mnem ' + mnem + ' at ' + str(hex(cursor)))
        cursor += 4

if __name__ == '__main__':
    u0_xorpatch(0x10007A928, 0x10007BC7C)

执行效果

在完成脚本后,我尝试解密了 sub_10007A8F8 中的所有字符串,解密段的起始和终止位置非常好找,它是函数开场白后很长的一段代码块:

找到了起始地址和结束地址,将他们写入脚本的入参,然后通过 File -> Script File 加载,这里实验前建议先备份 idb,以防止 binpatch 后写坏数据库,最终效果为:


完整实现

Posts: 6

Participants: 3

Read full topic

Viewing all 301 articles
Browse latest View live