Playing Hacks and Stuffs!
Hi! In this writeup I’ll give the solution to all the binary exploitation challenges. Maybe if I have time and I’m online I’ll try put the other challenge I solved
Have fun reading!
I first connected to the remote instance but at the moment it is down
So let’s work with the binary
Seems we can purchase the flag is our amount is $20000
but currently our amount is $10000
We can also purchase a test flag which worth $3000
Let us do that
Ok it works but now what’s our current account balance
Nice it deducted $3000
from our balance which is expected
At this point we can try purchase a negative value of flag
Ok that seems to work now we can try to buy the real flag
We get content of flag not found why?
If you run ltrace you will see it trys to open up the flag
So we can create a fake flag file and run the program again
That works!
If you want to know why that works you can decompile the binary in ghidra and view the functions used by the program
I won’t go through the whole source but I’ll show you where the vuln lies
We can see that it will subtract the multiplication of the number of flag to be bought
and the fake flag price
with our current balance
Since it doesn’t check if the number of flag to be bought is negative therefore the whole arithmetic will be changed to:
currentBalance = currentBalance + nFlag * fakeFlagPrice
With that our balance would be increased therefore bypassing this check making us get the real flag
During the ctf I didn’t manage to solve this for some silly reason anyways this is an upsolve.
After downloading the binary I checked the file type and protections enabled on the binary
We are working with a x64 binary which is dynamically linked and not stripped.
The only protection not enabled is Stack Canary.
Opening the binary in ghidra for decompilation shows this available functions
Let us view the main function
undefined8 main(void)
{
code *shellcode;
setup();
shellcode = (code *)mmap((void *)0x0,0x1000,7,0x22,-1,0);
printf("%s","Easy! Fire it up: ");
fgets((char *)shellcode,23,stdin);
(*shellcode)();
return 0;
}
It first calls the setup
function which does some buffering and nothing much
But the main function is pretty small and nothing much going on
Here’s what it does:
mmap
Basically all this binary does is to receive our input then run it as shellcode
But the catch here is that it has to be a 23 bytes shellcode
Well we could just google x64 23 bytes shellcode
doing that would lead here
If we try that it won’t work
The shellcode looks good but the thing is here:
push 59
pop rax
That part can be optimized using this assembly instruction
mov al, 59
Basically instead of push 59
to the stack and putting it in the rax register we can just directly put it in the lower byte register of the rax register
Also this shellcode is basically called execve
which requires three arguments /bin/bash, 0, 0
Modifying the shellcode to that worked
Here’s my solve script
After downloading the binary I checked the file type and the protection enabled on it
We can see that this is a x64 binary which is dynamically linked and not stripepd
The only protection enabled on the binary is NX (No-Execute) and PIE (Position Independent Executable)
So basically when NX is enabled this means that the stack is not executeable meaning we won’t be able to execute shellcode that’s placed on the stack
While when PIE is enabled that means when ever we run the binary it will get loaded into random addresses
So on each execution the binary memory address would change
To understand what the binary does I loaded and decompiled it in ghidra
Here’s the main function
undefined8 main(void)
{
char local_98 [143];
char option;
setup();
memset(local_98,0,0x82);
fwrite(&banner,1,0xbb,stdout);
__isoc99_scanf("%c",&option);
if (option == '1') {
puts("Go back to where you came!");
/* WARNING: Subroutine does not return */
exit(0);
}
if (option == '2') {
question();
fwrite("Would you like to tell me more about pointers? (y/n): ",1,0x36,stdout);
__isoc99_scanf("%c",&option);
if (option == 'y') {
question();
fwrite("Anything else? (y/n): ",1,0x16,stdout);
__isoc99_scanf("%c",&option);
if (option != 'y') {
if (option == 'n') {
puts("Cheers mate!");
/* WARNING: Subroutine does not return */
exit(0);
}
puts("It\'s a y/n question :)");
/* WARNING: Subroutine does not return */
exit(0);
}
fwrite("Shoot: ",1,7,stdout);
getchar();
fgets(local_98,0x82,stdin);
printf(local_98);
}
else {
if (option != 'n') {
puts("It\'s a y/n question :)");
/* WARNING: Subroutine does not return */
exit(0);
}
getchar();
fwrite("Hate to see you leave. Were the challenges fun? (y/n): ",1,0x37,stdout);
__isoc99_scanf("%c",&option);
if (option == 'y') {
puts("Splendid!");
}
else if (option == 'n') {
puts("There\'s nooo waaay!");
}
else {
puts("It\'s a y/n question :)");
}
}
return 0;
}
puts("It\'s either 1 or 2 :)");
/* WARNING: Subroutine does not return */
exit(0);
}
I’ll work through each of what this binary does:
First it prints out some banner which is more of the option and receives our input option and if our option chosen is 1
it will exit
Option 2 tends to perform more things
So it will ask a question and if our answer is y
it will call the question
function
Here’s the decompiled function
void question(void)
{
char buffer [144];
memset(buffer,0,0x82);
fwrite("Educate me, what\'s so interesting about pointers: ",1,0x32,stdout);
getchar();
fgets(buffer,0x82,stdin);
printf(buffer);
return;
}
So basically what all this does is to receive our input and print it our back using printf
Back to the main function, we are asked the same question again if our answer is y
it will call the question
function again
And after that it would ask Anything else
if our answer if n
it will exit the program
Else it receives our input and prints it out using printf
But if the initial question is n
it would do this
Nothing much going on there so I won’t look explain that
At this point the vulnerability is quite obvious and it’s occurs here:
void question(void)
{
char buffer [144];
memset(buffer,0,0x82);
fwrite("Educate me, what\'s so interesting about pointers: ",1,0x32,stdout);
getchar();
fgets(buffer,0x82,stdin);
printf(buffer);
return;
}
And here:
fwrite("Shoot: ",1,7,stdout);
getchar();
fgets(local_98,0x82,stdin);
printf(local_98);
So the vulnerability here is Format String Vuln
And that happens because the binary uses printf
to print out our input without using a format specifier
With that we can leak address off the stack and also exploit the binary
Here’s how my exploitation would go:
printf@got
to system
in libc so that on the third part where printf
is called on our input it would be evaluated as system
therefore giving us command executionAnother thing to know if where the offset of our input is on the stack
And we can easily calculate it using this
At offset 6
is where our input is on the stack
We also need an offset where a binary address and libc address is on the stack so I made a simple fuzz script to get me that
Ok but before we move forward one thing to note is that we would need the libc for solving this but since it wasn’t provided I asked one of the mod if the docker instance is the same for all challenges and he said yes
So I got rce on one of the web box (Demon Slayer) then transferred the libc for it to my device and patched the binary using pwninit
With that said the binary would be the same as the one in the remote instance
For the second chain I’ll perform a Global Offset Table (GOT) overwrite of printf@got
to system@libc
Here’s my solve script
Doing the usual gives this
Note that I already patched the binary with the remote libc file
So we’re working with a x64 binary which is dynamically linked and not stripped
The protection not enabled is PIE
Using ghidra I decompiled the binary here’s the main function
undefined8 main(void)
{
undefined8 buffer;
undefined8 local_30;
undefined8 local_28;
undefined8 local_20;
code *idk;
undefined8 *mmap_;
setup();
mmap_ = (undefined8 *)mmap((void *)0x999999000,0x20,3,0x21,-1,0);
idk = notcalled;
fwrite("Tell me, what\'s your strategy here: ",1,0x24,stdout);
read(0,&buffer,0x20);
printf("Riiiiight, %s",&buffer);
*mmap_ = buffer;
mmap_[1] = local_30;
mmap_[2] = local_28;
mmap_[3] = local_20;
fwrite("This might actually come to fruition. Try fire it up: ",1,0x36,stdout);
fgets((char *)&buffer,0x100,stdin);
return 0;
}
The binary is fairly simple and here’s what it does:
mmap
notcalled
and what it does is just a ret
call0x20
bytes of our input and stores in the buffer
variable0x100
bytes of our input and stores in the buffer
variableSo the buffer overflow is pretty obvious right? but the issue is because PIE is enabled this is going to look kinda hard
But there’s another issue with this binary
And it’s here:
printf("Riiiiight, %s",&buffer);
It uses printf
to print our input and this time it uses a format specifier %s
but the issue is this
So when printf
is used it would print our input till it meets a null byte this means that if we give it a specific amount of bytes let’s say we full the buffer with characters it would leak values
In this case our buffer can hold up to 32
bytes of data so I did some trial and error and got the best leak to be at 31
bytes
With that said the leak turned out to be a binary section address and I just calculated the offset to the elf base address
From here since the second fgets
gives us a buffer overflow we can calculate the offset to overwrite the instruction pointer
It is 56
.
With this we can just perform a ROP technique called Ret2Libc
But initially I was trying to call execve()
since it had some available gadgets but it didn’t work cause we need /bin/bash
to be placed somewhere in the binary but that I couldn’t do
So I just went with Ret2Libc
Basically what that would do is this:
printf@got
using printf@plt
main address + 0x1231
to avoid stack allignmentone_gadget
or do system(/bin/sh)
Here’s my solve solve
First I ran the binary to know what it does
It asks for a pin
I used ltrace
which is a library trace to know what’s happening
It uses strcmp
of our input with the flag
So I just ran the strings
command and grep for the flag
Flag: acdfCTF{5tr1ngs_b1n4ry_t0_g3t_fl4g}
First thing I did was to run it to know what it does
We can use ltrace
Ok it uses strcmp
but this time around we can’t use strings
command cause it’s likely not hardcoded in the binary
I opened it in gdb got the list of function available
Since it uses strcmp
therefore our input would be in the rdi
register while the expected value would be in the rsi
register
So I set a breakpoint before the strcmp
call
Now when we run it we would get the flag in the rsi
register
Flag: acdfCTF{Th3_p3rf3ct_r3c1p3_for_3t3rn1ty_l1f3}
And that’s all :P
I played with team sudoers
and we got 3rd