Added new mechanisms of smali decoding and building.

This commit is contained in:
Ryszard Wiśniewski 2010-04-27 21:31:52 +02:00
parent f9c9588f1d
commit 060572e824
5 changed files with 656 additions and 0 deletions

View File

@ -0,0 +1,225 @@
/*
* Copyright 2010 Ryszard Wiśniewski <brut.alll@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* under the License.
*/
package brut.androlib.src;
import brut.androlib.AndrolibException;
import java.util.ListIterator;
import org.jf.dexlib.Code.Opcode;
/**
* @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/
public class DebugInjector {
public static void inject(ListIterator<String> it, StringBuilder out)
throws AndrolibException {
new DebugInjector(it, out).inject();
}
private DebugInjector(ListIterator<String> it, StringBuilder out) {
mIt = it;
mOut = out;
}
private void inject() throws AndrolibException {
String definition = nextAndAppend();
if (definition.contains(" abstract ")) {
nextAndAppend();
return;
}
injectParameters(definition);
boolean end = false;
while (!end) {
end = step();
}
}
private void injectParameters(String definition) throws AndrolibException {
int pos = definition.indexOf('(');
if (pos == -1) {
throw new AndrolibException();
}
int pos2 = definition.indexOf(')', pos);
if (pos2 == -1) {
throw new AndrolibException();
}
String params = definition.substring(pos + 1, pos2);
int i = definition.contains(" static ") ? 0 : 1;
int argc = TypeName.listFromInternalName(params).size() + i;
while(i < argc) {
mOut.append(".parameter \"p").append(i).append("\"\n");
i++;
}
}
private boolean step() {
String line = next();
if (line.isEmpty()) {
return false;
}
switch (line.charAt(0)) {
case '#':
return false;
case ':':
append(line);
return false;
case '.':
return processDirective(line);
default:
return processInstruction(line);
}
}
private boolean processDirective(String line) {
String line2 = line.substring(1);
if (
line2.startsWith("line ") ||
line2.equals("prologue") ||
line2.startsWith("local ") ||
line2.startsWith("parameter")
) {
return false;
}
append(line);
if (line2.equals("end method")) {
return true;
}
if (
line2.startsWith("annotation ") ||
line2.equals("sparse-switch") ||
line2.startsWith("packed-switch ") ||
line2.startsWith("array-data ")
) {
while(true) {
line2 = nextAndAppend();
if (line2.startsWith(".end ")) {
break;
}
}
}
return false;
}
private boolean processInstruction(String line) {
if (mFirstInstruction) {
mOut.append(".prologue\n");
mFirstInstruction = false;
}
mOut.append(".line ").append(mIt.nextIndex()).append('\n')
.append(line).append('\n');
int pos = line.indexOf(' ');
if (pos == -1) {
return false;
}
Opcode opcode = Opcode.getOpcodeByName(line.substring(0, pos));
if (! opcode.setsRegister()) {
return false;
}
int pos2 = line.indexOf(',', pos);
String register = pos2 == -1 ? line.substring(pos + 1) :
line.substring(pos + 1, pos2);
mOut.append(".local ").append(register).append(", ").append(register)
.append(':').append(getRegisterTypeForOpcode(opcode)).append('\n');
return false;
}
private String getRegisterTypeForOpcode(Opcode opcode) {
switch (opcode.value) {
case (byte)0x0d: // ?
case (byte)0x1a:
case (byte)0x1b:
case (byte)0x1c: // ?
case (byte)0x22:
case (byte)0x23: // ?
case (byte)0xf4: // ?
return "Ljava/lang/Object;";
case (byte)0x1f: // ?
case (byte)0x20: // ?
return "Z";
case (byte)0x21: // ?
return "I";
}
String name = opcode.name;
int pos = name.lastIndexOf('-');
if (pos != -1) {
int pos2 = name.indexOf('/');
String type = pos2 == -1 ? name.substring(pos + 1) :
name.substring(pos + 1, pos2);
if (type.equals("object")) {
return "Ljava/lang/Object;";
}
if (type.equals("int")) {
return "I";
}
if (type.equals("boolean")) {
return "Z";
}
if (type.equals("float")) {
return "F";
}
if (type.equals("double")) {
return "D";
}
if (type.equals("long")) {
return "J";
}
if (type.equals("byte")) {
return "B";
}
if (type.equals("char")) {
return "C";
}
if (type.equals("short")) {
return "S";
}
if (type.equals("long")) {
return "J";
}
}
return "I";
}
private String next() {
return mIt.next().trim();
}
private String nextAndAppend() {
String line = next();
append(line);
return line;
}
private void append(String append) {
mOut.append(append).append('\n');
}
private final ListIterator<String> mIt;
private final StringBuilder mOut;
private boolean mFirstInstruction = true;
}

View File

@ -0,0 +1,109 @@
/*
* Copyright 2010 Ryszard Wiśniewski <brut.alll@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* under the License.
*/
package brut.androlib.src;
import brut.androlib.AndrolibException;
import brut.androlib.res.util.ExtFile;
import brut.androlib.util.DexFileBuilder;
import brut.directory.DirectoryException;
import java.io.*;
import java.util.List;
import java.util.ListIterator;
import org.apache.commons.io.IOUtils;
/**
* @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/
public class SmaliBuilder {
public static void build(ExtFile smaliDir, File dexFile, boolean debug)
throws AndrolibException {
new SmaliBuilder(smaliDir, dexFile, debug).build();
}
private SmaliBuilder(ExtFile smaliDir, File dexFile, boolean debug) {
mSmaliDir = smaliDir;
mDexFile = dexFile;
mDebug = debug;
}
private void build() throws AndrolibException {
try {
mDexBuilder = new DexFileBuilder();
for (String fileName : mSmaliDir.getDirectory().getFiles(true)) {
buildFile(fileName);
}
mDexBuilder.writeTo(mDexFile);
} catch (IOException ex) {
throw new AndrolibException(ex);
} catch (DirectoryException ex) {
throw new AndrolibException(ex);
}
}
private void buildFile(String fileName) throws AndrolibException,
IOException {
File inFile = new File(mSmaliDir, fileName);
InputStream inStream = new FileInputStream(inFile);
if (fileName.endsWith(".smali")) {
mDexBuilder.addSmaliFile(inFile);
return;
}
if (! fileName.endsWith(".java")) {
throw new AndrolibException("Unknown file type: " + inFile);
}
StringBuilder out = new StringBuilder();
List<String> lines = IOUtils.readLines(inStream);
if (!mDebug) {
final String[] linesArray = lines.toArray(new String[0]);
for (int i = 2; i < linesArray.length - 2; i++) {
out.append(linesArray[i]).append('\n');
}
} else {
lines.remove(lines.size() - 1);
lines.remove(lines.size() - 1);
ListIterator<String> it = lines.listIterator(2);
out.append(".source \"").append(inFile.getName()).append("\"\n");
while (it.hasNext()) {
String line = it.next().trim();
if (line.isEmpty() || line.charAt(0) == '#' ||
line.startsWith(".source")) {
continue;
}
if (line.startsWith(".method ")) {
it.previous();
DebugInjector.inject(it, out);
continue;
}
out.append(line).append('\n');
}
}
mDexBuilder.addSmaliFile(
IOUtils.toInputStream(out.toString()), fileName);
}
private final ExtFile mSmaliDir;
private final File mDexFile;
private final boolean mDebug;
private DexFileBuilder mDexBuilder;
}

View File

@ -0,0 +1,88 @@
/*
* Copyright 2010 Ryszard Wiśniewski <brut.alll@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* under the License.
*/
package brut.androlib.src;
import brut.androlib.AndrolibException;
import brut.androlib.src.mod.IndentingWriter;
import java.io.*;
import org.jf.baksmali.Adaptors.ClassDefinition;
import org.jf.baksmali.baksmali;
import org.jf.dexlib.ClassDefItem;
import org.jf.dexlib.DexFile;
/**
* @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/
public class SmaliDecoder {
public static void decode(File apkFile, File outDir, boolean debug)
throws AndrolibException {
new SmaliDecoder(apkFile, outDir, debug).decode();
}
private SmaliDecoder(File apkFile, File outDir, boolean debug) {
mApkFile = apkFile;
mOutDir = outDir;
mDebug = debug;
}
private void decode() throws AndrolibException {
try {
baksmali.useLocalsDirective = true;
baksmali.useSequentialLabels = true;
DexFile dexFile = new DexFile(mApkFile);
for (ClassDefItem classDefItem :
dexFile.ClassDefsSection.getItems()) {
decodeClassDefItem(classDefItem);
}
} catch (IOException ex) {
throw new AndrolibException(ex);
}
}
private void decodeClassDefItem(ClassDefItem classDefItem)
throws AndrolibException, IOException {
TypeName name = TypeName.fromInternalName(
classDefItem.getClassType().getTypeDescriptor());
File outFile = new File(mOutDir, name.getFilePath(true)
+ (mDebug ? ".java" : ".smali"));
if (outFile.exists()) {
throw new AndrolibException(
"File already exists: " + outFile);
}
outFile.getParentFile().mkdirs();
IndentingWriter indentWriter =
new IndentingWriter(new FileWriter(outFile));
if (mDebug) {
indentWriter.write("package " + name.package_ + "; class "
+ name.getName(true, true) + " {/*\n\n");
}
new ClassDefinition(classDefItem).writeTo(indentWriter);
if (mDebug) {
indentWriter.write("\n*/}\n");
}
indentWriter.close();
}
private final File mApkFile;
private final File mOutDir;
private final boolean mDebug;
}

View File

@ -0,0 +1,205 @@
/*
* Copyright 2010 Ryszard Wiśniewski <brut.alll@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* under the License.
*/
package brut.androlib.src;
import brut.androlib.AndrolibException;
import brut.util.Duo;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
/**
* @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/
public class TypeName {
public final String package_;
public final String type;
public final String innerType;
public final int array;
public TypeName(String type, int array) {
this(null, type, null, array);
}
public TypeName(String package_, String type, String innerType, int array) {
this.package_ = package_;
this.type = type;
this.innerType = innerType;
this.array = array;
}
public String getShortenedName() {
return getName("java.lang".equals(package_), isFileOwner());
}
public String getName() {
return getName(false, false);
}
public String getName(boolean excludePackage, boolean separateInner) {
String name =
(package_ == null || excludePackage ? "" : package_ + '.') +
type +
(innerType != null ? (separateInner ? '$' : '.') + innerType : "");
for (int i = 0; i < array; i++) {
name += "[]";
}
return name;
}
public String getJavaFilePath() {
return getFilePath(isFileOwner()) + ".java";
}
public String getSmaliFilePath() {
return getFilePath(true) + ".smali";
}
public String getFilePath(boolean separateInner) {
return package_.replace('.', File.separatorChar) + File.separatorChar
+ type + (separateInner && isInner() ? "$" + innerType : "");
}
public boolean isInner() {
return innerType != null;
}
public boolean isArray() {
return array != 0;
}
public boolean isFileOwner() {
if (mIsFileOwner == null) {
mIsFileOwner = true;
if (isInner()) {
char c = innerType.charAt(0);
if (c < '0' || c > '9') {
mIsFileOwner = false;
}
}
}
return mIsFileOwner;
}
@Override
public String toString() {
return getName();
}
public static TypeName fromInternalName(String internal)
throws AndrolibException {
Duo<TypeName, Integer> duo = fetchFromInternalName(internal);
if (duo.m2 != internal.length()) {
throw new AndrolibException(
"Invalid internal name: " + internal);
}
return duo.m1;
}
public static List<TypeName> listFromInternalName(String internal)
throws AndrolibException {
List<TypeName> types = new ArrayList<TypeName>();
while (! internal.isEmpty()) {
Duo<TypeName, Integer> duo = fetchFromInternalName(internal);
types.add(duo.m1);
internal = internal.substring(duo.m2);
}
return types;
}
public static Duo<TypeName, Integer> fetchFromInternalName(String internal)
throws AndrolibException {
String origInternal = internal;
int array = 0;
boolean isArray = false;
do {
if (internal.isEmpty()) {
throw new AndrolibException(
"Invalid internal name: " + origInternal);
}
isArray = internal.charAt(0) == '[';
if (isArray) {
array++;
internal = internal.substring(1);
}
} while (isArray);
int length = array + 1;
String package_ = null;
String type = null;
String innerType = null;
switch (internal.charAt(0)) {
case 'B':
type = "byte";
break;
case 'C':
type = "char";
break;
case 'D':
type = "double";
break;
case 'F':
type = "float";
break;
case 'I':
type = "int";
break;
case 'J':
type = "long";
break;
case 'S':
type = "short";
break;
case 'Z':
type = "boolean";
break;
case 'V':
type = "void";
break;
case 'L':
int pos = internal.indexOf(';');
if (pos == -1) {
throw new AndrolibException(
"Invalid internal name: " + origInternal);
}
length += pos;
internal = internal.substring(1, pos);
pos = internal.lastIndexOf('/');
package_ = internal.substring(0, pos).replace('/', '.');
type = internal.substring(pos + 1);
pos = type.indexOf('$');
if (pos != -1) {
innerType = type.substring(pos + 1);
type = type.substring(0, pos);
}
break;
default:
throw new AndrolibException(
"Invalid internal name: " + origInternal);
}
return new Duo<TypeName, Integer>(
new TypeName(package_, type, innerType, array), length);
}
private Boolean mIsFileOwner;
}

View File

@ -0,0 +1,29 @@
/*
* Copyright 2010 Ryszard Wiśniewski <brut.alll@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* under the License.
*/
package brut.androlib.src.mod;
import java.io.Writer;
/**
* @author Ryszard Wiśniewski <brut.alll@gmail.com>
*/
public class IndentingWriter extends org.jf.baksmali.IndentingWriter {
public IndentingWriter(Writer writer) {
super(writer);
}
}