뉴비에욤
Shellcode 만들기 - 2 (시스템 및 프로그래밍 기본 사항) 본문
시스템 및 프로그래밍 기본 사항
쉘코드의 동작 원리에 대한 설명에 앞서, 앞으로 이를 이해하는 데 필요한 기본적인 시스템의 메모리 구조 및 어셈블리 명령어, 그리고 프로그램 세부 동작 과정을 차례로 살펴본다.
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 |
해당 프로그램 중지. 인터럽트 처리 루틴으로 이동 |
'System Hacking(OS) > Shellcode' 카테고리의 다른 글
Universal Shellcode for Windows ( 윈도우 범용 쉘코드 ) (0) | 2016.05.13 |
---|---|
Shellcode 만들기 - 4 (쉘코드 동작 원리 이해 및 제작) (0) | 2015.10.01 |
Shellcode 만들기 - 3 (메모리 세그먼트 구조) (0) | 2015.10.01 |
Shellcode 만들기 - 1 (쉘코드 정의 및 종류) (0) | 2015.09.30 |