블로그 운영을 시작합니다.새로운 글을 쓰기 전에 이전에 썼던 글을 미리 링크합니다.블랙펄시큐리티에 다니면서 브라우저 취약점에 대해 공부했던 내용입니다. 자바스크립트 엔진 취약점을 만들고, 익스플로잇을 연습하기까지 과정을 적어놓았습니다.
https://bpsecblog.wordpress.com/2017/04/27/javascript_engine_array_oob/
Javascript Engine(Spider Monkey) Array OOB Analyzing
안녕하세요.
블랙펄시큐리티 pesante입니다.
2016년 화이트햇 콘테스트, 2017년 코드게이트 문제를 내면서 자바스크립트 엔진을 공부하게 되어 글을 씁니다. 사실 문제의 라이트업이라기보다 분석하면서 삽질했던 것들을 써보려 합니다. 원래는 크롬이 쓰는 V8을 이용하여 문제를 내려고 했으나 소스를 분석해본 결과 모질라의 spidermonkey가 조금 더 소스가 직관적으로 되어 있어 문제를 내기가 용이했습니다.
- V8 자바스크립트 엔진(V8 JavaScript Engine)은 구글에서 개발된 오픈 소스 JIT 가상 머신형식의 자바스크립트 엔진이며 구글 크롬 브라우저와 안드로이드 브라우저에 탑재되어 있다.
- SpiderMonkey is the code name for the first JavaScript engine, written by Brendan Eich at Netscape Communications, later released as open source and currently maintained by the Mozilla Foundation. SpiderMonkey provides JavaScript support for Mozilla Firefox and various embeddings such as the GNOME 3 desktop.
가장 강력한 취약점 중 하나인 Array의 out of bound 취약점을 목표로 삼았습니다. 목차는 다음과 같습니다.
- OOB(Out Of Bound) 취약점 개요
- spidermonkey 다운로드 및 컴파일
- 소스분석 및 OOB 배열 생성
- JIT 개념 및 테스트
- Array 메모리 분석 및 익스플로잇
- 후기
a
1. OOB(Out Of Bound) 취약점 개요
OOB(Out Of Bound)는 여러가지 취약점 중 가장 강력한 취약점 중 하나로 Array의 lengh를 속여 해당 길이만큼의 메모리를 read 혹은 write 할 수 있습니다. 자바스크립트 엔진 코드 안에 Array의 크기는 최대 0xffffffff로 정의되어 있고 십진수로 전환하면 4294967295만큼의 메모리를 읽고 쓸수 있습니다. 32bit 어플리캐이션이라면 모든 메모리를 쉽게 바로 읽고 쓸 수 있으며 64bit 어플리캐이션 또한 약간의 테크닉을 이용하여 모든 메모리를 읽고 쓸 수 있습니다. 물론 해당 메모리에 읽기, 쓰기, 접근을 하려면 각각의 권한이 있을때 가능합니다.
그럼 이제 직접 자바스크립트 엔진을 컴파일하여 분석해보겠습니다.
2. spidermonkey 다운로드 및 컴파일
SpiderMonkey 공식 홈페이지에 다운로드 및 컴파일을 하는 방법이 잘 나와있습니다.
https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey
컴파일하기 전에 다음과 같은 패키지들이 필요합니다. 미리 설치를 해둡니다.
apt-get updated
apt-get install python-pip gcc make g++ perl python autoconf -y
spidermonkey의 컴파일 방법은 생각보다 간단합니다. 우선 아래 명령어를 통해 소스를 다운로드 받고 압축을 풉니다.
mkdir mozilla
cd mozilla
wget http://ftp.mozilla.org/pub/mozilla.org/js/mozjs-24.2.0.tar.bz2
tar xjf mozjs-24.2.0.tar.bz2
그리고 다음과 같이 configure 하면 해당 컴퓨터에 필요한 패키지가 있는지 검사합니다. 만약 없으면 apt-get으로 설치후 build 폴더를 clear한 다음에 다시 configure 합니다. 단, 코드게이트 2017 예선 문제는 pie를 추가하기위해 configure를 할때 CXXFLAGS=”-fpic -pie” 옵션을 추가하여 컴파일했습니다.
$ mkdir build
$ cd build
$ ../mozjs-24.2.0/js/src/configure
creating cache ./config.cache
checking host system type... x86_64-apple-darwin16.4.0
checking target system type... x86_64-apple-darwin16.4.0
checking build system type... x86_64-apple-darwin16.4.0
checking for gawk... no
checking for mawk... no
checking for nawk... no
checking for awk... awk
checking for clang... /usr/bin/clang
.........
Reticulating splines...
Finished reading 7 moz.build files into 20 descriptors in 0.01s
Backend executed in 0.03s
16 total backend files. 16 created; 0 updated; 0 unchanged
Total wall time: 0.04s; CPU time: 0.04s; Efficiency: 99%
invoking /usr/bin/make to create js-config script
rm -f js-config.tmp
/Users/ijihun/Documents/blackperl/mozjs/mozilla/build/_virtualenv/bin/python ../mozjs-24.2.0/js/src/config/Preprocessor.py --marker % -Dprefix="/usr/local" -Dexec_prefix="/usr/local" -Dincludedir="/usr/local/include" -Dlibdir="/usr/local/lib" -DMOZILLA_VERSION="" -DLIBRARY_NAME="mozjs-" -DJS_CONFIG_LIBS=" -dynamiclib -install_name @executable_path/libmozjs-.dylib -compatibility_version 1 -current_version 1 -single_module -lm -lz" -DJS_CONFIG_MOZ_JS_LIBS="-L/usr/local/lib -lmozjs-" -DMOZJS_MAJOR_VERSION="" -DMOZJS_MINOR_VERSION="" -DMOZJS_PATCH_VERSION="" -DMOZJS_ALPHA="" -DNSPR_CFLAGS="" -DNSPR_PKGCONF_CHECK="nspr" -DUSE_CXX11="" ../mozjs-24.2.0/js/src/js-config.in > js-config.tmp \
&& mv js-config.tmp js-config && chmod +x js-config
그 다음 make를 쳐주기만 하면 엄청난 로그들과 함께 build를 하기 시작합니다. 소스가 꽤 크기 때문에 build하는 데에 시간이 꽤 걸립니다(10분~20분 소요).
$ make
.........
/Applications/Xcode.app/Contents/Developer/usr/bin/make tools
/Applications/Xcode.app/Contents/Developer/usr/bin/make -C config tools
make[2]: Nothing to be done for `tools'.
/Applications/Xcode.app/Contents/Developer/usr/bin/make -C editline tools
make[2]: Nothing to be done for `tools'.
/Applications/Xcode.app/Contents/Developer/usr/bin/make -C shell tools
make[2]: Nothing to be done for `tools'.
/Applications/Xcode.app/Contents/Developer/usr/bin/make -C jsapi-tests tools
make[2]: Nothing to be done for `tools'.
/Applications/Xcode.app/Contents/Developer/usr/bin/make -C tests tools
make[2]: Nothing to be done for `tools'.
/Applications/Xcode.app/Contents/Developer/usr/bin/make -C gdb tools
make[2]: Nothing to be done for `tools'.
if test -d dist/bin ; then touch dist/bin/.purgecaches ; fi
컴파일이 끝나면 여러 오브젝트가 생성됩니다. 우리가 이용해야 할 핵심 파일은 js라는 파일입니다. ls를 통해 해당 파일을 확인해보면 아래와 같습니다.
$ ls -l js
lrwxr-xr-x 1 ijihun staff 62 2 12 17:14 js -> /Users/ijihun/Documents/blackperl/mozjs/mozilla/build/shell/js
js를 실행했을 때 “js>”라는 인터프리터 쉘이 뜨면 컴파일은 완료되었고 실행이 된것입니다.
3. 소스분석 및 OOB 배열 생성
다음은 임의로 소스를 수정하여 특정 조건을 만족했을때 OOB 취약점을 가진 Array를 만드는 것이 목표입니다. Array에 관련된 코드는 대부분 “/mozjs-24.2.0/js/src/jsarray.cpp”에 존재합니다. 기존 Array에서는 pop할때 길이가 0이라면 아무 작업을 하지 않고 리턴합니다. 그러나 코드를 수정하여 길이가 0일때 pop하면 길이가 0xffffffff가 되어 OOB가 발생하도록 했습니다. jsarray.cpp에서 다음의 함수를 수정했습니다.
js::array_pop(JSContext *cx, unsigned argc, Value *vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
/* Step 1. */
RootedObject obj(cx, ToObject(cx, args.thisv()));
if (!obj)
return false;
/* Steps 2-3. */
uint32_t index;
if (!GetLengthProperty(cx, obj, &index))
return false;
----------------코드 수정 전-------------------------
/* Steps 4-5. */
if (index == 0) {
/* Step 4b. */
args.rval().setUndefined();
} else {
/* Step 5a. */
index--;
/* Step 5b, 5e. */
JSBool hole;
if (!GetElement(cx, obj, index, &hole, args.rval()))
return false;
/* Step 5c. */
if (!hole && !DeletePropertyOrThrow(cx, obj, index))
return false;
}
----------------------------------------------------
----------------코드 수정 후-------------------------
/* Step 5a. */
index--;
/* Step 5b, 5e. */
JSBool hole;
if (!GetElement(cx, obj, index, &hole, args.rval()))
return false;
/* Step 5c. */
if (!hole && !DeletePropertyOrThrow(cx, obj, index))
return false;
----------------------------------------------------
// Keep dense initialized length optimal, if possible. Note that this just
// reflects the possible deletion above: in particular, it's okay to do
// this even if the length is non-writable and SetLengthProperty throws.
----------------코드 수정 전-------------------------
if (obj->isNative() && obj->getDenseInitializedLength() > index)
obj->setDenseInitializedLength(index);
----------------------------------------------------
----------------코드 수정 후-------------------------
if (obj->isNative() )
obj->setDenseInitializedLength(index);
----------------------------------------------------
/* Steps 4a, 5d. */
return SetLengthProperty(cx, obj, index);
}
수정 후 build 폴더에서 make만 치면 수정된 파일만 다시 컴파일할 수 있습니다. 다음과 같이 test.js라는 테스트 스크립트를 통해 OOB가 발생하는지 확인합니다.
//test.js
a=[]
a.pop()
print('length:'+a.length)
for (var i=300; i<350; i++)
print(a[i]);
js의 인자로 test.js를 넘겨준 후 실행시키면 다음과 같이 메모리 릭이 되는 것을 알 수 있습니다.
root@ubuntu:~/mozilla/build# ./js test.js
length:4294967295
6.923693930519e-310
8.289053e-317
6.9236938849808e-310
6.9236938849887e-310
6.92369388110297e-310
6.9236939312858e-310
2.130284842e-314
6.92369388498277e-310
6.92369388499067e-310
6.92369388111364e-310
6.9236939305206e-310
8.2890535e-317
Part-2 에서는 이 취약점에 대해 익스플로잇을 하는 법에 대해 배워보겠습니다.
“브라우저 취약점 익스플로잇 Part-1”에 대한 답글 1개