Android下的系统调用 (syscall),内联汇编syscall

版权归作者所有,如有转发,请注明文章出处:https://cyrus-studio.github.io/blog/

什么是系统调用 (syscall)

系统调用是操作系统提供给应用程序的一组接口,允许用户空间程序与内核进行交互。

在 Android(基于 Linux 内核)中,系统调用由 软中断 实现,通常通过 svc 指令(在 ARM 架构中)触发。系统调用会将 CPU 从用户模式切换到内核模式,使得程序可以执行更高权限的操作。

Android 使用的 C 库是 Bionic,它是为移动设备优化的轻量级 C 库。对应的模块为 libc.so。

Bionic 提供了对系统调用的封装。大多数标准库函数(如 printf、malloc、pthread_create)都通过 Bionic 实现,底层调用了相应的系统调用。

在 NDK 目录中可以找到相关的系统调用号定义头文件。例如

<NDK_PATH>\27.1.12297006\toolchains\llvm\prebuilt\windows-x86_64\sysroot\usr\include\asm-generic\unistd.h

image.png

搜索 bionic 模块 可以找到不同CPU架构下的 syscall 实现
image.png

在 http://androidxref.com/9.0.0_r3/xref/bionic/libc/arch-arm/syscalls/ 可以找到 Android 中所有系统调用的汇编代码文件
image.png

syscall 在 Android 上的应用场景

系统工具和调试:如 strace、lsof 等工具,通过 syscall 获取系统状态。

安全与反调试:某些安全检测和反调试技术会直接使用 syscall 绕过标准的 libc 函数,以防止被 hook。

嵌入式开发:在一些嵌入式系统中,开发者需要直接控制硬件,这时通常会使用 syscall。

如何在 Android 中使用 syscall

假设我们希望通过 syscall 直接读取文件内容,编写 native 方法代码如下

// 引入必要的头文件
#include <jni.h>
#include <string>
#include <fcntl.h>          // 文件控制定义(如 O_RDONLY)
#include <unistd.h>         // 系统调用号(如 __NR_openat)
#include <sys/syscall.h>    // 系统调用函数
#include <android/log.h>

#define LOG_TAG "syscall-lib.cpp"

// 定义 Android 日志宏,用于输出信息级别日志
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)

// 使用 extern "C" 告诉编译器按照 C 语言的方式来编译和链接这个函数
extern "C"
JNIEXPORT jstring JNICALL
Java_com_cyrus_example_syscall_SyscallActivity_readFileWithSyscall(JNIEnv *env, jobject,
                                                                   jstring path) {
    // 将 Java 字符串 (jstring) 转换为 C 字符串 (const char *)
    const char *filePath = env->GetStringUTFChars(path, nullptr);

    // 使用 syscall 系统调用打开文件
    // __NR_openat 是 openat() 系统调用的调用号
    // AT_FDCWD 表示使用当前工作目录
    // O_RDONLY 表示以只读方式打开文件
    int fd = syscall(__NR_openat, AT_FDCWD, filePath, O_RDONLY);

    // 如果文件打开失败,返回错误信息
    if (fd < 0) {
        // 释放通过 GetStringUTFChars 分配的资源
        env->ReleaseStringUTFChars(path, filePath);
        return env->NewStringUTF("Failed to open file");
    }

    // 定义一个缓冲区,用于存储文件内容
    char buffer[1024];

    // 使用 syscall 系统调用读取文件内容
    // __NR_read 是 read() 系统调用的调用号
    // 读取的内容存储到 buffer 中,最多读取 sizeof(buffer) - 1 字节
    ssize_t bytesRead = syscall(__NR_read, fd, buffer, sizeof(buffer) - 1);

    // 如果读取失败,返回错误信息
    if (bytesRead < 0) {
        // 关闭文件描述符
        syscall(__NR_close, fd);

        // 释放通过 GetStringUTFChars 分配的资源
        env->ReleaseStringUTFChars(path, filePath);
        return env->NewStringUTF("Failed to read file");
    }

    // 使用 syscall 系统调用关闭文件
    syscall(__NR_close, fd);

    // 释放通过 GetStringUTFChars 分配的资源
    env->ReleaseStringUTFChars(path, filePath);

    // 确保缓冲区以 '\0' 结尾(C 字符串需要以 '\0' 作为结束符)
    buffer[bytesRead] = '\0';

    // 输出读取到的文件内容到控制台
    printf("File content: %s\n", buffer);

    // 将读取到的文件内容转换为 Java 字符串 (jstring) 并返回
    return env->NewStringUTF(buffer);
}

代码中用到的系统调用号 __NR_openat 对应的的 openat 方法签名如下

int openat(int dirfd, const char *pathname, int flags, ... /* mode_t mode */ );

具体可参考 Linux 手册:https://man7.org/linux/man-pages/man2/open.2.html

调用 native 方法读取文件并显示文件内容

package com.cyrus.example.syscall

import android.os.Bundle
import android.widget.Button
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.cyrus.example.R

class SyscallActivity : AppCompatActivity() {

    // 加载 native 库
    init {
        System.loadLibrary("syscall-lib")
    }

    external fun readFileWithSyscall(path: String): String

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_syscall)

        findViewById<Button>(R.id.button_syscall).setOnClickListener {
            // 指定文件路径
            val filePath = "/data/local/tmp/test.txt"

            // 调用 native 方法读取文件内容
            val fileContent = readFileWithSyscall(filePath)

            // 显示 Toast
            Toast.makeText(this, fileContent, Toast.LENGTH_SHORT).show()
        }
    }

}

配置 CMakeLists.txt

cmake_minimum_required(VERSION 3.4.1)

find_library( # Sets the name of the path variable.
        log-lib

        # Specifies the NDK library that you want CMake to locate.
        log)

add_library( # 设置库的名称
        syscall-lib

        # 设置库的类型
        SHARED

        # 设置源文件路径
        syscall-lib.cpp)

target_link_libraries( # 将 log 库链接到目标库
        syscall-lib
        ${log-lib})

进入 adb shell,创建文件 test.txt,并输入内容为 hello syscall

adb shell

wayne:/ # cd /data/local/tmp/
wayne:/data/local/tmp # echo "hello syscall" > test.txt

调用测试
image.png

内联汇编实现 syscall

直接内联汇编实现 syscall 调用可以让你跳过标准库的封装层,隐藏 syscall 调用,防 hook 增加逆向难度。

1. 找到 syscall 的汇编代码

把 Bionic 模块对应的 libc.so 拉取到本地

// 32 位库
adb pull /system/lib/libc.so  

// 64 位库
adb pull /system/lib64/libc.so
// 或
adb pull /apex/com.android.runtime/lib64/bionic/libc.so  

用 IDA 打开 libc.so,在 Functions 窗口搜索 syscall
image.png

得到 syscall 的汇编代码如下

MOV             R12, SP            // R12 = SP (保存栈指针)
PUSH            {R4-R7}           // 保存 R4-R7 到栈
MOV             R7, R0            // R7 = 系统调用号
MOV             R0, R1            // R0 = 第一个参数
MOV             R1, R2            // R1 = 第二个参数
MOV             R2, R3            // R2 = 第三个参数
LDM             R12, {R3-R6}      // R3-R6 = 额外参数 (从栈中加载)
SVC             0                 // 触发系统调用
POP             {R4-R7}           // 恢复 R4-R7
CMN             R0, #0x1000       // 检查返回值 (是否小于 0)
BXLS            LR                // 成功则返回调用地址

由于我们内联汇编 syscall 需要和原来的汇编保持一致,不需要编译器自动生成的函数入口代码和退出代码,所有需要用到 “裸函数”(naked function)。

2. 裸函数(naked function)

在 C 和 C++ 编程中,attribute((naked)) 是 GCC(GNU Compiler Collection)和 Clang 编译器提供的一个属性,用于定义一个 “裸函数”(naked function)。

裸函数是一种特殊的函数,它允许你直接控制函数的汇编指令,而不会自动为你生成函数的入口代码(如保存寄存器、调整栈指针)和退出代码(恢复寄存器、恢复栈指针)。

attribute((naked)) 提供了一种方式,让开发者完全掌控函数的汇编指令布局,而不受编译器默认生成的代码影响。它适合在对性能要求极高或者需要直接操作硬件的情况下使用,例如系统调用、中断处理程序和上下文切换函数。

基本语法

__attribute__((naked)) void myFunction() {
    // 手动编写汇编指令
}

3. 编写内联汇编代码

__attribute__((naked)) long raw_syscall(long __number, ...) {
    __asm__ __volatile__(
            "MOV             R12, SP\n"
            "PUSH            {R4-R7}\n"
            "MOV             R7, R0\n"
            "MOV             R0, R1\n"
            "MOV             R1, R2\n"
            "MOV             R2, R3\n"
            "LDM             R12, {R3-R6}\n"
            "SVC             0\n"
            "POP             {R4-R7}\n"
            "mov             pc, lr");
}

4. 读取文件内容并返回 kotlin 层调用

// 读取文件内容
std::string read_file(const char *filePath) {
    char buffer[1024] = {0};

    // 调用 raw_syscall 打开文件
    int fd = raw_syscall(SYS_openat, 0, filePath, O_RDONLY, 0);
    if (fd < 0) {
        return "Failed to open file";
    }

    // 调用 raw_syscall 读取文件
    ssize_t bytesRead = raw_syscall(SYS_read, fd, buffer, sizeof(buffer) - 1);
    if (bytesRead < 0) {
        raw_syscall(SYS_close, fd);
        return "Failed to read file";
    }

    // 关闭文件
    raw_syscall(SYS_close, fd);

    // 输出读取到的文件内容到控制台
    LOGI("File content: %s\n", buffer);

    return std::string(buffer);
}


extern "C"
JNIEXPORT jstring JNICALL
Java_com_cyrus_example_syscall_SyscallActivity_readFileWithAssemblySyscall(JNIEnv *env, jobject,
                                                                           jstring path) {
    // 将 Java 字符串 (jstring) 转换为 C 字符串 (const char *)
    const char *filePath = env->GetStringUTFChars(path, nullptr);

    std::string file_content = read_file(filePath);

    // 释放通过 GetStringUTFChars 分配的资源
    env->ReleaseStringUTFChars(path, filePath);

    return env->NewStringUTF(file_content.c_str());
}

CMakeLists.txt 加载汇编文件

按上面的方法找到 ARM64 和 AMR 的 syscall 的汇编代码。

在 cpp 目录 创建 syscall64.s (ARM64)汇编代码文件,定义 raw_syscall 汇编函数

    .text                      // 表示接下来的代码段是可执行代码段
    .global raw_syscall        // 将 `raw_syscall` 设为全局符号,使其可以被其他文件引用
    .type raw_syscall, @function // 指定 `raw_syscall` 是一个函数

raw_syscall:
        // 将第一个参数 (系统调用号) 传递给 X8 寄存器
        MOV             X8, X0    // X8 = X0, 系统调用号存储在 X8 中

        // 将其余的参数从 X1-X6 依次向前移动一位 (为系统调用准备参数)
        MOV             X0, X1    // X0 = X1, 系统调用的第一个参数
        MOV             X1, X2    // X1 = X2, 系统调用的第二个参数
        MOV             X2, X3    // X2 = X3, 系统调用的第三个参数
        MOV             X3, X4    // X3 = X4, 系统调用的第四个参数
        MOV             X4, X5    // X4 = X5, 系统调用的第五个参数
        MOV             X5, X6    // X5 = X6, 系统调用的第六个参数

        // 使用 SVC 指令触发系统调用 (Supervisor Call)
        SVC             0         // 发起系统调用,中断进入内核态执行

        RET                      // 返回

在 cpp 目录 创建 syscall32.s (ARM)汇编代码文件,定义 raw_syscall 汇编函数

    .text
    .global raw_syscall
    .type raw_syscall,%function

raw_syscall:
        MOV             R12, SP
        STMFD           SP!, {R4-R7}
        MOV             R7, R0
        MOV             R0, R1
        MOV             R1, R2
        MOV             R2, R3
        LDMIA           R12, {R3-R6}
        SVC             0
        LDMFD           SP!, {R4-R7}
        mov             pc, lr

在 C++ 代码文件中添加 raw_syscall 函数声明

extern "C" long raw_syscall(long __number, ...);

配置 CMakeLists.txt

# 启用 C 和汇编语言的支持
enable_language(C ASM)

# 根据系统处理器架构选择不同的汇编文件
if (CMAKE_SYSTEM_PROCESSOR MATCHES "aarch")  # 检查当前系统是否为 AArch64 (ARM 64-bit) 架构
    # 为 `syscall64.s` 设置编译标志
    # `-x assembler-with-cpp` 表示使用 C 预处理器来编译汇编文件
    set_source_files_properties(syscall64.s PROPERTIES COMPILE_FLAGS "-x assembler-with-cpp")

    # 添加一个共享库 (Shared Library)
    add_library(
            syscall-lib          # 设置库的名称为 `syscall-lib`

            SHARED               # 指定库的类型为共享库

            syscall64.s          # 添加 ARM64 汇编源文件

            syscall-lib.cpp      # 添加 C++ 源文件
    )

    # 如果系统处理器架构为 ARM (ARM 32-bit)
elseif (CMAKE_SYSTEM_PROCESSOR MATCHES "arm")
    # 为 `syscall32.s` 设置编译标志
    # `-x assembler-with-cpp` 表示使用 C 预处理器来编译汇编文件
    set_source_files_properties(syscall32.s PROPERTIES COMPILE_FLAGS "-x assembler-with-cpp")

    # 添加一个共享库 (Shared Library)
    add_library(
            syscall-lib          # 设置库的名称为 `syscall-lib`

            SHARED               # 指定库的类型为共享库

            syscall32.s          # 添加 ARM 32 位汇编源文件

            syscall-lib.cpp      # 添加 C++ 源文件
    )
endif ()

调用 raw_syscall

// 读取文件内容
std::string read_file(const char *filePath) {
    char buffer[1024] = {0};

    // 调用 raw_syscall 打开文件
    int fd = raw_syscall(SYS_openat, 0, filePath, O_RDONLY, 0);
    if (fd < 0) {
        return "Failed to open file";
    }

    // 调用 raw_syscall 读取文件
    ssize_t bytesRead = raw_syscall(SYS_read, fd, buffer, sizeof(buffer) - 1);
    if (bytesRead < 0) {
        raw_syscall(SYS_close, fd);
        return "Failed to read file";
    }

    // 关闭文件
    raw_syscall(SYS_close, fd);

    // 输出读取到的文件内容到控制台
    LOGI("File content: %s\n", buffer);

    return std::string(buffer);
}


extern "C"
JNIEXPORT jstring JNICALL
Java_com_cyrus_example_syscall_SyscallActivity_readFileWithAssemblySyscall(JNIEnv *env, jobject,
                                                                           jstring path) {
    // 将 Java 字符串 (jstring) 转换为 C 字符串 (const char *)
    const char *filePath = env->GetStringUTFChars(path, nullptr);

    std::string file_content = read_file(filePath);

    // 释放通过 GetStringUTFChars 分配的资源
    env->ReleaseStringUTFChars(path, filePath);

    return env->NewStringUTF(file_content.c_str());
}

最后,运行测试
image.png

源码

完整源码地址:https://github.com/CYRUS-STUDIO/AndroidExample

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

linux-vlan

# VLAN # 1.topo # 2.创建命名空间 ip netns add ns1 ip netns add ns2 ip netns add ns3 # 3.创建veth设备 ip link add ns1-veth0 type veth peer name ns21-veth0 ip link add ns3-veth0 type veth peer name ns23-veth0 # 4.veth设备放入命名空间,启动接口 ip link set n…

浙江大学高等数学研究所已变样

跟我199X年春季到访相比&#xff0c;现改名为“研究院”&#xff0c;说是2017年建立的&#xff0c;刘克峰&#xff08;1965-&#xff0c;研究黎曼几何&#xff0c;加州洛杉矶大学教授&#xff09;已退位&#xff0c;励建书&#xff08;1959-&#xff0c;香港科技大学教授&#…

使用 AMD GPU 上的 Whisper 进行语音转文字

Speech-to-Text on an AMD GPU with Whisper — ROCm Blogs 2024年4月16日&#xff0c;作者&#xff1a;Clint Greene. 介绍 Whisper是由 OpenAI 开发的高级自动语音识别&#xff08;ASR&#xff09;系统。它采用了一个简单的编码器-解码器 Transformer 架构&#xff0c;其中…

统信UOS开发环境支持rust

集成了Rust编译器和包管理工具,支持系统级编程、网络应用等多场景,为开发者提供丰富的库支持。 文章目录 一、环境部署1. rust开发环境安装2. rust开发环境配置二、代码示例三、常见问题1. 借用和所有权问题2. 编译器错误和警告一、环境部署 1. rust开发环境安装 rust是一门…

上海沪尚茗居干货分享:码住这4步,投影仪不再吃灰

在追求高品质家庭娱乐的今天&#xff0c;投影仪已成为年轻人打造家庭影院的新宠。然而&#xff0c;面对市场上琳琅满目的投影仪品牌和型号&#xff0c;如何做出明智的选择呢&#xff1f;上海沪尚茗居为您精心整理了一份投影选择4步曲&#xff0c;助您轻松选购心仪的家庭投影仪。…

[NewStarCTF 2023 公开赛道]逃1

代码审计. 这段代码分为三部分&#xff1a;1.war函数&#xff0c;2.GetFlag类&#xff0c;3.GetFlag类对象的定义&#xff0c;waf过滤以及反序列化 . 很经典的的一道题&#xff0c;键值对逃逸&#xff0c;改变cmd的value&#xff0c;去获取flag. 而war就是我们的突破点&#xf…

基于微信小程序的电商平台+LW示例参考

1.项目介绍 系统角色&#xff1a;管理员、普通用户功能模块&#xff1a;管理员&#xff08;用户管理、商品分类、商品管理、订单管理、系统管理等&#xff09;&#xff0c;普通用户&#xff08;个人中心、收藏、我的订单、查看商品等&#xff09;技术选型&#xff1a;SpringBo…

腾讯混元宣布大语言模型和3D模型正式开源

腾讯混元大模型正在加快开源步伐。 11月5日&#xff0c;腾讯混元宣布最新的MoE模型“混元Large“以及混元3D生成大模型“ Hunyuan3D-1.0”正式开源&#xff0c;支持企业及开发者精调、部署等不同场景的使用需求&#xff0c;可在HuggingFace、Github等技术社区直接下载&#xff…

力扣二叉树题解含思路(C++实现)

1.求二叉树的最近公共祖先&#xff1a; 原题链接&#xff1a;. - 力扣&#xff08;LeetCode&#xff09; 假设这题的p&#xff0c;q分别为7和8&#xff0c;而它们的最近公共祖先肯定是为3。 这题我们大致的思路为保存p&#xff0c;q的绝对路径&#xff0c;接着通过存储的绝对路…

K8S资源介绍之configmap

1 configmap介绍 是什么&#xff1a;是K8S内置的一种存储卷&#xff0c;数据存储在etcd数据库中 应用场景&#xff1a;主要是存储应用的配置&#xff0c;实现配置与应分离&#xff0c;可以实现类似配置配置中心的功能 由于镜像是只读的特性&#xff0c;如果想要修改需要重新…

数据结构与算法学习——背包问题总结

主要学习01背包和完全背包。 01 背包 有n件物品和一个最多能背重量为w 的背包。第i件物品的重量是weight[i]&#xff0c;得到的价值是value[i] 。每件物品只能用一次&#xff0c;求解将哪些物品装入背包里物品价值总和最大。 装满问题 二维&#xff1a; 一维&#xff1a; 组…

算法分析中的渐进符号

在算法分析中,渐进符号用于描述算法在输入规模趋于无穷大时的运行时间或空间增长速率。主要的渐进符号包括 O O O、 Ω \Omega Ω、 Θ \Theta Θ、 o o o 和 ω \omega ω。这些符号各自描述了不同的增长界限,本文给出详细的定义和区别。 渐进符号 1. 大 O O O 符号(B…

计算机毕业设计Python+大模型农产品价格预测 ARIMA自回归模型 农产品可视化 农产品爬虫 机器学习 深度学习 大数据毕业设计 Django Flask

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…

sql专题 之 三大范式

文章目录 背景范式介绍第一范式&#xff1a;属性不可再分第二范式第三范式注意事项 为什么不遵循后续的范式数据库范式在实际应用中会遇到哪些挑战&#xff1f; 背景 数据库的范式&#xff08;Normal Form&#xff09;是一组规则&#xff0c;用于设计数据库表结构以 减少数据冗…

Linux下进程链接结构,命令行参数,环境变量

bash 是一种 shell。在 Linux 系统中&#xff0c;当我们在终端输入命令时&#xff0c;通常是在一个 shell 环境下进行的。如果这个 shell 是 bash&#xff0c;那么所有命令行执行的命令都是 bash 的子进程。 1.Linux下进程链接结构 进程链接补充知识&#xff1a; 所有进程都…

FPGA实现串口升级及MultiBoot(八)四样错误实例演示

本文目录索引 一个指令和三种方式二种位流和四样错误Golden位流工程Watchdog的原理1、打开自己使用的Vivado版本的TCL SHELL2、进入multiboot_address_table.tcl 文件所在目录3、运行 multiboot_address_table.tcl 文件4、按照需求输入参数启动地址确定MultiBoot位流工程验证ex…

信息安全工程师(84)UNIX/Linux操作系统安全分析与防护

前言 UNIX/Linux操作系统&#xff0c;尤其是Linux&#xff0c;以其开放性、稳定性和安全性在服务器、桌面、嵌入式设备和超级计算机中占据重要地位。然而&#xff0c;没有任何操作系统可以百分之百地保证安全&#xff0c;UNIX/Linux也不例外。 一、UNIX/Linux操作系统安全分析 …

day08(单片机)时钟系统+定时器+PWM

目录 时钟系统定时器PWM 时钟系统 时钟基本概念 时钟源 晶体振荡器&#xff08;Crystal Oscillator&#xff09; RC振荡器&#xff08;Resistor-Capacitor Oscillator&#xff09; ​​​​​​​STM32U5时钟源 HSI(High Speed Internal) HSE(High Speed External) LSI(Low Spe…

【JavaEE初阶 — 多线程】内存可见性问题 volatile

1. 内存可见性问题 内存可见性的概念 什么是内存可见性问题呢&#xff1f; 当一个线程对共享变量进行了修改&#xff0c;那么另外的线程都是立即可以看到修改后的最新值。在Java中&#xff0c;可以借助 synchronized、volatile 以及各种Lock 实现可见性。如果我们将变量声…

通用特效Shader

一、通用特效Shader介绍 1.1 什么是通用特效材质 Unity支持SRP Batcher后&#xff0c;使用UberShader的优势非常明显。所谓&#xff0c;UberShader&#xff0c;即一个超级Shader&#xff0c;覆盖一类功能&#xff0c;而不是多个分散的小Shader&#xff0c;比如一个通用特效Sh…