Skip to content

Instantly share code, notes, and snippets.

@adripo
Forked from shreve/tp.md
Created March 30, 2024 04:47
Show Gist options
  • Save adripo/a3a2ded45cf82bdb06c49004a594114c to your computer and use it in GitHub Desktop.
Save adripo/a3a2ded45cf82bdb06c49004a594114c to your computer and use it in GitHub Desktop.
TP-Link Router Config Decrypt

TP-Link Router Config

Update 2021-04-29: This may still work for you if you've got an old TP-Link router, but this is not maintained and doesn't work with newer models. If you've got a newer router, other projects like tpconf_bin_xml will likely work better for you.

TP-Link allows you to backup and restore your router's config file. For some reason, they decided to encrypt these backups so you cannot modify them. I think this is dumb. This script lets you decrypt and re-encrypt your config files so you can modify them as you see fit.

I use this to modify my reserved addresses list because editing them through the web interface is terribly slow and cumbersome.

  1. Go to the router and download the config file from the "Backup & Restore" section of "System Tools".
  2. Run ruby tp.rb config.bin
  3. Edit the created config file config.cfg as you please.
  4. Run ruby tp.rb config.cfg
  5. Return to the "Backup & Restore" section and upload your modified config.bin file.

This work is based on the hack by Matteo Croce here. Thank you for doing the hard part.

#!/usr/bin/env ruby
require 'openssl'
infile = ARGV[0]
content = File.read(infile)
mode = infile.end_with?('.bin') ? :decrypt : :encrypt
# Configure the cipher with what we know about the TP-Link encryption
cipher = OpenSSL::Cipher::DES.new(:ECB)
cipher.key = ["478DA50BF9E3D2CF"].pack("H*")
cipher.padding = 0
case mode
when :decrypt
cipher.decrypt
plaintext = cipher.update(content) + cipher.final
# First 16 bytes are checksum of content
reported_checksum = plaintext[0...16]
plaintext = plaintext[16..-1]
# Trim empty bytes used to pad out the plaintext to be multiple of 8
plaintext = plaintext[0...-1] while plaintext.end_with?("\x0")
checksum = OpenSSL::Digest::MD5.digest(plaintext)
if reported_checksum != checksum
$stderr.puts "Checksums do not match"
$stderr.puts "reported: " << reported_checksum.bytes.map { |b| b.to_s(16) }.join
$stderr.puts "actual: " << checksum.bytes.map { |b| b.to_s(16) }.join
end
File.write(infile.sub('.bin', '.cfg'), plaintext)
when :encrypt
# The body is prefixed with the 16 byte md5 checksum of the contents
checksum = OpenSSL::Digest::MD5.digest(content)
body = checksum + content
# The body needs to be padded out with zero bytes until the byte
# length is a multiple of 8.
remaining = 8 - (body.length % 8)
pad = remaining == 8 ? '' : ("\x0" * remaining)
# Set the cipher to encryption mode and perform the action
cipher.encrypt
ciphertext = cipher.update(body + pad) + cipher.final
File.write(infile.sub('.cfg', '.bin'), ciphertext)
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment