1010The order of preference is:
1111- ``windbgx``
1212- ``windbg``
13+ - ``x64dbg``
1314
1415If automatic lookup fails, you can manually set :attr:`.context.debugger` to
1516the debugger of your choice and provide the path to the debugger binary
16- using :attr:`.context.windbgx_binary` or :attr:`.context.windbg_binary`.
17+ using :attr:`.context.windbgx_binary`, :attr:`.context.windbg_binary`, or
18+ :attr:`.context.x64dbg_binary`.
1719
1820Useful Functions
1921----------------
7274import atexit
7375import os
7476import signal
75-
7677import subprocess
7778import tempfile
79+ from pathlib import Path
7880
7981from pwnlib import tubes
8082from pwnlib .context import LocalContext
@@ -125,44 +127,52 @@ def debug(args, dbgscript=None, exe=None, env=None, creationflags=0, **kwargs):
125127 log .warn_once ("Skipping debugger since context.noptrace==True" )
126128 return tubes .process .process (args , executable = exe , env = env , creationflags = creationflags )
127129
130+ debugger , _ = binary ()
128131 dbgscript = dbgscript or ''
129132 if isinstance (dbgscript , str ):
130133 dbgscript = dbgscript .split ('\n ' )
131134 # resume main thread
132- dbgscript = ['~0m' ] + dbgscript
135+ if debugger in ('windbg' , 'windbgx' ):
136+ dbgscript = ['~0m' ] + dbgscript
137+ elif debugger == 'x64dbg' :
138+ dbgscript = ['threadresumeall' ] + dbgscript
139+ log .info_once ('x64dbg: To resume the main thread, you need to use the "threadresumeall" command manually before version 2025.08.19.' )
133140 creationflags |= CREATE_SUSPENDED
134141 io = tubes .process .process (args , executable = exe , env = env , creationflags = creationflags )
135142 attach (target = io , dbgscript = dbgscript , ** kwargs )
136143
137144 return io
138145
139146def binary ():
140- """binary() -> str
147+ """binary() -> ( str, str)
141148
142149 Returns the path to the debugger binary depending on the context.
143150 :attr:`.context.debugger` is used to determine which debugger to use.
144151
145152 Returns:
153+ str: Name of the selected debugger.
146154 str: Path to the appropriate ``windbg`` binary to use.
147155 """
148156 if context .debugger == 'auto' :
149157 for debugger in context .debugger_choices :
158+ if debugger == 'auto' :
159+ continue
150160 with context .local (debugger = debugger , log_level = 'critical' ):
151161 try :
152162 return binary ()
153163 except Exception :
154164 pass
155165 else :
156166 log .error ('No debugger found. Please set context.debugger to one of: %s\n '
157- 'You might have to specify the path to the debugger binary with context.windbg_binary or context.windbgx_binary.' ,
167+ 'You might have to specify the path to the debugger binary with context.x64dbg_binary, context. windbg_binary or context.windbgx_binary.' ,
158168 ', ' .join (context .debugger_choices ))
159169
160- if context .debugger == 'windbg' :
170+ elif context .debugger == 'windbg' :
161171 if context .windbg_binary :
162172 windbg = misc .which (context .windbg_binary )
163173 if not windbg :
164174 log .warn_once ('Path to WinDbg binary `{}` not found' .format (context .windbg_binary ))
165- return windbg
175+ return context . debugger , windbg
166176
167177 windbg = misc .which ('windbg.exe' )
168178 if not windbg and os .environ .get ('ProgramFiles(x86)' ):
@@ -176,29 +186,74 @@ def binary():
176186 windbg = os .path .join (os .environ .get ('ProgramFiles(x86)' ), 'Windows Kits' , '10' , 'Debuggers' , arch_str , 'windbg.exe' )
177187 if not windbg or not os .path .exists (windbg ):
178188 log .error ('windbg is not installed or in system PATH. You can set context.windbg_binary to specify the path manually.' )
179- return windbg
189+ return context . debugger , windbg
180190
181- if context .debugger == 'windbgx' :
191+ elif context .debugger == 'windbgx' :
182192 if context .windbgx_binary :
183193 windbg = misc .which (context .windbgx_binary )
184194 if not windbg :
185195 log .warn_once ('Path to WinDbgx binary `{}` not found' .format (context .windbgx_binary ))
186- return windbg
196+ return context . debugger , windbg
187197
188198 windbg = misc .which ('windbgx.exe' )
189199 if not windbg and os .environ .get ('LocalAppData' ):
190200 windbg = os .path .join (os .environ .get ('LocalAppData' ), 'Microsoft' , 'WindowsApps' , 'WinDbgX.exe' )
191201 if not windbg or not os .path .exists (windbg ):
192202 log .error ('windbgx is not installed or in system PATH. You can set context.windbgx_binary to specify the path manually.' )
193- return windbg
203+ return context .debugger , windbg
204+
205+ elif context .debugger == 'x64dbg' :
206+ return context .debugger , _lookup_x64dbg ()
194207
195208 log .error ('Invalid debugger selection: %s' , context .debugger )
196209
210+ def _lookup_x64dbg ():
211+ def _select_arch_binary (path ):
212+ # Select the appropriate x64dbg binary based on the architecture directly
213+ # instead of the x96dbg.exe selector binary.
214+ # The x96dbg.exe proxy launches the correct one but our `wait_for_debugger`
215+ # function doesn't follow that and reports the proxy binary exiting early.
216+ base_path = Path (path ).resolve ().parent
217+ if base_path .name in ('x32' , 'x64' ):
218+ base_path = base_path .parent
219+ if context .arch == 'i386' :
220+ return base_path / 'x32' / 'x32dbg.exe'
221+ elif context .arch == 'amd64' :
222+ return base_path / 'x64' / 'x64dbg.exe'
223+ else :
224+ log .error ('Unsupported architecture for x64dbg: %s' , context .arch )
225+ if context .x64dbg_binary :
226+ x64dbg = misc .which (context .x64dbg_binary )
227+ if not x64dbg :
228+ log .warn_once ('Path to x64dbg binary `{}` not found' .format (context .x64dbg_binary ))
229+ return _select_arch_binary (x64dbg )
230+
231+ x64dbg = misc .which ('x96dbg.exe' )
232+ if x64dbg :
233+ return _select_arch_binary (x64dbg )
234+
235+ # See if the "Debug with x64dbg" shell extension is installed
236+ try :
237+ import winreg # pylint: disable=import-error winreg is only available on Windows
238+ with winreg .OpenKey (winreg .HKEY_CLASSES_ROOT , r'exefile\shell\Debug with x64dbg\Command' ) as key :
239+ regcmd = winreg .QueryValueEx (key , None )
240+ # ('"C:\\Users\\User\\Downloads\\x64dbg\\bin\\x96dbg.exe" "%1"', 2)
241+ if regcmd [1 ] != winreg .REG_EXPAND_SZ :
242+ log .error ('x64dbg registry key is not REG_EXPAND_SZ' )
243+ command = regcmd [0 ].split ('"' )[1 ]
244+ if not os .path .exists (command ):
245+ log .error ('x64dbg path from registry does not exist' )
246+ return _select_arch_binary (command )
247+ except FileNotFoundError :
248+ pass
249+
250+ log .error ('x64dbg is not installed or in system PATH' )
251+
197252@LocalContext
198253def attach (target , dbgscript = None , dbg_args = []):
199254 """attach(target, dbgscript=None, dbg_args=[]) -> int
200255
201- Attach to a running process with WinDbg.
256+ Attach to a running process with WinDbg or x64dbg .
202257
203258 Arguments:
204259 target(int, str, process): Process to attach to.
@@ -262,31 +317,41 @@ def attach(target, dbgscript=None, dbg_args=[]):
262317 if not pid :
263318 log .error ('could not find target process' )
264319
265- cmd = [binary ()]
320+ debugger , debugger_path = binary ()
321+ cmd = [debugger_path ]
266322 if dbg_args :
267323 cmd .extend (dbg_args )
268-
324+
269325 cmd .extend (['-p' , str (pid )])
270326
271327 dbgscript = dbgscript or ''
272328 if isinstance (dbgscript , str ):
273329 dbgscript = dbgscript .split ('\n ' )
274- with tempfile .NamedTemporaryFile (mode = 'w+' , delete = False , suffix = '.dbg' ) as tmp :
275- tmp .write ('\n ' .join (script .strip () for script in dbgscript if script .strip ()))
276- tmp .flush ()
277- dbgscript_file = tmp .name
278-
330+ dbgscript_file = None
279331 if dbgscript :
280- cmd .extend (['-c' , '$<{}' .format (dbgscript_file )])
332+ with tempfile .NamedTemporaryFile (mode = 'w+' , delete = False , suffix = '.dbg' ) as tmp :
333+ tmp .write ('\n ' .join (script .strip () for script in dbgscript if script .strip ()))
334+ tmp .flush ()
335+ dbgscript_file = tmp .name
336+
337+ if debugger in ('windbg' , 'windbgx' ):
338+ cmd .extend (['-c' , '$<{}' .format (dbgscript_file )])
339+ # x64dbg got support to run commands on startup in version 2025.08.19.
340+ # But absolute paths were not supported until version 2026.05.27.
341+ elif debugger == 'x64dbg' :
342+ cmd .extend (['-cf' , dbgscript_file ])
343+ else :
344+ log .warn_once ('dbgscript is not supported for %s' , debugger )
281345
282- log .info ("Launching a new process: %r" % cmd )
346+ log .info ("Launching a new process: %r" , cmd )
283347
284348 io = subprocess .Popen (cmd )
285349 debugger_pid = io .pid
286350
287351 def kill ():
288352 try :
289- os .unlink (dbgscript_file )
353+ if dbgscript_file is not None :
354+ os .unlink (dbgscript_file )
290355 os .kill (debugger_pid , signal .SIGTERM )
291356 except OSError :
292357 pass
0 commit comments