After mastering the tools mentioned above, you can start developing plug-ins. First, you should think about the needs that the plug-in needs to implement and find the relevant views and controllers. The first step is to use class-dump to analyze the shelled APP. Class-dump can analyze all class names and method declarations based on the symbol table in the Mach-O file.
~ » ./class-dump -H -o /header/path WeChat
After exporting the header files, pull these header files into an XCode project to facilitate subsequent searches.
After you have the header file, you need to lock it to the controller class of the relevant interface. Use OpenSSH to connect to the jailbroken iPhone with Mac, use Cycript to inject the process, call the method [[UIApp keyWindow] recursiveDescription]
to get the hierarchy of the current view, get the address of a view and repeatedly call the [#0x2b2b2b00 nextResponder]
method, you will finally get the interface controller class.
At this point, there are generally two entry points to continue the reverse analysis.
If we want the Hook operation to be triggered by the touch event of a view on the interface, there are generally two ways to locate the action:
[button allTargets]
to get the Targets address, [button actionsForTarget: Targets forControlEvent: [button allControlEvents]]
to get the corresponding action method.The controller's data source may be used during plug-in writing. The data source can be obtained by analyzing the member variable list of the controller class, or by analyzing the data source proxy method of some views (such as TableView). With a data source, richer information about the controller can be obtained.
A simple plug-in may be able to lock the target and hook it using only Cycript, but in many cases the target function has multiple parameters and the logic is relatively complex. If you want to know the details of its internal implementation, you must use the static analysis artifact IDA ---- It can decompile code written in Objective-C into assembly code, and the implementation details of the function will be clear at a glance. When analyzing the disassembly results, due to the message passing characteristics of Objective-C, the general message sending actually calls the objc_msgSend
function. Just remember the meaning of each parameter of objc_msgSend
and the calling convention of the ARM architecture, and you can successfully restore the function call from the assembly code. The calling convention is that the first four parameters of the function are passed using the R0-R3 general registers, more parameters are pushed onto the stack, and the return value is stored in the R0 register. Then [aObject aMessage: arg1];
corresponds to objc_msgSend(aObject, aMessage, arg1);
R0 stores the receiver address of the message, R1 stores the selector, and R2 stores the first parameter address. The message format for more parameters is as follows:
objc_msgSend(R0, R1, R2, R3, *SP, *(SP + sizeOfLastArg), …)
Through the above format, the logic in a certain function can be parsed step by step. If this is supplemented by LLDB's dynamic single-step debugging and breakpoint tracking debugging at the address of a certain assembly statement, it will help to understand the details of the function implementation.
There are many solutions for the Hook target function, but in principle they are all based on the dynamic characteristics of Objective-C and use Method Swizzling to replace the original implementation. This time we will introduce the use of CaptainHook library in detail. This library is implemented based on MSHookMessageEx()
in Cydia Substrate. The declaration of this function is:
void MSHookMessageEx(Class _class, SEL message, IMP hook, IMP *old);
After iOSOpenDev is installed, you can use the CaptainHook Tweak template to create a project. The CaptainHook library introduces a new syntax for writing Hook functions. First, load the class where the function to be Hooked is located in CHConstructor()
, such as CHLoadLateClass(UIView)
. Then register the function CHHook(argNumber, className, arg1, arg2)
to be Hooked. The macro definition of CHConstructor
is as follows:
#define CHConcat(a, b) CHConcat_(a, b)
#define CHConstructor static __attribute__((constructor)) void CHConcat(CHConstructor, __LINE__)()
The content after __attribute__((constructor))
is guaranteed to run when the dylib is loaded, usually when the program starts. Similarly, other symbols are introduced through macro definitions.
Let me introduce how to use CaptainHook to declare the Hook function and implement it, directly into the code.
CHDeclareClass ( BXViewController );
CHOptimizedMethod ( 0 , self , void , BXViewController , viewDidLoad ) {
CHSuper ( 0 , BXViewController , viewDidLoad );
/* HERE TO WRITE YOUR CODE */
}
CHDeclareMethod0 ( void , BXViewController , addFriends ) {
/* HERE TO WRITE YOUR CODE */
}
After the writing is completed, connect to the iPhone at hand for compilation to ensure that a dynamic library corresponding to the architecture is generated.
Use the tool yololib to inject the compiled dylib file into the Load Commands list of the Mach-O executable file.
~ » ./yololib [binary] [dylib file]
The structure of Mach-O files mainly consists of three parts. The front-end part is the Header structure, which saves Mach-O's platform type, file type, number of LoadCommands and other information; followed by the Header is the Load Commands part. By parsing this part, the logical structure of the file and its location in the virtual file can be determined. In-memory layout. The yololib tool changes the information in the Load Commands section to load dylib. The specific implementation process is as follows:
Because the Load Commands information changes, both ncmds and sizeofcmds in the corresponding Header structure will change, so the Header must be modified first:
// 取出 Header
fseek(newFile, top, SEEK_SET);
struct mach_header mach;
fread(&mach, sizeof(struct mach_header), 1, newFile);
NSData* data = [DYLIB_PATH dataUsingEncoding:NSUTF8StringEncoding];
// 计算 dylib 的大小
uint32_t dylib_size = (uint32_t)[data length] + sizeof(struct dylib_command);
dylib_size += sizeof(long) - (dylib_size % sizeof(long));
// 修改 cmds 和 sizeofcmds
mach.ncmds += 1;
uint32_t sizeofcmds = mach.sizeofcmds;
mach.sizeofcmds += dylib_size;
// 写回修改后的 Header
fseek(newFile, -sizeof(struct mach_header), SEEK_CUR);
fwrite(&mach, sizeof(struct mach_header), 1, newFile);
Then change the Load Commands section and add dylib loading information:
fseek(newFile, sizeofcmds, SEEK_CUR);
// 创建一个 dylib 类型的 command
struct dylib_command dyld;
fread(&dyld, sizeof(struct dylib_command), 1, newFile);
// 修改 dyld 结构体数据
dyld.cmd = LC_LOAD_DYLIB;
dyld.cmdsize = dylib_size;
dyld.dylib.compatibility_version = DYLIB_COMPATIBILITY_VERSION;
dyld.dylib.current_version = DYLIB_CURRENT_VER;
dyld.dylib.timestamp = 2;
dyld.dylib.name.offset = sizeof(struct dylib_command);
// 写回修改
fseek(newFile, -sizeof(struct dylib_command), SEEK_CUR);
fwrite(&dyld, sizeof(struct dylib_command), 1, newFile);
The last data written to dylib.
fwrite([data bytes], [data length], 1, newFile);
Use the codesign
command to re-sign the generated dynamic library and all executable files in the APP (including the APP Extension in the Plugin folder). Use the xcrun -sdk iphoneos PackageApplication -v
command to package the dynamic library and all files together. You will understand the whole process. of. If there is an enterprise certificate, the signed and packaged application can be installed on a non-jailbroken iPhone that trusts the certificate. A rowing star!