├── .vscode ├── launch.json └── settings.json ├── README.md ├── cpp ├── CMakeLists.txt ├── build.bat ├── dalvik_system_DexFile.cpp ├── dalvik_system_DexFile.h └── yongyejiagu.cpp ├── demo.keystore ├── images ├── image-20200406155245997.png ├── image-20200415234950548.png ├── wx.jpg ├── wx.png ├── zfb.jpg ├── zfb.png └── 内存加载DEX.png ├── lib ├── libdalvik_system_DexFile.so └── libyongyejiagu.so ├── shellApplicationSourceCode ├── AndroidManifest.xml ├── java │ └── cn │ │ └── yongye │ │ └── nativeshell │ │ ├── MainActivity.java │ │ ├── R.java │ │ ├── StubApp.java │ │ ├── common │ │ ├── Config.java │ │ ├── FileUtils.java │ │ └── RefInvoke.java │ │ └── inmemoryloaddex │ │ ├── ClassPathURLStreamHandler.java │ │ ├── DexFile.java │ │ ├── DexPathList.java │ │ ├── Handler.java │ │ ├── JarFileFactory.java │ │ ├── JarURLConnection.java │ │ ├── MyDexClassLoader.java │ │ ├── ParseUtil.java │ │ ├── ThreadLocalCoders.java │ │ ├── URLJarFile.java │ │ └── URLUtil.java └── res │ ├── drawable-v24 │ └── ic_launcher_foreground.xml │ ├── drawable │ └── ic_launcher_background.xml │ ├── layout │ └── activity_main.xml │ ├── mipmap-anydpi-v26 │ ├── ic_launcher.xml │ └── ic_launcher_round.xml │ ├── mipmap-hdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-mdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ └── values │ ├── colors.xml │ ├── strings.xml │ └── styles.xml ├── sheller.py └── test.apk /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Python: 当前文件", 9 | "type": "python", 10 | "request": "launch", 11 | "program": "${file}", 12 | "console": "integratedTerminal", 13 | "args": ["-f", "test.apk"] 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "python.pythonPath": "C:\\Python38\\python.exe" 3 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 概况 2 | 3 | 本文目的:通过**内存加载DEX文件技术**,完成**一键DEX加固脚本** 4 | 5 | # 使用说明 6 | 7 | ```powershell 8 | python sheller.py -f xxx.apk 9 | ``` 10 | 11 | # 加固原理 12 | 13 | 和我的另一个项目[Native层DEX一键加固脚本](https://github.com/yongyecc/dexsheller)基本一样,只是多了一步,引入了内存加载DEX技术 14 | 15 | # 一键加固脚本实现步骤 16 | 17 | 1. 准备原DEX加密算法以及隐藏位置(壳DEX尾部) 18 | 19 | ```python 20 | """ 21 | 1. 第一步:确定加密算法 22 | """ 23 | inKey = 0xFF 24 | print("[*] 确定加密解密算法,异或: {}".format(str(inKey))) 25 | ``` 26 | 27 | 2. 生成壳DEX。(壳Application动态加载原application中需要原application的name字段) 28 | 29 | ```python 30 | """ 31 | 2. 第二步:准备好壳App 32 | """ 33 | # 反编译原apk 34 | decompAPK(fp) 35 | # 获取Applicaiton name并保存到壳App源码中 36 | stSrcDexAppName = getAppName(fp) 37 | save_appName(stSrcDexAppName) 38 | # 编译出壳DEX 39 | compileShellDex() 40 | print("[*] 壳App的class字节码文件编译为:shell.dex完成") 41 | ``` 42 | 43 | 3. 修改原APK文件中的AndroidManifest.xml文件的applicationandroid:name字段,实现从壳application启动 44 | 45 | ```python 46 | """ 47 | 3. 第三步:修改原apk AndroidManifest.xml文件中的Application name字段为壳的Application name字段 48 | """ 49 | # 替换壳Applicaiton name到原apk的AndroidManifest.xml内 50 | replaceTag(fp, "cn.yongye.nativeshell.StubApp") 51 | print("[*] 原apk文件AndroidManifest.xml中application的name字段替换为壳application name字段完成") 52 | ``` 53 | 54 | 4. 加密原DEX到壳DEX尾部并将壳DEX替换到原APK中 55 | 56 | ```python 57 | """ 58 | 4. 替换原apk中的DEX文件为壳DEX 59 | """ 60 | replaceSDexToShellDex(os.path.join(stCurrentPt, "result.apk")) 61 | print("[*] 壳DEX替换原apk包内的DEX文件完成") 62 | ``` 63 | 64 | 5. 添加脱壳lib库到原apk中 65 | 66 | ```python 67 | """ 68 | 5. 添加脱壳lib库到原apk中 69 | """ 70 | addLib("result.apk") 71 | ``` 72 | 73 | 6. apk签名 74 | 75 | ```python 76 | """ 77 | 6. apk签名 78 | """ 79 | signApk(os.path.join(stCurrentPt, "result.apk"), os.path.join(stCurrentPt, "demo.keystore")) 80 | print("[*] 签名完成,生成apk文件: {}".format(os.path.join(stCurrentPt, "result.apk"))) 81 | ``` 82 | 83 | # 内存加载DEX 84 | 85 | 了解内存加载DEX文件内存加载技术之前,我们需要了解DEX文件加载这一过程。 86 | 87 | **DEX文件加载过程:**即下面左图所示的API调用链就是加载过程。DEX文件加载的核心是最后一层,通过Native层函数传入DEX文件从而获取一个cookie值 88 | 89 | 所以我们要**实现内存加载DEX**的话,不仅需要自己重写这套DEX文件调用链,最主要的是寻找一个Native层API实现传入DEX文件字节流来获取cookie值,就是下面右图所示调用链,API即openMemory(4.3 ART虚拟机以后, 4.3及其以前Dalvik虚拟机用openDexFile([byte...)API)即可实现这样的功能 90 | 91 | ![内存加载DEX](/images/内存加载DEX.png) 92 | 93 | Android 8以后BaseDexClassLoader类就有内存加载的能力,参考Android 8源码,已经加入了这样的接口,我们只需要将相关代码文件copy下来,将最底层的Native函数用openMemory替代即可 94 | 95 | ![image-20200415234950548](/images/image-20200415234950548.png) 96 | 97 | ## 自定义MyDexClassLoader 98 | 99 | 通过dlopen和dlsym的方法找到openMemory函数实现核心函数即可 100 | 101 | ```c++ 102 | JNICALL extern "C" 103 | JNIEXPORT jlong JNICALL 104 | Java_cn_yongye_inmemoryloaddex_DexFile_createCookieWithArray(JNIEnv *env, jclass clazz, 105 | jbyteArray buf, jint start, jint end) { 106 | // TODO: implement createCookieWithArray() 107 | void *artlib = fake_dlopen("/system/lib/libart.so", RTLD_LAZY); 108 | if(!artlib){ 109 | exit(0); 110 | } 111 | org_artDexFileOpenMemory22 openMemory22 = (org_artDexFileOpenMemory22)fake_dlsym(artlib, 112 | "_ZN3art7DexFile10OpenMemoryEPKhjRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPNS_6MemMapEPKNS_7OatFileEPS9_"); 113 | 114 | if(!openMemory22){ 115 | exit(0); 116 | } 117 | jbyte *bytes; 118 | char *pDex = NULL; 119 | std::string location = ""; 120 | std::string err_msg; 121 | 122 | 123 | bytes = env->GetByteArrayElements(buf, 0); 124 | int inDexLen = env->GetArrayLength(buf); 125 | pDex = new char[inDexLen + 1]; 126 | memset(pDex, 0, inDexLen + 1); 127 | memcpy(pDex, bytes, inDexLen); 128 | pDex[inDexLen] = 0; 129 | env->ReleaseByteArrayElements(buf, bytes, 0); 130 | const Header *dex_header = reinterpret_cast(pDex); 131 | void *value = openMemory22((const unsigned char *)pDex, inDexLen, location, dex_header->checksum_, NULL, NULL, &err_msg); 132 | 133 | jlong cookie = replace_cookie(env, value, 22); 134 | return cookie; 135 | 136 | } 137 | ``` 138 | 139 | 还需要实现DEX加载器另外一个重要功能,即加载类的能力,这个功能本来也是需要Native函数实现的,这里我们可以通过反射调用DexFile类的defineClass方法(加载类的调用链是:findClass->defineClass)实现 140 | 141 | ```java 142 | private Class defineClass(String name, ClassLoader loader, Object cookie, 143 | DexFile dexFile, List suppressed) { 144 | Class result = null; 145 | try { 146 | Class clsDexFile = Class.forName("dalvik.system.DexFile"); 147 | Method mdDefineClass = clsDexFile.getDeclaredMethod("defineClass", String.class, ClassLoader.class, long.class, List.class); 148 | mdDefineClass.setAccessible(true); 149 | result = (Class) mdDefineClass.invoke(null, name, loader, cookie, suppressed); 150 | } catch (NoClassDefFoundError e) { 151 | if (suppressed != null) { 152 | suppressed.add(e); 153 | } 154 | } catch (ClassNotFoundException e) { 155 | if (suppressed != null) { 156 | suppressed.add(e); 157 | } 158 | } catch (NoSuchMethodException e) { 159 | e.printStackTrace(); 160 | } catch (IllegalAccessException e) { 161 | e.printStackTrace(); 162 | } catch (InvocationTargetException e) { 163 | e.printStackTrace(); 164 | } 165 | return result; 166 | } 167 | ``` 168 | 169 | # Cmake、ninja编译cpp文件 170 | 171 | native层代码源文件在cpp目录下,可以直接调用cpp/build.bat一键编译批处理文件 172 | 173 | ## cmake生成ninja编译配置文件 174 | 175 | ```powershell 176 | D:\Android\Sdk\cmake\3.10.2.4988404\bin\cmake.exe . -DCMAKE_BUILD_TYPE=Debug -DCMAKE_TOOLCHAIN_FILE=D:\Android\Sdk\ndk\20.0.5594570\build\c make\android.toolchain.cmake -DANDROID_ABI=x86 -DANDROID_NDK=D:\Android\Sdk\ndk\20.0.5594570 -DANDROID_PLATFORM=android-16 -DCMAKE_ANDROID_ARCH_ABI=x86 -DCMAKE_ANDROID_NDK=D:\Android\Sdk\ndk\20.0.5594570 -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_MAKE_PROGRAM=D:\Android\Sdk\cmake\3.10.2.4988404\bin\ninja.exe -DCMAKE_SYSTEM_NAME=Android -DCMAKE_SYSTEM_VERSION=16 -GNinja 177 | ``` 178 | 179 | ## ninja生成so文件 180 | 181 | 只是修改cpp文件,直接运行ninja命令,不用重新用cmake生成ninja配置文件 182 | 如果有新cpp文件的增加删除,还是需要删除配置文件,重新运行cmake的 183 | 184 | ```powershell 185 | D:\Android\Sdk\cmake\3.10.2.4988404\bin\ninja.exe 186 | ``` 187 | 188 | # 参考 189 | 190 | 【1】[Android中apk加固完善篇之内存加载dex方案实现原理(不落地方式加载)](http://www.520monkey.com/archives/629) 191 | 192 | 【2】[内存解密并加载DEX](https://github.com/woxihuannisja/Bangcle) 193 | 194 | # 鼓励 195 | 196 | ![支付宝](/images/zfb.png) 197 | 198 | ![微信](/images/wx.png) 199 | -------------------------------------------------------------------------------- /cpp/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # For more information about using CMake with Android Studio, read the 2 | # documentation: https://d.android.com/studio/projects/add-native-code.html 3 | 4 | # Sets the minimum version of CMake required to build the native library. 5 | 6 | cmake_minimum_required(VERSION 3.4.1) 7 | 8 | # Creates and names a library, sets it as either STATIC 9 | # or SHARED, and provides the relative paths to its source code. 10 | # You can define multiple libraries, and CMake builds them for you. 11 | # Gradle automatically packages shared libraries with your APK. 12 | 13 | add_library( # Sets the name of the library. 14 | dalvik_system_DexFile 15 | 16 | # Sets the library as a shared library. 17 | SHARED 18 | 19 | # Provides a relative path to your source file(s). 20 | dalvik_system_DexFile.cpp) 21 | 22 | add_library(yongyejiagu SHARED yongyejiagu.cpp) 23 | 24 | 25 | # Searches for a specified prebuilt library and stores the path as a 26 | # variable. Because CMake includes system libraries in the search path by 27 | # default, you only need to specify the name of the public NDK library 28 | # you want to add. CMake verifies that the library exists before 29 | # completing its build. 30 | 31 | find_library( # Sets the name of the path variable. 32 | log-lib 33 | 34 | # Specifies the name of the NDK library that 35 | # you want CMake to locate. 36 | log ) 37 | 38 | # Specifies libraries CMake should link to your target library. You 39 | # can link multiple libraries, such as libraries you define in this 40 | # build script, prebuilt third-party libraries, or system libraries. 41 | 42 | target_link_libraries( # Specifies the target library. 43 | dalvik_system_DexFile 44 | 45 | # Links the target library to the log library 46 | # included in the NDK. 47 | ${log-lib} ) 48 | 49 | 50 | target_link_libraries(yongyejiagu ${log-lib}) -------------------------------------------------------------------------------- /cpp/build.bat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongyecc/dexshellerInMemory/ffe7defad3cc7eb6d181592e50488007ded446a8/cpp/build.bat -------------------------------------------------------------------------------- /cpp/dalvik_system_DexFile.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include "dalvik_system_DexFile.h" 14 | #include 15 | 16 | 17 | #define TAG "yongye" 18 | 19 | #define log_info(fmt,args...) __android_log_print(ANDROID_LOG_INFO, TAG, (const char *) fmt, ##args) 20 | #define log_err(fmt,args...) __android_log_print(ANDROID_LOG_ERROR, TAG, (const char *) fmt, ##args) 21 | 22 | #ifdef LOG_DBG 23 | #define log_dbg //log_info 24 | #else 25 | #define log_dbg(...) 26 | #endif 27 | 28 | 29 | //#ifdef __arm__ 30 | #define Elf_Ehdr Elf32_Ehdr 31 | #define Elf_Shdr Elf32_Shdr 32 | #define Elf_Sym Elf32_Sym 33 | //#else defined(__aarch64__) 34 | //#define Elf_Ehdr Elf64_Ehdr 35 | //#define Elf_Shdr Elf64_Shdr 36 | //#define Elf_Sym Elf64_Sym 37 | //#endif 38 | 39 | struct ctx { 40 | void *load_addr; 41 | void *dynstr; 42 | void *dynsym; 43 | int nsyms; 44 | off_t bias; 45 | }; 46 | 47 | 48 | 49 | void *fake_dlopen(const char *libpath, int flags); 50 | void *fake_dlsym(void *handle, const char *name); 51 | 52 | 53 | typedef void *(*org_artDexFileOpenMemory22)(const uint8_t *base, size_t size, 54 | const std::string &location, uint32_t location_checksum, 55 | void *mem_map, const void *oat_file, std::string *error_msg); 56 | 57 | jlong replace_cookie(JNIEnv *env, void *c_dex_cookie, int sdk_int); 58 | 59 | JNICALL extern "C" 60 | JNIEXPORT jlong JNICALL 61 | Java_cn_yongye_nativeshell_inmemoryloaddex_DexFile_createCookieWithDirectBuffer(JNIEnv *env, jclass clazz, 62 | jobject buf, jint start, 63 | jint end) { 64 | // TODO: implement createCookieWithDirectBuffer() 65 | 66 | } 67 | 68 | 69 | JNICALL extern "C" 70 | JNIEXPORT jlong JNICALL 71 | Java_cn_yongye_nativeshell_inmemoryloaddex_DexFile_createCookieWithArray(JNIEnv *env, jclass clazz, 72 | jbyteArray buf, jint start, jint end) { 73 | // TODO: implement createCookieWithArray() 74 | void *artlib = fake_dlopen("/system/lib/libart.so", RTLD_LAZY); 75 | if(!artlib){ 76 | exit(0); 77 | } 78 | org_artDexFileOpenMemory22 openMemory22 = (org_artDexFileOpenMemory22)fake_dlsym(artlib, 79 | "_ZN3art7DexFile10OpenMemoryEPKhjRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPNS_6MemMapEPKNS_7OatFileEPS9_"); 80 | 81 | if(!openMemory22){ 82 | exit(0); 83 | } 84 | jbyte *bytes; 85 | char *pDex = NULL; 86 | std::string location = ""; 87 | std::string err_msg; 88 | 89 | 90 | bytes = env->GetByteArrayElements(buf, 0); 91 | int inDexLen = env->GetArrayLength(buf); 92 | pDex = new char[inDexLen + 1]; 93 | memset(pDex, 0, inDexLen + 1); 94 | memcpy(pDex, bytes, inDexLen); 95 | pDex[inDexLen] = 0; 96 | env->ReleaseByteArrayElements(buf, bytes, 0); 97 | const Header *dex_header = reinterpret_cast(pDex); 98 | void *value = openMemory22((const unsigned char *)pDex, inDexLen, location, dex_header->checksum_, NULL, NULL, &err_msg); 99 | 100 | jlong cookie = replace_cookie(env, value, 22); 101 | return cookie; 102 | 103 | } 104 | 105 | 106 | 107 | void *fake_dlsym(void *handle, const char *name) 108 | { 109 | int k; 110 | struct ctx *ctx = (struct ctx *) handle; 111 | if(!ctx){ 112 | exit(0); 113 | } 114 | Elf_Sym *sym = (Elf_Sym *) ctx->dynsym; 115 | char *strings = (char *) ctx->dynstr; 116 | 117 | for(k = 0; k < ctx->nsyms; k++, sym++) 118 | if(strcmp(strings + sym->st_name, name) == 0) { 119 | /* NB: sym->st_value is an offset into the section for relocatables, 120 | but a VMA for shared libs or exe files, so we have to subtract the bias */ 121 | void *ret = (char*)ctx->load_addr + sym->st_value - ctx->bias; 122 | //log_info("%s found at %p", name, ret); 123 | return ret; 124 | } 125 | return 0; 126 | } 127 | 128 | void *fake_dlopen(const char *libpath, int flags) 129 | { 130 | FILE *maps; 131 | char buff[256]; 132 | struct ctx *ctx = 0; 133 | off_t load_addr, size; 134 | int k, fd = -1, found = 0; 135 | char *shoff; 136 | Elf_Ehdr *elf = (Elf_Ehdr *)MAP_FAILED; 137 | 138 | #define fatal(fmt,args...) do { ; goto err_exit; } while(0) 139 | 140 | maps = fopen("/proc/self/maps", "r"); 141 | if(!maps) fatal("failed to open maps"); 142 | 143 | while (fgets(buff, sizeof(buff), maps)) { 144 | if ((strstr(buff, "r-xp") || strstr(buff, "r--p")) && strstr(buff, libpath)) { 145 | found = 1; 146 | break; 147 | } 148 | } 149 | 150 | fclose(maps); 151 | 152 | if(!found) fatal("%s not found in my userspace", libpath); 153 | 154 | if(sscanf(buff, "%lx", &load_addr) != 1) 155 | fatal("failed to read load address for %s", libpath); 156 | 157 | //log_info("%s loaded in Android at 0x%08lx", libpath, load_addr); 158 | 159 | /* Now, mmap the same library once again */ 160 | 161 | fd = open(libpath, O_RDONLY); 162 | if(fd < 0) fatal("failed to open %s", libpath); 163 | 164 | size = lseek(fd, 0, SEEK_END); 165 | if(size <= 0) fatal("lseek() failed for %s", libpath); 166 | 167 | elf = (Elf_Ehdr *) mmap(0, size, PROT_READ, MAP_SHARED, fd, 0); 168 | close(fd); 169 | fd = -1; 170 | 171 | if(elf == MAP_FAILED) fatal("mmap() failed for %s", libpath); 172 | 173 | ctx = (struct ctx *) calloc(1, sizeof(struct ctx)); 174 | if(!ctx) fatal("no memory for %s", libpath); 175 | 176 | ctx->load_addr = (void *) load_addr; 177 | shoff = ((char* ) elf) + elf->e_shoff; 178 | 179 | for(k = 0; k < elf->e_shnum; k++, shoff += elf->e_shentsize) { 180 | 181 | Elf_Shdr *sh = (Elf_Shdr *) shoff; 182 | log_dbg("%s: k=%d shdr=%p type=%x", __func__, k, sh, sh->sh_type); 183 | 184 | switch(sh->sh_type) { 185 | 186 | case SHT_DYNSYM: 187 | if(ctx->dynsym) fatal("%s: duplicate DYNSYM sections", libpath); /* .dynsym */ 188 | ctx->dynsym = malloc(sh->sh_size); 189 | if(!ctx->dynsym) fatal("%s: no memory for .dynsym", libpath); 190 | memcpy(ctx->dynsym, ((char *) elf) + sh->sh_offset, sh->sh_size); 191 | ctx->nsyms = (sh->sh_size/sizeof(Elf_Sym)) ; 192 | break; 193 | 194 | case SHT_STRTAB: 195 | if(ctx->dynstr) break; /* .dynstr is guaranteed to be the first STRTAB */ 196 | ctx->dynstr = malloc(sh->sh_size); 197 | if(!ctx->dynstr) fatal("%s: no memory for .dynstr", libpath); 198 | memcpy(ctx->dynstr, ((char *) elf) + sh->sh_offset, sh->sh_size); 199 | break; 200 | 201 | case SHT_PROGBITS: 202 | if(!ctx->dynstr || !ctx->dynsym) break; 203 | /* won't even bother checking against the section name */ 204 | ctx->bias = (off_t) sh->sh_addr - (off_t) sh->sh_offset; 205 | k = elf->e_shnum; /* exit for */ 206 | break; 207 | } 208 | } 209 | 210 | munmap(elf, size); 211 | elf = 0; 212 | 213 | if(!ctx->dynstr || !ctx->dynsym) fatal("dynamic sections not found in %s", libpath); 214 | 215 | #undef fatal 216 | 217 | log_dbg("%s: ok, dynsym = %p, dynstr = %p", libpath, ctx->dynsym, ctx->dynstr); 218 | 219 | return ctx; 220 | 221 | err_exit: 222 | if(fd >= 0) close(fd); 223 | if(elf != MAP_FAILED) munmap(elf, size); 224 | // fake_dlclose(ctx); 225 | return 0; 226 | } 227 | 228 | jlong replace_cookie(JNIEnv *env, void *c_dex_cookie, int sdk_int) { 229 | if ((sdk_int == 21) || (sdk_int == 22)){ 230 | std::unique_ptr> dex_files(new std::vector()); 231 | dex_files.get()->push_back(c_dex_cookie); 232 | jlong mCookie = static_cast(reinterpret_cast(dex_files.release())); 233 | return mCookie; 234 | } 235 | 236 | } 237 | -------------------------------------------------------------------------------- /cpp/dalvik_system_DexFile.h: -------------------------------------------------------------------------------- 1 | #ifndef _DEXHEADER_H 2 | #define _DEXHEADER_H 3 | 4 | #include 5 | 6 | static const size_t kSha1DigestSize = 20; 7 | 8 | // Raw header_item. 9 | struct Header 10 | { 11 | uint8_t magic_[8]; 12 | uint32_t checksum_; // See also location_checksum_ 13 | uint8_t signature_[kSha1DigestSize]; 14 | uint32_t file_size_; // size of entire file 15 | uint32_t header_size_; // offset to start of next section 16 | uint32_t endian_tag_; 17 | uint32_t link_size_; // unused 18 | uint32_t link_off_; // unused 19 | uint32_t map_off_; // unused 20 | uint32_t string_ids_size_; // number of StringIds 21 | uint32_t string_ids_off_; // file offset of StringIds array 22 | uint32_t type_ids_size_; // number of TypeIds, we don't support more than 65535 23 | uint32_t type_ids_off_; // file offset of TypeIds array 24 | uint32_t proto_ids_size_; // number of ProtoIds, we don't support more than 65535 25 | uint32_t proto_ids_off_; // file offset of ProtoIds array 26 | uint32_t field_ids_size_; // number of FieldIds 27 | uint32_t field_ids_off_; // file offset of FieldIds array 28 | uint32_t method_ids_size_; // number of MethodIds 29 | uint32_t method_ids_off_; // file offset of MethodIds array 30 | uint32_t class_defs_size_; // number of ClassDefs 31 | uint32_t class_defs_off_; // file offset of ClassDef array 32 | uint32_t data_size_; // unused 33 | uint32_t data_off_; // unused 34 | }; 35 | 36 | #endif // ifndef _DEXHEADER_H -------------------------------------------------------------------------------- /cpp/yongyejiagu.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by yongye 3 | // 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | 14 | #define TAG "yongye" 15 | #define log_info(fmt,args...) __android_log_print(ANDROID_LOG_INFO, TAG, (const char *) fmt, ##args) 16 | #define log_err(fmt,args...) __android_log_print(ANDROID_LOG_ERROR, TAG, (const char *) fmt, ##args) 17 | 18 | #ifdef LOG_DBG 19 | #define log_dbg //log_info 20 | #else 21 | #define log_dbg(...) 22 | #endif 23 | 24 | char* jstringToChar(JNIEnv* env, jstring jstr) { 25 | char* rtn = NULL; 26 | jclass clsstring = env->FindClass("java/lang/String"); 27 | jstring strencode = env->NewStringUTF("GB2312"); 28 | jmethodID mid = env->GetMethodID(clsstring, "getBytes", "(Ljava/lang/String;)[B"); 29 | jbyteArray barr = (jbyteArray) env->CallObjectMethod(jstr, mid, strencode); 30 | jsize alen = env->GetArrayLength(barr); 31 | jbyte* ba = env->GetByteArrayElements(barr, JNI_FALSE); 32 | if (alen > 0) { 33 | rtn = (char*) malloc(alen + 1); 34 | memcpy(rtn, ba, alen); 35 | rtn[alen] = 0; 36 | } 37 | env->ReleaseByteArrayElements(barr, ba, 0); 38 | return rtn; 39 | } 40 | 41 | jbyteArray readDexFileFromApk(JNIEnv* env, jobject oApplication){ 42 | //调用java层方法从原DEX中获取壳DEX数据 43 | log_dbg("开始获取壳DEX数据"); 44 | jclass jcFUtil = env->FindClass("cn/yongye/nativeshell/common/FileUtils"); 45 | jbyteArray jbaDex = static_cast(env->CallStaticObjectMethod(jcFUtil, 46 | env->GetStaticMethodID( 47 | jcFUtil, 48 | "readDexFileFromApk", 49 | "(Landroid/content/Context;)[B"), 50 | oApplication)); 51 | __android_log_write(ANDROID_LOG_WARN, "yongye", "成功获取到壳DEX数据"); 52 | return jbaDex; 53 | } 54 | 55 | /** 56 | * 解密原始DEX 57 | * @param env 58 | * @param jbaShellDex 59 | * @return 60 | */ 61 | jobjectArray decyptSrcDex(JNIEnv *env, jobject oApplication, jbyteArray jbaShellDex){ 62 | __android_log_write(ANDROID_LOG_WARN, "yongye", "开始解密原始DEX"); 63 | jbyte* jbDEX = env->GetByteArrayElements(jbaShellDex, JNI_FALSE); 64 | jsize inShDexLen = env->GetArrayLength(jbaShellDex); 65 | 66 | char * ptShDex = NULL; 67 | if(inShDexLen > 0){ 68 | ptShDex = (char*)malloc(inShDexLen + 1); 69 | memcpy(ptShDex, jbDEX, inShDexLen); 70 | ptShDex[inShDexLen] = 0; 71 | } 72 | env->ReleaseByteArrayElements(jbaShellDex, jbDEX, 0); 73 | 74 | char *ptEncryptedSrcDexLen = &ptShDex[inShDexLen - 4]; 75 | char ptSrcDEXLen[5] = {0}; 76 | int inStart = 0; 77 | for (int i = 3; i >= 0; i--) 78 | { 79 | if (memcmp(&ptShDex[i], "\xff", 1) == 0) 80 | continue; 81 | ptSrcDEXLen[inStart] = ptEncryptedSrcDexLen[i] ^ 0xff; 82 | inStart += 1; 83 | } 84 | int inSrcDexLen = *(int*)ptSrcDEXLen; 85 | char *ptSrcDex = (char *)(malloc(inSrcDexLen + 1)); 86 | memcpy(ptSrcDex, &ptShDex[inShDexLen-inSrcDexLen-4], inSrcDexLen); 87 | for(int i=0; iFindClass("java/nio/ByteBuffer"); 96 | 97 | jbyteArray baSrcDex = env->NewByteArray(inSrcDexLen); 98 | env->SetByteArrayRegion(baSrcDex, 0, inSrcDexLen, (jbyte*)ptSrcDex); 99 | jobject oDexByteBufferNull = env->CallStaticObjectMethod(clsByteBuffer, env->GetStaticMethodID(clsByteBuffer, "allocate", "(I)Ljava/nio/ByteBuffer;"), 100 | inSrcDexLen); 101 | jobject oDexByteBuffer = env->CallStaticObjectMethod(clsByteBuffer, env->GetStaticMethodID(clsByteBuffer, "wrap", "([B)Ljava/nio/ByteBuffer;"), baSrcDex); 102 | jobjectArray baDexBufferArray = env->NewObjectArray(1, clsByteBuffer, oDexByteBuffer); 103 | return baDexBufferArray; 104 | } 105 | 106 | /** 107 | * 加载原始DEX 108 | * @param env 109 | * @param oApplication 110 | * @param stSrcDEXFp 111 | */ 112 | void loadSrcDEX(JNIEnv *env, jobject oApplication, jobjectArray ptSrcDex){ 113 | __android_log_write(ANDROID_LOG_WARN, "yongye", "开始加载原始DEX"); 114 | 115 | jclass clsContextWrapper = env->FindClass("android/content/ContextWrapper"); 116 | jclass clsFile = env->FindClass("java/io/File"); 117 | jclass clsActivityThread = env->FindClass("android/app/ActivityThread"); 118 | jclass clsLoadedApk = env->FindClass("android/app/LoadedApk"); 119 | jclass clsFileUtils = env->FindClass("cn/yongye/nativeshell/common/FileUtils"); 120 | jclass clsMap = env->FindClass("java/util/Map"); 121 | // jclass clsDexLoader = env->FindClass("dalvik/system/DexClassLoader"); 122 | jclass clsMyDexClassLoader = env->FindClass("cn/yongye/nativeshell/inmemoryloaddex/MyDexClassLoader"); 123 | jclass clsReference = env->FindClass("java/lang/ref/Reference"); 124 | jclass clsClass = env->FindClass("java/lang/Class"); 125 | 126 | 127 | jobject oCacheDir = env->CallObjectMethod(oApplication, env->GetMethodID(clsContextWrapper, "getCacheDir", "()Ljava/io/File;")); 128 | jstring stCacheDir = (jstring)env->CallObjectMethod(oCacheDir, env->GetMethodID(clsFile, "getAbsolutePath", "()Ljava/lang/String;")); 129 | 130 | 131 | jstring stCurrntPkgNa = static_cast(env->CallObjectMethod(oApplication, 132 | env->GetMethodID( 133 | clsContextWrapper, 134 | "getPackageName","()Ljava/lang/String;"))); 135 | jobject olibsFile = env->CallObjectMethod(oApplication, env->GetMethodID(clsContextWrapper, "getDir", "(Ljava/lang/String;I)Ljava/io/File;"), env->NewStringUTF("libs"), 0); 136 | jobject oParentDirPathFile = env->CallStaticObjectMethod(clsFileUtils, env->GetStaticMethodID(clsFileUtils, "getParent", "(Ljava/io/File;)Ljava/io/File;"), olibsFile); 137 | jobject oNativeLibFile = env->NewObject(clsFile, env->GetMethodID(clsFile, "", "(Ljava/io/File;Ljava/lang/String;)V"), oParentDirPathFile, env->NewStringUTF("lib")); 138 | jstring stNativeLibFp = static_cast(env->CallObjectMethod(oNativeLibFile, 139 | env->GetMethodID(clsFile, 140 | "getAbsolutePath", 141 | "()Ljava/lang/String;"))); 142 | jobject oCurActivityThread = env->CallStaticObjectMethod(clsActivityThread, env->GetStaticMethodID(clsActivityThread, "currentActivityThread", "()Landroid/app/ActivityThread;")); 143 | jobject oMPkgs = env->GetObjectField(oCurActivityThread, env->GetFieldID(clsActivityThread, "mPackages", "Landroid/util/ArrayMap;")); 144 | jobject oWRShellLoadedApk = env->CallObjectMethod(oMPkgs, env->GetMethodID(clsMap, "get", "(Ljava/lang/Object;)Ljava/lang/Object;"), stCurrntPkgNa); 145 | jobject oShellLoadedApk = env->CallObjectMethod(oWRShellLoadedApk, env->GetMethodID(clsReference, "get", "()Ljava/lang/Object;")); 146 | 147 | 148 | jobject oSrcDexClassLoader = env->NewObject(clsMyDexClassLoader, env->GetMethodID(clsMyDexClassLoader, "", "([Ljava/nio/ByteBuffer;Ljava/lang/ClassLoader;)V"), ptSrcDex, env->GetObjectField(oShellLoadedApk, env->GetFieldID(clsLoadedApk, "mClassLoader", "Ljava/lang/ClassLoader;"))); 149 | jobject mainadmin = env->CallObjectMethod(oSrcDexClassLoader, env->GetMethodID(clsMyDexClassLoader, "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;"), env->NewStringUTF("com.h.MyAdmin")); 150 | jobject mainstring = env->CallObjectMethod(mainadmin, env->GetMethodID(clsClass, "toString", "()Ljava/lang/String;")); 151 | __android_log_write(ANDROID_LOG_WARN, "yongye", jstringToChar(env, (jstring)mainstring)); 152 | __android_log_write(ANDROID_LOG_WARN, "yongye", jstringToChar(env, (jstring)mainstring)); 153 | // jobject oSrcDexClassLoader = env->NewObject(clsDexLoader, env->GetMethodID(clsDexLoader, "", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/ClassLoader;)V"), 154 | // ptSrcDex, stCacheDir, stNativeLibFp, env->GetObjectField(oShellLoadedApk, env->GetFieldID(clsLoadedApk, "mClassLoader", "Ljava/lang/ClassLoader;"))); 155 | 156 | //将原始DEX的classLoader替换进当前线程块中 157 | env->SetObjectField(oShellLoadedApk, env->GetFieldID(clsLoadedApk, "mClassLoader", "Ljava/lang/ClassLoader;"), oSrcDexClassLoader); 158 | __android_log_write(ANDROID_LOG_WARN, "yongye", "加载原始DEX完成"); 159 | } 160 | 161 | 162 | void startSrcApplication(JNIEnv *env){ 163 | __android_log_write(ANDROID_LOG_WARN, "yongye", "开始启动原始DEX的Application组件"); 164 | jclass clsActivityThread = env->FindClass("android/app/ActivityThread"); 165 | jclass clsConfig = env->FindClass("cn/yongye/nativeshell/common/Config"); 166 | jclass clsAppBindData = env->FindClass("android/app/ActivityThread$AppBindData"); 167 | jclass clsApplicationInfo = env->FindClass("android/content/pm/ApplicationInfo"); 168 | jclass clsList = env->FindClass("java/util/List"); 169 | jclass clsApplication = env->FindClass("android/app/Application"); 170 | jclass clsLoadedApk = env->FindClass("android/app/LoadedApk"); 171 | 172 | 173 | jobject oCurActivityThread = env->CallStaticObjectMethod(clsActivityThread, env->GetStaticMethodID(clsActivityThread, "currentActivityThread", "()Landroid/app/ActivityThread;")); 174 | jstring strSrcDexMainAppNa = (jstring)env->GetStaticObjectField(clsConfig, env->GetStaticFieldID(clsConfig, "MAIN_APPLICATION", "Ljava/lang/String;")); 175 | jobject mBoundApplication = env->GetObjectField(oCurActivityThread, env->GetFieldID(clsActivityThread, "mBoundApplication", "Landroid/app/ActivityThread$AppBindData;")); 176 | jobject oloadedApkInfo = env->GetObjectField(mBoundApplication, env->GetFieldID(clsAppBindData, "info", "Landroid/app/LoadedApk;")); 177 | env->SetObjectField(oloadedApkInfo, env->GetFieldID(clsLoadedApk, "mApplication", "Landroid/app/Application;"), nullptr); 178 | jobject omInitApplication = env->GetObjectField(oCurActivityThread, env->GetFieldID(clsActivityThread, "mInitialApplication", "Landroid/app/Application;")); 179 | jobject omAllApplications = env->GetObjectField(oCurActivityThread, env->GetFieldID(clsActivityThread, "mAllApplications", "Ljava/util/ArrayList;")); 180 | jboolean oRemoveRet = (jboolean)env->CallBooleanMethod(omAllApplications, env->GetMethodID(clsList, "remove", "(Ljava/lang/Object;)Z"), omInitApplication); 181 | jobject omApplicationInfo = env->GetObjectField(oloadedApkInfo, env->GetFieldID(clsLoadedApk, "mApplicationInfo", "Landroid/content/pm/ApplicationInfo;")); 182 | env->SetObjectField(omApplicationInfo, env->GetFieldID(clsApplicationInfo, "className", "Ljava/lang/String;"), strSrcDexMainAppNa); 183 | jobject oappInfo = env->GetObjectField(mBoundApplication, env->GetFieldID(clsAppBindData, "appInfo", "Landroid/content/pm/ApplicationInfo;")); 184 | env->SetObjectField(oappInfo, env->GetFieldID(clsApplicationInfo, "className", "Ljava/lang/String;"), strSrcDexMainAppNa); 185 | jobject omakeApplication = env->CallObjectMethod(oloadedApkInfo, env->GetMethodID(clsLoadedApk, "makeApplication", "(ZLandroid/app/Instrumentation;)Landroid/app/Application;"), 186 | false, nullptr); 187 | env->SetObjectField(oCurActivityThread, env->GetFieldID(clsActivityThread, "mInitialApplication", "Landroid/app/Application;"), omakeApplication); 188 | env->CallVoidMethod(omakeApplication, env->GetMethodID(clsApplication, "onCreate", "()V")); 189 | __android_log_write(ANDROID_LOG_WARN, "yongye", "启动原始DEX Application完成"); 190 | } 191 | 192 | /** 193 | * 加固:将加载原始DEX文件以及启动原始DEX的application在Native层实现 194 | */ 195 | extern "C" 196 | JNIEXPORT void JNICALL 197 | Java_cn_yongye_nativeshell_StubApp_loadDEX(JNIEnv *env, jobject app) { 198 | // TODO: implement loadDEX() 199 | /** 200 | * 第一步:从原APK中读取壳DEX 201 | */ 202 | jbyteArray jbaShellDEX = readDexFileFromApk(env, app); 203 | 204 | /** 205 | * 第二步:内存解密原始DEX 206 | */ 207 | jobjectArray strSrcDexFp = decyptSrcDex(env, app, jbaShellDEX); 208 | 209 | /** 210 | * 第三步:内存加载原始DEX 211 | */ 212 | loadSrcDEX(env, app, strSrcDexFp); 213 | 214 | /** 215 | * 第四步:启动原始DEX的Application组件 216 | */ 217 | startSrcApplication(env); 218 | } 219 | 220 | 221 | 222 | 223 | 224 | 225 | -------------------------------------------------------------------------------- /demo.keystore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongyecc/dexshellerInMemory/ffe7defad3cc7eb6d181592e50488007ded446a8/demo.keystore -------------------------------------------------------------------------------- /images/image-20200406155245997.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongyecc/dexshellerInMemory/ffe7defad3cc7eb6d181592e50488007ded446a8/images/image-20200406155245997.png -------------------------------------------------------------------------------- /images/image-20200415234950548.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongyecc/dexshellerInMemory/ffe7defad3cc7eb6d181592e50488007ded446a8/images/image-20200415234950548.png -------------------------------------------------------------------------------- /images/wx.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongyecc/dexshellerInMemory/ffe7defad3cc7eb6d181592e50488007ded446a8/images/wx.jpg -------------------------------------------------------------------------------- /images/wx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongyecc/dexshellerInMemory/ffe7defad3cc7eb6d181592e50488007ded446a8/images/wx.png -------------------------------------------------------------------------------- /images/zfb.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongyecc/dexshellerInMemory/ffe7defad3cc7eb6d181592e50488007ded446a8/images/zfb.jpg -------------------------------------------------------------------------------- /images/zfb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongyecc/dexshellerInMemory/ffe7defad3cc7eb6d181592e50488007ded446a8/images/zfb.png -------------------------------------------------------------------------------- /images/内存加载DEX.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongyecc/dexshellerInMemory/ffe7defad3cc7eb6d181592e50488007ded446a8/images/内存加载DEX.png -------------------------------------------------------------------------------- /lib/libdalvik_system_DexFile.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongyecc/dexshellerInMemory/ffe7defad3cc7eb6d181592e50488007ded446a8/lib/libdalvik_system_DexFile.so -------------------------------------------------------------------------------- /lib/libyongyejiagu.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongyecc/dexshellerInMemory/ffe7defad3cc7eb6d181592e50488007ded446a8/lib/libyongyejiagu.so -------------------------------------------------------------------------------- /shellApplicationSourceCode/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /shellApplicationSourceCode/java/cn/yongye/nativeshell/MainActivity.java: -------------------------------------------------------------------------------- 1 | package cn.yongye.nativeshell; 2 | 3 | import android.app.Activity; 4 | import android.os.Bundle; 5 | 6 | import java.nio.ByteBuffer; 7 | 8 | public class MainActivity extends Activity { 9 | 10 | 11 | 12 | @Override 13 | protected void onCreate(Bundle savedInstanceState) { 14 | super.onCreate(savedInstanceState); 15 | setContentView(R.layout.activity_main); 16 | 17 | } 18 | 19 | 20 | } 21 | -------------------------------------------------------------------------------- /shellApplicationSourceCode/java/cn/yongye/nativeshell/R.java: -------------------------------------------------------------------------------- 1 | /* AUTO-GENERATED FILE. DO NOT MODIFY. 2 | * 3 | * This class was automatically generated by the 4 | * aapt tool from the resource data it found. It 5 | * should not be modified by hand. 6 | */ 7 | 8 | package cn.yongye.nativeshell; 9 | 10 | public final class R { 11 | public static final class attr { 12 | } 13 | public static final class color { 14 | public static final int colorAccent=0x7f050002; 15 | public static final int colorPrimary=0x7f050000; 16 | public static final int colorPrimaryDark=0x7f050001; 17 | } 18 | public static final class drawable { 19 | public static final int ic_launcher_background=0x7f020000; 20 | public static final int ic_launcher_foreground=0x7f020001; 21 | public static final int ic_launcher_foreground_1=0x7f020002; 22 | } 23 | public static final class layout { 24 | public static final int activity_main=0x7f040000; 25 | } 26 | public static final class mipmap { 27 | public static final int ic_launcher=0x7f030000; 28 | public static final int ic_launcher_round=0x7f030001; 29 | } 30 | public static final class string { 31 | public static final int app_name=0x7f060000; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /shellApplicationSourceCode/java/cn/yongye/nativeshell/StubApp.java: -------------------------------------------------------------------------------- 1 | package cn.yongye.nativeshell; 2 | 3 | import java.io.File; 4 | import java.io.FileInputStream; 5 | import java.io.FileNotFoundException; 6 | import java.io.IOException; 7 | import java.nio.ByteBuffer; 8 | import android.util.Log; 9 | import android.app.Application; 10 | import android.content.Context; 11 | import cn.yongye.nativeshell.inmemoryloaddex.MyDexClassLoader; 12 | 13 | 14 | public class StubApp extends Application { 15 | 16 | public static final String TAG = "yongye"; 17 | 18 | static { 19 | System.loadLibrary("yongyejiagu"); 20 | } 21 | 22 | @Override 23 | protected void attachBaseContext(Context base) { 24 | super.attachBaseContext(base); 25 | loadDEX(); 26 | 27 | } 28 | 29 | public native void loadDEX(); 30 | } 31 | -------------------------------------------------------------------------------- /shellApplicationSourceCode/java/cn/yongye/nativeshell/common/Config.java: -------------------------------------------------------------------------------- 1 | package cn.yongye.nativeshell.common; 2 | 3 | public class Config { 4 | public static final String MAIN_APPLICATION = "android.app.Application"; 5 | } -------------------------------------------------------------------------------- /shellApplicationSourceCode/java/cn/yongye/nativeshell/common/FileUtils.java: -------------------------------------------------------------------------------- 1 | package cn.yongye.nativeshell.common; 2 | 3 | import android.content.Context; 4 | import android.util.Log; 5 | 6 | import java.io.BufferedInputStream; 7 | import java.io.ByteArrayInputStream; 8 | import java.io.ByteArrayOutputStream; 9 | import java.io.DataInputStream; 10 | import java.io.File; 11 | import java.io.FileInputStream; 12 | import java.io.FileOutputStream; 13 | import java.io.IOException; 14 | import java.util.zip.ZipEntry; 15 | import java.util.zip.ZipInputStream; 16 | 17 | public class FileUtils { 18 | 19 | /** 20 | * 获取父目录的绝对路径 21 | * @param f 文件对象 22 | * @return 父目录对象 23 | */ 24 | public static File getParent(File f) { 25 | String path = f.getAbsolutePath(); 26 | int separator = path.lastIndexOf(File.separator); 27 | return separator == -1 ? new File(path) : new File(path.substring(0, separator)); 28 | } 29 | 30 | /** 31 | * 获取应用classes.dex字节数据 32 | * @param context 33 | * @return 34 | * @throws IOException 35 | */ 36 | public static byte[] readDexFileFromApk(Context context) throws IOException { 37 | ByteArrayOutputStream dexByteArrayOutputStream = new ByteArrayOutputStream(); 38 | ZipInputStream localZipInputStream = new ZipInputStream( 39 | new BufferedInputStream(new FileInputStream( 40 | context.getApplicationInfo().sourceDir))); 41 | while (true) { 42 | ZipEntry localZipEntry = localZipInputStream.getNextEntry(); 43 | if (localZipEntry == null) { 44 | localZipInputStream.close(); 45 | break; 46 | } 47 | if (localZipEntry.getName().equals("classes.dex")) { 48 | byte[] arrayOfByte = new byte[1024]; 49 | while (true) { 50 | int i = localZipInputStream.read(arrayOfByte); 51 | if (i == -1) 52 | break; 53 | dexByteArrayOutputStream.write(arrayOfByte, 0, i); 54 | } 55 | } 56 | localZipInputStream.closeEntry(); 57 | } 58 | localZipInputStream.close(); 59 | return dexByteArrayOutputStream.toByteArray(); 60 | } 61 | 62 | /** 63 | * 将原DEX从壳DEX上分离出来 64 | * @param stDexPt 65 | * @param apkdata 壳DEX数据 66 | * @throws IOException 67 | */ 68 | public static void splitPayLoadFromDex(String stDexPt, byte[] apkdata) throws IOException { 69 | String TAG = "yongye"; 70 | apkdata = decrypt(apkdata); 71 | int ablen = apkdata.length; 72 | byte[] dexlen = new byte[4]; 73 | System.arraycopy(apkdata, ablen - 4, dexlen, 0, 4); 74 | ByteArrayInputStream bais = new ByteArrayInputStream(dexlen); 75 | DataInputStream in = new DataInputStream(bais); 76 | int readInt = in.readInt(); 77 | Log.i(TAG, "原始DEX长度:" + String.valueOf(readInt)); 78 | byte[] newdex = new byte[readInt]; 79 | System.arraycopy(apkdata, ablen - 4 - readInt, newdex, 0, readInt); 80 | File file = new File(stDexPt); 81 | if(file.exists()){ 82 | Log.i(TAG, file.getAbsolutePath() + "文件还存在"); 83 | } 84 | try { 85 | FileOutputStream localFileOutputStream = new FileOutputStream(file); 86 | localFileOutputStream.write(newdex); 87 | localFileOutputStream.close(); 88 | } catch (IOException localIOException) { 89 | Log.e(TAG, "dump 原始DEX失败"); 90 | throw new RuntimeException(localIOException); 91 | } 92 | } 93 | 94 | private static byte[] decrypt(byte[] baEncryted){ 95 | byte[] baRes = new byte[baEncryted.length]; 96 | for(int i=0; iUse {@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 | -------------------------------------------------------------------------------- /shellApplicationSourceCode/java/cn/yongye/nativeshell/inmemoryloaddex/DexFile.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2007 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 cn.yongye.nativeshell.inmemoryloaddex; 18 | 19 | 20 | import java.io.File; 21 | import java.io.FileNotFoundException; 22 | import java.io.IOException; 23 | import java.lang.reflect.InvocationTargetException; 24 | import java.lang.reflect.Method; 25 | import java.nio.ByteBuffer; 26 | import java.util.Enumeration; 27 | import java.util.List; 28 | 29 | /** 30 | * Loads DEX files. This class is meant for internal use and should not be used 31 | * by applications. 32 | * 33 | * @deprecated This class should not be used directly by applications. It will hurt 34 | * performance in most cases and will lead to incorrect execution of bytecode in 35 | * the worst case. Applications should use one of the standard classloaders such 36 | * as {@link dalvik.system.PathClassLoader} instead. This API will be removed 37 | * in a future Android release. 38 | */ 39 | @Deprecated 40 | public final class DexFile { 41 | public static final String TAG = "永夜" ; 42 | /** 43 | * If close is called, mCookie becomes null but the internal cookie is preserved if the close 44 | * failed so that we can free resources in the finalizer. 45 | */ 46 | 47 | private long mCookie; 48 | 49 | private Object mInternalCookie; 50 | private final String mFileName; 51 | 52 | /** 53 | * Opens a DEX file from a given File object. 54 | * 55 | * @deprecated Applications should use one of the standard classloaders such 56 | * as {@link dalvik.system.PathClassLoader} instead. This API will be removed 57 | * in a future Android release. 58 | */ 59 | @Deprecated 60 | public DexFile(File file) throws IOException { 61 | this(file.getPath()); 62 | } 63 | /* 64 | * Private version with class loader argument. 65 | * 66 | * @param file 67 | * the File object referencing the actual DEX file 68 | * @param loader 69 | * the class loader object creating the DEX file object 70 | * @param elements 71 | * the temporary dex path list elements from DexPathList.makeElements 72 | */ 73 | DexFile(File file, ClassLoader loader, DexPathList.Element[] elements) 74 | throws IOException { 75 | this(file.getPath(), loader, elements); 76 | } 77 | 78 | /** 79 | * Opens a DEX file from a given filename. 80 | * 81 | * @deprecated Applications should use one of the standard classloaders such 82 | * as {@link dalvik.system.PathClassLoader} instead. This API will be removed 83 | * in a future Android release. 84 | */ 85 | @Deprecated 86 | public DexFile(String fileName) throws IOException { 87 | this(fileName, null, null); 88 | } 89 | 90 | /* 91 | * Private version with class loader argument. 92 | * 93 | * @param fileName 94 | * the filename of the DEX file 95 | * @param loader 96 | * the class loader creating the DEX file object 97 | * @param elements 98 | * the temporary dex path list elements from DexPathList.makeElements 99 | */ 100 | DexFile(String fileName, ClassLoader loader, DexPathList.Element[] elements) throws IOException { 101 | // mCookie = openDexFile(fileName, null, 0, loader, elements); 102 | mInternalCookie = mCookie; 103 | mFileName = fileName; 104 | //System.out.println("DEX FILE cookie is " + mCookie + " fileName=" + fileName); 105 | } 106 | 107 | DexFile(ByteBuffer buf) throws IOException { 108 | mCookie = openInMemoryDexFile(buf); 109 | mInternalCookie = mCookie; 110 | mFileName = null; 111 | } 112 | 113 | /** 114 | * Opens a DEX file from a given filename, using a specified file 115 | * to hold the optimized data. 116 | * 117 | * @param sourceName 118 | * Jar or APK file with "classes.dex". 119 | * @param outputName 120 | * File that will hold the optimized form of the DEX data. 121 | * @param flags 122 | * Enable optional features. 123 | * @param loader 124 | * The class loader creating the DEX file object. 125 | * @param elements 126 | * The temporary dex path list elements from DexPathList.makeElements 127 | */ 128 | private DexFile(String sourceName, String outputName, int flags, ClassLoader loader, 129 | DexPathList.Element[] elements) throws IOException { 130 | if (outputName != null) { 131 | try { 132 | String parent = new File(outputName).getParent(); 133 | // if (Libcore.os.getuid() != Libcore.os.stat(parent).st_uid) { 134 | // throw new IllegalArgumentException("Optimized data directory " + parent 135 | // + " is not owned by the current user. Shared storage cannot protect" 136 | // + " your application from code injection attacks."); 137 | // } 138 | } catch (Exception ignored) { 139 | // assume we'll fail with a more contextual error later 140 | } 141 | } 142 | 143 | // mCookie = openDexFile(sourceName, outputName, flags, loader, elements); 144 | mInternalCookie = mCookie; 145 | mFileName = sourceName; 146 | //System.out.println("DEX FILE cookie is " + mCookie + " sourceName=" + sourceName + " outputName=" + outputName); 147 | } 148 | 149 | /** 150 | * Open a DEX file, specifying the file in which the optimized DEX 151 | * data should be written. If the optimized form exists and appears 152 | * to be current, it will be used; if not, the VM will attempt to 153 | * regenerate it. 154 | * 155 | * @deprecated Applications should use one of the standard classloaders such 156 | * as {@link dalvik.system.PathClassLoader} instead. This API will be removed 157 | * in a future Android release. 158 | */ 159 | @Deprecated 160 | static public DexFile loadDex(String sourcePathName, String outputPathName, 161 | int flags) throws IOException { 162 | 163 | /* 164 | * TODO: we may want to cache previously-opened DexFile objects. 165 | * The cache would be synchronized with close(). This would help 166 | * us avoid mapping the same DEX more than once when an app 167 | * decided to open it multiple times. In practice this may not 168 | * be a real issue. 169 | */ 170 | return loadDex(sourcePathName, outputPathName, flags, null, null); 171 | } 172 | 173 | /* 174 | * Private version of loadDex that also takes a class loader. 175 | * 176 | * @param sourcePathName 177 | * Jar or APK file with "classes.dex". (May expand this to include 178 | * "raw DEX" in the future.) 179 | * @param outputPathName 180 | * File that will hold the optimized form of the DEX data. 181 | * @param flags 182 | * Enable optional features. (Currently none defined.) 183 | * @param loader 184 | * Class loader that is aloading the DEX file. 185 | * @param elements 186 | * The temporary dex path list elements from DexPathList.makeElements 187 | * @return 188 | * A new or previously-opened DexFile. 189 | * @throws IOException 190 | * If unable to open the source or output file. 191 | */ 192 | static DexFile loadDex(String sourcePathName, String outputPathName, 193 | int flags, ClassLoader loader, DexPathList.Element[] elements) throws IOException { 194 | 195 | /* 196 | * TODO: we may want to cache previously-opened DexFile objects. 197 | * The cache would be synchronized with close(). This would help 198 | * us avoid mapping the same DEX more than once when an app 199 | * decided to open it multiple times. In practice this may not 200 | * be a real issue. 201 | */ 202 | return new DexFile(sourcePathName, outputPathName, flags, loader, elements); 203 | } 204 | 205 | /** 206 | * Gets the name of the (already opened) DEX file. 207 | * 208 | * @return the file name 209 | */ 210 | public String getName() { 211 | return mFileName; 212 | } 213 | 214 | @Override public String toString() { 215 | if (mFileName != null) { 216 | return getName(); 217 | } else { 218 | // return "InMemoryDexFile[cookie=" + Arrays.toString((long[]) mCookie) + "]"; 219 | return "InMemoryDexFile[cookie=" + mCookie + "]"; 220 | } 221 | } 222 | 223 | /** 224 | * Closes the DEX file. 225 | *

226 | * This may not be able to release all of the resources. If classes from this DEX file are 227 | * still resident, the DEX file can't be unmapped. In the case where we do not release all 228 | * the resources, close is called again in the finalizer. 229 | * 230 | * @throws IOException 231 | * if an I/O error occurs during closing the file, which 232 | * normally should not happen 233 | */ 234 | // public void close() throws IOException { 235 | // if (mInternalCookie != null) { 236 | // if (closeDexFile(mInternalCookie)) { 237 | // mInternalCookie = null; 238 | // } 239 | // mCookie = null; 240 | // } 241 | // } 242 | 243 | /** 244 | * Loads a class. Returns the class on success, or a {@code null} reference 245 | * on failure. 246 | *

247 | * If you are not calling this from a class loader, this is most likely not 248 | * going to do what you want. Use {@link Class#forName(String)} instead. 249 | *

250 | * The method does not throw {@link ClassNotFoundException} if the class 251 | * isn't found because it isn't reasonable to throw exceptions wildly every 252 | * time a class is not found in the first DEX file we look at. 253 | * 254 | * @param name 255 | * the class name, which should look like "java/lang/String" 256 | * 257 | * @param loader 258 | * the class loader that tries to load the class (in most cases 259 | * the caller of the method 260 | * 261 | * @return the {@link Class} object representing the class, or {@code null} 262 | * if the class cannot be loaded 263 | */ 264 | public Class loadClass(String name, ClassLoader loader) { 265 | String slashName = name.replace('.', '/'); 266 | return loadClassBinaryName(slashName, loader, null); 267 | } 268 | 269 | /** 270 | * See {@link #loadClass(String, ClassLoader)}. 271 | * 272 | * This takes a "binary" class name to better match ClassLoader semantics. 273 | * 274 | * @hide 275 | */ 276 | public Class loadClassBinaryName(String name, ClassLoader loader, List suppressed) { 277 | return defineClass(name, loader, mCookie, this, suppressed); 278 | } 279 | 280 | private Class defineClass(String name, ClassLoader loader, Object cookie, 281 | DexFile dexFile, List suppressed) { 282 | Class result = null; 283 | try { 284 | //反射调用DexFile的 285 | Class clsDexFile = Class.forName("dalvik.system.DexFile"); 286 | Method mdDefineClass = clsDexFile.getDeclaredMethod("defineClass", String.class, ClassLoader.class, long.class, List.class); 287 | mdDefineClass.setAccessible(true); 288 | result = (Class) mdDefineClass.invoke(null, name, loader, cookie, suppressed); 289 | // result = defineClassNative(name, loader, cookie, dexFile); 290 | } catch (NoClassDefFoundError e) { 291 | if (suppressed != null) { 292 | suppressed.add(e); 293 | } 294 | } catch (ClassNotFoundException e) { 295 | if (suppressed != null) { 296 | suppressed.add(e); 297 | } 298 | } catch (NoSuchMethodException e) { 299 | e.printStackTrace(); 300 | } catch (IllegalAccessException e) { 301 | e.printStackTrace(); 302 | } catch (InvocationTargetException e) { 303 | e.printStackTrace(); 304 | } 305 | return result; 306 | } 307 | 308 | /** 309 | * Enumerate the names of the classes in this DEX file. 310 | * 311 | * @return an enumeration of names of classes contained in the DEX file, in 312 | * the usual internal form (like "java/lang/String"). 313 | */ 314 | public Enumeration entries() { 315 | return new DFEnum(this); 316 | } 317 | 318 | /* 319 | * Helper class. 320 | */ 321 | private static class DFEnum implements Enumeration { 322 | private int mIndex; 323 | private String[] mNameList; 324 | 325 | DFEnum(DexFile df) { 326 | mIndex = 0; 327 | try { 328 | Class clsgetClassNameList = Class.forName("dalvik.system.DexFile"); 329 | Method mdgetClassNameList = clsgetClassNameList.getMethod("getClassNameList", long.class); 330 | mdgetClassNameList.invoke(null, df.mCookie); 331 | } catch (ClassNotFoundException e) { 332 | e.printStackTrace(); 333 | } catch (NoSuchMethodException e) { 334 | e.printStackTrace(); 335 | } catch (IllegalAccessException e) { 336 | e.printStackTrace(); 337 | } catch (InvocationTargetException e) { 338 | e.printStackTrace(); 339 | } 340 | // mNameList = getClassNameList(df.mCookie); 341 | } 342 | 343 | public boolean hasMoreElements() { 344 | return (mIndex < mNameList.length); 345 | } 346 | 347 | public String nextElement() { 348 | return mNameList[mIndex++]; 349 | } 350 | } 351 | 352 | /** 353 | * Called when the class is finalized. Makes sure the DEX file is closed. 354 | * 355 | * @throws IOException 356 | * if an I/O error occurs during closing the file, which 357 | * normally should not happen 358 | */ 359 | @Override protected void finalize() throws Throwable { 360 | try { 361 | // if (mInternalCookie != null && !closeDexFile(mInternalCookie)) { 362 | if (mInternalCookie != null) { 363 | throw new AssertionError("Failed to close dex file in finalizer."); 364 | } 365 | mInternalCookie = null; 366 | mCookie = 0; 367 | } finally { 368 | super.finalize(); 369 | } 370 | } 371 | 372 | 373 | /* 374 | * Open a DEX file. The value returned is a magic VM cookie. On 375 | * failure, an IOException is thrown. 376 | */ 377 | // private static Object openDexFile(String sourceName, String outputName, int flags, 378 | // ClassLoader loader, DexPathList.Element[] elements) throws IOException { 379 | // // Use absolute paths to enable the use of relative paths when testing on host. 380 | // return openDexFileNative(new File(sourceName).getAbsolutePath(), 381 | // (outputName == null) 382 | // ? null 383 | // : new File(outputName).getAbsolutePath(), 384 | // flags, 385 | // loader, 386 | // elements); 387 | // } 388 | 389 | static { 390 | System.loadLibrary("dalvik_system_DexFile"); 391 | } 392 | 393 | private static long openInMemoryDexFile(ByteBuffer buf) throws IOException { 394 | // if (buf.isDirect()) { 395 | // return createCookieWithDirectBuffer(buf, buf.position(), buf.limit()); 396 | // } else { 397 | // long ret = createCookieWithArray(buf.array(), buf.position(), buf.limit()); 398 | // return ret; 399 | // } 400 | return createCookieWithArray(buf.array(), buf.position(), buf.limit()); 401 | } 402 | 403 | private static native long createCookieWithDirectBuffer(ByteBuffer buf, int start, int end); 404 | private static native long createCookieWithArray(byte[] buf, int start, int end); 405 | 406 | 407 | /* 408 | * Returns true if the dex file is backed by a valid oat file. 409 | */ 410 | // /*package*/ boolean isBackedByOatFile() { 411 | // return isBackedByOatFile(mCookie); 412 | // } 413 | 414 | /* 415 | * Set the dex file as trusted: it can access hidden APIs of the platform. 416 | */ 417 | // /*package*/ void setTrusted() { 418 | // setTrusted(mCookie); 419 | // } 420 | 421 | /* 422 | * Returns true if we managed to close the dex file. 423 | */ 424 | // private static native boolean closeDexFile(Object cookie); 425 | // private static native Class defineClassNative(String name, ClassLoader loader, Object cookie, 426 | // DexFile dexFile) 427 | // throws ClassNotFoundException, NoClassDefFoundError; 428 | // private static native String[] getClassNameList(Object cookie); 429 | // private static native boolean isBackedByOatFile(Object cookie); 430 | // private static native void setTrusted(Object cookie); 431 | /* 432 | * Open a DEX file. The value returned is a magic VM cookie. On 433 | * failure, an IOException is thrown. 434 | */ 435 | // private static native Object openDexFileNative(String sourceName, String outputName, int flags, 436 | // ClassLoader loader, DexPathList.Element[] elements); 437 | 438 | /** 439 | * Returns true if the VM believes that the apk/jar file is out of date 440 | * and should be passed through "dexopt" again. 441 | * 442 | * @param fileName the absolute path to the apk/jar file to examine. 443 | * @return true if dexopt should be called on the file, false otherwise. 444 | * @throws FileNotFoundException if fileName is not readable, 445 | * not a file, or not present. 446 | * @throws IOException if fileName is not a valid apk/jar file or 447 | * if problems occur while parsing it. 448 | * @throws NullPointerException if fileName is null. 449 | */ 450 | // public static native boolean isDexOptNeeded(String fileName) 451 | // throws FileNotFoundException, IOException; 452 | 453 | 454 | public static final int NO_DEXOPT_NEEDED = 0; 455 | 456 | /** 457 | * dex2oat should be run to update the apk/jar from scratch. 458 | * 459 | * See }. 460 | * 461 | * @hide 462 | */ 463 | public static final int DEX2OAT_FROM_SCRATCH = 1; 464 | 465 | /** 466 | * dex2oat should be run to update the apk/jar because the existing code 467 | * is out of date with respect to the boot image. 468 | * 469 | * See }. 470 | * 471 | * @hide 472 | */ 473 | public static final int DEX2OAT_FOR_BOOT_IMAGE = 2; 474 | 475 | /** 476 | * dex2oat should be run to update the apk/jar because the existing code 477 | * is out of date with respect to the target compiler filter. 478 | * 479 | * See }. 480 | * 481 | * @hide 482 | */ 483 | public static final int DEX2OAT_FOR_FILTER = 3; 484 | 485 | /** 486 | * dex2oat should be run to update the apk/jar because the existing code 487 | * is not relocated to match the boot image. 488 | * 489 | * See {@link }. 490 | * 491 | * @hide 492 | */ 493 | public static final int DEX2OAT_FOR_RELOCATION = 4; 494 | 495 | 496 | /** 497 | * Calls {@link #getDexOptNeeded(String, String, String, String, String, boolean, boolean)} 498 | * with a null class loader context. 499 | * 500 | * TODO(ngeoffray, calin): deprecate / remove. 501 | * @hide 502 | */ 503 | // public static int getDexOptNeeded(String fileName, 504 | // String instructionSet, String compilerFilter, boolean newProfile, boolean downgrade) 505 | // throws FileNotFoundException, IOException { 506 | // return getDexOptNeeded( 507 | // fileName, instructionSet, compilerFilter, null, newProfile, downgrade); 508 | // } 509 | 510 | /** 511 | * Returns the VM's opinion of what kind of dexopt is needed to make the 512 | * apk/jar file up to date, where {@code targetMode} is used to indicate what 513 | * type of compilation the caller considers up-to-date, and {@code newProfile} 514 | * is used to indicate whether profile information has changed recently. 515 | * 516 | * @param fileName the absolute path to the apk/jar file to examine. 517 | * @param compilerFilter a compiler filter to use for what a caller considers up-to-date. 518 | * @param classLoaderContext a string encoding the class loader context the dex file 519 | * is intended to have at runtime. 520 | * @param newProfile flag that describes whether a profile corresponding 521 | * to the dex file has been recently updated and should be considered 522 | * in the state of the file. 523 | * @param downgrade flag that describes if the purpose of dexopt is to downgrade the 524 | * compiler filter. If set to false, will be evaluated as an upgrade request. 525 | * @return NO_DEXOPT_NEEDED, or DEX2OAT_*. See documentation 526 | * of the particular status code for more information on its 527 | * meaning. Returns a positive status code if the status refers to 528 | * the oat file in the oat location. Returns a negative status 529 | * code if the status refers to the oat file in the odex location. 530 | * @throws FileNotFoundException if fileName is not readable, 531 | * not a file, or not present. 532 | * @throws IOException if fileName is not a valid apk/jar file or 533 | * if problems occur while parsing it. 534 | * @throws NullPointerException if fileName is null. 535 | * 536 | * @hide 537 | */ 538 | // public static native int getDexOptNeeded(String fileName, 539 | // String instructionSet, String compilerFilter, String classLoaderContext, 540 | // boolean newProfile, boolean downgrade) 541 | // throws FileNotFoundException, IOException; 542 | 543 | /** 544 | * Returns the status of the dex file {@code fileName}. The returned string is 545 | * an opaque, human readable representation of the current status. The output 546 | * is only meant for debugging and is not guaranteed to be stable across 547 | * releases and/or devices. 548 | * 549 | * @hide 550 | */ 551 | // public static native String getDexFileStatus(String fileName, String instructionSet) 552 | // throws FileNotFoundException; 553 | 554 | /** 555 | * Encapsulates information about the optimizations performed on a dex file. 556 | * 557 | * Note that the info is only meant for debugging and is not guaranteed to be 558 | * stable across releases and/or devices. 559 | * 560 | * @hide 561 | */ 562 | public static final class OptimizationInfo { 563 | // The optimization status. 564 | private final String status; 565 | // The optimization reason. The reason might be "unknown" if the 566 | // the compiler artifacts were not annotated during optimizations. 567 | private final String reason; 568 | 569 | private OptimizationInfo(String status, String reason) { 570 | this.status = status; 571 | this.reason = reason; 572 | } 573 | 574 | public String getStatus() { 575 | return status; 576 | } 577 | 578 | public String getReason() { 579 | return reason; 580 | } 581 | } 582 | 583 | /** 584 | * Retrieves the optimization info for a dex file. 585 | * 586 | * @hide 587 | */ 588 | // public static OptimizationInfo getDexFileOptimizationInfo( 589 | // String fileName, String instructionSet) throws FileNotFoundException { 590 | // String[] status = getDexFileOptimizationStatus(fileName, instructionSet); 591 | // return new OptimizationInfo(status[0], status[1]); 592 | // } 593 | 594 | /** 595 | * Returns the optimization status of the dex file {@code fileName}. The returned 596 | * array will have 2 elements which specify: 597 | * - index 0: the level of optimizations 598 | * - index 1: the optimization reason. The reason might be "unknown" if the 599 | * the compiler artifacts were not annotated during optimizations. 600 | * 601 | * The output is only meant for debugging and is not guaranteed to be stable across 602 | * releases and/or devices. 603 | * 604 | * @hide 605 | */ 606 | // private static native String[] getDexFileOptimizationStatus( 607 | // String fileName, String instructionSet) throws FileNotFoundException; 608 | 609 | /** 610 | * Returns the paths of the optimized files generated for {@code fileName}. 611 | * If no optimized code exists the method returns null. 612 | * @hide 613 | */ 614 | // public static native String[] getDexFileOutputPaths(String fileName, String instructionSet) 615 | // throws FileNotFoundException; 616 | 617 | /** 618 | * Returns whether the given filter is a valid filter. 619 | * 620 | * @hide 621 | */ 622 | // public native static boolean isValidCompilerFilter(String filter); 623 | // 624 | 625 | /** 626 | * Returns the static file size of the original dex file. 627 | * The original size of the uncompressed dex file is returned. 628 | * On device the dex file may be compressed or embedded in some other 629 | * file (e.g. oat) in a platform implementation dependent manner. This 630 | * method abstracts away from those details and provides an efficient 631 | * implementation given that the dex file in question has already been 632 | * uncompressed, extracted, and/or loaded by the runtime as appropriate. 633 | *

634 | * In the case of multidex, returns the sum of the original uncompressed 635 | * multidex entry file sizes. 636 | * 637 | * @hide 638 | */ 639 | // public long getStaticSizeOfDexFile() { 640 | // return getStaticSizeOfDexFile(mCookie); 641 | // } 642 | // 643 | // private static native long getStaticSizeOfDexFile(Object cookie); 644 | } 645 | -------------------------------------------------------------------------------- /shellApplicationSourceCode/java/cn/yongye/nativeshell/inmemoryloaddex/Handler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 1997, 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.IOException; 29 | import java.net.*; 30 | 31 | /* 32 | * Jar URL Handler 33 | */ 34 | public class Handler extends URLStreamHandler { 35 | 36 | private static final String separator = "!/"; 37 | 38 | protected URLConnection openConnection(URL u) 39 | throws IOException { 40 | return new JarURLConnection(u, this); 41 | } 42 | 43 | private static int indexOfBangSlash(String spec) { 44 | int indexOfBang = spec.length(); 45 | while((indexOfBang = spec.lastIndexOf('!', indexOfBang)) != -1) { 46 | if ((indexOfBang != (spec.length() - 1)) && 47 | (spec.charAt(indexOfBang + 1) == '/')) { 48 | return indexOfBang + 1; 49 | } else { 50 | indexOfBang--; 51 | } 52 | } 53 | return -1; 54 | } 55 | 56 | /** 57 | * Compare two jar URLs 58 | */ 59 | @Override 60 | protected boolean sameFile(URL u1, URL u2) { 61 | if (!u1.getProtocol().equals("jar") || !u2.getProtocol().equals("jar")) 62 | return false; 63 | 64 | String file1 = u1.getFile(); 65 | String file2 = u2.getFile(); 66 | int sep1 = file1.indexOf(separator); 67 | int sep2 = file2.indexOf(separator); 68 | 69 | if (sep1 == -1 || sep2 == -1) { 70 | return super.sameFile(u1, u2); 71 | } 72 | 73 | String entry1 = file1.substring(sep1 + 2); 74 | String entry2 = file2.substring(sep2 + 2); 75 | 76 | if (!entry1.equals(entry2)) 77 | return false; 78 | 79 | URL enclosedURL1 = null, enclosedURL2 = null; 80 | try { 81 | enclosedURL1 = new URL(file1.substring(0, sep1)); 82 | enclosedURL2 = new URL(file2.substring(0, sep2)); 83 | } catch (MalformedURLException unused) { 84 | return super.sameFile(u1, u2); 85 | } 86 | 87 | if (!super.sameFile(enclosedURL1, enclosedURL2)) { 88 | return false; 89 | } 90 | 91 | return true; 92 | } 93 | 94 | @Override 95 | protected int hashCode(URL u) { 96 | int h = 0; 97 | 98 | String protocol = u.getProtocol(); 99 | if (protocol != null) 100 | h += protocol.hashCode(); 101 | 102 | String file = u.getFile(); 103 | int sep = file.indexOf(separator); 104 | 105 | if (sep == -1) 106 | return h + file.hashCode(); 107 | 108 | URL enclosedURL = null; 109 | String fileWithoutEntry = file.substring(0, sep); 110 | try { 111 | enclosedURL = new URL(fileWithoutEntry); 112 | h += enclosedURL.hashCode(); 113 | } catch (MalformedURLException unused) { 114 | h += fileWithoutEntry.hashCode(); 115 | } 116 | 117 | String entry = file.substring(sep + 2); 118 | h += entry.hashCode(); 119 | 120 | return h; 121 | } 122 | 123 | 124 | @Override 125 | @SuppressWarnings("deprecation") 126 | protected void parseURL(URL url, String spec, 127 | int start, int limit) { 128 | String file = null; 129 | String ref = null; 130 | // first figure out if there is an anchor 131 | int refPos = spec.indexOf('#', limit); 132 | boolean refOnly = refPos == start; 133 | if (refPos > -1) { 134 | ref = spec.substring(refPos + 1, spec.length()); 135 | if (refOnly) { 136 | file = url.getFile(); 137 | } 138 | } 139 | // then figure out if the spec is 140 | // 1. absolute (jar:) 141 | // 2. relative (i.e. url + foo/bar/baz.ext) 142 | // 3. anchor-only (i.e. url + #foo), which we already did (refOnly) 143 | boolean absoluteSpec = false; 144 | if (spec.length() >= 4) { 145 | absoluteSpec = spec.substring(0, 4).equalsIgnoreCase("jar:"); 146 | } 147 | spec = spec.substring(start, limit); 148 | 149 | if (absoluteSpec) { 150 | file = parseAbsoluteSpec(spec); 151 | } else if (!refOnly) { 152 | file = parseContextSpec(url, spec); 153 | 154 | // Canonize the result after the bangslash 155 | int bangSlash = indexOfBangSlash(file); 156 | String toBangSlash = file.substring(0, bangSlash); 157 | String afterBangSlash = file.substring(bangSlash); 158 | ParseUtil canonizer = new ParseUtil(); 159 | afterBangSlash = canonizer.canonizeString(afterBangSlash); 160 | file = toBangSlash + afterBangSlash; 161 | } 162 | setURL(url, "jar", "", -1, file, ref); 163 | } 164 | 165 | private String parseAbsoluteSpec(String spec) { 166 | URL url = null; 167 | int index = -1; 168 | // check for !/ 169 | if ((index = indexOfBangSlash(spec)) == -1) { 170 | throw new NullPointerException("no !/ in spec"); 171 | } 172 | // test the inner URL 173 | try { 174 | String innerSpec = spec.substring(0, index - 1); 175 | url = new URL(innerSpec); 176 | } catch (MalformedURLException e) { 177 | throw new NullPointerException("invalid url: " + 178 | spec + " (" + e + ")"); 179 | } 180 | return spec; 181 | } 182 | 183 | private String parseContextSpec(URL url, String spec) { 184 | String ctxFile = url.getFile(); 185 | // if the spec begins with /, chop up the jar back !/ 186 | if (spec.startsWith("/")) { 187 | int bangSlash = indexOfBangSlash(ctxFile); 188 | if (bangSlash == -1) { 189 | throw new NullPointerException("malformed " + 190 | "context url:" + 191 | url + 192 | ": no !/"); 193 | } 194 | ctxFile = ctxFile.substring(0, bangSlash); 195 | } 196 | if (!ctxFile.endsWith("/") && (!spec.startsWith("/"))){ 197 | // chop up the last component 198 | int lastSlash = ctxFile.lastIndexOf('/'); 199 | if (lastSlash == -1) { 200 | throw new NullPointerException("malformed " + 201 | "context url:" + 202 | url); 203 | } 204 | ctxFile = ctxFile.substring(0, lastSlash + 1); 205 | } 206 | return (ctxFile + spec); 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /shellApplicationSourceCode/java/cn/yongye/nativeshell/inmemoryloaddex/JarFileFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 1997, 2013, 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.FileNotFoundException; 29 | import java.io.IOException; 30 | import java.net.URL; 31 | import java.net.URLConnection; 32 | import java.security.Permission; 33 | import java.util.HashMap; 34 | import java.util.jar.JarFile; 35 | 36 | /* A factory for cached JAR file. This class is used to both retrieve 37 | * and cache Jar files. 38 | * 39 | * @author Benjamin Renaud 40 | * @since JDK1.2 41 | */ 42 | class JarFileFactory implements URLJarFile.URLJarFileCloseController { 43 | 44 | /* the url to file cache */ 45 | private static final HashMap fileCache = new HashMap<>(); 46 | 47 | /* the file to url cache */ 48 | private static final HashMap urlCache = new HashMap<>(); 49 | 50 | private static final JarFileFactory instance = new JarFileFactory(); 51 | 52 | private JarFileFactory() { } 53 | 54 | public static JarFileFactory getInstance() { 55 | return instance; 56 | } 57 | 58 | URLConnection getConnection(JarFile jarFile) throws IOException { 59 | URL u; 60 | synchronized (instance) { 61 | u = urlCache.get(jarFile); 62 | } 63 | if (u != null) 64 | return u.openConnection(); 65 | 66 | return null; 67 | } 68 | 69 | public JarFile get(URL url) throws IOException { 70 | return get(url, true); 71 | } 72 | 73 | JarFile get(URL url, boolean useCaches) throws IOException { 74 | 75 | JarFile result; 76 | JarFile local_result; 77 | 78 | if (useCaches) { 79 | synchronized (instance) { 80 | result = getCachedJarFile(url); 81 | } 82 | if (result == null) { 83 | local_result = URLJarFile.getJarFile(url, this); 84 | synchronized (instance) { 85 | result = getCachedJarFile(url); 86 | if (result == null) { 87 | fileCache.put(URLUtil.urlNoFragString(url), local_result); 88 | urlCache.put(local_result, url); 89 | result = local_result; 90 | } else { 91 | if (local_result != null) { 92 | local_result.close(); 93 | } 94 | } 95 | } 96 | } 97 | } else { 98 | result = URLJarFile.getJarFile(url, this); 99 | } 100 | if (result == null) 101 | throw new FileNotFoundException(url.toString()); 102 | 103 | return result; 104 | } 105 | 106 | /** 107 | * Callback method of the URLJarFileCloseController to 108 | * indicate that the JarFile is close. This way we can 109 | * remove the JarFile from the cache 110 | */ 111 | public void close(JarFile jarFile) { 112 | synchronized (instance) { 113 | URL urlRemoved = urlCache.remove(jarFile); 114 | if (urlRemoved != null) 115 | fileCache.remove(URLUtil.urlNoFragString(urlRemoved)); 116 | } 117 | } 118 | 119 | private JarFile getCachedJarFile(URL url) { 120 | assert Thread.holdsLock(instance); 121 | JarFile result = fileCache.get(URLUtil.urlNoFragString(url)); 122 | 123 | /* if the JAR file is cached, the permission will always be there */ 124 | if (result != null) { 125 | Permission perm = getPermission(result); 126 | if (perm != null) { 127 | SecurityManager sm = System.getSecurityManager(); 128 | if (sm != null) { 129 | try { 130 | sm.checkPermission(perm); 131 | } catch (SecurityException se) { 132 | // fallback to checkRead/checkConnect for pre 1.2 133 | // security managers 134 | if ((perm instanceof java.io.FilePermission) && 135 | perm.getActions().indexOf("read") != -1) { 136 | sm.checkRead(perm.getName()); 137 | } else if ((perm instanceof 138 | java.net.SocketPermission) && 139 | perm.getActions().indexOf("connect") != -1) { 140 | sm.checkConnect(url.getHost(), url.getPort()); 141 | } else { 142 | throw se; 143 | } 144 | } 145 | } 146 | } 147 | } 148 | return result; 149 | } 150 | 151 | private Permission getPermission(JarFile jarFile) { 152 | try { 153 | URLConnection uc = getConnection(jarFile); 154 | if (uc != null) 155 | return uc.getPermission(); 156 | } catch (IOException ioe) { 157 | // gulp 158 | } 159 | 160 | return null; 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /shellApplicationSourceCode/java/cn/yongye/nativeshell/inmemoryloaddex/JarURLConnection.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 1997, 2013, 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.BufferedInputStream; 29 | import java.io.FileNotFoundException; 30 | import java.io.IOException; 31 | import java.io.InputStream; 32 | import java.net.MalformedURLException; 33 | import java.net.URL; 34 | import java.net.URLConnection; 35 | import java.security.Permission; 36 | import java.util.List; 37 | import java.util.Map; 38 | import java.util.jar.JarEntry; 39 | import java.util.jar.JarFile; 40 | 41 | /** 42 | * @author Benjamin Renaud 43 | * @since 1.2 44 | */ 45 | public class JarURLConnection extends java.net.JarURLConnection { 46 | 47 | private static final boolean debug = false; 48 | 49 | /* the Jar file factory. It handles both retrieval and caching. 50 | */ 51 | private static final JarFileFactory factory = JarFileFactory.getInstance(); 52 | 53 | /* the url for the Jar file */ 54 | private URL jarFileURL; 55 | 56 | /* the permission to get this JAR file. This is the actual, ultimate, 57 | * permission, returned by the jar file factory. 58 | */ 59 | private Permission permission; 60 | 61 | /* the url connection for the JAR file */ 62 | private URLConnection jarFileURLConnection; 63 | 64 | /* the entry name, if any */ 65 | private String entryName; 66 | 67 | /* the JarEntry */ 68 | private JarEntry jarEntry; 69 | 70 | /* the jar file corresponding to this connection */ 71 | private JarFile jarFile; 72 | 73 | /* the content type for this connection */ 74 | private String contentType; 75 | 76 | public JarURLConnection(URL url, Handler handler) 77 | throws MalformedURLException, IOException { 78 | super(url); 79 | 80 | jarFileURL = getJarFileURL(); 81 | jarFileURLConnection = jarFileURL.openConnection(); 82 | entryName = getEntryName(); 83 | } 84 | 85 | public JarFile getJarFile() throws IOException { 86 | connect(); 87 | return jarFile; 88 | } 89 | 90 | public JarEntry getJarEntry() throws IOException { 91 | connect(); 92 | return jarEntry; 93 | } 94 | 95 | public Permission getPermission() throws IOException { 96 | return jarFileURLConnection.getPermission(); 97 | } 98 | 99 | class JarURLInputStream extends java.io.FilterInputStream { 100 | JarURLInputStream (InputStream src) { 101 | super (src); 102 | } 103 | public void close () throws IOException { 104 | try { 105 | super.close(); 106 | } finally { 107 | if (!getUseCaches()) { 108 | jarFile.close(); 109 | } 110 | } 111 | } 112 | } 113 | 114 | 115 | 116 | public void connect() throws IOException { 117 | if (!connected) { 118 | /* the factory call will do the security checks */ 119 | jarFile = factory.get(getJarFileURL(), getUseCaches()); 120 | 121 | /* we also ask the factory the permission that was required 122 | * to get the jarFile, and set it as our permission. 123 | */ 124 | if (getUseCaches()) { 125 | boolean oldUseCaches = jarFileURLConnection.getUseCaches(); 126 | jarFileURLConnection = factory.getConnection(jarFile); 127 | jarFileURLConnection.setUseCaches(oldUseCaches); 128 | } 129 | 130 | if ((entryName != null)) { 131 | jarEntry = (JarEntry)jarFile.getEntry(entryName); 132 | if (jarEntry == null) { 133 | try { 134 | if (!getUseCaches()) { 135 | jarFile.close(); 136 | } 137 | } catch (Exception e) { 138 | } 139 | throw new FileNotFoundException("JAR entry " + entryName + 140 | " not found in " + 141 | jarFile.getName()); 142 | } 143 | } 144 | connected = true; 145 | } 146 | } 147 | 148 | public InputStream getInputStream() throws IOException { 149 | connect(); 150 | 151 | InputStream result = null; 152 | 153 | if (entryName == null) { 154 | throw new IOException("no entry name specified"); 155 | } else { 156 | if (jarEntry == null) { 157 | throw new FileNotFoundException("JAR entry " + entryName + 158 | " not found in " + 159 | jarFile.getName()); 160 | } 161 | result = new JarURLInputStream (jarFile.getInputStream(jarEntry)); 162 | } 163 | return result; 164 | } 165 | 166 | public int getContentLength() { 167 | long result = getContentLengthLong(); 168 | if (result > Integer.MAX_VALUE) 169 | return -1; 170 | return (int) result; 171 | } 172 | 173 | public long getContentLengthLong() { 174 | long result = -1; 175 | try { 176 | connect(); 177 | if (jarEntry == null) { 178 | /* if the URL referes to an archive */ 179 | result = jarFileURLConnection.getContentLengthLong(); 180 | } else { 181 | /* if the URL referes to an archive entry */ 182 | result = getJarEntry().getSize(); 183 | } 184 | } catch (IOException e) { 185 | } 186 | return result; 187 | } 188 | 189 | public Object getContent() throws IOException { 190 | Object result = null; 191 | 192 | connect(); 193 | if (entryName == null) { 194 | result = jarFile; 195 | } else { 196 | result = super.getContent(); 197 | } 198 | return result; 199 | } 200 | 201 | public String getContentType() { 202 | if (contentType == null) { 203 | if (entryName == null) { 204 | contentType = "x-java/jar"; 205 | } else { 206 | try { 207 | connect(); 208 | InputStream in = jarFile.getInputStream(jarEntry); 209 | contentType = guessContentTypeFromStream( 210 | new BufferedInputStream(in)); 211 | in.close(); 212 | } catch (IOException e) { 213 | // don't do anything 214 | } 215 | } 216 | if (contentType == null) { 217 | contentType = guessContentTypeFromName(entryName); 218 | } 219 | if (contentType == null) { 220 | contentType = "content/unknown"; 221 | } 222 | } 223 | return contentType; 224 | } 225 | 226 | public String getHeaderField(String name) { 227 | return jarFileURLConnection.getHeaderField(name); 228 | } 229 | 230 | /** 231 | * Sets the general request property. 232 | * 233 | * @param key the keyword by which the request is known 234 | * (e.g., "accept"). 235 | * @param value the value associated with it. 236 | */ 237 | public void setRequestProperty(String key, String value) { 238 | jarFileURLConnection.setRequestProperty(key, value); 239 | } 240 | 241 | /** 242 | * Returns the value of the named general request property for this 243 | * connection. 244 | * 245 | * @return the value of the named general request property for this 246 | * connection. 247 | */ 248 | public String getRequestProperty(String key) { 249 | return jarFileURLConnection.getRequestProperty(key); 250 | } 251 | 252 | /** 253 | * Adds a general request property specified by a 254 | * key-value pair. This method will not overwrite 255 | * existing values associated with the same key. 256 | * 257 | * @param key the keyword by which the request is known 258 | * (e.g., "accept"). 259 | * @param value the value associated with it. 260 | */ 261 | public void addRequestProperty(String key, String value) { 262 | jarFileURLConnection.addRequestProperty(key, value); 263 | } 264 | 265 | /** 266 | * Returns an unmodifiable Map of general request 267 | * properties for this connection. The Map keys 268 | * are Strings that represent the request-header 269 | * field names. Each Map value is a unmodifiable List 270 | * of Strings that represents the corresponding 271 | * field values. 272 | * 273 | * @return a Map of the general request properties for this connection. 274 | */ 275 | public Map> getRequestProperties() { 276 | return jarFileURLConnection.getRequestProperties(); 277 | } 278 | 279 | /** 280 | * Set the value of the allowUserInteraction field of 281 | * this URLConnection. 282 | * 283 | * @param allowuserinteraction the new value. 284 | * @see URLConnection#allowUserInteraction 285 | */ 286 | public void setAllowUserInteraction(boolean allowuserinteraction) { 287 | jarFileURLConnection.setAllowUserInteraction(allowuserinteraction); 288 | } 289 | 290 | /** 291 | * Returns the value of the allowUserInteraction field for 292 | * this object. 293 | * 294 | * @return the value of the allowUserInteraction field for 295 | * this object. 296 | * @see URLConnection#allowUserInteraction 297 | */ 298 | public boolean getAllowUserInteraction() { 299 | return jarFileURLConnection.getAllowUserInteraction(); 300 | } 301 | 302 | /* 303 | * cache control 304 | */ 305 | 306 | /** 307 | * Sets the value of the useCaches field of this 308 | * URLConnection to the specified value. 309 | *

310 | * Some protocols do caching of documents. Occasionally, it is important 311 | * to be able to "tunnel through" and ignore the caches (e.g., the 312 | * "reload" button in a browser). If the UseCaches flag on a connection 313 | * is true, the connection is allowed to use whatever caches it can. 314 | * If false, caches are to be ignored. 315 | * The default value comes from DefaultUseCaches, which defaults to 316 | * true. 317 | * 318 | * @see URLConnection#useCaches 319 | */ 320 | public void setUseCaches(boolean usecaches) { 321 | jarFileURLConnection.setUseCaches(usecaches); 322 | } 323 | 324 | /** 325 | * Returns the value of this URLConnection's 326 | * useCaches field. 327 | * 328 | * @return the value of this URLConnection's 329 | * useCaches field. 330 | * @see URLConnection#useCaches 331 | */ 332 | public boolean getUseCaches() { 333 | return jarFileURLConnection.getUseCaches(); 334 | } 335 | 336 | /** 337 | * Sets the value of the ifModifiedSince field of 338 | * this URLConnection to the specified value. 339 | * 340 | * @param value the new value. 341 | * @see URLConnection#ifModifiedSince 342 | */ 343 | public void setIfModifiedSince(long ifmodifiedsince) { 344 | jarFileURLConnection.setIfModifiedSince(ifmodifiedsince); 345 | } 346 | 347 | /** 348 | * Sets the default value of the useCaches field to the 349 | * specified value. 350 | * 351 | * @param defaultusecaches the new value. 352 | * @see URLConnection#useCaches 353 | */ 354 | public void setDefaultUseCaches(boolean defaultusecaches) { 355 | jarFileURLConnection.setDefaultUseCaches(defaultusecaches); 356 | } 357 | 358 | /** 359 | * Returns the default value of a URLConnection's 360 | * useCaches flag. 361 | *

362 | * Ths default is "sticky", being a part of the static state of all 363 | * URLConnections. This flag applies to the next, and all following 364 | * URLConnections that are created. 365 | * 366 | * @return the default value of a URLConnection's 367 | * useCaches flag. 368 | * @see URLConnection#useCaches 369 | */ 370 | public boolean getDefaultUseCaches() { 371 | return jarFileURLConnection.getDefaultUseCaches(); 372 | } 373 | } 374 | -------------------------------------------------------------------------------- /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/ParseUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 1998, 2007, 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.net.MalformedURLException; 30 | import java.net.URI; 31 | import java.net.URISyntaxException; 32 | import java.net.URL; 33 | import java.nio.ByteBuffer; 34 | import java.nio.CharBuffer; 35 | import java.nio.charset.CharacterCodingException; 36 | import java.nio.charset.CharsetDecoder; 37 | import java.nio.charset.CoderResult; 38 | import java.nio.charset.CodingErrorAction; 39 | import java.util.BitSet; 40 | 41 | /** 42 | * A class that contains useful routines common to sun.net.www 43 | * @author Mike McCloskey 44 | */ 45 | 46 | public class ParseUtil { 47 | static BitSet encodedInPath; 48 | 49 | static { 50 | encodedInPath = new BitSet(256); 51 | 52 | // Set the bits corresponding to characters that are encoded in the 53 | // path component of a URI. 54 | 55 | // These characters are reserved in the path segment as described in 56 | // RFC2396 section 3.3. 57 | encodedInPath.set('='); 58 | encodedInPath.set(';'); 59 | encodedInPath.set('?'); 60 | encodedInPath.set('/'); 61 | 62 | // These characters are defined as excluded in RFC2396 section 2.4.3 63 | // and must be escaped if they occur in the data part of a URI. 64 | encodedInPath.set('#'); 65 | encodedInPath.set(' '); 66 | encodedInPath.set('<'); 67 | encodedInPath.set('>'); 68 | encodedInPath.set('%'); 69 | encodedInPath.set('"'); 70 | encodedInPath.set('{'); 71 | encodedInPath.set('}'); 72 | encodedInPath.set('|'); 73 | encodedInPath.set('\\'); 74 | encodedInPath.set('^'); 75 | encodedInPath.set('['); 76 | encodedInPath.set(']'); 77 | encodedInPath.set('`'); 78 | 79 | // US ASCII control characters 00-1F and 7F. 80 | for (int i=0; i<32; i++) 81 | encodedInPath.set(i); 82 | encodedInPath.set(127); 83 | } 84 | 85 | /** 86 | * Constructs an encoded version of the specified path string suitable 87 | * for use in the construction of a URL. 88 | * 89 | * A path separator is replaced by a forward slash. The string is UTF8 90 | * encoded. The % escape sequence is used for characters that are above 91 | * 0x7F or those defined in RFC2396 as reserved or excluded in the path 92 | * component of a URL. 93 | */ 94 | public static String encodePath(String path) { 95 | return encodePath(path, true); 96 | } 97 | /* 98 | * flag indicates whether path uses platform dependent 99 | * File.separatorChar or not. True indicates path uses platform 100 | * dependent File.separatorChar. 101 | */ 102 | public static String encodePath(String path, boolean flag) { 103 | char[] retCC = new char[path.length() * 2 + 16]; 104 | int retLen = 0; 105 | char[] pathCC = path.toCharArray(); 106 | 107 | int n = path.length(); 108 | for (int i=0; i= 'a' && c <= 'z' || 115 | c >= 'A' && c <= 'Z' || 116 | c >= '0' && c <= '9') { 117 | retCC[retLen++] = c; 118 | } else 119 | if (encodedInPath.get(c)) 120 | retLen = escape(retCC, c, retLen); 121 | else 122 | retCC[retLen++] = c; 123 | } else if (c > 0x07FF) { 124 | retLen = escape(retCC, (char)(0xE0 | ((c >> 12) & 0x0F)), retLen); 125 | retLen = escape(retCC, (char)(0x80 | ((c >> 6) & 0x3F)), retLen); 126 | retLen = escape(retCC, (char)(0x80 | ((c >> 0) & 0x3F)), retLen); 127 | } else { 128 | retLen = escape(retCC, (char)(0xC0 | ((c >> 6) & 0x1F)), retLen); 129 | retLen = escape(retCC, (char)(0x80 | ((c >> 0) & 0x3F)), retLen); 130 | } 131 | } 132 | //worst case scenario for character [0x7ff-] every single 133 | //character will be encoded into 9 characters. 134 | if (retLen + 9 > retCC.length) { 135 | int newLen = retCC.length * 2 + 16; 136 | if (newLen < 0) { 137 | newLen = Integer.MAX_VALUE; 138 | } 139 | char[] buf = new char[newLen]; 140 | System.arraycopy(retCC, 0, buf, 0, retLen); 141 | retCC = buf; 142 | } 143 | } 144 | return new String(retCC, 0, retLen); 145 | } 146 | 147 | /** 148 | * Appends the URL escape sequence for the specified char to the 149 | * specified StringBuffer. 150 | */ 151 | private static int escape(char[] cc, char c, int index) { 152 | cc[index++] = '%'; 153 | cc[index++] = Character.forDigit((c >> 4) & 0xF, 16); 154 | cc[index++] = Character.forDigit(c & 0xF, 16); 155 | return index; 156 | } 157 | 158 | /** 159 | * Un-escape and return the character at position i in string s. 160 | */ 161 | private static byte unescape(String s, int i) { 162 | return (byte) Integer.parseInt(s.substring(i+1,i+3),16); 163 | } 164 | 165 | 166 | /** 167 | * Returns a new String constructed from the specified String by replacing 168 | * the URL escape sequences and UTF8 encoding with the characters they 169 | * represent. 170 | */ 171 | public static String decode(String s) { 172 | int n = s.length(); 173 | if ((n == 0) || (s.indexOf('%') < 0)) 174 | return s; 175 | 176 | StringBuilder sb = new StringBuilder(n); 177 | ByteBuffer bb = ByteBuffer.allocate(n); 178 | CharBuffer cb = CharBuffer.allocate(n); 179 | CharsetDecoder dec = ThreadLocalCoders.decoderFor("UTF-8") 180 | .onMalformedInput(CodingErrorAction.REPORT) 181 | .onUnmappableCharacter(CodingErrorAction.REPORT); 182 | 183 | char c = s.charAt(0); 184 | for (int i = 0; i < n;) { 185 | assert c == s.charAt(i); 186 | if (c != '%') { 187 | sb.append(c); 188 | if (++i >= n) 189 | break; 190 | c = s.charAt(i); 191 | continue; 192 | } 193 | bb.clear(); 194 | int ui = i; 195 | for (;;) { 196 | assert (n - i >= 2); 197 | try { 198 | bb.put(unescape(s, i)); 199 | } catch (NumberFormatException e) { 200 | throw new IllegalArgumentException(); 201 | } 202 | i += 3; 203 | if (i >= n) 204 | break; 205 | c = s.charAt(i); 206 | if (c != '%') 207 | break; 208 | } 209 | bb.flip(); 210 | cb.clear(); 211 | dec.reset(); 212 | CoderResult cr = dec.decode(bb, cb, true); 213 | if (cr.isError()) 214 | throw new IllegalArgumentException("Error decoding percent encoded characters"); 215 | cr = dec.flush(cb); 216 | if (cr.isError()) 217 | throw new IllegalArgumentException("Error decoding percent encoded characters"); 218 | sb.append(cb.flip().toString()); 219 | } 220 | 221 | return sb.toString(); 222 | } 223 | 224 | /** 225 | * Returns a canonical version of the specified string. 226 | */ 227 | public String canonizeString(String file) { 228 | int i = 0; 229 | int lim = file.length(); 230 | 231 | // Remove embedded /../ 232 | while ((i = file.indexOf("/../")) >= 0) { 233 | if ((lim = file.lastIndexOf('/', i - 1)) >= 0) { 234 | file = file.substring(0, lim) + file.substring(i + 3); 235 | } else { 236 | file = file.substring(i + 3); 237 | } 238 | } 239 | // Remove embedded /./ 240 | while ((i = file.indexOf("/./")) >= 0) { 241 | file = file.substring(0, i) + file.substring(i + 2); 242 | } 243 | // Remove trailing .. 244 | while (file.endsWith("/..")) { 245 | i = file.indexOf("/.."); 246 | if ((lim = file.lastIndexOf('/', i - 1)) >= 0) { 247 | file = file.substring(0, lim+1); 248 | } else { 249 | file = file.substring(0, i); 250 | } 251 | } 252 | // Remove trailing . 253 | if (file.endsWith("/.")) 254 | file = file.substring(0, file.length() -1); 255 | 256 | return file; 257 | } 258 | 259 | public static URL fileToEncodedURL(File file) 260 | throws MalformedURLException 261 | { 262 | String path = file.getAbsolutePath(); 263 | path = ParseUtil.encodePath(path); 264 | if (!path.startsWith("/")) { 265 | path = "/" + path; 266 | } 267 | if (!path.endsWith("/") && file.isDirectory()) { 268 | path = path + "/"; 269 | } 270 | return new URL("file", "", path); 271 | } 272 | 273 | public static URI toURI(URL url) { 274 | String protocol = url.getProtocol(); 275 | String auth = url.getAuthority(); 276 | String path = url.getPath(); 277 | String query = url.getQuery(); 278 | String ref = url.getRef(); 279 | if (path != null && !(path.startsWith("/"))) 280 | path = "/" + path; 281 | 282 | // 283 | // In java.net.URI class, a port number of -1 implies the default 284 | // port number. So get it stripped off before creating URI instance. 285 | // 286 | if (auth != null && auth.endsWith(":-1")) 287 | auth = auth.substring(0, auth.length() - 3); 288 | 289 | URI uri; 290 | try { 291 | uri = createURI(protocol, auth, path, query, ref); 292 | } catch (URISyntaxException e) { 293 | uri = null; 294 | } 295 | return uri; 296 | } 297 | 298 | // 299 | // createURI() and its auxiliary code are cloned from java.net.URI. 300 | // Most of the code are just copy and paste, except that quote() 301 | // has been modified to avoid double-escape. 302 | // 303 | // Usually it is unacceptable, but we're forced to do it because 304 | // otherwise we need to change public API, namely java.net.URI's 305 | // multi-argument constructors. It turns out that the changes cause 306 | // incompatibilities so can't be done. 307 | // 308 | private static URI createURI(String scheme, 309 | String authority, 310 | String path, 311 | String query, 312 | String fragment) throws URISyntaxException 313 | { 314 | String s = toString(scheme, null, 315 | authority, null, null, -1, 316 | path, query, fragment); 317 | checkPath(s, scheme, path); 318 | return new URI(s); 319 | } 320 | 321 | private static String toString(String scheme, 322 | String opaquePart, 323 | String authority, 324 | String userInfo, 325 | String host, 326 | int port, 327 | String path, 328 | String query, 329 | String fragment) 330 | { 331 | StringBuffer sb = new StringBuffer(); 332 | if (scheme != null) { 333 | sb.append(scheme); 334 | sb.append(':'); 335 | } 336 | appendSchemeSpecificPart(sb, opaquePart, 337 | authority, userInfo, host, port, 338 | path, query); 339 | appendFragment(sb, fragment); 340 | return sb.toString(); 341 | } 342 | 343 | private static void appendSchemeSpecificPart(StringBuffer sb, 344 | String opaquePart, 345 | String authority, 346 | String userInfo, 347 | String host, 348 | int port, 349 | String path, 350 | String query) 351 | { 352 | if (opaquePart != null) { 353 | /* check if SSP begins with an IPv6 address 354 | * because we must not quote a literal IPv6 address 355 | */ 356 | if (opaquePart.startsWith("//[")) { 357 | int end = opaquePart.indexOf("]"); 358 | if (end != -1 && opaquePart.indexOf(":")!=-1) { 359 | String doquote, dontquote; 360 | if (end == opaquePart.length()) { 361 | dontquote = opaquePart; 362 | doquote = ""; 363 | } else { 364 | dontquote = opaquePart.substring(0,end+1); 365 | doquote = opaquePart.substring(end+1); 366 | } 367 | sb.append (dontquote); 368 | sb.append(quote(doquote, L_URIC, H_URIC)); 369 | } 370 | } else { 371 | sb.append(quote(opaquePart, L_URIC, H_URIC)); 372 | } 373 | } else { 374 | appendAuthority(sb, authority, userInfo, host, port); 375 | if (path != null) 376 | sb.append(quote(path, L_PATH, H_PATH)); 377 | if (query != null) { 378 | sb.append('?'); 379 | sb.append(quote(query, L_URIC, H_URIC)); 380 | } 381 | } 382 | } 383 | 384 | private static void appendAuthority(StringBuffer sb, 385 | String authority, 386 | String userInfo, 387 | String host, 388 | int port) 389 | { 390 | if (host != null) { 391 | sb.append("//"); 392 | if (userInfo != null) { 393 | sb.append(quote(userInfo, L_USERINFO, H_USERINFO)); 394 | sb.append('@'); 395 | } 396 | boolean needBrackets = ((host.indexOf(':') >= 0) 397 | && !host.startsWith("[") 398 | && !host.endsWith("]")); 399 | if (needBrackets) sb.append('['); 400 | sb.append(host); 401 | if (needBrackets) sb.append(']'); 402 | if (port != -1) { 403 | sb.append(':'); 404 | sb.append(port); 405 | } 406 | } else if (authority != null) { 407 | sb.append("//"); 408 | if (authority.startsWith("[")) { 409 | int end = authority.indexOf("]"); 410 | if (end != -1 && authority.indexOf(":")!=-1) { 411 | String doquote, dontquote; 412 | if (end == authority.length()) { 413 | dontquote = authority; 414 | doquote = ""; 415 | } else { 416 | dontquote = authority.substring(0,end+1); 417 | doquote = authority.substring(end+1); 418 | } 419 | sb.append (dontquote); 420 | sb.append(quote(doquote, 421 | L_REG_NAME | L_SERVER, 422 | H_REG_NAME | H_SERVER)); 423 | } 424 | } else { 425 | sb.append(quote(authority, 426 | L_REG_NAME | L_SERVER, 427 | H_REG_NAME | H_SERVER)); 428 | } 429 | } 430 | } 431 | 432 | private static void appendFragment(StringBuffer sb, String fragment) { 433 | if (fragment != null) { 434 | sb.append('#'); 435 | sb.append(quote(fragment, L_URIC, H_URIC)); 436 | } 437 | } 438 | 439 | // Quote any characters in s that are not permitted 440 | // by the given mask pair 441 | // 442 | private static String quote(String s, long lowMask, long highMask) { 443 | int n = s.length(); 444 | StringBuffer sb = null; 445 | boolean allowNonASCII = ((lowMask & L_ESCAPED) != 0); 446 | for (int i = 0; i < s.length(); i++) { 447 | char c = s.charAt(i); 448 | if (c < '\u0080') { 449 | if (!match(c, lowMask, highMask) && !isEscaped(s, i)) { 450 | if (sb == null) { 451 | sb = new StringBuffer(); 452 | sb.append(s.substring(0, i)); 453 | } 454 | appendEscape(sb, (byte)c); 455 | } else { 456 | if (sb != null) 457 | sb.append(c); 458 | } 459 | } else if (allowNonASCII 460 | && (Character.isSpaceChar(c) 461 | || Character.isISOControl(c))) { 462 | if (sb == null) { 463 | sb = new StringBuffer(); 464 | sb.append(s.substring(0, i)); 465 | } 466 | appendEncoded(sb, c); 467 | } else { 468 | if (sb != null) 469 | sb.append(c); 470 | } 471 | } 472 | return (sb == null) ? s : sb.toString(); 473 | } 474 | 475 | // 476 | // To check if the given string has an escaped triplet 477 | // at the given position 478 | // 479 | private static boolean isEscaped(String s, int pos) { 480 | if (s == null || (s.length() <= (pos + 2))) 481 | return false; 482 | 483 | return s.charAt(pos) == '%' 484 | && match(s.charAt(pos + 1), L_HEX, H_HEX) 485 | && match(s.charAt(pos + 2), L_HEX, H_HEX); 486 | } 487 | 488 | private static void appendEncoded(StringBuffer sb, char c) { 489 | ByteBuffer bb = null; 490 | try { 491 | bb = ThreadLocalCoders.encoderFor("UTF-8") 492 | .encode(CharBuffer.wrap("" + c)); 493 | } catch (CharacterCodingException x) { 494 | assert false; 495 | } 496 | while (bb.hasRemaining()) { 497 | int b = bb.get() & 0xff; 498 | if (b >= 0x80) 499 | appendEscape(sb, (byte)b); 500 | else 501 | sb.append((char)b); 502 | } 503 | } 504 | 505 | private final static char[] hexDigits = { 506 | '0', '1', '2', '3', '4', '5', '6', '7', 507 | '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' 508 | }; 509 | 510 | private static void appendEscape(StringBuffer sb, byte b) { 511 | sb.append('%'); 512 | sb.append(hexDigits[(b >> 4) & 0x0f]); 513 | sb.append(hexDigits[(b >> 0) & 0x0f]); 514 | } 515 | 516 | // Tell whether the given character is permitted by the given mask pair 517 | private static boolean match(char c, long lowMask, long highMask) { 518 | if (c < 64) 519 | return ((1L << c) & lowMask) != 0; 520 | if (c < 128) 521 | return ((1L << (c - 64)) & highMask) != 0; 522 | return false; 523 | } 524 | 525 | // If a scheme is given then the path, if given, must be absolute 526 | // 527 | private static void checkPath(String s, String scheme, String path) 528 | throws URISyntaxException 529 | { 530 | if (scheme != null) { 531 | if ((path != null) 532 | && ((path.length() > 0) && (path.charAt(0) != '/'))) 533 | throw new URISyntaxException(s, 534 | "Relative path in absolute URI"); 535 | } 536 | } 537 | 538 | 539 | // -- Character classes for parsing -- 540 | 541 | // Compute a low-order mask for the characters 542 | // between first and last, inclusive 543 | private static long lowMask(char first, char last) { 544 | long m = 0; 545 | int f = Math.max(Math.min(first, 63), 0); 546 | int l = Math.max(Math.min(last, 63), 0); 547 | for (int i = f; i <= l; i++) 548 | m |= 1L << i; 549 | return m; 550 | } 551 | 552 | // Compute the low-order mask for the characters in the given string 553 | private static long lowMask(String chars) { 554 | int n = chars.length(); 555 | long m = 0; 556 | for (int i = 0; i < n; i++) { 557 | char c = chars.charAt(i); 558 | if (c < 64) 559 | m |= (1L << c); 560 | } 561 | return m; 562 | } 563 | 564 | // Compute a high-order mask for the characters 565 | // between first and last, inclusive 566 | private static long highMask(char first, char last) { 567 | long m = 0; 568 | int f = Math.max(Math.min(first, 127), 64) - 64; 569 | int l = Math.max(Math.min(last, 127), 64) - 64; 570 | for (int i = f; i <= l; i++) 571 | m |= 1L << i; 572 | return m; 573 | } 574 | 575 | // Compute the high-order mask for the characters in the given string 576 | private static long highMask(String chars) { 577 | int n = chars.length(); 578 | long m = 0; 579 | for (int i = 0; i < n; i++) { 580 | char c = chars.charAt(i); 581 | if ((c >= 64) && (c < 128)) 582 | m |= (1L << (c - 64)); 583 | } 584 | return m; 585 | } 586 | 587 | 588 | // Character-class masks 589 | 590 | // digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | 591 | // "8" | "9" 592 | private static final long L_DIGIT = lowMask('0', '9'); 593 | private static final long H_DIGIT = 0L; 594 | 595 | // hex = digit | "A" | "B" | "C" | "D" | "E" | "F" | 596 | // "a" | "b" | "c" | "d" | "e" | "f" 597 | private static final long L_HEX = L_DIGIT; 598 | private static final long H_HEX = highMask('A', 'F') | highMask('a', 'f'); 599 | 600 | // upalpha = "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" | "I" | 601 | // "J" | "K" | "L" | "M" | "N" | "O" | "P" | "Q" | "R" | 602 | // "S" | "T" | "U" | "V" | "W" | "X" | "Y" | "Z" 603 | private static final long L_UPALPHA = 0L; 604 | private static final long H_UPALPHA = highMask('A', 'Z'); 605 | 606 | // lowalpha = "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" | 607 | // "j" | "k" | "l" | "m" | "n" | "o" | "p" | "q" | "r" | 608 | // "s" | "t" | "u" | "v" | "w" | "x" | "y" | "z" 609 | private static final long L_LOWALPHA = 0L; 610 | private static final long H_LOWALPHA = highMask('a', 'z'); 611 | 612 | // alpha = lowalpha | upalpha 613 | private static final long L_ALPHA = L_LOWALPHA | L_UPALPHA; 614 | private static final long H_ALPHA = H_LOWALPHA | H_UPALPHA; 615 | 616 | // alphanum = alpha | digit 617 | private static final long L_ALPHANUM = L_DIGIT | L_ALPHA; 618 | private static final long H_ALPHANUM = H_DIGIT | H_ALPHA; 619 | 620 | // mark = "-" | "_" | "." | "!" | "~" | "*" | "'" | 621 | // "(" | ")" 622 | private static final long L_MARK = lowMask("-_.!~*'()"); 623 | private static final long H_MARK = highMask("-_.!~*'()"); 624 | 625 | // unreserved = alphanum | mark 626 | private static final long L_UNRESERVED = L_ALPHANUM | L_MARK; 627 | private static final long H_UNRESERVED = H_ALPHANUM | H_MARK; 628 | 629 | // reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | 630 | // "$" | "," | "[" | "]" 631 | // Added per RFC2732: "[", "]" 632 | private static final long L_RESERVED = lowMask(";/?:@&=+$,[]"); 633 | private static final long H_RESERVED = highMask(";/?:@&=+$,[]"); 634 | 635 | // The zero'th bit is used to indicate that escape pairs and non-US-ASCII 636 | // characters are allowed; this is handled by the scanEscape method below. 637 | private static final long L_ESCAPED = 1L; 638 | private static final long H_ESCAPED = 0L; 639 | 640 | // Dash, for use in domainlabel and toplabel 641 | private static final long L_DASH = lowMask("-"); 642 | private static final long H_DASH = highMask("-"); 643 | 644 | // uric = reserved | unreserved | escaped 645 | private static final long L_URIC = L_RESERVED | L_UNRESERVED | L_ESCAPED; 646 | private static final long H_URIC = H_RESERVED | H_UNRESERVED | H_ESCAPED; 647 | 648 | // pchar = unreserved | escaped | 649 | // ":" | "@" | "&" | "=" | "+" | "$" | "," 650 | private static final long L_PCHAR 651 | = L_UNRESERVED | L_ESCAPED | lowMask(":@&=+$,"); 652 | private static final long H_PCHAR 653 | = H_UNRESERVED | H_ESCAPED | highMask(":@&=+$,"); 654 | 655 | // All valid path characters 656 | private static final long L_PATH = L_PCHAR | lowMask(";/"); 657 | private static final long H_PATH = H_PCHAR | highMask(";/"); 658 | 659 | // userinfo = *( unreserved | escaped | 660 | // ";" | ":" | "&" | "=" | "+" | "$" | "," ) 661 | private static final long L_USERINFO 662 | = L_UNRESERVED | L_ESCAPED | lowMask(";:&=+$,"); 663 | private static final long H_USERINFO 664 | = H_UNRESERVED | H_ESCAPED | highMask(";:&=+$,"); 665 | 666 | // reg_name = 1*( unreserved | escaped | "$" | "," | 667 | // ";" | ":" | "@" | "&" | "=" | "+" ) 668 | private static final long L_REG_NAME 669 | = L_UNRESERVED | L_ESCAPED | lowMask("$,;:@&=+"); 670 | private static final long H_REG_NAME 671 | = H_UNRESERVED | H_ESCAPED | highMask("$,;:@&=+"); 672 | 673 | // All valid characters for server-based authorities 674 | private static final long L_SERVER 675 | = L_USERINFO | L_ALPHANUM | L_DASH | lowMask(".:@[]"); 676 | private static final long H_SERVER 677 | = H_USERINFO | H_ALPHANUM | H_DASH | highMask(".:@[]"); 678 | } 679 | -------------------------------------------------------------------------------- /shellApplicationSourceCode/java/cn/yongye/nativeshell/inmemoryloaddex/ThreadLocalCoders.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 | 27 | package cn.yongye.nativeshell.inmemoryloaddex; 28 | 29 | import java.nio.charset.Charset; 30 | import java.nio.charset.CharsetDecoder; 31 | import java.nio.charset.CharsetEncoder; 32 | 33 | 34 | /** 35 | * Utility class for caching per-thread decoders and encoders. 36 | */ 37 | 38 | public class ThreadLocalCoders { 39 | 40 | private static final int CACHE_SIZE = 3; 41 | 42 | private static abstract class Cache { 43 | 44 | // Thread-local reference to array of cached objects, in LRU order 45 | private ThreadLocal cache = new ThreadLocal<>(); 46 | private final int size; 47 | 48 | Cache(int size) { 49 | this.size = size; 50 | } 51 | 52 | abstract Object create(Object name); 53 | 54 | private void moveToFront(Object[] oa, int i) { 55 | Object ob = oa[i]; 56 | for (int j = i; j > 0; j--) 57 | oa[j] = oa[j - 1]; 58 | oa[0] = ob; 59 | } 60 | 61 | abstract boolean hasName(Object ob, Object name); 62 | 63 | Object forName(Object name) { 64 | Object[] oa = cache.get(); 65 | if (oa == null) { 66 | oa = new Object[size]; 67 | cache.set(oa); 68 | } else { 69 | for (int i = 0; i < oa.length; i++) { 70 | Object ob = oa[i]; 71 | if (ob == null) 72 | continue; 73 | if (hasName(ob, name)) { 74 | if (i > 0) 75 | moveToFront(oa, i); 76 | return ob; 77 | } 78 | } 79 | } 80 | 81 | // Create a new object 82 | Object ob = create(name); 83 | oa[oa.length - 1] = ob; 84 | moveToFront(oa, oa.length - 1); 85 | return ob; 86 | } 87 | 88 | } 89 | 90 | private static Cache decoderCache = new Cache(CACHE_SIZE) { 91 | boolean hasName(Object ob, Object name) { 92 | if (name instanceof String) 93 | return (((CharsetDecoder)ob).charset().name().equals(name)); 94 | if (name instanceof Charset) 95 | return ((CharsetDecoder)ob).charset().equals(name); 96 | return false; 97 | } 98 | Object create(Object name) { 99 | if (name instanceof String) 100 | return Charset.forName((String)name).newDecoder(); 101 | if (name instanceof Charset) 102 | return ((Charset)name).newDecoder(); 103 | assert false; 104 | return null; 105 | } 106 | }; 107 | 108 | public static CharsetDecoder decoderFor(Object name) { 109 | CharsetDecoder cd = (CharsetDecoder)decoderCache.forName(name); 110 | cd.reset(); 111 | return cd; 112 | } 113 | 114 | private static Cache encoderCache = new Cache(CACHE_SIZE) { 115 | boolean hasName(Object ob, Object name) { 116 | if (name instanceof String) 117 | return (((CharsetEncoder)ob).charset().name().equals(name)); 118 | if (name instanceof Charset) 119 | return ((CharsetEncoder)ob).charset().equals(name); 120 | return false; 121 | } 122 | Object create(Object name) { 123 | if (name instanceof String) 124 | return Charset.forName((String)name).newEncoder(); 125 | if (name instanceof Charset) 126 | return ((Charset)name).newEncoder(); 127 | assert false; 128 | return null; 129 | } 130 | }; 131 | 132 | public static CharsetEncoder encoderFor(Object name) { 133 | CharsetEncoder ce = (CharsetEncoder)encoderCache.forName(name); 134 | ce.reset(); 135 | return ce; 136 | } 137 | 138 | } 139 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /shellApplicationSourceCode/java/cn/yongye/nativeshell/inmemoryloaddex/URLUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2009, 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.net.URL; 29 | 30 | /** 31 | * URL Utility class. 32 | */ 33 | public class URLUtil { 34 | /** 35 | * Returns a string form of the url suitable for use as a key in HashMap/Sets. 36 | * 37 | * The string form should be behave in the same manner as the URL when 38 | * compared for equality in a HashMap/Set, except that no nameservice 39 | * lookup is done on the hostname (only string comparison), and the fragment 40 | * is not considered. 41 | * 42 | * 43 | */ 44 | public static String urlNoFragString(URL url) { 45 | StringBuilder strForm = new StringBuilder(); 46 | 47 | String protocol = url.getProtocol(); 48 | if (protocol != null) { 49 | /* protocol is compared case-insensitive, so convert to lowercase */ 50 | protocol = protocol.toLowerCase(); 51 | strForm.append(protocol); 52 | strForm.append("://"); 53 | } 54 | 55 | String host = url.getHost(); 56 | if (host != null) { 57 | /* host is compared case-insensitive, so convert to lowercase */ 58 | host = host.toLowerCase(); 59 | strForm.append(host); 60 | 61 | int port = url.getPort(); 62 | if (port == -1) { 63 | /* if no port is specificed then use the protocols 64 | * default, if there is one */ 65 | port = url.getDefaultPort(); 66 | } 67 | if (port != -1) { 68 | strForm.append(":").append(port); 69 | } 70 | } 71 | 72 | String file = url.getFile(); 73 | if (file != null) { 74 | strForm.append(file); 75 | } 76 | 77 | return strForm.toString(); 78 | } 79 | 80 | // Android-removed: Android doesn't support SecurityManager, so Permissions logic is dead code. 81 | /* 82 | public static Permission getConnectPermission(URL url) throws IOException { 83 | String urlStringLowerCase = url.toString().toLowerCase(); 84 | if (urlStringLowerCase.startsWith("http:") || urlStringLowerCase.startsWith("https:")) { 85 | return getURLConnectPermission(url); 86 | } else if (urlStringLowerCase.startsWith("jar:http:") || urlStringLowerCase.startsWith("jar:https:")) { 87 | String urlString = url.toString(); 88 | int bangPos = urlString.indexOf("!/"); 89 | urlString = urlString.substring(4, bangPos > -1 ? bangPos : urlString.length()); 90 | URL u = new URL(urlString); 91 | return getURLConnectPermission(u); 92 | // If protocol is HTTP or HTTPS than use URLPermission object 93 | } else { 94 | return url.openConnection().getPermission(); 95 | } 96 | } 97 | 98 | private static Permission getURLConnectPermission(URL url) { 99 | String urlString = url.getProtocol() + "://" + url.getAuthority() + url.getPath(); 100 | return new URLPermission(urlString); 101 | } 102 | */ 103 | } 104 | 105 | -------------------------------------------------------------------------------- /shellApplicationSourceCode/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /shellApplicationSourceCode/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /shellApplicationSourceCode/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 13 | 14 | -------------------------------------------------------------------------------- /shellApplicationSourceCode/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /shellApplicationSourceCode/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /shellApplicationSourceCode/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongyecc/dexshellerInMemory/ffe7defad3cc7eb6d181592e50488007ded446a8/shellApplicationSourceCode/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /shellApplicationSourceCode/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongyecc/dexshellerInMemory/ffe7defad3cc7eb6d181592e50488007ded446a8/shellApplicationSourceCode/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /shellApplicationSourceCode/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongyecc/dexshellerInMemory/ffe7defad3cc7eb6d181592e50488007ded446a8/shellApplicationSourceCode/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /shellApplicationSourceCode/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongyecc/dexshellerInMemory/ffe7defad3cc7eb6d181592e50488007ded446a8/shellApplicationSourceCode/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /shellApplicationSourceCode/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongyecc/dexshellerInMemory/ffe7defad3cc7eb6d181592e50488007ded446a8/shellApplicationSourceCode/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /shellApplicationSourceCode/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongyecc/dexshellerInMemory/ffe7defad3cc7eb6d181592e50488007ded446a8/shellApplicationSourceCode/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /shellApplicationSourceCode/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongyecc/dexshellerInMemory/ffe7defad3cc7eb6d181592e50488007ded446a8/shellApplicationSourceCode/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /shellApplicationSourceCode/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongyecc/dexshellerInMemory/ffe7defad3cc7eb6d181592e50488007ded446a8/shellApplicationSourceCode/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /shellApplicationSourceCode/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongyecc/dexshellerInMemory/ffe7defad3cc7eb6d181592e50488007ded446a8/shellApplicationSourceCode/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /shellApplicationSourceCode/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongyecc/dexshellerInMemory/ffe7defad3cc7eb6d181592e50488007ded446a8/shellApplicationSourceCode/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /shellApplicationSourceCode/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #6200EE 4 | #3700B3 5 | #03DAC5 6 | 7 | -------------------------------------------------------------------------------- /shellApplicationSourceCode/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | ShellApp 3 | 4 | -------------------------------------------------------------------------------- /shellApplicationSourceCode/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /test.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongyecc/dexshellerInMemory/ffe7defad3cc7eb6d181592e50488007ded446a8/test.apk --------------------------------------------------------------------------------