diff --git a/README.md b/README.md index dd922d9..f21f04b 100644 --- a/README.md +++ b/README.md @@ -24,3 +24,5 @@ This repository is intended to be consumed from a parent repository as a shared - The parent project owns the compiler toolchain and top-level CMake configuration. - Module tests are enabled by the parent through `C2CORE_BUILD_TESTS`. +- Functional, env-driven module tests are enabled through `C2CORE_BUILD_FUNCTIONAL_TESTS`. +- Test strategy, flags, and ready-to-run commands are documented in [TESTING.md](/mnt/e/Dev/C2Implant/core/TESTING.md). diff --git a/TESTING.md b/TESTING.md new file mode 100644 index 0000000..a866e6e --- /dev/null +++ b/TESTING.md @@ -0,0 +1,361 @@ +# C2Core Testing + +## Scope + +`C2Core` is shared by multiple parent projects across Windows and Linux. The test strategy separates: + +- unit and local integration tests that must be CI-safe and autonomous +- functional tests that exercise environment-dependent features without polluting standard CI + +This file documents the current test logic, the relevant CMake flags, and the commands used in practice on Windows x64 and x86. + +## CMake Flags + +- `BUILD_TEAMSERVER` + Enables teamserver-side parsing/help paths inside modules. This is for production builds that need CLI parsing and help text on the server side. + +- `C2CORE_BUILD_TESTS` + Enables the standard C2Core test targets used by CI. These tests must stay local, deterministic, and not depend on lab infrastructure. + +- `C2CORE_BUILD_FUNCTIONAL_TESTS` + Enables manual or env-driven functional tests. These tests are intended for operator-driven validation of modules that need credentials, network reachability, remote hosts, Kerberos tickets, or specific Windows services. + +`enable_testing()` is active when either `C2CORE_BUILD_TESTS` or `C2CORE_BUILD_FUNCTIONAL_TESTS` is enabled. + +## Test Categories + +### Standard tests + +These are the `tests` targets. + +- compiled with `C2CORE_BUILD_TESTS=ON` +- expected to run in CI +- should complete quickly +- should not require manual input +- should not require external infrastructure + +For modules that are hard to execute for real in CI, the unit tests should still validate: + +- command-line parsing +- packing and unpacking of `C2Message` +- error mapping through `errorCodeToMsg()` +- local guard rails before any network or remote execution is attempted + +### Functional tests + +These are the `testsFunctional` targets. + +- compiled with `C2CORE_BUILD_FUNCTIONAL_TESTS=ON` +- intended for manual runs, lab validation, or env-driven automation +- should skip cleanly when configuration is missing +- should not run remote actions unless `--execute` is provided + +Current functional tests are registered with `SKIP_RETURN_CODE 77`, so an unconfigured lab does not make CTest fail. + +## Functional Test Helper Contract + +`core/modules/tests/FunctionalTestHelpers.hpp` provides the shared behavior. + +- `--help` + Prints accepted options and environment variables. + +- `--interactive` + Prompts for missing values on stdin. + +- `--execute` + Actually calls the module `process()` path. + +Without `--execute`, a functional test validates only: + +- input collection +- command construction +- `init()` +- `C2Message` packing + +With `--execute`, the helper treats the run as failed only when `result.errorCode() > 0`. + +This is important because many modules keep the default `errorCode == -1` on success. + +## Current Functional Test Modules + +Current module-level functional tests: + +- `testsCimExecFunctional` +- `testsDcomExecFunctional` +- `testsEnumerateRdpSessionsFunctional` +- `testsSpawnAsFunctional` +- `testsSshExecFunctional` +- `testsTaskSchedulerFunctional` +- `testsWinRMFunctional` +- `testsWmiExecFunctional` + +Current transport-level functional tests: + +- listener functional tests +- beacon functional tests + +Use `ctest -L functional` to discover them from a configured build tree. + +## Windows Build Commands + +### Configure x64 functional build + +From PowerShell: + +```powershell +& 'C:\Program Files\Microsoft Visual Studio\2022\Community\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin\cmake.exe' ` + -S 'E:\Dev\C2Implant' ` + -B 'E:\Dev\C2Implant\buildx64-functional' ` + -G 'Visual Studio 17 2022' ` + -A x64 ` + -DC2CORE_BUILD_TESTS=ON ` + -DC2CORE_BUILD_FUNCTIONAL_TESTS=ON ` + -DLibssh2_DIR='E:/Dev/C2Implant/buildx64/conan/build/generators' ` + -Dnlohmann_json_DIR='E:/Dev/C2Implant/buildx64/conan/build/generators' +``` + +### Configure x86 functional build + +```powershell +& 'C:\Program Files\Microsoft Visual Studio\2022\Community\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin\cmake.exe' ` + -S 'E:\Dev\C2Implant' ` + -B 'E:\Dev\C2Implant\buildx86-functional' ` + -G 'Visual Studio 17 2022' ` + -A Win32 ` + -DC2CORE_BUILD_TESTS=ON ` + -DC2CORE_BUILD_FUNCTIONAL_TESTS=ON ` + -DLibssh2_DIR='E:/Dev/C2Implant/buildx86/conan/build/generators' ` + -Dnlohmann_json_DIR='E:/Dev/C2Implant/buildx86/conan/build/generators' +``` + +### Build selected test targets + +```powershell +& 'C:\Program Files\Microsoft Visual Studio\2022\Community\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin\cmake.exe' ` + --build 'E:\Dev\C2Implant\buildx64-functional' ` + --config Release ` + --target testsSpawnAs testsSshExec testsWmiExec testsSpawnAsFunctional testsSshExecFunctional testsWmiExecFunctional +``` + +```powershell +& 'C:\Program Files\Microsoft Visual Studio\2022\Community\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin\cmake.exe' ` + --build 'E:\Dev\C2Implant\buildx86-functional' ` + --config Release ` + --target testsSpawnAs testsSshExec testsWmiExec testsSpawnAsFunctional testsSshExecFunctional testsWmiExecFunctional +``` + +## Standard CTest Commands + +### Run one unit test + +```powershell +Set-Location 'E:\Dev\C2Implant\buildx64' +& 'C:\Program Files\Microsoft Visual Studio\2022\Community\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin\ctest.exe' ` + -C Release ` + -R testsSshExec ` + --timeout 10 ` + --output-on-failure +``` + +### Run a small group of unit tests + +```powershell +Set-Location 'E:\Dev\C2Implant\buildx64' +& 'C:\Program Files\Microsoft Visual Studio\2022\Community\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin\ctest.exe' ` + -C Release ` + -R 'tests(SpawnAs|SshExec|WmiExec)' ` + --timeout 10 ` + --output-on-failure +``` + +### Run all functional tests + +```powershell +Set-Location 'E:\Dev\C2Implant\buildx64-functional' +& 'C:\Program Files\Microsoft Visual Studio\2022\Community\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin\ctest.exe' ` + -C Release ` + -L functional ` + --timeout 10 ` + --output-on-failure +``` + +### Run only functional tests for remote execution modules + +```powershell +Set-Location 'E:\Dev\C2Implant\buildx64-functional' +& 'C:\Program Files\Microsoft Visual Studio\2022\Community\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin\ctest.exe' ` + -C Release ` + -R 'tests(SpawnAs|SshExec|WmiExec)Functional' ` + --timeout 10 ` + --output-on-failure +``` + +If no environment is configured, these tests should be reported as skipped rather than failed. + +## Direct Functional Test Invocation + +The binaries are copied under `${build}/tests`. + +Examples on x64: + +```powershell +E:\Dev\C2Implant\buildx64-functional\tests\testsSshExecFunctional.exe --help +E:\Dev\C2Implant\buildx64-functional\tests\testsWmiExecFunctional.exe --help +E:\Dev\C2Implant\buildx64-functional\tests\testsSpawnAsFunctional.exe --help +``` + +### Dry-run packing validation + +This validates parsing and `init()` only: + +```powershell +E:\Dev\C2Implant\buildx64-functional\tests\testsSshExecFunctional.exe ` + --host 10.10.10.20 ` + --port 22 ` + --user alice ` + --password secret ` + --command "whoami" +``` + +### Interactive mode + +```powershell +E:\Dev\C2Implant\buildx64-functional\tests\testsWmiExecFunctional.exe --interactive +``` + +### Real execution + +Only this form runs the remote or privileged action: + +```powershell +E:\Dev\C2Implant\buildx64-functional\tests\testsSshExecFunctional.exe ` + --execute ` + --host 10.10.10.20 ` + --user alice ` + --password secret ` + --command "whoami" +``` + +## Environment Variables For Functional Tests + +### SpawnAs + +- `C2_FUNC_SPAWNAS_USER` +- `C2_FUNC_SPAWNAS_PASSWORD` +- `C2_FUNC_SPAWNAS_COMMAND` +- `C2_FUNC_SPAWNAS_DOMAIN` +- `C2_FUNC_SPAWNAS_LOGON_TYPE` +- `C2_FUNC_SPAWNAS_PROFILE` +- `C2_FUNC_SPAWNAS_SHOW_WINDOW` + +Example: + +```powershell +$env:C2_FUNC_SPAWNAS_USER='DOMAIN\alice' +$env:C2_FUNC_SPAWNAS_PASSWORD='secret' +$env:C2_FUNC_SPAWNAS_COMMAND='cmd.exe /c whoami' +E:\Dev\C2Implant\buildx64-functional\tests\testsSpawnAsFunctional.exe --execute +``` + +Notes: + +- logon type `2` is the default interactive mode +- logon type `9` maps to `--netonly` +- `with-profile` and `no-profile` control profile loading +- this test is Windows-only for real execution + +### SshExec + +- `C2_FUNC_SSH_HOST` +- `C2_FUNC_SSH_PORT` +- `C2_FUNC_SSH_USER` +- `C2_FUNC_SSH_PASSWORD` +- `C2_FUNC_SSH_COMMAND` + +Example: + +```powershell +$env:C2_FUNC_SSH_HOST='10.10.10.20' +$env:C2_FUNC_SSH_PORT='22' +$env:C2_FUNC_SSH_USER='alice' +$env:C2_FUNC_SSH_PASSWORD='secret' +$env:C2_FUNC_SSH_COMMAND='whoami' +E:\Dev\C2Implant\buildx64-functional\tests\testsSshExecFunctional.exe --execute +``` + +### WmiExec + +- `C2_FUNC_WMI_AUTH` +- `C2_FUNC_WMI_TARGET` +- `C2_FUNC_WMI_COMMAND` +- `C2_FUNC_WMI_USER` +- `C2_FUNC_WMI_PASSWORD` +- `C2_FUNC_WMI_DC` + +Example NTLM or user/password flow: + +```powershell +$env:C2_FUNC_WMI_AUTH='userpass' +$env:C2_FUNC_WMI_TARGET='server01' +$env:C2_FUNC_WMI_COMMAND='cmd.exe /c whoami' +$env:C2_FUNC_WMI_USER='DOMAIN\alice' +$env:C2_FUNC_WMI_PASSWORD='secret' +E:\Dev\C2Implant\buildx64-functional\tests\testsWmiExecFunctional.exe --execute +``` + +Example Kerberos flow: + +```powershell +$env:C2_FUNC_WMI_AUTH='kerberos' +$env:C2_FUNC_WMI_TARGET='server01' +$env:C2_FUNC_WMI_COMMAND='cmd.exe /c hostname' +$env:C2_FUNC_WMI_DC='DOMAIN\dc01' +E:\Dev\C2Implant\buildx64-functional\tests\testsWmiExecFunctional.exe --execute +``` + +Notes: + +- `no-cred` maps to `wmiExec -n` +- `userpass` maps to `wmiExec -u` +- `kerberos` maps to `wmiExec -k` +- this test is Windows-only for real execution + +## Module-Specific Intent + +### SpawnAs + +The unit test should stay focused on parsing and packing. The functional test is where real credentialed process creation is exercised because it depends on: + +- local Windows privileges +- local or domain credentials +- profile loading behavior +- logon type selection + +### SshExec + +The unit test should validate local parameter handling and local failure paths. The functional test is the place for real SSH coverage because it depends on: + +- reachable host +- valid credentials +- server-side shell behavior + +### WmiExec + +The unit test should validate packing and error mapping. The functional test is required for meaningful execution coverage because it depends on: + +- COM and WMI availability +- remote host state +- firewall and service exposure +- credential mode or Kerberos ticket state + +## CI Guidance + +Standard CI should keep using `C2CORE_BUILD_TESTS=ON` only. + +`C2CORE_BUILD_FUNCTIONAL_TESTS=ON` is appropriate for: + +- operator-driven validation +- dedicated lab jobs +- manual pre-release verification + +Do not make the default CI pipeline depend on environment-specific functional tests.