internlm/SWE-Fixer-Train-110K huggingface dataset:
Repository: Gallopsled/pwntools

Issue:
ELF.functions not updated after ELF.address change
When setting a new base address using `ELF.address` the `ELF.functions` list isn't updated to reflect that change opposed to what [the documentation is saying](http://docs.pwntools.com/en/stable/elf/elf.html#pwnlib.elf.elf.ELF.address).

```python
from pwn import *
e = ELF('./vuln')
print(hex(e.symbols.main)) # 0x8048a0f
print(hex(e.functions.main.address)) # 0x8048a0f
e.address = 0x1234
print(hex(e.symbols.main)) # 0x1c43
print(hex(e.functions.main.address)) # 0x8048a0f

print(e.functions.main.disasm())
[ERROR] Address 0x8048a0f is not contained in ELF('./vuln')
```


Discussion:
Thanks @peace-maker, would you mind submitting a Pull Request for this?

The way we update everything else is pretty straightforward: https://github.com/Gallopsled/pwntools/blob/dev/pwnlib/elf/elf.py#L587-L606

It should probably be a one-line change

Resolving patch:
diff --git a/pwnlib/elf/elf.py b/pwnlib/elf/elf.py
--- a/pwnlib/elf/elf.py
+++ b/pwnlib/elf/elf.py
@@ -591,6 +591,8 @@ def address(self, new):
         self.symbols = dotdict({k:update(v) for k,v in self.symbols.items()})
         self.plt     = dotdict({k:update(v) for k,v in self.plt.items()})
         self.got     = dotdict({k:update(v) for k,v in self.got.items()})
+        for f in self.functions.values():
+            f.address += delta
 
         # Update our view of memory
         memory = intervaltree.IntervalTree()


Test patch:
Repository: Gallopsled/pwntools

Issue:
Logging modules should attempt to convert log statement to text type, if it is bytes
On Python3, we occasionally pass in bytes objects to the logging mechanism.  We should opportunistically convert these to text strings, since the logs are intended to be text.

```
>>> info('asdf')
[*] asdf
>>> info(b'asdf')
[*] b'asdf' # <--- Should get auto-converted instead of repr()'ing
```


Discussion:
This could be unified with what happens in `tube.send` and `tube._fillbuffer`.

Resolving patch:
diff --git a/pwnlib/log.py b/pwnlib/log.py
--- a/pwnlib/log.py
+++ b/pwnlib/log.py
@@ -98,6 +98,7 @@
 import os
 import random
 import re
+import six
 import sys
 import threading
 import time
@@ -183,6 +184,9 @@ def __init__(self, logger, msg, status, level, args, kwargs):
         self.last_status = 0
 
     def _log(self, status, args, kwargs, msgtype):
+        # Logs are strings, not bytes.  Handle Python3 bytes() objects.
+        status = six.ensure_text(status)
+
         # this progress logger is stopped, so don't generate any more records
         if self._stopped:
             return
@@ -284,6 +288,9 @@ def _getlevel(self, levelString):
         return logging._levelNames[levelString.upper()]
 
     def _log(self, level, msg, args, kwargs, msgtype, progress = None):
+        # Logs are strings, not bytes.  Handle Python3 bytes() objects.
+        msg = six.ensure_text(msg)
+
         extra = kwargs.get('extra', {})
         extra.setdefault('pwnlib_msgtype', msgtype)
         extra.setdefault('pwnlib_progress', progress)


Test patch:
Repository: Gallopsled/pwntools

Issue:
fit() fails when keys are not ordered
This results in what appears to be an infinite loop.

```
from pwn import *

fit({
	0x61616168: 1,
	0x61616167: 2
})
```

This should be valid since these correspond to consecutive cyclic values:

```
>>> pack(0x61616167)
'gaaa'
>>> pack(0x61616168)
'haaa'
```

These also work individually, and do not overlap:

```
>>> fit({
... 0x61616167: 2,
... })
'aaaabaaacaaadaaaeaaafaaa\x02\x00\x00\x00'
>>> fit({
... 0x61616168: 1,
... })
'aaaabaaacaaadaaaeaaafaaagaaa\x01\x00\x00\x00'
```


Discussion:
I can confirm this works as expected on 3.9.3:

```
>>> pwnlib.version
'3.9.3'
>>> fit({
... 0x61616168: 1,
... 0x61616167: 2
... })
'aaaabaaacaaadaaaeaaafaaa\x02\x00\x00\x00\x01\x00\x00\x00'
```
I've confirmed this comes down to ordering of the fields.  On `dev`, I see:

```
>>> fit({0x61616167: 'a', 0x61616168: 'b'})
b'aaaabaaacaaadaaaeaaafaaaaaaab'
>>> fit({0x61616168: 'a', 0x61616167: 'b'})
^C (infinite loop)
```

Resolving patch:
diff --git a/pwnlib/util/packing.py b/pwnlib/util/packing.py
--- a/pwnlib/util/packing.py
+++ b/pwnlib/util/packing.py
@@ -33,6 +33,7 @@
 from __future__ import absolute_import
 from __future__ import division
 
+import collections
 import six
 import struct
 import sys
@@ -548,6 +549,7 @@ def _flat(args, preprocessor, packer, filler):
         elif isinstance(arg, (list, tuple)):
             val = _flat(arg, preprocessor, packer, filler)
         elif isinstance(arg, dict):
+            arg = collections.OrderedDict(((k,v) for k,v in sorted(arg.items())))
             filler, val = _fit(arg, preprocessor, packer, filler)
         elif isinstance(arg, bytes):
             val = arg
@@ -614,6 +616,8 @@ def flat(*args, **kwargs):
       sign (str): Signedness of the converted integer (False/True)
 
     Examples:
+
+      >>> context.clear()
       >>> flat(1, "test", [[["AB"]*2]*3], endianness = 'little', word_size = 16, sign = False)
       b'\x01\x00testABABABABABAB'
       >>> flat([1, [2, 3]], preprocessor = lambda x: str(x+1))
@@ -633,7 +637,17 @@ def flat(*args, **kwargs):
       b'aaaaX'
       >>> flat({4: {0: 'X', 4: 'Y'}})
       b'aaaaXaaaY'
-
+      >>> flat({0x61616161:'x', 0x61616162:'y'})
+      b'xaaay'
+      >>> flat({0x61616162:'y', 0x61616161:'x'})
+      b'xaaay'
+      >>> fit({
+      ...     0x61616161: 'a',
+      ...     1: 'b',
+      ...     0x61616161+2: 'c',
+      ...     3: 'd',
+      ... })
+      b'abadbaaac'
     """
     # HACK: To avoid circular imports we need to delay the import of `cyclic`
     from pwnlib.util import cyclic


Test patch:
Repository: Gallopsled/pwntools

Issue:
Python3: process() dumps entire environment when `log_level='debug'`
When using DEBUG log level, the entire environment is printed even when it is unmodified.

## Bug

It should only be printed if the environment has been modified.  

```py
        if self.isEnabledFor(logging.DEBUG):
            if self.argv != [self.executable]: message += ' argv=%r ' % self.argv
            if self.env  != os.environ:        message += ' env=%r ' % self.env
```

## Repro

This occurs on Python3 on `dev`, e.g.

```py
context.log_level = 'debug'
process('sh')
```

Should not print out the entire environment, but it does.  On python2, it works correctly, and only prints:

```
[x] Starting local process '/bin/sh' argv=['sh']
[+] Starting local process '/bin/sh' argv=['sh'] : pid 50012
```


Discussion:
It looks like we do a transformation to `bytes` in `process._validate` which makes the following statement to be always `True`:

```py
            if self.env  != os.environ:        message += ' env=%r ' % self.env
```

Resolving patch:
diff --git a/pwnlib/tubes/process.py b/pwnlib/tubes/process.py
--- a/pwnlib/tubes/process.py
+++ b/pwnlib/tubes/process.py
@@ -242,6 +242,9 @@ def __init__(self, argv = None,
         #: :class:`subprocess.Popen` object that backs this process
         self.proc = None
 
+        # We need to keep a copy of the un-_validated environment for printing
+        original_env = env
+
         if shell:
             executable_val, argv_val, env_val = executable, argv, env
         else:
@@ -298,7 +301,7 @@ def __init__(self, argv = None,
 
         if self.isEnabledFor(logging.DEBUG):
             if argv != [self.executable]: message += ' argv=%r ' % self.argv
-            if env not in (os.environ, None):  message += ' env=%r ' % self.env
+            if original_env not in (os.environ, None):  message += ' env=%r ' % self.env
 
         with self.progress(message) as p:
 


Test patch:
Repository: Gallopsled/pwntools

Issue:
ssh.process(..., timeout=) does not work as expected
The following will hang forever:

```
>>> io = shell.process('cat', timeout=1)
>>> io.recvline()
```

Note that passing `timeout=` directly to `recvline` works.


Discussion:

Resolving patch:
diff --git a/pwnlib/tubes/ssh.py b/pwnlib/tubes/ssh.py
--- a/pwnlib/tubes/ssh.py
+++ b/pwnlib/tubes/ssh.py
@@ -832,6 +832,10 @@ def process(self, argv=None, executable=None, tty=True, cwd=None, env=None, time
 
             >>> s.process('echo hello', shell=True).recvall()
             b'hello\n'
+
+            >>> io = s.process(['cat'], timeout=5)
+            >>> io.recvline()
+            b''
         """
         if not argv and not executable:
             self.error("Must specify argv or executable")
@@ -1044,11 +1048,14 @@ def is_exe(path):
 
         msg = 'Starting remote process %s on %s' % (execve_repr, self.host)
 
+        if timeout == Timeout.default:
+            timeout = self.timeout
+
         with self.progress(msg) as h:
 
             script = 'for py in python2.7 python2 python; do test -x "$(which $py 2>&1)" && exec $py -c %s check; done; echo 2' % sh_string(script)
             with context.quiet:
-                python = ssh_process(self, script, tty=True, raw=True, level=self.level, timeout=self.timeout)
+                python = ssh_process(self, script, tty=True, raw=True, level=self.level, timeout=timeout)
 
             try:
                 result = safeeval.const(python.recvline())


Test patch:
Repository: Gallopsled/pwntools

Issue:
Pull Request #1608 is not Python2-compatible
The pull request in #1608 introduced a change that is not backward compatible.

This should be resolved in `stable`

```
[*] running in new terminal: /usr/local/bin/gdb -q  "./challenge" -x /tmp/pwntools/pwnn9EWNB.gdb
Traceback (most recent call last):
  File "exploit.py", line 5, in <module>
    io = gdb.debug('./challenge')
  File "/home/pwntools/pwntools/pwnlib/context/__init__.py", line 1449, in setter
    return function(*a, **kw)
  File "/home/pwntools/pwntools/pwnlib/gdb.py", line 465, in debug
    garbage = gdbserver.recvline(timeout=1)
  File "/home/pwntools/pwntools/pwnlib/tubes/tube.py", line 467, in recvline
    return self.recvuntil(self.newline, drop = not keepends, timeout = timeout)
  File "/home/pwntools/pwntools/pwnlib/tubes/tube.py", line 310, in recvuntil
    res = self.recv(timeout=self.timeout)
  File "/home/pwntools/pwntools/pwnlib/tubes/tube.py", line 82, in recv
    return self._recv(numb, timeout) or b''
  File "/home/pwntools/pwntools/pwnlib/tubes/tube.py", line 160, in _recv
    if not self.buffer and not self._fillbuffer(timeout):
  File "/home/pwntools/pwntools/pwnlib/tubes/tube.py", line 131, in _fillbuffer
    data = self.recv_raw(self.buffer.get_fill_size())
  File "/home/pwntools/pwntools/pwnlib/tubes/process.py", line 701, in recv_raw
    if not self.can_recv_raw(self.timeout):
  File "/home/pwntools/pwntools/pwnlib/tubes/process.py", line 755, in can_recv_raw
    if v.errno == errno.EINTR:
AttributeError: 'error' object has no attribute 'errno'
```


Discussion:
This breaks common usage of `gdb.debug` on Python2-based systems
Looks like this is only in `beta` and not in `stable` so the fix is easier (three branch model is working!)
Alright, so apparently `socket.error` is a subclass of `EnvironmentError`, while `select.error` is not (was not in py2).
The universal way to fix it is to use `e.args[0]`. PR on its way.

Resolving patch:
diff --git a/pwnlib/tubes/process.py b/pwnlib/tubes/process.py
--- a/pwnlib/tubes/process.py
+++ b/pwnlib/tubes/process.py
@@ -752,7 +752,7 @@ def can_recv_raw(self, timeout):
             # ValueError: I/O operation on closed file
             raise EOFError
         except select.error as v:
-            if v.errno == errno.EINTR:
+            if v.args[0] == errno.EINTR:
                 return False
 
     def connected_raw(self, direction):


Test patch:
Repository: Gallopsled/pwntools

Issue:
qemu debugging support doesn't load target file
When launching GDB via `gdb.debug()` when the target is a native binary, it seems to quite happily locate and download the target, printing something like:
```
Downloading '<somefile>' from the remote server: OK
```

However, when the target is a program compiled for a foreign architecture and pwntools uses the qemu gdb stub, gdb-multiarch is unable to do the same, printing the following:
```
warning: No executable has been specified and target does not support
determining executable automatically.  Try using the "file" command.
```

It looks like f727b66475dc8eb779e2d0af56250892f7642fa9 removed the `file` command from the gdbscript generated by `gdb.py` to address #1323 
Perhaps gdb-mutiarch doesn't cope with this situation in the same way as gdb? Adding the `file <target>` command to the generated gdbscript allows things to work as normal.

The setup I've encountered this with is Ubuntu 18.04 LTS with the `qemu` package installed and the appropriate symlinks in `/etc/qemu-binfmt/` to allow running ARM, MIPS etc. programs as if they were native.


Discussion:

Resolving patch:
diff --git a/pwnlib/gdb.py b/pwnlib/gdb.py
--- a/pwnlib/gdb.py
+++ b/pwnlib/gdb.py
@@ -631,6 +631,9 @@ def attach(target, gdbscript = '', exe = None, gdb_args = None, ssh = None, sysr
         if context.os == 'android':
             pre += 'set gnutarget ' + _bfdname() + '\n'
 
+        if exe:
+            pre += 'file %s\n' % exe
+
     # let's see if we can find a pid to attach to
     pid = None
     if   isinstance(target, six.integer_types):
diff --git a/pwnlib/version.py b/pwnlib/version.py
--- a/pwnlib/version.py
+++ b/pwnlib/version.py
@@ -1 +1 @@
-__version__ = '4.2.0'
+__version__ = '4.2.1'
diff --git a/setup.py b/setup.py
--- a/setup.py
+++ b/setup.py
@@ -83,7 +83,7 @@
     name                 = 'pwntools',
     python_requires      = '>=2.7',
     packages             = find_packages(),
-    version              = '4.2.0',
+    version              = '4.2.1',
     data_files           = [('',
                              glob.glob('*.md') + glob.glob('*.txt')),
                             ],


Test patch:
Repository: Gallopsled/pwntools

Issue:
Enable Write Memory from /proc/pic/mem, add more tooling around that
For exploit development, it would be useful to be able to read and write memory from a process.   This allows advanced debugging and manipulation that's not otherwise available.   

We already have `process.leak` that does this, but it could possibly use an alias e.g. `readmem`  with a  companion  `writemem`.

## Finding Memory

We already have `process.libs` which gets all of the library offsets.  

```
>>> proc.libs()
{'/usr/bin/dash': 94365341839360,
 '/usr/lib/x86_64-linux-gnu/libc-2.31.so': 140601119346688,
 '/usr/lib/x86_64-linux-gnu/ld-2.31.so': 140601121509376}
```

We might also want to add a `maps` property that dumps out all of the memory maps, not just the shared libraries.

Since we have `libs`,  it might similarly be useful to have `libs_elf` or `.mapped_elfs` which automatically loads all of the ELF images from disk and rebases them.


Discussion:

Resolving patch:
diff --git a/pwnlib/tubes/process.py b/pwnlib/tubes/process.py
--- a/pwnlib/tubes/process.py
+++ b/pwnlib/tubes/process.py
@@ -994,6 +994,52 @@ def leak(self, address, count=1):
             mem.seek(address)
             return mem.read(count) or None
 
+    readmem = leak
+
+    def writemem(self, address, data):
+        r"""Writes memory within the process at the specified address.
+
+        Arguments:
+            address(int): Address to write memory
+            data(bytes): Data to write to the address
+
+        Example:
+        
+            Let's write data to  the beginning of the mapped memory of the  ELF.
+
+            >>> context.clear(arch='i386')
+            >>> address = 0x100000
+            >>> data = cyclic(32)
+            >>> assembly = shellcraft.nop() * len(data)
+
+            Wait for one byte of input, then write the data to stdout
+
+            >>> assembly += shellcraft.write(1, address, 1)
+            >>> assembly += shellcraft.read(0, 'esp', 1)
+            >>> assembly += shellcraft.write(1, address, 32)
+            >>> assembly += shellcraft.exit()
+            >>> asm(assembly)[32:]
+            b'j\x01[\xb9\xff\xff\xef\xff\xf7\xd1\x89\xdaj\x04X\xcd\x801\xdb\x89\xe1j\x01Zj\x03X\xcd\x80j\x01[\xb9\xff\xff\xef\xff\xf7\xd1j Zj\x04X\xcd\x801\xdbj\x01X\xcd\x80'
+
+            Assemble the binary and test it
+
+            >>> elf = ELF.from_assembly(assembly, vma=address)
+            >>> io = elf.process()
+            >>> _ = io.recvuntil(b'\x90')
+            >>> _ = io.writemem(address, data)
+            >>> io.send(b'X')
+            >>> io.recvall()
+            b'aaaabaaacaaadaaaeaaafaaagaaahaaa'
+        """
+
+        if 'qemu-' in os.path.realpath('/proc/%i/exe' % self.pid):
+            self.error("Cannot use leaker on binaries under QEMU.")
+
+        with open('/proc/%i/mem' % self.pid, 'wb') as mem:
+            mem.seek(address)
+            return mem.write(data)
+
+
     @property
     def stdin(self):
         """Shorthand for ``self.proc.stdin``


Test patch:
Repository: Gallopsled/pwntools

Issue:
Fatal error when $TERM is not set
I am trying to use `pwntools` in a SageMath script. This works great interactively, but when I run my script with `sage foo.sage`, it fails somewhere in terminal code. I have traced this back to sage unsetting $TERM in non-interactive calls due to https://trac.sagemath.org/ticket/12263. Thus, issue can be easily reproduced without SageMath:

```sh
~$ docker run -it pwntools/pwntools:stable
pwntools@bce19e99e965:~$ TERM= python -c 'import pwn'
Warning: error: setupterm: could not find terminfo database

Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "/usr/local/lib/python2.7/dist-packages/pwn/__init__.py", line 4, in <module>
    from pwn.toplevel import *
  File "/usr/local/lib/python2.7/dist-packages/pwn/toplevel.py", line 20, in <module>
    import pwnlib
  File "/usr/local/lib/python2.7/dist-packages/pwnlib/__init__.py", line 43, in <module>
    importlib.import_module('.%s' % module, 'pwnlib')
  File "/usr/lib/python2.7/importlib/__init__.py", line 37, in import_module
    __import__(name)
  File "/usr/local/lib/python2.7/dist-packages/pwnlib/args.py", line 62, in <module>
    from pwnlib import term
  File "/usr/local/lib/python2.7/dist-packages/pwnlib/term/__init__.py", line 6, in <module>
    from pwnlib.term import completer
  File "/usr/local/lib/python2.7/dist-packages/pwnlib/term/completer.py", line 7, in <module>
    from pwnlib.term import readline
  File "/usr/local/lib/python2.7/dist-packages/pwnlib/term/readline.py", line 13, in <module>
    from pwnlib.term import text
  File "/usr/local/lib/python2.7/dist-packages/pwnlib/term/text.py", line 126, in <module>
    sys.modules[__name__] = Module()
  File "/usr/local/lib/python2.7/dist-packages/pwnlib/term/text.py", line 49, in __init__
    s = termcap.get(y)
  File "/usr/local/lib/python2.7/dist-packages/pwnlib/term/unix_termcap.py", line 28, in get
    s = curses.tigetstr(cap)
_curses.error: must call (at least) setupterm() first
```


Discussion:

Resolving patch:
diff --git a/pwnlib/term/unix_termcap.py b/pwnlib/term/unix_termcap.py
--- a/pwnlib/term/unix_termcap.py
+++ b/pwnlib/term/unix_termcap.py
@@ -60,6 +60,8 @@ def init():
         except curses.error as e:
             import traceback
             print('Warning:', ''.join(traceback.format_exception_only(e.__class__, e)), file=sys.stderr)
+            print('Terminal features will not be available.  Consider setting TERM variable to your current terminal name (or xterm).', file=sys.stderr)
+            os.environ['PWNLIB_NOTERM'] = '1'
 
     cache = {}
     # Manually add reset sequence into the cache.


Test patch:
Repository: Gallopsled/pwntools

Issue:
SSL Timeout Error immediately when switching to interactive
#### PoC
```
from pwn import *
r = remote('google.com', 443, ssl=True)
r.interactive()
r.close()
```
It immediately results in:
```
[+] Opening connection to google.com on port 443: Done
[*] Switching to interactive mode
Exception in thread Thread-2:
Traceback (most recent call last):
  File "/usr/lib/python2.7/threading.py", line 801, in __bootstrap_inner
    self.run()
  File "/usr/lib/python2.7/threading.py", line 754, in run
    self.__target(*self.__args, **self.__kwargs)
  File "/home/hopkins/.local/lib/python2.7/site-packages/pwnlib/tubes/tube.py", line 784, in recv_thread
    cur = self.recv(timeout = 0.05)
  File "/home/hopkins/.local/lib/python2.7/site-packages/pwnlib/tubes/tube.py", line 78, in recv
    return self._recv(numb, timeout) or ''
  File "/home/hopkins/.local/lib/python2.7/site-packages/pwnlib/tubes/tube.py", line 156, in _recv
    if not self.buffer and not self._fillbuffer(timeout):
  File "/home/hopkins/.local/lib/python2.7/site-packages/pwnlib/tubes/tube.py", line 126, in _fillbuffer
    data = self.recv_raw(self.buffer.get_fill_size())
  File "/home/hopkins/.local/lib/python2.7/site-packages/pwnlib/tubes/sock.py", line 37, in recv_raw
    data = self.sock.recv(numb, *a)
  File "/usr/lib/python2.7/ssl.py", line 772, in recv
    return self.read(buflen)
  File "/usr/lib/python2.7/ssl.py", line 659, in read
    v = self._sslobj.read(len)
SSLError: ('The read operation timed out',)

```

Note that doing so on a non-SSL server doesn't have this issue:
```
from pwn import *
r = remote('google.com', 80, ssl=False)
r.interactive()
r.close()
```

It allows you to type in HTTP Request in interactive mode, and return the server response without any issues.
```
GET /
```
```
<HTTP Responses>
```

Is the SSL feature is broken in pwntools?


Discussion:
I can reproduce on the release branch but it works fine on the dev3 branch at least.
It seems this is only related to Python 2.7.
Any updates?

Resolving patch:
diff --git a/pwnlib/tubes/sock.py b/pwnlib/tubes/sock.py
--- a/pwnlib/tubes/sock.py
+++ b/pwnlib/tubes/sock.py
@@ -48,6 +48,8 @@ def recv_raw(self, numb, *a):
                     raise EOFError
                 elif e.errno == errno.EINTR:
                     continue
+                elif 'timed out' in e.message:
+                    return None
                 else:
                     raise
 


Test patch:
Repository: Gallopsled/pwntools

Issue:
ROP gadget loading fails when it matches "add esp, esi"
I can't successfully load ROP gadgets with pwntools 4.2.1 if the binary contains a gadget with "add esp, esi" in it.  This happens at [rop.py on line 1241](https://github.com/Gallopsled/pwntools/blob/dev/pwnlib/rop/rop.py#L1241) because it tries to parse the capture from the regex "^add [er]sp, (\S+)$" as a hexadecimal integer.  I replaced the regex on my local installation with "^add [er]sp, (0?[xX]?[0-9a-fA-F]+)$" to capture only valid hexadecimal integers and it works/passes all tests.  

This should be reproducible with the following binary/python script 

```c
void invalid() {
	asm("add %esi,%esp");
	asm("pop %rbx");
	asm("ret");
}

int main() {

}
```

```python
from pwn import *
elf = ELF("./a.out")

rop = ROP(elf)
```




Discussion:

Resolving patch:
diff --git a/pwnlib/rop/rop.py b/pwnlib/rop/rop.py
--- a/pwnlib/rop/rop.py
+++ b/pwnlib/rop/rop.py
@@ -1101,7 +1101,7 @@ def __load(self):
         #
         # - leave
         # - pop reg
-        # - add $sp, value
+        # - add $sp, <hexadecimal value>
         # - ret
         #
         # Currently, ROPgadget does not detect multi-byte "C2" ret.
@@ -1109,7 +1109,7 @@ def __load(self):
         #
 
         pop   = re.compile(r'^pop (.{3})')
-        add   = re.compile(r'^add [er]sp, (\S+)$')
+        add   = re.compile(r'^add [er]sp, ((?:0[xX])?[0-9a-fA-F]+)$')
         ret   = re.compile(r'^ret$')
         leave = re.compile(r'^leave$')
         int80 = re.compile(r'int +0x80')
@@ -1125,6 +1125,8 @@ def __load(self):
         # False
         # >>> valid('add esp, 0x24')
         # True
+        # >>> valid('add esp, esi')
+        # False
         #
         valid = lambda insn: any(map(lambda pattern: pattern.match(insn), [pop,add,ret,leave,int80,syscall,sysenter]))
 


Test patch: