At first glance, this binary is exactly what it’s name tells us: an echoservice.
So, it seems to be vulnerable to format string attacks, but like the challenge description already stated, we won’t be able to use %n.
Ok, this might get interesting. But for a start, just let’s use the format string in the casual manner to leak some addresses. Since PIE and ASLR is active, we’ll sure need some later on.
Ok, with this out of way… How can this be exploitable without being able to overflow a buffer or using %n.
Well, that stumped me a while, but after reversing the binary, the idea arose, that this might have something to do with the objective c implementation for format strings.
And yes, they added an additional format string parameter : %@
From the apple developer reference:
Let’s give it a try.
Well, doesn’t this look intriguing?
Analyzing it in gdb, shows, that it breaks in objc_msg_lookup trying to dereference our input string and stuffing it into rbp
Since we control rdi, we can also control the value rbp will get. Let’s start forging a frame for setting the registers.
Searching our input string on the heap and calculating it’s offset from the leaked heap address gives us a base address to work with.
From here on, it’s just some debugging work, finding the correct offsets for our variables and build a frame on the heap, so that the registers get overwritten with the correct pointers.
Let’s highlight the interesting parts from objc_msg_lookup, which will get called in one print call
Again, since we control rdi, we control from where rbp gets filled. We aren’t able to influence raxat this point, but rdx gets filled from rbp+0x40 , so we’ll be able to control the value for rdx also.
And here it goes on. Since we control rdx we control r8. rax will be 0x3 and rcx will be 0x13 at this point.
By knowing this and controlling r8, we’re also controlling rax…
And now something beautiful happens :)
Calling rax…
Though we cannot overwrite an specific address in memory like we would have done with %n, but that shiny new format string parameter allows us to call a function instead.
So our payload for setting up a frame to get to this call looks like this:
We should be able to use a magic gadget now to trigger a shell. On my local machine, none of the constraints for one_gadget could be satisfied, since my stack was filled with junk.
I just added a call to the function, which reads our input, thus creating a new stack frame and now successfully executing the magic gadget.
A local shell popping up… Victory within reach… But… remote service says no =(
This took me quite a while to fix. It turned out, that the heap structure on the remote machine was different, and so our frame on the heap wasn’t stored at the same offset from the leaked heap address.
In despair, I wrote a “heap scanner”, putting 1000 A’s on the heap, and tried to find them with %13$s
Not a very clean method, but with this, I was able to find the payload on the heap again. Whilst becoming desparate, not getting the exploit working, I also had rearranged the frame.