profile image

L o a d i n g . . .

실습에 사용할 tomato.c 파일이다.

tomato.c 파일은 strcpy를 할 때 복사 버퍼의 길이를 검증하지 않기에 버퍼 크기인 10보다 더 넣어줘서 bof를 일으켜 num의 값을 1로 바꾸면 될 것으로 보인다.

 

그리고 실습 환경을 최대한 똑같이 해주기 위해 다음과 같이 몇 가지 보호 기법들을 해제해서 컴파일 해주었다.

gcc -m32 -fno-stack-protector -mpreferred-stack-boundary=2 -fno-pie -o tomato tomato.c

 

-fno-stack-protector 옵션을 포함하는 이유
gcc 스택을 보호하기 위해 ‘canary’라는 것을 삽입한다.
함수 내에서 사용하는 스택 프레임과 return address 사이에 'canary'를 넣는 것이다.

 

그리고 Buffer Overflow 발생해 canary 덮었을 때, 이를 감지하고 프로그램을 강제 종료 해 버린다.

이를 SSP(Stack Smashing Protection)이라고 한다.

 

여기에선 오버플로우가 발생해도 프로그램이 강제 종료되지 않도록 –fno-stack-protector 옵션을 사용해 보호기법을 해제한 것이다.

반대로 모든 프로시져에 이 보호기법을 적용하기 위해서는 -fstack-protector-all 옵션을 사용하면 된다.

 

 

 

 

GDB 사용법

GDB란?

gdb는 오픈소스로 공개되어있는 무료 디버거이다.

코드에서 이 라인을 실행할 때 어떤 값이 어떤 메모리 주소에 올라가고 그 과정을 보여주는 것이다.

특히, ELF 파일과 같은 Linux 기반의 실행파일을 동적으로 따라가며 분석할 때 쓸 만하다.

 

 

이것은 단지 gdb로 tomato에 붙은 것에 불과하고 tomato는 아직 실행되지 않았다.

 

 

set disassembly-flavor [명령어 형식]
어셈블리 코드 문법을 설정하는 명령어
x86에서 Intel과 at&t 둘 중 하나를 골라 쓰면 됨

 

disas [함수이름]
함수의 어셈블리 코드를 보는 명령어

 

 

main breakpoint 걸고 한번 실행해보겠다.

b *[메모리주소]
breakpoint를 거는 명령

[메모리 주소]나 [함수의 이름] 혹은 이를 기준으로 한 [offset <+0>]으로 breakpoint 걸면 된다.

 

 

breakpoint 정보를 확인해보면 모두 똑같은 곳에 break가 걸림을 알 수 있다.

info b
breakpoint 정보를 열람할 수 있는 명령어

 

 

Breakpoint가 중복되니 삭제한 것이다.

d (breakpoint 번호)
breakpoint를 삭제 할 수 있는 명령어

 

 

프로그램 위와 같이 실행한다.

run [매개변수]
gdb 내부에서 프로그램 실행

명령행 인자로 main의 매개변수에 “aaaaaaaaa” 스트링을 넘겨준 것이다.

 

 

현재 실행 흐름이 어디에 있는지 EIP를 확인해보면, Main에 멈춰있음을 알 수 있다.

=> 화살표

 

 

 

ni
다음 인스트럭션 실행

 

현재 EIP가 멈춰져있는 곳 이 전의 밑줄 친 인스트럭션을 보면 esp 값을 ebp에 저장하고 있다.
esp, ebp는 스택과 관련한 레지스터로, 그 메모리에 어떤 값이 담겨있는지 gdb로 확인해보자!

 

 

그 전에 메모리 출력 방식에 대해 알고가자.
몇 바이트만큼 그리고 몇 진법으로 출력할 것인지 정해 옵션을 주어 출력해 주면 된다.

해당 메모리 주소의 값을 각각 1바이트, 2바이트, 4바이트만큼 출력해주었다.

 

해당 메모리 주소 값을 16진수, 10진수로 출력해주었다.

 

 

다음은 ebp esp를 4바이트만큼 16진수로 출력한 것이다.

ebp와 esp가 같은 곳을 가르킴을 알 수 있다.
info reg 혹은 i r은 레지스터 정보를 출력하는 명령이다.

 

 

 

STACK

EBP : 스택의 가장 밑바닥의 주소를 가진 레지스터

ESP : 스택의 가장 상단의 주소를 가진 레지스터

EIP : 현재 실행 할 명령의 주소를 가진 레지스터

 

1. 스택의 시작

PUSH EBP
MOV EBP, ESP


이 두 줄의 의미는 ‘스택(프레임)이 생성’이 된다는 것이다.

 

즉, 저 두 줄의 의미는 ‘함수가 실행이 될 때, 그 이전의 ebp(sfp)를 스택에 push하고 현재 esp를 ebp에 저장하라.( 새로운 ebp 생성! ) ‘ 이다.

 

0x080484e0에 breakpoint를 걸어 sum이 호출되기 직전의 상황을 보겠다.

 

sum이 call 되기 전 상황

하얀색 박스가 각각 ESP, EBP이다. (main의 스택프레임)

 

이제 sum 함수 안으로 들어가보도록 하겠다.
그리고 call을 할 때는 다음 인스트럭션의 주소(return address)를 스택에 쌓고 간다.

 

sum이 call 된 이후의 상황

call sum에 breakpoint를 걸고 (0x080484ae) sum 안으로 들어가면 스택에 ESP(0x00000001)위에 0x080485e5가 쌓인 것을 확인할 수 있다.

 

 

그리고 sum 함수 안으로 들어와보면 역시나 Push ebp / Mov ebp, esp를 하고 있다.
Push ebp 실행하면  이전 스택 프레임(main)의 ebpsum 스택 프레임에 push될 것이다.
, main ebp 0xbffffcfb8이 esp 들어가있다.

 

그리고 Mov ebp, esp 하면 현재 esp sum stack frame 바닥 , sum의 ebp로 만들어준다.
그러므로 sum의 ebp에는 main의  ebp가 있다.

 


 

새로운 sum의 스택 프레임 완성

이제 새로운 스택 프레임이 완성되었다.

앞에서부터 차례대로 main의 ebp(ebp) 그리고 return address(ebp+4) 그리고 sum에 전달된 인자 두 개(ebp+12)이다.

 

 

화살표 앞이 sum의 스택 프레임, 그리고 화살표 뒤가 main의 스택 프레임이다.

 

그리고 이번엔 sum 안에서 func2 호출했다.
이때 스택의 모습은 | func2 stack frame | sum stack frame | main stack frame|와 같이 될 것이다.

 

맨 앞 박스가 func2의 stack frame으로, sum의 ebp가 들어가있다.

그 다음 박스가 sum의 stack frame으로, 먼저 RET이 들어가 있고 그 다음 main의 ebp가 들어가있다.

그리고 0xffffcfb8까지가 main의 stack frame으로, 먼저 RET이 들어가있고 sum의 인자들도 보인다.

 

 

2. STACK의 끝

Leave
Ret

 

 

현재 EIP 상황

 

leave 실행 전의 스택 상황

앞에서 차례대로 fun2의 stack frame, sum의 stack frame, main의 stack frame이다.

 

 

leave 실행 후의 스택 상황

Leave 실행 후에 func2 stack frame 모두 정리된 것을 볼 수 있다.
그리고 ebp sum ebp 바뀌고 esp sum esp 바뀌었다.

 

그리고 Ret 만나면 0x080484cc 리턴해 나머지 sum 인스트럭션을 실행하고, 끝으로 sum stack frame도 정리되고 나머지 main의 인스트럭션을 실행한 후 프로그램을 종료하게 된다.

 

 

 

정리

스택 프레임은 push ebp / mov ebp, esp 단 두 줄로 생성된다.

하나의 함수에서 또 하나의 함수를 호출할 때 먼저 인자를 전달해주고, 돌아올 주소인 RET을 스택에 넣고, 그 다음 push ebp를 하면 새로운 스택 프레임이 생성된다. 이때 push 해주는 ebp는 그 이전 함수의 ebp이다. 

 

새로운 스택 프레임에서 계속 스택을 쌓다가 leave, ret 을 통해 stack이 끝나게 된다.

leave를 하면 앞에서 한 과정의 반대인 mov esp, ebp / pop ebp를 하게 되고 따라서 그 이전 스택프레임의 ebp와 esp로 돌아간다. 그 다음 ret을 하면 return address로 리턴해 나머지 인스트럭션들을 실행하게 된다.

 

이것을 반복하다가 main의 인스트럭션들도 모두 실행한 후 main 역시 leave, ret을 만나게 되면 main의 스택 프레임도 끝나면서 프로그램이 종료된다.

 

 

 

참고
https://bpsecblog.wordpress.com/2016/04/04/gdb_memory_2/

 

 

복사했습니다!