ChiChou / bagbak

Yet another frida based iOS dumpdecrypted. Also decrypts app extensions
MIT License
1.14k stars 187 forks source link

write to tmp dir: permission denied for appex #16

Closed pwn0rz closed 4 years ago

pwn0rz commented 4 years ago

Got this error on

Device: iOS 12.4 iPhone 8

I have tried to change tmpdir() implementation to Documents at the same dir as tmp but got the same error

plugin xxxxxxxxxxxxxxx, pid=9516
[info] decrypting module FilterDataProvider to /var/mobile/Containers/Data/PluginKitPlugin/74CC0931-AA14-4B48-97AC-6CF273513430/tmp/FilterDataProvider.decrypted
[error] failed to copy file: Error Domain=NSCocoaErrorDomain Code=513 "“FilterDataProvider” couldn’t be copied because you don’t have permission to access “tmp”." UserInfo={NSSourceFilePathErrorKey=/var/containers/Bundle/Application/6FFE9BDF-9F4A-48AE-97AC-AF6A9DA272E5/xxxxxx.app/PlugIns/xxxxxxxx.appex/FilterDataProvider, NSUserStringVariant=(
    Copy
), NSDestinationFilePath=/var/mobile/Containers/Data/PluginKitPlugin/74CC0931-AA14-4B48-97AC-6CF273513430/tmp/FilterDataProvider.decrypted, NSFilePath=/var/containers/Bundle/Application/6FFE9BDF-9F4A-48AE-97AC-AF6A9DA272E5/xxxxxxxxx, NSUnderlyingError=0x101308c30 {Error Domain=NSPOSIXErrorDomain Code=1 "Operation not permitted"}}

Traceback (most recent call last):
  File "./dump.py", line 253, in <module>
    main()
  File "./dump.py", line 249, in main
    task.run()
  File "./dump.py", line 227, in run
    self.dump()
  File "./dump.py", line 162, in dump
    self.dump_with_plugins()
  File "./dump.py", line 189, in dump_with_plugins
    decrypted += script.exports.decrypt(self.root)
  File "/Users/pwn-orz/.pyenv/versions/3.7.3/lib/python3.7/site-packages/frida/core.py", line 322, in method
    return script._rpc_request('call', js_name, args)
  File "/Users/pwn-orz/.pyenv/versions/3.7.3/lib/python3.7/site-packages/frida/core.py", line 250, in _rpc_request
    raise result[2]
frida.InvalidOperationError: script is destroyed
pwn0rz commented 4 years ago

After further investigation, I have found that the appex "FilterDataProvider" runs in a very restrictive sandbox according to Apple[1].

For example, your filter control provider might download a set of filtering rules and save them to a shared app group. Your filter data provider has read-only access to that app group, allowing it use those rules to filter content but still preventing it from exporting user network content.

Assumption: As a result, we might not have permission to write any data info file system since the Frida script calls the OC Filesystem API inside this sandboxed appex?

So I use this way and it proved to be working for me in this case but at the most time it crashed for any other module. I have not idea why. Most of the code was copy and pasted from frida-ios-dump and I hope can help someone with the similar issue.

//TLDR: decrypt mach-o into memory instead of file, and return it as bytes via RPC
function dumpModule(name) {
    if (modules == null) {
        modules = getAllAppModules();
    }

    var targetmod = null;
    for (var i = 0; i < modules.length; i++) {
        if (modules[i].path.indexOf(name) != -1) {
            targetmod = modules[i];
            break;
        }
    }
    if (targetmod == null) {
        console.log("Cannot find module");
        return;
    }
    var modbase = modules[i].base;
    var modsize = modules[i].size;
    var newmodname = modules[i].name;

    var oldmodpath = modules[i].path;

    var foldmodule = open(oldmodpath, O_RDONLY, 0);

    if (foldmodule == -1) {
        console.log("Cannot open file" + oldmodpath);
        return;
    }

    var is64bit = false;
    var size_of_mach_header = 0;
    var magic = getU32(modbase);
    console.log("file magic " + ptr(magic))
    if (magic == MH_MAGIC || magic == MH_CIGAM) {
        is64bit = false;
        size_of_mach_header = 28;
    }else if (magic == MH_MAGIC_64 || magic == MH_CIGAM_64) {
        is64bit = true;
        size_of_mach_header = 32;
    }

    console.log("loading old module into memory: " + oldmodpath)
    var filesize = lseek(foldmodule,0,SEEK_END);
    var buffer = malloc(filesize);
    if(!buffer){
        console.error("failed to allocate memory");
        return undefined;
    }else{
        console.log("allocated memory " + filesize + " bytes at " + ptr(buffer));
    }

    var BLOCK_SIZE = 4096;
    lseek(foldmodule, 0, SEEK_SET);
    read(foldmodule, buffer, BLOCK_SIZE);

    magic = getU32(buffer);
    console.log("magic = " + ptr(magic))
    if(magic == MH_CIGAM_64 || magic == MH_MAGIC_64){
        lseek(foldmodule, 0, SEEK_SET);

        var pos = buffer;
        var readLen = 0;
        while(0 != (readLen = read(foldmodule, pos, BLOCK_SIZE))) {
            pos = pos.add(readLen);
        }
    }else{
        console.log("only 64bit macho supported")
        return undefined;
    }

    var ncmds = getU32(modbase.add(16));
    var off = size_of_mach_header;
    var offset_cryptid = -1;
    var crypt_off = 0;
    var crypt_size = 0;
    for (var i = 0; i < ncmds; i++) {
        var cmd = getU32(modbase.add(off));
        var cmdsize = getU32(modbase.add(off + 4));
        if (cmd == LC_ENCRYPTION_INFO || cmd == LC_ENCRYPTION_INFO_64) {
            offset_cryptid = off + 16;
            crypt_off = getU32(modbase.add(off + 8));
            crypt_size = getU32(modbase.add(off + 12));
        }
        off += cmdsize;
    }

    if (offset_cryptid != -1) {
        var tpbuf = malloc(8);
        putU64(tpbuf, 0);
        memcpy(buffer.add(offset_cryptid),tpbuf,4);
        memcpy(buffer.add(crypt_off),modbase.add(crypt_off),crypt_size);

    }

    close(foldmodule);
    return Memory.readByteArray(buffer,filesize);
}

rpc.exports = {
    dumpModule,
   getAllAppModules
}

Reference: [1]. https://developer.apple.com/documentation/networkextension/content_filter_providers

ChiChou commented 4 years ago

Will use a fileless approach

ChiChou commented 4 years ago

Fileless implementation now merged