heap-trm / cve_tests /hard_heap.c
amarck's picture
Benchmark: heaptrm 100% vs blind 0% on hard_heap (no UAF read, heap noise)
d812679
/*
* hard_heap.c - A harder heap challenge where blind exploitation fails.
*
* Differences from vuln_heap:
* 1. No UAF read β€” show is disabled after free (returns "deleted")
* 2. Partial overwrite only β€” edit writes at most 8 bytes
* 3. Heap noise β€” random allocations on startup pollute layout
* 4. Size restricted β€” can only alloc 3 fixed sizes (small/medium/large)
* 5. Limited slots β€” only 8 slots, must manage carefully
* 6. Output only prints "OK" or "ERR" β€” no address leaks
*
* The vulnerability: edit doesn't check if chunk is freed (UAF write).
* But without UAF read or address leaks, blind exploitation requires
* guessing the heap layout.
*
* Menu (stdin, one per line):
* 1 <slot> <size_class> β€” alloc (size_class: 1=0x20, 2=0x40, 3=0x80)
* 2 <slot> <hex_8bytes> β€” edit (write 8 bytes, works on freed = UAF)
* 3 <slot> β€” show (only if allocated, prints first 8 bytes hex)
* 4 <slot> β€” delete
* 5 β€” exit
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#define MAX_SLOTS 8
#define SIZES_COUNT 3
static int sizes[SIZES_COUNT] = {0x20, 0x40, 0x80};
struct slot {
char *data;
int size_class;
int active; /* 1 = allocated, 0 = freed/empty */
};
static struct slot slots[MAX_SLOTS];
/* Heap noise: random allocations to make layout unpredictable */
static void heap_noise(void) {
srand(time(NULL) ^ getpid());
int n = 3 + (rand() % 5); /* 3-7 noise allocations */
for (int i = 0; i < n; i++) {
int sz = sizes[rand() % SIZES_COUNT];
char *p = malloc(sz);
memset(p, 'N', sz);
if (rand() % 3 == 0) free(p); /* free some randomly */
}
}
int main(void) {
setbuf(stdin, NULL);
setbuf(stdout, NULL);
heap_noise();
int choice, slot, sc;
char hexbuf[20];
while (scanf("%d", &choice) == 1) {
switch (choice) {
case 1: /* alloc */
if (scanf("%d %d", &slot, &sc) != 2) break;
if (slot < 0 || slot >= MAX_SLOTS) { puts("ERR"); break; }
if (sc < 1 || sc > SIZES_COUNT) { puts("ERR"); break; }
if (slots[slot].data != NULL) { puts("ERR"); break; }
slots[slot].data = malloc(sizes[sc-1]);
if (!slots[slot].data) { puts("ERR"); break; }
memset(slots[slot].data, 0, sizes[sc-1]);
slots[slot].size_class = sc;
slots[slot].active = 1;
puts("OK");
break;
case 2: /* edit β€” VULN: works on freed chunks too (UAF write) */
if (scanf("%d %16s", &slot, hexbuf) != 2) break;
if (slot < 0 || slot >= MAX_SLOTS || !slots[slot].data) { puts("ERR"); break; }
/* Parse hex, write 8 bytes max */
{
unsigned char bytes[8] = {0};
int len = strlen(hexbuf) / 2;
if (len > 8) len = 8;
for (int i = 0; i < len; i++) {
unsigned int b;
sscanf(hexbuf + i*2, "%2x", &b);
bytes[i] = (unsigned char)b;
}
memcpy(slots[slot].data, bytes, len);
}
puts("OK");
break;
case 3: /* show β€” only works if active (no UAF read!) */
if (scanf("%d", &slot) != 1) break;
if (slot < 0 || slot >= MAX_SLOTS || !slots[slot].data) { puts("ERR"); break; }
if (!slots[slot].active) {
puts("DELETED");
break;
}
{
unsigned char *p = (unsigned char *)slots[slot].data;
for (int i = 0; i < 8; i++) printf("%02x", p[i]);
puts("");
}
break;
case 4: /* delete */
if (scanf("%d", &slot) != 1) break;
if (slot < 0 || slot >= MAX_SLOTS || !slots[slot].data) { puts("ERR"); break; }
free(slots[slot].data);
/* BUG: don't null pointer (UAF write still possible) */
slots[slot].active = 0;
puts("OK");
break;
case 5:
goto done;
default:
puts("ERR");
break;
}
}
done:
return 0;
}