├── LICENSE ├── README.md └── poly.py /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 REDD 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PolyZip 2 | ## (Nothing.. is.. Safe..) 3 | 4 | PolyZip is a Python 3 command-line tool that embeds ZIP archives into a wide range of media and document formats—PNG, JPEG, GIF, PDF, BMP, WebP, TIFF, WAV, MP3, FLAC, OGG, AVI, MKV, WebM, FLV, ICO, CUR, ICNS, MP4, MOV, M4A, EXE, DLL, ELF, MSI, TTF, OTF, and WOFF —by leveraging each format's trailing-data tolerance or internal chunk/atom structure. For PNG, it injects the ZIP archive into the first IDAT chunk and corrects central directory offsets; for JPEG/GIF/PDF, it appends ZIP data after the EOI/trailer/EOF markers; for RIFF-based formats (BMP, WebP, WAV, AVI), it appends ZIP data beyond the declared chunk size; for TIFF, MP3, FLAC, OGG, ICO/CUR/ICNS, MP4/MOV/M4A, ELF, MSI, and font formats (TTF/OTF/WOFF), it appends after the last valid header/frame/atom and updates ZIP offsets accordingly. The resulting files display normally in standard viewers yet remain fully valid ZIP archives when renamed to `.zip` or extracted with standard tools. 5 | 6 | **🆕 NEW: Enhanced with full encryption support!** PolyZip 2.1.2 now includes AES-256 encryption for both file hiding and chat functionality. Hide encrypted files or create encrypted chat logs within any supported file format. All encrypted data is completely invisible to external analysis and protected by military-grade encryption using Argon2id password derivation (with Scrypt fallback) or secure key files. 7 | 8 | Original PNG+ZIP polyglot technique by [DavidBuchanan314](https://github.com/DavidBuchanan314/tweetable-polyglot-png); extended multi-format embedding, multi-file packing, extraction, detection, and encrypted chat implementation by [InfoSecREDD](https://github.com/InfoSecREDD). 9 | 10 | ## Features 11 | 12 | - Create polyglot files from multiple formats: 13 | - **PNG + ZIP** 14 | - **JPG + ZIP** 15 | - **JPEG + ZIP** 16 | - **GIF + ZIP** 17 | - **PDF + ZIP** 18 | - **BMP + ZIP** 19 | - **WebP + ZIP** 20 | - **TIFF + ZIP** 21 | - **WAV + ZIP** 22 | - **MP3 + ZIP** 23 | - **FLAC + ZIP** 24 | - **OGG + ZIP** 25 | - **AVI + ZIP** 26 | - **MKV + ZIP** 27 | - **WebM + ZIP** 28 | - **FLV + ZIP** 29 | - **ICO + ZIP** 30 | - **CUR + ZIP** 31 | - **ICNS + ZIP** 32 | - **MP4 + ZIP** 33 | - **MOV + ZIP** 34 | - **M4A + ZIP** 35 | - **EXE + ZIP** 36 | - **DLL + ZIP** 37 | - **ELF + ZIP** 38 | - **MSI + ZIP** 39 | - **TTF + ZIP** 40 | - **OTF + ZIP** 41 | - **WOFF + ZIP** 42 | 43 | - **🔒 Comprehensive Encryption System**: 44 | - **Encrypted File Hiding**: Secure file embedding with AES-256-GCM encryption 45 | - **Encrypted Chat Logs**: Create secure chat logs hidden in any supported file format 46 | - **Military-Grade Security**: AES-256-GCM encryption with authenticated encryption 47 | - **Robust Key Derivation**: Argon2id password derivation with Scrypt fallback for maximum compatibility 48 | - **Key File Authentication**: Auto-generated secure keys with file-based authentication 49 | - **Perfect Steganography**: Files appear completely normal to external analysis 50 | - **Tamper Detection**: GCM authentication prevents modification without detection 51 | - **Export Options**: Chat export to HTML, JSON, or text formats 52 | 53 | - Automatic detection of embedded content 54 | - Extract hidden files from polyglot containers 55 | - Support for multiple files in a single container 56 | - Automatic virtual environment management 57 | - Cross-platform compatibility 58 | 59 | ## Installation 60 | 61 | 1. Clone the repository: 62 | ```bash 63 | git clone https://github.com/InfoSecREDD/PolyZip.git 64 | cd PolyZip 65 | ``` 66 | 67 | 2. Make the script executable: 68 | ```bash 69 | chmod +x poly.py 70 | ``` 71 | 72 | 3. Run the script - it will automatically set up the required environment: 73 | ```bash 74 | ./poly.py 75 | ``` 76 | 77 | ## Usage 78 | 79 | ### Packing Files 80 | 81 | Embed one or more files into a cover file: 82 | ```bash 83 | ./poly.py pack cover.[format] file1 [file2 ...] output.[format] [--key=file.key|--password=pass] 84 | ``` 85 | 86 | **Basic Examples:** 87 | ```bash 88 | # Hide files without encryption 89 | ./poly.py pack cover.png secret.txt output.png 90 | 91 | # Hide multiple files 92 | ./poly.py pack cover.jpg file1.txt file2.pdf output.jpg 93 | ``` 94 | 95 | **🔒 Encrypted File Hiding:** 96 | ```bash 97 | # Hide files with password encryption 98 | ./poly.py pack cover.png secret.txt encrypted.png --password=MySecurePass123 99 | 100 | # Hide files with key file encryption (auto-generates key if missing) 101 | ./poly.py pack cover.pdf confidential.doc encrypted.pdf --key=mykey.key 102 | 103 | # Hide multiple files with encryption 104 | ./poly.py pack cover.mp4 file1.txt file2.jpg encrypted.mp4 --password=SecretPass 105 | ``` 106 | 107 | ### Extracting Files 108 | 109 | Extract hidden files from a polyglot container: 110 | ```bash 111 | ./poly.py extract input.[format] [output_directory] [--key=file.key|--password=pass] 112 | ``` 113 | 114 | If no output directory is specified, files will be extracted to a directory named after the input file. 115 | 116 | **Basic Examples:** 117 | ```bash 118 | # Extract unencrypted files 119 | ./poly.py extract hidden.png 120 | 121 | # Extract to specific directory 122 | ./poly.py extract hidden.png extracted_files/ 123 | ``` 124 | 125 | **🔒 Encrypted File Extraction:** 126 | ```bash 127 | # Extract password-protected files 128 | ./poly.py extract encrypted.png decrypted/ --password=MySecurePass123 129 | 130 | # Extract key-protected files 131 | ./poly.py extract encrypted.pdf decrypted/ --key=mykey.key 132 | 133 | # Extract with automatic directory creation 134 | ./poly.py extract encrypted.mp4 --password=SecretPass 135 | ``` 136 | 137 | ### Detecting Embedded Content 138 | 139 | Scan files for embedded content: 140 | ```bash 141 | ./poly.py detect [input.[png|jpg|jpeg|gif|pdf|bmp|webp|tiff|tif|wav|mp3|flac|ogg|avi|mkv|webm|flv|ico|cur|icns|mp4|mov|m4a|exe|dll|elf|msi|ttf|otf|woff]] 142 | ``` 143 | 144 | If no file is specified, it will scan the current directory for all supported files. 145 | 146 | Example: 147 | ```bash 148 | ./poly.py detect 149 | ``` 150 | 151 | ### 🔒 Encrypted Hidden Chat System 152 | 153 | #### Creating Encrypted Chats 154 | 155 | Create a new encrypted chat hidden in any file format: 156 | 157 | **With Password Protection:** 158 | ```bash 159 | ./poly.py chat create cover.[any] output.[any] "Chat Title" --password=yourpassword 160 | ``` 161 | 162 | **With Key File Authentication:** 163 | ```bash 164 | ./poly.py chat create cover.[any] output.[any] "Chat Title" --key=keyfile.key 165 | ``` 166 | 167 | **Examples:** 168 | ```bash 169 | # Create password-protected chat in PNG 170 | ./poly.py chat create photo.png secret_chat.png "Team Communications" --password=SecurePass123 171 | 172 | # Create key-protected chat in PDF 173 | ./poly.py chat create document.pdf secret_chat.pdf "Private Discussion" --key=team.key 174 | 175 | # Create unencrypted chat in JPEG 176 | ./poly.py chat create image.jpg chat.jpg "Public Chat" 177 | 178 | # Works with any file format 179 | ./poly.py chat create video.mp4 secret_chat.mp4 "Mission Planning" --password=classified 180 | ``` 181 | 182 | #### Adding Messages 183 | 184 | Add messages to existing chats: 185 | 186 | ```bash 187 | ./poly.py chat add chat.[any] "Sender Name" "Message content" [encryption_option] 188 | ``` 189 | 190 | **Examples:** 191 | ```bash 192 | # Add to password-protected chat 193 | ./poly.py chat add secret_chat.png "Alice" "Hello team!" --password=SecurePass123 194 | 195 | # Add to key-protected chat 196 | ./poly.py chat add secret_chat.pdf "Bob" "Mission status update" --key=team.key 197 | 198 | # Add to unencrypted chat 199 | ./poly.py chat add chat.jpg "Charlie" "Public message" 200 | ``` 201 | 202 | #### Reading Chats 203 | 204 | Display chat contents: 205 | 206 | ```bash 207 | ./poly.py chat read chat.[any] [--format=terminal|json|html] [encryption_option] 208 | ``` 209 | 210 | **Examples:** 211 | ```bash 212 | # Read password-protected chat 213 | ./poly.py chat read secret_chat.png --password=SecurePass123 214 | 215 | # Read key-protected chat with JSON format 216 | ./poly.py chat read secret_chat.pdf --format=json --key=team.key 217 | 218 | # Read with HTML format 219 | ./poly.py chat read chat.mp4 --format=html 220 | ``` 221 | 222 | #### Exporting Chats 223 | 224 | Export chats to files: 225 | 226 | ```bash 227 | ./poly.py chat export chat.[any] output.[txt|json|html] [encryption_option] 228 | ``` 229 | 230 | **Examples:** 231 | ```bash 232 | # Export to HTML 233 | ./poly.py chat export secret_chat.png backup.html --password=SecurePass123 234 | 235 | # Export to JSON 236 | ./poly.py chat export secret_chat.pdf backup.json --key=team.key 237 | 238 | # Export to text 239 | ./poly.py chat export chat.jpg backup.txt 240 | ``` 241 | 242 | ### 🔐 Security Features 243 | 244 | - **AES-256-GCM Encryption**: Military-grade encryption with authenticated encryption 245 | - **Argon2id Password Derivation**: Secure password hashing resistant to GPU attacks (with Scrypt fallback for compatibility) 246 | - **Key File Authentication**: Auto-generated secure keys for file-based authentication 247 | - **Perfect Steganography**: Hidden data is completely undetectable by external analysis 248 | - **Tamper Detection**: GCM authentication prevents modification of encrypted data 249 | - **No Information Leakage**: Even if extracted, encrypted data remains unreadable 250 | 251 | **💡 Pro Tip**: Files with hidden chats can be opened normally in any image viewer, PDF reader, or media player. The hidden chat is completely invisible to external tools and analysis. 252 | 253 | ## Technical Details 254 | 255 | Our tool leverages each format's tolerance for extra or embedded data beyond its standard file structure. Most viewers stop reading at defined end markers or fields and ignore trailing bytes, allowing a valid ZIP archive to be appended without affecting normal display. 256 | 257 | #### PNG + ZIP 258 | - **Chunk-based format**: PNG files begin with an 8-byte signature, followed by a sequence of chunks (4-byte length, 4-byte type, chunk data, 4-byte CRC). IDAT chunks contain compressed image data; decoders concatenate all IDAT payloads based on declared lengths and verify each chunk's CRC. Any extra bytes beyond a chunk's specified length (including our appended ZIP) are ignored by standard PNG parsers. 259 | - **Embedding approach**: We insert the ZIP archive into the first IDAT chunk after its declared data, update the length and CRC so the image remains valid, and leave IEND untouched. Viewers decode the image normally, while the ZIP central directory offsets point into the embedded data. 260 | 261 | #### JPEG + ZIP 262 | - **Marker-based format**: JPEG streams consist of markers (`FF D8` SOI, APPn segments, `FF DA` SOS, compressed scan data, `FF D9` EOI). Parsers stop decoding at the EOI marker and ignore any trailing bytes. 263 | - **Embedding approach**: We split the file at the final `FF D9`, write out the image data, then append the ZIP archive. ZIP offsets are fixed so renaming to `.zip` yields a valid archive, while image viewers ignore the appended data. 264 | 265 | #### GIF + ZIP 266 | - **Block-oriented format**: GIF files start with a header (`GIF87a`/`GIF89a`), logical screen descriptor, optional color tables, data blocks, and end with a single trailer byte (`0x3B`). 267 | - **Embedding approach**: We truncate the file at the trailer, append our ZIP archive, and adjust ZIP pointers. GIF viewers stop at `0x3B` and ignore the ZIP data. 268 | 269 | #### PDF + ZIP 270 | - **Text-based format**: PDF documents end with a `%%EOF` marker (often followed by a newline). PDF readers scan backwards to locate the last `%%EOF` and ignore any content that follows. 271 | - **Embedding approach**: We write out the PDF up to and including the final `%%EOF`, then append the ZIP archive. PDF viewers load the document normally, while ZIP tools see a playable archive. 272 | 273 | #### BMP + ZIP 274 | - **Header-driven format**: BMP files start with 'BM', followed by a 4-byte file size field at offset 2 (little-endian), reserved fields, and a pixel-data offset pointer. Readers rely on the declared file size or pixel-data offset to load image data, ignoring trailing data. 275 | - **Embedding approach**: We append the ZIP archive after the pixel data, then fix ZIP offsets. Standard image loaders read only the declared bytes and dismiss extra content. 276 | 277 | #### WebP + ZIP / WAV + ZIP / AVI + ZIP 278 | - **RIFF container**: These formats use a RIFF header (`RIFF`, 4-byte total size, format tag) followed by sub-chunks (e.g., `WEBP`, `WAVE`, `AVI `). Parsers read chunks based on length fields and ignore bytes beyond the container size. 279 | - **Embedding approach**: We append the ZIP archive after the declared RIFF data and update ZIP offsets so the archive remains valid. 280 | 281 | #### TIFF + ZIP 282 | - **Tagged format**: TIFF files begin with an endianness marker (`II`/`MM`), magic number 42, and an offset to the first Image File Directory (IFD). Readers traverse IFD pointers and image data strips; trailing bytes are never referenced. 283 | - **Embedding approach**: We append the ZIP archive at the end, then update ZIP offsets. TIFF viewers ignore the extra data. 284 | 285 | #### MP3 + ZIP 286 | - **Frame-based format**: MP3 audio is composed of sequential frames, each with a frame sync header (`0xFFF`) and payload. Decoders process frames until no valid header remains; any extra bytes at the end are ignored. 287 | - **Embedding approach**: We append the ZIP archive after the last frame and fix ZIP offsets; audio players play all frames and disregard the ZIP. 288 | 289 | #### FLAC + ZIP 290 | - **Block-based format**: FLAC files start with `fLaC`, followed by metadata blocks (ending with the LAST-METADATA-BLOCK flag) and audio frames. Decoders read until the last audio frame and ignore trailing data. 291 | - **Embedding approach**: We append the ZIP archive after the final frame and adjust ZIP offsets. 292 | 293 | #### ICO + ZIP / CUR + ZIP 294 | - **Directory-entry format**: Icon/Cursor files begin with an ICONDIR header (entry count) and ICONDIRENTRY array (each with image data offset and size). Readers load images based on these offsets and ignore data beyond. 295 | - **Embedding approach**: We append the ZIP archive after all image entries and fix ZIP offsets. 296 | 297 | #### MP4 + ZIP / MOV + ZIP 298 | - **ISO BMFF container**: MP4/MOV files are organized in boxes (4-byte size, 4-byte type, data). Parsers read each box based on its size field and disregard any extra bytes after the final box. 299 | - **Embedding approach**: We append the ZIP archive after the last atom and update ZIP offsets so renaming to `.zip` yields a valid archive. 300 | 301 | #### EXE + ZIP / DLL + ZIP 302 | - **PE executable format**: Windows PE files start with an `MZ` DOS stub, followed by a PE header (at `e_lfanew`), section table, and section data blocks. Loaders and PE tools reference section table sizes to map data; trailing bytes beyond the last section are ignored. 303 | - **Embedding approach**: We append the ZIP archive after all declared sections and fix ZIP offsets so the archive remains valid. 304 | 305 | #### OGG + ZIP 306 | - **Container format**: OGG files use a bitstream container with pages containing packet data. Each page has a header with capture pattern, stream serial number, and page sequence. Decoders process pages sequentially until no more valid pages remain; trailing bytes are ignored. 307 | - **Embedding approach**: We append the ZIP archive after the last valid OGG page and update ZIP offsets. Audio players decode all valid pages and ignore the appended ZIP data. 308 | 309 | #### MKV + ZIP 310 | - **Matroska container**: MKV files use the Extensible Binary Meta Language (EBML) format with elements containing size and data fields. The container has a defined structure with header, segment info, tracks, and clusters. Parsers read elements based on size fields and ignore trailing data. 311 | - **Embedding approach**: We append the ZIP archive after the last cluster element and fix ZIP offsets so the archive remains valid while video players ignore extra bytes. 312 | 313 | #### WebM + ZIP 314 | - **Matroska-based format**: WebM is a subset of the Matroska container using VP8/VP9 video and Vorbis/Opus audio. Like MKV, it uses EBML structure with size-defined elements. Decoders stop at the last valid element and ignore trailing bytes. 315 | - **Embedding approach**: We append the ZIP archive after the final cluster and update ZIP offsets. WebM players process only the declared elements and disregard the ZIP data. 316 | 317 | #### FLV + ZIP 318 | - **Adobe Flash Video format**: FLV files start with an FLV header (signature, version, flags, header length) followed by tag packets (type, size, timestamp, data). Decoders process tags sequentially based on size fields and ignore any data beyond the last valid tag. 319 | - **Embedding approach**: We append the ZIP archive after the final tag and adjust ZIP offsets. Flash players and compatible decoders ignore the trailing ZIP data. 320 | 321 | #### ICNS + ZIP 322 | - **Apple Icon format**: ICNS files begin with a 4-byte type identifier (`icns`), followed by a 4-byte file length, then a series of icon elements (4-byte type, 4-byte length, icon data). Readers process elements based on declared lengths and ignore trailing bytes. 323 | - **Embedding approach**: We append the ZIP archive after all icon elements and fix ZIP offsets so the archive remains valid while icon viewers ignore extra data. 324 | 325 | #### M4A + ZIP 326 | - **ISO BMFF container**: M4A files use the same ISO Base Media File Format as MP4, organized in atoms/boxes (4-byte size, 4-byte type, data). Audio players read atoms based on size fields and ignore trailing data beyond the last atom. 327 | - **Embedding approach**: We append the ZIP archive after the final atom and update ZIP offsets. M4A players process only the declared atoms and disregard the ZIP data. 328 | 329 | #### ELF + ZIP 330 | - **Linux executable format**: ELF files start with an ELF header containing entry point, program header table offset, section header table offset, and other metadata. Loaders map segments based on program headers and ignore data beyond the declared sections. 331 | - **Embedding approach**: We append the ZIP archive after all section data and fix ZIP offsets so the archive remains valid while ELF loaders ignore trailing bytes. 332 | 333 | #### MSI + ZIP 334 | - **Microsoft Installer format**: MSI files are structured storage compound documents with a defined directory structure. Installers read the compound document structure and ignore any trailing data beyond the declared storage size. 335 | - **Embedding approach**: We append the ZIP archive after the compound document structure and update ZIP offsets. MSI installers process only the declared storage content and ignore extra bytes. 336 | 337 | #### TTF + ZIP / OTF + ZIP / WOFF + ZIP 338 | - **Font file formats**: TTF/OTF files contain font tables with a table directory specifying offsets and lengths. WOFF adds compression wrapper with declared uncompressed size. Font renderers read tables based on directory entries and ignore trailing data. 339 | - **Embedding approach**: We append the ZIP archive after all font table data and fix ZIP offsets. Font systems load only the declared tables and disregard the ZIP data. 340 | 341 | #### 🔒 Encrypted Chat Technical Implementation 342 | 343 | The encrypted chat system uses a sophisticated multi-layered approach: 344 | 345 | 1. **Data Structure**: Chat messages are stored in JSON format with timestamps, participants, and message content 346 | 2. **Encryption Layer**: 347 | - **AES-256-GCM**: Provides both confidentiality and authentication 348 | - **Argon2id**: Password-based key derivation resistant to GPU attacks (with Scrypt fallback) 349 | - **Random Salt**: Unique salt for each chat prevents rainbow table attacks 350 | - **Key Files**: Auto-generated 256-bit keys for file-based authentication 351 | 3. **Steganographic Embedding**: Encrypted data is embedded using the same techniques as ZIP files 352 | 4. **Format Agnostic**: Works with any supported file format (PNG, JPEG, PDF, MP4, etc.) 353 | 5. **Tamper Detection**: GCM authentication tag prevents modification without detection 354 | 355 | **Security Guarantees**: 356 | - Even if the cover file is extracted, the chat data remains encrypted 357 | - No information leakage about chat contents or participants 358 | - Files appear completely normal to external analysis tools 359 | - Brute force attacks are mitigated by Argon2id's computational cost 360 | 361 | ### Limitations 362 | 363 | 1. **Cover File Requirements**: 364 | - **PNG**: 365 | - Must compress well to leave space for embedded data 366 | - Must have at least 257 unique colors if not using a palette (to prevent Twitter from converting to indexed color) 367 | - Example: A 1000x1000 pixel PNG with 256 colors will be converted to indexed color by Twitter, breaking the polyglot 368 | - Example: A noisy or complex PNG might not compress well, leaving little space for embedded data 369 | 370 | - **JPEG**: 371 | - Must have a valid EOI (End of Image) marker (0xFFD9) 372 | - Some JPEGs may have multiple EOI markers - only the last one is used 373 | - Example: A JPEG with corrupted EOI marker will fail to embed data 374 | 375 | - **GIF**: 376 | - Must have a valid trailer marker (0x3B) 377 | - Multi-frame GIFs are supported, but data is embedded after the last frame 378 | - Example: A GIF with corrupted trailer will fail to embed data 379 | 380 | - **PDF**: 381 | - Must have a valid EOF marker (%%EOF) 382 | - Some PDFs may have multiple EOF markers - only the last one is used 383 | - Example: A PDF with corrupted EOF marker will fail to embed data 384 | 385 | - **BMP**: 386 | - Must have a valid file size field at offset 2 (little endian); viewers ignore trailing bytes 387 | - Example: A malformed BMP size field may prevent the image from loading 388 | 389 | - **WebP**: 390 | - Must start with "RIFF" and include the "WEBP" chunk within the first 12 bytes; parsers read only declared chunk size 391 | - Example: Missing or corrupted "WEBP" chunk can break embedding 392 | 393 | - **TIFF**: 394 | - Must have a valid byte-order marker ("II" or "MM") and correct IFD pointers; viewers ignore trailing data 395 | - Example: Corrupted IFD pointers may cause the viewer to reject the file 396 | 397 | - **WAV**: 398 | - Must start with "RIFF" and include the "WAVE" chunk; header chunk size must match file length 399 | - Example: Incorrect RIFF size can break audio playback 400 | 401 | - **MP3**: 402 | - Must end with a proper frame sequence; decoders ignore trailing bytes after the last valid frame 403 | - Example: Frame sync errors will prevent embedding 404 | 405 | - **FLAC**: 406 | - Must start with the "fLaC" marker; decoders ignore trailing bytes after the last metadata block 407 | - Example: Missing STREAMINFO block will prevent decoding 408 | 409 | - **AVI**: 410 | - Must start with "RIFF" and include the "AVI " chunk; chunk size header must be correct 411 | - Example: Corrupted chunk size will break video playback 412 | 413 | - **ICO**: 414 | - Must include a valid icon directory and entries; parsers read only declared images 415 | - Example: Invalid entry headers may prevent the icon from displaying 416 | 417 | - **CUR**: 418 | - Must include a valid cursor directory and entries; trailing bytes are ignored 419 | - Example: Incorrect hotspot coordinates can affect cursor positioning 420 | 421 | - **MP4/MOV**: 422 | - Must include valid ISO BMFF atoms ("ftyp", "moov"); atom size fields must match declared lengths 423 | - Example: Missing or misplaced "moov" atom will break playback 424 | 425 | - **OGG**: 426 | - Must have valid OGG page headers with correct capture patterns and checksums 427 | - Example: Corrupted page headers will prevent audio/video decoding 428 | 429 | - **MKV**: 430 | - Must include valid EBML header and segment structure; element size fields must be accurate 431 | - Example: Missing or corrupted EBML header will prevent video playback 432 | 433 | - **WebM**: 434 | - Must be a valid Matroska subset with VP8/VP9 video and Vorbis/Opus audio codecs 435 | - Example: Using unsupported codecs may cause playback failures 436 | 437 | - **FLV**: 438 | - Must have valid FLV header and properly formatted tag structure 439 | - Example: Incorrect tag sizes will break Flash video playback 440 | 441 | - **ICNS**: 442 | - Must include valid icon elements with correct type identifiers and sizes 443 | - Example: Malformed icon elements may prevent macOS from displaying the icon 444 | 445 | - **M4A**: 446 | - Must follow ISO BMFF structure with valid audio-specific atoms ("mdhd", "stsd") 447 | - Example: Missing audio track information will prevent playback 448 | 449 | - **ELF**: 450 | - Must have valid ELF header with correct magic number and architecture information 451 | - Example: Incorrect architecture or corrupted headers will prevent execution 452 | 453 | - **MSI**: 454 | - Must be a valid compound document with proper storage structure 455 | - Example: Corrupted storage directory will prevent installation 456 | 457 | - **TTF/OTF**: 458 | - Must have valid font table directory and required tables ("cmap", "glyf", "head", "hhea", "hmtx", "loca", "maxp", "name", "post") 459 | - Example: Missing required tables will prevent font rendering 460 | 461 | - **WOFF**: 462 | - Must have valid WOFF header with correct signature and table directory 463 | - Example: Incorrect compression or table checksums will prevent font loading 464 | 465 | 2. **Size Limitations**: 466 | - **PNG**: 467 | - Maximum size depends on the image characteristics and hosting platform 468 | - For Twitter: Must be under 3MB to avoid JPEG conversion 469 | - Compressed size must be less than `(width * height) - size_of_embedded_file` 470 | - Image must have enough space in its IDAT chunks to embed the ZIP data 471 | - Example: A 2000x2000 PNG with simple content might compress to 500KB, leaving 2.5MB for embedded data 472 | - Example: A 2000x2000 PNG with complex content might compress to 2.9MB, leaving only 100KB for embedded data 473 | 474 | - **JPEG/GIF/PDF**: 475 | - No strict size limits beyond the file format's maximum size 476 | - Limited only by the available space after the file's end marker 477 | - Hosting platforms may impose their own size restrictions 478 | - Example: A 1MB JPEG can embed up to 2MB of data (if the platform allows 3MB total) 479 | - Example: A 5MB PDF can embed up to 5MB of data (if the platform allows 10MB total) 480 | 481 | - **BMP**: 482 | - Limited by the 32-bit file size field (max ~4 GB); can embed up to (file size field) minus header size 483 | - Example: Embedding near 4 GB may hit format or hosting limits 484 | 485 | - **WebP**: 486 | - Limited by the 32-bit RIFF chunk size (max ~4 GB); subject to image hosting size restrictions 487 | - Example: Very large WebP files may be disallowed by some platforms 488 | 489 | - **TIFF**: 490 | - Limited by 32-bit IFD offsets (max ~4 GB); trailing data supports large payloads 491 | - Example: Hosting large TIFFs may incur platform-specific size constraints 492 | 493 | - **WAV**: 494 | - Limited by 32-bit RIFF chunk size (max ~4 GB); decoders ignore trailing bytes 495 | - Example: Embedding several GB of data in WAV may exceed hosting quotas 496 | 497 | - **MP3**: 498 | - No strict format size limit; embed until file system or hosting limits are reached 499 | - Example: Some services re-encode or strip trailing bytes on MP3 uploads 500 | 501 | - **FLAC**: 502 | - No strict format size limit; embed until file system or hosting limits are reached 503 | - Example: FLAC uploads may be converted or transcoded by certain platforms 504 | 505 | - **AVI**: 506 | - Limited by 32-bit RIFF chunk size (max ~4 GB); supports large embedded payloads 507 | - Example: Video hosting sites may strip trailing bytes when transcoding 508 | 509 | - **ICO/CUR**: 510 | - Limited by 32-bit directory offsets; can embed up to (file size) minus icon data 511 | - Example: Many icon hosting services discard non-icon data 512 | 513 | - **MP4/MOV**: 514 | - Limited by 32-bit atom size fields (max ~4 GB); larger files require extended atoms not commonly supported 515 | - Example: Embedding >4 GB requires extended format support, which is uncommon in viewers 516 | 517 | - **OGG**: 518 | - No strict format size limit; limited by available disk space and hosting platform restrictions 519 | - Example: Large OGG files may be transcoded by streaming services, removing ZIP data 520 | 521 | - **MKV**: 522 | - Limited by EBML variable-length encoding (practically unlimited for most use cases) 523 | - Example: Some players may have performance issues with very large files 524 | 525 | - **WebM**: 526 | - Same as MKV but typically smaller due to web optimization requirements 527 | - Example: Web browsers may impose stricter size limits for streaming 528 | 529 | - **FLV**: 530 | - Limited by 32-bit size fields in tags and file header (max ~4 GB) 531 | - Example: Flash players may not handle files approaching the size limit 532 | 533 | - **ICNS**: 534 | - Limited by icon element count and size fields; typically small files unsuitable for large payloads 535 | - Example: Very large ICNS files may be rejected by macOS icon caching 536 | 537 | - **M4A**: 538 | - Same limitations as MP4/MOV due to shared ISO BMFF structure (max ~4 GB) 539 | - Example: Audio streaming services often strip trailing data during processing 540 | 541 | - **ELF**: 542 | - Limited by architecture-specific constraints and section table size limits 543 | - Example: Some systems may reject executables with unusual sizes or structures 544 | 545 | - **MSI**: 546 | - Limited by compound document format constraints (max ~4 GB for compatibility) 547 | - Example: Windows installer may flag very large MSI files as suspicious 548 | 549 | - **TTF/OTF/WOFF**: 550 | - Limited by font table size fields and system font loading limits 551 | - Example: Font renderers may reject fonts with unusual file sizes or structures 552 | 553 | 3. **Image Hosting**: 554 | - **Works on**: 555 | - Twitter: PNGs under 3MB, JPEGs under 5MB 556 | - Imgur: Various size limits depending on account type 557 | - GitHub: Large file support available 558 | - Discord: Supports large files 559 | 560 | - **May not work on**: 561 | - Reddit: Re-encodes images, breaking the polyglot 562 | - Facebook: Re-encodes and strips metadata 563 | - Instagram: Re-encodes and strips metadata 564 | 565 | - **Platform-specific considerations**: 566 | - Some platforms may strip metadata or re-encode images 567 | - Example: Twitter's image processing preserves PNG structure but may convert to JPEG if over 3MB 568 | - Example: Reddit's image processing breaks the polyglot by re-encoding the image 569 | 570 | - **Audio & Video Hosting**: 571 | - Streaming platforms (YouTube, Vimeo, SoundCloud) often re-encode media, removing trailing ZIP data 572 | - Use direct file hosting (GitHub Releases, cloud storage) to preserve raw polyglot files 573 | - **Document Hosting**: 574 | - PDF attachments in email or file-sharing services typically preserve trailing data 575 | - Example: Sending a polyglot PDF as an email attachment maintains both viewability and ZIP payload 576 | 577 | - **New Format Hosting Considerations**: 578 | - **OGG/MKV/WebM/FLV**: Video platforms typically transcode these formats, breaking polyglots 579 | - **M4A**: Audio services may normalize or compress files, stripping trailing data 580 | - **ICNS**: macOS icon services may process or cache icons, potentially removing ZIP data 581 | - **ELF**: Linux package repositories may strip or modify executables during processing 582 | - **MSI**: Software distribution platforms may scan and modify installer files 583 | - **TTF/OTF/WOFF**: Font hosting services may validate and potentially strip non-font data 584 | - **Recommendation**: Use direct file hosting or cloud storage for new formats to preserve polyglot integrity 585 | 586 | 4. **Technical Limitations**: 587 | - ZIP file offsets must be adjusted to account for the cover file's size 588 | - Some file formats may have internal size checks that limit embedding 589 | - Example: A ZIP file with absolute paths may fail if the cover file is too large 590 | - Example: Some PDF readers may fail to open very large polyglot PDFs 591 | - **PE Formats (EXE/DLL)**: 592 | - Some third-party GUI utilities (WinRAR, macOS Archive Utility) may report the archive as corrupted due to trailing data. Windows Explorer's built-in extractor and command-line tools (`unzip`, `7z`) handle it correctly after renaming to `.zip`. 593 | 594 | 5. **🔒 Encryption System Limitations**: 595 | - **Password Security**: Passwords are only as strong as the user makes them 596 | - **Key File Security**: Key files must be stored securely and transmitted safely 597 | - **Platform Processing**: Some platforms may strip or modify files, breaking the hidden data 598 | - **File Size**: Large encrypted files may exceed platform upload limits 599 | - **Backup Responsibility**: Users must backup key files and passwords - lost credentials mean lost access to both files and chats 600 | - **Compatibility**: Scrypt fallback provides compatibility when Argon2id is unavailable, but with slightly different security parameters 601 | 602 | ### Dependencies 603 | 604 | - Python 3.x 605 | - `cryptography` library (for AES-256-GCM and Argon2id) 606 | - Optional: python-magic (for better file type detection) 607 | 608 | ## Security Considerations 609 | 610 | - The tool creates valid files that can be viewed normally 611 | - **🔒 Both files and chats are protected by military-grade AES-256-GCM encryption** 612 | - **🔑 Passwords use Argon2id derivation (with Scrypt fallback) resistant to GPU attacks** 613 | - **🛡️ Key files provide secure file-based authentication** 614 | - **👁️ Hidden data is completely undetectable by external analysis** 615 | - **🔒 Even if extracted, encrypted data remains unreadable without proper credentials** 616 | - Embedded data is not encrypted by default (except for chat system) 617 | - Files can be extracted using standard ZIP tools by renaming the file extension 618 | - Detection tools may flag these files as suspicious 619 | - **⚠️ Users are responsible for securing their passwords and key files** 620 | 621 | ## Quick Start Examples 622 | 623 | ### File Hiding 624 | ```bash 625 | # Hide files in an image (unencrypted) 626 | ./poly.py pack photo.png secrets.txt hidden_photo.png 627 | 628 | # Hide files with encryption 629 | ./poly.py pack photo.png secrets.txt encrypted_photo.png --password=MyPassword123 630 | 631 | # Extract hidden files (unencrypted) 632 | ./poly.py extract hidden_photo.png 633 | 634 | # Extract encrypted files 635 | ./poly.py extract encrypted_photo.png --password=MyPassword123 636 | 637 | # Detect hidden data 638 | ./poly.py detect hidden_photo.png 639 | ``` 640 | 641 | ### 🔒 Encrypted Chat System 642 | ```bash 643 | # Create encrypted chat 644 | ./poly.py chat create photo.jpg secret_chat.jpg "Team Alpha" --password=SecretMission2024 645 | 646 | # Add encrypted messages 647 | ./poly.py chat add secret_chat.jpg "Smith" "Package secured" --password=SecretMission2024 648 | ./poly.py chat add secret_chat.jpg "Jones" "Extraction complete" --password=SecretMission2024 649 | 650 | # Read encrypted chat 651 | ./poly.py chat read secret_chat.jpg --password=SecretMission2024 652 | 653 | # Export to HTML 654 | ./poly.py chat export secret_chat.jpg backup.html --password=SecretMission2024 655 | ``` 656 | 657 | ### Multi-Format Support 658 | ```bash 659 | # Works with any format 660 | ./poly.py chat create document.pdf secret.pdf "Legal Team" --key=legal.key 661 | ./poly.py chat create video.mp4 secret.mp4 "Production Team" --password=MovieSecret 662 | ./poly.py chat create audio.mp3 secret.mp3 "Music Team" --key=band.key 663 | ``` 664 | 665 | ## Credits 666 | 667 | - Original PNG polyglot technique by [DavidBuchanan314](https://github.com/DavidBuchanan314/tweetable-polyglot-png) 668 | - Current implementation & new techniques by InfoSecREDD 669 | 670 | ## License 671 | 672 | MIT License - See LICENSE file for details 673 | -------------------------------------------------------------------------------- /poly.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | PolyZip - A tool for creating polyglot files (FILE + ZIP) 4 | Based on the work of DavidBuchanan314 (https://github.com/DavidBuchanan314/tweetable-polyglot-png) 5 | Author: InfoSecREDD 6 | Version: 2.1.3 7 | """ 8 | 9 | import zlib 10 | import sys 11 | import os 12 | import glob 13 | import json 14 | import struct 15 | import mimetypes 16 | import subprocess 17 | import importlib.util 18 | import platform 19 | import tempfile 20 | import zipfile 21 | import datetime 22 | import base64 23 | import secrets 24 | import shutil 25 | 26 | ARGON2_AVAILABLE = False 27 | 28 | def check_and_install_dependencies(): 29 | """Always ensure we're running in our own virtual environment with all dependencies""" 30 | venv_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), '.poly_venv') 31 | 32 | if platform.system() == 'Windows': 33 | our_venv_python = os.path.join(venv_dir, 'Scripts', 'python.exe') 34 | else: 35 | our_venv_python = os.path.join(venv_dir, 'bin', 'python') 36 | 37 | current_python = os.path.abspath(sys.executable) 38 | our_python = os.path.abspath(our_venv_python) 39 | 40 | in_our_venv = ( 41 | current_python == our_python or 42 | os.path.basename(current_python) == os.path.basename(our_python) and current_python.startswith(os.path.abspath(venv_dir)) or 43 | (hasattr(sys, 'base_prefix') and sys.base_prefix != sys.prefix and os.path.abspath(sys.prefix) == os.path.abspath(venv_dir)) 44 | ) 45 | 46 | if not in_our_venv: 47 | venv_exists = os.path.exists(venv_dir) 48 | venv_python_working = False 49 | 50 | if platform.system() == 'Windows': 51 | venv_python = os.path.join(venv_dir, 'Scripts', 'python.exe') 52 | else: 53 | venv_python = os.path.join(venv_dir, 'bin', 'python') 54 | 55 | if venv_exists and os.path.exists(venv_python): 56 | try: 57 | result = subprocess.run([venv_python, '--version'], 58 | capture_output=True, timeout=10) 59 | venv_python_working = result.returncode == 0 60 | except Exception: 61 | venv_python_working = False 62 | 63 | if not venv_exists or not venv_python_working: 64 | if venv_exists and not venv_python_working: 65 | print(f"[\033[33m!\033[0m] Virtual environment is corrupted, recreating...") 66 | try: 67 | shutil.rmtree(venv_dir) 68 | except Exception as e: 69 | print(f"[\033[33m!\033[0m] Warning: Could not remove old venv: {e}") 70 | 71 | print(f"[\033[36m*\033[0m] Setting up dedicated virtual environment...") 72 | print(f"[\033[36m+\033[0m] Creating virtual environment in {venv_dir}") 73 | try: 74 | subprocess.check_call([sys.executable, '-m', 'venv', venv_dir]) 75 | except Exception as e: 76 | print(f"[\033[31m!\033[0m] Failed to create virtual environment: {e}") 77 | sys.exit(1) 78 | print(f"[\033[36m*\033[0m] Restarting script in virtual environment...") 79 | 80 | if not os.path.exists(venv_python): 81 | print(f"[\033[31m!\033[0m] Virtual environment Python not found at {venv_python}") 82 | sys.exit(1) 83 | 84 | try: 85 | result = subprocess.run([venv_python, '--version'], 86 | capture_output=True, timeout=10) 87 | if result.returncode != 0: 88 | print(f"[\033[31m!\033[0m] Virtual environment Python is not functional") 89 | sys.exit(1) 90 | except Exception as e: 91 | print(f"[\033[31m!\033[0m] Virtual environment Python test failed: {e}") 92 | sys.exit(1) 93 | 94 | os.execl(venv_python, venv_python, *sys.argv) 95 | 96 | required_packages = [ 97 | {'package': 'python-magic', 'import_name': 'magic', 'optional': True}, 98 | {'package': 'cryptography', 'import_name': 'cryptography', 'optional': False} 99 | ] 100 | 101 | missing_packages = [] 102 | for package_info in required_packages: 103 | if importlib.util.find_spec(package_info['import_name']) is None: 104 | missing_packages.append(package_info['package']) 105 | 106 | if missing_packages: 107 | print(f"[\033[36m*\033[0m] Installing missing packages: {', '.join(missing_packages)}") 108 | try: 109 | subprocess.check_call([sys.executable, '-m', 'pip', 'install'] + missing_packages) 110 | print(f"[\033[32m✓\033[0m] Dependencies installed successfully!") 111 | except Exception as e: 112 | print(f"[\033[33m!\033[0m] pip failed: {e}, trying pip3...") 113 | try: 114 | subprocess.check_call([sys.executable, '-m', 'pip3', 'install'] + missing_packages) 115 | print(f"[\033[32m✓\033[0m] Dependencies installed successfully using pip3!") 116 | except Exception as e2: 117 | print(f"[\033[31m!\033[0m] Failed to install dependencies with both pip and pip3: {e2}") 118 | print(f"[\033[31m!\033[0m] Please install manually in the virtual environment:") 119 | print(f" source {venv_dir}/bin/activate # or {venv_dir}\\Scripts\\activate on Windows") 120 | print(f" pip install {' '.join(missing_packages)}") 121 | sys.exit(1) 122 | else: 123 | print(f"[\033[32m✓\033[0m] Using virtual environment") 124 | 125 | def import_cryptography(): 126 | """Import cryptography modules after dependency checking""" 127 | global ARGON2_AVAILABLE, Cipher, algorithms, modes, Scrypt, hashes, default_backend, Argon2id 128 | 129 | try: 130 | from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes 131 | from cryptography.hazmat.primitives.kdf.scrypt import Scrypt 132 | from cryptography.hazmat.primitives import hashes 133 | from cryptography.hazmat.backends import default_backend 134 | 135 | try: 136 | from cryptography.hazmat.primitives.kdf.argon2 import Argon2id 137 | ARGON2_AVAILABLE = True 138 | except ImportError: 139 | ARGON2_AVAILABLE = False 140 | 141 | except ImportError as e: 142 | print(f"\033[31m[!] Error importing cryptography: {e}\033[0m") 143 | print(f"\033[31m[!] Please install cryptography: pip install cryptography\033[0m") 144 | sys.exit(1) 145 | 146 | if __name__ == "__main__": 147 | check_and_install_dependencies() 148 | import_cryptography() 149 | 150 | BANNER = r''' 151 | ██████╗ ██████╗ ██╗ ██╗ ██╗███████╗██╗██████╗ 152 | ██╔══██╗██╔═══██╗██║ ╚██╗ ██╔╝╚══███╔╝██║██╔══██╗ 153 | ██████╔╝██║ ██║██║ ╚████╔╝ ███╔╝ ██║██████╔╝ 154 | ██╔═══╝ ██║ ██║██║ ╚██╔╝ ███╔╝ ██║██╔═══╝ 155 | ██║ ╚██████╔╝███████╗██║ ███████╗██║██║ 156 | ╚═╝ ╚═════╝ ╚══════╝╚═╝ ╚══════╝╚═╝╚═╝ 157 | 158 | [ Multi-Format Polyglot Tool - Hide Any Data ] 159 | ╔══════════════════════════════════════════╗ 160 | ║ pack ≫ extract ≫ detect ≫ ghost in file ║ 161 | ╚══════════════════════════════════════════╝ 162 | ''' 163 | 164 | PNG_MAGIC = b"\x89PNG\r\n\x1a\n" 165 | JPEG_MAGIC = b"\xff\xd8\xff" 166 | GIF_MAGIC = b"GIF8" 167 | PDF_MAGIC = b"%PDF-" 168 | BMP_MAGIC = b"BM" 169 | WEBP_MAGIC = b"RIFF" 170 | TIFF_MAGIC_LE = b"II*\x00" 171 | TIFF_MAGIC_BE = b"MM\x00*" 172 | WAV_MAGIC = b"RIFF" 173 | MP3_MAGIC = b"\xFF\xFB" 174 | MP3_MAGIC2 = b"\xFF\xF3" 175 | MP3_MAGIC3 = b"\xFF\xFA" 176 | MP3_MAGIC4 = b"\xFF\xF2" 177 | FLAC_MAGIC = b"fLaC" 178 | OGG_MAGIC = b"OggS" 179 | AVI_MAGIC = b"RIFF" 180 | MKV_MAGIC = b"\x1a\x45\xdf\xa3" 181 | M4A_MAGIC = b"ftyp" 182 | WEBM_MAGIC = b"\x1a\x45\xdf\xa3" 183 | FLV_MAGIC = b"FLV" 184 | ICO_MAGIC = b"\x00\x00\x01\x00" 185 | CUR_MAGIC = b"\x00\x00\x02\x00" 186 | ICNS_MAGIC = b"icns" 187 | MP4_MAGIC = b"ftyp" 188 | MZ_MAGIC = b"MZ" 189 | ELF_MAGIC = b"\x7fELF" 190 | MSI_MAGIC = b"\xd0\xcf\x11\xe0" 191 | TTF_MAGIC = b"\x00\x01\x00\x00" 192 | OTF_MAGIC = b"OTTO" 193 | WOFF_MAGIC = b"wOFF" 194 | 195 | 196 | 197 | def print_banner(): 198 | colors = { 199 | 'cyan': '\033[36m', 200 | 'green': '\033[32m', 201 | 'blue': '\033[34m', 202 | 'red': '\033[31m', 203 | 'magenta': '\033[35m', 204 | 'yellow': '\033[33m', 205 | 'white': '\033[37m', 206 | 'reset': '\033[0m' 207 | } 208 | 209 | colored_banner = colors['cyan'] + BANNER + colors['reset'] 210 | print(colored_banner) 211 | 212 | version_info = f"{colors['green']}[+]{colors['reset']} {colors['white']}Version 2.1.3{colors['reset']} | " + \ 213 | f"{colors['green']}[+]{colors['reset']} {colors['white']}Data Hidden in Plain Sight{colors['reset']}" 214 | print(version_info) 215 | print(f"{colors['green']}[+]{colors['reset']} {colors['white']}Use {colors['cyan']}detect{colors['reset']} to scan for hidden data") 216 | print() 217 | 218 | def print_usage(): 219 | colors = { 220 | 'cyan': '\033[36m', 221 | 'green': '\033[32m', 222 | 'blue': '\033[34m', 223 | 'red': '\033[31m', 224 | 'magenta': '\033[35m', 225 | 'yellow': '\033[33m', 226 | 'white': '\033[37m', 227 | 'bold': '\033[1m', 228 | 'reset': '\033[0m' 229 | } 230 | 231 | print(f"\n{colors['bold']}{colors['cyan']}USAGE:{colors['reset']}") 232 | print(f" {colors['white']}{sys.argv[0]}{colors['reset']} {colors['yellow']}[command]{colors['reset']} {colors['green']}[options...]{colors['reset']}") 233 | print(f"\n{colors['bold']}{colors['magenta']}COMMANDS:{colors['reset']}") 234 | print(f"\n {colors['bold']}{colors['cyan']}pack{colors['reset']} - {colors['white']}Hide files inside images/documents{colors['reset']}") 235 | print(f" {colors['yellow']}Usage:{colors['reset']} {sys.argv[0]} pack {colors['green']}cover_file{colors['reset']} {colors['blue']}file1 [file2 ...]{colors['reset']} {colors['magenta']}output_file{colors['reset']} {colors['red']}[encryption]{colors['reset']}") 236 | print(f" {colors['yellow']}Example:{colors['reset']} {sys.argv[0]} pack photo.png secret.txt hidden_photo.png") 237 | print(f" {colors['yellow']}Encrypted:{colors['reset']} {sys.argv[0]} pack photo.png secret.txt hidden_photo.png --key=my.key") 238 | print(f" {colors['yellow']}Supports:{colors['reset']} PNG, JPEG, GIF, PDF, BMP, WebP, TIFF, WAV, MP3, FLAC, OGG,") 239 | print(f" AVI, MKV, WebM, FLV, ICO, CUR, ICNS, MP4, MOV, M4A, EXE, DLL, ELF, MSI, TTF, OTF, WOFF") 240 | print(f"\n {colors['bold']}{colors['cyan']}extract{colors['reset']} - {colors['white']}Extract hidden files from images/documents{colors['reset']}") 241 | print(f" {colors['yellow']}Usage:{colors['reset']} {sys.argv[0]} extract {colors['green']}input_file{colors['reset']} {colors['blue']}[output_directory]{colors['reset']} {colors['red']}[encryption]{colors['reset']}") 242 | print(f" {colors['yellow']}Example:{colors['reset']} {sys.argv[0]} extract hidden_photo.png extracted_files/") 243 | print(f" {colors['yellow']}Encrypted:{colors['reset']} {sys.argv[0]} extract hidden_photo.png extracted_files/ --key=my.key") 244 | print(f" {colors['yellow']}Note:{colors['reset']} If output directory is omitted, extracts to folder named after input file") 245 | print(f"\n {colors['bold']}{colors['cyan']}detect{colors['reset']} - {colors['white']}Scan for hidden data in files{colors['reset']}") 246 | print(f" {colors['yellow']}Usage:{colors['reset']} {sys.argv[0]} detect {colors['green']}[file]{colors['reset']}") 247 | print(f" {colors['yellow']}Example:{colors['reset']} {sys.argv[0]} detect suspicious_image.png") 248 | print(f" {colors['yellow']}Note:{colors['reset']} If no file specified, scans all supported files in current directory") 249 | print(f"\n {colors['bold']}{colors['cyan']}chat{colors['reset']} - {colors['white']}Create encrypted hidden chat logs in images{colors['reset']}") 250 | print(f" {colors['bold']}{colors['green']}Subcommands:{colors['reset']}") 251 | print(f"\n {colors['bold']}{colors['yellow']}create{colors['reset']} - Create new encrypted chat") 252 | print(f" {colors['yellow']}Usage:{colors['reset']} {sys.argv[0]} chat create {colors['green']}cover.[any]{colors['reset']} {colors['magenta']}output.[any]{colors['reset']} {colors['blue']}[\"title\"]{colors['reset']} {colors['red']}[encryption]{colors['reset']}") 253 | print(f" {colors['yellow']}Examples:{colors['reset']}") 254 | print(f" {sys.argv[0]} chat create photo.jpg secret_chat.jpg \"Team Chat\" --key=team.key") 255 | print(f" {sys.argv[0]} chat create document.pdf secret_chat.pdf \"Private\" --password=mypass123") 256 | print(f" {sys.argv[0]} chat create image.gif secret_chat.gif # Unencrypted") 257 | print(f" {sys.argv[0]} chat create cover.png output.png \"Any file type works!\"") 258 | print(f" {colors['cyan']}Supported formats:{colors['reset']} PNG, JPG, GIF, PDF, BMP, WEBP, TIFF, WAV, MP3, FLAC, OGG, AVI, MKV, WebM, FLV, ICO, MP4, MOV, EXE, and more") 259 | print(f"\n {colors['bold']}{colors['yellow']}add{colors['reset']} - Add message to existing chat") 260 | print(f" {colors['yellow']}Usage:{colors['reset']} {sys.argv[0]} chat add {colors['green']}chat.[any]{colors['reset']} {colors['blue']}sender{colors['reset']} {colors['cyan']}\"message\"{colors['reset']} {colors['magenta']}[output.[any]]{colors['reset']} {colors['red']}[encryption]{colors['reset']}") 261 | print(f" {colors['yellow']}Examples:{colors['reset']}") 262 | print(f" {sys.argv[0]} chat add secret_chat.jpg Alice \"Hello team!\" --key=team.key") 263 | print(f" {sys.argv[0]} chat add secret_chat.pdf Bob \"Secret message\" --password=mypass123") 264 | print(f" {sys.argv[0]} chat add hidden_chat.gif Charlie \"Works with any file type!\"") 265 | print(f"\n {colors['bold']}{colors['yellow']}read{colors['reset']} - Display chat messages") 266 | print(f" {colors['yellow']}Usage:{colors['reset']} {sys.argv[0]} chat read {colors['green']}chat.[any]{colors['reset']} {colors['blue']}[--format=terminal|json|html]{colors['reset']} {colors['red']}[encryption]{colors['reset']}") 267 | print(f" {colors['yellow']}Examples:{colors['reset']}") 268 | print(f" {sys.argv[0]} chat read secret_chat.jpg --key=team.key") 269 | print(f" {sys.argv[0]} chat read secret_chat.pdf --format=json --password=mypass123") 270 | print(f" {sys.argv[0]} chat read hidden_chat.mp4 --format=html") 271 | print(f"\n {colors['bold']}{colors['yellow']}export{colors['reset']} - Export chat to file") 272 | print(f" {colors['yellow']}Usage:{colors['reset']} {sys.argv[0]} chat export {colors['green']}chat.[any]{colors['reset']} {colors['magenta']}output.[txt|json|html]{colors['reset']} {colors['red']}[encryption]{colors['reset']}") 273 | print(f" {colors['yellow']}Examples:{colors['reset']}") 274 | print(f" {sys.argv[0]} chat export secret_chat.jpg backup.html --key=team.key") 275 | print(f" {sys.argv[0]} chat export secret_chat.pdf backup.json --password=mypass123") 276 | print(f" {sys.argv[0]} chat export hidden_chat.mkv backup.txt") 277 | print(f"\n {colors['bold']}{colors['red']}ENCRYPTION OPTIONS:{colors['reset']}") 278 | print(f" {colors['cyan']}--key=filename.key{colors['reset']} Use AES-256 encryption with key file (auto-generated if missing)") 279 | print(f" {colors['cyan']}--password=yourpass{colors['reset']} Use AES-256 encryption with password (Argon2id + salt)") 280 | print(f" {colors['yellow']}Note:{colors['reset']} Encrypted chats are completely secure and undetectable") 281 | print(f"\n{colors['bold']}{colors['green']}QUICK EXAMPLES:{colors['reset']}") 282 | print(f" {colors['white']}Hide files:{colors['reset']} {sys.argv[0]} pack photo.jpg document.pdf hidden.jpg") 283 | print(f" {colors['white']}Hide files encrypted:{colors['reset']} {sys.argv[0]} pack photo.jpg secret.txt hidden.jpg --key=my.key") 284 | print(f" {colors['white']}Extract files:{colors['reset']} {sys.argv[0]} extract hidden.jpg") 285 | print(f" {colors['white']}Extract encrypted:{colors['reset']} {sys.argv[0]} extract hidden.jpg output/ --key=my.key") 286 | print(f" {colors['white']}Scan for hidden data:{colors['reset']} {sys.argv[0]} detect") 287 | print(f" {colors['white']}Create encrypted chat:{colors['reset']} {sys.argv[0]} chat create cover.jpg chat.jpg --key=my.key") 288 | print(f" {colors['white']}Add encrypted message:{colors['reset']} {sys.argv[0]} chat add chat.pdf Alice \"Hello!\" --key=my.key") 289 | print(f" {colors['white']}Read encrypted chat:{colors['reset']} {sys.argv[0]} chat read chat.gif --key=my.key") 290 | print(f" {colors['cyan']}💡 Both files AND chat work with ANY file format - images, videos, documents, executables, etc!{colors['reset']}") 291 | print(f"\n{colors['bold']}{colors['yellow']}SECURITY FEATURES:{colors['reset']}") 292 | print(f" • {colors['green']}AES-256-GCM encryption{colors['reset']} with authenticated encryption for files & chat") 293 | print(f" • {colors['green']}Perfect steganography{colors['reset']} - files appear completely normal") 294 | print(f" • {colors['green']}Key file generation{colors['reset']} - automatic secure key creation") 295 | print(f" • {colors['green']}Password protection{colors['reset']} - Argon2id or Scrypt with secure parameters") 296 | print(f" • {colors['green']}Tamper detection{colors['reset']} - GCM authentication prevents modification") 297 | 298 | print(f"\n{colors['bold']}{colors['cyan']}💡 TIP:{colors['reset']} {colors['white']}Files with hidden data can be opened normally in any image viewer/PDF reader!{colors['reset']}") 299 | print() 300 | sys.exit(1) 301 | 302 | if len(sys.argv) < 2: 303 | print_banner() 304 | print_usage() 305 | 306 | def create_chat_data(title="Chat Log"): 307 | """Create initial chat data structure""" 308 | return { 309 | "version": "1.0", 310 | "title": title, 311 | "created": datetime.datetime.now().isoformat(), 312 | "participants": [], 313 | "messages": [] 314 | } 315 | 316 | def add_message_to_chat(chat_data, sender, message): 317 | """Add a message to the chat data""" 318 | timestamp = datetime.datetime.now().isoformat() 319 | 320 | if sender not in chat_data["participants"]: 321 | chat_data["participants"].append(sender) 322 | 323 | chat_data["messages"].append({ 324 | "timestamp": timestamp, 325 | "sender": sender, 326 | "message": message 327 | }) 328 | 329 | return chat_data 330 | 331 | def generate_key(): 332 | """Generate a new AES-256 key""" 333 | return secrets.token_bytes(32) 334 | 335 | def save_key_to_file(key, filename): 336 | """Save AES key to file in base64 format""" 337 | with open(filename, 'wb') as f: 338 | f.write(base64.b64encode(key)) 339 | print(f"[\033[32m+\033[0m] New encryption key saved to: {filename}") 340 | 341 | def load_key_from_file(filename): 342 | """Load AES key from file""" 343 | try: 344 | with open(filename, 'rb') as f: 345 | return base64.b64decode(f.read()) 346 | except Exception as e: 347 | raise ValueError(f"Could not load key from {filename}: {e}") 348 | 349 | def derive_key_from_password(password, salt): 350 | """Derive AES key from password using Argon2id or Scrypt fallback""" 351 | if 'Cipher' not in globals(): 352 | import_cryptography() 353 | 354 | if ARGON2_AVAILABLE: 355 | kdf = Argon2id( 356 | salt=salt, 357 | length=32, 358 | lanes=4, 359 | memory_cost=65536, 360 | iterations=3 361 | ) 362 | else: 363 | kdf = Scrypt( 364 | salt=salt, 365 | length=32, 366 | n=2**15, # 32768 367 | r=8, 368 | p=1, 369 | backend=default_backend() 370 | ) 371 | return kdf.derive(password.encode()) 372 | 373 | def encrypt_data(data, key): 374 | """Encrypt text data using AES-256-GCM""" 375 | if 'Cipher' not in globals(): 376 | import_cryptography() 377 | 378 | iv = secrets.token_bytes(12) 379 | 380 | cipher = Cipher(algorithms.AES(key), modes.GCM(iv), backend=default_backend()) 381 | encryptor = cipher.encryptor() 382 | ciphertext = encryptor.update(data.encode()) + encryptor.finalize() 383 | encrypted_payload = iv + encryptor.tag + ciphertext 384 | return base64.b64encode(encrypted_payload).decode() 385 | 386 | def decrypt_data(encrypted_data, key): 387 | """Decrypt text data using AES-256-GCM""" 388 | if 'Cipher' not in globals(): 389 | import_cryptography() 390 | 391 | try: 392 | encrypted_payload = base64.b64decode(encrypted_data.encode()) 393 | iv = encrypted_payload[:12] 394 | auth_tag = encrypted_payload[12:28] 395 | ciphertext = encrypted_payload[28:] 396 | cipher = Cipher(algorithms.AES(key), modes.GCM(iv, auth_tag), backend=default_backend()) 397 | decryptor = cipher.decryptor() 398 | plaintext = decryptor.update(ciphertext) + decryptor.finalize() 399 | return plaintext.decode() 400 | 401 | except Exception as e: 402 | raise ValueError(f"Decryption failed: {e}") 403 | 404 | def encrypt_file_data(data, key): 405 | """Encrypt binary file data using AES-256-GCM""" 406 | if 'Cipher' not in globals(): 407 | import_cryptography() 408 | 409 | iv = secrets.token_bytes(12) 410 | cipher = Cipher(algorithms.AES(key), modes.GCM(iv), backend=default_backend()) 411 | encryptor = cipher.encryptor() 412 | ciphertext = encryptor.update(data) + encryptor.finalize() 413 | encrypted_payload = iv + encryptor.tag + ciphertext 414 | return encrypted_payload 415 | 416 | def decrypt_file_data(encrypted_data, key): 417 | """Decrypt binary file data using AES-256-GCM""" 418 | if 'Cipher' not in globals(): 419 | import_cryptography() 420 | 421 | try: 422 | iv = encrypted_data[:12] 423 | auth_tag = encrypted_data[12:28] 424 | ciphertext = encrypted_data[28:] 425 | cipher = Cipher(algorithms.AES(key), modes.GCM(iv, auth_tag), backend=default_backend()) 426 | decryptor = cipher.decryptor() 427 | plaintext = decryptor.update(ciphertext) + decryptor.finalize() 428 | return plaintext 429 | 430 | except Exception as e: 431 | raise ValueError(f"File decryption failed: {e}") 432 | 433 | def parse_encryption_args(args): 434 | """Parse encryption arguments from command line""" 435 | key_file = None 436 | password = None 437 | 438 | for arg in args: 439 | if arg.startswith("--key="): 440 | key_file = arg.split("=", 1)[1] 441 | elif arg.startswith("--password="): 442 | password = arg.split("=", 1)[1] 443 | 444 | return key_file, password 445 | 446 | def get_encryption_key(key_file, password, create_if_missing=False): 447 | """Get encryption key from file or password""" 448 | if key_file and password: 449 | raise ValueError("Cannot specify both --key and --password options") 450 | 451 | if key_file: 452 | if os.path.exists(key_file): 453 | return load_key_from_file(key_file), None 454 | elif create_if_missing: 455 | key = generate_key() 456 | save_key_to_file(key, key_file) 457 | return key, None 458 | else: 459 | raise ValueError(f"Key file '{key_file}' not found") 460 | 461 | elif password: 462 | salt = secrets.token_bytes(16) 463 | key = derive_key_from_password(password, salt) 464 | return key, salt 465 | 466 | else: 467 | return None, None 468 | 469 | def format_chat_terminal(chat_data): 470 | """Format chat data for terminal display""" 471 | output = [] 472 | output.append(f"\n[\033[36m*\033[0m] \033[1m{chat_data['title']}\033[0m") 473 | output.append(f"[\033[36m+\033[0m] Created: {chat_data['created']}") 474 | output.append(f"[\033[36m+\033[0m] Participants: {', '.join(chat_data['participants'])}") 475 | output.append(f"[\033[36m+\033[0m] Messages: {len(chat_data['messages'])}") 476 | output.append("\n" + "=" * 60) 477 | 478 | for msg in chat_data["messages"]: 479 | timestamp = datetime.datetime.fromisoformat(msg["timestamp"]).strftime("%Y-%m-%d %H:%M:%S") 480 | output.append(f"\n[\033[32m{timestamp}\033[0m] \033[1m{msg['sender']}\033[0m:") 481 | output.append(f" {msg['message']}") 482 | 483 | output.append("\n" + "=" * 60) 484 | return "\n".join(output) 485 | 486 | def format_chat_html(chat_data): 487 | """Format chat data as HTML""" 488 | html = f""" 489 | 490 | 491 | {chat_data['title']} 492 | 499 | 500 | 501 |
502 |

{chat_data['title']}

503 |

Created: {chat_data['created']}

504 |

Participants: {', '.join(chat_data['participants'])}

505 |

Messages: {len(chat_data['messages'])}

506 |
507 | """ 508 | 509 | for msg in chat_data["messages"]: 510 | timestamp = datetime.datetime.fromisoformat(msg["timestamp"]).strftime("%Y-%m-%d %H:%M:%S") 511 | html += f""" 512 |
513 |
{timestamp}
514 |
{msg['sender']}:
515 |
{msg['message']}
516 |
""" 517 | 518 | html += """ 519 | 520 | """ 521 | return html 522 | 523 | def extract_chat_from_image(image_path, encryption_key=None, password=None): 524 | """Extract chat data from image file with optional decryption""" 525 | input_type = detect_cover_file_type(image_path) 526 | if not input_type: 527 | raise ValueError(f"Unsupported image format: {image_path}") 528 | 529 | with tempfile.NamedTemporaryFile(suffix='.zip', delete=False) as temp_zip_file: 530 | temp_zip_path = temp_zip_file.name 531 | 532 | try: 533 | with open(image_path, "rb") as f_in: 534 | if input_type == "png": 535 | png_header = f_in.read(len(PNG_MAGIC)) 536 | if png_header != PNG_MAGIC: 537 | raise ValueError(f"Not a valid PNG file: {image_path}") 538 | 539 | zip_data_start = None 540 | while True: 541 | chunk_len_bytes = f_in.read(4) 542 | if not chunk_len_bytes or len(chunk_len_bytes) < 4: 543 | break 544 | 545 | chunk_len = int.from_bytes(chunk_len_bytes, "big") 546 | chunk_type = f_in.read(4) 547 | 548 | if not chunk_type or len(chunk_type) < 4: 549 | break 550 | 551 | if chunk_type == b"IEND": 552 | f_in.seek(chunk_len + 4, 1) 553 | zip_data_start = f_in.tell() 554 | break 555 | else: 556 | f_in.seek(chunk_len + 4, 1) 557 | 558 | if zip_data_start is None: 559 | raise ValueError(f"Could not find chat data in {image_path}") 560 | 561 | f_in.seek(zip_data_start) 562 | zip_data = f_in.read() 563 | 564 | elif input_type == "jpeg": 565 | jpeg_data = f_in.read() 566 | eoi_pos = jpeg_data.rfind(b'\xFF\xD9') 567 | if eoi_pos == -1: 568 | raise ValueError(f"Invalid JPEG file: {image_path}") 569 | zip_data = jpeg_data[eoi_pos+2:] 570 | 571 | elif input_type == "gif": 572 | gif_data = f_in.read() 573 | trailer_pos = gif_data.rfind(b'\x3B') 574 | if trailer_pos == -1: 575 | raise ValueError(f"Invalid GIF file: {image_path}") 576 | zip_data = gif_data[trailer_pos+1:] 577 | 578 | elif input_type == "pdf": 579 | pdf_data = f_in.read() 580 | eof_pos = pdf_data.rfind(b'%%EOF') 581 | if eof_pos == -1: 582 | raise ValueError(f"Invalid PDF file: {image_path}") 583 | eol_pos = pdf_data.find(b'\n', eof_pos) 584 | if eol_pos == -1: 585 | eol_pos = pdf_data.find(b'\r', eof_pos) 586 | if eol_pos == -1: 587 | eol_pos = len(pdf_data) 588 | else: 589 | eol_pos += 1 590 | else: 591 | eol_pos += 1 592 | zip_data = pdf_data[eol_pos:] 593 | 594 | else: 595 | data = f_in.read() 596 | zip_signatures = [b'PK\x03\x04', b'PK\x05\x06', b'PK\x07\x08'] 597 | for sig in zip_signatures: 598 | sig_pos = data.find(sig) 599 | if sig_pos > 0: 600 | zip_data = data[sig_pos:] 601 | break 602 | else: 603 | raise ValueError(f"Could not find chat data in {image_path}") 604 | 605 | with open(temp_zip_path, "wb") as f_out: 606 | f_out.write(zip_data) 607 | with zipfile.ZipFile(temp_zip_path, 'r') as zipf: 608 | file_list = zipf.namelist() 609 | if 'metadata.json' in file_list and 'chat.enc' in file_list: 610 | metadata_json = zipf.read('metadata.json').decode('utf-8') 611 | metadata = json.loads(metadata_json) 612 | 613 | if not metadata.get('encrypted', False): 614 | raise ValueError(f"Invalid encrypted chat format in {image_path}") 615 | 616 | if not encryption_key and not password: 617 | raise ValueError(f"Chat is encrypted but no decryption key/password provided") 618 | if password: 619 | if 'salt' not in metadata: 620 | raise ValueError(f"Password-encrypted chat but no salt found in metadata") 621 | salt = base64.b64decode(metadata['salt'].encode()) 622 | actual_key = derive_key_from_password(password, salt) 623 | else: 624 | actual_key = encryption_key 625 | encrypted_json = zipf.read('chat.enc').decode('utf-8') 626 | chat_json = decrypt_data(encrypted_json, actual_key) 627 | return json.loads(chat_json) 628 | 629 | elif 'chat.json' in file_list: 630 | if encryption_key or password: 631 | print("[\033[33m!\033[0m] Warning: Decryption key/password provided but chat is not encrypted") 632 | 633 | chat_json = zipf.read('chat.json').decode('utf-8') 634 | return json.loads(chat_json) 635 | 636 | else: 637 | raise ValueError(f"No chat data found in {image_path}") 638 | 639 | finally: 640 | try: 641 | os.unlink(temp_zip_path) 642 | except: 643 | pass 644 | 645 | def extract_cover_from_chat_image(chat_image_path): 646 | """Extract original cover image from a chat image (before the embedded data)""" 647 | input_type = detect_cover_file_type(chat_image_path) 648 | if not input_type: 649 | raise ValueError(f"Unsupported image format: {chat_image_path}") 650 | 651 | with open(chat_image_path, "rb") as f_in: 652 | if input_type == "png": 653 | png_header = f_in.read(len(PNG_MAGIC)) 654 | if png_header != PNG_MAGIC: 655 | raise ValueError(f"Not a valid PNG file: {chat_image_path}") 656 | cover_data = bytearray(png_header) 657 | 658 | while True: 659 | chunk_len_bytes = f_in.read(4) 660 | if not chunk_len_bytes or len(chunk_len_bytes) < 4: 661 | break 662 | 663 | chunk_len = int.from_bytes(chunk_len_bytes, "big") 664 | chunk_type = f_in.read(4) 665 | 666 | if not chunk_type or len(chunk_type) < 4: 667 | break 668 | 669 | chunk_body = f_in.read(chunk_len) 670 | if len(chunk_body) < chunk_len: 671 | break 672 | 673 | chunk_csum_bytes = f_in.read(4) 674 | if not chunk_csum_bytes or len(chunk_csum_bytes) < 4: 675 | break 676 | 677 | cover_data.extend(chunk_len_bytes) 678 | cover_data.extend(chunk_type) 679 | cover_data.extend(chunk_body) 680 | cover_data.extend(chunk_csum_bytes) 681 | if chunk_type == b"IEND": 682 | break 683 | 684 | return bytes(cover_data) 685 | 686 | elif input_type == "jpeg": 687 | jpeg_data = f_in.read() 688 | eoi_pos = jpeg_data.rfind(b'\xFF\xD9') 689 | if eoi_pos == -1: 690 | raise ValueError(f"Invalid JPEG file: {chat_image_path}") 691 | return jpeg_data[:eoi_pos+2] 692 | 693 | elif input_type == "gif": 694 | gif_data = f_in.read() 695 | trailer_pos = gif_data.rfind(b'\x3B') 696 | if trailer_pos == -1: 697 | raise ValueError(f"Invalid GIF file: {chat_image_path}") 698 | return gif_data[:trailer_pos+1] 699 | 700 | elif input_type == "pdf": 701 | pdf_data = f_in.read() 702 | eof_pos = pdf_data.rfind(b'%%EOF') 703 | if eof_pos == -1: 704 | raise ValueError(f"Invalid PDF file: {chat_image_path}") 705 | eol_pos = pdf_data.find(b'\n', eof_pos) 706 | if eol_pos == -1: 707 | eol_pos = pdf_data.find(b'\r', eof_pos) 708 | if eol_pos == -1: 709 | eol_pos = len(pdf_data) 710 | else: 711 | eol_pos += 1 712 | else: 713 | eol_pos += 1 714 | return pdf_data[:eol_pos] 715 | 716 | else: 717 | data = f_in.read() 718 | zip_signatures = [b'PK\x03\x04', b'PK\x05\x06', b'PK\x07\x08'] 719 | for sig in zip_signatures: 720 | sig_pos = data.find(sig) 721 | if sig_pos > 0: 722 | return data[:sig_pos] 723 | raise ValueError(f"Could not find original cover in {chat_image_path}") 724 | 725 | def save_chat_to_image(cover_image, chat_data, output_image, encryption_key=None, salt=None): 726 | """Save chat data embedded in an image with optional encryption""" 727 | with tempfile.NamedTemporaryFile(suffix='.zip', delete=False) as temp_zip_file: 728 | temp_zip_path = temp_zip_file.name 729 | 730 | try: 731 | with zipfile.ZipFile(temp_zip_path, 'w') as zipf: 732 | chat_json = json.dumps(chat_data, indent=2) 733 | 734 | if encryption_key: 735 | encrypted_json = encrypt_data(chat_json, encryption_key) 736 | metadata = { 737 | "encrypted": True, 738 | "version": "1.0" 739 | } 740 | if salt: 741 | metadata["salt"] = base64.b64encode(salt).decode() 742 | zipf.writestr('chat.enc', encrypted_json) 743 | zipf.writestr('metadata.json', json.dumps(metadata)) 744 | else: 745 | zipf.writestr('chat.json', chat_json) 746 | cover_type = detect_cover_file_type(cover_image) 747 | if not cover_type: 748 | raise ValueError(f"Unsupported cover image format: {cover_image}") 749 | 750 | try: 751 | cover_in = open(cover_image, "rb") 752 | content_in = open(temp_zip_path, "rb") 753 | output_out = open(output_image, "wb") 754 | except Exception as e: 755 | raise ValueError(f"Error opening files: {e}") 756 | 757 | try: 758 | if cover_type == "png": 759 | png_header = cover_in.read(len(PNG_MAGIC)) 760 | if png_header != PNG_MAGIC: 761 | raise ValueError(f"Not a valid PNG file: {cover_image}") 762 | 763 | output_out.write(png_header) 764 | 765 | content_embedded = False 766 | 767 | while True: 768 | chunk_len_bytes = cover_in.read(4) 769 | if not chunk_len_bytes or len(chunk_len_bytes) < 4: 770 | break 771 | 772 | chunk_len = int.from_bytes(chunk_len_bytes, "big") 773 | chunk_type = cover_in.read(4) 774 | 775 | if not chunk_type or len(chunk_type) < 4: 776 | break 777 | 778 | chunk_body = cover_in.read(chunk_len) 779 | if len(chunk_body) < chunk_len: 780 | break 781 | 782 | chunk_csum_bytes = cover_in.read(4) 783 | if not chunk_csum_bytes or len(chunk_csum_bytes) < 4: 784 | break 785 | 786 | chunk_csum = int.from_bytes(chunk_csum_bytes, "big") 787 | 788 | output_out.write(chunk_len.to_bytes(4, "big")) 789 | output_out.write(chunk_type) 790 | output_out.write(chunk_body) 791 | output_out.write(chunk_csum.to_bytes(4, "big")) 792 | 793 | if chunk_type == b"IEND" and not content_embedded: 794 | current_pos = output_out.tell() 795 | start_offset = current_pos 796 | 797 | content_dat = bytearray(content_in.read()) 798 | fixup_zip(content_dat, start_offset) 799 | 800 | output_out.write(content_dat) 801 | content_embedded = True 802 | break 803 | 804 | elif cover_type == "jpeg": 805 | jpeg_data = cover_in.read() 806 | eoi_pos = jpeg_data.rfind(b'\xFF\xD9') 807 | if eoi_pos == -1: 808 | raise ValueError(f"Invalid JPEG file: {cover_image}") 809 | 810 | output_out.write(jpeg_data[:eoi_pos+2]) 811 | start_offset = eoi_pos + 2 812 | content_dat = bytearray(content_in.read()) 813 | fixup_zip(content_dat, start_offset) 814 | output_out.write(content_dat) 815 | 816 | elif cover_type == "gif": 817 | gif_data = cover_in.read() 818 | trailer_pos = gif_data.rfind(b'\x3B') 819 | if trailer_pos == -1: 820 | raise ValueError(f"Invalid GIF file: {cover_image}") 821 | 822 | output_out.write(gif_data[:trailer_pos+1]) 823 | start_offset = trailer_pos + 1 824 | content_dat = bytearray(content_in.read()) 825 | fixup_zip(content_dat, start_offset) 826 | output_out.write(content_dat) 827 | 828 | elif cover_type == "pdf": 829 | pdf_data = cover_in.read() 830 | eof_pos = pdf_data.rfind(b'%%EOF') 831 | if eof_pos == -1: 832 | raise ValueError(f"Invalid PDF file: {cover_image}") 833 | 834 | eol_pos = pdf_data.find(b'\n', eof_pos) 835 | if eol_pos == -1: 836 | eol_pos = pdf_data.find(b'\r', eof_pos) 837 | if eol_pos == -1: 838 | eol_pos = len(pdf_data) 839 | else: 840 | eol_pos += 1 841 | else: 842 | eol_pos += 1 843 | 844 | output_out.write(pdf_data[:eol_pos]) 845 | start_offset = eol_pos 846 | content_dat = bytearray(content_in.read()) 847 | fixup_zip(content_dat, start_offset) 848 | output_out.write(content_dat) 849 | 850 | else: 851 | format_data = cover_in.read() 852 | output_out.write(format_data) 853 | start_offset = len(format_data) 854 | content_dat = bytearray(content_in.read()) 855 | fixup_zip(content_dat, start_offset) 856 | output_out.write(content_dat) 857 | 858 | finally: 859 | cover_in.close() 860 | content_in.close() 861 | output_out.close() 862 | 863 | finally: 864 | try: 865 | os.unlink(temp_zip_path) 866 | except: 867 | pass 868 | 869 | if len(sys.argv) == 4 and sys.argv[1] not in ["pack", "extract", "detect", "chat"]: 870 | sys.argv = [sys.argv[0], "pack"] + sys.argv[1:] 871 | 872 | command = sys.argv[1] 873 | 874 | print_banner() 875 | 876 | def fixup_zip(data, start_offset): 877 | try: 878 | end_central_dir_offset = data.rindex(b"PK\x05\x06") 879 | cdent_count = int.from_bytes(data[end_central_dir_offset+10:end_central_dir_offset+10+2], "little") 880 | cd_range = slice(end_central_dir_offset+16, end_central_dir_offset+16+4) 881 | central_dir_start_offset = int.from_bytes(data[cd_range], "little") 882 | data[cd_range] = (central_dir_start_offset + start_offset).to_bytes(4, "little") 883 | for _ in range(cdent_count): 884 | central_dir_start_offset = data.index(b"PK\x01\x02", central_dir_start_offset) 885 | off_range = slice(central_dir_start_offset+42, central_dir_start_offset+42+4) 886 | off = int.from_bytes(data[off_range], "little") 887 | data[off_range] = (off + start_offset).to_bytes(4, "little") 888 | central_dir_start_offset += 1 889 | return True 890 | except Exception as e: 891 | print(f"Error fixing ZIP file: {e}") 892 | return False 893 | 894 | def detect_png_file(png_path): 895 | if not os.path.exists(png_path): 896 | print(f"Error: File '{png_path}' not found") 897 | return False 898 | 899 | try: 900 | with open(png_path, "rb") as png_in: 901 | png_header = png_in.read(len(PNG_MAGIC)) 902 | if png_header != PNG_MAGIC: 903 | print(f"Not a valid PNG file: {png_path}") 904 | return False 905 | 906 | idat_chunks = 0 907 | total_data_size = 0 908 | suspicious_patterns = [] 909 | 910 | while True: 911 | chunk_len_bytes = png_in.read(4) 912 | if not chunk_len_bytes or len(chunk_len_bytes) < 4: 913 | break 914 | 915 | chunk_len = int.from_bytes(chunk_len_bytes, "big") 916 | chunk_type = png_in.read(4) 917 | 918 | if not chunk_type or len(chunk_type) < 4: 919 | break 920 | 921 | chunk_position = png_in.tell() - 8 922 | 923 | if chunk_type == b"IDAT": 924 | idat_chunks += 1 925 | total_data_size += chunk_len 926 | 927 | if idat_chunks > 1: 928 | peek_data = png_in.read(16) 929 | 930 | if b"PK\x03\x04" in peek_data: 931 | suspicious_patterns.append(f"ZIP header at offset {chunk_position + 8}") 932 | elif b"\x50\x4B\x05\x06" in peek_data: 933 | suspicious_patterns.append(f"ZIP end header at offset {chunk_position + 8}") 934 | elif b"\x89PNG" in peek_data: 935 | suspicious_patterns.append(f"PNG signature at offset {chunk_position + 8}") 936 | elif b"PDF-" in peek_data: 937 | suspicious_patterns.append(f"PDF header at offset {chunk_position + 8}") 938 | elif b"\xFF\xD8\xFF" in peek_data: 939 | suspicious_patterns.append(f"JPEG header at offset {chunk_position + 8}") 940 | elif b"POLY" in peek_data: 941 | suspicious_patterns.append(f"POLY multi-file header at offset {chunk_position + 8}") 942 | 943 | png_in.seek(chunk_position + 8) 944 | 945 | png_in.seek(chunk_len, 1) 946 | png_in.seek(4, 1) 947 | 948 | if chunk_type == b"IEND": 949 | break 950 | 951 | is_suspicious = idat_chunks > 1 or len(suspicious_patterns) > 0 952 | 953 | print(f"\n[\033[36m*\033[0m] PNG File: \033[1m{png_path}\033[0m") 954 | print(f"[\033[36m+\033[0m] Total IDAT chunks: {idat_chunks}") 955 | 956 | if idat_chunks > 1: 957 | print(f"[\033[33m!\033[0m] Multiple IDAT chunks detected ({idat_chunks}), which could indicate embedded data") 958 | 959 | if idat_chunks > 1: 960 | print(f"[\033[36m+\033[0m] Total size of all IDAT chunks: {total_data_size} bytes") 961 | 962 | if suspicious_patterns: 963 | print("[\033[31m!\033[0m] Potential embedded content detected:") 964 | for pattern in suspicious_patterns: 965 | print(f" [\033[31m>\033[0m] {pattern}") 966 | print("[\033[31m!\033[0m] This file likely contains embedded data") 967 | else: 968 | if idat_chunks > 1: 969 | print("[\033[33m!\033[0m] Multiple IDAT chunks found, but no obvious embedded file signatures detected") 970 | print("[\033[33m!\033[0m] The file might still contain embedded data in an uncommon format") 971 | else: 972 | print("[\033[32m✓\033[0m] No evidence of embedded data found") 973 | 974 | return is_suspicious 975 | 976 | except Exception as e: 977 | print(f"\033[31m[!] Error analyzing {png_path}: {e}\033[0m") 978 | return False 979 | 980 | def detect_file_type(data): 981 | signatures = { 982 | b'\x89PNG\r\n\x1a\n': '.png', 983 | b'\xff\xd8\xff': '.jpg', 984 | b'GIF87a': '.gif', 985 | b'GIF89a': '.gif', 986 | b'%PDF': '.pdf', 987 | b'PK\x03\x04': '.zip', 988 | b'Rar!\x1a\x07': '.rar', 989 | b'\x1f\x8b\x08': '.gz', 990 | b'BM': '.bmp', 991 | b'\x49\x49\x2a\x00': '.tif', 992 | b'\x4d\x4d\x00\x2a': '.tif', 993 | b'RIFF': '.wav', 994 | b'OggS': '.ogg', 995 | b'\x50\x4b\x05\x06': '.zip', 996 | b'\x50\x4b\x07\x08': '.zip', 997 | b'\x75\x73\x74\x61\x72': '.tar', 998 | b'7z\xbc\xaf\x27\x1c': '.7z', 999 | b'\x00\x01\x00\x00\x00': '.ttf', 1000 | b'OTTO': '.otf', 1001 | b'\x4d\x5a': '.exe', 1002 | b' 13 and data[i] < 32): 1012 | is_text = False 1013 | break 1014 | 1015 | if is_text: 1016 | if data.startswith(b'#!'): 1017 | return '.sh' 1018 | elif data.startswith(b' 13 and byte < 32) or byte > 126): 1074 | binary_chars += 1 1075 | 1076 | if len(sample) > 0 and binary_chars / len(sample) > 0.05: 1077 | return False 1078 | 1079 | return True 1080 | 1081 | def detect_cover_file_type(file_path): 1082 | try: 1083 | with open(file_path, "rb") as f: 1084 | header = f.read(16) 1085 | 1086 | if header.startswith(PNG_MAGIC): 1087 | return "png" 1088 | elif header.startswith(JPEG_MAGIC): 1089 | return "jpeg" 1090 | elif header.startswith(GIF_MAGIC): 1091 | return "gif" 1092 | elif header.startswith(PDF_MAGIC): 1093 | return "pdf" 1094 | elif header.startswith(BMP_MAGIC): 1095 | return "bmp" 1096 | elif header.startswith(WEBP_MAGIC) and b"WEBP" in header: 1097 | return "webp" 1098 | elif header.startswith(TIFF_MAGIC_LE) or header.startswith(TIFF_MAGIC_BE): 1099 | return "tiff" 1100 | elif header.startswith(WAV_MAGIC) and b"WAVE" in header: 1101 | return "wav" 1102 | elif header.startswith(b"ID3") or header.startswith(MP3_MAGIC) or header.startswith(MP3_MAGIC2) or header.startswith(MP3_MAGIC3) or header.startswith(MP3_MAGIC4): 1103 | return "mp3" 1104 | elif header.startswith(FLAC_MAGIC): 1105 | return "flac" 1106 | elif header.startswith(OGG_MAGIC): 1107 | return "ogg" 1108 | elif header.startswith(MKV_MAGIC): 1109 | try: 1110 | f.seek(0) 1111 | ebml_data = f.read(1024) 1112 | if b"webm" in ebml_data: 1113 | return "webm" 1114 | elif b"matroska" in ebml_data: 1115 | return "mkv" 1116 | else: 1117 | return "mkv" 1118 | except: 1119 | return "mkv" 1120 | elif header.startswith(FLV_MAGIC): 1121 | return "flv" 1122 | elif header.startswith(AVI_MAGIC) and b"AVI " in header: 1123 | return "avi" 1124 | elif header.startswith(ICO_MAGIC): 1125 | return "ico" 1126 | elif header.startswith(CUR_MAGIC): 1127 | return "cur" 1128 | elif header.startswith(ICNS_MAGIC): 1129 | return "icns" 1130 | elif header.startswith(ELF_MAGIC): 1131 | return "elf" 1132 | elif header.startswith(MSI_MAGIC): 1133 | return "msi" 1134 | elif header.startswith(TTF_MAGIC): 1135 | return "ttf" 1136 | elif header.startswith(OTF_MAGIC): 1137 | return "otf" 1138 | elif header.startswith(WOFF_MAGIC): 1139 | return "woff" 1140 | 1141 | elif header.startswith(MZ_MAGIC): 1142 | ext = os.path.splitext(file_path)[1].lower().lstrip('.') 1143 | if ext == 'dll': 1144 | return "dll" 1145 | return "exe" 1146 | 1147 | elif header[4:8] == b"ftyp": 1148 | if len(header) >= 12: 1149 | brand = header[8:12] 1150 | if brand == b"qt ": 1151 | return "mov" 1152 | elif brand in [b"M4A ", b"M4B ", b"mp42"]: 1153 | return "m4a" 1154 | return "mp4" 1155 | 1156 | 1157 | if len(header) >= 8: 1158 | size = int.from_bytes(header[0:4], byteorder='big') 1159 | if size >= 8 and size <= 1024: 1160 | f.seek(0) 1161 | box_data = f.read(size) 1162 | if len(box_data) >= 8 and box_data[4:8] == b"ftyp": 1163 | major = box_data[8:12] if len(box_data) >= 12 else None 1164 | if major == b"qt ": 1165 | return "mov" 1166 | return "mp4" 1167 | 1168 | return None 1169 | except Exception as e: 1170 | return None 1171 | 1172 | if command == "pack": 1173 | if len(sys.argv) < 5: 1174 | print(f"USAGE: {sys.argv[0]} pack cover.[png|pdf|jpg|gif|bmp|webp|tiff|wav|mp3|flac|ogg|avi|mkv|webm|flv|ico|cur|icns|mp4|mov|m4a|exe|dll|elf|msi|ttf|otf|woff] file1 [file2 file3 ...] output.[png|pdf|jpg|gif|bmp|webp|tiff|wav|mp3|flac|ogg|avi|mkv|webm|flv|ico|cur|icns|mp4|mov|m4a|exe|dll|elf|msi|ttf|otf|woff] [--key=file.key|--password=pass]") 1175 | print(f" # Embeds files into cover file and saves to output file") 1176 | print(f" # Use --key=file.key for AES-256 encryption with key file") 1177 | print(f" # Use --password=pass for AES-256 encryption with password") 1178 | sys.exit(1) 1179 | 1180 | encryption_args = [] 1181 | regular_args = [] 1182 | for arg in sys.argv[2:]: 1183 | if arg.startswith('--key=') or arg.startswith('--password='): 1184 | encryption_args.append(arg) 1185 | else: 1186 | regular_args.append(arg) 1187 | 1188 | if len(regular_args) < 3: 1189 | print(f"USAGE: {sys.argv[0]} pack cover_file file1 [file2 ...] output_file [--key=file.key|--password=pass]") 1190 | sys.exit(1) 1191 | 1192 | cover_file = regular_args[0] 1193 | output_file = regular_args[-1] 1194 | files_to_embed = regular_args[1:-1] 1195 | 1196 | key_file, password = parse_encryption_args(encryption_args) 1197 | encryption_key, salt = None, None 1198 | 1199 | if key_file or password: 1200 | try: 1201 | encryption_key, salt = get_encryption_key(key_file, password, create_if_missing=True) 1202 | if encryption_key: 1203 | if key_file: 1204 | print(f"[\033[36m+\033[0m] Using encryption with key file: {key_file}") 1205 | else: 1206 | print(f"[\033[36m+\033[0m] Using encryption with password") 1207 | except Exception as e: 1208 | print(f"\033[31m[!] Encryption setup error: {e}\033[0m") 1209 | sys.exit(1) 1210 | 1211 | if not os.path.exists(cover_file): 1212 | print(f"\033[31m[!] Error: Cover file '{cover_file}' not found\033[0m") 1213 | sys.exit(1) 1214 | 1215 | for file_path in files_to_embed: 1216 | if not os.path.exists(file_path): 1217 | print(f"\033[31m[!] Error: Input file '{file_path}' not found\033[0m") 1218 | sys.exit(1) 1219 | 1220 | cover_type = detect_cover_file_type(cover_file) 1221 | if not cover_type: 1222 | print(f"\033[31m[!] Error: Cover file '{cover_file}' is not a supported format (png, jpg, gif, pdf, bmp, webp, tiff, wav, mp3, flac, ogg, avi, mkv, webm, flv, ico, cur, icns, mp4, mov, m4a, exe, dll, elf, msi, ttf, otf, woff)\033[0m") 1223 | sys.exit(1) 1224 | 1225 | print(f"[\033[36m+\033[0m] Detected cover file type: {cover_type.upper()}") 1226 | 1227 | is_single_file = len(files_to_embed) == 1 1228 | 1229 | with tempfile.NamedTemporaryFile(suffix='.zip', delete=False) as temp_zip_file: 1230 | temp_zip_path = temp_zip_file.name 1231 | 1232 | try: 1233 | with zipfile.ZipFile(temp_zip_path, 'w') as zipf: 1234 | if encryption_key: 1235 | metadata = { 1236 | "encrypted": True, 1237 | "version": "1.0", 1238 | "files": [] 1239 | } 1240 | if salt: 1241 | metadata["salt"] = base64.b64encode(salt).decode() 1242 | 1243 | for file_path in files_to_embed: 1244 | with open(file_path, 'rb') as f: 1245 | file_data = f.read() 1246 | 1247 | file_size = len(file_data) 1248 | print(f"[\033[36m+\033[0m] Encrypting and adding to ZIP: \033[1m{os.path.basename(file_path)}\033[0m ({file_size:,} bytes)") 1249 | 1250 | encrypted_data = encrypt_file_data(file_data, encryption_key) 1251 | encrypted_filename = os.path.basename(file_path) + '.enc' 1252 | zipf.writestr(encrypted_filename, encrypted_data) 1253 | 1254 | metadata["files"].append({ 1255 | "original": os.path.basename(file_path), 1256 | "encrypted": encrypted_filename, 1257 | "size": file_size 1258 | }) 1259 | 1260 | zipf.writestr('metadata.json', json.dumps(metadata, indent=2)) 1261 | print(f"[\033[32m+\033[0m] Files encrypted with AES-256-GCM") 1262 | 1263 | else: 1264 | for file_path in files_to_embed: 1265 | file_size = os.path.getsize(file_path) 1266 | print(f"[\033[36m+\033[0m] Adding to ZIP: \033[1m{os.path.basename(file_path)}\033[0m ({file_size:,} bytes)") 1267 | zipf.write(file_path, os.path.basename(file_path)) 1268 | 1269 | try: 1270 | cover_in = open(cover_file, "rb") 1271 | except Exception as e: 1272 | print(f"\033[31m[!] Error opening cover file: {e}\033[0m") 1273 | os.unlink(temp_zip_path) 1274 | sys.exit(1) 1275 | 1276 | try: 1277 | content_in = open(temp_zip_path, "rb") 1278 | except Exception as e: 1279 | cover_in.close() 1280 | print(f"\033[31m[!] Error opening temp ZIP file: {e}\033[0m") 1281 | os.unlink(temp_zip_path) 1282 | sys.exit(1) 1283 | 1284 | try: 1285 | output_out = open(output_file, "wb") 1286 | except Exception as e: 1287 | cover_in.close() 1288 | content_in.close() 1289 | print(f"\033[31m[!] Error creating output file: {e}\033[0m") 1290 | os.unlink(temp_zip_path) 1291 | sys.exit(1) 1292 | 1293 | try: 1294 | if cover_type == "png": 1295 | png_header = cover_in.read(len(PNG_MAGIC)) 1296 | if png_header != PNG_MAGIC: 1297 | raise ValueError(f"Not a valid PNG file: {cover_file}") 1298 | 1299 | output_out.write(png_header) 1300 | 1301 | content_embedded = False 1302 | idat_count = 0 1303 | 1304 | while True: 1305 | chunk_len_bytes = cover_in.read(4) 1306 | if not chunk_len_bytes or len(chunk_len_bytes) < 4: 1307 | break 1308 | 1309 | chunk_len = int.from_bytes(chunk_len_bytes, "big") 1310 | chunk_type = cover_in.read(4) 1311 | 1312 | print(f"[\033[36m+\033[0m] Found chunk: {chunk_type.decode('ascii', errors='replace')} of length {chunk_len}") 1313 | 1314 | if not chunk_type or len(chunk_type) < 4: 1315 | break 1316 | 1317 | chunk_body = cover_in.read(chunk_len) 1318 | if len(chunk_body) < chunk_len: 1319 | print("\033[33m[!] Warning: Truncated chunk body\033[0m") 1320 | break 1321 | 1322 | chunk_csum_bytes = cover_in.read(4) 1323 | if not chunk_csum_bytes or len(chunk_csum_bytes) < 4: 1324 | break 1325 | 1326 | chunk_csum = int.from_bytes(chunk_csum_bytes, "big") 1327 | 1328 | output_out.write(chunk_len.to_bytes(4, "big")) 1329 | output_out.write(chunk_type) 1330 | output_out.write(chunk_body) 1331 | output_out.write(chunk_csum.to_bytes(4, "big")) 1332 | 1333 | if chunk_type == b"IDAT": 1334 | idat_count += 1 1335 | 1336 | if chunk_type == b"IEND" and not content_embedded: 1337 | current_pos = output_out.tell() 1338 | start_offset = current_pos 1339 | 1340 | content_dat = bytearray(content_in.read()) 1341 | print(f"[\033[32m*\033[0m] ZIP data will start at offset \033[1m{hex(start_offset)}\033[0m") 1342 | 1343 | print("[\033[36m+\033[0m] Fixing up zip offsets for PNG/ZIP compatibility...") 1344 | success = fixup_zip(content_dat, start_offset) 1345 | if not success: 1346 | print("\033[33m[!] Warning: ZIP fix-up may not have worked correctly\033[0m") 1347 | 1348 | output_out.write(content_dat) 1349 | content_embedded = True 1350 | break 1351 | 1352 | elif cover_type == "jpeg": 1353 | jpeg_data = cover_in.read() 1354 | 1355 | eoi_pos = jpeg_data.rfind(b'\xFF\xD9') 1356 | 1357 | if eoi_pos == -1: 1358 | raise ValueError(f"Invalid JPEG file: {cover_file}, no EOI marker found") 1359 | 1360 | output_out.write(jpeg_data[:eoi_pos+2]) 1361 | 1362 | start_offset = eoi_pos + 2 1363 | print(f"[\033[32m*\033[0m] ZIP data will start at offset \033[1m{hex(start_offset)}\033[0m") 1364 | 1365 | content_dat = bytearray(content_in.read()) 1366 | 1367 | print("[\033[36m+\033[0m] Fixing up zip offsets for JPEG/ZIP compatibility...") 1368 | success = fixup_zip(content_dat, start_offset) 1369 | if not success: 1370 | print("\033[33m[!] Warning: ZIP fix-up may not have worked correctly\033[0m") 1371 | 1372 | output_out.write(content_dat) 1373 | content_embedded = True 1374 | 1375 | elif cover_type == "gif": 1376 | gif_data = cover_in.read() 1377 | 1378 | trailer_pos = gif_data.rfind(b'\x3B') 1379 | 1380 | if trailer_pos == -1: 1381 | raise ValueError(f"Invalid GIF file: {cover_file}, no trailer marker found") 1382 | 1383 | output_out.write(gif_data[:trailer_pos+1]) 1384 | 1385 | start_offset = trailer_pos + 1 1386 | print(f"[\033[32m*\033[0m] ZIP data will start at offset \033[1m{hex(start_offset)}\033[0m") 1387 | 1388 | content_dat = bytearray(content_in.read()) 1389 | 1390 | print("[\033[36m+\033[0m] Fixing up zip offsets for GIF/ZIP compatibility...") 1391 | success = fixup_zip(content_dat, start_offset) 1392 | if not success: 1393 | print("\033[33m[!] Warning: ZIP fix-up may not have worked correctly\033[0m") 1394 | 1395 | output_out.write(content_dat) 1396 | content_embedded = True 1397 | 1398 | elif cover_type == "pdf": 1399 | pdf_data = cover_in.read() 1400 | 1401 | eof_pos = pdf_data.rfind(b'%%EOF') 1402 | 1403 | if eof_pos == -1: 1404 | raise ValueError(f"Invalid PDF file: {cover_file}, no EOF marker found") 1405 | 1406 | eol_pos = pdf_data.find(b'\n', eof_pos) 1407 | if eol_pos == -1: 1408 | eol_pos = pdf_data.find(b'\r', eof_pos) 1409 | if eol_pos == -1: 1410 | eol_pos = len(pdf_data) 1411 | else: 1412 | eol_pos += 1 1413 | else: 1414 | eol_pos += 1 1415 | 1416 | output_out.write(pdf_data[:eol_pos]) 1417 | 1418 | start_offset = eol_pos 1419 | print(f"[\033[32m*\033[0m] ZIP data will start at offset \033[1m{hex(start_offset)}\033[0m") 1420 | 1421 | content_dat = bytearray(content_in.read()) 1422 | 1423 | print("[\033[36m+\033[0m] Fixing up zip offsets for PDF/ZIP compatibility...") 1424 | success = fixup_zip(content_dat, start_offset) 1425 | if not success: 1426 | print("\033[33m[!] Warning: ZIP fix-up may not have worked correctly\033[0m") 1427 | 1428 | output_out.write(content_dat) 1429 | content_embedded = True 1430 | 1431 | elif cover_type == "bmp": 1432 | bmp_data = cover_in.read() 1433 | if len(bmp_data) >= 6: 1434 | bmp_size = int.from_bytes(bmp_data[2:6], "little") 1435 | zip_data = bmp_data[bmp_size:] 1436 | else: 1437 | raise ValueError(f"Invalid BMP file: {cover_file}, file too small") 1438 | 1439 | output_out.write(bmp_data) 1440 | 1441 | start_offset = len(bmp_data) 1442 | print(f"[\033[32m*\033[0m] ZIP data will start at offset \033[1m{hex(start_offset)}\033[0m") 1443 | 1444 | content_dat = bytearray(content_in.read()) 1445 | 1446 | print("[\033[36m+\033[0m] Fixing up zip offsets for BMP/ZIP compatibility...") 1447 | success = fixup_zip(content_dat, start_offset) 1448 | if not success: 1449 | print("\033[33m[!] Warning: ZIP fix-up may not have worked correctly\033[0m") 1450 | 1451 | output_out.write(content_dat) 1452 | content_embedded = True 1453 | 1454 | elif cover_type == "webp" or cover_type == "wav": 1455 | riff_data = cover_in.read() 1456 | if len(riff_data) < 12: 1457 | raise ValueError(f"Invalid {cover_type.upper()} file: {cover_file}, file too small") 1458 | 1459 | output_out.write(riff_data) 1460 | 1461 | start_offset = len(riff_data) 1462 | print(f"[\033[32m*\033[0m] ZIP data will start at offset \033[1m{hex(start_offset)}\033[0m") 1463 | 1464 | content_dat = bytearray(content_in.read()) 1465 | 1466 | print(f"[\033[36m+\033[0m] Fixing up zip offsets for {cover_type.upper()}/ZIP compatibility...") 1467 | success = fixup_zip(content_dat, start_offset) 1468 | if not success: 1469 | print("\033[33m[!] Warning: ZIP fix-up may not have worked correctly\033[0m") 1470 | 1471 | output_out.write(content_dat) 1472 | content_embedded = True 1473 | 1474 | 1475 | 1476 | elif cover_type in ["tiff", "mp3", "flac", "ogg", "avi", "mkv", "webm", "flv", "ico", "cur", "icns", "mp4", "mov", "m4a", "exe", "dll", "elf", "msi", "ttf", "otf", "woff"]: 1477 | format_data = cover_in.read() 1478 | 1479 | output_out.write(format_data) 1480 | 1481 | start_offset = len(format_data) 1482 | print(f"[\033[32m*\033[0m] ZIP data will start at offset \033[1m{hex(start_offset)}\033[0m") 1483 | 1484 | content_dat = bytearray(content_in.read()) 1485 | 1486 | print(f"[\033[36m+\033[0m] Fixing up zip offsets for {cover_type.upper()}/ZIP compatibility...") 1487 | success = fixup_zip(content_dat, start_offset) 1488 | if not success: 1489 | print("\033[33m[!] Warning: ZIP fix-up may not have worked correctly\033[0m") 1490 | 1491 | output_out.write(content_dat) 1492 | content_embedded = True 1493 | 1494 | if content_embedded: 1495 | total_size = 0 1496 | for file_path in files_to_embed: 1497 | total_size += os.path.getsize(file_path) 1498 | 1499 | print(f"\n[\033[32m✓\033[0m] \033[1mOperation successful\033[0m:") 1500 | print(f"[\033[32m+\033[0m] Created dual-format {cover_type.upper()}/ZIP file: \033[1m{output_file}\033[0m") 1501 | print(f"[\033[32m+\033[0m] This file can be viewed as {cover_type.upper()} or renamed to .zip and extracted") 1502 | 1503 | print(f"\n[\033[36m*\033[0m] \033[1mEmbedded Files Summary:\033[0m") 1504 | for file_path in files_to_embed: 1505 | file_size = os.path.getsize(file_path) 1506 | print(f" [\033[32m>\033[0m] \033[1m{os.path.basename(file_path)}\033[0m - {file_size:,} bytes") 1507 | 1508 | print(f"\n[\033[32m+\033[0m] Total embedded files: \033[1m{len(files_to_embed)}\033[0m") 1509 | print(f"[\033[32m+\033[0m] Total embedded data: \033[1m{total_size:,} bytes\033[0m") 1510 | if encryption_key: 1511 | print(f"[\033[32m+\033[0m] Files are \033[1mAES-256-GCM encrypted\033[0m and secure") 1512 | print() 1513 | else: 1514 | print(f"\033[31m[!] Error: Could not embed content in {cover_file}\033[0m") 1515 | 1516 | except Exception as e: 1517 | print(f"\033[31m[!] Error during packing: {e}\033[0m") 1518 | finally: 1519 | cover_in.close() 1520 | content_in.close() 1521 | output_out.close() 1522 | finally: 1523 | try: 1524 | os.unlink(temp_zip_path) 1525 | except: 1526 | pass 1527 | 1528 | elif command == "extract": 1529 | if len(sys.argv) < 3: 1530 | print(f"USAGE: {sys.argv[0]} extract input.[png|pdf|jpg|gif|bmp|webp|tiff|wav|mp3|flac|ogg|avi|mkv|webm|flv|ico|cur|icns|mp4|mov|m4a|exe|dll|elf|msi|ttf|otf|woff] [output] [--key=file.key|--password=pass]") 1531 | print(f" # If output is omitted, extracts to directory named after input file") 1532 | print(f" # Use --key=file.key for AES-256 decryption with key file") 1533 | print(f" # Use --password=pass for AES-256 decryption with password") 1534 | sys.exit(1) 1535 | 1536 | encryption_args = [] 1537 | regular_args = [] 1538 | for arg in sys.argv[2:]: 1539 | if arg.startswith('--key=') or arg.startswith('--password='): 1540 | encryption_args.append(arg) 1541 | else: 1542 | regular_args.append(arg) 1543 | 1544 | if len(regular_args) < 1: 1545 | print(f"USAGE: {sys.argv[0]} extract input_file [output_directory] [--key=file.key|--password=pass]") 1546 | sys.exit(1) 1547 | 1548 | input_file = regular_args[0] 1549 | 1550 | key_file, password = parse_encryption_args(encryption_args) 1551 | encryption_key, salt = None, None 1552 | 1553 | if key_file or password: 1554 | try: 1555 | encryption_key, salt = get_encryption_key(key_file, password, create_if_missing=False) 1556 | if encryption_key: 1557 | if key_file: 1558 | print(f"[\033[36m+\033[0m] Using decryption with key file: {key_file}") 1559 | else: 1560 | print(f"[\033[36m+\033[0m] Using decryption with password") 1561 | except Exception as e: 1562 | print(f"\033[31m[!] Decryption setup error: {e}\033[0m") 1563 | sys.exit(1) 1564 | 1565 | if len(regular_args) >= 2: 1566 | output_path = regular_args[1] 1567 | else: 1568 | output_path = os.path.splitext(os.path.basename(input_file))[0] 1569 | print(f"[\033[36m+\033[0m] No output specified, extracting to directory: \033[1m{output_path}\033[0m") 1570 | 1571 | if not os.path.exists(output_path): 1572 | os.makedirs(output_path, exist_ok=True) 1573 | print(f"[\033[36m+\033[0m] Created directory: {output_path}") 1574 | 1575 | if not os.path.exists(input_file): 1576 | print(f"\033[31m[!] Error: Input file '{input_file}' not found\033[0m") 1577 | sys.exit(1) 1578 | 1579 | input_type = detect_cover_file_type(input_file) 1580 | if not input_type: 1581 | print(f"\033[31m[!] Error: Input file '{input_file}' is not a supported format (png, jpg, gif, pdf, bmp, webp, tiff, wav, mp3, flac, ogg, avi, mkv, webm, flv, ico, cur, icns, mp4, mov, m4a, exe, dll, elf, msi, ttf, otf, woff)\033[0m") 1582 | sys.exit(1) 1583 | 1584 | print(f"[\033[36m+\033[0m] Detected input file type: {input_type.upper()}") 1585 | 1586 | with tempfile.NamedTemporaryFile(suffix='.zip', delete=False) as temp_zip_file: 1587 | temp_zip_path = temp_zip_file.name 1588 | 1589 | try: 1590 | with open(input_file, "rb") as f_in: 1591 | if input_type == "png": 1592 | png_header = f_in.read(len(PNG_MAGIC)) 1593 | if png_header != PNG_MAGIC: 1594 | raise ValueError(f"Not a valid PNG file: {input_file}") 1595 | 1596 | zip_data_start = None 1597 | 1598 | while True: 1599 | chunk_len_bytes = f_in.read(4) 1600 | if not chunk_len_bytes or len(chunk_len_bytes) < 4: 1601 | break 1602 | 1603 | chunk_len = int.from_bytes(chunk_len_bytes, "big") 1604 | chunk_type = f_in.read(4) 1605 | 1606 | if not chunk_type or len(chunk_type) < 4: 1607 | break 1608 | 1609 | if chunk_type == b"IEND": 1610 | f_in.seek(chunk_len + 4, 1) 1611 | zip_data_start = f_in.tell() 1612 | break 1613 | else: 1614 | f_in.seek(chunk_len + 4, 1) 1615 | 1616 | if zip_data_start is None: 1617 | raise ValueError(f"Could not find ZIP data in {input_file}") 1618 | 1619 | f_in.seek(zip_data_start) 1620 | zip_data = f_in.read() 1621 | 1622 | elif input_type == "jpeg": 1623 | jpeg_data = f_in.read() 1624 | eoi_pos = jpeg_data.rfind(b'\xFF\xD9') 1625 | if eoi_pos == -1: 1626 | raise ValueError(f"Invalid JPEG file: {input_file}, no EOI marker found") 1627 | zip_data = jpeg_data[eoi_pos+2:] 1628 | 1629 | elif input_type == "gif": 1630 | gif_data = f_in.read() 1631 | trailer_pos = gif_data.rfind(b'\x3B') 1632 | if trailer_pos == -1: 1633 | raise ValueError(f"Invalid GIF file: {input_file}, no trailer marker found") 1634 | zip_data = gif_data[trailer_pos+1:] 1635 | 1636 | elif input_type == "pdf": 1637 | pdf_data = f_in.read() 1638 | eof_pos = pdf_data.rfind(b'%%EOF') 1639 | if eof_pos == -1: 1640 | raise ValueError(f"Invalid PDF file: {input_file}, no EOF marker found") 1641 | eol_pos = pdf_data.find(b'\n', eof_pos) 1642 | if eol_pos == -1: 1643 | eol_pos = pdf_data.find(b'\r', eof_pos) 1644 | if eol_pos == -1: 1645 | eol_pos = len(pdf_data) 1646 | else: 1647 | eol_pos += 1 1648 | else: 1649 | eol_pos += 1 1650 | zip_data = pdf_data[eol_pos:] 1651 | 1652 | elif input_type == "bmp": 1653 | bmp_data = f_in.read() 1654 | if len(bmp_data) >= 6: 1655 | bmp_size = int.from_bytes(bmp_data[2:6], "little") 1656 | zip_data = bmp_data[bmp_size:] 1657 | else: 1658 | raise ValueError(f"Invalid BMP file: {input_file}, file too small") 1659 | 1660 | elif input_type == "webp" or input_type == "wav": 1661 | riff_data = f_in.read() 1662 | if len(riff_data) >= 12: 1663 | riff_size = int.from_bytes(riff_data[4:8], "little") + 8 1664 | zip_data = riff_data[riff_size:] 1665 | else: 1666 | raise ValueError(f"Invalid {input_type.upper()} file: {input_file}, file too small") 1667 | 1668 | elif input_type in ["tiff", "mp3", "flac", "ogg", "avi", "mkv", "webm", "flv", "ico", "cur", "icns", "mp4", "mov", "m4a", "exe", "dll", "elf", "msi", "ttf", "otf", "woff"]: 1669 | data = f_in.read() 1670 | zip_signatures = [b'PK\x03\x04', b'PK\x05\x06', b'PK\x07\x08'] 1671 | 1672 | for sig in zip_signatures: 1673 | sig_pos = data.find(sig) 1674 | if sig_pos > 0: 1675 | zip_data = data[sig_pos:] 1676 | break 1677 | else: 1678 | raise ValueError(f"Could not find ZIP data in {input_file}") 1679 | 1680 | with open(temp_zip_path, "wb") as f_out: 1681 | f_out.write(zip_data) 1682 | 1683 | output_is_dir = os.path.isdir(output_path) 1684 | 1685 | if not output_is_dir: 1686 | os.makedirs(output_path, exist_ok=True) 1687 | output_is_dir = True 1688 | 1689 | try: 1690 | with zipfile.ZipFile(temp_zip_path, 'r') as zipf: 1691 | file_list = zipf.namelist() 1692 | print(f"[\033[36m+\033[0m] Found {len(file_list)} files in ZIP data") 1693 | 1694 | is_encrypted = 'metadata.json' in file_list 1695 | files_to_decrypt = [] 1696 | 1697 | if is_encrypted: 1698 | if not encryption_key: 1699 | print(f"\033[31m[!] Error: Files are encrypted but no decryption key/password provided\033[0m") 1700 | sys.exit(1) 1701 | 1702 | metadata_json = zipf.read('metadata.json').decode('utf-8') 1703 | metadata = json.loads(metadata_json) 1704 | 1705 | if not metadata.get('encrypted', False): 1706 | print(f"\033[31m[!] Error: Invalid encrypted file format\033[0m") 1707 | sys.exit(1) 1708 | 1709 | actual_key = encryption_key 1710 | if password and 'salt' in metadata: 1711 | salt = base64.b64decode(metadata['salt'].encode()) 1712 | actual_key = derive_key_from_password(password, salt) 1713 | 1714 | print(f"[\033[36m+\033[0m] Decrypting {len(metadata['files'])} encrypted files") 1715 | 1716 | for file_info in metadata['files']: 1717 | encrypted_filename = file_info['encrypted'] 1718 | original_filename = file_info['original'] 1719 | encrypted_data = zipf.read(encrypted_filename) 1720 | 1721 | try: 1722 | decrypted_data = decrypt_file_data(encrypted_data, actual_key) 1723 | except Exception as e: 1724 | print(f"\033[31m[!] Failed to decrypt {original_filename}: {e}\033[0m") 1725 | continue 1726 | 1727 | output_file_path = os.path.join(output_path, original_filename) 1728 | with open(output_file_path, 'wb') as f: 1729 | f.write(decrypted_data) 1730 | 1731 | file_size = len(decrypted_data) 1732 | print(f"[\033[32m+\033[0m] Decrypted and extracted: \033[1m{original_filename}\033[0m ({file_size} bytes)") 1733 | 1734 | if file_size < 1024: 1735 | is_text_by_ext = output_file_path.endswith(('.txt', '.md', '.csv', '.json', '.xml', '.html', '.css', '.js', '.py', '.sh')) 1736 | 1737 | sample_data = decrypted_data[:100] 1738 | is_text = is_text_by_ext or is_likely_text_file(sample_data) 1739 | 1740 | if is_text: 1741 | try: 1742 | content = decrypted_data.decode('utf-8').strip() 1743 | print(f"[\033[36m*\033[0m] File content preview:") 1744 | print(f"\033[33m----------------------------------------\033[0m") 1745 | print(f"\033[37m{content}\033[0m") 1746 | print(f"\033[33m----------------------------------------\033[0m") 1747 | except UnicodeDecodeError: 1748 | pass 1749 | 1750 | extracted_count = len(metadata['files']) 1751 | print(f"\n[\033[32m✓\033[0m] \033[1mOperation successful\033[0m:") 1752 | print(f"[\033[32m+\033[0m] Successfully decrypted and extracted {extracted_count} files to {output_path}") 1753 | print(f"[\033[32m+\033[0m] Files were AES-256-GCM encrypted") 1754 | 1755 | else: 1756 | if encryption_key: 1757 | print("[\033[33m!\033[0m] Warning: Decryption key/password provided but files are not encrypted") 1758 | 1759 | for file_name in file_list: 1760 | output_file_path = os.path.join(output_path, file_name) 1761 | zipf.extract(file_name, output_path) 1762 | 1763 | file_size = os.path.getsize(output_file_path) 1764 | print(f"[\033[32m+\033[0m] Extracted: \033[1m{file_name}\033[0m ({file_size} bytes)") 1765 | 1766 | if file_size < 1024: 1767 | is_text_by_ext = output_file_path.endswith(('.txt', '.md', '.csv', '.json', '.xml', '.html', '.css', '.js', '.py', '.sh')) 1768 | 1769 | with open(output_file_path, 'rb') as f: 1770 | sample_data = f.read(100) 1771 | is_text = is_text_by_ext or is_likely_text_file(sample_data) 1772 | 1773 | if is_text: 1774 | try: 1775 | with open(output_file_path, 'r') as f: 1776 | content = f.read().strip() 1777 | print(f"[\033[36m*\033[0m] File content preview:") 1778 | print(f"\033[33m----------------------------------------\033[0m") 1779 | print(f"\033[37m{content}\033[0m") 1780 | print(f"\033[33m----------------------------------------\033[0m") 1781 | except UnicodeDecodeError: 1782 | pass 1783 | 1784 | print(f"\n[\033[32m✓\033[0m] \033[1mOperation successful\033[0m:") 1785 | print(f"[\033[32m+\033[0m] Successfully extracted {len(file_list)} files to {output_path}") 1786 | 1787 | except zipfile.BadZipFile: 1788 | print(f"\033[31m[!] Error: Could not extract ZIP data from {input_file}. The file might be corrupted or not contain embedded ZIP data.\033[0m") 1789 | 1790 | except Exception as e: 1791 | print(f"\033[31m[!] Error during extraction: {e}\033[0m") 1792 | finally: 1793 | try: 1794 | os.unlink(temp_zip_path) 1795 | except: 1796 | pass 1797 | 1798 | elif command == "detect": 1799 | def detect_embedded_data_in_file(file_path): 1800 | if not os.path.exists(file_path): 1801 | print(f"Error: File '{file_path}' not found") 1802 | return False 1803 | 1804 | file_type = detect_cover_file_type(file_path) 1805 | if not file_type: 1806 | print(f"\033[31m[!] File '{file_path}' is not a supported format (png, jpg, gif, pdf, bmp, webp, tiff, wav, mp3, flac, ogg, avi, mkv, webm, flv, ico, cur, icns, mp4, mov, m4a, exe, dll, elf, msi, ttf, otf, woff)\033[0m") 1807 | return False 1808 | 1809 | print(f"[\033[36m*\033[0m] File type: \033[1m{file_type.upper()}\033[0m") 1810 | 1811 | try: 1812 | with open(file_path, "rb") as f: 1813 | content = f.read() 1814 | 1815 | zip_signatures = [b'PK\x03\x04', b'PK\x05\x06', b'PK\x07\x08'] 1816 | 1817 | is_suspicious = False 1818 | for sig in zip_signatures: 1819 | sig_pos = content.find(sig) 1820 | if sig_pos > 0: 1821 | is_suspicious = True 1822 | print(f"[\033[31m!\033[0m] Found ZIP signature at offset {sig_pos}") 1823 | 1824 | if file_type == "png": 1825 | idat_count = content.count(b'IDAT') 1826 | if idat_count > 1: 1827 | print(f"[\033[33m!\033[0m] Multiple IDAT chunks detected ({idat_count}), could indicate embedded data") 1828 | is_suspicious = True 1829 | 1830 | elif file_type == "jpeg": 1831 | eoi_pos = content.rfind(b'\xFF\xD9') 1832 | if eoi_pos > 0 and eoi_pos < len(content) - 2: 1833 | trailing_bytes = len(content) - (eoi_pos + 2) 1834 | print(f"[\033[33m!\033[0m] Found {trailing_bytes} bytes after JPEG EOI marker") 1835 | is_suspicious = True 1836 | 1837 | elif file_type == "gif": 1838 | trailer_pos = content.rfind(b'\x3B') 1839 | if trailer_pos > 0 and trailer_pos < len(content) - 1: 1840 | trailing_bytes = len(content) - (trailer_pos + 1) 1841 | print(f"[\033[33m!\033[0m] Found {trailing_bytes} bytes after GIF trailer marker") 1842 | is_suspicious = True 1843 | 1844 | elif file_type == "pdf": 1845 | eof_pos = content.rfind(b'%%EOF') 1846 | if eof_pos > 0: 1847 | eol_pos = content.find(b'\n', eof_pos) 1848 | if eol_pos == -1: 1849 | eol_pos = content.find(b'\r', eof_pos) 1850 | 1851 | if eol_pos > 0 and eol_pos < len(content) - 1: 1852 | trailing_bytes = len(content) - (eol_pos + 1) 1853 | print(f"[\033[33m!\033[0m] Found {trailing_bytes} bytes after PDF EOF marker") 1854 | is_suspicious = True 1855 | 1856 | elif file_type == "bmp": 1857 | if len(content) >= 6: 1858 | bmp_size = int.from_bytes(content[2:6], "little") 1859 | trailing_data = content[bmp_size:] 1860 | found_sig = False 1861 | for sig in zip_signatures: 1862 | pos = trailing_data.find(sig) 1863 | if pos >= 0: 1864 | abs_pos = bmp_size + pos 1865 | print(f"[\033[31m!\033[0m] Found ZIP signature at offset {abs_pos}") 1866 | found_sig = True 1867 | if found_sig: 1868 | trailing_bytes = len(trailing_data) 1869 | print(f"[\033[33m!\033[0m] Found {trailing_bytes} bytes after BMP declared size") 1870 | is_suspicious = True 1871 | 1872 | elif file_type == "webp" or file_type == "wav": 1873 | if len(content) >= 12: 1874 | riff_size = int.from_bytes(content[4:8], "little") + 8 1875 | if len(content) > riff_size: 1876 | trailing_bytes = len(content) - riff_size 1877 | print(f"[\033[33m!\033[0m] Found {trailing_bytes} bytes after {file_type.upper()} declared size") 1878 | is_suspicious = True 1879 | 1880 | elif file_type == "tiff": 1881 | pass 1882 | 1883 | elif file_type == "mp3": 1884 | pass 1885 | 1886 | elif file_type == "flac": 1887 | pass 1888 | 1889 | if is_suspicious: 1890 | print("[\033[31m!\033[0m] This file likely contains embedded data") 1891 | 1892 | with tempfile.NamedTemporaryFile(suffix='.zip', delete=False) as temp_file: 1893 | temp_path = temp_file.name 1894 | 1895 | try: 1896 | with open(temp_path, "wb") as temp: 1897 | if file_type == "png": 1898 | idat_pos = content.find(b'IDAT') 1899 | if idat_pos > 0: 1900 | pos = idat_pos + 4 1901 | chunk_len = int.from_bytes(content[idat_pos-4:idat_pos], "big") 1902 | pos += chunk_len + 4 1903 | temp.write(content[pos:]) 1904 | elif file_type == "jpeg": 1905 | temp.write(content[eoi_pos+2:]) 1906 | elif file_type == "gif": 1907 | temp.write(content[trailer_pos+1:]) 1908 | elif file_type == "pdf": 1909 | temp.write(content[eol_pos+1:]) 1910 | elif file_type == "bmp": 1911 | bmp_size = int.from_bytes(content[2:6], "little") 1912 | temp.write(content[bmp_size:]) 1913 | elif file_type == "webp" or file_type == "wav": 1914 | riff_size = int.from_bytes(content[4:8], "little") + 8 1915 | temp.write(content[riff_size:]) 1916 | elif file_type in ["tiff", "mp3", "flac", "avi", "ico", "cur", "mp4", "mov", "exe", "dll", "elf", "msi", "ttf", "otf", "woff", "tar", "iso", "cab", "unity", "dex", "class"]: 1917 | for sig in zip_signatures: 1918 | sig_pos = content.find(sig) 1919 | if sig_pos > 0: 1920 | temp.write(content[sig_pos:]) 1921 | break 1922 | 1923 | try: 1924 | with zipfile.ZipFile(temp_path, 'r') as zipf: 1925 | zip_files = zipf.namelist() 1926 | if zip_files: 1927 | print(f"[\033[32m✓\033[0m] Confirmed! Contains a valid ZIP archive with {len(zip_files)} files:") 1928 | for zf in zip_files: 1929 | print(f" [\033[32m>\033[0m] {zf}") 1930 | except zipfile.BadZipFile: 1931 | print("[\033[33m!\033[0m] File contains suspicious patterns but ZIP validation failed") 1932 | finally: 1933 | try: 1934 | os.unlink(temp_path) 1935 | except: 1936 | pass 1937 | 1938 | return True 1939 | else: 1940 | print("[\033[32m✓\033[0m] No evidence of embedded data found") 1941 | return False 1942 | 1943 | except Exception as e: 1944 | print(f"\033[31m[!] Error analyzing {file_path}: {e}\033[0m") 1945 | return False 1946 | 1947 | if len(sys.argv) == 3: 1948 | detect_embedded_data_in_file(sys.argv[2]) 1949 | else: 1950 | print("\033[36m[*]\033[0m Scanning current directory for files with potential embedded content...") 1951 | 1952 | supported_files = (glob.glob("*.png") + glob.glob("*.jpg") + glob.glob("*.jpeg") + glob.glob("*.gif") + glob.glob("*.pdf") + glob.glob("*.bmp") + glob.glob("*.webp") + glob.glob("*.tiff") + glob.glob("*.tif") + glob.glob("*.wav") + glob.glob("*.mp3") + glob.glob("*.flac") + glob.glob("*.ogg") + glob.glob("*.avi") + glob.glob("*.mkv") + glob.glob("*.webm") + glob.glob("*.flv") + glob.glob("*.ico") + glob.glob("*.cur") + glob.glob("*.icns") + glob.glob("*.mp4") + glob.glob("*.mov") + glob.glob("*.m4a") + glob.glob("*.exe") + glob.glob("*.dll") + glob.glob("*.elf") + glob.glob("*.msi") + glob.glob("*.ttf") + glob.glob("*.otf") + glob.glob("*.woff")) 1953 | 1954 | if not supported_files: 1955 | print("\033[33m[!]\033[0m No supported files found in the current directory.") 1956 | sys.exit(0) 1957 | 1958 | print(f"\033[36m[*]\033[0m Found {len(supported_files)} files to analyze.") 1959 | 1960 | suspicious_files = [] 1961 | for file_path in supported_files: 1962 | print(f"\n[\033[36m*\033[0m] Analyzing {file_path}:") 1963 | is_suspicious = detect_embedded_data_in_file(file_path) 1964 | if is_suspicious: 1965 | suspicious_files.append(file_path) 1966 | 1967 | print("\n\033[36m===================== Analysis Summary =====================\033[0m") 1968 | print(f"\033[36m[*]\033[0m Total files scanned: {len(supported_files)}") 1969 | 1970 | if suspicious_files: 1971 | print(f"\033[31m[!]\033[0m Suspicious files detected: {len(suspicious_files)}") 1972 | print("\033[31m[!]\033[0m The following files likely contain embedded data:") 1973 | for file in suspicious_files: 1974 | print(f" \033[31m>\033[0m \033[1m{file}\033[0m") 1975 | print("\n\033[36m[*]\033[0m Use \033[1mextract\033[0m command to retrieve hidden content") 1976 | else: 1977 | print("\033[32m[✓]\033[0m No suspicious files detected in the current directory.") 1978 | 1979 | elif command == "chat": 1980 | if len(sys.argv) < 3: 1981 | print(f"USAGE: {sys.argv[0]} chat [create|add|read|export] args...") 1982 | print(f" create: {sys.argv[0]} chat create cover_image.ext output_chat.ext [title] [--key=file.key|--password=pass]") 1983 | print(f" # Creates a new encrypted chat log hidden in an image") 1984 | print(f" add: {sys.argv[0]} chat add chat_image.ext sender \"message\" [output.ext] [--key=file.key|--password=pass]") 1985 | print(f" # Adds a message to existing encrypted chat (if no output, overwrites original)") 1986 | print(f" read: {sys.argv[0]} chat read chat_image.ext [--format=terminal|json|html] [--key=file.key|--password=pass]") 1987 | print(f" # Displays encrypted chat messages (default: terminal)") 1988 | print(f" export: {sys.argv[0]} chat export chat_image.ext output.[txt|json|html] [--key=file.key|--password=pass]") 1989 | print(f" # Exports encrypted chat to specified format") 1990 | sys.exit(1) 1991 | 1992 | subcommand = sys.argv[2] 1993 | 1994 | if subcommand == "create": 1995 | if len(sys.argv) < 5: 1996 | print(f"USAGE: {sys.argv[0]} chat create cover_image.ext output_chat.ext [title] [--key=file.key|--password=pass]") 1997 | sys.exit(1) 1998 | 1999 | cover_image = sys.argv[3] 2000 | output_chat = sys.argv[4] 2001 | 2002 | title = "Chat Log" 2003 | remaining_args = sys.argv[5:] 2004 | if remaining_args and not remaining_args[0].startswith('--'): 2005 | title = remaining_args[0] 2006 | remaining_args = remaining_args[1:] 2007 | 2008 | key_file, password = parse_encryption_args(remaining_args) 2009 | 2010 | if not os.path.exists(cover_image): 2011 | print(f"\033[31m[!] Error: Cover image '{cover_image}' not found\033[0m") 2012 | sys.exit(1) 2013 | 2014 | try: 2015 | 2016 | encryption_key, salt = get_encryption_key(key_file, password, create_if_missing=True) 2017 | 2018 | if encryption_key: 2019 | print(f"[\033[36m+\033[0m] Creating new encrypted chat log '{title}' hidden in {cover_image}") 2020 | if key_file: 2021 | print(f"[\033[36m+\033[0m] Using key file: {key_file}") 2022 | else: 2023 | print(f"[\033[36m+\033[0m] Using password-based encryption") 2024 | else: 2025 | print(f"[\033[36m+\033[0m] Creating new unencrypted chat log '{title}' hidden in {cover_image}") 2026 | 2027 | chat_data = create_chat_data(title) 2028 | 2029 | save_chat_to_image(cover_image, chat_data, output_chat, encryption_key, salt) 2030 | 2031 | print(f"[\033[32m✓\033[0m] \033[1mChat creation successful\033[0m:") 2032 | print(f"[\033[32m+\033[0m] Created chat log: {title}") 2033 | print(f"[\033[32m+\033[0m] Hidden chat saved to: {output_chat}") 2034 | if encryption_key: 2035 | print(f"[\033[32m+\033[0m] Chat is AES-256 encrypted") 2036 | print(f"[\033[32m+\033[0m] This file can be viewed as a normal image or used for hidden chat") 2037 | 2038 | except Exception as e: 2039 | print(f"\033[31m[!] Error creating chat: {e}\033[0m") 2040 | sys.exit(1) 2041 | 2042 | elif subcommand == "add": 2043 | if len(sys.argv) < 6: 2044 | print(f"USAGE: {sys.argv[0]} chat add chat_image.ext sender \"message\" [output.ext] [--key=file.key|--password=pass]") 2045 | sys.exit(1) 2046 | 2047 | chat_image = sys.argv[3] 2048 | sender = sys.argv[4] 2049 | message = sys.argv[5] 2050 | 2051 | remaining_args = sys.argv[6:] 2052 | output_image = chat_image 2053 | 2054 | if remaining_args and not remaining_args[0].startswith('--'): 2055 | output_image = remaining_args[0] 2056 | remaining_args = remaining_args[1:] 2057 | key_file, password = parse_encryption_args(remaining_args) 2058 | 2059 | if not os.path.exists(chat_image): 2060 | print(f"\033[31m[!] Error: Chat image '{chat_image}' not found\033[0m") 2061 | sys.exit(1) 2062 | 2063 | try: 2064 | 2065 | encryption_key, salt = get_encryption_key(key_file, password, create_if_missing=False) 2066 | 2067 | print(f"[\033[36m+\033[0m] Adding message from {sender} to chat") 2068 | 2069 | chat_data = extract_chat_from_image(chat_image, encryption_key, password) 2070 | chat_data = add_message_to_chat(chat_data, sender, message) 2071 | with tempfile.NamedTemporaryFile(suffix='.png', delete=False) as temp_cover: 2072 | temp_cover_path = temp_cover.name 2073 | 2074 | try: 2075 | cover_data = extract_cover_from_chat_image(chat_image) 2076 | with open(temp_cover_path, 'wb') as f: 2077 | f.write(cover_data) 2078 | 2079 | save_chat_to_image(temp_cover_path, chat_data, output_image, encryption_key, salt) 2080 | finally: 2081 | try: 2082 | os.unlink(temp_cover_path) 2083 | except: 2084 | pass 2085 | 2086 | print(f"[\033[32m✓\033[0m] \033[1mMessage added successfully\033[0m:") 2087 | print(f"[\033[32m+\033[0m] From: {sender}") 2088 | print(f"[\033[32m+\033[0m] Message: {message}") 2089 | print(f"[\033[32m+\033[0m] Total messages: {len(chat_data['messages'])}") 2090 | if output_image != chat_image: 2091 | print(f"[\033[32m+\033[0m] Updated chat saved to: {output_image}") 2092 | else: 2093 | print(f"[\033[32m+\033[0m] Chat updated in place") 2094 | 2095 | except Exception as e: 2096 | print(f"\033[31m[!] Error adding message: {e}\033[0m") 2097 | sys.exit(1) 2098 | 2099 | elif subcommand == "read": 2100 | if len(sys.argv) < 4: 2101 | print(f"USAGE: {sys.argv[0]} chat read chat_image.ext [--format=terminal|json|html] [--key=file.key|--password=pass]") 2102 | sys.exit(1) 2103 | 2104 | chat_image = sys.argv[3] 2105 | remaining_args = sys.argv[4:] 2106 | 2107 | format_type = "terminal" 2108 | format_found = False 2109 | encryption_args = [] 2110 | 2111 | for arg in remaining_args: 2112 | if arg.startswith("--format="): 2113 | format_type = arg.split("=")[1] 2114 | format_found = True 2115 | elif arg.startswith("--key=") or arg.startswith("--password="): 2116 | encryption_args.append(arg) 2117 | 2118 | if format_type not in ["terminal", "json", "html"]: 2119 | print(f"\033[31m[!] Error: Invalid format '{format_type}'. Use terminal, json, or html\033[0m") 2120 | sys.exit(1) 2121 | 2122 | key_file, password = parse_encryption_args(encryption_args) 2123 | 2124 | if not os.path.exists(chat_image): 2125 | print(f"\033[31m[!] Error: Chat image '{chat_image}' not found\033[0m") 2126 | sys.exit(1) 2127 | 2128 | try: 2129 | encryption_key, salt = get_encryption_key(key_file, password, create_if_missing=False) 2130 | 2131 | print(f"[\033[36m+\033[0m] Reading chat from {chat_image}") 2132 | chat_data = extract_chat_from_image(chat_image, encryption_key, password) 2133 | 2134 | if format_type == "terminal": 2135 | print(format_chat_terminal(chat_data)) 2136 | elif format_type == "json": 2137 | print(json.dumps(chat_data, indent=2)) 2138 | elif format_type == "html": 2139 | print(format_chat_html(chat_data)) 2140 | 2141 | except Exception as e: 2142 | print(f"\033[31m[!] Error reading chat: {e}\033[0m") 2143 | sys.exit(1) 2144 | 2145 | elif subcommand == "export": 2146 | if len(sys.argv) < 5: 2147 | print(f"USAGE: {sys.argv[0]} chat export chat_image.ext output.[txt|json|html] [--key=file.key|--password=pass]") 2148 | sys.exit(1) 2149 | 2150 | chat_image = sys.argv[3] 2151 | output_file = sys.argv[4] 2152 | remaining_args = sys.argv[5:] 2153 | 2154 | key_file, password = parse_encryption_args(remaining_args) 2155 | 2156 | if not os.path.exists(chat_image): 2157 | print(f"\033[31m[!] Error: Chat image '{chat_image}' not found\033[0m") 2158 | sys.exit(1) 2159 | 2160 | output_ext = os.path.splitext(output_file)[1].lower() 2161 | if output_ext not in ['.txt', '.json', '.html']: 2162 | print(f"\033[31m[!] Error: Unsupported output format '{output_ext}'. Use .txt, .json, or .html\033[0m") 2163 | sys.exit(1) 2164 | 2165 | try: 2166 | 2167 | encryption_key, salt = get_encryption_key(key_file, password, create_if_missing=False) 2168 | 2169 | print(f"[\033[36m+\033[0m] Exporting chat from {chat_image} to {output_file}") 2170 | 2171 | chat_data = extract_chat_from_image(chat_image, encryption_key, password) 2172 | 2173 | with open(output_file, 'w', encoding='utf-8') as f: 2174 | if output_ext == '.json': 2175 | f.write(json.dumps(chat_data, indent=2)) 2176 | elif output_ext == '.html': 2177 | f.write(format_chat_html(chat_data)) 2178 | elif output_ext == '.txt': 2179 | f.write(f"Chat Log: {chat_data['title']}\n") 2180 | f.write(f"Created: {chat_data['created']}\n") 2181 | f.write(f"Participants: {', '.join(chat_data['participants'])}\n") 2182 | f.write(f"Messages: {len(chat_data['messages'])}\n") 2183 | f.write("=" * 60 + "\n\n") 2184 | 2185 | for msg in chat_data["messages"]: 2186 | timestamp = datetime.datetime.fromisoformat(msg["timestamp"]).strftime("%Y-%m-%d %H:%M:%S") 2187 | f.write(f"[{timestamp}] {msg['sender']}:\n") 2188 | f.write(f" {msg['message']}\n\n") 2189 | 2190 | print(f"[\033[32m✓\033[0m] \033[1mChat exported successfully\033[0m:") 2191 | print(f"[\033[32m+\033[0m] Format: {output_ext[1:].upper()}") 2192 | print(f"[\033[32m+\033[0m] Output file: {output_file}") 2193 | print(f"[\033[32m+\033[0m] Messages exported: {len(chat_data['messages'])}") 2194 | 2195 | except Exception as e: 2196 | print(f"\033[31m[!] Error exporting chat: {e}\033[0m") 2197 | sys.exit(1) 2198 | 2199 | else: 2200 | print(f"\033[31m[!] Unknown chat subcommand: {subcommand}\033[0m") 2201 | print(f"USAGE: {sys.argv[0]} chat [create|add|read|export] args...") 2202 | sys.exit(1) 2203 | 2204 | else: 2205 | print(f"\033[31m[!] Unknown command: {command}\033[0m") 2206 | print_usage() 2207 | --------------------------------------------------------------------------------