iOS開發之runtime(16):設置/獲取section數據詳解

背景

在前面的文章中,筆者有講解如何設置以及獲取一個section的數據,demo以下:html

#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;
}
複製代碼

本文就帶你們詳細分析一下,這段代碼的含義。本文您將瞭解到ios

  • __attribute__
  • dladdr
  • uintptr_t 等含義

__attribute__

關於__attribute__,其實前面的文章有說過其中的一種用法:objective-c

__attribute__((constructor)) void myentry(){
    NSLog(@"constructor");
}
複製代碼

這段代碼會優先於main方法執行。 很顯然,其於通常方法不同的地方在於有修飾符:安全

__attribute__((constructor))
複製代碼

那麼__attribute__修飾符有什麼做用呢,這裏引用一段:bash

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/… 這裏筆者也摘抄了其餘博客的一些論述:post

__attribute__能夠設置函數屬性(Function Attribute)、變量屬性(Variable Attribute)和類型屬性(Type Attribute)。它的書寫特徵是:__attribute__先後都有兩個下劃線,並切後面會緊跟一對原括弧,括弧裏面是相應的__attribute__參數,語法格式以下: __attribute__((attribute-list)) 另外,它必須放於聲明的尾部「;」以前。ui

這裏再介紹一篇文章,用於講解__attribute__的做用: 不使用 NSOBJECT 的 OBJECTIVE-C CLASS 這篇文章講解了atom

__attribute__((objc_root_class))
複製代碼

的用法,完整的代碼以下:spa

#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安全–驗證函數地址,檢測是否被替換,反注入

相關文章
相關標籤/搜索