write to tmp dir: permission denied for appex
pwn0rz opened this issue · comments
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
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
Will use a fileless approach
Fileless implementation now merged