KalmarCTF 2023 - mjs (JS Engine, OOB)
Toddler’s first browser exploitation: https://github.com/cesanta/mjs
nc 54.93.211.13 10002
clone
,pwn
,warmup
- [52 solves / 100 points]
처음으로 포너블에 나온 자바스크립트 엔진 문제를 완주했다..! 풀이를 두 가지 소개한다. 첫 번째 풀이는 내가 풀이한 방식이고 두 번째는 친구가 풀이한 방식이다. 친구 풀이 보면서 많이 공부할 수 있었다.
Analysis
이 문제는 description에 포함된 mjs 레포를 참고해서 분석을 해야한다. 문제에서는 브라우저 익스라고 표현했지만 레포에 들어가서 Overview를 읽어보면 브라우저에 실제로 쓰이는 엔진인지는 잘 모르겠다..ㅎ IoT 기기에서 볼 수 있을 것 같다.
아무튼 이 문제에서 diff.patch 파일을 제공해줬는데 살펴보자.
1 | diff --git a/Makefile b/Makefile |
Built-in API에서 ffi
, ffi_cb_free
, mkstr
기능을 지웠다고 이해했다. 지워진 기능들 중 하나인 ffi
는 아주 강력한 API이다. 아래와 같이 사용이 가능한데, C함수를 아래와 같이 Import해서 사용 가능하다. 이것을 사용할 수 있으면 바로 system('/bin/sh\x00')
을 실행해버리면 될텐데..
1 | let f = ffi('int foo(int)'); |
- Import C function into mJS. See next section.
라는 생각을 하면서 이것저것 코드를 작성하다가 알게된 사실 두 가지가 있다. 미리 이야기하자면 OOB가 발생한다.
(1)
1 | getMJS()[0x41414141]; |
OOB가 발생해서 터진다. 터진 instruction을 보면 rax와 rcx를 더한 주소에 접근하려다가 터진 것을 볼 수 있다. getMJS 객체가 가리키는 곳은 0x005555555812a0
이다. 이 주소를 기준으로 원하는 주소에 접근하거나 쓸 수 있다. 테스트를 하다보면 OOB read와 OOB write 모두 발생한다. Solve 2에서 OOB read를 이용해서 필요한 주소를 leak했고, OOB write를 이용해서 got를 덮어서 문제를 익스했다.
Solve 1에서는 read나 write가 발생한다는 것에 집중하지 않고 임의 주소에 접근이 가능하다는 것만 생각하면서 소스 코드를 보고 풀이를 작성했다.
(2)
소스 코드를 보면서 알아낸 것이다.
patch에서 분명 API만 주석처리 했다. 그러면 저 함수는 어떻게 될까? 저 함수도 분명 컴파일이 되어서 바이너리 내부에 존재할 것이고 OOB로 임의 주소에 접근 가능하니까 저 함수를 실행할 수도 있겠다고 생각했다.
Solve 1
나머지는 이제 ffi
API 사용법만 알면 된다. 이 부분은 mjs 레포를 살펴보고 사용법을 익혔다.
Exploit Code
1 | let system = (getMJS+0x6a10)('int system(char *)'); system('/bin/sh\x00'); |
풀려서 당황했고 풀고나서 하루종일 기분이 좋았다.. ㅎ
Solve 2
OOB read와 write를 이용해서 필요한 주소를 leak하고 got를 덮어서 system('/bin/sh\x00')
를 실행시키는 방법이다.
libc leak
environ 이용해서 pie leak
- 처음에 프로그램이 시작되면 MJS 객체가 생긴다. (
0x005555555812a0
)getMJS()
를 호출하게 되면 힙에 새로운 주소가 생기는데 저 MJS 객체를 가리킨다. 참고로 environ은 stack에서 main 아래 영역을 가리킨다. 즉, environ에는 stack 주소가 저장되어 있다. stack에는 pie 주소가 저장되어 있기 마련이다. 처음에 MJS 객체를 가리키는 mjs와 mjs2를 생성했다. mjs2가 가리키는 주소를 MJS 객체가 아닌 environ으로 변경한다. OOB read를 이용해서 environ에 저장된 주소를 출력할 수 있는데 이 stack 주소에서 적절히 offset을 조절해서 pie base를 구할 수 있다.
- free의 got를 system 함수로 덮기
- 앞서 pie leak을 한 것과 비슷하다. mjs2가 가리키는 주소를 free_got로 변경하고 free_got에 system 주소를 쓴다.
- ‘/bin/sh\x00’
- system 함수는 인자가 하나니까 첫 번째 인자를 호출하는 함수를 조작하는 것을 목표로 한다. 두 가지 방식을 찾았다. mjs의 Built-in API 중 하나인
die
구현 내부에서 free를 호출한다. 이 방법이 친구가 풀이한 방식이다. 그리고 다시 풀이하면서 다른 방식도 찾아봤다. 또 다른 방식은 소스 코드 초반에//bin/sh
를 추가하고 그냥 자바스크립트 코드를 실행한다. mjs 바이너리에 자바스크립트 코드를 전부 실행하고 한 줄씩 해제하는 코드가 있기 때문에 해당 풀이가 가능하다.
Exploit Code
1 | //bin/sh |