[교육목표]
■ 리버싱(보안쪽) 기술 대표적 사용법 익히기
* 멀웨어(악의적인 목적으로 만든 프로그램) 분석
* 포렌식 분석
* 권한 상승(모의 해킹) -> 이 부분에 집중 (해킹 대회 필수 지식)
■ 리버싱을 통한 의사 코드(가상 코드) 생성
* POC 코드 (코드에 있을 버그를 테스트 하는 코드)
■ Exploit 코드 개발(BoF, FSB)
* Remote Exploit -> 사용언어 : Shell Script
* Local Exploit -> 사용언어 : C / Python
1. 백도어(Backdoor)
ㄱ. 메모리 구조에 대한 이해
메모리 레이아웃(Layout)에 대한 예제.
hello.c 프로그램을 작성 후 컴파일하여 실행하면
hello 프로그램은 메모리에 올라가게 된다.
이를 통해, 메모리에 올라가는 정보에 대해 이해한다.
# vi hello.c
/* Header File 헤더파일
[참고] C 언어 매뉴얼 보고 싶을때
$ export LANG=C
$ man malloc
$ man –f printf
*/
#include <stdio.h> / h는 header의 약자.
#include <stdlib.h>
/* Global Variable 전역변수 프로그램 실행될 때 만들어졌다가 프로그램 끝나면 땡*/
int retVal = 0;
int outVal;
/* main function 지역변수. 해당 함수가 실행될 때 실행, 함수가 끝날 때 사라짐*/
int main(void) /void는 비어있다는 뜻. ()과 똑같은 뜻,
{
char string[] = "hello"; /배열값 비워두면 \n 까지 알아서 계산되어 적용됨.
char *ptr;
static int output = 1; /static은 변하는 값이 아닌 고정된 값이다
/* Dynamic heap memory allocation */
ptr = (char *)malloc(sizeof(string));
printf("%s\n", string);
return retVal;
}
■ 메모리의 구조
물리주소 |
메모리 세그먼트 |
저장데이터 |
실제데이터 |
0x000000 ..... ..... |
Text(Code) |
실행코드(보통 읽을 수 있는 부분이 있으나, 변조될 경우 Segment Fault 발생) |
#include <stdio.h> int main() { printf("%s\n", string); return retVal } |
..... |
Data |
전역, const, static 변수, 초기화된 변수 |
int retVal = 0 static int output = 1; |
..... |
BSS(Data에포함) |
초기화되지 않은 변수 |
int outVal; |
..... |
Heap |
동적메모리(동적으로 할당된 데이터가 저장) |
ptr = (char *)malloc(sizeof(string)); |
..... |
| A V | |
힙과 스택의 여분 공간 |
변수가 늘어날 경우 힙과 스택의 시작점 사이에 있는 이 공간에 할당 |
..... |
Stack |
지역변수(로컬 인자와 프로세스 상태가 저장) |
char string[] = "hello"; char *ptr; |
..... |
argc |
환경변수와 명령어 창의 데이터(인자)가 저장 |
|
..... |
ENV/argv pointers |
||
..... ..... 0xFFFFFF |
ENV/argv strings |
*Text Segment
: 함수의 Code값이 위치하는 부분. CPU에 의해 실행되는 머신 코드가 있는 영역
*Data(BSS Segment 포함)
: 전역 변수가 위치한 Data 영역과 정적 변수가 위치한 BSS 영역으로 나뉜다.
데이터 세그먼트는 초기화된 외부 변수나 static 변수 등이 저장되는 영역.
보통 텍스트 세그먼트(Text Segment)와 데이터 세그먼트(Data Segment) 영역을 합쳐 프로그램이라 하고,
BSS 세그먼트는 초기화 되지 않은 데이터 세그먼트(Uninitalized data segment)라고 불리며, 프로그램이 실행될 때 0이나 NULL 포인터로 초기화. 외부 변수나 static 변수중 초기화 되지 않은 변수들이 정의될 때 저장.
*Heap
:프로그램 실행 중 필요한 기억 장소를 할당하기 위해 운영체제에 예약되어 있는 기억 장소 영역.
동적할당을 할 때 사용, 메모리의 효율성이 가장 좋다.
*Stack
: 일반적으로 지역함수, 함수의 인자값,복귀 조수 등이 위치, CPU에서 접근 속도가 가장 빠름.
LIFO(Last-In First Out) 방식에 의해 정보를 관리.
TOP 이라고 불리는 스택의 끝 부분에서 데이터의 삽입과 삭제가 발생.
ㄴ. 백도어(Backdoor) 실습
: HackMe(ftz.hackershool.org)
: 실습 내용은 takudaddy.tistory.com/241 참고
-순서-
* SetUID/SetGID/Sticky Bit 이해
* gdb 기본 사용법
* 해커스쿨 FTZ 문제 풀기
2. 리버싱을 통한 의사코드 복원
의사코드(Pseudo Code, 가상코드)란?
리버싱을 통해 원본 소스코드를 복원한 코드(가상코드)이다.
소스코드를 복원할 때 배열의 크기나 조건문이 switch문으로 되어 있는지,
if 문으로 되어 있는지에 대한 정확한 복원은 아닌 단순 복원 코드이다.
따라서 원본 코드와는 모양이 약간 다를수 있다. 하지만 동일한 동작을 한다.
■ 프로그램 분석 방법(EX: 프로그램 실행시 분석 유무)
- 정적 분석(Static) => 프로그램 실행하지 않고 분석
- 동적 분석(Dynamic) => 프로그램 실행하고 break pointer를 정해서 분석하는 방법
■ 프로그램 분석 방법(EX: 소스 코드 존재 유무)
- 블랙 박스(Black Box) 기법 => 바이너리 코드
- 화이트 박스(White Box) 기법 => 바이너리 코드 + 소스 코드
ㄱ. 레지스터(Register)
어셈블리 언어로 프로그램밍을 하기 위해서는 C/C++ 같은 고급 언어와는 달리
CPU 내부에 있는 레지스터에 대해서 잘 알고 있어야 한다.
레지스터는 CPU가 접근할 수 있는 메모리 주에서 가장 빠르게 동작하는 메모리로
CPU가 여러가지 연산등의 처리 하는 동안 임시적으로 데이터를 보관하는데 사용된다.
메인 메모리(일반적으로 RAM 이라고도 불린다.)는 바이트 단위의 번지 혹은
주소를 이용해서 접근할 위치를 구분하는데, 레지스터의 경우는 번지의 개념이 없고
모두 고유한 이름이 부여되어 있다.
레지스터(Register)란?
시스템을 제어하는 CPU에 존재하는 메모리
실행 중인 명령어를 제어
메모리 번지를 지정
산술연산을 수행
레지스터의 종류
* 세그먼트 레지스터(Segment Register)
: 현재 32, 64비트 컴퓨터에 사용되지 않는다.
* 범용 레지스터(General-Purpose Registers)
: 일반적인 레지스터
* 명령어 포인터 레지스터(Instruction Point Register, IP)
: 다음 실행할 코드의 주소를 담는 레지스터
* 포인터 레지스터(Pointer Register)
: 스택 영역을 표시하기 위한 레지스터
* 인덱스 레지스터(Index Register)
: 문자열의 시작주소나 기타 다른 데이터들을 저장하기 위해 사용되는 레지스터
범주q |
80386 레지스터 |
이름 |
비트 |
용도 |
포인터 레지스터 (Pointer Register) |
EBP |
베이스 포인터 (Base Pointer) |
32 |
SS 레지스터와 함께 사용되어 스택 내의 변수 값을 읽는 데 사용 |
ESP |
스택 포인터 (Stack Pointer) |
32 |
SS 레지스터와 함께 사용되며 스택의 가장 끝 주소를 가리킴 |
|
EIP |
명령 포인터 (Instruction Pointer) |
32 |
다음 명령어의 오프셋(Offset, 상대 위치 주소)을 저장하며 CS 레지스터와 합쳐져 다음에 수행될 명령의 주소 형성 |
|
인덱스 레지스터 (Index Register) |
EDI |
목적지 인덱스 (Destination Index) |
32 |
목적지 주소에 대한 값 저장 |
ESI |
출발지 인덱스 (Source Index) |
32 |
출발지 주소에 대한 값 저장 |
|
플래그 레지스터 |
EFLAGS |
플래그 레지스터 (Flag Register) |
32 |
연산 결과 및 시스템 상태와 관련된 여러 가지 플래그 값 저장 |
■ 범용레지스터(General-Purpose Registers) : 32bit만 표시
* EAX(Accumulator, 누산기)
어큐물레이터(Accumulator): 모든 연산 명령에 사용되는 레지스터,
산술연산, 입출력, Translate 명령어, 주로 산술 연산에 사용(함수의 결과값 저장)
* EBX(Base Register, 베이스 레지스터)
임의의 번지 지정에 사용되는 어드레스 레지스터, offset, 특정 주소 저장
(주소 지정을 확대하기 위한 인덱스로 사용)
* ECX(Count Register, 카운트 레지스터)
스트링 조작 명령이나 반복 루프의 계수기로 사용, 반복적으로 실행되는 특정 명령에 사용(루프의 반복 횟수나 좌우 방향 시프트 비트 수 기억)
* EDX(Data Register, 데이터 레지스터)
산술연산, EAX와 함께 자주 사용, 일반 자료 저장(입출력 동작에 사용)
범용레지스터는 EAX, EBX, ECX, EDX 4개의 레지스터로 구성되며, 32비트 크기를 갖는다.
EAX는 누산기로 함수의 리턴값을 기억하는데 사용된다.
EDX는 연산시 Overflow된 결과를 저장하거나 나눗셈 연산시 나머지를 저장하는데 사용된다.
■ 세그먼트 레지스터(Segment Registers)
* CS(Code Segment Register)
DOS의 프로그램 코드 세그먼트의 시작 번지 저장,
이 번지에 명령어 포인터(IP) 레지스터 내의 옵션값을 더하면
실행을 위한 명령어의 번지가 된다.
실행될 기계 명령어가 저장된 메모리 주소 지정.
* DS(Data Segment Register)
프로그램의 데이터 세그먼트 레지스터의 시작 번지를 기억, 프로그램에서 정의된 데이터, 상수, 작업 영역의 메모리 주소 지정.
* SS(Stack Segment Register)
번지와 데이터를 임시로 저장할 목적으로 쓰이는 스택을 메모리에 구현, 스택 포인터 레지스터의 오프셋 값을 더하면 스택 내의 현재 워드를 가리키는 번지. 프로그램이 임시로 저장할 필요가 있거나 사용자의 피호출 서브 루팅이 사용할 데이터와 주소 포함.
* ES(Extra Segment Register)
지정하기 위해 본 레지스터를 사용할 때 DI 레지스터와 연관
FS, GS
286이후에 추가된 레지스터로서 보조 세그먼트 레지스터
■ 포인터 레지스터(Pointer Register)
* EBP(Base Pointer)
함수의 파라미터나 변수의 위치를 얻어오는데 간접적으로 사용(스택 메모리를 가리킴).
호출된 프로시저(Procedure)를 위한 스택 프레임 내의 고정 Reference point를 나타냄.
저장된 이전의 EBP 값을 SFP(Stack Function Flame Pointer)라고 함.
SS 레지스터와 함께 사용되어 스택 내의 변수 값을 읽는 데 사용.
* ESP(Stak Pointer)
스택(stack)의 맨 꼭대기를 가리키는데 사용,그러나 프로그램 안에서 수시로 변경되기 때문에
특정 기준 시점을 잡아 ESP값을 EBP에 저장하여 EBP를 기준으로 변수나 패러미터에 접근.
SS 레지스터와 함께 사용되며, 스택의 가장 끝 주소를 가리킴.
* EIP(Instruction Pointer)
현재 수행중인 코드를 가리킴. 다음 명령어의 오프셋(상대 위치 주소)를 저장하며
CS 레지스터와 합쳐져 다음에 수행될 명령의 주소 형성.
■ 플래그 레지스터(Flag Register)
OF(Overflow Flag)
DF(Direction Flag)
SF(Sign Flag)
ZF(Zero Flag) : 체크
CF(Carry Flag) : 체크
TF(Trap Flag)
PF(Parity Flag)
AF(Auxiliary Flag)
IF(Interrupt Flag)
플래그 레지스터는 CPU 연산 시 발생하는 각 상황에 대한 것들로서
총 32비트로 이루어져 있으며, 이중 16비트만 사용
■ 명령어 포인터 레지스터(Instruction Pointer Registers, IP)
* SP(Stack Pointer) -> ESP
스택내의 현재 워드를 참조할 오프셋 값을 기억.
80386 이후의 프로세서는 확장 스택 포인터(ESP)
* BP(Base Pointer) -> EBP
스택에 들어오고 나가는 데이터나 번지를 참조하는데 사용.
80386 이후의 프로세서는 확장 스택 포인터(ESP)
스택이란?
LIFO(Last Input First Ouput)의 구조를 갖는 것으로
여기서는 스택의 기능을 구현한 메모리 영역을 의미
BP란 이 스택의 시작 주소를 의미하며,
SP는 현재 Stack에서 진행되고 있는 위치를 의미
■ 인텍스 레지스터(Index Registers)
* SI(Source Index)
스트링 조작에 사용, DS 레지스터와 연관 번지의 간접지정에 사용,
특히 스트링 명령에 있어서는 메모리 부터 레지스터로 데이터를 전송하기 위한
전송 측 번지의 지정
*DI(Destination Index)
스트링 조작에 사용, ES 레지스터와 연관 번지의 간접번지에 사용,
특히 스트링 명령에 있어서는 레지스터로 부터 메모리에 데이터를 전송하기 위한
수신 측 번지를 지정할 때 사용
인덱스 레지스터는 주로 문자열의 시작 주소를 담아서 표현하는데 사용
ㄴ. 어셈블리어(Assembly Language)
1.용어
■ 기계어
종류 : 각 CPU 마다 고유 기계어가 있음
모양 : CPU가 바로 해독할 수 있는 0과 1로 구성
특징 : 사람이 이해하기 어려움
기타 : 컴파일을 할 필요가 없음
■ 어셈블리어
종류 : 각 CPU 마다 고유 어셈블리어 존재
모양 : 사람이 이해하기 쉽도록 OP-CODE 사용
특징 : 기계어와 1:1로 모든 명령이 대응
기타 : 어셈블러를 통해 기계어로 컴파일 해야 함
■ 어셈블리(Assembly) 소개
기계어 |
어셈블리어 |
고급언어 |
55 98ES 83EC 08 C70424 57000000 E8 7E050000 C9 C3 |
PUSH EBP MOV EBP,ESP SUB ESP,8 MOV DWORD PTR [ESP],57 CALL <JMP.&msvcrt.putchar> LEAVE RETN |
void aChar(void) { putchar('W'); } |
<------- assemble
--------> disassemble
<--------------------------------------- compile
---------------------------------------> decompile
2. 어셈블리(Assembly) 구성
Intel 문법과 AT&T 문법이 있다.
(윈도우) Intel 문법 사용: add (Destination) (Source)
(리눅스) AT&T 문법 사용 : add (Source) (Destination) /* !!! 수업에서 사용 !!! *
movb : b는 bytes 단위로 다룬다는 뜻이다.
movl : l는 long 크기 만큼이란 뜻이다.
RAX(64bits) -> EAX(32bits) -> AH/AL(16bits)
gdb> set disassembly-flavor intel
gdb> set disassembly-flavor att
■ 연상기호/연산자(mnemonic, opcode)
add 는 덧셈을 한다라는 명령을 인간이 알기 쉬운 문자열로 대응시킨 연상기호(Mnemonic)이다.
■ 피연산자(operand)
기계 코드의 명령들에 따라 피 연산자의 형태와 개수가 다름
각각의 명령들은 고정된 수의 피연산자 수(보통 0~3)를 갖는다.
레지스터: 이는 피연산자들은 CPU 레지스터 들에 직접으로 접근
메모리: 이는 메모리에 저장된 데이터를 가리킴. 언제나 세그먼트 최상단 부터 오프셋 값으로 나타냄
즉시 피연산자(Immediate): 명령 자체에 있는 고정된 값들, 명령자체에 저장, 코드 세그먼트에 저장
묵시적 피연산자(Implied): 정확히 나타나지 않는다. 예를 들어 증가명령은 레지스터나 메모리에 1을 더한다. 이때 1은 묵시적 피연산자로 부른다.
■ 어셈블리 명령어 형식
(ㄱ) opcode EX) std
(ㄴ) opcode operand1 EX) push eax
(ㄷ) opcode operand1, operand2 EX) add 10, eax
(ㄹ) opcode operand1, operand2, operand3 EX) shld 16, edx, eax
■ 오퍼랜드(operand)의 종류
명령의 대상이 되는 오퍼랜드에는 다음과 같이 3가지 종류가 있다.
레지스터, 메모리, 직접값
mov 레지스터, 레지스터 EX) mov ebx, eax
mov 레지스터, 메모리 EX) mov eax, Value /* Value : 미리 선언된 메모리 변수명 */
mov 메모리, 레지스터 EX) mov Value,eax
mov 직접값, 레지스터 EX) mov 100, eax
mov 직접값, 메모리 EX) mov 100, Value
3. 명령(Opcode)의 종류(자주 사용되는 어셈블러 명령)
* 모든 어셈블러 명령을 기억할 필요는 없다 *
* 하지만 아래 빨간색으로 표시한 명령은 기억하자 *
명령의 종류는
(ㄱ) 산술 연산 명령 종류
(ㄴ) 데이터 전송 명령 종류
(ㄷ) 논리 연산 명령 종류
(ㄹ) 제어 전송 명령 종류
(ㅁ) 스트링 명령 종류
(ㅂ) 프로세스 제어 명령 종류
등이 있다.
[산술연산 명령 종류]
기본적인 정수를 계산하는 명령의 종류이다.
* add(Addition)
: 덧셈명령, 캐리를 포함하지 않는 덧셈을 한다.
(예) add $0x10,%esp (해석) esp += 10 /* eax에 10를 가산 */
(예) add ecx,eax (해석) eax += ecx /* eax에 ecx를 가산 */
* sub(Subtraction)
: 캐리를 포함하지 않는 뺄셈(뺄셈 연산 처리에 사용)
(예) sub $0xc,%esp (해석) esp -= 0xc
* inc(Increment)
: 오퍼랜드 값을 1만큼 증가한다.
(예) inc eax (해석) eax++ /* eax에 1을 가산 */
* dec(Decrement)
: 오퍼랜드 값을 1만큼 감소한다.
(예) dec eax (해석) eax-- /* eax에 1을 감산 */
* cmp(Compare)
: 두 데이터를 비교하여 같으면 CF(Carry Flag)는 0, ZF(Zero Flag)는 1로 설정
: CF가 0인 상태에서 두 데이터가 다르면 ZF는 0으로 설정
: 두 오퍼랜드 값이 같다는 것을 비교하는 방법은 오퍼랜드를 뺀값이 0이면 참, 아니면 거짓.
(예) cmp %ecx,%eax (해석) if (ecx == eax) ZF=1
else ZF=0
* mul(Immediate Multiplication)
: 부호 없는 곱셈.
: EAX와 오퍼랜드를 곱셈 후 결과를 EAX에 저장한다.(곱셈 연산 처리에 사용)
(예) mul %ecx
(해석) eax 레지스터의 내용에 소스의 내용을 곱해서 레지스터에 저장한다.
결과 값이 커서 목적지에 다 들어가지 못할 때는 edx 레지스터에 넘치는 상위 비트 값을 저장한다.
(연산전) eax(00077777), ecx(00077777), edx(00000000)
(연산후) eax(4861d951), ecx(00077777), edx(000037c0)
* div(Dividend)
: 부호 없는 나눗셈
: EAX의 내용을 오퍼랜드로 나누어 몫은 AL에 나머지는 EAH, EDX로 저장한다.
(예) idv ecx
(해석) eax 레지스터 값을 소스의 내용으로 나누어서 몫은 eax 레지스터에 저장하고 나머지는 edx 레지스터에 저장한다. div 연산을 테스트해서 원하는 값을 얻으려면 edx 레지스터 값을 먼저 0으로 변경해야 한다.
(연산전) eax(0000000a), ecx(00000003), edx(00000000)
(연산후) eax(00000003), ecx(00000003), edx(00000001)
* 기타 산술연산 명령
ADC (Add with Carry) : 캐리를 포함한 덧셈을 수행한다.
SBB (Subtraction with Borrow) : 캐리를 포함한 뺄셈을 수행한다.
NEG (Change Sign) : 피연산자의 2의 보수, 즉 부호를 반전한다.
AAA (ASCII adjust for add) : 덧셈결과의 AL 값을 UNPACK 10진수로 보정한다.
DAA (Decimal adjust for add) : 덧셈결과의 AL 값을 PACK 10진수로 보정한다.
ASS (ASCII adjust for subtract) : 뺄셈 결과의 AL 값을 UNPACK 10진수로 보정한다.
DAS (Decimal adjust for subtract): 뺄셈 결과의 AL 값을 PACK 10빈수로 보정한다.
IMUL(Multiply(Unsigned)) : 부호화된 곱셈을 수행한다.
AAM (ASCII adjust for Multiply) : 곱셈 결과의 AX 값을 UNPACK 10진수로 보정한다.
IDIV(Integer Divide(Signed)) : 부호화된 나눗셈
AAD (ASCII adjust for Divide) : 나눗셈 결과 AX 값을 UNPACK 10진수로 보정한다.
CBW (Convert byte to word) : AL의 바이트 데이터를 부호 비트를 포함하여 AX 워드로 확장한다.
CWD (Convert word to double word): AX의 워드 데이터를 부호를 포함하여 DX:AX의 더블 워드로 변환한다.
[데이터 전송 명령 종류]
데이터 전송 명령 종류는
(ㄱ) 메모리,
(ㄴ) 범용레지스터,
(ㄷ) 세그먼트 레지스터로 참조되는 주소에
존재하는 데이터 전송한다.
* mov(Move) : RAM --> CPU(Register) or CPU(Register) --> RAM
: 데이터 이동하는데 사용.
: RAM 끼리의 연산은 이루어 지지 않으므로 RAM의 데이터를 레지스터로 옮기거나
: 레지스터의 데이터를 RAM으로 옮기는 작업을 처리할 때 주로 사용한다.
(예) mov $0x0,%eax (해석) EAX = 0 /* EAX 값을 0으로 대입한다. */
(예) mov ecx,eax (해석) EAX = ECX /* ECX 값을 EAX에 저장 */
* push(Push) : --> RAM(stack)
: 오퍼랜드의 내용을 스택에 넣는다.
: 레지스터의 데이터 임시 보관등에 사용한다.
(예) push $0x8048680 (해석) Stack에 0x8048680 값을 저장
* pop(Pop) : <-- RAM(Stack)
: 스택으로 부터 값을 빼낸다.
: 레지스터의 데이터를 복원할 때 주로 사용한다.
(예) pop eax (해석) Stack 값을 EAX에 저장
* lea(Load Effective Address to Register) : RAM(주소의 값) --> CPU(Register)
: 유효주소를 레지스터로 로드(Load).
: lea 명령어는 데이터를 복사하는 것이 아니라 주소만을 복사한다.
: RAM에서 CPU에 있는 레지스터에 데이터를 옮길때 로드(Load)라는 단어를 사용,
: 반대로 레지스터에서 RAM으로 가는 경우 저장(Store)이라는 표현을 사용한다.
RAM -- Load --> CPU(Register)
RAM <-- Save -- CPU(Register)
(예) lea 0xffffffd8(%ebp),%eax (해석) RAM(주소(0xffffffd8))을 eax 저장
= lea DWORD PTR [%ebp-8],%eax
* 기타 데이터 전송 명령
xchg(Exchange Register Memory with Register): 첫번째 피연산자와 두번째 피연산자를 바꾼다.
IN(Input from AL/AX to Fixed port) : 피연산자로 지시된 포트로 AX에 데이터를 입력한다.
OUT(Output from AL/AX to Fixed port) : 피연산자가 지시한 포트로 AX의 데이터를 출력한다.
XLAT(Translate byte to AL) : BX:AL이 지시한 테이블의 내용을 AL로 로드한다.
LDS(Load Pointer to DS): LEA 명령과 유사한 방식으로 다른 DS 데이터의 주소의 내용을 참조시 사용한다.
LES(Load Pointer to DS) : LEA 명령과 유사한 방식으로 ES 데이터의 주소의 내용을 참조시 사용한다.
LAHF(Load AH with Flags) : 플래그의 내용을 AH의 특정 비트로 로드한다.
SAHF(Store AH into Flags) : AH의 특정 비트를 플래그 레지스터로 전송한다.
PUSHF(Push flags) : 플래그 레지스터의 내용을 스택에 삽입한다.
POPF(Pop flags) : 스택에 저장되어 있던 플래그 레지스터 값을 삭제한다.
[논리연산 명령 종류]
연산부호가 논리연산을 지정하는 명령으로
(ㄱ) 자리옮김,
(ㄴ) 논리 합(OR),
(ㄷ) 논리곱(AND),
(ㄹ) 기호 변환
등이 있다.
* and(AND)
: 논리 and 연산,
: 두수의 곱셈 연산 후 결과를 참과 거짓으로 표현하는 것으로 교집합의 형태와 동일하다.
: 대응되는 비트가 둘다 1일 때만 결과가 1이고, 그 이외에는 모두 0
(예) and AX,10h
--------------------------------------
AX 1 0 0 0 /* if AX 값이 0x08h라면 이진수로 1000이다. */
0x10h 1 0 1 0
--------------------------------------
결과 1 0 0 0
--------------------------------------
* or(OR)
: 논리 or 연산,
: 두수의 덧셈 연산 후 결과를 참과 거짓으로 표현하는 것으로 합집합의 형태와 동일하다.
: 대응되는 비트 중 하나만 1이어도 결과가 1이고, 둘 다 0인 경우에만 0
(예) or AX,10h
--------------------------------------
AX 1 0 0 0 /* if AX 값이 0x08h라면 이진수로 1000이다. */
0x10h 1 0 1 0
--------------------------------------
결과 1 0 1 0
--------------------------------------
* xor(Exclusive OR)
: 배타적 논리 합(OR) 연산,
: 두수가 같은 값을 가지면 거짓, 다른 값을 가지면 참을 반환하는 연산으로
초기화 연산이나 암호화 시에 주로 사용한다.
: 대응되는 비트 중에서 한 비트가 1이고 다른 비트가 0이면 1, 두개의 비트가 모두 0 또는 1일 때 0,
: 따라서 두개의 비트가 같으면 0, 다르면 1이다.
(예) xor AX,10h
--------------------------------------
AX 1 0 0 0 /* if AX 값이 0x08h라면 이진수로 1000이다. */
0x10h 1 0 1 0
--------------------------------------
결과 0 0 1 0
--------------------------------------
* not(Invert)
: 오퍼랜드의 1의 보수,
: 각 비트별 반대의 값으로 변환하여 1의 보수화
(예) not AX
--------------------------------------
AX 1 0 0 0 /* if AX 값이 0x08h라면 이진수로 1000이다. */
--------------------------------------
결과 0 1 1 1
--------------------------------------
* test(And Function to Flags, no result)
: 첫번째 오퍼랜드와 두번째 오퍼랜드를 AND 연산하여 결과가 0이면 ZF(Zero Flag)를 1로 설정한다.
: 일반적으로, NULL 체크할 때 주로 사용된다.
(예) test %eax,%eax
(해석) if((%eax & %eax) == 0) ZF=1 (의역) if(%eax == NULL) ZF=1
else ZF=0 else ZF=0
(예) test %eax, %ecx
(연산전) eax(0000ffff), ecx(ffff0000), ZF(0)
(연산후) eax(0000ffff), ecx(ffff0000), ZF(1)
* ror(Rotate Right)
오른쪽으로 오퍼랜드만큼 회전 이동(오른쪽으로 Rotation 시키는 연산)
* rol(Rotate Left)
왼쪽으로 오퍼랜드 만큼 회전 이동(왼쪽으로 Rotation 시키는 연산)
* 기타 논리연산 명령
SHL/SAL(Shift Left/Arithmetic Left) : 왼쪽으로 피연산자만큼 자리 이동
SHR/SAR(Shift Right/Shift Arithmethic Right) : 오른쪽으로 피연산자만큼 자이 이동
RCL(Rotate through carry left): 자리올림(Carry)을 포함하여 왼쪽으로 피연산자 만큼 회전 이동
RCR(Rotate through carry Right): 자리올림(Carry)을 포함하여 오른쪽으로 연산자만큼 회전이동
[제어 전송 명령 종류]
제어 전송 명령은
(ㄱ) 점프(분기, Jump),
(ㄴ) 조건 점프(조건 분기, Conditional jump),
(ㄷ) 루프(Loop),
(ㄹ) 호출(Call)과
(ㅁ) 리턴(Return)
연산등으로 프로그램의 흐름 제어한다.
1. jmp(Unconditional Jump)
* (무조건 점프) 프로그램을 실행할 주소 또는 라벨로 이동한다.
* 목적지 주소로 주소로 이동하고 복귀 주소를 백업하지는 않는다.
(예) jmp 04001000 (해석) if(무조건) 04001000 번지로 jump
2. je(Jump Equal)/jz(Jump Zero)
* je : (같을 경우 점프) ZF(Zero Flag)가 1일 경우 점프한다. 보통 CMP 명령어 뒤에 온다.
즉, 비교 결과가 같을 때 점프한다.
(점프조건) ZF == 1
(예) je 04001000 (해석) if(ZF == 1) 04001000 번지로 jump
* jz : 결과가 0일 때 점프, je와 동일(cmp 명령에서 결과가 같으면 0을 출력한다.)
3. jne(Jump not equal)/jnz(Jump not zero)
* jne : ZF가 0일 경우 점프한다. 보통 CMP 명령어 뒤에 온다. 즉, 비교결과가 다를 때 점프한다.
(점프조건) ZF == 0
(예) jne 04001000 (해석) if(ZF == 0) 04001000 번지로 jump
* jnz : ZF가 0이거나 앞의 연산 결과가 0이 아니면 점프한다. 보통 DEC나 CMP 명령 뒤에 온다.
4. call(Call)
* 함수(프로시저) 호출시 사용된다.
* PUSH(EIP) + JMP(jump)의 두가지 기능을 하는 것으로
* 함수 호출시 그 함수의 복귀 주소를 push로 stack에 저장 시키며 jmp를 이용하여 그 함수로 이동한다.
(예) call 0x80483a8 <printf> (해석) 0x80483a8 번지의 printf() 호출(push + jump)
5. ret(Return)
* 함수에서 호출(call)한 위치로 돌아갈때 사용한다.
* POP(EIP) + JMP(jump)의 두가지 기능을 하는 것으로 함수 지역으로 복귀한다.
6. 기타 제어(점프 제어) 전송 명령
jc(jump carry flag set) : CF(Carry Flag)가 1일 때 점프
jnc(jump not carry flag set) : CF가 0일 때 점프
ja(jump above) : cmp a, b에서 a가 클때 점프
jane(jump not below nor equal) : 작지 않거나 같지 않을 때 점프(ja 같은 의미)
jae(jump above or equal) : 크거나 같을 때 점프
jnb(jump not below) : 작지 않을 때 점프(jae 같은 의미)
jb(jump below) : cmp a, b에서 a가 작을 때 점프
jnae(jump not above nor equal) : 크지 않거나 같지 않을 때 점프(jb 같은 의미)
jl(jump less) : cmp a, b에서 a가 작을 때 점프
jnge(jump not greater nor equal) : 크지 않거나 같지 않을 때 점프(jl 같은 의미)
jbe(jump below equal) : 작거나 같을 때 점프
jna(jump not above) : 크지 않을 때 점프(jbe 같은 의미)
jge(jump greater or equal) : 크거나 같을 때 점프
jnl(jump not less) : 작지 않을 때 점프(jge 같은 의미)
jle(jump less or equal) : 작거나 같을 때 점프
jng(jump not greater) : 크지 않을 때 점프(jle 같은 의미)
jnp(jump not parity) : PF(Parity Flag)가 0일 때 점프
jpo(jump parity odd) : PF가 홀수 일 때 점프(일반적으로 jnp 같은 의미)
js(jump sign) : SF(Sign Flag)가 1일 때(음수) 점프
jns(jump not sign) : SF(Sign Flag)가 0일 때(양수) 점프
jo(jump overflow) : OF(Overflow Flag)가 1일 때 점프
jno(jump not overflow) : OF가 0일 때 점프
jp(jump parity) : PF(Parity Flag)가 1일 때 점프
jpe(jump parity even) : PF가 짝수일 때 점프
INT(interrupt) : 인터럽트가 호출되면 CS:IP(code segment:instruction pointer)와 플래그를 스택에 저장, 그 인터럽트에 관련된 서브 루팅이 실행
LOOP(loop CX times) : 문장들의 블록을 지정된 횟수만큼 반복한다. CX는 자동적으로 카운터로 사용되며, 루프가 반복할 때 마다 감소한다.
[참고] "above, below" versus "greater, less"
* above, below : 부호 없는 두 수의 크기 관계
* greater, less : 부호 있는 두 수의 크기 관계
[스트링(Strings) 명령 종류]
바이트로 구성된 스트링(Strings of bytes)을 메모리 내에서 전송
데이터 전송 명령
REP(Repeat) : ADD나 MOVS와 같은 작동 코드의 앞에 위치, CX가 0이 될 때까지 뒤에 오는 스트링 명령 반복
MOVS(Move String) : 바이트, 워드, 더블워드를 옮기는 명령. MOVSB, MOVSW, MOVSD 등이 있다.
CMPS(Compare String) : DS:SI와 ES:DI의 내용을 비교한 결과에 따라 플래그를 설정한다.
SCAS(Scan String) : AL 또는 AX와 ES:DI가 지시한 메모리 내용을 비교한 결과에 따라 플래그를 설정한다.
LODS(Load String) : SI 내용을 AL 또는 AX로 로드한다.
STOS(Store String) : AL 또는 AX를 ES:DI가 지시하는 메모리에 저장한다.
프로세스 제어 명령 종류
CLC(claer carry)
CMC(Complement Carry)
CLD(Clear Direction)
CLI(Clear Interrupt)
STD(Set Direction)
STI(Set Interrupt)
WAIT(wait)
ESC(Escape to External device)
[EX] C 언어 파일을 어셈블리어로 변환
sample.c (C 언어 작성) --- 변환 --> sample.a (어셈블리어)
(HackMe)
$ cd ~/tmp
$ vi sample.c
int function(int a, int b) { char buffer[10]; a = a + b; return a; }
int main(void) { int c; c = function(1,2); return 0; } |
[참고] gcc 옵션
# man gcc
-S Stop after the stage of compilation proper; do not
assemble. The output is in the form of an assembler
code file for each non-assembler input file specified.
By default, the assembler file name for a source file
is made by replacing the suffix .c, .i, etc., with .s.
Input files that don't require compilation are
ignored.
-o file
Place output in file file. This applies regardless to
whatever sort of output is being produced, whether it
be an executable file, an object file, an assembler
file or preprocessed C code.
Since only one output file can be specified, it does
not make sense to use -o when compiling more than one
input file, unless you are producing an executable
file as output.
If -o is not specified, the default is to put an exe-
cutable file in a.out, the object file for source.suf-
fix in source.o, its assembler file in source.s, and
all preprocessed C source on standard output.
$ gcc -S -o sample.as sample.c
$ ls -l
-rw-rw-r-- 1 level1 level1 570 11월 25 12:17 sample.a -rw-rw-r-- 1 level1 level1 131 11월 25 12:16 sample.c |
$ file *
sample.as: ASCII assembler program text sample.c: ASCII C program text |
$ vi sample.as
.file "sample.c" .text .globl function .type function,@function function: pushl %ebp movl %esp, %ebp subl $24, %esp movl 12(%ebp), %eax addl %eax, 8(%ebp) movl 8(%ebp), %eax leave ret .Lfe1: .size function,.Lfe1-function .globl main .type main,@function main: pushl %ebp movl %esp, %ebp subl $8, %esp andl $-16, %esp movl $0, %eax subl %eax, %esp subl $8, %esp pushl $2 pushl $1 call function addl $16, %esp movl %eax, -4(%ebp) movl $0, %eax leave |
[EX2] C 언어 파일을 컴파일하고 disassemble 하기
sample.c (C 언어) -----> sample (바이너리) -----> disassemble (어셈블리어)
(HackMe)
$ gcc -o sample sample.c
$ file sample*
sample: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), for GNU/Linux 2.2.5, dynamically linked (uses shared libs), not stripped sample.a: ASCII assembler program text sample.c: ASCII C program text |
$ gdb -q sample
(gdb) disassemble main Dump of assembler code for function main: 0x08048305 <main+0>: push %ebp 0x08048306 <main+1>: mov %esp,%ebp 0x08048308 <main+3>: sub $0x8,%esp 0x0804830b <main+6>: and $0xfffffff0,%esp 0x0804830e <main+9>: mov $0x0,%eax 0x08048313 <main+14>: sub %eax,%esp 0x08048315 <main+16>: sub $0x8,%esp 0x08048318 <main+19>: push $0x2 0x0804831a <main+21>: push $0x1 0x0804831c <main+23>: call 0x80482f4 <function> 0x08048321 <main+28>: add $0x10,%esp 0x08048324 <main+31>: mov %eax,0xfffffffc(%ebp) 0x08048327 <main+34>: mov $0x0,%eax 0x0804832c <main+39>: leave 0x0804832d <main+40>: ret 0x0804832e <main+41>: nop 0x0804832f <main+42>: nop End of assembler dump. |
(정리)
sample.c (C 언어) --- gcc -S --> sample.as (-S 어셈블리어)
sample.c (C 언어) --- gcc --> sample (바이너리) --- gdb --> disassemble (어셈블리어)
+
sample.as(어셈블리어) --- as ---> sample(바이너리)
바이너리와 어셈블리는 1대1 맵핑 방식이라 언제든 교환이 가능
[출처]
솔데스크 백승찬 강사님
'정보보안공부 > 정보보안전문과정' 카테고리의 다른 글
정보보안 과정 Day65 : Reverse Engineering 3 (0) | 2020.12.08 |
---|---|
정보보안 과정 Day64 : Reverse Engineering 2 (0) | 2020.12.07 |
정보보안 과정 Day53~62 모의해킹 실습 (0) | 2020.12.04 |
정보보안 과정 Day42~52 : 네트워크 해킹과 보안 (0) | 2020.11.20 |
정보보안 과정 Day41 : 모의해킹 실습환경 구축 (1) | 2020.11.20 |