small-q 发表于 2020-11-24 12:26:50

iCleaner Pro 网验与注册算法分析

本帖最后由 small-q 于 2020-12-4 18:22 编辑

0x00 本文缘起:      
   此App用来给iOS越狱机器清理系统垃圾,用了的人都感觉良好。之前不懂iOS应用破解时就是找别人修改好的版本用,导致不能及时用上新版,后来通过学习《iOS应用逆向与安全之道》一书了解iOS破解后有了自己破解的想法,当时信心满满咨询了C版,等我还没回过神来,C版已经分析写好注册机形式的插件直接注册了,但存在一个联网就注册失效的问题,之前的做法就是断网使用,这是去年之前的小经历,出于工作忙的原因我也没再分析,直到今天又想起此事,又重新捡起这个问题用软件,发现注册也失效了,然后跟C版要了之前注册插件的源进行参考分析,在此感谢C版。

0x01 所需工具:
      * MacBook Pro(或虚拟机)      
      * iPhone7(iOS13.5)
      * IDA Pro
      * Theos
      * 《iOS应用逆向与安全之道》一书

0x02 探测敌情:
   分析一款应用之前,先是进行探测,看看应用有什么限制之类。下载最新版``v7.9.0``进入App,需要注册、广告开启等等...,如下图所示。


0x03 寻找入口:
   使用爱思助手导出相关文件(越狱软件一般都没有加壳)。如下图所示。


然后把主文件拖到IDA进行分析,我的习惯就是对函数窗口进行关键字搜索。这里用``License``关键字试试,果不然奇然,有结果,如下图所示。


第一个方法存在``checkLicense``这种敏感字眼,进去看看伪代码(F5),幸福来得有点突然,貌似这里就是检查注册与广告处理。如下图所示。


至此,切入点就找到了,此处的``sub_1000A8124``应该是注册控制流程的关键了。接下来进行详细分析。


0x04 网络验证:
   进入``sub_1000A8124()``,流程由``qword_100648CB0``全局变量控制,继续对其查找交叉引用,如下图所示。


找到两处赋值方法调用!666啊,进展非常顺利,如下图片所示。


先看``sub_100066740``函数并对其查找交叉引用,如下图所示。

结果如下图所示。


``sub_100065D50``函数伪代码如下:
void __fastcall sub_100065D50(double a1)
{
double v1;
//为了文章好看,略掉
void *v27;
__int64 v28;
dispatch_time_t v29;
if ( !(byte_100647680 & 1) )
{
    v1 = a1;
    byte_100647681 = 0;
    byte_100647680 = 1;
    objc_msgSend(&OBJC_CLASS___NSNotificationCenter, "defaultCenter");
    v2 = (void *)objc_retainAutoreleasedReturnValue();
    objc_msgSend(v2, "postNotificationName:object:", CFSTR("FILETYPEMSG"), 0LL);
    objc_release(v2);
    if ( v1 <= 0.0 )
      v1 = 60.0;
    v4 = (void *)objc_alloc(&OBJC_CLASS___NSString, v3);
    sub_100065A00((__int64)v4, v5);
    v6 = objc_retainAutoreleasedReturnValue();
    v7 = objc_msgSend(v4, "initWithFormat:", CFSTR("%c%@%s%@"), 0x75LL, CFSTR("di"), "d=", v6);// did机器码
    objc_release(v6);
    objc_msgSend(
      &OBJC_CLASS___NSString,
      "stringWithFormat:",
      CFSTR("%s%c%s%c%c%@%c%s%@%c%s%c%c%@%s%c%s%@%s%c%@%s%c%s%c"),
      "ht",
      0x74LL,
      "ps:",
      0x2FLL,
      0x2FLL,
      CFSTR("ib"),                              // 呵呵,作者有心了,防字符串搜索快速定位
      0x2DLL,
      "so",
      CFSTR("ft"),
      0x2ELL,
      "ne",
      0x74LL,
      0x2FLL,
      CFSTR("icle"),
      "aner",
      0x2FLL,
      "che",
      CFSTR("ckRe"),
      "gist",
      0x72LL,
      CFSTR("ati"),
      "on",
      0x2ELL,
      "ph",
      0x70LL);                                  //https://ib-soft.net/icleaner/checkRegistration.php
    v8 = objc_retainAutoreleasedReturnValue();
    objc_msgSend(CFSTR("PO"), "stringByAppendingString:", CFSTR("ST"));// POST 请求
    v9 = objc_retainAutoreleasedReturnValue();
    objc_msgSend(
      &OBJC_CLASS___NSURLRequest,
      "requestWithURLString:withHTTPMethod:withHTTPBodyString:withTimeout:",
      v8,
      v9,
      v7,
      v1);                                    // 联网构造
    v10 = objc_retainAutoreleasedReturnValue();
    objc_release(v9);
    objc_release(v8);
    objc_release(v7);
    if ( !v10 )
      exit(1);
    v12 = (void *)objc_alloc(&OBJC_CLASS___NSURLConnection, v11);
    if ( !qword_100647690 )
    {
      v13 = objc_msgSend(&OBJC_CLASS___NSObject, "class");
      v14 = objc_allocateClassPair(v13, "ICRC", 0LL);
      v15 = objc_retain();
      class_addProtocol();
      protocol_getMethodDescription(v15, "connection:didReceiveResponse:", 0LL, 1LL);
      class_addMethod(v14, "connection:didReceiveResponse:", sub_1000666F0, v16);
      protocol_getMethodDescription(v15, "connection:didReceiveData:", 0LL, 1LL);
      class_addMethod(v14, "connection:didReceiveData:", sub_100066728, v17);
      protocol_getMethodDescription(v15, "connectionDidFinishLoading:", 0LL, 1LL);
      class_addMethod(v14, "connectionDidFinishLoading:", sub_100066740, v18);// 引用到此处
      v19 = objc_retain();
      objc_release(v15);
      class_addProtocol();
      protocol_getMethodDescription(v19, "connection:didFailWithError:", 0LL, 1LL);
      class_addMethod(v14, "connection:didFailWithError:", sub_100066838, v20);
      objc_registerClassPair(v14);
      v22 = (void *)objc_alloc(v14, v21);
      v23 = objc_msgSend(v22, "init");
      v24 = qword_100647690;
      qword_100647690 = (__int64)v23;
      objc_release(v24);
      objc_release(v19);
    }
    v25 = objc_retain();
    v26 = v25;
    v27 = objc_msgSend(v12, "initWithRequest:delegate:", v10, v25);
    v28 = qword_100647678;
    qword_100647678 = (__int64)v27;
    objc_release(v28);
    objc_release(v26);
    objc_msgSend((void *)qword_100647678, "start");
    v29 = dispatch_time(0LL, (signed __int64)(v1 * 1000000000.0));
    dispatch_after(v29, &_dispatch_main_q, &off_1004C6C38);
    objc_release(v10);
}
}


从上面的伪代码可以看出``sub_100065D50``内部方法就是一个网络检测函数,在此请各位看官想想应对方案(可不能像我之前一样断网了哦)
ok,网验已找到,接着分析注册算法部分。

0x05 本地算法:
   继之前所说的两处赋值方法的第二处,继续查找引用,如下图所示。


逐个查看后,直接定位算法核心``sub_1000663D4``,算法分析请看代码注释部分:
void __fastcall sub_1000663D4(__int64 a1, __int64 a2)
{
void *v2; // x19
__int64 v3; // x20
__int64 v4; // x20
__int64 v5; // x21
__int64 v6; // x22
__int64 v7; // x23
__int64 v8; // x0
void *v9; // x24
const char *v10; // x25
char *v11; // x24
__int64 v12; // x27
char *v13; // x26
void *v14; // x20
__int64 v15; // x19
__int64 v16; // x0
__int64 v17; // x1
__int64 v18; // x21
__int64 v19; // x20
void *v20; // x0
void *v21; // x19
char *v22; // x0
char v23; //

if ( !qword_100648CB8 )
{
    sub_100065A00(a1, a2);
    v2 = (void *)objc_retainAutoreleasedReturnValue();
    if ( (unsigned __int64)objc_msgSend(v2, "length") <= 0x27 )
    {
      objc_msgSend(v2, "stringByPaddingToLength:withString:startingAtIndex:", 0x28LL, CFSTR("0"), 0LL);
      v3 = objc_retainAutoreleasedReturnValue();
      objc_release(v2);
      v2 = (void *)v3;
    }
    objc_msgSend(v2, "substringWithRange:", 0x12LL, 5LL);// uid 取 0x12 开始 5位 即: 10b15
    v4 = objc_retainAutoreleasedReturnValue();
    objc_msgSend(v2, "substringToIndex:", 0x1BLL);// uid 从首位开始切片取值(大小0x1B) 即: 9c017b111b006b555a10b15baad
    v5 = objc_retainAutoreleasedReturnValue();
    objc_msgSend(v2, "substringWithRange:", 0xDLL, 0xALL);// uid 取 0xD 开始 0xA位 即: b555a10b15
    v6 = objc_retainAutoreleasedReturnValue();
    objc_msgSend(v2, "substringFromIndex:", 0x1FLL);// uid 从0x1F开始切片取值 即: 6efa7875b
    v7 = objc_retainAutoreleasedReturnValue();
    objc_msgSend(&OBJC_CLASS___NSString, "stringWithFormat:", CFSTR("%@%@%@%@"), v4, v5, v6, v7);// 组合成一串:10b159c017b111b006b555a10b15baadb555a10b156efa7875b
    v8 = objc_retainAutoreleasedReturnValue();
    v9 = (void *)objc_retainAutorelease(v8);
    v10 = (const char *)objc_msgSend(v9, "UTF8String");
    objc_release(v9);
    strlen(v10);
    CC_MD5();                                 // md5计算结果就是注册码
    v11 = (char *)malloc(0x21uLL);
    v12 = 0LL;
    v13 = v11;
    do
      v13 += snprintf(v13, 0x21uLL, "%02x", (unsigned __int8)v23);
    while ( v12 != 0x10 );
    qword_100648CB8 = (__int64)v11;
    objc_release(v7);
    objc_release(v6);
    objc_release(v5);
    objc_release(v4);
    objc_release(v2);
}
if ( !qword_100648CB0 )                     // 比较
{
    objc_msgSend(&OBJC_CLASS___NSUserDefaults, "standardUserDefaults");
    v14 = (void *)objc_retainAutoreleasedReturnValue();
    objc_msgSend(v14, "stringForKey:", CFSTR("lastRequestValue"));
    v15 = objc_retainAutoreleasedReturnValue();
    v16 = objc_release(v14);
    if ( !v15 )
    {
      sub_100066B74(v16, v17);                  // 注册文件检查位置
      v18 = objc_retainAutoreleasedReturnValue();
      objc_msgSend(&OBJC_CLASS___NSString, "stringWithContentsOfFile:encoding:error:", v18, 1LL, 0LL);
      v19 = objc_retainAutoreleasedReturnValue();
      objc_release(0LL);
      objc_release(v18);
      sub_100066ADC();
      v15 = v19;
    }
    v20 = (void *)objc_retainAutorelease(v15);
    v21 = v20;
    v22 = (char *)objc_msgSend(v20, "UTF8String");
    sub_10006683C(v22);                         // 引用到此处
    objc_release(v21);
}
}
// 注册文件检查位置
__int64 __fastcall sub_100066B74(__int64 a1, __int64 a2)
{
void *v2; // x0

v2 = (void *)objc_alloc(&OBJC_CLASS___NSString, a2);
objc_msgSend(
    v2,
    "initWithFormat:",
    CFSTR("%c%s%c%c%@%s%c%c%@%s%c%@%s%c%s%c%@%s%c%@%c"),
    0x2FLL,
    "va",
    0x72LL,
    0x2FLL,
    CFSTR("mob"),
    "ile",
    0x2FLL,
    0x4CLL,
    CFSTR("ibra"),
    "ry",
    0x2FLL,
    CFSTR("iCle"),
    "aner",
    0x2FLL,
    "li",
    0x63LL,
    CFSTR("en"),
    "se",
    0x2ELL,
    CFSTR("cache"),
    0x64LL);
return objc_autoreleaseReturnValue();
}



0x06 插件代码:
算法分析完毕了哦! 是时候把码了:
#import <substrate.h>
#include <mach-o/dyld.h>
#import <dlfcn.h>
#import <CommonCrypto/CommonDigest.h>
#include <unistd.h>
#include <sys/types.h>
#include <mach/mach.h>
%hook NSURL
- (id)initWithString:(NSString*)url {
      if ()
      {
      NSLog(@"[+] 网络访问->:%@",url);
                %log;
                /* code */
                return %orig(@"http://127.0.0.1");
      }
      return %orig(url);
}
%end

static NSString *uniqueDeviceID(void) {
    static CFStringRef (*$MGCopyAnswer)(CFStringRef);
    void *gestalt = dlopen("/usr/lib/libMobileGestalt.dylib", RTLD_GLOBAL | RTLD_LAZY);
    $MGCopyAnswer = (dlsym(gestalt, "MGCopyAnswer"));
    return (__bridge NSString *)$MGCopyAnswer(CFSTR("UniqueDeviceID"));
}

static NSString *md5(NSString *str) {
   const char *cStr = ;
      unsigned char result;
      CC_MD5( cStr, strlen(cStr),result );
      NSMutableString *hash =;
      for (int i = 0; i < 16; i++) {
                ];
      }
      return hash;
}

static void genLicenseFile(NSString* hash) {
    // NSLog(@"[+]Hash:%@",hash);
    NSLog(@"[+] 写入注册信息");
    ;
}

%ctor
{
    NSString* udid = uniqueDeviceID();
    NSLog(@"[+] 获取 did->:%@",udid);
    NSString* tmpStr = ,,,];
    NSLog(@"[+] 取值 tmpStr->:%@",tmpStr);
    NSString* hash = md5(tmpStr);
    NSLog(@"[+] 计算 MD5_hash->:%@",hash);
    [ setObject:hash forKey:@"lastRequestValue"];
    genLicenseFile(hash);
}


编译安装:


0x07 光明之巅:


运行日志如下图所示。



完美注册!不再联网验,感谢C版!


0x08 注意事项:
   * 尽量用新版分析,生成的插件老版可能不会加载!
   * 之前说的失效原因是新版更改了bundleId,添加进去即可:
{
      Filter = {
                Bundles = (
                        "com.exile90.icleanerpro",
                        "com.ivanobilenchi.icleaner",
                );
      };
}
* 不提供bin文件,感谢观看。

希望你学有所用,简单而优雅的分析希望得到你的认可,有错误欢迎指正,谢谢!


机械工业出版社正版购买通道:

https://item.jd.com/12800426.html

1.凡是通过以上链接购买,均可换取飘云阁安全论坛邀请码1枚(或飘云币200枚)。具体换取方式请在本公众号回复:换取

2.根据实际情况可安排少量签名版。具体购买方式请在本公众号回复:签名版


yyjpcx 发表于 2020-11-24 13:23:01

iOS的软件 能做到怎么优雅 望尘莫及   膜拜!~~~

朦胧的睡了 发表于 2020-11-24 13:30:48

看到IDA就膜拜了,还是个IOS的牛B可拉斯

smallhorse 发表于 2020-11-24 13:38:54

弱弱的问一句:这码是go?

Rooking 发表于 2020-11-24 17:13:33

我说怎么C++把码破解ISO了{:lol:}

wgz001 发表于 2020-11-24 20:05:32

我13.3测试失败什么时候解决一下{:lol:}

cxj98 发表于 2020-11-24 20:30:07

很6b的样子。

小七柒 发表于 2020-11-25 10:14:13

优秀,支持大神。

7Loopy 发表于 2020-11-25 16:14:27

a12以上设备udid只有25位,已经用其他方式实现了{:biggrin:}

风轻云淡 发表于 2020-11-25 19:39:09

可惜了,手中没苹果手机~
页: [1] 2 3 4 5 6 7 8
查看完整版本: iCleaner Pro 网验与注册算法分析