어셈블리어 튜토리얼 (1) 설치/기초

1. 시작

이 글의 목적은 어셈블리를 이용해 복잡한 프로그램을 짜는것이 아니고..

어디까지나 리버스 엔지니어링에 필요한 최소한의 어셈블리를 익히는 것이다.

해서 기초적인 어셈블리만 소개하도록 하겠다.

물논 복잡한 어셈블리는 나도 모름. ㄲㄲ

어셈블러는 MASM(The Microsoft Assembler) 으로 한다. NASM, FASM등 다른 어셈블러도 많지만..

나의 첫 어셈블러책이 MASM 으로 시작해서 어쩔수가 없이 MASM을 사용한다. ㅋㅎ..

더우기 64비트 프로그램을 생각하면 다른 어셈블러를 사용하는게 좋다.

앞으로 설치할 MASM32 는 64비트를 지원하지 않는다. 하핫.

뭐.. 어셈블러라는게 어차피 명령어 하나하나가 기계어 1:1 대응이라 어떤 어셈블러를 이용하든 큰차이는 없다.

push ebp
mov ebp, esp
sub esp, 20

이런 명령어에서 크게 벗어나지 않는다. 그냥 어디까지나 편의성의 문제이다.

1.1 설치/설정

먼저 MASM32를 설치하자. 다음 링크에서 MASM32 (http://www.masm32.com) 다운받고 실행하면된다.

설치는 다운받은 masm32v11r.zip 압축파일을 풀고 install.exe 를 실행하고 그냥 확인~ 확인~ 확인~을 누르면된다. 나의 경우 설치 위치는 C:로 선택했다.

설치가 완료되면 C:\masm32 가 생겼을 것이다. 폴더안에 많은 파일이 있지만 다 무시해도 되고

bin폴더 (ml.exe link.exe) include폴더 lib폴더만 이용할 것이다.

tutorial폴더에 튜토리얼도 있다.

1.2 기초명령어

어셈블리 명령어가 그렇게 많지 않다. 그중에서도 자주쓰이는 명령어는 매우 한정적이다.

굵은 글씨의 명령어가 자주 쓰이는 명령어라고 생각하면된다.

mul(곱셈), div(나눗셈), xchg(교환) 등등 이런거 튜토리얼 작성때말고 써본적 없다..

mov : 값복사 (친구들 movsx, movzx)

lea : 주소복사

add : 덧셈

sub : 뺄셈

dec : 감소

inc : 증가

cmp : 비교

jmp : 점프 (친구들 je, jne, jz, jnz, jg, jl, jge, jle, jmps)

call : 함수 호출

ret : 함수 리턴

push : 스택에 push

pop : 스택에 pop

nop : 놉놉. 아무것도 하지 않는 코드

loop : 루프 반복

shl : 비트 연산 <<

shr : 비트 연산 >>

mul : 곱셈

div : 나눗셈

and, or, not, xor : 비트연산

xchg : 값 교환

rep, repnz : 반복

pushad : 모든 레지스터 스택에 push

popad : 모든 레지스터 스택에서 pop

movsb : byte 복사

movsw : word 복사

movsd : dword 복사

int : 인터럽트

1.3 기초레지스터

모든 레지스터는 32bit 프로그래밍 기준으로 32bit(4byte)의 크기를 갖는다.

레지스터는 eax, ebx, ecx, edx, esi, edi 6개의 일반 레지스터와 ebp, esp 2개의 스택관련 레지스터가 있다.

6개의 일반 레지스터는 그냥 6개의 4byte 저장소라고 보면 된다.

그외에 명령어를 사용할때 각각의 역할이 있기도하다.

예를들어 loop명령어를 쓸때 ecx에 반복할 횟수를 넣어서 사용한다든지..

mul명령어(곱셈)를 쓸때 eax에 값을 넣어서 곱셈을 한다든지.. 이런저런 역할이 있다.

그리고 이런 역할이 레지스터 이름에 관계된 역할이기도 한데..

별로 큰의미는 없다. 그냥 가운데 알파벳 a, b, c, ds, d 만 기억해서 6개의 레지스터만 기억하면 될듯하다.

eax : accumulator 레지스터
ebx : base 레지스터
ecx : counter 레지스터
edx : data 레지스터
esi : source 레지스터
edi : destination 레지스터

ebp : base 스택 레지스터
esp : 스택 레지스터

스택관련 레지스터 ebp, esp는 직접 조작할일은 거의 없다.

스택메모리 주소값을 가지고 있다 정도만 이해하고 있으면 된다.

그리고 중요하다면 중요할 수 있는 eip 레지스터가 있다.

현재 실행되는 위치를 나타내는 레지스터다.

eip : 0040100C

주소     | 기계어                    | 어셈블리
0040100C | 55                       | push ebp
0040100D | 8B EC                    | mov ebp,esp
0040100F | 83 C4 F8                 | add esp,FFFFFFF8

이런 어셈블리로 설명을 하면 현재 위치는 0040100C 이다.

push ebp 가 실행되고 나면

eip는 다음 명령어 위치인 0040100D 가 된다.

mov ebp,esp 가 실행되고 나면

eip는 0040100F 가 된다.

eip 레지스터는 이런식으로 명령어가 실행될 위치를 가르키며 명령어에따라 순차적으로 실행된다.

eip를 수정해서 다른 위치의 명령어를 바로 실행 할 수도 있다.

여기서 한가지 더 덧붙이자면 모든 어셈블리는 기계어로 1:1 대응이 된다고 설명했다.

push ebp 는 55 (1byte) 로 mov ebp, esp 는 8B EC (2byte) 이렇게 변환된다.

이 기계어라는 것을 설명하면

exe실행 파일로 변환했을때 실제 파일에 55 8B EC 83 C4 F8 이렇게 써져있는 것이다.

exe실행 파일이 실행될때 컴퓨터는 55 8B EC 83 C4 F8 이것을 해석해서 실행하는 것이다.

그리고 아시다시피 해석된 명령어는 기계어에 대응되는 아래의 어셈블리가 되겠다.

push ebp
mov ebp,esp
add esp,FFFFFFF8

1.4 데이터타입

byte : 1byte 크기의 데이터
word : 2byte 크기의 데이터
dword : 4byte 크기의 데이터

c프로그래밍의 데이터타입으로 표현하자면

byte = char
word = short
dword = int

위에서 모든 레지스터의 크기는 32bit(4byte)라고 하였다. 즉, 모든 레지스터의 데이터타입은 dword 이다.

그리고 레지스터는 16bit(2byte) 8bit(1byte) 로 하위값을 쪼개서 표현할 수 있는데

eax를 예를 들면

eax는 dword

ax는 하위 word

al은 하위 byte

ah도 상위 byte <- 무시해도 된다.

이렇게 4byte의 일부분만 사용할 수 있다.

구체적으로 예를 들면

eax에 [12 34 56 78] 라는 4byte 값을 가지고 있다고하면

ax는 [56 78]

al는 [78]

ah는 [56] <-무시해도 된다. 최하위 byte가 아닌 ax의 상위 byte를 가르킨다.

이렇게 사용할 수 있다. 거의 사용할 일 없고 드물게 사용된다.

ebxbx, bl, bh 가 있고 ecx, edx 모두 마찬가지다.

esi, edi는 하위 word인 si, di 만 있다.

1.5 명령어 사용방법

모든 명령어는 단순하게 실행된다.

1. 인자가 두개인 경우
명령어 대상, 소스

2. 인자가 하나인 경우
명령어 대상

3. 명령어만 있는 경우
명령어

이런식으로 3가지 방식으로 사용된다. 굉장히 간단하다.

1번의 경우 (인자가 두개인) 적용되는 방향은 소스 -> 대상 이렇게 된다.

대표적인 예로 mov를 들면

mov eax, 1
; eax레지스터에 1을 복사하라는 뜻이다. 실행하면 eax레지스터에 1이 들어가있다.

소스/대상에는 이렇게 값이 들어갈 수 도있고 메모리가 들어갈 수도 있고 레지스터가 들어갈 수도 있다.

mov eax, 1
; eax에 값 1을 복사
mov ebx, eax
; ebx에 eax를 복사. ebx레지스터에 1이 들어가있다.

mov dword ptr [00400000], 1
;메모리[00400000]에 값 1을 복사
mov ecx, dword ptr [00400000]
; ecx에 메모리[00400000]에 들어있는 값을 복사.

다만 명령어에따라 소스/대상 둘다 메모리가 될 수 없는 경우도 있다.

mov dword ptr [00400000], dword ptr [00800000]
; 메모리[00800000]의 값을 메모리[00400000]에 복사. 이 경우 에러가 난다.

mov eax, dword ptr [00800000]
mov dword ptr [00400000], eax
; 레지스터를 사용해서 이렇게 한번 거쳐서 복사한다.

메모리주소를 00400000, 00800000 같은 값으로 사용했는데 이 부분을 레지스터를 이용 할 수도 있다.

mov ebx, 00400000
mov ecx, dword ptr [ebx]
; ecx에 메모리[00400000]에 들어있는 값을 복사.

이렇게 레지스터를 사용할때 +값 -값을 넣어서 사용할 수도 있다.

mov ebx, 00400000
mov ecx, dword ptr [ebx + 00400000]
; ecx에 메모리[00800000]에 들어있는 값을 복사.

2번의 경우 (인자가 하나인) 마찬가지로 대상은 값, 레지스터, 메모리가 들어갈 수 있다.

jmp로 설명을 해보면

jmp 00401000
; 00401000주소로 점프~

mov eax, 00401000
jmp eax
; 00401000주소로 점프~

mov dword ptr [00800000], 00401000
jmp dword ptr [00800000]
; 00401000주소로 점프~

다음에는 디버거와 함께 스택메모리등 나머지 어셈블리 기초내용을 알아보겠다.

목차 이전글 어셈블리어 튜토리얼 (0) 목차