보안/SYSTEM HACKING

dreamhack stage 3 - Tool:gdb

rladmswl_1116 2024. 3. 15. 13:02

STAGE 3

Tool:gdb

1. 디버거

  • 버그(bug): 실수로 발생한 프로그램의 결함
  • 디버거(Debugger)

: 버그를 없애기 위해 사용하는 도구

만든 배경: 버그를 잡는 어려움을 해결하기 위해 개발된 도구

작동 과정: 프로그램을 어셈블리 코드 단위로 실행 + 실행 결과를 사용자에게 보여줌 -> 개발자는 디버거를 사용해 코드의 문제점을 찾을 수 있다.

그런데, 디버거로 인해 누구나 버그 탐색의 효율을 높일 수 있게 되었지만 버그 발견이 쉬워지면서 해커들은

취약점을 발견하기 쉬워졌다.


2. gdb & pwndbg

2-1. 정의와 설치, 예제

  • gdb (GNU debugger)

: 리눅스의 대표적인 디버거

- 다양한 플러그인들이 개발되었고 Ubuntu 18.04에는 기본적으로 설치되어 있다.

- pwndbg - 바이너리 분석 용도로 사용

  • gdb 설치

GitHub - pwndbg/pwndbg: Exploit Development and Reverse Engineering with GDB Made Easy 를 참고하여 설치한다.

실행 결과

gdb를 입력했을 때 다음과 같이 출력되면 된다.

  • gdb 실습 예제

Ubuntu C언어로 프로그래밍하기 ( 컴파일 ) (tistory.com)

[Linux] 리눅스환경(Ubuntu)에서 C언어 코딩하기 (tistory.com) 참고

코드 작성:

실행 결과


2-2. 리눅스의 ELF

리눅스는 실행파일의 형식으로 ELF ( Executable and Linkable Format) 를 규정하고 있다.

ELF 는 헤더와 여러 섹션으로 구성되어 있다.

- 헤더 -- 실행에 필요한 여러 정보

- 섹션 -- 컴파일된 기계어 코드, 프로그램 문자열 등의 여러 데이터

ELF의 헤더 중 진입점(Entry Point, EP) 가 있다.

운영체제는 ELF를 실행할 때 진입점의 값부터 프로그램을 실행한다.

readlf 로 확인해보면 진입점은 Entry point address 에 적힌 0x400400 이다.

  • START 명령어

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

- DISASM 영역의 화살표가 가리키는 주소는 현재 rip의 값, start 명령어를 실행하고 보면 0x400400 이다.

  • Context ( 맥락)

: pwndbg에서 주요 메모리들의 상태를 프로그램이 실행되고 있는 맥락이라고 한다.

가독성 있게 표현할 수 있는 인터페이스도 갖추고 있다.

- 배경: 디버거를 이용해 프로그램의 실행 과정을 자세히 관찰하려면 컴퓨터의 각종 메모리를 한눈에 파악하는

이 좋다.

- context의 영역

1. registers: 레지스터의 상태를 보여준다.

2. dissam: rip부터 여러 줄에 걸쳐 디스어셈블된 결과를 보여준다.

3. stack: rsp부터 여러 줄에 걸쳐 스택의 값을 보여준다.

4. backtrace: 현재 rip에 도달할 때까지 어떤 함수가 중첩되어 호출됐는지 보여준다.

--> 어셈블리를 실행할 때마다 갱신되어 실행한 어셈블리 명령어가 어떤 영향을 줬는지 파악하기 쉽다.

context

  • Break & Continue

- break: 특정 주소에 중단점(breakpoint)를 설정하는 기능)

- continue: 중단된 프로그램을 계속 실행시키는 기능

- 장점: break로 원하는 함수에 중단점 설정하고 프로그램 계속 실행하면 해당 함수까지 멈추지 않고 실행된 후

중단된다. 따라서 중단된 지점부터 다시 세밀하게 분석할 수 있다.

- 배경: 코드를 한 줄씩 실행시키며 main 함수에 도달해야 하면 디버깅은 효율적이지 못하다.

중단된 start 함수부터 main 함수까지 실행

  • run

: 단순히 실행만 시킨다.

중단점을 설정해놓지 않았다면 프로그램이 끝까지 멈추지 않고 실행된다.

  • gdb의 명령어 축약 ( 자주 사용되는 명령어들의 단축키)

- b: break

- c: continue

- r: run

- si: step into

- ni: next instruction

- i: info

- k: kill

- pd: pdisas

  • disassemble

: gdb가 기본적으로 제공하는 디스어셈블 명령어

 

- 배경 :gdb는 프로그램을 어셈블리 코드 단위로 실행하고 프로그램은 기계어로 이루어져있다.

 

--> gdb는 기계어를 디스어셈블(disassemble)하는 기능을 기본적으로 가지고 pwndbg에는 디스어셈블된

결과를 가독성 좋게 출력해주는 기능이 있다.

함수 이름을 인자로 전달하면 함수가 반환될 때까지 전부 디스어셈블하여 보여준다.

+ u, nearpc, pdisassemble: pwndbg에서 제공하는 디스어셈블 명령어

가독성 좋게 출력

  • navigate

관찰하고자 하는 함수의 중단점에 도달-> 그 지점부터 명령어를 한 줄씩 자세히 분석

- ni 와 si 사용

공통점: 모두 어셈블리 명령어를 한 줄 실행

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

si는 서브루틴의 내부로 들어간다.

확인해보자) main 함수에서 printf 함수 호출 지점까지 실행하자.

ni를 입력하면 printf 다음에 rip 가 이동했다는 것을 알 수 있다.

+ printf가 출력할 문자열은 stdout의 버퍼에서 대기한 후 출력한다.

+ 버퍼: 데이터가 목적지로 이동하기 전에 잠시 저장되는 저장소, 특정 조건 만족 시에만 데이터를 목적지로 이동

printf 실행했을 때 문자열이 출력되지 않는 이유는 아래의 조건을 하나도 만족하지 않을 때이다.

1. 프로그램이 종료될 때

2. 버퍼가 가득 찼을 때

3. fflush와 같은 함수로 버퍼를 비우도록 명시했을 때

4. 개행문자가 버퍼에 들어왔을 때

  • Step into

- si: 프로그램 분석 중 함수의 내부를 알고 싶을 때

- ni: 프로그램 분석 중 함수의 내부를 알지 않아도 될 때

  • finish

: 함수의 끝까지 한 번에 실행할 수 있다.

- 언제 사용?

step into로 함수 내부에서 다 분석했는데 함수 규모가 커서 ni를 사용해 원래 실행의 흐름으로 돌아가기

어려울 때 함수의 끝까지 한 번에 실행하기 위해서 사용한다.

  • examine

프로그램을 분석하다 가상 메모리에 존재하는 임의 주소의 값을 관찰해야할 때가 있다.

gdb에서는 명령어 'x' 제공 -> 특정 주소에서 원하는 길이만큼 데이터를 원하는 형식으로 인코딩하여

볼 수 있다.

예시)

1. rsp 부터 80바이트를 8바이트씩 hex 형식으로 출력

2. rip부터 5wnfdml djtpaqmffl audfuddj cnffur

3. 특정 주소의 문자열 출력

  • telescope

: pwndbg가 제공하는 강력한 메모리 덤프 기능

특정 주소의 메모리 값을 보여줌 + 메모리가 참조하고 있는 주소를 재귀적으로 탐색하여

값을 보여줌

  • vmmap

: 가상 메모리의 레이아웃 보여줌

파일이 매핑된 영역일 경우 해당 파일의 경로까지 보여준다.

+ 파일 매핑: 어떤 파일을 메모리에 적재하는 것

+ 리눅스에서 ELF 실행 시 ELF코드와 여러 데이터를 가상 메모리에 매핑하고 ELF에 링크된 공유 오브젝트를

추가로 메모리에 매핑한다.

+ 공유 오브젝트에 이미 구현된 함수를 호출할 때 매핑된 메모리에 존재하는 함수를 대신 호출한다.


2-3. gdb와 python의 사용

  • gdb와 python

- gdb를 통해 디버깅할 때 직접 입력할 수 없는 경우

: 파이썬으로 입력값 생성 후 사용

 

  • gdb와 python argv

run 명령어의 인자로 $()와 파이썬 코드를 입력하면 값을 전달할 수 있다.

printf 를 통해 출력한 값을 run 명령어의 인자로 전달하는 명령어

  • gdb와 python input

$()과 파이썬 코드를 입력하면 값을 입력할 수 있다. 입력값으로 전달하기 위해서 <<< 을 사용한다.

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


3. 결론

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

모든 메모리값 출력

  • vmmap: 메모리 레이아웃 출력