Repository: pwndbg/pwndbg

Issue:
Bad unsigned casting
### Description

`pwndbg.memory.u` returns signed integers (with minus `-` sign).

### Steps to reproduce


```c
#include <stdio.h>
#include <stdint.h>

int main(int argc, char const *argv[])
{
    uint64_t x = 0xb60ad86e8fb52ea8;
    printf("%p\n", &x);
    getc(stdin);
    return 0;
}
```

```
clang bad_u.c -g -o bad_u
gdb ./bad_u

pwndbg> x/xg 0x7fffffffab18
0x7fffffffab18:	0xb60ad86e8fb52ea8
pwndbg> python-interactive 
>>> pwndbg.memory.u(0x7fffffffab18)
-5329209239670542680
```

Idk why it doesn't break the pwndbg visibly. Found it running `vis_heap_chunks` on arbitrary addresses (the minus were printed in few places).

### My setup

```
GNU gdb (Ubuntu 8.1-0ubuntu3.2) 8.1.0.20180409-git
python: 3.6.9 (default, Nov  7 2019, 10:44:02)
pwndbg: dev branch
```
Bad unsigned casting
### Description

`pwndbg.memory.u` returns signed integers (with minus `-` sign).

### Steps to reproduce


```c
#include <stdio.h>
#include <stdint.h>

int main(int argc, char const *argv[])
{
    uint64_t x = 0xb60ad86e8fb52ea8;
    printf("%p\n", &x);
    getc(stdin);
    return 0;
}
```

```
clang bad_u.c -g -o bad_u
gdb ./bad_u

pwndbg> x/xg 0x7fffffffab18
0x7fffffffab18:	0xb60ad86e8fb52ea8
pwndbg> python-interactive 
>>> pwndbg.memory.u(0x7fffffffab18)
-5329209239670542680
```

Idk why it doesn't break the pwndbg visibly. Found it running `vis_heap_chunks` on arbitrary addresses (the minus were printed in few places).

### My setup

```
GNU gdb (Ubuntu 8.1-0ubuntu3.2) 8.1.0.20180409-git
python: 3.6.9 (default, Nov  7 2019, 10:44:02)
pwndbg: dev branch
```


Discussion:
**EDIT: To see what is the issue, scroll to https://github.com/pwndbg/pwndbg/issues/726#issuecomment-600760345**

I can reproduce this
```
pwndbg> x/xg 0x7fffffffab18
0x7fffffffab18:	0xb60ad86e8fb52ea8
pwndbg> pi pwndbg.typeinfo.uint64.name
'unsigned long long'
pwndbg> pi pwndbg.typeinfo.uint64.pointer().name
pwndbg> pi gdb.Value(0x7fffffffab18).cast(pwndbg.typeinfo.uint64.pointer())
<gdb.Value object at 0x7fb472434a70>
pwndbg> pi int(gdb.Value(0x7fffffffab18).cast(pwndbg.typeinfo.uint64.pointer()))
140737488333592
pwndbg> pi hex(140737488333592)
'0x7fffffffab18'
pwndbg> pi int(gdb.Value(0x7fffffffab18).cast(pwndbg.typeinfo.uint64.pointer()).dereference())
-5329209239670542680
pwndbg> pi gdb.Value(0x7fffffffab18).cast(pwndbg.typeinfo.uint64.pointer()).dereference().type.name
'unsigned long long'
```

I am not sure, but if I remember correctly we had some discussions about this behaviour...
Here, pure/raw GDB equivalent:
```
pi int(gdb.Value(0x7fffffffab18).cast(gdb.lookup_type('unsigned long long').pointer()).dereference())
```
And I can confirm this being a Pwndbg bug, not a GDB behavior:
```
(gdb) x/xg 0x7fffffffe190
0x7fffffffe190:	0xb60ad86e8fb52ea8

(gdb) pi int(gdb.Value(0x7fffffffe190).cast(gdb.lookup_type('unsigned long long').pointer()).dereference())
13117534834039008936
```
Our inthook fials again :(
```
pwndbg> pi pwndbg.inthook.xint.__new__(pwndbg.inthook.xint, 13117534834039008936)
xint.__new__ <class 'pwndbg.inthook.xint'> 13117534834039008936 () {}
13117534834039008936

pwndbg> pi pwndbg.inthook.xint.__new__(pwndbg.inthook.xint, gdb.Value(13117534834039008936))
xint.__new__ <class 'pwndbg.inthook.xint'> 13117534834039008936 () {}
-5329209239670542680
```

EDIT: Well actually... I am not sure if that is the case, but maaaybe.

EDIT2: Yes.

```
pwndbg> pi int(gdb.Value(0x7fffffffab18).cast(gdb.lookup_type('unsigned long long').pointer()).dereference())
xint.__new__ <class 'pwndbg.inthook.xint'> 13117534834039008936 (type: <class 'gdb.Value'>) () {}
-5329209239670542680
```
One idea: drop support for Python 2 completely and remove the inthook. **Removing the inthook source code makes this case work _as expected_, heh.**

@zachriggle thoughts about dropping Py2 support finally? GEF did that few months ago :P.
Just so that all readers of this issue are on the same page, the problematic code is [here](https://github.com/pwndbg/pwndbg/blob/dev/pwndbg/inthook.py#L37-L42):
```python
class xint(with_metaclass(IsAnInt, builtins.int)):
    def __new__(cls, value, *a, **kw):
        if isinstance(value, gdb.Value):
            if pwndbg.typeinfo.is_pointer(value):
                value = value.cast(pwndbg.typeinfo.size_t)
            else:
                value = value.cast(pwndbg.typeinfo.ssize_t)

        # (...) - code handling other types, removed as not needed to describe the issue

        return _int(_int(value, *a, **kw))
```

This is our inthook implementation: we do it to support both PY2 and PY3 in GDB. Why? Because `int(gdb.Value(...))` on GDB with PY2 didn't work.

So we hooked int globally via `globals()['int'] = xint` and `globals()['long'] = xint` (in py2) and handled "proper conversions to int" by ourselves.

This code has been changed through time, to e.g. add support for Python 3 enum classes or to fix some bugs that were found in the meantime.

**The bug can be fixed by changing this line:**
```diff
-                 value = value.cast(pwndbg.typeinfo.ssize_t)
+                return _int.__new__(cls, value, *a, **kw)
```

However, I have no idea if it doesn't break anything else...

**Given that, and the fact we are in 2020, maybe it is time to drop Py2 support and go with Py3 only.**

As long as we do one last release for Python2 support, I'm down for dropping it in the next release
**EDIT: To see what is the issue, scroll to https://github.com/pwndbg/pwndbg/issues/726#issuecomment-600760345**

I can reproduce this
```
pwndbg> x/xg 0x7fffffffab18
0x7fffffffab18:	0xb60ad86e8fb52ea8
pwndbg> pi pwndbg.typeinfo.uint64.name
'unsigned long long'
pwndbg> pi pwndbg.typeinfo.uint64.pointer().name
pwndbg> pi gdb.Value(0x7fffffffab18).cast(pwndbg.typeinfo.uint64.pointer())
<gdb.Value object at 0x7fb472434a70>
pwndbg> pi int(gdb.Value(0x7fffffffab18).cast(pwndbg.typeinfo.uint64.pointer()))
140737488333592
pwndbg> pi hex(140737488333592)
'0x7fffffffab18'
pwndbg> pi int(gdb.Value(0x7fffffffab18).cast(pwndbg.typeinfo.uint64.pointer()).dereference())
-5329209239670542680
pwndbg> pi gdb.Value(0x7fffffffab18).cast(pwndbg.typeinfo.uint64.pointer()).dereference().type.name
'unsigned long long'
```

I am not sure, but if I remember correctly we had some discussions about this behaviour...
Here, pure/raw GDB equivalent:
```
pi int(gdb.Value(0x7fffffffab18).cast(gdb.lookup_type('unsigned long long').pointer()).dereference())
```
And I can confirm this being a Pwndbg bug, not a GDB behavior:
```
(gdb) x/xg 0x7fffffffe190
0x7fffffffe190:	0xb60ad86e8fb52ea8

(gdb) pi int(gdb.Value(0x7fffffffe190).cast(gdb.lookup_type('unsigned long long').pointer()).dereference())
13117534834039008936
```
Our inthook fials again :(
```
pwndbg> pi pwndbg.inthook.xint.__new__(pwndbg.inthook.xint, 13117534834039008936)
xint.__new__ <class 'pwndbg.inthook.xint'> 13117534834039008936 () {}
13117534834039008936

pwndbg> pi pwndbg.inthook.xint.__new__(pwndbg.inthook.xint, gdb.Value(13117534834039008936))
xint.__new__ <class 'pwndbg.inthook.xint'> 13117534834039008936 () {}
-5329209239670542680
```

EDIT: Well actually... I am not sure if that is the case, but maaaybe.

EDIT2: Yes.

```
pwndbg> pi int(gdb.Value(0x7fffffffab18).cast(gdb.lookup_type('unsigned long long').pointer()).dereference())
xint.__new__ <class 'pwndbg.inthook.xint'> 13117534834039008936 (type: <class 'gdb.Value'>) () {}
-5329209239670542680
```
One idea: drop support for Python 2 completely and remove the inthook. **Removing the inthook source code makes this case work _as expected_, heh.**

@zachriggle thoughts about dropping Py2 support finally? GEF did that few months ago :P.
Just so that all readers of this issue are on the same page, the problematic code is [here](https://github.com/pwndbg/pwndbg/blob/dev/pwndbg/inthook.py#L37-L42):
```python
class xint(with_metaclass(IsAnInt, builtins.int)):
    def __new__(cls, value, *a, **kw):
        if isinstance(value, gdb.Value):
            if pwndbg.typeinfo.is_pointer(value):
                value = value.cast(pwndbg.typeinfo.size_t)
            else:
                value = value.cast(pwndbg.typeinfo.ssize_t)

        # (...) - code handling other types, removed as not needed to describe the issue

        return _int(_int(value, *a, **kw))
```

This is our inthook implementation: we do it to support both PY2 and PY3 in GDB. Why? Because `int(gdb.Value(...))` on GDB with PY2 didn't work.

So we hooked int globally via `globals()['int'] = xint` and `globals()['long'] = xint` (in py2) and handled "proper conversions to int" by ourselves.

This code has been changed through time, to e.g. add support for Python 3 enum classes or to fix some bugs that were found in the meantime.

**The bug can be fixed by changing this line:**
```diff
-                 value = value.cast(pwndbg.typeinfo.ssize_t)
+                return _int.__new__(cls, value, *a, **kw)
```

However, I have no idea if it doesn't break anything else...

**Given that, and the fact we are in 2020, maybe it is time to drop Py2 support and go with Py3 only.**

As long as we do one last release for Python2 support, I'm down for dropping it in the next release

Resolving patch:
diff --git a/pwndbg/inthook.py b/pwndbg/inthook.py
--- a/pwndbg/inthook.py
+++ b/pwndbg/inthook.py
@@ -39,7 +39,7 @@ def __new__(cls, value, *a, **kw):
             if pwndbg.typeinfo.is_pointer(value):
                 value = value.cast(pwndbg.typeinfo.size_t)
             else:
-                value = value.cast(pwndbg.typeinfo.ssize_t)
+                return _int.__new__(cls, value, *a, **kw)
 
         elif isinstance(value, gdb.Symbol):
             symbol = value


Test patch: