본문 바로가기

카테고리 없음

35C3 CTF krautflare Write up

일단 제가 이해한 내용을 적었습니다. 틀린 부분이 존재 할 수 있습니다.

 

먼저 .patch 파일을 총 3개를 주게 되는데 가장 중요한 피일인 revert-bugfix-880207.patch 파일을 보도록 하겠습니다. 먼저 위에를 보면 kMathexpm1 함수에 Return 값을 Number 에서 PlaneNumber또는 NaN 값으로 바꿔주고 있습니다. 이 정도만 가지고 취약점을 파악하기 힘드니 간단한 POC를 통해서 취약점을 알 수 있습니다. 

 

간단한 소스코드 파일에 대한 설명입니다. 저희는 compiler 라는 디렉토리 안에 존재하는데 typer.cc 파일을 바꿨으니 Turbofan이 작동되야지 이 소스코드가 적용이 된다는 것을 알 수 있습니다. 또한 typer.cc 에 의미를 알았으니 디핑 된 파일이 무엇인지 이해가 되었습니다.

먼저 문제에서 주어진 POC를 통해서 접근해보도록 하겠습니다. 저희가 Project zero 블로그에따르면 두번째 foo 출력에서는 False 가 나와야됩니다. 근데 여기서는 true를 반환합니다. 이 취약점의 근본적인 문제는 Typer가 Math.expm1의 인자 값을 PlainNumber, NaN 으로 바꿔주는데 이 과정에서 -0의 을 처리해주는 루틴이 없습니다. 이로써 최적화 과정에서 문제를 일으킬 수 있고 그래서 결국 Math.expm1(-0)의 리턴값은 -0이 됩니다. 

이때 0은 smi로 0을 갖고있지만 -0 은 HEAP_NUMBER_TYPE 즉 Float64 타잎을 갖고있습니다. v8에서는 엄연히 서로 다른 숫자입니다. 

 

그러 여기서 문뜩 생각이 듭니다. Project zero 링크 줘서 안에 있는 POC줬는데 안되는데 그럼 어케함? 이라고 생각하실수 있습니다.

근데 다시 디핑 파일을 보시게 되면

도저히 모르겠으니 Turbolizer라는 툴을 이용하여 Turbofan 동작 과정을 살펴봅시다.

아까전에 코드를 Turbolizer를 통해서 보게되면 Numberexpm1 그대로 samevalue에 들어가 리턴을 해주고 있습니다.

다음 단계를 보게되면 sameValue 와 -0이 합쳐저 Minuszero 로 바뀐것을 확인 할 수 있습니다.

그 후 값은 Float64Expm1 로 바뀌게 되고 ObjectisMinuszero 에서 NumberisMinuszero 로 바뀌게 됩니다

 

FloatExpm1 이 함수는 숫자에 최적화 된 노드입니다. 그리고 컴파일러는 이 함수에 인자가 숫자일 것이라고 추측하며 최적화된 코드를 실행할 것입니다.만약 숫자가 아닌경우 최적화된 함수를 인터프린터에 도움을 받아 deoptimization 하게 됩니다. 이떄 인터프린터는 모든 유형을 수용 할 수 있는 built_in 을 사용하게 됩니다. 이제 이 함수가 다음에 컴파일이 될떄 이 함수의 인자는 숫자가 아니라는 정보를 계속 가지고 있습니다.

 

 

--trace-deopt 라는 옵션으로 Floatexpm1 이 deopt 되는것을 볼수가 있습니다.

 

이런식으로 코드를 짜준후 foo 인자에 문자 값을 넣습니다.



코드를 작성후 실행하게되면 앞에 나온것처럼 deopt 가 되는것을 확인 할 수 있습니다. 그럼 이제 저희가 원하는 곳에 취약점을 터트릴 수 있으니 코드를 다시 작성해 봅시다.

아까 diff 에 의해서 이제 이 코드를 작성 한 후 돌리게되면 두번째 출력에서 false가 나올 것입니다.

이제 디핑대로 3번쨰꺼까지 전부다 false를 반환하게 됩니다. 왜 그러냐를 보기위해 turbolizer를 다시 보겠습니다

turbolizer최적화를 typer로 바꿔준 후 JSCall 을 PlainNumber | NaN 으로 반환해 준다. 패치된 내역하고 같습니다.

typed lowering 으로 바꾸고 다시 확인해보면 minuszero 와 Plainumber 하고 합쳐지면서 false를 리턴을 해주는것을 확인 할 수 있습니다. 여기서 추측해 줄 수 있는게 turbofan 은 -0이라는 값이 나올수 없다고 판단하여 무조건 false를 반환하게 되었습니다.

그럼 여기까지 조합한걸 가지고 checkbounds 를 제거 하여 OOB를 할 수 있다는 뜻입니다. 그럼 간단한 OOB코드를 작성해봅시다.

간단한 OOB코드를 작성하여 아까 취약점을 적용을 해보았습니다. 이 코드를 실행하면 turbofan 은 b에 반환값을 무조건 0이라고 생각할 것입니다. 그렇게 되면 checkbounds 는 당연히 제거가 될것이고 oob가 가능하게 될 것입니다.

하지만 되지 않습니다. 이유는 math.expm1 함수가 -0이 나올수가 없다고 turbofan 이 가정을 하고 있기 떄문에 return을 true로 만들어도 b 가 0이였던 것입니다.

 

다시 생각을 해봅시다.EscapeAnalysisPhase 전에 값이 확정이 되어버리면 변수b의 값은 0이 되어버리고 맙니다. 왜냐하면 math.expm1 에 리턴값은 절대 -0 나올수 없다고 결정 되어버리기 떄문입니다. 그럼 저희는 최대한 -0 비교를 늧춰야됩니다. 이 방법에 최적화된 코드를 작성해보면 이런식으로 가능합니다.

aux 라는 매개변수가 있는데 이 값은 EscapeAnalysisPhase 전까지 값을 알 수가 없습니다. 그럼 이제 둘이 값이 같은 것을 알았으니 sameValue 노드를 생성을 합니다. 그러면 이제 sameValue는 -0 값이 되게 되는데 math.expm1 에 리턴값은 -0이 될수 없으니 turbofan 은 object.is 함수에 리턴값을 무조건 false라고 판단할 것입니다. 그럼 이제 SimplifiedLoweringPhase 단계에서 Checkbounds 가 완전히 제거되게 됩니다.

여기서 의문이 드는데 하게되며 세번째 호출해주는 과정에서 원래 -0 리턴값은 false입니다. 하지만 실제로는 true를 반환해주고있습니다.다시말해서 외부적으로는 false를 말해주지만 실제로는 true가 반환된겁니다. 사실 false는 고정된 값이 아닙니다.. 왜 값이 false로 고정되지 않냐면 기능이 없어서 입니다.

먼저 익스플로잇에 필요한 코드입니다. 배열 2개를 만들어 줌으로써 변수a를 oob시켜 oob에 .lenth 부분을 변경해 줍니다.

 

잘 변경되었는지 확인해 주기 위해서 DebugPrint 를 추가해 주었습니다.

다음은 저희가 원하는 변수의 메모리를 Leak을 해야지 익스가 가능합니다. 그래서 위에 코드를 짰습니다.

디버거를 보게되면 oob를 변수를 oob시켜 victim 안에있는 argv 값을 return을 해주게되면 원하는 변수에 메모리를 출력이 가능합니다

먼저 저희는 이제 원하는 변수의 메모리 값을 알았으니 원하는 주소의 메모리 값을 Leak 할수 도 있습니다.

이 코드는 arb_buf 에 BackingStore pointer를 덮는 것입니다.

backingstore를 덮게되면 그 주소에 있는 값들이 배열에 들어가게됩니다.

GDB로 확인해보면 그 값에 있는 메모리들이 배열에 들어가는것을 확인 할 수 있습니다.

메모리 쓰기도 같은 원리로 배열에 값을 추가하는것밖에 다른게 없습니다.

마지막은 v8 구버전 이기때문에 wasm을 이용하여 RWX권한이 있는 영역을 만들어 줄 수 있습니다. 이것을 이용하여 RWX권한이 만들어진 곳을 Leak 을 한후 쉘코드로 덮어주었습니다. 그런 후 RWX권한이 있는 영역을 호출 해 주면 익스플로잇이 됩니다.

 

REF :

 

www.jaybosamiya.com/blog/2019/01/02/krautflare/

sunrinjuntae.tistory.com/171

aro7i.github.io/post/35c3-ctf-2018-krautflare-writeup/