Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
palant committed Feb 24, 2021
0 parents commit 982f609
Show file tree
Hide file tree
Showing 11 changed files with 1,062 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*.jar
373 changes: 373 additions & 0 deletions LICENSE.txt

Large diffs are not rendered by default.

53 changes: 53 additions & 0 deletions README.md
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
48 changes: 48 additions & 0 deletions build
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))
15 changes: 15 additions & 0 deletions config.properties.example
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
1 change: 1 addition & 0 deletions src/Manifest.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Main-Class: info.palant.apkInstrumentation.Main
54 changes: 54 additions & 0 deletions src/info/palant/apkInstrumentation/AssignmentRemover.java
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();
}
}
59 changes: 59 additions & 0 deletions src/info/palant/apkInstrumentation/Filter.java
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;
}
}
Loading

0 comments on commit 982f609

Please sign in to comment.