본문 바로가기

개념정리/System Hacking

5. gdb 디버거

 

이번 시간은 리눅스의 대표적인 디버거 gdb에 대해서 공부 할 것이다.

pwndbg 설치 및 기능을 공부한다.

 


1. 디버거

디버거는 버그를 없애기 위해 사용하는 도구이다. 

프로그램을 개발할 때는 일반적으로 코드의 논리 구조를 신중하게 설계하고 코드를 작성한다. 하지만 아무리 신중하게 설계해도 규모가 커지면 실수가 발생한다. 컴퓨터과학에서는 이러한 실수로 발생한 프로그램의 결함을 버그라고 한다. 또한 이미 완성된 코드에서 버그를 찾는 것은 어렵다. 그래서 이런 어려움을 해소하고자 디버거라는 도구가 개발되었다. 

그러나 디버거의 효용을 개발자만 얻는 것이 아니라 해커, 리버스 엔지니어 등도 디버거를 사용하여 버그 탐색의 효율을 높인다. 개발자는 버그를 고치기 쉬워졌고, 해커는 취약점을 발견하기 쉬워졌다. 

리눅스의 대표적인 디버거 gdb에 대하여 알아볼 것이다.

2. gdb 기능 및 명령어

리눅스는 실행파일의 형식으로 ELF를 규정하고 있다. ELF는 크게 헤더와 여러 섹션들로 구성된다. 헤더에는 실행에 필요한 여러 정보가 적혀 있고, 섹션들에는 컴파일된 기계어 코드, 프로그램 문자열을 비롯한 여러 데이터가 포함되어 있다. 

 

1) readelf : readelf -h [filename]

ELF의 헤더 중에 진입점(Entry Point)이라는 필드가 있는데 운영체제는 ELF를 실행할 때 진입점의 값부터 프로그램을 실행한다. readelf 명령어를 이용하여 확인할 수 있다.

 

2) context

프로그램은 실행되면서 레지스터를 비롯한 여러 메모리에 접근한다. 따라서 디버거를 이용하여 프로그램의 실행 과정을 자세히 관찰하려면 컴퓨터의 각종 메모리를 파악하는 것이 좋다. 주요 메모리들의 상태를 context라고 부르며 가독성있게 표현할 수 있는 인터페이스를 갖추고 있다.

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

 

3) start

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

 

4) break &continue

gdb를 이용하여 프로그램을 분석할 때, 일반적으로 전체 프로그램 중 아주 일부분의 동작에만 관심이 있다. 진입점부터 원하는 동작하는 부분까지 한 줄씩 실행시켜가며 도달하는 것은 효율적인 방법이 아니다. 그래서 디버거에는 break와 continue라는 기능이 있다.

  • break : 특정 주소에 중단점을 설정하는 기능 (b *main, b *main+10)
  • continue : 중단된 프로그램을 계속 실행시키는 기능( c )

break로 원하는 함수에 중단점을 설정하고 프로그램을 계속 실행하면 해당 함수까지 멈추지 않고 실행한 다음 중단된다. 그러면 중단된 지점부터 다시 분석할 수 있다.

 

5) run

run은 단순히 실행만 시킨다. 중단점을 설정하지 않았다면 프로그램이 끝까지 멈추지 않고 실행된다. 중단점을 설정해놨으면 중단점에서 멈춘다.

 

6) disassembly

gdb는 프로그램을 어셈블리 코드 단위로 실행하고 결과를 보여준다. 프로그램 코드는 기계어로 이루어져 있으므로 gdb는 기계어를 디스어셈블하는 기능을 기본적으로 탑재하고 있다. 

  • disassemble : gdb가 기본적으로 제공하는 디스어셈블 명령어, 함수 이름을 인자로 전달하면 해당 함수가 반환될 때까지 전부 디스어셈블하여 보여준다.
  • u, nearpc, pdisassemble : pwndbg에서 제공하는 디스어셈블 명령어이며, 디스어셈블된 코드를 가독성 좋게 출력한다.

 

7) navigate

분석하고자 하는 함수의 중단점에 도달하면 그 지점부터 명령어를 한 줄씩 자세히 분석해야한다. 이때 사용하는 명령어는 ni(next instruction)와 si(step into)가 있다.

모두 어셈블리 명령어를 한 줄 실행한다는 공통점이 있다. 그러나 call 등을 통해 서브루틴을 호출하는 경우 ni는 서브루틴의 내부로 들어가지 않지만, si는 서브루틴의 내부로 들어간다는 차이점이 있다.

  • ni : 호출할 함수 내부로 들어 가지 않고 다음으로 rip가 이동한다. 
  • si : 호출할 함수 내부로 rip가 이동한다. 

즉, 프로그램을 분석하다가 어떤 함수를 만나면 그 함수가 궁금하면 si, 궁금하지 않으면 ni를 사용한다. 

 

8) finish

si(step into)로 함수 내부에 들어가서 필요한 부분을 모두 분석했는데 함수 규모가 커서 ni로는 원래 실행 흐름으로 돌아갈 수 없을 때 사용한다. finish 명령어를 사용하면 함수의 끝까지 한 번에 실행하여 함수를 빠져나올 수 있다.

 

9) examine

프로그램을 분석하다 보면 가상 메모리에 존재하는 임의 주소의 값을 관찰해야 할 때가 있다. 이를 위해 gdb에서는 기본적으로 x라는 명령어를 제공한다. x를 이용하면 특정 주소에서 원하는 길이만큼의 데이터를 원하는 형식으로 인코딩하여 볼 수 있다.


Format letters are

o(octal), x(hex), d(decimal), u(unsigned decimal), t(binary), f(float),

a(address), i(instruction), c(char), s(string) and z(hex, zero padded on the left).

Size letters are

b(byte), h(halfword), w(word), g(giant, 8 bytes).


ex)

  • rsp부터 80바이트를 8바이트씩 hex형식으로 출력 : x/10gx $rsp
  • rip부터 5줄의 어셈블리 명령어 출력 : x/5i $rip
  • 특정 주소의 문자열 출력 : x/s 0x40000

 

10) telescope

telescope은 pwndbg가 제공하는 강력한 메모리 덤프 기능이다. 특정 주소의 메모리 값들을 보여주는 것에서 그치지 않고 메모리가 참조하고 있는 주소를 재귀적으로 탐색하여 값을 보여준다.(명령어 : tele)

 

11) vmmap

vmmap은 가상 메모리의 레이아웃을 보여준다. 어떤 파일이 매핑된 영역일 경우 해당 파일의 경로까지 보여준다.

 

cf) gdb 명령어 축약 및 정리

  • break : b
  • continue : c
  • run : r
  • step into : si
  • next instruction : ni
  • info : i
  • kill : k
  • pdisas : pd
  • x : 메모리 조회
  • telescope : tele
  • vmmap : 메모리 레이아웃 출력

 


 

이번 시간에는 디버거 gdb에 대하여 공부하였다.

다음시간은 pwntools에 대하여 공부할 것이다.