diff --git a/.github/patches/binexport-bdb8c4430549e69d4a9a7531c59b197f3a0757e6.patch b/.github/patches/binexport-bdb8c4430549e69d4a9a7531c59b197f3a0757e6.patch new file mode 100644 index 00000000..0baf4666 --- /dev/null +++ b/.github/patches/binexport-bdb8c4430549e69d4a9a7531c59b197f3a0757e6.patch @@ -0,0 +1,25 @@ +diff --git a/cmake/FindIdaSdk.cmake b/cmake/FindIdaSdk.cmake +index 9e0cfa1..811fa7b 100644 +--- a/cmake/FindIdaSdk.cmake ++++ b/cmake/FindIdaSdk.cmake +@@ -99,6 +99,7 @@ else() + endif() + + message(STATUS "IDA_SDK_VERSION = ${IDA_SDK_VERSION}") ++add_compile_definitions(IDA_SDK_VERSION=${IDA_SDK_VERSION}) + + # Define some platform specific variables for later use. + set(_so "${CMAKE_SHARED_LIBRARY_SUFFIX}") +@@ -213,9 +214,9 @@ if(APPLE) + -output "${_ida64_universal_lib}" + ) + endif() +- add_library(ida SHARED IMPORTED) +- add_dependencies(ida ida64_universal) +- set_target_properties(ida PROPERTIES ++ add_library(ida64 SHARED IMPORTED) ++ add_dependencies(ida64 ida64_universal) ++ set_target_properties(ida64 PROPERTIES + IMPORTED_LOCATION "${_ida64_universal_lib}" + ) + diff --git a/.github/workflows/build-single.yml b/.github/workflows/build-single.yml new file mode 100644 index 00000000..cebefa40 --- /dev/null +++ b/.github/workflows/build-single.yml @@ -0,0 +1,280 @@ +name: build-single + +on: + workflow_dispatch: + inputs: + ida_version: + description: "IDA version to build for (e.g. 9.3 or latest)" + required: false + default: "latest" + type: string + release_tag: + description: "Existing release tag to append the artifact to (e.g. v8.0.0, latest, or none to skip release)" + required: false + default: "none" + type: string + repository_dispatch: + types: [build-single] + +permissions: read-all + +concurrency: + group: build-single-${{ github.event.inputs.ida_version || github.event.client_payload.ida_version || 'latest' }} + cancel-in-progress: true + +jobs: + resolve: + runs-on: ubuntu-latest + outputs: + ida_version: ${{ steps.resolve.outputs.ida_version }} + release_tag: ${{ steps.resolve.outputs.release_tag }} + steps: + - name: Setup uv + uses: astral-sh/setup-uv@v6 + with: + ignore-empty-workdir: true + enable-cache: false + + - name: Resolve inputs + id: resolve + env: + HCLI_API_KEY: ${{ secrets.HCLI_API_KEY }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + INPUT_VERSION: ${{ github.event.inputs.ida_version || github.event.client_payload.ida_version || 'latest' }} + INPUT_TAG: ${{ github.event.inputs.release_tag || github.event.client_payload.release_tag || 'latest' }} + shell: bash + run: | + # Resolve IDA version + if [ "$INPUT_VERSION" = "latest" ]; then + version=$(uvx --from ida-hcli hcli --disable-updates download --list-tags 2>/dev/null \ + | grep -oP 'ida-pro:\K[0-9]+\.[0-9]+(?=:)' \ + | sort -Vu \ + | awk -F. '$1 >= 9' \ + | tail -1) + if [ -z "$version" ]; then + echo "ERROR: Could not resolve latest IDA version from hcli" + exit 1 + fi + echo "Resolved IDA version 'latest' to $version" + else + version="$INPUT_VERSION" + # Verify the requested version exists + available=$(uvx --from ida-hcli hcli --disable-updates download --list-tags 2>/dev/null \ + | grep -oP 'ida-pro:\K[0-9]+\.[0-9]+(?=:)' \ + | sort -Vu) + if ! echo "$available" | grep -qx "$version"; then + echo "ERROR: IDA version $version not found. Available versions:" + echo "$available" + exit 1 + fi + fi + echo "ida_version=$version" >> "$GITHUB_OUTPUT" + + # Resolve release tag + if [ "$INPUT_TAG" = "none" ]; then + tag="none" + echo "Release: skipped" + elif [ "$INPUT_TAG" = "latest" ]; then + tag=$(gh release list --repo "${{ github.repository }}" --limit 1 --json tagName -q '.[0].tagName') + if [ -z "$tag" ]; then + echo "ERROR: No releases found in this repository" + exit 1 + fi + echo "Resolved release tag 'latest' to $tag" + else + tag="$INPUT_TAG" + # Verify the release exists + if ! gh release view "$tag" --repo "${{ github.repository }}" > /dev/null 2>&1; then + echo "ERROR: Release $tag not found" + exit 1 + fi + fi + echo "release_tag=$tag" >> "$GITHUB_OUTPUT" + + - name: Check if version already exists in release + if: steps.resolve.outputs.release_tag != 'none' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + RELEASE_TAG: ${{ steps.resolve.outputs.release_tag }} + IDA_VERSION: ${{ steps.resolve.outputs.ida_version }} + shell: bash + run: | + if gh release view "$RELEASE_TAG" --repo "${{ github.repository }}" --json assets -q '.assets[].name' \ + | grep -qx "bindiff-ida${IDA_VERSION}.zip"; then + echo "ERROR: bindiff-ida${IDA_VERSION}.zip already exists in release $RELEASE_TAG" + exit 1 + fi + + build: + needs: resolve + permissions: + contents: write + name: IDA ${{ needs.resolve.outputs.ida_version }} on ${{ matrix.os_name }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: + - ubuntu-latest + - windows-latest + - macos-latest + include: + - os: ubuntu-latest + os_name: "linux" + - os: windows-latest + os_name: "windows" + - os: macos-latest + os_name: "macos" + env: + IDA_VERSION: ${{ needs.resolve.outputs.ida_version }} + + steps: + - name: Setup MSBuild + if: matrix.os_name == 'windows' + uses: microsoft/setup-msbuild@v1.1 + + - name: Setup Visual Studio 2022 + if: matrix.os_name == 'windows' + uses: ilammy/msvc-dev-cmd@v1 + with: + vsversion: 2022 + + - name: Setup uv + uses: astral-sh/setup-uv@v5 + + - name: Download IDA SDK + shell: bash + env: + HCLI_API_KEY: ${{ secrets.HCLI_API_KEY }} + run: | + SDK_COMPACT=$(echo "$IDA_VERSION" | tr -d '.') + SDK_SLUG="release/${IDA_VERSION}/sdk-and-utilities/idasdk${SDK_COMPACT}.zip" + echo "Downloading SDK: $SDK_SLUG" + + uvx --from ida-hcli hcli --disable-updates download "$SDK_SLUG" + unzip idasdk*.zip -d ./ida-temp/ + + SDK_ROOT=$(dirname "$(find ./ida-temp/ -maxdepth 3 -name allmake.mak | head -1)") + mv "$SDK_ROOT" ./ida-sdk + + - name: Setup IDA SDK environment + if: matrix.os_name == 'windows' + working-directory: ./ida-sdk/ + shell: cmd + run: | + REM via: https://hex-rays.com/blog/building-ida-python-on-windows + set __EA64__=1 + set NDEBUG=1 + make env + + - name: Checkout bindiff + uses: actions/checkout@v4 + with: + fetch-depth: 0 + submodules: "recursive" + path: bindiff + ref: ${{ needs.resolve.outputs.release_tag != 'none' && needs.resolve.outputs.release_tag || '' }} + + - name: Setup CMake + uses: jwlawson/actions-setup-cmake@v2 + with: + cmake-version: '3.28.x' + + - name: Install Ninja + run: uv tool install ninja + + - name: Checkout BinExport + uses: actions/checkout@v4 + with: + repository: google/binexport + path: binexport + ref: main + + - name: Patch BinExport for IDA SDK 9.x on macOS + if: matrix.os_name == 'macos' + shell: bash + run: | + cd binexport + git apply --verbose ../bindiff/.github/patches/binexport-bdb8c4430549e69d4a9a7531c59b197f3a0757e6.patch + + - name: Configure BinDiff build + working-directory: ./bindiff/ + shell: bash + run: | + if [ "${{ matrix.os_name }}" = "macos" ]; then + CMAKE_EXTRA_FLAGS="-DCMAKE_OSX_ARCHITECTURES=arm64;x86_64" + else + CMAKE_EXTRA_FLAGS="" + fi + + cmake -S . -B ../build -G Ninja \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX=../install \ + -DBINDIFF_BINEXPORT_DIR=../binexport \ + "-DIdaSdk_ROOT_DIR=$PWD/../ida-sdk" \ + $CMAKE_EXTRA_FLAGS + + - name: Build BinDiff + shell: bash + run: cmake --build build --config Release --parallel 4 + + - name: Run tests + shell: bash + run: ctest --test-dir build --build-config Release --output-on-failure + + - name: Install BinDiff + shell: bash + run: cmake --install build --config Release + + - name: Collect artifacts + working-directory: ./bindiff/ + shell: bash + run: | + mkdir -p ../artifacts + cp -v ../install/bindiff-prefix/* ../artifacts/ + jq ".plugin.idaVersions = \"==${IDA_VERSION}\"" ida/ida-plugin.json > ../artifacts/ida-plugin.json + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: bindiff-ida${{ env.IDA_VERSION }}-${{ matrix.os_name }} + path: artifacts/* + if-no-files-found: error + + release: + if: needs.resolve.outputs.release_tag != 'none' + needs: [resolve, build] + runs-on: ubuntu-latest + permissions: + contents: write + env: + IDA_VERSION: ${{ needs.resolve.outputs.ida_version }} + RELEASE_TAG: ${{ needs.resolve.outputs.release_tag }} + steps: + - name: Download all artifacts + uses: actions/download-artifact@v4 + with: + path: artifacts + + - name: Create archive + shell: bash + run: | + mkdir -p stage + for dir in artifacts/bindiff-ida${IDA_VERSION}-*/; do + [ -d "$dir" ] && cp -v "$dir"/* stage/ + done + + rm -f stage/bindiff stage/bindiff.exe + rm -f stage/bindiff_config_setup* + rm -f stage/bindiff_launcher_macos + + cd stage + zip -r "../bindiff-ida${IDA_VERSION}.zip" * + + - name: Append to release + shell: bash + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh release upload "$RELEASE_TAG" "bindiff-ida${IDA_VERSION}.zip" \ + --repo "${{ github.repository }}" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000..f5839db8 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,229 @@ +name: build + +on: + push: + branches: [main, ci-gha] + tags: + - "v*" + pull_request: + branches: [main] + workflow_dispatch: + +permissions: read-all + +concurrency: + group: build-${{ github.ref }} + cancel-in-progress: true + +jobs: + discover: + runs-on: ubuntu-latest + outputs: + versions: ${{ steps.get-versions.outputs.versions }} + steps: + - name: Setup uv + uses: astral-sh/setup-uv@v6 + with: + ignore-empty-workdir: true + enable-cache: false + + - name: Discover available IDA versions + id: get-versions + env: + HCLI_API_KEY: ${{ secrets.HCLI_API_KEY }} + shell: bash + run: | + # List all available tags and extract unique IDA 9.x+ version numbers, + # excluding "latest", service packs (sp), and test builds. + versions=$(uvx --from ida-hcli hcli --disable-updates download --list-tags 2>/dev/null \ + | grep -oP 'ida-pro:\K[0-9]+\.[0-9]+(?=:)' \ + | sort -Vu \ + | awk -F. '$1 >= 9' \ + | jq -R -s -c 'split("\n") | map(select(length > 0))') + echo "versions=$versions" >> "$GITHUB_OUTPUT" + echo "Discovered IDA versions: $versions" + + build: + needs: discover + permissions: + contents: write + name: IDA ${{ matrix.ida_version }} on ${{ matrix.os_name }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + ida_version: ${{ fromJson(needs.discover.outputs.versions) }} + os: + - ubuntu-latest + - windows-latest + - macos-latest + include: + - os: ubuntu-latest + os_name: "linux" + - os: windows-latest + os_name: "windows" + - os: macos-latest + os_name: "macos" + + steps: + - name: Setup MSBuild + if: matrix.os_name == 'windows' + uses: microsoft/setup-msbuild@v1.1 + + - name: Setup Visual Studio 2022 + if: matrix.os_name == 'windows' + uses: ilammy/msvc-dev-cmd@v1 + with: + vsversion: 2022 + + - name: Setup uv + # on act, need use v5 instead of v6, due to: + # https://github.com/astral-sh/setup-uv/issues/535 + # (ultimately an issue with act images) + uses: astral-sh/setup-uv@v5 + + - name: Download IDA SDK ${{ matrix.ida_version }} + shell: bash + env: + HCLI_API_KEY: ${{ secrets.HCLI_API_KEY }} + run: | + SDK_COMPACT=$(echo "${{ matrix.ida_version }}" | tr -d '.') + SDK_SLUG="release/${{ matrix.ida_version }}/sdk-and-utilities/idasdk${SDK_COMPACT}.zip" + echo "Downloading SDK: $SDK_SLUG" + + uvx --from ida-hcli hcli --disable-updates download "$SDK_SLUG" + unzip idasdk*.zip -d ./ida-temp/ + + SDK_ROOT=$(dirname "$(find ./ida-temp/ -maxdepth 3 -name allmake.mak | head -1)") + mv "$SDK_ROOT" ./ida-sdk + + - name: Setup IDA SDK environment + if: matrix.os_name == 'windows' + working-directory: ./ida-sdk/ + shell: cmd + run: | + REM via: https://hex-rays.com/blog/building-ida-python-on-windows + set __EA64__=1 + set NDEBUG=1 + make env + + - name: Checkout bindiff + # on act, need use v4 instead of v5 due to node24 reference + uses: actions/checkout@v4 + with: + fetch-depth: 0 + submodules: "recursive" + path: bindiff + + - name: Setup CMake + uses: jwlawson/actions-setup-cmake@v2 + with: + cmake-version: '3.28.x' + + - name: Install Ninja + run: uv tool install ninja + + - name: Checkout BinExport + uses: actions/checkout@v4 + with: + repository: google/binexport + path: binexport + ref: main + + # There's a bug in BinExport's IDA 9+ support on macOS + # reported here: https://github.com/google/binexport/issues/159 + - name: Patch BinExport for IDA SDK 9.x on macOS + if: matrix.os_name == 'macos' + shell: bash + run: | + cd binexport + git apply --verbose ../bindiff/.github/patches/binexport-bdb8c4430549e69d4a9a7531c59b197f3a0757e6.patch + + - name: Configure BinDiff build + working-directory: ./bindiff/ + shell: bash + run: | + # Build universal binaries on macOS (both arm64 and x86_64) + if [ "${{ matrix.os_name }}" = "macos" ]; then + CMAKE_EXTRA_FLAGS="-DCMAKE_OSX_ARCHITECTURES=arm64;x86_64" + else + CMAKE_EXTRA_FLAGS="" + fi + + cmake -S . -B ../build -G Ninja \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX=../install \ + -DBINDIFF_BINEXPORT_DIR=../binexport \ + "-DIdaSdk_ROOT_DIR=$PWD/../ida-sdk" \ + $CMAKE_EXTRA_FLAGS + + - name: Build BinDiff + shell: bash + run: | + cmake --build build --config Release --parallel 4 + + - name: Run tests + shell: bash + run: ctest --test-dir build --build-config Release --output-on-failure + + - name: Install BinDiff + shell: bash + run: cmake --install build --config Release + + - name: Collect artifacts + working-directory: ./bindiff/ + shell: bash + run: | + mkdir -p ../artifacts + cp -v ../install/bindiff-prefix/* ../artifacts/ + jq '.plugin.idaVersions = "==${{ matrix.ida_version }}"' ida/ida-plugin.json > ../artifacts/ida-plugin.json + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: bindiff-ida${{ matrix.ida_version }}-${{ matrix.os_name }} + path: artifacts/* + if-no-files-found: error + + release: + if: startsWith(github.ref, 'refs/tags/v') + needs: [discover, build] + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Download all artifacts + uses: actions/download-artifact@v4 + with: + path: artifacts + + - name: Create per-version archives + shell: bash + run: | + versions='${{ needs.discover.outputs.versions }}' + for version in $(echo "$versions" | jq -r '.[]'); do + echo "Creating archive for IDA $version..." + mkdir -p "stage-${version}" + + # Merge artifacts from all OSes for this version + for dir in artifacts/bindiff-ida${version}-*/; do + [ -d "$dir" ] && cp -v "$dir"/* "stage-${version}/" + done + + # Remove non-plugin files + rm -f "stage-${version}/bindiff" "stage-${version}/bindiff.exe" + rm -f "stage-${version}"/bindiff_config_setup* + rm -f "stage-${version}"/bindiff_launcher_macos + + cd "stage-${version}" + zip -r "../bindiff-ida${version}.zip" * + cd .. + done + + - name: Upload release assets + uses: softprops/action-gh-release@v2 + with: + files: bindiff-ida*.zip + fail_on_unmatched_files: false + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml deleted file mode 100644 index ca9ffeab..00000000 --- a/.github/workflows/cmake.yml +++ /dev/null @@ -1,78 +0,0 @@ -name: cmake-build - -on: [push, pull_request] - -env: - BUILD_TYPE: Release - -jobs: - build: - strategy: - fail-fast: false - matrix: - include: - - os: ubuntu-22.04 - - os: macos-12 - - os: windows-2022 - runs-on: ${{ matrix.os }} - - steps: - - uses: actions/checkout@v4 - with: - path: bindiff - - uses: actions/checkout@v4 - with: - repository: google/binexport - path: binexport - - - name: Install ninja-build tool - uses: turtlesec-no/get-ninja@1.1.0 - - - name: Create Build Environment - env: - IDASDK_SECRET: ${{ secrets.IDASDK_SECRET }} - working-directory: ${{ github.workspace }} - shell: bash - run: | - mkdir -p "${{ runner.workspace }}/build" \ - ida/idasdk - mv binexport/ida/idasdk/idasdk*.gpg \ - ida/idasdk/ - binexport/.github/scripts/decrypt_secret.sh - unzip -q "${{ runner.workspace }}/build/idasdk_teams82.zip" \ - -d "${{ runner.workspace }}/build/" - - - name: Enable Developer Command Prompt (Windows) - if: matrix.os == 'windows-2022' - uses: ilammy/msvc-dev-cmd@v1.12.1 - - - name: Enable mold linker (Linux) - if: matrix.os == 'ubuntu-22.04' - uses: rui314/setup-mold@v1 - - - name: Configure CMake - working-directory: ${{ runner.workspace }}/build - shell: bash - run: | - cmake "${{ github.workspace }}/bindiff" -G Ninja \ - "-DCMAKE_BUILD_TYPE=${BUILD_TYPE}" \ - "-DIdaSdk_ROOT_DIR=${{ runner.workspace }}/build/idasdk_teams82" - - - name: Build - working-directory: ${{ runner.workspace }}/build - shell: bash - run: cmake --build . --config "${BUILD_TYPE}" - - - name: Test - working-directory: ${{ runner.workspace }}/build - shell: bash - run: ctest --build-config "${BUILD_TYPE}" --output-on-failure -R '^[A-Z]' - - - name: Upload Build Artifacts - uses: actions/upload-artifact@v3.1.2 - with: - name: BinDiff-${{ runner.os }} - path: | - ${{ runner.workspace }}/build/bindiff - ${{ runner.workspace }}/build/ida/bindiff* - ${{ runner.workspace }}/build/tools/bindiff* diff --git a/ida/ida-plugin.json b/ida/ida-plugin.json new file mode 100644 index 00000000..0fc6ec79 --- /dev/null +++ b/ida/ida-plugin.json @@ -0,0 +1,47 @@ +{ + "IDAMetadataDescriptorVersion": 1, + "plugin": { + "name": "bindiff", + "entryPoint": "bindiff8_ida64", + "version": "8.0.0", + "idaVersions": ["9.0", "9.1", "9.2"], + "platforms": [ + "windows-x86_64", + "linux-x86_64", + "macos-x86_64", + "macos-aarch64" + ], + "description": "Compare binary files to quickly find differences and similarities in disassembled code", + "license": "Apache-2.0", + "categories": [ + "integration-with-third-parties-interoperability", + "collaboration-and-productivity" + ], + "keywords": [ + "bindiff", + "binary-diffing", + "comparison", + "vulnerability-research", + "patch-analysis" + ], + "urls": { + "repository": "https://github.com/HexRays-plugin-contributions/bindiff" + }, + "authors": [ + { + "name": "Christian Blichmann", + "email": "cblichmann@google.com" + }, + { + "name": "BinDiff Authors", + "email": "bindiff-team@google.com" + } + ], + "maintainers": [ + { + "name": "Willi Ballenthin", + "email": "wballenthin@hex-rays.com" + } + ] + } +}