That's what you get for making a binary so user-dependent.
Get a shell.
The purpose of the challenge was to teach you the concept of stack pivoting. Unfortunately no one (so far) solved it the intended way, so they practically learnt nothing new. As for my PoC now (which was the intended way):
The binary has an out-of-bound read (both positive and negative indices) and a partial out-of-bound-write (negative indices). At offset -9 from the integer array (meaning - 9*4 bytes since we are on a 32-bit arch) is the return address of any function you call inside main(). What do I mean by that? Let's say store() is being called.
Stack grows | ... | <--- main's frame
towards low | uint* array |
| ... | <---- store's frame
Store() receives a stack pointer as an argument, the integer array to be precise. It's really important to note the stack pointer part. Why? Well, since we have an OOB read and a partial OOB write, we can affect stack values below and above the integer array. And as we all know, what's definitely stored on the stack? The return address!
The way to find the exact offset where the return address of store() is can be done either by trial and error (kinda skid-ish) or by looking at the assembly.
0804904b lea eax, [esp+0x20] /* uint array */
0804904f mov dword [esp], eax
08049052 call store_value
So the array is at offset +0x20 (32 in decimal) from the stack pointer. Here's the stack view right when store() is called:
| ... |
esp + 0x20 ---> | uint* array |
| ... |
old esp ---> | uint* array | <--- mov dword [esp], eax
| ret addr | <--- call store_value
new esp ---> +--------------+
I think it's crystal clear now as to what's going on with the stack and why at offset -36 you can overwrite the return address.
As for the stack pivot trick now, I overwrote the return address with the gadget add esp, 0x2c (could have used a different gadget for pivot ofc) which means the stack pointer will point 0x2c (44 decimal) above (higher address) from where it was pointing to.
Keep in mind, once store() is about to return, esp is pointing to where old esp was pointing to on the above ascii-art. Which means, the array buffer and esp will have 0x20 bytes difference. In other words, at offset esp + 0x20 we can reach the 1st array value (0th index). But, since we are adding 0x2c to esp (0x20 + 12) we can reach the 4th array value (3rd index). That's why I stored the gadgets from the 3rd index and up.
I hope it makes more sense now.