A Strange Alignment Issue with GDB behind A Simple Challenge

*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 to 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

You can read the original writeup 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

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 functions will create stack frame at exactly same address.
CleanShot 2024-02-05 at 15.09.56@2x


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

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

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

CleanShot 2024-02-05 at 21.43.28@2x

Okay… then WTF is xsavec?

From the source we know that _dl_runtime_resolve_xsavec is a variety implementation of _dl_runtime_resolve, and from the instruction manual 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


The prologue of this function build a stack frame of dynamic size, which depends on some cpu features 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

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


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 cases 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.