Mach-O is a very important tool for iOS APP startup Optimization (IPA package and Mach Object File Format).
Facebook/Fishhook is an open source Facebook iOS library that allows you to dynamically modify processes (processes: Mach-o executable files run on iOS), (__DATA, __got), (__DATA, __la_symbol_ptr), (__DATA, Pointer to symbols (non-lazy Symbol Pointers and Lazy Symbol Pointers) in the __nl_symbol_ptr Section, Given that these sections store non-lazy Symbol Pointers and Lazy Symbol Pointers, Fishhook changes the Pointer of the Symbol Pointer (to our custom function or any other function).
I don’t know if it’s useful or useless
Non-lazy Symbol Pointers are currently visible in the (__DATA, __got) and (__DATA, __nl_symbol_ptr) sections. Where (__DATA, __NL_symbol_ptr) fields are present in executables (Mach-o binaries) when main.m functions look like the following:
#import <stdio.h>
int main(int argc, char * argv[]) {
printf("%s\n"."hello world");
printf("%s\n"."hello desgard");
return 0;
}
Copy the code
For the OC project, only the (__DATA, __got) partition exists in the executable file for storing non-lazy Symbol Pointers. The contents of these partitions are non-lazy Symbol Pointers, which are identified by S_NON_LAZY_SYMBOL_POINTERS in the FLAGS field of their corresponding Load command. For comparison (__DATA, __la_symbol_ptr) the flags field for the Load command is S_LAZY_SYMBOL_POINTERS, Indicates that (__DATA, __la_symbol_ptr) stores Lazy Symbol Pointers.
Limitations of Fishhook
The title used here is “limitations”, but it is not a limitation, after all, each tool has its own scope, it should be called the scope of Fishhook, or what functions FishHook can hook.
Fishhook can hook non-lazy Symbol Pointers and Lazy Symbol Pointers. (__DATA, __got), (__DATA, __la_symbol_ptr), (__DATA, __nl_symbol_ptr) Section; From the previous Learning of Mach-0, we know that only C functions introduced into the system dynamic library in the project will be stored in these areas, while our own custom functions (C/OC) do not have corresponding Symbol Pointer stored in these areas. Their definitions are stored in the (__TEXT, __TEXT) Section, and the corresponding symbols are stored in the Symbol Table. Fishhook can only be used for C functions in the hook system dynamic library.
Fishhook is used to hook C functions. Fishhook can only hook C functions of the system, and cannot hook custom C functions written by ourselves. C functions are static. The address of the function is determined at compile time (the implementation address is in the mach-O local file), whereas system C functions have dynamic parts. So why do system level C functions have dynamic parts? This brings us to PIC (position-independent code) technology, also known as position-independent code/position-independent code, which is a technical means for system C functions to confirm an address during compilation.
Reserve a space in the Mach-O file at compile time — symbol table (__DATA
When dyld loads the application into memory (which depends on Foundation in load command), it is found in the symbol tableNSLog
The link binding function assigns the real address of the NSLog in Foundation to__DATA
Part of theNSLog
The symbol. A custom C function does not generate a symbol table, but is simply a function address, so fishhook’s limitation is that only symbols in the symbol table can be hook (rebind symbols).
Fishhook interpretation
Official introduction to Fishhook
First let’s look at the official description:
A library that enables dynamically rebinding symbols in Mach-o binaries running on iOS. Libraries that enable dynamic rebinding of symbols in mach-O binaries running on iOS. (Limited to C functions in the system dynamic library)
Fishhook is a very simple library that supports dynamic rebinding of symbols in Mach-O binaries running on iOS systems on emulators and devices (limited to C functions in the system dynamic library). This provides functionality similar to using DYLD_INTERPOSE on OS X (fishhook is used exactly like it in the example code below). On Facebook, we found it to be a very useful tool, You can hook calls in libSystem for debugging/tracing (for example, tracing). Auditing for double-close issues with file descriptors).
Here’s an excerpt from DYLD_INTERPOSE for OS X:
#if! defined(_DYLD_INTERPOSING_H_)
#define _DYLD_INTERPOSING_H_
/* * Example: * * static * int * my_open(const char* path, int flags, mode_t mode) * { * int value; * * // do stuff before open (including changing the arguments) * * value = open(path, flags, mode); * * // do stuff after open (including changing the return value(s)) * * return value; * } * DYLD_INTERPOSE(my_open, open) */
#define DYLD_INTERPOSE(_replacement,_replacee) \
__attribute__((used)) static struct{ const void* replacement; const void* replacee; } _interpose_##_replacee \
__attribute__ ((section ("__DATA,__interpose"))) = { (const void*)(unsigned long)&_replacement, (const void*)(unsigned long)&_replacee };
#endif
Copy the code
Fishhook usage
Fishhook is very simple to use. Just drag the fishhook.h/fishhook.c file into our project and rebind the symbol as shown in the following example:
#import <dlfcn.h>
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
#import "fishhook.h"
// Declare two static global function pointer variables to record the addresses of the system's original close and open functions, respectively
static int (*orig_close)(int);
static int (*orig_open)(const char *, int,...).;
int my_close(int fd) {
printf("🤯 🤯 🤯 Calling real close \ n (% d)", fd);
// By calling rebind_symbols in the main function, orig_close points to the system's original close function.
return orig_close(fd);
}
int my_open(const char *path, int oflag, ...) {
va_list ap = {0};
mode_t mode = 0;
if((oflag & O_CREAT) ! =0) {
// mode only applies to O_CREAT
va_start(ap, oflag);
mode = va_arg(ap, int);
va_end(ap);
printf("🤯🤯🤯 Calling real Open ('%s', %d, %d)\n", path, oflag, mode);
// Orig_open is a pointer to the system's original open function
return orig_open(path, oflag, mode);
} else {
printf("🤯🤯🤯 Calling real Open ('%s', %d)\n", path, oflag);
// Orig_open is a pointer to the system's original open function
return orig_open(path, oflag, mode); }}int main(int argc, char * argv[])
{
@autoreleasepool {
// Replace Symbol Pointer to close and open functions with my_close and my_open functions.
// Use the orig_close and orig_open static global pointer variables to record the close and open addresses of the system.
// When the following open and close functions are called, our custom my_open and my_close functions will be executed. My_open and my_close are then called internally by the orig_open and orig_close function Pointers to the system's original open and close functions.
rebind_symbols((struct rebinding[2]) {{"close", my_close, (void *)&orig_close}, {"open", my_open, (void *)&orig_open}}, 2);
// Open our own binary and print out first 4 bytes (which is the same for all Mach-O binaries on a given architecture)
// Remember argv[0], which is the local path to the current process's executable, and then read the first four bytes directly,
Mach-o Type mach-o Type mach-o Type mach-o Type mach-o Type
// If we remember Mach -o, the first thing that comes to mind is "64-bit executables".
printf("➡ ️ ➡ ️ ➡ ️ % s \ n", argv[0]);
// Open the file
int fd = open(argv[0], O_RDONLY);
// Read the first four bytes in the magic_number variable
uint32_t magic_number = 0;
read(fd, &magic_number, 4);
// This will print out the familiar feedfacf magic number
printf("🤯🤯🤯 Mach -o Magic Number: %x \n", magic_number);
// Close the file
close(fd);
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); }}Copy the code
Run the above code and our console should see the following output:
Argv [0] is the local path to the executable for our current process➡ ️ ➡ ️ ➡ ️ / Users/HMC/Library/Developer/CoreSimulator/Devices/CC2922E4 - A2DB43 -DF-8B6F-D2987F683525/data/Containers/Bundle/Application/37AD7905-E15C- 4039.- 905.D-B474D67074AE/Test_ipa_simple.app/Test_ipa_simple
// int fd = open(argv[0], O_RDONLY); Calling the system's open function changes to calling our own my_open function🤯 🤯 🤯my_open Calling real open('/Users/hmc/Library/Developer/CoreSimulator/Devices/CC2922E4-A2DB-43DF-8B6F-D2987F683525/data/Containers/Bundle/Applicat ion/BD248843-0DA5-4D0F-91C5-7EBE5D97E687/Test_ipa_simple.app/Test_ipa_simple'.0)
// Feedfacf is an executable file in Mach-O format🤯🤯🤯 main Mach -o Magic Number: feedfacf // close(fd);Similarly, calling the system's close function is converted to calling our own my_close function 🤯🤯🤯my_close Calling real close(3).// Then there is a stack of my_open and my_close printouts below, which are other open and close calls while the program is running.
Copy the code
Debug the Lazy Symbol Pointer binding process through LLDB and the result of fishHook
(__DATA, __la_symbol_ptr) Lazy Symbol Pointer to _open is bound. And the process that the Lazy Symbol Pointer points to changes after fishhook processing.
Let’s take a look at the sequence of Pointers to the _open pointer. This will not only help us understand Mach-O better, but also improve our understanding of Pointers! (I thought I knew Pointers pretty well before I started, but I’m still pretty young!)
First is the first figure, a Pointer (Indirect Pointer) in (__DATA, __la_symbol_ptr) with an offset of 0x000C0E0 bytes from the start of the Mach-O binary. Suppose the Mach -O binary starts at 0x100000000, that is, the address of this indirect pointer is 0x10000C0E0, and then the value of 0x000000010000609E is stored in eight consecutive bytes under this address. A pointer starting at 0x10000C0E0 to 0x10000609E! So now some of you might be asking, where does _open come from? (The essence of a pointer is an 8-byte space that holds something, normally an address, so how does that address relate to the _open string?) No hurry. We’ll take our time down here. 0x10000609E is located in the (__TEXT, __stub_helper) section of the MachOView. The Symbol Pointer in the (__DATA, __la_symbol_ptr) section starts with a Pointer to the (__TEXT, __stub_helper) section.
(__TEXT, __stub_helper) area of knowledge, we will not extend down, it is actually super important, we will temporarily stay behind analysis. How does the pointer to 0x10000C0E0 connect to _open? LC_DYSYMTAB Load Command
LC_DYSYMTAB IndSym Table Offset = 0x00011D20 So it can be calculated that the position of the Dynamic Symbol Table should be at 0x100011d20. Sure enough, looking down at the Mach -O binary, we basically see the Dynamic Symbol Table at the bottom, whose Offset value is exactly 0x0011D20, which is the same as the value in the figure above. In addition, all Symbols stored in the Dynamic Symbol Table are Indirect Symbols. At this time, we can find them in the area of (__DATA, __la_symbol_ptr). And the Indirect Address is a Symbol of 0x10000COE0, as shown in the following figure. We have indeed found the Indirect Address, and we see the familiar _open character again. We see that the Index of this Symbol in the Symbol Table is 0x0000014C (#332). (The Indirect Address value is the offset of these symbols relative to the starting point of the Mach-O binary + the starting point of the Mach-O binary, the location of the Section in which these symbols reside)
We can find the content of our _open Symbol in index 332 of Symbol Table. So where does the _open string come from? The name of the Symbol at #332 in the Symbol Table is #1171.
Select * from String Table where index #1171 = 1;
Finally, we find the 0x10000C0E0 pointer in (__DATA, __la_symbol_ptr) that points to _open! And the _open String comes from the String Table.
Okay, so now that we know where the _open string comes from, let’s look at how the _open pointer is bound to the symbol.
First there is a lazy loaded symbol pointer to _open in __DATA, __la_symbol_ptr. Dyld will bind the symbol pointer open correctly only when we use the open function, as well as the _open address in the symbol table, which we know comes from the system’s dynamic library.
The image list command lists the memory address and local path of the current process, as well as the memory address and local path of the shared library image that the process depends on.
(lldb) image
Commands for accessing information for one or more target modules.
Syntax: target modules <sub-command> ...
The following subcommands are supported:
add -- Add a new module to the current target's modules.
dump -- Commands for dumping information about one or more target
modules.
list -- List current executable and dependent shared library
images.
load -- Set the load addresses for one or more sections in a
target module.
lookup -- Look up information within executable and dependent
shared library images.
search-paths -- Commands for managing module search paths for a target.
show-unwind -- Show synthesized unwind instructions for a function.
For more help on any particular subcommand, type 'help <command> <subcommand>'.
Copy the code
To make it easier for us to verify the hook process, let’s tweak the above sample code and use the following code as a demonstration.
int main(int argc, char * argv[]) {
// ⬇️ make a breakpoint on this line
NSLog(@"🎃 🎃 🎃 % p", open);
int fd = open(argv[0], O_RDONLY);
uint32_t magic_number = 0;
read(fd, &magic_number, 4);
printf("🤯🤯🤯 %s Mach -o Magic Number: %x \n", __func__, magic_number);
close(fd);
rebind_symbols((struct rebinding[2]) {{"close", my_close, (void *)&orig_close}, {"open", my_open, (void *)&orig_open}}, 2);
NSString * appDelegateClassName;
@autoreleasepool {
// Setup code that might create autoreleased objects goes here.
appDelegateClassName = NSStringFromClass([AppDelegate class]);
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
Copy the code
Execute the code above and enter the first breakpoint:
- through
image list
Print the memory address where the current process and its dependent shared library image were loaded. The first is the memory header of our current process:0x000000010b35f000
. (Also found that sometimes the first one is/usr/lib/dyld
, we need to look at the path behind, we need the current process in memory first address.
[ 0] BAAF897A- 1463.- 3D9E-BDFE-EA61525D3435 0x000000010b35f000/Users/hmc/Library/Developer/Xcode/DerivedData/Test_ipa_simple-hfabjfhaswcxjleagxtdjjvbnnhi/Build/Products/Debug-iphones imulator/Test_ipa_simple.app/Test_ipa_simpleCopy the code
- Right-click on test_ipa_simple. app in the Products folder and click Show in Finder, then select test_ipa_simple. app to Show the package contents. Then open the Test_ipa_simple binary with MachOView, where the Mach-O file’s
Section64(__DATA,__la_symbol_ptr)
In the areaLazy Symbol Pointers
seeopen
The symbolic pointer starts at an offset relative to the Mach-O binary:0x000C0D8
, and then pass the current process’s initial address in memory +open
The symbol pointer is obtained at the offset of the binary fileopen
Symbolic pointer address in memory:0x000000010b35f000
+0x000C0D8
=0x10B36B0D8
.
- through
memory read
Just read what we calculated aboveopen
The address of the symbolic pointer in memory (i.e., read the value stored in the address of the pointer, that is, the point of the pointer). We know that iOS is in small-endian mode, so we need to read the contents of the 8 bytes of the pointer backwards, that is:0x010b3650d0
.
- The following through
dis -s
See the above0x010b3650d0
Assembly code in the address. Due to theopen
Is a lazy loading symbol, and at this timeopen
The function has not been used and is not bound at this time. (Currently it is(__TEXT, __stub_helper)
A position of
- And then we step down and pass
NSLog (@ "🎃 🎃 🎃 % p", the open).
And then we printed it outopen
The address to which the function pointer points:0x7fff61133e65
This address is the system to which our current process refersopen
The real address of the function, and then we can go throughdis -s 0x7fff61133e65
Check the assembly code for this address to verify, you can see that this address islibsystem_kernel.dylib open
Function.
- So now let’s do it again
memory read 0x10B36B0D8
Check out oursopen
Is there any change in what the symbol pointer points to? If you print it, it still points0x010b3650d0
That’s ouropen
The point of the symbolic pointer remains the same. (The correct direction should be0x7fff61133e65
)
- So you might have a question at this point, this
open
When can symbolic Pointers be bound and actually point to the systemopen
Function? We have also seen the system aboveopen
The address of the function is0x7fff61133e65
That is, when we performmemory read 0x10B36B0D8
Command output0x7fff61133e65
When, isopen
The symbolic pointer is bound correctly. Looking at the code area above we see our current step toint fd = open(argv[0], O_RDONLY);
At this point, we go to the next step to execute, and then execute againmemory read 0x10B36B0D8
The command is visible0x10B36B0D8
That’s what’s stored in the address0x7fff61133e65
That is, when we first callopen
Delta function,Section64(__DATA,__la_symbol_ptr)
的Lazy Symbol Pointers
In theopen
The symbolic pointer really points to the systemopen
Function.
- So here we have our
open
The symbol pointer is bound correctly, so it is our turn to hook the fishhook functionopen
The pointing of the symbolic pointer has been changed to point to our own function. Let’s do it one step at a time until we get torebind_symbols
The next line, and then we’ll do itmemory read 0x10B36B0D8
Command. We have a visualopen
The point of the symbol pointer changed and now it points0x010b363f60
And then we executedis -s 0x010b363f60
Convert to assembly code, as can be seen at this pointopen
The sign pointer is pointingTest_ipa_simple my_open
Delta function, right hereopen
The symbol pointer points to our custommy_open
Function.
- And then there’s another point that’s going to be in our system
open
Where’s my function? That’s our currentorig_open
The function pointer is pointing to our systemopen
Function to see the currentorig_open
The function pointer points to exactly this address:0x00007fff61133e65
For the system we saw aboveopen
Address of the function. That’s when it goes throughorig_open
The function pointer calls our system’sopen
Function.
In this way, we have seen the Symbol Pointer binding process of _open and the result of fishhook implementation through LLDB. The specific execution details will be analyzed in the following chapters.
Symbol Pointer A process to a Symbol
Before we look at the inner workings of Fishhook, let’s take a look at one other point. In main we print the address of the NSLog function (NSLog(@”🎃🎃🎃 %p”, NSLog); Console output: 🎃🎃🎃 0x7ffF20805d0d), we print for many times, or run the print again after deleting APP. It can be seen that the address of NSLog function is always fixed. (It doesn’t change because the NSLog function comes from a shared dynamic library called Foundation, which is in the shared cache and can be referenced by multiple processes, so even if we print the address of the NSLog function in another process, You can see that the printed address is the same.
Here we use Fangshufeng /MachOView to look directly at the Mach-O file and find the implementation address of the function. Here we take the NSLog function as an example.
Below we find the function implementation of the string in the shared library based on its pointer to the symbol table.
- in
Section64(__DATA, __la_symbol_ptr)
的Lazy Symbol Pointers
,_NSLog
It’s the first one, subscript 0.
- in
Dynamic Symbol Table
的Indirect Symbols
,_NSLog
It’s also the first one, but it’s(__TEXT, __stubs)
In the area_NSLog
Symbol Pointer, and then we can look at it sequentiallyDynamic Symbol Table
In theIndirect Symbol
, can be found here(__DATA, __got)
In the Section_NSLog
Symbol Pointer, and then finally(__DATA, __la_symbol_ptr)
In the Section_NSLog
Symbol Pointer, and then all three of them inIndirect Symbols
The value of Data is:0x111
(decimal: 273), that is, their index in the Symbol Table is the same, that is, they all point to the same Symbol in the Symbol Table.
- Then we take the index 273 that we got above
Symbol Table
的Symbols
We look at the value of Offset and scroll up until we get to #273 and we find it_NSLog
At this point we see that_NSLog
The value of the entry’s Data is:Ox000000E1
, this value corresponds to_NSLog
在String Table Index
The offset value in.
- Finally, in
String Table
Calculation header (Ox00011D00
) + offset (Ox000000E1
) :Ox00011DE1
, that is, foundNSLog
The function name string inString Table
The location of the.
Section64(__DATA, __la_symbol_ptr) : Lazy Symbol Pointers -> Dynamic Symbol Table Indirect Symbols (Data converted to base 10 for a subscript) -> Symbol Table: Symbols (Data is used as an offset) -> String Table. Keep in mind that the following fishhook implementation code looks for names in a rebinding. (If you understand this search process, the fishhook sample diagram below is easy to see!)
Let’s move on to Fishhook and see how it works.
fishhook How it works
The following is an introduction to the fishhook process in the official documentation:
Dyld binds lazy and non-lazy symbols by updating Pointers in a specific part of the __DATA segment of the Mach-O binary. Fishhook rebinds these symbols by determining the update location of each symbol name passed to Rebind_Symbols and then writing the corresponding substitution.
For a given image, the __DATA section may contain two dynamic symbol bindings sections: (__DATA __nl_symbol_ptr) and (__DATA, __la_symbol_ptr). __nl_symbol_ptr is an array of Pointers to non-lazily bound data (these are bound when the library is loaded), and __la_symbol_ptr is an array of Pointers to imported functions, The symbol is usually filled with a routine named dyLD_STUB_binder the first time it is called (dyLD can also be told to bind these at startup). In order to find the symbol name corresponding to a particular location in one of these sections, we must skip several layers of indirection. For the two related sections, the Section headers (struct section from
) provides an offset (in the reserveD1 field) to the so-called indirect symbol table. The indirect symbol table in the __LINKEDIT section of the binary is simply an array of indexes for the symbol table (also in __LINKEDIT) in the same order as Pointers in the non-lazy and lazy symbol sections. Therefore, for a given struct section NL_symbol_ptr, the symbol table for the first address in the section is indirect_symbol_TABLE [NL_symbol_ptr -> reserveD1]. The symbol table itself is an array of struct nLists (see < Mach-o /nlist.h>), and each nlist contains an index to the string table in __LINKEDIT, where the actual symbol names are stored. So, for each pointer __nl_symbol_ptr and __la_symbol_ptr, we are able to find the corresponding symbol, then find the corresponding string to compare with the requested symbol name, and replace the pointer in section if there is a match.
The procedure for finding a given entry name in the lazy and non-lazy pointer tables is as follows:
Fishhook. h/fishhook.c
Fishhook source code interpretation
fishhook.h
The fishhook.h file is minimal, with only one structure definition and two function declarations.
struct rebinding
/* * A structure representing a particular intended rebinding from a symbol name to its replacement */
struct rebinding {
const char *name;
void *replacement;
void **replaced;
};
Copy the code
A rebinding represents a structure that is specifically intended to be rebound from the symbol name to its replacement.
As in our sample code above: (struct rebinding [2]) {{” close “, my_close, (void *) & orig_close}, {” open “, my_open, (void *) & orig_open}}, A rebinding is an array of rebinding structures of length 2, where the literal of the first element can be converted to:
struct rebinding closeVariable;
closeVariable.name = "close";
closeVariable.replacement = my_close;
closeVariable.replaced = (void *)&orig_close;
Copy the code
The rebinding structure is a data structure used to define the replacement function. The name member variable represents the name of the function to Hook (a C string), and the replacement pointer is used to specify the new function address, that is, the function address corresponding to name is replaced by replacement (the name of a C function is a function pointer, which is determined at static language compilation time). Replaced is a two-dimensional pointer. It is used to directly change the pointing of external pointer variables inside functions. It is used in rebinding structures to record the address of the original function corresponding to name. When executing the closeVariable as an argument to the rebind_symbols function, locate the close symbol pointer (which is a system function) and point it to the my_close function in the symbol table. Orig_close is used to record the original address of the close function.
rebind_symbols
/* * For each rebinding in rebindings, rebinds references to external, * indirect symbols with the specified name to instead point at replacement * for each image in the calling process as well as for all future images that are loaded by the process. * If rebind_functions is called more than once, the symbols to rebind are added to the existing list of rebindings, * and if a given symbol is rebound more than once, the later rebinding will take precedence. */
FISHHOOK_VISIBILITY
int rebind_symbols(struct rebinding rebindings[], size_t rebindings_nel);
Copy the code
For each rebinding in REbindings, the reference to the external indirect symbol with the specified name is rebound to replace every image in the calling process and all future images loaded by that process. If rebIND_functions is called more than once, the symbol to be rebound is added to the existing rebind list, and if the given symbol is rebound more than once, subsequent rebindings take precedence. (that is, the last one overrides the previous symbol binding)
rebind_symbols_image
/* * Rebinds as above, but only in the specified image. The header should point * to the mach-o header, the slide should be the slide offset. Others as above. */
FISHHOOK_VISIBILITY
int rebind_symbols_image(void *header,
intptr_t slide,
struct rebinding rebindings[],
size_t rebindings_nel);
Copy the code
Rebinding is the same as above, but only in the specified image, the header argument points to the header of the mach-o file, the slide argument specifies the slide offset of the image in virtual memory, and all other functions are the same as rebind_symbols.
Note that the rebind_symbols and rebind_symbols_image functions are still a bit confusing, so let’s look directly at their function definitions in Fishhook.
fishhook.c
Fishhook.c isn’t much, either, so let’s take a look at the data structures involved and then follow the function call process to see how each function is executed.
The first is a familiar set of typedef renaming structures used to describe data structures in Mach-O files. (Use 32/64-bit definitions depending on platform)
#ifdef __LP64__
typedef struct mach_header_64 mach_header_t; // ⬅️ represents the hader (header) structure of Mach-o
typedef struct segment_command_64 segment_command_t; // ⬅️ represents the segment load command structure in Mach -o
typedef struct section_64 section_t; // ⬅️ represents the structure for sections in a segment in Mach-o
typedef struct nlist_64 nlist_t; // ⬅️ represents the symbol table entry structure in Mach-o.
#define LC_SEGMENT_ARCH_DEPENDENT LC_SEGMENT_64
#else
typedef struct mach_header mach_header_t;
typedef struct segment_command segment_command_t;
typedef struct section section_t;
typedef struct nlist nlist_t;
#define LC_SEGMENT_ARCH_DEPENDENT LC_SEGMENT
#endif
#ifndef SEG_DATA_CONST
#define SEG_DATA_CONST "__DATA_CONST"
#endif
Copy the code
rebindings_entry
struct rebindings_entry {
struct rebinding *rebindings;
size_t rebindings_nel;
struct rebindings_entry *next;
};
static struct rebindings_entry* _rebindings_head;
Copy the code
The rebindingS_entry structure is used as a data structure for a one-way list node (the contents of the list are the rebinding structure above), rebindings represents the contents of the current node, rebindings_NEL is the current list length (the number of nodes), Next points to the next node.
_rebindingS_head is a static global pointer to RebindingS_ENTRY, which is used as the head of a linked list. The contents of each node in the list are the rebinding array passed in each time we call rebind_symbols. Calling the rebind_symbols function once builds a rebindingS_entry node. This new node is not spliced at the end of the list. The next of this new node points directly to _rebindings_head. Then update _rebindings_head to the address of the new node, which is concatenated directly to the head of the original list every time a new node comes in.
rebind_symbols
Let’s look at the internal implementation details of each function along with the call flow of rebind_symbols.
int rebind_symbols(struct rebinding rebindings[], size_t rebindings_nel) {
// The prepend_rebindings function is resolved below ⬇️⬇️
int retval = prepend_rebindings(&_rebindings_head, rebindings, rebindings_nel);
// If retval returns -1, the prepend_rebindings function (malloc) has failed
if (retval < 0) {
return retval;
}
// If this was the first call, register callback for image additions (which is also invoked for existing images, otherwise, just run on existing images
// If this is the first call, register the image-added callback. (_rebindingS_head ->next will be nil on the first call to rebind_symbols)
// the _dyLD_register_func_for_add_image function is used to register the _rebind_symbols_for_image function as a callback for adding images.
// If _rebindingS_head ->next does not exist, the rebind_symbols function is called for the first time
if(! _rebindings_head->next) {// Register the _rebind_symbols_for_image function as a callback for image addition
// the ⬇️ _dyLD_register_func_for_add_image function is analyzed in detail below
_dyld_register_func_for_add_image(_rebind_symbols_for_image);
} else {
// Get the number of current images mapped by dyld
uint32_t c = _dyld_image_count();
The segment load command of the image uses the segment load command to bind all rebindings_head nodes to the segment load command of the image
for (uint32_t i = 0; i < c; i++) {
// ⬇️ _rebind_symbols_for_image function breaks down below (find binding in current image)_rebind_symbols_for_image(_dyld_get_image_header(i), _dyld_get_image_vmaddr_slide(i)); }}return retval;
}
Copy the code
_dyld_register_func_for_add_image
/* * The following functions allow you to install callbacks which will be called by dyld whenever an image is loaded or unloaded. * During a call to _dyld_register_func_for_add_image() the callback func is called for every existing image. * Later, it is called as each new image is loaded and bound (but initializers not yet run). * The callback registered with _dyld_register_func_for_remove_image() is called after any terminators in an image are run and before the image is un-memory-mapped. */
extern void _dyld_register_func_for_add_image(void (*func)(const struct mach_header* mh, intptr_t vmaddr_slide)) __OSX_AVAILABLE_STARTING(__MAC_10_1, __IPHONE_2_0);
extern void _dyld_register_func_for_remove_image(void (*func)(const struct mach_header* mh, intptr_t vmaddr_slide)) __OSX_AVAILABLE_STARTING(__MAC_10_1, __IPHONE_2_0);
Copy the code
The following functions allow you to register callbacks that dyLD calls whenever an image is loaded or unloaded. The callback function is also called for each existing image during the call to _dyLD_register_func_for_add_image (). The previously registered callback function is also called later when each new image is loaded and bound (but the initializer has not yet been run). Callbacks registered with _dyLD_register_func_for_remove_image () are called after any terminators in the image have been run and before the image unmapped memory (un-memory-mapped).
The _DYLD_OBJC_notify_register function is a callback function that registers a situation for dyLD. The _dyLD_register_func_for_add_image registered callback function here has two call times:
- Call out
_dyld_register_func_for_add_image
The current state will be iterated directlyimage->getState() >= dyld_image_state_bound && image->getState() < dyld_image_state_terminated
Image calls the callback function. - When a new image is added to the
sAddImageCallbacks
(It is statically globalstd::vector<ImageCallback>
The callback function is called when the variable is used.
(You can see the definition of these functions in the dyLD source code, so I won’t post the source code here.)
_dyld_image_count _dyld_get_image_header _dyld_get_image_vmaddr_slide
The following functions help you traverse all loaded images. This is not a thread-safe operation, and another thread may add or remove images during this thread’s iteration. Many of the purposes of these routines can be replaced by calling dladdr(), which returns the mach_header and image name, given the address in the image. Dladdr () is thread-safe.
/* * The following functions allow you to iterate through all loaded images. * This is not a thread safe operation. Another thread can add or remove an image during the iteration. * * Many uses of these routines can be replace by a call to dladdr() which will return the mach_header and name of an image, given an address in the image. dladdr() is thread safe. */
extern uint32_t _dyld_image_count(void) __OSX_AVAILABLE_STARTING(__MAC_10_1, __IPHONE_2_0);
extern const struct mach_header* _dyld_get_image_header(uint32_t image_index) __OSX_AVAILABLE_STARTING(__MAC_10_1, __IPHONE_2_0);
extern intptr_t _dyld_get_image_vmaddr_slide(uint32_t image_index)
Copy the code
_dyLD_IMAGe_count returns the number of current images mapped by dyLD. Note: Iterating through all images using this count is not thread-safe, as another thread may add or remove images during the iteration.
_dyLD_GET_image_header returns a pointer to the mach_header of the image indexed by image_index. If image_index is out of range, NULL is returned.
_dyLD_GEt_image_vmaddr_slide returns the virtual memory address slip of the image indexed by image_index. Returns zero if image_index is out of range.
Now that we have some background on DyLD, let’s move on to the code in Fishhook.c.
prepend_rebindings
static int prepend_rebindings(struct rebindings_entry **rebindings_head,
struct rebinding rebindings[],
size_t nel) {
// Call malloc to request 24 bytes of space and force the first address into the struct Rebindings_entry pointer. (sizeof(struct rebindings_entry) = 24)
struct rebindings_entry *new_entry = (struct rebindings_entry *) malloc(sizeof(struct rebindings_entry));
// If malloc fails to apply for space, -1 is returned
if(! new_entry) {return - 1;
}
// Call malloc to claim space and assign the starting address to new_entry's rebinings. (The size of the requested space is the memory size of the rebinding times the total number of rebinding structures.)
new_entry->rebindings = (struct rebinding *) malloc(sizeof(struct rebinding) * nel);
// If malloc fails to apply for space, return -1 (the space requested by malloc must be freed by the new_entry pointer)
if(! new_entry->rebindings) {/ / release new_entry
free(new_entry);
return - 1;
}
// Call the memcpy function to copy all the rebinding elements in the rebindings array passed in by the input parameter into the rebindings member variable of new_entry
memcpy(new_entry->rebindings, rebindings, sizeof(struct rebinding) * nel);
// nel is the length of the input rebindings array, assigned to the rebindings_nel member variable of new_entry, and also indicates how many rebinding structures are currently stored in new_entry
new_entry->rebindings_nel = nel;
// The next member of new_entry points to the first argument *rebindings_head
new_entry->next = *rebindings_head;
// new_entry is assigned to the first argument.
Static struct rebindingS_entry * _rebindingS_head; This global variable,
// The prepend_rebindings function builds a struct rebindingS_entry *new_entry every time it is called, and adds new_entry to the head of the list.
// Then pass *rebindings_head = new_entry; Make sure rebindingS_head is always the head of the list)
*rebindings_head = new_entry;
return 0;
}
Copy the code
The prepend_rebindings function is finished. Inside it, you build a struct rebindingS_entry *new_entry variable, Then copy the elements in the struct rebinding rebindings[] array directly to the struct rebinding *rebindings of new_entry; Then assign the size_t nel entry parameter to new_entry’s size_t rebindings_nel; (NEL is the length of the ReBindings array), and then finally and most importantly new_entry will be stitched into the rebindingS_HEAD list to the header, and the value of rebindingS_HEAD will be updated to ensure that it is still the header of the current list.
Let’s look at the core function of Fishhook, which is also the core function of the rebind_symbols function above: _rebind_symbols_for_image.
_rebind_symbols_for_image
static void _rebind_symbols_for_image(const struct mach_header *header,
intptr_t slide) {
rebind_symbols_for_image(_rebindings_head, header, slide);
}
Copy the code
Emmm… Internally call rebind_symbols_for_image (with _ removed) and pass in the global variable _rebindings_head as the first argument. Let’s take a closer look at the rebind_symbols_for_image function definition.
rebind_symbols_for_image
Before we start looking at rebind_symbols_for_image, let’s take a few screenshots, The rebind_symbols_for_image function iterates through all the Load commands in the image corresponding to the header parameter and finds the following three Load commands:
LC_SEGMENT_64 (__LINKEDIT) :
LC_SYMTAB:
LC_DYSYMTAB:
The last screenshot shows the String Table, which is used to record the symbol names in the symbol Table. The name String in the Rebinding structure is compared to the symbol name String in the String Table.
static void rebind_symbols_for_image(struct rebindings_entry *rebindings,
const struct mach_header *header,
intptr_t slide) {
// The dladdr function is used to determine whether the header address has a corresponding image
Dl_info info;
if (dladdr(header, &info) == 0) {
return;
}
< span style = "max-width: 100%; clear: both
// cur_seg_cmd is only used to record the segment load command for each loop
segment_command_t *cur_seg_cmd;
LC_SEGMENT_64(__LINKEDIT), LC_SYMTAB, LC_DYSYMTAB
segment_command_t *linkedit_segment = NULL;
struct symtab_command* symtab_cmd = NULL;
struct dysymtab_command* dysymtab_cmd = NULL;
The Mach header part of mach-o goes directly to the start of the load Command part
uintptr_t cur = (uintptr_t)header + sizeof(mach_header_t);
// Iterate over each segment load commands in the Mach-o file
// Select segment load commands of type LC_SEGMENT_ARCH_DEPENDENT(name is __LINKEDIT), LC_SYMTAB, LC_DYSYMTAB
for (uint i = 0; i < header->ncmds; i++, cur += cur_seg_cmd->cmdsize) {
cur_seg_cmd = (segment_command_t *)cur;
// #define LC_SEGMENT_ARCH_DEPENDENT LC_SEGMENT_64
// Check whether the Load command is of type LC_SEGMENT_64 and whether its segname is __LINKEDIT. If yes, LC_SEGMENT_64 is found.
if (cur_seg_cmd->cmd == LC_SEGMENT_ARCH_DEPENDENT) {
// #define SEG_LINKEDIT "__LINKEDIT"
if (strcmp(cur_seg_cmd->segname, SEG_LINKEDIT) == 0) {
/ / SEG_LINKEDIT: the segment containing all structs created and maintained by the link editor. Created with -seglinkedit option to ld(1) for MH_EXECUTE and FVMLIB file types only
// SEG_LINKEDIT: The segment that contains all the structures created and maintained by the Link Editor. Create LD (1) using the -Seglinkedit option only for MH_EXECUTE and FVMLIB file typeslinkedit_segment = cur_seg_cmd; }}else if (cur_seg_cmd->cmd == LC_SYMTAB) {
// #define LC_SYMTAB 0x2 /* link-edit stab symbol table info */
// LC_SYMTAB: link-edit stab symbol table info
LC_SYMTAB Load command, which records the offset of Symbol Table and String Table
symtab_cmd = (struct symtab_command*)cur_seg_cmd;
} else if (cur_seg_cmd->cmd == LC_DYSYMTAB) {
// #define LC_DYSYMTAB 0xb /* dynamic link-edit symbol table info */
// LC_DYSYMTAB: dynamic link-edit symbol table info
LC_DYSYMTAB Load command, which records the offset of the Dynamic Symbol Tabledysymtab_cmd = (struct dysymtab_command*)cur_seg_cmd; }}// If the segment load command is not found, return the segment load command
if(! symtab_cmd || ! dysymtab_cmd || ! linkedit_segment || ! dysymtab_cmd->nindirectsyms) {return;
}
// Find base symbol/string table addresses
// Find the base of symbol/string table
LC_SYMTAB = Symbol Table Offset = String Table Offset = Symbol Table Offset = String Table Offset Symbol Table (String Table) Symbol Table (String Table)
Debug Lazy Symbol Pointer binding using LLDB and fishhook
// What is the value of linkedit_base? (Image address in memory)
// We can directly break the point on this line and compare it with the address we obtained from the image list. When the header is the image of our current process, we can find that the values are equal. (test: 0x0000000106730000 not screenshot here, you can print it for yourself)
Uintptr_t slide + linkedit_segment-> vmaddr-linkedit_segment -> fileOff
// uint64_t fileoff; /* file offset of this segment */ fileOFF indicates the segment offset in the Mach-O binary
// Process start address = __linkedit.vm_address - __linkedit.file_offset + silde change value
// LC_SEGMENT_64(__LINKEDIT) segment's virtual address, then subtracts this segment's file offset in the Mach-O binary to return to the starting position of the Mach-O binary, then adds the silde random value to change to the starting address of the image
Header and linkedit_base are, for the most part, equal.
uintptr_t linkedit_base = (uintptr_t)slide + linkedit_segment->vmaddr - linkedit_segment->fileoff;
// ⬇️⬇️⬇️ The following three local variables correspond to the Symbol Table, String Table, Dynamic Symbol Table, three tables!
// address of symbol table = base address + symbol table offset
nlist_t *symtab = (nlist_t *)(linkedit_base + symtab_cmd->symoff);
// Address of the string table = base address + string table offset
char *strtab = (char *)(linkedit_base + symtab_cmd->stroff);
// Get indirect symbol table (array of uint32_t indices into symbol table)
// Dynamic symbol table address = base address + dynamic symbol table offset
uint32_t *indirect_symtab = (uint32_t *)(linkedit_base + dysymtab_cmd->indirectsymoff);
// The Mach header part of mach-o is passed directly to the first address of the load Command part and assigned to cur
cur = (uintptr_t)header + sizeof(mach_header_t);
// Segment load command Where SECTION_TYPE in the __DATA and __DATA_CONST sections is S_LAZY_SYMBOL_POINTERS and S_NON_LAZY_SYMBOL_POINTERS
__DATA_CONST, __got), (__DATA, __la_symbol_ptr), and (__DATA, __nl_symbol_ptr).
for (uint i = 0; i < header->ncmds; i++, cur += cur_seg_cmd->cmdsize) {
cur_seg_cmd = (segment_command_t *)cur;
// Load commands of type LC_SEGMENT_64 whose name is __DATA or __DATA_CONST will be skipped
// #define LC_SEGMENT_ARCH_DEPENDENT LC_SEGMENT_64
if (cur_seg_cmd->cmd == LC_SEGMENT_ARCH_DEPENDENT) {
// #define SEG_DATA "__DATA" /* the tradition UNIX data segment */
// #define SEG_DATA_CONST "__DATA_CONST"
if (strcmp(cur_seg_cmd->segname, SEG_DATA) ! =0 && strcmp(cur_seg_cmd->segname, SEG_DATA_CONST) ! =0) {
continue;
}
_la_symbol_ptr, __nl_symbol_ptr, and __got are available in __DATA/__DATA_CONST sections.
for (uint j = 0; j < cur_seg_cmd->nsects; j++) {
section_t *sect = (section_t *)(cur + sizeof(segment_command_t)) + j;
// #define S_LAZY_SYMBOL_POINTERS 0x7 /* section with only lazy symbol pointers */
// #define S_NON_LAZY_SYMBOL_POINTERS 0x6 /* section with only non-lazy symbol pointers */
// (__DATA_CONST, __got), (__DATA, __la_symbol_ptr), and (__DATA_CONST, __got) call perform_rebinding_with_section
if ((sect->flags & SECTION_TYPE) == S_LAZY_SYMBOL_POINTERS) {
perform_rebinding_with_section(rebindings, sect, slide, symtab, strtab, indirect_symtab);
}
if ((sect->flags & SECTION_TYPE) == S_NON_LAZY_SYMBOL_POINTERS) {
perform_rebinding_with_section(rebindings, sect, slide, symtab, strtab, indirect_symtab);
}
}
}
}
}
Copy the code
The internal flow of rebind_symbolS_for_image is clear, Find the lazy and non-lazy symbol Pointers in the Mach-o file and call the perform_rebinding_with_section function.
Now let’s look at the contents of the perform_rebinding_with_section function.
/* * An indirect symbol table entry is simply a 32bit index into the symbol table to the symbol that the pointer or stub is refering to. Unless it is for a non-lazy symbol pointer section for a defined symbol which strip(1) as removed. In which case it has the value INDIRECT_SYMBOL_LOCAL. If the symbol was also absolute INDIRECT_SYMBOL_ABS is or'ed with that. */
#define INDIRECT_SYMBOL_LOCAL 0x80000000
#define INDIRECT_SYMBOL_ABS 0x40000000
Copy the code
perform_rebinding_with_section
static void perform_rebinding_with_section(struct rebindings_entry *rebindings,
section_t *section,
intptr_t slide,
nlist_t *symtab,
char *strtab,
uint32_t *indirect_symtab) {
// #define SEG_DATA_CONST "__DATA_CONST"
// isDataConst is used to mark whether the entry argument section belongs to the __DATA_CONST segment
const bool isDataConst = strcmp(section->segname, SEG_DATA_CONST) == 0;
// The reserveD1 fields in the NL_symbol_ptr and LA_symbol_ptr sections indicate their respective starting indexes in the indirect Symbol table.
Indirect_symtab is the start address for Indirect Symbols. The symbol in question is Indirect Symbols. These are symbols Stubs in (__TEXT, __stubs) and non-lazy Symbol Pointers in (_DATA_CONST, __got) and (__DATA, __la_symbol_ptr) for Lazy Symbol Pointers.
// For example, under the Indirect Symbols in the Dynamic Symbol Table:
// [#0 ~ #33] is the Symbol Stubs in (__TEXT, __stubs)
// [#34 to #39] is a non-lazy Symbol pointer for (_DATA_CONST, __got)
// [#40 ~ #73] is a Lazy Symbol pointer for (__DATA, __la_symbol_ptr)
The value of the reserved1 field in the Load command is Indirect Sym Index and the value of the Indirect Sym Index is 34.
// Then (__DATA, __la_symbol_ptr) the value of the reserveD1 field in the Load command is Indirect Sym Index and the value is 40.
// We can understand the following: The value of indirect_symtab + section->reserved1 is either (_DATA_CONST, __got) or (__DATA, The symbolic pointer in __la_symbol_ptr) is the starting point in Indirect Symbols,
// Where each starting point is an array of uint32_t, Every 4 bytes is the Symbol Table index of a Symbol pointer in (_DATA_CONST, __got) or (__DATA, __la_symbol_ptr).
/ / 1 ⃣ ️ 1 ⃣ ️ 1 ⃣ ️
uint32_t *indirect_symbol_indices = indirect_symtab + section->reserved1;
// Slide + section-> addr is the starting address of the symbol pointer array in (_DATA_CONST, __got) or (__DATA, __la_symbol_ptr), You can then iterate through all symbol Pointers via indirecT_symbol_Bindings
/ / 2 ⃣ ️ 2 ⃣ ️ 2 ⃣ ️
void **indirect_symbol_bindings = (void((* *)uintptr_t)slide + section->addr);
// typedef int vm_prot_t;
// #define VM_PROT_READ ((vm_prot_t) 0x01) /* read permission */
vm_prot_t oldProtection = VM_PROT_READ;
// If it is a section from a __DATA_CONST section, the __DATA_CONST section, as its name indicates, can only be read and cannot be written, so it cannot be modified.
if (isDataConst) {
oldProtection = get_protection(rebindings);
// Change the permission to read and write
mprotect(indirect_symbol_bindings, section->size, PROT_READ | PROT_WRITE);
}
// iterate over (_DATA_CONST, __got) or (__DATA, For each Symbol Pointer in the __la_symbol_ptr section (i.e. iterating through each Symbol Pointer for Lazy and non-lazy Symbol Pointers) The sizeof(void *) in the for loop evaluates the length of a pointer.
for (uint i = 0; i < section->size / sizeof(void *); i++) {
In the case of Indirect Symbols, I have an array of Symbols in the Symbol Table. By using this index, I can find the corresponding Symbol in the Symbol Table
/ / 1 ⃣ ️ 1 ⃣ ️ 1 ⃣ ️
uint32_t symtab_index = indirect_symbol_indices[i];
if (symtab_index == INDIRECT_SYMBOL_ABS || symtab_index == INDIRECT_SYMBOL_LOCAL ||
symtab_index == (INDIRECT_SYMBOL_LOCAL | INDIRECT_SYMBOL_ABS)) {
continue;
}
// Use symtab_index as the subscript to access the symbol table, and find the corresponding symbol, and then strtab_offset value is the first character index of the symbol name String in the String table
uint32_t strtab_offset = symtab[symtab_index].n_un.n_strx;
// The first address of the String Table is offset, so we get symbol_name
char *symbol_name = strtab + strtab_offset;
// Check if the symbol name string has two characters, and why it has two characters, because it is preceded by an _, so the symbol name must be at least 1 in length
bool symbol_name_longer_than_1 = symbol_name[0] && symbol_name[1];
// This is a double-layer loop. The outer loop iterates over the symbol pointer and the inner loop iterates over the rebinding array of the linked list constructed by calling rebind_symbols to see if we can find the function we want to hook
// Hook the list with a rebinding and assign the head to cur
struct rebindings_entry *cur = rebindings;
// The outer while loop is used to iterate over the RebindingS_ENTRY list
while (cur) {
// The inner for loop iterates through the Rebinding array for each node of the list
for (uint j = 0; j < cur->rebindings_nel; j++) {
// Check that the symbol name is longer than 1 and that the symbol name is the same as the rebinding name
if (symbol_name_longer_than_1 && strcmp(&symbol_name[1], cur->rebindings[j].name) == 0) {
// If rebinding values are not NULL, cur->rebindings[j].
// If the current symbol pointer points to a different symbol than the function the hook is replacing, it is necessary to hook.
if(cur->rebindings[j].replaced ! =NULL&& indirect_symbol_bindings[i] ! = cur->rebindings[j].replacement) {// rebinding - replace records function bindings[j]. Replace stores function addresses of indirect_symbol_Bindings [I]
*(cur->rebindings[j].replaced) = indirect_symbol_bindings[i];
}
// Replace the function pointed to by the symbolic pointer for this loop with the replacement function we prepared in rebinding
/ / 2 ⃣ ️ 2 ⃣ ️ 2 ⃣ ️
indirect_symbol_bindings[i] = cur->rebindings[j].replacement;
// the goto statement jumps to symbol_loop below, where the current symbol pointer has been replaced, and starts looking for the next symbol pointer
gotosymbol_loop; }}// Update cur to the next node in the linked list
cur = cur->next;
}
symbol_loop:;
}
// If the section comes from the __DATA_CONST segment, it has changed its read and write permissions, so it needs to change it back!
if (isDataConst) {
int protection = 0;
if (oldProtection & VM_PROT_READ) {
protection |= PROT_READ;
}
if (oldProtection & VM_PROT_WRITE) {
protection |= PROT_WRITE;
}
if (oldProtection & VM_PROT_EXECUTE) {
protection |= PROT_EXEC;
}
mprotect(indirect_symbol_bindings, section->size, protection); }}Copy the code
Perform_rebinding_with_section iterates (_DATA_CONST, __got), (__DATA, __la_symbol_ptr), (__DATA, Each Symbol Pointer in the __nl_symbol_ptr section (i.e. iterating through each Symbol Pointer for Lazy and non-lazy Symbol Pointers) finds and The symbol pointer to the name field of the rebinding structure in rebindingS_ENTRY is the same as the symbol pointer. Then the address of the symbol to which the symbol pointer points is recorded in the press and the function to which the original symbol pointer points. Replace with the replacement field of the rebinding structure.
Now we’ve seen the whole fishhook implementation! 🎉 🎉 🎉
Refer to the link
Reference link :🔗
-
Using symbol Tables skillfully – Exploring the Fishhook Principle (PART 1)
-
Read the Fishhook principle
-
Analysis of the implementation principle of Fishhook
-
Fishhook usage Scenarios & source code analysis
-
Knowledge learned from Fishhook third-party library
-
Self-cultivation for iOS Programmers — The Fishhook Principle (part 5)
-
IOS reverse – Hook/fishHook principle and symbol table
-
IOS reverse RSA theory
-
IOS reverse HOOK principle of Fishhook
-
LXDZombieSniffer
-
SDMagicHook