├── .gitignore ├── .github ├── dependabot.yml └── workflows │ ├── simulate_activity.yml │ ├── check_new_commits.yml │ └── ci.yml ├── mac_arm_64.init.gradle ├── linux_arm_64.init.gradle ├── package.json ├── README.md └── generate_changelog.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | -------------------------------------------------------------------------------- /mac_arm_64.init.gradle: -------------------------------------------------------------------------------- 1 | allprojects { 2 | model { 3 | toolChains { 4 | clang(Clang) { 5 | target("mac_arm_64") { 6 | cCompiler.withArguments { args -> args += ['-target', 'arm64-apple-macos11' ] } 7 | cppCompiler.withArguments { args -> args += ['-target', 'arm64-apple-macos11' ] } 8 | } 9 | } 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /linux_arm_64.init.gradle: -------------------------------------------------------------------------------- 1 | allprojects { 2 | model { 3 | toolChains { 4 | gcc(Gcc) { 5 | target("linux_arm_64") { 6 | assembler.executable 'aarch64-linux-gnu-as' 7 | cCompiler.executable 'aarch64-linux-gnu-gcc' 8 | cppCompiler.executable 'aarch64-linux-gnu-g++' 9 | linker.executable 'aarch64-linux-gnu-g++' 10 | staticLibArchiver.executable 'aarch64-linux-gnu-ar' 11 | } 12 | } 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.github/workflows/simulate_activity.yml: -------------------------------------------------------------------------------- 1 | name: Simulate activity 2 | on: 3 | schedule: 4 | - cron: '0 0 1 * *' 5 | workflow_dispatch: 6 | inputs: {} 7 | 8 | jobs: 9 | simulate-activity: 10 | name: Simulate activity to keep workflows runnign 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | with: 15 | ref: master 16 | fetch-depth: 0 17 | - name: Amend and push force last commit to keep it up to date. 18 | run: | 19 | git config --global user.name 'github-actions[bot]' 20 | git config --global user.email '41898282+github-actions[bot]@users.noreply.github.com' 21 | git commit --amend --no-edit 22 | git push --force origin master 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "get-range", 3 | "version": "1.0.0", 4 | "description": "Ghidra is a software reverse engineering (SRE) framework created and maintained by the [National Security Agency][nsa] Research Directorate. This framework includes a suite of full-featured, high-end software analysis tools that enable users to analyze compiled code on a variety of platforms including Windows, macOS, and Linux. Capabilities include disassembly, assembly, decompilation, graphing, and scripting, along with hundreds of other features. Ghidra supports a wide variety of processor instruction sets and executable formats and can be run in both user-interactive and automated modes. Users may also develop their own Ghidra plug-in components and/or scripts using Java or Python.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/roblabla/ghidra.git" 12 | }, 13 | "author": "", 14 | "license": "ISC", 15 | "bugs": { 16 | "url": "https://github.com/roblabla/ghidra/issues" 17 | }, 18 | "homepage": "https://github.com/roblabla/ghidra#readme", 19 | "dependencies": { 20 | "nodegit": "^0.27.0" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /.github/workflows/check_new_commits.yml: -------------------------------------------------------------------------------- 1 | name: Check for new commits 2 | on: 3 | schedule: 4 | - cron: '30 12 * * *' 5 | workflow_dispatch: 6 | inputs: 7 | repo: 8 | description: Ghidra GitHub repository 9 | default: NationalSecurityAgency/ghidra 10 | required: true 11 | 12 | env: 13 | GHIDRA_REPO: ${{ inputs.repo || vars.GHIDRA_REPO || 'NationalSecurityAgency/ghidra' }} 14 | 15 | jobs: 16 | # Ensure all steps use a common revision 17 | check: 18 | name: Check for new commits since last release 19 | runs-on: ubuntu-latest 20 | steps: 21 | - uses: actions/checkout@v4 22 | with: 23 | repository: ${{ env.GHIDRA_REPO }} 24 | - name: Check for new commits 25 | id: check 26 | run: | 27 | LAST_REL_NAME=$(curl --silent "https://api.github.com/repos/${{github.repository}}/releases/latest" | jq .name) 28 | LAST_REL_COMMITID=$(echo $LAST_REL_NAME | grep -oP "\(\K\w+(?=\))") 29 | COMMIT_HASH_SHORT=$(git rev-parse --short HEAD) 30 | COMMIT_HASH_LONG=$(git rev-parse HEAD) 31 | echo "Latest commit is $COMMIT_HASH_LONG" 32 | if [[ "$LAST_REL_NAME" == *"$COMMIT_HASH_SHORT"* ]]; then 33 | echo "No commits since last release $LAST_REL_NAME" 34 | else 35 | echo "Found new commits since $LAST_REL_NAME. Triggering ci." 36 | echo "trigger=true" >> $GITHUB_OUTPUT 37 | echo "rev=$COMMIT_HASH_LONG" >> $GITHUB_OUTPUT 38 | echo "lastrev=$LAST_REL_COMMITID" >> $GITHUB_OUTPUT 39 | fi 40 | - name: Trigger build 41 | if: steps.check.outputs.trigger 42 | uses: benc-uk/workflow-dispatch@v1.2 43 | with: 44 | workflow: "Ghidra Build" 45 | inputs: '{ "repo": "${{ env.GHIDRA_REPO }}", "rev": "${{steps.check.outputs.rev}}", "prevRev": "${{steps.check.outputs.lastrev}}" }' 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ghidra CI 2 | 3 | Here, you'll find nightly builds of the NSA's [Ghidra], a Software Reverse 4 | Engineering framework. In particular, it creates a single cross-platform ZIP 5 | as close as possible to the official builds. 6 | 7 | ## How it works. 8 | 9 | The CI can be found in the [.github/workflows] folder. There are two main 10 | components: 11 | 12 | - `check_new_commits.yml`: Responsible for comparing the latest release of this 13 | repo with the Upstream's latest commits, to find out if some new commits were 14 | made since the last run (Ghidra doesn't get new commits every day, so we avoid 15 | doing unnecessary rebuilds on the silent days). 16 | - `ci.yml`: Actually does the work of building the CI. 17 | 18 | There's also `simulate_activity.yml`, which is mostly there to work around 19 | [Github disabling workflows after two months of inactivity]. By simulating 20 | activity on the master branch, we avoid this. 21 | 22 | ### Check new commits 23 | 24 | `check_new_commits.yml` runs every night. It parses the latest release name to 25 | find out which commit it was built from. It then checks whether this matches 26 | the latest commit from NSA's Ghidra master branch. If it doesn't, it triggers 27 | the `ci.yml` workflow through a [`workflow_dispatch`] call. 28 | 29 | ### CI 30 | 31 | The actual CI is split in two jobs: 32 | 33 | - `build-natives`: Builds the native binaries for all the supported platforms. 34 | This job will install the necessary tools to build the native binaries on the 35 | given platform, and run `gradle buildNatives_`. Note that, for building 36 | on arm64, we copy over `linux_arm_64.init.gradle` or `mac_arm_64.init.gradle` 37 | to the `~/.gradle/init.gradle`. This is used to cross-compile the ARM64 38 | binaries from an X64 host. This is used because GitHub does not provide an 39 | ARM64 Github Runner, so cross-compilation must be used. 40 | 41 | - `dist`: Downloads all the artifacts from `build-natives`, downloads the 42 | FunctionID from the [`ghidra-data`] repository, and runs `gradle buildGhidra`. 43 | This will create a cross-platform dist, which is then uploaded to the releases. 44 | Finally, the `generate_changelog.js` script is run to generate the changelog. 45 | 46 | ## Building a fork 47 | 48 | If you want to use this CI to build your fork of ghidra, it's now very simple: 49 | 50 | - First, fork this repository 51 | - Navigate to the settings of the ghidra-ci fork, then go to Secrets and 52 | Variables -> Actions -> Variables -> New Repository Variable. 53 | - Here, create a variable named GHIDRA_REPO, and set its value to the `username/reponame` 54 | of your ghidra fork, for instance `NationalSecurityAgency/Ghidra`. 55 | - Save that variable. 56 | - Enable github actions by navigating to the actions of your ghidra-ci fork, 57 | and enabling all three workflows. 58 | 59 | ## Remaining work 60 | 61 | There are several improvements to be made: 62 | 63 | 1. The Eclipse IDE plugin should be built. This is not really possible today, 64 | as eclipse IDE plugins can only be built through manual action from the 65 | Eclipse IDE software. 66 | 67 | 68 | 69 | [.github/workflows]: .github/workflows 70 | [Ghidra]: https://github.com/NationalSecurityAgency/ghidra 71 | [ghidra-data]: https://github.com/NationalSecurityAgency/ghidra-data 72 | [Github disabling workflows after two months of inactivity]: https://docs.github.com/en/actions/managing-workflow-runs/disabling-and-enabling-a-workflow 73 | [`workflow_dispatch`]: https://docs.github.com/en/actions/managing-workflow-runs/manually-running-a-workflow 74 | -------------------------------------------------------------------------------- /generate_changelog.js: -------------------------------------------------------------------------------- 1 | const { symlinkSync } = require("fs"); 2 | const NodeGit = require("nodegit"); 3 | const path = require("path"); 4 | const { stderr } = require("process"); 5 | 6 | async function main() { 7 | const repoName = process.argv[2]; 8 | const commitFrom = process.argv[3]; 9 | const commitTo = process.argv[4]; 10 | 11 | console.error(`Generating commit for range ${commitFrom}..${commitTo}`); 12 | 13 | const pathToRepo = path.resolve("../"); 14 | const repo = await NodeGit.Repository.open(pathToRepo); 15 | 16 | const revwalk = NodeGit.Revwalk.create(repo); 17 | revwalk.reset(); 18 | const res = revwalk.pushRange(`${commitFrom}..${commitTo}`); 19 | if (res !== 0) { 20 | throw "Failed to push range: " + res; 21 | } 22 | 23 | const graph = []; 24 | const childMap = new Map(); 25 | 26 | while (true) { 27 | let commitId; 28 | 29 | try { 30 | commitId = await revwalk.next(); 31 | } catch (err) { 32 | console.error(err); 33 | break; 34 | } 35 | 36 | const commit = await NodeGit.Commit.lookup(repo, commitId); 37 | const isMerge = commit.parentcount() != 1; 38 | if (isMerge) { 39 | const otherParent = await commit.parent(1); 40 | childMap.set(otherParent.sha(), graph.length); 41 | graph.push({ 42 | type: "merge", 43 | message: commit.message(), 44 | sha: commit.sha(), 45 | children: [] 46 | }); 47 | } else if (childMap.has(commit.sha())) { 48 | const rootMergeIdx = childMap.get(commit.sha()); 49 | graph[rootMergeIdx].children.push({ 50 | message: commit.message(), 51 | sha: commit.sha(), 52 | }); 53 | 54 | for (let parentId of commit.parents()) { 55 | let parent = await NodeGit.Commit.lookup(repo, parentId); 56 | childMap.set(parent.sha(), rootMergeIdx); 57 | } 58 | } else { 59 | graph.push({ 60 | type: "simple", 61 | message: commit.message(), 62 | sha: commit.sha(), 63 | }); 64 | } 65 | } 66 | 67 | var generatedMarkdown = "# Changelog\n\n"; 68 | 69 | generatedMarkdown += `Commit range: [${commitFrom}..${commitTo}](https://github.com/${repoName}/compare/${commitFrom}...${commitTo})\n\n`; 70 | 71 | for (let change of graph) { 72 | if (change.type == "merge") { 73 | if (change.children.length === 1) { 74 | generatedMarkdown += formatCommit(change.children[0], false); 75 | } else { 76 | generatedMarkdown += formatCommit(change, false) 77 | generatedMarkdown += "\n
Commit details\n\n" 78 | for (let innerChange of change.children) { 79 | generatedMarkdown += " " + formatCommit(innerChange, true); 80 | } 81 | generatedMarkdown += "
\n"; 82 | } 83 | } else { 84 | generatedMarkdown += formatCommit(change, false); 85 | } 86 | } 87 | 88 | console.log(generatedMarkdown); 89 | } 90 | 91 | function formatCommit(change, isChild) { 92 | let replaceWithSpaces = isChild ? "\n " : "\n "; 93 | const linkSha = `[${change.sha.slice(0, 8)}](https://github.com/${repoName}/commit/${change.sha})` 94 | return "- " + linkSha + " " + change.message.trim().split("\n").join(replaceWithSpaces) + "\n"; 95 | } 96 | 97 | main().catch(function(err) { 98 | console.error(err) 99 | }); 100 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Ghidra Build 2 | on: 3 | workflow_dispatch: 4 | inputs: 5 | repo: 6 | description: Ghidra GitHub repository 7 | default: NationalSecurityAgency/ghidra 8 | required: true 9 | prevRev: 10 | description: 'Revision of the previous release' 11 | required: false 12 | rev: 13 | description: 'Revision to build' 14 | required: true 15 | 16 | env: 17 | GRADLE_VERSION: 8.5 18 | JAVA_VERSION: 21 19 | 20 | jobs: 21 | build-natives: 22 | strategy: 23 | matrix: 24 | include: 25 | #- target: win_x86_32 26 | # os: windows-latest 27 | - target: win_x86_64 28 | os: windows-latest 29 | - target: linux_x86_64 30 | os: ubuntu-latest 31 | - target: linux_arm_64 32 | os: ubuntu-latest 33 | - target: mac_x86_64 34 | os: macos-latest 35 | - target: mac_arm_64 36 | os: macos-latest 37 | fail-fast: false 38 | 39 | name: Build ${{ matrix.target }} Binaries 40 | runs-on: ${{ matrix.os }} 41 | steps: 42 | - uses: actions/checkout@v4 43 | with: 44 | repository: ${{ github.event.inputs.repo }} 45 | ref: ${{ github.event.inputs.rev }} 46 | - uses: actions/setup-java@v4 47 | with: 48 | distribution: 'zulu' 49 | java-version: ${{ env.JAVA_VERSION }} 50 | - name: Install bison 51 | if: ${{ matrix.os == 'windows-latest' }} 52 | shell: pwsh 53 | run: | 54 | Invoke-WebRequest -URI "https://github.com/lexxmark/winflexbison/releases/download/v2.4.12/win_flex_bison-2.4.12.zip" -OutFile "win_flex_bison-2.4.12.zip" 55 | Expand-Archive -Path "win_flex_bison-2.4.12.zip" -DestinationPath "winflexbison" 56 | Rename-Item -Path "$pwd\winflexbison\win_bison.exe" -NewName "bison.exe" 57 | Rename-Item -Path "$pwd\winflexbison\win_flex.exe" -NewName "flex.exe" 58 | "$pwd\winflexbison" >> $env:GITHUB_PATH 59 | - name: Checkout Ghidra-CI Repo 60 | uses: actions/checkout@v4 61 | with: 62 | path: ghidra-ci 63 | - uses: eskatos/gradle-command-action@v3 64 | with: 65 | gradle-version: ${{ env.GRADLE_VERSION }} 66 | arguments: --init-script gradle/support/fetchDependencies.gradle init 67 | - name: Setup Linux ARM toolchain 68 | if: ${{ matrix.target == 'linux_arm_64' }} 69 | run: | 70 | sudo dpkg --add-architecture arm64 71 | sudo bash -c 'echo "http://ports.ubuntu.com/ubuntu-ports/ priority:0" >> /etc/apt/apt-mirrors.txt' 72 | sudo apt-get update 73 | sudo apt-get install g++-aarch64-linux-gnu libc6-dev-arm64-cross zlib1g-dev:arm64 74 | mkdir -p $HOME/.gradle 75 | cp ghidra-ci/linux_arm_64.init.gradle $HOME/.gradle/init.gradle 76 | - name: Setup MacOS ARM toolchain 77 | if: ${{ matrix.target == 'mac_arm_64' }} 78 | run: | 79 | mkdir -p $HOME/.gradle 80 | cp ghidra-ci/mac_arm_64.init.gradle $HOME/.gradle/init.gradle 81 | - uses: eskatos/gradle-command-action@v3 82 | with: 83 | gradle-version: ${{ env.GRADLE_VERSION }} 84 | arguments: buildNatives_${{ matrix.target }} 85 | - name: "Sign macOS binaries" 86 | if: ${{ env.MACOS_CODESIGN_CRT_PWD != '' && (matrix.target == 'mac_arm_64' || matrix.target == 'mac_x86_64') }} 87 | run: | 88 | echo "$MACOS_CODESIGN_CRT" | base64 -d > certificate.p12 89 | security create-keychain -p test123 build.keychain 90 | security default-keychain -s build.keychain 91 | security unlock-keychain -p test123 build.keychain 92 | security import certificate.p12 -k build.keychain -P "$MACOS_CODESIGN_CRT_PWD" -T /usr/bin/codesign 93 | rm certificate.p12 94 | security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k test123 build.keychain 95 | for file in $(find . -path "*/build/os/${{ matrix.target }}/*" -type f); do 96 | echo "Signing file $file" 97 | /usr/bin/codesign --force -s "$MACOS_CODESIGN_CRT_IDENTITY" --options runtime "$file" -v 98 | done 99 | security lock-keychain build.keychain 100 | security default-keychain -s 101 | security delete-keychain build.keychain 102 | env: 103 | MACOS_CODESIGN_CRT: ${{ secrets.MACOS_CODESIGN_CRT }} 104 | MACOS_CODESIGN_CRT_PWD: ${{ secrets.MACOS_CODESIGN_CRT_PWD }} 105 | MACOS_CODESIGN_CRT_IDENTITY: ${{ secrets.MACOS_CODESIGN_CRT_IDENTITY }} 106 | # Apparently, github is an incompetent idiot that can't handle permissions 107 | # properly. https://github.com/actions/upload-artifact/issues/38 108 | # Wrap the binaries in a tar archive to fix that. 109 | - name: Tar the binaries 110 | run: tar -cvf "${{matrix.target}}.build.tar" $(find . -path "*/build/os/${{ matrix.target }}/*" -type f) 111 | shell: bash 112 | - name: "Notarize macOS binaries" 113 | if: ${{ env.MACOS_APPLE_USERNAME != '' && (matrix.target == 'mac_arm_64' || matrix.target == 'mac_x86_64') }} 114 | run: | 115 | for file in $(find . -path "*/build/os/${{ matrix.target }}/*" -type f); do 116 | echo "Notarizing file $file" 117 | ditto -c -k "$file" "${file}.zip" 118 | xcrun notarytool submit --apple-id "$MACOS_APPLE_USERNAME" --password "$MACOS_APPLE_PASSWORD" --team-id "$MACOS_APPLE_TEAMID" --wait "${file}.zip" 119 | rm "${file}.zip" 120 | done 121 | env: 122 | MACOS_APPLE_USERNAME: ${{ secrets.MACOS_APPLE_USERNAME }} 123 | MACOS_APPLE_PASSWORD: ${{ secrets.MACOS_APPLE_PASSWORD }} 124 | MACOS_APPLE_TEAMID: ${{ secrets.MACOS_APPLE_TEAMID }} 125 | - name: Upload artifacts 126 | uses: actions/upload-artifact@v4 127 | with: 128 | name: natives-${{ matrix.target }} 129 | path: "${{matrix.target}}.build.tar" 130 | 131 | dist: 132 | name: "Build Ghidra distributable zip" 133 | needs: ["build-natives"] 134 | runs-on: ubuntu-latest 135 | steps: 136 | - uses: actions/checkout@v4 137 | with: 138 | repository: ${{ github.event.inputs.repo }} 139 | ref: ${{ github.event.inputs.rev }} 140 | fetch-depth: 0 141 | 142 | - name: Download native binaries 143 | uses: actions/download-artifact@v4 144 | with: 145 | pattern: natives-* 146 | merge-multiple: true 147 | 148 | - name: Extract all binaries 149 | run: | 150 | for file in *.build.tar; do 151 | echo "Extracting $file" 152 | tar xvf "$file" 153 | done 154 | 155 | - uses: actions/setup-java@v4 156 | with: 157 | distribution: 'zulu' 158 | java-version: ${{ env.JAVA_VERSION }} 159 | - name: Fetch the Ghidra dependencies. 160 | uses: eskatos/gradle-command-action@v3 161 | with: 162 | gradle-version: ${{ env.GRADLE_VERSION }} 163 | arguments: --init-script gradle/support/fetchDependencies.gradle init 164 | # TODO: Pre-build GhidraDev 165 | - name: Checkout ghidra-data 166 | uses: actions/checkout@v4 167 | with: 168 | repository: NationalSecurityAgency/ghidra-data 169 | path: 'ghidra-data' 170 | - name: Copy ghidra-data files into the appropriate directories 171 | run: cp -r ghidra-data/FunctionID/* Ghidra/Features/FunctionID/data 172 | - name: Build ghidra, create a cross-platform distribution 173 | uses: eskatos/gradle-command-action@v3 174 | with: 175 | gradle-version: ${{ env.GRADLE_VERSION }} 176 | arguments: -x ip -PallPlatforms buildGhidra 177 | # TODO: remove upload-artifact when create release is sure to work 178 | - name: Upload final dist 179 | uses: actions/upload-artifact@v4 180 | with: 181 | path: "build/dist/*" 182 | - name: Remove temporary artifacts 183 | uses: geekyeggo/delete-artifact@v5 184 | with: 185 | name: natives-* 186 | - name: Get current date, rev and dist name 187 | id: date 188 | run: | 189 | echo date=$(date +'%Y-%m-%d') >> $GITHUB_OUTPUT 190 | echo dist=$(ls build/dist) >> $GITHUB_OUTPUT 191 | echo rev=$(git rev-parse --short HEAD) >> $GITHUB_OUTPUT 192 | - uses: actions/checkout@v4 193 | with: 194 | path: ghidra-ci 195 | - name: Generate CHANGELOG.md 196 | if: ${{ inputs.prevRev != '' }} 197 | run: | 198 | cd ghidra-ci 199 | sudo apt-get update 200 | sudo apt-get install libkrb5-dev 201 | npm i 202 | node generate_changelog.js ${{ github.event.inputs.repo }} ${{github.event.inputs.prevRev}} ${{github.event.inputs.rev}} > CHANGELOG.md 203 | - name: Generate fallback CHANGELOG.md 204 | if: ${{ inputs.prevRev == '' }} 205 | run: | 206 | cd ghidra-ci 207 | echo "# Changelog" > CHANGELOG.md 208 | echo "Built from [${{ inputs.repo }}@${{ inputs.rev }}](https://github.com/${{ inputs.repo }}/commit/${{ inputs.rev }})" >> CHANGELOG.md 209 | - name: Create Release 210 | id: create_release 211 | uses: actions/create-release@v1 212 | env: 213 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 214 | with: 215 | tag_name: ${{ steps.date.outputs.date }} 216 | release_name: Release ${{ steps.date.outputs.date }}(${{ steps.date.outputs.rev }}) 217 | body_path: ./ghidra-ci/CHANGELOG.md 218 | # TODO: This is a horrible hack. 219 | commitish: "master" 220 | draft: false 221 | prerelease: false 222 | - name: Upload Release Asset 223 | id: upload-release-asset 224 | uses: actions/upload-release-asset@v1 225 | env: 226 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 227 | with: 228 | upload_url: ${{ steps.create_release.outputs.upload_url }} 229 | asset_path: ./build/dist/${{ steps.date.outputs.dist }} 230 | asset_name: release.zip 231 | asset_content_type: application/zip 232 | --------------------------------------------------------------------------------