본문 바로가기

스터디/파이썬 해킹 프로그래밍

레지스터 / 브레이크 포인트

레지스터 란?

CPU의 작은 저장 공간으로, CPU가 데이터에 접근하는 가장 빠른 방법을 제공한다. 

32bit기준, 

CPU는 8개의 범용 레지스터 (EAX,EDX,ECX,ESI,EDI,EBP,ESP,EBX)를 사용한다.

(이 밖에도 다른 레지스터들이 많다.)

범용 레지스터는 CPU가 명령을 효과적으로 처리할 수 있도록 각기 용도에 맞게 설계되었다.


레지스터의 역할.

EAX Register

어큐뮬레이터 레지스터 (accumulator register) 라고도 부르며, 산술 연산을 수행하기 위해 사용. 또는 함수의 리턴 값을 전달하기 위해 사용 됨.

최적화된 많은 명령이 데이터 계산과 저장을 위해 EAX 레지스터를 사용하게 설계됨.

즉, 사칙연산 같은 기본적인 연산이 EAX 레지스터를 사용함.

(곱하기나 나누기 같은 특화된 연산은 EAX 레지스터 내에서만 수행됨.)

--> 이러한 특징을 이용하여 EAX 내의 값을 확인하면 호출한 함수의 실패 여부, 반환한 값을 알 수 있다.


EDX Register

데이터 레지스터 (Data register)다.

기본적으로 EAX 레지스터의 확장 개념으로 사용되며, 곱하기나 나누기 같은 복잡한 연산 시 추가적인 데이터 저장이 필요할 때 사용된다.

또한 범용 목적의 저장소로도 사용된다. 하지만 대부분 EAX 레지스터와 함께 연동하여 수행되는 연산에 사용됨.


ECX Register

ECX 레지스터는 카운트 레지스터(count register)라 불리며, 반복적으로 수행되는 연산에 주로 사용. 

반복 연산--> 문자열 저장, 반복문의 카운트 등.

중요한 것은, 이 ECX 레지스터는 값을 증가시키며 카운트 하는 것이 아닌, 값을 감소시키며 카운트를 한다.


예제:

counter = 0


while counter < 10:

 print "Loop number: %d" % counter

 counter = counter + 1


위 코드의 어셈블리 코드에서, 

처음 카운트 값을 나타내는 ECX 레지스터의 값이 첫번째에는 10, 다음 연산시에는 9로 감소.

--> 어셈블리는 코드의 연산 방향과는 상관없이, (증가시키든, 갑소시키든) 항상 감소하는 방향으로 연산된다.


x86 어셈블리 언어에서 데이터를 처리하는 반복문에서 효과적인 데이터 처리를 위해 ESI, EDI 레지스터를 사용한다.


ESI Register

데이터 연산을 위한 원천지 인덱스(Source index)를 나타냄.

입력 데이터 스트림의 위치를 나타내기 위해 사용됨.


EDI Register

데이터 연산의 목적지 인덱스 (Destination index)를 나타냄

데이터 연산의 결과가 저장되는 위치를 나타내는데 사용됨.


ESI Register 는 읽기위함.

EDI Register 는 쓰기위함.


--> 이처럼 데이터 연산에 레지스터를 사용함으로써 프로그램의 실행 성능이 향상됨.


ESP Register

- 스택 포인터

EBP Register

- 베이스 포인터


두 레지스터는 함수 호출과 스택 연산에 사용됨.


함수를 호출할 때,

함수에 전달되는 파라미터가 스택에 PUSH 되고 그 다음에는 리턴 주소(RET)가 스택에 PUSH 된다.

이 때, ESP 레지스터는 스택의 가장 높은 위치를 가리킨다.

따라서 ESP 레지스터는 리턴 주소를 가리킨다. (가장 높은 == 마지막에 전달된)


EBP 레지스터는 호출 스택의 가장 낮은 위치를 가리킨다.

경우에 따라서는 컴파일러가 코드의 최적화를 위해 EBP 레지스터의 사용을 제거할 때도 있으며, 이런 경우 기존 EBP 레지스터는 다른 범용 레지스터와 동일한 용도로 사용한다.


EBX Register

EBX 레지스터는 다른 특정한 용도로 만든 레지스터는 아니다.

따라서 추가적인 저장소로 이 레지스터를 사용한다.


EIP Register

이 레지스터는 현재 실행 중인 명령의 주소를 가리킨다.

CPU가 바이너리 코드를 실행시킴에 따라 EIP 레지스터는 CPU가 현재 어느 코드를 실행 중인지 나타내기 위해 계속적으로 실행되는 코드의 주소를 갱신한다.


--> 디버거는 레지스터의 값을 쉽게 읽거나 변경할 수 있어야 한다.

각 운영체제는 디버거가 CPU와 상호 작용하고, 레지스터의 값을 읽고 변경할 수 있는 인터페이스를 제공한다.



Stack

스택은 위 내용에서 다루었듯이, 디버거 개발시에 가장 중요한 "구조체"다.

스택에는 함수가 호출된 방법과 전달된 파라미터, 리턴 주소 등이 들어가 있다.

기본적으로 FIFO (First In Last Out) 구조이며, 함수를 호출할 때 해당 함수에 전달되는 파라미터를 스택에 PUSH 하고, 함수가 리턴할 때는 POP 한다. 

--> 여기서 ESP, EBP 레지스터는 각각 이 스택의 가장 윗부분, 가장 아랫부분을 가리킨다.



함수의 형태에 따라 스택에 어떻게 쌓이는지 살펴보자.


C_language

int my_socks(color_one, color_two, color_three);


ASM_language

push color_three

push color_two

push color_one

call my_socks


In Stack

 RET ----------------- ESP

 color_one

 color_two

 color_three

 Stack 바닥 ---------- EBP


함수 리턴 시 스택의 모든 값을 POP 한다. (꺼낸다)

그 후 RET가 가르키는 주소로 돌아가 작업을 계속한다.


안에 메모리를 차지하는 변수를 선언하면,

( char stinky_sock_color_one[10]; )



저 스택의 RET 위치 위에 

stinky_sock_color_one의 스택이 또 쌓인다.

(RET 아래 쌓이는게 아님)



그리고 ESP 레지스터는 stinky_sock_color_one 을 가리키고 있을 것이다.


디버거는 디버그 이벤트가 발생할 때까지 계속적으로 루프를 돌면서 대기한다.

만약 디버그 이벤트가 발생할 경우 루프를 종료하고 해당 이벤트에 맞는 헨들러를 콜한다.

이벤트 핸들러가 호출되면 디버거는 이후 작업을 어떻게 수행할지 알려주는 명령을 기다린다.


디버거가 반드시 처리해야할 일반적인 이벤트들

- 브레이크포인트

- 메모리 충돌 (Segmentation fault)

- 디버깅되는 프로그램에 의해서 발생한 예외



브레이크 포인트

--> 실행중인 디버깅 대상 프로세스를 멈추게 하는 것. 또는 그 지점


프로세스 정지 후 그 시점의 변수의 값이나 스택 파라미터, 특정 메모리 위치의 값들을

조사할 수 있다.


브레이크 포인트는 

소프트 브레이크 포인트

하드웨어 브레이크 포인트

메모리 브레이크 포인트


세 가지 브레이크 포인트 모두 유사한 동작을 수행하지만, 구현되는 방법은 완전히 다름.



소프트 브레이크 포인트

실행중인 CPU를 일시 중지 시키는 데 사용됨.

(가장 흔하게 사용됨)


소프트 브레이크 포인트는 "한 바이트 명령"을 사용해 디버깅 대상 프로세스의 실행을 중지시킴.

프로세스의 실행이 중지되면 디버거의 브레이크 퐁니트 예외 핸들러가 제어권을 전달받음.


MOV EAX, EBX --> EBX의 값을 EAX 에 저장.

이 명령을 CPU가 실행하는 기계어 명령 (opcode) 으로 바꾸면

8BC3 으로 됨.


만약 

0x44332211 위치의 어셈블리 명령이

MOV EAX, EBX 일 경우,


이 지점에 소프트 브레이크를 걸고 싶다면

2 바이트인 8BC3 에서 정지를 의미하는 0xCC 로 1바이트를 교체해야 한다. (한 바이트 명령)


소프트 브레이크 설정 전

0x44332211: 8BC3 MOV EAX, EBX


소프트 브레이크 설정 후

0x44332211: CCC3 MOV EAX, EBX


8B 명령이 CC 명령으로 바뀌었다.


CPU가 이 바이트를 만날 경우 INT 3 이벤트를 발생시킨다.

그 후 디버거에 전달한다.


디버거가 이벤트를 전달받으면,

1. EIP 레지스터가 자신이 이전에 설정한 브레이크 포인트 주소를 가리키는지 확인한다.

---> 자신이 설정한 소프트 브레이크 포인트에서 멈췄는지 확인한다.

2. EIP 레지스터가 가리키는 주소가 디버거 내부의 브레이크 포인트 리스트에 존재 확인

3. 디버깅 완료 후, 디버거는 다시 올바른 실행을 위해 원래 있던 1 바이트 (예시에서 8B) 를 다시 써 넣는다.


소프트 브레이크 포인트에는 두가지 종류가 있다.

- 일회성 브레이크 포인트

- 지속적 브레이크 포인트


일회성 브레이크 포인트는 한번 브레이크 포인트 이벤트가 발생하면 디버거의 내부 브레이크 포인트 리스트에서 해당 브레이크 정보가 제거됨.

--> 한번만 브레이크 포인트를 걸 때 사용됨.


지속성 브레이크 포인트는 한번 브레이크 포인트가 걸리고 나서도, 리스트에서 해당 지점이 지워지지 않는 것을 의미한다.


소프트 브레이크 포인트는 한 가지 단점이 있다. 

메모리 상의 실행 바이너리 바이트를 변경하기 때문에 CRC (Cyclic Redundancy Check) 체크섬 값이 변경된다.

따라서 만약 코드에서 자신의 CRC 값을 체크하면서 실행된다면, 

자신의 코드가 변경되었는지 (예를 들어 소프트 브레이크 포인트가 걸렸다던지) 체크 할 수 있고, 변경되었다면 바로 종료할 수 있다.

 

이런 한계점을 극복하기 위해 하드웨어 브레이크 포인트를 사용한다.


하드웨어 브레이크 포인트는

브레이크 포인트의 수가 적거나

디버깅할 소프트 웨어의 코드가 변경되면 안될 때

사용한다.


CPU 레벨에서 브레이크 포인트를 설정하는데, 이 때 디버그 레지스터 라는 특별한 레지스터를 이용한다. 

CPU에는 일반적으로 하드웨어 브레이크 포인트를 설정하는 데 사용 되는 디버그 레지스터가 

DR0 ~ DR7 까지 8개 있다.


DR0 ~ DR3 까지의 디버그 레지스터는 브레이크 포인트의 주소를 저장하기 위해 사용된다.

--> 이는 한번에 4개까지의 하드웨어 브레이크 포인트를 걸 수 있다는 뜻이다.


DR4 ~ DR5 까지의 레지스터는 예약된 레지스터


DR6 브레이크 포인트에 의해 발생되는 디버깅 이벤트의 종류를 판단하기 위해 사용되는 상태 레지스터.


DR7 은 하드웨어 브레이크 포인트의 on/off 스위치로 사용됨.

-> 서로 다른 브레이크 포인트의 조건도 저장함.

DR7의 특정 플래그 값 설정 시,

- 지정된 주소의 명령이 실행될 때 

- 데이터가 어느 주소에 써질 때

- 어느 주소에 대한 읽기 또는 쓰기 작업이 수행될 때

의 조건형 브레이크 포인트를 만들어 낼 수 있다.


하드웨어 브레이크 포인트는

최대 4개의 개별적인 브레이크 포인트 설정 가능

브레이크 포인트를 설정할 수 있는 데이터의 최대 크기가 4바이트

--> 큰 메모리 영역에 대한 접근을 추적하고자 하는 경우에는 맞지 않음.

그래서 메모리 브레이크 포인트를 사용함


메모리 브레이크 포인트는 엄밀히 말하면 브레이크 포인트는 아니지만,

디버거가 메모리 브레이크 포인트를 설정하면 

해당 메모리 영역이나 페이지에 대한 접근 권한이 변경된다.


부여되는 접근 권한

페이지 실행

- 이 권한이 할당된 메모리 페이지는 실행시킬 수 있다. 하지만 이 메모리 페이지에서 데이터를 읽거나 쓰려고 하면 접근 위반 예외가 발생한다.


페이지 읽기

- 프로세스는 이 권한이 할당된 메모리의 내용을 읽을 수 있다. 하지만 데이터를 쓰거나 실행 시키려고 하면 접근 위반 예외가 발생한다.


페이지 쓰기

- 이 접근 권한은 프로세스가 해당 메모리 페이지에 데이터를 쓰는 것만 허용한다.


보호 페이지

- 이 권한이 할당된 페이지에 대해 어떤 종류의 접근이라도 발생하면 예외를 발생시킨다.

  예외를 발생시킨 이후에는 페이지의 원래 상태로 복귀된다.

--> 이 페이지에 대한 데이터 접근 발생 시 예외 발생.


메모리 접근 권한들 중에 가장 관심있게 보아야 할 것은 보호 페이지 권한이다.

이 접근 권한은 스택에서 힙을 분리해 내거나 (스택은 힙+데이터 으로 구성)

특정 메모리 영역이 특정 범위 이상으로 커지는지 확인하는데 유용하다.

특정 메모리 영역에 대한 접근 발생 시 프로세스를 중지시키고자 할 때 매우 유용하게 사용된다.

Ex) 네트워크 서버 프로그램을 전달된 패킷의 페이로드가 저장되는 메모리 영역에 메모리 브레이크 포인트를 설정. 

--> 설정한 브레이크 포인트 메모리에 대한 접근 발생 시 CPU가 보호 페이지 예외를 발생시키기 때문에 어플리케이션이 전달된 패킷의 내용을 언제 어떻게 사용하는지 알 수 있다.

더불어 해당 메모리 페이지에 접근하는 명령을 조사하여 어플리케이션이 패킷의 내용으로 인해 어떤 작업을 수행하는지 알아낼 수 있다.


메모리 브레이크 포인트는 실행되는 어떤 코드도 변경하지 않기 때문에 소프트 브레이크 퐁니트가 가지고 있는 코드 변경으로 인한 제약을 극복할 수 있다.


'스터디 > 파이썬 해킹 프로그래밍' 카테고리의 다른 글

스터디 질문 답변  (0) 2015.01.14
2015-01-15 스터디  (0) 2015.01.12
2015-01-12 질문 정리  (0) 2015.01.12
환경 구축 및 동적 라이브러리  (0) 2015.01.11
스터디 OT (feat. Skype)  (0) 2015.01.11