ROP Mitigation using orp

바로 이전 포스팅에서 ROP 공격을 완화시킬 수 있는 한 방안인 in-place randomization 기법을 소개했는데, 한 번 testing해 봤다. 이 논문의 저자인 Vasilis Pappas는 그 해 rop 관련 방어기법인 kbouncer를 소개해 MS의 Bluehat에서 상금 20만불을 거머쥐기도 했다. (http://www.microsoft.com/security/bluehatprize/) 여기서 사용하는 orp라는 도구 역시 Vasilis가 디자인한 상당히 강력한 ROP 방어 기법 중 하나다.

Return-Oriented Programming은 운영체제에서 임의의 데이터(arbitrary data)를 실행할 수 없도록 NX bit, ASLR, DEP 등 다양한 기법을 동원하더라도 우회할 수 있는 방법 중 하나다.  공격자는 이미 실행할 수 있는 영역에 있는 library (windows DLLs)에서 조각나 있는 byte를 모아 gadget으로 구성한 후, eip와 필요한 register를 이용해 공격에 필요한 연산을 수행할 수 있도록 스택을 꾸미는 게 핵심이다.  ROP는 2008년 Blackhat에서도 Erik Buchanan에 의해 소개되었고 corelan 팀이 2010년 기술한 exploit writing tutorial에서 매우 상세히 다루고 있다.

기존 연구에서 code randomization, 제어 플로우 무결성을 이용하는 방법도 있지만 이는 소스에 디버깅 정보가 있어야 한다. 또한 runtime 시 체크하는 방법론도 제시되었으나 overhead가 커서 실용적이지 못하다. 이 논문의(Smashing the Gadgets: Hindering Return-Oriented Programming Using In-Place Code Randomization) 아이디어는 간단한 편이지만, 방어력은 실용적(practical)이고 효율적(effective)이다. 최종 결과를 보면 전체 gadget으로 이용할 수 있는 코드 중 10% 정도만을 변환해서 80% gadget을 쓸모없게 (useless) 만들었다고 한다.

핵심은 파일크기를 전혀 변경하지 않고 기존 code flow를 전혀 손상하지 않은 채로 같은 코드로 전환(transformation)하는 것이다. 즉 기존코드에서 gadget으로 사용할 수 있는 부분을 다음과 같은 세 가지 형태로 변환하는 과정을 거친다.

  • 개별 명령어 치환 (Atomic instruction substitution)
    같은 행위를 하는 명령어는 여러 가지 형태로 변형할 수 있음
    아래의 경우 같은 연산을 하지만 opcode는 다름
    예) add r/m32, r32 ↔ add r32, r/m32
    test r/m8, r8 ↔ or r/m8, r8 ↔ or r8, r/m8
  • 재배치(reordering): 내부 Basic Block, register 값이 유지되는 레지스터 변경
    종속 그래프를 그려서 코드 블록을 재배치함
  • Register 재할당 (reassignment)
    CFG(Control Flow Graph, 제어 흐름도)를 그려서 상호 간 영향을 미치지 않는 영역(parallel, self-contained regions)을 확인하고 이해 한해서 register를 바꾸는 방법

이 중 첫 번째 단순 치환은 이해하기 쉽다. 다음 코드를 보자.

Highlight 부분인 sub esp, 28h가 add esp, 0FFFFFD8h 로 바뀌었으나 의미는 esp에서 28을 빼거나 -28을 더하라는 것이므로 동일하다.  유사한 방식으로 다음 코드치환도 마찬가지다.

두 번째, 재배치 부분을 보자. (그림은 논문에서 인용했음)

rop1

위 그림은 x86 assembly에서 길이가 유동적인(또는 가변적인) instruction/operand 특성 때문에 여러가지로 해석될 수 있음을 나타낸다. 윗부분은 실제 프로그램 코드를 읽어야 하는 순서를 나타내고 있지만, 내부에 있는 코드를 다르게 잘라 읽으면 아래부분의 gadget처럼 전혀 compiler가 의도하지 않는 instruction set이 나올 수 있다. 여기서 위 코드를 보고, 상호 종속적인 부분이 있는지 확인하는 그래프를 그려보면 다음과 같다. 예를 들어 push ebx는 mov ebx, [ecx+0xc]와 종속적이지만 mov eax, [ecx+0x10]과는 독립적이다.

rop2

이를 이용해 보면 아래 코드 좌측은 우측과 같이 변형해도 완전히 동일하게 된다. 이 경우 초기 세 번의 push는 마지막 부분 stack에서 pop 연산을 할 때 LIFO (Last-In First-Out) 특성을 고려해 위치만 맞춰주면 된다.

rop3

 

마지막으로 register 재할당 부분은 바로 예제를 보자.

이 경우 원래 esi를 전체 흐름에서 사용하고 있지 않던 ebx로 재할당했다. CPU의 입장에서 보면 한 thread에서 사용하고 있지 않은 다른 저장소를 이용한 셈이므로 전혀 문제될 것이 없다. 하지만 ROP 공격 관점에서 보면, gadget이 없어지거나 사용할 수 없게끔 흐트러지는 결과를 낳는다.

orp는 현재 [-d|-p|-r|-c] 네 가지 option을 사용할 수 있으며, 우선 -d 옵션으로 대상을 IDA Pro를 이용해 분석하는 작업을 한다. 그런 후 -r 옵션으로 randomization하면 된다. -p와 -c는 각각 profiling, coverage evaluation을 의미한다. 아래 결과는 Adobe Reader 9.3 버전에 있는 BIB.dll을 대상으로 1,737개의 명령어가 변경되었음을 알려준다.

아래는 변경된 바이트를 byte 단위로 diff한 모습이다.

rop4