Gallopsled / pwntools

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

Add basic support to debug processes on Windows #2327

Closed peace-maker closed 3 months ago

peace-maker commented 6 months ago

Currently only windbg.debug() and windbg.attach() are implemented, which open a WinDbg instance and attach to the process.

peace-maker commented 6 months ago

Can you test and review again please @masthoon?

masthoon commented 6 months ago

I added some comments. I can't think of a way to wait for the debugger without using Windows specific code.

There is a issue with CREATE_SUSPENDED process in case the thread is not resumed (for example: using invalid windbg command line argument or if windbg is not installed). On exit, the tube atexit callback will call process.close that first calls poll() then closes the file descriptors and finally stops the process. The problem is that the stdout fd.close is blocking (I don't know why) thus Python stops responding (even to ctrl+c) and it must be killed manually using Task Manager. Reproduce with:

from pwn import *
process(['cmd.exe'], creationflags=4)
exit(0)

One way to fix this issue is to first terminate the process then close the file descriptors in process.close:

# pwntools\pwnlib\tubes\process.py
    def close(self):
        if self.proc is None:
            return

        # First check if we are already dead
        self.poll()
        # Terminate before closing fd
        if not self._stop_noticed:
            try:
                self.proc.kill()
                self.proc.wait()
                self._stop_noticed = time.time()
                self.info('Stopped process %r (pid %i)' % (self.program, self.pid))
            except OSError:
                pass

        # close file descriptors
        for fd in [self.proc.stdin, self.proc.stdout, self.proc.stderr]:
            if fd is not None:
                try:
                    fd.close()
                except IOError as e:
                    if e.errno != errno.EPIPE and e.errno != errno.EINVAL:
                        raise

Also resuming the process would work. I'm not sure if changing the cleanup order in 'process.close' would cause issues on other operating systems.

peace-maker commented 6 months ago

Maybe we can use psutil.Process.resume() to resume all threads in the process atexit, but that seems finicky.

Changing the order of killing and closing file descriptiors doesn't seem to matter. The file descriptors were closed in #576 and the testcase of starting and killing lots of processes still doesn't leave dangling file descriptors around when moving the closing after the killing of the process. So I think we can switch up the order and be fine on Linux too.