ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [시스템해킹 #1] 함수의 호출/복귀 원리, 메모리 구조, 스택 프레임, 함수 프롤로그/에필로그, 지역변수/전역변수 할당, 어셈블리어 기
    EVI$ION/SYSTEM HACKING 2019. 5. 5. 00:48

    [함수의 호출/복귀 원리]

    아래 코드의 메인 함수 내에서 정의된 함수(get_area) 호출되면, 컴파일러는 해당 함수(get_area) 시작점으로 간다는 의미의 기계어를 생성한다. 기계어로 인해 프로그램 실행 도중에 함수를 만나면(12 라인) 프로그램은 함수의 시작점(1 라인)으로 되돌아가 함수를 수행한다. 함수 수행을 모두 끝마치면 프로그램은 처음 함수가 호출된 곳으로 다시 되돌아가는데, 이유는 함수의 리턴값을 변수(area) 저장해야 하기 때문이다.

    get_area 함수

    그런데 같은 함수를 여러 사용하는 경우, 함수가 호출된 위치를 미리 기록해두지 않으면 함수 수행이 끝난 다음 코드로 되돌아갈 없기 때문에 프로그램이 무한 루프에 갇힐 위험이 있다.

    그래서 프로그램은 함수가 호출되기 직전, 함수 수행 전에, 함수가 끝난 다음 실행될 위치를 메모리 어딘가에 기록한다.

     

    함수 수행이 끝난 다음 실행될 코드의 위치를 리턴 주소(Return Address)라고 하며, 리턴 주소가 저장된 메모리의 위치를 알아내는 것이 시스템해킹에서는 매우 중요하다.

    예를 들면 메모리의 공간(A) 공격 코드를 작성한 , 리턴 주소를 A 주소로 바꾸면 공격자가 원하는대로 공격이 가능하다.

     

     

    [메모리 구조]

    데이터는 메모리의 높은 주소부터 낮은 주소로 순서대로 쌓인다. 프로그램 A 코드(아래) 예로 들어 설명하겠다.

    get_area 함수와 main 함수

    프로그램 A 실행 결과는 아래와 같다.

    프로그램 A 실행 결과

    위 결과를 표로 나타내보면 아래와 같다.

    프로그램 A 실행 결과

    위 표를 보면, 프로그램 A에서 선언된 변수들이 실행 순서대로 메모리에 로드된 것을 있다. (높은 주소 낮은 주소 순으로 쌓인다.또한 x, y, area 모두 int형이므로 메모리를 4 byte 차지하기 때문에 주소 또한 4 byte 줄어든다.

     

    변수가 메모리에 올라올 때마다 4 byte 줄어드는 와중에 번째 x area 사이에 8 byte 공간이 존재함을 있다. 이 8 byte의 공간에는 아래와 같이 채워져 있다.

    x와 area 사이 8 byte의 공간

    x area 사이에 리턴 주소와 SFP 메모리에 올라와 있다. 위치에 리턴 주소가 존재하는 이유는 리턴 주소가 부분에 위치해 있어야 가장 효율적으로 프로그램을 실행할 있기 때문이다

    만약 함수 지역변수를 시작으로 버퍼 오버플로우를 시도할 있는 취약점이 존재한다면, 리턴 주소 위치에 다른 코드(주소) 덮어씌울 있다.

     

     

    32bit 기준 메모리 구조

     

    메모리 구조 (32bit)

    1. 커널 영역

    • 커널 + 커널이 사용하는 데이터들이 위치한 영역

     

    2. 세그먼트

    • 비슷한 성격의 정보들을 모아 집합 단위로 관리하는
    • 세그먼트를 사용함으로써 메모리 관리 효율이 높아진다.
    • 참고) 세그멘테이션 오류(Segmentation Fault) 건드리면 안되는 주소 공간을 건드려서 생기는 오류이다.

     

    3. 코드 영역

    • CPU 읽고 해석할 있는 기계어 코드가 위치한 영역
    • 프로그램 실행 코드 영역의 기계어 명령 집합들이 순차적으로 해석되면서 프로그램이 실행된다.

     

    4. 데이터 영역

    • 전역 변수, 정적 변수 등의 각종 변수들이 실제로 위치한
    • 변수 선언 초기화 유무에 따라 저장 위치가 달라진다.

     

    5. Heap 영역

    • malloc( ) 같이 동적으로 메모리를 할당받는 함수를 통해 생성되는 영역
    • 프로그래머가 원하는대로 메모리를 할당받거나 해제할 있기 때문에 효율적인 메모리 관리가 가능하다.

     

    6. 공유 라이브러리 영역

    • 메인 프로그램이 내부적으로 사용하는 라이브러리 함수와 관련된 파일들이 위치한
    • ex) printf( ) 라이브러리 함수는 /lib/libc.so.6 파일에 위치하고, 파일은 printf( ) 프로그램에서 사용될 통째로 공유 라이브러리 영역에 적재된다.
    • 라이브러리 파일 함수 목록은 /usr/bin/nm에서 확인 가능

     

    7. 스택 영역

    • 함수 호출과 관련된 정보들이 위치한
    • 리턴 주소, 함수 지역 변수, 쉘의 환경변수 등이 영역에 적재된다.
    • 스택 바로 위에 커널 영역이 존재하기 때문에, 커널 영역의 데이터를 보호하기 위해 스택은 거꾸로(높은 주소에서 낮은 주소로) 데이터가 적재된다.
    • ESP 레지스터: 현재 프로그램이 사용하는 스택의 위치를 나타내는 포인터
    • 스택의 역할

          ① 서브루틴(함수) 인자를 전달

          ② 서브루틴 내의 지역 변수가 저장되는 공간을 제공

          ③ 서브루틴이 종료될 되돌아갈 주소(리턴 주소) 저장

     

     

    [스택 프레임]

    스택 프레임

    • 서브루틴(함수)이 가지는 자신만의 스택 영역
    • 함수 호출과 관련된 지역 변수와 매개 변수가 저장되는 영역
    • 함수가 호출될 때 생성되고, 함수 수행이 끝나고 리턴 주소로 되돌아갈 때 소멸된다.

     

    스택 프레임의 생성 과정

    1. 서브 루틴 호출 시 필요한 인자들을 먼저 스택에 PUSH 한다.
    2. 리턴 주소를 스택에 PUSH 한다.
    3. 이전 루틴이 사용했던 EBP 레지스터의 값을 SFP에 백업한 후, SFP가 저장된 주소를 새로운 스택 프레임의 EBP로 지정한다. (함수 프롤로그와 같다.)

    ESP와 EBP, SFP

     

     

    [함수 프롤로그와 에필로그]

    함수 프롤로그

    • 스택 프레임을 생성하기 위한 과정의 코드
    • EBP 레지스터를 백업하고 사용할 메모리 공간을 확보한다.

     

    함수 프롤로그 코드

     

    1) PUSH EBP

    • 이전 EBP 레지스터 값을 SFP 형태로 백업한다. ESP 같이 움직인다.

     

    2) MOV EBP, ESP

    • 현재 스택의 위치, 방금 움직인 ESP 레지스터의 주소를 EBP 설정한다. 이로써 새로운 EBP 생성된다.

     

     

    함수 에필로그

    • 현재 함수에서 사용했던 스택을 정리한 EBP 복원하여 이전 함수로 되돌아가는 과정의 코드

     

    함수 에필로그 코드

     

    1) MOV ESP, EBP

    • ESP EBP 옮겨서 스택을 정리한다.
    • 대부분의 경우 함수를 수행한 뒤에는 ESP EBP 위치가 다르기 때문에 레지스터가 가리키는 위치가 같도록 정리해준다.

     

    2) POP EBP

    • 현재 ESP 가리키는 (SFP, 이전 함수의 EBP) EBP 저장하는 코드로, 코드가 수행되면 EBP 이전 함수의 EBP 복구된다.

     

    3) RET (= POP EIP, JMP EIP)

    • POP EBP 인해 ESP 곳을 가리키게 되면, POP EIP 통해 ESP 가리키는 값이 EIP 저장된다. JMP EIP 통해 함수 호출이 끝난 다음 명령을 실행한다.

     

     

    [지역 변수/전역 변수 할당]

    지역 변수 할당

    SUB ESP, 4

    • 인자 하나가 들어갈 메모리 공간을 확보한다.

     

    MOB DWARD PTR SS:[EBP-4], value

    • 바로 전에 SUB 확보한 공간(EBP-4), 현재 ESP 바로 아래까지 (value) 대입한다.

     

     

    전역 변수 할당

    • DS:[메모리 주소] 같이 표시해 해당 주소에 있는 데이터에 접근하고 값을 저장한다.
    • 지역 변수와 달리 [ ] 안에 들어가는 것이 레지스터가 아닌 주소이다.
    • ex) MOV EAX, DWARD PTR DS:[403374], 0A

     

     

     

    [어셈블리어 기초]

    어셈블리어는 Intel 문법(Window) AT&T 문법(Linux) 존재하는데, 거의 Intel 문법을 사용한다. 문법은 정말 비슷하게 생겼는데 결정적으로 연산 방향이 반대이므로 디버깅 매우 주의해야 한다. 리눅스 gdb 실행 반드시 Intel 문법으로 설정을 바꿔줘야 한다.

     

    몰랐던 어셈블리어 정리

     

    CALL [접근할 함수 주소]

    - 실행 가지 동작으로 처리된다.

      ① PUSH EIP // 현재 EIP 가리키는 (리턴 주소) 스택에 저장

      ② JMP [접근할 함수 주소] // 함수의 시작점으로 이동

    - 이후 함수 프롤로그가 수행된다.

     

    PUSH [인자]

    - 지역 변수 값을 저장하거나, 함수 시작 전에 인자값을 전달하거나, 단순 백업 등을 위해 사용한다.

    - 인자값 전달을 목적으로 PUSH 사용되는 경우, 이후에 바로 CALL 등장한다.

       ⇒ PUSH CALL 연달아 있는 경우 함수 호출로 간주

     

    NOP

    - 아무 일도 하지 않는 코드

    - \x90으로도 표현한다.

    - 의미 있는 코드를 만날 때까지 메모리 영역을 그대로 지나가는 코드

     

     

     

     

Designed by Tistory.