Skip to content

Commit

Permalink
Fix waiting for gdb under WSL2 (#2470)
Browse files Browse the repository at this point in the history
* Fix returning wrong pid in WSL for run_in_new_terminal

We run cmd.exe which and other Windows processes before going back into WSL. The returned pid would be the one for the ephemeral cmd.exe process instead of the real command we wanted to launch.

I don't know how to properly trace the execution and get the command pid, so scan for newer pids matching the command for a while instead as a workaround.

We wrap the command in a script so psutil.Process.exe() returns the path to the shell instead of the real command. Look at psutil.Process.cmdline() too which contains the real program running right now.

* Make `proc.wait_for_debugger` return the debugger pid

If we fail to get the pid when launching gdb, grab it after tracing the debugger at least. gdb won't be closed when the exploit exits but at least we have the correct pid.

* Fix working directory of run_in_new_terminal in WSL

The process' cwd would be %WINDIR% due to cmd.exe not supporting WSL paths.

* Update CHANGELOG
  • Loading branch information
peace-maker authored Sep 29, 2024
1 parent 69ab205 commit fa5a288
Show file tree
Hide file tree
Showing 4 changed files with 33 additions and 4 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,12 +78,14 @@ The table below shows which release corresponds to each branch, and what date th
- [#2457][2457] Catch exception of non-ELF files in checksec.
- [#2444][2444] Add `ELF.close()` to release resources
- [#2413][2413] libcdb: improve the search speed of `search_by_symbol_offsets` in local libc-database
- [#2470][2470] Fix waiting for gdb under WSL2

[2471]: https://github.com/Gallopsled/pwntools/pull/2471
[2358]: https://github.com/Gallopsled/pwntools/pull/2358
[2457]: https://github.com/Gallopsled/pwntools/pull/2457
[2444]: https://github.com/Gallopsled/pwntools/pull/2444
[2413]: https://github.com/Gallopsled/pwntools/pull/2413
[2470]: https://github.com/Gallopsled/pwntools/pull/2470

## 4.14.0 (`beta`)

Expand Down
2 changes: 1 addition & 1 deletion pwnlib/gdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -1251,7 +1251,7 @@ def preexec_fn():
gdb_pid = misc.run_in_new_terminal(cmd, preexec_fn = preexec_fn)

if pid and context.native:
proc.wait_for_debugger(pid, gdb_pid)
gdb_pid = proc.wait_for_debugger(pid, gdb_pid)

if not api:
return gdb_pid
Expand Down
25 changes: 24 additions & 1 deletion pwnlib/util/misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@
import sys
import tempfile
import inspect
import time
import types

from pwnlib import atexit
from pwnlib.context import context
from pwnlib.log import getLogger
from pwnlib.timeout import Timeout
from pwnlib.util import fiddling
from pwnlib.util import lists
from pwnlib.util import packing
Expand Down Expand Up @@ -439,6 +441,11 @@ def run_in_new_terminal(command, terminal=None, args=None, kill_at_exit=True, pr
tmp.flush()
os.chmod(tmp.name, 0o700)
argv = [which(terminal), tmp.name]
# cmd.exe does not support WSL UNC paths as working directory
# so it gets reset to %WINDIR% before starting wsl again.
# Set the working directory correctly in WSL.
elif terminal == 'cmd.exe':
argv[-1] = "cd '{}' && {}".format(os.getcwd(), argv[-1])

log.debug("Launching a new terminal: %r" % argv)

Expand Down Expand Up @@ -472,10 +479,26 @@ def run_in_new_terminal(command, terminal=None, args=None, kill_at_exit=True, pr
kittyid = None
if kittyid is None:
log.error("Could not parse kitty window ID from output (%r)", out)
elif terminal == 'cmd.exe':
# p.pid is cmd.exe's pid instead of the WSL process we want to start eventually.
# I don't know how to trace the execution through Windows and back into the WSL2 VM.
# Do a best guess by waiting for a new process matching the command to be run.
# Otherwise it's better to return nothing instead of a know wrong pid.
from pwnlib.util.proc import pid_by_name
pid = None
ran_program = command.split(' ')[0] if isinstance(command, six.string_types) else command[0]
t = Timeout()
with t.countdown(timeout=5):
while t.timeout:
new_pid = pid_by_name(ran_program)
if new_pid and new_pid[0] > p.pid:
pid = new_pid[0]
break
time.sleep(0.01)
else:
pid = p.pid

if kill_at_exit:
if kill_at_exit and pid:
def kill():
try:
if terminal == 'qdbus':
Expand Down
8 changes: 6 additions & 2 deletions pwnlib/util/proc.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ def match(p):
try:
if p.exe() == name:
return True
if p.cmdline()[0] == name:
return True
except Exception:
pass
return False
Expand Down Expand Up @@ -398,7 +400,7 @@ def wait_for_debugger(pid, debugger_pid=None):
pid (int): PID of the process.
Returns:
None
The PID of the debugger that attached to the process.
"""
t = Timeout()
with t.countdown(timeout=15):
Expand All @@ -416,9 +418,11 @@ def wait_for_debugger(pid, debugger_pid=None):
else:
time.sleep(0.01)

if tracer(pid):
tracer_pid = tracer(pid)
if tracer_pid:
l.success()
elif debugger_pid == 0:
l.failure("debugger exited! (maybe check /proc/sys/kernel/yama/ptrace_scope)")
else:
l.failure('Debugger did not attach to pid %d within 15 seconds', pid)
return tracer_pid

0 comments on commit fa5a288

Please sign in to comment.