ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • dreamhack stage 3 - Tool:gdb
    보안/SYSTEM HACKING 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: 메모리 레이아웃 출력
Designed by Tistory.