eat_my_bugs
- Event: p4ctf 2023 teaser
- Category: pwn
Preliminary
This is a challange that I prepared for CTF contest organized by my CTF team p4
.
Files can be downloaded from here.
Following files are provided:
eat_bugs.c
ld.so
libc.so
As you see there is source code but no binary. I decided not to publish binary for CTF players because it would make spotting this bug easier.
Vulnerability
I spoiled in a title of my blog post about the vulnerability type but CTF participants didn’t receive any spoilers.
The vulnerability is not where people always look - it’s not inside a function but in global variables.
Let’s see:
There is no comma at the end of one line - after Turkey
:
Some people spent a lot of time trying to find this bug because it’s unusual bug type but there are 2 methods that allow us to spot this bug easily:
The first:
$ clang eat_bugs.c -o test -Wall -Wextra
eat_bugs.c:18:2: warning: suspicious concatenation of string literals in an array initialization; did you mean to separate the elements with a comma? [-Wstring-concatenation]
"Duck,", "Lamb,", "Goat,", "Seafood,"};
^
eat_bugs.c:17:33: note: place parentheses around the string literal to silence warning
{"Pork,", "Beef,", "Chicken,", "Turkey,"
The second is to use ChatGPT
.
After just pasting the whole code we get some results and somewhere between meaningless information we can spot:
Missing Comma in Meats Array: There's a missing comma in the meats array after "Turkey". It should be "Turkey,", to maintain consistency.
So our array meats
will contain 8 elements:
"Pork,"
"Beef,"
"Chicken,"
"Turkey,Duck,"
"Lamb,"
"Goat,"
"Seafood,"
and an empty string at index 7
Now you can stop and think how to exploit this vulnerability or you can read the post further.
Hint 1
Ok, let’s look at the code:
The user just provides a type of food (fruits/vegetables/meats/drinks/bugs) and the index.
Food names are copied into plate
buffer. It can be for example Apple,Beef,
string.
After the loop, the last comma is removed: plate[plate_len-1]='\x00';
.
After this operation, our buffer will look Apple,Beef
.
Elements must be 2 or more.
If the user provides meats
and index 7, then the empty string is added.
When providing it 2 times, the buffer will be an empty string.
Hint 2
The operation plate[plate_len-1]='\x00';
does plate[-1]='\x00';
We have buffer underflow, we can overwrite one byte before the buffer…. but it doesn’t matter, there is nothing interesting to overwrite.
Hint 3
What’s most important - buffer plate
is uninitialized now.
In usual situation we copy strings to the buffer and put null byte at the end.
In the case of empty string, buffer is not ended by null byte.
After the loop uninitialized buffer is printfed: printf(plate);
.
Hint 4
It can be format string vulnerability, we need only to find how.
Hint 5
You need to know how stack works in binaries and this blog post is not about this.
If you don’t know maybe you will find out somewhere on YT.
The basic idea is that if some function A
calls some another function B
,
function B
finishes and A
calls next function C
- then it can happen that C
will have their variables
at the same place (address) in memory where B
had their own variables.
If B
won’t clear data before returning and C
won’t initialize their variables at this place,
then magic happens.
Hint 6
Remember that for example atoi("2%s")
works good and returns 2
so we can write format string payload to tmp
buffer.
But function read_int
is called several times, we are interested in main -> read_elements -> read_int
.
Example
$ ./eat_bugs
Tell me your name: penis
How much elements on plate: 2 %p
Type of food: 2
Idx: 7
Type of food: 2
Idx: 7
Good choice penis
Here is your yummy plate:
2 0x1
Exploitation
Let’s create a useful function. In my exploit it’s called go
It just does one format string attack, the example of how to use this function is just go(b"%18$p#")
.
Let’a also create some helpful functions:
The next step is to leak the stack address, you do it by leaking different %p
’s and looking for addresses that look like a stack.
The stack is at %18$p
What we can do with it? look below:
make_plate
allows us to do one format string attack so the whole loop allows us to do it 3 times.
We prefer more so we want to overwrite people
variable to a negative value.
This variable is placed somewhere on the stack but we don’t know where, let’s find it.
My idea was to use the following code, it writes a value from provided address 2 times.
If we guessed an address of people
, it should return 1
and later 2
.
We try this code with people_address
pointing at different addresses and it turned out that
people_address = stack-8*36
gives:
0x1e276cab0
0x2e276cab0
This is very interesting! people_address = stack-8*36+4
gives us:
0x1
0x2
Yeah, we need to remember that variable people
is 4-bytes of size. It turned out that
address of this variable is not aligned to 8 bytes - just some space optimization by a compiler (I was trying various optimization flags for gcc to make format string bug possible).
Following code just overwrite this variable to a negative value:
Finishing the challenge (boring)
From this point, the rest of exploitation is straight-forward - the same as in usual entry-level challenge where you have a format string vulnerability and no binary, so I won’t be writing details of this.
You can like LiveOverflow - Format String to dump binary and gain RCE if you don’t know this trick.
Following steps in my solution are:
- leak base addres
- dump the binary
- leak libc base
- find a good place on the stack where is kept the return address
- write ROP in this place by format string attack
- exit
And we got a flag which is p4{43nrd3n82j7gf___https://www.youtube.com/watch?v=LSerlz47srg}
Here is full exploit source.
Or you can download also binary but remember that this file wasn’t provided during CTF.