Two challenges below contain real C/C++ bug classes from our Testing Handbook checklist.
Identify the vulnerabilities and explain the issues. Stuck? The handbook chapter has hints.
Use the form below to submit. The first 10 correct solvers win Trail of Bits swag.
Both challenges contain real bug classes from the C/C++ Testing Handbook checklist. If you get stuck, check out the handbook chapter for hints.
In this simple ping program, there are two libc gotchas that make the program trivially exploitable. Can you find and explain the issues? Both bugs are covered in the Linux usermode section of the handbook.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#define ALLOWED_IP "127.3.3.1"
int main() {
char ip_addr[128];
struct in_addr to_ping_host, trusted_host;
// get address
if (!fgets(ip_addr, sizeof(ip_addr), stdin))
return 1;
ip_addr[strcspn(ip_addr, "\n")] = 0;
// verify address
if (!inet_aton(ip_addr, &to_ping_host))
return 1;
char *ip_addr_resolved = inet_ntoa(to_ping_host);
// prevent SSRF
if ((ntohl(to_ping_host.s_addr) >> 24) == 127)
return 1;
// only allowed
if (!inet_aton(ALLOWED_IP, &trusted_host))
return 1;
char *trusted_resolved = inet_ntoa(trusted_host);
if (strcmp(ip_addr_resolved, trusted_resolved) != 0)
return 1;
// ping
char cmd[256];
snprintf(cmd, sizeof(cmd), "ping '%s'", ip_addr);
system(cmd);
return 0;
}This Windows Driver Framework (WDF) driver request handler queries product version values from the registry. There are several bugs here, including an easy-to-exploit denial-of-service (DoS), but one of them leads to kernel code execution by messing with the registry values. Can you figure out the bug and how to exploit it?
NTSTATUS
InitServiceCallback(
_In_ WDFREQUEST Request
)
{
NTSTATUS status;
PWCHAR regPath = NULL;
size_t bufferLength = 0;
// fetch the product registry path from the request
status = WdfRequestRetrieveInputBuffer(
Request, 4, ®Path, &bufferLength);
if (!NT_SUCCESS(status))
{
TraceEvents(
TRACE_LEVEL_ERROR,
TRACE_QUEUE,
"%!FUNC! Failed to retrieve input buffer."
" Status: %d", (int)status
);
return status;
}
/* check that the buffer size is a null-terminated
Unicode (UTF-16) string of a sensible size */
if (bufferLength < 4 ||
bufferLength > 512 ||
(bufferLength % 2) != 0 ||
regPath[(bufferLength / 2) - 1] != L'\0')
{
TraceEvents(
TRACE_LEVEL_ERROR,
TRACE_QUEUE,
"%!FUNC! Buffer length %d was incorrect.",
(int)bufferLength
);
return STATUS_INVALID_PARAMETER;
}
ProductVersionInfo version = { 0 };
HandlerCallback handlerCallback = NewCallback;
int readValue = 0;
// read the major version from the registry
RTL_QUERY_REGISTRY_TABLE regQueryTable[2];
RtlZeroMemory(
regQueryTable,
sizeof(RTL_QUERY_REGISTRY_TABLE) * 2);
regQueryTable[0].Name = L"MajorVersion";
regQueryTable[0].EntryContext = &readValue;
regQueryTable[0].Flags = RTL_QUERY_REGISTRY_DIRECT;
regQueryTable[0].QueryRoutine = NULL;
status = RtlQueryRegistryValues(
RTL_REGISTRY_ABSOLUTE,
regPath,
regQueryTable,
NULL,
NULL
);
if (!NT_SUCCESS(status))
{
TraceEvents(
TRACE_LEVEL_ERROR,
TRACE_QUEUE,
"%!FUNC! Failed to query registry."
" Status: %d", (int)status
);
return status;
}
TraceEvents(
TRACE_LEVEL_INFORMATION,
TRACE_QUEUE,
"%!FUNC! Major version is %d",
(int)readValue
);
version.Major = readValue;
if (version.Major < 3)
{
// versions prior to 3.0 need an additional check
RtlZeroMemory(
regQueryTable,
sizeof(RTL_QUERY_REGISTRY_TABLE) * 2);
regQueryTable[0].Name = L"MinorVersion";
regQueryTable[0].EntryContext = &readValue;
regQueryTable[0].Flags =
RTL_QUERY_REGISTRY_DIRECT;
regQueryTable[0].QueryRoutine = NULL;
status = RtlQueryRegistryValues(
RTL_REGISTRY_ABSOLUTE,
regPath,
regQueryTable,
NULL,
NULL
);
if (!NT_SUCCESS(status))
{
TraceEvents(
TRACE_LEVEL_ERROR,
TRACE_QUEUE,
"%!FUNC! Failed to query registry."
" Status: %d",
(int)status
);
return status;
}
TraceEvents(
TRACE_LEVEL_INFORMATION,
TRACE_QUEUE,
"%!FUNC! Minor version is %d",
(int)readValue
);
version.Minor = readValue;
if (!DoesVersionSupportNewCallback(version))
{
handlerCallback = OldCallback;
}
}
SetGlobalHandlerCallback(handlerCallback);
}The first 10 people to correctly solve a challenge win Trail of Bits swag. All participants who submit a valid answer receive digital badges.
Physical swag is available for winners in the United States and most European countries. Due to export regulations and shipping logistics, we cannot ship to OFAC-sanctioned countries or regions where customs duties would require recipients to pay fees. Winners in unsupported regions will receive a digital alternative.
First 10 correct solvers will appear here in real time.
10 swag spots remaining
Open to everyone. No purchase necessary.
Identify bug classes and explain vulnerabilities. Send us a full writeup explaining the issues and how to exploit them.
You may submit answers for one or both challenges. First correct submission counts.
First 10 correct solvers receive Trail of Bits apparel and a sticker pack. Sizes and shipping addresses are collected after winning. Physical swag ships to the US and most of Europe. Winners in unsupported regions receive a digital alternative.
Winners are announced in real time on this page and on our social media (X and LinkedIn).
We'll publish a follow-up blog post with full solutions after the challenge closes. Subscribe to our RSS feed to catch it.
Fill out the form below with your analysis of the challenges. Explain the bug classes and vulnerabilities you found.
Submission form loading...
Trail of Bits has reviewed C/C++ codebases for Oracle, Microsoft, Google, Discord, and dozens of other organizations. We go beyond checklists to find the bugs that matter.
Get in Touch