InfoSec CTF 2022 Writeup

この大会は2022/12/3 16:00(JST)~2022/12/4 16:00(JST)に開催されました。
今回もチームで参戦。結果は2219点で219チーム中6位でした。
自分で解けた問題をWriteupとして書いておきます。

Discord, anyone? (Hello)

Discordに入り、#announcementsチャネルのメッセージを見ると、フラグのサンプルが記載されている。

flag{d@_Flag_m@y_b3_wr1773n_1n_1337_o_have_random_nums14344555}

2048 (Hello)

https://infosec-2048.chals.io/js/scoreboard.jsを見ると、こう書いてある。

function send_score(score) {
	var url  = "/score.php?score=" + score;
	//alert("Over111 " + url);
	var xhr  = new XMLHttpRequest();
	xhr.open('GET', url, true);
	xhr.onload = function () {
    	var users = JSON.parse(xhr.responseText);
    	if (xhr.readyState == 4 && xhr.status == "200") {
    		console.log(users);
		alert(users.message);
    		//console.log(typeof users)
        	//console.table(users);
    	} else {
        	console.error(users);
    	}
	}
	xhr.send(null);
}

以下のパスでアクセスできるようだ。

/score.php?score=<score>

scoreに十分高い値を指定してアクセスしてみる。
https://infosec-2048.chals.io/score.php?score=10000000000000にアクセスしたら、以下のように表示された。

{"message":"That is impossible!!!!! flag{Y0R_D4_B35T_1N_GAM35}!"}
flag{Y0R_D4_B35T_1N_GAM35}

Roll (Hello)

Unicodeで最後の籯籵籪籰の部分がflag{になると推測する。

\xe7\xb1\xaf -> f
\xe7\xb1\xb5 -> l
\xe7\xb1\xaa -> a
\xe7\xb1\xb0 -> g
\xe7\xb2\x84 -> {

このコードから差分を計算し、復号する。

#!/usr/bin/env python3
with open('ct.txt', 'rb') as f:
    enc = f.read()

flag = ''
code = b''
for i in range(len(enc)):
    if enc[i] != ord(' '):
        code += bytes([enc[i]])
    else:
        flag += ' '
    if len(code) == 3:
        if code[:2] == b'\xe7\xb0':
            flag += chr(code[2] - 0x89)
        elif code[:2] == b'\xe7\xb1':
            flag += chr(code[2] - 0x49)
        else:
            flag += chr(code[2] - 0x9)
        code = b''
print(flag)

復号結果は以下の通り。

Yep! This is flag{Ju5T_a_R0T8OOO}
flag{Ju5T_a_R0T8OOO}

Characters (Hello)

$ file sc.exe 
sc.exe: PE32 executable (console) Intel 80386 Mono/.Net assembly, for MS Windows

dnSpyでデコンパイルする。

using System;

namespace Task1
{
	// Token: 0x02000002 RID: 2
	internal class Program
	{
		// Token: 0x06000001 RID: 1 RVA: 0x00002050 File Offset: 0x00000250
		private static void Main(string[] args)
		{
			string text = "The flag is flag{5+r1n9Se@rCh15c00l}";
			Console.WriteLine("Today's lucky number is " + text.Length.ToString());
		}
	}
}

コードにフラグが書かれていた。

flag{5+r1n9Se@rCh15c00l}

Power (Reverse)

$ file enc.exe 
enc.exe: PE32 executable (console) Intel 80386 Mono/.Net assembly, for MS Windows

dnSpyでデコンパイルする。

using System;
using System.IO;
using System.Linq;
using System.Security.Cryptography;

namespace Task2
{
	// Token: 0x02000002 RID: 2
	internal class Program
	{
		// Token: 0x06000001 RID: 1 RVA: 0x00002050 File Offset: 0x00000250
		private static void Main(string[] args)
		{
			try
			{
				Console.WriteLine("Input three numbers separated by a space");
				string text = Console.ReadLine();
				string[] source = text.Split(new char[]
				{
					' '
				});
				byte[] array = source.Take(3).ToArray<string>().Select(new Func<string, byte>(byte.Parse)).ToArray<byte>();
				byte[] array2 = new byte[Program.keyArr.Length + array.Length];
				Program.keyArr.CopyTo(array2, 0);
				array.CopyTo(array2, Program.keyArr.Length);
				Program.keyArr = array2;
			}
			catch (Exception)
			{
				return;
			}
			Program.EncryptAesManaged();
		}

		// Token: 0x06000002 RID: 2 RVA: 0x000020F4 File Offset: 0x000002F4
		private static void EncryptAesManaged()
		{
			try
			{
				string arg = Program.Decrypt(Program.encStr, Program.keyArr, Program.ivArr);
				Console.WriteLine("Decrypted data: {0}", arg);
			}
			catch (Exception)
			{
			}
			Console.ReadKey();
		}

		// Token: 0x06000003 RID: 3 RVA: 0x0000213C File Offset: 0x0000033C
		private static byte[] Encrypt(string plainText, byte[] Key, byte[] IV)
		{
			byte[] result;
			using (AesManaged aesManaged = new AesManaged())
			{
				ICryptoTransform transform = aesManaged.CreateEncryptor(Key, IV);
				using (MemoryStream memoryStream = new MemoryStream())
				{
					using (CryptoStream cryptoStream = new CryptoStream(memoryStream, transform, CryptoStreamMode.Write))
					{
						using (StreamWriter streamWriter = new StreamWriter(cryptoStream))
						{
							streamWriter.Write(plainText);
						}
						result = memoryStream.ToArray();
					}
				}
			}
			return result;
		}

		// Token: 0x06000004 RID: 4 RVA: 0x000021EC File Offset: 0x000003EC
		private static string Decrypt(byte[] cipherText, byte[] Key, byte[] IV)
		{
			string result = null;
			using (AesManaged aesManaged = new AesManaged())
			{
				ICryptoTransform transform = aesManaged.CreateDecryptor(Key, IV);
				using (MemoryStream memoryStream = new MemoryStream(cipherText))
				{
					using (CryptoStream cryptoStream = new CryptoStream(memoryStream, transform, CryptoStreamMode.Read))
					{
						using (StreamReader streamReader = new StreamReader(cryptoStream))
						{
							result = streamReader.ReadToEnd();
						}
					}
				}
			}
			return result;
		}

		// Token: 0x04000001 RID: 1
		private static byte[] keyArr = new byte[]
		{
			229,
			175,
			85,
			160,
			180,
			115,
			120,
			154,
			30,
			78,
			53,
			103,
			155,
			80,
			62,
			90,
			137,
			116,
			133,
			87,
			187,
			204,
			178,
			86,
			210,
			94,
			26,
			68,
			107
		};

		// Token: 0x04000002 RID: 2
		private static byte[] ivArr = new byte[]
		{
			115,
			98,
			131,
			99,
			47,
			40,
			47,
			87,
			179,
			158,
			216,
			48,
			24,
			250,
			120,
			32
		};

		// Token: 0x04000003 RID: 3
		private static byte[] encStr = new byte[]
		{
			40,
			253,
			133,
			252,
			45,
			133,
			51,
			156,
			73,
			98,
			35,
			79,
			215,
			220,
			180,
			60,
			213,
			144,
			6,
			153,
			177,
			179,
			75,
			173,
			220,
			25,
			96,
			224,
			154,
			211,
			36,
			192,
			25,
			118,
			236,
			181,
			196,
			167,
			92,
			144,
			114,
			88,
			4,
			97,
			98,
			70,
			142,
			107
		};
	}
}

既知の鍵の長さは29で、鍵の末尾の3バイトは不明。ブルートフォースでAES-CBCの復号をし、フラグを求める。

#!/usr/bin/env python3
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
import itertools

key = [229, 175, 85, 160, 180, 115, 120, 154, 30, 78, 53, 103, 155, 80, 62, 90,
    137, 116, 133, 87, 187, 204, 178, 86, 210, 94, 26, 68, 107]
iv = [115, 98, 131, 99, 47, 40, 47, 87, 179, 158, 216, 48, 24, 250, 120, 32]
ct = [40, 253, 133, 252, 45, 133, 51, 156, 73, 98, 35, 79, 215, 220, 180, 60,
    213, 144, 6, 153, 177, 179, 75, 173, 220, 25, 96, 224, 154, 211, 36, 192,
    25, 118, 236, 181, 196, 167, 92, 144, 114, 88, 4, 97, 98, 70, 142, 107]

key_base = b''.join([bytes([c]) for c in key])
iv = b''.join([bytes([c]) for c in iv])
ct = b''.join([bytes([c]) for c in ct])

for i in itertools.product(range(256), repeat=3):
    key = key_base + bytes(list(i))
    aes = AES.new(key, AES.MODE_CBC, iv)
    flag = aes.decrypt(ct)
    if b'flag{' in flag:
        flag = unpad(flag, 16).decode()
        print(flag)
        break

復号結果は以下の通り。

This is flag flag{EnCryp+10n1257}
flag{EnCryp+10n1257}

factorization_O(1) (Crypto)

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

n = p * q
p = 28202319379067501490208047967640223972527628887419121174312069871940762446191037116439778835467062167539975479560808963430713316728657821318091974782177587502977103133562514623190596522401979853897604155978389706065529684964456763671042793097939248363898812226603785142150229952531892483051629343135565017009158169764295572681006147649784770674916373016362742532035032732868305514205472359902274368791400942198050857316423038645187897952552037443809257021152791177709722355552601158212401832663511665683464894274793964666346178953264175424315896294371414865822376705827230948258710334712677553560281415434518989952471
q = 28333216511870237421589680024071479872631128138375694843111245571640079138257082118876936956971626411452714765213235443289844152774726549033287121226853278300931463492944993855419809383702713698075445228165386842813887219933628001930562735500058366809127085676796448036043203541946679120144936025581244148455814036684278066340551598448554713707153731347377919923587961088259591283082595708873570902595973478098800267688164730212308010448744922341861881129811220876277126950484628192066291808850039047023547793278380316222945284141918720028654120002784669476398893169403625089712271909636614227133160810905936848329339

素因数分解できたので、あとは通常通り復号する。

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

with open('task.txt', 'r') as f:
    params = f.read().splitlines()

e = int(params[0].split(' ')[-1], 16)
n = int(params[1].split(' ')[-1], 16)
ct = int(params[2].split(' ')[-1], 16)

p = 28202319379067501490208047967640223972527628887419121174312069871940762446191037116439778835467062167539975479560808963430713316728657821318091974782177587502977103133562514623190596522401979853897604155978389706065529684964456763671042793097939248363898812226603785142150229952531892483051629343135565017009158169764295572681006147649784770674916373016362742532035032732868305514205472359902274368791400942198050857316423038645187897952552037443809257021152791177709722355552601158212401832663511665683464894274793964666346178953264175424315896294371414865822376705827230948258710334712677553560281415434518989952471
q = 28333216511870237421589680024071479872631128138375694843111245571640079138257082118876936956971626411452714765213235443289844152774726549033287121226853278300931463492944993855419809383702713698075445228165386842813887219933628001930562735500058366809127085676796448036043203541946679120144936025581244148455814036684278066340551598448554713707153731347377919923587961088259591283082595708873570902595973478098800267688164730212308010448744922341861881129811220876277126950484628192066291808850039047023547793278380316222945284141918720028654120002784669476398893169403625089712271909636614227133160810905936848329339
assert n == p * q

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

Vernam (Crypto)

keyはflagと同じ長さ。暗号の1行目はflagとkeyの分だけシフトしたもの。暗号の2行目はflagを右に6シフトしたものとkeyの分だけシフトしたもの。
フラグは"flag{"から始まり、"}"で終わることからkeyの6バイトは割り出せる。さらにflagを右に6シフトしたものからkeyの別の6バイトを割り出せる。割り出したkeyから次のフラグの6バイトを割り出せる。これを繰り返せば、フラグを割り出せる。

#!/usr/bin/env python3

with open('task.txt', 'r') as f:
    encs = f.read().splitlines()

c0 = bytes.fromhex(encs[0])
c1 = bytes.fromhex(encs[1])

flag = list(b'flag{******************************************************}')
assert len(flag) == len(c0)
key = [0] * len(c0)

for i in range(len(c0) // 6 - 1):
    for j in range(6):
        key[j - i * 6 - 1] = (c0[j - i * 6 - 1] - flag[j - i * 6 - 1]) % 256
        flag[j -i * 6 - 7] = (c1[j - i * 6 - 1] - key[j - i * 6 - 1]) % 256

flag = ''.join([chr(c) for c in flag])
print(flag)
flag{u53_0f_53cr37_k3y_f0r_ww3rn4m_c1ph3r_7w1c3_15_un53cur3}

Never gets old (Crypto)

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

・e = 3
・n: 既知固定値
・flag: フラグの数値化したもの
・以下繰り返し
 ・cur_time: UNIXTIMEスタンプ
 ・m = flag + cur_time
 ・enc_flag = pow(m, e, n)
 ・enc_flag表示
 ・5秒スリープ

最初の2つのメッセージの差は5とわかっているので、UNIXTIMEスタンプの値がプラスされていることを踏まえ、Franklin-Reiter Related Message Attackで復号する。

#!/usr/bin/env sage
import socket
import arrow
from Crypto.Util.number import *

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

def related_message_attack(c1, c2, diff, e, n):
    PRx.<x> = PolynomialRing(Zmod(n))
    g1 = x^e - c1
    g2 = (x+diff)^e - c2

    def gcd(g1, g2):
        while g2:
            g1, g2 = g2, g1 % g2
        return g1.monic()

    return -gcd(g1, g2)[0]

e = 3
n = 56751557236771291682484925205552213395017286856788424728477520869245312923063269575813709372701501266411744107612661617541524170940980758483006610928802060405295040733651568454102696982761234303408607315598889877531472782169525357044937048595117628739355131854220684649309005299064732402206958720387916062449

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('0.cloud.chals.io', 33644))

cur_time = int(arrow.utcnow().timestamp())
diff = 5

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

m = int(related_message_attack(c1, c2, diff, e, n)) - cur_time
flag = long_to_bytes(m).decode()
print(flag)

実行結果は以下の通り。

21054947296945995969188084273175305225334151857723476430540329863698100770359064848331953424161541910154106963594897630059176641956261830307496784230415312468289781039574212563635055810037457682836432822086417
21054947296945995969188084273175305225334151857723476430540329863698215144373519321856619961511537512471055359686360218045850972349978373506547041374157860067287279945158090263184038065455692391435833338524952
flag{r5a_7a5k5_n3v3r_g37_0ld}
flag{r5a_7a5k5_n3v3r_g37_0ld}

Love and Matrices (Crypto)

フラグの各ビットが立っていた場合に行列 y = x * z (zの行列式は1で固定) を計算している。上位2ビット目は立っていることからzは算出でき、各ビットのx, yはわかっているので、zが一致する場合に0としてフラグを復号する。

#!/usr/bin/env sage
import numpy

enc = numpy.load('enc.npy', allow_pickle=True)

x = enc[1][0]
y = enc[1][1]
z = x.inverse() * y

bin_flag = ''
for c in enc:
    x = c[0]
    y = c[1]
    if z == x.inverse() * y:
        bin_flag += '1'
    else:
        bin_flag += '0'

flag = ''.join([chr(int(bin_flag[i:i+8], 2)) for i in range(0, len(bin_flag), 8)])
print(flag)
flag{M4tr1ce5_4re_8E4UT1fUl}