params) {
88 | logger.info("Executing C emulator plugin");
89 |
90 | IRuntimeProject prj = context.getProject(0);
91 | ILiveArtifact a = prj.getLiveArtifact(0);
92 | IUnit topLevelUnit = a.getMainUnit();
93 |
94 | INativeCodeUnit> codeUnit = (INativeCodeUnit>)UnitUtil
95 | .findDescendantsByType(topLevelUnit, INativeCodeUnit.class, false).get(0);
96 | logger.info("Code unit: %s", codeUnit);
97 |
98 | if(!codeUnit.process()) {
99 | logger.info("ERROR: Failed to process code unit");
100 | return;
101 | }
102 |
103 | INativeDecompilerUnit> decomp = (INativeDecompilerUnit>)DecompilerHelper.getDecompiler(codeUnit);
104 | logger.info("Decompiler: %s", decomp);
105 |
106 | parseParameters(params);
107 | if(firstRtnAddress == null) {
108 | throw new JebRuntimeException("ERROR: address of routine to emulate is undefined");
109 | }
110 |
111 | // initial emulator state
112 | EmulatorState emulatorState;
113 | if(stackDump != null && heapDump != null) {
114 | emulatorState = new EmulatorState(codeUnit, stackDump, heapDump);
115 | emulatorState.setRegisterValue(SimpleCEmulator.REG_RBP_ID, stackDump.basePointer);
116 | }
117 | else {
118 | emulatorState = new EmulatorState(codeUnit);
119 | emulatorState.setRegisterValue(SimpleCEmulator.REG_RBP_ID, 0x7fffffffdf90L); //dummy value
120 | }
121 | emulatorState.allocateStackSpace();
122 |
123 | SimpleCEmulator emulator = marsAnalyticaMode ? new MarsAnalyticaCEmulator(): new SimpleCEmulator();
124 |
125 | // analyze first handler
126 | Long handlerAddress = firstRtnAddress;
127 | ICMethod handlerMethod = disassembleAndDecompile(decomp, handlerAddress);
128 |
129 | // tracing loop
130 | while(true) {
131 | logger.info("> emulating method %s...", handlerMethod.getName());
132 |
133 | // emulate handler
134 | EmulatorLog log = emulator.emulate(handlerMethod, emulatorState);
135 | emulatorState = log.getCurrentEmulatorState();
136 |
137 | // get next handler address
138 | handlerAddress = emulatorState.getRegisterValue(SimpleCEmulator.REG_NEXT_METHOD_ID);
139 | if(handlerAddress == null) {
140 | logger.info(" >> STOP: no next entry-point address found");
141 | break;
142 | }
143 |
144 | emulator.dumpLog(logFile);
145 |
146 | if(!tracerMode) {
147 | break;
148 | }
149 |
150 | logger.info(" >> done; found next method entry point to emulate: 0x%08x", handlerAddress);
151 | handlerMethod = disassembleAndDecompile(decomp, handlerAddress);
152 | }
153 | }
154 |
155 | /**
156 | * Disassemble and decompile (or use the existing decompiled unit).
157 | *
158 | * @param decomp
159 | * @param methodAddress
160 | * @return decompiled method
161 | */
162 | private ICMethod disassembleAndDecompile(INativeDecompilerUnit> decomp, long methodAddress) {
163 | // disassemble, if needed
164 | if(!decomp.getCodeUnit().getCodeModel().isRoutineHeader(methodAddress)) {
165 | EntryPointDescription nextHandlerEPD = decomp.getCodeUnit().getProcessor().createEntryPoint(methodAddress);
166 | decomp.getCodeUnit().getCodeAnalyzer().enqueuePointerForAnalysis(nextHandlerEPD);
167 | decomp.getCodeUnit().getCodeAnalyzer().analyze();
168 | }
169 |
170 | // decompile, if needed
171 | String decompUnitId = Strings.ff("%x", methodAddress);
172 | return (ICMethod)((INativeSourceUnit)decomp.decompile(decompUnitId)).getASTItem();
173 | }
174 |
175 | @Override
176 | public IPluginInformation getPluginInformation() {
177 | return new PluginInformation("CEmulator", "Plugin to emulate JEB's decompiled C code",
178 | "Joan Calvet (PNF Software)", Version.create(1, 0, 0));
179 | }
180 |
181 | @Override
182 | public List extends IOptionDefinition> getExecutionOptionDefinitions() {
183 | return Arrays.asList(new OptionDefinition("FirstRtnAddr", "Address of routine to emulate"),
184 | new BooleanOptionDefinition(
185 | "TracerMode", true,
186 | "Tracer mode enabled (emulator follows subroutine calls -- until it cannot anymore)"),
187 | new BooleanOptionDefinition(
188 | "MarsAnalyticaMode", true,
189 | "MarsAnalytica's specific logic enabled"),
190 | new OptionDefinition("LogFilePath",
191 | "Path to log file (optional -- if unspecified logs will be written as a sub unit in JEB project)"));
192 | }
193 | }
194 |
--------------------------------------------------------------------------------
/src/com/pnf/plugin/cemulator/CFG.java:
--------------------------------------------------------------------------------
1 | package com.pnf.plugin.cemulator;
2 |
3 | import java.util.ArrayList;
4 | import java.util.IdentityHashMap;
5 | import java.util.List;
6 | import java.util.Map;
7 |
8 | import com.pnfsoftware.jeb.core.units.code.asm.decompiler.ast.ICBlock;
9 | import com.pnfsoftware.jeb.core.units.code.asm.decompiler.ast.ICBreak;
10 | import com.pnfsoftware.jeb.core.units.code.asm.decompiler.ast.ICCompound;
11 | import com.pnfsoftware.jeb.core.units.code.asm.decompiler.ast.ICContinue;
12 | import com.pnfsoftware.jeb.core.units.code.asm.decompiler.ast.ICControlBreaker;
13 | import com.pnfsoftware.jeb.core.units.code.asm.decompiler.ast.ICForStm;
14 | import com.pnfsoftware.jeb.core.units.code.asm.decompiler.ast.ICGoto;
15 | import com.pnfsoftware.jeb.core.units.code.asm.decompiler.ast.ICIfStm;
16 | import com.pnfsoftware.jeb.core.units.code.asm.decompiler.ast.ICJumpFar;
17 | import com.pnfsoftware.jeb.core.units.code.asm.decompiler.ast.ICMethod;
18 | import com.pnfsoftware.jeb.core.units.code.asm.decompiler.ast.ICReturn;
19 | import com.pnfsoftware.jeb.core.units.code.asm.decompiler.ast.ICStatement;
20 | import com.pnfsoftware.jeb.core.units.code.asm.decompiler.ast.ICWhileStm;
21 | import com.pnfsoftware.jeb.util.format.Strings;
22 |
23 | /**
24 | * Control-Flow Graph (CFG) representing the control-flow of an {@link ICMethod}.
25 | *
26 | * This class is a minimal implementation to be used by {@link SimpleCEmulator}. The computed
27 | * graph only provides the reachable statements from each statement (through
28 | * {@link #getNextStatement(ICStatement)} and {@link #getNthNextStatement(ICStatement, int)}).
29 | *
30 | * Known limitations/simplifications:
31 | *
32 | * - statements are assumed to be unique within the method, and serves to identify graph nodes
33 | * (i.e. no basic blocks)
34 | *
- for compound statements (see {@link ICCompound}), the embedded {@link ICBlock} are kept in
35 | * the CFG and transfer control to their first statement
36 | *
- do-while loops are not specifically handled; they are ordered as while-loops
37 | *
- switch-case are not handled
38 | *
- predicates are not stored on their corresponding edges; it's the client responsibility to
39 | * retrieve them
40 | *
- graph exit(s) are represented by 'null' nodes
41 | *
42 | *
43 | * @author Joan Calvet
44 | *
45 | */
46 | public class CFG {
47 |
48 | /**
49 | * Reachable statements (from -> to)
50 | *
51 | * Implementation note: if there are several reachable statements, the first statement is the
52 | * fallthrough (see {@link #getNextStatement(ICStatement)}). Others statements correspond to
53 | * conditional branches (see {@link #getNthNextStatement(ICStatement, int)}.
54 | */
55 | private Map> outEdges = new IdentityHashMap<>();
56 |
57 | /**
58 | * CFG's entry point
59 | */
60 | private ICStatement entryPoint;
61 |
62 | /**
63 | * Process method's AST and build the corresponding CFG.
64 | *
65 | * @param method
66 | * @return cfg of the method
67 | */
68 | public static CFG buildCFG(ICMethod method) {
69 | CFG cfg = new CFG();
70 | buildCFGRecursive(cfg, method.getBody(), null, null);
71 | cfg.entryPoint = method.getBody().isEmpty() ? null: method.getBody().get(0);
72 | return cfg;
73 | }
74 |
75 | /**
76 | * Build a CFG by recursively processing {@link ICBlock}.
77 | *
78 | * @param cfg
79 | * @param currentBlock the block to process
80 | * @param parentStatement the statement containing the block in the AST, null if none
81 | * @param parentLoop the loop containing the block in the AST, null if none
82 | */
83 | private static void buildCFGRecursive(CFG cfg, ICBlock currentBlock, ICStatement parentStatement,
84 | ICStatement parentLoop) {
85 | int index = 0;
86 | while(index < currentBlock.size()) {
87 | ICStatement curStatement = currentBlock.get(index);
88 | ICStatement parentLoop_ = parentLoop;
89 |
90 | // add default fallthrough edge on all statements
91 | // (only if not already set)
92 | ICStatement ftStatement = null;
93 | if(index < currentBlock.size() - 1) {
94 | ftStatement = currentBlock.get(index + 1);
95 | }
96 | else {
97 | // last element of the block: fallthrough is parent's fallthrough
98 | ftStatement = cfg.getNextStatement(parentStatement);
99 | }
100 | cfg.setFallThrough(curStatement, ftStatement, false);
101 |
102 | if(curStatement instanceof ICGoto) {
103 | cfg.setFallThrough(curStatement, ((ICGoto)curStatement).getLabel());
104 | }
105 | else if(curStatement instanceof ICWhileStm || curStatement instanceof ICForStm) {
106 | parentLoop_ = curStatement;
107 | ICBlock loopBody = ((ICCompound)curStatement).getBlocks().get(0);
108 | cfg.addConditionalTarget(curStatement, loopBody);
109 |
110 | // set backward edge
111 | if(loopBody.isEmpty()) {
112 | cfg.setFallThrough(loopBody, curStatement);
113 | }
114 | else {
115 | cfg.setFallThrough(loopBody.getLast(), curStatement);
116 | }
117 | }
118 | else if(curStatement instanceof ICIfStm) {
119 | // for each branch, add an edge
120 | List extends ICBlock> ifBlocks = ((ICIfStm)curStatement).getBlocks();
121 | for(int i = 0; i < ((ICIfStm)curStatement).size(); i++) {
122 | cfg.addConditionalTarget(curStatement, ifBlocks.get(i));
123 | }
124 | }
125 | else if(curStatement instanceof ICReturn || curStatement instanceof ICJumpFar) {
126 | // end of method
127 | cfg.setFallThrough(curStatement, null);
128 | }
129 | else if(curStatement instanceof ICControlBreaker) {
130 | if(((ICControlBreaker)curStatement).getLabel() != null) {
131 | cfg.setFallThrough(curStatement, ((ICControlBreaker)curStatement).getLabel());
132 | }
133 | else {
134 | if(curStatement instanceof ICBreak) {
135 | // goto parent loop's fallthrough
136 | cfg.setFallThrough(curStatement, cfg.getNextStatement(parentLoop_));
137 | }
138 | else if(curStatement instanceof ICContinue) {
139 | // goto parent loop
140 | cfg.setFallThrough(curStatement, parentLoop_);
141 | }
142 | }
143 | }
144 | else if(curStatement instanceof ICBlock) {
145 | cfg.setFallThrough(curStatement, ((ICBlock)curStatement).get(0));
146 | }
147 |
148 | // recursion
149 | if(curStatement instanceof ICCompound) {
150 | for(ICBlock subBlock: ((ICCompound)curStatement).getBlocks()) {
151 | if(!subBlock.isEmpty()) {
152 | cfg.setFallThrough(subBlock, subBlock.get(0));
153 | }
154 | buildCFGRecursive(cfg, subBlock, curStatement, parentLoop_);
155 | }
156 | }
157 |
158 | index += 1;
159 | }
160 | }
161 |
162 |
163 | /**
164 | * Get the statement reachable from the given statement, when its predicate is true. For
165 | * multi-predicates statement, see {@link #getNthNextStatement(ICStatement, int)}.
166 | *
167 | * @param from
168 | * @return statement conditionally reachable, null if none
169 | */
170 | public ICStatement getNextTrueStatement(ICStatement from) {
171 | return outEdges.get(from) != null && outEdges.get(from).size() > 1 ? outEdges.get(from).get(1)
172 | : null;
173 | }
174 |
175 | /**
176 | * Get the statement reachable from the given statement, when its n-th predicate is true. This
177 | * method is the preferred one for multi-predicate statements (if-elseif-..), and the reachable
178 | * statements are ordered as they are in their container statement.
179 | *
180 | * @param from
181 | * @param n
182 | * @return the n-th conditionally reachable statement, null if none
183 | */
184 | public ICStatement getNthNextStatement(ICStatement from, int n) {
185 | return outEdges.get(from) != null && outEdges.get(from).size() > n + 1
186 | ? outEdges.get(from).get(n + 1): null;
187 | }
188 |
189 | /**
190 | * Get next statement reachable from the given statement, defined as:
191 | *
192 | *
193 | * - for unconditional statements: the next reachable statement (eg, target of goto statement
194 | * or next statement after an assignment)
195 | *
- for conditional statements: the statement reachable when predicates are false
196 | *
197 | *
198 | * @param from
199 | * @return first next possible statement, might be null
200 | */
201 | public ICStatement getNextStatement(ICStatement from) {
202 | return outEdges.get(from) != null ? outEdges.get(from).get(0): null;
203 | }
204 |
205 | private boolean setFallThrough(ICStatement from, ICStatement to) {
206 | return setFallThrough(from, to, true);
207 | }
208 |
209 | private boolean setFallThrough(ICStatement from, ICStatement to, boolean eraseExisting) {
210 | List nexts = outEdges.get(from);
211 | if(nexts == null) {
212 | nexts = new ArrayList<>();
213 | outEdges.put(from, nexts);
214 | }
215 | if(eraseExisting && nexts.size() >= 1) {
216 | nexts.set(0, to);
217 | return true;
218 | }
219 | else if(nexts.isEmpty()) {
220 | nexts.add(to);
221 | return true;
222 | }
223 | return false;
224 | }
225 |
226 | /**
227 | * Add a new conditional reachable statement.
228 | *
229 | * Important: this method assumes the fallthrough statement has already been set.
230 | *
231 | * @param from
232 | * @param to
233 | * @return
234 | */
235 | private boolean addConditionalTarget(ICStatement from, ICStatement to) {
236 | List nexts = outEdges.get(from);
237 | if(nexts == null) {
238 | nexts = new ArrayList<>();
239 | outEdges.put(from, nexts);
240 | }
241 | nexts.add(to);
242 | return true;
243 | }
244 |
245 | /**
246 | * Get CFG entry point.
247 | *
248 | * @return cfg entry point, might be null
249 | */
250 | public ICStatement getEntryPoint() {
251 | return entryPoint;
252 | }
253 |
254 | public void setEntryPoint(ICStatement entryPoint) {
255 | this.entryPoint = entryPoint;
256 | }
257 |
258 | @Override
259 | public String toString() {
260 | StringBuilder sb = new StringBuilder();
261 | for(ICStatement from: outEdges.keySet()) {
262 | sb.append("--edge--");
263 | sb.append(Strings.LINESEP);
264 | sb.append("> from:");
265 | sb.append(from.getClass().getSimpleName().toString());
266 | sb.append(Strings.LINESEP);
267 | sb.append(from);
268 | sb.append(Strings.LINESEP);
269 | for(ICStatement to: outEdges.get(from)) {
270 | if(to == null) {
271 | sb.append("> to:EXIT");
272 | sb.append(Strings.LINESEP);
273 | }
274 | else {
275 | sb.append("> to:");
276 | sb.append(to.getClass().getSimpleName().toString());
277 | sb.append(Strings.LINESEP);
278 | sb.append(to);
279 | sb.append(Strings.LINESEP);
280 | }
281 | }
282 | }
283 | return sb.toString();
284 | }
285 | }
286 |
--------------------------------------------------------------------------------
/src/com/pnf/plugin/cemulator/EmulatorException.java:
--------------------------------------------------------------------------------
1 | package com.pnf.plugin.cemulator;
2 |
3 | import com.pnfsoftware.jeb.core.exceptions.JebRuntimeException;
4 |
5 | /**
6 | * Exception for {@link SimpleCEmulator}
7 | *
8 | * @author Joan Calvet
9 | *
10 | */
11 | public class EmulatorException extends JebRuntimeException {
12 | private static final long serialVersionUID = 1L;
13 |
14 | public EmulatorException() {
15 | super();
16 | }
17 |
18 | public EmulatorException(String message) {
19 | super(message);
20 | }
21 |
22 | public EmulatorException(Throwable cause) {
23 | super(cause);
24 | }
25 |
26 | public EmulatorException(String message, Throwable cause) {
27 | super(message, cause);
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/com/pnf/plugin/cemulator/EmulatorLog.java:
--------------------------------------------------------------------------------
1 | package com.pnf.plugin.cemulator;
2 |
3 | import java.util.ArrayList;
4 | import java.util.List;
5 |
6 | import com.pnfsoftware.jeb.core.units.code.asm.decompiler.ast.ICStatement;
7 |
8 | /**
9 | * Log of a method emulation done by {@link SimpleCEmulator}. Provides access to the execution trace
10 | * and the current emulator state.
11 | *
12 | * @author Joan Calvet
13 | *
14 | */
15 | public class EmulatorLog {
16 |
17 | private List executionTrace = new ArrayList<>();
18 | private EmulatorState currentState;
19 |
20 | public void addExecutedStatement(ICStatement stmt) {
21 | executionTrace.add(stmt.toString());
22 | }
23 |
24 | /**
25 | * Get the list of string representations of the executed statements
26 | */
27 | public List getExecutionTrace() {
28 | return executionTrace;
29 | }
30 |
31 | public void setEmulatorState(EmulatorState state) {
32 | currentState = state;
33 | }
34 |
35 | public EmulatorState getCurrentEmulatorState() {
36 | return currentState;
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/src/com/pnf/plugin/cemulator/EmulatorState.java:
--------------------------------------------------------------------------------
1 | package com.pnf.plugin.cemulator;
2 |
3 |
4 | import java.io.File;
5 | import java.io.IOException;
6 | import java.nio.file.Files;
7 | import java.util.HashMap;
8 | import java.util.Map;
9 |
10 | import com.pnfsoftware.jeb.core.exceptions.JebRuntimeException;
11 | import com.pnfsoftware.jeb.core.units.INativeCodeUnit;
12 | import com.pnfsoftware.jeb.core.units.code.asm.decompiler.ast.CIdentifierClass;
13 | import com.pnfsoftware.jeb.core.units.code.asm.decompiler.ast.ICDecl;
14 | import com.pnfsoftware.jeb.core.units.code.asm.decompiler.ast.ICElement;
15 | import com.pnfsoftware.jeb.core.units.code.asm.decompiler.ast.ICIdentifier;
16 | import com.pnfsoftware.jeb.core.units.code.asm.decompiler.ast.ICType;
17 | import com.pnfsoftware.jeb.core.units.code.asm.memory.IVirtualMemory;
18 | import com.pnfsoftware.jeb.core.units.code.asm.memory.MemoryException;
19 | import com.pnfsoftware.jeb.core.units.code.asm.memory.VirtualMemoryUtil;
20 | import com.pnfsoftware.jeb.core.units.code.asm.type.INativeType;
21 | import com.pnfsoftware.jeb.core.units.code.asm.type.ITypeManager;
22 | import com.pnfsoftware.jeb.util.base.Assert;
23 | import com.pnfsoftware.jeb.util.format.Strings;
24 | import com.pnfsoftware.jeb.util.logging.GlobalLog;
25 | import com.pnfsoftware.jeb.util.logging.ILogger;
26 |
27 | /**
28 | * State of the emulator (memory + registers)
29 | *
30 | * @author Joan Calvet
31 | *
32 | */
33 | public class EmulatorState {
34 | private static final ILogger logger = GlobalLog.getLogger(EmulatorState.class);
35 |
36 | private IVirtualMemory memory;
37 | private Map registers = new HashMap<>(); // id -> value
38 |
39 | /** default pointer size, in bytes */
40 | private Integer defaultPointerSize;
41 | private INativeCodeUnit> nativeUnit;
42 | private ITypeManager typeManager;
43 |
44 | static class MemoryDump {
45 | long baseAddress;
46 | File dumpFile;
47 | long basePointer; // optional
48 |
49 | public MemoryDump(long baseAddress, File dumpFile) {
50 | this.baseAddress = baseAddress;
51 | this.dumpFile = dumpFile;
52 | }
53 |
54 | public MemoryDump(long baseAddress, File dumpFile, long basePointer) {
55 | this(baseAddress, dumpFile);
56 | this.basePointer = basePointer;
57 | }
58 | }
59 |
60 | public EmulatorState(INativeCodeUnit> nativeUnit) {
61 | Assert.a(nativeUnit != null);
62 | this.nativeUnit = nativeUnit;
63 | typeManager = nativeUnit.getTypeManager();
64 |
65 | // memory initialization
66 | defaultPointerSize = nativeUnit.getMemory().getSpaceBits() / 8;
67 | memory = nativeUnit.getMemory();
68 | }
69 |
70 | /**
71 | * Initialize state from stack/heap memory dumps
72 | */
73 | public EmulatorState(INativeCodeUnit> nativeUnit, MemoryDump stackDump, MemoryDump heapDump) {
74 | Assert.a(nativeUnit != null);
75 | this.typeManager = nativeUnit.getTypeManager();
76 | defaultPointerSize = nativeUnit.getMemory().getSpaceBits() / 8;
77 | this.memory = nativeUnit.getMemory().duplicate();
78 |
79 | // stack allocation
80 | byte[] src = null;
81 | try {
82 | src = Files.readAllBytes(stackDump.dumpFile.toPath());
83 | allocateMemory(stackDump.baseAddress, src.length);
84 | this.memory.write(stackDump.baseAddress, src.length, src, 0);
85 | }
86 | catch(IOException e) {
87 | throw new JebRuntimeException("error when reading stack dump");
88 | }
89 |
90 | // heap allocation
91 | src = null;
92 | try {
93 | src = Files.readAllBytes(heapDump.dumpFile.toPath());
94 | allocateMemory(heapDump.baseAddress, src.length);
95 | this.memory.write(heapDump.baseAddress, src.length, src, 0);
96 | }
97 | catch(IOException e) {
98 | throw new JebRuntimeException("error when reading heap dump");
99 | }
100 | }
101 |
102 | public void allocateMemory(long baseAddress, int size) {
103 | VirtualMemoryUtil.allocateFillGaps(this.memory, baseAddress, size, IVirtualMemory.ACCESS_RW);
104 | }
105 |
106 | public boolean allocateStackSpace() {
107 | Long baseStackPointerValue = getRegisterValue(SimpleCEmulator.REG_RBP_ID);
108 | if(baseStackPointerValue != null) {
109 | // arbitrary size
110 | VirtualMemoryUtil.allocateFillGaps(memory, (baseStackPointerValue & 0xFFFFFFFFFFFFF000L) - 0x10_0000,
111 | 0x11_0000, IVirtualMemory.ACCESS_RW);
112 | return true;
113 | }
114 | return false;
115 | }
116 |
117 | public Long getVarValue(ICElement element) {
118 | ICIdentifier id = getIdentifier(element);
119 | if(id.getIdentifierClass() == CIdentifierClass.LOCAL || id.getIdentifierClass() == CIdentifierClass.GLOBAL) {
120 | return readMemory(getVarAddress(id), getTypeSize(id.getType()));
121 | }
122 | else {
123 | return registers.get(id.getId());
124 | }
125 | }
126 |
127 | public void setVarValue(ICElement element, long value) {
128 | ICIdentifier id = getIdentifier(element);
129 | if(id.getIdentifierClass() == CIdentifierClass.LOCAL || id.getIdentifierClass() == CIdentifierClass.GLOBAL) {
130 | writeMemory(getVarAddress(id), value, getTypeSize(id.getType()));
131 | }
132 | else {
133 | int typeSize = getTypeSize(id.getType());
134 | switch(typeSize) {
135 | case 8:
136 | registers.put(id.getId(), value);
137 | break;
138 | case 4:
139 | registers.put(id.getId(), value & 0xFFFFFFFFL);
140 | break;
141 | case 2:
142 | registers.put(id.getId(), value & 0xFFFFL);
143 | break;
144 | case 1:
145 | registers.put(id.getId(), value & 0xFFL);
146 | break;
147 | default:
148 | throw new EmulatorException(Strings.ff("TBI: register size %d", typeSize));
149 | }
150 | }
151 | }
152 |
153 | private ICIdentifier getIdentifier(ICElement element) {
154 | if(element instanceof ICIdentifier) {
155 | return (ICIdentifier)element;
156 | }
157 | if(element instanceof ICDecl) {
158 | return ((ICDecl)element).getIdentifier();
159 | }
160 | return null;
161 | }
162 |
163 | public Long getVarAddress(ICIdentifier var) {
164 | if(var.getIdentifierClass() == CIdentifierClass.LOCAL) {
165 | return var.getAddress() + getRegisterValue(SimpleCEmulator.REG_RBP_ID) + 8; // we assume stack does not change
166 | }
167 | else if(var.getIdentifierClass() == CIdentifierClass.GLOBAL) {
168 | return var.getAddress();
169 | }
170 | else {
171 | throw new EmulatorException(Strings.ff("TBI: get address for var (%s)", var));
172 | }
173 | }
174 |
175 | /**
176 | * Get type size in bytes.
177 | *
178 | * @param type
179 | * @return type size in bytes
180 | */
181 | public int getTypeSize(ICType type) {
182 | String typeName = type.getSignature();
183 | INativeType typeItem = typeManager.getType(typeName);
184 | if(typeItem == null) {
185 | throw new EmulatorException(Strings.ff("ERROR: unknown type (%s)", typeName));
186 | }
187 | return typeItem.getSize();
188 | }
189 |
190 | /**
191 | * Get base type size in bytes, i.e. the size of TYPE in 'TYPE *'
192 | */
193 | public int getBaseTypeSize(ICType type) {
194 | String typeName = type.getSignature();
195 | if(typeName.endsWith("*")) {
196 | INativeType typeItem = typeManager.getType(type.getBaseTypeSignature());
197 | if(typeItem == null) {
198 | throw new EmulatorException(Strings.ff("unknown base type (%s)", typeName));
199 | }
200 | return typeItem.getSize();
201 | }
202 | else {
203 | if(defaultPointerSize != null) {
204 | return defaultPointerSize;
205 | }
206 | throw new EmulatorException(Strings.ff("not a pointer type (%s)", typeName));
207 | }
208 | }
209 |
210 | /**
211 | * Copy n bytes from source to destination
212 | *
213 | * @param src source address
214 | * @param dst destination address
215 | * @param n number of bytes to copy
216 | */
217 | public void copyMemory(long src, long dst, int n) {
218 | byte[] toCopy = new byte[n];
219 | try {
220 | memory.read(src, n, toCopy, 0);
221 | memory.write(dst, n, toCopy, 0);
222 | }
223 | catch(MemoryException e) {
224 | throw new EmulatorException("ERROR: memory copy failed");
225 | }
226 | }
227 |
228 | /**
229 | * Read memory with default endianness. Default value (0L) is returned when memory read failed.
230 | *
231 | * @param address
232 | * @param bytesToRead number of bytes to read
233 | * @return read value, upper-casted as long, if memory couldn't be read return 0
234 | */
235 | public Long readMemorySafe(long address, int bytesToRead) {
236 | try {
237 | return readMemory(address, bytesToRead);
238 | }
239 | catch(EmulatorException e) {
240 | logger.info("> warning: cannot read memory at 0x%08x -- returning 0L", address);
241 | return 0L;
242 | }
243 | }
244 |
245 | /**
246 | * Read memory with default endianness.
247 | *
248 | * @param address
249 | * @param bytesToRead number of bytes to read
250 | * @return read value, upper-casted as long
251 | */
252 | public Long readMemory(long address, int bytesToRead) {
253 | Long value = 0L;
254 | try {
255 | switch(bytesToRead) {
256 | case 8:
257 | value = memory.readLong(address);
258 | break;
259 | case 4:
260 | value = memory.readInt(address) & 0xFFFFFFFFL;
261 | break;
262 | case 2:
263 | value = memory.readShort(address) & 0xFFFFL;
264 | break;
265 | case 1:
266 | value = memory.readByte(address) & 0xFFL;
267 | break;
268 | default:
269 | throw new EmulatorException(Strings.ff("TBI: read memory size (%d)", bytesToRead));
270 | }
271 | }
272 | catch(MemoryException e) {
273 | throw new EmulatorException("ERROR: cant read memory");
274 | }
275 | return value;
276 | }
277 |
278 | /**
279 | * Write memory with default endianness.
280 | *
281 | * @param address
282 | * @param value
283 | * @param bytesToWrite
284 | */
285 | public void writeMemory(long address, long value, int bytesToWrite) {
286 | try {
287 | switch(bytesToWrite) {
288 | case 8:
289 | memory.writeLong(address, value);
290 | break;
291 | case 4:
292 | memory.writeInt(address, (int)value);
293 | break;
294 | case 2:
295 | memory.writeShort(address, (short)value);
296 | break;
297 | case 1:
298 | memory.writeByte(address, (byte)value);
299 | break;
300 | default:
301 | throw new EmulatorException(Strings.ff("TBI: write memory size (%d)", bytesToWrite));
302 | }
303 | }
304 | catch(MemoryException e) {
305 | throw new EmulatorException("ERROR: cant write memory");
306 | }
307 | }
308 |
309 | public void setRegisterValue(int id, long value) {
310 | registers.put(id, value);
311 | }
312 |
313 | /**
314 | * Get register value
315 | *
316 | * @param id
317 | * @return register value, null if not set
318 | */
319 | public Long getRegisterValue(int id) {
320 | return registers.get(id);
321 | }
322 |
323 | public Integer getDefaultPointerSize() {
324 | return defaultPointerSize;
325 | }
326 |
327 | public void setDefaultPointerSize(Integer defaultPointedSize) {
328 | this.defaultPointerSize = defaultPointedSize;
329 | }
330 |
331 | public INativeCodeUnit> getNativeCodeUnit() {
332 | return nativeUnit;
333 | }
334 |
335 | public String toRegisterString() {
336 | return "registers=" + registers;
337 | }
338 |
339 | @Override
340 | public String toString() {
341 | return "[memory=" + memory + ", registers=" + registers + "]";
342 | }
343 |
344 | }
345 |
--------------------------------------------------------------------------------
/src/com/pnf/plugin/cemulator/HeadlessClient.java:
--------------------------------------------------------------------------------
1 | package com.pnf.plugin.cemulator;
2 |
3 | import java.io.File;
4 | import java.io.IOException;
5 |
6 | import com.pnf.plugin.cemulator.EmulatorState.MemoryDump;
7 | import com.pnfsoftware.jeb.client.HeadlessClientContext;
8 | import com.pnfsoftware.jeb.core.Artifact;
9 | import com.pnfsoftware.jeb.core.IEnginesContext;
10 | import com.pnfsoftware.jeb.core.IRuntimeProject;
11 | import com.pnfsoftware.jeb.core.exceptions.JebException;
12 | import com.pnfsoftware.jeb.core.input.FileInput;
13 | import com.pnfsoftware.jeb.util.base.Assert;
14 | import com.pnfsoftware.jeb.util.logging.GlobalLog;
15 | import com.pnfsoftware.jeb.util.logging.ILogger;
16 |
17 | /**
18 | * Headless JEB client to run the companion {@link CEmulatorPlugin}
19 | *
20 | * Refer to jeb-template-plugin for
21 | * a more complete example of a headless client.
22 | *
23 | * @author Joan Calvet
24 | *
25 | */
26 | public class HeadlessClient {
27 | private static final ILogger logger = GlobalLog.getLogger(HeadlessClient.class);
28 |
29 | // arguments
30 | static File targetExecutablePath = null;
31 | static Long targetRoutineAddress = null;
32 | static File logPath = null;
33 | static Long stackBaseAddress = null;
34 | static File stackDump = null;
35 | static Long stackBasePointer = null;
36 | static Long heapBaseAddress = null;
37 | static File heapDump = null;
38 |
39 | public static void main(String[] args) throws JebException, IOException {
40 | HeadlessClientContext client = new HeadlessClientContext() {
41 | @Override
42 | public boolean isDevelopmentMode() {
43 | return true;
44 | }
45 | };
46 |
47 | parseArguments(args);
48 |
49 | // initialize and start the client
50 | client.initialize(args);
51 | client.start();
52 |
53 | IEnginesContext engctx = client.getEnginesContext();
54 |
55 | // process target file
56 | IRuntimeProject prj = engctx.loadProject("ProjectTest");
57 | prj.processArtifact(new Artifact(targetExecutablePath.getName(), new FileInput(targetExecutablePath)));
58 |
59 | // execute plugin
60 | try {
61 | CEmulatorPlugin plugin;
62 | if(stackDump != null && heapDump != null) {
63 | plugin = new CEmulatorPlugin(targetRoutineAddress,
64 | new MemoryDump(stackBaseAddress, stackDump, stackBasePointer),
65 | new MemoryDump(heapBaseAddress, heapDump));
66 | }
67 | else {
68 | plugin = new CEmulatorPlugin(targetRoutineAddress);
69 | }
70 | if(logPath != null) {
71 | plugin.setLogFile(logPath);
72 | }
73 | plugin.execute(client.getEnginesContext());
74 | }
75 | catch(Exception e) {
76 | logger.catching(e);
77 | }
78 |
79 | client.stop();
80 | }
81 |
82 | private static void parseArguments(String[] args) {
83 | for(int i = 0; i < args.length; i += 2) {
84 | if(args[i].equals("--stack-dump")) {
85 | stackDump = new File(args[i + 1]);
86 | Assert.a(stackDump.isFile(), "cannot find stack-dump");
87 | }
88 | else if(args[i].equals("--stack-base-adr")) {
89 | stackBaseAddress = Long.decode(args[i + 1]);
90 | }
91 | else if(args[i].equals("--stack-base-ptr")) {
92 | stackBasePointer = Long.decode(args[i + 1]);
93 | }
94 | else if(args[i].equals("--heap-dump")) {
95 | heapDump = new File(args[i + 1]);
96 | Assert.a(heapDump.isFile(), "cannot find heap-dump");
97 | }
98 | else if(args[i].equals("--heap-base-adr")) {
99 | heapBaseAddress = Long.decode(args[i + 1]);
100 | }
101 | else if(args[i].equals("--target")) {
102 | targetExecutablePath = new File(args[i + 1]);
103 | Assert.a(targetExecutablePath.isFile(), "cannot find target exec");
104 | }
105 | else if(args[i].equals("--rtn")) {
106 | targetRoutineAddress = Long.decode(args[i + 1]);
107 | }
108 | else if(args[i].equals("--log")) {
109 | logPath = new File(args[i + 1]);
110 | Assert.a(logPath.isFile(), "cannot find log file");
111 | }
112 | else {
113 | logger.i("> ERROR: invalid argument (%s)", args[i]);
114 | usage();
115 | return;
116 | }
117 | }
118 | if(targetExecutablePath == null || targetRoutineAddress == null) {
119 | logger.i("> ERROR: missing arguments");
120 | usage();
121 | return;
122 | }
123 | }
124 |
125 | private static void usage() {
126 | //@formatter:off
127 | logger.i("Usage: " +
128 | "--target path : path to executable file to emulate" +
129 | "--rtn 0xAAAAAAAA : address of first routine to emulate" +
130 | "--log path : path to logfile (optional)" +
131 | "--stack-dump path : path to stack dump file (optional)" +
132 | "--stack-base-adr 0xAAAAAAAA : stack dump base address (optional)" +
133 | "--stack-base-ptr 0xAAAAAAAA : stack base pointer (optional)" +
134 | "--heap-dump path : path to heap dump file (optional)" +
135 | "--heap-base-adr 0xAAAAAAAA : heap dump base address (optional)");
136 | //@formatter:on
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/src/com/pnf/plugin/cemulator/MarsAnalyticaCEmulator.java:
--------------------------------------------------------------------------------
1 | package com.pnf.plugin.cemulator;
2 |
3 | import java.util.List;
4 |
5 | import com.pnfsoftware.jeb.core.units.code.asm.decompiler.ast.ICConstantInteger;
6 | import com.pnfsoftware.jeb.core.units.code.asm.decompiler.ast.ICExpression;
7 | import com.pnfsoftware.jeb.core.units.code.asm.decompiler.ast.ICIfStm;
8 | import com.pnfsoftware.jeb.core.units.code.asm.decompiler.ast.ICMethod;
9 | import com.pnfsoftware.jeb.core.units.code.asm.decompiler.ast.ICOperation;
10 | import com.pnfsoftware.jeb.core.units.code.asm.decompiler.ast.ICStatement;
11 | import com.pnfsoftware.jeb.util.format.Strings;
12 |
13 | /**
14 | * MarsAnalytica's crackme specific emulation logic
15 | *
16 | * @author Joan Calvet
17 | *
18 | */
19 | public class MarsAnalyticaCEmulator extends SimpleCEmulator {
20 |
21 | private char currentChar = 0x61;
22 |
23 | /** Stack machine memory layout */
24 | private final int CHUNK_SIZE = 16;
25 | private long curFreeChunkAddr = 0x1000000; // arbitrary address
26 |
27 | private int popCounter;
28 |
29 | @Override
30 | protected void initEmulation() {
31 | super.initEmulation();
32 | popCounter = 0;
33 | defaultLogging = false; // MA emulator does its own logging for stack machine operations
34 | }
35 |
36 | @Override
37 | protected void preEmulateMethodCallback(ICMethod method, EmulatorState inputState) {
38 | if(method.getName().equals("sub_402AB2")) {
39 | outputLog.append(Strings.ff("S: SWAP"));
40 | outputLog.append(Strings.LINESEP);
41 | }
42 | }
43 |
44 | @Override
45 | protected void preEmulateStatementCallback(CFG cfg, ICStatement currentStatement) {
46 | if(popCounter > 0) {
47 | if(currentStatement instanceof ICIfStm) {
48 | ICIfStm ifStm = ((ICIfStm)currentStatement);
49 | // MarsAnalytica ifs are always if/else or if
50 | if(ifStm.getBlocks().size() <= 2) {
51 |
52 | // we log the 'true' predicate
53 | ICOperation pred = (ICOperation)((ICIfStm)currentStatement).getBranchPredicate(0).getExpression()
54 | .duplicate();
55 | if(evaluateExpression(pred) == 0) {
56 | pred.reverse(method.getOperatorFactory());
57 | }
58 | if(pred.getSecondOperand() instanceof ICConstantInteger) {
59 | outputLog.append(String.format("S: TEST (%s,cte=%d)", pred.getOperator(),
60 | ((ICConstantInteger>)pred.getSecondOperand()).getValueAsLong()));
61 | outputLog.append(Strings.LINESEP);
62 | }
63 | else {
64 | outputLog.append(
65 | Strings.ff("S: TEST (%s,#op=%d)", pred.getOperator(), pred.getCountOfOperands()));
66 | outputLog.append(Strings.LINESEP);
67 | }
68 | popCounter = 0;
69 | }
70 | }
71 | }
72 | return;
73 | }
74 |
75 | @Override
76 | protected Long simulateWellKnownMethods(ICMethod calledMethod, List parameters) {
77 | Long defaultEmulationResult = super.simulateWellKnownMethods(calledMethod, parameters);
78 | if(defaultEmulationResult != null) {
79 | return defaultEmulationResult;
80 | }
81 |
82 | /** MarsAnalytica's specific emulation */
83 |
84 | /** Inject dummy characters */
85 | if(calledMethod.getName().equals("→getchar")) {
86 | return (long)currentChar++;
87 | }
88 | if(calledMethod.getName().equals("→putchar")) {
89 | logger.i("putchar");
90 | return 0L;
91 | }
92 |
93 | /**
94 | * Stack machine handlers
95 | */
96 |
97 | /** PUSH(STACK_PTR, VALUE) */
98 | else if(calledMethod.getName().equals("sub_400AAE")) {
99 | Long pStackPtr = evaluateExpression(parameters.get(0));
100 | Long pValue = evaluateExpression(parameters.get(1));
101 |
102 | long newChunkAddr = allocateNewChunk();
103 |
104 | // write value
105 | state.writeMemory(newChunkAddr + 8, pValue, 4);
106 |
107 | // link new chunk to existing stack
108 | Long stackAdr = state.readMemory(pStackPtr, 8);
109 | state.writeMemory(newChunkAddr, stackAdr, 8);
110 |
111 | // make new chunk the new stack head
112 | state.writeMemory(pStackPtr, newChunkAddr, 8);
113 |
114 | outputLog.append(Strings.ff("S: PUSH %d", pValue));
115 | outputLog.append(Strings.LINESEP);
116 |
117 | if(popCounter == 2) {
118 | // parameters.get(1) is an operation?
119 | ICExpression expr = parameters.get(1);
120 | if(expr instanceof ICOperation) {
121 | // cast + operation
122 | while(expr instanceof ICOperation && ((ICOperation)expr).getOperator().isCast()) {
123 | expr = ((ICOperation)expr).getFirstOperand();
124 | }
125 | }
126 | if(expr instanceof ICOperation) {
127 | outputLog.append(Strings.ff(" | operation: (%s,#op=%d)", ((ICOperation)expr).getOperator(),
128 | ((ICOperation)expr).getCountOfOperands()));
129 | outputLog.append(Strings.LINESEP);
130 | popCounter = 0;
131 | }
132 | }
133 |
134 | return 0L;
135 | }
136 | /** SET(STACK_PTR, INDEX, VALUE) */
137 | else if(calledMethod.getName().equals("sub_400D55")) {
138 | Long pStackPtr = evaluateExpression(parameters.get(0));
139 | Long pIndex = evaluateExpression(parameters.get(1));
140 | Long pValue = evaluateExpression(parameters.get(2));
141 | Long retVal = emulateSetElementFromEnd(pStackPtr, pIndex, pValue);
142 |
143 | outputLog.append(Strings.ff("S: SET index:%d value:%d", pIndex, pValue));
144 | outputLog.append(Strings.LINESEP);
145 |
146 | return retVal;
147 | }
148 | /** GET(STACK_PTR, INDEX) */
149 | else if(calledMethod.getName().equals("sub_400D08")) {
150 | Long pStackPtr = evaluateExpression(parameters.get(0));
151 | Long pIndex = evaluateExpression(parameters.get(1));
152 | Long retVal = emulateGetElementFromEnd(pStackPtr, pIndex);
153 |
154 | outputLog.append(Strings.ff("S: GET index:%d", pIndex));
155 | outputLog.append(Strings.LINESEP);
156 |
157 | return retVal;
158 | }
159 | /** POP(STACK_PTR) */
160 | else if(calledMethod.getName().equals("sub_4009D7")) {
161 | Long pStackPtr = evaluateExpression(parameters.get(0));
162 | Long retVal = emulateUnlink(pStackPtr);
163 |
164 | outputLog.append(Strings.ff("S: POP (%d)", retVal));
165 | outputLog.append(Strings.LINESEP);
166 |
167 | popCounter++;
168 |
169 | return retVal;
170 | }
171 | else if(calledMethod.getName().equals("sub_402AB2")) {
172 | outputLog.append(Strings.ff("S: SWAP"));
173 | outputLog.append(Strings.LINESEP);
174 |
175 | return 0L;
176 | }
177 |
178 | return null;
179 | }
180 |
181 |
182 | private long allocateNewChunk() {
183 | long freeChunkAddr = curFreeChunkAddr;
184 | curFreeChunkAddr += CHUNK_SIZE;
185 | state.allocateMemory(freeChunkAddr, CHUNK_SIZE);
186 | return freeChunkAddr;
187 | }
188 |
189 | private Long emulateSetElementFromEnd(Long param1, Long param2, Long param3) {
190 | long lastIndex = emulateGetLength(param1) - 1;
191 | long curElement = param1;
192 | for(int i = 0; i != lastIndex - param2; i++) {
193 | curElement = state.readMemory(curElement, 8);
194 | }
195 | state.writeMemory(curElement + 8, param3, 4);
196 | return curElement;
197 | }
198 |
199 | private Long emulateGetElementFromEnd(Long param1, Long param2) {
200 | long lastIndex = emulateGetLength(param1) - 1;
201 | long curElement = param1;
202 | for(int i = 0; i != lastIndex - param2; i++) {
203 | curElement = state.readMemory(curElement, 8);
204 | }
205 | return state.readMemory(curElement + 8, 4);
206 | }
207 |
208 | private Long emulateGetLength(Long param1) {
209 | long length = 0;
210 | long current = param1;
211 | while(current != 0) {
212 | current = state.readMemory(current, 8);
213 | length++;
214 | }
215 | return length;
216 | }
217 |
218 | private Long emulateUnlink(Long param1) {
219 | Long nextChunkAddress = state.readMemory(param1, 8);
220 | Long nextChunkValue = state.readMemory(nextChunkAddress + 8, 4);
221 | Long nextNextChunkAddress = state.readMemory(nextChunkAddress, 8);
222 | state.writeMemory(param1, nextNextChunkAddress, 8);
223 | // note: we do not free memory
224 | return nextChunkValue;
225 | }
226 |
227 |
228 |
229 | }
230 |
--------------------------------------------------------------------------------
/src/com/pnf/plugin/cemulator/SimpleCEmulator.java:
--------------------------------------------------------------------------------
1 | package com.pnf.plugin.cemulator;
2 |
3 | import java.io.File;
4 | import java.io.IOException;
5 | import java.util.List;
6 |
7 | import com.pnfsoftware.jeb.core.exceptions.JebRuntimeException;
8 | import com.pnfsoftware.jeb.core.input.BytesInput;
9 | import com.pnfsoftware.jeb.core.units.AbstractBinaryUnit;
10 | import com.pnfsoftware.jeb.core.units.INativeCodeUnit;
11 | import com.pnfsoftware.jeb.core.units.IUnit;
12 | import com.pnfsoftware.jeb.core.units.WellKnownUnitTypes;
13 | import com.pnfsoftware.jeb.core.units.code.asm.decompiler.ast.COperatorType;
14 | import com.pnfsoftware.jeb.core.units.code.asm.decompiler.ast.ICAssignment;
15 | import com.pnfsoftware.jeb.core.units.code.asm.decompiler.ast.ICBlock;
16 | import com.pnfsoftware.jeb.core.units.code.asm.decompiler.ast.ICCall;
17 | import com.pnfsoftware.jeb.core.units.code.asm.decompiler.ast.ICConstantInteger;
18 | import com.pnfsoftware.jeb.core.units.code.asm.decompiler.ast.ICConstantPointer;
19 | import com.pnfsoftware.jeb.core.units.code.asm.decompiler.ast.ICControlBreaker;
20 | import com.pnfsoftware.jeb.core.units.code.asm.decompiler.ast.ICDecl;
21 | import com.pnfsoftware.jeb.core.units.code.asm.decompiler.ast.ICElement;
22 | import com.pnfsoftware.jeb.core.units.code.asm.decompiler.ast.ICExpression;
23 | import com.pnfsoftware.jeb.core.units.code.asm.decompiler.ast.ICGoto;
24 | import com.pnfsoftware.jeb.core.units.code.asm.decompiler.ast.ICIdentifier;
25 | import com.pnfsoftware.jeb.core.units.code.asm.decompiler.ast.ICIfStm;
26 | import com.pnfsoftware.jeb.core.units.code.asm.decompiler.ast.ICJumpFar;
27 | import com.pnfsoftware.jeb.core.units.code.asm.decompiler.ast.ICLabel;
28 | import com.pnfsoftware.jeb.core.units.code.asm.decompiler.ast.ICMethod;
29 | import com.pnfsoftware.jeb.core.units.code.asm.decompiler.ast.ICOperation;
30 | import com.pnfsoftware.jeb.core.units.code.asm.decompiler.ast.ICOperator;
31 | import com.pnfsoftware.jeb.core.units.code.asm.decompiler.ast.ICPredicate;
32 | import com.pnfsoftware.jeb.core.units.code.asm.decompiler.ast.ICReturn;
33 | import com.pnfsoftware.jeb.core.units.code.asm.decompiler.ast.ICStatement;
34 | import com.pnfsoftware.jeb.core.units.code.asm.decompiler.ast.ICWhileStm;
35 | import com.pnfsoftware.jeb.util.logging.GlobalLog;
36 | import com.pnfsoftware.jeb.util.logging.ILogger;
37 | import com.pnfsoftware.jeb.util.math.MathUtil;
38 | import com.pnfsoftware.jeb.util.format.Strings;
39 | import com.pnfsoftware.jeb.util.io.IO;
40 |
41 | /**
42 | * Simple emulator for {@link ICMethod} (JEB's AST). Originally implemented to be used with
43 | * {@link CEmulatorPlugin}.
44 | *
45 | * Limitations:
46 | *
47 | * - Emulator relies on a minimalist CFG implementation (see {@link CFG}), and hence has the same
48 | * limitations (in particular switch and do-while statements are not emulated)
49 | *
- Called subroutines are either simulated (ie, reimplemented in Java) or emulated as
50 | * non-retourning routines (see {{@link #evaluateCall(ICCall)})
51 | *
- Emulator is tailored for x64 machine code, as it assumes calls' returned values are passed
52 | * through RAX register
53 | *
54 | *
55 | * @author Joan Calvet
56 | *
57 | */
58 | public class SimpleCEmulator {
59 | protected static final ILogger logger = GlobalLog.getLogger(SimpleCEmulator.class);
60 |
61 | /** IDs for register variables (mirrors) */
62 | public static final int REG_RAX_ID = -65536;
63 | public static final int REG_RBP_ID = -65856;
64 | public static final int REG_RBX_ID = -65728;
65 | /** synthetic register to store next method to emulate */
66 | public static final int REG_NEXT_METHOD_ID = 1;
67 |
68 | protected ICMethod method;
69 | protected EmulatorState state;
70 |
71 | protected boolean defaultLogging = true;
72 | protected StringBuilder outputLog = new StringBuilder();
73 |
74 | private IUnit logUnit;
75 |
76 | private int logSize = 0;
77 |
78 | /**
79 | * Emulate the given method using the given input state.
80 | *
81 | * @param method
82 | * @param inputState
83 | * @return log of the emulation
84 | */
85 | public EmulatorLog emulate(ICMethod method, EmulatorState inputState) {
86 | preEmulateMethodCallback(method, inputState);
87 |
88 | state = inputState;
89 | this.method = method;
90 |
91 | initEmulation();
92 | EmulatorLog log = new EmulatorLog();
93 |
94 | CFG cfg = CFG.buildCFG(method);
95 | ICStatement currentStatement = cfg.getEntryPoint();
96 |
97 | if(defaultLogging) {
98 | outputLog.append("> emulator trace:");
99 | outputLog.append(Strings.LINESEP);
100 | }
101 |
102 | while(currentStatement != null) {
103 | log.addExecutedStatement(currentStatement);
104 |
105 | if(defaultLogging) {
106 | outputLog.append(Strings.ff(" %s", currentStatement));
107 | outputLog.append(Strings.LINESEP);
108 | }
109 |
110 | currentStatement = emulateStatement(cfg, currentStatement);
111 |
112 | // uncomment to see register + memory state
113 | // if(defaultLogging) {
114 | // outputLog.append(Strings.ff(" > output registers: %s", state.toString()));
115 | // outputLog.append(Strings.LINESEP);
116 | // }
117 | }
118 |
119 | log.setEmulatorState(state);
120 | return log;
121 | }
122 |
123 | protected void preEmulateMethodCallback(ICMethod method, EmulatorState inputState) {
124 | // default implementation does nothing - override with specific logic
125 | return;
126 | }
127 |
128 | protected void initEmulation() {
129 | // default implementation does nothing - override with specific logic
130 | return;
131 | }
132 |
133 | protected void preEmulateStatementCallback(CFG cfg, ICStatement currentStatement) {
134 | // default implementation does nothing - override with specific logic
135 | return;
136 | }
137 |
138 | private ICStatement emulateStatement(CFG cfg, ICStatement currentStatement) {
139 | preEmulateStatementCallback(cfg, currentStatement);
140 |
141 | if(currentStatement instanceof ICGoto) {
142 | return cfg.getNextStatement(currentStatement);
143 | }
144 | else if(currentStatement instanceof ICLabel) {
145 | return cfg.getNextStatement(currentStatement);
146 | }
147 | else if(currentStatement instanceof ICReturn) {
148 | ICExpression retExpression = ((ICReturn)currentStatement).getExpression();
149 | if(retExpression != null) {
150 | // note: would need to check calling convention
151 | state.setRegisterValue(REG_RAX_ID,
152 | evaluateExpression(retExpression));
153 | }
154 | return cfg.getNextStatement(currentStatement);
155 | }
156 | else if(currentStatement instanceof ICAssignment) {
157 | evaluateAssignment((ICAssignment)currentStatement);
158 | return cfg.getNextStatement(currentStatement);
159 | }
160 | else if(currentStatement instanceof ICIfStm) {
161 | ICIfStm ifStm = (ICIfStm)currentStatement;
162 | List extends ICPredicate> predicates = ifStm.getBranchPredicates();
163 | for(int i = 0; i < predicates.size(); i++) {
164 | if(evaluateExpression(predicates.get(i)) != 0) {
165 | return cfg.getNthNextStatement(currentStatement, i);
166 | }
167 | }
168 | // ...or go to else block if present (last conditional target)
169 | if(ifStm.hasDefaultBlock()) {
170 | return cfg.getNthNextStatement(currentStatement, predicates.size());
171 | }
172 | // ...or to fallthrough
173 | return cfg.getNextStatement(currentStatement);
174 | }
175 | else if(currentStatement instanceof ICWhileStm) {
176 | ICWhileStm wStm = (ICWhileStm)currentStatement;
177 | if(evaluateExpression(wStm.getPredicate()) != 0) {
178 | return cfg.getNextTrueStatement(wStm);
179 | }
180 | else {
181 | return cfg.getNextStatement(wStm);
182 | }
183 | }
184 | else if(currentStatement instanceof ICBlock) {
185 | return cfg.getNextStatement(currentStatement);
186 | }
187 | else if(currentStatement instanceof ICControlBreaker) {
188 | return cfg.getNextStatement(currentStatement);
189 | }
190 | else if(currentStatement instanceof ICDecl) {
191 | return cfg.getNextStatement(currentStatement);
192 | }
193 | else if(currentStatement instanceof ICCall) {
194 | evaluateCall((ICCall)currentStatement);
195 | return cfg.getNextStatement(currentStatement);
196 | }
197 | else if(currentStatement instanceof ICJumpFar) {
198 | long targetAddr = evaluateExpression(((ICJumpFar)currentStatement).getJumpsite());
199 | state.setRegisterValue(REG_NEXT_METHOD_ID, targetAddr);
200 | return cfg.getNextStatement(currentStatement);
201 | }
202 | else {
203 | throw new EmulatorException(
204 | Strings.ff("ERROR: unimplemented statement emulation (%s)", currentStatement));
205 | }
206 | }
207 |
208 | private void evaluateCall(ICCall ccall) {
209 | if(ccall.getMethod() != null) { // resolved calls
210 | Long returnValue = simulateWellKnownMethods(ccall.getMethod(),
211 | ccall.getArguments());
212 | if(returnValue == null) {
213 | // simulation failed, we need to emulate callee
214 | throw new EmulatorException(
215 | Strings.ff("ERROR: unimplemented recursive emulation (%s)", ccall));
216 | }
217 | else {
218 | state.setRegisterValue(REG_RAX_ID, returnValue);
219 | }
220 | }
221 | else { // calls whose target is not resolved yet
222 | // here we need to go emulate target, and we assume such call is non-returning
223 | Long nextHandlerAddr = evaluateExpression(ccall.getCallsite());
224 | if(nextHandlerAddr == null) {
225 | throw new EmulatorException(Strings.ff("ERROR: cannot resolve target (%s)", ccall));
226 | }
227 | else {
228 | state.setRegisterValue(REG_NEXT_METHOD_ID, nextHandlerAddr);
229 | }
230 | }
231 | }
232 |
233 | private void evaluateAssignment(ICAssignment assign) {
234 | if(assign.isSimpleAssignment()) {
235 | // right hand side eval
236 | Long rightValue = evaluateExpression(assign.getRight());
237 | if(rightValue == null){
238 | throw new EmulatorException(Strings.ff("right value evaluation (%s)", assign));
239 | }
240 | // left hand side eval
241 | ICExpression leftDerefExpr = getDereferencedExpression(assign.getLeft());
242 | if(leftDerefExpr != null) {
243 | Long leftExprValue = evaluateExpression(leftDerefExpr);
244 | if(leftExprValue != null) {
245 | // memory access
246 | if(((ICOperation)assign.getLeft()).getFirstOperand() instanceof ICOperation) {
247 | ICOperation leftFirstOperand = (ICOperation)(((ICOperation)assign.getLeft()).getFirstOperand());
248 | if(leftFirstOperand.getOperator().isCast()) {
249 | int derefCastSize = state
250 | .getBaseTypeSize(((ICOperation)leftFirstOperand).getOperator().getCastType());
251 | state.writeMemory(leftExprValue, rightValue, derefCastSize);
252 | }
253 | else {
254 | state.writeMemory(leftExprValue, rightValue, 8);
255 | }
256 | }
257 | }
258 | }
259 | else {
260 | // identifier (possibly within a definition)
261 | state.setVarValue(assign.getLeft(), rightValue);
262 | }
263 | }
264 | else {
265 | throw new EmulatorException("ERROR: not implemented: non simple assignments");
266 | }
267 | }
268 |
269 | private ICExpression getDereferencedExpression(ICElement element) {
270 | if(element instanceof ICOperation) {
271 | if(((ICOperation)element).getOperatorType() == COperatorType.PTR) {
272 | return ((ICOperation)element).getFirstOperand();
273 | }
274 | }
275 | return null;
276 | }
277 |
278 | protected Long evaluateExpression(ICExpression expr) {
279 | if(expr instanceof ICConstantInteger) {
280 | return ((ICConstantInteger>)expr).getValueAsLong();
281 | }
282 | else if(expr instanceof ICConstantPointer) {
283 | return ((ICConstantPointer)expr).getValue();
284 | }
285 | else if(expr instanceof ICOperation) {
286 | return evaluateOperation((ICOperation)expr);
287 | }
288 | else if(expr instanceof ICIdentifier) {
289 | Long varValue = state.getVarValue((ICIdentifier)expr);
290 | if(varValue == null) {
291 | logger.info("> warning: non initialized identifier (%s) -- defining it to 0L", expr);
292 | varValue = 0L;
293 | }
294 | return varValue;
295 | }
296 | else if(expr instanceof ICPredicate) {
297 | return evaluateExpression(((ICPredicate)expr).getExpression()) != 0 ? 1L: 0L;
298 | }
299 | else if(expr instanceof ICCall) {
300 | evaluateCall(((ICCall)expr));
301 | return state.getRegisterValue(REG_RAX_ID);
302 | }
303 | else {
304 | throw new EmulatorException(Strings.ff("ERROR: unimplemented expression eval (%s)", expr));
305 | }
306 | }
307 |
308 | /**
309 | * Simulate well known methods; ie, rather than emulating their actual code, we simulate it in
310 | * Java.
311 | *
312 | * @return method return value, null if failure
313 | */
314 | protected Long simulateWellKnownMethods(ICMethod calledMethod,
315 | List parameters) {
316 | /**
317 | * libc APIs
318 | */
319 | if(calledMethod.getName().equals("→time")) {
320 | return 42L;
321 | }
322 | else if(calledMethod.getName().equals("→srand")) {
323 | return 37L;
324 | }
325 | else if(calledMethod.getName().equals("→memcpy")) {
326 | ICExpression dst = parameters.get(0);
327 | ICExpression src = parameters.get(1);
328 | ICExpression n = parameters.get(2);
329 | if(src instanceof ICOperation && ((ICOperation)src).checkOperatorType(COperatorType.CAST)
330 | && n instanceof ICConstantInteger) {
331 | long src_ = ((ICConstantPointer)((ICOperation)src).getFirstOperand()).getValue();
332 | int n_ = (int)((ICConstantInteger>)n).getValueAsLong();
333 | long dst_ = evaluateExpression(dst);
334 | state.copyMemory(src_, dst_, n_);
335 | return dst_;
336 | }
337 | else {
338 | throw new EmulatorException("TBI: memcpy");
339 | }
340 | }
341 |
342 |
343 | return null;
344 | }
345 |
346 | private Long evaluateOperation(ICOperation operation) {
347 | Long value = null;
348 |
349 | ICExpression opnd1 = operation.getFirstOperand();
350 | ICExpression opnd2 = operation.getSecondOperand();
351 | ICExpression opnd3 = operation.getThirdOperand();
352 | ICOperator operator = operation.getOperator();
353 |
354 | switch(operator.getType()) {
355 | case ADD:
356 | value = evaluateExpression(opnd1) + evaluateExpression(opnd2);
357 | break;
358 | case AND:
359 | value = evaluateExpression(opnd1) & evaluateExpression(opnd2);
360 | break;
361 | case CAST:
362 | int castSize = state.getTypeSize(operator.getCastType());
363 | long castOperand = MathUtil.makeMask(castSize * 8);
364 | value = evaluateExpression(opnd1) & castOperand;
365 | break;
366 | case COND:
367 | value = evaluateExpression(opnd1) != 0 ? evaluateExpression(opnd2): evaluateExpression(opnd3);
368 | break;
369 | case CUSTOM:
370 | break;
371 | case DIV:
372 | value = evaluateExpression(opnd1) / evaluateExpression(opnd2);
373 | break;
374 | case EQ:
375 | value = evaluateExpression(opnd1).equals(evaluateExpression(opnd2)) ? 1L: 0L;
376 | break;
377 | case GE:
378 | value = evaluateExpression(opnd1) >= evaluateExpression(opnd2) ? 1L: 0L;
379 | break;
380 | case GT:
381 | value = evaluateExpression(opnd1) > evaluateExpression(opnd2) ? 1L: 0L;
382 | break;
383 | case LE:
384 | value = evaluateExpression(opnd1) <= evaluateExpression(opnd2) ? 1L: 0L;
385 | break;
386 | case LOG_AND:
387 | value = (evaluateExpression(opnd1) != 0 && evaluateExpression(opnd2) != 0) ? 1L: 0L;
388 | break;
389 | case LOG_IDENT:
390 | value = evaluateExpression(opnd1);
391 | break;
392 | case LOG_NOT:
393 | value = evaluateExpression(opnd1) != 0 ? 0L: 1L;
394 | break;
395 | case LOG_OR:
396 | value = (evaluateExpression(opnd1) != 0 || evaluateExpression(opnd2) != 0) ? 1L: 0L;
397 | break;
398 | case LT:
399 | value = evaluateExpression(opnd1) < evaluateExpression(opnd2) ? 1L: 0L;
400 | break;
401 | case MUL:
402 | value = evaluateExpression(opnd1) * evaluateExpression(opnd2);
403 | break;
404 | case NE:
405 | value = evaluateExpression(opnd1) != evaluateExpression(opnd2) ? 1L: 0L;
406 | break;
407 | case NEG:
408 | value = -evaluateExpression(opnd1);
409 | break;
410 | case NOT:
411 | value = ~evaluateExpression(opnd1);
412 | break;
413 | case OR:
414 | value = evaluateExpression(opnd1) | evaluateExpression(opnd2);
415 | break;
416 | case PTR:
417 | if(opnd1 instanceof ICIdentifier) {
418 | value = state.readMemorySafe(evaluateExpression(opnd1),
419 | state.getBaseTypeSize(((ICIdentifier)opnd1).getType()));
420 | }
421 | else if(opnd1 instanceof ICOperation) {
422 | ICIdentifier basePointer = getBasePointer((ICOperation)opnd1);
423 | if(((ICOperation)opnd1).getOperator().isCast()) {
424 | int derefCastSize = state
425 | .getBaseTypeSize(((ICOperation)opnd1).getOperator().getCastType());
426 | value = state.readMemorySafe(
427 | evaluateExpression(((ICExpression)((ICOperation)opnd1).getFirstOperand())),
428 | derefCastSize);
429 | value = MathUtil.signExtend(value, derefCastSize * 8);
430 | }
431 | else if(basePointer != null) {
432 | value = state.readMemorySafe(evaluateExpression(opnd1),
433 | state.getBaseTypeSize(basePointer.getType()));
434 | }
435 | else {
436 | if(state.getDefaultPointerSize() != null) {
437 | value = state.readMemorySafe(evaluateExpression(opnd1),
438 | state.getDefaultPointerSize());
439 | }
440 | else {
441 | throw new EmulatorException("cant find size to read for PTR operation");
442 | }
443 | }
444 | }
445 | else if(opnd1 instanceof ICConstantInteger) {
446 | logger.info("> warning: read with fixed size (%d) at address %x",
447 | state.getDefaultPointerSize(),
448 | ((ICConstantInteger>)opnd1).getValueAsLong());
449 | value = state.readMemorySafe(((ICConstantInteger>)opnd1).getValueAsLong(),
450 | state.getDefaultPointerSize());
451 | }
452 | else {
453 | throw new EmulatorException(Strings.ff("PTR invalid (%s)", opnd1));
454 | }
455 | break;
456 | case REF:
457 | if(!(opnd1 instanceof ICIdentifier)) {
458 | throw new EmulatorException(Strings.ff("REF on non id (%s)", opnd1));
459 | }
460 | value = state.getVarAddress((ICIdentifier)opnd1);
461 | break;
462 | case REM:
463 | value = evaluateExpression(opnd1) % evaluateExpression(opnd2);
464 | break;
465 | case SHL:
466 | value = evaluateExpression(opnd1) << evaluateExpression(opnd2);
467 | break;
468 | case SHR:
469 | value = evaluateExpression(opnd1) >> evaluateExpression(opnd2);
470 | break;
471 | case SIZEOF:
472 | break;
473 | case SUB:
474 | value = evaluateExpression(opnd1) - evaluateExpression(opnd2);
475 | break;
476 | case USHR:
477 | value = evaluateExpression(opnd1) >>> evaluateExpression(opnd2);
478 | break;
479 | case XOR:
480 | value = evaluateExpression(opnd1) ^ evaluateExpression(opnd2);
481 | break;
482 | default:
483 | break;
484 | }
485 | if(value == null) {
486 | throw new EmulatorException(Strings.ff("TBI: operator (%s)", operator));
487 | }
488 | return value;
489 | }
490 |
491 | /**
492 | * Get the base pointer in a pointer arithmetic operation. Simple check for the first identifier
493 | * in the operation.
494 | *
495 | * @param operation
496 | * @return base pointer, null if cannot be found
497 | */
498 | public ICIdentifier getBasePointer(ICOperation operation) {
499 | ICIdentifier basePointer = null;
500 | if(operation.getFirstOperand() instanceof ICIdentifier) {
501 | basePointer = (ICIdentifier)operation.getFirstOperand();
502 | }
503 | else if(operation.getSecondOperand() instanceof ICIdentifier) {
504 | basePointer = (ICIdentifier)operation.getSecondOperand();
505 | }
506 | else if(operation.getThirdOperand() instanceof ICIdentifier) {
507 | basePointer = (ICIdentifier)operation.getThirdOperand();
508 | }
509 | return basePointer;
510 | }
511 |
512 | public void dumpLog(File logFile) {
513 | if(logFile != null) {
514 | if(outputLog.length() != logSize) { // if something new...
515 | try {
516 | IO.writeFile(logFile, Strings.encodeUTF8(outputLog.toString()));
517 | logSize = outputLog.length();
518 | }
519 | catch(IOException e) {
520 | throw new JebRuntimeException("failed to write log file");
521 | }
522 | }
523 | }
524 | else {
525 | // dump as text unit
526 | INativeCodeUnit> codeUnit = state.getNativeCodeUnit();
527 | if(codeUnit != null && codeUnit.getCodeObjectContainer() != null) {
528 | if(logUnit == null) {
529 | logUnit = codeUnit.getUnitProcessor().process("C emulator log",
530 | new BytesInput(Strings.encodeUTF8(outputLog.toString())), codeUnit.getCodeObjectContainer(),
531 | WellKnownUnitTypes.typeGeneric);
532 | codeUnit.getCodeObjectContainer().addChild(logUnit);
533 | }
534 | else {
535 | ((AbstractBinaryUnit)logUnit).setInput(new BytesInput(Strings.encodeUTF8(outputLog.toString())));
536 | }
537 | }
538 | }
539 | }
540 |
541 | }
542 |
--------------------------------------------------------------------------------