디버거는 프로그램을 실행시키면서 분석하는 도구이다. 실행중에 특정 지점에서의 프로세스 상태 진단 및 버그 찾기를 도와준다
sudo apt-get install gdb 으로 gdb를 설치할 수 있다.
gef, peda, pwngdb, pwndbg 등 바이너리 분석을 위한 여러 플러그인들이 존재한다.
여기서는 pwndbg를 사용한다. 설치 과정은 아래와 같다.
git clone https://github.com/pwndbg/pwndbg
cd pwndbg
./setup.sh
위와 같은 텍스트가 나온다면 성공적으로 설치된 것이다.
파일 불러오기
gdb로 디버깅 터미널을 생성한 후 가장 먼저 할 일은 디버깅할 바이너리를 GDB에 불러오는 것이다.
이는 file 명령어로 부를 수 있다. 처음 gdb를 실행할 때 gdb ./debugee 처럼 인자에 디버깅할 바이너리 경로를 넣어도 된다.
(No debugging symbols found in /home/kwonhan/DH_sys/debugee) 는 바이너리 안에 디버깅 정보가 포함되지 않았음을 알림
실행중인 프로세스를 디버깅하고 싶을 경우 PID로 디버깅 가능. gdb -p <PID>
실행 흐름 제어
run <프로그램 인자> 프로그램 인자 필요없을 경우 run 명령어만. 단축 명령어 rbreak 설정 후 run 했을 때 출력 화면 중 일부
break 특정 주소에 중단점(Breakpoint) 설정. 프로그램 실행하면 해당 함수에서 멈춤. 단축 명령어 b 함수명과 주소로 중단점 설정 가능. 주소 이용 시 주소 앞 * 붙여야 함
continue <숫자> 중단된 프로그램 계속 실행. 단축 명령어 c 현재 프로그램이 중단되어있는 위치의 중단점을 여러번 건너뛸 수 있게 해줌(반복문 안에 중단점...) 이후 중단점 없다면 끝까지 실행됨
리눅스는 실행파일 형식으로 ELF(Executable and Linkable Format)를 규정하고있다. ELF는 헤더와 여러 섹션으로 구성된다. 헤더에 진입점(EP, Entry Point)가 있는데, 운영체제가 ELF 실행 시 진입점 명령어부터 프로그램을 실행한다. readelf -h <파일이름> 으로 확인할 수 있다
entry 진입점 부터 프로그램 분석. 화살표▶가 가리키는 주소는 rip 레지스터 값으로, 현재 실행중인 명령어 주소.
start main() 부터 프로그램 분석. main()을 찾지 못하면 entry 처럼 동작
실행 흐름 추적
중단점에 도달했으니 명령어를 한 줄 씩 자세히 분석
ni (next instruction) call 어셈블리 명령어 등을 통해 함수 호출하면 함수 내부로 안들어감
si (step into) call 어셈블리 명령어 등을 통해 함수 호출하면 함수 내부로 들어감
finish si로 함수에 들어갔다가 ni로 원래 실행 흐름으로 돌아가기 어려울 때 사용. 함수 끝까지 한 번에 실행해줌
원래 실행 흐름으로 돌아온 것을 확인할 수 있다.
자주 사용하는 명령어들
info 정보를 보여줌
info registers 프로그램이 실행 중일 때 사용. 단축 명령어 i r 레지스터에 저장된 값을 출력. $<레지스터명>으로 값 바로 사용 가능 주소값을 다룰때는 앞에 * 필요
info breakpoints 설정한 중단점 확인. 단축 명령어 i b
중단점 비활성화는 disable <Num>, 재활성화는 enable <Num>, 삭제는 del <Num> 혹은 d <Num>
disassemble <함수 이름> 단축 명령어 disass. 해당 함수가 반환될 때 까지 전부 디스어셈블해서 보여줌
u, nearpc, pdisass는 디스어셈블된 코드를 가독성 좋게 출력해준다. (결과는 다 같음)
x/<개수><포맷 및 크기> (examine) 특정 주소에서 원하는 길이만큼 원하는 형식으로 인코딩해서 확인 가능 하나만 본다면 <개수> 필드 빼면 된다. 가상 메모리는 프로그램 실행 중에만 할당되기에 실행중에만 사용 가능
vmmap 가상 메모리 레이아웃 보여줌. 파일이 매핑(파일을 메모리에 적재)된 영역이라면 파일 경로까지 출력 프로그램 실행 중에만 사용 가능
backtrace, bt 콜 스택 확인
콜 스택(Call Stack)은 프로그램이 실행되는 동안 함수가 호출되는 순서를 저장하는 구조. 함수 실행이 끝나면 가장 최근에 호출된 함수부터 순차적으로 제거 어떤 함수에 전달된 인자에 문제가 있어 버그가 발생했다면, 해당 인자가 어떤 함수로부터 왔는지 거슬러 올라가면서 찾아야함
b add, r 이후 bt 결과
dump memory <파일명> <시작주소> <끝주소> 프로세스 메모리 상태 파일로 저장
entry로 프로그램 실행 > vmmap으로 코드영역 시작, 끝 주소 확인 > 덤프 뜨기 *코드 영역은 r-x 권한. r-x 권한 가진 영역이 바이너리의 코드영역임
context, ctx 주요 메모리들의 상태를 프로그램이 실행되고 있는 맥락(Context)로 칭함
REGISTERS : 레지스터 상태
DISASM : rip(다음에 실행할거) 부터 디스어셈블 된 결과
STACK : rsp(스택 포인터, 가장 최신) 부터 스택의 값들
BACKTRACRE : 현재 rip에 도달할 때까지 어떤 함수들이 중첩되어 호출되었는지
set <주소/레지스터> = <변경할 값> 프로세스의 메모리 상태를 변경 레지스터나 특정 주소 메모리 값 변경 위해 사용. 프로그램이 실행 중인 상태에서만 동작