Repository: Gallopsled/pwntools

Issue:
pwn checksec: do not require libunicorn to be loaded
It seems that `pwn checksec` requires libunicorn. Is that really required? The Unicorn Engine mmaps a 1GB+ memory page which aborts the program on failures. This can happen on low-memory systems such as cheap VPSes. This is actually a bug in libunicorn that I reported in https://github.com/unicorn-engine/unicorn/issues/1766 but it still hasn't been fixed.

Given all this, it would be great if we could not require libunicorn in `checksec`. But if we need, it would be nice to get it documented here for users who stumble upon the same issue and try to search it here.

Here is an example issue of this:

```
root@pwndbg:~# free -h
               total        used        free      shared  buff/cache   available
Mem:           957Mi       265Mi       153Mi       4.0Mi       538Mi       532Mi
Swap:             0B          0B          0B

root@pwndbg:~# strace -e openat,mmap pwn checksec /root/x/bin/main 2>&1 | tail -n 10
openat(AT_FDCWD, "/usr/lib/python3/dist-packages/mpmath-0.0.0.egg-info/PKG-INFO", O_RDONLY|O_CLOEXEC) = 7
openat(AT_FDCWD, "/usr/local/lib/python3.10/dist-packages/unicorn/lib/libunicorn.so.2", O_RDONLY|O_CLOEXEC) = 7
mmap(NULL, 22447520, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 7, 0) = 0x7f2604f9d000
mmap(0x7f2605339000, 13496320, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 7, 0x39c000) = 0x7f2605339000
mmap(0x7f2606018000, 3039232, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 7, 0x107b000) = 0x7f2606018000
mmap(0x7f26062fe000, 1601536, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 7, 0x1360000) = 0x7f26062fe000
mmap(0x7f2606485000, 525728, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f2606485000
mmap(NULL, 1073741824, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = -1 ENOMEM (Cannot allocate memory)
Could not allocate dynamic translator buffer
+++ exited with 1 +++
```
pwn checksec: do not require libunicorn to be loaded
It seems that `pwn checksec` requires libunicorn. Is that really required? The Unicorn Engine mmaps a 1GB+ memory page which aborts the program on failures. This can happen on low-memory systems such as cheap VPSes. This is actually a bug in libunicorn that I reported in https://github.com/unicorn-engine/unicorn/issues/1766 but it still hasn't been fixed.

Given all this, it would be great if we could not require libunicorn in `checksec`. But if we need, it would be nice to get it documented here for users who stumble upon the same issue and try to search it here.

Here is an example issue of this:

```
root@pwndbg:~# free -h
               total        used        free      shared  buff/cache   available
Mem:           957Mi       265Mi       153Mi       4.0Mi       538Mi       532Mi
Swap:             0B          0B          0B

root@pwndbg:~# strace -e openat,mmap pwn checksec /root/x/bin/main 2>&1 | tail -n 10
openat(AT_FDCWD, "/usr/lib/python3/dist-packages/mpmath-0.0.0.egg-info/PKG-INFO", O_RDONLY|O_CLOEXEC) = 7
openat(AT_FDCWD, "/usr/local/lib/python3.10/dist-packages/unicorn/lib/libunicorn.so.2", O_RDONLY|O_CLOEXEC) = 7
mmap(NULL, 22447520, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 7, 0) = 0x7f2604f9d000
mmap(0x7f2605339000, 13496320, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 7, 0x39c000) = 0x7f2605339000
mmap(0x7f2606018000, 3039232, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 7, 0x107b000) = 0x7f2606018000
mmap(0x7f26062fe000, 1601536, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 7, 0x1360000) = 0x7f26062fe000
mmap(0x7f2606485000, 525728, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f2606485000
mmap(NULL, 1073741824, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = -1 ENOMEM (Cannot allocate memory)
Could not allocate dynamic translator buffer
+++ exited with 1 +++
```


Discussion:
This issue happens because `checksec.py` invokes the `ELF` object constructor that calls its `self._populate_plt()` function that invokes Unicorn engine to emulate PLT instructions to figure out some addresses.

Funnily, modifying the code so that the `_populate_plt` fail, for example by introducing a line: `asdf` which causes a `name 'asdf' is not defined` error, makes the `checksec` command work, because it handles the error and logs a warning:

https://github.com/Gallopsled/pwntools/blob/b2cba039d4d07eb8b8c98369fd69dcd13ae80441/pwnlib/elf/elf.py#L352-L355



This issue happens because `checksec.py` invokes the `ELF` object constructor that calls its `self._populate_plt()` function that invokes Unicorn engine to emulate PLT instructions to figure out some addresses.

Funnily, modifying the code so that the `_populate_plt` fail, for example by introducing a line: `asdf` which causes a `name 'asdf' is not defined` error, makes the `checksec` command work, because it handles the error and logs a warning:

https://github.com/Gallopsled/pwntools/blob/b2cba039d4d07eb8b8c98369fd69dcd13ae80441/pwnlib/elf/elf.py#L352-L355




Resolving patch:
diff --git a/pwnlib/elf/plt.py b/pwnlib/elf/plt.py
--- a/pwnlib/elf/plt.py
+++ b/pwnlib/elf/plt.py
@@ -53,9 +53,30 @@ def emulate_plt_instructions(elf, got, address, data, targets):
 
     return rv
 
+
+def __ensure_memory_to_run_unicorn():
+    """
+    Check if there is enough memory to run Unicorn Engine.
+    Unicorn Engine requires 1GB of memory to run, if there isn't enough memory it calls exit(1).
+
+    This is a bug in Unicorn Engine, see: https://github.com/unicorn-engine/unicorn/issues/1766
+    """
+    try:
+        from mmap import mmap, MAP_ANON, MAP_PRIVATE, PROT_EXEC, PROT_READ, PROT_WRITE
+
+        mm = mmap(
+            -1, 1024 * 1024 * 1024, MAP_PRIVATE | MAP_ANON, PROT_WRITE | PROT_READ | PROT_EXEC
+        )
+        mm.close()
+    except OSError:
+        raise OSError("Cannot allocate 1GB memory to run Unicorn Engine")
+
+
 def prepare_unicorn_and_context(elf, got, address, data):
     import unicorn as U
 
+    __ensure_memory_to_run_unicorn()
+
     # Instantiate the emulator with the correct arguments for the current
     # architecutre.
     arch = {


Test patch: