为Android应用程序读取/dev下设备而提权(二)

本文深入探讨了Android系统中提权的过程,特别是通过init.rc配置文件和device_init函数实现的设备权限修改。分析了init流程,包括关键文件如init.c、init.rc的作用及其相互关系。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

为Android应用程序读取/dev下设备而提权(二)
为Android应用程序读取/dev下设备而提权(一) 中,简单总结了提权的两种方法: device_init init.rc 。在此篇文章中,我将详细总结的是稍一不留神,就容易把人弄晕乎的 init.c device_initinit.rc 三者之间的关系,TA们到底是如何工作的。


目录结构

ls一下system/core/init/
devices.c、devices.h、init.c、init.h、keywords.h、parser.c、property_service.c....
另外system/core/rootdir/init.rc ,当然init.rc的位置可以另行指定。



init流程

init过程的起点是init.c *注释中的序号表示执行顺序
int main(int argc, char **argv) { … … mkdir("/dev", 0755); //建立基本文件系统节点 mkdir("/proc", 0755); mkdir("/sys", 0755); mount("tmpfs", "/dev", "tmpfs", 0, "mode=0755"); mkdir("/dev/pts", 0755); mkdir("/dev/socket", 0755); mount("devpts", "/dev/pts", "devpts", 0, NULL); mount("proc", "/proc", "proc", 0, NULL); mount("sysfs", "/sys", "sysfs", 0, NULL); … … INFO("reading config file\n"); parse_config_file("/init.rc"); // 1、 调用parse_config 函数解析init.rc脚本 //11、经过解析,init.rc的内容就被分为多少个段,被串在action_list链表中。on 开头的都是action类型的段,比如init段,init段用一个结构体struct action表示, 其中name是init,所有这个段内的命令,都被串在commands链表中。 action_for_each_trigger("early-init", action_add_queue_tail); //12、 遍历action_list链表,查找name是early-init的那个action,将这个节点放在action_queu e的尾部。 drain_action_queue(); // 13、将action_queue尾部的节点遍历,然后删除。就相当于遍历name是early-init的action节点内的commands链表。就是在执行init.rc脚本中 on early-init段内的所有命令。 … … INFO("device init\n"); device_fd = device_init(); //常见必要的设备节点 property_init(); //init 以后的任务就是proper_service action_for_each_trigger("init", action_add_queue_tail); //14、将init 段,加入action_queue drain_action_queue(); // 执行init段得命令 … … }
system/core/init/parser.c:

static void parse_config(const char *fn, char *s) { struct parse_state state; char *args[MAXARGS]; int nargs; nargs = 0; state.filename = fn; state.line = 1; state.ptr = s; state.nexttoken = 0; state.parse_line = parse_line_no_op; //这个函数是空的,就是什么都不做 for (;;) { switch (next_token(&state)) { // 2、 和T_TEXT状态配合,先把把每一行的参数都放在args数组里 case T_EOF: state.parse_line(&state, 0, 0); // 最后看这,到此文件解析完成,也是上一段的完成,需要写个NULL表示末尾。 return; case T_NEWLINE: if (nargs) { int kw = lookup_keyword(args[0]); // 3、得到新的一行,开始解析,判断一下拿到的第一个参数是什么关键字,这里面有几种情,命令COMMAND,段SECTION,和选项OPTION,这个选项是针对服务的,开启,关闭等操作。 if (kw_is(kw, SECTION)) { // 4、判断得到的关键字是不是段,keywords.h里定义了各种能解析的关键字分别是什么属性。 state.parse_line(&state, 0, 0); // 表示上一段解析结束,因为使用的是双向链表,这样就给链表最后一个元素写NULL,表示到末尾了。 parse_new_section(&state, kw, nargs, args); // 5、创建一个新段的链表,比如init段,先跳到这个函数看,然后再回来。 } else { state.parse_line(&state, nargs, args); //10、 得到新的一行,通过上面的操作已经知道现在是在什么段中,是on 还是service,行解析函数也做了相应变化,开始解析这一行,加入action的commands链表中。 } nargs = 0; } break; case T_TEXT: if (nargs < MAXARGS) { args[nargs++] = state.text; } break; } } } void parse_new_section(struct parse_state *state, int kw, int nargs, char **args) { printf("[ %s %s ]\n", args[0], nargs > 1 ? args[1] : ""); switch(kw) { // 6、这里判断 是什么类型的段,不同类型的段使用的解析函数不同,说白了就是分命令还是服务。 case K_service: state->context = parse_service(state, nargs, args); if (state->context) { state->parse_line = parse_line_service; return; } break; case K_on: state->context = parse_action(state, nargs, args); // 7、创建一个action 链表,把这个链表加入到action_list中 if (state->context) { state->parse_line = parse_line_action; // 8、把行解析函数换掉,原来是parse_no_op 什么都不做,再在要把每行都解析成一个命令动作。把这个命令动作加入到action中的commands链表内 return; } break; } state->parse_line = parse_line_no_op; // 9、走到这就是出错了,段的名字没写或者写多了 }


本章小结


经过上面的分析,对/dev/设备权限的修改放在不同的位置会有覆盖的效果,device.c内的修改会覆盖early-init段内的命令,init 段内的命令会覆盖device.c中的修改,如果3个位置都有对用一个设备权限的修改,那init段的修改会最终生效。




### Android 中打开串口 `/dev/ttyS0` 并获取文件描述符 `fd` 在 Android 系统中,可以通过标准的 POSIX 文件 I/O 函数来访问串口设。以下是实现此功能的具体方法以及相关代码示例。 #### 方法概述 为了在 Android 上成功打开串口 `/dev/ttyS0` 并获得文件描述符 `fd`,需要遵循以下原则: 1. 使用 C/C++ 编程语言中的 `open()` 函数来打开指定的串口设。 2. 设置适当的限以便应用程序能够读取和写入该设。 3. 配置串口参数(波特率、数据位数等),这通常通过调用 `termios` 结构体及其关联函数完成。 4. 如果目标平台运行的是 SELinux,则可能还需要调整安全策略以允许应用访问特定路径下的设节点。 下面是具体的实现方式及注意事项: --- #### 示例代码 以下是一个完整的 C 语言程序片段用于演示如何在 Android NDK 下打开 `/dev/ttyS0` 设并设置基本属性: ```c #include <fcntl.h> /* File Control Definitions */ #include <errno.h> /* Error Number Definitions */ #include <termios.h> /* POSIX Terminal Control Definitions */ #include <unistd.h> /* UNIX Standard Definitions */ int open_serial_port(const char *device_path) { int fd; // 尝试打开串口设 if ((fd = open(device_path, O_RDWR | O_NOCTTY | O_NDELAY)) < 0) { perror("Failed to Open Serial Port"); return -1; } struct termios options; // 获取当前配置项副本 tcgetattr(fd, &options); // 清除旧模式标志 cfmakeraw(&options); // 设置波特率为9600bps (可根据需求修改) cfsetispeed(&options, B9600); cfsetospeed(&options, B9600); // 应用新的选项至端口中 if(tcsetattr(fd,TCSANOW,&options)!=0){ close(fd); perror("Error Setting Attributes on Serial Port"); return -1; } return fd; } // 调用例子 void main(){ const char* device="/dev/ttyS0"; int serial_fd=open_serial_port(device); if(serial_fd>=0){ printf("Serial port %s opened successfully with file descriptor:%d\n",device,serial_fd); // 此处可继续执行其他操作... close(serial_fd); }else{ fprintf(stderr,"Could not open serial port %s.\n",device); } } ``` 以上代码实现了以下几个重要部分的功能: - **打开串口**: 利用了带标志位 `O_RDWR`, `O_NOCTTY`, 和 `O_NDELAY` 的 `open()` 来确保即使没有连接硬件也能正常工作[^3]。 - **初始化串口参数**: 借助于 `struct termios` 类型变量存储所需的各种通信特性,并利用辅助宏如 `cfmakeraw()`, `cfsetispeed()`, 及其对应输出版本设定速率等功能完成了初步定制化过程[^1]。 --- #### 关键点解析 - **限管理** 在某些情况下,默认安装的应用无法直接访问位于 `/dev/` 目录下的特殊字符设文件。因此,在开发阶段建议授予调试 APK 更高的限或者手动更改这些资源的所有者组别关系。例如,可以尝试如下命令赋予适当许可限给测试账户: ```bash sudo chmod a+rw /dev/ttyS* ``` 同时注意生产环境中应考虑安全性因素而谨慎处理此类变更行为[^2]. - **错误检测机制** 对每一个关键步骤都加入了必要的状态检验逻辑以防止单一环节失败影响整体流程稳定性。比如当调用 `tcsetattr()` 失败时立即释放已分配资源并通过日志记录具体原因便于后续排查问题所在位置. --- ####
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值