Heroina….

Dosłownie….

jest 06:50 – o 23 odkryłem w końcu flagę… ale nie miałem siły już jej odwracać. Wprowadziłem drobne poprawki i teraz publikuję.

Zadanie straszne – Misja 013 – i jednocześnie fascynujące, wciągające. Kilka razy podchodziłem i odchodziłem od kompa, wyłączałem wkurzony, żeby wrócić i pójść krok dalej… jak jakaś substancja przyspieszająca przesyłanie neuronów w moim mózgu nie dawało mi spokoju…..

Zastanawiałem się jak do tego podejść… i od razu wyszedłem od specyfikacji PNG z książki „Zrozumieć programowanie” – postanowiłem użyć do tego „prostego parsera”, ale przeróbki kodu z Pythona 2 na 3 mi jakoś nie wychodziły, wiec stwierdziłem, że lepiej poczuję to w c#.

Zrobiłem w c# test sprawdzający ilość kolorów w wejściowym pliku PNG i były to tylko 2 kolory – czarny i biały, żadnego kanału alpha który coś ukrywa .net swoimi bibliotekami nie wykrył.

Tak jak czułem od początku trzeba było zejść na poziom bajtów…

Zacząłem mozolną pracę pisania parsera. Nie wszystko rozumiałem. Początkowo nie kleiłem że każda część pliku PNG poza magic’em składa się z takiego samego członu. No ale w końcu – kiedy po 10 letniej praktyce programistycznej, 5 latach studiów dowiedziałem się, że nie czytałem Przygód Guliwera i nie słyszałem o littleendian i binendian – czyli o zwolennikach konsumowania jajek od jednej ze stron… zaczęło to iść we właściwym kierunku.

Późnej trochę bawiłem się z czytaniem zdekompresowanych bajtów i wychodziły mi takie cuda:

by w końcu… po kilku testach i błędach dojść do tego….

stwierdziłem, że to kod kreskowy…. zacząłem kminić, szukać mieszać – poprosiłem kolegów o pomoc….

i nic….

Oj kombinowałem….

W końcu idąc na spacerze i czytając nowego Programistę – artykuł o analizie obrazu wykorzystywanego w biometrii przyszło mi do głowy, że to ciąg bitów…..

Już wcześniej kombinowałem z bitami znajdując „1” gdzieś na granicach czcionki, którą pisany jest tekst – w poszczególnych kolorach….

No i po kilku mozolnych próbach jest:

Więcej nie napiszę bo muszę lecieć do pracy….

Kodzik:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.Drawing;
using System.IO.Compression;
 
namespace MISJA_013
{
    public static class BitHelper
    {
        public static uint getUint(byte[] buffer)
        {
            if (BitConverter.IsLittleEndian)
                Array.Reverse(buffer);
            return BitConverter.ToUInt32(buffer, 0);
        }
        public static uint readUint(BinaryReader br)
        {
            var buffer = br.ReadBytes(4);
            return getUint(buffer);
        }
        public static T[] SubArray(this T[] data, int index, int length)
        {
            T[] result = new T[length];
            Array.Copy(data, index, result, 0, length);
            return result;
        }
        public static void CopyTo(Stream src, Stream dest)
        {
            byte[] bytes = new byte[4096];
 
            int cnt;
 
            while ((cnt = src.Read(bytes, 0, bytes.Length)) != 0)
            {
                dest.Write(bytes, 0, cnt);
            }
        }
        public static byte[] decompress(byte[] input)
        {
            var output = new MemoryStream();
            using (var compressStream = new MemoryStream(input))
            {
                compressStream.ReadByte();
                compressStream.ReadByte();
                using (var decompressor = new DeflateStream(compressStream, CompressionMode.Decompress))
                    decompressor.CopyTo(output);
                return output.ToArray();
            }
        }
        public static byte[] GetBytesFromBinaryString(String binary)
        {
            var list = new List();
 
            for (int i = 0; i < binary.Length; i += 8) { String t = binary.Substring(i, 8); list.Add(Convert.ToByte(t, 2)); } return list.ToArray(); } } class Program { public class png { public class pngIHGR { public uint Width { get; } public uint Height { get; } public byte BitDepth { get; } public byte ColorType { get; } public byte CompressionMethod { get; } public byte FilterMethod { get; } public byte InterlaceMethod { get; } public pngIHGR(pngData data) { this.Width = BitHelper.getUint( BitHelper.SubArray(data.ChunkData, 0, 4) ); this.Height = BitHelper.getUint( BitHelper.SubArray(data.ChunkData, 4, 4) ); this.BitDepth = data.ChunkData[8]; this.ColorType = data.ChunkData[9]; this.CompressionMethod = data.ChunkData[10]; this.FilterMethod = data.ChunkData[11]; this.InterlaceMethod = data.ChunkData[12]; } public new string ToString() { return string.Format("Width {0}\n" + "Height {1}\n" + "BitDepth {2}\n" + "ColorType {3}\n" + "CompressionMethod {4}\n" + "FilterMethod {5}\n" + "InterlaceMethod {6}\n" , Width, Height, BitDepth, ColorType, CompressionMethod, FilterMethod, InterlaceMethod ); } } public pngIHGR IHDR { get; } public class pngData { public uint Length { get; set; } public char[] ChunkType { get; set; } public byte[] ChunkData { get; set; } public uint CRC { get; set; } public bool CRCValid { get; } public pngData(BinaryReader br) { this.Length = BitHelper.readUint(br); this.ChunkType = br.ReadChars(4); this.ChunkData = br.ReadBytes((int)this.Length); this.CRC = BitHelper.readUint(br); this.CRCValid = VerifyCRC(); } public bool VerifyCRC() { var crc32 = new NullFX.Security.Crc32(); byte[] type = this.ChunkType.Select(x => Convert.ToByte(x)).ToArray();
                    byte[] toCRC = new byte[type.Length + this.ChunkData.Length];
                    Array.Copy(type, toCRC, type.Length);
                    Array.Copy(this.ChunkData, 0, toCRC, type.Length, ChunkData.Length);
                    var crc = crc32.ComputeChecksum(toCRC);
                    if (crc == this.CRC)
                        return true;
                    else
                        return false;
                }
            }
            public byte[] ImageData { get; }
 
            public png(BinaryReader br)
            {
                if (br.ReadByte() != 0X89
                    || br.ReadBytes(3).Equals(Encoding.ASCII.GetBytes("PNG"))
                    || br.ReadByte() != 0X0D
                    || br.ReadByte() != 0X0A
                    || br.ReadByte() != 0X1A
                    || br.ReadByte() != 0X0A
                    )
                    throw new Exception("wrong PNG header");
 
                var ihdr = new pngData(br);
                this.IHDR = new pngIHGR(ihdr);
                var idata = new pngData(br);
                this.ImageData = BitHelper.decompress(idata.ChunkData);
                var iend = new pngData(br);
                br.Close();
            }
        }
        static void Main(string[] args)
        {
            try
            {
                var fn = "e30fd841613c376e45653fa679cb710bd898b69f_misja013.png";
                var stream = File.Open(fn, FileMode.Open);
                var br = new BinaryReader(stream);
                var png = new png(br);
                Console.WriteLine(png.IHDR.ToString());
                uint filter = 0;
                Bitmap result = new Bitmap((int)png.IHDR.Width
                    , (int)png.IHDR.Height);
                for (int i = 0; i < result.Width; i++)
                    for (int j = 0; j < result.Height; j++)
                        result.SetPixel(i, j, Color.Red);
                var ms = new MemoryStream(png.ImageData);
                var reader = new BinaryReader(ms);
                var cnt = 0;
                for (uint y = 0; y < png.IHDR.Height; y++)
                    for (uint x = 0; x < png.IHDR.Width; x++) { cnt++; if (x == 0) filter = reader.ReadByte(); if (cnt >= png.ImageData.Length)
                            break;
                        var r = reader.ReadByte();
                        var g = reader.ReadByte();
                        var b = reader.ReadByte();
 
                        var color = Color.FromArgb(r, g, b);
                        result.SetPixel((int)x, (int)y, color);
                    }
 
                Console.WriteLine("cnt: " + cnt.ToString());
                Console.WriteLine("Matrix: " + ms.Length.ToString()+"\n");
                if (File.Exists("result.png"))
                    File.Delete("result.png");
                result.Save("result.png"
                    , System.Drawing.Imaging.ImageFormat.Png);
 
                string bits = "";
                for (int y = 0; y < result.Height; y++)
                {
                    bits += result.GetPixel(1, y)
                        == Color.FromArgb(0, 0, 0)
                        ? "1" : "0";
                }
                //Console.WriteLine(bits);
                string revBits = "";
                var tmp = bits.ToCharArray();
                Array.Reverse(tmp);
                revBits = new string(tmp);
 
                var data = BitHelper.GetBytesFromBinaryString(revBits);
                var text = Encoding.ASCII.GetString(data);
                tmp = text.ToArray();
                Array.Reverse(tmp);
                Console.WriteLine(new string(tmp));
 
                Console.ReadLine();
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                Console.WriteLine(ex.StackTrace);
                if (ex.InnerException != null)
                {
                    Console.WriteLine(ex.InnerException.Message);
                    Console.WriteLine(ex.InnerException.StackTrace);
                }
                Console.ReadKey();
            }
        }
    }
}

 

ostatecznie:

Wszystko kryło się w filtrze…. pan CLT to znalazł, zastosowany filtr „SUB” eliminuje w parserach implementujących filtry 🙂 linie… choć nie do końca czujemy specyfikację, jak dzieje się taka magia że linie znikają a tekst zostaje…. trzeba to będzie jeszcze przekminić….

Mam nadzieję, że swoją implementację w Pythonie w końcu opublikuje…. a nie będzie dalej ghostwriterem 🙂

Heroina dalej wciąga, więc postanowiłem dodać filtr, aby uzyskać oryginalny obrazek po przejściu parserem

Takie tam:

if (filter == 1 && x > 0)
{
    var c = result.GetPixel((int)x - 1, (int)y);
    r = (byte)((r + c.R) % 256);
    g = (byte)((g + c.G) % 256);
    b = (byte)((b + c.B) % 256);
}

nie wiem czy to w 100% zgodne ze specyfikacją, ale działa… 🙂 😛 256 uznaje za 2^8 czyli bpp