해당 글의 원문은 아래 주소에서 확인할 수 있다.

bpsecblog.wordpress.com/2016/04/04/gdb_memory_2/

 

gdb를 사용하기 위해 다음의 코드를 컴파일하여 사용한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//tomato.c
#include <string.h>
#include <stdio.h>
 
void func2() {
    puts("func2()");
}
 
void sum(int a, int b) {
    printf("sum : %d\n", a+b);
    func2();
}
 
int main(int argc, char *argv[]) {
 
    int num=0;
    char arr[10];
 
    sum(1,2);
    strcpy(arr,argv[1]);
    printf("arr: %s\n", arr);
    if(num==1){
        system("/bin/sh");
    }
    return 0;
}
cs

이 소스코드에서 sum함수는 코드 내부에서 a + b의 결과값을 출력해주는 함수를 새로 만든 것이고, strcpy() 함수는 문자열을 다른 배열이나 포인터(메모리)로 복사하는 함수이다. system함수는 인수로 실행시킬 프로세스의 이름을 받아 그 프로세스를 실행하는 것이다.

 

이 소스코드는 다음 명령으로 컴파일 한다.

gcc -fno-stack-protector -o tomato tomato.c

-fno-stack-protector 옵션을 사용하는 이유는  gcc는 스택을 보호하기 위해 'canary' 라는 것을 삽입하여 함수 내에서 사용하는 스택 프레임과 return address 사이에 canary를 넣는다. 만약 Buffer Overflow가 발생하여 canary를 덮으면 이를 감지하고 프로그램을 강제 종료 한다. 이 과정을 SSP(Stack Smashing Protection)이라고 한다. 해당 옵션을 사용라게 되면 오버플로우가 발생해도 프로그램이 강제 종료 되지 않도록 보호기법을 해제한 것이다.

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

 

gdb는 오픈소스로 공개되어 있는 무료 디버거로 코드에서 어떤 라인을 실행할 때 어떤 값이 어떤 메모리 주소에 올라가는지 과정을 보여주는 것이다. 

다음과 같은 명령어로 gdb로 프로그램을 실행할 수 있다.

gdb ./tomato

gdb로 tomato를 실행하면 다음과 같은 화면이 나온다.

보기 편하도록 어셈코드를 인텔 형식으로 설정하려면 다음 명령어를 사용한다.

set disassembly-flavor intel

at&t 형식으로 보려면 intel 자리에 att를 넣으면 된다.

disas main으로 main함수의 어셈블리 코드를 확인하면 다음과 같다.

b는 breakpoint를 거는 명령으로 b *[메모리주소]의 형태로 사용한다. 메모리 주소에는 메모리 주소, 함수의 이름, 이를 기준으로 한 offset <+0> 으로 걸어도 된다.

모두 같은 주소에 breakpoint가 걸림을 확인할 수 있다.

 

info b 명령으로 breakpoint 정보를 확인할 수 있다.

breakpoint가 중복되니 삭제한다.

삭제할때는 d (breakpoint 번호) 의 형태로 삭제한다.

프로그램을 실행할 때는 run 명령을 사용하는데 뒤에 매개변수를 넣을 수 있다. 

위와 같은 실행은 터미널에서 ./tomato aaaaaaaaaa로 실행한 것과 동일하다.

main 함수의 시작 위치에 breakpoint를 걸었기 때문에 main 함수를 확인해 보면  => 화살표로 main에 멈춰있는 것을 확인할 수 있다.

ni 명령을 통해 인스트럭션을 한줄 한줄 실행할 수 있다.

ni를 몇번 더 해서 원문과 비슷한 위치에 eip가 멈추도록 했다.

현재 멈춘곳 이전의 인스트럭션을 보면 esp 값을 ebp 값에 저장하고 있다. esp, ebp는 스택에 관련된 레지스터 이다.

 

그 메모리에 어떤 값이 담겨있는지 확인하기 전에 메모리 출력 방식에 대해 정리하겠다.

몇바이트 만큼, 몇 진법으로 출력할 지 옵션을 주어 출력할 수 있다.

해당 메모리 주소의 값을 각각 1바이트, 2바이트, 4바이트 만큼 출력한 것이다. x/ 뒤에 b는 1바이트, h는 2바이트, w는 4바이트를 의미한다.

위는 해당 메모리 주소 값을 각각 16진수 10진수로 출력한 것이다. 

다음과 같이 두 바이트 옵션과 진법 옵션을 같이 사용할 수 있다.

위를 보면 4바이트 출력한 것과 1바이트씩 4개를 출력한 것이 순서가 반대이다. Intel CPU는 바이트를 배열할 때 거꾸로 써서 예를 들어 0x12345678을 저장한다고 하면 0x78563412처럼 거꾸로 저장하는 것이며 이를 Little Endian 이라 지칭한다.

gdb는 디버깅시 보기 편하게 하기 위해 Big Endian 형식으로 0x12345678와 같이 출력하기 때문에 1바이트씩 출력할 때와 4바이트로 출력할 때 달리보인다.

 

특정 레지스터를 기준으로 메모리 값을 보고 싶으면 다음과 같이 $register_name로 출력할 수 있다.

info reg 또는 i r 명령을 사용하면 레지스터 정보를 출력할 수 있다.

 

스택과 관련있는 레지스터의 종류는 다음과 같다.

  • EBP : 스택의 가장 밑바닥 주소를 가진 레지스터
  • ESP : 스택의 가장 상단 주소를 가진 레지스터
  • EIP : 현재 실행중인 명령의 주소를 가진 레지스터

현재 EIP의 상황은 화살표가 가리키고 있고, 그 전에 실행된 push ebp 와 mov ebp, esp라는 두 줄의 어셈 코드가 의미하는 것은 스택(프레임)이 생성된다는 것이다. 저 두 줄의 의미는 함수가 실행이 될때 그 이전의 ebp(sfp)를 스택에 push 하고, 현재 esp를 ebp에 저장하라(새로운 ebp 생성)는 뜻이다. push는 스택 포인터를 하나씩 증가시키는 어셈 코드이다.

 

이제 sum 함수에 breakpoint를 걸고 실행시킨 후  스택의 상황을 볼것이다. breakpoint까지 한번에 실행하려면 c명령을 사용하면 된다.

sum의 함수 위치에 정확히 멈춰있는 것을 확인할 수 있다.

sum 함수가 call 되기 전의 상황은 다음과 같다.

sum 함수의 주소는 0x80484b4 이기 때문에 이 위치에 breakpoint를 걸고 함수 안으로 들어가면 스택 esp(0x00000001)위에 다음 인스트럭션의 주소가 쌓일 것이다.

이 함수의 어셈 코드를 확인해 보면 다음과 같다.

이 함수도 마찬가지로 push ebp, mov ebp,esp로 시작하고 있다. push ebp를 실행하면 그 이전 스택프레임(main)의 ebp 가 sum의 스택프레임에 push 되서 main의 ebp 0xbffff038이 esp에 들어갈 것이다. 

그리고 mov ebp, esp를 실행하면 현재 esp를 sum stack frame의 바닥 즉, sum의 ebp가 되서 sum의 ebp에는 main의 ebp가 있게 될 것이다. 

상단 첫번째 줄의 상태는 다음과 같다.

새 스택프레임의 ebp를 기준으로 return address는 ebp-4, 매개인자는 ebp-8, ebp-12에 들어가게 된다.

다음은 sum의 어셈 코드이다.

sum함수 안에서 func2 함수를 호출하고 있다. func 함수 주소에 breakpoint를 걸고 실행한 모습은 다음과 같다.

스택의 모습은 func2 stack frame, sum stack frame, main stack frame 가 차례로 있을 것이다. 

구간별로 나눠보면 다음과 같다.

현재 eip의 상황은 다음과 같다.

leave 실행 전의 스택 상황은 다음과 같다.

leave 를 실행한 후 스택의 상황은 다음과 같다.

위에서 빨간색으로 표시해둔 func2의 stack frame이 정리되며 ebp가 sum의 ebp로 바뀌고 esp도 sum의 esp로 바뀐다. 그리고 ret를 만나면 0x080484d8로 리턴해서 나머지 sum의 인스트럭션을 실행하고, sum의 스택프레임을 정리하고 나머지 main의 인스트럭션도 실행한 후 프로그램은 종료된다. 

+ Recent posts