iOS开发之深入理解runtimeRuntime源码iOS

iOS开发之runtime(16):设置/获取section数据

2019-01-29  本文已影响9人  kyson老师

本系列博客是本人的源码阅读笔记,如果有iOS开发者在看runtime的,欢迎大家多多交流。为了方便讨论,本人新建了一个微信群(iOS技术讨论群),想要加入的,请添加本人微信:zhujinhui207407,【加我前请备注:ios 】,本人博客http://www.kyson.cn 也在不停的更新中,欢迎一起讨论

runtime logo

本文完整版详见笔者小专栏:https://xiaozhuanlan.com/runtime

背景

在前面的文章中,笔者有讲解如何设置以及获取一个section的数据,demo如下:

#import <Foundation/Foundation.h>
#import <dlfcn.h>
#include <mach-o/loader.h>
#include <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 = @"";
//设置"__DATA,__customSection"的数据为kyson
char *kString __attribute__((section("__DATA,__customSection"))) = (char *)"kyson";

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        //设置machheader信息
        if (machHeader == NULL)
        {
            Dl_info info;
            dladdr((__bridge const void *)(configuration), &info);
            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);
        }

    }
    return 0;
}

本文就带大家详细分析一下,这段代码的含义。本文您将了解到

__attribute__

关于__attribute__,其实前面的文章有说过其中的一种用法:

__attribute__((constructor)) void myentry(){
    NSLog(@"constructor");
}

这段代码会优先于main方法执行。
很显然,其于一般方法不一样的地方在于有修饰符:

__attribute__((constructor))

那么__attribute__修饰符有什么作用呢,这里引用一段:

This section describes the syntax with which __attribute__ may be used, and the constructs to which attribute specifiers bind, for the C language. Some details may vary for C++ and Objective-C. Because of infelicities in the grammar for attributes, some forms described here may not be successfully parsed in all cases.
There are some problems with the semantics of attributes in C++. For example, there are no manglings for attributes, although they may affect code generation, so problems may arise when attributed types are used in conjunction with templates or overloading. Similarly, typeid does not distinguish between types with different attributes. Support for attributes in C++ may be restricted in future to attributes on declarations only, but not on nested declarators.

以上摘自gcc:https://gcc.gnu.org/onlinedocs/gcc-4.9.2/gcc/Attribute-Syntax.html#Attribute-Syntax
这里笔者也摘抄了其他博客的一些论述:

__attribute__可以设置函数属性(Function Attribute)、变量属性(Variable Attribute)和类型属性(Type Attribute)。它的书写特征是:__attribute__前后都有两个下划线,并切后面会紧跟一对原括弧,括弧里面是相应的__attribute__参数,语法格式如下:
__attribute__((attribute-list))
另外,它必须放于声明的尾部“;”之前。

这里再介绍一篇文章,用于讲解__attribute__的作用:
不使用 NSOBJECT 的 OBJECTIVE-C CLASS
这篇文章讲解了

__attribute__((objc_root_class))

的用法,完整的代码如下:

#import <stdio.h>
#import <stdlib.h>
#import <objc/runtime.h>

__attribute__((objc_root_class))
@interface Answer
{
    Class isa;
}

+ (id)instantiate;
- (void)die;

@property(assign, nonatomic) int value;

@end

@implementation Answer

+ (id)instantiate
{
    Answer *result = malloc(class_getInstanceSize(self));
    result->isa = self;
    return result;
}

- (void)die
{
    free(self);
}

@end

int main(int argc, char const *argv[])
{
    Answer *answer = [Answer instantiate];
    answer.value = 42;
    printf("The answer is: %d\n", answer.value);
    [answer die];
    return 0;
}

回到本文开头的Demo,使用的是另外一个属性:

__attribute__((section("__DATA,__customSection"))) 

明显看出,是声明的一个变量属性。
这个变量要“被放到” section为“__DATA,__customSection”里面。这么一来就不难理解了。

dladdr

使用dladdr方法可以获得一个函数所在模块,名称以及地址。

下面我们通过一个实例来说明:

#include <dlfcn.h>
#include <objc/objc.h>
#include <objc/runtime.h>
#include <stdio.h>
#include <string.h>

int main()
{
    Dl_info info;
    IMP imp = class_getMethodImplementation(objc_getClass("NSArray"),sel_registerName("description"));
    printf("pointer %p\n", imp);
    if (dladdr(imp,&info))
    {
        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);
    } else
    {
        printf("error: can't find that symbol.\n");
    }
}

运行结果如下:


result

所以我们可以通过这种方式来判断一个函数是不是被非法修改了。

为了更好说明函数dladdr作用,这里笔者再举个Demo:

static inline BOOL validate_methods(const char *cls,const char *fname) __attribute__ ((always_inline));

BOOL validate_methods(const char *cls,const char *fname){
    Class aClass = objc_getClass(cls);
    Method *methods;
    unsigned int nMethods;
    Dl_info info;
    IMP imp;
    char buf[128];
    Method m;
    
    if(!aClass)
        return NO;
    methods = class_copyMethodList(aClass, &nMethods);
    while (nMethods--) {
        m = methods[nMethods];
        printf("validating [%s %s]\n",(const char *)class_getName(aClass),(const char *)method_getName(m));
        
        imp = method_getImplementation(m);
        //imp = class_getMethodImplementation(aClass, sel_registerName("allObjects"));
        if(!imp){
            printf("error:method_getImplementation(%s) failed\n",(const char *)method_getName(m));
            free(methods);
            return NO;
        }
        
        if(!dladdr(imp, &info)){
            printf("error:dladdr() failed for %s\n",(const char *)method_getName(m));
            free(methods);
            return NO;
        }
        
        /*Validate image path*/
        if(strcmp(info.dli_fname, fname))
            goto FAIL;
        
        if (info.dli_sname != NULL && strcmp(info.dli_sname, "<redacted>") != 0) {
            /*Validate class name in symbol*/
            snprintf(buf, sizeof(buf), "[%s ",(const char *) class_getName(aClass));
            if(strncmp(info.dli_sname + 1, buf, strlen(buf))){
                snprintf(buf, sizeof(buf),"[%s(",(const char *)class_getName(aClass));
                if(strncmp(info.dli_sname + 1, buf, strlen(buf)))
                    goto FAIL;
            }
            
            /*Validate selector in symbol*/
            snprintf(buf, sizeof(buf), " %s]",(const char*)method_getName(m));
            if(strncmp(info.dli_sname + (strlen(info.dli_sname) - strlen(buf)), buf, strlen(buf))){
                goto FAIL;
            }
        }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;
}

回到本文开头的Demo,不难看出,其实

if (machHeader == NULL)
{
    Dl_info info;
    dladdr((__bridge const void *)(configuration), &info);
    machHeader = (struct mach_header_64*)info.dli_fbase;
}

这段代码的用途仅仅是为了获取header。至于header前面的 文章也提到过了,这里不多做讲解了,拿到的header作为函数getsectiondata的参数:

uintptr_t* data = (uintptr_t *) getsectiondata(machHeader, "__DATA", "__customSection", &byteCount);

这里需要注意的是getsectiondata的定义如下:

extern uint8_t *getsectiondata(
    const struct mach_header_64 *mhp,
    const char *segname,
    const char *sectname,
    unsigned long *size);

所以一开始笔者在返回类型的时候,使用了uint8_t结果发现不管怎么操作都不能打印出想要的数据。改成uintptr_t才能打印成功。原因大家可以猜想一下。
最后我们查看一下打印的结果:

打印结果

本文参考

iOS安全–验证函数地址,检测是否被替换,反注入

上一篇下一篇

猜你喜欢

热点阅读