rootcheck

1.问题描述

经常会有听说root手机,其实质就是让使用手机的人获得手机系统的最大权限。因为android系统是基于linux内核开发出来的系统,对不同等级的用户会授予不同的权限,其中权限最大的就是root权限。而rootcheck(Root check mechanism to avoid being rooted)就是是一个手机的保护机制,防止用户因为获得过大的权限而对手机造成“破坏”,该机制的主要作用就是检查手机是否被root过。

2.analysis

  • Root Check功能,用于检测机器是否被用户root,用户root手机有2种方式:

    1. 通过刷机root手机,即替换掉手机了的某些image文件, 从而增/删/改某些功能或模块。
    2. 通过root工具,为用户提供root权限,从而使用户可以以root权限去做某些原本不允许操作的操作。
  • Solution:根据上面用户的2中root方式,所以会通过如下方法来检查用户是否已经root了手机。

    1. 对image进行处理,主要是指在Uboot(U开头/lk.bin)、Boot image(B开头/boot.img)、System image(Y开头/system.img)这三个image的文件头填写标识字符串,如果用户通过刷机root手机,则该写入的标示会被擦除掉(当然一般是默认用户不知道该标识的写入位置和标示内容的,要是用户都知道确实可以跳过此检测方法的)。
    2. 添加root检测进程到手机, 如果发现手机用户有root权限,则该进程就会在某个地方写入标记。在检验事只要发现这个标记存在, 则判定手机被root过。

3.solution

  1. 通过脚本对lk(uboot是早期的lk), boot, system 3个image的头部偏移0x100字节处, 分别写入特定字符. 代码如下:
  1. #!perl -w
  2. my $prj = $ARGV[0];
  3. my $outdir = "out/target/product/$prj";
  4. if($#ARGV == 0) {
  5. &add_magic();
  6. } else {
  7. &usage();
  8. }
  9. sub add_magic {
  10. my $us_offset = 0x100;
  11. my $ls_offset = 0x100;
  12. my $bs_offset = 0x100;
  13. my $ss_offset = 0x100;
  14. my $pattern = "109f10eed3f021e3";
  15. if (-e "$outdir/uboot_$prj.bin") {
  16. open FP, "+<$outdir/uboot_$prj.bin" or die "can't open uboot image!\n";
  17. binmode FP;
  18. seek FP, $us_offset, 0 or die $!;
  19. print FP $pattern;
  20. print "uboot_$prj.bin signed.\n";
  21. close FP;
  22. }
  23. if (-e "$outdir/lk.bin") {
  24. open FP, "+<$outdir/lk.bin" or die "can't open lk image!\n";
  25. binmode FP;
  26. seek FP, $ls_offset, 0 or die $!;
  27. print FP $pattern;
  28. print "lk.bin signed.\n";
  29. close FP;
  30. }
  31. if (-e "$outdir/boot.img") {
  32. open FP, "+<$outdir/boot.img" or die "can't open boot image!\n";
  33. binmode FP;
  34. seek FP, $bs_offset, 0 or die $!;
  35. print FP $pattern;
  36. print "boot.img signed.\n";
  37. close FP;
  38. }
  39. }
  40. sub usage {
  41. print "usage: perl jrd_magic.pl project_name\)n";
  42. exit (0);
  43. }
  1. 创建一个应用,用来检测用户是否有root权限,相关代码如下所示:

    首先,创建一个编译脚本Android.mk
  1. LOCAL_PATH:= $(call my-dir)
  2. include $(CLEAR_VARS)
  3. LOCAL_SRC_FILES := jb.c
  4. LOCAL_CFLAGS += -DDBGMODE=0
  5. LOCAL_MODULE:= forcc
  6. LOCAL_STATIC_LIBRARIES := libcutils
  7. LOCAL_MODULE_TAGS := optional
  8. include $(BUILD_EXECUTABLE)

然后,编写具体的程序代码jb.c如下所示:

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #include <stdint.h>
  5. #include <fcntl.h>
  6. #include <unistd.h>
  7. #include <sys/ioctl.h>
  8. #include <sys/inotify.h>
  9. #include <errno.h>
  10. #include <cutils/properties.h>
  11. #define EVENT_NUM 16
  12. #define MAX_BUF_SIZE 1024
  13. #define MAX_PATH_LEN 255
  14. #define MAGIC_SIZE 16
  15. #define MAGIC_ROOTED "109f10eed3f021e3"
  16. #define MAGIC_OFFSET (2*1024*1024)
  17. #define PROINFO_DEV_NODE "/dev/pro_info"
  18. #define FNUM 4
  19. #define WD_NUM 3
  20. static const char * monitored_folders[] = {
  21. "/sbin",
  22. "/system/bin",
  23. "/system/xbin"
  24. };
  25. struct wd_name {
  26. int wd;
  27. char * name;
  28. };
  29. static const char *g_breaker_filename[] = {
  30. "su",
  31. "Su",
  32. "sU",
  33. "SU"
  34. };
  35. struct wd_name wd_array[WD_NUM];
  36. #ifdef DEBUG
  37. char * event_array[] = {
  38. "File was accessed",
  39. "File was modified",
  40. "File attributes were changed",
  41. "writtable file closed",
  42. "Unwrittable file closed",
  43. "File was opened",
  44. "File was moved from X",
  45. "File was moved to Y",
  46. "Subfile was created",
  47. "Subfile was deleted",
  48. "Self was deleted",
  49. "Self was moved",
  50. "",
  51. "Backing fs was unmounted",
  52. "Event queued overflowed",
  53. "File was ignored"
  54. };
  55. #endif
  56. /*将magic number 写入手机某个节点中,或者从某个节点中读取sz个字符到magic中*/
  57. static int rw_pro_info(const size_t offset, const ssize_t sz,const int rw, char* magic)
  58. {
  59. ssize_t count = 0;
  60. int fid;
  61. if(1 == rw) { /*read command*/
  62. fid = open(PROINFO_DEV_NODE, O_RDONLY);
  63. if(fid < 0){
  64. fprintf(stderr, "can not open file %s\n", PROINFO_DEV_NODE);
  65. goto bail;
  66. }
  67. lseek(fid, offset, SEEK_SET);
  68. count = read(fid, magic, sz);
  69. if(count < sz){
  70. fprintf(stderr, "read magic fails\n");
  71. if(fid > 0)
  72. close(fid);
  73. }
  74. } else { /*write command*/
  75. fid = open(PROINFO_DEV_NODE, O_RDWR|O_SYNC);
  76. if(fid < 0){
  77. fprintf(stderr, "can not open file %s\n", PROINFO_DEV_NODE);
  78. goto bail;
  79. }
  80. lseek(fid, offset, SEEK_SET);
  81. count = write(fid, magic, sz);
  82. if(count < sz){
  83. fprintf(stderr, "write magic fails\n");
  84. if(fid > 0)
  85. close(fid);
  86. }
  87. }
  88. return (count == sz ? 1 : 0);
  89. }
  90. /*分别检测"/sbin","/system/bin","/system/xbin"目录下是否有su这样子的文件,如果有就表示存在被root的风险*/
  91. static int check_su_exists()
  92. {
  93. /*concat pathes, access them*/
  94. size_t sz_files = sizeof(g_breaker_filename)/sizeof(char *);
  95. size_t sz_folders = sizeof(monitored_folders)/sizeof(char *);
  96. int i,j;
  97. char *path = (char *)malloc(sizeof(char) * MAX_PATH_LEN);
  98. int fid;
  99. for(i = 0; i < sz_files; ++i){
  100. for(j = 0; j < sz_folders; ++j){
  101. memset(path, 0, MAX_PATH_LEN);
  102. strncpy(path, monitored_folders[j], strlen(monitored_folders[j]));
  103. strncpy(path + strlen(monitored_folders[j]), "/", 1);
  104. strncpy(path + strlen(monitored_folders[j]) + 1, g_breaker_filename[i],
  105. strlen(g_breaker_filename[i])+1);
  106. #ifdef DEBUG
  107. printf("open file %s\n", path);
  108. #else
  109. fid = open(path, O_RDONLY);
  110. if(fid > 0){
  111. close(fid);
  112. return 1;
  113. }
  114. #endif
  115. }
  116. }
  117. return 0;
  118. }
  119. int main(void)
  120. {
  121. int fd;
  122. int wd;
  123. char buffer[1024];
  124. char * offset = NULL;
  125. struct inotify_event * event;
  126. int len, tmp_len;
  127. char strbuf[16];
  128. int i = 0;
  129. char magic[4];
  130. FILE * rfd =NULL;
  131. int rootflag= 0,count;
  132. /*将去指定位置读数据*/
  133. rw_pro_info(MAGIC_OFFSET, MAGIC_SIZE, 1, &magic[0]);
  134. /*如果该位置已经存在了特定标示 便是已经被root过了*/
  135. if(!strncmp(MAGIC_ROOTED, magic, strlen(MAGIC_ROOTED))) {
  136. property_set("persist.su_flag", "1");
  137. //exit(1); /*already rooted, exit*/
  138. rootflag = 1;
  139. goto EXIT;
  140. } else if(check_su_exists()) {
  141. /*如果存在root风险 将magic number 写入手机某个节点中*/
  142. rw_pro_info(MAGIC_OFFSET, MAGIC_SIZE, 0, MAGIC_ROOTED);
  143. property_set("persist.su_flag", "1");
  144. //exit(1);
  145. rootflag = 1;
  146. goto EXIT;
  147. }
  148. fd = inotify_init();
  149. if (fd < 0) {
  150. printf("Fail to initialize inotify.\n");
  151. exit(-1);
  152. }
  153. for (i=0; i<WD_NUM; i++) {
  154. wd_array[i].name = monitored_folders[i];
  155. wd = inotify_add_watch(fd, wd_array[i].name, IN_MOVED_TO | IN_CREATE);
  156. if (wd < 0) {
  157. printf("Can't add watch for %s.\n", wd_array[i].name);
  158. exit(-1);
  159. }
  160. wd_array[i].wd = wd;
  161. }
  162. while(len = read(fd, buffer, MAX_BUF_SIZE)) {
  163. offset = buffer;
  164. //printf("Some event happens, len = %d.\n", len);
  165. event = (struct inotify_event *)buffer;
  166. while (((char *)event - buffer) < len) {
  167. #ifdef DEBUG
  168. if (event->mask & IN_ISDIR) {
  169. memcpy(strbuf, "Direcotory", 11);
  170. }
  171. else {
  172. memcpy(strbuf, "File", 5);
  173. }
  174. printf("Object type: %s\n", strbuf);
  175. for (i=0; i<WD_NUM; i++) {
  176. if (event->wd != wd_array[i].wd) continue;
  177. printf("Object name: %s\n", wd_array[i].name);
  178. break;
  179. }
  180. printf("Event mask: %08X\n", event->mask);
  181. printf("Event name: %s\n", event->name);
  182. for (i=0; i<EVENT_NUM; i++) {
  183. if (event_array[i][0] == '\0') continue;
  184. if (event->mask & (1<<i)) {
  185. printf("Event: %s\n", event_array[i]);
  186. }
  187. }
  188. #endif
  189. for(i=0; i<FNUM; i++){
  190. if(0 == strcmp(event->name, g_breaker_filename[i])){
  191. rw_pro_info(MAGIC_OFFSET, MAGIC_SIZE, 0, MAGIC_ROOTED);
  192. property_set("persist.su_flag", "1");
  193. //exit(1);
  194. rootflag = 1;
  195. goto EXIT;
  196. }
  197. }
  198. tmp_len = sizeof(struct inotify_event) + event->len;
  199. event = (struct inotify_event *)(offset + tmp_len);
  200. offset += tmp_len;
  201. }
  202. }
  203. EXIT:
  204. if(rootflag == 1){
  205. rfd = fopen("/data/rootflag", "w");
  206. if(rfd == NULL){
  207. fprintf(stderr, "can not open file %s\n", "/data/rootflag");
  208. exit(-1);
  209. }
  210. count = fwrite(magic, 1, MAGIC_SIZE,rfd);
  211. if(count < MAGIC_SIZE){
  212. fprintf(stderr, "write magic fails\n");
  213. fclose(rfd);
  214. exit(-1);
  215. }
  216. fclose(rfd);
  217. chmod("/data/rootflag", 0644);
  218. }
  219. return 0;
  220. }
  1. 进过上面的修改后,其实这个应用还是不能生效,其原因就是还没为该应用配置selinux权限。其具体配置如下:

    在目录device/mediatek/common/sepolicy/full/目录先为forcc应用创建域

  1. # forcc
  2. type forcc, domain;
  3. type forcc_exec, exec_type, file_type;
  4. init_daemon_domain(forcc)
  5. #定义访问规则
  6. allow forcc nvram_device:blk_file { open read write };
  7. allow forcc block_device:dir search;
  8. allow forcc su_exec:file { open read };
  9. allow forcc rootfs:dir read;
  10. allow forcc system_file:dir read;

修改文件mediatek/common/sepolicy/full/file_contexts,定义/system/bin/forcc文件的type,具体如下:

  1. /system/bin/forcc u:object_r:forcc_exec:s0

进过上面的修改,这个应用就基本ok了。

4.总结

这个问题只要理解了原理,其实也不难。就2个点,第一个点,向boot, system 3个image特定位置写标示,主要由pythen完成;第二个点,检测文件标识是否被修改以及判断设备中是否有su进程。在整个开发过程中可能selinux会麻烦点,反正做的时候,访问规则都是一条一条的加,只要小心点也一切ok 。

rootckeck的更多相关文章

随机推荐

  1. [BUUCTF]REVERSE——[MRCTF2020]hello_world_go

    [MRCTF2020]hello_world_go 附件 步骤: 例行检查,64位程序,无壳 64位ida载入,检索程序里的字符串,有很多,直接检索flag 一个一个点过去,找到了flag 按a,提取 ...

  2. Windows异常分发

    当有异常发生时,CPU会通过IDT表找到异常处理函数,即内核中的KiTrapXX系列函数,然后转去执行.但是,KiTrapXX函数通常只是对异常做简单的表征和描述,为了支持调试和软件自己定义的异常处理 ...

  3. 自定义日历(Project)

    <Project2016 企业项目管理实践>张会斌 董方好 编著 日历有三种:标准日历.24小时日历和夜班日历. 但这三种在现实中远远不够用,别的不说,就说那个标准日历,默认是8点到12点 ...

  4. AT266 迷子のCDケース 题解

    Content 有 \(n+1\) 个碟,编号为 \(0\sim n\),一开始 \(0\) 号碟在播放机上,其他的碟依次放进了 \(n\) 个盒子里面.现在有 \(m\) 次操作,每次操作找到当前在 ...

  5. java 输入输出IO流:标准输入/输出System.in;System.out;System.err;【重定向输入System.setIn(FileinputStream);输出System.setOut(printStream);】

    Java的标准输入输出分别通过System.in和System.out来代表的,在默认情况下它分别代表键盘和显示器,当程序通过System.in来获取输入时,实际上是从键盘读取输入 当程序试图通过 S ...

  6. 『学了就忘』Linux日志管理 — 90、Linux中日志介绍

    目录 1.日志相关服务 2.系统中常见的日志文件 1.日志相关服务 在CentOS 6.x中日志服务已经由rsyslogd取代了原先的syslogd服务.RedHat认为syslogd已经不能满足在工 ...

  7. 如何把Electron做成一个Runtime,让多个应用共享同一个Electron

    这个问题涉及到很多知识,而且要想把这个Runtime做好很绕. 下面我就说一下我的思路:(以下内容以Windows平台为基础,Mac平台和Linux平台还得去调查一下,才能确定是否可行) 首先,我们先 ...

  8. Spring Boot应用程序启动器

    官网地址:https://docs.spring.io/spring-boot/docs/2.1.12.RELEASE/reference/html/using-boot-build-systems. ...

  9. JAVA验证身份证号码是否正确

    package com.IdCard; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.D ...

  10. 【LeetCode】104. Maximum Depth of Binary Tree 解题报告(Python)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 方法一:BFS 方法二:DFS 参考资料 日期 题目 ...