Skip to content

Commit

Permalink
IRS formatting fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
despawningbone committed Feb 20, 2024
1 parent 7cdac1f commit 0753680
Showing 1 changed file with 13 additions and 10 deletions.
23 changes: 13 additions & 10 deletions _posts/2024-02-08-dicectf2024-irs.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ in which attributes can be accessed by executing a format string:
subclasses = '{0.__subclasses__}'.format(object)
```

Originally, we thought that this is pretty useless for our case since it only allows information disclosure since it only returns the attribute as a **string** rather than the object itself, so we didn't think much about it either.
Originally, we thought that this is pretty useless for our case since it only allows information disclosure since it only returns the attribute as a **string** rather than the object itself, so we didn't think much about it either, and continued to find more things to work with.

Digging up basically everything we learnt during the RestrictedPython adventures, we also came across another weird quirk of Python's, namely the `AttributeError.obj` field, which can yield some really interesting results:
```py
Expand All @@ -217,7 +217,7 @@ This gives us an iterator for a sequence, without ever calling `__iter__` or the
sandboxes like RestrictedPython which doesn't even allow for loops if it was not explicitly allowed - we already have all the builtin methods we need,
including `iter` in this challenge.

But then [@nneonneo](../../../authors/Nneonneo) stepped in and combined them both, resulting in something much more than the sum of its parts:
But then [@nneonneo](../../../authors/Nneonneo) stepped in, combined them both, and gave us something much more than the sum of its parts:
```py
try:
"{__getitem__.xx}".format_map(vars(dict))
Expand Down Expand Up @@ -400,7 +400,7 @@ baset(memory, id(250) + 24, 100)
print(250)
```

...except the challenge exits without printing anything. We are foiled again by the challenge... or are we?
...except the challenge exits without printing anything. We have been foiled yet again... or have we?

We know that using the repr of a custom function can yield us something like `<function func at 0x000002459495F160>`,
which we can then parse and obtain the pointer (that is equivalent to the value given by `id`). But this doesn't work with builtin methods nor literals,
Expand Down Expand Up @@ -443,17 +443,20 @@ baset(memory, getptr(aud) + 16, baget(memory, getptr(nop)))
system("sh")
```

But, of course, there are more hurdles to go over. Replacing `sys.audit` doesn't let us do `os.system`, since system is implemented in C, and calls the
equivalent function in the Python C API instead. We still have a lot of things we can try out since we have both arbitrary access both inside and outside
of the Python environment - just that we need more knowledge on the Python interpreter internals.
But, as it seems to be the theme for this challenge, there are more hurdles to go over. Replacing `sys.audit` doesn't let us do `os.system`, since
system is implemented in C, and calls the equivalent function in the Python C API instead. We still have a lot of things we can try out since we have
both arbitrary access both inside and outside of the Python environment - just that we need more knowledge on the Python interpreter internals.

# Finally, harmony

One such knowledge is the difference between C level audit hooks (`PySys_AddAuditHook`, triggers on every audit event including those in
sub-interpreters) and Python level audit hooks (`sys.addaudithook`, triggers on a per-interpreter basis). This is important because they dictate
where the audit hook actually resides (`_PyRuntime->audit_hooks` vs `PyInterpreterState->audit_hooks`). For our case, we only care about the `_PyRuntime`
version since the audit hook is registered with the C API. This benefits us a fair bit - there is only one global `_PyRuntime` instance, and the data
is written directly in the `.PyRuntime` segment. With some IDA referencing to get the offsets of `libpython3.12.so` (can't believe I'm saying this on a pyjail writeup), we can obtain the audit_hook head easily, and NULL it out so that on firing audit event Python would think there is no hooks registered.
where the audit hook actually resides (`_PyRuntime->audit_hooks` vs `PyInterpreterState->audit_hooks`).

For our case, we only care about the `_PyRuntime` version since the audit hook is registered with the C API. This benefits us a fair bit - there is only
one global `_PyRuntime` instance, and the data is written directly in the `.PyRuntime` segment. With some IDA referencing to get the offsets of
`libpython3.12.so` (can't believe I'm saying this on a pyjail writeup), we can obtain the audit_hook head easily, and NULL it out so that on firing
audit event Python would think there is no hooks registered.

With that, we have finally obtained the full solve script (with convenient annotations for what each section is for, since you probably skipped through all of the explanations didn't you 😢):

Expand Down Expand Up @@ -539,7 +542,7 @@ system("ls -la") #run system normally now that our audit hook linked list is cl
```

(Please ignore the fact that some of these offsets lead to weird locations - it was 4am and we only cared enough to get the solve in, not whether it
made sense or not, and not the fact that the offsets are so off due how the first offset was supposed to be ` + 16` not ` + 24` 🤪👍)
made sense or not, and not the fact that the offsets are so off due how the first offset was supposed to be `+ 16` not `+ 24` 🤪👍)

Overall, this was a really fun pyjail challenge that utilized basically everything about Python in the challenge! Turns out while memory exploits were
intended, `getattr` wasn't intended and the intended solution was to use another UAF instead ([Issue #43838](https://bugs.python.org/issue43838)).
Expand Down

0 comments on commit 0753680

Please sign in to comment.