├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── apilevels ├── .gitignore ├── android-19.dex └── gen.sh ├── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── img ├── icon-large.png └── icon-small.png ├── src ├── main │ └── java │ │ └── patdroid │ │ ├── Main.java │ │ ├── Settings.java │ │ ├── core │ │ ├── ClassDetail.java │ │ ├── ClassDetailLoader.java │ │ ├── ClassInfo.java │ │ ├── FieldInfo.java │ │ ├── FullMethodSignature.java │ │ ├── MethodInfo.java │ │ ├── MethodSignature.java │ │ ├── PrimitiveInfo.java │ │ ├── ReflectionClassDetailLoader.java │ │ ├── Scope.java │ │ └── TryBlockInfo.java │ │ ├── dalvik │ │ ├── Dalvik.java │ │ ├── Instruction.java │ │ └── Invocation.java │ │ ├── fs │ │ ├── EmulatedFS.java │ │ ├── FileNode.java │ │ └── ZipBackedNode.java │ │ ├── permission │ │ ├── APIMapping.java │ │ └── PScoutParser.java │ │ ├── smali │ │ ├── MethodImplementationTranslator.java │ │ └── SmaliClassDetailLoader.java │ │ └── util │ │ ├── JSONWriter.java │ │ ├── Log.java │ │ ├── Pair.java │ │ └── Report.java └── test │ └── java │ └── patdroid │ ├── core │ ├── ClassInfoTest.java │ └── PrimitiveTest.java │ ├── dalvik │ └── DalvikTest.java │ ├── permission │ └── PScoutParserTest.java │ ├── regtest │ └── RegTest.java │ └── smali │ └── SmaliLoaderTest.java └── tools ├── .gitignore ├── README.md ├── __main__.py └── tool_parse_layout.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | 3 | # Mobile Tools for Java (J2ME) 4 | .mtj.tmp/ 5 | 6 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 7 | hs_err_pid* 8 | 9 | .gradle 10 | .idea 11 | build 12 | 13 | *.iml 14 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | notifications: 3 | email: false 4 | slack: appcademy:DxG0Uzxs6XGl9GZspTSYtuWj 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PATDroid [![Build Status](https://travis-ci.org/mingyuan-xia/PATDroid.svg?branch=master)](https://travis-ci.org/mingyuan-xia/PATDroid) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/me.mxia/patdroid/badge.svg)](https://maven-badges.herokuapp.com/maven-central/me.mxia/patdroid) 2 | 3 | PATDroid is a collection of tools and data structures for analyzing Android applications and the system itself. We intend to build it as a common base for developing novel mobile software debugging, refactoring, reverse engineering tools. 4 | 5 | ```groovy 6 | dependencies { 7 | compile group: 'mxia.me', name: 'patdroid', version: '1.0.0' 8 | } 9 | ``` 10 | The `master` branch is the nightly dev branch, which could diverge greatly from the maven artifacts. 11 | 12 | ## Packages 13 | Here is a one-sentence description for each package. Find the detailed usage tutorials on our wiki by clicking on the package name to redirect to their wiki pages. Most public APIs are Java-doced. PATDroid requires Java6+. It goes well with Oracle/OpenJDK 1.6, 1.7, Dalvik (Yes, you can run it on a smartphone). Gradle (wrapper) is the default build system. You can import the project to IntelliJ IDEA (File->Import from Gradle Project) and Eclipse (similar). 14 | 15 | * [`patdroid.core`](https://github.com/mingyuan-xia/PATDroid/wiki/package:-core): provide abstractions for methods, classes, fields, and primitive Java type values 16 | * [`patdroid.permission`](https://github.com/mingyuan-xia/PATDroid/wiki/package:-permission): specify what Android permissions are needed for every Android APIs 17 | * [`patdroid.fs`](https://github.com/mingyuan-xia/PATDroid/wiki/package:-fs): an emulated and simplified Android file system 18 | * [`patdroid.dalvik`](https://github.com/mingyuan-xia/PATDroid/wiki/package:-dalvik): Android Dalvik JVM instructions and representations 19 | * [`patdroid.smali`](https://github.com/mingyuan-xia/PATDroid/wiki/package:-smali): using [SMALI](https://github.com/JesusFreke/smali) to extract classes, methods, fields and instructions from an APK 20 | 21 | Closely related functionality: 22 | * ~~`patdroid.dex2jar`~~: using [dex2jar](https://github.com/pxb1988/dex2jar) to extract classes, methods, fields and instructions from an APK. This has been deprecated and removed. 23 | * Layout XMLs and manifest file, please refer to [apktool](https://ibotpeaches.github.io/Apktool/) and various AXML parsers exist for different programming languages. 24 | * Taint sources and sinks: FlowDroid provides a list of [sources and sinks for taint analysis](https://github.com/secure-software-engineering/soot-infoflow-android/blob/develop/SourcesAndSinks.txt) that we cross referenced. 25 | * Soot: my tribute to [Sable's Soot](http://sable.github.io/soot/) and the happy seminar time at [McGill McConnell 2rd floor](https://www.mcgill.ca/maps/mcconnell-engineering-building). Soot provides a disassembler similar to smali, and a lot of high-level program analysis constructs and tasks, such as Call Graph. Also [FlowDroid](https://github.com/secure-software-engineering/soot-infoflow-android) provides a nice and complete flow analysis. 26 | 27 | 28 | ## History and Philosophy 29 | PATDroid was part of [AppAudit](http://appaudit.io), which is a tool that simulates the execution of app code and checks if it leaks sensitive user data. 30 | You can find out more details from our [S&P'15 paper](http://www.ieee-security.org/TC/SP2015/papers-archived/6949a899.pdf). 31 | We make part of AppAudit public to be useful to researchers and developers. 32 | Overall, we try to make the entire project 33 | 34 | 1. concise (with fewer abstractions as possible such that users wont feel like searching a needle in the ocean) 35 | 2. properly documented (javadoc, and wiki tutorial) 36 | 3. loosely coupled (packages trying to be self-contained) 37 | 4. efficient (graduate students need life with bf/gf not waiting for computers to complete analyses) 38 | 5. look like good code 39 | 40 | If you want to contribute, make sure you follow these traditions and feel free to submit a pull request. 41 | Note that quick-and-dirty patches require many efforts to make them ready, and thus take more time to merge. 42 | I am always open to suggestions and willing to hear interesting projects that make use of PATDroid. 43 | Right now, several exciting research projects across McGill University and Shanghai Jiao Tong University are using PATDroid. We will update links to them soon. 44 | 45 | * Contact: [email](mailto:mxia@mxia.me), new issues, pull requests. 46 | * PATDroid uses `Apache License 2.0`. If you would like to use PATDroid in academic publications, bibtex can be found [here](http://dl.acm.org/citation.cfm?id=2867539.2867691). 47 | -------------------------------------------------------------------------------- /apilevels/.gitignore: -------------------------------------------------------------------------------- 1 | *.dex 2 | -------------------------------------------------------------------------------- /apilevels/android-19.dex: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mingyuan-xia/PATDroid/454629a00cde6b7f617d19916bfa5284cd731ec7/apilevels/android-19.dex -------------------------------------------------------------------------------- /apilevels/gen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | if [ $# -ne 1 ]; then 3 | echo "Usage: gen.sh path/to/android/sdk" 4 | exit 5 | fi 6 | for d in $1/platforms/*; do 7 | p=$(basename "$d") 8 | echo "converting $p" 9 | dx --dex --core-library --output=$p.dex $d/android.jar 10 | done 11 | echo "Done" 12 | 13 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java' 2 | apply plugin: 'maven' 3 | apply plugin: 'application' 4 | apply plugin: 'signing' 5 | 6 | group = 'me.mxia' 7 | archivesBaseName = 'patdroid' 8 | sourceCompatibility = 1.6 9 | version = '1.1.0-SNAPSHOT' 10 | mainClassName = "patdroid.Main" 11 | 12 | repositories { 13 | mavenCentral() 14 | } 15 | 16 | signing { 17 | required { gradle.taskGraph.hasTask('uploadArchives') } 18 | sign configurations.archives 19 | } 20 | 21 | dependencies { 22 | testCompile group: 'junit', name: 'junit', version: '4.12' 23 | compile group: 'org.smali', name: 'dexlib2', version: '2.1.3' 24 | compile group: 'com.google.guava', name: 'guava', version: '20.0' 25 | } 26 | 27 | test { 28 | systemProperty 'regtest.apkpath', System.getProperty('regtest.apkpath') 29 | systemProperty 'regtest.updatedump', System.getProperty('regtest.updatedump') 30 | testLogging.showStandardStreams = true 31 | } 32 | 33 | task javadocJar(type: Jar) { 34 | classifier = 'javadoc' 35 | from javadoc 36 | } 37 | 38 | task sourcesJar(type: Jar) { 39 | classifier = 'sources' 40 | from sourceSets.main.allSource 41 | } 42 | 43 | artifacts { 44 | archives javadocJar, sourcesJar 45 | } 46 | 47 | uploadArchives { 48 | repositories { 49 | mavenDeployer { 50 | beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) } 51 | if (rootProject.hasProperty('ossrhUsername') && rootProject.hasProperty('ossrhPassword')) { 52 | repository(url: "https://oss.sonatype.org/service/local/staging/deploy/maven2/") { 53 | authentication(userName: ossrhUsername, password: ossrhPassword) 54 | } 55 | 56 | snapshotRepository(url: "https://oss.sonatype.org/content/repositories/snapshots/") { 57 | authentication(userName: ossrhUsername, password: ossrhPassword) 58 | } 59 | } 60 | 61 | 62 | pom.project { 63 | name 'PATDroid' 64 | packaging 'jar' 65 | description 'A Program Analysis Toolkit for Android' 66 | url 'https://github.com/mingyuan-xia/PATDroid' 67 | 68 | scm { 69 | connection 'scm:git:git@github.com:mingyuan-xia/PATDroid.git' 70 | developerConnection 'scm:git:git@github.com:mingyuan-xia/PATDroid.git' 71 | url 'https://github.com/mingyuan-xia/PATDroid' 72 | } 73 | 74 | licenses { 75 | license { 76 | name 'The Apache License, Version 2.0' 77 | url 'http://www.apache.org/licenses/LICENSE-2.0.txt' 78 | } 79 | } 80 | 81 | developers { 82 | developer { 83 | id 'mingyuan-xia' 84 | name 'Mingyuan Xia' 85 | email 'mxia@mxia.me' 86 | } 87 | 88 | developer { 89 | id 'iceb0y' 90 | name 'Lu Gong' 91 | email 'me@iceboy.org' 92 | } 93 | } 94 | } 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mingyuan-xia/PATDroid/454629a00cde6b7f617d19916bfa5284cd731ec7/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Jan 24 14:34:29 CST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-3.2-bin.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn ( ) { 37 | echo "$*" 38 | } 39 | 40 | die ( ) { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | for s in "${@}" ; do 159 | s=\"$s\" 160 | APP_ARGS=$APP_ARGS" "$s 161 | done 162 | 163 | # Collect all arguments for the java command, following the shell quoting and substitution rules 164 | eval set -- "$DEFAULT_JVM_OPTS" "$JAVA_OPTS" "$GRADLE_OPTS" "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 165 | 166 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 167 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 168 | cd "$(dirname "$0")" 169 | fi 170 | 171 | exec "$JAVACMD" "$@" 172 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /img/icon-large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mingyuan-xia/PATDroid/454629a00cde6b7f617d19916bfa5284cd731ec7/img/icon-large.png -------------------------------------------------------------------------------- /img/icon-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mingyuan-xia/PATDroid/454629a00cde6b7f617d19916bfa5284cd731ec7/img/icon-small.png -------------------------------------------------------------------------------- /src/main/java/patdroid/Main.java: -------------------------------------------------------------------------------- 1 | package patdroid; 2 | 3 | import patdroid.core.ClassInfo; 4 | import patdroid.core.MethodInfo; 5 | import patdroid.core.Scope; 6 | import patdroid.dalvik.Instruction; 7 | import patdroid.smali.SmaliClassDetailLoader; 8 | 9 | import java.io.File; 10 | import java.io.IOException; 11 | import java.util.zip.ZipFile; 12 | 13 | public class Main { 14 | private static final File FRAMEWORK_CLASSES_FOLDER = new File("apilevels"); 15 | private static final int API_LEVEL = 19; 16 | 17 | /** 18 | * An example using the PATDroid APIs to print all classes 19 | * and containing methods in an APK file 20 | * @param args The first arg should be the path/to/apk 21 | * @throws IOException when the file is not OK 22 | */ 23 | public static void main(String[] args) throws IOException { 24 | if (args.length <= 0) { 25 | System.out.println("Usage: patdroid path/to/apk"); 26 | return; 27 | } 28 | Scope scope = new Scope(); 29 | // load all framework classes, choose an API level installed 30 | SmaliClassDetailLoader.fromFramework(FRAMEWORK_CLASSES_FOLDER, API_LEVEL).loadAll(scope); 31 | // pick an apk 32 | ZipFile apkFile = new ZipFile(new File(args[0])); 33 | // load all classes, methods, fields and instructions from an apk 34 | // we are using smali as the underlying engine 35 | SmaliClassDetailLoader.fromApkFile(apkFile, API_LEVEL, true).loadAll(scope); 36 | // get the class representation for the MainActivity class in the apk 37 | for (ClassInfo c : scope.getAllClasses()) { 38 | if (!c.isFrameworkClass()) { 39 | System.out.println(c.fullName); 40 | for (MethodInfo m: c.getAllMethods()) { 41 | System.out.println("\t" + m.signature.partialSignature.name); 42 | if (m.insns == null) continue; 43 | for (Instruction i: m.insns) { 44 | System.out.println("\t\t" + i.toString()); 45 | } 46 | } 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/patdroid/Settings.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Mingyuan Xia (http://mxia.me) and contributors 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 | * Contributors: 17 | * Mingyuan Xia 18 | */ 19 | 20 | package patdroid; 21 | 22 | import patdroid.util.Log; 23 | 24 | import java.io.File; 25 | 26 | public class Settings { 27 | /** 28 | * Minimum log level to be printed 29 | */ 30 | public static int logLevel = Log.MODE_REPORT; 31 | /** 32 | * The report mode generates a JSON output 33 | */ 34 | public static boolean enableReportMode = logLevel >= Log.MODE_REPORT; 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/patdroid/core/ClassDetail.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Mingyuan Xia (http://mxia.me) and contributors 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 | * Contributors: 17 | * Mingyuan Xia 18 | * Lu Gong 19 | */ 20 | 21 | package patdroid.core; 22 | 23 | import java.util.*; 24 | 25 | import com.google.common.collect.ImmutableList; 26 | import com.google.common.collect.ImmutableMap; 27 | import com.google.common.collect.ImmutableMultimap; 28 | import com.google.common.collect.Multimap; 29 | import patdroid.util.Log; 30 | 31 | /** 32 | * The details of a class, including its methods, fields, inheritance relationship. 33 | * These details are only available when the class is loaded. 34 | * The details of a class are only supposed to be filled by class loader. 35 | */ 36 | public final class ClassDetail { 37 | public final ClassInfo baseType; 38 | public final ImmutableList interfaces; 39 | public final int accessFlags; 40 | /** 41 | * Primary index, from a full method signature (with return type) to exactly one possible method 42 | */ 43 | public final ImmutableMap methods; 44 | /** 45 | * Secondary index, from a method signature (without return type) to multiple possible methods 46 | */ 47 | public final ImmutableMultimap methodsIndex; 48 | public final ImmutableMap fields; 49 | public final ImmutableMap staticFields; 50 | public final boolean isFrameworkClass; 51 | 52 | public static final class Builder { 53 | ClassInfo baseType; 54 | ImmutableList interfaces; 55 | int accessFlags; 56 | ImmutableMap methods; 57 | ImmutableMultimap methodsIndex; 58 | ImmutableMap fields; 59 | ImmutableMap staticFields; 60 | boolean isFrameworkClass; 61 | public Builder() { 62 | baseType = null; 63 | interfaces = ImmutableList.of(); 64 | accessFlags = 0; 65 | methods = ImmutableMap.of(); 66 | methodsIndex = ImmutableMultimap.of(); 67 | fields = ImmutableMap.of(); 68 | isFrameworkClass = true; 69 | } 70 | 71 | public Builder setBaseType(ClassInfo baseType) { 72 | this.baseType = baseType; 73 | return this; 74 | } 75 | public Builder setInterfaces(List interfaces) { 76 | this.interfaces = ImmutableList.copyOf(interfaces); 77 | return this; 78 | } 79 | public Builder setAccessFlags(int accessFlags) { 80 | this.accessFlags = accessFlags; 81 | return this; 82 | } 83 | public Builder setAllMethods(List methods) { 84 | // build secondary index 85 | ImmutableMultimap.Builder indexBuilder = ImmutableMultimap.builder(); 86 | ImmutableMap.Builder methodsBuilder = ImmutableMap.builder(); 87 | for (MethodInfo method : methods) { 88 | methodsBuilder.put(method.signature, method); 89 | indexBuilder.put(method.signature.partialSignature, method); 90 | } 91 | this.methods = methodsBuilder.build(); 92 | this.methodsIndex = indexBuilder.build(); 93 | return this; 94 | } 95 | public Builder setMethods(Map methods) { 96 | this.methods = ImmutableMap.copyOf(methods); 97 | return this; 98 | } 99 | public Builder setMethodsIndex(Multimap methodsIndex) { 100 | this.methodsIndex = ImmutableMultimap.copyOf(methodsIndex); 101 | return this; 102 | } 103 | public Builder setFields(Map fields) { 104 | this.fields = ImmutableMap.copyOf(fields); 105 | return this; 106 | } 107 | public Builder setStaticFields(Map staticFields) { 108 | this.staticFields = ImmutableMap.copyOf(staticFields); 109 | return this; 110 | } 111 | public Builder setIsFrameworkClass(boolean isFrameworkClass) { 112 | this.isFrameworkClass = isFrameworkClass; 113 | return this; 114 | } 115 | public ClassDetail build() { 116 | return new ClassDetail(this); 117 | } 118 | } 119 | 120 | /** 121 | * A list of classes inherit this class. 122 | * Note that derivedClasses only covers loaded classes. 123 | * Use with care! 124 | */ 125 | public ArrayList derivedClasses = new ArrayList(); 126 | 127 | /** 128 | * Create a details class from a builder 129 | */ 130 | private ClassDetail(Builder builder) { 131 | this.accessFlags = builder.accessFlags; 132 | this.baseType = builder.baseType; 133 | this.interfaces = builder.interfaces; 134 | this.methods = builder.methods; 135 | this.methodsIndex = builder.methodsIndex; 136 | this.fields = builder.fields; 137 | this.staticFields = builder.staticFields; 138 | this.isFrameworkClass = builder.isFrameworkClass; 139 | } 140 | 141 | /** 142 | * Get the type of a non-static field. This functions will look into the base class. 143 | * @param fieldName the field name 144 | * @return the type of the field, or null if the field is not found 145 | */ 146 | public ClassInfo getFieldType(String fieldName) { 147 | ClassInfo r = fields.get(fieldName); 148 | if (r != null) { 149 | return r; 150 | } else { 151 | if (baseType == null) { 152 | Log.warnwarn("failed to find field: "+ fieldName); 153 | return null; 154 | } 155 | return baseType.mutableDetail.getFieldType(fieldName); 156 | } 157 | } 158 | 159 | /** 160 | * Get the type of a static field. This functions will look into the base class. 161 | * @param fieldName the field name 162 | * @return the type of the field, or null if the field is not found 163 | */ 164 | public ClassInfo getStaticFieldType(String fieldName) { 165 | ClassInfo r = staticFields.get(fieldName); 166 | if (r != null) { 167 | return r; 168 | } else { 169 | if (baseType == null) { 170 | Log.warnwarn("failed to find static field: "+ fieldName); 171 | return null; 172 | } 173 | return baseType.mutableDetail.getStaticFieldType(fieldName); 174 | } 175 | } 176 | 177 | /** 178 | * Find a concrete method given a method prototype 179 | * 180 | * @param signature The signature of a method 181 | * @return The method matching the prototype in the class 182 | */ 183 | public MethodInfo findMethod(FullMethodSignature signature) { 184 | Deque q = new ArrayDeque(); 185 | q.push(this); 186 | while (!q.isEmpty()) { 187 | ClassDetail detail = q.pop(); 188 | MethodInfo mi = detail.methods.get(signature); 189 | if (mi != null) { 190 | return mi; 191 | } 192 | if (detail.baseType != null) 193 | q.push(detail.baseType.mutableDetail); 194 | for (ClassInfo i : detail.interfaces) 195 | q.push(i.mutableDetail); 196 | } 197 | return null; 198 | } 199 | 200 | /** 201 | * Find all concrete methods given a name 202 | * @param name The method name 203 | * @return All rebound methods 204 | */ 205 | public MethodInfo[] findMethods(String name) { 206 | ArrayList result = new ArrayList(); 207 | ArrayDeque q = new ArrayDeque(); 208 | q.push(this); 209 | while (!q.isEmpty()) { 210 | ClassDetail detail = q.pop(); 211 | MethodInfo[] mis = detail.findMethodsHere(name); 212 | 213 | for (MethodInfo mi : mis) { 214 | boolean overrided = false; 215 | for (MethodInfo mi0 : result) { 216 | // N.B. mi and mi0 may belong to different super class or 217 | // interfaces that have no inheritance relationship 218 | if (mi0.canOverride(mi)) { 219 | overrided = true; 220 | break; 221 | } 222 | } 223 | if (!overrided) 224 | result.add(mi); 225 | } 226 | 227 | if (detail.baseType != null) 228 | q.push(detail.baseType.mutableDetail); 229 | for (ClassInfo i : detail.interfaces) 230 | q.push(i.mutableDetail); 231 | } 232 | return result.toArray(new MethodInfo[result.size()]); 233 | } 234 | 235 | /** 236 | * Find all methods that is only in the declaration of this class 237 | * @param name The method name 238 | * @return The real methods 239 | */ 240 | public MethodInfo[] findMethodsHere(String name) { 241 | ArrayList result = new ArrayList(); 242 | for (MethodInfo m : methods.values()) { 243 | if (m.signature.partialSignature.name.equals(name)) { 244 | result.add(m); 245 | } 246 | } 247 | return result.toArray(new MethodInfo[result.size()]); 248 | } 249 | 250 | /** 251 | * TypeA is convertible to TypeB if and only if TypeB is an (indirect) 252 | * base type or an (indirect) interface of TypeA. 253 | * 254 | * @param type typeB 255 | * @return if this class can be converted to the other. 256 | */ 257 | public final boolean isConvertibleTo(ClassInfo type) { 258 | ClassDetail that = type.mutableDetail; 259 | if (this == that) { 260 | return true; 261 | } 262 | if (baseType != null && baseType.isConvertibleTo(type)) { 263 | return true; 264 | } 265 | for (ClassInfo c : interfaces) { 266 | if (c.isConvertibleTo(type)) { 267 | return true; 268 | } 269 | } 270 | return false; 271 | // derivedClasses is not that reliable 272 | // return type.derivedClasses.contains(this); 273 | } 274 | 275 | public ClassDetail changeBaseType(ClassInfo baseType) { 276 | Builder builder = new Builder(); 277 | ClassDetail details = builder.setBaseType(baseType) 278 | .setInterfaces(interfaces) 279 | .setAccessFlags(accessFlags) 280 | .setMethods(methods) 281 | .setMethodsIndex(methodsIndex) 282 | .setFields(fields) 283 | .setStaticFields(staticFields) 284 | .setIsFrameworkClass(isFrameworkClass) 285 | .build(); 286 | details.derivedClasses = this.derivedClasses; 287 | return details; 288 | } 289 | 290 | public final void updateDerivedClasses(ClassInfo ci) { 291 | ArrayDeque a = new ArrayDeque(); 292 | if (baseType != null) 293 | a.add(baseType.mutableDetail); 294 | for (ClassInfo i : interfaces) { 295 | a.add(i.mutableDetail); 296 | } 297 | while (!a.isEmpty()) { 298 | ClassDetail detail = a.pop(); 299 | detail.derivedClasses.add(ci); 300 | detail.derivedClasses.addAll(derivedClasses); 301 | if (detail.baseType != null) 302 | a.add(detail.baseType.mutableDetail); 303 | for (ClassInfo i : detail.interfaces) { 304 | a.add(i.mutableDetail); 305 | } 306 | } 307 | } 308 | 309 | public final void removeDerivedClasses(ClassInfo ci) { 310 | ArrayDeque a = new ArrayDeque(); 311 | if (baseType != null) 312 | a.add(baseType.mutableDetail); 313 | for (ClassInfo i : interfaces) { 314 | a.add(i.mutableDetail); 315 | } 316 | while (!a.isEmpty()) { 317 | ClassDetail detail = a.pop(); 318 | detail.derivedClasses.remove(ci); 319 | detail.derivedClasses.removeAll(derivedClasses); 320 | if (detail.baseType != null) 321 | a.add(detail.baseType.mutableDetail); 322 | for (ClassInfo i : detail.interfaces) { 323 | a.add(i.mutableDetail); 324 | } 325 | } 326 | } 327 | } 328 | -------------------------------------------------------------------------------- /src/main/java/patdroid/core/ClassDetailLoader.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Mingyuan Xia (http://mxia.me) and contributors 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 | * Contributors: 17 | * Mingyuan Xia 18 | */ 19 | 20 | package patdroid.core; 21 | 22 | import com.google.common.collect.ImmutableList; 23 | import patdroid.util.Log; 24 | 25 | import java.util.HashMap; 26 | 27 | /** 28 | * The base ClassDetail loader. Itself does nothing but throwing an exception. 29 | * Any loader extending it should do some work. 30 | */ 31 | public class ClassDetailLoader { 32 | private static final ClassNotFoundException x_x = 33 | new ClassNotFoundException("the bare ClassDetail loader does not load anything"); 34 | public void load(ClassInfo ci) throws ClassNotFoundException, 35 | ExceptionInInitializerError, NoClassDefFoundError 36 | { throw x_x; } 37 | 38 | /** 39 | * Set the details of the class, usually used only by class loader 40 | *

41 | * Note: this might start class loading if the class is not loaded yet 42 | * @param type the owner type 43 | * @param detail the detailed info about the class 44 | */ 45 | protected static void setDetail(ClassInfo type, ClassDetail detail) { 46 | Log.warnwarn(type.mutableDetail == null, "class is already loaded" + type); 47 | type.mutableDetail = detail; 48 | detail.updateDerivedClasses(type); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/patdroid/core/ClassInfo.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Mingyuan Xia (http://mxia.me) and contributors 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 | * Contributors: 17 | * Mingyuan Xia 18 | * Lu Gong 19 | */ 20 | 21 | package patdroid.core; 22 | 23 | import java.lang.reflect.Modifier; 24 | 25 | import com.google.common.collect.ImmutableCollection; 26 | import com.google.common.collect.ImmutableList; 27 | import com.google.common.collect.ImmutableMap; 28 | import patdroid.util.Log; 29 | 30 | import static com.google.common.base.Preconditions.checkState; 31 | 32 | /** 33 | * The class representation. Each class is uniquely identified by its full name. 34 | * So given a class full name, there is exactly one ClassInfo representing it. 35 | * ClassInfo works in a late-bind manner with ClassDetail. 36 | * A ClassInfo could just refer to a type without any details about its methods, fields, etc. 37 | * Then later on, when the class details become available, a ClassDetail iobject is created and 38 | * attached to the ClassInfo. 39 | *

40 | * ClassInfos are obtained by find-series functions not created by constructors. 41 | */ 42 | public final class ClassInfo { 43 | private static final ClassDetail MISSING_DETAIL = new ClassDetail.Builder().build(); 44 | public final FullMethodSignature STATIC_INITIALIZER; 45 | public final FullMethodSignature DEFAULT_CONSTRUCTOR; 46 | 47 | public final Scope scope; 48 | public final String fullName; 49 | public ClassDetail mutableDetail = MISSING_DETAIL; 50 | 51 | /** 52 | * @param scope the scope that this ClassInfo belongs to 53 | * @param fullName the full name of the class 54 | */ 55 | public ClassInfo(Scope scope, String fullName) { 56 | this.scope = scope; 57 | this.fullName = fullName; 58 | this.DEFAULT_CONSTRUCTOR = new FullMethodSignature(scope.primitiveVoid, MethodInfo.CONSTRUCTOR, this); 59 | this.STATIC_INITIALIZER = new FullMethodSignature(scope.primitiveVoid, MethodInfo.STATIC_INITIALIZER); 60 | } 61 | 62 | /** 63 | * A framework class is a class that is not found in the apk being parsed 64 | *

65 | * Note: this might start class loading if the class is not loaded yet 66 | * @return if the class is a framework class 67 | */ 68 | public boolean isFrameworkClass() { 69 | return mutableDetail.isFrameworkClass; 70 | } 71 | 72 | /** 73 | * Sometimes the apk has missing classes. A missing class is not 74 | * a framework class and cannot be found in the apk 75 | * @return if this class is missing 76 | */ 77 | public boolean isMissing() { 78 | return mutableDetail == MISSING_DETAIL; 79 | } 80 | 81 | /** 82 | * Get the type of a non-static field. This functions will look into the base class. 83 | *

84 | * Note: this might start class loading if the class is not loaded yet 85 | * @param fieldName the name of the field. 86 | * @return the type, or null if not found or the class is missing 87 | */ 88 | public ClassInfo getFieldType(String fieldName) { 89 | return mutableDetail.getFieldType(fieldName); 90 | } 91 | 92 | /** 93 | * Get the type of a static field. This functions might look into its base class. 94 | *

95 | * Note: this might start class loading if the class is not loaded yet 96 | * @param fieldName the name of the static field 97 | * @return the type of the static field, or null if not found or the class is missing 98 | */ 99 | public ClassInfo getStaticFieldType(String fieldName) { 100 | return mutableDetail.getStaticFieldType(fieldName); 101 | } 102 | 103 | /** 104 | * Get all the fields declared in this class. 105 | *

106 | * Note: this might start class loading if the class is not loaded yet 107 | * @return a key-value store mapping field name to their types 108 | */ 109 | public ImmutableMap getAllFieldsHere() { 110 | return mutableDetail.fields; 111 | } 112 | 113 | /** 114 | * Get all the static fields declared in this class. 115 | *

116 | * Note: this might start class loading if the class is not loaded yet 117 | * @return a key-value store mapping static field name to their types 118 | */ 119 | public ImmutableMap getAllStaticFieldsHere() { 120 | return mutableDetail.staticFields; 121 | } 122 | 123 | /** 124 | * 125 | * @return all methods in the class 126 | */ 127 | public ImmutableCollection getAllMethods() { 128 | return mutableDetail.methods.values(); 129 | } 130 | 131 | /** 132 | * Find a method declared in this class 133 | *

134 | * Note: this might start class loading if the class is not loaded yet 135 | * @param signature the method signature 136 | * @return the method in this class, or null if not found or the class is missing 137 | */ 138 | public MethodInfo findMethodHere(FullMethodSignature signature) { 139 | return mutableDetail.methods.get(signature); 140 | } 141 | 142 | /** 143 | * Find all methods that have the give name and are declared in this class 144 | *

145 | * Note: this might start class loading if the class is not loaded yet 146 | * @param name the method name 147 | * @return an array of methods, or null if the class is missing. 148 | * An empty array will be returned in case of not finding any method 149 | */ 150 | public MethodInfo[] findMethodsHere(String name) { 151 | return mutableDetail.findMethodsHere(name); 152 | } 153 | 154 | /** 155 | * Find all methods that have the give name. This might need to look into base classes 156 | *

157 | * Note: this might start class loading if the class is not loaded yet 158 | * @param name the method name 159 | * @return an array of methods, or null if the class is missing. 160 | * An empty array will be returned in case of not finding any method 161 | */ 162 | public MethodInfo[] findMethods(String name) { 163 | return mutableDetail.findMethods(name); 164 | } 165 | 166 | /** 167 | * Find a method with given function prototype. This might need to look into base classes 168 | *

169 | * Note: this might start class loading if the class is not loaded yet 170 | * @param signature the method signature 171 | * @return the method representation, or null if not found or the class is missing 172 | */ 173 | public MethodInfo findMethod(FullMethodSignature signature) { 174 | return mutableDetail.findMethod(signature); 175 | } 176 | 177 | /** 178 | * TypeA is convertible to TypeB if and only if TypeB is an indirect 179 | * base type or an indirect interface of TypeA. 180 | *

181 | * Note: this might start class loading if the class is not loaded yet 182 | * @param type type B 183 | * @return if this class can be converted to the other. 184 | */ 185 | public boolean isConvertibleTo(ClassInfo type) { 186 | if (type.isPrimitive()) { 187 | return (type == scope.primitiveVoid || isPrimitive()); 188 | } else { 189 | return mutableDetail.isConvertibleTo(type); 190 | } 191 | } 192 | 193 | @Override 194 | public String toString() { 195 | return fullName; 196 | } 197 | 198 | /** 199 | * @return if this class is an array type 200 | */ 201 | public boolean isArray() { 202 | return fullName.startsWith("["); 203 | } 204 | 205 | /** 206 | * Return the element type given this class as an array type. 207 | * @return the element type 208 | */ 209 | public ClassInfo getElementClass() { 210 | checkState(isArray(), "Try getting the element class of a non-array class " + this); 211 | final char first = fullName.charAt(1); 212 | switch (first) { 213 | case 'C': return scope.primitiveChar; 214 | case 'I': return scope.primitiveInt; 215 | case 'B': return scope.primitiveByte; 216 | case 'Z': return scope.primitiveBoolean; 217 | case 'F': return scope.primitiveFloat; 218 | case 'D': return scope.primitiveDouble; 219 | case 'S': return scope.primitiveShort; 220 | case 'J': return scope.primitiveLong; 221 | case 'V': return scope.primitiveVoid; 222 | case 'L': return scope.findOrCreateClass(fullName.substring(2, fullName.length() - 1)); 223 | case '[': return scope.findOrCreateClass(fullName.substring(1)); 224 | default: 225 | Log.err("unknown element type for:" + fullName); 226 | return null; 227 | } 228 | } 229 | 230 | /** 231 | * 232 | * Note: this might cause class loading if the class is not loaded yet 233 | * @return the base type, or null if this class is java.lang.Object 234 | */ 235 | public ClassInfo getBaseType() { 236 | return mutableDetail.baseType; 237 | } 238 | 239 | /** 240 | * Get the interfaces that the current class implements 241 | * @return interfaces 242 | */ 243 | public ImmutableList getInterfaces() { return mutableDetail.interfaces; } 244 | 245 | /** 246 | * Change the super class of this class to a new super class, the 247 | * derivedClasses will be updated accordingly. 248 | * @param baseType new super class for this class 249 | */ 250 | public void setBaseType(ClassInfo baseType) { 251 | ClassDetail origDetails = mutableDetail; 252 | origDetails.removeDerivedClasses(this); 253 | mutableDetail = origDetails.changeBaseType(baseType); 254 | mutableDetail.updateDerivedClasses(this); 255 | } 256 | 257 | /** 258 | * @return if this class is an inner class 259 | */ 260 | public boolean isInnerClass() { 261 | return fullName.lastIndexOf('$') != -1; 262 | } 263 | 264 | /** 265 | * @return the outer class 266 | */ 267 | public ClassInfo getOuterClass() { 268 | checkState(isInnerClass(), "Try getting the outer class from a non-inner class" + this); 269 | return scope.findOrCreateClass(fullName.substring(0, fullName.lastIndexOf('$'))); 270 | } 271 | 272 | /** 273 | * @return true if the class is a primitive type 274 | */ 275 | public boolean isPrimitive() { 276 | return scope.primitives.contains(this); 277 | } 278 | 279 | /** 280 | * @return if the class is final 281 | */ 282 | public boolean isFinal() { 283 | return Modifier.isFinal(mutableDetail.accessFlags); 284 | } 285 | 286 | /** 287 | * @return if the class is an interface 288 | */ 289 | public boolean isInterface() { 290 | return Modifier.isInterface(mutableDetail.accessFlags); 291 | } 292 | 293 | public boolean isAbstract() { 294 | return Modifier.isAbstract(mutableDetail.accessFlags); 295 | } 296 | 297 | /** 298 | * Get the default constructor 299 | *

300 | * Note: this might start class loading if the class is not loaded yet 301 | * @return the default constructor, or null if not found 302 | */ 303 | public MethodInfo getDefaultConstructor() { 304 | return findMethodHere(DEFAULT_CONSTRUCTOR); 305 | } 306 | 307 | /** 308 | * Find the static initializer method of the class 309 | *

310 | * Note: this might start class loading if the class is not loaded yet 311 | * @return the static initializer or null if not found 312 | */ 313 | public MethodInfo getStaticInitializer() { 314 | return findMethod(STATIC_INITIALIZER); 315 | } 316 | 317 | /** 318 | * Get the short name of the class. The short name is the part after the last 319 | * '.' in the full name of the class 320 | * @return the short name 321 | */ 322 | public String getShortName() { 323 | final int idx = fullName.lastIndexOf('.'); 324 | if (idx == -1) { 325 | return fullName; 326 | } else { 327 | return fullName.substring(idx+1, fullName.length()); 328 | } 329 | } 330 | 331 | /** 332 | * An almost final class has no derived classes in the current class tree 333 | * @return if a class is "almost final" 334 | */ 335 | public boolean isAlmostFinal() { 336 | return mutableDetail.derivedClasses.isEmpty(); 337 | } 338 | } 339 | -------------------------------------------------------------------------------- /src/main/java/patdroid/core/FieldInfo.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Mingyuan Xia (http://mxia.me) and contributors 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 | * Contributors: 17 | * Mingyuan Xia 18 | * Lu Gong 19 | */ 20 | 21 | package patdroid.core; 22 | 23 | import com.google.common.base.Objects; 24 | import com.google.common.collect.ImmutableMap; 25 | import patdroid.util.Log; 26 | 27 | /** 28 | * The representation of a field in a class 29 | */ 30 | public final class FieldInfo { 31 | /** 32 | * The owning class of this filed 33 | */ 34 | public final ClassInfo owner; 35 | /** 36 | * The name of the field 37 | */ 38 | public final String fieldName; 39 | 40 | public FieldInfo(ClassInfo owner, String fieldName) { 41 | this.owner = owner; 42 | this.fieldName = fieldName; 43 | } 44 | 45 | @Override 46 | public boolean equals(Object o) { 47 | if (!(o instanceof FieldInfo)) { 48 | return false; 49 | } 50 | final FieldInfo that = (FieldInfo)o; 51 | return owner == that.owner && this.fieldName.equals(that.fieldName); 52 | } 53 | 54 | @Override 55 | public int hashCode() { 56 | return Objects.hashCode(owner, fieldName); 57 | } 58 | 59 | @Override 60 | public String toString() { 61 | return owner.toString() + "." + fieldName; 62 | } 63 | 64 | public final ClassInfo getFieldType() { 65 | return owner.getFieldType(fieldName); 66 | } 67 | 68 | public boolean isValid() { 69 | return owner.getFieldType(fieldName) != null; 70 | } 71 | 72 | /* 73 | * Bind to the real owner of the field, which may not be loaded at 74 | * decompiling stage, thus late bind is needed. 75 | */ 76 | public FieldInfo bind() { 77 | ClassInfo type = owner; 78 | while (true) { 79 | ImmutableMap fields = type.getAllFieldsHere(); 80 | if (fields != null && fields.containsKey(fieldName)) 81 | return new FieldInfo(type, fieldName); 82 | final ClassInfo baseType = type.getBaseType(); 83 | if (baseType == null) { 84 | Log.warn("field bind failed"); 85 | return new FieldInfo(type, fieldName); 86 | } 87 | type = baseType; 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/main/java/patdroid/core/FullMethodSignature.java: -------------------------------------------------------------------------------- 1 | package patdroid.core; 2 | 3 | import com.google.common.base.Objects; 4 | 5 | import java.util.List; 6 | 7 | public class FullMethodSignature { 8 | /** 9 | * The return type 10 | *

if the method is a constructor or a static initializer, this will always be void

11 | */ 12 | public final ClassInfo returnType; 13 | /** 14 | * A partial signature without return type 15 | */ 16 | public final MethodSignature partialSignature; 17 | 18 | public FullMethodSignature(ClassInfo returnType, MethodSignature partialSignature) { 19 | this.returnType = returnType; 20 | this.partialSignature = partialSignature; 21 | } 22 | 23 | public FullMethodSignature(ClassInfo returnType, String name, List paramTypes) { 24 | this.returnType = returnType; 25 | this.partialSignature = new MethodSignature(name, paramTypes); 26 | } 27 | 28 | public FullMethodSignature(ClassInfo returnType, String name, ClassInfo... paramTypes) { 29 | this.returnType = returnType; 30 | this.partialSignature = MethodSignature.of(name, paramTypes); 31 | } 32 | 33 | @Override 34 | public int hashCode() { 35 | return Objects.hashCode(returnType, partialSignature); 36 | } 37 | 38 | @Override 39 | public boolean equals(Object o) { 40 | if (!(o instanceof FullMethodSignature)) { 41 | return false; 42 | } 43 | FullMethodSignature ms = (FullMethodSignature) o; 44 | return returnType == ms.returnType && partialSignature.equals(ms.partialSignature); 45 | } 46 | 47 | @Override 48 | public String toString() { 49 | return partialSignature + ":" + returnType; 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/patdroid/core/MethodInfo.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Mingyuan Xia (http://mxia.me) and contributors 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 | * Contributors: 17 | * Mingyuan Xia 18 | * Lu Gong 19 | */ 20 | 21 | package patdroid.core; 22 | 23 | import patdroid.dalvik.Instruction; 24 | 25 | import java.lang.reflect.Method; 26 | import java.lang.reflect.Modifier; 27 | import java.util.Arrays; 28 | 29 | /** 30 | * The method representation. 31 | *

32 | * This class contains an immutable function signature 33 | * and mutable info such as local variables, instructions 34 | * and analysis-specific tags 35 | *

36 | *

Constructors have a special name "<init>"

37 | *

The static initializer has a special name "<clinit>"

38 | */ 39 | public final class MethodInfo { 40 | public static final String STATIC_INITIALIZER = ""; 41 | public static final String CONSTRUCTOR = ""; 42 | 43 | /** 44 | * The type containing this method. 45 | */ 46 | public final ClassInfo type; 47 | /** 48 | * The signature of the method 49 | */ 50 | public final FullMethodSignature signature; 51 | /** 52 | * The modifiers, in the format of {@link java.lang.reflect.Modifier} 53 | */ 54 | public final int modifiers; 55 | /** 56 | * Whether the method is a compiler-generated method 57 | */ 58 | public final boolean isSynthetic; 59 | 60 | /** 61 | * Instruction streamline 62 | */ 63 | public Instruction[] insns; 64 | /** 65 | * Try blocks 66 | */ 67 | public TryBlockInfo[] tbs; 68 | /** 69 | * Anything that should be attached to the method, no guarantee of thread-safe update of this field 70 | */ 71 | public Object extra; 72 | 73 | /** 74 | * Create a method info that is part of a class 75 | * @param type the class 76 | * @param signature the full method signature 77 | * @param accessFlags the access flags 78 | */ 79 | public MethodInfo(ClassInfo type, FullMethodSignature signature, int accessFlags, boolean isSynthetic) { 80 | this.type = type; 81 | this.signature = signature; 82 | this.modifiers = accessFlags; 83 | this.isSynthetic = isSynthetic; 84 | } 85 | 86 | /** 87 | * Get the method in the superclass/interfaces that is overridden by the current method. 88 | * If the current method is non-virtual, the result will be null. 89 | * If the current method is not overriding any method from its parent classes, the result will 90 | * be null too. 91 | * @return the overriding method or null if none 92 | */ 93 | public MethodInfo getOverridingMethod() { 94 | if (this.isConstructor() || this.isStatic()) return null; 95 | final ClassInfo baseType = this.type.getBaseType(); 96 | MethodInfo matching; 97 | if (baseType != null) { 98 | matching = baseType.findMethod(signature); 99 | if (matching != null) return matching; 100 | } 101 | for (ClassInfo intf: this.type.getInterfaces()) { 102 | matching = intf.findMethod(signature); 103 | if (matching != null) return matching; 104 | } 105 | return null; 106 | } 107 | 108 | @Override 109 | public String toString() { 110 | String s = (type == null ? "" : type.toString()); 111 | return s + "/" + signature; 112 | } 113 | 114 | /** 115 | * Check if this method can override another 116 | * (same signature and classes are inherited) 117 | * @param m another method 118 | * @return true if this method can override the other one 119 | */ 120 | public boolean canOverride(MethodInfo m) { 121 | return this == m || 122 | (this.type.isConvertibleTo(m.type) && this.signature.equals(m.signature)); 123 | } 124 | 125 | /** 126 | * @return true if the method is static 127 | */ 128 | public boolean isStatic() { 129 | return Modifier.isStatic(modifiers); 130 | } 131 | 132 | /** 133 | * @return true if the method is native 134 | */ 135 | public boolean isNative() { 136 | return Modifier.isNative(modifiers); 137 | } 138 | 139 | /** 140 | * @return true if the method is a constructor 141 | */ 142 | public boolean isConstructor() { 143 | return signature.partialSignature.name.equals(CONSTRUCTOR); 144 | } 145 | 146 | /** 147 | * @return true if the method is abstract 148 | */ 149 | public boolean isAbstract() { 150 | return Modifier.isAbstract(modifiers); 151 | } 152 | 153 | /** 154 | * @return true if the method is final 155 | */ 156 | public boolean isFinal() { 157 | return Modifier.isFinal(modifiers); 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /src/main/java/patdroid/core/MethodSignature.java: -------------------------------------------------------------------------------- 1 | package patdroid.core; 2 | 3 | import com.google.common.base.Objects; 4 | import com.google.common.collect.ImmutableList; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * Signature of a method. 10 | * 11 | * A signature contains the name and parameter types (no type, access flags and return type). 12 | */ 13 | public class MethodSignature { 14 | public final String name; 15 | public final ImmutableList paramTypes; 16 | 17 | public MethodSignature(String name, List paramTypes) { 18 | this.name = name; 19 | this.paramTypes = ImmutableList.copyOf(paramTypes); 20 | } 21 | 22 | public static MethodSignature of(String name, ClassInfo... paramTypes) { 23 | return new MethodSignature(name, ImmutableList.copyOf(paramTypes)); 24 | } 25 | 26 | public static MethodSignature of(Scope scope, String name, Class... paramTypes) { 27 | return new MethodSignature(name, scope.findOrCreateClasses(paramTypes)); 28 | } 29 | 30 | @Override 31 | public int hashCode() { 32 | return Objects.hashCode(name, paramTypes); 33 | } 34 | 35 | @Override 36 | public boolean equals(Object o) { 37 | if (!(o instanceof MethodSignature)) { 38 | return false; 39 | } 40 | MethodSignature ms = (MethodSignature) o; 41 | return name.equals(ms.name) && paramTypes.equals(ms.paramTypes); 42 | } 43 | 44 | @Override 45 | public String toString() { 46 | return name + paramTypes; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/patdroid/core/PrimitiveInfo.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Mingyuan Xia (http://mxia.me) and contributors 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 | * Contributors: 17 | * Mingyuan Xia 18 | */ 19 | 20 | package patdroid.core; 21 | 22 | import com.google.common.base.Objects; 23 | import patdroid.util.Log; 24 | 25 | import static com.google.common.base.Preconditions.checkState; 26 | 27 | /** 28 | * Low-level primitive type value (immutable). 29 | */ 30 | public final class PrimitiveInfo { 31 | 32 | /** 33 | * The class object of the value type 34 | */ 35 | public final ClassInfo type; 36 | /** 37 | * 64-bit store for primitive types 38 | */ 39 | public final int low32; 40 | public final int high32; 41 | 42 | private PrimitiveInfo(ClassInfo type, int low32, int high32) { 43 | this.type = type; 44 | this.low32 = low32; 45 | this.high32 = high32; 46 | } 47 | 48 | private PrimitiveInfo(ClassInfo type, long l) { 49 | this.type = type; 50 | this.low32 = (int)l; 51 | this.high32 = (int)(l >> 32); 52 | } 53 | 54 | public static PrimitiveInfo fromInt(Scope scope, int value) { 55 | return new PrimitiveInfo(scope.primitiveInt, value, 0); 56 | } 57 | 58 | public static PrimitiveInfo fromLong(Scope scope, long value) { 59 | return new PrimitiveInfo(scope.primitiveLong, value); 60 | } 61 | 62 | public static PrimitiveInfo fromDouble(Scope scope, double value) { 63 | return new PrimitiveInfo(scope.primitiveDouble, Double.doubleToLongBits(value)); 64 | } 65 | 66 | public static PrimitiveInfo fromFloat(Scope scope, float value) { 67 | return new PrimitiveInfo(scope.primitiveFloat, Float.floatToIntBits(value), 0); 68 | } 69 | 70 | public static PrimitiveInfo fromBoolean(Scope scope, boolean value) { 71 | return new PrimitiveInfo(scope.primitiveBoolean, value ? 1 : 0, 0); 72 | } 73 | 74 | /** 75 | * Parse a Java built-in object 76 | * 77 | * @param o an arbitrary object of primitive boxing type 78 | * @return a PrimitiveInfo 79 | */ 80 | public static PrimitiveInfo fromObject(Scope scope, Object o) { 81 | if (o instanceof Integer) { 82 | return fromInt(scope, (Integer) o); 83 | } else if (o instanceof Short) { 84 | return fromInt(scope, (Short) o); 85 | } else if (o instanceof Byte) { 86 | return fromInt(scope, (Byte) o); 87 | } else if (o instanceof Long) { 88 | return fromLong(scope, (Long) o); 89 | } else if (o instanceof Float) { 90 | return fromFloat(scope, (Float) o); 91 | } else if (o instanceof Double) { 92 | return fromDouble(scope, (Double) o); 93 | } else if (o instanceof Boolean) { 94 | return fromBoolean(scope, (Boolean) o); 95 | } else { 96 | Log.err("unsupported object to ValueInfo" + o.getClass().toString()); 97 | return null; 98 | } 99 | } 100 | 101 | private long getLong() { 102 | return (((long)high32) << 32) | (low32 & 0xffffffffL); 103 | } 104 | 105 | public final int intValue() { 106 | checkState(isInteger(), "invalid type"); 107 | return low32; 108 | } 109 | 110 | public final long longValue() { 111 | checkState(isLong(), "invalid type"); 112 | return getLong(); 113 | } 114 | 115 | public final double doubleValue() { 116 | checkState(isDouble(), "invalid type"); 117 | return Double.longBitsToDouble(getLong()); 118 | } 119 | 120 | public final float floatValue() { 121 | checkState(isFloat(), "invalid type"); 122 | return Float.intBitsToFloat(low32); 123 | } 124 | 125 | public final boolean booleanValue() { 126 | checkState(isBoolean(), "invalid type"); 127 | return low32 == 1; 128 | } 129 | 130 | public PrimitiveInfo unsafeCastTo(ClassInfo targetType) { 131 | checkState(targetType.isPrimitive(), "must cast to a primitive type"); 132 | if (targetType == type.scope.primitiveChar || 133 | targetType == type.scope.primitiveShort || 134 | targetType == type.scope.primitiveByte) { 135 | targetType = type.scope.primitiveInt; 136 | } 137 | return new PrimitiveInfo(targetType, low32, high32); 138 | } 139 | 140 | public final PrimitiveInfo castTo(ClassInfo targetType) { 141 | checkState(targetType.isPrimitive(), "must cast to a primitive type"); 142 | checkState(targetType.scope == type.scope, "must be in the same scope"); 143 | PrimitiveInfo v = null; 144 | if (targetType == type.scope.primitiveInt) { 145 | int val = 0; 146 | if (isLong()) { 147 | val = (int) longValue(); 148 | } else if (isInteger()) { 149 | val = intValue(); 150 | } else if (isBoolean()) { 151 | val = booleanValue() ? 1 : 0; 152 | } else if (isFloat()) { 153 | val = (int) floatValue(); 154 | } else if (isDouble()) { 155 | val = (int) doubleValue(); 156 | } 157 | v = fromInt(type.scope, val); 158 | } else if (targetType == type.scope.primitiveLong) { 159 | long val = 0l; 160 | if (isLong()) { 161 | val = (long) longValue(); 162 | } else if (isInteger()) { 163 | val = (long) intValue(); 164 | } else if (isBoolean()) { 165 | val = booleanValue() ? 1 : 0; 166 | } else if (isFloat()) { 167 | val = (long) floatValue(); 168 | } else if (isDouble()) { 169 | val = (long) doubleValue(); 170 | } 171 | v = fromLong(type.scope, val); 172 | } else if (targetType == type.scope.primitiveFloat) { 173 | float val = 0f; 174 | if (isLong()) { 175 | val = (float) longValue(); 176 | } else if (isInteger()) { 177 | val = (float) intValue(); 178 | } else if (isBoolean()) { 179 | val = booleanValue() ? 1 : 0; 180 | } else if (isFloat()) { 181 | val = (float) floatValue(); 182 | } else if (isDouble()) { 183 | val = (float) doubleValue(); 184 | } 185 | v = fromFloat(type.scope, val); 186 | } else if (targetType == type.scope.primitiveDouble) { 187 | double val = 0.0; 188 | if (isLong()) { 189 | val = (double) longValue(); 190 | } else if (isInteger()) { 191 | val = (double) intValue(); 192 | } else if (isBoolean()) { 193 | val = booleanValue() ? 1 : 0; 194 | } else if (isFloat()) { 195 | val = (double) floatValue(); 196 | } else if (isDouble()) { 197 | val = (double) doubleValue(); 198 | } 199 | v = fromDouble(type.scope, val); 200 | } else { // short, boolean, char, byte 201 | v = new PrimitiveInfo(type.scope.primitiveInt, this.low32); 202 | } 203 | return v; 204 | } 205 | 206 | public static PrimitiveInfo twoIntsToDouble(Scope scope, int vlow, int vhigh) { 207 | return new PrimitiveInfo(scope.primitiveDouble, vlow, vhigh); 208 | } 209 | 210 | public static PrimitiveInfo twoIntsToLong(Scope scope, int vlow, int vhigh) { 211 | return new PrimitiveInfo(scope.primitiveLong, vlow, vhigh); 212 | } 213 | 214 | public final boolean isInteger() { 215 | return type == type.scope.primitiveInt; 216 | } 217 | 218 | public final boolean isLong() { 219 | return type == type.scope.primitiveLong; 220 | } 221 | 222 | public final boolean isDouble() { 223 | return type == type.scope.primitiveDouble; 224 | } 225 | 226 | public final boolean isFloat() { 227 | return type == type.scope.primitiveFloat; 228 | } 229 | 230 | public final boolean isBoolean() { 231 | return type == type.scope.primitiveBoolean; 232 | } 233 | 234 | public final boolean isZero() { 235 | return low32 == 0 && high32 == 0; 236 | } 237 | /** 238 | * Check if two value are of the same type 239 | * 240 | * @param that another value info 241 | * @return true if the two values are of same type 242 | */ 243 | public final boolean isSameType(PrimitiveInfo that) { 244 | return this.type == that.type; 245 | } 246 | 247 | @Override 248 | public final String toString() { 249 | String prefix = ""; 250 | if (isInteger()) { 251 | return prefix + intValue(); 252 | } else if (isLong()) { 253 | return prefix + longValue()+"l"; 254 | } else if (isBoolean()) { 255 | return prefix + booleanValue(); 256 | } else if (isFloat()) { 257 | return prefix + floatValue()+"f"; 258 | } else if (isDouble()) { 259 | return prefix + doubleValue(); 260 | } else { 261 | Log.err("unsupported ValueInfo type " + type); 262 | return ""; 263 | } 264 | } 265 | 266 | public String castToString() { 267 | if (isInteger()) { 268 | return ""+intValue(); 269 | } else if (isLong()) { 270 | return ""+longValue(); 271 | } else if (isBoolean()) { 272 | return ""+booleanValue(); 273 | } else if (isFloat()) { 274 | return ""+floatValue(); 275 | } else if (isDouble()) { 276 | return ""+doubleValue(); 277 | } else { 278 | Log.err("unsupported ValueInfo type " + type); 279 | return ""; 280 | } 281 | } 282 | 283 | @Override 284 | public int hashCode() { 285 | return Objects.hashCode(type, low32, high32); 286 | } 287 | 288 | @Override 289 | public boolean equals(Object o) { 290 | if (!(o instanceof PrimitiveInfo)) { 291 | return false; 292 | } 293 | final PrimitiveInfo v = (PrimitiveInfo) o; 294 | return v.type == type && v.low32 == low32 && v.high32 == high32; 295 | } 296 | } 297 | -------------------------------------------------------------------------------- /src/main/java/patdroid/core/ReflectionClassDetailLoader.java: -------------------------------------------------------------------------------- 1 | package patdroid.core; 2 | 3 | import com.google.common.collect.ImmutableList; 4 | 5 | import java.lang.reflect.Constructor; 6 | import java.lang.reflect.Field; 7 | import java.lang.reflect.Method; 8 | import java.lang.reflect.Modifier; 9 | import java.util.ArrayList; 10 | import java.util.HashMap; 11 | 12 | /** 13 | * The detail of the class is available in the current Java environment. 14 | * Load that with standard Java reflection 15 | */ 16 | @Deprecated 17 | public class ReflectionClassDetailLoader extends ClassDetailLoader { 18 | private final Scope scope; 19 | 20 | public ReflectionClassDetailLoader(Scope scope) { 21 | this.scope = scope; 22 | } 23 | 24 | @Override 25 | public void load(ClassInfo type) throws ClassNotFoundException, 26 | ExceptionInInitializerError, NoClassDefFoundError { 27 | String fullName = type.toString(); 28 | Class c = null; 29 | if (type.isArray()) { 30 | c = int[].class; // use int[] for generic array 31 | } else { 32 | c = Class.forName(fullName); 33 | } 34 | ClassInfo baseType = scope.findOrCreateClass(c.getSuperclass()); 35 | /* 36 | * Java spec: When an interface has no direct SuperInterface , it will 37 | * create abstract public method for all those public methods present in 38 | * the Object class 39 | */ 40 | if (baseType == null && c.isInterface()) { 41 | baseType = scope.rootObject; 42 | } 43 | 44 | ArrayList methods = new ArrayList(); 45 | 46 | // transform fields 47 | boolean hasStaticFields = false; 48 | Field[] raw_fields = c.getDeclaredFields(); 49 | HashMap fields = new HashMap(); 50 | HashMap staticFields = new HashMap(); 51 | for (Field f : raw_fields) { 52 | if (Modifier.isStatic(f.getModifiers())) { 53 | staticFields.put(f.getName(), scope.findOrCreateClass(f.getType())); 54 | hasStaticFields = true; 55 | } else { 56 | fields.put(f.getName(), scope.findOrCreateClass(f.getType())); 57 | } 58 | } 59 | if (hasStaticFields) { 60 | methods.add(new MethodInfo(type, type.STATIC_INITIALIZER, Modifier.STATIC, false)); 61 | } 62 | // TODO: do we actually need this?? I think the synthetic fields are included in declared fields 63 | // see http://www.public.iastate.edu/~java/docs/guide/innerclasses/html/innerclasses.doc.html 64 | if (type.isInnerClass()) { 65 | // say A is inside B and B is inside C 66 | // then in C, this$0 is A.this, this$1 is B.this 67 | fields.put("this$0", type.getOuterClass()); 68 | } 69 | 70 | // transform the class methods 71 | for (Method m : c.getDeclaredMethods()) { 72 | MethodSignature signature = MethodSignature.of(scope, m.getName(), m.getParameterTypes()); 73 | ClassInfo returnType = scope.findOrCreateClass(m.getReturnType()); 74 | methods.add(new MethodInfo(type, new FullMethodSignature(returnType, signature), m.getModifiers(), false)); 75 | } 76 | 77 | // transform the class constructors 78 | for (Constructor m : c.getDeclaredConstructors()) { 79 | MethodSignature signature = MethodSignature.of(scope, MethodInfo.CONSTRUCTOR, m.getParameterTypes()); 80 | ClassInfo returnType = scope.primitiveVoid; 81 | methods.add(new MethodInfo(type, new FullMethodSignature(returnType, signature), m.getModifiers(), false)); 82 | } 83 | 84 | // transform interfaces 85 | ImmutableList interfaces = scope.findOrCreateClasses(c.getInterfaces()); 86 | 87 | // loaded as a framework class 88 | ClassDetail detail = new ClassDetail.Builder() 89 | .setBaseType(baseType) 90 | .setInterfaces(interfaces) 91 | .setAccessFlags(c.getModifiers()) 92 | .setAllMethods(methods) 93 | .setFields(fields) 94 | .setStaticFields(staticFields) 95 | .setIsFrameworkClass(true) 96 | .build(); 97 | setDetail(type, detail); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/main/java/patdroid/core/Scope.java: -------------------------------------------------------------------------------- 1 | package patdroid.core; 2 | 3 | import com.google.common.collect.ImmutableList; 4 | import com.google.common.collect.ImmutableSet; 5 | 6 | import java.util.Collection; 7 | import java.util.HashMap; 8 | import java.util.List; 9 | 10 | /** 11 | * A scope is a container of classes. A scope can be used to represent different entities, which 12 | * entirely depends on the upper layer program analysis task. 13 | * Just to list a few things that a scope can stand for: 14 | * An APK file (as a whole), a Dex file (consider multi-dex), android framework classes (framework.jar) 15 | * Java core library classes (java.*), 3rd party library code (e.g. with a specific package name). 16 | *

17 | * Overall it is suggested multiple scopes stay disjoint. 18 | */ 19 | public class Scope { 20 | private final HashMap classes = new HashMap(); 21 | public final ClassInfo rootObject = findOrCreateClass(java.lang.Object.class); 22 | public final ClassInfo primitiveWide = findOrCreateClass("AndroidWide"); 23 | public final ClassInfo primitiveVoid = findOrCreateClass(void.class); 24 | public final ClassInfo primitiveLong = findOrCreateClass(long.class); 25 | public final ClassInfo primitiveBoolean = findOrCreateClass(boolean.class); 26 | public final ClassInfo primitiveByte = findOrCreateClass(byte.class); 27 | public final ClassInfo primitiveInt = findOrCreateClass(int.class); 28 | public final ClassInfo primitiveShort = findOrCreateClass(short.class); 29 | public final ClassInfo primitiveChar = findOrCreateClass(char.class); 30 | public final ClassInfo primitiveDouble = findOrCreateClass(double.class); 31 | public final ClassInfo primitiveFloat = findOrCreateClass(float.class); 32 | public final ImmutableSet primitives = 33 | ImmutableSet.of( 34 | primitiveWide, 35 | primitiveVoid, 36 | primitiveLong, 37 | primitiveBoolean, 38 | primitiveByte, 39 | primitiveInt, 40 | primitiveShort, 41 | primitiveChar, 42 | primitiveDouble, 43 | primitiveFloat); 44 | 45 | public ClassInfo findClass(String fullName) { 46 | return classes.get(fullName); 47 | } 48 | 49 | private ClassInfo createClass(String fullName) { 50 | ClassInfo ci = new ClassInfo(this, fullName); 51 | classes.put(fullName, ci); 52 | if (ci.isArray()) { 53 | findOrCreateClass(fullName.substring(1)); 54 | } 55 | return ci; 56 | } 57 | 58 | public boolean hasClass(ClassInfo ci) { 59 | return classes.containsValue(ci); 60 | } 61 | 62 | public Collection getAllClasses() { 63 | return classes.values(); 64 | } 65 | 66 | public Collection getAllClassNames() { 67 | return classes.keySet(); 68 | } 69 | 70 | public ClassInfo findOrCreateClass(String fullName) { 71 | ClassInfo u = findClass(fullName); 72 | return (u == null ? createClass(fullName) : u); 73 | } 74 | 75 | /** 76 | * Find or create a class representation 77 | * 78 | * @param c the java Class object 79 | * @return the class found or just created 80 | */ 81 | public ClassInfo findOrCreateClass(Class c) { 82 | return (c == null ? null : findOrCreateClass(c.getName())); 83 | } 84 | 85 | /** 86 | * Find or create a list of class representations 87 | * 88 | * @param classes the list of java Class objects 89 | * @return the list of class representations 90 | */ 91 | public ImmutableList findOrCreateClasses(Class[] classes) { 92 | ImmutableList.Builder builder = ImmutableList.builder(); 93 | for (Class clazz : classes) { 94 | builder.add(findOrCreateClass(clazz)); 95 | } 96 | return builder.build(); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/main/java/patdroid/core/TryBlockInfo.java: -------------------------------------------------------------------------------- 1 | package patdroid.core; 2 | 3 | /** 4 | * A try-block covers a range of instructions and provides the exception handlers 5 | * associated with these instructions. 6 | * 7 | */ 8 | public class TryBlockInfo { 9 | /** 10 | * An exception handler starts at a particular instruction index, 11 | * handling a particular exception type, e.g. IOException. 12 | * The catch-all handler would have a null exception type 13 | */ 14 | public static class ExceptionHandler { 15 | public ClassInfo exceptionType; 16 | public int handlerInsnIndex; 17 | } 18 | /** 19 | * The range of covered instructions, [startInsnIndex, endInsnIndex) 20 | */ 21 | public int startInsnIndex, endInsnIndex; 22 | /** 23 | * Event handlers, a map from ClassInfo to instruction index 24 | */ 25 | public ExceptionHandler[] handlers; 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/patdroid/dalvik/Dalvik.java: -------------------------------------------------------------------------------- 1 | package patdroid.dalvik; 2 | 3 | import patdroid.core.ClassInfo; 4 | import patdroid.core.Scope; 5 | import patdroid.util.Log; 6 | 7 | /** 8 | * Provide common support for dalvik VM 9 | *

10 | * The class name convention difference (Java vs. Dalvik): 11 | * 12 | * 13 | * 14 | * 15 | * 16 | * 17 | * 18 | * 19 | * 20 | * 21 | *
CaseJavaDalvik
String.class.getName()"java.lang.String""Ljava/lang/String;"
byte.class.getName()"byte""B"
(new Object[3]).getClass().getName()"[Ljava.lang.Object;""[Ljava/lang/Object;"
(new int[3][4][5][6][7][8][9]).getClass().getName()"[[[[[[[I""[[[[[[[I"
22 | */ 23 | public class Dalvik { 24 | /** 25 | * Convert an Android class identifier to its canonical form. 26 | * @param dalvikName the dalvik-style class name 27 | * @return canonical java class name 28 | */ 29 | public static String toCanonicalName(String dalvikName) { 30 | dalvikName = dalvikName.replace('/', '.'); 31 | char first = dalvikName.charAt(0); 32 | 33 | switch (first) { 34 | case 'C': return "char"; 35 | case 'I': return "int"; 36 | case 'B': return "byte"; 37 | case 'Z': return "boolean"; 38 | case 'F': return "float"; 39 | case 'D': return "double"; 40 | case 'S': return "short"; 41 | case 'J': return "long"; 42 | case 'V': return "void"; 43 | case 'L': return dalvikName.substring(1, dalvikName.length() - 1); 44 | case '[': return dalvikName; 45 | default: 46 | Log.err("unknown dalvik type:" + dalvikName); 47 | return ""; 48 | } 49 | } 50 | 51 | /** 52 | * Convert a canonical Java class name to Dalvik flavor. 53 | * @param canonicalName canonical java class name 54 | * @return the dalvik-style class name 55 | */ 56 | public static String toDalvikName(String canonicalName) { 57 | final boolean isArray = (canonicalName.charAt(0) == '['); 58 | if (isArray) { 59 | return canonicalName.replace('.', '/'); 60 | } else { 61 | if (canonicalName.equals("char")) 62 | return "C"; 63 | else if (canonicalName.equals("int")) 64 | return "I"; 65 | else if (canonicalName.equals("byte")) 66 | return "B"; 67 | else if (canonicalName.equals("boolean")) 68 | return "Z"; 69 | else if (canonicalName.equals("float")) 70 | return "F"; 71 | else if (canonicalName.equals("double")) 72 | return "D"; 73 | else if (canonicalName.equals("short")) 74 | return "S"; 75 | else if (canonicalName.equals("long")) 76 | return "J"; 77 | else if (canonicalName.equals("void")) 78 | return "V"; 79 | else 80 | return "L" + canonicalName.replace('.', '/') + ";"; 81 | /* only on Java7 82 | switch (canonicalName) { 83 | case "char": return "C"; 84 | case "int": return "I"; 85 | case "byte": return "B"; 86 | case "boolean": return "Z"; 87 | case "float": return "F"; 88 | case "double": return "D"; 89 | case "short": return "S"; 90 | case "long": return "J"; 91 | case "void": return "V"; 92 | default: return "L" + canonicalName.replace('.', '/') + ";"; 93 | } 94 | */ 95 | } 96 | } 97 | 98 | /** 99 | * Find a ClassInfo by Dalvik class name 100 | * 101 | * @param dalvikClassName The class name in Dalvik flavor 102 | * @return The ClassInfo 103 | */ 104 | public static ClassInfo findOrCreateClass(Scope scope, String dalvikClassName) { 105 | return scope.findOrCreateClass(toCanonicalName(dalvikClassName)); 106 | } 107 | 108 | /** 109 | * Find a ClassInfo by Dalvik class name 110 | * 111 | * @param dalvikClassName The class name in Dalvik flavor 112 | * @return The ClassInfo 113 | */ 114 | public static ClassInfo findClass(Scope scope, String dalvikClassName) { 115 | return scope.findClass(toCanonicalName(dalvikClassName)); 116 | } 117 | 118 | /** 119 | * Find a bunch of ClassInfos by Dalvik class name 120 | * 121 | * @param dalvikNames The class names in Dalvik flavor 122 | * @return The ClassInfo 123 | */ 124 | public static ClassInfo[] findOrCreateClass(Scope scope, String[] dalvikNames) { 125 | ClassInfo[] ci = new ClassInfo[dalvikNames.length]; 126 | for (int i = 0; i < dalvikNames.length; ++i) { 127 | ci[i] = findOrCreateClass(scope, dalvikNames[i]); 128 | } 129 | return ci; 130 | } 131 | 132 | /** 133 | * Find a bunch of ClassInfos by Dalvik class name 134 | * 135 | * @param dalvikNames The class names in Dalvik flavor 136 | * @return The ClassInfo 137 | */ 138 | public static ClassInfo[] findClass(Scope scope, String[] dalvikNames) { 139 | ClassInfo[] ci = new ClassInfo[dalvikNames.length]; 140 | for (int i = 0; i < dalvikNames.length; ++i) { 141 | ci[i] = findClass(scope, dalvikNames[i]); 142 | } 143 | return ci; 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /src/main/java/patdroid/dalvik/Instruction.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Mingyuan Xia (http://mxia.me) and contributors 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 | * Contributors: 17 | * Mingyuan Xia 18 | */ 19 | 20 | package patdroid.dalvik; 21 | 22 | import java.util.Arrays; 23 | import java.util.Map; 24 | 25 | import patdroid.core.ClassInfo; 26 | import patdroid.core.FieldInfo; 27 | import patdroid.util.Pair; 28 | 29 | /** 30 | * Unified Dalvik VM instruction 31 | *

With C/C++, union can help save space. This only is designed to be compact.

32 | *

Plain-old opcode table is preferred than inheritance as compiler could 33 | * generate more efficient code with this

34 | */ 35 | public final class Instruction { 36 | // major opcodes 37 | public final static byte OP_NOP = 0; 38 | public final static byte OP_MOV = 0x01; 39 | public final static byte OP_RETURN = 0x02; 40 | public final static byte OP_SPECIAL = 0x03; 41 | public final static byte OP_NEW = 0x04; 42 | public final static byte OP_EXCEPTION_OP = 0x05; 43 | public final static byte OP_GOTO = 0x06; 44 | public final static byte OP_CMP = 0x07; 45 | public final static byte OP_IF = 0x08; 46 | public final static byte OP_INSTANCE_OP = 0x09; 47 | public final static byte OP_ARRAY_OP = 0x0A; 48 | public final static byte OP_STATIC_OP = 0x0B; 49 | public final static byte OP_INVOKE_OP = 0x0C; 50 | public final static byte OP_ARITHETIC = 0x0D; 51 | public final static byte OP_SWITCH = 0x0E; 52 | public static final byte OP_HALT = 0x0F; 53 | 54 | // auxiliary opcodes 55 | public final static byte OP_MOV_REG = 0x01; 56 | public final static byte OP_MOV_CONST = 0x02; 57 | public final static byte OP_RETURN_VOID = 0x03; 58 | public final static byte OP_RETURN_SOMETHING = 0x04; 59 | public final static byte OP_MONITOR_ENTER = 0x05; 60 | public final static byte OP_MONITOR_EXIT = 0x06; 61 | public final static byte OP_SP_ARGUMENTS = 0x07; 62 | public final static byte OP_NEW_INSTANCE = 0x08; 63 | public final static byte OP_NEW_ARRAY = 0x09; 64 | public final static byte OP_NEW_FILLED_ARRAY = 0x0A; 65 | public final static byte OP_INVOKE_DIRECT = 0x0B; 66 | public final static byte OP_INVOKE_SUPER = 0x0C; 67 | public final static byte OP_INVOKE_VIRTUAL = 0x0D; 68 | public final static byte OP_INVOKE_STATIC = 0x0E; 69 | public final static byte OP_INVOKE_INTERFACE = 0x0F; 70 | public final static byte OP_A_INSTANCEOF = 0x10; 71 | public final static byte OP_A_ARRAY_LENGTH = 0x11; 72 | public final static byte OP_A_CHECKCAST = 0x12; 73 | public final static byte OP_A_NOT = 0x13; 74 | public final static byte OP_A_NEG = 0x14; 75 | public final static byte OP_MOV_RESULT = 0x15; 76 | public final static byte OP_MOV_EXCEPTION = 0x16; 77 | public final static byte OP_A_CAST = 0x17; 78 | public final static byte OP_IF_EQ = 0x18; 79 | public final static byte OP_IF_NE = 0x19; 80 | public final static byte OP_IF_LT = 0x1A; 81 | public final static byte OP_IF_GE = 0x1B; 82 | public final static byte OP_IF_GT = 0x1C; 83 | public final static byte OP_IF_LE = 0x1D; 84 | public final static byte OP_IF_EQZ = 0x1E; 85 | public final static byte OP_IF_NEZ = 0x1F; 86 | public final static byte OP_IF_LTZ = 0x20; 87 | public final static byte OP_IF_GEZ = 0x21; 88 | public final static byte OP_IF_GTZ = 0x22; 89 | public final static byte OP_IF_LEZ = 0x23; 90 | public final static byte OP_ARRAY_GET = 0x24; 91 | public final static byte OP_ARRAY_PUT = 0x25; 92 | public static final byte OP_A_ADD = 0x26; 93 | public static final byte OP_A_SUB = 0x27; 94 | public static final byte OP_A_MUL = 0x28; 95 | public static final byte OP_A_DIV = 0x29; 96 | public static final byte OP_A_REM = 0x2A; 97 | public static final byte OP_A_AND = 0x2B; 98 | public static final byte OP_A_OR = 0x2C; 99 | public static final byte OP_A_XOR = 0x2D; 100 | public static final byte OP_A_SHL = 0x2E; 101 | public static final byte OP_A_SHR = 0x2F; 102 | public static final byte OP_A_USHR = 0x30; 103 | public static final byte OP_CMP_LONG = 0x31; 104 | public static final byte OP_CMP_LESS = 0x32; 105 | public static final byte OP_CMP_GREATER = 0x33; 106 | public static final byte OP_STATIC_GET_FIELD = 0x34; 107 | public static final byte OP_STATIC_PUT_FIELD = 0x35; 108 | public static final byte OP_INSTANCE_GET_FIELD = 0x36; 109 | public static final byte OP_INSTANCE_PUT_FIELD = 0x37; 110 | public static final byte OP_EXCEPTION_TRYCATCH = 0x38; 111 | public static final byte OP_EXCEPTION_THROW = 0x39; 112 | 113 | private static String[] opname = { "NOP", "MOV", "RETURN", "SPECIAL", 114 | "NEW", "EXCEPTION", "GOTO", "CMP", "IF", "INSTANCE", "ARRAY", "STATIC", 115 | "INVOKE", "ARITHMETIC", "SWITCH", "HALT" }; 116 | private static String[] opaux_name = { "NIL", "REG", "CONST", "VOID", 117 | "VALUE", "MONITOR_ENTER", "MONITOR_EXIT", "ARGUMENT_SET", 118 | "INSTANCE", "ARRAY", "FILLED_ARRAY", "DIRECT", "SUPER", "VIRTUAL", 119 | "STATIC", "INTERFACE", "INSTANCE_OF", "ARRAY_LENGTH", 120 | "CHECK-AND-CAST", "NOT", "NEG", "RESULT", "EXCETPION", "CAST", 121 | "EQ", "NE", "LT", "GE", "GT", "LE", "EQZ", "NEZ", "LTZ", "GEZ", 122 | "GTZ", "LEZ", "AGET", "APUT", "ADD", "SUB", "MUL", "DIV", "REM", 123 | "AND", "OR", "XOR", "SHL", "SHR", "USHR", "CMPLONG", "CMPL", "CMPG", 124 | "SGET", "SPUT", "IGET", "IPUT", "TRYCATCH", "THROW" }; 125 | 126 | /** 127 | * Major opcode 128 | */ 129 | public byte opcode = OP_NOP; 130 | /** 131 | * Auxiliary opcode 132 | */ 133 | public byte opcode_aux = OP_NOP; 134 | /** 135 | * Register operands 136 | */ 137 | public short rdst = -1, r0 = -1, r1 = -1; 138 | /** 139 | * Type field 140 | */ 141 | public ClassInfo type = null; 142 | /** 143 | * Instruction-specific data 144 | */ 145 | public Object extra = null; 146 | 147 | @Override 148 | public String toString() { 149 | final StringBuilder s = new StringBuilder(); 150 | s.append("<"); 151 | s.append(opname[opcode]); 152 | if (opcode_aux != OP_NOP) { 153 | s.append(",").append(opaux_name[opcode_aux]); 154 | } 155 | if (rdst != -1) { 156 | s.append(",dst=r").append(rdst); 157 | } 158 | if (r0 != -1) { 159 | s.append(",r0=r").append(r0); 160 | } 161 | if (r1 != -1) { 162 | s.append(",r1=r").append(r1); 163 | } 164 | if (type != null) { 165 | s.append(",type=").append(type.toString()); 166 | } 167 | if (extra != null) { 168 | s.append(",extra=").append(extraToString()); 169 | } 170 | s.append(">"); 171 | return s.toString(); 172 | } 173 | 174 | private String extraToString() { 175 | if (extra instanceof int[]) { 176 | return Arrays.toString((int[]) extra); 177 | } else if (extra instanceof Integer) { 178 | return "index:" + extra.toString(); 179 | } else if (extra instanceof String) { 180 | return "\""+(String) extra +"\""; 181 | } else if (extra instanceof Pair) { 182 | return extra.toString(); 183 | } else if (extra instanceof Map) { 184 | return extra.toString(); 185 | } else if (extra instanceof Object[]) { 186 | return Arrays.deepToString((Object[]) extra); 187 | } else if (extra instanceof FieldInfo) { 188 | return extra.toString(); 189 | } else if (extra instanceof Invocation) { 190 | return extra.toString(); 191 | } else { 192 | return "?" + extra.toString(); 193 | } 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /src/main/java/patdroid/dalvik/Invocation.java: -------------------------------------------------------------------------------- 1 | package patdroid.dalvik; 2 | 3 | import patdroid.core.MethodInfo; 4 | 5 | import java.util.Arrays; 6 | 7 | public class Invocation { 8 | public Invocation(boolean isResolved, MethodInfo target, int[] args) { 9 | this.isResolved = isResolved; 10 | this.target = target; 11 | this.args = args; 12 | } 13 | public boolean isResolved; 14 | public MethodInfo target; 15 | public int[] args; 16 | @Override 17 | public String toString() { 18 | return "[" + target + ", " + Arrays.toString(args) + (isResolved ? "]" : "]"); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/patdroid/fs/EmulatedFS.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Mingyuan Xia (http://mxia.me) and contributors 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 | * Contributors: 17 | * Lu Gong 18 | */ 19 | 20 | package patdroid.fs; 21 | 22 | import java.util.zip.ZipFile; 23 | 24 | /** 25 | * Emulating an Android file system, containing apps and some system-wise folders (e.g. /proc/). 26 | * Currently, the emulated fs does not have access control. 27 | * For apps, their apks are mounted as if they are installed onto the emulated filesystem. 28 | * 29 | */ 30 | public class EmulatedFS { 31 | /** 32 | * The root of the file system 33 | */ 34 | public final FileNode root = new FileNode(); 35 | /** 36 | * Emulate a proc file system 37 | */ 38 | private final FileNode procFS = new FileNode(); 39 | /** 40 | * The content of /proc/cpuinfo 41 | */ 42 | private final static String CPUINFO = "Serial: 0000000000000000"; 43 | /** 44 | * Emulate an external storage card 45 | */ 46 | private final FileNode sdcardFS = new FileNode(); 47 | public ZipBackedNode apkAssets; 48 | 49 | /** 50 | * Create an empty Android file system with only system folders 51 | */ 52 | public EmulatedFS() { 53 | procFS.setContent("/cpuinfo", CPUINFO); 54 | root.mount("/proc", procFS); 55 | root.mount("/sdcard", sdcardFS); 56 | root.mount("/mnt/sdcard", sdcardFS); 57 | } 58 | 59 | /** 60 | * Create an emulated Android file system and load the apk content 61 | * @param apkFile the apk file 62 | */ 63 | public EmulatedFS(ZipFile apkFile) { 64 | this(); 65 | loadApk("TODO", apkFile); // TODO: obtain the package name to mount it 66 | } 67 | 68 | /** 69 | * Load an apk file to the emulated file system. 70 | * By spec, the apk file would be located at /data/app/[package_name].apk; 71 | * Its data would be accessible at /data/data/[package_name]/; 72 | * @param pkgName the package name of the apk 73 | * @param apkFile the apk file 74 | */ 75 | public void loadApk(String pkgName, ZipFile apkFile) { 76 | apkAssets = new ZipBackedNode(apkFile, "assets/"); 77 | root.mount("/data/" + pkgName, apkAssets); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/patdroid/fs/FileNode.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Mingyuan Xia (http://mxia.me) and contributors 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 | * Contributors: 17 | * Lu Gong 18 | */ 19 | 20 | package patdroid.fs; 21 | 22 | import java.io.ByteArrayInputStream; 23 | import java.io.InputStream; 24 | import java.util.ArrayList; 25 | import java.util.HashMap; 26 | 27 | import patdroid.util.Pair; 28 | 29 | /** 30 | * A FileNode represents a set of files and mount points 31 | */ 32 | public class FileNode { 33 | private final HashMap mountList = new HashMap(); 34 | private final HashMap fileList = new HashMap(); 35 | 36 | /** 37 | * process a path into an array of folders 38 | * @param path e.g. /foo/bar/.././haha 39 | * @return e.g. ["foo", "haha"] 40 | */ 41 | private static String[] norm(String path) { 42 | String[] elements = path.split("[/\\\\]"); 43 | ArrayList stack = new ArrayList(); 44 | for (String e: elements) { 45 | if (e.isEmpty() || e.equals(".")) 46 | continue; 47 | if (e.equals("..")) { 48 | if (!stack.isEmpty()) 49 | stack.remove(stack.size() - 1); 50 | else 51 | return null; 52 | continue; 53 | } 54 | stack.add(e); 55 | } 56 | return stack.toArray(new String[stack.size()]); 57 | } 58 | 59 | private Pair dispatch(String path) { 60 | String[] norm = norm(path); 61 | if (norm == null) 62 | return null; 63 | 64 | FileNode node = this; 65 | String newPath = ""; 66 | for (String e: norm) { 67 | newPath += "/" + e; 68 | if (node.mountList.containsKey(newPath)) { 69 | node = node.mountList.get(newPath); 70 | newPath = ""; 71 | } 72 | } 73 | 74 | return new Pair(node, newPath); 75 | } 76 | 77 | public final void mount(String path, FileNode node) { 78 | Pair u = dispatch(path); 79 | u.first.mountHere(u.second, node); 80 | } 81 | 82 | protected void mountHere(String path, FileNode node) { 83 | mountList.put(path, node); 84 | } 85 | 86 | public final InputStream openRead(String path) { 87 | Pair u = dispatch(path); 88 | return u.first.openReadHere(u.second); 89 | } 90 | 91 | protected InputStream openReadHere(String path) { 92 | byte[] content = fileList.get(path); 93 | if (content == null) 94 | return null; 95 | return new ByteArrayInputStream(content); 96 | } 97 | 98 | /** 99 | * Set the content of a file in the node. 100 | * The new content will override existing content if any. 101 | * @param path the path to the file 102 | * @param content the content of the file 103 | */ 104 | public final void setContent(String path, byte[] content) { 105 | Pair u = dispatch(path); 106 | u.first.setContentHere(u.second, content); 107 | } 108 | 109 | public final void setContent(String path, String content) { 110 | setContent(path, content.getBytes()); 111 | } 112 | 113 | protected void setContentHere(String path, byte[] content) { 114 | fileList.put(path, content); 115 | } 116 | 117 | } 118 | -------------------------------------------------------------------------------- /src/main/java/patdroid/fs/ZipBackedNode.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Mingyuan Xia (http://mxia.me) and contributors 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 | * Contributors: 17 | * Lu Gong 18 | */ 19 | 20 | package patdroid.fs; 21 | 22 | import java.io.IOException; 23 | import java.io.InputStream; 24 | import java.util.zip.ZipEntry; 25 | import java.util.zip.ZipFile; 26 | 27 | public final class ZipBackedNode extends FileNode { 28 | private final ZipFile zip; 29 | private final String prefix; 30 | 31 | public ZipBackedNode(ZipFile zip) { 32 | this(zip, ""); 33 | } 34 | 35 | public ZipBackedNode(ZipFile zip, String prefix) { 36 | this.zip = zip; 37 | this.prefix = prefix; 38 | } 39 | 40 | @Override 41 | protected InputStream openReadHere(String path) { 42 | ZipEntry entry = zip.getEntry(prefix + path.substring(1)); 43 | if (entry == null) 44 | return null; 45 | try { 46 | return zip.getInputStream(entry); 47 | } catch (IOException e) { 48 | return null; 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/patdroid/permission/APIMapping.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Mingyuan Xia (http://mxia.me) and contributors 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 | * Contributors: 17 | * Mingyuan Xia 18 | */ 19 | 20 | package patdroid.permission; 21 | 22 | import patdroid.core.MethodInfo; 23 | 24 | import java.util.ArrayList; 25 | import java.util.HashMap; 26 | 27 | /** 28 | * Specify what permissions are needed by which APIs. 29 | * The mapping is bi-directional, i.e. from API(MethodInfo) to Permission(String) 30 | * and vice verse 31 | */ 32 | public class APIMapping { 33 | private final HashMap> mtoperm 34 | = new HashMap>(); 35 | private final HashMap> permtom 36 | = new HashMap>(); 37 | public void add(MethodInfo m, String perm) { 38 | if (mtoperm.containsKey(m)) { 39 | get(m).add(perm); 40 | } else { 41 | ArrayList l = new ArrayList(); 42 | l.add(perm); 43 | mtoperm.put(m, l); 44 | } 45 | if (permtom.containsKey(perm)) { 46 | get(perm).add(m); 47 | } else { 48 | ArrayList l = new ArrayList(); 49 | l.add(m); 50 | permtom.put(perm, l); 51 | } 52 | } 53 | public ArrayList get(MethodInfo m) { 54 | return mtoperm.get(m); 55 | } 56 | public ArrayList get(String perm) { 57 | return permtom.get(perm); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/patdroid/permission/PScoutParser.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Mingyuan Xia (http://mxia.me) and contributors 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 | * Contributors: 17 | * Mingyuan Xia 18 | */ 19 | 20 | package patdroid.permission; 21 | 22 | import com.google.common.collect.ImmutableList; 23 | import patdroid.core.*; 24 | 25 | import java.io.*; 26 | 27 | /** 28 | * A parser for the output file of PScout from UToronto. 29 | * See http://pscout.csl.toronto.edu/ for more details 30 | */ 31 | public class PScoutParser { 32 | private final Scope scope; 33 | 34 | private PScoutParser(Scope scope) { 35 | this.scope = scope; 36 | } 37 | 38 | public APIMapping parse(File f) throws IOException { 39 | final APIMapping r = new APIMapping(); 40 | final BufferedReader br = new BufferedReader(new FileReader(f)); 41 | String perm = "", line = br.readLine(); 42 | while (line != null) { 43 | perm = line.replace("Permission:", ""); 44 | br.readLine(); // skip a line telling how many callers in total 45 | do { 46 | line = br.readLine(); 47 | if (line == null || !line.startsWith("<")) { 48 | break; 49 | } 50 | MethodInfo m = parseMethod(line); 51 | r.add(m, perm); 52 | } while (true); 53 | } 54 | br.close(); 55 | return r; 56 | } 57 | 58 | private MethodInfo parseMethod(String line) { 59 | // example: 60 | String className, returnType, methodName; 61 | String[] paramTypes; 62 | String s = line.substring(1, line.length() - 1); 63 | String[] a = s.split(":"); // class, rest 64 | className = a[0]; 65 | s = a[1]; 66 | int pos = s.indexOf('('); 67 | a[0] = s.substring(0, pos); // ret methodName 68 | a[1] = s.substring(pos + 1, s.length() - 1); // params 69 | returnType = a[0].trim().split(" ")[0]; 70 | methodName = a[0].trim().split(" ")[1]; 71 | paramTypes = a[1].replace(" ", "").split(","); 72 | final MethodSignature signature = new MethodSignature(methodName, findOrCreateClasses(paramTypes)); 73 | ClassInfo ci = scope.findOrCreateClass(className); 74 | return (ci == null ? null : ci.findMethod(new FullMethodSignature(findOrCreateClass(returnType), signature))); 75 | } 76 | 77 | /** 78 | * Convert PSCout-style type name to canonical form 79 | * 80 | * @param t PSCout-style type name 81 | * @return a ClassInfo 82 | */ 83 | private ClassInfo findOrCreateClass(String t) { 84 | if (!t.endsWith("[]")) { 85 | return scope.findOrCreateClass(t); 86 | } else { 87 | String baseType = t.substring(0, t.indexOf("[]")); 88 | int level = (t.length() - t.indexOf("[]")) / 2; 89 | String s = ""; 90 | for (int i = 0; i < level; ++i) 91 | s += "["; 92 | // TODO: map all primitive types to short form 93 | if (baseType.equals("int")) 94 | s += "I"; 95 | else if (baseType.equals("boolean")) 96 | s += "B"; 97 | else 98 | s += "L" + baseType + ";"; 99 | return scope.findOrCreateClass(s); 100 | } 101 | } 102 | 103 | public ImmutableList findOrCreateClasses(String[] fullNames) { 104 | ImmutableList.Builder builder = ImmutableList.builder(); 105 | for (String fullName : fullNames) { 106 | builder.add(findOrCreateClass(fullName)); 107 | } 108 | return builder.build(); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/main/java/patdroid/smali/SmaliClassDetailLoader.java: -------------------------------------------------------------------------------- 1 | package patdroid.smali; 2 | 3 | import java.io.BufferedInputStream; 4 | import java.io.File; 5 | import java.io.IOException; 6 | import java.lang.reflect.Modifier; 7 | import java.util.ArrayList; 8 | import java.util.HashMap; 9 | import java.util.IdentityHashMap; 10 | import java.util.List; 11 | import java.util.zip.ZipEntry; 12 | import java.util.zip.ZipFile; 13 | 14 | import com.google.common.collect.ImmutableList; 15 | import org.jf.dexlib2.AccessFlags; 16 | import org.jf.dexlib2.DexFileFactory; 17 | import org.jf.dexlib2.Opcodes; 18 | import org.jf.dexlib2.dexbacked.DexBackedDexFile; 19 | import org.jf.dexlib2.iface.ClassDef; 20 | import org.jf.dexlib2.iface.DexFile; 21 | import org.jf.dexlib2.iface.Field; 22 | import org.jf.dexlib2.iface.Method; 23 | import org.jf.dexlib2.iface.MethodImplementation; 24 | 25 | import patdroid.Settings; 26 | import patdroid.core.*; 27 | import patdroid.util.Log; 28 | 29 | import patdroid.dalvik.Dalvik; 30 | 31 | /** 32 | * Load classes, methods, fields and instructions from an APK file with SMALI 33 | * https://github.com/JesusFreke/smali 34 | */ 35 | public class SmaliClassDetailLoader extends ClassDetailLoader { 36 | private final DexFile[] dexFiles; 37 | private final boolean translateInstructions; 38 | private final boolean isFramework; 39 | 40 | private SmaliClassDetailLoader(DexFile[] dexFiles, boolean translateInstructions, boolean isFramework) { 41 | this.dexFiles = dexFiles; 42 | this.translateInstructions = translateInstructions; 43 | this.isFramework = isFramework; 44 | } 45 | 46 | /** 47 | * Create a loader that loads from an APK file (could contain multiple DEX files), optionally loading instructions 48 | * @param apkFile the APK file 49 | * @param apiLevel the Android API level 50 | * @param translateInstructions true if the instructions shall be loaded 51 | * @return A smali class detail loader to load classes from the containing DEX files 52 | */ 53 | public static SmaliClassDetailLoader fromApkFile(ZipFile apkFile, int apiLevel, boolean translateInstructions) { 54 | ArrayList dexEntries = new ArrayList(); 55 | dexEntries.add(apkFile.getEntry("classes.dex")); 56 | for (int i = 2; i < 99; ++i) { 57 | final ZipEntry e = apkFile.getEntry("classes" + i +".dex"); 58 | if (e != null) { 59 | dexEntries.add(e); 60 | } else { 61 | break; 62 | } 63 | } 64 | final int n = dexEntries.size(); 65 | if (n == 0) { 66 | Log.err("Source apk does not have any dex files"); 67 | } 68 | 69 | DexFile[] dexFiles = new DexFile[n]; 70 | final Opcodes opcodes = Opcodes.forApi(apiLevel); 71 | try { 72 | for (int i = 0; i < n; ++i) { 73 | dexFiles[i] = DexBackedDexFile.fromInputStream(opcodes, 74 | new BufferedInputStream(apkFile.getInputStream(dexEntries.get(i)))); 75 | } 76 | } catch (IOException e) { 77 | Log.err("failed to process the source apk file"); 78 | Log.err(e); 79 | } 80 | return new SmaliClassDetailLoader(dexFiles, translateInstructions, false); 81 | } 82 | 83 | public static SmaliClassDetailLoader fromFramework(File frameworkClassesFolder, int apiLevel) { 84 | File f = new File(frameworkClassesFolder, "android-" + apiLevel + ".dex"); 85 | if (!f.exists()) 86 | throw new RuntimeException("framework file not available"); 87 | DexFile dex; 88 | try { 89 | dex = DexFileFactory.loadDexFile(f, apiLevel); 90 | } catch (IOException e) { 91 | throw new RuntimeException("failed to load framework classes"); 92 | } 93 | return new SmaliClassDetailLoader(new DexFile[] {dex}, false, true); 94 | } 95 | 96 | public static SmaliClassDetailLoader fromDexfile(DexFile dex, boolean translateInstructions) throws RuntimeException { 97 | return new SmaliClassDetailLoader(new DexFile[] {dex}, translateInstructions, false); 98 | } 99 | 100 | /** 101 | * Parse an apk file and extract all classes, methods, fields and optionally instructions 102 | */ 103 | public void loadAll(Scope scope) { 104 | IdentityHashMap collector = new IdentityHashMap(); 105 | for (DexFile dexFile: dexFiles) { 106 | for (final ClassDef classDef : dexFile.getClasses()) { 107 | ClassInfo ci = Dalvik.findOrCreateClass(scope, classDef.getType()); 108 | ClassDetail detail = translateClassDef(ci, classDef, collector); 109 | setDetail(ci, detail); 110 | } 111 | } 112 | if (translateInstructions) { 113 | for (MethodInfo mi: collector.keySet()) { 114 | final MethodImplementation impl = collector.get(mi); 115 | // Decode instructions 116 | if (impl != null) { 117 | new MethodImplementationTranslator(scope).translate(mi, impl); 118 | } 119 | } 120 | } 121 | } 122 | 123 | private ClassDetail translateClassDef(ClassInfo ci, ClassDef classDef, IdentityHashMap collector) { 124 | ClassDetail.Builder builder = new ClassDetail.Builder(); 125 | if (classDef.getSuperclass() == null) { 126 | builder.setBaseType(null); // for java.lang.Object 127 | } else { 128 | builder.setBaseType(Dalvik.findOrCreateClass(ci.scope, classDef.getSuperclass())); 129 | } 130 | builder.setInterfaces(findOrCreateClasses(ci.scope, classDef.getInterfaces())); 131 | builder.setAccessFlags(translateAccessFlags(classDef.getAccessFlags())); 132 | builder.setAllMethods(translateMethods(ci, classDef.getMethods(), collector)); 133 | builder.setStaticFields(translateFields(ci.scope, classDef.getStaticFields())); 134 | HashMap fields = translateFields(ci.scope, classDef.getInstanceFields()); 135 | // TODO: do we need this? 136 | if (ci.isInnerClass()) { 137 | fields.put("this$0", ci.getOuterClass()); 138 | } 139 | builder.setFields(fields); 140 | builder.setIsFrameworkClass(isFramework); 141 | return builder.build(); 142 | } 143 | 144 | private MethodInfo translateMethod(ClassInfo ci, Method method, IdentityHashMap collector) { 145 | final ClassInfo retType = Dalvik.findOrCreateClass(ci.scope, method.getReturnType()); 146 | final ImmutableList paramTypes = findOrCreateClasses(ci.scope, method.getParameterTypes()); 147 | final MethodSignature signature = new MethodSignature(method.getName(), paramTypes); 148 | final FullMethodSignature fullSignature = new FullMethodSignature(retType, signature); 149 | final int accessFlags = translateAccessFlags(method.getAccessFlags()); 150 | final MethodInfo mi = new MethodInfo(ci, fullSignature, accessFlags, AccessFlags.SYNTHETIC.isSet(method.getAccessFlags())); 151 | Log.msg("Translating method: %s", mi.toString()); 152 | collector.put(mi, method.getImplementation()); 153 | return mi; 154 | } 155 | 156 | private ImmutableList translateMethods(ClassInfo ci, Iterable methods 157 | , IdentityHashMap collector) { 158 | ImmutableList.Builder builder = ImmutableList.builder(); 159 | for (Method method : methods) { 160 | builder.add(translateMethod(ci, method, collector)); 161 | } 162 | return builder.build(); 163 | } 164 | 165 | private HashMap translateFields( 166 | Scope scope, Iterable fields) { 167 | HashMap result = new HashMap(); 168 | for (Field field: fields) { 169 | // TODO access flags and initial value are ignored 170 | result.put(field.getName(), Dalvik.findOrCreateClass(scope, field.getType())); 171 | } 172 | return result; 173 | } 174 | 175 | private static int translateAccessFlags(int accessFlags) { 176 | int f = 0; 177 | f |= (AccessFlags.ABSTRACT.isSet(accessFlags) ? Modifier.ABSTRACT : 0); 178 | // f |= (AccessFlags.ANNOTATION.isSet(accessFlags) ? Modifier.ANNOTATION : 0); 179 | // f |= (AccessFlags.BRIDGE.isSet(accessFlags) ? Modifier.BRIDGE : 0); 180 | // f |= (AccessFlags.CONSTRUCTOR.isSet(accessFlags) ? Modifier.CONSTRUCTOR : 0); 181 | // f |= (AccessFlags.DECLARED_SYNCHRONIZED.isSet(accessFlags) ? Modifier.DECLARED_SYNCHRONIZED : 0); 182 | // f |= (AccessFlags.ENUM.isSet(accessFlags) ? Modifier.ENUM : 0); 183 | f |= (AccessFlags.FINAL.isSet(accessFlags) ? Modifier.FINAL : 0); 184 | f |= (AccessFlags.INTERFACE.isSet(accessFlags) ? Modifier.INTERFACE : 0); 185 | f |= (AccessFlags.NATIVE.isSet(accessFlags) ? Modifier.NATIVE : 0); 186 | f |= (AccessFlags.PRIVATE.isSet(accessFlags) ? Modifier.PRIVATE : 0); 187 | f |= (AccessFlags.PROTECTED.isSet(accessFlags) ? Modifier.PROTECTED : 0); 188 | f |= (AccessFlags.PUBLIC.isSet(accessFlags) ? Modifier.PUBLIC : 0); 189 | f |= (AccessFlags.STATIC.isSet(accessFlags) ? Modifier.STATIC : 0); 190 | f |= (AccessFlags.STRICTFP.isSet(accessFlags) ? Modifier.STRICT : 0); 191 | f |= (AccessFlags.SYNCHRONIZED.isSet(accessFlags) ? Modifier.SYNCHRONIZED : 0); 192 | // f |= (AccessFlags.SYNTHETIC.isSet(accessFlags) ? Modifier.SYNTHETIC : 0); 193 | f |= (AccessFlags.TRANSIENT.isSet(accessFlags) ? Modifier.TRANSIENT : 0); 194 | // f |= (AccessFlags.VARARGS.isSet(accessFlags) ? Modifier.VARARGS : 0); 195 | f |= (AccessFlags.VOLATILE.isSet(accessFlags) ? Modifier.VOLATILE : 0); 196 | return f; 197 | } 198 | 199 | static ImmutableList findOrCreateClasses(Scope scope, List l) { 200 | ImmutableList.Builder builder = ImmutableList.builder(); 201 | for (CharSequence s : l) { 202 | builder.add(Dalvik.findOrCreateClass(scope, s.toString())); 203 | } 204 | return builder.build(); 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /src/main/java/patdroid/util/JSONWriter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Mingyuan Xia (http://mxia.me) and contributors 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 | * Contributors: 17 | * Mingyuan Xia 18 | */ 19 | 20 | package patdroid.util; 21 | 22 | import java.io.Closeable; 23 | import java.io.Flushable; 24 | import java.io.IOException; 25 | import java.io.Writer; 26 | import java.util.ArrayList; 27 | import java.util.Stack; 28 | 29 | public final class JSONWriter implements Closeable, Flushable { 30 | private final Writer writer; 31 | private final Stack enders = new Stack(); 32 | private int indent = 0; 33 | private boolean needComma = false; 34 | 35 | public JSONWriter(Writer writer) { 36 | this.writer = writer; 37 | } 38 | 39 | private final String getIndent() { 40 | return new String(new char[indent]).replace("\0", "\t"); 41 | } 42 | 43 | private final JSONWriter writeItem(String k, String content) throws IOException { 44 | if (needComma) writer.write(",\n"); else writer.write("\n"); 45 | writer.write(getIndent() + "\"" + k + "\": " + content); 46 | needComma = true; 47 | return this; 48 | } 49 | 50 | private final JSONWriter writeStarter(String starter, String ender, String k) throws IOException { 51 | if (needComma) writer.write(",\n"); else writer.write("\n"); 52 | writer.write(getIndent()); 53 | writer.write(k != null ? "\"" + k + "\": " : ""); 54 | writer.write(starter); 55 | enders.push(ender); 56 | indent++; 57 | needComma = false; 58 | return this; 59 | } 60 | 61 | public final JSONWriter writeStartObject(String k) throws IOException { 62 | return writeStarter("{", "}", k); 63 | } 64 | 65 | public final JSONWriter writeStartObject() throws IOException { 66 | return writeStarter("{", "}", null); 67 | } 68 | 69 | public final JSONWriter writeStartArray(String k) throws IOException { 70 | return writeStarter("[", "]", k); 71 | } 72 | 73 | public final JSONWriter writeEnd() throws IOException { 74 | writer.write("\n"); 75 | indent--; 76 | writer.write(getIndent() + enders.pop()); 77 | needComma = true; 78 | return this; 79 | } 80 | 81 | public final JSONWriter write(String k, String str) throws IOException { 82 | return writeItem(k, "\"" + str + "\""); 83 | } 84 | 85 | public final JSONWriter write(String k, int v) throws IOException { 86 | return writeItem(k, Integer.toString(v)); 87 | } 88 | 89 | public final JSONWriter write(String k, boolean b) throws IOException { 90 | return writeItem(k, Boolean.toString(b)); 91 | } 92 | 93 | public final JSONWriter writeNull(String k) throws IOException { 94 | return writeItem(k, "null"); 95 | } 96 | 97 | public final JSONWriter writeObjectAsString(String k, Object o) throws IOException { 98 | return (o == null ? writeNull(k) : write(k, o.toString())); 99 | } 100 | 101 | public JSONWriter writeArray(String k, ArrayList list) throws IOException { 102 | String s = ""; 103 | int counter = 0; 104 | s += "["; 105 | for (Object o : list) { 106 | s += "\"" + o.toString() + "\""; 107 | if (++counter != list.size()) { 108 | s += ", "; 109 | } 110 | } 111 | s += "]"; 112 | return writeItem(k, s); 113 | } 114 | 115 | @Override 116 | public void flush() throws IOException { 117 | writer.flush(); 118 | } 119 | 120 | @Override 121 | public void close() throws IOException { 122 | flush(); 123 | writer.close(); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/main/java/patdroid/util/Log.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Mingyuan Xia (http://mxia.me) and contributors 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 | * Contributors: 17 | * Mingyuan Xia 18 | * Lu Gong 19 | */ 20 | 21 | package patdroid.util; 22 | 23 | import patdroid.Settings; 24 | 25 | import java.io.BufferedWriter; 26 | import java.io.IOException; 27 | import java.io.OutputStreamWriter; 28 | import java.io.Writer; 29 | 30 | public class Log { 31 | public static final int MODE_VERBOSE = 0; 32 | public static final int MODE_MSG = 1; 33 | public static final int MODE_DEBUG = 2; 34 | public static final int MODE_WARNING = 3; 35 | public static final int MODE_SEVERE_WARNING = 4; 36 | public static final int MODE_ERROR = 5; 37 | public static final int MODE_REPORT = 6; 38 | public static final int MODE_CONCISE_REPORT = 7; 39 | public static final Writer stdout = new BufferedWriter(new OutputStreamWriter(System.out)); 40 | public static final Writer stderr = new BufferedWriter(new OutputStreamWriter(System.err)); 41 | public static Writer out = stdout; 42 | public static Writer err = stderr; 43 | 44 | private static ThreadLocal indent = new ThreadLocal() { 45 | @Override 46 | protected String initialValue() { return ""; } 47 | }; 48 | 49 | private static void writeLog(int theLevel, String title, String msg, Writer w) { 50 | if (theLevel >= Settings.logLevel) { 51 | try { 52 | w.write(indent.get() + "[" + title + "]: " + msg + "\n"); 53 | } catch (IOException e) { 54 | // logging system should never die 55 | exit(1); 56 | } 57 | } 58 | } 59 | 60 | public static void exit(int r) { 61 | try { 62 | Log.out.close(); 63 | Log.err.close(); 64 | } catch (IOException e) { 65 | e.printStackTrace(); 66 | } 67 | System.exit(r); 68 | } 69 | 70 | protected static void log(int theLevel, String title, String msg) { 71 | writeLog(theLevel, title, msg, out); 72 | } 73 | 74 | protected static void badlog(int theLevel, String title, String msg) { 75 | switch (theLevel) { 76 | case MODE_WARNING: Report.nWarnings++; break; 77 | case MODE_SEVERE_WARNING: Report.nSevereWarnings++; break; 78 | case MODE_ERROR: Report.nErrors++; break; 79 | default: break; 80 | } 81 | writeLog(theLevel, title, msg, err); 82 | } 83 | 84 | public static void increaseIndent() { indent.set(indent.get() + " "); } 85 | public static void decreaseIndent() { indent.set(indent.get().substring(2)); } 86 | public static void resetIndent() { indent.remove(); } 87 | public static void msg(String format, Object... args) { msg(String.format(format, args)); } 88 | public static void msg(String s) { log(MODE_MSG, "MSG", s); } 89 | public static void debug(String format, Object... args) { debug(String.format(format, args)); } 90 | public static void debug(String s) { log(MODE_DEBUG, "DEBUG", s); } 91 | 92 | private static String exceptionToString(Exception e) { 93 | String s = e.toString() + "\n"; 94 | StackTraceElement[] st = e.getStackTrace(); 95 | for (StackTraceElement i : st) { 96 | s += i.toString() + "\n"; 97 | } 98 | return s; 99 | } 100 | 101 | public static void warn(String format, Object... args) { warn(String.format(format, args)); } 102 | public static void warn(Exception e) { warn(exceptionToString(e)); } 103 | public static void warn(String s) { badlog(MODE_WARNING, "WARN", s); } 104 | // forgive me for these cute names 105 | public static void warnwarn(String format, Object... args) { warnwarn(String.format(format, args)); } 106 | public static void warnwarn(String s) { badlog(MODE_SEVERE_WARNING, "WARN*", s); } 107 | public static void warnwarn(boolean b, String s) { if (!b) { warnwarn(s); } } 108 | public static void err(Exception e) { err(exceptionToString(e)); } 109 | public static void err(String format, Object... args) { err(String.format(format, args)); } 110 | public static void err(String msg) { badlog(MODE_ERROR, "ERROR", msg); } 111 | } 112 | -------------------------------------------------------------------------------- /src/main/java/patdroid/util/Pair.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Mingyuan Xia (http://mxia.me) and contributors 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 | * Contributors: 17 | * Lu Gong 18 | */ 19 | 20 | package patdroid.util; 21 | 22 | public final class Pair { 23 | final public T1 first; 24 | final public T2 second; 25 | 26 | public Pair(T1 first, T2 second) { 27 | this.first = first; 28 | this.second = second; 29 | } 30 | 31 | @Override 32 | final public boolean equals(Object o) { 33 | if (o instanceof Pair) { 34 | Pair u = (Pair)o; 35 | return first.equals(u.first) && second.equals(u.second); 36 | } 37 | return false; 38 | } 39 | 40 | @Override 41 | final public int hashCode() { 42 | return 997 * first.hashCode() ^ 991 * second.hashCode(); 43 | } 44 | 45 | @Override 46 | final public String toString() { 47 | return ("(" + first.toString() + ", " + second.toString() + ")"); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/patdroid/util/Report.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Mingyuan Xia (http://mxia.me) and contributors 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 | * Contributors: 17 | * Mingyuan Xia 18 | */ 19 | 20 | package patdroid.util; 21 | 22 | public class Report { 23 | public static int nWarnings = 0; 24 | public static int nSevereWarnings = 0; 25 | public static int nErrors = 0; 26 | } 27 | -------------------------------------------------------------------------------- /src/test/java/patdroid/core/ClassInfoTest.java: -------------------------------------------------------------------------------- 1 | package patdroid.smali; 2 | 3 | import org.junit.Assert; 4 | import org.junit.Test; 5 | import patdroid.core.ClassInfo; 6 | import patdroid.core.MethodInfo; 7 | import patdroid.core.Scope; 8 | 9 | import java.io.File; 10 | import java.util.logging.Logger; 11 | 12 | public class ClassInfoTest { 13 | private static final File FRAMEWORK_CLASSES_FOLDER = new File("apilevels"); 14 | private static final int API_LEVEL = 19; 15 | private final Scope scope = new Scope(); 16 | private static final Logger logger = Logger.getLogger(Logger.GLOBAL_LOGGER_NAME); 17 | 18 | @Test 19 | public void testClassInfo() { 20 | SmaliClassDetailLoader ldr; 21 | try { 22 | ldr = SmaliClassDetailLoader.fromFramework(FRAMEWORK_CLASSES_FOLDER, API_LEVEL); 23 | } catch (RuntimeException e) { 24 | logger.info("framework classes loader test skipped, API19 not available"); 25 | return ; 26 | } 27 | ldr.loadAll(scope); 28 | ClassInfo urlConnection = scope.findClass("java.net.URLConnection"); 29 | ClassInfo httpUrlConnection = scope.findClass("java.net.HttpURLConnection"); 30 | Assert.assertTrue((httpUrlConnection.isConvertibleTo(urlConnection))); 31 | Assert.assertFalse(urlConnection.isConvertibleTo(httpUrlConnection)); 32 | 33 | MethodInfo[] urlConnectionConnect = urlConnection.findMethodsHere("connect"); 34 | MethodInfo[] httpUrlConnectionConnect = httpUrlConnection.findMethodsHere("connect"); 35 | Assert.assertTrue(urlConnectionConnect.length == 1); 36 | Assert.assertTrue(httpUrlConnectionConnect.length == 0); 37 | 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/test/java/patdroid/core/PrimitiveTest.java: -------------------------------------------------------------------------------- 1 | package patdroid.core; 2 | 3 | import org.junit.Assert; 4 | import org.junit.Test; 5 | 6 | import static org.junit.Assert.assertEquals; 7 | 8 | public class PrimitiveTest { 9 | private static final Scope scope = new Scope(); 10 | 11 | @Test 12 | public void testPrimitiveType() { 13 | assertEquals(15, PrimitiveInfo.fromInt(scope, 15).intValue()); 14 | assertEquals(20L, PrimitiveInfo.fromLong(scope, 20L).longValue()); 15 | assertEquals(true, PrimitiveInfo.fromBoolean(scope, true).booleanValue()); 16 | assertEquals(2.5f, PrimitiveInfo.fromFloat(scope, 2.5f).floatValue(), 0.0f); 17 | assertEquals(6.8, PrimitiveInfo.fromDouble(scope, 6.8).doubleValue(), 0.0); 18 | } 19 | 20 | @Test 21 | public void testConversion() { 22 | assertEquals(15L, PrimitiveInfo.fromInt(scope, 15).castTo(scope.primitiveLong).longValue()); 23 | assertEquals(15, PrimitiveInfo.fromDouble(scope, 15.0).castTo(scope.primitiveInt).intValue()); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/test/java/patdroid/dalvik/DalvikTest.java: -------------------------------------------------------------------------------- 1 | package patdroid.dalvik; 2 | 3 | import org.junit.Assert; 4 | import org.junit.Test; 5 | 6 | public class DalvikTest { 7 | @Test 8 | public void testNameConversion() { 9 | Assert.assertEquals(Dalvik.toDalvikName("java.lang.String"), "Ljava/lang/String;"); 10 | Assert.assertEquals(Dalvik.toDalvikName("byte"), "B"); 11 | Assert.assertEquals(Dalvik.toDalvikName("[Ljava.lang.Object;"), "[Ljava/lang/Object;"); 12 | Assert.assertEquals(Dalvik.toDalvikName("[[[[[[[I"), "[[[[[[[I"); 13 | Assert.assertEquals("java.lang.String", Dalvik.toCanonicalName("Ljava/lang/String;")); 14 | Assert.assertEquals("byte", Dalvik.toCanonicalName("B")); 15 | Assert.assertEquals("[Ljava.lang.Object;", Dalvik.toCanonicalName("[Ljava/lang/Object;")); 16 | Assert.assertEquals("[[[[[[[I", Dalvik.toCanonicalName("[[[[[[[I")); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/test/java/patdroid/permission/PScoutParserTest.java: -------------------------------------------------------------------------------- 1 | package patdroid.permission; 2 | 3 | import org.junit.Assert; 4 | import org.junit.Test; 5 | 6 | public class PScoutParserTest { 7 | @Test 8 | public void testParse() { 9 | // TODO: test with 10 | // http://pscout.csl.toronto.edu/download.php?file=results/jellybean_publishedapimapping 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/test/java/patdroid/regtest/RegTest.java: -------------------------------------------------------------------------------- 1 | package patdroid.regtest; 2 | 3 | import com.google.common.collect.ImmutableList; 4 | import com.google.common.collect.Ordering; 5 | import com.google.common.io.Files; 6 | import com.google.common.io.PatternFilenameFilter; 7 | import org.junit.After; 8 | import org.junit.Before; 9 | import org.junit.Test; 10 | import org.junit.runner.RunWith; 11 | import org.junit.runners.Parameterized; 12 | import patdroid.core.ClassInfo; 13 | import patdroid.core.MethodInfo; 14 | import patdroid.core.Scope; 15 | import patdroid.dalvik.Instruction; 16 | import patdroid.smali.SmaliClassDetailLoader; 17 | 18 | import java.io.*; 19 | import java.util.*; 20 | import java.util.logging.Logger; 21 | import java.util.zip.ZipFile; 22 | 23 | import static org.junit.Assert.assertEquals; 24 | import static org.junit.Assert.assertNotNull; 25 | 26 | /** 27 | * Regression test: reads APK and compare with dump files. 28 | * 29 | *

Specify -Dregtest.apkpath= to run regression test. 30 | *

Also specify -Dregtest.updatedump=true to update dump files. 31 | */ 32 | @RunWith(Parameterized.class) 33 | public class RegTest { 34 | private static final File FRAMEWORK_CLASSES_FOLDER = new File("apilevels"); 35 | private static final int API_LEVEL = 19; 36 | private static final Logger logger = Logger.getLogger(Logger.GLOBAL_LOGGER_NAME); 37 | 38 | private final Scope scope = new Scope(); 39 | private final File apkFile; 40 | private final File dumpFile; 41 | private final boolean updateDump; 42 | private BufferedReader dumpReader; 43 | private BufferedWriter dumpWriter; 44 | private int lineNumber; 45 | 46 | public RegTest(File apkFile, File dumpFile, boolean updateDump) { 47 | this.apkFile = apkFile; 48 | this.dumpFile = dumpFile; 49 | this.updateDump = updateDump; 50 | } 51 | 52 | @Before 53 | public void setUp() throws IOException { 54 | if (updateDump) { 55 | this.dumpWriter = new BufferedWriter(new FileWriter(dumpFile)); 56 | } else { 57 | this.dumpReader = new BufferedReader(new FileReader(dumpFile)); 58 | } 59 | this.lineNumber = 0; 60 | } 61 | 62 | @After 63 | public void tearDown() throws IOException { 64 | if (updateDump) { 65 | this.dumpWriter.close(); 66 | } 67 | } 68 | 69 | @Test 70 | public void run() throws IOException { 71 | if (updateDump) { 72 | logger.info("Updating dump for " + apkFile); 73 | } else { 74 | logger.info("Running regression test for " + apkFile); 75 | } 76 | 77 | SmaliClassDetailLoader.fromFramework(FRAMEWORK_CLASSES_FOLDER, API_LEVEL).loadAll(scope); 78 | SmaliClassDetailLoader.fromApkFile(new ZipFile(apkFile), API_LEVEL, true).loadAll(scope); 79 | 80 | List sortedClasses = Ordering.usingToString().sortedCopy(scope.getAllClasses()); 81 | for (ClassInfo c : sortedClasses) { 82 | if (c.isFrameworkClass()) { 83 | continue; 84 | } 85 | handleEntry(c.toString()); 86 | if (c.isMissing()) { 87 | handleEntry("\t(missing class)"); 88 | continue; 89 | } 90 | List sortedMethods = Ordering.usingToString().sortedCopy(c.getAllMethods()); 91 | for (MethodInfo m : sortedMethods) { 92 | handleEntry("\t" + m); 93 | if (m.insns == null) { 94 | handleEntry("\t\t(no instructions)"); 95 | } else { 96 | for (Instruction i : m.insns) { 97 | handleEntry("\t\t" + i); 98 | } 99 | } 100 | } 101 | } 102 | } 103 | 104 | private void handleEntry(String entry) throws IOException { 105 | BufferedReader entryReader = new BufferedReader(new StringReader(entry)); 106 | String line; 107 | while ((line = entryReader.readLine()) != null) { 108 | ++lineNumber; 109 | if (updateDump) { 110 | this.dumpWriter.write(line); 111 | this.dumpWriter.newLine(); 112 | } else { 113 | assertEquals("line " + lineNumber, this.dumpReader.readLine(), line); 114 | } 115 | } 116 | } 117 | 118 | @Parameterized.Parameters 119 | public static List params() { 120 | String apkPath = System.getProperty("regtest.apkpath", ""); 121 | if (apkPath.isEmpty()) { 122 | logger.warning("regtest.apkpath not set, skipping regression test."); 123 | return ImmutableList.of(); 124 | } 125 | String updateDumpProperty = System.getProperty("regtest.updatedump", ""); 126 | boolean updateDump = updateDumpProperty.equalsIgnoreCase("true"); 127 | File apkDir = new File(apkPath); 128 | File[] apkFiles = apkDir.listFiles(new PatternFilenameFilter("^.*.apk")); 129 | assertNotNull("failed to list files", apkFiles); 130 | ImmutableList.Builder params = ImmutableList.builder(); 131 | for (File apkFile : apkFiles) { 132 | String dumpFileName = Files.getNameWithoutExtension(apkFile.getName()) + ".txt"; 133 | File dumpFile = new File(apkFile.getParentFile(), dumpFileName); 134 | params.add(new Object[]{apkFile, dumpFile, updateDump}); 135 | } 136 | return params.build(); 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/test/java/patdroid/smali/SmaliLoaderTest.java: -------------------------------------------------------------------------------- 1 | package patdroid.smali; 2 | 3 | import org.junit.Assert; 4 | import org.junit.Test; 5 | import patdroid.core.Scope; 6 | 7 | import java.io.File; 8 | import java.util.logging.Logger; 9 | 10 | public class SmaliLoaderTest { 11 | private static final File FRAMEWORK_CLASSES_FOLDER = new File("apilevels"); 12 | private static final int API_LEVEL = 19; 13 | private final Scope scope = new Scope(); 14 | private static final Logger logger = Logger.getLogger(Logger.GLOBAL_LOGGER_NAME); 15 | 16 | @Test 17 | public void testLoadFrameworkClasses() { 18 | SmaliClassDetailLoader ldr; 19 | try { 20 | ldr = SmaliClassDetailLoader.fromFramework(FRAMEWORK_CLASSES_FOLDER, API_LEVEL); 21 | } catch (RuntimeException e) { 22 | logger.info("framework classes loader test skipped, API19 not available"); 23 | return ; 24 | } 25 | ldr.loadAll(scope); 26 | Assert.assertNotNull(scope.findClass("android.app.Activity")); 27 | Assert.assertNotNull(scope.findClass("android.view.View")); 28 | Assert.assertTrue(scope.findClass("android.view.View").isConvertibleTo(scope.findClass("java.lang.Object"))); 29 | Assert.assertFalse(scope.findClass("java.lang.Object").isConvertibleTo(scope.findClass("android.view.View"))); 30 | Assert.assertNull(scope.findClass("android.bluetooth.le.ScanResult")); // api21 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /tools/.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | -------------------------------------------------------------------------------- /tools/README.md: -------------------------------------------------------------------------------- 1 | PATDroid also provides integrated Python tools for various analyses. 2 | This folder contains a command line entry point (`__main__.py`) and several tools prefixed by `tool_`. 3 | To use these tools, simply type `python tools/__main__.py` or just `python tools/`. 4 | The command line utility has many "environment variables". Simply type "set" to see them all or set a new one. 5 | Each tool has a single command. When invoked, the tool itself can read from these "envs" for related parameters. 6 | After completion, the tool will update these envs for use of later commands. 7 | Each tool should have a short description telling what it is about. 8 | 9 | ## Add a tool 10 | New tools are welcome! 11 | 12 | * First create your `tool_your_tool.py` and use the following snippet to integrate with the top-level CLI: 13 | 14 | ```python 15 | # a module-level cli_entry function, to be integrated by the top-level patdroid cli 16 | # it must use all keyword parameters 17 | def cli_entry(arg1=None, arg2='some string', **kwargs): 18 | """ the pydoc will be displayed as the command description""" 19 | # check arguments 20 | if arg1 is None: 21 | print('Must provide "arg1": explain arg1') 22 | return 23 | if arg2 is None: 24 | print('Must provide "arg2": explain arg2, default value: "some string"') 25 | return 26 | # do the work here 27 | # better print something indicating the progress and the final result 28 | # but dont print too much 29 | ret = { 30 | 'param_to_update': 'value' 31 | } 32 | # instruct the cli to update some parameters (e.g., the output file path) 33 | # these updates will be available to later commands 34 | # if return value is None, patdroid cli will do nothing 35 | return ret 36 | ``` 37 | 38 | * Then, add one line to the `__main__.py`, something like: 39 | ```python 40 | cli.do_your_tool = _attach_tool(tool_your_tool.cli_entry) 41 | ``` 42 | 43 | * Finally, submit a pull request. 44 | 45 | -------------------------------------------------------------------------------- /tools/__main__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # 5 | # Copyright 2016 PATDroid project (https://github.com/mingyuan-xia/PATDroid) 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | # Contributors: 20 | # Mingyuan Xia 21 | # 22 | 23 | import cmd 24 | import pprint 25 | import subprocess 26 | import tool_parse_layout 27 | 28 | 29 | class cli(cmd.Cmd): 30 | prompt = "> " 31 | intro = "patdroid integrated tools" 32 | 33 | def __init__(self): 34 | cmd.Cmd.__init__(self) 35 | self.env = {} 36 | 37 | def do_EOF(self, line): 38 | """Say goodbye""" 39 | print("Bye") 40 | return True 41 | 42 | def do_shell(self, line): 43 | """ Invoke a shell command""" 44 | subprocess.Popen(line, shell=True).communicate() 45 | 46 | def do_quit(self, line): 47 | """Say goodbye""" 48 | raise SystemExit 49 | 50 | def do_set(self, line): 51 | """ set key value; if none, print all current parameters""" 52 | data = tuple(line.strip().split(' ')) 53 | if len(data) == 2: 54 | k, v = data 55 | self.env[k] = v 56 | print('%s => %s' % (k, v)) 57 | else: 58 | pprint.pprint(self.env) 59 | 60 | def emptyline(self): 61 | # do nothing 62 | pass 63 | 64 | def _update_env(self, extras): 65 | if extras is None: 66 | return 67 | for k in extras: 68 | self.env[k] = extras[k] 69 | 70 | def _parse_extras(self, line): 71 | # TODO: 72 | return None 73 | 74 | 75 | def _attach_tool(tool_entry): 76 | def wrapper(cli, line): 77 | extras = cli._parse_extras(line) 78 | cli._update_env(extras) 79 | ret = tool_entry(**cli.env) 80 | cli._update_env(ret) 81 | wrapper.__doc__ = tool_entry.__doc__ 82 | return wrapper 83 | 84 | # all integrated tools 85 | cli.do_parse_layout = _attach_tool(tool_parse_layout.cli_entry) 86 | 87 | if __name__ == "__main__": 88 | cli().cmdloop() 89 | -------------------------------------------------------------------------------- /tools/tool_parse_layout.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # 4 | # Copyright 2016 PATDroid project (https://github.com/mingyuan-xia/PATDroid) 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # Contributors: 19 | # Zhuocheng Ding 20 | # Mingyuan Xia 21 | # 22 | 23 | import xml.etree.ElementTree as Et 24 | import json 25 | import os 26 | 27 | 28 | class LayoutAnalyzer: 29 | def __init__(self, res_dir): 30 | self._res_dir = res_dir 31 | self.on_clicks = set() 32 | self.classes = set() 33 | 34 | def parse(self, layout): 35 | tree = Et.parse(layout) 36 | for e in tree.iter(): 37 | if e.tag not in ["include", "merge", "requestFocus", "view", "fragment"]: 38 | self.classes.add(e.tag) 39 | if e.tag == "view": 40 | clazz = e.get("{http://schemas.android.com/apk/res/android}class", None) 41 | if clazz: 42 | self.classes.add(clazz) 43 | if e.tag == "fragment": 44 | clazz = e.get("{http://schemas.android.com/apk/res/android}name", None) 45 | if clazz: 46 | self.classes.add(clazz) 47 | on_click = e.get("{http://schemas.android.com/apk/res/android}onClick", None) 48 | if on_click: 49 | self.on_clicks.add(on_click) 50 | 51 | def analyze(self): 52 | """ 53 | analyze all xml files under 'layout' or 'layout-xxx' directory to collect useful information 54 | """ 55 | layout_dirs = [d for d in os.listdir(self._res_dir) 56 | if d.startswith("layout") and os.path.isdir(os.path.join(self._res_dir, d))] 57 | for layout_dir in layout_dirs: 58 | for root, _, files in os.walk(os.path.join(self._res_dir, layout_dir)): 59 | for f in files: 60 | _, ext = os.path.splitext(f) 61 | if ext != ".xml": 62 | continue 63 | layout = os.path.join(root, f) 64 | self.parse(layout) 65 | 66 | def output(self, layout_json): 67 | with open(layout_json, "w") as f: 68 | json.dump({"external_methods": {"onClick": list(self.on_clicks)}, 69 | "classes": list(self.classes)}, f, indent=4) 70 | 71 | 72 | def cli_entry(disassembled=None, layout_database='layout_database.json', **kwargs): 73 | """ Generate the layout database from a disassembled apk""" 74 | if disassembled is None: 75 | print('Must provide "disassembled": the path to the disassembled apk folder') 76 | return 77 | if layout_database is None: 78 | print('Must provide "layout_database": the file path to store the parsed layout database') 79 | return 80 | res_dir = os.path.join(disassembled, 'res') 81 | layout_json = layout_database 82 | analyzer = LayoutAnalyzer(res_dir) 83 | analyzer.analyze() 84 | analyzer.output(layout_json) 85 | print('Done, saved to %s' % (layout_database, )) 86 | ret = { 87 | 'layout_database': layout_database 88 | } 89 | return ret 90 | --------------------------------------------------------------------------------