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
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
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)
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 verifyDat

00014908 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
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

Conclusion

Designing and implementing secure network stack is not easy. With this NetUSB example, we can see that how it can be broken with a proprietary encryption scheme and basic memory operations. Open source software typically goes through multiple checks such that problems like basic buffer length error are caught and fixed relatively easily. Proprietary software without any source code might look very secure, but reversing them is not so difficult.

The other thing we want to point out is the fact that it is not easy to verify whether a security fix has been applied to an appliance. For normal users, the network appliances like home routers are not something they can self-service easily. They are usually given web administrator interface, but no console access unless you have tools and devices to access it through hardware interface. This is a problem of transparency and verification.

Ultimately you never know if your device is updated properly, and sometimes you have no choice but to trust the vendor. But, sometimes the vendor can be wrong. As it turns out, there appears to be much more work to be done regarding this Internet connected home device.

Recommendation

In the meantime, it is recommended that blocking tcp port 20005 on the local network could help mitigate the issue by preventing access to the NetUSB service.

 

References

https://www.kb.cert.org/vuls/id/177092

CWE-120: Buffer Copy without Checking Size of Input (‘Classic Buffer Overflow’) – CVE-2015-3036