Can embed any file in a PNG/WebM/GIF/JPEG and upload it to a third-party host through 4chan
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

139 lines
4.1 KiB

import { Buffer } from "buffer";
import * as ebml from "ts-ebml";
// unused, but will in case 4chan does file sig checks
//const password = Buffer.from("NOA");
const xor = (a: Buffer, p: Buffer) => {
let n = 0;
for (let i = 0; i < a.byteLength; ++i) {
a[i] ^= p[n];
n++;
n %= p.byteLength;
}
};
// just some debugging
const printChunks = (chunks: ebml.EBMLElementDetail[], ptr = 0, depth = 0): void => {
if (ptr >= chunks.length)
return;
const k = chunks[ptr];
const closing = ('isEnd' in k && k.isEnd ? 1 : 0);
console.log('\t'.repeat(depth - closing) + (closing ? '/' : '') + k.name);
switch (k.type) {
case "m":
if (k.isEnd) {
return printChunks(chunks, ptr + 1, depth - 1);
} else {
return printChunks(chunks, ptr + 1, depth + 1);
}
default:
return printChunks(chunks, ptr + 1, depth);
}
};
const findEnclosingTag = (ch: ebml.EBMLElementDetail[], name: string): [number, number] | undefined => {
const first = ch.findIndex(e => e.type == 'm' && e.name == name);
if (first < 0)
return;
const second = ch.slice(first).findIndex(e => e.type == 'm' && e.name == name);
if (second < 0)
return;
return [
first, first + second
];
};
const embed = (webm: Buffer, data: Buffer) => {
const dec = new ebml.Decoder();
const chunks = dec.decode(webm);
const enc = new ebml.Encoder();
let embed = chunks.findIndex(e => e.name == "Tracks" && e.type == "m" && e.isEnd);
const findOrInsert = (n: string) => {
let tags = findEnclosingTag(chunks, n);
const stack = [];
if (!tags) {
stack.push({
type: "m",
isEnd: false,
name: n,
data: Buffer.from('')
});
stack.push({
type: "m",
isEnd: true,
name: n,
data: Buffer.from('')
});
chunks.splice(embed + 1, 0, ...stack as any);
tags = findEnclosingTag(chunks, n);
}
embed = tags![1];
};
findOrInsert('Tags');
findOrInsert('Tag');
findOrInsert('Targets');
embed++;
chunks.splice(embed + 1, 0, ...[
{
type: "m",
isEnd: false,
name: 'SimpleTag',
data: Buffer.from('')
},
{
type: "8",
isEnd: false,
name: 'TagName',
data: Buffer.from('COOM')
},
{
type: "8",
isEnd: false,
name: 'TagBinary',
data
},
{
type: "m",
isEnd: true,
name: 'SimpleTag',
data: Buffer.from('')
}
] as any);
return Buffer.from(enc.encode(chunks.filter(e => e.name != "unknown")));
};
export const extract = (webm: Buffer) => {
const dec = new ebml.Decoder();
const chunks = dec.decode(webm);
const embed = chunks.findIndex(e => e.name == "TagName" && e.type == '8' && e.value == "COOM");
const cl = chunks.find(e => e.name == "Cluster");
if (cl && embed == -1)
return;
if (embed == -1)
return;
const chk = chunks[embed + 1];
if (chk.type == "b" && chk.name == "TagBinary")
return { filename: 'string', data: chk.data };
};
export const inject = async (container: File, inj: File): Promise<Buffer> =>
embed(Buffer.from(await container.arrayBuffer()), Buffer.from(await inj.arrayBuffer()));
export const has_embed = (webm: Buffer) => {
const dec = new ebml.Decoder();
const chunks = dec.decode(webm);
const embed = chunks.findIndex(e => e.name == "TagName" && e.type == '8' && e.value == "COOM");
const cl = chunks.find(e => e.name == "Cluster");
if (cl && embed == -1)
return false; // Tags appear before Cluster, so if we have a Cluster and no coomtag, then it's a definite no
if (embed == -1) // Found no coomtag, but no cluster, so it might be further
return;
return true;
};