본문 바로가기

ASLR을 비활성화 하는 법

@Dizet2025. 3. 17. 03:49

ASLR은 Address Space Layout Randomization 으로, code segment와 같은 움직이면 안되는 부분을 제외하고 다른 부분을 임의의 상대주소로 옮기는 역할을 한다. 이는 RIP를 기준으로 RIP + Offset과 같은 형태로 code가 작성되며, 그 base가 random화 된다.

ASLR이 켜져있으면 stack, heap, so(라이브러리) 부분의 주소가 계속 바뀐다.

워게임이나 CTF를 푼다면 서버의 환경과 동일한 환경을 위해서라도 aslr이 켜져있어야하겠지만, ASLR을 꺼야하는 상황에서는 끌 수 있어야 한다.

`/proc/sys/kernel/randomize_va_space` 의 값을 Kernel은 참조하여 ASLR의 여부를 결정한다.

  • 2 : 초기값. ASLR이 켜진다. Stack과 Heap, so가 전부 랜덤화된다.
  • 1 : ASLR이 켜진다. 다만, 랜덤 스택 & 랜덤 라이브러리 설정이 된다.
  • 0 : ASLR이 꺼진다.

해당 값을 바꾸려면…

  1. `sudo echo 0 > /proc/sys/kernel/randomize_va_space`을 실행한다.
  2. `sysctl -w kernel.randomize_va_space=0`을 실행한다.

다만 이는 리눅스를 껏다 키면 다시 복구된다고 한다.

 

 

 

추가적으로 ASLR과 함께 작동하는 PIE는 gcc를 통해 꺼줘야하는데,

보호기법 관련 gcc parameter은 아래와 같다.

`-fno-stack-protector` 이 카나리의 해제.

`-zexecstack` 은 NX(윈도우에서 dep)의 해제.

`-no-pie`는 PIE의 해제.

`-zrelro`는 relro의 해제


추가적으로 gcc compile 관련

`-m32`, `-m64`는 64bit, 32bit compile. (다만 gcc-multilib, g++-multilib이 깔려있어야한다.)

`-mpreferred-stack-boundary=2`, `-mpreferred-stack-boundary=4`는 더미의 제거이다.

    대충 스택 포인터 정렬을 생각하면 편하다. 만약 =2의 경우엔 함수 호출 시 스택 포인터를 2^2, 즉 4바이트 경계로 정렬하도록 지시한다. 4는 16바이트 정렬이다.

     32bit인 경우엔 2, 64bit는 4를 많이 쓴다.

          64비트 ABI(가령 AMD64 ABI)와 SSE 같은 SIMD 명령어 사용 시 요구되는 정렬 조건이 16바이트인 경우가 많기 때문이다. (rop할 때를 생각해보라)

`-fno-builtin` 는 내장함수의 사용을 금지하는 것이다.

     gcc, g++은 기본적으로 몇몇 표준 함수들을 내장 함수로 대체하여 최적화를 수행하는 편이며,

          이러한 내장 함수 대체가 이루어지지 않고 실제 라이브러리 함수 호출을 그대로 사용하게 하는 설정이다.

     이는 사용자 정의 함수와의 충돌 방지를 위할 때 유용한 기능이며 (만약 표준 함수 이름과 동일한 이름으로 사용자 정의 함수를 작성했거나, 라이브러리의 동작이 아닌 자신의 구현을 사용하고자 할 때)

     내장 함수의 최적화 동작 때문에 발생할 수 있는 문제를 피하고자 할 때, 또는 디버깅 시 함수 호출의 실제 동작을 보고자 할 때 사용하는 편이다.

`-dynamic`, `-static`은 말 그대로 so를 쓸 것인지, 전부 바이너리에 담을 것인지에 대한 것이다.

     static 옵션은 실행 파일에 필요한 라이브러리 코드를 모두 포함시켜 외부 동적 라이브러리(.so, .dll 등)에 의존하지 않게 만드는 것이고,

     dynamic 옵션은 실행 파일이 실행될 때 외부의 공유 라이브러리(예: .so 파일)를 참조하여 필요한 함수들을 로드하게 하는 것이다.

     위의 -fno-bultin이 dynamic아닌가? 생각할 수 있지만…

          builtin같은 경우엔 컴파일러에게 표준 라이브러리 함수의 내장(builtin) 최적화를 사용하지 말라는 의미이다.

          가령 컴파일러가 memcpy와 같은 함수를 더 빠른 내장 버전으로 대체하는데, 이를 막아 실제 라이브러리 함수 호출을 유지하는 것이다.

          이때 내장 함수(내장 버전?)이란?

               컴파일러 내부에 미리 정의되어 있는 함수 구현을 의미한다.

               일반적으로 C 표준 라이브러리 함수들(memcpy, strcpy등)이 라이브러리에서 제공되지만, gcc와 같은 컴파일러는 이러한 함수를 인식하여 내부에서 최적화된 코드(인라인 코드나 특수한 최적화 경로)를 생성한다.

`-c`는 object file의 생성이다.

`-O0`는 함수의 최적화를 없애주는 argument이다.

     이외에도 -O1이나 -O2처럼 사용이 가능한 것으로 보인다.

     디버깅을 용이 및 코드의 원본 구조를 그대로 유지하기 위한 설정이다.

     목적에 맞게 난 -O0은 최적화를 비활성화해 디버깅을 편하게 할 때 사용한다.(난 그런다 사실 별 차이 없다. 내가 작은 바이너리만 봐서 그럴지도)

     그렇다면 만약 -O0를 사용하지 않으면 위에 있는 내장함수로 대체되는 거니까, 결국 -fno-builtin과 비슷하지 않냐? 할 수 있지만…

          바이너리의 최적화는 내장함수만으로 이루어지지 않는다.

`-U_FORTIFY_SOURCE`, `-D_FORTIFY_SOURCE=0` 는 Fortify Source를 비활성화하는 argument이다.

     `-U_FORTIFY_SOURCE`는 _FORTIFY_SOURCE 매크로의 정의를 완전히 제거하며,

          즉, 소스 코드 내에서 #ifdef _FORTIFY_SOURCE와 같이 해당 매크로의 정의 여부를 확인할 때, 아예 정의되어 있지 않은 상태가 되며

     `-D_FORTIFY_SOURCE=0`은 _FORTIFY_SOURCE를 0이라는 값으로 정의하는 것으로

          코드에서는 매크로가 정의되어 있으나 그 값이 0임을 인지하게 된다.

               일부 구현에서는 _FORTIFY_SOURCE가 정의되어 있는지만 확인할 수도 있으므로, 이 경우 미묘하게 다르게 동작할 가능성이 생긴다.

     이때, Fortify Source는 C 라이브러리 함수(strcpy, memcpy 등)에 대해 추가적인 런타임 안전 검사를 삽입하는 보안 메커니즘으로

          컴파일러가 소스 코드에서 버퍼 크기 등 상수 정보를 분석해, 만약 버퍼 오버플로우와 같은 위험 상황이 감지되면 경고하거나 프로그램을 중단하도록 돕는 역할을 한다.

               이는 주로 컴파일러의 최적화 과정 중 상수 전파 등 분석을 통해 활성화되며, 이를 위해 적절한 최적화 레벨(보통 `-O2` 이상)이 필요해진다.

                    Fortify Source 자체가 보안 강화를 위해 런타임 검사 코드를 삽입하는 기능인데, 이 기능은 컴파일러가 코드 내 상수 값들을 분석할 수 있을 때 제대로 동작한다.

                    이를 위해 적어도 -O2 이상이 필요해지는 건데, -O0가 켜지면 Fortify Source가 요구하는 상수 분석이 제대로 이루어지지 않아 검사 기능이 제대로 이루어지지 않거나 비활성화될 것이다.(아마도?)

'\ > L' 카테고리의 다른 글

권한(RUID, SUID, EUID) & Sticky Bit  (0) 2025.03.17
Dizet
@Dizet :: Dizet

컴퓨터

공감하셨다면 ❤️ 구독도 환영합니다! 🤗

목차