[운영체제 보안] Return-Oriented Programming (ROP)

cloudbread·2025년 12월 8일

보안

목록 보기
4/7

1. Return-Oriented Programming

  • 기본적인 ret2libc 공격에서는 단순히 system()함수와 exit() 함수를 연결하여 2개의 함수만 chain 형태로 호출 하는 방식
  • 공격자가 system() 함수를 필요로 하지만, 대상 라이브러리에 해당 함수가 존재하지 않는 경우에는 system() 대신 fork() -> execve()와 같이 다른 libc 함수 조합을 활용하는 우회가 가능하다.
  • 코드 조각(code gadget)단위로 무제한 연결할 수 있는 기법을 제한하였다. => 이러한 코드 조각들은 일반적으로 명령어들의 끝이 ret으로 끝나는 짧은 코드들이다.

2. Return to functions

/*stack2.c*/
#include<stdio.h>
#include<stdlib.h>
#include<string.h>

void a_fun(void){
        system("/bin/sh");
}

void b_fun(void){
        printf("I am in b_fun()\n");
}

void c_fun(void){
        printf("I am in c_fun()\n");
}

int vul_func(char *str){
        char buff[10];
        strcpy(buff,str);
        return 1;
}

int main (int argc, char **argv)
{
        char str[100];

        vul_func(argv[1]);
        printf("returned properly\n");
        return 1;
}

  • 컴파일을 한다.
  • gcc -m32 -fno-stack-protector -z execstack -no-pie -o stack2_32 stack2.c
  • pie와 ASLR , stack canary모두 꺼줘야한다.
  • 지역변수 자리에 20byte를 할당함 => 패딩을 제공하고 있음.
  • 실제 배열은 0x12 => 즉 18byte위치해있다.
  • Buff (18B) + Previous FP(4B) = 22B 후 RET
  • 사진을 보면 22B후 eee를 입력 => return address에 0x00656565가 들어간것을 확인할 수 있다.
  • 마찬가지로 'aaab'가 리틀 엔디안으로 들어가 return address와 eip가 바뀐것을 확인할 수 있다.
  • buf크기를 넘지 않으면 프로그램이 정상 종료된다.
  • (geb) p를 사용하여 exit, getpid, system의 상세 주소를 알게 되었다.
  • exit address => 0xf7dc35b0 , getpid address => 0xf7e6a630 , system => 0xf7dd48e0
  • objdump -d 출력에서 왼쪽에 보이는 숫자는 실행 파일 내부에서 함수가 배치된 위치를 의미한다.
  • 실행 파일 내부에서 함수가 몇번 째 바이트에 있는지를 나타낸다.
  • 08049186 <a_fun>
  • 080491b1 <b_fun>
  • 080491dc <c_fun>
  • 08049207 <vul_func>
  • 현재 exit를 쓸 수 없어서 공격을 완벽하게 감추는 것에 한계가 있다. => 어떻게 만들 수 있을 까?

2. Return Oriented Programming (ROP)

  • 공격자가 system () 함수를 수행하고 싶다고 가정하자
  • 이때 문제 발생 => libc에 system()이 존재하지 않는다면 ?
int system(const char *command) {
    pid = fork();
    if (pid == 0) execve(...);
}
  • ROP은 쉘코드 주입 X , system 함수 호출 필요 X, 기존 코드 변조 X
  • 그러나 여전히 스택의 내용은 변조 또는 조작되어야한다.
  • 접근 방식
    • 전체 함수를 사용하는 대신 , libc 등에서 추출한 짧은 명령어 시퀸스를 이용한다.
    • 이러한 명령어 시퀸스는 일반적으로 2~5개로 구성
    • 모든 시퀸스는 ret 명령어로 종료된다
    • 이러한 짧은 명령어 시퀸스를 연결하여 가젯을 구성한다
    • 각 가젯은 다음과 같은 특정 작업을 수행한다. => load, store, xor, branch 등
    • 이후 공격자는 이러한 가젯들을 조합함으로써 자신이 원하는 동작을 강제한다.
  • 명령어 시퀸스 : ret 명령어로 끝나는 일련의 명령어 집합을 의미한다.
  • 가젯 : 여러 개의 명령어 시퀸스로 구성된 논리적 단위

3. 의도되지 않은 순차 명령어들

b8 04 00 00 00 c3
  • mov eax 0x4라는 의미를 가지고 있음
  • c3은 리턴
  • 이때 공격자가 중간 주소로 점프한다고 가정해보자 (CPU가 세번쨰 바이트에서 명령어 해석을 시작)
[00] 00 00 c3
  • add [eax], al
  • add bl, al
  • ret
  • 이 코드는 프로그래머가 전혀 의도하지 않은 명령이지만, 공격자는 이를 ROP 가젯으로 악용할 수 있다.
  • 이런 현상이 가능한 이유는 x86 명령어는 길이가 1바이트부터 15바이트까지 모두 가능하다. => 중간에서 시작하면 새로운 조합이 생성됨
  • 또한 메모리 정렬을 강제하지 않기 때문에 임의 주소에서 명령을 읽기 시작할 수 있다.

3. CISC와 RISC에서의 ROP 가젯 생성 가능성 차이

  • CISC 구조에서는 명령어 길이가 가변적으로 구성되기 때문에, 공격자가 이를 ROP 명령어 해석에 활용할 수 있어 ROP 공격에 유리한 환경이 형성된다.
profile
잡다한거 다 공부중....

0개의 댓글