[Pwnable] GDB 기초 사용법
GDB(GNU debugger)는 리눅스에서 사용하는 대표적인 디버거 중 하나로 GNU에서 소프트웨어 시스템을 위한 표준 디버거이다.

gcc로 텍스트 파일 컴파일 하기.
gcc -o debugee debugee.c -no-pie에서
메모장에 해당 코드를 적고 debugee.c로 저장한 뒤 리눅스에 해당 명령어를 입력하면, debugee.c라는 c 소스파일을 gcc로 컴파일해서, 보안 기능인 PIE를 끈 고정 주소 실행파일 debugee를 만들어 저장한다.
(고정 주소 실행 파일이 디버깅/리버싱할 때 주소 고정으로 편리함)
디버깅을 수행하기 위해서 먼저 디버깅 할 바이너리를 GDB에 불러와야 한다.

GDB에서 file 명령어는 바이너리를 GDB에 불러 와 실행하고 디버깅 할 수 있게 만든다.
처음 터미널에서 gdb를 실행할 때 gdb ./debugee와 같이 인자에 디버깅 할 바이너리 경로를 넣어도 똑같이 기능한다.

run <프로그램 인자> : 프로그램을 실행하는 명령어이다.
프로그램 인자가 필요 없는 경우 run 명령어만 입력해도 실행된다.

break : 특정 주소에 중단점을 설정하는 기능이다.
continue : 중단된 프로그램을 계속 실행시키는 기능이다.
break로 원하는 함수에 중단점을 설정하고, 프로그램을 계속 실행하면 해당 함수까지 멈추지 않고 실행한 다음 중단된다. 그 뒤 중단된 지점부터 세밀하게 분석한다.
break대신 b 명령어로 동일한 작업을 수행할 수 있으며, 함수명 뿐만 아니라 특정 주소로도 중단점을 설정할 수 있다.

b main으로 중단점에 bp를 건 뒤 run으로 프로그램을 실행하면, 중단점이 걸린 지점에서 실행이 중단되면서 다음과 같은 화면이 출력된다.
프로그램이 중단점에 도달했을 때 프로그램에 대한 상세한 정보들을 출력해주는데 실행되고 있는 어셈블리 명령어와 그 주변의 명령어들을 보여준다. 여러가지 디버거의 기능들을 통해 프로세스의 정보를 얻거나 변조도 가능하다.
중단점을 통해 프로그램을 중단한 뒤, continue(c)를 통해 다시 실행을 이어나갈 수 있다. (중단점을 여러개 건너 뛰고 싶을때는 continue <숫자> or c <숫자>를 이용해 여러번 건너뛸 수 있다)

리눅스는 실행파일 형식으로 ELF (Executable and Linkable Format) 형식을 따른다. 엘프의 헤더와 섹션은 리눅스에서 readelf 라는 명령어로 확인 가능하다.

(readelf -h 명령어는 ELF file header만 읽어들임)
이 중에서 Entry Point(EP, 진입점)이 있는데, 운영체제에서 ELF를 실행할 때 EP 명령어부터 시작한다.
debugee파일의 EP는 0x401050인데 GDB의 entry 명령어를 사용하면 EP부터 프로그램을 분석할 수 있다.
entry : EP로 rip를 설정하고 분석을 시작할 수 있게 해주는 명령어

=> entry 명령어를 실행했을 때 EP 지점인 0x401050부터 프로그램이 진입함
start : entry와 비슷하지만 main()부터 분석할 수 있게 시작점을 설정함. main()을 찾을 수 없는 경우 entry와 동일하게 동작함
b main + r 과의 차이점

관찰하고자 하는 함수의 중단점에 도달한 뒤, 명령어를 분석할 때 사용하는 명령어들이다
ni : next instruction을 줄인것으로 함수의 내부에 들어가지 않고 한 줄을 실행
si : step info을 줄인것으로 함수의 내부에 들어가서 한 줄을 실행
finish : 함수의 끝까지 한번에 실행하는 명령어




ni명령어 입력시 printf() 다음 주소인 0x401195에 rip가 이동한다.

si 명령어 입력시 printf()내부로 들어간다

si로 printf 내부 진입 후 finish 명령어를 내리면 함수의 끝까지 한번에 실행한다
info registers(i r) : 프로그램이 실행 중일 때 레지스터에 저장된 값을 출력

b *$(레지스터) : 레지스터 안에 주소값이 있을 때 그 주소값으로 바로 bp를 걸 수 있다

info breakpoints(i b) : 프로그램의 실행 여부와 상관 없이 bp를 확인 가능

disable 중단점 번호 : 중단점 비활성화(enable로 다시 활성화), 중단점을 다시 활성화 할 때까지 해당 중단점에서 프로그램이 멈추지 않음
delete 중단점 번호(d 중단점 번호) : 중단점 삭제

disassemble 함수이름(disass 함수이름) : 함수 이름을 인자로 전달하면 해당 함수가 반환될 때 까지 전부 디스어셈블하여 보여준다

u, nearpc, pdisass : pwndbg에서 제공하는 디스어셈블 명령어로, 디스어셈블된 코드를 가독성 좋게 출력해준다

x(examine)
x 명령어를 이용하면 특정 주소에서 원하는 길이만큼의 데이터를 원하는 형식으로 인코딩해서 볼 수 있다.
원하는 포멧과 크기를 조합하여 x/<포멧 및 크기> 형태의 명령어를 사용한다. 여러개의 값을 보기 위해서는 x/<개수><포멧 및 크기> 형태의 명령어를 사용한다.
x/<개수><포멧 및 크기> :
| 포멧 | 크기 |
| x: 16진수 | b: byte, 1 byte |
| o: 8진수 | h: halfword, 2byte |
| d: 10진수 | w: word, 4byte |
| t: 2진수 | g: giant, 8byte |
| f: float | |
| a: 주소 | |
| i: 명령어 | |
| c: 문자 | |
| s: 문자열 |


가상 메모리는 프로그램이 실행되기 전에는 할당되지 않으므로 x 명령어는 프로그램 실행 도중에만 사용할 수 있다.
telescope <주소> [개수](tele <주소> [개수] : 특정 주소의 메모리 값들을 보여주고 그 메모리가 참조하고 있는 주소를 재귀적으로 탐색하여 보여준다.

bt (k로도 가능) : 콜 스택을 출력
콜 스택이란 프로그램이 실행되는 동안 함수가 호출되는 순서를 저장하는 스택 자료구조이다. (FILO)
어떤 함수에 전달된 인자에 문제가 발생해 버그가 발생했다면, 이 인자가 어떤 함수로부터 왔는지 확인 할 때 유용하게 사용할 수 있다.



f (프레임 숫자): 특정 스택 프레임으로 이동할 때 사용한다. info frame이나 info locals를 더 정확하게 보기 위해 사용한다

bt(k)로 콜스택을 확인한 뒤 f (숫자) 명령어를 이용해서 원하는 프레임으로 이동할 수 있다.
f로 프레임에 접근 한 뒤 context를 통해 해당 함수 부근을 살피면서 문제가 발생한 지점을 확인할 수 있다. telescope를 이용하면 더 강력하게 분석할 수 있다!

dump memory <파일명> <시작주소> <끝주소> : 프로세스의 메모리 상태를 파일로 저장할 때 사용하는 명령어이다
ex)프로그램의 코드영역 덤프 뜨기
1. entry 명령어로 프로그램을 실행한 뒤
2. vmmap 명령어로 프로그램의 코드영역의 시작과 끝 주소를 얻는다

3.dump memory code_section 0x401000 0x402000(예시)로 코드영역의 시작부터 끝까지 메모리 덤프를 한다.

같은 파일에 code_section이라는 파일이 생성된다.


해당 파일을 확인해봤을 때 메모리 덤프가 정상적으로 일어났음을 확인해볼 수 있다.
context(ctx) : 주요 메모리들의 상태를 가독성 있는 인터페이스로 보여준다. 프로그램이 실행되다가 중단점에 도달하면 자동으로 실행되며, context 명령어로 별도 실행 가능하다
context 명령시 보여지는 4개의 영역
| REGISTERS | 레지스터의 상태를 보여준다 |
| DISASM | rip부터 여러 줄에 걸쳐 디스어셈블된 결과를 보여준다 |
| STACT | rsp부터 여러 줄에 걸쳐 스택의 값들을 보여준다 |
| BACKTRACE | 현재 rip에 도달할 때 까지의 어떤 함수들이 중첩되어 호출됐는지 보여준다 |
set <주소/레지스터> = <변경할 값> : ★프로세스의 메모리 상태를 변경할 수 있는 명령어이다. 주로 레지스터 값을 변경하거나 특정 주소의 메모리 값을 변경하며, 프로그램이 실행 중인 상태에서만 동작한다.
연습해보면서 자세히 알아보자!

여기서 printf에서 add(a,b)의 값을 출력하는데 이 add결과값인 5는 출력 전 특정 레지스터에 저장이 된 뒤 출력이 될 것이다.

b printf를 통해 printf에 bp를 걸어둔 뒤 r로 코드를 실행한다.

info register로 레지스터에 들어간 값을 보면 rcx에 add값인 5가, rdx와 rsi에 각각 b,a값이 들어감을 알 수 있다.

IDA로 어셈블리어를 분석해보았을 때에도 ecx에 add함수의 리턴값인 eax가 들어감을 알 수 있다
이 때 set $rcx를 통해 rcx레지스터에 들어간 값을 바꿔주면 함수의 출력값을 바꿀 수 있다.


그 다음 continue를 통해 함수 끝까지 실행하면 99999가 출력됨을 알 수 있다.

이상으로 GDB의 기본 사용에 대한 블로그 포스팅을 마치겠습니다!
아직 포너블이나 리버싱에 익숙하지 않은 초보지만 꾸준히 포스팅 하면서 공부한 내용들 정리해서 올리겠습니다~ㅎ
