본문으로 바로가기


[1] 어셈블리어 ( Assembly ) 는 무엇일까? & 배우는 목적?

어셈블리어는 기계어와 함께 유일한 저급 언어 ( Low level ) 언어이다.

기계어에 1대1 대응이 되는언어이며, 기계어 바로 전 단계의 언어이다.

여기서 저급 언어란, 컴퓨터와 더 가까운 언어라고 생각하면 되고, C언어도 포인터를 통하여 메모리에 접근하기때문에 저급언어적 특징을 가지고잇는 고급 ( High level ) 언어라고 생각하면 된다.

여기서 여러분들은 컴퓨터와 더 가까운 언어인 어셈블리어를 왜 배워야할까? 라고 생각할수도있다.

물론 나도그랬다.

그런데 시스템해킹이라던가, 리버스엔지니어링, 임베이드 프로그래밍 등등 여러가지 분야에서 어셈블리가 필수적이였다는것을 알게되었다.

시스템해킹을 공부하다보니 간단한수준의 어셈블리 명령어와 레지스터들은 외워야했고, 그 내용들을 정리해보기 위하여 글을 작성한다.


원시 프로그램 -- 번역 --> 목적 프로그램 --> 링크 --> 로드 모듈 -- 로드 --> 실행 
    Source       Compile         Object         Link       Module      Load      Start



위의 내용은 프로그램의 컴파일 과정이다. 원시프로그램은 우리가 작성하는 소스코드를 말하고, 컴파일러가 소스코드를 변환하여 목적프로그램으로 만드는데, 목적프로그램 ( CPU가 이해하여 할수있는 프로그램 ) 으로 변환되는 과정에서 몇몇 고급언어 ( C, Java ) 는 먼저 어셈블리어로 변환되는 과정을 거친다.

이것이 어셈블리어가 중요한 이유이다. 프로그램이 돌아가는 과정을 이해하기위한 필수 요소중 하나이다.

아래 내용은 몇가지 주요 레지스터와, 주요 명령어들의 목록과 내용이다.






[2] 어셈블리 레지스터

1. EAX 레지스터

사칙연산 등 산술 연산에 자동으로 사용되며, 함수의 반환값을 처리할때도 EAX 레지스터가 사용된다.

2. ESP 레지스터

하나의 스택프레임의 끝 지점 주소가 저장된다. PUSH / POP 명령어에 의해 값이 4씩 변한다.

3. EBP 레지스터

하나의 스택프레임의 시작주소가 저장된다. 현재 스택프레임동안에는 절대로 값이 바뀌지 않다가  현재 스택프레임이 소멸되면 이전 스택프레임을 가르키게 된다.

4. EIP 레지스터

다음에 실행할 명령어의 주소를 가지고있는 레지스터이다. 현재 실행하고있는 명령어가 종료되면 EIP 레지스터에있는 명령어를 실행하게 된다.

이외에도 다양한 레지스터들 ( 플래그 레지스터라던가, EBX 라던가 .. ) 이 존재하지만, 내가 프로그램을 뜯어볼때 필요한 부분은 위의 4가지의 레지스터들이였다.

5. SFP ( Stack Frame Pointer )

스택프레임을 거치고, 함수가 돌아가야할 위치를 저장해둔 포인터이다.  즉, 함수가 호출되기전의 스택의 흐름을 그대로 유지하기위한 레지스터 (?) 라고 할수있다.




[3] 어셈블리 명령어

아래 내용에서 1,2는  < (명령어) (1) (2) > 이다.

1. ADD

1에 2를 더하여 1에 저장한다.

ex ) ADD eax 100  =>  eax = eax + 100


2. SUB

1에 2를 빼서 1에 저장한다.

ex) SUB esp 30 => esp - sup - 30


3, MOV 

1에 2의값을 입력한다.

ex) MOV ebp 20 => ebp == 20


4. XOR 

1과 2가 XOR 연산된다.

XOR 연산은 각 비트가 같으면 0, 다르면 1을 반환한다. 다른 논리연산자들의 명령어도 있다. ( AND, OR 등등.. )


5. PUSH

ESP 레지스터가 가지고있는 메모리주소에 1를 복사하고, ESP의 값을 -4 한다. ( 무조건 4바이트 단위로 값이 감소함 )

=> ESP에 값을 밀어넣어 ( PUSH ) 줌.

나중에 메모리구조에 관해 글을 쓸거같은데, 스택에 대한 이해를 가지고있으면 이해하기 쉬울것이다.

스택은 차곡차곡 값이 쌓이는 프링글스같은 구조이며, LIFO ( 후입선출, Last in First Out ) 구조이다. 마지막에 넣은 값이 처음으로 나오고, 처음으로 넣은 값이 마지막에 나온다.

따라서 PUSH 명령어로 인해 ESP는 값이 감소하고, 스택은 아래로 자란다.

ex) PUSH 0x10 => 스택에 0x10 이라는 16진수를 넣고 ( 현재 ESP의 메모리 주소에 10이 들어감 ), ESP값을 4감소시킨다. 


6. POP

ESP 레지스터가 가지고있는 메모리주소 에 1을 복사하고,  ESP의 값을 +4 한다. ( 무조건 4바이트 단위로 값이 증가함 )

PUSH 명령어로 인해 ESP는 값이 증가하고, 스택이 위로 올라온다.

ex) POP eax => 현재 esp에 eax를 저장하고, esp+4를 한다.


7. LEA

1 ( 1은 무조건 레지스터 )에 2의 주소값을 입력한다.

ex) MOV eax ebp-4 => eax == ebp-4


8. JMP

2로 간다. ( 2가 가리키는 코드로 점프해서 실행함. C언어에서  goto 문이라고 생각하자. ) 

ex) JMP 1234 => 1234로 점프한다.


9. CALL

2로 간다. ( 함수 호출시 사용된다. JMP명렁어 같이 프로그램의 실행 흐름이 변경되지만 JMP명령와 다른 점은 되돌아올 위치를 스택에 저장한다는 것이다. 되돌아올 주소를 저장하기 떄문 ( RET )에 함수 호출 후 원래 위치로 실행 흐름을 되돌릴 수 있다. 호출한 함수가 일을 다 마치면 원래 위치에서 다시 프로그램이 실행될 수 있음을 의미한다. )

ex) CALL <hello>


10. RET

CALL 한 주소로 돌아간다.


11. NOP

아무것도 하지않는다.







'Reversing > Assembly' 카테고리의 다른 글

[ARM] ARM asm & instruction / ARM exploitation  (0) 2019.09.26