Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Binary's a little wacky, let's look into it.
There's no libc used whatsoever, everything is syscalls.
When the binary starts, it calls the _start function. This function is simple, it uses sys_write to print out "Can you pwn me?", then calls fn1, then jumps to the function nope.
Let's disassemble fn1. sets rax to the xor of 0xbeef and 0xdead, and pushes this onto the stack.
It then moves rbp to be rsp-0x400, and reads 0x800 bytes from stdin at rbp. Afterwards, it pops rax off of the stack, XORs it with 0xbeef, and checks if the value is 0xdead.
If not, it jumps to the nope function. Otherwise, it rets, popping rbp off the stack before hand
This creates a simple buffer overflow. At the time of our input, the stack looks like this
rbp 0x400 bytes rsp -> value to be popped into RAX value to be popped into RBP return address previous stack frame
we can overflow with 0x400 bytes of padding, and then the value of 0xdead ^ 0xbeef.
The binary has no protections whatsoever, including lack of NX.
There is a RWX segment within the binary. Because fn1 uses rbp to mark where it starts its input, and we get a pop into rbp, we can set rbp to be the address of the RWX segment, and then ret into the instruction in fn1 that starts the input. Then, we enter shellcode, and ret to that address. Script below. (I wrote custom shellcode that uses the fact that /bin/sh is written just before it)
We can ls, there is flag.txt. No cat though.
We'll have to use the bash builtin, read, instead.
Just do:
to execute a while loop that echoes all lines from flag.txt(note echo is a bash builtin too)
The binary is stripped of symbols, and even radare2 cannot resolve main, so this is a little difficult.
I stepped through in gdb in __libc_start_main until the call rax instruction.
At this point, rax was 0x4011d6, indicating to us that this was the address of main. I used x/100i to view all the instructions at this point, and found a small little buffer overflow.
We can use pattern.py to find the offset till the return address is 497.
Looking a little past main, there appears to be another function. It calls open, then read, then puts.
If we do some calculation on RIP and use x/s, we find that it calls open on flag.txt! This must be the flag function.
Essentially, we have a simple ret2win exploit. Overwrite ret address with the flag function.
Conveyor belt.
Running the binary, we have two options - add a part to the conveyor belt, and review the belt. When we review the belt, it goes through all the parts we have one by one, printing them, asking us if they are safe. If we say they aren't(that is, not responding with 'Y' or 'y') then we can edit the part.
Let's chuck it into ghidra and see what more we can get.
First of all, we see that the add_part function is like so - It seems to take a parameter being the address of the previous part. allocate 0x80 bytes of data. Read 0x80 bytes of data from stdin into this place. If the string contains "sh", say the part isn't safe, free the allocated data, return the parameter we got(essentially dont make any chunk and pretend nothing ever happened)
If not, then edit datapointer+0x78 to be the address of the previous part. Essentially, the parts are in the structure
Forming a list. The problem is, whenever it asks us to edit a part, it reads 0x80 bytes when only 0x78 are the data segment - giving us an overwrite of the previous_part field. More on this later.
Let's look at the safety check function. It starts on the last part, printing it, asking is if it's safe, and allowing us to edit it if it isn't. Here's our main vuln. An extra 8 bytes are read, letting us overwrite the previous_part field. The function then grabs the previous part field, visits that, prints it, asks if it's safe, etc. etc. until it hits a previous_part field of 0.
What can we do with this? We can create a single part, then activate the safety check. We can edit the previous part field in-place, and then it'll go wherever we want for the next part! This creates two things
Arbitrary read, as it'll print out the part.
Arbitrary write, as we can say the part isn't safe, and then edit it.
Therefore...
We can set the previous part field to puts@GOT, allowing us to read a libc address. Once we read this, we'll edit puts@GOT too! Let's edit it with the address of system.
Now what? It'll look 0x78 bytes later for the address of the next part, then continue. It'll grab this address, call puts("Next part:") and then calls puts on the next part.
Therefore, if we pretend the next part is the address of /bin/sh, it'll call system("/bin/sh") for us. So, our exploit:
Create new part
Safety check. Say the part isn't safe. Edit it with 0x78 bytes of junk + address of puts@GOT
It'll print the value of puts@got, which is puts@LIBC. Read this value, and subtract appropriate offset to get the libc base. Say part isn't safe. Send system address + 0x70 bytes of junk + /bin/sh address such that whenever it calls puts it'll actually call system, and the next thing it will call puts on is /bin/sh
Script below.
Using web.archive.org, we can see previous captures of a given site.
https://apporima.com/
has two captures, one on 9th May 2020
, and another on 18th April 2020
.
Seeing as this challenge appears to be themed around going back in time, 18th April
seems far more interesting to us.
There is a blog post in the April capture, missing from the most recent version which reads: "Today, I created my first CTF challenge.
The flag can be found at forward slash flag dot txt." If we visit https://apporima.com/flag.txt
, we get a 404 message, but putting the URL in web.archive.org
shows a capture in April which will give us the flag.
(haha maltego go brrr) because i felt like it i just https://instantusername.com/#/
and searched for NahamConTron
i found that he had a github account, so i went on there and looked at his commits he has a repo called dotfiles, and JohnHammond has committed there https://github.com/NahamConTron/dotfiles/commit/db31fa2e124443a7da945844ba2b59700eea0094
this has an ssh key, and also the ip, user and port of the server. if we connect (make sure to chmod 700
the key before connecting), we can see a file called flag.txt, and catting it gives us the flag
When we connect, if we try to enter anything, it sends {number}..well this is awkward..
This reminded me of a TAMU ctf chall I did long ago, there you could send commands but you only got the exit codes. Typing stuff like "llgltltltl" gave an output of 127, which is the command not found exit code. I tried "ls", and this gave 0 - indicating that these were probably exit codes. The key was to somehow communicate information via exit codes and exit codes alone.
With a combination of bash commands, we can accomplish this.
Firstly, var1=$(command). Simple, just sets a variable to the output of this command.
Exit codes can be between 0 and 255. This is enough to communicate one byte at a time.
var2=$(echo $var1 | cut -c number) this will grab a certain character of var1 using the bash command cut, and store it in var2.
Finally, exit $(echo -n $var2 | od -An -tuC) - echo -n $var2 | od -An -tuC will grab the ascii value of the character that var2 represents, plocking exit before this means that the output of this command chain will become the argument for exit, communicating our character via the exit code.
I wrote a simple loop to automate this and get the full output of a command.
This is kinda more rev imo but whatever.
We don't need to understand the main body of the script that much.
What we need to know is that it gets to incredibly large numbers, at which point the two functions it uses - a and b - become much too slow and inefficient to be useful.
Let's review the two functions: What do they do?
Function a simply iterates through all numbers between 2 and num - 1, and returns False if num % i == 0. Essentially, it checks if any numbers below a number are divisible by a number. What's that?
A prime checker.
We can just use Crypto.Util.number.isPrime
, which is much more efficient.
As for b, it's much simpler. It just checks if the reverse of the string version of the number is the same as the number - checks if it's a palindrome. I just copied some more efficient code off stack overflow.