Last active
July 15, 2022 22:31
-
-
Save hkva/d3cf8de59dfbd25499d77db0c06507e3 to your computer and use it in GitHub Desktop.
Format string vulnerability finder
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// Format string vulnerability finder | |
// | |
// $ gcc printfcheck.c -Wall -O3 -g -shared -lunwind -lunwind-x86_64 -o printfcheck.so | |
// | |
// $ gcc printfcheck.c -Wall -O3 -g -shared -m32 -lunwind -lunwind-x86 -o printfcheck.so | |
// | |
// $ PRINTFCHECK_TRACE=1 PRINTFCHECK_BACKTRACE=1 LD_PRELOAD=./printfcheck.so ./prog | |
// | |
#define _GNU_SOURCE | |
// Standard headers | |
#include <stdarg.h> | |
#include <stdbool.h> | |
#include <stddef.h> | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <string.h> | |
// Linux headers | |
#include <dlfcn.h> | |
#include <fcntl.h> | |
#include <signal.h> | |
#include <unistd.h> | |
// Libunwind | |
#include <libunwind.h> | |
// Check if a c-string is mutable | |
static bool is_str_mutable(const char* str) { | |
// gray beard unix wizardry | |
int devzero = open("/dev/zero", O_RDONLY); | |
char backup = str[0]; | |
bool wrote = read(devzero, (void*)str, 1) == 1; | |
if (wrote) { | |
((char*)str)[0] = backup; | |
} | |
close(devzero); | |
return wrote; | |
} | |
// Target functions | |
static struct { | |
// stdio.h | |
// https://cplusplus.com/reference/cstdio/ | |
typeof(fprintf)* fprintf; | |
typeof(printf)* printf; | |
typeof(snprintf)* snprintf; | |
typeof(sprintf)* sprintf; | |
typeof(vfprintf)* vfprintf; | |
typeof(vprintf)* vprintf; | |
typeof(vsnprintf)* vsnprintf; | |
typeof(vsprintf)* vsprintf; | |
} o = { 0 }; | |
static void write_stdout(const char* msg) { | |
write(STDOUT_FILENO, msg, strlen(msg)); | |
} | |
static void check_format_arg(const char* funcname, const char* format) { | |
// Load target functions | |
#define LOAD_FUNCTION(name) if (o.name == NULL) { o.name = dlsym(RTLD_NEXT, #name); } | |
LOAD_FUNCTION(fprintf); | |
LOAD_FUNCTION(printf); | |
LOAD_FUNCTION(snprintf); | |
LOAD_FUNCTION(sprintf); | |
LOAD_FUNCTION(vfprintf); | |
LOAD_FUNCTION(vprintf); | |
LOAD_FUNCTION(vsnprintf); | |
LOAD_FUNCTION(vsprintf); | |
#undef LOAD_FUNCTION | |
// Trace function calls | |
if (getenv("PRINTFCHECK_TRACE") != NULL) { | |
// use write() to prevent recursive call | |
write_stdout("[printf-check] "); | |
write_stdout(funcname); | |
write_stdout(" called with format arg \""); | |
write_stdout(format); | |
write_stdout("\"\n"); | |
} | |
// Check for potentially vulnerable argument | |
if (is_str_mutable(format)) { | |
write_stdout("[printf-check] !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n"); | |
write_stdout("[printf-check] Format argument is potentially vulnerable\n"); | |
write_stdout("[printf-check] !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n"); | |
if (getenv("PRINTFCHECK_BACKTRACE") != NULL) { | |
// Print stack trace | |
struct { | |
unw_context_t ctx; | |
unw_cursor_t cur; | |
} unw = { 0 }; | |
unw_getcontext(&unw.ctx); | |
unw_init_local(&unw.cur, &unw.ctx); | |
write_stdout("[printf-check] Backtrace:\n"); | |
// Walk stack | |
while (unw_step(&unw.cur) > 0) { | |
char sym[512]; | |
unw_word_t sym_offset; | |
if (unw_get_proc_name(&unw.cur, sym, sizeof(sym), &sym_offset) == 0) { | |
write_stdout("[printf-check] "); | |
write_stdout(sym); | |
write_stdout("\n"); | |
} | |
} | |
} | |
if (getenv("PRINTFCHECK_BREAK") != NULL) { | |
raise(SIGTRAP); | |
} | |
} | |
} | |
int fprintf(FILE* stream, const char* format, ...) { | |
check_format_arg("fprintf", format); | |
// return original | |
va_list va; | |
va_start(va, format); | |
int r = o.vfprintf(stream, format, va); | |
va_end(va); | |
return r; | |
} | |
int printf(const char* format, ...) { | |
check_format_arg("printf", format); | |
// return original | |
va_list va; | |
va_start(va, format); | |
int r = o.vprintf(format, va); | |
va_end(va); | |
return r; | |
} | |
int snprintf(char* s, size_t n, const char* format, ...) { | |
check_format_arg("snprintf", format); | |
// return original | |
va_list va; | |
va_start(va, format); | |
int r = o.vsnprintf(s, n, format, va); | |
va_end(va); | |
return r; | |
} | |
int sprintf(char* s, const char* format, ...) { | |
check_format_arg("sprintf", format); | |
// return original | |
va_list va; | |
va_start(va, format); | |
int r = o.vsprintf(s, format, va); | |
va_end(va); | |
return r; | |
} | |
int vfprintf(FILE* stream, const char* format, va_list arg) { | |
check_format_arg("vfprintf", format); | |
// return original | |
return o.vfprintf(stream, format, arg); | |
} | |
int vprintf(const char* format, va_list arg) { | |
check_format_arg("vprintf", format); | |
// return original | |
return o.vprintf(format, arg); | |
} | |
int vsnprintf(char* s, size_t n, const char* format, va_list arg) { | |
check_format_arg("vsnprintf", format); | |
// return original | |
return o.vsnprintf(s, n, format, arg); | |
} | |
int vsprintf(char* s, const char* format, va_list arg) { | |
check_format_arg("vsprintf", format); | |
// return original | |
return o.vsprintf(s, format, arg); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment