Search
Duplicate

Twice the Bits, Twice the Trouble: Vulnerabilities Induced by Migrating to 64-Bit Platforms

취약점 분석 트랙 박상준
본문은 32비트 코드를 64비트로 마이그레이션하는 과정에서 발생할 수 있는 취약점들에 대한 설명이다. 이에 대한 예시로 200여 개의 오픈소스를 포팅하는 과정에서 발생한 취약점들이 제시되며, 32비트와 64비트의 데이터 모델의 차이, 수식 표현 방법 등의 내용도 설명된다. 또한, 트러블슈팅과 관련된 예시도 제공되며, 이를 통해 실제로 취약점이 발생할 수 있는 상황들이 제시된다.
Loading PDF…

개요

32bit를 기준으로 작성된 코드를 64bit로 포팅하는 과정에서 생길 수 있는 취약점들을 소개한다. 실제로 200여개 이상의 오픈소스를 대상으로 포팅하는 과정에서 생긴 취약점들을 소개하며 증명한다.

본론

32bit vs 64bit

32bit와 64bit에서의 차이는 아래와 같이 데이터 모델의 width(바이트 수)가 다르다는 것이다. 아래 그림에서 중요한 점은 32bit → 64bit로 옮겨가면서 pointer 의 사이즈가 4→8byte로 된다는 것과 long type이 4→8byte로 확장되었다는 것이다.
Data Models
그럼 왜 32bit→64bit 아키텍쳐로 넘어갔는지 생각해봤다.
더 많은 가상 메모리와 가상메모리. 더 큰 수를 연산하기 위함인 것 같다. 즉, 32bit computer → 64bit computer의 마이그레이션은 현대사회에서 불가피하다는 것이다.

논문에서 사용하는 수식표현

Data models들을 수식으로 표현하기 위해 사용하는 규칙은 아래와 같다.
S(T) = 0 → unsigned type
S(T) = 1 → signed type
Integer type의 순위은 아래와 같다.
R(char) < R(short) < R(int) R(long) < R(long long) 더 큰 수를 표현하기 위해 어떤 이름의 타입을 사용하는지의 순위
Python
복사
Type별로의 바이트 수를 수식으로 표현하는 법.
W(T) ∈ {1, 2, 4, 8}.

Integer Issues종류

32bit 수와 64bit 수는 수를 표현하는 규칙이 달라서 생길 수 있는 문제는 Integer Issue이다. Integer Issue의 종류에는 truncation underflow/overflow integer Signedess Issue 가 있다.

truncation

truncation은 더 큰 바이트를 사용하는 자료형을 범위가 작은 자료형에 할당할 때 발생한다.
아래 코드를 논문 수식으로 표현하면 W(narrower) < W(wider)이의 상황이고 narrower = wider인스트럭션을 수행할 때 상위 바이트가 짤리는 truncation이 발생한다.
unsigned int wider; // like 4bytes unsigned short narrower; // like 2 bytes wider = narrower // truncations
C
복사
truncation으로 인해서 취약점까지 트리거될 수 있는 예시가 있다.
1.
x = UINT_MAX
2.
y = USHORT_MAX → truncation
3.
malloc(0xFF_FF)
4.
memcpy( 0xFF_FF사이즈 힙, src, UINT_MAX); → overflow
// gcc trunc.c -o truncation -fsanitize=address int main() { char src[]="OVERFLOW_OVERFLOW_OVERFLOW_OVERFLOW_OVERFLOW_OVERFLOW_OVERFLOW_OVERFLOW_OVERFLOW_OVERFLOW_OVERFLOW_OVERFLOW_OVERFLOW_OVERFLOW_OVERFLOW_OVERFLOW_OVERFLOW_OVERFLOW_OVERFLOW_OVERFLOW_"; unsigned int x; scanf("%u",&x); // 0xffffffff == 4294967295 unsigned short y = x; // truncation -> 0x0000ffff char *buf = (char*)malloc(y); memcpy(buf, src, x); }
C
복사
ASAN 을 끼고 빌드하고 버퍼 사이에 MAGIC 값들을 넣고 Overflow가 나는지 체크한다.
아래와 같이 ASAN이 반응한다. 힙 오버가 날 것 같지만 STACK OVER라고 뜨는건 좀 이상하다.
리얼월드에서 충분히 있을만한 Truncation상황으로 인해서 Stack Over, Heap Over취약점까지 트리거되는 상황이다.

integer overflow

컴퓨터에서 정수를 표현하기 위해서 사용하는 자료형은 아래 그림처럼 순환하는 관계이다.
Integer를expression과 expresssion을 Arithmetic연산하는 과정에서 UINT_MAX 값에 +1이상의 값을 더해주면 다시 0으로 돌아가서 Overflow 가 된다.
이런 Integer Overflow 도 리얼월드에서 충분히 있을만한 상황이다.
아래는 Integer Overflow로 Buffer Overflow까지 트리거 되는 POC코드이다.
1.
x = UINT_MAX == 0xFF_FF_FF_FF
2.
malloc( x + CONST ) = malloc(0xFF)
3.
memcpy( 0xFF힙, src, 0xFF_FF_FF_FF); // OverFlow
// gcc overflow.c -o overflow -fsanitize=address #define CONST 0x100 int main() { char src[]="OVERFLOW_OVERFLOW_OVERFLOW_OVERFLOW_OVERFLOW_OVERFLOW_OVERFLOW_OVERFLOW_OVERFLOW_OVERFLOW_OVERFLOW_OVERFLOW_OVERFLOW_OVERFLOW_OVERFLOW_OVERFLOW_OVERFLOW_OVERFLOW_OVERFLOW_OVERFLOW_"; unsigned int x; scanf("%u",&x); // 0xffffffff == 4294967295 char *buf = (char*)malloc(x+CONST); memcpy(buf, src, x); }
C
복사

Integer Signedness Issue

Integer Signedness Issue 는 부호 확장( Sign Extension )에 의해서 발생한다.
부호 확장이란, 정수형 데이터를 더 큰 자료형으로 할당할 때 부호비트를 유지하기 위해서 상위 비트를 0 (양수)또는 1(음수)로 채우는 것이다.
1.
x = 0xFFFF
2.
malloc(0xFFFF)짜리 힙
3.
memcpy( 0xFFFF짜리 힙, src, 0xFF_FF_FF_FF) → signed extension에 의해 1로 채워져 확장됌. // overflow
// gcc signedness-issues.c -o signedness-issues -fsanitize=address int main() { char src[]="OVERFLOW_OVERFLOW_OVERFLOW_OVERFLOW_OVERFLOW_OVERFLOW_OVERFLOW_OVERFLOW_OVERFLOW_OVERFLOW_OVERFLOW_OVERFLOW_OVERFLOW_OVERFLOW_OVERFLOW_OVERFLOW_OVERFLOW_OVERFLOW_OVERFLOW_OVERFLOW_"; short x=0xffff; char *buf = (char*)malloc((unsigned short)x); memcpy(buf, src, x); }
C
복사

32bit → 64bit로 바뀌면서 발생하는 Integer Issues

Effects of Integer Width Changes

new Truncations

W(A) < W(B)일 때, A=B상황에서 Truncation이 발생한다.
32bit를 기준으로 작성된 코드를 64bit로 바뀌면서 생길 수 있는 케이스는 아래와 같다.
1.
size_tunsigned int or int
2.
longint
3.
pointerunsigned int or int
size_t size = controlled_value();//size_t == 4 byte in 32bit, 8 byte in 64bit unsigned int = size; // truncation triggered long src= controlled_value();// long == 4 byte in 32bit, 8 byte in 64bit unsigned int = src; // truncation triggered char *ptr = malloc();//ptr== 4 byte in 32bit, 8 byte in 64bit unsigned int = ptr-0x10; // truncation triggered
C
복사
위의 상황이 발생할 수 있는 상황은 32bit→64bit로 옮겨가면서 Width의 확장으로 인해서 Truncation이 발생한다. Untitled 여기서 pointer 의 Width변화와 long && int 의 Width변화로 인해서 위와 같은 Truncation이 발생할 수 있다.

new signedness Issue

1.
unsigned intint or long
2.
아래 코드에서는 len 이 -1일 때 if(len BUF_SIZE)를 bypass하고 memcpy( ~, ~ ,0xFF_FF_FF_FF_FF_FF_FF_FF) 예시이다.
unsigned intlongcompare 할 때 Bypass를 할 수 있는 이유는 Implicit Type Conversion(a묵시적 형변환) 이 일어나서 unsigned int 가 8바이트 long으로 Conversion되어 -1 > 128 이 되어 Bypass할 수 있다.
// gcc miss.c -o miss -fsanitize=address int main() { const unsigned int BUF_SIZE = 128; char buf[128]; char src[]="OVERFLOW_OVERFLOW_OVERFLOW_OVERFLOW_OVERFLOW_OVERFLOW_OVERFLOW_OVERFLOW_OVERFLOW_OVERFLOW_OVERFLOW_OVERFLOW_OVERFLOW_OVERFLOW_OVERFLOW_OVERFLOW_OVERFLOW_OVERFLOW_OVERFLOW_OVERFLOW_"; long len; scanf("%ld",&len); // -1 if(len > BUF_SIZE) return; memcpy(buf, src, len); }
C
복사

Effects of a Larger Address Space

지금까지 소개한 원리들을 기반으로 일어나는 다양하게 소개하고 있다.

Dormant Integer Overflows

아래 코드는 32bit 환경에서는 문제없이 돌아가지만 64bit환경에서는 무한루프를 돈다.
1.
W(unsigned int i ) < W(size_t len)
2.
만약 size_t len 이 0xFF_FF_FF_FF_FF_FF_FF_FF일 경우에 i는 최대 0xFF_FF_FF_FF까지만 더해지고 Overflow되어 for문의 조건문을 무한으로 만족한다.
3.
Infinite Loop

Dormant Signedness Issues

만약 문자열의 길이가 INT_MAX 값을 넘어가거나 literal이 아니면 sanitize check를 bypass하고 memcpy 에서 Overflow가 나는 케이스이다.
I_M(int) < len <max I_M(unsigned int) 라고 논문에서는 표현한다.
즉, INT_MAX < len < UINT_MAX까지 입력값을 조절할 수 있으면 Overflow가 나는 케이스이다.

Unexpected Behavior of Library Functions

신기하게도 Library Functions에서도 32bit→64bit로 마이그레이션 하는 과정에서 Integer Issue가 발생한다.
snprintt 는 쓸 바이트를 입력받는 인자로는 size_t 를 사용하지만 return값int 형을 사용하기 때문에 size_t를 INT_MAX보다 크게 할 경우 Integer Issue가 발생한다.
아래와 같은 코드가 이를 악용할 수 있는 코드 상황이다.
만약 BUF_LEN이 INT_MAX보다 클 경우 snprintf는 -1을 return하고 결국엔 pos-1을 Return하고 다른 메모리를 접근할 수 있다.
또 다른 케이스로는 파일 끝의 index를 확인하는 ftell 이 있다.
ftell 은 0xFF_FF_FF_FF를 넘어가면 0을 return하도록 설계되어 있어서
아래 코드에서 파일 크기가 0xFF_FF_FF_FF를 넘어가게 하면 malloc(1)이 되어 실제 파일크기보다 훨씬 작은 버퍼가 할당되고 그 버퍼에 파일을 그대로 복사해 Overflow가 나는 케이스를 확인할 수 있다.

64bit 마이그레이션에서 발생하는 Integer Issue 패턴

1.
New Truncations → W(A) < W(B)상황에서 A=B처럼 더 작은 메모리에 값을 할당할 때 Truncation 발생
2.
New signedness issues → 작은 메모리 → 큰 메모리로 이동/비교할 때 Implicit Type Conversion 으로 인하여 부호가 바뀜 발생
3.
Dormant integer Overflow → size_tint/unsigned int 를 혼용할 때 발생하는 잠재적 Overflow
4.
Dormant signedness issues→ 잠재적인 부호 문제이다. strlen 의 return값과 return값을 unsigned int 로 사용할 때 발생한다.
5.
Unexpected behavior of library functions → library함수에서 인자로 받는 size_t 와 return값인 int 형의 Width차이로 인하여 발생한다.

결론

이 논문을 통해서 32bit→ 64bit로 마이그레이션 하는 과정에서 생기는 Integer Issue 케이스들을 알 수 있었다.
이러한 Integer Issue가 생기는 이유는 pointer , long , size_t 의 자료형 크기가 4byte→8byte로 확장되었기 때문에 발생했던 것이다.
이러한 확장으로 인해서 32bit환경에서는 발생하지 않는 trunction signedness issue Integer Overflow 등이 발생할 수 있다.

느낀 점

앞으로 퍼징이나 Static Analysis로 취약점을 찾을 때 32bit와 64bit로 모두 빌드하여 다양한 환경에서 취약점을 찾도록 해야겠다.
실제로 최근에 Usenix Security 나온 Arbiter에서도 32bit OCaml compiler에서도 버그를 찾았던 것 같았다.
이런 Integer Issue들을 자동으로 찾을 수 있도록 하는 것도 연구를 더 찾아봐야겠다.

참고 및 인용