普通视图
iOS小技能:给debugserver添加task_for_pid权限,以便调试从AppStore中获取的App。
iOS小技能:__attribute__的应用
持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第26天,点击查看活动详情
引言
LLVM和其他 GCC 特性一样,Clang 支持了 attribute, 还加入了一小部分扩展特性。
__attribute__ 语法格式为:__attribute__ ((attribute-list))
constructor(priority), destructor(priority) 分别可以在main() 先后执⾏,可⽤于全局资源初始化和回收。
destructor让系统在main()函数退出或者调用了exit()之后,调用我们的函数。
Function-Attributes: https://gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html
![]()
I __attribute__的应用案例
1.1 代码注入
- ARM (通过汇编调用svc实现用户态到内核态的转换)
// 使用inline方式将函数在调用处强制展开,防止被hook和追踪符号
static __attribute__((always_inline)) void anti_debug()
#ifdef __arm__
asm volatile(
"mov r0,#31\n"
"mov r1,#0\n"
"mov r2,#0\n"
"mov r12,#26\n"
"svc #80\n"
);
#endif
#ifdef __arm64__
asm volatile(
"mov x0,#26\n"
"mov x1,#31\n"
"mov x2,#0\n"
"mov x3,#0\n"
"mov x16,#0\n"
"svc #128\n"
);
#endif
}
- 代码注入: facebook/fishhook符号表替换
/*
* A structure representing a particular intended rebinding from a symbol
* name to its replacement
*/
struct rebinding {//rebinding结构体
const char *name; //符号名称,C字符串,用来表明我们要hook哪个函数。
void *replacement; //新函数的地址
void **replaced; //原始函数地址的指针!
};
//重新绑定符号
FISHHOOK_VISIBILITY
int rebind_symbols(struct rebinding rebindings[], size_t rebindings_nel);
//rebindings[] 是一个 rebinding类型数组,用来存储需要hook的函数
//rebindings_nel 表示数组的长度
/*
* 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);
//指定镜像的header, slide 表示偏移量
hook ptrace函数,进行反反调试。
PT_DENY_ATTACH is an Apple-specific constant that can prevent debuggers (gdb, DTrace, etc.) from debugging your binary in kernel-level.
ptrace(PT_DENY_ATTACH, 0, 0, 0);
#if !defined(PT_DENY_ATTACH)
#define PT_DENY_ATTACH 31
#endif
Rebinding customRebind = {"ptrace", my_ptrace, (void*)&orig_ptrace};
//第一个参数为需要替换的符号
//第二个参数为自己实现的函数名称
//第三个参数为原函数地址,因为fishhook是基于地址进行替换的+ `__attribute__((constructor))`实现注入
rebind_symbols((struct rebinding[1]){customRebind},1);
int my_ptrace(int _request, pid_t _pid, caddr_t _addr, int _data){
if(_request != PT_DENY_ATTACH){
return orig_ptrace(_request,_pid,_addr,_data);
}
return 0;
}
- 自定义打印方法:用真正的方法替换去拦截 NSLog 的功能(
iOS 11 之后这种方法失效了),使用__attribute__((constructor));进行实现,extern进行申明公共方法。
#ifdef DEBUG
// iOS 11 之前用真正的方法替换去实现拦截 NSLog 的功能,iOS 11 之后这种方法失效了,所以只能用宏定义的方式覆盖 NSLog。这也就意味着在 iOS 11 下一些如果某些代码编译时机比 QMUI 早,则这些代码里的 NSLog 是无法被替换为 KNLog 的
extern void _NSSetLogCStringFunction(void (*)(const char *string, unsigned length, BOOL withSyslogBanner));
static void PrintNSLogMessage(const char *string, unsigned length, BOOL withSyslogBanner) {
QMUILog(@"NSLog", @"%s", string);
}
static void HackNSLog(void) __attribute__((constructor));
static void HackNSLog(void) {
_NSSetLogCStringFunction(PrintNSLogMessage);
}
#define NSLog(...) KNLog(@"NSLog", __VA_ARGS__)// iOS 11 以后真正生效的是这一句
#endif
1.2 对格式化字符串进行类型检查
extern int
my_printf (void *my_object, const char *my_format, ...)
__attribute__((format(printf, 2, 3)));
//format 属性用于指定一个函数接收类似 printf, scanf, strftime 和 strfmon 风格的参数,应该按照参数对格式化字符串进行类型检查。
1.3 控制符号的可见性
#define STD_EXPORTS __attribute__ ((visibility("default")))
The -fvisibility=vis compiler option lets you set the visibility for symbols in the current compilation. When set to hidden, symbols not explicitly marked as visible are hidden.
__attribute__((visibility("default"))) void MyFunction1() {}
__attribute__((visibility("hidden"))) void MyFunction2() {}
1.4 表明一些函数参数应该是非空的指针
extern void *
my_memcpy (void *dest, const void *src, size_t len)
__attribute__((nonnull (1, 2)));
1.5 确保线程在应用整个生命周期内都能一直运行
AFNetworking 在网络请求线程的入口使用 noreturn 属性,用于网络请求的 NSThread。
+ (void) __attribute__((noreturn)) networkRequestThreadEntryPoint:(id)__unused object {//确保这个线程在应用整个生命周期内都能一直运行
do {
@autoreleasepool {
[[NSRunLoop currentRunLoop] run];
}
} while (YES);
}
+ (NSThread *)networkRequestThread {//专门用于网络请求的 NSThread
static NSThread *_networkRequestThread = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil];
[_networkRequestThread start];
});
return _networkRequestThread;
}
1.6 检查能否使用特定的属性
可以用 __has_attribute 这个指令
#ifndef AX_REQUIRES_SUPER
#if __has_attribute(objc_requires_super)
#define AX_REQUIRES_SUPER __attribute__((objc_requires_super))
#else
#define AX_REQUIRES_SUPER
__attribute((objc_requires_super)) was first introduced as work in progress into CLANG in September 2012 and was documented in October 2013. On both OS X and iOS there is now a NS_REQUIRES_SUPER macro that conditionally wraps the objc_requires_super attribute depending on compiler support. Once a method declaration is appended with this macro, the compiler will produce a warning if super is not called by a subclass overriding the method.
II 导出和隐藏符号
2.1 导出符号信息
- 查看导出符号信息:
nm -gm tmp_64.dylib
(__DATA,__data) external (undefined) external _CFDataCreate (from CoreFoundation) (undefined) external _CFNotificationCenterGetDarwinNotifyCenter (from CoreFoundation) (__TEXT,__text) external (undefined) external _IOObjectRelease (from IOKit) (undefined) external _IORegistryEntryCreateCFProperty (from IOKit) 000000010ffa3f97 (__DATA,__objc_data) external OBJC_CLASS_BslyjNwZmPCJkVst 000000010ffa3f97 (__DATA,__objc_data) external _OBJC_CLASS__ChiDDQmRSQpwQJgm
2.2 __attribute__控制符号是否导出
The
-fvisibility=vis compiler optionlets you set the visibility for symbols in the current compilation. When set to hidden, symbols not explicitly marked as visible are hidden.
#define EXPORT __attribute__((visibility("default")))
隐藏未明确标记为可见的符号:
-
在编译参数中加入
-exported_symbols_list export_list -
在编译参数中指定-fvisibility=hidden,对指定符号增加visibility(“default”)来导出符号
__attribute__((visibility("default"))) void MyFunction1() {}
__attribute__((visibility("hidden"))) void MyFunction2() {}
static 参数修饰,不会导出符号信息
static char _person_name[30] = {'\0'};
2.3 Pragmas控制符号是否导出
void f() { }
#pragma GCC visibility push(default)
void g() { }
void h() { }
#pragma GCC visibility pop
III ptrace系统调用
为了方便应用软件的开发和调试,unix的早期版本提供了一种对运行中的进程进行跟踪和控制手段:系统调用ptrace;通过ptrace,可以对另一个进程实现调试跟踪,同时ptrace提供了一个PT_DENY_ATTACH = 31参数用于告诉系统阻止调试器的依附。
//ptrace系统调用 用于实现断点调试和对进程进行跟踪和控制
#include <sys/ptrace.h>
long ptrace(enum __ptrace_request request, pid_t pid, void *addr, void *data);
//enum __ptrace_request request:指示了ptrace要执行的命令。
//pid_t pid: 指示ptrace要跟踪的进程。
//void *addr: 指示要监控的内存地址。
//void *data: 存放读取出的或者要写入的数据。
//PT_DENY_ATTACH is an Apple-specific constant that can prevent debuggers (gdb, DTrace, etc.) from debugging your binary in kernel-level.
//ptrace(PT_DENY_ATTACH, 0, 0, 0);
gdb利用ptrace系统调用,在被调试程序和gdb之间建立跟踪关系。然后所有发送给被调试程序的信号(除SIGKILL)都会被gdb截获,gdb根据截获的信号,查看被调试程序相应的内存地址,并控制被调试的程序继续运行。
3.1 syscall
syscall是通过软中断来实现从用户态到内核态,syscall (26,31,0,0)来调用系统函数ptrace(PT_DENY_ATTACH, 0, 0, 0);。
ptrace的系统调用函数号是26,31是PT_DENY_ATTACH(用于告诉系统阻止调试器的依附)。
int syscall(int, ...);#defineSYS_ptrace 26
3.2 反调试
-
运行时期,断点ptrace,直接返回
-
分析如何调用的ptrace,hook ptrace
-
通过tweak,替换disable_gdb函数
-
修改 PT_DENY_ATTACH:在二进制文件中 ,修改 PT_DENY_ATTACH的31,改成 任意一个值,如PT_ATTACH 0。 blog.csdn.net/z929118967/… AlipayWalletTweakF.xm
-
ARM (通过汇编调用svc实现用户态到内核态的转换)
// 使用inline方式将函数在调用处强制展开,防止被hook和追踪符号
static __attribute__((always_inline)) void anti_debug()
#ifdef __arm__
asm volatile(
"mov r0,#31\n"
"mov r1,#0\n"
"mov r2,#0\n"
"mov r12,#26\n"
"svc #80\n"
);
#endif
#ifdef __arm64__
asm volatile(
"mov x0,#26\n"
"mov x1,#31\n"
"mov x2,#0\n"
"mov x3,#0\n"
"mov x16,#0\n"
"svc #128\n"
);
#endif
}
see also
小程序:iOS逆向
iOS小技能:消息发送的步骤(利用类型编码加快消息分发)
持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第23天,点击查看活动详情
前言
运行时API的应用:
- 路由的实现(接口控制app跳任意界面)
- 获取修改对象的成员属性
- 动态添加/交换方法的实现: blog.csdn.net/z929118967/…
- 属性关联: blog.csdn.net/z929118967/…
- iOS 间接实现多继承的方式:消息转发、类别、delegate和protocol(委托协助主体完成操作任务,将需要定制化的操作预留给委托对象来自定义实现 与block类似) 。
I runtime的作用
因为objective-c是一门动态语言,也就是说只有编译器是不够的,还需要一个运行时系统(runtime system)来执行编译后的代码。
runtime简称运行时,其中最主要的就是消息机制。
对于编译期语言,会在编译的时候决定调用哪个函数。对于OC的函数,是动态调用的,在编译的时候并不能决定真正调用哪个函数,只有在运行时才会根据函数的名称找到对应的函数来调用。
1.1 消息发送的步骤
messages aren’t bound to method implementations until Runtime。(消息直到运行时才会与方法实现进行绑定)
objc_msgSend 是当方法的实现被调用后才会返回数据。
![]()
- 当向someObject发送消息时,先在本类中的方法缓存列表中进行查找
- 如果找不到就在本类中的法列表中进行查找
- 如果没找到,就去父类中进行查找。
- 如果没找到,runtime system并不会立即报错使程序崩溃,而是依次执行消息转发。
![]()
检查是否有动态添加对应的方法->检查是否有其他对象实现了对应的方法(快速转发给其他对象处理)->(标准消息转发)
- 动态方法解析 :
向当前类发送 resolveInstanceMethod: 信号,检查是否动态向该类添加了方法。 - 快速消息转发: 获取转发对象,
检查该类是否实现了 forwardingTargetForSelector: 方法,若实现了则调用这个方法。若该方法返回值对象非nil或非self,那么就进行消息的常规转发。 - 标准消息转发:获取方法签名,
runtime发送methodSignatureForSelector:消息获取Selector对应的方法签名。返回值非空则通过forwardInvocation:转发消息,返回值为空则向当前对象发送doesNotRecognizeSelector:消息,程序崩溃退出。
1.2 Runtime API
- 通过对 Runtime 库函数的直接调用
runtime源码:github.com/opensource-…
其中主要使用的函数定义在message.h和runtime.h这两个文件中。
- 通过 Foundation 框架的 NSObject 类定义的方法
Cocoa 程序中绝大部分类都是 NSObject 类的子类,所以都继承了 NSObject 的行为。(NSProxy 类是个例外,它是个抽象超类)
- class方法返回对象的类;
- isKindOfClass: 和 -isMemberOfClass: 方法检查对象是否存在于指定的类的继承体系中(是否是其子类或者父类或者当前类的成员变量);
- respondsToSelector: 检查对象能否响应指定的消息;
-
conformsToProtocol:检查对象是否实现了指定协议类的方法; -
methodForSelector:返回指定方法实现的地址。
1.3 理解instance、class object、metaclass的关系
![]()
一个实例对象struct objc_object的isa指针指向它的struct objc_class类对象,类对象的isa指针指向它的元类;super_class指针指向了父类的类对象,而元类的super_class指针指向了父类的元类。
II 消息转发
2.1 消息处理(动态地添加一个方法实现)
当在相应的类以及父类中找不到类方法实现时会执行+resolveInstanceMethod:这。该方法如果在类中不被重写的话,默认返回NO。如果返回NO就表明不做任何处理,走下一步。
如果返回YES的话,就说明在该方法中对这个找不到实现的方法进行了处理。
在+resolveInstanceMethod:方法中,我们可以为找不到实现的SEL动态地添加一个方法实现,添加完毕后,就会执行我们添加的方法实现。这样,当一个类调用不存在的方法时,就不会崩溃了。具体做法如下所示:
//运行时方法拦截
- (void)dynamicAddMethod: (NSString *) value {
NSLog(@"OC替换的方法:%@", value);
}
/**
没有找到SEL的IMP实现时会执行下方的方法
@param sel 当前对象调用并且找不到IMP的SEL
@return 找到其他的执行方法,并返回yes
*/
+ (BOOL)resolveInstanceMethod:(SEL)sel {
return NO; //当返回NO时,会接着执行forwordingTargetForSelector:方法,
[KNRuntimeKit addMethod:[self class] method:sel method:@selector(dynamicAddMethod:)];//动态地添加一个方法实现
return YES;
}
2.2 消息快速转发
如果不对上述消息进行处理的话,也就是+resolveInstanceMethod:返回NO时,会走下一步消息转发,即-forwardingTargetForSelector:。
该方法会返回一个类的对象,这个类的对象有SEL对应的实现。当调用这个找不到的方法时,就会被转发到SecondClass中去进行处理。
/**
将当前对象不存在的SEL传给其他存在该SEL的对象
@param aSelector 当前类中不存在的SEL
@return 存在该SEL的对象
*/
- (id)forwardingTargetForSelector:(SEL)aSelector {
return self;//当该方法返回self或者nil, 说明不对相应的方法进行转发,那么就进行消息的常规转发。
return [SecondClass new]; //让SecondClass中相应的SEL去执行该方法
}
例子: 教师执行手术
//Returns the object to which unrecognized messages should first be directed.
- (id)forwardingTargetForSelector:(SEL)aSelector
{
Doctor *doctor = [[Doctor alloc]init];
if ([doctor respondsToSelector:aSelector]) {
return doctor;
}
return nil;
}
这个方式的好处是隐藏了我要转发的消息,没有类目那么清晰。
2.3 消息常规转发
如果不将消息转发给其他类的对象,那么就只能自己进行处理了。
如果上述方法返回nil或者self的话,会执行-methodSignatureForSelector:方法来获取方法的参数以及返回数据类型(方法签名)。
返回值非空则通过forwardInvocation:转发消息,返回值为空则向当前对象发送doesNotRecognizeSelector:消息,程序崩溃,报出找不到相应的方法实现的崩溃信息。
//在+resolveInstanceMethod:返回NO时就会执行下方的方法,下方也是将该方法转发给SecondClass。
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
//查找父类的方法签名
NSMethodSignature *signature = [super methodSignatureForSelector:selector];
if(signature == nil) {
// signature = [NSMethodSignature signatureWithObjCTypes:"@@:"];
signature = [someObj methodSignatureForSelector:aSelector]; //返回指定方法签名
}
return signature;
}
- (void)forwardInvocation:(NSInvocation *)invocation {
SecondClass * forwardClass = [SecondClass new];
SEL sel = invocation.selector;
if ([forwardClass respondsToSelector:sel]) {
[invocation invokeWithTarget:forwardClass];
} else {
[self doesNotRecognizeSelector:sel];
}
}
2.4 两种消息转发方式的比较
- 快速消息转发:简单、快速、但仅能转发给一个对象。
- 标准消息转发:稍复杂、较慢、但转发操作实现可控,可以实现多对象转发
III 编译器指令 @encode()
苹果的 Objective-C 运行时库内部利用类型编码帮助加快消息分发。
NSValue 的 +valueWithBytes:objCType:第二个参数需要用@encode()指令来创建一种内部表示的字符串 。
@encode(int) → i
+ (NSValue *)valueWithBytes:(const void *)value objCType:(const char *)type;
+ (NSValue *)value:(const void *)value withObjCType:(const char *)type;
3.1 Objective-C type encodings
| Code | Meaning |
|---|---|
| c | A char |
| i | An int |
| s | A short |
| l | A long ,l is treated as a 32-bit quantity on 64-bit programs. |
| q | A long long |
| C | An unsigned char |
| I | An unsigned int |
| S | An unsigned short |
| L | An unsigned long |
| Q | An unsigned long long |
| f | A float |
| d | A double |
| B | A C++ bool or a C99 _Bool |
| v | A void |
| * | A character string (char *) |
| @ | An object (whether statically typed or typed id) |
| # | A class object (Class) |
| : | A method selector (SEL) |
| [array type] | An array |
| {name=type...} | A structure |
| (name=type...) | A union |
| bnum | A bit field of num bits |
| ^type | A pointer to type |
| ? | An unknown type (among other things, this code is used for function pointers) |
- (void)testtypeEncodings{
NSLog(@"int : %s", @encode(int));
NSLog(@"float : %s", @encode(float));
NSLog(@"float * : %s", @encode(float*));//指针的标准编码是加一个前置的 ^
NSLog(@"char : %s", @encode(char));
NSLog(@"char * : %s", @encode(char *));//char * 拥有自己的编码 *,因为 C 的字符串被认为是一个实体,而不是指针。
NSLog(@"BOOL : %s", @encode(BOOL));//BOOL 是 c,而不是i。原因是 char 比 int 小。BOOL 更确切地说是 signed char 。
NSLog(@"void : %s", @encode(void));
NSLog(@"void * : %s", @encode(void *));
NSLog(@"NSObject * : %s", @encode(NSObject *));
NSLog(@"NSObject : %s", @encode(NSObject));
NSLog(@"[NSObject] : %s", @encode(typeof([NSObject class])));
NSLog(@"NSError ** : %s", @encode(typeof(NSError **)));
int intArray[5] = {1, 2, 3, 4, 5};
NSLog(@"int[] : %s", @encode(typeof(intArray)));
float floatArray[3] = {0.1f, 0.2f, 0.3f};
NSLog(@"float[] : %s", @encode(typeof(floatArray)));
typedef struct _struct {
short a;
long long b;
unsigned long long c;
} Struct;
NSLog(@"struct : %s", @encode(typeof(Struct)));
}
结果:
2022-06-14 11:18:06.069425+0800 SDKSample[9300:3648699] int : i
2022-06-14 11:18:06.069517+0800 SDKSample[9300:3648699] float : f
2022-06-14 11:18:06.069559+0800 SDKSample[9300:3648699] float * : ^f
2022-06-14 11:18:06.069597+0800 SDKSample[9300:3648699] char : c
2022-06-14 11:18:06.069634+0800 SDKSample[9300:3648699] char * : *
2022-06-14 11:18:06.069669+0800 SDKSample[9300:3648699] BOOL : B
2022-06-14 11:18:06.069706+0800 SDKSample[9300:3648699] void : v
2022-06-14 11:18:06.070191+0800 SDKSample[9300:3648699] void * : ^v
2022-06-14 11:18:06.070230+0800 SDKSample[9300:3648699] NSObject * : @
2022-06-14 11:18:06.070267+0800 SDKSample[9300:3648699] NSObject : {NSObject=#}
2022-06-14 11:18:06.070303+0800 SDKSample[9300:3648699] [NSObject] : #
2022-06-14 11:18:06.070339+0800 SDKSample[9300:3648699] NSError ** : ^@
2022-06-14 11:18:06.070374+0800 SDKSample[9300:3648699] int[] : [5i]
2022-06-14 11:18:06.070870+0800 SDKSample[9300:3648699] float[] : [3f]
2022-06-14 11:18:06.072298+0800 SDKSample[9300:3648699] struct : {_struct=sqQ}
3.2 method encodings
| Code | Meaning |
|---|---|
| r | const |
| n | in |
| N | inout |
| o | out |
| O | bycopy |
| R | byref |
| V | oneway |
带inout 的参数表明它在发消息时对象即可传入又可传出,将参数特别标注为 in 或 out,程序将避免一些来回的开销。
增加一个 bycopy 修饰符以保证发送了一份完整的拷贝
IV 运行时API应用
运行时API的应用:
-
路由的实现(接口控制app跳任意界面) :kunnan.blog.csdn.net/article/det…
-
获取修改对象的成员属性
-
动态添加/交换方法的实现: blog.csdn.net/z929118967/…
-
iOS 间接实现多继承的方式:消息转发、类别、delegate和protocol(委托协助主体完成操作任务,将需要定制化的操作预留给委托对象来自定义实现 与block类似) 。
-
NSClassFromString、NSSelectorFromString 使用例子
id activityIndicator = [NSClassFromString(@"SDNetworkActivityIndicator") performSelector:NSSelectorFromString(@"sharedActivityIndicator")];
#import "KNRuntimeKit.h"
@implementation KNRuntimeKit
/**
获取类名
@param class 相应类
@return NSString:类名
*/
+ (NSString *)fetchClassName:(Class)class {
const char *className = class_getName(class);
return [NSString stringWithUTF8String:className];
}
/**
获取成员变量
@param class Class
@return NSArray
*/
+ (NSArray *)fetchIvarList:(Class)class {
unsigned int count = 0;
Ivar *ivarList = class_copyIvarList(class, &count);
NSMutableArray *mutableList = [NSMutableArray arrayWithCapacity:count];
for (unsigned int i = 0; i < count; i++ ) {
NSMutableDictionary *dic = [NSMutableDictionary dictionaryWithCapacity:2];
const char *ivarName = ivar_getName(ivarList[i]);
const char *ivarType = ivar_getTypeEncoding(ivarList[i]);
dic[@"type"] = [NSString stringWithUTF8String: ivarType];
dic[@"ivarName"] = [NSString stringWithUTF8String: ivarName];
[mutableList addObject:dic];
}
free(ivarList);
return [NSArray arrayWithArray:mutableList];
}
/**
获取类的属性列表, 包括私有和公有属性,以及定义在延展中的属性
@param class Class
@return 属性列表数组
*/
+ (NSArray *)fetchPropertyList:(Class)class {
unsigned int count = 0;
objc_property_t *propertyList = class_copyPropertyList(class, &count);
NSMutableArray *mutableList = [NSMutableArray arrayWithCapacity:count];
for (unsigned int i = 0; i < count; i++ ) {
const char *propertyName = property_getName(propertyList[i]);
[mutableList addObject:[NSString stringWithUTF8String: propertyName]];
}
free(propertyList);
return [NSArray arrayWithArray:mutableList];
}
/**
获取类的实例方法列表:getter, setter, 对象方法等。但不能获取类方法
@param class <#class description#>
@return <#return value description#>
*/
+ (NSArray *)fetchMethodList:(Class)class {
unsigned int count = 0;
Method *methodList = class_copyMethodList(class, &count);
NSMutableArray *mutableList = [NSMutableArray arrayWithCapacity:count];
for (unsigned int i = 0; i < count; i++ ) {
Method method = methodList[i];
SEL methodName = method_getName(method);
[mutableList addObject:NSStringFromSelector(methodName)];
}
free(methodList);
return [NSArray arrayWithArray:mutableList];
}
/**
获取协议列表
@param class <#class description#>
@return <#return value description#>
*/
+ (NSArray *)fetchProtocolList:(Class)class {
unsigned int count = 0;
__unsafe_unretained Protocol **protocolList = class_copyProtocolList(class, &count);
NSMutableArray *mutableList = [NSMutableArray arrayWithCapacity:count];
for (unsigned int i = 0; i < count; i++ ) {
Protocol *protocol = protocolList[i];
const char *protocolName = protocol_getName(protocol);
[mutableList addObject:[NSString stringWithUTF8String: protocolName]];
}
return [NSArray arrayWithArray:mutableList];
return nil;
}
/**
往类上添加新的方法与其实现
@param class 相应的类
@param methodSel 方法的名
@param methodSelImpl 对应方法实现的方法名
*/
+ (void)addMethod:(Class)class method:(SEL)methodSel method:(SEL)methodSelImpl {
Method method = class_getInstanceMethod(class, methodSelImpl);
IMP methodIMP = method_getImplementation(method);
const char *types = method_getTypeEncoding(method);
class_addMethod(class, methodSel, methodIMP, types);
}
/**
方法交换
@param class 交换方法所在的类
@param method1 方法1
@param method2 方法2
*/
+ (void)methodSwap:(Class)class firstMethod:(SEL)method1 secondMethod:(SEL)method2 {
Method firstMethod = class_getInstanceMethod(class, method1);
Method secondMethod = class_getInstanceMethod(class, method2);
method_exchangeImplementations(firstMethod, secondMethod);
}
@end
see also
小程序:iOS逆向
iOS小技能:动态地给类添加新的方法、实例变量、属性。
持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第16天,点击查看活动详情
前言
添加新的实例变量的原理:利用category结合runtime的API实现
![]()
动态创建属性的应用场景:利用属性进行传值的时候,我们就可以利用本文的方法进行动态创建属性。尤其在逆向其他app的时候,往已经存在class新增一个属性,用于数据传递,尤其是异步操作的时候。
I 添加新的实例变量
1.1 原理
利用 runtime APIobjc_setAssociatedObject和objc_getAssociatedObject
objc_setAssociatedObject
/**
* Sets an associated value for a given object using a given key and association policy.
*
* @param object The source object for the association.
* @param key The key for the association.
* @param value The value to associate with the key key for object. Pass nil to clear an existing association.
* @param policy The policy for the association. For possible values, see “Associative Object Behaviors.”
*
* @see objc_setAssociatedObject
* @see objc_removeAssociatedObjects
*/
OBJC_EXPORT void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0);
/**
* Returns the value associated with a given object for a given key.
*
* @param object The source object for the association.
* @param key The key for the association.
*
* @return The value associated with the key \e key for \e object.
*
* @see objc_setAssociatedObject
*/
OBJC_EXPORT id objc_getAssociatedObject(id object, const void *key)
OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0);
1.2 例子
类别(Category)通过增加新的类和实例方法来扩展现有类的行为。作为惯例,类别被定义在它们自己的.{h,m}文件里。
//
// Teacher+Profession.m
//
#import "Teacher+Profession.h"
#import <objc/runtime.h>
const char *ProfessionType = "NSString *"; //就是属性的key
@implementation Teacher (Profession)
-(void)setProf:(NSString*)prof
{
objc_setAssociatedObject(self, ProfessionType, prof, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
-(NSString *)prof
{
NSString *pro = objc_getAssociatedObject(self, ProfessionType);
return pro;
}
@end
II 动态创建属性
使用分类、@dynamic、objc_setAssociatedObject、objc_getAssociatedObject 实现。
2.1 应用场景
利用属性进行传值的时候,我们就可以利用本文的方法进行动态创建属性。尤其在逆向其他app的时候,往已经存在class新增一个属性,用于数据传递,尤其是异步操作的时候。
//结合@dynamic的 associatedObject例子
@implementation NSObject (AssociatedObject)
@dynamic associatedObject;
- (void)setAssociatedObject:(id)object {
objc_setAssociatedObject(self,
@selector(associatedObject), object,
OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (id)associatedObject {
return objc_getAssociatedObject(self,
@selector(associatedObject));
}
2.2 例子:为VC新增一个属性
WCNewCommitViewController+KNWCNewCommitViewControllerAssociatedObject.h
#import "WCNewCommitViewController.h"
@interface NSObject (KNWCNewCommitViewControllerAssociatedObject)
// isa (Class): NSKVONotifying_WCNewCommitViewController (isa, 0x5a10db2abf7)
@property (nonatomic, strong) id associatedObject;
@end
WCNewCommitViewController+KNWCNewCommitViewControllerAssociatedObject.m
#import "WCNewCommitViewController+KNWCNewCommitViewControllerAssociatedObject.h"
@implementation NSObject (KNWCNewCommitViewControllerAssociatedObject)
@dynamic associatedObject;
- (void)setAssociatedObject:(id)object {
objc_setAssociatedObject(self,
@selector(associatedObject), object,
OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (id)associatedObject {
return objc_getAssociatedObject(self,
@selector(associatedObject));
}
@end
2.3 效果
- usage:
#import "WCNewCommitViewController+KNWCNewCommitViewControllerAssociatedObject.h"
[WCNewCommit setAssociatedObject:@"sssss"];
- ret
NSLog(@"associatedObject:%@",[self valueForKey:@"associatedObject"]);//2018-09-06 12:06:06.977711 WeChat[717:226743] associatedObject:sssss
See Also
- iOS运行时的应用:
1、实现路由(接口控制app跳任意界面 )
2、获取修改对象的成员属性
3、动态添加/交换方法的实现
4、属性关联
iOS动态库的注入原理
「这是我参与2022首次更文挑战的第20天,活动详情查看:2022首次更文挑战」。
前言
动态库的注入原理:
- 一个是基于修改Mach-O 的Load Commands,即通过修改可执行文件的Load Commands来实现的. 在Load Commands中增加一个LC_LOAD_DYLIB , 写入dylib路径。
Usage: insert_dylib dylib_path binary_path [new_binary_path] - 一个是利用环境变量DYLD_INSERT_LIBRARIES,例如使用它进行dumpdecrypted(补充:Clutch 通过
posix_spawnp生成一个新的进程,然后暂停进程并dump内存) - 另一个是在挂载的进程上创建一个挂起的线程, 然后在这个线程里申请一片用于加载动态库的内存,然后恢复线程,动态库就被注入(通过 taskfor_pid函数获取目标进程句柄,然后通过在进程内创建新线程并执行自己的代码。) cycript 就是以这种方式执行脚本代码。
I、静态库和动态库的区别
![]()
1.1 动态库的特点
- 存在形式有 .dylib,.framework 和链接符号 .tdb;
- 它的好处是可以只保留一份文件和内存空间,从而能够被多个进程使用,例如系统动态库;
- 可减小可执行文件的体积,不需要链接到目标文件。
1.2 静态库的特点
- 以.a 或者.framework形式存在的一种共享程序代码的方式,从本质上来讲就是一种可执行文件的二进制形式;常常会将程序的部分功能编译成库,暴露出头文件的形式供开发者调用
- 静态库以一个或者多个object文件组成;可以将一个静态库拆解成多个object文件(ar -x)
- 静态库链接的时会直接链接到目标文件,并作为它的一部分存在。
II、动态库的编译和注入
2.1 编译
xcrun --sdk iphoneos clang++ dynamiclib -arch arm64 -framework Foundation Person.mm -o target.dylib -fvisibility=hidden
- Makefile
CC = xcrun --sdk iphoneos clang++
ARCH = arm64
FRAMEWORK = -framework Foundation
VERSION = -compatibility_version 1 -current_version 1
VISIBLE = -fvisibility=hidden
TARGET = target.dylib
SOURCE = Person.m
$(TARGET):$(SOURCE)
$(CC) -dynamiclib -arch $(ARCH) $(FRAMEWORK) $(SOURCE) -o $(TARGET) $(VERSION)
.PHONY:clean
clean:
rm $(TARGET)
2.2 动态库的注入方式
2.2.1 cycript注入动态库的方式
在挂载的进程上创建一个挂起的线程, 然后在这个线程里申请一片用于加载动态库的内存,然后恢复线程,动态库就被注入(通过 taskfor_pid函数获取目标进程句柄,然后通过在进程内创建新线程并执行自己的代码。)
2.2.2 通过环境变量DYLD_INSERT_LIBRARIES 注入
DYLD_INSERT_LIBRARIES=/PathFrom/dumpdecrypted.dylib /PathTo
#New Run Script Phase:
cd ${TARGET_BUILD_DIR}
export DYLD_INSERT_LIBRARIES=./libKNoke.dylib && /Applications/QKNQ.app/Contents/MacOS/QKNQ
2.2.3 通过增加load command 的LC_LOAD_DYLIB或者LC_LOAD_WEAK_DYLIB,指定动态库的路径来实现注入
修改App可执行文件的头部,给它添加这么一个load command,并指定load我们构造的dylib就好
- 二次打包动态库的注入:
避免每次从环境变量注入–偏静态:通过LC_LOAD_DYLIB实现dylib的加载
通过修改可执行文件的Load Commands来实现的. 在Load Commands中增加一个LC_LOAD_DYLIB , 写入dylib路径 Usage: insert_dylib dylib_path binary_path [new_binary_path]
1、现在iOS上的绝大多数以root权限运行的App,都是通过setuid + bash来实现的
2、App运行所需要的信息,一般都存放在其MachO头部43中,其中dylib的信息是由load commands指定的.
这些信息是以静态的方式存放在二进制文件里(不是由DYLD_INSERT_LIBRARIES动态指定),而又是由dyld动态加载的,所以我们给它起了个“偏静态”的名字--在此App得到执行时,dyld会查看其MachO头部中的load commands,并把里面LC_LOAD_DYLIB相关的dylib给加载到进程的内存空间
- 如果需要修改LC_ID_DYLIDB、、LC_LOAD_DYLIB,可以使用install_name_tool
install_name_toll -id xxx imputfile
install_name_toll -change old new imputfile
- 通过cydia substrate提高的注入:
配置plist文件,并将对应的plist、dylib文件放入指定目录 /Layout/Library/MobileSubstrate/DynamicLibraries/、/usr/lib/TweakInject其实也是通过DYLD_INSERT_LIBRARIES将自己注入,然后遍历DynamicLibraries目录下的plist文件,再将符合规则的动态库通过dlopen打开
III、导出和隐藏符号
3.1 导出符号
- 查看导出符号信息
nm -gm tmp_64.dylib
(__DATA,__data) external
(undefined) external _CFDataCreate (from CoreFoundation)
(undefined) external _CFNotificationCenterGetDarwinNotifyCenter (from CoreFoundation)
(__TEXT,__text) external
(undefined) external _IOObjectRelease (from IOKit)
(undefined) external _IORegistryEntryCreateCFProperty (from IOKit)
000000010ffa3f97 (__DATA,__objc_data) external _OBJC_CLASS_$_BslyjNwZmPCJkVst
000000010ffa3f97 (__DATA,__objc_data) external _OBJC_CLASS_$_ChiDDQmRSQpwQJgm
3.2 隐藏符号
- static 参数修饰,不会导出符号信息
static char _person_name[30] = {'\0'};
- 在编译参数中加入-exported_symbols_list export_list
CC = xcrun --sdk iphoneos clang
ARCH = arm64
FRAMEWORK = -framework Foundation
VERSION = -compatibility_version 1 -current_version 1
EXPORT = -exported_symbols_list export_list
VISIBLE = -fvisibility=hidden
TARGET = target.dylib
SOURCE = Person.mm
target1:$(SOURCE1)
$(CC) -dynamiclib -arch $(ARCH) $(FRAMEWORK) $(SOURCE) -o $(TARGET) $(VERSION)
target2:$(SOURCE1)
$(CC) -dynamiclib -arch $(ARCH) $(FRAMEWORK) $(SOURCE) -o $(TARGET) $(VERSION) $(EXPORT)
target3:$(SOURCE1)
$(CC) -dynamiclib -arch $(ARCH) $(FRAMEWORK) $(SOURCE) -o $(TARGET) $(VERSION) $(VISIBLE)
clean:
rm $(TARGET)
- 在编译参数中指定-fvisibility=hidden,对指定符号增加visibility(“default”)来导出符号
//#define EXPORT __attribute__((visibility("default")))
CC = xcrun --sdk iphoneos clang++
ARCH = arm64
FRAMEWORK = -framework Foundation
VERSION = -compatibility_version 1 -current_version 1
VISIBLE = -fvisibility=hidden
TARGET = target.dylib
SOURCE = Person.m
$(TARGET):$(SOURCE)
$(CC) -dynamiclib -arch $(ARCH) $(FRAMEWORK) $(SOURCE) -o $(TARGET) $(VERSION)
.PHONY:clean
clean:
rm $(TARGET)
see also
由于篇幅原因,更多内容请关注 #小程序:iOS逆向,只为你呈现有价值的信息,专注于移动端技术研究领域;更多服务和咨询请关注#公众号:iOS逆向。
🍅 联系作者: iOS逆向(公号:iosrev)
🍅 作者简介:CSDN 博客专家认证🏆丨全站 Top 50、华为云云享专家认证🏆、iOS逆向公号号主
🍅 简历模板、技术互助。关注我,都给你。
iOS逆向小技能:Cydia Substrate的组成部分、编写Tweak的步骤
iOS设备日志查看工具:syslog、socat
「这是我参与2022首次更文挑战的第3天,活动详情查看:2022首次更文挑战」。
前言
本文介绍iOS设备日志查看工具syslog、deviceconsole和socat,如果上述工具都不满意,你也可以使用Mac系统自带的console控制台进行查看。
>
I syslog
1.1 安装syslog
在cydia搜索syslogd to /var/log/syslog安装即可
1.2 syslog用法
syslog是把系统日志写入到/var/log/syslog文件里,用法很简单,执行tail -f /var/log/syslog就能看到了
如果需要过滤某一应用的日志,只需加上grep即可,比如过滤微信
tail -f /var/log/syslog |grep WeChat
II socat
2.1 安装
- 在iOS设备安装
使用 APT 0.6 Transitional 安装socat 几乎所有流行的黑客工具都可以在 BigBoss Recommendation tools这个包中找到 ( APT 0.6 Transitional, Git, GNU Debugger, less, make, unzip, wget 和 SQLite 3.x)
apt-get install socat
如果找不到安装包的时候,运行一下 apt-get update, 获得最新的包列表.
2.2 连接到系统日志的sock文件
socat - UNIX-CONNECT:/var/run/lockdown/syslog.sock
- 进入到命令行交互界面,这时可以输入help查看帮助
iPhone:~ root# socat - UNIX-CONNECT:/var/run/lockdown/syslog.sock
========================
ASL is here to serve you
>
2.3 日志的查看
输入watch查看,输入stop停止
2.4 清除日志文件数据
cat /dev/null >/var/log/syslog
III deviceconsole
自从iOS8之后,我就习惯使用Mac系统自带的console ,后来发现有些同事的Mac中console 版本低,没有device 选项;于是乎,就推荐他们使用deviceconsole
- deviceconsole --help
➜ bin git:(master) ✗ deviceconsole --help Usage: deviceconsole [options] Options: -d Include connect/disconnect messages in standard out -u <udid> Show only logs from a specific device -p <process name> Show only logs from a specific process Control-C to disconnect Mail bug reports and suggestions to <ryan.petrich@medialets.com>
knlog -help
Usage: knlog [options] Options: -i | --case-insensitive Make filters case-insensitive -f | --filter <string> Filter include by single word occurrences (case-sensitive) -x | --exclude <string> Filter exclude by single word occurrences (case-sensitive) -p | --process <string> Filter by process name (case-sensitive) -u | --udid <udid> Show only logs from a specific device -s | --simulator <version> Show logs from iOS Simulator --debug Include connect/disconnect messages in standard out --use-separators Skip a line between each line --force-color Force colored text --message-only Display only level and message Control-C to disconnect
- 编译之后的可执行文件 knlog
see also
更多内容请关注 #小程序:iOS逆向,只为你呈现有价值的信息,专注于移动端技术研究领域;更多服务和咨询请关注#公众号:iOS逆向。
iOS逆向小技能:Theos的安装
iOS逆向小技能:使用substrate及runtime进行hook(定时检测app是否开启)
iOS小技能: 处理接口的暂无数据
这是我参与11月更文挑战的第9天,活动详情查看:2021最后一次更文挑战。
引言
在日常开发中经常涉及数据列表的查询,处理服务侧无数据返回的情况或者网络异常的手段是iOS必备小技能。
I 处理暂无数据
网络请求失败,业务逻辑错误,返回数据为空都是需要处理界面的显示,推荐使用暂无数据进行提示。
![]()
1.1 用法
if (weakSelf.viewModel.listDataArray.count == 0) {
[weakSelf.viewModel.ShowNoviewSubject sendNext:QCTLocal(CRM_nodata_Info)];
}else{
[weakSelf.viewModel.hidenNoviewSubject sendNext:nil];
}
1.2 核心实现
V层初始化暂无数据视图:将视图添加到tableView,这样可以不影响下拉刷新和上拉加载
- (CRMNoDatatView *)NoView{
if (nil == _NoView) {
CRMNoDatatView *tmpView = [[CRMNoDatatView alloc]init];
_NoView = tmpView;
[self.tableView addSubview:_NoView];
__weak __typeof__(self) weakSelf = self;
[_NoView mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerY.equalTo(weakSelf.tableView.mas_centerY).offset(kAdjustRatio(k_noteViewH));
make.width.equalTo(weakSelf);
make.left.right.bottom.equalTo(weakSelf.tableView);//tableView
}];
}
return _NoView;
}
- (void)ShowNoview:(NSString *)title img:(NSString*)imgName
{
self.NoView.title = title;
self.NoView.imgName = imgName;
[self.tableView bringSubviewToFront:self.NoView];
}
V层监听C层的事件
[self.viewModel.hidenNoviewSubject subscribeNext:^(id _Nullable x) {
weakSelf.NoView.hidden = YES;
}];
[self.viewModel.ShowNoviewSubject subscribeNext:^(id _Nullable x) {
weakSelf.NoView.hidden = NO;
[weakSelf ShowNoview:x img:@"img_kongbai_zanwu"];
}];
暂无数据视图的实现
// 显示暂无数据图片
- (UIImageView *)imageV{
if (nil == _imageV) {
UIImageView *tmpView = [[UIImageView alloc]init];
_imageV = tmpView;
_imageV.contentMode = UIViewContentModeScaleAspectFit;
_imageV.image = [UIImage imageNamed:@"icon_wushuju"];
[self addSubview:_imageV];
__weak __typeof__(self) weakSelf = self;
[_imageV mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerX.equalTo(weakSelf);
make.centerY.equalTo(weakSelf).offset(-kAdjustRatio(35));
make.left.equalTo(weakSelf).offset(kAdjustRatio(33));
make.right.equalTo(weakSelf).offset(kAdjustRatio(-33));
}];
}
return _imageV;
}
//显示暂无数据文本
- (UILabel *)label{
if (nil == _label) {
UILabel *tmpView = [[UILabel alloc]init];
_label = tmpView;
[self addSubview:_label];
__weak __typeof__(self) weakSelf = self;
[_label mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerX.equalTo(weakSelf);
make.top.equalTo(weakSelf.imageV.mas_bottom).offset(kAdjustRatio(22));
_label.textAlignment = NSTextAlignmentCenter;
_label.font = kPingFangFont(15);
_label.textColor = rgb(51,51,51);
}
return _label;
}
// 更新图片数据
-(void)setImgName:(NSString *)imgName{
_imgName = imgName;
if (imgName.length<=0) {
return;
}
[self.imageV setImage:[UIImage imageNamed:imgName]];
self.reloadbtnView.hidden = !self.isShowReloadBtn;
// }
}
- (void)setTitle:(NSString *)title{
_title = title;
self.label.text = title;
}
see also
更多内容请关注 #小程序:iOS逆向,只为你呈现有价值的信息,专注于移动端技术研究领域。