(dex_files.release()));
233 | return mCookie;
234 | }
235 |
236 | }
237 |
--------------------------------------------------------------------------------
/shellApplicationSourceCode/java/cn/yongye/nativeshell/inmemoryloaddex/ClassPathURLStreamHandler.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 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 | package cn.yongye.nativeshell.inmemoryloaddex;
17 |
18 | import java.io.File;
19 | import java.io.FileNotFoundException;
20 | import java.io.FilterInputStream;
21 | import java.io.IOException;
22 | import java.io.InputStream;
23 | import java.net.JarURLConnection;
24 | import java.net.MalformedURLException;
25 | import java.net.URL;
26 | import java.net.URLConnection;
27 | import java.net.URLStreamHandler;
28 | import java.util.jar.JarFile;
29 | import java.util.zip.ZipEntry;
30 |
31 |
32 | /**
33 | * A {@link URLStreamHandler} for a specific class path {@link JarFile}. This class avoids the need
34 | * to open a jar file multiple times to read resources if the jar file can be held open. The
35 | * {@link URLConnection} objects created are a subclass of {@link JarURLConnection}.
36 | *
37 | * Use {@link #getEntryUrlOrNull(String)} to obtain a URL backed by this stream handler.
38 | */
39 | public class ClassPathURLStreamHandler extends Handler {
40 | private final String fileUri;
41 | private final JarFile jarFile;
42 |
43 | public ClassPathURLStreamHandler(String jarFileName) throws IOException {
44 | jarFile = new JarFile(jarFileName);
45 |
46 | // File.toURI() is compliant with RFC 1738 in always creating absolute path names. If we
47 | // construct the URL by concatenating strings, we might end up with illegal URLs for relative
48 | // names.
49 | this.fileUri = new File(jarFileName).toURI().toString();
50 | }
51 |
52 | /**
53 | * Returns a URL backed by this stream handler for the named resource, or {@code null} if the
54 | * entry cannot be found under the exact name presented.
55 | */
56 | public URL getEntryUrlOrNull(String entryName) {
57 | if (findEntryWithDirectoryFallback(jarFile, entryName) != null) {
58 | try {
59 | // Encode the path to ensure that any special characters like # survive their trip through
60 | // the URL. Entry names must use / as the path separator.
61 | String encodedName = ParseUtil.encodePath(entryName, false);
62 | return new URL("jar", null, -1, fileUri + "!/" + encodedName, this);
63 | } catch (MalformedURLException e) {
64 | throw new RuntimeException("Invalid entry name", e);
65 | }
66 | }
67 | return null;
68 | }
69 |
70 | /**
71 | * Returns true if an entry with the specified name exists and is stored (not compressed),
72 | * and false otherwise.
73 | */
74 | public boolean isEntryStored(String entryName) {
75 | ZipEntry entry = jarFile.getEntry(entryName);
76 | return entry != null && entry.getMethod() == ZipEntry.STORED;
77 | }
78 |
79 | @Override
80 | protected URLConnection openConnection(URL url) throws IOException {
81 | return new ClassPathURLConnection(url);
82 | }
83 |
84 | /** Used from tests to indicate this stream handler is finished with. */
85 | public void close() throws IOException {
86 | jarFile.close();
87 | }
88 |
89 | /**
90 | * Finds an entry with the specified name in the {@code jarFile}. If an exact match isn't found it
91 | * will also try with "/" appended, if appropriate. This is to maintain compatibility with
92 | * and its treatment of directory entries.
93 | */
94 | static ZipEntry findEntryWithDirectoryFallback(JarFile jarFile, String entryName) {
95 | ZipEntry entry = jarFile.getEntry(entryName);
96 | if (entry == null && !entryName.endsWith("/") ) {
97 | entry = jarFile.getEntry(entryName + "/");
98 | }
99 | return entry;
100 | }
101 |
102 | private class ClassPathURLConnection extends JarURLConnection {
103 | // The JarFile instance can be shared across URLConnections and should not be closed when it is:
104 | //
105 | // Sharing occurs if getUseCaches() is true when connect() is called (which can take place
106 | // implicitly). useCachedJarFile records the state of sharing at connect() time.
107 | // useCachedJarFile == true is the common case. If developers call getJarFile().close() when
108 | // sharing is enabled then it will affect other users (current and future) of the shared
109 | // JarFile.
110 | //
111 | // Developers could call ClassLoader.findResource().openConnection() to get a URLConnection and
112 | // then call setUseCaches(false) before connect() to prevent sharing. The developer must then
113 | // call getJarFile().close() or close() on the inputStream from getInputStream() will do it
114 | // automatically. This is likely to be an extremely rare case.
115 | //
116 | // Most developers are not expecting to deal with the lifecycle of the underlying JarFile object
117 | // at all. The presence of the getJarFile() method and setUseCaches() forces us to consider /
118 | // handle it.
119 | private JarFile connectionJarFile;
120 |
121 | private ZipEntry jarEntry;
122 | private InputStream jarInput;
123 | private boolean closed;
124 |
125 | /**
126 | * Indicates the behavior of the {@link #jarFile}. If true, the reference is shared and should
127 | * not be closed. If false, it must be closed.
128 | */
129 | private boolean useCachedJarFile;
130 |
131 |
132 | public ClassPathURLConnection(URL url) throws MalformedURLException {
133 | super(url);
134 | }
135 |
136 | @Override
137 | public void connect() throws IOException {
138 | if (!connected) {
139 | this.jarEntry = findEntryWithDirectoryFallback(ClassPathURLStreamHandler.this.jarFile,
140 | getEntryName());
141 | if (jarEntry == null) {
142 | throw new FileNotFoundException(
143 | "URL does not correspond to an entry in the zip file. URL=" + url
144 | + ", zipfile=" + jarFile.getName());
145 | }
146 | useCachedJarFile = getUseCaches();
147 | connected = true;
148 | }
149 | }
150 |
151 | @Override
152 | public JarFile getJarFile() throws IOException {
153 | connect();
154 |
155 | // We do cache in the surrounding class if useCachedJarFile is true to
156 | // preserve garbage collection semantics and to avoid leak warnings.
157 | if (useCachedJarFile) {
158 | connectionJarFile = jarFile;
159 | } else {
160 | connectionJarFile = new JarFile(jarFile.getName());
161 | }
162 | return connectionJarFile;
163 | }
164 |
165 | @Override
166 | public InputStream getInputStream() throws IOException {
167 | if (closed) {
168 | throw new IllegalStateException("JarURLConnection InputStream has been closed");
169 | }
170 | connect();
171 | if (jarInput != null) {
172 | return jarInput;
173 | }
174 | return jarInput = new FilterInputStream(jarFile.getInputStream(jarEntry)) {
175 | @Override
176 | public void close() throws IOException {
177 | super.close();
178 | // If the jar file is not cached then closing the input stream will close the
179 | // URLConnection and any JarFile returned from getJarFile(). If the jar file is cached
180 | // we must not close it because it will affect other URLConnections.
181 | if (connectionJarFile != null && !useCachedJarFile) {
182 | connectionJarFile.close();
183 | closed = true;
184 | }
185 | }
186 | };
187 | }
188 |
189 | /**
190 | * Returns the content type of the entry based on the name of the entry. Returns
191 | * non-null results ("content/unknown" for unknown types).
192 | *
193 | * @return the content type
194 | */
195 | @Override
196 | public String getContentType() {
197 | String cType = guessContentTypeFromName(getEntryName());
198 | if (cType == null) {
199 | cType = "content/unknown";
200 | }
201 | return cType;
202 | }
203 |
204 | @Override
205 | public int getContentLength() {
206 | try {
207 | connect();
208 | return (int) getJarEntry().getSize();
209 | } catch (IOException e) {
210 | // Ignored
211 | }
212 | return -1;
213 | }
214 | }
215 | }
216 |
--------------------------------------------------------------------------------
/sheller.py:
--------------------------------------------------------------------------------
1 | # -*- coding: UTF-8 -*-
2 |
3 | import struct
4 | import zlib
5 | import hashlib
6 | import re
7 | from subprocess import check_call
8 | import argparse
9 | import os
10 | from xml.dom.minidom import parse
11 | import xml.dom.minidom
12 | import zipfile
13 | from PIL import Image
14 | import shutil
15 |
16 |
17 | stApkToolPt = r'D:\Tools\ReverseTools\android\apktool_2.4.1.jar'
18 | stShellAppPt = r'.\shellApplicationSourceCode'
19 | staaptPt = r'D:\Android\Sdk\build-tools\29.0.3\aapt.exe'
20 | stAndroidJarlibPt = r'D:\Android\Sdk\platforms\android-29\android.jar'
21 | stdxJarPt = r'D:\Android\Sdk\build-tools\29.0.3\lib\dx.jar'
22 | stApksignJarPt = r'D:\Android\Sdk\build-tools\29.0.3\lib\apksigner.jar'
23 | stCurrentPt = os.path.abspath(__file__).replace(os.path.basename(__file__), "")
24 |
25 |
26 | def add_srcDexToShellDex(srcDex, shellDex):
27 |
28 | liShellDt = []
29 | liSrcDexDt = []
30 | liAllDt = []
31 |
32 | #将原始DEX和壳DEX数据放在一个列表中
33 | with open(shellDex, "rb") as f:
34 | shellData = f.read()
35 | liShellDt = list(struct.unpack(len(shellData)*'B', shellData))
36 | with open(srcDex, 'rb') as f:
37 | srcDt = f.read()
38 | liSrcDexDt = list(struct.unpack(len(srcDt)*'B', srcDt))
39 | liAllDt.extend(shellData)
40 | # 加密原DEX
41 | for i in liSrcDexDt:
42 | liAllDt.append(i ^ 0xff)
43 |
44 | iSrcDexLen = len(liSrcDexDt)
45 | liSrcDexLen = intToSmalEndian(iSrcDexLen)
46 | liSrcDexLen.reverse()
47 | # 加密原DEX长度
48 | for i in liSrcDexLen:
49 | liAllDt.append(i ^ 0xff)
50 |
51 | # 计算合成后DEX文件的checksum、signature、file_size
52 | # 更改文件头
53 | newFsize = len(liAllDt)
54 | liNewFSize = intToSmalEndian(newFsize)
55 | for i in range(4):
56 | liAllDt[32 + i] = liNewFSize[i]
57 |
58 | newSignature = hashlib.sha1(bytes(liAllDt[32:])).hexdigest()
59 | liNewSignature = re.findall(r'.{2}', newSignature)
60 | for i in range(len(liNewSignature)):
61 | liNewSignature[i] = ord(bytes.fromhex(liNewSignature[i]))
62 | for i in range(20):
63 | liAllDt[12 + i] = liNewSignature[i]
64 |
65 | newChecksum = zlib.adler32(bytes(liAllDt[12:]))
66 | liNewChecksum = intToSmalEndian(newChecksum)
67 | for i in range(4):
68 | liAllDt[8 + i] = liNewChecksum[i]
69 |
70 | with open(os.path.join(stCurrentPt, 'classes.dex'), 'wb') as f:
71 | f.write(bytes(liAllDt))
72 |
73 |
74 | def intToSmalEndian(numb):
75 | liRes = []
76 |
77 | stHexNumb = hex(numb)[2:]
78 | for i in range(8 - len(stHexNumb)):
79 | stHexNumb = '0' + stHexNumb
80 | liRes = re.findall(r'.{2}', stHexNumb)
81 | for i in range(len(liRes)):
82 | liRes[i] = ord(bytes.fromhex(liRes[i]))
83 | liRes.reverse()
84 |
85 | return liRes
86 |
87 | def decompAPK(fp):
88 | cmd = []
89 | cmd.append('java')
90 | cmd.append('-jar')
91 | cmd.append(stApkToolPt)
92 | cmd.append('d')
93 | cmd.append('-o')
94 | cmd.append(fp + "decompile")
95 | cmd.append(fp)
96 | check_call(cmd)
97 |
98 | def getAppName(fp):
99 | stAppName = "android.app.Application"
100 | stDisassembleDp = fp + "decompile"
101 | stAMFp = os.path.join(stDisassembleDp, "AndroidManifest.xml")
102 | oDomTree = parse(stAMFp)
103 | manifest = oDomTree.documentElement
104 | apps = manifest.getElementsByTagName('application')
105 |
106 | if len(apps) != 1:
107 | print("存在多个Application")
108 | raise(Exception)
109 | if apps[0].getAttribute("android:name") != "":
110 | stAppName = apps[0].getAttribute("android:name")
111 |
112 | return stAppName
113 |
114 | def replaceTag(fp, stValue):
115 | stAXMLFp = os.path.join(stCurrentPt, fp + "decompile", "AndroidManifest.xml")
116 | dom = None
117 | with open(stAXMLFp, 'r', encoding='UTF-8') as f:
118 | dom = parse(f)
119 | root = dom.documentElement
120 | app = root.getElementsByTagName('application')[0]
121 | app.setAttribute("android:name", stValue)
122 | with open(stAXMLFp, "w", encoding='UTF-8') as f:
123 | dom.writexml(f, encoding='UTF-8')
124 | stDecompDp = os.path.join(stCurrentPt, fp + "decompile")
125 | # 修复PNG文件BUG
126 |
127 | PngBug(stDecompDp)
128 | cmd = []
129 | cmd.append('java')
130 | cmd.append('-jar')
131 | cmd.append(stApkToolPt)
132 | cmd.append('b')
133 | cmd.append('-o')
134 | cmd.append("result.apk")
135 | cmd.append(stDecompDp)
136 | check_call(cmd)
137 |
138 | shutil.rmtree(stDecompDp)
139 |
140 |
141 | def save_appName(appName):
142 | stCfgFp = os.path.join(stShellAppPt, "java/cn/yongye/nativeshell/common/Config.java")
143 | with open(stCfgFp, 'w') as f:
144 | f.write("package cn.yongye.nativeshell.common;\n")
145 | f.write("\n")
146 | f.write("public class Config {\n")
147 | f.write(" public static final String MAIN_APPLICATION = \"{}\";\n".format(appName))
148 | f.write("}")
149 |
150 | def compileSrcApk(dp):
151 | cmd = []
152 | cmd.append('java')
153 | cmd.append('-jar')
154 | cmd.append(stApkToolPt)
155 | cmd.append('b')
156 | cmd.append('-f')
157 | cmd.append('-o')
158 | cmd.append(stCurrentPt + "\src.apk")
159 | cmd.append(dp)
160 | check_call(cmd)
161 |
162 | def compileShellDex():
163 |
164 | licmd2 = []
165 | licmd2.append("javac")
166 | licmd2.append("-encoding")
167 | licmd2.append("UTF-8")
168 | licmd2.append("-target")
169 | licmd2.append("1.8")
170 | licmd2.append("-bootclasspath")
171 | licmd2.append(stAndroidJarlibPt)
172 | licmd2.append("-d")
173 | licmd2.append(stCurrentPt)
174 | licmd2.append(stCurrentPt + r"\shellApplicationSourceCode\java\cn\yongye\nativeshell\*.java")
175 | licmd2.append(stCurrentPt + r"\shellApplicationSourceCode\java\cn\yongye\nativeshell\common\*.java")
176 | licmd2.append(stCurrentPt + r"\shellApplicationSourceCode\java\cn\yongye\nativeshell\inmemoryloaddex\*.java")
177 | check_call(licmd2)
178 |
179 | licmd3 = []
180 | licmd3.append("java")
181 | licmd3.append("-jar")
182 | licmd3.append(stdxJarPt)
183 | licmd3.append("--dex")
184 | licmd3.append("--output=" + stCurrentPt + "\shell.dex")
185 | licmd3.append(r"cn\yongye\nativeshell\*.class")
186 | licmd3.append(r"cn\yongye\nativeshell\common\*.class")
187 | licmd3.append(r"cn\yongye\nativeshell\inmemoryloaddex\*.class")
188 | check_call(licmd3)
189 |
190 | shutil.rmtree("cn")
191 |
192 |
193 | def replaceSDexToShellDex(stSApkFp):
194 | # 提取原APK中的DEX到本地
195 | oFApk = zipfile.ZipFile(stSApkFp)
196 | oFApk.extract("classes.dex", stCurrentPt)
197 | oFApk.close()
198 | stSDexFp = os.path.join(stCurrentPt, "classes.dex")
199 | stShellDexFp = os.path.join(stCurrentPt, "shell.dex")
200 | # 将原DEX添加到壳DEX尾部,保存到本目录下为classes.dex
201 | add_srcDexToShellDex(stSDexFp, stShellDexFp)
202 | #将修改后的壳DEX添加到原DEX中
203 |
204 | cmd1 = []
205 | cmd1.append(staaptPt)
206 | cmd1.append("r")
207 | cmd1.append(stSApkFp)
208 | cmd1.append("classes.dex")
209 | check_call(cmd1)
210 |
211 | oNewApk = zipfile.ZipFile(stSApkFp, "a")
212 | oNewApk.write("classes.dex", "classes.dex")
213 | oNewApk.close()
214 |
215 | os.remove(stSDexFp)
216 | os.remove(stShellDexFp)
217 |
218 |
219 | def signApk(fp, stKeystoreFp):
220 | cmd = []
221 | cmd.append("java")
222 | cmd.append("-jar")
223 | cmd.append(stApksignJarPt)
224 | cmd.append("sign")
225 | cmd.append("--ks")
226 | cmd.append(stKeystoreFp)
227 | cmd.append("--ks-pass")
228 | cmd.append("pass:123456")
229 | cmd.append(fp)
230 | check_call(cmd)
231 |
232 |
233 | def PngBug(decompFp):
234 | import filetype
235 | liFiles = os.listdir(decompFp)
236 | for fn in liFiles:
237 | fp = os.path.join(decompFp, fn)
238 | if os.path.isdir(fp):
239 | PngBug(fp)
240 | if fp.endswith(".png") and filetype.guess(fp).MIME == "image/gif":
241 | im = Image.open(fp)
242 | current = im.tell()
243 | im.save(fp)
244 |
245 | def addLib(fp):
246 | stLib2Fp1 = os.path.join(stCurrentPt, "lib", "libdalvik_system_DexFile.so")
247 | stLib2Fp2 = os.path.join(stCurrentPt, "lib", "libyongyejiagu.so")
248 |
249 | apk = zipfile.ZipFile(fp, "a")
250 | apk.write(stLib2Fp1, "lib/x86/libdalvik_system_DexFile.so")
251 | apk.write(stLib2Fp2, "lib/x86/libyongyejiagu.so")
252 | apk.close()
253 |
254 | if __name__ == "__main__":
255 |
256 | parser = argparse.ArgumentParser()
257 | parser.add_argument('-f', help="对指定文件进行加固", nargs=1, metavar='APK')
258 | args = parser.parse_args()
259 |
260 | if args.f:
261 | fp = args.__dict__.get('f')[0]
262 | """
263 | 1. 第一步:确定加密算法
264 | """
265 | inKey = 0xFF
266 | print("[*] 确定加密解密算法,异或: {}".format(str(inKey)))
267 |
268 | """
269 | 2. 第二步:准备好壳App
270 | """
271 | # 反编译原apk
272 | decompAPK(fp)
273 | # print("[*] 反编译原的apk文件{}完成".format(fp))
274 | # 获取Applicaiton name并保存到壳App源码中
275 | stSrcDexAppName = getAppName(fp)
276 | # print("[*] 获取原apk文件的Application Android:name=\"{}\" 完成".format(stSrcDexAppName))
277 | save_appName(stSrcDexAppName)
278 | # print("[*] 保存原apk文件的Application Android:name=\"{}\" 到壳App源码的配置文件完成".format(stSrcDexAppName))
279 | # 编译出壳DEX
280 | compileShellDex()
281 | print("[*] 壳App的class字节码文件编译为:shell.dex完成")
282 |
283 | """
284 | 3. 第三步:修改原apk AndroidManifest.xml文件中的Application name字段为壳的Application name字段
285 | """
286 | # 替换壳Applicaiton name到原apk的AndroidManifest.xml内
287 | replaceTag(fp, "cn.yongye.nativeshell.StubApp")
288 | print("[*] 原apk文件AndroidManifest.xml中application的name字段替换为壳application name字段完成")
289 |
290 | """
291 | 4. 替换原apk中的DEX文件为壳DEX
292 | """
293 | replaceSDexToShellDex(os.path.join(stCurrentPt, "result.apk"))
294 | print("[*] 壳DEX替换原apk包内的DEX文件完成")
295 |
296 | """
297 | 5. 添加脱壳lib库到原apk中
298 | """
299 | addLib("result.apk")
300 |
301 | """
302 | 6. apk签名
303 | """
304 | signApk(os.path.join(stCurrentPt, "result.apk"), os.path.join(stCurrentPt, "demo.keystore"))
305 | print("[*] 签名完成,生成apk文件: {}".format(os.path.join(stCurrentPt, "result.apk")))
306 |
307 |
308 | else:
309 | parser.print_help()
310 |
311 |
312 |
--------------------------------------------------------------------------------
/shellApplicationSourceCode/java/cn/yongye/nativeshell/inmemoryloaddex/MyDexClassLoader.java:
--------------------------------------------------------------------------------
1 | package cn.yongye.nativeshell.inmemoryloaddex;
2 |
3 | import android.os.Build;
4 | import android.text.TextUtils;
5 |
6 | import java.io.File;
7 | import java.net.URL;
8 | import java.nio.ByteBuffer;
9 | import java.util.ArrayList;
10 | import java.util.Collection;
11 | import java.util.Enumeration;
12 | import java.util.List;
13 |
14 | public class MyDexClassLoader extends ClassLoader {
15 |
16 | /**
17 | * Hook for customizing how dex files loads are reported.
18 | *
19 | * This enables the framework to monitor the use of dex files. The
20 | * goal is to simplify the mechanism for optimizing foreign dex files and
21 | * enable further optimizations of secondary dex files.
22 | *
23 | * The reporting happens only when new instances of BaseDexClassLoader
24 | * are constructed and will be active only after this field is set with
25 | * {@link MyDexClassLoader#setReporter}.
26 | */
27 | /* @NonNull */ private static volatile Reporter reporter = null;
28 |
29 | private final DexPathList pathList;
30 |
31 | /**
32 | * Constructs an instance.
33 | * Note that all the *.jar and *.apk files from {@code dexPath} might be
34 | * first extracted in-memory before the code is loaded. This can be avoided
35 | * by passing raw dex files (*.dex) in the {@code dexPath}.
36 | *
37 | * @param dexPath the list of jar/apk files containing classes and
38 | * resources, delimited by {@code File.pathSeparator}, which
39 | * defaults to {@code ":"} on Android.
40 | * @param optimizedDirectory this parameter is deprecated and has no effect since API level 26.
41 | * @param librarySearchPath the list of directories containing native
42 | * libraries, delimited by {@code File.pathSeparator}; may be
43 | * {@code null}
44 | * @param parent the parent class loader
45 | */
46 | public MyDexClassLoader(String dexPath, File optimizedDirectory,
47 | String librarySearchPath, ClassLoader parent) {
48 | this(dexPath, optimizedDirectory, librarySearchPath, parent, false);
49 | }
50 |
51 | /**
52 | * @hide
53 | */
54 | public MyDexClassLoader(String dexPath, File optimizedDirectory,
55 | String librarySearchPath, ClassLoader parent, boolean isTrusted) {
56 | super(parent);
57 | this.pathList = new DexPathList(this, dexPath, librarySearchPath, null, isTrusted);
58 |
59 | if (reporter != null) {
60 | reportClassLoaderChain();
61 | }
62 | }
63 |
64 | /**
65 | * Reports the current class loader chain to the registered {@code reporter}.
66 | * The chain is reported only if all its elements are {@code BaseDexClassLoader}.
67 | */
68 | private void reportClassLoaderChain() {
69 | ArrayList classLoadersChain = new ArrayList<>();
70 | ArrayList classPaths = new ArrayList<>();
71 |
72 | classLoadersChain.add(this);
73 | classPaths.add(TextUtils.join(File.pathSeparator, pathList.getDexPaths()));
74 |
75 | boolean onlySawSupportedClassLoaders = true;
76 | ClassLoader bootClassLoader = ClassLoader.getSystemClassLoader().getParent();
77 | ClassLoader current = getParent();
78 |
79 | while (current != null && current != bootClassLoader) {
80 | if (current instanceof MyDexClassLoader) {
81 | MyDexClassLoader bdcCurrent = (MyDexClassLoader) current;
82 | classLoadersChain.add(bdcCurrent);
83 | classPaths.add(TextUtils.join(File.pathSeparator, bdcCurrent.pathList.getDexPaths()));
84 | } else {
85 | onlySawSupportedClassLoaders = false;
86 | break;
87 | }
88 | current = current.getParent();
89 | }
90 |
91 | if (onlySawSupportedClassLoaders) {
92 | reporter.report(classLoadersChain, classPaths);
93 | }
94 | }
95 |
96 | /**
97 | * Constructs an instance.
98 | *
99 | * dexFile must be an in-memory representation of a full dexFile.
100 | *
101 | * @param dexFiles the array of in-memory dex files containing classes.
102 | * @param parent the parent class loader
103 | *
104 | * @hide
105 | */
106 | public MyDexClassLoader(ByteBuffer[] dexFiles, ClassLoader parent) {
107 | // TODO We should support giving this a library search path maybe.
108 | super(parent);
109 | this.pathList = new DexPathList(this, dexFiles);
110 | }
111 |
112 | @Override
113 | protected Class> findClass(String name) throws ClassNotFoundException {
114 | List suppressedExceptions = new ArrayList();
115 | Class c = pathList.findClass(name, suppressedExceptions);
116 | if (c == null) {
117 | ClassNotFoundException cnfe = new ClassNotFoundException(
118 | "Didn't find class \"" + name + "\" on path: " + pathList);
119 | for (Throwable t : suppressedExceptions) {
120 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
121 | cnfe.addSuppressed(t);
122 | }
123 | }
124 | throw cnfe;
125 | }
126 | return c;
127 | }
128 |
129 | /**
130 | * @hide
131 | */
132 | public void addDexPath(String dexPath) {
133 | addDexPath(dexPath, false /*isTrusted*/);
134 | }
135 |
136 | /**
137 | * @hide
138 | */
139 | public void addDexPath(String dexPath, boolean isTrusted) {
140 | pathList.addDexPath(dexPath, null /*optimizedDirectory*/, isTrusted);
141 | }
142 |
143 | /**
144 | * Adds additional native paths for consideration in subsequent calls to
145 | * {@link #findLibrary(String)}
146 | * @hide
147 | */
148 | public void addNativePath(Collection libPaths) {
149 | pathList.addNativePath(libPaths);
150 | }
151 |
152 | @Override
153 | protected URL findResource(String name) {
154 | return pathList.findResource(name);
155 | }
156 |
157 | @Override
158 | protected Enumeration findResources(String name) {
159 | return pathList.findResources(name);
160 | }
161 |
162 | @Override
163 | public String findLibrary(String name) {
164 | return pathList.findLibrary(name);
165 | }
166 |
167 | /**
168 | * Returns package information for the given package.
169 | * Unfortunately, instances of this class don't really have this
170 | * information, and as a non-secure {@code ClassLoader}, it isn't
171 | * even required to, according to the spec. Yet, we want to
172 | * provide it, in order to make all those hopeful callers of
173 | * {@code myClass.getPackage().getName()} happy. Thus we construct
174 | * a {@code Package} object the first time it is being requested
175 | * and fill most of the fields with dummy values. The {@code
176 | * Package} object is then put into the {@code ClassLoader}'s
177 | * package cache, so we see the same one next time. We don't
178 | * create {@code Package} objects for {@code null} arguments or
179 | * for the default package.
180 | *
181 | * There is a limited chance that we end up with multiple
182 | * {@code Package} objects representing the same package: It can
183 | * happen when when a package is scattered across different JAR
184 | * files which were loaded by different {@code ClassLoader}
185 | * instances. This is rather unlikely, and given that this whole
186 | * thing is more or less a workaround, probably not worth the
187 | * effort to address.
188 | *
189 | * @param name the name of the class
190 | * @return the package information for the class, or {@code null}
191 | * if there is no package information available for it
192 | */
193 | @Override
194 | protected synchronized Package getPackage(String name) {
195 | if (name != null && !name.isEmpty()) {
196 | Package pack = super.getPackage(name);
197 |
198 | if (pack == null) {
199 | pack = definePackage(name, "Unknown", "0.0", "Unknown",
200 | "Unknown", "0.0", "Unknown", null);
201 | }
202 |
203 | return pack;
204 | }
205 |
206 | return null;
207 | }
208 |
209 | /**
210 | * @hide
211 | */
212 | public String getLdLibraryPath() {
213 | StringBuilder result = new StringBuilder();
214 | for (File directory : pathList.getNativeLibraryDirectories()) {
215 | if (result.length() > 0) {
216 | result.append(':');
217 | }
218 | result.append(directory);
219 | }
220 |
221 | return result.toString();
222 | }
223 |
224 | @Override public String toString() {
225 | return getClass().getName() + "[" + pathList + "]";
226 | }
227 |
228 | /**
229 | * Sets the reporter for dex load notifications.
230 | * Once set, all new instances of BaseDexClassLoader will report upon
231 | * constructions the loaded dex files.
232 | *
233 | * @param newReporter the new Reporter. Setting null will cancel reporting.
234 | * @hide
235 | */
236 | public static void setReporter(Reporter newReporter) {
237 | reporter = newReporter;
238 | }
239 |
240 | /**
241 | * @hide
242 | */
243 | public static Reporter getReporter() {
244 | return reporter;
245 | }
246 |
247 | /**
248 | * @hide
249 | */
250 | public interface Reporter {
251 | /**
252 | * Reports the construction of a BaseDexClassLoader and provides information about the
253 | * class loader chain.
254 | * Note that this only reports if all class loader in the chain are BaseDexClassLoader.
255 | *
256 | * @param classLoadersChain the chain of class loaders used during the construction of the
257 | * class loader. The first element is the BaseDexClassLoader being constructed,
258 | * the second element is its parent, and so on.
259 | * @param classPaths the class paths of the class loaders present in
260 | * {@param classLoadersChain}. The first element corresponds to the first class
261 | * loader and so on. A classpath is represented as a list of dex files separated by
262 | * {@code File.pathSeparator}.
263 | */
264 | void report(List classLoadersChain, List classPaths);
265 | }
266 |
267 | }
268 |
--------------------------------------------------------------------------------
/shellApplicationSourceCode/java/cn/yongye/nativeshell/inmemoryloaddex/URLJarFile.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2001, 2011, Oracle and/or its affiliates. All rights reserved.
3 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 | *
5 | * This code is free software; you can redistribute it and/or modify it
6 | * under the terms of the GNU General Public License version 2 only, as
7 | * published by the Free Software Foundation. Oracle designates this
8 | * particular file as subject to the "Classpath" exception as provided
9 | * by Oracle in the LICENSE file that accompanied this code.
10 | *
11 | * This code is distributed in the hope that it will be useful, but WITHOUT
12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 | * version 2 for more details (a copy is included in the LICENSE file that
15 | * accompanied this code).
16 | *
17 | * You should have received a copy of the GNU General Public License version
18 | * 2 along with this work; if not, write to the Free Software Foundation,
19 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 | *
21 | * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 | * or visit www.oracle.com if you need additional information or have any
23 | * questions.
24 | */
25 |
26 | package cn.yongye.nativeshell.inmemoryloaddex;
27 |
28 | import java.io.File;
29 | import java.io.FileOutputStream;
30 | import java.io.IOException;
31 | import java.io.InputStream;
32 | import java.net.URL;
33 | import java.security.AccessController;
34 | import java.security.CodeSigner;
35 | import java.security.PrivilegedActionException;
36 | import java.security.PrivilegedExceptionAction;
37 | import java.security.cert.Certificate;
38 | import java.util.Map;
39 | import java.util.jar.Attributes;
40 | import java.util.jar.JarEntry;
41 | import java.util.jar.JarFile;
42 | import java.util.jar.Manifest;
43 | import java.util.zip.ZipEntry;
44 | import java.util.zip.ZipFile;
45 |
46 | /* URL jar file is a common JarFile subtype used for JarURLConnection */
47 | public class URLJarFile extends JarFile {
48 |
49 | // Android-changed: Removed setCallBack(URLJarFileCallBack) and field callback (dead code).
50 | // /*
51 | // * Interface to be able to call retrieve() in plugin if
52 | // * this variable is set.
53 | // */
54 | // private static URLJarFileCallBack callback = null;
55 |
56 | /* Controller of the Jar File's closing */
57 | private URLJarFileCloseController closeController = null;
58 |
59 | private static int BUF_SIZE = 2048;
60 |
61 | private Manifest superMan;
62 | private Attributes superAttr;
63 | private Map superEntries;
64 |
65 | static JarFile getJarFile(URL url) throws IOException {
66 | return getJarFile(url, null);
67 | }
68 |
69 | static JarFile getJarFile(URL url, URLJarFileCloseController closeController) throws IOException {
70 | if (isFileURL(url))
71 | return new URLJarFile(url, closeController);
72 | else {
73 | return retrieve(url, closeController);
74 | }
75 | }
76 |
77 | /*
78 | * Changed modifier from private to public in order to be able
79 | * to instantiate URLJarFile from sun.plugin package.
80 | */
81 | public URLJarFile(File file) throws IOException {
82 | this(file, null);
83 | }
84 |
85 | /*
86 | * Changed modifier from private to public in order to be able
87 | * to instantiate URLJarFile from sun.plugin package.
88 | */
89 | public URLJarFile(File file, URLJarFileCloseController closeController) throws IOException {
90 | super(file, true, ZipFile.OPEN_READ | ZipFile.OPEN_DELETE);
91 | this.closeController = closeController;
92 | }
93 |
94 | private URLJarFile(URL url, URLJarFileCloseController closeController) throws IOException {
95 | super(ParseUtil.decode(url.getFile()));
96 | this.closeController = closeController;
97 | }
98 |
99 | private static boolean isFileURL(URL url) {
100 | if (url.getProtocol().equalsIgnoreCase("file")) {
101 | /*
102 | * Consider this a 'file' only if it's a LOCAL file, because
103 | * 'file:' URLs can be accessible through ftp.
104 | */
105 | String host = url.getHost();
106 | if (host == null || host.equals("") || host.equals("~") ||
107 | host.equalsIgnoreCase("localhost"))
108 | return true;
109 | }
110 | return false;
111 | }
112 |
113 | /*
114 | * close the jar file.
115 | */
116 | // Android-note: All important methods here and in superclasses should synchronize on this
117 | // to avoid premature finalization. The actual close implementation in ZipFile does.
118 | protected void finalize() throws IOException {
119 | close();
120 | }
121 |
122 | /**
123 | * Returns the ZipEntry for the given entry name or
124 | * null if not found.
125 | *
126 | * @param name the JAR file entry name
127 | * @return the ZipEntry for the given entry name or
128 | * null if not found
129 | * @see ZipEntry
130 | */
131 | public ZipEntry getEntry(String name) {
132 | ZipEntry ze = super.getEntry(name);
133 | if (ze != null) {
134 | if (ze instanceof JarEntry)
135 | return new URLJarFileEntry((JarEntry)ze);
136 | else
137 | throw new InternalError(super.getClass() +
138 | " returned unexpected entry type " +
139 | ze.getClass());
140 | }
141 | return null;
142 | }
143 |
144 | public Manifest getManifest() throws IOException {
145 |
146 | if (!isSuperMan()) {
147 | return null;
148 | }
149 |
150 | Manifest man = new Manifest();
151 | Attributes attr = man.getMainAttributes();
152 | attr.putAll((Map)superAttr.clone());
153 |
154 | // now deep copy the manifest entries
155 | if (superEntries != null) {
156 | Map entries = man.getEntries();
157 | for (String key : superEntries.keySet()) {
158 | Attributes at = superEntries.get(key);
159 | entries.put(key, (Attributes) at.clone());
160 | }
161 | }
162 |
163 | return man;
164 | }
165 |
166 | /* If close controller is set the notify the controller about the pending close */
167 | public void close() throws IOException {
168 | if (closeController != null) {
169 | closeController.close(this);
170 | }
171 | super.close();
172 | }
173 |
174 | // optimal side-effects
175 | private synchronized boolean isSuperMan() throws IOException {
176 |
177 | if (superMan == null) {
178 | superMan = super.getManifest();
179 | }
180 |
181 | if (superMan != null) {
182 | superAttr = superMan.getMainAttributes();
183 | superEntries = superMan.getEntries();
184 | return true;
185 | } else
186 | return false;
187 | }
188 |
189 | /**
190 | * Given a URL, retrieves a JAR file, caches it to disk, and creates a
191 | * cached JAR file object.
192 | */
193 | private static JarFile retrieve(final URL url) throws IOException {
194 | return retrieve(url, null);
195 | }
196 |
197 | /**
198 | * Given a URL, retrieves a JAR file, caches it to disk, and creates a
199 | * cached JAR file object.
200 | */
201 | private static JarFile retrieve(final URL url, final URLJarFileCloseController closeController) throws IOException {
202 | // Android-changed: Removed setCallBack(URLJarFileCallBack) and field callback (dead code).
203 | // /*
204 | // * See if interface is set, then call retrieve function of the class
205 | // * that implements URLJarFileCallBack interface (sun.plugin - to
206 | // * handle the cache failure for JARJAR file.)
207 | // */
208 | // if (callback != null)
209 | // {
210 | // return callback.retrieve(url);
211 | // }
212 | //
213 | // else
214 | {
215 |
216 | JarFile result = null;
217 |
218 | /* get the stream before asserting privileges */
219 | // try (final InputStream in = url.openConnection().getInputStream()) {
220 | try {
221 | final InputStream in = url.openConnection().getInputStream();
222 | result = AccessController.doPrivileged(
223 | new PrivilegedExceptionAction() {
224 | public JarFile run() throws IOException {
225 | // Path tmpFile = Files.createTempFile("jar_cache", null);
226 | File tmpFile = new File("jar_cache");
227 | if(!tmpFile.exists())
228 | tmpFile.createNewFile();
229 | try {
230 | // Files.copy(in, tmpFile, StandardCopyOption.REPLACE_EXISTING);
231 | // JarFile jarFile = new URLJarFile(tmpFile.toFile(), closeController);
232 | // tmpFile.toFile().deleteOnExit();
233 | FileOutputStream fout = new FileOutputStream(tmpFile);
234 | byte[] btTmp = new byte[1024];
235 | int inReadNumb = 0;
236 | while((inReadNumb = in.read(btTmp, 0, btTmp.length)) != -1){
237 | fout.write(btTmp, 0, inReadNumb);
238 | }
239 | fout.close();
240 | JarFile jarFile = new URLJarFile(tmpFile, closeController);
241 | return jarFile;
242 | } catch (Throwable thr) {
243 | tmpFile.delete();
244 | // try {
245 | // Files.delete(tmpFile);
246 | // } catch (IOException ioe) {
247 | // thr.addSuppressed(ioe);
248 | // }
249 | throw thr;
250 | }
251 | }
252 | });
253 | } catch (PrivilegedActionException pae) {
254 | throw (IOException) pae.getException();
255 | }
256 |
257 | return result;
258 | }
259 | }
260 |
261 | // Android-changed: Removed setCallBack(URLJarFileCallBack) and field callback (dead code).
262 | // /*
263 | // * Set the call back interface to call retrive function in sun.plugin
264 | // * package if plugin is running.
265 | // */
266 | // public static void setCallBack(URLJarFileCallBack cb)
267 | // {
268 | // callback = cb;
269 | // }
270 |
271 | private class URLJarFileEntry extends JarEntry {
272 | private JarEntry je;
273 |
274 | URLJarFileEntry(JarEntry je) {
275 | super(je);
276 | this.je=je;
277 | }
278 |
279 | public Attributes getAttributes() throws IOException {
280 | if (URLJarFile.this.isSuperMan()) {
281 | Map e = URLJarFile.this.superEntries;
282 | if (e != null) {
283 | Attributes a = e.get(getName());
284 | if (a != null)
285 | return (Attributes)a.clone();
286 | }
287 | }
288 | return null;
289 | }
290 |
291 | public Certificate[] getCertificates() {
292 | Certificate[] certs = je.getCertificates();
293 | return certs == null? null: certs.clone();
294 | }
295 |
296 | public CodeSigner[] getCodeSigners() {
297 | CodeSigner[] csg = je.getCodeSigners();
298 | return csg == null? null: csg.clone();
299 | }
300 | }
301 |
302 | public interface URLJarFileCloseController {
303 | public void close(JarFile jarFile);
304 | }
305 | }
306 |
--------------------------------------------------------------------------------
/cpp/yongyejiagu.cpp:
--------------------------------------------------------------------------------
1 | //
2 | // Created by yongye
3 | //
4 |
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 | #include
11 | #include