├── .github
└── workflows
│ └── rust.yml
├── .gitignore
├── Cargo.toml
├── README.md
├── apk_analyzer
├── .gitignore
├── .idea
│ ├── .gitignore
│ ├── .name
│ ├── codeStyles
│ │ ├── Project.xml
│ │ └── codeStyleConfig.xml
│ ├── compiler.xml
│ ├── jarRepositories.xml
│ ├── kotlinc.xml
│ ├── misc.xml
│ ├── runConfigurations.xml
│ └── vcs.xml
├── README.md
├── build.gradle.kts
├── gradle.properties
├── gradle
│ └── wrapper
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── settings.gradle.kts
└── src
│ ├── main
│ └── kotlin
│ │ └── com
│ │ └── ammaraskar
│ │ └── intent
│ │ └── fuzz
│ │ ├── ApkAnalyzer.kt
│ │ ├── IntentExtraUsageTreeVisitor.kt
│ │ ├── IntentReceiver.kt
│ │ ├── IntentTemplate.kt
│ │ └── Main.kt
│ └── test
│ └── kotlin
│ └── com
│ └── ammaraskar
│ └── intent
│ └── fuzz
│ ├── ApkAnalyzerTest.kt
│ └── IntentReceiverTest.kt
├── content_provider
├── .gitignore
├── .idea
│ ├── .gitignore
│ ├── .name
│ ├── compiler.xml
│ ├── gradle.xml
│ ├── kotlinc.xml
│ ├── misc.xml
│ └── vcs.xml
├── README.md
├── app
│ ├── .gitignore
│ ├── build.gradle
│ ├── proguard-rules.pro
│ └── src
│ │ └── main
│ │ ├── AndroidManifest.xml
│ │ ├── java
│ │ └── org
│ │ │ └── gts3
│ │ │ └── jnifuzz
│ │ │ └── contentprovider
│ │ │ ├── MainActivity.kt
│ │ │ └── UriPermissionManager.kt
│ │ └── res
│ │ ├── drawable-v24
│ │ └── ic_launcher_foreground.xml
│ │ ├── drawable
│ │ └── ic_launcher_background.xml
│ │ ├── layout
│ │ └── activity_main.xml
│ │ ├── mipmap-anydpi-v26
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── values-night
│ │ └── themes.xml
│ │ ├── values
│ │ ├── colors.xml
│ │ ├── strings.xml
│ │ └── themes.xml
│ │ └── xml
│ │ ├── backup_rules.xml
│ │ ├── data_extraction_rules.xml
│ │ └── provider_paths.xml
├── build.gradle
├── gradle.properties
├── gradle
│ └── wrapper
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
├── eval
├── .gitignore
├── collect_edgecount_data.sh
├── compute_metrics.py
├── generate_configs.sh
└── tmp.sh
├── intent_template.json
├── patches
└── ActivityThread.patch
├── run.sh
├── run_multiple_apps.sh
├── rust-toolchain.toml
├── src
├── adb_device.rs
├── adb_executor.rs
├── intent_generator.rs
├── intent_input.rs
├── intent_mutator.rs
├── main.rs
├── socket_coverage_observer.rs
└── util.rs
└── test_coverage_agent
├── README.md
└── test_server.py
/.github/workflows/rust.yml:
--------------------------------------------------------------------------------
1 | name: Rust
2 |
3 | on:
4 | workflow_dispatch:
5 | push:
6 | branches: [ "master" ]
7 | pull_request:
8 | branches: [ "master" ]
9 |
10 | env:
11 | CARGO_TERM_COLOR: always
12 |
13 | jobs:
14 | build:
15 |
16 | runs-on: ubuntu-latest
17 |
18 | steps:
19 | - uses: actions/checkout@v3
20 | - name: Check style
21 | run: cargo fmt --check
22 | - name: Build
23 | run: cargo build --verbose
24 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Generated by Cargo
2 | # will have compiled files and executables
3 | debug/
4 | target/
5 |
6 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
7 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
8 | Cargo.lock
9 |
10 | # These are backup files generated by rustfmt
11 | **/*.rs.bk
12 |
13 | # MSVC Windows builds of rustc generate these, which store debugging information
14 | *.pdb
15 |
16 | # Ammar's notes
17 | PERSONAL_NOTES.md
18 |
19 | # Byte-compiled / optimized / DLL files
20 | __pycache__/
21 | *.py[cod]
22 | *$py.class
23 |
24 | # Crashes and corpus directory
25 | crashes/
26 | corpus/
27 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "intent_fuzzer_lib_afl"
3 | version = "0.1.0"
4 | edition = "2021"
5 | # libafl needs at least rust 1.64 for let-else statements
6 | rust-version = "1.64"
7 |
8 | [dependencies]
9 | libafl = "0.10.0"
10 | # Needed to implement a custom Input for libAFL
11 | serde = { version = "1.0", default-features = false, features = ["alloc", "derive"] }
12 | # Needed to parse intent_template.json file
13 | serde_json = { version = "1.0", default-features = false, features = ["alloc"] }
14 | # For parsing command line args
15 | clap = { version = "4.0", features = ["derive"] }
16 | # Include strum_macros
17 | strum = "0.24"
18 | strum_macros = "0.24"
19 | # Include fasthash
20 | fasthash = "0.4.0"
21 | # Include TempDir
22 | tempfile = "3.6.0"
23 | # Include futures
24 | futures = { version = "0.3", features = ["compat"] }
25 | # Include subprocess
26 | subprocess = { version = "0.2" }
27 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # IntentFuzzer - libAFL version
2 |
3 | This is an automated greybox fuzzer for Intent receivers on Android.
4 |
5 | ## How To Use
6 |
7 | `cargo run -- --help`
8 |
9 | ## Architecture
10 |
11 | ```
12 | Fuzzer Android Device/Emulator
13 | ┌───────────┐ ┌──────────────────┐
14 | │ │ TCP Port │ App │
15 | │ Collects │ over ADB │ ┌──────────────┐ │
16 | │ coverage ◄├─────────────┼►┤Coverage Agent│ │
17 | │ │ │ ├──────────────┤ │
18 | │ │ │ │ │ │
19 | │ │ │ │ │ │
20 | │ │ │ │ │ │
21 | │ │ │ │ │ │
22 | │ │ │ └──────▲───────┘ │
23 | │ │ │ │ │
24 | │ Mutates │ │ │(Intents)│
25 | │ intents │Sends Intents├────────┴─────────┤
26 | └───────────┴────────────►│ Android Activity │
27 | │ Manager │
28 | └──────────────────┘
29 | ```
30 |
31 | ## Project Structure
32 |
33 | [AndroidCoverageAgent](https://github.com/sslab-gatech/AndroidCoverageAgent) is
34 | used to instrument apps on-device or on-emulator for coverage feedback.
35 |
36 | The `apk_analyzer` subfolder contains a Kotlin project that uses the
37 | [jadx](https://github.com/skylot/jadx) API to analyze an apk file and create
38 | an `intent_template.json` file from it.
39 |
40 | The root folder `.` contains the fuzzer written in Rust using
41 | [libafl](https://github.com/AFLplusplus/LibAFL) to implement the fuzzing loop
42 | and uses the generated `intent_template.json` and `adb` to communicate with the
43 | coverage agent in the Android environment.
44 |
--------------------------------------------------------------------------------
/apk_analyzer/.gitignore:
--------------------------------------------------------------------------------
1 | # Compiled class file
2 | *.class
3 |
4 | # Log file
5 | *.log
6 |
7 | # BlueJ files
8 | *.ctxt
9 |
10 | # Mobile Tools for Java (J2ME)
11 | .mtj.tmp/
12 |
13 | # Package Files #
14 | *.jar
15 | *.war
16 | *.nar
17 | *.ear
18 | *.zip
19 | *.tar.gz
20 | *.rar
21 |
22 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
23 | hs_err_pid*
24 | replay_pid*
25 |
26 | .gradle
27 | **/build/
28 | !src/**/build/
29 |
30 | # Ignore Gradle GUI config
31 | gradle-app.setting
32 |
33 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
34 | !gradle-wrapper.jar
35 |
36 | # Avoid ignore Gradle wrappper properties
37 | !gradle-wrapper.properties
38 |
39 | # Cache of project
40 | .gradletasknamecache
41 |
42 | # Eclipse Gradle plugin generated files
43 | # Eclipse Core
44 | .project
45 | # JDT-specific (Eclipse Java Development Tools)
46 | .classpath
47 |
48 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
49 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
50 |
51 | # User-specific stuff
52 | .idea/**/workspace.xml
53 | .idea/**/tasks.xml
54 | .idea/**/usage.statistics.xml
55 | .idea/**/dictionaries
56 | .idea/**/shelf
57 |
58 | # AWS User-specific
59 | .idea/**/aws.xml
60 |
61 | # Generated files
62 | .idea/**/contentModel.xml
63 |
64 | # Sensitive or high-churn files
65 | .idea/**/dataSources/
66 | .idea/**/dataSources.ids
67 | .idea/**/dataSources.local.xml
68 | .idea/**/sqlDataSources.xml
69 | .idea/**/dynamic.xml
70 | .idea/**/uiDesigner.xml
71 | .idea/**/dbnavigator.xml
72 |
73 | # Gradle
74 | .idea/**/gradle.xml
75 | .idea/**/libraries
76 |
77 | # Gradle and Maven with auto-import
78 | # When using Gradle or Maven with auto-import, you should exclude module files,
79 | # since they will be recreated, and may cause churn. Uncomment if using
80 | # auto-import.
81 | # .idea/artifacts
82 | # .idea/compiler.xml
83 | # .idea/jarRepositories.xml
84 | # .idea/modules.xml
85 | # .idea/*.iml
86 | # .idea/modules
87 | # *.iml
88 | # *.ipr
89 |
90 | # CMake
91 | cmake-build-*/
92 |
93 | # Mongo Explorer plugin
94 | .idea/**/mongoSettings.xml
95 |
96 | # File-based project format
97 | *.iws
98 |
99 | # IntelliJ
100 | out/
101 |
102 | # mpeltonen/sbt-idea plugin
103 | .idea_modules/
104 |
105 | # JIRA plugin
106 | atlassian-ide-plugin.xml
107 |
108 | # Cursive Clojure plugin
109 | .idea/replstate.xml
110 |
111 | # SonarLint plugin
112 | .idea/sonarlint/
113 |
114 | # Crashlytics plugin (for Android Studio and IntelliJ)
115 | com_crashlytics_export_strings.xml
116 | crashlytics.properties
117 | crashlytics-build.properties
118 | fabric.properties
119 |
120 | # Editor-based Rest Client
121 | .idea/httpRequests
122 |
123 | # Android studio 3.1+ serialized cache file
124 | .idea/caches/build_file_checksums.ser
125 |
126 | # Android studio local properties
127 | local.properties
128 |
--------------------------------------------------------------------------------
/apk_analyzer/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 | # Datasource local storage ignored files
5 | /dataSources/
6 | /dataSources.local.xml
7 | # Editor-based HTTP Client requests
8 | /httpRequests/
9 |
--------------------------------------------------------------------------------
/apk_analyzer/.idea/.name:
--------------------------------------------------------------------------------
1 | ApkAnalyzer
--------------------------------------------------------------------------------
/apk_analyzer/.idea/codeStyles/Project.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/apk_analyzer/.idea/codeStyles/codeStyleConfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/apk_analyzer/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/apk_analyzer/.idea/jarRepositories.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/apk_analyzer/.idea/kotlinc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/apk_analyzer/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/apk_analyzer/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
--------------------------------------------------------------------------------
/apk_analyzer/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/apk_analyzer/README.md:
--------------------------------------------------------------------------------
1 | # Apk Analyzer
2 |
3 | This is a Kotlin project that takes an Android `.apk` file, analyzes the
4 | `AndroidManifest.xml` and performs some basic static analysis to generate an
5 | `intent_template.json` file to be used with the IntentFuzzer.
6 |
7 | ## Usage
8 |
9 | ```bash
10 | gradle run --args="/path/to/application.apk /path/to/output/directory/"
11 | ```
12 |
--------------------------------------------------------------------------------
/apk_analyzer/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
2 |
3 | plugins {
4 | kotlin("jvm") version "1.7.10"
5 | kotlin("plugin.serialization") version "1.7.10"
6 | application
7 | }
8 |
9 | group = "com.ammaraskar"
10 | version = "1.0-SNAPSHOT"
11 |
12 | repositories {
13 | mavenCentral()
14 | google()
15 | }
16 |
17 | dependencies {
18 | implementation("io.github.skylot:jadx-core:1.4.7")
19 | implementation("io.github.skylot:jadx-dex-input:1.4.7")
20 |
21 | // Needed for jadx's logging library.
22 | implementation("org.slf4j:slf4j-simple:2.0.7")
23 |
24 | implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0")
25 |
26 | testImplementation(kotlin("test"))
27 | }
28 |
29 | tasks.test {
30 | useJUnit()
31 | }
32 |
33 | tasks.withType() {
34 | kotlinOptions.jvmTarget = "1.8"
35 | }
36 |
37 | application {
38 | mainClass.set("com.ammaraskar.intent.fuzz.MainKt")
39 | }
--------------------------------------------------------------------------------
/apk_analyzer/gradle.properties:
--------------------------------------------------------------------------------
1 | kotlin.code.style=official
2 |
--------------------------------------------------------------------------------
/apk_analyzer/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sslab-gatech/MALintent/3bb0d77f66beb224ab86417bd1f5a1935451e44e/apk_analyzer/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/apk_analyzer/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 |
--------------------------------------------------------------------------------
/apk_analyzer/gradlew:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | #
4 | # Copyright © 2015-2021 the original authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | #
21 | # Gradle start up script for POSIX generated by Gradle.
22 | #
23 | # Important for running:
24 | #
25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
26 | # noncompliant, but you have some other compliant shell such as ksh or
27 | # bash, then to run this script, type that shell name before the whole
28 | # command line, like:
29 | #
30 | # ksh Gradle
31 | #
32 | # Busybox and similar reduced shells will NOT work, because this script
33 | # requires all of these POSIX shell features:
34 | # * functions;
35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»;
37 | # * compound commands having a testable exit status, especially «case»;
38 | # * various built-in commands including «command», «set», and «ulimit».
39 | #
40 | # Important for patching:
41 | #
42 | # (2) This script targets any POSIX shell, so it avoids extensions provided
43 | # by Bash, Ksh, etc; in particular arrays are avoided.
44 | #
45 | # The "traditional" practice of packing multiple parameters into a
46 | # space-separated string is a well documented source of bugs and security
47 | # problems, so this is (mostly) avoided, by progressively accumulating
48 | # options in "$@", and eventually passing that to Java.
49 | #
50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
52 | # see the in-line comments for details.
53 | #
54 | # There are tweaks for specific operating systems such as AIX, CygWin,
55 | # Darwin, MinGW, and NonStop.
56 | #
57 | # (3) This script is generated from the Groovy template
58 | # https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
59 | # within the Gradle project.
60 | #
61 | # You can find Gradle at https://github.com/gradle/gradle/.
62 | #
63 | ##############################################################################
64 |
65 | # Attempt to set APP_HOME
66 |
67 | # Resolve links: $0 may be a link
68 | app_path=$0
69 |
70 | # Need this for daisy-chained symlinks.
71 | while
72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
73 | [ -h "$app_path" ]
74 | do
75 | ls=$( ls -ld "$app_path" )
76 | link=${ls#*' -> '}
77 | case $link in #(
78 | /*) app_path=$link ;; #(
79 | *) app_path=$APP_HOME$link ;;
80 | esac
81 | done
82 |
83 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
84 |
85 | APP_NAME="Gradle"
86 | APP_BASE_NAME=${0##*/}
87 |
88 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
89 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
90 |
91 | # Use the maximum available, or set MAX_FD != -1 to use that value.
92 | MAX_FD=maximum
93 |
94 | warn () {
95 | echo "$*"
96 | } >&2
97 |
98 | die () {
99 | echo
100 | echo "$*"
101 | echo
102 | exit 1
103 | } >&2
104 |
105 | # OS specific support (must be 'true' or 'false').
106 | cygwin=false
107 | msys=false
108 | darwin=false
109 | nonstop=false
110 | case "$( uname )" in #(
111 | CYGWIN* ) cygwin=true ;; #(
112 | Darwin* ) darwin=true ;; #(
113 | MSYS* | MINGW* ) msys=true ;; #(
114 | NONSTOP* ) nonstop=true ;;
115 | esac
116 |
117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
118 |
119 |
120 | # Determine the Java command to use to start the JVM.
121 | if [ -n "$JAVA_HOME" ] ; then
122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
123 | # IBM's JDK on AIX uses strange locations for the executables
124 | JAVACMD=$JAVA_HOME/jre/sh/java
125 | else
126 | JAVACMD=$JAVA_HOME/bin/java
127 | fi
128 | if [ ! -x "$JAVACMD" ] ; then
129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
130 |
131 | Please set the JAVA_HOME variable in your environment to match the
132 | location of your Java installation."
133 | fi
134 | else
135 | JAVACMD=java
136 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
137 |
138 | Please set the JAVA_HOME variable in your environment to match the
139 | location of your Java installation."
140 | fi
141 |
142 | # Increase the maximum file descriptors if we can.
143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
144 | case $MAX_FD in #(
145 | max*)
146 | MAX_FD=$( ulimit -H -n ) ||
147 | warn "Could not query maximum file descriptor limit"
148 | esac
149 | case $MAX_FD in #(
150 | '' | soft) :;; #(
151 | *)
152 | ulimit -n "$MAX_FD" ||
153 | warn "Could not set maximum file descriptor limit to $MAX_FD"
154 | esac
155 | fi
156 |
157 | # Collect all arguments for the java command, stacking in reverse order:
158 | # * args from the command line
159 | # * the main class name
160 | # * -classpath
161 | # * -D...appname settings
162 | # * --module-path (only if needed)
163 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
164 |
165 | # For Cygwin or MSYS, switch paths to Windows format before running java
166 | if "$cygwin" || "$msys" ; then
167 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
168 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
169 |
170 | JAVACMD=$( cygpath --unix "$JAVACMD" )
171 |
172 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
173 | for arg do
174 | if
175 | case $arg in #(
176 | -*) false ;; # don't mess with options #(
177 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
178 | [ -e "$t" ] ;; #(
179 | *) false ;;
180 | esac
181 | then
182 | arg=$( cygpath --path --ignore --mixed "$arg" )
183 | fi
184 | # Roll the args list around exactly as many times as the number of
185 | # args, so each arg winds up back in the position where it started, but
186 | # possibly modified.
187 | #
188 | # NB: a `for` loop captures its iteration list before it begins, so
189 | # changing the positional parameters here affects neither the number of
190 | # iterations, nor the values presented in `arg`.
191 | shift # remove old arg
192 | set -- "$@" "$arg" # push replacement arg
193 | done
194 | fi
195 |
196 | # Collect all arguments for the java command;
197 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
198 | # shell script including quotes and variable substitutions, so put them in
199 | # double quotes to make sure that they get re-expanded; and
200 | # * put everything else in single quotes, so that it's not re-expanded.
201 |
202 | set -- \
203 | "-Dorg.gradle.appname=$APP_BASE_NAME" \
204 | -classpath "$CLASSPATH" \
205 | org.gradle.wrapper.GradleWrapperMain \
206 | "$@"
207 |
208 | # Use "xargs" to parse quoted args.
209 | #
210 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed.
211 | #
212 | # In Bash we could simply go:
213 | #
214 | # readarray ARGS < <( xargs -n1 <<<"$var" ) &&
215 | # set -- "${ARGS[@]}" "$@"
216 | #
217 | # but POSIX shell has neither arrays nor command substitution, so instead we
218 | # post-process each arg (as a line of input to sed) to backslash-escape any
219 | # character that might be a shell metacharacter, then use eval to reverse
220 | # that process (while maintaining the separation between arguments), and wrap
221 | # the whole thing up as a single "set" statement.
222 | #
223 | # This will of course break if any of these variables contains a newline or
224 | # an unmatched quote.
225 | #
226 |
227 | eval "set -- $(
228 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
229 | xargs -n1 |
230 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
231 | tr '\n' ' '
232 | )" '"$@"'
233 |
234 | exec "$JAVACMD" "$@"
235 |
--------------------------------------------------------------------------------
/apk_analyzer/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/apk_analyzer/settings.gradle.kts:
--------------------------------------------------------------------------------
1 |
2 | rootProject.name = "ApkAnalyzer"
3 |
4 |
--------------------------------------------------------------------------------
/apk_analyzer/src/main/kotlin/com/ammaraskar/intent/fuzz/ApkAnalyzer.kt:
--------------------------------------------------------------------------------
1 | package com.ammaraskar.intent.fuzz
2 |
3 | import jadx.api.JadxArgs
4 | import jadx.api.JadxDecompiler
5 | import jadx.core.dex.visitors.IDexTreeVisitor
6 | import jadx.core.dex.visitors.ReSugarCode
7 | import java.io.File
8 |
9 | class ApkAnalyzer(private val apkFile: File) {
10 |
11 | private val _intentTemplates: MutableList = mutableListOf()
12 | val intentTemplates: List
13 | get() = _intentTemplates
14 |
15 | init {
16 | val jadxArgs = JadxArgs()
17 | jadxArgs.setInputFile(apkFile)
18 |
19 | JadxDecompiler(jadxArgs).use { decompiler ->
20 | decompiler.load();
21 | val intentExtraUsageVisitor = IntentExtraUsageTreeVisitor()
22 | addCustomPassAfter(decompiler.root.passes, intentExtraUsageVisitor)
23 |
24 | val manifestResource =
25 | decompiler.resources.stream().filter { resource -> resource.originalName.equals("AndroidManifest.xml") }
26 | .findFirst();
27 | if (!manifestResource.isPresent) {
28 | throw IllegalArgumentException("APK does not contain AndroidManifest.xml")
29 | }
30 |
31 | val contents = manifestResource.get().loadContent();
32 | val intentReceivers =
33 | parseIntentReceiversFromManifest(contents.text.codeStr, decompiler)
34 |
35 | // Invoke the tree visitor by decompiling all the classes in the apk.
36 | for (cls in decompiler.classes) {
37 | // Don't bother looking at built-in android, kotlin or java classes.
38 | if (cls.fullName.startsWith("androidx.") || cls.fullName.startsWith("kotlin.")) {
39 | continue;
40 | }
41 | cls.decompile()
42 | }
43 |
44 | for (intentReceiver in intentReceivers) {
45 | _intentTemplates.add(IntentTemplate(
46 | intentReceiver.receiverType,
47 | intentReceiver.componentName,
48 | intentReceiver.actions,
49 | intentReceiver.categories,
50 | // For now we assign all extras to every intent receiver. In the future maybe with some more
51 | // advanced static analysis we could figure out which extras correspond to which intent receiver.
52 | intentExtraUsageVisitor.extras,
53 | ))
54 | }
55 | }
56 | }
57 |
58 | }
59 |
60 | // From https://github.com/skylot/jadx/issues/1482
61 | private inline fun addCustomPassAfter(passes: MutableList, customPass: IDexTreeVisitor) {
62 | for ((i, pass) in passes.withIndex()) {
63 | if (pass is T) {
64 | passes.add(i + 1, customPass)
65 | break
66 | }
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/apk_analyzer/src/main/kotlin/com/ammaraskar/intent/fuzz/IntentExtraUsageTreeVisitor.kt:
--------------------------------------------------------------------------------
1 | package com.ammaraskar.intent.fuzz
2 |
3 | import jadx.core.dex.instructions.BaseInvokeNode
4 | import jadx.core.dex.instructions.ConstStringNode
5 | import jadx.core.dex.instructions.args.InsnWrapArg
6 | import jadx.core.dex.nodes.MethodNode
7 | import jadx.core.dex.visitors.AbstractVisitor
8 |
9 | class IntentExtraUsageTreeVisitor : AbstractVisitor() {
10 |
11 | /**
12 | * Mapping of intent extra keys to their types.
13 | */
14 | val extras: HashMap = hashMapOf()
15 |
16 | override fun visit(mth: MethodNode) {
17 | if (mth.isNoCode) {
18 | return;
19 | }
20 |
21 | for (basicBlock in mth.basicBlocks) {
22 | for (instr in basicBlock.instructions) {
23 | if (instr is BaseInvokeNode) {
24 | this.visitInvokeNode(instr)
25 | }
26 | }
27 | }
28 | }
29 |
30 | private val gettersToExtraTypes = hashMapOf(
31 | "getStringExtra" to "String",
32 | "getBooleanExtra" to "Boolean",
33 | "getByteExtra" to "Byte",
34 | "getCharExtra" to "Char",
35 | "getShortExtra" to "Short",
36 | "getIntExtra" to "Int",
37 | "getLongExtra" to "Long",
38 | "getFloatExtra" to "Float",
39 | "getDoubleExtra" to "Double",
40 | "getStringArrayExtra" to "StringArray",
41 | "getBooleanArrayExtra" to "BooleanArray",
42 | "getByteArrayExtra" to "ByteArray",
43 | "getCharArrayExtra" to "CharArray",
44 | "getShortArrayExtra" to "ShortArray",
45 | "getIntArrayExtra" to "IntArray",
46 | "getLongArrayExtra" to "LongArray",
47 | "getDoubleArrayExtra" to "DoubleArray",
48 | "getIntegerArrayListExtra" to "IntArrayList",
49 | "getStringArrayListExtra" to "StringArrayList",
50 | )
51 |
52 | private fun visitInvokeNode(node: BaseInvokeNode) {
53 | if (node.callMth.declClass.fullName != "android.content.Intent") {
54 | return;
55 | }
56 |
57 | val extraType = gettersToExtraTypes[node.callMth.name] ?: return
58 | var key: String? = null;
59 |
60 | for (argument in node.arguments) {
61 | if (argument !is InsnWrapArg) {
62 | continue;
63 | }
64 | val wrappedInstruction = argument.wrapInsn
65 | if (wrappedInstruction !is ConstStringNode) {
66 | continue;
67 | }
68 | key = wrappedInstruction.string
69 | }
70 |
71 | if (key == null) {
72 | return
73 | }
74 | extras[key] = extraType
75 | }
76 | }
--------------------------------------------------------------------------------
/apk_analyzer/src/main/kotlin/com/ammaraskar/intent/fuzz/IntentReceiver.kt:
--------------------------------------------------------------------------------
1 | package com.ammaraskar.intent.fuzz
2 |
3 | import jadx.api.JadxDecompiler
4 | import javax.xml.parsers.DocumentBuilderFactory
5 |
6 | class IntentReceiver(
7 | val receiverType: String,
8 | val componentName: String,
9 | val actions: Collection,
10 | val categories: Collection,
11 | val aliasTarget: String?
12 | ) {
13 | override fun equals(other: Any?): Boolean {
14 | if (other !is IntentReceiver) {
15 | return false
16 | }
17 | return receiverType == other.receiverType &&
18 | componentName == other.componentName &&
19 | actions == other.actions &&
20 | categories == other.categories &&
21 | aliasTarget == other.aliasTarget
22 | }
23 | }
24 |
25 | fun parseIntentReceiversFromManifest(manifestXML: String, decompiler: JadxDecompiler): List {
26 | val targets = mutableListOf()
27 |
28 | val documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder()
29 | val document = documentBuilder.parse(manifestXML.byteInputStream())
30 |
31 | val manifestElement = document.getElementsByTagName("manifest")
32 | if (manifestElement.length != 1) {
33 | throw IllegalArgumentException("AndroidManifest contained ${manifestElement.length} manifest elements")
34 | }
35 | val packageName = manifestElement.item(0).attributes.getNamedItem("package")?.nodeValue
36 | ?: throw IllegalArgumentException(" element did not contain package")
37 |
38 | // Find all tags and iterate over them.
39 | val intentFilters = document.getElementsByTagName("intent-filter")
40 | for (i in 0 until intentFilters.length) {
41 | val node = intentFilters.item(i)
42 |
43 | val containingComponent = node.parentNode
44 |
45 | // Get the class name of the component containing this intent-filter.
46 | val intentClass = containingComponent.attributes.getNamedItem("android:name")?.nodeValue
47 | if (intentClass == null) {
48 | println("Skipping intent receiver because it doesn't have an android:name attribute")
49 | continue
50 | }
51 |
52 | // Check to see if the component is exported. That is, either the "android:exported" attribute is marked as
53 | // "true" or if it isn't present, it takes a default value of true when there is an
54 | val exportedAttribute =
55 | containingComponent.attributes.getNamedItem("android:exported")?.nodeValue
56 | ?: "true"
57 | val isExported = exportedAttribute == "true"
58 |
59 | // Only add to list of targets if it is exported.
60 | if (!isExported) {
61 | continue
62 | }
63 |
64 | // Check if this is an alias.
65 | val aliasTargetActivity = if (containingComponent.nodeName == "activity-alias") {
66 | containingComponent.attributes.getNamedItem("android:targetActivity")?.nodeValue
67 | ?: throw IllegalArgumentException("Manifest has an activity-alias without an android:targetActivity")
68 | } else {
69 | null
70 | }
71 |
72 | val actionNames = mutableListOf()
73 | val categoryNames = mutableListOf()
74 | // Gather all the action tags.
75 | for (j in 0 until node.childNodes.length) {
76 | val intentFilterChild = node.childNodes.item(j)
77 |
78 | // Check if this is a or an
79 | if (intentFilterChild.nodeName == "action") {
80 | actionNames.add(intentFilterChild.attributes.getNamedItem("android:name").nodeValue)
81 | } else if (intentFilterChild.nodeName == "category") {
82 | categoryNames.add(intentFilterChild.attributes.getNamedItem("android:name").nodeValue)
83 | }
84 | }
85 |
86 | val receiverType = when (containingComponent.nodeName) {
87 | "activity" -> "Activity"
88 | "activity-alias" -> "Activity"
89 | "service" -> continue // We do not support fuzzing services for now
90 | "receiver" -> "BroadcastReceiver"
91 | "provider" -> continue // We do not support content providers for now
92 | else -> throw IllegalArgumentException("Unknown component type: ${containingComponent.nodeName}")
93 | }
94 |
95 | // Create the intent receiver object.
96 | val intentReceiver = IntentReceiver(
97 | receiverType = receiverType,
98 | componentName = "$packageName/$intentClass",
99 | actions = actionNames,
100 | categories = categoryNames,
101 | aliasTarget = aliasTargetActivity
102 | )
103 |
104 | // Only add alias if it has new actions or categories.
105 | if (aliasTargetActivity != null) {
106 | val existingTargets = targets.filter {
107 | it.componentName == "$packageName/$aliasTargetActivity" || it.aliasTarget == aliasTargetActivity
108 | }
109 |
110 | // We skip this alias if there already exists one that covers the same actions and categories.
111 | if (existingTargets.any {
112 | it.actions.containsAll(intentReceiver.actions) && it.categories.containsAll(intentReceiver.categories)
113 | }) {
114 | println("Skipping alias: ${intentReceiver.componentName} with target $aliasTargetActivity")
115 | continue
116 | }
117 | }
118 |
119 | // Skip duplicate intent receivers (i.e., all properties are the same).
120 | // Duplicates happen because an activity can declare multiple intent filters.
121 | // They may have different tags, which we do not parse (yet?).
122 | // Might be helpful to use them since they hint at the scheme and host of the URI.
123 | if (targets.contains(intentReceiver)) {
124 | println("Skipping duplicate intent receiver: ${intentReceiver.componentName}")
125 | continue
126 | }
127 |
128 | println("Adding intent receiver: ${intentReceiver.componentName}")
129 | targets.add(intentReceiver)
130 | }
131 |
132 | return targets
133 | }
134 |
--------------------------------------------------------------------------------
/apk_analyzer/src/main/kotlin/com/ammaraskar/intent/fuzz/IntentTemplate.kt:
--------------------------------------------------------------------------------
1 | package com.ammaraskar.intent.fuzz
2 |
3 | import kotlinx.serialization.Serializable
4 | import kotlinx.serialization.encodeToString
5 | import kotlinx.serialization.json.Json
6 | import java.io.File
7 |
8 | @Serializable
9 | class IntentTemplate(
10 | val receiver_type: String,
11 | val component: String,
12 | val actions: Collection,
13 | val categories: Collection,
14 | val known_extras_keys: Map,
15 | ) {
16 | // Save this template to a json file in a given directory
17 | fun saveToFile(outputDir: File) {
18 | val jsonFormat = Json { prettyPrint = true }
19 | outputDir.mkdirs()
20 | File(outputDir, "${getActivityName()}.json").writeText(jsonFormat.encodeToString(this))
21 | }
22 |
23 | fun getActivityName(): String {
24 | return component.substringAfterLast("/")
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/apk_analyzer/src/main/kotlin/com/ammaraskar/intent/fuzz/Main.kt:
--------------------------------------------------------------------------------
1 | package com.ammaraskar.intent.fuzz
2 |
3 | import kotlinx.serialization.encodeToString
4 | import kotlinx.serialization.json.Json
5 | import java.io.File
6 |
7 | fun main(args: Array) {
8 | val analyzer = ApkAnalyzer(File(args[0]))
9 |
10 | val jsonFormat = Json { prettyPrint = true }
11 | println(jsonFormat.encodeToString(analyzer.intentTemplates))
12 |
13 | // Save all intent templates to a given output directory
14 | analyzer.intentTemplates.forEach { it.saveToFile(File(args[1])) }
15 | }
--------------------------------------------------------------------------------
/apk_analyzer/src/test/kotlin/com/ammaraskar/intent/fuzz/ApkAnalyzerTest.kt:
--------------------------------------------------------------------------------
1 | package com.ammaraskar.intent.fuzz
2 |
3 | import org.junit.Ignore
4 | import org.junit.Test
5 | import java.io.File
6 |
7 | class ApkAnalyzerTest {
8 | @Ignore("Ignored until we actually commit actual testing apk files into the repo")
9 | @Test
10 | fun testLoadsApk() {
11 | ApkAnalyzer(File("../../VulnerableApp/app/build/outputs/apk/debug/app-debug.apk"))
12 | }
13 | }
--------------------------------------------------------------------------------
/apk_analyzer/src/test/kotlin/com/ammaraskar/intent/fuzz/IntentReceiverTest.kt:
--------------------------------------------------------------------------------
1 | package com.ammaraskar.intent.fuzz
2 |
3 | import junit.framework.Assert.assertEquals
4 | import org.junit.Test
5 |
6 | internal class IntentReceiverTest {
7 |
8 | @Test
9 | fun parseIntentTargetsWorksOnARealManifest() {
10 | val testManifest = """
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | """.trimIndent()
24 |
25 | val targets = parseIntentReceiversFromManifest(testManifest, decompiler)
26 | assertEquals(targets.size, 1)
27 | assertEquals(targets[0].componentName, "com.ammaraskar.vulnerableapp/com.ammaraskar.vulnerableapp.MainActivity")
28 | assertEquals(targets[0].categories, listOf("android.intent.category.LAUNCHER"))
29 | assertEquals(targets[0].actions, listOf("android.intent.action.MAIN"))
30 | }
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/content_provider/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/caches
5 | /.idea/libraries
6 | /.idea/modules.xml
7 | /.idea/workspace.xml
8 | /.idea/navEditor.xml
9 | /.idea/assetWizardSettings.xml
10 | .DS_Store
11 | /build
12 | /captures
13 | .externalNativeBuild
14 | .cxx
15 | local.properties
16 |
--------------------------------------------------------------------------------
/content_provider/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 |
--------------------------------------------------------------------------------
/content_provider/.idea/.name:
--------------------------------------------------------------------------------
1 | Content Provider
--------------------------------------------------------------------------------
/content_provider/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/content_provider/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/content_provider/.idea/kotlinc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/content_provider/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/content_provider/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/content_provider/README.md:
--------------------------------------------------------------------------------
1 | # Content Provider
2 |
3 | This content provider needs to be installed on the device to allow the fuzzer
4 | to create files in a content provider. This app doesn't have a UI and only
5 | needs to be installed; the fuzzer will handle the rest.
6 |
7 | ## Installation
8 |
9 | The following command will build and install the content provider:
10 |
11 | ```bash
12 | gradle installDebug
13 | ```
14 |
--------------------------------------------------------------------------------
/content_provider/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/content_provider/app/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.application'
3 | id 'org.jetbrains.kotlin.android'
4 | }
5 |
6 | android {
7 | namespace 'org.gts3.jnifuzz.contentprovider'
8 | compileSdk 33
9 |
10 | defaultConfig {
11 | applicationId "org.gts3.jnifuzz.contentprovider"
12 | minSdk 24
13 | targetSdk 33
14 | versionCode 1
15 | versionName "1.0"
16 |
17 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
18 | vectorDrawables {
19 | useSupportLibrary true
20 | }
21 | }
22 |
23 | buildTypes {
24 | release {
25 | minifyEnabled false
26 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
27 | }
28 | }
29 | compileOptions {
30 | sourceCompatibility JavaVersion.VERSION_1_8
31 | targetCompatibility JavaVersion.VERSION_1_8
32 | }
33 | kotlinOptions {
34 | jvmTarget = '1.8'
35 | }
36 | buildFeatures {
37 | compose true
38 | }
39 | composeOptions {
40 | kotlinCompilerExtensionVersion '1.4.6'
41 | }
42 | packagingOptions {
43 | resources {
44 | excludes += '/META-INF/{AL2.0,LGPL2.1}'
45 | }
46 | }
47 | }
48 |
49 | dependencies {
50 |
51 | implementation 'androidx.core:core-ktx:1.8.0'
52 | implementation 'androidx.appcompat:appcompat:1.6.1'
53 | implementation 'com.google.android.material:material:1.9.0'
54 | implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1'
55 | implementation 'androidx.activity:activity-compose:1.5.1'
56 | implementation platform('androidx.compose:compose-bom:2022.10.00')
57 | implementation 'androidx.compose.ui:ui'
58 | implementation 'androidx.compose.ui:ui-graphics'
59 | implementation 'androidx.compose.ui:ui-tooling-preview'
60 | implementation 'androidx.compose.material3:material3'
61 | testImplementation 'junit:junit:4.13.2'
62 | androidTestImplementation 'androidx.test.ext:junit:1.1.5'
63 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
64 | androidTestImplementation platform('androidx.compose:compose-bom:2022.10.00')
65 | androidTestImplementation 'androidx.compose.ui:ui-test-junit4'
66 | debugImplementation 'androidx.compose.ui:ui-tooling'
67 | debugImplementation 'androidx.compose.ui:ui-test-manifest'
68 | }
69 |
--------------------------------------------------------------------------------
/content_provider/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/content_provider/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
17 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
34 |
37 |
38 |
39 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/content_provider/app/src/main/java/org/gts3/jnifuzz/contentprovider/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package org.gts3.jnifuzz.contentprovider
2 |
3 | import android.content.pm.PackageManager
4 | import android.os.Bundle
5 | import android.view.View
6 | import android.widget.Toast
7 | import androidx.appcompat.app.AppCompatActivity
8 |
9 |
10 | class MainActivity : AppCompatActivity() {
11 | override fun onCreate(savedInstanceState: Bundle?) {
12 | super.onCreate(savedInstanceState)
13 | setContentView(R.layout.activity_main)
14 | }
15 |
16 | fun grantUriPermissions(view: View?) {
17 |
18 | // Bail out if view is null
19 | if (view == null) {
20 | Toast.makeText(this, "Failed to grant permissions (view is null)", Toast.LENGTH_LONG).show()
21 | return
22 | }
23 |
24 | val context = view.getContext();
25 |
26 | // Go through all packages
27 | val pm = getPackageManager();
28 | val packages = pm.getInstalledPackages(PackageManager.GET_META_DATA);
29 |
30 | for (packageInfo in packages) {
31 | val packageName = packageInfo.packageName;
32 |
33 | // Skip packages that start with "com.android" or "android"
34 | if (packageName.startsWith("com.android") || packageName.startsWith("android")) {
35 | continue;
36 | }
37 |
38 | UriPermissionManager.grantUriPermissionsForPackage(context, packageName)
39 | }
40 | }
41 | }
42 |
43 |
--------------------------------------------------------------------------------
/content_provider/app/src/main/java/org/gts3/jnifuzz/contentprovider/UriPermissionManager.kt:
--------------------------------------------------------------------------------
1 | package org.gts3.jnifuzz.contentprovider
2 |
3 | import android.content.BroadcastReceiver
4 | import android.content.Context
5 | import android.content.Intent
6 | import android.content.Intent.EXTRA_PACKAGE_NAME
7 | import android.net.Uri
8 | import android.util.Log
9 |
10 | class UriPermissionManager : BroadcastReceiver() {
11 | private val suffixes = listOf(
12 | "aac",
13 | "apk",
14 | "gif",
15 | "html",
16 | "jpg",
17 | "midi",
18 | "mp3",
19 | "mp4",
20 | "ogg",
21 | "pdf",
22 | "png",
23 | "txt",
24 | "wav",
25 | "wma",
26 | "wmv",
27 | "xml"
28 | );
29 |
30 | override fun onReceive(p0: Context?, p1: Intent?) {
31 | Log.i("UriPermissionManager", "Received intent")
32 |
33 | // Load the package name from the intent (string extra EXTRA_PACKAGE_NAME)
34 | val packageName = p1?.getStringExtra(EXTRA_PACKAGE_NAME)
35 |
36 | // Log if the context is null
37 | if (p0 == null) {
38 | Log.i("UriPermissionManager", "Context is null")
39 | return
40 | }
41 |
42 | if (packageName != null) {
43 | if (packageName.startsWith("com.android") || packageName.startsWith("android")) {
44 | Log.i("UriPermissionManager", "Package name starts with com.android or android")
45 | return
46 | }
47 |
48 | // Grant permissions to the package
49 | grantUriPermissionsForPackage(p0!!, packageName!!);
50 | } else {
51 | Log.i("UriPermissionManager", "No package specified, not granting permissions")
52 | return
53 | }
54 | }
55 |
56 | fun grantUriPermissionsForPackage(context: Context, packageName: String) {
57 | for (suffix in suffixes) {
58 | for (i in 0..10) {
59 | val uri =
60 | Uri.parse("content://" + context.packageName + ".provider/external_files/extra_input_" + i + "." + suffix);
61 |
62 | Log.i(
63 | "contentprovider",
64 | "Granting permission to " + packageName + " for " + uri.toString()
65 | );
66 |
67 | context.grantUriPermission(
68 | packageName,
69 | uri,
70 | Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION
71 | );
72 | }
73 | }
74 | }
75 |
76 | companion object {
77 | fun grantUriPermissionsForPackage(context: Context, packageName: String) {
78 | UriPermissionManager().grantUriPermissionsForPackage(context, packageName)
79 | }
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/content_provider/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
31 |
--------------------------------------------------------------------------------
/content_provider/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/content_provider/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/content_provider/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/content_provider/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/content_provider/app/src/main/res/mipmap-hdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sslab-gatech/MALintent/3bb0d77f66beb224ab86417bd1f5a1935451e44e/content_provider/app/src/main/res/mipmap-hdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/content_provider/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sslab-gatech/MALintent/3bb0d77f66beb224ab86417bd1f5a1935451e44e/content_provider/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/content_provider/app/src/main/res/mipmap-mdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sslab-gatech/MALintent/3bb0d77f66beb224ab86417bd1f5a1935451e44e/content_provider/app/src/main/res/mipmap-mdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/content_provider/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sslab-gatech/MALintent/3bb0d77f66beb224ab86417bd1f5a1935451e44e/content_provider/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/content_provider/app/src/main/res/mipmap-xhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sslab-gatech/MALintent/3bb0d77f66beb224ab86417bd1f5a1935451e44e/content_provider/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/content_provider/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sslab-gatech/MALintent/3bb0d77f66beb224ab86417bd1f5a1935451e44e/content_provider/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/content_provider/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sslab-gatech/MALintent/3bb0d77f66beb224ab86417bd1f5a1935451e44e/content_provider/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/content_provider/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sslab-gatech/MALintent/3bb0d77f66beb224ab86417bd1f5a1935451e44e/content_provider/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/content_provider/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sslab-gatech/MALintent/3bb0d77f66beb224ab86417bd1f5a1935451e44e/content_provider/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/content_provider/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sslab-gatech/MALintent/3bb0d77f66beb224ab86417bd1f5a1935451e44e/content_provider/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/content_provider/app/src/main/res/values-night/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
17 |
--------------------------------------------------------------------------------
/content_provider/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFBB86FC
4 | #FF6200EE
5 | #FF3700B3
6 | #FF03DAC5
7 | #FF018786
8 | #FF000000
9 | #FFFFFFFF
10 |
11 |
--------------------------------------------------------------------------------
/content_provider/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Content Provider
3 | Content Provider
4 | Grant URI Permissions
5 |
6 |
--------------------------------------------------------------------------------
/content_provider/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
17 |
--------------------------------------------------------------------------------
/content_provider/app/src/main/res/xml/backup_rules.xml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
13 |
14 |
--------------------------------------------------------------------------------
/content_provider/app/src/main/res/xml/data_extraction_rules.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
12 |
13 |
19 |
20 |
--------------------------------------------------------------------------------
/content_provider/app/src/main/res/xml/provider_paths.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/content_provider/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 | plugins {
3 | id 'com.android.application' version '8.0.2' apply false
4 | id 'com.android.library' version '8.0.2' apply false
5 | id 'org.jetbrains.kotlin.android' version '1.8.20' apply false
6 | }
7 |
--------------------------------------------------------------------------------
/content_provider/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app's APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Kotlin code style for this project: "official" or "obsolete":
19 | kotlin.code.style=official
20 | # Enables namespacing of each library's R class so that its R class includes only the
21 | # resources declared in the library itself and none from the library's dependencies,
22 | # thereby reducing the size of the R class for that library
23 | android.nonTransitiveRClass=true
24 |
--------------------------------------------------------------------------------
/content_provider/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sslab-gatech/MALintent/3bb0d77f66beb224ab86417bd1f5a1935451e44e/content_provider/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/content_provider/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Tue Jun 13 11:00:41 EDT 2023
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
5 | zipStoreBase=GRADLE_USER_HOME
6 | zipStorePath=wrapper/dists
7 |
--------------------------------------------------------------------------------
/content_provider/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 |
86 | # Determine the Java command to use to start the JVM.
87 | if [ -n "$JAVA_HOME" ] ; then
88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
89 | # IBM's JDK on AIX uses strange locations for the executables
90 | JAVACMD="$JAVA_HOME/jre/sh/java"
91 | else
92 | JAVACMD="$JAVA_HOME/bin/java"
93 | fi
94 | if [ ! -x "$JAVACMD" ] ; then
95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
96 |
97 | Please set the JAVA_HOME variable in your environment to match the
98 | location of your Java installation."
99 | fi
100 | else
101 | JAVACMD="java"
102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
103 |
104 | Please set the JAVA_HOME variable in your environment to match the
105 | location of your Java installation."
106 | fi
107 |
108 | # Increase the maximum file descriptors if we can.
109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
110 | MAX_FD_LIMIT=`ulimit -H -n`
111 | if [ $? -eq 0 ] ; then
112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
113 | MAX_FD="$MAX_FD_LIMIT"
114 | fi
115 | ulimit -n $MAX_FD
116 | if [ $? -ne 0 ] ; then
117 | warn "Could not set maximum file descriptor limit: $MAX_FD"
118 | fi
119 | else
120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
121 | fi
122 | fi
123 |
124 | # For Darwin, add options to specify how the application appears in the dock
125 | if $darwin; then
126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
127 | fi
128 |
129 | # For Cygwin or MSYS, switch paths to Windows format before running java
130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
133 |
134 | JAVACMD=`cygpath --unix "$JAVACMD"`
135 |
136 | # We build the pattern for arguments to be converted via cygpath
137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
138 | SEP=""
139 | for dir in $ROOTDIRSRAW ; do
140 | ROOTDIRS="$ROOTDIRS$SEP$dir"
141 | SEP="|"
142 | done
143 | OURCYGPATTERN="(^($ROOTDIRS))"
144 | # Add a user-defined pattern to the cygpath arguments
145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
147 | fi
148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
149 | i=0
150 | for arg in "$@" ; do
151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
153 |
154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
156 | else
157 | eval `echo args$i`="\"$arg\""
158 | fi
159 | i=`expr $i + 1`
160 | done
161 | case $i in
162 | 0) set -- ;;
163 | 1) set -- "$args0" ;;
164 | 2) set -- "$args0" "$args1" ;;
165 | 3) set -- "$args0" "$args1" "$args2" ;;
166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
172 | esac
173 | fi
174 |
175 | # Escape application args
176 | save () {
177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
178 | echo " "
179 | }
180 | APP_ARGS=`save "$@"`
181 |
182 | # Collect all arguments for the java command, following the shell quoting and substitution rules
183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
184 |
185 | exec "$JAVACMD" "$@"
186 |
--------------------------------------------------------------------------------
/content_provider/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/content_provider/settings.gradle:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | google()
4 | mavenCentral()
5 | gradlePluginPortal()
6 | }
7 | }
8 | dependencyResolutionManagement {
9 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
10 | repositories {
11 | google()
12 | mavenCentral()
13 | }
14 | }
15 | rootProject.name = "Content Provider"
16 | include ':app'
17 |
--------------------------------------------------------------------------------
/eval/.gitignore:
--------------------------------------------------------------------------------
1 | */
2 |
--------------------------------------------------------------------------------
/eval/collect_edgecount_data.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -e
4 |
5 | SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
6 | cd "$SCRIPT_DIR"
7 |
8 | # This scripts copies the edge count data files from the servers to this machine.
9 | # The file structure is the same as the one on the servers.
10 | # The edge count data is stored in directories named './/edgecount*'.
11 |
12 | servers=${SERVERS:-rodimus nickel.gtisc.gatech.edu zinc.gtisc.gatech.edu goldbug.gtisc.gatech.edu grimlock.gtisc.gatech.edu computron.gtisc.gatech.edu iron.gtisc.gatech.edu}
13 | #servers=${SERVERS:-nickel.gtisc.gatech.edu}
14 |
15 | for server in $servers; do
16 | # Find all the relevant files on the server.
17 | # ssh "$server" find src/jni-fuzz/IntentFuzzerLibAFL/eval/ -maxdepth 2 -name 'edgecount\*' -type d
18 | # rsync -avz --include '*/' --include '/*/edgecount*/*' --exclude '*' --prune-empty-dirs "$server:src/jni-fuzz/IntentFuzzerLibAFL/eval/" "./tmp/"
19 |
20 |
21 | ssh "$server" find src/jni-fuzz/IntentFuzzerLibAFL/eval/ -maxdepth 2 -name 'edgecount\*' -type d -printf '%P\\n' | while read -r line; do
22 | # Copy the files to the local machine.
23 | echo "Copying $line from $server"
24 | rsync -avz "$server:src/jni-fuzz/IntentFuzzerLibAFL/eval/$line" "$(dirname $line)"
25 | done
26 | done
27 |
--------------------------------------------------------------------------------
/eval/compute_metrics.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | import sys
3 | import statistics
4 |
5 | from pathlib import Path
6 |
7 |
8 | def load_app(app):
9 |
10 | results = []
11 |
12 | # Go through all targets in the edgcount directory
13 | for target in app.glob("edgecount/*"):
14 | # Get the number from the file
15 | with open(target, "r") as f:
16 | num_edges_withcov = int(f.read().strip())
17 |
18 | # Construct the path to the file with the number of edges
19 | target_nocov = target.parent.parent / "edgecount-nocov" / target.name
20 |
21 | # Skip if the file does not exist
22 | if not target_nocov.exists():
23 | continue
24 |
25 | # Get the number of edges from the file
26 | with open(target_nocov, "r") as f:
27 | num_edges_nocov = int(f.read().strip())
28 |
29 | if num_edges_nocov > num_edges_withcov:
30 | print(target, num_edges_withcov, num_edges_nocov)
31 |
32 | if 'androidx.work.impl.diagnostics.DiagnosticsReceiver' in str(target):
33 | continue
34 |
35 | if num_edges_withcov/num_edges_nocov > 1000:
36 | # Exclude outlier
37 | continue
38 |
39 | #print(target, num_edges_withcov, num_edges_nocov)
40 | results.append((num_edges_withcov, num_edges_nocov))
41 |
42 | return results
43 |
44 |
45 | def analysis_per_target(data):
46 | average_improvements = list(map(lambda x: x[0] / x[1], data))
47 | average_improvements.sort()
48 |
49 | print("Average improvement:", statistics.mean(average_improvements))
50 | print("Median improvement:", statistics.median(average_improvements))
51 | print("Standard deviation:", statistics.stdev(average_improvements))
52 | print("25 percentile:", statistics.quantiles(average_improvements)[0])
53 | print("75 percentile:", statistics.quantiles(average_improvements)[2])
54 |
55 |
56 | def analysis_per_app(data):
57 | # Print percentage apps where at least one target has an improvement
58 | count = 0
59 | for app in data:
60 | for target in app:
61 | if target[0] < target[1]:
62 | count += 1
63 | break
64 |
65 | print("Percentage apps with improvement:", count / len(data))
66 |
67 |
68 |
69 |
70 | def main():
71 | data = []
72 |
73 | # All apps are in this directory
74 | for edgecount_dir in Path(".").glob("*/edgecount/"):
75 | app = edgecount_dir.parent
76 | #print(app)
77 |
78 | # Load the data for this app
79 | data.append(load_app(app))
80 |
81 | analysis_per_target([item for sublist in data for item in sublist])
82 | analysis_per_app(data)
83 |
84 |
85 | if __name__ == "__main__":
86 | main()
87 |
--------------------------------------------------------------------------------
/eval/generate_configs.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -e
4 |
5 | SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
6 |
7 | cd "$SCRIPT_DIR"
8 |
9 | APK_DIR="../../jni-fuzz/top-productivity/apks/"
10 |
11 | # List of apps
12 | APPS=(
13 | "com.facebook.katana"
14 | "com.facebook.lite"
15 | # "com.facebook.orca"
16 | # "com.google.android.apps.translate"
17 | # "com.azure.authenticator"
18 |
19 | "com.google.android.apps.dynamite"
20 | "com.whatsapp"
21 | "org.telegram.messenger"
22 | "us.zoom.videomeetings"
23 | "com.paypal.android.p2pmobile"
24 | )
25 |
26 |
27 | #APPS=(
28 | # "bloodpressure.bloodpressureapp.bloodpressuretracker"
29 | # "com.affinity.rewarded_play"
30 | # "com.ai.polyverse.mirror"
31 | # "com.appsinnova.weatherforecast.liveweather"
32 | # "com.azure.authenticator"
33 | # "com.cbs.app"
34 | # "com.dd.doordash"
35 | # "com.discord"
36 | # "com.duolingo"
37 | # "com.enflick.android.TextNow"
38 | # "com.facebook.katana"
39 | # "com.facebook.lite"
40 | # "com.facebook.orca"
41 | # "com.foxsports.android"
42 | # "com.gamma.scan"
43 | # "com.google.android.apps.dynamite"
44 | # "com.google.android.apps.translate"
45 | # "com.hbo.hbonow"
46 | # "com.hopper.mountainview.play"
47 | # "com.instagram.android"
48 | # "com.life360.android.safetymapd"
49 | # "com.mcdonalds.app"
50 | # "com.microsoft.teams"
51 | # "com.mistplay.mistplay"
52 | # "com.ncaa.mmlive.app"
53 | # "com.novanews.android.localnews.en"
54 | # "com.offerup"
55 | # "com.onedebit.chime"
56 | # "com.particlenews.newsbreak"
57 | # "com.paypal.android.p2pmobile"
58 | # "com.roku.remote"
59 | # "com.scaleup.chatai"
60 | # "com.sec.android.easyMover"
61 | # "com.spotify.music"
62 | # "com.squareup.cash"
63 | # "com.tmobile.tuesdays"
64 | # "com.ubercab.driver"
65 | # "com.venmo"
66 | # "com.walmart.android"
67 | # "com.whatsapp"
68 | # "com.zellepay.zelle"
69 | # "com.zzkko"
70 | # "de.dynofreevpn.app"
71 | # "me.lyft.android"
72 | # "org.telegram.messenger"
73 | # "tv.pluto.android"
74 | # "us.zoom.videomeetings"
75 | #)
76 |
77 |
78 | #APPS=(
79 | # "tz.co.documentapp.pdfreader.pdfviewer.documentreader"
80 | # "com.ezt.pdfreader.pdfviewer"
81 | # "com.gamma.scan"
82 | # "com.appswing.qr.barcodescanner.barcodereader"
83 | #
84 | # "com.teacapps.barcodescanner"
85 | # "com.microsoft.office.outlook"
86 | # "com.microsoft.office.officehubrow"
87 | #)
88 |
89 | APPS=(
90 | "io.opensea"
91 | "app.token_maker.nft"
92 | "org.toshi"
93 | "com.nftcreatorandroid"
94 | "com.pinterest.twa"
95 | "com.canva.editor"
96 | "com.google.android.gm"
97 | "com.google.android.apps.docs.editors.docs"
98 | "com.google.android.apps.messaging"
99 | "com.google.android.apps.nbu.files"
100 | "com.google.android.apps.tachyon"
101 | "com.wallet.crypto.trustapp"
102 | "com.adobe.reader"
103 | )
104 |
105 | APPS=(
106 | "com.google.android.apps.tachyon"
107 | "com.adobe.reader"
108 | "com.atomczak.notepat"
109 | "com.dropbox.android"
110 | "com.thegrizzlylabs.geniusscan.free"
111 | "com.google.android.calendar"
112 | "alldocumentreader.office.viewer.filereader"
113 | "com.documentreader.documentapp.filereader"
114 | "all.documentreader.filereader.office.viewer"
115 | "com.all.document.reader.doc.pdf.reader"
116 | )
117 |
118 | APPS=(
119 | "com.docx.docxreaderdocviewerassistantwordpdfexcelalldocumentreaderoffice"
120 | "documentreader.pdfviewerapp.filereader.word.docs"
121 | )
122 |
123 | APPS=(
124 | "com.tinaapps.officereader.documents"
125 | "com.document.pdf.reader.alldocument"
126 | )
127 |
128 | APPS=(
129 | "pdf.documentreader.pdfreader.documentviewer"
130 | "com.pdfviewer.pdfreader.eagle.apps"
131 | "com.adobe.spark.post"
132 | "com.adobe.lrmobile"
133 | "app.over.editor"
134 | "com.skype.raider"
135 | "com.simplemobiletools.filemanager"
136 | "com.google.android.keep"
137 | "com.box.android"
138 | "com.pcloud.pcloud"
139 | )
140 |
141 |
142 | APPS=(
143 | "app.grotinou.sushi"
144 | "app.source.getcontact"
145 | "cab.snapp.passenger.ha"
146 | "co.benx.weverse"
147 | "com.adhoclabs.burner"
148 | "com.adobe.reader"
149 | "com.americasbestpics"
150 | "com.android.chrome"
151 | "com.antivirus.viruscleaner.mobilesecurity"
152 | "com.aol.mobile.aolapp"
153 | "com.apalon.myclockfree"
154 | "com.app.recoverdeletedmesasges.viewdeleted.messagerecovery.restoredeletedmessages"
155 | "com.appriss.mobilepatrol"
156 | "com.appsinnova.weatherforecast.liveweather"
157 | "com.appswing.qr.barcodescanner.barcodereader"
158 | "com.atomczak.notepat"
159 | "com.att.shm"
160 | "com.azure.authenticator"
161 | "com.bereal.ft"
162 | "com.blockchainvault"
163 | "com.calculatorapp.simplecalculator.calculator"
164 | "com.calendar.schedule.event"
165 | "com.callapp.contacts"
166 | "com.cardsapp.android"
167 | "com.cbs.app"
168 | "com.chating.messages.chat.fun"
169 | "com.circle38.photocircle"
170 | "com.clap.find.phone.find.lostphone.by.clap"
171 | "com.classdojo.android"
172 | "com.clubhouse.app"
173 | "com.codeway.chatapp"
174 | "com.coffeebeanventures.easyvoicerecorder"
175 | "com.dd.doordash"
176 | "com.dencreak.dlcalculator"
177 | "com.discord"
178 | "com.dish.vvm"
179 | "com.dish.wireless.boostone"
180 | "com.docusign.ink"
181 | "com.dollargeneral.android"
182 | "com.dropbox.android"
183 | "com.duolingo"
184 | "com.enflick.android.TextNow"
185 | "com.enflick.android.tn2ndLine"
186 | "com.ertunga.wifihotspot"
187 | "com.espn.fantasy.lm.football"
188 | "com.espn.score_center"
189 | "com.ezt.pdfreader.pdfviewer"
190 | "com.facebook.katana"
191 | "com.facebook.lite"
192 | "com.facebook.orca"
193 | "com.facebook.talk"
194 | "com.framy.placey"
195 | "com.gamma.scan"
196 | "com.GoFundMe.GoFundMe"
197 | "com.gogii.textplus"
198 | "com.google.android.apps.docs.editors.docs"
199 | "com.google.android.apps.docs.editors.sheets"
200 | "com.google.android.apps.docs.editors.slides"
201 | "com.google.android.apps.googleassistant"
202 | "com.google.android.apps.googlevoice"
203 | "com.google.android.apps.messaging"
204 | "com.google.android.apps.subscriptions.red"
205 | "com.google.android.apps.tachyon"
206 | "com.google.android.apps.tasks"
207 | "com.google.android.apps.translate"
208 | "com.google.android.apps.walletnfcrel"
209 | "com.google.android.apps.youtube.kids"
210 | "com.google.android.calendar"
211 | "com.google.android.contacts"
212 | "com.google.android.gm"
213 | "com.google.android.keep"
214 | "com.grammarly.android.keyboard"
215 | "com.groupme.android"
216 | "com.guppir.swipr"
217 | "com.hotmart.sparkle"
218 | "com.hp.android.printservice"
219 | "com.hp.printercontrol"
220 | "com.hubolabs.hubo"
221 | "com.ilocatemobile.navigation"
222 | "com.indeed.android.jobsearch"
223 | "com.instabridge.android"
224 | "com.instagram.android"
225 | "com.instagram.barcelona"
226 | "com.ita.plus.tel"
227 | "com.kiwibrowser.browser"
228 | "com.letterboxd.letterboxd"
229 | "com.life360.android.safetymapd"
230 | "com.locket.Locket"
231 | "com.loudtalks"
232 | "com.mail.inbox.allemailaccess"
233 | "com.maps.locator.gps.gpstracker.phone"
234 | "com.martianmode.applock"
235 | "com.mcdonalds.app"
236 | "com.meetup"
237 | "com.messenger.messengerpro.social.chat"
238 | "com.microsoft.office.excel"
239 | "com.microsoft.office.officehubrow"
240 | "com.microsoft.office.onenote"
241 | "com.microsoft.office.outlook"
242 | "com.microsoft.office.word"
243 | "com.microsoft.teams"
244 | "com.mightybell.mb"
245 | "com.netflix.mediaclient"
246 | "com.netgear.netgearup"
247 | "com.nglreactnative"
248 | "com.nhn.android.band"
249 | "com.nianticlabs.campfire"
250 | "com.officedocument.word.docx.document.viewer"
251 | "com.okta.android.auth"
252 | "com.olo.dairyqueen.production"
253 | "com.onedebit.chime"
254 | "com.opera.browser"
255 | "com.opera.gx"
256 | "com.pandora.android"
257 | "com.parentsquare.psapp"
258 | "com.particlenews.newsbreak"
259 | "com.paypal.android.p2pmobile"
260 | "com.pinterest.twa"
261 | "com.pitbull.vpnfree"
262 | "com.productivity.findphone.whistle"
263 | "com.radio.pocketfm"
264 | "com.RanaSourav.android.notes"
265 | "com.recommended.videocall"
266 | "com.robokiller.app"
267 | "com.rodolfogs.nextdooralerts"
268 | "com.roku.remote"
269 | "com.scaleup.chatai"
270 | "com.schedule.telmate.telmatescheduleandroid"
271 | "com.sec.android.easyMover"
272 | "com.securus.videoclient"
273 | "com.skype.raider"
274 | "com.smartwatch.bluetooth.sync.notifications"
275 | "com.smartwidgetlabs.chatgpt"
276 | "com.spotify.music"
277 | "com.squareup.cash"
278 | "com.story.downloader.reels.videodownloader.repost.fast.save.video.photos"
279 | "com.subway.mobile.subwayapp03"
280 | "com.talkatone.android"
281 | "com.teacapps.barcodescanner"
282 | "com.textmeinc.freetone"
283 | "com.textmeinc.textme"
284 | "com.ticketmaster.mobile.android.na"
285 | "com.tracfone.straighttalk.myaccount"
286 | "com.tracfone.tracfone.myaccount"
287 | "com.tribel.android"
288 | "com.tsheets.android.hammerhead"
289 | "com.usedialpade.icaller"
290 | "com.v2ray.ang"
291 | "com.venmo"
292 | "com.verizon.contenttransfer"
293 | "com.viber.voip"
294 | "com.walmart.android"
295 | "com.wbd.stream"
296 | "com.whatsapp"
297 | "com.whatsapp.w4b"
298 | "com.wix.android"
299 | "com.xfinity.digitalhome"
300 | "com.yahoo.mobile.client.android.mail"
301 | "com.zellepay.zelle"
302 | "com.zleaguemobile"
303 | "com.zzkko"
304 | "co.seasoned.seasonedMessaging"
305 | "co.yellw.yellowapp"
306 | "info.myapp.allemailaccess"
307 | "instantdigital.all.downloader"
308 | "ir.divar.ha"
309 | "ir.medu.shadha"
310 | "kik.android"
311 | "me.bukovitz.noteit"
312 | "mega.privacy.android.app"
313 | "me.lyft.android"
314 | "mg.locations.track5"
315 | "my.diary.journal"
316 | "net.familo.android"
317 | "net.idt.um.android.bossrevapp"
318 | "notes.notepad.checklist.calendar.todolist.notebook"
319 | "org.antifilter.eitagram"
320 | "org.findmykids.child"
321 | "org.messengerrobik.app"
322 | "org.mozilla.firefox"
323 | "org.mykeap.messenger"
324 | "org.thoughtcrime.securesms"
325 | "org.torproject.torbrowser"
326 | "org.watchduty.app"
327 | "qrcodereader.barcodescanner.scan.qrscanner"
328 | "statussaver.deleted.messages.savevidieos"
329 | "statussaver.statusdownloader.downloadstatus.savestatus"
330 | "statussaver.statusdownloader.downloadstatus.videoimagesaver"
331 | "todolist.scheduleplanner.dailyplanner.todo.reminders"
332 | "tv.pluto.android"
333 | "us.zoom.videomeetings"
334 | "whatsapp.web.whatscan.whatsweb.qrscan"
335 | )
336 |
337 |
338 | pushd ../apk_analyzer
339 |
340 | for app in "${APPS[@]}"; do
341 | echo "Generating config for $app"
342 | out_dir="../eval/$app/configs"
343 | gradle run --args="$(realpath ${APK_DIR}/${app}/base.apk) '${out_dir}'"
344 | done
345 |
--------------------------------------------------------------------------------
/eval/tmp.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -e
4 |
5 | SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
6 |
7 | cd "$SCRIPT_DIR"
8 |
9 | APK_DIR="../../jni-fuzz/top-*/apks/"
10 |
11 | # List of apps
12 | APPS=(
13 | "com.google.android.apps.dynamite"
14 | "com.whatsapp"
15 | "org.telegram.messenger"
16 | "us.zoom.videomeetings"
17 | "com.paypal.android.p2pmobile"
18 |
19 | "com.teacapps.barcodescanner"
20 | "com.microsoft.office.outlook"
21 | "com.microsoft.office.officehubrow"
22 |
23 | "io.opensea"
24 | "app.token_maker.nft"
25 | )
26 |
27 |
28 | pushd ../apk_analyzer
29 |
30 | BASE_PORT=5580
31 | index=0
32 |
33 | for app in "${APPS[@]}"; do
34 | echo "Installing $app"
35 |
36 | # Install the app
37 | ANDROID_SERIAL="localhost:$((BASE_PORT + index))" adb install-multiple -g $APK_DIR/$app/*.apk
38 |
39 | index=$((index+1))
40 | done
41 |
--------------------------------------------------------------------------------
/intent_template.json:
--------------------------------------------------------------------------------
1 | {
2 | "component": "com.ammaraskar.vulnerableapp/.MainActivity",
3 | "actions": [
4 | "android.intent.action.MAIN"
5 | ],
6 | "categories": [
7 | "android.intent.category.LAUNCHER"
8 | ],
9 | "known_extras_keys": {
10 | "my key": "String"
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/patches/ActivityThread.patch:
--------------------------------------------------------------------------------
1 | diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
2 | index 4cc486a0..6716dd4e 100644
3 | --- a/core/java/android/app/ActivityThread.java
4 | +++ b/core/java/android/app/ActivityThread.java
5 | @@ -218,6 +218,7 @@ import java.util.Objects;
6 | import java.util.TimeZone;
7 | import java.util.concurrent.Executor;
8 | import java.util.concurrent.atomic.AtomicInteger;
9 | +import java.util.concurrent.Semaphore;
10 | import java.util.function.Consumer;
11 |
12 | final class RemoteServiceException extends AndroidRuntimeException {
13 | @@ -235,10 +236,17 @@ final class RemoteServiceException extends AndroidRuntimeException {
14 | * {@hide}
15 | */
16 | public final class ActivityThread extends ClientTransactionHandler {
17 | + public final static Semaphore semaphore = new Semaphore(0, true);
18 | + public static boolean synchronizing = false;
19 | + public static void waitForIdle() {
20 | + Slog.v(TAG, "Acquiring semaphore");
21 | + semaphore.acquireUninterruptibly();
22 | + Slog.v(TAG, "Acquired the semaphore");
23 | + }
24 | /** @hide */
25 | public static final String TAG = "ActivityThread";
26 | private static final android.graphics.Bitmap.Config THUMBNAIL_FORMAT = Bitmap.Config.RGB_565;
27 | - static final boolean localLOGV = false;
28 | + static final boolean localLOGV = true;
29 | static final boolean DEBUG_MESSAGES = false;
30 | /** @hide */
31 | public static final boolean DEBUG_BROADCAST = false;
32 | @@ -2108,6 +2116,12 @@ public final class ActivityThread extends ClientTransactionHandler {
33 | TAG, "Reporting idle of " + a +
34 | " finished=" +
35 | (a.activity != null && a.activity.mFinished));
36 | +
37 | + if (synchronizing) {
38 | + Slog.v(TAG, "Releasing semaphore");
39 | + semaphore.release();
40 | + }
41 | +
42 | if (a.activity != null && !a.activity.mFinished) {
43 | try {
44 | am.activityIdle(a.token, a.createdConfig, stopProfiling);
45 |
--------------------------------------------------------------------------------
/run.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | #set -e
4 |
5 | usage() {
6 | echo "Usage: $(basename "$0") [configs_dir] [coverage_socket_address] [fuzzer_arg_1 ...]"
7 | echo " $(basename "$0") -h, --help"
8 | echo
9 | }
10 |
11 | if [[ $1 == "-h" || $1 == "--help" ]]; then
12 | usage
13 | exit 0
14 | fi
15 |
16 | if [[ $# -lt 1 ]]; then
17 | echo "Invalid number of arguments."
18 | usage
19 | exit 1
20 | fi
21 |
22 | app_name=${1}
23 | configs_dir=${2:-.}
24 | coverage_socket_address=${3:-localhost:6249}
25 | args=${@:4}
26 |
27 |
28 | COVERAGE_AGENT="/data/local/tmp/libcoverage_instrumenting_agent.so"
29 |
30 |
31 | run_command() {
32 | local -a cmd=("$@")
33 | echo "Executing command: ${cmd[*]}"
34 | "${cmd[@]}"
35 | }
36 |
37 | # Log the entire output
38 | if [[ "${NO_COV}" == "1" ]]; then
39 | logfile="${configs_dir}/log_nocov.txt"
40 | else
41 | logfile="${configs_dir}/log_cov.txt"
42 | fi
43 | exec > >(tee ${logfile}) 2>&1
44 |
45 | # Check 'RERUN_CORPUS' to determine if we just run the existing corpus
46 | if [[ "${RERUN_CORPUS}" == "1" ]]; then
47 | echo "Re-running corpus instead of fuzzing"
48 | flag_rerun="--run-corpus"
49 | fi
50 |
51 | # Check 'NO_COV'
52 | if [[ "${NO_COV}" == "1" ]]; then
53 | echo "Not using coverage feedback for fuzzing"
54 | nocov="-nocov"
55 | flag_nocov="--no-coverage"
56 | else
57 | nocov="-cov"
58 | fi
59 |
60 |
61 | activity=$(basename "${config%.json}")
62 |
63 | echo "Fuzzing app: ${app_name}"
64 | date
65 |
66 | # Bail out if the coverage agent does not exist.
67 | if ! adb shell ls "${COVERAGE_AGENT}" &>/dev/null; then
68 | echo "Coverage agent not found in /data/local/tmp. Aborting."
69 | exit 1
70 | fi
71 |
72 | # Copy the coverage agent from /data/local/tmp to the app's data directory.
73 | echo "Copying coverage agent to app's data directory."
74 | run_command adb shell mkdir -p "/data/data/${app_name}/code_cache/startup_agents/"
75 | run_command adb shell cp "${COVERAGE_AGENT}" "/data/data/${app_name}/code_cache/startup_agents/"
76 |
77 | echo "Force-stopping all active apps"
78 | running_apps=$(adb shell dumpsys activity recents | grep RecentTaskInfo -A 20 | grep baseActivity={ | sed -e 's:^.*{::' -e 's:/.*$::' )
79 |
80 | for app in ${running_apps}; do
81 | run_command adb shell am force-stop "${app}"
82 | done
83 |
84 | sleep 2
85 |
86 | # Check 'NATIVE_TRACE' environment variable to determine whether to enable native tracing.
87 | # If 'NATIVE_TRACE' is set to 'true', enable native tracing.
88 | if [[ "${NATIVE_TRACE}" == "1" ]]; then
89 | echo "Enabling native tracing"
90 | flag_tracing="-t --traces-dir ${configs_dir}/traces${nocov}/"
91 | else
92 | TIMEOUT="timeout $((3600*24))"
93 | fi
94 |
95 | echo "Starting fuzzer"
96 | run_command $TIMEOUT cargo run -- -i ${configs_dir}/configs/ --corpus-dir ${configs_dir}/corpus${nocov}/ --crashes-dir ${configs_dir}/crashes${nocov}/ --overall-coverage-file ${configs_dir}/edgecount${nocov}.txt --coverage-socket-address=${coverage_socket_address} ${flag_nocov} ${flag_rerun} ${flag_tracing} ${args}
97 |
--------------------------------------------------------------------------------
/run_multiple_apps.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | apps="$@"
4 |
5 | eval_dir="eval-fdroid"
6 |
7 | for app in $apps; do
8 | echo -e "\nRunning $app"
9 | echo "RUST_BACKTRACE=full ANDROID_SERIAL=localhost:55${PORT} ./run.sh ${app} ./${eval_dir}/${app} localhost:62${PORT}"
10 | RUST_BACKTRACE=full ANDROID_SERIAL=localhost:55${PORT} ./run.sh ${app} ./${eval_dir}/${app} localhost:62${PORT}
11 | done
12 |
--------------------------------------------------------------------------------
/rust-toolchain.toml:
--------------------------------------------------------------------------------
1 | [toolchain]
2 | channel = "1.70"
3 |
--------------------------------------------------------------------------------
/src/adb_device.rs:
--------------------------------------------------------------------------------
1 | //! ADB device representing an Android device or emulator.
2 | //!
3 | //! This contains utility functions to interact with the device through adb.
4 |
5 | use core::panic;
6 | use std::{
7 | fs,
8 | io::{self, BufRead, BufReader, Read, Write},
9 | path::PathBuf,
10 | process::{Child, Command},
11 | sync::{Arc, Mutex},
12 | thread,
13 | time::{Duration, Instant, SystemTime, UNIX_EPOCH},
14 | };
15 |
16 | use serde::{Deserialize, Serialize};
17 |
18 | use crate::util::encode_hex;
19 |
20 | use tempfile::tempdir;
21 |
22 | use subprocess::ExitStatus;
23 | use subprocess::Popen;
24 | use subprocess::PopenConfig;
25 | use subprocess::Redirection;
26 |
27 | #[derive(Clone, Debug, Serialize, Deserialize)]
28 | pub struct AdbDevice {
29 | adb_command: String,
30 | }
31 |
32 | impl AdbDevice {
33 | pub fn new(adb_command: &str) -> Self {
34 | Self {
35 | adb_command: adb_command.to_owned(),
36 | }
37 | }
38 |
39 | /// Runs a command on the device and returns the stdout.
40 | fn run_command(&self, command: &str) -> Result {
41 | let mut adb_command = Command::new(&self.adb_command);
42 | adb_command.arg("shell").arg(command);
43 | println!("Running command: {:?}", adb_command);
44 | let output = adb_command
45 | .output()
46 | .expect(&format!("Failed to execute command: {}", command));
47 |
48 | let stdout = String::from_utf8(output.stdout).expect("Failed to parse command stdout");
49 |
50 | // Check the exit code
51 | if !output.status.success() {
52 | let stderr = String::from_utf8(output.stderr).expect("Failed to parse command stderr");
53 |
54 | return Err(libafl::Error::unknown(&format!(
55 | "Command failed: {}\nStdout: {}\nStderr: {}",
56 | command, stdout, stderr
57 | )));
58 | }
59 |
60 | Ok(stdout)
61 | }
62 |
63 | /// Runs a command on the device and returns the stdout as a reader.
64 | fn run_command_io(&self, command: &str) -> Result {
65 | let mut adb_command = Command::new(&self.adb_command);
66 | adb_command.arg("shell").arg(command);
67 |
68 | let child = adb_command
69 | .stdin(std::process::Stdio::piped())
70 | .stdout(std::process::Stdio::piped())
71 | .stderr(std::process::Stdio::piped())
72 | .spawn()
73 | .expect(&format!("Failed to spawn command: {}", command));
74 |
75 | Ok(child)
76 | }
77 |
78 | /// Runs an "am start" command on the device
79 | pub fn run_am_start(&self, command: &str, app_name: &str, timeout: Duration) -> Result<(), io::Error> {
80 | let mut adb_command = Command::new(&self.adb_command);
81 | adb_command.arg("shell").arg(command);
82 |
83 | for i in 0..5 {
84 | let mut restart = false;
85 |
86 | println!("Running command: {:?}", adb_command);
87 |
88 | let mut p = Popen::create(
89 | &[
90 | &self.adb_command.to_owned(),
91 | &"shell".to_owned(),
92 | &command.to_owned(),
93 | ],
94 | PopenConfig {
95 | stdout: Redirection::Pipe,
96 | stderr: Redirection::Pipe,
97 | ..Default::default()
98 | },
99 | )
100 | .unwrap();
101 |
102 | // Wait for the command to finish
103 | let result = p.wait_timeout(timeout);
104 | if let Ok(None) = result {
105 | println!("Command timed out");
106 |
107 | // A timeout indicates a lack of resources
108 | restart = true;
109 |
110 | if p.kill().is_err() {
111 | println!("Failed to kill");
112 | }
113 |
114 | // Wait for the command to finish
115 | println!("Waiting for command to finish");
116 | p.wait().unwrap();
117 | }
118 |
119 | // Capture stdout
120 | let mut stdout = String::new();
121 | BufReader::new(p.stdout.take().unwrap())
122 | .read_to_string(&mut stdout)
123 | .unwrap();
124 |
125 | // Capture stderr
126 | let mut stderr = String::new();
127 | BufReader::new(p.stderr.take().unwrap())
128 | .read_to_string(&mut stderr)
129 | .unwrap();
130 |
131 | // Capture the exit code
132 | let exit_code = p.poll().unwrap();
133 |
134 | // The command failed when there is either a non-zero exit code or
135 | // output on stderr.
136 | // Thus, we return Ok only if the command succeeded.
137 | if stderr.contains("intent has been delivered to currently running top-most instance.")
138 | {
139 | return Ok(());
140 | }
141 |
142 | if let ExitStatus::Exited(0) = exit_code {
143 | // Now, we need to check the output on stderr.
144 | // Successfull, if stderr is empty or contains "has been delivered"
145 | if stderr.is_empty() {
146 | return Ok(());
147 | }
148 |
149 | if stderr.contains("Activity class") && stderr.contains("does not exist") {
150 | // This should be handled like a timeout
151 | println!("Activity does not exist");
152 | return Err(io::Error::new(io::ErrorKind::TimedOut, "Activity does not exist"));
153 | }
154 |
155 | println!("Command failed (stderr)");
156 | } else {
157 | println!("Command failed (exit code): {:?}", exit_code);
158 | }
159 |
160 | // If the device is low on resources, we restart it
161 | if stderr.contains("OutOfResourcesException")
162 | || stderr.contains(
163 | "Activity not started, its current task has been brought to the front",
164 | )
165 | {
166 | restart = true;
167 | }
168 |
169 | println!("Stdout: {}", stdout);
170 | println!("Stderr: {}", stderr);
171 |
172 | if restart {
173 | if i > 1 {
174 | self.restart_device();
175 | }
176 |
177 | self.restart_app(app_name);
178 | }
179 |
180 | std::thread::sleep(std::time::Duration::from_secs(2));
181 | }
182 |
183 | Err(io::Error::new(
184 | io::ErrorKind::Other,
185 | "Maximum retries reached",
186 | ))
187 | }
188 |
189 | #[allow(dead_code)]
190 | fn start_app_monkey(&self, app_name: &str) {
191 | // Calling the monkey multiple times will not create additional idle events
192 | self.run_command(&format!("monkey --pct-syskeys 0 -p {} 1", app_name))
193 | .expect("Failed to start app");
194 | }
195 |
196 | fn start_app_explicit(&self, app_name: &str) -> Result<(), libafl::Error> {
197 | // Get the main activity of the app
198 | let output = self
199 | .run_command(&format!(
200 | "cmd package resolve-activity --brief {} | tail -n 1",
201 | app_name
202 | ))
203 | .expect("Failed to get main activity");
204 |
205 | let main_activity = output.trim();
206 |
207 | // Bail out if there is any space character in the main activity
208 | if main_activity.contains(" ") {
209 | return Err(libafl::Error::unknown(&format!(
210 | "Invalid main activity: {}",
211 | main_activity
212 | )));
213 | }
214 |
215 | // Start the app
216 | let command = format!(
217 | "am start-activity --attach-agent /data/user/0/{}/code_cache/startup_agents/libcoverage_instrumenting_agent.so {}",
218 | app_name, main_activity
219 | );
220 |
221 | match self.run_command(&command) {
222 | Ok(_) => Ok(()),
223 | Err(err) => {
224 | Err(libafl::Error::unknown(&format!(
225 | "Failed to start app: {}",
226 | err
227 | )))
228 | },
229 | }
230 | }
231 |
232 | /// Tries to start the app with the given name.
233 | pub fn start_app(&self, app_name: &str) -> Result<(), libafl::Error> {
234 | println!("Starting app: {}", app_name);
235 | //self.start_app_monkey(app_name);
236 | self.start_app_explicit(app_name)?;
237 |
238 | // Get the pid of the app
239 | std::thread::sleep(std::time::Duration::from_secs(2));
240 | let pid = self.pid_of(app_name)?;
241 |
242 | println!("App started (pid {}), waiting for idle", pid);
243 |
244 | std::thread::sleep(std::time::Duration::from_secs(5));
245 |
246 | // Wait for the app to start
247 | let shell_command = format!("logcat --pid={}", pid,);
248 |
249 | let mut logcat_child = self
250 | .run_command_io(&shell_command)
251 | .expect("Failed to start logcat command");
252 |
253 | let stdout = logcat_child.stdout.take().expect("Failed to get stdout");
254 | let reader = &mut BufReader::new(stdout).lines();
255 |
256 | // Shared object holding the time of the last update
257 | let last_update = Arc::new(Mutex::new(Some(Instant::now())));
258 | let last_update_clone = Arc::clone(&last_update);
259 |
260 | // Start timeout thread
261 | let handle = thread::spawn(move || {
262 | loop {
263 | match *last_update_clone.lock().unwrap() {
264 | Some(my_time) => {
265 | if my_time.elapsed() > Duration::from_secs(20) {
266 | break;
267 | }
268 | }
269 | None => {
270 | break;
271 | }
272 | }
273 |
274 | thread::sleep(Duration::from_secs(1));
275 | }
276 |
277 | logcat_child.kill().expect("Failed to kill child");
278 | });
279 |
280 | let wait_for_timeout_thread = || {
281 | *last_update.lock().unwrap() = None;
282 | handle.join().unwrap();
283 | };
284 |
285 | while let Some(line) = reader.next() {
286 | // Update the last_update
287 | *last_update.lock().unwrap() = Some(Instant::now());
288 |
289 | match line {
290 | Ok(line) => {
291 | if line.contains("ActivityThread: Reporting idle of ActivityRecord") {
292 | println!("Found idle message: {:?}", line);
293 |
294 | // Signal thread to stop
295 | wait_for_timeout_thread();
296 |
297 | return Ok(());
298 | }
299 | }
300 | Err(_) => {
301 | continue;
302 | }
303 | }
304 |
305 | }
306 |
307 | println!("Failed to find idle message");
308 |
309 | // Signal thread to stop
310 | wait_for_timeout_thread();
311 |
312 | return Err(libafl::Error::unknown(
313 | "Could not find idle message in logcat",
314 | ));
315 | }
316 |
317 | /// Stops the app with the given name.
318 | pub fn stop_app(&self, app_name: &str) -> Result<(), libafl::Error> {
319 | println!("Stopping app: {}", app_name);
320 | for _ in 0..5 {
321 | if self
322 | .run_command(&format!("pm disable {}", app_name))
323 | .is_ok()
324 | {
325 | if self.run_command(&format!("pm enable {}", app_name)).is_ok() {
326 | return Ok(());
327 | }
328 | }
329 |
330 | std::thread::sleep(std::time::Duration::from_secs(1));
331 | }
332 |
333 | return Err(libafl::Error::unknown(&format!(
334 | "Failed to stop app {}",
335 | app_name
336 | )));
337 | }
338 |
339 | /// Restarts the app with the given name.
340 | pub fn restart_app(&self, app_name: &str) {
341 | println!("Restarting app: {}", app_name);
342 |
343 | for i in 0..5 {
344 | if i > 1 {
345 | self.restart_device();
346 | }
347 |
348 | self.stop_app(app_name).expect("Failed to stop app");
349 |
350 | // Some apps need to be started immediately, others need some time
351 | std::thread::sleep(std::time::Duration::from_secs(i % 2));
352 |
353 | match self.start_app(app_name) {
354 | Ok(_) => return,
355 | Err(err) => {
356 | println!("Failed to start app: {}", err);
357 | }
358 | }
359 | }
360 |
361 | // Failed to start the app
362 | panic!("Failed to re-start app");
363 | }
364 |
365 | /// Returns the pid of the app with the given name.
366 | pub fn pid_of(&self, app_name: &str) -> Result {
367 | let shell_command = format!("pidof -s {}", app_name,);
368 |
369 | let output = self.run_command(&shell_command)?;
370 |
371 | let pid = output.trim().to_owned();
372 |
373 | // Return Err if pid is empty
374 | if pid.is_empty() {
375 | return Err(libafl::Error::unknown(&format!(
376 | "Failed to get pid of app {}",
377 | app_name
378 | )));
379 | }
380 |
381 | Ok(pid)
382 | }
383 |
384 | /// Restart the entire device via adb.
385 | pub fn restart_device(&self) {
386 | println!("Restarting device");
387 | self.run_command("stop").expect("Failed to stop device");
388 | std::thread::sleep(std::time::Duration::from_secs(1));
389 | self.run_command("start").expect("Failed to start device");
390 | std::thread::sleep(std::time::Duration::from_secs(3));
391 | }
392 |
393 | /// Enables native hooking for an application and restarts it, if it was not already enabled.
394 | pub fn enable_native_hooking(&self, app_name: &str) {
395 | println!("Enabling native hooking for app: {}", app_name);
396 | let was_enabled = self.is_native_hooking_enabled(app_name);
397 |
398 | // The filename is the file ".hook_native" in the app's data directory
399 | let filename = format!("/data/user/0/{}/.hook_native", app_name);
400 |
401 | // Create the file
402 | self.run_command(&format!("touch {}", filename))
403 | .expect("Failed to touch file");
404 |
405 | if !was_enabled {
406 | // Restart the app if it was not already enabled
407 | self.restart_app(app_name);
408 | }
409 | }
410 |
411 | /// Disables native hooking for an application and restarts it, if it was enabled.
412 | pub fn disable_native_hooking(&self, app_name: &str) {
413 | println!("Disabling native hooking for app: {}", app_name);
414 | let was_enabled = self.is_native_hooking_enabled(app_name.clone());
415 |
416 | // The filename is the file ".hook_native" in the app's data directory
417 | let filename = format!("/data/user/0/{}/.hook_native", app_name);
418 |
419 | // Delete the file
420 | self.run_command(&format!("rm -f {}", filename))
421 | .expect("Failed to delete file");
422 |
423 | if was_enabled {
424 | // Restart the app if it was enabled
425 | self.restart_app(app_name);
426 | }
427 | }
428 |
429 | /// Check if native hooking is enabled for the given app.
430 | pub fn is_native_hooking_enabled(&self, app_name: &str) -> bool {
431 | // The filename is the file ".hook_native" in the app's data directory
432 | let filename = format!("/data/user/0/{}/.hook_native", app_name);
433 |
434 | // Check if the file exists
435 | let output = self.run_command(&format!("ls {}", filename));
436 |
437 | return match output {
438 | Ok(output) => output.trim() == filename,
439 | Err(_) => false,
440 | };
441 | }
442 |
443 | /// Pulls trace files from the device.
444 | pub fn pull_native_trace_files(
445 | &self,
446 | app_name: &str,
447 | trace_dir_host: &PathBuf,
448 | ) -> Result<(), io::Error> {
449 | println!("Pulling trace files for app: {}", app_name);
450 |
451 | // The trace files are located in the app's data directory
452 | let trace_dir = format!("/data/user/0/{}/native_traces", app_name);
453 |
454 | // Pull the files to a temporary directory
455 | let temp_dir = tempdir()?;
456 | let temp_dir_path = temp_dir.path().to_owned();
457 |
458 | // Pull the files
459 | let output = Command::new(&self.adb_command)
460 | .arg("pull")
461 | .arg(&trace_dir)
462 | .arg(&temp_dir_path)
463 | .output()?;
464 |
465 | // Print the output
466 | println!("Output: {}", String::from_utf8_lossy(&output.stdout));
467 |
468 | // Move the files to the destination directory
469 | let native_traces_dir = temp_dir_path.join("native_traces");
470 |
471 | if !native_traces_dir.exists() {
472 | println!("No native traces found");
473 | return Ok(());
474 | }
475 |
476 | fs::create_dir_all(trace_dir_host)?;
477 | for entry in fs::read_dir(native_traces_dir)? {
478 | let entry = entry?;
479 | let dest_path = trace_dir_host.join(entry.file_name());
480 | fs::copy(entry.path(), dest_path)?;
481 | }
482 |
483 | // Delete the files on the device
484 | self.run_command(&format!("rm -rf {}", trace_dir))
485 | .expect("Failed to delete files on the device");
486 |
487 | Ok(())
488 | }
489 |
490 | // Creates a file on the device with the given bytes
491 | pub fn create_file(&self, filename: &str, content: Vec) {
492 | //println!("Creating file: {} (length: {})", filename, content.len());
493 |
494 | // Create the file
495 | self.run_command(&format!("touch {}", filename))
496 | .expect("Failed to touch file");
497 |
498 | // Write the byte content to the file
499 | self.run_command(&format!(
500 | "echo -n -e \"{}\" > {}",
501 | encode_hex(&content),
502 | filename
503 | ))
504 | .expect("Failed to write to file");
505 | }
506 |
507 | // Register a content on the device with the given bytes
508 | pub fn register_content(&self, uri: &str, content: Vec) {
509 | //println!("Registering content: {} (length: {})", uri, content.len());
510 |
511 | let mut child = self
512 | .run_command_io(&format!("content write --uri {}", uri))
513 | .expect("Failed to register content");
514 |
515 | // Write the byte content to the file
516 | let mut stdin = child.stdin.take().unwrap();
517 |
518 | stdin.write_all(&content).expect("Failed to write to file");
519 |
520 | // Close stdin
521 | drop(stdin);
522 |
523 | // Wait for the command to finish
524 | child
525 | .wait()
526 | .expect("Failed to wait for content command to finish");
527 | }
528 |
529 | // Grant content provider uri permissions to the given package
530 | pub fn grant_uri_permissions(&self, package: &str) {
531 | // The command is something like: am broadcast -n 'org.gts3.jnifuzz.contentprovider/org.gts3.jnifuzz.contentprovider.UriPermissionManager' -a org.gts3.jnifuzz.sampleintent.UriPermissionManager --es android.intent.extra.PACKAGE_NAME 'com.instagram.android'
532 | // This function uses run_command
533 | //println!("Granting uri permissions: {} to {}", uri, package);
534 |
535 | self.run_command(&format!(
536 | "am broadcast -n 'org.gts3.jnifuzz.contentprovider/org.gts3.jnifuzz.contentprovider.UriPermissionManager' \
537 | -a org.gts3.jnifuzz.sampleintent.GRANT_PERMISSION \
538 | --es android.intent.extra.PACKAGE_NAME '{}'",
539 | package,
540 | )).expect("Failed to grant uri permissions");
541 | }
542 |
543 | // Set the given app as debug app
544 | pub fn set_debug_app(&self, package: &str) {
545 | self.run_command(&format!(
546 | "am set-debug-app --persistent {}",
547 | package,
548 | )).expect("Failed to set debug app");
549 | }
550 |
551 | // Reports if a native crash happened in the app, and whether it's caused by
552 | // the coverage agent (i.e., libcoverage_agent found in the stack trace)
553 | pub fn report_native_crash(&self, app_name: &str) {
554 | // Check the logcat 'crash' buffer of the past 3 seconds for native crashes
555 | let start_time = (SystemTime::now() - Duration::from_secs(3))
556 | .duration_since(UNIX_EPOCH)
557 | .unwrap();
558 |
559 | let shell_command = &format!(
560 | "logcat -b crash -t {}.{:03}",
561 | start_time.as_secs(),
562 | start_time.subsec_millis()
563 | );
564 |
565 | let output = self
566 | .run_command(&shell_command)
567 | .expect("Failed to start logcat command");
568 |
569 | let mut found_crash = false;
570 | let mut caused_by_coverage = false;
571 |
572 | for line in output.lines() {
573 | // Check if the line contains "pid: "
574 | if line.contains("Fatal signal") {
575 | found_crash = line.contains(&format!("({})", app_name));
576 | }
577 |
578 | if found_crash && line.contains("libcoverage_instrumenting_agent.so") {
579 | caused_by_coverage = true;
580 | break;
581 | }
582 | }
583 |
584 | if found_crash {
585 | println!("Found native crash (caused by coverage: {})", caused_by_coverage);
586 | }
587 | }
588 | }
589 |
--------------------------------------------------------------------------------
/src/adb_executor.rs:
--------------------------------------------------------------------------------
1 | //! [Executor] for running a single intent through adb on device/emulator.
2 | //!
3 | //! This contains the main [AdbExecutor] struct which implements the libAFL
4 | //! [Executor] trait. This struct contains the logic to actually invoke and
5 | //! monitor the execution of the intent on the device.
6 |
7 | use std::fmt::Debug;
8 | use std::time::Duration;
9 | use std::{fmt::Formatter, marker::PhantomData};
10 |
11 | use libafl::prelude::{
12 | ExitKind, HasBytesVec, HasObservers, MatchName, ObserversTuple, UsesObservers,
13 | };
14 | use libafl::{executors::Executor, prelude::UsesInput, state::UsesState};
15 |
16 | use crate::adb_device::AdbDevice;
17 | use crate::intent_input::{ExtraType, IntentInput, ReceiverType, URIScheme};
18 |
19 | // Lots of single letter generic types get confusing. A best-effort explanation
20 | // from my understanding:
21 | //
22 | // EM: Execution Manager?
23 | // OT: ObserversType, usually a subtype of ObserversTuple.
24 | // Z: Fuzzer
25 | // S: State, the input state for the program?
26 | pub struct AdbExecutor {
27 | adb_device: AdbDevice,
28 |
29 | observers: OT,
30 | phantom: PhantomData<(EM, S, Z)>,
31 | }
32 |
33 | impl AdbExecutor {
34 | pub fn new(adb_device: AdbDevice, observers: OT) -> Self {
35 | Self {
36 | adb_device,
37 | observers,
38 | phantom: PhantomData,
39 | }
40 | }
41 | }
42 |
43 | impl Executor for AdbExecutor
44 | where
45 | EM: UsesState,
46 | OT: Debug + MatchName + ObserversTuple,
47 | S: UsesInput,
48 | Z: UsesState,
49 | {
50 | fn run_target(
51 | &mut self,
52 | _fuzzer: &mut Z,
53 | _state: &mut Self::State,
54 | _mgr: &mut EM,
55 | input: &Self::Input,
56 | ) -> Result {
57 | //println!("Asked to run with input: {:?}", input);
58 |
59 | // Only 'activity' and 'broadcastReceiver' as receiver types are implemented as of now
60 | let timeout = match input.receiver_type {
61 | ReceiverType::Activity => Duration::from_secs(5),
62 | _ => Duration::from_secs(20),
63 | };
64 |
65 | // Get the command to run on the device
66 | let shell_command = input.shell_command();
67 |
68 | // Create required files and content on the device for all URI extras
69 | input
70 | .extras
71 | .iter()
72 | .enumerate()
73 | .filter_map(|(index, extra)| match &extra.value {
74 | ExtraType::URI(uri) => Some((index + 1, uri)),
75 | _ => None,
76 | })
77 | .chain(input.data.iter().map(|uri| (0, uri)))
78 | .for_each(|(id, uri)| {
79 | let identifier = uri.identifier(id);
80 | let content_bytes = uri.content.bytes().to_vec();
81 |
82 | // Depending on the scheme, create the file or register the content on the adb device
83 | // Note that we need to skip the "file://" prefix for the identifier if it is a file
84 | match uri.scheme {
85 | URIScheme::Content => {
86 | self.adb_device.register_content(&identifier, content_bytes)
87 | }
88 | URIScheme::File => self.adb_device.create_file(&identifier[7..], content_bytes),
89 | URIScheme::Other => {}
90 | }
91 | });
92 |
93 | // Run the command
94 | println!("Running command: {:?}", shell_command);
95 | let result = self
96 | .adb_device
97 | .run_am_start(&shell_command, &input.component_package, timeout);
98 |
99 | // The command failed when there is either a non-zero exit code or
100 | // output on stderr.
101 | // Thus, we return Ok only if the command succeeded.
102 | match result {
103 | Ok(_) => Ok(ExitKind::Ok),
104 | Err(_) => Ok(ExitKind::Timeout)
105 | }
106 | }
107 | }
108 |
109 | // Need to implement HasObservers so we can use observers with this executor.
110 | impl HasObservers for AdbExecutor
111 | where
112 | S: UsesInput,
113 | OT: ObserversTuple,
114 | {
115 | fn observers(&self) -> &Self::Observers {
116 | &self.observers
117 | }
118 |
119 | fn observers_mut(&mut self) -> &mut Self::Observers {
120 | &mut self.observers
121 | }
122 | }
123 |
124 | // Debug and UsesState are required traits for implementing Executor in libAFL.
125 | impl Debug for AdbExecutor {
126 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
127 | f.debug_struct("AdbExecutor").finish()
128 | }
129 | }
130 |
131 | impl UsesState for AdbExecutor
132 | where
133 | S: UsesInput,
134 | {
135 | type State = S;
136 | }
137 |
138 | impl UsesObservers for AdbExecutor
139 | where
140 | OT: ObserversTuple,
141 | S: UsesInput,
142 | {
143 | type Observers = OT;
144 | }
145 |
--------------------------------------------------------------------------------
/src/intent_generator.rs:
--------------------------------------------------------------------------------
1 | //! [Generator] for creating an initial intent.
2 | //!
3 | //! This module implements logic for creating an initial [IntentInput] for
4 | //! fuzzing.
5 | use std::{cmp::max, collections::HashMap, fs::File};
6 |
7 | use libafl::{impl_serdeany, prelude::Generator, state::HasNamedMetadata};
8 | use serde::{Deserialize, Serialize};
9 |
10 | use crate::intent_input::{IntentInput, MimeType, ReceiverType};
11 |
12 | /// A template for an intent to start mutating, loaded from intent_template.json
13 | #[derive(Serialize, Deserialize, Clone, Debug)]
14 | pub struct IntentTemplate {
15 | receiver_type: ReceiverType,
16 | component: String,
17 | actions: Vec,
18 | categories: Vec,
19 | pub known_extras_keys: HashMap,
20 | }
21 |
22 | impl_serdeany!(IntentTemplate);
23 |
24 | impl IntentTemplate {
25 | /// Get the package name from the component attribute.
26 | pub fn package_name(&self) -> String {
27 | return self.component.split("/").collect::>()[0].to_string();
28 | }
29 |
30 | /// Get the class name from the component attribute.
31 | pub fn class_name(&self) -> String {
32 | return self.component.split("/").collect::>()[1].to_string();
33 | }
34 |
35 | pub fn number_of_intents(&self) -> usize {
36 | return self.actions.len() * max(1, self.categories.len());
37 | }
38 |
39 | pub fn get_intent_input_for_index(&self, index: usize) -> IntentInput {
40 | let action_index = index % self.actions.len();
41 | let category_index = index / max(1, self.actions.len());
42 |
43 | IntentInput {
44 | receiver_type: self.receiver_type.clone(),
45 | action: self.actions[action_index].clone(),
46 | category: self.categories.get(category_index).cloned().unwrap_or_default(),
47 | component_package: self.package_name(),
48 | component_class: self.class_name(),
49 |
50 | data: None,
51 | mime_type: MimeType::TextPlain,
52 | flags: 0,
53 |
54 | extras: Vec::new(),
55 | }
56 | }
57 | }
58 |
59 | /// Generates some starting intents based on the data from intent_template.json
60 | pub struct IntentGenerator {
61 | templates: Vec,
62 | read_count: u32,
63 | }
64 |
65 | impl IntentGenerator {
66 | pub fn new(config: &str) -> Self {
67 | // Create empty vec to store the templates
68 | // If str is a file, read the file and parse the JSON
69 | if let Ok(dir) = std::fs::read_dir(config) {
70 | // If str is a directory, read all the files in the directory and parse the JSON
71 | let mut templates: Vec = Vec::new();
72 | for entry in dir {
73 | if let Ok(entry) = entry {
74 | let file = File::open(entry.path()).expect("Failed to open intent template file");
75 | let template: IntentTemplate =
76 | serde_json::from_reader(file).expect("Failed to parse intent template file");
77 | if template.receiver_type == ReceiverType::Activity {
78 | templates.push(template);
79 | }
80 | }
81 | }
82 | if templates.is_empty() {
83 | panic!("No intent templates found in directory");
84 | }
85 | return Self { templates, read_count: 0 };
86 | } else if let Ok(file) = File::open(config) {
87 | let template: IntentTemplate =
88 | serde_json::from_reader(file).expect("Failed to parse intent template file");
89 | return Self { templates: vec![template], read_count: 0 };
90 | }
91 |
92 | // If str is not a file or directory, panic
93 | panic!("Failed to open intent template file");
94 | }
95 |
96 | /// Get the total number of base intents, a combination of all the actions
97 | /// and categories.
98 | pub fn number_of_intents(&self) -> usize {
99 | return self.templates.iter().map(|t| t.number_of_intents()).sum();
100 | }
101 |
102 | pub fn package_name(&self) -> String {
103 | // Return the package name of the first template
104 | return self.templates[0].package_name();
105 | }
106 |
107 | pub fn enable_synchronization(&self) -> bool {
108 | self.templates[0].receiver_type == ReceiverType::Activity
109 | }
110 |
111 | /// Return whether the receiver of this template is supported.
112 | pub fn is_supported(&self) -> bool {
113 | return self.templates[0].receiver_type == ReceiverType::Activity
114 | || self.templates[0].receiver_type == ReceiverType::BroadcastReceiver;
115 | }
116 | }
117 |
118 | impl Generator for IntentGenerator
119 | where
120 | S: HasNamedMetadata,
121 | {
122 | fn generate(&mut self, state: &mut S) -> Result {
123 | // Go through all the templates and generate the intent inputs for each template.
124 | // Keep in mind that every template generates one or more intent inputs.
125 | let input = self.templates.iter().flat_map(|t| {
126 | (0..t.number_of_intents()).map(move |i| t.get_intent_input_for_index(i))
127 | }).nth(self.read_count as usize).unwrap();
128 |
129 | if !state.has_named_metadata::("intent_template") {
130 | // Save the template to the state so that we can use it later.
131 | state.add_named_metadata(self.templates[0].clone(), "intent_template");
132 | }
133 |
134 | self.read_count += 1;
135 |
136 | Ok(input)
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/src/intent_input.rs:
--------------------------------------------------------------------------------
1 | //! A libafl [Input] representing a single intent.
2 |
3 | use std::fmt;
4 | use std::{fmt::Write, hash::Hasher};
5 | use strum_macros::EnumIter;
6 |
7 | use libafl::prelude::{BytesInput, HasBytesVec, Input};
8 | use serde::{Deserialize, Serialize};
9 |
10 | use fasthash::{farm::Hasher128, FastHasher, HasherExt};
11 |
12 | use crate::util::encode_hex;
13 |
14 | #[derive(Serialize, Deserialize, Clone, Debug)]
15 | pub struct IntentInput {
16 | // The stuff up here usually doesn't get mutated because it is needed for
17 | // the intent to even match and hit the intent receiver.
18 | /// The type of the receiver (i.e., activity, service, or broadcast receiver)
19 | pub receiver_type: ReceiverType,
20 | /// The component that receives the intent, e.g
21 | /// `com.example.app/.ExampleActivity`
22 | pub component_package: String,
23 | pub component_class: String,
24 | /// The action of the intent.
25 | pub action: String,
26 | /// The category of the intent.
27 | pub category: String,
28 |
29 | // These fields get mutated!
30 | /// The `data` uri component of the Input, raw UTF-8 bytes.
31 | pub data: Option,
32 | // The `type`, a mime type for the data.
33 | pub mime_type: MimeType,
34 | // The `flags` for the intent.
35 | pub flags: u32,
36 | // The `extras` for the intent.
37 | pub extras: Vec,
38 | }
39 |
40 | impl IntentInput {
41 | /// Command to send this intent via adb shell.
42 | pub fn shell_command(&self) -> String {
43 | // The way adb shell handles commands is documented here:
44 | // https://developer.android.com/studio/command-line/adb#shellcommands
45 | // but basically we need to generate the command we want to run as
46 | // a single string fit for use on the android shell.
47 | let am_command = match self.receiver_type {
48 | // Activity
49 | ReceiverType::Activity => "start",
50 | // Broadcast Receiver
51 | ReceiverType::BroadcastReceiver => "broadcast",
52 | // Service is not yet implemented
53 | _ => panic!("Unsupported receiver type"),
54 | };
55 |
56 | let mut command = format!(
57 | "am {} -n '{}' -a '{}' -t '{}' --grant-read-uri-permission ",
58 | am_command,
59 | self.component(),
60 | self.action,
61 | self.mime_type
62 | );
63 |
64 | // Append data to the shell_command if it exists.
65 | if let Some(data) = &self.data {
66 | write!(&mut command, " -d '{}'", data.identifier(0)).unwrap();
67 | }
68 |
69 | // Append category to the shell_command if it exists.
70 | if !self.category.is_empty() {
71 | write!(&mut command, " -c {}", self.category).unwrap();
72 | }
73 |
74 | // Append extras to the shell_command.
75 | let extras_command = self
76 | .extras
77 | .iter()
78 | .enumerate()
79 | .filter_map(|(index, extra)| extra.command_args(index + 1))
80 | .collect::>()
81 | .join(" ");
82 |
83 | write!(&mut command, " ").unwrap();
84 | command.push_str(&extras_command);
85 |
86 | command
87 | }
88 |
89 | /// Creates a unique hash of this input.
90 | pub fn hash(&self) -> String {
91 | let mut hasher = Hasher128::new();
92 |
93 | hasher.write(self.component().as_bytes());
94 | hasher.write(self.action.as_bytes());
95 | hasher.write(self.category.as_bytes());
96 | hasher.write(&serde_json::to_vec(&self.data).unwrap());
97 | hasher.write(self.mime_type.to_string().as_bytes());
98 | hasher.write(&self.flags.to_le_bytes());
99 |
100 | for extra in &self.extras {
101 | hasher.write(extra.key.as_bytes());
102 | hasher.write(&serde_json::to_vec(&extra.value).unwrap());
103 | }
104 |
105 | format!("{:032x}", hasher.finish_ext())
106 | }
107 |
108 | /// The component that receives the intent, e.g
109 | /// `com.example.app/.ExampleActivity`
110 | pub fn component(&self) -> String {
111 | format!("{}/{}", self.component_package, self.component_class)
112 | }
113 | }
114 |
115 | impl Input for IntentInput {
116 | /// Generate a name for this input
117 | #[must_use]
118 | fn generate_name(&self, idx: usize) -> String {
119 | format!("id_{idx}_{hash}", idx = idx, hash = self.hash())
120 | }
121 | }
122 |
123 | #[derive(Serialize, Deserialize, Clone, Debug, EnumIter, Copy, PartialEq)]
124 | pub enum ReceiverType {
125 | Activity,
126 | Service,
127 | BroadcastReceiver,
128 | }
129 |
130 | #[derive(Serialize, Deserialize, Clone, Debug)]
131 | pub struct ExtraInput {
132 | // The `key` of the extra input.
133 | pub key: String,
134 | // The type of the extra input (for example, s, z, i, f).
135 | pub value: ExtraType,
136 | }
137 |
138 | impl ExtraInput {
139 | /// The command line arguments for this extra input.
140 | pub fn command_args(&self, index: usize) -> Option {
141 | let arg_string = match &self.value {
142 | ExtraType::URI(uri_input) => Some(uri_input.identifier(index)),
143 | ExtraType::String(d_input) => Some(encode_hex(d_input.buffer.bytes())),
144 | ExtraType::Boolean(d_input) => {
145 | if d_input.buffer.bytes().get(0) == Some(&0) {
146 | Some("false".to_string())
147 | } else {
148 | Some("true".to_string())
149 | }
150 | }
151 | ExtraType::Int(d_input) => {
152 | Some(i32::from_le_bytes(d_input.buffer.bytes().try_into().ok()?).to_string())
153 | }
154 | ExtraType::Long(d_input) => {
155 | Some(i64::from_le_bytes(d_input.buffer.bytes().try_into().ok()?).to_string())
156 | }
157 | ExtraType::Float(d_input) => {
158 | let value_f32 = f32::from_le_bytes(d_input.buffer.bytes().try_into().ok()?);
159 | if value_f32.is_infinite() {
160 | Some(if value_f32.is_sign_positive() {
161 | "Infinity".to_string()
162 | } else {
163 | "-Infinity".to_string()
164 | })
165 | } else if value_f32.is_nan() {
166 | Some("NaN".to_string())
167 | } else {
168 | Some(value_f32.to_string())
169 | }
170 | }
171 | ExtraType::IntArray(d_input) | ExtraType::IntArrayList(d_input) => {
172 | let values: Vec = d_input
173 | .buffer
174 | .bytes()
175 | .chunks(4)
176 | .map(|chunk| {
177 | let mut bytes = [0u8; 4];
178 | bytes[..chunk.len()].copy_from_slice(chunk);
179 | i32::from_le_bytes(bytes)
180 | })
181 | .collect();
182 |
183 | let output = values
184 | .iter()
185 | .map(ToString::to_string)
186 | .collect::>()
187 | .join(",");
188 |
189 | Some(output).filter(|output| !output.is_empty())
190 | }
191 | ExtraType::LongArray(d_input) | ExtraType::LongArrayList(d_input) => {
192 | let values: Vec = d_input
193 | .buffer
194 | .bytes()
195 | .chunks(8)
196 | .map(|chunk| {
197 | let mut bytes = [0u8; 8];
198 | bytes[..chunk.len()].copy_from_slice(chunk);
199 | i64::from_le_bytes(bytes)
200 | })
201 | .collect();
202 |
203 | let output = values
204 | .iter()
205 | .map(ToString::to_string)
206 | .collect::>()
207 | .join(",");
208 |
209 | Some(output).filter(|output| !output.is_empty())
210 | }
211 | ExtraType::FloatArray(d_input) | ExtraType::FloatArrayList(d_input) => {
212 | let values: Vec = d_input
213 | .buffer
214 | .bytes()
215 | .chunks(4)
216 | .map(|chunk| {
217 | let mut bytes = [0u8; 4];
218 | bytes[..chunk.len()].copy_from_slice(chunk);
219 | f32::from_le_bytes(bytes)
220 | })
221 | .collect();
222 |
223 | let output = values
224 | .iter()
225 | .map(ToString::to_string)
226 | .collect::>()
227 | .join(",");
228 |
229 | Some(output).filter(|output| !output.is_empty())
230 | }
231 | ExtraType::StringArray(d_input) | ExtraType::StringArrayList(d_input) => {
232 | let result = d_input
233 | .buffer
234 | .bytes()
235 | .iter()
236 | .map(|byte| if *byte == 0 { b',' } else { *byte })
237 | .collect::>();
238 | Some(encode_hex(&result)).filter(|output| !output.is_empty())
239 | }
240 | _ => None,
241 | };
242 |
243 | arg_string.map(|v| format!(" --e{} '{}' $'{}'", self.value, self.key, v))
244 | }
245 | }
246 |
247 | #[derive(Serialize, Deserialize, Clone, Debug)]
248 | pub struct URIInput {
249 | // The `scheme` of the URI input (for example, content, file).
250 | pub scheme: URIScheme,
251 | // The suffix of the URI input.
252 | pub suffix: URISuffix,
253 | // The content of the URI input.
254 | pub content: BytesInput,
255 | }
256 |
257 | impl URIInput {
258 | pub fn identifier(&self, id: usize) -> String {
259 | match &self.scheme {
260 | URIScheme::Other => encode_hex(self.content.bytes()),
261 | _ => {
262 | let path = match &self.scheme {
263 | URIScheme::Content => {
264 | "org.gts3.jnifuzz.contentprovider.provider/external_files"
265 | }
266 | URIScheme::File => "/data/local/tmp",
267 | _ => unreachable!(),
268 | };
269 |
270 | format!(
271 | "{}://{}/extra_input_{}{}",
272 | self.scheme, path, id, self.suffix
273 | )
274 | }
275 | }
276 | }
277 | }
278 |
279 | #[derive(Serialize, Deserialize, Clone, Debug)]
280 | pub struct DirectInput {
281 | // The value of the primitive input.
282 | pub buffer: BytesInput,
283 | }
284 |
285 | // Enum for the different types of URI schemes.
286 | #[derive(Serialize, Deserialize, Clone, Debug, EnumIter)]
287 | pub enum URIScheme {
288 | Content,
289 | File,
290 | Other,
291 | }
292 |
293 | impl fmt::Display for URIScheme {
294 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
295 | match self {
296 | URIScheme::Content => write!(f, "content"),
297 | URIScheme::File => write!(f, "file"),
298 | URIScheme::Other => Ok(()),
299 | }
300 | }
301 | }
302 |
303 | // Enum for the different suffixes of URI inputs.
304 | #[derive(Serialize, Deserialize, Clone, Debug, EnumIter)]
305 | pub enum URISuffix {
306 | AAC,
307 | APK,
308 | GIF,
309 | HTML,
310 | JPG,
311 | MIDI,
312 | MP3,
313 | MP4,
314 | OGG,
315 | PDF,
316 | PNG,
317 | TXT,
318 | WAV,
319 | WMA,
320 | WMV,
321 | XML,
322 | }
323 |
324 | impl fmt::Display for URISuffix {
325 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
326 | match self {
327 | URISuffix::AAC => write!(f, ".aac"),
328 | URISuffix::APK => write!(f, ".apk"),
329 | URISuffix::GIF => write!(f, ".gif"),
330 | URISuffix::HTML => write!(f, ".html"),
331 | URISuffix::JPG => write!(f, ".jpg"),
332 | URISuffix::MIDI => write!(f, ".midi"),
333 | URISuffix::MP3 => write!(f, ".mp3"),
334 | URISuffix::MP4 => write!(f, ".mp4"),
335 | URISuffix::OGG => write!(f, ".ogg"),
336 | URISuffix::PDF => write!(f, ".pdf"),
337 | URISuffix::PNG => write!(f, ".png"),
338 | URISuffix::TXT => write!(f, ".txt"),
339 | URISuffix::WAV => write!(f, ".wav"),
340 | URISuffix::WMA => write!(f, ".wma"),
341 | URISuffix::WMV => write!(f, ".wmv"),
342 | URISuffix::XML => write!(f, ".xml"),
343 | }
344 | }
345 | }
346 |
347 | // Enum for the different types of extras.
348 | #[derive(Serialize, Deserialize, Clone, Debug)]
349 | pub enum ExtraType {
350 | String(DirectInput),
351 | Boolean(DirectInput),
352 | Int(DirectInput),
353 | Long(DirectInput),
354 | Float(DirectInput),
355 | URI(URIInput),
356 | ComponentName(DirectInput),
357 | IntArray(DirectInput),
358 | IntArrayList(DirectInput),
359 | LongArray(DirectInput),
360 | LongArrayList(DirectInput),
361 | FloatArray(DirectInput),
362 | FloatArrayList(DirectInput),
363 | StringArray(DirectInput),
364 | StringArrayList(DirectInput),
365 | }
366 |
367 | impl ExtraType {
368 | pub fn content_buffer(&mut self) -> &mut BytesInput {
369 | match self {
370 | ExtraType::URI(uri_input) => &mut uri_input.content,
371 | ExtraType::String(d_input) => &mut d_input.buffer,
372 | ExtraType::Boolean(d_input) => &mut d_input.buffer,
373 | ExtraType::Int(d_input) => &mut d_input.buffer,
374 | ExtraType::Long(d_input) => &mut d_input.buffer,
375 | ExtraType::Float(d_input) => &mut d_input.buffer,
376 | ExtraType::ComponentName(d_input) => &mut d_input.buffer,
377 | ExtraType::IntArray(d_input) => &mut d_input.buffer,
378 | ExtraType::IntArrayList(d_input) => &mut d_input.buffer,
379 | ExtraType::LongArray(d_input) => &mut d_input.buffer,
380 | ExtraType::LongArrayList(d_input) => &mut d_input.buffer,
381 | ExtraType::FloatArray(d_input) => &mut d_input.buffer,
382 | ExtraType::FloatArrayList(d_input) => &mut d_input.buffer,
383 | ExtraType::StringArray(d_input) => &mut d_input.buffer,
384 | ExtraType::StringArrayList(d_input) => &mut d_input.buffer,
385 | }
386 | }
387 | }
388 |
389 | impl fmt::Display for ExtraType {
390 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
391 | match self {
392 | ExtraType::String(_) => write!(f, "s"),
393 | ExtraType::Boolean(_) => write!(f, "z"),
394 | ExtraType::Int(_) => write!(f, "i"),
395 | ExtraType::Long(_) => write!(f, "l"),
396 | ExtraType::Float(_) => write!(f, "f"),
397 | ExtraType::URI(_) => write!(f, "u"),
398 | ExtraType::ComponentName(_) => write!(f, "cn"),
399 | ExtraType::IntArray(_) => write!(f, "ia"),
400 | ExtraType::IntArrayList(_) => write!(f, "ial"),
401 | ExtraType::LongArray(_) => write!(f, "la"),
402 | ExtraType::LongArrayList(_) => write!(f, "lal"),
403 | ExtraType::FloatArray(_) => write!(f, "fa"),
404 | ExtraType::FloatArrayList(_) => write!(f, "fal"),
405 | ExtraType::StringArray(_) => write!(f, "sa"),
406 | ExtraType::StringArrayList(_) => write!(f, "sal"),
407 | }
408 | }
409 | }
410 |
411 | // Enum for the following mime types:
412 | #[derive(Serialize, Deserialize, Clone, Debug, EnumIter, Copy)]
413 | pub enum MimeType {
414 | ApplicationPdf,
415 | ApplicationVndAndroidPackageArchive,
416 | AudioAac,
417 | AudioMidi,
418 | AudioMpeg,
419 | AudioMpeg4Generic,
420 | AudioOgg,
421 | AudioWav,
422 | AudioXMsWma,
423 | ImageGif,
424 | ImageJpeg,
425 | ImagePng,
426 | TextHtml,
427 | TextPlain,
428 | TextXml,
429 | VideoMp4,
430 | VideoXMsVideo,
431 | VideoXMsWmv,
432 | }
433 |
434 | impl fmt::Display for MimeType {
435 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
436 | match self {
437 | MimeType::ApplicationPdf => write!(f, "application/pdf"),
438 | MimeType::ApplicationVndAndroidPackageArchive => {
439 | write!(f, "application/vnd.android.package-archive")
440 | }
441 | MimeType::AudioAac => write!(f, "audio/aac"),
442 | MimeType::AudioMidi => write!(f, "audio/midi"),
443 | MimeType::AudioMpeg => write!(f, "audio/mpeg"),
444 | MimeType::AudioMpeg4Generic => write!(f, "audio/mpeg4-generic"),
445 | MimeType::AudioOgg => write!(f, "audio/ogg"),
446 | MimeType::AudioWav => write!(f, "audio/wav"),
447 | MimeType::AudioXMsWma => write!(f, "audio/x-ms-wma"),
448 | MimeType::ImageGif => write!(f, "image/gif"),
449 | MimeType::ImageJpeg => write!(f, "image/jpeg"),
450 | MimeType::ImagePng => write!(f, "image/png"),
451 | MimeType::TextHtml => write!(f, "text/html"),
452 | MimeType::TextPlain => write!(f, "text/plain"),
453 | MimeType::TextXml => write!(f, "text/xml"),
454 | MimeType::VideoMp4 => write!(f, "video/mp4"),
455 | MimeType::VideoXMsVideo => write!(f, "video/x-msvideo"),
456 | MimeType::VideoXMsWmv => write!(f, "video/x-ms-wmv"),
457 | }
458 | }
459 | }
460 |
--------------------------------------------------------------------------------
/src/intent_mutator.rs:
--------------------------------------------------------------------------------
1 | //! [Mutator]s for [IntentInput].
2 |
3 | use std::marker::PhantomData;
4 |
5 | use libafl::{
6 | prelude::{
7 | tuple_list, tuple_list_type, BytesInput, HasBytesVec, MutationResult, Mutator, Named, Rand,
8 | StdScheduledMutator,
9 | },
10 | state::{HasCorpus, HasMaxSize, HasNamedMetadata, HasRand},
11 | };
12 | use strum::IntoEnumIterator;
13 |
14 | use crate::{
15 | intent_generator::IntentTemplate,
16 | intent_input::{
17 | DirectInput, ExtraInput, ExtraType, IntentInput, MimeType, URIInput, URIScheme, URISuffix,
18 | },
19 | util::COMMON_EXTRA_KEYS,
20 | };
21 |
22 | /// Mutator that randomly modifies the flags attribute of the intent.
23 | pub struct IntentRandomFlagMutator
24 | where
25 | S: HasRand,
26 | {
27 | phantom: PhantomData,
28 | }
29 |
30 | impl IntentRandomFlagMutator
31 | where
32 | S: HasRand,
33 | {
34 | pub fn new() -> Self {
35 | Self {
36 | phantom: PhantomData,
37 | }
38 | }
39 | }
40 |
41 | impl Named for IntentRandomFlagMutator
42 | where
43 | S: HasRand,
44 | {
45 | fn name(&self) -> &str {
46 | "IntentRandomFlagMutator"
47 | }
48 | }
49 |
50 | impl Mutator for IntentRandomFlagMutator
51 | where
52 | S: HasRand,
53 | {
54 | fn mutate(
55 | &mut self,
56 | state: &mut S,
57 | input: &mut IntentInput,
58 | _stage_idx: i32,
59 | ) -> Result {
60 | let bit = 1 << state.rand_mut().choose(0..8);
61 | input.flags ^= bit;
62 | Ok(MutationResult::Mutated)
63 | }
64 | }
65 |
66 | /// Mutator that randomly modifies the data attribute of the intent.
67 | pub struct IntentRandomDataMutator
68 | where
69 | S: HasRand + HasCorpus + HasMaxSize,
70 | {
71 | backing_byte_mutator: StdScheduledMutator,
72 | }
73 |
74 | impl IntentRandomDataMutator
75 | where
76 | S: HasRand + HasCorpus + HasMaxSize,
77 | {
78 | pub fn new() -> Self {
79 | Self {
80 | backing_byte_mutator: StdScheduledMutator::new(base_byte_mutations()),
81 | }
82 | }
83 | }
84 |
85 | impl Named for IntentRandomDataMutator
86 | where
87 | S: HasRand + HasCorpus + HasMaxSize,
88 | {
89 | fn name(&self) -> &str {
90 | "IntentRandomDataMutator"
91 | }
92 | }
93 |
94 | impl Mutator for IntentRandomDataMutator
95 | where
96 | S: HasRand + HasCorpus + HasMaxSize,
97 | {
98 | fn mutate(
99 | &mut self,
100 | state: &mut S,
101 | input: &mut IntentInput,
102 | stage_idx: i32,
103 | ) -> Result {
104 | // Check if the data is already a byte input
105 | match &mut input.data {
106 | Some(uri_input) => match state.rand_mut().between(1, 3) {
107 | 1 => {
108 | // Mutate the scheme
109 | uri_input.scheme = state.rand_mut().choose(URIScheme::iter());
110 | }
111 | 2 => {
112 | // Mutate the suffix
113 | uri_input.suffix = state.rand_mut().choose(URISuffix::iter());
114 | }
115 | _ => {
116 | // Mutate the content
117 | return self.backing_byte_mutator.mutate(
118 | state,
119 | &mut uri_input.content,
120 | stage_idx,
121 | );
122 | }
123 | },
124 | None => {
125 | let mut uri_input = URIInput {
126 | scheme: state.rand_mut().choose(URIScheme::iter()),
127 | suffix: state.rand_mut().choose(URISuffix::iter()),
128 | content: BytesInput::new(Vec::new()),
129 | };
130 |
131 | let result =
132 | self.backing_byte_mutator
133 | .mutate(state, &mut uri_input.content, stage_idx);
134 |
135 | input.data.get_or_insert(uri_input);
136 |
137 | return result;
138 | }
139 | }
140 |
141 | Ok(MutationResult::Mutated)
142 | }
143 | }
144 |
145 | /// Mutator that modifies the type attribute of the intent.
146 | pub struct IntentRandomMimeTypeMutator
147 | where
148 | S: HasRand + HasCorpus + HasMaxSize,
149 | {
150 | phantom: PhantomData,
151 | }
152 |
153 | impl IntentRandomMimeTypeMutator
154 | where
155 | S: HasRand + HasCorpus + HasMaxSize,
156 | {
157 | pub fn new() -> Self {
158 | Self {
159 | phantom: PhantomData,
160 | }
161 | }
162 | }
163 |
164 | impl Named for IntentRandomMimeTypeMutator
165 | where
166 | S: HasRand + HasCorpus + HasMaxSize,
167 | {
168 | fn name(&self) -> &str {
169 | "IntentRandomTypeMutator"
170 | }
171 | }
172 |
173 | impl Mutator for IntentRandomMimeTypeMutator
174 | where
175 | S: HasRand + HasCorpus + HasMaxSize,
176 | {
177 | fn mutate(
178 | &mut self,
179 | state: &mut S,
180 | input: &mut IntentInput,
181 | _stage_idx: i32,
182 | ) -> Result {
183 | // Choose a random mimetype from the enum.
184 | input.mime_type = state.rand_mut().choose(MimeType::iter());
185 | Ok(MutationResult::Mutated)
186 | }
187 | }
188 |
189 | // Mutator that randomly modifies the key attribute of the extra.
190 | pub struct IntentRandomAddExtraMutator
191 | where
192 | S: HasRand + HasCorpus + HasMaxSize + HasNamedMetadata,
193 | {
194 | backing_byte_mutator: StdScheduledMutator,
195 | }
196 |
197 | impl Named for IntentRandomAddExtraMutator
198 | where
199 | S: HasRand + HasCorpus + HasMaxSize + HasNamedMetadata,
200 | {
201 | fn name(&self) -> &str {
202 | "IntentRandomAddExtraMutator"
203 | }
204 | }
205 |
206 | impl IntentRandomAddExtraMutator
207 | where
208 | S: HasRand + HasCorpus + HasMaxSize + HasNamedMetadata,
209 | {
210 | pub fn new() -> Self {
211 | Self {
212 | backing_byte_mutator: StdScheduledMutator::new(base_byte_mutations()),
213 | }
214 | }
215 | }
216 |
217 | impl Mutator for IntentRandomAddExtraMutator
218 | where
219 | S: HasRand + HasCorpus + HasMaxSize + HasNamedMetadata,
220 | {
221 | fn mutate(
222 | &mut self,
223 | state: &mut S,
224 | input: &mut IntentInput,
225 | stage_idx: i32,
226 | ) -> Result {
227 | if input.extras.len() >= 10 {
228 | return Ok(MutationResult::Skipped);
229 | }
230 |
231 | input.extras.push(generate_random_extra(state));
232 |
233 | let extra: &mut ExtraInput = &mut input.extras.last_mut().unwrap();
234 |
235 | // Mutate the content
236 | mutate_content(&mut self.backing_byte_mutator, state, extra, stage_idx)
237 | }
238 | }
239 |
240 | // Mutator that randomly modifies the key attribute of the extra.
241 | pub struct IntentRandomExtraKeyMutator
242 | where
243 | S: HasRand + HasCorpus + HasMaxSize + HasNamedMetadata,
244 | {
245 | phantom: PhantomData,
246 | }
247 |
248 | impl Named for IntentRandomExtraKeyMutator
249 | where
250 | S: HasRand + HasCorpus + HasMaxSize + HasNamedMetadata,
251 | {
252 | fn name(&self) -> &str {
253 | "IntentRandomExtraKeyMutator"
254 | }
255 | }
256 |
257 | impl IntentRandomExtraKeyMutator
258 | where
259 | S: HasRand + HasCorpus + HasMaxSize + HasNamedMetadata,
260 | {
261 | pub fn new() -> Self {
262 | Self {
263 | phantom: PhantomData,
264 | }
265 | }
266 | }
267 |
268 | impl Mutator for IntentRandomExtraKeyMutator
269 | where
270 | S: HasRand + HasCorpus + HasMaxSize + HasNamedMetadata,
271 | {
272 | fn mutate(
273 | &mut self,
274 | state: &mut S,
275 | input: &mut IntentInput,
276 | _stage_idx: i32,
277 | ) -> Result {
278 | let extra = match get_extra_to_mutate(state, input) {
279 | Ok(extra) => extra,
280 | Err(_) => return Ok(MutationResult::Skipped),
281 | };
282 |
283 | // Mutate the key
284 | let intent_template = state
285 | .named_metadata::("intent_template")
286 | .expect("Missing intent template")
287 | .clone();
288 |
289 | let extras_keys: Vec<&str> = intent_template
290 | .known_extras_keys
291 | .keys()
292 | .map(String::as_str)
293 | .chain(COMMON_EXTRA_KEYS.iter().map(|s| s.0))
294 | .collect();
295 |
296 | let extra_key = state.rand_mut().choose(extras_keys);
297 |
298 | extra.key = extra_key.to_owned();
299 |
300 | Ok(MutationResult::Mutated)
301 | }
302 | }
303 |
304 | // Mutator that randomly modifies the content attribute of the extra.
305 | pub struct IntentRandomExtraContentMutator
306 | where
307 | S: HasRand + HasCorpus + HasMaxSize + HasNamedMetadata,
308 | {
309 | backing_byte_mutator: StdScheduledMutator,
310 | }
311 |
312 | impl Named for IntentRandomExtraContentMutator
313 | where
314 | S: HasRand + HasCorpus + HasMaxSize + HasNamedMetadata,
315 | {
316 | fn name(&self) -> &str {
317 | "IntentRandomExtraContentMutator"
318 | }
319 | }
320 |
321 | impl IntentRandomExtraContentMutator
322 | where
323 | S: HasRand + HasCorpus + HasMaxSize + HasNamedMetadata,
324 | {
325 | pub fn new() -> Self {
326 | Self {
327 | backing_byte_mutator: StdScheduledMutator::new(base_byte_mutations()),
328 | }
329 | }
330 | }
331 |
332 | impl Mutator for IntentRandomExtraContentMutator
333 | where
334 | S: HasRand + HasCorpus + HasMaxSize + HasNamedMetadata,
335 | {
336 | fn mutate(
337 | &mut self,
338 | state: &mut S,
339 | input: &mut IntentInput,
340 | stage_idx: i32,
341 | ) -> Result {
342 | let extra = match get_extra_to_mutate(state, input) {
343 | Ok(extra) => extra,
344 | Err(_) => return Ok(MutationResult::Skipped),
345 | };
346 |
347 | // Mutate the content
348 | mutate_content(&mut self.backing_byte_mutator, state, extra, stage_idx)
349 | }
350 | }
351 |
352 | // Mutator that randomly modifies the scheme attribute of the extra.
353 | pub struct IntentRandomExtraSchemeMutator
354 | where
355 | S: HasRand + HasCorpus + HasMaxSize + HasNamedMetadata,
356 | {
357 | phantom: PhantomData,
358 | }
359 |
360 | impl Named for IntentRandomExtraSchemeMutator
361 | where
362 | S: HasRand + HasCorpus + HasMaxSize + HasNamedMetadata,
363 | {
364 | fn name(&self) -> &str {
365 | "IntentRandomExtraSchemeMutator"
366 | }
367 | }
368 |
369 | impl IntentRandomExtraSchemeMutator
370 | where
371 | S: HasRand + HasCorpus + HasMaxSize + HasNamedMetadata,
372 | {
373 | pub fn new() -> Self {
374 | Self {
375 | phantom: PhantomData,
376 | }
377 | }
378 | }
379 |
380 | impl Mutator for IntentRandomExtraSchemeMutator
381 | where
382 | S: HasRand + HasCorpus + HasMaxSize + HasNamedMetadata,
383 | {
384 | fn mutate(
385 | &mut self,
386 | state: &mut S,
387 | input: &mut IntentInput,
388 | _stage_idx: i32,
389 | ) -> Result {
390 | let extra = match get_extra_to_mutate(state, input) {
391 | Ok(extra) => extra,
392 | Err(_) => return Ok(MutationResult::Skipped),
393 | };
394 |
395 | // Mutate the scheme
396 | Ok(match &mut extra.value {
397 | ExtraType::URI(uri) => {
398 | uri.scheme = state.rand_mut().choose(URIScheme::iter());
399 | MutationResult::Mutated
400 | }
401 | _ => MutationResult::Skipped,
402 | })
403 | }
404 | }
405 |
406 | // Mutator that randomly modifies the suffix attribute of the extra.
407 | pub struct IntentRandomExtraSuffixMutator
408 | where
409 | S: HasRand + HasCorpus + HasMaxSize + HasNamedMetadata,
410 | {
411 | phantom: PhantomData,
412 | }
413 |
414 | impl Named for IntentRandomExtraSuffixMutator
415 | where
416 | S: HasRand + HasCorpus + HasMaxSize + HasNamedMetadata,
417 | {
418 | fn name(&self) -> &str {
419 | "IntentRandomExtraSuffixMutator"
420 | }
421 | }
422 |
423 | impl IntentRandomExtraSuffixMutator
424 | where
425 | S: HasRand + HasCorpus + HasMaxSize + HasNamedMetadata,
426 | {
427 | pub fn new() -> Self {
428 | Self {
429 | phantom: PhantomData,
430 | }
431 | }
432 | }
433 |
434 | impl Mutator for IntentRandomExtraSuffixMutator
435 | where
436 | S: HasRand + HasCorpus + HasMaxSize + HasNamedMetadata,
437 | {
438 | fn mutate(
439 | &mut self,
440 | state: &mut S,
441 | input: &mut IntentInput,
442 | _stage_idx: i32,
443 | ) -> Result {
444 | let extra = match get_extra_to_mutate(state, input) {
445 | Ok(extra) => extra,
446 | Err(_) => return Ok(MutationResult::Skipped),
447 | };
448 |
449 | // Mutate the suffix
450 | Ok(match &mut extra.value {
451 | ExtraType::URI(uri) => {
452 | uri.suffix = state.rand_mut().choose(URISuffix::iter());
453 | MutationResult::Mutated
454 | }
455 | _ => MutationResult::Skipped,
456 | })
457 | }
458 | }
459 |
460 | // -----------------------------------------
461 |
462 | /// Helper function to get an ExtraInput to mutate. Creates a new one if there
463 | /// are no extras yet.
464 | fn get_extra_to_mutate<'a, S>(
465 | state: &mut S,
466 | input: &'a mut IntentInput,
467 | ) -> Result<&'a mut ExtraInput, libafl::Error>
468 | where
469 | S: HasRand + HasCorpus + HasMaxSize + HasNamedMetadata,
470 | {
471 | if input.extras.is_empty() {
472 | // Add a new extra.
473 | return Err(libafl::Error::unknown("No extras to mutate"));
474 | }
475 |
476 | // Mutate one extra value.
477 | let index = state.rand_mut().between(0, (input.extras.len() - 1) as u64) as usize;
478 |
479 | Ok(input.extras.get_mut(index).unwrap())
480 | }
481 |
482 | /// Helper function to get a random ExtraInput.
483 | fn generate_random_extra(state: &mut S) -> ExtraInput
484 | where
485 | S: HasRand + HasCorpus + HasMaxSize + HasNamedMetadata,
486 | {
487 | // Choose an extra from the template in the state.
488 | let intent_template = state
489 | .named_metadata::("intent_template")
490 | .expect("Missing intent template")
491 | .clone();
492 |
493 | // Get a random key and its type from the template.
494 | let combined_iterator = intent_template
495 | .known_extras_keys
496 | .iter()
497 | .map(|(key, extra_type)| (key.as_str(), extra_type.as_str()))
498 | .chain(COMMON_EXTRA_KEYS)
499 | .collect::>();
500 |
501 | let (key, extra_type) = state.rand_mut().choose(combined_iterator);
502 |
503 | //println!("Generating extra with key {} and type {}", key, extra_type);
504 |
505 | // Create an extra with the key and a random value.
506 | let extra = match extra_type {
507 | "Boolean" => ExtraType::Boolean(DirectInput {
508 | buffer: BytesInput::new(Vec::new()),
509 | }),
510 | "Float" => ExtraType::Float(DirectInput {
511 | buffer: BytesInput::new(Vec::new()),
512 | }),
513 | "Int" => ExtraType::Int(DirectInput {
514 | buffer: BytesInput::new(Vec::new()),
515 | }),
516 | "Long" => ExtraType::Long(DirectInput {
517 | buffer: BytesInput::new(Vec::new()),
518 | }),
519 | "String" => ExtraType::String(DirectInput {
520 | buffer: BytesInput::new(Vec::new()),
521 | }),
522 | "URI" => ExtraType::URI(URIInput {
523 | scheme: state.rand_mut().choose(URIScheme::iter()),
524 | suffix: state.rand_mut().choose(URISuffix::iter()),
525 | content: BytesInput::new(Vec::new()),
526 | }),
527 | "ComponentName" => ExtraType::ComponentName(DirectInput {
528 | buffer: BytesInput::new(Vec::new()),
529 | }),
530 | "IntArray" => ExtraType::IntArray(DirectInput {
531 | buffer: BytesInput::new(Vec::new()),
532 | }),
533 | "IntArrayList" => ExtraType::IntArrayList(DirectInput {
534 | buffer: BytesInput::new(Vec::new()),
535 | }),
536 | "LongArray" => ExtraType::LongArray(DirectInput {
537 | buffer: BytesInput::new(Vec::new()),
538 | }),
539 | "LongArrayList" => ExtraType::LongArrayList(DirectInput {
540 | buffer: BytesInput::new(Vec::new()),
541 | }),
542 | "FloatArray" => ExtraType::FloatArray(DirectInput {
543 | buffer: BytesInput::new(Vec::new()),
544 | }),
545 | "FloatArrayList" => ExtraType::FloatArrayList(DirectInput {
546 | buffer: BytesInput::new(Vec::new()),
547 | }),
548 | "StringArray" => ExtraType::StringArray(DirectInput {
549 | buffer: BytesInput::new(Vec::new()),
550 | }),
551 | "StringArrayList" => ExtraType::StringArrayList(DirectInput {
552 | buffer: BytesInput::new(Vec::new()),
553 | }),
554 | _ => ExtraType::Boolean(DirectInput {
555 | // TODO: Implement me
556 | buffer: BytesInput::new(Vec::new()),
557 | }),
558 | };
559 |
560 | ExtraInput {
561 | key: key.to_owned(),
562 | value: extra,
563 | }
564 | }
565 |
566 | fn mutate_content(
567 | mutator: &mut StdScheduledMutator,
568 | state: &mut S,
569 | extra: &mut ExtraInput,
570 | stage_idx: i32,
571 | ) -> Result
572 | where
573 | S: HasRand + HasCorpus + HasMaxSize + HasNamedMetadata,
574 | {
575 | let result = mutator.mutate(state, &mut extra.value.content_buffer(), stage_idx);
576 |
577 | // If the mutation was successful, resize the extra value to the correct size.
578 | if let Ok(MutationResult::Mutated) = result {
579 | match &mut extra.value {
580 | ExtraType::Boolean(value) => value.buffer.bytes_mut().resize(1, 0),
581 | ExtraType::Int(value) | ExtraType::Float(value) => {
582 | value.buffer.bytes_mut().resize(4, 0)
583 | }
584 | ExtraType::Long(value) => value.buffer.bytes_mut().resize(8, 0),
585 | _ => {}
586 | }
587 | }
588 |
589 | result
590 | }
591 |
592 | /// This is basically a copy of
593 | /// but without the crossover mutations which require the corpus to be a
594 | /// BytesInput.
595 | type BaseByteMutationsType = tuple_list_type!(
596 | libafl::prelude::BitFlipMutator,
597 | libafl::prelude::ByteIncMutator,
598 | libafl::prelude::ByteDecMutator,
599 | libafl::prelude::ByteNegMutator,
600 | libafl::prelude::ByteRandMutator,
601 | libafl::prelude::ByteAddMutator,
602 | libafl::prelude::WordAddMutator,
603 | libafl::prelude::DwordAddMutator,
604 | libafl::prelude::QwordAddMutator,
605 | libafl::prelude::ByteInterestingMutator,
606 | libafl::prelude::WordInterestingMutator,
607 | libafl::prelude::DwordInterestingMutator,
608 | libafl::prelude::BytesDeleteMutator,
609 | libafl::prelude::BytesDeleteMutator,
610 | libafl::prelude::BytesDeleteMutator,
611 | libafl::prelude::BytesDeleteMutator,
612 | libafl::prelude::BytesExpandMutator,
613 | libafl::prelude::BytesInsertMutator,
614 | libafl::prelude::BytesRandInsertMutator,
615 | libafl::prelude::BytesSetMutator,
616 | libafl::prelude::BytesRandSetMutator,
617 | libafl::prelude::BytesCopyMutator,
618 | libafl::prelude::BytesInsertCopyMutator,
619 | libafl::prelude::BytesSwapMutator,
620 | );
621 |
622 | fn base_byte_mutations() -> BaseByteMutationsType {
623 | tuple_list!(
624 | libafl::prelude::BitFlipMutator::new(),
625 | libafl::prelude::ByteIncMutator::new(),
626 | libafl::prelude::ByteDecMutator::new(),
627 | libafl::prelude::ByteNegMutator::new(),
628 | libafl::prelude::ByteRandMutator::new(),
629 | libafl::prelude::ByteAddMutator::new(),
630 | libafl::prelude::WordAddMutator::new(),
631 | libafl::prelude::DwordAddMutator::new(),
632 | libafl::prelude::QwordAddMutator::new(),
633 | libafl::prelude::ByteInterestingMutator::new(),
634 | libafl::prelude::WordInterestingMutator::new(),
635 | libafl::prelude::DwordInterestingMutator::new(),
636 | libafl::prelude::BytesDeleteMutator::new(),
637 | libafl::prelude::BytesDeleteMutator::new(),
638 | libafl::prelude::BytesDeleteMutator::new(),
639 | libafl::prelude::BytesDeleteMutator::new(),
640 | libafl::prelude::BytesExpandMutator::new(),
641 | libafl::prelude::BytesInsertMutator::new(),
642 | libafl::prelude::BytesRandInsertMutator::new(),
643 | libafl::prelude::BytesSetMutator::new(),
644 | libafl::prelude::BytesRandSetMutator::new(),
645 | libafl::prelude::BytesCopyMutator::new(),
646 | libafl::prelude::BytesInsertCopyMutator::new(),
647 | libafl::prelude::BytesSwapMutator::new(),
648 | )
649 | }
650 |
--------------------------------------------------------------------------------
/src/main.rs:
--------------------------------------------------------------------------------
1 | mod adb_device;
2 | mod adb_executor;
3 | mod intent_generator;
4 | mod intent_input;
5 | mod intent_mutator;
6 | mod socket_coverage_observer;
7 | mod util;
8 |
9 | use adb_device::AdbDevice;
10 | use clap::Parser;
11 | use intent_generator::IntentGenerator;
12 | use intent_input::IntentInput;
13 | use intent_mutator::{
14 | IntentRandomAddExtraMutator, IntentRandomDataMutator, IntentRandomExtraContentMutator,
15 | IntentRandomExtraKeyMutator, IntentRandomExtraSchemeMutator, IntentRandomExtraSuffixMutator,
16 | IntentRandomFlagMutator, IntentRandomMimeTypeMutator,
17 | };
18 | use socket_coverage_observer::SocketCoverageObserver;
19 |
20 | use std::{env, path::PathBuf};
21 |
22 | use libafl::{
23 | prelude::{
24 | tuple_list, AflMapFeedback, CachedOnDiskCorpus, ConstFeedback, CrashFeedback,
25 | InMemoryCorpus, OnDiskCorpus, SimpleEventManager, SimpleMonitor, StdRand,
26 | StdScheduledMutator, OnDiskTOMLMonitor,
27 | },
28 | schedulers::QueueScheduler,
29 | stages::StdMutationalStage,
30 | state::StdState,
31 | Fuzzer, StdFuzzer,
32 | };
33 |
34 | /// Executes through adb on a device or emulator receiving coverage feedback
35 | /// through a socket.
36 | #[derive(Parser, Debug)]
37 | #[command(version, about)]
38 | struct CommandLineArgs {
39 | /// The address of the coverage agent socket
40 | #[arg(short, long, default_value = "localhost:6249")]
41 | coverage_socket_address: String,
42 |
43 | /// The adb command used to send intents and control the device, can also
44 | /// be set with the `ADB_COMMAND` environment variable
45 | #[arg(short, long, default_value = "adb")]
46 | adb_command: String,
47 |
48 | /// The config file or directory from where to read the intent information
49 | #[arg(short, long, default_value = "intent_template.json")]
50 | intent_config: String,
51 |
52 | /// Re-run corpus instead of fuzzing
53 | #[arg(short, long, default_value = "false")]
54 | run_corpus: bool,
55 |
56 | /// Trace JNI calls instead of Java coverage
57 | #[arg(short, long, default_value = "false")]
58 | trace_native: bool,
59 |
60 | /// Switch to disable usage of coverage feedback
61 | #[arg(long, default_value = "false")]
62 | no_coverage: bool,
63 |
64 | /// The directory to store the corpus in
65 | #[arg(long, default_value = "corpus")]
66 | corpus_dir: PathBuf,
67 |
68 | /// The directory to store the crashes in
69 | #[arg(long, default_value = "crashes")]
70 | crashes_dir: PathBuf,
71 |
72 | /// The directory to store the traces in
73 | #[arg(long, default_value = "traces")]
74 | traces_dir: PathBuf,
75 |
76 | /// The file to store the fuzzer stats in
77 | #[arg(long, default_value = "fuzzer_stats.toml")]
78 | stats_file: PathBuf,
79 |
80 | /// The file to store the overall edge count in
81 | #[arg(long, default_value = "overall_coverage.txt")]
82 | overall_coverage_file: PathBuf,
83 | }
84 |
85 | fn main() {
86 | let mut args = CommandLineArgs::parse();
87 |
88 | // Set ADB_COMMAND from environment if present.
89 | if let Ok(command) = env::var("ADB_COMMAND") {
90 | args.adb_command = command;
91 | }
92 |
93 | // Generator of initial intents.
94 | let generator = IntentGenerator::new(&args.intent_config);
95 | let app_name = generator.package_name();
96 |
97 | // Check if the receiver type is supported
98 | if !generator.is_supported() {
99 | println!("Receiver type not supported");
100 | return;
101 | }
102 |
103 | // Adb device to send intents to.
104 | let adb_device = AdbDevice::new(&args.adb_command);
105 |
106 | adb_device.grant_uri_permissions(&app_name);
107 | adb_device.set_debug_app(&app_name);
108 |
109 | let enable_synchronization = generator.enable_synchronization();
110 |
111 | if args.run_corpus {
112 | // Create the ".hook_native" file to enable JNI tracing.
113 | if args.trace_native {
114 | adb_device.enable_native_hooking(&app_name);
115 | } else {
116 | adb_device.disable_native_hooking(&app_name);
117 | }
118 | adb_device.restart_app(&app_name);
119 |
120 | // Observer to get coverage feedback from the device.
121 | let observer = socket_coverage_observer::create_coverage_map_observer(
122 | adb_device.clone(),
123 | app_name.clone(),
124 | &args.coverage_socket_address,
125 | true,
126 | enable_synchronization,
127 | !args.no_coverage,
128 | &args.overall_coverage_file,
129 | );
130 |
131 | re_run(observer, adb_device.clone(), args.corpus_dir);
132 |
133 | // Stop app to disable JNI tracing.
134 | adb_device.stop_app(&app_name).expect("Failed to stop app");
135 |
136 | if args.trace_native {
137 | // Pull the trace files from the device.
138 | adb_device
139 | .pull_native_trace_files(&app_name, &args.traces_dir)
140 | .expect("Failed to pull trace files");
141 | }
142 | } else {
143 | // Fuzzing with native hooking is not supported.
144 | if args.trace_native {
145 | println!("Native hooking is not supported for fuzzing. Please use the --run-corpus option.");
146 | return;
147 | }
148 |
149 | // Start the app.
150 | adb_device.disable_native_hooking(&app_name);
151 | adb_device.restart_app(&app_name);
152 |
153 | // Observer to get coverage feedback from the device.
154 | let observer = socket_coverage_observer::create_coverage_map_observer(
155 | adb_device.clone(),
156 | app_name.clone(),
157 | &args.coverage_socket_address,
158 | false,
159 | enable_synchronization,
160 | !args.no_coverage,
161 | &args.overall_coverage_file,
162 | );
163 |
164 | fuzz(observer, adb_device, args, generator);
165 | }
166 | }
167 |
168 | fn re_run(observer: SocketCoverageObserver, adb_device: AdbDevice, corpus_dir: PathBuf) {
169 | let mut feedback = ConstFeedback::new(true);
170 | let mut objective = ConstFeedback::new(false);
171 | // The Monitor trait defines how the fuzzer stats are displayed to the user
172 | let mon = SimpleMonitor::new(|s| println!("{s}"));
173 | // The event manager handles the various events generated during the fuzzing loop
174 | // such as the notification of the addition of a new item to the corpus
175 | let mut mgr = SimpleEventManager::new(mon);
176 |
177 | let mut state = StdState::new(
178 | // RNG
179 | StdRand::with_seed(0),
180 | // The corpus is kept in memory for performance
181 | InMemoryCorpus::::new(),
182 | // Do not store solutions
183 | InMemoryCorpus::::new(),
184 | // Constant feedbacks
185 | &mut feedback,
186 | // Same for objective feedbacks
187 | &mut objective,
188 | )
189 | .unwrap();
190 |
191 | let scheduler = QueueScheduler::new();
192 |
193 | let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective);
194 |
195 | let mut executor = adb_executor::AdbExecutor::new(adb_device, tuple_list!(observer));
196 |
197 | state
198 | .load_initial_inputs_forced(
199 | &mut fuzzer,
200 | &mut executor,
201 | &mut mgr,
202 | &[PathBuf::from(corpus_dir)],
203 | )
204 | .expect("Failed to load the corpus");
205 | }
206 |
207 | fn fuzz(
208 | observer: SocketCoverageObserver,
209 | adb_device: AdbDevice,
210 | args: CommandLineArgs,
211 | mut generator: IntentGenerator,
212 | ) {
213 | let mut feedback = AflMapFeedback::new(&observer);
214 | // The Monitor trait defines how the fuzzer stats are displayed to the user
215 | let simple_mon = SimpleMonitor::new(|s| println!("{s}"));
216 |
217 | let mon = OnDiskTOMLMonitor::new(
218 | args.stats_file,
219 | simple_mon,
220 | );
221 |
222 | // The event manager handles the various events generated during the fuzzing loop
223 | // such as the notification of the addition of a new item to the corpus
224 | let mut mgr = SimpleEventManager::new(mon);
225 |
226 | // A feedback to choose if an input is a solution or not
227 | let mut objective = CrashFeedback::new();
228 |
229 | // create a State from scratch
230 | let mut state = StdState::new(
231 | // RNG
232 | StdRand::with_seed(0),
233 | // Corpus that will be evolved.
234 | CachedOnDiskCorpus::::new(PathBuf::from(args.corpus_dir), 128).unwrap(),
235 | // Corpus in which we store solutions (crashes in this example),
236 | // on disk so the user can get them after stopping the fuzzer
237 | OnDiskCorpus::::new(PathBuf::from(args.crashes_dir)).unwrap(),
238 | // States of the feedbacks.
239 | // The feedbacks can report the data that should persist in the State.
240 | &mut feedback,
241 | // Same for objective feedbacks
242 | &mut objective,
243 | )
244 | .unwrap();
245 |
246 | // A queue policy to get testcases from the corpus
247 | let scheduler = QueueScheduler::new();
248 |
249 | // A fuzzer with feedbacks and a corpus scheduler
250 | let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective);
251 |
252 | let mut executor = adb_executor::AdbExecutor::new(adb_device, tuple_list!(observer));
253 |
254 | let number_of_intents = generator.number_of_intents();
255 |
256 | // Generate initial inputs
257 | state
258 | .generate_initial_inputs_forced(
259 | &mut fuzzer,
260 | &mut executor,
261 | &mut generator,
262 | &mut mgr,
263 | number_of_intents,
264 | )
265 | .expect("Failed to generate the initial corpus");
266 |
267 | let mutator = StdScheduledMutator::new(tuple_list!(
268 | IntentRandomDataMutator::new(),
269 | IntentRandomFlagMutator::new(),
270 | IntentRandomMimeTypeMutator::new(),
271 | IntentRandomAddExtraMutator::new(),
272 | IntentRandomExtraKeyMutator::new(),
273 | IntentRandomExtraContentMutator::new(),
274 | IntentRandomExtraSchemeMutator::new(),
275 | IntentRandomExtraSuffixMutator::new()
276 | ));
277 | let mut stages = tuple_list!(StdMutationalStage::new(mutator));
278 |
279 | fuzzer
280 | .fuzz_loop(&mut stages, &mut executor, &mut state, &mut mgr)
281 | .expect("Error in the fuzzing loop");
282 | }
283 |
--------------------------------------------------------------------------------
/src/socket_coverage_observer.rs:
--------------------------------------------------------------------------------
1 | //! Handles getting the coverage map from CoverageAgent over a socket.
2 |
3 | use std::{
4 | io::{BufReader, Read, Write},
5 | net::TcpStream, time::Duration, path::PathBuf,
6 | };
7 |
8 | use libafl::prelude::{
9 | AsIter, AsSlice, AsMutSlice, ConstMapObserver, HasLen, HitcountsMapObserver, MapObserver, Named,
10 | Observer, UsesInput,
11 | };
12 | use serde::{Deserialize, Serialize};
13 |
14 | use crate::{adb_device::AdbDevice, intent_input::IntentInput};
15 |
16 | const COVERAGE_MAP_SIZE: usize = 1024 * 1024;
17 |
18 | pub fn create_coverage_map_observer<'a>(
19 | adb_device: AdbDevice,
20 | app_name: String,
21 | address: &str,
22 | trace_native: bool,
23 | enable_synchronization: bool,
24 | use_coverage: bool,
25 | overall_coverage_file: &PathBuf,
26 | ) -> SocketCoverageObserver<'a> {
27 | return SocketCoverageObserver::new(
28 | adb_device,
29 | app_name,
30 | address,
31 | trace_native,
32 | enable_synchronization,
33 | use_coverage,
34 | overall_coverage_file,
35 | );
36 | }
37 |
38 | #[derive(Debug, Serialize, Deserialize)]
39 | pub struct SocketCoverageObserver<'a> {
40 | adb_device: AdbDevice,
41 | app_name: String,
42 | address: String,
43 | trace_native: bool,
44 | enable_synchronization: bool,
45 | use_coverage: bool,
46 |
47 | #[serde(skip, default = "default_stream")]
48 | stream: TcpStream,
49 | #[serde(skip, default = "default_reader")]
50 | reader: BufReader,
51 |
52 | base_observer: HitcountsMapObserver>,
53 | // array to keep track of which edges have been covered
54 | overall_coverage: ConstMapObserver<'a, u8, COVERAGE_MAP_SIZE>,
55 |
56 | overall_coverage_file: PathBuf,
57 | // Save the start time
58 | start_time: std::time::SystemTime,
59 | last_overall_coverage: u64,
60 | }
61 |
62 | impl<'a> SocketCoverageObserver<'a> {
63 | fn new(
64 | adb_device: AdbDevice,
65 | app_name: String,
66 | address: &str,
67 | trace_native: bool,
68 | enable_synchronization: bool,
69 | use_coverage: bool,
70 | overall_coverage_file: &PathBuf,
71 | ) -> Self {
72 | let mut stream = TcpStream::connect(address).expect("Failed to connect to socket");
73 | stream.set_read_timeout(Some(Duration::from_secs(10))).expect("Failed to set read timeout");
74 | let reader = BufReader::new(stream.try_clone().expect("Failed to clone tcp stream"));
75 |
76 | stream
77 | .set_nodelay(true)
78 | .expect("Failed to set nodelay on socket");
79 |
80 | // Set up the socket for synchronization if requested.
81 | stream
82 | .write(if enable_synchronization { b"ss" } else { b"se" })
83 | .expect("Failed to write to socket");
84 |
85 | // Delete coverage file if it exists
86 | if overall_coverage_file.exists() {
87 | std::fs::remove_file(overall_coverage_file).unwrap();
88 | }
89 | // Write first entry to coverage file
90 | let mut file = std::fs::File::create(overall_coverage_file).unwrap();
91 | file.write_all(b"0: 0\n").unwrap();
92 |
93 | Self {
94 | adb_device,
95 | app_name,
96 | address: address.to_owned(),
97 | trace_native,
98 | enable_synchronization,
99 | use_coverage,
100 | stream,
101 | reader,
102 | base_observer: HitcountsMapObserver::new(ConstMapObserver::owned(
103 | "edges_from_socket",
104 | vec![0; COVERAGE_MAP_SIZE],
105 | )),
106 | overall_coverage: ConstMapObserver::owned(
107 | "overall_edges",
108 | vec![0; COVERAGE_MAP_SIZE],
109 | ),
110 | overall_coverage_file: overall_coverage_file.to_owned(),
111 | start_time: std::time::SystemTime::now(),
112 | last_overall_coverage: 0,
113 | }
114 | }
115 |
116 | fn init(&mut self) {
117 | self.stream =
118 | TcpStream::connect(self.address.clone()).expect("Failed to connect to socket");
119 | self.stream.set_read_timeout(Some(Duration::from_secs(10))).expect("Failed to set read timeout");
120 | self.reader = BufReader::new(self.stream.try_clone().expect("Failed to clone tcp stream"));
121 |
122 | self.stream
123 | .set_nodelay(true)
124 | .expect("Failed to set nodelay on socket");
125 |
126 | // Set up the socket for synchronization if requested.
127 | self.stream
128 | .write(if self.enable_synchronization {
129 | b"ss"
130 | } else {
131 | b"se"
132 | })
133 | .expect("Failed to write to socket");
134 | }
135 |
136 | fn reset_coverage(&mut self, hash: String) -> Result<(), libafl::Error> {
137 | let mut buffer = [0; 1];
138 |
139 | if self.trace_native {
140 | // Write "ts", the filename, and a newline to the socket.
141 | // The filename is "id_.txt"
142 | self.stream.write(b"ts")?;
143 | self.stream
144 | .write(format!("trace_{}.txt", hash).as_bytes())?;
145 | self.stream.write(b"\n")?;
146 | }
147 |
148 | self.stream.write(b"r")?;
149 | self.reader.read(&mut buffer)?;
150 | // Check buffer contains b'd'
151 | if buffer[0] != b'd' {
152 | return Err(libafl::Error::unknown(format!(
153 | "Failed to reset coverage map (got {:?})",
154 | buffer
155 | )));
156 | }
157 | Ok(())
158 | }
159 |
160 | pub fn save_overall_edge_count(&self) {
161 | // Number of bytes not 0 in the overall coverage.
162 | let overall_coverage = self.overall_coverage.as_slice().iter().filter(|&b| *b != 0).count();
163 |
164 | // Do nothing if the overall coverage hasn't changed.
165 | if overall_coverage <= self.last_overall_coverage as usize {
166 | return;
167 | }
168 |
169 | // Create the directory if it doesn't exist
170 | let mut dir = self.overall_coverage_file.clone();
171 | dir.pop();
172 | std::fs::create_dir_all(&dir).unwrap();
173 |
174 | // Get the time since the start of the program
175 | let elapsed = self.start_time.elapsed().unwrap();
176 |
177 | // Append the overall coverage to the file
178 | let mut file = std::fs::OpenOptions::new()
179 | .create(true)
180 | .append(true)
181 | .open(&self.overall_coverage_file)
182 | .unwrap();
183 | file.write_all(format!("{}: {}\n", elapsed.as_secs(), overall_coverage).as_bytes())
184 | .unwrap();
185 | }
186 | }
187 |
188 | impl Observer for SocketCoverageObserver<'_>
189 | where
190 | S: UsesInput,
191 | {
192 | #[inline]
193 | fn pre_exec(
194 | &mut self,
195 | state: &mut S,
196 | input: &::Input,
197 | ) -> Result<(), libafl::Error> {
198 | for i in 0..5 {
199 | if let Err(err) = self.reset_coverage(input.hash()) {
200 | println!(
201 | "Failed to write reset message to socket. Restarting app. Error: {:?}",
202 | err
203 | );
204 |
205 | if self.trace_native {
206 | self.adb_device.report_native_crash(&self.app_name);
207 | }
208 |
209 | if i > 1 {
210 | self.adb_device.restart_device();
211 | }
212 |
213 | self.adb_device.restart_app(&self.app_name);
214 |
215 | std::thread::sleep(std::time::Duration::from_secs(1 + i));
216 |
217 | self.init();
218 |
219 | std::thread::sleep(std::time::Duration::from_secs(1 + i));
220 | } else {
221 | // Reset the local coverage map.
222 | return self.base_observer.pre_exec(state, input);
223 | }
224 | }
225 |
226 | Err(libafl::Error::unknown(
227 | "Failed to reset coverage map (after restarting)",
228 | ))
229 | }
230 |
231 | #[inline]
232 | fn post_exec(
233 | &mut self,
234 | state: &mut S,
235 | input: &::Input,
236 | exit_kind: &libafl::prelude::ExitKind,
237 | ) -> Result<(), libafl::Error> {
238 | // Retrieve the coverage from the socket into the observer.
239 | self.stream
240 | .write(b"d")
241 | .expect("Failed to write send-coverage message to socket");
242 |
243 | let mut buffer = vec![0; COVERAGE_MAP_SIZE];
244 | if let Err(_err) = self.reader.read_exact(&mut buffer) {
245 | println!("Failed to read entire coverage from socket.");
246 | return Ok(());
247 | }
248 |
249 | if self.use_coverage {
250 | let observer_buffer = self.base_observer.as_mut_slice();
251 | // Copy into the observer buffer
252 | observer_buffer.copy_from_slice(&buffer);
253 | }
254 |
255 | // Update the overall coverage.
256 | let overall_buffer = self.overall_coverage.as_mut_slice();
257 | for (i, &b) in buffer.iter().enumerate() {
258 | if b != 0 {
259 | overall_buffer[i] = b;
260 | }
261 | }
262 |
263 | // Save the overall edge count to a file
264 | self.save_overall_edge_count();
265 |
266 | self.base_observer.post_exec(state, input, exit_kind)
267 | }
268 | }
269 |
270 | impl Named for SocketCoverageObserver<'_> {
271 | fn name(&self) -> &str {
272 | "SocketCoverageObserver"
273 | }
274 | }
275 |
276 | impl HasLen for SocketCoverageObserver<'_> {
277 | fn len(&self) -> usize {
278 | self.base_observer.len()
279 | }
280 | }
281 |
282 | impl<'it> AsIter<'it> for SocketCoverageObserver<'_> {
283 | type Item = u8;
284 | type IntoIter = as AsIter<'it>>::IntoIter;
285 |
286 | fn as_iter(&'it self) -> Self::IntoIter {
287 | self.base_observer.as_iter()
288 | }
289 | }
290 |
291 | impl MapObserver for SocketCoverageObserver<'_> {
292 | type Entry = u8;
293 |
294 | #[inline]
295 | fn get(&self, idx: usize) -> &Self::Entry {
296 | self.base_observer.get(idx)
297 | }
298 |
299 | #[inline]
300 | fn get_mut(&mut self, idx: usize) -> &mut Self::Entry {
301 | self.base_observer.get_mut(idx)
302 | }
303 |
304 | #[inline]
305 | fn usable_count(&self) -> usize {
306 | self.base_observer.usable_count()
307 | }
308 |
309 | fn count_bytes(&self) -> u64 {
310 | self.base_observer.count_bytes()
311 | }
312 |
313 | fn hash(&self) -> u64 {
314 | self.base_observer.hash()
315 | }
316 |
317 | #[inline]
318 | fn initial(&self) -> Self::Entry {
319 | self.base_observer.initial()
320 | }
321 |
322 | #[inline]
323 | fn reset_map(&mut self) -> Result<(), libafl::Error> {
324 | self.base_observer.reset_map()
325 | }
326 |
327 | fn to_vec(&self) -> Vec {
328 | self.base_observer.to_vec()
329 | }
330 |
331 | fn how_many_set(&self, indexes: &[usize]) -> usize {
332 | self.base_observer.how_many_set(indexes)
333 | }
334 | }
335 |
336 | // For some reason MapObserver requires the struct to implement Serialize/Deserialize.
337 | //
338 | // As far as I can tell it's not really used but since TcpStream and BufReader
339 | // can't be serialized we need these two methods to make serde happy.
340 | //
341 | // Panic if they ever get called.
342 | fn default_stream() -> TcpStream {
343 | panic!("Deserialize (default_stream) called on SocketCoverageObserver")
344 | }
345 | fn default_reader() -> BufReader {
346 | panic!("Deserialize (default_reader) called on SocketCoverageObserver")
347 | }
348 |
--------------------------------------------------------------------------------
/src/util.rs:
--------------------------------------------------------------------------------
1 | /// Encodes bytes into a hexstring like \x41\x42\x43
2 | pub fn encode_hex(bytes: &[u8]) -> String {
3 | bytes.iter().map(|b| format!("\\x{:02x}", b)).collect()
4 | }
5 |
6 | /// Array that contains common extra keys and types.
7 | pub const COMMON_EXTRA_KEYS: [(&str, &str); 14] = [
8 | ("android.intent.extra.CC", "StringArray"),
9 | ("android.intent.extra.COMPONENT_NAME", "ComponentName"),
10 | ("android.intent.extra.EMAIL", "StringArray"),
11 | ("android.intent.extra.HTML_TEXT", "String"),
12 | ("android.intent.extra.INDEX", "Int"),
13 | // ("android.intent.extra.INITIAL_INTENTS", "ParcelableArray"),
14 | ("android.intent.extra.MIME_TYPES", "StringArray"),
15 | ("android.intent.extra.PACKAGE_NAME", "String"),
16 | ("android.intent.extra.PHONE_NUMBER", "String"),
17 | ("android.intent.extra.QUICK_VIEW_FEATURES", "StringArray"),
18 | ("android.intent.extra.STREAM", "URI"),
19 | ("android.intent.extra.SUBJECT", "String"),
20 | ("android.intent.extra.TEXT", "String"),
21 | ("android.intent.extra.TITLE", "String"),
22 | ("android.intent.extra.UID", "Int"),
23 | ];
24 |
--------------------------------------------------------------------------------
/test_coverage_agent/README.md:
--------------------------------------------------------------------------------
1 | # Test Coverage Agent
2 |
3 | A quick and hacky test server in Python that pretends to act as a coverage
4 | agent.
5 |
--------------------------------------------------------------------------------
/test_coverage_agent/test_server.py:
--------------------------------------------------------------------------------
1 | import socketserver
2 | import random
3 |
4 |
5 | COVERAGE_MAP_SIZE = 64 * 1024
6 | fake_map = bytearray([0] * COVERAGE_MAP_SIZE)
7 |
8 |
9 | class MyTCPHandler(socketserver.BaseRequestHandler):
10 | def handle(self):
11 | # self.request is the TCP socket connected to the client
12 | while True:
13 | command = self.request.recv(1)
14 | if command == b'r':
15 | continue
16 | elif command == b'd':
17 | # send the coverage map.
18 | if random.choice([False, False, False, True]):
19 | print("[!] Changing fake map")
20 | fake_map[23] = fake_map[23] + 1
21 | self.request.sendall(fake_map)
22 | elif command == b'':
23 | print("[-] Client disconnected")
24 | break
25 | else:
26 | raise ValueError("Received incorrect command")
27 |
28 | if __name__ == "__main__":
29 | HOST, PORT = "localhost", 6249
30 |
31 | with socketserver.TCPServer((HOST, PORT), MyTCPHandler) as server:
32 | server.serve_forever()
33 |
--------------------------------------------------------------------------------