본문 바로가기
문제 풀이/[DreamHack]

[DreamHack] System Hacking Stage3 - Tool: gdb 설치

by 조랩 2022. 12. 21.

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부터 실행한다.

readelf -h debugee로 확인한 결과, 시작점이 0x400400으로 되어있는것을 확인할 수 있다.

- gdb의 start 명령어는 진입점부터 프로그램을 분석할 수 있게 해준다.

start를 입력한 모습니다. DISASM영역의 화살표가 현재 rip의 주소값인데, 0x400400을 가리키고 있는것을 확인할 수 있다.

Context

- pwndbg는 주요 메모리들의 상태를 프로그램이 실행되고 있는 맥락(Context)라고 부른다.

- context는 크게 4개의 영역으로 나뉜다.

   
registers 레지스터의 상태를 보여준다.
disasm rip부터 여러 줄에 걸쳐 디스어셈블된 결과를 보여준다.
stack rsp부터 여러 줄에 걸쳐 스택의 값들을 보여준다.
backtrace 현재 rip에 도달할 때 까지 어떤 함수들이 중첩되어 호출되었는지 보여준다.

4가지 context 화면

break & continue

- break: 특정 주소에 중단점(breakpoint)을 설정하는 기능이다.

- continue: 중단된 프로그램을 계속 실행시키는 기능이다.
- break로 원하는 함수에 중단점을 설정하고, 프로그램을 계속 실행하면 해당 함수까지 멈추지 않고 실행한 다음 중단된다.

 

break와 continue를 입력한 모습이다.

run

- run은 단순히 실행만 시킨다.

- 중단점을 설정하지 않았다면, 프로그램이 끝까지 멈추지 않고 진행된다.

- 지금은 main함수에 중단점을 설정해놨기 때문에 run 명령어를 실행해도, main함수에서 실행이 멈춘다.

 

main에서 멈춘 모습이다. 맨 위를 보면 Breakpoint 2, ~~ 가 보인다.

축약어  
b break
c continue
r run
si step into
ni next instruction
i info
k kill
pd pdisas

disassembly

- gdb는 기계어를 디스어셈블(Disassemble)하는 기능을 기본적으로 탑재하고 있다.

main을 디스어셈블 한 모습이다. 기본 포멧 보다는 u나 nearpc가 더 가독성이 좋은 모습이다.

navigate

- 명령어를 한 줄씩 분석해야할 때 호출하는 명령어: ni, si

- 공통점: 어셈블리 명령어를 한 줄 실행한다는 공통점이 있다.

- 차이점: 서브루틴을 호출하는 경우 ni는 서브루틴의 내부로 들어가는 반면에, si는 들어가지 않는다.

main함수의 printf문이 있는 곳 까지 실행한 모습니다.

- ni를 입력하면 printf다음으로 rip가 이동한것을 확인할 수 있다.

ni를 입력한 모습이다.

printf를 실행했는데, 왜 아무 문자열도 출력되지 않는가?

- printf가 출력하고자 하는 문자열은 stdout의 버퍼에서 잠시 대기한 뒤 출력되는데, 여기에서 stdout버퍼는 특정 조건(프로그램이 종료될 때, 버퍼가 가득 찼을 때, fflush와 같은 함수로 버퍼를 비우도록 명시했을 때, 개행문자가 버퍼에 들어왔을 때)이 만족되었을 때만 출력하기 때문에 프로그램 종료시까지 문자열을 출력하지 않는다.

step into

- si는 printf함수 내부로 이동한다.

- 아래의 사진을 보면, BackTrace의 main위에 printf가 쌓인것을 볼 수 있다.

Backtracd의 main함수 위에 printf가 쌓인 모습이다.

finish

- si로 함수 내부에 들어가서 필요한 부분을 모두 분석했는데, 함수의 규모가 커서 ni로는 원해 실행 흐름으로 돌아가기 어려울 때 사용한다.

finish를 입력한 모습이다.

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명령어의 인자로 $()와 함께 파이썬 코드를 입력하면 값을 전달할 수 있다.

 

- 위와 같이 $()와 함께 파이썬 코드를 입력하면 값을 입력할 수 있다.

- 입력값으로 전달하기 위해서는 <<< 문자를 사용한다.

 

argv[1]에 임의의 값을 전달하고, 값을 입력하는 명령어 이다.


Note

 

명령어 설명 명령어 설명
start 진입점에 중단점을 설정하고 실행한다. run(r) 프로그램을 처음부터 실행한다.
break(b) 중단점을 설정한다. context 레지스터, 코드, 스택, 백트레이스의 상태를 출력한다.
continue(c) 계속 실행한다. nexti(ni) 명령어를 실행한다. 함수 내부로는 들어가지 않는다.
disassemble 디스어셈블 결과를 출력한다. stepi(si) 명령어를 실행한다. 함수 내부로도 들어간다.
u, nearpc, pd 디스어셈블 결과를 가독성 좋게 출력한다. telescope(tele) 메모리를 조회한다. 메모리 값이 표인터일 경우 재귀적으로 따라가며 모든 메모리값을 출력한다.
x 메모리를 조회한다. vmmap 메모리의 레이아웃을 출력한다.

 

728x90