android应用程序fps|android应用程序fps meter[帧数显示]的分析 —— 浅谈root的风险 (1)
fps meter是常用的检测帧率的软件,该软件需要root权限才能工作,一直比较好奇它一个apk是如何知道系统当前的帧率情况的,就针对此apk分析了一下其工作原理。
Apk组成 首先看一下apk的组成,apk文件就是一个压缩包,可以解压缩软件如winrar解压查看,也可以用[apktools]反编译apk,以供进一步分析。
文章图片
从运行结果和代码组织上的推测
- java代码主要负责上层控制和显示。
- bin0和lib0.so是一个真正获取fps的binary工作进程的代码
- jni层的libnp_read.so,负责和工作进程桥接,通过pipe与工作进程通信,上报分析的数据给java层显示。
使用[dex2jar]反编译java代码,查看其中的信息。对于这个apk,可以看到它使用了[RootTools]的jar库,来实现通过su过的shell执行一些命令或binary程序。从dex文件的常量字符中可以看到,这个apk中使用的库是v2.2版本,该版本的源码可以在[https://code.google.com/p/roottools/source/checkout]下载到到,svn版本号是208。
Java代码:
接下来我们可以分析java代码。java代码都已经被加扰过了,不过如果你有足够的耐心并足够仔细,还是能从中读出很多内容。对java代码的分析,一般都是从activity/Application或service的onCreate方法入手(对外的接口不可能加扰,这些方法还是存在的),可以对照AndroidManifest.xml找到入口的Activity及Service,再查看相关信息
【android应用程序fps|android应用程序fps meter[帧数显示]的分析 —— 浅谈root的风险 (1)】在FPSMActivity的onCreate中,如下两句:
t.a(this, 2131034112, "0", "744");
t.a(this, 2131034113, "0.so", "744");
这是混淆过的代码,不过从参数来分析,这是调用RootTools的installBinary()方法,可以直接对照RootTools的源码来看。这个方法的作用是将apk的res/raw下的bin0和lib0.so分别安装到/data/data/com.aatt.fpsm/files/下,分别命名为0和0.so。
FPSMService的onStartCommand方法中,可以看到这个机制就是创建一个pipe:/data/data/com.aatt.fpsm/pipe,不停的从这个pipe中读取数据,显示在前端创建的的浮动window中。可以在adb shell中,
busybox dumphex /data/data/com.aatt.fpsm/pipe
验证帧率的值,刚好是从这个pipe中读入的。
其他位置未看到特别有用的信息。
JNI代码:
Jni库libnp_reader的分析,主要是一些Java的native方法的实现,并没有看到特别异常的现象。
$ arm-linux-androideabi-readelf -s libnp_read.soSymbol table '.dynsym' contains 69 entries:
Num:ValueSize TypeBindVisNdx Name
0: 000000000 NOTYPELOCALDEFAULTUND
1: 000000000 FUNCGLOBAL DEFAULTUND __cxa_finalize
2: 000000000 FUNCGLOBAL DEFAULTUND __cxa_atexit
3: 00000ed532 FUNCGLOBAL DEFAULT7 Java_com_aatt_fpsm_FPSMSe
4: 000000000 FUNCGLOBAL DEFAULTUND umask
5: 000000000 FUNCGLOBAL DEFAULTUND mknod
6: 000017c48 FUNCWEAKDEFAULT7 __aeabi_unwind_cpp_pr1
7: 00000ef516 FUNCGLOBAL DEFAULT7 Java_com_aatt_fpsm_FPSMSe
8: 000000000 FUNCGLOBAL DEFAULTUND remove
9: 00000f0540 FUNCGLOBAL DEFAULT7 Java_com_aatt_fpsm_FPSMSe
10: 000000000 FUNCGLOBAL DEFAULTUND read
11: 000040044 OBJECTGLOBAL DEFAULT16 fd
12: 000040084 OBJECTGLOBAL DEFAULT16 trash
13: 000017cc8 FUNCGLOBAL DEFAULT7 __aeabi_unwind_cpp_pr0
14: 00000f2d36 FUNCGLOBAL DEFAULT7 Java_com_aatt_fpsm_FPSMSe
15: 000000000 FUNCGLOBAL DEFAULTUND open
16: 00000f5120 FUNCGLOBAL DEFAULT7 Java_com_aatt_fpsm_FPSMSe
17: 000000000 FUNCGLOBAL DEFAULTUND close
18: 00000f6536 FUNCGLOBAL DEFAULT7 Java_com_aatt_fpsm_FPSMSe
19: 0000400c4 OBJECTGLOBAL DEFAULT16 value
20: 000040040 NOTYPEGLOBAL DEFAULTABS _edata
21: 000040040 NOTYPEGLOBAL DEFAULTABS __bss_start
22: 000040100 NOTYPEGLOBAL DEFAULTABS _end
23: 000000000 FUNCWEAKDEFAULTUND __gnu_Unwind_Find_exidx
24: 000000000 FUNCGLOBAL DEFAULTUND abort
25: 000000000 FUNCGLOBAL DEFAULTUND memcpy
26: 000017bc8 FUNCWEAKDEFAULT7 __aeabi_unwind_cpp_pr2
27: 00001d880 FUNCGLOBAL DEFAULT7 __gnu_Unwind_Restore_VFP_
28: 00001d780 FUNCGLOBAL DEFAULT7 __gnu_Unwind_Restore_VFP
29: 00001d980 FUNCGLOBAL DEFAULT7 __gnu_Unwind_Restore_VFP_
30: 00001da80 FUNCGLOBAL DEFAULT7 __gnu_Unwind_Restore_WMMX
31: 00001e300 FUNCGLOBAL DEFAULT7 __gnu_Unwind_Restore_WMMX
32: 00001d6420 FUNCGLOBAL DEFAULT7 restore_core_regs
33: 0000134c68 FUNCGLOBAL DEFAULT7 _Unwind_VRS_Get
34: 000013b868 FUNCGLOBAL DEFAULT7 _Unwind_VRS_Set
35: 000000000 NOTYPEWEAKDEFAULTUND __cxa_begin_cleanup
36: 000000000 NOTYPEWEAKDEFAULTUND __cxa_type_match
37: 00001f64916 FUNCGLOBAL DEFAULT7 __gnu_unwind_execute
38: 000000000 NOTYPEWEAKDEFAULTUND __cxa_call_unexpected
39: 000017d4856 FUNCGLOBAL DEFAULT7 _Unwind_VRS_Pop
40: 00001d900 FUNCGLOBAL DEFAULT7 __gnu_Unwind_Save_VFP_D
41: 00001d800 FUNCGLOBAL DEFAULT7 __gnu_Unwind_Save_VFP
42: 00001da00 FUNCGLOBAL DEFAULT7 __gnu_Unwind_Save_VFP_D_1
43: 00001dec0 FUNCGLOBAL DEFAULT7 __gnu_Unwind_Save_WMMXD
44: 00001e440 FUNCGLOBAL DEFAULT7 __gnu_Unwind_Save_WMMXC
45: 00001b2c8 FUNCGLOBAL DEFAULT7 _Unwind_GetCFA
46: 00001b34164 FUNCGLOBAL DEFAULT7 __gnu_Unwind_RaiseExcepti
47: 00001bd828 FUNCGLOBAL DEFAULT7 __gnu_Unwind_ForcedUnwind
48: 00001bf4108 FUNCGLOBAL DEFAULT7 __gnu_Unwind_Resume
49: 00001c6032 FUNCGLOBAL DEFAULT7 __gnu_Unwind_Resume_or_Re
50: 00001c804 FUNCGLOBAL DEFAULT7 _Unwind_Complete
51: 00001c8432 FUNCGLOBAL DEFAULT7 _Unwind_DeleteException
52: 00001ca4192 FUNCGLOBAL DEFAULT7 __gnu_Unwind_Backtrace
53: 00001d6420 FUNCGLOBAL DEFAULT7 __restore_core_regs
54: 00001e5836 FUNCGLOBAL DEFAULT7 ___Unwind_RaiseException
55: 00001e5836 FUNCGLOBAL DEFAULT7 _Unwind_RaiseException
56: 00001e7c36 FUNCGLOBAL DEFAULT7 ___Unwind_Resume
57: 00001e7c36 FUNCGLOBAL DEFAULT7 _Unwind_Resume
58: 00001ea036 FUNCGLOBAL DEFAULT7 ___Unwind_Resume_or_Rethr
59: 00001ea036 FUNCGLOBAL DEFAULT7 _Unwind_Resume_or_Rethrow
60: 00001ec436 FUNCGLOBAL DEFAULT7 ___Unwind_ForcedUnwind
61: 00001ec436 FUNCGLOBAL DEFAULT7 _Unwind_ForcedUnwind
62: 00001ee836 FUNCGLOBAL DEFAULT7 ___Unwind_Backtrace
63: 00001ee836 FUNCGLOBAL DEFAULT7 _Unwind_Backtrace
64: 000022f864 FUNCGLOBAL DEFAULT7 __gnu_unwind_frame
65: 0000233844 FUNCGLOBAL DEFAULT7 _Unwind_GetRegionStart
66: 0000236456 FUNCGLOBAL DEFAULT7 _Unwind_GetLanguageSpecif
67: 0000239c8 FUNCGLOBAL DEFAULT7 _Unwind_GetDataRelBase
68: 000023a48 FUNCGLOBAL DEFAULT7 _Unwind_GetTextRelBase
从elf信息可以见到,这个库主要是JNI的实现及将unwind实现包含在库中,unwind库一般是用作异常处理的,用它可以获取函数的调用栈信息。
这里由于使用pipe,则怀疑这里需要跨进程通信;而此pipe的通信使用是apk私有的pipe,则推论此apk通过rootTool中提供的Shell类来运行su后的shell,su的作用是通过linux开的后门,允许用户程序通过setuid系统调用,更改用户id,达到root效果。su root后,执行一个native程序,native程序应该就是res/raw/下的bin0。初步怀疑pipe的写端是这个bin0,bin0的symbol信息:
$ readelf.exe -s bin0Symbol table '.dynsym' contains 32 entries:
Num:ValueSize TypeBindVisNdx Name
0: 000000000 NOTYPELOCALDEFAULTUND
1: 000000000 FUNCGLOBAL DEFAULTUND __libc_init
2: 000000000 FUNCGLOBAL DEFAULTUND __cxa_atexit
3: 000000000 FUNCGLOBAL DEFAULTUND snprintf
4: 000000000 FUNCGLOBAL DEFAULTUND fopen
5: 000000000 FUNCGLOBAL DEFAULTUND fgets
6: 000000000 FUNCGLOBAL DEFAULTUND strstr
7: 000000000 FUNCGLOBAL DEFAULTUND strtok
8: 000000000 FUNCGLOBAL DEFAULTUND strtoul
9: 000000000 FUNCGLOBAL DEFAULTUND fclose
10: 000000000 FUNCGLOBAL DEFAULTUND __stack_chk_fail
11: 000000000 OBJECTGLOBAL DEFAULTUND __stack_chk_guard
12: 000000000 FUNCGLOBAL DEFAULTUND memcpy
13: 000000000 FUNCGLOBAL DEFAULTUND ptrace
14: 000000000 FUNCGLOBAL DEFAULTUND waitpid
15: 000000000 FUNCGLOBAL DEFAULTUND opendir
16: 000000000 FUNCGLOBAL DEFAULTUND readdir
17: 000000000 FUNCGLOBAL DEFAULTUND atoi
18: 000000000 FUNCGLOBAL DEFAULTUND sprintf
19: 000000000 FUNCGLOBAL DEFAULTUND strcmp
20: 000000000 FUNCGLOBAL DEFAULTUND closedir
21: 000000000 FUNCGLOBAL DEFAULTUND dlsym
22: 000000000 FUNCGLOBAL DEFAULTUND strlen
23: 0000c0000 NOTYPEGLOBAL DEFAULTABS _edata
24: 0000c0000 NOTYPEGLOBAL DEFAULTABS __bss_start
25: 0000c0040 NOTYPEGLOBAL DEFAULTABS _end
26: 000000000 FUNCWEAKDEFAULTUND __gnu_Unwind_Find_exidx
27: 000000000 FUNCGLOBAL DEFAULTUND abort
28: 000000000 FUNCGLOBAL DEFAULTUND raise
29: 000000000 NOTYPEWEAKDEFAULTUND __cxa_begin_cleanup
30: 000000000 NOTYPEWEAKDEFAULTUND __cxa_type_match
31: 000000000 NOTYPEWEAKDEFAULTUND __cxa_call_unexpected
我们通过ps来看一下运行时的进程情况,并没有看到此bin0进程,看样子不像是常驻内存的,那么它太不可能是pipe的write端。
从elf的symbol来看,此bin文件应该会使用dlsym和ptrace。使用ptrace的话,基本可以确定,这个bin文件会通过PTRACE_ATTACH到别的进程中,然后修改别的进程数据或代码,以达到自己的目的。ptrace的执行过程应该就是分析的关键,接下来需要从ptrace入手。
小结
这个fps meter没有按照android应用程序的开发规范,通过使用SDK和NDK开发java和jni代码实现,而是使用第三方库,通过su获取root权限,执行自己的可执行binary程序,apk自己实现的库或binary程序,以android资源的形式打包在apk中。这种运行方式,对用户安全及系统稳定来说,是一个危险的动作。
本小结简单了解了此apk的内容,下节将详细介绍下apk资源包中bin文件通过ptrace感染android系统进程的过程。
推荐阅读
- android第三方框架(五)ButterKnife
- Android中的AES加密-下
- 带有Hilt的Android上的依赖注入
- android|android studio中ndk的使用
- Android事件传递源码分析
- RxJava|RxJava 在Android项目中的使用(一)
- Android7.0|Android7.0 第三方应用无法访问私有库
- 深入理解|深入理解 Android 9.0 Crash 机制(二)
- android防止连续点击的简单实现(kotlin)
- Android|Android install 多个设备时指定设备