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

本文深入探讨了Android系统中为应用程序提权以访问特定设备文件的方法。主要介绍了通过修改init.rc脚本或device.c源码实现对/dev目录下设备文件权限调整的技术细节。
为Android应用程序读取/dev下设备而提权(一)

         倘若应用程序需要对/dev/xxx进行读写操作,就需要提升其权限。提权方法不唯一,需要根据具体需求情况而选择。归根结底,终究都落到chmod 777 /dev/xxx 上,不同的是,chmod操作被执行在何时何地,在此做个分析总结。
        内核启动后会执行/system/init,传说中的系统1号进程,init程序起初的任务是初始化,包括各种mkdir来构建文件系统,得到硬件信息建立设备节点,安装SIGCHLD信号来回收僵尸进程的资源,解析init.rc启动脚本等等,然后init程序变身为property_service来管理系统的权限。我们可以下手的地方有两处: device_init和init.rc



✿ init.rc

   这个方案是大家用的比较多的,在其中添加chmod操作很简单不多说。


✿ device.c

   这个方案用的比较少,先了解下device.c。
   device_init在/system/core/init/device.c中,详细分析如下:

  1. //分别遍历/sys/class /sys/block /sys/devices  
  2. device_init()  
  3. {  
  4.   coldboot(fd,"/sys/class");  
  5.   coldboot(fd,"/sys/block");  
  6.   coldboot(fd,"/sys/devices");  
  7. }  
  8.      
  9. //后面有个递归 /sys下是内核生成的设备,这就相当于udev的作用  
  10. do_coldboot()  
  11. {  
  12.   if(fd>= 0) {  
  13.   write(fd,"add\n", 4);  
  14.   close(fd);  
  15.   handle_device_fd(event_fd);  
  16.   }  
  17. }  
  18.   
  19. //从socket里读出add处理  
  20. handle_device_fd()   
  21. {  
  22.   if(!strcmp(uevent->action,"add")) {  
  23.   make_device(devpath,block, uevent->major, uevent->minor);  
  24.   return;  
  25.   }  
  26. }  
  27.   
  28. //得到设备的相关信息创建设备节点  
  29. make_device()   
  30. {  
  31.   mode= get_device_perm(path,&uid, &gid) | (block ? S_IFBLK : S_IFCHR);  
  32.   dev= (major << 8) | minor;  
  33.   mknod(path,mode, dev);  
  34.   chown(path,uid, gid);  
  35. }  
  36.   
  37. get_device_perm()  
  38. {  
  39.   if(get_device_perm_inner(qemu_perms, path, uid, gid, &perm) == 0) {  
  40.   returnperm;  
  41.   }elseif(get_device_perm_inner(devperms,path, uid, gid, &perm) == 0){  
  42.    returnperm;  
  43.   }else{  
  44.                 …….  
  45. }  
  46.   
  47. //得到devperms结构体的信息  
  48. get_device_perm_inner  
  49. {  
  50.  for(i= 0; perms[i].name; i++) {  
  51.   
  52.  if(perms[i].prefix){  
  53.  if(strncmp(path,perms[i].name, strlen(perms[i].name)))  
  54.  continue;  
  55.  }else{  
  56.  if(strcmp(path,perms[i].name))  
  57.  continue;  
  58.  }  
  59.  *uid= perms[i].uid;  
  60.  *gid= perms[i].gid;  
  61.  *perm= perms[i].perm; //权限位  
  62.  return0;  
  63.  }  
  64. }  
  65.   
  66. 这是devperms的具体内容  
  67. structperms_ {  
  68.    char*name;  
  69.    mode_tperm;  
  70.    unsignedintuid;  
  71.    unsignedintgid;  
  72.    unsignedshortprefix;  
  73. };  
  74. staticstructperms_ devperms[] = {  
  75. {"/dev/null", 0666, AID_ROOT, AID_ROOT, 0 },  
  76. {"/dev/zero", 0666, AID_ROOT, AID_ROOT, 0 },  
  77. {"/dev/full", 0666, AID_ROOT, AID_ROOT, 0 },  
  78. {"/dev/ptmx", 0666, AID_ROOT, AID_ROOT, 0 },  
  79. {"/dev/tty", 0666, AID_ROOT, AID_ROOT, 0 },  
  80. {"/dev/random", 0666, AID_ROOT, AID_ROOT, 0 },  
  81. {"/dev/urandom", 0666, AID_ROOT, AID_ROOT, 0 },  
  82. {"/dev/ashmem", 0666, AID_ROOT, AID_ROOT, 0 },  
  83. {"/dev/binder", 0666, AID_ROOT, AID_ROOT, 0 },  
  84.   
  85. /* logger should be world writable (for logging) but not readable*/  
  86. {"/dev/log/", 0662, AID_ROOT, AID_LOG, 1 },  
  87.   
  88. /*these should not be world writable */  
  89. {"/dev/android_adb", 0660, AID_ADB, AID_ADB, 0 },  
  90. {"/dev/android_adb_enable", 0660, AID_ADB, AID_ADB, 0 },  
  91. {"/dev/ttyMSM0", 0660, AID_BLUETOOTH, AID_BLUETOOTH, 0 },  
  92. {"/dev/alarm", 0664, AID_SYSTEM, AID_RADIO, 0 },  
  93. {"/dev/tty0", 0666, AID_ROOT, AID_SYSTEM, 0 },  
  94. {"/dev/graphics/", 0660, AID_ROOT, AID_GRAPHICS, 1 },  
  95. {"/dev/hw3d", 0660, AID_SYSTEM, AID_GRAPHICS, 0 },  
  96. {"/dev/input/", 0660, AID_ROOT, AID_INPUT, 1 },  
  97. {"/dev/eac", 0660, AID_ROOT, AID_AUDIO, 0 },  
  98. {"/dev/cam", 0660, AID_ROOT, AID_CAMERA, 0 },  
  99. {"/dev/pmem", 0660, AID_SYSTEM, AID_GRAPHICS, 0 },  
  100. {"/dev/pmem_gpu", 0660, AID_SYSTEM, AID_GRAPHICS, 1 },  
  101. {"/dev/pmem_adsp", 0660, AID_SYSTEM, AID_AUDIO, 1 },  
  102. {"/dev/pmem_camera", 0660, AID_SYSTEM, AID_CAMERA, 1 },  
  103. {"/dev/oncrpc/", 0660, AID_ROOT, AID_SYSTEM, 1 },  
  104. {"/dev/adsp/", 0660, AID_SYSTEM, AID_AUDIO, 1 },  
  105. {"/dev/mt9t013", 0660, AID_SYSTEM, AID_SYSTEM, 0 },  
  106. {"/dev/akm8976_daemon",0640, AID_COMPASS, AID_SYSTEM, 0 },  
  107. {"/dev/akm8976_aot", 0640, AID_COMPASS, AID_SYSTEM, 0 },  
  108. {"/dev/akm8976_pffd", 0640, AID_COMPASS, AID_SYSTEM, 0 },  
  109. {"/dev/msm_pcm_out", 0660, AID_SYSTEM, AID_AUDIO, 1 },  
  110. {"/dev/msm_pcm_in", 0660, AID_SYSTEM, AID_AUDIO, 1 },  
  111. {"/dev/msm_pcm_ctl", 0660, AID_SYSTEM, AID_AUDIO, 1 },  
  112. {"/dev/msm_mp3", 0660, AID_SYSTEM, AID_AUDIO, 1 },  
  113. {"/dev/smd0", 0640, AID_RADIO, AID_RADIO, 0 },  
  114. {"/dev/qmi", 0640, AID_RADIO, AID_RADIO, 0 },  
  115. {"/dev/qmi0", 0640, AID_RADIO, AID_RADIO, 0 },  
  116. {"/dev/qmi1", 0640, AID_RADIO, AID_RADIO, 0 },  
  117. {"/dev/qmi2", 0640, AID_RADIO, AID_RADIO, 0 },  
  118. {"/dev/htc-acoustic", 0640, AID_RADIO, AID_RADIO, 0 },  
  119. {NULL, 0, 0, 0, 0 },  
  120. };  

✿ init.c

       init.rc脚本和老版本android中的init.goldfish.rc脚本很早就被parse_config_file()函数解析将脚本内容分为几个段,early-init,init,early-boot,boot,和各个服务。然后在不同的时间点上执行各个段得命令或者开启各种服务。

   
init.c的一段节选:
  
  1. int main(intargc, char**argv)  
  2. {  
  3.   ……  
  4.   mkdir("/dev",0755);   
  5.   mkdir("/proc",0755);  
  6.   mkdir("/sys",0755);  
  7.   mount("tmpfs","/dev""tmpfs", 0, "mode=0755");  
  8.   mkdir("/dev/pts",0755);  
  9.   mkdir("/dev/socket",0755);  
  10.   mount("devpts","/dev/pts""devpts", 0, NULL);  
  11.   mount("proc","/proc""proc", 0, NULL);  
  12.   mount("sysfs","/sys""sysfs", 0, NULL);  
  13.   … …  
  14.   INFO("readingconfig file\n");  
  15.   parse_config_file("/init.rc");   
  16. //调用parse_config解析init.rc脚本  
  17. //经过解析,init.rc的内容就被分为多少个段,被串在action_list链表中。  
  18. //on开头的都是action类型的段,比如init段,init段用一个结构体struct action表示,其中name是init,  
  19. //所有这个段内的命令,都被串在commands链表中。  
  20.   
  21.   action_for_each_trigger("early-init",action_add_queue_tail);   
  22. //遍历action_list链表,查找name是early-init的那个action,将这个节点放在action_queue的尾部。  
  23.   drain_action_queue();  
  24. //将action_queue尾部的节点遍历,然后删除。  
  25. //就相当于遍历name是early-init的action节点内的commands链表。  
  26. //就是在执行init.rc脚本中onearly-init段内的所有命令。  
  27.   
  28. ……  
  29.   INFO("deviceinit\n");  
  30.   device_fd= device_init(); //常见必要的设备节点  
  31.   
  32.   property_init();//init 以后的任务就是proper_service  
  33.   
  34.   action_for_each_trigger("init",action_add_queue_tail); //将init段,加入action_queue  
  35.   drain_action_queue();// 执行init段得命令  
  36.   
  37. … …  
  38. }  

✿ 本节小结

       device_init其实就是linux中的udev的一个简单的替代。把/sys/下的所有内核提供的设备都安排在/dev下创建设备节点。如果要改动/dev/一些设备的权限,可以把chmod 777写在init.rc中,但是要注意写的位置,不能太早执行,不能写在early-init段内,因为那时/dev/下的设备节点还没有被创建。
      在devices.c中修改的方法隐藏的较深不容易被发现,但是如果init.rc内再次修改就可能把之前的修改覆盖掉。


<think> 我们需要注意的是,用户实际上想要操作的是"/dev/mfd"设备,但通常这类设备是内存映射设备(Memory-Mapped Device)。然而,在Android中,直接操作物理内存的设备节点般是"/dev/mem"。不过,根据用户需求,我们假设"/dev/mfd"是个类似的设备,用于访问特定的内存区域。 由于写入64MB数据并校验,我们需要注意以下几点: 1. 需要打开设备文件。 2. 使用mmap将设备内存映射到用户空间。 3. 写入数据(这里我们生成个64MB的测试数据)。 4. 将数据写入设备,然后读取回来校验。 但是,请注意: - 操作设备内存通常需要root限。 - 必须知道要映射的物理地址(这里假设为0x10000000,用户需要根据实际情况修改)。 - 64MB数据较大,需要确保设备内存区域足够且可读写。 以下是个完整的示例代码,包括JNI部分和Java调用部分。 ### 步骤1:配置CMakeLists.txt ```cmake cmake_minimum_required(VERSION 3.4.1) add_library( mfd_operation SHARED src/main/cpp/mfd_operation.cpp) find_library( log-lib log ) target_link_libraries( mfd_operation ${log-lib} ) ``` ### 步骤2:C++实现(mfd_operation.cpp) ```cpp #include <jni.h> #include <string> #include <unistd.h> #include <sys/mman.h> #include <fcntl.h> #include <cstring> #include <android/log.h> #define LOG_TAG "MFD_Operation" #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) #define DATA_SIZE (64 * 1024 * 1024) // 64MB // 假设我们要映射的物理地址(根据实际情况修改) #define TARGET_ADDRESS 0x10000000 extern "C" JNIEXPORT jboolean JNICALL Java_com_example_mfdoperation_MainActivity_writeAndVerifyMFD( JNIEnv *env, jobject /* this */) { int fd = open("/dev/mfd", O_RDWR | O_SYNC); if (fd < 0) { LOGE("Failed to open /dev/mfd: %s", strerror(errno)); return JNI_FALSE; } // 映射物理地址 void* mapped = mmap(0, DATA_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, TARGET_ADDRESS); if (mapped == MAP_FAILED) { LOGE("mmap failed: %s", strerror(errno)); close(fd); return JNI_FALSE; } // 生成测试数据(64MB) auto *testData = new (std::nothrow) unsigned char[DATA_SIZE]; if (!testData) { LOGE("Failed to allocate memory for test data"); munmap(mapped, DATA_SIZE); close(fd); return JNI_FALSE; } // 填充测试数据(这里用0xAA填充) memset(testData, 0xAA, DATA_SIZE); // 写入数据 memcpy(mapped, testData, DATA_SIZE); LOGI("Data written."); // 验证数据 auto success = true; for (size_t i = 0; i < DATA_SIZE; i++) { // 分段校验,避免次性校验64MB导致卡顿,这里每次校验1MB if (i % (1024*1024) == 0) { if (memcmp((unsigned char*)mapped + i, testData + i, 1024*1024) != 0) { LOGE("Verification failed at block %zu", i / (1024*1024)); success = false; break; } } } // 清理 delete[] testData; munmap(mapped, DATA_SIZE); close(fd); return success ? JNI_TRUE : JNI_FALSE; } ``` ### 步骤3:Java调用 ```java package com.example.mfdoperation; import androidx.appcompat.app.AppCompatActivity; import android.os.Bundle; import android.widget.Toast; public class MainActivity extends AppCompatActivity { static { System.loadLibrary("mfd_operation"); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); new Thread(() -> { boolean result = writeAndVerifyMFD(); runOnUiThread(() -> Toast.makeText(MainActivity.this, result ? "操作成功" : "操作失败", Toast.LENGTH_LONG).show()); }).start(); } public native boolean writeAndVerifyMFD(); } ``` ### 步骤4:AndroidManifest.xml 添加限 ```xml <uses-permission android:name="android.permission.ACCESS_MEMORY_DEVICE"/> ``` ### 注意事项: 1. **限问题**:操作`/dev/mfd`需要root限,或者设备本身开放了该节点的读写限给应用程序。在非root设备上,可能需要系统签名。 2. **物理地址**:`TARGET_ADDRESS`需要根据实际硬件确定,不能随意设置。 3. **内存映射大小**:64MB较大,确保目标物理地址区域足够大,避免映射到其他设备内存区域。 4. **性能**:在循环校验时,我们分段校验(每1MB校验次),避免长时间占用CPU导致ANR。 ### 相关问题: 1. 如何在Android中获取root限来操作设备节点? 2. 如何确定硬件设备的内存映射物理地址? 3. 如果写入的数据量很大,如何优化写入和校验的速度? 4. 除了`/dev/mfd`,还有哪些设备节点可以用于内存映射操作? 请根据实际硬件情况调整物理地址和限设置。 </think> ### Android C++ 操作 /dev/mfd 设备写入 64MB 数据并校验的代码示例 以下是个完整的 Android NDK 实现,用于操作 `/dev/mfd` 设备(Memory-Mapped Device)写入 64MB 数据并进行校验的解决方案: #### 1. C++ 核心实现 (mfd_operation.cpp) ```cpp #include <jni.h> #include <fcntl.h> #include <unistd.h> #include <sys/mman.h> #include <cstring> #include <cerrno> #include <android/log.h> #define TAG "MFD_Operation" #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__) #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__) constexpr size_t DATA_SIZE = 64 * 1024 * 1024; // 64MB constexpr off_t MFD_OFFSET = 0; // 设备偏移量 extern "C" JNIEXPORT jboolean JNICALL Java_com_example_mfdoperation_MainActivity_writeAndVerify( JNIEnv* env, jobject thiz) { // 1. 打开mfd设备 int fd = open("/dev/mfd", O_RDWR | O_SYNC); if (fd < 0) { LOGE("打开设备失败: %s", strerror(errno)); return JNI_FALSE; } LOGD("设备打开成功,fd=%d", fd); // 2. 内存映射 void* mfd_map = mmap(0, DATA_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, MFD_OFFSET); if (mfd_map == MAP_FAILED) { LOGE("内存映射失败: %s", strerror(errno)); close(fd); return JNI_FALSE; } LOGD("内存映射成功,地址: %p", mfd_map); // 3. 生成测试数据 uint8_t* test_data = new (std::nothrow) uint8_t[DATA_SIZE]; if (!test_data) { LOGE("内存分配失败"); munmap(mfd_map, DATA_SIZE); close(fd); return JNI_FALSE; } // 填充模式数据(0xAA, 0x55交替) for (size_t i = 0; i < DATA_SIZE; i += 2) { test_data[i] = 0xAA; test_data[i + 1] = 0x55; } // 4. 写入数据(分段写入避免卡顿) bool write_success = true; const size_t CHUNK_SIZE = 1 * 1024 * 1024; // 1MB分块 for (size_t offset = 0; offset < DATA_SIZE; offset += CHUNK_SIZE) { size_t chunk = (DATA_SIZE - offset) < CHUNK_SIZE ? (DATA_SIZE - offset) : CHUNK_SIZE; memcpy((uint8_t*)mfd_map + offset, test_data + offset, chunk); } if (!write_success) { LOGE("数据写入失败"); delete[] test_data; munmap(mfd_map, DATA_SIZE); close(fd); return JNI_FALSE; } LOGD("64MB数据写入完成"); // 5. 校验数据(分段校验减少内存压力) bool verify_ok = true; for (size_t offset = 0; offset < DATA_SIZE; offset += CHUNK_SIZE) { size_t chunk = (DATA_SIZE - offset) < CHUNK_SIZE ? (DATA_SIZE - offset) : CHUNK_SIZE; if (memcmp((uint8_t*)mfd_map + offset, test_data + offset, chunk) != 0) { LOGE("数据校验失败 @ offset=0x%zx", offset); verify_ok = false; break; } } // 6. 清理资源 delete[] test_data; munmap(mfd_map, DATA_SIZE); close(fd); return verify_ok ? JNI_TRUE : JNI_FALSE; } ``` #### 2. Java 调用接口 (MainActivity.java) ```java package com.example.mfdoperation; import androidx.appcompat.app.AppCompatActivity; import android.os.Bundle; import android.widget.Toast; public class MainActivity extends AppCompatActivity { static { System.loadLibrary("mfd_operation"); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); new Thread(() -> { boolean result = writeAndVerify(); runOnUiThread(() -> { String msg = result ? "✅ 操作成功:数据写入并校验通过" : "❌ 操作失败:数据校验未通过"; Toast.makeText(this, msg, Toast.LENGTH_LONG).show(); }); }).start(); } public native boolean writeAndVerify(); } ``` #### 3. CMake 配置 (CMakeLists.txt) ```cmake cmake_minimum_required(VERSION 3.18.1) project("mfd_operation") add_library( mfd_operation SHARED src/main/cpp/mfd_operation.cpp ) find_library(log-lib log) target_link_libraries( mfd_operation ${log-lib} ) ``` #### 4. AndroidManifest.xml 限配置 ```xml <manifest ...> <!-- 添加必要限 --> <uses-permission android:name="android.permission.ACCESS_MEMORY_DEVICE"/> <application ...> ... </application> </manifest> ``` ### 关键实现说明 1. **设备访问优化**: - 使用 `O_SYNC` 标志确保直接写入设备内存 - 通过 `mmap()` 将设备内存映射到用户空间 - 分段处理避免次性操作大内存(64MB) $$ \text{分段大小} = \min(\text{剩余数据量}, 1\text{MB}) $$ 2. **数据校验机制**: - 使用模式数据(0xAA/0x55交替)高错误检测率 - 分段校验减少内存峰值压力 - 错误定位精确到具体偏移位置 3. **内存安全**: - 使用 `std::nothrow` 处理大内存分配失败 - RAII 风格确保资源释放(munmap/close) - 每步操作均有错误日志输出 ### 设备访问注意事项 1. **限要求**: ```bash adb shell chmod 666 /dev/mfd # 开发阶段临时授 ``` 生产环境需系统签名或内核配置支持[^1] 2. **物理地址对齐**: - `MFD_OFFSET` 必须按页对齐(通常4KB) - 可通过 `/proc/iomem` 查询设备物理地址范围: ```bash adb shell cat /proc/iomem | grep mfd ``` 3. **Android 11+ 适配**: ```cpp // 使用ashmem作为后方案 int fd = ashmem_create_region("mfd_emulation", DATA_SIZE); ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值