뉴비에욤

Shellcode 만들기 - 2 (시스템 및 프로그래밍 기본 사항) 본문

System Hacking(OS)/Shellcode

Shellcode 만들기 - 2 (시스템 및 프로그래밍 기본 사항)

초보에욤 2015. 9. 30. 23:59

시스템 및 프로그래밍 기본 사항

 

쉘코드의 동작 원리에 대한 설명에 앞서, 앞으로 이를 이해하는 데 필요한 기본적인 시스템의 메모리 구조 및 어셈블리 명령어, 그리고 프로그램 세부 동작 과정을 차례로 살펴본다.

 

 

 

1. 인텔(Intel) x86 32비트 레지스터

인텔 x86 아키텍쳐에서 제공하는 32비트 레지스터는 크게 범용(General-Purpose) 레지스터, 세그먼트(Segment) 레지스터, 명령어 포인터(Instruction Pointer), 플래그(Flag) 레지스터로 나누어 볼 수 있다.

 

 

먼저 범용 레지스터는 일반적인 연산이나 데이터 저장 등의 용도로 사용되는 4개의 일반 레지스터와(EAX,EBX,ECX,EDX), 간접 주소 지정 및 문자열 비교에 사용되는 2개의 인덱스(Index) 레지스터(ESI,EDI) 그리고 스택의 특정 위치를 가리키는 2개의 포인터(Pointer) 레지스터가(EBP,ESP) 있다.

 

4개의 일반 레지스터는 순서대로 EAX,EBX,ECX,EDX라 부르며 (사실 알파벳 순서는 아니지만 그냥 외우기 편하게 알파엣 순서로 생각), 하나의 목적을 가지고 사용하는 다른 레지스터와 달리 여러 가지 용도로 사용된다. 첫 글자의 'E'는 "Extended"의 약자로, 32비트 시스템으로 전환되면서 이전 16비트 시스템에서 사용되던 AX,BX,CX,DX 레지스터를 32비트로 확장했다는 의미이다. 각 16비트 레지스터들은 다시 상위(H, high) 비트와 하위(L, low) 비트로 나누어 진다. AX 레지스터를 예로 보면 AH,AL 각각 8비트씩 사용할 수 있다. 또한 각 레지스터는 범용 레지스터로서 여러 가지 용도로 사용되지만, 설계 목적에 따라 개별 레지스터가 갖는 의미는 서로 다르다.

 

 

명칭 

의미 

내용 

EAX 

Accumulator 

산술 논리 연산
(어셈블리어로 코딩 시)함수 리턴 값 저장 

EBX 

Base 

간접 주소 지정 

ECX 

Counter 

반복문 카운터 

EDX 

Data 

데이터 저장 

 

 

한편 ESI(Extended Source Index) 레지스터와 EDI(Extended Destination Index) 레지스터 => 2개의 인덱스 레지스터들은 문자열을 복사하거나 비교 등을 수행할 때 두 대상 문자열을 저장하기 위한 목적으로 설계 되었다.

 

또한 ESP(Extended Stack Pointer) 레지스터와 EBP(Extended Base Pointer) 레지스터 => 2개의 포인터 레지스터들은 각각 스택의 탑(Top, 꼭대기)와 바닥(Bottom)을 가리킨다.

 

이 외에도 프로세스의 특정 세그먼트를 가리키는 CS(Code Segment), DS(Data Segment), SS(Stack Segment), ES(이하 Data Segment), FS, GS 등 총 6개의 세그먼트 레지스터, 다음에 실행할 명령어의 주소를 가리키는 명령어 포인터(EIP, Extended Instruction Pointer) 레지스터, 그리고 프로그램의 상태 및 시스템 정보 등을 담고 있는 플래그 레지스터(EFLAGS)가 있다.

 

 

 

2. 기본 어셈블리 명령어

일반적으로 어세블리어 문법은 표기 방식에 따라 인텔 방식과 AT&T 방식으로 나뉜다. 리눅스의 개발에 직접적인 영향을 준 유닉스가 처음 AT&T에서 개발되었기 때문에, 오늘날 대부분의 리눅스 계열 시스템에서는 기본적으로 AT&T 방식을 사용한다. 그러나 대부분의 사람들은 표기법상 직관적으로 이해가 편리한 인텔 방식을 선호하는 편이다. (대부분의 어셈블리어/리버싱 서적이나 문서에도 인텔 문법을 많이 사용한다). 앞으로 설명에서도 대부분 인텔 방식을 사용하지만 AT&T 방식을 사용하는 경우에는 따로 명시 할 것이다. 서로 호환되지 않는 두 문법의 차이점을 간단히 나타내면 아래 표와 같다.

 

구분 

인텔 

AT&T 

명령어 

mov 

movb, movw, movl 

인자 순서

dest,src

src,dest 

레지스터  

eax 

%eax 

상수 

0h 

$0x0 

간접 주소 

[eax] 

(%eax) 

 

 

앞으로 진행될 설명은 프로그램 동작 원리와 그에 따른 스택의 변화 등 일반적으로 상위 단계(사용자 레벨)에서 파악하기 어려운 부분(커널 레벨)을 다루기 때문에 어셈블리 명령어에 대한 이해가 필수적이다. 기본적으로 어셈블리 명령어를 특징에 따라 분류하면 아래 표와 같다.

 

 구분

명령어 

데이터 이동 

mov, lea, push, pop 

산술 연산 

add, sub, inc, dec, cmp 

논리 연산 

neg, not, and, or, xor, shl, shr, test 

흐름 제어(분기) 

call, ret, jmp, jg, jl, je 

기타 

nop, int 

 

먼저 데이터 이동과 관련된 명령어 "mov"는 두 개의 인자를 갖는 대표적인 명령어로, 출발지(src, Srouce)의 인자를 도착지(dst, Destination) 인자에 복사한다. 또한 주소 이동과 관련된 명령어 "lea, Load Effective Address"는 출발지 인자의 주소를 도착지에 복사한다. 이처럼 인자가 두 개인 명령어에서는 대부분 연산 결과과 도착지 인자에 복사되는 경향이 있다.

 

 

반면 push, pop 명령어는 스택의 입출력과 관련된 명령어로, 모두 하나의 인자를 가지며 이를 각각 스택에 넣거나(push) 빼는(pop) 역활을 한다. 이 때 해당 명령어는 스택의 변화를 가져오기 때문에 스택의 꼭대기를 가리키는 ESP 레지스터의 위치가 변하게 된다. 이상 데이터 이동과 관련된 명령어를 정리하면 아래 표와 같다.

 

명령어 

표기 

동작 

mov 

mov dest,src 

dest := src 

lea 

lea dest,src 

dest := src 주소 

push 

push src 

dec sp,[sp] := src 

pop 

pop dest 

dest := [sp], inc sp 

 

 

 

산술 연산과 관련된 명령어는 두 개의 인자를 더하거나 빼는 add,sub, 하나의 인자를 1만큼 증가하거나 감소기키는 inc,dec 그리고 두 인자의 비교를 위해 사용되는 cmp 등이 있다. 이 중 cmp 연산은 내부적으로 두 안지에 대하여 sub(뺄셈) 연산을 통해 이루어 지며, 연산 결과의 부호(Sign, 양수/음수)에 따라 비교를 수행한다. 이상 산술 연산과 관련된 명령어를 정리하면 아래 표와 같다.

 

명령어 

표기 

동작 

add 

add dest,src 

dest := dest + src 

sub 

sub dest,src 

dest := dest - src 

inc 

inc op 

op := op + 1 

dec

dec op 

op := op - 1 

cmp 

cmp op1, op2 

op1 - op2 

 

 

논리 연산은 비트 단위로 이루어지는 연산을 말하며, 대표적으로 and,or,xor 명령어가 있다. 그 외에 2의 보수를 통해 부정(Negate)을 표현하는 neg 명령어, 각 비트를 반전(Invert) 시키는 not 명령어, 그릭 각각 왼쪽 또는 오른쪽으로 한 비트씩 이동(Shift)하는 shl(Shift Left), shr(Shift Right) 명령어 등이 있다. 이상 논리 연산과 관련된 명령어를 정리하면 아래 표와 같다.

 

명령어 

표기 

동작 

neg 

neg op 

op := 0 - op 

not 

not op 

op := ¬ op

and 

and dest, src 

dest := dest ∧ src 

or 

or dest, src 

dest := dest ∨ src 

xor 

xor dest, src 

dest := dest (ex-or) src 

shl 

shl op, quantity

op := op << quantity 

shr 

shr op, quantity 

op := op >> quantity

 

 

흐름 제어 명령어는 함수 호출이나 분기문 등 명령어의 실행 흐름을 변경하는 명령어로, 함수를 호출하는 call, 함수 실행을 마치고 돌아가는 ret, 분기를 수행하는 jmp 등이 있다. 이상 흐름 제어와 관련된 명령어를 정리하면 아래 표와 같다.

 

명령어 

표기 

동작 

call 

call proc 

함수 호출 

ret 

ret 

함수 호출 복귀 

jmp 

jmp dest 

dest로 분기 

 

 

그 외에 기타 명령어로는 아무 동작도 수행하지 않는 nop(사실 내부적으로는 eax 레지스터 값을 eax 레지스터로 복사), 인터렵트(interrupt)를 발생시켜 해당 프로그램의 실행을 중지하고 인터럽트 처리 루틴으로 이동하는 int 등이 있다. 이상 기타 명령어를 정리하면 아래 표와 같다.

 

명령어 

표기 

동작 

nop 

nop 

없음 (EAX <=> EAX) 

int 

int number 

해당 프로그램 중지.

인터럽트 처리 루틴으로 이동 

 

Comments