mirror of
https://github.com/maxDcb/C2Core
synced 2026-06-08 15:48:01 +00:00
Functional tests
This commit is contained in:
+58
@@ -80,9 +80,15 @@ This is important because many modules keep the default `errorCode == -1` on suc
|
||||
|
||||
Current module-level functional tests:
|
||||
|
||||
- `testsAssemblyExecFunctional`
|
||||
- `testsCimExecFunctional`
|
||||
- `testsDcomExecFunctional`
|
||||
- `testsDotnetExecFunctional`
|
||||
- `testsEvasionFunctional`
|
||||
- `testsEnumerateRdpSessionsFunctional`
|
||||
- `testsInjectFunctional`
|
||||
- `testsPsExecFunctional`
|
||||
- `testsPwShFunctional`
|
||||
- `testsSpawnAsFunctional`
|
||||
- `testsSshExecFunctional`
|
||||
- `testsTaskSchedulerFunctional`
|
||||
@@ -320,6 +326,58 @@ Notes:
|
||||
- `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
|
||||
|
||||
@@ -109,7 +109,7 @@ std::string AssemblyExec::getInfo()
|
||||
|
||||
int AssemblyExec::init(std::vector<std::string> &splitedCmd, C2Message &c2Message)
|
||||
{
|
||||
#if defined(BUILD_TEAMSERVER) || defined(C2CORE_BUILD_TESTS)
|
||||
#if defined(BUILD_TEAMSERVER) || defined(C2CORE_BUILD_TESTS) || defined(C2CORE_BUILD_FUNCTIONAL_TESTS)
|
||||
if(splitedCmd.size() == 2)
|
||||
{
|
||||
if(splitedCmd[1]=="thread")
|
||||
@@ -263,7 +263,7 @@ int AssemblyExec::init(std::vector<std::string> &splitedCmd, C2Message &c2Messag
|
||||
|
||||
int AssemblyExec::initConfig(const nlohmann::json &config)
|
||||
{
|
||||
#if defined(BUILD_TEAMSERVER) || defined(C2CORE_BUILD_TESTS)
|
||||
#if defined(BUILD_TEAMSERVER) || defined(C2CORE_BUILD_TESTS) || defined(C2CORE_BUILD_FUNCTIONAL_TESTS)
|
||||
for (auto& it : config.items())
|
||||
{
|
||||
if(it.key()=="process")
|
||||
|
||||
@@ -39,7 +39,7 @@ endif()
|
||||
|
||||
set_property(TARGET AssemblyExec PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded")
|
||||
|
||||
if(C2CORE_BUILD_TESTS)
|
||||
if(C2CORE_BUILD_TESTS OR C2CORE_BUILD_FUNCTIONAL_TESTS)
|
||||
target_link_libraries(AssemblyExec PRIVATE Donut )
|
||||
endif()
|
||||
c2core_disable_safeseh_for_x86(AssemblyExec)
|
||||
@@ -47,10 +47,14 @@ c2core_disable_safeseh_for_x86(AssemblyExec)
|
||||
add_custom_command(TARGET AssemblyExec POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy
|
||||
$<TARGET_FILE:AssemblyExec> "${C2_RUNTIME_MODULE_OUTPUT_DIR}/$<TARGET_FILE_NAME:AssemblyExec>")
|
||||
|
||||
if(C2CORE_BUILD_TESTS)
|
||||
if(C2CORE_BUILD_TESTS OR C2CORE_BUILD_FUNCTIONAL_TESTS)
|
||||
if(WIN32)
|
||||
add_executable(assemblyExecDummyExe tests/dummyAssemblyExec.cpp)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(C2CORE_BUILD_TESTS)
|
||||
if(WIN32)
|
||||
if (CMAKE_GENERATOR_PLATFORM STREQUAL "ARM64" OR CMAKE_SYSTEM_PROCESSOR MATCHES "^(ARM64|arm64|aarch64)$")
|
||||
add_executable(testsAssemblyExec
|
||||
tests/testsAssemblyExec.cpp
|
||||
@@ -100,6 +104,53 @@ if(C2CORE_BUILD_TESTS)
|
||||
|
||||
add_custom_command(TARGET testOutputWriter POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy
|
||||
$<TARGET_FILE:testOutputWriter> "${C2_TEST_BIN_OUTPUT_DIR}/$<TARGET_FILE_NAME:testOutputWriter>")
|
||||
|
||||
add_test(NAME testsAssemblyExec COMMAND "${C2_TEST_BIN_OUTPUT_DIR}/$<TARGET_FILE_NAME:testsAssemblyExec>")
|
||||
endif()
|
||||
|
||||
add_test(NAME testsAssemblyExec COMMAND "${C2_TEST_BIN_OUTPUT_DIR}/$<TARGET_FILE_NAME:testsAssemblyExec>")
|
||||
endif()
|
||||
|
||||
if(C2CORE_BUILD_FUNCTIONAL_TESTS)
|
||||
if(WIN32)
|
||||
if (CMAKE_GENERATOR_PLATFORM STREQUAL "ARM64" OR CMAKE_SYSTEM_PROCESSOR MATCHES "^(ARM64|arm64|aarch64)$")
|
||||
add_executable(testsAssemblyExecFunctional
|
||||
tests/functional/testsAssemblyExecFunctional.cpp
|
||||
AssemblyExec.cpp
|
||||
../ModuleCmd/syscall.cpp
|
||||
${SYSCALL_OBJ}
|
||||
../ModuleCmd/peb.cpp
|
||||
../ModuleCmd/hwbp.cpp
|
||||
)
|
||||
elseif (CMAKE_SIZEOF_VOID_P EQUAL 8)
|
||||
add_executable(testsAssemblyExecFunctional
|
||||
tests/functional/testsAssemblyExecFunctional.cpp
|
||||
AssemblyExec.cpp
|
||||
../ModuleCmd/syscall.cpp
|
||||
${SYSCALL_OBJ}
|
||||
../ModuleCmd/peb.cpp
|
||||
../ModuleCmd/hwbp.cpp
|
||||
)
|
||||
else()
|
||||
add_executable(testsAssemblyExecFunctional
|
||||
tests/functional/testsAssemblyExecFunctional.cpp
|
||||
AssemblyExec.cpp
|
||||
../ModuleCmd/syscall.cpp
|
||||
${SYSCALL_OBJ}
|
||||
../ModuleCmd/peb.cpp
|
||||
../ModuleCmd/hwbp.cpp
|
||||
)
|
||||
endif()
|
||||
target_link_libraries(testsAssemblyExecFunctional PRIVATE Donut)
|
||||
c2core_disable_safeseh_for_x86(testsAssemblyExecFunctional)
|
||||
add_dependencies(testsAssemblyExecFunctional assemblyExecDummyExe)
|
||||
target_compile_definitions(testsAssemblyExecFunctional PRIVATE C2_ASSEMBLYEXEC_DUMMY_EXE="$<TARGET_FILE:assemblyExecDummyExe>")
|
||||
else()
|
||||
add_executable(testsAssemblyExecFunctional tests/functional/testsAssemblyExecFunctional.cpp AssemblyExec.cpp)
|
||||
target_link_libraries(testsAssemblyExecFunctional PRIVATE Donut ${aplib64})
|
||||
endif()
|
||||
|
||||
target_include_directories(testsAssemblyExecFunctional PRIVATE ../tests)
|
||||
add_custom_command(TARGET testsAssemblyExecFunctional POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy
|
||||
$<TARGET_FILE:testsAssemblyExecFunctional> "${C2_TEST_BIN_OUTPUT_DIR}/$<TARGET_FILE_NAME:testsAssemblyExecFunctional>")
|
||||
|
||||
add_test(NAME testsAssemblyExecFunctional COMMAND "${C2_TEST_BIN_OUTPUT_DIR}/$<TARGET_FILE_NAME:testsAssemblyExecFunctional>")
|
||||
set_tests_properties(testsAssemblyExecFunctional PROPERTIES LABELS "functional;assemblyexec;local-exec" SKIP_RETURN_CODE 77)
|
||||
endif()
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
#include "../../AssemblyExec.hpp"
|
||||
#include "../../../tests/FunctionalTestHelpers.hpp"
|
||||
|
||||
#include <filesystem>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
using namespace functional_tests;
|
||||
|
||||
namespace
|
||||
{
|
||||
std::string defaultPayloadPath()
|
||||
{
|
||||
#ifdef C2_ASSEMBLYEXEC_DUMMY_EXE
|
||||
return C2_ASSEMBLYEXEC_DUMMY_EXE;
|
||||
#else
|
||||
return {};
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string normalizeMode(const std::string& mode)
|
||||
{
|
||||
if (mode == "thread")
|
||||
{
|
||||
return "thread";
|
||||
}
|
||||
if (mode == "process-spoofed" || mode == "processWithSpoofedParent")
|
||||
{
|
||||
return "processWithSpoofedParent";
|
||||
}
|
||||
return "process";
|
||||
}
|
||||
|
||||
std::string normalizeKind(const std::string& kind)
|
||||
{
|
||||
if (kind == "dll")
|
||||
{
|
||||
return "dll";
|
||||
}
|
||||
if (kind == "raw")
|
||||
{
|
||||
return "raw";
|
||||
}
|
||||
return "exe";
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
const std::vector<Input> inputs = {
|
||||
{"--payload", "C2_FUNC_ASSEMBLYEXEC_PAYLOAD", "Assembly or shellcode path", false, false, defaultPayloadPath()},
|
||||
{"--kind", "C2_FUNC_ASSEMBLYEXEC_KIND", "Payload kind: exe, dll, or raw", false, false, "exe"},
|
||||
{"--mode", "C2_FUNC_ASSEMBLYEXEC_MODE", "Execution mode: thread, process, or process-spoofed", false, false, "process"},
|
||||
{"--method", "C2_FUNC_ASSEMBLYEXEC_METHOD", "DLL method name for dll mode", false},
|
||||
{"--args", "C2_FUNC_ASSEMBLYEXEC_ARGS", "Arguments passed to the assembly entrypoint", false},
|
||||
{"--process", "C2_FUNC_ASSEMBLYEXEC_PROCESS", "Optional process to spawn for process mode", false},
|
||||
{"--spoofed-parent", "C2_FUNC_ASSEMBLYEXEC_SPOOFED_PARENT", "Optional spoofed parent process path", false},
|
||||
};
|
||||
|
||||
if (hasFlag(argc, argv, "--help"))
|
||||
{
|
||||
printUsage("testsAssemblyExecFunctional", inputs);
|
||||
return 0;
|
||||
}
|
||||
|
||||
const bool interactive = hasFlag(argc, argv, "--interactive");
|
||||
const bool execute = hasFlag(argc, argv, "--execute");
|
||||
|
||||
#ifndef _WIN32
|
||||
if (execute)
|
||||
{
|
||||
std::cout << "testsAssemblyExecFunctional skipped: execution is only supported on Windows in this harness.\n";
|
||||
return skipReturnCode;
|
||||
}
|
||||
#endif
|
||||
|
||||
const std::string payload = readInput(inputs[0], argc, argv, interactive);
|
||||
const std::string kind = normalizeKind(readInput(inputs[1], argc, argv, interactive));
|
||||
const std::string mode = normalizeMode(readInput(inputs[2], argc, argv, interactive));
|
||||
const std::string method = readInput(inputs[3], argc, argv, interactive);
|
||||
const std::string arguments = readInput(inputs[4], argc, argv, interactive);
|
||||
const std::string processToSpawn = readInput(inputs[5], argc, argv, interactive);
|
||||
const std::string spoofedParent = readInput(inputs[6], argc, argv, interactive);
|
||||
|
||||
std::vector<Input> missing;
|
||||
if (payload.empty())
|
||||
{
|
||||
missing.push_back(inputs[0]);
|
||||
}
|
||||
if (kind == "dll" && method.empty())
|
||||
{
|
||||
missing.push_back(inputs[3]);
|
||||
}
|
||||
if (!missing.empty())
|
||||
{
|
||||
return skipMissing("testsAssemblyExecFunctional", missing);
|
||||
}
|
||||
|
||||
AssemblyExec module;
|
||||
|
||||
C2Message modeMessage;
|
||||
std::vector<std::string> modeCommand = {"assemblyExec", mode};
|
||||
if (module.init(modeCommand, modeMessage) != -1)
|
||||
{
|
||||
std::cerr << "testsAssemblyExecFunctional mode selection failed\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::vector<std::string> command = {"assemblyExec"};
|
||||
if (kind == "raw")
|
||||
{
|
||||
command.push_back("-r");
|
||||
command.push_back(payload);
|
||||
}
|
||||
else if (kind == "dll")
|
||||
{
|
||||
command.push_back("-d");
|
||||
command.push_back(payload);
|
||||
command.push_back(method);
|
||||
}
|
||||
else
|
||||
{
|
||||
command.push_back("-e");
|
||||
command.push_back(payload);
|
||||
}
|
||||
if (!arguments.empty())
|
||||
{
|
||||
command.push_back(arguments);
|
||||
}
|
||||
|
||||
C2Message message;
|
||||
if (module.init(command, message) != 0)
|
||||
{
|
||||
std::cerr << "testsAssemblyExecFunctional init failed:\n" << message.returnvalue() << '\n';
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::cout << "testsAssemblyExecFunctional init OK\n";
|
||||
std::cout << "instruction: " << message.instruction() << '\n';
|
||||
std::cout << "input: " << message.inputfile() << '\n';
|
||||
|
||||
if (!execute)
|
||||
{
|
||||
std::cout << "execution skipped; pass --execute to run the module process path.\n";
|
||||
return 0;
|
||||
}
|
||||
|
||||
nlohmann::json config;
|
||||
if (!processToSpawn.empty())
|
||||
{
|
||||
config["process"] = processToSpawn;
|
||||
}
|
||||
if (!spoofedParent.empty())
|
||||
{
|
||||
config["spoofedParent"] = spoofedParent;
|
||||
}
|
||||
if (!config.empty())
|
||||
{
|
||||
module.initConfig(config);
|
||||
}
|
||||
|
||||
C2Message result;
|
||||
module.process(message, result);
|
||||
std::cout << result.returnvalue() << '\n';
|
||||
return 0;
|
||||
}
|
||||
@@ -15,7 +15,7 @@ target_link_libraries(DotnetExec )
|
||||
add_custom_command(TARGET DotnetExec POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy
|
||||
$<TARGET_FILE:DotnetExec> "${C2_RUNTIME_MODULE_OUTPUT_DIR}/$<TARGET_FILE_NAME:DotnetExec>")
|
||||
|
||||
if(C2CORE_BUILD_TESTS)
|
||||
if(C2CORE_BUILD_TESTS OR C2CORE_BUILD_FUNCTIONAL_TESTS)
|
||||
if(WIN32)
|
||||
find_program(C2_CSC_EXECUTABLE
|
||||
NAMES csc.exe
|
||||
@@ -34,7 +34,11 @@ if(C2CORE_BUILD_TESTS)
|
||||
VERBATIM)
|
||||
add_custom_target(dummyDotnetExecAssembly DEPENDS "${C2_DOTNETEXEC_DUMMY_EXE}")
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(C2CORE_BUILD_TESTS)
|
||||
if(WIN32)
|
||||
add_executable(testsDotnetExec tests/testsDotnetExec.cpp DotnetExec.cpp HostControl.cpp AssemblyManager.cpp AssemblyStore.cpp HostMalloc.cpp MemoryManager.cpp ../ModuleCmd/syscall.cpp ${SYSCALL_OBJ} tests/project.rc)
|
||||
if(C2_CSC_EXECUTABLE)
|
||||
add_dependencies(testsDotnetExec dummyDotnetExecAssembly)
|
||||
@@ -48,6 +52,26 @@ if(C2CORE_BUILD_TESTS)
|
||||
set_property(TARGET testsDotnetExec PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded")
|
||||
add_custom_command(TARGET testsDotnetExec POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy
|
||||
$<TARGET_FILE:testsDotnetExec> "${C2_TEST_BIN_OUTPUT_DIR}/$<TARGET_FILE_NAME:testsDotnetExec>")
|
||||
|
||||
add_test(NAME testsDotnetExec COMMAND "${C2_TEST_BIN_OUTPUT_DIR}/$<TARGET_FILE_NAME:testsDotnetExec>")
|
||||
|
||||
add_test(NAME testsDotnetExec COMMAND "${C2_TEST_BIN_OUTPUT_DIR}/$<TARGET_FILE_NAME:testsDotnetExec>")
|
||||
endif()
|
||||
|
||||
if(C2CORE_BUILD_FUNCTIONAL_TESTS)
|
||||
if(WIN32)
|
||||
add_executable(testsDotnetExecFunctional tests/functional/testsDotnetExecFunctional.cpp DotnetExec.cpp HostControl.cpp AssemblyManager.cpp AssemblyStore.cpp HostMalloc.cpp MemoryManager.cpp ../ModuleCmd/syscall.cpp ${SYSCALL_OBJ})
|
||||
if(C2_CSC_EXECUTABLE)
|
||||
add_dependencies(testsDotnetExecFunctional dummyDotnetExecAssembly)
|
||||
target_compile_definitions(testsDotnetExecFunctional PRIVATE C2_DOTNETEXEC_DUMMY_EXE="${C2_DOTNETEXEC_DUMMY_EXE}")
|
||||
endif()
|
||||
else()
|
||||
add_executable(testsDotnetExecFunctional tests/functional/testsDotnetExecFunctional.cpp DotnetExec.cpp)
|
||||
endif()
|
||||
target_include_directories(testsDotnetExecFunctional PRIVATE ../tests)
|
||||
target_link_libraries(testsDotnetExecFunctional )
|
||||
set_property(TARGET testsDotnetExecFunctional PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded")
|
||||
add_custom_command(TARGET testsDotnetExecFunctional POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy
|
||||
$<TARGET_FILE:testsDotnetExecFunctional> "${C2_TEST_BIN_OUTPUT_DIR}/$<TARGET_FILE_NAME:testsDotnetExecFunctional>")
|
||||
|
||||
add_test(NAME testsDotnetExecFunctional COMMAND "${C2_TEST_BIN_OUTPUT_DIR}/$<TARGET_FILE_NAME:testsDotnetExecFunctional>")
|
||||
set_tests_properties(testsDotnetExecFunctional PROPERTIES LABELS "functional;dotnetexec;local-exec" SKIP_RETURN_CODE 77)
|
||||
endif()
|
||||
|
||||
@@ -193,7 +193,7 @@ std::string DotnetExec::getInfo()
|
||||
#define runDll "00003"
|
||||
|
||||
|
||||
#if defined(BUILD_TEAMSERVER) || defined(C2CORE_BUILD_TESTS)
|
||||
#if defined(BUILD_TEAMSERVER) || defined(C2CORE_BUILD_TESTS) || defined(C2CORE_BUILD_FUNCTIONAL_TESTS)
|
||||
|
||||
bool endsWithDLL(const std::string& str)
|
||||
{
|
||||
@@ -221,7 +221,7 @@ bool endsWithEXE(const std::string& str)
|
||||
|
||||
int DotnetExec::init(std::vector<std::string> &splitedCmd, C2Message &c2Message)
|
||||
{
|
||||
#if defined(BUILD_TEAMSERVER) || defined(C2CORE_BUILD_TESTS)
|
||||
#if defined(BUILD_TEAMSERVER) || defined(C2CORE_BUILD_TESTS) || defined(C2CORE_BUILD_FUNCTIONAL_TESTS)
|
||||
|
||||
if((splitedCmd.size()==4 || splitedCmd.size()==5) && splitedCmd[1]=="load")
|
||||
{
|
||||
@@ -1187,7 +1187,7 @@ int DotnetExec::invokeMethodDll(const std::string name, const string& method, co
|
||||
|
||||
int DotnetExec::errorCodeToMsg(const C2Message &c2RetMessage, std::string& errorMsg)
|
||||
{
|
||||
#if defined(BUILD_TEAMSERVER) || defined(C2CORE_BUILD_TESTS)
|
||||
#if defined(BUILD_TEAMSERVER) || defined(C2CORE_BUILD_TESTS) || defined(C2CORE_BUILD_FUNCTIONAL_TESTS)
|
||||
int errorCode = c2RetMessage.errorCode();
|
||||
if(errorCode>0)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
#include "../../DotnetExec.hpp"
|
||||
#include "../../../tests/FunctionalTestHelpers.hpp"
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
using namespace functional_tests;
|
||||
|
||||
namespace
|
||||
{
|
||||
std::string defaultAssemblyPath()
|
||||
{
|
||||
#ifdef C2_DOTNETEXEC_DUMMY_EXE
|
||||
return C2_DOTNETEXEC_DUMMY_EXE;
|
||||
#else
|
||||
return {};
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string normalizeKind(const std::string& kind)
|
||||
{
|
||||
return kind == "dll" ? "dll" : "exe";
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
const std::vector<Input> inputs = {
|
||||
{"--assembly", "C2_FUNC_DOTNETEXEC_ASSEMBLY", "Managed assembly path", false, false, defaultAssemblyPath()},
|
||||
{"--kind", "C2_FUNC_DOTNETEXEC_KIND", "Assembly kind: exe or dll", false, false, "exe"},
|
||||
{"--name", "C2_FUNC_DOTNETEXEC_NAME", "Short in-memory module name", false, false, "dummy"},
|
||||
{"--type", "C2_FUNC_DOTNETEXEC_TYPE", "Fully-qualified type name for dll mode", false},
|
||||
{"--method", "C2_FUNC_DOTNETEXEC_METHOD", "Method to invoke for dll mode", false},
|
||||
{"--args", "C2_FUNC_DOTNETEXEC_ARGS", "Arguments passed to the managed entrypoint", false, false, "alpha beta"},
|
||||
};
|
||||
|
||||
if (hasFlag(argc, argv, "--help"))
|
||||
{
|
||||
printUsage("testsDotnetExecFunctional", inputs);
|
||||
return 0;
|
||||
}
|
||||
|
||||
const bool interactive = hasFlag(argc, argv, "--interactive");
|
||||
const bool execute = hasFlag(argc, argv, "--execute");
|
||||
|
||||
#ifndef _WIN32
|
||||
if (execute)
|
||||
{
|
||||
std::cout << "testsDotnetExecFunctional skipped: execution is only supported on Windows.\n";
|
||||
return skipReturnCode;
|
||||
}
|
||||
#endif
|
||||
|
||||
const std::string assembly = readInput(inputs[0], argc, argv, interactive);
|
||||
const std::string kind = normalizeKind(readInput(inputs[1], argc, argv, interactive));
|
||||
const std::string name = readInput(inputs[2], argc, argv, interactive);
|
||||
const std::string type = readInput(inputs[3], argc, argv, interactive);
|
||||
const std::string method = readInput(inputs[4], argc, argv, interactive);
|
||||
const std::string arguments = readInput(inputs[5], argc, argv, interactive);
|
||||
|
||||
std::vector<Input> missing;
|
||||
if (assembly.empty())
|
||||
{
|
||||
missing.push_back(inputs[0]);
|
||||
}
|
||||
if (kind == "dll" && type.empty())
|
||||
{
|
||||
missing.push_back(inputs[3]);
|
||||
}
|
||||
if (kind == "dll" && method.empty())
|
||||
{
|
||||
missing.push_back(inputs[4]);
|
||||
}
|
||||
if (!missing.empty())
|
||||
{
|
||||
return skipMissing("testsDotnetExecFunctional", missing);
|
||||
}
|
||||
|
||||
DotnetExec module;
|
||||
std::vector<std::string> loadCommand = {"dotnetExec", "load", name, assembly};
|
||||
if (kind == "dll")
|
||||
{
|
||||
loadCommand.push_back(type);
|
||||
}
|
||||
|
||||
C2Message loadMessage;
|
||||
if (module.init(loadCommand, loadMessage) != 0)
|
||||
{
|
||||
std::cerr << "testsDotnetExecFunctional load init failed:\n" << loadMessage.returnvalue() << '\n';
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::cout << "testsDotnetExecFunctional load init OK\n";
|
||||
std::cout << "instruction: " << loadMessage.instruction() << '\n';
|
||||
|
||||
std::vector<std::string> runCommand = {"dotnetExec", kind == "dll" ? "runDll" : "runExe", name};
|
||||
if (kind == "dll")
|
||||
{
|
||||
runCommand.push_back(method);
|
||||
}
|
||||
if (!arguments.empty())
|
||||
{
|
||||
runCommand.push_back(arguments);
|
||||
}
|
||||
|
||||
C2Message runMessage;
|
||||
if (module.init(runCommand, runMessage) != 0)
|
||||
{
|
||||
std::cerr << "testsDotnetExecFunctional run init failed:\n" << runMessage.returnvalue() << '\n';
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::cout << "testsDotnetExecFunctional run init OK\n";
|
||||
|
||||
if (!execute)
|
||||
{
|
||||
std::cout << "execution skipped; pass --execute to run the module process path.\n";
|
||||
return 0;
|
||||
}
|
||||
|
||||
C2Message loadResult;
|
||||
module.process(loadMessage, loadResult);
|
||||
if (loadResult.errorCode() > 0)
|
||||
{
|
||||
std::string error;
|
||||
module.errorCodeToMsg(loadResult, error);
|
||||
std::cerr << "testsDotnetExecFunctional load failed: " << error << '\n';
|
||||
return 1;
|
||||
}
|
||||
|
||||
C2Message runResult;
|
||||
module.process(runMessage, runResult);
|
||||
if (runResult.errorCode() > 0)
|
||||
{
|
||||
std::string error;
|
||||
module.errorCodeToMsg(runResult, error);
|
||||
std::cerr << "testsDotnetExecFunctional run failed: " << error << '\n';
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::cout << runResult.returnvalue() << '\n';
|
||||
return 0;
|
||||
}
|
||||
@@ -13,3 +13,14 @@ if(C2CORE_BUILD_TESTS)
|
||||
|
||||
add_test(NAME testsEvasion COMMAND "${C2_TEST_BIN_OUTPUT_DIR}/$<TARGET_FILE_NAME:testsEvasion>")
|
||||
endif()
|
||||
|
||||
if(C2CORE_BUILD_FUNCTIONAL_TESTS)
|
||||
add_executable(testsEvasionFunctional tests/functional/testsEvasionFunctional.cpp Evasion.cpp)
|
||||
target_include_directories(testsEvasionFunctional PRIVATE ../tests)
|
||||
target_link_libraries(testsEvasionFunctional )
|
||||
add_custom_command(TARGET testsEvasionFunctional POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy
|
||||
$<TARGET_FILE:testsEvasionFunctional> "${C2_TEST_BIN_OUTPUT_DIR}/$<TARGET_FILE_NAME:testsEvasionFunctional>")
|
||||
|
||||
add_test(NAME testsEvasionFunctional COMMAND "${C2_TEST_BIN_OUTPUT_DIR}/$<TARGET_FILE_NAME:testsEvasionFunctional>")
|
||||
set_tests_properties(testsEvasionFunctional PROPERTIES LABELS "functional;evasion;windows-local" SKIP_RETURN_CODE 77)
|
||||
endif()
|
||||
|
||||
@@ -92,7 +92,7 @@ std::string Evasion::getInfo()
|
||||
|
||||
int Evasion::init(std::vector<std::string> &splitedCmd, C2Message &c2Message)
|
||||
{
|
||||
#if defined(BUILD_TEAMSERVER) || defined(C2CORE_BUILD_TESTS)
|
||||
#if defined(BUILD_TEAMSERVER) || defined(C2CORE_BUILD_TESTS) || defined(C2CORE_BUILD_FUNCTIONAL_TESTS)
|
||||
if (splitedCmd.size() >= 2)
|
||||
{
|
||||
std::string cmd = splitedCmd[1];
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
#include "../../Evasion.hpp"
|
||||
#include "../../../tests/FunctionalTestHelpers.hpp"
|
||||
|
||||
using namespace functional_tests;
|
||||
|
||||
namespace
|
||||
{
|
||||
bool requiresValue(const std::string& action)
|
||||
{
|
||||
return action == "Introspection" || action == "ReadMemory" || action == "PatchMemory";
|
||||
}
|
||||
|
||||
bool requiresExtra(const std::string& action)
|
||||
{
|
||||
return action == "ReadMemory" || action == "PatchMemory";
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
const std::vector<Input> inputs = {
|
||||
{"--action", "C2_FUNC_EVASION_ACTION", "Evasion action, e.g. CheckHooks, DisableETW, Unhook, AmsiBypass, Introspection, ReadMemory, PatchMemory, RemotePatch", false, false, "CheckHooks"},
|
||||
{"--value", "C2_FUNC_EVASION_VALUE", "Primary value for actions such as Introspection, ReadMemory, or PatchMemory", false},
|
||||
{"--extra", "C2_FUNC_EVASION_EXTRA", "Secondary value for actions such as ReadMemory size or PatchMemory bytes", false},
|
||||
};
|
||||
|
||||
if (hasFlag(argc, argv, "--help"))
|
||||
{
|
||||
printUsage("testsEvasionFunctional", inputs);
|
||||
return 0;
|
||||
}
|
||||
|
||||
const bool interactive = hasFlag(argc, argv, "--interactive");
|
||||
const bool execute = hasFlag(argc, argv, "--execute");
|
||||
|
||||
#ifndef _WIN32
|
||||
if (execute)
|
||||
{
|
||||
std::cout << "testsEvasionFunctional skipped: execution is only supported on Windows.\n";
|
||||
return skipReturnCode;
|
||||
}
|
||||
#endif
|
||||
|
||||
const std::string action = readInput(inputs[0], argc, argv, interactive);
|
||||
const std::string value = readInput(inputs[1], argc, argv, interactive);
|
||||
const std::string extra = readInput(inputs[2], argc, argv, interactive);
|
||||
|
||||
std::vector<Input> missing;
|
||||
if (requiresValue(action) && value.empty())
|
||||
{
|
||||
missing.push_back(inputs[1]);
|
||||
}
|
||||
if (requiresExtra(action) && extra.empty())
|
||||
{
|
||||
missing.push_back(inputs[2]);
|
||||
}
|
||||
if (!missing.empty())
|
||||
{
|
||||
return skipMissing("testsEvasionFunctional", missing);
|
||||
}
|
||||
|
||||
std::vector<std::string> command = {"evasion", action};
|
||||
if (!value.empty())
|
||||
{
|
||||
command.push_back(value);
|
||||
}
|
||||
if (!extra.empty())
|
||||
{
|
||||
command.push_back(extra);
|
||||
}
|
||||
|
||||
Evasion module;
|
||||
return runModuleScenario("testsEvasionFunctional", module, command, execute);
|
||||
}
|
||||
@@ -19,7 +19,7 @@ endif()
|
||||
|
||||
set_property(TARGET Inject PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded")
|
||||
|
||||
if(C2CORE_BUILD_TESTS)
|
||||
if(C2CORE_BUILD_TESTS OR C2CORE_BUILD_FUNCTIONAL_TESTS)
|
||||
target_link_libraries(Inject PRIVATE Donut )
|
||||
endif()
|
||||
c2core_disable_safeseh_for_x86(Inject)
|
||||
@@ -27,10 +27,14 @@ c2core_disable_safeseh_for_x86(Inject)
|
||||
add_custom_command(TARGET Inject POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy
|
||||
$<TARGET_FILE:Inject> "${C2_RUNTIME_MODULE_OUTPUT_DIR}/$<TARGET_FILE_NAME:Inject>")
|
||||
|
||||
if(C2CORE_BUILD_TESTS)
|
||||
if(C2CORE_BUILD_TESTS OR C2CORE_BUILD_FUNCTIONAL_TESTS)
|
||||
if(WIN32)
|
||||
add_executable(dummyInject tests/dummyInject.cpp)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(C2CORE_BUILD_TESTS)
|
||||
if(WIN32)
|
||||
add_executable(testsInject tests/testsInject.cpp Inject.cpp ../ModuleCmd/syscall.cpp ${SYSCALL_OBJ})
|
||||
target_link_libraries(testsInject PRIVATE Donut )
|
||||
c2core_disable_safeseh_for_x86(testsInject)
|
||||
@@ -43,6 +47,26 @@ if(C2CORE_BUILD_TESTS)
|
||||
|
||||
add_custom_command(TARGET testsInject POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy
|
||||
$<TARGET_FILE:testsInject> "${C2_TEST_BIN_OUTPUT_DIR}/$<TARGET_FILE_NAME:testsInject>")
|
||||
|
||||
add_test(NAME testsInject COMMAND "${C2_TEST_BIN_OUTPUT_DIR}/$<TARGET_FILE_NAME:testsInject>")
|
||||
endif()
|
||||
|
||||
add_test(NAME testsInject COMMAND "${C2_TEST_BIN_OUTPUT_DIR}/$<TARGET_FILE_NAME:testsInject>")
|
||||
endif()
|
||||
|
||||
if(C2CORE_BUILD_FUNCTIONAL_TESTS)
|
||||
if(WIN32)
|
||||
add_executable(testsInjectFunctional tests/functional/testsInjectFunctional.cpp Inject.cpp ../ModuleCmd/syscall.cpp ${SYSCALL_OBJ})
|
||||
target_link_libraries(testsInjectFunctional PRIVATE Donut)
|
||||
c2core_disable_safeseh_for_x86(testsInjectFunctional)
|
||||
add_dependencies(testsInjectFunctional dummyInject)
|
||||
target_compile_definitions(testsInjectFunctional PRIVATE C2_INJECT_DUMMY_EXE="$<TARGET_FILE:dummyInject>")
|
||||
else()
|
||||
add_executable(testsInjectFunctional tests/functional/testsInjectFunctional.cpp Inject.cpp)
|
||||
target_link_libraries(testsInjectFunctional PRIVATE Donut ${aplib64})
|
||||
endif()
|
||||
|
||||
target_include_directories(testsInjectFunctional PRIVATE ../tests)
|
||||
add_custom_command(TARGET testsInjectFunctional POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy
|
||||
$<TARGET_FILE:testsInjectFunctional> "${C2_TEST_BIN_OUTPUT_DIR}/$<TARGET_FILE_NAME:testsInjectFunctional>")
|
||||
|
||||
add_test(NAME testsInjectFunctional COMMAND "${C2_TEST_BIN_OUTPUT_DIR}/$<TARGET_FILE_NAME:testsInjectFunctional>")
|
||||
set_tests_properties(testsInjectFunctional PROPERTIES LABELS "functional;inject;local-exec" SKIP_RETURN_CODE 77)
|
||||
endif()
|
||||
|
||||
@@ -81,7 +81,7 @@ std::string Inject::getInfo()
|
||||
|
||||
int Inject::init(std::vector<std::string> &splitedCmd, C2Message &c2Message)
|
||||
{
|
||||
#if defined(BUILD_TEAMSERVER) || defined(C2CORE_BUILD_TESTS)
|
||||
#if defined(BUILD_TEAMSERVER) || defined(C2CORE_BUILD_TESTS) || defined(C2CORE_BUILD_FUNCTIONAL_TESTS)
|
||||
if (splitedCmd.size() >= 4)
|
||||
{
|
||||
bool donut=false;
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
#include "../../Inject.hpp"
|
||||
#include "../../../tests/FunctionalTestHelpers.hpp"
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
using namespace functional_tests;
|
||||
|
||||
namespace
|
||||
{
|
||||
std::string defaultPayloadPath()
|
||||
{
|
||||
#ifdef C2_INJECT_DUMMY_EXE
|
||||
return C2_INJECT_DUMMY_EXE;
|
||||
#else
|
||||
return {};
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string defaultSpawnProcess()
|
||||
{
|
||||
#ifdef C2_INJECT_DUMMY_EXE
|
||||
return C2_INJECT_DUMMY_EXE;
|
||||
#else
|
||||
return {};
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string normalizeKind(const std::string& kind)
|
||||
{
|
||||
if (kind == "dll")
|
||||
{
|
||||
return "dll";
|
||||
}
|
||||
if (kind == "raw")
|
||||
{
|
||||
return "raw";
|
||||
}
|
||||
return "exe";
|
||||
}
|
||||
|
||||
bool isTruthy(const std::string& value)
|
||||
{
|
||||
return value == "1" || value == "true" || value == "TRUE" || value == "yes" || value == "YES";
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
const std::vector<Input> inputs = {
|
||||
{"--payload", "C2_FUNC_INJECT_PAYLOAD", "Payload path", false, false, defaultPayloadPath()},
|
||||
{"--kind", "C2_FUNC_INJECT_KIND", "Payload kind: exe, dll, or raw", false, false, "exe"},
|
||||
{"--pid", "C2_FUNC_INJECT_PID", "Target pid, or -1 to spawn a new process", false, false, "-1"},
|
||||
{"--method", "C2_FUNC_INJECT_METHOD", "DLL method name for dll mode", false},
|
||||
{"--args", "C2_FUNC_INJECT_ARGS", "Arguments passed to the payload", false},
|
||||
{"--spawn-process", "C2_FUNC_INJECT_PROCESS", "Process path to spawn when pid is negative", false, false, defaultSpawnProcess()},
|
||||
{"--syscall", "C2_FUNC_INJECT_SYSCALL", "Enable syscall path (1/0)", false, false, "0"},
|
||||
};
|
||||
|
||||
if (hasFlag(argc, argv, "--help"))
|
||||
{
|
||||
printUsage("testsInjectFunctional", inputs);
|
||||
return 0;
|
||||
}
|
||||
|
||||
const bool interactive = hasFlag(argc, argv, "--interactive");
|
||||
const bool execute = hasFlag(argc, argv, "--execute");
|
||||
|
||||
#ifndef _WIN32
|
||||
if (execute)
|
||||
{
|
||||
std::cout << "testsInjectFunctional skipped: execution is only supported on Windows in this harness.\n";
|
||||
return skipReturnCode;
|
||||
}
|
||||
#endif
|
||||
|
||||
const std::string payload = readInput(inputs[0], argc, argv, interactive);
|
||||
const std::string kind = normalizeKind(readInput(inputs[1], argc, argv, interactive));
|
||||
const std::string pid = readInput(inputs[2], argc, argv, interactive);
|
||||
const std::string method = readInput(inputs[3], argc, argv, interactive);
|
||||
const std::string arguments = readInput(inputs[4], argc, argv, interactive);
|
||||
const std::string processToSpawn = readInput(inputs[5], argc, argv, interactive);
|
||||
const std::string useSyscall = readInput(inputs[6], argc, argv, interactive);
|
||||
|
||||
std::vector<Input> missing;
|
||||
if (payload.empty())
|
||||
{
|
||||
missing.push_back(inputs[0]);
|
||||
}
|
||||
if (kind == "dll" && method.empty())
|
||||
{
|
||||
missing.push_back(inputs[3]);
|
||||
}
|
||||
if (!missing.empty())
|
||||
{
|
||||
return skipMissing("testsInjectFunctional", missing);
|
||||
}
|
||||
|
||||
std::vector<std::string> command = {"inject"};
|
||||
if (kind == "raw")
|
||||
{
|
||||
command.push_back("-r");
|
||||
command.push_back(payload);
|
||||
}
|
||||
else if (kind == "dll")
|
||||
{
|
||||
command.push_back("-d");
|
||||
command.push_back(payload);
|
||||
command.push_back(pid);
|
||||
command.push_back(method);
|
||||
if (!arguments.empty())
|
||||
{
|
||||
command.push_back(arguments);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
command.push_back("-e");
|
||||
command.push_back(payload);
|
||||
command.push_back(pid);
|
||||
if (!arguments.empty())
|
||||
{
|
||||
command.push_back(arguments);
|
||||
}
|
||||
}
|
||||
|
||||
if (kind == "raw")
|
||||
{
|
||||
command.push_back(pid);
|
||||
}
|
||||
|
||||
Inject module;
|
||||
C2Message message;
|
||||
if (module.init(command, message) != 0)
|
||||
{
|
||||
std::cerr << "testsInjectFunctional init failed:\n" << message.returnvalue() << '\n';
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::cout << "testsInjectFunctional init OK\n";
|
||||
std::cout << "instruction: " << message.instruction() << '\n';
|
||||
std::cout << "pid: " << message.pid() << '\n';
|
||||
|
||||
if (!execute)
|
||||
{
|
||||
std::cout << "execution skipped; pass --execute to run the module process path.\n";
|
||||
return 0;
|
||||
}
|
||||
|
||||
nlohmann::json config;
|
||||
if (!processToSpawn.empty())
|
||||
{
|
||||
config["process"] = processToSpawn;
|
||||
}
|
||||
if (isTruthy(useSyscall))
|
||||
{
|
||||
config["syscall"] = true;
|
||||
}
|
||||
if (!config.empty())
|
||||
{
|
||||
module.initConfig(config);
|
||||
}
|
||||
|
||||
C2Message result;
|
||||
module.process(message, result);
|
||||
std::cout << result.returnvalue() << '\n';
|
||||
return 0;
|
||||
}
|
||||
@@ -29,4 +29,30 @@ if(C2CORE_BUILD_TESTS)
|
||||
|
||||
endif (WIN32)
|
||||
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(C2CORE_BUILD_FUNCTIONAL_TESTS)
|
||||
add_executable(testsPsExecFunctional tests/functional/testsPsExecFunctional.cpp PsExec.cpp)
|
||||
target_include_directories(testsPsExecFunctional PRIVATE ../tests)
|
||||
target_link_libraries(testsPsExecFunctional )
|
||||
|
||||
add_custom_command(TARGET testsPsExecFunctional POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy
|
||||
$<TARGET_FILE:testsPsExecFunctional> "${C2_TEST_BIN_OUTPUT_DIR}/$<TARGET_FILE_NAME:testsPsExecFunctional>")
|
||||
|
||||
if (WIN32)
|
||||
if (NOT TARGET TestService)
|
||||
add_executable(TestService tests/TestService.cpp)
|
||||
target_link_libraries(TestService PRIVATE advapi32)
|
||||
add_custom_command(TARGET TestService POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy
|
||||
$<TARGET_FILE:TestService>
|
||||
"${C2_TEST_BIN_OUTPUT_DIR}/$<TARGET_FILE_NAME:TestService>"
|
||||
)
|
||||
endif()
|
||||
add_dependencies(testsPsExecFunctional TestService)
|
||||
target_compile_definitions(testsPsExecFunctional PRIVATE C2_PSEXEC_TEST_SERVICE="$<TARGET_FILE:TestService>")
|
||||
endif()
|
||||
|
||||
add_test(NAME testsPsExecFunctional COMMAND "${C2_TEST_BIN_OUTPUT_DIR}/$<TARGET_FILE_NAME:testsPsExecFunctional>")
|
||||
set_tests_properties(testsPsExecFunctional PROPERTIES LABELS "functional;psexec;windows-network" SKIP_RETURN_CODE 77)
|
||||
endif()
|
||||
|
||||
@@ -133,7 +133,7 @@ std::string PsExec::getInfo()
|
||||
|
||||
int PsExec::init(std::vector<std::string> &splitedCmd, C2Message &c2Message)
|
||||
{
|
||||
#if defined(BUILD_TEAMSERVER) || defined(C2CORE_BUILD_TESTS)
|
||||
#if defined(BUILD_TEAMSERVER) || defined(C2CORE_BUILD_TESTS) || defined(C2CORE_BUILD_FUNCTIONAL_TESTS)
|
||||
|
||||
if (splitedCmd.size() >= 2)
|
||||
{
|
||||
@@ -522,7 +522,7 @@ int PsExec::process(C2Message &c2Message, C2Message &c2RetMessage)
|
||||
|
||||
int PsExec::errorCodeToMsg(const C2Message& c2RetMessage, std::string& errorMsg)
|
||||
{
|
||||
#if defined(BUILD_TEAMSERVER) || defined(C2CORE_BUILD_TESTS)
|
||||
#if defined(BUILD_TEAMSERVER) || defined(C2CORE_BUILD_TESTS) || defined(C2CORE_BUILD_FUNCTIONAL_TESTS)
|
||||
int errorCode = c2RetMessage.errorCode();
|
||||
if (errorCode > 0)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
#include "../../PsExec.hpp"
|
||||
#include "../../../tests/FunctionalTestHelpers.hpp"
|
||||
|
||||
using namespace functional_tests;
|
||||
|
||||
namespace
|
||||
{
|
||||
std::string defaultServicePath()
|
||||
{
|
||||
#ifdef C2_PSEXEC_TEST_SERVICE
|
||||
return C2_PSEXEC_TEST_SERVICE;
|
||||
#else
|
||||
return {};
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
const std::vector<Input> inputs = {
|
||||
{"--auth", "C2_FUNC_PSEXEC_AUTH", "Authentication mode: no-cred, kerberos, or userpass", false, false, "no-cred"},
|
||||
{"--target", "C2_FUNC_PSEXEC_TARGET", "Remote hostname or IP", true},
|
||||
{"--service", "C2_FUNC_PSEXEC_SERVICE", "Windows service executable to deploy", false, false, defaultServicePath()},
|
||||
{"--user", "C2_FUNC_PSEXEC_USER", "Username for userpass mode, optionally DOMAIN\\user", false},
|
||||
{"--password", "C2_FUNC_PSEXEC_PASSWORD", "Password for userpass mode", false, true},
|
||||
};
|
||||
|
||||
if (hasFlag(argc, argv, "--help"))
|
||||
{
|
||||
printUsage("testsPsExecFunctional", inputs);
|
||||
return 0;
|
||||
}
|
||||
|
||||
const bool interactive = hasFlag(argc, argv, "--interactive");
|
||||
const bool execute = hasFlag(argc, argv, "--execute");
|
||||
|
||||
#ifndef _WIN32
|
||||
if (execute)
|
||||
{
|
||||
std::cout << "testsPsExecFunctional skipped: execution is only supported on Windows.\n";
|
||||
return skipReturnCode;
|
||||
}
|
||||
#endif
|
||||
|
||||
const std::string auth = readInput(inputs[0], argc, argv, interactive);
|
||||
const std::string target = readInput(inputs[1], argc, argv, interactive);
|
||||
const std::string service = readInput(inputs[2], argc, argv, interactive);
|
||||
const std::string user = readInput(inputs[3], argc, argv, interactive);
|
||||
const std::string password = readInput(inputs[4], argc, argv, interactive);
|
||||
|
||||
std::vector<Input> missing;
|
||||
if (target.empty())
|
||||
{
|
||||
missing.push_back(inputs[1]);
|
||||
}
|
||||
if (service.empty())
|
||||
{
|
||||
missing.push_back(inputs[2]);
|
||||
}
|
||||
if (auth == "userpass" && user.empty())
|
||||
{
|
||||
missing.push_back(inputs[3]);
|
||||
}
|
||||
if (auth == "userpass" && password.empty())
|
||||
{
|
||||
missing.push_back(inputs[4]);
|
||||
}
|
||||
if (!missing.empty())
|
||||
{
|
||||
return skipMissing("testsPsExecFunctional", missing);
|
||||
}
|
||||
|
||||
std::vector<std::string> command = {"psExec"};
|
||||
if (auth == "userpass")
|
||||
{
|
||||
command.insert(command.end(), {"-u", user, password, target, service});
|
||||
}
|
||||
else if (auth == "kerberos")
|
||||
{
|
||||
command.insert(command.end(), {"-k", target, service});
|
||||
}
|
||||
else
|
||||
{
|
||||
command.insert(command.end(), {"-n", target, service});
|
||||
}
|
||||
|
||||
PsExec module;
|
||||
return runModuleScenario("testsPsExecFunctional", module, command, execute);
|
||||
}
|
||||
@@ -26,6 +26,22 @@ if(C2CORE_BUILD_TESTS)
|
||||
set_property(TARGET testsPwSh PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded")
|
||||
add_custom_command(TARGET testsPwSh POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy
|
||||
$<TARGET_FILE:testsPwSh> "${C2_TEST_BIN_OUTPUT_DIR}/$<TARGET_FILE_NAME:testsPwSh>")
|
||||
|
||||
add_test(NAME testsPwSh COMMAND "${C2_TEST_BIN_OUTPUT_DIR}/$<TARGET_FILE_NAME:testsPwSh>")
|
||||
|
||||
add_test(NAME testsPwSh COMMAND "${C2_TEST_BIN_OUTPUT_DIR}/$<TARGET_FILE_NAME:testsPwSh>")
|
||||
endif()
|
||||
|
||||
if(C2CORE_BUILD_FUNCTIONAL_TESTS)
|
||||
if(WIN32)
|
||||
add_executable(testsPwShFunctional tests/functional/testsPwShFunctional.cpp PwSh.cpp HostControl.cpp AssemblyManager.cpp AssemblyStore.cpp HostMalloc.cpp MemoryManager.cpp ../ModuleCmd/syscall.cpp ${SYSCALL_OBJ})
|
||||
else()
|
||||
add_executable(testsPwShFunctional tests/functional/testsPwShFunctional.cpp PwSh.cpp)
|
||||
endif()
|
||||
target_include_directories(testsPwShFunctional PRIVATE ../tests)
|
||||
target_link_libraries(testsPwShFunctional )
|
||||
set_property(TARGET testsPwShFunctional PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded")
|
||||
add_custom_command(TARGET testsPwShFunctional POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy
|
||||
$<TARGET_FILE:testsPwShFunctional> "${C2_TEST_BIN_OUTPUT_DIR}/$<TARGET_FILE_NAME:testsPwShFunctional>")
|
||||
|
||||
add_test(NAME testsPwShFunctional COMMAND "${C2_TEST_BIN_OUTPUT_DIR}/$<TARGET_FILE_NAME:testsPwShFunctional>")
|
||||
set_tests_properties(testsPwShFunctional PROPERTIES LABELS "functional;pwsh;local-exec" SKIP_RETURN_CODE 77)
|
||||
endif()
|
||||
|
||||
@@ -198,7 +198,7 @@ std::string PwSh::getInfo()
|
||||
#define scriptPS "00005"
|
||||
|
||||
|
||||
#if defined(BUILD_TEAMSERVER) || defined(C2CORE_BUILD_TESTS)
|
||||
#if defined(BUILD_TEAMSERVER) || defined(C2CORE_BUILD_TESTS) || defined(C2CORE_BUILD_FUNCTIONAL_TESTS)
|
||||
|
||||
bool endsWithDLL(const std::string& str)
|
||||
{
|
||||
@@ -226,7 +226,7 @@ bool endsWithEXE(const std::string& str)
|
||||
|
||||
int PwSh::init(std::vector<std::string> &splitedCmd, C2Message &c2Message)
|
||||
{
|
||||
#if defined(BUILD_TEAMSERVER) || defined(C2CORE_BUILD_TESTS)
|
||||
#if defined(BUILD_TEAMSERVER) || defined(C2CORE_BUILD_TESTS) || defined(C2CORE_BUILD_FUNCTIONAL_TESTS)
|
||||
|
||||
if ((splitedCmd.size() == 2 || splitedCmd.size() == 4) && splitedCmd[1] == "init")
|
||||
{
|
||||
@@ -970,7 +970,7 @@ int PwSh::invokeMethodDll(const string& argument, std::string& result)
|
||||
|
||||
int PwSh::errorCodeToMsg(const C2Message &c2RetMessage, std::string& errorMsg)
|
||||
{
|
||||
#if defined(BUILD_TEAMSERVER) || defined(C2CORE_BUILD_TESTS)
|
||||
#if defined(BUILD_TEAMSERVER) || defined(C2CORE_BUILD_TESTS) || defined(C2CORE_BUILD_FUNCTIONAL_TESTS)
|
||||
int errorCode = c2RetMessage.errorCode();
|
||||
if(errorCode>0)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
#include "../../PwSh.hpp"
|
||||
#include "../../../tests/FunctionalTestHelpers.hpp"
|
||||
|
||||
#include <filesystem>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
using namespace functional_tests;
|
||||
|
||||
namespace
|
||||
{
|
||||
std::string defaultRunnerPath()
|
||||
{
|
||||
return (std::filesystem::path(__FILE__).parent_path().parent_path() / "rdm.dll").string();
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
const std::vector<Input> inputs = {
|
||||
{"--mode", "C2_FUNC_PWSH_MODE", "Execution mode: run, import, or script", false, false, "run"},
|
||||
{"--runner", "C2_FUNC_PWSH_RUNNER", "PowerShell runner dll path", false, false, defaultRunnerPath()},
|
||||
{"--type", "C2_FUNC_PWSH_TYPE", "Fully-qualified runner type", false, false, "rdm.rdm"},
|
||||
{"--command", "C2_FUNC_PWSH_COMMAND", "PowerShell command to run", false, false, "Write-Output \"c2core-pwsh-functional\""},
|
||||
{"--import", "C2_FUNC_PWSH_IMPORT", "PowerShell script path to import as a module", false},
|
||||
{"--script", "C2_FUNC_PWSH_SCRIPT", "PowerShell script path to execute", false},
|
||||
};
|
||||
|
||||
if (hasFlag(argc, argv, "--help"))
|
||||
{
|
||||
printUsage("testsPwShFunctional", inputs);
|
||||
return 0;
|
||||
}
|
||||
|
||||
const bool interactive = hasFlag(argc, argv, "--interactive");
|
||||
const bool execute = hasFlag(argc, argv, "--execute");
|
||||
|
||||
#ifndef _WIN32
|
||||
if (execute)
|
||||
{
|
||||
std::cout << "testsPwShFunctional skipped: execution is only supported on Windows.\n";
|
||||
return skipReturnCode;
|
||||
}
|
||||
#endif
|
||||
|
||||
const std::string mode = readInput(inputs[0], argc, argv, interactive);
|
||||
const std::string runner = readInput(inputs[1], argc, argv, interactive);
|
||||
const std::string type = readInput(inputs[2], argc, argv, interactive);
|
||||
const std::string commandText = readInput(inputs[3], argc, argv, interactive);
|
||||
const std::string importPath = readInput(inputs[4], argc, argv, interactive);
|
||||
const std::string scriptPath = readInput(inputs[5], argc, argv, interactive);
|
||||
|
||||
std::vector<Input> missing;
|
||||
if (runner.empty())
|
||||
{
|
||||
missing.push_back(inputs[1]);
|
||||
}
|
||||
if (mode == "import" && importPath.empty())
|
||||
{
|
||||
missing.push_back(inputs[4]);
|
||||
}
|
||||
if (mode == "script" && scriptPath.empty())
|
||||
{
|
||||
missing.push_back(inputs[5]);
|
||||
}
|
||||
if (mode == "run" && commandText.empty())
|
||||
{
|
||||
missing.push_back(inputs[3]);
|
||||
}
|
||||
if (!missing.empty())
|
||||
{
|
||||
return skipMissing("testsPwShFunctional", missing);
|
||||
}
|
||||
|
||||
PwSh module;
|
||||
std::vector<std::string> loadCommand = {"pwSh", "init", runner, type};
|
||||
C2Message loadMessage;
|
||||
if (module.init(loadCommand, loadMessage) != 0)
|
||||
{
|
||||
std::cerr << "testsPwShFunctional init failed:\n" << loadMessage.returnvalue() << '\n';
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::cout << "testsPwShFunctional runner init OK\n";
|
||||
|
||||
std::vector<std::string> actionCommand = {"pwSh"};
|
||||
if (mode == "import")
|
||||
{
|
||||
actionCommand.insert(actionCommand.end(), {"import", importPath});
|
||||
}
|
||||
else if (mode == "script")
|
||||
{
|
||||
actionCommand.insert(actionCommand.end(), {"script", scriptPath});
|
||||
}
|
||||
else
|
||||
{
|
||||
actionCommand.insert(actionCommand.end(), {"run", commandText});
|
||||
}
|
||||
|
||||
C2Message actionMessage;
|
||||
if (module.init(actionCommand, actionMessage) != 0)
|
||||
{
|
||||
std::cerr << "testsPwShFunctional action init failed:\n" << actionMessage.returnvalue() << '\n';
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::cout << "testsPwShFunctional action init OK\n";
|
||||
|
||||
if (!execute)
|
||||
{
|
||||
std::cout << "execution skipped; pass --execute to run the module process path.\n";
|
||||
return 0;
|
||||
}
|
||||
|
||||
C2Message loadResult;
|
||||
module.process(loadMessage, loadResult);
|
||||
if (loadResult.errorCode() > 0)
|
||||
{
|
||||
std::string error;
|
||||
module.errorCodeToMsg(loadResult, error);
|
||||
std::cerr << "testsPwShFunctional load failed: " << error << '\n';
|
||||
return 1;
|
||||
}
|
||||
|
||||
C2Message actionResult;
|
||||
module.process(actionMessage, actionResult);
|
||||
if (actionResult.errorCode() > 0)
|
||||
{
|
||||
std::string error;
|
||||
module.errorCodeToMsg(actionResult, error);
|
||||
std::cerr << "testsPwShFunctional action failed: " << error << '\n';
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::cout << actionResult.returnvalue() << '\n';
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user