Repository: Gallopsled/pwntools

Issue:
Pwn shellcraft is slower than needed
Currently, `shellcraft` does this:

```py
    if args.shellcode not in shellcraft.templates:
        log.error("Unknown shellcraft template %r. Use --list to see available shellcodes." % args.shellcode)
```

Which requires a recursive walk of all of the shellcraft templates and their directories.  This can be very slow if e.g. the pwntools installation is mounted as a shared Docker folder.

```py
    @property
    def templates(self):
        if self._templates:
            return self._templates

        template_dir = os.path.join(os.path.dirname(__file__), 'templates')
        templates    = []

        for root, _, files in os.walk(template_dir, followlinks=True):
            for file in filter(lambda x: x.endswith('.asm'), files):
                value = os.path.splitext(file)[0]
                value = os.path.join(root, value)
                value = value.replace(template_dir, '')
                value = value.replace(os.path.sep, '.')
                value = value.lstrip('.')
                templates.append(value)

        templates = sorted(templates)
        self._templates = templates
        return templates
```

We should be able to check to see if the template exists by its exact name first, and avoid this recursive walk.

Here's an example showing the `strace` output of  walking the entire hierarchy.

```
$ strace -r -e open,openat shellcraft i386.linux.sh
...
     0.011731 openat(AT_FDCWD, "/home/pwntools/pwntools/pwnlib/shellcraft/templates", O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_DIRECTORY) = 3
     0.013546 openat(AT_FDCWD, "/home/pwntools/pwntools/pwnlib/shellcraft/templates/powerpc", O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_DIRECTORY) = 3
     0.005800 openat(AT_FDCWD, "/home/pwntools/pwntools/pwnlib/shellcraft/templates/powerpc/linux", O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_DIRECTORY) = 3
     0.008314 openat(AT_FDCWD, "/home/pwntools/pwntools/pwnlib/shellcraft/templates/powerpc/linux/syscalls", O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_DIRECTORY) = 3
     0.661969 openat(AT_FDCWD, "/home/pwntools/pwntools/pwnlib/shellcraft/templates/powerpc/android", O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_DIRECTORY) = 3
     0.009786 openat(AT_FDCWD, "/home/pwntools/pwntools/pwnlib/shellcraft/templates/powerpc/android/syscalls", O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_DIRECTORY) = 3
     0.697323 openat(AT_FDCWD, "/home/pwntools/pwntools/pwnlib/shellcraft/templates/thumb", O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_DIRECTORY) = 3
     0.020890 openat(AT_FDCWD, "/home/pwntools/pwntools/pwnlib/shellcraft/templates/thumb/freebsd", O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_DIRECTORY) = 3
     0.004973 openat(AT_FDCWD, "/home/pwntools/pwntools/pwnlib/shellcraft/templates/thumb/linux", O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_DIRECTORY) = 3
     0.028175 openat(AT_FDCWD, "/home/pwntools/pwntools/pwnlib/shellcraft/templates/thumb/linux/syscalls", O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_DIRECTORY) = 3
```


Discussion:

Resolving patch:
diff --git a/pwnlib/commandline/shellcraft.py b/pwnlib/commandline/shellcraft.py
--- a/pwnlib/commandline/shellcraft.py
+++ b/pwnlib/commandline/shellcraft.py
@@ -198,11 +198,11 @@ def main(args):
         common.parser.print_usage()
         exit()
 
-    if args.shellcode not in shellcraft.templates:
+    try:
+        func = get_template(args.shellcode)
+    except AttributeError:
         log.error("Unknown shellcraft template %r. Use --list to see available shellcodes." % args.shellcode)
 
-    func = get_template(args.shellcode)
-
     if args.show:
         # remove doctests
         doc = []


Test patch: