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

背景

在前面的文章中,笔者有讲解如何设置以及获取一个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__
  • dladdr
  • uintptr_t 等含义

__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:gcc.gnu.org/onlinedocs/… 这里笔者也摘抄了其他博客的一些论述:

__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");
    }
}
复制代码

运行结果如下:

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

为了更好说明函数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安全–验证函数地址,检测是否被替换,反注入

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值