Repository: Gallopsled/pwntools

Issue:
Unfreeze unicorn: broken MIPS PLT
Probably because of unicorn-engine/unicorn#1243 MIPS PLT got broken (tldr, processor exception is thrown before PC register gets set to jump target). So the simple solution was to freeze it to `<1.0.2rc4`. Whether this is a bug or not, is probably a matter of that project. We can probably change examining `pc` to examining `t9`, as we currently rely on PLT index placed in `t8` anyway.


Discussion:
Is there a unit test that reproduces this?
The minimal I could create:
```py
from pwn import *
context.log_level = logging.DEBUG - 1 # just in case: PLT debugging needs even more verbosity than 'DEBUG'
e = ELF(pwnlib.data.elf.get('test-mips-big')) # messages are printed out
print(hex(e.plt['__stack_chk_fail'])) # should be some meaningful address
```
I think we could step one more instruction in the emulation.
To clarify, this isn't an issue that affects any releases because we are pinned to a specific Unicorn version, right?
Correct. It is pinned, and this issue is about adding support for latest Unicorn and unfreezing it back.
@Arusekk is this really broken?  I seem to get the same correct results for newer Unicorn, we just don't emulate the PLT to find the corresponding GOT entries.

Maybe it's just the `test-mips-big` binary is a "simple" case where the GOT and PLT ordering are the same -- the reason for emulating them at all is because the ordering is not GUARANTEED, just happens via convention.

## Script

```py
#!/usr/bin/env python3
from pwn import *
from pprint import pprint

context.log_level = 1

e = ELF(pwnlib.data.elf.get('test-mips-big'))

def print_plt(entry):
	print(f"{entry} is", hex(e.got[entry]))

print_plt('atoi')
print_plt('printf')
print_plt('srand')

pprint(e.got)
```

## Using unicorn==1.0.2rc3

```
~ ❯❯❯ python3 go.py3
[DEBUG] PLT 0x400bb0 printf
[DEBUG] PLT 0x400bc0 __stack_chk_fail
[DEBUG] PLT 0x400bd0 __libc_start_main
[DEBUG] PLT 0x400be0 __isoc99_scanf
[DEBUG] PLT 0x400bf0 atoi
[*] '/Users/heapcrash/pwntools/pwnlib/data/elf/test-mips-big'
    Arch:     mips-32-big
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX disabled
    PIE:      No PIE (0x400000)
    RWX:      Has RWX segments
atoi is 0x411050
printf is 0x411070
srand is 0x411060
{'_ITM_deregisterTMCloneTable': 4264052,
 '_ITM_registerTMCloneTable': 4264012,
 '_Jv_RegisterClasses': 4264024,
 '__gmon_start__': 4264036,
 '__isoc99_scanf': 4264020,
 '__libc_start_main': 4264028,
 '__stack_chk_fail': 4264040,
 '__stack_chk_guard': 4264044,
 'atoi': 4264016,
 'printf': 4264048,
 'srand': 4264032}
```

## With unicorn==1.0.2rc6

```
~ ❯❯❯ python3 go.py3
[*] '/Users/zachriggle/pwntools/pwnlib/data/elf/test-mips-big'
    Arch:     mips-32-big
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX disabled
    PIE:      No PIE (0x400000)
    RWX:      Has RWX segments
atoi is 0x411050
printf is 0x411070
srand is 0x411060
{'_ITM_deregisterTMCloneTable': 4264052,
 '_ITM_registerTMCloneTable': 4264012,
 '_Jv_RegisterClasses': 4264024,
 '__gmon_start__': 4264036,
 '__isoc99_scanf': 4264020,
 '__libc_start_main': 4264028,
 '__stack_chk_fail': 4264040,
 '__stack_chk_guard': 4264044,
 'atoi': 4264016,
 'printf': 4264048,
 'srand': 4264032}
```
Guys, please review this bug report and remove the unicorn version limitation if it is no longer required.

It might just be easier to disable MIPS PLT detection (only available via
emulation) to unpin.

On Sun, Jul 11, 2021 at 10:06 PM Anton Bolshakov ***@***.***>
wrote:

> Guys, please review this bug report and remove the unicorn version
> limitation if it is no longer required.
>
> —
> You are receiving this because you commented.
>
>
> Reply to this email directly, view it on GitHub
> <https://github.com/Gallopsled/pwntools/issues/1538#issuecomment-877934467>,
> or unsubscribe
> <https://github.com/notifications/unsubscribe-auth/APYTIFLAPN4ZRGGWQAG2G23TXJL5BANCNFSM4NRERJRA>
> .
>
-- 
*Heap Crash (Twitter <https://twitter.com/heapcrash>, GitHub
<http://github.com/heapcrash>)*


Resolving patch:
diff --git a/pwnlib/elf/plt.py b/pwnlib/elf/plt.py
--- a/pwnlib/elf/plt.py
+++ b/pwnlib/elf/plt.py
@@ -10,10 +10,6 @@
 
 log = getLogger(__name__)
 
-DEBUG = args.PLT_DEBUG or 0
-
-if not DEBUG:
-    log.setLevel(logging.DEBUG + 1)
 
 def emulate_plt_instructions(elf, got, address, data, targets):
     """Emulates instructions in ``data``
@@ -31,6 +27,9 @@ def emulate_plt_instructions(elf, got, address, data, targets):
     """
     rv = {}
 
+    if not args.PLT_DEBUG:
+        log.setLevel(logging.DEBUG + 1)
+
     # Unicorn doesn't support big-endian for everything yet.
     if elf.endian == 'big' and elf.arch == 'mips':
         data = packing.unpack_many(data, bits=32, endian='little')
@@ -46,7 +45,7 @@ def emulate_plt_instructions(elf, got, address, data, targets):
             log.debug(elf.disasm(pc, 4))
 
         uc.context_restore(ctx)
-        target = emulate_plt_instructions_inner(uc, elf, got, pc, data[i*4:], targets)
+        target = emulate_plt_instructions_inner(uc, elf, got, pc, data[i*4:])
 
         if target in targets:
             log.debug("%#x -> %#x", pc, target)
@@ -106,14 +105,14 @@ def prepare_unicorn_and_context(elf, got, address, data):
     # Because of this, we have to support loading memory from this location.
     #
     # https://www.cr0.org/paper/mips.elf.external.resolution.txt
-    magic_addr = 0xdbdbdbdb
+    magic_addr = 0x7c7c7c7c
 
     if elf.arch == 'mips':
         # Map the GOT so that MIPS can access it
         p_magic = packing.p32(magic_addr)
         start = got & (~0xfff)
         try:
-            uc.mem_map(start, start+0x1000)
+            uc.mem_map(start, 0x1000)
         except Exception:
             # Ignore double-mapping
             pass
@@ -125,7 +124,7 @@ def prepare_unicorn_and_context(elf, got, address, data):
         # the magic address.
         start = magic_addr & (~0xfff)
         try:
-            uc.mem_map(start, start+0x1000)
+            uc.mem_map(start, 0x1000)
         except Exception:
             # Ignore double-mapping
             pass
@@ -134,14 +133,15 @@ def prepare_unicorn_and_context(elf, got, address, data):
 
     return uc, uc.context_save()
 
-def emulate_plt_instructions_inner(uc, elf, got, pc, data, targets):
+
+def emulate_plt_instructions_inner(uc, elf, got, pc, data):
     import unicorn as U
 
     # Hook invalid addresses and any accesses out of the specified address range
     stopped_addr = []
 
     # For MIPS. Explanation at prepare_unicorn_and_context.
-    magic_addr = 0xdbdbdbdb
+    magic_addr = 0x7c7c7c7c
 
     def hook_mem(uc, access, address, size, value, user_data):
         # Special case to allow MIPS to dereference the _DYNAMIC pointer
@@ -154,14 +154,15 @@ def hook_mem(uc, access, address, size, value, user_data):
         return False
 
     hooks = [
-        uc.hook_add(U.UC_HOOK_MEM_READ, hook_mem, stopped_addr),
-        uc.hook_add(U.UC_HOOK_MEM_UNMAPPED, hook_mem, stopped_addr)
+        uc.hook_add(U.UC_HOOK_MEM_READ | U.UC_HOOK_MEM_READ_UNMAPPED,
+                    hook_mem, stopped_addr),
     ]
 
     # callback for tracing instructions
     # def hook_code(uc, address, size, user_data):
-    #     print(">>> Tracing instruction at 0x%x, instruction size = 0x%x, data=%r" %(address, size, uc.mem_read(address, size)))
-    # uc.hook_add(U.UC_HOOK_CODE, hook_code)
+    #     print(">>> Tracing instruction at %#x, instr size = %#x, data=%r"
+    #           % (address, size, uc.mem_read(address, size)))
+    # hooks.append(uc.hook_add(U.UC_HOOK_CODE, hook_code))
 
     # For Intel, set the value of EBX
     if elf.arch == 'i386':
@@ -183,7 +184,7 @@ def hook_mem(uc, access, address, size, value, user_data):
 
     if elf.arch == 'mips':
         pc = uc.reg_read(U.mips_const.UC_MIPS_REG_PC)
-        if pc+1 == magic_addr:
+        if pc == magic_addr:
             t8 = uc.reg_read(U.mips_const.UC_MIPS_REG_T8)
             stopped_addr.append(elf._mips_got.get(t8, 0))
 
diff --git a/setup.py b/setup.py
--- a/setup.py
+++ b/setup.py
@@ -60,7 +60,8 @@
                         'psutil>=3.3.0',
                         'intervaltree>=3.0',
                         'sortedcontainers',
-                        'unicorn>=1.0.2rc1,<1.0.2rc4', # see unicorn-engine/unicorn#1100, unicorn-engine/unicorn#1170, Gallopsled/pwntools#1538
+                        # see unicorn-engine/unicorn#1100 and #1170
+                        'unicorn>=1.0.2rc1',
                         'six>=1.12.0',
                         'rpyc',
                         'colored_traceback',


Test patch: