# -*- coding: utf-8 -*-
'''
一键Dump iOS加密MachO至A64Dbg与之对应的缓存目录。
'''
# https://gitee.com/geekneo/A64Dbg/blob/master/python3/adpdef.py
# 引入adp基础定义,比如error/event定义
from adpdef import *

# https://gitee.com/geekneo/A64Dbg/blob/master/python3/adp.py
# 引入adp所有python api
from adp import *

import os
import platform
import struct

# 开发模式则只使用源代码,不使用adc二进制模块
dev_mode = False

# 将unpack得到的字符串字节数组转为字符串
def simplify_strbuff(tmps):
    result = ''
    for c in tmps:
        if c > 0:
            result += chr(c)
        else:
            break
    return result

# 远程adcpp代码将会发送数据至该接口
# adcpp_ios_dump是adcpp api str2py/buf2py的第一个字符串参数,用于发送数据到该函数
'''
// 文件传输协议字段
struct dump_info {
  char filename[64];
  char min_version[12];
  int filesize;
  char filebuff[0];
};
'''
def adcpp_ios_dump(data):
    # MachO文件名称
    filename = simplify_strbuff(struct.unpack_from('64s', data, 0)[0])
    # MachO文件要求的最低系统版本号
    min_version = simplify_strbuff(struct.unpack_from('12s', data, 64)[0])
    # MachO大小
    filesize = struct.unpack_from('i', data, 76)[0]
    # MachO数据
    filebuff = struct.unpack_from('%ds' % (filesize), data, 80)[0]
    print('Received %s, %s, %d.' % (filename, min_version, filesize))
    # 保存至A64Dbg与之对应的缓存目录
    usrdir = os.path.expanduser('~')
    cachedir = usrdir + '/A64Dbg/decache/iOS/arm64-apple-ios'
    versioneddir = cachedir + min_version
    # 创建目录
    if not os.path.exists(cachedir):
        os.mkdir(cachedir)
    if not os.path.exists(versioneddir):
        os.mkdir(versioneddir)
    # 保存解密后的MachO文件
    filepath = cachedir + '/' + filename
    print('Saved to %s.' % (filepath))
    with open(filepath, 'wb') as fp:
        fp.write(filebuff)
    # 创建软连接至带版本后缀的目录,lldb模式需要访问这个目录中的MachO文件
    versionedpath = versioneddir + '/' + filename
    print('Linked to %s.' % (versionedpath))
    if platform.system() == 'Windows':
        os.system('mklink "%s" "%s"' % (versionedpath, filepath))
    else:
        os.system('ln -s -f "%s" "%s"' % (filepath, versionedpath))

# a64dbg插件入口
def adp_on_event(args):
    # 获取当前的事件码
    event = args[adp_inkey_type]
    # 用户执行了插件主菜单命令
    if event == adp_event_main_menu:
        # 判断当前目标平台是否是remote_unicornvm_ios
        plat = curPlatform()
        if plat == adp_remote_unicornvm_ios:
            plugdir = os.path.dirname(__file__)
            adcppfile = plugdir + '/adcpp-ios-dump.adc'
            if not os.path.exists(adcppfile) or dev_mode:
                # macOS平台可以直接使用脚本源码
                # 开发模式只使用源代码
                if platform.system() == 'Darwin' or dev_mode:
                    adcppfile = plugdir + '/adcpp-ios-dump.mm'
            if os.path.exists(adcppfile):
                # 将窗口焦点切换为Log窗口
                focusLog()
                # 在远程iOS进程执行该脚本代码进行Dump操作
                # 需要预先利用远程iOS虚拟化调试模式Attach目标进程,否则该脚本执行会报错
                runadc(adcppfile)
            else:
                print('Missing %s.' % (adcppfile))
            return success()
        else:
            print('adcpp-ios-dump插件只支持iOS虚拟化调试模式remote_unicornvm_ios.')
            return failed(adp_err_unsupport)
    # 插件框架向插件询问主菜单名称
    if event == adp_event_menuname:
        return success('adcpp-ios-dump')
    # 插件框架向插件询问插件版本和简介
    if event == adp_event_adpinfo:
        return success(('0.1.1', "一键Dump iOS加密MachO至A64Dbg与之对应的缓存目录。"))
    return failed(adp_err_unimpl)