0. 📍 개요

기존의 docker 내부 process에 대해, WSL root 계정으로 gdb-pwndbg -p PID 로 attach하는 방식이 더 이상 유효하지 않아 새로운 방법을 찾아보았다.

근데 왜 안 되는거지?

기존에 내가 썼던 방법은 🧷 [로되리안의 해결방법 1] https://0nehundred4ndt3n.tistory.com/7 이었다.


1. Docker, gdbserver - CykorCTF2025-pwn-2-dbfs

  1. dockerfile setting + run, exec
    보통 로컬에서 p.remote가 가능하게 하기 위해, 제공되는 dockerfile은 EXPOSE nnnn 포트를 하나 열어둔다. 여기서 PORT 하나 더 추가 + apt intsll -y gdbserver을 하도록 docker-compose-debug.yaml을 커스텀해준다.
     services:
     dbfs-debug:
       image: ubuntu:24.04@sha256:4fdf0125919d24aec972544669dcd7d6a26a8ad7e6561c73d5549bd6db258ac2
       ports:
         - "22222:22222"
         - "1234:1234"
       volumes:
         - ./client:/app/client
         - ./flag:/flag
       privileged: true
       stdin_open: true
       tty: true
       command: bash -c "
         apt update && 
         apt install -y gdbserver &&
         chmod +x /app/client &&
         mkdir -p /tmp/dbfs-test &&
         gdbserver :1234 /app/client /tmp/dbfs-test
    

    완료되었다면 아래를 실행한다.

      docker-compose -f docker-compose-debug.yaml up
    

    .yaml이 아닌 Dockerfile인 경우 아래를 참고한다.

    Dockerfile 기준
         docker build -f Dockerfile.debug -t cydf_debug .
         docker run -it --rm -p 7183:7183 -p 1234:1234 --cap-add=SYS_PTRACE //opt1
         docker run -d -p 7183:7183 --cap-add=SYS_PTRACE --name cydf cydf_debug sleep infinity //opt2
    

    이렇게 하면 터미널이 열리지 않은 것 처럼 보여도, socat이 이미 듣고 있으므로 pwntools를 통해 p.remote(localhost, 7183)을 해주면 바이너리가 열린다.
    추가로 docker 터미널을 열어주자.

         docker exec -d cydf bash -c 'socat -T 300 TCP-LISTEN:7183,reuseaddr,fork EXEC:./CYDF_Average'
    

    이렇게 docker 터미널을 열었다면 socat으로 listening 상태를 만들어주어야한다. 열어두기로 정한 포트로 통신이 들어오면 바이너리를 실행하는식.

      socat TCP-LISTEN:22222,reuseaddr,fork EXEC:'/app/client /tmp/dbfs-test' &
    
  2. localhost에 pwntools로 remote 걸기
     from pwn import *
    
     p = remote('localhost', 22222)
     pause()  # 여기서 멈춤 - gdb attach 할 시간
     p.sendlineafter(b'dbfs> ', b'SET AAA set')
     # ...
    

    익스 코드 최상단에 위와 같이 작성한다. 이 때 유의하여야할 점은, 1-1 단계에서 docker를 만들면서 EXPOSE한 두 개 포트 가운데 하나에 해당하는 포트번호를 적어주어야 한단 것이다.
    그리고 pause()를 필수로 걸어줘야 한다. python 스크립트 실행 이후에 디버거를 붙이기 때문에, pause없이 이후 익스플로잇을 실행하게 되면 디버거에서 적절한 관찰이 어려울 수 있다.

  3. docker에서 gdbserver 열고 local에서 gdb-pwndbg로 target remote
     $docker> gdbserver :1234 --attach $(pgrep -n client)
    

    위 스크립트는 socat으로 파이너리가 실행중인 상태가 아니면 작동하지 않는다.

     $local> gdb-pwndbg ./client
     $pwndbg> target remote localhost:1234
     $pwndbg> b *handle_info + 0x20d
     $pwndbg> c
     #...
    
  4. (opt) tmp 디렉터리 초기화
    socat이 예상 외의 오류를 낸다면 아래와 같이 docker 내에서 socat을 다시 시작할 수 있다.
     $docker> pkill socat
     $docker> rm -rf /tmp/dbfs-test
     $docker> mkdir -p /tmp/dbfs-test
     $docker> socat TCP-LISTEN:22222,reuseaddr,fork EXEC:'/app/client /tmp/dbfs-test' &
    

2. qemu-user-static + ARM + pwndbg/gef - 내부CTF-pwn-forty-seven-bells

고마워...
고마워...

동기가 만든 문제인데, 1번의 방법으론 디버깅이 되지 않기 때문에 들고왔다.

출처로 출제자의 깃헙을 첨부한다.

  1. qemu-user-static 설치, gdbinit.py 찾기
     sudo apt install qemu-user-static
     # pwndbg 경로 찾기 
     find ~ -name "gdbinit.py" 2>/dev/null | grep pwndbg
     # gef 경로 찾기
     find ~ -name "gef.py" 2>/dev/null
    

    pwndbg, gef 모두 가능하기 때문에 비교적 편한 것으로 진행하면 된다. pwndbg의 경우 …/pwndbg/gdbinit.py로 출력되는 경로를 복사해둔다.

  2. exploit code 실행 with qemu server
     from pwn import *
    
     context.arch = 'arm'
     context.bits = 32
    
     # 로컬 디버깅용
     p = process(["qemu-arm-static", "-g", "1234", "-L", "/usr/arm-linux-gnueabihf", "./prob"])
     # pause() 대신 gdb가 붙을 시간
     sleep(1)
    
     # 터미널 2에서 gdb-multiarch ... target remote :1234 실행
     pause()  # gdb 붙인 후 엔터
    
     # 익스 진행...
    

    위 코드를 실행한다. 바이너리, 사용할 포트 번호에 따라 대동소이할 수 있다.

  3. gdb-multiarch using pwndbg를 프로세스에 attach하기
     gdb-multiarch -q \
     -ex "source /home/jshrb/pwndbg/gdbinit.py" \
     -ex "set architecture arm" \
     -ex "target remote localhost:1234" \
     -ex "file ./prob"
    
    ARM attach 성공한 모습
    ARM attach 성공한 모습