Daily torture, learning the bottom in the end is useless, many people think that learning the bottom knowledge is just for the interview, the daily development is not used, is it really the case? Today we are going to summarize some situations where Mach – O is used in daily development to see if we can learn the basics.
Stores data in the specified segment and section
In the process of learning about Mach-O and DYLD, we saw that DyLD loads arbitrary sections of the specified segment in the Mach-O file. Can we interfere with Xcode’s generation of the Mach-O file? So, is there a way to dynamically insert new segments and sections into Mach-O during Xcode builds? The __attribute__ section is used to store the specified data into the specified segment and section.
__attribute__ knowledge points are extended
__attribute__ (attribute__) __attribute__ can be used to set Function attributes, Variable attributes, and Type attributes. __attribute__ has two underscores before and after it, followed by a pair of parentheses with the corresponding __attribute__ parameter in the syntax: __attribute__((attribute-list)) Additionally, it must be placed at the end of the declaration; Before. Let’s look at some of the more commonly used GCC Attribute syntax.
__attribute__((format()))
Check parameters in the specified format.__attribute__((__always_inline__))
Enforce inlining.__attribute__((deprecated("Use xxx: instead")
This is probably the one we see more often, to signal that a method has been deprecated and needs to be replaced by another method.__attribute__((__unused__))
Tag functions or variables may not be used.__attribute__((visibility("visibility_type")))
Flags whether the dynamic library symbol is visible, with the following values:
default
Symbols are visible and exportable.hidden
Symbols are hidden, cannot be exported, can only be called in this dynamic library.
-
__attribute__((objc_designated_initializer)) explicitly specifies the method used for initialization. For a good design, there can be multiple initializers, but eventually the designed Initializer method will be called for multiple initializers.
-
__attribute__((unavailable)), __attribute__((unavailable(“Must use XXX: instead.”))); The tag method is disabled and cannot be called directly, but that doesn’t mean it can’t be called; it can still be called using The Runtime in Objective-C.
-
__attribute__((section(“segment,section”))) stores the specified data into the desired segment and section.
-
__attribute__((constructor)) a function marked by attribute((constructor) that is executed before main or when the dynamic library is loaded. In Mach-o, functions marked by attribute((constructor) are in the __mod_init_func section of the _DATA section. What if multiple tagged attribute((constructor) methods want to be executed sequentially? Attribute ((constructor)) supports priority: _attribute((constructor(1))).
-
__attribute__((destructor)) is the opposite of attribute((constructor)) : functions marked by attribute((destructor)) are executed when main exits or the dynamic library is uninstalled. In Mach-O such functions are placed in the __mod_term_func section of the _DATA section.
__attribute__((objc_root_class))
Here we extend another important point that may have been overlooked. We’ve probably always known that NSObject is the root class, and its parent class is nil, and every class we use everyday is a subclass of NSObject (except NSProxy, which is another root class that just follows the NSObject protocol, Does not inherit from NSObject. So can we create a class that doesn’t inherit NSObject to use? The objective-C CLASS that doesn’t use NSOBJECT in this article will give us the answer.
Classes used as root classes are declared using the NS_ROOT_CLASS macro, for example:
- NSProxy
NS_ROOT_CLASS @interface NSProxy <NSObject> { __ptrauth_objc_isa_pointer Class isa; }...Copy the code
- NSObject
OBJC_AVAILABLE(10.0.2.0.9.0.1.0.2.0)
OBJC_ROOT_CLASS // ⬅️ there is an OBJC_ROOT_CLASS macro
OBJC_EXPORT
@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
Class isa OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}
Copy the code
Let’s look at the definition of the OBJC_ROOT_CLASS macro, which is actually __attribute__((OBJC_ROOT_CLASS)).
#if! defined(OBJC_ROOT_CLASS)
# if __has_attribute(objc_root_class)
# define OBJC_ROOT_CLASS __attribute__((objc_root_class))
# else
# define OBJC_ROOT_CLASS
# endif
#endif
Copy the code
__attribute__ ((section (“segment, section”)))
Let’s look at an example code:
struct __COM_easeapi {
int count;
const char *name;
};
// easeAPI_section is a structure variable of type __COM_easeapi, which is then modified with __attribute__ in the __easeAPI section of the __COM segment
volatile struct __COM_easeapi easeapi_section __attribute__ ((section(" __COM, __easeapi"))) = {255."name"};
Copy the code
__attribute__ ((section (“segment, section”))) can only declare C (global) functions, global (static) variables, Objective-C (global) methods, and attributes. For example, if we use it directly in our main function, we get an error like this: ‘Section’ attribute only applies to functions, global variables, Objective-C methods, and Objective-C properties,
Since we need to store the specified information, the typical approach is to use structure variables as in the example above. This approach seems to solve the problem, but there are many limitations:
- Newly inserted section data must be static or global and cannot be generated at run time. (Instead of dynamic data, it can be the return value of a global function.)
__TEXT
Segments, which are read-only, are even more restrictive, supporting only absolute addressing, so they also cannot use string Pointers. The following code:
char *tempString __attribute__((section("__TEXT, __customSection"))) = (char *)"customSection string value";
int tempInt __attribute__((section("__TEXT, __customSection"=)))5;
Copy the code
A pint can be stored and read in the __customSection of the __TEXT segment, while a tempString can be reported directly: Absolute Addressing not allowed in arm64 code but used in ‘_string5’ Referencing ‘cstring’
__attribute__ ((section (“segment, section”))) where the segment can be a known segment name or a custom segment name, and then read the same.
The __attribute__ section is actually populated with data after mach-o loads it into memory, not directly into the Mach-O file. For example, in the example above we used custom segment names. We then use MachOView to view the structure of our executable file. We don’t have any custom sections. Similarly, we use __TEXT and __DATA sections and don’t add any new sections.
Let’s take a look at another example of how to read the values we put in the specified segments and ranges. IOS Development Runtime (12) : Go deep into Mach-O
#import <Foundation/Foundation.h>
#import <dlfcn.h>
#import <mach-o/getsect.h>
#ifndef __LP64__
#define mach_header mach_header
#else
#define mach_header mach_header_64
#endif
const struct mach_header *machHeader = NULL;
static NSString *configuration = @"";
// write __DATA, __customSection
char *string1 __attribute__((section("__DATA, __customSection"))) = (char *)"__DATA, __customSection1";
char *string2 __attribute__((section("__DATA, __customSection"))) = (char *)"__DATA, __customSection2";
// write __CUSTOMSEGMENT, __customSection
char *string3 __attribute__((section("__CUSTOMSEGMENT, __customSection"))) = (char *)"__CUSTOMSEGMENT, __customSection1";
char *string4 __attribute__((section("__CUSTOMSEGMENT, __customSection"))) = (char *)"__CUSTOMSEGMENT, __customSection2";
// Write string in __TEXT, __customSection.
// Absolute addressing not allowed in arm64 code but used in '_string5' referencing 'cstring'
//char *string5 __attribute__((section("__TEXT, __customSection"))) = (char *)"customSection string value";
// write __TEXT, __customSection
int tempInt __attribute__((section("__TEXT, __customSection"=)))5;
int main(int argc, const char * argv[]) {
@autoreleasepool {
// ⬇️ using __attribute__ section directly from main returns the following error:
// 'section' attribute only applies to functions, global variables, Objective-C methods, and Objective-C properties
// int tempInt2 __attribute__((section("__TEXT, __customSection"))) = 5;
if (machHeader == NULL) {
Dl_info info;
dladdr((__bridge const void *)(configuration), &info);
printf("😮 😮 😮 dli_fname: % s \ n", info.dli_fname);
printf("😮 😮 😮 dli_fbase: % p \ n", info.dli_fbase);
printf("😮 😮 😮 dli_sname: % s \ n", info.dli_sname);
printf("😮 😮 😮 dli_saddr: % p \ n", info.dli_saddr);
machHeader = (struct mach_header_64 *)info.dli_fbase;
}
unsigned long byteCount = 0;
uintptr_t *data = (uintptr_t *)getsectiondata(machHeader, "__DATA"."__customSection", &byteCount);
NSUInteger counter = byteCount/sizeof(void*);
for (NSUInteger idx = 0; idx < counter; ++idx) {
char *string = (char *)data[idx];
NSString *str = [NSString stringWithUTF8String:string];
NSLog(@"✳ ️ % @", str);
}
unsigned long byteCount1 = 0;
uintptr_t *data1 = (uintptr_t *)getsectiondata(machHeader, "__CUSTOMSEGMENT"."__customSection", &byteCount1);
NSUInteger counter1 = byteCount/sizeof(void*);
for (NSUInteger idx = 0; idx < counter1; ++idx) {
char *string = (char *)data1[idx];
NSString *str = [NSString stringWithUTF8String:string];
NSLog(@"✳ ️ ✳ ️ % @", str);
}
unsigned long byteCount2 = 0;
uintptr_t *data2 = (uintptr_t *)getsectiondata(machHeader, "__TEXT"."__customSection", &byteCount2);
NSUInteger counter2 = byteCount2/sizeof(int);
for (NSUInteger idx = 0; idx < counter2; ++idx) {
int intTemp = (int)data2[idx];
NSLog(@"✳ ️ ✳ ️ ✳ ️ % d", intTemp); }}return 0;
}
// ⬇️ console print:
/ / the header information😮 😮 😮 dli_fname:/Users/hmc/Library/Developer/Xcode/DerivedData/objc-efzravoaasjkrvghpezsjgrtdmuy/Build/Products/Debug/KCObjc 😮 😮 😮 dli_fbase:0x100000000😮 😮 😮 dli_sname: GCC_except_table1 😮 😮 😮 dli_saddr:0x100003d0c✳️ __DATA, __customSection1 ✳️ __DATA, __customSection2 ✳️✳️ __CUSTOMSEGMENT, __customSection1 ✳️ __CUSTOMSEGMENT, __customSection2 ✳ ️ ✳ ️ ✳ ️5
Copy the code
Some people will think, what is the meaning of setting the data of section, maybe it will be used in the design of the underlying library, but is there any use scenario in our daily development? The answer is yes. This is due to its nature: the section is set before the main function. In fact, this position may help us to do some administrative work, such as APP launcher management: Declare the module name in any module that you want to start independently and write it into the corresponding section. Then when APP starts, the function of loading the startup module can be realized by accessing the content in the specified section. IOS Development Runtime (12) : Go deep into Mach-O
Dladdr introduction
The Dl_info structure and dlADDR function in the sample code may be unfamiliar to us; both are declared in dlfcn.h. If (machHeader == NULL) {… } is used to retrieve the header as an argument to getSectionData to retrieve data from the specified segment or range.
/* * Structure filled in by dladdr(). */
typedef struct dl_info {
const char *dli_fname; /* Pathname of shared object */
void *dli_fbase; /* Base address of shared object */
const char *dli_sname; /* Name of nearest symbol */
void *dli_saddr; /* Address of nearest symbol */
} Dl_info;
extern int dladdr(const void *, Dl_info *);
Copy the code
The dladdr method can be used to retrieve the module, name, and address of a function. Let’s continue with an example that uses the dlADDR method to get the DL_info information for the description function of the NSArray class.
#import <dlfcn.h>
#include <objc/objc.h>
#include <objc/runtime.h>
#include <stdio.h>
int main(int argc, const char * argv[]) {
/ / / *
// * Structure filled in by dladdr().
/ / * /
// typedef struct dl_info {
// const char *dli_fname; /* Pathname of shared object */
// void *dli_fbase; /* Base address of shared object */
// const char *dli_sname; /* Name of nearest symbol */
// void *dli_saddr; /* Address of nearest symbol */
// } Dl_info;
//
// extern int dladdr(const void *, Dl_info *);
Dl_info info;
IMP imp = class_getMethodImplementation(objc_getClass("NSArray"), sel_registerName("description"));
printf("✳ ️ ✳ ️ ✳ ️ pointer p \ % n", imp);
if (dladdr((const void *)(imp), &info)) {
printf("✳ ️ ✳ ️ ✳ ️ dli_fname: % s \ n", info.dli_fname);
printf("✳ ️ ✳ ️ ✳ ️ dli_fbase: % p \ n", info.dli_fbase);
printf("✳ ️ ✳ ️ ✳ ️ dli_sname: % s \ n", info.dli_sname);
printf("✳ ️ ✳ ️ ✳ ️ dli_saddr: % p \ n", info.dli_saddr);
} else {
printf("error: can't find that symbol.\n");
}
return 0;
}
// ⬇️ The console prints the following information:✳ ️ ✳ ️ ✳ ️ pointer0x7fff203f44dd✳ ️ ✳ ️ ✳ ️ dli_fname: / System/Library/Frameworks/CoreFoundation framework Versions/A/CoreFoundation ✳ ️ ✳ ️ ✳ ️ dli_fbase:0x7fff20387000✳ ️ ✳ ️ ✳ ️ dli_sname: - [NSArray description] ✳ ️ ✳ ️ ✳ ️ dli_saddr:0x7fff203f44dd
Copy the code
As printed by the console, we just need to pass the IMP of the description function of the NSArray class as a parameter to the dlADDR function, it can obtain the name and address of the module where the IMP is located, the corresponding function. So we can tell if a function has been illegally modified in this way.
So let’s look at an example to verify that the function has been modified:
// 🤯 the header file to use
#include <objc/runtime.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#import <dlfcn.h>
static inline BOOL validate_methods(const char *cls,const char *fname) __attribute__ ((always_inline));
BOOL validate_methods(const char *cls, const char *fname) {
// Get the class object based on the class name
Class aClass = objc_getClass(cls);
// A list of methods used to record the aClass class
Method *methods;
// Use to record the number of methods listed
unsigned int nMethods;
// Get the specified
Dl_info info;
// IMP for method
IMP imp;
char buf[128];
Method m;
if(! aClass)return NO;
// 🤯 get all the methods of aClass
methods = class_copyMethodList(aClass, &nMethods);
// 🤯 loop through each method in the list of methods
while (nMethods--) {
m = methods[nMethods];
printf("✳ ️ ✳ ️ ✳ ️ validating [] % s % s \ n", (const char *)class_getName(aClass), (const char *)method_getName(m));
// 🤯 gets the IMP of the function
imp = method_getImplementation(m);
// imp = class_getMethodImplementation(aClass, sel_registerName("allObjects"));
if(! imp) {// IMP does not exist and return
printf("✳ ️ ✳ ️ ✳ ️ error: method_getImplementation (% s) failed \ n", (const char *)method_getName(m));
free(methods);
return NO;
}
// 🤯 imp do parameters, through dladdr function imp information
if (!dladdr((const void *)imp, &info)) {
// Get an error and return
printf("✳️✳️✳️ error: dladdr() failed for %s\n", (const char *)method_getName(m));
free(methods);
return NO;
}
// 🤯 Validate image path (the name of the module in which the function is located, if different, the goto statement fails and prints info)
if (strcmp(info.dli_fname, fname)) {
goto FAIL;
}
// 🤯 the function name obtained from the dladdr function is not NULL and is not equal to
. Otherwise, print "✳️✳️✳️
" and continue the loop
if(info.dli_sname ! =NULL && strcmp(info.dli_sname, "<redacted>") != 0) {
// 🤯 let's first look at the definition of the snprintf function, which is a C library function.
// 🤯 C library function int snprintf(char * STR, size_t size, const char *format,...)
// Set the variable parameter (...) Format to a string and copy the string to STR. Size is the maximum number of characters to be written.
// Return value:
// 1. If the formatted string length is less than or equal to size, the string is copied to STR with a string terminator \0 appended to it;
// 2. If the formatted string length is greater than size, the portion exceeding size is truncated, and only (size-1) characters are copied to STR, followed by a string terminator \0, which returns the length of the string to be written.
// 🤯 Validate class name in symbol
// Get the name of the aClass object and store it in buf in the format "[%s ".
snprintf(buf, sizeof(buf), "[%s ", (const char *)class_getName(aClass));
// String comparison here. Dli_saddr = info.dli_saddr; dli_saddr = info.dli_saddr;
if (strncmp(info.dli_sname + 1, buf, strlen(buf))) {
// Get the name of the aClass object and press "[%s "to store it in buf (buf is a char array of 128 length declared earlier)
// This is the same as above, but the format has changed
snprintf(buf, sizeof(buf), "[%s(", (const char *)class_getName(aClass));
// String comparison. Info.dli_sname + 1 is info.dli_saddr.
if (strncmp(info.dli_sname + 1, buf, strlen(buf))) {
gotoFAIL; }}🤯 Validate selector in symbol
// Get the name of the m method and press "%s]" to save it in buf (buf is a char array of length 128)
snprintf(buf, sizeof(buf), " %s]", (const char *)method_getName(m));
if (strncmp(info.dli_sname + (strlen(info.dli_sname) - strlen(buf)), buf, strlen(buf))) {
gotoFAIL; }}else {
printf("✳ ️ ✳ ️ ✳ ️ < redacted > \ n"); }}return YES;
FAIL:
printf("🥶🥶🥶 method %s failed integrity test: \n", (const char *)method_getName(m));
printf("🥶 🥶 🥶 dli_fname: % s \ n", info.dli_fname);
printf("🥶 🥶 🥶 dli_sname: % s \ n", info.dli_sname);
printf("🥶 🥶 🥶 dli_fbase: % p \ n", info.dli_fbase);
printf("🥶 🥶 🥶 dli_saddr: % p \ n", info.dli_saddr);
free(methods);
return NO;
}
Copy the code
We can then call the validate_methods function from the module path of the NSArray obtained in the previous example: validate_methods(“NSArray”, “/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation”); Because the printed content is too long, I won’t copy and paste here, interested friends can paste the code to try it out.
Here is iOS security – verify the function address, check if it has been replaced, reverse injection is the original text, look at the original should be more clear!
Refer to the link
Reference link :🔗
-
IOS DyLD loading process
-
The association between DYLD and ObjC
-
OC basic principle – class loading process – on (_objc_init implementation principle)
-
Principles and implementation of TLS(Thread Local Storage)
-
Imp_implementationWithBlock (
-
The underlying principle of iOS – dyLD and OBJC association
-
Dyld – 832.7.3
-
OC Basic principles -App startup process (DYLD loading process)
-
What is dyLD cache in iOS?
-
IOS advanced basic principles – application loading (dyLD loading process, class and classification loading)
-
What does the iOS application do before entering the main function?
-
Dyld load application startup details
-
Dynamic and static libraries in iOS
-
Link path problems in Xcode
-
IOS uses the Framework for dynamic updates
-
Namespace, and problem resolution for repeated definitions
-
C++ namespace namespace
-
Learn about Xcode’s process for generating “static libraries” and “dynamic libraries”
-
Hook static initializers
-
The reason I didn’t go into alloc, I went into objc_alloc, and what I looked up was that at compile time if the symbol binding failed it would trigger a fix like this, call fixupMessageRef, If (MSG ->sel == SEL_alloc), MSG ->imp = (imp)&objc_alloc
-
IOS startup optimization + monitoring practices
-
In-depth understanding of MachO data parsing rules
-
Explore the Mach-O file
-
An in-depth analysis of Macho (1)
-
The actual reference links are listed below at 🔗 and above are references to other previous articles at 🔗 :
-
IOS security: Modified Mach-o
-
6.33 Variable Attributes of Functions
-
IOS Development Runtime (12) : Go deep into Mach-O
-
IOS Runtime (16) : Set/get section data details
-
No NSOBJECT Objective-C CLASS
-
IOS security – Verify function address, detect if it has been replaced, reverse injection
-
AloneMonkey