R&D
이번생에 버그헌팅은 처음이라
오우진
Sep 24, 2020

이번생에 버그헌팅은 처음이라

많은 주니어 해커들은 CTF로 해킹을 배운다. 나 역시도 많은 CTF를 참여하며 실력을 키워나갔다. CTF는 제한된 시간안에 많은 지식을 배울수있는 좋은 “Play”다. 하지만 리얼월드와 상당한 괴리감이 존재한다고 생각한다. 내가 CTF에서 일명 고인물이 돼가는 시점에서 버그헌팅을 시작했을때 정말 막막했다. 어떤 소프트웨어를 대상으로 할지도 몰랐고 Fuzzer를 만들어야될지 순수 오디팅으로 찾아야될지 선택하는것도 정말 어려웠다. CTF와 달리 버그헌팅은 명확한 Goal과 Methodology이 존재하지않았다.

인터넷상에는 1day 분석글이나 Exploitation관한 기술론 밖에 없었다. 버그헌팅하는데에 많은 도움을 주었지만 처음시작하는 사람들에게는 이러한글보다 버그헌팅 스토리가 더 도움이 될거라고 생각한다.

이 글에서는 기술적인 내용보다 내가 2019년부터 오늘날까지 버그헌팅을 하기위해 어떤노력을 하였고 어떻게 학습하였는지를 이야기할 것이다.

포석

바둑에서는 초반에 돌을 두는 방법들을 총칭하여 포석이라한다. 이는 대국 중반부터 게임을 이끌어나가기위한 중요한 열쇠이기때문에 신중하게 두곤한다. 나도 버그헌팅을 처음 시작할때 좋은 포석을 두기위해 많은 고민을 했다. 먼저 나의 장점과 단점을 생각해보았다. CTF을 할때부터 나는 리버싱실력이 다른 동기들에 비해 수준이하였지만 취약점은 나름 빠르고 쉽게 찾았던거 같았다. 그래서 리버싱이 비교적 많이 필요한 클로즈소스 소프트웨어보다 소스코드가 공개돼있는 오픈소스 소프트웨어를 타겟으로 하기로 마음먹었다.

오픈소스는 상당히 진입장벽이 높았다. 대게 버그바운티를 하는 오픈소스들은 많은 해커들의 타겟이 되어왔고 이미 많은 취약점들이 발견됐다. 그 중 웹브라우저는 레드오션 그 자체였다. 하지만 그 만큼 부와 명예가 따르고 있었기때문에 나의 첫 취약점을 웹브라우저 취약점으로 멋있게 장식하고 싶었다.

삼자택일

웹브라우저는 대표적으로 Firefox, Safari, Chrome, Edge가 있다. 그 중 Edge는 현재 Chromium 코드를 사용하지만 내가 버그헌팅을 시작했을때는 Mshtml과 ChakraCore엔진을 사용하고있었다.

FireFox와 Chrome은 다른 브라우저보다 최신 Web Platform 기능들을 빨리 적용하는 특징이 있었다. 또한 Chrome의 경우 다양한 레퍼런스가 Public하게 제공되어서 전체적인 아키텍처를 이해하는데 많은 도음을 받을수있었다. 이러한 이유때문인지 Chrome은 많은 버그들이 제보되어왔고 지금은 Critical한 버그들이 많이 줄어든 상태이다. 흔히들 난공불락이라고도 부른다.

chrome-vrp

하지만 버그바운티 금액이 상당히 높기때문에 해볼만한 도전이라고 생각했다. 그리고 앞서 말했듯이 공개된 레퍼런스가 많아서 혼자 학습하는데 좋은 조건이였다.

콩 심은 데 콩 나고 팥 심은 데 팥 난다

CTF를 하다보면 문득 고등학교때 수학문제를 푸는 상황이 연상될때가 있다. 정해진 답이 있고 필요한 것은 연필, 지우개만 있으면 됐다. CTF도 마찬가지로 시스템해킹문제가 나오면 문제바이너리를 실행할수있는 VM환경과 리버싱을 위한 IDA 하나면 충분했고 출제자의 “의도”대로 취약점을 찾으면 됐다.

하지만 버그헌팅은 달랐다. 출제자의 의도라는건 애초에 존재하지않고 CTF보다 비교도 안되게 큰 리얼월드 바이너리를 편하게 분석할 수 있는 환경을 만드는것이 보기보다 어려운 작업이였다.

내 타겟은 크로미움이였고 매우 큰 오픈소스 프로젝트이기때문에 필연적으로 분석할수있는 충분한 성능의 데스크탑이 필요하였다.(성능이 안좋을경우 생각보다 빌드시간이 많이 낭비된다)

빌드환경 구축은 구글에서 충분히 자세하게 문서로 정리되어있었다. (해당 문서가 생각보다 길어보일수있는데 유용한 팁들이 많이 적혀있으니 꼼꼼히 읽어봐야한다. 예를 들어 빌드시간을 단축할수있는 팁)

처음에는 빌드하기도 어려웠고 디버깅하기도 어려웠다. 하지만 여러가지 시행착오를 겪으며 노하우가 생기고 나에게 맞는 환경을 구축할 수 있게되었다.

(빌드, 디버깅, Exploit 개발 환경을 효율적으로 구축하는 것도 실력이고 자산이라는 것을 알게되었다.)

사활

Chromium의 Attack Surface는 대표적으로 자바스크립트 엔진인 v8과 Web Platform엔진인 Blink가 있다. 나의 경우 2017년에 ChakraCore엔진을 본 경험이 있어서 V8를 먼저 보기로하였다. 지금 생각해보면 이 선택이 지금까지 Chromium에서 취약점을 찾는데에 있어서 좋은 기반을 쌓게해준거 같다. (웹브라우저의 원격 취약점들은 다양한 컴포넌트들이 있지만 대부분 Javascript로 Trigger한다. 이 때문에 javascript 엔진을 정확히 이해하는것은 많은 도움이 된다.)

고찰

Fuzzing 은 정말 매력적인 기법이다. 이미 많은 해커들이 Fuzzing을 해왔고 지금 이 순간에도 취약점들이 찾아지고있을것이다. 누구나 버그헌팅을 시작할때 Fuzzer를 만들어볼까라는 생각이 해보았을것이고 나도 그런 생각을 많이 했다. 하지만 과연 내가 지금 Fuzzer만들어서 의미있는 결과를 만들수 있을까?라는 생각이 더 지배적이였다. 물론 창의적인 Fuzzing Idea를 생각해내면 좋은 결과가 나왔었겠지만 나는 뭐 딱히 좋은 아이디어가 생각나지 않았다. 그래서 좋은 아이디어가 생각날때까지 Fuzzing을 사용하지않고 소스오디팅을 하기로하였다. (아직까지도 소스오디팅을 하고 있다..)

처음에는 무작정 코드를 읽었고 6개월이상동안 열심히 하였지만 취약점을 하나도 못찾았다. 이 일을 겪은 후 난 무작정 소스오디팅으로 찾는건 비효율적이라는것을 깨달았다. 오래전부터 Javascript대상으로 Fuzzing연구들이 많이 되어왔기때문이다. 이 깨달음은 별거없어보이지만 내 버그헌팅에 있어서 큰 전환점이 되었다.

이후 난 Fuzzing과의 싸움에서 이길수 있는 방법을 계속해서 고민했고 여러가지 시행착오를 거쳐 몇가지 결과를 도출해냈다.

도약

v8 코드와 열심히 싸우는 도중에 우연히 Project Zero 블로그에서 글 하나를 보게되었다. 내용은 대충 WebAssembly 취약점에 관한 것이였다. 버그 클래스들은 딱히 흥미로운게 없었다.하지만 난 WebAssembly의 미래와 관심도(?)를 유심있게 보았다. 내가 왜 이러한 생각을 한 것인지 이해하려면 꽤 오래전으로 거슬러 올라가야한다.

2016년, ChakraCore가 처음으로 공개되었을때 취약점이 아주 많이 발생했다. 대게 ECMAScript6 와 관련된 취약점이였다. 왜 이런 취약점이 많이 발생했을까? 물론 ChakraCore가 릴리즈된지 얼마안돼서 그런것 일 수 도 있다. 하지만 근본적인 문제는 ECMAScript 6 표준 구현이라고 생각한다. 표준을 코드로서 구현하는것은 매우 어려운일이다. V8, SpiderMonkey, JavascriptCore들은 ECMAscript라는 같은 표준을 사용하지만 구현코드를 보면 전혀 다르다. ChakraCore는 릴리즈된지 얼마되지도 않았을뿐만 아니라 ECMAScript 6도 Draft가 끝난지 얼마 안된상태에서 구현을 했기때문에 매우 불안정한 상태였을것이다. 이는 ChakraCore의 수많은 취약점들이 증명해준다.

난 이 상황이 WebAssembly에도 적용될것이라고 생각했다. WebAssembly또한 ECMAscript처럼 표준이 존재하며 현재 많은 기능들이 추가되고 있다. 무엇보다 V8과 SpiderMonkey는 다른 Javascript엔진들보다 훨씬 빨리 표준을 구현하고있다. 표준이 구현된지 얼마 안되었고 계속해서 개발되고있기때문에 분명히 취약점이 있을거라고 확신했다.

또한 WebAssembly 취약점에 대한 조사를 하던도중 흥미로운 것을 발견하였다. The Problems and Promise of WebAssembly 포스팅은 2018.8에 작성되었고 10개 남짓한 Webassembly 취약점들은 2018 ~ 2017 사이에 발견되었다. 이 말은 2018년 이후 WebAssembly 취약점이 발견되지 않았다는것이다. 위에서 언급했듯이 WebAssembly는 활발히 개발되고있었기때문에 2018년 이후에 새로 나온 기능들이 상당히 많았다. 이 기능들은 아직 해커들에게 관심을 못했기때문에 충분한 블루오션이였다.

데뷔전

나의 첫번째 두번째 취약점은 WebAssembly에서 나왔고 위 전략이 효과적이었다는것을 증명해준다.

나의 첫 취약점, issue 964607 는 WebAssembly Reference Table 구현 코드에서 발생했다. 이 기능은 활발히 개발중(experimental)이였기때문에 보안성이 매우 떨어진 상태였다. 리포트를 보면 알 수 있듯이 취약점 자체는 매우 쉽고 간단했다. 하지만 우리는 여기서 취약점의 복잡성, 창의성보다는 이 취약점이 어떻게 기인되었는지를 주목해야한다.

WebAssembly는 초기에 Function Table만 있었지만 Any Reference Table이 새로 생기게되었다. 이러한 객체 타입의 확장은 이전기능과 새로운 기능 구현 코드들이 필연적으로 병합된다. 기존에 구현된 코드에서 사용된 모듈들은 새로 추가된 기능들에 맞춰 변화해야 될 수 도있다. 이 과정에서 예기치 못한 상황이 발생될수있다. 예를 들어 배열 길이의 증가, 배열 타입 변형, User callback(side effect) 등이 있다. issue 964607 는 변화한 배열 길이의 증가를 예상하지 못해 발생했다.

두번째 취약점(issue 980475)도 WebAssembly Reference Table과 관련이 깊다. 첫 취약점과 다른점이 있다면 WebAssembly의 새로운 기능인 bulk memory와도 관련이 있다는것이다. 이 취약점 역시 새로 추가된 기능이 기존 모듈에 영향을 줄 수 있다는 것을 예상하지못해 발생했다.

나의 데뷔전은 이렇게 마무리가 되었다. 비록 Stable이 아닌 experimental 취약점들이여서 CVE는 못 받았지만 이것으로 인해 많은 성장을 이루었다는것은 확실하다.(Stable이 아니여도 포상금을 똑같이 받을수있다!)

comment

수명주기

수험생 시절 역사 선생님들이 항상 하시던 말이 있었다. 역사는 반복된다. 전 세대에 있었던 일이 현 세대에도 일어날수 있다는 말이다. 1-day 취약점들을 조사하면서 이 말이 버그에 적용될수도 있다는 생각이 들었다.

Issue 782145는 2017년 11월에 발생한 취약점이다. RegExp 객체는 fast type과 slow type이 존재한다. Replace함수는 인자로 RegExp 객체를 받을 수 있는데 타입에 따라 함수 Flow가 달라진다. 하지만 해당함수에서는 타입을 적절하게 검사하지 못하여 Type Confusion이 일어날수 있었다.

다음은 pwn2own 2019에서 사용된 Issue 944971이다. 해당 취약점은 Issue 782145와 매우 유사하였다. poc코드를 보면 더욱 비슷해보일뿐만 아니라 Root Cause도 같은 패턴이었다. 마치 생명주기를 보는듯 했다. 이와 비슷한 현상을 갖는 취약점들은 한 두개가 아니였다.

이로부터 내가 얻은 교훈이 있었다.

  1. 1-day 분석은 전체적인 코드의 흐름뿐만 아니라 미래의 다시 나올 취약점에 대비를 할수있다.

  2. 안전했던 코드도 첨삭을 통해 다시 취약해질수 있다.

때로는 우연히

WebAssembly 취약점을 찾은후 학교생활을때문에 몇달간 버그헌팅을 못했다. 2020년 2월부터 다시 시작했는데 이때는 Chrome Mojo(Sandbox Escape 취약점) 붐이 일어나고 있었다. 원래 Javascript 취약점을 다시 찾고싶었지만 트렌드에 따라 Mojo 취약점을 보기 시작했다.

Mojo에 관련된 레퍼런스들은 생각보다 많았고 Mark BrandMan Yue Mo가 찾은 Mojo 취약점들은 아주 많은 도움이 됐다. Mojo를 공부한지 한달이 됐을 무렵에 소스오디팅을 시작했다. Mojo붐은 이미 1년넘게 지속된지라 블루오션에서 레드오션으로 변화되는 시점이였다. 지푸라기라도 잡고싶은 심정으로 코드를 주구장창 보았다. 하지만 2주넘게 수확이 없었다.

포기 할 때 쯤 우연히 한 커밋을 보았다. 이 커밋은 내가 1주전에 보았던 WebSocket Mojo Service 부분이였고 몇몇의 함수을 추가하는 커밋이었다.

처음에는 별 생각 안하고 리뷰를 했지만 이 커밋으로 인해 전에는 없었던 UAF 취약점이 일어날수 있다는것을 인지하는데 그리 많은시간이 걸리지 않았다. 난 이 취약점을 발견하고 나서 꾸준히 커밋을 보는 습관이 생겼다. 이 습관은 향후 내 버그헌팅 삶의 많은 발전을 기여했다. 버그헌팅을 부업으로 삼고있는 사람에게 딱 한개의 조언을 할 수있다면 난 무조건 커밋 읽는 습관을 들이라고 말 할 것이다.

때때로 취약점은 작은 커밋 하나로 기인 될 수 있다.

같은 그림찾기

스타크래프트에는 다양한 치트키가 있다. Show me the money라고 치면 미네랄과 가스를 얻을 수 있고 Power Overwhelming를 치면 무적이 될 수 있다. 버그헌팅에는 당연히 치트키가 없다. 하지만 비슷한 것은 있다! 바로 버그클래스다. 좋은 버그 클래스를 발견하면 그것 하나로도 상당히 많은 취약점을 찾을 수 있다. 나도 몇개의 버그 클래스로 많은 재미를 봤다.

좋은 버그 클래스는 무엇일까? 난 두 가지 조건이 필요하다고 생각한다.

  1. 다른 해커는 모르고 나만 아는 버그 클래스이다.

  2. 개발자는 모르고 해커만 인지하고 있다.

사실 첫번째 조건이 충족되면 두번째 조건도 충족될것이다. 하지만 두번째 조건만 충족해도 많은 재미를 볼 수 있을것이다. 버그클래스가 한번 리포트되면 해커들에게 알려지는건 시간문제이고 다른해커들이 찾기전에 내가 더 빨리 찾으면 된다.

Issue 931640 리포트를 보면 재밌는 트릭이 있다.

Object.prototype.__defineGetter__("then", function() {

  if (++then_counter == 1) {

   controller.close();

   performMicrotaskCheckpoint();

  }

 });

이 트릭은 2016년 UXSS 취약점에서 기인한 트릭이다.

var thenable = {get then() { console.log(2); }}

console.log(1); Promise.resolve(thenable); console.log(3)

Promise.resolve는 인자로 오브젝트를 받으면 then이라는 property를 가져온다. 이때 유저는 getter를 사용하여 then를 가져올때 user-defined function를 실행할수있다. 아마 이 글을 읽고있는 대부분의 독자분들은 몰랐을것이다. 보통의 개발자들도 이러한 스펙을 알지 못했을것이다. 이러한 side effect는 많은 취약점을 야기 시킬 수 있다.

내가 이 Thenable Object 트릭을 인지하고 찾기 시작했을때는 다른 해커들이 별로 관심을 갖지않고 있었다. 덕분에 많은 취약점을 찾을 수 있었다. 하지만 3~4달후에 해커들이 해당 트릭을 인지하고 많은 버그들이 Kill 되어버렸다…

재미를 본 또다른 버그클래스는 render_frame_host라는 raw pointer의 lifetime 문제였다. 이 클래스는 Plaid CTF 문제로 출제되었다. 이로인해 해커들에게 많은 관심을 받게되었다. 그래서 난 빠르게 취약점을 찾아 바로바로 신고하였다.

앞서말한 좋은 버그 클래스의 조건중에서 첫번째 조건을 만족 시키는것은 매우 어려운 일이다. 그렇기때문에 이미 공개된 버그클래스를 선별하여 빠르게 찾는게 더 효율적인 방법이라고 생각한다.

초읽기

크롬에서 버그헌팅을 하다보면 정식버전(stable)이 아닌 취약점을 찾을때가 많다. 솔직히 버그를 찾는 입장에서 Stable취약점을 찾아 CVE credit을 받고 싶을것이다.(stable이 아니면 CVE Credit안줌) 그래서 이러한 취약점을 찾았을때 난 가끔 초읽기를 한다. 크롬은 보통 한달마다 버전업을 한다. 스케쥴이 엄청 체계적이며 베타, 개발자 버전에서 많은 취약점들이 제거된다. 때문에 자신의 취약점이 정식버전으로 되는것은 여간 쉬운일이 아니다. 버그 초읽기를 하는건 정말 스트레스를 받는 일 이었다. 이제는 내 버그가 정말 찾기 어려운 버그인지 자가진단을 하고 초읽기를 하는 편이다.

나의 진단법은 chrome release security update를 보는것이다. 크롬은 일주일마다 보안업데이트를 하는데 그 주에 패치된 버그들의 ID를 공개해준다. 이를 통해 패치된 버그의 Root Cause를 분석하여 버그클래스를 알아낼수있다.

버그클래스들은 유행를 타기 때문에 내가 찾은 버그와 같은 클래스라면 초읽기를 그만두고 신고해야한다.

Chrome Platform Status 사이트에서 크롬 릴리즈 스케쥴을 확인할수있다. 이것을 보고 기도를 하자

복기

버그헌팅을 시작한지 2년차가 되어서야 실적이 조금씩 생기기 시작했다. 난 남들처럼 재능이 있는 해커가 아니었기때문에 코드를 “읽는다” 라는 것보다는 “익숙해진다”라는 느낌으로 해왔던거 같다. 매일매일 아침을 먹으며 코드와 커밋을 보니 이해가 안되던 크롬 아키텍처가 조금씩 익숙해졌다. 구조가 익숙해짐으로써 코드를 빠르게 읽어갈수있게되었고 취약점이 보이기 시작했다.

내가 버그를 찾았던 순간은 잠을 줄여가며 코드를 보던 밤이 아니라 편안한 숙면을 취하고 간단하게 코드를 리뷰하던 아침이었다. 버그헌팅은 CTF처럼 짧은 시간에 끝내는 놀이가 아니였다. 멀리보며 꾸준히 코드를 읽는게 중요하다는 것을 깨달았다.

물론 내가 부족했던점도 있었다. 레퍼런스들을 잘 보지 않았다는 것이다. 때때로 코드에서는 수천줄을 읽어야 되는 것을 레퍼런스 한개만 읽어도 이해되는 구조가 몇몇 있었다. 이러한 시행착오를 격고 최근에서는 레퍼런스를 열심히 읽는중이다.

이 글은 오로지 저의 생각이며 정답이 아닙니다. :D

오우진
RECENT POST
Minjoong Kim
Android 1day Exploit Analysis (CVE-2019-2215)
Android 1day Exploit Analysis by Newbie
이주협, 이주영
뉴비들의 하드웨어 해킹 입문기
뉴비들의 하드웨어 해킹 입문기