티스토리 뷰

포너블/포너블 공부

스택(달고나 정리편)-1

삼전동해커 2018. 6. 27. 21:24

출처 : (달고나)와우해커 BOF 기초문서.pdf



1.목적

bufferoverflow 기술을 이용해 원하는 명령을 실행할 수 있다.

그럼 bufferoverflow가 뭐고 어떤 원리로 동작하는지 알아보자.


메모리의 구조



시스템이 초기화 되기 시작하면 시스템은 커널을 메모리에 적재시키고 가용 메모리 영역을 확인한다. 시스템은 운영에 필요한 명령어들을 커널에서 찾기 때문에 커널은 항상 저 위치에 있어야 한다.

32bit 운영체제에서는 cpu가 한번에 처리할 수 있는 데이터의 크기가 32bit 단위라는 의미이다. 그럼 메모리 영역에 주소를 할당할 수 있는 범위가 0~2^32-1이다. 64bit이면 0~2^64-1이 되겠다.


그럼 하나의 프로세스, 프로그램이 실행되기 위한 메모리 구조를 알아보자. 하나의 프로세스를 실행하면 segment라는 단위로 묶어서 가용 메모리 영역에 적재한다. 




여러개의 세그먼트들이 동시에 적재되어 멀티 테스킹이 가능하다.


하나의 세그먼터는 오른쪽과 같이 Stack,Data,Code로 구성되어 있다. 하나 씩 알아보자.


Code Segment

시스템이 알아 들을 수 있는 명령어들이 들어있다. 이 명령어들은 기계어 코드인데 컴파일러가 만든 것이다.

 명령들이 실행되면서 여기저기 점프하거나 호출을 실행하면서 특정 위치에 있는 명령을 실행해야 한다. 하지만 segment는 자신이 현재 메모리 상에 어느 위치에 저장될지(여러 프로그램이 동시에 올라가니 자신의 위치를 정확히 알 수 없다.) 컴파일 과정에서는 알 수 없기 때문에 logical address를 사용한다. logical address는 실제 메모리 상의 주소(physical address)와 매핑되어 있다.

 즉 segment는 segment selector에 의해 자신의 시작 위치(offset)을 찾을 수 있고, logical address에 있는 명령을 수행할 지를 결정할 수 있다. 따라서 실제 메모리 주소(physical address)는 logical address+offset이라 할 수 있다.


아래 예제를 보면 이해가 쉽다.



segment의 실제 위치가 0x80010000이다. segment내에 명령어1 의 위치가 0x00000100이다. 이건 logical address이고, 0x80010000+0x0000100=0x80010100이 physical address 라이다.



Data Segment

data segment에는 프로그램이 실행시에 사용되는 전역변수들이 들어간다. 


Stack Segment

stack segment가 우리가 사용할 buffer라고 할 수 있다. 여기에 지역변수들이 자리잡는다.

스택은 처음 생성될 때 필요한 크기만큼 자리를 만들고, 명령에 의해 데이터들을 저장한다. stack pointer(SP)라고 하는 레지스터가 스택의 맨위에 위치한다. push와 pop명령어를 통해 데이터(code에 위치한 명령어)를 저장하고 읽어들인다.

 스택은 FILO(First In Last Out)으로 동작한다. 접시 닦이처럼 먼저 씻은건 맨 아래로, 가장 나중에 닦은건 맨 위에 위치한다. 접시를 쌓는 과정을 push이고, 맨 위부터 하나씩 쓰는 과정을pop이라 한다.


레지스터

프로세스를 cpu가 실행하기 위해서는 프로세스를 cpu에 적재해야 한다. 그리고 실행되어진 결과 값이나 데이터들을 읽고 저장하기 위해서는 저장공간이 필요하다. cpu 내부에 이런 저장공간이 바로 레지스터이다. 

레지스터는 종류에 따라 범용 레지스터, 세그먼트 레지스터, 플래그 레지스터, 인스트럭션 포인터로 구성된다.




범용 레지스터



논리 연산(+,-,*,/)에 사용되는 피연산자, 주소를 계산하는데 사용되는 피연산자, 메모리 포인터가 저장되는 레지스터 이다.

프로그래머가 임의로 조작할 수 있게 허용되어 있는 레지스터이다. 변수와 비슷한 느낌이다. 

16bit시절에는 AX,BX,CX,DX로 불렸지만 32비트가 되면서 E(Extended)AX,EBX,ECX,EDX가 되었다. 각 레지스터 마다 용도가 정해져 있다. 하나씩 알아보자.


EAX - 피연산자와 연산 결과의 저장소

EBX - Data Segment안에 데이터를 가리키는 레지스터

ECX - 루프를 위한 카운터

EDX - I/O 포인터


ESI - 주소 이동(호출이나 점프)시에 출발지 주소를 저장해 놓은 레지스터

EDI - 주소 이동시에 도착지 주소를 저장해 놓은 레지스터


ESP - stack segment의 맨 꼭대기를 가리키는 레지스터

EBP - segment의 시작점를 저장해놓은 레지스터


세그먼트 레지스터



세그먼트 레지스터는 프로세스의 특정 세그먼트를 가리키는 포인터 역할이다. CS 레지스터는 code segment를 가리키고, DS,ES,FS,GS 레지스터는 data segment를, SS 레지스터는 stack segment를 가리킨다. 세그먼트 레지스터가 가리키는 위치를 바탕으로 segment안의 특정 데이터, 명령어들을 읽어올 수 있다.



플래그 레지스터



컨트롤 플래그 레지스터는 상태 플래그, 컨트롤 플래그, 시스템 플래그들의 집합이다. 시스템이 리셋되어 초기화 되면 이 레지스터는 0x000000002의 값을 가진다. 그리고 1,3,5,15,22~31번 비트는 예약되어 있어 조작할 수 없다. 


상태 플래그

CF - carry flag. 연산을 수행하면서 carroy 혹은 borrow가 발생하면 1이 된다. carry 와 borrow는 덧셈 연산시 경계값을 넘어가는 경우나, 뺄셈에서 빌려오는 경우를 말한다.


PF - parity flag. 연산 결과 최하위 바이트의 값이 1이 짝수일 경우에 1이 된다. 패리티 비트 체크시 사용한다.


AF - Adjust flag. 연산 결과 carry나 borrow가 3bit 이상 발생할 경우 1이 된다.


ZF - zero flag. 결과가 zero임을 가리킨다. if문 같은 조건문이 만족될 경우 1이 된다.


SF - sign flag. 연산 결과 최상위 비트의 값과 같다.


OF - overflow flag. 정수형 결과값이 너무 큰 양수이거나 너무 작은 음수여서 피연산자의 데이터 타입에 모두 들어가지 않을 경우 1이 된다.


DF - direction flag. 문자열 처리에 있어서 1일 경우 문자열 처리 명령이 자동으로 감소, 0일 경우 자동으로 증가.


인스트럭션 포인터

명령어 포인터(IP)는 다음 실행할 명령어가 있는 현재 code segment의 offset값을 가진다.

EIP 레지스터는 바로 엑세스 할수 없고, jmp,call,ret에 의해 제어된다.

EIP 레지스터를 읽는 방법은 call 명령을 수행하고 프로시저 스택으로부터 리턴하는 명령의 주소를 읽는 것이다.

리턴 어드레스를 수정하고, 리턴 후의 명령어를 수정함으로써 EIP의 값을 간접적으로 수정.

'포너블 > 포너블 공부' 카테고리의 다른 글

PLT(Procedure Linkage Table),GOT(Global Offset Table)  (0) 2020.09.15
execve() 함수  (0) 2020.09.08
Return To Libc 기법  (0) 2018.07.31
레이스 컨디션 해킹  (0) 2018.06.27
심볼릭 링크 해킹  (0) 2018.06.27
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/04   »
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 27
28 29 30
글 보관함