SYNful Knock - A Cisco router implant - Part II

In our previous blog, we detailed the inner workings of the SYNful Knock Cisco router implant. You may be asking yourself: "How do I detect and mitigate such a threat in my network?" The detection method largely depends on the possible interaction with the router, but generally falls into two main categories:

  • Host-based indicators are useful for organizations that can issue commands and receive responses. This is only possible for a small amount of routers located in easily accessible areas of the network.
  • Network-based indicators are useful for organizations that are more dispersed or those who lack the ability to easily execute local commands and receive the response.

Ultimately a combination of host- and network-based indicators is the best way to determine the cleanliness of the underlying network.

Host-based Indicators

If command-line access to your router is possible, the techniques shown in Table 1 can be used to detect the implant:

Command

Expected Output

"show platform | include RO, Valid"

Implanted router may produce no results

Table 1:  Host-based indicator command and expected output

In addition to the commands above, other detection techniques are contained within Cisco's IOS Integrity Assurance document.

In the case of this implant, the size of the implanted IOS binary is the same size as the legitimate image. Thus, when comparing file size, it appears to be unmodified. Hashing the image and comparing the result to the hash from Cisco is one of the best methods to detect a modified binary; however, this will only work with an image on disk and not one that is loaded into memory.

Network-Based Indicators

Both active and passive network detection can be deployed to detect and prevent a SYNful Knock compromise. Passive detection can be incorporated into network defense sensors, while active techniques can be used to search for the backdoor.

Passive Network Detection

There are a number of approaches for passive network detection. Our network detection signatures focus on four parts of a Command and Control (CnC) session: the SYN, SYN-ACK, malware response messages, and controller commands. An IDS must be able to monitor the external interface of the router to effectively detect this backdoor from the network.

  1. SYN:  The first signature (included in Appendix A) detects SYN packets with the necessary delta between the TCP sequence and acknowledgment numbers. To reduce the chance of false positives, the signature assumes that the acknowledgement field is not set to zero. This signature detects probing for the malware, and does not necessarily indicate that the destination is compromised.
  2. SYN/ACK:  The second signature (included in Appendix B) validates the delta between TCP sequence and acknowledgement numbers and the TCP options to detect the SYN ACK response from the malware. This signature does not assume that the acknowledgement in the SYN packet is not zero.
  3. Malware response message:  The signature shown below detects the HTTP server response when a command is issued. The advantage of the signature below is that it is a standard Snort signature; however, it does not have the capability to validate the delta between the TCP sequence and acknowledgment numbers.

    alert tcp any any -> any any (\
    msg: "SYNful Knock Cisco Implant HTTP Header";\
    flow: from_server;\
    content: "HTTP/1.1 200 OK|0d 0a|Server: Apache/2.2.17 (Ubuntu)|0d 0a|X-Powered-By: PHP/5.3.5-1ubuntu7.7|0d 0a|Keep-Alive: timeout=15, max=100|0d 0a|Connection: Keep-Alive|0d 0a|Content-Type: text/html|0d 0a 0d 0a|<html><body><div>"; offset:0;\
    flags:PA;\
    sid:201504232;\

  4. Controller commands:  The following signature detects a command issued from the controller. It uses the “text” string at the location expected by the malware and message size less than 256 bytes. The signature also assumes no TCP header options are present. If TCP header options are included, this signature may need to be converted to a compiled signature or multiple variants created to deal with each length.

    alert tcp any any -> any any (\
    msg: "SYNful Knock Cisco Implant HTTP Request";\
    flow: to_server;\
    content: "text"; offset:78; depth:4;\
    content: "|00 00 00|"; offset: 83; depth: 3;\
    content: "|45 25 6d|"; offset: 87; depth: 3;\
    sid:201504233;\
    )

Active Network Detection

Active network detection involves hunting the implant by sending packets to trigger a certain response. Since each environment has different capabilities, we developed multiple tools and methods for active network detection. The correct tool may vary depending on the environment size and resources available.

Nmap Scripting Engine (NSE)

We wrote an NSE script in LUA to actively scan for the presence of this Cisco implant.

Requirements

Modified NSE library

Since the NSE packet library does not allow the user modification of ACK values, the library needs to be modified to allow for this capability. The changes are shown below using a diff command.

root@nix:/usr/share/nmap/nselib# diff packet.lua packet2.lua
1013a1014,1021
>
> --- Set the TCP acknowledgment field.
> -- @param new_ack Acknowledgment.
> function Packet:tcp_set_ack(new_ack)
>   self:set_u32(self.tcp_offset + 8, new_ack)
>   self.tcp_ack = new_ack
> end

Estimated Worst-case Speed (this factors in high unused IP space)

We first ran this scan using Nmap's default scan speed of -T3:

nmap -sS -PN -n -T3 -p 80 --script="SYNfulKnock" 10.1.1.1/24
Class C - 256 IP addresses (4 hosts up) - scanned in 2.29 seconds

Mandiant then ran this scan using Nmap's scan speed of -T4:

nmap -sS -PN -n -T4 -p 80 --script="SYNfulKnock" 10.1.1.1/24
Class C - 256 IP addresses (4 hosts up) - scanned in 2.28 seconds

nmap -sS -PN -n -T4 -p 80 --script="SYNfulKnock" 10.1.1.1/16
Class B - 65536 IP addresses (4 hosts up) - scanned in 2557.50 seconds (42 min)

nmap -sS -PN -n -T4 -p 80 --script="SYNfulKnock" 10.1.1.1/8
Class A - 16,777,216 IP addresses - Estimated scan time = 10,752 minutes (179 hours) = 7 days

Usage:

-sS = SYN scan
-PN = Don't perform host discovery
-n = Don't perform name resolution
-T4 = Throttle to speed 4
-p = port number
--script = script to execute
optional:  --scriptargs="reportclean=1"  Shows the seq and ack for clean devices too

Python Detection Script

We created a Python script to actively scan for the presence of this Cisco implant. This script sends a crafted TCP SYN packet and analyzes the SYN/ACK response for indications of the implant. The script relies on the Scapy packet manipulation library for processing, sending and receiving packets. The scanning process uses several scan threads and a single thread for collecting the responses. This script is about 30 times slower than leveraging the nmap LUA script above; however, it is useful for small scans and for verifying the faster scan.

Requirements

Speed

Class C - 256 IP addresses (4 hosts up) - 59.26 seconds
Class B
- Terminated the script early due to time

Command line

python ./SYNfulKnock_scanner.py -d 10.1.1.1/10.1.1.2

Usage:

-d = Target to be scanned (IP, IP/CIDR, First IP/Last IP)

Output

Nping with flags

We discovered that it is also possible to use a tool such as nping (or hping) to detect this variant of the Cisco implant.

Requirements:

Speed

Class C - 256 IP addresses (4 hosts up) - 257.27 seconds

Command line:

nping -c1 -v3 --tcp -p 80 --seq 791104 --ack 3 10.1.1.1

Usage:

-c = count
-v = verbosity level
-tcp = TCP probe mode
-p = port
-seq = sequence number
-ack = acknowledge number
-H = (optional)  Hide sent, which can speed up the scan

Output

Figure 1:  Using nping to detect the backdoor

Highlighted areas include the sequence and acknowledge numbers. The difference must equal 791102 as well as the TCP flag options described above, which must be:  "02 04 05 b4 01 01 04 02 01 03 03 05".

Mitigation

After confirming compromise, the most effective mitigation is to reimage the router with a known clean download from Cisco. Ensure the new image hash values match and then harden the device to prevent future compromise. We believe the initial compromise occurred due to either default or discovered credentials, so make sure you use new and non-default settings. After ensuring the routers are clean, focus attention on the rest of the network. If the router did not have default credentials, the infection must have occurred some other way. The next step is to begin the compromise assessment.

Feel free to download our whitepaper to easily share this information with the appropriate teams within your organization. Our code will be available at https://github.com/fireeye.

Appendix A: Compiled Snort Signature (TCP SYN Connections)

#include "sf_snort_plugin_api.h"

#include "sf_snort_packet.h"

/* declare detection functions */

int rule201504230eval(void *p);

/* declare rule data structures */

/* flow:to_server; */

static FlowFlags rule201504230flow0 =

{

   FLOW_TO_SERVER

};

static RuleOption rule201504230option0 =

{

   OPTION_TYPE_FLOWFLAGS,

   {

      &rule201504230flow0

   }

};

/* references for sid 201504230 */

static RuleReference *rule201504230refs[] =

{

   NULL

};

/* metadata for sid 201504230 */

/* metadata:; */

static RuleMetaData *rule201504230metadata[] =

{

   NULL

};

RuleOption *rule201504230options[] =

{

   &rule201504230option0,

   NULL

};

Rule rule201504230 = {

   /* rule header, akin to => tcp any any -> any any */

   {

      IPPROTO_TCP, /* proto */

      "any", /* SRCIP     */

      "any", /* SRCPORT   */

      0, /* DIRECTION */

      "any", /* DSTIP     */

      "any", /* DSTPORT   */

   },

   /* metadata */

   {

      3,  /* genid */

      201504230, /* sigid */

      1, /* revision */

      "misc-activity", /* classification */

      0,  /* hardcoded priority */

      "TCP Trigger SEQ SYN",     /* message */

      rule201504230refs, /* ptr to references */

      rule201504230metadata /* ptr to metadata */

   },

   rule201504230options, /* ptr to rule options */

   &rule201504230eval, /* use custom detection function */

   0 /* am I initialized yet? */

};

/* detection functions */

int rule201504230eval(void *p) {

   const u_int8_t *cursor_normal = 0;

   SFSnortPacket *sp = (SFSnortPacket *) p;

   uint32_t seq = 0;

   uint32_t ack = 0;

   if(sp == NULL)

      return RULE_NOMATCH;

   if(sp->payload == NULL)

      return RULE_NOMATCH;

   ack = ntohl(sp->tcp_header->acknowledgement);

   seq= ntohl(sp->tcp_header->sequence); 

   if (ack == 0){

      return RULE_NOMATCH;

   }

   //Test for SYN packets

   if((sp->tcp_header->flags & TCPHEADER_SYN)&& !(sp->tcp_header->flags & TCPHEADER_ACK)){

      if ((ack > seq) && (ack - seq == 0xC123D)){ return RULE_MATCH; }

      else if ((seq > ack) && (seq - ack == 0xC123D)){ return RULE_MATCH;}

   }

   return RULE_NOMATCH;

}

Appendix B: Compiled Snort Signature (TCP SYN-ACK Connections)

#include "sf_snort_plugin_api.h"

#include "sf_snort_packet.h"

/* declare detection functions */

int rule201504231eval(void *p);

 

/* declare rule data structures */

/* flow:to_server; */

static FlowFlags rule201504231flow0 =

{

   FLOW_TO_SERVER

};

static RuleOption rule201504231option0 =

{

   OPTION_TYPE_FLOWFLAGS,

   {

      &rule201504231flow0

   }

};

/* references for sid 201504230 */

static RuleReference *rule201504231refs[] =

{

   NULL

};

/* metadata for sid 201504230 */

/* metadata:; */

static RuleMetaData *rule201504231metadata[] =

{

   NULL

};

RuleOption *rule201504231options[] =

{

   &rule201504231option0,

   NULL

};

Rule rule201504231 = {

   /* rule header, akin to => tcp any any -> any any */

   {

      IPPROTO_TCP, /* proto */

      "any", /* SRCIP     */

      "any", /* SRCPORT   */

      0, /* DIRECTION */

      "any", /* DSTIP     */

      "any", /* DSTPORT   */

   },

   /* metadata */

   {

      3,  /* genid */

      201504231, /* sigid */

      1, /* revision */

      "trojan-activity", /* classification */

      0,  /* hardcoded priority */

      "TCP Trigger SEQ SYN/ACK",     /* message */

      rule201504231refs, /* ptr to references */

      rule201504231metadata /* ptr to metadata */

   },

   rule201504231options, /* ptr to rule options */

   &rule201504231eval, /* use custom detection function */

   0 /* am I initialized yet? */

};

/* detection functions */

int rule201504231eval(void *p) {

   const u_int8_t *cursor_normal = 0;

   SFSnortPacket *sp = (SFSnortPacket *) p;

   uint32_t seq = 0;

   uint32_t ack = 0;

   if(sp == NULL)

      return RULE_NOMATCH;

   if(sp->payload == NULL)

      return RULE_NOMATCH;

   ack = ntohl(sp->tcp_header->acknowledgement);

   seq= ntohl(sp->tcp_header->sequence);

   //Test for SYN/ACK packets 

   if((sp->tcp_header->flags & TCPHEADER_SYN) && (sp->tcp_header->flags & TCPHEADER_ACK))

      if ((ack > seq) &&  (ack - seq != 0xC123E)){ return RULE_NOMATCH; }

      else if ((seq > ack) && (seq - ack != 0xC123E)){ return RULE_NOMATCH; }

      //Hardcoded SYN/ACK has 6 options

      if (sp->num_tcp_options != 6) return RULE_NOMATCH;

      //MSS

      if(sp->tcp_options[0].option_code != 2) return RULE_NOMATCH;

      //NOP

      if(sp->tcp_options[1].option_code != 1) return RULE_NOMATCH;

      //NOP

      if(sp->tcp_options[2].option_code != 1) return RULE_NOMATCH;

      //SACK

      if(sp->tcp_options[3].option_code != 4) return RULE_NOMATCH;

      //NOP

      if(sp->tcp_options[4].option_code != 1) return RULE_NOMATCH;

      //Window Scale

      if(sp->tcp_options[5].option_code != 3) return RULE_NOMATCH;

      //compare the entire TCP options section

      if (memcmp(sp->tcp_options[0].option_data - 2,

         "\x02\x04\x05\xb4\x01\x01\x04\x02\x01\x03\x03\x05", 12) != 0)

         return RULE_NOMATCH;  

      //All conditions satisfied

      return RULE_MATCH;

   }

   return RULE_NOMATCH;

}

/*

Rule *rules[] = {

    &rule201504231,

    NULL

};

*/