This technology is implemented in YSBlockHook.

1. Several Hook mechanisms for method invocation

There are three types of method invocation in iOS system: C function, Block and OC class method. Hook The purpose of a method call is generally to monitor interception or to count the behavior of some system. There are many Hook mechanisms, and usually good Hook methods are implemented in the form of AOP.

Method is used when we want to Hook specific methods of an OC class Swizzling technology to achieve, when we want to Hook dynamic library export a CERTAIN C function can be achieved by modifying the information in the address table of the import function (can use the open source library Fishhook to complete), when we want to Hook all OC class methods can be achieved by replacing objc_msgSend series of functions .

What about the Block method?

2. Introduction to the internal implementation principle and mechanism of Block

This assumes that you have some understanding of the internal implementation of blocks and how they work. If you don’t, please refer to the article “In Depth Deconstructing iOS Block closure Implementation Principles” or do your own search engine.

Each Block defined in the source program is converted to an object with a similar layout to the OC class object at compile time. Each Block also has the data member ISA. Depending on what ISA points to, There are three types of blocks: __NSStackBlock, __NSMallocBlock, and __NSGlobalBlock. That is, Block objects are in some sense OC objects. The following class diagram describes the hierarchy of Block classes.

The Block class and its derived classes are defined and implemented in coreFoundation. framework and are not exposed to the public.

The layout of each Block object in memory, the storage structure of the Block object, is defined as follows (from libclosure’s block_private.h) :

// It is important to note that the following two blocks are only templates, and each Block definition is always defined by this template. Struct Block_descriptor_1 {uintptr_t reserved; struct Block_descriptor_1 {uintptr_t reserved; // Remember this variable and structure, it is important!! uintptr_t size; }; Struct Block_layout {void *isa; struct Block_layout {void *isa; volatile int32_t flags; // contains ref count int32_t reserved; uintptr_t invoke; Struct Block_descriptor_1 *descriptor; // Imported variables, here are the specific data members of each block object};Copy the code

Note that the reserved data member in struct Block_descriptor_1 is not used, but it will be used next and is important!

Now that you know the type of Block objects and their memory layout, let’s look at how a Block is implemented from definition to invocation. Take the following source code for example:

Int main(int argc, char *argv[]) {// define int a = 10; void (^testblock)(void) = ^(){ NSLog(@"Hello world! %d", a); }; / / execution testblock ();return 0;
}

Copy the code

After OC code is translated into C language code, the definition and call of each Block will become the following pseudocode:

Struct Block_descriptor_1_fortestblock {uintptr_t reserved; uintptr_t size; }; Struct Block_layout_fortestblock {void *isa; volatile int32_t flags; // contains ref count int32_t reserved; uintptr_t invoke; Struct Block_descriptor_1_fortestblock *descriptor; struct Block_descriptor_1_fortestblock *descriptor; int m_a; // External data passed in. }; // The implementation of testblock. void main_invoke_fortestblock(struct Block_layout_fortestblock *cself) { NSLog(@"Hello world! %d", cself->m_a); } // TestBlock object description instance, Struct Block_descriptor_1_fortestblock _testBlockDESC = {0, sizeof(struct Block_layout_fortestblock)}; Int main(int argc, char *argv[]) {// define part of int a = 10; struct Block_layout_fortestblock testblock = { .isa = __NSConcreteStackBlock, .flags =0, .reserved = 0, .invoke = main_invoke_fortestblock, .descriptor = & _testblockdesc, .m_a = a }; // Invoke part testBlock. invoke(testblock);return 0;
}


Copy the code

As you can see, Block objects are built and called at compile time, unlike other OC objects that call methods indirectly through the Runtime. And all symbolic information about blocks in online applications will be stripped. So none of the above Hook methods can Hook a function call of a Block object.

If you want to Hook all Block calls to the system, you need to solve the following problems:

A. How to replace all Block invoke functions with a unified Hook function at run time.

B. How the unified Hook function calls the original Block’s invoke function.

C. How to build this unified Hook function.

3. The method and principle of realizing Block object Hook

An instance of an OC class object manages the life cycle of the object through reference counting. In the MRC era, when an object is assigned or copied, it needs to call the retain method to increase the reference count. In the ARC era, when an object is assigned or copied, it no longer needs to call the retain method. Instead, the system automatically inserts corresponding code during compilation to add and decrease the reference count. Any time an assignment copy is performed on an OC object, the retain method is eventually called internally.

Block objects are also OC objects!!

The retain method is also invoked whenever an assignment or copy operation is required on a Block object. Since Block assignment usually occurs before Block Method execution, we can Hook the retain Method using Method Swizzling. Then replace the Invoke data member of the Block object with a unified Hook function inside the overridden retain method!

The __NSStackBlock, __NSMallocBlock, and __NSGlobalBlock classes all override the retain method of NSObject. This eliminates the need to replace the retain Method of NSObject when executing Method Swizzling, but only the retain of the three classes described above.

Can you tell why all three derived classes override the retain method? The answer can be found in the type definitions and meanings of the three blocks.

Block technology can not only be used in OC language, but also in the extension of LLVM to C language. For example, Block is widely used in GCD library. To assign or copy a Block in C, the system uses C library functions:

// Create a heap based copy of a Block or simply add a reference to an existing one. // This must be  paired with Block_release to recover memory, even when running // under Objective-C Garbage Collection. BLOCK_EXPORT void *_Block_copy(const void *aBlock) __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);Copy the code

This function is defined in the libsystem_blocks. Dylib library and its implementation is open source: libclosure. The Fishhook library can therefore be used to replace __Block_copy, and then replace a Block’s original invoke function with a unified Hook function in the replacement function.

Another C function, objc_retainBlock, also increases the reference count when assigning a Block by simply calling __Block_copy. So we can also add a replacement for objc_retainBlock.

After solving the first problem, move on to the second. Remember the reserved data member in struct Block_descriptor_1 mentioned above? We can save the Block object’s original invoke function to this reserved field before replacing all Block invoke members with a single Hook function using the above method. The original saved invoke function in this reserved field can then be read inside the unified Hook function to perform the actual method call.

Since the first argument to a Block object function is actually a hidden argument, the hidden argument is the Block object itself, so it is easy to get the corresponding reserved field from the hidden argument.

The following code shows pseudocode that implements Hook handling through method interchange

struct Block_descriptor { void *reserved; uintptr_t size; }; struct Block_layout { void *isa; int32_t flags; // contains ref count int32_t reserved; void *invoke; struct Block_descriptor *descriptor; }; Void blockhook(void *obj,...) void blockhook(void *obj,... { struct Block_layout *layout = (struct Block_layout*) obj; // Invoke the original layout-> Descriptor ->reserved(...) ; Void blockhook_stret(void *pret, void *obj,...); void blockhook_stret(void *pret, void *obj,...); { struct Block_layout *layout = (struct Block_layout*) obj; // Invoke the original layout-> Descriptor ->reserved(...) ; } / / execution Block object method of alternative processing void replaceBlockInvokeFunction (const void * blockObj) {struct Block_layout * layout = (struct Block_layout*)blockObj;if(layout ! = NULL && layout->descriptor ! = NULL){ int32_t BLOCK_USE_STRET = (1 << 29); // If the type returned under the simulator is a structure larger than 16 bytes, the first argument to the block is the returned pointer, not the block object. void *hookfunc = ((layout->flags & BLOCK_USE_STRET) == BLOCK_USE_STRET) ? blockhook_stret : blockhook;if(layout->invoke ! = hookfunc){ layout->descriptor->reserved = layout->invoke; layout->invoke = hookfunc; } } } void *(*__NSStackBlock_retain_old)(void *obj, SEL cmd) = NULL; void *__NSStackBlock_retain_new(void *obj, SEL cmd) { replaceBlockInvokeFunction(obj);return __NSStackBlock_retain_old(obj, cmd);
}

void *(*__NSMallocBlock_retain_old)(void *obj, SEL cmd) = NULL;
void *__NSMallocBlock_retain_new(void *obj, SEL cmd)
{
    replaceBlockInvokeFunction(obj);
    return __NSMallocBlock_retain_old(obj, cmd);
}

void *(*__NSGlobalBlock_retain_old)(void *obj, SEL cmd) = NULL;
void *__NSGlobalBlock_retain_new(void *obj, SEL cmd)
{
    replaceBlockInvokeFunction(obj);
    return__NSGlobalBlock_retain_old(obj, cmd); } int main(int argc, char *argv[]) {int main(int argc, char *argv[]) { __NSStackBlock_retain_old = (void *(*)(void*,SEL))class_replaceMethod(NSClassFromString(@"__NSStackBlock"), sel_registerName("retain"), (IMP)__NSStackBlock_retain_new, nil);
    __NSMallocBlock_retain_old = (void *(*)(void*,SEL))class_replaceMethod(NSClassFromString(@"__NSMallocBlock"), sel_registerName("retain"), (IMP)__NSMallocBlock_retain_new, nil);
    __NSGlobalBlock_retain_old = (void *(*)(void*,SEL))class_replaceMethod(NSClassFromString(@"__NSGlobalBlock"), sel_registerName("retain"), (IMP)__NSGlobalBlock_retain_new, nil);

    return 0;
 }

Copy the code

Having solved the second problem, we need to solve the third. The above unified Hook functions blockhook and block_stret are only pseudo-code implementations, because the type and number of arguments in any Block function are not the same, and unified Hook functions also need to call the original default Block function implementation at the appropriate time, and do not destroy the parameter information. In order to solve these problems, the unified Hook function can not be implemented in high-level language, but can only be implemented in assembly language. The following is the implementation code under arm64 bit system:

.text.align 5. private_extern _blockhook _blockhook: //#-0x20]!
  stp q4, q5, [sp, #-0x20]!
  stp q2, q3, [sp, #-0x20]!
  stp q0, q1, [sp, #-0x20]!
  stp x6, x7, [sp, #-0x10]!
  stp x4, x5, [sp, #-0x10]!
  stp x2, x3, [sp, #-0x10]!
  stp x0, x1, [sp, #-0x10]!
  stp x8, x30, [sp, #-0x10]!// Any logic can be added here to hook. LDP x8, x30, [sp],#0x10
  ldp x0, x1, [sp], #0x10
  ldp x2, x3, [sp], #0x10
  ldp x4, x5, [sp], #0x10
  ldp x6, x7, [sp], #0x10
  ldp q0, q1, [sp], #0x20
  ldp q2, q3, [sp], #0x20
  ldp q4, q5, [sp], #0x20
  ldp q6, q7, [sp], #0x20

  ldr x16, [x0, #0x18] // Fetch the descriptor data member of the block objectLDR x16, [x16] // The reserved member in descriptor br x16 // Executes the original function pointer stored in reserved. LExit_blockhook:Copy the code

For x86_64/ arm32-bit systems, if the block function returns a structure with a length greater than 16 bytes (arm32 is 8 bytes). The flags attribute inside the block object is set to BLOCK_USE_STRET. X86_64 / ARM32-bit systems store the return value of a function of this type in the memory indicated by the first argument, and change the original block object to the second argument, so special treatment is required for this case.

The technical implementation principle of all Block method calls of Hook at run time is introduced here. Of course a complete system may require some other capabilities:

  • If you only want blocks defined in Hook executables, please refer to my article “Deep dive into the iOS image manipulation API” to implement filtering of Hook functions.
  • If you don’t want to use reserved in the Block_descriptor to save the original invoke function, refer to my article: Thunk program implementation principle and application in iOS (2) introduced in the implementation of the unified Hook function and complete the original invoke function call technology.

The complete code can be accessed from my Github project :YSBlockHook. This project in the form of AOP implementation of real machine ARM 64-bit mode to Hook all defined blocks in the executable program method, Hook is to print the symbolic information of the Block before all Block calls.


Welcome to ouyang Big Brother 2013Making the addressandJane’s address book