본문 바로가기

Layer7

리버싱 3차시 과제(x64 어셈블리)

어셈블리어

기계어와 일대일 대응이 되는 저급 언어이다.

사람이 읽기 쉬운 C언어와 읽기 어려운 기계어의 중간 단계이고

기계어를 조금 더 쉽게 읽기 위해서 일대일로 문자화 해서 만들어졌다.

기계어와 어셈블리어는 서로 대응하기 때문에 명령어 집합 구조(ISA)의 종류에 따라 어셈블리어도 달라진다.

 

명령어 집합 구조 (ISA: Instruction Set Architecture): CPU가 해석하고 실행할 수 있는 모든 기계어 명령어들을 포함한다. 설계 방식에 따라 CISC와 RISC로 나뉘는데 CISC는 x64, x86등이 있고, RISC는 ARM,MIPS등이 있다.

 

 

x64

CISC방식의 ISA(명령어 집합 구조) 중 하나이다.

반도체 회사인 Intel과 AMD 등이 사용하며, x86과 함께 압도적인 CPU의 점유율를 보유하고 있다.

x64는 x86의 확장 버전, 즉 상위 버전이기 때문에 x86을 완벽하게 지원하지만 반대로 x86은 x64보다 하위 버전이므로 지원하지 못한다. 그리고 x64는 x86-64, AMD64, Intel64등 여러 개의 명칭으로 불리는데 거의 다 x64를 의미하고 앞에 두 회사에서 각각 수정하며 이름을 바꾼 것이다.

 

 

x64 레지스터

 

레지스터(Register): 메모리 계층 제일 위에 있는 메모리이며, CPU안에 들어있다.

용량이 매우 작지만 연산속도가 매우 빠르기 때문에 일반적으로 현재 연산중인 값, 명령어의 주소, 스택 프레임의 위치같이 이 제일 필요한 값들이 저장된다.

 

 

<레지스터의 크기>

 

레지스터의 크기

 

x64:  - 64bit = 8Byte (8B)

         - R로 시작함 (ex: RAX, RBX, R8..)

         - R8~R15가 있음 (x86에는 없음)

         - 최소 단위 QWORD (Quad WORD)

      

 

x86: - 32bit = 4Byte (4B)

        - E로 시작함 (ex: EAX, EBX, ECX..)

        - 최소단위 DWORD (Double WORD)

        - EAX~EDX (4B)를 반으로 나눈 AX, BX, CX, DX(2B)

        - 반으로 나눈 AX~DX 중 상위(high) : AH~DH(1B)

                                                하위(low)  :  AL~DL(1B)

         (EAX가 AX를 포함, 그 AX가 AH, AL을 포함하고 있는 형태, 시스템 호환을 위해 설계된 형태이다.)

 

 

WORD:  16bit 컴뷰터가 레지스터에 옮겨 놓을 수 있는 데이터 최소 단위이다.

 

               WORD(16bit) -> DWORD(32bit) -> QWORD(64bit)

 

 

<레지스터의 종류>

 

범용 레지스터: 범용적으로 사용되는 레지스터이다. 권고되는 역할이 있지만 참고만 해도 되고 아무렇게나 써도 된다.

 

RAX Accumulator -산술/논리 연산을 수행
-함수의 return값이 저장됨
RBX Base 메모리 주소를 저장하기 위한 용도로 사용
RCX Count 반복문에서 카운터 변수로 사용
고급언어 for문의 i, j와 같은 역할
RDX Data 다른 레지스터 서포트하는 여분 레지스터
큰 수의 곱셈/나눗셈에서 RAX랑 사용
R8 ~ R15   다용도인 여분 레지스터

 

 

 

인덱스 레지스터: 데이터가 저장되어 있는 메모리 주소를 가리키는 포인터로 사용되는 레지스터이다.

 

RSI Source 데이터를 복사할 때 
복사할 데이터(원본)출발지 주소 저장됨
RDI Destination 데이터를 복사할 때 
복사된 데이터 목적지 주소가 저장됨

 

 

 

포인터 레지스터: 프로그램 실행 과정에서 사용하는 특정 메모리 위치 주소를 가리키는 레지스터이다.

 

RSP Stack Pointer
현재 사용중인 스택에 끝 지점 주소,
즉 맨 위(top)를 가리킨다.
RBP Base Pointer 현재 사용중인 스택의 시작 지점 주소,
즉  맨 아래(스택 복귀 주소)를 가리킨다.
RIP Instruction Pointer 프로그램카운터의 역할을 한다.
프로그램 카운터는 다음에 실행될
명령어가 위치한 주소를 가리킨다.

 

 

 

함수의 매개변수로서의 레지스터: 프로그램이 실행될 때 여러 가지 함수들의 인자 전달을 하는 레지스터이다.

 

출처: https://velog.io/@sdc337dc/9.-%EB%A7%A4%EA%B0%9C%EB%B3%80%EC%88%98parameter%EC%99%80-%EC%9D%B8%EC%9E%90argument

 

매개변수(parameter): 호출된 함수에서 넘어온 값

인자(argument) : 함수가 호출될 때 넘기는 변숫값 

 

호출 규약: 함수 호출 시 일어나는 행동에 대한 규칙을 의미하며 함수 인자들에 대해 어떤 순서로 스택에 쌓을 것 인지, 인자를 레지스터로 이용할 것 인지 그리고 함수 종료 후 스택을 누가 정리할 것 인지에 대해 정해놓은 약속이다.

(ex: __cdecl, __stdcall, __fastcall, __thiscall)

 

출처: https://jasonryu.tistory.com/93

 

x64
순서대로 지정된 레지스터에 들어감,
r9까지 들어가면 나머지는 스택에 쌓음
int function(int a, int b, int c, int d, int e, int f, int g, int h)

                        |        |                |         |         |           |  
                      rdi     rsi      rdx    rcx     r8       r9      STACK
x86

레지스터 사용 X
모두 스택에 쌓음
int function(int a, int b, int c, int d, int e, int f, int g, int h)
                                                       |
                                                  STACK
                    <------------------------------------------------------------
                                                                 
                                                                      STACK      ^
                                                                         int a        | 
                                                                         int b        |
                                                                         int c        |
                                                                         int d        |
                                                                         int e        |
                                                                         int f         |
                                                                         int g        |
                                                                         int h        |

                                                                         

 

 

 

플래그 레지스터 : 일반적인 레지스터와 다르게 여러 가지의 특정한 상태 값을 저장하는 레지스터이다. 이 레지스터 안에는 하나하나의 비트가 각각 서로 다른 의미를 갖고 있어서 각각 독립적으로 사용된다. 0(거짓) or 1(참)의 상태를 가진다.

 

CF Carry Flag 부호 없는 수의 연산 결과가
비트의 범위를 넘을 경우(자리올림)
1이 됨
ZF Zero Flag    연산 결과가 0일 경우 1이 됨
SF Sign Flag 부호 있는 수의 연산 결과가
음수일 경우 1이 됨
(양수면 0)
OF Overflow Flag 부호 있는 수의 연산결과가
비트의 범위를 넘을 경우(=오버플로우)
1이 됨

OF: 오버플로우 원리로 최대 숫자(01111111)에 +1을 더하면 최소 숫자(10000000, 맨 앞에 부호 비트가 건드려져 음수)가 됨)

 

 

 

스택 프레임

 

<메모리의 구조>

 

출처:http://www.tcpschool.com/c/c_memory_structure

 

스택(Stack): - 함수의 지역변수(함수 안에서 정의된 변수여서 함수 밖에서는 못씀)

                    - 높은 주소 -> 낮은 주소 방향(힙과 반대방향)

                    - 후입 선출(LIFO)의 특성을 가진 자료구조이며, 푸시(PUSH) 연산과 팝(POP) 연산을 이용

 

 

<함수의 스택 프레임>

 

스택 프레임(Stack Frame): - 함수가 호출될 때, 그 함수만의 스택 영역을 구분하기 위해 생성되는 공간

                                            - 이 공간에는 함수와 관계되는 지역변수, 매개변수가 저장됨,

                                            - 함수 호출 시 할당되고, 함수 종료 시 소멸됨

                                            - 이 영역을 표현하기 위해 함수 프롤로그(Prolog) 함수 에필로그(Epilog)를 수행

 

포인터 레지스터 부분에 있었던 RBP는 현재 사용 중인 프레임의 맨 아래, RSP는 맨 위를 가리킨다.

 

 

함수 프롤로그(Prolog)

 

1. 현재 실행 중인 main이 func를 호출하면

2. func의 스택 프레임을 만들기 위해 RSP에 RBP를 올리고(main의 맨 아래에 있었던 RBP가 main의 맨 위로 가서 func의 맨 아래를 가리킴)

3. RSP를 func의 프레임 크기만큼 올려서 RBP와 RSP 사이의 공간, 즉 스택 프레임이 만들어진다.

 

 

함수 에필로그(Epilog)

 

1. func 함수의 실행이 끝났을 때

2. 더 이상 필요 없어진 func 함수 프레임을 소멸시키기 위해서 RSP가 다시 main 맨 위로 내려오고

3. RBP는 main의 맨 아래로 돌아가서 main 함수 프레임의 시작 부분(경계)을 가리킴

*그런데 RBP가 main함수의 경계위치로 돌아가는 것이 어려움

-> 스택 프레임의 구조를 보면 어떻게 돌아갈 위치를 찾는지 알 수 있음

 

 

<스택 프레임의 구조>

 

func 함수 스택 프레임

SFP : main 함수 프레임의 맨 아래(경계)의 주소가 저장됨 -> 다시 돌아갈 때 SFP에 있는 주소를 RBP가 꺼내서 넣기 때문에  돌아갈 수 있음

 

RET: main에서  func으로 이동하기 전에, 실행되고 있던 main함수가 몇 번째 명령어까지 실행하고 있었는지의 주소가 저장됨, 즉 RIP의 값이 만들어짐( func 함수가 다 실행되면 RET에 저장된 주소가 main함수로 돌아갔을 때 다음으로 이어서 실행해야 할 명령어의 주소를 의미함)

 

 

어셈블리어 명령어

 

 

<산술 명령어>

and rax, rdx    ->  rax = rax & rdx

or rax, rdx       ->  rax = rax | rdx

xor rax, rdx     ->  rax = rax ^ rdx

cmp rax, rdx   ->  raxrdx의 결과를 플래그 레지스터에 저장

test rax, rdx    ->  rax & rdx의 결과로 ZF 설정(1 or 0)

 

 

 

<데이터 이동 명령어>

movA, B     ->      B의 값을 A에 복사

lea A, B       ->     B의 주소를  A에 복사(* [ ] =  주소에 있는 값에 접근)

                            좌변엔 레지스터, 우변엔 주소(상수 X)

push A       ->      스택 맨 위에 A를 올림(RSP가 한 칸 올라감)

pop B         ->      스택 맨 위 값을 B에 넣고 스택에서 삭제(RSP 한 칸 내려감)

 

 

 

<흐름 제어 명령어>

call func      -> func 함수 호출

ret(return)    -> 호출된 함수에서 호출한 함수로 복귀

jmp               -> 조건에 따라 특정한 곳으로 분기(점프)할지 결정

 

 

jmp 명령어

https://8jz5.tistory.com/50