Exploiting a buffer overflow to manipulate control flow
The objective of this mission is to demonstrate arbitrary code execution through a control-flow attack, despite CHERI protections. You will attack three different versions of the program:
-
A baseline RISC-V compilation, to establish that the vulnerability is exploitable without any CHERI protections.
-
A baseline CHERI-RISC-V compilation, offering strong spacial safety between heap allocations, including accounting for imprecision in the bounds of large capabilities.
-
A weakened CHERI-RISC-V compilation, reflecting what would occur if a memory allocator failed to pad allocations to account for capability bounds imprecision.
The success condition for an exploit, given attacker-provided input overflowing
a buffer, is to modify control flow in the program such that the success
function is executed.
- Compile
buffer-overflow.c
andbtpalloc.c
together with a RISC-V target and exploit the binary to execute thesuccess
function.
buffer-overflow.c
/*
* SPDX-License-Identifier: BSD-2-Clause-DARPA-SSITH-ECATS-HR0011-18-C-0016
* Copyright (c) 2020 Jessica Clarke
*/
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include "btpalloc.h"
void
success(void)
{
puts("Exploit successful!");
}
void
failure(void)
{
puts("Exploit unsuccessful!");
}
static uint16_t
ipv4_checksum(uint16_t *buf, size_t words)
{
uint16_t *p;
uint_fast32_t sum;
sum = 0;
for (p = buf; words > 0; --words, ++p) {
sum += *p;
if (sum > 0xffff)
sum -= 0xffff;
}
return (~sum & 0xffff);
}
#include "main-asserts.inc"
int
main(void)
{
int ch;
char *buf, *p;
uint16_t sum;
void (**fptr)(void);
buf = btpmalloc(25000);
fptr = btpmalloc(sizeof(*fptr));
main_asserts(buf, fptr);
*fptr = &failure;
p = buf;
while ((ch = getchar()) != EOF)
*p++ = (char)ch;
if ((uintptr_t)p & 1)
*p++ = '\0';
sum = ipv4_checksum((uint16_t *)buf, (p - buf) / 2);
printf("Checksum: 0x%04x\n", sum);
btpfree(buf);
(**fptr)();
btpfree(fptr);
return (0);
}
- Recompile with a CHERI-RISC-V target, attempt to exploit the binary and, if it cannot be exploited, explain why.
- Recompile with a CHERI-RISC-V target but this time adding
-DCHERI_NO_ALIGN_PAD
, attempt to exploit the binary and, if it cannot be exploited, explain why.
btpalloc.c
/*
* SPDX-License-Identifier: BSD-2-Clause-DARPA-SSITH-ECATS-HR0011-18-C-0016
* Copyright (c) 2020 Jessica Clarke
*/
#include "btpalloc.h"
#include <assert.h>
#include <stddef.h>
#include <sys/mman.h>
#ifdef __CHERI_PURE_CAPABILITY__
#include <cheriintrin.h>
#endif
static void *btpmem;
static size_t btpsize;
static void
btpinit(void)
{
btpsize = 0x100000;
btpmem = mmap(NULL, btpsize, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANON, -1, 0);
assert(btpmem != MAP_FAILED);
}
void *
btpmalloc(size_t size)
{
void *alloc;
size_t allocsize;
if (btpmem == NULL)
btpinit();
alloc = btpmem;
/* RISC-V ABIs require 16-byte alignment */
allocsize = __builtin_align_up(size, 16);
#if defined(__CHERI_PURE_CAPABILITY__) && !defined(CHERI_NO_ALIGN_PAD)
allocsize = cheri_representable_length(allocsize);
alloc = __builtin_align_up(alloc,
~cheri_representable_alignment_mask(allocsize) + 1);
allocsize += (char *)alloc - (char *)btpmem;
#endif
if (allocsize > btpsize)
return (NULL);
btpmem = (char *)btpmem + allocsize;
btpsize -= allocsize;
#ifdef __CHERI_PURE_CAPABILITY__
alloc = cheri_bounds_set(alloc, size);
#endif
return (alloc);
}
void
btpfree(void *ptr)
{
(void)ptr;
}
Support code
btpalloc.h
/*
* SPDX-License-Identifier: BSD-2-Clause-DARPA-SSITH-ECATS-HR0011-18-C-0016
* Copyright (c) 2020 Jessica Clarke
*/
#include <stddef.h>
void *btpmalloc(size_t size);
void btpfree(void *ptr);
main-asserts.inc
/*
* SPDX-License-Identifier: BSD-2-Clause-DARPA-SSITH-ECATS-HR0011-18-C-0016
* Copyright (c) 2020 Jessica Clarke
*/
#include <assert.h>
#include <stdint.h>
#ifdef __CHERI_PURE_CAPABILITY__
#include <cheriintrin.h>
#endif
static void
main_asserts(void *buf, void *fptr)
{
uintptr_t ubuf = (uintptr_t)buf;
uintptr_t ufptr = (uintptr_t)fptr;
#ifdef __CHERI_PURE_CAPABILITY__
ptraddr_t ubuf_top;
#endif
#ifdef __CHERI_PURE_CAPABILITY__
ubuf_top = cheri_base_get(ubuf) + cheri_length_get(ubuf);
#endif
#if defined(__CHERI_PURE_CAPABILITY__) && !defined(CHERI_NO_ALIGN_PAD)
/*
* For the normal pure-capability case, `buf`'s allocation should be
* adequately padded to ensure precise capability bounds and `fptr`
* should be adjacent.
*/
assert(ubuf_top == ufptr);
#else
/*
* Otherwise `fptr` should be 8 bytes (not 0 due to malloc's alignment
* requirements) after the end of `buf`.
*/
assert(ubuf + 25008 == ufptr);
#ifdef __CHERI_PURE_CAPABILITY__
/*
* For pure-capability code this should result in the bounds of the
* large `buf` allocation including all of `fptr`.
*/
assert(ubuf_top >= ufptr + sizeof(void *));
#endif
#endif
}