diff --git a/.github/workflows/Release.yml b/.github/workflows/Release.yml index c3c86f3..f4a4346 100644 --- a/.github/workflows/Release.yml +++ b/.github/workflows/Release.yml @@ -1,69 +1,186 @@ -name: Release +name: CI CD -on: +on: workflow_dispatch: + pull_request: push: + branches: + - "**" tags: - - '*' + - "*" env: - # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_DIR: build-conan BUILD_TYPE: Release + RELEASE_TARBALL: Release.tar.gz + RELEASE_ARTIFACT: C2LinuxImplant-Linux-Release permissions: - contents: write + contents: read jobs: - buildAndRelease: - # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. - # You can convert this to a matrix build if you need cross-platform coverage. - # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix - # compile for old libc to cover more linux versions + build-test: + name: Build and test Linux deliverables runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v4 + - name: Checkout + uses: actions/checkout@v4 + with: + submodules: recursive - - name: Install Samba client dev headers - run: | - sudo apt-get update - sudo apt-get install -y libsmbclient-dev + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.x" - # Update references - - name: Git Sumbodule Update - run: | - git submodule update --init - - - name: Get Conan - # You may pin to the exact commit or the version. - # uses: turtlebrowser/get-conan@c171f295f3f507360ee018736a6608731aa2109d - uses: turtlebrowser/get-conan@v1.2 + - name: Install Linux dependencies + run: | + sudo apt-get update + sudo apt-get install -y libsmbclient-dev - - name: Create default profile - run: conan profile detect - - - name: Configure CMake - run: cmake -B ${{github.workspace}}/build -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES=${{github.workspace}}/conan_provider.cmake + - name: Install Conan + run: | + python -m pip install --upgrade pip + python -m pip install "conan>=2,<3" + conan profile detect --force - - name: Build - run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} -j 18 + - name: Cache Conan packages + uses: actions/cache@v4 + with: + path: ~/.conan2 + key: conan-${{ runner.os }}-${{ hashFiles('conanfile.txt') }} + restore-keys: | + conan-${{ runner.os }}- - - name: Prep release - shell: bash - run: | - rm ./Release/Beacons/.gitignore - mv ./Release/Beacons ./Release/LinuxBeacons - rm ./Release/Modules/.gitignore - mv ./Release/Modules ./Release/LinuxModules - tar -zcvf Release.tar.gz Release + - name: Configure + run: | + cmake \ + -S . \ + -B "$BUILD_DIR" \ + -DCMAKE_BUILD_TYPE="$BUILD_TYPE" \ + -DC2CORE_BUILD_TESTS=ON \ + -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES="$GITHUB_WORKSPACE/conan_provider.cmake" - - name: Upload release - uses: svenstaro/upload-release-action@v2 - with: - repo_token: ${{ secrets.GITHUB_TOKEN }} - file: Release.tar.gz - asset_name: Release.tar.gz - tag: ${{ github.ref }} - overwrite: true - body: "Linux beacons and modules" + - name: Build + run: cmake --build "$BUILD_DIR" --config "$BUILD_TYPE" --parallel "$(nproc)" + - name: Test + run: ctest --test-dir "$BUILD_DIR" --output-on-failure --timeout 60 + + - name: Validate deliverables + run: | + set -euo pipefail + + expected_beacons=( + BeaconDns + BeaconGithub + BeaconHttp + BeaconSmb + BeaconTcp + ) + + expected_modules=( + libAssemblyExec.so + libCat.so + libChangeDirectory.so + libChisel.so + libCimExec.so + libCoff.so + libDcomExec.so + libDotnetExec.so + libDownload.so + libEnumerateRdpSessions.so + libEnumerateShares.so + libEvasion.so + libGetEnv.so + libInject.so + libIpConfig.so + libKerberosUseTicket.so + libKeyLogger.so + libKillProcess.so + libListDirectory.so + libListProcesses.so + libMakeToken.so + libMiniDump.so + libMkDir.so + libNetstat.so + libPowershell.so + libPrintWorkingDirectory.so + libPsExec.so + libPwSh.so + libRegistry.so + libRemove.so + libRev2self.so + libRun.so + libScreenShot.so + libScript.so + libShell.so + libSpawnAs.so + libSshExec.so + libStealToken.so + libTaskScheduler.so + libTree.so + libUpload.so + libWhoami.so + libWinRM.so + libWmiExec.so + ) + + for file in "${expected_beacons[@]}"; do + test -s "Release/Beacons/$file" || { + echo "Missing or empty beacon deliverable: $file" >&2 + exit 1 + } + done + + for file in "${expected_modules[@]}"; do + test -s "Release/Modules/$file" || { + echo "Missing or empty module deliverable: $file" >&2 + exit 1 + } + done + + - name: Stage release archive + run: | + set -euo pipefail + stage="artifacts/Release" + + rm -rf artifacts "$RELEASE_TARBALL" + mkdir -p "$stage/LinuxBeacons" "$stage/LinuxModules" + + find Release/Beacons -maxdepth 1 -type f ! -name ".gitignore" -exec cp -t "$stage/LinuxBeacons" {} + + find Release/Modules -maxdepth 1 -type f ! -name ".gitignore" -exec cp -t "$stage/LinuxModules" {} + + + tar -C artifacts -czf "$RELEASE_TARBALL" Release + + - name: Upload CI artifact + uses: actions/upload-artifact@v4 + with: + name: ${{ env.RELEASE_ARTIFACT }} + path: ${{ env.RELEASE_TARBALL }} + if-no-files-found: error + + release: + name: Publish GitHub release + runs-on: ubuntu-22.04 + needs: build-test + if: startsWith(github.ref, 'refs/tags/') + permissions: + contents: write + + steps: + - name: Download release archive + uses: actions/download-artifact@v4 + with: + name: ${{ env.RELEASE_ARTIFACT }} + + - name: Upload release + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: ${{ env.RELEASE_TARBALL }} + asset_name: ${{ env.RELEASE_TARBALL }} + tag: ${{ github.ref }} + overwrite: true + body: "Linux beacons and modules" diff --git a/AGENT.md b/AGENT.md new file mode 100644 index 0000000..8b7201e --- /dev/null +++ b/AGENT.md @@ -0,0 +1,33 @@ +# AGENT.md - C2LinuxImplant + +## CI/CD Contract + +- CI runs on `pull_request` and every branch push, including tag pushes before release publication. +- CD publishes only from tag builds after the CI build, tests, deliverable validation, and archive staging have succeeded. +- GitHub Actions permissions stay read-only by default. Only the release publication job may request `contents: write`. + +## Build and Test Contract + +- Linux CI builds with Conan through `conan_provider.cmake`. +- The CI configure step must pass `-DC2CORE_BUILD_TESTS=ON`. +- Tests must be visible from the top-level build directory and run with `ctest --test-dir --output-on-failure --timeout 60`. +- Do not silently skip unstable tests. Fix them when reasonable, or isolate them explicitly with a documented reason. +- `libs/libDns/tests/fonctionalTest` is intentionally not registered in CI because it is a manual server/client harness and exits with usage information unless runtime arguments are provided. + +## Release Layout + +C2TeamServer consumes Linux release payloads from the archive layout below: + +```text +Release/ + LinuxBeacons/ + BeaconDns + BeaconGithub + BeaconHttp + BeaconSmb + BeaconTcp + LinuxModules/ + lib*.so +``` + +The CMake build may copy intermediate deliverables into `Release/Beacons` and `Release/Modules`, but packaging must not rename, delete, or mutate those source output directories. Stage archives from a clean `artifacts/Release` tree instead. diff --git a/CMakeLists.txt b/CMakeLists.txt index 3a6b309..cda361f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,6 +8,9 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_POSITION_INDEPENDENT_CODE ON) option(C2CORE_BUILD_TESTS "Enable C2Core tests from the parent project" OFF) +if(C2CORE_BUILD_TESTS) + enable_testing() +endif() set(C2_RUNTIME_MODULE_OUTPUT_DIR "${CMAKE_SOURCE_DIR}/Release/Modules" CACHE PATH "Directory where C2Core runtime modules are copied after build") @@ -28,9 +31,12 @@ add_definitions(-DBUILD_IMPLANT) set(CMAKE_PREFIX_PATH ${CMAKE_BINARY_DIR}) find_package(OpenSSL REQUIRED) -find_package(httplib REQUIRED) -find_package(Libssh2 REQUIRED) +find_package(httplib REQUIRED) +find_package(Libssh2 REQUIRED) find_package(nlohmann_json REQUIRED CONFIG) +if(C2CORE_BUILD_TESTS) + find_package(Catch2 REQUIRED CONFIG) +endif() include_directories(${CMAKE_INCLUDE_PATH}) @@ -46,9 +52,20 @@ endif() include_directories(thirdParty) -add_subdirectory(libs) - -add_subdirectory(thirdParty) +add_subdirectory(libs) +if(C2CORE_BUILD_TESTS) + add_test(NAME dnsTest COMMAND dnsTest) + add_test(NAME utilsTest COMMAND utilsTest) + # fonctionalTest is a manual server/client harness that requires runtime args. + # Keep it built, but do not register it in CI ctest without explicit parameters. + add_test(NAME responseDecodeTest COMMAND responseDecodeTest) + add_test(NAME messageTest COMMAND messageTest) + add_test(NAME interleavedTest COMMAND interleavedTest) + add_test(NAME TestsSockerHandler COMMAND TestsSockerHandler) + add_test(NAME TestsSocksServer COMMAND TestsSocksServer) +endif() + +add_subdirectory(thirdParty) include_directories(thirdParty/base64) include_directories(thirdParty/donut/include) @@ -58,10 +75,14 @@ set_target_properties(Donut PROPERTIES IMPORTED_LOCATION "${DONUT_BUILD_DIR}/lib/libdonut.a" ) -add_subdirectory(core/modules) -add_subdirectory(core/beacon) - -include_directories(core/listener) +add_subdirectory(core/modules) +add_subdirectory(core/beacon) +if(C2CORE_BUILD_TESTS) + add_subdirectory(core/beacon/tests) + add_subdirectory(core/listener/tests) +endif() + +include_directories(core/listener) include_directories(core/beacon) include_directories(core/modules/ModuleCmd) add_subdirectory(beacon/beacon) diff --git a/README.md b/README.md index 20087a7..ad9344b 100644 --- a/README.md +++ b/README.md @@ -87,3 +87,23 @@ make -j4 * Compiled Beacons: `Release/Beacons` * Compiled Modules: `Release/Modules` + +### CI/CD Release Contract + +CI runs on pull requests and branch pushes. It installs Conan, configures a Release Linux build with `C2CORE_BUILD_TESTS=ON`, builds all Linux beacons/modules, runs `ctest --output-on-failure --timeout 60`, and validates every expected deliverable before archiving. + +Release publication runs only from tag builds after CI succeeds. The archive is staged from a clean `artifacts/Release` tree and uses the C2TeamServer-facing layout: + +```text +Release/ + LinuxBeacons/ + BeaconDns + BeaconGithub + BeaconHttp + BeaconSmb + BeaconTcp + LinuxModules/ + lib*.so +``` + +Packaging must not rename, delete, or mutate the CMake output directories `Release/Beacons` and `Release/Modules`; it only copies their non-`.gitignore` contents into staging. diff --git a/conanfile.txt b/conanfile.txt index bd88010..f0acb74 100644 --- a/conanfile.txt +++ b/conanfile.txt @@ -3,6 +3,7 @@ openssl/3.6.2 cpp-httplib/0.39.0 libssh2/1.11.1 nlohmann_json/3.12.0 +catch2/3.14.0 [options] libssh2/*:shared=False @@ -11,4 +12,4 @@ libssh2/*:shared=False cmake_layout [generators] -CMakeDeps \ No newline at end of file +CMakeDeps diff --git a/core b/core index 04fc881..35fe79d 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit 04fc881a07d79d947e8e606ca21a4a77cc39f093 +Subproject commit 35fe79d44227abb6239beaf49c847506841b5c29