어셈블리언어
어셈블리언어란?
기계어를 사람이 알아보기 쉬운 니모닉 기호(mnemonic symbol)를 사용해 1:1 대응이 되는 컴퓨터 프로그래밍의 저급 언어이다.
* 기계어: 컴퓨터가 읽을 수 있는 2 진 숫자(Binary digit, 0 과 1)로 이루어진 언어.
* 니모닉 기호: 기계어로 된 일련의 숫자를 알아보기 쉽게 언어(단어)로 바꿔준 것.
- IA-32 레지스터(32bit) // IA -64 레지스터 (64bit) 는 E 대신 R이 붙음
EAX : 누산기 , 산술·연산저장 , 리턴값
EBX : 간접주소지정, 산술·연산저장
ECX : 반복카운터(차감) , ECX > 0
EDX : EAX 보조역할 //리턴값이 32bit수를 넘어갈 때 나머지를 EDX에 저장
EBP : 스택프레임 기준
ESP : 스택프레임 최상단
ESI I: 출발지(Source) 주소를 담는 공간
EDI I: 목적지(Destination) 주소를 담는 공간
EIP I: 다음 실행될 명령어의 주소를 담고 있는 레지스터
EFLAGS : [상태값 : 0(clear), 1(set)], AF,CF,OF,PF,SF,ZF
ZF: 산술연산결과가 0일 때 1로 set 되는 flag
ex) 1+1 = 2 ZF : 0
1-1 = 0 ZF : 1
- 데이터 형식
BYTE | 부호 없는 1 byte |
WORD | 부호 없는 2 byte |
DWORD | 부호 없는 4 byte |
- INC //operand의 값 1 증가
#include<stdio.h>
int main(){
int a=0;
_asm{ //_asm{} -> 인라인 어셈블러
INC a //or INC [EBP-0x04]
}
printf("%d\n",a);
return 0;
}
- DEC //operand의 값 1 감소
#include<stdio.h>
int main(){
int a=1;
_asm{
DEC a //or DEC [EBP-0x04]
}
printf("%d\n",a);
return 0;
}
- ADD //덧셈
#include<stdio.h>
int main(){
int a=0;
_asm{
ADD a, 0x05
}
printf("%d\n",a);
return 0;
}
- SUB //뺄셈
#include<stdio.h>
int main(){
int a=1;
_asm{
SUB a, 0x01
}
printf("%d\n",a);
return 0;
}
- MOV //복사,대입
#include<stdio.h>
int main(){
int a=0;
_asm{
MOV a, 0x02
}
printf("%d\n",a);
return 0;
}
ex) 예제문제
#include<stdio.h>
int main(){
int a,b,c,d;
a=b=c=0;
_asm{
MOV a , 0x03 // a주소에 3 대입
MOV b , 0x05 // b주소에 5 대입
MOV c , 0x0A // c주소에 10 대입
INC [EBP-0x08] // b주소에 1 증가
DEC [EBP-0x0C] // c주소에 1 감소
MOV EAX,[EBP-0x08] // EAX에 b주소에 있는 값 대입
ADD EAX,[EBP-0x0C] // EAX(b) , c주소에 있는 값 두 개 더해서 EAX에 대입
MOV [EBP-0x04],EAX // a주소에 EAX값 대입
MOV EBX,[EBP-0x04]
SUB EBX,[EBP-0x0C]
MOV [EBP-0x08], EBX
}
printf("%d\n",a);
printf("%d\n",b);
printf("%d\n",c);
return 0;
}
- AND
#include<stdio.h>
int main(){
int a=10;
_asm{
AND a, 0x06
}
printf("%d\n",a);
return 0;
}
- OR
#include<stdio.h>
int main(){
int a=20;
_asm{
OR a, 0x0C
}
printf("%d\n",a);
return 0;
}
- XOR
#include<stdio.h>
int main(){
int a=12;
_asm{
XOR a, 0x08
}
printf("%d\n",a);
return 0;
}
- SHL & SHR //bit 이동
#include<stdio.h>
int main(){
int a=4;
printf("%d\n",a);
_asm{
SHL a, 0x01 //×2
}
printf("%d\n",a);
_asm{
SHR a, 0x02 //÷2
}
printf("%d\n",a);
return 0;
}
- PUSH & POP //push 한 만큼 pop 해야함
#include<stdio.h>
int main(){
int a=0;
_asm{
push a
push 0x10
push eax
pop eax
pop a
pop ebx
}
printf("%d\n",a);
return 0;
}
- PUSHAD //EAX->ECX->EDX->EBX->ESP->EBP->ESI->EDI , PUSH만 했기 때문에 에러남!
#include<stdio.h>
int main(){
int a=0;
_asm{
PUSHAD //범용 레지스터의 값들을 스택에 저장 = 백업하는 용도
}
printf("%d\n",a);
return 0;
}
- POPAD
#include<stdio.h>
int main(){
int a=0;
_asm{
POPAD //스택의 데이터를 범용 레지스터에 채움
}
printf("%d\n",a);
return 0;
}
- PTR //데이터 타입 재정의
#include<stdio.h>
int main(){
int a = 1;
printf("%x\n",a); //1
_asm{
MOV WORD PTR a, 0x11111111
}
printf("%x\n",a); //1111
_asm{
MOV DWORD PTR a, 0x11111111
}
printf("%x\n",a); //11111111
return 0;
}
- OFFSET //전역변수 주소값 구하기
#include<stdio.h>
int g=10;
int main(){
int addr = 0;
int val = 0;
_asm{
MOV EAX, OFFSET g //데이터 세그먼트(영역) 시작부터의 상대적 거리값
MOV addr , EAX
MOV EBX, [EAX]
MOV val, EBX
}
printf("%x\n",addr);
printf("%x\n",val);
return 0;
}
- LEA //주소값 구하기
#include<stdio.h>
int main(){
int a = 4;
printf("%.8x\n",a);
printf("%.8x\n",&a);
_asm{
MOV EAX, [EBP-0x04]
MOV a, EAX
}
printf("%.8x\n",a);
_asm{
LEA EAX, [EBP-0x04]
MOV a, EAX
}
printf("%.8x\n",a);
return 0;
}
- STOS // EDI가 가르키는 주소에 EAX 값을 저장
#include<stdio.h>
int main(){
char Buffer[20];
_asm{
MOV EAX, 0x00
LEA EDI, DWORD PTR [Buffer]
STOS DWORD PTR [EDI]
}
printf("%s\n",Buffer);
return 0;
}
- REP //Repeat , STOS,MOVS,SCAS 에서 사용가능
#include<stdio.h>
int main(){
char Buffer[20];
_asm{
MOV EAX, 0x00
LEA EDI, DWORD PTR [Buffer]
MOV ECX, 0x05
REP STOS DWORD PTR [EDI]
}
printf("%s\n",Buffer);
return 0;
}
- MOVS //ESI가 가리키는 곳에 값을 EDI가 가리키는 곳에 복사(대입)
#include<stdio.h>
#include<string.h>
int main(){
char * str1 = "Hello~ world!!!";
char str2[20];
int Len = strlen(str1);
_asm{ //strcpy 구현
MOV ESI, DWORD PTR [str1]
LEA EDI, DWORD PTR [str2]
MOV ECX, Len
REP MOVS BYTE PTR [EDI], BYTE PTR[ESI]
MOV BYTE PTR [EDI], 0x00
}
printf("%s\n",str2);
return 0;
}
- TEST //묵시적 AND 연산 , OPRAND에는 영향을 미치지 않음 , ZF:0
#include<stdio.h>
int main(){
int a = 3;
_asm{
TEST a, 0x02
//ex) TEST EAX, EAX 리턴값이 NULL이 아닌지 확인
}
printf("%d\n",a);
return 0;
}
- CMP //내부적으로 - 연산
#include<stdio.h>
int main(){
int a = 5;
_asm{
CMP a, 0x05 //OPRAND에는 영향을 미치지 않음
//CF:부호없는 산술연산 결과(음수 포함)가 너무 커서 담을 수 없을 때 1로 set 되는 flag , ZF:0
}
printf("%d\n",a);
return 0;
}
ex) CMP 5, 6 = -1 ZF:0 CF:1
ex) CMP 6, 5 = 1 ZF:0 CF:0
ex) CMP 5, 5 = 0 ZF:1 CF:0
- JMP//지정한 위치 이동에 사용
#include<stdio.h>
int main(){
int a = 5;
_asm{
JMP L1
MOV a, 0x09
L1: //레이블은 인라인 어셈 안이나 밖이나 사용 가능
MOV a, 0x01
}
printf("%d\n",a);
return 0;
}
- 조건 점프 명령
JA |
CMP a > b |
JB |
CMP a < b |
JE |
CMP a == b |
JNE |
CMP a != b |
JZ |
TEST EAX, EAX (EAX = 0), ZF = 1 |
JNZ |
TEST EAX, EAX (EAX = 1), ZF = 0 |
JAE |
CF = 0 and ZF = 0 |
JBE |
CF = 1 and ZF = 1 |
JC |
CF = 1 |
JCXZ |
CX = 0 |
JECXZ |
ECX = 0 |
JG |
ZF = 0 and SF == OF |
- call //레이블은 인라인 어셈 안이나 밖이나 사용 가능
#include<sdtio.h>
int sum(){
int a = 0;
int b = 2;
printf("Result\n");
return a + b;
}
int main(){
int result =0;
printf("%d\n", result);
_asm{
CALL sum
MOV result, EAX
}
printf("%d\n",result);
return 0;
}
- RET (== RETN) //ESP 레지스터가 가리키는 값을 EIP 레지스터에 저장
호출한 함수가 명령을 마치고 돌아갈 주소를 지정한 공간이다.
인자를 정리할 때 사용한다. * RET imm8 = RET 0~255
ex) RET 8 -> ESP 를 8bit 만큼 증가
- NOP //아무 일도 하지 않는 명령어
1byte 의 크기를 가지며 16 진수로 0x90 이다.
명령어 사이의 빈공간을 채워주는 역할을 한다.
- 바이너리 디버거 다운로드
OllyDBG http://www.ollydbg.de/
Immunity DBG http://debugger.immunityinc.com/
IDA Pro https://www.hex-rays.com/products/ida/support/download.shtml
WinDBG https://msdn.microsoft.com/ko-kr/windows/hardware/hh852365.aspx