728x90
반응형

Opcode: 스택

x64 아키텍쳐에서는 다음의 명령어로 스택을 조작할 수 있음

push val: val을 스택 최상단에 쌓음 (연산: rsp -= 8, [rsp] = val)

[Register]
rsp = 0x7fffffffc400

[Stack]
0x7fffffffc400 | 0x0 ≦ rsp
0x7fffffffc408 | 0x0

[Code]
push 0x31337

 

해석

rsp(Stack Pointer): 현재 스택의 최상위 주소
rsp 주소에 현재 스택의 최상위 값이 존재 - '0x0'

rsp 주소에서 8바이트(64비트 아키텍처에서는 8바이트가 한 워드) 위로 올라가면 또 다른 8바이트 값이 있음 - '0x0'

스택에 0x31337 푸시 - c400 - c3f8 -> 8만큼 차이

 

결과

[Register]
rsp = 0x7fffffffc3f8

[Stack]
0x7fffffffc3f8 | 0x31337 ≦ rsp
0x7fffffffc400 | 0x0
0x7fffffffc408 | 0x0

 

pop reg: 스택 최상단의 값을 꺼내서 reg에 대입 (연산: reg = [rsp], rsp += 8)

[Register]
rax = 0
rsp = 0x7fffffffc3f8

[Stack]
0x7fffffffc3f8 | 0x31337 <= rsp 
0x7fffffffc400 | 0x0
0x7fffffffc408 | 0x0

[Code]
pop rax

결과

[Register]
rax = 0x31337
rsp = 0x7fffffffc400

[Stack]
0x7fffffffc400 | 0x0 <= rsp 
0x7fffffffc408 | 0x0

 

Opcode: 프로시저

특정 기능을 수행하는 코드 조각

호출(Call): 프로시저를 부르는 행위

반환(Return): 프로시저에서 돌아오는 것

call 다음의 명령어 주소(return address, 반환 주소)를 스택에 저장하고 프로시저로 rip 이동

x64 어셈블리언어에는 프로시저의 호출과 반환을 위한 call, leave, ret 명령어 존재

call addr: addr에 위치한 프로시져 호출

연산: push return_address / jmp addr

예제

[Register]
rip = 0x400000
rsp = 0x7fffffffc400

[Stack]
0x7fffffffc3f8 | 0x0
0x7fffffffc400 | 0x0 <= rsp

[Code]
0x400000 | call 0x401000  <= rip
0x400005 | mov esi, eax
...
0x401000 | push rbp

 

해석

Register(레지스터): rip(명령 포인터 레지스터), rsp(스택 포인터)

after call 0x401000 - 현재 위치의 다음 명령어의 주소(0x400005)가 스택에 푸시됨

rsp는 8만큼 감소하여 스택의 새로운 맨 위를 가리킴

 

결과

[Register]
rip = 0x401000
rsp = 0x7fffffffc3f8

[Stack]
0x7fffffffc3f8 | 0x400005  <= rsp
0x7fffffffc400 | 0x0

[Code]
0x400000 | call 0x401000
0x400005 | mov esi, eax
...
0x401000 | push rbp  <= rip

 

leave: 스택프레임 정리

연산: mov rsp rbp / pop rbp

예제

[Register]
rsp = 0x7fffffffc400
rbp = 0x7fffffffc480

[Stack]
0x7fffffffc400 | 0x0 <= rsp
...
0x7fffffffc480 | 0x7fffffffc500 <= rbp
0x7fffffffc488 | 0x31337 

[Code]
leave

 

해석

Register(레지스터): rsp(스택 포인터), rbp(베이스 포인터)

mov rsp, rbp / pop rbp

leave 명령: 현재 함수의 프롤로그에서 설정된 스택 프레임이 제거됨

rsp가 rbp로 복원되고 rbp의 원래 값인 0x7fffffffc500이 rbp로 복원됨

 

결과

[Register]
rsp = 0x7fffffffc488
rbp = 0x7fffffffc500

[Stack]
0x7fffffffc400 | 0x0
...
0x7fffffffc480 | 0x7fffffffc500
0x7fffffffc488 | 0x31337 <= rsp
...
0x7fffffffc500 | 0x7fffffffc550 <= rbp

스택 프레임이란?

함수별로 자신의 지역변수 또는 연산과정에서 부차적으로 생겨나는 임시 값들을 저장하는 영역
스택 영역을 구분 없이 사용한다면, 서로 다른 두 함수가 같은 메모리 영역을 사용할 수 있음

A라는 함수가 B라는 함수를 호출하는데, 같은 스택 영역을 사용한다면 B에서 A의 지역변수를 모두 오염시킬 수 있음, B에서 반환한 뒤 A는 정상적인 연산을 수행할 수 없음

ret: return address로 반환

연산: pop rip

예제

[Register]
rip = 0x401021
rsp = 0x7fffffffc3f8

[Stack]
0x7fffffffc3f8 | 0x400005    <= rsp
0x7fffffffc400 | 0x123456789abcdef

[Code]
0x400000 | call 0x401000
0x400005 | mov esi, eax
...
0x401000 | push rbp
0x401001 | mov rbp, rsp
0x401004 | sub rsp, 0x30
0x401008 | mov BYTE PTR [RSP], 0x3
...
0x401020 | leave
0x401021 | ret <= rip

해석

Register: rip(명령 포인터 레지스터), rsp(스택 포인터)

Stack: rsp 주소에 현재 스택의 맨 위 값이 있음, 0x400005
rsp 주소에서 8바이트 아래로 내려가면 다음 슬롯이 있고 값은 0x123456789abcdef

call 0x401000: rip은 0x401000으로 변경됨

call 0x401000: 스택과 레지스터 상태

 

결과

[Register]
rip = 0x400005
rsp = 0x7fffffffc400

[Stack]
0x7fffffffc3f8 | 0x400005
0x7fffffffc400 | 0x123456789abcdef    <= rsp

[Code]
0x400000 | call 0x401000
0x400005 | mov esi, eax   <= rip
...
0x401000 | push rbp
0x401001 | mov rbp, rsp
0x401004 | sub rsp, 0x30
0x401008 | mov BYTE PTR [RSP], 0x3
...
0x401020 | leave
0x401021 | ret

 

스택 프레임의 할당과 해제

func 함수를 호출, 다음 명령어의 주소인 0x4000005는 스택에 push

main: rip

stack: rsp(0x7fffffffc400), rbp(0x7fffffffc480)

 

스택

push val: rsp를 8만큼 빼고, 스택의 최상단에 val을 쌓음

pop reg: 스택 최상단의 값을 reg에 넣고, rsp를 8만큼 더함

 

프로시저

call addr: addr의 프로시저 호출

leave: 스택 프레임 정리

ret: 호출자의 실행 흐름으로 돌아감

 

Q1. 레지스터, 메모리 및 코드가 다음과 같다. Code를 1까지 실행했을 때, rax에 저장된 값은?

[Register]
rbx = 0x401A40

=================================

[Memory]
0x401a40 | 0x0000000012345678
0x401a48 | 0x0000000000C0FFEE
0x401a50 | 0x00000000DEADBEEF
0x401a58 | 0x00000000CAFEBABE
0x401a60 | 0x0000000087654321

=================================

[Code]
1: mov rax, [rbx+8]
2: lea rax, [rbx+8]

0xC0FFEE

 

Q2. Code를 2까지 실행했을 때, rax에 들어있는 값은?

0x401A48

 

Q3. 레지스터, 메모리 및 코드가 다음과 같다. Code를 1까지 실행했을 때, rax에 저장된 값은?

[Register]
rax = 0x31337
rbx = 0x555555554000
rcx = 0x2

=================================

[Memory]
0x555555554000| 0x0000000000000000
0x555555554008| 0x0000000000000001
0x555555554010| 0x0000000000000003
0x555555554018| 0x0000000000000005
0x555555554020| 0x000000000003133A

==================================

[Code]
1: add rax, [rbx+rcx*8]
2: add rcx, 2
3: sub rax, [rbx+rcx*8]
4: inc rax

0x3133A

 

Q4. Code를 3까지 실행했을 때, rax에 저장된 값은?

0

 

Q5. Code를 4까지 실행했을 때, rax에 저장된 값은?

1

 

Q6. 레지스터, 메모리 및 코드가 다음과 같다. Code를 1까지 실행했을 때, rax에 저장된 값은?

[Register]
rax = 0xffffffff00000000
rbx = 0x00000000ffffffff
rcx = 0x123456789abcdef0

==================================

[Code]
1: and rax, rcx
2: and rbx, rcx
3: or rax, rbx

 

0x1234567800000000

 

Q7. Code를 2까지 실행했을 때, rbx에 저장된 값은?

0x000000009ABCDEF0

 

Q8. Code를 3까지 실행했을 때, rax에 저장된 값은?

0x123456789ABCDEF0

 

Q9. 레지스터, 메모리 및 코드가 다음과 같다. Code를 1까지 실행했을 때, rax에 저장된 값은?

[Register]
rax = 0x35014541
rbx = 0xdeadbeef

==================================

[Code]
1: xor rax, rbx
2: xor rax, rbx
3: not eax

0xEBACFBAE

 

35014541

deadbeef

3d 5e (0a) (1d) 4b 5e (4e) (1f)

EB AC FB AE

 

728x90
728x90
728x90
반응형

 

공격자는 비지박스(busybox)라는 리눅스 명령어 패키지를 이용해서 wget 명령으로 악성코드를 다운로드 해 실행

 

x86-64 아키텍처

인텔의 32비트 CPU 아키텍처, 64: CPU가 한번에 처리할 수 있는 데이터의 크기
- 인텔의 x86 아키텍처와 호환되는 64bit 아키텍처 IA-64 발표

Intel 회사에서 개발한 컴퓨터의 중앙처리장치(CPU)
ISA(Instruction Set Architecture)

 

범용 레지스터(General Register)

이름 주용도
rax (accumulator register) 함수의 반환 값
rbx (base register) x64에서는 주된 용도 없음
rcx (counter register) 반복문의 반복 횟수, 각종 연산의 시행 횟수
rdx (data register) x64에서는 주된 용도 없음
rsi (source index) 데이터를 옮길 때 원본을 가리키는 포인터
rdi (destination index) 데이터를 옮길 때 목적지를 가리키는 포인터
rsp (stack pointer) 사용중인 스택의 위치를 가리키는 포인터
rbp (stack base pointer) 스택의 바닥을 가리키는 포인터

세그먼트 레지스터(Segment Register)

x64 아키텍처에는 cs, ss, ds, es, fs, gs 총 6가지 세그먼트 레지스터 존재

arm, mips, mpsl, x86

 

Q1. rax = 0x0123456789abcedf일 때, eax의 값은?

0x89abcdef

 

Q2. rax = 0x0123456789abcdef일 때, al의 값은?

0xef

 

Q3. rax에서 rbx를 뺐을 때, ZF가 설정되었다. rax와 rbx의 대소를 비교하시오.

==

 

Q4. rax = 0x0123456789abcdef일 때, ax의 값은?

0xcdef

 

Q5. rax = 0x0123456789abcdef일 때, ah의 값은?

0xcd

 

프로세스 메모리 구조

섹션

윈도우의 PE 파일은 PE 헤더와 1개 이상의 섹션으로 구성됨

".text" 섹션: PE코드, 실행 가능한 기계 코드가 위치하는 영역

".data" 섹션: PE가 실행중에 참조하는 데이터, 컴파일 시점에 값이 정해진 전역 변수들이 위치, 읽기/쓰기 권한 부여

".rdata" 섹션: 컴파일 시점에 값이 정해진 전역 상수와 참조할 DLL 및 외부 함수들의 정보 저장, 읽기 권한 부여(쓰기 불가능), str_ptr: "readonly" 문자열

섹션이 아닌 메모리

윈도우의 가상 메모리 공간에는 섹션 + 스택/힙 등이 적재됨

스택: 지역 변수나 함수의 리턴 주소가 저장됨, 읽기/쓰기 권한 부여 (기존 주소보다 낮은 주소로 확장됨)

어셈블리어와 x86-64

어셈블리 언어

컴퓨터의 기계어와 치환되는 언어, 명령어 집합구조(Instruction Set Architecture, ISA)

x64 어셈블리 언어

명령어(Operation Code, Opcode)와 목적어에 해당하는 피연산자(Operand)

x86-64 어셈블리어 문법 구조

mov eax, 3

(opcode: 대입해라, operand1: eax에, operand2: 3을)

명령어

인텔의 x64에는 매우 많은 명령어가 존재함, 본 커리큘럼에서는 이 코스와 다음 코스에 걸쳐, 이 중 중요한 21개의 명령어를 자세히 학습할 것.

명령 코드  
데이터 이동(Data Transfer) mov, lea
산술 연산(Arithmetic) inc, dec, add, sub
논리 연산(Logical) and, or, xor, not
비교(Comparison) cmp, test
분기(Branch) jmp, je, jg
스택(Stack) push, pop
프로시져(Procedure) call, ret, leave
시스템 콜(System call) syscall

피연산자

상수(Immediate Value), 레지스터(Register), 메모리(Memory)

크기 지정자(Size Directive) TYPE PTR

여기에 타입에는 BYTE, WORD, DWORD, QWORD가 올 수 있으며, 각각 1바이트, 2바이트, 4바이트, 8바이트의 크기 지정

메모리 피연산자의 예

메모리 피연산자  
QWORD PTR [0x8048000] 0x8048000의 데이터를 8바이트만큼 참조
DWORD PTR [0x8048000] 0x8048000의 데이터를 4바이트만큼 참조
WORD PTR [rax] rax가 가르키는 주소에서 데이터를 2바이트 만큼 참조

자료형 WORD의 크기가 2바이트인 이유

초기에 인텔은 WORD의 크기가 16비트인 IA-16 아키텍처를 개발했음
CPU의 WORD가 16비트였기 때문에, 어셈블리어에서도 WORD를 16비트 자료형으로 정의하는 것이 자연스러웠음

이후에 개발된 IA-32, x86-64 아키텍처는 CPU의 WORD가 32비트, 64비트로 확장됨
이 둘의 아키텍처에서는 WORD 자료형이 32비트, 64비트의 크기를 지정하는 것이 당연할 것

하지만 인텔은 WORD 자료형의 크기를 16비트로 유지함 (새로운 아키텍처 호환 여부)

DWORD(Double Word, 32bit), QWORD(Quad Word, 64bit) 자료형을 추가로 만듦

논리 연산 - and & or

# add
[Register]
eax = 0xffff0000
ebx = 0xcafebabe

[Code]
and eax, ebx

[Result]
eax = 0xcafe0000
# or
[Register]
eax = 0xffff0000
ebx = 0xcafebabe

[Code]
or eax, ebx

[Result]
eax = 0xffffbabe

논리 연산 - xor & not

[Register]
eax = 0xffffffff
ebx = 0xcafebabe

[Code]
xor eax, ebx

[Result]
eax = 0x35014541

xor dst, src: dst와 src의 비트가 서로 다르면 1, 같으면 0

- f0 e1 d2 c3 b4 a5

[Register]
eax = 0xffffffff

[Code]
not eax

[Result]
eax = 0x00000000

 

정리

종류 연산자 의미
데이터 이동 연산자 mov dst, src src의 값을 dst에 대입
데이터 이동 연산자 lea dst, src src의 유효 주소를 dst에 대입
산술 연산 add dst, src src의 값을 dst에 더함
산술 연산 sub dst, src src의 값을 dst에서 뺌
산술 연산 inc op op의 값을 1 더함
산술 연산 dec op op의 값을 1 뺌
논리 연산 and dst, src dst와 src가 모두 1이면 1, 아니면 0
논리 연산 or dst, src dst와 src 중 한 쪽이라도 1이면 1, 아니면 0
논리 연산 xor dst, src dst와 src가 다르면 1, 같으면 0
논리 연산 not op op의 비트를 모두 반전
비교 cmp op1, op2 op1에서 op2를 빼고 플래그를 설정
비교 test op1, op2 op1과 op2에 AND 연산을 하고, 플래그를 설정
분기 jmp addr addf로 rip 이동
분기 je addr 직전 비교에서 두 피연산자의 값이 같을 경우 addr로 rip 이동
분기 jg addr 직전 비교에서 두 피연산자 중 전자의 값이 더 클 경우 addr로 rip 이동

 

728x90
728x90
728x90
반응형

전처리(Preprocessing)

컴파일러가 소스 코드를 어셈블리어로 컴파일하기 전에, 필요한 형식으로 가공하는 것

add.c를 add.i로 전처리

$ gcc -E add.c > add.i
$ cat add.i

 

컴파일(Compile)

C로 작성된 소스 코드를 어셈블리어로 변역하는 것

소스 코드를 어셈블리 코드로 컴파일: "-S" 옵션

$ gcc -S add.i -o add.S
$ cat add.S

 

어셈블(Assemble)

컴파일로 생성된 어셈블리어 코드를 ELF 형식의 목적 파일(Object file)로 변환하는 과정

* ELF: 리눅스 실행파일 형식, PE: 윈도우 목적 파일 형식

목적 파일 변환 - 어셈블리 코드가 기계어로 번역되므로 더이상 사람이 해석하기 어려워짐

gcc "-c" 옵션을 통해 add.S를 목적 파일로 변환하고, 결과로 나온 파일을 16진수로 출력한 것

링크(Link)

여러 목적 파일들을 연결하여 실행 가능한 바이너리로 만드는 과정

디컴파일러(Decompiler)

-

정적 분석(Static Analysis), 동적 분석(Dynamic Analysis)

정적 분석

장점

1. 프로그램의 전체 구조를 파악하기 쉬움
2. 분석 환경의 제약에서도 비교적 자유로움
3. 바이러스와 같은 악성 프로그램의 위협으로부터 안전함

단점

1. 난독화(Obfuscation)가 적용되면 분석이 매우 어려워짐

2. 다양한 동적 요소를 고려하기 어려움

동적 분석

장점

1. 코드를 자세히 분석해보지 않고도 프로그램의 개략적인 동작을 파악할 수 있음

단점

1. 분석 환경을 구축하기 어려울 수 있음

2. 안티 디버깅(Anti Debudding)

if (is_debugging())
	exit(-1);
Func();

 

컴퓨터 구조(Computer Architecture)

컴퓨터가 효율적으로 작동할 수 있도록 하드웨어 및 소프트웨어의 기능을 고안하고, 이들을 구성하는 방법

폰 노이만 구조, 하버드 구조, 수정된 하버드 구조

명령어 집합구조(Instruction Set Architecture, ISA)

CPU가 처리해야하는 명령어를 설계하는 분야

(e.g., ARM, MIPS, AVR, 인텔의 x86 및 x86-64 아키텍처)

마이크로 아키텍처(Micro Architecture)

정의된 명령어 집합을 효율적으로 처리할 수 있도록, CPU의 회로를 설계하는 분야

 

> 폰 노이만 구조와 명령어 집합 구조(Instruction Set Architecture) 중 x86-64 아키텍처

폰 노이만: 초기 컴퓨터 과학자 중 컴퓨터에 연산, 제어, 저장의 세 가지 핵심 기능이 필요하다고 생각함

근대의 컴퓨터는 연산과 제어를 위해 중앙처리장치(Central Processing Unit, CPU)를, 저장을 위해 기억장치(memory)를 사용함

장치간에 데이터나 제어 신호를 교환할 수 있도록 버스(bus)라는 전자 통로를 사용함

중앙처리장치

산술논리장치(Arithmetic Logic Unit, ALU): CPU의 산술/논리 연산 처리

제어장치(Control Unit): CPU 제어

레지스터(Register): CPU에 필요한 데이터 저장

기억장치

컴퓨터가 동작하는데 필요한 여러 데이터를 저장하기 위해 사용됨

주기억장치: 프로그램 실행과정에서 필요한 데이터들을 임시로 저장하기 위해 사용 (e.g., 램(Random-Access Memory, RAM)

보조기억장치: 운영 체제, 프로그램 등과 같은 데이터를 장기간 보관하고자 할 때 사용 (e.g., 하드 드라이브(Hard Disk Drive, HDD), SSD(Solid State Drive))

버스

컴퓨터 부품과 부품 사이 또는 컴퓨터와 컴퓨터 사이에 신호를 전송하는 통로

데이터 버스(Data Bus): 데이터 이동

주소 버스(Address Bus): 주소 지정

 

  • 범용 레지스터(General Register)
  • 세그먼트 레지스터(Segment Register)
  • 플래그 레지스터(Flag Register)
  • 명령어 포인터 레지스터(Instruction Pointer Register, IP)

 

728x90
728x90

+ Recent posts