前言
在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逆向或许对师傅们并不算一件难事。
首发于先知论坛