HacktivityCTF Writeups

Aviral
Aviral
Aug 1 · 6 min read

So, here we are after playing a very fun ctf. My team ended up at no. 49 with 3140 points. Here are the writeups for some challenges.

Image for post
Image for post

1. Tyrannosaurus Rex

Description:

We found this fossil. Can you reverse time and bring this back to life?

In the python script, we can see that each character of the b64 encoded flag is being xored with next character.

while i < len(e):
z += [ e[i] ^ e[((i + 1) % len(e))]]
i = i + 1

Thus, we are given with some form of: a^b, b^c, c^d, d^a which implies knowing a single character of the b64 encoded flag will give us all the characters.

Since, the flag format is flag{.*}, therefore I encoded it with b64(ZmxhZw) and got the first character as ‘Z’.

Final Script:

from pwn import xor
s="37151032694744553d12220a0f584315517477520e2b3c226b5b1e150f5549120e5540230202360f0d20220a376c0067".decode('hex')
crib="Z"
flag="Z"
for i in range(len(s)):
flag+=xor(s[i], flag[i])
print flag[:-1].decode('base64')

Flag: flag{tyrannosauras_xor_in_reverse}

2. Perfect XOR

Description:

Can you decrypt the flag?

I think I missed the intended solution, but managed to solve it in a overcomplicated way.

We had to xor first 14 perfect-numbers with the cipher list.

Got some knowledge about perfect numbers from this page https://byjus.com/maths/perfect-numbers/ .

N = 2^(p-1)*(2^p -1) where p is a prime for which 2p -1 is a Mersenne prime.

So, I wrote a python script to get about 15 perfect numbers.

import base64
import sympy
cipher_b64 = b"MTE0LDg0LDQzNyw4MDk1LDMzNTUwNDM0LDg1ODk4NjkxNzAsMTM3NDM4NjkxMzc2LDIzMDU4NDMwMDgxMzk5NTIyMzUsMjY1ODQ1NTk5MTU2OTgzMTc0NDY1NDY5MjYxNTk1Mzg0MjI0NSwxOTE1NjE5NDI2MDgyMzYxMDcyOTQ3OTMzNzgwODQzMDM2MzgxMzA5OTczMjE1NDgxNjkyOTQsMTMxNjQwMzY0NTg1Njk2NDgzMzcyMzk3NTM0NjA0NTg3MjI5MTAyMjM0NzIzMTgzODY5NDMxMTc3ODM3MjgyMjMsMTQ0NzQwMTExNTQ2NjQ1MjQ0Mjc5NDYzNzMxMjYwODU5ODg0ODE1NzM2Nzc0OTE0NzQ4MzU4ODkwNjYzNTQzNDkxMzExOTkxNTIyMTYsMjM1NjI3MjM0NTcyNjczNDcwNjU3ODk1NDg5OTY3MDk5MDQ5ODg0Nzc1NDc4NTgzOTI2MDA3MTAxNDMwMjc1OTc1MDYzMzcyODMxNzg2MjIyMzk3MzAzNjU1Mzk2MDI2MDA1NjEzNjAyNTU1NjY0NjI1MDMyNzAxNzUwNTI4OTI1NzgwNDMyMTU1NDMzODI0OTg0Mjg3NzcxNTI0MjcwMTAzOTQ0OTY5MTg2NjQwMjg2NDQ1MzQxMjgwMzM4MzE0Mzk3OTAyMzY4Mzg2MjQwMzMxNzE0MzU5MjIzNTY2NDMyMTk3MDMxMDE3MjA3MTMxNjM1Mjc0ODcyOTg3NDc0MDA2NDc4MDE5Mzk1ODcxNjU5MzY0MDEwODc0MTkzNzU2NDkwNTc5MTg1NDk0OTIxNjA1NTU2NDcwODcsMTQxMDUzNzgzNzA2NzEyMDY5MDYzMjA3OTU4MDg2MDYzMTg5ODgxNDg2NzQzNTE0NzE1NjY3ODM4ODM4Njc1OTk5OTU0ODY3NzQyNjUyMzgwMTE0MTA0MTkzMzI5MDM3NjkwMjUxNTYxOTUwNTY4NzA5ODI5MzI3MTY0MDg3NzI0MzY2MzcwMDg3MTE2NzMxMjY4MTU5MzEzNjUyNDg3NDUwNjUyNDM5ODA1ODc3Mjk2MjA3Mjk3NDQ2NzIzMjk1MTY2NjU4MjI4ODQ2OTI2ODA3Nzg2NjUyODcwMTg4OTIwODY3ODc5NDUxNDc4MzY0NTY5MzEzOTIyMDYwMzcwNjk1MDY0NzM2MDczNTcyMzc4Njk1MTc2NDczMDU1MjY2ODI2MjUzMjg0ODg2MzgzNzE1MDcyOTc0MzI0NDYzODM1MzAwMDUzMTM4NDI5NDYwMjk2NTc1MTQzMzY4MDY1NTcwNzU5NTM3MzI4MjQy"
#l=[6, 28, 496, 8128, 33550336, 8589869056, 137438691328, 2305843008139952128, 2658455991569831744654692615953842176]
primes=[2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59]
l=[]
for i in primes:
try:
if(sympy.prime[pow(2,i)-1]==1):
l.append(pow(2,i-1)*(pow(2,i)-1))
except:
pass
print(l)
print("flag{", end='', flush=True)
cipher = base64.b64decode(cipher_b64).decode().split(",")
for i in range(len(l)):
print(chr(l[i]^int(cipher[i])), end='')
print("}")

Flag: flag{tHE_br0kEN_Xor}

3. Bon Appetit

Description:

Wow, look at the size of that! There is just so much to eat!

Attached in this chall was a file in which values of n, e, c were given.

Since, n(1024 bit) and e(1023 bit) were very big which meant that it can be Weiner attack or Boneh-Durfee attack.

import owiener
d=owiener.attack(e,n)

It wasn’t Weiner, so I went for Boneh-Durfee. Got a link to a sage script from ctf-katana(https://github.com/JohnHammond/ctf-katana). Tweaked some values and after 5–10 mins of tweaking got right values of delta and m.

delta=0.22

m=10

This was the output from online sage compiler:

=== solution found ===
private key found: 5448511435693918250863484721514292687178096328572373396537572878464059764348289027
=== 113.94044065475464 seconds ===

Flag: flag{bon_appetit_that_was_one_big_meal}

4. OFBuscated

Description:

I just learned some lesser used AES versions and decided to make my own!

This was a really easy challenge, I donno why it got so less solves.

In the handle function, there is an interesting line:

assert len(flag) % 16 == 1

From, this we know that our flag is of length 16*i+1 and the last character is ‘}’ as evident from the flag format.

Now, the flag is being divided into blocks of 16 and the last block is being padded. Furthermore, these blocks are being shuffled.

So, the last block should be something like this: ‘}\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f’

Now, we have to get a basic idea of how AES-OFB works.

Image for post
Image for post

Since, iv and key remains same everytime, therefore ECB encryption of it will also remain same everytime which implies =>

a^block[randint(1,3)]=c1

b^block[randint(1,3)]=c2

c^block[randint(1,3)]=c3

a,b,c remains same in every case(Only the blocks are being shuffled).

So, if consider that our known block is being xored with ‘a’ to get ‘c1’. Thus, we now have ‘a’. Now, in another run of the server, if we xor ‘a’ with the cipher then we are sure to get one different block of plaintext.

Here, is the script I wrote:

from pwn import *
from Crypto.Util.Padding import pad
from Crypto.Cipher import AES
crib=pad('}', 16)
r=connect("jh2i.com", 50028)
c=r.recvuntil('\n')[:-1]
#print(c,len(c))
c=c.decode('hex')
xored=xor(crib, c)
for i in range(10):
r=connect("jh2i.com", 50028)
c=r.recvuntil('\n')[:-1]
c=c.decode('hex')
print(xor(xored, c))

After running the script, you will se the other blocks.

Flag: flag{bop_it_twist_it_pull_it_lol}

5. Prophecy

Description:

C A N Y O U S E E T H E F U T U R E ?

In this challenge we have to make some guesses of the next number. Noticing that the server is returning the correct number if we provide the wrong input.

So, running same instances of the server i.e. stealing from one instance and giving to another instance will do the job.

Here is the script:

from pwn import *
s=connect("jh2i.com", 50012)
l=[]
while(True):
r=connect("jh2i.com", 50012)
for i in range(len(l)):
r.recvuntil('> ')
r.sendline(l[i])

r.recvuntil('> ')
r.sendline('1')
r.recvuntil('\n')
x=r.recvuntil('\n').split(' ')[-1]
l.append(x)
print(l)
for i in range(len(l)):
s.recvuntil('> ')
s.sendline(l[i])
print(s.recvuntil('\n'))
print(s.recvuntil('\n'))
s.recvuntil('\n')
s.interactive()
#l=['99126\n', '76106\n', '32378\n', '49560\n', '87935\n', '17366\n', '36639\n', '33561\n', '51241\n', '24009\n', '82718\n', '65774\n', '87030\n', '53097\n', '53885\n', '29931\n', '10890\n', '20583\n', '46190\n', '83643\n']

Flag: flag{does_this_count_as_artificial_intelligence}

6. Rescue Mission

Description:

Oh no! The flag has been lost in the most disconcerting places! Can you save the flag??

Again a very easy challenge, just googled few commads of command promt and got the flag.

ls: dir

cd: cd

xxd: Format-Hex

After running the instance, we get a shell to work upon. Using ‘dir’ to list directory contents.

PS /c_drive> dir
dir
Directory: /c_driveMode LastWriteTime Length Name
---- ------------- ------ ----
d---- 07/29/2020 01:52 stuck_in

Change directory to stuck_in/

cd stuck_in/

Using ‘dir’ to list directory contents.

dirDirectory: /c_drive/stuck_inMode                 LastWriteTime         Length Name
---- ------------- ------ ----
d---- 07/29/2020 01:52 a_cave
d---- 07/29/2020 01:52 a_coffin_underground
d---- 07/29/2020 01:52 a_haunted_hotel
d---- 07/29/2020 01:52 a_nightmare
d---- 07/29/2020 01:52 a_roller_coaster
d---- 07/29/2020 01:52 a_ski_lift
d---- 07/29/2020 01:52 a_stalled_elevator
d---- 07/29/2020 01:52 quarantine
d---- 07/29/2020 01:52 space
d---- 07/29/2020 01:52 the_ocean
d---- 07/29/2020 01:52 the_subway

I quickly went through all of the folders.(Advice: Starting from the last is always beneficial xD). Got a ‘flag.png’ file in ‘the_ocean’ folder.

dirDirectory: /c_drive/stuck_in/the_oceanMode                 LastWriteTime         Length Name
---- ------------- ------ ----
----- 07/25/2020 01:57 2197 flag.png

Now, using Format-Hex, recovered file hex.

00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
------ ----------------------------------------------- -----
0000000000000000 89 50 4E 47 0D 0A 1A 0A 00 00 00 0D 49 48 44 52 �PNG���� �I…
0000000000000010 00 00 01 55 00 00 00 2A 08 02 00 00 00 8C 6D 5F �U *�� …
0000000000000020 D9 00 00 00 01 73 52 47 42 00 AE CE 1C E9 00 00 Ù �sRGB ®Î�…
0000000000000030 00 04 67 41 4D 41 00 00 B1 8F 0B FC 61 05 00 00 �gAMA ±��üa…
0000000000000040 00 09 70 48 59 73 00 00 0E C3 00 00 0E C3 01 C7 �pHYs �à �…

Used sublime to delete unwanted text, recovering only the hex. Used online hex to image convertor to get the image(https://tomeko.net/online_tools/hex_to_file.php?lang=en).

Got the flag in the png file:

Flag: flag{thanks_you_saved_me}

7. Impartial

Description:

Check out the terminal-interface for this new company! Can you uncover any secrets?

After connecting to the instance, we get 5 options:

1. About
2. Login
3. Register
4. Contact
?. Exit

Go to Login section and it will ask for a username and password.

Username is ‘admin’ and here is the fun part. It asks us to enter some specific characters of the flag(password).

For your security, please only enter a partial password.
To protect your account from hackers, enter only the characters
at position 27, 31, and 7 (separated by spaces).

If we enter a correct or wrong letter, it shows CORRECT and WRONG.

Password: a a a
27 character: WRONG
31 character: WRONG
7 character: CORRECT

So, I wrote a script to provide random ascii-printable characters to the Password and checking if they are correct or not. I correct, then appending them to the flag.

c=[chr(randint(97,126)), chr(randint(97,126)), chr(randint(97,126))]
r.sendline('{0} {1} {2}'.format(c[0],c[1],c[2]))

Here, is the final script:

from pwn import *
import os, random
r=connect("jh2i.com", 50026)
flag=['0']*50
while(True):
r.recvuntil('> ')
r.sendline('2')
r.recvuntil(': ')
r.sendline('admin')
r.recvuntil(': ')
c=[chr(randint(97,126)), chr(randint(97,126)), chr(randint(97,126))]
r.sendline('{0} {1} {2}'.format(c[0],c[1],c[2]))
for i in range(3):
x=r.recvuntil('\n').split(' ')
if(x[-1]=="CORRECT\n"):
flag[int(x[0])]=c[i]
print(''.join(flag))print(''.join(flag))
#flag{partial000s0wo0d000z0000000c0s}

After running it for a while, we get something like:

fl0g{0a0tial000sswo0d0puz0l00pi0c0s}

I filled in the remaining characters by intuition.

Flag: flag{partial_password_puzzle_pieces}


That’s all for now guys. If you writeups for some forensics or warmup challenges too, please comment out the challenge name.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch

Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore

Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store