ふるつき

v(*'='*)v かに

HSCTF 6 writeup

HSCTF 6 had been held from June 3, 2019 7:00 AM EST — June 7, 2019 7:00 PM EDT. We played this competition as a team yoshikingdom. It is a joke team because we only participated in the last 19 hours. We got 12761 points and reached 11th place. Many educational challenges were provided in this CTF. All of them were pretty good.

f:id:Furutsuki:20190608092124p:plain

[Crypto 198points] A Lost Cause

Written by: Disha Name Credits: Shray

Pirate Keith loves cryptography and has protected his treasure with a very annoying caesar shift. He has witten “CGULKVIPFRGDOOCSJTRRVMORCQDZG” on his treasure chest and has left a piece of paper with the following message: “every subsequent letter is shifted one less than the previous.” Knowing this, can you unlock Pirate Keith’s treasure chest?

Following the explanation of the challenge, I wrote a brute force decryption program as shown below.

def keithshift(s, n):
    r = ""
    for c in s:
        r += chr((ord(c) - ord("A") + n) % 26 + ord("A"))

        n = (n + 1) % 26
    return r


s = "CGULKVIPFRGDOOCSJTRRVMORCQDZG"
for i in range(26):
    print(keithshift(s, i))

The output of the program included a readable sentence GLASSESAREUSEFULDONOTLOSETHEM. My teammate yoshiking found the correct flag format: hsctf{glassesareusefuldonotlosethem}.

CHWOOAOWNAQOABQHZKJKPHKOAPDAI
DIXPPBPXOBRPBCRIALKLQILPBQEBJ
EJYQQCQYPCSQCDSJBMLMRJMQCRFCK
FKZRRDRZQDTRDETKCNMNSKNRDSGDL
GLASSESAREUSEFULDONOTLOSETHEM    <----
HMBTTFTBSFVTFGVMEPOPUMPTFUIFN
INCUUGUCTGWUGHWNFQPQVNQUGVJGO
JODVVHVDUHXVHIXOGRQRWORVHWKHP
KPEWWIWEVIYWIJYPHSRSXPSWIXLIQ
LQFXXJXFWJZXJKZQITSTYQTXJYMJR
MRGYYKYGXKAYKLARJUTUZRUYKZNKS
NSHZZLZHYLBZLMBSKVUVASVZLAOLT
OTIAAMAIZMCAMNCTLWVWBTWAMBPMU
PUJBBNBJANDBNODUMXWXCUXBNCQNV
QVKCCOCKBOECOPEVNYXYDVYCODROW
RWLDDPDLCPFDPQFWOZYZEWZDPESPX
SXMEEQEMDQGEQRGXPAZAFXAEQFTQY
TYNFFRFNERHFRSHYQBABGYBFRGURZ
UZOGGSGOFSIGSTIZRCBCHZCGSHVSA
VAPHHTHPGTJHTUJASDCDIADHTIWTB
WBQIIUIQHUKIUVKBTEDEJBEIUJXUC
XCRJJVJRIVLJVWLCUFEFKCFJVKYVD
YDSKKWKSJWMKWXMDVGFGLDGKWLZWE
ZETLLXLTKXNLXYNEWHGHMEHLXMAXF
AFUMMYMULYOMYZOFXIHINFIMYNBYG
BGVNNZNVMZPNZAPGYJIJOGJNZOCZH

[Crypto 316points]Really Secure Algorithm

Written by: cppio

I heard about RSA, so I took a go at implementing it.

We were given n, e, and c in RSA. When I factorized n by factordb.com, it turned out that n is square of some prime p.

n = 263267198123727104271550205341958556303174876064032565857792727663848160746900434003334094378461840454433227578735680279553650400052510227283214433685655389241738968354222022240447121539162931116186488081274412377377863765060659624492965287622808692749117314129201849562443565726131685574812838404826685772784018356022327187718875291322282817197153362298286311745185044256353269081114504160345675620425507611498834298188117790948858958927324322729589237022927318641658527526339949064156992164883005731437748282518738478979873117409239854040895815331355928887403604759009882738848259473325879750260720986636810762489517585226347851473734040531823667025962249586099400648241100437388872231055432689235806576775408121773865595903729724074502829922897576209606754695074134609
e = 65537
c = 63730750663034420186054203696069279764587723426304400672168802689236894414173435574483861036285304923175308990970626739416195244195549995430401827434818046984872271300851807150225874311165602381589988405416304964847452307525883351225541615576599793984531868515708574409281711313769662949003103013799762173274319885217020434609677019589956037159254692138098542595148862209162217974360672409463898048108702225525424962923062427384889851578644031591358064552906800570492514371562100724091169894418230725012261656940082835040737854122792213175137748786146901908965502442703781479786905292956846018910885453170712237452652785768243138215686333746130607279614237568018186440315574405008206846139370637386144872550749882260458201528561992116159466686768832642982965722508678847

In such a case, Euler's totient function  \phi(n) is given as  p(p-1) *1. Thus, we just have to decrypt it.

from Crypto.Util.number import *

p = 16225510719965861964299051658340559066224635411075742500953901749924501886090804067406052688894869028683583501052917637552385089084807531319036985272636554557876754514524927502408114799014949174520357440885167280739363628642463479075654764698947461583766215118582826142179234382923872619079721726020446020581078274482268162477580369246821166693123724514271177264591824616458410293414647

exec(open("secure.txt").read())

assert p * p == n

phi = p * (p - 1)
d = inverse(e, phi)

m = pow(c, d, n)
print(m)
print(bytes.fromhex(hex(m)[2:]))

The flag was hsctf{square_number_time}.

[Reversal 180points] A Byte

Written by: ItzSomebody

Just one byte makes all the difference.

We were given a 64bit ELF executable. It takes one command line argument and determines if the given text is equal to the correct one. By disassembling it, I found the correct length was 35. Since it transformed the given text and compared it to magic text irbugzv1v^x1t^jo1v^e5^v@2^9i3c@138|, I tried giving irbugzv1v^x1t^jo1v^e5^v@2^9i3c@138|. Then it converted to hsctf{w0w_y0u_kn0w_d4_wA3_8h2bA029}. It seemed the flag.

[Reversal 252points] License

Written by: ItzSomebody

Description: Keith made a cool license-checking program but he forgot the flag he used to create the key! To make matters worse, he lost the source code and stripped the binary for his license-generator program. Can you help Keith recover his flag? All he knows is:

  • The license key is 4-EZF2M-7O5F4-V9P7O-EVFDP-E4VDO-O
  • He put his name (in the form of 'k3ith') as the first part of the flag
  • There are 3 underscores
  • The flag is in the format hsctf{}
  • The flag doesn't have random character sequences (you should be able to read the entire flag easily).
  • The flag only contains lowercase English letters and numbers.
  • The generator might produce the same keys for different inputs because Keith was too lazy to write the algorithm properly.

We were given a license generator. It took the text and generated the corresponding license. By the observation, it turned out that the binary converted one byte to byte and the conversion had regularity. For example, o is converted to A, p -> B, q -> C, and r -> D. Using this characteristic and hint given at the description, we could get the flag byte by byte. The flag was hsctf{k3ith_m4k3s_tr4sh_r3}. We needed to note that both 3 and } are converted to O.

[Reversal 295points] DaHeck

Written by: ItzSomebody

Unicode? ...da heck?

We were given the following program written in Java. It converted the given text byte by byte. So we could determine correct input by brute-forcing.

public class DaHeck {
    private static boolean check_flag(String s) {
        char[] cs = s.toCharArray();
        char[] daheck = new char[cs.length];
        int n = cs.length ^ daheck.length;
        char[] heck = "001002939948347799120432047441372907443274204020958757273".toCharArray();

        while (true) {

            try {
                if (heck[n] - cs[n % cs.length] < 0) daheck[n] = (char) (heck[n] - cs[n % cs.length] % 128);
                else daheck[n] = (char) (heck[n] - cs[n % cs.length] % 255);

                n++;
            } catch (Throwable t) {
                break;
            }
        }

        return "\uffc8\uffbd\uffce\uffbc\uffca\uffb7\uffc5\uffcb\u0005\uffc5\uffd5\uffc1\uffff\uffc1\uffd8\uffd1\uffc4\uffcb\u0010\uffd3\uffc4\u0001\uffbf\uffbf\uffd1\uffc0\uffc5\uffbb\uffd5\uffbe\u0003\uffca\uffff\uffda\uffc3\u0007\uffc2\u0001\uffd4\uffc0\u0004\uffbe\uffff\uffbe\uffc1\ufffd\uffb5".equals(new String(daheck));
    }

    public static void main(String... args) {
        if (args.length != 1) {
            System.out.println(":thonk:");
            System.exit(1);
        }

        if (check_flag(args[0])) System.out.println("Huh. How'd you know?");
        else System.out.println("Da heck? No.");
    }
}

I wrote the following solve function. The correct input was hsctf{th4t_w4s_fun!_l3ts_try_s0m3_m0r3_r3v3rs3}.

    public static void solve() {
        char[] daheck =  "\uffc8\uffbd\uffce\uffbc\uffca\uffb7\uffc5\uffcb\u0005\uffc5\uffd5\uffc1\uffff\uffc1\uffd8\uffd1\uffc4\uffcb\u0010\uffd3\uffc4\u0001\uffbf\uffbf\uffd1\uffc0\uffc5\uffbb\uffd5\uffbe\u0003\uffca\uffff\uffda\uffc3\u0007\uffc2\u0001\uffd4\uffc0\u0004\uffbe\uffff\uffbe\uffc1\ufffd\uffb5".toCharArray();
        char[] heck = "001002939948347799120432047441372907443274204020958757273".toCharArray();

        for (int i = 0; i < daheck.length; i++) {
            for (int c = 0; c < 256; c++) {
                if (heck[i] - c < 0 && daheck[i] == (char) (heck[i] - c % 128)) {
                    System.out.print((char)c);
                    break;
                } else if (daheck[i] == (char) (heck[i] - c % 255) ) {
                    System.out.print((char)c);
                    break;
                }
            }
        }
    }

[Reversal 375points]I Thought Trig Was Really Easy

Written by: v1sanjay

After finishing a hard lesson in geometry class, Keith decided that he wanted to put your understanding of trig and python to the test. Can you solve his challenge?

We were given the following script. It seems a bit difficult to read.

import math

def nice_math(x, y):
    return round(x + y*math.cos(math.pi * x))

lots_of_nums = lambda n,a:(lambda r:[*r,n-sum(r)])(range(n//a-a//2,n//a+a//2+a%2))

def get_number(char):
    return ord(char) - 96

inp = input("Enter the text: ")

out = []
for i in range(0, len(inp)):
    for j in lots_of_nums(nice_math(get_number(inp[i]), len(inp) - i), i + 1):
        out.append(nice_math(j, i + 1))

ans = [-25, 1, 10, 7, 4, 7, 2, 9, 3, 8, 1, 10,
            3, -1, -8, 3, -6, 5, -4, 7, -5, 8, -3,
            10, -1, 12, 10, 7, -6, 9, -4, 11, -2,
            13, -2, -11, 6, -9, 8, -7, 10, -5, 12,
            1, -12, 7, -10, 9, -8, 11, -6, 13, -4,
            11, 6, -13, 8, -11, 10, -9, 12, -7, 14,
            -5, 22, -16, 7, -14, 9, -12, 11, -10, 13,
            -8, 15, -6, -2, 2, -21, 4, -19, 6, -17, 8,
            -15, 10, -13, 12, -11, 5]
if (out == ans):
    print("That is correct! Flag: hsctf{" + inp + "}")
else:
    print("Nope sorry, try again!")

The lots_of_nums function takes two numbers n and a and creates the numerical array whose size equals to a+1. So we could divide the array ans into corresponding to each input character. As array for first character has a length of 2, the first two elements of ans (i.e. [-25, 1]) correspond. For the second character, ans[2:5] corresponds.

From the avobe, we could determine the correct input byte by byte.

import math


def nice_math(x, y):
    return round(x + y * math.cos(math.pi * x))


lots_of_nums = lambda n, a: (lambda r: [*r, n - sum(r)])(
    range(n // a - a // 2, n // a + a // 2 + a % 2)
)


def get_number(char):
    return ord(char) - 96


# inp = input("Enter the text: ")

ans = [-25, 1, 10, 7, 4, 7, 2, 9, 3, 8, 1, 10,
            3, -1, -8, 3, -6, 5, -4, 7, -5, 8, -3,
            10, -1, 12, 10, 7, -6, 9, -4, 11, -2,
            13, -2, -11, 6, -9, 8, -7, 10, -5, 12,
            1, -12, 7, -10, 9, -8, 11, -6, 13, -4,
            11, 6, -13, 8, -11, 10, -9, 12, -7, 14,
            -5, 22, -16, 7, -14, 9, -12, 11, -10, 13,
            -8, 15, -6, -2, 2, -21, 4, -19, 6, -17, 8,
            -15, 10, -13, 12, -11, 5]

def get_len(n):
    res = 0
    for i in range(n + 1):
        res += i + 2
    return res


for n in range(12):
    inp = "A" * 12
    for ic in range(0x20, 0x7F):
        inp = inp[:n] + chr(ic) + inp[n + 1 :]
        out = []
        for i in range(0, len(inp)):
            for j in lots_of_nums(nice_math(get_number(inp[i]), len(inp) - i), i + 1):
                out.append(nice_math(j, i + 1))
        assert len(out) == len(ans)
        p1 = get_len(n - 1)
        p2 = get_len(n)
        if out[p1:p2] == ans[p1:p2]:
            print(chr(ic))
            break

The solver script outputs :hyperthonk: and flag is hsctf{:hyperthonk:}.

[Reversal 467points]Forgot your Password?

Written by: Ptomerty

Help! I got this new lock for Christmas, but I've forgotten the first two values. I know the last value is hsctfissocoolwow. I also managed to grab a copy of their secret key generator. Can you help me out?

Note: submit the first two combo values separated by a space in hex format.

We were given the generator.py

#!/usr/bin/env python2

ch = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!\"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~ '
s = [SECRET_1, SECRET_2]
# TOP SECRET: DO NOT LEAK
def o(x,k):
    return x<<k
def m(a):
    return a&0xffffffffffffffff
def next():
    b = m(s[0]+s[1])
    h()
    return m(b)
def p(k, x):
    return x>>(64-k)
def x(b, a):
    return a^b
def oro(a, b):
    return a|b
def h():
    s1 = m(x(s[0],s[1]))
    s[0] = m(x(oro(o(s[0],55),p(55,s[0])),x(s1,(o(s1,14)))))
    s[1] = m(oro(o(s1,36),p(36,s1)))

# Helper methods
def bin2chr(data):
    result = ''
    while data:
        char = data & 0xff
        result += chr(char)
        data >>= 8
    return result

def isp(d):
    if all(c in ch for c in d):
        return d
    else:
        return d.encode('hex')

# throw away first value for additional randomness
next()
next()

COMBO_NUM_1 = isp(bin2chr(next())) + isp(bin2chr(next()))
COMBO_NUM_2 = isp(bin2chr(next())) + isp(bin2chr(next()))
COMBO_NUM_3 = isp(bin2chr(next())) + isp(bin2chr(next()))

print "Thanks! Your numbers are: "
print COMBO_NUM_1
print COMBO_NUM_2
print COMBO_NUM_3

The function next is the core of this challenge. It works as shown below.

def next(x, y):
    xx = RotateLeft(x, 55) ^ ((x ^ y) ^ ((x ^ y) << 14))
    yy = RotateLeft(x ^ y, 36)
    return xx & 0xFFFFFFFFFFFFFFFF, yy & 0xFFFFFFFFFFFFFFFF

As we know that the last x2, y2 (let x2, y2 = next(x, y) ) have a constraint that bin2chr(x2 + y2) == 'ocoolwow' and previous one (i.e. x and y) has bin2chr(x + y) == 'hsctfiss' as its constraint. Using this fact, I managed to get x and y with z3.

from z3 import *


def chr2bin(s):
    data = 0
    for c in s:
        data <<= 8
        data += ord(c)
    return data


def next(x, y):
    xx = RotateLeft(x, 55) ^ ((x ^ y) ^ ((x ^ y) << 14))
    yy = RotateLeft(x ^ y, 36)
    return xx & 0xFFFFFFFFFFFFFFFF, yy & 0xFFFFFFFFFFFFFFFF


X, Y = BitVecs("x y", 64)

s = Solver()
s.add(X + Y == chr2bin("hsctfiss"[::-1]))
X2, Y2 = next(X, Y)
s.add(X2 + Y2 == chr2bin("ocoolwow"[::-1]))

r = s.check()
print(r)
if r != sat:
    exit()
m = s.model()
x = m[X].as_long()
y = m[Y].as_long()
print([x, y])

Also, one more previous pair (u, v) could get by the following script.

U, V = BitVecs("u v", 64)

s = Solver()
X2, Y2 = next(U, V)
s.add(X2 == x)
s.add(Y2 == y)

r = s.check()
if r != sat:
    exit()
m = s.model()
u = m[U].as_long()
v = m[V].as_long()
print([u, v])

By repeating this several times, I could get the first pair (SECRET_1, SECRET_2). It was s = [7294543424534665732, 16423178736247365903] and generator.py outputted the combo e06f76cd556604f0f21c34f1519d2fd2 73c8535ab0f954b5ad1cbab7abc18309 hsctfissocoolwow.

from z3 import *


def chr2bin(s):
    data = 0
    for c in s:
        data <<= 8
        data += ord(c)
    return data


def next(x, y):
    xx = RotateLeft(x, 55) ^ ((x ^ y) ^ ((x ^ y) << 14))
    yy = RotateLeft(x ^ y, 36)
    return xx & 0xFFFFFFFFFFFFFFFF, yy & 0xFFFFFFFFFFFFFFFF


X, Y = BitVecs("x y", 64)

s = Solver()
s.add(X + Y == chr2bin("hsctfiss"[::-1]))
X2, Y2 = next(X, Y)
s.add(X2 + Y2 == chr2bin("ocoolwow"[::-1]))

r = s.check()
print(r)
if r != sat:
    exit()
m = s.model()
x = m[X].as_long()
y = m[Y].as_long()
print([x, y])

for i in range(6):
    X, Y = BitVecs("x y", 64)

    s = Solver()
    X2, Y2 = next(X, Y)
    s.add(X2 == x)
    s.add(Y2 == y)

    r = s.check()
    if r != sat:
        exit()
    m = s.model()
    x = m[X].as_long()
    y = m[Y].as_long()
    print([x, y])