├── Resource └── HermesLogo.png ├── README.md ├── Hermes.h └── Hermes.cpp /Resource/HermesLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Skeletal-Group/Hermes/HEAD/Resource/HermesLogo.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Logo](https://github.com/Skeletal-Group/Hermes/blob/main/Resource/HermesLogo.png) 2 | 3 | ## Hermes, a POC for effective covert timing channels on Windows systems 4 | Originally known as [CTC](https://github.com/Peribunt/CTC), this POC has a few significant improvements, including but not limited to faster data transfer rates and improved reliability. 5 | This repository has been published exclusively for educational reasons, and we are not responsible for any re implementations of the functionality herein. 6 | -------------------------------------------------------------------------------- /Hermes.h: -------------------------------------------------------------------------------- 1 | // 2 | // File: 3 | // Hermes.h 4 | // 5 | // Abstract: 6 | // A fast, reliable and refined covert timing channel proof of concept 7 | // for Windows systems. This proof of concept is derived from a much earlier 8 | // proof of concept you can find here: https://github.com/Peribunt/CTC 9 | // It contains many improvements, and there is still room for more. 10 | // 11 | // This proof of concept was made strictly for educational purposes, we are 12 | // in no way responsible for any software that might implement this functionality 13 | // for any other purpose. 14 | // 15 | #ifndef __HERMES_H__ 16 | #define __HERMES_H__ 17 | 18 | #include 19 | #include 20 | 21 | /** 22 | * @brief Initialize the Hermes covert timing channel functionality 23 | * 24 | * @param [in] CacheLines: A pointer to a preferably page aligned virtual address 25 | * whose cache lines will be used to transmit data. 26 | * 27 | * @return TRUE if the running system supports all architecture features required 28 | * @return FALSE if the running system does not support all architecture features required 29 | */ 30 | BOOLEAN 31 | HermesInitialize( 32 | _In_ LPVOID CacheLines 33 | ); 34 | 35 | /** 36 | * @brief Attempt to establish a connection with the receiving process, and transmit 37 | * a given buffer to it. 38 | * 39 | * @param [in] Data: A pointer to the data to send 40 | * @param [in] DataLength: The length of the data to send 41 | * 42 | * @return TRUE if the data was transmitted successfully 43 | * @return FALSE if the data was not transmitted successfully 44 | */ 45 | BOOLEAN 46 | HermesSendData( 47 | _In_ LPVOID Data, 48 | _In_ SIZE_T DataLength 49 | ); 50 | 51 | /** 52 | * @brief Attempt to establish a connection with the sending process, and receive 53 | * transmitted data within a given buffer. 54 | * 55 | * @param [in] Data: A pointer to a buffer that will receive the data 56 | * @param [in] BufferLength: The length of the buffer that will receive the data 57 | * 58 | * @return TRUE if the data was received successfully 59 | * @return FALSE if the data was not received successfully 60 | */ 61 | BOOLEAN 62 | HermesReceiveData( 63 | _Out_ LPVOID Data, 64 | _In_ SIZE_T BufferLength 65 | ); 66 | 67 | #endif 68 | -------------------------------------------------------------------------------- /Hermes.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // File: 3 | // Hermes.cpp 4 | // 5 | // Abstract: 6 | // A fast, reliable and refined covert timing channel proof of concept 7 | // for Windows systems. This proof of concept is derived from a much earlier 8 | // proof of concept you can find here: https://github.com/Peribunt/CTC 9 | // It contains many improvements, and there is still room for more. 10 | // 11 | // This proof of concept was made strictly for educational purposes, we are 12 | // in no way responsible for any software that might implement this functionality 13 | // for any other purpose. 14 | // 15 | #include "Hermes.h" 16 | 17 | #pragma warning( push ) 18 | #pragma warning( disable : 6385 ) 19 | #pragma warning( disable : 6386 ) 20 | 21 | #define HERMES_TRANSMIT_TIMEOUT 1000000 22 | #define HERMES_TRANSMIT_FLUSH_COUNT 1000 23 | 24 | #define HERMES_EXECUTABLE __declspec( allocate( ".text" ) ) 25 | #define HERMES_NOINLINE __declspec( noinline ) 26 | #define HERMES_INLINE __forceinline 27 | 28 | #pragma pack( push, 1 ) 29 | typedef union _HERMES_TRANSMIT_BLOCK 30 | { 31 | UINT64 AsUInt[ 5 ]; 32 | 33 | struct 34 | { 35 | UINT64 Data[ 2 ]; 36 | UINT32 Position; 37 | UINT32 Length; 38 | UINT64 Checksum; 39 | UINT64 Acknowledgement; 40 | }; 41 | }HERMES_TRANSMIT_BLOCK, *PHERMES_TRANSMIT_BLOCK; 42 | #pragma pack( pop ) 43 | 44 | CONST __m128i HermesTransmitStartMagic = _mm_set_epi64x( 0x7C0DE000CAFECAFE, 0xDEAFDEAFCAFECAFE ); 45 | CONST __m128i HermesTransmitEndMagic = _mm_set_epi64x( 0x7C0DE001CAFECAFE, 0xCAFECAFEDEAFDEAF ); 46 | 47 | // 48 | // The base virtual address of the memory region whose cache lines 49 | // will be used for our communication channel 50 | // 51 | UINT8* HermesCacheLines = NULL; 52 | 53 | // 54 | // The size of a single cache line on the current executing CPU 55 | // 56 | UINT64 HermesLineSize = 0; 57 | 58 | #pragma code_seg( push, ".text" ) 59 | 60 | HERMES_EXECUTABLE UINT8 HermesMeasureCacheLineData[ ] = 61 | { 62 | 0x49, 0x89, 0xC9, // mov r9, rcx 63 | 0x0F, 0x31, // rdtsc 64 | 0x49, 0x89, 0xC0, // mov r8, rax 65 | 0x45, 0x8A, 0x09, // mov r9b, byte ptr[r9] 66 | 0x0F, 0x01, 0xF9, // rdtscp 67 | 0x4C, 0x29, 0xC0, // sub rax, r8 68 | 0xC3 // ret 69 | }; 70 | 71 | HERMES_EXECUTABLE UINT8 HermesClflushData[ ] = 72 | { 73 | 0x66, // Using the 0x66 prefix for the CLFLUSH instruction 74 | // results in CLFLUSHOPT on CPUs that support it. 75 | 0x0F, 0xAE, 0x39, // clflush byte ptr[rcx] 76 | 0xC3 // ret 77 | }; 78 | 79 | #pragma code_seg( pop ) 80 | 81 | // 82 | // Time a load operation from a specified cache line using the TSC. 83 | // 84 | UINT32( *HermesMeasureCacheLine )( 85 | _In_ LPVOID CacheLine 86 | ) = NULL; 87 | 88 | // 89 | // Flush a cache line from all levels of the cache hierarchy 90 | // 91 | VOID( *HermesClflush )( 92 | _In_ LPVOID CacheLine 93 | ) = NULL; 94 | 95 | // 96 | // Initialize or obtain a memory region whose cache lines will be 97 | // used as communication channel. Additionally, probe CPU features 98 | // to determine if Hermes is supported on the running system. 99 | // 100 | BOOLEAN 101 | HermesInitialize( 102 | _In_ LPVOID CacheLines 103 | ) 104 | { 105 | INT32 CpuidRegs[ 4 ]{ }; 106 | 107 | // 108 | // CPUID EAX=7, ECX=0, extended features 109 | // 110 | __cpuidex( CpuidRegs, 7, 0 ); 111 | 112 | HermesClflush = ( decltype( HermesClflush ) )&HermesClflushData[ 0 ]; 113 | 114 | // 115 | // EBX bit 23, CLFLUSHOPT instruction support 116 | // 117 | if ( ( ( CpuidRegs[ 1 ] >> 23 ) & 1 ) == FALSE ) 118 | { 119 | // 120 | // CLFLUSHOPT is not supported on the current system. 121 | // Use the regular CLFLUSH instruction by not using the 0x66 prefix. 122 | // 123 | HermesClflush = ( decltype( HermesClflush ) )&HermesClflushData[ 1 ]; 124 | } 125 | 126 | if ( HermesLineSize == 0 ) 127 | { 128 | // 129 | // CPUID EAX=1 ECX=0, feature bits and processor information 130 | // 131 | __cpuidex( CpuidRegs, 1, 0 ); 132 | 133 | // 134 | // EBX bits 15:8, CLFLUSH line size. This line size should be 135 | // multiplied by 8 to obtain the line size in bytes. 136 | // 137 | HermesLineSize = ( ( CpuidRegs[ 1 ] >> 8 ) & 0xFF ) * 8; 138 | } 139 | 140 | if ( HermesCacheLines == NULL ) 141 | { 142 | if ( CacheLines != NULL ) 143 | { 144 | // 145 | // The caller specified a preferred cache line region 146 | // 147 | HermesCacheLines = ( UINT8* )CacheLines; 148 | } 149 | else 150 | { 151 | // 152 | // Use ntdll.dll as default communication cache line region. 153 | // 154 | HermesCacheLines = ( UINT8* )GetModuleHandleA( "ntdll.dll" ); 155 | } 156 | } 157 | 158 | // 159 | // Use the Intel specific cahce line measurement routine when on Intel systems 160 | // 161 | HermesMeasureCacheLine = ( decltype( HermesMeasureCacheLine ) )&HermesMeasureCacheLineData; 162 | 163 | return TRUE; 164 | } 165 | 166 | #pragma optimize( push ) 167 | #pragma optimize( "", off ) 168 | 169 | /** 170 | * @brief Flush the communcation region's cache lines that correspond to set bits within a given bitmap 171 | * 172 | * @param [in] Bitmap: A bitmap whose bit positions correspond to the cache lines to flush 173 | * @param [in] NumBits: The number of bits in the bitmap 174 | */ 175 | VOID 176 | HermesSetLines( 177 | _In_ UINT64* Bitmap, 178 | _In_ UINT32 NumBits 179 | ) 180 | { 181 | CONST UINT64 LineSize = HermesLineSize; 182 | 183 | for ( UINT32 i = 0; i < NumBits; i++ ) 184 | { 185 | if ( Bitmap[ i / 64 ] & ( 1ull << ( i % 64 ) ) ) 186 | { 187 | // 188 | // Flush the cache line that corresponds to the current set bit 189 | // 190 | HermesClflush( HermesCacheLines + ( i * LineSize ) ); 191 | } 192 | } 193 | } 194 | 195 | /** 196 | * @brief Measure the average access time for a given number of cache lines within the commuincation region 197 | * 198 | * @param [in] BaseAddress: The base address of the cache lines for which to obtain the average 199 | * @param [in] NumLines: The number of cache lines to measure 200 | * @param [in] NumSamples: The number of samples to obtain the average from 201 | * @param [in] Results: A pointer to a list of UINT64s that will receive the average per line 202 | */ 203 | VOID 204 | HermesMeasureLineAverage( 205 | _In_ LPVOID BaseAddress, 206 | _In_ UINT32 NumLines, 207 | _In_ UINT32 NumSamples, 208 | _Out_ PUINT64 Results 209 | ) 210 | { 211 | CONST UINT64 LineSize = HermesLineSize; 212 | 213 | RtlZeroMemory( Results, NumLines * sizeof( UINT64 ) ); 214 | 215 | for ( UINT32 i = NumSamples; i--; ) 216 | { 217 | for ( UINT32 j = 0; j < NumLines; j++ ) 218 | { 219 | Results[ j ] += HermesMeasureCacheLine( ( UINT8* )BaseAddress + ( j * LineSize ) ); 220 | } 221 | } 222 | 223 | for ( UINT32 i = 0; i < NumLines; i++ ) 224 | { 225 | Results[ i ] = Results[ i ] / NumSamples; 226 | } 227 | } 228 | 229 | /** 230 | * @brief Create a CRC32-C checksum for a given transmit block. 231 | * 232 | * @param [in] Block: The block for which to create the checksum 233 | * 234 | * @return The CRC32-C checksum 235 | */ 236 | UINT64 237 | HermesCreateChecksum( 238 | _In_ PHERMES_TRANSMIT_BLOCK Block 239 | ) 240 | { 241 | UINT64 Result = 0; 242 | UINT32 Crc = ~1ul; 243 | 244 | for ( UINT32 i = 0; i < 4; i++ ) { 245 | Crc = _mm_crc32_u32( Crc, ( ( UINT32* )Block->Data )[ i ] ); 246 | } 247 | 248 | Crc = _mm_crc32_u32( Crc, Block->Length ); 249 | Result = _mm_crc32_u32( Crc, Block->Position ) ^ ~1ul; 250 | Result = ( Result << 32ull ) 251 | ^ Block->Length 252 | ^ Block->Position 253 | ^ Block->Data[ 0 ] & 0xFFFFFFFF; 254 | 255 | return Result; 256 | } 257 | 258 | UINT64 259 | HermesLinesToUint64( 260 | VOID 261 | ) 262 | { 263 | CONST UINT64 NumBits = 64; 264 | 265 | UINT64 Average [ NumBits ]{}, 266 | Likelihood[ NumBits ]{}, 267 | Result = 0; 268 | 269 | UINT32 NumSamples = 16; 270 | while ( NumSamples > 0 ) 271 | { 272 | HermesMeasureLineAverage( HermesCacheLines, 64, 10, Average ); 273 | 274 | for ( UINT32 i = 0; i < NumBits; i++ ) 275 | { 276 | if ( Average[ i ] > 250 ) 277 | Likelihood[ i ]++; 278 | } 279 | 280 | NumSamples--; 281 | } 282 | 283 | for ( UINT32 i = 0; i < NumBits; i++ ) 284 | { 285 | BOOLEAN Bit = ( Likelihood[ i ] > ( NumSamples / 2 ) ); 286 | 287 | Result |= ( UINT64 )Bit << i; 288 | } 289 | 290 | return Result; 291 | } 292 | 293 | VOID 294 | HermesLinesToBlock( 295 | _Out_ PHERMES_TRANSMIT_BLOCK Block 296 | ) 297 | { 298 | if ( Block == NULL ) 299 | { 300 | return; 301 | } 302 | 303 | RtlZeroMemory( Block, sizeof( HERMES_TRANSMIT_BLOCK ) ); 304 | 305 | CONST UINT64 NumBits = sizeof( HERMES_TRANSMIT_BLOCK ) * 8; 306 | 307 | UINT64 Average [ NumBits ]{}, 308 | Likelihood[ NumBits ]{}; 309 | 310 | UINT32 NumSamples = 16; 311 | while ( NumSamples > 0 ) 312 | { 313 | for ( UINT32 i = 0; i < NumBits; i += 32 ) 314 | { 315 | HermesMeasureLineAverage( HermesCacheLines + ( i * HermesLineSize ), 32, 10, &Average[ i ] ); 316 | } 317 | 318 | for ( UINT32 i = 0; i < NumBits; i++ ) 319 | { 320 | if ( Average[ i ] > 250 ) 321 | Likelihood[ i ]++; 322 | } 323 | 324 | NumSamples--; 325 | } 326 | 327 | for ( UINT32 i = 0; i < NumBits; i++ ) 328 | { 329 | BOOLEAN Bit = ( Likelihood[ i ] > ( NumSamples / 2 ) ); 330 | 331 | Block->AsUInt[ i / 64 ] |= ( UINT64 )Bit << ( i % 64 ); 332 | } 333 | } 334 | 335 | VOID 336 | HermesBroadcastTransmitBlock( 337 | _In_ PHERMES_TRANSMIT_BLOCK Block 338 | ) 339 | { 340 | UINT64 FlushCount = HERMES_TRANSMIT_FLUSH_COUNT; 341 | 342 | while ( FlushCount > 0 ) 343 | { 344 | HermesSetLines( Block->AsUInt, sizeof( HERMES_TRANSMIT_BLOCK ) * 8 ); 345 | 346 | FlushCount--; 347 | } 348 | } 349 | 350 | BOOLEAN 351 | HermesSendReliableTransmitBlock( 352 | _In_ PHERMES_TRANSMIT_BLOCK Block 353 | ) 354 | { 355 | UINT64 Timeout = HERMES_TRANSMIT_TIMEOUT; 356 | HERMES_TRANSMIT_BLOCK OurBlock{ }; 357 | 358 | while ( Timeout > 0 ) 359 | { 360 | HermesBroadcastTransmitBlock( Block ); 361 | HermesLinesToBlock( &OurBlock ); 362 | 363 | if ( OurBlock.Acknowledgement == Block->Checksum ) 364 | { 365 | return TRUE; 366 | } 367 | 368 | Timeout--; 369 | } 370 | 371 | return FALSE; 372 | } 373 | 374 | BOOLEAN 375 | HermesReceiveReliableTransmitBlock( 376 | _Out_ PHERMES_TRANSMIT_BLOCK RecvBlock 377 | ) 378 | { 379 | UINT64 Timeout = HERMES_TRANSMIT_TIMEOUT; 380 | HERMES_TRANSMIT_BLOCK Block{ }; 381 | 382 | while ( Timeout > 0 ) 383 | { 384 | // 385 | // Obtain a transmit block from the cache lines 386 | // 387 | HermesLinesToBlock( &Block ); 388 | 389 | if ( Block.Checksum != HermesCreateChecksum( &Block ) ) 390 | { 391 | // 392 | // Continue attempting to read a transmit block whose 393 | // hash is correct, uness we reached timeout. 394 | // 395 | Timeout--; 396 | 397 | continue; 398 | } 399 | 400 | // 401 | // Set the acknowledgement field in the transmit block, 402 | // when the sender reads this field from it. It will 403 | // move onto the next block if there is one in the queue. 404 | // 405 | Block.Acknowledgement = Block.Checksum; 406 | 407 | // 408 | // Store the received transmit block for further processing 409 | // 410 | RtlCopyMemory( RecvBlock, &Block, sizeof( HERMES_TRANSMIT_BLOCK ) ); 411 | 412 | // 413 | // Broadcast the block along with the acknowledgement 414 | // back to the sender. 415 | // 416 | HermesBroadcastTransmitBlock( &Block ); 417 | 418 | return TRUE; 419 | } 420 | 421 | return FALSE; 422 | } 423 | 424 | BOOLEAN 425 | HermesSendTransmissionEvent( 426 | _In_ BOOLEAN StartOrEnd 427 | ) 428 | { 429 | UINT64 Timeout = HERMES_TRANSMIT_TIMEOUT; 430 | 431 | HERMES_TRANSMIT_BLOCK Block { }, 432 | CurBlock{ }; 433 | 434 | if ( StartOrEnd == TRUE ) 435 | { 436 | Block.Data[ 0 ] = HermesTransmitStartMagic.m128i_u64[ 0 ]; 437 | Block.Data[ 1 ] = HermesTransmitStartMagic.m128i_u64[ 1 ]; 438 | } 439 | else 440 | { 441 | Block.Data[ 0 ] = HermesTransmitEndMagic.m128i_u64[ 0 ]; 442 | Block.Data[ 1 ] = HermesTransmitEndMagic.m128i_u64[ 1 ]; 443 | } 444 | 445 | Block.Length = 16; 446 | Block.Checksum = HermesCreateChecksum( &Block ); 447 | 448 | while ( Timeout > 0 ) 449 | { 450 | // 451 | // Broadcast our transmission magic 452 | // 453 | HermesBroadcastTransmitBlock( &Block ); 454 | 455 | // 456 | // Read the current transmission block to see if ackmnowledgement 457 | // cache lines are set. 458 | // 459 | HermesLinesToBlock( &CurBlock ); 460 | 461 | if ( CurBlock.Acknowledgement == Block.Checksum ) 462 | { 463 | // 464 | // Indicate the transmission event was successfully acknowledged 465 | // 466 | return TRUE; 467 | } 468 | 469 | Timeout--; 470 | } 471 | 472 | return FALSE; 473 | } 474 | 475 | BOOLEAN 476 | HermesGetTransmissionEvent( 477 | _In_ PHERMES_TRANSMIT_BLOCK Block, 478 | _Out_ PBOOLEAN StartOrEnd 479 | ) 480 | { 481 | if ( Block->Data[ 0 ] == HermesTransmitStartMagic.m128i_u64[ 0 ] && 482 | Block->Data[ 1 ] == HermesTransmitStartMagic.m128i_u64[ 1 ] ) 483 | { 484 | *StartOrEnd = TRUE; 485 | 486 | return TRUE; 487 | } 488 | 489 | if ( Block->Data[ 0 ] == HermesTransmitEndMagic.m128i_u64[ 0 ] && 490 | Block->Data[ 1 ] == HermesTransmitEndMagic.m128i_u64[ 1 ] ) 491 | { 492 | *StartOrEnd = FALSE; 493 | 494 | return TRUE; 495 | } 496 | 497 | return FALSE; 498 | } 499 | 500 | #include 501 | 502 | BOOLEAN 503 | HermesSendData( 504 | _In_ LPVOID Data, 505 | _In_ SIZE_T DataLength 506 | ) 507 | { 508 | SIZE_T CurLength = 0, 509 | CurBlockNum = 0, 510 | BlockDataLength = sizeof( HERMES_TRANSMIT_BLOCK::Data ); 511 | 512 | HERMES_TRANSMIT_BLOCK CurBlock{ }; 513 | 514 | if ( HermesSendTransmissionEvent( TRUE ) == FALSE ) 515 | { 516 | // 517 | // Transmission event was not received, and resulted in a timeout 518 | // 519 | return FALSE; 520 | } 521 | 522 | SIZE_T AlignedLength = DataLength & ~( BlockDataLength - 1 ), 523 | RemainingLength = DataLength & ( BlockDataLength - 1 ); 524 | 525 | while ( CurLength < AlignedLength || RemainingLength != 0 ) 526 | { 527 | CurBlock.Length = BlockDataLength; 528 | 529 | if ( CurLength >= AlignedLength ) 530 | { 531 | CurBlock.Length = RemainingLength; 532 | RemainingLength = 0; 533 | } 534 | 535 | CurBlock.Position = CurBlockNum; 536 | 537 | RtlCopyMemory( CurBlock.Data, ( UINT8* )Data + CurLength, CurBlock.Length ); 538 | 539 | CurBlock.Checksum = HermesCreateChecksum( &CurBlock ); 540 | 541 | if ( HermesSendReliableTransmitBlock( &CurBlock ) == FALSE ) 542 | { 543 | // 544 | // Transmission block was not received, and resulted in a timeout 545 | // 546 | return FALSE; 547 | } 548 | 549 | CurBlockNum += 1; 550 | CurLength += BlockDataLength; 551 | } 552 | 553 | if ( HermesSendTransmissionEvent( FALSE ) == FALSE ) 554 | { 555 | // 556 | // Transmission event not received, and resulted in a timeout 557 | // 558 | return FALSE; 559 | } 560 | 561 | return TRUE; 562 | } 563 | 564 | BOOLEAN 565 | HermesReceiveData( 566 | _Out_ LPVOID Data, 567 | _In_ SIZE_T BufferLength 568 | ) 569 | { 570 | RtlZeroMemory( Data, BufferLength ); 571 | 572 | HERMES_TRANSMIT_BLOCK Block{ }; 573 | BOOLEAN TransmissionState = FALSE, 574 | DataWritten = FALSE; 575 | 576 | HermesReceiveReliableTransmitBlock( &Block ); 577 | 578 | if ( HermesGetTransmissionEvent( &Block, &TransmissionState ) == FALSE ) 579 | { 580 | // 581 | // Transmission event was not received in time, return false for now. 582 | // 583 | return FALSE; 584 | } 585 | 586 | while ( TransmissionState == TRUE ) 587 | { 588 | if ( HermesReceiveReliableTransmitBlock( &Block ) == TRUE ) 589 | { 590 | // 591 | // Attempt to obtain a transmission event from the read block to see 592 | // if the transmission has ended. 593 | // 594 | if ( HermesGetTransmissionEvent( &Block, &TransmissionState ) == FALSE ) 595 | { 596 | UINT8* Position = ( UINT8* )Data + ( Block.Position * sizeof( Block.Data ) ), 597 | * Boundary = ( UINT8* )Data + BufferLength; 598 | 599 | if ( Position > Boundary ) 600 | { 601 | // 602 | // Buffer is too small 603 | // 604 | return FALSE; 605 | } 606 | 607 | RtlCopyMemory( Position, Block.Data, Block.Length ); 608 | 609 | DataWritten = TRUE; 610 | } 611 | } 612 | else return FALSE; 613 | } 614 | 615 | return DataWritten; 616 | } 617 | 618 | #pragma optimize( pop ) 619 | #pragma warning( pop ) 620 | --------------------------------------------------------------------------------