rootcheck
1.问题描述
经常会有听说root手机,其实质就是让使用手机的人获得手机系统的最大权限。因为android系统是基于linux内核开发出来的系统,对不同等级的用户会授予不同的权限,其中权限最大的就是root权限。而rootcheck(Root check mechanism to avoid being rooted)就是是一个手机的保护机制,防止用户因为获得过大的权限而对手机造成“破坏”,该机制的主要作用就是检查手机是否被root过。
2.analysis
Root Check功能,用于检测机器是否被用户root,用户root手机有2种方式:
- 通过刷机root手机,即替换掉手机了的某些image文件, 从而增/删/改某些功能或模块。
- 通过root工具,为用户提供root权限,从而使用户可以以root权限去做某些原本不允许操作的操作。
Solution:根据上面用户的2中root方式,所以会通过如下方法来检查用户是否已经root了手机。
- 对image进行处理,主要是指在Uboot(U开头/lk.bin)、Boot image(B开头/boot.img)、System image(Y开头/system.img)这三个image的文件头填写标识字符串,如果用户通过刷机root手机,则该写入的标示会被擦除掉(当然一般是默认用户不知道该标识的写入位置和标示内容的,要是用户都知道确实可以跳过此检测方法的)。
- 添加root检测进程到手机, 如果发现手机用户有root权限,则该进程就会在某个地方写入标记。在检验事只要发现这个标记存在, 则判定手机被root过。
3.solution
- 通过脚本对lk(uboot是早期的lk), boot, system 3个image的头部偏移0x100字节处, 分别写入特定字符. 代码如下:
#!perl -w
my $prj = $ARGV[0];
my $outdir = "out/target/product/$prj";
if($#ARGV == 0) {
&add_magic();
} else {
&usage();
}
sub add_magic {
my $us_offset = 0x100;
my $ls_offset = 0x100;
my $bs_offset = 0x100;
my $ss_offset = 0x100;
my $pattern = "109f10eed3f021e3";
if (-e "$outdir/uboot_$prj.bin") {
open FP, "+<$outdir/uboot_$prj.bin" or die "can't open uboot image!\n";
binmode FP;
seek FP, $us_offset, 0 or die $!;
print FP $pattern;
print "uboot_$prj.bin signed.\n";
close FP;
}
if (-e "$outdir/lk.bin") {
open FP, "+<$outdir/lk.bin" or die "can't open lk image!\n";
binmode FP;
seek FP, $ls_offset, 0 or die $!;
print FP $pattern;
print "lk.bin signed.\n";
close FP;
}
if (-e "$outdir/boot.img") {
open FP, "+<$outdir/boot.img" or die "can't open boot image!\n";
binmode FP;
seek FP, $bs_offset, 0 or die $!;
print FP $pattern;
print "boot.img signed.\n";
close FP;
}
}
sub usage {
print "usage: perl jrd_magic.pl project_name\)n";
exit (0);
}
- 创建一个应用,用来检测用户是否有root权限,相关代码如下所示:
首先,创建一个编译脚本Android.mk
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_SRC_FILES := jb.c
LOCAL_CFLAGS += -DDBGMODE=0
LOCAL_MODULE:= forcc
LOCAL_STATIC_LIBRARIES := libcutils
LOCAL_MODULE_TAGS := optional
include $(BUILD_EXECUTABLE)
然后,编写具体的程序代码jb.c如下所示:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/inotify.h>
#include <errno.h>
#include <cutils/properties.h>
#define EVENT_NUM 16
#define MAX_BUF_SIZE 1024
#define MAX_PATH_LEN 255
#define MAGIC_SIZE 16
#define MAGIC_ROOTED "109f10eed3f021e3"
#define MAGIC_OFFSET (2*1024*1024)
#define PROINFO_DEV_NODE "/dev/pro_info"
#define FNUM 4
#define WD_NUM 3
static const char * monitored_folders[] = {
"/sbin",
"/system/bin",
"/system/xbin"
};
struct wd_name {
int wd;
char * name;
};
static const char *g_breaker_filename[] = {
"su",
"Su",
"sU",
"SU"
};
struct wd_name wd_array[WD_NUM];
#ifdef DEBUG
char * event_array[] = {
"File was accessed",
"File was modified",
"File attributes were changed",
"writtable file closed",
"Unwrittable file closed",
"File was opened",
"File was moved from X",
"File was moved to Y",
"Subfile was created",
"Subfile was deleted",
"Self was deleted",
"Self was moved",
"",
"Backing fs was unmounted",
"Event queued overflowed",
"File was ignored"
};
#endif
/*将magic number 写入手机某个节点中,或者从某个节点中读取sz个字符到magic中*/
static int rw_pro_info(const size_t offset, const ssize_t sz,const int rw, char* magic)
{
ssize_t count = 0;
int fid;
if(1 == rw) { /*read command*/
fid = open(PROINFO_DEV_NODE, O_RDONLY);
if(fid < 0){
fprintf(stderr, "can not open file %s\n", PROINFO_DEV_NODE);
goto bail;
}
lseek(fid, offset, SEEK_SET);
count = read(fid, magic, sz);
if(count < sz){
fprintf(stderr, "read magic fails\n");
if(fid > 0)
close(fid);
}
} else { /*write command*/
fid = open(PROINFO_DEV_NODE, O_RDWR|O_SYNC);
if(fid < 0){
fprintf(stderr, "can not open file %s\n", PROINFO_DEV_NODE);
goto bail;
}
lseek(fid, offset, SEEK_SET);
count = write(fid, magic, sz);
if(count < sz){
fprintf(stderr, "write magic fails\n");
if(fid > 0)
close(fid);
}
}
return (count == sz ? 1 : 0);
}
/*分别检测"/sbin","/system/bin","/system/xbin"目录下是否有su这样子的文件,如果有就表示存在被root的风险*/
static int check_su_exists()
{
/*concat pathes, access them*/
size_t sz_files = sizeof(g_breaker_filename)/sizeof(char *);
size_t sz_folders = sizeof(monitored_folders)/sizeof(char *);
int i,j;
char *path = (char *)malloc(sizeof(char) * MAX_PATH_LEN);
int fid;
for(i = 0; i < sz_files; ++i){
for(j = 0; j < sz_folders; ++j){
memset(path, 0, MAX_PATH_LEN);
strncpy(path, monitored_folders[j], strlen(monitored_folders[j]));
strncpy(path + strlen(monitored_folders[j]), "/", 1);
strncpy(path + strlen(monitored_folders[j]) + 1, g_breaker_filename[i],
strlen(g_breaker_filename[i])+1);
#ifdef DEBUG
printf("open file %s\n", path);
#else
fid = open(path, O_RDONLY);
if(fid > 0){
close(fid);
return 1;
}
#endif
}
}
return 0;
}
int main(void)
{
int fd;
int wd;
char buffer[1024];
char * offset = NULL;
struct inotify_event * event;
int len, tmp_len;
char strbuf[16];
int i = 0;
char magic[4];
FILE * rfd =NULL;
int rootflag= 0,count;
/*将去指定位置读数据*/
rw_pro_info(MAGIC_OFFSET, MAGIC_SIZE, 1, &magic[0]);
/*如果该位置已经存在了特定标示 便是已经被root过了*/
if(!strncmp(MAGIC_ROOTED, magic, strlen(MAGIC_ROOTED))) {
property_set("persist.su_flag", "1");
//exit(1); /*already rooted, exit*/
rootflag = 1;
goto EXIT;
} else if(check_su_exists()) {
/*如果存在root风险 将magic number 写入手机某个节点中*/
rw_pro_info(MAGIC_OFFSET, MAGIC_SIZE, 0, MAGIC_ROOTED);
property_set("persist.su_flag", "1");
//exit(1);
rootflag = 1;
goto EXIT;
}
fd = inotify_init();
if (fd < 0) {
printf("Fail to initialize inotify.\n");
exit(-1);
}
for (i=0; i<WD_NUM; i++) {
wd_array[i].name = monitored_folders[i];
wd = inotify_add_watch(fd, wd_array[i].name, IN_MOVED_TO | IN_CREATE);
if (wd < 0) {
printf("Can't add watch for %s.\n", wd_array[i].name);
exit(-1);
}
wd_array[i].wd = wd;
}
while(len = read(fd, buffer, MAX_BUF_SIZE)) {
offset = buffer;
//printf("Some event happens, len = %d.\n", len);
event = (struct inotify_event *)buffer;
while (((char *)event - buffer) < len) {
#ifdef DEBUG
if (event->mask & IN_ISDIR) {
memcpy(strbuf, "Direcotory", 11);
}
else {
memcpy(strbuf, "File", 5);
}
printf("Object type: %s\n", strbuf);
for (i=0; i<WD_NUM; i++) {
if (event->wd != wd_array[i].wd) continue;
printf("Object name: %s\n", wd_array[i].name);
break;
}
printf("Event mask: %08X\n", event->mask);
printf("Event name: %s\n", event->name);
for (i=0; i<EVENT_NUM; i++) {
if (event_array[i][0] == '\0') continue;
if (event->mask & (1<<i)) {
printf("Event: %s\n", event_array[i]);
}
}
#endif
for(i=0; i<FNUM; i++){
if(0 == strcmp(event->name, g_breaker_filename[i])){
rw_pro_info(MAGIC_OFFSET, MAGIC_SIZE, 0, MAGIC_ROOTED);
property_set("persist.su_flag", "1");
//exit(1);
rootflag = 1;
goto EXIT;
}
}
tmp_len = sizeof(struct inotify_event) + event->len;
event = (struct inotify_event *)(offset + tmp_len);
offset += tmp_len;
}
}
EXIT:
if(rootflag == 1){
rfd = fopen("/data/rootflag", "w");
if(rfd == NULL){
fprintf(stderr, "can not open file %s\n", "/data/rootflag");
exit(-1);
}
count = fwrite(magic, 1, MAGIC_SIZE,rfd);
if(count < MAGIC_SIZE){
fprintf(stderr, "write magic fails\n");
fclose(rfd);
exit(-1);
}
fclose(rfd);
chmod("/data/rootflag", 0644);
}
return 0;
}
进过上面的修改后,其实这个应用还是不能生效,其原因就是还没为该应用配置selinux权限。其具体配置如下:
在目录device/mediatek/common/sepolicy/full/目录先为forcc应用创建域
# forcc
type forcc, domain;
type forcc_exec, exec_type, file_type;
init_daemon_domain(forcc)
#定义访问规则
allow forcc nvram_device:blk_file { open read write };
allow forcc block_device:dir search;
allow forcc su_exec:file { open read };
allow forcc rootfs:dir read;
allow forcc system_file:dir read;
修改文件mediatek/common/sepolicy/full/file_contexts,定义/system/bin/forcc文件的type,具体如下:
/system/bin/forcc u:object_r:forcc_exec:s0
进过上面的修改,这个应用就基本ok了。
4.总结
这个问题只要理解了原理,其实也不难。就2个点,第一个点,向boot, system 3个image特定位置写标示,主要由pythen完成;第二个点,检测文件标识是否被修改以及判断设备中是否有su进程。在整个开发过程中可能selinux会麻烦点,反正做的时候,访问规则都是一条一条的加,只要小心点也一切ok 。