Information Security

함수 본문

System Hacking

함수

leeeeye321 2018. 1. 23. 04:57

함수

어셈블리어에서 함수라는 개념은 없다.

하지만 함수처럼 동작하도록 구현할 수는 있다. 어셈블리어에서 함수를 구현해보기 위해서는 스택 메모리에 대한 이해가 필요하다.

 

프로세스가 사용하는 메모리의 구조를 알아보기 위해서 간단한 C코드를 작성했다.

 

C코드를 컴파일한 후, 실행 파일을 실행한다.

-> sleep에 의해서 대기 상태가 된다.

-> Ctrl+Z로 빠져나온 후 ps -ef로 프로세스 목록을 확인한다.

 

백그라운드로 실행 중인 것을 알 수 있다.

여기서 프로세스의 PID를 확인한다. (817)

 

proc 디렉터리에는 이름이 PID인 디렉터리들이 존재한다.

-> 우리는 PID 817 디렉터리를 확인해야 한다.

 

프로세스는 파일 형태로 저장되어 있고, 그에 대한 정보도 파일이다.

-> 817 디렉터리 중 maps 파일은 프로세스가 사용 중인 메모리에 대한 정보를 가지고 있다.

 

-> 프로세스 메모리의 구조를 그림으로 표현해 보았다.

 

08048000-08049000 r-xp 00000000 03:01 425344     /root/a.out
시작 메모리의 주소는 [08048000]이다. 이 전의 메모리는 사용하지 않는다.

-> 실행 권한을 가지는 것을 봐서 TEXT 세그먼트인 것을 알 수 있다.

 

08049000-0804a000 rw-p 00000000 03:01 425344     /root/a.out
-> DATA 세그먼트(data + bss + heap)

-> 전체 4GB 중 실행 파일이 차지하는 부분(08048000-0804a000)은 매우 작다.

-> 0804a000-40000000까지는 예약된(reserved) 공간(heap)이다.

 

40000000-40013000 r-xp 00000000 03:01 310116     /lib/ld-2.1.3.so

40013000-40014000 rw-p 00012000 03:01 310116     /lib/ld-2.1.3.so
40014000-40016000 rw-p 00000000 00:00 0
4001c000-40109000 r-xp 00000000 03:01 310123     /lib/libc-2.1.3.so
40109000-4010d000 rw-p 000ec000 03:01 310123     /lib/libc-2.1.3.so
4010d000-40111000 rw-p 00000000 00:00 0
프로그램이 라이브러리를 사용하면 이 공간(shared lib)을 이용하게 된다.

-> 라이브러리도 elf 구조를 따른 실행 파일이므로 TEXT, DATA 세그먼트를 가진다. 

 

bfffe000-c0000000 rwxp fffff000 00:00 0

스택 메모리는 모든 권한을 가진다.

여기서 한 바이트를 넘어가게 되면 커널 메모리를 침범하게 되므로 위험하다.

-> 그래서 스택을 사용하다가 추가적으로 메모리가 필요하면 낮은 주소쪽으로 할당해서 사용한다.

-> 스택 메모리의 시작 주소(bfffe000)부터 shared lib가 끝나는 부분까지가 추가적으로 사용할 예약된(reserved) 공간이다.

 

Stack

-실행 파일 별로 DATA, TEXT 메모리를 가지는데, 스택은 누구나 공통으로 사용할 수 있는 공유메모리이다.

-스택은 4byte씩 정렬된다.

-마지막으로 들어간 값이 가장 먼저 나오는 LIFO(Last In First Out) 구조이다.

-데이터를 쌓을 때 push 명령어를 사용하고, 데이터를 꺼낼 때는 pop 명령어를 사용한다.

-top은 가장 위 쪽의 데이터를 가리킨다. 가장 밑에서 부터 top까지가 현재 사용중인 메모리이다.

push를 하면 top 기준으로 주소가 +4byte되고, pop을 하면 top 기준으로 주소가 -4byte 된다. 

-ESP, EBP 레지스터를 이용하여 스택을 관리한다.

 

ESP(Stack Pointer): 현재 top의 주소를 나타내며, 4byte 단위로 수시로 변경된다.

EBP(Base Pointer): bottom의 주소를 값으로 가지며, 기준점을 잡을 때 사용한다. 값이 변경되지 않는다.

 

ESP의 값을 확인했다.

 

push esp (-4) -> push prompt_hex (-4)

두 번째로 출력한 ESP의 값은 앞에서 PUSH를 두 번 했기 때문에 앞의 ESP 값보다 8byte 작게 나온다.

앞에서 말했듯이 스택은 주소가 늘어나면 커널을 침범하기 때문에 주소가 줄어드는 쪽으로 데이터를 쌓는다.

 

스택 메모리에 대해서 어느정도 이해를 했으니 sum 함수를 정의하는 C코드를 어셈블리어로 표현해볼 것이다.

 

int result = 0;

int a, b;

 

int sum()

{

return a+b;

}

 

int main()

{

result = sum(10, 20);

printf("sum: %d\n", result);

 

return 0;

}
->

모두 jmp로 함수 호출, return을 구현했다.

지금은 코드가 몇 줄 되지 않아서 jmp로 쉽게 구현이 가능하지만, 더 긴 코드에서는 jmp로 구현하기가 어려울 것이다.

 

jmp를 사용하지 않고 EIP 레지스터를 이용할 것이다.

EIP(Instruction Pointer) 레지스터는 다음에 실행될 명령어의 주소를 가진다.

-> CPU는 이를 참조하여 EIP가 가리키는 주소의 명령어를 실행한다. 

 

push eip

jmp sum

스택에 현재 EIP의 값을 저장시켜 놓고(Saved EIP)

 

pop eip

함수가 종료될 때 EIP를 꺼내면, 리턴을 구현할 수 있을 것이다.

 

push eip / jmp sum -> call sum

pop eip -> ret

하지만 현재 nasm에서 eip을 확인할 수 없다.

-> call, ret를 사용하여 함수 호출, 리턴을 구현한다.

 

 

오호 이렇게 함수처럼 구현하여 결과가 나왔다.

우리는 지금 data 세그먼트를 이용한 전역 변수로 함수를 구현했다.

-> 하지만 보통 함수를 정의할 때 전역 변수보다는 지역 변수를 사용한다.

-> 이제 지역 변수를 사용하여 함수를 구현해볼 것이다.

 

지역 변수는 스택 메모리를 사용하게 된다.

★ 함수를 실행하기 전 스택에 지역 변수의 크기만큼 메모리를 잡는다.

-> 이 메모리는 함수가 끝날때까지 변하지 않기 때문에 정적 메모리(static memory)라고도 한다.

 

int sum(int a, int b)

{

return a+b;

}

 

int main()

{

int result = 0;

result = sum(10, 20);

printf("sum: %d\n", result);

 

return 0;

}
이제 이 C코드를 어셈블리어로 표현해 볼 것이다.

 

1) 메인 함수

main:
        push    ebp
        mov     ebp, esp
        sub     esp, 4

 

위에서 ESP는 값이 수시로 변하지만 EBP는 값이 변하지 않는다고 했다.

지역 변수를 할당하기 위해서 함수 종료까지 변하지 않을 main 함수의 기준점을 EBP에 저장한다.

-> 이 부분은 function prologue라고 한다.

-> esp의 값을 4 줄여준다. result 변수의 메모리가 확보된다.

 

        push    20
        push    10
        call    sum
두 개의 인자를 push한 후 sum을 호출한다.

call 하면서 현재 위치(?)가 EIP에 기록될 것이다.

 

 low

 EIP 

 10

 20

 result

 EBP(ESP)

 EIP

 high

스택은 현재 이런 상태일 것이다.

 

2) sum 함수

sum:
        push    ebp
        push    ebp, esp

function prologue

sum 함수의 기준점을 잡아준다.

 

        mov     ebx, dword [ebp+8]
        add     ebx, dword [ebp+12]
        mov     eax, ebx

 

 low

 EBP(ESP)

 EIP

 10

 20

 result

 EBP(ESP)

 EIP

 high 

기준점 ebp에서부터 +8을 하면 지역 변수 a, +12를 하면 b에 접근할 수 있다.

ebp를 ebx 레지스터에 저장하고 add 연산을 통해 sum 함수의 기능을 구현한다.

eax 레지스터에 return 값을 저장한다.

 

mov    esp, ebp

pop    ebp

ret

->

leave

ret

esp를 ebp로 위치하게 한 후 ebp를 pop한다. 이 두 과정은 leave로 한꺼번에 사용할 수 있다.

그리고, pop eip(ret)까지 하면 return이 구현된다.

 

call    sum

add    esp, 8

call 한 곳으로 돌아가게 된다.

caller는 메모리를 낭비 되지 않도록 회수해야 한다.

-> 8을 더한 주소를 esp가 가리키게 한다. 지역 변수 a, b가 사용하던 메모리를 회수한다.

 

         mov     dword [ebp-4], eax
        push    dword [ebp-4]
        push    prompt
        call    printf
        add     esp, 8

return 값을 가지는 eax를 result에 옮기고, 이제 드디어 결과 값을 출력한다.

 

        mov     esp, ebp
        pop     ebp
        ret

return 0 까지 해주면 끝!!!

 

전체 코드

'System Hacking' 카테고리의 다른 글

시스템 콜(System Call)  (1) 2018.01.25
main 함수의 인자 argc, argv  (0) 2018.01.24
관계 연산(cmp) + 제어문  (0) 2018.01.19
레지스터(Register)와 사칙연산  (0) 2018.01.18
주소 vs 메모리  (0) 2018.01.16