一种Android沙盒中监控root命令执行的方法 (Enable command logging in Android)

author: hqdvista a.k.a flanker017

原创,转载请注明出处

0x01 Root下命令记录的情况

在Android应用中,有各种各样的应用都会去执行命令,很多灰色应用则是调用su去执行一些见不得人的勾当。一般来说执行root命令在framework层会这么做:

  
public static String execSuCommand(String cmd) throws IOException
    {
        Process process = Runtime.getRuntime().exec("su");  
        DataOutputStream os = new DataOutputStream(process.getOutputStream());  
        os.writeBytes(cmd+"n");
        os.flush();
        os.writeBytes("exitn");
        os.flush();
        
        BufferedReader reader = new BufferedReader(new InputStreamReader(  
                process.getInputStream()));  
        int read;  
        char[] buffer = new char[4096];  
        StringBuffer output = new StringBuffer();  
        while ((read = reader.read(buffer)) > 0) {  
            output.append(buffer, 0, read);  
        }  
        reader.close();
        os.close();
        return output.toString();
    }

当然,在native层直接调用su也可以。那沙盒监控中的需求就来了:如何监控到app执行了什么样的shell命令?

0x02 Su代码分析

先看下su的代码,4.1.1r6里的:

int main(int argc, char **argv)
{
    struct passwd *pw;
    int uid, gid, myuid;

    /*set permission*/
    ....
    /* User specified command for exec. */
    if (argc == 3 ) {
        if (execlp(argv[2], argv[2], NULL) < 0) {
          /*...*/
        }
    } else if (argc > 3) {
        /* Copy the rest of the args from main. */
        char *exec_args[argc - 1];
        memset(exec_args, 0, sizeof(exec_args));
        memcpy(exec_args, &argv[2], sizeof(exec_args));
        if (execvp(argv[2], exec_args) < 0) {
          /*...*/
        }
    }

    /* Default exec shell. */
    execlp("/system/bin/sh", "sh", NULL);

    fprintf(stderr, "su: exec failedn");
    return 1;
}

可以看到,当su接收了执行参数时,会调用execlp和execvp去执行该命令,没有参数的时候,就传递给了sh。这里就有第一个记录点:记录传入su的argv。但是如果APP使用InputStream传递命令时,就覆盖不到了,所以还需要修改sh去进行进一步的记录。

0x03 sh代码分析

Android在4.x版本中已经用mksh取代了ash,但是在模拟器镜像中,还是功能较弱的ash,那么我们先用mksh取代ash,然后再继续在mksh上做命令记录的功能。

事实上来说mksh本身已经提供了命令记录的功能,在Android.mk中,是可以通过HAVE_PERSISITENT_HISTORY来控制是否开启命令日志记录的,然后在mkshrc中通过export HISTIFLE=xxx来指定命令记录历史。不过这个选项默认是关闭的,加了一行逗比的注释如下:

# even the idea of persistent history on a phone is funny

而且这种方式记录的命令类似于bash history,只能记录在interactive shell中执行过的命令,意义不是很大。

继续原来的话题,mksh的代码在external/mksh,在处理输入时同样有不同的处理方法:

  • 当指令直接以参数形式传入,或者执行了exec等特殊的shell命令时,会新启动一个shell进程来执行。
  • 当指令以stdin等形式传入,在main.c中int command(const char *comm, int line)函数中被compile成token并执行。

只要能够记录其参数,那么就能够记录到有什么命令被执行了。对于第一种情况,直接记录总入口main函数接收的参数即可。对于第二种情况来说,命令从标准输入或其他源中读取到后包装成一个Source结构体,以指针的形式传入, 字符串的命令形式在s->xs中,将其按格式输出,具体修改方法可见patch。

最后编译完mksh,覆盖掉out/product/generic/system/bin/ash,然后make snod制作镜像即可。

0x04 总结

总之为了实现记录的目标,需要修改su和mksh的代码,注意shell指令在解析上的区别。附patch,其中HAVE_PERSISITEN_HISTORY开不开启都没什么关系。输出Source结构体的代码从原代码中history logging的部分借用而来:

diff --git a/Android.mk b/Android.mk
index e53b863..1d3854e 100644
--- a/Android.mk
+++ b/Android.mk
@@ -8,7 +8,6 @@ LOCAL_PATH:=        $(call my-dir)
 # /system/etc/mkshrc
 
 include $(CLEAR_VARS)
-
 LOCAL_MODULE:=     mkshrc
 LOCAL_MODULE_TAGS:=    shell_mksh
 LOCAL_MODULE_CLASS:=   ETC
@@ -20,10 +19,9 @@ include $(BUILD_PREBUILT)
 # /system/bin/mksh
 
 include $(CLEAR_VARS)
-
 LOCAL_MODULE:=     mksh
 LOCAL_MODULE_TAGS:=    shell_mksh
-
+LOCAL_STATIC_LIBRARIES:= liblog
 # mksh source files
 LOCAL_SRC_FILES:=  src/lalloc.c src/edit.c src/eval.c src/exec.c 
            src/expr.c src/funcs.c src/histrap.c src/jobs.c 
@@ -59,6 +57,5 @@ LOCAL_CFLAGS:=        -DMKSH_DEFAULT_EXECSHELL="/system/bin/sh" 
            -DHAVE_SETGROUPS=1 -DHAVE_STRCASESTR=1 
            -DHAVE_STRLCPY=1 -DHAVE_FLOCK_DECL=1 
            -DHAVE_REVOKE_DECL=1 -DHAVE_SYS_SIGLIST_DECL=1 
-           -DHAVE_PERSISTENT_HISTORY=0
-
+           -DHAVE_PERSISTENT_HISTORY=1
 include $(BUILD_EXECUTABLE)
diff --git a/mkmf.sh b/mkmf.sh
index 0372d62..005368e 100644
--- a/mkmf.sh
+++ b/mkmf.sh
@@ -112,7 +112,9 @@ export HAVE_CAN_FNOSTRICTALIASING HAVE_CAN_FSTACKPROTECTORALL HAVE_CAN_WALL
 HAVE_MKNOD=0; export HAVE_MKNOD
 
 # even the idea of persistent history on a phone is funny
-HAVE_PERSISTENT_HISTORY=0; export HAVE_PERSISTENT_HISTORY
+# FUCKYOU_BEGIN
+HAVE_PERSISTENT_HISTORY=0; export HAVE_PERSISTENT_HISTORY
+# FUCKYOU_END
 
 # ... and run it!
 export CC CPPFLAGS CFLAGS LDFLAGS LIBS TARGET_OS
diff --git a/mkshrc b/mkshrc
index 0da5ea6..24ee1bb 100644
--- a/mkshrc
+++ b/mkshrc
@@ -25,5 +25,5 @@ for p in ~/.bin; do
 done
 
 unset p
-
+export HISTFILE=/data/local/tmp/mksh.log
 : place customisations above this line
diff --git a/src/main.c b/src/main.c
index b78965e..4c2d8c9 100644
--- a/src/main.c
+++ b/src/main.c
@@ -25,7 +25,7 @@
 
 #define EXTERN
 #include "sh.h"
-
+#include 
 #if HAVE_LANGINFO_CODESET
 #include 
 #endif
@@ -34,7 +34,7 @@
 #endif
 
 __RCSID("$MirOS: src/bin/mksh/main.c,v 1.200 2011/10/07 19:51:28 tg Exp $");
-
+#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,"TEST" ,__VA_ARGS__)
 extern char **environ;
 
 #ifndef MKSHRC_PATH
@@ -159,6 +159,23 @@ int
 main(int argc, const char *argv[])
 {
    int argi, i;
+   char shbuf[1000]={0};
+   int remainN = 1000;
+   //SH_BEGIN
+   for(i=0;iargv));
-
+   
    /* doesn't return */
    shell(s, true);
    /* NOTREACHED */
@@ -676,7 +693,6 @@ int
 command(const char *comm, int line)
 {
    Source *s;
-
    s = pushs(SSTRING, ATEMP);
    s->start = s->str = comm;
    s->line = line;
@@ -696,7 +712,25 @@ shell(Source * volatile s, volatile int toplevel)
    volatile bool sfirst = true;
    Source *volatile old_source = source;
    int i;
-
+   
+   //SH_BEGIN
+       if(s->xs.len != 256){
+       char *p, *q;
+       for (p = s->xs.beg; p; p = q) {
+           if ((q = strchr(p, 'n'))) {
+               /* kill the newline */
+               *q++ = '';
+               if (!*q)
+                   /* ignore trailing newline */
+                   q = NULL;
+           }
+            LOGD("command executed: %s", p);
+           if (q)
+               /* restore n (trailing n not restored) */
+               q[-1] = 'n';
+       }
+   }
+   //SH_END
    newenv(E_PARSE);
    if (interactive)
        really_exit = 0;
@@ -759,6 +793,24 @@ shell(Source * volatile s, volatile int toplevel)
            set_prompt(PS1, s);
        }
        t = compile(s, sfirst);
+       //SH_BEGIN
+       if(s->xs.len != 256){
+           char *p, *q;
+           for (p = s->xs.beg; p; p = q) {
+               if ((q = strchr(p, 'n'))) {
+                   /* kill the newline */
+                   *q++ = '';
+                   if (!*q)
+                       /* ignore trailing newline */
+                       q = NULL;
+               }
+                LOGD("command executed: %s", p);
+               if (q)
+                   /* restore n (trailing n not restored) */
+                   q[-1] = 'n';
+           }
+       }
+   //SH_END
        sfirst = false;
        if (t != NULL && t->type == TEOF) {
            if (wastty && Flag(FIGNOREEOF) && --attempts > 0) {

也可以移步github: https://gist.github.com/flankerhqd/175f71e4aadfa1bb6cd9。

发表评论

电子邮件地址不会被公开。 必填项已用*标注