MISC

KcufsJ

We got a file called “kcufsj.dms”.

If we reverse its name, it will be “jsfuck”, which is an esoteric subset of JavaScript, where code is written in only six characters [, ], (, ), !, and +.
For more information: JSFuck wiki

Now we can implement code to get the information of the file.

1
2
3
4
5
6
7
f = open("kcufsj.dms", "r")
d = f.readline()
s = ""
for c in d:
	s = c + s
f.close()
print(s)

And we get a very long string written in JSFuck.

Past that string into the console of a web developer in your web browser, and we can get the flag AIS3{R33v33rs33_JSFUCKKKKKK}.

Local Picture


Crystal Maze

When you connect to the server, it is a maze-like program.

We are at the entrance of the maze, the prompt will ask for whether to go up, down, left, or right.

Whenever we send our decision, it will send the feedback to us. There are three kinds of feedback:

  • OK, and ask your next move
  • Hit the wall, and connection will be cut down
  • Get the flag

My idea to deal with this problem is that whenever I go to a point, I will try all four directions, and store those serializations of the footwork in a two-dimensional list about what I need to check next.

Such as
[[up, left, left],
[up, left, right],
[up, left down]]

This is how I implemented

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
from pwn import *
import numpy as np

steps = []
steps.append([])
steps[0].append("right")

def check(s):
	p = remote('pre-exam-chals.ais3.org', 10202)

	for step in steps[0]:
		p.recvuntil('move:')
		payload = step
		p.sendline(payload)

	p.recvuntil('move:')
	payload = s
	p.sendline(payload)
	feedback = p.recvline(keepends=False)

	return feedback


success = 0

while success is 0:

	feedback_1 = check("up")
	feedback_2 = check("down")
	feedback_3 = check("left")
	feedback_4 = check("right")
	#print(feedback_1, feedback_2, feedback_3, feedback_4)
	feedback = str(feedback_1) + str(feedback_2) + str(feedback_3) + str(feedback_4)
	if "AIS3" in feedback:
		print(feedback)
		break

	last_step = steps[0][-1]
	
	if ("ok" in str(feedback_1)) and (last_step is not "down"):
		l = steps[0].copy()
		steps.append(l)
		steps[-1].append("up")
	if ("ok" in str(feedback_2)) and (last_step is not "up"):
		l = steps[0].copy()
		steps.append(l)
		steps[-1].append("down")
	if ("ok" in str(feedback_3)) and (last_step is not "right"):
		l = steps[0].copy()
		steps.append(l)
		steps[-1].append("left")
	if ("ok" in str(feedback_4)) and (last_step is not "left"):
		l = steps[0].copy()
		steps.append(l)
		steps[-1].append("right")
	
	steps.remove(steps[0])

	print(steps)

And we get the flag AIS3{4R3_Y0U_RUNN1NG_45_F45T_45_CRY5T4L?}.


Are you admin?

We got a source code main.rb.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#!/usr/bin/ruby
require 'json'

STDOUT.sync = true

puts "Your name:"
name = STDIN.gets.chomp
puts "Your age:"
age = STDIN.gets.chomp

if age.match(/[[:alpha:]]/)
    puts "No!No!No!"
    exit
end


string = "{\"name\":\"#{name}\",\"is_admin\":\"no\", \"age\":\"#{age}\"}"
res = JSON.parse(string)

if res['is_admin'] == "yes"
    puts "AIS3{xxxxxxxxxxxx}"  # flag is here
else
    puts "Hello, " + res['name']
    puts "You are not admin :("
end

It seems like we need to make is_admin to yes to get the flag.

Type bc", "is_admin":"yes","report":[{"subject":"Math for Your name, and type 23"}], “00":"01 for your age.

In this way, the whole string will become {"name"=>"bc", "is_admin"=>"yes", "report"=>[{"subject"=>"Math", "is_admin"=>"no", "age"=>"23"}], "00"=>"01"}, which makes is_admin to yes.

Local Picture


Reverse

Trivial

It is a relatively easy one.

We get a file Trivial.dms. After looking at its strings, we can get the flag AIS3{This_is_a_reallllllllllly_boariiing_challenge}.


TsaiBro

In this question, we get a file TsaiBro.dms, which is the main program on the connection. And we get a file flag.txt, which contains the context of the encoded flag.

The program takes the argument as input, and output its encoded form.

I input characters to the program to make a dictionary. With this dictionary, I can read flag.txt and transfer the code back to its original character.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import os

test_list = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789{}_"

this_dict = {}

for i in test_list:
	a = os.popen("./TsaiBro %c" % i).read()
	a = a.split("\n")[1]
	if a != "":
		this_dict[str(a)] = i


f = open("flag.txt", "r")
gar = f.readline()
target = f.readline()

num = 0

l = []
s = ""

for c in target:		
	if c != ".":
		num += 1
		if num == 5:
			num = 1
			l.append(s)
			s = ""
	s += c
l.append(s)

f.close()

for e in l:
	print(this_dict[e], end='')
print("\n", end='')

And we can get the flag AIS3{y0u_4re_a_b1g_f4n_0f_tsaibro_n0w}.


HolyGrenade

We get files HolyGrenade.pyc and output.txt, which contains encoded message.

If we decompile the .pyc file directly with pycdc, we can only get part of the code because pycdc does not do a modification for python version 3.7.

So we should modify the .pyc file before decompile.

Address Before Modification After Modification Meaning
0x14E and 0x8E a0 6a LOAD_METHOD -> LOAD_ATTR
0x152 and 0x90 a1 83 CALL_METHOD -> CALL_FUNCTION

After modification, we can decompile successfully.

This is the code after rename.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from secret import flag
from hashlib import md5

# 0 -> 1
# 1 -> 3
# 2 -> 0
# 3 -> 2
def func(arg):
    arg = bytearray(arg, 'ascii')
    for i in range(0, len(arg), 4):
        a = arg[i]
        b = arg[i + 1]
        c = arg[i + 2]
        d = arg[i + 3]
        arg[i + 2] = d
        arg[i + 1] = a
        arg[i + 3] = b
        arg[i] = c

    return arg.decode('ascii')

flag += b'0' * (len(flag) % 4)
for i in range(0, len(flag), 4):
    print(func(md5(bytes(flag[i:i + 4])).hexdigest()))

It cut the flag into pieces and do md5 hash, switch the position in each block with func(arg).

This is how I reverse this process. I get the flag by reversing func(arg) and make a dictionary with md5 hash value from a combination of 4 characters. Finally, make the collision.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
from hashlib import md5
from itertools import product

# 0 -> 1
# 1 -> 3
# 2 -> 0
# 3 -> 2
def func(arg):
    arg = bytearray(arg, 'ascii')
    for i in range(0, len(arg), 4):
        a = arg[i]
        b = arg[i + 1]
        c = arg[i + 2]
        d = arg[i + 3]
        arg[i + 2] = d
        arg[i + 1] = a
        arg[i + 3] = b
        arg[i] = c

    return arg.decode('ascii')

def de_func(arg):
    arg = bytearray(arg, 'ascii')
    for i in range(0, len(arg), 4):
        a = arg[i]
        b = arg[i + 1]
        c = arg[i + 2]
        d = arg[i + 3]
        arg[i + 1] = d
        arg[i + 2] = a
        arg[i] = b
        arg[i + 3] = c

    return arg.decode('ascii')

l = []
f = open("output.txt")
for line in f:
	l.append(line[:-1])
f.close()

l_defunc = []

for i in l:
	l_defunc.append(de_func(i))

this_dict = {}

char_list = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ{}0123456789_"

tmp = list(product(char_list, repeat=4))
for i in range(len(tmp)):
	tmp[i] = ''.join(list(tmp[i]))


for i in tmp:
	arg = bytearray(i, 'ascii')
	this_dict[md5(bytes(arg)).hexdigest()] = i


s = ''
for i in l_defunc:
	if i in this_dict:
		s += this_dict[i]

print(s)

And we can get the flag AIS3{7here_15_the_k1ll3r_ra661t}.

Note: Instead of pycdc, using uncompyle6 can be a better solution because there is no need to revise the binary code.


Crypto

TCash

We get a file task.py.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
from hashlib import md5,sha256
from secret import FLAG
cand = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPWRSTUVWXYZ1234567890@,- _{}'

md5s = []
sha256s = []
for f in FLAG :
    assert f in cand 
    md5s.append( int(md5(f.encode()).hexdigest(),16)%64 )
    sha256s.append( int(sha256(f.encode()).hexdigest(),16)%64 )

# md5s = [41, 63, 46, 51, 6, 26, 42, 50, 44, 33, 29, 50, 27, 28, 30, 17, 31, 19, 46, 50, 33, 45, 26, 26, 29, 31, 52, 33, 1, 45, 31, 22, 50, 50, 50, 50, 50, 31, 22, 50, 44, 26, 44, 49, 50, 49, 26, 45, 31, 30, 22, 44, 30, 31, 17, 50, 50, 50, 31, 43, 52, 50, 53, 31, 30, 17, 26, 31, 46, 41, 44, 26, 31, 52, 50, 30, 31, 26, 39, 31, 46, 33, 27, 1, 42, 50, 31, 30, 12, 26, 27, 52, 31, 30, 12, 31, 46, 26, 27, 14, 50, 31, 22, 52, 33, 31, 41, 50, 46, 31, 22, 23, 41, 31, 53, 26, 21, 31, 33, 30, 31, 19, 39, 51, 33, 30, 39, 51, 12, 58, 60, 31, 41, 33, 53, 31, 3, 17, 50, 31, 51, 26, 29, 52, 31, 33, 22, 26, 31, 41, 51, 54, 41, 29, 52, 31, 19, 23, 33, 30, 44, 26, 27, 38, 8, 50, 29, 15]
# sha256s = [61, 44, 3, 14, 22, 41, 43, 30, 49, 59, 58, 30, 11, 3, 24, 35, 40, 46, 3, 42, 59, 36, 41, 41, 41, 40, 9, 59, 23, 36, 40, 33, 42, 42, 42, 42, 42, 40, 44, 42, 49, 24, 49, 28, 42, 33, 24, 36, 40, 24, 33, 10, 24, 40, 35, 42, 42, 42, 40, 39, 9, 42, 3, 40, 24, 35, 24, 40, 3, 61, 49, 24, 40, 9, 42, 24, 40, 41, 17, 40, 12, 57, 11, 23, 43, 42, 40, 24, 18, 41, 11, 9, 40, 24, 18, 40, 3, 41, 11, 12, 42, 40, 44, 9, 59, 40, 61, 42, 3, 40, 44, 13, 61, 40, 3, 24, 29, 40, 59, 24, 40, 19, 18, 6, 59, 24, 18, 6, 22, 0, 39, 40, 61, 57, 3, 40, 17, 35, 42, 40, 58, 24, 58, 9, 40, 59, 44, 24, 40, 61, 48, 52, 61, 58, 9, 40, 19, 13, 59, 24, 53, 41, 11, 55, 55, 42, 58, 18]

It takes each character in the flag, encodes with md5, transfers hex to int, and modulo 64. Besides md5, it operates sha256 as well.

To solve this problem, I implement two lists containing md5 and sha256 of each character. We can get the flag back by comparing the encoded parts with these two lists.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from hashlib import md5,sha256

cand = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPWRSTUVWXYZ1234567890@,- _{}'
cand_md5s = []
cand_sha256s = []


FLAG = 'AIS3{'

md5s = [41, 63, 46, 51, 6, 26, 42, 50, 44, 33, 29, 50, 27, 28, 30, 17, 31, 19, 46, 50, 33, 45, 26, 26, 29, 31, 52, 33, 1, 45, 31, 22, 50, 50, 50, 50, 50, 31, 22, 50, 44, 26, 44, 49, 50, 49, 26, 45, 31, 30, 22, 44, 30, 31, 17, 50, 50, 50, 31, 43, 52, 50, 53, 31, 30, 17, 26, 31, 46, 41, 44, 26, 31, 52, 50, 30, 31, 26, 39, 31, 46, 33, 27, 1, 42, 50, 31, 30, 12, 26, 27, 52, 31, 30, 12, 31, 46, 26, 27, 14, 50, 31, 22, 52, 33, 31, 41, 50, 46, 31, 22, 23, 41, 31, 53, 26, 21, 31, 33, 30, 31, 19, 39, 51, 33, 30, 39, 51, 12, 58, 60, 31, 41, 33, 53, 31, 3, 17, 50, 31, 51, 26, 29, 52, 31, 33, 22, 26, 31, 41, 51, 54, 41, 29, 52, 31, 19, 23, 33, 30, 44, 26, 27, 38, 8, 50, 29, 15]
sha256s = [61, 44, 3, 14, 22, 41, 43, 30, 49, 59, 58, 30, 11, 3, 24, 35, 40, 46, 3, 42, 59, 36, 41, 41, 41, 40, 9, 59, 23, 36, 40, 33, 42, 42, 42, 42, 42, 40, 44, 42, 49, 24, 49, 28, 42, 33, 24, 36, 40, 24, 33, 10, 24, 40, 35, 42, 42, 42, 40, 39, 9, 42, 3, 40, 24, 35, 24, 40, 3, 61, 49, 24, 40, 9, 42, 24, 40, 41, 17, 40, 12, 57, 11, 23, 43, 42, 40, 24, 18, 41, 11, 9, 40, 24, 18, 40, 3, 41, 11, 12, 42, 40, 44, 9, 59, 40, 61, 42, 3, 40, 44, 13, 61, 40, 3, 24, 29, 40, 59, 24, 40, 19, 18, 6, 59, 24, 18, 6, 22, 0, 39, 40, 61, 57, 3, 40, 17, 35, 42, 40, 58, 24, 58, 9, 40, 59, 44, 24, 40, 61, 48, 52, 61, 58, 9, 40, 19, 13, 59, 24, 53, 41, 11, 55, 55, 42, 58, 18]

for c in cand :
	cand_md5s.append(int(md5(c.encode()).hexdigest(), 16) % 64)
	cand_sha256s.append(int(sha256(c.encode()).hexdigest(), 16) % 64)

for i in range(5, len(md5s)):
	for j in range(len(cand)):
		if md5s[i] == cand_md5s[j] and sha256s[i] == cand_sha256s[j]:
			FLAG += cand[j]
			break

print(FLAG)

And we can get the flag AIS3{0N_May_16th @Sead00g said Heeeee ReMEMBerEd tH4t heee UseD thE SAME set 0f On1iNe to01s to S01Ve Rsa AeS RCA DE5 at T-cat-cup, AnD 7he kEys aRE AlWAys TCat2019Key}.


RSA101

In this challenge, the connection provides public key (N, e) and encrypted flag.

Then, it will provide a Phi oracle. In this oracle, user can input a number n, and the program will output ((n % phi) % 64). If we can get phi, we are able to get the flag.

In the observation, if n <= phi, it will output 0. So we can keep trying from the top. For instance, 2^2048 output non-zero, then try 2^2047, and output zero, then keep 2^2047, try 2^2047 + 2^2046 …

After we get phi, we can get d by doing modular multiplicative inverse. With d, we can decrypt the flag.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
from pwn import *
from Crypto.Util.number import getPrime, isPrime, inverse
import sys

p = remote('pre-exam-chals.ais3.org', 10201)

p.recvuntil('(e,N) : (', drop=True)

e = p.recvuntil(',', drop=True)

N = p.recvuntil(')', drop=True)

p.recvuntil('Encrypted Flag : ')

enc_flag = p.recvuntil('\n', drop=True)

e = int(e)
N = int(N)
enc_flag = int(enc_flag)

upper_bound = len(bin(N)[2:])

print(upper_bound)

p.recvuntil('n = ? \n')

phi = 0

feedback = -1

while upper_bound > 0:
	for i in range(2048):
		guess = upper_bound - 1
		#print("guess: " + str(guess))
		payload = phi + pow(2, guess)
		p.send(str(payload))
		p.recvuntil(' = ')
		feedback = p.recvuntil('\n', drop=True)
		feedback = int(feedback)
		if feedback == 0:
			break
		upper_bound -= 1
		if upper_bound == 0:
			break
		p.recvuntil('n = ? \n')
	if upper_bound != 0:
		phi += pow(2, guess)
		upper_bound = guess
		print("upper_bound: " + str(upper_bound))
		p.recvuntil('n = ? \n')

"""
print("phi: " + str(phi))
print("e: " + str(e))
print("N: " + str(N))
print("Enc_flag: " + str(enc_flag))
"""

d = inverse(e, phi)

flag = pow(enc_flag, d, N)

print(bytearray.fromhex(hex(flag)[2:]).decode())

And we can get the flag AIS3{RSA_L0L_01100110011101010110001101101011}.


RSA202

In this challenge, we get lots of information after connection:

  • e
  • n1 : r * next_prime(r)
  • n2 : p * q
  • enc : pow(FLAG1, e, n1)
  • enc : pow(FLAG2, e, n2)

And two hints:

  • p, q, r are prime numbers.
  • ((p - 1) % r)^2 + ((r^5 - 1) % p)^2 == 0

For n1:
I use the tool yafu to bruteforce to get r and next_prime(r), which is p and q.

For n2:

  1. Since ((p - 1) % r)^2 + ((r^5 - 1) % p)^2 == 0, (p - 1) % r = 0 and (r^5 - 1) % p = 0.
  2. Since r^5 - 1 = (r - 1) * (r^4 + r^3 + r^2 + r + 1), either (r - 1) % p = 0 or (r^4 + r^3 + r^2 + r + 1) % p = 0.
  3. Since p and r are prime numbers, (r - 1) % p = 0 cannot be true.
  4. That is, (r^4 + r^3 + r^2 + r + 1) = kp.
  5. We already know r, we can calculate (r^4 + r^3 + r^2 + r + 1) and find out that it is a prime.
  6. Regarding (r^4 + r^3 + r^2 + r + 1) = kp, k needs to be 1, and we get p. n2 / p = q.

With p and q, we can get phi = (p - 1) * (q - 1), and we can get d by doing the modular multiplicative inverse. And we get the flag.

However, it is a decimal number. We need to transfer the flag to hex and later transfer to ASCII code.

Finally, we get the flag AIS3{S0me7im3s_I_h4tE_factorDB}.