Since the Mirai source code has been released more than 2 years ago, we know that a compromised host will search for other hosts to compromise with a SYN scan.
The SYN packets are forge with the TTL == 64, a random TCP window size, a very specific TCP sequence number and for the mirai variants, mainly these destination ports (23, 2323, 80, 8080, 8081, 8181, 9000, 5555, 7547, 8443, 37215).
All these elements make the network detection easy on SYN packets (even if one or two elements are changed).
Mirai source code scanner : scanner.c.
/* Line 113 (TTL value) */
iph->ttl = 64;
/* Line 120 (Random TCP window size) */
tcph->window = rand_next() & 0xffff;
/* Line 227 (SYN scan with TCP seq number == int(IP)) */
tcph->check = checksum_tcpudp(iph, tcph, htons(sizeof (struct tcphdr)), sizeof (struct tcphdr));
...
sendto(rsck, scanner_rawpkt, sizeof (scanner_rawpkt), MSG_NOSIGNAL, (struct sockaddr *)&paddr, sizeof (paddr));
/* Line 270 (SYN/ACK condition) */
if (htonl(ntohl(tcph->ack_seq) - 1) != iph->saddr)
continue;
The TCP sequence numbers of the SYN packets correspond to the integer value of the IP destination. The original campaign use this method to test if the TCP sequence number of the SYN/ACK packet minus one correspond to the initiale targeted IP.
This element is a well know and very simple network detection signature (already in many talks and av/researcher papers) and also described in https://mirai.badpackets.net/about/.
The TCP sequence number, like an IPv4 address, is 32 bits. Let's say the targeted IP is 62.210.75.75, the source code will set the TCP sequence number to 1053969227.
To do it, convert each byte of the IP to hexadecimal (3e d2 4b 4b) and then convert this to integer.
>>> int(''.join(['{:02x}'.format(int(x), 'x') for x in '62.210.75.75'.split('.')]), 16)
1053969227
# Reverse function
>>> '.'.join(str(x) for x in [x for x in bytearray.fromhex('{:08x}'.format(1053969227))])
'62.210.75.75'
Today, multiple campaigns based on this source code continue to use this function. This tracker based in Europe is logging every SYN packet that match with this signature.
Here is a solution with SCAPY to detect it (you can also simply set the TCP sequence number corresponding to your public IP).
from scapy.all import *
def packet(pkt):
dst_ip = pkt[IP].dst
seq = pkt[TCP].seq
seq_to_ip_format = '.'.join(str(x) for x in [x for x in bytearray.fromhex('{:08x}'.format(seq))])
if dst_ip == seq_to_ip_format:
print(pkt.summary())
sniff(iface="eth0", filter="tcp[0xd]&18=2", prn=packet, count=100)
I added to this tracker the destination port, the TTL value and the window. These details can help to make some correlations on the Mirai variant recognition method.