小组里用的砸壳工具主要有两种:
一种是Clutch,使用比较简单的,但是总是会砸失败(原因大佬说过我忘了)
一种是frida,使用稍微复杂一点,不过效果好,当然也有问题,下面主要讨论frida。
使用frida砸壳需要这些东西:(大部分步骤是网上的资料,一些是自己实际使用的时候调整的)
iPhone端:
1. 添加源:https://build.frida.re
2. 按机子架构安装对应的砸壳工具
Mac端:
基于Python2.7,如果不是Python2.7的先切换至2.7(这一点没有留意,我自己的版本是2.7.1)
1. sudo easy_install pip
2. 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
3. 如果调用frida显示command not found,是因为新版本(12.2.26会出现这种问题)需要安装frida和frida-tools
sudo pip install frida-tools
以上可能还涉及到安装路径的问题,调整好路径就能解决。
准备工作做完之后,使用usbmuxd进行端口转发(原本写的是iproxy,但大佬们实际上使用的都是usbmuxd),然后直接在frida-ios-dump-master目录下运行命令行 ./dump.py +指令即可实现相关操作
相关代码:https://github.com/AloneMonkey/frida-ios-dump
一般来说这就算实现一键砸壳,但是针对实际应用还是进行了一点点优化。
就是把端口转发也加进原来的dump.py里面,这主要是因为希望这个砸壳工具同样能提供给运营使用(目前还没有,因为可能需要做个配套的app?)
这个优化说起来简单,实际也不难,主要处理两个细节
1.端口转发会占用端口,因此在每次启动(或者是结束时)应该处理这个问题
2.端口转发需要时间,因此应该在转发完成的时候通信一下,接受完成的消息(庆幸usbmuxd的python-client如此易懂)
# 删除 ~/.ssh/known_hosts 文件
os.system('sudo chmod -R 777 ~/.ssh/')
result = os.system("rm ~/.ssh/known_hosts")
if result == 256:
print('not remove ~/.ssh/known_hosts becasuse file not exist')
elif result != 0:
print('remove ~/.ssh/known_hosts failed')
exit(0);
# 检查 2222 端口占用 如果占用则kill
try:
find_port = 'ps -e | grep %s' %Port
print('search port 2222 state')
result = os.popen(find_port)
text = result.read()
text = text.split('\n')
pid = []
for index in range(len(text)):
if "tcprelay" in text[index]:
pid = text[index].split()[0]
break
if pid != []:
find_kill= 'kill %s' %pid
print(find_kill)
result = os.popen(find_kill)
print ('kill port 2222 process')
time.sleep(0.1) #等待进程kill
except:
print ('search port 2222 error')
exit(1);
print ('check port 2222 finish')
可以看到还是有瑕疵的,要等待进程kill该端口而让其sleep了一点时间,这显然不太好。
在dump.py中加入代码作为服务器端(注意我用的是tcprelay2.py,因为保留了原文件没有动)
# 建立socket连接是为了监听tcprelay.py成功的信号
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
host = Host
port = 9999
sock.bind((host, port))
sock.listen(5)
except:
print ('build listen socket failed,try run again')
sock.close()
exit(1);
# 调用python-client工具转换端口 22:2222
os.system("python -t ./usbmuxd/python-client/tcprelay2.py 22:2222 &")
# 接收端口信息来判断端口已经转发成功 (tcprelay.py作为客户端发送信息)
while True:
connection,address = sock.accept()
data = connection.recv(1024)
if data == "Forwarding Finish":
connection.close()
break;
sock.close()
改一下usbmuxd/python-client/tcprelay.py(更名为tcprelay2.py)作为客户端,加在最后
for rport, lport in ports:
print "Forwarding local port %d to remote port %d"%(lport, rport)
server = serverclass((HOST, lport), TCPRelay)
server.rport = rport
server.bufsize = options.bufsize
servers.append(server)
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
host = HOST
port = 9999
sock.connect((host, port))
sock.send("Forwarding Finish")
sock.close()
alive = True
while alive:
try:
rl, wl, xl = select.select(servers, [], [])
for server in rl:
server.handle_request()
except:
alive = False
只有中间那段是新添加的代码
frida砸壳有一个问题,他只会砸执行时在内存里面的framework,而有一些应用在启动时(指在砸壳之前)并没有把framework加载进内存,于是就需要我们手动去把他们加载进内存(这个是大佬的思路,我只是负责实现)
采用的方法也很简单,直接遍历bundle下的framework,对他们执行dlopen
需要注意的一点是dlopen的配置选项应该选RTLD_LAZY。
我把这个写成了一个钩子(OC),然后在dump.py中把钩子dylib和plist放进了砸壳手机的对应目录。
当然如果这样处理的话在dump.py中打开应用后也是需要sleep的,保证砸壳在钩子执行之后,这显然也不是一个好方法。
在原来的砸壳代码中有一份dump.js,这一份是真正调用了frida的javascript API。
启动砸壳的代码如下:
function handleMessage(message) {
//start dump
modules = getAllAppModules();
for (var i = 0; i < modules.length; i++) {
console.log("start dump " + modules[i].path);
result = dumpModule(modules[i].path);
send({ dump: result, path: modules[i].path});
}
send({app: ObjC.classes.NSBundle.mainBundle().bundlePath().toString()});
send({done: "ok"});
recv(handleMessage);
}
可以看到我们只要在 getAllAppModules之前执行dlopen所有framework就能够保证砸壳前所有framwork都被加载进内存,而不再需要复制钩子进入对应目录,避免使用sleep这种不稳定手段。
具体代码之后更新,目前还未研究出js中调用OC(实际上dlopen是C++方法)的方法。
实际上对于优化二还是有优化空间,直接从dump.js下手,调用OC方法来dlopen所有framework。(需要看一下frida文档)
但现在要回去处理之前的功能整合需求,因此这个优化就暂时搁置了,毕竟已经算是基本实现一键砸壳了。