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。