[DreamHack] System Hacking Stage3 - Tool: gdb 설치
Lecture
디버거란?
- 컴퓨터과학에서는 실수로 발생한 프로그램의 결함을 버그(bug)라고 한다.
- 개발자들은 이미 완성된 코드에서 버그를 찾는 디버거(Debugger)라는 도구를 개발했다.
gdb & pwndbg
gdb
- gdb(GNU debugger)는 리눅스의 대표적인 디버거이다.
설치 방법
1. 콘솔을 연다
2. git clone https://github.com/pwndbg/pwndbg 를 입력한다.
3. root 비밀번호를 입력한다.
4. 설치가 완료되면 콘솔에 gdb를 입력하여 설치에 성공했는지 확인한다.
- 실습 예제
1. 아래 코드를 작성하고 컴파일 한다.
// Name: debugee.c
// Compile: gcc -o debugee debugee.c -no-pie
#include <stdio.h>
int main(void) {
int sum = 0;
int val1 = 1;
int val2 = 2;
sum = val1 + val2;
printf("1 + 2 = %d\\n", sum);
return 0;
}
2. gdb debugee로 디버깅을 시작한다.
Start
- 리눅스는 실행파일의 형식으로 ELF(Executable and Linkable Format)을 규정하고 있다.
- ELF는 크게 헤더와 어려 섹션으로 구성되어있다.
- 헤더: 실행에 필요한 정보가 적혀있다.
- 섹션: 컴파일된 기계어 코드, 프로그램 문자열을 비롯한 여러 데이터가 적혀있다.
- ELF의 헤더 중에 진입점(Entry Point, EP)가 있는데, 운영체제는 ELF를 실행할 때 EP부터 실행한다.
- gdb의 start 명령어는 진입점부터 프로그램을 분석할 수 있게 해준다.
Context
- pwndbg는 주요 메모리들의 상태를 프로그램이 실행되고 있는 맥락(Context)라고 부른다.
- context는 크게 4개의 영역으로 나뉜다.
registers | 레지스터의 상태를 보여준다. |
disasm | rip부터 여러 줄에 걸쳐 디스어셈블된 결과를 보여준다. |
stack | rsp부터 여러 줄에 걸쳐 스택의 값들을 보여준다. |
backtrace | 현재 rip에 도달할 때 까지 어떤 함수들이 중첩되어 호출되었는지 보여준다. |
break & continue
- break: 특정 주소에 중단점(breakpoint)을 설정하는 기능이다.
- continue: 중단된 프로그램을 계속 실행시키는 기능이다.
- break로 원하는 함수에 중단점을 설정하고, 프로그램을 계속 실행하면 해당 함수까지 멈추지 않고 실행한 다음 중단된다.
run
- run은 단순히 실행만 시킨다.
- 중단점을 설정하지 않았다면, 프로그램이 끝까지 멈추지 않고 진행된다.
- 지금은 main함수에 중단점을 설정해놨기 때문에 run 명령어를 실행해도, main함수에서 실행이 멈춘다.
축약어 | |
b | break |
c | continue |
r | run |
si | step into |
ni | next instruction |
i | info |
k | kill |
pd | pdisas |
disassembly
- gdb는 기계어를 디스어셈블(Disassemble)하는 기능을 기본적으로 탑재하고 있다.
navigate
- 명령어를 한 줄씩 분석해야할 때 호출하는 명령어: ni, si
- 공통점: 어셈블리 명령어를 한 줄 실행한다는 공통점이 있다.
- 차이점: 서브루틴을 호출하는 경우 ni는 서브루틴의 내부로 들어가는 반면에, si는 들어가지 않는다.
- ni를 입력하면 printf다음으로 rip가 이동한것을 확인할 수 있다.
printf를 실행했는데, 왜 아무 문자열도 출력되지 않는가?
- printf가 출력하고자 하는 문자열은 stdout의 버퍼에서 잠시 대기한 뒤 출력되는데, 여기에서 stdout버퍼는 특정 조건(프로그램이 종료될 때, 버퍼가 가득 찼을 때, fflush와 같은 함수로 버퍼를 비우도록 명시했을 때, 개행문자가 버퍼에 들어왔을 때)이 만족되었을 때만 출력하기 때문에 프로그램 종료시까지 문자열을 출력하지 않는다.
step into
- si는 printf함수 내부로 이동한다.
- 아래의 사진을 보면, BackTrace의 main위에 printf가 쌓인것을 볼 수 있다.
finish
- si로 함수 내부에 들어가서 필요한 부분을 모두 분석했는데, 함수의 규모가 커서 ni로는 원해 실행 흐름으로 돌아가기 어려울 때 사용한다.
examine
- x: 특정 주소에서 원하는 길이만큼의 데이터를 원하는 형식으로 인코딩하여 볼 수 있다.
o | octal | c | char |
x | hex | s | string |
d | decimal | z | hex, zero padded on the left |
u | unsigned decimal | b | byte |
t | binary | h | halfword |
f | float | w | word |
a | address | g | giant, 8 bytes |
i | instruction |
- 예시1) rsp부터 80바이트를 8바이트씩 hex형식으로 출력
- 예시2) rip부터 5줄의 어셈블리 명령어 출력
- 예시3) 특정 주소의 문자열 출력
telescope
- pwndbg가 제공하는 강력한 메모리 덤프 기능이다.
- 특정 주소의 메모리 값들을 보여준다.
- 메모리가 참조하고 있는 주소를 재귀적으로 탐색하여 값을 보여준다.
vmmap
- vmmap은 가상 메모리의 레이아웃을 보여준다.
- 어떤 파일이 매핑된 영역일 경우, 해당 파일의 경로까지 보여준다.
파일 매핑?
- 어떤 파일을 메모리에 적재하는 것이다.
gdb / python
- run명령어의 인자로 $()와 함께 파이썬 코드를 입력하면 값을 전달할 수 있다.
- 위와 같이 $()와 함께 파이썬 코드를 입력하면 값을 입력할 수 있다.
- 입력값으로 전달하기 위해서는 <<< 문자를 사용한다.
Note
명령어 | 설명 | 명령어 | 설명 |
start | 진입점에 중단점을 설정하고 실행한다. | run(r) | 프로그램을 처음부터 실행한다. |
break(b) | 중단점을 설정한다. | context | 레지스터, 코드, 스택, 백트레이스의 상태를 출력한다. |
continue(c) | 계속 실행한다. | nexti(ni) | 명령어를 실행한다. 함수 내부로는 들어가지 않는다. |
disassemble | 디스어셈블 결과를 출력한다. | stepi(si) | 명령어를 실행한다. 함수 내부로도 들어간다. |
u, nearpc, pd | 디스어셈블 결과를 가독성 좋게 출력한다. | telescope(tele) | 메모리를 조회한다. 메모리 값이 표인터일 경우 재귀적으로 따라가며 모든 메모리값을 출력한다. |
x | 메모리를 조회한다. | vmmap | 메모리의 레이아웃을 출력한다. |