/* SAAF: A static analyzer for APK files.
* Copyright (C) 2013 syssec.rub.de
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package de.rub.syssec.saaf.application.methods;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;
import java.util.TreeSet;
import org.apache.log4j.Logger;
import de.rub.syssec.saaf.analysis.steps.obfuscation.Entropy;
import de.rub.syssec.saaf.application.Field;
import de.rub.syssec.saaf.misc.ByteUtils;
import de.rub.syssec.saaf.misc.KMP;
import de.rub.syssec.saaf.model.application.BasicBlockInterface;
import de.rub.syssec.saaf.model.application.ClassInterface;
import de.rub.syssec.saaf.model.application.CodeLineInterface;
import de.rub.syssec.saaf.model.application.DetectionLogicError;
import de.rub.syssec.saaf.model.application.FieldInterface;
import de.rub.syssec.saaf.model.application.MethodInterface;
import de.rub.syssec.saaf.model.application.SmaliClassError;
import de.rub.syssec.saaf.model.application.SyntaxException;
import de.rub.syssec.saaf.model.application.instruction.InstructionInterface;
import de.rub.syssec.saaf.model.application.instruction.InstructionType;
public class Method implements MethodInterface {
private String name;
private String parameters = null;
private String returnValueString = null;
private byte[] rawParameters;
private byte[] returnValue;
private boolean hasUnlinkedBBs = false;
private LinkedList<CodeLineInterface> codeLines;
private LinkedList<FieldInterface> localFieldList = null;
private static final byte[] CONSTRUCTOR_NAME = "<init>".getBytes();
public static final byte[] STATIC_CONSTRUCTOR_NAME = "<clinit>".getBytes();
private METHOD_TYPE methodType;
private ClassInterface smaliClass; // the SMALI file this method belongs to
private LinkedList<BasicBlockInterface> bbList = new LinkedList<BasicBlockInterface>();
private int id = -1; // ID from the table in db
private boolean isStatic = false;
private String fuzzyHash = null;
private int label;
private static final Logger LOGGER = Logger.getLogger(Method.class);
private boolean isCurrentLineInSwitch = false;
public enum METHOD_TYPE {
CONSTRUCTOR, STATIC_CONSTRUCTOR, // static { ... } block
METHOD; // all other "normal" methods
}
public Method(LinkedList<CodeLineInterface> codeLines,
ClassInterface smaliClass, int label) {
this.codeLines = codeLines;
this.smaliClass = smaliClass;
this.label = label;
parseNameAndType();
// parseBB();
this.changed=true;
}
/**
* Does this method contain anything else besides an empty declaration?
*
* @return true if the method is "empty", false otherwise
*/
public boolean isEmpty() {
return bbList.isEmpty() ? true : false;
}
private void parseNameAndType() {
// ex: .method public constructor <init>(Landroid/content/Context;)V
byte[] firstLine = codeLines.getFirst().getLine();
int openingParenthesisIndex = ByteUtils.indexOf(firstLine, '(');
int closingParenthesisIndex = ByteUtils.indexOf(firstLine, ')');
int spaceBeforeIndex = ByteUtils.indexOfReverse(firstLine, ' ',
openingParenthesisIndex);
byte[] n = ByteUtils.subbytes(firstLine, spaceBeforeIndex + 1,
openingParenthesisIndex); // cut the space
if (Arrays.equals(n, CONSTRUCTOR_NAME))
methodType = METHOD_TYPE.CONSTRUCTOR;
else if (Arrays.equals(n, STATIC_CONSTRUCTOR_NAME))
methodType = METHOD_TYPE.STATIC_CONSTRUCTOR;
else
methodType = METHOD_TYPE.METHOD;
name = new String(n);
// Check if the method is static, only use the first part b/c the method
// could have the word static in its name
byte[] lineTillSpace = ByteUtils.subbytes(firstLine, 0,
spaceBeforeIndex);
if (ByteUtils.contains(lineTillSpace, "static".getBytes()))
isStatic = true;
// get the parameters
rawParameters = ByteUtils.subbytes(firstLine,
openingParenthesisIndex + 1, closingParenthesisIndex);
// return value
returnValue = ByteUtils.subbytes(firstLine, closingParenthesisIndex + 1);
}
// FIXME: crude hack to replace the autogeneration in parseNameAndType(),
// could perhaps still always be generated in parseNameAndType (for safety)
// and use this just for explicitly generating bbs anew
public void generateBBs() throws DetectionLogicError, SmaliClassError {
bbList = generateBlocksNew();
DFS dfs = new DFS();
dfs.labelAllBB(this);
}
private LinkedList<BasicBlockInterface> generateBlocksNew() throws SmaliClassError {
// CHANGE ALL PARAMS TO PASS THE LABELS
// possibly change everything to index in codeline array instead of cls
// still have to fix problem that switchtable will be part of the last
// BB
LinkedList<BasicBlockInterface> blocks = null;
LinkedList<TryBlock> tries = null;
HashMap<String, CodeLineInterface> labels = getLabelLines();
LinkedList<Link> links = findLeadersNew();
LinkedList<Integer> leaders = new LinkedList<Integer>();
links = findGotoTargetsNew(links, labels, leaders);
links = findIfTargetsNew(links, labels);// instruction.getType() ==
// InstructionType.LABEL and or
// isCode
links = findSwitchTargetsNew(links, labels);
tries = findTryTargetsNew(labels, links);
// build blocks from tries + links
blocks = buildBlocks(links, tries, leaders);
blocks = linkNew(blocks, links, tries);
return blocks;
}
private LinkedList<BasicBlockInterface> linkNew(
LinkedList<BasicBlockInterface> blocks, LinkedList<Link> links,
LinkedList<TryBlock> tries) {
// first add all links
for (Link l : links) {
for (BasicBlockInterface start : blocks) {
if (/*!start.hasReturn() && !start.hasThrow() &&*///added for return in block followed by a goto, which is most likely patched into the code
!start.getCodeLines().isEmpty()
&& start.getCodeLines().getLast().getLineNr()
>= (l.getFrom().getLineNr())
&& start.getCodeLines().getFirst().getLineNr()
<= (l.getFrom().getLineNr())
) {
for (BasicBlockInterface target : blocks) {
// could also be done via line number
if (
!target.getCodeLines().isEmpty()
&& target.getCodeLines().getFirst().getLineNr()
<= (l.getTo().getLineNr())
&& target.getCodeLines().getLast().getLineNr()
>= (l.getTo().getLineNr())
) {
//linkBBs(start, target);
start.addNextBB(target);
target.addPreviousBB(start);
break;
}
}
break;
}
}
}
//add all try code
for(TryBlock t:tries){
for(BasicBlockInterface start:blocks){
//found a block within the try
if(
!start.getCodeLines().isEmpty() && // TODO Fix for issue 54
start.getCodeLines().getLast().getLineNr()>=t.getBegin()&&start.getCodeLines().getLast().getLineNr()<=t.getEnd()){
//for every block, check if it is one of the catches
for(CodeLineInterface c:t.getCatches()){
//check all catches
for(BasicBlockInterface target:blocks){
if(
!target.getCodeLines().isEmpty() && // TODO Fix for issue 54
target.getCodeLines().getFirst().getLineNr()<=(c.getLineNr())&&
target.getCodeLines().getLast().getLineNr()>=(c.getLineNr())){
//linkBBs(start, target);
start.addNextBB(target);
target.addPreviousBB(start);
start.setIsTryBlock(true);
target.setIsCatchBlock(true);
break;
}
}
}
}
}
}
// link all the default cases
// there is always a fall through, except for 3 cases:
// goto, return, throw
Iterator<BasicBlockInterface> iter = blocks.iterator();
BasicBlockInterface first = null;
BasicBlockInterface next = null;
if (iter.hasNext())
first = iter.next();
while (iter.hasNext()) {
next = iter.next();
try {
CodeLineInterface lastLine = BasicBlock.getLastCodeLine(
first).getCodeLine();
//!(hasReturn() && !hasDeadCode()) && !(hasth)
//TODO: check again if we need the add !first.hasDeadCode() to this default fall through case in BB
if (/*!first.hasDeadCode() && */!first.hasGoto() && lastLine.getInstruction().getType() != InstructionType.RETURN
&& !(KMP.indexOf(lastLine.getLine(), "throw".getBytes()) == 0)/*&& !first.hasReturn() &&! first.hasThrow()*/){
//linkBBs(first, next);
first.addNextBB(next);
next.addPreviousBB(first);
}
first = next;
} catch (SyntaxException e) {
// this just happens if there is no code in the bb, so error is
// no error...
// it can happen if there is a BB just consisting of a single
// label,
// so link the bb to the next and previous on
//linkBBs(first, next);
first.addNextBB(next);
next.addPreviousBB(first);
first = next;
}
}
return blocks;
}
// /**
// * this links two BBs
// * @param first the starting block (this does first.addNextBB() )
// * @param next the target block (this does next.addPreviousBB() )
// */
// private void linkBBs(BasicBlockInterface first, BasicBlockInterface next) {
// first.addNextBB(next);
// next.addPreviousBB(first);
// }
private LinkedList<BasicBlockInterface> buildBlocks(LinkedList<Link> links,
LinkedList<TryBlock> tries, LinkedList<Integer> gotoLeaders) throws SmaliClassError {
// leaders = lines (or ints) which start a block, thus all targets of
// the links + first line + all targets of tries
// get the position of the leader codeline in this methods codeline
// array and put it into a treeset to sort all leaders
// TODO: getting the position is quite inefficient, so instead of
// codelines save the int (or both)
TreeSet<Integer> leaders = new TreeSet<Integer>();
LinkedList<CodeLineInterface> returnList = new LinkedList<CodeLineInterface>();
// first line of the code is always a leader
leaders.add(0);
leaders.addAll(gotoLeaders);
boolean hasThrow;
boolean hasReturn;
boolean hasGoto;
hasThrow = false;
hasReturn = false;
hasGoto = false;
// TODO: if ints are saved these lockups become unnecessary
// TODO: could be deleted from here until next TODO (if ints used)
for (Link l : links) {
CodeLineInterface current = codeLines.getFirst();
int index = 0;
while (current.getLineNr() != l.getTo().getLineNr()
&& index < codeLines.size() - 1) {
index++;
current = codeLines.get(index);
}
if (index < codeLines.size())
leaders.add(index);
else {
throw new SmaliClassError("Could not link BasicBlocks in method: "
+ this.getName() + " from File: "
+ this.getSmaliClass().getFile().getAbsolutePath());
}
}
for (TryBlock t : tries) {
CodeLineInterface current = codeLines.getFirst();
// special case which should never ever happen
if (t.getBlockEnd() != null
&& t.getBlockEnd().getLineNr() == current.getLineNr()) {
leaders.add(1);
}
LinkedList<CodeLineInterface> targets = t.getCatches();
for (CodeLineInterface c : targets) {
int index = 0;
while (current.getLineNr() != c.getLineNr()
&& index < codeLines.size() - 1) {
index++;
current = codeLines.get(index);
if (t.getBlockEnd() != null
&& t.getBlockEnd().getLineNr() == current
.getLineNr()
&& index < codeLines.size() - 1) {
leaders.add(index + 1);
}
}
if (index < codeLines.size())
leaders.add(index);
else {
throw new SmaliClassError("Could not link BasicBlocks in method: "
+ this.getName() + " from File: "
+ this.getSmaliClass().getFile().getAbsolutePath());
}
}
}
// TODO: delete if ints used, up to here
// build all the blocks, based on the leaders
// a block starts at a leader and extends until on line before the next
// leader
// now that we know the leaders
// start building + filling BBs
// 25.01.2013 added some control structures for cosmetic reasons (like
// not showing the switchtables)
// TODO: speed up the switch hiding, could be done by changing that
// triple if construct
// or by passing lines to hide as argument (and building a TreeSet
// beforehand (in findSwitchTargets) containing
// the lines to hide, so it ends up being just an if comparing line
// numbers)
LinkedList<BasicBlockInterface> blocks = new LinkedList<BasicBlockInterface>();
Iterator<Integer> iter = leaders.iterator();
Integer leader = iter.next();
int nextLeader = -1;
if (iter.hasNext()) {
nextLeader = iter.next();
}
// case if nextLeader == -1, just 1 block
if (nextLeader == -1) {
LinkedList<CodeLineInterface> lines = new LinkedList<CodeLineInterface>();
for (int i = leader; i < codeLines.size(); i++) {
CodeLineInterface c = codeLines.get(i);
if (isSwitchStart(c))
continue;
if (isSwitchEnd(c))
continue;
if (isInSwitch())
continue;
if(c.getInstruction().getType() == InstructionType.GOTO){
hasGoto = true;
}
if(c.getInstruction().getType() == InstructionType.RETURN){
hasReturn = true;
returnList.add(c);
}
if((KMP.indexOf(c.getLine(), "throw".getBytes()) == 0)){
hasThrow = true;
}
lines.add(c);
}
BasicBlockInterface block = new BasicBlock(lines, this);
block.setHasReturn(hasReturn);
block.setHasThrow(hasThrow);
block.setHasGoto(hasGoto);
if(block.hasReturn()){
try {
CodeLineInterface lastCodeLine = BasicBlock.getLastCodeLine(block).getCodeLine();
if (returnList.size()>1 || (returnList.size()==1 && lastCodeLine.getLineNr()!= returnList.getFirst().getLineNr())){
block.setHasDeadCode(true);
}
} catch (SyntaxException e) {
LOGGER.error("Could not find last Codeline, while searching for return codes");
}
}
returnList.clear();
hasReturn = false;
hasThrow = false;
hasGoto = false;
blocks.add(block);
} else {
// else
// add first block
LinkedList<CodeLineInterface> lines = new LinkedList<CodeLineInterface>();
boolean justDotComment = true;
for (int i = leader; i < nextLeader; i++) {
CodeLineInterface c = codeLines.get(i);
if (c.isCode()) {
justDotComment = false;
}
if (isSwitchStart(c))
continue;
if (isSwitchEnd(c))
continue;
if (isInSwitch())
continue;
if(c.getInstruction().getType() == InstructionType.GOTO){
hasGoto = true;
}
if(c.getInstruction().getType() == InstructionType.RETURN){
hasReturn = true;
returnList.add(c);
}
if((KMP.indexOf(c.getLine(), "throw".getBytes()) == 0)){
hasThrow = true;
}
lines.add(c);
}
if (!justDotComment) {
BasicBlockInterface block = new BasicBlock(lines, this);
block.setHasReturn(hasReturn);
block.setHasThrow(hasThrow);
block.setHasGoto(hasGoto);
if(block.hasReturn()){
try {
CodeLineInterface lastCodeLine = BasicBlock.getLastCodeLine(block).getCodeLine();
if (returnList.size()>1 || (returnList.size()==1 && lastCodeLine.getLineNr()!= returnList.getFirst().getLineNr())){
block.setHasDeadCode(true);
}
} catch (SyntaxException e) {
LOGGER.error("Could not find last Codeline, while searching for return codes");
}
}
returnList.clear();
hasReturn = false;
hasThrow = false;
hasGoto = false;
blocks.add(block);
}
// add all intermediate blocks
while (iter.hasNext()) {
if (!justDotComment)
lines = new LinkedList<CodeLineInterface>();
justDotComment = true;
// this while is used to merge blocks, which just consist of dot
// comments with the next block
while (justDotComment && iter.hasNext()) {
leader = nextLeader;
nextLeader = iter.next();
for (int i = leader; i < nextLeader; i++) {
CodeLineInterface c = codeLines.get(i);
if (c.isCode()) {
justDotComment = false;
}
if (isSwitchStart(c))
continue;
if (isSwitchEnd(c))
continue;
if (isInSwitch())
continue;
if(c.getInstruction().getType() == InstructionType.GOTO){
hasGoto = true;
}
if(c.getInstruction().getType() == InstructionType.RETURN){
hasReturn = true;
returnList.add(c);
}
if((KMP.indexOf(c.getLine(), "throw".getBytes()) == 0)){
hasThrow = true;
}
lines.add(c);
}
}
// just add the block if the while stopped because of no more
// dot comment
// if it stopped because there is no following block, the code
// handling the last block
// will add all this code aswell, because all the previous code
// was just .comment
if (!justDotComment) {
BasicBlockInterface block = new BasicBlock(lines, this);
block.setHasReturn(hasReturn);
block.setHasThrow(hasThrow);
block.setHasGoto(hasGoto);
if(block.hasReturn()){
try {
CodeLineInterface lastCodeLine = BasicBlock.getLastCodeLine(block).getCodeLine();
if (returnList.size()>1 || (returnList.size()==1 && lastCodeLine.getLineNr()!= returnList.getFirst().getLineNr())){
block.setHasDeadCode(true);
}
} catch (SyntaxException e) {
LOGGER.error("3 "+"Could not find last Codeline, while searching for return codes");
}
}
returnList.clear();
hasReturn = false;
hasThrow = false;
hasGoto = false;
blocks.add(block);
}
}
//add last block
//TODO: maybe switch check
leader=nextLeader;//System.out.println("lastBlock");
//just reset if we left the intermediate part by adding a block (and not when we still have "buffered"
//codelines in the list, because of .comment merging
if(!justDotComment)
lines=new LinkedList<CodeLineInterface>();
justDotComment = true;
for(int i=leader;i<codeLines.size();i++){
CodeLineInterface c = codeLines.get(i);
if(isSwitchStart(c)){
continue;
}
if(isSwitchEnd(c)){
continue;
}
if (isInSwitch()){
continue;
}
if(c.getInstruction().getType() == InstructionType.GOTO){
hasGoto = true;
}
if(c.getInstruction().getType() == InstructionType.RETURN){
hasReturn = true;
returnList.add(c);
}
if((KMP.indexOf(c.getLine(), "throw".getBytes()) == 0)){
hasThrow = true;
}
if (c.isCode()) {
justDotComment = false;
}
lines.add(c);
}
if (!justDotComment) {
BasicBlockInterface block = new BasicBlock(lines, this);
block.setHasReturn(hasReturn);
block.setHasThrow(hasThrow);
block.setHasGoto(hasGoto);
if(block.hasReturn()){
try {
CodeLineInterface lastCodeLine = BasicBlock.getLastCodeLine(block).getCodeLine();
if (returnList.size()>1 || (returnList.size()==1 && lastCodeLine.getLineNr()!= returnList.getFirst().getLineNr())){
block.setHasDeadCode(true);
}
} catch (SyntaxException e) {
LOGGER.error("Could not find last Codeline, while searching for return codes");
}
}
returnList.clear();
hasReturn = false;
hasThrow = false;
hasGoto = false;
blocks.add(block);
}else {
LinkedList<CodeLineInterface> linesPreviousBlock = blocks.getLast().getCodeLines();
linesPreviousBlock.addAll(lines);
BasicBlockInterface block = new BasicBlock(linesPreviousBlock, this);
blocks.removeLast();
blocks.add(block);
}
}
return blocks;
}
private boolean isSwitchStart(CodeLineInterface cl) {
if (KMP.indexOf(cl.getLine(), ":sswitch_data".getBytes()) == 0
|| KMP.indexOf(cl.getLine(), ":pswitch_data".getBytes()) == 0) {
isCurrentLineInSwitch = true;
return true;
}
return false;
}
private boolean isInSwitch() {
return isCurrentLineInSwitch;
}
private boolean isSwitchEnd(CodeLineInterface cl) {
if (KMP.indexOf(cl.getLine(), ".end sparse-switch".getBytes()) == 0
|| KMP.indexOf(cl.getLine(), ".end packed-switch".getBytes()) == 0) {
isCurrentLineInSwitch = false;
return true;
}
return false;//changed from isCurrentLineInSwitch to false similar error to issue 54
}
// private boolean inSwitch(CodeLineInterface cl) {
// if (isCurrentLineInSwitch) {
// if (KMP.indexOf(cl.getLine(), ".end sparse-switch".getBytes()) == 0
// || KMP.indexOf(cl.getLine(),
// ".end packed-switch".getBytes()) == 0) {
// isCurrentLineInSwitch = false;// TODO: maybe change on first
// // line after switch???
// return true;
// }
// return isCurrentLineInSwitch;
// }
// if (KMP.indexOf(cl.getLine(), ":sswitch_data".getBytes()) == 0
// || KMP.indexOf(cl.getLine(), ":pswitch_data".getBytes()) == 0) {
// isCurrentLineInSwitch = true;
// }
// return isCurrentLineInSwitch;
// }
// TODO: give this everywhere as parameter, so we dont need to do it in
// every function again and again
private HashMap<String, CodeLineInterface> getLabelLines() {
HashMap<String, CodeLineInterface> labels = new HashMap<String, CodeLineInterface>();
// boolean inSwitchTable=false;
for (int currentLine = 0; currentLine < codeLines.size(); currentLine++) {
CodeLineInterface cl = codeLines.get(currentLine);
if (ByteUtils.startsWith(cl.getLine(), ":".getBytes())) {
String label = new String(cl.getLine());
if (!labels.containsKey(label))
labels.put(label, cl);
}
}
return labels;
}
private LinkedList<TryBlock> findTryTargetsNew(
HashMap<String, CodeLineInterface> labels, LinkedList<Link> links) {
LinkedList<TryBlock> tryList = new LinkedList<TryBlock>();
TryBlock block = null;
int begin = -1;
int end = -1;
int endOfTryCode = -1;
boolean findCatches = false;
for (int currentLine = 0; currentLine < codeLines.size(); currentLine++) {
CodeLineInterface cl = codeLines.get(currentLine);
if (findCatches) {
if (KMP.indexOf(cl.getLine(), ".catch".getBytes()) == 0) {
// we want the actual CodeLineInterface, so we read the
// label of the catch block and then look up which CodeLine
// that is
CodeLineInterface currentCatchTarget = labels
.get(new String(
ByteUtils.subbytes(
cl.getLine(),
ByteUtils.indexOfReverse(
cl.getLine(), ':'))));
block.addCatch(currentCatchTarget);
end = cl.getLineNr();
continue;
} else {
findCatches = false;
block.setEnd(end);
block.setBlockEnd(codeLines.get(currentLine - 1));
tryList.add(block);
block = null;
//add fall through link here?
//System.out.println("Fall through try link: ");
if(currentLine < codeLines.size()-1 && endOfTryCode != -1){
Link link = new Link(codeLines.get(endOfTryCode), codeLines.get(currentLine));
//System.out.println("link... from: "+link.getFrom()+" to: "+link.getTo());
links.add(link);
}
}
}
if (KMP.indexOf(cl.getLine(), ":try_start_".getBytes()) == 0) {
begin = cl.getLineNr();
endOfTryCode = -1;
}
if (KMP.indexOf(cl.getLine(), ":try_end_".getBytes()) == 0) {
block = new TryBlock(begin, end);
findCatches = true;
endOfTryCode = currentLine -1;
}
}
return tryList;
}
private LinkedList<Link> findIfTargetsNew(LinkedList<Link> links,
HashMap<String, CodeLineInterface> labels) {
for (int currentLine = 0; currentLine < codeLines.size(); currentLine++) {
CodeLineInterface cl = codeLines.get(currentLine);
if (cl.getInstruction().getType() == InstructionType.JMP) {
String label = new String(cl.getInstruction().getLabel());
CodeLineInterface to = labels.get(label);
Link link = new Link(cl, to);
if (!links.contains(link))
links.add(link);
//fallthrough
//need to check if next is switch? probably not, because there is always a fallthrough so
//compiler should prevent fall to switchtable (would probably result in an error)
if(currentLine < codeLines.size()-1){
to = codeLines.get(currentLine+1);
link = new Link(cl, to);
if (!links.contains(link))
links.add(link);
}
}
}
return links;
}
private LinkedList<Link> findGotoTargetsNew(LinkedList<Link> links,
HashMap<String, CodeLineInterface> labels, LinkedList<Integer> leaders) {
for (int currentLine = 0; currentLine < codeLines.size(); currentLine++) {
CodeLineInterface cl = codeLines.get(currentLine);
if (cl.getInstruction().getType() == InstructionType.GOTO) {
String label = new String(cl.getInstruction().getLabel());
CodeLineInterface to = labels.get(label);
Link link = new Link(cl, to);
if (!links.contains(link))
links.add(link);
if(currentLine<codeLines.size()-1){
leaders.add(currentLine+1);
}
}
}
return links;
}
private LinkedList<Link> findSwitchTargetsNew(LinkedList<Link> links,
HashMap<String, CodeLineInterface> labels) {
HashMap<String, LinkedList<Target>> switchTablesNew = new HashMap<String, LinkedList<Target>>();
HashMap<String, CodeLineInterface> switchInstructions = new HashMap<String, CodeLineInterface>();
LinkedList<Target> tmpList = new LinkedList<Target>();
String switchName = null;
boolean inSwitchTable = false;
for (int currentLine = 0; currentLine < codeLines.size(); currentLine++) {
CodeLineInterface cl = codeLines.get(currentLine);
// switch start found
// build association between the switch-statement and the
// corresponding switch-table
if (cl.getInstruction().getType() == InstructionType.SWITCH) {
String tableName = new String(cl.getInstruction().getLabel());
if (!switchInstructions.containsKey(tableName))
switchInstructions.put(tableName, cl);
}
// switch table BEGIN found
// if(ByteUtils.startsWith(cl.getLine(), ":switch ".getBytes())){
// label of current switch tables
// Table starts in the following line
// name of the switch table
// next line is a .sparse/packed_switch, after that , the switches
// are listed, containing the initial value too compare too
if (KMP.indexOf(cl.getLine(), ":sswitch_data".getBytes()) == 0
|| KMP.indexOf(cl.getLine(), ":pswitch_data".getBytes()) == 0) {
switchName = new String(cl.getLine());
continue;
}
// the initial value... in hex
// example : .packed_switch 0x0
// -> first one taken if value = 0, second if value 2 etc.
// TODO: might be used for labels, but will be ignored at the moment
if (KMP.indexOf(cl.getLine(), ".sparse-switch".getBytes()) == 0
|| KMP.indexOf(cl.getLine(), ".packed-switch".getBytes()) == 0) {
// initialvalue = end of line (from hex to decimal)
inSwitchTable = true;
continue;
}
// read table
// determine which switch label belongs to which switch table
// condition shoudl be in front of : , target = :blabla
if (inSwitchTable) {
// check of end of table first
// switch table END found
// end of switch table
if (KMP.indexOf(cl.getLine(), ".end sparse-switch".getBytes()) == 0
|| KMP.indexOf(cl.getLine(),
".end packed-switch".getBytes()) == 0) {
// put links into table
switchTablesNew.put(switchName, tmpList);
// ini new list
tmpList = new LinkedList<Target>();
switchName = null;
inSwitchTable = false;
continue;
}
// if not end of table, make links and put them into the list
// use switchName for map association
int start = KMP.indexOf(cl.getLine(), ":".getBytes());
// targets are starting with a :
String switchTarget = new String(ByteUtils.subbytes(
cl.getLine(), start));
CodeLineInterface switchTargetLine = labels.get(switchTarget);
// TODO: add label
tmpList.add(new Target(switchTargetLine));
continue;
}
}
// now that we have seen all the switches, make all the links based on
// the instructions and corresponding tables
for (Map.Entry<String, CodeLineInterface> entry : switchInstructions
.entrySet()) {
String key = entry.getKey();
CodeLineInterface value = entry.getValue();
LinkedList<Target> targets = switchTablesNew.get(key);
for (Target t : targets) {
links.add(new Link(value, t.getTo()));
}
}
// return all the links
return links;
}
private LinkedList<Link> findLeadersNew() {
// all leaders of the BBs
LinkedList<Link> links = new LinkedList<Link>();
// list of BBs
// LinkedList<BasicBlock> blockList = new LinkedList<BasicBlock>();
// nur durchrutsch bedingungen
for (int currentLine = 0; currentLine < codeLines.size() - 1; currentLine++) {
CodeLineInterface cl = codeLines.get(currentLine);
Link link = new Link(cl, codeLines.get(currentLine + 1));
if (isLeaderNew(cl) && !links.contains(link)) {
links.add(link);
}
}
return links;
}
/**
* Get the unparsed parameters of this method. .method public constructor
* <init>(Landroid/content/Context;)V would return Landroid/content/Context;
*
* @return the parameter declaration
*/
public byte[] getParameters() {
return rawParameters;
}
/**
* Get the unparsed return value of this method. .method public constructor
* <init>(Landroid/content/Context;)V would return V.
*
* @return the return value
*/
public byte[] getReturnValue() {
return returnValue;
}
/**
* Get the first BB.
*
* @return the first BB or null if none is available
*/
public BasicBlockInterface getFirstBasicBlock() {
if (bbList.isEmpty())
return null;
return bbList.getFirst(); // firstBasicBlock;
}
public LinkedList<BasicBlockInterface> getBasicBlocks() {
return bbList;
}
public String getName() {
return name;
}
public LinkedList<FieldInterface> getLocalFields() {
if (localFieldList == null)
localFieldList = Field.parseAllFields(codeLines);
return localFieldList;
}
// /**
// * Check if the line is the end of a BB, so the next Line is a leader TODO:
// * rename
// *
// * @param line
// * @return
// */
// private boolean isLeader(CodeLineInterface cl) {
// if (cl.getInstruction().getType() == InstructionType.JMP
// || cl.getInstruction().getType() == InstructionType.GOTO
// || cl.getInstruction().getType() == InstructionType.SWITCH)
// return true;
// return false;
// }
/**
* Check if the line is the end of a BB, so the next Line is a leader TODO:
* rename
*
* @param line
* @return
*/
private boolean isLeaderNew(CodeLineInterface cl) {
if (cl.getInstruction().getType() == InstructionType.JMP
|| cl.getInstruction().getType() == InstructionType.SWITCH // für
// den
// default
// fall
// TODO:
// maybe
// remove
// and
// handle
// in
// switch
)
return true;
return false;
}
/**
* Check if a given instruction is within the methods code lines.
*
* TODO: methode einbauen die auch noch die enstprechenden CLS als
* linkedList zurueckgibt?
*
* @param instruction
* the instruction, or parts of it
* @return true if the instruction is found inside the instruction, comments
* etc are not searched.
*/
public boolean contains(byte[] instruction) {
for (CodeLineInterface cl : codeLines) {
if (cl.isCode() && cl.contains(instruction))
return true;
}
return false;
}
@Override
public String toString(){
return name+"("+getParameterString()+")"+getReturnValueString();
}
/**
* Can be used to print all CodeLines and some
* other data to stdout.
* @return the "contents" of the method
*/
public String dump() {
StringBuilder sb = new StringBuilder();
sb.append("Methodname: " + name);
sb.append("\nMethodtype: ");
sb.append(methodType);
sb.append("\n");
for (FieldInterface f : getLocalFields()) {
sb.append(f);
sb.append("\n");
}
sb.append("Code: ");
sb.append("\n");
for (CodeLineInterface cl : codeLines) {
// if (!cl.isCode()) continue;
sb.append(cl);
sb.append("\n");
}
return sb.toString();
}
private Float arithOpsAmount = null;
private boolean changed;
private boolean obfuscated;
private Entropy entropy;
/**
* Calculate the percentage of artithmetic operations in this function.
*
* @return
*/
public float arithOps() {
if (arithOpsAmount != null)
return arithOpsAmount;
// else
float arithOps = 0F;
float allOps = 0F;
for (CodeLineInterface cl : codeLines) {
// don't count comments and annotations
if (cl.isCode())
allOps++;
// arithmetic operations
InstructionInterface i = cl.getInstruction();
if (i.getType() == InstructionType.MATH_1
|| i.getType() == InstructionType.MATH_2
|| i.getType() == InstructionType.MATH_2C) {
arithOps++;
}
}
if (allOps == 0) {
arithOps = 0F;
} else {
arithOps = (arithOps / allOps);
}
arithOpsAmount = arithOps;
return arithOps;
}
public LinkedList<CodeLineInterface> getCodeLines() {
return codeLines;
}
/**
*
* @return the SMALI file this method belongs to
*/
public ClassInterface getSmaliClass() {
return smaliClass;
}
// ################# getter and setter ##################################
public boolean isStatic() {
return isStatic;
}
/**
* Return the class with full path, the method name and the (raw) parameters
* as byte arrays.
*
* @return [ classname, name, parameters ]
*/
public byte[][] getCmp() {
byte[][] cmp = new byte[3][];
cmp[0] = getSmaliClass().getFullClassName(false).getBytes();
cmp[1] = name.getBytes();
cmp[2] = rawParameters;
return cmp;
}
/**
* This gives the parameters in their short form.
* see: https://code.google.com/p/smali/wiki/TypesMethodsAndFields for more information
* @return the parameters of this method in their short letter form
*/
public String getParameterString(){
if(parameters == null) {
parameters = new String(this.getParameters());
}
return parameters;
}
/**
*
* @param hash
*/
public void setFuzzyHash(String hash) {
fuzzyHash = hash;
}
/**
* @return the fuzzy hash of this method
*/
public String getFuzzyHash() {
return fuzzyHash;
}
/**
* Get the unique label of this Method within a SmaliClass.
*
* @return
*/
public int getLabel() {
return label;
}
public String getUniqueLabel() {
StringBuffer sb = new StringBuffer();
sb.append(getSmaliClass().getUniqueId());
sb.append(',');
sb.append(label);
return sb.toString();
}
/**
* TODO and FIXME: convert the .method... line to a more java syntax, such
* as private void full-class-name.methodname(String s); Right now this
* method will only return the classpath and the .method name. It would be
* best to overwrite this method which will append the full-class-name and
* one method which does not do it (real java syntax)
*
* @return
*/
public String getReadableJavaName() {
return getSmaliClass().getFullClassName(true) + ": "
+ new String(getCodeLines().getFirst().getLine());
}
@Override
public int getId() {
return this.id;
}
@Override
public void setId(int id) {
this.id = id;
}
@Override
public void setEmpty(boolean isEmpty) {
// TODO Auto-generated method stub
}
@Override
public void setBasicBlocks(LinkedList<BasicBlockInterface> blocks) {
this.bbList = blocks;
setChanged(true);
}
@Override
public void setName(String name) {
this.name = name;
setChanged(true);
}
@Override
public void setLocalFields(LinkedList<FieldInterface> localFields) {
this.localFieldList = localFields;
setChanged(true);
}
@Override
public void setCodeLines(LinkedList<CodeLineInterface> lines) {
this.codeLines = lines;
setChanged(true);
}
@Override
public void setSmaliClass(ClassInterface smaliClass) {
this.smaliClass = smaliClass;
setChanged(true);
}
@Override
public void isStatic(boolean isStatic) {
this.isStatic = isStatic;
}
@Override
public void setParameterString(String params) {
throw new IllegalArgumentException("Params cannot be set as they are parsed from the smali file/class.");
}
@Override
public void setLabel(int label) {
this.label = label;
setChanged(true);
}
@Override
public void setUniqueLabel(String ulabel) {
// TODO Auto-generated method stub
}
@Override
public void setReadableJavaName(String javaName) {
// TODO Auto-generated method stub
}
@Override
public void setChanged(boolean changed) {
this.changed = changed;
}
@Override
public boolean isChanged() {
return this.changed;
}
@Override
public String getReturnValueString() {
if(returnValueString == null) {
returnValueString = new String(this.returnValue);
returnValueString = returnValueString.replaceAll("L.[^;]*;", "L");
}
return returnValueString;
}
@Override
public void setHasUnlinkedBlocks(boolean hasUnlinkedBBs) {
this.hasUnlinkedBBs = hasUnlinkedBBs;
}
@Override
public boolean hasUnlinkedBBs() {
return hasUnlinkedBBs;
}
@Override
public boolean isProbablyPatched() {
if (hasUnlinkedBBs) return true;
else {
for (BasicBlockInterface bb : getBasicBlocks()) {
if (bb.hasDeadCode()) return true;
}
}
return false;
}
@Override
public void setObfuscated(boolean b) {
this.obfuscated=b;
}
@Override
public boolean isObfuscated() {
return this.obfuscated;
}
@Override
public void setEntropy(Entropy entropy) {
this.entropy=entropy;
}
@Override
public Entropy getEntropy() {
return this.entropy;
}
}