├── .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 [](https://travis-ci.org/mingyuan-xia/PATDroid) [](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 | *
Case
Java
Dalvik
14 | *
15 | *
16 | *
String.class.getName()
"java.lang.String"
"Ljava/lang/String;"
17 | *
byte.class.getName()
"byte"
"B"
18 | *
(new Object[3]).getClass().getName()
"[Ljava.lang.Object;"
"[Ljava/lang/Object;"
19 | *
(new int[3][4][5][6][7][8][9]).getClass().getName()
"[[[[[[[I"
"[[[[[[[I"
20 | *
21 | *
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 extends Method> 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 extends Field> 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 extends CharSequence> 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 | *