LibScout-2.3.2/000077500000000000000000000000001342431362400132445ustar00rootroot00000000000000LibScout-2.3.2/.gitignore000066400000000000000000000000131342431362400152260ustar00rootroot00000000000000bin build LibScout-2.3.2/LICENSE000066400000000000000000000261361342431362400142610ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. LibScout-2.3.2/README.md000066400000000000000000000276031342431362400145330ustar00rootroot00000000000000# LibScout LibScout is a light-weight and effective static analysis tool to detect third-party libraries in Android/Java apps. The detection is resilient against common bytecode obfuscation techniques such as identifier renaming or code-based obfuscations such as reflection-based API hiding or control-flow randomization. Further, LibScout is capable of pinpointing exact library versions including versions that contain severe bugs or security issues.
LibScout requires the original library SDKs (compiled .jar/.aar files) to extract library profiles that can be used for detection on Android apps. Pre-generated library profiles are hosted at the repository [LibScout-Profiles](https://github.com/reddr/LibScout-Profiles). Unique detection features: * Library detection resilient against many kinds of bytecode obfuscation (e.g. obfuscations by ProGuard) * Capability of pinpointing the exact library version (in some cases to a set of 2-3 candidate versions) * Capability of handling dead-code elimination, by computing a similarity score against baseline SDKs * Library API usage analysis upon detection Over time LibScout has been extended to perform additional analyses both on library SDKs and detected libraries in apps: * API compatibility analysis across library versions and lib developers adherence to semantic versioning. * Library updatability analysis to infer if and to which extent detected libraries in apps can be updated without code changes based on their API usage. In addition, there is an Android Studio extension [up2dep](https://github.com/ngcuongst/up2dep) that integrates the API compatibility information into the IDE to help developers keeping their dependencies up-to-date (and more). ## Library History Scraper (./scripts) The scripts directory contains a [library-scraper](scripts/library-scraper.py) python script to automatically download original library SDKs including complete version histories from *Maven Central*, *JCenter* and *custom mvn repositories*. The original library SDKs can be used to generate profiles and to conduct library API compatibility analyses (see modules below). Use the [library-profile-generator](scripts/library-profile-generator.sh) script to conveniently generate profiles at scale. The scrapers need to be configured with a json config that includes metadata of the libraries to be fetched (name, repo, groupid, artefactid). The *scripts/library-specs* directory contains config files to retrieve over 100 libraries from maven central and a config to download Amazon libraries and Android libraries from Google's maven repository (350 libraries, including support, gms, ktx, jetpack, ..). ## Detecting (vulnerable) library versions Ready-to-use library profiles and library meta-data can be found in the repository [LibScout-Profiles](https://github.com/reddr/LibScout-Profiles). LibScout has builtin functionality to report library versions with the following security vulnerabilities.
The pre-generated profiles for vulnerable versions are tagged with [SECURITY], patches with [SECURITY-FIX].
This information is encoded in the library.xml files that have been used to generate the profiles. We try to update the list/profiles whenever we encounter new security issues. If you can share information, please let us know. | Library | Version(s) | Fix Version | Vulnerability | Link | | ---------- | ---------------:|--------------:|--------------------------------------- | --------------------------------------------------------------------------------------------------------------- | | Airpush | < 8.1 | > 8.1 | Unsanitized default WebView settings | [Link](https://support.google.com/faqs/answer/6376737) | | Apache CC | 3.2.1 / 4.0 | 3.2.2 / 4.1 | Deserialization vulnerability | [Link](http://www.kb.cert.org/vuls/id/576313) | | Dropbox | 1.5.4 - 1.6.1 | 1.6.2 | DroppedIn vulnerability | [Link](https://blogs.dropbox.com/developers/2015/03/security-bug-resolved-in-the-dropbox-sdks-for-android) | | Facebook | 3.15 | 3.16 | Account hijacking vulnerability | [Link](http://thehackernews.com/2014/07/facebook-sdk-vulnerability-puts.html) | | MoPub | < 4.4.0 | 4.4.0 | Unsanitized default WebView settings | [Link](https://support.google.com/faqs/answer/6345928) | | OkHttp | 2.1 - 2.7.4
3.0.0- 3.1.2 | 2.7.5
3.2.0 | Certificate pinning bypass | [Link](https://medium.com/square-corner-blog/vulnerability-in-okhttps-certificate-pinner-2a7326ad073b) | | Plexus Archiver | < 3.6.0 | 3.6.0 | Zip Slip vulnerability | [Link](https://github.com/snyk/zip-slip-vulnerability) | SuperSonic | < 6.3.5 | 6.3.5 | Unsafe functionality exposure via JS | [Link](https://support.google.com/faqs/answer/7126517) | | Vungle | < 3.3.0 | 3.3.0 | MitM attack vulnerability | [Link](https://support.google.com/faqs/answer/6313713) | | ZeroTurnaround | < 1.13 | 1.13 | Zip Slip vulnerability | [Link](https://github.com/snyk/zip-slip-vulnerability) ### Identified Issues On our last scan of free apps on Google Play (05/25/2017), LibScout detected >20k apps still containing one of these vulnerable lib versions. The findings have been reported to Google's [ASI program](https://developer.android.com/google/play/asi.html). Unfortunately, the report seemed to be ignored. In consequence, we manually notified many app developers. Among others, McAfee published a [Security Advisory](http://service.mcafee.com/FAQDocument.aspx?&id=TS102785) for one of their apps. ## LibScout 101 * LibScout requires Java 1.8 or higher and can be build with Gradle. * Generate a runnable jar with the gradle wrapper gradlew (Linux/MacOS) or gradlew.bat (Windows), by invoking it with the build task, e.g. ./gradlew build
The LibScout.jar is output to the build/libs directory. * Some less frequently changing options can be configured via LibScout's config file [LibScout.toml](config/LibScout.toml). * Most LibScout modules require an Android SDK (jar) to distinguish app code from framework code (via the -a switch). Refer to https://developer.android.com/studio/ for download instructions. * By default, LibScout logs to stdout. Use the -d switch to redirect output to files. The -m switch disables any text output. Depending on the operation mode (see below), LibScout's results can be written to disk in JSON format or JAVA serialization. * LibScout repo structure in a nutshell:

|_ gradlew / gradlew.bat (gradle wrappers to generate runnable LibScout.jar)
|_ assets
|    |_ library.xml (Library meta-data template)
|_ config
|    |_ LibScout.toml (LibScout's config file)
|    |_ logback.xml (log4j configuration file)
|_ data
|    |_ app-version-codes.csv (Google Play app packages with valid version codes)
|_ lib
|    Android axml
|_ scripts
|    |_ library-specs (pre-defined library specs)
|    |_ library-scraper.py   (scraper for mvn-central, jcenter, custom mvn)
|    |_ library-profile-generator.sh (convenience profile generator)
|_ src
    source directory of LibScout (de/infsec/tpl). Includes some open-source,
    third-party code to parse AXML resources / app manifests etc.
* LibScout supports different use cases implemented as modules (modes of operation). Below a detailed description for each module. ### Library Profiling (-o profile) This module generates unique library fingerprints from original lib SDKs (.jar and .aar files supported). These profiles can subsequently be used for testing whether the respective library versions are included in apps. Each library file additionally requires a library.xml that contains meta data (e.g. name, version,..). A template can be found in the assets directory. For your convenience, you can use the library scraper (./scripts) to download full library histories from Maven repositories. By default, LibScout generates hashtree-based profiles with Package and Class information (omitting methods).
java -jar LibScout.jar -o profile [-a android_sdk_jar] -x path_to_library_xml path_to_library_file
### Library Detection (-o match) Detects libraries in apps using pre-generated profiles. Optionally, LibScout also conducts an API usage analysis for detected libraries, i.e. which library APIs are used by the app or by other libraries (-u switch).
Analysis results can be written in different formats.
  1. the JSON format (-j switch), creates subfolders in the specified directory following the app package, i.e. *com.foo* will create *com/foo* subfolders. This is useful when coping with a large number of apps. For detailed information about the information stored, please refer to the JSON output specification.
  2. the serialization option (-s switch) writes stat files per app to disk (deprecated)
java -jar LibScout.jar -o match -p path_to_profiles [-a android_sdk_jar] [-u] [-j json_dir] [-m] [-d log_dir] path_to_app(s)  
### Library API compatibility analysis (-o lib_api_analysis) Analyzes changes in the documented (public) API sets of library versions.
The analysis results currently include the following information: Compliance to Semantic Versioning (SemVer), i.e. whether the change in the version string between consecutive versions (expected SemVer) matches the changes in the respective public API sets (actual SemVer). Results further include statistics about changes in API sets (additions/removals/modifcations). For removed APIs, LibScout additionally tries to infer alternative APIs (based on different features).
For the analysis, you have to provide a path to the original library SDKs. LibScout recursively searches for library jars|aars (leaf directories are expected to have at most one jar|aar file and one library.xml file). For your convenience use the library scraper. Analysis results are written to disk in JSON format (-j switch).
java -jar LibScout.jar -o lib_api_analysis [-a android_sdk_jar] [-j json_dir] path_to_lib_sdks
### Library Updatability analysis (-o updatability) This mode is an extension to the match mode. It first detects library versions in the provided apps and conducts a library usage analysis (-u is implied). In addition, it requires library API compat data (via the -l switch) as generated in the lib_api_analysis mode . Based on the lib API usage in the app and the compat info, LibScout determines the highest version that is still compatible to the set of used lib APIs.
Note: The new implementation still lacks some features, e.g. the results are currently logged but not yet written to json. See the code comments for more information.
java -jar LibScout.jar -o updatability [-a android_sdk_jar] [-j json_dir] -l lib_api_data_dir path_to_app(s)
## Scientific Publications For technical details and large-scale evaluation results, please refer to our publications:
> Reliable Third-Party Library Detection in Android and its Security Applications
> https://www.infsec.cs.uni-saarland.de/~derr/publications/pdfs/derr_ccs16.pdf > > Keep me Updated: An Empirical Study of Third-Party Library Updatability on Android
> https://www.infsec.cs.uni-saarland.de/~derr/publications/pdfs/derr_ccs17.pdf If you use LibScout in a scientific publication, we would appreciate citations using these Bibtex entries: [[bib-ccs16]](https://www.infsec.cs.uni-saarland.de/~derr/publications/bib/derr_ccs16.bib) [[bib-ccs17]](https://www.infsec.cs.uni-saarland.de/~derr/publications/bib/derr_ccs17.bib)
LibScout-2.3.2/assets/000077500000000000000000000000001342431362400145465ustar00rootroot00000000000000LibScout-2.3.2/assets/library.xml000066400000000000000000000011441342431362400167340ustar00rootroot00000000000000 LibScout-2.3.2/build.gradle000066400000000000000000000026311342431362400155250ustar00rootroot00000000000000apply plugin: 'java' repositories { mavenCentral() } configurations { provided // Make implementation extend from our provided configuration so that classes from dependencies can be bundled implementation.extendsFrom(provided) } sourceSets { main { java { srcDirs = ['src'] } } } dependencies { provided 'commons-io:commons-io:2.6' provided 'commons-cli:commons-cli:1.2' provided 'com.google.code.gson:gson:2.8.0' provided 'com.github.zafarkhaja:java-semver:0.9.0' provided 'ch.qos.logback:logback-classic:1.1.2' provided 'ch.qos.logback:logback-core:1.1.2' provided 'org.slf4j:slf4j-api:1.7.7' provided 'net.consensys.cava:cava-toml:0.5.0' provided 'com.google.guava:guava:27.0.1-jre' // WALA and dexlib provided 'com.ibm.wala:com.ibm.wala.core:1.5.1' provided 'com.ibm.wala:com.ibm.wala.dalvik:1.5.1' provided 'com.ibm.wala:com.ibm.wala.shrike:1.5.1' provided 'com.ibm.wala:com.ibm.wala.util:1.5.1' provided 'org.smali:dexlib2:2.2.6' provided files('lib/android-xml.jar') // axml relevant classes from Android SDK } jar { manifest { attributes 'Main-Class': 'de.infsec.tpl.TplCLI', 'Tool': 'LibScout', 'Version': '2.3.2' } // build fat jar from { configurations.provided.collect { it.isDirectory() ? it : zipTree(it) } } } LibScout-2.3.2/config/000077500000000000000000000000001342431362400145115ustar00rootroot00000000000000LibScout-2.3.2/config/LibScout.toml000066400000000000000000000012061342431362400171310ustar00rootroot00000000000000# # This is the LibScout configuration file in TOML format # [ reporting ] # if true, shows comment from library.xml in logs/json # upon lib detection show_comments = false [ sdk ] # path to Android SDK jar file # (recommended to use an absolute path) ## android_sdk_jar = "/path/to/sdk/android.jar" [ logging ] # path to log4j config file # (recommended to use an absolute path) log4j_config_file = "./config/logback.xml" [ packageTree ] # if set to true, the packageTree rendering uses ASCII characters # instead of box-drawing unicode characters (e.g. if the console # cannot correctly render unicode chars) ascii_rendering = false LibScout-2.3.2/config/logback.xml000066400000000000000000000033031342431362400166340ustar00rootroot00000000000000 %d{HH:mm:ss} %-5level %-25logger{0} : %msg%n appPath ./defaultApp ${appPath}.log false %d{HH:mm:ss} %-5level %-25logger{0} : %msg%n LibScout-2.3.2/data/000077500000000000000000000000001342431362400141555ustar00rootroot00000000000000LibScout-2.3.2/gradle/000077500000000000000000000000001342431362400145025ustar00rootroot00000000000000LibScout-2.3.2/gradle/wrapper/000077500000000000000000000000001342431362400161625ustar00rootroot00000000000000LibScout-2.3.2/gradle/wrapper/gradle-wrapper.properties000066400000000000000000000003101342431362400232060ustar00rootroot00000000000000distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-4.9-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists LibScout-2.3.2/gradlew000077500000000000000000000122601342431362400146200ustar00rootroot00000000000000#!/usr/bin/env sh ############################################################################## ## ## Gradle start up script for UN*X ## ############################################################################## # Attempt to set APP_HOME # Resolve links: $0 may be a link PRG="$0" # Need this for relative symlinks. while [ -h "$PRG" ] ; do ls=`ls -ld "$PRG"` link=`expr "$ls" : '.*-> \(.*\)$'` if expr "$link" : '/.*' > /dev/null; then PRG="$link" else PRG=`dirname "$PRG"`"/$link" fi done SAVED="`pwd`" cd "`dirname \"$PRG\"`/" >/dev/null APP_HOME="`pwd -P`" cd "$SAVED" >/dev/null APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS="" # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" warn () { echo "$*" } die () { echo echo "$*" echo exit 1 } # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false case "`uname`" in CYGWIN* ) cygwin=true ;; Darwin* ) darwin=true ;; MINGW* ) msys=true ;; NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD="$JAVA_HOME/jre/sh/java" else JAVACMD="$JAVA_HOME/bin/java" fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else JAVACMD="java" which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi # Increase the maximum file descriptors if we can. if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then MAX_FD="$MAX_FD_LIMIT" fi ulimit -n $MAX_FD if [ $? -ne 0 ] ; then warn "Could not set maximum file descriptor limit: $MAX_FD" fi else warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" fi fi # For Darwin, add options to specify how the application appears in the dock if $darwin; then GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" fi # For Cygwin, switch paths to Windows format before running java if $cygwin ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` SEP="" for dir in $ROOTDIRSRAW ; do ROOTDIRS="$ROOTDIRS$SEP$dir" SEP="|" done OURCYGPATTERN="(^($ROOTDIRS))" # Add a user-defined pattern to the cygpath arguments if [ "$GRADLE_CYGPATTERN" != "" ] ; then OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" fi # Now convert the arguments - kludge to limit ourselves to /bin/sh i=0 for arg in "$@" ; do CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` else eval `echo args$i`="\"$arg\"" fi i=$((i+1)) done case $i in (0) set -- ;; (1) set -- "$args0" ;; (2) set -- "$args0" "$args1" ;; (3) set -- "$args0" "$args1" "$args2" ;; (4) set -- "$args0" "$args1" "$args2" "$args3" ;; (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; esac fi # Escape application args save () { for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done echo " " } APP_ARGS=$(save "$@") # Collect all arguments for the java command, following the shell quoting and substitution rules eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then cd "$(dirname "$0")" fi exec "$JAVACMD" "$@" LibScout-2.3.2/gradlew.bat000066400000000000000000000043241342431362400153640ustar00rootroot00000000000000@if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @rem @rem ########################################################################## @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS= @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if "%ERRORLEVEL%" == "0" goto init echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto init echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :init @rem Get command-line arguments, handling Windows variants if not "%OS%" == "Windows_NT" goto win9xME_args :win9xME_args @rem Slurp the command line arguments. set CMD_LINE_ARGS= set _SKIP=2 :win9xME_args_slurp if "x%~1" == "x" goto execute set CMD_LINE_ARGS=%* :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% :end @rem End local scope for the variables with windows NT shell if "%ERRORLEVEL%"=="0" goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 exit /b 1 :mainEnd if "%OS%"=="Windows_NT" endlocal :omega LibScout-2.3.2/lib/000077500000000000000000000000001342431362400140125ustar00rootroot00000000000000LibScout-2.3.2/scripts/000077500000000000000000000000001342431362400147335ustar00rootroot00000000000000LibScout-2.3.2/scripts/library-profile-generator.sh000077500000000000000000000056421342431362400223670ustar00rootroot00000000000000#!/bin/bash # # Convenience script to generate library profiles with LibScout # Usage: $0 # # In each subdirectory of there must only be one library .jar|.aar file # with its library.xml description. The mvn scraper scripts automatically create such a file structure, e.g. # # |_OkHttp # |_3.0.0 # | |_library.xml # | |_okhttp-3.0.0.jar # |_3.0.1 # |_library.xml # |_okhttp-3.0.1.jar # # ATTENTION: # Before being able to use this script, you first have to replace the placeholder # values for the LibScout root directory and the path to the Android SDK. # # The LibScout.jar is automatically being built, if not existing. # Change $JOBS to run multiple LibScout instances in parallel. # The profiles are emitted to $LIBSCOUT_ROOT/profiles # # @author Erik Derr [derr@cs.uni-saarland.de] # # LibScout dir and arguments LIBSCOUT_ROOT="" # path to the LibScout root directory LIBSCOUT="$LIBSCOUT_ROOT/build/libs/LibScout.jar" ANDROID_SDK="" # argument: path to Android SDK LOG_DIR="" # optional argument: enable logging via "-d " JOBS=2 # Number of parallel instances GRADLE_BUILD="$LIBSCOUT_ROOT/gradlew build" LIBXML="library.xml" function usage() { echo "Usage $0 " exit 0 } function seconds2Time() { H=$(($1/60/60%24)) M=$(($1/60%60)) S=$(($1%60)) if [ ${H} != 0 ]; then echo ${H} h ${M} min ${S} sec elif [ ${M} != 0 ]; then echo ${M} min ${S} sec else echo ${S} sec fi } ## 1. check for variables if [ $LIBSCOUT_ROOT = "" ]; then echo "Please set the path to LibScout.jar via the \"LIBSCOUT_ROOT\" variable and retry." exit 1 fi if [ $ANDROID_SDK = "" ]; then echo "Please set the path to the Android SDK via the \"ANDROID_SDK\" variable and retry." exit 1 fi ## 2. process command line args if [ $# -gt 1 -o $# -eq 0 ]; then usage elif [ $# -eq 1 ]; then LIBDIR=$1 if [ ! -d $LIBDIR ]; then echo "[error] $LIBDIR is not a directory"! usage fi fi # change to libscout root CUR_DIR=`pwd` cd $LIBSCOUT_ROOT ## 3. generate LibScout.jar if not existing if [ ! -e $LIBSCOUT ]; then echo -n "[info] $LIBSCOUT does not exist, generating jar file now..." $GRADLE_BUILD > /dev/null if [ $? != 0 ]; then echo "[failed]" exit $rc; fi echo "[done]" fi ## 4. generate library profiles echo "= Generating library profiles =" STARTTIME=$(date +%s) # run $JOBS instances in parallel echo "# `find $LIBDIR -type f -name $LIBXML| wc -l` library.xml files found in $LIBDIR" find $LIBDIR -type f -name $LIBXML | parallel --no-notice --jobs $JOBS "echo \" - gen profile: {//}\" ; java -jar $LIBSCOUT -o profile -m -a $ANDROID_SDK $LOG_DIR -x {} {//}" ENDTIME=$(date +%s) echo echo "# processing done in `seconds2Time $[ $ENDTIME - $STARTTIME ]`" cd $CUR_DIR # restore old dir LibScout-2.3.2/scripts/library-scraper.py000077500000000000000000000230371342431362400204160ustar00rootroot00000000000000#!/usr/bin/python # # Scraper for libraries hosted at jcenter / mvn central / custom maven repos # Retrieves jar|aar files along with some meta data # # @author erik derr [derr@cs.uni-saarland.de] # import sys import json from urllib.request import urlopen from urllib.error import URLError from urllib.error import HTTPError import datetime import os import errno import zipfile import traceback import xml.etree.ElementTree as ElementTree from retrying import retry ## functions ## def unix2Date(unixTime): unixTime = int(str(unixTime)[:-3]) return datetime.datetime.fromtimestamp(unixTime).strftime('%d.%m.%Y') def make_sure_path_exists(path): try: os.makedirs(path) except OSError as exception: if exception.errno != errno.EEXIST: raise def write_library_description(fileName, libName, category, version, date, comment): make_sure_path_exists(os.path.dirname(fileName)) # write lib description in xml format with open(fileName, "w") as desc: desc.write("\n") desc.write("\n") desc.write(" \n") desc.write(" {}\n".format(libName)) desc.write("\n") desc.write(" \n") desc.write(" {}\n".format(category)) desc.write("\n") desc.write(" \n") desc.write(" {}\n".format(version)) desc.write("\n") desc.write(" \n") desc.write(" {}\n".format(date)) desc.write("\n") desc.write(" \n") desc.write(" {}\n".format(comment)) desc.write("\n") @retry(URLError, tries=3, delay=3, backoff=1) def urlopen_with_retry(URL): return urlopen(URL) def downloadLibFile(targetDir, repo, groupid, artefactid, version, filetype): make_sure_path_exists(os.path.dirname(targetDir + "/")) # assemble download URL artefactid_r = artefactid.replace(".","/") groupid_r = groupid.replace(".","/") if repo == MVN_CENTRAL: repoURL = "http://search.maven.org/remotecontent?filepath=" fileName = artefactid_r + "-" + version + "." + filetype else: repoURL = repo fileName = artefactid + "-" + version + "." + filetype # retrieve and save file URL = repoURL + groupid_r + "/" + artefactid_r + "/" + version + "/" + fileName targetFile = targetDir + "/" + fileName try: libFile = urlopen(URL) with open(targetFile,'wb') as output: output.write(libFile.read()) return 0 except HTTPError as e: if filetype != 'aar': print(' !! HTTP Error while retrieving ' + filetype + ' file: ' + str(e.code)) return 1 except URLError as e: print(' !! URL Error while retrieving ' + filetype + ' file: ' + str(e.reason)) return 1 except Exception as excp: print(' !! Download failed: ' + str(excp)) return 1 ## library updating routine for mvn central def updateLibraryMvnCentral(libName, category, comment, groupId, artefactId): # replace all blanks with dash libName = libName.replace(" ", "-") print(" # check library " + libName + " [" + category + "] (g:\"" + groupId + "\" AND a:\"" + artefactId + "\")") baseDirName = localRepoDir + category + "/" + libName + "/" dir = os.path.dirname(baseDirName) make_sure_path_exists(dir); # Assemble mvn central search URL and retrieve meta data try: mvnSearchURL = "http://search.maven.org/solrsearch/select?q=g:%22" + groupId + "%22+AND+a:%22" + artefactId + "%22&rows=100&core=gav" response = urlopen(mvnSearchURL) data = json.loads(response.read()) except URLError as e: print('URLError = ' + str(e.reason)) return except Exception as excp: print('Could not retrieve meta data for ' + libName + ' [SKIP] (' + str(excp) + ')') return # DEBUG: pretty print json #print(json.dumps(data, indent=4, sort_keys=True)) #print() numberOfVersions = data["response"]["numFound"] print(" - retrieved meta data for " + str(numberOfVersions) + " versions:") numberOfUpdates = 0 if numberOfVersions > 0: for version in data["response"]["docs"]: if skipAlphaBeta and any(x in version["v"].lower() for x in SKIP_KEYWORDS): continue; # skip lib version if already existing if not os.path.isfile(baseDirName + "/" + version["v"] + "/" + LIB_DESCRIPTOR_FILE_NAME): numberOfUpdates += 1 date = unix2Date(version["timestamp"]) targetDir = baseDirName + version["v"] print(" - update version: {} type: {} date: {} target-dir: {}".format(version["v"], version["p"], date, targetDir)) result = downloadLibFile(targetDir, MVN_CENTRAL, groupId, artefactId, version["v"], "aar") if result == 1: result = downloadLibFile(targetDir, MVN_CENTRAL, groupId, artefactId, version["v"], "jar") if result == 0: # write lib description fileName = targetDir + "/" + LIB_DESCRIPTOR_FILE_NAME write_library_description(fileName, libName, category, version["v"], date, comment) if numberOfUpdates == 0: print(" -> all versions up-to-date") ## library updating routine for jcenter + custom mvn repos def updateLibrary(libName, category, comment, repoURL, groupId, artefactId): # replace all blanks with dash libName = libName.replace(" ", "-") print(" # check library " + libName + " [" + category + "] (g:\"" + groupId + "\" AND a:\"" + artefactId + "\")") baseDirName = localRepoDir + category + "/" + libName + "/" dir = os.path.dirname(baseDirName) make_sure_path_exists(dir); # Assemble base URL and retrieve meta data try: if repoURL == "jcenter": repoURL = JCENTER_URL if not repoURL.endswith("/"): repoURL = repoURL + "/" metaURL = repoURL + groupId.replace(".","/") + "/" + artefactId.replace(".","/") + "/maven-metadata.xml" response = urlopen(metaURL) data = response.read() response.close() except URLError as e: print('URLError = ' + str(e.reason)) return except Exception as excp: print('Could not retrieve meta data for ' + libName + ' [SKIP] (' + str(excp) + ')') return # retrieve available versions versions = [] root = ElementTree.fromstring(data) for vg in root.find('versioning'): for v in vg.iter('version'): if not skipAlphaBeta or (skipAlphaBeta and not any(x in v.text.lower() for x in SKIP_KEYWORDS)): versions.append(v.text) numberOfVersions = len(versions) print(" - retrieved meta data for " + str(numberOfVersions) + " versions:") numberOfUpdates = 0 if numberOfVersions > 0: for version in versions: # skip lib version if already existing if not os.path.isfile(baseDirName + "/" + version + "/" + LIB_DESCRIPTOR_FILE_NAME): numberOfUpdates += 1 targetDir = baseDirName + version fileType = "aar" result = downloadLibFile(targetDir, repoURL, groupId, artefactId, version, fileType) if result == 1: fileType = "jar" result = downloadLibFile(targetDir, repoURL, groupId, artefactId, version, fileType) if result == 0: print(" - update version: {} type: {} date: {} target-dir: {}".format(version, fileType, "n/a", targetDir)) fileName = targetDir + "/" + LIB_DESCRIPTOR_FILE_NAME write_library_description(fileName, libName, category, version, "", comment) if numberOfUpdates == 0: print(" -> all versions up-to-date") ## Main functionality ## LIB_DESCRIPTOR_FILE_NAME = "library.xml" JCENTER = "jcenter" JCENTER_URL = "http://jcenter.bintray.com" MVN_CENTRAL = "mvn-central" SKIP_KEYWORDS = ['-alpha', '-prealpha', '-beta', '-rc', '-dev', '-snapshot'] skipAlphaBeta = True # skip alpha and beta versions localRepoDir = "my-lib-repo/" # the directory to store libraries on disk with trailing path separator, e.g. "/" print("== maven/jcenter scraper ==") # Requires one argument (path to json file with library descriptions) args = len(sys.argv) if args != 2: print("Usage: " + sys.argv[0] + " ") sys.exit(1) else: inputFile = sys.argv[1] print("- Load library info from " + sys.argv[1]) print("- Store libs to " + localRepoDir) # load library definitions with open(inputFile) as ifile: data = json.load(ifile) # update each lib print("- Update libraries" + (" (skip alpha/beta versions)" if skipAlphaBeta else "") + ":") for lib in data["libraries"]: if 'repo' not in lib: print("[WARN] Skip library: " + lib["name"] + " (No repo defined!)") continue elif lib['repo'] == MVN_CENTRAL: updateLibraryMvnCentral(lib["name"], lib["category"], lib["comment"], lib["groupid"], lib["artefactid"]) else: # jcenter or custom mvn repo URL updateLibrary(lib["name"], lib["category"], lib["comment"], lib['repo'], lib["groupid"], lib["artefactid"]) LibScout-2.3.2/scripts/library-specs/000077500000000000000000000000001342431362400175125ustar00rootroot00000000000000LibScout-2.3.2/scripts/library-specs/amazon-libraries.json000066400000000000000000000204461342431362400236520ustar00rootroot00000000000000{ "libraries": [ { "name": "Amazon AWS Mobile Analytics", "category": "Analytics", "comment": "With Amazon Mobile Analytics, you can measure app usage and app revenue.", "groupid": "com.amazonaws", "artefactid": "aws-android-sdk-mobileanalytics", "repo": "mvn-central" },{ "name": "Amazon AWS Mobile SDK core", "category": "Cloud", "comment": "", "groupid": "com.amazonaws", "artefactid": "aws-android-sdk-core", "repo": "mvn-central" },{ "name": "Amazon API Gateway", "category": "Cloud", "comment": "API Gateway is an AWS service that enables developers to create, publish, maintain, monitor, and secure APIs at any scale.", "groupid": "com.amazonaws", "artefactid": "aws-android-sdk-apigateway-core", "repo": "mvn-central" },{ "name": "Amazon AWS IoT", "category": "Cloud", "comment": "AWS IoT is a managed cloud platform that lets connected devices easily and securely interact with cloud applications and other devices", "groupid": "com.amazonaws", "artefactid": "aws-android-sdk-iot", "repo": "mvn-central" },{ "name": "Amazon AWS Elastic Beanstalk", "category": "Cloud", "comment": "With Elastic Beanstalk, you can quickly deploy and manage applications in the AWS Cloud without worrying about the infrastructure that runs those applications.", "groupid": "com.amazonaws", "artefactid": "aws-android-sdk-elb", "repo": "mvn-central" },{ "name": "Amazon SimpleDB", "category": "Cloud", "comment": "Amazon SimpleDB is a highly available NoSQL data store that offloads the work of database administration.", "groupid": "com.amazonaws", "artefactid": "aws-android-sdk-sdb", "repo": "mvn-central" },{ "name": "Amazon Rekognition", "category": "Cloud", "comment": "Amazon Rekognition is a service that enables you to add image analysis to your applications.", "groupid": "com.amazonaws", "artefactid": "aws-android-sdk-rekognition", "repo": "mvn-central" },{ "name": "Amazon Cloud Watch", "category": "Cloud", "comment": "Amazon CloudWatch is a monitoring service for AWS cloud resources and the applications you run on AWS.", "groupid": "com.amazonaws", "artefactid": "aws-android-sdk-cloudwatch", "repo": "mvn-central" },{ "name": "Amazon Cognito Identity Provider", "category": "Cloud", "comment": "", "groupid": "com.amazonaws", "artefactid": "aws-android-sdk-cognitoidentityprovider", "repo": "mvn-central" },{ "name": "Amazon Cognito Auth", "category": "Cloud", "comment": "", "groupid": "com.amazonaws", "artefactid": "aws-android-sdk-cognitoauth", "repo": "mvn-central" },{ "name": "Amazon Cognito Sync", "category": "Cloud", "comment": "Using Amazon Cognito Identity, you can create unique identities for your users and authenticate them for secure access to your AWS resources", "groupid": "com.amazonaws", "artefactid": "aws-android-sdk-cognito", "repo": "mvn-central" },{ "name": "Amazon DynamoDB", "category": "Cloud", "comment": "DynamoDB is a fast, highly scalable, highly available, cost-effective, nonrelational database service.", "groupid": "com.amazonaws", "artefactid": "aws-android-sdk-ddb", "repo": "mvn-central" },{ "name": "Amazon DynamoDB Document", "category": "Cloud", "comment": "", "groupid": "com.amazonaws", "artefactid": "aws-android-sdk-ddb-document", "repo": "mvn-central" },{ "name": "Amazon DynamoDB Object Mapper", "category": "Cloud", "comment": "DynamoDB Object Mapper lets you map client-side classes to DynamoDB tables; perform various create, read, update, and delete (CRUD) operations; and execute queries.", "groupid": "com.amazonaws", "artefactid": "aws-android-sdk-ddb-mapper", "repo": "mvn-central" },{ "name": "Amazon Elastic Compute Cloud (EC2)", "category": "Cloud", "comment": "Amazon Elastic Compute Cloud (Amazon EC2) is a web service that provides secure, resizable compute capacity in the cloud.", "groupid": "com.amazonaws", "artefactid": "aws-android-sdk-ec2", "repo": "mvn-central" },{ "name": "Amazon Kinesis Streams", "category": "Cloud", "comment": "Amazon Kinesis is a fully managed service for real-time processing of streaming data at massive scale.", "groupid": "com.amazonaws", "artefactid": "aws-android-sdk-kinesis", "repo": "mvn-central" },{ "name": "Amazon Machine Learning", "category": "Cloud", "comment": "Amazon Machine Learning is a service that makes it easy for developers of all skill levels to use machine learning technology.", "groupid": "com.amazonaws", "artefactid": "aws-android-sdk-machinelearning", "repo": "mvn-central" },{ "name": "Amazon Polly", "category": "Cloud", "comment": "Add Text to Speech Capability to your Android App with Amazon Polly", "groupid": "com.amazonaws", "artefactid": "aws-android-sdk-polly", "repo": "mvn-central" },{ "name": "Amazon Pinpoint", "category": "Cloud", "comment": "Create Push Notification Campaigns with Amazon Pinpoint", "groupid": "com.amazonaws", "artefactid": "aws-android-sdk-pinpoint", "repo": "mvn-central" },{ "name": "Amazon Simple Storage Service (S3)", "category": "Cloud", "comment": "Amazon S3 provides secure, durable, highly-scalable object storage in the cloud.", "groupid": "com.amazonaws", "artefactid": "aws-android-sdk-s3", "repo": "mvn-central" },{ "name": "Amazon Simple Email Service (SES)", "category": "Cloud", "comment": "SES eliminates the complexity and expense of building an in-house email solution.", "groupid": "com.amazonaws", "artefactid": "aws-android-sdk-ses", "repo": "mvn-central" },{ "name": "Amazon Simple Notification Service (SNS)", "category": "Cloud", "comment": "SNS is a flexible, fully managed pub/sub messaging and mobile notifications service for coordinating the delivery of messages to subscribing endpoints and clients.", "groupid": "com.amazonaws", "artefactid": "aws-android-sdk-sns", "repo": "mvn-central" },{ "name": "Amazon Simple Queue Service (SQS)", "category": "Cloud", "comment": "SQS is a fully managed message queuing service that makes it easy to decouple and scale microservices, distributed systems, and serverless applications.", "groupid": "com.amazonaws", "artefactid": "aws-android-sdk-sqs", "repo": "mvn-central" },{ "name": "Amazon AWS Auto Scaling", "category": "Cloud", "comment": "Auto Scaling helps you maintain application availability and allows you to dynamically scale your Amazon EC2 capacity up or down automatically according to conditions you define.", "groupid": "com.amazonaws", "artefactid": "aws-android-sdk-autoscaling", "repo": "mvn-central" },{ "name": "Amazon AWS Key Management Service (KMS)", "category": "Cloud", "comment": "KMS is a managed service that makes it easy for you to create and control the encryption keys used to encrypt your data, and uses Hardware Security Modules (HSMs) to protect the security of your keys.", "groupid": "com.amazonaws", "artefactid": "aws-android-sdk-kms", "repo": "mvn-central" },{ "name": "Amazon AWS Lambda", "category": "Cloud", "comment": "Execute Code On Demand with Amazon Lambda", "groupid": "com.amazonaws", "artefactid": "aws-android-sdk-lambda", "repo": "mvn-central" },{ "name": "Amazon Lex", "category": "Cloud", "comment": "Amazon Lex is an AWS service for building conversational interfaces for any applications using voice and text.", "groupid": "com.amazonaws", "artefactid": "aws-android-sdk-lex", "repo": "mvn-central" } ] } LibScout-2.3.2/scripts/library-specs/google-libraries.json000066400000000000000000002624761342431362400236540ustar00rootroot00000000000000{ "libraries": [ { "artefactid": "constraint-layout-solver", "category": "Android", "comment": "", "groupid": "com.android.support.constraint", "name": "com.android.support.constraint::constraint-layout-solver", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "constraint-layout", "category": "Android", "comment": "", "groupid": "com.android.support.constraint", "name": "com.android.support.constraint::constraint-layout", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "library", "category": "Android", "comment": "", "groupid": "com.android.databinding", "name": "com.android.databinding::library", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "adapters", "category": "Android", "comment": "", "groupid": "com.android.databinding", "name": "com.android.databinding::adapters", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "compiler", "category": "Android", "comment": "", "groupid": "com.android.databinding", "name": "com.android.databinding::compiler", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "compilerCommon", "category": "Android", "comment": "", "groupid": "com.android.databinding", "name": "com.android.databinding::compilerCommon", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "baseLibrary", "category": "Android", "comment": "", "groupid": "com.android.databinding", "name": "com.android.databinding::baseLibrary", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "support-compat", "category": "Android", "comment": "", "groupid": "com.android.support", "name": "com.android.support::support-compat", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "leanback-v17", "category": "Android", "comment": "", "groupid": "com.android.support", "name": "com.android.support::leanback-v17", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "recommendation", "category": "Android", "comment": "", "groupid": "com.android.support", "name": "com.android.support::recommendation", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "support-tv-provider", "category": "Android", "comment": "", "groupid": "com.android.support", "name": "com.android.support::support-tv-provider", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "support-vector-drawable", "category": "Android", "comment": "", "groupid": "com.android.support", "name": "com.android.support::support-vector-drawable", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "recyclerview-v7", "category": "Android", "comment": "", "groupid": "com.android.support", "name": "com.android.support::recyclerview-v7", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "preference-leanback-v17", "category": "Android", "comment": "", "groupid": "com.android.support", "name": "com.android.support::preference-leanback-v17", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "preference-v14", "category": "Android", "comment": "", "groupid": "com.android.support", "name": "com.android.support::preference-v14", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "percent", "category": "Android", "comment": "", "groupid": "com.android.support", "name": "com.android.support::percent", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "support-media-compat", "category": "Android", "comment": "", "groupid": "com.android.support", "name": "com.android.support::support-media-compat", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "cardview-v7", "category": "Android", "comment": "", "groupid": "com.android.support", "name": "com.android.support::cardview-v7", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "wearable", "category": "Android", "comment": "", "groupid": "com.android.support", "name": "com.android.support::wearable", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "exifinterface", "category": "Android", "comment": "", "groupid": "com.android.support", "name": "com.android.support::exifinterface", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "support-annotations", "category": "Android", "comment": "", "groupid": "com.android.support", "name": "com.android.support::support-annotations", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "appcompat-v7", "category": "Android", "comment": "", "groupid": "com.android.support", "name": "com.android.support::appcompat-v7", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "palette-v7", "category": "Android", "comment": "", "groupid": "com.android.support", "name": "com.android.support::palette-v7", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "multidex-instrumentation", "category": "Android", "comment": "", "groupid": "com.android.support", "name": "com.android.support::multidex-instrumentation", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "multidex", "category": "Android", "comment": "", "groupid": "com.android.support", "name": "com.android.support::multidex", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "mediarouter-v7", "category": "Android", "comment": "", "groupid": "com.android.support", "name": "com.android.support::mediarouter-v7", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "preference-v7", "category": "Android", "comment": "", "groupid": "com.android.support", "name": "com.android.support::preference-v7", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "support-dynamic-animation", "category": "Android", "comment": "", "groupid": "com.android.support", "name": "com.android.support::support-dynamic-animation", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "support-fragment", "category": "Android", "comment": "", "groupid": "com.android.support", "name": "com.android.support::support-fragment", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "design", "category": "Android", "comment": "", "groupid": "com.android.support", "name": "com.android.support::design", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "transition", "category": "Android", "comment": "", "groupid": "com.android.support", "name": "com.android.support::transition", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "customtabs", "category": "Android", "comment": "", "groupid": "com.android.support", "name": "com.android.support::customtabs", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "support-core-ui", "category": "Android", "comment": "", "groupid": "com.android.support", "name": "com.android.support::support-core-ui", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "gridlayout-v7", "category": "Android", "comment": "", "groupid": "com.android.support", "name": "com.android.support::gridlayout-v7", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "animated-vector-drawable", "category": "Android", "comment": "", "groupid": "com.android.support", "name": "com.android.support::animated-vector-drawable", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "support-core-utils", "category": "Android", "comment": "", "groupid": "com.android.support", "name": "com.android.support::support-core-utils", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "support-v13", "category": "Android", "comment": "", "groupid": "com.android.support", "name": "com.android.support::support-v13", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "instantvideo", "category": "Android", "comment": "", "groupid": "com.android.support", "name": "com.android.support::instantvideo", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "support-v4", "category": "Android", "comment": "", "groupid": "com.android.support", "name": "com.android.support::support-v4", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "support-emoji", "category": "Android", "comment": "", "groupid": "com.android.support", "name": "com.android.support::support-emoji", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "wear", "category": "Android", "comment": "", "groupid": "com.android.support", "name": "com.android.support::wear", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "support-emoji-appcompat", "category": "Android", "comment": "", "groupid": "com.android.support", "name": "com.android.support::support-emoji-appcompat", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "support-emoji-bundled", "category": "Android", "comment": "", "groupid": "com.android.support", "name": "com.android.support::support-emoji-bundled", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "support-content", "category": "Android", "comment": "", "groupid": "com.android.support", "name": "com.android.support::support-content", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "design-bottomnavigation", "category": "Android", "comment": "", "groupid": "com.android.support", "name": "com.android.support::design-bottomnavigation", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "design-button", "category": "Android", "comment": "", "groupid": "com.android.support", "name": "com.android.support::design-button", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "design-circularreveal-cardview", "category": "Android", "comment": "", "groupid": "com.android.support", "name": "com.android.support::design-circularreveal-cardview", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "design-bottomappbar", "category": "Android", "comment": "", "groupid": "com.android.support", "name": "com.android.support::design-bottomappbar", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "design-card", "category": "Android", "comment": "", "groupid": "com.android.support", "name": "com.android.support::design-card", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "design-shape", "category": "Android", "comment": "", "groupid": "com.android.support", "name": "com.android.support::design-shape", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "design-drawable", "category": "Android", "comment": "", "groupid": "com.android.support", "name": "com.android.support::design-drawable", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "design-bottomsheet", "category": "Android", "comment": "", "groupid": "com.android.support", "name": "com.android.support::design-bottomsheet", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "design-floatingactionbutton", "category": "Android", "comment": "", "groupid": "com.android.support", "name": "com.android.support::design-floatingactionbutton", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "design-circularreveal-coordinatorlayout", "category": "Android", "comment": "", "groupid": "com.android.support", "name": "com.android.support::design-circularreveal-coordinatorlayout", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "design-textfield", "category": "Android", "comment": "", "groupid": "com.android.support", "name": "com.android.support::design-textfield", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "design-stateful", "category": "Android", "comment": "", "groupid": "com.android.support", "name": "com.android.support::design-stateful", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "design-circularreveal", "category": "Android", "comment": "", "groupid": "com.android.support", "name": "com.android.support::design-circularreveal", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "design-expandable", "category": "Android", "comment": "", "groupid": "com.android.support", "name": "com.android.support::design-expandable", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "design-navigation", "category": "Android", "comment": "", "groupid": "com.android.support", "name": "com.android.support::design-navigation", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "design-dialog", "category": "Android", "comment": "", "groupid": "com.android.support", "name": "com.android.support::design-dialog", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "design-canvas", "category": "Android", "comment": "", "groupid": "com.android.support", "name": "com.android.support::design-canvas", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "design-tabs", "category": "Android", "comment": "", "groupid": "com.android.support", "name": "com.android.support::design-tabs", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "design-chip", "category": "Android", "comment": "", "groupid": "com.android.support", "name": "com.android.support::design-chip", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "design-snackbar", "category": "Android", "comment": "", "groupid": "com.android.support", "name": "com.android.support::design-snackbar", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "design-theme", "category": "Android", "comment": "", "groupid": "com.android.support", "name": "com.android.support::design-theme", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "design-math", "category": "Android", "comment": "", "groupid": "com.android.support", "name": "com.android.support::design-math", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "design-transformation", "category": "Android", "comment": "", "groupid": "com.android.support", "name": "com.android.support::design-transformation", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "design-widget", "category": "Android", "comment": "", "groupid": "com.android.support", "name": "com.android.support::design-widget", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "design-animation", "category": "Android", "comment": "", "groupid": "com.android.support", "name": "com.android.support::design-animation", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "design-typography", "category": "Android", "comment": "", "groupid": "com.android.support", "name": "com.android.support::design-typography", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "design-color", "category": "Android", "comment": "", "groupid": "com.android.support", "name": "com.android.support::design-color", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "design-internal", "category": "Android", "comment": "", "groupid": "com.android.support", "name": "com.android.support::design-internal", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "design-resources", "category": "Android", "comment": "", "groupid": "com.android.support", "name": "com.android.support::design-resources", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "design-ripple", "category": "Android", "comment": "", "groupid": "com.android.support", "name": "com.android.support::design-ripple", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "coordinatorlayout", "category": "Android", "comment": "", "groupid": "com.android.support", "name": "com.android.support::coordinatorlayout", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "collections", "category": "Android", "comment": "", "groupid": "com.android.support", "name": "com.android.support::collections", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "slidingpanelayout", "category": "Android", "comment": "", "groupid": "com.android.support", "name": "com.android.support::slidingpanelayout", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "asynclayoutinflater", "category": "Android", "comment": "", "groupid": "com.android.support", "name": "com.android.support::asynclayoutinflater", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "slices-view", "category": "Android", "comment": "", "groupid": "com.android.support", "name": "com.android.support::slices-view", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "recyclerview-selection", "category": "Android", "comment": "", "groupid": "com.android.support", "name": "com.android.support::recyclerview-selection", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "viewpager", "category": "Android", "comment": "", "groupid": "com.android.support", "name": "com.android.support::viewpager", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "cursoradapter", "category": "Android", "comment": "", "groupid": "com.android.support", "name": "com.android.support::cursoradapter", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "localbroadcastmanager", "category": "Android", "comment": "", "groupid": "com.android.support", "name": "com.android.support::localbroadcastmanager", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "heifwriter", "category": "Android", "comment": "", "groupid": "com.android.support", "name": "com.android.support::heifwriter", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "customview", "category": "Android", "comment": "", "groupid": "com.android.support", "name": "com.android.support::customview", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "print", "category": "Android", "comment": "", "groupid": "com.android.support", "name": "com.android.support::print", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "slices-builders", "category": "Android", "comment": "", "groupid": "com.android.support", "name": "com.android.support::slices-builders", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "interpolator", "category": "Android", "comment": "", "groupid": "com.android.support", "name": "com.android.support::interpolator", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "slices-core", "category": "Android", "comment": "", "groupid": "com.android.support", "name": "com.android.support::slices-core", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "loader", "category": "Android", "comment": "", "groupid": "com.android.support", "name": "com.android.support::loader", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "swiperefreshlayout", "category": "Android", "comment": "", "groupid": "com.android.support", "name": "com.android.support::swiperefreshlayout", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "drawerlayout", "category": "Android", "comment": "", "groupid": "com.android.support", "name": "com.android.support::drawerlayout", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "documentfile", "category": "Android", "comment": "", "groupid": "com.android.support", "name": "com.android.support::documentfile", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "webkit", "category": "Android", "comment": "", "groupid": "com.android.support", "name": "com.android.support::webkit", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "car", "category": "Android", "comment": "", "groupid": "com.android.support", "name": "com.android.support::car", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "versionedparcelable", "category": "Android", "comment": "", "groupid": "com.android.support", "name": "com.android.support::versionedparcelable", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "compiler", "category": "Android", "comment": "", "groupid": "android.arch.persistence.room", "name": "android.arch.persistence.room::compiler", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "support-db-impl", "category": "Android", "comment": "", "groupid": "android.arch.persistence.room", "name": "android.arch.persistence.room::support-db-impl", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "runtime", "category": "Android", "comment": "", "groupid": "android.arch.persistence.room", "name": "android.arch.persistence.room::runtime", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "support-db", "category": "Android", "comment": "", "groupid": "android.arch.persistence.room", "name": "android.arch.persistence.room::support-db", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "migration", "category": "Android", "comment": "", "groupid": "android.arch.persistence.room", "name": "android.arch.persistence.room::migration", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "rxjava2", "category": "Android", "comment": "", "groupid": "android.arch.persistence.room", "name": "android.arch.persistence.room::rxjava2", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "common", "category": "Android", "comment": "", "groupid": "android.arch.persistence.room", "name": "android.arch.persistence.room::common", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "db", "category": "Android", "comment": "", "groupid": "android.arch.persistence.room", "name": "android.arch.persistence.room::db", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "db-impl", "category": "Android", "comment": "", "groupid": "android.arch.persistence.room", "name": "android.arch.persistence.room::db-impl", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "guava", "category": "Android", "comment": "", "groupid": "android.arch.persistence.room", "name": "android.arch.persistence.room::guava", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "compiler", "category": "Android", "comment": "", "groupid": "android.arch.lifecycle", "name": "android.arch.lifecycle::compiler", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "runtime", "category": "Android", "comment": "", "groupid": "android.arch.lifecycle", "name": "android.arch.lifecycle::runtime", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "extensions", "category": "Android", "comment": "", "groupid": "android.arch.lifecycle", "name": "android.arch.lifecycle::extensions", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "reactivestreams", "category": "Android", "comment": "", "groupid": "android.arch.lifecycle", "name": "android.arch.lifecycle::reactivestreams", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "common", "category": "Android", "comment": "", "groupid": "android.arch.lifecycle", "name": "android.arch.lifecycle::common", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "common-java8", "category": "Android", "comment": "", "groupid": "android.arch.lifecycle", "name": "android.arch.lifecycle::common-java8", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "viewmodel", "category": "Android", "comment": "", "groupid": "android.arch.lifecycle", "name": "android.arch.lifecycle::viewmodel", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "livedata-core", "category": "Android", "comment": "", "groupid": "android.arch.lifecycle", "name": "android.arch.lifecycle::livedata-core", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "livedata", "category": "Android", "comment": "", "groupid": "android.arch.lifecycle", "name": "android.arch.lifecycle::livedata", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "core", "category": "Android", "comment": "", "groupid": "android.arch.core", "name": "android.arch.core::core", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "runtime", "category": "Android", "comment": "", "groupid": "android.arch.core", "name": "android.arch.core::runtime", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "common", "category": "Android", "comment": "", "groupid": "android.arch.core", "name": "android.arch.core::common", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "instantapps", "category": "Android", "comment": "", "groupid": "com.google.android.instantapps", "name": "com.google.android.instantapps::instantapps", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "volleycompat", "category": "Android", "comment": "", "groupid": "com.google.android.instantapps.thirdpartycompat", "name": "com.google.android.instantapps.thirdpartycompat::volleycompat", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "dvlib", "category": "Android", "comment": "", "groupid": "com.android.tools", "name": "com.android.tools::dvlib", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "sdklib", "category": "Android", "comment": "", "groupid": "com.android.tools", "name": "com.android.tools::sdklib", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "repository", "category": "Android", "comment": "", "groupid": "com.android.tools", "name": "com.android.tools::repository", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "annotations", "category": "Android", "comment": "", "groupid": "com.android.tools", "name": "com.android.tools::annotations", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "devicelib", "category": "Android", "comment": "", "groupid": "com.android.tools", "name": "com.android.tools::devicelib", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "sdk-common", "category": "Android", "comment": "", "groupid": "com.android.tools", "name": "com.android.tools::sdk-common", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "common", "category": "Android", "comment": "", "groupid": "com.android.tools", "name": "com.android.tools::common", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "r8", "category": "Android", "comment": "", "groupid": "com.android.tools", "name": "com.android.tools::r8", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "firebase-dynamic-links", "category": "Android", "comment": "", "groupid": "com.google.firebase", "name": "com.google.firebase::firebase-dynamic-links", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "firebase-crash", "category": "Android", "comment": "", "groupid": "com.google.firebase", "name": "com.google.firebase::firebase-crash", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "firebase-ads", "category": "Android", "comment": "", "groupid": "com.google.firebase", "name": "com.google.firebase::firebase-ads", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "firebase-analytics", "category": "Android", "comment": "", "groupid": "com.google.firebase", "name": "com.google.firebase::firebase-analytics", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "firebase-common", "category": "Android", "comment": "", "groupid": "com.google.firebase", "name": "com.google.firebase::firebase-common", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "firebase-auth", "category": "Android", "comment": "", "groupid": "com.google.firebase", "name": "com.google.firebase::firebase-auth", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "firebase-appindexing", "category": "Android", "comment": "", "groupid": "com.google.firebase", "name": "com.google.firebase::firebase-appindexing", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "firebase-auth-common", "category": "Android", "comment": "", "groupid": "com.google.firebase", "name": "com.google.firebase::firebase-auth-common", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "firebase-invites", "category": "Android", "comment": "", "groupid": "com.google.firebase", "name": "com.google.firebase::firebase-invites", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "firebase-config", "category": "Android", "comment": "", "groupid": "com.google.firebase", "name": "com.google.firebase::firebase-config", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "firebase-analytics-impl", "category": "Android", "comment": "", "groupid": "com.google.firebase", "name": "com.google.firebase::firebase-analytics-impl", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "firebase-storage", "category": "Android", "comment": "", "groupid": "com.google.firebase", "name": "com.google.firebase::firebase-storage", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "firebase-messaging", "category": "Android", "comment": "", "groupid": "com.google.firebase", "name": "com.google.firebase::firebase-messaging", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "firebase-auth-module", "category": "Android", "comment": "", "groupid": "com.google.firebase", "name": "com.google.firebase::firebase-auth-module", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "firebase-auth-impl", "category": "Android", "comment": "", "groupid": "com.google.firebase", "name": "com.google.firebase::firebase-auth-impl", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "firebase-database-connection", "category": "Android", "comment": "", "groupid": "com.google.firebase", "name": "com.google.firebase::firebase-database-connection", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "firebase-storage-common", "category": "Android", "comment": "", "groupid": "com.google.firebase", "name": "com.google.firebase::firebase-storage-common", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "firebase-core", "category": "Android", "comment": "", "groupid": "com.google.firebase", "name": "com.google.firebase::firebase-core", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "firebase-database", "category": "Android", "comment": "", "groupid": "com.google.firebase", "name": "com.google.firebase::firebase-database", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "firebase-perf", "category": "Android", "comment": "", "groupid": "com.google.firebase", "name": "com.google.firebase::firebase-perf", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "firebase-iid", "category": "Android", "comment": "", "groupid": "com.google.firebase", "name": "com.google.firebase::firebase-iid", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "firebase-firestore", "category": "Android", "comment": "", "groupid": "com.google.firebase", "name": "com.google.firebase::firebase-firestore", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "firebase-functions", "category": "Android", "comment": "", "groupid": "com.google.firebase", "name": "com.google.firebase::firebase-functions", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "firebase-ads-lite", "category": "Android", "comment": "", "groupid": "com.google.firebase", "name": "com.google.firebase::firebase-ads-lite", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "firebase-database-collection", "category": "Android", "comment": "", "groupid": "com.google.firebase", "name": "com.google.firebase::firebase-database-collection", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "firebase-abt", "category": "Android", "comment": "", "groupid": "com.google.firebase", "name": "com.google.firebase::firebase-abt", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "firebase-iid-interop", "category": "Android", "comment": "", "groupid": "com.google.firebase", "name": "com.google.firebase::firebase-iid-interop", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "protolite-well-known-types", "category": "Android", "comment": "", "groupid": "com.google.firebase", "name": "com.google.firebase::protolite-well-known-types", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "firebase-measurement-connector", "category": "Android", "comment": "", "groupid": "com.google.firebase", "name": "com.google.firebase::firebase-measurement-connector", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "firebase-auth-interop", "category": "Android", "comment": "", "groupid": "com.google.firebase", "name": "com.google.firebase::firebase-auth-interop", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "firebase-measurement-connector-impl", "category": "Android", "comment": "", "groupid": "com.google.firebase", "name": "com.google.firebase::firebase-measurement-connector-impl", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "firebase-ml-common", "category": "Android", "comment": "", "groupid": "com.google.firebase", "name": "com.google.firebase::firebase-ml-common", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "firebase-ml-vision-image-label-model", "category": "Android", "comment": "", "groupid": "com.google.firebase", "name": "com.google.firebase::firebase-ml-vision-image-label-model", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "firebase-ml-vision", "category": "Android", "comment": "", "groupid": "com.google.firebase", "name": "com.google.firebase::firebase-ml-vision", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "firebase-ml-model-interpreter", "category": "Android", "comment": "", "groupid": "com.google.firebase", "name": "com.google.firebase::firebase-ml-model-interpreter", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "play-services-vision", "category": "Android", "comment": "", "groupid": "com.google.android.gms", "name": "com.google.android.gms::play-services-vision", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "play-services-tagmanager", "category": "Android", "comment": "", "groupid": "com.google.android.gms", "name": "com.google.android.gms::play-services-tagmanager", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "play-services-vision-common", "category": "Android", "comment": "", "groupid": "com.google.android.gms", "name": "com.google.android.gms::play-services-vision-common", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "play-services-all-wear", "category": "Android", "comment": "", "groupid": "com.google.android.gms", "name": "com.google.android.gms::play-services-all-wear", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "play-services-instantapps", "category": "Android", "comment": "", "groupid": "com.google.android.gms", "name": "com.google.android.gms::play-services-instantapps", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "play-services-tasks", "category": "Android", "comment": "", "groupid": "com.google.android.gms", "name": "com.google.android.gms::play-services-tasks", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "play-services-ads-lite", "category": "Android", "comment": "", "groupid": "com.google.android.gms", "name": "com.google.android.gms::play-services-ads-lite", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "play-services-analytics-impl", "category": "Android", "comment": "", "groupid": "com.google.android.gms", "name": "com.google.android.gms::play-services-analytics-impl", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "play-services-maps", "category": "Android", "comment": "", "groupid": "com.google.android.gms", "name": "com.google.android.gms::play-services-maps", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "play-services-appindexing", "category": "Android", "comment": "", "groupid": "com.google.android.gms", "name": "com.google.android.gms::play-services-appindexing", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "play-services-wearable", "category": "Android", "comment": "", "groupid": "com.google.android.gms", "name": "com.google.android.gms::play-services-wearable", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "play-services-awareness", "category": "Android", "comment": "", "groupid": "com.google.android.gms", "name": "com.google.android.gms::play-services-awareness", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "play-services", "category": "Android", "comment": "", "groupid": "com.google.android.gms", "name": "com.google.android.gms::play-services", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "play-services-nearby", "category": "Android", "comment": "", "groupid": "com.google.android.gms", "name": "com.google.android.gms::play-services-nearby", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "play-services-basement", "category": "Android", "comment": "", "groupid": "com.google.android.gms", "name": "com.google.android.gms::play-services-basement", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "play-services-tagmanager-api", "category": "Android", "comment": "", "groupid": "com.google.android.gms", "name": "com.google.android.gms::play-services-tagmanager-api", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "play-services-tagmanager-v4-impl", "category": "Android", "comment": "", "groupid": "com.google.android.gms", "name": "com.google.android.gms::play-services-tagmanager-v4-impl", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "play-services-safetynet", "category": "Android", "comment": "", "groupid": "com.google.android.gms", "name": "com.google.android.gms::play-services-safetynet", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "play-services-plus", "category": "Android", "comment": "", "groupid": "com.google.android.gms", "name": "com.google.android.gms::play-services-plus", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "play-services-base", "category": "Android", "comment": "", "groupid": "com.google.android.gms", "name": "com.google.android.gms::play-services-base", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "play-services-iid", "category": "Android", "comment": "", "groupid": "com.google.android.gms", "name": "com.google.android.gms::play-services-iid", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "play-services-panorama", "category": "Android", "comment": "", "groupid": "com.google.android.gms", "name": "com.google.android.gms::play-services-panorama", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "play-services-contextmanager", "category": "Android", "comment": "", "groupid": "com.google.android.gms", "name": "com.google.android.gms::play-services-contextmanager", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "play-services-games", "category": "Android", "comment": "", "groupid": "com.google.android.gms", "name": "com.google.android.gms::play-services-games", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "play-services-cast", "category": "Android", "comment": "", "groupid": "com.google.android.gms", "name": "com.google.android.gms::play-services-cast", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "play-services-location", "category": "Android", "comment": "", "groupid": "com.google.android.gms", "name": "com.google.android.gms::play-services-location", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "play-services-places", "category": "Android", "comment": "", "groupid": "com.google.android.gms", "name": "com.google.android.gms::play-services-places", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "play-services-wallet", "category": "Android", "comment": "", "groupid": "com.google.android.gms", "name": "com.google.android.gms::play-services-wallet", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "play-services-identity", "category": "Android", "comment": "", "groupid": "com.google.android.gms", "name": "com.google.android.gms::play-services-identity", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "play-services-analytics", "category": "Android", "comment": "", "groupid": "com.google.android.gms", "name": "com.google.android.gms::play-services-analytics", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "play-services-appinvite", "category": "Android", "comment": "", "groupid": "com.google.android.gms", "name": "com.google.android.gms::play-services-appinvite", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "play-services-auth-base", "category": "Android", "comment": "", "groupid": "com.google.android.gms", "name": "com.google.android.gms::play-services-auth-base", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "play-services-auth-api-phone", "category": "Android", "comment": "", "groupid": "com.google.android.gms", "name": "com.google.android.gms::play-services-auth-api-phone", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "play-services-gass", "category": "Android", "comment": "", "groupid": "com.google.android.gms", "name": "com.google.android.gms::play-services-gass", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "play-services-appstate", "category": "Android", "comment": "", "groupid": "com.google.android.gms", "name": "com.google.android.gms::play-services-appstate", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "play-services-fitness", "category": "Android", "comment": "", "groupid": "com.google.android.gms", "name": "com.google.android.gms::play-services-fitness", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "play-services-drive", "category": "Android", "comment": "", "groupid": "com.google.android.gms", "name": "com.google.android.gms::play-services-drive", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "play-services-measurement", "category": "Android", "comment": "", "groupid": "com.google.android.gms", "name": "com.google.android.gms::play-services-measurement", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "play-services-ads", "category": "Android", "comment": "", "groupid": "com.google.android.gms", "name": "com.google.android.gms::play-services-ads", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "play-services-clearcut", "category": "Android", "comment": "", "groupid": "com.google.android.gms", "name": "com.google.android.gms::play-services-clearcut", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "play-services-gcm", "category": "Android", "comment": "", "groupid": "com.google.android.gms", "name": "com.google.android.gms::play-services-gcm", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "play-services-auth", "category": "Android", "comment": "", "groupid": "com.google.android.gms", "name": "com.google.android.gms::play-services-auth", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "play-services-cast-framework", "category": "Android", "comment": "", "groupid": "com.google.android.gms", "name": "com.google.android.gms::play-services-cast-framework", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "play-services-fido", "category": "Android", "comment": "", "groupid": "com.google.android.gms", "name": "com.google.android.gms::play-services-fido", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "auth-api-impl", "category": "Android", "comment": "", "groupid": "com.google.android.gms", "name": "com.google.android.gms::auth-api-impl", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "play-services-places-placereport", "category": "Android", "comment": "", "groupid": "com.google.android.gms", "name": "com.google.android.gms::play-services-places-placereport", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "play-services-measurement-base", "category": "Android", "comment": "", "groupid": "com.google.android.gms", "name": "com.google.android.gms::play-services-measurement-base", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "play-services-audience", "category": "Android", "comment": "", "groupid": "com.google.android.gms", "name": "com.google.android.gms::play-services-audience", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "play-services-ads-identifier", "category": "Android", "comment": "", "groupid": "com.google.android.gms", "name": "com.google.android.gms::play-services-ads-identifier", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "play-services-flags", "category": "Android", "comment": "", "groupid": "com.google.android.gms", "name": "com.google.android.gms::play-services-flags", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "play-services-phenotype", "category": "Android", "comment": "", "groupid": "com.google.android.gms", "name": "com.google.android.gms::play-services-phenotype", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "play-services-ads-base", "category": "Android", "comment": "", "groupid": "com.google.android.gms", "name": "com.google.android.gms::play-services-ads-base", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "play-services-stats", "category": "Android", "comment": "", "groupid": "com.google.android.gms", "name": "com.google.android.gms::play-services-stats", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "play-services-vision-image-label", "category": "Android", "comment": "", "groupid": "com.google.android.gms", "name": "com.google.android.gms::play-services-vision-image-label", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "runtime", "category": "Android", "comment": "", "groupid": "android.arch.paging", "name": "android.arch.paging::runtime", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "common", "category": "Android", "comment": "", "groupid": "android.arch.paging", "name": "android.arch.paging::common", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "rxjava2", "category": "Android", "comment": "", "groupid": "android.arch.paging", "name": "android.arch.paging::rxjava2", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "answers", "category": "Android", "comment": "", "groupid": "com.crashlytics.sdk.android", "name": "com.crashlytics.sdk.android::answers", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "beta", "category": "Android", "comment": "", "groupid": "com.crashlytics.sdk.android", "name": "com.crashlytics.sdk.android::beta", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "crashlytics-core", "category": "Android", "comment": "", "groupid": "com.crashlytics.sdk.android", "name": "com.crashlytics.sdk.android::crashlytics-core", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "crashlytics", "category": "Android", "comment": "", "groupid": "com.crashlytics.sdk.android", "name": "com.crashlytics.sdk.android::crashlytics", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "crashlytics-ndk", "category": "Android", "comment": "", "groupid": "com.crashlytics.sdk.android", "name": "com.crashlytics.sdk.android::crashlytics-ndk", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "fabric", "category": "Android", "comment": "", "groupid": "io.fabric.sdk.android", "name": "io.fabric.sdk.android::fabric", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "db-framework", "category": "Android", "comment": "", "groupid": "android.arch.persistence", "name": "android.arch.persistence::db-framework", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "db", "category": "Android", "comment": "", "groupid": "android.arch.persistence", "name": "android.arch.persistence::db", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "wearable", "category": "Android", "comment": "", "groupid": "com.google.android.wearable", "name": "com.google.android.wearable::wearable", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "wearable", "category": "Android", "comment": "", "groupid": "com.google.android.support", "name": "com.google.android.support::wearable", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "installreferrer", "category": "Android", "comment": "", "groupid": "com.android.installreferrer", "name": "com.android.installreferrer::installreferrer", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "core-ktx", "category": "Android", "comment": "", "groupid": "androidx.core", "name": "androidx.core::core-ktx", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "core", "category": "Android", "comment": "", "groupid": "androidx.core", "name": "androidx.core::core", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "androidthings", "category": "Android", "comment": "", "groupid": "com.google.android.things", "name": "com.google.android.things::androidthings", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "baseLibrary", "category": "Android", "comment": "", "groupid": "androidx.databinding", "name": "androidx.databinding::baseLibrary", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "compiler", "category": "Android", "comment": "", "groupid": "androidx.databinding", "name": "androidx.databinding::compiler", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "compilerCommon", "category": "Android", "comment": "", "groupid": "androidx.databinding", "name": "androidx.databinding::compilerCommon", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "adapters", "category": "Android", "comment": "", "groupid": "androidx.databinding", "name": "androidx.databinding::adapters", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "library", "category": "Android", "comment": "", "groupid": "androidx.databinding", "name": "androidx.databinding::library", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "databinding-runtime", "category": "Android", "comment": "", "groupid": "androidx.databinding", "name": "androidx.databinding::databinding-runtime", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "databinding-adapters", "category": "Android", "comment": "", "groupid": "androidx.databinding", "name": "androidx.databinding::databinding-adapters", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "databinding-compiler-common", "category": "Android", "comment": "", "groupid": "androidx.databinding", "name": "androidx.databinding::databinding-compiler-common", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "databinding-compiler", "category": "Android", "comment": "", "groupid": "androidx.databinding", "name": "androidx.databinding::databinding-compiler", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "databinding-common", "category": "Android", "comment": "", "groupid": "androidx.databinding", "name": "androidx.databinding::databinding-common", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "constraintlayout-solver", "category": "Android", "comment": "", "groupid": "androidx.constraintlayout", "name": "androidx.constraintlayout::constraintlayout-solver", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "constraintlayout", "category": "Android", "comment": "", "groupid": "androidx.constraintlayout", "name": "androidx.constraintlayout::constraintlayout", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "core", "category": "Android", "comment": "", "groupid": "com.google.android.play", "name": "com.google.android.play::core", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "multidex", "category": "Android", "comment": "", "groupid": "androidx.multidex", "name": "androidx.multidex::multidex", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "multidex-instrumentation", "category": "Android", "comment": "", "groupid": "androidx.multidex", "name": "androidx.multidex::multidex-instrumentation", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "material", "category": "Android", "comment": "", "groupid": "com.google.android.material", "name": "com.google.android.material::material", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "room-rxjava2", "category": "Android", "comment": "", "groupid": "androidx.room", "name": "androidx.room::room-rxjava2", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "room-common", "category": "Android", "comment": "", "groupid": "androidx.room", "name": "androidx.room::room-common", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "room-compiler", "category": "Android", "comment": "", "groupid": "androidx.room", "name": "androidx.room::room-compiler", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "room-guava", "category": "Android", "comment": "", "groupid": "androidx.room", "name": "androidx.room::room-guava", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "room-migration", "category": "Android", "comment": "", "groupid": "androidx.room", "name": "androidx.room::room-migration", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "room-runtime", "category": "Android", "comment": "", "groupid": "androidx.room", "name": "androidx.room::room-runtime", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "paging-common", "category": "Android", "comment": "", "groupid": "androidx.paging", "name": "androidx.paging::paging-common", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "paging-runtime", "category": "Android", "comment": "", "groupid": "androidx.paging", "name": "androidx.paging::paging-runtime", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "paging-rxjava2", "category": "Android", "comment": "", "groupid": "androidx.paging", "name": "androidx.paging::paging-rxjava2", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "lifecycle-reactivestreams-ktx", "category": "Android", "comment": "", "groupid": "androidx.lifecycle", "name": "androidx.lifecycle::lifecycle-reactivestreams-ktx", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "lifecycle-viewmodel", "category": "Android", "comment": "", "groupid": "androidx.lifecycle", "name": "androidx.lifecycle::lifecycle-viewmodel", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "lifecycle-process", "category": "Android", "comment": "", "groupid": "androidx.lifecycle", "name": "androidx.lifecycle::lifecycle-process", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "lifecycle-viewmodel-ktx", "category": "Android", "comment": "", "groupid": "androidx.lifecycle", "name": "androidx.lifecycle::lifecycle-viewmodel-ktx", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "lifecycle-reactivestreams", "category": "Android", "comment": "", "groupid": "androidx.lifecycle", "name": "androidx.lifecycle::lifecycle-reactivestreams", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "lifecycle-common-java8", "category": "Android", "comment": "", "groupid": "androidx.lifecycle", "name": "androidx.lifecycle::lifecycle-common-java8", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "lifecycle-extensions", "category": "Android", "comment": "", "groupid": "androidx.lifecycle", "name": "androidx.lifecycle::lifecycle-extensions", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "lifecycle-livedata-core", "category": "Android", "comment": "", "groupid": "androidx.lifecycle", "name": "androidx.lifecycle::lifecycle-livedata-core", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "lifecycle-runtime", "category": "Android", "comment": "", "groupid": "androidx.lifecycle", "name": "androidx.lifecycle::lifecycle-runtime", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "lifecycle-compiler", "category": "Android", "comment": "", "groupid": "androidx.lifecycle", "name": "androidx.lifecycle::lifecycle-compiler", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "lifecycle-livedata", "category": "Android", "comment": "", "groupid": "androidx.lifecycle", "name": "androidx.lifecycle::lifecycle-livedata", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "lifecycle-common", "category": "Android", "comment": "", "groupid": "androidx.lifecycle", "name": "androidx.lifecycle::lifecycle-common", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "lifecycle-service", "category": "Android", "comment": "", "groupid": "androidx.lifecycle", "name": "androidx.lifecycle::lifecycle-service", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "sqlite-framework", "category": "Android", "comment": "", "groupid": "androidx.sqlite", "name": "androidx.sqlite::sqlite-framework", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "sqlite", "category": "Android", "comment": "", "groupid": "androidx.sqlite", "name": "androidx.sqlite::sqlite", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "sqlite-ktx", "category": "Android", "comment": "", "groupid": "androidx.sqlite", "name": "androidx.sqlite::sqlite-ktx", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "core-common", "category": "Android", "comment": "", "groupid": "androidx.arch.core", "name": "androidx.arch.core::core-common", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "core-runtime", "category": "Android", "comment": "", "groupid": "androidx.arch.core", "name": "androidx.arch.core::core-runtime", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "work-runtime-ktx", "category": "Android", "comment": "", "groupid": "android.arch.work", "name": "android.arch.work::work-runtime-ktx", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "work-firebase", "category": "Android", "comment": "", "groupid": "android.arch.work", "name": "android.arch.work::work-firebase", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "work-runtime", "category": "Android", "comment": "", "groupid": "android.arch.work", "name": "android.arch.work::work-runtime", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "navigation-common", "category": "Android", "comment": "", "groupid": "android.arch.navigation", "name": "android.arch.navigation::navigation-common", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "navigation-runtime", "category": "Android", "comment": "", "groupid": "android.arch.navigation", "name": "android.arch.navigation::navigation-runtime", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "navigation-common-ktx", "category": "Android", "comment": "", "groupid": "android.arch.navigation", "name": "android.arch.navigation::navigation-common-ktx", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "navigation-ui", "category": "Android", "comment": "", "groupid": "android.arch.navigation", "name": "android.arch.navigation::navigation-ui", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "navigation-fragment-ktx", "category": "Android", "comment": "", "groupid": "android.arch.navigation", "name": "android.arch.navigation::navigation-fragment-ktx", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "navigation-ui-ktx", "category": "Android", "comment": "", "groupid": "android.arch.navigation", "name": "android.arch.navigation::navigation-ui-ktx", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "navigation-safe-args-generator", "category": "Android", "comment": "", "groupid": "android.arch.navigation", "name": "android.arch.navigation::navigation-safe-args-generator", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "navigation-runtime-ktx", "category": "Android", "comment": "", "groupid": "android.arch.navigation", "name": "android.arch.navigation::navigation-runtime-ktx", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "navigation-fragment", "category": "Android", "comment": "", "groupid": "android.arch.navigation", "name": "android.arch.navigation::navigation-fragment", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "mediarouter", "category": "Android", "comment": "", "groupid": "androidx.mediarouter", "name": "androidx.mediarouter::mediarouter", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "percentlayout", "category": "Android", "comment": "", "groupid": "androidx.percentlayout", "name": "androidx.percentlayout::percentlayout", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "emoji", "category": "Android", "comment": "", "groupid": "androidx.emoji", "name": "androidx.emoji::emoji", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "emoji-appcompat", "category": "Android", "comment": "", "groupid": "androidx.emoji", "name": "androidx.emoji::emoji-appcompat", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "emoji-bundled", "category": "Android", "comment": "", "groupid": "androidx.emoji", "name": "androidx.emoji::emoji-bundled", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "cardview", "category": "Android", "comment": "", "groupid": "androidx.cardview", "name": "androidx.cardview::cardview", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "preference", "category": "Android", "comment": "", "groupid": "androidx.preference", "name": "androidx.preference::preference", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "preference-ktx", "category": "Android", "comment": "", "groupid": "androidx.preference", "name": "androidx.preference::preference-ktx", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "wear", "category": "Android", "comment": "", "groupid": "androidx.wear", "name": "androidx.wear::wear", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "legacy-support-v13", "category": "Android", "comment": "", "groupid": "androidx.legacy", "name": "androidx.legacy::legacy-support-v13", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "legacy-preference-v14", "category": "Android", "comment": "", "groupid": "androidx.legacy", "name": "androidx.legacy::legacy-preference-v14", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "legacy-support-v4", "category": "Android", "comment": "", "groupid": "androidx.legacy", "name": "androidx.legacy::legacy-support-v4", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "legacy-support-core-ui", "category": "Android", "comment": "", "groupid": "androidx.legacy", "name": "androidx.legacy::legacy-support-core-ui", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "legacy-support-core-utils", "category": "Android", "comment": "", "groupid": "androidx.legacy", "name": "androidx.legacy::legacy-support-core-utils", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "documentfile", "category": "Android", "comment": "", "groupid": "androidx.documentfile", "name": "androidx.documentfile::documentfile", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "car", "category": "Android", "comment": "", "groupid": "androidx.car", "name": "androidx.car::car", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "swiperefreshlayout", "category": "Android", "comment": "", "groupid": "androidx.swiperefreshlayout", "name": "androidx.swiperefreshlayout::swiperefreshlayout", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "leanback", "category": "Android", "comment": "", "groupid": "androidx.leanback", "name": "androidx.leanback::leanback", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "leanback-preference", "category": "Android", "comment": "", "groupid": "androidx.leanback", "name": "androidx.leanback::leanback-preference", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "appcompat", "category": "Android", "comment": "", "groupid": "androidx.appcompat", "name": "androidx.appcompat::appcompat", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "customview", "category": "Android", "comment": "", "groupid": "androidx.customview", "name": "androidx.customview::customview", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "gridlayout", "category": "Android", "comment": "", "groupid": "androidx.gridlayout", "name": "androidx.gridlayout::gridlayout", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "vectordrawable-animated", "category": "Android", "comment": "", "groupid": "androidx.vectordrawable", "name": "androidx.vectordrawable::vectordrawable-animated", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "vectordrawable", "category": "Android", "comment": "", "groupid": "androidx.vectordrawable", "name": "androidx.vectordrawable::vectordrawable", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "heifwriter", "category": "Android", "comment": "", "groupid": "androidx.heifwriter", "name": "androidx.heifwriter::heifwriter", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "transition", "category": "Android", "comment": "", "groupid": "androidx.transition", "name": "androidx.transition::transition", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "print", "category": "Android", "comment": "", "groupid": "androidx.print", "name": "androidx.print::print", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "viewpager", "category": "Android", "comment": "", "groupid": "androidx.viewpager", "name": "androidx.viewpager::viewpager", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "annotation", "category": "Android", "comment": "", "groupid": "androidx.annotation", "name": "androidx.annotation::annotation", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "exifinterface", "category": "Android", "comment": "", "groupid": "androidx.exifinterface", "name": "androidx.exifinterface::exifinterface", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "dynamicanimation", "category": "Android", "comment": "", "groupid": "androidx.dynamicanimation", "name": "androidx.dynamicanimation::dynamicanimation", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "browser", "category": "Android", "comment": "", "groupid": "androidx.browser", "name": "androidx.browser::browser", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "localbroadcastmanager", "category": "Android", "comment": "", "groupid": "androidx.localbroadcastmanager", "name": "androidx.localbroadcastmanager::localbroadcastmanager", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "asynclayoutinflater", "category": "Android", "comment": "", "groupid": "androidx.asynclayoutinflater", "name": "androidx.asynclayoutinflater::asynclayoutinflater", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "contentpager", "category": "Android", "comment": "", "groupid": "androidx.contentpager", "name": "androidx.contentpager::contentpager", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "slidingpanelayout", "category": "Android", "comment": "", "groupid": "androidx.slidingpanelayout", "name": "androidx.slidingpanelayout::slidingpanelayout", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "cursoradapter", "category": "Android", "comment": "", "groupid": "androidx.cursoradapter", "name": "androidx.cursoradapter::cursoradapter", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "media-widget", "category": "Android", "comment": "", "groupid": "androidx.media", "name": "androidx.media::media-widget", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "media", "category": "Android", "comment": "", "groupid": "androidx.media", "name": "androidx.media::media", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "loader", "category": "Android", "comment": "", "groupid": "androidx.loader", "name": "androidx.loader::loader", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "interpolator", "category": "Android", "comment": "", "groupid": "androidx.interpolator", "name": "androidx.interpolator::interpolator", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "coordinatorlayout", "category": "Android", "comment": "", "groupid": "androidx.coordinatorlayout", "name": "androidx.coordinatorlayout::coordinatorlayout", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "fragment-ktx", "category": "Android", "comment": "", "groupid": "androidx.fragment", "name": "androidx.fragment::fragment-ktx", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "fragment", "category": "Android", "comment": "", "groupid": "androidx.fragment", "name": "androidx.fragment::fragment", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "tvprovider", "category": "Android", "comment": "", "groupid": "androidx.tvprovider", "name": "androidx.tvprovider::tvprovider", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "slice-core", "category": "Android", "comment": "", "groupid": "androidx.slice", "name": "androidx.slice::slice-core", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "slice-builders", "category": "Android", "comment": "", "groupid": "androidx.slice", "name": "androidx.slice::slice-builders", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "slice-view", "category": "Android", "comment": "", "groupid": "androidx.slice", "name": "androidx.slice::slice-view", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "slice-builders-ktx", "category": "Android", "comment": "", "groupid": "androidx.slice", "name": "androidx.slice::slice-builders-ktx", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "collection-ktx", "category": "Android", "comment": "", "groupid": "androidx.collection", "name": "androidx.collection::collection-ktx", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "collection", "category": "Android", "comment": "", "groupid": "androidx.collection", "name": "androidx.collection::collection", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "recommendation", "category": "Android", "comment": "", "groupid": "androidx.recommendation", "name": "androidx.recommendation::recommendation", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "drawerlayout", "category": "Android", "comment": "", "groupid": "androidx.drawerlayout", "name": "androidx.drawerlayout::drawerlayout", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "recyclerview-selection", "category": "Android", "comment": "", "groupid": "androidx.recyclerview", "name": "androidx.recyclerview::recyclerview-selection", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "recyclerview", "category": "Android", "comment": "", "groupid": "androidx.recyclerview", "name": "androidx.recyclerview::recyclerview", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "webkit", "category": "Android", "comment": "", "groupid": "androidx.webkit", "name": "androidx.webkit::webkit", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "palette-ktx", "category": "Android", "comment": "", "groupid": "androidx.palette", "name": "androidx.palette::palette-ktx", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "palette", "category": "Android", "comment": "", "groupid": "androidx.palette", "name": "androidx.palette::palette", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "consent-library", "category": "Android", "comment": "", "groupid": "com.google.android.ads.consent", "name": "com.google.android.ads.consent::consent-library", "repo": "https://dl.google.com/dl/android/maven2" },{ "artefactid": "versionedparcelable", "category": "Android", "comment": "", "groupid": "androidx.versionedparcelable", "name": "androidx.versionedparcelable::versionedparcelable", "repo": "https://dl.google.com/dl/android/maven2" } ] } LibScout-2.3.2/scripts/library-specs/mvn-central-libraries.json000066400000000000000000000541701342431362400246140ustar00rootroot00000000000000{ "libraries": [ { "name": "Facebook Audience", "category": "Advertising", "comment": "", "groupid": "com.facebook.android", "artefactid": "audience-network-sdk", "repo": "mvn-central" }, { "name": "Picasso", "category": "Android", "comment": "A powerful image downloading and caching library for Android", "groupid": "com.squareup.picasso", "artefactid": "picasso", "repo": "mvn-central" },{ "name": "Fresco", "category": "Android", "comment": "An Android library for managing images and the memory they use. (http://frescolib.org)", "groupid": "com.facebook.fresco", "artefactid": "fresco", "repo": "mvn-central" },{ "name": "RxAndroid", "category": "Android", "comment": "Android specific bindings for RxJava", "groupid": "io.reactivex", "artefactid": "rxandroid", "repo": "mvn-central" },{ "name": "glide", "category": "Android", "comment": "An image loading and caching library for Android focused on smooth scrolling", "groupid": "com.github.bumptech.glide", "artefactid": "glide", "repo": "mvn-central" },{ "name": "New Relic", "category": "Android", "comment": "Mobile app performance monitoring", "groupid": "com.newrelic.agent.android", "artefactid": "android-agent", "repo": "mvn-central" },{ "name": "ACRA", "category": "Android", "comment": "Application Crash Reports for Android", "groupid": "ch.acra", "artefactid": "acra", "repo": "mvn-central" },{ "name": "Dexter", "category": "Android", "comment": "Dexter is an Android library that simplifies the process of requesting permissions at runtime", "groupid": "com.karumi", "artefactid": "dexter", "repo": "mvn-central" },{ "name": "universal image loader", "category": "Android", "comment": "Powerful and flexible library for loading, caching and displaying images on Android.", "groupid": "com.nostra13.universalimageloader", "artefactid": "universal-image-loader", "repo": "mvn-central" },{ "name": "CleverTap", "category": "Android", "comment": "App Personalization and Engagement", "groupid": "com.clevertap.android", "artefactid": "clevertap-android-sdk", "repo": "mvn-central" },{ "name": "Ticker", "category": "Android", "comment": "Android text view with scrolling text change animation", "groupid": "com.robinhood.ticker", "artefactid": "ticker", "repo": "mvn-central" },{ "name": "MaterialEditText", "category": "Android", "comment": "EditText in Material Design", "groupid": "com.rengwuxian.materialedittext", "artefactid": "library", "repo": "mvn-central" },{ "name": "Calligraphy", "category": "Android", "comment": "Custom fonts in Android the easy way", "groupid": "uk.co.chrisjenx", "artefactid": "calligraphy", "repo": "mvn-central" },{ "name": "Ion", "category": "Android", "comment": "Android Asynchronous Networking and Image Loading", "groupid": "com.koushikdutta.ion", "artefactid": "ion", "repo": "mvn-central" },{ "name": "AndroidAsync", "category": "Android", "comment": "low level network protocol library", "groupid": "com.koushikdutta.async", "artefactid": "androidasync", "repo": "mvn-central" },{ "name": "uCrop", "category": "Android", "comment": "Image Cropping Library for Android", "groupid": "com.yalantis", "artefactid": "ucrop", "repo": "mvn-central" },{ "name": "android-gif-drawable", "category": "Android", "comment": "Views and Drawable for displaying animated GIFs on Android", "groupid": "pl.droidsonroids.gif", "artefactid": "android-gif-drawable", "repo": "mvn-central" },{ "name": "AirMapView", "category": "Android", "comment": "A view abstraction to provide a map user interface with various underlying map providers", "groupid": "com.airbnb.android", "artefactid": "airmapview", "repo": "mvn-central" },{ "name": "Logger", "category": "Android", "comment": "Simple, pretty and powerful logger for Android", "groupid": "com.orhanobut", "artefactid": "logger", "repo": "mvn-central" },{ "name": "RxAndroidBle", "category": "Android", "comment": "RxAndroidBle is a powerful painkiller for Android's Bluetooth Low Energy headaches.", "groupid": "com.polidea.rxandroidble", "artefactid": "rxandroidble", "repo": "mvn-central" },{ "name": "Sensey", "category": "Android", "comment": "Detecting gestures in a snap", "groupid": "com.github.nisrulz", "artefactid": "sensey", "repo": "mvn-central" },{ "name": "Material Calendar View", "category": "Android", "comment": "A Material design back port of Android's CalendarView", "groupid": "com.prolificinteractive", "artefactid": "material-calendarview", "repo": "mvn-central" },{ "name": "TimesSquare", "category": "Android", "comment": "Standalone Android widget for picking a single date from a calendar view.", "groupid": "com.squareup", "artefactid": "android-times-square", "repo": "mvn-central" },{ "name": "Stetho", "category": "Android", "comment": "A debug bridge for Android applications", "groupid": "com.facebook.stetho", "artefactid": "stetho", "repo": "mvn-central" },{ "name": "Android Beacon Library", "category": "Android", "comment": "An Android library providing APIs to interact with BLE beacons", "groupid": "org.altbeacon", "artefactid": "android-beacon-library", "repo": "mvn-central" },{ "name": "EventBus", "category": "Android", "comment": "EventBus is a publish/subscribe event bus optimized for Android", "groupid": "org.greenrobot", "artefactid": "eventbus", "repo": "mvn-central" },{ "name": "Butter Knife", "category": "Android", "comment": "Bind Android views and callbacks to fields and methods", "groupid": "com.jakewharton", "artefactid": "butterknife", "repo": "mvn-central" }, { "name": "Crittercism", "category": "Analytics", "comment": "", "groupid": "com.crittercism", "artefactid": "crittercism-android-agent", "repo": "mvn-central" },{ "name": "HockeyApp", "category": "Analytics", "comment": "Microsoft HockeyApp: http://hockeyapp.net/features/", "groupid": "net.hockeyapp.android", "artefactid": "HockeySDK", "repo": "mvn-central" },{ "name": "Segment", "category": "Analytics", "comment": "", "groupid": "com.segment.analytics.android", "artefactid": "analytics-core", "repo": "mvn-central" },{ "name": "Segment", "category": "Analytics", "comment": "", "groupid": "com.segment.analytics.android", "artefactid": "analytics", "repo": "mvn-central" },{ "name": "Urban Airship", "category": "Analytics", "comment": "", "groupid": "com.urbanairship.android", "artefactid": "urbanairship-sdk", "repo": "mvn-central" },{ "name": "AppsFlyer", "category": "Analytics", "comment": "Mobile Attribution and Marketing Analytics", "groupid": "com.appsflyer", "artefactid": "af-android-sdk", "repo": "mvn-central" }, { "name": "Parse", "category": "Cloud", "comment": "", "groupid": "com.parse", "artefactid": "parse-android", "repo": "mvn-central" },{ "name": "OneDrive", "category": "Cloud", "comment": "Microsoft OneDrive", "groupid": "com.onedrive.sdk", "artefactid": "onedrive-sdk-android", "repo": "mvn-central" },{ "name": "Dropbox", "category": "Cloud", "comment": "A Java library to access Dropbox's HTTP-based Core API v2", "groupid": "com.dropbox.core", "artefactid": "dropbox-core-sdk", "repo": "mvn-central" },{ "name": "Twilio", "category": "Cloud", "comment": "High-quality VoIP calling for web and mobile apps", "groupid": "com.twilio.sdk", "artefactid": "twilio-java-sdk", "repo": "mvn-central" }, { "name": "Facebook", "category": "SocialMedia", "comment": "", "groupid": "com.facebook.android", "artefactid": "facebook-android-sdk", "repo": "mvn-central" },{ "name": "Facebook Core", "category": "SocialMedia", "comment": "", "groupid": "com.facebook.android", "artefactid": "facebook-core", "repo": "mvn-central" },{ "name": "Facebook Login", "category": "SocialMedia", "comment": "", "groupid": "com.facebook.android", "artefactid": "facebook-login", "repo": "mvn-central" },{ "name": "Facebook Sharing", "category": "SocialMedia", "comment": "", "groupid": "com.facebook.android", "artefactid": "facebook-share", "repo": "mvn-central" },{ "name": "Facebook Places", "category": "SocialMedia", "comment": "", "groupid": "com.facebook.android", "artefactid": "facebook-places", "repo": "mvn-central" },{ "name": "Facebook Messenger", "category": "SocialMedia", "comment": "", "groupid": "com.facebook.android", "artefactid": "facebook-messenger", "repo": "mvn-central" },{ "name": "Facebook App Links", "category": "SocialMedia", "comment": "", "groupid": "com.facebook.android", "artefactid": "facebook-applinks", "repo": "mvn-central" },{ "name": "vkontakte", "category": "SocialMedia", "comment": "", "groupid": "com.vk", "artefactid": "androidsdk", "repo": "mvn-central" },{ "name": "flickrj", "category": "SocialMedia", "comment": "A Java Flickr API library built for POJO, Android and Google App Engine", "groupid": "com.googlecode.flickrj-android", "artefactid": "flickrj-android", "repo": "mvn-central" },{ "name": "twitter4j", "category": "SocialMedia", "comment": "Twitter4J is an unofficial Java library for the Twitter API", "groupid": "org.twitter4j", "artefactid": "twitter4j-core", "repo": "mvn-central" }, { "name": "apache httpclient android", "category": "Utilities", "comment": "Apache HttpClient for Android", "groupid": "org.apache.httpcomponents", "artefactid": "httpclient-android", "repo": "mvn-central" },{ "name": "log4j", "category": "Utilities", "comment": "Apache Log4j 2 is an upgrade to Log4j that provides significant improvements over its predecessor", "groupid": "org.apache.logging.log4j", "artefactid": "log4j-core", "repo": "mvn-central" },{ "name": "MessagePack", "category": "Utilities", "comment": "MessagePack is an efficient binary serialization format", "groupid": "org.msgpack", "artefactid": "msgpack", "repo": "mvn-central" },{ "name": "roboguice", "category": "Utilities", "comment": "Google Guice on Android", "groupid": "org.roboguice", "artefactid": "roboguice", "repo": "mvn-central" },{ "name": "ScribeJava Core", "category": "Utilities", "comment": "Simple OAuth library for Java", "groupid": "com.github.scribejava", "artefactid": "scribejava-core", "repo": "mvn-central" },{ "name": "scribe", "category": "Utilities", "comment": "", "groupid": "org.scribe", "artefactid": "scribe", "repo": "mvn-central" },{ "name": "android-oauth-client", "category": "Utilities", "comment": "Android OAuth Client Library", "groupid": "com.wu-man", "artefactid": "android-oauth-client", "repo": "mvn-central" },{ "name": "MapBox", "category": "Utilities", "comment": "", "groupid": "com.mapbox.mapboxsdk", "artefactid": "mapbox-android-sdk", "repo": "mvn-central" },{ "name": "Paypal", "category": "Utilities", "comment": "", "groupid": "com.paypal.sdk", "artefactid": "paypal-android-sdk", "repo": "mvn-central" },{ "name": "braintree payments", "category": "Utilities", "comment": "", "groupid": "com.braintreepayments.api", "artefactid": "braintree", "repo": "mvn-central" },{ "name": "Guice", "category": "Utilities", "comment": "Guice is a lightweight dependency injection framework for Java 6+", "groupid": "com.google.inject", "artefactid": "guice", "repo": "mvn-central" },{ "name": "Guava", "category": "Utilities", "comment": "Google Core Libraries for Java 6+", "groupid": "com.google.guava", "artefactid": "guava", "repo": "mvn-central" },{ "name": "Gson", "category": "Utilities", "comment": "A Java serialization/deserialization library that can convert Java Objects into JSON and back.", "groupid": "com.google.code.gson", "artefactid": "gson", "repo": "mvn-central" },{ "name": "okio", "category": "Utilities", "comment": "fast I/O and resizable buffers", "groupid": "com.squareup.okio", "artefactid": "okio", "repo": "mvn-central" },{ "name": "leakcanary", "category": "Utilities", "comment": "A memory leak detection library for Android and Java", "groupid": "com.squareup.leakcanary", "artefactid": "leakcanary-android", "repo": "mvn-central" },{ "name": "Dagger", "category": "Utilities", "comment": "A fast dependency injector for Android and Java.", "groupid": "com.squareup.dagger", "artefactid": "dagger", "repo": "mvn-central" },{ "name": "Dagger2", "category": "Utilities", "comment": "A fast dependency injector for Android and Java.", "groupid": "com.google.dagger", "artefactid": "dagger", "repo": "mvn-central" },{ "name": "OkHttp", "category": "Utilities", "comment": "An HTTP and HTTP/2 client for Android and Java applications", "groupid": "com.squareup.okhttp", "artefactid": "okhttp", "repo": "mvn-central" },{ "name": "OkHttp", "category": "Utilities", "comment": "An HTTP and HTTP/2 client for Android and Java applications", "groupid": "com.squareup.okhttp3", "artefactid": "okhttp", "repo": "mvn-central" },{ "name": "Tape", "category": "Utilities", "comment": "A lightning fast, transactional, file-based FIFO for Android and Java.", "groupid": "com.squareup", "artefactid": "tape", "repo": "mvn-central" },{ "name": "Bolts", "category": "Utilities", "comment": "Bolts is a collection of low-level libraries designed to make developing mobile apps easier.", "groupid": "com.parse.bolts", "artefactid": "bolts-android", "repo": "mvn-central" },{ "name": "jsoup", "category": "Utilities", "comment": "Java HTML Parser", "groupid": "org.jsoup", "artefactid": "jsoup", "repo": "mvn-central" },{ "name": "Joda Time", "category": "Utilities", "comment": "Joda-Time is the widely used replacement for the Java date and time classes.", "groupid": "joda-time", "artefactid": "joda-time", "repo": "mvn-central" },{ "name": "FasterXML Jackson-Core", "category": "Utilities", "comment": "Core part of Jackson that defines Streaming API as well as basic shared abstractions", "groupid": "com.fasterxml.jackson.core", "artefactid": "jackson-core", "repo": "mvn-central" },{ "name": "OrmLite", "category": "Utilities", "comment": "Lightweight Object Relational Mapping (ORM)", "groupid": "com.j256.ormlite", "artefactid": "ormlite-android", "repo": "mvn-central" },{ "name": "mapsforge", "category": "Utilities", "comment": "Maps rendering based on OpenStreetMap data", "groupid": "org.mapsforge", "artefactid": "mapsforge-core", "repo": "mvn-central" },{ "name": "GeoJSON", "category": "Utilities", "comment": "A complete GeoJSON implementation for Android.", "groupid": "com.cocoahero.android", "artefactid": "geojson", "repo": "mvn-central" },{ "name": "Retrofit", "category": "Utilities", "comment": "Type-safe HTTP client for Android and Java", "groupid": "com.squareup.retrofit", "artefactid": "retrofit", "repo": "mvn-central" },{ "name": "Retrofit", "category": "Utilities", "comment": "Type-safe HTTP client for Android and Java", "groupid": "com.squareup.retrofit2", "artefactid": "retrofit", "repo": "mvn-central" },{ "name": "PrettyTime", "category": "Utilities", "comment": "PrettyTime is an OpenSource time formatting library", "groupid": "org.ocpsoft.prettytime", "artefactid": "prettytime", "repo": "mvn-central" },{ "name": "SQLCipher", "category": "Utilities", "comment": "Android SQLite API based on SQLCipher", "groupid": "net.zetetic", "artefactid": "android-database-sqlcipher", "repo": "mvn-central" },{ "name": "PubNub", "category": "Utilities", "comment": "Publish/Subscribe model for real-time data streaming and device signaling", "groupid": "com.pubnub", "artefactid": "pubnub", "repo": "mvn-central" },{ "name": "SQLBrite", "category": "Utilities", "comment": "A lightweight wrapper around SQLiteOpenHelper which introduces reactive stream semantics to SQL operations.", "groupid": "com.squareup.sqlbrite", "artefactid": "sqlbrite", "repo": "mvn-central" },{ "name": "Retrolambda", "category": "Utilities", "comment": "Backport of Java 8's lambda expressions to Java 7, 6 and 5", "groupid": "net.orfjackal.retrolambda", "artefactid": "retrolambda", "repo": "mvn-central" },{ "name": "JSch", "category": "Utilities", "comment": "Java Secure Channel (JSch) is a pure Java implementation of SSH2.", "groupid": "com.jcraft", "artefactid": "jsch", "repo": "mvn-central" },{ "name": "RxJava", "category": "Utilities", "comment": "Reactive Extensions for the JVM", "groupid": "io.reactivex.rxjava2", "artefactid": "rxjava", "repo": "mvn-central" } ] } LibScout-2.3.2/src/000077500000000000000000000000001342431362400140335ustar00rootroot00000000000000LibScout-2.3.2/src/android/000077500000000000000000000000001342431362400154535ustar00rootroot00000000000000LibScout-2.3.2/src/android/content/000077500000000000000000000000001342431362400171255ustar00rootroot00000000000000LibScout-2.3.2/src/android/content/res/000077500000000000000000000000001342431362400177165ustar00rootroot00000000000000LibScout-2.3.2/src/android/content/res/AXmlResourceParser.java000066400000000000000000000577511342431362400243260ustar00rootroot00000000000000/* * Copyright 2008 Android4ME * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.content.res; import java.io.IOException; import java.io.InputStream; import java.io.Reader; import org.xmlpull.v1.XmlPullParserException; import android.util.TypedValue; /** * @author Dmitry Skiba * * Binary xml files parser. * * Parser has only two states: * (1) Operational state, which parser obtains after first successful call * to next() and retains until open(), close(), or failed call to next(). * (2) Closed state, which parser obtains after open(), close(), or failed * call to next(). In this state methods return invalid values or throw exceptions. * * TODO: * * check all methods in closed state * */ public class AXmlResourceParser implements XmlResourceParser { public AXmlResourceParser() { resetEventInfo(); } public void open(InputStream stream) { close(); if (stream!=null) { m_reader=new IntReader(stream,false); } } public void close() { if (!m_operational) { return; } m_operational=false; m_reader.close(); m_reader=null; m_strings=null; m_resourceIDs=null; m_namespaces.reset(); resetEventInfo(); } /////////////////////////////////// iteration public int next() throws XmlPullParserException,IOException { if (m_reader==null) { throw new XmlPullParserException("Parser is not opened.",this,null); } try { doNext(); return m_event; } catch (IOException e) { close(); throw e; } } public int nextToken() throws XmlPullParserException,IOException { return next(); } public int nextTag() throws XmlPullParserException,IOException { int eventType=next(); if (eventType==TEXT && isWhitespace()) { eventType=next(); } if (eventType!=START_TAG && eventType!=END_TAG) { throw new XmlPullParserException("Expected start or end tag.",this,null); } return eventType; } public String nextText() throws XmlPullParserException,IOException { if(getEventType()!=START_TAG) { throw new XmlPullParserException("Parser must be on START_TAG to read next text.",this,null); } int eventType=next(); if (eventType==TEXT) { String result=getText(); eventType=next(); if (eventType!=END_TAG) { throw new XmlPullParserException("Event TEXT must be immediately followed by END_TAG.",this,null); } return result; } else if (eventType==END_TAG) { return ""; } else { throw new XmlPullParserException("Parser must be on START_TAG or TEXT to read text.",this,null); } } public void require(int type,String namespace,String name) throws XmlPullParserException,IOException { if (type!=getEventType() || (namespace!=null && !namespace.equals(getNamespace())) || (name!=null && !name.equals(getName()))) { throw new XmlPullParserException(TYPES[type]+" is expected.",this,null); } } public int getDepth() { return m_namespaces.getDepth()-1; } public int getEventType() throws XmlPullParserException { return m_event; } public int getLineNumber() { return m_lineNumber; } public String getName() { if (m_name==-1 || (m_event!=START_TAG && m_event!=END_TAG)) { return null; } return m_strings.getString(m_name); } public String getText() { if (m_name==-1 || m_event!=TEXT) { return null; } return m_strings.getString(m_name); } public char[] getTextCharacters(int[] holderForStartAndLength) { String text=getText(); if (text==null) { return null; } holderForStartAndLength[0]=0; holderForStartAndLength[1]=text.length(); char[] chars=new char[text.length()]; text.getChars(0,text.length(),chars,0); return chars; } public String getNamespace() { return m_strings.getString(m_namespaceUri); } public String getPrefix() { int prefix=m_namespaces.findPrefix(m_namespaceUri); return m_strings.getString(prefix); } public String getPositionDescription() { return "XML line #"+getLineNumber(); } public int getNamespaceCount(int depth) throws XmlPullParserException { return m_namespaces.getAccumulatedCount(depth); } public String getNamespacePrefix(int pos) throws XmlPullParserException { int prefix=m_namespaces.getPrefix(pos); return m_strings.getString(prefix); } public String getNamespaceUri(int pos) throws XmlPullParserException { int uri=m_namespaces.getUri(pos); return m_strings.getString(uri); } /////////////////////////////////// attributes public String getClassAttribute() { if (m_classAttribute==-1) { return null; } int offset=getAttributeOffset(m_classAttribute); int value=m_attributes[offset+ATTRIBUTE_IX_VALUE_STRING]; return m_strings.getString(value); } public String getIdAttribute() { if (m_idAttribute==-1) { return null; } int offset=getAttributeOffset(m_idAttribute); int value=m_attributes[offset+ATTRIBUTE_IX_VALUE_STRING]; return m_strings.getString(value); } public int getIdAttributeResourceValue(int defaultValue) { if (m_idAttribute==-1) { return defaultValue; } int offset=getAttributeOffset(m_idAttribute); int valueType=m_attributes[offset+ATTRIBUTE_IX_VALUE_TYPE]; if (valueType!=TypedValue.TYPE_REFERENCE) { return defaultValue; } return m_attributes[offset+ATTRIBUTE_IX_VALUE_DATA]; } public int getStyleAttribute() { if (m_styleAttribute==-1) { return 0; } int offset=getAttributeOffset(m_styleAttribute); return m_attributes[offset+ATTRIBUTE_IX_VALUE_DATA]; } public int getAttributeCount() { if (m_event!=START_TAG) { return -1; } return m_attributes.length/ATTRIBUTE_LENGHT; } public String getAttributeNamespace(int index) { int offset=getAttributeOffset(index); int namespace=m_attributes[offset+ATTRIBUTE_IX_NAMESPACE_URI]; if (namespace==-1) { return ""; } return m_strings.getString(namespace); } public String getAttributePrefix(int index) { int offset=getAttributeOffset(index); int uri=m_attributes[offset+ATTRIBUTE_IX_NAMESPACE_URI]; int prefix=m_namespaces.findPrefix(uri); if (prefix==-1) { return ""; } return m_strings.getString(prefix); } public String getAttributeName(int index) { int offset=getAttributeOffset(index); int name=m_attributes[offset+ATTRIBUTE_IX_NAME]; if (name==-1) { return ""; } return m_strings.getString(name); } public int getAttributeNameResource(int index) { int offset=getAttributeOffset(index); int name=m_attributes[offset+ATTRIBUTE_IX_NAME]; if (m_resourceIDs==null || name<0 || name>=m_resourceIDs.length) { return 0; } return m_resourceIDs[name]; } public int getAttributeValueType(int index) { int offset=getAttributeOffset(index); return m_attributes[offset+ATTRIBUTE_IX_VALUE_TYPE]; } public int getAttributeValueData(int index) { int offset=getAttributeOffset(index); return m_attributes[offset+ATTRIBUTE_IX_VALUE_DATA]; } public String getAttributeValue(int index) { int offset=getAttributeOffset(index); int valueType=m_attributes[offset+ATTRIBUTE_IX_VALUE_TYPE]; if (valueType==TypedValue.TYPE_STRING) { int valueString=m_attributes[offset+ATTRIBUTE_IX_VALUE_STRING]; return m_strings.getString(valueString); } int valueData=m_attributes[offset+ATTRIBUTE_IX_VALUE_DATA]; return "";//TypedValue.coerceToString(valueType,valueData); } public boolean getAttributeBooleanValue(int index,boolean defaultValue) { return getAttributeIntValue(index,defaultValue?1:0)!=0; } public float getAttributeFloatValue(int index,float defaultValue) { int offset=getAttributeOffset(index); int valueType=m_attributes[offset+ATTRIBUTE_IX_VALUE_TYPE]; if (valueType==TypedValue.TYPE_FLOAT) { int valueData=m_attributes[offset+ATTRIBUTE_IX_VALUE_DATA]; return Float.intBitsToFloat(valueData); } return defaultValue; } public int getAttributeIntValue(int index,int defaultValue) { int offset=getAttributeOffset(index); int valueType=m_attributes[offset+ATTRIBUTE_IX_VALUE_TYPE]; if (valueType>=TypedValue.TYPE_FIRST_INT && valueType<=TypedValue.TYPE_LAST_INT) { return m_attributes[offset+ATTRIBUTE_IX_VALUE_DATA]; } return defaultValue; } public int getAttributeUnsignedIntValue(int index,int defaultValue) { return getAttributeIntValue(index,defaultValue); } public int getAttributeResourceValue(int index,int defaultValue) { int offset=getAttributeOffset(index); int valueType=m_attributes[offset+ATTRIBUTE_IX_VALUE_TYPE]; if (valueType==TypedValue.TYPE_REFERENCE) { return m_attributes[offset+ATTRIBUTE_IX_VALUE_DATA]; } return defaultValue; } public String getAttributeValue(String namespace,String attribute) { int index=findAttribute(namespace,attribute); if (index==-1) { return null; } return getAttributeValue(index); } public boolean getAttributeBooleanValue(String namespace,String attribute,boolean defaultValue) { int index=findAttribute(namespace,attribute); if (index==-1) { return defaultValue; } return getAttributeBooleanValue(index,defaultValue); } public float getAttributeFloatValue(String namespace,String attribute,float defaultValue) { int index=findAttribute(namespace,attribute); if (index==-1) { return defaultValue; } return getAttributeFloatValue(index,defaultValue); } public int getAttributeIntValue(String namespace,String attribute,int defaultValue) { int index=findAttribute(namespace,attribute); if (index==-1) { return defaultValue; } return getAttributeIntValue(index,defaultValue); } public int getAttributeUnsignedIntValue(String namespace,String attribute,int defaultValue) { int index=findAttribute(namespace,attribute); if (index==-1) { return defaultValue; } return getAttributeUnsignedIntValue(index,defaultValue); } public int getAttributeResourceValue(String namespace,String attribute,int defaultValue) { int index=findAttribute(namespace,attribute); if (index==-1) { return defaultValue; } return getAttributeResourceValue(index,defaultValue); } public int getAttributeListValue(int index,String[] options,int defaultValue) { // TODO implement return 0; } public int getAttributeListValue(String namespace,String attribute,String[] options,int defaultValue) { // TODO implement return 0; } public String getAttributeType(int index) { return "CDATA"; } public boolean isAttributeDefault(int index) { return false; } /////////////////////////////////// dummies public void setInput(InputStream stream,String inputEncoding) throws XmlPullParserException { throw new XmlPullParserException(E_NOT_SUPPORTED); } public void setInput(Reader reader) throws XmlPullParserException { throw new XmlPullParserException(E_NOT_SUPPORTED); } public String getInputEncoding() { return null; } public int getColumnNumber() { return -1; } public boolean isEmptyElementTag() throws XmlPullParserException { return false; } public boolean isWhitespace() throws XmlPullParserException { return false; } public void defineEntityReplacementText(String entityName,String replacementText) throws XmlPullParserException { throw new XmlPullParserException(E_NOT_SUPPORTED); } public String getNamespace(String prefix) { throw new RuntimeException(E_NOT_SUPPORTED); } public Object getProperty(String name) { return null; } public void setProperty(String name,Object value) throws XmlPullParserException { throw new XmlPullParserException(E_NOT_SUPPORTED); } public boolean getFeature(String feature) { return false; } public void setFeature(String name,boolean value) throws XmlPullParserException { throw new XmlPullParserException(E_NOT_SUPPORTED); } ///////////////////////////////////////////// implementation /** * Namespace stack, holds prefix+uri pairs, as well as * depth information. * All information is stored in one int[] array. * Array consists of depth frames: * Data=DepthFrame*; * DepthFrame=Count+[Prefix+Uri]*+Count; * Count='count of Prefix+Uri pairs'; * Yes, count is stored twice, to enable bottom-up traversal. * increaseDepth adds depth frame, decreaseDepth removes it. * push/pop operations operate only in current depth frame. * decreaseDepth removes any remaining (not pop'ed) namespace pairs. * findXXX methods search all depth frames starting * from the last namespace pair of current depth frame. * All functions that operate with int, use -1 as 'invalid value'. * * !! functions expect 'prefix'+'uri' pairs, not 'uri'+'prefix' !! * */ private static final class NamespaceStack { public NamespaceStack() { m_data=new int[32]; } public final void reset() { m_dataLength=0; m_count=0; m_depth=0; } public final int getTotalCount() { return m_count; } public final int getCurrentCount() { if (m_dataLength==0) { return 0; } int offset=m_dataLength-1; return m_data[offset]; } public final int getAccumulatedCount(int depth) { if (m_dataLength==0 || depth<0) { return 0; } if (depth>m_depth) { depth=m_depth; } int accumulatedCount=0; int offset=0; for (;depth!=0;--depth) { int count=m_data[offset]; accumulatedCount+=count; offset+=(2+count*2); } return accumulatedCount; } public final void push(int prefix,int uri) { if (m_depth==0) { increaseDepth(); } ensureDataCapacity(2); int offset=m_dataLength-1; int count=m_data[offset]; m_data[offset-1-count*2]=count+1; m_data[offset]=prefix; m_data[offset+1]=uri; m_data[offset+2]=count+1; m_dataLength+=2; m_count+=1; } public final boolean pop(int prefix,int uri) { if (m_dataLength==0) { return false; } int offset=m_dataLength-1; int count=m_data[offset]; for (int i=0,o=offset-2;i!=count;++i,o-=2) { if (m_data[o]!=prefix || m_data[o+1]!=uri) { continue; } count-=1; if (i==0) { m_data[o]=count; o-=(1+count*2); m_data[o]=count; } else { m_data[offset]=count; offset-=(1+2+count*2); m_data[offset]=count; System.arraycopy( m_data,o+2, m_data,o, m_dataLength-o); } m_dataLength-=2; m_count-=1; return true; } return false; } public final boolean pop() { if (m_dataLength==0) { return false; } int offset=m_dataLength-1; int count=m_data[offset]; if (count==0) { return false; } count-=1; offset-=2; m_data[offset]=count; offset-=(1+count*2); m_data[offset]=count; m_dataLength-=2; m_count-=1; return true; } public final int getPrefix(int index) { return get(index,true); } public final int getUri(int index) { return get(index,false); } public final int findPrefix(int uri) { return find(uri,false); } public final int findUri(int prefix) { return find(prefix,true); } public final int getDepth() { return m_depth; } public final void increaseDepth() { ensureDataCapacity(2); int offset=m_dataLength; m_data[offset]=0; m_data[offset+1]=0; m_dataLength+=2; m_depth+=1; } public final void decreaseDepth() { if (m_dataLength==0) { return; } int offset=m_dataLength-1; int count=m_data[offset]; if ((offset-1-count*2)==0) { return; } m_dataLength-=2+count*2; m_count-=count; m_depth-=1; } private void ensureDataCapacity(int capacity) { int available=(m_data.length-m_dataLength); if (available>capacity) { return; } int newLength=(m_data.length+available)*2; int[] newData=new int[newLength]; System.arraycopy(m_data,0,newData,0,m_dataLength); m_data=newData; } private final int find(int prefixOrUri,boolean prefix) { if (m_dataLength==0) { return -1; } int offset=m_dataLength-1; for (int i=m_depth;i!=0;--i) { int count=m_data[offset]; offset-=2; for (;count!=0;--count) { if (prefix) { if (m_data[offset]==prefixOrUri) { return m_data[offset+1]; } } else { if (m_data[offset+1]==prefixOrUri) { return m_data[offset]; } } offset-=2; } } return -1; } private final int get(int index,boolean prefix) { if (m_dataLength==0 || index<0) { return -1; } int offset=0; for (int i=m_depth;i!=0;--i) { int count=m_data[offset]; if (index>=count) { index-=count; offset+=(2+count*2); continue; } offset+=(1+index*2); if (!prefix) { offset+=1; } return m_data[offset]; } return -1; } private int[] m_data; private int m_dataLength; private int m_count; private int m_depth; } /////////////////////////////////// package-visible // final void fetchAttributes(int[] styleableIDs,TypedArray result) { // result.resetIndices(); // if (m_attributes==null || m_resourceIDs==null) { // return; // } // boolean needStrings=false; // for (int i=0,e=styleableIDs.length;i!=e;++i) { // int id=styleableIDs[i]; // for (int o=0;o!=m_attributes.length;o+=ATTRIBUTE_LENGHT) { // int name=m_attributes[o+ATTRIBUTE_IX_NAME]; // if (name>=m_resourceIDs.length || // m_resourceIDs[name]!=id) // { // continue; // } // int valueType=m_attributes[o+ATTRIBUTE_IX_VALUE_TYPE]; // int valueData; // int assetCookie; // if (valueType==TypedValue.TYPE_STRING) { // valueData=m_attributes[o+ATTRIBUTE_IX_VALUE_STRING]; // assetCookie=-1; // needStrings=true; // } else { // valueData=m_attributes[o+ATTRIBUTE_IX_VALUE_DATA]; // assetCookie=0; // } // result.addValue(i,valueType,valueData,assetCookie,id,0); // } // } // if (needStrings) { // result.setStrings(m_strings); // } // } final StringBlock getStrings() { return m_strings; } /////////////////////////////////// private final int getAttributeOffset(int index) { if (m_event!=START_TAG) { throw new IndexOutOfBoundsException("Current event is not START_TAG."); } int offset=index*5; if (offset>=m_attributes.length) { throw new IndexOutOfBoundsException("Invalid attribute index ("+index+")."); } return offset; } private final int findAttribute(String namespace,String attribute) { if (m_strings==null || attribute==null) { return -1; } int name=m_strings.find(attribute); if (name==-1) { return -1; } int uri=(namespace!=null)? m_strings.find(namespace): -1; for (int o=0;o!=m_attributes.length;++o) { if (name==m_attributes[o+ATTRIBUTE_IX_NAME] && (uri==-1 || uri==m_attributes[o+ATTRIBUTE_IX_NAMESPACE_URI])) { return o/ATTRIBUTE_LENGHT; } } return -1; } private final void resetEventInfo() { m_event=-1; m_lineNumber=-1; m_name=-1; m_namespaceUri=-1; m_attributes=null; m_idAttribute=-1; m_classAttribute=-1; m_styleAttribute=-1; } private final void doNext() throws IOException { // Delayed initialization. if (m_strings==null) { ChunkUtil.readCheckType(m_reader,CHUNK_AXML_FILE); /*chunkSize*/m_reader.skipInt(); m_strings=StringBlock.read(m_reader); m_namespaces.increaseDepth(); m_operational=true; } if (m_event==END_DOCUMENT) { return; } int event=m_event; resetEventInfo(); while (true) { if (m_decreaseDepth) { m_decreaseDepth=false; m_namespaces.decreaseDepth(); } // Fake END_DOCUMENT event. if (event==END_TAG && m_namespaces.getDepth()==1 && m_namespaces.getCurrentCount()==0) { m_event=END_DOCUMENT; break; } int chunkType; if (event==START_DOCUMENT) { // Fake event, see CHUNK_XML_START_TAG handler. chunkType=CHUNK_XML_START_TAG; } else { chunkType=m_reader.readInt(); } if (chunkType==CHUNK_RESOURCEIDS) { int chunkSize=m_reader.readInt(); if (chunkSize<8 || (chunkSize%4)!=0) { throw new IOException("Invalid resource ids size ("+chunkSize+")."); } m_resourceIDs=m_reader.readIntArray(chunkSize/4-2); continue; } if (chunkTypeCHUNK_XML_LAST) { throw new IOException("Invalid chunk type ("+chunkType+")."); } // Fake START_DOCUMENT event. if (chunkType==CHUNK_XML_START_TAG && event==-1) { m_event=START_DOCUMENT; break; } // Common header. /*chunkSize*/m_reader.skipInt(); int lineNumber=m_reader.readInt(); /*0xFFFFFFFF*/m_reader.skipInt(); if (chunkType==CHUNK_XML_START_NAMESPACE || chunkType==CHUNK_XML_END_NAMESPACE) { if (chunkType==CHUNK_XML_START_NAMESPACE) { int prefix=m_reader.readInt(); int uri=m_reader.readInt(); m_namespaces.push(prefix,uri); } else { /*prefix*/m_reader.skipInt(); /*uri*/m_reader.skipInt(); m_namespaces.pop(); } continue; } m_lineNumber=lineNumber; if (chunkType==CHUNK_XML_START_TAG) { m_namespaceUri=m_reader.readInt(); m_name=m_reader.readInt(); /*flags?*/m_reader.skipInt(); int attributeCount=m_reader.readInt(); m_idAttribute=(attributeCount>>>16)-1; attributeCount&=0xFFFF; m_classAttribute=m_reader.readInt(); m_styleAttribute=(m_classAttribute>>>16)-1; m_classAttribute=(m_classAttribute & 0xFFFF)-1; m_attributes=m_reader.readIntArray(attributeCount*ATTRIBUTE_LENGHT); for (int i=ATTRIBUTE_IX_VALUE_TYPE;i>>24); i+=ATTRIBUTE_LENGHT; } m_namespaces.increaseDepth(); m_event=START_TAG; break; } if (chunkType==CHUNK_XML_END_TAG) { m_namespaceUri=m_reader.readInt(); m_name=m_reader.readInt(); m_event=END_TAG; m_decreaseDepth=true; break; } if (chunkType==CHUNK_XML_TEXT) { m_name=m_reader.readInt(); /*?*/m_reader.skipInt(); /*?*/m_reader.skipInt(); m_event=TEXT; break; } } } /////////////////////////////////// data /* * All values are essentially indices, e.g. m_name is * an index of name in m_strings. */ private IntReader m_reader; private boolean m_operational=false; private StringBlock m_strings; private int[] m_resourceIDs; private NamespaceStack m_namespaces=new NamespaceStack(); private boolean m_decreaseDepth; private int m_event; private int m_lineNumber; private int m_name; private int m_namespaceUri; private int[] m_attributes; private int m_idAttribute; private int m_classAttribute; private int m_styleAttribute; private static final String E_NOT_SUPPORTED ="Method is not supported."; private static final int ATTRIBUTE_IX_NAMESPACE_URI =0, ATTRIBUTE_IX_NAME =1, ATTRIBUTE_IX_VALUE_STRING =2, ATTRIBUTE_IX_VALUE_TYPE =3, ATTRIBUTE_IX_VALUE_DATA =4, ATTRIBUTE_LENGHT =5; private static final int CHUNK_AXML_FILE =0x00080003, CHUNK_RESOURCEIDS =0x00080180, CHUNK_XML_FIRST =0x00100100, CHUNK_XML_START_NAMESPACE =0x00100100, CHUNK_XML_END_NAMESPACE =0x00100101, CHUNK_XML_START_TAG =0x00100102, CHUNK_XML_END_TAG =0x00100103, CHUNK_XML_TEXT =0x00100104, CHUNK_XML_LAST =0x00100104; } LibScout-2.3.2/src/android/content/res/ChunkUtil.java000066400000000000000000000020251342431362400224660ustar00rootroot00000000000000/* * Copyright 2008 Android4ME * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.content.res; import java.io.IOException; /** * @author Dmitry Skiba * */ class ChunkUtil { public static final void readCheckType(IntReader reader,int expectedType) throws IOException { int type=reader.readInt(); if (type!=expectedType) { throw new IOException( "Expected chunk of type 0x"+Integer.toHexString(expectedType)+ ", read 0x"+Integer.toHexString(type)+"."); } } } LibScout-2.3.2/src/android/content/res/IntReader.java000066400000000000000000000066631342431362400224510ustar00rootroot00000000000000/* * Copyright 2008 Android4ME * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.content.res; import java.io.EOFException; import java.io.IOException; import java.io.InputStream; /** * @author Dmitry Skiba * * Simple helper class that allows reading of integers. * * TODO: * * implement buffering * */ public final class IntReader { public IntReader() { } public IntReader(InputStream stream,boolean bigEndian) { reset(stream,bigEndian); } public final void reset(InputStream stream,boolean bigEndian) { m_stream=stream; m_bigEndian=bigEndian; m_position=0; } public final void close() { if (m_stream==null) { return; } try { m_stream.close(); } catch (IOException e) { } reset(null,false); } public final InputStream getStream() { return m_stream; } public final boolean isBigEndian() { return m_bigEndian; } public final void setBigEndian(boolean bigEndian) { m_bigEndian=bigEndian; } public final int readByte() throws IOException { return readInt(1); } public final int readShort() throws IOException { return readInt(2); } public final int readInt() throws IOException { return readInt(4); } public final int readInt(int length) throws IOException { if (length<0 || length>4) { throw new IllegalArgumentException(); } int result=0; if (m_bigEndian) { for (int i=(length-1)*8;i>=0;i-=8) { int b=m_stream.read(); if (b==-1) { throw new EOFException(); } m_position+=1; result|=(b<0;length-=1) { array[offset++]=readInt(); } } public final byte[] readByteArray(int length) throws IOException { byte[] array=new byte[length]; int read=m_stream.read(array); m_position+=read; if (read!=length) { throw new EOFException(); } return array; } public final void skip(int bytes) throws IOException { if (bytes<=0) { return; } long skipped=m_stream.skip(bytes); m_position+=skipped; if (skipped!=bytes) { throw new EOFException(); } } public final void skipInt() throws IOException { skip(4); } public final int available() throws IOException { return m_stream.available(); } public final int getPosition() { return m_position; } /////////////////////////////////// data private InputStream m_stream; private boolean m_bigEndian; private int m_position; } LibScout-2.3.2/src/android/content/res/StringBlock.java000066400000000000000000000133171342431362400230070ustar00rootroot00000000000000/* * Copyright 2008 Android4ME * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.content.res; import java.io.IOException; /** * @author Dmitry Skiba * * Block of strings, used in binary xml and arsc. * * TODO: * - implement get() * */ public class StringBlock { /** * Reads whole (including chunk type) string block from stream. * Stream must be at the chunk type. */ public static StringBlock read(IntReader reader) throws IOException { ChunkUtil.readCheckType(reader,CHUNK_TYPE); int chunkSize=reader.readInt(); int stringCount=reader.readInt(); int styleOffsetCount=reader.readInt(); /*?*/reader.readInt(); int stringsOffset=reader.readInt(); int stylesOffset=reader.readInt(); StringBlock block=new StringBlock(); block.m_stringOffsets=reader.readIntArray(stringCount); if (styleOffsetCount!=0) { block.m_styleOffsets=reader.readIntArray(styleOffsetCount); } { int size=((stylesOffset==0)?chunkSize:stylesOffset)-stringsOffset; if ((size%4)!=0) { throw new IOException("String data size is not multiple of 4 ("+size+")."); } block.m_strings=reader.readIntArray(size/4); } if (stylesOffset!=0) { int size=(chunkSize-stylesOffset); if ((size%4)!=0) { throw new IOException("Style data size is not multiple of 4 ("+size+")."); } block.m_styles=reader.readIntArray(size/4); } return block; } /** * Returns number of strings in block. */ public int getCount() { return m_stringOffsets!=null? m_stringOffsets.length: 0; } /** * Returns raw string (without any styling information) at specified index. */ public String getString(int index) { if (index<0 || m_stringOffsets==null || index>=m_stringOffsets.length) { return null; } int offset=m_stringOffsets[index]; int length=getShort(m_strings,offset); StringBuilder result=new StringBuilder(length); for (;length!=0;length-=1) { offset+=2; result.append((char)getShort(m_strings,offset)); } return result.toString(); } /** * Not yet implemented. * * Returns string with style information (if any). */ public CharSequence get(int index) { return getString(index); } /** * Returns string with style tags (html-like). */ public String getHTML(int index) { String raw=getString(index); if (raw==null) { return raw; } int[] style=getStyle(index); if (style==null) { return raw; } StringBuilder html=new StringBuilder(raw.length()+32); int offset=0; while (true) { int i=-1; for (int j=0;j!=style.length;j+=3) { if (style[j+1]==-1) { continue; } if (i==-1 || style[i+1]>style[j+1]) { i=j; } } int start=((i!=-1)?style[i+1]:raw.length()); for (int j=0;j!=style.length;j+=3) { int end=style[j+2]; if (end==-1 || end>=start) { continue; } if (offset<=end) { html.append(raw,offset,end+1); offset=end+1; } style[j+2]=-1; html.append('<'); html.append('/'); html.append(getString(style[j])); html.append('>'); } if (offset'); style[i+1]=-1; } return html.toString(); } /** * Finds index of the string. * Returns -1 if the string was not found. */ public int find(String string) { if (string==null) { return -1; } for (int i=0;i!=m_stringOffsets.length;++i) { int offset=m_stringOffsets[i]; int length=getShort(m_strings,offset); if (length!=string.length()) { continue; } int j=0; for (;j!=length;++j) { offset+=2; if (string.charAt(j)!=getShort(m_strings,offset)) { break; } } if (j==length) { return i; } } return -1; } ///////////////////////////////////////////// implementation private StringBlock() { } /** * Returns style information - array of int triplets, * where in each triplet: * * first int is index of tag name ('b','i', etc.) * * second int is tag start index in string * * third int is tag end index in string */ private int[] getStyle(int index) { if (m_styleOffsets==null || m_styles==null || index>=m_styleOffsets.length) { return null; } int offset=m_styleOffsets[index]/4; int style[]; { int count=0; for (int i=offset;i>> 16); } } private int[] m_stringOffsets; private int[] m_strings; private int[] m_styleOffsets; private int[] m_styles; private static final int CHUNK_TYPE=0x001C0001; } LibScout-2.3.2/src/com/000077500000000000000000000000001342431362400146115ustar00rootroot00000000000000LibScout-2.3.2/src/com/googlecode/000077500000000000000000000000001342431362400167205ustar00rootroot00000000000000LibScout-2.3.2/src/com/googlecode/dex2jar/000077500000000000000000000000001342431362400202575ustar00rootroot00000000000000LibScout-2.3.2/src/com/googlecode/dex2jar/reader/000077500000000000000000000000001342431362400215215ustar00rootroot00000000000000LibScout-2.3.2/src/com/googlecode/dex2jar/reader/io/000077500000000000000000000000001342431362400221305ustar00rootroot00000000000000LibScout-2.3.2/src/com/googlecode/dex2jar/reader/io/ArrayDataIn.java000066400000000000000000000061171342431362400251370ustar00rootroot00000000000000/* * Copyright (c) 2009-2012 Panxiaobo * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.googlecode.dex2jar.reader.io; import java.io.ByteArrayInputStream; import java.io.IOException; import java.util.Stack; /** * * @author Panxiaobo * @version $Rev: 9fd8005bbaa4 $ */ public abstract class ArrayDataIn extends ByteArrayInputStream implements DataIn { private Stack stack = new Stack(); public ArrayDataIn(byte[] data) { super(data); } @Override public int readShortx() { return (short) readUShortx(); } @Override public int readIntx() { return readUIntx(); } @Override public int getCurrentPosition() { return super.pos; } @Override public void move(int absOffset) { super.pos = absOffset; } @Override public void pop() { super.pos = stack.pop(); } @Override public void push() { stack.push(super.pos); } @Override public void pushMove(int absOffset) { this.push(); this.move(absOffset); } @Override public byte[] readBytes(int size) { byte[] data = new byte[size]; try { super.read(data); } catch (IOException e) { throw new RuntimeException(e); } return data; } @Override public long readLeb128() { int bitpos = 0; long vln = 0L; do { int inp = readUByte(); vln |= ((long) (inp & 0x7F)) << bitpos; bitpos += 7; if ((inp & 0x80) == 0) { break; } } while (true); if (((1L << (bitpos - 1)) & vln) != 0) { vln -= (1L << bitpos); } return vln; } @Override public long readULeb128() { long value = 0; int count = 0; int b = readUByte(); while ((b & 0x80) != 0) { value |= (b & 0x7f) << count; count += 7; b = readUByte(); } value |= (b & 0x7f) << count; return value; } @Override public void skip(int bytes) { super.skip(bytes); } @Override public int readByte() { return (byte) readUByte(); } @Override public int readUByte() { if (super.pos >= super.count) { throw new RuntimeException("EOF"); } return super.read(); } } LibScout-2.3.2/src/com/googlecode/dex2jar/reader/io/DataIn.java000066400000000000000000000030541342431362400241350ustar00rootroot00000000000000/* * Copyright (c) 2009-2012 Panxiaobo * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.googlecode.dex2jar.reader.io; /** * 输入流 * * @author Panxiaobo * @version $Rev: 3b20152caede $ */ public interface DataIn { /** * 获取当前位置 * * @return */ int getCurrentPosition(); void move(int absOffset); void pop(); void push(); /** * equals to * *
     * push();
     * move(absOffset);
     * 
* * @see #push() * @see #move(int) * @param absOffset */ void pushMove(int absOffset); /** * */ int readByte(); byte[] readBytes(int size); int readIntx(); int readUIntx(); int readShortx(); int readUShortx(); long readLeb128(); /** * @return */ int readUByte(); long readULeb128(); /** * @param i */ void skip(int bytes); } LibScout-2.3.2/src/com/googlecode/dex2jar/reader/io/DataOut.java000066400000000000000000000004621342431362400243360ustar00rootroot00000000000000package com.googlecode.dex2jar.reader.io; import java.io.IOException; public interface DataOut { void writeBytes(byte[] bs) throws IOException; void writeByte(int b) throws IOException; void writeInt(int i) throws IOException; void writeShort(int i) throws IOException; } LibScout-2.3.2/src/com/googlecode/dex2jar/reader/io/LeArrayDataIn.java000066400000000000000000000021651342431362400254170ustar00rootroot00000000000000/* * Copyright (c) 2009-2012 Panxiaobo * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.googlecode.dex2jar.reader.io; /** * @see DexFileReader#ENDIAN_CONSTANT * @author Panxiaobo * */ public class LeArrayDataIn extends ArrayDataIn implements DataIn { public LeArrayDataIn(byte[] data) { super(data); } @Override public int readUShortx() { return readUByte() | (readUByte() << 8); } @Override public int readUIntx() { return readUByte() | (readUByte() << 8) | (readUByte() << 16) | (readUByte() << 24); } } LibScout-2.3.2/src/com/googlecode/dex2jar/reader/io/LeDataOut.java000066400000000000000000000014371342431362400246220ustar00rootroot00000000000000package com.googlecode.dex2jar.reader.io; import java.io.IOException; import java.io.OutputStream; public class LeDataOut implements DataOut { public LeDataOut(OutputStream os) { super(); this.os = os; } private OutputStream os; @Override public void writeByte(int v) throws IOException { os.write(v); } @Override public void writeShort(int v) throws IOException { os.write(v); os.write(v >> 8); } @Override public void writeInt(int v) throws IOException { os.write(v); os.write(v >> 8); os.write(v >> 16); os.write(v >>> 24); } @Override public void writeBytes(byte[] bs)throws IOException { os.write(bs); } } LibScout-2.3.2/src/de/000077500000000000000000000000001342431362400144235ustar00rootroot00000000000000LibScout-2.3.2/src/de/infsec/000077500000000000000000000000001342431362400156725ustar00rootroot00000000000000LibScout-2.3.2/src/de/infsec/tpl/000077500000000000000000000000001342431362400164715ustar00rootroot00000000000000LibScout-2.3.2/src/de/infsec/tpl/TplCLI.java000066400000000000000000000461651342431362400204370ustar00rootroot00000000000000/* * Copyright (c) 2015-2017 Erik Derr [derr@cs.uni-saarland.de] * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this * file except in compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package de.infsec.tpl; import java.io.File; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import de.infsec.tpl.config.LibScoutConfig; import de.infsec.tpl.modules.libapi.LibraryApiAnalysis; import de.infsec.tpl.modules.libmatch.LibraryIdentifier; import de.infsec.tpl.modules.libprofiler.LibraryProfiler; import de.infsec.tpl.modules.updatability.LibraryUpdatability; import de.infsec.tpl.profile.Profile; import de.infsec.tpl.stats.AppStats; import org.apache.commons.cli.BasicParser; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.CommandLineParser; import org.apache.commons.cli.HelpFormatter; import org.apache.commons.cli.OptionBuilder; import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ch.qos.logback.classic.LoggerContext; import ch.qos.logback.classic.joran.JoranConfigurator; import ch.qos.logback.core.joran.spi.JoranException; import ch.qos.logback.core.util.StatusPrinter; import de.infsec.tpl.utils.Utils; import de.infsec.tpl.profile.LibProfile; public class TplCLI { private static final Logger logger = LoggerFactory.getLogger(de.infsec.tpl.TplCLI.class); private static Options options; public static class CliArgs { static final String ARG_OPMODE = "o"; static final String ARGL_OPMODE = "opmode"; static final String ARG_CONFIG = "c"; public static final String ARGL_CONFIG = "libscout-conf"; static final String ARG_ANDROID_LIB = "a"; static final String ARGL_ANDROID_LIB = "android-sdk"; static final String ARG_LOG_DIR = "d"; static final String ARGL_LOG_DIR = "log-dir"; static final String ARG_STATS_DIR = "s"; static final String ARGL_STATS_DIR = "stats-dir"; static final String ARG_JSON_DIR = "j"; static final String ARGL_JSON_DIR = "json-dir"; static final String ARG_PROFILES_DIR = "p"; static final String ARGL_PROFILES_DIR = "profiles-dir"; static final String ARG_MUTE = "m"; static final String ARGL_MUTE = "mute"; static final String ARG_NO_PARTIAL_MATCHING = "n"; static final String ARGL_NO_PARTIAL_MATCHING = "no-partial-matching"; static final String ARG_LIB_USAGE_ANALYSIS = "u"; static final String ARGL_LIB_USAGE_ANALYSIS = "lib-usage-analysis"; static final String ARG_LIB_DESCRIPTION = "x"; static final String ARGL_LIB_DESCRIPTION = "library-description"; static final String ARG_LIB_VERBOSE_PROFILES = "v"; static final String ARGL_LIB_VERBOSE_PROFILES = "verbose-profiles"; static final String ARG_LIB_DEPENDENCY_ANALYSIS = "da"; static final String ARGL_LIB_DEPENDENCY_ANALYSIS = "lib-dependency-analysis"; static final String ARG_LIB_API_COMPAT_DIR = "l"; static final String ARGL_LIB_API_COMPAT_DIR = "lib-api-compat-dir"; } private static ArrayList inputFiles; private static File libraryDescription = null; public static void main(String[] args) { // parse command line arguments parseCL(args); List profiles = null; LibraryUpdatability libUp = null; try { // parse LibScout.toml (args from CLI take precedence) LibScoutConfig.loadConfig(); // sanity check for required options that can be set from both CLI/config file checkRequiredOptions(); // initialize logback initLogging(); LibScoutConfig.whoAmI(); /* * one time data loading */ if (LibScoutConfig.opMatch() || LibScoutConfig.opUpdatability()) profiles = Profile.loadLibraryProfiles(LibScoutConfig.profilesDir); if (LibScoutConfig.opUpdatability()) libUp = new LibraryUpdatability(LibScoutConfig.libApiCompatDir); } catch (ParseException e) { logger.error("Error: " + e.getMessage()); usage(); } /* * choose mode of operation */ // process input files, either library files or apps for (File inputFile: inputFiles) { try { if (LibScoutConfig.opMatch()) { LibraryIdentifier.run(inputFile, profiles, LibScoutConfig.runLibUsageAnalysis); } else if (LibScoutConfig.opUpdatability()) { AppStats stats = LibraryIdentifier.run(inputFile, profiles, true); libUp.checkUpdatability(stats); } else if (LibScoutConfig.opProfile()) { LibraryProfiler.extractFingerPrints(inputFile, libraryDescription); } else if (LibScoutConfig.opLibApiAnalysis()) { LibraryApiAnalysis.run(inputFile); } } catch (Throwable t) { logger.error("[FATAL " + (t instanceof Exception? "EXCEPTION" : "ERROR") + "] analysis aborted: " + t.getMessage()); logger.error(Utils.stacktrace2Str(t)); } } } private static void parseCL(String[] args) { try { CommandLineParser parser = new BasicParser(); CommandLine cmd = parser.parse(setupOptions(), args); // parse mode of operation if (cmd.hasOption(CliArgs.ARG_OPMODE)) { try { LibScoutConfig.opmode = LibScoutConfig.OpMode.valueOf(cmd.getOptionValue(CliArgs.ARG_OPMODE).toUpperCase()); } catch (IllegalArgumentException e) { throw new ParseException(Utils.stacktrace2Str(e)); } } else usage(); /* * Logging options (apply to all modes, default settings: console logging, logdir="./logs") * -m, disable logging (takes precedence over -d) * -d [logdir], if provided without argument output is logged to default dir, otherwise to the provided dir */ if (cmd.hasOption(CliArgs.ARG_MUTE)) { LibScoutConfig.logType = LibScoutConfig.LogType.NONE; } else if (cmd.hasOption(CliArgs.ARG_LOG_DIR)) { LibScoutConfig.logType = LibScoutConfig.LogType.FILE; if (cmd.getOptionValue(CliArgs.ARG_LOG_DIR) != null) { // we have a log dir File logDir = new File(cmd.getOptionValue(CliArgs.ARG_LOG_DIR)); if (logDir.exists() && !logDir.isDirectory()) throw new ParseException("Log directory " + logDir + " already exists and is not a directory"); LibScoutConfig.logDir = logDir; } } // path to Android SDK jar if (checkOptionalUse(cmd, CliArgs.ARG_ANDROID_LIB, LibScoutConfig.OpMode.PROFILE, LibScoutConfig.OpMode.MATCH, LibScoutConfig.OpMode.LIB_API_ANALYSIS, LibScoutConfig.OpMode.UPDATABILITY)) { LibScoutConfig.pathToAndroidJar = new File(cmd.getOptionValue(CliArgs.ARG_ANDROID_LIB)); LibScoutConfig.checkIfValidFile(cmd.getOptionValue(CliArgs.ARG_ANDROID_LIB)); } // path to LibScout.toml if (checkOptionalUse(cmd, CliArgs.ARG_CONFIG, LibScoutConfig.OpMode.PROFILE, LibScoutConfig.OpMode.MATCH, LibScoutConfig.OpMode.LIB_API_ANALYSIS, LibScoutConfig.OpMode.UPDATABILITY)) { LibScoutConfig.libScoutConfigFileName = cmd.getOptionValue(CliArgs.ARG_CONFIG); LibScoutConfig.checkIfValidFile(LibScoutConfig.libScoutConfigFileName); } // profiles dir option, if provided without argument output is written to default dir if (checkOptionalUse(cmd, CliArgs.ARG_PROFILES_DIR, LibScoutConfig.OpMode.PROFILE, LibScoutConfig.OpMode.MATCH, LibScoutConfig.OpMode.UPDATABILITY)) { File profilesDir = new File(cmd.getOptionValue(CliArgs.ARG_PROFILES_DIR)); if (profilesDir.exists() && !profilesDir.isDirectory()) throw new ParseException("Profiles directory " + profilesDir + " already exists and is not a directory"); LibScoutConfig.profilesDir = profilesDir; } // disable partial matching (full lib matching only) if (checkOptionalUse(cmd, CliArgs.ARG_NO_PARTIAL_MATCHING, LibScoutConfig.OpMode.MATCH, LibScoutConfig.OpMode.UPDATABILITY)) { LibScoutConfig.noPartialMatching = true; } // run library usage analysis (for full matches only) if (checkOptionalUse(cmd, CliArgs.ARG_LIB_USAGE_ANALYSIS, LibScoutConfig.OpMode.MATCH)) { LibScoutConfig.runLibUsageAnalysis = true; } // provide library description file if (checkRequiredUse(cmd, CliArgs.ARG_LIB_DESCRIPTION, LibScoutConfig.OpMode.PROFILE)) { File libraryDescriptionFile = new File(cmd.getOptionValue(CliArgs.ARG_LIB_DESCRIPTION)); if (libraryDescriptionFile.exists() && libraryDescriptionFile.isDirectory()) throw new ParseException("Library description (" + libraryDescriptionFile + ") must not be a directory"); libraryDescription = libraryDescriptionFile; } // generate verbose library profiles? if (checkOptionalUse(cmd, CliArgs.ARG_LIB_VERBOSE_PROFILES, LibScoutConfig.OpMode.PROFILE)) { LibScoutConfig.genVerboseProfiles = true; } // enable library dependency analysis if (checkOptionalUse(cmd, CliArgs.ARG_LIB_DEPENDENCY_ANALYSIS, LibScoutConfig.OpMode.LIB_API_ANALYSIS)) { LibScoutConfig.libDependencyAnalysis = true; } // enable/disable generation of stats with optional stats directory if (checkOptionalUse(cmd, CliArgs.ARG_STATS_DIR, LibScoutConfig.OpMode.MATCH)) { LibScoutConfig.generateStats = true; if (cmd.getOptionValue(CliArgs.ARG_STATS_DIR) != null) { // stats dir provided? File statsDir = new File(cmd.getOptionValue(CliArgs.ARG_STATS_DIR)); if (statsDir.exists() && !statsDir.isDirectory()) throw new ParseException("Stats directory " + statsDir + " already exists and is not a directory"); LibScoutConfig.statsDir = statsDir; } } // enable/disable generation of json output if (checkOptionalUse(cmd, CliArgs.ARG_JSON_DIR, LibScoutConfig.OpMode.MATCH, LibScoutConfig.OpMode.LIB_API_ANALYSIS, LibScoutConfig.OpMode.UPDATABILITY)) { LibScoutConfig.generateJSON = true; if (cmd.getOptionValue(CliArgs.ARG_JSON_DIR) != null) { // json dir provided? File jsonDir = new File(cmd.getOptionValue(CliArgs.ARG_JSON_DIR)); if (jsonDir.exists() && !jsonDir.isDirectory()) throw new ParseException("JSON directory " + jsonDir + " already exists and is not a directory"); LibScoutConfig.jsonDir = jsonDir; } } // provide directory to lib api compat files (generated with api-analysis mode) if (checkRequiredUse(cmd, CliArgs.ARG_LIB_API_COMPAT_DIR, LibScoutConfig.OpMode.UPDATABILITY)) { File apiCompatDir = new File(cmd.getOptionValue(CliArgs.ARG_LIB_API_COMPAT_DIR)); if (!apiCompatDir.isDirectory()) throw new ParseException(apiCompatDir + " is not a directory"); LibScoutConfig.libApiCompatDir = apiCompatDir; } /* * process lib|app arguments * - in profile mode pass *one* library (since it is linked to lib description file) * - in match mode pass one application file or one directory (including apks) */ inputFiles = new ArrayList(); if (LibScoutConfig.opLibApiAnalysis()) { // we require a directory including library packages/descriptions for (String path: cmd.getArgs()) { File dir = new File(path); if (dir.isDirectory()) inputFiles.add(dir); } if (inputFiles.isEmpty()) { throw new ParseException("You have to provide at least one directory that includes a library package and description"); } } else { String[] fileExts = LibScoutConfig.opMatch() || LibScoutConfig.opUpdatability() ? new String[]{"apk"} : new String[]{"jar", "aar"}; for (String inputFile : cmd.getArgs()) { File arg = new File(inputFile); if (arg.isDirectory()) { inputFiles.addAll(Utils.collectFiles(arg, fileExts)); } else if (arg.isFile()) { if (arg.getName().endsWith("." + fileExts[0])) inputFiles.add(arg); else if (fileExts.length > 1 && arg.getName().endsWith("." + fileExts[1])) inputFiles.add(arg); else throw new ParseException("File " + arg.getName() + " is no valid ." + Utils.join(Arrays.asList(fileExts), "/") + " file"); } else { throw new ParseException("Argument " + inputFile + " is no valid file or directory!"); } } if (inputFiles.isEmpty()) { if (LibScoutConfig.opProfile()) throw new ParseException("No libraries (jar|aar files) found to profile in " + cmd.getArgList()); else throw new ParseException("No apk files found in " + cmd.getArgList()); } else if (inputFiles.size() > 1 && LibScoutConfig.opProfile()) throw new ParseException("You have to provide a path to a single library file or a directory incl. a single lib file"); } } catch (ParseException e) { System.err.println("Command line parsing failed:\n " + e.getMessage() + "\n"); usage(); } catch (Exception e) { System.err.println("Error occurred during argument processing:\n" + e.getMessage()); } } private static boolean checkRequiredUse(CommandLine cmd, String option, LibScoutConfig.OpMode... modes) throws ParseException { if (!Arrays.asList(modes).contains(LibScoutConfig.opmode)) return false; if (!cmd.hasOption(option)) throw new ParseException("Required CLI Option " + option + " is missing in mode " + LibScoutConfig.opmode); return true; } private static boolean checkOptionalUse(CommandLine cmd, String option, LibScoutConfig.OpMode... modes) { if (!Arrays.asList(modes).contains(LibScoutConfig.opmode)) return false; return cmd.hasOption(option); } /** * Checks whether required option (for current mode) is either provided via CLI or config file */ private static void checkRequiredOptions() throws ParseException { try { LibScoutConfig.checkIfValidFile(LibScoutConfig.log4jConfigFileName); } catch (ParseException e) { throw new ParseException("Could not find the log4j config file logback.xml . Please add the path in the LibScout.toml config"); } // android-sdk.jar if (Arrays.asList(LibScoutConfig.OpMode.PROFILE, LibScoutConfig.OpMode.MATCH, LibScoutConfig.OpMode.LIB_API_ANALYSIS).contains(LibScoutConfig.opmode) && LibScoutConfig.pathToAndroidJar == null) { throw new ParseException("Required option " + CliArgs.ARGL_ANDROID_LIB + " is neither provided via command line nor config file"); } } @SuppressWarnings("static-access") private static Options setupOptions() { options = new Options(); options.addOption(OptionBuilder.withArgName("value") .hasArgs(1) .isRequired(true) .withLongOpt(CliArgs.ARGL_OPMODE) .withDescription("mode of operation, one of [" + LibScoutConfig.OpMode.getOpModeString() + "]") .create(CliArgs.ARG_OPMODE)); options.addOption(OptionBuilder.withArgName("file") .hasArgs(1) .isRequired(false) .withLongOpt(CliArgs.ARGL_CONFIG) .withDescription("path to LibScout's config file, defaults to \"" + LibScoutConfig.libScoutConfigFileName + "\"") .create(CliArgs.ARG_CONFIG)); options.addOption(OptionBuilder.withArgName("file") .hasArgs(1) .isRequired(false) .withLongOpt(CliArgs.ARGL_ANDROID_LIB) .withDescription("path to SDK android.jar") .create(CliArgs.ARG_ANDROID_LIB)); options.addOption(OptionBuilder.withArgName("directory") .hasOptionalArgs(1) .isRequired(false) .withLongOpt(CliArgs.ARGL_LOG_DIR) .withDescription("path to store the logfile(s), defaults to \"" + LibScoutConfig.logDir + "\"") .create(CliArgs.ARG_LOG_DIR)); options.addOption(OptionBuilder.withArgName("directory") .hasOptionalArgs(1) .isRequired(false) .withLongOpt(CliArgs.ARGL_STATS_DIR) .withDescription("path to app stat(s), defaults to \"" + LibScoutConfig.statsDir + "\"") .create(CliArgs.ARG_STATS_DIR)); options.addOption(OptionBuilder.withArgName("directory") .hasOptionalArgs(1) .isRequired(false) .withLongOpt(CliArgs.ARGL_JSON_DIR) .withDescription("path to json output directory, defaults to \"" + LibScoutConfig.jsonDir + "\"") .create(CliArgs.ARG_JSON_DIR)); options.addOption(OptionBuilder.withArgName("value") .isRequired(false) .withLongOpt(CliArgs.ARGL_MUTE) .withDescription("disable file and console logging, takes precedence over -d") .create(CliArgs.ARG_MUTE)); options.addOption(OptionBuilder.withArgName("directory") .hasArgs(1) .isRequired(false) .withLongOpt(CliArgs.ARGL_PROFILES_DIR) .withDescription("path to library profiles, defaults to \"" + LibScoutConfig.profilesDir + "\"") .create(CliArgs.ARG_PROFILES_DIR)); options.addOption(OptionBuilder.withArgName("value") .isRequired(false) .withLongOpt(CliArgs.ARGL_LIB_VERBOSE_PROFILES) .withDescription("enable verbose profiling (trace + pubonly)") .create(CliArgs.ARG_LIB_VERBOSE_PROFILES)); options.addOption(OptionBuilder.withArgName("value") .isRequired(false) .withLongOpt(CliArgs.ARGL_NO_PARTIAL_MATCHING) .withDescription("disables partial matching (full matching only)") .create(CliArgs.ARG_NO_PARTIAL_MATCHING)); options.addOption(OptionBuilder.withArgName("value") .isRequired(false) .withLongOpt(CliArgs.ARGL_LIB_USAGE_ANALYSIS) .withDescription("Enables library usage analysis (for full matches only)") .create(CliArgs.ARG_LIB_USAGE_ANALYSIS)); options.addOption(OptionBuilder.withArgName("file") .hasArgs(1) .isRequired(false) .withLongOpt(CliArgs.ARGL_LIB_DESCRIPTION) .withDescription("xml file to describe the library") .create(CliArgs.ARG_LIB_DESCRIPTION)); options.addOption(OptionBuilder.withArgName("value") .isRequired(false) .withLongOpt(CliArgs.ARGL_LIB_DEPENDENCY_ANALYSIS) .withDescription("enable analysis of secondary library dependencies") .create(CliArgs.ARG_LIB_DEPENDENCY_ANALYSIS)); options.addOption(OptionBuilder.withArgName("directory") .hasArgs(1) .isRequired(false) .withLongOpt(CliArgs.ARGL_LIB_API_COMPAT_DIR) .withDescription("path to library api compatibility data files") .create(CliArgs.ARG_LIB_API_COMPAT_DIR)); return options; } private static void usage() { // automatically generate the help statement HelpFormatter formatter = new HelpFormatter(); String helpMsg = LibScoutConfig.OpMode.getUsageMessage(LibScoutConfig.opmode); formatter.printHelp(helpMsg, options); System.exit(1); } private static void initLogging() { LoggerContext context = (LoggerContext) org.slf4j.LoggerFactory.getILoggerFactory(); try { JoranConfigurator configurator = new JoranConfigurator(); configurator.setContext(context); context.reset(); // clear any previous configuration configurator.doConfigure(LibScoutConfig.log4jConfigFileName); ch.qos.logback.classic.Logger rootLogger = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME); switch (LibScoutConfig.logType) { case CONSOLE: rootLogger.detachAppender("FILE"); break; case FILE: rootLogger.detachAppender("CONSOLE"); break; case NONE: rootLogger.detachAndStopAllAppenders(); break; } } catch (JoranException je) { // StatusPrinter will handle this } StatusPrinter.printInCaseOfErrorsOrWarnings(context); } } LibScout-2.3.2/src/de/infsec/tpl/config/000077500000000000000000000000001342431362400177365ustar00rootroot00000000000000LibScout-2.3.2/src/de/infsec/tpl/config/LibScoutConfig.java000066400000000000000000000141671342431362400234640ustar00rootroot00000000000000package de.infsec.tpl.config; import de.infsec.tpl.TplCLI; import de.infsec.tpl.utils.Utils; import net.consensys.cava.toml.Toml; import net.consensys.cava.toml.TomlParseResult; import org.apache.commons.cli.ParseException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Arrays; import java.util.stream.Collectors; public class LibScoutConfig { private static final Logger logger = LoggerFactory.getLogger(de.infsec.tpl.config.LibScoutConfig.class); public static final String TOOLNAME = "LibScout"; public static final String TOOLVERSION = "2.3.2"; // modes of operations public enum OpMode { // generate library profiles from original lib SDKs and descriptions PROFILE("profile", "-x path_to_lib_desc [options] path_to_lib(jar|aar)"), // match lib profiles in provided apps MATCH("match", "[options] path_to_app(dir)"), // analyzes library api stability (api additions, removals, changes) LIB_API_ANALYSIS("lib_api_analysis", "path_to_lib_sdks"), // infer library usage in apps and check to which extent detected libs can be updated UPDATABILITY( "updatability", "[options] -l path_to_lib_api_compat path_to_app(dir)"); public String name; public String usageMsg; OpMode(String name, String usageMsg) { this.name = name; this.usageMsg = usageMsg; } public static String getUsageMessage(OpMode op) { return op == null? getToolUsageMsg() : TOOLNAME + " --opmode " + op.name + " " + op.usageMsg; } public static String getToolUsageMsg() { return TOOLNAME + " --opmode <" + getOpModeString() + "> [options]"; } public static String getOpModeString() { return Utils.join(Arrays.stream(OpMode.values()).map(op -> op.name.toLowerCase()).collect(Collectors.toList()), "|"); } } public static OpMode opmode = null; public static boolean opMatch() { return OpMode.MATCH.equals(opmode); } public static boolean opProfile() { return OpMode.PROFILE.equals(opmode); } public static boolean opLibApiAnalysis() { return OpMode.LIB_API_ANALYSIS.equals(opmode); } public static boolean opUpdatability() { return OpMode.UPDATABILITY.equals(opmode); } // config files public static String libScoutConfigFileName = "./config/LibScout.toml"; public static String log4jConfigFileName = "./config/logback.xml"; public static File pathToAndroidJar; public static boolean noPartialMatching = false; public static boolean runLibUsageAnalysis = false; public static boolean genVerboseProfiles = false; // generate lib profiles with TRACE + PubOnly public static boolean libDependencyAnalysis = false; public enum LogType { NONE, CONSOLE, FILE } public static LogType logType = LogType.CONSOLE; public static File logDir = new File("./logs"); public static boolean generateStats = false; public static File statsDir = new File("./stats"); public static boolean generateJSON = false; public static File jsonDir = new File("./json"); public static File profilesDir = new File("./profiles"); public static File libApiCompatDir = null; // package tree public static class PckgTree { public static boolean useAsciiRendering = false; } // reporting (logs, json) public static class Reporting { // upon detection, print/hide comments from library description public static boolean showComments = false; } public static void whoAmI() { logger.info("This is " + TOOLNAME + " " + TOOLVERSION); } public static boolean loadConfig() throws ParseException { File libScoutConfigFile; try { libScoutConfigFile = checkIfValidFile(libScoutConfigFileName); } catch (ParseException e) { throw new ParseException("Could not find the config file LibScout.toml . Please provide it via the --" + TplCLI.CliArgs.ARGL_CONFIG + " switch"); } try { Path confPath = Paths.get(libScoutConfigFile.toURI()); TomlParseResult conf = Toml.parse(confPath); if (conf.hasErrors()) { logger.warn("Error while parsing config file:"); conf.errors().forEach(e -> logger.info(Utils.indent() + "line: " + e.position().line() + ": " + e.toString())); return false; } else { for (String k: conf.dottedKeySet()) { parseConfig(k, conf.get(k)); } } } catch (IOException e) { logger.warn("Error while parsing config file: " + Utils.stacktrace2Str(e)); return false; } return true; } private static void parseConfig(String key, Object value) throws ParseException { try { //logger.debug("Parse config key : " + key + " value: " + value); if ("logging.log4j_config_file".equals(key)) { File f = checkIfValidFile((String) value); log4jConfigFileName = f.getAbsolutePath(); } else if ("sdk.android_sdk_jar".equals(key)) { if (pathToAndroidJar == null) // CLI takes precedence pathToAndroidJar = checkIfValidFile((String) value); } else if ("packageTree.ascii_rendering".equals(key)) { PckgTree.useAsciiRendering = (Boolean) value; } else if ("reporting.show_comments".equals(key)) { Reporting.showComments = (Boolean) value; } else logger.warn("Found unknown config key: " + key); } catch (ParseException e) { throw new ParseException("Could not parse config option " + key + " : " + e.getMessage()); } } public static File checkIfValidFile(String fileName) throws ParseException { File f = new File(fileName); if (f.exists() && f.isFile()) return f; else throw new ParseException("No valid file: " + fileName); } } LibScout-2.3.2/src/de/infsec/tpl/exceptions/000077500000000000000000000000001342431362400206525ustar00rootroot00000000000000LibScout-2.3.2/src/de/infsec/tpl/exceptions/MethodNotFoundException.java000066400000000000000000000015321342431362400262720ustar00rootroot00000000000000/* * Copyright (c) 2015-2017 Erik Derr [derr@cs.uni-saarland.de] * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this * file except in compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package de.infsec.tpl.exceptions; public class MethodNotFoundException extends Exception { private static final long serialVersionUID = -5657700822870573562L; public MethodNotFoundException(String message) { super(message); } }LibScout-2.3.2/src/de/infsec/tpl/hash/000077500000000000000000000000001342431362400174145ustar00rootroot00000000000000LibScout-2.3.2/src/de/infsec/tpl/hash/AccessFlags.java000066400000000000000000000076061342431362400224460ustar00rootroot00000000000000/* * Copyright (c) 2015-2017 Erik Derr [derr@cs.uni-saarland.de] * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this * file except in compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package de.infsec.tpl.hash; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import com.ibm.wala.classLoader.IMethod; import de.infsec.tpl.utils.Utils; public enum AccessFlags { NO_FLAG (0x0, "no-flag"), PUBLIC (0x1, "public"), PRIVATE (0x2, "private"), PROTECTED (0x4, "protected"), PACKAGE_PROTECTED (0x8, "package-protected"); private int value; private String accessFlagName; //cache the array of all AccessFlags, because .values() allocates a new array for every call private final static AccessFlags[] allFlags; private final static List validFlagValues; private static HashMap accessFlagsByName; static { allFlags = AccessFlags.values(); validFlagValues = new ArrayList(); for (AccessFlags flag: allFlags) validFlagValues.add(flag.getValue()); accessFlagsByName = new HashMap(); for (AccessFlags accessFlag: allFlags) { accessFlagsByName.put(accessFlag.accessFlagName, accessFlag); } } private AccessFlags(int value, String accessFlagName) { this.value = value; this.accessFlagName = accessFlagName; } private static String flags2Str(AccessFlags[] accessFlags) { int size = 0; for (AccessFlags accessFlag: accessFlags) { size += accessFlag.toString().length() + 1; } StringBuilder sb = new StringBuilder(size); for (AccessFlags accessFlag: accessFlags) { sb.append(accessFlag.toString()); sb.append(" "); } if (accessFlags.length > 0) { sb.delete(sb.length() - 1, sb.length()); } return sb.toString(); } public static boolean isValidFlag(int code) { return validFlagValues.contains(code); } public static String flags2Str(int code) { List matchedFlags = new ArrayList(); for (AccessFlags flag: allFlags) { if ((code & flag.value) != 0x0) { matchedFlags.add(flag.accessFlagName + "(" + flag.value + ")"); } } return Utils.join(matchedFlags, ","); } public static AccessFlags getAccessFlag(String accessFlag) { return accessFlagsByName.get(accessFlag); } public int getValue() { return value; } public String toString() { return accessFlagName; } public static int getAccessFlagFilter(AccessFlags... flags) { int filter = NO_FLAG.getValue(); if (flags != null) { for (AccessFlags flag: flags) { if (!AccessFlags.isValidFlag(flag.getValue())) continue; filter |= flag.getValue(); } } return filter; } public static int getPublicOnlyFilter() { return getAccessFlagFilter(AccessFlags.PRIVATE, AccessFlags.PACKAGE_PROTECTED, AccessFlags.PROTECTED); } public static int getMethodAccessCode(IMethod m) { int res = 0x0; if (m == null) return res; if (m.isPublic()) { res |= AccessFlags.PUBLIC.getValue(); } else if (m.isProtected()) { res |= AccessFlags.PROTECTED.getValue(); } else if (m.isPrivate()) { res |= AccessFlags.PRIVATE.getValue(); } else { res |= AccessFlags.PACKAGE_PROTECTED.getValue(); } return res; } } LibScout-2.3.2/src/de/infsec/tpl/hash/Hash.java000066400000000000000000000027401342431362400211450ustar00rootroot00000000000000/* * Copyright (c) 2015-2017 Erik Derr [derr@cs.uni-saarland.de] * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this * file except in compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package de.infsec.tpl.hash; import java.util.Arrays; import java.util.Comparator; @Deprecated public abstract class Hash implements IHash { public static boolean equals(byte[] hash1, byte[] hash2) { return Arrays.equals(hash1, hash2); } public static String hash2Str(byte[] hash) { String format = "%" + (hash.length*2) + "x"; return String.format(format, new java.math.BigInteger(1, hash)); } // Lexicographical comparator for byte arrays public class ByteArrayComparator implements Comparator { @Override public int compare(byte[] left, byte[] right) { for (int i = 0, j = 0; i < left.length && j < right.length; i++, j++) { int a = (left[i] & 0xff); int b = (right[j] & 0xff); if (a != b) { return a - b; } } return left.length - right.length; } } } LibScout-2.3.2/src/de/infsec/tpl/hash/HashCode.java000066400000000000000000000024411342431362400217360ustar00rootroot00000000000000/* * Copyright (c) 2015-2017 Erik Derr [derr@cs.uni-saarland.de] * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this * file except in compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package de.infsec.tpl.hash; public class HashCode extends Hash { public HashCode() {} @Override public byte[] hash(String str) { int value = str.hashCode(); return new byte[] { (byte)(value >>> 24), (byte)(value >>> 16), (byte)(value >>> 8), (byte) value}; } @Override public byte[] hash(byte[] b) { String str = new String(b); return hash(str); } // adapted from String.hashCode(), generates a 64bit hash /* public static long hash(String string) { long h = 1125899906842597L; // prime int len = string.length(); for (int i = 0; i < len; i++) { h = 31*h + string.charAt(i); } return h; }*/ } LibScout-2.3.2/src/de/infsec/tpl/hash/HashImpl.java000066400000000000000000000026351342431362400217720ustar00rootroot00000000000000/* * Copyright (c) 2015-2017 Erik Derr [derr@cs.uni-saarland.de] * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this * file except in compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package de.infsec.tpl.hash; import java.io.UnsupportedEncodingException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; public class HashImpl extends Hash { private final MessageDigest digest; /** * Creates a new hash implementation object * @param algorithm e.g. MD5 or SHA-256 * @throws NoSuchAlgorithmException */ public HashImpl(String algorithm) throws NoSuchAlgorithmException { digest = MessageDigest.getInstance(algorithm); /* e.g. MD5, SHA-256 */ } @Override public byte[] hash(String str) { try { digest.update(str.getBytes("UTF-8")); } catch (UnsupportedEncodingException e) { digest.update(str.getBytes()); } return digest.digest(); } @Override public byte[] hash(byte[] b) { digest.update(b); return digest.digest(); } } LibScout-2.3.2/src/de/infsec/tpl/hash/HashTreeOLD.java000066400000000000000000000702571342431362400223340ustar00rootroot00000000000000/* * Copyright (c) 2015-2017 Erik Derr [derr@cs.uni-saarland.de] * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this * file except in compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package de.infsec.tpl.hash; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.Serializable; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.TreeSet; import java.util.stream.Collectors; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.ibm.wala.classLoader.IClass; import com.ibm.wala.classLoader.IMethod; import com.ibm.wala.ipa.cha.IClassHierarchy; import com.ibm.wala.types.ClassLoaderReference; import com.ibm.wala.types.Descriptor; import de.infsec.tpl.hash.Hash.ByteArrayComparator; import de.infsec.tpl.pkg.PackageTree; import de.infsec.tpl.pkg.PackageUtils; import de.infsec.tpl.profile.ProfileMatch.MatchLevel; import de.infsec.tpl.utils.Utils; import de.infsec.tpl.utils.WalaUtils; /** * The main data structure for (library/app) profiles * @author ederr * */ @Deprecated public class HashTreeOLD implements Serializable { private static final long serialVersionUID = 8890771073564530924L; private static final Logger logger = LoggerFactory.getLogger(HashTreeOLD.class); private Config config = new Config(); public enum HTREE_BUILD_VERBOSENESS { MINIMAL /* Root and Package hashes/names only */, NORMAL /* Root/Package/Class hashes including package names (DEFAULT) */, DEBUG /* Root/Package/Class hashes including package/class names */, TRACE /* Root/Package/Class/Method hashes including all names/signatures */ } /** * Build config for HashTreeOLD */ public class Config implements Serializable { private static final long serialVersionUID = -8693957635226365553L; // if true, filters duplicate method hashes, i.e. methods that have the same fuzzy descriptor // this introduces some kind of fuzziness as we abstract from the concrete number of certain descriptor public boolean filterDups = false; // exclude methods while hashing whose access specifier (see {@link AccessFlags}) matches the filter value public int accessFlagsFilter = AccessFlags.NO_FLAG.getValue(); // if true, inner classes are not considered during hashing public boolean filterInnerClasses = false; // the hash algorithm used for hashing public HashAlgorithm hashAlgorithm = HashAlgorithm.MD5; public HTREE_BUILD_VERBOSENESS buildVerboseness = HTREE_BUILD_VERBOSENESS.NORMAL; public Config() {} public Config(boolean filterDups, boolean filterInnerClasses) { this.filterDups = filterDups; this.filterInnerClasses = filterInnerClasses; } public Config(boolean filterDups, boolean filterInnerClasses, HashAlgorithm hashAlgorithm) { this(filterDups, filterInnerClasses); this.hashAlgorithm = hashAlgorithm; } @Override public boolean equals(Object obj) { if (!(obj instanceof Config)) return false; Config c = (Config) obj; return c.filterDups == this.filterDups && c.accessFlagsFilter == this.accessFlagsFilter && c.filterInnerClasses == this.filterInnerClasses && c.hashAlgorithm.equals(this.hashAlgorithm); } @Override public int hashCode() { return 10000 * (this.filterDups? 1 : 0) + 1000 * this.accessFlagsFilter + 100 * (this.filterInnerClasses? 1 : 0) + hashAlgorithm.value.hashCode(); } @Override public String toString() { StringBuilder sb = new StringBuilder("[Config]"); sb.append("filterDups? " + this.filterDups); sb.append(" | accessFlagsFilter? [" + this.accessFlagsFilter + "] " + AccessFlags.flags2Str(this.accessFlagsFilter)); sb.append(" | filterInnerClasses: " + this.filterInnerClasses); sb.append(" | hash-algo: "+ this.hashAlgorithm); sb.append(" | verboseness: "+ this.buildVerboseness); return sb.toString(); } } private Node rootNode; public class Node implements Serializable { private static final long serialVersionUID = 8649289911402320347L; public byte[] hash; public List childs; public Node(byte[] hash) { this.hash = hash; this.childs = new ArrayList(); } @Override public boolean equals(Object obj) { if (!(obj instanceof Node)) return false; return Hash.equals(((Node) obj).hash, this.hash); } @Override public int hashCode() { return hash.hashCode() + childs.size(); } @Override public String toString() { return Hash.hash2Str(this.hash); } public int numberOfChilds() { return this.childs.size(); } public void debug() {} public String getStats() { StringBuilder sb = new StringBuilder(); int pNodes = 0; int cNodes = 0; int mNodes = 0; LinkedList worklist = new LinkedList(); worklist.add(this); Node curNode; while (!worklist.isEmpty()) { curNode = worklist.poll(); worklist.addAll(curNode.childs); for (Node n: curNode.childs) { if (n instanceof PackageNode) pNodes++; else if (n instanceof ClassNode) cNodes++; else if (n instanceof MethodNode) mNodes++; } } sb.append("Node stats:\n"); sb.append(Utils.INDENT + "- contains " + mNodes + " method hashes.\n"); sb.append(Utils.INDENT + "- contains " + cNodes + " clazz hashes.\n"); sb.append(Utils.INDENT + "- contains " + pNodes + " package hashes."); return sb.toString(); } public boolean isLeaf() { return childs.isEmpty(); } } public class NodeComparator implements Comparator { private ByteArrayComparator comp; public NodeComparator() throws NoSuchAlgorithmException { IHash hashFunc = new HashImpl(config.hashAlgorithm.toString()); comp = ((Hash) hashFunc).new ByteArrayComparator(); } @Override public int compare(Node n0, Node n1) { return comp.compare(n0.hash, n1.hash); } } public class PackageNode extends Node implements Serializable { private static final long serialVersionUID = -2824664777266635012L; public String packageName; public PackageNode(byte[] hash, String packageName) { super(hash); this.packageName = packageName; } @Override public void debug() { logger.info("Debug PackageNode: " + packageName + " (childs: " + childs.size() + ", " + Hash.hash2Str(hash) + ")"); for (Node n: this.childs) { ClassNode cn = (ClassNode) n; logger.info(Utils.INDENT + "- " + cn.clazzName + " :: " + cn.numberOfChilds() + " :: " + Hash.hash2Str(cn.hash)); // cn.debug(); } } public List getClassNodes() { return this.childs; } public List getMethodNodes() { ArrayList result = new ArrayList(); for (Node n: this.childs) { result.addAll(((ClassNode) n).getMethodNodes()); } return result; } @Override public boolean equals(Object obj) { if (!(obj instanceof PackageNode)) return false; return Hash.equals(((PackageNode) obj).hash, this.hash); } @Override public String toString() { return "PNode(" + packageName + ")"; } } public class ClassNode extends Node implements Serializable { private static final long serialVersionUID = 4538829579264140006L; public String clazzName; public ClassNode(byte[] hash, String clazzName) { super(hash); this.clazzName = clazzName; } public List getMethodNodes() { return this.childs; } @Override public void debug() { //logger.info("Debug ClassNode: " + clazzName + " (childs: " + childs.size() + ", " + Hash.hash2Human(hash) + ")"); for (Node n: this.childs) { MethodNode mn = (MethodNode) n; logger.info(Utils.INDENT2 + "- " + mn.signature + " :: " + Hash.hash2Str(mn.hash)); } } @Override public boolean equals(Object obj) { if (!(obj instanceof ClassNode)) return false; return Hash.equals(((ClassNode) obj).hash, this.hash); } @Override public String toString() { return "CNode(" + clazzName + ")"; } } public class MethodNode extends Node implements Serializable { private static final long serialVersionUID = -1147942448831557142L; public String signature; public MethodNode(byte[] hash, String signature) { super(hash); this.signature = signature; } @Override public boolean equals(Object obj) { if (!(obj instanceof MethodNode)) return false; return Hash.equals(((MethodNode) obj).hash, this.hash); } @Override public String toString() { return "MNode(" + signature + ")"; } } public enum HashAlgorithm { MD5("MD5"), SHA1("SHA-1"), SHA256("SHA-256"); private String value; HashAlgorithm(String value) { this.value = value; } @Override public String toString() { return this.value; } }; public HashTreeOLD() { this.config = new Config(); } public HashTreeOLD(boolean filterDups, boolean filterInnerClasses, HashAlgorithm algorithm) throws NoSuchAlgorithmException { this.config = new Config(filterDups, filterInnerClasses, algorithm); } /* * Setter methods */ /** * Sets the method access flag filter * @param flags an array of {@link AccessFlags} (only private, package-protected, protected, and public). Unset the filter by providing a null argument. */ public void setAccessFlagFilter(AccessFlags... flags) { config.accessFlagsFilter = AccessFlags.getAccessFlagFilter(flags); } /** * Filter any method but public methods for hashing */ public void setPublicOnlyFilter() { setAccessFlagFilter(AccessFlags.PRIVATE, AccessFlags.PACKAGE_PROTECTED, AccessFlags.PROTECTED); } public void setPrivateMethodsFilter() { setAccessFlagFilter(AccessFlags.PRIVATE); } public void setFilterDups(final boolean filterDups) { config.filterDups = filterDups; } public void setFilterInnerClasses(final boolean filterInnerClasses) { config.filterInnerClasses = filterInnerClasses; } public void setHashAlgorithm(final HashAlgorithm algorithm) { config.hashAlgorithm = algorithm; } public void setBuildVerboseness(final HTREE_BUILD_VERBOSENESS v) { config.buildVerboseness = v; } public boolean hasDefaultConfig() { return !this.config.filterDups && !this.config.filterInnerClasses && this.config.accessFlagsFilter == 0x0; } /* * Getter methods */ public Node getRootNode() { return this.rootNode; } public byte[] getRootHash() { return this.rootNode.hash; } public Config getConfig() { return this.config; } public Collection getPackageNodes() { return this.getRootNode().childs; } public int getNumberOfPackages() { return rootNode.numberOfChilds(); } public int getNumberOfClasses() { int cCount = 0; for (Node pNode: rootNode.childs) cCount += getNumberOfClasses((PackageNode) pNode); return cCount; } public int getNumberOfClasses(PackageNode pNode) { return pNode.numberOfChilds(); } public int getNumberOfMethods(PackageNode pNode) { int mCount = 0; for (Node cNode: pNode.childs) mCount += cNode.numberOfChilds(); return mCount; } public int getNumberOfMethods() { int mCount = 0; for (Node pNode: rootNode.childs) mCount += getNumberOfMethods((PackageNode) pNode); return mCount; } public List getAllMethodSignatures() { List signatures = new ArrayList(); for (Node pNode: rootNode.childs) { for (Node cNode: pNode.childs) { for (Node mNode: cNode.childs) { signatures.add(((MethodNode) mNode).signature); } } } Collections.sort(signatures); return signatures; } public int getNumberOfHashesByLevel(MatchLevel lvl) { switch(lvl) { case CLASS: return getNumberOfClasses(); case METHOD: return getNumberOfMethods(); case PACKAGE: return getNumberOfPackages(); } return -1; } public void printConfig() { logger.info(config.toString()); } public boolean matchesConfig(HashTreeOLD hTree) { return this.config.equals(hTree.getConfig()); } @Override public boolean equals(Object obj) { if (!(obj instanceof HashTreeOLD)) return false; HashTreeOLD ht = (HashTreeOLD) obj; if (!ht.config.equals(this.config)) return false; return Hash.equals(this.getRootHash(), ht.getRootHash()); } /** * Generates a HashTreeOLD for every class loaded via application classLoader * @throws NoSuchAlgorithmException */ public void generate(IClassHierarchy cha) throws NoSuchAlgorithmException { logger.debug("Generate library hash tree.."); if (logger.isDebugEnabled()) printConfig(); IHash hashFunc = new HashImpl(config.hashAlgorithm.toString()); NodeComparator comp = new NodeComparator(); int classHashCount = 0; int methodHashCount = 0; // create map package name -> list of clazzNodes HashMap> packageMap = new HashMap>(); for (IClass clazz: cha) { if (WalaUtils.isAppClass(clazz)){ // inner class filter if (config.filterInnerClasses && WalaUtils.isInnerClass(clazz)) { continue; } // duplicate method filter Collection methodNodes = config.filterDups? new TreeSet(comp) : new ArrayList(); Collection methods = clazz.getDeclaredMethods(); // filter methods by access flag if (config.accessFlagsFilter != AccessFlags.NO_FLAG.getValue()) { methods = methods.stream() .filter(m -> { int code = AccessFlags.getMethodAccessCode(m); return code > 0 && (code & config.accessFlagsFilter) == 0x0; }) // if predicate is true, keep in list .collect(Collectors.toCollection(ArrayList::new)); } for (IMethod m: methods) { // normalize java|dex bytecode by skipping compiler-generated methods if (m.isBridge() || m.isSynthetic()) { continue; } String normalizedDesc = normalizeAnonymousInnerClassConstructor(m); byte[] hash = hashFunc.hash(normalizedDesc != null? normalizedDesc : getFuzzyDescriptor(m)); methodNodes.add(new MethodNode(hash, m.getSignature())); } // normalization (if we have no methods, either because there are none or due to our filter properties, skip this class) if (methodNodes.isEmpty()) { logger.trace(Utils.INDENT + ">> No methods found for clazz: " + WalaUtils.simpleName(clazz) + " [SKIP]"); continue; } if (!config.filterDups) Collections.sort((List) methodNodes, comp); // sort but do not filter dups methodHashCount += methodNodes.size(); classHashCount++; byte[] clazzHash = hash(methodNodes, hashFunc); String classIdentifier = config.buildVerboseness == HTREE_BUILD_VERBOSENESS.DEBUG || config.buildVerboseness == HTREE_BUILD_VERBOSENESS.TRACE? WalaUtils.simpleName(clazz) : ""; ClassNode clazzNode = new ClassNode(clazzHash, classIdentifier); // only store method hashes if configured (space vs accuracy) clazzNode.childs = config.buildVerboseness == HTREE_BUILD_VERBOSENESS.TRACE? new ArrayList(methodNodes) : new ArrayList(); String pckgName = PackageUtils.getPackageName(clazz); if (!packageMap.containsKey(pckgName)) { packageMap.put(pckgName, config.filterDups? new TreeSet(comp) : new ArrayList()); } packageMap.get(pckgName).add(clazzNode); } } Collection packageNodes = config.filterDups? new TreeSet(comp) : new ArrayList(); for (String pckgName: new TreeSet(packageMap.keySet())) { if (!config.filterDups) Collections.sort((List) packageMap.get(pckgName), comp); // sort but do not filter dups byte[] packageHash = hash(packageMap.get(pckgName), hashFunc); PackageNode n = new PackageNode(packageHash, pckgName); if (!config.buildVerboseness.equals(HTREE_BUILD_VERBOSENESS.MINIMAL)) // do not add class nodes in min verboseness n.childs.addAll(packageMap.get(pckgName)); packageNodes.add(n); } logger.debug(Utils.INDENT + "- generated " + methodHashCount + " method hashes."); logger.debug(Utils.INDENT + "- generated " + classHashCount + " clazz hashes."); logger.debug(Utils.INDENT + "- generated " + packageNodes.size() + " package hashes."); // generate library hash if (!config.filterDups) Collections.sort((List) packageNodes, comp); // sort but do not filter dups byte[] libraryHash = hash(packageNodes, hashFunc); rootNode = new Node(libraryHash); rootNode.childs.addAll(packageNodes); logger.debug(Utils.INDENT + "=> Library Hash: " + Hash.hash2Str(libraryHash)); } public Node getSubTreeByPackage(PackageTree ptree) throws NoSuchAlgorithmException { String rootPackage = ptree.getRootPackage(); // Since we have a flattened tree (in terms of package nodes, we collect all package nodes that // equal or start with the rootPackage, then create and return a new rootnode with the collected package nodes // as child NodeComparator comp = new NodeComparator(); IHash hashFunc = new HashImpl(config.hashAlgorithm.toString()); Collection childs = config.filterDups? new TreeSet(comp) : new ArrayList(); for (Node n: rootNode.childs) { PackageNode pn = (PackageNode) n; if (pn.packageName.startsWith(rootPackage)) childs.add(pn); } if (!config.filterDups) Collections.sort((List) childs, comp); // sort but do not filter dups // generate new root node if (!childs.isEmpty()) { Node rootNode = new Node(hash(childs, hashFunc)); rootNode.childs.addAll(childs); return rootNode; } else return null; // no matching node found } public Node generateRootNode(Collection pnodes) throws NoSuchAlgorithmException { // Since we have a flattened tree (in terms of package nodes, we collect all package nodes that // equal or start with the rootPackage, then create and return a new rootnode with the collected package nodes // as child NodeComparator comp = new NodeComparator(); IHash hashFunc = new HashImpl(config.hashAlgorithm.toString()); Collection childs = config.filterDups? new TreeSet(comp) : new ArrayList(); childs.addAll(pnodes); if (!config.filterDups) Collections.sort((List) childs, comp); // sort but do not filter dups // generate new root node if (childs.isEmpty()) { logger.warn("[generateRootNode] no childs - return empy rootNode"); } Node rootNode = new Node(hash(childs, hashFunc)); rootNode.childs.addAll(childs); return rootNode; } /** * Generic hash function that takes a list of hashes, concatenates and hashes them * @param nodes a collection of input {@link Node} * @param hashFunc a hash function * @return a hash */ public static byte[] hash(Collection nodes, final IHash hashFunc) { ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); try { for (Node node: nodes) outputStream.write(node.hash); } catch (IOException e) { e.printStackTrace(); } byte[] arr = outputStream.toByteArray(); return hashFunc.hash(arr); } /** * This normalization of constructors of anonymous inner classes is due to the fact that * (in some cases) the dx compiler adds the superclass of the enclosing class as second parameter, * while the javac does not. This results in different hashes for this classes which implies that * this library can't be matched exactly although it is the same version. Therefore, this * normalization will skip this second argument during generation of the fuzzy descriptor. * * See example code: method createAdapter() in * https://github.com/facebook/facebook-android-sdk/blob/f6c4e4c1062bcc74e5a25b3243f6bee2e32d949e/facebook/src/com/facebook/widget/PlacePickerFragment.java * * In the disassembled dex the constructor can look like this: * * method constructor (Lcom/facebook/widget/PlacePickerFragment;Lcom/facebook/widget/PickerFragment;Landroid/content/Context;)V * .locals 0 * .param p3, "$anonymous0" # Landroid/content/Context; * * iput-object p1, p0, Lcom/facebook/widget/PlacePickerFragment$1;->this$0:Lcom/facebook/widget/PlacePickerFragment; * invoke-direct {p0, p2, p3}, Lcom/facebook/widget/PickerFragment$PickerFragmentAdapter;->(Lcom/facebook/widget/PickerFragment;Landroid/content/Context;)V * return-void * .end method * * * while the decompiled class file looks as follows: * * PlacePickerFragment$1(PlacePickerFragment paramPlacePickerFragment, Context x0) { * super(paramPlacePickerFragment, x0); * } * * * @param m the {@link IMethod} to normalize * @return null if this normalization does not apply, otherwise the normalized fuzzy descriptor {@see getFuzzyDescriptor} */ private static String normalizeAnonymousInnerClassConstructor(IMethod m) { if (WalaUtils.isAnonymousInnerInnerClass(m.getDeclaringClass()) && m.isInit() && m.getNumberOfParameters() > 1) { // this can be anything -> normalize constructor to (X)V logger.trace("[normalizeAnonymousInnerClassConstructor] found anonymous inner inner class constructor: "+ m.getSignature()); return "(X)V"; } // check if we have an anonymous inner class constructor with a sufficient number of arguments if (WalaUtils.isAnonymousInnerClass(m.getDeclaringClass()) && m.isInit() && m.getNumberOfParameters() > 2) { logger.trace("[normalizeAnonymousInnerClassConstructor] method: " + m.getSignature()); logger.trace(Utils.INDENT + "descriptor: " + m.getDescriptor() + " # params: " + m.getNumberOfParameters()); String enclosingClazzName = WalaUtils.simpleName(m.getDeclaringClass()); enclosingClazzName = enclosingClazzName.substring(0, enclosingClazzName.lastIndexOf('$')); // check if both argument types are custom types for (int i : new Integer[]{1, 2}) { if (m.getParameterType(i).getClassLoader().equals(ClassLoaderReference.Application)) { IClass ct = m.getClassHierarchy().lookupClass(m.getParameterType(1)); boolean isAppClazz = ct == null || WalaUtils.isAppClass(ct); if (!isAppClazz) return null; } else return null; } IClass superClazz = null; try { IClass ic = WalaUtils.lookupClass(m.getClassHierarchy(), enclosingClazzName); superClazz = ic.getSuperclass(); } catch (ClassNotFoundException e) { // class lookup can also fail for lambdas, e.g. if superclass is kotlin.jvm.internal.Lambda // we then default to fuzzy descriptor logger.trace("Could not lookup " + enclosingClazzName + " in bytecode normalization"); return null; } String argType1 = Utils.convertToFullClassName(m.getParameterType(1).getName().toString()); String argType2 = Utils.convertToFullClassName(m.getParameterType(2).getName().toString()); // now check whether this normalization needs to be applied if (argType1.equals(enclosingClazzName) && argType2.equals(WalaUtils.simpleName(superClazz))) { StringBuilder sb = new StringBuilder("("); // param0 is the object (for non-static calls), param1 the first arg to be skipped (doesn't matter // if whether we skip param1 or param2 since both are replaced by placeholder value) for (int i = 2; i < m.getNumberOfParameters(); i++) { if (m.getParameterType(i).getClassLoader().equals(ClassLoaderReference.Application)) { IClass ct = m.getClassHierarchy().lookupClass(m.getParameterType(i)); boolean isAppClazz = ct == null || WalaUtils.isAppClass(ct); sb.append(isAppClazz ? customTypeReplacement : m.getParameterType(i).getName().toString()); } else sb.append(m.getParameterType(i).getName().toString()); } sb.append(")V"); logger.trace(Utils.INDENT + "> bytecode normalization applied to " + m.getSignature() + " fuzzy desc: " + sb.toString()); return sb.toString(); } } return null; } private static final String customTypeReplacement = "X"; /** * A {@link Descriptor} only describes input arg types + return type, e.g. * The Descriptor of AdVideoView.onError(Landroid/media/MediaPlayer;II)Z is (Landroid/media/MediaPlayerII)Z * In order to produce a fuzzy (robust against identifier-renaming) descriptor we replace each custom type by a fixed * replacement, e.g. we receive a descriptor like (XII)Z * Note: library dependencies, i.e. lib A depends on lib B are not a problem. If we analyze lib A without loading lib B, * any type of lib B will be loaded with the Application classloader but will _not_ be in the classhierarchy. * @param m {@link IMethod} * @return a fuzzy descriptor */ private static String getFuzzyDescriptor(IMethod m) { logger.trace("[getFuzzyDescriptor]"); logger.trace("- signature: " + m.getSignature()); logger.trace("- descriptor: " + m.getDescriptor().toString()); StringBuilder sb = new StringBuilder("("); for (int i = (m.isStatic()? 0 : 1) ; i < m.getNumberOfParameters(); i++) { boolean isAppClazz = false; if (m.getParameterType(i).getClassLoader().equals(ClassLoaderReference.Application)) { IClass ct = m.getClassHierarchy().lookupClass(m.getParameterType(i)); isAppClazz = ct == null || WalaUtils.isAppClass(ct); sb.append(isAppClazz? customTypeReplacement : m.getParameterType(i).getName().toString()); } else sb.append(m.getParameterType(i).getName().toString()); //logger.trace(LogConfig.INDENT + "- param ref: " + m.getParameterType(i).getName().toString() + (isAppClazz? " -> " + customTypeReplacement : "")); } //logger.trace(""); sb.append(")"); if (m.getReturnType().getClassLoader().equals(ClassLoaderReference.Application)) { IClass ct = m.getClassHierarchy().lookupClass(m.getReturnType()); sb.append(ct == null || WalaUtils.isAppClass(ct)? customTypeReplacement : m.getReturnType().getName().toString()); } else sb.append(m.getReturnType().getName().toString()); logger.trace("-> new type: " + sb.toString()); return sb.toString(); } public static HashTreeOLD getTreeByConfig(Collection treeList, Config config) { for (HashTreeOLD lht: treeList) if (lht.getConfig().equals(config)) return lht; return null; } public static HashTreeOLD getTreeByConfig(Collection treeList, boolean filterDups, int accessFlagFilter, boolean filterInnerClasses) { for (HashTreeOLD lht: treeList) { Config cfg = lht.getConfig(); if (cfg.filterDups == filterDups && cfg.accessFlagsFilter == accessFlagFilter && cfg.filterInnerClasses == filterInnerClasses) return lht; } return null; } public static List toPackageNode(Collection col) { List res = new ArrayList(); for (Node n: col) { if (n instanceof PackageNode) res.add((PackageNode) n); } return res; } public static void debug_compareNodes(PackageNode libNode, PackageNode appNode) { logger.info("Compare packageNodes: [LIB] " + libNode.packageName + " vs [APP] " + appNode.packageName); for (Node n: libNode.childs) { if (!appNode.childs.contains(n)) { logger.info(" - LibNode:"); n.debug(); Node an = debug_getNodeByName(appNode.childs, ((ClassNode) n).clazzName); if (an != null) { logger.info(" - AppNode:"); an.debug(); } } } } private static Node debug_getNodeByHash(Collection col, byte[] hash) { if (col != null) { for (Iterator it = col.iterator(); it.hasNext(); ) { Node n = it.next(); if (Hash.equals(n.hash, hash)) return n; } } return null; } private static Node debug_getNodeByName(Collection col, String clazzName) { if (col != null) { for (Iterator it = col.iterator(); it.hasNext(); ) { Node n = it.next(); if (n instanceof ClassNode && (((ClassNode) n).clazzName.equals(clazzName))) return n; } } return null; } } LibScout-2.3.2/src/de/infsec/tpl/hash/IHash.java000066400000000000000000000013441342431362400212550ustar00rootroot00000000000000/* * Copyright (c) 2015-2017 Erik Derr [derr@cs.uni-saarland.de] * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this * file except in compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package de.infsec.tpl.hash; public interface IHash { public byte[] hash(String str); public byte[] hash(byte[] b); } LibScout-2.3.2/src/de/infsec/tpl/hashtree/000077500000000000000000000000001342431362400202745ustar00rootroot00000000000000LibScout-2.3.2/src/de/infsec/tpl/hashtree/HashTree.java000066400000000000000000000147341342431362400226530ustar00rootroot00000000000000/* * Copyright (c) 2015-2017 Erik Derr [derr@cs.uni-saarland.de] * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this * file except in compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package de.infsec.tpl.hashtree; import com.google.common.hash.Hasher; import com.ibm.wala.classLoader.IClass; import com.ibm.wala.classLoader.IMethod; import com.ibm.wala.ipa.cha.IClassHierarchy; import de.infsec.tpl.hash.AccessFlags; import de.infsec.tpl.hashtree.comp.clazz.DefaultClassNodeComp; import de.infsec.tpl.hashtree.comp.clazz.IClassNodeComp; import de.infsec.tpl.hashtree.comp.method.IMethodNodeComp; import de.infsec.tpl.hashtree.comp.method.SignatureMethodNodeComp; import de.infsec.tpl.hashtree.comp.pckg.DefaultPackageNodeComp; import de.infsec.tpl.hashtree.comp.pckg.IPackageNodeComp; import de.infsec.tpl.hashtree.node.*; import de.infsec.tpl.pkg.PackageUtils; import de.infsec.tpl.utils.Utils; import de.infsec.tpl.utils.WalaUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.Serializable; import java.util.*; import java.util.stream.Collectors; /* * props: * single/multi-version * * common functionality:: * - stats * - debug? * - generate?? depends on data * * abstract/interface Node * props:: single vs multi-version nodes * */ /** * A Merkle hash tree implementation used to efficiently * identify code reuse in apps through library code */ public class HashTree implements Serializable { private static final long serialVersionUID = 8890771073564531337L; transient private static final Logger logger = LoggerFactory.getLogger(HashTree.class); transient final IPackageNodeComp pnComp; transient final IClassNodeComp cnComp; transient final IMethodNodeComp mnComp; private Node rootNode; protected TreeConfig config = new TreeConfig(); public HashTree() { this(new DefaultPackageNodeComp(), new DefaultClassNodeComp(), new SignatureMethodNodeComp()); } public HashTree(IPackageNodeComp pnComp, IClassNodeComp cnComp, IMethodNodeComp mnComp) { this.pnComp = pnComp; this.cnComp = cnComp; this.mnComp = mnComp; } @Override public boolean equals(Object obj) { if (!(obj instanceof HashTree)) return false; return Arrays.equals(this.getRootHash(), ((HashTree) obj).getRootHash()); } public static Node compNode(Collection nodes, boolean prune, Hasher hasher) { nodes.stream().sorted(HashUtils.comp).forEach(n -> hasher.putBytes(n.hash)); Node n = new Node(hasher.hash().asBytes()); if (!prune) n.childs.addAll(nodes); return n; } public void generate(IClassHierarchy cha) { logger.debug("Generate hash tree.."); int classHashCount = 0; int methodHashCount = 0; // create map package name -> set of clazzNodes HashMap> packageMap = new HashMap<>(); for (IClass clazz: cha) { if (WalaUtils.isAppClass(clazz)) { Collection methods = clazz.getDeclaredMethods(); // filter methods by access flag if (config.accessFlagsFilter != AccessFlags.NO_FLAG) { methods = methods.stream() .filter(m -> { int code = AccessFlags.getMethodAccessCode(m); return code > 0 && (code & config.accessFlagsFilter.getValue()) == 0x0; }) // if predicate is true, keep in list .collect(Collectors.toCollection(ArrayList::new)); } List methodNodes = methods.stream() .filter(m -> !(m.isBridge() || m.isSynthetic())) // normalize java|dex bytecode by skipping compiler-generated methods .map(m -> mnComp.comp(m, config)) .sorted(HashUtils.comp) // sort but do not filter dups .collect(Collectors.toList()); // normalize - skip classes with no methods if (methodNodes.isEmpty()) { logger.trace(Utils.INDENT + ">> No methods found for clazz: " + WalaUtils.simpleName(clazz)); continue; } // update stats methodHashCount += methodNodes.size(); classHashCount++; ClassNode clazzNode = cnComp.comp(methodNodes, clazz, config); // keep track on classes per package String pckgName = PackageUtils.getPackageName(clazz); if (!packageMap.containsKey(pckgName)) { packageMap.put(pckgName, new ArrayList<>()); } packageMap.get(pckgName).add(clazzNode); } } packageMap.values().forEach(l -> l.sort(HashUtils.comp)); // sort class nodes List packageNodes = packageMap.keySet().stream() .map(p -> pnComp.comp(packageMap.get(p), p, cha, config)) .sorted(HashUtils.comp) .collect(Collectors.toList()); // generate root rootNode = compNode(packageNodes, false, config.getHasher()); logger.debug(Utils.INDENT + "- generated " + packageNodes.size() + " package hashes."); logger.debug(Utils.INDENT + "- generated " + classHashCount + " clazz hashes."); logger.debug(Utils.INDENT + "- generated " + methodHashCount + " method hashes."); logger.debug(Utils.INDENT + "=> Library Hash: " + HashUtils.hash2Str(rootNode.hash)); } /* * Getter methods */ public Node getRootNode() { return this.rootNode; } public byte[] getRootHash() { return this.rootNode.hash; } public TreeConfig getConfig() { return this.config; } public void setConfig(TreeConfig config) { this.config = config; } public static List toPackageNode(Collection col) { return col.stream() .filter(n -> n instanceof PackageNode) .map(n -> (PackageNode) n).collect(Collectors.toList()); } public Collection getPackageNodes() { return this.getRootNode().childs.stream().map(pn -> (PackageNode) pn).collect(Collectors.toList()); } public int getNumberOfPackages() { return rootNode.numberOfChilds(); } public int getNumberOfClasses() { return rootNode.childs.stream().mapToInt(pn -> pn.childs.size()).sum(); } public int getNumberOfClasses(PackageNode pn) { return pn.numberOfChilds(); } public int getNumberOfMethods(PackageNode pn) { return pn.childs.stream().mapToInt(cn -> cn.childs.size()).sum(); } public int getNumberOfMethods() { return rootNode.childs.stream() .map(pn -> pn.childs) .flatMap(Collection::stream) .mapToInt(cn -> cn.childs.size()).sum(); } } LibScout-2.3.2/src/de/infsec/tpl/hashtree/HashUtils.java000066400000000000000000000017451342431362400230520ustar00rootroot00000000000000package de.infsec.tpl.hashtree; import de.infsec.tpl.hashtree.node.Node; import java.util.Comparator; public class HashUtils { public static String hash2Str(byte[] hash) { String format = "%" + (hash.length*2) + "x"; return String.format(format, new java.math.BigInteger(1, hash)); } public static final NodeComparator comp = new NodeComparator(); public static class NodeComparator implements Comparator { public NodeComparator() {} private int compare(byte[] left, byte[] right) { for (int i = 0, j = 0; i < left.length && j < right.length; i++, j++) { int a = (left[i] & 0xff); int b = (right[j] & 0xff); if (a != b) { return a - b; } } return left.length - right.length; } @Override public int compare(Node n0, Node n1) { return compare(n0.hash, n1.hash); } } } LibScout-2.3.2/src/de/infsec/tpl/hashtree/TreeConfig.java000066400000000000000000000027641342431362400231750ustar00rootroot00000000000000package de.infsec.tpl.hashtree; import com.google.common.hash.HashFunction; import com.google.common.hash.Hasher; import com.google.common.hash.Hashing; import de.infsec.tpl.hash.AccessFlags; import de.infsec.tpl.utils.Utils; import java.io.Serializable; import java.util.ArrayList; import java.util.List; public class TreeConfig implements Serializable { private static final long serialVersionUID = 1190771073563431337L; public HashFunction hf = Hashing.md5(); public AccessFlags accessFlagsFilter = AccessFlags.NO_FLAG; // verboseness public boolean keepPackageNames = true; public boolean keepClassNames = false; public boolean keepMethodSignatures = false; // node pruning public boolean pruneClasses = false; public boolean pruneMethods = true; public Hasher getHasher() { return hf.newHasher(); } @Override public String toString() { List l = new ArrayList<>(); if (keepPackageNames) l.add("PN"); if (keepClassNames) l.add("CN"); if (keepMethodSignatures) l.add("MSIG"); String keep = l.isEmpty()? "" : Utils.join(l, "|"); l = new ArrayList<>(); if (pruneClasses) l.add("CN"); if (pruneMethods) l.add("MSIG"); String prune = l.isEmpty()? "" : Utils.join(l, "|"); return hf.toString() + " | Flags: " + accessFlagsFilter + (keep.isEmpty()? "" : " | Keep: " + keep) + (prune.isEmpty()? "" : " | Prune: " + prune); } }LibScout-2.3.2/src/de/infsec/tpl/hashtree/comp/000077500000000000000000000000001342431362400212325ustar00rootroot00000000000000LibScout-2.3.2/src/de/infsec/tpl/hashtree/comp/clazz/000077500000000000000000000000001342431362400223555ustar00rootroot00000000000000LibScout-2.3.2/src/de/infsec/tpl/hashtree/comp/clazz/DefaultClassNodeComp.java000066400000000000000000000015771342431362400272310ustar00rootroot00000000000000package de.infsec.tpl.hashtree.comp.clazz; import com.ibm.wala.classLoader.IClass; import de.infsec.tpl.hashtree.HashTree; import de.infsec.tpl.hashtree.TreeConfig; import de.infsec.tpl.hashtree.node.ClassNode; import de.infsec.tpl.hashtree.node.Node; import de.infsec.tpl.utils.WalaUtils; import java.util.ArrayList; import java.util.Collection; public class DefaultClassNodeComp implements IClassNodeComp { @Override public ClassNode comp(Collection methodNodes, IClass clazz, TreeConfig config) { String className = config.keepClassNames ? WalaUtils.simpleName(clazz) : ""; // default behaviour, just create hash from child nodes ClassNode cn = new ClassNode(HashTree.compNode(methodNodes, true, config.getHasher()).hash, className); if (!config.pruneMethods) cn.childs = new ArrayList<>(methodNodes); return cn; } } LibScout-2.3.2/src/de/infsec/tpl/hashtree/comp/clazz/IClassNodeComp.java000066400000000000000000000005651342431362400260310ustar00rootroot00000000000000package de.infsec.tpl.hashtree.comp.clazz; import com.ibm.wala.classLoader.IClass; import de.infsec.tpl.hashtree.TreeConfig; import de.infsec.tpl.hashtree.node.ClassNode; import de.infsec.tpl.hashtree.node.Node; import java.util.Collection; public interface IClassNodeComp { ClassNode comp(Collection methodNodes, IClass clazz, TreeConfig config); } LibScout-2.3.2/src/de/infsec/tpl/hashtree/comp/method/000077500000000000000000000000001342431362400225125ustar00rootroot00000000000000LibScout-2.3.2/src/de/infsec/tpl/hashtree/comp/method/IMethodNodeComp.java000066400000000000000000000004101342431362400263260ustar00rootroot00000000000000package de.infsec.tpl.hashtree.comp.method; import com.ibm.wala.classLoader.IMethod; import de.infsec.tpl.hashtree.TreeConfig; import de.infsec.tpl.hashtree.node.MethodNode; public interface IMethodNodeComp { MethodNode comp(IMethod m, TreeConfig config); } LibScout-2.3.2/src/de/infsec/tpl/hashtree/comp/method/SignatureMethodNodeComp.java000066400000000000000000000212501342431362400301040ustar00rootroot00000000000000package de.infsec.tpl.hashtree.comp.method; import com.ibm.wala.classLoader.IClass; import com.ibm.wala.classLoader.IMethod; import com.ibm.wala.types.ClassLoaderReference; import com.ibm.wala.types.Descriptor; import de.infsec.tpl.hashtree.HashTree; import de.infsec.tpl.hashtree.TreeConfig; import de.infsec.tpl.hashtree.node.MethodNode; import de.infsec.tpl.utils.Utils; import de.infsec.tpl.utils.WalaUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class SignatureMethodNodeComp implements IMethodNodeComp { private static final Logger logger = LoggerFactory.getLogger(HashTree.class); @Override public MethodNode comp(IMethod m, TreeConfig config) { String desc = normalizeAnonymousInnerClassConstructor(m); if (desc == null) desc = getFuzzyDescriptor(m); String signature = config.keepMethodSignatures? m.getSignature() : ""; return new MethodNode(config.getHasher().putBytes(desc.getBytes()).hash().asBytes(), signature); } /** * This normalization of constructors of anonymous inner classes is due to the fact that * (in some cases) the dx compiler adds the superclass of the enclosing class as second parameter, * while the javac does not. This results in different hashes for this classes which implies that * this library can't be matched exactly although it is the same version. Therefore, this * normalization will skip this second argument during generation of the fuzzy descriptor. * * See example code: method createAdapter() in * https://github.com/facebook/facebook-android-sdk/blob/f6c4e4c1062bcc74e5a25b3243f6bee2e32d949e/facebook/src/com/facebook/widget/PlacePickerFragment.java * * In the disassembled dex the constructor can look like this: * * method constructor (Lcom/facebook/widget/PlacePickerFragment;Lcom/facebook/widget/PickerFragment;Landroid/content/Context;)V * .locals 0 * .param p3, "$anonymous0" # Landroid/content/Context; * * iput-object p1, p0, Lcom/facebook/widget/PlacePickerFragment$1;->this$0:Lcom/facebook/widget/PlacePickerFragment; * invoke-direct {p0, p2, p3}, Lcom/facebook/widget/PickerFragment$PickerFragmentAdapter;->(Lcom/facebook/widget/PickerFragment;Landroid/content/Context;)V * return-void * .end method * * * while the decompiled class file looks as follows: * * PlacePickerFragment$1(PlacePickerFragment paramPlacePickerFragment, Context x0) { * super(paramPlacePickerFragment, x0); * } * * * @param m the {@link IMethod} to normalize * @return null if this normalization does not apply, otherwise the normalized fuzzy descriptor {@see getFuzzyDescriptor} */ private static String normalizeAnonymousInnerClassConstructor(IMethod m) { if (WalaUtils.isAnonymousInnerInnerClass(m.getDeclaringClass()) && m.isInit() && m.getNumberOfParameters() > 1) { // this can be anything -> normalize constructor to (X)V logger.trace("[normalizeAnonymousInnerClassConstructor] found anonymous inner inner class constructor: "+ m.getSignature()); return "(X)V"; } // check if we have an anonymous inner class constructor with a sufficient number of arguments if (WalaUtils.isAnonymousInnerClass(m.getDeclaringClass()) && m.isInit() && m.getNumberOfParameters() > 2) { logger.trace("[normalizeAnonymousInnerClassConstructor] method: " + m.getSignature()); logger.trace(Utils.INDENT + "descriptor: " + m.getDescriptor() + " # params: " + m.getNumberOfParameters()); String enclosingClazzName = WalaUtils.simpleName(m.getDeclaringClass()); enclosingClazzName = enclosingClazzName.substring(0, enclosingClazzName.lastIndexOf('$')); // check if both argument types are custom types for (int i : new Integer[]{1, 2}) { if (m.getParameterType(i).getClassLoader().equals(ClassLoaderReference.Application)) { IClass ct = m.getClassHierarchy().lookupClass(m.getParameterType(1)); boolean isAppClazz = ct == null || WalaUtils.isAppClass(ct); if (!isAppClazz) return null; } else return null; } IClass superClazz = null; try { IClass ic = WalaUtils.lookupClass(m.getClassHierarchy(), enclosingClazzName); superClazz = ic.getSuperclass(); } catch (ClassNotFoundException e) { // class lookup can also fail for lambdas, e.g. if superclass is kotlin.jvm.internal.Lambda // we then default to fuzzy descriptor logger.trace("Could not lookup " + enclosingClazzName + " in bytecode normalization"); return null; } String argType1 = Utils.convertToFullClassName(m.getParameterType(1).getName().toString()); String argType2 = Utils.convertToFullClassName(m.getParameterType(2).getName().toString()); // now check whether this normalization needs to be applied if (argType1.equals(enclosingClazzName) && argType2.equals(WalaUtils.simpleName(superClazz))) { StringBuilder sb = new StringBuilder("("); // param0 is the object (for non-static calls), param1 the first arg to be skipped (doesn't matter // if whether we skip param1 or param2 since both are replaced by placeholder value) for (int i = 2; i < m.getNumberOfParameters(); i++) { if (m.getParameterType(i).getClassLoader().equals(ClassLoaderReference.Application)) { IClass ct = m.getClassHierarchy().lookupClass(m.getParameterType(i)); boolean isAppClazz = ct == null || WalaUtils.isAppClass(ct); sb.append(isAppClazz ? customTypeReplacement : m.getParameterType(i).getName().toString()); } else sb.append(m.getParameterType(i).getName().toString()); } sb.append(")V"); logger.trace(Utils.INDENT + "> bytecode normalization applied to " + m.getSignature() + " fuzzy desc: " + sb.toString()); return sb.toString(); } } return null; } private static final String customTypeReplacement = "X"; /** * A {@link Descriptor} only describes input arg types + return type, e.g. * The Descriptor of AdVideoView.onError(Landroid/media/MediaPlayer;II)Z is (Landroid/media/MediaPlayerII)Z * In order to produce a fuzzy (robust against identifier-renaming) descriptor we replace each custom type by a fixed * replacement, e.g. we receive a descriptor like (XII)Z * Note: library dependencies, i.e. lib A depends on lib B are not a problem. If we analyze lib A without loading lib B, * any type of lib B will be loaded with the Application classloader but will _not_ be in the classhierarchy. * @param m {@link IMethod} * @return a fuzzy descriptor */ private static String getFuzzyDescriptor(IMethod m) { logger.trace("[getFuzzyDescriptor]"); logger.trace("- signature: " + m.getSignature()); logger.trace("- descriptor: " + m.getDescriptor().toString()); StringBuilder sb = new StringBuilder("("); for (int i = (m.isStatic()? 0 : 1) ; i < m.getNumberOfParameters(); i++) { boolean isAppClazz = false; if (m.getParameterType(i).getClassLoader().equals(ClassLoaderReference.Application)) { IClass ct = m.getClassHierarchy().lookupClass(m.getParameterType(i)); isAppClazz = ct == null || WalaUtils.isAppClass(ct); sb.append(isAppClazz? customTypeReplacement : m.getParameterType(i).getName().toString()); } else sb.append(m.getParameterType(i).getName().toString()); //logger.trace(LogConfig.INDENT + "- param ref: " + m.getParameterType(i).getName().toString() + (isAppClazz? " -> " + customTypeReplacement : "")); } //logger.trace(""); sb.append(")"); if (m.getReturnType().getClassLoader().equals(ClassLoaderReference.Application)) { IClass ct = m.getClassHierarchy().lookupClass(m.getReturnType()); sb.append(ct == null || WalaUtils.isAppClass(ct)? customTypeReplacement : m.getReturnType().getName().toString()); } else sb.append(m.getReturnType().getName().toString()); logger.trace("-> new type: " + sb.toString()); return sb.toString(); } } LibScout-2.3.2/src/de/infsec/tpl/hashtree/comp/pckg/000077500000000000000000000000001342431362400221565ustar00rootroot00000000000000LibScout-2.3.2/src/de/infsec/tpl/hashtree/comp/pckg/DefaultPackageNodeComp.java000066400000000000000000000017311342431362400273100ustar00rootroot00000000000000package de.infsec.tpl.hashtree.comp.pckg; import com.ibm.wala.ipa.cha.IClassHierarchy; import de.infsec.tpl.hashtree.HashTree; import de.infsec.tpl.hashtree.TreeConfig; import de.infsec.tpl.hashtree.node.Node; import de.infsec.tpl.hashtree.node.PackageNode; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.Collection; public class DefaultPackageNodeComp implements IPackageNodeComp { private static final Logger logger = LoggerFactory.getLogger(HashTree.class); @Override public PackageNode comp(Collection classNodes, String packageName, IClassHierarchy cha, TreeConfig config) { // default behaviour, just create hash from child nodes PackageNode pn = new PackageNode(HashTree.compNode(classNodes, false, config.getHasher()).hash, (config.keepPackageNames? packageName : "")); if (!config.pruneClasses) pn.childs = new ArrayList<>(classNodes); return pn; } } LibScout-2.3.2/src/de/infsec/tpl/hashtree/comp/pckg/IPackageNodeComp.java000066400000000000000000000006311342431362400261120ustar00rootroot00000000000000package de.infsec.tpl.hashtree.comp.pckg; import com.ibm.wala.ipa.cha.IClassHierarchy; import de.infsec.tpl.hashtree.TreeConfig; import de.infsec.tpl.hashtree.node.Node; import de.infsec.tpl.hashtree.node.PackageNode; import java.util.Collection; public interface IPackageNodeComp { PackageNode comp(Collection classNodes, String packageName, IClassHierarchy cha, TreeConfig config); } LibScout-2.3.2/src/de/infsec/tpl/hashtree/node/000077500000000000000000000000001342431362400212215ustar00rootroot00000000000000LibScout-2.3.2/src/de/infsec/tpl/hashtree/node/ClassNode.java000066400000000000000000000023561342431362400237450ustar00rootroot00000000000000package de.infsec.tpl.hashtree.node; import java.io.Serializable; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; public class ClassNode extends Node implements Serializable { private static final long serialVersionUID = 7790771073564531337L; public String clazzName; public ClassNode(byte[] hash, String clazzName) { super(hash); this.clazzName = clazzName; } public List getMethodNodes() { return this.childs.stream().map(mn -> (MethodNode) mn).collect(Collectors.toList()); } /* @Override public void debug() { //logger.info("Debug ClassNode: " + clazzName + " (childs: " + childs.size() + ", " + Hash.hash2Human(hash) + ")"); for (Node n: this.childs) { HashTreeOLD.MethodNode mn = (HashTreeOLD.MethodNode) n; logger.info(Utils.INDENT2 + "- " + mn.signature + " :: " + Hash.hash2Str(mn.hash)); } }*/ @Override public boolean equals(Object obj) { if (!(obj instanceof ClassNode)) return false; return Arrays.equals(((Node) obj).hash, this.hash); } @Override public String toString() { return "CNode(" + clazzName + ")"; } } LibScout-2.3.2/src/de/infsec/tpl/hashtree/node/MethodNode.java000066400000000000000000000012271342431362400241140ustar00rootroot00000000000000package de.infsec.tpl.hashtree.node; import java.io.Serializable; import java.util.Arrays; public class MethodNode extends Node implements Serializable { private static final long serialVersionUID = 5590771073564531337L; public String signature; public MethodNode(byte[] hash, String signature) { super(hash); this.signature = signature; } @Override public boolean equals(Object obj) { if (!(obj instanceof MethodNode)) return false; return Arrays.equals(((Node) obj).hash, this.hash); } @Override public String toString() { return "MNode(" + signature + ")"; } } LibScout-2.3.2/src/de/infsec/tpl/hashtree/node/Node.java000066400000000000000000000022121342431362400227460ustar00rootroot00000000000000package de.infsec.tpl.hashtree.node; import de.infsec.tpl.hashtree.HashUtils; import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.TreeSet; public class Node implements Serializable { private static final long serialVersionUID = 6690771073564531337L; public byte[] hash; public List childs; public TreeSet versions; public Node(byte[] hash) { this.hash = hash; this.childs = new ArrayList<>(); } @Override public boolean equals(Object obj) { if (!(obj instanceof Node)) return false; return Arrays.equals(((Node) obj).hash, this.hash); } @Override public int hashCode() { return Arrays.hashCode(hash) + childs.size(); } @Override public String toString() { return HashUtils.hash2Str(this.hash); } public int numberOfChilds() { return this.childs.size(); } public boolean isLeaf() { return childs.isEmpty(); } public boolean isMultiVersionNode() { return versions != null && !versions.isEmpty(); } } LibScout-2.3.2/src/de/infsec/tpl/hashtree/node/PackageNode.java000066400000000000000000000031411342431362400242240ustar00rootroot00000000000000package de.infsec.tpl.hashtree.node; import java.io.Serializable; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.stream.Collectors; public class PackageNode extends Node implements Serializable { private static final long serialVersionUID = 3390771073564531337L; public String packageName; public PackageNode(byte[] hash, String packageName) { super(hash); this.packageName = packageName; } /* @Override public void debug() { logger.info("Debug PackageNode: " + packageName + " (childs: " + childs.size() + ", " + Hash.hash2Str(hash) + ")"); for (Node n: this.childs) { HashTreeOLD.ClassNode cn = (HashTreeOLD.ClassNode) n; logger.info(Utils.INDENT + "- " + cn.clazzName + " :: " + cn.numberOfChilds() + " :: " + Hash.hash2Str(cn.hash)); // cn.debug(); } } */ public List getClassNodes() { return this.childs.stream() .map(mn -> (ClassNode) mn) .collect(Collectors.toList()); } // TODO public List getMethodNodes() { return this.childs.stream() .map(cn -> cn.childs) .flatMap(Collection::stream) .map(mn -> ((MethodNode) mn)) .collect(Collectors.toList()); } @Override public boolean equals(Object obj) { if (!(obj instanceof PackageNode)) return false; return Arrays.equals(((Node) obj).hash, this.hash); } @Override public String toString() { return "PNode(" + packageName + ")"; } } LibScout-2.3.2/src/de/infsec/tpl/manifest/000077500000000000000000000000001342431362400202775ustar00rootroot00000000000000LibScout-2.3.2/src/de/infsec/tpl/manifest/IManifestHandler.java000066400000000000000000000016401342431362400243200ustar00rootroot00000000000000/* * Copyright (c) 2015-2017 Erik Derr [derr@cs.uni-saarland.de] * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this * file except in compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package de.infsec.tpl.manifest; import java.io.InputStream; /** * Interface to work on Manifest files */ public interface IManifestHandler { /** * Process Android manifest files * @param stream Input stream of manifest file */ public void handleManifest(InputStream stream); } LibScout-2.3.2/src/de/infsec/tpl/manifest/ProcessManifest.java000066400000000000000000000263261342431362400242600ustar00rootroot00000000000000/* * Copyright (c) 2015-2017 Erik Derr [derr@cs.uni-saarland.de] * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this * file except in compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package de.infsec.tpl.manifest; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.Serializable; import java.util.Enumeration; import java.util.HashSet; import java.util.Set; import java.util.TreeSet; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; import org.xmlpull.v1.XmlPullParser; import android.content.res.AXmlResourceParser; import pxb.android.axml.AXMLPrinter; public class ProcessManifest implements Serializable { private static final long serialVersionUID = -6763632946511685516L; private static final Logger logger = LoggerFactory.getLogger(de.infsec.tpl.manifest.ProcessManifest.class); // TODO: make a ParsedManifest class with those values private Set entryPointsClasses = new HashSet(); private String packageName = ""; private int versionCode = 0; private int minSdkVersion = 1; // if not explicitly set, defaults to 1 private int targetSdkVersion = 1; // if not explicitly set, defaults to minSdkValue private String sharedUserId = ""; private String applicationName = ""; private Set permissions = new TreeSet(); private Set libDependencies = new HashSet(); public final String MANIFEST_FILENAME = "AndroidManifest.xml"; /** * Opens the given apk file and provides the given handler with a stream for * accessing the contained android manifest file * @param apk The apk file to process * @param handler The handler for processing the apk file * * @author Steven Arzt * @author Erik Derr */ private void handleAndroidManifestFile(String apk, IManifestHandler handler) { File apkF = new File(apk); if (!apkF.exists()) throw new RuntimeException("file '" + apk + "' does not exist!"); boolean found = false; try { ZipFile archive = null; try { archive = new ZipFile(apkF); Enumeration entries = archive.entries(); while (entries.hasMoreElements()) { ZipEntry entry = (ZipEntry) entries.nextElement(); String entryName = entry.getName(); // We are dealing with the Android manifest if (entryName.equals(MANIFEST_FILENAME)) { found = true; handler.handleManifest(archive.getInputStream(entry)); break; } } } finally { if (archive != null) archive.close(); } } catch (Exception e) { throw new RuntimeException("Error when looking for manifest in apk: " + e); } if (!found) throw new RuntimeException("No manifest file found in apk"); } public void loadManifestFile(String apk) { handleAndroidManifestFile(apk, new IManifestHandler() { @Override public void handleManifest(InputStream stream) { loadClassesFromBinaryManifest(stream); } }); } // TODO TODO: parse meta data protected void loadClassesFromBinaryManifest(InputStream manifestIS) { try { AXmlResourceParser parser = new AXmlResourceParser(); parser.open(manifestIS); int type = -1; boolean applicationEnabled = true; while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) { switch (type) { case XmlPullParser.START_DOCUMENT: break; case XmlPullParser.START_TAG: String tagName = parser.getName(); if (tagName.equals("manifest")) { this.packageName = getAttributeValue(parser, "package"); this.sharedUserId = getAttributeValue(parser, "sharedUserId"); try { this.versionCode = Integer.parseInt(getAttributeValue(parser, "versionCode")); } catch (NumberFormatException e) { logger.warn("Could not parse versionCode: " + getAttributeValue(parser, "versionCode")); } // TODO parse shareduser id label if we have a string parser } else if (tagName.equals("activity") || tagName.equals("receiver") || tagName.equals("service") || tagName.equals("provider")) { // We ignore disabled activities if (!applicationEnabled) continue; String attrValue = getAttributeValue(parser, "enabled"); if (attrValue != null && attrValue.equals("false")) continue; // Get the class name attrValue = getAttributeValue(parser, "name"); entryPointsClasses.add(expandClassName(attrValue)); } else if (tagName.equals("uses-permission")) { String permissionName = getAttributeValue(parser, "name"); // We probably don't want to do this in some cases, so leave it // to the user // permissionName = permissionName.substring(permissionName.lastIndexOf(".") + 1); this.permissions.add(permissionName); } else if (tagName.equals("uses-library")) { String libraryName = getAttributeValue(parser, "name"); this.libDependencies.add(libraryName); } else if (tagName.equals("uses-sdk")) { try { this.minSdkVersion = Integer.parseInt(getAttributeValue(parser, "minSdkVersion")); } catch (NumberFormatException e) { logger.warn("Could not parse minSdkVersion: " + getAttributeValue(parser, "minSdkVersion")); } try { this.targetSdkVersion = Integer.parseInt(getAttributeValue(parser, "targetSdkVersion")); } catch (NumberFormatException e) { /* targetSdkValue is optional */ } } else if (tagName.equals("application")) { // Check whether the application is disabled String attrValue = getAttributeValue(parser, "enabled"); applicationEnabled = (attrValue == null || !attrValue.equals("false")); // Get the application name which is also the fully-qualified // name of the custom application object this.applicationName = getAttributeValue(parser, "name"); if (this.applicationName != null && !this.applicationName.isEmpty()) this.entryPointsClasses.add(expandClassName(this.applicationName)); } break; case XmlPullParser.END_TAG: break; case XmlPullParser.TEXT: break; } } } catch (Exception e) { e.printStackTrace(); } } /** * Generates a full class name from a short class name by appending the * globally-defined package when necessary * @param className The class name to expand * @return The expanded class name for the given short name */ private String expandClassName(String className) { if (className.startsWith(".")) { return this.packageName + className; } else if (!className.contains(".")) { // if only the classname is present without leading dot, Android's manifest parser safely expands the class name as if there was a leading dot return this.packageName + "." + className; } else { return className; } } private String getAttributeValue(AXmlResourceParser parser, String attributeName) { for (int i = 0; i < parser.getAttributeCount(); i++) if (parser.getAttributeName(i).equals(attributeName)) return AXMLPrinter.getAttributeValue(parser, i); return ""; } protected void loadClassesFromTextManifest(InputStream manifestIS) { try { DocumentBuilder db = DocumentBuilderFactory.newInstance().newDocumentBuilder(); Document doc = db.parse(manifestIS); Element rootElement = doc.getDocumentElement(); this.packageName = rootElement.getAttribute("package"); NodeList appsElement = rootElement.getElementsByTagName("application"); if (appsElement.getLength() > 1) throw new RuntimeException("More than one application tag in manifest"); for (int appIdx = 0; appIdx < appsElement.getLength(); appIdx++) { Element appElement = (Element) appsElement.item(appIdx); this.applicationName = appElement.getAttribute("android:name"); if (this.applicationName != null && !this.applicationName.isEmpty()) this.entryPointsClasses.add(expandClassName(this.applicationName)); NodeList activities = appElement.getElementsByTagName("activity"); NodeList receivers = appElement.getElementsByTagName("receiver"); NodeList services = appElement.getElementsByTagName("service"); for (int i = 0; i < activities.getLength(); i++) { Element activity = (Element) activities.item(i); loadManifestEntry(activity, "android.app.Activity", this.packageName); } for (int i = 0; i < receivers.getLength(); i++) { Element receiver = (Element) receivers.item(i); loadManifestEntry(receiver, "android.content.BroadcastReceiver", this.packageName); } for (int i = 0; i < services.getLength(); i++) { Element service = (Element) services.item(i); loadManifestEntry(service, "android.app.Service", this.packageName); } NodeList permissions = appElement.getElementsByTagName("uses-permission"); for (int i = 0; i < permissions.getLength(); i++) { Element permission = (Element) permissions.item(i); this.permissions.add(permission.getAttribute("android:name")); } } } catch (IOException ex) { logger.error("Could not parse manifest: " + ex.getMessage()); ex.printStackTrace(); } catch (ParserConfigurationException ex) { logger.error("Could not parse manifest: " + ex.getMessage()); ex.printStackTrace(); } catch (SAXException ex) { logger.error("Could not parse manifest: " + ex.getMessage()); ex.printStackTrace(); } } private void loadManifestEntry(Element activity, String baseClass, String packageName) { if (activity.getAttribute("android:enabled").equals("false")) return; String className = activity.getAttribute("android:name"); entryPointsClasses.add(expandClassName(className)); } public Set getEntryPointClasses() { return this.entryPointsClasses; } public String getApplicationName() { return this.applicationName; } public Set getPermissions() { return this.permissions; } public String getPackageName() { return this.packageName; } public int getVersionCode() { return this.versionCode; } public String getSharedUserId() { return this.sharedUserId; } public Set getLibraryDependencies() { return this.libDependencies; } public int getMinSdkVersion() { return this.minSdkVersion; } public int getTargetSdkVersion() { return this.targetSdkVersion > 1? this.targetSdkVersion : this.minSdkVersion; } } LibScout-2.3.2/src/de/infsec/tpl/modules/000077500000000000000000000000001342431362400201415ustar00rootroot00000000000000LibScout-2.3.2/src/de/infsec/tpl/modules/libapi/000077500000000000000000000000001342431362400214015ustar00rootroot00000000000000LibScout-2.3.2/src/de/infsec/tpl/modules/libapi/DependencyAnalysis.java000066400000000000000000000207431342431362400260340ustar00rootroot00000000000000package de.infsec.tpl.modules.libapi; import com.github.zafarkhaja.semver.Version; import com.ibm.wala.classLoader.CallSiteReference; import com.ibm.wala.classLoader.IClass; import com.ibm.wala.classLoader.IMethod; import com.ibm.wala.shrikeCT.InvalidClassFileException; import de.infsec.tpl.pkg.PackageTree; import de.infsec.tpl.pkg.PackageUtils; import de.infsec.tpl.stats.Exportable; import de.infsec.tpl.utils.Utils; import de.infsec.tpl.utils.WalaUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.*; import java.util.stream.Collectors; /** * Feature: Tests for nested/secondary library dependencies, i.e. libraries depending on other libraries. * The analysis is performed for every documented API of every lib version via reachability analysis */ public class DependencyAnalysis { private static final Logger logger = LoggerFactory.getLogger(DependencyAnalysis.class); /** * Secondary lib dependencies for every version and documented API */ class LibDependencies implements Exportable { Version version; // documented APIs to set of APIs from dependencies Map> api2Dependencies; LibDependencies(Version version, Map> api2Dependencies) { this.version = version; this.api2Dependencies = api2Dependencies; } @Override public DependencyAnalysis.LibDependencies.Export export() { return new Export(this); } public class Export { String version; Map> api2Dependencies = new HashMap>(); public Export(LibDependencies deps) { this.version = deps.version.toString(); for (IMethod m: deps.api2Dependencies.keySet()) this.api2Dependencies.put(m.getSignature(), deps.api2Dependencies.get(m).stream().map(c -> c.getDeclaredTarget().getSignature()).collect(Collectors.toSet())); } } } // need to infer expected and actual semver protected Map run(LibApiStats stats) { Map version2Deps = new TreeMap(); for (Version version: stats.getVersions()) { logger.info(Utils.INDENT + "- version: " + version); Map> api2Dependencies = analyzeDependencies(stats.getDocumentedAPIs(version)); version2Deps.put(version, new LibDependencies(version, api2Dependencies)); } // debug collect all dependencies (APIs) per lib (incl. all versions) Set signatures = new TreeSet(); version2Deps.values(). forEach(ld -> { ld.api2Dependencies.values() .forEach(set -> { set.forEach(csf -> signatures.add(PackageUtils.getPackageName(csf.getDeclaredTarget().getSignature()))); }) ;} ); logger.info(Utils.INDENT + "-> Dependencies of " + stats.libName); signatures.forEach(s -> logger.info(Utils.INDENT2 + "- dep: " + s)); PackageTree pt = PackageTree.make(signatures); pt.print(true); /// TODO show empty packages + non-empty on depth == 1 return version2Deps; } private Map> analyzeDependencies(Set pubApis) { HashMap> secDeps = new HashMap>(); // perform method reachability analysis for every pubAPI for (IMethod docApi: pubApis) { logger.debug("-> check API: " + docApi.getSignature()); LinkedList queue = new LinkedList(); queue.push(docApi); // types to methods signatures in which they are used Set unresolvedCalls = new HashSet(); Set visited = new HashSet(); // Check method invocations try { while (!queue.isEmpty()) { IMethod m = queue.poll(); if (!visited.add(m.getSignature())) continue; for (CallSiteReference csf : com.ibm.wala.classLoader.CodeScanner.getCallSites(m)) { IClass c = m.getClassHierarchy().lookupClass(csf.getDeclaredTarget().getDeclaringClass()); IMethod inv = m.getClassHierarchy().resolveMethod(csf.getDeclaredTarget()); if (inv == null) { // inherited final methods can not be looked up or (abstract) interface methods inherited from another interface // workaround, we check if the class is part of the CHA if (c == null) { logger.trace(" ## unresolved call: " + csf.getDeclaredTarget().getSignature() + (csf.isInterface() ? " [Interface]" : "")); unresolvedCalls.add(csf); } } else { if (c != null && WalaUtils.isAppClass(c)) queue.push(inv); } } } } catch (InvalidClassFileException e) { logger.error(Utils.stacktrace2Str(e)); } if (!unresolvedCalls.isEmpty()) { secDeps.put(docApi, unresolvedCalls); } } // Results secDeps.keySet() .forEach(m -> { logger.debug("- Method: " + m.getSignature()); secDeps.get(m).stream() .map(c -> c.getDeclaredTarget().getSignature()) .sorted().distinct() .forEach(s -> logger.debug(" - dep: " + s)); }); logger.info(Utils.INDENT2 + "- " + secDeps.size() + "/" + pubApis.size() + " APIs with secondary dependencies"); return secDeps; } /* private void printSTATS(){ logger.info("# Processed libs: " + libName2Stats.size()); logger.info(" - w/o secondary dependencies: " + libName2Stats.values().stream().filter(s -> s.version2Dependencies.isEmpty()).count()); libName2Stats.values().stream().filter(s -> s.version2Dependencies.isEmpty()).forEach(st -> logger.error(Utils.INDENT + "- lib: " + st.libName)); Set rootPackages = new TreeSet(); libName2Stats.values().stream().map(s -> s.rootPackages).forEach(rootPackages::addAll); // rootPackages.forEach(rp -> logger.error("-- root pckg: " + rp)); logger.info(" - with secondary dependencies: " + libName2Stats.values().stream().filter(s -> s.version2Dependencies.size() > 0).count()); libName2Stats.values().forEach(s -> { s.version2Dependencies.keySet().forEach(v -> { Set prunedDeps = new TreeSet(); for (String dep: s.version2Dependencies.get(v)) { boolean added = false; for (String rp: rootPackages) { if (dep.startsWith(rp)) { added = true; prunedDeps.add(rp); break; } } if (!added) prunedDeps.add(dep); }; s.version2Dependencies.put(v, prunedDeps); }); }); for (LibApiStats stats: libName2Stats.values()) { if (!stats.version2Dependencies.isEmpty()) { logger.info(Utils.INDENT + "- lib: " + stats.libName); // per lib package dep Set perLibDep = new TreeSet(); stats.version2Dependencies.values().forEach(perLibDep::addAll); perLibDep.forEach(dep -> logger.info(Utils.INDENT2 + "- dep: " + dep)); */ // per version package dep /*for (String v: stats.version2Dependencies.keySet().stream().sorted().collect(Collectors.toList())) { logger.info(" - version: " + v); for (String dep: stats.version2Dependencies.get(v)) logger.info(" -- dep: " + dep); }*/ /* } } }*/ } LibScout-2.3.2/src/de/infsec/tpl/modules/libapi/JvmMethodAccessFlags.java000066400000000000000000000105021342431362400262360ustar00rootroot00000000000000package de.infsec.tpl.modules.libapi; import com.ibm.wala.classLoader.IMethod; import de.infsec.tpl.utils.Utils; import java.util.ArrayList; import java.util.HashMap; import java.util.List; /** * Spec taken from https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html */ public enum JvmMethodAccessFlags { NO_FLAG (0x0000, "no-flag"), PUBLIC (0x0001, "public"), PRIVATE (0x0002, "private"), PROTECTED (0x0004, "protected"), STATIC (0x0008, "static"), FINAL (0x0010, "final"), ABSTRACT (0x0400, "abstract"), PACKAGE_PROTECTED (0x2000, "package-proteced"); private int value; private String accessFlagName; //cache the array of all AccessFlags, because .values() allocates a new array for every call private final static JvmMethodAccessFlags[] allFlags; private final static List validFlagValues; private static HashMap accessFlagsByName; static { allFlags = JvmMethodAccessFlags.values(); validFlagValues = new ArrayList(); for (JvmMethodAccessFlags flag: allFlags) validFlagValues.add(flag.getValue()); accessFlagsByName = new HashMap(); for (JvmMethodAccessFlags accessFlag: allFlags) { accessFlagsByName.put(accessFlag.accessFlagName, accessFlag); } } private JvmMethodAccessFlags(int value, String accessFlagName) { this.value = value; this.accessFlagName = accessFlagName; } private static String flags2Str(JvmMethodAccessFlags[] accessFlags) { int size = 0; for (JvmMethodAccessFlags accessFlag: accessFlags) { size += accessFlag.toString().length() + 1; } StringBuilder sb = new StringBuilder(size); for (JvmMethodAccessFlags accessFlag: accessFlags) { sb.append(accessFlag.toString()); sb.append(" "); } if (accessFlags.length > 0) { sb.delete(sb.length() - 1, sb.length()); } return sb.toString(); } public static boolean isValidFlag(int code) { return validFlagValues.contains(code); } public static String flags2Str(int code) { List matchedFlags = new ArrayList(); for (JvmMethodAccessFlags flag: allFlags) { if ((code & flag.value) != 0x0) { matchedFlags.add(flag.accessFlagName + "(" + flag.value + ")"); } } return Utils.join(matchedFlags, ","); } public static String flags2Str(IMethod m) { return flags2Str(getMethodAccessCode(m)); } public static JvmMethodAccessFlags getAccessFlag(String accessFlag) { return accessFlagsByName.get(accessFlag); } public int getValue() { return value; } public String toString() { return accessFlagName; } public static int getAccessFlagFilter(JvmMethodAccessFlags... flags) { int filter = NO_FLAG.getValue(); if (flags != null) { for (JvmMethodAccessFlags flag: flags) { if (!JvmMethodAccessFlags.isValidFlag(flag.getValue())) continue; filter |= flag.getValue(); } } return filter; } public static int getPublicOnlyFilter() { return getAccessFlagFilter(JvmMethodAccessFlags.PRIVATE, JvmMethodAccessFlags.PROTECTED, JvmMethodAccessFlags.STATIC, JvmMethodAccessFlags.FINAL, JvmMethodAccessFlags.ABSTRACT, JvmMethodAccessFlags.PACKAGE_PROTECTED); } public static int getMethodAccessCode(IMethod m) { int res = 0x0; if (m == null) return res; if (m.isPublic()) { res |= JvmMethodAccessFlags.PUBLIC.getValue(); } else if (m.isProtected()) { res |= JvmMethodAccessFlags.PROTECTED.getValue(); } else if (m.isPrivate()) { res |= JvmMethodAccessFlags.PRIVATE.getValue(); } else { res |= JvmMethodAccessFlags.PACKAGE_PROTECTED.getValue(); } if (m.isStatic()) res |= JvmMethodAccessFlags.STATIC.getValue(); if (m.isFinal()) res |= JvmMethodAccessFlags.FINAL.getValue(); if (m.isAbstract()) res |= JvmMethodAccessFlags.ABSTRACT.getValue(); return res; } } LibScout-2.3.2/src/de/infsec/tpl/modules/libapi/LibApiComparator.java000066400000000000000000000427021342431362400254410ustar00rootroot00000000000000package de.infsec.tpl.modules.libapi; import com.github.zafarkhaja.semver.Version; import com.ibm.wala.classLoader.IClass; import com.ibm.wala.classLoader.IMethod; import de.infsec.tpl.stats.Exportable; import de.infsec.tpl.utils.Utils; import de.infsec.tpl.utils.VersionWrapper; import de.infsec.tpl.utils.WalaUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.*; import java.util.stream.Collectors; /** * Feature: Tests the relationship/compatibility of documented API sets of consecutive versions */ public class LibApiComparator { private static final Logger logger = LoggerFactory.getLogger(LibApiComparator.class); private Map version2ApiDiff; /** * API Diff Statistics compared to predecessor version */ class ApiDiff implements Exportable { Version v; int apiCount; VersionWrapper.SEMVER expectedSemver = null; VersionWrapper.SEMVER actualSemver = null; Set removed = new HashSet(); Set added = new HashSet(); Map> alternatives = new HashMap>(); ApiDiff(Version v, int apiCount) { this.v = v; this.apiCount = apiCount; } public void print(boolean verbose) { boolean diff = false; boolean critical = false; if (expectedSemver != null) { diff = !expectedSemver.equals(actualSemver); critical = (expectedSemver.equals(VersionWrapper.SEMVER.PATCH) || expectedSemver.equals(VersionWrapper.SEMVER.MINOR)) && actualSemver.equals(VersionWrapper.SEMVER.MAJOR); } // version, #docAPI, apiCountDiff expSemver, actSemver, diff?, critical? logger.info(String.format("%10s %10d %-15s %8s %8s %10s %15s", v.toString(), apiCount, added.size() > 0 || removed.size() > 0? " (+" + added.size() + "/-" + removed.size() + ")" : "", expectedSemver != null? expectedSemver.toString() : "---", actualSemver != null? actualSemver.toString() : "---", diff? "[DIFF]" : "", critical? "[CRITICAL]" : "")); if (verbose) { final int LIMIT = 15; // print max number of methods removed.stream().map(IMethod::getSignature).sorted().limit(LIMIT).forEach(m -> logger.info(Utils.INDENT2 + "- removed: " + m)); for (IMethod m: alternatives.keySet()) { logger.info(Utils.INDENT2 + "Alternatives for " + m.getSignature()); alternatives.get(m).stream().map(IMethod::getSignature).sorted().forEach(s -> logger.info(Utils.indent(3) + "> alt: " + s)); } added.stream().map(IMethod::getSignature).sorted().limit(LIMIT).forEach(m -> logger.info(Utils.INDENT2 + "+ added: " + m)); } } @Override public Export export() { return new Export(this); } public class Export { String version; int apiCount; int apiAdditionsCount; int apiDeletionsCount; Set apiAdditions = new TreeSet(); Set apiDeletions = new TreeSet(); String expectedSemver; String actualSemver; Map> alternatives = new TreeMap>(); public Export(ApiDiff diff, boolean verbose) { version = diff.v.toString(); apiCount = diff.apiCount; apiAdditionsCount = diff.added.size(); apiDeletionsCount = diff.removed.size(); actualSemver = diff.actualSemver == null? "" : diff.actualSemver.name(); expectedSemver = diff.expectedSemver == null? "" : diff.expectedSemver.name(); for (IMethod m: diff.alternatives.keySet()) { Set apis = diff.alternatives.get(m).stream().map(IMethod::getSignature).collect(Collectors.toSet()); alternatives.put(m.getSignature(), apis); } if (verbose) { apiAdditions = diff.added.stream().map(IMethod::getSignature).sorted().collect(Collectors.toSet()); apiDeletions = diff.removed.stream().map(IMethod::getSignature).sorted().collect(Collectors.toSet()); } } public Export(ApiDiff diff) { this(diff, false); } } } // need to infer expected and actual semver protected Map run(LibApiStats stats) { version2ApiDiff = new TreeMap(); Map> version2Api = generatePerVersionApiSet(stats); for (Version v: version2Api.keySet()) { version2ApiDiff.put(v, new ApiDiff(v, version2Api.get(v).size())); } // infer expected/actual semver inferExpectedSemver(); inferActualSemver(version2Api); inferAlternativeAPIs(version2Api); // if (logger.isDebugEnabled()) { logger.info("======================================"); logger.info("== Library: " + stats.libName + " =="); logger.info("======================================"); printStats(); // } return version2ApiDiff; } private void printStats() { logger.info(String.format("%10s %10s %-15s %10s %10s %10s %15s", "Version", "#docAPI", " apiDiff", "expSemver", "actSemver", " diff? ", " critical? ")); logger.info("---------------------------------------------------------------------------------------------"); for (Version v: version2ApiDiff.keySet()) { version2ApiDiff.get(v).print(false);//true);//false); } logger.info("---------------------------------------------------------------------------------------------"); } void inferExpectedSemver() { Iterator it = version2ApiDiff.keySet().iterator(); Version old = it.next(); while (it.hasNext()) { Version cur = it.next(); VersionWrapper.SEMVER sem = VersionWrapper.getExpectedSemver(old,cur); version2ApiDiff.get(cur).expectedSemver = sem; logger.debug(Utils.INDENT2 + "Expected SemVer:: " + old.toString() + " : " + cur.toString() + " -> " + sem.name()); old = cur; } } Map> generatePerVersionApiSet(LibApiStats stats) { Map> version2Api = new TreeMap>(); for (Version v: stats.getVersions()) { Set apis = new HashSet(); for (IMethod api: stats.api2Versions.keySet()) { if (stats.api2Versions.get(api).contains(v)) apis.add(api); } version2Api.put(v, apis); } return version2Api; } void inferActualSemver(Map> version2Api) { Iterator it = version2ApiDiff.keySet().iterator(); Version v0 = it.next(); while (it.hasNext()) { Version v1 = it.next(); VersionWrapper.SEMVER sem = compareApis(version2Api.get(v0), version2Api.get(v1)); version2ApiDiff.get(v1).actualSemver = sem; // determine added/removed APIs if (!sem.equals(VersionWrapper.SEMVER.PATCH)) { Set removed = new HashSet(version2Api.get(v0)); removed.removeAll(version2Api.get(v1)); version2ApiDiff.get(v1).removed = removed; Set added = new HashSet(version2Api.get(v1)); added.removeAll(version2Api.get(v0)); version2ApiDiff.get(v1).added = added; } logger.debug(Utils.INDENT2 + "Actual SemVer:: " + v0.toString() + " : " + v1.toString() + " -> " + sem.name()); v0 = v1; } } /* * Check for each major version (actual semver) for alternative * APIs for each removed API * NOTE: This is getting really slow if there are 10k+ doc APIs and more than * 1k+ API removals between adjacent versions */ void inferAlternativeAPIs(Map> version2Api) { for (Version v: version2ApiDiff.keySet()) { ApiDiff diff = version2ApiDiff.get(v); if (diff.actualSemver != null && diff.actualSemver.equals(VersionWrapper.SEMVER.MAJOR)) { for (IMethod m: diff.removed) { Set alternatives = version2Api.get(v).parallelStream() .filter(api -> !diff.removed.contains(api)) // exclude removed apis .filter(api -> isAlternativeApi(m, api)) .collect(Collectors.toSet()); // if we have three or more alternatives (e.g. renamed methods with one argument) // the suggestions will probably be wrong -> do not store any alternatives if (alternatives.size() <= 2) diff.alternatives.put(m, alternatives); } } } } /** * Check for alternative APIs in case an API is no longer available in new library version * Tests include * 1. whether only the method name was renamed (same descriptor) * 2. whether method name/return type are the same but one or more argument types have been generalized * (e.g. ArrayList to List|Collection) * 3. Same method one new argument was prepended/appended * 4. Same method same arguments, different return type (e.g. String -> String[]) * @param target * @param test * @return */ protected static boolean isAlternativeApi(IMethod target, IMethod test) { // Test2 if (isApiCompatible(target, test)) return true; // check whether both APIs reside in the same code Package/Class if (! WalaUtils.simpleName(target.getDeclaringClass()).equals(WalaUtils.simpleName(test.getDeclaringClass()))) return false; // check for changes in access specifier if (JvmMethodAccessFlags.getMethodAccessCode(target) != JvmMethodAccessFlags.getMethodAccessCode(test)) { logger.trace("Access Flags incompatible: old: " + JvmMethodAccessFlags.flags2Str(target) + " new: " + JvmMethodAccessFlags.flags2Str(test)); return false; } // Test1: check whether method was renamed (with same descriptor) // Since this is very fuzzy, we further require // - constructors can't be an alternative to non-constructors // - at least one argument (still fuzzy for methods with one primitive/String arg) // TODO: at least one non-framework arg || at least two prim/framework args if (! WalaUtils.getName(target).equals(WalaUtils.getName(test))) { int numberOfArgs = target.getNumberOfParameters() - (target.isStatic()? 0 : 1); return target.getDescriptor().toString().equals(test.getDescriptor().toString()) && numberOfArgs > 0 && (!WalaUtils.getName(target).equals("")) && (!WalaUtils.getName(test).equals("")); } // Test3: introduction of new argument at first/last position if (WalaUtils.getName(target).equals(WalaUtils.getName(test)) && // same method name (target.getReturnType().toString().equals(test.getReturnType().toString())) && // same return type (target.getNumberOfParameters() == test.getNumberOfParameters()-1)) { // one more arg // check if new arg was prepended boolean check = true; for (int i = (target.isStatic()? 0 : 1); i < target.getNumberOfParameters(); i++) { if (!target.getParameterType(i).getName().toString().equals(test.getParameterType(i+1).getName().toString())) { check = false; break; } } if (check) return true; // prepended // check if new arg was appended check = true; for (int i = (target.isStatic()? 0 : 1); i < target.getNumberOfParameters(); i++) { if (!target.getParameterType(i).getName().toString().equals(test.getParameterType(i).getName().toString())) { check = false; break; } } if (check) return true; // appended } // Test4: same arg list: different return type if (WalaUtils.getName(target).equals(WalaUtils.getName(test)) && // same method name (!target.getReturnType().toString().equals(test.getReturnType().toString())) && // different return type (target.getNumberOfParameters() == test.getNumberOfParameters())) { // same number of args // check that all arg types are equal boolean equal = true; for (int i = (target.isStatic()? 0 : 1); i < target.getNumberOfParameters(); i++) { if (!target.getParameterType(i).getName().toString().equals(test.getParameterType(i).getName().toString())) { equal = false; break; } } if (equal) return true; } return false; } /** * Check whether two API are compatible, i.e. no code changes (calls to APIs) have to be made by app developers * This checks whether method name/return type are the same but one or more argument types have been generalized * (e.g. ArrayList to List|Collection */ protected static boolean isApiCompatible(IMethod target, IMethod test) { // check whether both APIs reside in the same code Package/Class if (! WalaUtils.simpleName(target.getDeclaringClass()).equals(WalaUtils.simpleName(test.getDeclaringClass()))) return false; // check for changes in access specifier if (JvmMethodAccessFlags.getMethodAccessCode(target) != JvmMethodAccessFlags.getMethodAccessCode(test)) { logger.trace("Access Flags incompatible: old: " + JvmMethodAccessFlags.flags2Str(target) + " new: " + JvmMethodAccessFlags.flags2Str(test)); return false; } // check whether method name changed if (! WalaUtils.getName(target).equals(WalaUtils.getName(test))) { return false; } // if method name changed, check whether non primitive arguments were generified // - first check wether return types are the same if (! target.getReturnType().toString().equals(test.getReturnType().toString())) return false; // - check argument types for generalization, i.e. when a ArrayList argument was changed to List if (target.getNumberOfParameters() == test.getNumberOfParameters()) { for (int i = (target.isStatic()? 0 : 1); i < target.getNumberOfParameters(); i++) { if (!target.getParameterType(i).toString().equals(test.getParameterType(i).toString())) { // skip primitive types if (test.getParameterType(i).isPrimitiveType()) return false; // check if test argument type is supertype IClass paramTestClazz = test.getDeclaringClass().getClassHierarchy().lookupClass(test.getParameterType(i)); // could be null because it's a type of a different library (sub-dependency) that is not part of the cha if (paramTestClazz == null) { logger.warn("Could not lookup superclazz (maybe sub-dependency):"); logger.warn(Utils.INDENT + "target param type: " + target.getParameterType(i).getName().toString()); logger.warn(Utils.INDENT + "test param type : " + test.getParameterType(i).getName().toString()); return false; } List superClazzes = WalaUtils.getSuperClasses(paramTestClazz); boolean found = false; for (IClass ic: superClazzes) { if (WalaUtils.simpleName(target.getDeclaringClass()).equals(WalaUtils.simpleName(ic))) { found = true; break; } } if (!found) return false; // incompatible type replacement in test API } } return true; // all arguments are the same } return false; } // IMethod test as IMethod objects stem from different IClassHierarchies protected static boolean equals(IMethod m1, IMethod m2) { return m1.getSignature().equals(m2.getSignature()) && JvmMethodAccessFlags.getMethodAccessCode(m1) == JvmMethodAccessFlags.getMethodAccessCode(m2); } protected static VersionWrapper.SEMVER compareApis(Set s0, Set s1) { if (s1.containsAll(s0)) { if (s0.size() == s1.size()) // exact match return VersionWrapper.SEMVER.PATCH; else if (s1.size() > s0.size()) // contains complete set of APIs and additions -> backwards-compatible return VersionWrapper.SEMVER.MINOR; } return VersionWrapper.SEMVER.MAJOR; } } LibScout-2.3.2/src/de/infsec/tpl/modules/libapi/LibApiStats.java000066400000000000000000000055711342431362400244330ustar00rootroot00000000000000package de.infsec.tpl.modules.libapi; import com.github.zafarkhaja.semver.Version; import com.ibm.wala.classLoader.IMethod; import de.infsec.tpl.config.LibScoutConfig; import de.infsec.tpl.stats.Exportable; import de.infsec.tpl.utils.VersionWrapper; import java.util.*; import java.util.stream.Collectors; /** * Container class to store library API changes across versions */ public class LibApiStats implements Exportable { public String libName; // set of version strings private Set versions; // maps documented API signatures to list of library versions including them public Map> api2Versions; public Map version2Diff; public Map version2Deps; public class Export { public String libName; public List versions; public List apiDiffs; public List libDeps; // maps documented API signatures to list of library versions including them public Map> api2Versions; public Export(LibApiStats stats) { this.libName = stats.libName; this.versions = stats.versions.stream().map(Version::toString).collect(Collectors.toList()); this.apiDiffs = stats.version2Diff.values().stream().map(LibApiComparator.ApiDiff::export).collect(Collectors.toList()); if (LibScoutConfig.libDependencyAnalysis) this.libDeps = stats.version2Deps.values().stream().map(DependencyAnalysis.LibDependencies::export).collect(Collectors.toList()); this.api2Versions = new HashMap<>(); for (IMethod m: stats.api2Versions.keySet()) { this.api2Versions.put(m.getSignature(), stats.api2Versions.get(m).stream().map(Version::toString).collect(Collectors.toList())); } } } @Override public Export export() { return new Export(this); } public LibApiStats(String libName) { this.libName = libName; this.api2Versions = new HashMap<>(); this.versions = new TreeSet<>(); } /* * Update functions */ public void addVersion(String version) { versions.add(VersionWrapper.valueOf(version)); } public Set getVersions() { return versions; } void setDocumentedAPIs(String version, Set docAPIs) { for (IMethod api: docAPIs) { if (!api2Versions.containsKey(api)) api2Versions.put(api, new TreeSet()); api2Versions.get(api).add(VersionWrapper.valueOf(version)); } } Set getDocumentedAPIs(Version version) { return api2Versions.keySet().stream().filter(m -> api2Versions.get(m).contains(version)).collect(Collectors.toSet()); } } LibScout-2.3.2/src/de/infsec/tpl/modules/libapi/LibraryApiAnalysis.java000066400000000000000000000145231342431362400260130ustar00rootroot00000000000000package de.infsec.tpl.modules.libapi; import com.ibm.wala.classLoader.IMethod; import com.ibm.wala.ipa.callgraph.AnalysisScope; import com.ibm.wala.ipa.cha.ClassHierarchy; import com.ibm.wala.ipa.cha.ClassHierarchyException; import com.ibm.wala.ipa.cha.ClassHierarchyFactory; import com.ibm.wala.ipa.cha.IClassHierarchy; import com.ibm.wala.types.ClassLoaderReference; import de.infsec.tpl.TplCLI; import de.infsec.tpl.config.LibScoutConfig; import de.infsec.tpl.profile.LibraryDescription; import de.infsec.tpl.utils.AarFile; import de.infsec.tpl.utils.Utils; import de.infsec.tpl.xml.XMLParser; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; import java.io.IOException; import java.util.*; import java.util.jar.JarFile; /** * = Library API Analysis = * * - Extracts the public (documented) API from original library code packages * - Checks API stability across versions for the same library (expected/actual semantic versioning) * - Searches for API alternatives in case of removed APIs * - Results of type {@link LibApiStats.Export} are written to {@link TplCLI.CliArgs#ARG_JSON_DIR} */ public class LibraryApiAnalysis { private static final Logger logger = LoggerFactory.getLogger(LibraryApiAnalysis.class); // Mapping from -> private Map meta2Code = new HashMap(); private Map libName2Stats = new HashMap(); public static void run(File libDir) { new LibraryApiAnalysis(libDir); } private LibraryApiAnalysis(File libDir) { locateLibrarySDKs(libDir); parseLibrarySDKs(true); if (LibScoutConfig.libDependencyAnalysis) analyzeSecondaryDependencies(); analyzeLibraryAPIs(); // write stats to disk libName2Stats.values().forEach(s -> writeLibData(s)); } private void analyzeLibraryAPIs() { LibApiComparator comp = new LibApiComparator(); for (LibApiStats lib: libName2Stats.values()) { logger.info("- Analyze lib APIs: " + lib.libName); lib.version2Diff = comp.run(lib); } } private void analyzeSecondaryDependencies() { DependencyAnalysis dp = new DependencyAnalysis(); for (LibApiStats lib: libName2Stats.values()) { logger.info("- Analyze secondary deps: " + lib.libName); lib.version2Deps = dp.run(lib); } } private void writeLibData(LibApiStats stats) { // output results in json format File jsonOutputFile = new File(LibScoutConfig.jsonDir + File.separator + "libApis" + File.separator + stats.libName + ".json"); try { Utils.obj2JsonFile(jsonOutputFile, stats.export()); logger.info("Results for library: " + stats.libName + " written to " + jsonOutputFile); } catch (IOException e) { logger.warn("Could not write json results: " + Utils.stacktrace2Str(e)); } } private void parseLibrarySDKs(boolean skipBeta) { for (File libXML : meta2Code.keySet()) { try { LibraryDescription ld = XMLParser.readLibraryXML(libXML); if (ld.version.matches(".*[a-zA-Z-]+.*") && skipBeta) { // skip alpha/beta/rc .. logger.info("Skip lib: " + ld.name + " version: " + ld.version); continue; } logger.info("- Parse lib: " + ld.name + " version: " + ld.version); // if stats file not existing add new one if (!libName2Stats.containsKey(ld.name)) libName2Stats.put(ld.name, new LibApiStats(ld.name)); libName2Stats.get(ld.name).addVersion(ld.version); // extract public documented API IClassHierarchy cha = createClassHierarchy(meta2Code.get(libXML)); Set docAPIs = PublicInterfaceExtractor.getDocumentedPublicInterface(cha); libName2Stats.get(ld.name).setDocumentedAPIs(ld.version, docAPIs); logger.info(Utils.INDENT + "- " + docAPIs.size() + " documented public APIs"); } catch (Exception e) { logger.warn(Utils.stacktrace2Str(e)); } } } private IClassHierarchy createClassHierarchy(File libCodeFile) throws ClassHierarchyException, IOException, ClassNotFoundException { // create analysis scope and generate class hierarchy final AnalysisScope scope = AnalysisScope.createJavaAnalysisScope(); JarFile jf = libCodeFile.getName().endsWith(".aar")? new AarFile(libCodeFile).getJarFile() : new JarFile((libCodeFile)); scope.addToScope(ClassLoaderReference.Application, jf); scope.addToScope(ClassLoaderReference.Primordial, new JarFile(LibScoutConfig.pathToAndroidJar)); IClassHierarchy cha = ClassHierarchyFactory.makeWithRoot(scope); // cleanup tmp files if library input was an .aar file if (libCodeFile.getName().endsWith(".aar")) { File tmpJar = new File(jf.getName()); tmpJar.delete(); logger.trace(Utils.indent() + "tmp jar-file deleted at " + tmpJar.getName()); } return cha; } /** * Find library descriptor files and their associated code package on disk (recursively) * @param basePath */ private void locateLibrarySDKs(File basePath) { long s = System.currentTimeMillis(); // recursively search for library.xml file and their associated code packages (.jar|.aar) within the same directory try { for (File f : Utils.collectFiles(basePath, new String[]{"xml"})) { if (f.getName().equals("library.xml")) { logger.trace("Found lib.xml: " + f.getName() + " parent: " + f.getParent() ); // grep the associated code package (TODO: prefer aar|jar?) for (File j : Utils.collectFiles(f.getParentFile(), new String[]{"jar", "aar"})) { logger.trace(" -> Found code pckg: " + j); meta2Code.put(f,j); break; } } else logger.trace("Found something different: " + f); } } catch (Exception e) { logger.warn(Utils.stacktrace2Str(e)); } } } LibScout-2.3.2/src/de/infsec/tpl/modules/libapi/PublicInterfaceExtractor.java000066400000000000000000000161651342431362400272100ustar00rootroot00000000000000package de.infsec.tpl.modules.libapi; import com.ibm.wala.classLoader.IClass; import com.ibm.wala.classLoader.IMethod; import com.ibm.wala.ipa.cha.IClassHierarchy; import de.infsec.tpl.hash.AccessFlags; import de.infsec.tpl.pkg.PackageUtils; import de.infsec.tpl.utils.Utils; import de.infsec.tpl.utils.WalaUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.*; import java.util.stream.Collectors; /** * Extracts the public API and documented public API (to be used by client) from a library.jar * Filters ProGuard obfuscated methods/classes (identifier renaming) * * Related info: * - http://wiki.eclipse.org/Evolving_Java-based_APIs * -> javadoc parsing for pre-/post-condition changes in same APIs (aka semantic changes) * */ public class PublicInterfaceExtractor { private static final Logger logger = LoggerFactory.getLogger(de.infsec.tpl.modules.libapi.PublicInterfaceExtractor.class); public static Set getPublicInterface(IClassHierarchy cha) { int classCount = 0; // how many distinct classes have public methods HashSet pubMethods = new HashSet(); for (IClass clazz: cha) { if (!WalaUtils.isAppClass(clazz)) continue; Collection methods = clazz.getDeclaredMethods(); // filter anything but public and non-compiler generated methods methods = methods.stream() .filter(m -> { int code = AccessFlags.getMethodAccessCode(m); return code > 0 && (code & AccessFlags.getPublicOnlyFilter()) == 0x0; }) // if predicate is true, keep in list .filter(m -> !(m.isBridge() || m.isSynthetic())) // filter compiler-generated methods .collect(Collectors.toCollection(ArrayList::new)); if (!methods.isEmpty()) classCount++; pubMethods.addAll(methods); } logger.debug("[getPublicInterface] Retrieved " + pubMethods.size() + " public methods from " + classCount + " distinct classes"); return pubMethods; } /** * Try to programmatically infer the documented public library interface, i.e., the public methods * that the app developer is supposed to use. * This filter obfuscated names (id-renaming via ProGuard), public method of anonymous inner-classes and * public methods in internal packages (based on keywords) * @param cha * @return */ public static Set getDocumentedPublicInterface(IClassHierarchy cha) { Set methods = getPublicInterface(cha); int pubApiCount = methods.size(); methods = methods.stream() .filter(m -> !WalaUtils.isAnonymousInnerClass(m.getDeclaringClass())) // filter pub methods in anonymous inner classes .filter(m -> !isSignatureRenamed(m)) // filter obfuscated names .filter(m -> !isLibInternalMethod(m)) // is lib internal based on keywords .collect(Collectors.toCollection(HashSet::new)); ArrayList list = new ArrayList(methods); list.sort(Comparator.comparing(IMethod::getSignature)); // list.forEach(m -> logger.debug(Utils.INDENT + "[DocAPI] " + m.getSignature() + (m.getDeclaringClass().isInterface()? " [IS_IF_METHOD] " : " "))); logger.debug("[getDocumentedPublicInterface] Retrieved " + list.size() + " doc public APIs (filtered: " + (pubApiCount-list.size()) + " public APIs)"); // TODO: entrypoint analysis with code? // filter other? find lib entry points (calls within the lib?) // -> if pub method is never called, likely to be doc api (or thread->start -> run) return methods; } /** * Check if package name includes a subpackage named 'internal' * @param m * @return */ private static boolean isLibInternalMethod(IMethod m) { // check a part of the package contains 'internal' List pckgFragments = PackageUtils.parsePackage(m.getDeclaringClass()); for (String frag: pckgFragments) { if ("internal".equals(frag)) { return true; } } return false; } private static boolean isSignatureRenamed(IMethod m) { logger.trace(Utils.INDENT2 + "- Check for renaming: " + m.getSignature()); // check if method name has been renamed boolean renamed = isIdentifierRenamed(m.getReference().getName().toString()); if (renamed) { logger.trace(Utils.indent(3) + "-> method name " + m.getReference().getName().toString() + " is renamed"); return true; } // check whether the class name has been renamed (e.g. when method is something like <[cl]init> or toString() // in case we have (a) named inner-class(es), we check whether all classes were renamed List clazz = new ArrayList(); String[] cz = WalaUtils.getClassName(m.getDeclaringClass()).split("\\$"); clazz.addAll(Arrays.asList(cz)); renamed = false; for (String c: clazz) { renamed = isIdentifierRenamed(c); } if (renamed) { logger.trace(Utils.indent(3) + "-> class name " + WalaUtils.getClassName(m.getDeclaringClass()) + " is renamed"); return true; } // TODO: probably not necessary (unlikely that method/class is not renamed but packagename // check whether a subset of the package name has been renamed renamed = false; List pckgFragments = PackageUtils.parsePackage(m.getDeclaringClass()); // logger.debug(" pckg fragments: " + Utils.join(pckgFragments, ",")); for (String frag: pckgFragments) { boolean fragRenamed = isIdentifierRenamed(frag); // logger.debug(" frag " + frag + " renamed: " + fragRenamed + " currentRenamed: " + renamed); if (renamed && !fragRenamed) // non-obfuscated package fragments must not follow obfuscated ones return false; renamed = fragRenamed; } return renamed; } /** * filter proguard-obfuscated methods and subpackage identifier * (created with its SimpleNameFactory and SpecialNameFactory) * * Limit detection to 2-char names to avoid false positives (non-obfuscated method names such as "foo") * For mixedCaseNames this is 56^2 = 2704 combinations (e.g. aR, Bm, mB,..) * For lowerCaseNames this is 26^2 = 676 combinations (e.g. aa, cd, ea,..) * The number of combinations is per-package. Default Android Studio settings are lowercase only. * In addition, ProGuard might append an underscore '_' to resolve ambiguous names (that were created during obfuscation) */ private static boolean isIdentifierRenamed(String str) { if (str.length() == 3 && str.charAt(2) == '_') str = str.substring(0,1); // strip underscore if (str.length() == 1 || str.length() == 2) { return str.matches("[a-zA-Z]{1,2}"); } return false; } } LibScout-2.3.2/src/de/infsec/tpl/modules/libmatch/000077500000000000000000000000001342431362400217245ustar00rootroot00000000000000LibScout-2.3.2/src/de/infsec/tpl/modules/libmatch/LibCodeUsage.java000066400000000000000000000210171342431362400250560ustar00rootroot00000000000000/* * Copyright (c) 2015-2017 Erik Derr [derr@cs.uni-saarland.de] * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this * file except in compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package de.infsec.tpl.modules.libmatch; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.TreeSet; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.ibm.wala.classLoader.CallSiteReference; import com.ibm.wala.classLoader.CodeScanner; import com.ibm.wala.classLoader.IClass; import com.ibm.wala.classLoader.IMethod; import com.ibm.wala.ipa.cha.IClassHierarchy; import com.ibm.wala.shrikeCT.InvalidClassFileException; import de.infsec.tpl.hash.AccessFlags; import de.infsec.tpl.profile.ProfileMatch; import de.infsec.tpl.utils.Utils; import de.infsec.tpl.utils.WalaUtils; /** * Library Code Usage Analysis * Checks which library code is used by the application in terms of API calls * To this end, for each full match (partial matches are excluded) it is checked which calls are used within * the code base that does not belong the identified library root package name. */ public class LibCodeUsage { private static final Logger logger = LoggerFactory.getLogger(LibCodeUsage.class); // TODO // how to deal with libs with ambiguous root pckg (currently excluded) // -> need to check all matched package name for such libs public static void checkUsage(final IClassHierarchy cha, final List results) { logger.info(""); logger.info("== Check lib usage =="); long starttime = System.currentTimeMillis(); // get unique libraries (multiple lib versions with an exact match have the same API, thus checking only one of them suffices) final HashMap lib2Profile = new HashMap(); // libname -> profile final HashMap rootPckg2Lib = new HashMap(); // root package -> libname for (ProfileMatch pm: results) { if (!pm.doAllConfigsMatch()) continue; String libName = pm.lib.description.name; if (!lib2Profile.containsKey(libName)) { lib2Profile.put(libName, pm); } String rootPckg = pm.getMatchedPackageTree().getRootPackage(); // TODO: currently exclude libs with ambiguous root packages if (!LibraryIdentifier.ambiguousRootPackages.contains(rootPckg) && !rootPckg2Lib.containsKey(rootPckg)) { rootPckg2Lib.put(rootPckg, libName); } } // shortcut - if there are no lib matches there is no need to walk over the bytecode if (lib2Profile.isEmpty()) { logger.info(Utils.INDENT + ">> lib code usage analysis done - No libraries matched to scan for (" + Utils.millisecondsToFormattedTime(System.currentTimeMillis() - starttime) + ")"); return; } // scan code once // library root package -> signature, access specifier Map> usedMethods = new TreeMap>(); Map> unresolvableMethods = new TreeMap>(); // iterate all classes for (IClass clazz: cha) { if (!WalaUtils.isAppClass(clazz)) continue; // iterate all methods for (IMethod im: clazz.getDeclaredMethods()) { if (im.isAbstract() || im.isNative()) continue; try { // iterate all call sites for (CallSiteReference csr: CodeScanner.getCallSites(im)) { for (String rootPckg: rootPckg2Lib.keySet()) { if (csr.getDeclaredTarget().getSignature().startsWith(rootPckg) && !WalaUtils.simpleName(im.getDeclaringClass()).startsWith(rootPckg)) { //TODO how to store? extend DB? flag whether usage was found in app dev code (based on (fractions of) package name) or other lib code? we could also classify by using matched lib packages // TODO: add additional heuristic to check for app package name levels boolean usedInApp = !rootPckg2Lib.keySet().contains(WalaUtils.simpleName(im.getDeclaringClass())); // check access specifier for target method IClass targetClazz = cha.lookupClass(csr.getDeclaredTarget().getDeclaringClass()); if (targetClazz == null) { // no lookup possible - dead / legacy code? logger.debug(Utils.INDENT + "Unresolvable class for lib method in use: " + csr.getDeclaredTarget().getSignature()); if (!unresolvableMethods.containsKey(rootPckg2Lib.get(rootPckg))) unresolvableMethods.put(rootPckg2Lib.get(rootPckg), new TreeSet()); unresolvableMethods.get(rootPckg2Lib.get(rootPckg)).add(csr.getDeclaredTarget().getSignature()); break; } IMethod targetMethod = targetClazz.getMethod(csr.getDeclaredTarget().getSelector()); if (targetMethod == null) { // e.g. if clazz is interface without declared methods targetMethod = WalaUtils.resolveMethod(clazz, csr); } int accessSpecifier = AccessFlags.getMethodAccessCode(targetMethod); String accessSpec = AccessFlags.flags2Str(accessSpecifier); logger.trace(Utils.INDENT + "- method in use (in " + (usedInApp? "app" : "lib") +"): " + csr.getDeclaredTarget().getSignature() + " in bm: "+ im.getSignature() + " access: " + accessSpec); String normalizedSig = csr.getDeclaredTarget().getSignature(); // normalize signature if lib root package does not match app lib root package (e.g. due to id renaming) if (!rootPckg.equals(lib2Profile.get(rootPckg2Lib.get(rootPckg)).lib.packageTree.getRootPackage())) { // replace package name in dot notation String r = rootPckg.replaceAll("\\.", "\\\\."); String rx = lib2Profile.get(rootPckg2Lib.get(rootPckg)).lib.packageTree.getRootPackage().replaceAll("\\.", "\\\\."); // replace arguments in / notation String r2 = rootPckg.replaceAll("\\.", "/"); String rx2 = lib2Profile.get(rootPckg2Lib.get(rootPckg)).lib.packageTree.getRootPackage().replaceAll("\\.", "/"); normalizedSig = csr.getDeclaredTarget().getSignature().replaceAll(r,rx).replaceAll(r2,rx2); } // update usedMethods if (!usedMethods.containsKey(rootPckg2Lib.get(rootPckg))) usedMethods.put(rootPckg2Lib.get(rootPckg), new TreeMap()); usedMethods.get(rootPckg2Lib.get(rootPckg)).put(normalizedSig, accessSpecifier); } } } } catch (InvalidClassFileException e) { logger.error(Utils.stacktrace2Str(e)); } } } // debug output for (String lib: usedMethods.keySet()) { ProfileMatch pm = lib2Profile.get(lib); String libRootPckg = pm.lib.packageTree.getRootPackage(); // TODO: currently unresolvable methods and access spec are not stored! // store used methods pm.usedLibMethods = new TreeSet(usedMethods.get(lib).keySet()); logger.info("- check lib: " + pm.lib.getLibIdentifier() + " root package: " + libRootPckg + (!libRootPckg.equals(pm.getMatchedPackageTree().getRootPackage())? " vs matched root package: " + pm.getMatchedPackageTree().getRootPackage() : "")); // retrieve number of unique lib classes used Set uniqueClazzes = new HashSet(); for (String sig: usedMethods.get(lib).keySet()) { uniqueClazzes.add(Utils.getFullClassName(sig)); } logger.info(" (found " + usedMethods.get(lib).size() + " unique library methods and " + uniqueClazzes.size() + " unique lib clazzes"); for (String sig: usedMethods.get(lib).keySet()) { logger.debug(Utils.INDENT + "- method in use: " + sig + " " + AccessFlags.flags2Str(usedMethods.get(lib).get(sig))); } // This can happen if a library contains multiple sub-libraries like OrmLite-[android,core,jdbc] and only a subset is profiled. if (unresolvableMethods.containsKey(lib)) { logger.info(""); for (String sig: unresolvableMethods.get(lib)) logger.debug(Utils.INDENT + "!! unresolvable library method in use by app: " + sig); } logger.info(""); } logger.info(Utils.INDENT + ">> lib code usage analysis done (" + Utils.millisecondsToFormattedTime(System.currentTimeMillis() - starttime) + ")"); } } LibScout-2.3.2/src/de/infsec/tpl/modules/libmatch/LibraryIdentifier.java000066400000000000000000001044541342431362400262060ustar00rootroot00000000000000/* * Copyright (c) 2015-2017 Erik Derr [derr@cs.uni-saarland.de] * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this * file except in compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package de.infsec.tpl.modules.libmatch; import java.io.File; import java.io.IOException; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; import com.ibm.wala.ipa.cha.ClassHierarchyFactory; import de.infsec.tpl.config.LibScoutConfig; import de.infsec.tpl.hashtree.HashTree; import de.infsec.tpl.hashtree.node.Node; import de.infsec.tpl.hashtree.node.PackageNode; import de.infsec.tpl.utils.WalaUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.slf4j.MDC; import com.ibm.wala.dalvik.util.AndroidAnalysisScope; import com.ibm.wala.ipa.callgraph.AnalysisScope; import com.ibm.wala.ipa.cha.ClassHierarchyException; import com.ibm.wala.ipa.cha.IClassHierarchy; import de.infsec.tpl.manifest.ProcessManifest; import de.infsec.tpl.hash.Hash; import de.infsec.tpl.pkg.PackageTree; import de.infsec.tpl.pkg.PackageUtils; import de.infsec.tpl.pkg.PackageUtils.RELATIONSHIP; import de.infsec.tpl.profile.AppProfile; import de.infsec.tpl.profile.LibProfile; import de.infsec.tpl.profile.ProfileMatch; import de.infsec.tpl.profile.ProfileMatch.HTreeMatch; import de.infsec.tpl.profile.ProfileMatch.MatchLevel; import de.infsec.tpl.stats.AppStats; import de.infsec.tpl.stats.SerializableAppStats; import de.infsec.tpl.utils.ApkUtils; import de.infsec.tpl.utils.Pair; import de.infsec.tpl.utils.Utils; public class LibraryIdentifier { private static final Logger logger = LoggerFactory.getLogger(LibraryIdentifier.class); private IClassHierarchy cha; private Map uniqueLibraries; // unique library name -> highest version private AppStats stats; private static final String FILE_EXT_SERIALIZED = ".data"; private static final String FILE_EXT_JSON = ".json"; public static Set ambiguousRootPackages = new TreeSet() { private static final long serialVersionUID = 7951760067476257884L; { add("com.google"); add("com.google.android"); add("com.google.android.gms"); add("android.support"); }}; public static AppStats run(File appFile, List profiles, boolean libUsageAnalysis) throws ClassHierarchyException, NoSuchAlgorithmException, IOException { LibraryIdentifier libid = new LibraryIdentifier(appFile); return libid.identifyLibraries(profiles, libUsageAnalysis); } private LibraryIdentifier(File appFile) { this.stats = new AppStats(appFile); // set identifier for logging String logIdentifier = LibScoutConfig.logDir.getAbsolutePath() + File.separator; logIdentifier += appFile.getName().replaceAll("\\.jar", "").replaceAll("\\.apk", "").replaceAll("\\.aar", ""); MDC.put("appPath", logIdentifier); } private void createClassHierarchy() throws IOException, ClassHierarchyException { long s = System.currentTimeMillis(); // check if we have a multi-dex file stats.isMultiDex = ApkUtils.isMultiDexApk(stats.appFile); if (stats.isMultiDex) logger.info("Multi-dex apk detected - Code is merged to single class hierarchy!"); // create analysis scope and generate class hierarchy // we do not need additional libraries like support libraries, // as they are statically linked in the app code. final AnalysisScope scope = AndroidAnalysisScope.setUpAndroidAnalysisScope(new File(stats.appFile.getAbsolutePath()).toURI(), null /* no exclusions */, null /* we always pass an android lib */, LibScoutConfig.pathToAndroidJar.toURI()); cha = ClassHierarchyFactory.makeWithRoot(scope); logger.info("Generated class hierarchy (in " + Utils.millisecondsToFormattedTime(System.currentTimeMillis() - s) + ")"); WalaUtils.getChaStats(cha); } private AppStats identifyLibraries(List profiles, boolean libUsageAnalysis) throws NoSuchAlgorithmException, IOException, ClassHierarchyException { long starttime = System.currentTimeMillis(); logger.info("Process app: " + stats.appFile.getName()); // parse AndroidManifest.xml stats.manifest = parseManifest(stats.appFile); // check stat file /package-level1/package-level2/appName_appVersionCode.data String statsFileName = stats.appFile.getName().replaceAll("\\.jar", "").replaceAll("\\.apk", "").replaceAll("\\.aar", "") + "_" + stats.manifest.getVersionCode(); // without file suffix File statsSubDir = PackageUtils.packageToPath(stats.manifest.getPackageName()); File statsFile = new File(LibScoutConfig.statsDir + File.separator + statsSubDir + File.separator + statsFileName + FILE_EXT_SERIALIZED); // if stat file already exists for this app, return if (LibScoutConfig.generateStats && statsFile.exists()) { logger.info(Utils.INDENT + "Stat file " + statsFile + " already exists - ABORT!"); return null; } stats.profiles = profiles; uniqueLibraries = LibProfile.getUniqueLibraries(profiles); logger.info("Found " + uniqueLibraries.size() + " unique libraries in " + profiles.size() + " library profiles"); // create CHA createClassHierarchy(); // generate app package tree and hash trees AppProfile appProfile = AppProfile.create(cha); stats.pTree = appProfile.packageTree; stats.appHashTrees = appProfile.hashTrees; // fast scan (heuristic) - check if lib root package is in app logger.info("= Scan for library root packages (heuristic) ="); for (LibProfile profile: profiles) { // check if library root package is present in app (for validation purposes) String rootPackage = profile.packageTree.getRootPackage(); // In some edge case the automatic root package extraction gives us a generic package that could match multiple different libraries. // In these cases it is better to ignore them instead of getting a lot of false matches if (rootPackage == null || ambiguousRootPackages.contains(rootPackage)) continue; boolean match = appProfile.packageTree.containsPackage(rootPackage); if (match && !stats.packageOnlyMatches.containsKey(profile.description.name)) { stats.packageOnlyMatches.put(profile.description.name, rootPackage); logger.info(Utils.INDENT + "- Found lib root package " + rootPackage + " (" + profile.description.name + ")"); } } logger.info(""); // check app against all loaded profiles (exact + partial matching) long s = System.currentTimeMillis(); logger.info("= Match profiles ="); List results = new ArrayList(); for (LibProfile profile: profiles) { logger.debug("- Match Library: " + profile); logger.trace("Lib PackageTree:"); if (logger.isTraceEnabled()) { profile.packageTree.print(true); logger.trace(""); } // check if this is the most current library version profile.setIsDeprecatedLib(!uniqueLibraries.get(profile.description.name).equals(profile.description.version)); // compute similarity scores for each hash tree ProfileMatch pm = partialMatchForTrees(cha, appProfile, profile, MatchLevel.CLASS); results.add(pm); // do we have a one-to-one copy of the library? if (pm.doAllConfigsMatch()) { logger.debug(Utils.INDENT + "- all configs match!"); logger.debug(Utils.INDENT + "- re-obfuscated library? " + pm.isLibObfuscated()); logger.debug(""); } } logger.info(Utils.INDENT + ">> profile matching done (" + Utils.millisecondsToFormattedTime(System.currentTimeMillis() - s) + ")"); stats.pMatches = results; printResults(results); // run library API usage analysis for full matches only if (libUsageAnalysis) LibCodeUsage.checkUsage(cha, results); logger.info(""); stats.processingTime = System.currentTimeMillis() - starttime; // write app results to json if (LibScoutConfig.generateJSON) { File jsonFile = new File(LibScoutConfig.jsonDir + File.separator + statsSubDir + File.separator + statsFileName + FILE_EXT_JSON); Utils.obj2JsonFile(jsonFile, stats); logger.info("Write app stats to JSON (dir: " + LibScoutConfig.jsonDir + ")"); } // serialize appstats to disk if (LibScoutConfig.generateStats) { if (!stats.pMatches.isEmpty()) { logger.info("Serialize app stats to disk (dir: " + LibScoutConfig.statsDir + ")"); Utils.object2Disk(statsFile, new SerializableAppStats(stats)); // TODO mv from java serialization to protobufs or remove w/o replacement } } logger.info("App processing time: " + Utils.millisecondsToFormattedTime(stats.processingTime)); return stats; } /** * Compute similarity scores for all provided {@link HashTree}. * @param cha the {@link IClassHierarchy} * @param appProfile the {@link AppProfile} * @param libProfile the {@link LibProfile} * @param lvl the level of matching to be applied (currently either Package or Class level) * @return a {@link ProfileMatch} for the provided library profile * @throws NoSuchAlgorithmException */ public ProfileMatch partialMatchForTrees(final IClassHierarchy cha, final AppProfile appProfile, final LibProfile libProfile, final MatchLevel lvl) throws NoSuchAlgorithmException { ProfileMatch pMatch = new ProfileMatch(libProfile); logger.trace("Partial match of lib: " + libProfile); // check if library root package is present in app (for validation purposes) String rootPackage = libProfile.packageTree.getRootPackage(); pMatch.libRootPackagePresent = rootPackage == null? false : appProfile.packageTree.containsPackage(rootPackage); logger.trace(Utils.INDENT + "Library root package " + rootPackage + " is " + (pMatch.libRootPackagePresent? "" : "not ") + " present in app!"); // calculate scores for each hash tree for (HashTree tree : appProfile.hashTrees) { partialMatch(cha, pMatch, tree, appProfile.packageTree, libProfile, lvl); } if (logger.isTraceEnabled()) pMatch.printResults(3); return pMatch; } /** * Multi-step approach to compute the similarity score between a given library and an application. * We first check for a full match by comparing the package hashes of the library with the ones from the application. * If there is no full match we compute a candidate list for each app package, compute partitions (potential root packages) and * determine the maximum over all partitions. * * @param cha the {@IClassHierarchy} * @param pMatch a {@ProfileMatch} in which the result is stored as side-effect * @param appHashTree the generated {@link HashTree} * @param appTree the application {@link PackageTree} * @param lib the {@link LibProfile} to match against * @param lvl the level of matching to be applied (currently either Package or Class level) * @throws NoSuchAlgorithmException */ public void partialMatch(final IClassHierarchy cha, final ProfileMatch pMatch, final HashTree appHashTree, final PackageTree appTree, final LibProfile lib, final MatchLevel lvl) throws NoSuchAlgorithmException { // retrieve hash tree with same config from profile HashTree libHashTree = pMatch.lib.hashTrees.get(0); // TODO mult? HTreeMatch match = pMatch.createResult(appHashTree.getConfig()); logger.debug(Utils.INDENT + "- partial match for config: " + appHashTree.getConfig()); if (libHashTree == null) { logger.error("Could not find lib hash tree for config: " + appHashTree.getConfig()); return; } /* * step 0. shortcut - check if library fully matches by comparing the package hashes */ logger.debug(Utils.INDENT2 + "# step 0: check if lib fully matches"); if (appHashTree.getPackageNodes().containsAll(libHashTree.getPackageNodes())) { logger.debug(Utils.indent(3) + "-> All package hashes (" + libHashTree.getPackageNodes().size() + ") of library match!"); // update results match.simScore = ProfileMatch.MATCH_HTREE_FULL; List matchingNodes = new ArrayList<>(appHashTree.getPackageNodes()); matchingNodes.retainAll(libHashTree.getPackageNodes()); match.matchingNodes = matchingNodes; pMatch.addResult(match); return; } // abort if partial matching has been disabled via cli-option if (LibScoutConfig.noPartialMatching) { logger.debug(Utils.INDENT2 + "Partial matching disabled - [SKIP]"); match.simScore = ProfileMatch.MATCH_HTREE_NONE; pMatch.addResult(match); return; } // we do not perform partial matching for libs that have multiple lib root packages logger.debug(Utils.INDENT2 + "# step 0.5: Check root package: " + lib.packageTree.getRootPackage()); if (lib.packageTree.getRootPackage() == null) { logger.debug(Utils.indent(3) + "-> No partial matching performed due to multiple lib root packages"); match.simScore = ProfileMatch.MATCH_HTREE_NO_ROOT_PCKG; pMatch.addResult(match); return; } /* * step 1. compute candidate list * * Candidate list of each library package sorted by similarity score, i.e. * lp1 ∶ ap1 (0.95), ap2 (0.84), ap3 (0.75) * lp3 ∶ ap6 (0.91), ap4 (0.60) * lp2 ∶ ap7 (0.85), ap9 (0.82) */ logger.debug(Utils.INDENT2 + "# step 1: compute candidate list"); long time = System.currentTimeMillis(); HashMap>> candidateList = new HashMap>>(); for (Node lp: libHashTree.getPackageNodes()) { ArrayList> clist = new ArrayList>(); // candidate list for lp for (Node ap: appHashTree.getPackageNodes()) { // filter application packages that start with declared manifest app package name // TODO: unfortunately most app packages do only partially match the manifest package name. This means to match more app packages // we would have to test partially (but: this could lead to false positives if we have libs from the same developer) if (((PackageNode) ap).packageName.startsWith(stats.manifest.getPackageName())) continue; float score = calcNodeSimScore(lp, ap); if (score > ProfileMatch.MIN_CLAZZ_SCORE) { // update candidate list clist.add(new Pair(ap, score)); } } Collections.sort(clist, new SimScoreComparator()); // sort candidate list by simScore candidateList.put(lp, clist); } // sort tree map by highest candidate values TreeSet>>> sortedCList = new TreeSet>>>(new CandidateListComparator()); sortedCList.addAll(candidateList.entrySet()); // debug print (sorted candidate list) if (logger.isTraceEnabled()) { logger.trace(Utils.indent(3) + "Sorted candidate list (" + Utils.millisecondsToFormattedTime(System.currentTimeMillis() - time) + "):"); for (Map.Entry>> entry: sortedCList) { logger.trace(Utils.indent(4) + entry.getKey() + " : " + Utils.join(entry.getValue(), ", ")); } } /* * step 2. get potential root packages -- partitions (skip package name of apk) * Note: This will _not_ work if app developer manually renamed the library packages (e.g. prefixed lib packages with app package) */ logger.debug(Utils.INDENT2 +"# step 2: determine partitions"); time = System.currentTimeMillis(); String libraryRootPackage = pMatch.lib.packageTree.getRootPackage(); int libPDepth = PackageUtils.packageDepth(libraryRootPackage); // retrieve potential app root packages of depth libPDepth Set appRootPackages = new TreeSet(); for (Node ap: appHashTree.getPackageNodes()) { PackageNode apn = (PackageNode) ap; String pRootPackage = PackageUtils.getSubPackageOfDepth(apn.packageName, libPDepth); if (pRootPackage != null) appRootPackages.add(pRootPackage); } logger.trace(Utils.indent(3) + "# partitions(" + appRootPackages.size() + "): " + appRootPackages); appRootPackages = getPartitionsByRootPackage(appRootPackages, lib.packageTree.getRootPackage()); logger.debug(Utils.indent(3) + "# unique partitions(" + appRootPackages.size() + "): " + appRootPackages); logger.debug(Utils.indent(4) + "- step 2 processing time: " + Utils.millisecondsToFormattedTime(System.currentTimeMillis() - time)); /* * step 3. compute maximum score for each partition (including selected app packages) * a score is a mapping of root package -> pair < score, nodes with individual package scores > */ logger.debug(Utils.INDENT2 + "# step 3: partitions scores"); // pre-compute library package relationships // TODO: could be computed even earlier (pmatchForTrees) List libPackageRel = computePackageRelationships(sortedCList); HashMap>>> scores = new HashMap>>>(); for (String rootPackage: appRootPackages) { time = System.currentTimeMillis(); Pair>> partSimScore = calcPartitionSimScore(rootPackage, sortedCList, libPackageRel); if (partSimScore != null) { logger.debug(Utils.indent(4) + "-> partition: " + rootPackage + " sim score: " + partSimScore); scores.put(rootPackage, partSimScore); } logger.debug(Utils.indent(5) + "- processing time: " + Utils.millisecondsToFormattedTime(System.currentTimeMillis() - time)); } /* * step 4. chose overall maximum (with a minimum threshold of @ProfileMatch.MIN_PARTIAL_MATCHING_SCORE) */ logger.debug(Utils.INDENT2 + "# step 4: compute overall maximum score"); if (scores.isEmpty()) { logger.debug("No partial matching for " + pMatch.lib.getLibIdentifier() + "\n"); // update results match.simScore = ProfileMatch.MATCH_HTREE_NONE; pMatch.addResult(match); } else { logger.debug(Utils.indent(3) + scores.size() + " results for partial matching of lib " + pMatch.lib.getLibIdentifier() + " (" + appHashTree.getConfig() + ")"); // get best score over partitions String rootPckg = ""; float highScore = 0f; for (String root: scores.keySet()) { logger.debug(Utils.indent(4) + "- rootPckg: " + root + " score: " + scores.get(root).first()); if (scores.get(root).first() > highScore) { highScore = scores.get(root).first(); rootPckg = root; } } logger.debug(Utils.INDENT + "=> maximum partial matching score: " + highScore + " (partition: " + rootPckg + ")\n"); match.rootPackage = rootPckg; match.simScore = highScore; List matchingNodes = new ArrayList(); for (Pair p: scores.get(rootPckg).second()) { if (p != null) matchingNodes.add(p.first()); } match.matchingNodes = HashTree.toPackageNode(matchingNodes); pMatch.addResult(match); } } // TODO: cumbersome private static List computePackageRelationships(TreeSet>>> candidateList) { // retrieve ordered list of library packages from the current candidate list ArrayList libraryPackageNodes = new ArrayList(); for (Map.Entry>> entry: candidateList) { libraryPackageNodes.add((PackageNode) entry.getKey()); } // compute package relationship for adjacent packages (used as additional app package filter in getCombinations) ArrayList result = new ArrayList(); for (int i = 0; i < libraryPackageNodes.size()-1; i++) { result.add(PackageUtils.testRelationship(libraryPackageNodes.get(i).packageName, libraryPackageNodes.get(i+1).packageName)); } return result; } private Pair>> calcPartitionSimScore(final String rootPackage, final TreeSet>>> candidateList, final List libPackageRel) { logger.trace(Utils.indent(3) + "- Calculate sim score for partition: " + rootPackage); // create view on candidate list (filter app packages that do not start with rootPackage and app packages // that have a different depth as the lib package) // TODO: maybe could be improved by using a bitmask ArrayList>> cList = new ArrayList>>(); int packagesWithCandidates = 0; for (Iterator>>> it = candidateList.iterator(); it.hasNext(); ) { Map.Entry>> pckgCandidates = it.next(); ArrayList> filteredCandidates = new ArrayList>(); for (Pair candidate: pckgCandidates.getValue()) { PackageNode pn = (PackageNode) candidate.first(); // check if candidate package starts with root package and has the same package depth if (pn.packageName.startsWith(rootPackage) && PackageUtils.packageDepth(((PackageNode) pckgCandidates.getKey()).packageName) == PackageUtils.packageDepth(pn.packageName)) filteredCandidates.add(candidate); } if (!filteredCandidates.isEmpty()) { packagesWithCandidates++; } cList.add(filteredCandidates); } // stop if less than half of lib packages have no candidate if ((((float) packagesWithCandidates / (float) candidateList.size()) < 0.5f)) { logger.trace(Utils.indent(4) + "Only " + packagesWithCandidates + " / " + candidateList.size() + " lib packages for partition: " + rootPackage + " have candidates - [SKIP]"); return null; } // debug print (sorted+filtered candidate list) if (logger.isTraceEnabled()) { logger.trace(Utils.indent(4) + "Sorted + filtered (package name) candidate list:"); int i = 0; for (Map.Entry>> entry: candidateList) { logger.trace(Utils.indent(5) + entry.getKey() + " : " + Utils.join(cList.get(i), ", ")); i++; } } // test all combinations and retrieve maximum return getBestMatch(cList, libPackageRel); } /** * Given a candidate list of app packages for every lib package, compute the optimal solution * while preserving the package relationship. * @param cList ordered candidate list * @param libPackageRel pre-computed relationship between two consecutive library packages * @return the optimal solution as similarity score and corresponding list of packages */ public static Pair>> getBestMatch(final ArrayList>> cList, final List libPackageRel) { // keep track of the size of candidate arrays for each lib // Example cList: // lp1 ∶ ap1 (0.95), ap2 (0.84), ap3 (0.75) // lp3 ∶ ap6 (0.91), ap4 (0.60) // lp2 ∶ ap7 (0.85), ap9 (0.82) // => size array [3,2,2] int sizeArray[] = new int[cList.size()]; // keep track of the index of each inner String array which will be used // to make the next combination (access pattern) int counterArray[] = new int[cList.size()]; // Discover the size of each inner array and populate sizeArray. // Calculate the total number of combinations possible, here: 3*2*2 = 12 int totalCombinationCount = 1; for(int i = 0; i < cList.size(); ++i) { sizeArray[i] = cList.get(i).size(); totalCombinationCount *= cList.get(i).isEmpty()? 1 : cList.get(i).size(); // tolerate empty candidate list } // stop if we have too much combinations if (totalCombinationCount > 65536) { logger.trace(Utils.indent(4) + "[getBestMatch] more than 2^16 combinations (" + totalCombinationCount + ") - stop!"); return null; } else logger.trace(Utils.indent(4) +"- testing " + totalCombinationCount + " combinations!"); // only consider solutions that are better than the min matching score float highScore = ProfileMatch.MIN_PARTIAL_MATCHING_SCORE; List> bestMatch = null; // test all combinations List> curSolution = null; for (int countdown = totalCombinationCount; countdown > 0; --countdown) { // calculate sim score for current combination (set in counterArray) float simScore = 0f; for(int i = 0; i < cList.size(); ++i) { List> candidates = cList.get(i); simScore += candidates.isEmpty()? 0f : candidates.get(counterArray[i]).second(); } simScore = simScore / (float) cList.size(); // if we have a new highscore, perform structural matching (package relationships) to // verify correctness of the solution if (simScore > highScore) { curSolution = new ArrayList>(); for (int i = 0; i < cList.size()-1; i++) { if (cList.get(i).isEmpty() || cList.get(i+1).isEmpty()) // tolerate empty candidates continue; RELATIONSHIP candidateRel = PackageUtils.testRelationship(((PackageNode) cList.get(i).get(counterArray[i]).first()).packageName, ((PackageNode) cList.get(i+1).get(counterArray[i+1]).first()).packageName); if (!libPackageRel.get(i).equals(candidateRel)) { curSolution = null; break; } else { curSolution.add(cList.get(i).get(counterArray[i])); } } if (curSolution != null) { highScore = simScore; bestMatch = new ArrayList>(curSolution); if (!cList.get(cList.size()-1).isEmpty()) bestMatch.add(cList.get(cList.size()-1).get(counterArray[cList.size()-1])); // add last element (if existing) logger.trace(Utils.indent(4) + "- Found new highscore: " + highScore + " at position " + intArray2List(counterArray)); } } // Increment the counterArray so that the next combination is taken on the next iteration of this loop. for (int incIndex = cList.size()-1; incIndex >= 0; --incIndex) { if (counterArray[incIndex] + 1 < sizeArray[incIndex]) { ++counterArray[incIndex]; // None of the indices of higher significance need to be // incremented, so jump out of this for loop at this point. break; } // The index at this position is at its max value, so zero it // and continue this loop to increment the index which is more // significant than this one. counterArray[incIndex] = 0; } } return bestMatch == null? null : new Pair>>(highScore, bestMatch); } // Filter candidates by lib root package private static Set getPartitionsByRootPackage(Collection partitions, String libRootPackage) { if (partitions.isEmpty() || partitions.size() == 1 || libRootPackage == null) return new HashSet(partitions); Set result = new TreeSet(); for (String partition: partitions) { if (partition.startsWith(libRootPackage)) result.add(partition); } return result.isEmpty()? new TreeSet(partitions) : result; } private static List intArray2List(int[] a) { ArrayList list = new ArrayList(); for (int i: a) list.add(i); return list; } /** * Computes the similarity score between two nodes based on matching child nodes. * @param libNode * @param appNode * @return zero, if type of both nodes does not match (e.g. PackageNode/ClassNode) * one, if both node hashes match (per definition all childs match then as well) * a similarity score between 0..1 otherwise */ public static float calcNodeSimScore(final Node libNode, final Node appNode) { // if they have not the exact same class e.g. PackageNode vs ClassNode, simScore is 0f if (!(libNode.getClass().equals(appNode.getClass()))) return 0f; // if hashes are equal return 1f if (Hash.equals(libNode.hash, appNode.hash)) return 1f; // calculate partial score List matchedNodes = new ArrayList(); for (Node lNode: libNode.childs) { if (appNode.childs.contains(lNode)) { matchedNodes.add(lNode); } } return (float) matchedNodes.size() / (float) libNode.numberOfChilds(); } /** * Verbose human-readable log file report using pre-computed results * @param results pre-computed results for each hashtree */ // TODO: if debug, report any match with simScore > .25 public void printResults(final List results) { logger.info(""); logger.info("== Report =="); logger.info("- Full library matches:"); // Step1: print libs for which all configs match Set exactMatches = new TreeSet(); for (String libName: uniqueLibraries.keySet()) { for (ProfileMatch pm: results) { if (pm.lib.description.name.equals(libName) && pm.doAllConfigsMatch()) { exactMatches.add(libName); pm.print(); } } } // Step2: print all libs which have at least one matching config but do not match all configs Set almostExactMatches = new TreeSet(); for (String libName: uniqueLibraries.keySet()) { if (exactMatches.contains(libName)) continue; // if the same lib matches in different versions and different number of exact matches, show only the best matches List bestMatches = new ArrayList(); for (ProfileMatch pm: results) { if (pm.lib.description.name.equals(libName) && pm.isMatch()) { if (bestMatches.isEmpty() || bestMatches.get(0).getMatchedConfigs().size() == pm.getMatchedConfigs().size()) bestMatches.add(pm); else if (bestMatches.get(0).getMatchedConfigs().size() < pm.getMatchedConfigs().size()) { bestMatches.clear(); bestMatches.add(pm); } } } for (ProfileMatch pm: bestMatches) { almostExactMatches.add(libName); /// TODO TODO : full code match? (if not how much code?) pm.print(); } } // unify all libraries for which we have at least one matching config exactMatches.addAll(almostExactMatches); logger.info(""); logger.info("- Partial library matches:"); if (LibScoutConfig.noPartialMatching) { logger.info(Utils.INDENT + "## Partial matching disabled ##"); logger.info(""); } else { final int MAX_PRINT_CONFIGS = 3; for (String lib: uniqueLibraries.keySet()) { if (!exactMatches.contains(lib)) { // print only highest match for a given library (can be multiple libs) List pMatches = new ArrayList(); float highScore = ProfileMatch.MATCH_HTREE_NONE; for (ProfileMatch pm: results) { if (pm.lib.description.name.equals(lib)) { if (pm.getHighestSimScore().simScore >= highScore && pm.getHighestSimScore().simScore > ProfileMatch.MATCH_HTREE_NONE) { pMatches = new ArrayList(); pMatches.add(pm); highScore = pm.getHighestSimScore().simScore; } } } for (ProfileMatch pm: pMatches) { for (String str: pm.lib.description.getDescription()) { if (str.contains("comment:")) continue; if (str.contains("version") && pm.lib.isDeprecatedLib()) str += " [OLD VERSION]"; // TODO store in db? logger.info(Utils.INDENT + str); } logger.info(Utils.INDENT + "lib root package: \"" + pm.lib.packageTree.getRootPackage() + "\"" + (pm.isLibObfuscated()? " (in app: \"" + pm.getMatchedPackageTree().getRootPackage() + "\")" : "")); logger.info(Utils.INDENT2 + "Partial matching results:"); for (HTreeMatch htm: pm.getBestResults(MAX_PRINT_CONFIGS)) logger.info(Utils.indent(3) + " - config: " + htm.config + " score: " + (htm != null? htm.simScore : " unknown")); logger.info(""); } } } } if (logger.isTraceEnabled()) { // Print package tree without matched libraries Set matchedPackages = new TreeSet(); for (ProfileMatch pm: results) { if (pm.isMatch()) { matchedPackages.addAll(pm.getMatchedPackageTree().getPackages().keySet()); } } logger.trace(""); logger.trace("Un-matched package tree (only fully matched libs are removed):"); PackageTree croppedTree = PackageTree.make(cha, true, matchedPackages); croppedTree.print(true); } } private ProcessManifest parseManifest(File appFile) { ProcessManifest pm = new ProcessManifest(); pm.loadManifestFile(appFile.getAbsolutePath()); logger.info("= Manifest Parser ="); logger.info(Utils.INDENT + " Package name: " + pm.getPackageName()); logger.info(Utils.INDENT + " Version code: " + pm.getVersionCode()); logger.info(Utils.INDENT + " minSdkVersion: " + pm.getMinSdkVersion()); logger.info(Utils.INDENT + "targetSdkVersion: " + pm.getTargetSdkVersion()); logger.info(Utils.INDENT + " SharedUserId: " + (pm.getSharedUserId().isEmpty()? " - none -" : pm.getSharedUserId())); // library dependencies, e.g. // - com.google.android.maps // - com.sec.android.app.multiwindow logger.info(Utils.INDENT + "Library dependencies:" + (pm.getLibraryDependencies().isEmpty()? " - none -" : "")); for (String libDep: pm.getLibraryDependencies()) logger.debug(Utils.INDENT2 + "- " + libDep); // permissions logger.debug(Utils.INDENT + "Declared permissions: " + (pm.getPermissions().isEmpty()? " - none -" : "")); for (String p: pm.getPermissions()) logger.debug(Utils.INDENT2 + "# " + p); logger.info(""); return pm; } /* * Compares Collections with Pair according the float value (descending) */ private class SimScoreComparator implements Comparator> { @Override public int compare(Pair p1, Pair p2) { return p2.second().compareTo(p1.second()); } } private class CandidateListComparator implements Comparator>>> { @Override public int compare(Map.Entry>> entry1, Map.Entry>> entry2) { if (entry1.getValue().isEmpty()) return 1; else if (entry2.getValue().isEmpty()) return -1; else { int val = entry2.getValue().get(0).second().compareTo(entry1.getValue().get(0).second()); // candidate lists are already ordered, hence it suffices to compare first value of each list return val == 0? ((PackageNode) entry1.getValue().get(0).first()).packageName.compareTo(((PackageNode) entry2.getValue().get(0).first()).packageName) : val; } } } } LibScout-2.3.2/src/de/infsec/tpl/modules/libprofiler/000077500000000000000000000000001342431362400224525ustar00rootroot00000000000000LibScout-2.3.2/src/de/infsec/tpl/modules/libprofiler/LibraryProfiler.java000066400000000000000000000120331342431362400264230ustar00rootroot00000000000000/* * Copyright (c) 2015-2017 Erik Derr [derr@cs.uni-saarland.de] * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this * file except in compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package de.infsec.tpl.modules.libprofiler; import java.io.File; import java.io.IOException; import java.text.ParseException; import java.util.List; import java.util.jar.JarFile; import javax.xml.parsers.ParserConfigurationException; import com.ibm.wala.ipa.cha.ClassHierarchyFactory; import de.infsec.tpl.config.LibScoutConfig; import de.infsec.tpl.hashtree.HashTree; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.slf4j.MDC; import org.xml.sax.SAXException; import com.ibm.wala.ipa.callgraph.AnalysisScope; import com.ibm.wala.ipa.cha.ClassHierarchy; import com.ibm.wala.ipa.cha.ClassHierarchyException; import com.ibm.wala.ipa.cha.IClassHierarchy; import com.ibm.wala.types.ClassLoaderReference; import de.infsec.tpl.pkg.PackageTree; import de.infsec.tpl.profile.LibProfile; import de.infsec.tpl.profile.LibraryDescription; import de.infsec.tpl.profile.Profile; import de.infsec.tpl.utils.AarFile; import de.infsec.tpl.utils.Utils; import de.infsec.tpl.utils.WalaUtils; import de.infsec.tpl.xml.XMLParser; public class LibraryProfiler { private static final Logger logger = LoggerFactory.getLogger(LibraryProfiler.class); public static String FILE_EXT_LIB_PROFILE = "libv"; // single library version profile private File libraryFile; // library.jar || library.aar private LibraryDescription libDesc; // library description parsed from an XML file public static void extractFingerPrints(File libraryFile, File libDescriptionFile) throws ParserConfigurationException, SAXException, IOException, ParseException, ClassHierarchyException, ClassNotFoundException { new LibraryProfiler(libraryFile,libDescriptionFile).extractFingerPrints(); } private LibraryProfiler(File libraryFile, File libDescriptionFile) throws ParserConfigurationException, SAXException, IOException, ParseException { this.libraryFile = libraryFile; // read library description this.libDesc = XMLParser.readLibraryXML(libDescriptionFile); // set identifier for logging String logIdentifier = LibScoutConfig.logDir.getAbsolutePath() + File.separator; logIdentifier += libDesc.name.replaceAll(" ", "-") + "_" + libDesc.version; MDC.put("appPath", logIdentifier); } private void extractFingerPrints() throws IOException, ClassHierarchyException, ClassNotFoundException { long starttime = System.currentTimeMillis(); logger.info("Process library: " + libraryFile.getName()); logger.info("Library description:"); for (String desc: libDesc.getDescription()) logger.info(desc); // create analysis scope and generate class hierarchy final AnalysisScope scope = AnalysisScope.createJavaAnalysisScope(); JarFile jf = libraryFile.getName().endsWith(".aar")? new AarFile(libraryFile).getJarFile() : new JarFile(libraryFile); scope.addToScope(ClassLoaderReference.Application, jf); scope.addToScope(ClassLoaderReference.Primordial, new JarFile(LibScoutConfig.pathToAndroidJar)); IClassHierarchy cha = ClassHierarchyFactory.makeWithRoot(scope); WalaUtils.getChaStats(cha); // cleanup tmp files if library input was an .aar file if (libraryFile.getName().endsWith(".aar")) { File tmpJar = new File(jf.getName()); tmpJar.delete(); logger.debug(Utils.indent() + "tmp jar-file deleted at " + tmpJar.getName()); } PackageTree pTree = Profile.generatePackageTree(cha); if (pTree.getRootPackage() == null) { logger.warn(Utils.INDENT + "Library contains multiple root packages"); } List hTrees = Profile.generateHashTrees(cha); // if hash tree is empty do not dump a profile if (hTrees.isEmpty() || hTrees.get(0).getNumberOfClasses() == 0) { logger.error("Empty Hash Tree generated - SKIP"); return; } // write profile to disk serialize(pTree, hTrees); logger.info(""); logger.info("Processing time: " + Utils.millisecondsToFormattedTime(System.currentTimeMillis() - starttime)); } // serialize lib profiles to disk (//libName_libVersion.lib) private void serialize(PackageTree pTree, List hTrees) { File targetDir = new File(LibScoutConfig.profilesDir + File.separator + libDesc.category.toString()); File proFile = new File(targetDir + File.separator + libDesc.name.replaceAll(" ", "-") + "_" + libDesc.version + "." + FILE_EXT_LIB_PROFILE); logger.info(""); logger.info("Serialize library fingerprint to disk (dir: " + targetDir + ")"); LibProfile lp = new LibProfile(libDesc, pTree, hTrees); Utils.object2Disk(proFile, lp); } } LibScout-2.3.2/src/de/infsec/tpl/modules/updatability/000077500000000000000000000000001342431362400226345ustar00rootroot00000000000000LibScout-2.3.2/src/de/infsec/tpl/modules/updatability/LibraryUpdatability.java000066400000000000000000000122771342431362400274700ustar00rootroot00000000000000/* * Copyright (c) 2015-2017 Erik Derr [derr@cs.uni-saarland.de] * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this * file except in compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package de.infsec.tpl.modules.updatability; import java.io.*; import java.util.*; import com.google.gson.Gson; import com.google.gson.stream.JsonReader; import de.infsec.tpl.modules.libapi.LibApiStats; import de.infsec.tpl.profile.ProfileMatch; import de.infsec.tpl.stats.AppStats; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import de.infsec.tpl.utils.Utils; import de.infsec.tpl.utils.VersionWrapper; public class LibraryUpdatability { private static final Logger logger = LoggerFactory.getLogger(de.infsec.tpl.modules.updatability.LibraryUpdatability.class); // libName -> stats private Map lib2ApiStats = new HashMap<>(); private final int FLAG_UP2DATE = 0; // already up2date private final int FLAG_NO_UPDATE = -1; // no update possible without code changes private final int FLAG_NO_LIBSTATS_AVAILABLE = -2; // no libstats available private final int FLAG_NO_USED_METHDOS = -3; // no library methods in use /** * TODO:: * [OK] check updatability * - include nested deps? * - integrate into libIdentifier * - write out stats / extend appstats * - check for sec vuln (+ vuln data should be read from file rather than hardcoded?) */ public LibraryUpdatability(File fdir) { // load lib-api compat information loadLibApiCompatData(fdir); } public void checkUpdatability(AppStats appStats) { logger.info(""); logger.info("== Check library updatability =="); // check for every detected lib if we have compat info for (ProfileMatch pm: appStats.pMatches) { if (pm.isMatch()) { // compare with usage info in appstats to infer latest version checkLibUpdatability(pm); } } } private void loadLibApiCompatData(File fdir) { logger.trace("Load lib api compat data:"); Gson gson = new Gson(); for (File f : Utils.collectFiles(fdir, new String[]{"json"})) { try { JsonReader reader = new JsonReader(new FileReader(f)); LibApiStats.Export libStats = gson.fromJson(reader, LibApiStats.Export.class); lib2ApiStats.put(libStats.libName, libStats); logger.trace(Utils.indent() + "# lib: " + libStats.libName + " # versions: " + libStats.apiDiffs.size()); } catch (Exception e) { logger.warn(Utils.stacktrace2Str(e)); } } } private int checkLibUpdatability(ProfileMatch pm) { logger.info("Check: " + pm.lib.getLibIdentifier()); String libName = pm.lib.description.name; String libVersion = pm.lib.description.version; // do we have compat info if (!lib2ApiStats.containsKey(libName)) { logger.info(Utils.indent() + ">> No lib api compat info for library: " + libName); return FLAG_NO_LIBSTATS_AVAILABLE; } LibApiStats.Export libstat = lib2ApiStats.get(libName); // check if already latest version if (libVersion.equals(libstat.versions.get(libstat.versions.size()-1))) { logger.info(Utils.indent() + ">> Library version is already up2date!"); return FLAG_UP2DATE; } // check if we have used lib methods if (pm.usedLibMethods.isEmpty()) { logger.info(Utils.indent() + ">> No identified used library methods!"); return FLAG_NO_USED_METHDOS; } // for every API determine max version, then take min version supported by all apis TreeSet maxVersions = new TreeSet<>(); for (String used: pm.usedLibMethods) { if (libstat.api2Versions.containsKey(used)) { // get max supported version for api (exclude matched version) String maxVersion = libstat.api2Versions.get(used).get(libstat.api2Versions.get(used).size()-1); logger.debug(Utils.indent() + "API: " + used + " maxVersion: " + maxVersion); if (!maxVersion.equals(libVersion)) maxVersions.add(maxVersion); } else logger.debug(Utils.indent() + "Could not lookup API: " + used + " [protected]"); } if (maxVersions.isEmpty()) { logger.info(Utils.indent() + ">> Library is not updatable"); return FLAG_NO_UPDATE; } // check if global minVersion is supported by all used apis (= updatable) for (String used: pm.usedLibMethods) { if (libstat.api2Versions.containsKey(used) && !libstat.api2Versions.get(used).contains(maxVersions.first())) { logger.info(Utils.indent() + ">> API: " + used + " does not support min version " + maxVersions.first() + ". Library is not updatable."); return FLAG_NO_UPDATE; } } // updatable by how many versions int vdiff = libstat.versions.indexOf(maxVersions.first()) - libstat.versions.indexOf(VersionWrapper.valueOf(libVersion).toString()); logger.info(Utils.indent() + ">> Library is updatable by " + vdiff + " versions (" + maxVersions.first() + ")"); return vdiff; } } LibScout-2.3.2/src/de/infsec/tpl/pkg/000077500000000000000000000000001342431362400172525ustar00rootroot00000000000000LibScout-2.3.2/src/de/infsec/tpl/pkg/PackageTree.java000066400000000000000000000454321342431362400223000ustar00rootroot00000000000000/* * Copyright (c) 2015-2017 Erik Derr [derr@cs.uni-saarland.de] * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this * file except in compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package de.infsec.tpl.pkg; import java.io.Serializable; import java.util.*; import de.infsec.tpl.config.LibScoutConfig; import de.infsec.tpl.hashtree.node.PackageNode; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.ibm.wala.classLoader.IClass; import com.ibm.wala.ipa.cha.IClassHierarchy; import de.infsec.tpl.utils.Utils; import de.infsec.tpl.utils.WalaUtils; public class PackageTree implements Serializable { private static final long serialVersionUID = -8286612852897097767L; private static final Logger logger = LoggerFactory.getLogger(de.infsec.tpl.pkg.PackageTree.class); private Node rootNode; public class Node implements Serializable { private static final long serialVersionUID = -2117889548993263279L; public String name; public int clazzCount; public List childs; public Node(String name) { this.name = name; this.clazzCount = 0; this.childs = new ArrayList(); } public int getNumberOfLeafNodes() { int result = 0; for (Node child: childs) if (child.isLeaf()) result++; return result; } @Override public boolean equals(Object obj) { if (!(obj instanceof Node)) return false; return ((Node) obj).name.equals(this.name); } public void print(boolean includeClazzCount) { print("", true, includeClazzCount, drawingCharacters.get(LibScoutConfig.PckgTree.useAsciiRendering)); } final Map drawingCharacters = new HashMap() {{ // unicode box-drawing characters ("└── ", "├── ", "│ ") put(false, new String[]{"\u2514\u2500\u2500 ", "\u251C\u2500\u2500 ", "\u2502 "}); // ascii characters put(true , new String[]{"|___ ", "|--- ", "| "}); }}; private void print(String prefix, boolean isTail, boolean includeClazzCount, final String[] charset) { logger.info(prefix + (isTail ? charset[0] : charset[1]) + name + (includeClazzCount && clazzCount > 0? " (" + clazzCount + ")" : "")); for (int i = 0; i < childs.size(); i++) { childs.get(i).print(prefix + (isTail ? " " : charset[2]), i == childs.size()-1, includeClazzCount, charset); } } @Override public String toString() { return this.name; } public boolean hasClasses() { return this.clazzCount > 0; } public boolean isLeaf() { return childs.isEmpty(); } } /** * Alternative style for a copy constructor, using a static newInstance * method. */ public Node newNodeInstance(Node nNode) { Node n = this.new Node(nNode.name); n.clazzCount = nNode.clazzCount; return n; } public static PackageTree make(IClassHierarchy cha) { return make(cha, false); } public static PackageTree make(IClassHierarchy cha, boolean appClassesOnly) { return make(cha, appClassesOnly, null); } public static PackageTree make(IClassHierarchy cha, boolean appClassesOnly, Set filteredPackages) { PackageTree tree = new PackageTree(); for (IClass clazz: cha) { if (!appClassesOnly || (appClassesOnly && WalaUtils.isAppClass(clazz))) { if (filteredPackages == null || !filteredPackages.contains(PackageUtils.getPackageName(clazz))) tree.update(clazz); } } return tree; } /** * Generate PackageTree with class name references provided as * collection of {@link IClass}, {@link String}, or {@link PackageNode} objects. * @param col Collection of {@link IClass}, {@link String}, or {@link PackageNode} objects * @return {@link PackageTree} instance */ public static PackageTree make(Collection col) { PackageTree tree = new PackageTree(); for (Object o: col) { if (o instanceof IClass) tree.update((IClass) o); else if (o instanceof String) tree.update((String) o, true); else if (o instanceof PackageNode) tree.update(((PackageNode) o).packageName, false); } return tree; } private PackageTree() { this.rootNode = new Node("Root"); } @Override public boolean equals(Object obj) { if (!(obj instanceof PackageTree)) return false; // compare list of all package names PackageTree pt = (PackageTree) obj; return new TreeSet(pt.getAllPackages()).equals(new TreeSet(this.getAllPackages())); } @Override public String toString() { return getRootPackage(); } public void print(boolean includeClazzCount) { logger.info("Root Package: " + (getRootPackage() == null? " - none -" : getRootPackage())); if (rootNode.childs.size() == 1 && !rootNode.hasClasses()) rootNode.childs.get(0).print(includeClazzCount); else rootNode.print(includeClazzCount); } /** * Dump package names that contain at least one class * @return a mapping from package name to number of included classes */ public Map getPackages() { return getPackages(rootNode, "", false); } /** * Dump all package names encoded in the tree * @return an ordered set of package names */ public Set getAllPackages() { return getPackages(rootNode, "", true).keySet(); } public int getNumberOfNonEmptyPackages() { return getPackages().keySet().size(); } public int getNumberOfAppClasses() { Map packages = getPackages(); int count = 0; for (int c: packages.values()) count += c; return count; } /** * Determine root package of the tree (if any). Expands to the longest unique package name. * Note: This method only works for libraries. It's not applicable to apps since there are many different namespaces/libraries involved. * @return the unique root package name or null otherwise */ public String getRootPackage() { String rootPackage = ""; Node curNode = rootNode; // This is another heuristic to determine the proper root package in presence of another lib dependency // whose package name differs at depth 1 or at depth 2 if depth 1 is some common namespace if (rootNode.childs.size() > 1 || (rootNode.childs.size() == 1 && (rootNode.childs.get(0).name.equals("com") || rootNode.childs.get(0).name.equals("de") || rootNode.childs.get(0).name.equals("org")))) { if (rootNode.childs.size() == 1) { curNode = rootNode.childs.get(0); rootPackage += curNode.name; } int id = 0; int max = 0; // determine largest subtree in terms of packages for (int i = 0; i < curNode.childs.size(); i++) { int tmp = getPackages(curNode.childs.get(i), "", true).size(); if (tmp > max) { id = i; max = tmp; } } curNode = curNode.childs.get(id); rootPackage += (rootPackage.isEmpty()? "" : ".") + curNode.name; if (curNode.hasClasses()) return rootPackage.isEmpty()? null : rootPackage; } while (curNode.childs.size() == 1) { curNode = curNode.childs.get(0); rootPackage += (rootPackage.isEmpty()? "" : ".") + curNode.name; if (curNode.hasClasses()) break; } // disallow incomplete root packages of depth 1 that start with common namespace if (rootPackage.equals("com") || rootPackage.equals("de") || rootPackage.equals("org")) { rootPackage = ""; } return rootPackage.isEmpty()? null : rootPackage; } private Map getPackages(Node n, String curPath, boolean dumpAllPackages) { TreeMap res = new TreeMap(); if (n.hasClasses() || dumpAllPackages) res.put(curPath + n.name, n.clazzCount); if (!n.isLeaf()) { for (Node c: n.childs) { res.putAll(getPackages(c, curPath + (n.name.equals("Root")? "" : n.name + "."), dumpAllPackages)); } } return res; } /** * Retrieve longest matching node that does not contain classes * @param packageName * @return */ public String locateRootPackageForPackageName(String packageName) { List struct = PackageUtils.parsePackage(packageName, true); // TODO: check second arg List result = new ArrayList(); Node curNode = rootNode; for (int i = 0; i < struct.size(); i++) { Node n = matchChilds(curNode, struct.get(i)); if (n != null) { if (n.hasClasses()) return Utils.join(result, "."); else { curNode = n; result.add(n.name); } } else { return null; } } return Utils.join(result, "."); } public String assemblePackage(List path) { ArrayList pckgToken = new ArrayList(); for (Node n: path) { pckgToken.add(n.name); } return Utils.join(pckgToken, "."); } public Node locateNodeByPackage(String packageName) { List struct = PackageUtils.parsePackage(packageName, true); // TODO: check second arg Node curNode = rootNode; for (int i = 0; i < struct.size(); i++) { Node n = matchChilds(curNode, struct.get(i)); if (n != null) { curNode = n; } else { return null; } } return curNode; } public boolean containsPackage(String packageName) { return locateNodeByPackage(packageName) != null; } public boolean update(String packageName, boolean includesClazz) { List struct = PackageUtils.parsePackage(packageName, !includesClazz); // TODO: check second arg return update(struct); } public boolean update(IClass clazz) { List struct = PackageUtils.parsePackage(clazz); return update(struct); } private boolean update(List packageStruct) { // update Node curNode = rootNode; if (packageStruct.isEmpty()) curNode.clazzCount++; else { for (int i = 0; i < packageStruct.size(); i++) { Node n = matchChilds(curNode, packageStruct.get(i)); if (n != null) { curNode = n; } else { Node newNode = new Node(packageStruct.get(i)); curNode.childs.add(newNode); curNode = newNode; } if (i == packageStruct.size()-1) { curNode.clazzCount++; } } } return true; } private Node matchChilds(Node n, String str) { for (Node node: n.childs) { if (node.name.equals(str)) return node; } return null; } public void updateTreeClazzCount(IClassHierarchy cha) { Set packages = this.getAllPackages(); for (IClass clazz: cha) { if (WalaUtils.isAppClass(clazz)) { if (packages.contains(PackageUtils.getPackageName(clazz))) { updateClazzCount(clazz); } } } } private boolean updateClazzCount(IClass clazz) { List struct = PackageUtils.parsePackage(clazz); // update Node curNode = rootNode; for (int i = 0; i < struct.size(); i++) { curNode = matchChilds(curNode, struct.get(i)); if (curNode == null) return false; } curNode.clazzCount++; return true; } // match by node name public static Node matchSubTreeByName(PackageTree tree, PackageTree searchTree) { return findSubTreeInTreeByName(tree.rootNode, searchTree.rootNode); } private static Node findSubTreeInTreeByName(Node tree, Node testTree) { logger.trace("[findSubTreeInTree] orig: " + tree.name + " test: "+ testTree.name); if (tree.name.equals(testTree.name)) { logger.trace("[findSubTreeInTree] match children"); if (matchChildrenByName(tree, testTree)) { return tree; } } Node result = null; logger.trace("non-match -> test childs:"); for (Node child : tree.childs) { logger.trace(Utils.INDENT + "- test child: " + child.name); result = findSubTreeInTreeByName(child, testTree); if (result != null) { if (matchChildrenByName(tree, result)) { return result; } } } return result; } private static boolean matchChildrenByName(Node tree, Node testTree) { logger.trace("[matchChildren] orig: " + tree.name + " test: " + testTree.name); if (!tree.name.equals(testTree.name) || (tree.childs.size() < testTree.childs.size())) { return false; } // tests whether the complete testTree is in gameTree, however gameTree can have more packages, but not less boolean result; for (int idx = 0; idx < testTree.childs.size(); idx++) { result = false; for (int origIdx = 0; origIdx < tree.childs.size(); origIdx++) { result = matchChildrenByName(tree.childs.get(origIdx), testTree.childs.get(idx)); if (result == true) break; } if (result == false) return result; } return true; } /** * Match a given tree in another tree by structure only (i.e. number of children, number of leaf children} * @param gameTree the {@PackageTree} to search in * @param searchTree the test {@PackageTree} that should be matched * @return a {@List} of {@PackageTree} in the gameTree that matched the searchTree */ public static List matchSubTree(PackageTree gameTree, PackageTree searchTree) { List pckg = new ArrayList(); // (partial) package name of a gameTree match for (int i = 0; i < 50; i++) pckg.add(""); List result = new ArrayList(); boolean run = true; Set visited = new TreeSet(); List path = new ArrayList(); // current path during recursive descent for (int i = 0; i < 50; i++) path.add(""); // search game tree as long as matching subtrees are found and it is not completely traversed while (run) { run = matchChildren(gameTree.rootNode, searchTree.rootNode, 0, pckg, visited, path); if (run) { String packageName = Utils.join(pckg, "."); logger.debug("[RESULT] pckg: " + pckg +" join: " + packageName); visited.add(packageName); // update list of already found sub trees // reset package struct for (int i = 0; i < 50; i++) { pckg.set(i, ""); path.set(i, ""); } PackageTree pt = gameTree.getCopyOfSubTree(packageName); result.add(pt); logger.debug("DUMP OF SUBTREE:"); if (logger.isDebugEnabled()) pt.print(true); // TODO DEBUG } } return result; } private static boolean matchChildren(final Node gameTree, final Node testTree, final int depth, final List pckg, final Set visited, final List path) { logger.trace(Utils.indent(depth) +"[matchChildren] orig: " + gameTree.name + "(childs: " + gameTree.childs.size() + " leaves: "+ gameTree.getNumberOfLeafNodes() + ")" + " test: " + testTree.name + "(childs: + " + testTree.childs.size() + " leaves: "+ testTree.getNumberOfLeafNodes() + ")"); path.set(depth, gameTree.name); logger.trace(Utils.indent(depth) + " curPckg: " + Utils.join(path, ".") + " visited: " + visited); for (int i = depth+1; i < path.size(); i++) path.set(i, ""); if (visited.contains(Utils.join(path, "."))) { logger.trace(Utils.indent(depth) + "already visited : " + Utils.join(path, ".") + " -- stop"); return false; } // game tree may have more childs but not less if (gameTree.childs.size() < testTree.childs.size()) { return false; } if (gameTree.getNumberOfLeafNodes() < testTree.getNumberOfLeafNodes()) return false; // if number of children and number of children leaf nodes are equal we have a structural match if (gameTree.childs.size() == testTree.childs.size() && gameTree.getNumberOfLeafNodes() == testTree.getNumberOfLeafNodes() && gameTree.childs.size() == gameTree.getNumberOfLeafNodes()) { logger.trace(Utils.indent(depth) + "> same number of children + leaves (" + testTree.getNumberOfLeafNodes() + ")"); // TODO: only add if parent is not single leaf /* if (!gameTree.childs.isEmpty() && gameTree.childs.size() == gameTree.getNumberOfLeafNodes()) { logger.trace(LogConfig.indent(depth) + "[>> match true(" + depth + ")] " + gameTree.name); pckg.set(depth,gameTree.name); }*/ return true; } // tests whether the complete testTree is in gameTree, however gameTree can have more packages, but not less boolean result; for (int idx = 0; idx < testTree.childs.size(); idx++) { result = false; for (int origIdx = 0; origIdx < gameTree.childs.size(); origIdx++) { // save already visisted node names by recursionLevel result = matchChildren(gameTree.childs.get(origIdx), testTree.childs.get(idx), depth+1, pckg, visited, path); if (result == true) break; } if (result == false) return result; } if (gameTree.isLeaf() == testTree.isLeaf()) { logger.trace(Utils.indent(depth) + "[>> match true(" + depth + ")] " + gameTree.name); pckg.set(depth,gameTree.name); return true; } else { return false; } } public PackageTree getSubTree(PackageTree ptree) { return getCopyOfSubTree(ptree.getRootPackage()); } /** * Retrieves a copy of the subtree for a given package name * @param packageName package name without class, e.g. "Root.com.foo.bar" * @return a {@PackageTree} instance for the subtree that matches the provided package name */ public PackageTree getCopyOfSubTree(String packageName) { // parse provided package List fragments = PackageUtils.parsePackage(packageName, true); // TODO recheck second arg, precondition! if (fragments.isEmpty()) return null; PackageTree subTree = new PackageTree(); if (fragments.get(0).equals(this.rootNode.name)) subTree.rootNode = newNodeInstance(this.rootNode); Node curSubTreeNode = subTree.rootNode; Node curNode = this.rootNode; for (int i = 1; i < fragments.size(); i++) { Node n = matchChilds(curNode, fragments.get(i)); if (n == null) return subTree; else { curNode = n; Node newNode = newNodeInstance(curNode); curSubTreeNode.childs.add(newNode); curSubTreeNode = newNode; } } // copy any remaining subtree copySubTree(curNode, curSubTreeNode); return subTree; } /** * Copies the entire subtree from one node to another node * @param fromTreeNode the {@Node} to be copied from * @param toTreeNode the {@Node} to copy to */ public void copySubTree(Node fromTreeNode, Node toTreeNode) { for (Node child: fromTreeNode.childs) { Node copyChild = newNodeInstance(child); if (!child.isLeaf()) copySubTree(child, copyChild); toTreeNode.childs.add(copyChild); } } } LibScout-2.3.2/src/de/infsec/tpl/pkg/PackageUtils.java000066400000000000000000000070701342431362400224750ustar00rootroot00000000000000/* * Copyright (c) 2015-2017 Erik Derr [derr@cs.uni-saarland.de] * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this * file except in compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package de.infsec.tpl.pkg; import java.io.File; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Iterator; import java.util.List; import com.ibm.wala.classLoader.IClass; import de.infsec.tpl.utils.Utils; import de.infsec.tpl.utils.WalaUtils; public class PackageUtils { // Relationship between two packages public static enum RELATIONSHIP { PARENT, CHILD, SIBLING, UNRELATED }; public static List parsePackage(IClass clazz) { String clazzName = WalaUtils.simpleName(clazz); return parsePackage(clazzName); } public static List parsePackage(String name, boolean includeClazz) { String[] struct = name.split("\\."); List result = new ArrayList((List)Arrays.asList(struct)); if (!includeClazz) return result.subList(0, result.size()-1); // exclude clazz Name else return result; } public static List parsePackage(String clazzName) { return parsePackage(clazzName, false); } /** * Transforms a package name com.foo.bar to a file path, i.e. com/foo/bar * @param packageName the package name of the app * @return a {@link File} including the package path */ public static File packageToPath (String packageName) { return new File(Utils.join(parsePackage(packageName, true), File.separator)); } public static String getPackageName(IClass clazz) { return Utils.join(parsePackage(clazz), "."); } public static String getPackageName(String clazzName) { return Utils.join(parsePackage(clazzName), "."); } public static int packageDepth(IClass clazz) { return parsePackage(clazz).size(); } public static int packageDepth(String pckgName) { String[] struct = pckgName.split("\\."); return struct.length; } public static int getMaxDepth(Collection packageNames) { int depth = 0; for (Iterator it = packageNames.iterator(); it.hasNext(); ) { int curDepth = packageDepth(it.next()); if (curDepth > depth) depth = curDepth; } return depth; } public static String getSubPackageOfDepth(String packageName, int depth) { List token = parsePackage(packageName, true); // TODO: recheck second arg if (token.size()-1 >= depth) return Utils.join(token.subList(0, depth), "."); else return null; } /** * Tests relationship of package1 to package2. * @param packageName1 package name without class * @param packageName2 package name without class * @return @{link RELATIONSHIP} */ public static RELATIONSHIP testRelationship(String packageName1, String packageName2) { int p1Depth = packageDepth(packageName1); int p2Depth = packageDepth(packageName2); if (packageName1.startsWith(packageName2) && p1Depth > p2Depth) return RELATIONSHIP.PARENT; else if (packageName2.startsWith(packageName1) && p2Depth > p1Depth) return RELATIONSHIP.CHILD; else if (packageName1.equals(packageName2)) return RELATIONSHIP.SIBLING; else return RELATIONSHIP.UNRELATED; } } LibScout-2.3.2/src/de/infsec/tpl/profile/000077500000000000000000000000001342431362400201315ustar00rootroot00000000000000LibScout-2.3.2/src/de/infsec/tpl/profile/AppProfile.java000066400000000000000000000036271342431362400230450ustar00rootroot00000000000000/* * Copyright (c) 2015-2017 Erik Derr [derr@cs.uni-saarland.de] * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this * file except in compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package de.infsec.tpl.profile; import java.io.Serializable; import java.util.List; import de.infsec.tpl.hashtree.HashTree; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.ibm.wala.ipa.cha.IClassHierarchy; import de.infsec.tpl.pkg.PackageTree; import de.infsec.tpl.utils.Utils; public class AppProfile extends Profile implements Serializable { private static final long serialVersionUID = -1667876249936640164L; private static final Logger logger = LoggerFactory.getLogger(de.infsec.tpl.profile.AppProfile.class); public AppProfile(PackageTree pTree, List hashTrees) { super(pTree, hashTrees); } public static AppProfile create(IClassHierarchy cha) { long startTime = System.currentTimeMillis(); // generate app package tree PackageTree ptree = Profile.generatePackageTree(cha); logger.info("- generated app package tree (in " + Utils.millisecondsToFormattedTime(System.currentTimeMillis() - startTime) + ")"); logger.info(""); // generate app hash trees startTime = System.currentTimeMillis(); List hashTrees = Profile.generateHashTrees(cha); logger.info("- generated app hash trees (in " + Utils.millisecondsToFormattedTime(System.currentTimeMillis() - startTime) + ")"); logger.info(""); return new AppProfile(ptree, hashTrees); } } LibScout-2.3.2/src/de/infsec/tpl/profile/LibProfile.java000066400000000000000000000075031342431362400230300ustar00rootroot00000000000000/* * Copyright (c) 2015-2017 Erik Derr [derr@cs.uni-saarland.de] * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this * file except in compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package de.infsec.tpl.profile; import java.io.Serializable; import java.util.Collection; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import com.github.zafarkhaja.semver.Version; import de.infsec.tpl.hashtree.HashTree; import de.infsec.tpl.pkg.PackageTree; import de.infsec.tpl.utils.Pair; import de.infsec.tpl.utils.VersionWrapper; /** * A LibProfile instance includes any information about a particular version of a library. * This includes parsed meta data as well as generated package/hash-trees. */ public class LibProfile extends Profile implements Serializable { private static final long serialVersionUID = 7810050746806720101L; public static LibProfileComparator comp = new LibProfileComparator(); // basic facts about the library, read from XML file public LibraryDescription description; private boolean isDeprecated = true; // flag to indicate if this is the most current version of a library (according to our DB) public LibProfile(final LibraryDescription desc, final PackageTree packageTree, final List hashTrees) { super(packageTree, hashTrees); this.description = desc; } @Override public String toString() { return this.description.name + " (" + this.description.version + ")"; } public boolean isDeprecatedLib() { return isDeprecated; } public void setIsDeprecatedLib(boolean isDeprecated) { this.isDeprecated = isDeprecated; } // compares library names first then library versions public static class LibProfileComparator implements Comparator { @Override public int compare(LibProfile p0, LibProfile p1) { if (p0.description.name.equals(p1.description.name)) { try { // Compare by version string according to SemVer rules Version v0 = VersionWrapper.valueOf(p0.description.version); Version v1 = VersionWrapper.valueOf(p1.description.version); return v0.compareWithBuildsTo(v1); } catch (Exception e) { // if versions do not adhere to semver rules and cannot be // easily transformed into compliant version string, // do string compare as fallback return p0.description.version.compareTo(p1.description.version); } } return p0.description.name.compareTo(p1.description.name); } } /** * Given a collection of profiles, return distinct libraries with their highest version * @param profiles * @return a {@link Map} containing unique library names -> highest version */ public static Map getUniqueLibraries(Collection profiles) { HashMap result = new HashMap(); for (LibProfile p: profiles) { if (!result.containsKey(p.description.name)) result.put(p.description.name, p.description.version); else { try { Version v1 = VersionWrapper.valueOf(result.get(p.description.name)); Version v2 = VersionWrapper.valueOf(p.description.version); if (v2.greaterThan(v1)) result.put(p.description.name, p.description.version); } catch (Exception e) { /* if at least one version is not semver compliant */ } } } return result; } public Pair getLibIdentifier() { return new Pair(description.name, description.version); } } LibScout-2.3.2/src/de/infsec/tpl/profile/LibraryDescription.java000066400000000000000000000051711342431362400246100ustar00rootroot00000000000000/* * Copyright (c) 2015-2017 Erik Derr [derr@cs.uni-saarland.de] * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this * file except in compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package de.infsec.tpl.profile; import java.io.Serializable; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; import de.infsec.tpl.utils.Utils; public class LibraryDescription implements Serializable { private static final long serialVersionUID = 8175426582245756480L; // library name public final String name; public final LibraryCategory category; // optional version string public final String version; // optional release date public final Date date; // optional comment public final String comment; public static enum LibraryCategory { Advertising, Analytics, Android, /*Tracker,*/ SocialMedia, Cloud, Utilities } public LibraryDescription(String name, LibraryCategory category, String version, Date date, String comment) { this.name = name; this.category = category; this.version = version; this.date = date; this.comment = comment; } public List getDescription() { ArrayList result = new ArrayList(); result.add(Utils.INDENT + " name: " + this.name); result.add(Utils.INDENT + " category: " + this.category); result.add(Utils.INDENT + " version: " + (this.version != null? this.version : " --")); if (this.date != null) { SimpleDateFormat formatter = new SimpleDateFormat("dd.MM.yyyy"); String formattedDate = formatter.format(this.date); result.add(Utils.INDENT + "release-date: " + formattedDate); } else result.add(Utils.INDENT + "release-date: --"); result.add(Utils.INDENT + " comment: " + (this.comment != null? this.comment : " --")); return result; } @Override public String toString() { StringBuilder sb = new StringBuilder("Library Description:\n"); for (String line: getDescription()) { sb.append(line + "\n"); } return sb.toString(); } public String getFormattedDate() { if (this.date != null) { SimpleDateFormat formatter = new SimpleDateFormat("dd.MM.yyyy"); return formatter.format(this.date); } else { return "---"; } } } LibScout-2.3.2/src/de/infsec/tpl/profile/Profile.java000066400000000000000000000061361342431362400224020ustar00rootroot00000000000000/* * Copyright (c) 2015-2017 Erik Derr [derr@cs.uni-saarland.de] * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this * file except in compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package de.infsec.tpl.profile; import java.io.File; import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import de.infsec.tpl.hashtree.HashTree; import de.infsec.tpl.modules.libprofiler.LibraryProfiler; import org.apache.commons.cli.ParseException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.ibm.wala.ipa.cha.IClassHierarchy; import de.infsec.tpl.pkg.PackageTree; import de.infsec.tpl.utils.Utils; public abstract class Profile implements Serializable { private static final long serialVersionUID = 4112271380387644511L; private static final Logger logger = LoggerFactory.getLogger(de.infsec.tpl.profile.Profile.class); // Package structure of the library|app public PackageTree packageTree; public List hashTrees; protected Profile(PackageTree pTree, List hashTrees) { this.packageTree = pTree; this.hashTrees = hashTrees; } public static PackageTree generatePackageTree(IClassHierarchy cha) { logger.info("= PackageTree ="); PackageTree tree = PackageTree.make(cha, true); tree.print(true); logger.debug(""); logger.debug("Package names (included classes):"); Map pTree = tree.getPackages(); for (String pkg: pTree.keySet()) logger.debug(Utils.INDENT + pkg + " (" + pTree.get(pkg) + ")"); logger.info(""); return tree; } public static List generateHashTrees(final IClassHierarchy cha) { HashTree ht = new HashTree(); ht.generate(cha); return Collections.singletonList(ht); } public static List loadLibraryProfiles(File profilesDir) throws ParseException { long s = System.currentTimeMillis(); List profiles = new ArrayList(); logger.info("Load library profiles:"); try { // de-serialize library profiles for (File f : Utils.collectFiles(profilesDir, new String[]{LibraryProfiler.FILE_EXT_LIB_PROFILE})) { LibProfile lp = (LibProfile) Utils.disk2Object(f); profiles.add(lp); } profiles.sort(LibProfile.comp); logger.info(Utils.indent() + "Loaded " + profiles.size() + " profiles in " + Utils.millisecondsToFormattedTime(System.currentTimeMillis() - s)); logger.info(""); } catch (ClassNotFoundException e) { throw new ParseException("Could not load profiles: " + Utils.stacktrace2Str(e)); } if (profiles.isEmpty()) { throw new ParseException("No profiles found in " + profilesDir + ". Check your settings!"); } return profiles; } } LibScout-2.3.2/src/de/infsec/tpl/profile/ProfileMatch.java000066400000000000000000000230601342431362400233520ustar00rootroot00000000000000/* * Copyright (c) 2015-2017 Erik Derr [derr@cs.uni-saarland.de] * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this * file except in compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package de.infsec.tpl.profile; import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Set; import java.util.TreeSet; import de.infsec.tpl.config.LibScoutConfig; import de.infsec.tpl.hashtree.HashTree; import de.infsec.tpl.hashtree.TreeConfig; import de.infsec.tpl.hashtree.node.PackageNode; import de.infsec.tpl.stats.Exportable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import de.infsec.tpl.pkg.PackageTree; import de.infsec.tpl.utils.Utils; public class ProfileMatch implements Exportable, Serializable { private static final long serialVersionUID = 62089083096037815L; private static final Logger logger = LoggerFactory.getLogger(de.infsec.tpl.profile.ProfileMatch.class); public static final float MIN_PARTIAL_MATCHING_SCORE = .7f; public static final float MIN_PCKG_SCORE = .3f; public static final float MIN_CLAZZ_SCORE = .33f; public static final float MIN_CLAZZ_APP_SCORE = .2f; public enum MatchLevel {PACKAGE, CLASS, METHOD}; public LibProfile lib; // if lib code usage analysis is enabled, store normalized (i.e. if matched root packages differs from original root package) lib method signatures used public Set usedLibMethods = new TreeSet(); private PackageTree matchedPackageTree; public boolean libRootPackagePresent; // special sim scores public static final float MATCH_HTREE_FULL = 1f; public static final float MATCH_HTREE_NONE = 0f; public static final float MATCH_HTREE_NO_ROOT_PCKG = -1f; // stores the results for each HashTree comparison private List results; public class HTreeMatch { public TreeConfig config; // identifier - config public List matchingNodes; // list of package nodes that matched (partially) public Float simScore = MATCH_HTREE_NONE; // between 0f..1f for partial match // if we have a partial match, we need the partition public String rootPackage; public HTreeMatch(TreeConfig config) { this.config = config; } public boolean isFullMatch() { return simScore == MATCH_HTREE_FULL; } public boolean isPartialMatch() { return simScore > MATCH_HTREE_NONE && simScore < MATCH_HTREE_FULL; } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("Config: " + config + "\n"); if (isFullMatch()) sb.append(" - full match"); else if (isPartialMatch()) sb.append(" - partial match: root package: " + rootPackage + " simScore: " + simScore); else if (simScore == MATCH_HTREE_NONE) sb.append(" - no match -"); return sb.toString(); } } public class Export { public final String libName; public final String libVersion; public final boolean isOriginalPackageName; public final String libRootPackage; public final boolean includesSecurityVulnerability; public final boolean includesSecurityVulnerabilityFix; public float simScore = 0f; // 1f if exact match public final String comment; // if lib code usage analysis is enabled, store normalized (i.e. if matched root packages differs from original root package) lib method signatures used public Set usedLibMethods = new TreeSet(); public Export(ProfileMatch pm) { this.libName = pm.lib.description.name; this.libVersion = pm.lib.description.version; this.isOriginalPackageName = !pm.isLibObfuscated(); this.libRootPackage = pm.getMatchedPackageTree() == null ? "" : pm.getMatchedPackageTree().getRootPackage() == null? "" : pm.getMatchedPackageTree().getRootPackage(); this.includesSecurityVulnerability = pm.lib.description.comment.contains("[SECURITY]"); this.includesSecurityVulnerabilityFix = pm.lib.description.comment.contains("[SECURITY-FIX]"); this.simScore = pm.getHighestSimScore().simScore; this.comment = LibScoutConfig.Reporting.showComments? pm.lib.description.comment : ""; if (!pm.usedLibMethods.isEmpty()) this.usedLibMethods = pm.usedLibMethods; } } @Override public Export export() { return new Export(this); } public void addResult(HTreeMatch res) { results.add(res); Collections.sort(results, SIM_SCORE_COMPARATOR); // ensure that results are sorted } public HTreeMatch getResultByConfig(TreeConfig cfg) { for (HTreeMatch htm: results) if (htm.config.equals(cfg)) return htm; return null; } public List getBestResults(int topX) { int upperLimit = Math.min(topX, results.size()); return results.subList(0, upperLimit); } public void printResults(int topX) { logger.info("Profile results:"); Collections.sort(results, new SimScoreComparator()); for (int i = 0; i < Math.min(topX, results.size()); i++) { if (results.get(i).simScore > MATCH_HTREE_NONE) logger.info("- " + results.get(i)); } } public HTreeMatch createResult(TreeConfig config) { return new HTreeMatch(config); } public ProfileMatch(LibProfile lib) { this.results = new ArrayList(); this.lib = lib; } public void print() { final String VULN_INDICATOR = "[SECURITY]"; // indicates that this version includes a security vulnerability final String VULN_FIX_INDICATOR = "[SECURITY-FIX]"; // indicates that this version includes a fix for a security vulnerability for (String str: lib.description.getDescription()) { if (str.contains("comment:") && (!LibScoutConfig.Reporting.showComments && !(str.contains(VULN_INDICATOR) || str.contains(VULN_FIX_INDICATOR)))) continue; if (str.contains("version") && lib.isDeprecatedLib()) str += " [OLD VERSION]"; // TODO store in db? logger.info(Utils.INDENT + str); } logger.info(Utils.INDENT + "lib root package: \"" + lib.packageTree.getRootPackage() + "\"" + (isLibObfuscated()? " (in app: \"" + getMatchedPackageTree().getRootPackage() + "\")" : "")); if (logger.isDebugEnabled() && !usedLibMethods.isEmpty()) { logger.debug(""); logger.debug(Utils.INDENT + "- used library methods in app -"); for (String sig: usedLibMethods) { logger.debug(Utils.INDENT2 + "- method: " + sig); } logger.debug(""); } if (logger.isTraceEnabled()) getMatchedPackageTree().print(false); logger.info(""); } public boolean doAllConfigsMatch() { // TODO to be deprecated for (HTreeMatch htm: results) { if (!htm.isFullMatch()) return false; } return true; } public boolean isMatch() { for (HTreeMatch htm: results) { if (htm.isFullMatch()) // at least one exact match return true; } return false; } public boolean isPartialMatch() { if (isMatch()) return false; for (HTreeMatch htm: results) { if (htm.isPartialMatch()) // at least one partial match return true; } return false; } public List getMatchedConfigs() { ArrayList result = new ArrayList<>(); for (HTreeMatch htm: results) { if (htm.isFullMatch()) result.add(htm.config); } return result; } public List getPartiallyMatchedConfigs() { ArrayList result = new ArrayList<>(); for (HTreeMatch htm: results) { if (htm.isPartialMatch()) result.add(htm.config); } return result; } public HTreeMatch getHighestSimScore() { return results.isEmpty()? null : results.get(0); } public PackageTree getMatchedPackageTree() { if (isMatch()) { if (matchedPackageTree == null) { for (HTreeMatch htm: results) { if (htm.isFullMatch()) { matchedPackageTree = PackageTree.make(htm.matchingNodes); break; } } } return matchedPackageTree; } return null; } /** * Compares the matched app package node identifier with the library package node identifier * If both (sorted) lists match, the library is not (re-)obfuscated * @return false, if lib only matches partially or all package names match, true otherwise */ public boolean isLibObfuscated() { for (HTreeMatch htm: results) { if (htm.isFullMatch()) { // take first exact match HashTree libHTree = lib.hashTrees.get(0); // TODO multiple trees? return !comparePackageNodes(new ArrayList<>(libHTree.getPackageNodes()), htm.matchingNodes); } } return false; } static boolean comparePackageNodes(List c1, List c2) { Comparator pckgComp = new Comparator() { @Override public int compare(PackageNode pn1, PackageNode pn2) { return pn1.packageName.compareTo(pn2.packageName); } }; c1.sort(pckgComp); c2.sort(pckgComp); int max = Math.min(c1.size(), c2.size()); for (int i = 0; i < max; i++) { if (!c1.get(i).packageName.equals(c2.get(i).packageName)) return false; } return true; } private class SimScoreComparator implements Comparator { @Override public int compare(HTreeMatch m1, HTreeMatch m2) { return Float.compare(m2.isFullMatch()? 1f : m2.simScore, m1.isFullMatch()? 1f : m1.simScore); // sort descending } } public final SimScoreComparator SIM_SCORE_COMPARATOR = new SimScoreComparator(); } LibScout-2.3.2/src/de/infsec/tpl/profile/SerializableProfileMatch.java000066400000000000000000000076431342431362400257120ustar00rootroot00000000000000/* * Copyright (c) 2015-2017 Erik Derr [derr@cs.uni-saarland.de] * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this * file except in compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package de.infsec.tpl.profile; import java.util.ArrayList; import java.util.List; import java.util.Set; import java.util.TreeSet; import java.io.Serializable; import de.infsec.tpl.hash.HashTreeOLD.Config; import de.infsec.tpl.hash.HashTreeOLD.HashAlgorithm; import de.infsec.tpl.utils.Pair; /* DEPRECATED - DO NOT USE ANY LONGER */ public class SerializableProfileMatch implements Serializable { private static final long serialVersionUID = -6375317863324765307L; public static final int MATCH_ALL_CONFIGS = 10000; public static final int MATCH_SOME_CONFIGS = 1000; public static final int MATCH_PARTIAL = 100; public static final int MATCH_PACKAGENAME = 10; public static final int MATCH_NONE = 0; public final String libName; public final String libVersion; public final boolean isLibObfuscated; public final boolean libRootPackagePresent; public final boolean includesSecurityVulnerability; public final boolean includesSecurityVulnerabilityFix; public final int matchLevel; public float simScore = 0f; // 1f if exact match public List matchedConfigs; // only if some configs match // if lib code usage analysis is enabled, store normalized (i.e. if matched root packages differs from original root package) lib method signatures used public Set usedLibMethods = new TreeSet(); public SerializableProfileMatch(ProfileMatch pm) { this.libName = pm.lib.description.name; this.libVersion = pm.lib.description.version; this.isLibObfuscated = pm.isLibObfuscated(); this.libRootPackagePresent = pm.libRootPackagePresent; this.includesSecurityVulnerability = pm.lib.description.comment.contains("[SECURITY]"); this.includesSecurityVulnerabilityFix = pm.lib.description.comment.contains("[SECURITY-FIX]"); if (pm.doAllConfigsMatch()) { this.matchLevel = MATCH_ALL_CONFIGS; this.simScore = 1f; } else if (pm.isPartialMatch()) { this.simScore = pm.getHighestSimScore().simScore; if (this.simScore > .9f) this.matchLevel = MATCH_SOME_CONFIGS; /// TODO RENAME else if (this.simScore > .6f && this.simScore < .9f) this.matchLevel = MATCH_PARTIAL; else this.matchLevel = MATCH_NONE; } else { this.matchLevel = MATCH_NONE; } if (!pm.usedLibMethods.isEmpty()) this.usedLibMethods = pm.usedLibMethods; } public Pair getLibIdentifier() { return new Pair(libName,libVersion); } public int numberOfMatchingConfigs() { // only relevant if some configs match return this.matchLevel >> 3 == 1? this.matchLevel - MATCH_SOME_CONFIGS : 0; } public List config2Serializable(List list) { ArrayList result = new ArrayList(); for (Config cfg: list) result.add(new SerializableConfig(cfg)); return result; } public class SerializableConfig implements Serializable { private static final long serialVersionUID = -3706418622137317023L; public final boolean filterDups; public final int accessFlagFilter; public final boolean filterInnerClasses; public final HashAlgorithm hashAlgorithm; public SerializableConfig(Config cfg) { this.filterDups = cfg.filterDups; this.accessFlagFilter = cfg.accessFlagsFilter; this.filterInnerClasses = cfg.filterInnerClasses; this.hashAlgorithm = cfg.hashAlgorithm; } } } LibScout-2.3.2/src/de/infsec/tpl/resourceparser/000077500000000000000000000000001342431362400215355ustar00rootroot00000000000000LibScout-2.3.2/src/de/infsec/tpl/resourceparser/AndroidView.java000066400000000000000000000061551342431362400246220ustar00rootroot00000000000000/* * Copyright (c) 2015-2017 Erik Derr [derr@cs.uni-saarland.de] * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this * file except in compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package de.infsec.tpl.resourceparser; import java.util.Collection; import com.ibm.wala.classLoader.IClass; import de.infsec.tpl.utils.Utils; import de.infsec.tpl.utils.WalaUtils; /** * Data class representing a android view/widget/layout container */ public class AndroidView { protected final int id; protected final IClass viewClazz; protected final String layoutFile; private boolean isSensitive; public AndroidView(int id, String layoutFile, IClass viewClass) { this.id = id; this.layoutFile = layoutFile; this.viewClazz = viewClass; } public AndroidView(int id, String layoutFile, IClass viewClass, boolean isSensitive) { this(id, layoutFile, viewClass); this.isSensitive = isSensitive; } public int getID() { return this.id; } public String getLayoutFile() { return this.layoutFile; } public IClass getViewClass() { return this.viewClazz; } public boolean isAppView() { return viewClazz == null? false : WalaUtils.isApplicationClass(viewClazz); } public void setIsSensitive(boolean isSensitive) { this.isSensitive = isSensitive; } public boolean isSensitive() { return this.isSensitive; } @Override public String toString() { return toShort() + " isSensitive: " + isSensitive; } public String toShort() { return "User control(" + id + ") in file: " + layoutFile + " type: " + (viewClazz == null? "unknown" : viewClazz.getName().toString()); } public static Collection getAppViews(Collection views) { Collection appViews = Utils.filter(views, new Utils.IPredicate() { @Override public boolean apply(AndroidView type) { return type.isAppView(); } }); return appViews; } public static Collection getSensitiveViews(Collection views) { Collection appViews = Utils.filter(views, new Utils.IPredicate() { @Override public boolean apply(AndroidView type) { return type.isSensitive(); } }); return appViews; } public static Collection getSensitiveAppViews(Collection views) { Collection appViews = Utils.filter(views, new Utils.IPredicate() { @Override public boolean apply(AndroidView type) { return type.isSensitive && type.isAppView(); } }); return appViews; } public static AndroidView findViewById(Collection views, int resId) { for (AndroidView view: views) { if (view.id == resId) return view; } return null; } } LibScout-2.3.2/src/de/infsec/tpl/resourceparser/FragmentLayoutControl.java000066400000000000000000000027511342431362400267070ustar00rootroot00000000000000/* * Copyright (c) 2015-2017 Erik Derr [derr@cs.uni-saarland.de] * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this * file except in compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package de.infsec.tpl.resourceparser; import com.ibm.wala.classLoader.IClass; /** * Data class representing a fragment layout control * */ public class FragmentLayoutControl extends AndroidView { private final IClass fragmentClazz; public FragmentLayoutControl(int id, String layoutFile, IClass viewClazz, IClass fragmentClazz) { super(id, layoutFile, viewClazz); this.fragmentClazz = fragmentClazz; } public void setIsSensitive(boolean isSensitive) { throw new UnsupportedOperationException(); } public boolean isSensitive() { return false; } public String getLayoutFile() { return this.layoutFile; } public IClass getFragmentClass() { return this.fragmentClazz; } @Override public String toString() { return "Fragment(" + id + ") in file: " + layoutFile + " clazz: " + (fragmentClazz == null? "null" : fragmentClazz.getName().toString()); } } LibScout-2.3.2/src/de/infsec/tpl/resourceparser/LayoutFileParser.java000066400000000000000000000402301342431362400256310ustar00rootroot00000000000000/* * Copyright (c) 2015-2017 Erik Derr [derr@cs.uni-saarland.de] * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this * file except in compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package de.infsec.tpl.resourceparser; import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.ibm.wala.classLoader.IClass; import com.ibm.wala.ipa.cha.IClassHierarchy; import com.ibm.wala.types.ClassLoaderReference; import com.ibm.wala.types.TypeReference; import de.infsec.tpl.utils.AndroidClassType; import de.infsec.tpl.utils.MapUtils; import de.infsec.tpl.utils.Utils; import de.infsec.tpl.utils.WalaUtils; import pxb.android.axml.AxmlReader; import pxb.android.axml.AxmlVisitor; import pxb.android.axml.AxmlVisitor.NodeVisitor; import soot.jimple.infoflow.android.resources.ARSCFileParser; import soot.jimple.infoflow.android.resources.ARSCFileParser.AbstractResource; import soot.jimple.infoflow.android.resources.ARSCFileParser.StringResource; import soot.jimple.infoflow.android.resources.AbstractResourceParser; import soot.jimple.infoflow.android.resources.IResourceHandler; /** * Parser for analyzing the layout XML files inside an android application * * @author Steven Arzt * @author Erik Derr * */ public class LayoutFileParser extends AbstractResourceParser { private static final Logger logger = LoggerFactory.getLogger(de.infsec.tpl.resourceparser.LayoutFileParser.class); private final Map androidViews = new HashMap(); // control res id to android view private final Map> fragments = new HashMap>(); // maps a layout filename to a fragment layout control private final Map> callbackMethods = new HashMap>(); // layout file name -> method names private final Map> includeDependencies = new HashMap>(); private final String packageName; private final ARSCFileParser resParser; private final static int TYPE_NUMBER_VARIATION_PASSWORD = 0x00000010; private final static int TYPE_TEXT_VARIATION_PASSWORD = 0x00000080; private final static int TYPE_TEXT_VARIATION_VISIBLE_PASSWORD = 0x00000090; private final static int TYPE_TEXT_VARIATION_WEB_PASSWORD = 0x000000e0; public LayoutFileParser(String packageName, ARSCFileParser resParser) { this.packageName = packageName; this.resParser = resParser; } private IClass getLayoutClass(IClassHierarchy cha, String clazzName) { // This is due to the fault-tolerant xml parser if (clazzName.equals("view")) clazzName = "View"; IClass iclazz = null; if (iclazz == null) iclazz = cha.lookupClass(TypeReference.findOrCreate(ClassLoaderReference.Application, Utils.convertToBrokenDexBytecodeNotation(clazzName))); if (iclazz == null && !packageName.isEmpty()) iclazz = cha.lookupClass(TypeReference.findOrCreate(ClassLoaderReference.Application, Utils.convertToBrokenDexBytecodeNotation(packageName + "." + clazzName))); if (iclazz == null) iclazz = cha.lookupClass(TypeReference.findOrCreate(ClassLoaderReference.Application, Utils.convertToBrokenDexBytecodeNotation("android.widget." + clazzName))); if (iclazz == null) iclazz = cha.lookupClass(TypeReference.findOrCreate(ClassLoaderReference.Application, Utils.convertToBrokenDexBytecodeNotation("android.webkit." + clazzName))); if (iclazz == null) iclazz = cha.lookupClass(TypeReference.findOrCreate(ClassLoaderReference.Application, Utils.convertToBrokenDexBytecodeNotation("android.view." + clazzName))); // PreferenceScreen, PreferenceCategory, (i)shape, item, selector, scale, corners, solid .. tags are no classes and thus there will be no corresponding layout class if (iclazz == null) logger.trace(Utils.INDENT + "Could not find layout class " + clazzName); return iclazz; } private class IncludeParser extends NodeVisitor { private final String layoutFile; public IncludeParser(String layoutFile) { this.layoutFile = layoutFile; } @Override public void attr(String ns, String name, int resourceId, int type, Object obj) { // Is this the target file attribute? String tname = name.trim(); if (tname.equals("layout")) { if (type == AxmlVisitor.TYPE_REFERENCE && obj instanceof Integer) { // We need to get the target XML file from the binary manifest AbstractResource targetRes = resParser.findResource((Integer) obj); if (targetRes == null) { logger.trace(Utils.INDENT + "Target resource " + obj + " for layout include not found"); return; } if (!(targetRes instanceof StringResource)) { logger.trace(Utils.INDENT + "Invalid target node for include tag in layout XML, was " + targetRes.getClass().getName()); return; } String targetFile = ((StringResource) targetRes).getValue(); // If we have already processed the target file, we can // simply copy the callbacks we have found there if (callbackMethods.containsKey(targetFile)) for (String callback : callbackMethods.get(targetFile)) addCallbackMethod(layoutFile, callback); else { // We need to record a dependency to resolve later MapUtils.addToSet(includeDependencies, targetFile, layoutFile); } } } super.attr(ns, name, resourceId, type, obj); } } /** * Adds a callback method found in an XML file to the result set * @param layoutFile The XML file in which the callback has been found * @param callback The callback found in the given XML file */ private void addCallbackMethod(String layoutFile, String callback) { MapUtils.addToSet(callbackMethods, layoutFile, callback); // Recursively process any dependencies we might have collected before // we have processed the target if (includeDependencies.containsKey(layoutFile)) for (String target : includeDependencies.get(layoutFile)) addCallbackMethod(target, callback); } private class FragmentParser extends LayoutParser { private IClass fragmentClazz = null; private Integer id = -1; public FragmentParser(IClassHierarchy cha, String layoutFile, IClass viewClazz) { super(cha, layoutFile, viewClazz); } @Override public void attr(String ns, String name, int resourceId, int type, Object obj) { String tname = name.trim(); if (tname.equals("id") && type == AxmlVisitor.TYPE_REFERENCE) this.id = (Integer) obj; else if ((tname.equals("name") || tname.equals("class") && type == AxmlVisitor.TYPE_STRING && obj instanceof String)) { String className = ((String) obj).trim(); if (className.startsWith(".")) { logger.debug("Fragment attr parser:: \"" + tname + "\" contains leading dot: " + className); className = className.substring(1); // TODO: sometimes the parser adds a leading "." } // weird we had sth. like "5apperfection.bluebox.ui.fragments.DeviceLinksFragment" although the file included the string "apperfection.bluebox.ui.fragments.DeviceLinksFragment" while (!className.substring(0, 1).matches("[a-zA-Z]")) { logger.debug("Fragment attr parser:: \"" + tname + "\" starts with a non-letter character!: " + className + " fixing.."); className = className.substring(1); } try { fragmentClazz = WalaUtils.lookupClass(cha, className); } catch (ClassNotFoundException e) { logger.warn("Could not lookup IClass for Fragment " + className); } } super.attr(ns, name, resourceId, type, obj); } @Override public void end() { if (id > 0) MapUtils.addValue(fragments, layoutFile, new FragmentLayoutControl(id, layoutFile, clazz, fragmentClazz)); id = -1; } } private class LayoutParser extends NodeVisitor { protected final IClassHierarchy cha; protected final String layoutFile; protected final IClass clazz; private Integer id = -1; private boolean isSensitive = false; public LayoutParser(IClassHierarchy cha, String layoutFile, IClass clazz) { this.cha = cha; this.layoutFile = layoutFile; this.clazz = clazz; } @Override public NodeVisitor child(String ns, String name) { if (name == null || name.isEmpty()) { logger.trace(Utils.INDENT + "Encountered a null node name or empty node name " + "in file " + layoutFile + ", skipping node..."); return null; } String tname = name.trim(); if (tname.equals("include")) /// TODO NOT SURE IF THIS IS CORRECT, include can occur in the middle of the file, anything afterwards seems not to be parsed anymore return new IncludeParser(layoutFile); // For layout defined fragments we need the class name that is either specified via the name- or class-tag if (tname.equals("fragment")) return new FragmentParser(cha, layoutFile, clazz); // The "merge" tag merges the next hierarchy level into the current // one for flattening hierarchies. if (tname.equals("merge")) return new LayoutParser(cha, layoutFile, clazz); final IClass childClass = getLayoutClass(cha, tname); if (childClass != null && (WalaUtils.classifyClazz(childClass) == AndroidClassType.LayoutContainer || WalaUtils.classifyClazz(childClass) == AndroidClassType.View)) return new LayoutParser(cha, layoutFile, childClass); else return super.child(ns, name); } private boolean isAndroidNamespace(String ns) { if (ns == null) return false; ns = ns.trim(); if (ns.startsWith("*")) ns = ns.substring(1); if (!ns.equals("http://schemas.android.com/apk/res/android")) return false; return true; } @Override public void attr(String ns, String name, int resourceId, int type, Object obj) { // Check that we're actually working on an android attribute if (!isAndroidNamespace(ns)) return; String tname = name.trim(); if (tname.equals("id") && type == AxmlVisitor.TYPE_REFERENCE) this.id = (Integer) obj; else if (tname.equals("password") && type == AxmlVisitor.TYPE_INT_BOOLEAN) isSensitive = ((Integer) obj) != 0; // -1 for true, 0 for false else if (!isSensitive && tname.equals("inputType") && type == AxmlVisitor.TYPE_INT_HEX) { int tp = (Integer) obj; isSensitive = ((tp & TYPE_NUMBER_VARIATION_PASSWORD) == TYPE_NUMBER_VARIATION_PASSWORD) || ((tp & TYPE_TEXT_VARIATION_PASSWORD) == TYPE_TEXT_VARIATION_PASSWORD) || ((tp & TYPE_TEXT_VARIATION_VISIBLE_PASSWORD) == TYPE_TEXT_VARIATION_VISIBLE_PASSWORD) || ((tp & TYPE_TEXT_VARIATION_WEB_PASSWORD) == TYPE_TEXT_VARIATION_WEB_PASSWORD); } else if (isActionListener(tname) && type == AxmlVisitor.TYPE_STRING && obj instanceof String) { String strData = ((String) obj).trim(); addCallbackMethod(layoutFile, strData); } else { if (type == AxmlVisitor.TYPE_STRING) logger.trace(Utils.INDENT + "Found unrecognized XML attribute: " + tname); } super.attr(ns, name, resourceId, type, obj); } /** * Checks whether this name is the name of a well-known Android listener * attribute. This is a function to allow for future extension. * @param name The attribute name to check. This name is guaranteed to * be in the android namespace. * @return True if the given attribute name corresponds to a listener, * otherwise false. */ private boolean isActionListener(String name) { return name.equals("onClick"); } @Override public void end() { if (id > 0) // filter views that do not have an Android id androidViews.put(id, new AndroidView(id, layoutFile, clazz, isSensitive)); } } /** * Parses all layout XML files in the given APK file and loads the IDs of * the user controls in it. * @param fileName The APK file in which to look for user controls */ public void parseLayoutFile(final IClassHierarchy cha, final String fileName) { handleAndroidResourceFiles(fileName, /*classes,*/ null, new IResourceHandler() { @Override public void handleResourceFile(final String fileName, Set fileNameFilter, InputStream stream) { // we only process valid layout XML files if (!(fileName.startsWith("res/layout") && fileName.endsWith(".xml"))) { return; } // Get the fully-qualified class name String entryClass = fileName.substring(0, fileName.lastIndexOf(".")); if (!packageName.isEmpty()) entryClass = packageName + "." + entryClass; // Filter files if desired if (fileNameFilter != null) { boolean found = false; for (String s : fileNameFilter) if (s.equalsIgnoreCase(entryClass)) { found = true; break; } if (!found) return; } try { ByteArrayOutputStream bos = new ByteArrayOutputStream(); int in; while ((in = stream.read()) >= 0) bos.write(in); bos.flush(); byte[] data = bos.toByteArray(); if (data == null || data.length == 0) // File empty? return; AxmlReader rdr = new AxmlReader(data); rdr.accept(new AxmlVisitor() { @Override public NodeVisitor first(String ns, String name) { if (name == null) return new LayoutParser(cha, fileName, null); final String tname = name.trim(); final IClass clazz; if (tname.isEmpty() || tname.equals("merge") || tname.equals("include")) clazz = null; else clazz = getLayoutClass(cha, tname); if (clazz == null || (clazz != null && WalaUtils.classifyClazz(clazz) == AndroidClassType.LayoutContainer)) return new LayoutParser(cha, fileName, clazz); else return super.first(ns, name); } }); } catch (Exception ex) { logger.warn("Could not read binary XML file (" + fileName + "): " + ex.getMessage()); ex.printStackTrace(); } } }); } /** * Gets all fragments defined in layout XML files. The result is a * mapping from layout file name to the respective fragment layout control. * @return The fragments found in XML files. */ public Map> getFragments() { return this.fragments; } /** * Gets the views/widgets/layout container found in the layout XML file. The result is a * mapping from the id to the respective layout control. * @return The layout controls found in the XML file. */ public Map getAndroidViews() { return this.androidViews; } /** * Gets the callback methods found in the layout XML file. The result is a * mapping from the file name to the set of found callback methods. * @return The callback methods found in the XML file. */ public Map> getCallbackMethods() { return this.callbackMethods; } } LibScout-2.3.2/src/de/infsec/tpl/stats/000077500000000000000000000000001342431362400176275ustar00rootroot00000000000000LibScout-2.3.2/src/de/infsec/tpl/stats/AppStats.java000066400000000000000000000114671342431362400222420ustar00rootroot00000000000000/* * Copyright (c) 2015-2017 Erik Derr [derr@cs.uni-saarland.de] * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this * file except in compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package de.infsec.tpl.stats; import java.io.File; import java.util.*; import de.infsec.tpl.hashtree.HashTree; import de.infsec.tpl.manifest.ProcessManifest; import de.infsec.tpl.pkg.PackageTree; import de.infsec.tpl.profile.LibProfile; import de.infsec.tpl.profile.ProfileMatch; public class AppStats implements Exportable { public File appFile; public ProcessManifest manifest; public boolean isMultiDex; public PackageTree pTree; public List appHashTrees; public List profiles; public List pMatches; public Map packageOnlyMatches = new TreeMap(); // lib name -> root package public long processingTime; public AppStats(File appFile) { this.appFile = appFile; } @Override public Export export() { return new Export(this); } private class Export { class AppInfo { String fileName; String appName; String packagename; Set permissions; int versionCode; int versionMinSDK; int versionTargetSDK; String sharedUserId; } AppInfo appInfo = new AppInfo(); int stats_packageCount; int stats_classCount; long stats_processingTime; // libraries detected via profiles List lib_matches; // includes only lib names (and their packages) that are not matched via profiles Map lib_packageOnlyMatches = new TreeMap(); public Export(AppStats stats) { this.appInfo.fileName = stats.appFile.getName(); this.appInfo.appName = stats.manifest.getApplicationName(); this.appInfo.packagename = stats.manifest.getPackageName(); this.appInfo.permissions = stats.manifest.getPermissions(); this.appInfo.versionCode = stats.manifest.getVersionCode(); this.appInfo.versionMinSDK = stats.manifest.getMinSdkVersion(); this.appInfo.versionTargetSDK = stats.manifest.getTargetSdkVersion(); this.appInfo.sharedUserId = stats.manifest.getSharedUserId(); this.stats_packageCount = stats.pTree.getNumberOfNonEmptyPackages(); this.stats_classCount = stats.pTree.getNumberOfAppClasses(); this.stats_processingTime = stats.processingTime; this.lib_matches = new ArrayList(); /* * - only save profiles that at least match partially * - if multiple profiles of the same library match (at least partially), only export the one(s) with the highest score */ Set libsMatched = new HashSet(); // LibName -> List of ProfileMatches with highest sim scores HashMap> exportedPMatches = new HashMap>(); for (ProfileMatch pm: stats.pMatches) { if (pm.getHighestSimScore() != null && pm.getHighestSimScore().simScore > ProfileMatch.MATCH_HTREE_NONE) { String libName = pm.lib.description.name; if (!exportedPMatches.containsKey(libName)) { // initialize list and add pm exportedPMatches.put(libName, new ArrayList()); exportedPMatches.get(libName).add(pm); } else { // check if we have to add this pm to existing list ProfileMatch firstPM = exportedPMatches.get(libName).get(0); if (firstPM.getHighestSimScore() != null && firstPM.getHighestSimScore().simScore.floatValue() < pm.getHighestSimScore().simScore.floatValue()) { // replace list exportedPMatches.get(libName).clear(); exportedPMatches.get(libName).add(pm); } else if (firstPM.getHighestSimScore() != null && firstPM.getHighestSimScore().simScore.floatValue() == pm.getHighestSimScore().simScore.floatValue()) { // add to existing list exportedPMatches.get(libName).add(pm); } } libsMatched.add(libName); } } // save the PM's that are to be exported for (String libName: exportedPMatches.keySet()) { for (ProfileMatch pm: exportedPMatches.get(libName)) { this.lib_matches.add(pm.export()); libsMatched.add(pm.lib.description.name); } } // save all library names that did not match via profiles but via root package name for (String matchedLibPckg: stats.packageOnlyMatches.keySet()) if (!libsMatched.contains(matchedLibPckg)) { this.lib_packageOnlyMatches.put(matchedLibPckg, stats.packageOnlyMatches.get(matchedLibPckg)); } } } } LibScout-2.3.2/src/de/infsec/tpl/stats/Exportable.java000066400000000000000000000005141342431362400225770ustar00rootroot00000000000000package de.infsec.tpl.stats; /** * Classes that implement this interface * need to return a custom object with class data * that is serializable/exportable via json. * * For this purpose, we create in these classes * an inner class Export that is returned by export() */ public interface Exportable { Object export(); } LibScout-2.3.2/src/de/infsec/tpl/stats/SerializableAppStats.java000066400000000000000000000074321342431362400245660ustar00rootroot00000000000000/* * Copyright (c) 2015-2017 Erik Derr [derr@cs.uni-saarland.de] * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this * file except in compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package de.infsec.tpl.stats; import java.io.Serializable; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Set; import de.infsec.tpl.manifest.ProcessManifest; import de.infsec.tpl.profile.ProfileMatch; import de.infsec.tpl.profile.SerializableProfileMatch; /* DEPRECATED - DO NOT USE ANY LONGER - */ @Deprecated public class SerializableAppStats implements Serializable { private static final long serialVersionUID = -5051966487916476377L; public String appFileName; public ProcessManifest manifest; public int appPackageCount; public int appClassCount; public List pMatches; public Set packageMatches; // includes only library names that are not matched via profiles public long processingTime; public SerializableAppStats(AppStats stats) { this.appFileName = stats.appFile.getName(); this.manifest = stats.manifest; this.appPackageCount = stats.pTree.getNumberOfNonEmptyPackages(); this.appClassCount = stats.pTree.getNumberOfAppClasses(); Set libsMatched = new HashSet(); pMatches = new ArrayList(); // LibName -> List of ProfileMatches with highest sim scores HashMap> exportedPMatches = new HashMap>(); /* * - only save profiles that at least match partially * - if multiple profiles of the same library match (at least partially), only export the one(s) with the highest score */ for (ProfileMatch pm: stats.pMatches) { if (pm.getHighestSimScore() != null && pm.getHighestSimScore().simScore > ProfileMatch.MATCH_HTREE_NONE) { String libName = pm.lib.description.name; if (!exportedPMatches.containsKey(libName)) { // initialize list and add pm exportedPMatches.put(libName, new ArrayList()); exportedPMatches.get(libName).add(pm); } else { // check if we have to add this pm to existing list ProfileMatch firstPM = exportedPMatches.get(libName).get(0); if (firstPM.getHighestSimScore() != null && firstPM.getHighestSimScore().simScore.floatValue() < pm.getHighestSimScore().simScore.floatValue()) { // replace list exportedPMatches.get(libName).clear(); exportedPMatches.get(libName).add(pm); } else if (firstPM.getHighestSimScore() != null && firstPM.getHighestSimScore().simScore.floatValue() == pm.getHighestSimScore().simScore.floatValue()) { // add to existing list exportedPMatches.get(libName).add(pm); } } libsMatched.add(libName); } } // save the PM's that are to be exported for (String libName: exportedPMatches.keySet()) { for (ProfileMatch pm: exportedPMatches.get(libName)) { pMatches.add(new SerializableProfileMatch(pm)); libsMatched.add(pm.lib.description.name); } } // save all library names that did not match via profiles but via root package name this.packageMatches = new HashSet(); for (String matchedLibPckg: stats.packageOnlyMatches.keySet()) if (!libsMatched.contains(matchedLibPckg)) this.packageMatches.add(matchedLibPckg); this.processingTime = stats.processingTime; } } LibScout-2.3.2/src/de/infsec/tpl/utils/000077500000000000000000000000001342431362400176315ustar00rootroot00000000000000LibScout-2.3.2/src/de/infsec/tpl/utils/AarFile.java000066400000000000000000000072451342431362400220070ustar00rootroot00000000000000/* * Copyright (c) 2015-2017 Erik Derr [derr@cs.uni-saarland.de] * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this * file except in compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package de.infsec.tpl.utils; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.jar.JarFile; import java.util.zip.ZipEntry; import java.util.zip.ZipException; import java.util.zip.ZipFile; /** * The 'aar' bundle is the binary distribution of an Android Library Project. * The file extension is .aar, and the maven artifact type should be aar as well, but the file itself a simple zip file with the following entries: * - /AndroidManifest.xml (mandatory) * - /classes.jar (mandatory) * - /res/ (mandatory) * - /R.txt (mandatory) * - /assets/ (optional) * - /libs/*.jar (optional) * - /jni//*.so (optional) * - /proguard.txt (optional) * - /lint.jar (optional) * These entries are directly at the root of the zip file. * The R.txt file is the output of aapt with --output-text-symbols. */ public class AarFile extends JarFile { public static final String CLASSES_JAR = "classes.jar"; private File jarFile; public AarFile(File file) throws ZipException, IOException, ClassNotFoundException { super(file); this.jarFile = extractClassesJar(file, new File(System.getProperty("java.io.tmpdir"))); } public AarFile(String fileName) throws ZipException, IOException, ClassNotFoundException { this(new File(fileName)); } public JarFile getJarFile() throws IOException { return new JarFile(this.jarFile); } /* @Override public Enumeration entries() { try (JarFile jf = new JarFile(jarFile)) { return jf.entries(); } catch (IOException e) {} return Collections.emptyEnumeration(); }*/ /** * Extracts a zip file specified by the zipFilePath to a directory specified by * destDirectory (will be created if does not exists) * @param apkFile * @param tmpDir * @throws IOException */ public File extractClassesJar(File aarFile, File tmpDir) throws IOException { try (ZipFile zip = new ZipFile(aarFile)) { ZipEntry entry = zip.getEntry(CLASSES_JAR); if (entry != null) { File outDir = new File(tmpDir + File.separator + "jarTmp" + File.separator + aarFile.getName().replace(".aar", "")); if (!outDir.exists()) outDir.mkdirs(); File outFile = new File(outDir + File.separator + entry.getName()); if (!entry.isDirectory()) { extractFile(zip.getInputStream(entry), outFile); return outFile; } } } throw new IOException("Could not extract classes.jar"); } /** * Extracts a zip entry (file entry) * @param zipIn * @param outFile * @throws IOException */ private static final int BUFFER_SIZE = 4096; private void extractFile(InputStream in, File outFile) throws IOException { BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(outFile)); byte[] bytesIn = new byte[BUFFER_SIZE]; int read = 0; while ((read = in.read(bytesIn)) != -1) { bos.write(bytesIn, 0, read); } bos.close(); } } LibScout-2.3.2/src/de/infsec/tpl/utils/AndroidClassType.java000066400000000000000000000017061342431362400237100ustar00rootroot00000000000000/* * Copyright (c) 2015-2017 Erik Derr [derr@cs.uni-saarland.de] * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this * file except in compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package de.infsec.tpl.utils; /** * Enumeration of various Android classes types */ public enum AndroidClassType { Activity, Application, AsyncTask, BroadcastReceiver, ContentProvider, Fragment, Handler, LayoutContainer, // Layout Container like RelativeLayout Plain, // default Runnable, Service, Thread, View // UI Widget } LibScout-2.3.2/src/de/infsec/tpl/utils/AndroidEntryPointConstants.java000066400000000000000000000220501342431362400260040ustar00rootroot00000000000000/* * Copyright (c) 2015-2017 Erik Derr [derr@cs.uni-saarland.de] * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this * file except in compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package de.infsec.tpl.utils; import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** * Class containing constants for the well-known Android lifecycle methods * @author Erik Derr */ public class AndroidEntryPointConstants { // Class constants public static final String ACTIVITYCLASS = "android.app.Activity"; public static final String FRAGMENTCLASS = "android.app.Fragment"; public static final String SUPPORTFRAGMENTCLASS = "android.support.v4.app.Fragment"; public static final String SERVICECLASS = "android.app.Service"; public static final String BROADCASTRECEIVERCLASS = "android.content.BroadcastReceiver"; public static final String CONTENTPROVIDERCLASS = "android.content.ContentProvider"; public static final String APPLICATIONCLASS = "android.app.Application"; public static final String ASYNCTASKCLASS = "android.os.AsyncTask"; public static final String RUNNABLECLASS = "java.lang.Runnable"; public static final String CALLABLECLASS = "java.util.concurrent.Callable"; public static final String THREADCLASS = "java.lang.Thread"; public static final String HANDLERCLASS = "android.os.Handler"; public static final String OBJECTCLASS = "java.lang.Object"; public static final String VIEWGROUP_TYPE = "android.view.ViewGroup"; public static final String VIEW_TYPE = "android.view.View"; public static final String WEBVIEW_TYPE = "android.webkit.WebView"; public static final String RUNNABLE_TYPE = "java.lang.Runnable"; // Activity lifecycle callback selectors public static final String ACTIVITY_ONCREATE = "onCreate(Landroid/os/Bundle;)V"; public static final String ACTIVITY_ONSTART = "onStart()V"; public static final String ACTIVITY_ONRESUME = "onResume()V"; public static final String ACTIVITY_ONPOSTRESUME = "onPostResume()V"; public static final String ACTIVITY_ONRESTOREINSTANCESTATE = "onRestoreInstanceState(Landroid/os/Bundle;)V"; public static final String ACTIVITY_ONPOSTCREATE = "onPostCreate(Landroid/os/Bundle;)V"; public static final String ACTIVITY_ONSAVEINSTANCESTATE = "onSaveInstanceState(Landroid/os/Bundle;)V"; public static final String ACTIVITY_ONPAUSE = "onPause()V"; public static final String ACTIVITY_ONSTOP = "onStop()V"; public static final String ACTIVITY_ONRESTART = "onRestart()V"; public static final String ACTIVITY_ONDESTROY = "onDestroy()V"; public static final String ACTIVITY_ONACTIVITYRESULT = "onActivityResult(IILandroid/content/Intent;)V"; public static final String ACTIVITY_ONNEWINTENT = "onNewIntent(Landroid/content/Intent;)V"; // Fragment lifecycle callback selectors public static final String FRAGMENT_ONATTACH = "onAttach(Landroid/app/Activity;)V"; public static final String FRAGMENT_ONCREATE = "onCreate(Landroid/os/Bundle;)V"; public static final String FRAGMENT_ONCREATEVIEW = "onCreateView(Landroid/view/LayoutInflater;Landroid/view/ViewGroup;Landroid/os/Bundle;)Landroid/view/View;"; public static final String FRAGMENT_ONACTIVITYCREATED = "onActivityCreated(Landroid/os/Bundle;)V"; public static final String FRAGMENT_ONVIEWSTATERESTORED = "onViewStateRestored(Landroid/os/Bundle;)V"; public static final String FRAGMENT_ONSTART = "onStart()V"; public static final String FRAGMENT_ONRESUME = "onResume()V"; public static final String FRAGMENT_ONPAUSE = "onPause()V"; public static final String FRAGMENT_ONSTOP = "onStop()V"; public static final String FRAGMENT_ONDESTROYVIEW = "onDestroyView()V"; public static final String FRAGMENT_ONDESTROY = "onDestroy()V"; public static final String FRAGMENT_ONDETACH = "onDetach()V"; // Service lifecycle callback selectors public static final String SERVICE_ONCREATE = "onCreate()V"; public static final String SERVICE_ONSTART = "onStart(Landroid/content/Intent;I)V"; public static final String SERVICE_ONSTARTCOMMAND = "onStartCommand(Landroid/content/Intent;II)I"; public static final String SERVICE_ONBIND = "onBind(Landroid/content/Intent;)Landroid/os/IBinder;"; public static final String SERVICE_ONREBIND = "onRebind(Landroid/content/Intent;)V"; public static final String SERVICE_ONUNBIND = "onUnbind(Landroid/content/Intent;)Z"; public static final String SERVICE_ONDESTROY = "onDestroy()V"; // BroadcastReceiver lifecycle callback selectors public static final String BROADCAST_ONRECEIVE = "onReceive(Landroid/content/Context;Landroid/content/Intent;)V"; // ContentProvider lifecycle callback selectors public static final String CONTENTPROVIDER_ONCREATE = "onCreate()Z"; // All other AsyncTask lifecycle methods have varying parameter types and have therefore to be generated public static final String ASYNCTASK_ONPREEXECUTE = "onPreExecute()V"; public static final String ASYNCTASK_ONCANCELLED = "onCancelled()V"; public static final String APPLICATION_ONCREATE = "onCreate()V"; public static final String APPLICATION_ONTERMINATE = "onTerminate()V"; public static final String APPLIFECYCLECALLBACK_ONACTIVITYSTARTED = "onActivityStarted(Landroid/app/Activity;)V"; public static final String APPLIFECYCLECALLBACK_ONACTIVITYSTOPPED = "onActivityStopped(Landroid/app/Activity;)V"; public static final String APPLIFECYCLECALLBACK_ONACTIVITYSAVEINSTANCESTATE = "onActivitySaveInstanceState(Landroid/app/Activity;Landroid/os/Bundle;)V"; public static final String APPLIFECYCLECALLBACK_ONACTIVITYRESUMED = "onActivityResumed(Landroid/app/Activity;)V"; public static final String APPLIFECYCLECALLBACK_ONACTIVITYPAUSED = "onActivityPaused(Landroid/app/Activity;)V"; public static final String APPLIFECYCLECALLBACK_ONACTIVITYDESTROYED = "onActivityDestroyed(Landroid/app/Activity;)V"; public static final String APPLIFECYCLECALLBACK_ONACTIVITYCREATED = "onActivityCreated(Landroid/app/Activity;Landroid/os/Bundle;)V"; // Activity lifecycle callback methods private static final String[] activityMethods = { ACTIVITY_ONCREATE, ACTIVITY_ONSTART, ACTIVITY_ONRESUME, ACTIVITY_ONPOSTRESUME, ACTIVITY_ONRESTOREINSTANCESTATE, ACTIVITY_ONPOSTCREATE, ACTIVITY_ONSAVEINSTANCESTATE, ACTIVITY_ONPAUSE, ACTIVITY_ONSTOP, ACTIVITY_ONRESTART, ACTIVITY_ONDESTROY, ACTIVITY_ONACTIVITYRESULT, ACTIVITY_ONNEWINTENT }; private static final String[] serviceMethods = { SERVICE_ONCREATE, SERVICE_ONDESTROY, SERVICE_ONSTART, SERVICE_ONSTARTCOMMAND, SERVICE_ONBIND, SERVICE_ONREBIND, SERVICE_ONUNBIND }; private static final String[] broadcastMethods = { BROADCAST_ONRECEIVE }; private static final String[] contentproviderMethods = { CONTENTPROVIDER_ONCREATE }; private static final String[] applicationMethods = { APPLICATION_ONCREATE, APPLICATION_ONTERMINATE, APPLIFECYCLECALLBACK_ONACTIVITYSTARTED, APPLIFECYCLECALLBACK_ONACTIVITYSTOPPED, APPLIFECYCLECALLBACK_ONACTIVITYSAVEINSTANCESTATE, APPLIFECYCLECALLBACK_ONACTIVITYRESUMED, APPLIFECYCLECALLBACK_ONACTIVITYPAUSED, APPLIFECYCLECALLBACK_ONACTIVITYDESTROYED, APPLIFECYCLECALLBACK_ONACTIVITYCREATED }; private static final String[] fragmentMethods = { FRAGMENT_ONATTACH, FRAGMENT_ONCREATE, FRAGMENT_ONCREATEVIEW, FRAGMENT_ONACTIVITYCREATED, FRAGMENT_ONVIEWSTATERESTORED, FRAGMENT_ONSTART, FRAGMENT_ONRESUME, FRAGMENT_ONPAUSE, FRAGMENT_ONSTOP, FRAGMENT_ONDESTROYVIEW, FRAGMENT_ONDESTROY, FRAGMENT_ONDETACH }; public static List getLifecycleMethods(AndroidClassType type) { switch (type) { case Activity: return getActivityLifecycleMethods(); case Application: return getApplicationLifecycleMethods(); // case AsyncTask: // see getAsyncTaskLifecycleMethods case BroadcastReceiver: return getBroadcastLifecycleMethods(); case ContentProvider: return getContentproviderLifecycleMethods(); case Fragment: return getFragmentLifecycleMethods(); case Service: return getServiceLifecycleMethods(); default: return new ArrayList(); } } public static List getActivityLifecycleMethods(){ return Arrays.asList(activityMethods); } public static List getFragmentLifecycleMethods(){ return Arrays.asList(fragmentMethods); } public static List getServiceLifecycleMethods(){ return Arrays.asList(serviceMethods); } public static List getBroadcastLifecycleMethods(){ return Arrays.asList(broadcastMethods); } public static List getContentproviderLifecycleMethods(){ return Arrays.asList(contentproviderMethods); } public static List getApplicationLifecycleMethods(){ return Arrays.asList(applicationMethods); } } LibScout-2.3.2/src/de/infsec/tpl/utils/ApkUtils.java000066400000000000000000000105051342431362400222310ustar00rootroot00000000000000/* * Copyright (c) 2015-2017 Erik Derr [derr@cs.uni-saarland.de] * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this * file except in compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package de.infsec.tpl.utils; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.Enumeration; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.zip.ZipEntry; import java.util.zip.ZipException; import java.util.zip.ZipFile; import java.util.zip.ZipInputStream; public class ApkUtils { public static boolean isMultiDexApk(File apkFile) throws ZipException, IOException { ZipFile f = new ZipFile(apkFile); boolean hasClasses2Dex = f.getEntry("classes2.dex") != null; f.close(); return hasClasses2Dex; } public static Set getClassesDex(File apkFile) throws ZipException, IOException { HashSet result = new HashSet(); ZipFile f = new ZipFile(apkFile); final Enumeration entries = f.entries(); while (entries.hasMoreElements()) { final ZipEntry entry = entries.nextElement(); if (entry.getName().matches("classes[1-9]{0,1}\\.dex")) result.add(entry); } // TODO: unzip those entries to tmp dir and return set f.close(); return result; } /** * Extracts a zip file specified by the zipFilePath to a directory specified by * destDirectory (will be created if does not exists) * @param apkFile * @param tmpDir * @throws IOException */ public static List unzipDexFiles(File apkFile, File tmpDir) throws IOException { if (!tmpDir.exists()) { tmpDir.mkdir(); } ZipInputStream zipIn = new ZipInputStream(new FileInputStream(apkFile)); ZipEntry entry = zipIn.getNextEntry(); // iterates over entries in the apk file List dexFiles = new ArrayList(); while (entry != null) { File out = new File(tmpDir + File.separator + entry.getName()); if (!entry.isDirectory()) { // match classes*.dex if (entry.getName().matches("classes[1-9]{0,1}\\.dex")) { extractFile(zipIn, out); dexFiles.add(out); } } zipIn.closeEntry(); entry = zipIn.getNextEntry(); } zipIn.close(); return dexFiles; } public static long getSizeOfClassesDex(File apkFile, boolean uncompressedSize) { ZipFile apkZipFile = null; try { apkZipFile = new ZipFile(apkFile.getAbsolutePath()); Enumeration zipEntries = apkZipFile.entries(); while (zipEntries.hasMoreElements()) { ZipEntry zipEntry = (ZipEntry) zipEntries.nextElement(); if (zipEntry.getName().equals("classes.dex")) return uncompressedSize? zipEntry.getSize() : zipEntry.getCompressedSize(); } } catch (IOException ex) { ex.printStackTrace(); } finally { if (apkZipFile != null) try { apkZipFile.close(); } catch (IOException e) {} } return -1; } /** * Extracts a zip entry (file entry) * @param zipIn * @param outFile * @throws IOException */ private static final int BUFFER_SIZE = 4096; private static void extractFile(ZipInputStream zipIn, File outFile) throws IOException { BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(outFile)); byte[] bytesIn = new byte[BUFFER_SIZE]; int read = 0; while ((read = zipIn.read(bytesIn)) != -1) { bos.write(bytesIn, 0, read); } bos.close(); } } LibScout-2.3.2/src/de/infsec/tpl/utils/MapUtils.java000066400000000000000000000056601342431362400222410ustar00rootroot00000000000000/* * Copyright (c) 2015-2017 Erik Derr [derr@cs.uni-saarland.de] * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this * file except in compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package de.infsec.tpl.utils; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; public class MapUtils { /** * Generic add function for maps that contain an list of values. * If the target list does not exist, it is created and the value is * then put in. * @param map * @param key * @param value */ @SuppressWarnings("unchecked") public static > void addValue(Map map, T key, V value) { ArrayList valList = map.containsKey(key)? new ArrayList(map.get(key)) : new ArrayList(); if (!valList.contains(value)) { valList.add(value); map.put(key, (L) valList); } } public static void addList(Map> map, T key, List valueList) { ArrayList valList = map.containsKey(key)? new ArrayList(map.get(key)) : new ArrayList(); for (V val: valueList) { if (!valList.contains(val)) { valList.add(val); } } map.put(key, valList); } public static void addList(Map> map, T key, List valueList, int skipEntryIdx) { ArrayList valList = map.containsKey(key)? map.get(key) : new ArrayList(); for (int i = 0; i < valueList.size(); i++) { V val = valueList.get(i); if (!valList.contains(val) && i != skipEntryIdx) { valList.add(val); } } map.put(key, valList); } /** * Getter function for HashMaps with an arraylist of values. * @param map * @param key * @return the arraylist for the input key or an empty list * if the key is not existing */ public static ArrayList getList(Map> map, T key) { return map.containsKey(key)? new ArrayList(map.get(key)) : new ArrayList(); } /** * Size function for HashMaps with an list of values. * @param map * @return the combined size of all individual lists in the map */ public static int size(Map> map) { int entries = 0; for (T key: map.keySet()) entries += map.get(key).size(); return entries; } /** * Adds a value to a set within a map. * @param map * @param key * @param value */ public static void addToSet(Map> map, T key, V value) { Set valSet = map.containsKey(key)? map.get(key) : new HashSet(); if (valSet.add(value)) { map.put(key, valSet); } } } LibScout-2.3.2/src/de/infsec/tpl/utils/MathUtils.java000066400000000000000000000037561342431362400224210ustar00rootroot00000000000000package de.infsec.tpl.utils; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; public class MathUtils { public static float computePercentage(int first, int second) { if (second == 0) return Float.NaN; else return (float) Math.round((Float.intBitsToFloat(first) / Float.intBitsToFloat(second)) * 100 * 100) / 100; } public static double computePercentage(long first, long second) { if (second == 0) return Double.NaN; else return (double) Math.round((Double.longBitsToDouble(first) / Double.longBitsToDouble(second)) * 100 * 100) / 100; } public static float round(float number, int digits) { return Math.round(number * ((float) Math.pow(10, digits))) / ((float)Math.pow(10, digits)); } public static double round(double number, int digits) { return Math.round(number * Math.pow(10, digits)) / Math.pow(10, digits); } public static double average(Collection values) { Float sum = 0.f; if (!values.isEmpty()) { for (Float val : values) { sum += val; } return sum.doubleValue() / values.size(); } return sum; } public static double median(Collection values) { if (values.isEmpty()) return Double.NaN; ArrayList sortedValues = new ArrayList(values); Collections.sort(sortedValues); int middle = sortedValues.size()/2; if (sortedValues.size() %2 == 1) { return sortedValues.get(middle); } else { return (sortedValues.get(middle-1) + sortedValues.get(middle)) / 2.0; } } // TODO REWORK public static double medianInt(Collection values) { if (values.isEmpty()) return Double.NaN; ArrayList sortedValues = new ArrayList(values); Collections.sort(sortedValues); int middle = sortedValues.size()/2; if (sortedValues.size() %2 == 1) { return sortedValues.get(middle); } else { return (sortedValues.get(middle-1) + sortedValues.get(middle)) / 2.0; } } } LibScout-2.3.2/src/de/infsec/tpl/utils/Pair.java000066400000000000000000000033661342431362400213770ustar00rootroot00000000000000/* * Copyright (c) 2015-2017 Erik Derr [derr@cs.uni-saarland.de] * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this * file except in compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package de.infsec.tpl.utils; /** * A simple Pair implementation * @author Erik Derr * * @param first object * @param second object */ public class Pair { private final F first; private final S second; public Pair(F first, S second) { this.first = first; this.second = second; } public static Pair create(F first, S second) { return new Pair(first, second); } public F first() { return this.first; } public S second() { return this.second; } @Override public int hashCode() { return first.hashCode() ^ second.hashCode(); } @Override @SuppressWarnings("unchecked") public boolean equals(Object o) { if (o == null) return false; if (!(o instanceof Pair)) return false; Pair pair = (Pair) o; return this.first.equals(pair.first()) && this.second.equals(pair.second()); } public boolean matches(F first, S second) { return this.first.equals(first) && this.second.equals(second); } @Override public String toString() { return "<" + this.first.toString() + ", " + this.second.toString() + ">"; } } LibScout-2.3.2/src/de/infsec/tpl/utils/Utils.java000066400000000000000000000403561342431362400216040ustar00rootroot00000000000000/* * Copyright (c) 2015-2017 Erik Derr [derr@cs.uni-saarland.de] * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this * file except in compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package de.infsec.tpl.utils; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.PrintWriter; import java.io.StringWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; import org.apache.commons.io.FileUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import de.infsec.tpl.stats.AppStats; import de.infsec.tpl.stats.SerializableAppStats; /** * Some random utility functions * @author Erik Derr */ public class Utils { // private static final Logger logger = LoggerFactory.getLogger(de.infsec.tpl.TplCLI.class); // indent for various kinds of messages public static final String INDENT = " "; public static final String INDENT2 = INDENT + INDENT; public static final String[] INDENTATION; static { INDENTATION = new String[11]; String curIndent = ""; for (int i = 0; i < 11; i++) { INDENTATION[i] = curIndent; curIndent += INDENT; } } public static String indent() { return indent(1); } public static String indent(int indentLevel) { indentLevel = Math.min(indentLevel, 10); indentLevel = Math.max(0, indentLevel); return INDENTATION[indentLevel]; } /** * Converts class name in dex bytecode notation to fully-qualified class name * @param class name in dex bytcode notation, e.g. "Lcom/ebay/motors/garage/myvehicles/GarageInsertActivity;" * @return className fully-qualified class name, e.g. "com.ebay.motors.garage.myvehicles.GarageInsertActivity" */ public static String convertToFullClassName(String className) { if (className.startsWith("L")) className = className.substring(1); if (className.endsWith(";")) className = className.substring(0, className.length()-1); return className.replaceAll("/", "\\."); } /** * Converts fully-qualified class name to class name in dex bytecode notation * @param className fully-qualified class name, e.g. "com.motors.myvehicles.GarageInsertActivity" * @return class name in broken dex bytcode notation (trailing ";" is missing), e.g. "Lcom/motors/myvehicles/GarageInsertActivity" * @deprecated once this classname notation mess in the dex frontend is fixed */ public static String convertToBrokenDexBytecodeNotation(String className) { if (className == null) return null; return className.startsWith("L")? className : "L" + className.replaceAll("\\.", "/"); } public static String convertToDexBytecodeNotation(String typeName) { if (typeName.isEmpty()) return typeName; // check if type is array int dimension = 0; while (typeName.endsWith("[]")) { typeName = typeName.substring(0, typeName.length()-2); dimension++; } if (TOVARTYPES.containsKey(typeName)) typeName = TOVARTYPES.get(typeName).toString(); else typeName = "L" + typeName.replaceAll("\\.", "/") + ";"; for (int i = 0; i < dimension; i++) typeName = "[" + typeName; return typeName; } /** * Checks whether a given method is a framework method * @param methodSignature * @return true if it's a framework method, false otherwise * * @deprecated */ // TODO spaghetti code, to be rewritten public static boolean isFrameworkCall(String methodSignature) { if (methodSignature.startsWith("java.") || // java packages methodSignature.startsWith("Ljava/") || // java packages methodSignature.startsWith("javax.") || // javax packages methodSignature.startsWith("Ljavax/") || // javax packages methodSignature.startsWith("junit.") || // junit package methodSignature.startsWith("Ljunit/") || // junit package methodSignature.startsWith("android.") || // android package methodSignature.startsWith("Landroid/") || // android package methodSignature.startsWith("dalvik.") || // dalvik package methodSignature.startsWith("Ldalvik/") || // dalvik package methodSignature.startsWith("org.apache.") || // org.apache.* package methodSignature.startsWith("Lorg/apache/") || // org.apache.* package methodSignature.startsWith("org.json.") || // org.json.* package methodSignature.startsWith("Lorg/json/") || // org.json.* package methodSignature.startsWith("org.w3c.dom.") || // W3C Java bindings for the Document Object Model methodSignature.startsWith("Lorg/w3c/dom/") || // W3C Java bindings for the Document Object Model methodSignature.startsWith("org.xml.sax.") || // core SAX APIs methodSignature.startsWith("Lorg/xml/sax/") || // core SAX APIs methodSignature.startsWith("org.xmlpull.v1.") || // XML Pull Parser methodSignature.startsWith("Lorg/xmlpull/v1/") || // XML Pull Parser methodSignature.startsWith("sun.") || // sun methodSignature.startsWith("Lsun/") || // sun methodSignature.startsWith("com.sun.") || // sun methodSignature.startsWith("Lcom/sun/")|| // sun methodSignature.startsWith("libcore.io.") || // libcore.io methodSignature.startsWith("Llibcore/io/")|| // libcore.io methodSignature.startsWith("Lorg/omg/")) return true; else return false; } /** * Returns the full class name of a method signature * @param methodSignature in notation "java.lang.StringBuilder.append(Ljava/lang/String;)" * @return the extracted class substring */ public static String getFullClassName(String methodSignature) { int endIdx = methodSignature.indexOf("("); if (endIdx == -1) return methodSignature; String result = methodSignature.substring(0, endIdx); // strip args and return type return result.substring(0, result.lastIndexOf(".")); } /** * Vartypes used in Dex bytecode and their mnemonics */ public static final HashMap VARTYPES = new HashMap() { private static final long serialVersionUID = 1L; { put ('V', "void"); // can only be used for return types put ('Z', "boolean"); put ('B', "byte"); put ('S', "short"); put ('C', "char"); put ('I', "int"); put ('J', "long"); // 64 bits put ('F', "float"); put ('D', "double"); // 64 bits } }; /** * Mnemonics to dex bytecode vartypes */ public static final HashMap TOVARTYPES = new HashMap() { private static final long serialVersionUID = 1L; { put ("void", 'V'); // can only be used for return types put ("boolean", 'Z'); put ("byte", 'B'); put ("short", 'S'); put ("char", 'C'); put ("int", 'I'); put ("long", 'J'); // 64 bits put ("float", 'F'); put ("double", 'D'); // 64 bits } }; public static boolean isPrimitiveType(String type) { return TOVARTYPES.containsKey(type) || VARTYPES.containsKey(type.charAt(0)); } public static boolean isArrayType(String type) { return type.startsWith("["); } public static boolean isParameterRegister(String register) { return register.matches("^p\\d{1,4}$"); } public static boolean isNormalRegister(String register) { return register.matches("^v\\d{1,5}$"); } /** * Parses the method argument header of a dex method signature * @param signature method signature in dex notation * @param humanReadable if true it converts ther dex vartypes to human readable types * @return an array of (human readable) argument types * * @deprecated * use IMethodReference directly instead of parsing ourselves (arguments are already parsed in CallInstructions) */ @Deprecated public static List parseMethodArguments(String signature, boolean humanReadable) { ArrayList result = new ArrayList(); // Parse arguments String args = signature.substring(signature.indexOf('(')+1, signature.indexOf(')')); Boolean parsingObject = false; String currentStr = ""; for (char c: args.toCharArray()) { currentStr += c; if (c == 'L') { // start of class object parsingObject = true; } else if (VARTYPES.containsKey(c) && !parsingObject) { // found var type result.add(humanReadable? VARTYPES.get(c) : currentStr); currentStr = ""; } else if (c == ';') { // end of class object parsingObject = false; result.add(humanReadable? currentStr.substring(1, currentStr.length()-1).replaceAll("/", ".") : currentStr); currentStr = ""; } } return result; } /** * Strips leading and trailing quotes of strings * @param str input string * @return dequoted string */ public static String dequote(String str) { if (isConstant(str)) { str = str.replaceFirst("\"", ""); return str.substring(0, str.length()-1); } return str; } /** * Returns a quoted String (double quote)· * @param str input string * @return quoted input string */ public static String quote(String str) { return "\"" + str + "\""; } public static String singleQuote(String str) { return "\'" + str + "\'"; } public static String escapeQuotes(String str) { return str.replaceAll("\\\"", "\\\\\"").replaceAll("\\\'", "\\\\\'"); } /** * Checks whether a given value is a constant. * Here a constant is a quoted value. * @param val * @return true if val is a constant, false otherwise */ public static boolean isConstant(String val) { return val.startsWith("\"") && val.endsWith("\""); } public static String millisecondsToFormattedTime(long milliseconds) { final String SEP = ", "; int millis = (int) milliseconds % 1000; int seconds = (int) (milliseconds / 1000) % 60 ; int minutes = (int) ((milliseconds / (1000*60)) % 60); int hours = (int) ((milliseconds / (1000*60*60)) % 24); StringBuilder sb = new StringBuilder(); sb.append(hours > 0? hours + " hours" + SEP : ""); sb.append(minutes > 0? minutes + " min" + SEP : ""); sb.append(seconds > 0? seconds + " sec" + SEP : ""); sb.append(millis >= 0? millis + " ms" : ""); return sb.toString(); } public static String humanReadableByteCount(long bytes, boolean si) { int unit = si ? 1000 : 1024; if (bytes < unit) return bytes + " B"; int exp = (int) (Math.log(bytes) / Math.log(unit)); String pre = (si ? "kMGTPE" : "KMGTPE").charAt(exp-1) + (si ? "" : "i"); return String.format("%.1f %sB", bytes / Math.pow(unit, exp), pre); } /** * List join function, which creates a single string of the items of the * array separated by _sep. If "spaceFirst" is set to true the first two elements * are joined by a space character. * @param list the input list * @param _sep * @param spaceFirst * @returns a single assembled string */ public static String join(List list, String _sep, Boolean spaceFirst) { String sep = ""; StringBuilder result = new StringBuilder(); for (int i = 0; i < list.size(); i++) { if (!list.get(i).toString().isEmpty()) result.append(sep + list.get(i).toString()); sep = (i == 0 && spaceFirst)? " " : _sep; } return result.toString(); } public static String join(List list, String _sep) { return join(list, _sep, false); } public static String join(List list) { return join(list, ""); } /** * Recursively searches a given directory for certain file types * @param dir the directory file * @param extensions an array of file extensions without leading dot * @return a list of {@link File}s */ public static List collectFiles(File dir, String[] extensions) { ArrayList files = new ArrayList(); // gather all input files if (dir.isDirectory()) { try { // Finds files within a root directory and optionally its· // subdirectories which match an array of extensions. // This method will returns matched file as java.io.File boolean recursive = true; Collection foundFiles = FileUtils.listFiles(dir, extensions, recursive); for (Iterator iterator = foundFiles.iterator(); iterator.hasNext();) { files.add(iterator.next()); } } catch (Exception e) { //e.printStackTrace(); } } return files; } public interface IPredicate { boolean apply(T type); } public static Collection filter(Collection target, IPredicate predicate) { Collection result = new ArrayList(); for (T element: target) { if (predicate.apply(element)) { result.add(element); } } return result; } public static Collection filter(T[] target, IPredicate predicate) { return filter(Arrays.asList(target), predicate); } /** * Serialize an Java Object to disk * @param targetFile the {@link File} to store the object * @param obj a serializable {@link Object} * @return true if no exception was thrown, false otherwise * @throws IOException */ public static boolean object2Disk(final File targetFile, final Object obj) { File basePath = new File(targetFile.getPath().substring(0, targetFile.getPath().lastIndexOf(File.separator))); if (!basePath.exists()) basePath.mkdirs(); try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(targetFile))) { oos.writeObject(obj); } catch (IOException e) { //logger.warn(Utils.stacktrace2Str(e)); return false; } return true; } /** * Deserialize a Java Object from disk * @param file the {@link File} to read the object from * @return the deserialized {@link Object} * @throws ClassNotFoundException */ public static Object disk2Object(File file) throws ClassNotFoundException { Object obj = null; try (ObjectInputStream in = new ObjectInputStream(new FileInputStream(file))) { obj = in.readObject(); } catch (IOException e) { //logger.warn(Utils.stacktrace2Str(e)); } return obj; } /** * Export app statistics to a JSON file * @param jsonFile the Json {@link File} to store the results * @param stats the {@link AppStats} object * @throws IOException */ public static void obj2JsonFile(File jsonFile, AppStats stats) throws IOException { File basePath = new File(jsonFile.getPath().substring(0, jsonFile.getPath().lastIndexOf(File.separator))); if (!basePath.exists()) basePath.mkdirs(); GsonBuilder builder = new GsonBuilder(); Gson gson = builder.create(); String jsonOut = gson.toJson(stats.export()); try (FileOutputStream fos = new FileOutputStream(jsonFile)) { fos.write(jsonOut.getBytes()); } catch (IOException e) { //logger.warn(Utils.stacktrace2Str(e)); } } public static void obj2JsonFile(File jsonFile, Object obj) throws IOException { File basePath = new File(jsonFile.getPath().substring(0, jsonFile.getPath().lastIndexOf(File.separator))); if (!basePath.exists()) basePath.mkdirs(); GsonBuilder builder = new GsonBuilder(); Gson gson = builder.create(); String jsonOut = gson.toJson(obj); try (FileOutputStream fos = new FileOutputStream(jsonFile)) { fos.write(jsonOut.getBytes()); } catch (IOException e) { //logger.warn(Utils.stacktrace2Str(e)); } } public static String stacktrace2Str(Throwable t) { StringWriter sw = new StringWriter(); t.printStackTrace(new PrintWriter(sw)); return sw.toString(); // stack trace as a string } } LibScout-2.3.2/src/de/infsec/tpl/utils/VersionWrapper.java000066400000000000000000000106331342431362400234650ustar00rootroot00000000000000package de.infsec.tpl.utils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.github.zafarkhaja.semver.Version; /** * Wrapper Class for {@link Version} */ // TODO extends Version? public class VersionWrapper { private static final Logger logger = LoggerFactory.getLogger(de.infsec.tpl.utils.VersionWrapper.class); public enum SEMVER { PATCH ("patch"), MINOR ("minor"), MAJOR ("major"); private final String name; private SEMVER(String s) { name = s; } public String toString() { return this.name; } } /** * {@link Version.valueOf} requires a valid x.y.z scheme. This version parser further allows x and x.y schemes * and initializes the missing values with zero. * @param versionStr * @return */ public static Version valueOf(String versionStr) { if (versionStr.isEmpty()) { logger.warn("Empty version string!"); return null; } String[] version = versionStr.split("\\."); if (version.length == 1) { // like 12 is transformed into 12.0.0 logger.debug("Invalid semVer (minor+patch level missing): " + versionStr); return Version.forIntegers(Integer.parseInt(version[0])); } if (version.length == 2) { // like 7.2 is transformed into 7.2.0 logger.debug("Invalid semVer (patch level missing): " + versionStr); // 11.1-rc1 is transformed into 11.1.0-rc1 if (version[1].indexOf('-') != -1) { String[] frag = version[1].split("-"); String ext = ""; for (int i = 1; i < frag.length; i++) ext += "-" + frag[i]; return Version.valueOf(version[0] + "." + frag[0] + ".0" + ext); } return Version.forIntegers(Integer.parseInt(version[0]),Integer.parseInt(version[1])); } if (version.length == 3) // like 3.2.1 return Version.valueOf(versionStr); if (version.length >= 3) { // like 5.2.1.3 is transformed into 5.2.1-build3 logger.debug("Invalid semVer (sub-patch level): " + versionStr); Version.Builder builder = new Version.Builder(version[0] + "." + version[1] + "." + version[2]); builder.setBuildMetadata("build." + version[3]); return builder.build(); } logger.debug("Invalid semVer: " + versionStr); return null; } /** * Determines change between two versions * @param versionStr0 first version string * @param versionStr1 second version string * @return version change, one of ["major", "minor", "patch"] or null if some error occurs */ // TODO TODO rewrite public static String determineVersionChange(String versionStr0, String versionStr1) { Version v0 = VersionWrapper.valueOf(versionStr0); Version v1 = VersionWrapper.valueOf(versionStr1); if (v0.getMajorVersion() < v1.getMajorVersion()) { return SEMVER.MAJOR.toString(); } else if (v0.getMajorVersion() == v1.getMajorVersion()) { if (v0.getMinorVersion() < v1.getMinorVersion()) { return SEMVER.MINOR.toString(); } else if (v0.getMinorVersion() == v1.getMinorVersion()) { if (v0.getPatchVersion() < v1.getPatchVersion()) { return SEMVER.PATCH.toString(); } else if (v0.getPatchVersion() == v1.getPatchVersion()) { if (!v1.getBuildMetadata().isEmpty()) // subpatch levels are encoded by build meta data through VersionWrapper return SEMVER.PATCH.toString(); } else return null; } } return null; } public static SEMVER getExpectedSemver(Version v0, Version v1) { if (v0.getMajorVersion() < v1.getMajorVersion()) { return SEMVER.MAJOR; } else if (v0.getMajorVersion() == v1.getMajorVersion()) { if (v0.getMinorVersion() < v1.getMinorVersion()) { return SEMVER.MINOR; } else if (v0.getMinorVersion() == v1.getMinorVersion()) { if (v0.getPatchVersion() < v1.getPatchVersion()) { return SEMVER.PATCH; } else if (v0.getPatchVersion() == v1.getPatchVersion()) { if (!v1.getBuildMetadata().isEmpty()) // subpatch levels are encoded by build meta data through VersionWrapper return SEMVER.PATCH; } else return null; } } return null; } // strip trailling zeros, e.g. 3.4.0 -> 3.4 public static String getTruncatedVersion(Version v) { String vStr = "" + v.getMajorVersion(); if (v.getMinorVersion() > 0 || (v.getMinorVersion() == 0 && v.getPatchVersion() > 0)) { vStr += "." + v.getMinorVersion(); if (v.getPatchVersion() > 0) vStr += "." + v.getPatchVersion(); if (v.getBuildMetadata().length() > 1) vStr += "-" + v.getBuildMetadata(); } return vStr; } } LibScout-2.3.2/src/de/infsec/tpl/utils/WalaUtils.java000066400000000000000000000475641342431362400224210ustar00rootroot00000000000000/* * Copyright (c) 2015-2017 Erik Derr [derr@cs.uni-saarland.de] * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this * file except in compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package de.infsec.tpl.utils; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.ibm.wala.classLoader.CallSiteReference; import com.ibm.wala.classLoader.IClass; import com.ibm.wala.classLoader.IMethod; import com.ibm.wala.ipa.callgraph.CGNode; import com.ibm.wala.ipa.callgraph.CallGraph; import com.ibm.wala.ipa.callgraph.propagation.InstanceKey; import com.ibm.wala.ipa.callgraph.propagation.PointerAnalysis; import com.ibm.wala.ipa.callgraph.propagation.PointerKey; import com.ibm.wala.ipa.cha.IClassHierarchy; import com.ibm.wala.shrikeBT.ConditionalBranchInstruction; import com.ibm.wala.shrikeBT.IComparisonInstruction; import com.ibm.wala.ssa.IR; import com.ibm.wala.ssa.SSACFG; import com.ibm.wala.ssa.SSAInvokeInstruction; import com.ibm.wala.ssa.SSACFG.BasicBlock; import com.ibm.wala.ssa.SSAInstruction; import com.ibm.wala.types.ClassLoaderReference;import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import com.ibm.wala.types.Descriptor; import com.ibm.wala.types.MethodReference; import com.ibm.wala.types.TypeReference; import com.ibm.wala.util.intset.OrdinalSet; import de.infsec.tpl.exceptions.MethodNotFoundException; import de.infsec.tpl.utils.AndroidClassType; import de.infsec.tpl.utils.AndroidEntryPointConstants; import de.infsec.tpl.utils.Utils.IPredicate; /** * Utility functions that facilitate the work with the WALA framework */ public class WalaUtils { private static final Logger logger = LoggerFactory.getLogger(de.infsec.tpl.utils.WalaUtils.class); public static final String WALA_FAKE_ROOT_CLASS = "com.ibm.wala.FakeRootClass"; public static final String WALA_FAKE_ROOT_METHOD_SIGNATURE = WALA_FAKE_ROOT_CLASS + ".fakeRootMethod()V"; public static final String WALA_FAKE_WORLD_CLINIT_SIGNATURE = WALA_FAKE_ROOT_CLASS + ".fakeWorldClinit()V"; // TIP: use vm argument -Dwala.dump.ssa to dump Wala IR /* * Example invoke instruction: * ------------------------------ * 25 = invokevirtual < Application, Landroid/content/Intent, putExtra(Ljava/lang/String;Landroid/os/Parcelable;)Landroid/content/Intent; > 20,22,23 @50 exception:24 * ------------------------------ * defs: 25,24 * uses: 20,22,23 * pc : 50 */ public static String simpleName(IClass c) { return c == null? "null" : Utils.convertToFullClassName(c.getName().toString()); } public static String getName(IMethod m) { return m.getReference().getName().toString(); } public static IPredicate invokeFilter = new Utils.IPredicate() { @Override public boolean apply(SSAInstruction type) { return type instanceof SSAInvokeInstruction; } }; /** * Looks up a method by name in a class object. If the method is overloaded, * the first one found is returned. * @param clazz IClass object * @param methodName name of the method to be looked up * @return IMethod if method is declared in clazz, null otherwise */ public static IMethod getMethodByName(IClass clazz, String methodName) { for (IMethod m: clazz.getAllMethods()) { // DeclaredMethods()) -> only impl./overriden methods if (m.getSelector().toString().startsWith(methodName)) { return m; } } return null; } public static boolean declaresMethodByName(IClass clazz, String methodName) { return getMethodByName(clazz, methodName) == null? false : true; } public static boolean isObjectClass(IClass clazz) { return "java.lang.Object".equals(simpleName(clazz)); } /** * Hierarchical lookup of an {@link IMethod} via {@link IClass} and {@link CallSiteReference}. * @param clazz the {@link IClass} to start with * @param csr the {@link CallSiteReference} * @return a {@link IMethod} object of the resolved method or null */ public static IMethod resolveMethod(IClass clazz, CallSiteReference csr) { IMethod targetMethod = null; while (targetMethod == null && !WalaUtils.isObjectClass(clazz)) { targetMethod = clazz.getMethod(csr.getDeclaredTarget().getSelector()); if (targetMethod != null) break; clazz = clazz.getSuperclass(); } return targetMethod; } /** * Hierarchy lookup of a method selector. If the method is not declared in the class * the lookup is continued at the superclass. This is continued until the method is found or * the Object class is reached * @param clazz the IClass to start with * @param selector the method selector * @return a IMethod object of the method in question * @throws MethodNotFoundException */ public static IMethod lookupMethod(IClass clazz, String selector) throws MethodNotFoundException { for (IMethod im: clazz.getAllMethods()) { if (im.getSelector().toString().equals(selector)) { return im; } } throw new MethodNotFoundException("[lookupMethod] Method " + selector + " not found in class: " + clazz.getName().toString() + " and its superclasses"); } /** * Checks whether some instruction is both an {@link com.ibm.wala.ssa.SSAInvokeInstruction} and if its signature * matches a given signature * @param ins an {@link com.ibm.wala.ssa.SSAInstruction} * @param methodSignature a method signature to check against * @return */ public static boolean checkMethodSignature(final SSAInstruction ins, final String methodSignature) { if (ins instanceof SSAInvokeInstruction) { final SSAInvokeInstruction inv = (SSAInvokeInstruction) ins; return inv.getDeclaredTarget().getSignature().equals(methodSignature); } return false; } /** * Collects all implemented interfaces for a given class * @param clazz the IClass object to analyze * @return a set of IClass objects representing the interfaces */ public static Set collectAllInterfaces(IClass clazz) { // do not check array classes if (clazz.isArrayClass()) return new HashSet(); Set interfaces = new HashSet(clazz.getDirectInterfaces()); for (IClass c : clazz.getDirectInterfaces()) interfaces.addAll(collectAllInterfaces(c)); return interfaces; } /** * Looks up an IClass for a given class name * @param cha a {@link IClassHierarchy} * @param clazzName in java notation, e.g. "de.infsec.MyActivity" * @return a {@link IClass} object * @throws ClassNotFoundException */ public static IClass lookupClass(IClassHierarchy cha, String clazzName) throws ClassNotFoundException { if (clazzName == null) throw new ClassNotFoundException(Utils.INDENT + "class name is NULL"); String convertedClass = Utils.convertToBrokenDexBytecodeNotation(clazzName); IClass iclazz = cha.lookupClass(TypeReference.findOrCreate(ClassLoaderReference.Application, convertedClass)); if (iclazz == null) throw new ClassNotFoundException(Utils.INDENT + "[lookupClass] Could'nt lookup IClass for " + clazzName); return iclazz; } @Deprecated public static boolean isApplicationClass(IClass clazz) { // we need the additional check as framework classes from the support package are loaded via the Application classloader return clazz.getClassHierarchy().getScope().isApplicationLoader(clazz.getClassLoader()) && !clazz.getName().toString().startsWith("Landroid"); // strip android.v4 / android.v7 support packages as they are considered part of the framework // TODO necessary? support packages are loaded via Primordial! } public static boolean isAppClass(IClass clazz) { // Normalization: // filter empty dummy classes // possibly related too: http://bugs.java.com/bugdatabase/view_bug.do?bug_id=4295934 boolean isEmptyInnerClass = WalaUtils.isInnerClass(clazz) && isAnonymousInnerClass(clazz) && (clazz.getDeclaredMethods().isEmpty() || (clazz.getDeclaredMethods().size() == 1 && clazz.getDeclaredMethods().iterator().next().isClinit()) && clazz.getDeclaredInstanceFields().isEmpty() && clazz.getDeclaredStaticFields().isEmpty() && clazz.getDirectInterfaces().isEmpty()); return clazz.getClassHierarchy().getScope().isApplicationLoader(clazz.getClassLoader()) && !isAndroidResourceClass(clazz) && !isEmptyInnerClass && !clazz.isSynthetic(); } public static boolean isExtensionClass(IClass clazz) { return clazz.getClassLoader().getReference().equals(clazz.getClassHierarchy().getScope().getExtensionLoader()); } /** * @param clazzName IClass * @return true if it is an anonymous inner class, false otherwise */ public static boolean isAnonymousInnerClass(final String clazzName) { final Pattern anonymousInnerClassPattern = Pattern.compile("^.+\\$[0-9]+$"); final Matcher matcher = anonymousInnerClassPattern.matcher(clazzName); return matcher.matches(); } public static boolean isAnonymousInnerClass(final IClass clazz) { return isAnonymousInnerClass(simpleName(clazz)); } public static boolean isAnonymousInnerInnerClass(final String clazzName) { final Pattern anonymousInnerClassPattern = Pattern.compile("^.+\\$[0-9]+\\$[0-9]+$"); final Matcher matcher = anonymousInnerClassPattern.matcher(clazzName); return matcher.matches(); } public static boolean isAnonymousInnerInnerClass(final IClass clazz) { return isAnonymousInnerInnerClass(simpleName(clazz)); } /** * Retrieves all superclasses for a given class including itself * @param clazz the input IClass object * @return a list of IClass superclass objects including the input class */ public static List getSuperClassesIncluding(IClass clazz) { LinkedList superclasses = new LinkedList(getSuperClasses(clazz)); superclasses.addFirst(clazz); return superclasses; } /** * Retrieves all superclasses for a given class * @param clazz the input IClass object * @return a list of IClass superclass objects or an empty list if there is no superclass */ public static List getSuperClasses(IClass clazz) { ArrayList superclasses = new ArrayList(); while (clazz.getSuperclass() != null) { clazz = clazz.getSuperclass(); superclasses.add(clazz); } return superclasses; } public static IMethod getIMethod(IClassHierarchy cha, String signature) { // TODO: throw exceptions String clazzName = Utils.getFullClassName(signature); String selector = signature.substring(clazzName.length()+1); try { IClass clazz = WalaUtils.lookupClass(cha, clazzName); for (IMethod m: clazz.getAllMethods()) { // DeclaredMethods()) -> only impl./overriden methods if (m.getSelector().toString().equals(selector)) { return m; } } } catch (ClassNotFoundException e) { logger.debug("Classname " + clazzName + " could not be looked up!"); } return null; // TODO: throw exception } public static IR getIR(IMethod method, CallGraph cg) { CGNode node = getCGNode(method, cg); return node == null? null : node.getIR(); } public static CGNode getCGNode(IMethod method, CallGraph cg) { logger.debug("Retrieve CGNode for " + method.getSignature()); MethodReference ref = method.getReference(); if (ref == null) return null; Set cgnode = cg.getNodes(ref); if (cgnode.isEmpty()) { logger.warn("Number of CGNode(s) for " + method.getSignature() + " is " + cgnode.size()); return null; } /*else if (cgnode.size() > 1) { logger.warn("Number of CGNode(s) for " + methodSignature + " is " + cgnode.size() + " refMethod.sig: " + refMethod.getSignature()); }*/ return cgnode.iterator().next(); } public static CGNode getCGNode(String methodSignature, CallGraph cg) { logger.debug("Retrieve CGNode for " + methodSignature); IMethod refMethod = getIMethod(cg.getClassHierarchy(), methodSignature); if (refMethod == null) return null; return getCGNode(refMethod, cg); } public static IR getIR(String methodSignature, CallGraph cg) { CGNode node = getCGNode(methodSignature, cg); return node == null? null : node.getIR(); } public static SSACFG getSSACFG(String methodSignature, CallGraph cg) { logger.debug("Retrieve SSACFG for " + methodSignature); IR ir = getIR(methodSignature, cg); if (ir == null && !methodSignature.startsWith(WALA_FAKE_ROOT_CLASS)) logger.warn("Could not retrieve IR for " + methodSignature); return ir == null? null : ir.getControlFlowGraph(); } public static SSAInstruction getSSAInstruction(CallGraph cg, String methodSignature, int iindex) { SSACFG cfg = getSSACFG(methodSignature, cg); if (cfg == null) { logger.warn("getSSAInstruction:: Did not find SSACFG for " + methodSignature); } else { BasicBlock block = cfg.getBlockForInstruction(iindex); if (block != null) { for (Iterator it = block.iterateNormalInstructions(); it.hasNext();) { SSAInstruction ins = it.next(); if (ins.iindex == iindex) { return ins; } } logger.warn("getSSAInstruction:: Did not find iindex " + iindex + " in SSACFG (" + methodSignature + ")"); } else logger.warn("getSSAInstruction:: Did not find basic block for iindex " + iindex + " in SSACFG (" + methodSignature + ")"); } return null; } public static String getClassName(IClass clazz) { // Strip package name String clazzName = clazz.getName().toString().substring(clazz.getName().toString().lastIndexOf("/")+1); return clazzName.endsWith(";")? clazzName.substring(0, clazzName.length()-1) : clazzName; } public static boolean isInnerClassOf(IClass clazz, IClass testClazz) { // TODO: innerclass check would be easier if the dex bytecode annotations would have been parsed! // here we have to fallback to the potentially non-reliable name check String clazzName = getClassName(clazz); String testClazzName = getClassName(testClazz); return testClazzName.contains("$") && testClazzName.startsWith(clazzName); } public static boolean isInnerClass(IClass clazz) { // TODO: innerclass check would be easier if the dex bytecode annotations would have been parsed! // here we have to fallback to the potentially non-reliable name check return getClassName(clazz).contains("$"); } public static boolean isAndroidResourceClass(IClass clazz) { // match R and BuildConfig class and their inner classes String clazzName = getClassName(clazz); return clazzName.equals("R") || clazzName.startsWith("R$") || clazzName.equals("BuildConfig"); } // Strangely there is no such function in IClass public static boolean isFinal(IClass clazz) { return (clazz.getModifiers() & 0x0010) != 0; } public static String op2str(ConditionalBranchInstruction.IOperator op) { if (op instanceof ConditionalBranchInstruction.Operator) { switch ((ConditionalBranchInstruction.Operator) op) { case EQ: return "=="; case GE: return ">="; case GT: return ">"; case LE: return "<="; case LT: return "<"; case NE: return "!="; } } return "?"; } public static String op2str(IComparisonInstruction.Operator opcode) { switch (opcode) { case CMP: return "=="; case CMPG: return ">"; case CMPL: return "<"; } return opcode.name(); } public static AndroidClassType classifyClazz(IClassHierarchy cha, String clazzName) throws ClassNotFoundException { IClass ic = lookupClass(cha, clazzName); return classifyClazz(ic); } /** * Checks whether a given class is one of Android's main class types (Activity, Service, ..) * @param clazz The IClass object to check * @return an AndroidClassType object */ public static AndroidClassType classifyClazz(IClass clazz) { for (IClass c: WalaUtils.getSuperClassesIncluding(clazz)) { if (WalaUtils.simpleName(c).equals(AndroidEntryPointConstants.ACTIVITYCLASS)) return AndroidClassType.Activity; else if (WalaUtils.simpleName(c).equals(AndroidEntryPointConstants.FRAGMENTCLASS) || WalaUtils.simpleName(c).equals(AndroidEntryPointConstants.SUPPORTFRAGMENTCLASS)) // Apps that support API level < 11 have to use the support.v4 version return AndroidClassType.Fragment; else if (WalaUtils.simpleName(c).equals(AndroidEntryPointConstants.SERVICECLASS)) return AndroidClassType.Service; else if (WalaUtils.simpleName(c).equals(AndroidEntryPointConstants.BROADCASTRECEIVERCLASS)) return AndroidClassType.BroadcastReceiver; else if (WalaUtils.simpleName(c).equals(AndroidEntryPointConstants.CONTENTPROVIDERCLASS)) return AndroidClassType.ContentProvider; else if (WalaUtils.simpleName(c).equals(AndroidEntryPointConstants.APPLICATIONCLASS)) return AndroidClassType.Application; else if (WalaUtils.simpleName(c).equals(AndroidEntryPointConstants.ASYNCTASKCLASS)) return AndroidClassType.AsyncTask; else if (WalaUtils.simpleName(c).equals(AndroidEntryPointConstants.THREADCLASS)) return AndroidClassType.Thread; else if (WalaUtils.simpleName(c).equals(AndroidEntryPointConstants.RUNNABLECLASS)) return AndroidClassType.Runnable; else if (WalaUtils.simpleName(c).equals(AndroidEntryPointConstants.HANDLERCLASS)) return AndroidClassType.Handler; else if (WalaUtils.simpleName(c).equals(AndroidEntryPointConstants.VIEWGROUP_TYPE)) return AndroidClassType.LayoutContainer; // technically webview is a viewgroup, however in practice it is used as an atomic view else if (WalaUtils.simpleName(c).equals(AndroidEntryPointConstants.VIEW_TYPE) || WalaUtils.simpleName(c).equals(AndroidEntryPointConstants.WEBVIEW_TYPE)) return AndroidClassType.View; } return AndroidClassType.Plain; } public static Set getChaStats(IClassHierarchy cha) { TreeSet publicMethods = new TreeSet(); int clCount = 0; int innerClCount = 0; int publicClCount = 0; int miscMethodCount = 0; HashMap clazzTypes = new HashMap<>(); for (AndroidClassType t: AndroidClassType.values()) clazzTypes.put(t, 0); // collect basic cha information for (IClass clazz: cha) { if (WalaUtils.isAppClass(clazz)) { AndroidClassType type = WalaUtils.classifyClazz(clazz); clazzTypes.put(type, clazzTypes.get(type)+1); logger.trace("App Class: " + WalaUtils.simpleName(clazz) + " (" + type + ")"); clCount++; if (WalaUtils.isInnerClass(clazz)) innerClCount++; if (clazz.isPublic()) publicClCount++; for (IMethod im: clazz.getDeclaredMethods()) { if (im.isBridge() || im.isSynthetic()) continue; if (im.isPublic()) { publicMethods.add(im.getSignature()); } else { miscMethodCount++; } } } } logger.info(""); logger.info("= ClassHierarchy Stats ="); logger.info(Utils.INDENT + "# of classes: " + clCount); logger.info(Utils.INDENT + "# thereof inner classes: " + innerClCount); logger.info(Utils.INDENT + "# thereof public classes: " + publicClCount); for (AndroidClassType t: AndroidClassType.values()) logger.info(Utils.INDENT2 + t + " : " + clazzTypes.get(t)); logger.info(Utils.INDENT + "# methods: " + (publicMethods.size() + miscMethodCount)); logger.info(Utils.INDENT2 + "# of publicly accessible methods: " + publicMethods.size()); logger.info(Utils.INDENT2 + "# of non-accessible methods: " + miscMethodCount); logger.info(""); return publicMethods; } } LibScout-2.3.2/src/de/infsec/tpl/xml/000077500000000000000000000000001342431362400172715ustar00rootroot00000000000000LibScout-2.3.2/src/de/infsec/tpl/xml/XMLParser.java000066400000000000000000000106701342431362400217550ustar00rootroot00000000000000/* * Copyright (c) 2015-2017 Erik Derr [derr@cs.uni-saarland.de] * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this * file except in compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package de.infsec.tpl.xml; import java.io.File; import java.io.IOException; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; import de.infsec.tpl.profile.LibraryDescription; import de.infsec.tpl.profile.LibraryDescription.LibraryCategory; /** * Parser for custom library.xml format (that includes lib meta-data) * * * * * {} * * * {} * * * {} * * * {} * * * @author ederr * */ public class XMLParser { private static final String TAG_NAME = "name"; private static final String TAG_VERSION = "version"; private static final String TAG_CATEGORY = "category"; private static final String TAG_DATE = "releasedate"; private static final String TAG_COMMENT = "comment"; public static LibraryDescription readLibraryXML(File file) throws ParserConfigurationException, SAXException, IOException, ParseException { if (file == null || !file.exists() || file.isDirectory()) throw new IOException("Library description file does not exist or is a directory!"); DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); DocumentBuilder dBuilder = dbFactory.newDocumentBuilder(); Document doc = dBuilder.parse(file); doc.getDocumentElement().normalize(); NodeList nList = doc.getElementsByTagName("library"); if (nList.getLength() != 1) throw new SAXException("The library description file must only contain one root node (found: " + nList.getLength() + ")"); // We only require one description per library Node nNode = nList.item(0); if (nNode.getNodeType() == Node.ELEMENT_NODE) { Element element = (Element) nNode; // mandatory values String name = element.getElementsByTagName(TAG_NAME).item(0).getTextContent(); LibraryCategory category; try { String catStr = element.getElementsByTagName(TAG_CATEGORY).item(0).getTextContent(); if (catStr.equals("Social Media") || catStr.equals("Social-Media") || catStr.equals("SocialMedia")) category = LibraryCategory.SocialMedia; else category = LibraryCategory.valueOf(catStr); } catch (IllegalArgumentException e) { throw new ParseException("Found unknown category: " + element.getElementsByTagName(TAG_CATEGORY).item(0).getTextContent() + " in file: " + file, -1); } // optional values String version = null; if (element.getElementsByTagName(TAG_VERSION).getLength() > 0) version = element.getElementsByTagName(TAG_VERSION).item(0).getTextContent(); Date date = null; if (element.getElementsByTagName(TAG_DATE).getLength() > 0) { String dateStr = element.getElementsByTagName(TAG_DATE).item(0).getTextContent(); if (!dateStr.isEmpty()) { SimpleDateFormat formatter = new SimpleDateFormat("dd.MM.yyyy"); date = formatter.parse(dateStr); } } String comment = null; if (element.getElementsByTagName(TAG_COMMENT).getLength() > 0) comment = element.getElementsByTagName(TAG_COMMENT).item(0).getTextContent(); return new LibraryDescription(name, category, version, date, comment); } else throw new SAXException("Root node (" + nNode.getNodeName() + " / " + nNode.getNodeValue() + ") is not an element-node"); } } LibScout-2.3.2/src/pxb/000077500000000000000000000000001342431362400146245ustar00rootroot00000000000000LibScout-2.3.2/src/pxb/android/000077500000000000000000000000001342431362400162445ustar00rootroot00000000000000LibScout-2.3.2/src/pxb/android/axml/000077500000000000000000000000001342431362400172055ustar00rootroot00000000000000LibScout-2.3.2/src/pxb/android/axml/AXMLPrinter.java000066400000000000000000000136711342431362400221650ustar00rootroot00000000000000/* * Copyright 2008 Android4ME * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package pxb.android.axml; import java.io.File; import java.io.PrintWriter; import java.io.StringReader; import java.io.StringWriter; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import org.xmlpull.v1.XmlPullParser; import android.content.res.AXmlResourceParser; import android.util.TypedValue; /** * This is example usage of AXMLParser class. * * Prints xml document from Android's binary xml file. */ public class AXMLPrinter { private static final String DEFAULT_XML = "AndroidManifest.xml"; public static void main(String[] arguments) { if (arguments.length<1) { System.out.println("Usage: AXMLPrinter "); return; } // String apkPath = "E:/apk_file/201008300227127991.apk"; String apkPath = arguments[0]; System.out.println(getManifestXMLFromAPK(apkPath)); } public static String getManifestXMLFromAPK(String apkPath) { ZipFile file = null; StringBuilder xmlSb = new StringBuilder(100); try { File apkFile = new File(apkPath); file = new ZipFile(apkFile, ZipFile.OPEN_READ); ZipEntry entry = file.getEntry(DEFAULT_XML); AXmlResourceParser parser=new AXmlResourceParser(); parser.open(file.getInputStream(entry)); StringBuilder sb=new StringBuilder(10); final String indentStep=" "; int type; while ((type=parser.next()) != XmlPullParser.END_DOCUMENT) { switch (type) { case XmlPullParser.START_DOCUMENT: { log(xmlSb,""); break; } case XmlPullParser.START_TAG: { log(false,xmlSb,"%s<%s%s",sb, getNamespacePrefix(parser.getPrefix()),parser.getName()); sb.append(indentStep); int namespaceCountBefore=parser.getNamespaceCount(parser.getDepth()-1); int namespaceCount=parser.getNamespaceCount(parser.getDepth()); for (int i=namespaceCountBefore;i!=namespaceCount;++i) { log(xmlSb,"%sxmlns:%s=\"%s\"", i==namespaceCountBefore?" ":sb, parser.getNamespacePrefix(i), parser.getNamespaceUri(i)); } for (int i=0,size=parser.getAttributeCount();i!=size;++i) { log(false,xmlSb, "%s%s%s=\"%s\""," ", getNamespacePrefix(parser.getAttributePrefix(i)), parser.getAttributeName(i), getAttributeValue(parser,i)); } // log("%s>",sb); log(xmlSb,">"); break; } case XmlPullParser.END_TAG: { sb.setLength(sb.length()-indentStep.length()); log(xmlSb,"%s",sb, getNamespacePrefix(parser.getPrefix()), parser.getName()); break; } case XmlPullParser.TEXT: { log(xmlSb,"%s%s",sb,parser.getText()); break; } } } parser.close(); } catch (Exception e) { e.printStackTrace(); } return xmlSb.toString(); } private static String getNamespacePrefix(String prefix) { if (prefix==null || prefix.length()==0) { return ""; } return prefix+":"; } public static String getAttributeValue(AXmlResourceParser parser,int index) { int type=parser.getAttributeValueType(index); int data=parser.getAttributeValueData(index); if (type==TypedValue.TYPE_STRING) { return parser.getAttributeValue(index); } if (type==TypedValue.TYPE_ATTRIBUTE) { return String.format("?%s%08X",getPackage(data),data); } if (type==TypedValue.TYPE_REFERENCE) { return String.format("@%s%08X",getPackage(data),data); } if (type==TypedValue.TYPE_FLOAT) { return String.valueOf(Float.intBitsToFloat(data)); } if (type==TypedValue.TYPE_INT_HEX) { return String.format("0x%08X",data); } if (type==TypedValue.TYPE_INT_BOOLEAN) { return data!=0?"true":"false"; } if (type==TypedValue.TYPE_DIMENSION) { return Float.toString(complexToFloat(data))+ DIMENSION_UNITS[data & TypedValue.COMPLEX_UNIT_MASK]; } if (type==TypedValue.TYPE_FRACTION) { return Float.toString(complexToFloat(data))+ FRACTION_UNITS[data & TypedValue.COMPLEX_UNIT_MASK]; } if (type>=TypedValue.TYPE_FIRST_COLOR_INT && type<=TypedValue.TYPE_LAST_COLOR_INT) { return String.format("#%08X",data); } if (type>=TypedValue.TYPE_FIRST_INT && type<=TypedValue.TYPE_LAST_INT) { return String.valueOf(data); } return String.format("<0x%X, type 0x%02X>",data,type); } private static String getPackage(int id) { if (id>>>24==1) { return "android:"; } return ""; } private static void log(StringBuilder xmlSb,String format,Object...arguments) { log(true,xmlSb, format, arguments); } private static void log(boolean newLine,StringBuilder xmlSb,String format,Object...arguments) { // System.out.printf(format,arguments); // if(newLine) System.out.println(); xmlSb.append(String.format(format, arguments)); if(newLine) xmlSb.append("\n"); } /////////////////////////////////// ILLEGAL STUFF, DONT LOOK :) public static float complexToFloat(int complex) { return (float)(complex & 0xFFFFFF00)*RADIX_MULTS[(complex>>4) & 3]; } private static final float RADIX_MULTS[]={ 0.00390625F,3.051758E-005F,1.192093E-007F,4.656613E-010F }; private static final String DIMENSION_UNITS[]={ "px","dip","sp","pt","in","mm","","" }; private static final String FRACTION_UNITS[]={ "%","%p","","","","","","" }; } LibScout-2.3.2/src/pxb/android/axml/AxmlReader.java000066400000000000000000000123371342431362400221020ustar00rootroot00000000000000/* * Copyright (c) 2009-2012 Panxiaobo * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package pxb.android.axml; import static pxb.android.axml.AxmlVisitor.TYPE_STRING; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Stack; import pxb.android.axml.AxmlVisitor.NodeVisitor; import pxb.android.axml.EmptyAdapter.EmptyNode; import com.googlecode.dex2jar.reader.io.DataIn; import com.googlecode.dex2jar.reader.io.LeArrayDataIn; /** * a class to read android axml * * @author Panxiaobo * */ public class AxmlReader { public static final NodeVisitor EMPTY_VISITOR = new EmptyNode() { @Override public NodeVisitor child(String ns, String name) { return EMPTY_VISITOR; } }; static final int UTF8_FLAG = 0x00000100; static final int CHUNK_AXML_FILE = 0x00080003; static final int CHUNK_RESOURCEIDS = 0x00080180; static final int CHUNK_STRINGS = 0x001C0001; static final int CHUNK_XML_END_NAMESPACE = 0x00100101; static final int CHUNK_XML_END_TAG = 0x00100103; static final int CHUNK_XML_START_NAMESPACE = 0x00100100; static final int CHUNK_XML_START_TAG = 0x00100102; static final int CHUNK_XML_TEXT = 0x00100104; private StringItems stringItems = new StringItems(); private List resourceIds = new ArrayList(); private DataIn in; public AxmlReader(byte[] data) { this(new LeArrayDataIn(data)); } public AxmlReader(DataIn in) { super(); this.in = in; } public void accept(final AxmlVisitor documentVisitor) throws IOException { DataIn in = this.in; int fileSize; { int type = in.readIntx(); if (type != CHUNK_AXML_FILE) { throw new RuntimeException(); } fileSize = in.readIntx(); } NodeVisitor root = documentVisitor == null ? EMPTY_VISITOR : new EmptyNode() { @Override public NodeVisitor child(String ns, String name) { return documentVisitor.first(ns, name); } }; NodeVisitor tos = root; Stack nvs = new Stack(); nvs.push(root); String name, ns; int nameIdx, nsIdx; int lineNumber; for (int p = in.getCurrentPosition(); p < fileSize; p = in.getCurrentPosition()) { int type = in.readIntx(); int size = in.readIntx(); switch (type) { case CHUNK_XML_START_TAG: { { lineNumber = in.readIntx(); in.skip(4);/* 0xFFFFFFFF */ nsIdx = in.readIntx(); nameIdx = in.readIntx(); int flag = in.readIntx();// 0x00140014 ? if (flag != 0x00140014) { throw new RuntimeException(); } name = stringItems.get(nameIdx).data; ns = nsIdx >= 0 ? stringItems.get(nsIdx).data : null; tos = tos.child(ns, name); if (tos == null) { tos = EMPTY_VISITOR; } nvs.push(tos); tos.line(lineNumber); } int attributeCount = in.readUShortx(); // int idAttribute = in.readUShortx(); // int classAttribute = in.readUShortx(); // int styleAttribute = in.readUShortx(); in.skip(6); if (tos != EMPTY_VISITOR) { for (int i = 0; i < attributeCount; i++) { nsIdx = in.readIntx(); nameIdx = in.readIntx(); in.skip(4);// skip valueString int aValueType = in.readIntx() >>> 24; int aValue = in.readIntx(); name = stringItems.get(nameIdx).data; ns = nsIdx >= 0 ? stringItems.get(nsIdx).data : null; Object value = aValueType == TYPE_STRING ? stringItems .get(aValue).data : aValue; int resourceId = nameIdx < resourceIds.size() ? resourceIds .get(nameIdx) : -1; tos.attr(ns, name, resourceId, aValueType, value); } } else { in.skip(5 * 4); } } break; case CHUNK_XML_END_TAG: { in.skip(size - 8); tos.end(); tos = nvs.pop(); } break; case CHUNK_XML_START_NAMESPACE: if (documentVisitor == null) { in.skip(4 * 4); } else { lineNumber = in.readIntx(); in.skip(4);/* 0xFFFFFFFF */ int prefixIdx = in.readIntx(); nsIdx = in.readIntx(); documentVisitor.ns(stringItems.get(prefixIdx).data, stringItems.get(nsIdx).data, lineNumber); } break; case CHUNK_XML_END_NAMESPACE: in.skip(size - 8); break; case CHUNK_STRINGS: stringItems.read(in, size); break; case CHUNK_RESOURCEIDS: int count = size / 4 - 2; for (int i = 0; i < count; i++) { resourceIds.add(in.readIntx()); } break; case CHUNK_XML_TEXT: if (tos == EMPTY_VISITOR) { in.skip(20); } else { lineNumber = in.readIntx(); in.skip(4);/* 0xFFFFFFFF */ nameIdx = in.readIntx(); in.skip(8); /* 00000008 00000000 */ name = stringItems.get(nameIdx).data; tos.text(lineNumber, name); } break; default: throw new RuntimeException(); } in.move(p + size); } } } LibScout-2.3.2/src/pxb/android/axml/AxmlVisitor.java000066400000000000000000000067101342431362400223350ustar00rootroot00000000000000/* * Copyright (c) 2009-2012 Panxiaobo * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package pxb.android.axml; /** * visitor to visit an axml * * @author Panxiaobo * */ public abstract class AxmlVisitor { public static final int TYPE_FIRST_INT = 0x10; public static final int TYPE_INT_BOOLEAN = 0x12; public static final int TYPE_INT_HEX = 0x11; public static final int TYPE_REFERENCE = 0x01; public static final int TYPE_STRING = 0x03; protected AxmlVisitor av; public AxmlVisitor(AxmlVisitor av) { super(); this.av = av; } public AxmlVisitor() { super(); } /** * create the first node * * @param ns * @param name * @return */ public NodeVisitor first(String ns, String name) { if (av != null) { return av.first(ns, name); } return null; }; /** * create a ns * * @param prefix * @param uri * @param ln */ public void ns(String prefix, String uri, int ln) { if (av != null) { av.ns(prefix, uri, ln); } }; /** * end the visit */ public void end() { if (av != null) { av.end(); } } public static abstract class NodeVisitor { protected NodeVisitor nv; public NodeVisitor(NodeVisitor nv) { super(); this.nv = nv; } public NodeVisitor() { super(); } /** * add attribute to the node * * @param ns * @param name * @param resourceId * @param type * {@link AxmlVisitor#TYPE_STRING} or others * @param obj * a string for {@link AxmlVisitor#TYPE_STRING} ,and Integer for others */ public void attr(String ns, String name, int resourceId, int type, Object obj) { if (nv != null) { nv.attr(ns, name, resourceId, type, obj); } } /** * create a child node * * @param ns * @param name * @return */ public NodeVisitor child(String ns, String name) { if (nv != null) { return nv.child(ns, name); } return null; } /** * the node text * * @param value */ public void text(int lineNumber, String value) { if (nv != null) { nv.text(lineNumber, value); } } /** * line number in the .xml * * @param ln */ public void line(int ln) { if (nv != null) { nv.line(ln); } } /** * end the visit */ public void end() { if (nv != null) { nv.end(); } } } } LibScout-2.3.2/src/pxb/android/axml/EmptyAdapter.java000066400000000000000000000022631342431362400224520ustar00rootroot00000000000000/* * Copyright (c) 2009-2012 Panxiaobo * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package pxb.android.axml; /** * do nothing to the visit * * @author Panxiaobo * */ public class EmptyAdapter extends AxmlVisitor { public EmptyAdapter() { super(null); } @Override public NodeVisitor first(String ns, String name) { return new EmptyNode(); } public static class EmptyNode extends NodeVisitor { public EmptyNode() { super(null); } @Override public NodeVisitor child(String ns, String name) { return new EmptyNode(); } } } LibScout-2.3.2/src/pxb/android/axml/StringItem.java000066400000000000000000000023271342431362400221410ustar00rootroot00000000000000/* * Copyright (c) 2009-2012 Panxiaobo * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package pxb.android.axml; class StringItem { public String data; public int dataOffset; public int index; public StringItem(String data) { super(); this.data = data; } public StringItem() { super(); } @Override public boolean equals(Object obj) { StringItem b = (StringItem) obj; return b.data.equals(data); } @Override public int hashCode() { return data.hashCode(); } public String toString() { return String.format("S%04d %s %d", index, data, dataOffset); } } LibScout-2.3.2/src/pxb/android/axml/StringItems.java000066400000000000000000000121151342431362400223200ustar00rootroot00000000000000/* * Copyright (c) 2009-2012 Panxiaobo * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package pxb.android.axml; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import java.util.TreeMap; import com.googlecode.dex2jar.reader.io.DataIn; import com.googlecode.dex2jar.reader.io.DataOut; @SuppressWarnings("serial") class StringItems extends ArrayList { byte[] stringData; public int getSize() { return 5 * 4 + this.size() * 4 + stringData.length + 0;// TODO } public void prepare() throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); int i = 0; int offset = 0; baos.reset(); Map map = new HashMap(); for (StringItem item : this) { item.index = i++; String stringData = item.data; Integer of = map.get(stringData); if (of != null) { item.dataOffset = of; } else { item.dataOffset = offset; map.put(stringData, offset); int length = stringData.length(); byte[] data = stringData.getBytes("UTF-16LE"); baos.write(length); baos.write(length >> 8); baos.write(data); baos.write(0); baos.write(0); offset += 4 + data.length; } } // TODO stringData = baos.toByteArray(); } public void read(DataIn in, int size) throws IOException { int trunkOffset = in.getCurrentPosition() - 4; int stringCount = in.readIntx(); int styleOffsetCount = in.readIntx(); int flags = in.readIntx(); int stringDataOffset = in.readIntx(); int stylesOffset = in.readIntx(); for (int i = 0; i < stringCount; i++) { StringItem stringItem = new StringItem(); stringItem.index = i; stringItem.dataOffset = in.readIntx(); this.add(stringItem); } Map stringMap = new TreeMap(); if (styleOffsetCount != 0) { throw new RuntimeException(); // for (int i = 0; i < styleOffsetCount; i++) { // StringItem stringItem = new StringItem(); // stringItem.index = i; // stringItems.add(stringItem); // } } int endOfStringData = stylesOffset == 0 ? size : stylesOffset; int base = in.getCurrentPosition(); if (0 != (flags & AxmlReader.UTF8_FLAG)) { for (int p = base; p < endOfStringData; p = in.getCurrentPosition()) { int length = (int) in.readLeb128(); if (length == 0) continue; // FIXME: sometimes the length determined above is negative, which causes the stream initializer to // throw an exception. we can set a fixed size here, which should work in most cases ByteArrayOutputStream bos = new ByteArrayOutputStream(100); // orig: (length + 10); for (int r = in.readByte(); r != 0; r = in.readByte()) { bos.write(r); } String value = new String(bos.toByteArray(), "UTF-8"); stringMap.put(p - base, value); } } else { for (int p = base; p < endOfStringData; p = in.getCurrentPosition()) { int length = in.readShortx(); byte[] data = in.readBytes(length * 2); in.skip(2); String value = new String(data, "UTF-16LE"); stringMap.put(p - base, value); // System.out.println(String.format("%08x %s", p - base, value)); } } if (stylesOffset != 0) { System.err.println("ignore style offset at 0x" + Integer.toHexString(trunkOffset)); } for (StringItem item : this) { item.data = stringMap.get(item.dataOffset); // System.out.println(item); } } public void write(DataOut out) throws IOException { out.writeInt(this.size()); out.writeInt(0);// TODO out.writeInt(0); out.writeInt(7 * 4 + this.size() * 4); out.writeInt(0); for (StringItem item : this) { out.writeInt(item.dataOffset); } out.writeBytes(stringData); // TODO } } LibScout-2.3.2/src/soot/000077500000000000000000000000001342431362400150175ustar00rootroot00000000000000LibScout-2.3.2/src/soot/jimple/000077500000000000000000000000001342431362400162775ustar00rootroot00000000000000LibScout-2.3.2/src/soot/jimple/infoflow/000077500000000000000000000000001342431362400201225ustar00rootroot00000000000000LibScout-2.3.2/src/soot/jimple/infoflow/android/000077500000000000000000000000001342431362400215425ustar00rootroot00000000000000LibScout-2.3.2/src/soot/jimple/infoflow/android/resources/000077500000000000000000000000001342431362400235545ustar00rootroot00000000000000LibScout-2.3.2/src/soot/jimple/infoflow/android/resources/ARSCFileParser.java000066400000000000000000001357461342431362400271440ustar00rootroot00000000000000/******************************************************************************* * Copyright (c) 2012 Secure Software Engineering Group at EC SPRIDE. * All rights reserved. This program and the accompanying materials * are made available under the terms of the GNU Lesser Public License v2.1 * which accompanies this distribution, and is available at * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html * * Contributors: Christian Fritz, Steven Arzt, Siegfried Rasthofer, Eric * Bodden, and others. ******************************************************************************/ package soot.jimple.infoflow.android.resources; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; /** * Parser for reading out the contents of Android's resource.arsc file. * Structure declarations and comments taken from the Android source * code and ported from C to Java. * * @author Steven Arzt */ public class ARSCFileParser extends AbstractResourceParser { private final static boolean DEBUG = false; protected final static int RES_STRING_POOL_TYPE = 0x0001; protected final static int RES_TABLE_TYPE = 0x0002; protected final static int RES_TABLE_PACKAGE_TYPE = 0x0200; protected final static int RES_TABLE_TYPE_SPEC_TYPE = 0x0202; protected final static int RES_TABLE_TYPE_TYPE = 0x0201; protected final static int SORTED_FLAG = 1<<0; protected final static int UTF8_FLAG = 1<<8; protected final static int SPEC_PUBLIC = 0x40000000; /** * Contains no data */ protected final static int TYPE_NULL = 0x00; /** * The 'data' holds a ResTable_ref, a reference to another resource table * entry. */ protected final static int TYPE_REFERENCE = 0x01; /** * The 'data' holds an attribute resource identifier. */ protected final static int TYPE_ATTRIBUTE = 0x02; /** * The 'data' holds an index into the containing resource table's global * value string pool. */ protected final static int TYPE_STRING = 0x03; /** * The 'data' holds a single-precision floating point number. */ protected final static int TYPE_FLOAT = 0x04; /** * The 'data' holds a complex number encoding a dimension value, such as * "100in". */ protected final static int TYPE_DIMENSION = 0x05; /** * The 'data' holds a complex number encoding a fraction of a container. */ protected final static int TYPE_FRACTION = 0x06; /** * Beginning of integer flavors... */ protected final static int TYPE_FIRST_INT = 0x10; /** * The 'data' is a raw integer value of the form n..n. */ protected final static int TYPE_INT_DEC = 0x10; /** * The 'data' is a raw integer value of the form 0xn..n. */ protected final static int TYPE_INT_HEX = 0x11; /** * The 'data' is either 0 or 1, for input "false" or "true" respectively. */ protected final static int TYPE_INT_BOOLEAN = 0x12; /** * Beginning of color integer flavors... */ protected final static int TYPE_FIRST_COLOR_INT = 0x1c; /** * The 'data' is a raw integer value of the form #aarrggbb. */ protected final static int TYPE_INT_COLOR_ARGB8 = 0x1c; /** * The 'data' is a raw integer value of the form #rrggbb. */ protected final static int TYPE_INT_COLOR_RGB8 = 0x1d; /** * The 'data' is a raw integer value of the form #argb. */ protected final static int TYPE_INT_COLOR_ARGB4 = 0x1e; /** * The 'data' is a raw integer value of the form #rgb. */ protected final static int TYPE_INT_COLOR_RGB4 = 0x1f; /** * ...end of integer flavors. */ protected final static int TYPE_LAST_COLOR_INT = 0x1f; /** * ...end of integer flavors. */ protected final static int TYPE_LAST_INT = 0x1f; /** * This entry holds the attribute's type code. */ protected final static int ATTR_TYPE = (0x01000000 | (0 & 0xFFFF)); /** * For integral attributes, this is the minimum value it can hold. */ protected final static int ATTR_MIN = (0x01000000 | (1 & 0xFFFF)); /** * For integral attributes, this is the maximum value it can hold. */ protected final static int ATTR_MAX = (0x01000000 | (2 & 0xFFFF)); /** * Localization of this resource is can be encouraged or required with * an aapt flag if this is set */ protected final static int ATTR_L10N = (0x01000000 | (3 & 0xFFFF)); // for plural support, see android.content.res.PluralRules#attrForQuantity(int) protected final static int ATTR_OTHER = (0x01000000 | (4 & 0xFFFF)); protected final static int ATTR_ZERO = (0x01000000 | (5 & 0xFFFF)); protected final static int ATTR_ONE = (0x01000000 | (6 & 0xFFFF)); protected final static int ATTR_TWO = (0x01000000 | (7 & 0xFFFF)); protected final static int ATTR_FEW = (0x01000000 | (8 & 0xFFFF)); protected final static int ATTR_MANY = (0x01000000 | (9 & 0xFFFF)); protected final static int NO_ENTRY = 0xFFFFFFFF; /** * Where the unit type information is. This gives us 16 possible types, as * defined below. */ protected final static int COMPLEX_UNIT_SHIFT = 0x0; protected final static int COMPLEX_UNIT_MASK = 0xf; /** * TYPE_DIMENSION: Value is raw pixels. */ protected final static int COMPLEX_UNIT_PX = 0; /** * TYPE_DIMENSION: Value is Device Independent Pixels. */ protected final static int COMPLEX_UNIT_DIP = 1; /** * TYPE_DIMENSION: Value is a Scaled device independent Pixels. */ protected final static int COMPLEX_UNIT_SP = 2; /** * TYPE_DIMENSION: Value is in points. */ protected final static int COMPLEX_UNIT_PT = 3; /** * TYPE_DIMENSION: Value is in inches. */ protected final static int COMPLEX_UNIT_IN = 4; /** * TYPE_DIMENSION: Value is in millimeters. */ protected final static int COMPLEX_UNIT_MM = 5; /** * TYPE_FRACTION: A basic fraction of the overall size. */ protected final static int COMPLEX_UNIT_FRACTION = 0; /** * TYPE_FRACTION: A fraction of the parent size. */ protected final static int COMPLEX_UNIT_FRACTION_PARENT = 1; /** * Where the radix information is, telling where the decimal place appears * in the mantissa. This give us 4 possible fixed point representations as * defined below. */ protected final static int COMPLEX_RADIX_SHIFT = 4; protected final static int COMPLEX_RADIX_MASK = 0x3; /** * The mantissa is an integral number -- i.e., 0xnnnnnn.0 */ protected final static int COMPLEX_RADIX_23p0 = 0; /** * The mantissa magnitude is 16 bits -- i.e, 0xnnnn.nn */ protected final static int COMPLEX_RADIX_16p7 = 1; /** * The mantissa magnitude is 8 bits -- i.e, 0xnn.nnnn */ protected final static int COMPLEX_RADIX_8p15 = 2; /** * The mantissa magnitude is 0 bits -- i.e, 0x0.nnnnnn */ protected final static int COMPLEX_RADIX_0p23 = 3; /** * Where the actual value is. This gives us 23 bits of precision. The top * bit is the sign. */ protected final static int COMPLEX_MANTISSA_SHIFT = 8; protected final static int COMPLEX_MANTISSA_MASK = 0xffffff; protected static final float MANTISSA_MULT = 1.0f / (1 << COMPLEX_MANTISSA_SHIFT); protected static final float[] RADIX_MULTS = new float[] { 1.0f * MANTISSA_MULT, 1.0f / (1<<7) * MANTISSA_MULT, 1.0f / (1<<15) * MANTISSA_MULT, 1.0f / (1<<23) * MANTISSA_MULT }; /** * If set, this is a complex entry, holding a set of name/value mappings. * It is followed by an array of ResTable_Map structures. */ public final static int FLAG_COMPLEX = 0x0001; /** * If set, this resource has been declared public, so libraries are * allowed to reference it. */ public final static int FLAG_PUBLIC = 0x0002; private final Map stringTable = new HashMap(); private final List packages = new ArrayList(); public class ResPackage { private int packageId; private String packageName; private List types = new ArrayList(); public int getPackageId() { return this.packageId; } public String getPackageName() { return this.packageName; } public List getDeclaredTypes() { return this.types; } } /** * A resource type in an Android resource file. All resources are associated * with a type. */ public class ResType { private int id; private String typeName; private List configurations = new ArrayList(); public String getTypeName() { return this.typeName; } public List getConfigurations() { return this.configurations; } /** * Gets a list of all resources in this type regardless of the * configuration. Resources sharing the same ID will only be returned * once, taking the value from the first applicable configuration. * @return A list of all resources of this type. */ public Collection getAllResources() { Map resources = new HashMap(); for (ResConfig rc : this.configurations) for (AbstractResource res : rc.getResources()) if (!resources.containsKey(res.resourceName)) resources.put(res.resourceName, res); return resources.values(); } /** * Gets the first resource with the given name or null if no such * resource exists * @param resourceName The resource name to look for * @return The first resource with the given name or null if no such * resource exists */ public AbstractResource getResourceByName(String resourceName) { for (ResConfig rc : this.configurations) for (AbstractResource res : rc.getResources()) if (res.getResourceName().equals(resourceName)) return res; return null; } /** * Gets the first resource of the current type that has the given name * @param resourceName The resource name to look for * @return The resource with the given name if it exists, otherwise * null */ public AbstractResource getFirstResource(String resourceName) { for (ResConfig rc : this.configurations) for (AbstractResource res : rc.getResources()) if (res.resourceName.equals(resourceName)) return res; return null; } /** * Gets the first resource of the current type with the given ID * @param resourceID The resource ID to look for * @return The resource with the given ID if it exists, otherwise * null */ public AbstractResource getFirstResource(int resourceID) { for (ResConfig rc : this.configurations) for (AbstractResource res : rc.getResources()) if (res.resourceID == resourceID) return res; return null; } @Override public String toString() { return this.typeName; } } /** * A configuration in an Android resource file. All resources are associated * with a configuration (which may be the default one). */ public class ResConfig { private List resources = new ArrayList(); public List getResources() { return this.resources; } } /** * Abstract base class for all Android resources. */ public abstract class AbstractResource { private String resourceName; private int resourceID; public String getResourceName() { return this.resourceName; } public int getResourceID() { return this.resourceID; } } /** * Android resource that does not contain any data */ public class NullResource extends AbstractResource { } /** * Android resource containing a reference to another resource. */ public class ReferenceResource extends AbstractResource { private int referenceID; public ReferenceResource(int id) { this.referenceID = id; } public int getReferenceID() { return this.referenceID; } } /** * Android resource containing an attribute resource identifier. */ public class AttributeResource extends AbstractResource { private int attributeID; public AttributeResource(int id) { this.attributeID = id; } public int getAttributeID() { return this.attributeID; } } /** * Android resource containing string data. */ public class StringResource extends AbstractResource { private String value; public StringResource(String value) { this.value = value; } public String getValue() { return this.value; } @Override public String toString() { return this.value; } } /** * Android resource containing integer data. */ public class IntegerResource extends AbstractResource { private int value; public IntegerResource(int value) { this.value = value; } public int getValue() { return this.value; } } /** * Android resource containing a single-precision floating point number */ public class FloatResource extends AbstractResource { private float value; public FloatResource(float value) { this.value = value; } public float getValue() { return this.value; } } /** * Android resource containing boolean data. */ public class BooleanResource extends AbstractResource { private boolean value; public BooleanResource(int value) { this.value = (value != 0); } public boolean getValue() { return this.value; } } /** * Android resource containing color data. */ public class ColorResource extends AbstractResource { private int a; private int r; private int g; private int b; public ColorResource(int a, int r, int g, int b) { this.a = a; this.r = r; this.g = g; this.b = b; } public int getA() { return this.a; } public int getR() { return this.r; } public int getG() { return this.g; } public int getB() { return this.b; } } /** * Enumeration containing the types of fractions supported in Android */ public enum FractionType { /** * A basic fraction of the overall size. */ Fraction, /** * A fraction of the parent size. */ FractionParent } /** * Android resource containing fraction data (e.g. element width relative to * some other control). */ public class FractionResource extends AbstractResource { private FractionType type; private float value; public FractionResource(FractionType type, float value) { this.type = type; this.value = value; } public FractionType getType() { return this.type; } public float getValue() { return this.value; } } /** * Enumeration containing all dimension units available in Android */ public enum Dimension { PX, DIP, SP, PT, IN, MM } /** * Android resource containing dimension data like "11pt". */ public class DimensionResource extends AbstractResource { private int value; private Dimension unit; public DimensionResource(int value, Dimension unit) { this.value = value; this.unit = unit; } DimensionResource(int dimension, int value) { this.value = value; switch (dimension) { case COMPLEX_UNIT_PX: this.unit = Dimension.PX; break; case COMPLEX_UNIT_DIP: this.unit = Dimension.DIP; break; case COMPLEX_UNIT_SP: this.unit = Dimension.SP; break; case COMPLEX_UNIT_PT: this.unit = Dimension.PT; break; case COMPLEX_UNIT_IN: this.unit = Dimension.IN; break; case COMPLEX_UNIT_MM: this.unit = Dimension.MM; break; default: throw new RuntimeException("Invalid dimension: " + dimension); } } public int getValue() { return this.value; } public Dimension getUnit() { return this.unit; } } /** * Android resource containing complex map data. */ public class ComplexResource extends AbstractResource { private Map value; public ComplexResource() { this.value = new HashMap(); } public ComplexResource(Map value) { this.value = value; } public Map getValue() { return this.value; } } protected class ResTable_Header { ResChunk_Header header = new ResChunk_Header(); /** * The number of ResTable_package structures */ int packageCount; // uint32 } /** * Header that appears at the front of every data chunk in a resource */ protected class ResChunk_Header { /** * Type identifier of this chunk. The meaning of this value depends on * the containing class. */ int type; // uint16 /** * Size of the chunk header (in bytes). Adding this value to the address * of the chunk allows you to find the associated data (if any). */ int headerSize; // uint16 /** * Total size of this chunk (in bytes). This is the chunkSize plus * the size of any data associated with the chunk. Adding this value * to the chunk allows you to completely skip its contents. If this * value is the same as chunkSize, there is no data associated with * the chunk. */ int size; // uint32 } protected class ResStringPool_Header { ResChunk_Header header; /** * Number of strings in this pool (number of uint32_t indices that follow * in the data). */ int stringCount; // uint32 /** * Number of style span arrays in the pool (number of uint32_t indices * follow the string indices). */ int styleCount; // uint32 /** * If set, the string index is sorted by the string values (based on * strcmp16()). */ boolean flagsSorted; // 1<<0 /** * String pool is encoded in UTF-8. */ boolean flagsUTF8; // 1<<8 /** * Index from the header of the string data. */ int stringsStart; // uint32 /** * Index from the header of the style data. */ int stylesStart; // uint32 } protected class ResTable_Package { ResChunk_Header header; /** * If this is the base package, its ID. Package IDs start at 1 * (corresponding to the value of the package bits in a resource * identifier). 0 means that this is not a base package. */ int id; // uint32 /** * Actual name of this package, \0-terminated */ String name; // char16 /** * Offset to a ResStringPool_Header defining the resource type symbol * table. If zero, this package is inheriting from another base package * (overriding specific values in it). */ int typeStrings; // uint32 /** * Last index into typeStrings that is for public use by others. */ int lastPublicType; // uint32 /** * Offset to a ResStringPool_Header defining the resource key symbol * table. If zero, this package is inheriting from another base package * (overriding specific values in it). */ int keyStrings; // uint32 /** * Last index into keyStrings that is for public use by others. */ int lastPublicKey; // uint32 } /** * A specification of the resources defined by a particular type. * * There should be one of these chunks for each resource type. * * This structure is followed by an array of integers providing the set of * configuration change flags (ResTable_Config::CONFIG_*) that have multiple * resources for that configuration. In addition, the high bit is set if * that resource has been made public. */ protected class ResTable_TypeSpec { ResChunk_Header header; /** * The type identifier this chunk is holding. Type IDs start at 1 * (corresponding to the value of the type bits in a resource * identifier). 0 is invalid. */ int id; // uint8 /** * Must be 0. */ int res0; // uint8 /** * Must be 1. */ int res1; // uint16 /** * Number of uint32_t entry configuration masks that follow. */ int entryCount; // uint32 } /** * A collection of resource entries for a particular resource data type. * Followed by an array of uint32_t defining the resource values, corresponding * to the array of type strings in the ResTable_Package::typeStrings string * block. Each of these hold an index from entriesStart; a value of NO_ENTRY * means that entry is not defined. * * There may be multiple of these chunks for a particular resource type, * supply different configuration variations for the resource values of * that type. * * It would be nice to have an additional ordered index of entries, so * we can do a binary search if trying to find a resource by string name. */ protected class ResTable_Type { ResChunk_Header header; /** * The type identifier this chunk is holding. Type IDs start at 1 * (corresponding to the value of the type bits in a resource * identifier). 0 is invalid. */ int id; // uint8 /** * Must be 0. */ int res0; // uint8 /** * Must be 1. */ int res1; // uint16 /** * Number of uint32_t entry indices that follow. */ int entryCount; // uint32 /** * Offset from header where ResTable_Entry data starts. */ int entriesStart; // uint32 /** * Configuration this collection of entries is designed for, */ ResTable_Config config = new ResTable_Config(); } /** * Describes a particular resource configuration. */ protected class ResTable_Config { /** * Number of bytes in this structure */ int size; // uint32 /** * Mobile country code (from SIM). "0" means any. */ int mmc; // uint16 /** * Mobile network code (from SIM). "0" means any. */ int mnc; // uint16 /** * \0\0 means "any". Otherwise, en, fr, etc. */ char[] language = new char[2]; // char[2] /** * \0\0 means "any". Otherwise, US, CA, etc. */ char[] country = new char[2]; // char[2] int orientation; // uint8 int touchscreen; // uint8 int density; // uint16 int keyboard; // uint8 int navigation; // uint8 int inputFlags; // uint8 int inputPad0; // uint8 int screenWidth; // uint16 int screenHeight; // uint16 int sdkVersion; // uint16 int minorVersion; // uint16 int screenLayout; // uint8 int uiMode; // uint8 int smallestScreenWidthDp; // uint16 int screenWidthDp; // uint16 int screenHeightDp; // uint16 } /** * This is the beginning of information about an entry in the resource table. * It holds the reference to the name of this entry, and is immediately * followed by one of: * * A Res_value structure, if FLAG_COMPLEX is -not- set * * An array of ResTable_Map structures, if FLAG_COMPLEX is set. * These supply a set of name/value mappings of data. */ protected class ResTable_Entry { /** * Number of bytes in this structure */ int size; // uint16 boolean flagsComplex; boolean flagsPublic; /** * Reference into ResTable_Package::KeyStrings identifying this entry. */ int key; } /** * Extended form of a ResTable_Entry for map entries, defining a parent map * resource from which to inherit values. */ protected class ResTable_Map_Entry extends ResTable_Entry { /** * Resource identifier of the parent mapping, or 0 if there is none. */ int parent; /** * Number of name/value pairs that follow for FLAG_COMPLEX. */ int count; // uint32 } /** * Representation of a value in a resource, supplying type information. */ protected class Res_Value { /** * Number of bytes in this structure. */ int size; // uint16 /** * Always set to 0. */ int res0; // uint8 int dataType; // uint8 /** * The data for this type, as interpreted according to dataType. */ int data; // uint16 } /** * A single name/value mapping that is part of a complex resource entry. */ protected class ResTable_Map { /** * The resource identifier defining this mapping's name. For attribute * resources, 'name' can be one of the following special resource types * to supply meta-data about the attribute; for all other resource types * it must be an attribute resource. */ int name; // uint32 /** * This mapping's value. */ Res_Value value = new Res_Value(); } /** * Class containing the data encoded in an Android resource ID */ public class ResourceId { private int packageId; private int typeId; private int itemIndex; public ResourceId(int packageId, int typeId, int itemIndex) { this.packageId = packageId; this.typeId = typeId; this.itemIndex = itemIndex; } public int getPackageId() { return this.packageId; } public int getTypeId() { return this.typeId; } public int getItemIndex() { return this.itemIndex; } @Override public String toString() { return "Package " + this.packageId + ", type " + this.typeId + ", item " + this.itemIndex; } } public ARSCFileParser() { } public void parse(String apkFile) throws IOException { this.handleAndroidResourceFiles(apkFile, null, new IResourceHandler() { @Override public void handleResourceFile(String fileName, Set fileNameFilter, InputStream stream) { try { if (fileName.equals("resources.arsc")) parse(stream); } catch (IOException ex) { System.err.println("Could not read resource file: " + ex.getMessage()); ex.printStackTrace(); } } }); } public void parse(InputStream stream) throws IOException { readResourceHeader(stream); } private void readResourceHeader(InputStream stream) throws IOException { final int BLOCK_SIZE = 2048; ResTable_Header resourceHeader = new ResTable_Header(); readChunkHeader(stream, resourceHeader.header); resourceHeader.packageCount = readUInt32(stream); if (DEBUG) System.out.println("Package Groups (" + resourceHeader.packageCount + ")"); // Do we have any packages to read? int remainingSize = resourceHeader.header.size - resourceHeader.header.headerSize; if (remainingSize <= 0) return; // Load the remaining data byte[] remainingData = new byte[remainingSize]; int totalBytesRead = 0; while (totalBytesRead < remainingSize) { byte[] block = new byte[Math.min(BLOCK_SIZE, remainingSize - totalBytesRead)]; int bytesRead = stream.read(block); if (bytesRead < 0) { System.err.println("Could not read block from resource file"); return; } System.arraycopy(block, 0, remainingData, totalBytesRead, bytesRead); totalBytesRead += bytesRead; } int offset = 0; int beforeBlock = 0; // Read the next chunk int packageCtr = 0; Map keyStrings = new HashMap(); Map typeStrings = new HashMap(); while (offset < remainingData.length - 1) { beforeBlock = offset; ResChunk_Header nextChunkHeader = new ResChunk_Header(); offset = readChunkHeader(nextChunkHeader, remainingData, offset); if (nextChunkHeader.type == RES_STRING_POOL_TYPE) { // Read the string pool header ResStringPool_Header stringPoolHeader = new ResStringPool_Header(); stringPoolHeader.header = nextChunkHeader; offset = parseStringPoolHeader(stringPoolHeader, remainingData, offset); // Read the string data offset = readStringTable(remainingData, offset, beforeBlock, stringPoolHeader, this.stringTable); assert this.stringTable.size() == stringPoolHeader.stringCount; } else if (nextChunkHeader.type == RES_TABLE_PACKAGE_TYPE) { // Read the package header ResTable_Package packageTable = new ResTable_Package(); packageTable.header = nextChunkHeader; offset = parsePackageTable(packageTable, remainingData, offset); if (DEBUG) System.out.println("\tPackage " + packageCtr + " id=" + packageTable.id + " name=" + packageTable.name); // Record the end of the object to know then to stop looking for // internal records int endOfRecord = beforeBlock + nextChunkHeader.size; // Create the data object and set the base data ResPackage resPackage = new ResPackage(); this.packages.add(resPackage); resPackage.packageId = packageTable.id; resPackage.packageName = packageTable.name; { // Find the type strings int typeStringsOffset = beforeBlock + packageTable.typeStrings; int beforeStringBlock = typeStringsOffset; ResChunk_Header typePoolHeader = new ResChunk_Header(); typeStringsOffset = readChunkHeader(typePoolHeader, remainingData, typeStringsOffset); if (typePoolHeader.type != RES_STRING_POOL_TYPE) throw new RuntimeException("Unexpected block type for package type strings"); ResStringPool_Header typePool = new ResStringPool_Header(); typePool.header = typePoolHeader; typeStringsOffset = parseStringPoolHeader(typePool, remainingData, typeStringsOffset); // Attention: String offset starts at the beginning of the StringPool // block, not the at the beginning of the Package block referring to it. readStringTable(remainingData, typeStringsOffset, beforeStringBlock, typePool, typeStrings); // Find the key strings int keyStringsOffset = beforeBlock + packageTable.keyStrings; beforeStringBlock = keyStringsOffset; ResChunk_Header keyPoolHeader = new ResChunk_Header(); keyStringsOffset = readChunkHeader(keyPoolHeader, remainingData, keyStringsOffset); if (keyPoolHeader.type != RES_STRING_POOL_TYPE) throw new RuntimeException("Unexpected block type for package key strings"); ResStringPool_Header keyPool = new ResStringPool_Header(); keyPool.header = keyPoolHeader; keyStringsOffset = parseStringPoolHeader(keyPool, remainingData, keyStringsOffset); // Attention: String offset starts at the beginning of the StringPool // block, not the at the beginning of the Package block referring to it. readStringTable(remainingData, keyStringsOffset, beforeStringBlock, keyPool, keyStrings); // Jump to the end of the string block offset = beforeStringBlock + keyPoolHeader.size; } while (offset < endOfRecord) { // Read the next inner block ResChunk_Header innerHeader = new ResChunk_Header(); int beforeInnerBlock = offset; offset = readChunkHeader(innerHeader, remainingData, offset); if (innerHeader.type == RES_TABLE_TYPE_SPEC_TYPE) { // Type specification block ResTable_TypeSpec typeSpecTable = new ResTable_TypeSpec(); typeSpecTable.header = innerHeader; offset = readTypeSpecTable(typeSpecTable, remainingData, offset); assert offset == beforeInnerBlock + typeSpecTable.header.headerSize; // Create the data object ResType tp = new ResType(); tp.id = typeSpecTable.id; tp.typeName = typeStrings.get(typeSpecTable.id - 1); resPackage.types.add(tp); // Normally, we also have a set of configurations following, but // we don't implement that at the moment } else if (innerHeader.type == RES_TABLE_TYPE_TYPE) { // Type resource entries. The id field maps to the type // for which we have a record. We create a mapping from // type IDs to declare resources. ResTable_Type typeTable = new ResTable_Type(); typeTable.header = innerHeader; offset = readTypeTable(typeTable, remainingData, offset); assert offset == beforeInnerBlock + typeTable.header.headerSize; // Create the data object ResType resType = null; for (ResType rt : resPackage.types) if (rt.id == typeTable.id) { resType = rt; break; } if (resType == null) throw new RuntimeException("Reference to undeclared type found"); ResConfig config = new ResConfig(); resType.configurations.add(config); // Read the table entries int resourceIdx = 0; for (int i = 0; i < typeTable.entryCount; i++) { int entryOffset = readUInt32(remainingData, offset); offset += 4; if (entryOffset == 0xFFFFFFFF) // NoEntry continue; entryOffset += beforeInnerBlock + typeTable.entriesStart; ResTable_Entry entry = readEntryTable(remainingData, entryOffset); entryOffset += entry.size; AbstractResource res; // If this is a simple entry, the data structure is // followed by RES_VALUE if (entry.flagsComplex) { ComplexResource cmpRes = new ComplexResource(); res = cmpRes; for (int j = 0; j < ((ResTable_Map_Entry) entry).count; j++) { ResTable_Map map = new ResTable_Map(); entryOffset = readComplexValue(map, remainingData, entryOffset); cmpRes.value.put(map.name + "", parseValue(map.value)); } } else { Res_Value val = new Res_Value(); entryOffset = readValue(val, remainingData, entryOffset); res = parseValue(val); if (res == null) { System.err.println("Could not parse resource " + keyStrings.get(entry.key) + " of type " + Integer.toHexString(val.dataType) + ", skipping entry"); continue; } } // Create the data object. For finding the correct ID, we // must check whether the entry is really new - if so, it // gets a new ID, otherwise, we reuse the old one if (keyStrings.containsKey(entry.key)) res.resourceName = keyStrings.get(entry.key); else res.resourceName = ""; AbstractResource r = resType.getResourceByName(res.resourceName); if (r != null) res.resourceID = r.resourceID; if (res.resourceID <= 0) res.resourceID = (packageTable.id << 24) + (typeTable.id << 16) + resourceIdx; config.resources.add(res); resourceIdx++; } } offset = beforeInnerBlock + innerHeader.size; } // Create the data objects for the types in the package for (ResType resType : resPackage.types) { if (DEBUG) { System.out.println("\t\tType " + resType.typeName + " " + (resType.id - 1) + ", configCount=" + resType.configurations.size() + ", entryCount=" + (resType.configurations.size() > 0 ? resType.configurations.get(0).resources.size() : 0)); for (ResConfig resConfig : resType.configurations) { System.out.println("\t\t\tconfig"); for (AbstractResource res : resConfig.resources) System.out.println("\t\t\t\tresource " + Integer.toHexString(res.resourceID) + " " + res.resourceName); } } } packageCtr++; } // Skip the block offset = beforeBlock + nextChunkHeader.size; remainingSize -= nextChunkHeader.size; } } /** * Checks whether the given complex map entry is one of the well-known * attributes. * @param map The map entry to check * @return True if the given entry is one of the well-known attributes, * otherwise false. */ protected boolean isAttribute(ResTable_Map map) { return map.name == ATTR_TYPE || map.name == ATTR_MIN || map.name == ATTR_MAX || map.name == ATTR_L10N || map.name == ATTR_OTHER || map.name == ATTR_ZERO || map.name == ATTR_ONE || map.name == ATTR_TWO || map.name == ATTR_FEW || map.name == ATTR_MANY; } /** * Taken from https://github.com/menethil/ApkTool/blob/master/src/android/util/TypedValue.java * @param complex * @return */ protected static float complexToFloat(int complex) { return (complex&(COMPLEX_MANTISSA_MASK << COMPLEX_MANTISSA_SHIFT)) * RADIX_MULTS[(complex>>COMPLEX_RADIX_SHIFT) & COMPLEX_RADIX_MASK]; } private AbstractResource parseValue(Res_Value val) { AbstractResource res; switch (val.dataType) { case TYPE_NULL: res = new NullResource(); break; case TYPE_REFERENCE: res = new ReferenceResource(val.data); break; case TYPE_ATTRIBUTE: res = new AttributeResource(val.data); break; case TYPE_STRING : res = new StringResource(stringTable.get(val.data)); break; case TYPE_INT_DEC: case TYPE_INT_HEX: res = new IntegerResource(val.data); break; case TYPE_INT_BOOLEAN: res = new BooleanResource(val.data); break; case TYPE_INT_COLOR_ARGB8: case TYPE_INT_COLOR_RGB8: case TYPE_INT_COLOR_ARGB4: case TYPE_INT_COLOR_RGB4: res = new ColorResource(val.data & 0xFF000000 >> 3 * 8, val.data & 0x00FF0000 >> 2 * 8, val.data & 0x0000FF00 >> 8, val.data & 0x000000FF); break; case TYPE_DIMENSION: res = new DimensionResource(val.data & COMPLEX_UNIT_MASK, val.data >> COMPLEX_UNIT_SHIFT); break; case TYPE_FLOAT: res = new FloatResource(Float.intBitsToFloat(val.data)); break; case TYPE_FRACTION: int fracType = (val.data >> COMPLEX_UNIT_SHIFT) & COMPLEX_UNIT_MASK; float data = complexToFloat(val.data); if (fracType == COMPLEX_UNIT_FRACTION) res = new FractionResource(FractionType.Fraction, data); else res = new FractionResource(FractionType.FractionParent, data); break; default: return null; } return res; } private int readComplexValue (ResTable_Map map, byte[] remainingData, int offset) throws IOException { map.name = readUInt32(remainingData, offset); offset += 4; return readValue(map.value, remainingData, offset); } private int readValue (Res_Value val, byte[] remainingData, int offset) throws IOException { int initialOffset = offset; val.size = readUInt16(remainingData, offset); offset += 2; if (val.size > 8) // This should always be 8. Check to not fail on broken resources in apps return 0; val.res0 = readUInt8(remainingData, offset); if (val.res0 != 0) throw new RuntimeException("File format error, res0 was not zero"); offset += 1; val.dataType = readUInt8(remainingData, offset); offset += 1; val.data = readUInt32(remainingData, offset); offset += 4; assert offset == initialOffset + val.size; return offset; } private ResTable_Entry readEntryTable(byte[] data, int offset) throws IOException { // The exact type of entry depends on the size int size = readUInt16(data, offset); offset += 2; ResTable_Entry entry; if (size == 0x8) entry = new ResTable_Entry(); else if (size == 0x10) entry = new ResTable_Map_Entry(); else throw new RuntimeException("Unknown entry type"); entry.size = size; int flags = readUInt16(data, offset); offset += 2; entry.flagsComplex = (flags & FLAG_COMPLEX) == FLAG_COMPLEX; entry.flagsPublic = (flags & FLAG_PUBLIC) == FLAG_PUBLIC; entry.key = readUInt32(data, offset); offset += 4; if (entry instanceof ResTable_Map_Entry) { ResTable_Map_Entry mapEntry = (ResTable_Map_Entry) entry; mapEntry.parent = readUInt32(data, offset); offset += 4; mapEntry.count = readUInt32(data, offset); offset += 4; } return entry; } private int readTypeTable (ResTable_Type typeTable, byte[] data, int offset) throws IOException { typeTable.id = readUInt8(data, offset); offset += 1; typeTable.res0 = readUInt8(data, offset); if (typeTable.res0 != 0) throw new RuntimeException("File format error, res0 was not zero"); offset += 1; typeTable.res1 = readUInt16(data, offset); if (typeTable.res1 != 0) throw new RuntimeException("File format error, res1 was not zero"); offset += 2; typeTable.entryCount = readUInt32(data, offset); offset += 4; typeTable.entriesStart = readUInt32(data, offset); offset += 4; return readConfigTable(typeTable.config, data, offset); } private int readConfigTable (ResTable_Config config, byte[] data, int offset) throws IOException { config.size = readUInt32(data, offset); offset += 4; config.mmc = readUInt16(data, offset); offset += 2; config.mnc = readUInt16(data, offset); offset += 2; config.language[0] = (char) data[offset]; config.language[1] = (char) data[offset + 1]; offset += 2; config.country[0] = (char) data[offset]; config.country[1] = (char) data[offset + 1]; offset += 2; config.orientation = readUInt8(data, offset); offset += 1; config.touchscreen = readUInt8(data, offset); offset += 1; config.density = readUInt16(data, offset); offset += 2; config.keyboard = readUInt8(data, offset); offset += 1; config.navigation = readUInt8(data, offset); offset += 1; config.inputFlags = readUInt8(data, offset); offset += 1; config.inputPad0 = readUInt8(data, offset); offset += 1; config.screenWidth = readUInt16(data, offset); offset += 2; config.screenHeight= readUInt16(data, offset); offset += 2; config.sdkVersion = readUInt16(data, offset); offset += 2; config.minorVersion = readUInt16(data, offset); offset += 2; if (config.size <= 28) return offset; config.screenLayout = readUInt8(data, offset); offset += 1; config.uiMode = readUInt8(data, offset); offset += 1; config.smallestScreenWidthDp = readUInt16(data, offset); offset += 2; if (config.size <= 32) return offset; config.screenWidthDp = readUInt16(data, offset); offset += 2; config.screenHeightDp = readUInt16(data, offset); offset += 2; return offset; } private int readTypeSpecTable (ResTable_TypeSpec typeSpecTable, byte[] data, int offset) throws IOException { typeSpecTable.id = readUInt8(data, offset); offset += 1; typeSpecTable.res0 = readUInt8(data, offset); offset += 1; if (typeSpecTable.res0 != 0) throw new RuntimeException("File format violation, res0 was not zero"); typeSpecTable.res1 = readUInt16(data, offset); offset += 2; if (typeSpecTable.res1 != 0) throw new RuntimeException("File format violation, res1 was not zero"); typeSpecTable.entryCount = readUInt32(data, offset); offset += 4; return offset; } private int readStringTable (byte[] remainingData, int offset, int blockStart, ResStringPool_Header stringPoolHeader, Map stringList) throws IOException { // Read the strings for (int i = 0; i < stringPoolHeader.stringCount; i++) { int stringIdx = readUInt32(remainingData, offset); offset += 4; // Offset begins at block start stringIdx += stringPoolHeader.stringsStart + blockStart; String str = ""; if (stringPoolHeader.flagsUTF8) str = readStringUTF8(remainingData, stringIdx).trim(); else str = readString(remainingData, stringIdx).trim(); stringList.put(i, str); } return offset; } private int parsePackageTable (ResTable_Package packageTable, byte[] data, int offset) throws IOException { packageTable.id = readUInt32(data, offset); offset += 4; // Read the package name, zero-terminated string StringBuilder bld = new StringBuilder(); for (int i = 0; i < 128; i++) { int curChar = readUInt16(data, offset); bld.append((char) curChar); offset += 2; } packageTable.name = bld.toString().trim(); packageTable.typeStrings = readUInt32(data, offset); offset += 4; packageTable.lastPublicType = readUInt32(data, offset); offset += 4; packageTable.keyStrings = readUInt32(data, offset); offset += 4; packageTable.lastPublicKey = readUInt32(data, offset); offset += 4; return offset; } private String readString(byte[] remainingData, int stringIdx) throws IOException { int strLen = readUInt16(remainingData, stringIdx); if (strLen == 0) return ""; stringIdx += 2; byte[] str = new byte[strLen * 2]; System.arraycopy(remainingData, stringIdx, str, 0, strLen * 2); return new String(remainingData, stringIdx, strLen * 2, "UTF-16LE"); } private String readStringUTF8(byte[] remainingData, int stringIdx) throws IOException { // skip the length, will usually be 0x1A1A // int strLen = readUInt16(remainingData, stringIdx); // the length here is somehow weird int strLen = readUInt8(remainingData, stringIdx + 1); stringIdx += 2; String str = new String(remainingData, stringIdx, strLen, "UTF-8"); return str; } private int parseStringPoolHeader (ResStringPool_Header stringPoolHeader, byte[] data, int offset) throws IOException { stringPoolHeader.stringCount = readUInt32(data, offset); stringPoolHeader.styleCount = readUInt32(data, offset + 4); int flags = readUInt32(data, offset + 8); stringPoolHeader.flagsSorted = (flags & SORTED_FLAG) == SORTED_FLAG; stringPoolHeader.flagsUTF8 = (flags & UTF8_FLAG) == UTF8_FLAG; stringPoolHeader.stringsStart = readUInt32(data, offset + 12); stringPoolHeader.stylesStart = readUInt32(data, offset + 16); return offset + 20; } /** * Reads a chunk header from the input stream and stores the data in the * given object. * @param stream The stream from which to read the chunk header * @param nextChunkHeader The data object in which to put the chunk header * @throws IOException Thrown if an error occurs during read */ private void readChunkHeader (InputStream stream, ResChunk_Header nextChunkHeader) throws IOException { byte[] header = new byte[8]; stream.read(header); readChunkHeader(nextChunkHeader, header, 0); } /** * Reads a chunk header from the input stream and stores the data in the * given object. * @param nextChunkHeader The data object in which to put the chunk header * @param data The data array containing the structure * @param offset The offset from which to start reading * @throws IOException Thrown if an error occurs during read */ private int readChunkHeader (ResChunk_Header nextChunkHeader, byte[] data, int offset) throws IOException { nextChunkHeader.type = readUInt16(data, offset); offset += 2; nextChunkHeader.headerSize = readUInt16(data, offset); offset += 2; nextChunkHeader.size = readUInt32(data, offset); offset += 4; return offset; } private int readUInt8(byte[] uint16, int offset) throws IOException { int b0 = uint16[0 + offset] & 0x000000FF; return b0; } private int readUInt16(byte[] uint16, int offset) throws IOException { int b0 = uint16[0 + offset] & 0x000000FF; int b1 = uint16[1 + offset] & 0x000000FF; return (b1 << 8) + b0; } private int readUInt32(InputStream stream) throws IOException { byte[] uint32 = new byte[4]; stream.read(uint32); return readUInt32(uint32, 0); } private int readUInt32(byte[] uint32, int offset) throws IOException { int b0 = uint32[0 + offset] & 0x000000FF; int b1 = uint32[1 + offset] & 0x000000FF; int b2 = uint32[2 + offset] & 0x000000FF; int b3 = uint32[3 + offset] & 0x000000FF; return (Math.abs(b3) << 24) + (Math.abs(b2) << 16) + (Math.abs(b1) << 8) + Math.abs(b0); } public Map getGlobalStringPool() { return this.stringTable; } public List getPackages() { return this.packages; } /** * Finds the resource with the given Android resource ID. This method is * configuration-agnostic and simply returns the first match it finds. * @param resourceId The Android resource ID for which to the find the * resource object * @return The resource object with the given Android resource ID if it * has been found, otherwise null. */ public AbstractResource findResource(int resourceId) { ResourceId id = parseResourceId(resourceId); for (ResPackage resPackage : this.packages) if (resPackage.packageId == id.packageId) { for (ResType resType : resPackage.types) if (resType.id == id.typeId) { return resType.getFirstResource(resourceId); } break; } return null; } /** * Parses an Android resource ID into its components * @param resourceId The numeric resource ID to parse * @return The data contained in the given Android resource ID */ public ResourceId parseResourceId(int resourceId) { return new ResourceId((resourceId & 0xFF000000) >> 24, (resourceId & 0x00FF0000) >> 16, resourceId & 0x0000FFFF); } } LibScout-2.3.2/src/soot/jimple/infoflow/android/resources/AbstractResourceParser.java000066400000000000000000000043231342431362400310510ustar00rootroot00000000000000/******************************************************************************* * Copyright (c) 2012 Secure Software Engineering Group at EC SPRIDE. * All rights reserved. This program and the accompanying materials * are made available under the terms of the GNU Lesser Public License v2.1 * which accompanies this distribution, and is available at * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html * * Contributors: Christian Fritz, Steven Arzt, Siegfried Rasthofer, Eric * Bodden, and others. ******************************************************************************/ package soot.jimple.infoflow.android.resources; import java.io.File; import java.util.Enumeration; import java.util.Set; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; /** * Common base class for all resource parser classes * * @author Steven Arzt */ public abstract class AbstractResourceParser { /** * Opens the given apk file and provides the given handler with a stream for * accessing the contained resource manifest files * @param apk The apk file to process * @param fileNameFilter If this parameter is non-null, only files with a * name (excluding extension) in this set will be analyzed. * @param handler The handler for processing the apk file * * @author Steven Arzt */ protected void handleAndroidResourceFiles(String apk, Set fileNameFilter, IResourceHandler handler) { File apkF = new File(apk); if (!apkF.exists()) throw new RuntimeException("file '" + apk + "' does not exist!"); try { ZipFile archive = null; try { archive = new ZipFile(apkF); Enumeration entries = archive.entries(); while (entries.hasMoreElements()) { ZipEntry entry = (ZipEntry) entries.nextElement(); String entryName = entry.getName(); handler.handleResourceFile(entryName, fileNameFilter, archive.getInputStream(entry)); } } finally { if (archive != null) archive.close(); } } catch (Exception e) { System.err.println("Error when looking for XML resource files in apk " + apk + ": " + e); e.printStackTrace(); if (e instanceof RuntimeException) throw (RuntimeException) e; else throw new RuntimeException(e); } } } LibScout-2.3.2/src/soot/jimple/infoflow/android/resources/IResourceHandler.java000066400000000000000000000024151342431362400276170ustar00rootroot00000000000000/******************************************************************************* * Copyright (c) 2012 Secure Software Engineering Group at EC SPRIDE. * All rights reserved. This program and the accompanying materials * are made available under the terms of the GNU Lesser Public License v2.1 * which accompanies this distribution, and is available at * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html * * Contributors: Christian Fritz, Steven Arzt, Siegfried Rasthofer, Eric * Bodden, and others. ******************************************************************************/ package soot.jimple.infoflow.android.resources; import java.io.InputStream; import java.util.Set; /** * Common interface for handlers working on Android resource XML files * * @author Steven Arzt * */ public interface IResourceHandler { /** * Called when the contents of an Android resource file shall be processed * @param fileName The name of the file in the APK being processed * @param fileNameFilter A list of names to be used for filtering the files * in the APK that actually get processed. * @param stream The stream through which the resource file can be accesses */ public void handleResourceFile(String fileName, Set fileNameFilter, InputStream stream); }