Frida Advanced Usage Part 8 – Frida Memory Operations Continued

Introduction 介绍

Welcome to another blog post in our Advanced Frida usage series. It is a continuation of our previous blog where we discussed Memory.scanMemory.copy, and MemoryAccessMonitor JavaScript APIs in analyzing native Android libraries and how to utilize them in performing memory operations.
欢迎阅读我们高级 Frida 使用系列中的另一篇博文。这是我们之前博客的延续,我们在博客中讨论了 Memory.scan 、 Memory.copy 和 MemoryAccessMonitor JavaScript API 在分析原生 Android 库以及如何利用它们在执行内存操作时。

In this article, we will focus on the remaining Frida memory operations APIs, Memory.scanSyncMemory.protect, and Memory.patchCode.
在本文中,我们将重点介绍剩余的 Frida 内存操作 API、 Memory.scanSync Memory.protect 和 Memory.patchCode .

You can find the APK files used in this Blog at: You can find the APK files at: You can find the APK files at: https://github.com/8kSec/Blog-resources/tree/main/Frida-Series
您可以在以下位置找到此博客中使用的 APK 文件: 您可以在以下位置找到 APK 文件: 您可以在以下位置找到 APK 文件: https://github.com/8kSec/Blog-resources/tree/main/Frida-Series

Analysis 分析

In this tutorial, we will use different Android applications to show usage of each API in performing different the named memory operations.
在本教程中,我们将使用不同的 Android 应用程序来展示每个 API 在执行不同的命名内存操作中的用法。

Memory Protection 内存保护

Memory.protect Frida API allows one to update protection permissions of a given memory page region to allow read and write access permissions. This is helpful in modifying runtime memory for patching or execution of protected memory pages.
Memory.protect Frida API 允许更新给定内存页区域的保护权限以允许 read 和 write 访问权限。这有助于修改运行时内存以修补或执行受保护的内存页。

Frida version 16.2.1 introduced a Memory protection query that enables one to enumerate memory ranges satisfying the protection string argument to the Memory.queryProtection query API.
Frida 版本 16.2.1 引入了内存保护查询,使人们能够枚举满足 Memory.queryProtection 查询 API 字符串 protection 参数的内存范围。

In our example, we will use the snapseed application responsible for photo editing in Android applications to show the usage of the API.
在我们的示例中,我们将使用负责 Android 应用程序中照片编辑的 snapseed 应用程序来展示 API 的使用情况。

The steps we need to take are:
我们需要采取的步骤是:

  • Attach the Frida Process to the snapseed application
    将 Frida 进程附加到 snapseed 应用程序

  • Run Process.enumerateModules() in the Frida Console to get all loaded Modules
    在 Frida 控制台中运行 Process.enumerateModules() 以获取所有加载的模块

  • Get the module object of libsnapped_native.so
    libsnapped_native.so 获取

  • Run Memory.queryProtection in the Frida Console
    在 Frida 控制台中运行 Memory.queryProtection

  • Repeat the query with different protection parameters
    使用不同的保护参数重复查询

Running the steps above in the Frida console, we can determine the protection of various functions.
在 Frida 控制台中运行上述步骤,我们可以确定各种功能的保护。

Frida Advanced Usage Part 8 – Frida Memory Operations Continued

The API is useful in querying permissions of different memory page regions for exploit primitives and checking violations.
该 API 可用于查询不同内存页面区域的漏洞利用原语权限和检查违规情况。

To change the protection level of a memory region, we will use Memory.Protect API. It returns true or false depending on the success of the API.
要更改内存区域的保护级别,我们将使用 Memory.Protect API。它返回 true 或 false 取决于 API 的成功。

Writing Frida Script 编写弗里达脚本

We will create a script to use the Memory.Protect API to change the permissions of the snapseed library.
我们将创建一个脚本来使用 Memory.Protect API 来更改 snapseed 库的权限。

Steps taken are: 采取的步骤是:

  • Get the target module to hook
    获取要挂钩的目标模块

  • Use the Memory.Protect to query for at least read and write protection
    使用 Memory.Protect to 查询至少提供读写保护

  • Use the Memory.Protect to query for read, write, and execute protection
    使用 Memory.Protect to 查询读取、写入和执行保护

  • Print out the protection results
    打印出保护结果

The implementation script looks like this:
实现脚本如下所示:

if (Java.available) {
    Java.perform(function () {
        let eightksec = Process.findModuleByName("libsnapseed_native.so");
        //Update the protection of the Module
        // Memory.protect(address, size, protection)
        let protection_results = Memory.protect(
            ptr(eightksec.base),
            eightksec.size,
            "rw-"
        );
        console.log("\nModule Path:", eightksec.path);
        console.log("Protection for Atleast 'rw':", protection_results);
        protection_results = Memory.protect(
            ptr(eightksec.base),
            eightksec.size,
            "rwx"
        );

        console.log("Protection for 'rwx':", protection_results);
    });
}

Running Frida Script 运行 Frida 脚本

Let’s now test our Frida script.
现在让我们测试我们的 Frida 脚本。

Frida Advanced Usage Part 8 – Frida Memory Operations Continued

From the above results, permissions allowed are at least `read` and `write` only.
从上述结果来看,允许的权限至少是“读取”和“写入”。

Memory Synchronous Scanning
内存同步扫描

Memory.scanSync Frida API is a synchronous version of Memory.scan used for finding occurrences of user patterns in a given memory range specified by the size. The returned array of matched objects contains absolute address and size as the properties.
Memory.scanSync Frida API 是用于 Memory.scan 查找由大小指定的给定内存范围内出现的用户模式的同步版本。返回的匹配对象数组包含绝对 address 属性和 size 属性。

TIn our example, we will use a simple android application that compares two strings and prints out You win message if the strings match.
在我们的示例中,我们将使用一个简单的 android 应用程序来比较两个字符串,如果字符串匹配,则打印出 You win 消息。

extern "C" JNIEXPORT jstring

JNICALL
Java_com_ksec_eightksec_MainActivity_stringFromJNI(
        JNIEnv *env,
        jobject /* this */) {
    const char *string_one = "eightksec-string";
    const char *string_two = "eightksec string";

    int comparison_one = std::strcmp(string_one, string_two);
    if (comparison_one == 0 ) {
        return  env->NewStringUTF("You Win");

    } else {
        return env->NewStringUTF("You Lose, Strings should be equal");
    }
}

The application always prints You lose everytime it is run because string one and two are not equal. The goal of the challenge is to find the string reference addresses in the application’s memory.
应用程序每次运行时都会打印 You lose,因为字符串 1 和 2 不相等。挑战的目标是在应用程序的内存中查找字符串引用地址。

Writing Frida Script 编写弗里达脚本

We write a Frida script to show a synchronous version of the Memory.scan API to search for user patterns in the entire memory range of the target module.
我们编写一个 Frida 脚本来显示 Memory.scan API 的同步版本,以在目标模块的整个内存范围内搜索用户模式。

The API supports wildcard search using question marks ?? in the pattern string. In our analysis, we will use the question mark to match both eightksec string and eightksec-string in the application memory.
API 支持在模式字符串中使用问号 ?? 进行通配符搜索。在我们的分析中,我们将使用问号来匹配应用程序内存 eightksec string 和 eightksec-string 应用程序内存中的问号。

Steps we need to take:
我们需要采取的步骤:

  • Get the target module 获取目标模块

  • Write the pattern to search for
    编写要搜索的模式

  • Scan memory using the Memory.scanSync API for the pattern
    使用 Memory.scanSync API 扫描模式的内存

  • Print the matched objects address and size
    打印匹配对象的地址和大小

Here is the full implementation script of the Memory.scanSync API
下面是 Memory.scanSync API 的完整实现脚本

if (Java.available) {
	Java.perform(function () {
		let eight8ksec = Process.findModuleByName("libeightksec.so");
		if (eight8ksec != null) {
			// patern to search for is "eightksec string"
			let pattern = "65 69 67 68 74 6b 73 65 63 ?? 73 74 72 69 6e 67";
			console.log("\nPattern:", pattern);

			// Memory.scanSync(address, size, pattern)
			let eight8ksec_results = Memory.scanSync(
				eight8ksec.base,
				eight8ksec.size,
				pattern
			);

			//print out matched pattern address and address size
			console.log("Memory.scanSync Result array: \n",JSON.stringify(eight8ksec_results));

			// print the first matched Address and size of the pattern
			console.log("Address of First Match:",eight8ksec_results[0].address);
			console.log("Size of First Match:", eight8ksec_results[0].size);

			// print the second matched Address and size of the pattern
			console.log("Address of second Match:",eight8ksec_results[1].address);
			console.log("Size of second Match:", eight8ksec_results[1].size);
		}
	});
}

Running Frida Script 运行 Frida 脚本

Running the script against the application, we get the memory addresses of the matched patterns objects.
对应用程序运行脚本,我们得到匹配的模式对象的内存地址。

Frida Advanced Usage Part 8 – Frida Memory Operations Continued

Further, this technique can utilised to search application memory for target addresses and use frida Interceptor to patch them.
此外,该技术可用于在应用程序内存中搜索目标地址,并使用 frida Interceptor 来修补它们。

Memory Patching 内存修补

Memory.patchCode JavaScript API allows one to safely modify the specified size of bytes at a given memory address referenced as a nativePointer. Memory Patchode can be used together with writers to generate machine code directly written to the memory. The currently supported writers by Frida for different architectures are X86WriterArm64Writer, and ThumbWriter
Memory.patchCode JavaScript API 允许人们安全地修改给定内存地址处的指定 size 字节,该地址被引用为 nativePointer .Memory Patchode 可以与写入器一起使用,以生成直接写入内存的机器代码。Frida 目前支持的不同架构的编写器是 X86Writer 、 Arm64Writer 和 ThumbWriter
.

To get the correct target architecture and size of the pointer run the following commands in the frida console Process.arch and Process.pointerSize
要获取正确的目标体系结构和指针大小,请在 frida 控制台中运行以下命令 Process.arch ,然后 Process.pointerSize
.

In our analysis, we will use a simple Android application that checks if Frida debugger is hooked into it. It prints Frida detected if the process is hooked using Frida else it prints Frida not detected
在我们的分析中,我们将使用一个简单的 Android 应用程序来检查 Frida 调试器是否已挂钩到其中。 Frida detected 如果使用 Frida 挂钩该过程,它会打印, Frida not detected 否则它会打印
.

extern "C" JNIEXPORT jstring JNICALL
Java_com_ksec_eightksec_MainActivity_FridaDetectFromJNI(
        JNIEnv *env,
        jobject /* this */) {
    FILE *fridaFile = fopen("/proc/self/maps", "r");
    if (fridaFile != nullptr) {
        char line[256];
        while (fgets(line, sizeof(line), fridaFile)) {
             char *result_pointer = strstr(line, "frida-agent");
            if (result_pointer != nullptr) {
                fclose(fridaFile);
                return env->NewStringUTF("Frida detected");
            }
        }
        fclose(fridaFile);
    }
    return env->NewStringUTF("Frida not detected");

}

To get the memory addresses of branches to hook, we use ghidra to reverse engineer the libeightksec.so library and get offsets of the branches.
为了获得要钩住的分支的内存地址,我们使用 ghidra 对 libeightksec.so 库进行逆向工程并获取分支的偏移量。

Frida Advanced Usage Part 8 – Frida Memory Operations Continued

From the assembly code above, the application branches to 0x001098c if the results of strstr are not NULL else the code will branch to 0x001019bc if the returned pointer is NULL.
从上面的汇编代码中,应用程序分支到 0x001098c 如果结果 strstr 不是 NULL 否则,如果返回的指针为 NULL,则代码将分支到 0x001019bc 。

The strstr is used for locating a substring and returns a pointer if found or NULL if the substring is not found.
用于 strstr 查找子字符串,如果找到或 NULL 未找到子字符串,则返回指针。

Writing Frida Script 编写弗里达脚本

Steps taken are: 采取的步骤是:

  • Get the base address of the Module
    获取模块的基址

  • Get the Frida Detected and Not detected branches
    获取检测到和未检测到的 Frida 分支

  • Use Memory.protect to change the permissions to rwx
    用于 Memory.protect 将权限更改为 rwx

  • Use Memory.patchCode to patch the Frida Detected branch
    用于 Memory.patchCode 修补 Frida 检测到的分支

  • Clean up the memory after patching
    修补后清理内存

The full code for our script is as follows:
脚本的完整代码如下:

if (Java.available) {
    let baseAddress = Module.findBaseAddress("libeightksec.so");

    // Check if the base address is not null
    if (baseAddress != null) {
        // Get the address for the Frida detect branch
        let frida_detected_branch = baseAddress.add(0x01988);
        console.log("\nFrida Detected branch address: ", frida_detected_branch);
        // Get the address for Frida not detected branch
        let frida_notDetected_branch = baseAddress.add(0x019b8);
        console.log("Frida Not Detected branch: ", frida_notDetected_branch);

        // Change Memory protection to allow "rwx"
        Memory.protect(frida_detected_branch, 0x1000, "rwx");

        Memory.patchCode(frida_detected_branch, 8, (code) => {
            // Instantiate a new Arm64 Writer
            let writer = new Arm64Writer(code);

            try {
                // Branch to Frida not Detected Branch
                writer.putBImm(frida_notDetected_branch);
                writer.flush();
                console.log("Memory Patched Successfully");
            } finally {
                // clean up Memory
                writer.dispose();
            }
        });
    }
}

Running Frida Script 运行 Frida 脚本

Let’s now test the script against our application.
现在让我们针对我们的应用程序测试脚本。

Frida Advanced Usage Part 8 – Frida Memory Operations Continued

Clicking the Check Frida button on the Application Interface, shows we have successfully altered the control flow of the program by Toasting Frida not detected while the process is hooked.
单击应用程序界面上的 Check Frida 按钮,表明我们已经在进程挂钩时通过 Toasting Frida not detected 成功更改了程序的控制流。

Frida Advanced Usage Part 8 – Frida Memory Operations Continued

In real-world applications, Knowledge learned can be used in defeating various anti-debug mechanisms implemented by developers that might be roadblocks to further security analysis or debugging.
在实际应用中,所学的知识可用于击败开发人员实现的各种反调试机制,这些机制可能会成为进一步安全分析或调试的障碍。

Conclusion 结论

This marks the end of the second part Frida Memory operations blogpost as part of our Advanced Frida series. We have now learned how to utilize various memory operations in inspecting the native memory of Android applications.
这标志着第二部分 Frida 内存操作博客文章的结束,这是我们高级 Frida 系列的一部分。我们现在已经学会了如何利用各种内存操作来检查 Android 应用程序的本机内存。

The same techniques and knowledge can be applied to iOS applications in inspecting runtime memory for debugging and security posture analysis.
同样的技术和知识可以应用于 iOS 应用程序,用于检查运行时内存以进行调试和安全状况分析。

原文始发于8ksecresearch:Frida Advanced Usage Part 8 – Frida Memory Operations Continued

版权声明:admin 发表于 2024年4月16日 下午5:31。
转载请注明:Frida Advanced Usage Part 8 – Frida Memory Operations Continued | CTF导航

相关文章