본 문서는 드림핵 강의를 요약한 문서입니다.
https://learn.dreamhack.io/55#1
드림핵 시스템 해킹 강의를 위해 아래 주소에서 dbg 다운로드
https://github.com/pwndbg/pwndbg
다운로드가 완료되면 다음과 같은 화면이 나온다.
소스코드
// 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;
}
gdb debugee로 디버깅 시작
리눅스의 실행파일은 ELF 형식을 가지고 있다. ELF는 헤더와 여러 섹션으로 구성된다. 헤더에는 진입점(Entry Point, EP)가 있는데 운영체제는 진입점의 값부터 프로그램을 실행한다.
readelf -h debugee 로 파악한 진입점은 0x401050이다.

gdb의 entry 명령어는 진입점부터 프로그램을 분석할 수 있게 해주는 gdb 명령어다. 화살표가 현재 rip의 값이다.
공룡책 시리즈 프로세스의 이해
를 참고하면 더 좋다. context는 크게 네 가지 영역으로 나뉜다.
rip부터 여러 줄에 걸쳐 디스어셈블 결과를 보여줌rsp부터 여러 줄에 걸쳐 스택의 값을 보여rip에 도달할 때까지 어떤 함수들이 중첩되어 호출되었는지 보여줌.일반적으로 main함수가 주 기능들을 수행하므로 main함수가 분석 대상이 된다. 하지만 main 함수까지 일일이 한 줄씩 실행하는 것은 효율적이지 못한 디버깅이다. 따라서 중단점(breakpoint)를 설정하고 continue를 활용한다. b *main이라고 입력해서 main함수에 중단점을 설정하고 c를 입력해서 continue를 실행 시키면 main 함수 위치에서 멈춘 것을 볼 수 있다.

run은 실행만 시킨다. 중단점을 설정하지 않았으면 끝까지 실행한다. 지금은 중단점을 설정했으므로 실행이 멈춘다.
gdb 명령어 축약
- b: break
- c: continue
- r: run
- si: step into
- ni: next instruction
- i: info
- k: kill
- pd: pdisas
disassemble는 gdb의 디스어셈블 명령어다. disassemble main 처럼 함수 이름을 인자로 전달하면 해당 함수가 반환될 때 까지 전부 디스어셈블해서 보여준다. u, nearpc, pdisass는 pwndbg에서 제공하는 디스어셈블 명령어로 가독성 좋게 출력해준다.
ni는 서브루틴의 내부로 들어가지 않지만, si는 서브루틴 내부로 들어간다. b *main+64로 새로운 중단점을 설정해서 printf까지 이동할 수 있다. si를 사용하면 함수 내부로 들어가며 BACKTRACE 부분에 printf 함수가 쌓여있는 것을 볼 수 있다.
printf가 출력하는 문자는 stdout의 버퍼에서 잠시 대기한 뒤 이 버퍼는 다음 조건을 만족해야 목적지로 데이터를 이동시킨다.
1. 프로그램이 종료될 때
2. 버퍼가 가득 찼을 때
3. fflush와 같은 함수로 버퍼를 비우도록 명시했을 때
4. 개행문자가 버퍼에 들어왔을 때
finish로 함수 끝까지 한 번에 실행 할 수 있다.
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).
예시
1. x/10gx $rsp : rsp부터 80바이트를 8바이트씩 hex형태로 출력
2. x/5i $rip: rip부터 5줄의 어셈블리 명령어 출력
3. x/s 0x400000: 특정 주소의 문자열 출력
tele는 pwndbg가 제공하는 메모리 덤프 기능이다. 메모리가 참조하고 있는 주소를 재귀적으로 탐색하여 값을 보여준다.
pwndbg> tele
00:0000│ rsp 0x7fffffffc228 —▸ 0x7ffff7a05b97 (__libc_start_main+231) ◂— mov edi, eax
01:0008│ 0x7fffffffc230 ◂— 0x1
02:0010│ 0x7fffffffc238 —▸ 0x7fffffffc308 —▸ 0x7fffffffc557 ◂— '/home/dreamhack/debugee'
03:0018│ 0x7fffffffc240 ◂— 0x100008000
04:0020│ 0x7fffffffc248 —▸ 0x4004e7 (main) ◂— push rbp
05:0028│ 0x7fffffffc250 ◂— 0x0
06:0030│ 0x7fffffffc258 ◂— 0x71eb993d1f26e436
07:0038│ 0x7fffffffc260 —▸ 0x400400 (_start) ◂— xor ebp, ebp
vmmap은 가상 메모리의 레이아웃을 보여준다. 어떤 파일이 매핑된 영역일 경우, 해당 파일의 경로까지 보여준다.
아래 파일을 가지고 gdb에서 파이썬 활용하는 방법을 살펴보자
// Name: debugee2.c
// Compile: gcc -o debugee2 debugee2.c -no-pie
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
char name[20];
if( argc < 2 ) {
printf("Give me the argv[1]!\n");
exit(0);
}
memset(name, 0, sizeof(name));
printf("argv[1] %s\n", argv[1]);
read(0, name, sizeof(name)-1);
printf("Name: %s\n", name);
return 0;
}
run 명령어의 인자로 $()와 함께 파이썬 코드를 입력하면 값을 전달 할 수 있다.
pwndbg> r $(python3 -c "print('\xff' * 100)")
위와 마찬가지로 값을 입력할 수 있다. 입력값은 <<<을 사용한다.
pwndbg> r $(python3 -c "print('\xff' * 100)") <<< $(python3 -c "print('dreamhack')")