frida / frida-objc-bridge

Objective-C runtime interop from Frida
49 stars 21 forks source link

add ObjC.ProxyBlock, #45

Open WeissRu opened 2 years ago

WeissRu commented 2 years ago

Change

Add ObjC.ProxyBlock. Use stackblock instead of globalblock to solve the strange crash encountered in the process of replacing block implementation.

Adjust blockDescriptorAllocSize and blockDescriptorOffsets to fit ProxyBlock's 3part-Descriptor, and keep offset.rest for Original ObjC.block.

Change descriptor->size from blockDescriptorDeclaredSize to blockSize.

Problems

Haven't written a test case yet.

If the script is disposed, the application may crash. (ObjC.block have the same problem?)

Reason

blockDescriptorDeclaredSize and blockSize

When Block_copy is used on stackblcok, descriptor->size is used to request memory to store "another part".So size should be block's size.(But have no effect on GlobalBlock)

//Code from libclosure-74/runtime.cpp _Block_copy

// Its a stack block.  Make a copy.
struct Block_layout *result =
    (struct Block_layout *)malloc(aBlock->descriptor->size);
if (!result) return NULL;
memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first

ProxyBlock

The globleblock has some difficulties in solving life cycle problems. For example: -[NSURLSession dataTaskWithURL:completionHandler:], send a network request and process the response in a callback. Now you want to know which request created the response.

Solution 1 Try the example provided in the documentation:

const pendingBlocks = new Set();

Interceptor.attach(..., {
  onEnter(args) {
    const block = new ObjC.Block(args[4]);
    pendingBlocks.add(block); // Keep it alive
    const appCallback = block.implementation;
    block.implementation = (error, value) => {
      // Do your logging here
      const result = appCallback(error, value);
      pendingBlocks.delete(block);
      return result;
    };
  }
});

It looks good, but what happens if each incoming block is the same? For example, a developer is only concerned about whether a network request is in error and receives a response with a globalblock.

Solution 2 Create a new block to replace the old one. And call the old one in the new block. Work like ObjC.Proxy.

handler = _Block_copy(handler);
const ori_block = new ObjC.Block(handler);
const new_block = new ObjC.Block({
    implementation: function (data, resp, error) {
        // Do your logging here
        const result = ori_block.implementation(data, resp, error);
        _Block_release(ori_block.handle);
        setTimeout(function () { pendingBlocks.delete(K); }, 400);//When?
        return result;
    },
    types: ori_block.types,
});
pendingBlocks.add(new_block);
return new_block;

It looks good, doesn't it? Each request received a different callback. Just release the callback at the right time. No, not that simple.

What is the difference between pendingBlocks.delete in the first two examples? Why need 'setTimeout'?

descriptor(system) <-- block(program) --> imp(fridaFREED)

descriptor(fridaFREED) <-- block(fridaFREED) --> imp(fridaFREED) --> realblock(program)

The problem is that the callback "holder" does not necessarily release the callback immediately after the callback has been executed, and we don't know when the release will occur by current implementation.(Unless we monitor the call of _Block_relase and judge whether this parameter is a callback generated by frida, ah,and we have to maintain retain count ourselves)

For most cases, setTimeout400 is enough, but some applications will crash unless setTimeout is set to more than ten seconds. One possibility I can think of is that the callback will be released when an autorelease pool is released, so there are differences between different applications.

In summary, it might be a good idea to use a stackblock and wait for the lifecycle call(copy/dispose helper) of the OC.

oleavr commented 2 years ago

Thanks! (And so sorry for the delay here, I somehow missed this PR.) This sounds great to me. This needs test coverage before we can land it though. Let me know if you need any guidance there.