rgbCTF 2020 Writeup

この大会は2020/7/12 2:00(JST)~2020/7/14 2:00(JST)に開催されました。
今回もチームで参戦。結果は4399点で1047チーム中72位でした。
自分で解けた問題をWriteupとして書いておきます。

Sanity Check (Beginner)

問題にフラグが書いてあった。

rgbCTF{g0_g3t_th4t_j4m!}

Joke Check! (Beginner)

シーザー暗号。https://www.geocachingtoolbox.com/index.php?lang=en&page=caesarCipherで復号する。

Rotation 11:
rgbCTF{a_chicken_caesar_salad}
rgbCTF{a_chicken_caesar_salad}

A Fine Day (Beginner)

quipqiupで復号する。

This is encrypted with an affine cipher. Affine ciphers are a form of substitution cipher. Its key consists of two numbers, a and b. To encrypt a letter, multiply its place in the alphabet (so a=0, b=1, etc.) by a, and then add b. Finally take that mod 26 and convert it back into a character. The affine cipher isn't really that strong. Since it's mod 26, there are only a few hundred different keys, which can be easily brute forced. Anyway, here's your flag: rgbCTF{a_fine_affine_cipher}

実際はAffine暗号になっていたようだ。

rgbCTF{a_fine_affine_cipher}

r/ciphers (Beginner)

quipqiupで復号する。

This is a monoalphabetic substitution cipher, which can be attacked with frequency analysis. Although this attack can be done by hand, it's usually much easier to use a program to do it for you. Two good websites that will decrypt substitution ciphers for you are at guballa.de/substitution-solver and quipqiup.com. If you haven't tried them before, you should check them out. Here's your flag: rgbCTF{just_4sk_th3_int3rn3t_t0_d3crypt_it} Also, Alec believes it's very important that you see this: https://i.redd.it/1p7w8k0272851.jpg
rgbCTF{just_4sk_th3_int3rn3t_t0_d3crypt_it}

Simple RSA (Beginner)

factordbでnを素因数分解する。

n = 255097177 * 22034393943473183756163118460342519430053

あとはリトルエンディアンになることに注意して、そのまま復号する。

#!/usr/bin/env python3
from sympy import mod_inverse
from math import gcd

def lcm(a: int, b: int) -> int:
    return a * b // gcd(a, b)

n = 5620911691885906751399467870749963159674169260381
e = 65537
c = 1415060907955076984980255543080831671725408472748

p = 255097177
q = 22034393943473183756163118460342519430053

lmbd = lcm(p - 1, q - 1)
d = mod_inverse(e, lmbd)
m = pow(c, d, n)
flag = m.to_bytes(m.bit_length()//8 + 1, byteorder='little')
print(flag)
rgbCTF{brut3_f0rc3}

Pieces (Beginner)

divideは以下の処理で1文字を2文字であらわす。

・ASCIIコードで2で割った値の文字
・2で割ったあまりが0の場合は|、1の場合は/

この処理を逆算して、フラグを割り出す。

def rev_divide(s):
    c1 = s[0]
    c2 = s[1]
    code = ord(c1) * 2
    if c2 == '/':
        code += 1
    return chr(code)

ct = '9|2/9/:|4/7|8|4/2/1/2/9/'
input = ''
for i in range(0, len(ct), 2):
    input += rev_divide(ct[i:i+2])

flag = 'rgbCTF{%s}' % input
print flag
rgbCTF{restinpieces}

Shoob (Beginner)

Stegsolveで開き、Green plane 0を見ると、フラグが書いてあった。
f:id:satou-y:20200723090629p:plain

rgbCTF{3zier_4haN_s4n1ty}

Quirky Resolution (Beginner)

Red plane 0を見ると、QRコードが出てきた。
f:id:satou-y:20200723090731p:plain
コードリーダでデコードする。

VfuflVAouacXoIOHsrNP
xgSDHLQSDBIO7fZ1w8zV
SUdwzyRJLKEHkqP2i0NK
h5fYSD1b3rymDWKkx9uw
fZELgZV2l0xllyyMADkw
voRu9GvHFy3qI7BK3yAL
EIUOvFKPbxcci6ExsXFv
G5YlE7Id9NELQ3NL5Kex
gaf6UQ77Q8aQkmjY6vzq
IF45ubpqdAZertRvfnER
XIj9b7ZGaep7Aspqvzhu
P6IhEFnLdlx0w5BSjo0A
L9ifDGyICNqaxNywIeAg
mxWg0h8XpHRN3Ai6P3aU
rgbCTF{th3_qu1rk!er_th3_b3tt3r}
VXPphIDrM0WagtZJYecY
PjXSHuJTAcLK9uUnGrCB
PW2MjAPE6KGe1iB8Hmqg
3WfKjbXOXXNVMj79F2UI
gzFHSU1HN9KAKhwPElOf
x9lL2wLHtee0KG90DFW9
OoO0YnJDU8Kfm7sFADjQ
XJ7FnxIARmTZ0WgGKeyS
AQEgDg8Sx1XBicDHfZ2i
OxvFwnTl9FKQTm1UikjK
aifFg3hc1gKLJWAjC5b4
q2SoVWnm95W2DNVGFtzf
cxaLH9lpesGwhehpv8YU
zO9W4DZM6uIlhCAyM1Hj

この文字列の中にフラグが入っていた。

rgbCTF{th3_qu1rk!er_th3_b3tt3r}

Ralphie! ([ZTC])

Stegsolveで開き、Red plane 3でQRコードが見えた。
f:id:satou-y:20200723091034p:plain
コードリーダでデコードする。

rgbCTF{BESURETODRINKYOUROVALTINE}

vaporwave1 ([ZTC])

Sonic Visualiserで開き、スペクトログラムを見るとフラグが現れた。
f:id:satou-y:20200723091155p:plain

rgbCTF{s331ng_s0undz}

icanhaz ([ZTC])

icanhazを解凍すると、hexdumpが1行8バイト単位で書いてある。バイナリにしてみる、

with open('icanhaz', 'r') as f:
    lines = f.readlines()

data = ''
for line in lines:
    data += ''.join(line.split(' ')[1:5]).decode('hex')

with open('icanhaz.7z', 'wb') as f:
    f.write(data)

7zのファイルフォーマットのファイルになった。そして7zipを解凍するとsvgファイルが展開された。そのままブラウザで見ても、色が分かりにくいので、#fffffdを#000000に変更して開く。
f:id:satou-y:20200723091406p:plain
QRコードになっているので、コードリーダでデコードする。

/Td6WFoAAATm1rRGAgAhARYAAAB0L+Wj4AbxAN1dAA2XxNFhRNBaOJSxhV08AXoOcZxtalpXU+c+q/ppfZc1/t0z3BU/P16F9jAlXbjrzh5cXk/9vLbc+8NQJ8PNawtALEPD17f25zdggODx3xzNLY3SjGTIlX0fbqo6HFkHYkIzOjjUgJcN1KbzGRouW+G8TakjrJ4y5Pk7jv/stqRiV0ICPYxKpnZSEn0aLzQSl46j6H3BBUBhRuGgxue3TXIzw5HGMlchgNBs6SCfHU0SkX4zlSKqOWSyKrJ5JMgwC47en2kI68/tRNQYaYzvGGcWcR/iEgNYO/jHVDVLAAAAADjqmgxrEIjCAAH5AfINAADD+B/oscRn+wIAAAAABFla

base64文字列なので、デコードしてファイル保存すると、また7zファイルになる。展開すると、UTF-8テキストでQRコードが書かれている。
f:id:satou-y:20200723091458p:plain
これをコードリーダでデコードする。

rgbCTF{iCanHaz4N6DEVJOB}

Too Slow (Pwn/Rev)

Ghidraでデコンパイルする。

undefined8 main(void)

{
  uint uVar1;
  
  puts("Flag Decryptor v1.0");
  puts("Generating key...");
  uVar1 = getKey();
  win((ulong)uVar1);
  return 0;
}

ulong getKey(void)

{
  uint local_10;
  uint local_c;
  
  local_10 = 0;
  while (local_10 < 0x265d1d23) {
    local_c = local_10;
    while (local_c != 1) {
      if ((local_c & 1) == 0) {
        local_c = (int)local_c / 2;
      }
      else {
        local_c = local_c * 3 + 1;
      }
    }
    local_10 = local_10 + 1;
  }
  return (ulong)local_10;
}

void win(uint param_1)

{
  long in_FS_OFFSET;
  uint local_3c;
  undefined8 local_38;
  undefined8 local_30;
  undefined8 local_28;
  undefined8 local_20;
  undefined4 local_18;
  undefined local_14;
  long local_10;
  
  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  local_38 = 0x12297e12426e6f53;
  local_30 = 0x79242e48796e7141;
  local_28 = 0x49334216426e2e4d;
  local_20 = 0x473e425717696a7c;
  local_18 = 0x42642a41;
  local_14 = 0;
  local_3c = 0;
  while (local_3c < 9) {
    *(uint *)((long)&local_38 + (ulong)local_3c * 4) =
         *(uint *)((long)&local_38 + (ulong)local_3c * 4) ^ param_1;
    local_3c = local_3c + 1;
  }
  printf("Your flag: rgbCTF{%36s}\n",&local_38);
  if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return;
}

getKey()は終わらないが、取得できたとしたら、0x265d1d23であると考える。32bit単位でXORを取ればフラグになる。

from Crypto.Util.strxor import strxor

key = '265d1d23'.decode('hex')[::-1]
ct0 = '12297e12426e6f53'.decode('hex')[::-1]
ct1 = '79242e48796e7141'.decode('hex')[::-1]
ct2 = '49334216426e2e4d'.decode('hex')[::-1]
ct3 = '473e425717696a7c'.decode('hex')[::-1]
ct4 = '42642a41'.decode('hex')[::-1]
ct = ct0 + ct1 + ct2 + ct3 + ct4

flag = ''
for i in range(0, len(ct), 4):
    flag += strxor(key, ct[i:i+4])

flag = 'rgbCTF{%s}' % flag
print flag
rgbCTF{pr3d1ct4bl3_k3y_n33d5_no_w41t_cab79d}

Advanced Reversing Mechanics 1 (Pwn/Rev)

Ghidraでデコンパイルする。

undefined4 main(undefined4 param_1,int param_2)

{
  char *pcVar1;
  byte *pbVar2;
  byte *pbVar3;
  byte local_110 [256];
  
  pbVar2 = local_110;
  pcVar1 = stpcpy((char *)local_110,*(char **)(param_2 + 4));
  while (local_110[0] != 0) {
    *pbVar2 = local_110[0] - 1;
    pbVar2 = pbVar2 + 1;
    local_110[0] = *pbVar2;
  }
  if (pcVar1 + -(int)local_110 != (char *)0x0) {
    pbVar2 = local_110;
    do {
      pbVar3 = pbVar2 + 1;
      printf("%02X, ",(uint)*pbVar2);
      pbVar2 = pbVar3;
    } while (local_110 + (int)(pcVar1 + -(int)local_110) != pbVar3);
  }
  putchar(10);
  return 0;
}

暗号処理は1つコードがシフトされるだけなので、簡単に戻せる。

enc = ['71', '66', '61', '42', '53', '45', '7A', '40', '51', '4C', '5E', '30',
    '79', '5E', '31', '5E', '64', '59', '5E', '38', '61', '36', '65', '37',
    '63', '7C']

flag = ''
for e in enc:
    code = int(e, 16) + 1
    flag += chr(code)
print flag
rgbCTF{ARM_1z_2_eZ_9b7f8d}

Alien Transmission 1 (Forensics)

wavの画像受信データがあるかもしれないので、sstvのツールでデコードしてみる。

$ sstv -d squeakymusic.wav -o flag.png
[sstv] Searching for calibration header... Found!    
[sstv] Detected SSTV mode Robot 36
[sstv] Decoding image...   [##############################################]  99%
[sstv] Reached end of audio whilst decoding.
[sstv] Drawing image data...
[sstv] ...Done!

f:id:satou-y:20200723200516p:plain

rgbctf{s10w_2c4n_1s_7h3_W4V3}

I Love Rainbows (Cryptography)

md5またはsha256の値が並んでいる。2文字以下のハッシュのブルートフォースで戻す。

from hashlib import *
from string import *
import itertools

with open('rainbows.txt', 'r') as f:
    hashes = [line.rstrip() for line in f.readlines()]

flag = ''
for h in hashes:
    found = False
    for r in range(1, 3):
        for c in itertools.product(printable, repeat=r):
            text = ''.join(c)
            if len(h) == 32:
                try_h = md5(text).hexdigest()
            else:
                try_h = sha256(text).hexdigest()
            if try_h == h:
                found = True
                flag += text
                break
        if found:
            break

print flag
rgbCTF{4lw4ys_us3_s4lt_wh3n_h4shing}

e (Cryptography)

eの値が小さいので、e乗根を取る。

#!/usr/bin/env python3
import gmpy

e = 0b1101
c = 0x6003a15ff3f9bc74fcc48dc0f5fc59c31cb84df2424c9311d94cb40570eeaa78e0f8fc2917addd1afc8e5810b2e80a95019c88c4ee74849777eb9d0ee27ab80d3528c6f3f95a37d1581f9b3cd8976904c42f8613ee79cf8c94074ede9f034b61433f1fef835f2a0a45663ec4a0facedc068f6fa2b534c9c7a2f4789c699c2dcd952ed82180a6de00a51904c2df74eb73996845842276d5523c66800034351204b921d4780180ca646421c61033017e4986d9f6a892ed649c4fd40d4cf5b4faf0befb1e2098ee33b8bea461a8626dd8cd2eed05ccd471700e2a1b99ed347660cbd0f202212f6c0d7ad8ef6f878d887af0cd0429c417c9f7dd64890146b91152ea0c30637ce503635018fd2caf436a12378e5892992b8ec563f0988fc0cebd2926662d4604b8393fb2000

m = int(gmpy.root(c, e)[0])
flag = m.to_bytes(m.bit_length()//8 + 1, byteorder='little')
print(flag)
rgbCTF{lucky_number_13}

Occasionally Tested Protocol (Cryptography)

UNIXTIMEをseedにしていて、10回乱数を出力してから、次以降の乱数とXORしている。UNIXTIMEの多少のずれを出力して試して特定してから、XORキーを割り出して、復号する。

#!/usr/bin/env python3
import socket
from random import seed, randint as w
from time import time

def recvuntil(s, tail):
    data = b''
    while True:
        if tail in data:
            return data.decode()
        data += s.recv(1)

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('167.172.123.213', 12345))

data = recvuntil(s, b'\n').rstrip()
print(data)

nums = []
for _ in range(10):
    data = recvuntil(s, b'\n').rstrip()
    print(data)
    nums.append(int(data))

ut = int(time()) - 1
while True:
    seed(ut)
    try_nums = []
    for _ in range(10):
        try_nums.append(w(5, 10000))
    if nums == try_nums:
        break
    ut += 1

data = recvuntil(s, b'\n').rstrip()
print(data)

c = int(data.split(' ')[-1])
ct = c.to_bytes(c.bit_length()//8 + 1, byteorder='little')

b = bytearray([w(0, 255) for _ in range(40)])

flag = bytes([l ^ p for l, p in zip(ct, b)])
print(flag)

実行結果は以下の通り。

Here's 10 numbers for you:
6530
3291
6449
1757
2206
6694
8303
4500
8037
8608
Here's another number I found:  11924462008570498664266304436057399923000110040161498324004106845812
b'rgbCTF{random_is_not_secure}'
rgbCTF{random_is_not_secure}

Shakespeare Play, Lost (and found!) (Cryptography)

行と文字の位置を書いていると推測して、抜き出す。

with open('play', 'r') as f:
    lines = [line.rstrip() for line in f.readlines()]

with open('some_numbers.txt', 'r') as f:
    nums = [line.rstrip() for line in f.readlines()]

flag = ''
for num in nums:
    n0 = int(num.split(', ')[0])
    n1 = int(num.split(', ')[1])
    flag += lines[n0][n1]

flag = 'rgbCTF{%s}' % flag
print flag
rgbCTF{itsanrgbtreeeeeee!}

N-AES (Cryptography)

サーバの処理概要は以下の通り。

・challenge: ランダム64バイトのbase64エンコード
・challengeにpaddingして、AES-ECB暗号化
 ・ランダム1バイトseed
 ・16バイトrandintで鍵生成

■1. Encrypt
・base64で平文を指定
・base64でkeyのseedを指定
・seedの各バイトの分暗号化

■2. Decrypt
・base64で暗号文を指定
・base64でkeyのseedを指定
・seedの各バイトの分暗号化

■3. Solve challenge
・challengeのbase64文字列を当てたら、フラグが表示される。

フラグの暗号化処理のコードが悪く、ランダム1バイト文字だけで、128回暗号化している。総当たりでchallengeとしてbase64文字列に復号できるものを探す。

#!/usr/bin/env python3
import socket
from base64 import b64encode, b64decode
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
from os import urandom
from random import seed, randint
from string import *

BLOCK_SIZE = 16

def recvuntil(s, tail):
    data = b''
    while True:
        if tail in data:
            return data.decode()
        data += s.recv(1)

def rand_block(key_seed=urandom(1)):
    seed(key_seed)
    return bytes([randint(0, 255) for _ in range(BLOCK_SIZE)])

def decrypt_chall(key, text):
    for i in range(128):
        text = AES.new(key, AES.MODE_ECB).decrypt(text)
    return text

def is_b64str(s):
    if len(s) == 0:
        return False

    chars = ascii_letters + digits + '+/'
    for c in s.rstrip('='):
        if c not in chars:
            return False
    return True

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('challenge.rgbsec.xyz', 34567))

data = recvuntil(s, b'\n').rstrip()
print(data)

ct = b64decode(data)

data = recvuntil(s, b'> ')
print(data + '3')
s.sendall(b'3\n')

for code in range(256):
    key = rand_block(bytes([code]))
    try:
        challenge = unpad(decrypt_chall(key, ct), BLOCK_SIZE)
        if is_b64str(challenge):
            break
    except:
        continue

ans = b64encode(challenge)
data = recvuntil(s, b': ')
print(data + ans.decode())
s.sendall(ans + b'\n')

data = recvuntil(s, b'\n').rstrip()
print(data)
data = recvuntil(s, b'\n').rstrip()
print(data)
data = recvuntil(s, b'\n').rstrip()
print(data)

実行結果は以下の通り。

UOVh5i8DP7uB2MbWOPevMmsOiddn5K8se5EXHEm4ON3/wMgHdzO2yJ+NrxFCt8CWwqS/kvpo7/N0Tmfh/DF17xE7FyyFL9nP7EOWnaMgswKzY4hwnFCmHOvrkUK2WX/u
[1] Encrypt
[2] Decrypt
[3] Solve challenge
[4] Give up
> 3
Enter the decrypted challenge, in base64: QXBuYWcyTVY5ai9zRUR1SVRQek84VUtqKzg4ejB4bWdrbVBjbDFjUUVaY1lUeVVHM0NZNS83SzNDU01saFBza01QUC9XOUtpQnBIR0NlNDRJSnVtcmc9PQ==
Correct!
Here's your flag:
rgbCTF{i_d0nt_7hink_7his_d03s_wh47_y0u_7hink_i7_d03s}
rgbCTF{i_d0nt_7hink_7his_d03s_wh47_y0u_7hink_i7_d03s}