技术 · 2024年10月15日

IOS逆向入门与技巧分享-以ByteCTF的极限逃脱为例

前言

在CTF中ios逆向是一个冷门的方向,赛题很少有见到。

最近字节跳动的ByteCTF则是出现了一道ios的题目,于是现比现学。

ios一般逆向流程

一般来说流程为 脱壳->class-dump-抓包->逆向分析

然后分析软件的核心算法,这些步骤基本就和正常逆向区别不大了。

苹果市场会对应用进行加壳。可以使用frida进行dump脱壳。

ios的一些函数可以在Apple Development上进行查找

常用工具

手机选型

对于Android,推荐使用Pixel/Nexus系列,系统版本6~13,通过Magisk进行root。

对于iOS,推荐使用iPhone 6、iPhone 8/X/SE等,系统版本13或14,购买时注意是否有ID锁,是否可以刷机。

手机软件

iOS越狱后,可以通过Cydia安装各种插件,如:

Apple File Conduit”2″:激活助手类工具的访问权限。

AppSync Unified:绕过应用签名验证。

Filza File Manager:文件管理器。

SSL Kill Switch 2:iOS版的justtrustme。

OpenSSH:用于电脑连接。

电脑工具

IDA:经典软件,不多解释

ifunbox:用于iPhone的文件管理。

frida:在电脑上也需要安装frida。

frida-ios-dump:一键脱壳工具。

class-dump:提取头文件。

iproxy:端口转发工具。

ByteCTF极限逃脱解法

字节跳动这道题直接给了ipa文件,所以我们直接解压ipa后IDA进行分析即可。

逆向分析逻辑

首先我们会进到第一个bottom函数

bottom1

void __cdecl -[ViewController firsButtonClicked:](ViewController *self, SEL a2, id a3)
{
  uint32_t v4; // w20
  const char *v5; // x1
  void *v6; // x0
  id v7; // x21
  const char *v8; // x1
  void *v9; // x0
  id v10; // x22
  const char *v11; // x1
  void *v12; // x23
  const char *v13; // x1
  void *v14; // x0
  id v15; // x20
  const char *v16; // x1
  void *v17; // x0
  id v18; // x21
  const char *v19; // x1
  const char *v20; // x1
  const char *v21; // x1
  void *v22; // x0
  id v23; // x22
  const char *v24; // x1
  const char *v25; // x1
  void *v26; // x0
  id v27; // x22
  const char *v28; // x1
  const char *v29; // x1
  void *v30; // x0
  id v31; // x19
  const char *v32; // x1
  void *v33; // x0
  const char *v34; // x1
  void *v35; // x0
  const char *v36; // x1
  const char *v37; // x1

  v4 = arc4random_uniform(0x1F4u);              //  macOS 和 iOS 系统中用于生成随机数的标准库函数
                                                // 
  v6 = objc_msgSend$firstInput(self, v5);
  v7 = objc_retainAutoreleasedReturnValue(v6);
  v9 = objc_msgSend$text(v7, v8);
  v10 = objc_retainAutoreleasedReturnValue(v9);
  v12 = objc_msgSend$integerValue(v10, v11);
  objc_release(v10);
  objc_release(v7);
  if ( v12 == (void *)v4 )                      // v4随机数rand
  {
    v14 = objc_msgSend$alertControllerWithTitle:message:preferredStyle:(
            &OBJC_CLASS_$_UIAlertController,
            v13,
            CFSTR("提示"),
            CFSTR("恭喜你拿到入场券"),
            1LL);
    v15 = objc_retainAutoreleasedReturnValue(v14);
    v17 = objc_msgSend$actionWithTitle:style:handler:(
            &OBJC_CLASS_$_UIAlertAction,
            v16,
            CFSTR("确定"),
            0LL,
            &__block_literal_global.58);
    v18 = objc_retainAutoreleasedReturnValue(v17);
    objc_msgSend$addAction:(v15, v19, v18);
    objc_msgSend$presentViewController:animated:completion:(self, v20, v15, 1LL, 0LL);
    v22 = objc_msgSend$inputText(self, v21);
    v23 = objc_retainAutoreleasedReturnValue(v22);
    objc_msgSend$setHidden:(v23, v24, 0LL);
    objc_release(v23);
    v26 = objc_msgSend$noticeLabel(self, v25);
    v27 = objc_retainAutoreleasedReturnValue(v26);
    objc_msgSend$setHidden:(v27, v28, 0LL);
    objc_release(v27);
    v30 = objc_msgSend$secondButton(self, v29);
    v31 = objc_retainAutoreleasedReturnValue(v30);
    objc_msgSend$setHidden:(v31, v32, 0LL);
    objc_release(v31);
  }
  else
  {
    v33 = objc_msgSend$alertControllerWithTitle:message:preferredStyle:(
            &OBJC_CLASS_$_UIAlertController,
            v13,
            CFSTR("提示"),
            CFSTR("咒语错误"),
            1LL);
    v15 = objc_retainAutoreleasedReturnValue(v33);
    v35 = objc_msgSend$actionWithTitle:style:handler:(
            &OBJC_CLASS_$_UIAlertAction,
            v34,
            CFSTR("确定"),
            0LL,
            &__block_literal_global.53);
    v18 = objc_retainAutoreleasedReturnValue(v35);
    objc_msgSend$addAction:(v15, v36, v18);
    objc_msgSend$presentViewController:animated:completion:(self, v37, v15, 1LL, 0LL);
    NSLog(&stru_10000C408.isa);
  }
  objc_release(v18);
  objc_release(v15);
}

这里我们简单分析一下就是输入一个数与rand的一个随机数进行校验,然后进入第二部分。

接下来是比较重要的第二部分

flag的输入

flag输入的整体逻辑,首先会对非法字符进行筛选,然后是提取字符串中的子字符串。代码会去除掉flag头和花括号以及-。

 v4 = objc_msgSend$inputText(self, a2, a3);
  v5 = objc_retainAutoreleasedReturnValue(v4);
  v7 = objc_msgSend$text(v5, v6);
  v8 = objc_retainAutoreleasedReturnValue(v7);
  objc_release(v5);
  v139 = 0LL;
  v10 = objc_msgSend$regularExpressionWithPattern:options:error:(
          &OBJC_CLASS_$_NSRegularExpression,
          v9,
          CFSTR("^ByteCTF\\{([0-9a-z]){8}-([0-9a-z]){4}-([0-9a-z]){4}-([0-9a-z]){4}-([0-9a-z]){12}\\}$"),
          1LL,
          &v139);
  v11 = objc_retainAutoreleasedReturnValue(v10);
  if ( objc_msgSend$length(v8, v12) )
  {
    v14 = objc_msgSend$length(v8, v13);
    v16 = objc_msgSend$firstMatchInString:options:range:(v11, v15, v8, 1LL, 0LL, v14);
    v17 = objc_retainAutoreleasedReturnValue(v16);
    objc_release(v17);
    if ( v17 )
    {
      v19 = objc_msgSend$stringByReplacingOccurrencesOfString:withString:(v8, v18, CFSTR("ByteCTF"), &stru_10000C1E8);
      v20 = objc_retainAutoreleasedReturnValue(v19);
      objc_release(v8);
      v22 = objc_msgSend$stringByReplacingOccurrencesOfString:withString:(v20, v21, CFSTR("{"), &stru_10000C1E8);
      v23 = objc_retainAutoreleasedReturnValue(v22);
      objc_release(v20);
      v25 = objc_msgSend$stringByReplacingOccurrencesOfString:withString:(v23, v24, CFSTR("}"), &stru_10000C1E8);
      v8 = objc_retainAutoreleasedReturnValue(v25);
      objc_release(v23);
      v27 = objc_msgSend$componentsSeparatedByString:(v8, v26, CFSTR("-"));
      v28 = objc_retainAutoreleasedReturnValue(v27);
      if ( (unsigned __int64)objc_msgSend$count(v28, v29) > 4 )// 输入的flag取字符串部分

构造字符串

随后是格式化字符串,构造字符串。

{
        v31 = objc_msgSend$first(self, v30);    // 1
        v138 = objc_retainAutoreleasedReturnValue(v31);
        v33 = objc_msgSend$second(self, v32);   // 2
        v34 = objc_retainAutoreleasedReturnValue(v33);
        v36 = objc_msgSend$third(self, v35);    // 3
        v37 = objc_retainAutoreleasedReturnValue(v36);
        v39 = objc_msgSend$fourth(self, v38);   // 4
        v40 = objc_retainAutoreleasedReturnValue(v39);
        v42 = objc_msgSend$fifth(self, v41);    // 5
        v132 = v40;
        v135 = v37;
        v136 = objc_retainAutoreleasedReturnValue(v42);
        v137 = v34;
        v44 = objc_msgSend$stringWithFormat:(
                &OBJC_CLASS_$_NSString,
                v43,
                CFSTR("{%@-%@-%@-%@-%@}"),      // 这里重新构造了一次字符串 5 2 3 4 5
                v136,
                v34,
                v37,
                v40,
                v136);
        v45 = objc_retainAutoreleasedReturnValue(v44);
        v131 = objc_retainAutorelease(v45);
        v47 = (const char *)objc_msgSend$UTF8String(v131, v46);
        v48 = strlen(v47);
        v31 = objc_msgSend$first(self, v30);    // 1
        v138 = objc_retainAutoreleasedReturnValue(v31);
        v33 = objc_msgSend$second(self, v32);   // 2
        v34 = objc_retainAutoreleasedReturnValue(v33);
        v36 = objc_msgSend$third(self, v35);    // 3
        v37 = objc_retainAutoreleasedReturnValue(v36);
        v39 = objc_msgSend$fourth(self, v38);   // 4
        v40 = objc_retainAutoreleasedReturnValue(v39);
        v42 = objc_msgSend$fifth(self, v41);    // 5
        v132 = v40;
        v135 = v37;
        v136 = objc_retainAutoreleasedReturnValue(v42);
        v137 = v34;
        v44 = objc_msgSend$stringWithFormat:(
                &OBJC_CLASS_$_NSString,
                v43,
                CFSTR("{%@-%@-%@-%@-%@}"),      // 这里重新构造了一次字符串 5 2 3 4 5
                v136,
                v34,
                v37,
                v40,
                v136);
        v45 = objc_retainAutoreleasedReturnValue(v44);
        v131 = objc_retainAutorelease(v45);
        v47 = (const char *)objc_msgSend$UTF8String(v131, v46);
        v48 = strlen(v47);

首先调用了五段函数,调取了字符串。

id __cdecl -[ViewController first](ViewController *self, SEL a2)
{
  return CFSTR("ffb53588");
}
id __cdecl -[ViewController second](ViewController *self, SEL a2)
{
  return CFSTR("b092");
}
id __cdecl -[ViewController third](ViewController *self, SEL a2)
{
  return CFSTR("bd3e");
}
id __cdecl -[ViewController fourth](ViewController *self, SEL a2)
{
  return CFSTR("e777");
}
id __cdecl -[ViewController fifth](ViewController *self, SEL a2)
{
  return CFSTR("a67be199da4b");
}

可以看出引用了5段字符串,我们假设他们为12345,后面进行了5-2-3-4-5的构造方式,最后构造出字符串,{a67be199da4b-b092-bd3e-e777-a67be199da4b}

随后对他进行了CC_SHA256编码,标准的sha256编码

CC_SHA256(v47, v48, (unsigned __int8 *)md);// 字符串sha256
        v50 = objc_msgSend$string(&OBJC_CLASS_$_NSMutableString, v49);
        v52 = objc_retainAutoreleasedReturnValue(v50);
        for ( i = 0LL; i != 32; ++i )
          objc_msgSend$appendFormat:(v52, v51, CFSTR("%02x"), (unsigned __int8)md[i]);// 字符串追加到接收器

6c9838a3c6810bdb2633ed5910b8547c09a7a4c08bf69ae3a95c5c37f9e8f57e 得到编码后的字符串

然后进行一些列的字字符串提取和字母置换

最后根据他一个函数在进行更换

重点关注substringWithRange

这个函数的作用就是,从数组下标i截取len长度的字符

已知长度为 8 4 4 4

第一段 1-9 长度为8

第二段 9 9+4 长度为4

第三段 5 5+4 长度为4

第四段 5 5+4 长度为4

第五段 5 5+12 长度为12

取了5次子串 并且置换字母

flag="6c9838a3c6810bdb2633ed5910b8547c"
print("ByteCTF{",end="")
print(flag[1:9],end="-")
print(flag[9:9+4],end="-")
print(flag[5:5+4],end="-")
print(flag[5:5+4],end="-")
print(flag[5:5+12],end="}")
#ByteCTF{c9838a3c-6810-8a3c-8a3c-8a3c6810bdb2}

#ByteCTF{c9838b3c-6810-8a3d-8a3c-8a3c6810bdb2} 

ByteCTF{c9838b3c-6810-8a3d-8a3c-8a3c6810bdb2}

总结

ios的逆向和安卓逆向的流程大差不差,只是文件格式和库函数上有些许区别。如果对安卓很熟悉的话,入手ios逆向或许对师傅们并不算一件难事。

首发于先知论坛

苏ICP备2024067700号 | 苏公网安备32098202000238号