-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 982f609
Showing
11 changed files
with
1,062 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
*.jar |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
# APK Instrumentation | ||
|
||
**Warning**: This is a repository with quick-and-dirty code rewriting tools based on [Soot](https://soot-oss.github.io/soot/) which I use for my own research. The documentation is limited and support cannot be provided. Use at your own risk! | ||
|
||
## Getting started | ||
|
||
You can download `apk-instrumentation.jar` from the Releases section of this repository. It is self-contained and requires nothing but Java. If you want to build it yourself, you will need Python 3 and Soot (JAR file with all dependencies). You then run the build command: | ||
|
||
./build /path/to/soot-jar-with-dependencies.jar | ||
|
||
To start the APK conversion process, use the following command: | ||
|
||
java -jar apk-instrumentation.jar [/path/to/config.properties] | ||
|
||
If no path to `config.properties` is given on the command line, the file is assumed to be present in the current directory. Its entries determine what code transformations should be performed. | ||
|
||
## General configuration options | ||
|
||
The following configuration options are independent of the components enabled: | ||
|
||
* `sdk`: (optional) directory where the Android SDK is installed. If omitted, `ANDROID_HOME` environment variable has to be set. | ||
* `input`: path to the input APK file | ||
* `output`: path of the instrumented APK file to be written | ||
* `keystore`: (optional) path to the key store containing the signing key | ||
* `keypass`: (optional) password protecting the key store | ||
|
||
## Filters | ||
|
||
Each component has a `filter` option allowing to restrict its functionality. It’s a space-separated list, entries can have to following format: | ||
|
||
* `com.example.test.*`: includes all classes with names matching a particular prefix | ||
* `com.example.test.Main`: includes all methods of a specific class | ||
* `com.example.test.Main.dump()`: includes all methods with a particular name inside a class (empty parentheses at the end are mandatory) | ||
|
||
## MethodLogger component | ||
|
||
This component will add a `System.out.println()` call to the start of each method. It will print the method name and parameter values to the log. | ||
|
||
Configuration options: | ||
|
||
* `MethodLogger.enabled`: add to enable this component | ||
* `MethodLogger.filter`: (optional) restricts functionality to a set of classes or methods (see Filters section above) | ||
* `MethodLogger.tag`: (optional) log tag to be used (default is `APKInstrumentation`) | ||
|
||
## AssignmentRemover component | ||
|
||
This component will remove any assignments with the specified result type. Note that Jimple does not support nested expressions, so any intermediate result is assigned to a local variable. | ||
|
||
Configuration options: | ||
|
||
* `AssignmentRemover.enabled`: add to enable this component | ||
* `AssignmentRemover.filter`: (optional) restricts functionality to a set of classes or methods (see Filters section above) | ||
* `AssignmentRemover.type`: the result type identifying the assignment to be removed |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
#!/usr/bin/env python3 | ||
|
||
import os | ||
import shutil | ||
import subprocess | ||
import sys | ||
import tempfile | ||
import zipfile | ||
|
||
if len(sys.argv) < 2: | ||
print('Please provide path to soot-jar-with-dependencies.jar on the command line', file=sys.stderr) | ||
sys.exit(1) | ||
soot = sys.argv[1] | ||
basedir = os.path.dirname(sys.argv[0]) or os.getcwd() | ||
|
||
if 'JAVA_HOME' in os.environ: | ||
javac = os.path.join(os.environ['JAVA_HOME'], 'bin', 'javac') | ||
jar = os.path.join(os.environ['JAVA_HOME'], 'bin', 'jar') | ||
else: | ||
javac = 'javac' | ||
jar = 'jar' | ||
|
||
def listFiles(dir, extension): | ||
result = [] | ||
for dirpath, dirnames, filenames in os.walk(dir): | ||
for filename in filenames: | ||
if os.path.splitext(filename)[1] == extension: | ||
result.append(os.path.relpath(os.path.join(dirpath, filename), dir)) | ||
return result | ||
|
||
classdir = tempfile.mkdtemp() | ||
try: | ||
source_dir = os.path.join(basedir, 'src') | ||
sources = listFiles(source_dir, '.java') | ||
subprocess.check_call([javac, '-d', classdir, *sources], cwd=source_dir, env={'CLASSPATH': soot}) | ||
|
||
outfile = os.path.abspath(os.path.join(basedir, 'apk-instrumentation.jar')) | ||
classes = listFiles(classdir, '.class') | ||
manifest_path = os.path.abspath(os.path.join(source_dir, 'Manifest.txt')) | ||
subprocess.check_call([jar, 'cfm', outfile, manifest_path, *classes], cwd=classdir) | ||
finally: | ||
shutil.rmtree(classdir) | ||
|
||
with zipfile.ZipFile(soot, 'r') as archive_in: | ||
with zipfile.ZipFile(outfile, 'a') as archive_out: | ||
for entry in archive_in.infolist(): | ||
if not entry.filename.startswith('META-INF/'): | ||
archive_out.writestr(entry, archive_in.read(entry)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
sdk=/path/to/android/sdk | ||
input=game.apk | ||
output=game-instrumented.apk | ||
keystore=debug.jks | ||
keypass=123456 | ||
|
||
MethodLogger.enabled=true | ||
MethodLogger.filter=com.example.funnygame.* \ | ||
com.example.util.Util \ | ||
com.example.util.Downloader.download() | ||
MethodLogger.tag=MethodEntered | ||
|
||
AssignmentRemover.enabled=true | ||
AssignmentRemover.filter=com.example.funnygame.SomeClass.<clinit>() | ||
AssignmentRemover.type=com.example.funnygame.SomeInterface |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Main-Class: info.palant.apkInstrumentation.Main |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
/* | ||
* This Source Code is subject to the terms of the Mozilla Public License | ||
* version 2.0 (the "License"). You can obtain a copy of the License at | ||
* http://mozilla.org/MPL/2.0/. | ||
*/ | ||
|
||
package info.palant.apkInstrumentation; | ||
|
||
import java.util.Map; | ||
import java.util.Properties; | ||
|
||
import soot.Body; | ||
import soot.BodyTransformer; | ||
import soot.jimple.AssignStmt; | ||
import soot.jimple.JimpleBody; | ||
|
||
public class AssignmentRemover extends BodyTransformer | ||
{ | ||
private Filter filter; | ||
private String type; | ||
|
||
public AssignmentRemover(Properties config) | ||
{ | ||
this.type = config.getProperty("AssignmentRemover.type"); | ||
if (this.type == null) | ||
throw new RuntimeException("Please add AssignmentRemover.type option to config file."); | ||
|
||
String filterSpec = config.getProperty("AssignmentRemover.filter"); | ||
if (filterSpec != null) | ||
this.filter = new Filter(filterSpec); | ||
else | ||
this.filter = null; | ||
} | ||
|
||
@Override | ||
protected void internalTransform(Body b, String phaseName, Map<String, String> options) | ||
{ | ||
JimpleBody body = (JimpleBody)b; | ||
if (this.filter != null && !this.filter.matches(body)) | ||
return; | ||
|
||
body.getUnits().removeIf(unit -> { | ||
if (unit instanceof AssignStmt) | ||
{ | ||
String typeName = ((AssignStmt)unit).getLeftOp().getType().toString(); | ||
if (typeName.equals(this.type)) | ||
return true; | ||
} | ||
return false; | ||
}); | ||
|
||
body.validate(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
/* | ||
* This Source Code is subject to the terms of the Mozilla Public License | ||
* version 2.0 (the "License"). You can obtain a copy of the License at | ||
* http://mozilla.org/MPL/2.0/. | ||
*/ | ||
|
||
package info.palant.apkInstrumentation; | ||
|
||
import java.util.ArrayList; | ||
import java.util.HashSet; | ||
import java.util.StringTokenizer; | ||
|
||
import soot.jimple.JimpleBody; | ||
|
||
public class Filter | ||
{ | ||
private ArrayList<String> prefixes; | ||
private HashSet<String> classes; | ||
private HashSet<String> methods; | ||
|
||
public Filter(String spec) | ||
{ | ||
this.prefixes = new ArrayList<String>(); | ||
this.classes = new HashSet<String>(); | ||
this.methods = new HashSet<String>(); | ||
|
||
StringTokenizer tokenizer = new StringTokenizer(spec); | ||
while (tokenizer.hasMoreTokens()) | ||
{ | ||
String token = tokenizer.nextToken(); | ||
if (token.equals("")) | ||
continue; | ||
|
||
if (token.endsWith("()")) | ||
this.methods.add(token.substring(0, token.length() - 2)); | ||
else if (token.endsWith("*")) | ||
this.prefixes.add(token.substring(0, token.length() - 1)); | ||
else | ||
this.classes.add(token); | ||
} | ||
} | ||
|
||
public boolean matches(JimpleBody body) | ||
{ | ||
String className = body.getMethod().getDeclaringClass().getName(); | ||
if (this.classes.contains(className)) | ||
return true; | ||
|
||
String methodName = body.getMethod().getName(); | ||
if (this.methods.contains(className + "." + methodName)) | ||
return true; | ||
|
||
for (String prefix: this.prefixes) | ||
if (className.startsWith(prefix)) | ||
return true; | ||
|
||
return false; | ||
} | ||
} |
Oops, something went wrong.