Rediscovery of NetUSB Vulnerability in Broadband Routers
Recently NewSky Security Labs performed white-box testing on a Netgear networking product, the R6050 model. During our investigation into the system, we found an exploitable vulnerability in the NetUSB module present in the system. NetUSB is a proprietary technology developed by the Taiwanese company KCodes, intended to provide “USB over IP” functionality.
NetUSB is included in millions of currently in-use and popular broadband routers, including models from the following vendors:
Allnet
Ambir Technology
AMIT
Asante
Atlantis
Corega
Digitus
D-Link
EDIMAX
Encore Electronics
Engenius
Etop
Hardlink
Hawking
IOGEAR
LevelOne
Longshine
NETGEAR
PCI
PROLiNK
Sitecom
Taifa
TP-LINK
TRENDnet
Western Digital
ZyXEL
Also, to our surprise the same issue was already reported back in 2015 by SEC Consult Vulnerability lab (CVE-2015–3036). We explain the issue in detail here with more focus on technical aspects of the problem.
White-box Approach
First, we purchased the device from a retail store. All testing was performed without any modification to the software. We opened up the device and connected UART cables and using USB-to-TTL device, we acquired serial console connection to the device so that we can experiment with the device.
Figure 1 Connecting UART connections to the target device
Basic Reconnaissance
We took a look around the system using tools already installed on the system. For example, netstat command gives you a basic understanding of what services run on the system. We checked on the services run on each port, and tcp port 20005 looked interesting to us:
# netstat -na|more
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 0 0 0.0.0.0:33344 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:20005 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:56688 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN
tcp 0 0 192.168.1.1:53 0.0.0.0:* LISTEN
udp 0 0 0.0.0.0:23 0.0.0.0:*
udp 0 0 192.168.1.1:53 0.0.0.0:*
udp 0 0 0.0.0.0:67 0.0.0.0:*
udp 0 0 0.0.0.0:40028 0.0.0.0:*
udp 0 0 0.0.0.0:1900 0.0.0.0:*
udp 0 0 192.168.1.1:57847 0.0.0.0:*
After some digging, we found that tcp port 20005 was used by the NetUSB service. The lsmod command gives you the kernel modules responsible for the service:
# lsmod
Module Size Used by
NetUSB 176416 0
GPL_NetUSB 2400 1 NetUSB
We acquired the file from the following locations.
# pwd
/lib/modules
# ls -la *NetUSB*
-rwxr-xr-x 1 root root 12848 Mar 1 2015 GPL_NetUSB.ko
-rwxr-xr-x 1 root root 291704 Mar 1 2015 NetUSB.ko
Looks like GPL_NetUSB.ko is merely a wrapper around the imports that are used in NetUSB.ko. They split the modules, so that they are not governed by license terms of GPL code.
Figure 2 GPL NetUSB Functions
The core logic is in the NetUSB.ko file. Since the module was developed by a private company in Taiwan and not available as opensource, we had to reverse-engineer the binary code to understand the functionalities.
Protocol Analysis
After some digging, we found that “run_init_sbus” function is the function that parses and processes the initial handshake packets between the server and the client.
Figure 3 run_init_sbus (shown assembly is where the computer name length variable is received)
Version Check
First, it receives a packet. The packet is 2 bytes long and it contains version number. The version is performed after this. ks_recv is the kernel call, it uses to receive TCP packet here. Register $a0 contains socket descriptor, $1 points to the receiving buffer and $a2 has the size of the buffer when the ks_recv is called by using “jalr” instruction.
000147C8 recv_packet_01:
000147C8 addiu $s0, $sp, 0x368+version_number
000147CC lui $s4, (ks_recv >> 16)
000147D0 move $a0, $s3
000147D4 move $a1, $s0
000147D8 li $a2, 2
000147DC addiu $v0, $s4, (ks_recv & 0xFFFF)
000147E0 jalr $v0 ; ks_recv
000147E4 move $a3, $zero
000147E8 bltz $v0, loc_0_14970
000147EC lui $a0, ($LC257 >> 16) # “INFO%04X: tcpConnector() receiving erro”…
Weak Encryption Scheme
The thing is that NetUSB uses a custom protocol and it also implements custom network packet encryption. The problem is that it uses predefined keys. The key was encrypted using another key in the binary. The following code calls aes_decrypt to retrieve the predefined master key for NetUSB session.
00014860
00014860 decrypt_key:
00014860 addiu $s1, $sp, 0x368+var_260
00014864 lui $v0, (aes_set_key >> 16)
00014868 move $a0, $s1
0001486C addiu $a1, $sp, 0x368+aes_key[0]
00014870 addiu $s5, $v0, (aes_set_key & 0xFFFF)
00014874 jalr $s5 ; aes_set_key
00014878 li $a2, 0x80 # in
0001487C addiu $s0, $sp, 0x368+decrypted_key # $s0=decrypted_key
00014880 lui $v0, (aes_decrypt >> 16)
00014884 move $a0, $s1
00014888 addiu $a1, $sp, 0x368+encrypted_key[0] # out
0001488C la $v0, (aes_decrypt & 0xFFFF)
00014890 jalr $v0 ; aes_decrypt
00014894 move $a2, $s0 # $s0=decrypted_key
Server Verification
The client sends random data of length 0x10 bytes and the server code encrypts them using a predefined key. The client will disconnect if the key is not the same.
00014898 recv_packet_buffer_02: # $s7=packet_buffer_02
00014898 addiu $s7, $sp, 0x368+packet_buffer_02
0001489C lui $v1, (ks_recv >> 16)
000148A0 li $a2, 0x10
000148A4 move $a0, $s3
000148A8 move $a1, $s7 # $s7=packet_buffer_02
000148AC la $v1, (ks_recv & 0xFFFF)
000148B0 jalr $v1 ; ks_recv
000148B4 move $a3, $zero
000148B8 move $a2, $v0
000148BC li $v0, 0x10
000148C0 beq $a2, $v0, encrypt_client_packet
000148C4 lui $a0, ($LC260 >> 16) # “INFO%04X: get verifyDat00014908 encrypt_client_packet: #
00014908 move $a1, $s0 # $s0=decrypted_key
0001490C move $a0, $s1 # $s1=aes context
00014910 jalr $s5 ; aes_set_key
00014914 li $a2, 0x80
00014918 addiu $s4, $sp, 0x368+packet_buffer
0001491C lui $v0, (aes_encrypt >> 16)
00014920 move $a0, $s1 # $s1=aes context
00014924 move $a1, $s7 # $s7=packet_buffer_02
00014928 la $v0, (aes_encrypt & 0xFFFF)
0001492C jalr $v0 ; aes_encrypt
00014930 move $a2, $s4 # $s4=packet_buffer
00014934
00014934 send_back:
00014934 la $v1, ks_send
0001493C move $a0, $s3
00014940 move $a1, $s4 # $s4=packet_buffer
00014944 li $a2, 0x10
00014948 jalr $v1 ; ks_send
0001494C move $a3, $zero
00014950 li $v1, 0x10
00014954 beq $v0, $v1, send_packet_02
00014958 lui $a0, ($LC261 >> 16) # “INFO%04X: sen
Client Verification
This time, the server sends back random bytes of 0x10 length to the client. The server verifies client’s response.
00014984 send_packet_02: # $s5=var_random_bytes
00014984 addiu $s5, $sp, 0x368+var_random_bytes[0]
00014988 la $s0, get_random_bytes
00014990 move $a0, $s5
00014994 jalr $s0 ; get_random_bytes
00014998 li $a1, 4
0001499C addiu $a0, $sp, 0x368+var_random_bytes[1]
000149A0 jalr $s0 ; get_random_bytes
000149A4 li $a1, 4
000149A8 addiu $a0, $sp, 0x368+var_random_bytes[2]
000149AC jalr $s0 ; get_random_bytes
000149B0 li $a1, 4
000149B4 addiu $a0, $sp, 0x368+var_random_bytes[3]
000149B8 jalr $s0 ; get_random_bytes
000149BC li $a1, 4
000149C0 lui $v1, (ks_send >> 16)
000149C4 move $a0, $s3
000149C8 move $a1, $s5 # $s5=var_random_bytes
000149CC li $a2, 0x10
000149D0 la $v1, (ks_send & 0xFFFF)
000149D4 jalr $v1 ; ks_send
000149D8 move $a3, $zero
000149DC li $s0, 0x10
000149E0 beq $v0, $s0, recv_packet_03
000149E4 lui $a0, ($LC262 >> 16) # “INFO%04X: send
00014A24 recv_packet_03:
00014A24 lui $v1, (ks_recv >> 16)
00014A28 li $a2, 0x10
00014A2C move $a0, $s3
00014A30 move $a1, $s4 # $s4=packet_buffer
00014A34 la $v1, (ks_recv & 0xFFFF)
00014A38 jalr $v1 ; ks_recv
00014A3C move $a3, $zero
00014A40 beq $v0, $s0, verify_packet_02
00014A44 move $a2, $v0
Receiving Computer Name
After the client and server verifies one another using this broken encryption and authentication scheme, the client will send its computer name to the server. The only problem here is that the code that parses this packet is written in a very insecure manner.
If you look at following disassembly code, it calls ks_recv at 00014AD8 where it will retrieve 4 bytes DWORD value from the client. This value is used for computer_name_len DWORD size variable.
00014AC8 recv_packet_04:
00014AC8 li $a2, 4
00014ACC move $a0, $s3
00014AD0 addiu $a1, $sp, 0x368+var_computer_name_len
00014AD4 la $v1, (ks_recv & 0xFFFF)
00014AD8 jalr $v1
00014ADC move $a3, $zero
00014AE0 bltz $v0, loc_0_14E40
00014AE4 move $a2, $v0
Buffer Overflow
Here is where the problem occurs. The DWORD var_computer_name_buffer variable is fed into ks_recv call at 00014B14 again as the buffer length parameter. But, the actual buffer passed to the call is var_computer_name_buffer which is a local variable that will be positioned in the stack.
00014AE8
00014AE8 recv_packet_05:
00014AE8 addiu $s7, $sp, 0x368+var_computer_name_buffer
00014AEC move $a0, $s7
00014AF0 move $a1, $zero
00014AF4 addiu $v0, $fp, (memset & 0xFFFF)
00014AF8 jalr $v0
00014AFC li $a2, 0x40
00014B00 lw $a2, 0x368+var_computer_name_len($sp)
00014B04 lui $v1, (ks_recv >> 16)
00014B08 move $a0, $s3
00014B0C move $a1, $s7
00014B10 la $v1, (ks_recv & 0xFFFF)
00014B14 jalr $v1 ; ks_recv
00014B18 move $a3, $zero
00014B1C bltz $v0, loc_0_14B30
00014B20 move $a2, $v0
If you dig more, you can calculate the size of the var_computer_name_buffer variable (0x328–0x2E8=0x40 bytes).
-00000328 var_computer_name_buffer:.byte ?
-00000327 .byte ? # undefined
-00000326 .byte ? # undefined
-00000325 .byte ? # undefined
…
-000002EA .byte ? # undefined
-000002E9 .byte ? # undefined
-000002E8 var_2E8: .word ?
-000002E4 var_2E4: .word ?
-000002E0 var_2E0: .byte ?
-000002DF var_2DF: .byte ?
-000002DE .byte ? # undefined
-000002DD .byte ? # undefined
-000002DC .byte ? # undefined
-000002DB .byte ? # undefined
…
So, the issue is that if you send any packets with computer name bigger than 0x40, basically you are corrupting the stack. If you send a packet with a large enough length, you will actually corrupt the return address of the control flow.
The following shows what happens when you corrupt the stack with a long string of characters “AAAA…”:
INFO1632: new connection from 192.168.1.9 : 878b75a0
INFO1EF1: Tunnel start sig error 4376
INFO1F58: connent fail from : 878b75a0
V4 : 0901A8C0
CPU 0 Unable to handle kernel paging request at virtual address 41414140, epc ==
41414141, ra == 41414141
Oops[#1]:
Cpu 0$ 0 : 00000000 00000000 87c06618 00000007
$ 4 : 87c01120 87c04780 87c047a8 86115b08
$ 8 : 805af7a0 00000000 81100000 00000057
$12 : 00000000 00000000 00000000 00000000
$16 : 41414141 41414141 41414141 41414141
$20 : 41414141 41414141 41414141 41414141
$24 : 00000000 803be0d0
$28 : 86114000 86115ec0 41414141 41414141
Hi : 00000592
Lo : 39185000
epc : 41414141 0x41414141
Tainted: P
ra : 41414141 0x41414141
Status: 1100ff03 KERNEL EXL IE
Cause : 10800008
BadVA : 41414140
PrId : 00019650 (MIPS 24Kc)
Modules linked in: NetUSB(P) GPL_NetUSB usbserial_filter(P) hw_nat(P) usb_storage wifi_isolation(P) ipt_REJECT MT7610_ap(P) others_dos(P) clamp_total_session_for_one_src tcp_syn_dos psd_and_special_udp_dos fake_source_dos(P) firewall_block common(P) natlimit(P) hairpin(P) cudp(P) cdmz(P) calg(P) cpm(P) cpt(P) cnapt(P) cnapt_core(P) xt_ct_dir ipt_macblock_dnshj(P) ipt_DLOG(P) ipt_dnshj ipt_http_str ing ipt_condition led_hw(P) led_pb_api(P)Process run_init_sbus (pid: 3869, threadinfo=86114000, task=86077730, tls=000000 00)
Stack : 41414141 41414141 41414141 41414141 41414141 41414141 41414141 41414141
41414141 41414141 41414141 41414141 41414141 41414141 41414141 41414141
41414141 41414141 41414141 41414141 41414141 41414141 41414141 41414141
41414141 41414141 41414141 41414141 41414141 41414141 41414141 41414141
41414141 41414141 41414141 41414141 41414141 41414141 41414141 41414141
…Call Trace:
Code: (Bad address in epc)
Aside from this crash testing, we didn’t attempt to further exploit the buffer overflow to achieve code execution.
Is there a fix?
The other issue we want to raise is a vendor response problem. When the original issue was raised a year ago, the vendor promised to fix this bug. If you visit Netgear page, R6050 model is marked as fixed as shown below.
Figure 4 The Netgear FAQ on NetUSB bug
But the latest firmware we acquired through the website, still contained the buggy NetUSB.ko module. We updated our device with this latest firmware image and we found that NetUSB.ko and GPL_NetUSB.ko has not been changed from previous firmware update!
We tested with our POC and it still crashed the kernel module.
Of the vendors that may be affected, only two others have listed this as fixed:
Vendor ……………………………………. Link
D-Link ………………………………………. SAP10057
Zyxel ………………………………………… Update
- Hacking
- IoT
- privacy
- Security