type
status
date
slug
summary
tags
category
icon
password
以xxx跑为例的iOS逆向
效果展示:这是xxx跑的OSS服务器,我们可以自己上传内容进去
1.目的:
完全解析加解密算法,文件上传算法,最终重写客户端。
2.抓包分析
直接用appdump砸壳,拿到可执行程序,脱进ida。
抓包可以看到关键的请求只有那么几个,这篇文章主要探讨如何获取OSS签名实现对服务器的文件上传。
通过抓包发现,跑步记录、用户头像等等内容均存储于OSS服务器上,跑步记录是txt,里面是base64编码。而跑步封面截图直接就是存的图片。
比如跑步记录txt:
解码后发现不可见。其实是进行了一层加密。我们先不管这个。

要逆这个地方非常简单,直接找base64解码的交叉引用,就会找到对应的加密方法和密钥。这里就不公开了。
上传文件的逻辑是,先请求它自己的api服务器,获取一次性签名(有时间和指定文件的限制)。用签名去oss服务器上传内容。
抓包发现发送给服务器请求签名的请求是key={<base64>},其中的内容也是加密过的,加密算法与刚才提到的一致,但密钥不同。
服务器返回base64编码,解码解密,算法依旧相同,密钥还是不同。
由此便可得到格式为
“OSS LTAI5tRbKExxxxxxxxx:nGmHxxxxxxxx”的签名,可以在一小段时间内用来访问服务器并上传内容。
抓包发现每次上传之前都给这个地址发送请求
内容如下
3.静态分析
解密刚才我们说到的请求字符串,得到类似内容
其中,nonce是随机数,除了用户信息之外,content是要进行上传的地址,有两个md5值,分别是sign和lpp2.
打开我们的ida,查找getOSSSign的交叉引用,发现用于构造http请求的函数是
[HttpRequest postWithURLString:parameters:success:failure:]
在这里面看了一圈,用于构造签名的函数是
sortDictionary:bySignAPPKey
代码块 loc_10046D73C 是上传文件用到的
还有另外一个类似的代码块,是用来上传用户头像的,我们不看。

取出机器码,保存到寄存器X22中
从字典中移除机器码这个键值对
获取iphone型号。调用的是iphoneType函数。这个函数对于新款iPhone没法获取型号,只能获取代号。但老iPhone没问题。
比如iPhone6s,但13pm只能得到iPhone14,3 应该是太老了没更新的问题
然后取子字符串 分别是iPhoneType的第二个字母,时间戳的第四个字符。二者与lp%@%@构造字符串。取小写。
所以目前构造出来的是lpp2,差不多一个月,时间戳变了,就会变成lpp3,以此类推

接着往下看。取出时间戳的值(又取了一次) 转换成整数类型 左移一位(x2),格式化字符串为长无符号整型,计算md5,设置为键lpp2对应的值。
然后将x3设置为常量,x20中是整个字典,这样做参数传递,传递给签名函数
因为arm64的参数传递是一套固定的规则,是由ARM的ABI(Application Binary Interface)定义的,主要通过寄存器和栈来传递参数,如下:
- 寄存器传递:
- 前8个整型参数(包括指针)使用x0到x7寄存器传递。
- 前8个浮点参数(包括单精度和双精度)使用v0到v7寄存器传递。
- 如果函数有混合的整型和浮点参数,寄存器是按顺序分配的,不会跨类型混合使用。
- 栈传递:
- 当参数超过8个整型或8个浮点寄存器时,剩余的参数将被传递到栈上。
- 栈上的参数按照从右到左的顺序传递,并且栈需要16字节对齐。
- 寄存器和栈的混合使用:
- 对于那些不能完全由寄存器传递的参数(例如超过8个整型或浮点参数),多余的参数将存储在栈上。前8个整型参数在x0到x7寄存器中传递,前8个浮点参数在v0到v7寄存器中传递,其他参数则放在栈上。
- 返回值:
- 返回值一般通过寄存器x0返回(对于整型和指针类型),对于浮点型返回值则通过v0返回。
- 如果返回值是一个复合类型(例如结构体或联合体),并且结构体超过16字节,则返回值通常通过指针传递,指针在x8寄存器中传递。
- 特殊情况:
- 大型数据结构(如超过16字节的结构体)通过内存传递,调用者需要分配内存并传递指向该内存的指针。
- 变长参数函数(如printf)中的变长参数按照上述规则先通过寄存器传递,不够用时再使用栈。
查看bySignAPPKey函数,签名过程如下:排序字典并把键值对连接成字符串。判断传入参数X3是否为空字符串,如果是,则在尾部追加默认值。否则,将X3的内容连接到整个字符串的头部,计算MD5

得到了签名之后,保存到字典的sign字段中

继续往下看,这里将mobileDeviceId又放了回来,从x22寄存器取出来重新设置成键值对。然后把字典转换为Json格式,传入加密函数,得到的密文设置为key的键值对,存储下来。
实际上,并不是在此处进行加密的,后面根据条件判断,调用了另外一个算法相同密钥不同的加密函数

仍然设置为key:””键值对 即用来发送请求。
大部分逻辑已经清晰,可以自己进行签名了。但实测根据我们上面解密出来的内容进行签名并不能成功。其实是我解密算法写的有问题,但我并没有意识到,所以进行动态调试。
4.动态调试
iOS的动态调试有几种方案:
非越狱机型:monkeydev
越狱机型:monkeydev/debugserver重签名/直接装debugserver插件
使用monkeydev一定需要砸过壳的ipa
实测15.5无根越狱的情况下,重新签名debugserver会报错,要么是无法连接gdb,要么就是直接被kill。
monkeydev在xcode15下无法正常工作,可以在安装之后使用这个仓库里的内容把模板全部替换,不然会出现诸如libstdc之类的问题
但是!monkeydev在面临少数程序时,会出现无法签名以至于无法安装至真机的情况,步道乐跑就是这样,所以只能放弃。转而使用debugserver
debugserver插件
直接在Sileo内安装即可,使用也非常简单
debugserver 0.0.0.0:<端口号> -a <进程名>
查看正在运行的某进程的名称可以使用
ps -A | grep /Application
先启动软件,然后运行debugserver命令,附加上去。
启动mac,确保有lldb。直接进入lldb。使用
process
connect
connect://手机ip:<端口号>
即可连接。我们只要在签名的时候下断点看一下就可以了。
首先需要这样几个命令
列出模块
下断点的命令
注意我们下断点的地址,一定是ASLR偏移量再加上我们在IDA内看到的地址才行。
比如 我们先查看ASLR偏移量

然后我们只下断这几个地方:
1.调用签名函数之前(也可以直接断这个函数,我这里断了调用前准备参数的地方)

断这个地址,X3内的内容是自定义的要附加的字符串,X20的内容就是整个字典
2.签名函数调用MD5计算之前

断这个地址,X27中是要计算的字符串
3.调用加密函数之前,因为进入加密的就是要发送的字符串了,不会再变,即最终要发送的结果。

然后开启一个自由跑,等一会结束跑步并且上传跑步记录,就会断到了。

这是进入签名函数之前,显然没有设备UUID

这是要进行MD5的字符串。此时我已经明白为什么我的签名不对了,因为我对换行符的处理,导致PUT txt Sat 以及GMT后的换行全部去掉了。这样就导致签出来的不一样。
这也印证了我们刚才静态调试的结果,即,给签名函数传入自定义的字符串,则会附加到最前面,进行MD5的计算。

显然最后进入加密函数的是有UUID的。
至此签名告破。
- Author:Lynnette177
- URL:https://next.lynnette.uk/article/ez_ios_reverse
- Copyright:All articles in this blog, except for special statements, adopt BY-NC-SA agreement. Please indicate the source!
Relate Posts