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——相册

    相册 附件 步骤: apk文件,习惯用apkide打开,看它反编译成了jar,就换jadx-gui打开,题目提示找邮箱,因此在导航栏里搜索mail 看到了sendMailByJavaMail(java ...

  2. [BUUCTF]PWN4——pwn1_sctf_2016

    [BUUCTF]PWN4--pwn1_sctf_2016 题目网址:https://buuoj.cn/challenges#pwn1_sctf_2016 步骤: 例行检查,32位,开启nx(堆栈不可执 ...

  3. MySQL 创建定时任务 详解

    自 MySQL5.1.6起,增加了一个非常有特色的功能–事件调度器(Event Scheduler),可以用做定时执行某些特定任务,来取代原先只能由操作系统的计划任务来执行的工作.事件调度器有时也可称 ...

  4. .NET6中一些常用组件的配置及使用记录,持续更新中。。。

    NET6App 介绍 .NET 6的CoreApp框架,用来学习.NET6的一些变动和新特性,使用EFCore,等一系列组件的运用,每个用单独的文档篇章记录,持续更新文档哦. 如果对您有帮助,点击右上 ...

  5. Django常用的QuerySet操作

    在这里我根据是否支持链式调用分类进行介绍 1. 支持链式调用的接口 all 使用频率比较高,相当于SELECT * FROM table 语句,用于查询所有数据. filter 使用频率比较高,根据条 ...

  6. LuoguB2101 计算矩阵边缘元素之和 题解

    Content 给定一个 \(m\times n\) 的矩阵,求矩阵边缘元素之和. 数据范围:\(1\leqslant m,n\leqslant 100\). Solution 对于新手来说,看到这题 ...

  7. 你假笨JVM参数 - 1 CMSScavengeBeforeRemark

    参数:-XX:CMSScavengeBeforeRemark含义:Enable scavenging attempts before the CMS remark step.开启或关闭在CMS重新标记 ...

  8. Tornado WEB服务器框架 Epoll

    引言: 回想Django的部署方式 以Django为代表的python web应用部署时采用wsgi协议与服务器对接(被服务器托管),而这类服务器通常都是基于多线程的,也就是说每一个网络请求服务器都会 ...

  9. 四、Uniapp+vue+腾讯IM+腾讯音视频开发仿微信的IM聊天APP,支持各类消息收发,音视频通话,附vue实现源码(已开源)-会话好友列表的实现

    会话好友列表的实现 1.项目引言 2.腾讯云后台配置TXIM 3.配置项目并实现IM登录 4.会话好友列表的实现 5.聊天输入框的实现 6.聊天界面容器的实现 7.聊天消息项的实现 8.聊天输入框扩展 ...

  10. Miniconda入门教程

    Miniconda 教程 介绍 Anaconda指的是一个开源的Python发行版本,其包含了conda.Python等180多个科学包及其依赖项.因为包含了大量的科学包,Anaconda 的下载文件 ...