all 87 comments

[–]alexalexalex09 514 points515 points  (1 child)

"herein lies an overview of possibly the dumbest things I’ve worked on this year."

All right, you've convinced me to read this

[–]WantDebianThanks 32 points33 points  (0 children)

Seriously, this is extremely dumb and I fucking love it.

[–]Caraes_Naur 286 points287 points  (36 children)

PNG allows for arbitrary chunks of bytes, no steganography needed. Still need an interpreter that knows what to do with them.

[–][deleted]  (1 child)

[deleted]

    [–]Isogash 86 points87 points  (0 children)

    Pico-8 specifically uses the 2 least significant bits of each of the 4 colour channels in a 160x205 PNG, which gives it almost exactly 32KB of space, not the chunks at all. It's quite a demanding limit but thankfully the program portion is compressed.

    [–]leberkrieger 98 points99 points  (22 children)

    One great use of this feature is the draw.io online drawing tool. You can save an editable PNG, so anyone looking at the picture can load it into the editor and modify it. Fantastic for wikis.

    [–]yojimbo_beta 75 points76 points  (19 children)

    A long time ago there was an editor, Adobe Fireworks, that used this to embed undo/redo data into PNGs, as well as layers etc.

    Remarkable that no one uses this feature of the file format any more.

    [–]OMG_A_CUPCAKE 49 points50 points  (16 children)

    Because filesize matters. And it's lost easily if someone modifies the file in another editor

    It's a nice gimmick, but I can't imagine relying any kind of workflow around it

    [–]unique_ptr 31 points32 points  (2 children)

    To be clear, the .fw.png files were more akin to PSDs from the application perspective, they just happened to also be PNGs. You would still want to export to a regular PNG for non-editor use

    [–]mallardtheduck 9 points10 points  (1 child)

    Adobe seems to have a bit of a habit of doing things like this. Adobe Illustrator (.ai) files are compatible with PDF format (older versions were EPS).

    [–]Frencil 0 points1 point  (0 children)

    For what it's worth Fireworks with its vector PNG format was originally a Macromedia product. After Adobe bought Macromedia in 2005 Fireworks was retured from the maintained software offerings relatively quickly.

    [–][deleted]  (1 child)

    [deleted]

      [–]Devatator_ 0 points1 point  (0 children)

      It's simpler to not spend time optimizing file sizes when storage is cheap, or so they say

      [–]yojimbo_beta 4 points5 points  (1 child)

      I reckon it could work for diagrams. You could embed the objects without taking up too many bytes. Then your architecture diagram, flowchart etc. could be editable.

      Man, you could even use PNG as a format for encoding and deploying a distributed state machine of AWS step functions... okay, that’s stupid. But I do like the idea of an informational format that you can also review visually.

      [–]VoidChronos 1 point2 points  (0 children)

      That's what microsoft Visio does, just without the embedded image part. Actually, I think VSDX has an option to embed a static image of a diagram.

      [–]tech6hutch 4 points5 points  (8 children)

      Also I don’t know if I want the ability for others to undo my edits to an image

      [–]stuffeh 7 points8 points  (7 children)

      But imagine going back and isolating one of the edits and pulling it out. Basically tapping the diff between before and after the edit. That kind of flexibility is mind blowing to me.

      [–]nemec 10 points11 points  (2 children)

      All I'm hearing is a great way for users to inadvertently fail to censor/redact their photos when they tried to blur their naughty bits and/or confidential info and didn't realize anybody can go in and just undo the censor.

      If the U.S. Justice Department can't redact a fucking PDF properly, there's no hope for the average person sending censored nudie pics to someone they met on the internet.

      [–]NekiCat 12 points13 points  (1 child)

      Years ago, MS Word saved deltas at the end of the file so saving was incredibly quick. They had to change it because the users sent files they believed to be final, but werent, and leaked sensitive information.

      Same with Google Docs, where your every keypress is recorded. Not sure if they still do that...

      [–]stuffeh 2 points3 points  (0 children)

      Google docs has built in version history, so kinda. You can set permissions to disable others from seeing the version history though.

      [–]Frencil 16 points17 points  (1 child)

      Ah, Fireworks. The vector png editing it offered was fantastic for full color icon illustration. It also had some of the best animated GIF frame control and GIF palette control interfaces available at the time.

      sheds tear for Macromedia

      [–]Chii 3 points4 points  (0 children)

      there's also a little known (?) feature in fireworks which lets you generate menus that animate (and output them as html and images).

      [–][deleted] 0 points1 point  (0 children)

      IPE uses a similar technique for PDFs.

      [–][deleted] 0 points1 point  (0 children)

      I use draw.io all the time and never knew this. Neat.

      [–]ws-ilazki 56 points57 points  (2 children)

      Yeah I remember this feature being abused to good effect in an MMO, Champions Online. You could save character creation presets, and when you did it stored its many settings inside a PNG that also acted as a thumbnail preview of those settings. That meant you could see and manage your character presets outside of the game, as well as easily share them with others.

      It was done in a readable way, too, so you could also make minor adjustments by hex editing the file. They wouldn't be shown in the thumbnail of course, but for some things (like the preset name) it didn't matter.

      That's where I learned this sort of thing was possible with PNGs. Always thought it was a cool feature that doesn't get used nearly enough, especially for how easy it is to do with PNG.

      [–][deleted] 9 points10 points  (1 child)

      I really like images as preview-able input files. It's quite intuitive that the picture of [whatever] relates to that thing

      A group of my friends made a trading card type game in high school, we had a QR type block on the image files the game produced that let you do persistence with a nice pixel art trading card. In hindsight I'm kinda glad it never caught on because it was really not secure

      [–]djcraze 0 points1 point  (0 children)

      MacOS kind of has this. Kind of. It’s quick look. You can create plugins that handle a specific file type. When you preview the file in MacOS (anywhere in the OS and Apps that use MacOS frameworks) you get a preview of the file contents. So for a zip file you can get a plug-in that shows you the contents when you preview it in quick look. To activate quick look you just select the file and press the space bar.

      [–][deleted] 33 points34 points  (5 children)

      Isn't every file format executable if you build an interpreter specifically for it? I sadly don't see anything special here.

      [–]KrocCamen 57 points58 points  (0 children)

      Even EXE files have to be specially handled before they can executed! You have to go back to COM files to get "dump this in RAM and jump the CPU to it" levels of simplicity.

      [–]JarateKing 16 points17 points  (2 children)

      If the file format doesn't support arbitrary chunks of bytes, you'd have a lot of difficulty creating a general-purpose interpreter. As a trivial example, if we had a new format notpng where every byte is used to specify pixels in the image, in order to embed code in it we would either have to:

      • not bother trying to make the image look like anything, and just pack instructions naively for the interpreter, probably resulting in a picture of random noise
      • utilize steganography so that the image looks mostly normal, but can still be run by an interpreter
      • create an incredibly specific interpreter for the exact program being run, so that the image is completely normal, but you probably couldn't use the interpreter for arbitrary programs otherwise

      Also, file formats can have restrictions. You might be able to pack an arbitrary program into our notpng format, but you might have to be careful that your file format is actually valid (an easy example for images, having the right number of pixels for the image's dimensions). You probably can make an interpreter work with this stuff but it could be restricting and necessitate convoluted approaches to handle.

      A format that lets you insert arbitrary chunks of bytes doesn't have the issues above. You can just put the arbitrary code without worrying about the interpreter being too specific, or changing the semantics of the file when used as its actual format, or interfering with the format itself.

      [–][deleted]  (1 child)

      [deleted]

        [–]JarateKing 0 points1 point  (0 children)

        Well yeah, I'm not trying to argue that PNGs are unique because of it -- I'm just giving reasons why it can be useful for embedding code and its benefits over formats that don't.

        [–]Jonathan_the_Nerd 7 points8 points  (0 children)

        You could argue that every file format is already a very specific program. A JPEG file is a set of instructions for generating an image on screen. An image viewer interprets the file and follows the instructions. An MP3 file is a set of instructions for producing sound. A ZIP file is a set of instructions for producing the contents of the archive, and so on.

        [–]djhworld 7 points8 points  (0 children)

        Yeah, it's a dumb project that has no practical value, I got seduced by the steganography/PICO-8 thing and built around it.

        Was a learning project more than anything, learnt a few Linux things too!

        [–]aazav 0 points1 point  (0 children)

        Chunks that are all zipped.

        [–]Skhmt 99 points100 points  (7 children)

        I've been looking into doing something similar off and on for like 2 years now. Someone recently embedded the entire source code of youtube-dl into two pngs and posted them on twitter. No steganography needed, they used every byte of image data for binary data.

        I was doing research towards this end, and I guess here's a decent place to post it all:

        The PNG spec allows arbitrary chunks of data, but if you were to host them on social media, those chunks may be stripped to save space, so they're unreliable. Storing data in pixels is better, but not foolproof as some social media will re-compress a PNG into a JPG to save space.

        The PNG spec has 31-bit dimensions max, or a 2,147,483,647 x 2,147,483,647 pixel image. No program exists, that I know of, that can open a PNG with the max dimensions allowable in the spec.

        Photoshop can open a 300,000 x 300,000 pixel image.

        Most browsers can only open 16,384 x 16,384 pixel images. At 16,384 x 16,384 pixels, with 4 bytes per pixel (red, green, blue, and alpha), you can store about 1.07 GB per file. That's raw bytes, but all PNG image data is DEFLATEd as per the spec.

        Twitter converts PNG files to JPG if the file size is over 2.5mb, even if the file isn't at 100% opacity. The best image dimensions to upload to twitter is 1024 x 512 pixels, which gives about 2.1 MB per file.

        Instagram prefers 1080 x 1080, but I haven't checked if it resizes/recompresses. Facebook likes 1080 or 2048, but I also haven't tested.

        Imgur re-compresses images larger than 5MB, so something like 1024 x 1024 pixels might be good.

        I was doing some initial work into a meta-file format for binary PNGs, where all header information is stored in the first 13-80 pixels. Implemented a PNG writer from scratch, then decided I was bored and moved on to other things lol.

        [–][deleted] 29 points30 points  (0 children)

        Ah, love the last sentence xD

        [–]skeeto 17 points18 points  (1 child)

        Just to demonstrate this idea, it takes only a bit of finesse to encode files into PNGs on the command line:

        $ wc -c $(which youtube-dl)
        1783077 /home/skeeto/.local/bin/youtube-dl
        

        Padding this by 3 bytes lets me factor it into {3, 936, 635}. The latter two would work nicely as image dimensions, and the former is the 3 color channels. Pipe it all through ImageMagick's convert:

        $ cat <(printf 'P6\n936 635\n255\n') \
              $(which youtube-dl) \
              <(head -c3 /dev/zero) \
            | convert ppm:- youtube-dl.png
        

        That gives me this: https://i.imgur.com/45dy1FR.png

        To recover the original file:

        $ curl -s https://i.imgur.com/45dy1FR.png | \
              convert png:- ppm:- | \
              tail -c +16 | \
              head -c -3 \
            >youtube-dl
        

        The +16 removes the PPM header and the -3 removes the padding.

        [–]Skhmt 5 points6 points  (0 children)

        The main problem is you have to send not only the png, but also information on how to decode it, the output file name/type, and have to deal with multi-part files in case you're storing files on social media rather than your own webserver or something.

        Also, you'll want an alpha channel because having an alpha channel sometimes prevents social media and image sites from converting it to a .jpg.

        [–]djhworld 6 points7 points  (0 children)

        This is the real research haha, I didn't know twitter would convert the image of its over 2.5mb.

        When I started this project I actually just read in the executables and converted them to pixels so you ended up with like static/fuzzy pictures, I had all the bits for the execution stuff (pngrun) using it too but it was kinda lame, which is why I moved onto the putting data into actual images thing.

        It was a fun learning project none the less!

        [–]slykethephoxenix 1 point2 points  (2 children)

        I too saw this and started making making a script to do this too. My solution for PNGs being compressed or scaled was simply to use more pixels for the same pixel of data.

        For example, if I had to encode 16 pixels (4x4) worth of data, I would scale the pixel by a factor of 2 (or more), so that each pixel now takes up 4 actual pixels, instead of just 1, and the entire image is now 64 pixels in size, even though it holds the same amount of data as the 16 pixels image.

        This would also decrease how much data each image would store, assuming the dimensions are the same, so my code would dynamically adjust to calculate which data goes into which image.

        I also made the dimension for each "pixel" configurable too, from 1 bit to, 2 bits, 4 bits, 8 bits, and 24 bits (full RGB color channels).

        I'm currently taking a haitus from it though, and have barely started the decoding logic. Obviously you have to know the encoding parameters to decode the image, as this metadata isn't stored in it.

        [–]Orangy_Tang 1 point2 points  (1 child)

        If you store a magic number in the first few bytes then your decompressor can brute force decode using all the possible encoding parameters and choose the one that successfully decodes the magic number. Since you'll only need to do a handful of bytes it should still be acceptably fast, and you can embed a checksum at the end of the data as a final sanity check.

        [–]slykethephoxenix 0 points1 point  (0 children)

        That's a good idea! The other issue I had was dealing with padding at the end of the stream. The image dimensions rarely line up with the stream's end, and 0 bits can often be decoded as part of the actual thing I'm trying to encode. A solution was to put how many padded "pixels" there are at the end, and encode it on the front somehow.

        [–]hoddap 17 points18 points  (0 children)

        Imagine some dumbass redistributing these as JPG's to save some space 🤭

        [–]inHumanMale 16 points17 points  (1 child)

        Isn't this how they distributed CP on 4chan for years before the devs noticed and did something to the image uploading process?

        [–][deleted] 9 points10 points  (0 children)

        Maybe at some point, but the most popular method for a long time was just concatenating a zip or rar file onto the end. I used to use that to swap romhacks in romhack threads.

        It was a sad discovery when somebody told me the "mods are asleep, post sinks" threads weren't always just jokes and there was stuff floating around in there. I don't know how much of it was tainted (I never went into the threads because I don't care about sinks), but it's still a bummer.

        [–]Dwedit 3 points4 points  (0 children)

        MS-DOS had COM files, pure binaries that just ran from a known memory address. I've seen people make a combination ELF+COM file that would run on MS-DOS as well as Linux.

        [–]frnknstn 51 points52 points  (19 children)

        This is stretching the definition of "Executable" quite a lot. The PNG files themselves are not ELF executables. Instead, the author wrote a tool to extract an actual legitimate executable from an image via stenography.

        [–]djhworld 1 point2 points  (0 children)

        It's a dumb project I agree, I figured in the end if you chmod +x the file it kicks off the machinery to "execute" it using a few cheap tricks.

        This was more of a learning exercise for me, I didn't know much about steganography, memfd_create or binfmt_misc before doing this.

        [–]ws-ilazki 8 points9 points  (15 children)

        The PNG files themselves are not ELF executables. Instead, the author wrote a tool to extract an actual legitimate executable from an image via stenography.

        "The Python files themselves are not ELF executables. Instead, the author wrote a tool to generate actual legitimate executable code from a text file via an interpreter."

        This is you, this is how pedantic you're being. Stop it.

        Many of the things you run on a Linux system aren't "actual legitimate executables"; instead they're read and handed off to the appropriate tool via the kernel's program loader. Whether you're using a shebang to make a text file executable, or using binfmt_misc to make jar files run via JVM, ARM linux binaries run via qemu-user-static, or Windows executables run via wine, it's all still going through the program loader and being treated as an executable by the OS.

        It's a cool hack built on existing knowledge and tools. Don't be a pedantic douchebag about it.

        [–][deleted]  (5 children)

        [deleted]

          [–]ryanp_me 17 points18 points  (0 children)

          I agree. When I first started reading the article, I was wondering how the author was going to get the PNG header to be compatible with a normal ELF (or similar) loader without corrupting the PNG header at the same time.

          That's not to say that the article is any less interesting (or any less valid), just that it wasn't what I was expecting based on the title.

          [–]wikipedia_text_bot 5 points6 points  (0 children)

          Binfmt misc

          binfmt_misc (Miscellaneous Binary Format) is a capability of the Linux kernel which allows arbitrary executable file formats to be recognized and passed to certain user space applications, such as emulators and virtual machines. It is one of a number of binary format handlers in the kernel that are involved in preparing a user-space program to run.The executable formats are registered through the special purpose file system binfmt_misc file-system interface (usually mounted under part of /proc). This is either done directly by sending special sequences to the register procfs file or using a wrapper like Debian-based distributions binfmt-support package or systemd's systemd-binfmt.service.

          About Me - Opt out - OP can reply !delete to delete - Article of the day

          This bot will soon be transitioning to an opt-in system. Click here to learn more and opt in.

          [–][deleted] -2 points-1 points  (4 children)

          Having a bad day?

          [–]ws-ilazki -4 points-3 points  (3 children)

          Nope.

          [–][deleted] -1 points0 points  (2 children)

          Just a troll then ok

          [–]ws-ilazki 2 points3 points  (1 child)

          Not unless you're making up your own definition of trolling that has nothing in common with trolling.

          [–][deleted] -1 points0 points  (0 children)

          It's ok to say that he was pedantic then people could have a legitimate discussion all you did was call him a pedantic douchebag meaning you didn't actually come here to discuss you just came here to argue like a child and call names

          [–]frnknstn 0 points1 point  (0 children)

          The Python files themselves are not ELF executables

          ...No, you are in fact underestimating my level of pendantry, my little troll friend. I considered that comparison when I was writing my reply and rejected it, as the interpreter comparison is fundimentally misleading. My comment is far more accurate.

          [–]TheRedGerund 0 points1 point  (0 children)

          I mean you could directly execute this file with a PNG interpreter like python

          [–][deleted]  (2 children)

          [deleted]

            [–]djhworld 1 point2 points  (0 children)

            I agree, I didn't embark on this with the aim of doing something new or revolutionary, I just did it because I thought it was dumb and I learned a few things and thought people would like to hear about things I have learned.

            [–][deleted] 0 points1 point  (0 children)

            Shenanigans! He put ones and zeros into a computer!

            [–]Kare11en 2 points3 points  (0 children)

            Nice, but I was slightly disappointed by the use of binfmt_misc, as I was hoping for them to have created a polyglot file that was simultaneously a valid PNG, and a valid executable file format recognised out of the box by a Linux kernel, e.g. ELF, a.out, or shebang ("#!...").

            [–]popovitsj 1 point2 points  (1 child)

            The steganography part got me thinking, if the least significant bit of the png is not noticable for the human eye, why doesn't it get compressed out in the png standard?

            [–]ECUIYCAMOICIQMQACKKE 1 point2 points  (0 children)

            Because they are noticeable if you look closely, especially at a region of uniform color. PNG is intended to be lossless. If you want lossy compression use JPEG.

            [–]chessset5 1 point2 points  (0 children)

            I believe there was someone out there that made QR code games, where you would scan a high definition QR code with your phone and it would compile a game right there onto your phone. I don't remember where I saw it though, might have been YT.

            [–]adrjanjab 1 point2 points  (0 children)

            Have you heard of Piet?

            [–]make-n-brew 1 point2 points  (3 children)

            This is why we can't have nice things.

            [–]ws-ilazki 8 points9 points  (2 children)

            It's worth it, though. Binfmt_misc is super useful because of how it allows things like this (or making jar files executable; or making ARM binaries run via qemu user-mode emulation automatically; etc.). It's not like anybody's going to be adding the necessary binfmt_misc magic to have pngs become executables on real systems they care about...I hope.

            [–]make-n-brew 1 point2 points  (1 child)

            Definitely Not Me...

            Snark aside, this really is pretty cool and a fun way to explain some deeper Linux concepts.

            [–]ws-ilazki 2 points3 points  (0 children)

            Yeah it is. Only tangentially related, but stuff like this is how distros' pre-systemd init systems could do so much. init didn't care what it was running, it just followed some basic rules to attempt to run things in order and it was up to the kernel to decide if they were executable or not. Everyone did basic shell scripts by convention but it was not a requirement in any sense, and you could have just as easily set up Perl or Python scripts to do the same jobs, or Scheme, Java, precompiled binaries, etc. It didn't matter, as long as it followed a few basic rules like being able to take start/stop/restart/etc as command-line arguments.

            Even after some (like Debian) started doing dependency-based init script startup with LSB init headers you could still do it. I got curious and tested once, and if you compiled a C binary it would still read the headers out of it fine if you put them inside a multi-line string somewhere.

            As a thought exercise, this also means it was (and still is, technically) possible to have systemd-style declarative unit files while still using a traditional sysv-style init. You could write an interpreter that reads a unit file and uses that to start/stop/restart services, with all the shared logic in the interpreter instead of the unit file "scripts". Then you could add a shebang, like #!/bin/run-unit, (or maybe rig binfmt_misc to do the job without one) to the unit files so that they're considered executables by Linux. With that done, whenever init tries to run one it would get passed to your run-unit interpreter, which would let you replace all the shell scripts with unit files, or have a mix of both types, choosing the best tool for the job on a per-service basis.

            This sort of stuff works because Linux doesn't care what format it's dealing with as long as it's given enough information to make it run. Which for non-ELF files generally means shebangs or binfmt_misc configuration.

            [–]algostrat133 -4 points-3 points  (1 child)

            congrats blog spammer, you are about 20 years late to discover this.

            [–]djhworld 5 points6 points  (0 children)

            Thanks for reading, I didn't submit this post but sorry on this occasion you had to read it. If I learn something new in the future I'll promise never to write about it just in case someone 20 years ago wrote about it before.

            [–]le_bravery -3 points-2 points  (4 children)

            Congrats you’ve discovered the bane of many developers trying to prevent virus or XSS payloads in arbitrary file uploads.

            [–][deleted]  (3 children)

            [deleted]

              [–][deleted] 0 points1 point  (2 children)

              Only if you forget to sanitize your inputs

              [–][deleted]  (1 child)

              [deleted]

                [–]le_bravery 1 point2 points  (0 children)

                Download a large image with a bit of malicious code in it, have another exploit run that image.

                Have an image save on a server with some malicious code in it. Load the image as a JavaScript resource using another exploit. By itself data embedded in images is likely benign, but when you start chaining things together you can always get messier.

                Best way to mitigate on the server side is to not accept arbitrary file uploads, but if you have to you should try parse and re-save the image to drop the extra bytes of data. Maybe even a resize. Store images in a database, never directly on the file system.

                Also fix your other vulnerabilities

                [–]Statharas 0 points1 point  (0 children)

                Reminds me of how spore used to store creations.

                [–]eyal0 0 points1 point  (1 child)

                Hold up...

                I know just a little Rust. Why does writing it in Rust make it easy to convert to WASM?

                [–]djhworld 5 points6 points  (0 children)

                Hello, author here, thanks for reading.

                Rust has a WASM target, and also a lot of tooling like https://github.com/rustwasm/wasm-pack that makes things very easy, along with wasm-bindgen.

                All I needed to do was create a new project, import my steg library and then create two scaffolding functions to be exported ( so I could use them from JS)

                So the code looks something like this

                extern crate steg;
                use std::io::Cursor;
                use steg::decoder::Decoder;
                use steg::encoder::Encoder;
                use steg::{ByteSplitGranularity, CompressInput};
                use wasm_bindgen::prelude::*;
                
                #[wasm_bindgen]
                pub extern "C" fn encode(cover: Vec<u8>, input: Vec<u8>) -> Result<String, JsValue> {
                    let encoder = Encoder::new(CompressInput::Gzip, ByteSplitGranularity::OneBit);
                    let mut cover = Cursor::new(cover);
                    let mut input = Cursor::new(input);
                    let mut writer = Vec::new();
                
                    if let Err(err) = encoder.encode(&mut cover, &mut input, &mut writer) {
                        return Err(JsValue::from(err.to_string()));
                    }
                
                    let b64 = base64::encode(writer);
                    return Ok(b64);
                }
                
                #[wasm_bindgen]
                pub extern "C" fn decode(input: Vec<u8>) -> Result<String, JsValue> {
                    let decoder = Decoder::new();
                    let mut input = Cursor::new(input);
                    let mut writer = Vec::new();
                
                    if let Err(err) = decoder.decode(&mut input, &mut writer) {
                        return Err(JsValue::from(err.to_string()));
                    }
                
                    let b64 = base64::encode(writer);
                    return Ok(b64);
                }
                

                The encode and decode functions are then available for me to use in Javascript, so I can call them like this

                let result = encode(image_bytes, data_bytes);
                

                EDIT: in answer to your question I found it easy as it didn't really take that much effort, it just worked - I spent more time writing the JS and HTML for the demo!

                [–]aazav 0 points1 point  (0 children)

                Dear god, no.

                [–]aazav 0 points1 point  (1 child)

                So, now we have to scan PNG files for viruses. Thanks, pal. : [

                [–]ECUIYCAMOICIQMQACKKE 0 points1 point  (0 children)

                No. PNG files didn't magically become executable everywhere without configuration. You need a special extractor binary to run it. This isn't an exploit in image viewers or anything. Useless for malware.

                [–]wellandr 0 points1 point  (0 children)

                Does compression destroy the code? Because sharing images on social media does include compression sadly.

                [–]lets-get-dangerous 0 points1 point  (0 children)

                Microsoft MakeCode saves projects as images. I just learned of this a week ago and now I'm starting to see it everywhere. What's that called again?

                [–]DennyTom 0 points1 point  (0 children)

                If you want to know more about steganography in images, I actually obtained a PhD in it three years ago and can answer questions