using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Security.Cryptography; using System.Text; using System.Threading; using System.Threading.Tasks; using BitVectorLib; namespace StegoHackathonConsole { class Program { // Настройки по умолчанию private static int _threads = Environment.ProcessorCount; private static bool _verbose = false; static async Task Main(string[] args) { // Настройка кодировки для корректного вывода кириллицы Console.OutputEncoding = Encoding.UTF8; PrintBanner(); if (args.Length == 0) { PrintHelp(); return; } var arguments = ParseArguments(args); string mode = arguments.GetValueOrDefault("mode") ?? string.Empty; mode = mode.ToLower(); // Глобальные настройки if (arguments.ContainsKey("threads")) int.TryParse(arguments["threads"], out _threads); if (arguments.ContainsKey("verbose")) _verbose = true; try { switch (mode) { case "hack": await RunHackModeFast(arguments); break; case "encrypt": await RunEncryptMode(arguments); break; case "verify": await RunVerifyMode(arguments); break; default: Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine("ОШИБКА: Неизвестный режим работы."); Console.ResetColor(); PrintHelp(); break; } } catch (Exception ex) { Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine($"\nКРИТИЧЕСКАЯ ОШИБКА: {ex.Message}"); if (_verbose) Console.WriteLine(ex.StackTrace); Console.ResetColor(); } } static void PrintBanner() { var process = Process.GetCurrentProcess(); var gcInfo = GC.GetGCMemoryInfo(); Console.ForegroundColor = ConsoleColor.Cyan; Console.WriteLine("================================================================================"); Console.WriteLine(" STEGOHACKATHON CLI v1.0 "); Console.WriteLine("================================================================================"); Console.ResetColor(); Console.WriteLine($" [SYSTEM] OS Description : {System.Runtime.InteropServices.RuntimeInformation.OSDescription}"); Console.WriteLine($" [SYSTEM] Architecture : {System.Runtime.InteropServices.RuntimeInformation.OSArchitecture}"); string cpuName = GetCpuModelName(); Console.WriteLine($" [CPU] Model : {cpuName}"); Console.WriteLine($" [CPU] Logical Cores : {Environment.ProcessorCount}"); long totalRamMb = gcInfo.TotalAvailableMemoryBytes / 1024 / 1024; long appMemMb = process.WorkingSet64 / 1024 / 1024; Console.WriteLine($" [RAM] System Total : {totalRamMb:N0} MB"); Console.WriteLine($" [RAM] App Current : {appMemMb:N0} MB"); Console.WriteLine($" [PROC] Threads Limit : {_threads}"); Console.WriteLine($" [PROC] GC Mode : {(System.Runtime.GCSettings.IsServerGC ? "Server" : "Workstation")}"); Console.ForegroundColor = ConsoleColor.Cyan; Console.WriteLine("================================================================================\n"); Console.ResetColor(); } static string GetCpuModelName() { try { if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Linux)) { if (File.Exists("/proc/cpuinfo")) { var lines = File.ReadAllLines("/proc/cpuinfo"); foreach (var line in lines) { if (line.StartsWith("model name", StringComparison.OrdinalIgnoreCase) || line.StartsWith("Model", StringComparison.OrdinalIgnoreCase)) { var parts = line.Split(':'); if (parts.Length > 1) return parts[1].Trim(); } } } } else if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Windows)) { string? name = Environment.GetEnvironmentVariable("PROCESSOR_IDENTIFIER"); if (!string.IsNullOrEmpty(name)) return name; } } catch { } return "Unknown / Generic"; } static void PrintHelp() { Console.WriteLine("Использование:"); Console.WriteLine(" --mode hack --data <файл.bin> --book <книга.txt> [--threads ]"); Console.WriteLine(" --mode encrypt --book <книга.txt> --out <вывод.bin>"); Console.WriteLine(" --mode verify --data <файл.bin> --key <ключ.key.bin>"); Console.WriteLine("\nПример:"); Console.WriteLine(" dotnet run -- --mode hack --data task1.bin --book WarAndPeace.txt --threads 8"); } static Dictionary ParseArguments(string[] args) { var dict = new Dictionary(StringComparer.OrdinalIgnoreCase); for (int i = 0; i < args.Length; i++) { if (args[i].StartsWith("--") || args[i].StartsWith("-")) { string key = args[i].TrimStart('-'); string val = "true"; if (i + 1 < args.Length && !args[i + 1].StartsWith("-")) { val = args[i + 1]; i++; } dict[key] = val; } } return dict; } // --- РЕЖИМ 1: ОПТИМИЗИРОВАННЫЙ ВЗЛОМ --- static async Task RunHackModeFast(Dictionary args) { string stegoPath = args.GetValueOrDefault("data") ?? string.Empty; string bookPath = args.GetValueOrDefault("book") ?? string.Empty; if (string.IsNullOrEmpty(stegoPath) || !File.Exists(stegoPath)) { Console.WriteLine("[ERR] Файл данных не найден."); return; } if (string.IsNullOrEmpty(bookPath) || !File.Exists(bookPath)) { Console.WriteLine("[ERR] Файл книги не найден."); return; } Console.WriteLine($"[INIT] Чтение файлов..."); byte[] stegoBytes = await File.ReadAllBytesAsync(stegoPath); string bookText = await File.ReadAllTextAsync(bookPath, Encoding.UTF8); byte[] bookBytes = Encoding.UTF8.GetBytes(bookText); Console.WriteLine($"[DATA] Стего-контейнер: {stegoBytes.Length} байт"); Console.WriteLine($"[BOOK] Книга: {bookBytes.Length} байт"); string[] etalonStrings = AssocStego.GetDefaultEtalons10(); int etalonLen = etalonStrings[0].Length; int blockByteSize = (etalonLen + 7) / 8; byte[][] etalonsRaw = new byte[10][]; for (int i = 0; i < 10; i++) { etalonsRaw[i] = new BitVector(etalonStrings[i]).ToByteArray(); } int bytesEncrypted = stegoBytes.Length / (3 * blockByteSize); Console.WriteLine($"[INFO] Длина блока: {blockByteSize} байт. Скрыто символов: {bytesEncrypted}"); List stegoChunks = new List(bytesEncrypted * 3); for (int i = 0; i < stegoBytes.Length; i += blockByteSize) { byte[] chunk = new byte[blockByteSize]; Array.Copy(stegoBytes, i, chunk, 0, blockByteSize); stegoChunks.Add(chunk); } int maxOffset = bookBytes.Length - bytesEncrypted; if (maxOffset <= 0) { Console.WriteLine("[FAIL] Размер книги меньше данных."); return; } Stopwatch sw = Stopwatch.StartNew(); long processed = 0; bool found = false; object lockObj = new object(); Console.WriteLine($"[RUN ] СТАРТ ПЕРЕБОРА (TURBO MODE) на {_threads} потоках..."); using var timer = new Timer(_ => { long current = Interlocked.Read(ref processed); double progress = (double)current / maxOffset * 100.0; double speed = current / sw.Elapsed.TotalSeconds; long ram = Process.GetCurrentProcess().WorkingSet64 / 1024 / 1024; Console.Write($"\r[PROG] {progress:F2}% | Speed: {speed:N0} ops/s | RAM: {ram} MB | Time: {sw.Elapsed:mm\\:ss} "); }, null, 1000, 1000); var pOptions = new ParallelOptions { MaxDegreeOfParallelism = _threads }; Parallel.For(0, maxOffset + 1, pOptions, (i, state) => { if (found) { state.Stop(); return; } if ((i & 4095) == 0) Interlocked.Add(ref processed, 4096); byte[][] currentMasks = new byte[10][]; for (int k = 0; k < 10; k++) { currentMasks[k] = new byte[blockByteSize]; for (int b = 0; b < blockByteSize; b++) currentMasks[k][b] = 0xFF; } bool[] digitUsed = new bool[10]; bool possible = true; int quickCheckLen = (bytesEncrypted < 15) ? bytesEncrypted : 15; for (int j = 0; j < quickCheckLen; j++) { byte b = bookBytes[i + j]; int d2 = b / 100; int d1 = (b % 100) / 10; int d0 = b % 10; int idx = j * 3; FastAndNotXor(currentMasks[d2], stegoChunks[idx], etalonsRaw[d2]); FastAndNotXor(currentMasks[d1], stegoChunks[idx + 1], etalonsRaw[d1]); FastAndNotXor(currentMasks[d0], stegoChunks[idx + 2], etalonsRaw[d0]); digitUsed[d2] = true; digitUsed[d1] = true; digitUsed[d0] = true; } for (int k = 0; k < 10; k++) { if (digitUsed[k]) { if (FastPopCount(currentMasks[k]) < 2) { possible = false; break; } } } if (!possible) return; for (int k = 0; k < 10; k++) { for (int b = 0; b < blockByteSize; b++) currentMasks[k][b] = 0xFF; digitUsed[k] = false; } for (int j = 0; j < bytesEncrypted; j++) { byte b = bookBytes[i + j]; int d2 = b / 100; int d1 = (b % 100) / 10; int d0 = b % 10; int idx = j * 3; FastAndNotXor(currentMasks[d2], stegoChunks[idx], etalonsRaw[d2]); FastAndNotXor(currentMasks[d1], stegoChunks[idx + 1], etalonsRaw[d1]); FastAndNotXor(currentMasks[d0], stegoChunks[idx + 2], etalonsRaw[d0]); digitUsed[d2] = true; digitUsed[d1] = true; digitUsed[d0] = true; } int totalBits = 0; for (int k = 0; k < 10; k++) { if (digitUsed[k]) totalBits += FastPopCount(currentMasks[k]); } if (totalBits > 5) { lock (lockObj) { if (!found) { found = true; sw.Stop(); Console.WriteLine($"\n\n[SUCCESS] Взлом успешен! Offset: {i}"); Console.WriteLine($"[TIME] Время: {sw.ElapsedMilliseconds} мс"); SaveFoundKey(stegoPath, bookBytes, i, bytesEncrypted, currentMasks, etalonStrings, etalonLen); state.Stop(); } } } }); if (!found) Console.WriteLine("\n[FAIL] Совпадений не найдено."); } static void FastAndNotXor(byte[] target, byte[] stego, byte[] etalon) { for (int i = 0; i < target.Length; i++) { target[i] = (byte)(target[i] & ~(stego[i] ^ etalon[i])); } } static int FastPopCount(byte[] data) { int count = 0; for (int i = 0; i < data.Length; i++) { int n = data[i]; while (n != 0) { count++; n &= (n - 1); } } return count; } static void SaveFoundKey(string stegoPath, byte[] bookBytes, int offset, int len, byte[][] masksRaw, string[] etalonsStr, int etalonLen) { byte[] decrypted = new byte[len]; Array.Copy(bookBytes, offset, decrypted, 0, len); string text = Encoding.UTF8.GetString(decrypted); Console.WriteLine("--- НАЧАЛО ТЕКСТА ---"); Console.WriteLine(text.Substring(0, Math.Min(100, text.Length)) + "..."); Console.WriteLine("---------------------"); BitVector[] keyVectors = new BitVector[10]; for (int k = 0; k < 10; k++) { keyVectors[k] = new BitVector(masksRaw[k], etalonLen); } var sys = new AssocStego(etalonsStr); string keyPath = Path.ChangeExtension(stegoPath, ".recovered.key.bin"); string txtPath = Path.ChangeExtension(stegoPath, ".txt"); sys.SaveKeyToFile(keyPath, keyVectors); File.WriteAllText(txtPath, text); Console.WriteLine($"[SAVE] Ключ: {keyPath}"); Console.WriteLine($"[SAVE] Текст: {txtPath}"); } // --- РЕЖИМ 2: ШИФРОВАНИЕ --- static async Task RunEncryptMode(Dictionary args) { string bookPath = args.GetValueOrDefault("book") ?? string.Empty; string outFile = args.GetValueOrDefault("out") ?? "output.bin"; if (string.IsNullOrEmpty(bookPath) || !File.Exists(bookPath)) throw new FileNotFoundException("Книга не найдена."); string text = await File.ReadAllTextAsync(bookPath); Random rnd = new Random(); int len = rnd.Next(200, 600); int start = rnd.Next(0, text.Length - len); string secretText = text.Substring(start, len); Console.WriteLine($"[ENCRYPT] Фрагмент: \"{secretText.Substring(0, Math.Min(30, secretText.Length))}...\""); var etalons = AssocStego.GetDefaultEtalons10(); var system = new AssocStego(etalons); Console.WriteLine("[KEY] Генерация масок..."); var keys = system.CreateKey(); string keyFile = Path.ChangeExtension(outFile, ".key.bin"); system.SaveKeyToFile(keyFile, keys); Console.WriteLine($"[WRITE] Запись в {outFile}..."); byte[] data = Encoding.UTF8.GetBytes(secretText); using (var fs = new FileStream(outFile, FileMode.Create)) using (var ss = new AssocStegoStream(fs, system)) { ss.Write(data, 0, data.Length); } Console.WriteLine("[DONE] Успешно."); } // --- РЕЖИМ 3: ПРОВЕРКА --- static async Task RunVerifyMode(Dictionary args) { string dataPath = args.GetValueOrDefault("data") ?? string.Empty; string keyPath = args.GetValueOrDefault("key") ?? string.Empty; if (!File.Exists(dataPath) || !File.Exists(keyPath)) throw new FileNotFoundException("Файлы не найдены."); var etalons = AssocStego.GetDefaultEtalons10(); var system = new AssocStego(etalons); Console.WriteLine($"[LOAD] Ключ: {keyPath}"); system.Key = system.LoadKeyFromFile(keyPath); byte[] encrypted = await File.ReadAllBytesAsync(dataPath); int containerByteLen = (system.etalonLength + 7) / 8; int count = encrypted.Length / (containerByteLen * 3); byte[] buffer = new byte[count]; Console.WriteLine($"[DECRYPT] Расшифровка..."); using (var ms = new MemoryStream(encrypted)) using (var ss = new AssocStegoStream(ms, system)) { ss.Read(buffer, 0, count); } Console.WriteLine($"[TEXT] {Encoding.UTF8.GetString(buffer)}"); } } // --- КЛАССЫ ЛОГИКИ (ИСПОЛЬЗУЮТ BitVectorLib) --- public class AssocStegoStream : Stream { private readonly Stream _stream; private readonly AssocStego _stegoAlg; private readonly int _containerByteLen; private readonly int _hiddenByteLen; public AssocStegoStream(Stream stream, AssocStego stegoAlg) { _stream = stream; _stegoAlg = stegoAlg; _containerByteLen = (_stegoAlg.etalonLength + 7) / 8; _hiddenByteLen = 3 * _containerByteLen; } public override bool CanRead => _stream.CanRead; public override bool CanSeek => false; public override bool CanWrite => _stream.CanWrite; public override long Length => _stream.Length; public override long Position { get => _stream.Position; set => throw new NotSupportedException(); } public override void Flush() => _stream.Flush(); public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); public override void SetLength(long value) => throw new NotSupportedException(); public override void Write(byte[] buffer, int offset, int count) { for (int i = offset; i < offset + count; i++) { byte[] hidden = HideByte(buffer[i]); _stream.Write(hidden, 0, hidden.Length); } } public override int Read(byte[] buffer, int offset, int count) { int readCount = 0; for (int i = offset; i < offset + count; i++) { var hidden = new byte[_hiddenByteLen]; int totalRead = 0; while (totalRead < _hiddenByteLen) { int r = _stream.Read(hidden, totalRead, _hiddenByteLen - totalRead); if (r == 0) return readCount; totalRead += r; } buffer[i] = DiscloseByte(hidden); readCount++; } return readCount; } private byte[] HideByte(byte value) { int v = value; int d2 = v / 100; int rem = v % 100; int d1 = rem / 10; int d0 = rem % 10; BitVector c2 = _stegoAlg.HideEtalon(d2); BitVector c1 = _stegoAlg.HideEtalon(d1); BitVector c0 = _stegoAlg.HideEtalon(d0); byte[] b2 = c2.ToByteArray(); byte[] b1 = c1.ToByteArray(); byte[] b0 = c0.ToByteArray(); byte[] result = new byte[b2.Length + b1.Length + b0.Length]; int pos = 0; Buffer.BlockCopy(b2, 0, result, pos, b2.Length); pos += b2.Length; Buffer.BlockCopy(b1, 0, result, pos, b1.Length); pos += b1.Length; Buffer.BlockCopy(b0, 0, result, pos, b0.Length); return result; } public byte DiscloseByte(byte[] hidden) { byte[] b2 = new byte[_containerByteLen]; byte[] b1 = new byte[_containerByteLen]; byte[] b0 = new byte[_containerByteLen]; int pos = 0; Buffer.BlockCopy(hidden, pos, b2, 0, _containerByteLen); pos += _containerByteLen; Buffer.BlockCopy(hidden, pos, b1, 0, _containerByteLen); pos += _containerByteLen; Buffer.BlockCopy(hidden, pos, b0, 0, _containerByteLen); int d2 = _stegoAlg.DiscloseEtalon(new BitVector(b2, _stegoAlg.etalonLength)); int d1 = _stegoAlg.DiscloseEtalon(new BitVector(b1, _stegoAlg.etalonLength)); int d0 = _stegoAlg.DiscloseEtalon(new BitVector(b0, _stegoAlg.etalonLength)); if (d2 < 0 || d1 < 0 || d0 < 0) return 63; return (byte)(d2 * 100 + d1 * 10 + d0); } } public class AssocStego { public readonly BitVector[] etalons; public readonly int etalonLength; private readonly List[] maskUnitPositions; private BitVector[]? key; private readonly BitVector zeroVector; // Свойство выбрасывает исключение, если ключ не задан public BitVector[] Key { get => key ?? throw new InvalidOperationException("Ключ не сгенерирован!"); set => key = value; } public AssocStego(params string[] e) { etalonLength = e[0].Length; etalons = new BitVector[e.Length]; maskUnitPositions = new List[e.Length]; zeroVector = new BitVector(new byte[(etalonLength + 7) / 8], etalonLength); for (int i = 0; i < e.Length; i++) { etalons[i] = new BitVector(e[i]); maskUnitPositions[i] = new List(); } } public BitVector[] CreateKey() { var eIndexes = Enumerable.Range(0, etalons.Length).ToList(); ResetMaskUnitPositions(); CreateKeyRec(eIndexes, true); var masks = new BitVector[etalons.Length]; for (int i = 0; i < etalons.Length; i++) masks[i] = CreateVector(etalonLength, maskUnitPositions[i].ToArray()); Key = masks; return masks; } private void CreateKeyRec(List eIndexes, bool firstPerm) { if (eIndexes.Count < 2) return; var c = firstPerm ? Shuffle(eIndexes) : eIndexes; var d1 = new List { c[0] }; BitVector a0 = etalons[c[0]] ^ etalons[c[1]]; for (int i = 0; i < c.Count - 2; i++) { BitVector a1 = etalons[c[0]] ^ etalons[c[i + 2]]; BitVector a3 = a0 & a1; if (IsZero(a3)) d1.Add(c[i + 2]); else a0 = a3; } int rndBit = GetRandomOneBitIndex(a0); foreach (int idx in c) maskUnitPositions[idx].Add(rndBit); var d2 = c.Except(d1).ToList(); Parallel.Invoke(() => CreateKeyRec(d1, false), () => CreateKeyRec(d2, false)); } private bool IsZero(BitVector v) { foreach (byte b in v.ToByteArray()) if (b != 0) return false; return true; } private List Shuffle(List list) { var res = new List(list); var rng = new Random(); int n = res.Count; while (n > 1) { n--; int k = rng.Next(n + 1); (res[k], res[n]) = (res[n], res[k]); } return res; } private void ResetMaskUnitPositions() { foreach (var l in maskUnitPositions) l.Clear(); } private int GetRandomOneBitIndex(BitVector v) { var bytes = v.ToByteArray(); var ones = new List(); for (int i = 0; i < bytes.Length * 8; i++) { int byteIdx = i / 8; if (byteIdx >= bytes.Length) break; int bitIdx = 7 - (i % 8); if ((bytes[byteIdx] & (1 << bitIdx)) != 0) ones.Add(i); } if (ones.Count == 0) return 0; return ones[new Random().Next(ones.Count)]; } private BitVector CreateVector(int len, int[] indexes) { int byteLen = (len + 7) / 8; byte[] arr = new byte[byteLen]; foreach (int idx in indexes) { int byteIndex = idx / 8; int bitIndex = 7 - (idx % 8); if (byteIndex < arr.Length) arr[byteIndex] |= (byte)(1 << bitIndex); } return new BitVector(arr, len); } public BitVector HideEtalon(int idx) { byte[] randomBytes = new byte[(etalonLength + 7) / 8]; RandomNumberGenerator.Fill(randomBytes); BitVector container = new BitVector(randomBytes, etalonLength); return container ^ ((container ^ etalons[idx]) & Key[idx]); } public int DiscloseEtalon(BitVector stego) { for (int i = 0; i < etalons.Length; i++) { BitVector a = stego & Key[i]; BitVector b = etalons[i] & Key[i]; if (a.ToByteArray().SequenceEqual(b.ToByteArray())) return i; } return -1; } public void SaveKeyToFile(string path, BitVector[] k) { using var bw = new BinaryWriter(File.Create(path)); bw.Write(0x41534B59); bw.Write(k.Length); bw.Write(etalonLength); foreach (var vec in k) { byte[] b = vec.ToByteArray(); bw.Write(b.Length); bw.Write(b); } } public BitVector[] LoadKeyFromFile(string path) { using var br = new BinaryReader(File.OpenRead(path)); if (br.ReadInt32() != 0x41534B59) throw new Exception("Bad magic"); int count = br.ReadInt32(); int len = br.ReadInt32(); var res = new BitVector[count]; for (int i = 0; i < count; i++) { int blen = br.ReadInt32(); byte[] b = br.ReadBytes(blen); res[i] = new BitVector(b, len); } return res; } public static string[] GetDefaultEtalons10() => new[] { "111111111111111111111111111111111111111111111111111111000000000000000000000000", "000000000100000000000000000111111111111111111100000000000000000000000011111111", "100000000000000000111111111111111111100000000111111111111111110000000000000000", "100000000100000000111111111100000000100000000000000000111111111111111111111111", "000000000111111111100000000111111111111111111100000000000000001111111100000000", "100000000111111111111111111100000000111111111111111111000000001111111100000000", "111111111100000000000000000100000000111111111111111111000000001111111111111111", "111111111100000000111111111100000000000000000000000000000000000000000011111111", "111111111111111111111111111111111111111111111111111111000000001111111100000000", "100000000111111111111111111111111111100000000000000000111111111111111100000000" }.Select(s => new string(s.Reverse().ToArray())).ToArray(); } public static class BitVectorHelpers { public static BitVector CreateFullMask(int bitLength) { int byteLen = (bitLength + 7) / 8; byte[] b = new byte[byteLen]; for (int i = 0; i < byteLen; i++) b[i] = 0xFF; return new BitVector(b, bitLength); } public static BitVector CreateZeroMask(int bitLength) => new BitVector(new byte[(bitLength + 7) / 8], bitLength); public static BitVector AndNot(BitVector a, BitVector b) { byte[] ba = a.ToByteArray(); byte[] bb = b.ToByteArray(); int len = Math.Min(ba.Length, bb.Length); byte[] result = new byte[ba.Length]; for (int i = 0; i < len; i++) result[i] = (byte)(ba[i] & ~bb[i]); return new BitVector(result, a.ToByteArray().Length * 8); } public static int PopCount(BitVector v) { byte[] bytes = v.ToByteArray(); int count = 0; foreach (byte b in bytes) { int n = b; while (n > 0) { if ((n & 1) == 1) count++; n >>= 1; } } return count; } } }