From 92d26b125902511f8368fe8df84d9acfc3511022 Mon Sep 17 00:00:00 2001 From: coomdev Date: Fri, 24 Dec 2021 06:26:16 +0100 Subject: [PATCH] Update documentation, use shorter embbed identifier --- README.md | 24 ++++++++++++++++++++-- main.user.js | 56 +++++++++++++++++++++++++++------------------------- src/main.ts | 2 +- 3 files changed, 52 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index d5a966f..ca93dec 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,6 @@ PNG Extra Embedder (PEE) Can embed any file in a PNG and upload it through 4chanX. Requires 4chanX and violentmonkey. - - How to Build ============ @@ -16,3 +14,25 @@ How to Install ============== Or use the prebuilt main.user.js at the root of this repo. + + +Format +====== + +This works by appending the file in the last IDAT chunk. +Metadata information is stored in a tEXt chunk, placed near the header so that a parser looking for that embedded information can bail out without having to parse the whole file. + +Metadata in the tEXt has the following meaning: +CUM[null]0 -> The last IDAT chunk is formatted as [filename length[LE 4 bytes], filename, filedata] + +CUM[null]X is reserved for future extensions + +Possible workaround for 4chan jannies would be to assoome IDAT chunks don't go over a certain size, slightly harder workaround would be to check if the deflate stream yields enough pixels to fit the described dimensions of the image. + +Other formats +============= + +The format used by Zip anon won't be supported because: +- it isn't extensible (if change were to the storage format, an extension update wouldn't be backcompatible) +- requires parsing the whole file to know if it has an embedded file +- and includes a private chunk type that is functionally defective (stores the length of the last IDAT chunk as a mean to identify it, instead of assooming it's simply the last one) \ No newline at end of file diff --git a/main.user.js b/main.user.js index efabad0..1dc136c 100644 --- a/main.user.js +++ b/main.user.js @@ -6978,8 +6978,6 @@ } } async dtor() { - this.reader.releaseLock(); - await this.reader.cancel(); } }; var PNGEncoder = class { @@ -6992,7 +6990,7 @@ b.writeInt32BE(chunk[1].length - 4, 0); await this.writer.write(b); await this.writer.write(chunk[1]); - b.writeInt32BE((0, import_crc_32.buf)(chunk[1].slice(4)), 0); + b.writeInt32BE((0, import_crc_32.buf)(chunk[1]), 0); await this.writer.write(b); } async dtor() { @@ -7005,35 +7003,40 @@ var IDAT = import_buffer2.Buffer.from("IDAT"); var IEND = import_buffer2.Buffer.from("IEND"); var tEXt = import_buffer2.Buffer.from("tEXt"); - var CUM0 = import_buffer2.Buffer.from("IMGONNACOOMAAAAAAAAA"); + var CUM0 = import_buffer2.Buffer.from("CUM\x000"); var extractEmbedded = async (reader) => { let magic = false; let sneed = new PNGDecoder(reader); try { + let lastIDAT = null; for await (let [name, chunk, crc, offset] of sneed.chunks()) { switch (name) { - case "CUM0": - magic = true; + case "tEXt": + if (chunk.slice(4, 4 + CUM0.length).equals(CUM0)) + magic = true; break; case "IDAT": if (magic) { - let data = chunk.slice(4); - let fnsize = data.readUInt32LE(0); - let fn = data.slice(4, 4 + fnsize).toString(); - data = data.slice(4 + fnsize); - return { filename: fn, data }; + lastIDAT = chunk; + break; } case "IEND": - await sneed.dtor(); - throw "Didn't find tExt Chunk"; + if (!magic) + throw "Didn't find tExt Chunk"; default: break; } } + if (lastIDAT) { + let data = lastIDAT.slice(4); + let fnsize = data.readUInt32LE(0); + let fn = data.slice(4, 4 + fnsize).toString(); + data = data.slice(4 + fnsize); + return { filename: fn, data }; + } } catch (e) { + console.error(e); } finally { - await sneed.dtor(); - await reader.cancel(); reader.releaseLock(); } }; @@ -7139,17 +7142,20 @@ let decoder = new PNGDecoder(container.stream().getReader()); let magic = false; for await (let [name, chunk, crc, offset] of decoder.chunks()) { + if (magic && name != "IDAT") + break; if (!magic && name == "IDAT") { - encoder.insertchunk(["CUM0", buildChunk("CUM0", import_buffer2.Buffer.from([])), 0, 0]); + await encoder.insertchunk(["tEXt", buildChunk("tEXt", CUM0), 0, 0]); magic = true; } - encoder.insertchunk([name, chunk, crc, offset]); + await encoder.insertchunk([name, chunk, crc, offset]); } let injb = import_buffer2.Buffer.alloc(4 + inj.name.length + inj.size); - injb.writeInt32BE(inj.name.length, 0); + injb.writeInt32LE(inj.name.length, 0); injb.write(inj.name, 4); - import_buffer2.Buffer.from(await inj.arrayBuffer()).copy(injb, 8); - encoder.insertchunk(["IDAT", buildChunk("IDAT", injb), 0, 0]); + import_buffer2.Buffer.from(await inj.arrayBuffer()).copy(injb, 4 + inj.name.length); + await encoder.insertchunk(["IDAT", buildChunk("IDAT", injb), 0, 0]); + await encoder.insertchunk(["IEND", buildChunk("IEND", import_buffer2.Buffer.from([])), 0, 0]); return { file: new Blob([extract()]), name: container.name }; }; var startup = async () => { @@ -7198,17 +7204,13 @@ if (container.files?.length && injection.files?.length) { let res = await buildInjection(container.files[0], injection.files[0]); let result = document.getElementById("result"); + let extracted = document.getElementById("extracted"); result.src = URL.createObjectURL(res.file); + let embedded = await extractEmbedded(res.file.stream().getReader()); + extracted.src = URL.createObjectURL(new Blob([embedded?.data])); } }; - console.log("aaaa"); }; - function downloaddd() { - let result = document.getElementById("result"); - let dlb = document.getElementById("dlb"); - dlb.href = result.src; - } - window["downloaddd"] = downloaddd; })(); /*! * The buffer module from node.js, for the browser. diff --git a/src/main.ts b/src/main.ts index 3b085f8..fcf74bc 100644 --- a/src/main.ts +++ b/src/main.ts @@ -7,7 +7,7 @@ import { concatAB, PNGDecoder, PNGEncoder } from "./png"; const IDAT = Buffer.from("IDAT"); const IEND = Buffer.from("IEND"); const tEXt = Buffer.from("tEXt"); -const CUM0 = Buffer.from("CUM0\0IMGONNACOOMAAAAAAAAA"); +const CUM0 = Buffer.from("CUM\0" + "0"); let extractEmbedded = async (reader: ReadableStreamDefaultReader) => { let magic = false;