├── bin
└── TweakJar.apk
├── Jar包微调框架TweakJar介绍.docx
├── libs
├── arm64-v8a
│ └── libtweakjar.so
└── armeabi
│ └── libtweakjar.so
├── res
├── drawable-hdpi
│ └── ic_launcher.png
├── drawable-mdpi
│ └── ic_launcher.png
├── drawable-xhdpi
│ └── ic_launcher.png
├── values
│ ├── strings.xml
│ └── styles.xml
├── values-v11
│ └── styles.xml
├── values-v14
│ └── styles.xml
└── layout
│ └── activity_main.xml
├── src
└── com
│ ├── android
│ └── guobao
│ │ └── liao
│ │ └── apptweak
│ │ ├── JavaTweakCallback.java
│ │ ├── JavaTweakReplace.java
│ │ ├── JavaTweakHook.java
│ │ ├── JavaTweakStub.java
│ │ ├── util
│ │ ├── StringUtil.java
│ │ └── ReflectUtil.java
│ │ └── JavaTweakBridge.java
│ └── demo
│ └── tweakjar
│ ├── BlockingDialog.java
│ └── MainActivity.java
├── .classpath
├── project.properties
├── proguard-project.txt
├── .project
├── AndroidManifest.xml
├── README.md
└── LICENSE
/bin/TweakJar.apk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liaoguobao/TweakJar/HEAD/bin/TweakJar.apk
--------------------------------------------------------------------------------
/Jar包微调框架TweakJar介绍.docx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liaoguobao/TweakJar/HEAD/Jar包微调框架TweakJar介绍.docx
--------------------------------------------------------------------------------
/libs/arm64-v8a/libtweakjar.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liaoguobao/TweakJar/HEAD/libs/arm64-v8a/libtweakjar.so
--------------------------------------------------------------------------------
/libs/armeabi/libtweakjar.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liaoguobao/TweakJar/HEAD/libs/armeabi/libtweakjar.so
--------------------------------------------------------------------------------
/res/drawable-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liaoguobao/TweakJar/HEAD/res/drawable-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/res/drawable-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liaoguobao/TweakJar/HEAD/res/drawable-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/res/drawable-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liaoguobao/TweakJar/HEAD/res/drawable-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | TweakJar
5 |
6 |
7 |
--------------------------------------------------------------------------------
/src/com/android/guobao/liao/apptweak/JavaTweakCallback.java:
--------------------------------------------------------------------------------
1 | package com.android.guobao.liao.apptweak;
2 |
3 | @SuppressWarnings("unused")
4 | public class JavaTweakCallback {
5 | static private Object handleHookedMethod(Object thiz, Object[] args, Object data) throws Throwable {
6 | return ((JavaTweakHook) data).handleHookedMethod(thiz, args);
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/res/values-v11/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/res/values-v14/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/.classpath:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/project.properties:
--------------------------------------------------------------------------------
1 | # This file is automatically generated by Android Tools.
2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED!
3 | #
4 | # This file must be checked in Version Control Systems.
5 | #
6 | # To customize properties used by the Ant build system edit
7 | # "ant.properties", and override values to adapt the script to your
8 | # project structure.
9 | #
10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
12 |
13 | # Project target.
14 | target=android-22
15 |
--------------------------------------------------------------------------------
/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
14 |
15 |
16 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/proguard-project.txt:
--------------------------------------------------------------------------------
1 | # To enable ProGuard in your project, edit project.properties
2 | # to define the proguard.config property as described in that file.
3 | #
4 | # Add project specific ProGuard rules here.
5 | # By default, the flags in this file are appended to flags specified
6 | # in ${sdk.dir}/tools/proguard/proguard-android.txt
7 | # You can edit the include path and order by changing the ProGuard
8 | # include property in project.properties.
9 | #
10 | # For more details, see
11 | # http://developer.android.com/guide/developing/tools/proguard.html
12 |
13 | # Add any project specific keep options here:
14 |
15 | # If your project uses WebView with JS, uncomment the following
16 | # and specify the fully qualified class name to the JavaScript interface
17 | # class:
18 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
19 | # public *;
20 | #}
21 |
--------------------------------------------------------------------------------
/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | TweakJar
4 |
5 |
6 |
7 |
8 |
9 | org.eclipse.andmore.ResourceManagerBuilder
10 |
11 |
12 |
13 |
14 | org.eclipse.andmore.PreCompilerBuilder
15 |
16 |
17 |
18 |
19 | org.eclipse.jdt.core.javabuilder
20 |
21 |
22 |
23 |
24 | org.eclipse.andmore.ApkBuilder
25 |
26 |
27 |
28 |
29 |
30 | org.eclipse.andmore.AndroidNature
31 | org.eclipse.jdt.core.javanature
32 |
33 |
34 |
--------------------------------------------------------------------------------
/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
10 |
11 |
16 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## TweakJar
2 | TweakJar是一个轻量级的运行时Jar包修改框架,它主要面向Android正向开发者(当然,逆向开发者也同样适用)。
3 | 他的主要功能是:在运行时hook任意的java方法,动态调整其方法逻辑。
4 | 框架专为希望微调或增强jar包中某些方法的功能又没有jar包源码的场景而设计。
5 | 你可以认为他是一个微缩版本的xposed,又没有它的笨重、需要root权限等限制,主打一个极简与方便集成。
6 | 此框架从我的另一个开源逆向框架**TweakMe**裁剪而来,剥离出其中的java拦截部分功能。
7 | 因为TweakMe是一个相对较重的逆向框架,而java拦截部分实际上可以单独提取以让其正逆两用,极大的方便使用者在自己的项目中集成开发。
8 | 目前TweakJar框架在5.0到14.0的android手机上测试通过。
9 |
10 | ## 使用前准备
11 | 需要eclipse for android 环境
12 | 如果只有android studio 环境,则需自己将工程转化AS工程。
13 |
14 | ## 框架使用
15 | 详细使用请浏览MainActivity.java中的测试代码。
16 | 如果你没有eclipse for android环境,可以直接安装**TweakJar.apk**查看运行效果。
17 | logcat中的日志过滤tag标签为 **TweakJar**
18 |
19 | ## 框架集成
20 | 如果你想要将TweakJar框架集成到你自己的项目中,你只需要做如下两步操作:
21 | 1、将下面的两个包中的所有java代码拷贝到你自己项目的src源码目录中。
22 | **package com.android.guobao.liao.apptweak.util;**
23 | **package com.android.guobao.liao.apptweak;**
24 |
25 | 2、将**libtweakjar.so**拷贝到你自己项目的lib库目录中。
26 |
27 |
28 |
29 | **如果本框架对你有帮助,记得github上为我点赞加星哦!!!**
30 |
31 | ## 免责声明
32 | **本框架为个人作品,任何人的复制、拷贝、使用等,只可用于正常的技术交流与学习,不可用于灰黑产业,不可从事违法犯罪行为。否则,后果自负!!!**
33 |
34 |
--------------------------------------------------------------------------------
/src/com/android/guobao/liao/apptweak/JavaTweakReplace.java:
--------------------------------------------------------------------------------
1 | package com.android.guobao.liao.apptweak;
2 |
3 | import android.util.Log;
4 |
5 | public abstract class JavaTweakReplace extends JavaTweakHook {
6 | public JavaTweakReplace() {
7 | super();
8 | }
9 |
10 | public JavaTweakReplace(int flags) {
11 | super(flags);
12 | }
13 |
14 | public JavaTweakReplace(String name) {
15 | super(name);
16 | }
17 |
18 | public JavaTweakReplace(int flags, String name) {
19 | super(flags, name);
20 | }
21 |
22 | @Override
23 | protected final void beforeHookedMethod(Object thiz, Object[] args) {
24 | try {
25 | setResult(replaceHookedMethod(thiz, args));
26 | } catch (Throwable e) {
27 | JavaTweakBridge.writeToLogcat(Log.ERROR, "replaceHookedMethod: %s: %s", getBackup(), e);
28 | }
29 | }
30 |
31 | @Override
32 | protected final void afterHookedMethod(Object thiz, Object[] args) {
33 | }
34 |
35 | protected abstract Object replaceHookedMethod(Object thiz, Object[] args);
36 |
37 | public static JavaTweakReplace constReturnReplace(final Object result) {
38 | return new JavaTweakReplace() {
39 | @Override
40 | protected Object replaceHookedMethod(Object thiz, Object[] args) {
41 | return result;
42 | }
43 | };
44 | }
45 |
46 | public static JavaTweakReplace nullReturnReplace() {
47 | return constReturnReplace(null);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/com/demo/tweakjar/BlockingDialog.java:
--------------------------------------------------------------------------------
1 | package com.demo.tweakjar;
2 |
3 | import android.app.Activity;
4 | import android.app.AlertDialog;
5 | import android.content.Context;
6 | import android.content.DialogInterface;
7 | import android.content.DialogInterface.OnClickListener;
8 | import android.os.Handler;
9 | import android.os.Looper;
10 | import android.os.Message;
11 |
12 | public class BlockingDialog {
13 | private int mWhich;
14 | static private Handler mHandler;
15 |
16 | static public int showBlockingDialog(Activity context, String title, String msg) {
17 | mHandler = new Handler(new Handler.Callback() {
18 | public boolean handleMessage(Message msg) {
19 | throw new RuntimeException();
20 | }
21 | });
22 | return new BlockingDialog(context, title, msg).mWhich;
23 | }
24 |
25 | private BlockingDialog(Context context, String title, String msg) {
26 | AlertDialog.Builder builder = new AlertDialog.Builder(context);
27 | builder.setPositiveButton("确定", new OnClickListener() {
28 | public void onClick(DialogInterface dialog, int which) {
29 | mWhich = which;
30 | mHandler.sendMessage(mHandler.obtainMessage());
31 | }
32 | });
33 | builder.setNegativeButton("取消", new OnClickListener() {
34 | public void onClick(DialogInterface dialog, int which) {
35 | mWhich = which;
36 | mHandler.sendMessage(mHandler.obtainMessage());
37 | }
38 | });
39 | builder.setTitle(title).setMessage(msg).create().show();
40 | try {
41 | Looper.loop();
42 | } catch (Exception e) {
43 | }
44 | }
45 | }
--------------------------------------------------------------------------------
/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
13 |
18 |
23 |
28 |
33 |
38 |
43 |
48 |
53 |
58 |
59 |
--------------------------------------------------------------------------------
/src/com/android/guobao/liao/apptweak/JavaTweakHook.java:
--------------------------------------------------------------------------------
1 | package com.android.guobao.liao.apptweak;
2 |
3 | import java.lang.reflect.InvocationTargetException;
4 | import java.lang.reflect.Method;
5 | import android.util.Log;
6 |
7 | import com.android.guobao.liao.apptweak.util.*;
8 |
9 | public abstract class JavaTweakHook {
10 | static public final int HOOK_FLAG_NO_CALL_LOG = 0x00000001; //不打印调用参数日志
11 | static public final int HOOK_FLAG_STACK_TRACE = 0x00000002; // 打印调用堆栈日志
12 |
13 | private Method backup_;
14 | private Object result_;
15 | private Throwable except_;
16 | private boolean return_;
17 | private int flags_;
18 | private String name_;
19 |
20 | public JavaTweakHook() {
21 | this(0, "");
22 | }
23 |
24 | public JavaTweakHook(int flags) {
25 | this(flags, "");
26 | }
27 |
28 | public JavaTweakHook(String name) {
29 | this(0, name);
30 | }
31 |
32 | public JavaTweakHook(int flags, String name) {
33 | backup_ = null;
34 | result_ = null;
35 | except_ = null;
36 | return_ = false;
37 | flags_ = flags;
38 | name_ = name == null ? "" : name;
39 | }
40 |
41 | protected void beforeHookedMethod(Object thiz, Object[] args) {
42 | }
43 |
44 | protected void afterHookedMethod(Object thiz, Object[] args) {
45 | }
46 |
47 | synchronized //lock
48 | protected final Object handleHookedMethod(Object thiz, Object[] args) throws Throwable {
49 | result_ = null;
50 | except_ = null;
51 | return_ = false;
52 | if ((flags_ & HOOK_FLAG_STACK_TRACE) != 0) {
53 | JavaTweakBridge.writeToLogcat(Log.INFO, Log.getStackTraceString(new Throwable()));
54 | }
55 | if (!return_) {
56 | try {
57 | beforeHookedMethod(thiz, args);
58 | } catch (Throwable e) {
59 | JavaTweakBridge.writeToLogcat(Log.ERROR, "beforeHookedMethod: %s: %s", backup_, e);
60 | }
61 | }
62 | if (!return_) {
63 | try {
64 | result_ = backup_.invoke(thiz, args);
65 | } catch (InvocationTargetException e) {
66 | except_ = e.getCause();
67 | JavaTweakBridge.writeToLogcat(Log.WARN, "invoke: %s: %s", backup_, except_);
68 | }
69 | }
70 | if (!return_) {
71 | try {
72 | afterHookedMethod(thiz, args);
73 | } catch (Throwable e) {
74 | JavaTweakBridge.writeToLogcat(Log.ERROR, "afterHookedMethod: %s: %s", backup_, e);
75 | }
76 | }
77 | if ((flags_ & HOOK_FLAG_NO_CALL_LOG) == 0) {
78 | JavaTweakBridge.writeToLogcat(Log.INFO, paramsToString(name_, backup_, result_, thiz, args));
79 | }
80 | if (except_ != null) {
81 | throw except_;
82 | }
83 | return result_;
84 | }
85 |
86 | private String paramsToString(String name, Method m, Object hr, Object thiz, Object[] args) {
87 | Class> type = m.getReturnType();
88 | Class>[] types = m.getParameterTypes();
89 |
90 | String log = String.format("%s::%s%s->{\r\n", m.getDeclaringClass().getName(), m.getName(), !name.equals("") && !m.getName().equals(name) ? "@" + name : "");
91 | log += String.format("\t_this_ = %s->%s\r\n", m.getDeclaringClass().getName(), thiz);
92 | for (int i = 0; i < args.length; i++) {
93 | log += String.format("\tparam%d = %s->%s\r\n", i + 1, types[i].getName(), ReflectUtil.peekValue(args[i]));
94 | }
95 | log += String.format("\treturn = %s->%s\r\n}\r\n", type.getName(), ReflectUtil.peekValue(hr));
96 | return log;
97 | }
98 |
99 | public Method getBackup() {
100 | return backup_;
101 | }
102 |
103 | public void setBackup(Method backup) {
104 | backup_ = backup;
105 | }
106 |
107 | public Object getResult() {
108 | return result_;
109 | }
110 |
111 | public void setResult(Object result) {
112 | result_ = result;
113 | except_ = null;
114 | return_ = true;
115 | }
116 |
117 | public Throwable getThrowable() {
118 | return except_;
119 | }
120 |
121 | public void setThrowable(Throwable except) {
122 | result_ = null;
123 | except_ = except;
124 | return_ = true;
125 | }
126 |
127 | public static JavaTweakHook onlyLogHook(Object... opts) {
128 | int hook_flags_for_log = 0;
129 | String method_name_for_log = "";
130 | for (int i = 0; i < opts.length; i++) {
131 | Object o = opts[i];
132 | if (String.class.isInstance(o)) {
133 | method_name_for_log = (String) o;
134 | } else if (Integer.class.isInstance(o)) {
135 | hook_flags_for_log = (Integer) o;
136 | } else if (Boolean.class.isInstance(o)) {
137 | hook_flags_for_log |= (Boolean) o ? JavaTweakHook.HOOK_FLAG_STACK_TRACE : 0;
138 | }
139 | }
140 | return new JavaTweakHook(hook_flags_for_log, method_name_for_log) {
141 | };
142 | }
143 | }
144 |
--------------------------------------------------------------------------------
/src/com/android/guobao/liao/apptweak/JavaTweakStub.java:
--------------------------------------------------------------------------------
1 | package com.android.guobao.liao.apptweak;
2 |
3 | import java.lang.reflect.Method;
4 | import android.util.Log;
5 |
6 | @SuppressWarnings("unused")
7 | public class JavaTweakStub {
8 | private static volatile int usedStub = 0;
9 |
10 | private static Method getStubMethod() {
11 | try {
12 | Method m = JavaTweakStub.class.getDeclaredMethod(String.format("stub%02d", usedStub++));
13 | return m;
14 | } catch (Throwable e) {
15 | return null;
16 | }
17 | }
18 |
19 | private void stub00() {
20 | }
21 |
22 | private void stub01() {
23 | }
24 |
25 | private void stub02() {
26 | }
27 |
28 | private void stub03() {
29 | }
30 |
31 | private void stub04() {
32 | }
33 |
34 | private void stub05() {
35 | }
36 |
37 | private void stub06() {
38 | }
39 |
40 | private void stub07() {
41 | }
42 |
43 | private void stub08() {
44 | }
45 |
46 | private void stub09() {
47 | }
48 |
49 | private void stub10() {
50 | }
51 |
52 | private void stub11() {
53 | }
54 |
55 | private void stub12() {
56 | }
57 |
58 | private void stub13() {
59 | }
60 |
61 | private void stub14() {
62 | }
63 |
64 | private void stub15() {
65 | }
66 |
67 | private void stub16() {
68 | }
69 |
70 | private void stub17() {
71 | }
72 |
73 | private void stub18() {
74 | }
75 |
76 | private void stub19() {
77 | }
78 |
79 | private void stub20() {
80 | }
81 |
82 | private void stub21() {
83 | }
84 |
85 | private void stub22() {
86 | }
87 |
88 | private void stub23() {
89 | }
90 |
91 | private void stub24() {
92 | }
93 |
94 | private void stub25() {
95 | }
96 |
97 | private void stub26() {
98 | }
99 |
100 | private void stub27() {
101 | }
102 |
103 | private void stub28() {
104 | }
105 |
106 | private void stub29() {
107 | }
108 |
109 | private void stub30() {
110 | }
111 |
112 | private void stub31() {
113 | }
114 |
115 | private void stub32() {
116 | }
117 |
118 | private void stub33() {
119 | }
120 |
121 | private void stub34() {
122 | }
123 |
124 | private void stub35() {
125 | }
126 |
127 | private void stub36() {
128 | }
129 |
130 | private void stub37() {
131 | }
132 |
133 | private void stub38() {
134 | }
135 |
136 | private void stub39() {
137 | }
138 |
139 | private void stub40() {
140 | }
141 |
142 | private void stub41() {
143 | }
144 |
145 | private void stub42() {
146 | }
147 |
148 | private void stub43() {
149 | }
150 |
151 | private void stub44() {
152 | }
153 |
154 | private void stub45() {
155 | }
156 |
157 | private void stub46() {
158 | }
159 |
160 | private void stub47() {
161 | }
162 |
163 | private void stub48() {
164 | }
165 |
166 | private void stub49() {
167 | }
168 |
169 | private void stub50() {
170 | }
171 |
172 | private void stub51() {
173 | }
174 |
175 | private void stub52() {
176 | }
177 |
178 | private void stub53() {
179 | }
180 |
181 | private void stub54() {
182 | }
183 |
184 | private void stub55() {
185 | }
186 |
187 | private void stub56() {
188 | }
189 |
190 | private void stub57() {
191 | }
192 |
193 | private void stub58() {
194 | }
195 |
196 | private void stub59() {
197 | }
198 |
199 | private void stub60() {
200 | }
201 |
202 | private void stub61() {
203 | }
204 |
205 | private void stub62() {
206 | }
207 |
208 | private void stub63() {
209 | }
210 |
211 | private void stub64() {
212 | }
213 |
214 | private void stub65() {
215 | }
216 |
217 | private void stub66() {
218 | }
219 |
220 | private void stub67() {
221 | }
222 |
223 | private void stub68() {
224 | }
225 |
226 | private void stub69() {
227 | }
228 |
229 | private void stub70() {
230 | }
231 |
232 | private void stub71() {
233 | }
234 |
235 | private void stub72() {
236 | }
237 |
238 | private void stub73() {
239 | }
240 |
241 | private void stub74() {
242 | }
243 |
244 | private void stub75() {
245 | }
246 |
247 | private void stub76() {
248 | }
249 |
250 | private void stub77() {
251 | }
252 |
253 | private void stub78() {
254 | }
255 |
256 | private void stub79() {
257 | }
258 |
259 | private void stub80() {
260 | }
261 |
262 | private void stub81() {
263 | }
264 |
265 | private void stub82() {
266 | }
267 |
268 | private void stub83() {
269 | }
270 |
271 | private void stub84() {
272 | }
273 |
274 | private void stub85() {
275 | }
276 |
277 | private void stub86() {
278 | }
279 |
280 | private void stub87() {
281 | }
282 |
283 | private void stub88() {
284 | }
285 |
286 | private void stub89() {
287 | }
288 |
289 | private void stub90() {
290 | }
291 |
292 | private void stub91() {
293 | }
294 |
295 | private void stub92() {
296 | }
297 |
298 | private void stub93() {
299 | }
300 |
301 | private void stub94() {
302 | }
303 |
304 | private void stub95() {
305 | }
306 |
307 | private void stub96() {
308 | }
309 |
310 | private void stub97() {
311 | }
312 |
313 | private void stub98() {
314 | }
315 |
316 | private void stub99() {
317 | }
318 | }
319 |
--------------------------------------------------------------------------------
/src/com/android/guobao/liao/apptweak/util/StringUtil.java:
--------------------------------------------------------------------------------
1 | package com.android.guobao.liao.apptweak.util;
2 |
3 | import java.nio.ByteBuffer;
4 | import java.util.Arrays;
5 |
6 | public class StringUtil {
7 | static public String hexToString(byte[] hex, boolean upper) {
8 | if (hex == null || hex.length <= 0) {
9 | return "";
10 | }
11 | byte[] digit = upper ? "0123456789ABCDEF".getBytes() : "0123456789abcdef".getBytes();
12 | byte[] str = new byte[hex.length << 1];
13 |
14 | for (int i = 0; i < hex.length; i++) {
15 | int cb = hex[i] & 0xFF; //byte范围为[-128, 127],如果要表示为[0, 255],必须要用int来表示,且要和0xff进行与操作后再赋值,否则在负数情况下后续查表会出现数组越界
16 | str[(i << 1) + 0] = digit[cb >> 4];
17 | str[(i << 1) + 1] = digit[cb & 0x0F];
18 | }
19 | String str_ = new String(str);
20 | return str_;
21 | }
22 |
23 | static public byte[] stringToHex(String str) {
24 | if (str == null || str.length() <= 0 || (str.length() & 1) != 0) {
25 | return new byte[0];
26 | }
27 | byte[] str_ = str.getBytes();
28 | byte[] hex = new byte[str_.length >> 1];
29 |
30 | for (int i = 0; i < str_.length; i += 2) {
31 | byte ch = str_[i + 0];
32 | byte cl = str_[i + 1];
33 |
34 | byte cb = 0;
35 | if (ch >= '0' && ch <= '9')
36 | cb |= ch - '0';
37 | else if (ch >= 'a' && ch <= 'f')
38 | cb |= ch - 'a' + 10;
39 | else if (ch >= 'A' && ch <= 'F')
40 | cb |= ch - 'A' + 10;
41 | else
42 | return new byte[0];
43 |
44 | cb <<= 4;
45 | if (cl >= '0' && cl <= '9')
46 | cb |= cl - '0';
47 | else if (cl >= 'a' && cl <= 'f')
48 | cb |= cl - 'a' + 10;
49 | else if (cl >= 'A' && cl <= 'F')
50 | cb |= cl - 'A' + 10;
51 | else
52 | return new byte[0];
53 |
54 | hex[i >> 1] = cb;
55 | }
56 | return hex;
57 | }
58 |
59 | static public String hexToVisible(byte[] hex) {
60 | return hexToVisible(hex, true);
61 | }
62 |
63 | static public String hexToVisible(byte[] hex, boolean upper) {
64 | if (hex == null || hex.length <= 0) {
65 | return "";
66 | }
67 | int len_ = 0;
68 | byte[] digit = upper ? "0123456789ABCDEF".getBytes() : "0123456789abcdef".getBytes();
69 | byte[] str = new byte[hex.length * 3];
70 |
71 | for (int i = 0; i < hex.length; i++) {
72 | int cb = hex[i] & 0xFF; //byte范围为[-128, 127],如果要表示为[0, 255],必须要用int来表示,且要和0xff进行与操作后再赋值,否则在负数情况下后续查表会出现数组越界
73 | if (cb > 0x20 && cb < 0x7F) {
74 | str[len_++] = (byte) cb;
75 | } else {
76 | str[len_++] = ' ';
77 | str[len_++] = digit[cb >> 4];
78 | str[len_++] = digit[cb & 0x0F];
79 | }
80 | }
81 | String str_ = new String(str, 0, len_);
82 | return str_;
83 | }
84 |
85 | static public byte[] visibleToHex(String str) {
86 | if (str == null || str.length() <= 0) {
87 | return new byte[0];
88 | }
89 | int len_ = 0;
90 | byte[] str_ = str.getBytes();
91 | byte[] hex = new byte[str_.length];
92 |
93 | for (int i = 0; i < str_.length; i++) {
94 | if (str_[i] != ' ') {
95 | hex[len_++] = str_[i];
96 | } else if (i + 2 >= str_.length) {
97 | return new byte[0];
98 | } else {
99 | byte ch = str_[i + 1];
100 | byte cl = str_[i + 2];
101 | i += 2;
102 |
103 | byte cb = 0;
104 | if (ch >= '0' && ch <= '9')
105 | cb |= ch - '0';
106 | else if (ch >= 'a' && ch <= 'f')
107 | cb |= ch - 'a' + 10;
108 | else if (ch >= 'A' && ch <= 'F')
109 | cb |= ch - 'A' + 10;
110 | else
111 | return new byte[0];
112 |
113 | cb <<= 4;
114 | if (cl >= '0' && cl <= '9')
115 | cb |= cl - '0';
116 | else if (cl >= 'a' && cl <= 'f')
117 | cb |= cl - 'a' + 10;
118 | else if (cl >= 'A' && cl <= 'F')
119 | cb |= cl - 'A' + 10;
120 | else
121 | return new byte[0];
122 |
123 | hex[len_++] = cb;
124 | }
125 | }
126 | hex = Arrays.copyOf(hex, len_);
127 | return hex;
128 | }
129 |
130 | static public byte[] bufferToByte(ByteBuffer buf, boolean flip) {
131 | if (!buf.hasRemaining()) {
132 | return new byte[0];
133 | }
134 | if (buf.hasArray()) {
135 | int ofs = buf.arrayOffset();
136 | byte[] ba = Arrays.copyOfRange(buf.array(), ofs + (flip ? buf.position() : 0), ofs + (flip ? buf.limit() : buf.position()));
137 | return ba;
138 | } else {
139 | byte[] ba = new byte[buf.remaining()];
140 | buf.get(ba, 0, ba.length);
141 | return ba;
142 | }
143 | }
144 |
145 | static public String replaceAll(String str, String from, String to) {
146 | if (str == null || from == null) {
147 | return "";
148 | }
149 | if (str.equals("") || from.equals("")) {
150 | return str;
151 | }
152 | if (to == null) {
153 | to = "";
154 | }
155 | String hr = "";
156 | int start = 0, index = 0;
157 | while ((index = str.indexOf(from, start)) >= 0) {
158 | hr += str.substring(start, index);
159 | hr += to;
160 | start = index + from.length();
161 | }
162 | hr += str.substring(start);
163 | return hr;
164 | }
165 | }
166 |
--------------------------------------------------------------------------------
/src/com/demo/tweakjar/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.demo.tweakjar;
2 |
3 | import com.android.guobao.liao.apptweak.JavaTweakBridge;
4 | import com.android.guobao.liao.apptweak.JavaTweakHook;
5 | import com.android.guobao.liao.apptweak.JavaTweakReplace;
6 |
7 | import android.app.Activity;
8 | import android.content.DialogInterface;
9 | import android.content.Intent;
10 | import android.os.Bundle;
11 | import android.view.View;
12 |
13 | public class MainActivity extends Activity {
14 | @Override
15 | protected void onCreate(Bundle savedInstanceState) {
16 | super.onCreate(savedInstanceState);
17 | setContentView(R.layout.activity_main);
18 |
19 | findViewById(R.id.btn_one).setOnClickListener(new View.OnClickListener() {
20 | public void onClick(View v) {
21 | //hook类的构造方法
22 | //() 不带参数的构造函数,如果构造函数有参数,hook时需要指定参数列表类型
23 | //(int) 带一个int参数的构造函数
24 | //(int,java.lang.String,byte[]) 带三个参数的构造函数,参数与参数之间不能有空格,如果是普通类需要指定类的全路径(包名.类名)
25 | JavaTweakBridge.hookJavaMethod(Activity.class, "()");
26 | }
27 | });
28 | findViewById(R.id.btn_two).setOnClickListener(new View.OnClickListener() {
29 | public void onClick(View v) {
30 | //hook类的非构造方法
31 | //非构造方法与构造方法HOOK写法的不同之处在于参数列表前多了方法名
32 | //如果某个方法没有重载方法的话,带参数列表与不带参数列表的hook写法等价
33 | //即JavaTweakBridge.hookJavaMethod(Activity.class, "isChild")和JavaTweakBridge.hookJavaMethod(Activity.class, "isChild()")写法等价
34 | JavaTweakBridge.hookJavaMethod(Activity.class, "startActivityForResult(android.content.Intent,int,android.os.Bundle)");
35 | }
36 | });
37 | findViewById(R.id.btn_three).setOnClickListener(new View.OnClickListener() {
38 | public void onClick(View v) {
39 | //hook类的所有重载方法
40 | //hook的写法上只需要填上方法名即可,无需填写参数列表
41 | JavaTweakBridge.hookAllJavaMethods(Activity.class, "startActivityForResult");
42 | }
43 | });
44 | findViewById(R.id.btn_four).setOnClickListener(new View.OnClickListener() {
45 | public void onClick(View v) {
46 | //hook类的所有构造方法
47 | JavaTweakBridge.hookAllJavaConstructors(Activity.class);
48 | }
49 | });
50 | findViewById(R.id.btn_five).setOnClickListener(new View.OnClickListener() {
51 | public void onClick(View v) {
52 | //hook类的所有非构造方法
53 | JavaTweakBridge.hookAllJavaMethods(Activity.class, "");
54 | }
55 | });
56 | findViewById(R.id.btn_six).setOnClickListener(new View.OnClickListener() {
57 | public void onClick(View v) {
58 | //hook类的所有方法
59 | JavaTweakBridge.hookJavaClass(Activity.class);
60 | }
61 | });
62 | findViewById(R.id.btn_seven).setOnClickListener(new View.OnClickListener() {
63 | public void onClick(View v) {
64 | //微调方法逻辑
65 | JavaTweakBridge.hookJavaMethod(Activity.class, "startActivityForResult(android.content.Intent,int,android.os.Bundle)", new JavaTweakHook() {
66 | protected void beforeHookedMethod(Object thiz, Object[] args) {
67 | BlockingDialog.showBlockingDialog((Activity) thiz, "提示", "创建Activity前");
68 | }
69 |
70 | protected void afterHookedMethod(Object thiz, Object[] args) {
71 | BlockingDialog.showBlockingDialog((Activity) thiz, "提示", "创建Activity后");
72 | }
73 | });
74 | }
75 | });
76 | findViewById(R.id.btn_eight).setOnClickListener(new View.OnClickListener() {
77 | public void onClick(View v) {
78 | //无条件替换方法逻辑
79 | JavaTweakBridge.hookJavaMethod(Activity.class, "startActivityForResult(android.content.Intent,int,android.os.Bundle)", new JavaTweakReplace() {
80 | protected Object replaceHookedMethod(Object thiz, Object[] args) {
81 | BlockingDialog.showBlockingDialog((Activity) thiz, "提示", "不再创建Activity");
82 | return null;
83 | }
84 | });
85 | }
86 | });
87 | findViewById(R.id.btn_nine).setOnClickListener(new View.OnClickListener() {
88 | public void onClick(View v) {
89 | //有条件替换逻辑
90 | JavaTweakBridge.hookJavaMethod(Activity.class, "startActivityForResult(android.content.Intent,int,android.os.Bundle)", new JavaTweakHook() {
91 | protected void beforeHookedMethod(Object thiz, Object[] args) {
92 | int which = BlockingDialog.showBlockingDialog((Activity) thiz, "提示", "点击确定会继续创建Activity\n点击取消会不再创建Activity");
93 | if (which == DialogInterface.BUTTON_NEGATIVE) {
94 | setResult(null);
95 | }
96 | }
97 | });
98 | }
99 | });
100 | findViewById(R.id.btn_test).setOnClickListener(new View.OnClickListener() {
101 | public void onClick(View v) {
102 | //自动打印调用堆栈日志(默认不打印,下面的两种写法任选其一)
103 | //JavaTweakBridge.hookJavaMethod(Activity.class, "()", true);
104 | //JavaTweakBridge.hookJavaMethod(Activity.class, "()", JavaTweakHook.HOOK_FLAG_STACK_TRACE);
105 |
106 | //禁止打印调用参数日志(默认会打印)
107 | //JavaTweakBridge.hookJavaMethod(Activity.class, "()", JavaTweakHook.HOOK_FLAG_NO_CALL_LOG);
108 |
109 | //为被混淆的方法设置别名(优化日志输出)
110 | //JavaTweakBridge.hookJavaMethod(Activity.class, "findViewById", "Activity_findViewById");
111 |
112 | //创建新的Activity以测试hook效果
113 | startActivity(new Intent(MainActivity.this, MainActivity.class));
114 | }
115 | });
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/src/com/android/guobao/liao/apptweak/JavaTweakBridge.java:
--------------------------------------------------------------------------------
1 | package com.android.guobao.liao.apptweak;
2 |
3 | import java.lang.reflect.Constructor;
4 | import java.lang.reflect.Member;
5 | import java.lang.reflect.Method;
6 | import java.util.ArrayList;
7 | import java.util.concurrent.ConcurrentHashMap;
8 | import android.util.Log;
9 | import com.android.guobao.liao.apptweak.util.ReflectUtil;
10 |
11 | public class JavaTweakBridge {
12 | static {
13 | System.loadLibrary("tweakjar");
14 | }
15 | static private final ConcurrentHashMap backupMethods = new ConcurrentHashMap();
16 |
17 | static public void writeToLogcat(int prio, String msg) {
18 | Log.println(prio, "TweakJar", msg + "\r\n");
19 | }
20 |
21 | static public void writeToLogcat(int prio, String format, Object... args) {
22 | writeToLogcat(prio, String.format(format, args));
23 | }
24 |
25 | static private native Method nativeHookMethod(Class> hook_class, String hook_method, Object hook_data, boolean can_hook_chain);
26 |
27 | synchronized //lock
28 | static public boolean hookJavaMethod(Class> hook_class, String hook_method, JavaTweakHook hook_data) {
29 | try {
30 | boolean can_hook_chain = false;
31 | if (hook_class == null || hook_method == null || hook_method.equals("")) {
32 | return false;
33 | }
34 | Member hook_member = ReflectUtil.findClassMember(hook_class, hook_method, false);
35 | if (hook_member == null) {
36 | writeToLogcat(Log.ERROR, "hookJavaMethod: method<%s> no exist.", hook_method);
37 | return false;
38 | }
39 | String method_decl = ReflectUtil.getMemberDeclare(hook_member, false);
40 | String method_key = String.format("%s@%x", method_decl, hook_class.getClassLoader().hashCode());
41 | if (!can_hook_chain && backupMethods.containsKey(method_key)) {
42 | //writeToLogcat(Log.WARN, "hookJavaMethod: method<%s> hook repeat.", hook_method);
43 | return false;
44 | }
45 | if (hook_data == null) {
46 | hook_data = JavaTweakHook.onlyLogHook();
47 | }
48 | Method m = nativeHookMethod(hook_class, hook_method, hook_data, can_hook_chain);
49 | if (m == null) {
50 | writeToLogcat(Log.ERROR, "hookJavaMethod: method<%s> hook error.", hook_method);
51 | return false;
52 | }
53 | hook_data.setBackup(m);
54 | backupMethods.put(method_key, hook_data);
55 | writeToLogcat(Log.INFO, "hookJavaMethod: method<%s> hook ok.", hook_method);
56 | return true;
57 | } catch (Throwable e) {
58 | writeToLogcat(Log.ERROR, "hookJavaMethod: method<%s> hook exception: %s.", hook_method, e);
59 | return false;
60 | }
61 | }
62 |
63 | static public boolean hookJavaMethod(Class> hook_class, String hook_method) {
64 | return hookJavaMethod(hook_class, hook_method, (JavaTweakHook) null);
65 | }
66 |
67 | static public boolean hookAllJavaMethods(Class> hook_class, String method_name, JavaTweakHook hook_data) {
68 | if (hook_class == null) {
69 | return false;
70 | }
71 | Method[] ms = hook_class.getDeclaredMethods();
72 | for (int i = 0; i < ms.length; i++) {
73 | if (method_name.equals("") || ms[i].getName().equals(method_name)) {
74 | hookJavaMember(ms[i], hook_data);
75 | }
76 | }
77 | return true;
78 | }
79 |
80 | static public boolean hookAllJavaMethods(Class> hook_class, String method_name) {
81 | return hookAllJavaMethods(hook_class, method_name, null);
82 | }
83 |
84 | static public boolean hookJavaClass(Class> hook_class, JavaTweakHook hook_data) {
85 | hookAllJavaMethods(hook_class, "", hook_data);
86 | hookAllJavaConstructors(hook_class, hook_data);
87 | return true;
88 | }
89 |
90 | static public boolean hookJavaClass(Class> hook_class) {
91 | return hookJavaClass(hook_class, null);
92 | }
93 |
94 | static public boolean hookAllJavaConstructors(Class> hook_class, JavaTweakHook hook_data) {
95 | if (hook_class == null) {
96 | return false;
97 | }
98 | Constructor>[] cs = hook_class.getDeclaredConstructors();
99 | for (int i = 0; i < cs.length; i++) {
100 | if (true) {
101 | hookJavaMember(cs[i], hook_data);
102 | }
103 | }
104 | return true;
105 | }
106 |
107 | static public boolean hookAllJavaConstructors(Class> hook_class) {
108 | return hookAllJavaConstructors(hook_class, null);
109 | }
110 |
111 | static private boolean hookJavaMember(Member hook_member, JavaTweakHook hook_data) {
112 | Class> clazz = hook_member.getDeclaringClass();
113 | String decl = ReflectUtil.getMemberDeclare(hook_member, true);
114 | if (hook_data == null) {
115 | hookJavaMethod(clazz, decl);
116 | return true;
117 | }
118 | Constructor> hook_constr = hook_data.getClass().getDeclaredConstructors()[0];
119 | String cons = ReflectUtil.getMemberDeclare(hook_constr, true);
120 |
121 | Class>[] ts = hook_constr.getParameterTypes();
122 | String zero_ = ts.length > 0 ? ts[0].getName() : "";
123 | String this_ = zero_.equals("int") || zero_.equals("java.lang.String") ? "" : zero_;
124 |
125 | Object thiz_ = ReflectUtil.getObjectField(hook_data, this_);
126 | Object name_ = ReflectUtil.getObjectField(hook_data, "name_");
127 | Object flags_ = ReflectUtil.getObjectField(hook_data, "flags_");
128 |
129 | ArrayList