Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Upon connecting to the server, it just sends lots of lines of ctf{pearlpearlpearl}
.
Nothing that interesting. I decided to script this to see if it was like some challenges we had seen in previous ctfs where we required speed, but this was not the case. I did however, notice something very interesting you wouldn't see on a terminal from doing nc - between lines of ctf{pearlpearlpearl}
, it didn't just use newlines, it used \r
too.
In fact, it alternated between the two in a non-regular pattern. I decided to strip all ctf{pearlpearlpearl}
from the output of the server and get the pure \r and \n. Then, I converted \r to 0 and \n to 1 and attempted to convert from binary.
This worked, and yielded the flag
OWO, we do seem to be getting a lot of these discowrd challenges in CTFs, and they arwe vewwy vewwy hawrd, this one especiawwy!
It seems the fiwrst mention of "Discowrd" being wewated to a challenge in the sewver is way back in Juwuly 2019, whewre miwstew benjamin techinson mentions a token, so that couwuld be a fwag! This awlso meant the challenge appeawed to be unsolvwed since then. As we werwe the team to blood this challenge, I think we should definitely make a wrwiteup on this tough challenge.
The bwiefing weads as fowwows:
Join our discord over at [redacted] and see if you can find the flag somewhere.
OwO, whats dis? A Discowd link? I wondewr what wouwuld happen if I wewre to click it >w< Howewew, I remembewed what happened duwwing angstwomCTF, whewre the link was actuawwy a wrickwoll, which was vewwy iwwitating. Considewwing that thewre wewre othewr challenges in RACTF that wewre inspiwred by angstwomCTF, like peawrl peawrl peawrl, this confiwrmed my theowy that the link was actuawwy fake. So, I decided not to click it. I had to get a Discowd invite somehow howevew, so I kept on seawching. Howevew, I wealised that I would need to make a discowrd account in owdewr to actually wead the fwag. I had done this befowre in TJCTF, so I weffewed back to the wrwiteup I cweated for it.
Thewre was something diffewent howevwew. I didnt spot this rwight away but aftewr following my TJCTF wrwiteup didnt wowrk, I rwealised that I was using the same email. This was vewwy cwucial, as this meant I would need to get a new email, and fast. Howevewr, as with most CTFs me and my team do, ouwr wivals PuWuN to 0w0xE4 wewre also twying to solve this challenge, as it was ouwrs and awlso theiw 59th last challenge, thewefowe this was going to be a fiewrce wace to twy and compwete the challenge. In TJCTF, they ouwutdid us by doing Nauwughty, but nowot this time. This time, we had a new membewr on ouwr team, Rowowan, who is quite good at web, and so he would accompany ouwr wesident newrd and web pewrson, Tony, also known as cluwubby789. This time, with Rowowan on ouwr team, we would be suwure to win and cwush PuWuN to 0w0xE4 once and fowr all. We would teawr them to shweds and fwex our shiny RACTF coins in theiwr faces.
Sowo, we had to find a way to get a new email. How wewre we going to do that though? Well, since I have to be able to access this email fowr vewification, I had to find an email I contwolled.
I was wecommended a site called 10minutemail, which would pwovide me a mailbox which I could weceive mail fwom. This tuwned out to wowrk pewrfectly, as we wewre able to wegistewr an account, and since I didnt have to givwe discowd a weal email, I didnt have any issues with twust like I did in TJCTF, which was a massive time save, and in the end is definitely what helped us cwush PuWuN to 0w0xE4.
Nowow that we had a Discowrd account we could uwuse to access discowrd, we needed to actually find a link to the RACTF discowd sewvewr, which we at fiwst assumed would be the location of the fwag. This again, like TJCTF, took quite a long time. Because they hadnt put the link in the bwiefing, this link took wayyy too long to find. And as always, PuWuN to 0w0xE4 wewre rwright on ouwr tail. Ouwr spies had wepowrted that they had found the link and wewre in the Discowd sewvewr, meaning that they wewre vewwy close to the fwag! This couldnt be happening. We wowrked so hard to solve all 0 challenges befowre this, and all ouwr hawrd wowrk would have gone to waste. We needed to keep going and get these all important 50 points.
We seawrched for ages, and pwaying the entiwre time that PuWuN wouldnt solve the challenge befowre us. Suddenly, one of ouwr team membewrs spotted something. If you went to the actual owiginal RACTF page, that is, https://ractf.co.uk/, thewre was a thing that said contact, and on thewre there was a button that said Discowrd.
UWU! This suwrely was the link we wewre looking for! With ouwr newly wegistewed Discowrd account, we could use the link to join the official RACTF Discowrd. This was it. We wewre at the same stage as PuWuN to 0w0xE4. It would just be a rwace to find the fwag.
My initial instinct was to try the good old !fla.g command, as castowrsCTF's also insanewly hawrd Discowrd challenge was to uwuse this command, and then the fwag would be DMed to you.
However, it seems the evil mastewrmind behind the sewvewr, Mistewr Benjamin Techinson, the wollewrcoastewr enthusiast, had developed some sowrt of filtewring the command! So this is why PuWuN to 0w0xE4 wewre taking so long! They must have been twrying to bypass the fiwlter that Mistewr Techinson had put in place for this. So this basicawwy confiwmed that this was the challenge. Twying to bypass the fiwlter was quite easy, since Mistewr Techinson cleawrly did a howwible job at fiwltewring and we bypasswed the fiwlter vewwy quickly. It seems he had neglected vewwy many things, for exampwle, using backslashes to not make the bot delete the !fla.g command. Howevewr, I beweive that he did a few patches duwwing the CTF to make the challenge mowre difficult, so I think it was vewwy lucky that we wewre able to get it done eawrly when the challenge was much easiewr, and of couwrse, to cwush PuWuN to 0w0xE4.
Swiftly typing in "!flag", we waited.
We waited some mowre.
A few milliseconds passed. A few cwickets chiwrped.
Eventawwy, we rweawised that this was all just a red hewwing to divwewrt us. This was not the way to go to get the fwag, and no wondewr PuWuN to 0w0xE4 hadnt gotten the fwag yet, as the filwtewr was way too easy to bypass for it to be a challenge.
So, we decided to look at ouwr TJCTF wrwiteup again.
It seems that the fwag could be hidden in announcements, so we had a look thewre for any hints of the fwag location.
We saw a message by thebeanowogamewr that read
"The Discord flag does not require you to run a command"
so we decided to compwetewy ignowre that and continuwue twying to bypass the fiwlter.
Eventually, after 2 mowre seconds, we decided to give up, as it was cleawr we wewre getting nowhewre.
Well whewre could this discowrd flag be? This was getting intenwse, and we knew PuWuN to 0w0xE4 would be clowose on our tail twrying to find the fwag. So, we kept fwantically seawching.
With time on the line, we fwanticawwy seawched the discowrd, thwough the hype channel, and the sociawl channel, but nothing was to be found.
Suddenly, we noticed an image, posted by some weirdo named willwam845.
It was a vewwy basic image, but it had a stwing in fwag fowmat.
ractf{discord_kinda_rocks}
And it had a celebratowy message congwatulating us on finding the fwag! This had to be it.
We wewre finally going to crush PuWuN to 0w0xE4 for once.
Slowly, I copied the fwag into the fwag box, with just milliseconds to spawre.
Howewvewr, it was wejected? How could this be happening?
PuWuN to 0w0xE4 wewre going to beat us now!! This couldn't be happening.
All these milliseconds we had worked so hawrd for. All that wowrk would hawve gone to waste.
Howevrwer, I was detewrmined to find this fwag.
And that's when I wemembewed.
The angstwom discord wwrwiteup.
Since peawlpeawlpeawl was inspiwred by angstwom, I was cewtain that the discowd challenge must also be inspiwred by it too!
And I was pwoven cowwect.
The fwag was indeed in the channel descwription of the genewal channel, and PuWuN to 0w0xE4 seemed to not have submitted it yet!
We wewre finally going to cwush them once and for all!
Slowlwy, but swiftlwy, I pasted the stwing, which was ractf{the_game_begins} into the fwag box, and then hit submit.
"You have already solved this challenge" it read.
What? How could this have happened??
It tuwrns out howevewr, that Rowowan, the new membewr, had alweady submitted it and gotten blood! We were finally on our way to cwush PuWuN to 0w0xE4!
In the end, we got cwushed by PuWuN to 0w0xE4 once again, as they solvwed EEE much fastewr than we did, meaning we did a gweat job, and cwushed them to bits!
Broken down into logic:
Move forward on tape 1, read the value into reg A
Compare reg A to 0 (read: have we reach end of indexes)
Load reg A into reg X
Load garbage address into reg A
If compare earlier was true jump to garbage and die
Else reset tape 0
---JUMP BACK POINT---
Load value from reg X back into reg A
Step forward in T0
Decrement reg A
Compare reg A to 0 (read: are we at right char)
Load reg A into reg X
Load address of jump back point into A
If earlier comparison not true (i.e not at the char yet) jump to jump back point
Else (we are at the char) read T0 and output, jump back to start
All we are provided with is a C file which #include's flag.txt.
I noticed however it was full of whitespace, which reminded me of the esoteric language WhiteSpace.
I simpled copy and pasted the file into an interpreter, which printed the flag.
The description simply read '🐍📦'.
This (or just googling the challenge name) leads us to the Python Package site PyPi, which had Spentalkux 13.37.
I installed and ran this, and received this message:
My creator left this behind but, I wonder what the key is? I don't know, but if I did I would say it's about 10 characters. Enjoy this. Ztpyh, Iq iir'jt vrtdtxa qzxw lhu'go gxfpkrw tz pckv bc ybtevy...*ffiieyano*. New cikm sekab gu xux cskfiwckr bs zfyo si lgmpd://zupltfvg.czw/lxo/QGvM0sa6
The reference to a key of a given length made me think of Vigenère cipher. I pasted the message into dcode.fr and set the known keylength to 10. Hello, If you're reading this you've managed to find my little... interface. The next stage of the challenge is over at https://pastebin.com/raw/BCiT0sp6 (key: SPENTALKUX)
This contained hex data, which gave us raw PNG data when decoded.
The png contained binary numbers translating to '_herring', and a message to look back in the past.
I checked the version history of Spentalkux, and found and installed 0.9. This returned the message
JA2HGSKBJI4DSZ2WGRAS6KZRLJKVEYKFJFAWSOCTNNTFCKZRF5HTGZRXJV2EKQTGJVTXUOLSIMXWI2KYNVEUCNLIKN5HK3RTJBHGIQTCM5RHIVSQGJ3C6MRLJRXXOTJYGM3XORSIJN4FUYTNIU4XAULGONGE6YLJJRAUYODLOZEWWNCNIJWWCMJXOVTEQULCJFFEGWDPK5HFUWSLI5IFOQRVKFWGU5SYJF2VQT3NNUYFGZ2MNF4EU5ZYJBJEGOCUMJWXUN3YGVSUS43QPFYGCWSIKNLWE2RYMNAWQZDKNRUTEV2VNNJDC43WGJSFU3LXLBUFU3CENZEWGQ3MGBDXS4SGLA3GMS3LIJCUEVCCONYSWOLVLEZEKY3VM4ZFEZRQPB2GCSTMJZSFSSTVPBVFAOLLMNSDCTCPK4XWMUKYORRDC43EGNTFGVCHLBDFI6BTKVVGMR2GPA3HKSSHNJSUSQKBIE
After a long process of trial and error, I solved it. To save you some pain, here's a Cyberchef link: [Here](https://gchq.github.io/CyberChef/#recipe=From_Base32('A-Z2-7%3D',true)From_Base64('A-Za-z0-9%2B/%3D',true)Gunzip()From_Binary('Space')From_Binary('Space')From_Hex('Auto')From_Base85('!-u')&input=SkEySEdTS0JKSTREU1oyV0dSQVM2S1pSTEpLVkVZS0ZKRkFXU09DVE5OVEZDS1pSRjVIVEdaUlhKVjJFS1FUR0pWVFhVT0xTSU1YV0kyS1lOVkVVQ05MSUtONUhLM1JUSkJIR0lRVENNNVJISVZTUUdKM0M2TVJMSlJYWE9USllHTTNYT1JTSUpONEZVWVROSVU0WEFVTEdPTkdFNllMSkpSQVVZT0RMT1pFV1dOQ05JSldXQ01KWE9WVEVRVUxDSkZGRUdXRFBLNUhGVVdTTEk1SUZPUVJWS0ZXR1U1U1lKRjJWUVQzTk5VWUZHWjJNTkY0RVU1WllKQkpFR09DVU1KV1hVTjNZR1ZTVVM0M1FQRllHQ1dTSUtOTFdFMlJZTU5BV1FaREtOUlVURVYyVk5OSkRDNDNXR0pTRlUzTFhMQlVGVTNDRU5aRVdHUTNNR0JEWFM0U0dMQTNHTVMzTElKQ1VFVkNDT05ZU1dPTFZMRVpFS1kzVk00WkZFWlJRUEIyR0NTVE1KWlNGU1NUVlBCVkZBT0xMTU5TRENUQ1BLNFhXTVVLWU9SUkRDNDNFR05URkdWQ0hMQkRGSTZCVEtWVkdNUjJHUEEzSEtTU0hOSlNVU1FLQklF)
Steghide with no password to get moo.txt Upon some research, we can see this is the esoland of COW, which we can compile with http://www.frank-buss.de/cow.html to get the flag
Most of these challenges don't have flags to submit, so we've included Google Maps Links of the Locations.
We can see a Spanish Flag in the background,
and then we can simply google "Spain Tree Man",
which gives us the location of the theme park.
We then look for the structure present in the background,
(the orange rocks) and then using a bit of Google Maps precision stuff, we can get the location pretty accurately
https://www.google.com/maps/@41.0858555,1.1545438,69m/data=!3m1!1e3
Just Reverse Image Search this image or use google lens, brings you around this:
After a lot of research into suspension bridges, we landed at the Ting Kau bridge in Hong Kong New Territories, which is found here:
After looking arount the image, The Haka Bar is mentioned. Look on google maps near the bern side of the french swiss border.
I searched 'war memorial cross' and scrolled through images.
I eventually found one that looked similar to the one in the photo.
It was a 'cross of sacrifice'.
However there is quite a few of them in many countries.
Woa managed to identify the country from some blurred writing in the back ground (Thailand).
From searching thailand cross of sacrifice I managed to find the place it was in and able to submit the flag.
When we download the file, we're presented with a file called flash.bin. Running file
on it, we get that it's just data. If we try and cat
the file, we get a whole load of junk. Great. Lets try adn see if we can enumerate any strings from it.
strings flash.bin
scroll up - yep, there's the flag.
Let's review the code.
There's two interesting functions - put_on_stack, which generates some interesting python bytecode to put a value onto the stack, and returns it.
Then, there's execute bytecode. It takes some bytecode, and, well, executes it. How though?
It loads 256 constants - 1,2,3, etc. matching up to 1,2,3,etc. As global variables, it loads the functions chr, ord, globals, locals, getattr and setattr. We have to use these functions alone to gain RCE. Impossible? No. But lets look into how we can do this.
We input a name. It takes the first 32 bytes of this, and generates some byte code that puts this name onto the stack. It then prepends said bytecode to all bytes afterwards, and executes it. If we put 0-32 chars, this is fine, but if we input anymore, then from the 33th character onwards is executed as python bytecode!
We must generate python bytecode that utilises the small set of functions and variables we have to gain RCE. My final payload in python code form was globals()[chr(101)+chr(118)+chr(97)+chr(108)](chr(95)+chr(95)+chr(98)+chr(117)+chr(105)+chr(108)+chr(116)+chr(105)+chr(110)+chr(115)+chr(95)+chr(95)+chr(46)+chr(95)+chr(95)+chr(105)+chr(109)+chr(112)+chr(111)+chr(114)+chr(116)+chr(95)+chr(95)+chr(40)+chr(39)+chr(111)+chr(115)+chr(39)+chr(41)+chr(46)+chr(112)+chr(111)+chr(112)+chr(101)+chr(110)+chr(40)+chr(39)+chr(99)+chr(97)+chr(116)+chr(32)+chr(47)+chr(104)+chr(111)+chr(109)+chr(101)+chr(47)+chr(114)+chr(97)+chr(99)+chr(116)+chr(102)+chr(47)+chr(102)+chr(108)+chr(97)+chr(103)+chr(46)+chr(116)+chr(120)+chr(116)+chr(39)+chr(41)+chr(46)+chr(114)+chr(101)+chr(97)+chr(100)+chr(40)+chr(41))
Not very readable, but in normal python code,
How can we do this? We can't just go about compiling a .pyc or code using compile() - those files create constants and globals to suit them. We can't do this however, we must only use constants and variables given to us. We can analyse the put_on_stack function, and see what it does
b"t\x00"
- load the first global, chr. We can replace with \x02 to load the 3rd global, globals
b"d<number>"
- load constant for argument. When doing globals(), we don't need this.
b"\x83\x01"
- call current function with 1 argument
b"\x17\x00"
- binary add. does first value on stack + second value. note it's called binary but it can be used to concatenate strings just like adding numbers bcoz its python at the end of the day
we can use this to generate bytecode to call globals, b"t\x02\x83\x00"
. Then, we must dynamically construct the word "eval" using only chr via the same methods the put_on_stack function uses. After that, we use the BINARY_SUBSCR opcode, b"\x19\x00", which computes TOP1 = TOP1[TOP2]
Now what? Loaded on the stack is the function eval. Now, using the same method as put_on_stack again, we construct our python payload that cats the flag. Finally, we wrap it all up with a b"\x83\x01" - call the function eval, loaded from globals()['eval'], with one argument.
I ended up doing some enumeration, and finding flag.txt
in /home/ractf
, then catting it.
This results in the flag ractf{Puff3rf1sh??}. I will post the final bytecode below and my generation script and final exploit script as generator.py
and puffer.py
.
Bytecode:
Very similar to FIAS, so I won't explain the parts about a stack canary.
This time, there's PIE.
What changes? What changes is that the address of our ever-so-important flag function changes every time, as PIE randomises the base of the binary.
What we need to do is leak a value on the stack that we can calculate the binary base from.
We're in luck. When a function is called, the address of the instruction for it to ret to is placed on the top of the stack.
This means if the value isn't later overwritten, the addresses of some instructions, including instructions of functions of the binary, will remain on the stack.
Thus we can leak an instruction of a function in the binary, and calculate the base off of this.
I chose stack item 3, the address of the instruction in say_hi after it calls the pc_get_thunk function. Our exploit:
Leak canary and binary base in one go with format string Send junk + canary + junk + flag address Script below:
Let's run a checksec. Canary, no PIE.
On our first input, there's a format string vuln.
On the second input, there's a buffer overflow as gets is used.
There is also a flag function that seems to call system("cat flag.txt"). We just need to return to this function. There's a canary, so this isn't so easy.
We can, however, use our format string vulnerability to leak the canary value, as the canary is stored on the stack and format strings let us leak values stored on the stack. I used a simple fuzzing script to find the canary offset, which was 11. We can set a breakpoint at the instruction which checks the canary and paste in a cyclic pattern to get the offset to the canary, which is 24 by checking the loaded canary value from the stack against the pattern. Our payload will be:
junk + canary + 12 bytes of junk + address of flag function
A pwn challenge. Just a simple format string exploit.
Protections: Partial RELRO, no PIE, therefore we can overwrite the GOT as it is not read-only.
When our input is asked for, it is printed out using printf without proper format strings.
Our input is put on the stack, so the program is vulnerable to arbtirary writes via %n
.
There's no buffer overflow, and only one input, so we shouldn't be leaking anything.
We can send a simple format string overwrite payload to rewrite puts@GOT with the address of the function flaggy, and the flag will be yours. This works as puts@plt is called just after printf is called on our input, so we can overwrite the GOT entry with a different value.
Thus when the plt is called it will find a different GOT value and jump to that.
Here is the solve script:
From the previous challenge, we know that this file is a compiled binary for the Arduino.
We are given a .bin file but we need to convert it to an Intel Hex file so it can be flashed to the Arduino.
There are various tools to do this, such as Bin2Hex.py, which is linked.
After converting flash.bin, to flash.hex, I flashed it onto my Arduino Uno with:
"C:\Program Files (x86)\Arduino\hardware\tools\avr/bin/avrdude" -C "C:\Program Files (x86)\Arduino\hardware\tools\avr/etc/avrdude.conf" -v -patmega328p -carduino -PCOM3 -b115200 -D -Uflash:w:C:\Path\To\flash.hex:i
I got this command from uploading a sketch to the Arduino with the software.
Once flashed, I checked the Serial Monitor in the Arduino software, but it was only outputting invalid characters.
I thought that the baudrate could be the problem, so I tried turning it down.
There were less invalid characters as I went further down and at 19200, I got the flag:
OK so the binary got patched 4 times lol, I'll just pretend I solved it from the first one
The header reads: mCTZ, indicated the rest of the binary is compressed with 'zstandard'. We can use zstd to uncompress and read the image. It has 2 sections: scode and sin. SIN is ROM (i thought it was input, causing me to delay my solve by at least 3 hours lmao) and SCODE is the code of the program. Extracting the data from these blocks, we can use the specification at https://github.com/Kantaja/MedeaCTF to break down the logic of the program.
Accept input
Call function at 0x3E
The functions reads input into SMAIN (general memory) one char at a time until it reaches a newline, and then returns the length of the string by counting the iterations.
If length of the input != 0x0C, print "Incorrect length!" and die
Else, loop through each char of the input with the corresponding char in SIN.
Take the 2s complement of the SIN char.
XOR ~SIN with the input char
Output the result & 0xFF
And with 0xFF causes no changes to a value, so that can be ignored. We can skip the twos complement bit by prematurely XORing every value in SIN with 0xFF. Therefore the program becomes INPUT ^ SIN = OUTPUT.
However, we dont have any information about input or output beyond their lengths, EXCEPT that OUTPUT is wrapped in flag format. Therefore we can express it as
Let's start from scratch - run the binary. It'll complain to us that we don't have some python library called memecrypt installed. That's odd.. why does it demand a python library if its an ELF file? More on this later. We can install with python3 -m pip install memecrypt
, and the binary runs smoothly, printing out what seems to be a python byte string.
Let's analyse this further. If we spam control c as the program is running, it gives us an error message about a KeyboardInterrupt, saying it's within a file owo.py. We can inspect further in classic reversing tools in ghidra, and find all sorts of python functions like __Pyx_PyObject_GetAttrStr
or __Pyx_PyObject_Call2Args
. This shows us it seems to be a python install zipped up with some python code/bytecode, or compiled cython. We decided to look into tools specialised in reversing things like this, and came across a program called REpdb which is part of the pyREtic library, made for reversing python that is put together in executable files like this as well as bytecode.
In order to use REpdb, we needed an inject point. That's where memecrypt comes in. REpdb has to be run by the process it's reversing - the easiest way to do this was to force the process to import malicious python code. We can accomplish this by editing our memecrypt.py install, or by creating a fake memecrypt.py file. I did the latter, tony did the former. Here is my fake memecrypt.py file where meme_orig is my renamed real memecrypt install.
I added those last two lines so that the program could use meme_cipher normally. With this setup, running the binary drops us into a REpdb prompt to inspect the program. What do we see? As we step through, we can see the ciphertext eDxTP2RoekN4KkVXeDpQQyU+Sy5cPidXZSR6QGRaLktkSycrITpQS1xXem5cV3pvZTpFb2NXekRcNkstZSRub2U6RW9qJGZH
being loaded into a meme_cipher object along with the key lmao. What does it decrypt to? It decrypts to "from secrets import token_bytes as hush;print(hush())"
A little research shows us that token_bytes generates some cryptographically secure random bytes. Since it prints the output of this, that seems to be what our byte string is. Tony also found this earlier through attaching tools like gdb into the process.
What's very interesting is that the program decrypts this, and seemingly executes it. This means that by tampering with our memecrypt install and changing things in the decrypt function, we can make it run whatever we want. I did lots of tampering to reap maximum information. In the meme cipher class, I added the following:
as well as this to decrypt:
and ttt = 0 to the top of the file , and print statements to always print the ciphertext and key being decrypted as well as the decrypted result. Such that on the first run of the decrypt function, it would just return our fake code. We can enumerate with this, calling globals() and similar things to look around the scope. We find lots and lots of functions. This includes flag, frag, rick_roll, grant, artemis, _, __, uwu_whats_this_nuzzles_rawr_xd
, and much more as well as meme cipher object "uwu" and the module webbrowser imported as communism.
I wrote a small script to call every function with a small delay.
Showing me the output of every function and it's respective function. We get a lot here. First of all, the flag function returns a fake flag, decrypting the ciphertext K3swTVxHOGtyWVclZmd0OGYueUZ5RzJYXForP1wwdHJbR1dhVGcrPytndFFUPzhMW0dcdA==
with the key 1337_revese_engineering.
Various youtube links like https://www.youtube.com/watch?v=h0oclM1Yw2A, https://youtu.be/h6DNdop6pD8 and https://www.youtube.com/watch?v=dQw4w9WgXcQ are decrypted with the key yt and then used in webbrowser. We can't find too much else useful with this code injection, but it allows us to get a handle on what exactly is happening in this program.
From here, we moved on to static analysis in the decompiled C code.
There's a lot more unknown ciphertexts we can find in the strings of the binary, specifically small ones. Wait a second...
here, in the function owo, one of the small ciphertexts, T0YqVGBGJzZiLXYp
, is referenced. Right afterwards, so is the word apollo
. Why don't we attempt to decrypt T0YqVGBGJzZiLXYp
with apollo
?
We do, and it decrypts to lastfrag
. Last frag implies fragments, fragments of some form of larger ciphertext or key. We decided this had to be on track, and looked for other frags.
here! the ciphertext QVZHZUEqOnM=
is mentioned, and so is rain
! This decrypts to frag1
. Finally...
the ciphertext ZGBXYmRbfXU=
is mentioned along with the word champions, and this decrypts to frag2. No more small ciphertexts remainded, and linus confirmed it for us - these were all the frags we needed.
Key: rain
, CT: QVZHZUEqOnM=
, PT: frag1 Key: champions
, CT: ZGBXYmRbfXU=
, PT: frag2 Key: apollo
, CT: T0YqVGBGJzZiLXYp
, PT: lastfrag
From there, we tried to figure out how to put them together. Putting together the ciphertexts, the keys, the plaintexts, all sorts of combinations. We searched for larger ciphertexts that we hadn't cracked yet, and found b2o+LiRjVll0eHw8TXteVk1dVXdVYyZzJHlvOiQ+XnQ2Y3xmU3tvOm97XklTOlYxNmMkXg==
, QnxZNFtibWxPWD9udF5tcjgmd1ZbPG1XJw==
, Xjk6Y1N3PDBWdz0jNjlTOFp9bCJ5bWxPRFZu
and JUdMP2czQ3MuM1s0TkclbHlsZ20vRmZteXxMZC9sfnZDeWo1TkdIQVF5IkFTNiRxJT5PIlY7ZnAlPGo1Tm58cg==
eventually, will was able to decrypt JUdMP2czQ3MuM1s0TkclbHlsZ20vRmZteXxMZC9sfnZDeWo1TkdIQVF5IkFTNiRxJT5PIlY7ZnAlPGo1Tm58cg==
with the key frag1frag2frag3 to get the plaintext ractf{Thing5_Wh1ch_Ar3_Bey0nd_My_C0mpreh3nsi0n}, which wasn't actually the flag.
But, we were told it was incredibly close.
frag1frag2frag3 didnt make much sense, we only had frag1, frag2 and lastfrag. Woa came up with the idea to try encrypting our "fake" flag with the key frag1frag2lastfrag to see what it looked like, and what did we get? Xjk6Y1N3PDAsP35BYn1uPGJtVk9Wdz1DU1leV1M6bix5d344Wn1eV159bkhaVzx4eXdTbg==
This is remarkably close to the ciphertext we found in the binary, Xjk6Y1N3PDBWdz0jNjlTOFp9bCJ5bWxPRFZu
The fact the encryption of a close flag with the key frag1frag2lastfrag resembles a ciphertext we had already found must've meant something.
From there, we tried concatenating Xjk6Y1N3PDBWdz0jNjlTOFp9bCJ5bWxPRFZu
(the binary's ciphertext) with a bunch of other ciphertexts and throwing it into our related-key-bruteforce-script created by Tony. Eventually, Xjk6Y1N3PDBWdz0jNjlTOFp9bCJ5bWxPRFZu
+ QnxZNFtibWxPWD9udF5tcjgmd1ZbPG1XJw==
was concatenated to create the larger ciphertext Xjk6Y1N3PDBWdz0jNjlTOFp9bCJ5bWxPRFZuQnxZNFtibWxPWD9udF5tcjgmd1ZbPG1XJw==
,which decrypted with the key frag1frag2lastfrag to the real flag.
We can see in HTML comments of the web page:
However we get a 403 Forbidden if we try to access this file with /backup.txt
We can see that the CSS file is included with href="/static?f=index.css"
So if we go to /static?f=backup.txt
, we get the credentials for the develop user.
This is the same principle as Baiting. This time we need to log into the admin user.
From the list of users we found with the devloper credentials, jimmyTehAdmin
is the account we need to log in to.
We can use a similar payload to before (this challenge is more resitant to SQLMap)
' OR username='jimmyTehAdmin' --
We are presented with a login page. As this was a low-rated challenge, I began by testing basic SQLi.
Entering ' or 1=1; -- -
in the username field returned the message 'You are trying to login as multiple users'.
From this, I determined that the injection was sucessful, but as this query returned multiple users the web app was rejecting it.
I simply edited the payload to be ' or 1=1 limit 1; -- -
, and I got access
If we log in with the credentials from Entrypoint, we can see a list of users.
One of these is called "loginToGetFlag"
, so we'll try to log into this one.
We get a SQL error if we put a ' so we can try SQL Injection payloads.
We can use the payload:
loginToGetFlag' --
to log into this user - and we get the flag.
Earlier on, I noticed the path /__adminPortal
in an undefined response header.
Now with admin access, I decided to visit it.
It gave us some nice ASCII art, but in a hidden <p>
there was some Zalgo'd text.
Using a , I retrieved the original text: Lorem Ipsum, but hidden inside:
The server expects two values (one
and two
) passed in as a JSON array.
It appends a secret to the start of both of these and runs them through a custom hashing function.
If the result is equal, we get the flag. However, our input is compared with the ===
operator, and if they match our input is rejected.
As this is a web challenge, not a crypto one, I realized the target was not the hashing function.
After some playing around, I found that ['a'] === ['a']
returns false, likely because the arrays are two different objects in memory.
We send to the server:
and the flag is returned.
Same concept as Entrypoint, but a different file.
We are told that it is running on a Python server,
so we can try to include common python web app filenames, such as app.py
and main.py
.
If we use the same method as Entrypoint and go to /static?f=main.py
, and we get the flag.
We get what appears to be a broken png file.
If we open it up in ghex, we can see a weird IEND chunk, which, after comparing it with another png, turns out to be not normal.
So, I just removed the chunk, and then using the fixed png as a guide, I patched the hex to reveal the image.
The next stage of the Quarantine series.
I noticed that our session cookie was a JWT, signed with HS256.
A vulnerability exists where these tokens can have their signing algorithm set to 'none' and their signature removed, and the web app will accept and process it.
I used the highly useful jwt_tool to resign it, editing my 'privilege' value from '1' to '10'.
This allowed me access to the admin panel (more of a single page).
When opening the video file, we're greeted with a music video.
After running strings, we can see 'password{guitarmass}' and when using binwalk we can get an image that simply says 'password{guitarmass}'.
It's clear we need this password for something, but what and where?
My initial assumptions were using steghide on the files or the video's thumbnail - but of course, nothing.
I immediately got to researching MP4 steganography techniques and found many articles that covered hiding TrueCrypt volumes within an MP4.
While TrueCrypt is now outdated and has many security flaws, I tried mounting the MP4 file, using the password I had found - and again, nothing.
However, with more research I had found that a more secure alternative exists: VeraCrypt.
Now, there weren't any articles I could find about hiding VeraCrypt volumes within an MP4 but I was hopeful and still tried.
I installed VeraCrypt and attempted to mount the MP4, alongside using the password.
And there it was:
flag.png
Opening the .png will give us the flag.
We are given the name of the source code file app.py
, and we need to find a way to read it.
We already have access to the admin section of the site from the previous challenge.
We are given links to three available videos that we can watch.
These all follow the format /watch/XXXX.mp4
.
We are told the name of the file we need to read is called app.py
So we can try /watch/app.py
.
In the source code of the page, we can see:
data:video/mp4;base64,ractf{qu3ry5tr1ng_m4n1pul4ti0n}
We're given a PNG with the width and height fields set to 0.
However, the original CRC is intact.
The CRC is a unique value used to check for the integrity of the preceeding chunk, similar to a hash funciton.
Because of this, we can 'crack' the original dimensions with this:
Sizes are: 0x00 0x00 0x05 0x62 0x00 0x00 0x01 0x6B
Adding this back to the image, we get the original image, containing the flag. [IMAGE HERE]
We are given a hard drive image of a Linux Alpine system.
Searching for recent files, we find 3 interesting ones. A PGP keypair, and a PGP encrypted file (/root and /home).
gpg --import 257-PRIVATE.PGP
gpg --output test.html --decrypt 197-NOTHINGH.ASC
Opening test.html gives us a moasai thing made out of coloured hexidecimal text.
I then decoded with Cyberchef, which revealed it to be raw png data.
So you get a .mp3 file.
Instincts suggest strings which shows a wav file in the mp3 therefore if I binwalk it I get a file called OwO.wav.
If you open it up in Sonic Visualizer and load it as a spectogram, it says Password (Shad0ws).
Therefore I binwalked the Owo.wav file and out came an empty flag.png and a .zip file.
The password for the zip is Shad0ws and once thats extracted the png will open and your flag will be:
So we get a file called flag.jpg but looking inside the hex shows png properties, Such as IHDR, IDAT, IEND etc etc.
So I overwrote the part of the file header that contained jpg with png.
After doing that I ran pngcheck on the file (after renaming it to flag.png) and it said thst the CRC of the image was 0 x 0, referring to the dimensions.
So I ran the script tony created for Dimensionless Loading and got (['\x00', '\x00', '\x01', '\xa4'], ['\x00', '\x00', '\x00', 'E']).
Here's the script:
Using this I overwrote the png bytes but again pngcheck said it was corrupt.
After this I didn't know what went wrong but it mentioned corruption errors.
Luckily I found a script that automatically fixes such errors though. This was called PCRT.py. After running this it opens the image and you get a flag of:
We are given a PCAP of some WPA2 encrypted traffic, including a handshake.
We run aircrack-ng + rockyou.txt against it to retrieve the key: nighthawk.
We then add this to Wireshark's 802.11 protocol settings, and reload to get the decrypted traffic.
One things stands out, a HTTP 200. This has attached with it a PDF, containing the flag.
So like the .mid file is a file playing a piano cover of Where Is My Mind (which is a banger btw go listen).
But like strings or binwalk shows nothing so confusion hit hard.
I scoured the internet for tools related to .mid file steganography and found one that converts a .mid to a .csv file.
This was called midicsv and literally does what it says.
So I ran that and it outputted a csv file with characters which looked like ASCII characters to the naked eye.
However there were 2 different streams of different ASCII characters.
I just opened up a site which converts ASCII to text and tried both streams.
The first one was nonsense but the second one had distinguishable features to it.
I could see rac and f5soci3 which could be fsociety?
A Mr. Robot reference which ties into Where Is My Mind.
The track played on the .mid file. However not everything was readable.
So I used another ASCII conversion site and I could see the flag clearly this time.
Basic CBC bit flipping - We're given ciphertext for access=9999;
+ an expiry timestamp, and have to provide an IV and ciphertext that decrypt to acesss=0000
.
Alright so this is basically obscurity crypto.
A little experimentation shows us that it for every byte of the plaintext and the matching byte of the key it adds the ascii values and then moduluses them by 256.
NOTE: because of how python .encode and decode works, for example, '\x7f'.encode() = b'\xc2\x7f'.
So, whenever we base 64 decode, we have to .decode().encode('latin-1') to get the proper byte values instead of weird UTF-8 ones.
It's possible to use modular arithmetic to get the key, in fact that probably wouldn't have been much harder, but instead I just ran a small byte by byte brute force of each char of the key, and then the flag, as this is a character by character cipher making it vulnerable to such an attack.
My script is below:
More of a rev really :/
Simple first step is to extract all the b85 encoded values as bytes. The ct and e are just b85 encoded, so we can store those for later use with rsactftool
P and Q on the other hand are treated differently. They are xored with a variable called 's' (state) using 'walrus operators'. The function can be expressed as:
The encrypted bytes of p and q are also interleaved throughout the value we receive. As XOR is commutative and s is XORed with the value we have just recovered, we can simply run the script again to recover the original values for p and q, making sure to use the recovered values of p and q bytes to update the state. We recover primes
Plugging our data into RsaCtfTool yields:
Essentially a scripting challenge. You'll be given all sorts of parameters and need to calculate the missing one. Instead of going through every possible solution, which would've been really excruciating, I went through this approach -
Create a function that solves for a value.
The function's logic is as follows:
if we want a value that's already defined, just return it
if we want n, call ourself to solve p and q, then multiply
if we want ct, call ourself to solve n, then do pt^e mod n
if we want pt, call ourself to solve d and n, then do ct^d mod n
if we want d, call ourself to solve p and q,compute phi, then return inverse(e,phi)
if we want p, or q, then:
pretend p is the prime we have and q is the prime we want
if we have n, return n // p
if we have phi, return (phi // (p - 1)) + 1
if all else fails and we have e and d, use Crypto.PublicKey.RSA to compute p and q via n, e and d
The OpenSSH private key is actually not a private key and is simply just base64.
We decode it to get a long useless message and 2 hex strings.
If we decode these hex strings we get,
ineedtoopenlocks
and initialisation12
Decoding the binary gets us a long hex string.
Looking at initialisation, I immediately thought of AES. Since an IV is provided, we can assume it is AES-CBC, so I just used cyberchef to
decrypt and get the flag.
Monoalphabetical Substitution Cipher (trial and error)
Flag: documents
Vigenere Cipher (trial and error)
Flag: zurich
Railfence Cipher (freq anal)
Flag: ANUALLEAVE
Columnar Transposition Cipher (freq analysis, x's at end for padding)
Flag: CONCERNEDENCRYPTED
Bifid Cipher (lack of j hinted at grid cipher, cant be playfair because of doubles)
Key: ReallyAwesome
Flag: Campbell
Periodic Gromark Cipher (number key, nothing else given)
Key: agency
Flag: organization