Challenge Description
You like linking with others, don’t you?
Challenge Overview
The challenge allows us to perform 4 operations:
[1] Insert new link[2] Unlink a link[3] Show links[4] Quit
Insert new link
The
do_linkfunction handles the creation of new links. It uses a regex to validate and parse the input format:[<src>](<dst>)
| |
- The function defines a local buffer
char text[512]to store the raw user input. - It reads up to 512 bytes using
fgetsand strips the trailing newline. - After a successful match, it allocates two buffers of size
LINKS_LEN(50 bytes) for the source (src) and destination (dst) strings.
After parsing, the program checks if the src and dst strings already exist in the links array to avoid redundant allocations:
| |
Once the source and destination indices are determined, the challenge stores the connection in a global dynamic array named linking. This is managed through a simple realloc call:
| |
Unlink a link
The
do_unlinkfunction is responsible for removing a link and cleaning up any associated references.
| |
Show Links
The
show_linksfunction is responsible to shows us all links.
| |
Quit
In the end, quit, it calls
exit()function. oh no, no more main ret
Vuln
The vulnerability in this challenge lies within the do_link function. Specifically, the vulnerability stems from how the function handles user input:
| |
However, following this, two chunks of size LINKS_LEN are allocated to store the src and dst strings provided in the input:
| |
This means that by providing a valid regex string with a payload longer than LINKS_LEN, we can achieve a heap overflow of up to 456 bytes (512 - 50 - 6).
It is important to note that links cannot contain null bytes, and neither the src nor the dst fields can be empty.
Finally, it is worth noting that the program provides a dedicated RWX (Read-Write-Execute) memory region at startup::
| |
Exploit
To begin, I allocated several chunks to ensure two of them were adjacent. Then, I freed them in a specific order so that the second chunk would remain untouched after the subsequent allocation:
| |
| |
It is crucial to remember that the application always allocates two chunks at a time. Even if we provide an existing src or dst, the program first allocates the memory and only performs the free() afterward. Therefore, maintaining an “extra” chunk (B) is essential to ensure our target chunks remain undisturbed.
By following this specific sequence, we can reliably trigger the overflow:
- Allocate chunk B: Acts as a buffer/placeholder.
- Allocate chunk E: This will be our “overflow source.”
- Free chunk B: Clears the space before/around our target.
- Result: Chunk F remains untouched in memory, positioned perfectly to be overwritten by the overflow originating from chunk E.
From here, we can leverage the overflow to leak the secret:
| |
(I incremented the secret value by 1, as this offset will be necessary for a specific operation later in the exploit)
After this leak the heap will be destroyed, so to continue allocating we need an additional send whose chunks must no longer be touched; this send I called “Bob l’aggiusta tutto”
| |
After this, I made many allocations so as to have some chunks to play with later; also, I managed everything in a way that generates a small bin and puts it in a convenient position to leak the libc base. (The challenge did not provide the libc, but it gave the dockerfile from which I retrieved it)
| |
Subsequently, I used the same technique again to allocate on the environ, leak the stack frame return address of do_link, and then allocate over it to overwrite the ret with the pointer to the RWX zone provided to us:
| |
Note that since I cannot send null bytes, otherwise the regex breaks and will not validate it, I send all addresses without null bytes; consequently, it is necessary to return not to 0x1337000, as it contains a null byte, but to 0x1337008. This means that we only have 24 bytes of space to write a working shellcode.
The shellcode I wrote is this:
| |
I think it is very clear; the only peculiar instruction is cdq, which is very small, saves a lot of space, and allows for completely clearing RDX if EAX is positive (which in this case it is)
Below I leave the entire script:
| |
~SoloPietro