May 17, 2016 Fixing TP-LINK login

Translation: ru

TP-LINK broke settings import after firmware update. Actually it was broken for quite some time (at least since 2014 according to some forums), but I've only just encountered this problem on my WDR3600 router. I don't update router firmware frequently. The breaking change was they stopped storing admin login passwords in plain text and switched to some hash sum instead (which is probably a good thing). As a result people are no longer able to login into their routers anymore after backing up and restoring their configs.

One possible solution is resetting router to factory defaults and repeating setup from memory or from some web UI screenshots conveniently taken beforehand. I had no such screenshots and the option to reconfigure various port-forwarding and IP-binding rules wasn't very attractive.

After some googling around I've learned that backed up config is actually more or less a text file encrypted with symmetric cypher (DES-ECB). Fortunately other people already done the research needed to decode it. Decoded config (backed up using older firmware) consists of ~1300 key-value pairs and looks like this:

lan_ip 192.168.8.200
lan_msk 255.255.255.0
...
lgn_ousr admin
lgn_opswd admin
lgn_usr **********
lgn_pwd **********
...

To encode it back to a form consumable by the router you need to prepend the file by its MD5 sum and to pad with null bytes to the next 8 byte boundary (before encoding it using the same cypher). I've made a helpful script to automate encoding/decoding process:

tlrecode.sh

#!/usr/bin/env bash

# tlrecode.sh
# Decode and encode TP-LINK router config files.
#
# Creative Commons CC0 License:
# http://creativecommons.org/publicdomain/zero/1.0/
#
# To the extent possible under law, the person who associated CC0 with this
# work has waived all copyright and related or neighboring rights to this work.

in=""
out=""
mode=""

# See http://teknoraver.net/software/hacks/tplink/
key="478DA50BF9E3D2CF"

tmp_template="tlrecode.XXXXXXXXXX"
tmp=""
tmp2=""

usage() {
    cat <<EOF
tlrecode.sh
Decode and encode TP-LINK router config files.

Usage: tlrecode.sh (-e|-d) [-i] INPUT [-o] OUTPUT

    -i, --input    Input file name.
    -o, --output   Output file name.
    -d, --decode   Decoding mode (default).
    -e, --encode   Encoding mode.
    -h, --help     Print this message.

EOF
}

error() {
    echo "Error: $1" >&2
    exit 1
}

decode() {
    openssl enc -d -des-ecb -K "${key}" -nopad -in "${in}" -out "${tmp}" &&
        tail -c +17 "${tmp}" > "${tmp2}" &&
        tr -d "\000" < "${tmp2}" > "${out}"
}

encode() {
    openssl md5 -binary "${in}" > "${tmp}" &&
        cat "${tmp}" "${in}" > "${tmp2}" &&
        truncate -s %8 "${tmp2}" &&
        openssl enc -e -des-ecb -K "${key}" -nopad -in "${tmp2}" -out "${out}"
}

while [ $# -gt 0 ]; do
    case "$1" in
        -i|--input)
            [ -z "${in}" ] || error "input file '${in}' is already set."
            [ $# -gt 1 ] || error "option '$1' needs an argument."
            in="$2"
            shift 2
            ;;
        -o|--output)
            [ -z "${out}" ] || error "output file '${out}' is already set."
            [ $# -gt 1 ] || error "option '$1' needs an argument."
            out="$2"
            shift 2
            ;;
        -d|--decode)
            [ -z "${mode}" ] || error "mode '${mode}' is already set."
            mode="decode"
            shift 1
            ;;
        -e|--encode)
            [ -z "${mode}" ] || error "mode '${mode}' is already set."
            mode="encode"
            shift 1
            ;;
        -h|--help)
            usage
            exit 0
            ;;
        *)
            if [ -z "${in}" ]; then
                in="$1"
            elif [ -z "${out}" ]; then
                out="$1"
            else
                error "unexpected argument '$1'"
            fi
            shift 1
            ;;
    esac
done

[ -z "${in}" ] && error "no input file given."
[ -f "${in}" ] || error "input file '${in}' does not exist."
[ -z "${out}" ] && error "no output file given."
[ -z "${mode}" ] && mode="decode"

tmp="$(mktemp ${tmp_template})"
tmp2="$(mktemp ${tmp_template})"

if [ "${mode}" == "decode" ]; then
    decode
else
    encode
fi

result=$?
rm "${tmp}" "${tmp2}"
exit ${result}

To restore router login using this script you need to:

  • back up config before firmware update;
  • reset to hardware defaults after the update (if you tried importing the backed up settings and are unable to log in);
  • change login/password to the desired ones and back up the new config;
  • decode both configs with tlrecode.sh config.bin config.txt && tlrecode.sh config-new.bin config-new.txt;
  • update the old config, replacing lgn_ lines with the ones from the new config;
  • encode the updated config with tlrecode.sh -e config.txt config-updated.bin;
  • restore router settings using config-updated.bin.

After update process finishes you should be able to login with your desired credentials.