Files
2026-04-30 13:49:51 +02:00

420 lines
11 KiB
Markdown

# 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<Module>` 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 `tests<Module>Functional` 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:
- `testsAssemblyExecFunctional`
- `testsCimExecFunctional`
- `testsDcomExecFunctional`
- `testsDotnetExecFunctional`
- `testsEvasionFunctional`
- `testsEnumerateRdpSessionsFunctional`
- `testsInjectFunctional`
- `testsPsExecFunctional`
- `testsPwShFunctional`
- `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
### AssemblyExec
- `C2_FUNC_ASSEMBLYEXEC_PAYLOAD`
- `C2_FUNC_ASSEMBLYEXEC_KIND`
- `C2_FUNC_ASSEMBLYEXEC_MODE`
- `C2_FUNC_ASSEMBLYEXEC_METHOD`
- `C2_FUNC_ASSEMBLYEXEC_ARGS`
- `C2_FUNC_ASSEMBLYEXEC_PROCESS`
- `C2_FUNC_ASSEMBLYEXEC_SPOOFED_PARENT`
### DotnetExec
- `C2_FUNC_DOTNETEXEC_ASSEMBLY`
- `C2_FUNC_DOTNETEXEC_KIND`
- `C2_FUNC_DOTNETEXEC_NAME`
- `C2_FUNC_DOTNETEXEC_TYPE`
- `C2_FUNC_DOTNETEXEC_METHOD`
- `C2_FUNC_DOTNETEXEC_ARGS`
### Evasion
- `C2_FUNC_EVASION_ACTION`
- `C2_FUNC_EVASION_VALUE`
- `C2_FUNC_EVASION_EXTRA`
### Inject
- `C2_FUNC_INJECT_PAYLOAD`
- `C2_FUNC_INJECT_KIND`
- `C2_FUNC_INJECT_PID`
- `C2_FUNC_INJECT_METHOD`
- `C2_FUNC_INJECT_ARGS`
- `C2_FUNC_INJECT_PROCESS`
- `C2_FUNC_INJECT_SYSCALL`
### PsExec
- `C2_FUNC_PSEXEC_AUTH`
- `C2_FUNC_PSEXEC_TARGET`
- `C2_FUNC_PSEXEC_SERVICE`
- `C2_FUNC_PSEXEC_USER`
- `C2_FUNC_PSEXEC_PASSWORD`
### PwSh
- `C2_FUNC_PWSH_MODE`
- `C2_FUNC_PWSH_RUNNER`
- `C2_FUNC_PWSH_TYPE`
- `C2_FUNC_PWSH_COMMAND`
- `C2_FUNC_PWSH_IMPORT`
- `C2_FUNC_PWSH_SCRIPT`
## 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.