어셈블리어 (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 사용법 = 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 (문자열) |
JMP
C의 goto 와 같은 역할로 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) 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
주어진 두 값을 비교함 |
INC
증가 |
DEC
감소 |
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 은 임의의 공간에 임시로 저장함. 배열이 4개면 첨자가 4개 있는 것. 첨자 값이 배열의 값보다 크게 되면 default로 점프하게 됨. 첨자 값이 맞으면 기준이 되는 주소에 계산을 하게 됨. 즉 [ 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
제 글을 복사할 시 출처를 명시해주세요.
글에 오타, 오류가 있다면 댓글로 알려주세요! 바로 수정하겠습니다!