블로그 이전했습니다. https://jeongzero.oopy.io/
리버싱 기초 with 어셈블리종류
본문 바로가기
보안/Reversing

리버싱 기초 with 어셈블리종류

728x90


 

1. 리버싱이란?

 

 

일반적으로 특정 언어로 개발을 하면 해당 소스코드가 컴파일 과정을 거쳐서 어셈블리어가 되고 컴퓨터가 알아들을 수 있는 바이너리 형태로 변경이되서 실행이 된다. 리버싱이란 이러한 과정을 역으로 분석하여 원래의 소스코드를 얻어내는(?) 과정을 말한다. 

 

개발자가 만들 프로그램에 대해서는 개발자 이외엔 아무도 모르지만,  

리버싱 엔지니어링을 통하여 어느정도 프로그램에 대한 분석이 가능하다. 특히 만약 해당 프로그램이 악성코드 일 경우 리버싱을 통하여 악성코드를 분석하여 동작 원리 등을 파악이 가능하고, 이를 이용해 피해를 예방할 수 있다. 


 

2. 어셈블리란?

  • 우리가 보통 프로그램을 개발할때 사용 하는 C, C++, 자바 등의 언어들을 보통 고급언어라고 한다. 여기서 말하는 고급언어란 컴퓨터가 직접 인식하지 못하여 기계어로의 변환이 필요한 언어들인데, 이러한 과정을 컴파일한다라고 하고 변환을 해주는 주체를 컴파일러라고 한다.

 

2.1 고급언어란?

  • 고급언어는 사람이 알기 쉽도록 구현된 프로그래밍 언어로서, 저급언어보다 가독성이 높고 다루기 간단하단느 장점이 있다. 컴파일러나 인터프리터에 의해 저급언어로 변환되어 실행이 되는 것이다.

 

2.2 저급언어란?

  • 저급언어는 컴퓨터 내부에서 바로 처리 가능한 프로그래밍 언어로, 일반적으로 기계어와 어셈블리어 언어가 있다. 0과 1로만 나태내는 언어를 바로 긱계어라고 한다. 컴퓨터는 0,1로만 인식되고 이러한 기계어들은 사람이 알 수 있는 형식으로 만들 필요가 있었는데 이렇게 기계어를 문자적으로 표현한 것이 어셈블리어라고 한다

 

  • 어셈블리어는 숫자가 0,1로 이루어진 명령어를 ADD, MOV와 같은 문자로 표현한언어이다. 고급언어의 컴파일과 같은 느낌으로다가 어셈블리어를 컴퓨터가 알 수 있는 기계어로 번역해 주어야 하는 과정이 필요한데, 이러한 과정을 어셈블이라고 한다. 어셈블리어를 기계어로 번역하는 주체는 어셈블러 라고 하는 언어 번역기가 담당한다

 

 

2.3 고급언어 VS 저급언어

저급 언어는 기계중심적인 언어로서 기계어와 일대일 대응이 되는 컴퓨터 프로그래밍 언어이다. 컴퓨터 아케텍처마다 사용하는 기계어가 다르고, 기계어에 대응되어 만들어지는 어셈블리어도 각각다르다. 그렇기 때문에 A회사 CPU에 대응되는 어셈블리어 코드를 B회사 CPU에 대응하지 못하고 각각 CPU에 맞게 코드를 작성해야 한다. 

 

이렇듯 코드를 각각 만들어야하는 불편함이 생겼고 그래서 나온 것이 고급언어이다. 고급언어란 위에서 설명한대로 사람들이 접근하기 쉬운 언어를 말한다. 

 

 

오른쪽 사진의 저급언어가 가지는 단점을 해결하기 위해 나온 것이 왼쪽 사진의 고급언어이다. 고급언어는 컴파일러에서 각 CPU에 맞게 번환만 해주기만 하면 CPU 마다 다르게 코드를 만드는 저급언어보다 훨신 효율적인 작업이 된다. 그래서 고급언어가 편한 이유가 이것이다 

 

3. 그렇다면 왜 어셈블리어를 알아야 하는가?

프로그램에 대한 실제 소스코드는 알 수 없어도 리버싱을 통해 CPU에서 처리되는 과정인 어셈블리어 코드를 통해 실제 소스코드를 유추할 수 있기 때문이다. 

 


 

4. CPU에 대해서

어셈블리어는 CPU에서 처리하는 것을 의미하는 언어이다. 그렇기에 CPU를 만든 제조사(AMD, Intel) 등 마다 표기 방식이 다를 수 있다. 대중적으로 가장 많이 사용하는 것은 Intel 사의 CPU이므로 Intel 기준으로 설명을 하겠다. 

 

Intel CPU (IA 32)의 구조 

  • IA32 : 인텔 아키텍처 (Intel Architecture 32bit)의 약자로 인텔 CPU 32비트를 의미
  • CPU : Central Processing Units의 약자로, 컴퓨터에서 계산을 처리하는 장치

 

 

4.1 CPU 구조

 

  • Register

    레지스터는 CPU 칩 내부에 존재하며 직접적으로 ALU에 연결되어 입출력 값을 저장하는 역할을 한다. 쉽게 말하면 CPU 내부의 데이터 임시 저장 공간이라고 보면 된다. 32비트 CPU에서 Register 공간은 32비트이다 

     

    레지스터 종류 

 

 

이러한 범용 레지스터들은 8/16/32/64 비트로 구분된다. 과거 16비트 도스 시절엔 응용 프로그램들이 16비트 레지스터를 사용했기 때문이다. 위 사진의 AX,BX,CD,DX가 바로 16비트 레지스터들 이다. 또한 이 16비트 레지스터들을 효율적으로 사용하기 위해 AH,AL과 같은 8비트로 나누어서 사용하였다. 

 

컴퓨터가 발전하면서 데이터의 사이즈도 커졌기 때문에 레지스터의 사이즈도 커졌다. 호환성을 위해서 16비트+16+16 이런식으로 확장되었다. 

 

💡
AH(8) → AX(16) → EAX(32) → RAX(64)

 

 

 

  • 제어장치

    제어장치는 컴퓨터에 있는 모든 장치들의 동작을 지시하고 제어하는 자치이다. 제어장치는 멍령 레지스터에서 읽어들인 명령어를 해독하여 해당하는 장치에 제어 신호를 보내 정확하게 수행하도록 제시한다 

     

    제어장치내안에도 레지스터들이 있다 

     

    1. EIP : 다음 실행 할 명령어의 주소를 저장하는 공간
    1. IR : 현재 실행 중인 명령어 주소를 저장하는 공간
    1. Eflags : 상태를 표현하는 수단

     

    Control Register(Eflags) 

    컨트롤 레지스터인 EFLAGS 레지스터는 각 비트마다 다른 의미를 지닌다. 각 비트는 1 or 0 의 값을 가지며 이는 on/off 혹은 True/False를 의미한다. 

     

    1. CF(Carry Flag)

      부호 없는 수의 오버플로가 발생할 경우 1로 세팅됨 

       

    1. PF(Parity Flag)

      연산결과, 1인 비트수가 짝수라면 설정된다 

       

    1. AF(Auxiliary Flag)

      CF가 Nibble에 대하여 적용된다 

       

    1. ZF(Zero Flag)

      연산 명령 후에 결과 값이 0일 시 ZF가 1로 세팅된다. 특히 if 같은 조건문에서 많이 사용된다 

       

    1. SF(Sign Flag)

      산술 및 논리 연산의 결과가 음수가 될 때 설정된다. 이 플래그의 값이 0 이면 양수, 1이면 음수이다. 

       

    1. TF(Trap Flag)

      하나의 명령어 처리후에 single-step interrupt를 발생시킨다 

       

    1. DF(Direction Flag)

      Increment or Decrement mode for 

       

    1. OF(Overflow Flag)

      부호가 있는 수의 오버플로가 발생할 경우 1로 세팅된다 

 


 

5. 데이터 크기 표현

 

  • 부호 없는 표현

     

  • 부호 있는 표현

 


 

6. 어셈블리어 종류

 

💡
데이터 복사
  • MOV
    • 형식

      MOV [Destination operand] [Source operand] 

    • 의미

      S의 값을 D로 복사 

    • 예제

      MOV ECX, EAX 

     

  • MOVZX
    • 형식

      MOVZX [Destination operand] [Source operand] 

    • 의미

      Source operand의 값을 Destination operand로 복사 한 후 채워지지 않은 나머지 공간을 0으로 채운다. 단 부호가 없는 경우에 사용된다 

    • 예제

      MOVZX ECX, AX 

      1. AX의 값을 ECX에 복사한다
      1. AX는 16비트이기 떄문에 ECX의 공간이 16비트 남게 된다. 이때 남은 공간이 0으로 채워진다

         

  • MOVSX
    • 형식

      MOVSX [Destination operand] [Source operand] 

    • 의미

      Source operand의 값을 Destination operand로 복사 한 후 채워지지 않은 나머지 공간을 부호비트 값으로 채운다 

    • 예제

      MOVSX ECX, EAX 

      1. EAX의 값을 ECX로 복사한다
      1. 이때 ECX의 남는 공간을 EAX의 부호비트 값으로 채운다

         

  • MOVS(문자열 복사)
    • 형식 : MOVS
    • 의미

      ESI가 가리키는 주소에 있는 값을 EDI가 가리키는 주소에 복사하라. 단 operand는 따로 없다 


 

 

💡
주소값 복사
  • LEA
    • 형식 : LEA [Destination operand] [Source operand]
    • 의미 : S의 주소값을 D로 복사한다

      LEA는 MOV와 다르게 값 자체가 아닌 주소값을 복사한다 

    • 예제

      MOV -> 데이터 값!! 복사 LEA -> 해당 operand 주소 값!! 복사 


 

 

💡
더하기
  • ADD
    • 형식 : ADD [operand1] [operand2]
    • 의미 : operand1 = operand1 + operand2

      (operand1 과operand2 를 더한 값을 operand1에 저장) 

    • 예제 : ADD EAX,5 ⇒ EAX = EAX + 5

 

 

💡
빼기
  • SUB
    • 형식 : ADD [operand1] [operand2]
    • 의미 : operand1 = operand1 - operand2

      (operand1 과operand2 를 뺀 값을 operand1에 저장) 

    • 예제 : ADD EAX,5 ⇒ EAX = EAX - 5

 

 

💡
곱하기
  • MUL - 부호 없는 곱셈
    • 형식 : MUL [operand]
    • 의미 : 기존의 EAX값과 operand 값을 곱하여 EAX에 결과 저장
    • 예제 : MUL EBX ⇒ Eax = Eax * EBX
  • IMUL - 부호 있는 곱셈
    • 형식 : IMUL [operand]
    • 의미

      기존의 EAX의 값과 operand 값을 곱하여 EAX에 결과를 저장한다. 계산을 하고 남은 비트를 부호비트로 채운다 


 

 

💡
나누기
  • DIV - 부호 없는 나누기
    • 형식 : DIV [operand]
    • 의미

      기존의 EAX의 값과 [operand] 값을 나누어 몫은 EAX에, 나머지는 EDX에 저장한다. 

    • 예제

      DIV EBX ⇒ EAX값과 EBX 값을 나누어 몫은 EAX에 저장하고 나머지는 EDX에 저장한다 

  • IDIV - 부호 있는 나누기
    • 형식 : IDIV [operand]
    • 의미

      기존의 EAX의 값과 [operand] 값을 나누어 몫은 EAX에 나머지는 EDX에 저장한다.  

       

      IDIV가 나타나기 전에 CBW, CWD, CDQ와 같은 부호비트를 확장하는 코드가 등장한다. 나눗셈을 진행하고 부호비트를 넣어줄 공간을 확보하는 데 기존 공간의 2배를 확장하게 된다. 

       

      CBW는 BYTE → WORD 로 변경 

      CWD는 WORD → DWORD로 변경 

      CDQ는 DWORD → QWORD로 변경 

       

      💡
      일반적으로 레지스터는 자신이 갖을 수 있는 범위보다 큰 범위를 요구하면 다른 레지스터를 통하여 확장한다. EDX는 다른 레지스터를 보조하는 역할을 한다. 각 레지스터는 32비트까지 저장가능한데, 만일 함수의 반환 값이 32비트를 넘어갈 경우 eax+edx이런 식으로 32비트를 넘어가는 반환 값을 나눠가진다. 이러한 서폿 역할을 EDX가 유일하게 한다

 

 

💡
증가/감소
  • INC - 증가
    • 형식 : INC [operand]
    • 의미 : [operand] 값에 1을 증가시킨다
    • 예제 : INC EBX ⇒ EBX의 값을 1 증가시킨다
  • DEC - 감소
    • 형식 : DEC [operand]
    • 의미 : [operand] 값에 1을 감소시킨다
    • 예제 : DEC EBX ⇒ EBX의 값을 1 감소시킨다

 

 

💡
반복
  • REP
    • 형식 : REP [OPCODE]
    • 의미

      특정 OPCODE를 반복시킨다. 이때 반복 카운터 레지스터인 ECX를 활용하여 반복 횟수를 지정한다. 


 

 

💡
논리 연산
  • XOR
    • 형식 : XOR [operand 1] [operand2]
    • 의미 : [operand1]을 [operand2]와 XOR 비트 연산을 수행한다. 이때, [operand1] [operand2]가 같다면 [operand]를 0 으로 초기화하는 역할을 수행한다
  • AND
    • 형식 : AND [operand 1] [operand2]
    • 의미 : [operand1]을 [operand2]와 AND 비트 연산을 수행한다
  • NOT
    • 형식 : NOT [operand]
    • 의미 : [operand] 비트를 0 으로 바꾼다
  • OR
    • 형식 : OR [operand 1] [operand2]
    • 의미 : [operand1]을 [operand2]와 OR 비트 연산을 수행한다

 

 

💡
비교 연산
  • CMP
    • 형식 : CMP [operand1] [operand2]
    • 의미 : 비교연산을 위하여 [operand1] - [operand2] 을 진행하고 값의 결과는 EFL(상태 레지스터)에 저장된다
      💡
      1. ZF CPU의 최근 연산 결과가 0 이면 1을 세팅하고 0이 아니면 0을 세팅 2. SF (sign Flag) CPU의 최근 연산 결과가 음수이면 1을 세팅하고 음수가 아니면 0 을 세팅
    • 예제 :
      1. CMP 10, 3 ⇒ [10-3] = 양수
        • ZF : 0
        • SF : 0
      1. CMP 3, 10 ⇒ [3-10] = 음수
        • ZF : 0
        • SF : 1
      1. CMP 3,3 ⇒ [3-3] = 0
        • ZF : 1
        • SF : 0
  • TEST
    • 형식 : TEST [operand1] [operand2]
    • 의미 : operand1 & operand2 진행한다. 두 operand가 모두 0 인지 아닌지 판단 할 수 있다
    • 예제 :

      TEST EAX, EAX와 같은 형태로 사용하여 이 값이 0인지 아닌지 확인할 떄 사용된다 


 

 

💡
데이터 분기
  • JMP
    • 형식 : JMP [operand]
    • 의미

      실행 가능한 코드의 주소의 operand로 이동한다. CMP와 같은 비교 연산 후에 JMP를 결정할 수 있다 

    • 예제
      1. CMP EAX, EAX 일 경우 두 값이 같으므로 JE를 통하여 OPER 구문으로 점프할 수 있다
      1. CMP 10, 3

        JL operand 

        ⇒ 10이 3보다 작으므로 operand로 점프한다 


 

 

💡
shift 비트 연산
  • SHL (SHR)
    • 형식 : SHL [opernad1] [operand2]
    • 의미 : [operand1]을 왼쪽(오른쪽)으로 [operand2] 방향만큼 민다
    • 예제

      SHL AL, 2  

      ⇒ AL 레지스터에 있는 값을 왼쪽(오른쪽)으로 비트 2만큼 민다 

  • ROL
    • 형식 : ROL [operand1] [operand2]
    • 의미 : [operand1]을 왼쪽으로 [operand2]만큼 민다. 만약 비트가 넘치면 뒤로 배치 받는다.

      (ROR은 그 반대) 


 

 

 

728x90

'보안 > Reversing' 카테고리의 다른 글

Bindiff 상세 기능 조사  (0) 2020.05.05
라이플캠페인 보고서 개인요약  (0) 2020.05.05
RTL 기법 파헤치기  (8) 2019.05.19
스택의 구조  (2) 2019.05.10
인텔 8086 메모리 구조  (0) 2019.05.10