【iOS】基于frida的砸壳工具优化方案(和砸壳本身关系不大)

小组里用的砸壳工具主要有两种:

一种是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文档)

但现在要回去处理之前的功能整合需求,因此这个优化就暂时搁置了,毕竟已经算是基本实现一键砸壳了。

 

你可能感兴趣的:(iOS学习)