60 | *
61 | * NOTE: This is not compatible with using extractNativeLibs="false" in your manifest!
62 | *
63 | * @param context Application or activity context
64 | * @param libname Name of the library
65 | * @return Full path to library or null
66 | */
67 | @SuppressLint("SdCardPath")
68 | public static String getLibraryPath(Context context, String libname) {
69 | if (Build.VERSION.SDK_INT >= 23) {
70 | if ((context.getApplicationInfo().flags & ApplicationInfo.FLAG_EXTRACT_NATIVE_LIBS) == 0) {
71 | throw new RuntimeException("incompatible with extractNativeLibs=\"false\" in your manifest");
72 | }
73 | }
74 |
75 | if (libname.toLowerCase().startsWith("lib")) {
76 | libname = libname.substring(3);
77 | }
78 | if (libname.toLowerCase().endsWith(".so")) {
79 | libname = libname.substring(0, libname.length() - 3);
80 | }
81 | String packageName = context.getPackageName();
82 |
83 | // try nativeLibraryDir
84 | ApplicationInfo appInfo = context.getApplicationInfo();
85 | for (String candidate : new String[] {
86 | appInfo.nativeLibraryDir + File.separator + "lib" + libname + ".so",
87 | appInfo.nativeLibraryDir + File.separator + libname + ".so" // unlikely but not impossible
88 | }) {
89 | if (new File(candidate).exists()) {
90 | return candidate;
91 | }
92 | }
93 |
94 | // try BaseDexClassLoader
95 | if (context.getClassLoader() instanceof BaseDexClassLoader) {
96 | try {
97 | BaseDexClassLoader bdcl = (BaseDexClassLoader)context.getClassLoader();
98 | return bdcl.findLibrary(libname);
99 | } catch (Throwable t) {
100 | // not a standard call: catch Errors and Violations too aside from Exceptions
101 | }
102 | }
103 |
104 | // try (old) default location
105 | for (String candidate : new String[] {
106 | String.format(Locale.ENGLISH, "/data/data/%s/lib/lib%s.so", packageName, libname),
107 | String.format(Locale.ENGLISH, "/data/data/%s/lib/%s.so", packageName, libname)
108 | }) {
109 | if (new File(candidate).exists()) {
110 | return candidate;
111 | }
112 | }
113 |
114 | return null;
115 | }
116 |
117 | /**
118 | * Get string to be executed (in a root shell) to launch the Java code as root.
119 | *
120 | * You would normally use {@link #getLaunchScript(Context, Class, String, String, String[], String)}
121 | *
122 | * @param context Application or activity context
123 | * @param clazz Class containing "main" method
124 | * @param app_process Specific app_process binary to use, or null for default
125 | * @param params Parameters to supply to Java code, or null
126 | * @param niceName Process name to use (ps) instead of app_process (should be unique to your app), or null
127 | * @return Script
128 | */
129 | public static String getLaunchString(Context context, Class> clazz, String app_process, String[] params, String niceName) {
130 | if (app_process == null) app_process = AppProcess.getAppProcess();
131 | return getLaunchString(context.getPackageCodePath(), clazz.getName(), app_process, AppProcess.guessIfAppProcessIs64Bits(app_process), params, niceName);
132 | }
133 |
134 | public static String getLaunchString(String packageCodePath, Class> clazz, String app_process, String[] params, String niceName) {
135 | if (app_process == null) app_process = AppProcess.getAppProcess();
136 | return getLaunchString(packageCodePath, clazz.getName(), app_process, AppProcess.guessIfAppProcessIs64Bits(app_process), params, niceName);
137 | }
138 |
139 | /**
140 | * Get string to be executed (in a root shell) to launch the Java code as root.
141 | *
142 | * You would normally use {@link #getLaunchScript(Context, Class, String, String, String[], String)}
143 | *
144 | * @param packageCodePath Path to APK
145 | * @param clazz Class containing "main" method
146 | * @param app_process Specific app_process binary to use
147 | * @param is64Bit Is specific app_process binary 64-bit?
148 | * @param params Parameters to supply to Java code, or null
149 | * @param niceName Process name to use (ps) instead of app_process (should be unique to your app), or null
150 | * @return Script
151 | */
152 | public static String getLaunchString(String packageCodePath, String clazz, String app_process, boolean is64Bit, String[] params, String niceName) {
153 | String ANDROID_ROOT = System.getenv("ANDROID_ROOT");
154 | StringBuilder prefix = new StringBuilder();
155 | if (ANDROID_ROOT != null) {
156 | prefix.append("ANDROID_ROOT=");
157 | prefix.append(ANDROID_ROOT);
158 | prefix.append(' ');
159 | }
160 |
161 | int p;
162 | String[] extraPaths = null;
163 | if ((p = app_process.lastIndexOf('/')) >= 0) {
164 | extraPaths = new String[] { app_process.substring(0, p) };
165 | }
166 | String LD_LIBRARY_PATH = Linker.getPatchedLdLibraryPath(is64Bit, extraPaths);
167 | if (LD_LIBRARY_PATH != null) {
168 | prefix.append("LD_LIBRARY_PATH=");
169 | prefix.append(LD_LIBRARY_PATH);
170 | prefix.append(' ');
171 | }
172 |
173 | String vmParams = "";
174 | String extraParams = "";
175 | if (niceName != null) {
176 | extraParams += " --nice-name=" + niceName;
177 | }
178 | if (Debugger.enabled) { // we don't use isEnabled() because that has a different meaning when called as root, and though rare we might call this method from root too
179 | vmParams += " -Xcompiler-option --debuggable";
180 | if (Build.VERSION.SDK_INT >= 28) {
181 | // Android 9.0 Pie changed things up a bit
182 | vmParams += " -XjdwpProvider:internal -XjdwpOptions:transport=dt_android_adb,suspend=n,server=y";
183 | } else {
184 | vmParams += " -agentlib:jdwp=transport=dt_android_adb,suspend=n,server=y";
185 | }
186 | }
187 | String ret = String.format("NO_ADDR_COMPAT_LAYOUT_FIXUP=1 %sCLASSPATH=%s %s%s /system/bin%s %s", prefix.toString(), packageCodePath, app_process, vmParams, extraParams, clazz);
188 | if (params != null) {
189 | StringBuilder full = new StringBuilder(ret);
190 | for (String param : params) {
191 | full.append(' ');
192 | full.append(param);
193 | }
194 | ret = full.toString();
195 | }
196 | return ret;
197 | }
198 |
199 | /**
200 | * Get script to be executed (in a root shell) to launch the Java code as root.
201 | *
202 | * app_process is relocated during script execution. If a relocate_path is supplied
203 | * it must already exist. It is also made linker-namespace-safe, so optionally you
204 | * can put native libraries there (rarely necessary). By default we relocate to the app's
205 | * cache dir, falling back to /dev in case of issues or the app living on external storage.
206 | *
207 | * Note that SELinux policy patching takes place only in the script returned from
208 | * the first call, so be sure to execute that script first if you call this method
209 | * multiple times. You can change this behavior with the {@link Policies#setPatched(Boolean)}
210 | * method. The patch is only needed for the Binder-based IPC calls, if you do not use those,
211 | * you may consider passing true to {@link Policies#setPatched(Boolean)} and prevent the
212 | * patching altogether.
213 | *
214 | * @param context Application or activity context
215 | * @param clazz Class containing "main" method
216 | * @param app_process Specific app_process binary to use, or null for default
217 | * @param relocate_path Path to relocate app_process to (must exist), or null for default
218 | * @param params Parameters to supply to Java code, or null
219 | * @param niceName Process name to use (ps) instead of app_process (should be unique to your app), or null
220 | * @return Script
221 | */
222 | public static List getLaunchScript(Context context, Class> clazz, String app_process, String relocate_path, String[] params, String niceName) {
223 | ArrayList pre = new ArrayList();
224 | ArrayList post = new ArrayList();
225 |
226 | // relocate app_process
227 | app_process = AppProcess.getAppProcessRelocate(context, app_process, pre, post, relocate_path);
228 |
229 | // librootjavadaemon uses this
230 | pre.add(0, "#app_process=" + app_process);
231 |
232 | // patch SELinux policies
233 | Policies.getPatch(pre);
234 |
235 | // combine
236 | ArrayList script = new ArrayList(pre);
237 | script.add(getLaunchString(context, clazz, app_process, params, niceName));
238 | script.addAll(post);
239 | return script;
240 | }
241 |
242 | public static List getLaunchScript(Context context,String packageCodePath, Class> clazz, String app_process, String relocate_path, String[] params, String niceName) {
243 | ArrayList pre = new ArrayList();
244 | ArrayList post = new ArrayList();
245 |
246 | // relocate app_process
247 | app_process = AppProcess.getAppProcessRelocate(context, app_process, pre, post, relocate_path);
248 |
249 | // librootjavadaemon uses this
250 | pre.add(0, "#app_process=" + app_process);
251 |
252 | // patch SELinux policies
253 | Policies.getPatch(pre);
254 |
255 | // combine
256 | ArrayList script = new ArrayList(pre);
257 | script.add(getLaunchString(packageCodePath, clazz, app_process, params, niceName));
258 | script.addAll(post);
259 | return script;
260 | }
261 |
262 | /** Prefixes of filename to remove from the app's cache directory */
263 | public static final String[] CLEANUP_CACHE_PREFIXES = new String[] { ".app_process32_", ".app_process64_" };
264 |
265 | /**
266 | * Clean up leftover files from our cache directory.
267 | *
268 | * In ideal circumstances no files should be left dangling, but in practise it happens sooner
269 | * or later anyway. Periodically (once per app launch or per boot) calling this method is
270 | * advised.
271 | *
272 | * This method should be called from a background thread, as it performs disk i/o.
273 | *
274 | * It is difficult to determine which of these files may actually be in use, especially in
275 | * daemon mode. We try to determine device boot time, and wipe everything from before that
276 | * time. For safety we explicitly keep files using our current UUID.
277 | *
278 | * @param context Context to retrieve cache directory from
279 | */
280 | public static void cleanupCache(Context context) {
281 | cleanupCache(context, CLEANUP_CACHE_PREFIXES);
282 | }
283 |
284 | /**
285 | * Clean up leftover files from our cache directory.
286 | *
287 | * This version is for internal use, see {@link #cleanupCache(Context)} instead.
288 | *
289 | * @param context Context to retrieve cache directory from
290 | * @param prefixes List of prefixes to scrub
291 | */
292 | public static void cleanupCache(Context context, final String[] prefixes) {
293 | try {
294 | File cacheDir = context.getCacheDir();
295 | if (cacheDir.exists()) {
296 | // determine time of last boot
297 | long boot = System.currentTimeMillis() - SystemClock.elapsedRealtime();
298 |
299 | // find our files
300 | for (File file : cacheDir.listFiles(new FilenameFilter() {
301 | @Override
302 | public boolean accept(File dir, String name) {
303 | boolean accept = false;
304 | for (String prefix : prefixes) {
305 | // just in case: don't return files that contain our current uuid
306 | if (name.startsWith(prefix) && !name.endsWith(AppProcess.UUID)) {
307 | accept = true;
308 | break;
309 | }
310 | }
311 | return accept;
312 | }
313 | })) {
314 | if (file.lastModified() < boot) {
315 | //noinspection ResultOfMethodCallIgnored
316 | file.delete();
317 | }
318 | }
319 | }
320 | } catch (Exception e) {
321 | e.printStackTrace();
322 | }
323 | }
324 |
325 | // ------------------------ calls for root ------------------------
326 |
327 | /**
328 | * Restores correct LD_LIBRARY_PATH environment variable. This should be one of the first
329 | * calls in your Java code running as root, after (optionally) loading native libraries.
330 | *
331 | * Failing to call this method may cause errors when executing other binaries (such as
332 | * running shell commands).
333 | *
334 | * @see Linker#restoreOriginalLdLibraryPath()
335 | * @see Linker#getPatchedLdLibraryPath(boolean, String[])
336 | */
337 | public static void restoreOriginalLdLibraryPath() {
338 | Linker.restoreOriginalLdLibraryPath();
339 | }
340 |
341 | /**
342 | * Returns a context that is useful for some calls - but this is not a proper full
343 | * context, and many calls that take a context do not actually work when running as root, or
344 | * not having the Android framework fully spun up, or not having an active ProcessRecord.
345 | * Some services can be accessed (see getPackageManager(), getSystemService(), ...).
346 | *
347 | * Due to preparing the main looper, this throws off libsuperuser if you use it for shell
348 | * commands on the main thread. If you use this call, you will probably need to call
349 | * Debug.setSanityChecksEnabled(false) to get any shell calls executed, and create a
350 | * separate HandlerThread (and Handler), and use both Shell.Builder.setAutoHandler(false)
351 | * and Shell.Builder.setHandler(^^^^) for Shell.Interactive to behave as expected.
352 | *
353 | * @return System context
354 | */
355 | public static Context getSystemContext() {
356 | return Reflection.getSystemContext();
357 | }
358 |
359 | /**
360 | * Returns a context with access to the resources of the passed package. This context is still
361 | * limited in many of the same ways the context returned by {@link #getSystemContext()} is, as
362 | * we still do not have an active ProcessRecord.
363 | *
364 | * @see #getSystemContext()
365 | *
366 | * @param packageName Name of the package to create Context for. Use BuildConfig.APPLICATION_ID (double check you're importing the correct BuildConfig!) to access our own package.
367 | * @return Package context
368 | * @throws PackageManager.NameNotFoundException If package could not be found
369 | */
370 | public static Context getPackageContext(String packageName) throws PackageManager.NameNotFoundException {
371 | return getSystemContext().createPackageContext(packageName, 0);
372 | }
373 |
374 | /**
375 | * Broadcast an intent using a reflected method that doesn't require us to have a Context or
376 | * ProcessRecord.
377 | *
378 | * @param intent Intent to broadcast
379 | */
380 | public static void sendBroadcast(Intent intent) {
381 | Reflection.sendBroadcast(intent);
382 | }
383 |
384 | }
385 |
--------------------------------------------------------------------------------
/core/src/main/java/io/sapp/core/utils/Debug.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2012-2019 Jorrit "Chainfire" Jongma
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.sapp.core.utils;
18 |
19 | import android.os.Looper;
20 | import android.os.Process;
21 | import android.util.Log;
22 |
23 | import androidx.annotation.AnyThread;
24 | import androidx.annotation.NonNull;
25 | import androidx.annotation.Nullable;
26 |
27 | import io.sapp.core.BuildConfig;
28 |
29 |
30 | /**
31 | * Utility class for logging and debug features that (by default) does nothing when not in debug mode
32 | */
33 | @SuppressWarnings({"WeakerAccess", "UnusedReturnValue", "unused"})
34 | @AnyThread
35 | public class Debug {
36 |
37 | // ----- DEBUGGING -----
38 |
39 | private static boolean debug = BuildConfig.DEBUG;
40 |
41 | /**
42 | *
Enable or disable debug mode
43 | *
44 | *
By default, debug mode is enabled for development
45 | * builds and disabled for exported APKs - see
46 | * BuildConfig.DEBUG
Is logging for specific types of messages enabled ?
166 | *
167 | *
You may | (or) LOG_* constants together, to learn if
168 | * all passed message types are enabled for logging. Note
169 | * that debug mode must also be enabled for actual logging
170 | * to occur.
Is logging for specific types of messages enabled ?
181 | *
182 | *
You may | (or) LOG_* constants together, to learn if
183 | * all message types are enabled for logging. Takes
184 | * debug mode into account for the result.
Replaces the log method (write to logcat) with your own
197 | * handler. Whether your handler gets called is still dependent
198 | * on debug mode and message types being enabled for logging.
257 | *
258 | * @return Running on main thread ?
259 | */
260 | public static boolean onMainThread() {
261 | return ((Looper.myLooper() != null) && (Looper.myLooper() == Looper.getMainLooper()) && (Process.myUid() != 0));
262 | }
263 |
264 | }
265 |
--------------------------------------------------------------------------------
/core/src/main/java/io/sapp/core/utils/ShellNotClosedException.java:
--------------------------------------------------------------------------------
1 | package io.sapp.core.utils;
2 |
3 | /**
4 | * Exception class used to notify developer that a shell was not close()d
5 | */
6 | @SuppressWarnings("serial")
7 | public class ShellNotClosedException extends RuntimeException {
8 | public static final String EXCEPTION_NOT_CLOSED = "Application did not close() interactive shell";
9 |
10 | public ShellNotClosedException() {
11 | super(EXCEPTION_NOT_CLOSED);
12 | }
13 | }
--------------------------------------------------------------------------------
/core/src/main/java/io/sapp/core/utils/ShellOnMainThreadException.java:
--------------------------------------------------------------------------------
1 | package io.sapp.core.utils;
2 |
3 | /**
4 | * Exception class used to crash application when shell commands are executed
5 | * from the main thread, and we are in debug mode.
6 | */
7 | @SuppressWarnings("serial")
8 | public class ShellOnMainThreadException extends RuntimeException {
9 | public static final String EXCEPTION_COMMAND = "Application attempted to run a shell command from the main thread";
10 | public static final String EXCEPTION_NOT_IDLE = "Application attempted to wait for a non-idle shell to close on the main thread";
11 | public static final String EXCEPTION_WAIT_IDLE = "Application attempted to wait for a shell to become idle on the main thread";
12 |
13 | public ShellOnMainThreadException(String message) {
14 | super(message);
15 | }
16 | }
--------------------------------------------------------------------------------
/core/src/main/java/io/sapp/core/utils/StreamGobbler.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2012-2019 Jorrit "Chainfire" Jongma
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.sapp.core.utils;
18 |
19 | import java.io.BufferedReader;
20 | import java.io.IOException;
21 | import java.io.InputStream;
22 | import java.io.InputStreamReader;
23 | import java.util.List;
24 |
25 | /**
26 | * Thread utility class continuously reading from an InputStream
27 | */
28 | public class StreamGobbler extends Thread {
29 | /**
30 | * Line callback interface
31 | */
32 | public interface OnLineListener {
33 | /**
34 | *
Line callback
35 | *
36 | *
This callback should process the line as quickly as possible.
37 | * Delays in this callback may pause the native process or even
38 | * result in a deadlock
We use this class because shell STDOUT and STDERR should be read as quickly as
54 | * possible to prevent a deadlock from occurring, or Process.waitFor() never
55 | * returning (as the buffer is full, pausing the native process)
56 | *
57 | * @param shell Name of the shell
58 | * @param inputStream InputStream to read from
59 | * @param outputList List to write to, or null
60 | */
61 | public StreamGobbler(String shell, InputStream inputStream, List outputList) {
62 | this.shell = shell;
63 | reader = new BufferedReader(new InputStreamReader(inputStream));
64 | writer = outputList;
65 | }
66 |
67 | /**
68 | *
StreamGobbler constructor
69 | *
70 | *
We use this class because shell STDOUT and STDERR should be read as quickly as
71 | * possible to prevent a deadlock from occurring, or Process.waitFor() never
72 | * returning (as the buffer is full, pausing the native process)
73 | *
74 | * @param shell Name of the shell
75 | * @param inputStream InputStream to read from
76 | * @param onLineListener OnLineListener callback
77 | */
78 | public StreamGobbler(String shell, InputStream inputStream, OnLineListener onLineListener) {
79 | this.shell = shell;
80 | reader = new BufferedReader(new InputStreamReader(inputStream));
81 | listener = onLineListener;
82 | }
83 |
84 | @Override
85 | public void run() {
86 | // keep reading the InputStream until it ends (or an error occurs)
87 | try {
88 | String line = null;
89 | while ((line = reader.readLine()) != null) {
90 | Debug.logOutput(String.format("[%s] %s", shell, line));
91 | if (writer != null) writer.add(line);
92 | if (listener != null) listener.onLine(line);
93 | }
94 | } catch (IOException e) {
95 | }
96 |
97 | // make sure our stream is closed and resources will be freed
98 | try {
99 | reader.close();
100 | } catch (IOException e) {
101 | }
102 | }
103 | }
--------------------------------------------------------------------------------
/core/src/main/res/values/theme.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
--------------------------------------------------------------------------------
/core/src/test/java/io/sapp/core/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package io.sapp.core;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/dynamic/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/dynamic/README.md:
--------------------------------------------------------------------------------
1 | # Dynamic
2 | 这是插件化Apk核心
--------------------------------------------------------------------------------
/dynamic/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.library'
3 | }
4 |
5 | android {
6 | compileSdkVersion 31
7 | buildToolsVersion "31.0.0"
8 |
9 | defaultConfig {
10 | minSdkVersion 21
11 | targetSdkVersion 31
12 | versionCode 1
13 | versionName "1.0"
14 |
15 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
16 | consumerProguardFiles "consumer-rules.pro"
17 | }
18 |
19 | buildTypes {
20 | release {
21 | minifyEnabled false
22 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
23 | }
24 | }
25 | compileOptions {
26 | sourceCompatibility JavaVersion.VERSION_1_8
27 | targetCompatibility JavaVersion.VERSION_1_8
28 | }
29 | }
30 |
31 | dependencies {
32 |
33 | implementation 'androidx.appcompat:appcompat:1.3.1'
34 | implementation 'com.google.android.material:material:1.4.0'
35 | testImplementation 'junit:junit:4.+'
36 | androidTestImplementation 'androidx.test.ext:junit:1.1.3'
37 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
38 | }
--------------------------------------------------------------------------------
/dynamic/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xw103/DeepApp/d30fab1e0a600d586bf10d83483ff2b7141b2575/dynamic/consumer-rules.pro
--------------------------------------------------------------------------------
/dynamic/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/dynamic/src/androidTest/java/io/dynamic/loader/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package io.dynamic.loader;
2 |
3 | import android.content.Context;
4 |
5 | import androidx.test.platform.app.InstrumentationRegistry;
6 | import androidx.test.ext.junit.runners.AndroidJUnit4;
7 |
8 | import org.junit.Test;
9 | import org.junit.runner.RunWith;
10 |
11 | import static org.junit.Assert.*;
12 |
13 | /**
14 | * Instrumented test, which will execute on an Android device.
15 | *
16 | * @see Testing documentation
17 | */
18 | @RunWith(AndroidJUnit4.class)
19 | public class ExampleInstrumentedTest {
20 | @Test
21 | public void useAppContext() {
22 | // Context of the app under test.
23 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
24 | assertEquals("com.dynamic.loader.test", appContext.getPackageName());
25 | }
26 | }
--------------------------------------------------------------------------------
/dynamic/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
--------------------------------------------------------------------------------
/dynamic/src/test/java/io/dynamic/loader/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package io.dynamic.loader;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app"s APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Automatically convert third-party libraries to use AndroidX
19 | android.enableJetifier=true
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/hide/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/hide/README.md:
--------------------------------------------------------------------------------
1 | # Hide App
2 | 这是APK隐藏核心,一键隐藏应用程序防止被检测。
3 | 由于需要对base.apk进行隐藏,所以请在确保不需要访问base.apk文件后再进行隐藏。
4 | ### 随机安装
5 | - [x] 随机应用名
6 | - [x] 随机包名
7 | - [x] 随机版本号
8 | - [ ] 随机图标
9 |
--------------------------------------------------------------------------------
/hide/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.library'
3 | }
4 |
5 | android {
6 | compileSdkVersion 30
7 | buildToolsVersion "31.0.0"
8 |
9 | defaultConfig {
10 | minSdkVersion 21
11 | targetSdkVersion 28
12 | versionCode 1
13 | versionName "1.0"
14 |
15 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
16 | consumerProguardFiles "consumer-rules.pro"
17 | }
18 |
19 | buildTypes {
20 | release {
21 | minifyEnabled false
22 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
23 | }
24 | }
25 | compileOptions {
26 | sourceCompatibility JavaVersion.VERSION_1_8
27 | targetCompatibility JavaVersion.VERSION_1_8
28 | }
29 | }
30 |
31 | dependencies {
32 | implementation fileTree(dir: 'libs', include: ['*.jar'])
33 | implementation 'androidx.appcompat:appcompat:1.3.1'
34 | implementation 'com.google.android.material:material:1.4.0'
35 | // testImplementation 'junit:junit:4.+'
36 | androidTestImplementation 'androidx.test.ext:junit:1.1.3'
37 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
38 | }
--------------------------------------------------------------------------------
/hide/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xw103/DeepApp/d30fab1e0a600d586bf10d83483ff2b7141b2575/hide/consumer-rules.pro
--------------------------------------------------------------------------------
/hide/libs/ManifestEditor-1.0.2.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xw103/DeepApp/d30fab1e0a600d586bf10d83483ff2b7141b2575/hide/libs/ManifestEditor-1.0.2.jar
--------------------------------------------------------------------------------
/hide/libs/bcprov-jdk15-143.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xw103/DeepApp/d30fab1e0a600d586bf10d83483ff2b7141b2575/hide/libs/bcprov-jdk15-143.jar
--------------------------------------------------------------------------------
/hide/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/hide/src/androidTest/java/io/app/hide/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package io.app.hide;
2 |
3 | import android.content.Context;
4 |
5 | import androidx.test.platform.app.InstrumentationRegistry;
6 | import androidx.test.ext.junit.runners.AndroidJUnit4;
7 |
8 | import org.junit.Test;
9 | import org.junit.runner.RunWith;
10 |
11 | import static org.junit.Assert.*;
12 |
13 | /**
14 | * Instrumented test, which will execute on an Android device.
15 | *
16 | * @see Testing documentation
17 | */
18 | @RunWith(AndroidJUnit4.class)
19 | public class ExampleInstrumentedTest {
20 | @Test
21 | public void useAppContext() {
22 | // Context of the app under test.
23 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
24 | assertEquals("io.app.hide.test", appContext.getPackageName());
25 | }
26 | }
--------------------------------------------------------------------------------
/hide/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/hide/src/main/java/io/app/hide/activity/InstallActivity.java:
--------------------------------------------------------------------------------
1 | package io.app.hide.activity;
2 |
3 | import android.app.Activity;
4 | import android.app.AlertDialog;
5 | import android.content.Intent;
6 | import android.content.pm.PackageInfo;
7 | import android.content.pm.PackageManager;
8 | import android.net.Uri;
9 | import android.os.Build;
10 | import android.os.Bundle;
11 | import android.os.Handler;
12 | import android.os.Looper;
13 | import android.util.Log;
14 |
15 | import androidx.annotation.Nullable;
16 |
17 | import com.wind.meditor.core.FileProcesser;
18 | import com.wind.meditor.property.AttributeItem;
19 | import com.wind.meditor.property.ModificationProperty;
20 | import com.wind.meditor.utils.NodeValue;
21 |
22 | import java.io.ByteArrayOutputStream;
23 | import java.io.File;
24 | import java.io.FileInputStream;
25 | import java.io.FileOutputStream;
26 | import java.io.IOException;
27 | import java.io.InputStream;
28 | import java.io.OutputStream;
29 |
30 | import io.app.hide.R;
31 | import io.app.hide.apksigner.KeyHelper;
32 | import io.app.hide.apksigner.SignApk;
33 | import io.app.hide.utils.RandomInfo;
34 |
35 | public class InstallActivity extends Activity implements Runnable {
36 | @Override
37 | protected void onCreate(@Nullable Bundle savedInstanceState) {
38 | super.onCreate(savedInstanceState);
39 | setContentView(R.layout.activity_install);
40 | new Thread(this).start();
41 | }
42 |
43 | @Override
44 | public void run() {
45 | try {
46 | File baseDir = new File(getFilesDir().getAbsolutePath());
47 | if (baseDir.exists()) {
48 | delete(baseDir);
49 | }
50 | baseDir.mkdirs();
51 | //拷贝apk到工作目录
52 | File inFile = //new File("/storage/emulated/0/base.apk");
53 | new File(getApplicationInfo().sourceDir);
54 | File outFile = new File(baseDir.getAbsolutePath() + "/base.apk");
55 | outFile.createNewFile();
56 | FileInputStream fis = new FileInputStream(inFile);
57 | FileOutputStream fos = new FileOutputStream(outFile);
58 | byte[] buf = new byte[10240];
59 | int len = 0;
60 | while ((len = fis.read(buf)) != -1) {
61 | fos.write(buf, 0, len);
62 | }
63 | fis.close();
64 | fos.flush();
65 | fos.close();
66 | ModificationProperty property = new ModificationProperty();
67 | property.addManifestAttribute(new AttributeItem(NodeValue.Manifest.VERSION_CODE, RandomInfo.getVersionCode()))
68 | .addManifestAttribute(new AttributeItem(NodeValue.Manifest.VERSION_NAME, RandomInfo.getVersionName()))
69 | .addApplicationAttribute(new AttributeItem(NodeValue.Application.LABEL, RandomInfo.getAppName()))
70 | .addManifestAttribute(new AttributeItem(NodeValue.Manifest.PACKAGE, RandomInfo.getRandomPackageName()).setNamespace(null));
71 | // 处理得到的未签名的apk
72 | FileProcesser.processApkFile(outFile.getAbsolutePath(), baseDir.getAbsolutePath() + "/app-unsigned.apk", property);
73 | delete(outFile);
74 | if (signApk(baseDir.getAbsolutePath() + "/app-unsigned.apk", baseDir.getAbsolutePath() + "/app-release.apk")) {
75 | installApk();
76 | } else {
77 | throw new Exception("签名失败");
78 | }
79 | } catch (Exception e) {
80 | e.printStackTrace();
81 | new Handler(Looper.getMainLooper()).post(() -> {
82 | AlertDialog.Builder builder = new AlertDialog.Builder(InstallActivity.this);
83 | builder.setTitle("随机安装失败");
84 | builder.setMessage(Log.getStackTraceString(e));
85 | builder.setPositiveButton("确定", (dialog, which) -> {
86 | dialog.dismiss();
87 | });
88 | AlertDialog d = builder.create();
89 | d.show();
90 | });
91 | }
92 | }
93 |
94 | public void installApk() {
95 | runShell("pm install " + getFilesDir().getAbsolutePath() + "/app-release.apk", true);
96 | PackageManager pm = getPackageManager();
97 | PackageInfo pi = pm.getPackageArchiveInfo(getFilesDir().getAbsolutePath() + "/app-release.apk", 0);
98 | if (pi == null) {
99 | new Handler(Looper.getMainLooper()).post(() -> {
100 | AlertDialog.Builder builder = new AlertDialog.Builder(InstallActivity.this);
101 | builder.setTitle("安装失败");
102 | builder.setMessage("apk打包失败");
103 | builder.setPositiveButton("确定", (dialog, which) -> {
104 | dialog.dismiss();
105 | finish();
106 | });
107 | AlertDialog d = builder.create();
108 | d.show();
109 | });
110 | }else {
111 | new Handler(Looper.getMainLooper()).post(() -> {
112 | AlertDialog.Builder builder = new AlertDialog.Builder(InstallActivity.this);
113 | builder.setTitle("提示");
114 | builder.setMessage("安装成功:" +
115 | "\n软件名:" + pi.applicationInfo.nonLocalizedLabel +
116 | "\n包名:" + pi.packageName);
117 | builder.setPositiveButton("确定", (dialog, which) -> {
118 | runShell("pm uninstall " + getPackageName(), true);
119 | });
120 | AlertDialog d = builder.create();
121 | d.show();
122 | });
123 | }
124 | }
125 |
126 |
127 | public byte[] runShell(String command, boolean isRoot) {
128 | // System.out.println("cmd:"+command);
129 | try {
130 | Process process = Runtime.getRuntime().exec(isRoot ? "su" : "sh");
131 | InputStream ins = process.getInputStream();
132 | InputStream es = process.getErrorStream();
133 | OutputStream ous = process.getOutputStream();
134 | ous.write("\n".getBytes());
135 | ous.flush();
136 | ous.write(command.getBytes());
137 | ous.flush();
138 | ous.write("\n".getBytes());
139 | ous.flush();
140 | ous.write("exit".getBytes());
141 | ous.flush();
142 | ous.write("\n".getBytes());
143 | ous.flush();
144 | byte[] result = readInputStream(ins, false);
145 | byte[] error = readInputStream(es, false);
146 | process.waitFor();
147 | ins.close();
148 | es.close();
149 | ous.close();
150 | if (new String(error).trim().isEmpty()) {
151 | return result;
152 | } else {
153 | return null;
154 | }
155 | } catch (Throwable th) {
156 | return null;
157 | }
158 | }
159 |
160 | public static byte[] readInputStream(InputStream ins, boolean close) {
161 | try {
162 | ByteArrayOutputStream bos = new ByteArrayOutputStream();
163 | int i = -1;
164 | byte[] buf = new byte[1024];
165 | while ((i = ins.read(buf)) != -1) {
166 | bos.write(buf, 0, i);
167 | }
168 | if (close) {
169 | ins.close();
170 | bos.close();
171 | }
172 | return bos.toByteArray();
173 | } catch (Throwable th) {
174 | return Log.getStackTraceString(th).getBytes();
175 | }
176 | }
177 |
178 | public static boolean signApk(String src, String dest) throws Exception {
179 | SignApk signApk = new SignApk(KeyHelper.privateKey, KeyHelper.sigPrefix);
180 |
181 | boolean signed = signApk.sign(src, dest);
182 | if (signed) {
183 | //verify signed apk
184 | return SignApk.verifyJar(dest);
185 | }
186 | return false;
187 | }
188 |
189 | public static void delete(File file) {
190 | if (file.isFile()) {
191 | file.delete();
192 | return;
193 | }
194 |
195 | if (file.isDirectory()) {
196 | File[] childFiles = file.listFiles();
197 | if (childFiles == null || childFiles.length == 0) {
198 | file.delete();
199 | return;
200 | }
201 |
202 | for (int i = 0; i < childFiles.length; i++) {
203 | delete(childFiles[i]);
204 | }
205 | file.delete();
206 | }
207 | }
208 | }
209 |
--------------------------------------------------------------------------------
/hide/src/main/java/io/app/hide/apksigner/KeyHelper.java:
--------------------------------------------------------------------------------
1 | package io.app.hide.apksigner;
2 |
3 | import android.util.Base64;
4 |
5 | import java.io.File;
6 | import java.io.FileInputStream;
7 | import java.io.IOException;
8 | import java.net.URISyntaxException;
9 | import java.security.Key;
10 | import java.security.KeyStore;
11 |
12 |
13 | /**
14 | * 用于生成 PrivateKey 和 SigPrefix
15 | * 用法,先用keytool生成keystore,在用这个keystore签名任意一个apk,
16 | * 解压出这个apk的META-INF/CERT.RSA文件
17 | * 复制这生成的keystore文件和CERT.RSA到src/resources目录
18 | */
19 | public final class KeyHelper {
20 |
21 | /**
22 | * 生成keystore 命令
23 | * keytool -genkey -alias mytestkey -keyalg RSA -keysize 512 -validity 40000 -keystore demo.keystore
24 | *
25 | * alias mytestkey
26 | * pwd 123456
27 | *
28 | * 签名apk
29 | * jarsigner -verbose -keystore demo.keystore -digestalg SHA1 -sigalg sha1withrsa -signedjar signed.apk unsign.apk mytestkey
30 | *
31 | * 验证apk是否签名成功
32 | * jarsigner -verify ~/sign.apk
33 | */
34 |
35 |
36 | public static void main(String[] args) {
37 | try {
38 | getPrivateKey();
39 | getSigPrefix();
40 | } catch (Exception e) {
41 | e.printStackTrace();
42 | }
43 | }
44 |
45 |
46 | /**
47 | * 读取 PrivateKey
48 | * @throws Exception
49 | */
50 | public static void getPrivateKey() throws Exception {
51 |
52 | String keystoreFileName = "demo.keystore"; //resources 目录下的keystore文件
53 | String keystorePassword = "123456";
54 | String alias = "mytestkey";
55 | String keyPassword = "123456";
56 |
57 | KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
58 | keystore.load(ClassLoader.getSystemClassLoader().getResourceAsStream(keystoreFileName), keystorePassword.toCharArray());
59 | Key key = keystore.getKey(alias, keyPassword.toCharArray());
60 | String string = new String(Base64.encode(key.getEncoded(),Base64.DEFAULT), "UTF-8");
61 | System.out.println("PrivateKey " + string);
62 | }
63 |
64 | /**
65 | * 签名前缀
66 | * 首先用上面生成的keystore签名任意一个apk,解压出这个apk里面 META-INF/CERT.RSA 的文件
67 | * @throws IOException
68 | */
69 | private static void getSigPrefix() throws IOException, URISyntaxException {
70 | System.out.println("----------");
71 | String rsaFileName="CERT.RSA";
72 | File file = new File(ClassLoader.getSystemClassLoader().getResource(rsaFileName).toURI());
73 | FileInputStream fis = new FileInputStream(file);
74 |
75 | /**
76 | * RSA-keysize signature-length
77 | # 512 64
78 | # 1024 128
79 | # 2048 256
80 | */
81 |
82 | int same = (int) (file.length() - 64); //当前-keysize 512
83 |
84 | byte[] buff = new byte[same];
85 | fis.read(buff, 0, same);
86 | fis.close();
87 |
88 | String string = new String(Base64.encode(buff,Base64.DEFAULT), "UTF-8");
89 | System.out.println("sigPrefix -->> " + string);
90 |
91 |
92 | }
93 |
94 |
95 | public static String privateKey = "MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAi3iIMEMo0ogaeXFEtooya6n+FZqxyRWpiKD9jg/LE5CxdTy7RlQesmfQ+uVJvMlF4ebpOhQp+aIHOp+UxZAUfQIDAQABAkAMt7jzbaxTRkXjvQhe/MsMNjwNDEYZ5/fFlaiJQ7do2S5dtCZQ966Vb1dLZlVWjPWnR99YiCYxd5qOenyTujgBAiEAwMDm9zEFN/YkFewdi0/4bW0OONlCJWCHqkN0poLIJsECIQC5O/AnP4vr/92+tcdemfROgfmlvK/NWnuEzSRJ1uF4vQIhALTgkBxo0MfZ37T+tB619Z7h1pW8MlkWw1ggIsfaM+5BAiBHSllAUcXBW6V1S7LipvAO8xko/3jN2SAm2Wk4/fmjJQIgEKdiL/87EQkL3unusUZgLyonz7d7FjHonUARloYZboU=";
96 | public static String sigPrefix = "MIICwwYJKoZIhvcNAQcCoIICtDCCArACAQExCzAJBgUrDgMCGgUAMAsGCSqGSIb3DQEHAaCCAckwggHFMIIBb6ADAgECAgQTmjt5MA0GCSqGSIb3DQEBCwUAMFcxCzAJBgNVBAYTAmNuMREwDwYDVQQIEwhzaGFuZ2hhaTERMA8GA1UEBxMIc2hhbmdoYWkxCjAIBgNVBAoTAXoxCjAIBgNVBAsTAXoxCjAIBgNVBAMTAXowIBcNMTUwOTIyMTIyMDUxWhgPMjEyNTAzMjkxMjIwNTFaMFcxCzAJBgNVBAYTAmNuMREwDwYDVQQIEwhzaGFuZ2hhaTERMA8GA1UEBxMIc2hhbmdoYWkxCjAIBgNVBAoTAXoxCjAIBgNVBAsTAXoxCjAIBgNVBAMTAXowXDANBgkqhkiG9w0BAQEFAANLADBIAkEAi3iIMEMo0ogaeXFEtooya6n+FZqxyRWpiKD9jg/LE5CxdTy7RlQesmfQ+uVJvMlF4ebpOhQp+aIHOp+UxZAUfQIDAQABoyEwHzAdBgNVHQ4EFgQUnjtnyFJuunyPb+Ez8F3wuJUxqCgwDQYJKoZIhvcNAQELBQADQQBHWscfuo5oEsCkeva0xg6Ub7qOfOUDqr1R3HJ4x5M17fN2GbBK7j0Wu7aRtabNHnEhGQNvVhLTWzrXpxQj+1/mMYHDMIHAAgEBMF8wVzELMAkGA1UEBhMCY24xETAPBgNVBAgTCHNoYW5naGFpMREwDwYDVQQHEwhzaGFuZ2hhaTEKMAgGA1UEChMBejEKMAgGA1UECxMBejEKMAgGA1UEAxMBegIEE5o7eTAJBgUrDgMCGgUAMA0GCSqGSIb3DQEBAQUABEA=";
97 |
98 |
99 | }
--------------------------------------------------------------------------------
/hide/src/main/java/io/app/hide/apksigner/SignApk.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2008 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.app.hide.apksigner;
18 |
19 |
20 | import java.io.ByteArrayOutputStream;
21 | import java.io.File;
22 | import java.io.FileOutputStream;
23 | import java.io.FilterOutputStream;
24 | import java.io.IOException;
25 | import java.io.InputStream;
26 | import java.io.OutputStream;
27 | import java.io.PrintStream;
28 | import java.io.UnsupportedEncodingException;
29 | import java.security.CodeSigner;
30 | import java.security.DigestOutputStream;
31 | import java.security.GeneralSecurityException;
32 | import java.security.KeyFactory;
33 | import java.security.MessageDigest;
34 | import java.security.PrivateKey;
35 | import java.security.Signature;
36 | import java.security.SignatureException;
37 | import java.security.spec.PKCS8EncodedKeySpec;
38 | import java.util.ArrayList;
39 | import java.util.Base64;
40 | import java.util.Collections;
41 | import java.util.Enumeration;
42 | import java.util.Map;
43 | import java.util.TreeMap;
44 | import java.util.Vector;
45 | import java.util.jar.Attributes;
46 | import java.util.jar.JarEntry;
47 | import java.util.jar.JarFile;
48 | import java.util.jar.JarOutputStream;
49 | import java.util.jar.Manifest;
50 | import java.util.regex.Pattern;
51 |
52 | /**
53 | * HISTORICAL NOTE:
54 | *
55 | * Prior to the keylimepie release, SignApk ignored the signature
56 | * algorithm specified in the certificate and always used SHA1withRSA.
57 | *
58 | * Starting with keylimepie, we support SHA256withRSA, and use the
59 | * signature algorithm in the certificate to select which to use
60 | * (SHA256withRSA or SHA1withRSA).
61 | *
62 | * Because there are old keys still in use whose certificate actually
63 | * says "MD5withRSA", we treat these as though they say "SHA1withRSA"
64 | * for compatibility with older releases. This can be changed by
65 | * altering the getAlgorithm() function below.
66 | */
67 |
68 | /**
69 | * 原始代码见aosp项目目录 build/tools/signapk/SignApk.java
70 | * 如何生成privateKey 和 sigPrefix 见{@see KeyHelper}
71 | */
72 | public class SignApk {
73 | private static final String META_INF = "META-INF/";
74 |
75 | // prefix for new signature-related files in META-INF directory
76 | private static final String SIG_PREFIX = META_INF + "SIG-";
77 |
78 |
79 | private static final String CERT_SF_NAME = META_INF+"CERT.SF";
80 | private static final String CERT_RSA_NAME = META_INF+"CERT.RSA";
81 |
82 |
83 | // Files matching this pattern are not copied to the output.
84 | private static Pattern stripPattern =
85 | Pattern.compile("^(META-INF/((.*)[.](SF|RSA|DSA)))|(" +
86 | Pattern.quote(JarFile.MANIFEST_NAME) + ")$");
87 |
88 |
89 | private String privateKey;
90 | private String sigPrefix;
91 |
92 | public SignApk(String privateKey, String sigPrefix) {
93 | this.privateKey = privateKey;
94 | this.sigPrefix = sigPrefix;
95 | }
96 |
97 |
98 | /**
99 | * Add the hash(es) of every file to the manifest, creating it if
100 | * necessary.
101 | */
102 | private Manifest addDigestsToManifest(JarFile jar)
103 | throws IOException, GeneralSecurityException {
104 | Manifest input = jar.getManifest();
105 | Manifest output = new Manifest();
106 | Attributes main = output.getMainAttributes();
107 | if (input != null) {
108 | main.putAll(input.getMainAttributes());
109 | } else {
110 | main.putValue("Manifest-Version", "1.0");
111 | main.putValue("Created-By", "1.0 (Android SignApk)");
112 | }
113 |
114 | MessageDigest md_sha1 = MessageDigest.getInstance("SHA1");
115 |
116 | byte[] buffer = new byte[4096];
117 | int num;
118 |
119 | // We sort the input entries by name, and add them to the
120 | // output manifest in sorted order. We expect that the output
121 | // map will be deterministic.
122 |
123 | TreeMap byName = new TreeMap();
124 |
125 | for (Enumeration e = jar.entries(); e.hasMoreElements(); ) {
126 | JarEntry entry = e.nextElement();
127 | byName.put(entry.getName(), entry);
128 | }
129 |
130 | for (JarEntry entry : byName.values()) {
131 | String name = entry.getName();
132 | if (!entry.isDirectory() &&
133 | (stripPattern == null || !stripPattern.matcher(name).matches())) {
134 | InputStream data = jar.getInputStream(entry);
135 | while ((num = data.read(buffer)) > 0) {
136 | md_sha1.update(buffer, 0, num);
137 | }
138 |
139 | Attributes attr = null;
140 | if (input != null) attr = input.getAttributes(name);
141 | attr = attr != null ? new Attributes(attr) : new Attributes();
142 | attr.putValue("SHA1-Digest", new String(Base64.getEncoder().encode(md_sha1.digest()), "ASCII"));
143 | output.getEntries().put(name, attr);
144 | }
145 | }
146 |
147 | return output;
148 | }
149 |
150 |
151 | /**
152 | * 此处会有bug,在jdk和android上出现的结果不同,建议只重写write(int b)即可,
153 | * 在android 上会调用write(int b)后再次调用write(byte[] b, int off, int len)导致数据重复出错
154 | *
155 | * Write to another stream and track how many bytes have been
156 | * written.
157 | */
158 | private static class CountOutputStream extends FilterOutputStream {
159 | private int mCount;
160 | private Signature mSignature;
161 |
162 | public CountOutputStream(OutputStream out, Signature sig) {
163 | super(out);
164 | mCount = 0;
165 | mSignature = sig;
166 | }
167 |
168 | @Override
169 | public void write(int b) throws IOException {
170 | try {
171 | mSignature.update((byte) b);
172 | } catch (SignatureException e) {
173 | throw new IOException("SignatureException: " + e);
174 | }
175 | super.write(b);
176 | mCount++;
177 | }
178 |
179 |
180 | // @Override
181 | // public void write(byte[] b, int off, int len) throws IOException {
182 | // try {
183 | // mSignature.update(b, off, len);
184 | // } catch (SignatureException e) {
185 | // throw new IOException("SignatureException: " + e);
186 | // }
187 | // super.write(b, off, len);
188 | // mCount += len;
189 | // }
190 |
191 | public int size() {
192 | return mCount;
193 | }
194 | }
195 |
196 | /**
197 | * Write a .SF file with a digest of the specified manifest.
198 | */
199 | private byte[] writeSignatureFile(Manifest manifest, OutputStream out)
200 | throws Exception {
201 | Manifest sf = new Manifest();
202 | Attributes main = sf.getMainAttributes();
203 | main.putValue("Signature-Version", "1.0");
204 | main.putValue("Created-By", "1.0 (Android SignApk)");
205 |
206 | MessageDigest md = MessageDigest.getInstance("SHA1");
207 | PrintStream print = new PrintStream(
208 | new DigestOutputStream(new ByteArrayOutputStream(), md),
209 | true, "UTF-8");
210 |
211 | // Digest of the entire manifest
212 | manifest.write(print);
213 | print.flush();
214 | main.putValue("SHA1-Digest-Manifest", new String(Base64.getEncoder().encode(md.digest()), "ASCII"));
215 |
216 | Map entries = manifest.getEntries();
217 | for (Map.Entry entry : entries.entrySet()) {
218 | // Digest of the manifest stanza for this entry.
219 | print.print("Name: " + entry.getKey() + "\r\n");
220 | for (Map.Entry