Gallopsled / pwntools

CTF framework and exploit development library
http://pwntools.com
Other
11.69k stars 1.67k forks source link

gdb.debug not working with fish shell #2377

Closed k4lizen closed 2 months ago

k4lizen commented 3 months ago
from pwn import *

p = gdb.debug(['./start'])

p.interactive()
p.close()

Hangs with:

[+] Starting local process '/usr/bin/gdbserver': pid 5956

Regardless of if tmux is active. Tried on both kitty terminal and gnome terminal. Ctrl+C gives:

Traceback (most recent call last):
  File "/home/maker/ctf/pwnabletw/start/mvp.py", line 3, in <module>
    p = gdb.debug(['./start'])
        ^^^^^^^^^^^^^^^^^^^^^^
  File "/home/maker/.local/lib/python3.11/site-packages/pwnlib/context/__init__.py", line 1581, in setter
    return function(*a, **kw)
           ^^^^^^^^^^^^^^^^^^
  File "/home/maker/.local/lib/python3.11/site-packages/pwnlib/gdb.py", line 574, in debug
    port = _gdbserver_port(gdbserver, ssh)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/maker/.local/lib/python3.11/site-packages/pwnlib/gdb.py", line 318, in _gdbserver_port
    process_created = gdbserver.recvline()
                      ^^^^^^^^^^^^^^^^^^^^
  File "/home/maker/.local/lib/python3.11/site-packages/pwnlib/tubes/tube.py", line 498, in recvline
    return self.recvuntil(self.newline, drop = not keepends, timeout = timeout)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/maker/.local/lib/python3.11/site-packages/pwnlib/tubes/tube.py", line 341, in recvuntil
    res = self.recv(timeout=self.timeout)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/maker/.local/lib/python3.11/site-packages/pwnlib/tubes/tube.py", line 106, in recv
    return self._recv(numb, timeout) or b''
           ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/maker/.local/lib/python3.11/site-packages/pwnlib/tubes/tube.py", line 176, in _recv
    if not self.buffer and not self._fillbuffer(timeout):
                               ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/maker/.local/lib/python3.11/site-packages/pwnlib/tubes/tube.py", line 155, in _fillbuffer
    data = self.recv_raw(self.buffer.get_fill_size())
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/maker/.local/lib/python3.11/site-packages/pwnlib/tubes/process.py", line 673, in recv_raw
    if not self.can_recv_raw(self.timeout):
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/maker/.local/lib/python3.11/site-packages/pwnlib/tubes/process.py", line 717, in can_recv_raw
    return select.select([self.proc.stdout], [], [], timeout) == ([self.proc.stdout], [], [])
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
KeyboardInterrupt
[*] Stopped process './start' (pid 5956)
peace-maker commented 3 months ago

This is due to fish forking multiple times when running fish -c somescript and we're returning the pid of the original fish process instead of the forked one which actually runs the script.

I don't use fish, so you're welcome to propose a pull request for the misc.run_in_new_terminal function to return the correct pid around: https://github.com/Gallopsled/pwntools/blob/75cc3c3d5cd1e4d03dee1cf31ba67dd8139b941c/pwnlib/util/misc.py#L305

iacore commented 3 months ago

This is due to fish forking multiple times when running fish -c somescript and we're returning the pid of the original fish process instead of the forked one which actually runs the script.

I only see one process here.

image

peace-maker commented 3 months ago

But your screenshot shows 3 processes?

When I run fish -c "sleep 999" I see the fish process staying alive instead of being replaced by the sleep binary

# fish -c "sleep 999"
# other terminal:
pstree -p `whoami` | grep sleep
zsh(367594)---fish(368747)---sleep(368753)

compared to bash or zsh:

# bash -c "sleep 999"
# other terminal:
pstree -p `whoami` | grep sleep
zsh(367594)---sleep(368783)

I've asked about this in fish's matrix channel and it's expected behavior - BUT I've confused myself with terminal emulators and shells. This issue isn't in pwntools since we don't run shell -c command at all but an instance of #2321 which boils down to a bug in gdbserver. pwntools 4.13.0 would print a more helpful error message suggesting you to set the SHELL environment variable to some other shell than fish like bash.

So try doing p = gdb.debug(['./start'], env={"SHELL": "/bin/bash"}) instead.

peace-maker commented 3 months ago

Looking a bit more: gdbserver supports a --no-startup-with-shell argument which might help too.

  --startup-with-shell
                        Start PROG using a shell.  I.e., execs a shell that
                        then execs PROG.  (default)
  --no-startup-with-shell
                        Exec PROG directly instead of using a shell.
                        Disables argument globbing and variable substitution
                        on UNIX-like systems.

@Arusekk can you think of why we don't start the program directly but have it go through a shell in gdb.debug? We don't do that when starting directly using process() and I don't think we support the shell=True argument when debugging?

~@iacore~ @k4lizen can you test if adding that flag fixes your issue please? in gdb.py in _gdbserver_args(), can you add the argument? Change that line to

gdbserver_args = [gdbserver, '--multi', '--no-startup-with-shell']

in your /home/maker/.local/lib/python3.11/site-packages/pwnlib/gdb.py

k4lizen commented 3 months ago

So try doing p = gdb.debug(['./start'], env={"SHELL": "/bin/bash"}) instead.

This does seem to work.

gdbserver_args = [gdbserver, '--multi', '--no-startup-with-shell']

This also seems to work.

A bit off topic but im seeing some weird behaviour with the SHELL env variable, like doing fish; bash; fish; points to bash sometimes. Or setting the variable to fish and going into zsh still has it pointing to fish.

But anyways yeah, the proposed fixes seem good (and works with my template script with tmux settings etc).

iacore commented 3 months ago

But your screenshot shows 3 processes?

I run fish -c xxx in fish shell (I use fish already).

When I run fish -c "sleep 999" I see the fish process staying alive instead of being replaced by the sleep binary

This is expected. -c runs the script in script mode, not execv. You are likely looking for fish -c 'exec sleep 999', which calls execv.

compared to bash or zsh: ...

It's not mentioned in sh, so I guess it's not even part of POSIX. fish is also not a POSIX-compliant shell, so..

peace-maker commented 3 months ago

Yes, faho mentioned this too and it seems the other shells are doing it wrong, but as I said, this is out of our control and done inside gdbserver.

Arusekk commented 3 months ago

No idea either, I guess nobody thought about it when it worked correctly (before fish even existed), and this is most likely GDB not supporting fish, not us not thinking :wink:

peace-maker commented 2 months ago

We're passing --no-startup-with-shell now, but the --wrapper argument appears to require a shell. So it's only passed when env is None and argv0 is unchanged. This will make debugging with a forking shell work most of the time, hopefully.