Remcos RAT 3.4.0 protocol
Contents
Remcos RAT is known for being very feature rich, with a lite version to test. They even provide an option to disable the TLS, making it very easy to reverse engineer the protocol. I’m not aware if the paid version has a different protocol.
The binary protocol is very simple:
- packets start with
$\x04\xff\x00
- then is the packet type
- then 7 usually miscellaneous bytes
- then often string arguments separated by
|\x1e\x1e\x1f|
I’ve mapped out a significant amount of the protocol available in the lite version. Protocols are generally a pain to document, so I’ve included my packet parsing code.
def parse_packet(packets, packet, payload, server):
if server:
if payload[:5] == b'$\x04\xff\x00\x0c':
return {
"type": "ping",
"settings": payload[12:].split(b"|\x1e\x1e\x1f|")
}
elif payload[:5] == b'$\x04\xff\x00\x0b':
return {
"type": "ping-latency",
"settings": payload[12:].split(b"|\x1e\x1e\x1f|")
}
elif payload[:5] == b'$\x04\xff\x00\x0e':
return {
"type": "execute-cmd-1",
"cmd": [str(a, "utf16") for a in payload[12:].split(b"|\x1e\x1e\x1f|")]
}
elif payload[:5] == b'$\x04\xff\x00(':
return {
"type": "execute-cmd-2",
"cmd": [str(a, "utf16") for a in payload[12:].split(b"|\x1e\x1e\x1f|")]
}
elif payload[:8] == b'$\x04\xff\x00\x04\x00\x00\x00' and chr(payload[8]) in ["(", "*"]:
return {
"type": "get-clipboard"
}
elif payload[:4] == b'$\x04\xff\x00' and chr(payload[4]) in ["\x18", "\xf2"]:
return {
"type": "set-clipboard",
"contents": str(payload[12:], "utf16")
}
elif payload[:9] == b'$\x04\xff\x00\x04\x00\x00\x00\x03':
return {
"type": "get-installed-programs"
}
elif payload[:9] == b'$\x04\xff\x00\x04\x00\x00\x00\x08':
return {
"type": "get-windows"
}
elif payload[:9] == b'$\x04\xff\x00\x04\x00\x00\x00\x06':
return {
"type": "get-processes"
}
elif payload[:5] == b'$\x04\xff\x00\n':
if payload[8] == 0xa:
return {
"type": "maximize-window",
"window": str(payload[12:])
}
elif payload[8] == 0x9:
return {
"type": "minimize-window",
"window": str(payload[12:])
}
elif payload[8] == 0x0b:
return {
"type": "show-window",
"window": str(payload[12:])
}
elif payload[8] == 0xae:
return {
"type": "hide-window",
"window": str(payload[12:])
}
elif payload[8] == 0xad:
return {
"type": "visible-window",
"window": str(payload[12:])
}
elif payload[8] == 0x0c:
return {
"type": "kill-window",
"window": str(payload[12:])
}
elif payload[:5] == b'$\x04\xff\x00h':
info = payload[12:].split(b"|\x1e\x1e\x1f|")
return {
"type": "set-window-title",
"window": info[0],
"title": str(info[1], "utf16")
}
elif payload[:4] == b'$\x04\xff\x00' and chr(payload[4]) in ["k", "]", "w", "M"]:
info = payload[12:].split(b"|\x1e\x1e\x1f|")
if payload[8] == 0x05:
location = "local"
contents = info[4]
elif payload[8] == 0x04:
location = "url"
contents = str(info[4], "utf16")
else:
location = payload[8]
contents = info[4]
if info[2] == b"1":
path = "application-path"
elif info[2] == b"7":
path = "user-profile"
elif info[2] == b"6":
path = "appdata"
elif info[2] == b"0":
path = "temp"
elif info[2] == b"2":
path = "root"
elif info[2] == b"3":
path = "windows"
elif info[2] == b"4":
path = "system32"
elif info[2] == b"5":
path = "program-files"
else:
path = info[2]
if info[1] == b'\x00':
is_exec = False
elif info[1] == b'\x01':
is_exec = True
else:
is_exec = info[1]
return {
"type": "download",
"location": location,
"unidentified-1": info[0],
"exec": is_exec,
"path": path,
"name": str(info[3], "utf16"),
"contents": contents
}
else:
if payload[:4] == b'$\x04\xff\x00' and chr(payload[4]) in ["\xaa", "\xae", "\xa9"]:
info = payload[12:].split(b"|\x1e\x1e\x1f|")
return {
"type": "init",
"assigned-name": info[0],
"computer-user-name": str(info[1], "utf16"),
"location": info[2],
"os": info[3],
"ram": info[5], # bytes
"version": info[6],
"path": str(info[8], "utf16"),
"path-2": str(info[10], "utf16"),
"unknown-2": info[11],
"idle-time": info[12], # milliseconds
"system-uptime": info[13], # milliseconds
"unknown-5": info[14],
"ip": info[15],
"mutex": info[16],
"unknown-7": info[17],
"cpu": info[19],
"agent-type": info[20]
}
elif payload[:4] == b'$\x04\xff\x00' and chr(payload[4]) in ["c", "d", "e", "f", "g", "i", "0", "3", "="] and payload[5:9] == b'\x00\x00\x00L':
info = payload[12:].split(b"|\x1e\x1e\x1f|")
return {
"type": "pong",
"unknown-1": info[0],
"active-window": str(info[1], "utf16"),
"idle-time": info[2],
"system-uptime": info[3]
}
elif payload[:4] == b'$\x04\xff\x00' and chr(payload[4]) in ["\xf0", "\x04", "\x16", "h"]:
return {
"type": "clipboard-data",
"contents": str(payload[12:], "utf16")
}
elif payload[:5] == b'$\x04\xff\x00\x80':
packet_i = packets.index(packet)
while payload[len(payload) - 4:len(payload)] != b'\x09\x00\x0a\x00':
packet_i += 1
payload += bytes(packets[packet_i]['TCP'].payload)
programs = []
for raw_program in str(payload[12:], "utf16").split("\t\n"):
if raw_program == "":
break
program = raw_program.split("\t")
programs.append({
"name": program[0],
"version": program[1],
"date-installed": program[2],
"publisher": program[3],
"location": program[4],
"uninstall-string": program[5]
})
return {
"type": "installed-programs",
"programs": programs,
"_skip": packet_i - packets.index(packet)
}
elif payload[:5] == b'$\x04\xff\x00\xc8':
packet_i = packets.index(packet)
while payload[len(payload) - 2:len(payload)] != b'\x00\x1e':
packet_i += 1
payload += bytes(packets[packet_i]['TCP'].payload)[12:]
windows = []
parts = payload[12:].split(b"\x1f")
for i in range(1, len(parts), 2):
if len(parts[i + 1]) == 2:
title = ""
else:
title = str(parts[i + 1][:-2], "utf16", "ignore")
windows.append({
"handle": parts[i],
"title": title,
"visible": parts[i + 1][-1]
})
return {
"type": "windows-list",
"windows": windows,
"_skip": packet_i - packets.index(packet)
}
elif payload[:9] == b'$\x04\xff\x00\x05\x00\x00\x00\xb3':
if chr(payload[12]) == "0":
status = "download-successful"
elif chr(payload[12]) == "1":
status = "download-execute-successful"
elif chr(payload[12]) == "2":
status = "download-error"
elif chr(payload[12]) == "3":
status = "execute-error"
else:
status = payload[12]
return {
"type": "download-file-status",
"status": status
}
The full python script is available here.
$ python3 parse.py
usage: parse.py [-h] --pcap <pcap file name> --server <server ip address> --client <client ip address>