├── GhidraLib ├── LibGhidra.java ├── LibHeadlessAnalyzer.java ├── LibHeadlessErrorLogger.java ├── LibHeadlessOptions.java ├── LibHeadlessScript.java ├── LibHeadlessTimedTaskMonitor.java └── LibProgramHandler.java ├── Media └── Sample1.PNG ├── README.md ├── Sample1 ├── .idea │ ├── description.html │ ├── encodings.xml │ ├── misc.xml │ ├── modules.xml │ ├── project-template.xml │ ├── uiDesigner.xml │ ├── vcs.xml │ └── workspace.xml ├── GhidraDemo.iml └── src │ ├── com │ └── nosecurecode │ │ ├── Demo.java │ │ └── libghidra │ │ ├── LibGhidra.java │ │ ├── LibHeadlessAnalyzer.java │ │ ├── LibHeadlessErrorLogger.java │ │ ├── LibHeadlessOptions.java │ │ ├── LibHeadlessScript.java │ │ ├── LibHeadlessTimedTaskMonitor.java │ │ └── LibProgramHandler.java │ └── lib │ └── ghidra.jar.txt └── Sample2 ├── .idea ├── description.html ├── encodings.xml ├── misc.xml ├── modules.xml ├── project-template.xml ├── uiDesigner.xml ├── vcs.xml └── workspace.xml ├── GhidraDemo.iml └── src ├── com └── nosecurecode │ ├── Demo.java │ └── libghidra │ ├── LibGhidra.java │ ├── LibHeadlessAnalyzer.java │ ├── LibHeadlessErrorLogger.java │ ├── LibHeadlessOptions.java │ ├── LibHeadlessScript.java │ ├── LibHeadlessTimedTaskMonitor.java │ └── LibProgramHandler.java └── lib └── ghidra.jar.txt /GhidraLib/LibGhidra.java: -------------------------------------------------------------------------------- 1 | /* ### 2 | * IP: GHIDRA 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /* 18 | Modified version of the Headless analyzer code, for any feedback, contact NADER SHALLABI at nader@nosecurecode.com 19 | */ 20 | 21 | package com.nosecurecode.libghidra; 22 | 23 | import java.io.File; 24 | import java.io.IOException; 25 | import java.net.MalformedURLException; 26 | import java.net.URL; 27 | import java.util.*; 28 | 29 | import generic.stl.Pair; 30 | import ghidra.app.util.opinion.Loader; 31 | import ghidra.framework.OperatingSystem; 32 | import ghidra.framework.Platform; 33 | import ghidra.framework.model.DomainFolder; 34 | import ghidra.framework.protocol.ghidra.Handler; 35 | import ghidra.util.Msg; 36 | import ghidra.util.exception.InvalidInputException; 37 | 38 | /** 39 | * Launcher entry point for running headless Ghidra. 40 | */ 41 | public class LibGhidra { 42 | 43 | private static final int EXIT_CODE_ERROR = 1; 44 | 45 | /** 46 | * Runs headless command 47 | * @param headlessCmd 48 | * @param handler 49 | * @throws Exception 50 | */ 51 | public static void runHeadlessCmd(String headlessCmd, 52 | LibProgramHandler handler) throws Exception { 53 | new LibGhidra(headlessCmd.split("\\s+"), handler); 54 | } 55 | 56 | /** 57 | * Run heqdless command (command line arguments) 58 | * @param headlessCmdArgs 59 | * @param handler 60 | * @throws Exception 61 | */ 62 | public static void runHeadlessCmd(String [] headlessCmdArgs, 63 | LibProgramHandler handler) throws Exception { 64 | new LibGhidra(headlessCmdArgs, handler); 65 | } 66 | 67 | /** 68 | * This is the main entry point 69 | * @param args 70 | * @param handler 71 | * @throws Exception 72 | */ 73 | private LibGhidra(String args[], LibProgramHandler handler) throws Exception { 74 | String projectName = null; 75 | String rootFolderPath = null; 76 | URL ghidraURL = null; 77 | List filesToImport = new ArrayList<>(); 78 | int optionStartIndex; 79 | 80 | // Make sure there are arguments 81 | if (args.length < 1) { 82 | usage(); 83 | } 84 | 85 | // Ghidra URL handler registration 86 | Handler.registerHandler(); 87 | 88 | if (args[0].startsWith("ghidra:")) { 89 | optionStartIndex = 1; 90 | try { 91 | ghidraURL = new URL(args[0]); 92 | } 93 | catch (MalformedURLException e) { 94 | System.err.println("Invalid Ghidra URL: " + args[0]); 95 | usage(); 96 | } 97 | } 98 | else { 99 | if (args.length < 2) { 100 | usage(); 101 | } 102 | optionStartIndex = 2; 103 | String projectNameAndFolder = args[1]; 104 | 105 | // Check to see if projectName uses back-slashes (likely if they are using Windows) 106 | projectNameAndFolder = projectNameAndFolder.replaceAll("\\\\", DomainFolder.SEPARATOR); 107 | projectName = projectNameAndFolder; 108 | 109 | rootFolderPath = "/"; 110 | int folderIndex = projectNameAndFolder.indexOf(DomainFolder.SEPARATOR); 111 | if (folderIndex == 0) { 112 | System.err.println(args[1] + " is an invalid project_name/folder_path."); 113 | usage(); 114 | } 115 | else if (folderIndex > 0) { 116 | projectName = projectNameAndFolder.substring(0, folderIndex); 117 | rootFolderPath = projectNameAndFolder.substring(folderIndex); 118 | } 119 | } 120 | 121 | // Determine the desired logging. 122 | File logFile = null; 123 | File scriptLogFile = null; 124 | for (int argi = optionStartIndex; argi < args.length; argi++) { 125 | if (checkArgument("-log", args, argi)) { 126 | logFile = new File(args[++argi]); 127 | } 128 | else if (checkArgument("-scriptlog", args, argi)) { 129 | scriptLogFile = new File(args[++argi]); 130 | } 131 | } 132 | 133 | // Instantiate new headless analyzer and parse options. 134 | LibHeadlessAnalyzer analyzer = 135 | LibHeadlessAnalyzer.getLoggableInstance(logFile, scriptLogFile, true, handler); 136 | LibHeadlessOptions options = analyzer.getOptions(); 137 | parseOptions(options, args, optionStartIndex, ghidraURL, filesToImport); 138 | 139 | // Do the headless processing 140 | try { 141 | if (ghidraURL != null) { 142 | analyzer.processURL(ghidraURL, filesToImport); 143 | } 144 | else { 145 | analyzer.processLocal(args[0], projectName, rootFolderPath, filesToImport); 146 | } 147 | } 148 | catch (Throwable e) { 149 | Msg.error(LibHeadlessAnalyzer.class, 150 | "Abort due to Headless analyzer error: " + e.getMessage(), e); 151 | System.exit(EXIT_CODE_ERROR); 152 | } 153 | } 154 | 155 | /** 156 | * Parses the command line arguments and uses them to set the headless options. 157 | * 158 | * @param options The headless options to set. 159 | * @param args The command line arguments to parse. 160 | * @param startIndex The index into the args array of where to start parsing. 161 | * @param ghidraURL The ghidra server url to connect to, or null if not using a url. 162 | * @param filesToImport A list to put files to import into. 163 | * @throws InvalidInputException if an error occurred parsing the arguments or setting 164 | * the options. 165 | */ 166 | private void parseOptions(LibHeadlessOptions options, String[] args, int startIndex, URL ghidraURL, 167 | List filesToImport) throws InvalidInputException { 168 | 169 | String loaderName = null; 170 | List> loaderArgs = new LinkedList<>(); 171 | String languageId = null; 172 | String compilerSpecId = null; 173 | String keystorePath = null; 174 | String serverUID = null; 175 | boolean allowPasswordPrompt = false; 176 | List> preScripts = new LinkedList<>(); 177 | List> postScripts = new LinkedList<>(); 178 | 179 | for (int argi = startIndex; argi < args.length; argi++) { 180 | 181 | String arg = args[argi]; 182 | if (checkArgument("-log", args, argi)) { 183 | // Already processed 184 | argi++; 185 | } 186 | else if (checkArgument("-scriptlog", args, argi)) { 187 | // Already processed 188 | argi++; 189 | } 190 | else if (arg.equalsIgnoreCase("-overwrite")) { 191 | options.enableOverwriteOnConflict(true); 192 | } 193 | else if (arg.equalsIgnoreCase("-noanalysis")) { 194 | options.enableAnalysis(false); 195 | } 196 | else if (arg.equalsIgnoreCase("-deleteproject")) { 197 | options.setDeleteCreatedProjectOnClose(true); 198 | } 199 | else if (checkArgument("-loader", args, argi)) { 200 | loaderName = args[++argi]; 201 | } 202 | else if (arg.startsWith(Loader.COMMAND_LINE_ARG_PREFIX)) { 203 | if (args[argi + 1].startsWith("-")) { 204 | throw new InvalidInputException(args[argi] + " expects value to follow."); 205 | } 206 | loaderArgs.add(new Pair<>(arg, args[++argi])); 207 | } 208 | else if (checkArgument("-processor", args, argi)) { 209 | languageId = args[++argi]; 210 | } 211 | else if (checkArgument("-cspec", args, argi)) { 212 | compilerSpecId = args[++argi]; 213 | } 214 | else if (checkArgument("-prescript", args, argi)) { 215 | String scriptName = args[++argi]; 216 | String[] scriptArgs = getSubArguments(args, argi); 217 | argi += scriptArgs.length; 218 | preScripts.add(new Pair<>(scriptName, scriptArgs)); 219 | } 220 | else if (checkArgument("-postscript", args, argi)) { 221 | String scriptName = args[++argi]; 222 | String[] scriptArgs = getSubArguments(args, argi); 223 | argi += scriptArgs.length; 224 | postScripts.add(new Pair<>(scriptName, scriptArgs)); 225 | } 226 | else if (checkArgument("-scriptPath", args, argi)) { 227 | options.setScriptDirectories(args[++argi]); 228 | } 229 | else if (checkArgument("-propertiesPath", args, argi)) { 230 | options.setPropertiesFileDirectories(args[++argi]); 231 | } 232 | else if (checkArgument("-import", args, argi)) { 233 | File inputFile = new File(args[++argi]); 234 | if (!inputFile.isDirectory() && !inputFile.isFile()) { 235 | throw new InvalidInputException( 236 | inputFile.getAbsolutePath() + " is not a valid directory or file."); 237 | } 238 | 239 | LibHeadlessAnalyzer.checkValidFilename(inputFile); 240 | 241 | filesToImport.add(inputFile); 242 | 243 | // Keep checking for OS-expanded files 244 | String nextArg; 245 | 246 | while (argi < (args.length - 1)) { 247 | nextArg = args[++argi]; 248 | 249 | // Check if next argument is a parameter 250 | if (nextArg.charAt(0) == '-') { 251 | argi--; 252 | break; 253 | } 254 | 255 | File otherFile = new File(nextArg); 256 | if (!otherFile.isFile() && !otherFile.isDirectory()) { 257 | throw new InvalidInputException( 258 | otherFile.getAbsolutePath() + " is not a valid directory or file."); 259 | } 260 | 261 | LibHeadlessAnalyzer.checkValidFilename(otherFile); 262 | 263 | filesToImport.add(otherFile); 264 | } 265 | } 266 | else if ("-connect".equals(args[argi])) { 267 | if ((argi + 1) < args.length) { 268 | arg = args[argi + 1]; 269 | if (!arg.startsWith("-")) { 270 | // serverUID is optional argument after -connect 271 | serverUID = arg; 272 | ++argi; 273 | } 274 | } 275 | } 276 | else if ("-commit".equals(args[argi])) { 277 | String comment = null; 278 | if ((argi + 1) < args.length) { 279 | arg = args[argi + 1]; 280 | if (!arg.startsWith("-")) { 281 | // comment is optional argument after -commit 282 | comment = arg; 283 | ++argi; 284 | } 285 | } 286 | options.setCommitFiles(true, comment); 287 | } 288 | else if (checkArgument("-keystore", args, argi)) { 289 | keystorePath = args[++argi]; 290 | File keystore = new File(keystorePath); 291 | if (!keystore.isFile()) { 292 | throw new InvalidInputException( 293 | keystore.getAbsolutePath() + " is not a valid keystore file."); 294 | } 295 | } 296 | else if (arg.equalsIgnoreCase("-p")) { 297 | allowPasswordPrompt = true; 298 | } 299 | else if ("-analysisTimeoutPerFile".equalsIgnoreCase(args[argi])) { 300 | options.setPerFileAnalysisTimeout(args[++argi]); 301 | } 302 | else if ("-process".equals(args[argi])) { 303 | if (options.runScriptsNoImport) { 304 | throw new InvalidInputException( 305 | "The -process option may only be specified once."); 306 | } 307 | String processBinary = null; 308 | if ((argi + 1) < args.length) { 309 | arg = args[argi + 1]; 310 | if (!arg.startsWith("-")) { 311 | // processBinary is optional argument after -process 312 | processBinary = arg; 313 | ++argi; 314 | } 315 | } 316 | options.setRunScriptsNoImport(true, processBinary); 317 | } 318 | else if ("-recursive".equals(args[argi])) { 319 | options.enableRecursiveProcessing(true); 320 | } 321 | else if ("-readOnly".equalsIgnoreCase(args[argi])) { 322 | options.enableReadOnlyProcessing(true); 323 | } 324 | else if (checkArgument("-max-cpu", args, argi)) { 325 | String cpuVal = args[++argi]; 326 | try { 327 | options.setMaxCpu(Integer.parseInt(cpuVal)); 328 | } 329 | catch (NumberFormatException nfe) { 330 | throw new InvalidInputException("Invalid value for max-cpu: " + cpuVal); 331 | } 332 | } 333 | else if ("-okToDelete".equalsIgnoreCase(args[argi])) { 334 | options.setOkToDelete(true); 335 | } 336 | else { 337 | throw new InvalidInputException("Bad argument: " + arg); 338 | } 339 | } 340 | 341 | // Set up pre and post scripts 342 | options.setPreScriptsWithArgs(preScripts); 343 | options.setPostScriptsWithArgs(postScripts); 344 | 345 | // Set loader and loader args 346 | options.setLoader(loaderName, loaderArgs); 347 | 348 | // Set user-specified language and compiler spec 349 | options.setLanguageAndCompiler(languageId, compilerSpecId); 350 | 351 | // Set up optional Ghidra Server authenticator 352 | try { 353 | options.setClientCredentials(serverUID, keystorePath, allowPasswordPrompt); 354 | } 355 | catch (IOException e) { 356 | throw new InvalidInputException( 357 | "Failed to install Ghidra Server authenticator: " + e.getMessage()); 358 | } 359 | 360 | // If -process was specified, inputFiles must be null or inputFiles.size must be 0. 361 | // Otherwise when not in -process mode, inputFiles can be null or inputFiles.size can be 0, 362 | // only if there are scripts to be run. 363 | if (options.runScriptsNoImport) { 364 | 365 | if (filesToImport != null && filesToImport.size() > 0) { 366 | System.err.print("Must use either -process or -import parameters, but not both."); 367 | System.err.print(" -process runs scripts over existing program(s) in a project, " + 368 | "whereas -import"); 369 | System.err.println(" imports new programs and runs scripts and/or analyzes them " + 370 | "after import."); 371 | System.exit(EXIT_CODE_ERROR); 372 | } 373 | 374 | if (options.overwrite) { 375 | Msg.warn(LibHeadlessAnalyzer.class, 376 | "The -overwrite parameter does not apply to -process mode. Ignoring overwrite " + 377 | "and continuing."); 378 | } 379 | 380 | if (options.readOnly && options.okToDelete) { 381 | System.err.println("You have specified the conflicting parameters -readOnly and " + 382 | "-okToDelete. Please pick one and try again."); 383 | System.exit(EXIT_CODE_ERROR); 384 | } 385 | } 386 | else { 387 | if (filesToImport == null || filesToImport.size() == 0) { 388 | if (options.preScripts.isEmpty() && options.postScripts.isEmpty()) { 389 | System.err.println("Nothing to do ... must specify -import, -process, or " + 390 | "prescript and/or postscript."); 391 | System.exit(EXIT_CODE_ERROR); 392 | } 393 | else { 394 | Msg.warn(LibHeadlessAnalyzer.class, 395 | "Neither the -import parameter nor the -process parameter was specified; " + 396 | "therefore, the specified prescripts and/or postscripts will be " + 397 | "executed without any type of program context."); 398 | } 399 | } 400 | } 401 | 402 | if (options.commit) { 403 | if (options.readOnly) { 404 | System.err.println("Can not use -commit and -readOnly at the same time."); 405 | System.exit(EXIT_CODE_ERROR); 406 | } 407 | } 408 | 409 | // Implied commit, only if not in process mode 410 | if (!options.commit && ghidraURL != null) { 411 | if (!options.readOnly) { 412 | // implied commit 413 | options.setCommitFiles(true, null); 414 | } 415 | else { 416 | Msg.warn(LibHeadlessAnalyzer.class, 417 | "-readOnly mode is on: for -process, changes will not be saved."); 418 | } 419 | } 420 | } 421 | 422 | /** 423 | * Prints out the usage details and exits the Java application with an exit code that 424 | * indicates error. 425 | * 426 | * @param execCmd the command used to run the headless analyzer from the calling method. 427 | */ 428 | public static void usage(String execCmd) { 429 | System.out.println("Headless Analyzer Usage: " + execCmd); 430 | System.out.println(" [/]"); 431 | System.out.println( 432 | " | ghidra://[:]/[/]"); 433 | System.out.println( 434 | " [[-import [|]+] | [-process []]]"); 435 | System.out.println(" [-preScript ]"); 436 | System.out.println(" [-postScript ]"); 437 | System.out.println(" [-scriptPath \"[;...]\"]"); 438 | System.out.println(" [-propertiesPath \"[;...]\"]"); 439 | System.out.println(" [-scriptlog ]"); 440 | System.out.println(" [-log ]"); 441 | System.out.println(" [-overwrite]"); 442 | System.out.println(" [-recursive]"); 443 | System.out.println(" [-readOnly]"); 444 | System.out.println(" [-deleteProject]"); 445 | System.out.println(" [-noanalysis]"); 446 | System.out.println(" [-processor ]"); 447 | System.out.println(" [-cspec ]"); 448 | System.out.println(" [-analysisTimeoutPerFile ]"); 449 | System.out.println(" [-keystore ]"); 450 | System.out.println(" [-connect ]"); 451 | System.out.println(" [-p]"); 452 | System.out.println(" [-commit [\"\"]]"); 453 | System.out.println(" [-okToDelete]"); 454 | System.out.println(" [-max-cpu ]"); 455 | System.out.println(" [-loader ]"); 456 | // ** NOTE: please update 'analyzeHeadlessREADME.html' if changing command line parameters ** 457 | 458 | if (Platform.CURRENT_PLATFORM.getOperatingSystem() != OperatingSystem.WINDOWS) { 459 | System.out.println(); 460 | System.out.println( 461 | " - All uses of $GHIDRA_HOME or $USER_HOME in script path must be" + 462 | " preceded by '\\'"); 463 | } 464 | System.out.println(); 465 | System.out.println( 466 | "Please refer to 'analyzeHeadlessREADME.html' for detailed usage examples " + 467 | "and notes."); 468 | 469 | System.out.println(); 470 | System.exit(EXIT_CODE_ERROR); 471 | } 472 | 473 | private void usage() { 474 | usage("analyzeHeadless"); 475 | } 476 | 477 | private String[] getSubArguments(String[] args, int argi) { 478 | List subArgs = new LinkedList<>(); 479 | int i = argi + 1; 480 | while (i < args.length && !args[i].startsWith("-")) { 481 | subArgs.add(args[i++]); 482 | } 483 | return subArgs.toArray(new String[0]); 484 | } 485 | 486 | private boolean checkArgument(String optionName, String[] args, int argi) 487 | throws InvalidInputException { 488 | // everything after this requires an argument 489 | if (!optionName.equalsIgnoreCase(args[argi])) { 490 | return false; 491 | } 492 | if (argi + 1 == args.length) { 493 | throw new InvalidInputException(optionName + " requires an argument"); 494 | } 495 | return true; 496 | } 497 | } 498 | -------------------------------------------------------------------------------- /GhidraLib/LibHeadlessErrorLogger.java: -------------------------------------------------------------------------------- 1 | /* ### 2 | * IP: GHIDRA 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /* 18 | Modified version of the Headless analyzer code, for any feedback, contact NADER SHALLABI at nader@nosecurecode.com 19 | */ 20 | 21 | package com.nosecurecode.libghidra; 22 | 23 | import java.io.*; 24 | 25 | import ghidra.util.ErrorLogger; 26 | 27 | /** 28 | * Custom headless error logger which is used when log4j is disabled. 29 | */ 30 | class LibHeadlessErrorLogger implements ErrorLogger { 31 | 32 | private PrintWriter logWriter; 33 | 34 | LibHeadlessErrorLogger(File logFile) { 35 | if (logFile != null) { 36 | setLogFile(logFile); 37 | } 38 | } 39 | 40 | synchronized void setLogFile(File logFile) { 41 | try { 42 | if (logFile == null) { 43 | if (logWriter != null) { 44 | writeLog("INFO", "File logging disabled"); 45 | logWriter.close(); 46 | logWriter = null; 47 | } 48 | return; 49 | } 50 | PrintWriter w = new PrintWriter(new FileWriter(logFile)); 51 | if (logWriter != null) { 52 | writeLog("INFO ", "Switching log file to: " + logFile); 53 | logWriter.close(); 54 | } 55 | logWriter = w; 56 | } 57 | catch (IOException e) { 58 | System.err.println("Failed to open log file " + logFile + ": " + e.getMessage()); 59 | } 60 | } 61 | 62 | private synchronized void writeLog(String line) { 63 | if (logWriter == null) { 64 | return; 65 | } 66 | logWriter.println(line); 67 | } 68 | 69 | private synchronized void writeLog(String level, String[] lines) { 70 | if (logWriter == null) { 71 | return; 72 | } 73 | for (String line : lines) { 74 | writeLog(level + " " + line); 75 | } 76 | logWriter.flush(); 77 | } 78 | 79 | private synchronized void writeLog(String level, String text) { 80 | if (logWriter == null) { 81 | return; 82 | } 83 | writeLog(level, chopLines(text)); 84 | } 85 | 86 | private synchronized void writeLog(String level, String text, Throwable throwable) { 87 | if (logWriter == null) { 88 | return; 89 | } 90 | writeLog(level, chopLines(text)); 91 | for (StackTraceElement element : throwable.getStackTrace()) { 92 | writeLog(level + " " + element.toString()); 93 | } 94 | logWriter.flush(); 95 | } 96 | 97 | private String[] chopLines(String text) { 98 | text = text.replace("\r", ""); 99 | return text.split("\n"); 100 | } 101 | 102 | @Override 103 | public void debug(Object originator, Object message) { 104 | // TODO for some reason debug is off 105 | // writeLog("DEBUG", message.toString()); 106 | } 107 | 108 | @Override 109 | public void debug(Object originator, Object message, Throwable throwable) { 110 | // TODO for some reason debug is off 111 | // writeLog("DEBUG", message.toString(), throwable); 112 | } 113 | 114 | @Override 115 | public void error(Object originator, Object message) { 116 | writeLog("ERROR", message.toString()); 117 | } 118 | 119 | @Override 120 | public void error(Object originator, Object message, Throwable throwable) { 121 | writeLog("ERROR", message.toString(), throwable); 122 | } 123 | 124 | @Override 125 | public void info(Object originator, Object message) { 126 | writeLog("INFO ", message.toString()); 127 | } 128 | 129 | @Override 130 | public void info(Object originator, Object message, Throwable throwable) { 131 | // TODO for some reason tracing is off 132 | // writeLog("INFO ", message.toString(), throwable); 133 | } 134 | 135 | @Override 136 | public void trace(Object originator, Object message) { 137 | // TODO for some reason tracing i soff 138 | // writeLog("TRACE", message.toString()); 139 | } 140 | 141 | @Override 142 | public void trace(Object originator, Object message, Throwable throwable) { 143 | // TODO for some reason tracing is off 144 | // writeLog("TRACE", message.toString(), throwable); 145 | } 146 | 147 | @Override 148 | public void warn(Object originator, Object message) { 149 | writeLog("WARN ", message.toString()); 150 | } 151 | 152 | @Override 153 | public void warn(Object originator, Object message, Throwable throwable) { 154 | writeLog("WARN ", message.toString(), throwable); 155 | } 156 | 157 | } 158 | -------------------------------------------------------------------------------- /GhidraLib/LibHeadlessOptions.java: -------------------------------------------------------------------------------- 1 | /* ### 2 | * IP: GHIDRA 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /* 18 | Modified version of the Headless analyzer code, for any feedback, contact NADER SHALLABI at nader@nosecurecode.com 19 | */ 20 | 21 | package com.nosecurecode.libghidra; 22 | 23 | import java.io.IOException; 24 | import java.util.*; 25 | 26 | import generic.jar.ResourceFile; 27 | import generic.stl.Pair; 28 | import ghidra.app.util.opinion.Loader; 29 | import ghidra.app.util.opinion.LoaderService; 30 | import ghidra.framework.client.HeadlessClientAuthenticator; 31 | import ghidra.program.model.lang.*; 32 | import ghidra.program.util.DefaultLanguageService; 33 | import ghidra.util.exception.InvalidInputException; 34 | 35 | /** 36 | * Options for headless analyzer. 37 | *

38 | * Option state may be adjusted to reflect assumed options 39 | * during processing. If multiple invocations of either 40 | * {@link LibHeadlessAnalyzer#processLocal(String, String, String, List)} or 41 | * {@link LibHeadlessAnalyzer#processURL(java.net.URL, List)} are performed, 42 | * these options should be reset and adjusted as necessary. 43 | */ 44 | 45 | public class LibHeadlessOptions { 46 | 47 | // -process and -import 48 | String domainFileNameToProcess; // may include pattern 49 | boolean runScriptsNoImport; 50 | 51 | // -preScript 52 | List> preScripts; 53 | Map preScriptFileMap; 54 | 55 | // -postScript 56 | List> postScripts; 57 | Map postScriptFileMap; 58 | 59 | // -scriptPath 60 | List scriptPaths; 61 | 62 | // -propertiesPath 63 | List propertiesFileStrPaths; 64 | List propertiesFilePaths; 65 | 66 | // -overwrite 67 | boolean overwrite; 68 | 69 | // -recursive 70 | boolean recursive; 71 | 72 | // -readOnly 73 | boolean readOnly; 74 | 75 | // -deleteProject 76 | boolean deleteProject; 77 | 78 | // -noanalysis 79 | boolean analyze; 80 | 81 | // -processor 82 | Language language; 83 | 84 | // -cspec 85 | CompilerSpec compilerSpec; 86 | 87 | // -analysisTimeoutPerFile 88 | int perFileTimeout; 89 | 90 | // -keystore 91 | String keystore; 92 | 93 | // -connect 94 | String connectUserID; 95 | 96 | // -p 97 | boolean allowPasswordPrompt; 98 | 99 | // -commit 100 | boolean commit; 101 | String commitComment; 102 | 103 | // -okToDelete 104 | boolean okToDelete; 105 | 106 | // -max-cpu 107 | int maxcpu; 108 | 109 | // -loader 110 | Class loaderClass; 111 | List> loaderArgs; 112 | 113 | // ------------------------------------------------------------------------------------------- 114 | 115 | /** 116 | * Creates a new headless options object with default settings. 117 | */ 118 | LibHeadlessOptions() { 119 | reset(); 120 | } 121 | 122 | /** 123 | * Resets the options to its default settings. 124 | */ 125 | public void reset() { 126 | domainFileNameToProcess = null; 127 | runScriptsNoImport = false; 128 | preScripts = new LinkedList<>(); 129 | preScriptFileMap = null; 130 | postScripts = new LinkedList<>(); 131 | postScriptFileMap = null; 132 | scriptPaths = null; 133 | propertiesFileStrPaths = new ArrayList<>(); 134 | propertiesFilePaths = new ArrayList<>(); 135 | overwrite = false; 136 | recursive = false; 137 | readOnly = false; 138 | deleteProject = false; 139 | analyze = true; 140 | language = null; 141 | compilerSpec = null; 142 | perFileTimeout = -1; 143 | keystore = null; 144 | connectUserID = null; 145 | allowPasswordPrompt = false; 146 | commit = false; 147 | commitComment = null; 148 | okToDelete = false; 149 | maxcpu = 0; 150 | loaderClass = null; 151 | loaderArgs = null; 152 | } 153 | 154 | /** 155 | * Set to run scripts (and optionally, analysis) without importing a 156 | * program. Scripts will run on specified folder or program that already 157 | * exists in the project. 158 | * 159 | * @param runScriptsOnly if true, no imports will occur and scripts 160 | * (and analysis, if enabled) will run on the specified existing program 161 | * or directory of programs. 162 | * @param filename name of specific project file or folder to be processed (the location 163 | * is passed in elsewhere by the user). If null, user has not specified 164 | * a file to process -- therefore, the entire directory will be processed. 165 | * The filename should not include folder path elements which should be 166 | * specified separately via project or URL specification. 167 | * @throws IllegalArgumentException if the specified filename is invalid and contains the 168 | * path separator character '/'. 169 | */ 170 | public void setRunScriptsNoImport(boolean runScriptsOnly, String filename) { 171 | if (filename != null) { 172 | filename = filename.trim(); 173 | if (filename.indexOf("/") >= 0) { 174 | throw new IllegalArgumentException("invalid filename specified"); 175 | } 176 | } 177 | this.runScriptsNoImport = runScriptsOnly; 178 | this.domainFileNameToProcess = filename; 179 | } 180 | 181 | /** 182 | * Set the ordered list of scripts to execute immediately following import and 183 | * prior to analyzing an imported program. If import not performed, 184 | * these scripts will execute once prior to any post-scripts. 185 | * 186 | * @param preScripts list of script names 187 | */ 188 | public void setPreScripts(List preScripts) { 189 | List> preScriptsEmptyArgs = new LinkedList<>(); 190 | for (String preScript : preScripts) { 191 | preScriptsEmptyArgs.add(new Pair<>(preScript, new String[0])); 192 | } 193 | setPreScriptsWithArgs(preScriptsEmptyArgs); 194 | } 195 | 196 | /** 197 | * Set the ordered list of scripts and their arguments to execute immediately following import 198 | * and prior to analyzing an imported program. If import not performed, 199 | * these scripts will execute once prior to any post-scripts. 200 | * 201 | * @param preScripts list of script names/script argument pairs 202 | */ 203 | public void setPreScriptsWithArgs(List> preScripts) { 204 | this.preScripts = preScripts; 205 | this.preScriptFileMap = null; 206 | } 207 | 208 | /** 209 | * Set the ordered list of scripts to execute immediately following import and 210 | * and analysis of a program. If import not performed, 211 | * these scripts will execute once following any pre-scripts. 212 | * 213 | * @param postScripts list of script names 214 | */ 215 | public void setPostScripts(List postScripts) { 216 | List> postScriptsEmptyArgs = new LinkedList<>(); 217 | for (String postScript : postScripts) { 218 | postScriptsEmptyArgs.add(new Pair<>(postScript, new String[0])); 219 | } 220 | setPostScriptsWithArgs(postScriptsEmptyArgs); 221 | } 222 | 223 | /** 224 | * Set the ordered list of scripts to execute immediately following import and 225 | * and analysis of a program. If import not performed, 226 | * these scripts will execute once following any pre-scripts. 227 | * 228 | * @param postScripts list of script names/script argument pairs 229 | */ 230 | public void setPostScriptsWithArgs(List> postScripts) { 231 | this.postScripts = postScripts; 232 | this.postScriptFileMap = null; 233 | } 234 | 235 | /** 236 | * Set the script source directories to be searched for secondary scripts. 237 | * The default set of enabled script directories within the Ghidra installation 238 | * will be appended to the specified list of newPaths. 239 | * Individual Paths may be constructed relative to Ghidra installation directory, 240 | * User home directory, or absolute system paths. Examples: 241 | *

242 | 	 *     Path.GHIDRA_HOME + "/Ghidra/Features/Base/ghidra_scripts"
243 | 	 *     Path.USER_HOME + "/Ghidra/Features/Base/ghidra_scripts"
244 | 	 *     "/shared/ghidra_scripts"
245 | 	 * 
246 | * 247 | * @param newPaths list of directories to be searched. 248 | */ 249 | public void setScriptDirectories(List newPaths) { 250 | scriptPaths = newPaths; 251 | } 252 | 253 | /** 254 | * List of valid script directory paths separated by a ';'. 255 | * The default set of enabled script directories within the Ghidra installation 256 | * will be appended to the specified list of newPaths. 257 | * Individual Paths may be constructed relative to Ghidra installation directory, 258 | * User home directory, or absolute system paths. Examples: 259 | *
260 | 	 * 		Path.GHIDRA_HOME + "/Ghidra/Features/Base/ghidra_scripts"
261 | 	 *      Path.USER_HOME + "/Ghidra/Features/Base/ghidra_scripts"
262 | 	 *		"/shared/ghidra_scripts"
263 | 	 * 
264 | * @param paths semicolon (';') separated list of directory paths 265 | */ 266 | public void setScriptDirectories(String paths) { 267 | String[] pathArray = paths.split(";"); 268 | setScriptDirectories(Arrays.asList(pathArray)); 269 | } 270 | 271 | /** 272 | * Sets a single location for .properties files associated with GhidraScripts. 273 | * 274 | * Typically, .properties files should be located in the same directory as their corresponding 275 | * scripts. However, this method may need to be used when circumstances make it impossible to 276 | * have both files in the same directory (i.e., if the scripts are included in ghidra.jar). 277 | * 278 | * @param path location of .properties file(s) 279 | */ 280 | public void setPropertiesFileDirectory(String path) { 281 | propertiesFileStrPaths = new ArrayList<>(); 282 | propertiesFileStrPaths.add(path); 283 | } 284 | 285 | /** 286 | * Sets one or more locations to find .properties files associated with GhidraScripts. 287 | * 288 | * Typically, .properties files should be located in the same directory as their corresponding 289 | * scripts. However, this method may need to be used when circumstances make it impossible to 290 | * have both files in the same directory (i.e., if the scripts are included in ghidra.jar). 291 | * 292 | * @param newPaths potential locations of .properties file(s) 293 | */ 294 | public void setPropertiesFileDirectories(List newPaths) { 295 | propertiesFileStrPaths = newPaths; 296 | } 297 | 298 | /** 299 | * List of valid .properties file directory paths, separated by a ';'. 300 | * 301 | * Typically, .properties files should be located in the same directory as their corresponding 302 | * scripts. However, this method may need to be used when circumstances make it impossible to 303 | * have both files in the same directory (i.e., if the scripts are included in ghidra.jar). 304 | * 305 | * @param paths String representation of directories (each separated by ';') 306 | */ 307 | public void setPropertiesFileDirectories(String paths) { 308 | String[] pathArray = paths.split(";"); 309 | setPropertiesFileDirectories(Arrays.asList(pathArray)); 310 | } 311 | 312 | /** 313 | * During import, the default behavior is to skip the import if a conflict occurs 314 | * within the destination folder. This method can be used to force the original 315 | * conflicting file to be removed prior to import. 316 | * If the pre-existing file is versioned, the commit option must also be 317 | * enabled to have the overwrite remove the versioned file. 318 | * 319 | * @param enabled if true conflicting domain files will be removed from the 320 | * project prior to importing the new file. 321 | */ 322 | public void enableOverwriteOnConflict(boolean enabled) { 323 | this.overwrite = enabled; 324 | } 325 | 326 | /** 327 | * This method can be used to enable recursive processing of files during 328 | * -import or -process modes. In order for recursive processing of files to 329 | * occur, the user must have specified a directory (and not a specific file) 330 | * for the Headless Analyzer to import or process. 331 | * 332 | * @param enabled if true, enables recursive processing 333 | */ 334 | public void enableRecursiveProcessing(boolean enabled) { 335 | this.recursive = enabled; 336 | } 337 | 338 | /** 339 | * When readOnly processing is enabled, any changes made by script or analyzers 340 | * are discarded when the Headless Analyzer exits. When used with import mode, 341 | * the imported program file will not be saved to the project or repository. 342 | * 343 | * @param enabled if true, enables readOnly processing or import 344 | */ 345 | public void enableReadOnlyProcessing(boolean enabled) { 346 | this.readOnly = enabled; 347 | } 348 | 349 | /** 350 | * Set project delete flag which allows temporary projects created 351 | * to be deleted upon completion. This option has no effect if a 352 | * Ghidra URL or an existing project was specified. This option 353 | * will be assumed when importing with the readOnly option enabled. 354 | * 355 | * @param enabled if true a created project will be deleted when 356 | * processing is complete. 357 | */ 358 | public void setDeleteCreatedProjectOnClose(boolean enabled) { 359 | this.deleteProject = enabled; 360 | } 361 | 362 | /** 363 | * Auto-analysis is enabled by default following import. This method can be 364 | * used to change the enablement of auto-analysis. 365 | * 366 | * @param enabled True if auto-analysis should be enabled; otherwise, false. 367 | */ 368 | public void enableAnalysis(boolean enabled) { 369 | this.analyze = enabled; 370 | } 371 | 372 | /** 373 | * Sets the language and compiler spec from the provided input. Any null value will attempt 374 | * a "best-guess" if possible. 375 | * 376 | * @param languageId The language to set. 377 | * @param compilerSpecId The compiler spec to set. 378 | * @throws InvalidInputException if the language and compiler spec combination is not valid. 379 | */ 380 | public void setLanguageAndCompiler(String languageId, String compilerSpecId) 381 | throws InvalidInputException { 382 | if (languageId == null && compilerSpecId == null) { 383 | return; 384 | } 385 | if (languageId == null) { 386 | throw new InvalidInputException("Compiler spec specified without specifying language."); 387 | } 388 | try { 389 | language = 390 | DefaultLanguageService.getLanguageService().getLanguage(new LanguageID(languageId)); 391 | if (compilerSpecId == null) { 392 | compilerSpec = language.getDefaultCompilerSpec(); 393 | } 394 | else { 395 | compilerSpec = language.getCompilerSpecByID(new CompilerSpecID(compilerSpecId)); 396 | } 397 | } 398 | catch (LanguageNotFoundException e) { 399 | language = null; 400 | compilerSpec = null; 401 | throw new InvalidInputException("Unsupported language: " + languageId); 402 | } 403 | catch (CompilerSpecNotFoundException e) { 404 | language = null; 405 | compilerSpec = null; 406 | throw new InvalidInputException("Compiler spec \"" + compilerSpecId + 407 | "\" is not supported for language \"" + languageId + "\""); 408 | } 409 | } 410 | 411 | /** 412 | * Set analyzer timeout on a per-file basis. 413 | * 414 | * @param stringInSecs timeout value in seconds (as a String) 415 | * @throws InvalidInputException if the timeout value was not a valid value 416 | */ 417 | public void setPerFileAnalysisTimeout(String stringInSecs) throws InvalidInputException { 418 | try { 419 | perFileTimeout = Integer.parseInt(stringInSecs); 420 | } 421 | catch (NumberFormatException nfe) { 422 | throw new InvalidInputException( 423 | "'" + stringInSecs + "' is not a valid integer representation."); 424 | } 425 | } 426 | 427 | public void setPerFileAnalysisTimeout(int secs) { 428 | perFileTimeout = secs; 429 | } 430 | 431 | /** 432 | * Set Ghidra Server client credentials to be used with "shared" projects. 433 | * 434 | * @param userID optional userId to use if server permits the user to use 435 | * a userId which differs from the process owner name. 436 | * @param keystorePath file path to keystore file containing users private key 437 | * to be used with PKI or SSH based authentication. 438 | * @param allowPasswordPrompt if true the user may be prompted for passwords 439 | * via the console (stdin). Please note that the Java console will echo 440 | * the password entry to the terminal which may be undesirable. 441 | * @throws IOException if an error occurs while opening the specified keystorePath. 442 | */ 443 | public void setClientCredentials(String userID, String keystorePath, 444 | boolean allowPasswordPrompt) throws IOException { 445 | this.connectUserID = userID; 446 | this.keystore = keystorePath; 447 | this.allowPasswordPrompt = allowPasswordPrompt; 448 | HeadlessClientAuthenticator.installHeadlessClientAuthenticator(userID, keystorePath, 449 | allowPasswordPrompt); 450 | } 451 | 452 | /** 453 | * Enable committing of processed files to the repository which backs the specified 454 | * project. 455 | * 456 | * @param commit if true imported files will be committed 457 | * @param comment optional comment to use when committing 458 | */ 459 | public void setCommitFiles(boolean commit, String comment) { 460 | this.commit = commit; 461 | this.commitComment = comment; 462 | } 463 | 464 | public void setOkToDelete(boolean deleteOk) { 465 | okToDelete = deleteOk; 466 | } 467 | 468 | /** 469 | * Sets the maximum number of cpu cores to use during headless processing. 470 | * 471 | * @param cpu The maximum number of cpu cores to use during headless processing. 472 | * Setting it to 0 or a negative integer is equivalent to setting it to 1. 473 | */ 474 | public void setMaxCpu(int cpu) { 475 | this.maxcpu = cpu; 476 | System.setProperty("cpu.core.limit", Integer.toString(cpu)); 477 | 478 | } 479 | 480 | /** 481 | * Sets the loader to use for imports, as well as any loader-specific arguments. A null loader 482 | * will attempt "best-guess" if possible. Loader arguments are not supported if a "best-guess" 483 | * is made. 484 | * 485 | * @param loaderName The name (simple class name) of the loader to use. 486 | * @param loaderArgs A list of loader-specific arguments. Could be null if there are none. 487 | * @throws InvalidInputException if an invalid loader name was specified, or if loader arguments 488 | * were specified but a loader was not. 489 | */ 490 | public void setLoader(String loaderName, List> loaderArgs) 491 | throws InvalidInputException { 492 | if (loaderName != null) { 493 | this.loaderClass = LoaderService.getLoaderClassByName(loaderName); 494 | if (this.loaderClass == null) { 495 | throw new InvalidInputException("Invalid loader name specified: " + loaderName); 496 | } 497 | this.loaderArgs = loaderArgs; 498 | } 499 | else { 500 | if (loaderArgs != null && loaderArgs.size() > 0) { 501 | throw new InvalidInputException( 502 | "Loader arguments defined without a loader being specified."); 503 | } 504 | this.loaderClass = null; 505 | this.loaderArgs = null; 506 | } 507 | } 508 | } 509 | -------------------------------------------------------------------------------- /GhidraLib/LibHeadlessScript.java: -------------------------------------------------------------------------------- 1 | /* ### 2 | * IP: GHIDRA 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /* 18 | Modified version of the Headless analyzer code, for any feedback, contact NADER SHALLABI at nader@nosecurecode.com 19 | */ 20 | 21 | package com.nosecurecode.libghidra; 22 | 23 | import java.io.IOException; 24 | 25 | import generic.jar.ResourceFile; 26 | import ghidra.app.script.*; 27 | import ghidra.framework.model.DomainFolder; 28 | import ghidra.util.InvalidNameException; 29 | 30 | /** 31 | * This class is analogous to GhidraScript, except that is only meant to be used with 32 | * the HeadlessAnalyzer. That is, if a user writes a script that extends HeadlessScript, 33 | * it should only be run in the Headless environment. 34 | */ 35 | public abstract class LibHeadlessScript extends GhidraScript { 36 | 37 | /** 38 | * Options for controlling disposition of program after the current script completes. 39 | */ 40 | public enum LibHeadlessContinuationOption { 41 | /** 42 | * Continue running scripts and/or analysis; -import and -process 43 | * modes complete normally. 44 | */ 45 | CONTINUE, 46 | 47 | /** 48 | * Continue running scripts and/or analysis; 49 | * -import mode does not save program, 50 | * -process mode deletes program. 51 | */ 52 | CONTINUE_THEN_DELETE, 53 | 54 | /** 55 | * Abort any scripts or analysis that come after this script; 56 | * -import mode does not save program, -process mode deletes program. 57 | */ 58 | ABORT_AND_DELETE, 59 | 60 | /** 61 | * Abort any scripts or analysis that come after this script; -import mode does 62 | * save program (but it may not be processed completely), 63 | * -process mode completes normally, minus scripts or analysis that 64 | * runs after the ABORT request. 65 | */ 66 | ABORT 67 | } 68 | 69 | private LibHeadlessAnalyzer headless = null; 70 | 71 | private LibHeadlessContinuationOption currentOption = LibHeadlessContinuationOption.CONTINUE; 72 | private LibHeadlessContinuationOption scriptSetOption = null; 73 | 74 | private boolean runningInnerScript = false; 75 | 76 | // This is necessary because it determine when we nullify the 'scriptSetOption' variable 77 | private void setRunningInnerScript(boolean b) { 78 | runningInnerScript = b; 79 | } 80 | 81 | /** 82 | * Sets the current headless instance -- doing so gives the user the ability to manipulate 83 | * headless analyzer-specific parameters. 84 | *

85 | * This method is declared with no access modifier to only allow package-level (no subclass) 86 | * access. This method is meant to only be used by the HeadlessAnalyzer class. 87 | * 88 | * @param ha HeadlessAnalyzer instance 89 | */ 90 | void setHeadlessInstance(LibHeadlessAnalyzer ha) { 91 | headless = ha; 92 | } 93 | 94 | /** 95 | * Sets the "beginning-of-script" continuation status. 96 | *

97 | * This method is declare with no access modifier to only allow package-level (no 98 | * subclass) access. This method is meant to only be used by the HeadlessAnalyzer class. 99 | * 100 | * @param option initial continuation option for this script 101 | */ 102 | void setInitialContinuationOption(LibHeadlessContinuationOption option) { 103 | currentOption = option; 104 | } 105 | 106 | /** 107 | * Returns the final resolved continuation option (after script processing is done). 108 | *

109 | * The continuation option specifies whether to continue or abort follow-on processing, 110 | * and whether to delete or keep the current program. 111 | *

112 | * This method is declared with no access modifier to only allow package-level (no 113 | * subclass) access. This method is meant to only be used by the HeadlessAnalyzer class. 114 | * 115 | * @return the script's final HeadlessContinuationOption 116 | */ 117 | LibHeadlessContinuationOption getContinuationOption() { 118 | return currentOption; 119 | } 120 | 121 | /** 122 | * Checks to see if this script is running in headless mode (it should be!). 123 | *

124 | * This method should be called at the beginning of every public method in HeadlessScript 125 | * that accesses HeadlessAnalyzer methods (for instance, 'headless.isAnalysisEnabled()'). 126 | * The call to this method can not be placed in the constructor, because 'setHeadlessInstance', 127 | * which connects the script with the current headless instance, is not called until after the 128 | * call to the constructor. 129 | * 130 | * @throws ImproperUseException if not in headless mode or headless instance not set 131 | */ 132 | private void checkHeadlessStatus() throws ImproperUseException { 133 | if (headless == null || !isRunningHeadless()) { 134 | throw new ImproperUseException("This method can only be used in the headless case!"); 135 | } 136 | } 137 | 138 | /** 139 | * Stores a key/value pair in the HeadlessAnalyzer instance for later use. 140 | *

141 | * This method, along with the 'getStoredHeadlessValue' method, is useful for debugging and 142 | * testing the Headless Analyzer (when the user has directly instantiated the HeadlessAnalyzer 143 | * instead of running it from analyzeHeadless.sh or analyzeHeadless.bat). This method is 144 | * intended to allow a HeadlessScript to store variables that reflect the current state of 145 | * processing (at the time the script is being run). Storing variables in the HeadlessAnalyzer 146 | * instance may be the only way to access the state of processing during cases when the user 147 | * is forced to run in -readOnly mode, or if there is a value that is only accessible at the 148 | * scripts stage. 149 | * 150 | * @param key storage key in String form 151 | * @param value value to store 152 | * @throws ImproperUseException if not in headless mode or headless instance not set 153 | * @see #getStoredHeadlessValue(String) 154 | * @see #headlessStorageContainsKey(String) 155 | */ 156 | public void storeHeadlessValue(String key, Object value) throws ImproperUseException { 157 | checkHeadlessStatus(); 158 | headless.addVariableToStorage(key, value); 159 | } 160 | 161 | /** 162 | * Get stored value by key from the HeadlessAnalyzer instance. 163 | *

164 | * This method, along with the 'storedHeadlessValue' method, is useful for debugging and 165 | * testing the Headless Analyzer (when the user has directly instantiated the HeadlessAnalyzer 166 | * instead of running it from analyzeHeadless.sh or analyzeHeadless.bat). This method is 167 | * intended to allow a HeadlessScript to store variables that reflect the current state of 168 | * processing (at the time the script is being run). Storing variables in the HeadlessAnalyzer 169 | * instance may be the only way to access the state of processing during cases when the user 170 | * is forced to run in -readOnly mode, or if there is a value that is only accessible at the 171 | * scripts stage. 172 | * 173 | * @param key key to retrieve the desired stored value 174 | * @return stored Object, or null if none exists for that key 175 | * @throws ImproperUseException if not in headless mode or headless instance not set 176 | * @see #storeHeadlessValue(String, Object) 177 | * @see #headlessStorageContainsKey(String) 178 | */ 179 | public Object getStoredHeadlessValue(String key) throws ImproperUseException { 180 | checkHeadlessStatus(); 181 | return headless.getVariableFromStorage(key); 182 | } 183 | 184 | /** 185 | * Returns whether the specified key was stored in the HeadlessAnalyzer instance. 186 | * 187 | * @param key value of key to check for in Headless Analyzer instance 188 | * @return true if the specified key exists 189 | * @throws ImproperUseException if not in headless mode or headless instance not set 190 | * @see #storeHeadlessValue(String, Object) 191 | * @see #getStoredHeadlessValue(String) 192 | */ 193 | public boolean headlessStorageContainsKey(String key) throws ImproperUseException { 194 | checkHeadlessStatus(); 195 | return headless.storageContainsKey(key); 196 | } 197 | 198 | /** 199 | * Sets the continuation option for this script 200 | *

201 | * The continuation option specifies whether to continue or abort follow-on processing, 202 | * and whether to delete or keep the current program. 203 | * 204 | * @param option HeadlessContinuationOption set by this script 205 | * @see #getHeadlessContinuationOption() 206 | */ 207 | public void setHeadlessContinuationOption(LibHeadlessContinuationOption option) { 208 | scriptSetOption = option; 209 | } 210 | 211 | /** 212 | * Returns the continuation option for the current script (if one has not been set in this 213 | * script, the option defaults to CONTINUE). 214 | *

215 | * The continuation option specifies whether to continue or abort follow-on processing, 216 | * and whether to delete or keep the current program. 217 | * 218 | * @return the current HeadlessContinuationOption 219 | * @see #setHeadlessContinuationOption(LibHeadlessContinuationOption) 220 | */ 221 | public LibHeadlessContinuationOption getHeadlessContinuationOption() { 222 | if (scriptSetOption == null) { 223 | return LibHeadlessContinuationOption.CONTINUE; 224 | } 225 | 226 | return scriptSetOption; 227 | } 228 | 229 | /** 230 | * Enables or disables analysis according to the passed-in boolean value. 231 | *

232 | * A script that calls this method should run as a 'preScript', since preScripts 233 | * execute before analysis would typically run. Running the script as a 'postScript' 234 | * is ineffective, since the stage at which analysis would have happened has already 235 | * passed. 236 | *

237 | * This change will persist throughout the current HeadlessAnalyzer session, unless 238 | * changed again (in other words, once analysis is enabled via script for one program, 239 | * it will also be enabled for future programs in the current session, unless changed). 240 | * 241 | * @param b true to enable analysis, false to disable analysis 242 | * @throws ImproperUseException if not in headless mode or headless instance not set 243 | * @see #isHeadlessAnalysisEnabled() 244 | */ 245 | public void enableHeadlessAnalysis(boolean b) throws ImproperUseException { 246 | checkHeadlessStatus(); 247 | 248 | headless.getOptions().enableAnalysis(b); 249 | } 250 | 251 | /** 252 | * Returns whether analysis is currently enabled or disabled in the HeadlessAnalyzer. 253 | * 254 | * @return whether analysis has been enabled or not 255 | * @throws ImproperUseException if not in headless mode or headless instance not set 256 | * @see #enableHeadlessAnalysis(boolean) 257 | */ 258 | public boolean isHeadlessAnalysisEnabled() throws ImproperUseException { 259 | checkHeadlessStatus(); 260 | 261 | return headless.getOptions().analyze; 262 | } 263 | 264 | /** 265 | * Returns whether the headless analyzer is currently set to -import mode or not (if not, 266 | * it is in -process mode). The use of -import mode implies that binaries are actively being 267 | * imported into the project (with optional scripts/analysis). The use of -process mode implies 268 | * that existing project files are being processed (using scripts and/or analysis). 269 | * 270 | * @return whether we are in -import mode or not 271 | * @throws ImproperUseException if not in headless mode or headless instance not set 272 | */ 273 | public boolean isImporting() throws ImproperUseException { 274 | checkHeadlessStatus(); 275 | 276 | return !headless.getOptions().runScriptsNoImport; 277 | } 278 | 279 | /** 280 | * Changes the path in the Ghidra project where imported files are saved. 281 | * The passed-in path is assumed to be relative to the project root. For example, 282 | * if the directory structure for the Ghidra project looks like this: 283 | * 284 | *

285 | 	 * 		MyGhidraProject:
286 | 	 * 		  /dir1
287 | 	 * 		    /innerDir1
288 | 	 * 		    /innerDir2
289 | 	 * 
290 | * 291 | * Then the following usage would ensure that any files imported after this call would 292 | * be saved in the MyGhidraProject:/dir1/innerDir2 folder. 293 | *
294 | 	 * 		setHeadlessImportDirectory("dir1/innerDir2");
295 | 	 * 
296 | * In contrast, the following usages would add new folders to the Ghidra project and save 297 | * the imported files into the newly-created path: 298 | *
299 | 	 * 		setHeadlessImportDirectory("innerDir2/my/folder");
300 | 	 * 
301 | * changes the directory structure to: 302 | *
303 | 	 * 		MyGhidraProject:
304 | 	 * 		  /dir1
305 | 	 * 		    /innerDir1
306 | 	 * 		    /innerDir2
307 | 	 * 		      /my
308 | 	 * 		        /folder
309 | 	 * 
310 | * and: 311 | *
312 | 	 * 		setHeadlessImportDirectory("newDir/saveHere");
313 | 	 * 
314 | * changes the directory structure to: 315 | *
316 | 	 * 		MyGhidraProject:
317 | 	 * 		  /dir1
318 | 	 * 		    /innerDir1
319 | 	 * 			/innerDir2
320 | 	 *		  /newDir
321 | 	 * 		    /saveHere
322 | 	 * 
323 | * As in the examples above, if the desired folder does not already exist, it is created. 324 | *

325 | * A change in the import save folder will persist throughout the current HeadlessAnalyzer 326 | * session, unless changed again (in other words, once the import directory has been changed, 327 | * it will remain the 'save' directory for import files in the current session, unless changed). 328 | *

329 | * To revert back to the default import location (that which was specified via command line), 330 | * pass the null object as the argument to this method, as below: 331 | *

332 | 	 * 		setHeadlessImportDirectory(null);	// Sets import save directory to default
333 | 	 * 
334 | * If a file with the same name already exists in the desired location, it will only be 335 | * overwritten if "-overwrite" is true. 336 | *

337 | * This method is only applicable when using the HeadlessAnalyzer -import mode and 338 | * is ineffective in -process mode. 339 | * 340 | * @param importDir the absolute path (relative to root) where inputs will be saved 341 | * @throws ImproperUseException if not in headless mode or headless instance not set 342 | * @throws IOException if there are issues creating the folder 343 | * @throws InvalidNameException if folder name is invalid 344 | */ 345 | public void setHeadlessImportDirectory(String importDir) 346 | throws ImproperUseException, IOException, InvalidNameException { 347 | checkHeadlessStatus(); 348 | 349 | // Do nothing if not importing -- we don't want to have arbitrary folders 350 | // created when not being used! 351 | 352 | if (!headless.getOptions().runScriptsNoImport) { 353 | DomainFolder saveFolder = null; 354 | 355 | if (importDir != null) { 356 | 357 | if (!importDir.startsWith("/")) { 358 | importDir = "/" + importDir; 359 | } 360 | 361 | // Add ending slash so the dir gets created for server projects 362 | if (!importDir.endsWith("/")) { 363 | importDir += "/"; 364 | } 365 | 366 | // Gets folder -- creates path if it doesn't already exist 367 | saveFolder = headless.getDomainFolder(importDir, true); 368 | } 369 | 370 | headless.setSaveFolder(saveFolder); 371 | } 372 | } 373 | 374 | /** 375 | * Returns whether analysis for the current program has timed out. 376 | *

377 | * Analysis will time out only in the case where: 378 | *

    379 | *
  1. the users has set an analysis timeout period using the -analysisTimeoutPerFile 380 | * parameter
  2. 381 | *
  3. analysis is enabled and has completed
  4. 382 | *
  5. the current script is being run as a postScript (since postScripts run after 383 | * analysis)
  6. 384 | *
385 | * 386 | * @return whether analysis timeout occurred 387 | * @throws ImproperUseException if not in headless mode or headless instance not set 388 | */ 389 | public boolean analysisTimeoutOccurred() throws ImproperUseException { 390 | checkHeadlessStatus(); 391 | return headless.checkAnalysisTimedOut(); 392 | } 393 | 394 | @Override 395 | public void runScript(String scriptName, String[] scriptArguments, GhidraState scriptState) 396 | throws Exception { 397 | 398 | boolean isHeadlessScript = false; 399 | 400 | if (scriptSetOption != null) { 401 | resolveContinuationOptionWith(scriptSetOption); 402 | scriptSetOption = null; 403 | } 404 | ResourceFile scriptSource = GhidraScriptUtil.findScriptByName(scriptName); 405 | if (scriptSource != null) { 406 | GhidraScriptProvider provider = GhidraScriptUtil.getProvider(scriptSource); 407 | 408 | if (provider == null) { 409 | throw new IOException("Attempting to run subscript '" + scriptName + 410 | "': unable to run this script type."); 411 | } 412 | 413 | GhidraScript script = provider.getScriptInstance(scriptSource, writer); 414 | isHeadlessScript = script instanceof LibHeadlessScript ? true : false; 415 | 416 | if (potentialPropertiesFileLocs.size() > 0) { 417 | script.setPotentialPropertiesFileLocations(potentialPropertiesFileLocs); 418 | } 419 | 420 | if (scriptState == state) { 421 | updateStateFromVariables(); 422 | } 423 | 424 | if (isHeadlessScript) { 425 | ((LibHeadlessScript) script).setHeadlessInstance(headless); 426 | ((LibHeadlessScript) script).setRunningInnerScript(true); 427 | } 428 | 429 | script.setScriptArgs(scriptArguments); 430 | 431 | script.execute(scriptState, monitor, writer); 432 | 433 | if (scriptState == state) { 434 | loadVariablesFromState(); 435 | } 436 | 437 | // Resolve continuations options, if they have changed 438 | if (isHeadlessScript) { 439 | LibHeadlessContinuationOption innerScriptOpt = 440 | ((LibHeadlessScript) script).getHeadlessContinuationOption(); 441 | 442 | if (innerScriptOpt != null) { 443 | resolveContinuationOptionWith(innerScriptOpt); 444 | } 445 | 446 | ((LibHeadlessScript) script).setRunningInnerScript(false); 447 | } 448 | 449 | return; 450 | } 451 | 452 | throw new IllegalArgumentException("Script does not exist: " + scriptName); 453 | } 454 | 455 | @Override 456 | public void cleanup(boolean success) { 457 | resolveContinuationOption(); 458 | 459 | if (!runningInnerScript) { 460 | scriptSetOption = null; 461 | } 462 | } 463 | 464 | private void resolveContinuationOption() { 465 | resolveContinuationOptionWith(scriptSetOption); 466 | } 467 | 468 | /** 469 | * Resolve continuation options according to the table in 'analyzeHeadlessREADME.html'. 470 | * (See "Multiple Scripts" section). 471 | * 472 | * @param opt continuation option to combine with current continuation option 473 | */ 474 | private void resolveContinuationOptionWith(LibHeadlessContinuationOption opt) { 475 | 476 | if (opt == null) { 477 | return; 478 | } 479 | 480 | switch (currentOption) { 481 | 482 | case CONTINUE: 483 | currentOption = opt; 484 | break; 485 | 486 | case CONTINUE_THEN_DELETE: 487 | switch (opt) { 488 | case ABORT: 489 | 490 | case ABORT_AND_DELETE: 491 | currentOption = LibHeadlessContinuationOption.ABORT_AND_DELETE; 492 | break; 493 | 494 | default: 495 | break; 496 | } 497 | break; 498 | 499 | case ABORT_AND_DELETE: 500 | // nothing changes 501 | break; 502 | 503 | case ABORT: 504 | // nothing changes 505 | break; 506 | } 507 | } 508 | } 509 | -------------------------------------------------------------------------------- /GhidraLib/LibHeadlessTimedTaskMonitor.java: -------------------------------------------------------------------------------- 1 | /* ### 2 | * IP: GHIDRA 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /* 18 | Modified version of the Headless analyzer code, for any feedback, contact NADER SHALLABI at nader@nosecurecode.com 19 | */ 20 | 21 | package com.nosecurecode.libghidra; 22 | 23 | import java.util.Timer; 24 | import java.util.TimerTask; 25 | 26 | import ghidra.util.exception.CancelledException; 27 | import ghidra.util.task.CancelledListener; 28 | import ghidra.util.task.TaskMonitor; 29 | 30 | /** 31 | * Monitor used by Headless Analyzer for "timeout" functionality 32 | */ 33 | public class LibHeadlessTimedTaskMonitor implements TaskMonitor { 34 | 35 | private Timer timer = new Timer(); 36 | private volatile boolean isCancelled; 37 | 38 | LibHeadlessTimedTaskMonitor(int timeoutSecs) { 39 | isCancelled = false; 40 | timer.schedule(new TimeOutTask(), timeoutSecs * 1000); 41 | } 42 | 43 | private class TimeOutTask extends TimerTask { 44 | @Override 45 | public void run() { 46 | LibHeadlessTimedTaskMonitor.this.cancel(); 47 | } 48 | } 49 | 50 | @Override 51 | public boolean isCancelled() { 52 | return isCancelled; 53 | } 54 | 55 | @Override 56 | public void setShowProgressValue(boolean showProgressValue) { 57 | // stub 58 | } 59 | 60 | @Override 61 | public void setMessage(String message) { 62 | // stub 63 | } 64 | 65 | @Override 66 | public String getMessage() { 67 | return null; 68 | } 69 | 70 | @Override 71 | public void setProgress(long value) { 72 | // stub 73 | } 74 | 75 | @Override 76 | public void initialize(long max) { 77 | // stub 78 | } 79 | 80 | @Override 81 | public void setMaximum(long max) { 82 | // stub 83 | } 84 | 85 | @Override 86 | public long getMaximum() { 87 | return 0; 88 | } 89 | 90 | @Override 91 | public void setIndeterminate(boolean indeterminate) { 92 | // stub 93 | } 94 | 95 | @Override 96 | public boolean isIndeterminate() { 97 | return false; 98 | } 99 | 100 | @Override 101 | public void checkCanceled() throws CancelledException { 102 | if (isCancelled()) { 103 | throw new CancelledException(); 104 | } 105 | } 106 | 107 | @Override 108 | public void incrementProgress(long incrementAmount) { 109 | // stub 110 | } 111 | 112 | @Override 113 | public long getProgress() { 114 | return 0; 115 | } 116 | 117 | @Override 118 | public void cancel() { 119 | timer.cancel(); // Terminate the timer thread 120 | isCancelled = true; 121 | } 122 | 123 | @Override 124 | public void addCancelledListener(CancelledListener listener) { 125 | // stub 126 | } 127 | 128 | @Override 129 | public void removeCancelledListener(CancelledListener listener) { 130 | // stub 131 | } 132 | 133 | @Override 134 | public void setCancelEnabled(boolean enable) { 135 | // stub 136 | } 137 | 138 | @Override 139 | public boolean isCancelEnabled() { 140 | return true; 141 | } 142 | 143 | @Override 144 | public void clearCanceled() { 145 | isCancelled = false; 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /GhidraLib/LibProgramHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | This interface implements the callback functionality, for any feedback, contact NADER SHALLABI at nader@nosecurecode.com 3 | */ 4 | 5 | 6 | package com.nosecurecode.libghidra; 7 | 8 | import ghidra.program.model.listing.Program; 9 | 10 | /** 11 | * Implement this interface in the class that need to have a callback after Ghidra processing 12 | */ 13 | public interface LibProgramHandler { 14 | public void PostProcessHandler(Program program); 15 | } 16 | -------------------------------------------------------------------------------- /Media/Sample1.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nshalabi/Coding-Ghidra/dad58814a184591adc301d82ec0b59f11989e619/Media/Sample1.PNG -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Coding Ghidra - Library and Samples 2 | 3 | Ghidra Java code can be bundled in a **Java Archive** file (**JAR**), this makes it easy to use Ghidra as a library for more advanced reverse engineering tasks, in this repository you will find samples to demonstrate how to use Ghidra as a disassembler library. 4 | 5 | ## Prerequisites 6 | 7 | The projects in this repository were created using IntelliJ IDEA, a Community Edition is available for free at [www.jetbrains.com](www.jetbrains.com), additionally, the samples need to be bundled with Ghidra JAR file, you will need to build this file yourself, run `buildGhidraJar.bat` batch file located in Ghidera archive under the `support` folder, then copy the generated ghidra.jar to projects lib file, for example, Sample1\src\lib. 8 | 9 | **UPDATE**: With Ghidra 10.1.1 there were many breaking changes in code tha was fixed, however, for building the JAR file, an additional step is needed: 10 | 11 | Before building the JAR file, edit the file `{GHIDRA_FOLDER}/Ghidra/Features/ByteViewer/Module.manifest` and **delete** the line (the only line) in the file **"EXCLUDE FROM GHIDRA JAR: true"**, then run the JAR build sxcript, if this step is missing, the headless analzer will fail to load due to a class loading error (since the mentioned library is missing from the JAR archive). 12 | 13 | Please note that the JAR file does not include all of Ghidra modules, if you want to include all modules, then adjust the manifest of each library as previously suggested and customize the `{GHIDRA_FOLDER}/Ghidra/Features/Base/ghidra_scripts/BuildGhidraJarScript.java` by removing the comment next to the **"addAllModules()"** call and then run the JAR build script. 14 | 15 | ## Library Design 16 | 17 | Ghidra supports a headless analysis mode that can be used to automate many of Ghidra's functionality, this mode can also be used with Ghidra plugins. The **GhidraLib** is a Java wrapper to Ghidra headless mode, most of the code is a copy from the existing Ghidra headless code branch with modifications to help hook the analysis process using a callback interface to enable access to analysis data, the headless analysis process can be started with two methods: 18 | 19 | ``` 20 | public static void runHeadlessCmd(String headlessCmd, LibProgramHandler handler) 21 | 22 | public static void runHeadlessCmd(String [] headlessCmdArgs, LibProgramHandler handler 23 | ``` 24 | 25 | Both methods are identical, the only difference is in the way the headless command is passed. The first method uses a command line in precisely the same format of the headless analyzer, for example, the following command string imports a binary /binaries/binary1.exe to a local Ghidra Project named Project1. Analysis is on by default. 26 | 27 | `/Users/user/ghidra/projects Project1 -import /binaries/binary1.exe` 28 | 29 | The second format uses string tokens instead, for example: 30 | 31 | `{"/Users/user/ghidra/projects", "-import", "/binaries/binary1.exe"}` 32 | 33 | The second argument is the callback (handler), this handler is an instance of an object that implements the **LibProgramHandler** interface and it's only method: 34 | 35 | ``` 36 | public interface LibProgramHandler { 37 | public void PostProcessHandler(Program program); 38 | } 39 | ``` 40 | 41 | When invoking headless analysis using any of the previous methods, the analysis code will pass an instance of **ghidra.program.model.listing.Program** object immediately after the binary analysis is done, but before it fully returns to invoking code (if handler parameter is not null), this object is the outcome of the headless analysis. As an example, the following code uses this object to dump all program imports: 42 | 43 | ``` 44 | // Get a list of external functions used 45 | FunctionIterator externalFunctions = program.getListing().getExternalFunctions(); 46 | 47 | // Print all functions in the program: [return type] [calling convention] [function name] 48 | while (externalFunctions.hasNext()) { 49 | 50 | Function function = externalFunctions.next(); 51 | 52 | System.out.println( function.getReturnType() + " " + function.getCallingConvention() + " " + function.getName() ); 53 | } 54 | ``` 55 | 56 | ## Objective 57 | 58 | The headless mode combined with Ghidra plugins, are powerful automation tools, the GhidraLibrary inherits all those features and allows for it to be easily embedded in your own, standalone applications or integration layer with other applications/solutions. 59 | 60 | ## Roadmap 61 | 62 | 1. Add more samples and use cases. 63 | 2. Add more event handlers (for example, PreProcessHandler, Pre/PostScriptRunHandler). 64 | 65 | More samples will be added in the future, and all are welcome to contribute. 66 | 67 | --- 68 | 69 | ## Sample 1 70 | 71 | Demonstrates basic usage of library initialization, loading a binary file for analysis and listing all external functions 72 | 73 | ![Sample1](https://github.com/nshalabi/Coding-Ghidra/blob/master/Media/Sample1.PNG "Sample1") 74 | 75 | ## Sample 2 76 | 77 | Using Ghidra in Vulnerability Research, based on the [Ghidra Plugin Development for Vulnerability Research - Part-1](https://www.somersetrecon.com/blog/2019/ghidra-plugin-development-for-vulnerability-research-part-1) article. The code demonstrates using cross references to identify code units calling external functions. 78 | 79 | **Sample output** 80 | 81 | [Function]>-----Address of calling function----->[External Function] 82 | 83 | `[FUN_00403ed2]>-----00403ee7----->[lstrcpynA]` 84 | 85 | `[FUN_00405a0c]>-----00405a19----->[lstrcpynA]` 86 | 87 | # License 88 | 89 | ``` 90 | Copyright 2019 Nader Shallabi. All rights reserved. 91 | 92 | "CODING GHIDRA" CAN BE COPIED AND/OR DISTRIBUTED WITHOUT ANY EXPRESS PERMISSION OF NADER SHALLABI. 93 | 94 | THIS SOFTWARE IS PROVIDED BY NADER SHALLABI ''AS IS'' AND ANY EXPRESS OR IMPLIED 95 | WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 96 | FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL NADER SHALLABI 97 | OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 98 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 99 | OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 100 | AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 101 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 102 | ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 103 | 104 | The views and conclusions contained in the software and documentation are those of the authors and 105 | should not be interpreted as representing official policies, either expressed or implied, of Nader Shallabi. 106 | ``` 107 | -------------------------------------------------------------------------------- /Sample1/.idea/description.html: -------------------------------------------------------------------------------- 1 | Simple Java application that includes a class with main() method -------------------------------------------------------------------------------- /Sample1/.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Sample1/.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Sample1/.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Sample1/.idea/project-template.xml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Sample1/.idea/uiDesigner.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /Sample1/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Sample1/.idea/workspace.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 10 | 11 | 16 | 17 | 18 | this.gp = createGhidraTestProject(projectName); 19 | createGhidraTestProject 20 | programManager 21 | 22 | 23 | 24 | 26 | 27 | 29 | 30 | 31 | 32 | 33 | 34 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 1552242709104 75 | 89 | 90 | 91 | 92 | 94 | 95 | 97 | 98 | 99 | 100 | 101 | jar://$PROJECT_DIR$/src/lib/ghidra.jar!/ghidra/framework/HeadlessGhidraApplicationConfiguration.class 102 | 33 103 | 105 | 106 | jar://$PROJECT_DIR$/src/lib/ghidra.jar!/ghidra/util/classfinder/ClassSearcher.class 107 | 141 108 | 110 | 111 | jar://$PROJECT_DIR$/src/lib/ghidra.jar!/ghidra/util/classfinder/ClassSearcher.class 112 | 145 113 | 115 | 116 | file://$PROJECT_DIR$/src/com/nosecurecode/Demo.java 117 | 46 118 | 120 | 121 | 122 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | program.getListing().getFunctions(true).next() 131 | JAVA 132 | EXPRESSION 133 | 134 | 135 | program.getListing().getFunctions(true) 136 | JAVA 137 | EXPRESSION 138 | 139 | 140 | program.getListing() 141 | JAVA 142 | EXPRESSION 143 | 144 | 145 | externalFunctions. 146 | JAVA 147 | EXPRESSION 148 | 149 | 150 | externalFunctions.next() 151 | JAVA 152 | EXPRESSION 153 | 154 | 155 | externalFunctions.hasNext() 156 | JAVA 157 | EXPRESSION 158 | 159 | 160 | path.substring(1) 161 | JAVA 162 | EXPRESSION 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 177 | 178 | 179 | 180 | 181 | 182 | No facets are configured 183 | 184 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 200 | 201 | 202 | 203 | 204 | 205 | 11 206 | 207 | 212 | 213 | 214 | 215 | 216 | 217 | GhidraDemo 218 | 219 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 235 | 236 | 237 | 238 | 239 | 240 | -------------------------------------------------------------------------------- /Sample1/GhidraDemo.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Sample1/src/com/nosecurecode/Demo.java: -------------------------------------------------------------------------------- 1 | /* 2 | PROGRAM : Coding Ghidra - Sample 1 3 | AUTHOR : NADER SHALLABI 4 | 5 | This sample code is free for use, redistribution and/or 6 | modification without any explicit permission from the author. 7 | 8 | This sample code is distributed in the hope that it will be useful, 9 | but WITHOUT ANY WARRANTY, implied or explicit. 10 | */ 11 | 12 | package com.nosecurecode; 13 | 14 | import ghidra.program.model.listing.Function; 15 | import ghidra.program.model.listing.FunctionIterator; 16 | import ghidra.program.model.listing.Program; 17 | import com.nosecurecode.libghidra.*; 18 | 19 | /** 20 | * Demo: Dump program external functions, this program demonstrates basic usage of the Coding-Ghidra library 21 | * The sample demonstrates looking for commonly used anti-debugging APIs, such as "IsDebuggerPresent" API function 22 | */ 23 | public class Demo implements LibProgramHandler { 24 | public static void main(String args[]) throws Exception { 25 | 26 | // Option 1 to call headless analyzer using a full command 27 | String headlessCmd = "/Users/nadershallabi/ghidra/projects Project1 -import /Users/nadershallabi/Downloads/1.exe -overwrite"; 28 | 29 | // We need an instance of this class to pass the analyzed program handler 30 | Demo ghidraLibraryDemo = new Demo(); 31 | 32 | // This will kickoff the analysis 33 | LibGhidra.runHeadlessCmd(headlessCmd, ghidraLibraryDemo); 34 | 35 | // Option 2 would be to call the analyzer passing command arguments: 36 | // LibGhidra.runHeadlessCmd(args, ghidraLibraryDemo); 37 | } 38 | 39 | /** 40 | * Use the passed Program instance to dump program imports 41 | * The code highlights the IsDebuggerPresent function 42 | * @param program 43 | */ 44 | @Override 45 | public void PostProcessHandler(Program program) { 46 | 47 | // Get a list of external functions used 48 | FunctionIterator externalFunctions = program.getListing().getExternalFunctions(); 49 | 50 | System.out.println("\033[1;33m"); 51 | System.out.println("================"); 52 | System.out.println("PROGRAM IMPORTS:"); 53 | System.out.println("================"); 54 | 55 | // Print all functions in the program: [return type] [calling convention] [function name] 56 | // Highlight the IsDebuggerPresent() API function 57 | 58 | System.out.println("\033[0;33m"); 59 | 60 | while (externalFunctions.hasNext()) { 61 | Function function = externalFunctions.next(); 62 | if (function.getName().equals("IsDebuggerPresent")) { 63 | System.out.print("\033[1;31m"); 64 | } else { 65 | System.out.print("\033[0;33m"); 66 | } 67 | System.out.println( 68 | function.getReturnType() + " " + function.getCallingConvention() + " " + function.getName() 69 | ); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Sample1/src/com/nosecurecode/libghidra/LibGhidra.java: -------------------------------------------------------------------------------- 1 | /* ### 2 | * IP: GHIDRA 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /* 18 | Modified version of the Headless analyzer code, for any feedback, contact NADER SHALLABI at nader@nosecurecode.com 19 | */ 20 | 21 | package com.nosecurecode.libghidra; 22 | 23 | import java.io.File; 24 | import java.io.IOException; 25 | import java.net.MalformedURLException; 26 | import java.net.URL; 27 | import java.util.*; 28 | 29 | import generic.stl.Pair; 30 | import ghidra.app.util.opinion.Loader; 31 | import ghidra.framework.OperatingSystem; 32 | import ghidra.framework.Platform; 33 | import ghidra.framework.model.DomainFolder; 34 | import ghidra.framework.protocol.ghidra.Handler; 35 | import ghidra.util.Msg; 36 | import ghidra.util.exception.InvalidInputException; 37 | 38 | /** 39 | * Launcher entry point for running headless Ghidra. 40 | */ 41 | public class LibGhidra { 42 | 43 | private static final int EXIT_CODE_ERROR = 1; 44 | 45 | /** 46 | * Runs headless command 47 | * @param headlessCmd 48 | * @param handler 49 | * @throws Exception 50 | */ 51 | public static void runHeadlessCmd(String headlessCmd, 52 | LibProgramHandler handler) throws Exception { 53 | new LibGhidra(headlessCmd.split("\\s+"), handler); 54 | } 55 | 56 | /** 57 | * Run heqdless command (command line arguments) 58 | * @param headlessCmdArgs 59 | * @param handler 60 | * @throws Exception 61 | */ 62 | public static void runHeadlessCmd(String [] headlessCmdArgs, 63 | LibProgramHandler handler) throws Exception { 64 | new LibGhidra(headlessCmdArgs, handler); 65 | } 66 | 67 | /** 68 | * This is the main entry point 69 | * @param args 70 | * @param handler 71 | * @throws Exception 72 | */ 73 | private LibGhidra(String args[], LibProgramHandler handler) throws Exception { 74 | String projectName = null; 75 | String rootFolderPath = null; 76 | URL ghidraURL = null; 77 | List filesToImport = new ArrayList<>(); 78 | int optionStartIndex; 79 | 80 | // Make sure there are arguments 81 | if (args.length < 1) { 82 | usage(); 83 | } 84 | 85 | // Ghidra URL handler registration 86 | Handler.registerHandler(); 87 | 88 | if (args[0].startsWith("ghidra:")) { 89 | optionStartIndex = 1; 90 | try { 91 | ghidraURL = new URL(args[0]); 92 | } 93 | catch (MalformedURLException e) { 94 | System.err.println("Invalid Ghidra URL: " + args[0]); 95 | usage(); 96 | } 97 | } 98 | else { 99 | if (args.length < 2) { 100 | usage(); 101 | } 102 | optionStartIndex = 2; 103 | String projectNameAndFolder = args[1]; 104 | 105 | // Check to see if projectName uses back-slashes (likely if they are using Windows) 106 | projectNameAndFolder = projectNameAndFolder.replaceAll("\\\\", DomainFolder.SEPARATOR); 107 | projectName = projectNameAndFolder; 108 | 109 | rootFolderPath = "/"; 110 | int folderIndex = projectNameAndFolder.indexOf(DomainFolder.SEPARATOR); 111 | if (folderIndex == 0) { 112 | System.err.println(args[1] + " is an invalid project_name/folder_path."); 113 | usage(); 114 | } 115 | else if (folderIndex > 0) { 116 | projectName = projectNameAndFolder.substring(0, folderIndex); 117 | rootFolderPath = projectNameAndFolder.substring(folderIndex); 118 | } 119 | } 120 | 121 | // Determine the desired logging. 122 | File logFile = null; 123 | File scriptLogFile = null; 124 | for (int argi = optionStartIndex; argi < args.length; argi++) { 125 | if (checkArgument("-log", args, argi)) { 126 | logFile = new File(args[++argi]); 127 | } 128 | else if (checkArgument("-scriptlog", args, argi)) { 129 | scriptLogFile = new File(args[++argi]); 130 | } 131 | } 132 | 133 | // Instantiate new headless analyzer and parse options. 134 | LibHeadlessAnalyzer analyzer = 135 | LibHeadlessAnalyzer.getLoggableInstance(logFile, scriptLogFile, true, handler); 136 | LibHeadlessOptions options = analyzer.getOptions(); 137 | parseOptions(options, args, optionStartIndex, ghidraURL, filesToImport); 138 | 139 | // Do the headless processing 140 | try { 141 | if (ghidraURL != null) { 142 | analyzer.processURL(ghidraURL, filesToImport); 143 | } 144 | else { 145 | analyzer.processLocal(args[0], projectName, rootFolderPath, filesToImport); 146 | } 147 | } 148 | catch (Throwable e) { 149 | Msg.error(LibHeadlessAnalyzer.class, 150 | "Abort due to Headless analyzer error: " + e.getMessage(), e); 151 | System.exit(EXIT_CODE_ERROR); 152 | } 153 | } 154 | 155 | /** 156 | * Parses the command line arguments and uses them to set the headless options. 157 | * 158 | * @param options The headless options to set. 159 | * @param args The command line arguments to parse. 160 | * @param startIndex The index into the args array of where to start parsing. 161 | * @param ghidraURL The ghidra server url to connect to, or null if not using a url. 162 | * @param filesToImport A list to put files to import into. 163 | * @throws InvalidInputException if an error occurred parsing the arguments or setting 164 | * the options. 165 | */ 166 | private void parseOptions(LibHeadlessOptions options, String[] args, int startIndex, URL ghidraURL, 167 | List filesToImport) throws InvalidInputException { 168 | 169 | String loaderName = null; 170 | List> loaderArgs = new LinkedList<>(); 171 | String languageId = null; 172 | String compilerSpecId = null; 173 | String keystorePath = null; 174 | String serverUID = null; 175 | boolean allowPasswordPrompt = false; 176 | List> preScripts = new LinkedList<>(); 177 | List> postScripts = new LinkedList<>(); 178 | 179 | for (int argi = startIndex; argi < args.length; argi++) { 180 | 181 | String arg = args[argi]; 182 | if (checkArgument("-log", args, argi)) { 183 | // Already processed 184 | argi++; 185 | } 186 | else if (checkArgument("-scriptlog", args, argi)) { 187 | // Already processed 188 | argi++; 189 | } 190 | else if (arg.equalsIgnoreCase("-overwrite")) { 191 | options.enableOverwriteOnConflict(true); 192 | } 193 | else if (arg.equalsIgnoreCase("-noanalysis")) { 194 | options.enableAnalysis(false); 195 | } 196 | else if (arg.equalsIgnoreCase("-deleteproject")) { 197 | options.setDeleteCreatedProjectOnClose(true); 198 | } 199 | else if (checkArgument("-loader", args, argi)) { 200 | loaderName = args[++argi]; 201 | } 202 | else if (arg.startsWith(Loader.COMMAND_LINE_ARG_PREFIX)) { 203 | if (args[argi + 1].startsWith("-")) { 204 | throw new InvalidInputException(args[argi] + " expects value to follow."); 205 | } 206 | loaderArgs.add(new Pair<>(arg, args[++argi])); 207 | } 208 | else if (checkArgument("-processor", args, argi)) { 209 | languageId = args[++argi]; 210 | } 211 | else if (checkArgument("-cspec", args, argi)) { 212 | compilerSpecId = args[++argi]; 213 | } 214 | else if (checkArgument("-prescript", args, argi)) { 215 | String scriptName = args[++argi]; 216 | String[] scriptArgs = getSubArguments(args, argi); 217 | argi += scriptArgs.length; 218 | preScripts.add(new Pair<>(scriptName, scriptArgs)); 219 | } 220 | else if (checkArgument("-postscript", args, argi)) { 221 | String scriptName = args[++argi]; 222 | String[] scriptArgs = getSubArguments(args, argi); 223 | argi += scriptArgs.length; 224 | postScripts.add(new Pair<>(scriptName, scriptArgs)); 225 | } 226 | else if (checkArgument("-scriptPath", args, argi)) { 227 | options.setScriptDirectories(args[++argi]); 228 | } 229 | else if (checkArgument("-propertiesPath", args, argi)) { 230 | options.setPropertiesFileDirectories(args[++argi]); 231 | } 232 | else if (checkArgument("-import", args, argi)) { 233 | File inputFile = new File(args[++argi]); 234 | if (!inputFile.isDirectory() && !inputFile.isFile()) { 235 | throw new InvalidInputException( 236 | inputFile.getAbsolutePath() + " is not a valid directory or file."); 237 | } 238 | 239 | LibHeadlessAnalyzer.checkValidFilename(inputFile); 240 | 241 | filesToImport.add(inputFile); 242 | 243 | // Keep checking for OS-expanded files 244 | String nextArg; 245 | 246 | while (argi < (args.length - 1)) { 247 | nextArg = args[++argi]; 248 | 249 | // Check if next argument is a parameter 250 | if (nextArg.charAt(0) == '-') { 251 | argi--; 252 | break; 253 | } 254 | 255 | File otherFile = new File(nextArg); 256 | if (!otherFile.isFile() && !otherFile.isDirectory()) { 257 | throw new InvalidInputException( 258 | otherFile.getAbsolutePath() + " is not a valid directory or file."); 259 | } 260 | 261 | LibHeadlessAnalyzer.checkValidFilename(otherFile); 262 | 263 | filesToImport.add(otherFile); 264 | } 265 | } 266 | else if ("-connect".equals(args[argi])) { 267 | if ((argi + 1) < args.length) { 268 | arg = args[argi + 1]; 269 | if (!arg.startsWith("-")) { 270 | // serverUID is optional argument after -connect 271 | serverUID = arg; 272 | ++argi; 273 | } 274 | } 275 | } 276 | else if ("-commit".equals(args[argi])) { 277 | String comment = null; 278 | if ((argi + 1) < args.length) { 279 | arg = args[argi + 1]; 280 | if (!arg.startsWith("-")) { 281 | // comment is optional argument after -commit 282 | comment = arg; 283 | ++argi; 284 | } 285 | } 286 | options.setCommitFiles(true, comment); 287 | } 288 | else if (checkArgument("-keystore", args, argi)) { 289 | keystorePath = args[++argi]; 290 | File keystore = new File(keystorePath); 291 | if (!keystore.isFile()) { 292 | throw new InvalidInputException( 293 | keystore.getAbsolutePath() + " is not a valid keystore file."); 294 | } 295 | } 296 | else if (arg.equalsIgnoreCase("-p")) { 297 | allowPasswordPrompt = true; 298 | } 299 | else if ("-analysisTimeoutPerFile".equalsIgnoreCase(args[argi])) { 300 | options.setPerFileAnalysisTimeout(args[++argi]); 301 | } 302 | else if ("-process".equals(args[argi])) { 303 | if (options.runScriptsNoImport) { 304 | throw new InvalidInputException( 305 | "The -process option may only be specified once."); 306 | } 307 | String processBinary = null; 308 | if ((argi + 1) < args.length) { 309 | arg = args[argi + 1]; 310 | if (!arg.startsWith("-")) { 311 | // processBinary is optional argument after -process 312 | processBinary = arg; 313 | ++argi; 314 | } 315 | } 316 | options.setRunScriptsNoImport(true, processBinary); 317 | } 318 | else if ("-recursive".equals(args[argi])) { 319 | options.enableRecursiveProcessing(true); 320 | } 321 | else if ("-readOnly".equalsIgnoreCase(args[argi])) { 322 | options.enableReadOnlyProcessing(true); 323 | } 324 | else if (checkArgument("-max-cpu", args, argi)) { 325 | String cpuVal = args[++argi]; 326 | try { 327 | options.setMaxCpu(Integer.parseInt(cpuVal)); 328 | } 329 | catch (NumberFormatException nfe) { 330 | throw new InvalidInputException("Invalid value for max-cpu: " + cpuVal); 331 | } 332 | } 333 | else if ("-okToDelete".equalsIgnoreCase(args[argi])) { 334 | options.setOkToDelete(true); 335 | } 336 | else { 337 | throw new InvalidInputException("Bad argument: " + arg); 338 | } 339 | } 340 | 341 | // Set up pre and post scripts 342 | options.setPreScriptsWithArgs(preScripts); 343 | options.setPostScriptsWithArgs(postScripts); 344 | 345 | // Set loader and loader args 346 | options.setLoader(loaderName, loaderArgs); 347 | 348 | // Set user-specified language and compiler spec 349 | options.setLanguageAndCompiler(languageId, compilerSpecId); 350 | 351 | // Set up optional Ghidra Server authenticator 352 | try { 353 | options.setClientCredentials(serverUID, keystorePath, allowPasswordPrompt); 354 | } 355 | catch (IOException e) { 356 | throw new InvalidInputException( 357 | "Failed to install Ghidra Server authenticator: " + e.getMessage()); 358 | } 359 | 360 | // If -process was specified, inputFiles must be null or inputFiles.size must be 0. 361 | // Otherwise when not in -process mode, inputFiles can be null or inputFiles.size can be 0, 362 | // only if there are scripts to be run. 363 | if (options.runScriptsNoImport) { 364 | 365 | if (filesToImport != null && filesToImport.size() > 0) { 366 | System.err.print("Must use either -process or -import parameters, but not both."); 367 | System.err.print(" -process runs scripts over existing program(s) in a project, " + 368 | "whereas -import"); 369 | System.err.println(" imports new programs and runs scripts and/or analyzes them " + 370 | "after import."); 371 | System.exit(EXIT_CODE_ERROR); 372 | } 373 | 374 | if (options.overwrite) { 375 | Msg.warn(LibHeadlessAnalyzer.class, 376 | "The -overwrite parameter does not apply to -process mode. Ignoring overwrite " + 377 | "and continuing."); 378 | } 379 | 380 | if (options.readOnly && options.okToDelete) { 381 | System.err.println("You have specified the conflicting parameters -readOnly and " + 382 | "-okToDelete. Please pick one and try again."); 383 | System.exit(EXIT_CODE_ERROR); 384 | } 385 | } 386 | else { 387 | if (filesToImport == null || filesToImport.size() == 0) { 388 | if (options.preScripts.isEmpty() && options.postScripts.isEmpty()) { 389 | System.err.println("Nothing to do ... must specify -import, -process, or " + 390 | "prescript and/or postscript."); 391 | System.exit(EXIT_CODE_ERROR); 392 | } 393 | else { 394 | Msg.warn(LibHeadlessAnalyzer.class, 395 | "Neither the -import parameter nor the -process parameter was specified; " + 396 | "therefore, the specified prescripts and/or postscripts will be " + 397 | "executed without any type of program context."); 398 | } 399 | } 400 | } 401 | 402 | if (options.commit) { 403 | if (options.readOnly) { 404 | System.err.println("Can not use -commit and -readOnly at the same time."); 405 | System.exit(EXIT_CODE_ERROR); 406 | } 407 | } 408 | 409 | // Implied commit, only if not in process mode 410 | if (!options.commit && ghidraURL != null) { 411 | if (!options.readOnly) { 412 | // implied commit 413 | options.setCommitFiles(true, null); 414 | } 415 | else { 416 | Msg.warn(LibHeadlessAnalyzer.class, 417 | "-readOnly mode is on: for -process, changes will not be saved."); 418 | } 419 | } 420 | } 421 | 422 | /** 423 | * Prints out the usage details and exits the Java application with an exit code that 424 | * indicates error. 425 | * 426 | * @param execCmd the command used to run the headless analyzer from the calling method. 427 | */ 428 | public static void usage(String execCmd) { 429 | System.out.println("Headless Analyzer Usage: " + execCmd); 430 | System.out.println(" [/]"); 431 | System.out.println( 432 | " | ghidra://[:]/[/]"); 433 | System.out.println( 434 | " [[-import [|]+] | [-process []]]"); 435 | System.out.println(" [-preScript ]"); 436 | System.out.println(" [-postScript ]"); 437 | System.out.println(" [-scriptPath \"[;...]\"]"); 438 | System.out.println(" [-propertiesPath \"[;...]\"]"); 439 | System.out.println(" [-scriptlog ]"); 440 | System.out.println(" [-log ]"); 441 | System.out.println(" [-overwrite]"); 442 | System.out.println(" [-recursive]"); 443 | System.out.println(" [-readOnly]"); 444 | System.out.println(" [-deleteProject]"); 445 | System.out.println(" [-noanalysis]"); 446 | System.out.println(" [-processor ]"); 447 | System.out.println(" [-cspec ]"); 448 | System.out.println(" [-analysisTimeoutPerFile ]"); 449 | System.out.println(" [-keystore ]"); 450 | System.out.println(" [-connect ]"); 451 | System.out.println(" [-p]"); 452 | System.out.println(" [-commit [\"\"]]"); 453 | System.out.println(" [-okToDelete]"); 454 | System.out.println(" [-max-cpu ]"); 455 | System.out.println(" [-loader ]"); 456 | // ** NOTE: please update 'analyzeHeadlessREADME.html' if changing command line parameters ** 457 | 458 | if (Platform.CURRENT_PLATFORM.getOperatingSystem() != OperatingSystem.WINDOWS) { 459 | System.out.println(); 460 | System.out.println( 461 | " - All uses of $GHIDRA_HOME or $USER_HOME in script path must be" + 462 | " preceded by '\\'"); 463 | } 464 | System.out.println(); 465 | System.out.println( 466 | "Please refer to 'analyzeHeadlessREADME.html' for detailed usage examples " + 467 | "and notes."); 468 | 469 | System.out.println(); 470 | System.exit(EXIT_CODE_ERROR); 471 | } 472 | 473 | private void usage() { 474 | usage("analyzeHeadless"); 475 | } 476 | 477 | private String[] getSubArguments(String[] args, int argi) { 478 | List subArgs = new LinkedList<>(); 479 | int i = argi + 1; 480 | while (i < args.length && !args[i].startsWith("-")) { 481 | subArgs.add(args[i++]); 482 | } 483 | return subArgs.toArray(new String[0]); 484 | } 485 | 486 | private boolean checkArgument(String optionName, String[] args, int argi) 487 | throws InvalidInputException { 488 | // everything after this requires an argument 489 | if (!optionName.equalsIgnoreCase(args[argi])) { 490 | return false; 491 | } 492 | if (argi + 1 == args.length) { 493 | throw new InvalidInputException(optionName + " requires an argument"); 494 | } 495 | return true; 496 | } 497 | } 498 | -------------------------------------------------------------------------------- /Sample1/src/com/nosecurecode/libghidra/LibHeadlessErrorLogger.java: -------------------------------------------------------------------------------- 1 | /* ### 2 | * IP: GHIDRA 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /* 18 | Modified version of the Headless analyzer code, for any feedback, contact NADER SHALLABI at nader@nosecurecode.com 19 | */ 20 | 21 | package com.nosecurecode.libghidra; 22 | 23 | import java.io.*; 24 | 25 | import ghidra.util.ErrorLogger; 26 | 27 | /** 28 | * Custom headless error logger which is used when log4j is disabled. 29 | */ 30 | class LibHeadlessErrorLogger implements ErrorLogger { 31 | 32 | private PrintWriter logWriter; 33 | 34 | LibHeadlessErrorLogger(File logFile) { 35 | if (logFile != null) { 36 | setLogFile(logFile); 37 | } 38 | } 39 | 40 | synchronized void setLogFile(File logFile) { 41 | try { 42 | if (logFile == null) { 43 | if (logWriter != null) { 44 | writeLog("INFO", "File logging disabled"); 45 | logWriter.close(); 46 | logWriter = null; 47 | } 48 | return; 49 | } 50 | PrintWriter w = new PrintWriter(new FileWriter(logFile)); 51 | if (logWriter != null) { 52 | writeLog("INFO ", "Switching log file to: " + logFile); 53 | logWriter.close(); 54 | } 55 | logWriter = w; 56 | } 57 | catch (IOException e) { 58 | System.err.println("Failed to open log file " + logFile + ": " + e.getMessage()); 59 | } 60 | } 61 | 62 | private synchronized void writeLog(String line) { 63 | if (logWriter == null) { 64 | return; 65 | } 66 | logWriter.println(line); 67 | } 68 | 69 | private synchronized void writeLog(String level, String[] lines) { 70 | if (logWriter == null) { 71 | return; 72 | } 73 | for (String line : lines) { 74 | writeLog(level + " " + line); 75 | } 76 | logWriter.flush(); 77 | } 78 | 79 | private synchronized void writeLog(String level, String text) { 80 | if (logWriter == null) { 81 | return; 82 | } 83 | writeLog(level, chopLines(text)); 84 | } 85 | 86 | private synchronized void writeLog(String level, String text, Throwable throwable) { 87 | if (logWriter == null) { 88 | return; 89 | } 90 | writeLog(level, chopLines(text)); 91 | for (StackTraceElement element : throwable.getStackTrace()) { 92 | writeLog(level + " " + element.toString()); 93 | } 94 | logWriter.flush(); 95 | } 96 | 97 | private String[] chopLines(String text) { 98 | text = text.replace("\r", ""); 99 | return text.split("\n"); 100 | } 101 | 102 | @Override 103 | public void debug(Object originator, Object message) { 104 | // TODO for some reason debug is off 105 | // writeLog("DEBUG", message.toString()); 106 | } 107 | 108 | @Override 109 | public void debug(Object originator, Object message, Throwable throwable) { 110 | // TODO for some reason debug is off 111 | // writeLog("DEBUG", message.toString(), throwable); 112 | } 113 | 114 | @Override 115 | public void error(Object originator, Object message) { 116 | writeLog("ERROR", message.toString()); 117 | } 118 | 119 | @Override 120 | public void error(Object originator, Object message, Throwable throwable) { 121 | writeLog("ERROR", message.toString(), throwable); 122 | } 123 | 124 | @Override 125 | public void info(Object originator, Object message) { 126 | writeLog("INFO ", message.toString()); 127 | } 128 | 129 | @Override 130 | public void info(Object originator, Object message, Throwable throwable) { 131 | // TODO for some reason tracing is off 132 | // writeLog("INFO ", message.toString(), throwable); 133 | } 134 | 135 | @Override 136 | public void trace(Object originator, Object message) { 137 | // TODO for some reason tracing i soff 138 | // writeLog("TRACE", message.toString()); 139 | } 140 | 141 | @Override 142 | public void trace(Object originator, Object message, Throwable throwable) { 143 | // TODO for some reason tracing is off 144 | // writeLog("TRACE", message.toString(), throwable); 145 | } 146 | 147 | @Override 148 | public void warn(Object originator, Object message) { 149 | writeLog("WARN ", message.toString()); 150 | } 151 | 152 | @Override 153 | public void warn(Object originator, Object message, Throwable throwable) { 154 | writeLog("WARN ", message.toString(), throwable); 155 | } 156 | 157 | } 158 | -------------------------------------------------------------------------------- /Sample1/src/com/nosecurecode/libghidra/LibHeadlessOptions.java: -------------------------------------------------------------------------------- 1 | /* ### 2 | * IP: GHIDRA 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /* 18 | Modified version of the Headless analyzer code, for any feedback, contact NADER SHALLABI at nader@nosecurecode.com 19 | */ 20 | 21 | package com.nosecurecode.libghidra; 22 | 23 | import java.io.IOException; 24 | import java.util.*; 25 | 26 | import generic.jar.ResourceFile; 27 | import generic.stl.Pair; 28 | import ghidra.app.util.opinion.Loader; 29 | import ghidra.app.util.opinion.LoaderService; 30 | import ghidra.framework.client.HeadlessClientAuthenticator; 31 | import ghidra.program.model.lang.*; 32 | import ghidra.program.util.DefaultLanguageService; 33 | import ghidra.util.exception.InvalidInputException; 34 | 35 | /** 36 | * Options for headless analyzer. 37 | *

38 | * Option state may be adjusted to reflect assumed options 39 | * during processing. If multiple invocations of either 40 | * {@link LibHeadlessAnalyzer#processLocal(String, String, String, List)} or 41 | * {@link LibHeadlessAnalyzer#processURL(java.net.URL, List)} are performed, 42 | * these options should be reset and adjusted as necessary. 43 | */ 44 | 45 | public class LibHeadlessOptions { 46 | 47 | // -process and -import 48 | String domainFileNameToProcess; // may include pattern 49 | boolean runScriptsNoImport; 50 | 51 | // -preScript 52 | List> preScripts; 53 | Map preScriptFileMap; 54 | 55 | // -postScript 56 | List> postScripts; 57 | Map postScriptFileMap; 58 | 59 | // -scriptPath 60 | List scriptPaths; 61 | 62 | // -propertiesPath 63 | List propertiesFileStrPaths; 64 | List propertiesFilePaths; 65 | 66 | // -overwrite 67 | boolean overwrite; 68 | 69 | // -recursive 70 | boolean recursive; 71 | 72 | // -readOnly 73 | boolean readOnly; 74 | 75 | // -deleteProject 76 | boolean deleteProject; 77 | 78 | // -noanalysis 79 | boolean analyze; 80 | 81 | // -processor 82 | Language language; 83 | 84 | // -cspec 85 | CompilerSpec compilerSpec; 86 | 87 | // -analysisTimeoutPerFile 88 | int perFileTimeout; 89 | 90 | // -keystore 91 | String keystore; 92 | 93 | // -connect 94 | String connectUserID; 95 | 96 | // -p 97 | boolean allowPasswordPrompt; 98 | 99 | // -commit 100 | boolean commit; 101 | String commitComment; 102 | 103 | // -okToDelete 104 | boolean okToDelete; 105 | 106 | // -max-cpu 107 | int maxcpu; 108 | 109 | // -loader 110 | Class loaderClass; 111 | List> loaderArgs; 112 | 113 | // ------------------------------------------------------------------------------------------- 114 | 115 | /** 116 | * Creates a new headless options object with default settings. 117 | */ 118 | LibHeadlessOptions() { 119 | reset(); 120 | } 121 | 122 | /** 123 | * Resets the options to its default settings. 124 | */ 125 | public void reset() { 126 | domainFileNameToProcess = null; 127 | runScriptsNoImport = false; 128 | preScripts = new LinkedList<>(); 129 | preScriptFileMap = null; 130 | postScripts = new LinkedList<>(); 131 | postScriptFileMap = null; 132 | scriptPaths = null; 133 | propertiesFileStrPaths = new ArrayList<>(); 134 | propertiesFilePaths = new ArrayList<>(); 135 | overwrite = false; 136 | recursive = false; 137 | readOnly = false; 138 | deleteProject = false; 139 | analyze = true; 140 | language = null; 141 | compilerSpec = null; 142 | perFileTimeout = -1; 143 | keystore = null; 144 | connectUserID = null; 145 | allowPasswordPrompt = false; 146 | commit = false; 147 | commitComment = null; 148 | okToDelete = false; 149 | maxcpu = 0; 150 | loaderClass = null; 151 | loaderArgs = null; 152 | } 153 | 154 | /** 155 | * Set to run scripts (and optionally, analysis) without importing a 156 | * program. Scripts will run on specified folder or program that already 157 | * exists in the project. 158 | * 159 | * @param runScriptsOnly if true, no imports will occur and scripts 160 | * (and analysis, if enabled) will run on the specified existing program 161 | * or directory of programs. 162 | * @param filename name of specific project file or folder to be processed (the location 163 | * is passed in elsewhere by the user). If null, user has not specified 164 | * a file to process -- therefore, the entire directory will be processed. 165 | * The filename should not include folder path elements which should be 166 | * specified separately via project or URL specification. 167 | * @throws IllegalArgumentException if the specified filename is invalid and contains the 168 | * path separator character '/'. 169 | */ 170 | public void setRunScriptsNoImport(boolean runScriptsOnly, String filename) { 171 | if (filename != null) { 172 | filename = filename.trim(); 173 | if (filename.indexOf("/") >= 0) { 174 | throw new IllegalArgumentException("invalid filename specified"); 175 | } 176 | } 177 | this.runScriptsNoImport = runScriptsOnly; 178 | this.domainFileNameToProcess = filename; 179 | } 180 | 181 | /** 182 | * Set the ordered list of scripts to execute immediately following import and 183 | * prior to analyzing an imported program. If import not performed, 184 | * these scripts will execute once prior to any post-scripts. 185 | * 186 | * @param preScripts list of script names 187 | */ 188 | public void setPreScripts(List preScripts) { 189 | List> preScriptsEmptyArgs = new LinkedList<>(); 190 | for (String preScript : preScripts) { 191 | preScriptsEmptyArgs.add(new Pair<>(preScript, new String[0])); 192 | } 193 | setPreScriptsWithArgs(preScriptsEmptyArgs); 194 | } 195 | 196 | /** 197 | * Set the ordered list of scripts and their arguments to execute immediately following import 198 | * and prior to analyzing an imported program. If import not performed, 199 | * these scripts will execute once prior to any post-scripts. 200 | * 201 | * @param preScripts list of script names/script argument pairs 202 | */ 203 | public void setPreScriptsWithArgs(List> preScripts) { 204 | this.preScripts = preScripts; 205 | this.preScriptFileMap = null; 206 | } 207 | 208 | /** 209 | * Set the ordered list of scripts to execute immediately following import and 210 | * and analysis of a program. If import not performed, 211 | * these scripts will execute once following any pre-scripts. 212 | * 213 | * @param postScripts list of script names 214 | */ 215 | public void setPostScripts(List postScripts) { 216 | List> postScriptsEmptyArgs = new LinkedList<>(); 217 | for (String postScript : postScripts) { 218 | postScriptsEmptyArgs.add(new Pair<>(postScript, new String[0])); 219 | } 220 | setPostScriptsWithArgs(postScriptsEmptyArgs); 221 | } 222 | 223 | /** 224 | * Set the ordered list of scripts to execute immediately following import and 225 | * and analysis of a program. If import not performed, 226 | * these scripts will execute once following any pre-scripts. 227 | * 228 | * @param postScripts list of script names/script argument pairs 229 | */ 230 | public void setPostScriptsWithArgs(List> postScripts) { 231 | this.postScripts = postScripts; 232 | this.postScriptFileMap = null; 233 | } 234 | 235 | /** 236 | * Set the script source directories to be searched for secondary scripts. 237 | * The default set of enabled script directories within the Ghidra installation 238 | * will be appended to the specified list of newPaths. 239 | * Individual Paths may be constructed relative to Ghidra installation directory, 240 | * User home directory, or absolute system paths. Examples: 241 | *

242 | 	 *     Path.GHIDRA_HOME + "/Ghidra/Features/Base/ghidra_scripts"
243 | 	 *     Path.USER_HOME + "/Ghidra/Features/Base/ghidra_scripts"
244 | 	 *     "/shared/ghidra_scripts"
245 | 	 * 
246 | * 247 | * @param newPaths list of directories to be searched. 248 | */ 249 | public void setScriptDirectories(List newPaths) { 250 | scriptPaths = newPaths; 251 | } 252 | 253 | /** 254 | * List of valid script directory paths separated by a ';'. 255 | * The default set of enabled script directories within the Ghidra installation 256 | * will be appended to the specified list of newPaths. 257 | * Individual Paths may be constructed relative to Ghidra installation directory, 258 | * User home directory, or absolute system paths. Examples: 259 | *
260 | 	 * 		Path.GHIDRA_HOME + "/Ghidra/Features/Base/ghidra_scripts"
261 | 	 *      Path.USER_HOME + "/Ghidra/Features/Base/ghidra_scripts"
262 | 	 *		"/shared/ghidra_scripts"
263 | 	 * 
264 | * @param paths semicolon (';') separated list of directory paths 265 | */ 266 | public void setScriptDirectories(String paths) { 267 | String[] pathArray = paths.split(";"); 268 | setScriptDirectories(Arrays.asList(pathArray)); 269 | } 270 | 271 | /** 272 | * Sets a single location for .properties files associated with GhidraScripts. 273 | * 274 | * Typically, .properties files should be located in the same directory as their corresponding 275 | * scripts. However, this method may need to be used when circumstances make it impossible to 276 | * have both files in the same directory (i.e., if the scripts are included in ghidra.jar). 277 | * 278 | * @param path location of .properties file(s) 279 | */ 280 | public void setPropertiesFileDirectory(String path) { 281 | propertiesFileStrPaths = new ArrayList<>(); 282 | propertiesFileStrPaths.add(path); 283 | } 284 | 285 | /** 286 | * Sets one or more locations to find .properties files associated with GhidraScripts. 287 | * 288 | * Typically, .properties files should be located in the same directory as their corresponding 289 | * scripts. However, this method may need to be used when circumstances make it impossible to 290 | * have both files in the same directory (i.e., if the scripts are included in ghidra.jar). 291 | * 292 | * @param newPaths potential locations of .properties file(s) 293 | */ 294 | public void setPropertiesFileDirectories(List newPaths) { 295 | propertiesFileStrPaths = newPaths; 296 | } 297 | 298 | /** 299 | * List of valid .properties file directory paths, separated by a ';'. 300 | * 301 | * Typically, .properties files should be located in the same directory as their corresponding 302 | * scripts. However, this method may need to be used when circumstances make it impossible to 303 | * have both files in the same directory (i.e., if the scripts are included in ghidra.jar). 304 | * 305 | * @param paths String representation of directories (each separated by ';') 306 | */ 307 | public void setPropertiesFileDirectories(String paths) { 308 | String[] pathArray = paths.split(";"); 309 | setPropertiesFileDirectories(Arrays.asList(pathArray)); 310 | } 311 | 312 | /** 313 | * During import, the default behavior is to skip the import if a conflict occurs 314 | * within the destination folder. This method can be used to force the original 315 | * conflicting file to be removed prior to import. 316 | * If the pre-existing file is versioned, the commit option must also be 317 | * enabled to have the overwrite remove the versioned file. 318 | * 319 | * @param enabled if true conflicting domain files will be removed from the 320 | * project prior to importing the new file. 321 | */ 322 | public void enableOverwriteOnConflict(boolean enabled) { 323 | this.overwrite = enabled; 324 | } 325 | 326 | /** 327 | * This method can be used to enable recursive processing of files during 328 | * -import or -process modes. In order for recursive processing of files to 329 | * occur, the user must have specified a directory (and not a specific file) 330 | * for the Headless Analyzer to import or process. 331 | * 332 | * @param enabled if true, enables recursive processing 333 | */ 334 | public void enableRecursiveProcessing(boolean enabled) { 335 | this.recursive = enabled; 336 | } 337 | 338 | /** 339 | * When readOnly processing is enabled, any changes made by script or analyzers 340 | * are discarded when the Headless Analyzer exits. When used with import mode, 341 | * the imported program file will not be saved to the project or repository. 342 | * 343 | * @param enabled if true, enables readOnly processing or import 344 | */ 345 | public void enableReadOnlyProcessing(boolean enabled) { 346 | this.readOnly = enabled; 347 | } 348 | 349 | /** 350 | * Set project delete flag which allows temporary projects created 351 | * to be deleted upon completion. This option has no effect if a 352 | * Ghidra URL or an existing project was specified. This option 353 | * will be assumed when importing with the readOnly option enabled. 354 | * 355 | * @param enabled if true a created project will be deleted when 356 | * processing is complete. 357 | */ 358 | public void setDeleteCreatedProjectOnClose(boolean enabled) { 359 | this.deleteProject = enabled; 360 | } 361 | 362 | /** 363 | * Auto-analysis is enabled by default following import. This method can be 364 | * used to change the enablement of auto-analysis. 365 | * 366 | * @param enabled True if auto-analysis should be enabled; otherwise, false. 367 | */ 368 | public void enableAnalysis(boolean enabled) { 369 | this.analyze = enabled; 370 | } 371 | 372 | /** 373 | * Sets the language and compiler spec from the provided input. Any null value will attempt 374 | * a "best-guess" if possible. 375 | * 376 | * @param languageId The language to set. 377 | * @param compilerSpecId The compiler spec to set. 378 | * @throws InvalidInputException if the language and compiler spec combination is not valid. 379 | */ 380 | public void setLanguageAndCompiler(String languageId, String compilerSpecId) 381 | throws InvalidInputException { 382 | if (languageId == null && compilerSpecId == null) { 383 | return; 384 | } 385 | if (languageId == null) { 386 | throw new InvalidInputException("Compiler spec specified without specifying language."); 387 | } 388 | try { 389 | language = 390 | DefaultLanguageService.getLanguageService().getLanguage(new LanguageID(languageId)); 391 | if (compilerSpecId == null) { 392 | compilerSpec = language.getDefaultCompilerSpec(); 393 | } 394 | else { 395 | compilerSpec = language.getCompilerSpecByID(new CompilerSpecID(compilerSpecId)); 396 | } 397 | } 398 | catch (LanguageNotFoundException e) { 399 | language = null; 400 | compilerSpec = null; 401 | throw new InvalidInputException("Unsupported language: " + languageId); 402 | } 403 | catch (CompilerSpecNotFoundException e) { 404 | language = null; 405 | compilerSpec = null; 406 | throw new InvalidInputException("Compiler spec \"" + compilerSpecId + 407 | "\" is not supported for language \"" + languageId + "\""); 408 | } 409 | } 410 | 411 | /** 412 | * Set analyzer timeout on a per-file basis. 413 | * 414 | * @param stringInSecs timeout value in seconds (as a String) 415 | * @throws InvalidInputException if the timeout value was not a valid value 416 | */ 417 | public void setPerFileAnalysisTimeout(String stringInSecs) throws InvalidInputException { 418 | try { 419 | perFileTimeout = Integer.parseInt(stringInSecs); 420 | } 421 | catch (NumberFormatException nfe) { 422 | throw new InvalidInputException( 423 | "'" + stringInSecs + "' is not a valid integer representation."); 424 | } 425 | } 426 | 427 | public void setPerFileAnalysisTimeout(int secs) { 428 | perFileTimeout = secs; 429 | } 430 | 431 | /** 432 | * Set Ghidra Server client credentials to be used with "shared" projects. 433 | * 434 | * @param userID optional userId to use if server permits the user to use 435 | * a userId which differs from the process owner name. 436 | * @param keystorePath file path to keystore file containing users private key 437 | * to be used with PKI or SSH based authentication. 438 | * @param allowPasswordPrompt if true the user may be prompted for passwords 439 | * via the console (stdin). Please note that the Java console will echo 440 | * the password entry to the terminal which may be undesirable. 441 | * @throws IOException if an error occurs while opening the specified keystorePath. 442 | */ 443 | public void setClientCredentials(String userID, String keystorePath, 444 | boolean allowPasswordPrompt) throws IOException { 445 | this.connectUserID = userID; 446 | this.keystore = keystorePath; 447 | this.allowPasswordPrompt = allowPasswordPrompt; 448 | HeadlessClientAuthenticator.installHeadlessClientAuthenticator(userID, keystorePath, 449 | allowPasswordPrompt); 450 | } 451 | 452 | /** 453 | * Enable committing of processed files to the repository which backs the specified 454 | * project. 455 | * 456 | * @param commit if true imported files will be committed 457 | * @param comment optional comment to use when committing 458 | */ 459 | public void setCommitFiles(boolean commit, String comment) { 460 | this.commit = commit; 461 | this.commitComment = comment; 462 | } 463 | 464 | public void setOkToDelete(boolean deleteOk) { 465 | okToDelete = deleteOk; 466 | } 467 | 468 | /** 469 | * Sets the maximum number of cpu cores to use during headless processing. 470 | * 471 | * @param cpu The maximum number of cpu cores to use during headless processing. 472 | * Setting it to 0 or a negative integer is equivalent to setting it to 1. 473 | */ 474 | public void setMaxCpu(int cpu) { 475 | this.maxcpu = cpu; 476 | System.setProperty("cpu.core.limit", Integer.toString(cpu)); 477 | 478 | } 479 | 480 | /** 481 | * Sets the loader to use for imports, as well as any loader-specific arguments. A null loader 482 | * will attempt "best-guess" if possible. Loader arguments are not supported if a "best-guess" 483 | * is made. 484 | * 485 | * @param loaderName The name (simple class name) of the loader to use. 486 | * @param loaderArgs A list of loader-specific arguments. Could be null if there are none. 487 | * @throws InvalidInputException if an invalid loader name was specified, or if loader arguments 488 | * were specified but a loader was not. 489 | */ 490 | public void setLoader(String loaderName, List> loaderArgs) 491 | throws InvalidInputException { 492 | if (loaderName != null) { 493 | this.loaderClass = LoaderService.getLoaderClassByName(loaderName); 494 | if (this.loaderClass == null) { 495 | throw new InvalidInputException("Invalid loader name specified: " + loaderName); 496 | } 497 | this.loaderArgs = loaderArgs; 498 | } 499 | else { 500 | if (loaderArgs != null && loaderArgs.size() > 0) { 501 | throw new InvalidInputException( 502 | "Loader arguments defined without a loader being specified."); 503 | } 504 | this.loaderClass = null; 505 | this.loaderArgs = null; 506 | } 507 | } 508 | } 509 | -------------------------------------------------------------------------------- /Sample1/src/com/nosecurecode/libghidra/LibHeadlessTimedTaskMonitor.java: -------------------------------------------------------------------------------- 1 | /* ### 2 | * IP: GHIDRA 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /* 18 | Modified version of the Headless analyzer code, for any feedback, contact NADER SHALLABI at nader@nosecurecode.com 19 | */ 20 | 21 | package com.nosecurecode.libghidra; 22 | 23 | import java.util.Timer; 24 | import java.util.TimerTask; 25 | 26 | import ghidra.util.exception.CancelledException; 27 | import ghidra.util.task.CancelledListener; 28 | import ghidra.util.task.TaskMonitor; 29 | 30 | /** 31 | * Monitor used by Headless Analyzer for "timeout" functionality 32 | */ 33 | public class LibHeadlessTimedTaskMonitor implements TaskMonitor { 34 | 35 | private Timer timer = new Timer(); 36 | private volatile boolean isCancelled; 37 | 38 | LibHeadlessTimedTaskMonitor(int timeoutSecs) { 39 | isCancelled = false; 40 | timer.schedule(new TimeOutTask(), timeoutSecs * 1000); 41 | } 42 | 43 | private class TimeOutTask extends TimerTask { 44 | @Override 45 | public void run() { 46 | LibHeadlessTimedTaskMonitor.this.cancel(); 47 | } 48 | } 49 | 50 | @Override 51 | public boolean isCancelled() { 52 | return isCancelled; 53 | } 54 | 55 | @Override 56 | public void setShowProgressValue(boolean showProgressValue) { 57 | // stub 58 | } 59 | 60 | @Override 61 | public void setMessage(String message) { 62 | // stub 63 | } 64 | 65 | @Override 66 | public String getMessage() { 67 | return null; 68 | } 69 | 70 | @Override 71 | public void setProgress(long value) { 72 | // stub 73 | } 74 | 75 | @Override 76 | public void initialize(long max) { 77 | // stub 78 | } 79 | 80 | @Override 81 | public void setMaximum(long max) { 82 | // stub 83 | } 84 | 85 | @Override 86 | public long getMaximum() { 87 | return 0; 88 | } 89 | 90 | @Override 91 | public void setIndeterminate(boolean indeterminate) { 92 | // stub 93 | } 94 | 95 | @Override 96 | public boolean isIndeterminate() { 97 | return false; 98 | } 99 | 100 | @Override 101 | public void checkCanceled() throws CancelledException { 102 | if (isCancelled()) { 103 | throw new CancelledException(); 104 | } 105 | } 106 | 107 | @Override 108 | public void incrementProgress(long incrementAmount) { 109 | // stub 110 | } 111 | 112 | @Override 113 | public long getProgress() { 114 | return 0; 115 | } 116 | 117 | @Override 118 | public void cancel() { 119 | timer.cancel(); // Terminate the timer thread 120 | isCancelled = true; 121 | } 122 | 123 | @Override 124 | public void addCancelledListener(CancelledListener listener) { 125 | // stub 126 | } 127 | 128 | @Override 129 | public void removeCancelledListener(CancelledListener listener) { 130 | // stub 131 | } 132 | 133 | @Override 134 | public void setCancelEnabled(boolean enable) { 135 | // stub 136 | } 137 | 138 | @Override 139 | public boolean isCancelEnabled() { 140 | return true; 141 | } 142 | 143 | @Override 144 | public void clearCanceled() { 145 | isCancelled = false; 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /Sample1/src/com/nosecurecode/libghidra/LibProgramHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | This interface implements the callback functionality, for any feedback, contact NADER SHALLABI at nader@nosecurecode.com 3 | */ 4 | 5 | 6 | package com.nosecurecode.libghidra; 7 | 8 | import ghidra.program.model.listing.Program; 9 | 10 | /** 11 | * Implement this interface in the class that need to have a callback after Ghidra processing 12 | */ 13 | public interface LibProgramHandler { 14 | public void PostProcessHandler(Program program); 15 | } 16 | -------------------------------------------------------------------------------- /Sample1/src/lib/ghidra.jar.txt: -------------------------------------------------------------------------------- 1 | Run buildGhidraJar.bat batch file located in Ghidera archive under the "support" folder, then copy the generated ghidra.jar to this folder -------------------------------------------------------------------------------- /Sample2/.idea/description.html: -------------------------------------------------------------------------------- 1 | Simple Java application that includes a class with main() method -------------------------------------------------------------------------------- /Sample2/.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Sample2/.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Sample2/.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Sample2/.idea/project-template.xml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Sample2/.idea/uiDesigner.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /Sample2/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Sample2/.idea/workspace.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 37 | 38 | 40 | 41 | 43 | 44 | 45 | 46 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 1552242709104 87 | 104 | 105 | 106 | 107 | 109 | 110 | 111 | 112 | program.getListing().getFunctions(true).next() 113 | JAVA 114 | EXPRESSION 115 | 116 | 117 | program.getListing().getFunctions(true) 118 | JAVA 119 | EXPRESSION 120 | 121 | 122 | program.getListing() 123 | JAVA 124 | EXPRESSION 125 | 126 | 127 | externalFunctions. 128 | JAVA 129 | EXPRESSION 130 | 131 | 132 | externalFunctions.next() 133 | JAVA 134 | EXPRESSION 135 | 136 | 137 | externalFunctions.hasNext() 138 | JAVA 139 | EXPRESSION 140 | 141 | 142 | path.substring(1) 143 | JAVA 144 | EXPRESSION 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 159 | 160 | 161 | 162 | 163 | 164 | No facets are configured 165 | 166 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 182 | 183 | 184 | 185 | 186 | 187 | 11 188 | 189 | 194 | 195 | 196 | 197 | 198 | 199 | GhidraDemo 200 | 201 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 217 | 218 | 219 | 220 | 221 | 222 | -------------------------------------------------------------------------------- /Sample2/GhidraDemo.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Sample2/src/com/nosecurecode/Demo.java: -------------------------------------------------------------------------------- 1 | /* 2 | PROGRAM : Coding Ghidra - Sample 2 3 | AUTHOR : NADER SHALLABI 4 | 5 | This sample code is free for use, redistribution and/or 6 | modification without any explicit permission from the author. 7 | 8 | This sample code is distributed in the hope that it will be useful, 9 | but WITHOUT ANY WARRANTY, implied or explicit. 10 | */ 11 | 12 | package com.nosecurecode; 13 | 14 | import ghidra.program.flatapi.FlatProgramAPI; 15 | import ghidra.program.model.address.Address; 16 | import ghidra.program.model.listing.*; 17 | import com.nosecurecode.libghidra.*; 18 | import ghidra.program.model.symbol.ExternalReference; 19 | import ghidra.program.model.symbol.Reference; 20 | import ghidra.program.model.symbol.ReferenceIterator; 21 | 22 | import java.util.Map; 23 | import java.util.*; 24 | 25 | /** 26 | * Demo: Identify vulnerable runtime functions and cross-reference them with parent functions 27 | */ 28 | public class Demo implements LibProgramHandler { 29 | public static void main(String args[]) throws Exception { 30 | 31 | // Option 1 to call headless analyzer using a full command 32 | String headlessCmd = "/Users/nadershallabi/ghidra/projects ProjectCrossReferences -import /Users/nadershallabi/Downloads/CoreFTPServer505.exe -overwrite"; 33 | 34 | // We need an instance of this class to pass the analyzed program handler 35 | Demo ghidraLibraryDemo = new Demo(); 36 | 37 | // This will kickoff the analysis 38 | LibGhidra.runHeadlessCmd(headlessCmd, ghidraLibraryDemo); 39 | } 40 | 41 | /** 42 | * Sample based on https://www.somersetrecon.com/blog/2019/ghidra-plugin-development-for-vulnerability-research-part-1 43 | * @param program 44 | */ 45 | @Override 46 | public void PostProcessHandler(Program program) { 47 | 48 | System.out.println("\033[1;33m"); 49 | System.out.println("================================"); 50 | System.out.println("PROCESSING PROGRAM : " + program.getName()); 51 | System.out.println("================================"); 52 | 53 | String [] sinks_array = new String[] { 54 | "strcpy", 55 | "memcpy", 56 | "gets", 57 | "memmove", 58 | "scanf", 59 | "strcpyA", 60 | "strcpyW", 61 | "wcscpy", 62 | "_tcscpy", 63 | "_mbscpy", 64 | "StrCpy", 65 | "StrCpyA", 66 | "StrCpyW", 67 | "lstrcpy", 68 | "lstrcpyA", 69 | "lstrcpyW", 70 | "lstrcpynA" 71 | }; 72 | 73 | List sinks = Arrays.asList(sinks_array); 74 | 75 | Hashtable>> sink_dic = new Hashtable>>(); 76 | ArrayList duplicate = new ArrayList(); 77 | 78 | Listing listing = program.getListing(); 79 | InstructionIterator ins_list = listing.getInstructions(true); 80 | 81 | // iterate over each instruction 82 | while (ins_list.hasNext()) { 83 | Instruction ins = ins_list.next(); 84 | String mnemonic = ins.getMnemonicString(); 85 | Object[] ops = ins.getOpObjects(0); 86 | if (mnemonic.equals("CALL")) { 87 | Object target_addr = ops[0]; 88 | String func_name = null; 89 | 90 | ExternalReference ref = null; 91 | if (target_addr instanceof Address) { 92 | CodeUnit code_unit = listing.getCodeUnitAt((Address) target_addr); 93 | if (code_unit != null) 94 | ref = code_unit.getExternalReference(0); 95 | if (ref != null) 96 | func_name = ref.getLabel(); 97 | else { 98 | Function func = listing.getFunctionAt((Address) target_addr); 99 | func_name = func.getName(); 100 | } 101 | } 102 | 103 | // check if function name is in our sinks list 104 | if (sinks.contains(func_name) && !duplicate.contains(func_name)) { 105 | duplicate.add(func_name); 106 | ReferenceIterator references = program.getReferenceManager().getReferencesTo((Address) target_addr); 107 | 108 | for (Reference ref_var : references) { 109 | Address call_addr = ref_var.getFromAddress(); 110 | Object sink_addr = ops[0]; 111 | String parent_func_name = new FlatProgramAPI(program).getFunctionBefore(call_addr).getName(); 112 | 113 | // check sink dictionary for parent function name 114 | if (!sink_dic.containsKey(parent_func_name)) { 115 | Vector> function_address_pair = new Vector>(); 116 | function_address_pair.add(new AbstractMap.SimpleEntry(func_name, call_addr.toString())); 117 | sink_dic.put(parent_func_name, function_address_pair); 118 | } 119 | else { 120 | sink_dic.get(parent_func_name).add(new AbstractMap.SimpleEntry(func_name, call_addr.toString())); 121 | } 122 | } 123 | } 124 | } 125 | } 126 | 127 | printDiscoveredFunctions(sink_dic); 128 | 129 | System.out.println("~==============================="); 130 | System.out.println("~ END OF PROCESSING PROGRAM : " + program.getName()); 131 | System.out.println("~==============================="); 132 | System.out.println("\033[0;33m"); 133 | } 134 | 135 | /** 136 | * Prints functions list (findings) 137 | * @param sink_dic 138 | */ 139 | private void printDiscoveredFunctions(Hashtable>> sink_dic) { 140 | for (String parent_func_name : sink_dic.keySet()) { 141 | Vector> item = sink_dic.get(parent_func_name); 142 | for (int i = 0; i < item.size(); i++) { 143 | Map.Entry item2 = item.get(i); 144 | String func_name = item2.getKey(); 145 | String call_addr = item2.getValue(); 146 | System.out.println("[" + parent_func_name + "]>-----" + call_addr + "----->[" + func_name + "]"); 147 | } 148 | } 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /Sample2/src/com/nosecurecode/libghidra/LibGhidra.java: -------------------------------------------------------------------------------- 1 | /* ### 2 | * IP: GHIDRA 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /* 18 | Modified version of the Headless analyzer code, for any feedback, contact NADER SHALLABI at nader@nosecurecode.com 19 | */ 20 | 21 | package com.nosecurecode.libghidra; 22 | 23 | import java.io.File; 24 | import java.io.IOException; 25 | import java.net.MalformedURLException; 26 | import java.net.URL; 27 | import java.util.*; 28 | 29 | import generic.stl.Pair; 30 | import ghidra.app.util.opinion.Loader; 31 | import ghidra.framework.OperatingSystem; 32 | import ghidra.framework.Platform; 33 | import ghidra.framework.model.DomainFolder; 34 | import ghidra.framework.protocol.ghidra.Handler; 35 | import ghidra.util.Msg; 36 | import ghidra.util.exception.InvalidInputException; 37 | 38 | /** 39 | * Launcher entry point for running headless Ghidra. 40 | */ 41 | public class LibGhidra { 42 | 43 | private static final int EXIT_CODE_ERROR = 1; 44 | 45 | /** 46 | * Runs headless command 47 | * @param headlessCmd 48 | * @param handler 49 | * @throws Exception 50 | */ 51 | public static void runHeadlessCmd(String headlessCmd, 52 | LibProgramHandler handler) throws Exception { 53 | new LibGhidra(headlessCmd.split("\\s+"), handler); 54 | } 55 | 56 | /** 57 | * Run heqdless command (command line arguments) 58 | * @param headlessCmdArgs 59 | * @param handler 60 | * @throws Exception 61 | */ 62 | public static void runHeadlessCmd(String [] headlessCmdArgs, 63 | LibProgramHandler handler) throws Exception { 64 | new LibGhidra(headlessCmdArgs, handler); 65 | } 66 | 67 | /** 68 | * This is the main entry point 69 | * @param args 70 | * @param handler 71 | * @throws Exception 72 | */ 73 | private LibGhidra(String args[], LibProgramHandler handler) throws Exception { 74 | String projectName = null; 75 | String rootFolderPath = null; 76 | URL ghidraURL = null; 77 | List filesToImport = new ArrayList<>(); 78 | int optionStartIndex; 79 | 80 | // Make sure there are arguments 81 | if (args.length < 1) { 82 | usage(); 83 | } 84 | 85 | // Ghidra URL handler registration 86 | Handler.registerHandler(); 87 | 88 | if (args[0].startsWith("ghidra:")) { 89 | optionStartIndex = 1; 90 | try { 91 | ghidraURL = new URL(args[0]); 92 | } 93 | catch (MalformedURLException e) { 94 | System.err.println("Invalid Ghidra URL: " + args[0]); 95 | usage(); 96 | } 97 | } 98 | else { 99 | if (args.length < 2) { 100 | usage(); 101 | } 102 | optionStartIndex = 2; 103 | String projectNameAndFolder = args[1]; 104 | 105 | // Check to see if projectName uses back-slashes (likely if they are using Windows) 106 | projectNameAndFolder = projectNameAndFolder.replaceAll("\\\\", DomainFolder.SEPARATOR); 107 | projectName = projectNameAndFolder; 108 | 109 | rootFolderPath = "/"; 110 | int folderIndex = projectNameAndFolder.indexOf(DomainFolder.SEPARATOR); 111 | if (folderIndex == 0) { 112 | System.err.println(args[1] + " is an invalid project_name/folder_path."); 113 | usage(); 114 | } 115 | else if (folderIndex > 0) { 116 | projectName = projectNameAndFolder.substring(0, folderIndex); 117 | rootFolderPath = projectNameAndFolder.substring(folderIndex); 118 | } 119 | } 120 | 121 | // Determine the desired logging. 122 | File logFile = null; 123 | File scriptLogFile = null; 124 | for (int argi = optionStartIndex; argi < args.length; argi++) { 125 | if (checkArgument("-log", args, argi)) { 126 | logFile = new File(args[++argi]); 127 | } 128 | else if (checkArgument("-scriptlog", args, argi)) { 129 | scriptLogFile = new File(args[++argi]); 130 | } 131 | } 132 | 133 | // Instantiate new headless analyzer and parse options. 134 | LibHeadlessAnalyzer analyzer = 135 | LibHeadlessAnalyzer.getLoggableInstance(logFile, scriptLogFile, true, handler); 136 | LibHeadlessOptions options = analyzer.getOptions(); 137 | parseOptions(options, args, optionStartIndex, ghidraURL, filesToImport); 138 | 139 | // Do the headless processing 140 | try { 141 | if (ghidraURL != null) { 142 | analyzer.processURL(ghidraURL, filesToImport); 143 | } 144 | else { 145 | analyzer.processLocal(args[0], projectName, rootFolderPath, filesToImport); 146 | } 147 | } 148 | catch (Throwable e) { 149 | Msg.error(LibHeadlessAnalyzer.class, 150 | "Abort due to Headless analyzer error: " + e.getMessage(), e); 151 | System.exit(EXIT_CODE_ERROR); 152 | } 153 | } 154 | 155 | /** 156 | * Parses the command line arguments and uses them to set the headless options. 157 | * 158 | * @param options The headless options to set. 159 | * @param args The command line arguments to parse. 160 | * @param startIndex The index into the args array of where to start parsing. 161 | * @param ghidraURL The ghidra server url to connect to, or null if not using a url. 162 | * @param filesToImport A list to put files to import into. 163 | * @throws InvalidInputException if an error occurred parsing the arguments or setting 164 | * the options. 165 | */ 166 | private void parseOptions(LibHeadlessOptions options, String[] args, int startIndex, URL ghidraURL, 167 | List filesToImport) throws InvalidInputException { 168 | 169 | String loaderName = null; 170 | List> loaderArgs = new LinkedList<>(); 171 | String languageId = null; 172 | String compilerSpecId = null; 173 | String keystorePath = null; 174 | String serverUID = null; 175 | boolean allowPasswordPrompt = false; 176 | List> preScripts = new LinkedList<>(); 177 | List> postScripts = new LinkedList<>(); 178 | 179 | for (int argi = startIndex; argi < args.length; argi++) { 180 | 181 | String arg = args[argi]; 182 | if (checkArgument("-log", args, argi)) { 183 | // Already processed 184 | argi++; 185 | } 186 | else if (checkArgument("-scriptlog", args, argi)) { 187 | // Already processed 188 | argi++; 189 | } 190 | else if (arg.equalsIgnoreCase("-overwrite")) { 191 | options.enableOverwriteOnConflict(true); 192 | } 193 | else if (arg.equalsIgnoreCase("-noanalysis")) { 194 | options.enableAnalysis(false); 195 | } 196 | else if (arg.equalsIgnoreCase("-deleteproject")) { 197 | options.setDeleteCreatedProjectOnClose(true); 198 | } 199 | else if (checkArgument("-loader", args, argi)) { 200 | loaderName = args[++argi]; 201 | } 202 | else if (arg.startsWith(Loader.COMMAND_LINE_ARG_PREFIX)) { 203 | if (args[argi + 1].startsWith("-")) { 204 | throw new InvalidInputException(args[argi] + " expects value to follow."); 205 | } 206 | loaderArgs.add(new Pair<>(arg, args[++argi])); 207 | } 208 | else if (checkArgument("-processor", args, argi)) { 209 | languageId = args[++argi]; 210 | } 211 | else if (checkArgument("-cspec", args, argi)) { 212 | compilerSpecId = args[++argi]; 213 | } 214 | else if (checkArgument("-prescript", args, argi)) { 215 | String scriptName = args[++argi]; 216 | String[] scriptArgs = getSubArguments(args, argi); 217 | argi += scriptArgs.length; 218 | preScripts.add(new Pair<>(scriptName, scriptArgs)); 219 | } 220 | else if (checkArgument("-postscript", args, argi)) { 221 | String scriptName = args[++argi]; 222 | String[] scriptArgs = getSubArguments(args, argi); 223 | argi += scriptArgs.length; 224 | postScripts.add(new Pair<>(scriptName, scriptArgs)); 225 | } 226 | else if (checkArgument("-scriptPath", args, argi)) { 227 | options.setScriptDirectories(args[++argi]); 228 | } 229 | else if (checkArgument("-propertiesPath", args, argi)) { 230 | options.setPropertiesFileDirectories(args[++argi]); 231 | } 232 | else if (checkArgument("-import", args, argi)) { 233 | File inputFile = new File(args[++argi]); 234 | if (!inputFile.isDirectory() && !inputFile.isFile()) { 235 | throw new InvalidInputException( 236 | inputFile.getAbsolutePath() + " is not a valid directory or file."); 237 | } 238 | 239 | LibHeadlessAnalyzer.checkValidFilename(inputFile); 240 | 241 | filesToImport.add(inputFile); 242 | 243 | // Keep checking for OS-expanded files 244 | String nextArg; 245 | 246 | while (argi < (args.length - 1)) { 247 | nextArg = args[++argi]; 248 | 249 | // Check if next argument is a parameter 250 | if (nextArg.charAt(0) == '-') { 251 | argi--; 252 | break; 253 | } 254 | 255 | File otherFile = new File(nextArg); 256 | if (!otherFile.isFile() && !otherFile.isDirectory()) { 257 | throw new InvalidInputException( 258 | otherFile.getAbsolutePath() + " is not a valid directory or file."); 259 | } 260 | 261 | LibHeadlessAnalyzer.checkValidFilename(otherFile); 262 | 263 | filesToImport.add(otherFile); 264 | } 265 | } 266 | else if ("-connect".equals(args[argi])) { 267 | if ((argi + 1) < args.length) { 268 | arg = args[argi + 1]; 269 | if (!arg.startsWith("-")) { 270 | // serverUID is optional argument after -connect 271 | serverUID = arg; 272 | ++argi; 273 | } 274 | } 275 | } 276 | else if ("-commit".equals(args[argi])) { 277 | String comment = null; 278 | if ((argi + 1) < args.length) { 279 | arg = args[argi + 1]; 280 | if (!arg.startsWith("-")) { 281 | // comment is optional argument after -commit 282 | comment = arg; 283 | ++argi; 284 | } 285 | } 286 | options.setCommitFiles(true, comment); 287 | } 288 | else if (checkArgument("-keystore", args, argi)) { 289 | keystorePath = args[++argi]; 290 | File keystore = new File(keystorePath); 291 | if (!keystore.isFile()) { 292 | throw new InvalidInputException( 293 | keystore.getAbsolutePath() + " is not a valid keystore file."); 294 | } 295 | } 296 | else if (arg.equalsIgnoreCase("-p")) { 297 | allowPasswordPrompt = true; 298 | } 299 | else if ("-analysisTimeoutPerFile".equalsIgnoreCase(args[argi])) { 300 | options.setPerFileAnalysisTimeout(args[++argi]); 301 | } 302 | else if ("-process".equals(args[argi])) { 303 | if (options.runScriptsNoImport) { 304 | throw new InvalidInputException( 305 | "The -process option may only be specified once."); 306 | } 307 | String processBinary = null; 308 | if ((argi + 1) < args.length) { 309 | arg = args[argi + 1]; 310 | if (!arg.startsWith("-")) { 311 | // processBinary is optional argument after -process 312 | processBinary = arg; 313 | ++argi; 314 | } 315 | } 316 | options.setRunScriptsNoImport(true, processBinary); 317 | } 318 | else if ("-recursive".equals(args[argi])) { 319 | options.enableRecursiveProcessing(true); 320 | } 321 | else if ("-readOnly".equalsIgnoreCase(args[argi])) { 322 | options.enableReadOnlyProcessing(true); 323 | } 324 | else if (checkArgument("-max-cpu", args, argi)) { 325 | String cpuVal = args[++argi]; 326 | try { 327 | options.setMaxCpu(Integer.parseInt(cpuVal)); 328 | } 329 | catch (NumberFormatException nfe) { 330 | throw new InvalidInputException("Invalid value for max-cpu: " + cpuVal); 331 | } 332 | } 333 | else if ("-okToDelete".equalsIgnoreCase(args[argi])) { 334 | options.setOkToDelete(true); 335 | } 336 | else { 337 | throw new InvalidInputException("Bad argument: " + arg); 338 | } 339 | } 340 | 341 | // Set up pre and post scripts 342 | options.setPreScriptsWithArgs(preScripts); 343 | options.setPostScriptsWithArgs(postScripts); 344 | 345 | // Set loader and loader args 346 | options.setLoader(loaderName, loaderArgs); 347 | 348 | // Set user-specified language and compiler spec 349 | options.setLanguageAndCompiler(languageId, compilerSpecId); 350 | 351 | // Set up optional Ghidra Server authenticator 352 | try { 353 | options.setClientCredentials(serverUID, keystorePath, allowPasswordPrompt); 354 | } 355 | catch (IOException e) { 356 | throw new InvalidInputException( 357 | "Failed to install Ghidra Server authenticator: " + e.getMessage()); 358 | } 359 | 360 | // If -process was specified, inputFiles must be null or inputFiles.size must be 0. 361 | // Otherwise when not in -process mode, inputFiles can be null or inputFiles.size can be 0, 362 | // only if there are scripts to be run. 363 | if (options.runScriptsNoImport) { 364 | 365 | if (filesToImport != null && filesToImport.size() > 0) { 366 | System.err.print("Must use either -process or -import parameters, but not both."); 367 | System.err.print(" -process runs scripts over existing program(s) in a project, " + 368 | "whereas -import"); 369 | System.err.println(" imports new programs and runs scripts and/or analyzes them " + 370 | "after import."); 371 | System.exit(EXIT_CODE_ERROR); 372 | } 373 | 374 | if (options.overwrite) { 375 | Msg.warn(LibHeadlessAnalyzer.class, 376 | "The -overwrite parameter does not apply to -process mode. Ignoring overwrite " + 377 | "and continuing."); 378 | } 379 | 380 | if (options.readOnly && options.okToDelete) { 381 | System.err.println("You have specified the conflicting parameters -readOnly and " + 382 | "-okToDelete. Please pick one and try again."); 383 | System.exit(EXIT_CODE_ERROR); 384 | } 385 | } 386 | else { 387 | if (filesToImport == null || filesToImport.size() == 0) { 388 | if (options.preScripts.isEmpty() && options.postScripts.isEmpty()) { 389 | System.err.println("Nothing to do ... must specify -import, -process, or " + 390 | "prescript and/or postscript."); 391 | System.exit(EXIT_CODE_ERROR); 392 | } 393 | else { 394 | Msg.warn(LibHeadlessAnalyzer.class, 395 | "Neither the -import parameter nor the -process parameter was specified; " + 396 | "therefore, the specified prescripts and/or postscripts will be " + 397 | "executed without any type of program context."); 398 | } 399 | } 400 | } 401 | 402 | if (options.commit) { 403 | if (options.readOnly) { 404 | System.err.println("Can not use -commit and -readOnly at the same time."); 405 | System.exit(EXIT_CODE_ERROR); 406 | } 407 | } 408 | 409 | // Implied commit, only if not in process mode 410 | if (!options.commit && ghidraURL != null) { 411 | if (!options.readOnly) { 412 | // implied commit 413 | options.setCommitFiles(true, null); 414 | } 415 | else { 416 | Msg.warn(LibHeadlessAnalyzer.class, 417 | "-readOnly mode is on: for -process, changes will not be saved."); 418 | } 419 | } 420 | } 421 | 422 | /** 423 | * Prints out the usage details and exits the Java application with an exit code that 424 | * indicates error. 425 | * 426 | * @param execCmd the command used to run the headless analyzer from the calling method. 427 | */ 428 | public static void usage(String execCmd) { 429 | System.out.println("Headless Analyzer Usage: " + execCmd); 430 | System.out.println(" [/]"); 431 | System.out.println( 432 | " | ghidra://[:]/[/]"); 433 | System.out.println( 434 | " [[-import [|]+] | [-process []]]"); 435 | System.out.println(" [-preScript ]"); 436 | System.out.println(" [-postScript ]"); 437 | System.out.println(" [-scriptPath \"[;...]\"]"); 438 | System.out.println(" [-propertiesPath \"[;...]\"]"); 439 | System.out.println(" [-scriptlog ]"); 440 | System.out.println(" [-log ]"); 441 | System.out.println(" [-overwrite]"); 442 | System.out.println(" [-recursive]"); 443 | System.out.println(" [-readOnly]"); 444 | System.out.println(" [-deleteProject]"); 445 | System.out.println(" [-noanalysis]"); 446 | System.out.println(" [-processor ]"); 447 | System.out.println(" [-cspec ]"); 448 | System.out.println(" [-analysisTimeoutPerFile ]"); 449 | System.out.println(" [-keystore ]"); 450 | System.out.println(" [-connect ]"); 451 | System.out.println(" [-p]"); 452 | System.out.println(" [-commit [\"\"]]"); 453 | System.out.println(" [-okToDelete]"); 454 | System.out.println(" [-max-cpu ]"); 455 | System.out.println(" [-loader ]"); 456 | // ** NOTE: please update 'analyzeHeadlessREADME.html' if changing command line parameters ** 457 | 458 | if (Platform.CURRENT_PLATFORM.getOperatingSystem() != OperatingSystem.WINDOWS) { 459 | System.out.println(); 460 | System.out.println( 461 | " - All uses of $GHIDRA_HOME or $USER_HOME in script path must be" + 462 | " preceded by '\\'"); 463 | } 464 | System.out.println(); 465 | System.out.println( 466 | "Please refer to 'analyzeHeadlessREADME.html' for detailed usage examples " + 467 | "and notes."); 468 | 469 | System.out.println(); 470 | System.exit(EXIT_CODE_ERROR); 471 | } 472 | 473 | private void usage() { 474 | usage("analyzeHeadless"); 475 | } 476 | 477 | private String[] getSubArguments(String[] args, int argi) { 478 | List subArgs = new LinkedList<>(); 479 | int i = argi + 1; 480 | while (i < args.length && !args[i].startsWith("-")) { 481 | subArgs.add(args[i++]); 482 | } 483 | return subArgs.toArray(new String[0]); 484 | } 485 | 486 | private boolean checkArgument(String optionName, String[] args, int argi) 487 | throws InvalidInputException { 488 | // everything after this requires an argument 489 | if (!optionName.equalsIgnoreCase(args[argi])) { 490 | return false; 491 | } 492 | if (argi + 1 == args.length) { 493 | throw new InvalidInputException(optionName + " requires an argument"); 494 | } 495 | return true; 496 | } 497 | } 498 | -------------------------------------------------------------------------------- /Sample2/src/com/nosecurecode/libghidra/LibHeadlessErrorLogger.java: -------------------------------------------------------------------------------- 1 | /* ### 2 | * IP: GHIDRA 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /* 18 | Modified version of the Headless analyzer code, for any feedback, contact NADER SHALLABI at nader@nosecurecode.com 19 | */ 20 | 21 | package com.nosecurecode.libghidra; 22 | 23 | import java.io.*; 24 | 25 | import ghidra.util.ErrorLogger; 26 | 27 | /** 28 | * Custom headless error logger which is used when log4j is disabled. 29 | */ 30 | class LibHeadlessErrorLogger implements ErrorLogger { 31 | 32 | private PrintWriter logWriter; 33 | 34 | LibHeadlessErrorLogger(File logFile) { 35 | if (logFile != null) { 36 | setLogFile(logFile); 37 | } 38 | } 39 | 40 | synchronized void setLogFile(File logFile) { 41 | try { 42 | if (logFile == null) { 43 | if (logWriter != null) { 44 | writeLog("INFO", "File logging disabled"); 45 | logWriter.close(); 46 | logWriter = null; 47 | } 48 | return; 49 | } 50 | PrintWriter w = new PrintWriter(new FileWriter(logFile)); 51 | if (logWriter != null) { 52 | writeLog("INFO ", "Switching log file to: " + logFile); 53 | logWriter.close(); 54 | } 55 | logWriter = w; 56 | } 57 | catch (IOException e) { 58 | System.err.println("Failed to open log file " + logFile + ": " + e.getMessage()); 59 | } 60 | } 61 | 62 | private synchronized void writeLog(String line) { 63 | if (logWriter == null) { 64 | return; 65 | } 66 | logWriter.println(line); 67 | } 68 | 69 | private synchronized void writeLog(String level, String[] lines) { 70 | if (logWriter == null) { 71 | return; 72 | } 73 | for (String line : lines) { 74 | writeLog(level + " " + line); 75 | } 76 | logWriter.flush(); 77 | } 78 | 79 | private synchronized void writeLog(String level, String text) { 80 | if (logWriter == null) { 81 | return; 82 | } 83 | writeLog(level, chopLines(text)); 84 | } 85 | 86 | private synchronized void writeLog(String level, String text, Throwable throwable) { 87 | if (logWriter == null) { 88 | return; 89 | } 90 | writeLog(level, chopLines(text)); 91 | for (StackTraceElement element : throwable.getStackTrace()) { 92 | writeLog(level + " " + element.toString()); 93 | } 94 | logWriter.flush(); 95 | } 96 | 97 | private String[] chopLines(String text) { 98 | text = text.replace("\r", ""); 99 | return text.split("\n"); 100 | } 101 | 102 | @Override 103 | public void debug(Object originator, Object message) { 104 | // TODO for some reason debug is off 105 | // writeLog("DEBUG", message.toString()); 106 | } 107 | 108 | @Override 109 | public void debug(Object originator, Object message, Throwable throwable) { 110 | // TODO for some reason debug is off 111 | // writeLog("DEBUG", message.toString(), throwable); 112 | } 113 | 114 | @Override 115 | public void error(Object originator, Object message) { 116 | writeLog("ERROR", message.toString()); 117 | } 118 | 119 | @Override 120 | public void error(Object originator, Object message, Throwable throwable) { 121 | writeLog("ERROR", message.toString(), throwable); 122 | } 123 | 124 | @Override 125 | public void info(Object originator, Object message) { 126 | writeLog("INFO ", message.toString()); 127 | } 128 | 129 | @Override 130 | public void info(Object originator, Object message, Throwable throwable) { 131 | // TODO for some reason tracing is off 132 | // writeLog("INFO ", message.toString(), throwable); 133 | } 134 | 135 | @Override 136 | public void trace(Object originator, Object message) { 137 | // TODO for some reason tracing i soff 138 | // writeLog("TRACE", message.toString()); 139 | } 140 | 141 | @Override 142 | public void trace(Object originator, Object message, Throwable throwable) { 143 | // TODO for some reason tracing is off 144 | // writeLog("TRACE", message.toString(), throwable); 145 | } 146 | 147 | @Override 148 | public void warn(Object originator, Object message) { 149 | writeLog("WARN ", message.toString()); 150 | } 151 | 152 | @Override 153 | public void warn(Object originator, Object message, Throwable throwable) { 154 | writeLog("WARN ", message.toString(), throwable); 155 | } 156 | 157 | } 158 | -------------------------------------------------------------------------------- /Sample2/src/com/nosecurecode/libghidra/LibHeadlessOptions.java: -------------------------------------------------------------------------------- 1 | /* ### 2 | * IP: GHIDRA 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /* 18 | Modified version of the Headless analyzer code, for any feedback, contact NADER SHALLABI at nader@nosecurecode.com 19 | */ 20 | 21 | package com.nosecurecode.libghidra; 22 | 23 | import java.io.IOException; 24 | import java.util.*; 25 | 26 | import generic.jar.ResourceFile; 27 | import generic.stl.Pair; 28 | import ghidra.app.util.opinion.Loader; 29 | import ghidra.app.util.opinion.LoaderService; 30 | import ghidra.framework.client.HeadlessClientAuthenticator; 31 | import ghidra.program.model.lang.*; 32 | import ghidra.program.util.DefaultLanguageService; 33 | import ghidra.util.exception.InvalidInputException; 34 | 35 | /** 36 | * Options for headless analyzer. 37 | *

38 | * Option state may be adjusted to reflect assumed options 39 | * during processing. If multiple invocations of either 40 | * {@link LibHeadlessAnalyzer#processLocal(String, String, String, List)} or 41 | * {@link LibHeadlessAnalyzer#processURL(java.net.URL, List)} are performed, 42 | * these options should be reset and adjusted as necessary. 43 | */ 44 | 45 | public class LibHeadlessOptions { 46 | 47 | // -process and -import 48 | String domainFileNameToProcess; // may include pattern 49 | boolean runScriptsNoImport; 50 | 51 | // -preScript 52 | List> preScripts; 53 | Map preScriptFileMap; 54 | 55 | // -postScript 56 | List> postScripts; 57 | Map postScriptFileMap; 58 | 59 | // -scriptPath 60 | List scriptPaths; 61 | 62 | // -propertiesPath 63 | List propertiesFileStrPaths; 64 | List propertiesFilePaths; 65 | 66 | // -overwrite 67 | boolean overwrite; 68 | 69 | // -recursive 70 | boolean recursive; 71 | 72 | // -readOnly 73 | boolean readOnly; 74 | 75 | // -deleteProject 76 | boolean deleteProject; 77 | 78 | // -noanalysis 79 | boolean analyze; 80 | 81 | // -processor 82 | Language language; 83 | 84 | // -cspec 85 | CompilerSpec compilerSpec; 86 | 87 | // -analysisTimeoutPerFile 88 | int perFileTimeout; 89 | 90 | // -keystore 91 | String keystore; 92 | 93 | // -connect 94 | String connectUserID; 95 | 96 | // -p 97 | boolean allowPasswordPrompt; 98 | 99 | // -commit 100 | boolean commit; 101 | String commitComment; 102 | 103 | // -okToDelete 104 | boolean okToDelete; 105 | 106 | // -max-cpu 107 | int maxcpu; 108 | 109 | // -loader 110 | Class loaderClass; 111 | List> loaderArgs; 112 | 113 | // ------------------------------------------------------------------------------------------- 114 | 115 | /** 116 | * Creates a new headless options object with default settings. 117 | */ 118 | LibHeadlessOptions() { 119 | reset(); 120 | } 121 | 122 | /** 123 | * Resets the options to its default settings. 124 | */ 125 | public void reset() { 126 | domainFileNameToProcess = null; 127 | runScriptsNoImport = false; 128 | preScripts = new LinkedList<>(); 129 | preScriptFileMap = null; 130 | postScripts = new LinkedList<>(); 131 | postScriptFileMap = null; 132 | scriptPaths = null; 133 | propertiesFileStrPaths = new ArrayList<>(); 134 | propertiesFilePaths = new ArrayList<>(); 135 | overwrite = false; 136 | recursive = false; 137 | readOnly = false; 138 | deleteProject = false; 139 | analyze = true; 140 | language = null; 141 | compilerSpec = null; 142 | perFileTimeout = -1; 143 | keystore = null; 144 | connectUserID = null; 145 | allowPasswordPrompt = false; 146 | commit = false; 147 | commitComment = null; 148 | okToDelete = false; 149 | maxcpu = 0; 150 | loaderClass = null; 151 | loaderArgs = null; 152 | } 153 | 154 | /** 155 | * Set to run scripts (and optionally, analysis) without importing a 156 | * program. Scripts will run on specified folder or program that already 157 | * exists in the project. 158 | * 159 | * @param runScriptsOnly if true, no imports will occur and scripts 160 | * (and analysis, if enabled) will run on the specified existing program 161 | * or directory of programs. 162 | * @param filename name of specific project file or folder to be processed (the location 163 | * is passed in elsewhere by the user). If null, user has not specified 164 | * a file to process -- therefore, the entire directory will be processed. 165 | * The filename should not include folder path elements which should be 166 | * specified separately via project or URL specification. 167 | * @throws IllegalArgumentException if the specified filename is invalid and contains the 168 | * path separator character '/'. 169 | */ 170 | public void setRunScriptsNoImport(boolean runScriptsOnly, String filename) { 171 | if (filename != null) { 172 | filename = filename.trim(); 173 | if (filename.indexOf("/") >= 0) { 174 | throw new IllegalArgumentException("invalid filename specified"); 175 | } 176 | } 177 | this.runScriptsNoImport = runScriptsOnly; 178 | this.domainFileNameToProcess = filename; 179 | } 180 | 181 | /** 182 | * Set the ordered list of scripts to execute immediately following import and 183 | * prior to analyzing an imported program. If import not performed, 184 | * these scripts will execute once prior to any post-scripts. 185 | * 186 | * @param preScripts list of script names 187 | */ 188 | public void setPreScripts(List preScripts) { 189 | List> preScriptsEmptyArgs = new LinkedList<>(); 190 | for (String preScript : preScripts) { 191 | preScriptsEmptyArgs.add(new Pair<>(preScript, new String[0])); 192 | } 193 | setPreScriptsWithArgs(preScriptsEmptyArgs); 194 | } 195 | 196 | /** 197 | * Set the ordered list of scripts and their arguments to execute immediately following import 198 | * and prior to analyzing an imported program. If import not performed, 199 | * these scripts will execute once prior to any post-scripts. 200 | * 201 | * @param preScripts list of script names/script argument pairs 202 | */ 203 | public void setPreScriptsWithArgs(List> preScripts) { 204 | this.preScripts = preScripts; 205 | this.preScriptFileMap = null; 206 | } 207 | 208 | /** 209 | * Set the ordered list of scripts to execute immediately following import and 210 | * and analysis of a program. If import not performed, 211 | * these scripts will execute once following any pre-scripts. 212 | * 213 | * @param postScripts list of script names 214 | */ 215 | public void setPostScripts(List postScripts) { 216 | List> postScriptsEmptyArgs = new LinkedList<>(); 217 | for (String postScript : postScripts) { 218 | postScriptsEmptyArgs.add(new Pair<>(postScript, new String[0])); 219 | } 220 | setPostScriptsWithArgs(postScriptsEmptyArgs); 221 | } 222 | 223 | /** 224 | * Set the ordered list of scripts to execute immediately following import and 225 | * and analysis of a program. If import not performed, 226 | * these scripts will execute once following any pre-scripts. 227 | * 228 | * @param postScripts list of script names/script argument pairs 229 | */ 230 | public void setPostScriptsWithArgs(List> postScripts) { 231 | this.postScripts = postScripts; 232 | this.postScriptFileMap = null; 233 | } 234 | 235 | /** 236 | * Set the script source directories to be searched for secondary scripts. 237 | * The default set of enabled script directories within the Ghidra installation 238 | * will be appended to the specified list of newPaths. 239 | * Individual Paths may be constructed relative to Ghidra installation directory, 240 | * User home directory, or absolute system paths. Examples: 241 | *

242 | 	 *     Path.GHIDRA_HOME + "/Ghidra/Features/Base/ghidra_scripts"
243 | 	 *     Path.USER_HOME + "/Ghidra/Features/Base/ghidra_scripts"
244 | 	 *     "/shared/ghidra_scripts"
245 | 	 * 
246 | * 247 | * @param newPaths list of directories to be searched. 248 | */ 249 | public void setScriptDirectories(List newPaths) { 250 | scriptPaths = newPaths; 251 | } 252 | 253 | /** 254 | * List of valid script directory paths separated by a ';'. 255 | * The default set of enabled script directories within the Ghidra installation 256 | * will be appended to the specified list of newPaths. 257 | * Individual Paths may be constructed relative to Ghidra installation directory, 258 | * User home directory, or absolute system paths. Examples: 259 | *
260 | 	 * 		Path.GHIDRA_HOME + "/Ghidra/Features/Base/ghidra_scripts"
261 | 	 *      Path.USER_HOME + "/Ghidra/Features/Base/ghidra_scripts"
262 | 	 *		"/shared/ghidra_scripts"
263 | 	 * 
264 | * @param paths semicolon (';') separated list of directory paths 265 | */ 266 | public void setScriptDirectories(String paths) { 267 | String[] pathArray = paths.split(";"); 268 | setScriptDirectories(Arrays.asList(pathArray)); 269 | } 270 | 271 | /** 272 | * Sets a single location for .properties files associated with GhidraScripts. 273 | * 274 | * Typically, .properties files should be located in the same directory as their corresponding 275 | * scripts. However, this method may need to be used when circumstances make it impossible to 276 | * have both files in the same directory (i.e., if the scripts are included in ghidra.jar). 277 | * 278 | * @param path location of .properties file(s) 279 | */ 280 | public void setPropertiesFileDirectory(String path) { 281 | propertiesFileStrPaths = new ArrayList<>(); 282 | propertiesFileStrPaths.add(path); 283 | } 284 | 285 | /** 286 | * Sets one or more locations to find .properties files associated with GhidraScripts. 287 | * 288 | * Typically, .properties files should be located in the same directory as their corresponding 289 | * scripts. However, this method may need to be used when circumstances make it impossible to 290 | * have both files in the same directory (i.e., if the scripts are included in ghidra.jar). 291 | * 292 | * @param newPaths potential locations of .properties file(s) 293 | */ 294 | public void setPropertiesFileDirectories(List newPaths) { 295 | propertiesFileStrPaths = newPaths; 296 | } 297 | 298 | /** 299 | * List of valid .properties file directory paths, separated by a ';'. 300 | * 301 | * Typically, .properties files should be located in the same directory as their corresponding 302 | * scripts. However, this method may need to be used when circumstances make it impossible to 303 | * have both files in the same directory (i.e., if the scripts are included in ghidra.jar). 304 | * 305 | * @param paths String representation of directories (each separated by ';') 306 | */ 307 | public void setPropertiesFileDirectories(String paths) { 308 | String[] pathArray = paths.split(";"); 309 | setPropertiesFileDirectories(Arrays.asList(pathArray)); 310 | } 311 | 312 | /** 313 | * During import, the default behavior is to skip the import if a conflict occurs 314 | * within the destination folder. This method can be used to force the original 315 | * conflicting file to be removed prior to import. 316 | * If the pre-existing file is versioned, the commit option must also be 317 | * enabled to have the overwrite remove the versioned file. 318 | * 319 | * @param enabled if true conflicting domain files will be removed from the 320 | * project prior to importing the new file. 321 | */ 322 | public void enableOverwriteOnConflict(boolean enabled) { 323 | this.overwrite = enabled; 324 | } 325 | 326 | /** 327 | * This method can be used to enable recursive processing of files during 328 | * -import or -process modes. In order for recursive processing of files to 329 | * occur, the user must have specified a directory (and not a specific file) 330 | * for the Headless Analyzer to import or process. 331 | * 332 | * @param enabled if true, enables recursive processing 333 | */ 334 | public void enableRecursiveProcessing(boolean enabled) { 335 | this.recursive = enabled; 336 | } 337 | 338 | /** 339 | * When readOnly processing is enabled, any changes made by script or analyzers 340 | * are discarded when the Headless Analyzer exits. When used with import mode, 341 | * the imported program file will not be saved to the project or repository. 342 | * 343 | * @param enabled if true, enables readOnly processing or import 344 | */ 345 | public void enableReadOnlyProcessing(boolean enabled) { 346 | this.readOnly = enabled; 347 | } 348 | 349 | /** 350 | * Set project delete flag which allows temporary projects created 351 | * to be deleted upon completion. This option has no effect if a 352 | * Ghidra URL or an existing project was specified. This option 353 | * will be assumed when importing with the readOnly option enabled. 354 | * 355 | * @param enabled if true a created project will be deleted when 356 | * processing is complete. 357 | */ 358 | public void setDeleteCreatedProjectOnClose(boolean enabled) { 359 | this.deleteProject = enabled; 360 | } 361 | 362 | /** 363 | * Auto-analysis is enabled by default following import. This method can be 364 | * used to change the enablement of auto-analysis. 365 | * 366 | * @param enabled True if auto-analysis should be enabled; otherwise, false. 367 | */ 368 | public void enableAnalysis(boolean enabled) { 369 | this.analyze = enabled; 370 | } 371 | 372 | /** 373 | * Sets the language and compiler spec from the provided input. Any null value will attempt 374 | * a "best-guess" if possible. 375 | * 376 | * @param languageId The language to set. 377 | * @param compilerSpecId The compiler spec to set. 378 | * @throws InvalidInputException if the language and compiler spec combination is not valid. 379 | */ 380 | public void setLanguageAndCompiler(String languageId, String compilerSpecId) 381 | throws InvalidInputException { 382 | if (languageId == null && compilerSpecId == null) { 383 | return; 384 | } 385 | if (languageId == null) { 386 | throw new InvalidInputException("Compiler spec specified without specifying language."); 387 | } 388 | try { 389 | language = 390 | DefaultLanguageService.getLanguageService().getLanguage(new LanguageID(languageId)); 391 | if (compilerSpecId == null) { 392 | compilerSpec = language.getDefaultCompilerSpec(); 393 | } 394 | else { 395 | compilerSpec = language.getCompilerSpecByID(new CompilerSpecID(compilerSpecId)); 396 | } 397 | } 398 | catch (LanguageNotFoundException e) { 399 | language = null; 400 | compilerSpec = null; 401 | throw new InvalidInputException("Unsupported language: " + languageId); 402 | } 403 | catch (CompilerSpecNotFoundException e) { 404 | language = null; 405 | compilerSpec = null; 406 | throw new InvalidInputException("Compiler spec \"" + compilerSpecId + 407 | "\" is not supported for language \"" + languageId + "\""); 408 | } 409 | } 410 | 411 | /** 412 | * Set analyzer timeout on a per-file basis. 413 | * 414 | * @param stringInSecs timeout value in seconds (as a String) 415 | * @throws InvalidInputException if the timeout value was not a valid value 416 | */ 417 | public void setPerFileAnalysisTimeout(String stringInSecs) throws InvalidInputException { 418 | try { 419 | perFileTimeout = Integer.parseInt(stringInSecs); 420 | } 421 | catch (NumberFormatException nfe) { 422 | throw new InvalidInputException( 423 | "'" + stringInSecs + "' is not a valid integer representation."); 424 | } 425 | } 426 | 427 | public void setPerFileAnalysisTimeout(int secs) { 428 | perFileTimeout = secs; 429 | } 430 | 431 | /** 432 | * Set Ghidra Server client credentials to be used with "shared" projects. 433 | * 434 | * @param userID optional userId to use if server permits the user to use 435 | * a userId which differs from the process owner name. 436 | * @param keystorePath file path to keystore file containing users private key 437 | * to be used with PKI or SSH based authentication. 438 | * @param allowPasswordPrompt if true the user may be prompted for passwords 439 | * via the console (stdin). Please note that the Java console will echo 440 | * the password entry to the terminal which may be undesirable. 441 | * @throws IOException if an error occurs while opening the specified keystorePath. 442 | */ 443 | public void setClientCredentials(String userID, String keystorePath, 444 | boolean allowPasswordPrompt) throws IOException { 445 | this.connectUserID = userID; 446 | this.keystore = keystorePath; 447 | this.allowPasswordPrompt = allowPasswordPrompt; 448 | HeadlessClientAuthenticator.installHeadlessClientAuthenticator(userID, keystorePath, 449 | allowPasswordPrompt); 450 | } 451 | 452 | /** 453 | * Enable committing of processed files to the repository which backs the specified 454 | * project. 455 | * 456 | * @param commit if true imported files will be committed 457 | * @param comment optional comment to use when committing 458 | */ 459 | public void setCommitFiles(boolean commit, String comment) { 460 | this.commit = commit; 461 | this.commitComment = comment; 462 | } 463 | 464 | public void setOkToDelete(boolean deleteOk) { 465 | okToDelete = deleteOk; 466 | } 467 | 468 | /** 469 | * Sets the maximum number of cpu cores to use during headless processing. 470 | * 471 | * @param cpu The maximum number of cpu cores to use during headless processing. 472 | * Setting it to 0 or a negative integer is equivalent to setting it to 1. 473 | */ 474 | public void setMaxCpu(int cpu) { 475 | this.maxcpu = cpu; 476 | System.setProperty("cpu.core.limit", Integer.toString(cpu)); 477 | 478 | } 479 | 480 | /** 481 | * Sets the loader to use for imports, as well as any loader-specific arguments. A null loader 482 | * will attempt "best-guess" if possible. Loader arguments are not supported if a "best-guess" 483 | * is made. 484 | * 485 | * @param loaderName The name (simple class name) of the loader to use. 486 | * @param loaderArgs A list of loader-specific arguments. Could be null if there are none. 487 | * @throws InvalidInputException if an invalid loader name was specified, or if loader arguments 488 | * were specified but a loader was not. 489 | */ 490 | public void setLoader(String loaderName, List> loaderArgs) 491 | throws InvalidInputException { 492 | if (loaderName != null) { 493 | this.loaderClass = LoaderService.getLoaderClassByName(loaderName); 494 | if (this.loaderClass == null) { 495 | throw new InvalidInputException("Invalid loader name specified: " + loaderName); 496 | } 497 | this.loaderArgs = loaderArgs; 498 | } 499 | else { 500 | if (loaderArgs != null && loaderArgs.size() > 0) { 501 | throw new InvalidInputException( 502 | "Loader arguments defined without a loader being specified."); 503 | } 504 | this.loaderClass = null; 505 | this.loaderArgs = null; 506 | } 507 | } 508 | } 509 | -------------------------------------------------------------------------------- /Sample2/src/com/nosecurecode/libghidra/LibHeadlessTimedTaskMonitor.java: -------------------------------------------------------------------------------- 1 | /* ### 2 | * IP: GHIDRA 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /* 18 | Modified version of the Headless analyzer code, for any feedback, contact NADER SHALLABI at nader@nosecurecode.com 19 | */ 20 | 21 | package com.nosecurecode.libghidra; 22 | 23 | import java.util.Timer; 24 | import java.util.TimerTask; 25 | 26 | import ghidra.util.exception.CancelledException; 27 | import ghidra.util.task.CancelledListener; 28 | import ghidra.util.task.TaskMonitor; 29 | 30 | /** 31 | * Monitor used by Headless Analyzer for "timeout" functionality 32 | */ 33 | public class LibHeadlessTimedTaskMonitor implements TaskMonitor { 34 | 35 | private Timer timer = new Timer(); 36 | private volatile boolean isCancelled; 37 | 38 | LibHeadlessTimedTaskMonitor(int timeoutSecs) { 39 | isCancelled = false; 40 | timer.schedule(new TimeOutTask(), timeoutSecs * 1000); 41 | } 42 | 43 | private class TimeOutTask extends TimerTask { 44 | @Override 45 | public void run() { 46 | LibHeadlessTimedTaskMonitor.this.cancel(); 47 | } 48 | } 49 | 50 | @Override 51 | public boolean isCancelled() { 52 | return isCancelled; 53 | } 54 | 55 | @Override 56 | public void setShowProgressValue(boolean showProgressValue) { 57 | // stub 58 | } 59 | 60 | @Override 61 | public void setMessage(String message) { 62 | // stub 63 | } 64 | 65 | @Override 66 | public String getMessage() { 67 | return null; 68 | } 69 | 70 | @Override 71 | public void setProgress(long value) { 72 | // stub 73 | } 74 | 75 | @Override 76 | public void initialize(long max) { 77 | // stub 78 | } 79 | 80 | @Override 81 | public void setMaximum(long max) { 82 | // stub 83 | } 84 | 85 | @Override 86 | public long getMaximum() { 87 | return 0; 88 | } 89 | 90 | @Override 91 | public void setIndeterminate(boolean indeterminate) { 92 | // stub 93 | } 94 | 95 | @Override 96 | public boolean isIndeterminate() { 97 | return false; 98 | } 99 | 100 | @Override 101 | public void checkCanceled() throws CancelledException { 102 | if (isCancelled()) { 103 | throw new CancelledException(); 104 | } 105 | } 106 | 107 | @Override 108 | public void incrementProgress(long incrementAmount) { 109 | // stub 110 | } 111 | 112 | @Override 113 | public long getProgress() { 114 | return 0; 115 | } 116 | 117 | @Override 118 | public void cancel() { 119 | timer.cancel(); // Terminate the timer thread 120 | isCancelled = true; 121 | } 122 | 123 | @Override 124 | public void addCancelledListener(CancelledListener listener) { 125 | // stub 126 | } 127 | 128 | @Override 129 | public void removeCancelledListener(CancelledListener listener) { 130 | // stub 131 | } 132 | 133 | @Override 134 | public void setCancelEnabled(boolean enable) { 135 | // stub 136 | } 137 | 138 | @Override 139 | public boolean isCancelEnabled() { 140 | return true; 141 | } 142 | 143 | @Override 144 | public void clearCanceled() { 145 | isCancelled = false; 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /Sample2/src/com/nosecurecode/libghidra/LibProgramHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | This interface implements the callback functionality, for any feedback, contact NADER SHALLABI at nader@nosecurecode.com 3 | */ 4 | 5 | 6 | package com.nosecurecode.libghidra; 7 | 8 | import ghidra.program.model.listing.Program; 9 | 10 | /** 11 | * Implement this interface in the class that need to have a callback after Ghidra processing 12 | */ 13 | public interface LibProgramHandler { 14 | public void PostProcessHandler(Program program); 15 | } 16 | -------------------------------------------------------------------------------- /Sample2/src/lib/ghidra.jar.txt: -------------------------------------------------------------------------------- 1 | Run buildGhidraJar.bat batch file located in Ghidera archive under the "support" folder, then copy the generated ghidra.jar to this folder --------------------------------------------------------------------------------