Leek
Writeup for Leek (Pwn) - Angstrom CTF (2023) ๐
Video Walkthrough
Reversing
source.c (manually renamed)
void input(void *user_input)
{
size_t buffer_len;
long in_FS_OFFSET;
char buffer [1288];
long canary;
canary = *(long *)(in_FS_OFFSET + 0x28);
fgets(buffer,1280,stdin);
buffer_len = strlen(buffer);
memcpy(user_input,buffer,buffer_len);
if (canary != *(long *)(in_FS_OFFSET + 0x28)) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
return;
}
void main(void)
{
__gid_t __rgid;
int iVar1;
time_t tVar2;
char *userinput_chunk;
char *random_chunk;
long in_FS_OFFSET;
int count;
int i;
char buffer [40];
long canary;
canary = *(long *)(in_FS_OFFSET + 0x28);
tVar2 = time((time_t *)0x0);
srand((uint)tVar2);
setbuf(stdout,(char *)0x0);
setbuf(stdin,(char *)0x0);
__rgid = getegid();
setresgid(__rgid,__rgid,__rgid);
puts("I dare you to leek my secret.");
count = 0;
while( true ) {
if (99 < count) {
puts("Looks like you made it through.");
win();
if (canary != *(long *)(in_FS_OFFSET + 0x28)) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
return;
}
userinput_chunk = (char *)malloc(16);
random_chunk = (char *)malloc(32);
memset(random_chunk,0,32);
getrandom(random_chunk,32,0);
for (i = 0; i < 32; i = i + 1) {
if ((random_chunk[i] == '\0') || (random_chunk[i] == '\n')) {
random_chunk[i] = '\x01';
}
}
printf("Your input (NO STACK BUFFER OVERFLOWS!!): ");
input(userinput_chunk);
printf(":skull::skull::skull: bro really said: ");
puts(userinput_chunk);
printf("So? What\'s my secret? ");
fgets(buffer,33,stdin);
iVar1 = strncmp(random_chunk,buffer,32);
if (iVar1 != 0) break;
puts("Okay, I\'ll give you a reward for guessing it.");
printf("Say what you want: ");
gets(userinput_chunk);
puts("Hmm... I changed my mind.");
free(random_chunk);
free(userinput_chunk);
puts("Next round!");
count = count + 1;
}
puts("Wrong!");
exit(-1);
}A chunk of 32 random bytes is created and we need to supply the matching bytes.
Do that 100 times, and we get the flag.
If we send 31 bytes, the output is leaked!
exploit.py
The secret comparison works but we get a corrupt pointer error when the random_chunk is freed.
Set a breakpoint at the free (break *0x4016b5):
Remember chunk layout:
Setup a breakpoint after both chunks were created (break *0x4015e2) and check the heap:
And a snippet of the chunks:
So, we should be setting the size of the chunk 0x234d2b0 to 0x31
To do that, we supply 0x00 * 24 into the userinput_chunk which fills up 0x234d2a0 until we reach the size of the next chunk, where we write 0x31 while being careful to ensure correct padding, e.g. 0x31 + (0x00 * 6).
Solution
TLDR; when we supply 31 bytes into our 16 byte userinput_chunk, we merge the chunks so that when our chunk is printed back to us, we also get the random data.
After supplying the secret, we need to repair the heap layout (fix the size of the random data chunk), ready for the next loop.
solve.py
Flag: actf{very_133k_of_y0u_777522a2c32b7dd6}
Last updated
