持续创作,加速成长!这是我参与「掘金日新计划 · 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 option
lets 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, ...);
#define SYS_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逆向