리버싱 2차시

k4bunny·2025년 5월 30일

Layer7

목록 보기
7/13

함수 호출 규약 (Calling Convention)

  • 호출자와 피호출자 간에 데이터(파라미터)를 전달할 때의 규칙
  • 함수 호출 전후에 레지스터나 스택을 다룰 방법을 정해 놓은 약속

함수 호출 규약의 종류

CPU 아키텍처와 컴파일러 종류에 따라 호출 규약 역시 바뀜
EX) x64(32bit) / x64-86 (64bit)

x64(32bit)
레지스터를 통해 피호출자의 인자를 전달하기에는 레지스터의 수가 적어 스택을 이용하는 함수 호출 규약 사용

x86 호출 규약

Cdecl

  • 인자를 오른쪽에서 왼쪽 순서로 스택에 push
  • 함수 호출 이후, Caller(호출자)가 스택 정리
  • 스택은 낮은 주소에서 높은 주소 방향으로 push
  • 함수 호출 규약을 지정하지 않으면 cdecl을 사용

Cdecl Stack Frame

요점

메모리 구조상 스택은 높은 주소에서 낮은 주소로 올라가며, cdecl은 caller가 스택을 정리한다. 리버싱에서 함수 인자나 지역 변수를 찾을 때 스택 프레임 구조와 오프셋이 핵심이라고 한다. 스택에서는 RET adress가 push되므로, BOF같은 공격이 가능하다.

Stdcall

  • 인자 전달은 오른쪽에서 왼쪽으로 전달하고 피호출자
  • WinAPI에서 사용하고 함수가 끝나면 스택을 정리

Cdecl과 Stdcall 비교

				cdecl					stdcall
인자 정리		호출자(caller)			피호출자(callee)
인자 전달 순서	오른쪽 > 왼쪽			오른쪽 > 왼쪽
가변 인자 지원	가능						불가능
함수명 맹글링		그대로(func)				_func@8(인자 크기 포함)
스택 안정성		낮음(호출자 실수 가능)	높음(callee가 항상 정리)
사용 예			일반 C 함수, GCC 환경		Windows 환경

Fastcall

  • 성능 향상을 위해 일부 인자를 레지스터를 통해 전달
  • Microsoft 컴파일러 / Windows 성능이 민감한 코드에 사용
  • 앞의 2~3개 인자는 레지스터에 전달하고 나머지는 스택에 전달
  • 피호출자이며 ECX, EDX를 사용, 성능 향상이 목적

Fastcall 특징

인자 전달 순서 					오른쪽 > 왼쪽
첫 번째, 두 번째 인자 			ECX, EDX 레지스터
나머지 인자 						스택에 저장
스택 정리 						callee (피호출자)
함수명 맹글링 					_@함수명@인자크기 (MSVC기준)
반환값							EAX

x86-64-SYSV

  • Linux, MacOs 등에서 사용하는 호출 규약

x86-6 System V

리눅스 및 유닉스 계열 OS에서 널리 사용되는 함수 호출 규약

함수 반환값		RAX Register 저장
스택 정렬		16 Byte 단위로 정렬
!! 함수 호출 전에 스택 포인터 RSP는 항상 16의 배수 !!

x84-64 Prologue

  • 함수 시작 직후 실행, 스택 프레임을 설정

함수가 실행되기 전

  1. Srack Frame 설정
  2. Register 백업
  3. 지역 변수 공간 확보 -> 함수 내 메모리 공간을 준비
push ebp/rbp		이전 함수의 베이스 포인터 값을 Stack에 저장

mov  ebp,esp		현재 stack 포인터(esp)를 base 포인터(ebp)로 복사
					새로운 스택 프레임 기준 설정
sub	 esp,XXX		지역 변수 공간 확보를 위해 stack 포인터를 감소

x84-64 Epilogue

  • 함수 종료 직전 실행, 스택 상태 복구 및 호출자에게 제어 반환

함수가 종료 후

  1. Srack Frame 해제 -> 함수 호출 전 상태 복구
  2. Regist 및 stack 상태 복원 후, Return Address로 복구
mov  esp/ebp		스택 포인터를 Base Pointer 위치로 복구
pop  ebp			이전 함수의 Base Pointer 값 복원
ret					호출한 함수로 복귀

아키텍처 (Architecture)

아키텍처란?

  • 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 		디버깅에 사용

명령어 포인터 레지스터

rip 	CPU가 실행시킬 코드를 가리키며 8byte의 크기를 지님

Caller / Callee

  • Caller : 호출자. 함수를 호출
  • Callee : 피호출자. 호출을 당하는 함수

EX) C언어 프로그래밍 도중 함수를 호출해야할 때

#include <stdio.h>
int (함수명)(매개변수) { 	<- 피호출자 (Callee)
- - -
return 0; 
}

int main() {
- - -
(함수명)(매개변수);		<- 호출자 (Caller)
- - -
return 0;
}

과제 1

1 - 2557

"Hello World!"를 출력하세요.

C

#include <stdio.h>

int main(){
    printf("Hello World!");
    return 0;
}

asm

section .data
    str db "Hello World!"

section .text
    global _start

_start:

    mov rax, 1		<- 시스템 콜 번호 1 (sys_write)
    mov rdi, 1
    mov rsi, str
    mov rdx, 13
    syscall			<- syscall 실행

    mov rax, 60
    mov rdi, 0
    syscall

section .data

  • db (define byte) : 바이트 단위로 데이터를 정의
  • str에 "Hello World!" 문자열 삽입

_start

  • rdi = 1: stdout
  • rsi = num: 출력할 문자열
  • rdx = 13: 출력할 문자열 길이
  • write 시스템 콜을 사용해서 "Hello World!"을 출력
    시스템 콜 번호 1은 sys_write

2 - 10171

고양이를 출력하세요.

C

#include <stdio.h>

int main(void) {
    printf("\\    /\\\n");
    printf(" )  ( ')\n");
    printf("(  /  )\n");
    printf(" \\(__)|\n");
    return 0;
}

asm

section .data
    cat db  "\    /\", 10, \
                " )  ( ')", 10, \
                "(  /  )", 10, \
                " \(__)|", 10
    catlen  equ $ - catmsg

section .text
    global _start

_start:
    mov     rax, 1
    mov     rdi, 1      
    mov     rsi, cat
    mov     rdx, catlen 
    syscall

    ; exit(0)
    mov     rax, 60        
    xor     rdi, rdi        
    syscall

section .data

  • db (define byte) : 바이트 단위로 데이터를 정의
  • cat에 출력하고자하는 모양 삽입

_start

  • rdi = 1: stdout
  • rdx = catlen : catlen에 저장된 값을 출력할 크기로 저장
  • write 시스템 콜을 사용해서 cat에 담긴 모양 출력

3 - 1000

두 수를 입력받고 더한 값을 출력하세요.

C

#include <stdio.h>
int main(){
    int a,b;
    scanf("%d %d",&a,&b);

    printf("%d",a+b);

    return 0;
}

asm

section .data
    in  db "%d %d", 0
    out db "%d", 10, 0

section .bss
    x resd 1
    y resd 1

section .text
    extern input
    extern print
    global start

start:
    lea rsi, [x]
    lea rdx, [y]
    mov rdi, in
    xor eax, eax
    call input

    mov eax, [x]
    add eax, [y]

    mov esi, eax
    mov rdi, out
    xor eax, eax
    call print

    mov eax, 0
    ret

section .data

  • in = scanf에 전달할 포맷 문자열
  • out = printf에 전달할 포맷 문자열

section .bss

  • 각각 int(4바이트) 공간을 확보

_start

  • x+y 결과가 eax에 저장
  • input는 scanf를 대신 호출하는 외부 함수
  • print는 printf를 대신 호출하는 외부 함수
  • main() 함수가 return 0; 과 같은 구조

4 - 1001

두 수를 입력받고 뺀 값을 출력하세요.

C

#include <stdio.h>
int main(){
    int a,b;
    scanf("%d %d",&a,&b);

    printf("%d",a-b);

    return 0;
}

asm

section .data
    in  db "%d %d", 0
    out db "%d", 10, 0

section .bss
    x resd 1
    y resd 1

section .text
    extern input
    extern print
    global start

start:
    lea rsi, [x]
    lea rdx, [y]
    mov rdi, in
    xor eax, eax
    call input

    mov eax, [x]
    sub eax, [y]

    mov esi, eax
    mov rdi, out
    xor eax, eax
    call print

    mov eax, 0
    ret

section .data

  • in = scanf에 전달할 포맷 문자열
  • out = printf에 전달할 포맷 문자열

section .bss

  • 각각 int(4바이트) 공간을 확보

_start

  • x-y 결과가 eax에 저장
  • input는 scanf를 대신 호출하는 외부 함수
  • print는 printf를 대신 호출하는 외부 함수
  • main() 함수가 return 0; 과 같은 구조

5 - 10998

두 수를 입력받고 곱한 값을 출력하세요.

C

#include <stdio.h>
int main(){
    int a,b;
    scanf("%d %d",&a,&b);

    printf("%d",a-b);

    return 0;
}

asm

section .data
    in  db "%d %d", 0
    out db "%d", 10, 0

section .bss
    x resd 1
    y resd 1

section .text
    extern input
    extern print
    global start

start:
    lea rsi, [x]
    lea rdx, [y]
    mov rdi, in
    xor eax, eax
    call input

    mov eax, [x]
    imul eax, [y]

    mov esi, eax
    mov rdi, out
    xor eax, eax
    call print

    mov eax, 0
    ret

section .data

  • in = scanf에 전달할 포맷 문자열
  • out = printf에 전달할 포맷 문자열

section .bss

  • 각각 int(4바이트) 공간을 확보

_start

  • x*y 결과가 eax에 저장
  • input는 scanf를 대신 호출하는 외부 함수
  • print는 printf를 대신 호출하는 외부 함수
  • main() 함수가 return 0; 과 같은 구조

과제 2

profile
배고파요 ..

0개의 댓글