> *NOTE: This article is once a part of the writeup of a CTF event. I found the challenge probelem is not that simple as it looks like, so I did much more detailed work and write a lot more. And since the part was going too long, I seperated it to this new post for more friendly reading. ## Story from a simple _format string_ vulnerable I recently worked on a CTF problem, which requires to take a left-over `rbp` in the _stack frame_ as the `%n` pointer to hijack the return address. ![CleanShot 2024-02-05 at 22.58.04@2x](https://hackmd.io/_uploads/rypMj_056.png) You can read the [original writeup](https://hackmd.io/@pnck/hgame2024week1#PWN-ezfmt) for more details of this program. ### The Amazing, The Weired But not everyone has the luck to see the left-over value, suffering this: ![CleanShot 2024-02-04 at 06.28.33@2x](https://hackmd.io/_uploads/SJQdAV29a.png) **Huh?! There isn't any available rbp chain at all!!!** #### What's going on? In the `main()` function we can see the stack keeps at the same position between calls, which means all these callee function will create _stack frame_ at exactly same address. ![CleanShot 2024-02-05 at 15.09.56@2x](https://hackmd.io/_uploads/HJkV6bA56.png) Imagine if a callee `C` created a very small _stack frame_ then soon called another function `F`, the saved `bp` of `F` would be left very close to the `bp` of `C`, which was very likely to overlap the latter _stack frames_. Now take a look into `init()`. ![CleanShot 2024-02-05 at 15.20.44@2x](https://hackmd.io/_uploads/rJnL2bR5a.png) That's it!, the _stack frame_ of `setbuf()` will definiely be overlapped with the `vuln()` function, which brings us the exploit point. And since `setbuf()` push 2 values before `rbp`, it `bp` address is slightly shifted. ![CleanShot 2024-02-05 at 15.40.05@2x](https://hackmd.io/_uploads/rkR1-fC5a.png) The problem is, why did they still exist at all after the `printf` call? **If you launch `gdb` or `gdbserver` directly**, you will probably see the stack empty out. When the `printf@got` is beeing resolved, inside `_dl_runtime_resolve_xsavec` the `xsavec` instruction overwrite the old `rbp` area: ![CleanShot 2024-02-05 at 21.43.06@2x](https://hackmd.io/_uploads/SyeBLP05a.png) ![CleanShot 2024-02-05 at 21.43.28@2x](https://hackmd.io/_uploads/BJMHLwRcT.png) Okay... then WTF is `xsavec`? From the [source](https://elixir.bootlin.com/glibc/glibc-2.35/source/sysdeps/x86_64/dl-trampoline.S#L105) we know that `_dl_runtime_resolve_xsavec` is a variety implementation of `_dl_runtime_resolve`, and from the [instruction manual](https://shell-storm.org/x86doc/XSAVEC.html) we know that `xsavec` is used to save the process status all in once. We can clearly see the save-restore procedure around a resolver call: ![CleanShot 2024-02-05 at 18.13.42@2x](https://hackmd.io/_uploads/BJZerNRq6.png) The prologue of this function build a _stack frame_ of dynamic size, which [depends on some cpu features ](https://elixir.bootlin.com/glibc/glibc-2.35/source/sysdeps/x86_64/dl-trampoline.h#L80)configured to build `glibc`. #### Now the **WEIRED** comes. To get the exploitable address kept, the gdb **must attach (not spawn)** to the target program. But it won't stop before resolving `printf@got`, so let's patch it first. There is a perfect patch point before calling `init()`. The original instruction `mov eax, 0` takes lots of bytes, which can be simplified to `xor eax, eax`. And the rest of bytes are enough to do an infinite loop: ![CleanShot 2024-02-05 at 19.04.22@2x](https://hackmd.io/_uploads/S1_mZHC9a.png) **start the program with `socat tcp-listen:xxx,fork,reuseaddr exec:vuln_patched`**. Make a connection, attach the gdbserver, and we'll be able to break before `init()`. Now try debugging the process, we'll see something different: ![CleanShot 2024-02-05 at 22.17.31@2x](https://hackmd.io/_uploads/HJHoCDRqp.png) #### AMAZINGLY Due to this aligment (64 bytes, required by `xsavec`), the stack space of attach-method process enlarged `0x10` bytes than the spawn one, which **happen to** let the "old `rbp`" alive until calling `vuln()` More tests show that it seems to be guaranteed that the address of processes spawned by gdb are **tweaked to be aligned**, that when entering the `_dl_runtime_resolve_xsavec()`, the `rsp` will have always been aligned. Thus no extra space will be alloced, and no former data is going to be leaked. ## Reflections It's actually quite common in `gblic` that do alignment for taking advantage of powerful extended instruction sets. Most of these case are a restriction. Such as `rsp` of `system()`, you get _segment fault_ if the stack is not aligned to `0x10`. But it is so rare that the memory / stack layout is affected by alignment, which happen to be the key to some special exploits. This interesting experience has been a great reminder that sometimes the seemingly random stuff may come from a very deep, obscure, but deterministic mechanism, which inspires us to keep exploring and diving.