개요
MIPS나 ARM 아키텍처 기반의 바이너리에서 buffer overflow 취약점이 존재하는 경우 다양한 방법으로 익스를 시도할 수 있다. 그 중 쉘코드를 삽입하고, return address를 삽입한 쉘코드가 위치한 주소로 변경하여 쉘코드를 실행시키는 방법이 존재한다.
하지만 위 사진처럼 에러가 발생하는 경우가 존재한다. 그래서 보통 MIPS에서 쉘코드를 이용하는 경우 ROP를 이용하여 sleep 함수를 먼저 호출한 뒤 쉘코드를 호출하는 방법을 이용한다. 어떤 이유 때문에 MIPS에서는 바로 쉘코드를 호출하면 안되는지 간단히 살펴보자. 우선 Cache가 뭔지 간단히 살펴보자.
Cache
Cache는 CPU 안에 들어가는 작고 빠른 메모리다. 프로세서가 매번 메인 메모리에 접근해 데이터를 받아오면 시간이 오래 걸리기 때문에 캐시에 자주 사용하는 데이터를 담아두고, 해당 데이터가 필요할 때 CPU가 메인 메모리 대신 캐시에 접근하도록해 처리 속도를 높인다.
•
L1 Cache : CPU와 가장 가까운 캐시로 속도를 위해 명령어 캐쉬와 데이터 캐쉬로 나뉜다.
◦
명령어 캐쉬(I$) : 메모리의 Text 영역 데이터를 다루는 캐쉬
◦
데이터 캐쉬(D$) : Text 영역을 제외한 모든 데이터를 다루는 캐쉬
•
L2 Cache : 용량이 큰 캐쉬
•
L3 Cache : 멀티 코어 시스템에서 여러 코어가 공유하는 캐쉬
위 사진은 인텔 코어 i7 쿼드 코어 칩의 Die map이다. 이를 보면 4개의 코어안에 L1 캐시가 존재하고(사진에선 안보임) L2 코어가 존재하며 모든 코어가 공유하는 8MB L3 캐시가 있는 것을 볼 수 있다.
MIPS Cache
MIPS 역시 데이터 캐쉬와 명령 캐쉬의 두 가지를 사용한다. 이는 주 메모리에 대한 읽기 및 쓰기를 비동기적으로 수행하여 메모리 액세스 속도를 높이도록 설계되었다. 데이터 캐쉬에는 데이터를 기록, 명령어 캐쉬에는 opcode 같은 명령을 기록한다.
우선적으로 실행 중인 프로세스는 주 메모리가 아닌 캐시에서 명령과 데이터를 가져오는데 캐쉬에서 값을 사용 할 수 없는 경우 캐쉬 miss가 발생하고, 캐쉬를 주 메모리와 동기화 한다. (LRU, LFU, FIFO 등의 알고리즘 사용) 해당 개념을 가지고 다음 예시를 살펴보자.
현재 정상적인 흐름으로 코드가 실행되고 있다고 가정해보자. 명령 캐쉬에는 이미 로드된 이전 명령어가 들어있다.(반드시 존재하는 것은 아님) CPU는 명령 캐시에 있는 명령만 실행할 수 있으며 데이터 캐쉬에 있는 데이터만 볼 수 있다.
중요한 점은 데이터 캐쉬에 있는 명령을 직접 실행 할 수 없으며 명령어 캐쉬에 있는 명령을 직접 읽거나 쓸 수 없다. 따라서 새로운 코드가 실행될 때는 다음과 같이 변경된다.
이 상태에서 코드가 실행되면 CPU는 현재 새로운 코드가 존재한다는 사실을 인지 못한채 이전 명령이 존재하는 명령 캐시를 확인하여 이를 실행한다. 이를 캐쉬 비 일관성 이라 부른다. 이러한 문제제를 해결하기 위해선 데이터 캐쉬에서 명령 캐쉬로 데이터를 가져와야 한다.(다이렉트로는 불가능하고 주 메모리를 거쳐서 동기화 됨)
현재 데이터 캐쉬에 들어있는 데이터는 새로운 정보이고 메모리의 내용과 일치하지 않고 캐쉬 비 일관성 문제가 발생한다. 따라서 데이터를 정리한 다음 write 시점까지 기다린 후 메모리와 동기화를 한다.(캐시 쓰기 정책)
캐시 쓰기 정책
이제 메모리에 새롭게 추가된 코드가 동기화 되었다. 코드가 실행 될 때 현재 명령 캐쉬에 존재하는 데이터와 메모리의 데이터가 일치하지 않으므로 명령 캐시를 비우고 메모리에 존재하는 코드를 실행하게 된다. 결과는 아래 사진과 같다.
Solution
데이터 캐쉬를 flush 해야하는게 분석 내용의 주요점이다. 그렇다면 어떻게 Data Cache를 flush하여 메모리에 데이터 쓰기를(캐쉬 쓰기 정책) 발생시킬 까?
가장 쉬운 방법은 CPU가 지정된 시간 동안 작업을 일시 중단하도록 하는 sleep 함수를 호출하는 것이다.
sleep이 호출되면 프로세스나 쓰레드는 자기에게 할당 된 시간을 포기하고 대기하고 있는 다른 프로세스나 쓰레드에게 실행을 양보한다. 그렇게 되면 당연히 Data Cache에 다른 데이터들이 채워질 것이고 저장되어있는 쉘코의 주소가 다음에 실행할 것으로 예상되는 위치(캐쉬)에서 멀리 떨어져 있으므로 캐쉬 미스가 발생하고 캐쉬 쓰기 정책으로 인해 flush가 되어 메모리에 새롭게 write된다.