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) 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;i<argc;i++) + { + strncat(shbuf, argv[i], remainN); + remainN -= strlen(argv[i]); + if(remainN < 0) + break; + strncat(shbuf, " ", remainN); + remainN -= 1; + if(remainN argv)); - + /* 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。
FROM :https://blog.flanker017.me/ | Author:Flanker
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论