이쿠의 슬기로운 개발생활

함께 성장하기 위한 보안 개발자 EverNote 내용 공유

코딩/어셈블리

어셈블리어 (Assembly)

이쿠우우 2020. 8. 9. 19:32
반응형

어셈블리어 (Assembly)

 

 

레지스터 설명

 

EAX, EBX, ECX, EDX

대표적인 범용 레지스터

임시 전역 변수로 사용됨

정수, 포인터, 혹은 그때그때 어셈블리 코드의 흐름에 따라 필요한 값을 저장함

 

ECX = 카운터로 주로 사용됨

 

ESI, EDI

ESI = 문자열을 출발지 주소 (원본 인덱스 source index)

EDI = 문자열 마지막 (목적지 인덱스 destination index)

 

EIP

명령 포인터(Instruction Pointer)의 약자로 현재 실행 중인 메모리 위치를 나타냄
프로그램이 비정상 종료됐을 때 어느 위치를 실행하다가 발생한 것인지 찾는데 많이 참조하는 레지스터

 

ESP, EBP

현재 사용 중인 스택의 메모리 주소를 나타냄

ESP = 스택 포인터 Stack Pointer (항상 스택의 가장 위, 마지막 항목을 가리킴)

          Push, Pop 명령을 사용할 때 자동으로 갱신

EBP = 베이스 포인테 Base Pointer (항상 스택의 바닥, 스택의 기준점을 설정할 때 사용 )

 


이동 명령

 

MOV

값을 이동시킴

사용법 = mov dest, src 

dest = 목적지 

src  = 출발지

(뒤에 있는 것을 앞에다 저장)

**********************************************************

예제 1) MOV [ebp+var_C] 0Ah

[ebp+var_C] 의 공간(메모리)에, 0Ah (0, h 가 나와있으면 16진수 , 앞에 0이 있으면 16진수)

A = 10을 저장 (숫자 값은 바로 만들어냄)

[] 로 묶여 있으면 주소를 뜻함

**********************************************************

예제 2) MOV eax, [ebp+var_4]

[ebp+var_4]주소에 있는 값을 eax에 저장

eax -레지스터에 어떤 값을 저장한다 ==4byte 사용

[ebp+var_4] = ebp+var_4의 주소

원래 앞에 단위를 적어줌 단위에 맞는 값에 저장해야 함 

1byte = AL,

BYTE = 1byte

WORD = 2byte

DWORD = 4byte

그래서 mov eax, [ebp+var_4]는 mov eax, dword ptr [ebp+var_4]로 사용하는 것이 맞음

ptr 은 주소를 뜻함 (해석 - 메모리에서 cpu레지스터로 저장한다)

메모리에서 메모리 저장은 불가능

메모리 ->cpu ->메모리로 가능

**********************************************************

C 코드 

int num =1 , num2;

num2 = num;

 

상위의 C 코드를 어셈블리로 변환 과정 예제

참고 : (num의 주소 = 100) (num2의 주소= 200)

{

mov dword ptr [주소를 적어줌 ebp+4 --(int는 4byte이기 때문) ] 1h 아니면 01 (16진수로 1)

(메모리에 cpu값을 넣어줌)

mov eax, dword ptr[ ebp+4 ] -- 100번지부터 4byte에 있는 값만큼을 eax에 넣어줌

(메모리 값을 cpu에 넣어줌)

mov dword ptr [ebp+8 ], eax

참고 : 실제로는 +8이 아니라 - 로 쓰임 (Stack 구조는 위로 쌓이는 구조) 바닥에서 얼마만큼 떨어져 있나

ebp는 베이스 포인터 (바닥) 가장 큰 수임

}

상위 예제로 알 수 있는 어셈블리 특징 : 메모리에서 메모리로 Mov는 안된다.

 

 

MOVZX

확장 MOVE
MOVE를 하는데 0을 확장시켜서 이동함

사용법 = MOVZX dest, src

작은 공간에 있는 것을 큰 공간으로 옮길 시 남은 공간을 모두 0으로 채워버림

MSB의 경우 앞의 남은 공간이 모두 0으로 되어 버리니 MSB가 무조건 0이 되어 양수가 됨

 

특징 1) 즉 부호 없는 정수에만 사용이 됨 (무조건 양수) 

 

unsigned short ( 2byte) -> unsigned int (4byte)로 이동할 시 자주 사용됨

 

특징 2) 목적지는 반드시 레지스터가 되어야 함

특징 3) 보통 cpu가 값을 가져가면서 형 변환하기 위해 사용

 

MOVZX 사용 예제)

원래는 코드로는 강제형 변환시켜서 변수를 맞추어줘야 하지만 그냥 예로 사용

unsigned short a =0;  //2byte

unsigned int = sum;   //4byte

sum = a+10;

(보통 정수끼리는 자동형 변환이 됨(컴파일러가 알아서 함))

이럴 경우 어셈블리에서 movzx사용

 

MOVSX

MOV를 확장하는데 부호가 있는 값을 확장함

movzx보다 많이 쓰임

작은 바이트에 있는 값의 부호가 무엇이냐에 따라 다름

맨 앞 msb 가 1이면 남는 공간을 모두 1로 채움

0이면 0

 

 

MOVS

S = string (문자열)
ESI = 시작 주소 (주소가 저장됨)
EDI = 목적지 주소
문자열 저장에 사용됨
보통 반복문 (rep)와 같이 조합해서 쓰임
esi 의 값을 edi로 이동시킴 (두 개다 값을 가리키고 있어야 함)

 

JMP

C의 goto 와 같은 역할로
지정된 위치로 이동
예) jmp 0x0011 = 0x0011로 이동
JMP의 하위 개념으로 다양한 종류가 있다.

JE = 좀 전에 수행한 비교문 (cmp) 결과가 같을 때만 점프

 

JA = 좀 전에 수행한 비교문 (cmp)에서 앞의 것이 클 때만 점프

 

JB = 좀 전에 수행한 비교문 (cmp)에서 뒤의 것이 클 때만 점프

 

JE, JA, JB는 CMP와 연관이 있음

 

 

PUSH

스택에 데이터를 넣음

PUSH 명령을 수행하고 나면 스택에 데이터가 추가되어서 

스택이 쌓여있다고 생각하면 ESP가 위로 하나 증가한다. 

즉 ESP = ESP - 4 

그리고 스택이 하나 위로 증가한 위치에 PUSH ????  의 ???? 값을 저장한다.

[ESP - 4 ] = ???? 저장 (리틀앤디안 방식으로 저장) 

 

참고 개념)

PUSH EBP를 하게 되면

현재 함수에서 다른 함수로 이동한다고 생각하면 된다.

다른 함수에서 다시 현재 함수로 돌아올 때 

현재 함수의 스택 바닥 위치를 알아야 하기 때문에

함수를 이동할 때 항상 PUSH EBP를 하여 다시 돌아올 스택 위치를 저장해놓는다.

 

POP

스택에 데이터를 꺼냄

POP명령을 수행하고 나면 스택에 데이터가 없어져서

ESP가 아래로 하나 감소한다.

즉 ESP = ESP + 4

그리고 POP EAX 라면

스택에서 꺼낸 값을 EAX에 저장한다.

 

POP을 수행하고 나면

스택에서 값을 빼는데 

그 뺀 값을 EAX에 저장

 

참고 : Byte Ordering

Byte Ordering - 디스크 메모리에 저장 읽기 쓰기 방식을 결정하는 것

-big Endian = 값을 그대로 저장

-little Endian = 1byte 단위로 쪼개서 뒤에서부터 저장(0012ff7c -> 7cff1200 ) 형태로

int num =10;

int *p;

p = #

*p = 20;

이걸 어셈블리어로.

num = [ebp-4]

p = [ebp-8]

mov   dword ptr [ebp-4], 0a

lea  ebx , dword ptr [ebp-4]

mov  dword ptr [ebp-8], ebx

sum = num+10

 


연산자  피연산자, 피연산자

 

ADD

값을 더한다

같은 크기끼리 계산한다

항상 같은 동일한 크기로 만든 다음 계산해야 한다

add   dst   src          (dst = 목적지, src = 출발지)

c의 += 와 비슷하다

왼쪽의 값에 오른쪽의 값을 더한다.

dst에 src의 값을 더한다

 

SUB

값을 뺀다

add와 동일하게 dst에서 src를 뺀다

결과는 dst에 저장  ( -= )

sub dst src

 

MUL 

부호 없는 곱셈

3가지 형태가 있음

1. mul oper  

2. mul oper1, oper2

3. mul oper1, oper2, oper3

 

 

MUL oper  

((피연산자가 하나)) //곱하기는 하는데 eax의  a (첫 번째 레지스터)에 *oper

값이 넘어가면 d에 저장(edx)  --2byte*2byte 인 경우 (자릿수가 커지면 ) 4byte가 넘어갈 경우

mul cl   // al * cl =   ((al 은 al,ah 의 1byte 말하는 것))

1byte = al, bl ...or ah, bh..

2byte = ax, bx ..

4byte = eax, ebx ..

mul cl  // al*cl = ah :al  즉(ax)

1byte 최고 값 = 255

255 =al

cl = 10

al*cl = 255*10 =2550 (1byte가 넘어감 ) overflow값

ax * cx =  dx(상위 16bit) : ax (하위 16은)

mul ecx //eax * ecx   = edx (상위 32bit) : eax (하위 32bit)

곱하기는 operate가 하나일 땐 operate의 byte크기에 따라 eax, ax, al 이 결정됨

확장을 염두해서 각 크기에 확장까지 고려해 저장 즉 eax -> edx도

2550을 2진수로 바꿈 (가장 가까운 게 2의 11승인 2048)

총 12개 비트가 나옴  1001 1111 0110

부족한 비트는 0으로 체음

0000  1001  1111 0110

    ah               al

           즉 ax

 

 

MUL oper1, oper2

곱하기의 피연산자가 두 개일 경우

mul bx, cx // bx *= cx  (add와 같음)

mul ebx, ecx // ebx *= ecx

값이 넘 칠경 우 해당 byte만 사용하고 상위는 버림

 

MUL oper1, oper2, oper3

곱하기의 피연산자가 3개인 경우

mul eax, ebx, 10 //eax 가 저장장소(eax = ebx *10 )

동일하게 저장장소가 4byte니 결과가 4byte가 넘으면 상위는 버림 (피연산자가 2개인 경우)

하나인 경우는 확장 대는 듯

즉 edx는 0으로 채워짐

 

IMUL

부호 있는 곱셈

음수 값을 유지하기 위해 사용

상위가 넘어갈 시 모두 f로 채움

넘치는 값도 처리하기 때문에 오버플로우 처리하지 않음

버리는 값이 없기 때문에 오버플로우가 아님

 

DIV

1. 나누기(DIV)
보통  피연산자 1개 사용
d와 a의 레지스터 모두 사용
dx  =0 (16bit)        0000 0000 0000 0000
ax = 8003h(16bit)     1000 0000 0000 0011    (32771)
cx = 100h             0000 0001 0000 0000    (256)
결과
ax -몫
dx -나머지

2. 나머지 (mod)

모두 명령어는 div인데 어떤 값을 가져오냐에 따라 나누기인지 나머지인지 결정됨

dlv cl  // ax :al / cl  ==몫 = al  나머지 = ah

div cx  // dx : ax  / cx  ==  몫 = ax 나머지 dx

div ecx // edx : eax /ecx == 몫 = edx  나머지 edx

즉 피연산자가 하나면 a에서 나눔

몫 = a ,, 나머지 =d

처음에 dx공간을 비워놓아야 함

 

IDIV

부호 있는 나누기

DIV와 동일하지만

CBW, CWD, CDQ 가 있음

cbw - convert byte to word

cwd - convert word to doble word(DWORD)

cdq - convert dowrd to qword(8byte)

를 이용해 부호부터 확장을 시킴

1byte- ah

2byte - dx

4byte - edx 가 더해지며 확장

음수면 모두 1로

양수면 모두 0으로 확장이 채워짐

 

CMP

주어진 두 값을 비교함
비교한 결과가 같으면 ZF (zero flag)라는 특별한 영역을 1로 설정
C언어의 strcmp, memcmp와 같이 두 값이 같을 때 -을 리턴한다고 생각하면 됨

 

INC

증가
c의 ++ 와 같음

 

DEC

감소
c의 --와 같음

 

AND

c의 &연산

and 연산

사용법 - AND dst, src (비트 연산임)

dst & src

결과값 dst에 저장

선택된 비트를 유지하고 나머지를 0으로 초기화할 때 사용

 

OR

or연산

or dst, src

c의 | 연산

and와 동일 표기

선택된 것을 초기화하고 나머지를 유지할 때 사용 (and와 반대)

 

XOR

xor연산

xor dst, src

c의 ^ 연산

마찬가지로 dst에 저장..

암호화에 사용됨

패리티 점검에 사용

 

RETN

의미는  pop EIP와 같다

즉 pop eax는

mov eax, dword ptr [esp]

add esp, 04      --esp가 내려간다

 

 


 

제어문

 

swtich case 문

1. case의 break가 있나 없나에 따라 다름

2. switch의 변수에 따라 다름

 

스위치용 스택을 하나 만들어

그곳에 switch( ? )의 ? 값을 넣어 비교하기 때문에

이유를 알 수 없는 스택이 하나 더 생긴다.

스위치의 경우 자기가 사용하기 위해 변수 공간을 따로 더 만듦

코드를 쭉 보는 데 공간이 남아있다면 switch문이 있는지 확인

비교, 점프, 비교, 점프 가 연속적으로 있으면 스위치일 가능성이 매우 큼(조건 je, jz사용)

******************************************************************************************

어셈 예제 1) : case의 개수가 3개 이하면

move eax , 변수

mov 임시 변수, eax(스위치가 만든 임시 변수)

cmp 임시 변수 , 변수값 1(case 1에 해당 값)

je 코드 1의 주소

cmp 임시 변수, 변수값2

je 코드 2의 주소

cmp 임시 변수 ,변수값3

je 코드 3의 주소

jmp 코드 4의 주소

코드 1

jmp 메인코드 (만약 case 끝에 break; 가없으면 점프 없음) 상위의 (1. case의 break가 있나 없나 에 따라 다름) 해당하는 내용

코드 2

jmp 메인코드

코드 3

jmp 메인코드

코드 4

메인코드

******************************************************************************************

3. case의 개수에 따라 다름 :  4개 이상, case의 차이 값(제일 큰 case 값 - 제일 작은 case값)이 8 이하면 (개수로 생각하면 9개)- 0~8

스위치의 경우 다른 스택 테이블에 저장했다가 불러오는 형식으로

점프 비교를 해서 까다로움 (switch table 부분)

 

case가 실행될 case의 주소를 배열로 만들고 switch문에서 쓰는 변숫값은 배열의 첨자로 이용.

- swtich table을 생성

switch문에서 쓰는 변수는 swtich table의 첨자로 이용이 됨.

즉 case의 주소를 배열로 만듦 (데이터 영역에)

switch(n)의 n 은 임의의 공간에 임시로 저장함.
(switch용 임시 값)--case에 해당하는 주소에 접근하기 위한 첨자(index) 값

배열이 4개면 첨자가 4개 있는 것.

첨자 값이 배열의 값보다 크게 되면 default로 점프하게 됨.

첨자 값이 맞으면 기준이 되는 주소에 계산을 하게 됨.

[ edx*4 + 기준주소 ] ->이게 배열이 저장되어있는 주소를 뜻하게 됨.

즉  [ edx*4 + 기준 주소 ]는 int *p [4]와 같다  기준 주소 = p   edx*4 = p+? 일 때? 와 같음.

(edx*4 의 4는 자료형의 크기) 주소는 무조건 4바이트이기 때문에 *4

무조건 case의 개수만큼 만들어지는 것은 아님 switch의 경우에 따라 모두 다름.

만약 case의 차이가  1 ,2 3, 4, 9일 경우는 배열이 총 9개가 만들어지는데 5,6,7,8, 의경우는 모두 가리키는 주소가 같음.

그리고 이 주소는 default 아님 main문을 가리킨다.

case의 개수가 4개 이상, case의 차이 값이 9 이상이면

실행될 case의 주소를 배열로 만들고 첨자 번호 또한 배열로 만들어서 주소에 접근

switch문에서 쓰는 변수값은 index배열의 첨자로 이용됨

index배열에서 찾은 값은 switch table의 첨자로 이용이 됨

  

테이블을 만드는 이유 : 더 빠르게 접근하기 위해서

 

if문  

조건 점프 (jg, jge, jl jle)의 경우 if문의 가능성이 큼

보통 어셈블의 jmp코드의 반대가 c의 if문이라고 보면댐

예로 jg 이면 s가 0일 경우 점프 즉 cmp의 왼쪽 것이 더 큰 경우 점프라는 건데

이것을 c의 if로 바꾸어보면   ->   왼쪽 <= 오른쪽   임

즉 어셈블러 상에서는

cmp oper1 oper2

조건 점프(jg, jl , jge, jle) c에서 조건이 거짓인 경우 점프할 주소(메인)

c에서 참일 경우 실행할 코드

 

어셈블리 예제)

*********************************************

1. 단순 if

c언어                    어셈

if(조건문)                cmp oper1, oper2

{                        조건 점프 주소

코드 1                    코드 1

}                        메인코드

메인코드

*******************************************

2. if else 문

c언어                   어셈

if(조건문)              cmp oper1, oper2

{                       조건 점프 주소(코드 2의 주소)

코드 1                   코드 1

}                       jmp(무조건 점프) 메인코드 주소

else                    코드 2

{

코드 2                   메인코드

}

메인코드        

******************************************

3. else if

c언어                       어셈

if(조건문 1)                 cmp oper1, oper2(조건문 1)

{                          조건 점프 주소 (else if주소 즉 조건 2 주소)

코드                        코드 1

}                           jmp(메인코드 주소)

else if(조건문 2)             cmp oper1, oper2(조건문 2)

{                           조건 점프  (else 주소)

코드 2                        코드 2

}                           jmp(메인코드)

else                        코드 3

{                           메인코드

코드 3

}

메인코드

 

 

반복문

******************************************************

while

cmp비교

조건 점프(jg, jl, je, jz, jge, jle) 메인코드 주소

코드

무조건 점프(jmp) 조건문 위의 (위의 cmp) 주소

메인 코드

*******************************************************

do while

코드 1

cmp비교

조건 점프(jg, jl, je, jz, jge, jle) 코드 1의 주소

메인코드

********************************************************

for

초기값

jmp 조건식

증감식

조건식 cmp

조건 점프(jg, jl, je, jz, jge, jle) 메인

코드

jmp 증감식

메인코드

 

함수 Call

전달 인자(파라미터) parameter 전달의 푸시 순서는 마지막 것부터 푸시함

(Caller : 함수 호출하는 놈)

- 함수를 호출하기 전에 Caller는 ESP부터 인자 값을 전달해서 스택에 쌓는다

- 스택에 쌓을 때는 오른쪽에 있는 전달 인자부터 넘겨준다

 

********************************************************

어셈 예제)

Func(1,2,3,4,5)

push 5

push 4

push 3

push 2

push 1

call Func주소

(실제로 call은 두 개의 어셈블러가 합쳐져 잇다 생각하면 편함) -- push eip , jmp Func의 주소

caller 기준  (호출하기 전)

첫 번째 인자 : 원래 함수의(Caller의) ESP   - 호출하는 놈의 입장

두 번째 인자 : 원래 함수의(Caller의) ESP+4

세 번째 인자 : 원래 함수의(Caller의) ESP+8

네 번째 인자 : 원래 함수의(Caller의) ESP+c

다섯 번째 인자 : 원래 함수의(Caller의) ESP+10

********************************************************

 

@Parameter 참조(Callee : 함수 호출당한 놈)

- Caller가 나를 호출하기 전에 ESP 위치부터 순서대로 쌓아줬음

- 호출당한 후에는 나의 Caller의 ESP가 Callee EBP가 댐

push EBP

mov EBP, ESP

callee 기준

첫 번째 인자 : EBP+8

두 번째 인자 : EBP+c

세 번째 인자 : EBP+10

네 번째 인자 : EBP+14

다섯 번째 인자 : EBP+18

콘솔의 매인 함수 호출 전 push=

첫 번째 push = envp  (환경설정)

두 번째 argv

세 번째 argc


 

기계어 표 (OpCode Table)

https://pnx.tf/files/x86_opcode_structure_and_instruction_overview.png

 

 

 


제 글을 복사할 시 출처를 명시해주세요.
글에 오타, 오류가 있다면 댓글로 알려주세요! 바로 수정하겠습니다!


 

 

 

반응형