/* 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.analysis.steps.cfg;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.StringTokenizer;
import java.util.Vector;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.swing.ImageIcon;
import com.mxgraph.layout.hierarchical.mxHierarchicalLayout;
import com.mxgraph.model.mxCell;
import com.mxgraph.swing.mxGraphComponent;
import com.mxgraph.swing.util.mxCellOverlay;
import com.mxgraph.util.mxConstants;
import com.mxgraph.view.mxGraph;
import de.rub.syssec.saaf.misc.Highlight;
import de.rub.syssec.saaf.model.APICall;
import de.rub.syssec.saaf.model.application.BasicBlockInterface;
import de.rub.syssec.saaf.model.application.CodeLineInterface;
import de.rub.syssec.saaf.model.application.MethodInterface;
public class CFGGraph {
MethodInterface method;
mxGraph graph;
mxGraphComponent graphComponent;
HashMap<Integer, Object> vertices;
HashMap<mxCell, BasicBlockInterface> cells;
HashMap<BasicBlockInterface, String> overlays;
ImageIcon permissionIcon;
public CFGGraph (MethodInterface m){
this(m, false);
}
public CFGGraph (MethodInterface m, boolean showAPICallasDOTcomment){
method = m;
vertices = new HashMap<Integer,Object>();
cells = new HashMap<mxCell,BasicBlockInterface>();
overlays = new HashMap<BasicBlockInterface, String>();
graph = new mxGraph();
Object parent = graph.getDefaultParent();
graph.getModel().beginUpdate();
graph.setHtmlLabels(true);
graph.setCellsDisconnectable(false);
URL imageURL = getClass().getResource("/images/permission.png");
permissionIcon = new ImageIcon(imageURL);
HashMap<CodeLineInterface, APICall> matches = method.getSmaliClass().getApplication().getMatchedCalls();
try {
graph.setAllowDanglingEdges(false);
graph.setAutoSizeCells(true);
graph.setCellsEditable(false);
Collection<mxCell> c = new ArrayList<mxCell>();
for(BasicBlockInterface bb: method.getBasicBlocks()){
c.clear();
Object startVertex = null;
int lineNr = bb.getCodeLines().getFirst().getLineNr();
if(vertices.containsKey(lineNr)){
startVertex = vertices.get(lineNr);
} else {
startVertex = graph.insertVertex(parent, null, generateBasicBlockString(
showAPICallasDOTcomment, matches, bb), 0, 0, 80, 30);
vertices.put(lineNr,startVertex);
}
graph.updateCellSize(startVertex);
for( BasicBlockInterface target: bb.getNextBB()){
Object targetVertex = null;
lineNr = target.getCodeLines().getFirst().getLineNr();
if(vertices.containsKey(lineNr)){
targetVertex = vertices.get(lineNr);
} else {
targetVertex = graph.insertVertex(parent, null, generateBasicBlockString(
showAPICallasDOTcomment, matches, target), 0, 0, 80, 30);
vertices.put(lineNr,targetVertex);
graph.updateCellSize(targetVertex);
}
graph.insertEdge(parent, null, "", startVertex, targetVertex);
}
}
graph.setCellStyles(mxConstants.STYLE_ALIGN, mxConstants.ALIGN_LEFT, vertices.values().toArray() );
}
finally {
graph.getModel().endUpdate();
}
graphComponent = new mxGraphComponent(graph);
for(CodeLineInterface line: matches.keySet()){
if(line.getMethod().equals(method)){
//TODO: maybe add a line.getBB method or a method.getBBforLine
for(BasicBlockInterface bb: method.getBasicBlocks()){
if(bb.containsLineNr(line.getLineNr())){
StringBuilder currentOverlayText = new StringBuilder();
currentOverlayText.append("found APICall match in line: ");
currentOverlayText.append(line.getNrAndLine());
currentOverlayText.append("<br>");
currentOverlayText.append(matches.get(line).getPermissionString());
currentOverlayText.append("<br>");
currentOverlayText.append(matches.get(line).getDescription());
//if a bb contains multiple apicalls
if(overlays.containsKey(bb)){
StringBuilder oldOverlayText = new StringBuilder();
oldOverlayText.append(overlays.get(bb));
oldOverlayText.append("<br><br>");
oldOverlayText.append(currentOverlayText);
overlays.put(bb, oldOverlayText.toString());
}else{
overlays.put(bb, currentOverlayText.toString());
}
StringBuilder overlayText = new StringBuilder();
overlayText.append("<html>");
overlayText.append(overlays.get(bb));
overlayText.append("</html>");
//TODO: fix imageicon location
mxCellOverlay overlay = new mxCellOverlay(permissionIcon, overlayText.toString());
//if new overlay, add it, if an old one exists replace it
graphComponent.addCellOverlay(vertices.get(bb.getCodeLines().getFirst().getLineNr()), overlay);
// mxCell cell = (mxCell)(vertices.get(bb.getCodeLines().getFirst().getLineNr()));
// cell.setStyle("fillColor=red");
// graph.setCellStyle("fillColor=blue", new Object[]{vertices.get(bb.getCodeLines().getFirst().getLineNr())});
graph.setCellStyles(mxConstants.STYLE_FILLCOLOR, "gray", new Object[]{vertices.get(bb.getCodeLines().getFirst().getLineNr())});
}
}
}
}
graphComponent.scrollCellToVisible(graph.getDefaultParent());
// graphComponent.setToolTips(true);
mxHierarchicalLayout layout = new mxHierarchicalLayout(graph);
layout.setDisableEdgeStyle(false);
layout.execute(graph.getDefaultParent());
}
private String generateBasicBlockString(boolean showAPICallasDOTcomment,
HashMap<CodeLineInterface, APICall> matches, BasicBlockInterface bb) {
String highlighted="";
if(showAPICallasDOTcomment){
ArrayList<CodeLineInterface> callLines = new ArrayList<CodeLineInterface>();
//TODO: Change this, this code is in very similar form used later on to find positions for overlays
for(CodeLineInterface line: matches.keySet()){
if(line.getMethod().equals(method)){
//This BB contains at least one APICall, so we have to build the string ourselfs
if(bb.containsLineNr(line.getLineNr())){
callLines.add(line);
}
}
}
StringBuilder out = new StringBuilder();
for(CodeLineInterface currentLine: bb.getCodeLines()){
if(callLines.contains(currentLine)){
// out.append(".");
// out.append("found APICall match in line: ");
// out.append(currentLine.getNrAndLine());
// out.append("\n");
out.append(".");
out.append(matches.get(currentLine).getPermissionString());
out.append("\n");
out.append(".");
out.append(matches.get(currentLine).getDescription());
out.append("\n");
}
out.append(currentLine.getNrAndLine());
out.append("\n");
}
highlighted = highlight(out.toString());
} else {
highlighted = highlight(bb.toString());
}
return highlighted;
}
public mxGraph getGraph(){
return graph;
}
public mxGraphComponent getGraphComponent(){
return graphComponent;
}
private String highlight(String zeile){
Pattern p = Pattern.compile("[vp][0-9]+");
// commands
Vector<String> commands = Highlight.OP_CODES;
for (String cmd : commands) {
zeile = zeile.replace(cmd, "<FONT COLOR=\"red\">" + cmd
+ "</FONT> ");
}
StringTokenizer st = new StringTokenizer(zeile, " ,}{", true);
String reg = new String();
while (st.hasMoreTokens()) {
String temp = st.nextToken();
Matcher m = p.matcher(temp);
if (m.matches())
reg += "<FONT COLOR=\"blue\">" + temp + "</FONT>";
else
reg += temp;
}
zeile = reg;
// replace
zeile = zeile.trim().replace("->",
"<FONT COLOR=\"blue\">-></FONT>");
// jumps
Vector<String> jumps = new Vector<String>();
jumps.add(":cond_");
jumps.add(":goto_");
jumps.add(":catch_");
jumps.add(":catchall_");
for (int i = 0; i < jumps.size(); i++) {
for (int z = 100; z >= 0; z--) {
zeile = zeile.trim().replace(
jumps.get(i) + z + "",
"<FONT COLOR=\"#009900\">" + jumps.get(i) + z
+ "</FONT>");
}
}
// Annotations
if (zeile.trim().startsWith(".") || zeile.trim().startsWith("#")
|| zeile.trim().startsWith(":")) {
zeile = "<FONT COLOR=\"#5F5F5F\">" + zeile + "</FONT>";
}
return zeile;
}
}