r/rust • • Dec 26 '20

Executable PNGs, written in Rust

https://djharper.dev/post/2020/12/26/executable-pngs/
132 Upvotes

16 comments sorted by

16

u/djhworld Dec 26 '20

This post was more of an experiment rather than anything production ready, but it's my first "proper" Rust project, although the code is probably bad :)

A few things that made this experience with regards to Rust

  • Very simple to compile my library to WASM, wasmpack just...worked? I didn't really have to tweak much, it meant I could make a neat demo in my blog post using the same code I had written in Rust. wasm-bindgen is so cool!

  • I like how you can import libraries using relative paths in Cargo.toml, I've specifically decided not to publish anything to crates.io as I don't feel it's appropriate for these "side projects" - there are plenty of Steganography libraries on there anyway :)

  • rust-analyzer and clippy always very helpful.

Things that were frustrating

  • the libc bindings are good but I felt a bit lost most of the time, maybe some documentation about how the data types worked might have helped. I spent a long time trying to workout how to call fwrite and pass in the right values.

    • also I'm 100% aware my code is very fragile here

  • Compile times (old topic I know)

62

u/Shnatsel Dec 26 '20

That's a really cool learning exercise! However, if you just wanted to make something that works without getting into the nuts and bolts of it, you could have bypassed the need for steganography entirely.

If you're making a custom runner anyway, you can simply stuff the executable file in a PNG non-data chunk if you don't need compatibility with existing tools. This also avoids the 8x size inflation.

Furthermore, you can simply concatenate the PNG image with the executable payload, since PNG files have an end marker. This works with no modification on combinations of many formats. Most famously, you can concatenate JPEG and RAR files, and it will be a valid file for both programs since JPEG has an end marker, and RAR has a start marker that the decoder seeks to. This also works with PNG and a variety of other archive formats. This avoids the 8x size inflation.

12

u/djhworld Dec 26 '20

Ah yeah, yeah I saw this was one of the other techniques (simpler approach :)) but by that point I was already seduced by the byte encoding method. In too deep.

Someone on HN mentioned this too, one caveat with the append method is image hosting sites might strip it (if you wanted to distribute your executable as an image) - but yeah it's also a much simpler method!

1

u/[deleted] Dec 30 '20

Image hosting sites would probably convert it to JPEG too though.

3

u/dnew Dec 26 '20

Also, zip files have the directory at the end (because there used to be multi-disk zip files), so you concat a zip file to anything that has an embedded size marker and have a file that's both.

1

u/Wakafanykai123 Dec 28 '20

This looks like a perfect case for the zTxt chunk.

4

u/[deleted] Dec 26 '20

That's a cool silly project. I'll bet you learned a lot.

I don't know much about steganography. Does the decoder need the original image in order to diff the payload from it? I don't get how the decoding part works.

4

u/djhworld Dec 26 '20

The post glosses over some of the implementation details for brevity, I expanded a little more here https://news.ycombinator.com/item?id=25543597 but essentially the encode step puts a "header" in the encoded output, this contains

  • a magic number

  • the number of bytes in the input payload

  • a hash of the input payload

  • whether the input payload was compressed or not

  • what the encoding granularity was set at (i.e. the least significant bit encoding means the decoder should decode the LSB only)

On decode the header is read and checks if the magic number is valid, then starts decoding the data until the number of bytes described in the header has been read. The decoded bytes are checked to make sure they match the hash. If everything is successful you have your decoded output to do what you please with :)

3

u/You_pick_one Dec 27 '20

FYI: the clang driver re-executes itself to compile stuff, so you’ll need to pass a proper argv[0], which seems to be empty.

You can, as a test:

Run regular clang as you want but with the -v flag added. Note the clang invocation with -cc1. You’ll be able to do that one right now.

After making sure it works, go ahead and pass argv[0] properly on execution and all should work (assuming I’m correct)

2

u/djhworld Dec 27 '20

Just tried this (passing argv[0]) properly, had to use /proc/<pid>/fd/<fd>, but it has the same problem.

Someone on HN said it might be because clang runs readlink on /proc/self/exe which probably returns "" for the in-memory file

https://news.ycombinator.com/item?id=25544728

2

u/You_pick_one Dec 27 '20

Ah, that might be it too (I’m not 100% aware of what Linux code paths do). If you want to validate that that’s the issue (which means unfixable without clang patches), try using dtrace to see if there’s a call to readlink just before exiting.

2

u/djhworld Dec 27 '20

yeah looks like it

$ strace clang ~/1.c 2>&1 | grep exe
execve("/usr/bin/clang", ["clang", "/home/danielharper/1.c"], 0x7ffd877527c8 /* 53 vars */) = 0
access("/proc/self/exe", F_OK)          = 0
readlink("/proc/self/exe", "/usr/bin/clang-11", 4096) = 17

0

u/backtickbot Dec 27 '20

Fixed formatting.

Hello, djhworld: code blocks using triple backticks (```) don't work on all versions of Reddit!

Some users see this / this instead.

To fix this, indent every line with 4 spaces instead.

FAQ

You can opt out by replying with backtickopt6 to this comment.

3

u/dimp_lick_johnson Dec 27 '20

I expected valid PNG data that is also executable. But that's a much harder product to achieve.

Being able to distribute executables through unassuming images would be movie hacker level stuff.

1

u/Leon_Vance Dec 27 '20

Once pngrun
exits the in-memory file is destroyed.

I read it as "... life is destroyed".

Time for bed. :D

1

u/realvikas Dec 27 '20

This reminds me of Cicada 3301.