ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Assembly Language 어셈블리어
    CTF/KNOWLEDGE 2021. 1. 14. 20:41

    PRELIMINARY 서론


    어셈블리어는 기계어와 일대일 대응되는 저수준 프로그래밍 언어이다. 고수준 언어로 작성된 소스 코드를 컴파일하면 어셈블리어로 변환되고, 어셈블러를 통해 이를 다시 기계어로 대응시키면 바이너리 파일이 만들어지게 된다. [1]

     

    프로그램 실행 과정 [2]

    바이너리 파일이 주어졌을때, 원형이 되는 소스 코드가 무엇인지는 정확히 알 수는 없다. 그러나 시스템 해킹(pwnable)을 위해서는 프로그램이 어떻게 동작하는지 알아야 하고, 바이너리를 역으로 분석하여 작동 과정을 알아내는 리버스 코드 엔지니어링(reversing)이 필요하다.


    IDA나 Ghidra와 같은 디컴파일러를 통해 코드의 윤곽을 유추하는데 도움을 받을 수 있지만, 디스어셈블된 어셈블리 코드는 프로그램에 대해 많은 정보를 가지고 있으므로 어셈블리어에 대한 이해는 중요하다.

     

    어셈블리어의 구성요소인 명령어(instruction)와 레지스터(register)에 대해 정리해보자.

    Register 레지스터 


    레지스터는 컴퓨터 프로세서가 빠르게 접근할 수 있는 저장 공간으로, 현재 계산중인 값을 저장하거나 메모리 영역의 주소를 보관하는 등의 역할을 수행한다. 레지스터마다 정해진 명칭과 역할이 존재하므로, 각각이 어떤 기능을 담당하는지 숙지하면 어셈블리어를 이해하는데 큰 도움이 될 것이다. [3]

     

    레지스터 문단을 작성하면서 참고한 글은 다음과 같다. [4][5][6][7]

    General Purpose Register 범용 레지스터

    산술 연산 레지스터는 각종 산술 연산과 입출력 등에서 범용적으로 사용된다.

    • EAX(Accumulator) : 산술 연산에 사용되는 상수, 변수와 함수의 리턴값을 저장
    • EBX(Base Index) : 배열의 인덱스와 같이 데이터를 가리키는 포인터
    • ECX(Counter)  : 반복문의 반복 횟수를 저장하거나 shift/rotate 명령어에 사용
    • EDX(Data) : AX와 같이 산술 연산에서 사용되거나 입출력 포트 접근에 사용

     

    인덱스 레지스터는 배열의 복사나 비교와 같이 source와 destination이 있는 연산에서 포인터로 사용된다.

    • ESI(Source Index) : 해당 연산에서 source를 가리키는 포인터
    • EDI(Destination Index) : 해당 연산에서 destination를 가리키는 포인터

     

    포인터 레지스터는 스택 영역의 특정 주소를 가리킨다.

    • ESP(Stack Pointer) : 스택의 맨 위를 가리키는 포인터
    • EBP(Base Pointer) : 스택의 기준점을 가리키는 포인터, 한 함수 호출 내에서 변하지 않음

    Instruction Pointer 명령 포인터 레지스터

    • EIP(Instruction Pointer) : 다음 명령어의 주소를 저장

    Segment Register 세그먼트 레지스터

    컴퓨터 메모리 영역의 관리를 위해 사용되는 레지스터이다.

    • SS(Stack Segment) : 스택 영역을 가리키는 포인터
    • CS(Code Segment) : 코드 영역을 가리키는 포인터
    • DS(Data Segment) : 데이터 영역을 가리키는 포인터
    • ES(Extra Segment) : 위의 레지스터를 변경하는 것이 어려운 상황에서 예비로 사용되는 포인터
    • FS(F Segment) : 다른 예비 포인터 ('E' 다음이 'F')
    • GS(G Segment) : 또다른 예비 포인터 ('F' 다음이 'G')

    EFLAGS Register 상태 레지스터

    Extended-Flag 레지스터는 기존의 16bit 상태 레지스터가 확장된 것으로, 프로세서의 상태와 각종 연산의 결과를 boolean 형태로 저장하는 flag들을 한군데에 모아놓은 레지스터이다.

    • 0, CF(Carry Flag) : 덧셈이나 뺄셈에서 올림, 버림이 발생했을 때 true
    • 2, PF(Parity Flag) : 연산의 결과에서 켜진 비트의 개수가 짝수일 경우 true
    • 4, AF(Auxiliary Carry Flag) : BCD(이진화 십진법) 연산에 사용되는 Carry Flag이다. 
    • 6, ZF(Zero Flag) : 연산의 결과가 0일 경우 true
    • 7, SF(Sign Flag) : 연산의 결과가 0보다 작을 경우 true
    • 8, TF(Trap Flag) : true일 경우, 한 명령어가 실행될 때마다 인터럽트가 발생한다. 디버깅에 사용된다.
    • 9, IF(Interruption Flag) : true일 경우 인터럽트 요청을 받아들인다.  
    • 10, DF(Direction Flag) : 문자열 조작의 진행 방향을 조작한다. true일 경우 주소가 증가하는 대신 감소한다.
    • 11, OF(Overflow Flag) : 부호 있는 산술 연산의 결과가 레지스터의 크기를 초과할 경우 true

     

    FLAGS layout

    위에서 설명한 x86 Architecture 상의 레지스터들을 한눈에 정리해보면 다음과 같다.

    x86 Architecture Register Layout

    x64 Architecture

    x64 Architecture 상의 레지스터는 R(register)로 시작하고, 64-bit로 크기가 확장되었다. 32-bit와 대응하는 레지스터는 E(extended)로 시작하며, 이는 기존 16-bit 레지스터에서 확장되었음을 의미한다. 또한 AX와 같은 16-bit 레지스터는 상위 8-bit(AH)와 하위 8-bit(AL)로 나뉘게 된다. 

    64-bit 레지스터 구조 [8]

    새로 추가된 레지스터의 기능과 기존 레지스터의 변경 사항은 다음과 같다.

    • RBP : 더이상 base pointer의 역할을 하지 않고, 다른 범용 레지스터처럼 활용된다. 
    • R8~R15 : 새로 추가된 8개의 범용 레지스터, R8D(32-bit), R8W(16-bit), R8B(8-bit)와 같은 크기로도 사용
    • RDI, RSI, RDX, RCX, R8, R9 : 순서대로 처음 여섯 함수 인자가 저장된다.

     

    함수 인자와 레지스터에 대한 자세한 내용은 스택 프레임 파트에서 다룬다.

    Instruction 명령어


    어셈블리에는 Intel과 AT&T의 두가지 문법이 존재한다. 디버깅에 사용할 도구인 pwngdb가 기본적으로 Intel 문법을 지원하므로 아래의 모든 명령어는 Intel 문법을 기준으로 작성하겠다.

     

    명령어 문단을 작성하면서 참고한 글은 다음과 같다. [9]

     

    Main Instructions 주요 명령어

    • PUSH dest : 스택의 맨 위에 dest를 저장한다. 따라서 esp가 감소하고 esp에 dest 값이 저장된다.
    • POP dest : 스택의 맨 위 값을 dest에 저장한다. esp가 가리키는 값이 dest에 저장되고 esp가 증가한다.
    • MOV dest src : src의 값을 dest에 저장한다.
    • LEA dest src : src의 메모리 주소를 dest에 저장한다.

    lea rbp [rax+0x1] 은 mov rbp rax, add rbp 0x1과 같다. [ ]는 포인터와 같은 역할을 하므로 mov rbp [rax+0x1]은 rax+0x1이 가리키는 값을 rbp로 옮기는 역할을 하지만, lea 명령어를 통해 주소 rax+0x1 자체를 rbp로 옮길 수 있다. mov rbp rax+0x1과 같이 레지스터에 직접 연산하는 것은 문법적으로 허용되지 않기 때문에 이러한 명령어를 만든 것으로 추정된다. [10]

    • CALL label : 지정한 label로 jump한다. 

    CALL은 JMP와 비슷하지만 함수 호출에 사용되며, 호출이 끝난 다음에 실행될 명령을 스택에 return address로 저장한다는 특징이 있다. call label은 push eip, jump label과 동치이다.

    • RET : esp가 가리키는 값을 eip에 저장한다. 

    함수 에필로그에서 esp는 return address를 가리키게 되므로 RET 명령어 이후에는 CALL에서 저장했던 다음 명령이 실행된다. ret은 pop eip, jmp eip와 동치이다.

    • NOP : 아무 일도 하지 않는다(No operation). 특정 부분을 공백처리하기 위해 사용한다.

    NOP 명령어는 이를 활용한 공격 기법인 NOP sled에서 자세히 다룰 것이다.

    • INT label : 지정한 label의 인터럽트를 발생시킨다.

    int 0x80은 시스템 콜을 호출하는 명령어로, 쉘코드를 작성할 때 자주 등장한다.

    Conditional Instructions 조건 명령어

    • CMP dest src : dest에서 src를 빼는 연산을 수행한다.
    • JMP label : 지정한 label로 jump한다.
    • JZ label : 비교한 결과가 같으면 label로 jump한다. (ZF=1)

     

    부호가 있는(signed) 데이터의 비교

    • JG label : dest가 크면 label로 jump한다. (ZF=0 and SF=OF)
    • JGE label : dest가 크거나 같으면 label로 jump한다. (SF=OF)

     

    부호가 없는(unsigned) 데이터의 비교

    • JA label : dest가 크면(above) label로 jump한다. (CF=0 and ZF=0)
    • JB label : dest가 작으면(below) label로 jump한다. (CF=1)

     

    플래그의 값을 통한 비교

    • JC label : CF가 1이면 jump한다. 
    • JNO label : OF가 0이면 jump한다.

    E는 equal을, N은 not을 의미하므로 JL은 JNGE, JZ는 JE 등으로 대체할 수 있다.

    Arithmetic Operation 산술 연산

    • ADD dest src : src의 값을 dest에 더한다. (dest = dest + src)
    • SUB dest src : src의 값을 dest에서 뺀다. (dest = dest - stc)

    Logical Operation 논리 연산

    • AND dest src:  bitwise AND 연산의 결과를 첫 번째 피연산자에 저장한다. (dest = dest & src)
    • OR dest src :  bitwise OR 연산의 결과를 첫 번째 피연산자에 저장한다. (dest = dest | src)
    • XOR dest src :  bitwise XOR 연산의 결과를 첫 번째 피연산자에 저장한다. (dest = dest ^ src)
    • TEST dest src :  bitwise AND 연산을 수행한다.
    • NOT dest :  bitwise NOT 연산의 결과를 피연산자에 저장한다. (dest = !dest)

    Type Specifier (BYTE, WORD, DWORD, QWORD) 타입 지정자

    어셈블리를 분석하다 보면 다음과 같이 ~ PTR 이라는 표현을 볼 수 있을 것이다.

     

    이때 PTR은 포인터를 의미하고, 그 앞에 붙는 접두사는 포인터가 가리키는 대상의 크기를 의미한다. 따라서 위 명령어는 주소 rsp+0x1008에 존재하는 8바이트 만큼의 값을 rax에 넣으라는 뜻으로 해석할 수 있다.

     

    BYTE(1byte), WORD(2byte), DWORD(double word, 4byte), QWORD(quad word, 8byte)라는 명명법은 초창기 16-bit가 기본 데이터 처리 단위이던 시절, 이 단위를 WORD라는 자료형으로 이름붙인 것에서 시작되었다.

     

    Reference

    [1] en.wikipedia.org/wiki/Assembly_language  

    [2] blog.hexabrain.net/2

    [3] en.wikipedia.org/wiki/Processor_register  

    [4] ece-research.unm.edu/jimp/310/slides/micro_arch1.html  

    [5] en.wikibooks.org/wiki/X86_Assembly/X86_Architecture

    [6] blog.naver.com/mjnms/220460825993

    [7] peemangit.tistory.com/37?category=820239

    [8] kuaaan.tistory.com/449

    [9] www.tutorialspoint.com/assembly_programming/index.htm  

    [10] stackoverflow.com/questions/1699748/what-is-the-difference-between-mov-and-lea

    'CTF > KNOWLEDGE' 카테고리의 다른 글

    CTF 사이트 만드는 방법 / How to make CTF site  (0) 2021.02.12
    Memory Mitigation 메모리 보호기법  (0) 2021.01.19

    댓글

Designed by Tistory.