heap-trm / ctf /vuln_heap.c
amarck's picture
Add heaptrm package: v2 harness, CLI, pwntools integration, CVE tests
22374d1
/*
* vuln_heap.c - Realistic CTF-style heap challenge
*
* Menu-driven note manager with:
* 1) Allocate note (up to 16 notes, size <= 0x80)
* 2) Edit note (off-by-one null byte overflow)
* 3) Show note
* 4) Delete note (UAF: doesn't clear pointer)
* 5) Exit
*
* Vulnerabilities:
* - Off-by-one null byte in edit (can corrupt next chunk's prev_inuse)
* - Use-after-free in show/edit (pointer not zeroed on delete)
*
* Reads commands from stdin as integers, one per line.
* Format: <menu_choice> [args...]
*
* This is driven by a script that sends commands via stdin.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define MAX_NOTES 16
#define MAX_SIZE 0x80
struct note {
char *data;
size_t size;
int in_use;
};
struct note notes[MAX_NOTES];
void alloc_note() {
int idx, size;
if (scanf("%d %d", &idx, &size) != 2) return;
if (idx < 0 || idx >= MAX_NOTES || size <= 0 || size > MAX_SIZE) return;
if (notes[idx].data != NULL) return; /* slot occupied */
notes[idx].data = malloc(size);
if (!notes[idx].data) return;
memset(notes[idx].data, 0, size);
notes[idx].size = size;
notes[idx].in_use = 1;
}
void edit_note() {
int idx;
if (scanf("%d", &idx) != 1) return;
if (idx < 0 || idx >= MAX_NOTES || notes[idx].data == NULL) return;
/* Read exactly size bytes + OFF-BY-ONE null byte overflow */
char buf[MAX_SIZE + 1];
size_t sz = notes[idx].size;
if (sz > MAX_SIZE) sz = MAX_SIZE;
/* Read hex-encoded data from stdin */
char hex[MAX_SIZE * 2 + 4];
if (scanf("%s", hex) != 1) return;
size_t len = strlen(hex) / 2;
if (len > sz) len = sz;
for (size_t i = 0; i < len; i++) {
unsigned int byte;
sscanf(hex + i * 2, "%2x", &byte);
buf[i] = (char)byte;
}
/* BUG: off-by-one null byte write */
memcpy(notes[idx].data, buf, len);
notes[idx].data[len] = '\0'; /* writes one byte past if len == size */
}
void show_note() {
int idx;
if (scanf("%d", &idx) != 1) return;
if (idx < 0 || idx >= MAX_NOTES || notes[idx].data == NULL) return;
/* UAF: can show freed data (pointer not cleared on delete) */
write(STDOUT_FILENO, notes[idx].data, notes[idx].size);
write(STDOUT_FILENO, "\n", 1);
}
void delete_note() {
int idx;
if (scanf("%d", &idx) != 1) return;
if (idx < 0 || idx >= MAX_NOTES || notes[idx].data == NULL) return;
free(notes[idx].data);
/* BUG: UAF - don't clear pointer or in_use flag */
notes[idx].in_use = 0;
/* notes[idx].data = NULL; <-- should do this but don't */
}
int main() {
setbuf(stdin, NULL);
setbuf(stdout, NULL);
setbuf(stderr, NULL);
int choice;
while (1) {
if (scanf("%d", &choice) != 1) break;
switch (choice) {
case 1: alloc_note(); break;
case 2: edit_note(); break;
case 3: show_note(); break;
case 4: delete_note(); break;
case 5: return 0;
default: break;
}
}
return 0;
}