al3xtjames / ghidra-firmware-utils

Ghidra utilities for analyzing PC firmware
Other
386 stars 43 forks source link

Does Not Work on Windows #14

Closed nstarke closed 2 years ago

nstarke commented 3 years ago

On windows, trying to mount a uefi filesystem results in the following JNI exception:

'byte[] firmware.common.EFIDecompressor.nativeDecompress(byte[])'
java.lang.UnsatisfiedLinkError: 'byte[] firmware.common.EFIDecompressor.nativeDecompress(byte[])'
    at firmware.common.EFIDecompressor.nativeDecompress(Native Method)
    at firmware.common.EFIDecompressor.decompress(EFIDecompressor.java:51)
    at firmware.uefi_fv.FFSCompressedSection.<init>(FFSCompressedSection.java:82)
    at firmware.uefi_fv.FFSSectionFactory.parseSection(FFSSectionFactory.java:64)
    at firmware.uefi_fv.UEFIFFSFile.<init>(UEFIFFSFile.java:147)
    at firmware.uefi_fv.UEFIFirmwareVolumeHeader.<init>(UEFIFirmwareVolumeHeader.java:252)
    at firmware.uefi_fv.UEFIFirmwareVolumeFileSystem.mount(UEFIFirmwareVolumeFileSystem.java:54)
    at firmware.uefi_fv.UEFIFirmwareVolumeFileSystemFactory.create(UEFIFirmwareVolumeFileSystemFactory.java:47)
    at firmware.uefi_fv.UEFIFirmwareVolumeFileSystemFactory.create(UEFIFirmwareVolumeFileSystemFactory.java:29)
    at ghidra.formats.gfilesystem.factory.FileSystemFactoryMgr.mountUsingFactory(FileSystemFactoryMgr.java:176)
    at ghidra.formats.gfilesystem.factory.FileSystemFactoryMgr.probe(FileSystemFactoryMgr.java:360)
    at ghidra.formats.gfilesystem.FileSystemService.probeFileForFilesystem(FileSystemService.java:672)
    at ghidra.formats.gfilesystem.FileSystemService.probeFileForFilesystem(FileSystemService.java:611)
    at ghidra.plugins.fsbrowser.FileSystemBrowserPlugin.doOpenFilesystem(FileSystemBrowserPlugin.java:258)
    at ghidra.plugins.fsbrowser.FileSystemBrowserPlugin.lambda$openFileSystem$0(FileSystemBrowserPlugin.java:117)
    at ghidra.util.task.TaskLauncher$2.run(TaskLauncher.java:117)
    at ghidra.util.task.Task.monitoredRun(Task.java:124)
    at ghidra.util.task.TaskRunner.lambda$startTaskThread$0(TaskRunner.java:104)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1130)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:630)
    at java.base/java.lang.Thread.run(Thread.java:832)

I hacked together a build.gradle file that will build a working version:

import org.apache.tools.ant.taskdefs.condition.Os

//----------------------START "DO NOT MODIFY" SECTION------------------------------
def ghidraInstallDir

if (System.env.GHIDRA_INSTALL_DIR) {
    ghidraInstallDir = System.env.GHIDRA_INSTALL_DIR
}
else if (project.hasProperty("GHIDRA_INSTALL_DIR")) {
    ghidraInstallDir = project.getProperty("GHIDRA_INSTALL_DIR")
}

if (ghidraInstallDir) {
    apply from: new File(ghidraInstallDir).getCanonicalPath() + "/support/buildExtension.gradle"
}
else {
    throw new GradleException("GHIDRA_INSTALL_DIR is not defined!")
}
//----------------------END "DO NOT MODIFY" SECTION-------------------------------

apply plugin: "c"
model {
    platforms {
        x64 {
            architecture "x86_64"
        }
    }
    toolChains {
        gcc(Gcc) {
            path "C:\\Program Files\\mingw-w64\\x86_64-8.1.0-posix-seh-rt_v6-rev0\\mingw64\\bin"
            eachPlatform {
                cCompiler.executable = "gcc.exe"
            }
        }
    }
    components {
        efidecompress(NativeLibrarySpec) {
            targetPlatform "x64"
            sources {
                c {
                    source {
                        srcDir "src/efidecompress/c"
                        include "efidecompress.c"
                    }
                }
            }

            binaries.all {
                cCompiler.args "-DCONFIG_JNI"
                if (targetPlatform.operatingSystem.macOsX) {
                    cCompiler.args "-I", "${System.properties['java.home']}/include"
                    cCompiler.args "-I", "${System.properties['java.home']}/include/darwin"
                    cCompiler.args "-mmacosx-version-min=10.9"
                    linker.args "-mmacosx-version-min=10.9"
                } else if (targetPlatform.operatingSystem.linux) {
                    cCompiler.args "-I", "${System.properties['java.home']}/include"
                    cCompiler.args "-I", "${System.properties['java.home']}/include/linux"
                    cCompiler.args "-D_FILE_OFFSET_BITS=64"
                } else if (targetPlatform.operatingSystem.windows) {
                    cCompiler.args "-I${System.properties['java.home']}\\include"
                    cCompiler.args "-I${System.properties['java.home']}\\include\\win32"
                    cCompiler.args "-D_JNI_IMPLEMENTATION_"
                    linker.args "-Wl,--kill-at"
                    linker.args "-shared"
                }
            }
        }
    }
}

repositories {
    mavenCentral()
}

configurations {
    toCopy
}

dependencies {
    toCopy group: "org.tukaani", name: "xz", version: "1.8"
}

task copyLibraries(type: Copy, dependsOn: "efidecompressSharedLibrary") {
    copy {
        from configurations.toCopy into "lib"
    }

    if (Os.isFamily(Os.FAMILY_MAC)) {
        from "$buildDir/libs/efidecompress/shared/libefidecompress.dylib" into "os/osx64"
    } else if (Os.isFamily(Os.FAMILY_UNIX)) {
        from "$buildDir/libs/efidecompress/shared/libefidecompress.so" into "os/linux64"
    } else if (Os.isFamily(Os.FAMILY_WINDOWS)) {
        from "$buildDir/libs/efidecompress/shared/efidecompress.dll" into "os/win64"
    }
}

buildExtension.dependsOn "copyLibraries"

task cleanLibraries(type: Delete) {
    delete fileTree("lib").matching {
        include "*.jar"
    }

    delete fileTree("os").matching {
        include "osx64/*.dylib"
        include "linux64/*.so"
        include "win64/*.dll"
    }
}

clean.dependsOn "cleanLibraries"

Now this hacked-together gradle file might build a working version for windows, but it almost certainly will not work for other operating systems. I'm not a good enough Java/Groovy/Gradle engineer to know how to refactor this to a form that can be committed to the repository, but it took me quite a bit of effort to figure out why the current master branch does not work so I wanted to share my work in case anyone else needs it.

FWIW, I documented what I found here: https://nstarke.github.io/0047-ghidra-firmware-utils-adventure.html

nstarke commented 3 years ago

Looks like ming-w64 isn't required, all that needs to happen is to set the architecture to x64 on 64-bit platforms. An updated build.gradle is provided below:

// Builds a Ghidra Extension for a given Ghidra installation.
//
// An absolute path to the Ghidra installation directory must be supplied either by setting the
// GHIDRA_INSTALL_DIR environment variable or Gradle project property:
//
//     > export GHIDRA_INSTALL_DIR=<Absolute path to Ghidra>
//     > gradle
//
//         or
//
//     > gradle -PGHIDRA_INSTALL_DIR=<Absolute path to Ghidra>
//
// Gradle should be invoked from the directory of the project to build.  Please see the
// application.gradle.version property in <GHIDRA_INSTALL_DIR>/Ghidra/application.properties
// for the correction version of Gradle to use for the Ghidra installation you specify.

import org.apache.tools.ant.taskdefs.condition.Os

//----------------------START "DO NOT MODIFY" SECTION------------------------------
def ghidraInstallDir

if (System.env.GHIDRA_INSTALL_DIR) {
    ghidraInstallDir = System.env.GHIDRA_INSTALL_DIR
}
else if (project.hasProperty("GHIDRA_INSTALL_DIR")) {
    ghidraInstallDir = project.getProperty("GHIDRA_INSTALL_DIR")
}

if (ghidraInstallDir) {
    apply from: new File(ghidraInstallDir).getCanonicalPath() + "/support/buildExtension.gradle"
}
else {
    throw new GradleException("GHIDRA_INSTALL_DIR is not defined!")
}
//----------------------END "DO NOT MODIFY" SECTION-------------------------------

apply plugin: "c"
model {
    platforms {
            x64 {
                architecture "x86_64"
            }
        }
    components {
        efidecompress(NativeLibrarySpec) {
            targetPlatform "x64"
            sources {
                c {
                    source {
                        srcDir "src/efidecompress/c"
                        include "efidecompress.c"
                    }
                }
            }

            binaries.all {
                cCompiler.args "-DCONFIG_JNI"
                if (targetPlatform.operatingSystem.macOsX) {
                    cCompiler.args "-I", "${System.properties['java.home']}/include"
                    cCompiler.args "-I", "${System.properties['java.home']}/include/darwin"
                    cCompiler.args "-mmacosx-version-min=10.9"
                    linker.args "-mmacosx-version-min=10.9"
                } else if (targetPlatform.operatingSystem.linux) {
                    cCompiler.args "-I", "${System.properties['java.home']}/include"
                    cCompiler.args "-I", "${System.properties['java.home']}/include/linux"
                    cCompiler.args "-D_FILE_OFFSET_BITS=64"
                } else if (targetPlatform.operatingSystem.windows) {
                    cCompiler.args "-I${System.properties['java.home']}\\include"
                    cCompiler.args "-I${System.properties['java.home']}\\include\\win32"
                }
            }
        }
    }
}

repositories {
    mavenCentral()
}

configurations {
    toCopy
}

dependencies {
    toCopy group: "org.tukaani", name: "xz", version: "1.8"
}

task copyLibraries(type: Copy, dependsOn: "efidecompressSharedLibrary") {
    copy {
        from configurations.toCopy into "lib"
    }

    if (Os.isFamily(Os.FAMILY_MAC)) {
        from "$buildDir/libs/efidecompress/shared/libefidecompress.dylib" into "os/osx64"
    } else if (Os.isFamily(Os.FAMILY_UNIX)) {
        from "$buildDir/libs/efidecompress/shared/libefidecompress.so" into "os/linux64"
    } else if (Os.isFamily(Os.FAMILY_WINDOWS)) {
        from "$buildDir/libs/efidecompress/shared/efidecompress.dll" into "os/win64"
    }
}

buildExtension.dependsOn "copyLibraries"

task cleanLibraries(type: Delete) {
    delete fileTree("lib").matching {
        include "*.jar"
    }

    delete fileTree("os").matching {
        include "osx64/*.dylib"
        include "linux64/*.so"
        include "win64/*.dll"
    }
}

clean.dependsOn "cleanLibraries"
al3xtjames commented 3 years ago

Thanks for the report and investigation. I recall being able to successfully build and run the plugin on Windows in the past (during GSoC), but I haven't tried it recently (CI would probably be useful here...). I'll see if there is a better way to do this without having to force x86-64 as the target.