FSOP on stdin
Background
This technique was used to solve a challenge called suisoku
created by Luma
which was the least solve pwn (among the solveable ones ofc) having only 2 solves.
Analysis
The flag is loaded somewhere in data, the target is to read from that address
void *sub_401296()
{
FILE *stream; // [rsp+0h] [rbp-10h]
stream = fopen("flag.txt", "r");
if ( stream )
{
*(_BYTE *)(fread(&unk_404240, 1uLL, 0x7FuLL, stream) + 4211264) = 0;
fclose(stream);
return &unk_404240;
}
else
{
printf("Error reading flag. Contact admin.");
return 0LL;
}
}
The vulnerability was quite easy to spot, the full code being under 100 lines, here are the relevant functions
int __fastcall custom_read(__int64 a1, int a2, const char *a3)
{
size_t v3; // rax
LODWORD(v3) = a2;
if ( (unsigned __int8)a2 <= 8u )
{
memcpy(s1, (const void *)(a1 + 2), (unsigned __int8)(a2 - 2));
v3 = strlen(a3);
if ( (unsigned __int8)(a2 - 2) == v3 )
{
LODWORD(v3) = strcmp(s1, a3);
if ( !(_DWORD)v3 )
LODWORD(v3) = puts("Wow, you guessed it!");
}
}
return v3;
}
ssize_t __fastcall custom_print(__int64 a1, unsigned int a2)
{
ssize_t result; // rax
result = a2;
if ( (unsigned __int8)a2 <= 8u )
return write(1, (const void *)(a1 + 2), (unsigned __int8)(a2 - 2));
return result;
}
These were called from main using this logic
v4 = 2;
while ( v4 )
{
fgets(s, 256, stdin);
s[strcspn(s, "\n")] = 0;
if ( s[0] == -59 )
{
custom_read((__int64)s, (unsigned __int8)s[1], v5);
--v4;
}
else if ( s[0] == -48 )
{
custom_print((__int64)s1, (unsigned __int8)s[1]);
}
}
puts("Goodbye.");
The vuln is a TOCTOU interger overflow, the program first checks if the length (a2) is equal or lower than 8, if yes then it unsigned subracts 2 from it and uses that as length, this is problematic as if lengths such as 1
and 0
are sent, they will pass the first check, but after the -2
they will become large numbers (254, 255 respectively).
This in turn allows us to read and write up to 255 from a 8 byte buffer, should be easy to get shell now right?
Exploitation
Actually not really, as the buffer is located just before .bss
so we have almost nothing we can overwrite. The only thing available in the 255 bytes after the buffer is a stdin
pointer, but can we actually exploit just this?
Firstly we can easily leak libc base using the print function as the stdin pointer is a libc address, now that we have that what can we accomplish by overwriting a stdin
pointer?
Doing some research into this it turns out we can gain arbitrary write using a technique called FSOP, I won’t give the payload here as it is quite large and there are many articles on this on the internet already.
Now armed with an arbitrary write I chose to target the libc stdout
structure to get an arbitrary read. The issue I had during the CTF, which took me a long time to spot was that i was writing directly in stdout
so when I sent another command to exit the program after writing to it stdout
the fake FILE structure header would be overwritten and the exploit would fail.
In order to fix this issue we can just write at stdout-0x10
or any other offset before it so that when we give the 2 bytes for exit it is written at stdout-0x10
so it doesn’t affect our payload.
Alternatives
In addition during the CTF, I also discovered an alternate path that sadly only worked locally as it required a very specific stack setup.
If you go back to the main function we can see that puts
was called the first time only after we exited the loop already, so in theory if we had a one shot gadget we could overwrite the libc got for puts with it.
The local stack layout was something like
random address
puts
flag_addr
So I found a perfect gadget for this case (another pop was done during the pop rbp of a function removing the random address from the stack)
0x0000000000125a00 : pop rax ; pop rdi ; call rax
But sadly the remote instance had a small difference so this didn’t work.