/*
* PDF Scrutinizer, a library for detecting and analyzing malicious PDF documents.
* Copyright 2013 Florian Schmitt <florian@florianschmitt.de>, Fraunhofer FKIE
*
* 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.pdf_scrutinizer.emulation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.log4j.NDC;
import org.apache.pdfbox.pdmodel.PDDocumentInformation;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.ContextAction;
import org.mozilla.javascript.EcmaError;
import org.mozilla.javascript.EvaluatorException;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.ScriptableNativeJavaObject;
import org.mozilla.javascript.ScriptableObject;
import de.pdf_scrutinizer.Scrutinizer;
import de.pdf_scrutinizer.Scrutinizer.Intensity;
import de.pdf_scrutinizer.data.AnalysisResult.Classification;
import de.pdf_scrutinizer.API.App;
import de.pdf_scrutinizer.API.Collab;
import de.pdf_scrutinizer.API.Event;
import de.pdf_scrutinizer.API.Spell;
import de.pdf_scrutinizer.API.Timeout;
import de.pdf_scrutinizer.API.Util;
import de.pdf_scrutinizer.API.XMLData;
import de.pdf_scrutinizer.API.display;
import de.pdf_scrutinizer.API.app.Doc;
import de.pdf_scrutinizer.API.app.plugIn;
import de.pdf_scrutinizer.API.app.doc.Media;
public abstract class InterpreterEmulation {
protected Log log = LogFactory.getLog(InterpreterEmulation.class);
protected Doc document;
protected Scrutinizer scrutinizer;
String viewerversions[] = {"8", "9"};
public void execute(final String code) {
List<String> codes = new ArrayList<String>();
codes.add(code);
execute(codes);
}
// ScriptableNativeJavaObject is needed for app object (and any JavaToJS-converted object) to accept dynamically added properties
public void execute(final List<String> code) {
if (document == null) {
log.error("doc object of InterpreterEmulation is not initialized");
return;
}
for (String x : viewerversions) {
new ScriptableNativeJavaObject.ScriptableNativeContextFactory().call(generateContextAction(x, "5.13", code));
if (scrutinizer.getAnalysisResult().getClassification() == Classification.malicious) return;
}
// if the document is not detected as malicious
// try another version of the first plugin
if (!(scrutinizer.getAnalysisResult().getClassification() == Classification.malicious)) {
for (String x : viewerversions) {
new ScriptableNativeJavaObject.ScriptableNativeContextFactory().call(generateContextAction(x, "8.13", code));
if (scrutinizer.getAnalysisResult().getClassification() == Classification.malicious) return;
}
}
}
protected ContextAction generateContextAction(final String viewerVersion, final String pluginVersion, final List<String> codes) {
ContextAction action = new ContextAction() {
public Object run(Context cx) {
log.info("initializing Rhino JavaScript engine...");
cx.setOptimizationLevel(-1); // Interpreter mode
Doc doc = document; //global 'this'-object
//define methods of doc object
String[] names = {
"getPageNumWords",
"getPageNthWord",
"selectPageNthWord",
"syncAnnotScan",
"getAnnots",
"getAnnot",
"getField",
"getURL",
"isBoxChecked",
"printSeps",
"breakpoint", //dummy
};
//TODO: problem: no overloading allowed
doc.defineFunctionProperties(names, Doc.class, ScriptableObject.DONTENUM);
Scriptable scope = cx.initStandardObjects(doc);
cx.putThreadLocal("topscope", scope);
cx.putThreadLocal("scrutinizer", scrutinizer);
//set up API
App app = new App(scrutinizer, doc);
ScriptableObject.putProperty(scope, "app", Context.javaToJS(app, scope));
ScriptableObject.putProperty(scope, "Collab", Context.javaToJS(new Collab(scrutinizer), scope));
ScriptableObject.putProperty(scope, "util", Context.javaToJS(new Util(scrutinizer), scope));
ScriptableObject.putProperty(scope, "event", Context.javaToJS(new Event(scrutinizer, doc), scope));
ScriptableObject.putProperty(scope, "media", Context.javaToJS(new Media(scrutinizer), scope));
ScriptableObject.putProperty(scope, "XMLData", Context.javaToJS(new XMLData(scrutinizer), scope));
ScriptableObject.putProperty(scope, "display", Context.javaToJS(new display(), scope));
ScriptableObject.putProperty(scope, "console", Context.javaToJS(doc.console, scope));
ScriptableObject.putProperty(scope, "info", Context.javaToJS(doc.info, scope));
ScriptableObject.putProperty(scope, "spell", Context.javaToJS(new Spell(scrutinizer), scope));
// redirect String.eval() and app.eval() to eval()
ScriptableObject.putProperty((Scriptable) scope.get("String", scope), "eval", (Scriptable) scope.get("eval", scope));
ScriptableObject.putProperty((Scriptable) scope.get("app", scope), "eval", (Scriptable) scope.get("eval", scope));
/* these are deprecated but still used in malicious samples */
ScriptableObject.putProperty(scope, "subject", Context.javaToJS(doc.info.Subject, scope));
ScriptableObject.putProperty(scope, "producer", Context.javaToJS(doc.info.Producer, scope));
ScriptableObject.putProperty(scope, "creator", Context.javaToJS(doc.info.Creator, scope));
ScriptableObject.putProperty(scope, "author", Context.javaToJS(doc.info.Author, scope));
ScriptableObject.putProperty(scope, "keywords", Context.javaToJS(doc.info.Keywords, scope));
ScriptableObject.putProperty(scope, "creationDate", Context.javaToJS(doc.info.CreationDate, scope));
ScriptableObject.putProperty(scope, "modDate", Context.javaToJS(doc.info.ModDate, scope));
ScriptableObject.putProperty(scope, "title", Context.javaToJS(doc.info.Title, scope));
ScriptableObject.putProperty(scope, "zoom", Context.javaToJS(doc.zoom, scope));
ScriptableObject.putProperty(scope, "URL", Context.javaToJS(doc.URL, scope));
ScriptableObject.putProperty(scope, "numPages", Context.javaToJS(doc.numPages, scope));
// if document catalog contains custom metadata we have to put them into the context.
if (scrutinizer.getDocumentAdapter() != null) {
PDDocumentInformation info = scrutinizer.getDocumentAdapter().getDocument().getDocumentInformation();
if (info != null) {
String[] docinfokeys = {"Author", "Creator", "CreationDate", "Subject", "Title", "Keywords", "ModDate", "Producer", "Trapped"};
Set<String> metadataKeys = info.getMetadataKeys();
metadataKeys.removeAll(Arrays.asList(docinfokeys)); // remove non-custom keys
for (String metadataKey : metadataKeys) {
log.debug(String.format("adding custom info metadata: %s value: %s", "info." + metadataKey, info.getCustomMetadataValue(metadataKey)));
ScriptableObject.putProperty((Scriptable) scope.get("info", scope), metadataKey, Context.javaToJS(info.getCustomMetadataValue(metadataKey), scope));
}
}
}
app.viewerVersion = Double.parseDouble(viewerVersion);
plugIn plugin = app.getPlugin();
plugin.version = Double.parseDouble(pluginVersion);
app.setPlugin(plugin);
log.info("using viewerVersion " + app.viewerVersion);
for (String str : codes) {
doExecute(str, cx, scope);
}
List<Timeout> timeouts = app.getTimeouts();
synchronized (timeouts) {
for (Timeout timeout : timeouts) {
timeout.stop();
}
}
return null;
}
};
return action;
}
private void doExecute(final String code, final Context cx, final Scriptable scope) {
String oneLineCode = code.trim().replace("\\s", " ").replace("\r\n", " ").replace("\n", " ");
if (oneLineCode.length() > 50) {
log.info("code peek: \"" + oneLineCode.substring(0, 50) + "...\"");
} else {
log.info("code peek: \"" + oneLineCode + "\"");
}
scrutinizer.getStaticAnalysis().doit(code);
if (scrutinizer.getIntensity() == Intensity.lousy) {
// if already classified as malicious do not execute the code.
if (scrutinizer.getAnalysisResult().getClassification() == Classification.malicious) {
return;
}
}
log.info("starting execution");
NDC.push("[EXE]");
try {
cx.evaluateString(scope, code, "InterpreterEmulation.doExecute", 0, null);
} catch (EvaluatorException e) {
scrutinizer.getAnalysisResult().addException(e);
} catch (EcmaError e) {
scrutinizer.getAnalysisResult().addException(e);
} finally {
NDC.pop();
}
}
}