Try   HackMD

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

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

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:

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

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.

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

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().

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

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.

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

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:

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

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:

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

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:

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

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:

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

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.