Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Split command before adding to argv #1725

Closed
wants to merge 4 commits into from

Conversation

qxxxb
Copy link

@qxxxb qxxxb commented Nov 23, 2020

Summary: Make gdb.attach work for kitty (and potentially other terminals?) by splitting command before adding it to argv.

Details

I noticed that using gdb.attach with kitty terminal results in:

Failed to launch child: /usr/bin/gdb -q  "./flag" 19523
With error: No such file or directory
Press Enter to exit.

Here was the test program I used:

from pwn import *

context.log_level = 'debug'
io = process('flag')
gdb.attach(io)

And here was the output when I ran the script:

$ python3 solve.py
<frozen importlib._bootstrap>:219: RuntimeWarning: greenlet.greenlet size changed, may indicate binary incompatibility. Expected 144 from C header, got 152 from PyObject
<frozen importlib._bootstrap>:219: RuntimeWarning: greenlet.greenlet size changed, may indicate binary incompatibility. Expected 144 from C header, got 152 from PyObject
<frozen importlib._bootstrap>:219: RuntimeWarning: greenlet.greenlet size changed, may indicate binary incompatibility. Expected 144 from C header, got 152 from PyObject
<frozen importlib._bootstrap>:219: RuntimeWarning: greenlet.greenlet size changed, may indicate binary incompatibility. Expected 144 from C header, got 152 from PyObject
[!] Could not find executable 'flag' in $PATH, using './flag' instead
[+] Starting local process './flag' argv=[b'flag'] : pid 19523
[*] running in new terminal: /usr/bin/gdb -q  "./flag" 19523
[DEBUG] Launching a new terminal: ['/usr/bin/x-terminal-emulator', '-e', '/usr/bin/gdb -q  "./flag" 19523']
[+] Waiting for debugger: Done

It seems like the issue is here:

[DEBUG] Launching a new terminal: ['/usr/bin/x-terminal-emulator', '-e', '/usr/bin/gdb -q  "./flag" 19523']

This should be:

[DEBUG] Launching a new terminal: ['/usr/bin/x-terminal-emulator', '-e', '/usr/bin/gdb', '-q', '"./flag"', '19523']

To resolve this I just split command on whitespaces before adding it to argv.

@zachriggle
Copy link
Member

zachriggle commented Nov 23, 2020

I would rather modify run_in_new_terminal to build up a list of args -- which are only joined at the end -- rather than using .split(), since the split() route breaks on any whitespace.

This should also help avoid cases where we're inadvertently introducing quotes into the arguments.

@qxxxb
Copy link
Author

qxxxb commented Nov 23, 2020

I tried using argv += command.split(" ", 1) to split on the first space, but for some reason this causes the terminal to exit immediately. I think it might be better to use shlex.split() which will preserve quotes.

@zachriggle
Copy link
Member

Rather than using shlex.split, we should just keep the arguments as an array and only join them at the end if needed.

We also have sh_string which does the "right" amount of quoting, for passing an actual shell (vs. shlex.split).

@qxxxb
Copy link
Author

qxxxb commented Nov 24, 2020

we should just keep the arguments as an array and only join them at the end if needed

Correct me if I'm misunderstanding, but that was what I tried to do that with argv += command.split(" ", 1) so that the command would look like

['/usr/bin/x-terminal-emulator', '-e', '/usr/local/bin/gdb', '-q  "./a.out" 21097 -x /tmp/pwnl5fthh5y.gdb']

But this results in the terminal closing immediately for some reason (I tested on kitty, termite, and xterm).

I'm trying to use sh_string right now, but it seems like there's a cyclic dependency between the misc and sh_string modules.

@qxxxb
Copy link
Author

qxxxb commented Nov 24, 2020

After looking at sh_string, I couldn't find any functionality similar to shlex.split. Would it be better to stick with shlex.split or would you prefer something else?

@zachriggle
Copy link
Member

sh_string will generate a safe string that can be passed to e.g. os.system() or anything else that is a compliant shell.

It operates on an array -- my suggestion is to keep all of the arguments as separate in an array, then only join them with sh_string when needed.

@qxxxb
Copy link
Author

qxxxb commented Nov 24, 2020

Sorry, I'm not sure I understand. For my example, if I do shlex.split(command) to separate all the arguments into an array, I get this, which seems to work on my terminal:

['/usr/bin/x-terminal-emulator', '-e', '/usr/local/bin/gdb', '-q', './a.out', '4524', '-x', '/tmp/pwnqla3yp2_.gdb']

How would I determine when/where I would need to join elements? Or do you mean that I should go through each element and run it through sh_string to be safe? For example argv = [sh_string(arg) for arg in argv]

@peace-maker
Copy link
Member

I think run_in_new_terminal should be called with an array of arguments instead of a single string and then apply sh_string to each argument.

e.g. don't join the arguments before calling the function and then try to split them again.

misc.run_in_new_terminal(' '.join(cmd))

- Revert run_in_new_terminal, add comment about potentially needing to
  split
@qxxxb
Copy link
Author

qxxxb commented Nov 24, 2020

I think run_in_new_terminal should be called with an array of arguments instead of a single string and then apply sh_string to each argument.

Thanks for the clarification. I changed the run_new_terminal calls to pass in an array of args and it seems to be working fine. The problem now is that there's a circular dependency between the misc and sh_string modules. Here's what happens when I add from pwnlib.util.sh_string import sh_string to misc.py

Traceback (most recent call last):
  File "solve.py", line 1, in <module>
    import pwn
  File "/home/plushie/Programs/archive/pwntools/pwn/__init__.py", line 4, in <module>
    from pwn.toplevel import *
  File "/home/plushie/Programs/archive/pwntools/pwn/toplevel.py", line 20, in <module>
    import pwnlib
  File "/home/plushie/Programs/archive/pwntools/pwnlib/__init__.py", line 43, in <module>
    importlib.import_module('.%s' % module, 'pwnlib')
  File "/usr/lib/python3.6/importlib/__init__.py", line 126, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "/home/plushie/Programs/archive/pwntools/pwnlib/asm.py", line 58, in <module>
    from pwnlib import shellcraft
  File "/home/plushie/Programs/archive/pwntools/pwnlib/shellcraft/__init__.py", line 11, in <module>
    from pwnlib import constants
  File "/home/plushie/Programs/archive/pwntools/pwnlib/constants/__init__.py", line 61, in <module>
    from pwnlib.util import safeeval
  File "/home/plushie/Programs/archive/pwntools/pwnlib/util/__init__.py", line 9, in <module>
    from pwnlib.util import misc
  File "/home/plushie/Programs/archive/pwntools/pwnlib/util/misc.py", line 19, in <module>
    from pwnlib.util.sh_string import sh_string
  File "/home/plushie/Programs/archive/pwntools/pwnlib/util/sh_string.py", line 251, in <module>
    from pwnlib.tubes.process import process
  File "/home/plushie/Programs/archive/pwntools/pwnlib/tubes/__init__.py", line 15, in <module>
    from pwnlib.tubes import process
  File "/home/plushie/Programs/archive/pwntools/pwnlib/tubes/process.py", line 30, in <module>
    from pwnlib.util.misc import parse_ldd_output
ImportError: cannot import name 'parse_ldd_output'

I can't seem to find an easy way to resolve this unfortunately

@Arusekk
Copy link
Member

Arusekk commented Nov 25, 2020

That's great! I hate dealing with strings and lexing/parsing/splitting/joining stuff where it would be possible to just use lists of them in the first place.

For the circular imports, you can always import whole modules, so a plain

from . import sh_string

and later

s = sh_string.sh_string(s)

would totally suffice IMO.

@Arusekk Arusekk mentioned this pull request Nov 25, 2020
@qxxxb
Copy link
Author

qxxxb commented Nov 25, 2020

For the circular imports, you can always import whole modules

Thanks for the suggestion, I tried that but unfortunately it didn't work :(

Traceback (most recent call last):
  File "simple.py", line 1, in <module>
    import pwn
  File "/home/plushie/Programs/archive/pwntools/pwn/__init__.py", line 4, in <module>
    from pwn.toplevel import *
  File "/home/plushie/Programs/archive/pwntools/pwn/toplevel.py", line 20, in <module>
    import pwnlib
  File "/home/plushie/Programs/archive/pwntools/pwnlib/__init__.py", line 43, in <module>
    importlib.import_module('.%s' % module, 'pwnlib')
  File "/usr/lib/python3.6/importlib/__init__.py", line 126, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "/home/plushie/Programs/archive/pwntools/pwnlib/asm.py", line 58, in <module>
    from pwnlib import shellcraft
  File "/home/plushie/Programs/archive/pwntools/pwnlib/shellcraft/__init__.py", line 11, in <module>
    from pwnlib import constants
  File "/home/plushie/Programs/archive/pwntools/pwnlib/constants/__init__.py", line 61, in <module>
    from pwnlib.util import safeeval
  File "/home/plushie/Programs/archive/pwntools/pwnlib/util/__init__.py", line 9, in <module>
    from pwnlib.util import misc
  File "/home/plushie/Programs/archive/pwntools/pwnlib/util/misc.py", line 19, in <module>
    from . import sh_string
  File "/home/plushie/Programs/archive/pwntools/pwnlib/util/sh_string.py", line 251, in <module>
    from pwnlib.tubes.process import process
  File "/home/plushie/Programs/archive/pwntools/pwnlib/tubes/__init__.py", line 15, in <module>
    from pwnlib.tubes import process
  File "/home/plushie/Programs/archive/pwntools/pwnlib/tubes/process.py", line 30, in <module>
    from pwnlib.util.misc import parse_ldd_output
ImportError: cannot import name 'parse_ldd_output'

@Arusekk Arusekk linked an issue Dec 1, 2020 that may be closed by this pull request
Arusekk added a commit to Arusekk/pwntools that referenced this pull request Dec 29, 2020
@Arusekk
Copy link
Member

Arusekk commented Apr 9, 2021

Superseded by #1846

@Arusekk Arusekk closed this Apr 9, 2021
Arusekk added a commit that referenced this pull request Apr 29, 2021
* Cleanup unconditional imports

This may aid #1725

* Import explicitly

* Add good ol' SIGALRM-based debugs for tests

* Fix py2 GDB tests somehow

XXX: see why this matters

* Update CHANGELOG.md
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants