diff --git a/.classpath b/.classpath
new file mode 100644
index 0000000..ad593f2
--- /dev/null
+++ b/.classpath
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/.project b/.project
new file mode 100644
index 0000000..fbece2a
--- /dev/null
+++ b/.project
@@ -0,0 +1,33 @@
+
+
+ ZjDroid
+
+
+
+
+
+ com.android.ide.eclipse.adt.ResourceManagerBuilder
+
+
+
+
+ com.android.ide.eclipse.adt.PreCompilerBuilder
+
+
+
+
+ org.eclipse.jdt.core.javabuilder
+
+
+
+
+ com.android.ide.eclipse.adt.ApkBuilder
+
+
+
+
+
+ com.android.ide.eclipse.adt.AndroidNature
+ org.eclipse.jdt.core.javanature
+
+
diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000..8000cd6
--- /dev/null
+++ b/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,11 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
+org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
+org.eclipse.jdt.core.compiler.compliance=1.6
+org.eclipse.jdt.core.compiler.debug.lineNumber=generate
+org.eclipse.jdt.core.compiler.debug.localVariable=generate
+org.eclipse.jdt.core.compiler.debug.sourceFile=generate
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.source=1.6
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
new file mode 100644
index 0000000..25e55bf
--- /dev/null
+++ b/AndroidManifest.xml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/README.md b/README.md
index efbcb42..873147b 100644
--- a/README.md
+++ b/README.md
@@ -2,3 +2,67 @@ ZjDroid
=======
Android app dynamic reverse tool based on Xposed framework.
+
+
+一、ZjDroid工具介绍
+
+ZjDroid是基于Xposed Framewrok的动态逆向分析模块,逆向分析者可以通过ZjDroid完成以下工作:
+1、DEX文件的内存dump
+2、基于Dalvik关键指针的内存BackSmali,有效破解加固应用
+3、敏感API的动态监控
+4、指定内存区域数据dump
+5、获取应用加载DEX信息。
+6、获取指定DEX文件加载类信息。
+7、dump Dalvik java堆信息。
+8、在目标进程动态运行lua脚本。
+
+
+二、ZjDroid相关命令
+
+1、获取APK当前加载DEX文件信息:
+adb shell am broadcast -a com.zjdroid.invoke --ei target pid --es cmd '{"action":"dump_dexinfo"}'
+
+2、获取指定DEX文件包含可加载类名:
+adb shell am broadcast -a com.zjdroid.invoke --ei target pid --es cmd '{"action":"dump_class","dexpath":"*****"}'
+
+4、根据Dalvik相关内存指针动态反编译指定DEX,并以文件形式保存。
+adb shell am broadcast -a com.zjdroid.invoke --ei target pid --es cmd '{"action":"backsmali","dexpath":"*****"}'
+
+该方式可以脱壳目前大部分流行的加固防护。(由于手机性能问题,运行较忙)
+例外情况:
+由于ApkProtect特定防修改检测,需要做如下修改即可解固该保护:
+(1)在设备上创建特定目录(如/data/local)并 chmod 为777
+(2)复制zjdroid.apk到该目录,并修改文件名为zjdroid.jar
+ (3) 修改/data/data/de.robv.android.xposed.installer/conf/modules.list 模块代码文件修改为"zjdroid.jar"
+从启设备即可。
+
+5、Dump指定DEX内存中的数据并保存到文件(数据为odex格式,可在pc上反编译)。
+adb shell am broadcast -a com.zjdroid.invoke --ei target pid --es cmd '{"action":"dump_dex","dexpath":"*****"}'
+
+
+6、Dump指定内存空间区域数据到文件
+adb shell am broadcast -a com.zjdroid.invoke --ei target pid --es cmd '{"action":"dump_mem","start":1234567,"length":123}'
+
+7、Dump Dalvik堆栈信息到文件,文件可以通过java heap分析工具分析处理。
+adb shell am broadcast -a com.zjdroid.invoke --ei target pid --es cmd '{"action":"dump_heap"}'
+
+8、运行时动态调用Lua脚本
+该功能可以通过Lua脚本动态调用java代码。
+使用场景:
+可以动态调用解密函数,完成解密。
+可以动态触发特定逻辑。
+adb shell am broadcast -a com.zjdroid.invoke --ei target pid --es cmd '{"action":"invoke","filepath":"****"}'
+
+luajava相关使用方法:
+http://www.keplerproject.org/luajava/
+
+8、敏感API调用监控
+
+
+三、相关命令执行结果查看:
+
+1、命令执行结果:
+adb shell logcat -s zjdroid-shell-{package name}
+
+2、敏感API调用监控输出结果:
+adb shell logcat -s zjdroid-apimonitor-{package name}
diff --git a/XposedBridgeApi-54.jar b/XposedBridgeApi-54.jar
new file mode 100644
index 0000000..fefd4ec
Binary files /dev/null and b/XposedBridgeApi-54.jar differ
diff --git a/assets/xposed_init b/assets/xposed_init
new file mode 100644
index 0000000..dd6a13f
--- /dev/null
+++ b/assets/xposed_init
@@ -0,0 +1 @@
+com.android.reverse.mod.ReverseXposedModule
\ No newline at end of file
diff --git a/ic_launcher-web.png b/ic_launcher-web.png
new file mode 100644
index 0000000..a18cbb4
Binary files /dev/null and b/ic_launcher-web.png differ
diff --git a/lib/.gitignore b/lib/.gitignore
new file mode 100644
index 0000000..85797d6
--- /dev/null
+++ b/lib/.gitignore
@@ -0,0 +1 @@
+/sdk_sources_15
diff --git a/lib/full_framework_15.jar b/lib/full_framework_15.jar
new file mode 100644
index 0000000..36129b6
Binary files /dev/null and b/lib/full_framework_15.jar differ
diff --git a/libs/android-support-v4.jar b/libs/android-support-v4.jar
new file mode 100644
index 0000000..cf12d28
Binary files /dev/null and b/libs/android-support-v4.jar differ
diff --git a/libs/antlr-3.5.2-complete.jar b/libs/antlr-3.5.2-complete.jar
new file mode 100644
index 0000000..260de76
Binary files /dev/null and b/libs/antlr-3.5.2-complete.jar differ
diff --git a/libs/armeabi/libdvmnative.so b/libs/armeabi/libdvmnative.so
new file mode 100644
index 0000000..ed67d4a
Binary files /dev/null and b/libs/armeabi/libdvmnative.so differ
diff --git a/libs/armeabi/libluajava.so b/libs/armeabi/libluajava.so
new file mode 100644
index 0000000..d5ed9d6
Binary files /dev/null and b/libs/armeabi/libluajava.so differ
diff --git a/libs/commons-cli-1.2.jar b/libs/commons-cli-1.2.jar
new file mode 100644
index 0000000..ce4b9ff
Binary files /dev/null and b/libs/commons-cli-1.2.jar differ
diff --git a/libs/guava-17.0.jar b/libs/guava-17.0.jar
new file mode 100644
index 0000000..661fc74
Binary files /dev/null and b/libs/guava-17.0.jar differ
diff --git a/lint.xml b/lint.xml
new file mode 100644
index 0000000..7edd00c
--- /dev/null
+++ b/lint.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/proguard-project.txt b/proguard-project.txt
new file mode 100644
index 0000000..f2fe155
--- /dev/null
+++ b/proguard-project.txt
@@ -0,0 +1,20 @@
+# To enable ProGuard in your project, edit project.properties
+# to define the proguard.config property as described in that file.
+#
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in ${sdk.dir}/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the ProGuard
+# include property in project.properties.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/project.properties b/project.properties
new file mode 100644
index 0000000..ce39f2d
--- /dev/null
+++ b/project.properties
@@ -0,0 +1,14 @@
+# This file is automatically generated by Android Tools.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+#
+# This file must be checked in Version Control Systems.
+#
+# To customize properties used by the Ant build system edit
+# "ant.properties", and override values to adapt the script to your
+# project structure.
+#
+# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
+#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
+
+# Project target.
+target=android-18
diff --git a/res/drawable-hdpi/ic_launcher.png b/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..b1d506a
Binary files /dev/null and b/res/drawable-hdpi/ic_launcher.png differ
diff --git a/res/drawable-hdpi/logo.png b/res/drawable-hdpi/logo.png
new file mode 100644
index 0000000..0bc3f97
Binary files /dev/null and b/res/drawable-hdpi/logo.png differ
diff --git a/res/drawable-mdpi/ic_launcher.png b/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..b1d506a
Binary files /dev/null and b/res/drawable-mdpi/ic_launcher.png differ
diff --git a/res/drawable-xhdpi/ic_launcher.png b/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..b1d506a
Binary files /dev/null and b/res/drawable-xhdpi/ic_launcher.png differ
diff --git a/res/drawable-xxhdpi/ic_launcher.png b/res/drawable-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..b1d506a
Binary files /dev/null and b/res/drawable-xxhdpi/ic_launcher.png differ
diff --git a/res/layout/activity_main.xml b/res/layout/activity_main.xml
new file mode 100644
index 0000000..43cddab
--- /dev/null
+++ b/res/layout/activity_main.xml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/menu/main.xml b/res/menu/main.xml
new file mode 100644
index 0000000..c002028
--- /dev/null
+++ b/res/menu/main.xml
@@ -0,0 +1,9 @@
+
diff --git a/res/values-sw600dp/dimens.xml b/res/values-sw600dp/dimens.xml
new file mode 100644
index 0000000..44f01db
--- /dev/null
+++ b/res/values-sw600dp/dimens.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
diff --git a/res/values-sw720dp-land/dimens.xml b/res/values-sw720dp-land/dimens.xml
new file mode 100644
index 0000000..61e3fa8
--- /dev/null
+++ b/res/values-sw720dp-land/dimens.xml
@@ -0,0 +1,9 @@
+
+
+
+ 128dp
+
+
diff --git a/res/values-v11/styles.xml b/res/values-v11/styles.xml
new file mode 100644
index 0000000..3c02242
--- /dev/null
+++ b/res/values-v11/styles.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
diff --git a/res/values-v14/styles.xml b/res/values-v14/styles.xml
new file mode 100644
index 0000000..a91fd03
--- /dev/null
+++ b/res/values-v14/styles.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
new file mode 100644
index 0000000..55c1e59
--- /dev/null
+++ b/res/values/dimens.xml
@@ -0,0 +1,7 @@
+
+
+
+ 16dp
+ 16dp
+
+
diff --git a/res/values/strings.xml b/res/values/strings.xml
new file mode 100644
index 0000000..34aeeef
--- /dev/null
+++ b/res/values/strings.xml
@@ -0,0 +1,8 @@
+
+
+
+ Baidu Security Lab Reverser
+ Settings
+ 百度安全实验室逆向分析工具,主要用于Android病毒逆向分析。
+
+
diff --git a/res/values/styles.xml b/res/values/styles.xml
new file mode 100644
index 0000000..6ce89c7
--- /dev/null
+++ b/res/values/styles.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/com/android/reverse/apimonitor/AbstractBahaviorHookCallBack.java b/src/com/android/reverse/apimonitor/AbstractBahaviorHookCallBack.java
new file mode 100644
index 0000000..8de82fc
--- /dev/null
+++ b/src/com/android/reverse/apimonitor/AbstractBahaviorHookCallBack.java
@@ -0,0 +1,40 @@
+package com.android.reverse.apimonitor;
+
+import com.android.reverse.hook.HookParam;
+import com.android.reverse.hook.MethodHookCallBack;
+import com.android.reverse.util.Logger;
+
+public abstract class AbstractBahaviorHookCallBack extends MethodHookCallBack {
+
+ @Override
+ public void beforeHookedMethod(HookParam param) {
+ // TODO Auto-generated method stub
+ Logger.log_behavior("Invoke "+ param.method.getDeclaringClass().getName()+"->"+param.method.getName());
+ this.descParam(param);
+ //this.printStackInfo();
+ }
+
+ @Override
+ public void afterHookedMethod(HookParam param) {
+ // TODO Auto-generated method stub
+ //Logger.log_behavior("End Invoke "+ param.method.toString());
+ }
+
+ private void printStackInfo(){
+ Throwable ex = new Throwable();
+ StackTraceElement[] stackElements = ex.getStackTrace();
+ if(stackElements != null){
+ StackTraceElement st;
+ for(int i=0; i");
+ }
+ });
+
+ Method getAccountsByTypeMethod = RefInvoke.findMethodExact(
+ "android.accounts.AccountManager", ClassLoader.getSystemClassLoader(),
+ "getAccountsByType",String.class);
+ hookhelper.hookMethod(getAccountsByTypeMethod, new AbstractBahaviorHookCallBack() {
+
+ @Override
+ public void descParam(HookParam param) {
+ // TODO Auto-generated method stub
+ String type = (String) param.args[0];
+ Logger.log_behavior("Get Account By Type ->");
+ Logger.log_behavior("type :" +type);
+ }
+ });
+ }
+
+}
diff --git a/src/com/android/reverse/apimonitor/ActivityManagerHook.java b/src/com/android/reverse/apimonitor/ActivityManagerHook.java
new file mode 100644
index 0000000..3968f7d
--- /dev/null
+++ b/src/com/android/reverse/apimonitor/ActivityManagerHook.java
@@ -0,0 +1,41 @@
+package com.android.reverse.apimonitor;
+
+import java.lang.reflect.Method;
+
+import com.android.reverse.hook.HookParam;
+import com.android.reverse.util.Logger;
+import com.android.reverse.util.RefInvoke;
+
+public class ActivityManagerHook extends ApiMonitorHook {
+
+ @Override
+ public void startHook() {
+
+ Method killBackgroundProcessesmethod = RefInvoke.findMethodExact(
+ "android.app.ActivityManager", ClassLoader.getSystemClassLoader(),
+ "killBackgroundProcesses", String.class);
+ hookhelper.hookMethod(killBackgroundProcessesmethod, new AbstractBahaviorHookCallBack() {
+
+ @Override
+ public void descParam(HookParam param) {
+ // TODO Auto-generated method stub
+ String packageName = (String) param.args[0];
+ Logger.log_behavior("kill packagename = "+packageName);
+ }
+ });
+
+ Method forceStopPackagemethod = RefInvoke.findMethodExact(
+ "android.app.ActivityManager", ClassLoader.getSystemClassLoader(),
+ "forceStopPackage", String.class);
+ hookhelper.hookMethod(forceStopPackagemethod, new AbstractBahaviorHookCallBack() {
+
+ @Override
+ public void descParam(HookParam param) {
+ // TODO Auto-generated method stub
+ String packageName = (String) param.args[0];
+ Logger.log_behavior("kill packagename = "+packageName);
+ }
+ });
+ }
+
+}
diff --git a/src/com/android/reverse/apimonitor/ActivityThreadHook.java b/src/com/android/reverse/apimonitor/ActivityThreadHook.java
new file mode 100644
index 0000000..7b80dea
--- /dev/null
+++ b/src/com/android/reverse/apimonitor/ActivityThreadHook.java
@@ -0,0 +1,35 @@
+package com.android.reverse.apimonitor;
+
+import java.lang.reflect.Method;
+import com.android.reverse.hook.HookParam;
+import com.android.reverse.util.Logger;
+import com.android.reverse.util.RefInvoke;
+
+public class ActivityThreadHook extends ApiMonitorHook {
+
+ @Override
+ public void startHook() {
+ // TODO Auto-generated method stub
+ try {
+ Class receiverDataClass = Class.forName("android.app.ActivityThread$ReceiverData");
+ if (receiverDataClass != null) {
+ Method handleReceiverMethod = RefInvoke.findMethodExact("android.app.ActivityThread", ClassLoader.getSystemClassLoader(),
+ "handleReceiver", receiverDataClass);
+ hookhelper.hookMethod(handleReceiverMethod, new AbstractBahaviorHookCallBack() {
+
+ @Override
+ public void descParam(HookParam param) {
+ Logger.log_behavior("The Receiver Information:");
+ Object data = param.args[0];
+ Logger.log_behavior(data.toString());
+
+ }
+ });
+ }
+ } catch (ClassNotFoundException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+
+}
diff --git a/src/com/android/reverse/apimonitor/AlarmManagerHook.java b/src/com/android/reverse/apimonitor/AlarmManagerHook.java
new file mode 100644
index 0000000..40c1edb
--- /dev/null
+++ b/src/com/android/reverse/apimonitor/AlarmManagerHook.java
@@ -0,0 +1,63 @@
+package com.android.reverse.apimonitor;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+import android.app.PendingIntent;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.os.WorkSource;
+import com.android.reverse.hook.HookParam;
+import com.android.reverse.util.Logger;
+import com.android.reverse.util.RefInvoke;
+
+
+public class AlarmManagerHook extends ApiMonitorHook {
+
+ @Override
+ public void startHook() {
+
+ Method setImplmethod = RefInvoke.findMethodExact(
+ "android.app.AlarmManager", ClassLoader.getSystemClassLoader(),
+ "setImpl",int.class,long.class,long.class,long.class,PendingIntent.class,WorkSource.class);
+ hookhelper.hookMethod(setImplmethod, new AbstractBahaviorHookCallBack() {
+
+ @Override
+ public void descParam(HookParam param) {
+ Logger.log_behavior("The Alarm Information:");
+ PendingIntent intent = (PendingIntent) param.args[4];
+ descPendingIntent(intent);
+ Logger.log_behavior("TriggerAtMillis = "+param.args[1]);
+ Logger.log_behavior("windowMillis = "+param.args[2]);
+ Logger.log_behavior("intervalMillis = "+param.args[3]);
+
+ }
+ });
+
+ }
+
+ private void descPendingIntent(PendingIntent pintent){
+ Method getIntentMethod = RefInvoke.findMethodExact(
+ "android.app.PendingIntent", ClassLoader.getSystemClassLoader(),
+ "getIntent");
+ try {
+ Intent intent = (Intent) getIntentMethod.invoke(pintent, new Object[]{});
+ ComponentName cn = intent.getComponent();
+ if(cn != null){
+ Logger.log_behavior("The ComponentName = "+cn.getPackageName()+"/"+cn.getClassName());
+ }
+ Logger.log_behavior("The Intent Action = "+intent.getAction());
+ } catch (IllegalArgumentException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (IllegalAccessException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (InvocationTargetException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+
+ }
+
+}
diff --git a/src/com/android/reverse/apimonitor/ApiMonitorHook.java b/src/com/android/reverse/apimonitor/ApiMonitorHook.java
new file mode 100644
index 0000000..64cedcf
--- /dev/null
+++ b/src/com/android/reverse/apimonitor/ApiMonitorHook.java
@@ -0,0 +1,20 @@
+package com.android.reverse.apimonitor;
+
+
+import com.android.reverse.hook.HookHelperFacktory;
+import com.android.reverse.hook.HookHelperInterface;
+
+public abstract class ApiMonitorHook {
+
+ protected HookHelperInterface hookhelper = HookHelperFacktory.getHookHelper();
+ public static class InvokeInfo{
+ private long invokeAtTime;
+ private String className;
+ private String methodName;
+ private Object[] argv;
+ private Object result;
+ private Object invokeState;
+ }
+ public abstract void startHook();
+
+}
diff --git a/src/com/android/reverse/apimonitor/ApiMonitorHookManager.java b/src/com/android/reverse/apimonitor/ApiMonitorHookManager.java
new file mode 100644
index 0000000..e364704
--- /dev/null
+++ b/src/com/android/reverse/apimonitor/ApiMonitorHookManager.java
@@ -0,0 +1,73 @@
+package com.android.reverse.apimonitor;
+
+
+
+public class ApiMonitorHookManager {
+
+ private static ApiMonitorHookManager hookmger;
+ private SmsManagerHook smsManagerHook;
+ private TelephonyManagerHook telephonyManagerHook;
+ private MediaRecorderHook mediaRecorderHook;
+ private AccountManagerHook accountManagerHook;
+ private ActivityManagerHook activityManagerHook;
+ private AlarmManagerHook alarmManagerHook;
+ private ConnectivityManagerHook connectivityManagerHook;
+ private ContentResolverHook contentResolverHook;
+ private ContextImplHook contextImplHook;
+ private PackageManagerHook packageManagerHook;
+ private RuntimeHook runtimeHook;
+ private ActivityThreadHook activityThreadHook;
+ private AudioRecordHook audioRecordHook;
+ private CameraHook cameraHook;
+ private NetWorkHook networkHook;
+ private NotificationManagerHook notificationManagerHook;
+ private ProcessBuilderHook processBuilderHook;
+
+
+ private ApiMonitorHookManager(){
+ this.smsManagerHook = new SmsManagerHook();
+ this.telephonyManagerHook = new TelephonyManagerHook();
+ this.mediaRecorderHook = new MediaRecorderHook();
+ this.accountManagerHook = new AccountManagerHook();
+ this.activityManagerHook = new ActivityManagerHook();
+ this.alarmManagerHook= new AlarmManagerHook();
+ this.connectivityManagerHook = new ConnectivityManagerHook();
+ this.contentResolverHook = new ContentResolverHook();
+ this.contextImplHook = new ContextImplHook();
+ this.packageManagerHook = new PackageManagerHook();
+ this.runtimeHook = new RuntimeHook();
+ this.activityThreadHook = new ActivityThreadHook();
+ this.audioRecordHook = new AudioRecordHook();
+ this.cameraHook = new CameraHook();
+ this.networkHook = new NetWorkHook();
+ this.notificationManagerHook = new NotificationManagerHook();
+ this.processBuilderHook = new ProcessBuilderHook();
+ }
+
+ public static ApiMonitorHookManager getInstance(){
+ if(hookmger == null)
+ hookmger = new ApiMonitorHookManager();
+ return hookmger;
+ }
+
+ public void startMonitor(){
+ this.smsManagerHook.startHook();
+ this.telephonyManagerHook.startHook();
+ this.mediaRecorderHook.startHook();
+ this.accountManagerHook.startHook();
+ this.activityManagerHook.startHook();
+ this.alarmManagerHook.startHook();
+ this.connectivityManagerHook.startHook();
+ this.contentResolverHook.startHook();
+ this.contextImplHook.startHook();
+ this.packageManagerHook.startHook();
+ this.runtimeHook.startHook();
+ this.activityThreadHook.startHook();
+ this.audioRecordHook.startHook();
+ this.cameraHook.startHook();
+ this.networkHook.startHook();
+ this.notificationManagerHook.startHook();
+ this.processBuilderHook.startHook();
+ }
+
+}
diff --git a/src/com/android/reverse/apimonitor/AudioRecordHook.java b/src/com/android/reverse/apimonitor/AudioRecordHook.java
new file mode 100644
index 0000000..cad256a
--- /dev/null
+++ b/src/com/android/reverse/apimonitor/AudioRecordHook.java
@@ -0,0 +1,41 @@
+package com.android.reverse.apimonitor;
+
+import java.lang.reflect.Method;
+
+import android.media.MediaSyncEvent;
+
+import com.android.reverse.hook.HookParam;
+import com.android.reverse.util.Logger;
+import com.android.reverse.util.RefInvoke;
+
+public class AudioRecordHook extends ApiMonitorHook {
+
+ @Override
+ public void startHook() {
+ // TODO Auto-generated method stub
+ Method startRecordingMethod = RefInvoke.findMethodExact(
+ "android.media.AudioRecord", ClassLoader.getSystemClassLoader(),
+ "startRecording");
+ hookhelper.hookMethod(startRecordingMethod, new AbstractBahaviorHookCallBack() {
+
+ @Override
+ public void descParam(HookParam param) {
+ // TODO Auto-generated method stub
+ Logger.log_behavior("Audio Recording ->");
+ }
+ });
+
+ Method startRecordingWithSyncMethod = RefInvoke.findMethodExact(
+ "android.media.AudioRecord", ClassLoader.getSystemClassLoader(),
+ "startRecording",MediaSyncEvent.class);
+ hookhelper.hookMethod(startRecordingWithSyncMethod, new AbstractBahaviorHookCallBack() {
+
+ @Override
+ public void descParam(HookParam param) {
+ // TODO Auto-generated method stub
+ Logger.log_behavior("Audio Recording ->");
+ }
+ });
+ }
+
+}
diff --git a/src/com/android/reverse/apimonitor/CameraHook.java b/src/com/android/reverse/apimonitor/CameraHook.java
new file mode 100644
index 0000000..c854d6e
--- /dev/null
+++ b/src/com/android/reverse/apimonitor/CameraHook.java
@@ -0,0 +1,67 @@
+package com.android.reverse.apimonitor;
+
+import java.lang.reflect.Method;
+
+import android.hardware.Camera.PictureCallback;
+import android.hardware.Camera.PreviewCallback;
+import android.hardware.Camera.ShutterCallback;
+
+import com.android.reverse.hook.HookParam;
+import com.android.reverse.util.Logger;
+import com.android.reverse.util.RefInvoke;
+
+public class CameraHook extends ApiMonitorHook {
+
+ @Override
+ public void startHook() {
+ // TODO Auto-generated method stub
+ Method takePictureMethod = RefInvoke.findMethodExact(
+ "android.hardware.Camera", ClassLoader.getSystemClassLoader(),
+ "takePicture",ShutterCallback.class,PictureCallback.class,PictureCallback.class,PictureCallback.class);
+ hookhelper.hookMethod(takePictureMethod, new AbstractBahaviorHookCallBack() {
+
+ @Override
+ public void descParam(HookParam param) {
+ // TODO Auto-generated method stub
+ Logger.log_behavior("Camera take a picture->");
+ }
+ });
+
+ Method setPreviewCallbackMethod = RefInvoke.findMethodExact(
+ "android.hardware.Camera", ClassLoader.getSystemClassLoader(),
+ "setPreviewCallback",PreviewCallback.class);
+ hookhelper.hookMethod(setPreviewCallbackMethod, new AbstractBahaviorHookCallBack() {
+
+ @Override
+ public void descParam(HookParam param) {
+ // TODO Auto-generated method stub
+ Logger.log_behavior("Camera setPreview ->");
+ }
+ });
+
+ Method setPreviewCallbackWithBufferMethod = RefInvoke.findMethodExact(
+ "android.hardware.Camera", ClassLoader.getSystemClassLoader(),
+ "setPreviewCallbackWithBuffer",PreviewCallback.class);
+ hookhelper.hookMethod(setPreviewCallbackWithBufferMethod, new AbstractBahaviorHookCallBack() {
+
+ @Override
+ public void descParam(HookParam param) {
+ // TODO Auto-generated method stub
+ Logger.log_behavior("Camera setPreview ->");
+ }
+ });
+
+ Method setOneShotPreviewCallbackMethod = RefInvoke.findMethodExact(
+ "android.hardware.Camera", ClassLoader.getSystemClassLoader(),
+ "setOneShotPreviewCallback",PreviewCallback.class);
+ hookhelper.hookMethod(setOneShotPreviewCallbackMethod, new AbstractBahaviorHookCallBack() {
+
+ @Override
+ public void descParam(HookParam param) {
+ // TODO Auto-generated method stub
+ Logger.log_behavior("Camera setPreview ->");
+ }
+ });
+ }
+
+}
diff --git a/src/com/android/reverse/apimonitor/ConnectivityManagerHook.java b/src/com/android/reverse/apimonitor/ConnectivityManagerHook.java
new file mode 100644
index 0000000..5b527be
--- /dev/null
+++ b/src/com/android/reverse/apimonitor/ConnectivityManagerHook.java
@@ -0,0 +1,29 @@
+package com.android.reverse.apimonitor;
+
+import java.lang.reflect.Method;
+
+
+import com.android.reverse.hook.HookParam;
+import com.android.reverse.util.Logger;
+import com.android.reverse.util.RefInvoke;
+
+public class ConnectivityManagerHook extends ApiMonitorHook {
+
+ @Override
+ public void startHook() {
+
+ Method setMobileDataEnabledmethod = RefInvoke.findMethodExact(
+ "android.net.ConnectivityManager", ClassLoader.getSystemClassLoader(),
+ "setMobileDataEnabled",boolean.class);
+ hookhelper.hookMethod(setMobileDataEnabledmethod, new AbstractBahaviorHookCallBack() {
+
+ @Override
+ public void descParam(HookParam param) {
+ boolean status = (Boolean) param.args[0];
+ Logger.log("Set MobileDataEnabled = "+status);
+ }
+ });
+
+ }
+
+}
diff --git a/src/com/android/reverse/apimonitor/ContentResolverHook.java b/src/com/android/reverse/apimonitor/ContentResolverHook.java
new file mode 100644
index 0000000..bd38580
--- /dev/null
+++ b/src/com/android/reverse/apimonitor/ContentResolverHook.java
@@ -0,0 +1,293 @@
+package com.android.reverse.apimonitor;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+
+import android.content.ContentProviderOperation;
+import android.content.ContentValues;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.CancellationSignal;
+import android.text.TextUtils;
+import com.android.reverse.hook.HookParam;
+import com.android.reverse.util.Logger;
+import com.android.reverse.util.RefInvoke;
+
+public class ContentResolverHook extends ApiMonitorHook {
+
+ private static final String[] privacyUris = { "content://com.android.contacts", "content://sms", "content://mms-sms", "content://contacts/",
+ "content://call_log", "content://browser/bookmarks" };
+
+ private boolean isSensitiveUri(Uri uri) {
+ String url = uri.toString().toLowerCase();
+ Logger.log_behavior(url);
+ for (int i = 0; i < privacyUris.length; i++) {
+ if (url.startsWith(privacyUris[i])) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private String concatenateStringArray(String[] array, String splitstr) {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < array.length; i++) {
+ if (i == array.length - 1)
+ sb.append(array[i]);
+ else
+ sb.append(array[i] + splitstr);
+ }
+ return sb.toString();
+ }
+
+ private String concatenateQuery(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
+ StringBuilder sb = new StringBuilder("select ");
+ if (projection == null) {
+ sb.append("* ");
+ } else {
+ sb.append(concatenateStringArray(projection, ","));
+ }
+ sb.append(" from [" + uri.toString() + "] ");
+ if (!TextUtils.isEmpty(selection)) {
+ sb.append(" where ");
+ if (selectionArgs == null) {
+ sb.append(selection);
+ } else {
+ String selectstr = selection;
+ for (int i = 0; i < selectionArgs.length; i++) {
+ selectstr = selectstr.replaceFirst("?", selectionArgs[i]);
+ }
+ sb.append(selectstr);
+ }
+ }
+ if (!TextUtils.isEmpty(sortOrder))
+ sb.append(" order by " + sortOrder);
+ return sb.toString();
+ }
+
+ private String concatenateInsert(Uri uri, ContentValues cv) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(" insert into ");
+ sb.append("[" + uri.toString() + "]");
+ sb.append(" ( ");
+ String[] keysArray = new String[cv.size()];
+ keysArray = cv.keySet().toArray(keysArray);
+ sb.append(concatenateStringArray(keysArray, ","));
+ sb.append(" ) ");
+ sb.append(" values (");
+ for (int i = 0; i < keysArray.length; i++) {
+ if (i == keysArray.length - 1)
+ sb.append(" " + cv.get(keysArray[i]));
+ else
+ sb.append(" " + cv.get(keysArray[i]) + ",");
+ }
+ sb.append(" )");
+ return sb.toString();
+ }
+
+ private String concatenateDelete(Uri uri, String selection, String[] selectionArgs) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(" delete from ");
+ sb.append("[" + uri.toString() + "]");
+ if (!TextUtils.isEmpty(selection)) {
+ sb.append(" where ");
+ if (selectionArgs == null)
+ sb.append(selection);
+ else {
+ String selectstr = selection;
+ for (int i = 0; i < selectionArgs.length; i++) {
+ selectstr = selectstr.replaceFirst("?", selectionArgs[i]);
+ }
+ sb.append(selectstr);
+ }
+ }
+ return sb.toString();
+ }
+
+ private String concatenateUpdate(Uri uri, ContentValues cv, String selection, String[] selectionArgs) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(" update ");
+ sb.append("[" + uri.toString() + "]");
+ sb.append(" set ");
+ String[] keysArray = (String[]) cv.keySet().toArray();
+ for (int i = 0; i < keysArray.length; i++) {
+ if (i == keysArray.length - 1)
+ sb.append(" " + keysArray[i] + "=" + cv.get(keysArray[i]));
+ else
+ sb.append(" " + keysArray[i] + "=" + cv.get(keysArray[i]) + ", ");
+ }
+ if (!TextUtils.isEmpty(selection)) {
+ sb.append(" where ");
+ if (selectionArgs == null)
+ sb.append(selection);
+ else {
+ String selectstr = selection;
+ for (int i = 0; i < selectionArgs.length; i++) {
+ selectstr = selectstr.replaceFirst("?", selectionArgs[i]);
+ }
+ sb.append(selectstr);
+ }
+ }
+ return sb.toString();
+ }
+
+ @Override
+ public void startHook() {
+
+ Method querymethod = RefInvoke.findMethodExact("android.content.ContentResolver", ClassLoader.getSystemClassLoader(), "query", Uri.class,
+ String[].class, String.class, String[].class, String.class, CancellationSignal.class);
+ hookhelper.hookMethod(querymethod, new AbstractBahaviorHookCallBack() {
+
+ @Override
+ public void descParam(HookParam param) {
+ // TODO Auto-generated method stub
+ Uri uri = (Uri) param.args[0];
+ if (isSensitiveUri(uri)) {
+ Logger.log_behavior("Read ContentProvider -> Uri = " + uri.toString());
+ String queryStr = concatenateQuery(uri, (String[]) param.args[1], (String) param.args[2], (String[]) param.args[3],
+ (String) param.args[4]);
+ Logger.log_behavior("Query SQL = " + queryStr);
+ }
+ }
+ });
+
+ Method registerContentObservermethod = RefInvoke.findMethodExact("android.content.ContentResolver", ClassLoader.getSystemClassLoader(),
+ "registerContentObserver", Uri.class, boolean.class, ContentObserver.class, int.class);
+ hookhelper.hookMethod(registerContentObservermethod, new AbstractBahaviorHookCallBack() {
+
+ @Override
+ public void descParam(HookParam param) {
+ // TODO Auto-generated method stub
+ Uri uri = (Uri) param.args[0];
+ if (isSensitiveUri(uri)) {
+ Logger.log_behavior("Register ContentProvider Change -> Uri = " + uri.toString());
+ Logger.log_behavior("ContentObserver ClassName =" + param.args[1].getClass().toString());
+ }
+ }
+ });
+
+ Method insertmethod = RefInvoke.findMethodExact("android.content.ContentResolver", ClassLoader.getSystemClassLoader(), "insert",
+ Uri.class, ContentValues.class);
+ hookhelper.hookMethod(insertmethod, new AbstractBahaviorHookCallBack() {
+
+ @Override
+ public void descParam(HookParam param) {
+ // TODO Auto-generated method stub
+ Uri uri = (Uri) param.args[0];
+ if (isSensitiveUri(uri)) {
+ Logger.log_behavior("Insert ContentProvider -> Uri = " + uri.toString());
+ ContentValues cv = (ContentValues) param.args[1];
+ String insertStr = concatenateInsert(uri, cv);
+ Logger.log_behavior("Insert SQL = " + insertStr);
+ }
+ }
+ });
+
+ Method bulkInsertmethod = RefInvoke.findMethodExact("android.content.ContentResolver", ClassLoader.getSystemClassLoader(), "bulkInsert",
+ Uri.class, ContentValues[].class);
+ hookhelper.hookMethod(bulkInsertmethod, new AbstractBahaviorHookCallBack() {
+
+ @Override
+ public void descParam(HookParam param) {
+ // TODO Auto-generated method stub
+ Uri uri = (Uri) param.args[0];
+ if (isSensitiveUri(uri)) {
+ ContentValues[] cv = (ContentValues[]) param.args[1];
+ Logger.log_behavior("Bulk Insert ContentProvider -> Uri = " + uri.toString());
+ String insertStr = null;
+ for (int i = 0; i < cv.length; i++) {
+ insertStr = concatenateInsert(uri, cv[i]);
+ Logger.log_behavior("Insert " + i + " SQL = " + insertStr);
+ }
+ }
+ }
+ });
+
+ Method deletemethod = RefInvoke.findMethodExact("android.content.ContentResolver", ClassLoader.getSystemClassLoader(), "delete",
+ Uri.class, String.class, String[].class);
+ hookhelper.hookMethod(deletemethod, new AbstractBahaviorHookCallBack() {
+
+ @Override
+ public void descParam(HookParam param) {
+ // TODO Auto-generated method stub
+ Uri uri = (Uri) param.args[0];
+ if (isSensitiveUri(uri)) {
+ Logger.log_behavior("Delete ContentProvider -> uri= " + uri.toString());
+ String deleteStr = concatenateDelete(uri, (String) param.args[1], (String[]) param.args[2]);
+ Logger.log_behavior("Delete SQL = " + deleteStr);
+ }
+
+ }
+ });
+
+ Method updatemethod = RefInvoke.findMethodExact("android.content.ContentResolver", ClassLoader.getSystemClassLoader(), "update",
+ Uri.class, ContentValues.class, String.class, String[].class);
+ hookhelper.hookMethod(updatemethod, new AbstractBahaviorHookCallBack() {
+
+ @Override
+ public void descParam(HookParam param) {
+ // TODO Auto-generated method stub
+ Uri uri = (Uri) param.args[0];
+ if (isSensitiveUri(uri)) {
+ Logger.log_behavior("Update ContentProvider -> uri=" + uri.toString());
+ String updateStr = concatenateUpdate(uri, (ContentValues) param.args[1], (String) param.args[2], (String[]) param.args[3]);
+ Logger.log_behavior("Update SQL = " + updateStr);
+ }
+ }
+ });
+
+ Method applyBatchMethod = RefInvoke.findMethodExact("android.content.ContentResolver", ClassLoader.getSystemClassLoader(),
+ "applyBatch", String.class, ArrayList.class);
+ hookhelper.hookMethod(applyBatchMethod, new AbstractBahaviorHookCallBack() {
+
+ @Override
+ public void descParam(HookParam param) {
+ // TODO Auto-generated method stub
+ ArrayList opts = (ArrayList) param.args[1];
+ for(int i=0; i< opts.size(); i++){
+ Logger.log_behavior("Batch SQL = " + descContentProviderOperation(opts.get(i)));
+ }
+
+ }
+ });
+
+ }
+
+ private final static int TYPE_INSERT = 1;
+ private final static int TYPE_UPDATE = 2;
+ private final static int TYPE_DELETE = 3;
+
+ private String descContentProviderOperation(ContentProviderOperation opt) {
+ String sqlstr = null;
+ int mType = RefInvoke.getFieldInt("android.content.ContentProviderOperation", opt, "mType");
+ switch (mType) {
+ case TYPE_INSERT:
+ {
+ Uri uri = (Uri) RefInvoke.getFieldOjbect("android.content.ContentProviderOperation", opt, "mUri");
+ ContentValues cv = (ContentValues) RefInvoke.getFieldOjbect("android.content.ContentProviderOperation", opt, "mValues");
+ sqlstr = concatenateInsert(uri, cv);
+ break;
+ }
+
+ case TYPE_UPDATE:
+ {
+ Uri uri = (Uri) RefInvoke.getFieldOjbect("android.content.ContentProviderOperation", opt, "mUri");
+ ContentValues cv = (ContentValues) RefInvoke.getFieldOjbect("android.content.ContentProviderOperation", opt, "mValues");
+ String selection = (String) RefInvoke.getFieldOjbect("android.content.ContentProviderOperation", opt, "mSelection");
+ String[] selectionArgs = (String[]) RefInvoke.getFieldOjbect("android.content.ContentProviderOperation", opt, "mSelectionArgs");
+ sqlstr = concatenateUpdate(uri, cv, selection, selectionArgs);
+ break;
+ }
+
+ case TYPE_DELETE:
+ Uri uri = (Uri) RefInvoke.getFieldOjbect("android.content.ContentProviderOperation", opt, "mUri");
+ String selection = (String) RefInvoke.getFieldOjbect("android.content.ContentProviderOperation", opt, "mSelection");
+ String[] selectionArgs = (String[]) RefInvoke.getFieldOjbect("android.content.ContentProviderOperation", opt, "mSelectionArgs");
+ sqlstr = concatenateDelete(uri, selection, selectionArgs);
+ break;
+
+ }
+ return sqlstr;
+ }
+
+}
diff --git a/src/com/android/reverse/apimonitor/ContextImplHook.java b/src/com/android/reverse/apimonitor/ContextImplHook.java
new file mode 100644
index 0000000..433ad04
--- /dev/null
+++ b/src/com/android/reverse/apimonitor/ContextImplHook.java
@@ -0,0 +1,46 @@
+package com.android.reverse.apimonitor;
+
+import java.lang.reflect.Method;
+import java.util.Iterator;
+import android.content.BroadcastReceiver;
+import android.content.IntentFilter;
+import com.android.reverse.hook.HookParam;
+import com.android.reverse.util.Logger;
+import com.android.reverse.util.RefInvoke;
+
+public class ContextImplHook extends ApiMonitorHook{
+
+ @Override
+ public void startHook() {
+ // TODO Auto-generated method stub
+ Method registerReceivermethod = RefInvoke.findMethodExact(
+ "android.app.ContextImpl", ClassLoader.getSystemClassLoader(),
+ "registerReceiver", BroadcastReceiver.class,IntentFilter.class);
+ hookhelper.hookMethod(registerReceivermethod, new AbstractBahaviorHookCallBack() {
+
+ @Override
+ public void descParam(HookParam param) {
+ // TODO Auto-generated method stub
+ Logger.log_behavior("Register BroatcastReceiver");
+ Logger.log_behavior("The BroatcastReceiver ClassName = "+param.args[0].getClass().toString());
+ if(param.args[1] != null){
+ String intentstr = descIntentFilter((IntentFilter) param.args[1]);
+ Logger.log_behavior("Intent Action = ["+intentstr+"]");
+ }
+ }
+ });
+ }
+
+ public String descIntentFilter(IntentFilter intentFilter){
+ StringBuilder sb = new StringBuilder();
+ Iterator actions =intentFilter.actionsIterator();
+ String action = null;
+ while(actions.hasNext()){
+ action = actions.next();
+ sb.append(action+",");
+ }
+ return sb.toString();
+
+ }
+
+}
diff --git a/src/com/android/reverse/apimonitor/MediaRecorderHook.java b/src/com/android/reverse/apimonitor/MediaRecorderHook.java
new file mode 100644
index 0000000..866b08c
--- /dev/null
+++ b/src/com/android/reverse/apimonitor/MediaRecorderHook.java
@@ -0,0 +1,48 @@
+package com.android.reverse.apimonitor;
+
+import java.io.FileDescriptor;
+import java.lang.reflect.Method;
+
+import com.android.reverse.hook.HookParam;
+import com.android.reverse.util.Logger;
+import com.android.reverse.util.RefInvoke;
+
+public class MediaRecorderHook extends ApiMonitorHook {
+
+ @Override
+ public void startHook() {
+
+ Method startmethod = RefInvoke.findMethodExact(
+ "android.media.MediaRecorder", ClassLoader.getSystemClassLoader(),
+ "start");
+ hookhelper.hookMethod(startmethod, new AbstractBahaviorHookCallBack() {
+
+ @Override
+ public void descParam(HookParam param) {
+ // TODO Auto-generated method stub
+ Logger.log_behavior("Media Record: Start ->");
+ String mPath = (String)RefInvoke.getFieldOjbect("android.media.MediaRecorder", param.thisObject, "mPath");
+ if(mPath != null)
+ Logger.log_behavior("Save Path: ->" +mPath);
+ else{
+ FileDescriptor mFd = (FileDescriptor) RefInvoke.getFieldOjbect("android.media.MediaRecorder", param.thisObject, "mFd");
+ Logger.log_behavior("Save Path: ->" +mFd.toString());
+ }
+ }
+ });
+
+ Method stopmethod = RefInvoke.findMethodExact(
+ "android.media.MediaRecorder", ClassLoader.getSystemClassLoader(),
+ "stop");
+ hookhelper.hookMethod(stopmethod, new AbstractBahaviorHookCallBack() {
+
+ @Override
+ public void descParam(HookParam param) {
+ // TODO Auto-generated method stub
+ Logger.log_behavior("Media Record: Stop ->");
+ }
+ });
+
+ }
+
+}
diff --git a/src/com/android/reverse/apimonitor/NetWorkHook.java b/src/com/android/reverse/apimonitor/NetWorkHook.java
new file mode 100644
index 0000000..cebb9ed
--- /dev/null
+++ b/src/com/android/reverse/apimonitor/NetWorkHook.java
@@ -0,0 +1,141 @@
+package com.android.reverse.apimonitor;
+
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.net.URL;
+
+import org.apache.http.Header;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpHost;
+import org.apache.http.HttpRequest;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.client.utils.URLEncodedUtils;
+import org.apache.http.protocol.HTTP;
+import org.apache.http.protocol.HttpContext;
+import com.android.reverse.hook.HookParam;
+import com.android.reverse.util.Logger;
+import com.android.reverse.util.RefInvoke;
+
+
+public class NetWorkHook extends ApiMonitorHook {
+
+ @Override
+ public void startHook() {
+ // TODO Auto-generated method stub
+ // HttpURLConnection
+ Method openConnectionMethod = RefInvoke.findMethodExact("java.net.URL", ClassLoader.getSystemClassLoader(), "openConnection");
+ hookhelper.hookMethod(openConnectionMethod, new AbstractBahaviorHookCallBack() {
+ @Override
+ public void descParam(HookParam param) {
+ // TODO Auto-generated method stub
+ URL url = (URL) param.thisObject;
+ Logger.log_behavior("Connect to URL ->");
+ Logger.log_behavior("The URL = " + url.toString());
+ }
+ });
+
+ Method executeRequest = RefInvoke.findMethodExact("org.apache.http.impl.client.AbstractHttpClient", ClassLoader.getSystemClassLoader(),
+ "execute", HttpHost.class, HttpRequest.class, HttpContext.class);
+
+ hookhelper.hookMethod(executeRequest, new AbstractBahaviorHookCallBack() {
+ @Override
+ public void descParam(HookParam param) {
+ // TODO Auto-generated method stub
+ Logger.log_behavior("Connect to URL ->");
+ HttpHost host = (HttpHost) param.args[0];
+ HttpRequest request = (HttpRequest) param.args[1];
+ if (request instanceof HttpGet) {
+ HttpGet httpGet = (HttpGet) request;
+ Logger.log_behavior("HTTP Method : " + httpGet.getMethod());
+ Logger.log_behavior("HTTP URL : " + httpGet.getURI().toString());
+ Header[] headers = request.getAllHeaders();
+ if (headers != null) {
+ for (int i = 0; i < headers.length; i++) {
+ Logger.log_behavior(headers[i].getName() + ":" + headers[i].getName());
+ }
+ }
+ } else if (request instanceof HttpPost) {
+ HttpPost httpPost = (HttpPost) request;
+ Logger.log_behavior("HTTP Method : " + httpPost.getMethod());
+ Logger.log_behavior("HTTP URL : " + httpPost.getURI().toString());
+ Header[] headers = request.getAllHeaders();
+ if (headers != null) {
+ for (int i = 0; i < headers.length; i++) {
+ Logger.log_behavior(headers[i].getName() + ":" + headers[i].getValue());
+ }
+ }
+ HttpEntity entity = httpPost.getEntity();
+ String contentType = null;
+ if (entity.getContentType() != null) {
+ contentType = entity.getContentType().getValue();
+ if (URLEncodedUtils.CONTENT_TYPE.equals(contentType)) {
+
+ try {
+ byte[] data = new byte[(int) entity.getContentLength()];
+ entity.getContent().read(data);
+ String content = new String(data, HTTP.DEFAULT_CONTENT_CHARSET);
+ Logger.log_behavior("HTTP POST Content : " + content);
+ } catch (IllegalStateException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+
+ } else if (contentType.startsWith(HTTP.DEFAULT_CONTENT_TYPE)) {
+ try {
+ byte[] data = new byte[(int) entity.getContentLength()];
+ entity.getContent().read(data);
+ String content = new String(data, contentType.substring(contentType.lastIndexOf("=") + 1));
+ Logger.log_behavior("HTTP POST Content : " + content);
+ } catch (IllegalStateException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+
+ }
+ }else{
+ byte[] data = new byte[(int) entity.getContentLength()];
+ try {
+ entity.getContent().read(data);
+ String content = new String(data, HTTP.DEFAULT_CONTENT_CHARSET);
+ Logger.log_behavior("HTTP POST Content : " + content);
+ } catch (IllegalStateException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+
+ }
+
+ }
+ }
+
+ @Override
+ public void afterHookedMethod(HookParam param) {
+ // TODO Auto-generated method stub
+ super.afterHookedMethod(param);
+ HttpResponse resp = (HttpResponse) param.getResult();
+ if (resp != null) {
+ Logger.log_behavior("Status Code = " + resp.getStatusLine().getStatusCode());
+ Header[] headers = resp.getAllHeaders();
+ if (headers != null) {
+ for (int i = 0; i < headers.length; i++) {
+ Logger.log_behavior(headers[i].getName() + ":" + headers[i].getValue());
+ }
+ }
+
+ }
+ }
+ });
+ }
+
+}
diff --git a/src/com/android/reverse/apimonitor/NotificationManagerHook.java b/src/com/android/reverse/apimonitor/NotificationManagerHook.java
new file mode 100644
index 0000000..b4c5f88
--- /dev/null
+++ b/src/com/android/reverse/apimonitor/NotificationManagerHook.java
@@ -0,0 +1,27 @@
+package com.android.reverse.apimonitor;
+
+import java.lang.reflect.Method;
+import android.app.Notification;
+
+import com.android.reverse.hook.HookParam;
+import com.android.reverse.util.Logger;
+import com.android.reverse.util.RefInvoke;
+
+public class NotificationManagerHook extends ApiMonitorHook {
+
+ @Override
+ public void startHook() {
+ // TODO Auto-generated method stub
+ Method notifyMethod = RefInvoke.findMethodExact("android.app.NotificationManager", ClassLoader.getSystemClassLoader(), "notify",int.class,Notification.class);
+ hookhelper.hookMethod(notifyMethod, new AbstractBahaviorHookCallBack() {
+ @Override
+ public void descParam(HookParam param) {
+ // TODO Auto-generated method stub
+ Notification notification = (Notification) param.args[1];
+ Logger.log_behavior("Send Notification ->");
+ Logger.log_behavior(notification.toString());
+ }
+ });
+ }
+
+}
diff --git a/src/com/android/reverse/apimonitor/PackageManagerHook.java b/src/com/android/reverse/apimonitor/PackageManagerHook.java
new file mode 100644
index 0000000..a0f00fa
--- /dev/null
+++ b/src/com/android/reverse/apimonitor/PackageManagerHook.java
@@ -0,0 +1,89 @@
+package com.android.reverse.apimonitor;
+
+import java.lang.reflect.Method;
+import android.content.ComponentName;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import com.android.reverse.hook.HookParam;
+import com.android.reverse.util.Logger;
+import com.android.reverse.util.RefInvoke;
+
+public class PackageManagerHook extends ApiMonitorHook {
+
+ @Override
+ public void startHook() {
+
+ Method setComponentEnableSettingmethod = RefInvoke.findMethodExact("android.app.ApplicationPackageManager",
+ ClassLoader.getSystemClassLoader(), "setComponentEnabledSetting", ComponentName.class, int.class, int.class);
+ hookhelper.hookMethod(setComponentEnableSettingmethod, new AbstractBahaviorHookCallBack() {
+
+ @Override
+ public void descParam(HookParam param) {
+ // TODO Auto-generated method stub
+ ComponentName cn = (ComponentName) param.args[0];
+ int newState = (Integer) param.args[1];
+ Logger.log_behavior("Set ComponentEnabled ->");
+ Logger.log_behavior("The Component ClassName: " + cn.getPackageName() + "/" + cn.getClassName());
+ if (newState == PackageManager.COMPONENT_ENABLED_STATE_DISABLED)
+ Logger.log_behavior("Component New State = " + "COMPONENT_ENABLED_STATE_DISABLED");
+ if (newState == PackageManager.COMPONENT_ENABLED_STATE_ENABLED)
+ Logger.log_behavior("Component New State = " + "COMPONENT_ENABLED_STATE_ENABLED");
+ if (newState == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED)
+ Logger.log_behavior("Component New State = " + "COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED");
+ if (newState == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER)
+ Logger.log_behavior("Component New State = " + "COMPONENT_ENABLED_STATE_DISABLED_USER");
+ if (newState == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT)
+ Logger.log_behavior("Component New State = " + "COMPONENT_ENABLED_STATE_DEFAULT");
+ }
+ });
+
+ Method installPackagemethod = null;
+ try {
+ installPackagemethod = RefInvoke.findMethodExact("android.app.ApplicationPackageManager", ClassLoader.getSystemClassLoader(),
+ "installPackage", Uri.class, Class.forName("android.content.pm.IPackageInstallObserver"), int.class, String.class);
+ } catch (ClassNotFoundException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ hookhelper.hookMethod(installPackagemethod, new AbstractBahaviorHookCallBack() {
+
+ @Override
+ public void descParam(HookParam param) {
+ // TODO Auto-generated method stub
+ Uri uri = (Uri) param.args[0];
+ Logger.log_behavior("Slient Install APK ->");
+ Logger.log_behavior("The APK URL = " + uri.toString());
+ }
+ });
+
+ Method deletePackagemethod = null;
+ try {
+ deletePackagemethod = RefInvoke.findMethodExact("android.app.ApplicationPackageManager", ClassLoader.getSystemClassLoader(),
+ "deletePackage", String.class, Class.forName("android.content.pm.IPackageDeleteObserver"), int.class);
+ } catch (ClassNotFoundException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ hookhelper.hookMethod(deletePackagemethod, new AbstractBahaviorHookCallBack() {
+
+ @Override
+ public void descParam(HookParam param) {
+ // TODO Auto-generated method stub
+ String packagename = (String) param.args[0];
+ Logger.log_behavior("Slient UnInstall APK ->");
+ Logger.log_behavior("The APK PackageName = " + packagename);
+ }
+ });
+
+ Method getInstalledPackagesMethod = RefInvoke.findMethodExact("android.app.ApplicationPackageManager",
+ ClassLoader.getSystemClassLoader(), "getInstalledPackages", int.class, int.class);
+ hookhelper.hookMethod(getInstalledPackagesMethod, new AbstractBahaviorHookCallBack() {
+ @Override
+ public void descParam(HookParam param) {
+ // TODO Auto-generated method stub
+ Logger.log_behavior("Query Installed Apps ->");
+ }
+ });
+ }
+
+}
diff --git a/src/com/android/reverse/apimonitor/ProcessBuilderHook.java b/src/com/android/reverse/apimonitor/ProcessBuilderHook.java
new file mode 100644
index 0000000..4456a0b
--- /dev/null
+++ b/src/com/android/reverse/apimonitor/ProcessBuilderHook.java
@@ -0,0 +1,34 @@
+package com.android.reverse.apimonitor;
+
+import java.lang.reflect.Method;
+import java.util.List;
+import com.android.reverse.hook.HookParam;
+import com.android.reverse.util.Logger;
+import com.android.reverse.util.RefInvoke;
+
+
+public class ProcessBuilderHook extends ApiMonitorHook {
+
+ @Override
+ public void startHook() {
+ // TODO Auto-generated method stub
+ Method execmethod = RefInvoke.findMethodExact(
+ "java.lang.ProcessBuilder", ClassLoader.getSystemClassLoader(),
+ "start");
+ hookhelper.hookMethod(execmethod, new AbstractBahaviorHookCallBack() {
+ @Override
+ public void descParam(HookParam param) {
+ // TODO Auto-generated method stub
+ Logger.log_behavior("Create New Process ->");
+ ProcessBuilder pb = (ProcessBuilder) param.thisObject;
+ List cmds = pb.command();
+ StringBuilder sb = new StringBuilder();
+ for(int i=0 ;i ");
+ String[] progs = (String[]) param.args[0];
+ for(int i=0 ;i ");
+ String dstNumber = (String)param.args[0];
+ String content = (String)param.args[2];
+ Logger.log_behavior("SMS DestNumber:"+dstNumber);
+ Logger.log_behavior("SMS Content:"+content);
+ }
+ });
+
+ Method getAllMessagesFromIccmethod = RefInvoke.findMethodExact(
+ "android.telephony.SmsManager", ClassLoader.getSystemClassLoader(),
+ "getAllMessagesFromIcc");
+ hookhelper.hookMethod(getAllMessagesFromIccmethod, new AbstractBahaviorHookCallBack() {
+
+ @Override
+ public void descParam(HookParam param) {
+ // TODO Auto-generated method stub
+ Logger.log_behavior("Read SMS From Icc ->");
+ }
+ });
+
+ Method sendDataMessagemethod = RefInvoke.findMethodExact(
+ "android.telephony.SmsManager", ClassLoader.getSystemClassLoader(),
+ "sendDataMessage",String.class,String.class,short.class,byte[].class,PendingIntent.class,PendingIntent.class);
+ hookhelper.hookMethod(sendDataMessagemethod, new AbstractBahaviorHookCallBack() {
+
+ @Override
+ public void descParam(HookParam param) {
+ // TODO Auto-generated method stub
+ Logger.log_behavior("Send Data SMS ->");
+ String dstNumber = (String)param.args[0];
+ short port = (Short)param.args[2];
+ String content = Base64.encodeToString((byte[]) param.args[3],0);
+ Logger.log_behavior("SMS DestNumber:"+dstNumber);
+ Logger.log_behavior("SMS destinationPort:"+port);
+ Logger.log_behavior("SMS Base64 Content:"+content);
+ }
+ });
+
+ Method sendMultipartTextMessagemethod = RefInvoke.findMethodExact(
+ "android.telephony.SmsManager", ClassLoader.getSystemClassLoader(),
+ "sendMultipartTextMessage",String.class,String.class,ArrayList.class,ArrayList.class,ArrayList.class);
+ hookhelper.hookMethod(sendMultipartTextMessagemethod, new AbstractBahaviorHookCallBack() {
+
+ @Override
+ public void descParam(HookParam param) {
+ // TODO Auto-generated method stub
+ Logger.log_behavior("Send Multipart SMS ->");
+ String dstNumber = (String)param.args[0];
+ ArrayList sms = (ArrayList) param.args[2];
+ StringBuilder sb = new StringBuilder();
+ for(int i=0; i");
+ }
+ });
+
+ Method listenMethod = RefInvoke.findMethodExact(
+ "android.telephony.TelephonyManager", ClassLoader.getSystemClassLoader(),
+ "listen", PhoneStateListener.class,int.class);
+ hookhelper.hookMethod(listenMethod, new AbstractBahaviorHookCallBack() {
+
+ @Override
+ public void descParam(HookParam param) {
+ // TODO Auto-generated method stub
+ Logger.log_behavior("Listen Telephone State Change ->");
+ Logger.log_behavior("PhoneStateListener ClassName = "+param.args[0].getClass().getName());
+ int event = (Integer) param.args[1];
+ if((event&PhoneStateListener.LISTEN_CELL_LOCATION) != 0){
+ Logger.log_behavior("Listen Enent = "+"LISTEN_CELL_LOCATION");
+ }
+ if((event&PhoneStateListener.LISTEN_SIGNAL_STRENGTHS) != 0){
+ Logger.log_behavior("Listen Enent = "+"LISTEN_SIGNAL_STRENGTHS");
+ }
+ if((event&PhoneStateListener.LISTEN_CALL_STATE) != 0){
+ Logger.log_behavior("Listen Enent = "+"LISTEN_CALL_STATE");
+ }
+ if((event&PhoneStateListener.LISTEN_DATA_CONNECTION_STATE) != 0){
+ Logger.log_behavior("Listen Enent = "+"LISTEN_DATA_CONNECTION_STATE");
+ }
+ if((event&PhoneStateListener.LISTEN_CELL_LOCATION) != 0){
+ Logger.log_behavior("Listen Enent = "+"LISTEN_SERVICE_STATE");
+ }
+
+ }
+ });
+
+ }
+
+}
diff --git a/src/com/android/reverse/client/MainActivity.java b/src/com/android/reverse/client/MainActivity.java
new file mode 100644
index 0000000..bd1e5b0
--- /dev/null
+++ b/src/com/android/reverse/client/MainActivity.java
@@ -0,0 +1,33 @@
+package com.android.reverse.client;
+
+import java.lang.reflect.InvocationTargetException;
+
+import com.android.reverse.R;
+
+import android.os.Bundle;
+import android.app.Activity;
+import android.view.Menu;
+
+public class MainActivity extends Activity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+ try {
+
+
+ } catch (Exception e) {
+ // TODO Auto-generated catch block
+ ((InvocationTargetException)e).getTargetException().printStackTrace();
+ }
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ // Inflate the menu; this adds items to the action bar if it is present.
+ getMenuInflater().inflate(R.menu.main, menu);
+ return true;
+ }
+
+}
diff --git a/src/com/android/reverse/collecter/DexFileInfo.java b/src/com/android/reverse/collecter/DexFileInfo.java
new file mode 100644
index 0000000..d3438a0
--- /dev/null
+++ b/src/com/android/reverse/collecter/DexFileInfo.java
@@ -0,0 +1,47 @@
+package com.android.reverse.collecter;
+
+public class DexFileInfo {
+
+ private String dexPath;
+ private int mCookie;
+ private ClassLoader defineClassLoader;
+
+
+ public DexFileInfo(String dexPath,int mCookie) {
+ super();
+ this.dexPath = dexPath;
+ this.mCookie = mCookie;
+ }
+
+ public DexFileInfo(String dexPath,int mCookie,ClassLoader classLoader) {
+ this(dexPath,mCookie);
+ this.defineClassLoader = classLoader;
+ }
+
+ public String getDexPath() {
+ return dexPath;
+ }
+
+ public int getmCookie() {
+ return mCookie;
+ }
+
+ public void setmCookie(int mCookie) {
+ this.mCookie = mCookie;
+ }
+
+ public ClassLoader getDefineClassLoader() {
+ return defineClassLoader;
+ }
+
+ public void setDefineClassLoader(ClassLoader defineClassLoader) {
+ this.defineClassLoader = defineClassLoader;
+ }
+
+ public void setDexPath(String dexPath) {
+ this.dexPath = dexPath;
+ }
+
+
+
+}
diff --git a/src/com/android/reverse/collecter/DexFileInfoCollecter.java b/src/com/android/reverse/collecter/DexFileInfoCollecter.java
new file mode 100644
index 0000000..2fd81ce
--- /dev/null
+++ b/src/com/android/reverse/collecter/DexFileInfoCollecter.java
@@ -0,0 +1,222 @@
+package com.android.reverse.collecter;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.HashMap;
+import java.util.Iterator;
+
+import com.android.reverse.hook.HookHelperFacktory;
+import com.android.reverse.hook.HookHelperInterface;
+import com.android.reverse.hook.HookParam;
+import com.android.reverse.hook.MethodHookCallBack;
+import com.android.reverse.smali.MemoryBackSmali;
+import com.android.reverse.util.Logger;
+import com.android.reverse.util.NativeFunction;
+import com.android.reverse.util.RefInvoke;
+import dalvik.system.DexFile;
+import dalvik.system.PathClassLoader;
+
+public class DexFileInfoCollecter{
+
+ private static PathClassLoader pathClassLoader;
+ private static HashMap dynLoadedDexInfo = new HashMap();
+ private static DexFileInfoCollecter collecter;
+ private HookHelperInterface hookhelper = HookHelperFacktory.getHookHelper();
+ private final static String DVMLIB_LIB = "dvmnative";
+
+ private DexFileInfoCollecter() {
+
+ }
+
+ public static DexFileInfoCollecter getInstance() {
+ if (collecter == null)
+ collecter = new DexFileInfoCollecter();
+ return collecter;
+ }
+
+ public void start() throws Throwable {
+
+ pathClassLoader = (PathClassLoader) ModuleContext.getInstance().getBaseClassLoader();
+
+ Method openDexFileNativeMethod = RefInvoke.findMethodExact("dalvik.system.DexFile", ClassLoader.getSystemClassLoader(), "openDexFileNative",
+ String.class, String.class, int.class);
+ hookhelper.hookMethod(openDexFileNativeMethod, new MethodHookCallBack() {
+
+ @Override
+ public void beforeHookedMethod(HookParam param) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void afterHookedMethod(HookParam param) {
+ // TODO Auto-generated method stub
+ String dexPath = (String) param.args[0];
+ int mCookie = (Integer) param.getResult();
+ if (mCookie != 0) {
+ dynLoadedDexInfo.put(dexPath, new DexFileInfo(dexPath,mCookie));
+ }
+ }
+ });
+
+ Method defineClassNativeMethod = RefInvoke.findMethodExact("dalvik.system.DexFile", ClassLoader.getSystemClassLoader(), "defineClassNative",
+ String.class, ClassLoader.class,int.class);
+ hookhelper.hookMethod(defineClassNativeMethod, new MethodHookCallBack() {
+
+ @Override
+ public void beforeHookedMethod(HookParam param) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void afterHookedMethod(HookParam param) {
+ // TODO Auto-generated method stub
+ if(!param.hasThrowable()){
+ int mCookie = (Integer) param.args[2];
+ setDefineClassLoader(mCookie,(ClassLoader) param.args[1]);
+ }
+ }
+ });
+
+ Method findLibraryMethod = RefInvoke.findMethodExact("dalvik.system.BaseDexClassLoader", ClassLoader.getSystemClassLoader(), "findLibrary",
+ String.class);
+ hookhelper.hookMethod(findLibraryMethod, new MethodHookCallBack() {
+
+ @Override
+ public void beforeHookedMethod(HookParam param) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void afterHookedMethod(HookParam param) {
+ Logger.log((String) param.args[0]);
+ if (DVMLIB_LIB.equals(param.args[0]) && param.getResult() == null) {
+ param.setResult("/data/data/com.android.reverse/lib/libdvmnative.so");
+ }
+ }
+ });
+ }
+
+ public HashMap dumpDexFileInfo() {
+ HashMap dexs = new HashMap(dynLoadedDexInfo);
+ Object dexPathList = RefInvoke.getFieldOjbect("dalvik.system.BaseDexClassLoader", pathClassLoader, "pathList");
+ Object[] dexElements = (Object[]) RefInvoke.getFieldOjbect("dalvik.system.DexPathList", dexPathList, "dexElements");
+ DexFile dexFile = null;
+ for (int i = 0; i < dexElements.length; i++) {
+ dexFile = (DexFile) RefInvoke.getFieldOjbect("dalvik.system.DexPathList$Element", dexElements[i], "dexFile");
+ String mFileName = (String) RefInvoke.getFieldOjbect("dalvik.system.DexFile", dexFile, "mFileName");
+ int mCookie = RefInvoke.getFieldInt("dalvik.system.DexFile", dexFile, "mCookie");
+ DexFileInfo dexinfo = new DexFileInfo(mFileName, mCookie, pathClassLoader);
+ dexs.put(mFileName, dexinfo);
+ }
+ return dexs;
+ }
+
+ public String[] dumpLoadableClass(String dexPath) {
+ int mCookie = this.getCookie(dexPath);
+ if (mCookie != 0) {
+ return (String[]) RefInvoke.invokeStaticMethod("dalvik.system.DexFile", "getClassNameList", new Class[] { int.class },
+ new Object[] { mCookie });
+ } else {
+ Logger.log("the cookie is not right");
+ }
+ return null;
+
+ }
+
+ public void backsmaliDexFile(String filename, String dexPath) {
+ File file = new File(filename);
+ try {
+ if (!file.exists())
+ file.createNewFile();
+ int mCookie = this.getCookie(dexPath);
+ if (mCookie != 0) {
+ MemoryBackSmali.disassembleDexFile(mCookie, filename);
+ } else {
+ Logger.log("the cookie is not right");
+ }
+ } catch (FileNotFoundException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+
+ public void dumpDexFile(String filename, String dexPath) {
+ File file = new File(filename);
+ try {
+ if (!file.exists())
+ file.createNewFile();
+ int mCookie = this.getCookie(dexPath);
+ if (mCookie != 0) {
+ FileOutputStream out = new FileOutputStream(file);
+ ByteBuffer data = NativeFunction.dumpDexFileByCookie(mCookie, ModuleContext.getInstance().getApiLevel());
+ data.order(ByteOrder.LITTLE_ENDIAN);
+ byte[] buffer = new byte[8192];
+ data.clear();
+ while (data.hasRemaining()) {
+ int count = Math.min(buffer.length, data.remaining());
+ data.get(buffer, 0, count);
+ try {
+ out.write(buffer, 0, count);
+ } catch (IOException e1) {
+ // TODO Auto-generated catch block
+ e1.printStackTrace();
+ }
+ }
+ } else {
+ Logger.log("the cookie is not right");
+ }
+ } catch (FileNotFoundException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+
+ private int getCookie(String dexPath) {
+
+ if (dynLoadedDexInfo.containsKey(dexPath)) {
+ DexFileInfo dexFileInfo = dynLoadedDexInfo.get(dexPath);
+ return dexFileInfo.getmCookie();
+ } else {
+ Object dexPathList = RefInvoke.getFieldOjbect("dalvik.system.BaseDexClassLoader", pathClassLoader, "pathList");
+ Object[] dexElements = (Object[]) RefInvoke.getFieldOjbect("dalvik.system.DexPathList", dexPathList, "dexElements");
+ DexFile dexFile = null;
+ for (int i = 0; i < dexElements.length; i++) {
+ dexFile = (DexFile) RefInvoke.getFieldOjbect("dalvik.system.DexPathList$Element", dexElements[i], "dexFile");
+ String mFileName = (String) RefInvoke.getFieldOjbect("dalvik.system.DexFile", dexFile, "mFileName");
+ if (mFileName.equals(dexPath)) {
+ return RefInvoke.getFieldInt("dalvik.system.DexFile", dexFile, "mCookie");
+ }
+
+ }
+ return 0;
+ }
+
+ }
+
+ private void setDefineClassLoader(int mCookie, ClassLoader classLoader){
+ Iterator dexinfos = dynLoadedDexInfo.values().iterator();
+ DexFileInfo info = null;
+ while(dexinfos.hasNext()){
+ info = dexinfos.next();
+ if(mCookie == info.getmCookie()){
+ if(info.getDefineClassLoader() == null)
+ info.setDefineClassLoader(classLoader);
+ }
+ }
+ }
+
+}
diff --git a/src/com/android/reverse/collecter/HeapDump.java b/src/com/android/reverse/collecter/HeapDump.java
new file mode 100644
index 0000000..4cbe7f6
--- /dev/null
+++ b/src/com/android/reverse/collecter/HeapDump.java
@@ -0,0 +1,18 @@
+package com.android.reverse.collecter;
+
+import java.io.IOException;
+
+import android.os.Debug;
+
+public class HeapDump {
+
+ public static void dumpHeap(String filename) {
+ try {
+ Debug.dumpHprofData(filename);
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+
+}
diff --git a/src/com/android/reverse/collecter/LuaScriptInvoker.java b/src/com/android/reverse/collecter/LuaScriptInvoker.java
new file mode 100644
index 0000000..b009d89
--- /dev/null
+++ b/src/com/android/reverse/collecter/LuaScriptInvoker.java
@@ -0,0 +1,140 @@
+package com.android.reverse.collecter;
+
+import java.lang.reflect.Method;
+import org.keplerproject.luajava.JavaFunction;
+import org.keplerproject.luajava.LuaException;
+import org.keplerproject.luajava.LuaState;
+import org.keplerproject.luajava.LuaStateFactory;
+
+import com.android.reverse.hook.HookHelperFacktory;
+import com.android.reverse.hook.HookHelperInterface;
+import com.android.reverse.hook.HookParam;
+import com.android.reverse.hook.MethodHookCallBack;
+import com.android.reverse.util.JsonWriter;
+import com.android.reverse.util.Logger;
+import com.android.reverse.util.RefInvoke;
+
+public class LuaScriptInvoker{
+
+ private static LuaScriptInvoker luaInvoker;
+ private final static String LUAJAVA_LIB = "luajava";
+ private HookHelperInterface hookhelper = HookHelperFacktory.getHookHelper();
+
+
+ private LuaScriptInvoker(){
+
+ }
+
+
+ public static LuaScriptInvoker getInstance(){
+ if(luaInvoker == null)
+ luaInvoker = new LuaScriptInvoker();
+ return luaInvoker;
+ }
+
+ public void start(){
+ Method findLibraryMethod = RefInvoke.findMethodExact("dalvik.system.BaseDexClassLoader", ClassLoader.getSystemClassLoader(), "findLibrary",
+ String.class);
+ hookhelper.hookMethod(findLibraryMethod, new MethodHookCallBack() {
+
+ @Override
+ public void beforeHookedMethod(HookParam param) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void afterHookedMethod(HookParam param) {
+ Logger.log((String) param.args[0]);
+ if (LUAJAVA_LIB.equals(param.args[0]) && param.getResult() == null) {
+ param.setResult("/data/data/com.android.reverse/lib/libluajava.so");
+ }
+ }
+ });
+
+ }
+
+ private void initLuaContext(LuaState luaState){
+ try {
+ JavaFunction logfunction = new LogFunctionCallBack(luaState);
+ logfunction.register("log");
+ JavaFunction tostringfunction = new ToStringFunctionCallBack(luaState);
+ tostringfunction.register("tostring");
+ } catch (LuaException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+
+ }
+
+ public void invokeScript(String script){
+ LuaState luaState = LuaStateFactory.newLuaState();
+ luaState.openLibs();
+ this.initLuaContext(luaState);
+ int error = luaState.LdoString(script);
+ if(error!=0){
+ Logger.log("Read/Parse lua error. Exit");
+ return;
+ }
+
+ luaState.close();
+ }
+
+ public void invokeFileScript(String scriptFilePath){
+ LuaState luaState = LuaStateFactory.newLuaState();
+ luaState.openLibs();
+ this.initLuaContext(luaState);
+ int error = luaState.LdoFile(scriptFilePath);
+ if(error!=0){
+ Logger.log("Read/Parse lua error. Exit");
+ return;
+ }
+ luaState.close();
+ }
+
+ public static class ToStringFunctionCallBack extends JavaFunction{
+
+ public ToStringFunctionCallBack(LuaState L) {
+ super(L);
+ }
+
+ @Override
+ public int execute() throws LuaException {
+
+ int param_size = this.L.getTop();
+ for(int i=2; i<=param_size; i++){
+ try {
+ String objDsrc = JsonWriter.parserInstanceToJson(this.getParam(i).getObject());
+ Logger.log(objDsrc);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ return 0;
+ }
+
+ }
+
+ public static class LogFunctionCallBack extends JavaFunction{
+
+ public LogFunctionCallBack(LuaState L) {
+ super(L);
+ }
+
+ @Override
+ public int execute() throws LuaException {
+
+ int param_size = this.L.getTop();
+ if(param_size ==2 ){
+ String message = this.L.getLuaObject(2).getString();
+ Logger.log(message);
+ }
+
+ return 0;
+ }
+
+ }
+
+
+
+}
diff --git a/src/com/android/reverse/collecter/MemDump.java b/src/com/android/reverse/collecter/MemDump.java
new file mode 100644
index 0000000..37d5067
--- /dev/null
+++ b/src/com/android/reverse/collecter/MemDump.java
@@ -0,0 +1,60 @@
+package com.android.reverse.collecter;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+import com.android.reverse.util.NativeFunction;
+
+public class MemDump {
+
+ private static void saveByteBuffer(OutputStream out, ByteBuffer data) {
+ data.order(ByteOrder.LITTLE_ENDIAN);
+ byte[] buffer = new byte[8192];
+ data.clear();
+ while (data.hasRemaining()) {
+ int count = Math.min(buffer.length, data.remaining());
+ data.get(buffer, 0, count);
+ try {
+ out.write(buffer, 0, count);
+ } catch (IOException e1) {
+ // TODO Auto-generated catch block
+ e1.printStackTrace();
+ }
+ }
+
+ }
+
+ public static void dumpMem(String filepath, int start, int length) {
+ ByteBuffer buffer = NativeFunction.dumpMemory(start, length);
+ File file = new File(filepath);
+ if (!file.exists()) {
+ try {
+ file.createNewFile();
+ file.setWritable(true);
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+
+ }
+ try {
+ saveByteBuffer(new FileOutputStream(file), buffer);
+ } catch (FileNotFoundException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+
+ }
+
+ public static void dumpMem(OutputStream outstream, int start, int length) {
+ ByteBuffer buffer = NativeFunction.dumpMemory(start, length);
+ saveByteBuffer(outstream, buffer);
+
+ }
+
+}
diff --git a/src/com/android/reverse/collecter/ModuleContext.java b/src/com/android/reverse/collecter/ModuleContext.java
new file mode 100644
index 0000000..ea65513
--- /dev/null
+++ b/src/com/android/reverse/collecter/ModuleContext.java
@@ -0,0 +1,129 @@
+package com.android.reverse.collecter;
+
+import java.lang.reflect.Method;
+
+import com.android.reverse.hook.HookHelperFacktory;
+import com.android.reverse.hook.HookHelperInterface;
+import com.android.reverse.hook.HookParam;
+import com.android.reverse.hook.MethodHookCallBack;
+import com.android.reverse.mod.CommandBroadcastReceiver;
+import com.android.reverse.mod.PackageMetaInfo;
+import com.android.reverse.util.Utility;
+import android.app.Application;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+
+public class ModuleContext {
+
+
+ private PackageMetaInfo metaInfo;
+ private int apiLevel;
+ private boolean HAS_REGISTER_LISENER = false;
+ private Application fristApplication;
+ private static ModuleContext moduleContext;
+ private HookHelperInterface hookhelper = HookHelperFacktory.getHookHelper();
+
+
+ private ModuleContext() {
+ this.apiLevel = Utility.getApiLevel();
+ }
+
+ public static ModuleContext getInstance() {
+ if (moduleContext == null)
+ moduleContext = new ModuleContext();
+ return moduleContext;
+ }
+
+ public void initModuleContext(PackageMetaInfo info) {
+ this.metaInfo = info;
+ String appClassName = this.getAppInfo().className;
+ if (appClassName == null) {
+ Method hookOncreateMethod = null;
+ try {
+ hookOncreateMethod = Application.class.getDeclaredMethod("onCreate", new Class[] {});
+ } catch (NoSuchMethodException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ hookhelper.hookMethod(hookOncreateMethod, new ApplicationOnCreateHook());
+
+ } else {
+ Class> hook_application_class = null;
+ try {
+ hook_application_class = this.getBaseClassLoader().loadClass(appClassName);
+ if (hook_application_class != null) {
+ Method hookOncreateMethod = hook_application_class.getDeclaredMethod("onCreate", new Class[] {});
+ if (hookOncreateMethod != null) {
+ hookhelper.hookMethod(hookOncreateMethod, new ApplicationOnCreateHook());
+ }
+ }
+ } catch (ClassNotFoundException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (NoSuchMethodException e) {
+ // TODO Auto-generated catch block
+ Method hookOncreateMethod;
+ try {
+ hookOncreateMethod = Application.class.getDeclaredMethod("onCreate", new Class[] {});
+ if (hookOncreateMethod != null) {
+ hookhelper.hookMethod(hookOncreateMethod, new ApplicationOnCreateHook());
+ }
+ } catch (NoSuchMethodException e1) {
+ // TODO Auto-generated catch block
+ e1.printStackTrace();
+ }
+
+ }
+ }
+ }
+
+ public String getPackageName() {
+ return metaInfo.getPackageName();
+ }
+
+ public String getProcssName() {
+ return metaInfo.getProcessName();
+ }
+
+ public ApplicationInfo getAppInfo() {
+ return metaInfo.getAppInfo();
+ }
+
+ public Application getAppContext() {
+ return this.fristApplication;
+ }
+
+ public int getApiLevel() {
+ return this.apiLevel;
+ }
+
+ public String getLibPath(){
+ return this.metaInfo.getAppInfo().nativeLibraryDir;
+ }
+
+ public ClassLoader getBaseClassLoader(){
+ return this.metaInfo.getClassLoader();
+ }
+
+ private class ApplicationOnCreateHook extends MethodHookCallBack {
+
+ @Override
+ public void beforeHookedMethod(HookParam param) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void afterHookedMethod(HookParam param) {
+ // TODO Auto-generated method stub
+ if (!HAS_REGISTER_LISENER) {
+ fristApplication = (Application) param.thisObject;
+ IntentFilter filter = new IntentFilter(CommandBroadcastReceiver.INTENT_ACTION);
+ fristApplication.registerReceiver(new CommandBroadcastReceiver(), filter);
+ HAS_REGISTER_LISENER = true;
+ }
+ }
+
+ }
+
+}
diff --git a/src/com/android/reverse/collecter/NativeHookCollecter.java b/src/com/android/reverse/collecter/NativeHookCollecter.java
new file mode 100644
index 0000000..3ccfb18
--- /dev/null
+++ b/src/com/android/reverse/collecter/NativeHookCollecter.java
@@ -0,0 +1,73 @@
+package com.android.reverse.collecter;
+
+import java.util.HashMap;
+import java.util.Iterator;
+
+import com.android.reverse.util.Logger;
+import com.android.reverse.util.NativeFunction;
+
+public class NativeHookCollecter{
+
+ private static NativeHookCollecter collecter;
+ private static HashMap> initSysLinkInfo;
+
+ private NativeHookCollecter() {
+
+ }
+
+ public static NativeHookCollecter getInstance() {
+ if (collecter == null) {
+ collecter = new NativeHookCollecter();
+ }
+ return collecter;
+ }
+
+ public void init() {
+ if(initSysLinkInfo == null)
+ initSysLinkInfo = NativeFunction.getSyslinkSnapshot();
+ }
+
+ public void parserNativeHookInfo() {
+ Logger.log("The parser native hook info start");
+ if (initSysLinkInfo == null) {
+ Logger.log("the init syslink info == null");
+ return;
+ }
+
+ int hookcount =0;
+ HashMap> currentInfo = NativeFunction
+ .getSyslinkSnapshot();
+ Iterator libkeys = currentInfo.keySet().iterator();
+ HashMap currentlinks;
+ HashMap initlinks;
+ while (libkeys.hasNext()) {
+ String libName = libkeys.next();
+ if (initSysLinkInfo.containsKey(libName)) {
+ currentlinks = currentInfo.get(libName);
+ initlinks = initSysLinkInfo.get(libName);
+ Iterator sysNamekeys = currentlinks.keySet().iterator();
+ while (sysNamekeys.hasNext()) {
+ String sysName = sysNamekeys.next();
+ if (initlinks.containsKey(sysName)) {
+ int currentAddr = currentlinks.get(sysName);
+ int initAddr = initlinks.get(sysName);
+ if (currentAddr != initAddr) {
+ Logger.log("The " + libName + " syslink:" + sysName
+ + " oldAddr:" + initAddr + " newAddr:"
+ + currentAddr);
+ hookcount++;
+ }
+
+ }
+ }
+ }
+ }
+ if(hookcount == 0 ){
+ Logger.log("the app can't hook native function");
+ }else{
+ Logger.log("The app total hook native function = "+hookcount);
+ }
+ Logger.log("The parser native hook info end");
+ }
+
+}
diff --git a/src/com/android/reverse/hook/HookHelperFacktory.java b/src/com/android/reverse/hook/HookHelperFacktory.java
new file mode 100644
index 0000000..7729d99
--- /dev/null
+++ b/src/com/android/reverse/hook/HookHelperFacktory.java
@@ -0,0 +1,13 @@
+package com.android.reverse.hook;
+
+public class HookHelperFacktory {
+
+ private static HookHelperInterface hookHelper;
+
+ public static HookHelperInterface getHookHelper(){
+ if(hookHelper == null)
+ hookHelper = new XposeHookHelperImpl();
+ return hookHelper;
+ }
+
+}
diff --git a/src/com/android/reverse/hook/HookHelperInterface.java b/src/com/android/reverse/hook/HookHelperInterface.java
new file mode 100644
index 0000000..deedec6
--- /dev/null
+++ b/src/com/android/reverse/hook/HookHelperInterface.java
@@ -0,0 +1,10 @@
+package com.android.reverse.hook;
+
+import java.lang.reflect.Member;
+
+
+public interface HookHelperInterface {
+
+ public abstract void hookMethod(Member method, MethodHookCallBack callback);
+
+}
diff --git a/src/com/android/reverse/hook/HookParam.java b/src/com/android/reverse/hook/HookParam.java
new file mode 100644
index 0000000..33e4662
--- /dev/null
+++ b/src/com/android/reverse/hook/HookParam.java
@@ -0,0 +1,97 @@
+package com.android.reverse.hook;
+
+import java.lang.reflect.Member;
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.Map;
+
+import de.robv.android.xposed.XC_MethodHook.MethodHookParam;
+
+public class HookParam {
+ public Member method;
+ public Object thisObject;
+ public Object[] args;
+ private Object mResult;
+ private Throwable mThrowable;
+ private boolean mHasResult = false;
+ private boolean mHasThrowable = false;
+ private Map mExtra = null;
+
+ private HookParam() {
+ }
+
+ public static HookParam fromXposed(MethodHookParam param) {
+ HookParam xparam = new HookParam();
+ xparam.method = param.method;
+ xparam.thisObject = param.thisObject;
+ xparam.args = param.args;
+ xparam.mResult = param.getResult();
+ xparam.mThrowable = param.getThrowable();
+ return xparam;
+ }
+
+ public boolean doesReturn(Class> result) {
+ if (this.method instanceof Method)
+ return (((Method) this.method).getReturnType().equals(result));
+ return false;
+ }
+
+ public void setResult(Object result) {
+ if (result instanceof Throwable) {
+ setThrowable((Throwable) result);
+ } else {
+ mResult = result;
+ mHasResult = true;
+ }
+ }
+
+ public boolean hasResult() {
+ return mHasResult;
+ }
+
+ public Object getResult() {
+ return mResult;
+ }
+
+ public boolean doesThrow(Class> ex) {
+ if (this.method instanceof Method)
+ for (Class> t : ((Method) this.method).getExceptionTypes())
+ if (t.equals(ex))
+ return true;
+ return false;
+ }
+
+ public void setThrowable(Throwable ex) {
+ mThrowable = ex;
+ mHasThrowable = true;
+ }
+
+ public boolean hasThrowable() {
+ return mHasThrowable;
+ }
+
+ public Throwable getThrowable() {
+ return mThrowable;
+ }
+
+ public Object getExtras() {
+ return mExtra;
+ }
+
+ @SuppressWarnings("unchecked")
+ public void setExtras(Object extra) {
+ mExtra = (Map) extra;
+ }
+
+ public void setObjectExtra(String name, Object value) {
+ if (mExtra == null)
+ mExtra = new HashMap();
+ mExtra.put(name, value);
+ }
+
+ public Object getObjectExtra(String name) {
+ return (mExtra == null ? null : mExtra.get(name));
+ }
+
+
+}
diff --git a/src/com/android/reverse/hook/MethodHookCallBack.java b/src/com/android/reverse/hook/MethodHookCallBack.java
new file mode 100644
index 0000000..8019b45
--- /dev/null
+++ b/src/com/android/reverse/hook/MethodHookCallBack.java
@@ -0,0 +1,30 @@
+package com.android.reverse.hook;
+
+import de.robv.android.xposed.XC_MethodHook;
+
+public abstract class MethodHookCallBack extends XC_MethodHook {
+
+ @Override
+ protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
+ // TODO Auto-generated method stub
+ super.beforeHookedMethod(param);
+ HookParam hookParam = HookParam.fromXposed(param);
+ this.beforeHookedMethod(hookParam);
+ if(hookParam.hasResult())
+ param.setResult(hookParam.getResult());
+ }
+
+ @Override
+ protected void afterHookedMethod(MethodHookParam param) throws Throwable {
+ // TODO Auto-generated method stub
+ super.afterHookedMethod(param);
+ HookParam hookParam = HookParam.fromXposed(param);
+ this.afterHookedMethod(hookParam);
+ if(hookParam.hasResult())
+ param.setResult(hookParam.getResult());
+ }
+
+ public abstract void beforeHookedMethod(HookParam param);
+ public abstract void afterHookedMethod(HookParam param);
+
+}
diff --git a/src/com/android/reverse/hook/XposeHookHelperImpl.java b/src/com/android/reverse/hook/XposeHookHelperImpl.java
new file mode 100644
index 0000000..4bb2534
--- /dev/null
+++ b/src/com/android/reverse/hook/XposeHookHelperImpl.java
@@ -0,0 +1,16 @@
+package com.android.reverse.hook;
+
+import java.lang.reflect.Member;
+
+
+import de.robv.android.xposed.XposedBridge;
+
+public class XposeHookHelperImpl implements HookHelperInterface {
+
+ @Override
+ public void hookMethod(Member method, MethodHookCallBack callback) {
+ // TODO Auto-generated method stub
+ XposedBridge.hookMethod(method, callback);
+ }
+
+}
diff --git a/src/com/android/reverse/mod/CommandBroadcastReceiver.java b/src/com/android/reverse/mod/CommandBroadcastReceiver.java
new file mode 100644
index 0000000..0f46a8c
--- /dev/null
+++ b/src/com/android/reverse/mod/CommandBroadcastReceiver.java
@@ -0,0 +1,47 @@
+package com.android.reverse.mod;
+
+import java.util.List;
+
+import com.android.reverse.request.CommandHandler;
+import com.android.reverse.request.CommandHandlerParser;
+import com.android.reverse.util.Logger;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+public class CommandBroadcastReceiver extends BroadcastReceiver {
+
+ public static String INTENT_ACTION = "com.zjdroid.invoke";
+ public static String TARGET_KEY = "target";
+ public static String COMMAND_NAME_KEY = "cmd";
+
+ @Override
+ public void onReceive(final Context arg0, Intent arg1) {
+ // TODO Auto-generated method stub
+ if (INTENT_ACTION.equals(arg1.getAction())) {
+ try {
+ int pid = arg1.getIntExtra(TARGET_KEY, 0);
+ if (pid == android.os.Process.myPid()) {
+ String cmd = arg1.getStringExtra(COMMAND_NAME_KEY);
+ final CommandHandler handler = CommandHandlerParser
+ .parserCommand(cmd);
+ if (handler != null) {
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ // TODO Auto-generated method stub
+ handler.doAction();
+ }
+ }).start();
+ }else{
+ Logger.log("the cmd is invalid");
+ }
+ }
+ } catch (Exception e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+ }
+
+}
diff --git a/src/com/android/reverse/mod/PackageMetaInfo.java b/src/com/android/reverse/mod/PackageMetaInfo.java
new file mode 100644
index 0000000..7f61a8c
--- /dev/null
+++ b/src/com/android/reverse/mod/PackageMetaInfo.java
@@ -0,0 +1,47 @@
+package com.android.reverse.mod;
+
+import de.robv.android.xposed.callbacks.XC_LoadPackage.LoadPackageParam;
+import android.content.pm.ApplicationInfo;
+
+public class PackageMetaInfo {
+
+ private String packageName;
+ private String processName;
+ private ClassLoader classLoader;
+ private ApplicationInfo appInfo;
+ private boolean isFirstApplication;
+
+ public static PackageMetaInfo fromXposed(LoadPackageParam lpparam){
+ PackageMetaInfo pminfo = new PackageMetaInfo();
+ pminfo.packageName = lpparam.packageName;
+ pminfo.processName = lpparam.processName;
+ pminfo.classLoader = lpparam.classLoader;
+ pminfo.appInfo = lpparam.appInfo;
+ pminfo.isFirstApplication = lpparam.isFirstApplication;
+ return pminfo;
+
+ }
+
+ public String getPackageName() {
+ return packageName;
+ }
+
+ public String getProcessName() {
+ return processName;
+ }
+
+ public ClassLoader getClassLoader() {
+ return classLoader;
+ }
+
+ public ApplicationInfo getAppInfo() {
+ return appInfo;
+ }
+
+ public boolean isFirstApplication() {
+ return isFirstApplication;
+ }
+
+
+
+}
diff --git a/src/com/android/reverse/mod/ReverseXposedModule.java b/src/com/android/reverse/mod/ReverseXposedModule.java
new file mode 100644
index 0000000..fb3ed5d
--- /dev/null
+++ b/src/com/android/reverse/mod/ReverseXposedModule.java
@@ -0,0 +1,37 @@
+package com.android.reverse.mod;
+
+import android.content.pm.ApplicationInfo;
+
+import com.android.reverse.apimonitor.ApiMonitorHookManager;
+import com.android.reverse.collecter.DexFileInfoCollecter;
+import com.android.reverse.collecter.LuaScriptInvoker;
+import com.android.reverse.collecter.ModuleContext;
+import com.android.reverse.util.Logger;
+
+import de.robv.android.xposed.IXposedHookLoadPackage;
+import de.robv.android.xposed.callbacks.XC_LoadPackage.LoadPackageParam;
+
+public class ReverseXposedModule implements IXposedHookLoadPackage {
+
+ private static final String ZJDROID_PACKAGENAME = "com.android.reverse";
+ @Override
+ public void handleLoadPackage(LoadPackageParam lpparam) throws Throwable {
+ // TODO Auto-generated method stub
+ if(lpparam.appInfo == null ||
+ (lpparam.appInfo.flags & (ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_UPDATED_SYSTEM_APP)) !=0){
+ return;
+ }else if(lpparam.isFirstApplication && !ZJDROID_PACKAGENAME.equals(lpparam.packageName)){
+ Logger.PACKAGENAME = lpparam.packageName;
+ Logger.log("the package = "+lpparam.packageName +" has hook");
+ Logger.log("the app target id = "+android.os.Process.myPid());
+ PackageMetaInfo pminfo = PackageMetaInfo.fromXposed(lpparam);
+ ModuleContext.getInstance().initModuleContext(pminfo);
+ DexFileInfoCollecter.getInstance().start();
+ LuaScriptInvoker.getInstance().start();
+ ApiMonitorHookManager.getInstance().startMonitor();
+ }else{
+
+ }
+ }
+
+}
diff --git a/src/com/android/reverse/request/BackSmaliCommandHandler.java b/src/com/android/reverse/request/BackSmaliCommandHandler.java
new file mode 100644
index 0000000..e7f56d7
--- /dev/null
+++ b/src/com/android/reverse/request/BackSmaliCommandHandler.java
@@ -0,0 +1,23 @@
+package com.android.reverse.request;
+
+import com.android.reverse.collecter.DexFileInfoCollecter;
+import com.android.reverse.collecter.ModuleContext;
+import com.android.reverse.util.Logger;
+
+public class BackSmaliCommandHandler implements CommandHandler {
+
+ private String dexpath;
+
+ public BackSmaliCommandHandler(String dexpath) {
+ this.dexpath = dexpath;
+ }
+
+ @Override
+ public void doAction() {
+ // TODO Auto-generated method stub
+ String filename = ModuleContext.getInstance().getAppContext().getFilesDir()+"/dexfile.dex";
+ DexFileInfoCollecter.getInstance().backsmaliDexFile(filename, dexpath);
+ Logger.log("the dexfile data save to ="+filename);
+ }
+
+}
diff --git a/src/com/android/reverse/request/CommandHandler.java b/src/com/android/reverse/request/CommandHandler.java
new file mode 100644
index 0000000..0269a39
--- /dev/null
+++ b/src/com/android/reverse/request/CommandHandler.java
@@ -0,0 +1,7 @@
+package com.android.reverse.request;
+
+public interface CommandHandler {
+
+ public abstract void doAction();
+
+}
diff --git a/src/com/android/reverse/request/CommandHandlerParser.java b/src/com/android/reverse/request/CommandHandlerParser.java
new file mode 100644
index 0000000..27ca92f
--- /dev/null
+++ b/src/com/android/reverse/request/CommandHandlerParser.java
@@ -0,0 +1,83 @@
+package com.android.reverse.request;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import com.android.reverse.request.InvokeScriptCommandHandler.ScriptType;
+import com.android.reverse.util.Logger;
+
+public class CommandHandlerParser {
+
+ private static String ACTION_NAME_KEY = "action";
+
+ private static String ACTION_DUMP_DEXINFO = "dump_dexinfo";
+ private static String ACTION_DUMP_HEAP = "dump_heap";
+
+ private static String ACTION_DUMP_DEXCLASS = "dump_class";
+ private static String PARAM_DEXPATH_DUMPDEXCLASS = "dexpath";
+
+ private static String ACTION_DUMP_DEXFILE = "dump_dexfile";
+ private static String ACTION_BACKSMALI_DEXFILE = "backsmali";
+ private static String PARAM_DEXPATH_DUMP_DEXFILE = "dexpath";
+
+ private static String ACTION_DUMP_MEMERY = "dump_mem";
+ private static String PARAM_START_DUMP_MEMERY = "startaddr";
+ private static String PARAM_LENGTH_DUMP_MEMERY = "length";
+
+ private static String ACTION_INVOKE_SCRIPT = "invoke";
+ private static String FILE_SCRIPT = "filepath";
+
+ public static CommandHandler parserCommand(String cmd) {
+ CommandHandler handler = null;
+ try {
+ JSONObject jsoncmd = new JSONObject(cmd);
+ String action = jsoncmd.getString(ACTION_NAME_KEY);
+ Logger.log("the cmd = " + action);
+ if (ACTION_DUMP_DEXINFO.equals(action)) {
+ handler = new DumpDexInfoCommandHandler();
+ } else if (ACTION_DUMP_DEXFILE.equals(action)) {
+ if (jsoncmd.has(PARAM_DEXPATH_DUMPDEXCLASS)) {
+ String dexpath = jsoncmd.getString(PARAM_DEXPATH_DUMPDEXCLASS);
+ handler = new DumpDexFileCommandHandler(dexpath);
+ } else {
+ Logger.log("please set the " + PARAM_DEXPATH_DUMPDEXCLASS + " value");
+ }
+ } else if (ACTION_BACKSMALI_DEXFILE.equals(action)) {
+ if (jsoncmd.has(PARAM_DEXPATH_DUMPDEXCLASS)) {
+ String dexpath = jsoncmd.getString(PARAM_DEXPATH_DUMPDEXCLASS);
+ handler = new BackSmaliCommandHandler(dexpath);
+ } else {
+ Logger.log("please set the " + PARAM_DEXPATH_DUMPDEXCLASS + " value");
+ }
+ } else if (ACTION_DUMP_DEXCLASS.equals(action)) {
+ if (jsoncmd.has(PARAM_DEXPATH_DUMPDEXCLASS)) {
+ String dexpath = jsoncmd.getString(PARAM_DEXPATH_DUMP_DEXFILE);
+ handler = new DumpClassCommandHandler(dexpath);
+ } else {
+ Logger.log("please set the " + PARAM_DEXPATH_DUMPDEXCLASS + " value");
+ }
+ } else if (ACTION_DUMP_HEAP.equals(action)) {
+ handler = new DumpHeapCommandHandler();
+ } else if (ACTION_INVOKE_SCRIPT.equals(action)) {
+ if (jsoncmd.has(FILE_SCRIPT)) {
+ String filepath = jsoncmd.getString(FILE_SCRIPT);
+ handler = new InvokeScriptCommandHandler(filepath, ScriptType.FILETYPE);
+ } else {
+ Logger.log("please set the " + FILE_SCRIPT);
+ }
+
+ } else if (ACTION_DUMP_MEMERY.equals(action)) {
+ int start = jsoncmd.getInt(PARAM_START_DUMP_MEMERY);
+ int length = jsoncmd.getInt(PARAM_LENGTH_DUMP_MEMERY);
+ handler = new DumpMemCommandHandler(start, length);
+ } else {
+ Logger.log(action + " cmd is invalid! ");
+ }
+ } catch (JSONException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ return handler;
+ }
+
+}
diff --git a/src/com/android/reverse/request/DumpClassCommandHandler.java b/src/com/android/reverse/request/DumpClassCommandHandler.java
new file mode 100644
index 0000000..198ab6b
--- /dev/null
+++ b/src/com/android/reverse/request/DumpClassCommandHandler.java
@@ -0,0 +1,46 @@
+package com.android.reverse.request;
+
+import com.android.reverse.collecter.DexFileInfoCollecter;
+import com.android.reverse.util.Logger;
+
+public class DumpClassCommandHandler implements CommandHandler {
+
+ private String dexpath;
+
+ public DumpClassCommandHandler(String dexpath) {
+ this.dexpath = dexpath;
+ }
+
+ @Override
+ public void doAction() {
+ // TODO Auto-generated method stub
+ String[] loadClass = DexFileInfoCollecter.getInstance().dumpLoadableClass(dexpath);
+ if (loadClass != null) {
+ Logger.log("Start Loadable ClassName ->");
+ String className = null;
+ for (int i = 0; i < loadClass.length; i++) {
+ className = loadClass[i];
+ if (!this.isFilterClass(className)) {
+ Logger.log("ClassName = " + className);
+ }
+ }
+ Logger.log("End Loadable ClassName");
+ }else{
+ Logger.log("Can't find class loaded by the dex");
+ }
+ }
+
+ private final String[] filterClassName = { "android.support.v4.", "com.android.reverse.", "org.jf.", "org.keplerproject." };
+
+ private boolean isFilterClass(String className) {
+ String filterName = null;
+ for (int i = 0; i < filterClassName.length; i++) {
+ filterName = filterClassName[i];
+ if (className.startsWith(filterName)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+}
diff --git a/src/com/android/reverse/request/DumpDexFileCommandHandler.java b/src/com/android/reverse/request/DumpDexFileCommandHandler.java
new file mode 100644
index 0000000..f9c9603
--- /dev/null
+++ b/src/com/android/reverse/request/DumpDexFileCommandHandler.java
@@ -0,0 +1,25 @@
+package com.android.reverse.request;
+
+
+import com.android.reverse.collecter.DexFileInfoCollecter;
+import com.android.reverse.collecter.ModuleContext;
+import com.android.reverse.util.Logger;
+
+public class DumpDexFileCommandHandler implements CommandHandler {
+
+ private String dexpath;
+
+ public DumpDexFileCommandHandler(String dexpath) {
+ this.dexpath = dexpath;
+ }
+
+ @Override
+ public void doAction() {
+ // TODO Auto-generated method stub
+ String filename = ModuleContext.getInstance().getAppContext().getFilesDir()+"/dexdump.odex";
+ DexFileInfoCollecter.getInstance().dumpDexFile(filename, dexpath);
+ Logger.log("the dexfile data save to ="+filename);
+ }
+
+
+}
diff --git a/src/com/android/reverse/request/DumpDexInfoCommandHandler.java b/src/com/android/reverse/request/DumpDexInfoCommandHandler.java
new file mode 100644
index 0000000..5d9485e
--- /dev/null
+++ b/src/com/android/reverse/request/DumpDexInfoCommandHandler.java
@@ -0,0 +1,24 @@
+package com.android.reverse.request;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import com.android.reverse.collecter.DexFileInfo;
+import com.android.reverse.collecter.DexFileInfoCollecter;
+import com.android.reverse.util.Logger;
+
+public class DumpDexInfoCommandHandler implements CommandHandler {
+
+ @Override
+ public void doAction() {
+ HashMap dexfileInfo = DexFileInfoCollecter.getInstance().dumpDexFileInfo();
+ Iterator itor = dexfileInfo.values().iterator();
+ DexFileInfo info = null;
+ Logger.log("The DexFile Infomation ->");
+ while (itor.hasNext()) {
+ info = itor.next();
+ Logger.log("filepath:"+ info.getDexPath()+" mCookie:"+info.getmCookie());
+ }
+ Logger.log("End DexFile Infomation");
+ }
+
+}
diff --git a/src/com/android/reverse/request/DumpHeapCommandHandler.java b/src/com/android/reverse/request/DumpHeapCommandHandler.java
new file mode 100644
index 0000000..64023dc
--- /dev/null
+++ b/src/com/android/reverse/request/DumpHeapCommandHandler.java
@@ -0,0 +1,25 @@
+package com.android.reverse.request;
+
+import com.android.reverse.collecter.HeapDump;
+import com.android.reverse.collecter.ModuleContext;
+import com.android.reverse.util.Logger;
+
+public class DumpHeapCommandHandler implements CommandHandler {
+
+ private static String dumpFileName;
+
+ public DumpHeapCommandHandler() {
+ dumpFileName = android.os.Process.myPid()+".hprof";
+ }
+
+ @Override
+ public void doAction() {
+ // TODO Auto-generated method stub
+ String heapfilePath =ModuleContext.getInstance().getAppContext().getFilesDir()+"/"+dumpFileName;
+ HeapDump.dumpHeap(heapfilePath);
+ Logger.log("the heap data save to ="+ heapfilePath);
+ }
+
+
+
+}
diff --git a/src/com/android/reverse/request/DumpMemCommandHandler.java b/src/com/android/reverse/request/DumpMemCommandHandler.java
new file mode 100644
index 0000000..fc6aa95
--- /dev/null
+++ b/src/com/android/reverse/request/DumpMemCommandHandler.java
@@ -0,0 +1,28 @@
+package com.android.reverse.request;
+
+
+import com.android.reverse.collecter.MemDump;
+import com.android.reverse.collecter.ModuleContext;
+import com.android.reverse.util.Logger;
+
+public class DumpMemCommandHandler implements CommandHandler {
+
+ private String dumpFileName;
+ private int start;
+ private int length;
+
+ public DumpMemCommandHandler(int start, int length){
+ this.start = start;
+ this.length = length;
+ this.dumpFileName = String.valueOf(start);
+ }
+
+ @Override
+ public void doAction() {
+ // TODO Auto-generated method stub
+ String memfilePath = ModuleContext.getInstance().getAppContext().getFilesDir()+"/"+dumpFileName;
+ MemDump.dumpMem(memfilePath,start, length);
+ Logger.log("the mem data save to ="+ memfilePath);
+ }
+
+}
diff --git a/src/com/android/reverse/request/InvokeScriptCommandHandler.java b/src/com/android/reverse/request/InvokeScriptCommandHandler.java
new file mode 100644
index 0000000..e7ec663
--- /dev/null
+++ b/src/com/android/reverse/request/InvokeScriptCommandHandler.java
@@ -0,0 +1,38 @@
+package com.android.reverse.request;
+
+import com.android.reverse.collecter.LuaScriptInvoker;
+import com.android.reverse.util.Logger;
+
+public class InvokeScriptCommandHandler implements CommandHandler {
+
+ private String script;
+ private String filePath;
+ private ScriptType type;
+
+ public static enum ScriptType {
+ TEXTTYPE, FILETYPE
+ }
+
+ public InvokeScriptCommandHandler(String str, ScriptType type) {
+ this.type = type;
+ if (type == ScriptType.TEXTTYPE)
+ this.script = str;
+ else if (type == ScriptType.FILETYPE)
+ this.filePath = str;
+ }
+
+ @Override
+ public void doAction() {
+ Logger.log("The Script invoke start");
+ if (this.type == ScriptType.TEXTTYPE) {
+ LuaScriptInvoker.getInstance().invokeScript(script);
+ } else if (this.type == ScriptType.FILETYPE) {
+ LuaScriptInvoker.getInstance().invokeFileScript(filePath);
+ } else {
+ Logger.log("the script type is invalid");
+ }
+ Logger.log("The Script invoke end");
+
+ }
+
+}
diff --git a/src/com/android/reverse/request/NativeHookInfoHandler.java b/src/com/android/reverse/request/NativeHookInfoHandler.java
new file mode 100644
index 0000000..329e3e7
--- /dev/null
+++ b/src/com/android/reverse/request/NativeHookInfoHandler.java
@@ -0,0 +1,13 @@
+package com.android.reverse.request;
+
+
+import com.android.reverse.collecter.NativeHookCollecter;
+
+public class NativeHookInfoHandler implements CommandHandler {
+
+ @Override
+ public void doAction() {
+ NativeHookCollecter.getInstance().parserNativeHookInfo();
+ }
+
+}
diff --git a/src/com/android/reverse/smali/DexFileBuilder.java b/src/com/android/reverse/smali/DexFileBuilder.java
new file mode 100644
index 0000000..b9216e9
--- /dev/null
+++ b/src/com/android/reverse/smali/DexFileBuilder.java
@@ -0,0 +1,177 @@
+package com.android.reverse.smali;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStreamReader;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+
+import javax.annotation.Nonnull;
+
+import org.antlr.runtime.CommonTokenStream;
+import org.antlr.runtime.Token;
+import org.antlr.runtime.TokenSource;
+import org.antlr.runtime.tree.CommonTree;
+import org.antlr.runtime.tree.CommonTreeNodeStream;
+import org.jf.dexlib2.writer.builder.DexBuilder;
+import org.jf.dexlib2.writer.io.FileDataStore;
+import org.jf.smali.LexerErrorInterface;
+import org.jf.smali.smaliFlexLexer;
+import org.jf.smali.smaliParser;
+import org.jf.smali.smaliTreeWalker;
+
+import com.android.reverse.collecter.ModuleContext;
+import com.android.reverse.util.Logger;
+import com.google.common.collect.Lists;
+
+public class DexFileBuilder {
+
+ public static boolean buildDexFile(String smaliPath,String dexFileName) {
+
+ int jobs = 8;
+ boolean allowOdex = false;
+ boolean verboseErrors = false;
+ boolean printTokens = false;
+
+ int apiLevel = ModuleContext.getInstance().getApiLevel();
+
+ try {
+ LinkedHashSet filesToProcess = new LinkedHashSet();
+
+ File argFile = new File(smaliPath);
+
+ if (!argFile.exists()) {
+ throw new RuntimeException("Cannot find file or directory \""
+ + smaliPath + "\"");
+ }
+
+ if (argFile.isDirectory()) {
+ getSmaliFilesInDir(argFile, filesToProcess);
+ }
+
+ boolean errors = false;
+
+ final DexBuilder dexBuilder = DexBuilder.makeDexBuilder(apiLevel);
+ ExecutorService executor = Executors.newFixedThreadPool(jobs);
+ List> tasks = Lists.newArrayList();
+
+ final boolean finalVerboseErrors = verboseErrors;
+ final boolean finalPrintTokens = printTokens;
+ final boolean finalAllowOdex = allowOdex;
+ final int finalApiLevel = apiLevel;
+ for (final File file : filesToProcess) {
+ tasks.add(executor.submit(new Callable() {
+ @Override
+ public Boolean call() throws Exception {
+ return assembleSmaliFile(file, dexBuilder,
+ finalVerboseErrors, finalPrintTokens,
+ finalAllowOdex, finalApiLevel);
+ }
+ }));
+ }
+
+ for (Future task : tasks) {
+ while (true) {
+ try {
+ if (!task.get()) {
+ errors = true;
+ }
+ } catch (InterruptedException ex) {
+ continue;
+ }
+ break;
+ }
+ }
+
+ executor.shutdown();
+
+ if (errors) {
+ Logger.log("build the dexfile error0");
+ return false;
+ }
+
+ dexBuilder.writeTo(new FileDataStore(new File(dexFileName)));
+ Logger.log("build the dexfile ok");
+ return true;
+ } catch (RuntimeException ex) {
+ Logger.log("build the dexfile error1");
+ return false;
+ } catch (Throwable ex) {
+ Logger.log("build the dexfile error2");
+ return false;
+ }
+ }
+
+ private static void getSmaliFilesInDir(@Nonnull File dir,
+ @Nonnull Set smaliFiles) {
+ File[] files = dir.listFiles();
+ if (files != null) {
+ for (File file : files) {
+ if (file.isDirectory()) {
+ getSmaliFilesInDir(file, smaliFiles);
+ } else if (file.getName().endsWith(".smali")) {
+ smaliFiles.add(file);
+ }
+ }
+ }
+ }
+
+ private static boolean assembleSmaliFile(File smaliFile,
+ DexBuilder dexBuilder, boolean verboseErrors, boolean printTokens,
+ boolean allowOdex, int apiLevel) throws Exception {
+ CommonTokenStream tokens;
+
+ LexerErrorInterface lexer;
+
+ FileInputStream fis = new FileInputStream(smaliFile.getAbsolutePath());
+ InputStreamReader reader = new InputStreamReader(fis, "UTF-8");
+
+ lexer = new smaliFlexLexer(reader);
+ ((smaliFlexLexer) lexer).setSourceFile(smaliFile);
+ tokens = new CommonTokenStream((TokenSource) lexer);
+
+ if (printTokens) {
+ tokens.getTokens();
+
+ for (int i = 0; i < tokens.size(); i++) {
+ Token token = tokens.get(i);
+ if (token.getChannel() == smaliParser.HIDDEN) {
+ continue;
+ }
+
+ System.out.println(smaliParser.tokenNames[token.getType()]
+ + ": " + token.getText());
+ }
+ }
+
+ smaliParser parser = new smaliParser(tokens);
+ parser.setVerboseErrors(verboseErrors);
+ parser.setAllowOdex(allowOdex);
+ parser.setApiLevel(apiLevel);
+
+ smaliParser.smali_file_return result = parser.smali_file();
+
+ if (parser.getNumberOfSyntaxErrors() > 0
+ || lexer.getNumberOfSyntaxErrors() > 0) {
+ return false;
+ }
+
+ CommonTree t = result.getTree();
+
+ CommonTreeNodeStream treeStream = new CommonTreeNodeStream(t);
+ treeStream.setTokenStream(tokens);
+
+ smaliTreeWalker dexGen = new smaliTreeWalker(treeStream);
+ dexGen.setVerboseErrors(verboseErrors);
+ dexGen.setDexBuilder(dexBuilder);
+ dexGen.smali_file();
+
+ return dexGen.getNumberOfSyntaxErrors() == 0;
+ }
+
+}
diff --git a/src/com/android/reverse/smali/DexFileHeadersPointer.java b/src/com/android/reverse/smali/DexFileHeadersPointer.java
new file mode 100644
index 0000000..070e331
--- /dev/null
+++ b/src/com/android/reverse/smali/DexFileHeadersPointer.java
@@ -0,0 +1,67 @@
+package com.android.reverse.smali;
+
+public class DexFileHeadersPointer {
+
+ private int baseAddr;
+ private int pStringIds;
+ private int pTypeIds;
+ private int pFieldIds;
+ private int pMethodIds;
+ private int pProtoIds;
+ private int pClassDefs;
+ private int classCount;
+
+ public int getClassCount() {
+ return classCount;
+ }
+ public void setClassCount(int classCount) {
+ this.classCount = classCount;
+ }
+ public void setBaseAddr(int baseAddr) {
+ this.baseAddr = baseAddr;
+ }
+ public void setpStringIds(int pStringIds) {
+ this.pStringIds = pStringIds;
+ }
+ public void setpTypeIds(int pTypeIds) {
+ this.pTypeIds = pTypeIds;
+ }
+ public void setpFieldIds(int pFieldIds) {
+ this.pFieldIds = pFieldIds;
+ }
+ public void setpMethodIds(int pMethodIds) {
+ this.pMethodIds = pMethodIds;
+ }
+ public void setpProtoIds(int pProtoIds) {
+ this.pProtoIds = pProtoIds;
+ }
+ public void setpClassDefs(int pClassDefs) {
+ this.pClassDefs = pClassDefs;
+ }
+ public int getBaseAddr() {
+ return baseAddr;
+ }
+ public int getpStringIds() {
+ return pStringIds;
+ }
+ public int getpTypeIds() {
+ return pTypeIds;
+ }
+ public int getpFieldIds() {
+ return pFieldIds;
+ }
+ public int getpMethodIds() {
+ return pMethodIds;
+ }
+ public int getpProtoIds() {
+ return pProtoIds;
+ }
+ public int getpClassDefs() {
+ return pClassDefs;
+ }
+
+ public String toString(){
+ return "baseAddr:"+baseAddr+";pStringIds:"+pStringIds +";pTypeIds:"+pTypeIds+";pFieldIds:"+pFieldIds+";pMethodIds:"+pMethodIds+";pProtoIds:"+pProtoIds+";pClassDefs:"+pClassDefs;
+ }
+
+}
diff --git a/src/com/android/reverse/smali/MemoryBackSmali.java b/src/com/android/reverse/smali/MemoryBackSmali.java
new file mode 100644
index 0000000..237269d
--- /dev/null
+++ b/src/com/android/reverse/smali/MemoryBackSmali.java
@@ -0,0 +1,280 @@
+package com.android.reverse.smali;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import org.jf.baksmali.baksmaliOptions;
+import org.jf.baksmali.Adaptors.ClassDefinition;
+import org.jf.dexlib2.Opcodes;
+import org.jf.dexlib2.analysis.ClassPath;
+import org.jf.dexlib2.analysis.CustomInlineMethodResolver;
+import org.jf.dexlib2.dexbacked.DexBackedClassDef;
+import org.jf.dexlib2.dexbacked.DexBackedDexFile;
+import org.jf.dexlib2.dexbacked.MemoryDexFileItemPointer;
+import org.jf.dexlib2.dexbacked.MemoryReader;
+import org.jf.dexlib2.iface.ClassDef;
+import org.jf.dexlib2.util.SyntheticAccessorResolver;
+import org.jf.util.ClassFileNameHandler;
+import org.jf.util.IndentingWriter;
+
+import com.android.reverse.collecter.ModuleContext;
+import com.android.reverse.util.Logger;
+import com.android.reverse.util.NativeFunction;
+import com.google.common.collect.Ordering;
+
+public class MemoryBackSmali {
+
+ private static baksmaliOptions configOptions() {
+
+ baksmaliOptions options = new baksmaliOptions();
+ options.apiLevel = ModuleContext.getInstance().getApiLevel();
+ options.outputDirectory = ModuleContext.getInstance().getAppContext().getFilesDir().getAbsolutePath()+"/smali";
+ options.allowOdex = true;
+ options.deodex = true;
+ options.jobs = 8;
+ options.bootClassPathDirs.add("/system/framework/");
+ if (options.apiLevel >= 17) {
+ options.checkPackagePrivateAccess = true;
+ }
+ options.registerInfo = 128;
+ options.noAccessorComments = false;
+ options.useLocalsDirective = true;
+ options.noParameterRegisters = false;
+ options.useSequentialLabels = true;
+ options.outputDebugInfo = true;
+ options.addCodeOffsets = false;
+ return options;
+ }
+
+ public static boolean disassembleDexFile(int mCookie, String outDexName) {
+
+ long startTime = System.currentTimeMillis();
+ Logger.log("start disassemble the mCookie " + mCookie);
+ final baksmaliOptions options = configOptions();
+
+ File outputDirectoryFile = new File(options.outputDirectory);
+ if (!outputDirectoryFile.exists()) {
+ if (!outputDirectoryFile.mkdirs()) {
+ Logger.log("Can't create the output directory "
+ + options.outputDirectory);
+ return false;
+ }
+ }
+
+ Opcodes opcodes = new Opcodes(ModuleContext.getInstance().getApiLevel());
+
+ MemoryReader reader = new NativeFunction();
+ MemoryDexFileItemPointer pointer = NativeFunction
+ .queryDexFileItemPointer(mCookie);
+ DexBackedDexFile mmDexFile = new DexBackedDexFile(opcodes, pointer,
+ reader);
+
+ options.bootClassPathEntries = getDefaultBootClassPathForApi(options.apiLevel);
+ options.classPath = ClassPath.fromClassPath(options.bootClassPathDirs,
+ options.bootClassPathEntries, mmDexFile, options.apiLevel);
+ String inlineString = NativeFunction.getInlineOperation();
+ options.inlineResolver = new CustomInlineMethodResolver(
+ options.classPath, inlineString);
+
+ List extends ClassDef> classDefs = Ordering.natural().sortedCopy(
+ mmDexFile.getClasses());
+
+ if (!options.noAccessorComments) {
+ options.syntheticAccessorResolver = new SyntheticAccessorResolver(
+ classDefs);
+ }
+
+ final ClassFileNameHandler fileNameHandler = new ClassFileNameHandler(
+ outputDirectoryFile, ".smali");
+
+ ExecutorService executor = Executors.newFixedThreadPool(options.jobs);
+ List> tasks = new ArrayList>();
+
+ for (final ClassDef classDef : classDefs) {
+ tasks.add(executor.submit(new Callable() {
+ @Override
+ public Boolean call() throws Exception {
+ return disassembleClass(classDef, fileNameHandler, options);
+ }
+ }));
+ }
+
+ boolean errorOccurred = false;
+ try {
+ for (Future task : tasks) {
+ while (true) {
+ try {
+ if (!task.get()) {
+ errorOccurred = true;
+ }
+ } catch (InterruptedException ex) {
+ continue;
+ } catch (ExecutionException ex) {
+ throw new RuntimeException(ex);
+ }
+ break;
+ }
+ }
+ } finally {
+ executor.shutdown();
+ }
+
+ Logger.log("end disassemble the mCookie: cost time = "
+ + ((System.currentTimeMillis() - startTime) / 1000) +"s");
+ startTime = System.currentTimeMillis();
+ Logger.log("start build the smali files to dex");
+ boolean result = DexFileBuilder.buildDexFile(options.outputDirectory,outDexName);
+ Logger.log("end build the smali files to dex: cost time = "
+ + ((System.currentTimeMillis() - startTime) / 1000)+"s");
+ if (result) {
+ try {
+ Runtime.getRuntime().exec("rm -rf " + options.outputDirectory);
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+ return !errorOccurred;
+
+ }
+
+ private static boolean disassembleClass(ClassDef classDef,
+ ClassFileNameHandler fileNameHandler, baksmaliOptions options) {
+
+ DexBackedClassDef classdf = (DexBackedClassDef) classDef;
+ if (!classdf.isValid())
+ return false;
+ String classDescriptor = classDef.getType();
+ //Logger.log("start backsmali the class = " + classDescriptor);
+ // validate that the descriptor is formatted like we expect
+ if (classDescriptor.charAt(0) != 'L'
+ || classDescriptor.charAt(classDescriptor.length() - 1) != ';') {
+ Logger.log("Unrecognized class descriptor - " + classDescriptor
+ + " - skipping class");
+ return false;
+ }
+
+ File smaliFile = fileNameHandler
+ .getUniqueFilenameForClass(classDescriptor);
+
+ // create and initialize the top level string template
+ ClassDefinition classDefinition = new ClassDefinition(options, classDef);
+
+ // write the disassembly
+ Writer writer = null;
+ try {
+ File smaliParent = smaliFile.getParentFile();
+ if (!smaliParent.exists()) {
+ if (!smaliParent.mkdirs()) {
+ // check again, it's likely it was created in a different
+ // thread
+ if (!smaliParent.exists()) {
+ Logger.log("Unable to create directory "
+ + smaliParent.toString() + " - skipping class");
+ return false;
+ }
+ }
+ }
+
+ if (!smaliFile.exists()) {
+ if (!smaliFile.createNewFile()) {
+ Logger.log("Unable to create file " + smaliFile.toString()
+ + " - skipping class");
+ return false;
+ }
+ }
+
+ BufferedWriter bufWriter = new BufferedWriter(
+ new OutputStreamWriter(new FileOutputStream(smaliFile),
+ "UTF8"));
+
+ writer = new IndentingWriter(bufWriter);
+ classDefinition.writeTo((IndentingWriter) writer);
+ } catch (Exception ex) {
+ Logger.log("\n\nError occurred while disassembling class "
+ + classDescriptor.replace('/', '.') + " - skipping class");
+ ex.printStackTrace();
+ // noinspection ResultOfMethodCallIgnored
+ smaliFile.delete();
+ return false;
+ } finally {
+ if (writer != null) {
+ try {
+ writer.close();
+ } catch (Throwable ex) {
+ Logger.log("\n\nError occurred while closing file "
+ + smaliFile.toString());
+ ex.printStackTrace();
+ }
+ }
+ }
+ return true;
+ }
+
+ private static List getDefaultBootClassPathForApi(int apiLevel) {
+ List list = new ArrayList();
+ if (apiLevel < 9) {
+ list.add("/system/framework/core.jar");
+ list.add("/system/framework/ext.jar");
+ list.add("/system/framework/framework.jar");
+ list.add("/system/framework/android.policy.jar");
+ list.add("/system/framework/services.jar");
+ } else if (apiLevel < 12) {
+
+ list.add("/system/framework/core.jar");
+ list.add("/system/framework/bouncycastle.jar");
+ list.add("/system/framework/ext.jar");
+ list.add("/system/framework/framework.jar");
+ list.add("/system/framework/android.policy.jar");
+ list.add("/system/framework/services.jar");
+ list.add("/system/framework/core-junit.jar");
+ } else if (apiLevel < 14) {
+
+ list.add("/system/framework/core.jar");
+ list.add("/system/framework/apache-xml.jar");
+ list.add("/system/framework/bouncycastle.jar");
+ list.add("/system/framework/ext.jar");
+ list.add("/system/framework/framework.jar");
+ list.add("/system/framework/android.policy.jar");
+ list.add("/system/framework/services.jar");
+ list.add("/system/framework/core-junit.jar");
+ } else if (apiLevel < 16) {
+
+ list.add("/system/framework/core.jar");
+ list.add("/system/framework/core-junit.jar");
+ list.add("/system/framework/bouncycastle.jar");
+ list.add("/system/framework/ext.jar");
+ list.add("/system/framework/framework.jar");
+ list.add("/system/framework/android.policy.jar");
+ list.add("/system/framework/services.jar");
+ list.add("/system/framework/apache-xml.jar");
+ list.add("/system/framework/filterfw.jar");
+
+ } else {
+ // this is correct as of api 17/4.2.2
+
+ list.add("/system/framework/core.jar");
+ list.add("/system/framework/core-junit.jar");
+ list.add("/system/framework/bouncycastle.jar");
+ list.add("/system/framework/ext.jar");
+ list.add("/system/framework/framework.jar");
+ list.add("/system/framework/telephony-common.jar");
+ list.add("/system/framework/mms-common.jar");
+ list.add("/system/framework/android.policy.jar");
+ list.add("/system/framework/services.jar");
+ list.add("/system/framework/apache-xml.jar");
+ }
+ return list;
+ }
+
+}
diff --git a/src/com/android/reverse/util/Constant.java b/src/com/android/reverse/util/Constant.java
new file mode 100644
index 0000000..cfe7204
--- /dev/null
+++ b/src/com/android/reverse/util/Constant.java
@@ -0,0 +1,7 @@
+package com.android.reverse.util;
+
+public class Constant {
+
+ public static String FILESAVEPATH = "XAndReverseTool";
+
+}
diff --git a/src/com/android/reverse/util/JsonWriter.java b/src/com/android/reverse/util/JsonWriter.java
new file mode 100644
index 0000000..588be59
--- /dev/null
+++ b/src/com/android/reverse/util/JsonWriter.java
@@ -0,0 +1,99 @@
+package com.android.reverse.util;
+
+import java.lang.reflect.Field;
+import java.util.Collection;
+import java.util.Map;
+
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+public class JsonWriter {
+
+ public static String parserInstanceToJson(Object data) throws Exception {
+
+ if (data.getClass().isPrimitive() || isSimpleType(data)) {
+ return data.toString();
+ }
+ JSONObject result = new JSONObject();
+ Field[] fields = data.getClass().getDeclaredFields();
+ Field field = null;
+ for (int i = 0; i < fields.length; i++) {
+ field = fields[i];
+ field.setAccessible(true);
+ Object value = field.get(data);
+ if (field.getType().isPrimitive()) {
+ result.put(field.getName(), value);
+ } else {
+ if (isSimpleType(value)) {
+ result.put(field.getName(), value);
+ } else if (value == null) {
+ result.put(field.getName(), null);
+ } else if (isSimpleTypeArray(value)) {
+ JSONArray arrayData = new JSONArray();
+ Object[] objArray = (Object[]) value;
+ if (objArray != null) {
+ for (int j = 0; j < objArray.length; j++) {
+ arrayData.put(objArray[j]);
+ }
+ }
+ result.put(field.getName(), arrayData);
+ } else if (value instanceof Object[]) {
+ JSONArray arrayData = new JSONArray();
+ Object[] objArray = (Object[]) value;
+ for (int j = 0; j < objArray.length; j++) {
+ arrayData.put(parserInstanceToJson(objArray[j]));
+ }
+ result.put(field.getName(), arrayData);
+ } else if (value instanceof Collection) {
+ JSONArray arrayData = new JSONArray();
+ Object[] objArray = ((Collection) value).toArray();
+ for (int j = 0; j < objArray.length; j++) {
+ if (isSimpleType(objArray[j]))
+ arrayData.put(objArray[j]);
+ else
+ arrayData.put(parserInstanceToJson(objArray[j]));
+ }
+ result.put(field.getName(), arrayData);
+ } else if (value instanceof Map) {
+ JSONArray arrayData = new JSONArray();
+ Map map = (Map) value;
+ Object[] keyArray = map.keySet().toArray();
+ for (int j = 0; j < keyArray.length; j++) {
+ JSONObject obj = new JSONObject();
+ obj.put("key", parserInstanceToJson(keyArray[j]));
+ obj.put("value", parserInstanceToJson(map.get(keyArray[j])));
+ arrayData.put(obj);
+ }
+ result.put(field.getName(), arrayData);
+ }
+
+ else if (value instanceof Object) {
+ result.put(field.getName(), parserInstanceToJson(value));
+ } else {
+ Logger.log("the field:" + field.getName() + " can't covert to json");
+ }
+ }
+ }
+ return result.toString();
+ }
+
+ private static boolean isSimpleType(Object obj) {
+
+ if (obj instanceof Integer || obj instanceof Long || obj instanceof Double || obj instanceof Float || obj instanceof Byte
+ || obj instanceof Short || obj instanceof Character || obj instanceof Boolean || obj instanceof String) {
+ return true;
+
+ }
+ return false;
+ }
+
+ private static boolean isSimpleTypeArray(Object obj) {
+
+ if (obj instanceof Integer[] || obj instanceof Long[] || obj instanceof Double[] || obj instanceof Float[] || obj instanceof Byte[]
+ || obj instanceof Short[] || obj instanceof Character[] || obj instanceof Boolean[] || obj instanceof String[]) {
+ return true;
+
+ }
+ return false;
+ }
+}
diff --git a/src/com/android/reverse/util/Logger.java b/src/com/android/reverse/util/Logger.java
new file mode 100644
index 0000000..abb8d9c
--- /dev/null
+++ b/src/com/android/reverse/util/Logger.java
@@ -0,0 +1,22 @@
+package com.android.reverse.util;
+
+import android.util.Log;
+
+public class Logger {
+
+ public static String LOGTAG_COMMAN = "zjdroid-shell-";
+ public static String LOGTAG_WORKFLOW = "zjdroid-apimonitor-";
+ public static boolean DEBUG_ENABLE = true;
+ public static String PACKAGENAME;
+
+ public static void log(String message){
+ if(DEBUG_ENABLE)
+ Log.d(LOGTAG_COMMAN+PACKAGENAME,message);
+ }
+
+ public static void log_behavior(String message){
+ if(DEBUG_ENABLE)
+ Log.d(LOGTAG_WORKFLOW+PACKAGENAME,message);
+ }
+
+}
diff --git a/src/com/android/reverse/util/NativeFunction.java b/src/com/android/reverse/util/NativeFunction.java
new file mode 100644
index 0000000..4c07f6a
--- /dev/null
+++ b/src/com/android/reverse/util/NativeFunction.java
@@ -0,0 +1,54 @@
+package com.android.reverse.util;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.HashMap;
+
+import org.jf.dexlib2.dexbacked.MemoryDexFileItemPointer;
+import org.jf.dexlib2.dexbacked.MemoryReader;
+
+import com.android.reverse.collecter.ModuleContext;
+import com.android.reverse.smali.DexFileHeadersPointer;
+
+
+public class NativeFunction implements MemoryReader {
+
+ private final static String DVMNATIVE_LIB = "dvmnative";
+
+ static{
+ System.loadLibrary(DVMNATIVE_LIB);
+ }
+
+ public static native ByteBuffer dumpDexFileByClass(Class classInDex,int version);
+ public static native ByteBuffer dumpDexFileByCookie(int cookie,int version);
+ public static native ByteBuffer dumpMemory(int start,int length);
+ private static native DexFileHeadersPointer getHeaderItemPtr(int cookie,int version);
+ public static native String getInlineOperation();
+ public static native HashMap getSyslinkSnapshot();
+
+ public byte[] readBytes(int arg0, int arg1) {
+ // TODO Auto-generated method stub
+ ByteBuffer data = dumpMemory(arg0, arg1);
+ data.order(ByteOrder.LITTLE_ENDIAN);
+ byte[] buffer = new byte[data.capacity()];
+ data.get(buffer, 0, data.capacity());
+ return buffer;
+ }
+
+ public static MemoryDexFileItemPointer queryDexFileItemPointer(int cookie){
+ int version = ModuleContext.getInstance().getApiLevel();
+ DexFileHeadersPointer iteminfo = getHeaderItemPtr(cookie,version);
+ MemoryDexFileItemPointer pointer = new MemoryDexFileItemPointer();
+ pointer.setBaseAddr(iteminfo.getBaseAddr());
+ pointer.setpClassDefs(iteminfo.getpClassDefs());
+ pointer.setpFieldIds(iteminfo.getpFieldIds());
+ pointer.setpMethodIds(iteminfo.getpMethodIds());
+ pointer.setpProtoIds(iteminfo.getpProtoIds());
+ pointer.setpStringIds(iteminfo.getpStringIds());
+ pointer.setpTypeIds(iteminfo.getpTypeIds());
+ pointer.setClassCount(iteminfo.getClassCount());
+ return pointer;
+
+ }
+
+}
diff --git a/src/com/android/reverse/util/RefInvoke.java b/src/com/android/reverse/util/RefInvoke.java
new file mode 100644
index 0000000..31307cf
--- /dev/null
+++ b/src/com/android/reverse/util/RefInvoke.java
@@ -0,0 +1,272 @@
+package com.android.reverse.util;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+import de.robv.android.xposed.XposedBridge;
+
+public class RefInvoke {
+
+
+ /** @see #findMethodExact(Class, String, Object...) */
+ public static Method findMethodExact(String className, ClassLoader classLoader, String methodName, Class>... parameterTypes) {
+
+ try {
+ Class clazz = classLoader.loadClass(className);
+ Method method = clazz.getDeclaredMethod(methodName, parameterTypes);
+ method.setAccessible(true);
+ return method;
+ } catch (NoSuchMethodException e) {
+ e.printStackTrace();
+ } catch (ClassNotFoundException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+
+ public static Object invokeStaticMethod(String class_name, String method_name, Class[] pareTyple, Object[] pareVaules){
+
+ try {
+ Class obj_class = Class.forName(class_name);
+ Method method = obj_class.getDeclaredMethod(method_name,pareTyple);
+ method.setAccessible(true);
+ return method.invoke(null, pareVaules);
+ } catch (SecurityException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (IllegalArgumentException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (IllegalAccessException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (NoSuchMethodException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (InvocationTargetException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (ClassNotFoundException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ return null;
+
+ }
+
+ public static Object invokeMethod(String class_name, String method_name, Object obj ,Class[] pareTyple, Object[] pareVaules){
+
+ try {
+ Class obj_class = Class.forName(class_name);
+ Method method = obj_class.getDeclaredMethod(method_name,pareTyple);
+ method.setAccessible(true);
+ return method.invoke(obj, pareVaules);
+ } catch (SecurityException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (IllegalArgumentException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (IllegalAccessException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (NoSuchMethodException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (InvocationTargetException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (ClassNotFoundException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ return null;
+
+ }
+
+ public static Object invokeDeclaredMethod(String class_name, String method_name, Object obj ,Class[] pareTyple, Object[] pareVaules){
+
+ try {
+ Class obj_class = Class.forName(class_name);
+ Method method = obj_class.getDeclaredMethod(method_name,pareTyple);
+ method.setAccessible(true);
+ return method.invoke(obj, pareVaules);
+ } catch (SecurityException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (IllegalArgumentException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (IllegalAccessException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (NoSuchMethodException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (InvocationTargetException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (ClassNotFoundException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ return null;
+
+ }
+
+ public static int getFieldInt(String class_name,Object obj, String filedName){
+ try {
+ Class obj_class = Class.forName(class_name);
+ Field field = obj_class.getDeclaredField(filedName);
+ field.setAccessible(true);
+ return field.getInt(obj);
+ } catch (SecurityException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (NoSuchFieldException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (IllegalArgumentException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (IllegalAccessException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (ClassNotFoundException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ return -1;
+
+ }
+
+
+ public static Object getFieldOjbect(String class_name,Object obj, String filedName){
+ try {
+ Class obj_class = Class.forName(class_name);
+ Field field = obj_class.getDeclaredField(filedName);
+ field.setAccessible(true);
+ return field.get(obj);
+ } catch (SecurityException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (NoSuchFieldException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (IllegalArgumentException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (IllegalAccessException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (ClassNotFoundException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ return null;
+
+ }
+
+ public static Object getStaticFieldOjbect(String class_name, String filedName){
+
+ try {
+ Class obj_class = Class.forName(class_name);
+ Field field = obj_class.getDeclaredField(filedName);
+ field.setAccessible(true);
+ return field.get(null);
+ } catch (SecurityException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (NoSuchFieldException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (IllegalArgumentException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (IllegalAccessException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (ClassNotFoundException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ return null;
+
+ }
+
+ public static void setFieldOjbect(String classname, String filedName, Object obj, Object filedVaule){
+ try {
+ Class obj_class = Class.forName(classname);
+ Field field = obj_class.getDeclaredField(filedName);
+ field.setAccessible(true);
+ field.set(obj, filedVaule);
+ } catch (SecurityException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (NoSuchFieldException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (IllegalArgumentException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (IllegalAccessException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (ClassNotFoundException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+
+ public static void setFieldInt(String className, String fieldName, Object obj, int value) {
+ try {
+ Class obj_class = Class.forName(className);
+ Field field = obj_class.getDeclaredField(fieldName);
+ field.setAccessible(true);
+ field.setInt(obj, value);
+ } catch (SecurityException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (NoSuchFieldException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (IllegalArgumentException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (IllegalAccessException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (ClassNotFoundException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+
+ public static void setStaticOjbect(String class_name, String filedName, Object filedVaule){
+ try {
+ Class obj_class = Class.forName(class_name);
+ Field field = obj_class.getDeclaredField(filedName);
+ field.setAccessible(true);
+ field.set(null, filedVaule);
+ } catch (SecurityException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (NoSuchFieldException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (IllegalArgumentException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (IllegalAccessException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (ClassNotFoundException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+
+}
diff --git a/src/com/android/reverse/util/Utility.java b/src/com/android/reverse/util/Utility.java
new file mode 100644
index 0000000..519b2d0
--- /dev/null
+++ b/src/com/android/reverse/util/Utility.java
@@ -0,0 +1,36 @@
+package com.android.reverse.util;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+public class Utility {
+
+ public static int getApiLevel() {
+
+ try {
+ Class> mClassType = Class.forName("android.os.SystemProperties");
+ Method mGetIntMethod = mClassType.getDeclaredMethod("getInt",
+ String.class, int.class);
+ mGetIntMethod.setAccessible(true);
+ return (Integer)mGetIntMethod.invoke(null, "ro.build.version.sdk",14);
+ } catch (ClassNotFoundException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (NoSuchMethodException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (IllegalArgumentException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (IllegalAccessException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (InvocationTargetException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ return 14;
+
+ }
+
+}
diff --git a/src/ds/tree/DuplicateKeyException.java b/src/ds/tree/DuplicateKeyException.java
new file mode 100644
index 0000000..5f660b3
--- /dev/null
+++ b/src/ds/tree/DuplicateKeyException.java
@@ -0,0 +1,41 @@
+/*
+The MIT License
+
+Copyright (c) 2008 Tahseen Ur Rehman
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+*/
+
+package ds.tree;
+
+/**
+ * excepion thrown if a duplicate key is inserted in a {@link RadixTree}
+ *
+ * @author Tahseen Ur Rehman
+ * email: tahseen.ur.rehman {at.spam.me.not} gmail.com
+ */
+public class DuplicateKeyException extends RuntimeException
+{
+ private static final long serialVersionUID = 3141795907493885706L;
+
+ public DuplicateKeyException(String msg)
+ {
+ super(msg);
+ }
+}
diff --git a/src/ds/tree/RadixTree.java b/src/ds/tree/RadixTree.java
new file mode 100644
index 0000000..bf05e9d
--- /dev/null
+++ b/src/ds/tree/RadixTree.java
@@ -0,0 +1,115 @@
+/*
+The MIT License
+
+Copyright (c) 2008 Tahseen Ur Rehman
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+*/
+
+package ds.tree;
+
+import java.util.ArrayList;
+
+/**
+ * This interface represent the operation of a radix tree. A radix tree,
+ * Patricia trie/tree, or crit bit tree is a specialized set data structure
+ * based on the trie that is used to store a set of strings. In contrast with a
+ * regular trie, the edges of a Patricia trie are labelled with sequences of
+ * characters rather than with single characters. These can be strings of
+ * characters, bit strings such as integers or IP addresses, or generally
+ * arbitrary sequences of objects in lexicographical order. Sometimes the names
+ * radix tree and crit bit tree are only applied to trees storing integers and
+ * Patricia trie is retained for more general inputs, but the structure works
+ * the same way in all cases.
+ *
+ * @author Tahseen Ur Rehman
+ * email: tahseen.ur.rehman {at.spam.me.not} gmail.com
+ */
+public interface RadixTree {
+ /**
+ * Insert a new string key and its value to the tree.
+ *
+ * @param key
+ * The string key of the object
+ * @param value
+ * The value that need to be stored corresponding to the given
+ * key.
+ * @throws DuplicateKeyException
+ */
+ public void insert(String key, T value);
+
+ /**
+ * Delete a key and its associated value from the tree.
+ * @param key The key of the node that need to be deleted
+ * @return
+ */
+ public boolean delete(String key);
+
+ /**
+ * Find a value based on its corresponding key.
+ *
+ * @param key The key for which to search the tree.
+ * @return The value corresponding to the key. null if iot can not find the key
+ */
+ public T find(String key);
+
+ /**
+ * Find an existing entry and replace it's value. If no existing entry, do nothing
+ *
+ * @param key The key for which to search the tree.
+ * @param value The value to set for the entry
+ * @return true if an entry was found for the given key, false if not found
+ */
+ public boolean replace(String key, final T value);
+
+ /**
+ * Check if the tree contains any entry corresponding to the given key.
+ *
+ * @param key The key that needto be searched in the tree.
+ * @return retun true if the key is present in the tree otherwise false
+ */
+ public boolean contains(String key);
+
+ /**
+ * Search for all the keys that start with given prefix. limiting the results based on the supplied limit.
+ *
+ * @param prefix The prefix for which keys need to be search
+ * @param recordLimit The limit for the results
+ * @return The list of values those key start with the given prefix
+ */
+ public ArrayList searchPrefix(String prefix, int recordLimit);
+
+ /**
+ * Return the size of the Radix tree
+ * @return the size of the tree
+ */
+ public long getSize();
+
+ /**
+ * Complete the a prefix to the point where ambiguity starts.
+ *
+ * Example:
+ * If a tree contain "blah1", "blah2"
+ * complete("b") -> return "blah"
+ *
+ * @param prefix The prefix we want to complete
+ * @return The unambiguous completion of the string.
+ */
+ public String complete(String prefix);
+}
diff --git a/src/ds/tree/RadixTreeImpl.java b/src/ds/tree/RadixTreeImpl.java
new file mode 100644
index 0000000..58797d4
--- /dev/null
+++ b/src/ds/tree/RadixTreeImpl.java
@@ -0,0 +1,462 @@
+/*
+The MIT License
+
+Copyright (c) 2008 Tahseen Ur Rehman, Javid Jamae
+
+http://code.google.com/p/radixtree/
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+*/
+
+package ds.tree;
+
+import java.util.ArrayList;
+import java.util.Formattable;
+import java.util.Formatter;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.Queue;
+
+/**
+ * Implementation for Radix tree {@link RadixTree}
+ *
+ * @author Tahseen Ur Rehman (tahseen.ur.rehman {at.spam.me.not} gmail.com)
+ * @author Javid Jamae
+ * @author Dennis Heidsiek
+ */
+public class RadixTreeImpl implements RadixTree, Formattable {
+
+ protected RadixTreeNode root;
+
+ protected long size;
+
+ /**
+ * Create a Radix Tree with only the default node root.
+ */
+ public RadixTreeImpl() {
+ root = new RadixTreeNode();
+ root.setKey("");
+ size = 0;
+ }
+
+ public T find(String key) {
+ Visitor visitor = new VisitorImpl() {
+
+ public void visit(String key, RadixTreeNode parent,
+ RadixTreeNode node) {
+ if (node.isReal())
+ result = node.getValue();
+ }
+ };
+
+ visit(key, visitor);
+
+ return visitor.getResult();
+ }
+
+ public boolean replace(String key, final T value) {
+ Visitor visitor = new VisitorImpl() {
+ public void visit(String key, RadixTreeNode parent, RadixTreeNode node) {
+ if (node.isReal()) {
+ node.setValue(value);
+ result = value;
+ } else {
+ result = null;
+ }
+ }
+ };
+
+ visit(key, visitor);
+
+ return visitor.getResult() != null;
+ }
+
+ public boolean delete(String key) {
+ Visitor visitor = new VisitorImpl(Boolean.FALSE) {
+ public void visit(String key, RadixTreeNode parent,
+ RadixTreeNode node) {
+ result = node.isReal();
+
+ // if it is a real node
+ if (result) {
+ // If there no children of the node we need to
+ // delete it from the its parent children list
+ if (node.getChildern().size() == 0) {
+ Iterator> it = parent.getChildern()
+ .iterator();
+ while (it.hasNext()) {
+ if (it.next().getKey().equals(node.getKey())) {
+ it.remove();
+ break;
+ }
+ }
+
+ // if parent is not real node and has only one child
+ // then they need to be merged.
+ if (parent.getChildern().size() == 1
+ && parent.isReal() == false) {
+ mergeNodes(parent, parent.getChildern().get(0));
+ }
+ } else if (node.getChildern().size() == 1) {
+ // we need to merge the only child of this node with
+ // itself
+ mergeNodes(node, node.getChildern().get(0));
+ } else { // we jus need to mark the node as non real.
+ node.setReal(false);
+ }
+ }
+ }
+
+ /**
+ * Merge a child into its parent node. Operation only valid if it is
+ * only child of the parent node and parent node is not a real node.
+ *
+ * @param parent
+ * The parent Node
+ * @param child
+ * The child Node
+ */
+ private void mergeNodes(RadixTreeNode parent,
+ RadixTreeNode child) {
+ parent.setKey(parent.getKey() + child.getKey());
+ parent.setReal(child.isReal());
+ parent.setValue(child.getValue());
+ parent.setChildern(child.getChildern());
+ }
+
+ };
+
+ visit(key, visitor);
+
+ if(visitor.getResult()) {
+ size--;
+ }
+ return visitor.getResult().booleanValue();
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see ds.tree.RadixTree#insert(java.lang.String, java.lang.Object)
+ */
+ public void insert(String key, T value) throws DuplicateKeyException {
+ try {
+ insert(key, root, value);
+ } catch (DuplicateKeyException e) {
+ // re-throw the exception with 'key' in the message
+ throw new DuplicateKeyException("Duplicate key: '" + key + "'");
+ }
+ size++;
+ }
+
+ /**
+ * Recursively insert the key in the radix tree.
+ *
+ * @param key The key to be inserted
+ * @param node The current node
+ * @param value The value associated with the key
+ * @throws DuplicateKeyException If the key already exists in the database.
+ */
+ private void insert(String key, RadixTreeNode node, T value)
+ throws DuplicateKeyException {
+
+ int numberOfMatchingCharacters = node.getNumberOfMatchingCharacters(key);
+
+ // we are either at the root node
+ // or we need to go down the tree
+ if (node.getKey().equals("") == true || numberOfMatchingCharacters == 0 || (numberOfMatchingCharacters < key.length() && numberOfMatchingCharacters >= node.getKey().length())) {
+ boolean flag = false;
+ String newText = key.substring(numberOfMatchingCharacters, key.length());
+ for (RadixTreeNode child : node.getChildern()) {
+ if (child.getKey().startsWith(newText.charAt(0) + "")) {
+ flag = true;
+ insert(newText, child, value);
+ break;
+ }
+ }
+
+ // just add the node as the child of the current node
+ if (flag == false) {
+ RadixTreeNode n = new RadixTreeNode();
+ n.setKey(newText);
+ n.setReal(true);
+ n.setValue(value);
+
+ node.getChildern().add(n);
+ }
+ }
+ // there is a exact match just make the current node as data node
+ else if (numberOfMatchingCharacters == key.length() && numberOfMatchingCharacters == node.getKey().length()) {
+ if (node.isReal() == true) {
+ throw new DuplicateKeyException("Duplicate key");
+ }
+
+ node.setReal(true);
+ node.setValue(value);
+ }
+ // This node need to be split as the key to be inserted
+ // is a prefix of the current node key
+ else if (numberOfMatchingCharacters > 0 && numberOfMatchingCharacters < node.getKey().length()) {
+ RadixTreeNode n1 = new RadixTreeNode();
+ n1.setKey(node.getKey().substring(numberOfMatchingCharacters, node.getKey().length()));
+ n1.setReal(node.isReal());
+ n1.setValue(node.getValue());
+ n1.setChildern(node.getChildern());
+
+ node.setKey(key.substring(0, numberOfMatchingCharacters));
+ node.setReal(false);
+ node.setChildern(new ArrayList>());
+ node.getChildern().add(n1);
+
+ if(numberOfMatchingCharacters < key.length()) {
+ RadixTreeNode n2 = new RadixTreeNode();
+ n2.setKey(key.substring(numberOfMatchingCharacters, key.length()));
+ n2.setReal(true);
+ n2.setValue(value);
+
+ node.getChildern().add(n2);
+ } else {
+ node.setValue(value);
+ node.setReal(true);
+ }
+ }
+ // this key need to be added as the child of the current node
+ else {
+ RadixTreeNode n = new RadixTreeNode();
+ n.setKey(node.getKey().substring(numberOfMatchingCharacters, node.getKey().length()));
+ n.setChildern(node.getChildern());
+ n.setReal(node.isReal());
+ n.setValue(node.getValue());
+
+ node.setKey(key);
+ node.setReal(true);
+ node.setValue(value);
+
+ node.getChildern().add(n);
+ }
+ }
+
+ public ArrayList searchPrefix(String key, int recordLimit) {
+ ArrayList keys = new ArrayList();
+
+ RadixTreeNode node = searchPefix(key, root);
+
+ if (node != null) {
+ if (node.isReal()) {
+ keys.add(node.getValue());
+ }
+ getNodes(node, keys, recordLimit);
+ }
+
+ return keys;
+ }
+
+ private void getNodes(RadixTreeNode parent, ArrayList keys, int limit) {
+ Queue> queue = new LinkedList>();
+
+ queue.addAll(parent.getChildern());
+
+ while (!queue.isEmpty()) {
+ RadixTreeNode node = queue.remove();
+ if (node.isReal() == true) {
+ keys.add(node.getValue());
+ }
+
+ if (keys.size() == limit) {
+ break;
+ }
+
+ queue.addAll(node.getChildern());
+ }
+ }
+
+ private RadixTreeNode searchPefix(String key, RadixTreeNode node) {
+ RadixTreeNode result = null;
+
+ int numberOfMatchingCharacters = node.getNumberOfMatchingCharacters(key);
+
+ if (numberOfMatchingCharacters == key.length() && numberOfMatchingCharacters <= node.getKey().length()) {
+ result = node;
+ } else if (node.getKey().equals("") == true
+ || (numberOfMatchingCharacters < key.length() && numberOfMatchingCharacters >= node.getKey().length())) {
+ String newText = key.substring(numberOfMatchingCharacters, key.length());
+ for (RadixTreeNode child : node.getChildern()) {
+ if (child.getKey().startsWith(newText.charAt(0) + "")) {
+ result = searchPefix(newText, child);
+ break;
+ }
+ }
+ }
+
+ return result;
+ }
+
+ public boolean contains(String key) {
+ Visitor visitor = new VisitorImpl(Boolean.FALSE) {
+ public void visit(String key, RadixTreeNode parent,
+ RadixTreeNode node) {
+ result = node.isReal();
+ }
+ };
+
+ visit(key, visitor);
+
+ return visitor.getResult().booleanValue();
+ }
+
+ /**
+ * visit the node those key matches the given key
+ * @param key The key that need to be visited
+ * @param visitor The visitor object
+ */
+ public void visit(String key, Visitor visitor) {
+ if (root != null) {
+ visit(key, visitor, null, root);
+ }
+ }
+
+ /**
+ * recursively visit the tree based on the supplied "key". calls the Visitor
+ * for the node those key matches the given prefix
+ *
+ * @param prefix
+ * The key o prefix to search in the tree
+ * @param visitor
+ * The Visitor that will be called if a node with "key" as its
+ * key is found
+ * @param node
+ * The Node from where onward to search
+ */
+ private void visit(String prefix, Visitor visitor,
+ RadixTreeNode parent, RadixTreeNode node) {
+
+ int numberOfMatchingCharacters = node.getNumberOfMatchingCharacters(prefix);
+
+ // if the node key and prefix match, we found a match!
+ if (numberOfMatchingCharacters == prefix.length() && numberOfMatchingCharacters == node.getKey().length()) {
+ visitor.visit(prefix, parent, node);
+ } else if (node.getKey().equals("") == true // either we are at the
+ // root
+ || (numberOfMatchingCharacters < prefix.length() && numberOfMatchingCharacters >= node.getKey().length())) { // OR we need to
+ // traverse the childern
+ String newText = prefix.substring(numberOfMatchingCharacters, prefix.length());
+ for (RadixTreeNode child : node.getChildern()) {
+ // recursively search the child nodes
+ if (child.getKey().startsWith(newText.charAt(0) + "")) {
+ visit(newText, visitor, node, child);
+ break;
+ }
+ }
+ }
+ }
+
+ public long getSize() {
+ return size;
+ }
+
+ /**
+ * Display the Trie on console.
+ *
+ * WARNING! Do not use this for a large Trie, it's for testing purpose only.
+ * @see formatTo
+ */
+ @Deprecated
+ public void display() {
+ formatNodeTo(new Formatter(System.out), 0, root);
+ }
+
+ @Deprecated
+ private void display(int level, RadixTreeNode node) {
+ formatNodeTo(new Formatter(System.out), level, node);
+ }
+
+ /**
+ * WARNING! Do not use this for a large Trie, it's for testing purpose only.
+ */
+ private void formatNodeTo(Formatter f, int level, RadixTreeNode node) {
+ for (int i = 0; i < level; i++) {
+ f.format(" ");
+ }
+ f.format("|");
+ for (int i = 0; i < level; i++) {
+ f.format("-");
+ }
+
+ if (node.isReal() == true)
+ f.format("%s[%s]*%n", node.getKey(), node.getValue());
+ else
+ f.format("%s%n", node.getKey());
+
+ for (RadixTreeNode child : node.getChildern()) {
+ formatNodeTo(f, level + 1, child);
+ }
+ }
+
+ /**
+ * Writes a textual representation of this tree to the given formatter.
+ *
+ * Currently, all options are simply ignored.
+ *
+ * WARNING! Do not use this for a large Trie, it's for testing purpose only.
+ */
+ public void formatTo(Formatter formatter, int flags, int width, int precision) {
+ formatNodeTo(formatter, 0, root);
+ }
+
+ /**
+ * Complete the a prefix to the point where ambiguity starts.
+ *
+ * Example:
+ * If a tree contain "blah1", "blah2"
+ * complete("b") -> return "blah"
+ *
+ * @param prefix The prefix we want to complete
+ * @return The unambiguous completion of the string.
+ */
+ public String complete(String prefix) {
+ return complete(prefix, root, "");
+ }
+
+ private String complete(String key, RadixTreeNode node, String base) {
+ int i = 0;
+ int keylen = key.length();
+ int nodelen = node.getKey().length();
+
+ while (i < keylen && i < nodelen) {
+ if (key.charAt(i) != node.getKey().charAt(i)) {
+ break;
+ }
+ i++;
+ }
+
+ if (i == keylen && i <= nodelen) {
+ return base + node.getKey();
+ }
+ else if (nodelen == 0 || (i < keylen && i >= nodelen)) {
+ String beginning = key.substring(0, i);
+ String ending = key.substring(i, keylen);
+ for (RadixTreeNode child : node.getChildern()) {
+ if (child.getKey().startsWith(ending.charAt(0) + "")) {
+ return complete(ending, child, base + beginning);
+ }
+ }
+ }
+
+ return "";
+ }
+}
\ No newline at end of file
diff --git a/src/ds/tree/RadixTreeNode.java b/src/ds/tree/RadixTreeNode.java
new file mode 100644
index 0000000..fabdd38
--- /dev/null
+++ b/src/ds/tree/RadixTreeNode.java
@@ -0,0 +1,103 @@
+/*
+The MIT License
+
+Copyright (c) 2008 Tahseen Ur Rehman
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+*/
+
+package ds.tree;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Represents a node of a Radix tree {@link RadixTreeImpl}
+ *
+ * @author Tahseen Ur Rehman
+ * @email tahseen.ur.rehman {at.spam.me.not} gmail.com
+ * @param
+ */
+class RadixTreeNode {
+ private String key;
+
+ private List> childern;
+
+ private boolean real;
+
+ private T value;
+
+ /**
+ * intailize the fields with default values to avoid null reference checks
+ * all over the places
+ */
+ public RadixTreeNode() {
+ key = "";
+ childern = new ArrayList>();
+ real = false;
+ }
+
+ public T getValue() {
+ return value;
+ }
+
+ public void setValue(T data) {
+ this.value = data;
+ }
+
+ public String getKey() {
+ return key;
+ }
+
+ public void setKey(String value) {
+ this.key = value;
+ }
+
+ public boolean isReal() {
+ return real;
+ }
+
+ public void setReal(boolean datanode) {
+ this.real = datanode;
+ }
+
+ public List> getChildern() {
+ return childern;
+ }
+
+ public void setChildern(List> childern) {
+ this.childern = childern;
+ }
+
+ public int getNumberOfMatchingCharacters(String key) {
+ int numberOfMatchingCharacters = 0;
+ while (numberOfMatchingCharacters < key.length() && numberOfMatchingCharacters < this.getKey().length()) {
+ if (key.charAt(numberOfMatchingCharacters) != this.getKey().charAt(numberOfMatchingCharacters)) {
+ break;
+ }
+ numberOfMatchingCharacters++;
+ }
+ return numberOfMatchingCharacters;
+ }
+
+ @Override
+ public String toString() {
+ return key;
+ }
+}
diff --git a/src/ds/tree/Visitor.java b/src/ds/tree/Visitor.java
new file mode 100644
index 0000000..816e08b
--- /dev/null
+++ b/src/ds/tree/Visitor.java
@@ -0,0 +1,56 @@
+/*
+The MIT License
+
+Copyright (c) 2008 Tahseen Ur Rehman, Javid Jamae
+
+http://code.google.com/p/radixtree/
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+*/
+
+package ds.tree;
+
+/**
+ * The visitor interface that is used by {@link RadixTreeImpl} for perfroming
+ * task on a searched node.
+ *
+ * @author Tahseen Ur Rehman (tahseen.ur.rehman {at.spam.me.not} gmail.com)
+ * @author Javid Jamae
+ * @author Dennis Heidsiek
+ * @param
+ */
+public interface Visitor {
+ /**
+ * This method gets called by {@link RadixTreeImpl#visit(String, Visitor) visit}
+ * when it finds a node matching the key given to it.
+ *
+ * @param key The key that matched the node
+ * @param parent The parent of the node being visited
+ * @param node The node that is being visited
+ */
+ public void visit(String key, RadixTreeNode parent, RadixTreeNode node);
+
+ /**
+ * The visitor can store any type of result object, depending on the context of
+ * what it is being used for.
+ *
+ * @return The result captured by the visitor.
+ */
+ public R getResult();
+}
diff --git a/src/ds/tree/VisitorImpl.java b/src/ds/tree/VisitorImpl.java
new file mode 100644
index 0000000..ff4e8d1
--- /dev/null
+++ b/src/ds/tree/VisitorImpl.java
@@ -0,0 +1,27 @@
+package ds.tree;
+
+
+/**
+ * A simple standard implementation for a {@link visitor}.
+ *
+ * @author Dennis Heidsiek
+ * @param
+ */
+public abstract class VisitorImpl implements Visitor {
+
+ protected R result;
+
+ public VisitorImpl() {
+ this.result = null;
+ }
+
+ public VisitorImpl(R initialValue) {
+ this.result = initialValue;
+ }
+
+ public R getResult() {
+ return result;
+ }
+
+ abstract public void visit(String key, RadixTreeNode parent, RadixTreeNode node);
+}
\ No newline at end of file
diff --git a/src/javax/annotation/CheckForNull.java b/src/javax/annotation/CheckForNull.java
new file mode 100644
index 0000000..6fe5200
--- /dev/null
+++ b/src/javax/annotation/CheckForNull.java
@@ -0,0 +1,16 @@
+package javax.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+import javax.annotation.meta.TypeQualifierNickname;
+import javax.annotation.meta.When;
+
+@Documented
+@TypeQualifierNickname
+@Nonnull(when = When.MAYBE)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface CheckForNull {
+
+}
diff --git a/src/javax/annotation/CheckForSigned.java b/src/javax/annotation/CheckForSigned.java
new file mode 100644
index 0000000..f3bc597
--- /dev/null
+++ b/src/javax/annotation/CheckForSigned.java
@@ -0,0 +1,24 @@
+package javax.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+import javax.annotation.meta.TypeQualifierNickname;
+import javax.annotation.meta.When;
+
+/**
+ * Used to annotate a value that may be either negative or nonnegative, and
+ * indicates that uses of it should check for
+ * negative values before using it in a way that requires the value to be
+ * nonnegative, and check for it being nonnegative before using it in a way that
+ * requires it to be negative.
+ */
+
+@Documented
+@TypeQualifierNickname
+@Nonnegative(when = When.MAYBE)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface CheckForSigned {
+
+}
diff --git a/src/javax/annotation/CheckReturnValue.java b/src/javax/annotation/CheckReturnValue.java
new file mode 100644
index 0000000..370fa38
--- /dev/null
+++ b/src/javax/annotation/CheckReturnValue.java
@@ -0,0 +1,17 @@
+package javax.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import javax.annotation.meta.When;
+
+@Documented
+@Target( { ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.TYPE,
+ ElementType.PACKAGE })
+@Retention(RetentionPolicy.RUNTIME)
+public @interface CheckReturnValue {
+ When when() default When.ALWAYS;
+}
diff --git a/src/javax/annotation/Detainted.java b/src/javax/annotation/Detainted.java
new file mode 100644
index 0000000..d8620f2
--- /dev/null
+++ b/src/javax/annotation/Detainted.java
@@ -0,0 +1,16 @@
+package javax.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+import javax.annotation.meta.TypeQualifierNickname;
+import javax.annotation.meta.When;
+
+@Documented
+@TypeQualifierNickname
+@Untainted(when = When.ALWAYS)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Detainted {
+
+}
diff --git a/src/javax/annotation/MatchesPattern.java b/src/javax/annotation/MatchesPattern.java
new file mode 100644
index 0000000..775d21c
--- /dev/null
+++ b/src/javax/annotation/MatchesPattern.java
@@ -0,0 +1,30 @@
+package javax.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.regex.Pattern;
+
+import javax.annotation.meta.TypeQualifier;
+import javax.annotation.meta.TypeQualifierValidator;
+import javax.annotation.meta.When;
+
+@Documented
+@TypeQualifier(applicableTo = String.class)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface MatchesPattern {
+ @RegEx
+ String value();
+
+ int flags() default 0;
+
+ static class Checker implements TypeQualifierValidator {
+ public When forConstantValue(MatchesPattern annotation, Object value) {
+ Pattern p = Pattern.compile(annotation.value(), annotation.flags());
+ if (p.matcher(((String) value)).matches())
+ return When.ALWAYS;
+ return When.NEVER;
+ }
+
+ }
+}
diff --git a/src/javax/annotation/Nonnegative.java b/src/javax/annotation/Nonnegative.java
new file mode 100644
index 0000000..21d1793
--- /dev/null
+++ b/src/javax/annotation/Nonnegative.java
@@ -0,0 +1,41 @@
+package javax.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+import javax.annotation.meta.TypeQualifier;
+import javax.annotation.meta.TypeQualifierValidator;
+import javax.annotation.meta.When;
+
+/** Used to annotate a value that should only contain nonnegative values */
+@Documented
+@TypeQualifier(applicableTo = Number.class)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Nonnegative {
+ When when() default When.ALWAYS;
+
+ class Checker implements TypeQualifierValidator {
+
+ public When forConstantValue(Nonnegative annotation, Object v) {
+ if (!(v instanceof Number))
+ return When.NEVER;
+ boolean isNegative;
+ Number value = (Number) v;
+ if (value instanceof Long)
+ isNegative = value.longValue() < 0;
+ else if (value instanceof Double)
+ isNegative = value.doubleValue() < 0;
+ else if (value instanceof Float)
+ isNegative = value.floatValue() < 0;
+ else
+ isNegative = value.intValue() < 0;
+
+ if (isNegative)
+ return When.NEVER;
+ else
+ return When.ALWAYS;
+
+ }
+ }
+}
diff --git a/src/javax/annotation/Nonnull.java b/src/javax/annotation/Nonnull.java
new file mode 100644
index 0000000..4b7aad9
--- /dev/null
+++ b/src/javax/annotation/Nonnull.java
@@ -0,0 +1,26 @@
+package javax.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+import javax.annotation.meta.TypeQualifier;
+import javax.annotation.meta.TypeQualifierValidator;
+import javax.annotation.meta.When;
+
+@Documented
+@TypeQualifier
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Nonnull {
+ When when() default When.ALWAYS;
+
+ static class Checker implements TypeQualifierValidator {
+
+ public When forConstantValue(Nonnull qualifierqualifierArgument,
+ Object value) {
+ if (value == null)
+ return When.NEVER;
+ return When.ALWAYS;
+ }
+ }
+}
diff --git a/src/javax/annotation/Nullable.java b/src/javax/annotation/Nullable.java
new file mode 100644
index 0000000..d31993d
--- /dev/null
+++ b/src/javax/annotation/Nullable.java
@@ -0,0 +1,16 @@
+package javax.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+import javax.annotation.meta.TypeQualifierNickname;
+import javax.annotation.meta.When;
+
+@Documented
+@TypeQualifierNickname
+@Nonnull(when = When.UNKNOWN)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Nullable {
+
+}
diff --git a/src/javax/annotation/OverridingMethodsMustInvokeSuper.java b/src/javax/annotation/OverridingMethodsMustInvokeSuper.java
new file mode 100644
index 0000000..4e3344e
--- /dev/null
+++ b/src/javax/annotation/OverridingMethodsMustInvokeSuper.java
@@ -0,0 +1,20 @@
+package javax.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * When this annotation is applied to a method, it indicates that if this method
+ * is overridden in a subclass, the overriding method should invoke this method
+ * (through method invocation on super).
+ *
+ */
+@Documented
+@Target( { ElementType.METHOD })
+@Retention(RetentionPolicy.RUNTIME)
+public @interface OverridingMethodsMustInvokeSuper {
+
+}
diff --git a/src/javax/annotation/ParametersAreNonnullByDefault.java b/src/javax/annotation/ParametersAreNonnullByDefault.java
new file mode 100644
index 0000000..6424b87
--- /dev/null
+++ b/src/javax/annotation/ParametersAreNonnullByDefault.java
@@ -0,0 +1,27 @@
+package javax.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+import javax.annotation.meta.TypeQualifierDefault;
+
+/**
+ * This annotation can be applied to a package, class or method to indicate that
+ * the method parameters in that element are nonnull by default unless there is:
+ *
+ *
An explicit nullness annotation
+ *
The method overrides a method in a superclass (in which case the
+ * annotation of the corresponding parameter in the superclass applies)
+ *
there is a default parameter annotation applied to a more tightly nested
+ * element.
+ *
+ *
+ */
+@Documented
+@Nonnull
+@TypeQualifierDefault(ElementType.PARAMETER)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface ParametersAreNonnullByDefault {
+}
diff --git a/src/javax/annotation/ParametersAreNullableByDefault.java b/src/javax/annotation/ParametersAreNullableByDefault.java
new file mode 100644
index 0000000..01d4d80
--- /dev/null
+++ b/src/javax/annotation/ParametersAreNullableByDefault.java
@@ -0,0 +1,30 @@
+package javax.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+import javax.annotation.meta.TypeQualifierDefault;
+
+/**
+ * This annotation can be applied to a package, class or method to indicate that
+ * the method parameters in that element are nullable by default unless there is:
+ *
+ *
An explicit nullness annotation
+ *
The method overrides a method in a superclass (in which case the
+ * annotation of the corresponding parameter in the superclass applies)
+ *
there is a default parameter annotation applied to a more tightly nested
+ * element.
+ *
+ *
This annotation implies the same "nullness" as no annotation. However, it is different
+ * than having no annotation, as it is inherited and it can override a ParametersAreNonnullByDefault
+ * annotation at an outer scope.
+ *
+ */
+@Documented
+@Nullable
+@TypeQualifierDefault(ElementType.PARAMETER)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface ParametersAreNullableByDefault {
+}
diff --git a/src/javax/annotation/PropertyKey.java b/src/javax/annotation/PropertyKey.java
new file mode 100644
index 0000000..780782d
--- /dev/null
+++ b/src/javax/annotation/PropertyKey.java
@@ -0,0 +1,15 @@
+package javax.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+import javax.annotation.meta.TypeQualifier;
+import javax.annotation.meta.When;
+
+@Documented
+@TypeQualifier
+@Retention(RetentionPolicy.RUNTIME)
+public @interface PropertyKey {
+ When when() default When.ALWAYS;
+}
diff --git a/src/javax/annotation/RegEx.java b/src/javax/annotation/RegEx.java
new file mode 100644
index 0000000..21697a7
--- /dev/null
+++ b/src/javax/annotation/RegEx.java
@@ -0,0 +1,42 @@
+package javax.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+
+import javax.annotation.meta.TypeQualifierNickname;
+import javax.annotation.meta.TypeQualifierValidator;
+import javax.annotation.meta.When;
+
+/**
+ * This qualifier is used to denote String values that should be a Regular
+ * expression.
+ *
+ */
+@Documented
+@Syntax("RegEx")
+@TypeQualifierNickname
+@Retention(RetentionPolicy.RUNTIME)
+public @interface RegEx {
+ When when() default When.ALWAYS;
+
+ static class Checker implements TypeQualifierValidator {
+
+ public When forConstantValue(RegEx annotation, Object value) {
+ if (!(value instanceof String))
+ return When.NEVER;
+
+ try {
+ Pattern.compile((String) value);
+ } catch (PatternSyntaxException e) {
+ return When.NEVER;
+ }
+ return When.ALWAYS;
+
+ }
+
+ }
+
+}
diff --git a/src/javax/annotation/Signed.java b/src/javax/annotation/Signed.java
new file mode 100644
index 0000000..2c2fa06
--- /dev/null
+++ b/src/javax/annotation/Signed.java
@@ -0,0 +1,18 @@
+package javax.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+import javax.annotation.meta.TypeQualifierNickname;
+import javax.annotation.meta.When;
+
+/** Used to annotate a value of unknown sign */
+
+@Documented
+@TypeQualifierNickname
+@Nonnegative(when = When.UNKNOWN)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Signed {
+
+}
diff --git a/src/javax/annotation/Syntax.java b/src/javax/annotation/Syntax.java
new file mode 100644
index 0000000..fa0586d
--- /dev/null
+++ b/src/javax/annotation/Syntax.java
@@ -0,0 +1,44 @@
+package javax.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+import javax.annotation.meta.TypeQualifier;
+import javax.annotation.meta.When;
+
+/**
+ * This annotation a value that is of a particular syntax, such as Java syntax
+ * or regular expression syntax. This can be used to provide syntax checking of
+ * constant values at compile time, run time checking at runtime, and can assist
+ * IDEs in deciding how to interpret String constants (e.g., should a
+ * refactoring that renames method x() to y() update the String constant "x()").
+ *
+ *
+ */
+@Documented
+@TypeQualifier(applicableTo = CharSequence.class)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Syntax {
+ /**
+ * Value indicating the particular syntax denoted by this annotation.
+ * Different tools will recognize different syntaxes, but some proposed
+ * canonical values are:
+ *
+ *
"Java"
+ *
"RegEx"
+ *
"JavaScript"
+ *
"Ruby"
+ *
"Groovy"
+ *
"SQL"
+ *
"FormatString"
+ *
+ *
+ * Syntax names can be followed by a colon and a list of key value pairs,
+ * separated by commas. For example, "SQL:dialect=Oracle,version=2.3". Tools
+ * should ignore any keys they don't recognize.
+ */
+ String value();
+
+ When when() default When.ALWAYS;
+}
diff --git a/src/javax/annotation/Tainted.java b/src/javax/annotation/Tainted.java
new file mode 100644
index 0000000..6715d70
--- /dev/null
+++ b/src/javax/annotation/Tainted.java
@@ -0,0 +1,16 @@
+package javax.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+import javax.annotation.meta.TypeQualifierNickname;
+import javax.annotation.meta.When;
+
+@Documented
+@TypeQualifierNickname
+@Untainted(when = When.MAYBE)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Tainted {
+
+}
diff --git a/src/javax/annotation/Untainted.java b/src/javax/annotation/Untainted.java
new file mode 100644
index 0000000..4a26a49
--- /dev/null
+++ b/src/javax/annotation/Untainted.java
@@ -0,0 +1,15 @@
+package javax.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+import javax.annotation.meta.TypeQualifier;
+import javax.annotation.meta.When;
+
+@Documented
+@TypeQualifier
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Untainted {
+ When when() default When.ALWAYS;
+}
diff --git a/src/javax/annotation/WillClose.java b/src/javax/annotation/WillClose.java
new file mode 100644
index 0000000..9ea70cf
--- /dev/null
+++ b/src/javax/annotation/WillClose.java
@@ -0,0 +1,15 @@
+package javax.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+/**
+ * Used to annotate a method parameter to indicate that this method will close
+ * the resource.
+ */
+public @interface WillClose {
+
+}
diff --git a/src/javax/annotation/WillCloseWhenClosed.java b/src/javax/annotation/WillCloseWhenClosed.java
new file mode 100644
index 0000000..13c927d
--- /dev/null
+++ b/src/javax/annotation/WillCloseWhenClosed.java
@@ -0,0 +1,15 @@
+package javax.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+/**
+ * Used to annotate a constructor/factory parameter to indicate that returned
+ * object (X) will close the resource when X is closed.
+ */
+public @interface WillCloseWhenClosed {
+
+}
diff --git a/src/javax/annotation/WillNotClose.java b/src/javax/annotation/WillNotClose.java
new file mode 100644
index 0000000..d698b7c
--- /dev/null
+++ b/src/javax/annotation/WillNotClose.java
@@ -0,0 +1,15 @@
+package javax.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+/**
+ * Used to annotate a method parameter to indicate that this method will not
+ * close the resource.
+ */
+public @interface WillNotClose {
+
+}
diff --git a/src/javax/annotation/concurrent/GuardedBy.java b/src/javax/annotation/concurrent/GuardedBy.java
new file mode 100644
index 0000000..9d6641d
--- /dev/null
+++ b/src/javax/annotation/concurrent/GuardedBy.java
@@ -0,0 +1,38 @@
+package javax.annotation.concurrent;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/*
+ * Copyright (c) 2005 Brian Goetz
+ * Released under the Creative Commons Attribution License
+ * (http://creativecommons.org/licenses/by/2.5)
+ * Official home: http://www.jcip.net
+ */
+
+/**
+ * GuardedBy
+ *
+ * The field or method to which this annotation is applied can only be accessed
+ * when holding a particular lock, which may be a built-in (synchronization)
+ * lock, or may be an explicit java.util.concurrent.Lock.
+ *
+ * The argument determines which lock guards the annotated field or method: this :
+ * The string literal "this" means that this field is guarded by the class in
+ * which it is defined. class-name.this : For inner classes, it may be necessary
+ * to disambiguate 'this'; the class-name.this designation allows you to specify
+ * which 'this' reference is intended itself : For reference fields only; the
+ * object to which the field refers. field-name : The lock object is referenced
+ * by the (instance or static) field specified by field-name.
+ * class-name.field-name : The lock object is reference by the static field
+ * specified by class-name.field-name. method-name() : The lock object is
+ * returned by calling the named nil-ary method. class-name.class : The Class
+ * object for the specified class should be used as the lock object.
+ */
+@Target( { ElementType.FIELD, ElementType.METHOD })
+@Retention(RetentionPolicy.CLASS)
+public @interface GuardedBy {
+ String value();
+}
diff --git a/src/javax/annotation/concurrent/Immutable.java b/src/javax/annotation/concurrent/Immutable.java
new file mode 100644
index 0000000..03f1cb5
--- /dev/null
+++ b/src/javax/annotation/concurrent/Immutable.java
@@ -0,0 +1,36 @@
+package javax.annotation.concurrent;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/*
+ * Copyright (c) 2005 Brian Goetz
+ * Released under the Creative Commons Attribution License
+ * (http://creativecommons.org/licenses/by/2.5)
+ * Official home: http://www.jcip.net
+ */
+
+/**
+ * Immutable
+ *
+ * The class to which this annotation is applied is immutable. This means that
+ * its state cannot be seen to change by callers. Of necessity this means that
+ * all public fields are final, and that all public final reference fields refer
+ * to other immutable objects, and that methods do not publish references to any
+ * internal state which is mutable by implementation even if not by design.
+ * Immutable objects may still have internal mutable state for purposes of
+ * performance optimization; some state variables may be lazily computed, so
+ * long as they are computed from immutable state and that callers cannot tell
+ * the difference.
+ *
+ * Immutable objects are inherently thread-safe; they may be passed between
+ * threads or published without synchronization.
+ */
+@Documented
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.CLASS)
+public @interface Immutable {
+}
diff --git a/src/javax/annotation/concurrent/NotThreadSafe.java b/src/javax/annotation/concurrent/NotThreadSafe.java
new file mode 100644
index 0000000..d948142
--- /dev/null
+++ b/src/javax/annotation/concurrent/NotThreadSafe.java
@@ -0,0 +1,30 @@
+package javax.annotation.concurrent;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/*
+ * Copyright (c) 2005 Brian Goetz
+ * Released under the Creative Commons Attribution License
+ * (http://creativecommons.org/licenses/by/2.5)
+ * Official home: http://www.jcip.net
+ */
+
+/**
+ * NotThreadSafe
+ *
+ * The class to which this annotation is applied is not thread-safe. This
+ * annotation primarily exists for clarifying the non-thread-safety of a class
+ * that might otherwise be assumed to be thread-safe, despite the fact that it
+ * is a bad idea to assume a class is thread-safe without good reason.
+ *
+ * @see ThreadSafe
+ */
+@Documented
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.CLASS)
+public @interface NotThreadSafe {
+}
diff --git a/src/javax/annotation/concurrent/ThreadSafe.java b/src/javax/annotation/concurrent/ThreadSafe.java
new file mode 100644
index 0000000..3c86128
--- /dev/null
+++ b/src/javax/annotation/concurrent/ThreadSafe.java
@@ -0,0 +1,22 @@
+package javax.annotation.concurrent;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * ThreadSafe
+ *
+ * The class to which this annotation is applied is thread-safe. This means that
+ * no sequences of accesses (reads and writes to public fields, calls to public
+ * methods) may put the object into an invalid state, regardless of the
+ * interleaving of those actions by the runtime, and without requiring any
+ * additional synchronization or coordination on the part of the caller.
+ */
+@Documented
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.CLASS)
+public @interface ThreadSafe {
+}
diff --git a/src/javax/annotation/meta/Exclusive.java b/src/javax/annotation/meta/Exclusive.java
new file mode 100644
index 0000000..d018c33
--- /dev/null
+++ b/src/javax/annotation/meta/Exclusive.java
@@ -0,0 +1,26 @@
+package javax.annotation.meta;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * This annotation can be applied to the value() element of an annotation that
+ * is annotated as a TypeQualifier.
+ *
+ * For example, the following defines a type qualifier such that if you know a
+ * value is {@literal @Foo(1)}, then the value cannot be {@literal @Foo(2)} or {{@literal @Foo(3)}.
+ *
+ *
+ * @TypeQualifier @interface Foo {
+ * @Exclusive int value();
+ * }
+ *
+ *
+ */
+
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Exclusive {
+
+}
diff --git a/src/javax/annotation/meta/Exhaustive.java b/src/javax/annotation/meta/Exhaustive.java
new file mode 100644
index 0000000..dc75f06
--- /dev/null
+++ b/src/javax/annotation/meta/Exhaustive.java
@@ -0,0 +1,33 @@
+package javax.annotation.meta;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * This annotation can be applied to the value() element of an annotation that
+ * is annotated as a TypeQualifier. This is only appropriate if the value field
+ * returns a value that is an Enumeration.
+ *
+ * Applications of the type qualifier with different values are exclusive, and
+ * the enumeration is an exhaustive list of the possible values.
+ *
+ * For example, the following defines a type qualifier such that if you know a
+ * value is neither {@literal @Foo(Color.Red)} or {@literal @Foo(Color.Blue)},
+ * then the value must be {@literal @Foo(Color.Green)}. And if you know it is
+ * {@literal @Foo(Color.Green)}, you know it cannot be
+ * {@literal @Foo(Color.Red)} or {@literal @Foo(Color.Blue)}
+ *
+ *
+ * @TypeQualifier @interface Foo {
+ * enum Color {RED, BLUE, GREEN};
+ * @Exhaustive Color value();
+ * }
+ *
+ */
+
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Exhaustive {
+
+}
diff --git a/src/javax/annotation/meta/TypeQualifier.java b/src/javax/annotation/meta/TypeQualifier.java
new file mode 100644
index 0000000..99f6312
--- /dev/null
+++ b/src/javax/annotation/meta/TypeQualifier.java
@@ -0,0 +1,27 @@
+package javax.annotation.meta;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * This qualifier is applied to an annotation to denote that the annotation
+ * should be treated as a type qualifier.
+ */
+
+@Documented
+@Target(ElementType.ANNOTATION_TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface TypeQualifier {
+
+ /**
+ * Describes the kinds of values the qualifier can be applied to. If a
+ * numeric class is provided (e.g., Number.class or Integer.class) then the
+ * annotation can also be applied to the corresponding primitive numeric
+ * types.
+ */
+ Class> applicableTo() default Object.class;
+
+}
diff --git a/src/javax/annotation/meta/TypeQualifierDefault.java b/src/javax/annotation/meta/TypeQualifierDefault.java
new file mode 100644
index 0000000..0c8bd52
--- /dev/null
+++ b/src/javax/annotation/meta/TypeQualifierDefault.java
@@ -0,0 +1,20 @@
+package javax.annotation.meta;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * This qualifier is applied to an annotation to denote that the annotation
+ * defines a default type qualifier that is visible within the scope of the
+ * element it is applied to.
+ */
+
+@Documented
+@Target(ElementType.ANNOTATION_TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface TypeQualifierDefault {
+ ElementType[] value() default {};
+}
diff --git a/src/javax/annotation/meta/TypeQualifierNickname.java b/src/javax/annotation/meta/TypeQualifierNickname.java
new file mode 100644
index 0000000..40c9983
--- /dev/null
+++ b/src/javax/annotation/meta/TypeQualifierNickname.java
@@ -0,0 +1,33 @@
+package javax.annotation.meta;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+
+/**
+ *
+ * This annotation is applied to a annotation, and marks the annotation as being
+ * a qualifier nickname. Applying a nickname annotation X to a element Y should
+ * be interpreted as having the same meaning as applying all of annotations of X
+ * (other than QualifierNickname) to Y.
+ *
+ *
+ * Thus, you might define a qualifier SocialSecurityNumber as follows:
+ *