Repository: Gallopsled/pwntools

Issue:
DynElf symbol lookup uses bytes as key in a string-keyed dictionary.
**tldr; lookup() in DynElf throws a KeyError when attempting to lookup a bytes symbol in a string-keyed dictionary** 

## POC
```
from pwn import *

p = process("./a.out")
p.recvline()

base = p.elf.address


def leak(address):
    data = p.leak(address, 4)
    log.debug("%#x => %s" % (address, enhex(data or '')))
    return data


print("Key of symbol table is:", type(next(iter(p.elf.symbols.keys()))))

d = DynELF(leak, base)
d.lookup('printf', 'libc')

```

### output

```
root@banana:/ctf/work# pwn version
[*] Pwntools v4.6.0

root@banana:/ctf/work# python3 sol.py 
[+] Starting local process './a.out': pid 67
[*] '/ctf/work/a.out'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)
Key of symbol table is: <class 'str'>
[!] No ELF provided.  Leaking is much faster if you have a copy of the ELF being leaked.
[+] Resolving b'printf' in b'libc': 0xf7f1a940
[*] Trying lookup based on Build ID: 0e188ec5f09c187a7a92784d4b97aa251b15a93c
[+] Downloading 'https://gitlab.com/libcdb/libcdb/raw/master/hashes/build_id/0e188ec5f09c187a7a92784d4b97aa251b15a93c': 67B
[+] Downloading b'https://gitlab.com/libcdb/libcdb/raw/master/libc/libc6_2.27-3ubuntu1_i386/lib/i386-linux-gnu/libc-2.27.so': 1.85MB
Traceback (most recent call last):
  File "sol.py", line 18, in <module>
    d.lookup(b'printf', b'libc')
  File "/usr/local/lib/python3.6/dist-packages/pwnlib/dynelf.py", line 582, in lookup
    result = e.symbols[symb]
KeyError: b'printf'
[*] Stopped process './a.out' (pid 67)
```

### Underlying cause

This is the offending codeblock in dynelf.py#lookup. 
```
        if symb and self.libcdb:
            # Try a quick lookup by build ID
            self.status("Trying lookup based on Build ID")
            build_id = dynlib._lookup_build_id(lib=lib)
            if build_id:
                log.info("Trying lookup based on Build ID: %s" % build_id)
                path = libcdb.search_by_build_id(build_id)
                if path:
                    with context.local(log_level='error'):
                        e = ELF(path)
                        e.address = dynlib.libbase
----------------------> result = e.symbols[symb]          
```

ELF##symbols returns a dictionary where the key is a string. symb is of type bytes (no matter what you pass in due to `symb = _encode(symb)` above).

A quick solution (atm) is to replace the offending line with with `result = e.symbols[symb.decode()]`, but symbols should probably be converted to use bytes?
DynElf symbol lookup uses bytes as key in a string-keyed dictionary.
**tldr; lookup() in DynElf throws a KeyError when attempting to lookup a bytes symbol in a string-keyed dictionary** 

## POC
```
from pwn import *

p = process("./a.out")
p.recvline()

base = p.elf.address


def leak(address):
    data = p.leak(address, 4)
    log.debug("%#x => %s" % (address, enhex(data or '')))
    return data


print("Key of symbol table is:", type(next(iter(p.elf.symbols.keys()))))

d = DynELF(leak, base)
d.lookup('printf', 'libc')

```

### output

```
root@banana:/ctf/work# pwn version
[*] Pwntools v4.6.0

root@banana:/ctf/work# python3 sol.py 
[+] Starting local process './a.out': pid 67
[*] '/ctf/work/a.out'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)
Key of symbol table is: <class 'str'>
[!] No ELF provided.  Leaking is much faster if you have a copy of the ELF being leaked.
[+] Resolving b'printf' in b'libc': 0xf7f1a940
[*] Trying lookup based on Build ID: 0e188ec5f09c187a7a92784d4b97aa251b15a93c
[+] Downloading 'https://gitlab.com/libcdb/libcdb/raw/master/hashes/build_id/0e188ec5f09c187a7a92784d4b97aa251b15a93c': 67B
[+] Downloading b'https://gitlab.com/libcdb/libcdb/raw/master/libc/libc6_2.27-3ubuntu1_i386/lib/i386-linux-gnu/libc-2.27.so': 1.85MB
Traceback (most recent call last):
  File "sol.py", line 18, in <module>
    d.lookup(b'printf', b'libc')
  File "/usr/local/lib/python3.6/dist-packages/pwnlib/dynelf.py", line 582, in lookup
    result = e.symbols[symb]
KeyError: b'printf'
[*] Stopped process './a.out' (pid 67)
```

### Underlying cause

This is the offending codeblock in dynelf.py#lookup. 
```
        if symb and self.libcdb:
            # Try a quick lookup by build ID
            self.status("Trying lookup based on Build ID")
            build_id = dynlib._lookup_build_id(lib=lib)
            if build_id:
                log.info("Trying lookup based on Build ID: %s" % build_id)
                path = libcdb.search_by_build_id(build_id)
                if path:
                    with context.local(log_level='error'):
                        e = ELF(path)
                        e.address = dynlib.libbase
----------------------> result = e.symbols[symb]          
```

ELF##symbols returns a dictionary where the key is a string. symb is of type bytes (no matter what you pass in due to `symb = _encode(symb)` above).

A quick solution (atm) is to replace the offending line with with `result = e.symbols[symb.decode()]`, but symbols should probably be converted to use bytes?


Discussion:

Resolving patch:
diff --git a/pwnlib/elf/elf.py b/pwnlib/elf/elf.py
--- a/pwnlib/elf/elf.py
+++ b/pwnlib/elf/elf.py
@@ -157,6 +157,12 @@ class dotdict(dict):
         >>> x.bar.baz
         4
     """
+    def __missing__(self, name):
+        if isinstance(name, (bytes, bytearray)):
+            name = packing._decode(name)
+            return self[name]
+        raise KeyError(name)
+
     def __getattr__(self, name):
         if name in self:
             return self[name]


Test patch: