본문 바로가기
프로그래밍/SYSTEM HACKING

[SYSTEM HACKING] 실행중인 프로세스의 전체 메모리 구조 / 스택에서 사용되는 레지스터 / 어셈블리에서 함수

by B T Y 2017. 10. 26.
반응형

실행중인 프로세스의 전체 메모리 구조 / 스택에서 사용되는 레지스터 / 어셈블리에서 함수에 대해서 정리한다.



스택 메모리


  - 함수

  - 지역변수




1. 실행중인 프로세스의 전체 메모리 구조


  - /proc/pid/maps

    ( 실행했던 프로세스 id에 메모리 구조를 나타내는 파일 )


      * 리눅스에서 .so 확장자는 동적 라이브러리를 나타내고 .a는 정적 라이브러리를 나타낸다.



( 리눅스 메모리 구조가 그림으로 표현된 것이다..

env, etc, argc, argv도 스택의 일부분이다... )



메모리 구조( maps )


08048000-08049000    r-xp    00000000    03:01    425344 /root/a.out

08049000-0804a000    rw-p   00000000    03:01    425344 /root/a.out

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

bfffe000-c00000000    rwxp   fffff000      00:00    0

  

    * 0x00000000 ~ 0x08047fff은 사용하지 않는 영역이다.


    * 0x08048000 ~ 0x3fffffff는 text, data, bss 세그먼트가 사용하는 코드 세그먼트 영역이고 그 이후 남는 영역은 heap 메모리 확장을 위한 예약된 영역이다.

      ( heap 메모리를 계속 할당하게 되면 확장하면서 해당 확장 영역을 사용한다 )


    * 0x40000000 ~ 0xbffdffff는 공유 라이브러리 영역이다.


    * 0xbfffe000 ~ 0xc0000000 영역은 stack 메모리 영역이다.

      ( 스택은 kernel의 메모리 영역을 침범하는 위험을 없애기 위해서 높은쪽에서 낮은쪽으로 주소가 자라난다 )


    * 0xc0000000 ~ 0xFFFFFFFF는 kernel이 사용하는 영역으로 접근이 불가능하다. 


    * stack 메모리는 여러 곳( 함수, 프로그램 )에서 같이 사용하는 공용 공간이다.

        ( 필요할때만 가져왔다가 사용후에 다시 메모리를 회수하는 식으로 동작한다 )


        !! stack 메모리에서 메모리의 할당은 높은쪽에서 낮은쪽으로 되지만 데이터의 할당은 낮은쪽에서 높은쪽으로 이루어진다.


        !! stack 메모리에는 선언한 순서대로 차곡차곡 쌓인다.




( 메모리 구조를 파악하기 위한 프로그램을 하나 간단하게 만들어서 sleep 명령을 이용해서

프로그램이 계속 동작 될 수 있도록 했다.. )



( 프로그램을 실행한다음 백그라운드에서 정지 상태로 만들어 두면 아직 프로세스가 끝난게 아니기 때문에

/proc/ 디렉터리에서 해당 프로세스에 대한 pid로 된 디렉터리가 없어지지 않을것이다..

그러므로 /proc/pid/maps 파일 확인이 가능하다 )



( pid가 767이기 때문에 /proc/767/ 디렉터리에 들어가면 해당 프로세스에 대한

메모리 구조가 나와있는 maps 파일을 볼 수 있다.. )




( /proc/pid/maps 파일을 이용해서 해당 메모리의 구조를 한눈에 파악 할 수 있다... )



( main() 함수는 entry point이므로 &main과 같이 main() 함수의 메모리 주소를 가져올 수 있다.. )









( 스택 메모리의 출력 결과를 자세히 보면 메모리가 순서대로 줄어드는걸 볼 수있는데

스택 메모리의 경우에는 push 된 내용이 차곡차곡 쌓이기 때문이다.. )





2. 스택 메모리에서 사용되는 레지스터


  - ESP: Stack Pointer ( stack 메모리에서 데이터를 어디까지 사용했는지를 알 수 있는 레지스터 )

  - EBP: Base Pointer ( 기준점 )


      * stack pointer의 경우 계속 값이 변하기 때문에 그에 대한 기준점으로 base pointer를 사용해서 

         base pointer를 기준으로 스택 메모리 영역에 접근할 수 있다.



( stack 형태 )




* esp 레지스터는 stack pointer로써 top의 위치를 가르키기 때문에 

그 값이 push나 pop을 할 때마다 계속 변하게 된다.


* ebp 레지스터는 base pointer, 즉 기준점 역할을 하기 때문에 stack pointer를 base pointer에 

넣어두고 해당 기준점으로부터 스택 메모리에 주소를 접근할 수도 있다.




( printf로 출력이 될 때는 push esp를 했을때가 아닌 push prompt_hex에 대한 주소가 출력되는 것이다.. )




( 스택 자료구조에서 볼수 있듯이 push를 이용해서 내용을 넣고 

pop을 이용해서 top에 있는 내용을 꺼낸다.. )




[ a와 b에 값을 할당해서 a와 b를 출력하는 C코드 ]

int main()

{

  int a;

  int b;


  a = 10;

  b = 20;


  printf("%d \n", a );

  printf("%d \n", b);

}


    * 지역변수는 함수가 실행되기 전까지는 메모리에 잡혀있지 않고 함수가 실행되서야 스택 메모리에 

      할당 된다.

        ( 컴파일러가 실행이 될때 지역변수를 다 가져와서 필요한 메모리를 미리 계산해둔다 )


        !! 함수가 실행되는 동안에는 스택 메모리에 대한 크기나 값을 변경할 수 없다. ( static memory )




( sub 명령을 이용해서 스택 메모리를 8byte만큼 사용한다음에 그 stack pointer의 주소 값을

base pointer 역할을 하는 레지스터인 ebp에 할당 해두면 stack pointer가 바뀌어도 

ebp를 이용해서 미리 확보해둔 메모리에 접근해서 값을 할당 시킬 수 있다.. )



* 위 그림에 있는 어셈블리 코드는 위에 있는 a와 b에 값을 할당해서 a와 b를 출력하는 C코드를

어셈블리 형태로 작성한 것이다.




3. 함수


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

  - 함수의 기계어 표현이 아주 정교하게 작성이 되어 있다.

  - 하지만 취약점도 이 과정에서 발생한다.


[ sum 함수가 포함된 C 코드 ]

int sum( int a, int b )

{

  int sum = 0;

  sum = a + b;


  return sum;

}


int main()

{

  int a = 10;

  int b = 20;

  int ret = 0;


  ret = sum( a, b );

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


  return 0;

}


[ 위 코드를 어셈블리 코드 표현한 경우 ( 어셈블리에서 함수 표현 ) ]

extern printf


section .data

prompt_sum db 'sum is: %d', 10, 00


section .text

global main


sum:

push ebp

mov ebp, esp ; function prologue

sub esp, 4


mov dword [ebp-4], eax

add dword [ebp-4], ebx    ;  sum = a + b;


mov eax, dword [ebp-4]    ;  return sum;


mov esp, ebp

pop ebp ;  function epilogue

jmp return


main:


sub esp, 12

mov ebp, esp


mov dword [ebp], 0

mov dword [ebp+4], 20

mov dword [ebp+8], 10


mov eax, [ebp+8]

mov ebx, [ebp+4]

jmp sum

return:

mov [ebp],    eax


push dword [ebp]

push prompt_sum

call printf


    * calling component 표준에 따르면 return 값을 전달할때도 eax를 사용한다.





반응형

댓글