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.syscallimport android.os.Bundle
import android.widget.Button
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.cyrus.example.Rclass SyscallActivity : AppCompatActivity() {// 加载 native 库init {System.loadLibrary("syscall-lib")}external fun readFileWithSyscall(path: String): Stringoverride 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)// 显示 ToastToast.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 shellwayne:/ # 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,%functionraw_syscall:MOV             R12, SPSTMFD           SP!, {R4-R7}MOV             R7, R0MOV             R0, R1MOV             R1, R2MOV             R2, R3LDMIA           R12, {R3-R6}SVC             0LDMFD           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

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

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

相关文章

初始JavaEE篇 —— 网络编程(2):了解套接字,从0到1实现回显服务器

找往期文章包括但不限于本期文章中不懂的知识点&#xff1a; 个人主页&#xff1a;我要学编程程(ಥ_ಥ)-CSDN博客 所属专栏&#xff1a;JavaEE 目录 TCP 与 UDP Socket套接字 UDP TCP 网络基础知识 在一篇文章中&#xff0c;我们了解了基础的网络知识&#xff0c;网络的出…

【月之暗面kimi-注册/登录安全分析报告】

前言 由于网站注册入口容易被黑客攻击&#xff0c;存在如下安全问题&#xff1a; 暴力破解密码&#xff0c;造成用户信息泄露短信盗刷的安全问题&#xff0c;影响业务及导致用户投诉带来经济损失&#xff0c;尤其是后付费客户&#xff0c;风险巨大&#xff0c;造成亏损无底洞 …

架构师备考-概念背诵(软件工程)

软件工程 软件开发生命周期: 软件定义时期:包括可行性研究和详细需求分析过程,任务是确定软件开发工程必须完成的总目标,具体可分成问题定义、可行性研究、需求分析等。软件开发时期:就是软件的设计与实现,可分成概要设计、详细设计、编码、测试等。软件运行和维护:就是…

[FBCTF 2019]rceservice 详细题解

知识点: json字符串 PHP正则表达式元字符 PCRE回溯机制绕过正则表达式 %0a 换行符绕过正则表达式(详细讲解) 提示 Enter command as JSON 题目还有一个附件,打开是index.php文件源码 <?php putenv(PATH/home/rceservice/jail); if (isset($_REQUEST[cmd])) {$json $_…

【竞技宝】DOTA2-梦幻联赛S24:圣剑美杜莎强拆基地终结比赛

北京时间11月9日,DOTA2的梦幻联赛S24继续进行。本日迎来第二阶段的B组二、三名加赛PARI对阵spirit。本场比赛双方前两局战至1-1平,决胜局同样是难分胜负打到了六十分钟之后,关键时刻spirit主动出击,圣剑美杜莎强拆基地成功一波结束比赛,最终spirit让一追二击败PARI。以下是本场…

计算机的错误计算(一百四十九)

摘要 探讨 MATLAB 中 的计算精度问题。当 为含有小数的大数或整数附近数时&#xff0c;输出会有错误数字。 例1. 已知 计算 直接贴图吧&#xff1a; 另外&#xff0c;16位的正确值分别为 0.6374239897486897e0、-0.6613118653236519e0、0.3769911184298822e-5 与…

力扣 多数元素

用了排序跟抵消。 题目 由题可知&#xff0c;多数元素是指在数组中出现次数大于一半的元素&#xff0c;且总是存在多数元素。不难想到&#xff0c;把数组排序后&#xff0c;这个数组的中间数一定是这个要找的元素。 用了sort排序&#xff0c;时间复杂度O&#xff08;nlogn&am…

Oracle OCP认证考试考点详解082系列11

题记&#xff1a; 本系列主要讲解Oracle OCP认证考试考点&#xff08;题目&#xff09;&#xff0c;适用于19C/21C,跟着学OCP考试必过。 51. 第51题&#xff1a; 题目 51.View the Exhibit and examine the description of the tables You execute this SQL statement Whi…

前端小知识:如何理解这个新特性 ?= 运算符

在日常的JavaScript开发中&#xff0c;我们经常会处理一些异步任务&#xff0c;避免代码出错&#xff0c;这时候常见的工具就是 try-catch 块和 async-await 语法。这些工具虽好&#xff0c;但当我们代码量一多&#xff0c;整个代码结构可能会显得很臃肿&#xff0c;阅读起来也…

Redhat切换其他源

1. 效果图 2. 安装 RPM 包的命令 rpm -ivh --nodeps --force epel-release-latest-8.noarch.rpm rpm -ivh --nodeps --force yum-4.7.0-4.el8.noarch.rpm rpm -ivh --nodeps --force yum-utils-4.0.21-3.el8.noarch.rpm 3. 修改默认源 vi /etc/yum.repos.d/redhat.repo[BaseO…

如何使用OpenCV和Python进行相机校准

《------往期经典推荐------》 一、AI应用软件开发实战专栏【链接】 项目名称项目名称1.【人脸识别与管理系统开发】2.【车牌识别与自动收费管理系统开发】3.【手势识别系统开发】4.【人脸面部活体检测系统开发】5.【图片风格快速迁移软件开发】6.【人脸表表情识别系统】7.【…

「Mac畅玩鸿蒙与硬件32」UI互动应用篇9 - 番茄钟倒计时应用

本篇将带你实现一个番茄钟倒计时应用&#xff0c;用户可以设置专注时间和休息时间的时长&#xff0c;点击“开始专注”或“开始休息”按钮启动计时&#xff0c;应用会在倒计时结束时进行提醒。番茄钟应用对于管理时间、提升工作效率非常有帮助&#xff0c;并且还会加入猫咪图片…

Qt/C++ 海康SDK开发示例Demo

*** 工业相机在机器视觉中起到关键作用&#xff0c;本文基于海康 SDK 详细解读了设备连接与控制的各个步骤。内容涵盖设备枚举、句柄创建、图像采集回调以及设备异常处理&#xff0c;帮助开发者快速理解如何通过代码控制相机&#xff0c;实时采集并处理图像数据。*** 1. 搜索并…

探索 Python 的新边疆:sh 库的革命性功能

文章目录 **探索 Python 的新边疆&#xff1a;sh 库的革命性功能**第一部分&#xff1a;背景介绍第二部分&#xff1a;sh 库是什么&#xff1f;第三部分&#xff1a;如何安装 sh 库&#xff1f;第四部分&#xff1a;简单库函数使用方法1. 执行 ls 命令2. 使用 grep 搜索文件内容…

深度学习——前向传播与反向传播、神经网络(前馈神经网络与反馈神经网络)、常见算法概要汇总

文章目录 &#x1f33a;深度学习面试八股汇总&#x1f33a;前向传播与反向传播前向传播&#xff08;Forward Propagation&#xff09;反向传播&#xff08;Back Propagation&#xff09;总结 神经网络简介结构类型前馈神经网络&#xff08;Feedforward Neural Network, FFNN&am…

MySQL 中的索引下推功能

看到索引&#xff0c;应该大家都可以联想到这个是和查询效率有关系的&#xff0c;既然有这个功能&#xff0c;那么那句古话说的好啊&#xff1a;存在即合理。那么这个就是说有了这个功能&#xff0c;可以提升查询效率。 什么是索引下推 我们先有一个大概的理解&#xff1a;在…

#渗透测试#SRC漏洞挖掘# 操作系统-Linux系统之基本命令、资源耗尽脚本编写

免责声明 本教程仅为合法的教学目的而准备&#xff0c;严禁用于任何形式的违法犯罪活动及其他商业行为&#xff0c;在使用本教程前&#xff0c;您应确保该行为符合当地的法律法规&#xff0c;继续阅读即表示您需自行承担所有操作的后果&#xff0c;如有异议&#xff0c;请立即停…

软考中级 软件设计师 上午考试内容笔记(个人向)Part.1

软考上午考试内容 1. 计算机系统 计算机硬件通过高/低电平来模拟1/0信息&#xff1b;【p进制】&#xff1a; K n K n − 1 . . . K 2 K 1 K 0 K − 1 K − 2... K − m K n r n . . . K 1 r 1 K 0 r 0 K − 1 r − 1 . . . K − m r − m K_nK_{n-1}...K_2K_1K_0K…

IDA*算法 Power Calculus————poj 3134

目录 闲聊 前言 DFS算法的无效搜索 BFS算法的空间浪费 IDDFS A*算法 IDA* Power Calculus 问题描述 输入 输出 问题分析 代码 闲聊 前几周在忙着数学竞赛&#xff0c;所以就没时间更新&#xff0c;高等数学&#xff0c;一生之敌&#xff0c;真不知道报名的时候我是怎么想…

基于python深度学习技术矩阵分解的推荐系统,通过学习隐含特征,实现推荐

实现了一个基于矩阵分解的推荐系统&#xff0c;用于预测用户对电影的评分。具体来说&#xff0c;该程序通过TensorFlow构建和训练一个模型&#xff0c;来学习用户和电影之间的隐含特征&#xff0c;并根据这些特征预测评分。以下是代码的主要功能和步骤的详细描述&#xff1a; …