리버싱 1차시

k4bunny·2025년 5월 26일

Layer7

목록 보기
6/13

리버싱

리버싱이란?

리버스 엔지니어링(Reverse Engineering)

Reverse - 뒤집다
Engineering - 공학

  • 역공학이라고 해석 가능
  • 완성된 프로그램을 해체하고 분석하여 구조와 기능, 디자인을 파악하는 기술을 의미
  • 리버싱은 각종 악성코드나 불법 프로그램에 대응을 위해 사용
  • 구조, 기능, 동작 등을 역으로 추적하여 분석하고 원리를 이해하며 부족한 부분을 보완하며 새로운 기능 등을 추가하는 작업

리버싱 방법

  • 정적 분석
    파일의 겉모습을 관찰하여 분석하는 방법
    파일을 열지 않고 파일 종류, 헤더, 디스어셈블리어, 디컴파일러로 분석
    디스어셈블러를 이용해서 내부코드와 구조를 확인하는 방법
  • 동적 분석
    파일을 실행하며 코드 흐름과 메모리 상태 등으로 분석하는 방법
    레지스트리, 네트워크 등을 관찰하면서 프로그램의 행위를 분석
    디버거를 이용하여 프로그램 내부 구조와 동작 원리를 분석

리버싱을 배우기 위해 필요한 지식

  • 컴퓨터 구조
  • ISA
  • Byte Ordering
  • Encoding/Decoding
  • 운영 체제
  • 메모리 구조, 컴파일, 인터프리터

어셈블리어 (Assembler)

구조

section.data

  • 데이터 영역
  • 초기값이 있는 데이터를 저장 (문자열, 상수 등)

section.bss

  • 비어 있는 데이터 영역
  • 초기값 없이 공간만 필요한 변수를 저장 (입력 버퍼 등)

section.text

  • 코드 영역
  • 명령어가 들어가는 부분 (기계어로 번역될 명령어/코드)

section.text

global_start;

  • 링커에게 시작할 위치를 알려줌

_start:

  • 실행할 명령어 작성
  • 프로그램이 실행될 때 가장 먼저 실행되는 지점
  • C언어의 main() 함수와 비슷하지만, 운영체제가 직접 호출하는 주소

아키텍처 (Architecture)

아키텍처란?

  • CPU가 명령어를 처리하는 방식을 나타냄
  • 하드웨어 시스템의 전반적인 구조와 동작을 나타냄

주요 아키텍처 비교

			x86			x86-64		ARM				ARM64
주소 크기	32bit		64bit		32bit			64bit
레지스터     EAX,EBX		RAX,RBX		R0~R15			X0~x30
엔디안		Little		Little		Little / Big	Little (일반적으로)

x86-64는 x86 아키텍처와 호환되는 64bit 아키텍처

  • 32 / 64bit 아키텍처 -> 32 / 64bit는 CPU가 한 번에 처리 할 수 있는 데이터의 크기

WORD

  • 하나의 기계어 명령어, 연산을 통해 저장된 장치에서 컴퓨터 프로세서로 옮겨 놓을 수 있는 데이터 단위
  • WORD의 길이는 컴퓨터의 데이터 버스 크기와 같음
  • 한번의 작업으로 저장장치에서 프로세서 레지스터로 데이터를 이동시킴

-> CPU가 한 번에 처리할 수 있는 데이터의 크기

x64 Register

레지스터의 종류

범용 레지스터

데이터 연산을 위해 사용되는 레지스터

  • EAX 산술 연산 및 논리 연산 수행 + 함수의 반환값 저장
  • EBX 메모리 주소 저장
  • ECX 반복문 사용 시 카운터로 사용
  • EDX EAX와 같이 사용 + 큰 수의 곱셈과 나눗셈 연산
  • EDI 복사할 때 목적지 주소 저장
  • ESI 데이터를 조작하거나 복사할 때 데이터의 주소 저장
  • ESP 메모리 스택의 끝 지점 주소 포인터
  • EBP 메모리 스택의 첫 지점 주소 포인터
  • EIP 다음에 실행해야 할 명령어의 주소 포인터

세그먼트 레지스터

아키텍처 메모리를 세그먼트 단위로 접근할 때 사용되는 특수한 레지스터

  • CS 기계 명령 포함 코드 세그먼트의 시작 주소를 가리킴
  • DS 프로그램에 정의된 데이터 영역의 시작 주소를 가리킴
  • SS 연산 결과 등을 임시로 저장 또는 삭제할 때 사용하는 스택 영역의 시작부분을 가리킴
  • ES 추가로 사용된 데이터 세그먼트의 주소를 가리킴
  • FS 여분 레지스터
  • GS 여분 레지스터

플래그 레지스터

CPU가 연산을 수행한 후 결과의 상태를 저장하는 특수한 레지스터

-> 조건문 등에 사용되어짐

ZF 연산결과가 0일 경우 참
CF 부호 없는 숫자의 연산 결과가 비트 범위를 넘으면 참
AF 연산 결과 하위 4 bit에서 비트 범위를 넘으면 참
OF 부호 있는 숫자의 연산 결과가 비트 범위를 넘으면 참
SF 연산 결과가 음수면 참
PF 연산 결과에서 1로 된 비트의 수가 짝수면 참
DF 문자열 조작에서 참이면 레지스터 값 감소, 거짓이면 증가
TF 디버깅에 사용

명령어 포인터 레지스터

  • 프로그램이 기계어로 이루어져 있을 때, CPU가 실행시킬 코드를 가리킴
  • 명령어 레지스터는 rip이며, 8byte의 크기를 지님

함수 호출 규약 (Calling Convention)

  • 함수 호출 시 호출자와 피호출자 간에 어떻게 데이터를 주고받을지에 대한 규칙
  • 함수에 인자를 전달 방법 / 반환값 저장 위치 / 함수 호출 전후에 레지스터나 스택을 다룰 방법에 대해 정해 놓은 약속

x86 호출 규약

  • Cdecl
    인자 전달은 오른쪽에서 왼쪽으로 전달하고 호출자
    기본 C언어 규약, 여러 인자를 지원하며 인자 함수를 지원
  • Stdcall
    인자 전달은 오른쪽에서 왼쪽으로 전달하고 피호출자
    WinAPI에서 사용하고 함수가 끝나면 스택을 정리
  • Fastcall
    앞의 2~3개 인자는 레지스터에 전달하고 나머지는 스택에 전달하고 피호출자이며 ECX, EDX를 사용하고 성능 향상이 목적이다.
  • Thiscall
    this는 ECX에 전달하고 나머지는 스택에 전달하고 호출자
    C++ 클래스 멤버 함수용으로 사용

x86-64 프롤로그와 에필로그

  • 함수를 호출하면 컴파일러는 정확한 규칙을 따라 어셈블리 코드를 생성
  • 프롤로그(Prologue)와 에필로그(Epilogue)

프롤로그(Prologue)

  • push rbp --- 이전 함수의 프레임 포인터 저장
  • mov rbp, rsp --- 현재 스택 포인터를 기준 프레임 포인터로 설정
  • sub rsp, N --- 지역 변수나 정렬 공간 확보 (N은 16의 배수)

에필로그(Epilogue)

  • mov rsp, rbp --- 스택 포인터 복원
  • pop rbp --- 이전 프레임 포인터 복원
  • ret --- 호출자 주소로 복귀

함수가 끝나면 호출한 쪽(Caller)으로 돌아가야 함

과제

Hello Layer7 12번 출력

section .data
    msg db 'Hello Layer7', 10 	; "Hello Layer7" 문자열 저장
    len equ $ - msg

section .text
    global _start

_start:
    ; 1
    mov eax, 4		; 시스템 호출 번호 4 : sys_write <- write 함수
	mov ebx, 1      ; 파일 디스크립터 1 : 출력 (stdout)
	mov ecx, msg    ; 출력할 문자열의 주소를 ECX에 이동
	mov edx, len    ; 출력할 길이를 EDX에 이동
	int 0x80        ; 커널 인터럽트를 호출해서 write 실행
    ; 2
    int 0x80
    ; 3
    int 0x80
    ; 4
    int 0x80
    ; 5
    int 0x80
    ; 6
    int 0x80
    ; 7
    int 0x80
    ; 8
    int 0x80
    ; 9
    int 0x80
    ; 10
    int 0x80
    ; 11
    int 0x80
    ; 12
    int 0x80

    ; exit
    mov eax, 1		; 시스템 호출 번호 1 : sys_exit
	xor ebx, ebx    ; ebx = 0 (exit 코드 0)
	int 0x80        ; 커널 인터럽트 호출 -> 종료

section .data

  • msg : 출력할 문자열 "Hello Layer7"에 줄바꿈 문자(ASCII 10, 즉 \n)
  • len : 문자열의 길이를 계산.
    $는 현재 주소, msg는 시작 주소 → $ - msg는 문자열 전체 길이

section .text

  • 프로그램의 시작을 _start 라벨을 외부(운영체제)에서 사용할 수 있도록 지정
  • Linux 커널이 실행을 시작할 때 _start 레이블을 기준으로 실행

_start:

  • 문자열 한 줄을 출력
  • int 0x80만 반복되어 총 12번 호출되며, 같은 메시지를 반복 출력

반복 구조

  • eax, ebx, ecx, edx 값이 바뀌지 않음
    따라서 int 0x80 반복 호출시 같은 메시지를 반복 출력
  • "Hello Layer7" 12번 출력
profile
배고파요 ..

0개의 댓글