When I went to register to compete in the 0x41414141 CTF I found that is was a little different from other CTFd based CTFs. Besides the normal registration information, it asks for a pin code (secret pin code for CTF registration).
Going back and looking through the site, I see on the About page that the secret pin code for CTF entry is hidden somewhere on the site:
After pouring through the source files for each page on the site, running curl POSTs, and looking at previous versions of the site on Archive.org, I considered the steganography approach.
Besides the normal social media link images, there are only two images on the site. One is the animated Offshift logo, which yielded no obvious results when running strings, binwalk, or other stego decoders:
The second image is a small Offshift logo that is used as the header logo:
After downloading this image, I ran strings on it to look for anything interesting:
Ahh! I see “secret: 100100100101” at the bottom of the results.
I convert the binary string to decimal:
echo "obase=10; ibase=2; 100100100101" | bc
Using the resulting decimal value as the pin, I am now able to register for the CTF.
For this challenge, we are just given details for two server instances; one for US and one for EU.
Connecting via netcat, we are told that we will be given a number of problems and we have to solve them. It mentions that level 1 is tower of hanoi. A quick google search and I find more information about it. After some trial and error using different possibilities for the number of pegs.
A manual check confirms the formula is correct, but the server responds with another problem.
There are four disks listed, so n=4. 24 – 1 = 15
We will need to automate this 🙂
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('45.134.3.200', 9660))
print('Server:', str(s.recv(1024)))
#print s.recv(1024)
while True:
data = s.recv(1024).strip()
if data:
count = 0
data = str(data)
print(data)
for i in data:
if i == ',':
count = count + 1
count = count + 1
n = pow(2, count) - 1
if '[' in data:
s.send(str(n))
print('Client: ', n)
s.close()
When I run this script, it iterates through quite a few problems, then the server reports that I’m in level 2: and have to give the number of inversions for the provided output.
import binascii
import random
from Crypto.Util.number import isPrime
flag = open("flag.txt", "rb").read().strip()
m = int(binascii.hexlify(flag), 16)
def genPrimes(size):
base = random.getrandbits(size // 2) << size // 2
base = base | (1 << 1023) | (1 << 1022) | 1
while True:
temp = base | random.getrandbits(size // 2)
if isPrime(temp):
p = temp
break
while True:
temp = base | random.getrandbits(size // 2)
if isPrime(temp):
q = temp
break
return (p, q)
p, q = genPrimes(1024)
n = p * q
e = 0x10001
print("c:", pow(m, e, n))
Based on the provided values and script, this appears to be an RSA type challenge.
1521152103631606164757991388657189704366976433537820099034648874538500153362765519668135545276650144504533686483692163171569868971464706026329525740394016509185464641520736454955410019736330026303289754303711165526821866422766844554206047678337249535003432035470125187072461808523973483360158652600992259609986591
and
152103631606164757991388657189704366976433537820099034648874538500153362765519668135545276650144504533686483692163171569868971464706026329525740394016509191077550351496973264159350455849525747355370985161471258126994336297660442739951587911017897809328177973473427538782352524239389465259173507406981248869793
I iterate these in a script to calculate phi:
primes = [152103631606164757991388657189704366976433537820099034648874538500153362765519668135545276650144504533686483692163171569868971464706026329525740394016509185464641520736454955410019736330026303289754303711165526821866422766844554206047678337249535003432035470125187072461808523973483360158652600992259609986591, 152103631606164757991388657189704366976433537820099034648874538500153362765519668135545276650144504533686483692163171569868971464706026329525740394016509191077550351496973264159350455849525747355370985161471258126994336297660442739951587911017897809328177973473427538782352524239389465259173507406981248869793]
phi = 1
for p in primes:
phi *= (int(p) - 1)
Based on the e value (e = 0x10001 = 65537) from the provided python script, I calculate d:
e = 65537
d = inverse(e,phi)
I then use the power function with the calculated d value and provided c and n values to get the plaintext value (which gets converted from long to bytes):
from Crypto.Util.number import inverse, long_to_bytes
primes = [152103631606164757991388657189704366976433537820099034648874538500153362765519668135545276650144504533686483692163171569868971464706026329525740394016509185464641520736454955410019736330026303289754303711165526821866422766844554206047678337249535003432035470125187072461808523973483360158652600992259609986591, 152103631606164757991388657189704366976433537820099034648874538500153362765519668135545276650144504533686483692163171569868971464706026329525740394016509191077550351496973264159350455849525747355370985161471258126994336297660442739951587911017897809328177973473427538782352524239389465259173507406981248869793]
e = 65537
c = 17830167351685057470426148820703481112309475954806278304600862043185650439097181747043204885329525211579732614665322698426329449125482709124139851522121862053345527979419420678255168453521857375994190985370640433256068675028575470040533677286141917358212661540266638008376296359267047685745805295747215450691069703625474047825597597912415099008745060616375313170031232301933185011013735135370715444443319033139774851324477224585336813629117088332254309481591751292335835747491446904471032096338134760865724230819823010046719914443703839473237372520085899409816981311851296947867647723573368447922606495085341947385255
n = 23135514747783882716888676812295359006102435689848260501709475114767217528965364658403027664227615593085036290166289063788272776788638764660757735264077730982726873368488789034079040049824603517615442321955626164064763328102556475952363475005967968681746619179641519183612638784244197749344305359692751832455587854243160406582696594311842565272623730709252650625846680194953309748453515876633303858147298846454105907265186127420148343526253775550105897136275826705375222242565865228645214598819541187583028360400160631947584202826991980657718853446368090891391744347723951620641492388205471242788631833531394634945663
phi = 1
for p in primes:
phi *= (int(p) - 1)
d = inverse(e,phi)
plaintext = pow(c,d,n)
print(long_to_bytes(plaintext))
We are given a blockchain address for the Rinkeby network:
0x5CDd53b4dFe8AE92d73F40894C67c1a6da82032d
And a solidity script:
pragma solidity ^0.7.0;
//SPDX-License-Identifier: UNLICENSED
contract sanity_check {
function welcome() public pure returns (string memory){
return "flag{}";
}
}
I googled the Rinkeby blockchain and found that I can look up addresses on the Etherscan.io site. Looking up the provided address gives me some information. Specifically under the “Contract” tab:
I see the handy “Decompile ByteCode” button, so I give it a shot:
This opens a new tab with more information about this tool and another button to execute it. This tool is based on the Panoramix decompiler and is designed to decompile smart contracts.
Running the tool gives me what I am looking for:
#
# Panoramix v4 Oct 2019
# Decompiled source of rinkeby:0x5CDd53b4dFe8AE92d73F40894C67c1a6da82032d
#
# Let's make the world open source
#
const welcome = 'flag{1t_1s_jus7_th3_st@rt}'
#
# Regular functions
#
def _fallback() payable: # default function
revert
Disclaimer: I did this challenge quick and dirty in order to get first blood.
For this challenge, we are told that there is a program that can decrypt the flag for us using the right password. The password is a number between 16000 and 20000. We are provided a zip file containing an ELF binary (numgen)and another zip file that contains a bunch of small images. These images are named 0.png … 39.png. They are pictures of random letters, numbers, and characters.
Running the executable I am told it wants a number argument.
I provide a number (1600) and get a series of numbers as a response.
Looking at the image file names with relation to the numbers in the response, I see it translates to gibberish. I can make an educated guess that the flag should end with a “}”, which is 39.png and the the first character should be an “S” (7.png) or an “F” (32.png) based on the CTF flag format.
Using this method, I can write a script to try all possibilities from 16000 to 20000 (4000 possibilities).
#!/bin/bash
for i in {16000..20000}
do
./numgen $i
done
I can run the script and output the results to a file. Yes, a more elegant solution could be crafted that would grep for the right results, but it is only a 24-hour CTF.
/runnum.sh > numgenout.txt
The output file contains 8,000 lines (the resulting numbers and the “Randomizing names of images …” string.
Quick and dirty I copy the lines into excel (yeah, I know) and drop all cells containing the “Randomizing names of images …” string.
I create a quick formula to get the 1 or two digit number before the first space in the cell and then throw it in the “B” column.
=LEFT(A1,(FIND(" ",A1,1)-1))
A quick filter to only show the cells that start with 7 or 32 (S or F), and I get 189 cells.
I then do a quick filter using “ends with” and use 18 (“}”) as the criteria.
That narrows it down to 9 possible cells.
I now look for the second letter based on my assumption that the first word is “shadow” or “flag”. The numbers should be either 6, 4, or 31 (there are two “L” images).I do this with another filter.
It gives me only one result:
A quick and dirty translation using the images gives me the flag:
SHADOWCTF{W3LLD0N3}
After submitting the flag for first blood, I make an HTML file to make the flag look pretty:
For this challenge we are given a mildly cryptic description:
do you know … i have secret organization called sad can’t anyone access it by any browser and you should be sad to access and decode anything in your bad life link : http://168.61.3.216/sad_agent/ author : Sad Coder
When I go to the link, I get this page:
When I click the “chek” button, I get some information…
This tell me that the browser’s user agent property is important. Referring back to the challenge text, I believe I need to change my browser’s user agent property to “sad”, so I do this in Chrome’s Developer Tools…
After doing this, I click the “chek” button again and get better results. (I highlighted the black text that is over the black background).
Looking at the resulting source code, I see a strange value for a form input field.
Seeing the makeup of the value, I think it might be base64, so I decode it to…
Interesting! The page appears to take commands through this base64 encoded field. To do a POC, I encode a different command to see what it does…
I see that I get something different. Let’s go for the flag using:
show_source("index.php");
BINGO! The flag was imbedded in the PHP code.
This was a fun challenge. Thank you to Sad Coder. Hopefully you can find some happiness in this writeup!
Full Disclosure: I did not complete this challenge in time for the CTF. I solved three of the four parts during the CTF and finally finished it the day after the CTF ended.
For this challenge, we are given a single file to download (no extention): “split” and the following text:
It’s a bird! It’s a plane! It’s… A story in 4 parts.
We are also provided two hints:
Are we exclusive? Or…
ALL CAPS
To begin, I download the file and open it in notepad++ (as I always do for questionable files).
I initially see that this is a Linux executable (ELF), but I also see a bunch of strange text (strange for an ELF file).
There appears to be some non-printable binary (typical of ELF files), HTML, JAVA, and Unicode (indicative of a PDF).
I first will run the file in Kali to see what it does…
It gives me a hexadecimal string: 4854487b312d62316e7a5f725f66756e5f
I do a hex to ASCII conversion and get: HTH{1-b1nz_r_fun_
This must be part one of the flag.
Next, I copied the file and gave it a .html extension. I can read the HTML, but it would be fun to see how it presents as a webpage. I opened it in Firefox and receive a pop-up alert with another string.
This time, the string is not hex: Ml9odG1sX3J1bGV6X2QwMGRf
I try the usual suspects for decoding. In this case, Base64 was the ticket.
2_html_rulez_d00d_
This must be part two of the flag. Half way there!
Next, based on the text in the file indicative of PDF documents…
I copy the file and give it a .pdf extension and open it as such.
I see a string at the bottom of the PDF that matches the format I would expect for part three of the flag except it is URL encoded. After decoding that I get: 3_a_p0rtabl3_d0c_
One more part to go!
As I mentioned earlier, I saw some JAVA code in the file contents, so I ran the file with java…
That gives me a strange string: |y&x7$)a}5
After a lot of trial and error, I remembered to review the hints and determined this is most likely the output from an XOR cipher (Hint #1).
The password must be all caps (Hint #2), but what is the password. I got lost in the weeds thinking that the password should be SUPERMAN because of the challenge name and text. This is where I stagnated.
After the CTF was over, I chatted with the challenge creator @mythdude and he indicated that the password is more simple than SUPERMAN.
I went back to dcode.fr/xor-cipher and tried simpler passwords that I could think of for the cipher. HTH was it.
That gave me the 4th part of the flag: 4-n0cla55}
The final full flag was: HTH{1-b1nz_r_fun_2_html_rulez_d00d_3_a_p0rtabl3_d0c_4-n0cla55}
This challenge was amazing as it was a polyglot. It was a single file that would be executed/ran in four different way without generating any errors or junk messages. It was a Linux binary program, HTML webpage, PDF file and a JAVA jar file all in one. Certainly the first one I have seen.
I want to thank @mythdude for putting this challenge together. It was very creative!
This was my first cloud challenge. We are given this challenge text:
Hey guys! I set up an AWS bucket for this year’s hth that we can use to store our flags for the ctf. I think I made the bucket private but I’m not very good at this cloud stuff. Send me a message if I need to edit the permissions.
With this as a hint (yeah I looked at it… This is my first cloud challenge)
Let’s keep a flag in hth2020-private where it should be safe!
Some quick google searching gave me some basic URL examples for AWS buckets… http://*******.s3.amazonaws.com/ So I plugged this in and I get what I was looking for… http://hth2020-private.s3.amazonaws.com/
I’m provided with a JAR file (super_secret_login.jar) and this challenge text: All of our secrets are hidden behind a secure Java application. What could go wrong?
Poking around I see that there is a shift by 13 characters (ROT13) for a decoded base64 string.
I see this value for PASSWORD: ZmJzZ19zeWhzc2xfeHZnZ3JhZg== I base64 decode this and get this: fbsg_syhssl_xvggraf I then treat it as a ROT13 cipher and decode it to get this: soft_fluffy_kittens
I see that the expected username is admin I then run the jar file with java and provide admin:soft_fluffy_kittens for credentials java -jar super_secret_login.jar