Simple Protocol
Category
Reverse Engineering
Points
90
Solves
-1
Tags
Amid whirring routers and blinking panel lights, ZeroTrace dissects a custom network protocol linking industrial subsystems. Patterns in the packet flow hint at secrets embedded deep within machine chatter.
This challenge involves reverse engineering a server that listens on port 4444
and processes incoming packets based on a custom protocol. The server validates packets using a checksum and metadata before executing specific commands based on the command_id
provided in the packet.
__int64 __fastcall main(int a1, char **a2, char **a3)
{
uint16_t v4; // [rsp+Ch] [rbp-74h]
uint16_t v5; // [rsp+Eh] [rbp-72h]
socklen_t addr_len; // [rsp+10h] [rbp-70h] BYREF
int fd; // [rsp+14h] [rbp-6Ch]
int v8; // [rsp+18h] [rbp-68h]
uint32_t v9; // [rsp+1Ch] [rbp-64h]
uint32_t v10; // [rsp+20h] [rbp-60h]
int v11; // [rsp+24h] [rbp-5Ch]
uint32_t v12; // [rsp+28h] [rbp-58h]
uint32_t v13; // [rsp+2Ch] [rbp-54h]
ssize_t v14; // [rsp+30h] [rbp-50h]
uint32_t v15[2]; // [rsp+3Ch] [rbp-44h] BYREF
uint16_t buf[2]; // [rsp+44h] [rbp-3Ch] BYREF
uint32_t netlong; // [rsp+48h] [rbp-38h]
uint32_t v18; // [rsp+4Ch] [rbp-34h]
sockaddr addr; // [rsp+50h] [rbp-30h] BYREF
struct sockaddr v20; // [rsp+60h] [rbp-20h] BYREF
unsigned __int64 v21; // [rsp+78h] [rbp-8h]
v21 = __readfsqword(0x28u);
addr_len = 16;
fd = socket(2, 1, 0);
if ( fd < 0 )
{
perror("socket");
exit(1);
}
addr.sa_family = 2;
*(_DWORD *)&addr.sa_data[2] = 0;
*(_WORD *)addr.sa_data = htons(0x115Cu);
if ( bind(fd, &addr, 0x10u) < 0 )
{
perror("bind");
exit(1);
}
if ( listen(fd, 5) < 0 )
{
perror("listen");
exit(1);
}
printf("Listening on port %d\n", 4444);
v8 = accept(fd, &v20, &addr_len);
if ( v8 < 0 )
{
perror("accept");
exit(1);
}
puts("Connection received.");
v14 = recv(v8, buf, 0xCuLL, 256);
if ( v14 != 12 )
{
fwrite("Failed to receive header.\n", 1uLL, 0x1AuLL, stderr);
exit(1);
}
v4 = ntohs(buf[0]);
v5 = ntohs(buf[1]);
v9 = ntohl(netlong);
v10 = ntohl(v18);
v11 = ((unsigned __int16)(v4 ^ v5) << 16) | (unsigned __int16)v10;
if ( v9 != v11 )
{
fwrite("Checksum validation failed.\n", 1uLL, 0x1CuLL, stderr);
exit(1);
}
v14 = recv(v8, v15, 8uLL, 256);
if ( v14 != 8 )
{
fwrite("Failed to receive body metadata.\n", 1uLL, 0x21uLL, stderr);
exit(1);
}
v12 = ntohl(v15[0]);
v13 = ntohl(v15[1]);
if ( v12 != v10 )
{
fwrite("Body payload_id does not match header.\n", 1uLL, 0x27uLL, stderr);
exit(1);
}
if ( v13 > 0x40 )
{
fwrite("Payload size too large.\n", 1uLL, 0x18uLL, stderr);
exit(1);
}
if ( v5 == 256 )
{
sub_1421((unsigned int)v8);
}
else
{
if ( v5 > 0x100u )
goto LABEL_27;
if ( v5 == 1 )
sub_13C9((unsigned int)v8);
if ( v5 != 16 )
{
LABEL_27:
fprintf(stderr, "Invalid command: 0x%x\n", v5);
exit(1);
}
sub_13E2((unsigned int)v8);
}
close(v8);
close(fd);
return 0LL;
}
Packet Structure
To interact with the server, packets must follow the structure defined in the protocol:
- Header:
v4
: A fixed value (e.g.,0x1234
).v5
: Thecommand_id
indicating the operation to execute.checksum
: A validation value calculated as((v4 ^ v5) << 16) | (payload_id & 0xFFFF)
.payload_id_net
: The payload ID.
- Metadata:
payload_id_net
: The payload ID (must match the header).unused
: A reserved field, typically set to0
.
Sending Packets
The following Python code demonstrates how to construct and send packets to the server:
import socket
import struct
SERVER_HOST = '10.10.162.28'
SERVER_PORT = 4444
def send_packet(command_id, payload_id):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((SERVER_HOST, SERVER_PORT))
v4 = 0x1234
v5 = command_id
payload_id_net = payload_id
some_value = payload_id_net
checksum = ((v4 ^ v5) << 16) | (some_value & 0xFFFF)
header = struct.pack('!HHII', v4, v5, checksum, payload_id_net)
metadata = struct.pack('!II', payload_id_net, 0)
sock.sendall(header)
sock.sendall(metadata)
print("Packet sent successfully.")
response = sock.recv(4096)
print("Response received:", response)
sock.close()
send_packet(command_id=256, payload_id=0xdeadbeef)
Simple Protocol Flag:
THM{REDACTED}