├── .github └── workflows │ └── main.yml ├── .gitignore ├── .gitmodules ├── Module.manifest ├── Readme.md ├── build.gradle ├── extension.properties ├── settings.gradle └── src └── main ├── java └── bindiffhelper │ ├── BinDiffHelperPlugin.java │ ├── BinDiffHelperProvider.java │ ├── BinExport2File.java │ ├── ComparisonTableModel.java │ ├── DiffWizard.java │ ├── ImportFunctionNamesAction.java │ ├── SettingsDialog.java │ ├── ToggleCheckSelectedAction.java │ ├── UpdateFunctionColoringAction.java │ └── UpdatePlateCommentsAction.java └── resources └── images ├── BDH.png ├── arrow_merge.png ├── bd.png ├── bindiff-48x48-rgba.png ├── check_box.png ├── color.png ├── comment.png ├── open_db.png ├── open_from_project.png ├── setting_tools.png ├── table_go.png └── table_lightning.png /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | on: 3 | push: 4 | pull_request: 5 | workflow_dispatch: 6 | inputs: 7 | ghidra_version: 8 | description: 'Specify the Ghidra version(s) you want to build for (e.g. "latest", "11.0")' 9 | required: true 10 | default: '"latest"' 11 | schedule: 12 | - cron: '0 0 1 * *' # Monthly 13 | permissions: 14 | contents: write 15 | 16 | jobs: 17 | build: 18 | runs-on: ubuntu-latest 19 | 20 | strategy: 21 | fail-fast: false 22 | matrix: 23 | ghidra: ${{ fromJSON(format('[{0}]', inputs.ghidra_version || '"latest","11.2.1","11.2","11.1.2","11.1.1","11.1","11.0.3","11.0.2","11.0.1","11.0"')) }} 24 | 25 | steps: 26 | - name: Clone Repository 27 | uses: actions/checkout@v4 28 | with: 29 | submodules: 'true' 30 | 31 | - name: Install Java 32 | uses: actions/setup-java@v4 33 | with: 34 | distribution: 'temurin' 35 | java-version: '21' 36 | 37 | - name: Install Gradle 38 | uses: gradle/actions/setup-gradle@v3 39 | 40 | - name: Install Ghidra ${{ matrix.ghidra }} 41 | uses: antoniovazquezblanco/setup-ghidra@v2.0.4 42 | with: 43 | auth_token: ${{ secrets.GITHUB_TOKEN }} 44 | version: ${{ matrix.ghidra }} 45 | 46 | - name: Build 47 | run: gradle 48 | 49 | - name: Upload artifacts 50 | uses: actions/upload-artifact@v4 51 | with: 52 | name: BinDiffHelper_Ghidra_${{ matrix.ghidra }} 53 | path: dist/*.zip 54 | 55 | release: 56 | runs-on: "ubuntu-latest" 57 | needs: build 58 | if: contains(github.ref, 'refs/tags/v') 59 | 60 | steps: 61 | - name: Download binaries 62 | uses: actions/download-artifact@v4 63 | 64 | - name: Release stable 65 | if: contains(github.ref, 'refs/tags/v') 66 | uses: marvinpinto/action-automatic-releases@v1.2.1 67 | with: 68 | repo_token: "${{ secrets.GITHUB_TOKEN }}" 69 | title: "BinDiffHelper ${{github.ref_name}}" 70 | files: BinDiffHelper_Ghidra_*/*.zip 71 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | build/ 3 | dist/ 4 | lib/* 5 | .externalToolBuilders/ 6 | src/main/java/com/ 7 | .project 8 | .classpath 9 | .settings 10 | .gradle/ -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "binexport"] 2 | path = binexport 3 | url = https://github.com/google/binexport.git 4 | -------------------------------------------------------------------------------- /Module.manifest: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ubfx/BinDiffHelper/417a73d6955a8fd868d079374d9220f138903368/Module.manifest -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # BinDiffHelper 2 | 3 | Ghidra extension that uses BinDiff on your Ghidra project to find matching functions and rename them automatically (BinDiff 6, 7, 8 supported). 4 | Check out the [BinDiff manual](https://www.zynamics.com/bindiff/manual/) to see how it works and how it matches functions / basic blocks. However, with this extension, BinDiff is automated from within Ghidra, so you don't have to diff your binaries yourself. 5 | 6 | **Please report bugs via the issue feature on github** 7 | 8 | ## What it does / Changelog 9 | ### v0.6.3 10 | * Allow table sorting 11 | 12 | ### v0.6.2 13 | * Fix a bug where the diffing hangs forever on BinDiff 8 14 | * When double clicking a symbol, follow it in both primary and secondary code listings 15 | 16 | ### v0.6 17 | * Update sqlite driver library, should now run on Apple silicon 18 | * Support external textual diffs between decompiled functions (see https://github.com/ubfx/BinDiffHelper/pull/24) 19 | * UI updates 20 | 21 | ### v0.5 22 | * Support BinDiff 8 23 | * Release for Ghidra 11.1 24 | 25 |
26 | Older changes 27 | ### v0.4.3 28 | * Release for Ghidra 11.0 29 | 30 | ### v0.4.2 31 | * Add function to import all functions (not just the ones checked in the table) 32 | * Add function to toggle the checkbox for multiple elements in the table 33 | * Update to support Ghidra 10.3 34 | 35 | ### v0.4.1 36 | * Update to support Gradle 7.5 and Ghidra 10.1 37 | 38 | ### v0.4.0 39 | * Support BinDiff 7 and Ghidra 10 40 | 41 | ### v0.3.2 42 | * Fixed a bug where diffing with a different file from same project didn't work 43 | * Fixed a bug where files with short names (< 3 characters>) could not be diffed 44 | 45 | ### v0.3.1 46 | * Fixed a crash when opening a file via the "from project" option 47 | 48 | ### v0.3 49 | * Coloring matched functions in the listing 50 | * Add comments to matched functions in the listing linking to the other binary 51 | * Fixed a bug where protobuf library was missing in some builds 52 | * New file load menu 53 | 54 | ### v0.2 55 | * Fix a bug where the file to import to needed to be checked out. 56 | * Increase size of project file selection dialog 57 | 58 | ### v0.1 59 | * BinDiff the binary opened in Ghidra with another binary from the same Ghidra project, show results and import function names 60 | * Open a BinDiff file, created with BinDiff 6, from two .BinExport files and import the matching function names in Ghidra 61 | 62 |
63 | 64 | ## How to Install 65 | Either download the .zip-File from the [release](https://github.com/ubfx/BinDiffHelper/releases), if it's compatible to your Ghidra version, otherwise see *How to build* below. 66 | 67 | 1. Open Ghidra 68 | 1. In the **Main Window**: Go to **File->Install Extensions...** 69 | 1. Click the +-Button in the top right and select the BinDiffHelper zip file 70 | 1. Close the plugin manager. Restart Ghidra to load the new plugin 71 | 1. See *Usage* below 72 | 73 | ## Recommended other tools 74 | * BinExport plugin [binaries](https://github.com/google/binexport/releases) or compiled from [source](https://github.com/google/binexport/tree/master/java/BinExport) for your specific Ghidra version 75 | * BinDiff (https://zynamics.com/software.html) 76 | 77 | Without these, you will only be able to import .BinDiff files and not automatically export and diff from your Ghidra project 78 | 79 | ## Usage 80 | Make sure the plugin is loaded in the Code Explorer by opening a file in Ghidra and in the **Code Explorer** go to **File->Configure** 81 | 82 | Click the small plug-icon in the top right: 83 | 84 | ![Configure tool](https://i.imgur.com/xVqdY9U.png) 85 | 86 | and make sure the checkbox next to BinDiffHelperPlugin is checked. 87 | 88 | ![Configure plugins](https://i.imgur.com/n6yhIpz.png) 89 | 90 | ## Import external .BinDiff 91 | 92 | The corresponding .BinExport-Files need to be in the same Folder. 93 | 94 | Open the file you want to import names into in the code Browser, then go to **Window->BinDiffHelper** 95 | ![Open BinDiffHelper](https://i.imgur.com/nl5Jino.png) 96 | 97 | Use the *Open from BinDiff* button or menu item and select your .BinDiff file. 98 | 99 | ![Example import](https://i.imgur.com/b9HXm3s.png) 100 | 101 | Select all the function names you want to import and click the **Import function names** button in the top right or the menu item. 102 | 103 | ## Compare between files in Ghidra project 104 | Go to **Window->BinDiffHelper**, make sure there are no warnings concerning BinExport or BinDiff, then click the **Open from project** button. 105 | 106 | Select the other file from the tree and click OK. 107 | 108 | ![Example project import](https://i.imgur.com/ebJ6CA4.png) 109 | 110 | ## How to build 111 | Requirements: 112 | 113 | * Ghidra installation (https://ghidra-sre.org) or compiled from source 114 | * Some jdk. There might be certain restrictions depending on how your Ghidra was built and what other plugins you're using. I recommend using [Temurin 21 LTS](https://adoptium.net/temurin/releases/?version=21), as this currently seems to work with both BinExport and BinDiffHelper. 115 | * gradle (tested with 7.5, 8.10) 116 | 117 | ### Clone the repository 118 | Recursively clone the repository, as it depends on [BinExport](https://github.com/google/binexport) for the respective protocol buffer definition. 119 | ``` 120 | git clone --recurse-submodules https://github.com/ubfx/BinDiffHelper.git 121 | ``` 122 | 123 | ### Build it 124 | You need to set the `GHIDRA_INSTALL_DIR` environment variable to the Ghidra installation dir. 125 | If you have different JDKs installed, make sure the environment variable `JAVA_HOME` points to the one your Ghidra installation uses. 126 | 127 | The extension will be built for that Ghidra version specifically. 128 | 129 | And then go the *BinDiffHelper folder* in your shell and do 130 | 131 | ``` 132 | gradle 133 | ``` 134 | 135 | This will build both BinExport in the respective submodule and BinDiffHelper in the parent directory. There should have been a .zip-File created in the dist directory. Use that .zip File to install according to the instructions above. 136 | 137 | ## Development / Debugging setup 138 | Sometimes it's useful to be able to debug the extension together with Ghidra, here are some notes on that: 139 | 140 | 1. Clone and build Ghidra and let gradle create eclipse projects according to the DevGuide 141 | 1. Import the projects into eclipse (make sure it has relevant plugins for extension development) 142 | 1. [Install GhidraDev](https://github.com/NationalSecurityAgency/ghidra/blob/master/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/README.md#manual-installation-in-eclipse-online) 143 | 1. Unpack the Ghidra build and link GhidraDev to it. Maybe have to set `GHIDRA_INSTALL_DIR` environment variable 144 | 1. Run Ghidra from eclipse and install BinExport extension 145 | 1. Create BinDiffHelper eclipse project with `gradle build eclipse` 146 | 1. Import it into eclipse and use GhidraDev to link it to the Ghidra build 147 | 1. Debug As->Ghidra 148 | 149 | When debugging Ghidra with the extension out of eclipse, the extension is loaded into Ghidra automatically (don't go through the usual extension install). However, the plugin has to be enabled in the Code Explorer. 150 | 151 | 152 | 153 | ## References 154 | * https://github.com/google/bindiff/releases 155 | * https://www.zynamics.com/bindiff/manual/ 156 | * https://github.com/google/binexport/ 157 | 158 | Icons from: [Fatcow free icons](https://www.fatcow.com/free-icons) 159 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java' 2 | apply plugin: 'eclipse' 3 | 4 | java { 5 | toolchain { 6 | languageVersion = JavaLanguageVersion.of(21) 7 | } 8 | } 9 | 10 | repositories { 11 | mavenCentral() 12 | } 13 | 14 | buildscript { 15 | repositories { 16 | mavenCentral() 17 | } 18 | 19 | dependencies { 20 | } 21 | } 22 | 23 | dependencies { 24 | implementation project(':BinExport') 25 | implementation 'org.xerial:sqlite-jdbc:3.46.0.1' 26 | } 27 | 28 | // Standard Ghidra extension Gradle code follows. 29 | //----------------------START "DO NOT MODIFY" SECTION------------------------------ 30 | def ghidraInstallDir 31 | 32 | if (System.env.GHIDRA_INSTALL_DIR) { 33 | ghidraInstallDir = System.env.GHIDRA_INSTALL_DIR 34 | } 35 | else if (project.hasProperty("GHIDRA_INSTALL_DIR")) { 36 | ghidraInstallDir = project.getProperty("GHIDRA_INSTALL_DIR") 37 | } 38 | 39 | if (ghidraInstallDir) { 40 | apply from: new File(ghidraInstallDir).getCanonicalPath() + "/support/buildExtension.gradle" 41 | } 42 | else { 43 | throw new GradleException("GHIDRA_INSTALL_DIR is not defined!") 44 | } 45 | //----------------------END "DO NOT MODIFY" SECTION------------------------------- 46 | 47 | //tasks.named("buildExtension").configure { 48 | // dependsOn(":BinExport:generateProto") 49 | //} 50 | 51 | tasks.named("copyDependencies").configure { 52 | duplicatesStrategy = DuplicatesStrategy.EXCLUDE 53 | } 54 | 55 | buildExtension.exclude '.github/**' 56 | buildExtension.exclude 'binexport/**' 57 | -------------------------------------------------------------------------------- /extension.properties: -------------------------------------------------------------------------------- 1 | name=BinDiffHelper 2 | description=Helps importing function names from BinDiffs 3 | author=Felix Schneider 4 | createdOn=06/03/2020 5 | version=@extversion@ 6 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = "BinDiffHelper" 2 | include "BinExport" 3 | project(":BinExport").projectDir = new File('binexport/java') -------------------------------------------------------------------------------- /src/main/java/bindiffhelper/BinDiffHelperPlugin.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 | package bindiffhelper; 17 | 18 | import java.io.BufferedReader; 19 | import java.io.File; 20 | import java.io.IOException; 21 | import java.io.InputStreamReader; 22 | import java.nio.file.FileSystems; 23 | import java.nio.file.Path; 24 | import java.util.ArrayList; 25 | import java.util.Arrays; 26 | import java.util.List; 27 | import java.util.concurrent.TimeUnit; 28 | import java.util.function.Consumer; 29 | 30 | import ghidra.MiscellaneousPluginPackage; 31 | import ghidra.app.plugin.PluginCategoryNames; 32 | import ghidra.app.plugin.ProgramPlugin; 33 | import ghidra.app.services.CodeViewerService; 34 | import ghidra.app.util.Option; 35 | import ghidra.app.util.exporter.Exporter; 36 | import ghidra.framework.model.DomainFile; 37 | import ghidra.framework.plugintool.PluginInfo; 38 | import ghidra.framework.plugintool.PluginTool; 39 | import ghidra.framework.plugintool.util.PluginStatus; 40 | import ghidra.framework.preferences.Preferences; 41 | import ghidra.program.model.listing.Program; 42 | import ghidra.util.Msg; 43 | import ghidra.util.classfinder.ClassSearcher; 44 | import ghidra.util.exception.CancelledException; 45 | import ghidra.util.task.TaskDialog; 46 | import ghidra.util.task.TaskMonitor; 47 | 48 | /** 49 | * TODO: Provide class-level documentation that describes what this plugin does. 50 | */ 51 | //@formatter:off 52 | @PluginInfo( 53 | status = PluginStatus.STABLE, 54 | packageName = MiscellaneousPluginPackage.NAME, 55 | category = PluginCategoryNames.ANALYSIS, 56 | shortDescription = "This plugin helps importing function names using BinDiff 6/7/8", 57 | description = "This plugin helps importing function names using BinDiff 6/7/8", 58 | servicesRequired = { CodeViewerService.class } 59 | ) 60 | //@formatter:on 61 | public class BinDiffHelperPlugin extends ProgramPlugin { 62 | 63 | BinDiffHelperProvider provider; 64 | Exporter binExportExporter; 65 | String binDiffBinary; 66 | String diffCommand; 67 | boolean enableNamespace; 68 | protected String defaultBinPath; 69 | protected String defaultDiffCommand; 70 | Program program; 71 | 72 | public final static String BDBINPROPERTY = "de.ubfx.bindiffhelper.bindiffbinary"; 73 | public final static String DIFFCOMMAND = "de.ubfx.bindiffhelper.diffCommand"; 74 | public final static String ENABLENAMESPACE = "de.ubfx.bindiffhelper.enableNamespace"; 75 | /** 76 | * Plugin constructor. 77 | * 78 | * @param tool The plugin tool that this plugin is added to. 79 | */ 80 | public BinDiffHelperPlugin(PluginTool tool) { 81 | super(tool); 82 | 83 | binExportExporter = null; 84 | 85 | try { 86 | Class binExportExporterClass = Class.forName("com.google.security.binexport.BinExportExporter"); 87 | 88 | List list = new ArrayList<>(ClassSearcher.getInstances(Exporter.class)); 89 | 90 | for (Exporter e: list) 91 | { 92 | if (e.getClass() == binExportExporterClass) 93 | { 94 | binExportExporter = e; 95 | break; 96 | } 97 | } 98 | 99 | } catch (ClassNotFoundException e) { 100 | } 101 | 102 | try { 103 | updateBinDiffBinary(); 104 | } catch (Exception e) { 105 | 106 | } 107 | 108 | if (System.getProperty("os.name").toLowerCase().contains("win")) { 109 | defaultBinPath = "C:\\Program Files\\BinDiff\\bin\\bindiff.exe"; 110 | defaultDiffCommand = "notepad++ -multiInst -nosession -lc -pluginMessage=compare \"$file1\" \"$file2\""; 111 | } 112 | if (System.getProperty("os.name").toLowerCase().contains("nix")) { 113 | // defaultBinPath = "/opt/bindiff/bin/bindiff"; 114 | defaultDiffCommand = "x-terminal-emulator -e 'diff -u \"$file1\" \"$file2\"'"; 115 | } 116 | 117 | binDiffBinary = Preferences.getProperty(BDBINPROPERTY, defaultBinPath); 118 | diffCommand = Preferences.getProperty(DIFFCOMMAND, defaultDiffCommand); 119 | enableNamespace = Boolean.parseBoolean(Preferences.getProperty(ENABLENAMESPACE, "false")); 120 | 121 | provider = new BinDiffHelperProvider(this, this.getCurrentProgram()); 122 | provider.setTitle("BinDiffHelper"); 123 | 124 | provider.addToTool(); 125 | } 126 | 127 | File binExportDomainFile(DomainFile df) 128 | { 129 | File out = null; 130 | 131 | try { 132 | var dof = df.getReadOnlyDomainObject(this, DomainFile.DEFAULT_VERSION, TaskMonitor.DUMMY); 133 | 134 | if (dof instanceof Program) 135 | { 136 | out = File.createTempFile(df.getName() + "_bdh", ".BinExport"); 137 | 138 | if (enableNamespace) { 139 | binExportExporter.setOptions( 140 | List.of( 141 | new Option("Prepend Namespace to Function Names", true) 142 | ) 143 | ); 144 | } 145 | 146 | if (binExportExporter.export(out, dof, null, TaskMonitor.DUMMY) == false) 147 | { 148 | out.delete(); 149 | out = null; 150 | } 151 | } 152 | 153 | 154 | dof.release(this); 155 | 156 | 157 | } catch (Exception e) { 158 | out = null; 159 | } 160 | 161 | return out; 162 | } 163 | 164 | public void callBinDiff(DomainFile df, Consumer callback) 165 | { 166 | if (binDiffBinary == null) { 167 | 168 | Msg.showError(this, null, "Error", "Unexpected error, no binDiffBinary found"); 169 | callback.accept(null); 170 | return; 171 | } 172 | TaskDialog d = new TaskDialog("Exporting", true, false, true); 173 | d.setMaximum(5); 174 | 175 | new Thread(() -> { 176 | File[] ret = null; 177 | 178 | d.setMessage("Exporting primary file"); 179 | final var sec = binExportDomainFile(df); 180 | d.incrementProgress(1); 181 | 182 | d.setMessage("Exporting secondary file"); 183 | final var pri = binExportDomainFile(program.getDomainFile()); 184 | d.incrementProgress(1); 185 | 186 | d.setMessage("Executing BinDiff"); 187 | 188 | String outputDir = pri.getParentFile().getAbsolutePath(); 189 | 190 | String[] cmd = {binDiffBinary, pri.getAbsolutePath(), sec.getAbsolutePath(), "--output_dir", outputDir}; 191 | 192 | Msg.debug(this, "bd: " + binDiffBinary + "\nfiles:" + pri.getAbsolutePath() + "," + sec.getAbsolutePath() + "\n"+ 193 | "output dir: " + outputDir); 194 | Msg.debug(this, "printing BD output for cmd: " + Arrays.toString(cmd)); 195 | Process p = null; 196 | try { 197 | p = Runtime.getRuntime().exec(cmd); 198 | var i = new BufferedReader(new InputStreamReader(p.getInputStream())); 199 | var stderr = p.getErrorStream(); 200 | while (!p.waitFor(1, TimeUnit.SECONDS)) { 201 | // Empty stderr buffer 202 | stderr.skip(stderr.available()); 203 | 204 | while (true) 205 | { 206 | String line = i.readLine(); 207 | 208 | if (line == null) 209 | break; 210 | 211 | Msg.debug(this, ">" + line); 212 | } 213 | 214 | d.checkCanceled(); 215 | } 216 | 217 | Msg.debug(this, "end of output"); 218 | 219 | } catch (IOException | InterruptedException e) { 220 | Msg.showError(this, d.getComponent(), "Error", "Error when Exporting"); 221 | d.close(); 222 | callback.accept(null); 223 | return; 224 | } catch (CancelledException e) { 225 | if (p != null) { 226 | p.destroyForcibly(); 227 | } 228 | d.close(); 229 | 230 | callback.accept(null); 231 | return; 232 | } 233 | 234 | d.incrementProgress(1); 235 | d.setMessage("Looking for generated file"); 236 | 237 | Path bindiff = FileSystems.getDefault().getPath(outputDir, 238 | pri.getName().replace(".BinExport", "") + 239 | "_vs_" + 240 | sec.getName().replace(".BinExport", "") 241 | + ".BinDiff"); 242 | 243 | Msg.debug(this, "looking for bindiff at " + bindiff.toFile().getAbsolutePath()); 244 | 245 | if (!bindiff.toFile().exists()) { 246 | ret = null; 247 | 248 | Msg.showError(this, d.getComponent(), "Error", "Error when trying to find generated BinDiff file"); 249 | } 250 | else 251 | { 252 | ret = new File[] {pri, sec, bindiff.toFile()}; 253 | } 254 | 255 | d.incrementProgress(1); 256 | d.close(); 257 | 258 | 259 | callback.accept(ret); 260 | }).start(); 261 | tool.showDialog(d); 262 | } 263 | 264 | public boolean updateBinDiffBinary() throws IOException 265 | { 266 | String bin = Preferences.getProperty(BDBINPROPERTY); 267 | binDiffBinary = null; 268 | 269 | if (bin == null || bin.isEmpty()) { 270 | return false; 271 | } 272 | 273 | File f = new File(bin); 274 | if (!f.exists() || !f.canExecute()) 275 | { 276 | throw new IOException("BinDiffBinary: File does not exist or is not executable"); 277 | } 278 | 279 | String[] cmd = {bin, ""}; 280 | try { 281 | var p = Runtime.getRuntime().exec(cmd); 282 | String outp = new BufferedReader(new InputStreamReader(p.getInputStream())).readLine(); 283 | 284 | p.waitFor(); 285 | 286 | 287 | if (!outp.startsWith("BinDiff 6") && !outp.startsWith("BinDiff 7") && !outp.startsWith("BinDiff 8")) 288 | { 289 | throw new IOException("BinDiffBinary: This does not seem to be a BinDiff 6/7/8 binary"); 290 | } 291 | 292 | 293 | } catch (Exception e) { 294 | throw new IOException("BinDiffBinary: Error running the file: " + e); 295 | } 296 | 297 | binDiffBinary = bin; 298 | 299 | return true; 300 | } 301 | 302 | public void updateEnableNamespace(boolean enable) 303 | { 304 | enableNamespace = enable; 305 | Preferences.setProperty(ENABLENAMESPACE, Boolean.toString(enable)); 306 | } 307 | 308 | public void updateDiffCommand(String cmd) 309 | { 310 | diffCommand = cmd == null || cmd.isEmpty() ? defaultDiffCommand : cmd; 311 | Preferences.setProperty(DIFFCOMMAND, cmd); 312 | } 313 | 314 | @Override 315 | public void init() { 316 | super.init(); 317 | 318 | // TODO: Acquire services if necessary 319 | } 320 | 321 | @Override 322 | protected void programActivated(Program p) 323 | { 324 | program = p; 325 | provider.setProgram(p); 326 | } 327 | 328 | } 329 | -------------------------------------------------------------------------------- /src/main/java/bindiffhelper/BinDiffHelperProvider.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 | package bindiffhelper; 17 | 18 | import java.awt.BorderLayout; 19 | import java.awt.Dimension; 20 | import java.awt.event.MouseAdapter; 21 | import java.awt.event.MouseEvent; 22 | import java.io.File; 23 | import java.nio.file.Files; 24 | import java.nio.file.Path; 25 | import java.nio.file.Paths; 26 | import java.sql.Connection; 27 | import java.sql.DriverManager; 28 | import java.sql.ResultSet; 29 | import java.sql.SQLException; 30 | import java.sql.Statement; 31 | import java.util.ArrayList; 32 | import java.util.List; 33 | import java.util.Set; 34 | import java.util.TreeSet; 35 | import java.util.Comparator; 36 | 37 | import javax.swing.JComponent; 38 | import javax.swing.JLabel; 39 | import javax.swing.JPanel; 40 | import javax.swing.JScrollPane; 41 | import javax.swing.SwingConstants; 42 | import javax.swing.table.TableRowSorter; 43 | 44 | import docking.ActionContext; 45 | import docking.DockingWindowManager; 46 | import docking.WindowPosition; 47 | import docking.action.DockingAction; 48 | import docking.action.MenuData; 49 | import docking.action.ToolBarData; 50 | import docking.widgets.table.GTable; 51 | import docking.wizard.WizardManager; 52 | import ghidra.app.decompiler.DecompInterface; 53 | import ghidra.app.decompiler.DecompileResults; 54 | import ghidra.app.events.ProgramLocationPluginEvent; 55 | import ghidra.app.services.CodeViewerService; 56 | import ghidra.framework.cmd.Command; 57 | import ghidra.framework.model.DomainFile; 58 | import ghidra.framework.model.Project; 59 | import ghidra.framework.plugintool.ComponentProviderAdapter; 60 | import ghidra.framework.plugintool.PluginEvent; 61 | import ghidra.framework.plugintool.PluginTool; 62 | import ghidra.program.model.address.Address; 63 | import ghidra.program.model.address.AddressSpace; 64 | import ghidra.program.model.listing.Function; 65 | import ghidra.program.model.listing.Program; 66 | import ghidra.program.model.symbol.Symbol; 67 | import ghidra.program.util.ProgramLocation; 68 | import ghidra.util.HTMLUtilities; 69 | import ghidra.util.Msg; 70 | import ghidra.util.task.TaskMonitor; 71 | import resources.ResourceManager; 72 | 73 | public class BinDiffHelperProvider extends ComponentProviderAdapter { 74 | 75 | protected BinDiffHelperPlugin plugin; 76 | 77 | protected JPanel gui; 78 | protected GTable table; 79 | ComparisonTableModel ctm; 80 | 81 | protected JScrollPane scrollPane; 82 | 83 | protected Connection conn; 84 | protected Program program; 85 | 86 | protected ToggleCheckSelectedAction toggleCheckedAction; 87 | protected ImportCheckedFunctionNamesAction importCheckedAction; 88 | protected ImportAllFunctionNamesAction importAllAction; 89 | protected UpdatePlateCommentsAction upca; 90 | protected UpdateFunctionColoringAction ufca; 91 | 92 | protected CodeViewerService cvs; 93 | 94 | protected final boolean hasExporter; 95 | 96 | protected String thisProg, otherProg; 97 | 98 | public BinDiffHelperProvider(BinDiffHelperPlugin plugin, Program program) { 99 | super(plugin.getTool(), "BinDiffHelper GUI Provider", plugin.getName()); 100 | 101 | hasExporter = plugin.binExportExporter != null; 102 | 103 | this.plugin = plugin; 104 | setProgram(program); 105 | 106 | setIcon(ResourceManager.loadImage("images/BDH.png")); 107 | 108 | gui = new JPanel(new BorderLayout()); 109 | 110 | generateWarnings(); 111 | 112 | setDefaultWindowPosition(WindowPosition.WINDOW); 113 | gui.setFocusable(true); 114 | gui.setMinimumSize(new Dimension(400, 400)); 115 | gui.setPreferredSize(new Dimension(1200, 425)); 116 | 117 | createActions(); 118 | } 119 | 120 | public void generateWarnings() { 121 | if (table != null) 122 | return; 123 | 124 | gui.removeAll(); 125 | 126 | List warnings = new ArrayList(); 127 | 128 | if (!hasExporter) { 129 | warnings.add("The BinExport plugin couldn't be found so some features are disabled.
" 130 | + "See the Readme at https://github.com/ubfx/BinDiffHelper for a link " 131 | + "to the BinExport as binaries or in source."); 132 | } 133 | 134 | if (plugin.binDiffBinary == null) { 135 | warnings.add("BinDiff binary has not been set so some features are disabled.
" 136 | + "Go to the settings button and select the BinDiff binary if you want to connect with BinDiff directly
" 137 | + "You can download BinDiff from https://zynamics.com/software.html"); 138 | } 139 | 140 | if (!warnings.isEmpty()) { 141 | String labelContent = ""; 142 | 143 | for (String w : warnings) 144 | labelContent += "

" + w + "


"; 145 | 146 | labelContent += ""; 147 | JLabel warningLabel = new JLabel(labelContent, SwingConstants.CENTER); 148 | gui.add(warningLabel, BorderLayout.CENTER); 149 | } 150 | 151 | refresh(); 152 | } 153 | 154 | public void refresh() { 155 | gui.revalidate(); 156 | gui.repaint(); 157 | } 158 | 159 | private void createActions() { 160 | GeneralOpenAction op = new GeneralOpenAction(plugin); 161 | SettingsDialogAction sa = new SettingsDialogAction(plugin); 162 | 163 | addLocalAction(sa); 164 | 165 | toggleCheckedAction = new ToggleCheckSelectedAction(plugin); 166 | importCheckedAction = new ImportCheckedFunctionNamesAction(plugin); 167 | importCheckedAction.setEnabled(false); 168 | importAllAction = new ImportAllFunctionNamesAction(plugin); 169 | importAllAction.setEnabled(false); 170 | upca = new UpdatePlateCommentsAction(plugin); 171 | upca.setEnabled(false); 172 | ufca = new UpdateFunctionColoringAction(plugin); 173 | ufca.setEnabled(false); 174 | 175 | addLocalAction(op); 176 | addLocalAction(toggleCheckedAction); 177 | addLocalAction(importCheckedAction); 178 | addLocalAction(importAllAction); 179 | addLocalAction(upca); 180 | addLocalAction(ufca); 181 | } 182 | 183 | void dispose() { 184 | removeFromTool(); 185 | } 186 | 187 | @Override 188 | public JComponent getComponent() { 189 | return gui; 190 | } 191 | 192 | public void execute(Command c) { 193 | if (program == null) 194 | return; 195 | 196 | tool.execute(c, program); 197 | } 198 | 199 | public void setProgram(Program p) { 200 | program = p; 201 | if (p != null) 202 | cvs = plugin.getTool().getService(CodeViewerService.class); 203 | } 204 | 205 | protected boolean isMetadataVersionOK(Connection c) throws Exception { 206 | boolean ret = true; 207 | 208 | Statement stmt = conn.createStatement(); 209 | ResultSet rs = stmt.executeQuery("SELECT version from metadata"); 210 | 211 | if (!rs.getString(1).startsWith("BinDiff 6") && !rs.getString(1).startsWith("BinDiff 7") 212 | && !rs.getString(1).startsWith("BinDiff 8")) { 213 | ret = false; 214 | } 215 | 216 | stmt.close(); 217 | 218 | return ret; 219 | } 220 | 221 | public class DiffState { 222 | DomainFile df; 223 | BinExport2File beFile; 224 | Program prog; 225 | String addressCol, filename, exefilename, hash; 226 | CodeViewerService cvs; 227 | } 228 | 229 | public DiffState primary, secondary; 230 | 231 | public void openDB(String dbFilename) throws Exception { 232 | if (!dbFilename.endsWith(".BinDiff")) 233 | throw new Exception("Unexpected filename ending (expected .BinDiff)"); 234 | 235 | conn = DriverManager.getConnection("jdbc:sqlite:" + dbFilename); 236 | 237 | if (!isMetadataVersionOK(conn)) { 238 | Msg.showError(this, this.getComponent(), "Error", "Can't open this file as a BinDiff 6/7/8 database."); 239 | return; 240 | } 241 | 242 | primary = new DiffState(); 243 | secondary = new DiffState(); 244 | 245 | Statement stmt = conn.createStatement(); 246 | 247 | ResultSet rs = stmt.executeQuery("SELECT filename, exefilename, hash FROM file"); 248 | 249 | DiffState ret[] = { primary, secondary }; 250 | for (int i = 0; i < 2; i++) { 251 | if (!rs.next()) 252 | throw new Exception("Couldn't get data from file table"); 253 | 254 | ret[i].filename = rs.getString("filename"); 255 | ret[i].exefilename = rs.getString("exefilename"); 256 | ret[i].hash = rs.getString("hash"); 257 | } 258 | 259 | stmt.close(); 260 | } 261 | 262 | public void openExternalDB(String dbFilename) throws Exception { 263 | openDB(dbFilename); 264 | 265 | File beFile[] = new File[2]; 266 | String filenames[] = { primary.filename, secondary.filename }; 267 | 268 | Path bindiff = Paths.get(dbFilename); 269 | 270 | for (int i = 0; i < 2; i++) { 271 | beFile[i] = bindiff.resolveSibling(filenames[i] + ".BinExport").toFile(); 272 | 273 | if (!beFile[i].exists()) { 274 | Msg.showError(this, getComponent(), "Error", "Could not open " + beFile[i].getAbsolutePath()); 275 | return; 276 | } 277 | } 278 | 279 | // Assume bi[0] is the primary for now, this can be swapped later after matching 280 | primary.beFile = new BinExport2File(beFile[0]); 281 | primary.addressCol = "address1"; 282 | primary.cvs = cvs; 283 | secondary.beFile = new BinExport2File(beFile[1]); 284 | secondary.addressCol = "address2"; 285 | } 286 | 287 | public void openExternalDBWithBinExports(String dbFilename, File pimaryBeFile, File secondaryBeFile) 288 | throws Exception { 289 | openDB(dbFilename); 290 | 291 | primary.beFile = new BinExport2File(pimaryBeFile); 292 | primary.addressCol = "address1"; 293 | primary.cvs = cvs; 294 | 295 | secondary.beFile = new BinExport2File(secondaryBeFile); 296 | secondary.addressCol = "address2"; 297 | } 298 | 299 | protected void doDiffWork() { 300 | if (primary == null || secondary == null) 301 | return; 302 | 303 | Project project = plugin.getTool().getProject(); 304 | Path projectPath = project.getProjectLocator().getProjectDir().toPath(); 305 | AddressSpace addrSpace = program.getAddressFactory().getDefaultAddressSpace(); 306 | AddressSpace addrSpace2 = secondary.prog != null ? secondary.prog.getAddressFactory().getDefaultAddressSpace() 307 | : null; 308 | 309 | var st = program.getSymbolTable(); 310 | 311 | Set priFnSet = primary.beFile.getFunctionAddressSet(); 312 | Set commonPriFnSet = new TreeSet(); 313 | Set secFnSet = secondary.beFile.getFunctionAddressSet(); 314 | Set commonSecFnSet = new TreeSet(); 315 | 316 | ctm = new ComparisonTableModel(plugin.enableNamespace); 317 | try { 318 | Statement stmt = conn.createStatement(); 319 | 320 | ResultSet rs = stmt.executeQuery("SELECT address1, address2, similarity, confidence, name " 321 | + "FROM function JOIN functionalgorithm ON function.algorithm=functionalgorithm.id " 322 | + "ORDER BY similarity ASC"); 323 | 324 | while (rs.next()) { 325 | 326 | Address priAddress = addrSpace.getAddress(rs.getLong(primary.addressCol)); 327 | long secAddress = rs.getLong(secondary.addressCol); 328 | 329 | // Store the addresses of functions that are the same so we can add sets of 330 | // functions that are in one but not the other 331 | commonPriFnSet.add(rs.getLong(primary.addressCol)); 332 | commonSecFnSet.add(rs.getLong(secondary.addressCol)); 333 | 334 | Symbol s = null; 335 | 336 | if (st.hasSymbol(priAddress)) 337 | s = st.getSymbols(priAddress)[0]; 338 | 339 | String priFn = primary.beFile.getFunctionName(priAddress); 340 | String secFn = secondary.beFile.getFunctionName(secAddress); 341 | 342 | double similarity = rs.getDouble("similarity"); 343 | double confidence = rs.getDouble("confidence"); 344 | 345 | // TODO: Document this: Note to self, this assumes that the DB that has had the 346 | // work done to it is the secondary. 347 | // IE we are looking at a new file and we've done a bindiff against a DB where 348 | // we've already done some RE work. 349 | // We may want to add toggle here so if you are trying to look at a "new" db as 350 | // the secondary file we do the right thing. 351 | boolean defaultTicked = similarity == 1.0f && !secFn.startsWith("thunk_FUN_") 352 | && !secFn.startsWith("FUN_") && !priFn.equals(secFn); 353 | ctm.addEntry(new ComparisonTableModel.Entry(defaultTicked, priAddress, priFn, s, secAddress, secFn, 354 | similarity, confidence, rs.getString("name"))); 355 | } 356 | stmt.close(); 357 | 358 | } catch (SQLException e) { 359 | // TODO Auto-generated catch block 360 | e.printStackTrace(); 361 | } 362 | 363 | // Now lets add functions that are in our program but not the other to the list 364 | Set onlyInPrimary = new TreeSet(priFnSet); 365 | onlyInPrimary.removeAll(commonPriFnSet); 366 | Set onlyInSecondary = new TreeSet(secFnSet); 367 | onlyInSecondary.removeAll(commonSecFnSet); 368 | // Lets add the functions that are only in the primary here 369 | for (Long x : onlyInPrimary) { 370 | Address priAddress = addrSpace.getAddress(x); 371 | Symbol s = null; 372 | String priFn = primary.beFile.getFunctionName(priAddress); 373 | if (st.hasSymbol(priAddress)) 374 | s = st.getSymbols(priAddress)[0]; 375 | ctm.addEntry(new ComparisonTableModel.Entry(false, priAddress, priFn, s, 0, null, 0, 1, "Only in primary")); 376 | } 377 | // Lets add the functions that are only in the secondary here 378 | for (Long x : onlyInSecondary) { 379 | String secFn = secondary.beFile.getFunctionName(x); 380 | ctm.addEntry(new ComparisonTableModel.Entry(false, null, null, null, x, secFn, 0, 1, "Only in Secondary")); 381 | } 382 | 383 | Msg.showInfo(this, this.getComponent(), "Success", "Opened successfully"); 384 | 385 | boolean createTable = table == null; 386 | 387 | if (createTable) { 388 | table = new GTable(); 389 | 390 | TableRowSorter sorter = new TableRowSorter<>(ctm); 391 | table.setRowSorter(sorter); 392 | 393 | // Ensure proper sorting for similarity and confidence 394 | sorter.setComparator(6, Comparator.comparingDouble(o -> (Double) o)); // Similarity 395 | sorter.setComparator(7, Comparator.comparingDouble(o -> (Double) o)); // Confidence 396 | 397 | scrollPane = new JScrollPane(table); 398 | gui.removeAll(); 399 | gui.add(scrollPane, BorderLayout.CENTER); 400 | } 401 | 402 | table.setModel(ctm); 403 | 404 | table.getColumn("Import").setMaxWidth(50); 405 | table.getColumn("Similarity").setMaxWidth(100); 406 | table.setAutoResizeMode(GTable.AUTO_RESIZE_ALL_COLUMNS); 407 | 408 | table.addMouseListener(new MouseAdapter() { 409 | public void mousePressed(MouseEvent e) { 410 | var entry = ctm.getEntry(table.convertRowIndexToModel(table.getSelectedRow())); 411 | 412 | if (e.getClickCount() == 2 && table.getSelectedRow() != -1) { 413 | PluginEvent ev = new ProgramLocationPluginEvent(null, new ProgramLocation(program, entry.primaryAddress), program); 414 | tool.firePluginEvent(ev); 415 | 416 | if (secondary.prog != null && secondary.df != null) { 417 | Address secAddress = secondary.prog.getAddressFactory().getDefaultAddressSpace() 418 | .getAddress(entry.secondaryAddress); 419 | PluginEvent secev = new ProgramLocationPluginEvent(null, new ProgramLocation(secondary.prog, secAddress), secondary.prog); 420 | 421 | for (var consumer : secondary.df.getConsumers()) { 422 | if (consumer instanceof PluginTool pt) 423 | pt.firePluginEvent(secev); 424 | } 425 | tool.firePluginEvent(secev); 426 | } 427 | } 428 | if (secondary.prog != null && e.getClickCount() == 3 && table.getSelectedRow() != -1) { 429 | String pname = program.getDomainFile().getName().toString(); 430 | String pname2 = secondary.prog.getDomainFile().getName().toString(); 431 | 432 | try { 433 | Function function = program.getListing().getFunctionAt(entry.primaryAddress); 434 | Address secAddress = addrSpace2.getAddress(entry.secondaryAddress); 435 | Function function2 = secondary.prog.getListing().getFunctionAt(secAddress); 436 | 437 | String decompiledCode1 = decompileFunction(function, program); 438 | String decompiledCode2 = decompileFunction(function2, secondary.prog); 439 | 440 | Path path1 = projectPath.resolve("decompiled").resolve(pname) 441 | .resolve("0x" + Long.toHexString(entry.primaryAddress.getUnsignedOffset()) + ".c"); 442 | Path path2 = projectPath.resolve("decompiled").resolve(pname2) 443 | .resolve("0x" + Long.toHexString(secAddress.getUnsignedOffset()) + ".c"); 444 | 445 | writeToFile(path1, decompiledCode1); 446 | writeToFile(path2, decompiledCode2); 447 | 448 | String command = plugin.diffCommand.replace("$file1", path1.toString()).replace("$file2", 449 | path2.toString()); 450 | Runtime.getRuntime().exec(command); 451 | } catch (Exception ex) { 452 | Msg.showError(this, getComponent(), "Error", ex.getMessage()); 453 | } 454 | } 455 | } 456 | }); 457 | 458 | importCheckedAction.setEntries(ctm.getEntries()); 459 | importCheckedAction.setEnabled(true); 460 | importAllAction.setEntries(ctm.getEntries()); 461 | importAllAction.setEnabled(true); 462 | upca.setEntries(ctm.getEntries()); 463 | upca.setEnabled(true); 464 | ufca.setEntries(ctm.getEntries()); 465 | ufca.setEnabled(true); 466 | 467 | refresh(); 468 | 469 | } 470 | 471 | protected String decompileFunction(Function function, Program program) { 472 | DecompInterface decompiler = new DecompInterface(); 473 | try { 474 | decompiler.openProgram(program); 475 | DecompileResults results = decompiler.decompileFunction(function, 0, TaskMonitor.DUMMY); 476 | if (results != null && results.decompileCompleted()) { 477 | String decompiledCode = results.getDecompiledFunction().getC(); 478 | return decompiledCode; 479 | } else { 480 | throw new Exception("Failed to decompile function: " + function.getName()); 481 | } 482 | } catch (Exception e) { 483 | throw new Error("Error during decompilation: " + e.getMessage()); 484 | } finally { 485 | decompiler.dispose(); 486 | } 487 | } 488 | 489 | protected void writeToFile(Path path, String content) { 490 | try { 491 | Path parent = path.getParent(); 492 | if (parent != null) 493 | Files.createDirectories(parent); 494 | Files.write(path, content.getBytes()); 495 | } catch (Exception e) { 496 | Msg.showError(this, this.getComponent(), "Error: ", e.toString()); 497 | } 498 | } 499 | 500 | public class GeneralOpenAction extends DockingAction { 501 | 502 | public GeneralOpenAction(BinDiffHelperPlugin plugin) { 503 | super("Open from Project", plugin.getName()); 504 | 505 | this.setMenuBarData(new MenuData(new String[] { "Open", "Open a file for comparison" }, "Open")); 506 | 507 | setToolBarData(new ToolBarData(ResourceManager.loadImage("images/open_from_project.png"), "Open")); 508 | 509 | setDescription(HTMLUtilities.toHTML("Open a file for comparison")); 510 | 511 | } 512 | 513 | @Override 514 | public void actionPerformed(ActionContext context) { 515 | // DockingWindowManager.showDialog(new GeneralOpenDialog(plugin)); 516 | DiffPanelManager panelManager = new DiffPanelManager(plugin); 517 | WizardManager wm = new WizardManager("New Diff", true, panelManager); 518 | wm.showWizard(tool.getToolFrame()); 519 | } 520 | } 521 | 522 | public class SettingsDialogAction extends DockingAction { 523 | 524 | private BinDiffHelperPlugin plugin; 525 | 526 | public SettingsDialogAction(BinDiffHelperPlugin plugin) { 527 | super("Settings", plugin.getName()); 528 | 529 | this.plugin = plugin; 530 | 531 | this.setMenuBarData(new MenuData(new String[] { "Settings" }, "Settings")); 532 | 533 | setToolBarData(new ToolBarData(ResourceManager.loadImage("images/setting_tools.png"), "Settings")); 534 | 535 | setDescription(HTMLUtilities.toHTML("Settings")); 536 | 537 | } 538 | 539 | @Override 540 | public void actionPerformed(ActionContext context) { 541 | DockingWindowManager.showDialog(new SettingsDialog(plugin)); 542 | } 543 | } 544 | 545 | public class BinDiffFileDescriptor { 546 | private String filename, exefilename, hash; 547 | 548 | public BinDiffFileDescriptor(String filename, String exefilename, String hash) { 549 | this.filename = filename; 550 | this.exefilename = exefilename; 551 | this.hash = hash; 552 | } 553 | 554 | public String getFilename() { 555 | return filename; 556 | } 557 | 558 | public String getExeFilename() { 559 | return exefilename; 560 | } 561 | 562 | public String getHash() { 563 | return hash; 564 | } 565 | } 566 | } 567 | -------------------------------------------------------------------------------- /src/main/java/bindiffhelper/BinExport2File.java: -------------------------------------------------------------------------------- 1 | package bindiffhelper; 2 | 3 | import java.io.File; 4 | import java.io.FileInputStream; 5 | import java.util.HashMap; 6 | import java.util.Set; 7 | 8 | import com.google.security.zynamics.BinExport.BinExport2; 9 | import com.google.security.zynamics.BinExport.BinExport2.CallGraph; 10 | import com.google.security.zynamics.BinExport.BinExport2.CallGraph.Vertex; 11 | 12 | import ghidra.program.model.address.Address; 13 | 14 | public class BinExport2File { 15 | 16 | protected BinExport2 be; 17 | protected HashMap functionNames; 18 | 19 | public BinExport2File(File f) throws Exception { 20 | FileInputStream fi = new FileInputStream(f); 21 | be = BinExport2.parseFrom(fi); 22 | fi.close(); 23 | 24 | functionNames = new HashMap(); 25 | CallGraph cg = be.getCallGraph(); 26 | for (Vertex v : cg.getVertexList()) { 27 | functionNames.put(v.getAddress(), v.getMangledName()); 28 | } 29 | } 30 | 31 | public Set getFunctionAddressSet() { 32 | return functionNames.keySet(); 33 | } 34 | 35 | public boolean hasFunctionName(long address) { 36 | return functionNames.containsKey(address); 37 | } 38 | 39 | public String getFunctionName(Address address) { 40 | return getFunctionName(address.getUnsignedOffset()); 41 | } 42 | 43 | public String getFunctionName(long address) { 44 | return functionNames.get(address); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/bindiffhelper/ComparisonTableModel.java: -------------------------------------------------------------------------------- 1 | package bindiffhelper; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import javax.swing.table.AbstractTableModel; 7 | 8 | import ghidra.program.model.address.Address; 9 | import ghidra.program.model.symbol.Symbol; 10 | 11 | public class ComparisonTableModel extends AbstractTableModel { 12 | /** 13 | * 14 | */ 15 | private static final long serialVersionUID = 1L; 16 | 17 | private String[] columnNames = { "Import", "Address this file", "Name this file", "Name Database", 18 | "Address other file", "Name other file", "Similarity", "Confidence", "Algorithm" }; 19 | 20 | private List data; 21 | 22 | private boolean showNamespace = false; 23 | 24 | public ComparisonTableModel(boolean showNamespace) { 25 | data = new ArrayList(); 26 | this.showNamespace = showNamespace; 27 | } 28 | 29 | public void addEntry(Entry e) { 30 | data.add(e); 31 | } 32 | 33 | public List getEntries() { 34 | return data; 35 | } 36 | 37 | public int getColumnCount() { 38 | return columnNames.length; 39 | } 40 | 41 | public int getRowCount() { 42 | return data.size(); 43 | } 44 | 45 | public String getColumnName(int col) { 46 | return columnNames[col]; 47 | } 48 | 49 | public Object getValueAt(int row, int col) { 50 | Entry e = data.get(row); 51 | 52 | switch (col) { 53 | case 0: 54 | return e.do_import; 55 | case 1: 56 | if (e.primaryAddress != null) 57 | return "0x" + Long.toHexString(e.primaryAddress.getUnsignedOffset()); 58 | return ""; 59 | case 2: 60 | if (e.primaryFunctionSymbol != null){ 61 | if (showNamespace) { 62 | var functionNameComponents = new ArrayList(); 63 | var parentNamespace = e.primaryFunctionSymbol.getParentNamespace(); 64 | while (parentNamespace != null && !"Global".equals(parentNamespace.getName())) { 65 | functionNameComponents.add(0, parentNamespace.getName()); 66 | parentNamespace = parentNamespace.getParentNamespace(); 67 | } 68 | // Add the name of the function as the last component. 69 | functionNameComponents.add(e.primaryFunctionSymbol.getName()); 70 | return String.join("::", functionNameComponents); 71 | } else { 72 | return e.primaryFunctionSymbol.getName(); 73 | } 74 | } 75 | return "No Symbol"; 76 | case 3: 77 | if (e.primaryFunctionNameDb != null) 78 | return e.primaryFunctionNameDb; 79 | return ""; 80 | case 4: 81 | return "0x" + Long.toHexString(e.secondaryAddress); 82 | case 5: 83 | if (e.secondaryFunctionName != null) 84 | return e.secondaryFunctionName; 85 | return ""; 86 | case 6: 87 | return e.similarity; 88 | case 7: 89 | return e.confidence; 90 | case 8: 91 | return e.algorithm; 92 | } 93 | 94 | return null; 95 | } 96 | 97 | @Override 98 | public boolean isCellEditable(int row, int col) { 99 | if (col == 0) 100 | return true; 101 | return false; 102 | } 103 | 104 | @Override 105 | public Class getColumnClass(int columnIndex) { 106 | return switch (columnIndex) { 107 | case 0 -> Boolean.class; // Checkbox column 108 | case 1 -> String.class; // Address this file (hex string) 109 | case 2 -> String.class; // Name this file 110 | case 3 -> String.class; // Name Database 111 | case 4 -> String.class; // Address other file (hex string) 112 | case 5 -> String.class; // Name other file 113 | case 6 -> Double.class; // Similarity (numeric) 114 | case 7 -> Double.class; // Confidence (numeric) 115 | case 8 -> String.class; // Algorithm 116 | default -> Object.class; 117 | }; 118 | } 119 | 120 | public void setValueAt(Object value, int row, int col) { 121 | if (col != 0) 122 | return; 123 | 124 | data.get(row).do_import = (boolean) value; 125 | 126 | fireTableCellUpdated(row, col); 127 | } 128 | 129 | public Entry getEntry(int row) { 130 | return data.get(row); 131 | } 132 | 133 | public static class Entry { 134 | boolean do_import; 135 | final Address primaryAddress; 136 | final String primaryFunctionNameDb; 137 | final Symbol primaryFunctionSymbol; 138 | final long secondaryAddress; 139 | final String secondaryFunctionName; 140 | final double similarity; 141 | final double confidence; 142 | final String algorithm; 143 | 144 | public Entry(boolean i, Address pa, String pfn, Symbol pfs, long sa, String sfn, double sim, double con, 145 | String alg) { 146 | do_import = i; 147 | primaryAddress = pa; 148 | primaryFunctionNameDb = pfn; 149 | primaryFunctionSymbol = pfs; 150 | secondaryAddress = sa; 151 | secondaryFunctionName = sfn; 152 | similarity = sim; 153 | confidence = con; 154 | algorithm = alg; 155 | } 156 | 157 | public boolean symbolRenamed() { 158 | if (primaryFunctionSymbol == null) 159 | return true; 160 | 161 | return !primaryFunctionNameDb.equals(primaryFunctionSymbol.getName()); 162 | } 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /src/main/java/bindiffhelper/DiffWizard.java: -------------------------------------------------------------------------------- 1 | package bindiffhelper; 2 | 3 | import java.awt.BorderLayout; 4 | import java.awt.Color; 5 | import java.awt.Component; 6 | import java.awt.Dimension; 7 | import java.awt.GridLayout; 8 | import java.awt.event.ActionEvent; 9 | import java.awt.event.ActionListener; 10 | import java.util.Collections; 11 | 12 | import javax.swing.BorderFactory; 13 | import javax.swing.Box; 14 | import javax.swing.BoxLayout; 15 | import javax.swing.ButtonGroup; 16 | import javax.swing.JButton; 17 | import javax.swing.JCheckBox; 18 | import javax.swing.JLabel; 19 | import javax.swing.JPanel; 20 | import javax.swing.JRadioButton; 21 | 22 | import bindiffhelper.BinDiffHelperProvider.DiffState; 23 | import docking.Tool; 24 | import docking.widgets.filechooser.GhidraFileChooserPanel; 25 | import docking.widgets.tree.support.GTreeSelectionEvent; 26 | import docking.widgets.tree.support.GTreeSelectionListener; 27 | import docking.wizard.AbstractWizardJPanel; 28 | import docking.wizard.IllegalPanelStateException; 29 | import docking.wizard.PanelManager; 30 | import docking.wizard.WizardManager; 31 | import docking.wizard.WizardPanel; 32 | import ghidra.app.services.CodeViewerService; 33 | import ghidra.framework.main.AppInfo; 34 | import ghidra.framework.main.datatree.ProjectDataTreePanel; 35 | import ghidra.framework.model.DomainFile; 36 | import ghidra.framework.model.DomainObject; 37 | import ghidra.program.model.listing.Program; 38 | import ghidra.util.Msg; 39 | import ghidra.util.task.TaskMonitor; 40 | 41 | class DiffKindPanel extends AbstractWizardJPanel { 42 | private JRadioButton rbFromProject, rbFromExternal; 43 | private GhidraFileChooserPanel extBDFilePanel; 44 | 45 | private BinDiffHelperPlugin plugin; 46 | 47 | private boolean loadedFromProject = false; 48 | 49 | DiffKindPanel(BinDiffHelperPlugin plugin) { 50 | super(new GridLayout(2, 1, 10, 10)); 51 | // setBorder(NewProjectPanelManager.EMPTY_BORDER); 52 | 53 | this.plugin = plugin; 54 | ButtonGroup bg = new ButtonGroup(); 55 | 56 | rbFromProject = new JRadioButton("Diff with other file from ghidra project (select in next panel)"); 57 | bg.add(rbFromProject); 58 | 59 | JPanel fromProjectPanel = new JPanel(); 60 | fromProjectPanel.setLayout(new BoxLayout(fromProjectPanel, BoxLayout.Y_AXIS)); 61 | fromProjectPanel.add(rbFromProject); 62 | fromProjectPanel.setBorder(BorderFactory.createLineBorder(Color.BLACK)); 63 | 64 | if (!plugin.provider.hasExporter || plugin.binDiffBinary == null) { 65 | rbFromProject.setEnabled(false); 66 | JLabel lab = new JLabel("BinExport2 plugin or BinDiff binary not found - this feature is disabled."); 67 | fromProjectPanel.add(lab); 68 | } 69 | add(fromProjectPanel); 70 | 71 | rbFromExternal = new JRadioButton("Use externally created .BinDiff file"); 72 | bg.add(rbFromExternal); 73 | 74 | JPanel extPanel = new JPanel(); 75 | extPanel.setLayout(new BoxLayout(extPanel, BoxLayout.Y_AXIS)); 76 | extPanel.add(rbFromExternal); 77 | extPanel.setBorder(BorderFactory.createLineBorder(Color.BLACK)); 78 | 79 | extBDFilePanel = new GhidraFileChooserPanel("", "de.ubfx.bindiffhelper.extbdfile", "", true, 80 | GhidraFileChooserPanel.INPUT_MODE); 81 | extBDFilePanel.setBorder(BorderFactory.createEmptyBorder()); 82 | extBDFilePanel.setAlignmentX(Component.LEFT_ALIGNMENT); 83 | 84 | extPanel.add(extBDFilePanel); 85 | 86 | JButton extLoadBtn = new JButton("Load"); 87 | extLoadBtn.addActionListener(e -> loadCallback()); 88 | extPanel.add(extLoadBtn); 89 | add(extPanel); 90 | 91 | if (rbFromProject.isEnabled()) 92 | rbFromProject.setSelected(true); 93 | else 94 | rbFromExternal.setSelected(true); 95 | 96 | ActionListener validityListener = new ActionListener() { 97 | @Override 98 | public void actionPerformed(ActionEvent e) { 99 | notifyListenersOfValidityChanged(); 100 | } 101 | }; 102 | 103 | rbFromProject.addActionListener(validityListener); 104 | rbFromExternal.addActionListener(validityListener); 105 | } 106 | 107 | protected void loadCallback() { 108 | try { 109 | plugin.provider.openExternalDB(extBDFilePanel.getFileName()); 110 | rbFromExternal.setSelected(true); 111 | loadedFromProject = true; 112 | } catch (Exception e) { 113 | e.printStackTrace(); 114 | loadedFromProject = false; 115 | } 116 | notifyListenersOfValidityChanged(); 117 | } 118 | 119 | @Override 120 | public String getTitle() { 121 | return "Choose diffing method"; 122 | } 123 | 124 | @Override 125 | public boolean isValidInformation() { 126 | if (rbFromProject.isSelected()) 127 | return true; 128 | 129 | return loadedFromProject; 130 | } 131 | 132 | @Override 133 | public void initialize() { 134 | } 135 | 136 | public boolean isFromProject() { 137 | return rbFromProject.isSelected(); 138 | } 139 | } 140 | 141 | class MatchPanel extends AbstractWizardJPanel { 142 | private JRadioButton rb0; 143 | private JRadioButton rb1; 144 | private BinDiffHelperPlugin plugin; 145 | 146 | private String buildTable(String fn, String fnColor, String efn, String efnColor, String hash, String hashColor) { 147 | return "" + "" 148 | + "" 149 | + "" + "
Filename" + fn + "
Binary Filename" + efn + "
SHA256" + hash + "
"; 150 | } 151 | 152 | MatchPanel(BinDiffHelperPlugin plugin) { 153 | super(); 154 | 155 | this.plugin = plugin; 156 | 157 | JLabel instructions = new JLabel("

" 158 | + "The external BinDiff database has been loaded. " 159 | + "Below, you need to select which of the files in the database matches the file loaded in Ghidra.
" 160 | + "The other file will be used to import function names from.



"); 161 | 162 | JPanel work = new JPanel(new BorderLayout()); 163 | work.add(instructions, BorderLayout.CENTER); 164 | 165 | JPanel panel = new JPanel(new BorderLayout()); 166 | 167 | JPanel ref = new JPanel(); 168 | JPanel bd1 = new JPanel(new BorderLayout()); 169 | JPanel bd2 = new JPanel(new BorderLayout()); 170 | 171 | rb0 = new JRadioButton("This file matches the loaded file"); 172 | rb1 = new JRadioButton("This file matches the loaded file"); 173 | 174 | ButtonGroup bg = new ButtonGroup(); 175 | bg.add(rb0); 176 | bg.add(rb1); 177 | 178 | bd1.add(rb0, BorderLayout.PAGE_START); 179 | bd2.add(rb1, BorderLayout.PAGE_START); 180 | 181 | ref.setBorder(BorderFactory.createTitledBorder("File loaded in Ghidra")); 182 | bd1.setBorder(BorderFactory.createTitledBorder("First file in BinDiff database")); 183 | bd2.setBorder(BorderFactory.createTitledBorder("Second file in BinDiff database")); 184 | 185 | panel.add(ref, BorderLayout.PAGE_START); 186 | panel.add(bd1, BorderLayout.LINE_START); 187 | panel.add(bd2, BorderLayout.LINE_END); 188 | 189 | Program program = plugin.provider.program; 190 | 191 | String hashRef = program.getExecutableSHA256(); 192 | String fnRef = program.getDomainFile().getName().toString(); 193 | String efnRef = program.getName(); 194 | 195 | String fnCol = fnRef.equalsIgnoreCase(plugin.provider.primary.filename) ? "green" : "red"; 196 | String efnCol = efnRef.equalsIgnoreCase(plugin.provider.primary.exefilename) ? "green" : "red"; 197 | String hashCol = hashRef.equalsIgnoreCase(plugin.provider.primary.hash) ? "green" : "red"; 198 | 199 | ref.add(new JLabel(buildTable(fnRef, "black", efnRef, "black", hashRef, "black"))); 200 | bd1.add(new JLabel(buildTable(plugin.provider.primary.filename, fnCol, plugin.provider.primary.exefilename, 201 | efnCol, plugin.provider.primary.hash, hashCol))); 202 | 203 | fnCol = fnRef.equalsIgnoreCase(plugin.provider.secondary.filename) ? "green" : "red"; 204 | efnCol = efnRef.equalsIgnoreCase(plugin.provider.secondary.exefilename) ? "green" : "red"; 205 | hashCol = hashRef.equalsIgnoreCase(plugin.provider.secondary.hash) ? "green" : "red"; 206 | 207 | bd2.add(new JLabel(buildTable(plugin.provider.secondary.filename, fnCol, plugin.provider.secondary.exefilename, 208 | efnCol, plugin.provider.secondary.hash, hashCol))); 209 | 210 | work.add(panel, BorderLayout.PAGE_END); 211 | 212 | add(work); 213 | 214 | ActionListener validityListener = new ActionListener() { 215 | @Override 216 | public void actionPerformed(ActionEvent e) { 217 | notifyListenersOfValidityChanged(); 218 | } 219 | }; 220 | 221 | rb0.addActionListener(validityListener); 222 | rb1.addActionListener(validityListener); 223 | } 224 | 225 | @Override 226 | public String getTitle() { 227 | return "Match the loaded file to the correct BinDiff file"; 228 | } 229 | 230 | @Override 231 | public boolean isValidInformation() { 232 | return rb0.isSelected() || rb1.isSelected(); 233 | } 234 | 235 | @Override 236 | public void initialize() { 237 | // TODO Auto-generated method stub 238 | } 239 | 240 | public boolean isSwapped() { 241 | return rb1.isSelected(); 242 | } 243 | 244 | } 245 | 246 | class FromProjectPanel extends AbstractWizardJPanel { 247 | private BinDiffHelperPlugin plugin; 248 | private ProjectDataTreePanel tp; 249 | 250 | FromProjectPanel(BinDiffHelperPlugin plugin) { 251 | this.plugin = plugin; 252 | 253 | JPanel panel = new JPanel(); 254 | panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS)); 255 | 256 | JPanel projectPanel; 257 | if (plugin.provider.hasExporter && plugin.binDiffBinary != null) { 258 | tp = new ProjectDataTreePanel(null); 259 | tp.setProjectData(AppInfo.getActiveProject().getName(), AppInfo.getActiveProject().getProjectData()); 260 | 261 | tp.setPreferredSize(new Dimension(400, 300)); 262 | 263 | projectPanel = tp; 264 | tp.addTreeSelectionListener(new GTreeSelectionListener() { 265 | @Override 266 | public void valueChanged(GTreeSelectionEvent e) { 267 | notifyListenersOfValidityChanged(); 268 | } 269 | }); 270 | } else { 271 | projectPanel = new JPanel(); 272 | projectPanel.add(new JLabel("

BinDiff binary not selected or BinExport " 273 | + "plugin not detected so this feature is not available. Check the settings menu.

")); 274 | } 275 | 276 | projectPanel.setBorder(BorderFactory.createTitledBorder("Diff with another file from Ghidra project")); 277 | projectPanel.setAlignmentX(Component.LEFT_ALIGNMENT); 278 | panel.add(projectPanel); 279 | 280 | panel.add(Box.createRigidArea(new Dimension(0, 20))); 281 | add(panel); 282 | } 283 | 284 | @Override 285 | public String getTitle() { 286 | return "Select a file that will be diffed to the file which you have currently opened in the Code Explorer"; 287 | } 288 | 289 | @Override 290 | public boolean isValidInformation() { 291 | if (tp == null || tp.getSelectedItemCount() != 1) 292 | return false; 293 | 294 | if (tp.getSelectedDomainFolder() != null) 295 | return false; 296 | 297 | var df = tp.getSelectedDomainFile(); 298 | return df != null; 299 | } 300 | 301 | @Override 302 | public void initialize() { 303 | } 304 | 305 | public DomainFile getDf() { 306 | return tp.getSelectedDomainFile(); 307 | } 308 | } 309 | 310 | class Program2Panel extends AbstractWizardJPanel { 311 | private BinDiffHelperPlugin plugin; 312 | private ProjectDataTreePanel tp; 313 | private JCheckBox cb; 314 | 315 | private Program program; 316 | private CodeViewerService cvs; 317 | 318 | Program2Panel(BinDiffHelperPlugin plugin) { 319 | this.plugin = plugin; 320 | setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); 321 | 322 | cb = new JCheckBox("Attach another ghidra file to the secondary diff file"); 323 | 324 | tp = new ProjectDataTreePanel(null); 325 | tp.setProjectData(AppInfo.getActiveProject().getName(), AppInfo.getActiveProject().getProjectData()); 326 | 327 | tp.setPreferredSize(new Dimension(400, 300)); 328 | 329 | tp.addTreeSelectionListener(new GTreeSelectionListener() { 330 | @Override 331 | public void valueChanged(GTreeSelectionEvent e) { 332 | notifyListenersOfValidityChanged(); 333 | } 334 | }); 335 | 336 | add(cb); 337 | add(tp); 338 | cb.addActionListener(new ActionListener() { 339 | @Override 340 | public void actionPerformed(ActionEvent e) { 341 | notifyListenersOfValidityChanged(); 342 | } 343 | }); 344 | } 345 | 346 | @Override 347 | public String getTitle() { 348 | return "Optionally attach another ghidra file to the secondary diff file"; 349 | } 350 | 351 | @Override 352 | public boolean isValidInformation() { 353 | if (!cb.isSelected()) 354 | return true; 355 | 356 | if (tp == null || tp.getSelectedItemCount() != 1) 357 | return false; 358 | 359 | if (tp.getSelectedDomainFolder() != null) 360 | return false; 361 | 362 | var df = tp.getSelectedDomainFile(); 363 | return df != null; 364 | } 365 | 366 | @Override 367 | public void initialize() { 368 | } 369 | 370 | public void open() { 371 | try { 372 | DomainFile df = tp.getSelectedDomainFile(); 373 | Tool newTool = plugin.getTool().getToolServices().launchDefaultTool(Collections.singletonList(df)); 374 | DomainObject domainObject = df.getDomainObject(this, true, false, TaskMonitor.DUMMY); 375 | program = (Program) domainObject; 376 | cvs = newTool.getService(CodeViewerService.class); 377 | } catch (Exception e) { 378 | Msg.showError(this, this, "Error", "Failed to open the program in new window: " + e.getMessage()); 379 | } 380 | } 381 | 382 | public boolean useProgram2() { 383 | return cb.isSelected(); 384 | } 385 | 386 | public CodeViewerService getCvs() { 387 | return cvs; 388 | } 389 | 390 | public Program getProg() { 391 | return program; 392 | } 393 | 394 | public DomainFile getDf() { 395 | return tp.getSelectedDomainFile(); 396 | } 397 | } 398 | 399 | class DiffPanelManager implements PanelManager { 400 | private WizardManager wizardMgr; 401 | private DiffKindPanel dkPanel; 402 | private MatchPanel matchPanel; 403 | private FromProjectPanel fromProjectPanel; 404 | private Program2Panel program2Panel; 405 | 406 | private BinDiffHelperPlugin plugin; 407 | 408 | DiffPanelManager(BinDiffHelperPlugin plugin) { 409 | dkPanel = new DiffKindPanel(plugin); 410 | this.plugin = plugin; 411 | } 412 | 413 | @Override 414 | public boolean canFinish() { 415 | WizardPanel curPanel = wizardMgr.getCurrentWizardPanel(); 416 | if (curPanel == null) 417 | return false; 418 | if (curPanel == fromProjectPanel) 419 | return true; 420 | if (curPanel == program2Panel) 421 | return true; 422 | return false; 423 | } 424 | 425 | @Override 426 | public boolean hasNextPanel() { 427 | WizardPanel curPanel = wizardMgr.getCurrentWizardPanel(); 428 | if (curPanel == null) 429 | return true; 430 | if (curPanel == fromProjectPanel) 431 | return false; 432 | if (curPanel == matchPanel) 433 | return true; 434 | if (curPanel == dkPanel) 435 | return true; 436 | if (curPanel == program2Panel) 437 | return false; 438 | return false; 439 | } 440 | 441 | @Override 442 | public boolean hasPreviousPanel() { 443 | WizardPanel curPanel = wizardMgr.getCurrentWizardPanel(); 444 | if (curPanel == null) 445 | return false; 446 | if (curPanel == fromProjectPanel) 447 | return true; 448 | if (curPanel == matchPanel) 449 | return true; 450 | if (curPanel == dkPanel) 451 | return false; 452 | if (curPanel == program2Panel) 453 | return true; 454 | return false; 455 | } 456 | 457 | @Override 458 | public WizardPanel getNextPanel() throws IllegalPanelStateException { 459 | 460 | if (wizardMgr.getCurrentWizardPanel() == null) 461 | return dkPanel; 462 | 463 | if (wizardMgr.getCurrentWizardPanel() == dkPanel) { 464 | if (dkPanel.isFromProject()) { 465 | fromProjectPanel = new FromProjectPanel(plugin); 466 | return fromProjectPanel; 467 | } else { 468 | matchPanel = new MatchPanel(plugin); 469 | return matchPanel; 470 | } 471 | } 472 | 473 | if (wizardMgr.getCurrentWizardPanel() == matchPanel) { 474 | program2Panel = new Program2Panel(plugin); 475 | return program2Panel; 476 | } 477 | 478 | return null; 479 | } 480 | 481 | @Override 482 | public WizardPanel getInitialPanel() throws IllegalPanelStateException { 483 | return dkPanel; 484 | } 485 | 486 | @Override 487 | public WizardPanel getPreviousPanel() throws IllegalPanelStateException { 488 | WizardPanel curPanel = wizardMgr.getCurrentWizardPanel(); 489 | if (curPanel == null) 490 | return null; 491 | if (curPanel == fromProjectPanel) 492 | return dkPanel; 493 | if (curPanel == matchPanel) 494 | return dkPanel; 495 | if (curPanel == dkPanel) 496 | return null; 497 | if (curPanel == program2Panel) 498 | return matchPanel; 499 | return null; 500 | } 501 | 502 | @Override 503 | public String getStatusMessage() { 504 | // TODO Auto-generated method stub 505 | return null; 506 | } 507 | 508 | @Override 509 | public void finish() throws IllegalPanelStateException { 510 | WizardPanel curPanel = wizardMgr.getCurrentWizardPanel(); 511 | if (curPanel == program2Panel) { 512 | if (matchPanel.isSwapped()) { 513 | DiffState temp = plugin.provider.secondary; 514 | plugin.provider.secondary = plugin.provider.primary; 515 | plugin.provider.primary = temp; 516 | } 517 | 518 | if (program2Panel.useProgram2()) 519 | program2Panel.open(); 520 | 521 | plugin.provider.secondary.cvs = program2Panel.getCvs(); 522 | plugin.provider.secondary.prog = program2Panel.getProg(); 523 | plugin.provider.secondary.df = program2Panel.getDf(); 524 | plugin.provider.doDiffWork(); 525 | wizardMgr.close(); 526 | } else { 527 | // from project 528 | DomainFile df = fromProjectPanel.getDf(); 529 | plugin.callBinDiff(df, files -> { 530 | if (files != null) { 531 | try { 532 | plugin.provider.openExternalDBWithBinExports(files[2].getAbsolutePath(), files[0], files[1]); 533 | } catch (Exception e) { 534 | e.printStackTrace(); 535 | } 536 | plugin.provider.secondary.df = df; 537 | try { 538 | var dof = df.getReadOnlyDomainObject(plugin, DomainFile.DEFAULT_VERSION, TaskMonitor.DUMMY); 539 | if (dof instanceof Program p) 540 | plugin.provider.secondary.prog = p; 541 | } catch (Exception e) { 542 | 543 | } 544 | plugin.provider.doDiffWork(); 545 | wizardMgr.close(); 546 | } 547 | }); 548 | } 549 | } 550 | 551 | @Override 552 | public void cancel() { 553 | // TODO Auto-generated method stub 554 | } 555 | 556 | @Override 557 | public void initialize() { 558 | // TODO Auto-generated method stub 559 | } 560 | 561 | @Override 562 | public Dimension getPanelSize() { 563 | return new Dimension(1000, 400); 564 | } 565 | 566 | @Override 567 | public void setWizardManager(WizardManager wm) { 568 | wizardMgr = wm; 569 | } 570 | 571 | @Override 572 | public WizardManager getWizardManager() { 573 | return wizardMgr; 574 | } 575 | } 576 | -------------------------------------------------------------------------------- /src/main/java/bindiffhelper/ImportFunctionNamesAction.java: -------------------------------------------------------------------------------- 1 | package bindiffhelper; 2 | 3 | import java.util.HashMap; 4 | import java.util.List; 5 | import java.util.Map; 6 | 7 | import docking.ActionContext; 8 | import docking.action.DockingAction; 9 | import docking.action.MenuData; 10 | import docking.action.ToolBarData; 11 | import ghidra.program.model.listing.Program; 12 | import ghidra.program.model.symbol.Namespace; 13 | import ghidra.program.model.symbol.SourceType; 14 | import ghidra.program.model.symbol.SymbolTable; 15 | import ghidra.util.HTMLUtilities; 16 | import ghidra.util.Msg; 17 | import resources.ResourceManager; 18 | 19 | class ImportFunctionNamesAction extends DockingAction { 20 | 21 | protected BinDiffHelperPlugin plugin; 22 | 23 | List entries; 24 | 25 | public ImportFunctionNamesAction(String name, BinDiffHelperPlugin plugin) { 26 | super(name, plugin.getName()); 27 | this.plugin = plugin; 28 | } 29 | 30 | public void setEntries(List e) { 31 | entries = e; 32 | } 33 | 34 | protected boolean shouldImportEntry(ComparisonTableModel.Entry e) { 35 | return false; 36 | } 37 | 38 | private Namespace getOrCreateNamespace(Program program, String namespacePath) throws Exception { 39 | 40 | SymbolTable symbolTable = program.getSymbolTable(); 41 | Namespace currentNamespace = program.getGlobalNamespace(); 42 | 43 | if (namespacePath == null || namespacePath.isEmpty()) { 44 | return currentNamespace; 45 | } 46 | 47 | String[] namespaces = namespacePath.split("::"); 48 | for (String namespaceName : namespaces) { 49 | ghidra.program.model.symbol.Namespace nextNamespace = symbolTable.getNamespace( 50 | namespaceName, currentNamespace); 51 | 52 | if (nextNamespace == null) { 53 | nextNamespace = symbolTable.createNameSpace( 54 | currentNamespace, namespaceName, SourceType.IMPORTED); 55 | } 56 | 57 | currentNamespace = nextNamespace; 58 | } 59 | 60 | return currentNamespace; 61 | } 62 | 63 | @Override 64 | public void actionPerformed(ActionContext arg0) { 65 | int trans = plugin.program.startTransaction("Rename functions"); 66 | 67 | Map changes = new HashMap(); 68 | 69 | for (var e : entries) { 70 | if (!shouldImportEntry(e)) 71 | continue; 72 | 73 | String transformation = "(none)"; 74 | Exception exp = null; 75 | try { 76 | transformation = e.primaryFunctionSymbol.getName() + " -> " + e.secondaryFunctionName; 77 | 78 | if (e.secondaryFunctionName.contains("::")) { 79 | String[] parts = e.secondaryFunctionName.split("::"); 80 | String methodName = parts[parts.length - 1]; 81 | 82 | StringBuilder namespacePath = new StringBuilder(); 83 | for (int i = 0; i < parts.length - 1; i++) { 84 | if (i > 0) namespacePath.append("::"); 85 | namespacePath.append(parts[i]); 86 | } 87 | 88 | Namespace namespace = getOrCreateNamespace(plugin.program, namespacePath.toString()); 89 | 90 | e.primaryFunctionSymbol.setNameAndNamespace(methodName, namespace, SourceType.IMPORTED); 91 | } else { 92 | e.primaryFunctionSymbol.setName(e.secondaryFunctionName, SourceType.IMPORTED); 93 | } 94 | 95 | e.do_import = false; 96 | } catch (Exception ex) { 97 | exp = ex; 98 | } 99 | 100 | changes.put(transformation, exp); 101 | } 102 | 103 | plugin.program.endTransaction(trans, true); 104 | 105 | String html = "
    "; 106 | 107 | for (var c : changes.entrySet()) { 108 | boolean wasSuccess = c.getValue() == null; 109 | 110 | String color = wasSuccess ? "green" : "red"; 111 | 112 | html += "
  • " + c.getKey(); 113 | 114 | if (!wasSuccess) { 115 | html += " unsuccessful (Exception: " + c.getValue().toString() + ")"; 116 | } 117 | 118 | html += "
  • "; 119 | } 120 | 121 | html += "
"; 122 | 123 | plugin.provider.refresh(); 124 | Msg.showInfo(this, plugin.provider.getComponent(), "Renamed functions", html); 125 | } 126 | } 127 | 128 | class ImportCheckedFunctionNamesAction extends ImportFunctionNamesAction { 129 | public ImportCheckedFunctionNamesAction(BinDiffHelperPlugin plugin) { 130 | super("Import selected function names", plugin); 131 | 132 | this.setMenuBarData(new MenuData(new String[] { "Import", "Checked functions' names" }, "Import")); 133 | 134 | setToolBarData(new ToolBarData(ResourceManager.loadImage("images/table_go.png"), "Import")); 135 | 136 | setDescription(HTMLUtilities.toHTML("Import checked functions' names")); 137 | } 138 | 139 | @Override 140 | protected boolean shouldImportEntry(ComparisonTableModel.Entry e) { 141 | return e.do_import; 142 | } 143 | } 144 | 145 | class ImportAllFunctionNamesAction extends ImportFunctionNamesAction { 146 | public ImportAllFunctionNamesAction(BinDiffHelperPlugin plugin) { 147 | super("Import all function names", plugin); 148 | 149 | this.setMenuBarData(new MenuData(new String[] { "Import", "All functions' names" }, "Import")); 150 | 151 | setToolBarData(new ToolBarData(ResourceManager.loadImage("images/table_lightning.png"), "Import")); 152 | 153 | setDescription(HTMLUtilities.toHTML("Import all function names")); 154 | } 155 | 156 | @Override 157 | protected boolean shouldImportEntry(ComparisonTableModel.Entry e) { 158 | return true; 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /src/main/java/bindiffhelper/SettingsDialog.java: -------------------------------------------------------------------------------- 1 | package bindiffhelper; 2 | 3 | import java.awt.BorderLayout; 4 | import java.io.IOException; 5 | 6 | import javax.swing.*; 7 | 8 | import docking.DialogComponentProvider; 9 | import docking.widgets.filechooser.GhidraFileChooserPanel; 10 | import ghidra.util.Msg; 11 | 12 | public class SettingsDialog extends DialogComponentProvider { 13 | 14 | protected BinDiffHelperPlugin plugin; 15 | protected GhidraFileChooserPanel fileChooserPanel; 16 | private JTextField customTextField; 17 | private JCheckBox enableNamespaceCheckBox; 18 | 19 | public SettingsDialog(BinDiffHelperPlugin plugin) { 20 | super("Settings"); 21 | 22 | this.plugin = plugin; 23 | 24 | JPanel panel = new JPanel(new BorderLayout()); 25 | panel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20)); 26 | 27 | JLabel fileChooserLabel = new JLabel("Select the BinDiff 6/7/8 binary"); 28 | panel.add(fileChooserLabel, BorderLayout.NORTH); 29 | 30 | fileChooserPanel = new GhidraFileChooserPanel("BinDiff", BinDiffHelperPlugin.BDBINPROPERTY, 31 | plugin.binDiffBinary, true, GhidraFileChooserPanel.INPUT_MODE); 32 | fileChooserPanel.setVisible(true); 33 | 34 | fileChooserPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); 35 | panel.add(fileChooserPanel, BorderLayout.CENTER); 36 | 37 | JPanel diffCommandPanel = new JPanel(new BorderLayout()); 38 | diffCommandPanel.setBorder(BorderFactory.createEmptyBorder()); 39 | 40 | JLabel diffCommandLabel = new JLabel("Custom diff command:"); 41 | diffCommandPanel.add(diffCommandLabel, BorderLayout.NORTH); 42 | 43 | JPanel diffCommandTextField = new JPanel(new BorderLayout()); 44 | diffCommandTextField.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); 45 | 46 | customTextField = new JTextField(); 47 | customTextField.setText(plugin.diffCommand); 48 | diffCommandTextField.add(customTextField, BorderLayout.CENTER); 49 | 50 | diffCommandPanel.add(diffCommandTextField, BorderLayout.CENTER); 51 | 52 | JPanel namespacePanel = new JPanel(new BorderLayout()); 53 | namespacePanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); 54 | enableNamespaceCheckBox = new JCheckBox("Enable export Namespace"); 55 | enableNamespaceCheckBox.setSelected(plugin.enableNamespace); 56 | namespacePanel.add(enableNamespaceCheckBox, BorderLayout.CENTER); 57 | 58 | JPanel southContainer = new JPanel(); 59 | southContainer.setLayout(new BoxLayout(southContainer, BoxLayout.Y_AXIS)); 60 | southContainer.add(diffCommandPanel); 61 | southContainer.add(namespacePanel); 62 | panel.add(southContainer, BorderLayout.SOUTH); 63 | 64 | addWorkPanel(panel); 65 | 66 | okButton = new JButton("OK"); 67 | okButton.addActionListener(e -> okCallback()); 68 | addButton(okButton); 69 | 70 | cancelButton = new JButton("Cancel"); 71 | cancelButton.setName("Cancel"); 72 | cancelButton.addActionListener(e -> cancelCallback()); 73 | addButton(cancelButton); 74 | } 75 | 76 | @Override 77 | protected void okCallback() { 78 | close(); 79 | 80 | try { 81 | plugin.updateBinDiffBinary(); 82 | } catch (IOException e) { 83 | Msg.showError(this, getComponent(), "Error", e.toString()); 84 | } 85 | 86 | plugin.updateDiffCommand(customTextField.getText()); 87 | plugin.updateEnableNamespace(enableNamespaceCheckBox.isSelected()); 88 | plugin.provider.generateWarnings(); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/main/java/bindiffhelper/ToggleCheckSelectedAction.java: -------------------------------------------------------------------------------- 1 | package bindiffhelper; 2 | 3 | import docking.ActionContext; 4 | import docking.action.DockingAction; 5 | import docking.action.MenuData; 6 | import docking.action.ToolBarData; 7 | import ghidra.util.HTMLUtilities; 8 | import resources.ResourceManager; 9 | 10 | public class ToggleCheckSelectedAction extends DockingAction { 11 | 12 | protected BinDiffHelperPlugin plugin; 13 | 14 | public ToggleCheckSelectedAction(BinDiffHelperPlugin plugin) { 15 | super("Toggle selected functions", plugin.getName()); 16 | 17 | this.setMenuBarData(new MenuData(new String[] { "Toggle selected functions" })); 18 | 19 | setToolBarData(new ToolBarData(ResourceManager.loadImage("images/check_box.png"), "Import")); 20 | 21 | setDescription(HTMLUtilities.toHTML("Toggle the selected functions in the table")); 22 | 23 | this.plugin = plugin; 24 | } 25 | 26 | @Override 27 | public void actionPerformed(ActionContext context) { 28 | if (plugin.provider.table != null) { 29 | for (var i : plugin.provider.table.getSelectedRows()) { 30 | int id = plugin.provider.table.convertRowIndexToModel(i); 31 | Boolean currentlyChecked = (Boolean) plugin.provider.ctm.getValueAt(id, 0); 32 | 33 | plugin.provider.ctm.setValueAt(!currentlyChecked, id, 0); 34 | } 35 | } 36 | 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/bindiffhelper/UpdateFunctionColoringAction.java: -------------------------------------------------------------------------------- 1 | package bindiffhelper; 2 | 3 | import java.awt.Color; 4 | import java.util.List; 5 | 6 | import docking.ActionContext; 7 | import docking.action.DockingAction; 8 | import docking.action.MenuData; 9 | import docking.action.ToolBarData; 10 | import ghidra.app.plugin.core.colorizer.ColorizingService; 11 | import ghidra.util.HTMLUtilities; 12 | import ghidra.util.Msg; 13 | import resources.ResourceManager; 14 | 15 | public class UpdateFunctionColoringAction extends DockingAction { 16 | BinDiffHelperPlugin plugin; 17 | List entries; 18 | 19 | public void setEntries(List e) { 20 | entries = e; 21 | } 22 | 23 | public UpdateFunctionColoringAction(BinDiffHelperPlugin plugin) { 24 | super("Colorize functions based on similarity", plugin.getName()); 25 | this.setMenuBarData(new MenuData(new String[] { "Update", "Function Colors" })); 26 | setToolBarData(new ToolBarData(ResourceManager.loadImage("images/color.png"), "Colorize Similar Functions")); 27 | setDescription(HTMLUtilities.toHTML("Colorize functions that are similar according to bindiff")); 28 | this.plugin = plugin; 29 | } 30 | 31 | @Override 32 | public void actionPerformed(ActionContext arg0) { 33 | // TODO Auto-generated method stub 34 | int trans = plugin.program.startTransaction("Colorize Functions"); 35 | var funmgr = plugin.program.getFunctionManager(); 36 | ColorizingService crayon = plugin.getTool().getService(ColorizingService.class); 37 | if (crayon == null) { 38 | Msg.showError(this, plugin.provider.getComponent(), "Failed to get colorizing service", 39 | "Failed to get colorizing service"); 40 | plugin.program.endTransaction(trans, false); 41 | return; 42 | } 43 | for (ComparisonTableModel.Entry e : entries) { 44 | if (e.primaryAddress == null) 45 | continue; // If we don't have a primary address, there is nothing to comment. 46 | var f = funmgr.getFunctionAt(e.primaryAddress); 47 | if (f == null) 48 | continue; // If there isn't a function at the address, nothing to do 49 | var body = f.getBody(); 50 | if (e.similarity > 0.90) { 51 | crayon.setBackgroundColor(body, Color.green); 52 | } 53 | if (e.similarity < 0.70) { 54 | crayon.setBackgroundColor(body, Color.RED); 55 | } 56 | } 57 | plugin.program.endTransaction(trans, true); 58 | 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/bindiffhelper/UpdatePlateCommentsAction.java: -------------------------------------------------------------------------------- 1 | package bindiffhelper; 2 | 3 | import java.util.List; 4 | 5 | import docking.ActionContext; 6 | import docking.action.DockingAction; 7 | import docking.action.MenuData; 8 | import docking.action.ToolBarData; 9 | import ghidra.util.HTMLUtilities; 10 | import resources.ResourceManager; 11 | 12 | public class UpdatePlateCommentsAction extends DockingAction { 13 | protected BinDiffHelperPlugin plugin; 14 | List entries; 15 | 16 | public UpdatePlateCommentsAction(BinDiffHelperPlugin plugin) { 17 | super("Create plate comments", plugin.getName()); 18 | this.setMenuBarData(new MenuData(new String[] { "Update", "Plate Comments" })); 19 | setToolBarData(new ToolBarData(ResourceManager.loadImage("images/comment.png"), "Update Plate Comments")); 20 | setDescription(HTMLUtilities.toHTML("Set plate comments with similarity and links to other binary")); 21 | this.plugin = plugin; 22 | } 23 | 24 | public void setEntries(List e) { 25 | entries = e; 26 | } 27 | 28 | /** 29 | * Take a string, split it apart and replace a string prefixed by the value in 30 | * key with the value in rep 31 | *

32 | * This is a utility function I use to update comments for updating 33 | * plate-comments at the top of functions 34 | *

35 | * This function automatically adds the key as the leader for rep 36 | * 37 | * @param str The string we are scanning for a line to replace 38 | * @param key The leader for the line we want to replace 39 | * @param rep The value we want in the string 40 | * @return A string with the line matching key replaced. If no line matched, 41 | * then we return rep 42 | */ 43 | public String updateKey(String str, String key, String rep) { 44 | String actualRep = String.format("%s: %s", key, rep); 45 | if (str == null) 46 | return actualRep; 47 | var strArr = str.split("\r?\n"); 48 | StringBuilder res = new StringBuilder(); 49 | 50 | boolean replaced = false; 51 | boolean prependSep = false; 52 | 53 | for (String s : strArr) { 54 | if (prependSep) 55 | res.append("\n"); 56 | if (s.startsWith(key)) { 57 | res.append(actualRep); 58 | replaced = true; 59 | prependSep = true; 60 | } else { 61 | res.append(s); 62 | prependSep = true; 63 | } 64 | } 65 | 66 | if (replaced == false) { 67 | if (prependSep) 68 | res.append("\n"); 69 | res.append(actualRep); 70 | } 71 | return res.toString(); 72 | } 73 | 74 | @Override 75 | public void actionPerformed(ActionContext arg0) { 76 | int trans = plugin.program.startTransaction("Create Plate Comments"); 77 | var symtab = plugin.program.getSymbolTable(); 78 | var funmgr = plugin.program.getFunctionManager(); 79 | 80 | for (ComparisonTableModel.Entry e : entries) { 81 | if (e.primaryAddress == null) 82 | continue; // If we don't have a primary address, there is nothing to comment. 83 | if (symtab.hasSymbol(e.primaryAddress)) { 84 | var f = funmgr.getFunctionAt(e.primaryAddress); 85 | if (f == null) // skip this if it is a symbol, but not a function 86 | continue; 87 | 88 | String origComment = f.getComment(); 89 | String newBindiffComment = String.format("*** %2f%% match with %2f%% confidence using %s ***", 90 | e.similarity * 100, e.confidence * 100, e.algorithm); 91 | String newFunctionLinkComment = String.format("*** %s@%08x {@program %s@%08x} ***", 92 | e.secondaryFunctionName, e.secondaryAddress, plugin.provider.otherProg, e.secondaryAddress); 93 | String newComment = updateKey(origComment, "BINDIFF_COMMENT", newBindiffComment); 94 | newComment = updateKey(newComment, "BINDIFF_MATCHED_FN", newFunctionLinkComment); 95 | f.setComment(newComment); 96 | } 97 | } 98 | plugin.program.endTransaction(trans, true); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/main/resources/images/BDH.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ubfx/BinDiffHelper/417a73d6955a8fd868d079374d9220f138903368/src/main/resources/images/BDH.png -------------------------------------------------------------------------------- /src/main/resources/images/arrow_merge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ubfx/BinDiffHelper/417a73d6955a8fd868d079374d9220f138903368/src/main/resources/images/arrow_merge.png -------------------------------------------------------------------------------- /src/main/resources/images/bd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ubfx/BinDiffHelper/417a73d6955a8fd868d079374d9220f138903368/src/main/resources/images/bd.png -------------------------------------------------------------------------------- /src/main/resources/images/bindiff-48x48-rgba.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ubfx/BinDiffHelper/417a73d6955a8fd868d079374d9220f138903368/src/main/resources/images/bindiff-48x48-rgba.png -------------------------------------------------------------------------------- /src/main/resources/images/check_box.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ubfx/BinDiffHelper/417a73d6955a8fd868d079374d9220f138903368/src/main/resources/images/check_box.png -------------------------------------------------------------------------------- /src/main/resources/images/color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ubfx/BinDiffHelper/417a73d6955a8fd868d079374d9220f138903368/src/main/resources/images/color.png -------------------------------------------------------------------------------- /src/main/resources/images/comment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ubfx/BinDiffHelper/417a73d6955a8fd868d079374d9220f138903368/src/main/resources/images/comment.png -------------------------------------------------------------------------------- /src/main/resources/images/open_db.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ubfx/BinDiffHelper/417a73d6955a8fd868d079374d9220f138903368/src/main/resources/images/open_db.png -------------------------------------------------------------------------------- /src/main/resources/images/open_from_project.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ubfx/BinDiffHelper/417a73d6955a8fd868d079374d9220f138903368/src/main/resources/images/open_from_project.png -------------------------------------------------------------------------------- /src/main/resources/images/setting_tools.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ubfx/BinDiffHelper/417a73d6955a8fd868d079374d9220f138903368/src/main/resources/images/setting_tools.png -------------------------------------------------------------------------------- /src/main/resources/images/table_go.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ubfx/BinDiffHelper/417a73d6955a8fd868d079374d9220f138903368/src/main/resources/images/table_go.png -------------------------------------------------------------------------------- /src/main/resources/images/table_lightning.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ubfx/BinDiffHelper/417a73d6955a8fd868d079374d9220f138903368/src/main/resources/images/table_lightning.png --------------------------------------------------------------------------------