🐈

【pwn】Docker内で動いているプロセスにgdbserverでデバッグする

に公開

本記事はこちらの記事を参考に書きました。ぜひ元の記事も見に行ってください。
https://hackmd.io/@blackpwner/S11lMQGwye

また本記事の内容は実際に以下の配信で検証しています。興味があればぜひ御覧ください。
https://youtube.com/live/B507VI-_02Y?feature=share

本記事で取り扱う問題

SECCON Beginners CTF 2025 の pivot4b++ で行います。
2025/08/01現在、まだ公式リポジトリはないので、公開され次第記載します。
gdbserverで接続してもgdbの設定はローカル側を参照するので、サーバーサイドにpwndbg等を入れなくても大丈夫です。

Dockerfile、docker-compose.yamlの編集

Dockerfiledocker-compose.yaml を以下のように編集します。

  1. FROM pwn.red/jail 以降をコメントアウトする
  2. gdb, gdbserver, socatをインストールする
  3. CMD でsocatを実行する。
  4. gdbserver用にport forwardingする
Dockerfile
FROM ubuntu:22.04@sha256:08e2cd26ee66d0d46d6394df594f2877fc9b9381d9630a9ef5d86e27dfae9a95 AS base
WORKDIR /app
COPY chall run
RUN apt update && apt install -y gdb gdbserver socat # step2: install gdb, gdbserver, socat
RUN echo "ctf4b{*** REDACTED ***}" > /flag.txt
RUN  mv /flag.txt /app/flag-$(md5sum /flag.txt | awk '{print $1}').txt
CMD socat TCP-LISTEN:5000,reuseaddr,fork EXEC:"./run" # step3: run socat

# FROM pwn.red/jail # step1: commentout
# COPY --from=base / /srv # step1: commentout
# RUN chmod +x /srv/app/run  # step1: commentout
# ENV JAIL_TIME=60 JAIL_CPU=100 JAIL_MEM=10M  # step1: commentout
docker-compose.yml
services:
  pivot4b:
    build: .
    ports:
      - 12300:5000
      - "9090:9090" # step4: port for gdbserver
    privileged: true
    restart: unless-stopped

solve.pyの編集

  1. ファイル名とコンテナ名を相当のものにする
  2. remote() でdocker内のバイナリをsocatで走らせ、 docker exec コマンドで、docker内の gdbserver を起動する。
    • read系命令で止まったところからデバッグが始まります
    • 起動するためだけにprocessを叩いています。コマンドが実行できれば何でもいいです
  3. gdb.attachで接続 (wsl環境の場合、tmuxがないと画面が出てくれません。)
  4. attachは非同期で行うため、タイムラグがあります。 pause()time.sleep() などで待機し、同期を待ちます。
solve.py
from pwn import *
from icecream import ic
import time

remote_connection = "nc addr 12300".split()
CONTAINER_NAME = "pivot4b-2-pivot4b-1" # step1: conatiner_name
FILE_NAME = "./chall" # step1: filename
local_port = 12300

exe = ELF(f"{FILE_NAME}")
rop = ROP(exe)

context.binary = f"{FILE_NAME}"

localscript = f'''
file {context.binary.path}

define rerun
!docker exec -u root -i {CONTAINER_NAME} bash -c 'kill -9 $(pidof gdbserver) &'
!docker exec -u root -i {CONTAINER_NAME} bash -c 'gdbserver :9090 --attach $(pidof run) &'
end

define con
target remote :9090
end
'''

gdbscript = '''
b *vuln+65
'''

def start():
    if args.REMOTE:
        return remote(remote_connection[1], int(remote_connection[2]))
    elif args.LOCAL:
        return remote("localhost", local_port)
    elif args.GDB:
        return gdb.debug(exe.path, gdbscript=gdbscript)
    else:
        return process([exe.path])

def GDB():
    if not args.LOCAL and not args.REMOTE:
        gdb.attach(p, gdbscript=gdbscript)
        pause()
    if args.LOCAL:
        gdbserver = process(f"docker exec -u root -i {CONTAINER_NAME} bash -c".split() +  [f"gdbserver :9090 --attach $(pidof run) &"]) # step2: gdb server の起動
        pid = gdb.attach(('0.0.0.0', 9090), exe=f'{context.binary.path}',  #gdbscript=localscript+gdbscript) # step3: gdb serverへのアタッチ
        pause() # step4: pause等で待機
        # time.sleep(0.5)

# r = remote("pivot4b-2.challenges.beginners.seccon.jp", 12300)
r = start()
GDB()

Discussion