GhidraJupyter / ghidra-jupyter-kotlin

MIT License
115 stars 10 forks source link

Support starting the jupyter without Ghidra GUI. #32

Open 5c4lar opened 2 years ago

5c4lar commented 2 years ago

Is there a way to run a kotlin jupyter notebook without starting the Ghidra GUI? That would be helpful when developing scripts on a server.

fmagin commented 2 years ago

Mmh, none that I have setup yet, but in theory it should be quite possible. You can simply call IkotlinKt.embedKernel(connectionFile, EmptyResolutionInfoProvider.INSTANCE, null);} in a postScript and it will set up the kernel, so using the jupyter-console is fairly trivial. For the notebook I guess there just needs to be one function that handles the initial setup of the notebook, which requires a bit of trickery so the Jupyter Notebook thinks that it started the server. I assume that the Jupyter Notebook server would be running on the same host as the Ghidra instance, and you would just be connecting to the notebook server from a remote client?

5c4lar commented 2 years ago

Thanks! @fmagin

I assume that the Jupyter Notebook server would be running on the same host as the Ghidra instance, and you would just be connecting to the notebook server from a remote client?

This is exactly what I'm trying to do.

fmagin commented 2 years ago

Okay, quickly gave this a try: The first issue is that the NotebookThread constructor takes a PluginTool as an argument, which is not available in headless mode.

But when I change the NotebookProxy class to be public, you can run the following script and it will somewhat start the kernel. It's a bit finicky about when you start the kernel in the Jupyter web interface and when to run the script, because most of the times it fails with Could not initialize class org.slf4j.LoggerFactory (HeadlessAnalyzer) java.lang.NoClassDefFoundError: Could not initialize class org.slf4j.LoggerFactory but sometimes it works.

Could not initialize class org.slf4j.LoggerFactory (HeadlessAnalyzer) java.lang.NoClassDefFoundError: Could not initialize class org.slf4j.LoggerFactory
    at org.jetbrains.kotlinx.jupyter.config.LoggingKt.getLogger(logging.kt:9)
    at org.jetbrains.kotlinx.jupyter.config.LoggingKt.getLogger$default(logging.kt:9)
    at org.jetbrains.kotlinx.jupyter.libraries.ResolutionUtilKt.<clinit>(resolutionUtil.kt:9)
    at org.jetbrains.kotlinx.jupyter.KernelConfig$Companion.create(config.kt:167)
    at org.jetbrains.kotlinx.jupyter.IkotlinKt.embedKernel(ikotlin.kt:105)
    at HeadlessNotebook.run(HeadlessNotebook.kt:27)
    at ghidra.app.script.GhidraScript.executeNormal(GhidraScript.java:379)
    at ghidra.app.script.GhidraScript.doExecute(GhidraScript.java:234)
    at ghidra.app.script.GhidraScript.execute(GhidraScript.java:212)
    at ghidra.app.util.headless.HeadlessAnalyzer.runScript(HeadlessAnalyzer.java:576)
    at ghidra.app.util.headless.HeadlessAnalyzer.runScriptsList(HeadlessAnalyzer.java:909)
    at ghidra.app.util.headless.HeadlessAnalyzer.processWithImport(HeadlessAnalyzer.java:1765)
    at ghidra.app.util.headless.HeadlessAnalyzer.processLocal(HeadlessAnalyzer.java:445)
    at ghidra.app.util.headless.AnalyzeHeadless.launch(AnalyzeHeadless.java:121)
    at ghidra.GhidraLauncher.launch(GhidraLauncher.java:59)
    at ghidra.GhidraLauncher.main(GhidraLauncher.java:74)

@tmr232 you built the Notebook proxy, what do you think is the best way to allow this to work both in the GUI and from a script in headless mode? I'm quite confused why this logging class error only happens sometimes, I guess there are some kind of incompatible libraries involved here.

Script:

import GhidraJupyterKotlin.CellContext
import GhidraJupyterKotlin.NotebookProxy
import ghidra.app.script.GhidraScript
import ghidra.util.Msg
import ghidra.util.task.DummyCancellableTaskMonitor
import org.jetbrains.kotlinx.jupyter.embedKernel
import org.jetbrains.kotlinx.jupyter.libraries.EmptyResolutionInfoProvider
import java.nio.file.Path
import java.util.*

class HeadlessNotebook : GhidraScript() {
    override fun run() {
        val proxyPath = Path.of(
            Optional.ofNullable(System.getenv("GHIDRA_JUPYTER_PROXY"))
                .orElse(
                    Path.of(System.getProperty("user.home"))
                        .resolve(".ghidra")
                        .resolve("notebook_proxy")
                        .toString()
                )
        )
        val connectionFile = NotebookProxy(proxyPath).waitForConnection(DummyCancellableTaskMonitor())
        Msg.info(this, connectionFile.toString())

        embedKernel(
            connectionFile,
            EmptyResolutionInfoProvider, listOf<CellContext>(CellContext(this))
        )
    }
}