Skip to content

Instantly share code, notes, and snippets.

@richinseattle
Created September 9, 2025 19:34
Show Gist options
  • Save richinseattle/c4c725da66aa05831e5c26b122f887d8 to your computer and use it in GitHub Desktop.
Save richinseattle/c4c725da66aa05831e5c26b122f887d8 to your computer and use it in GitHub Desktop.
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <sys/queue.h>
#define __USE_GNU // hsearch_r
#include <search.h>
#include <stdarg.h>
#include <libgen.h>
#include <assert.h>
/*
T A L O S V U L N D E V
░▀█▀░█▀█░█░░░█▀█░█▀▀░░░█░█░█░█░█░░░█▀█░█▀▄░█▀▀░█░█
░░█░░█▀█░█░░░█░█░▀▀█░░░▀▄▀░█░█░█░░░█░█░█░█░█▀▀░▀▄▀
░░▀░░▀░▀░▀▀▀░▀▀▀░▀▀▀░░░░▀░░▀▀▀░▀▀▀░▀░▀░▀▀░░▀▀▀░░▀░
I N T E R V I E W C H A L L E N G E 2 0 1 6
Congratulations, you got the code, now time to find some bugs!
There are three vulnerabilities in this code, find them and document them.
There is one addtional vulnerability that has been edited out of the code. Find it in the binary!
original binary built with the default options of gcc on Ubuntu 15.10
gcc version 5.2.1 20151010 (Ubuntu 5.2.1-22ubuntu2)
*/
#define _error(msg) do { perror(msg); exit(EXIT_FAILURE); } while (0);
#define http_error(stream, e) \
do { \
fprintf(stream, \
"HTTP/1.1 %s\r\n" \
"Server: vhttpd\r\n" \
"Content-Type: text/plain\r\n" \
"Content-Length: %d\r\n" \
"Connection: close\r\n" \
"\r\n" \
"%s\r\n\r\n", \
e, (int)strlen(e) + 4, e); \
} while(0);
#define _clienterror(code, msg) do { http_error(global.stream, ""#code " " msg); global.cleanup_func(global.stream); exit(EXIT_FAILURE); } while (0);
#define _clientwarning(code, msg) do { http_error(global.stream, ""#code " " msg); global.cleanup_func(global.stream); } while (0);
#define BUFSIZE 0x10000
#define XOR_KEY (unsigned char *)(banner + 51 + 151 + 69)
#define XOR_KEY_LEN 8
//#define PHF_HASH "\x0F\x37\x29\xCD\xFD\x28\xCD\x3A\x0F\x28\x2A\xC6\xEB\x17\xC5\x3C\xCD\x39\x35\x21\x30\xE3\x14\x39\x0F\x36\x2B\xCE\xFB\x29\xC5\x24\x09\xE6\xF2\xD6\x40\x32\xD8\x28\xC8\xFD\x35\xD2\x37\xEC\xDA\x41\xD4"
#define PHF_HASH_LEN 49
#define PHF_HASH "\x0F\x37\x25\xCD\xFD\x24\xCD\x3A\x0F\x28\x2E\xC6\xEB\x13\xC5\x3C\xCD\x39\x39\x21\x30\xE7\x14\x39\x0F\x36\x2F\xCE\xFB\x25\xC5\x24\x09\xE6\xF6\xD6\x40\x36\xD8\x28\xC8\xFD\x39\xD2\x37\xF0\xDA\x41\xD4"
/** constants */
const char *banner = // XOR key = offset 271 = row 3, beginning of V
" T A L O S V U L N D E V \n" // len = 51
"░▀█▀░█▀█░█░░░█▀█░█▀▀░░░█░█░█░█░█░░░█▀█░█▀▄░█▀▀░█░█\n" // len = 151
"░░█░░█▀█░█░░░█░█░▀▀█░░░▀▄▀░█░█░█░░░█░█░█░█░█▀▀░▀▄▀\n"
"░░▀░░▀░▀░▀▀▀░▀▀▀░▀▀▀░░░░▀░░▀▀▀░▀▀▀░▀░▀░▀▀░░▀▀▀░░▀░\n"
" I N T E R V I E W C H A L L E N G E 2 0 1 6 \n";
const char *instructions =
"Objectives:\n"
"\n"
"- Find the request that allows you to retrieve the source code for this binary\n"
" (The embedded archive is encrypted with the secret path)\n"
"- Identify three vulnerabilities in the source code\n"
"- Identify one additional vulnerability that is only in the binary\n"
"- Write trigger inputs for all vulnerabilites\n"
"- Exploit the memory corruption that exists only in the binary\n"
" - Choose the level of mitigations that shows your ability\n"
"\n"
"Please document your process through the stages of the challenge\n"
"and deliver a report with your findings and notes to [email protected]\n"
"\n"
"Good luck!\n";
/** static globals */
static struct {
struct hsearch_data headerTable;
char default_message[0xb8];
void (*cleanup_func)(void*);
FILE *stream;
char default_mimetype[64];
char request[BUFSIZE];
} global;
/** externals */
extern void *_binary_vhttpd_src_zip_start;
extern void *_binary_vhttpd_src_zip_end;
extern void *_binary_vhttpd_src_zip_size;
extern char **environ;
#define CGI_MAX_ARGS 32
char **cgi_args;
#define HEX_TO_DECIMAL(char1, char2) \
(((char1 >= 'A') ? (((char1 & 0xdf) - 'A') + 10) : (char1 - '0')) * 16) + \
(((char2 >= 'A') ? (((char2 & 0xdf) - 'A') + 10) : (char2 - '0')))
// caller must free
unsigned char *xor_multi(unsigned char *buf, int buf_len, unsigned char *key, int key_len)
{
int i;
unsigned char *hash = (unsigned char *)malloc(buf_len);
if(!hash)
_error("malloc");
for (i = 0; i < buf_len; i++)
hash[i] = (buf[i] ^ key[i % key_len]) + 0x42;
return hash;
}
// algorithm borrowed from boa webserver
int unescape_uri(char *uri, char **query_string)
{
char c, d;
char *uri_old;
uri_old = uri;
while ((c = *uri_old))
{
if (c == '%')
{
uri_old++;
if ((c = *uri_old++) && (d = *uri_old++))
*uri++ = HEX_TO_DECIMAL(c, d);
else
return 0; /* NULL in chars to be decoded */
}
else if (c == '?')
{ /* query string */
if (query_string)
*query_string = ++uri_old;
/* stop here */
*uri = '\0';
return(1);
break;
}
else if (c == '#')
{ /* fragment */
/* legal part of URL, but we do *not* care.
* However, we still have to look for the query string */
if (query_string)
{
++uri_old;
while((c = *uri_old))
{
if (c == '?')
{
*query_string = ++uri_old;
break;
}
++uri_old;
}
}
break;
}
else
{
*uri++ = c;
uri_old++;
}
}
*uri = '\0';
return 1;
}
int xdup(int fd, int target)
{
int result;
if((result = dup2(fd, target)) < 0)
_error("dup2");
return result;
}
// opaque struct behind hsearch
typedef struct _ENTRY
{
unsigned int used;
ENTRY entry;
}
_ENTRY;
void cgi_set_environment()
{
struct hsearch_data *e_table;
_ENTRY *e;
ENTRY *p;
int i;
cgi_args = calloc(CGI_MAX_ARGS, sizeof(char*const));
e_table = &global.headerTable;
for(i = 0; i < global.headerTable.size; i++)
{
if(e_table->table[i].used != 0)
{
p = &e_table->table[i].entry;
setenv(p->key, p->data, 1);
}
}
}
int cgi_exec(int io, char* path, ...)
{
int i;
int st, pid;
char** args;
va_list l;
pid = fork();
if (pid == -1) {
_error("fork");
}
else if (pid > 0)
{
if (pid != waitpid(pid, &st, 0))
_error("waitpid");
return st;
}
assert(pid == 0);
// dup some fd back to us
xdup(io, STDIN_FILENO);
xdup(io, STDOUT_FILENO);
xdup(io, STDERR_FILENO);
va_start(l, path);
cgi_args[0] = basename(path);
for(i = 1; (cgi_args[i] = va_arg(l, char*const)); i++);
va_end(l);
// now go and execv
if (execv(path, cgi_args) < 0)
_error("execve");
return 0;
}
void do_cgi_request(FILE *stream, char *uri)
{
int path_len;
char path[FILENAME_MAX];
unsigned char *uri_hash;
int uri_len;
const char *mimetype = "application/octet-stream";
char *query_string;
struct stat sbuf;
int st;
// download source
if((uri_len = strlen(uri)) == PHF_HASH_LEN)
{
uri_hash = xor_multi((unsigned char *)uri, uri_len, XOR_KEY, XOR_KEY_LEN);
if(!memcmp(uri_hash, PHF_HASH, PHF_HASH_LEN))
{
fprintf(stream, "HTTP/1.1 200 OK\r\n");
fprintf(stream, "Server: vhttpd\r\n");
fprintf(stream, "Content-Type: %s\r\n", mimetype);
fprintf(stream, "Content-Length: %d\r\n", (int)(long)&_binary_vhttpd_src_zip_size);
fprintf(stream, "Connection: close\r\n");
fprintf(stream, "Accept-Ranges: bytes\r\n");
//fprintf(stream, "Content-Name: vhttpd-src.zip\r\n");
fprintf(stream, "\r\n");
fflush(stream);
fwrite(&_binary_vhttpd_src_zip_start, (int)(long)&_binary_vhttpd_src_zip_size, 1, stream);
fflush(stream);
free(uri_hash);
return;
}
free(uri_hash);
}
// separate path from args
if ((query_string = strchr(uri, '?')))
{
*query_string = 0;
query_string++;
unescape_uri(uri, NULL);
}
else
query_string = (char *)"";
// make path relative
path_len = snprintf(path, sizeof(path), ".%s", uri);
if (path_len < 0)
_clienterror(404, "Not Found");
// stat cgi binary
if (stat(path, &sbuf) == -1)
_clienterror(404, "Not Found");
// check perms
if(!(S_ISREG(sbuf.st_mode) && (S_IXUSR & sbuf.st_mode)))
_clienterror(403, "Forbidden");
setenv("QUERY_STRING", query_string, 1);
fprintf(stream, "HTTP/1.1 200 OK\r\n");
fprintf(stream, "Server: vhttpd\r\n");
fprintf(stream, "Content-Type: %s\r\n", mimetype);
fprintf(stream, "Connection: close\r\n");
fprintf(stream, "Accept-Ranges: bytes\r\n");
fprintf(stream, "\r\n");
fflush(stream);
// exec cgi
cgi_set_environment();
if(cgi_exec(fileno(stream), getenv("SHELL"), "-c", path, NULL) < 0)
_clientwarning(500, "Internal Server Error");
return;
}
char *get_http_header(char *name)
{
ENTRY e = {name, NULL};
ENTRY* ep;
if (!(hsearch_r(e, FIND, &ep, &global.headerTable)))
_error("hsearch_r");
return ep? ep->data : NULL;
}
void add_http_header(char *name, char *value)
{
int res;
ENTRY e, *ep;
e.key = strdup(name);
e.data = strdup(value);
if (!(hsearch_r(e, ENTER, &ep, &global.headerTable)))
_error("hsearch_r");
}
int parse_http_headers(FILE *stream)
{
char *buf = malloc(BUFSIZE);
char *val = NULL;
// parse HTTP headers
while(1)
{
if(!fgets(buf, BUFSIZE, stream))
break;
if(!strcmp(buf, "\r\n") || !strcmp(buf, "\n"))
break;
// chomp off newlines
if((val = strchr(buf, '\r')))
*val = 0;
if((val = strchr(buf, '\n')))
*val = 0;
if((val = strstr(buf, ": ")))
{
*val = 0;
val += 2;
add_http_header(buf, val);
if(!strcmp(buf, "Content-Length")))
buf = malloc(atoi(val));
}
}
free(buf);
return 1;
}
void abort_cleanup(void* data)
{
FILE* stream = data;
fclose(stream);
}
int parse_http_request(int c_fd)
{
int res_fd; // for requested resource
FILE *stream;
char* method; char* version; char* uri;
int uri_len;
int path_len;
char path[FILENAME_MAX];
const char* mimetype = global.default_mimetype;
char* query_string;
struct stat sbuf;
unsigned char *filemap;
// open connection as stream
if ((stream = fdopen(c_fd, "r+")) == NULL)
_error("fdopen");
// initialize some global state
strcpy(global.default_mimetype, "text/plain");
global.cleanup_func = abort_cleanup;
global.stream = stream;
// HTTP request
fgets(global.request, sizeof(global.request), stream);
fprintf(stderr, "[vhttpd] %s", global.request);
// tokenize it
if(!(method = strtok(global.request, " ")))
_clienterror(400, "Bad Request");
if(!(uri = strtok(NULL, " ")))
_clienterror(400, "Bad Request");
if(!(version = strtok(NULL, " ")))
_clienterror(400, "Bad Request");
if (strtok(NULL, " ") != NULL)
_clienterror(400, "Bad Request");
// make sure it's valid
if(!strlen(method) || !strlen(uri) || !strlen(version))
_clienterror(400, "Bad Request");
uri_len = strlen(uri);
// only handle GET or POST
if (strcmp(method, "GET") != 0 && strcmp(method, "POST") != 0)
_clienterror(501, "Not Implemented");
// filter directory traversal
if(strstr(uri, ".."))
{
http_error(stream, "400 Bad Request");
fprintf(stream, "bad kitty!\n");
fclose(stream);
return 400;
}
// initialize hash table for headers
if (!hcreate_r(512, &global.headerTable))
_clienterror(500, "Internal Server Error");
if(!parse_http_headers(stream))
_clienterror(400, "Bad Request");
// parse URI
if (strstr(uri, "/cgi-bin/") == uri) {
do_cgi_request(stream, uri);
} else if (strstr(uri, "/robots.txt") == uri) {
fprintf(stream, "User-agent: *\r\n");
fprintf(stream, "Disallow: /cgi-bin/\r\n");
fprintf(stream, "Disallow: /\r\n");
fflush(stream);
} else {
// make relative path and look for the file
if(uri_len == 1)
path_len = snprintf(path, sizeof(path), "./index.html");
else
path_len = snprintf(path, sizeof(path), ".%s", uri);
path[path_len] = '\x00';
// remove query string from path
if ((query_string = strchr(path, '?')))
*query_string = 0;
// stat file and make sure it's there
if (stat(path, &sbuf) != 0)
_clienterror(404, "Not Found");
// figure out the mimetype
if (!strcmp(uri + uri_len - 5, ".html"))
mimetype = "text/html";
else if (!strcmp(uri + uri_len - 4, ".htm"))
mimetype = "text/html";
else if (!strcmp(uri + uri_len - 4, ".ico"))
mimetype = "image/x-ico";
else if (!strcmp(uri + uri_len - 4, ".bmp"))
mimetype = "image/bmp";
else if (!strcmp(uri + uri_len - 4, ".jpg"))
mimetype = "image/jpg";
else if (!strcmp(uri + uri_len - 4, ".gif"))
mimetype = "image/gif";
else if (!strcmp(uri + uri_len - 4, ".png"))
mimetype = "image/png";
else if (!strcmp(uri + uri_len - 4, ".zip"))
mimetype = "application/zip";
// serve static content
fprintf(stream, "HTTP/1.1 200 OK\r\n");
fprintf(stream, "Server: vhttpd\r\n");
fprintf(stream, "Content-Length: %d\r\n", (int)sbuf.st_size);
fprintf(stream, "Content-Type: %s\r\n", mimetype);
fprintf(stream, "\r\n");
fflush(stream);
// Use mmap to return arbitrary-sized response body
res_fd = open(path, O_RDONLY);
filemap = (unsigned char *)mmap(0, sbuf.st_size, PROT_READ, MAP_PRIVATE, res_fd, 0);
fwrite(filemap, sbuf.st_size, 1, stream);
fflush(stream);
munmap(filemap, sbuf.st_size);
}
fclose(stream);
hdestroy_r(&global.headerTable);
return 0;
}
int main(int argc, char **argv)
{
unsigned short port;
int s_fd, c_fd;
int optval;
struct sockaddr_in s_addr, c_addr;
socklen_t addrlen;
struct stat st;
pid_t pid;
// instructions
fprintf(stderr, "\n%s\n%s\n", banner, instructions);
// check args
if (argc != 2)
{
fprintf(stderr,
"error: Please select a port for the web server\n"
"\n"
"usage: %s <port>\n",
argv[0]);
exit(EXIT_FAILURE);
}
port = atoi(argv[1]);
// make sure cgi-bin exists
if (stat("./cgi-bin", &st) == -1)
mkdir("./cgi-bin", 0755);
symlink("/usr/bin/env", "./cgi-bin/env");
// open socket
if((s_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
_error("socket");
// close sock on exec
if (fcntl(s_fd, F_SETFD, 1) == -1)
_error("fcntl");
// reuse socket
optval = 1;
if((setsockopt(s_fd, SOL_SOCKET, SO_REUSEADDR, (const void *)&optval , sizeof(int))) == -1)
_error("setsockopt");
// bind
memset(&s_addr, 0, sizeof(s_addr));
s_addr.sin_family = AF_INET;
s_addr.sin_addr.s_addr = htonl(INADDR_ANY);
s_addr.sin_port = htons(port);
if (bind(s_fd, (struct sockaddr *) &s_addr, sizeof(s_addr)) < 0)
_error("bind");
if (listen(s_fd, 20) < 0)
_error("listen");
fprintf(stderr, "[vhttpd] listening on %s:%d\n", inet_ntoa(s_addr.sin_addr), port);
// ignore child signals
signal(SIGCHLD, SIG_IGN);
// accept
while (1)
{
// wait for a connection
addrlen = sizeof(c_addr);
c_fd = accept(s_fd, (struct sockaddr *) &c_addr, &addrlen);
if (c_fd < 0)
_error("accept");
// log connection
fprintf(stderr, "\n[vhttpd] Connection accepted from %s:\n", inet_ntoa(c_addr.sin_addr));
// fork to make remote exploits easier :)
pid = fork();
if(pid == -1)
{
close(c_fd);
_error("fork");
}
else if(pid > 0)
{
close(c_fd);
continue;
}
assert(pid == 0);
parse_http_request(c_fd);
close(c_fd);
break;
}
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment