천천히 정리하던 글이 하나 있었는데, 드디어 글로 올리게되었습니다. 오늘 이야기드릴 것은 웹 어셈블리와 보안분석 대한 내용입니다.

처음 개념을 접하는분들도 있을터이고, 아시는분들도 많을 것 같습니다.
나온지는 조금 지난 개념이지만, 최근에 블랙햇 발표로 한번 더 이슈가 되긴 했습니다. 관련 앱들이 많이 생길수록 보안적인 사항도 고려해야하기 때문에 글로 좀 정리해둘까 합니다.

웹 어셈블리로 만들어진 탱크게임 한번 하고 가시죠.
https://webassembly.org/demo/Tanks/



Web Assembly?

웹 어셈블리는 웹에서 바이너리 포맷을 처리할 수 있는 낮은 레벨의 어셈블리 언어입니다. 웹 위에서 동작하지만 네이티브단에서 동작하는 느낌으로 돌 수 있고, Javascript와 함꼐 동작할 수 있어 고성능 처리와 빠른 구현 모두 어느정도 잡을 수 있는 언어라고 보시면 됩니다.

공식 홈페이지에 있는 Hello world 예제인데, 딱 C랑 동일합니다.

main.c
#include <stdio.h>
#include <sys/uio.h>

#define WASM_EXPORT __attribute__((visibility("default")))

WASM_EXPORT
int main(void) {
  printf("Hello World\n");
}

/* External function that is implemented in JavaScript. */
extern void putc_js(char c);

/* Basic implementation of the writev sys call. */
WASM_EXPORT
size_t writev_c(int fd, const struct iovec *iov, int iovcnt) {
  size_t cnt = 0;
  for (int i = 0; i < iovcnt; i++) {
    for (int j = 0; j < iov[i].iov_len; j++) {
      putc_js(((char *)iov[i].iov_base)[j]);
    }
    cnt += iov[i].iov_len;
  }
  return cnt;
}

main.js
let x = '../out/main.wasm';

let instance = null;
let memoryStates = new WeakMap();

function syscall(instance, n, args) {
  switch (n) {
    default:
      // console.log("Syscall " + n + " NYI.");
      break;
    case /* brk */ 45: return 0;
    case /* writev */ 146:
      return instance.exports.writev_c(args[0], args[1], args[2]);
    case /* mmap2 */ 192:
      debugger;
      const memory = instance.exports.memory;
      let memoryState = memoryStates.get(instance);
      const requested = args[1];
      if (!memoryState) {
        memoryState = {
          object: memory,
          currentPosition: memory.buffer.byteLength,
        };
        memoryStates.set(instance, memoryState);
      }
      let cur = memoryState.currentPosition;
      if (cur + requested > memory.buffer.byteLength) {
        const need = Math.ceil((cur + requested - memory.buffer.byteLength) / 65536);
        memory.grow(need);
      }
      memoryState.currentPosition += requested;
      return cur;
  }
}

let s = "";
fetch(x).then(response =>
  response.arrayBuffer()
).then(bytes =>
  WebAssembly.instantiate(bytes, {
    env: {
      __syscall0: function __syscall0(n) { return syscall(instance, n, []); },
      __syscall1: function __syscall1(n, a) { return syscall(instance, n, [a]); },
      __syscall2: function __syscall2(n, a, b) { return syscall(instance, n, [a, b]); },
      __syscall3: function __syscall3(n, a, b, c) { return syscall(instance, n, [a, b, c]); },
      __syscall4: function __syscall4(n, a, b, c, d) { return syscall(instance, n, [a, b, c, d]); },
      __syscall5: function __syscall5(n, a, b, c, d, e) { return syscall(instance, n, [a, b, c, d, e]); },
      __syscall6: function __syscall6(n, a, b, c, d, e, f) { return syscall(instance, n, [a, b, c, d, e, f]); },
      putc_js: function (c) {
        c = String.fromCharCode(c);
        if (c == "\n") {
          console.log(s);
          s = "";
        } else {
          s += c;
        }
      }
    }
  })
).then(results => {
  instance = results.instance;
  document.getElementById("container").innerText = instance.exports.main();
}).catch(console.error);

잘 보시면 Javascript 코드에서 C에서 정의한 함수를 끌어쓰는 것(writev_c)을 볼 수 있습니다. 이렇게 고성능 처리같은 작업은 c단에서 돌리고 js로 웹에서 핸들링 해 사용하는 구조를 가질 수 있겠죠.
(거꾸로 putc_js 같이 c에서 js를 호출하는 함수도 있지요)

모질라쪽에선 emcc(https://github.com/juj/emsdk 의 sdk tool임)로 컴파일 하는 내용으로 가이드하고 있네요. wasm과 embed된 html을 바로 뽑을 수 있어서 테스트해보기엔 좋습니다.

#include <stdio.h>

int main(int argc, char ** argv) {
  printf("Hello World\n");
}

#> emcc hello.c -s WASM=1 -o hello.html

[ online IDE ]
https://webassembly.studio

[ 샘플 ]
https://github.com/mdn/webassembly-examples

그리고 .. 당연히 최신 브라우저들은 이미 적용되어 있기 떄문에 브라우저 개발자 콘솔에서 WebAssembly 오브젝트를 불러보면 내장된 function 정보를 얻어올 수 있습니다.
#> WebAssembly
WebAssembly
CompileError: function CompileError()
Global: function Global()
Instance: function Instance()
LinkError: function LinkError()
Memory: function Memory()
Module: function Module()
RuntimeError: function RuntimeError()
Table: function Table()
compile: function compile()
compileStreaming: function compileStreaming()
instantiate: function instantiate()
instantiateStreaming: function instantiateStreaming()
toSource: function toSource()
validate: function validate()
.....



Hacking/Security Analysis Point

사실 이번 포스팅의 목적이자 메인이 되는 부분입니다. 웹 어셈블리로 만들어진 어플리케이션은 무엇을 어떻게 봐야할까? 란 생각에 천천히 정리하고 있던겁니다.

우선 웹 어셈블리가 웹 기반에서 바이너리를 실행하고 자바스크립트와 연결해서 사용할 수 있기 때문에 전통적인 바이너리 공격 + 웹 해킹 기법이 모두 적용되는 케이스가 발생할 수 있습니다.

* 전통적인 바이너리 관련 공격들...
BOF, FSB, OOB 등등 알려진 기법들이 많이 있긴한데, Web Assembly 자체적으로 보안로직이 적용되어 있어서 ROP등은 어렵다고 합니다. (실제론 해봐야알듯?)
그리고 실행 구조떄문에 DEP나 SSP도 필요없어 적용되지 않았다고 하네요. 이쪽에서 가장 걱정하는건 Direct function call 인듯 합니다. 아마... XSS 등으로 JS 제어권을 가졌을 때 할 수 있는게 너무 많아져서 그런게 아닐까 싶습니다.
(정상 콜인지 구별하는것도 어려울테구요..)

* 웹 공격들..
기존 웹 공격과 약간 차이가 있다면, 추가적으로 웹 페이로드나 공격코드가 C단을 거쳐서 넘어올 수 있다는 점입니다.
웹 기반 방어로직은 당연하게 SOP 정도이고 non-web 플랫폼에선 POSIX 모델 적용이라고 합니다.

SOP도 어느정돈 강제적으로 적용된게, wasm 가져올때도 fetch 등을 이용하기 때문에 강제로.. 브라우저 SOP를 적용받게 됩니다.

fetch('https://www.hahwul.com')
Promise { <state>: "pending" }
교차 출처 요청 차단: 동일 출처 정책으로 인해 https://www.hahwul.com/에 있는 원격 자원을 차단하였습니다. (원인: ‘Access-Control-Allow-Origin’ CORS 헤더가 없음).[더 알아보기]

왜 fetch 등을 이용해서 가져올까? 라는 의문이 발생할 수 있는데요, WebAssembly는 아직 <script type='module'> 또는 ES2015 import statements와 통합되어 있지 않으므로 imports를 사용하여 브라우저에서 가져올 방법이 없습니다.
그래서 2 함수로 불러오는 경우가 권장되고 아래 함수 2개정도를 이용해서 가져오게 됩니다.  이 과정에서 대부분 fetch 등을 사용하게 되구요.

- WebAssembly.compileStreaming
- WebAssembly.instantiateStreaming

보안 관련해선 블랙햇 문서 읽어보시면 도움 많이됩니다.

https://i.blackhat.com/us-18/Thu-August-9/us-18-Lukasiewicz-WebAssembly-A-New-World-of-Native_Exploits-On-The-Web-wp.pdf

How to Testing??

아직 웹 어셈블리로 개발된 앱을 분석해본적은 없었습니다. 무엇부터 해야할까 고민이 좀 있었는데, 자료도 찾아보고 생각도 좀 해보니 외부 실행 파일을 JS에서 핸들링해서 쓴다는점에선 SWF 분석과 많이 유사한 부분이 있더군요.

그래서 전반적인 분석 방식을 SWF 보는 방식과 비슷하게 보려합니다. (바이너리 코드단 분석과 이를 이어주는 JS단 처리까지 보면 깔끔할듯 합니다)

SWF 분석 관련 포스팅
https://www.hahwul.com/2017/06/web-hacking-swfflash-vulnerability.html
https://www.hahwul.com/2017/06/web-hacking-swf-debugging-with.html
https://www.hahwul.com/2015/04/swf-ffdec-jpex-free-flash-decompiler.html

First - Find wasm file on web
우선 가장 먼저 웹 어셈블리 파일을 찾아야합니다. (wasm!!)
좋은 방법은 Javascript에서 웹 어셈을 다루는 instance를 찾아보는 방법인 것 같습니다. SWF, ActiveX 처럼 어차피 JS에서 핸들링하기 때문에 결국 코드에는 관련 함수나 주소 정보가 남아있기 마련입니다.
아까 위에서도 말씀드렸지만 웹 어셈블리의 경우 wasm을 불러올 때 instantiateStreaming 등의 함수로 가져온다고 했습니다. 함수 추적해보면 초기 로딩 부분에 가져오는 코드가 존재합니다.

WebAssembly.instantiateStreaming(fetch('simple.wasm'), importObject)
.then(results => {
  // Do something with the results!
});

아 물론, 이렇게 XMLHttpRequest, Ajax call 등으로 가져오는 경우도 있습니다. 결국은 웹 어셈블리의 instance 관련 부분을 찾는게 핵심이 되겠네요.

request = new XMLHttpRequest();
request.open('GET', 'simple.wasm');
request.responseType = 'arraybuffer';
request.send();

request.onload = function() {
  var bytes = request.response;
  WebAssembly.instantiate(bytes, importObject).then(results => {
    results.instance.exports.exported_func();
  });
};

그리고.. Firefox 디버거에서 wasm파일에 대해 볼 수 있음(아마 다른 브라우저도 동일할거임)
파일 경로 / 다운로드도 가능하며, 어느정도 내용을 볼 수 있어서 브라우저 디버거 만으로도 간단한 분석 정도는 가능합니다.

헤더만 대충 보면.. 이렇습니다(이게 뭔 의미여.. 안봐도되요)

> hexdump -C fail.wasm
00000000  00 61 73 6d 01 00 00 00  01 85 80 80 80 00 01 60  |.asm...........`|
00000010  00 01 7f 03 82 80 80 80  00 01 00 06 81 80 80 80  |................|
00000020  00 00 07 8b 80 80 80 00  01 07 66 61 69 6c 5f 6d  |..........fail_m|
00000030  65 00 00 0a 8d 80 80 80  00 01 87 80 80 80 00 00  |e...............|
00000040  41 01 41 00 6d 0b                                 |A.A.m.|
00000046

Second - Decompile wasm & find info
디컴파일이나 해줍시다. 내용을 보고 분석하는 것과 못보고 분석하는건 어마어마한 차이가 있죠(swf, activex 모두 동일.. 기능이나 함수를 최대한 많이 알아야 웹단에서 분석이 가능하죵)
git 좀 뒤져보면 디컴파일러 여러개 나오는데, 전 이게 가장 괜찮았던 것 같습니다. 공식에서 제공하는 웹 어셈블리 관련 바이너리 툴이고 디컴파일러 등도 내장하고 있어 편하게 쓸 수 있습니다(와 근데 이런면에선 진짜 SWF랑 동일하네요...)

#> git clone --recursive https://github.com/WebAssembly/wabt
#> apt install clang              // (clang이 없는 경우) 
#> make

(third party project가 좀 있습니다) 받고 빌드하면(물론 여기선 clang이 필요하고 설치해주심 됩니다, 왜 필요한지 궁금하면 웹 어셈블리 빌드 순서 보시면 됩니다)

#> cd gin
#> ls
CMakeCache.txt        dummy.c            wasm-interp        wasm2wat
CMakeFiles        hexfloat_test        wasm-objdump        wast2json
Makefile        liblibgtest.a        wasm-opcodecnt        wat-desugar
cmake_install.cmake    libwabt.a        wasm-strip        wat2wasm
config.h        spectest-interp        wasm-validate
dummy            wabt-unittests        wasm2c


1. obj-dump
여 타 objdump와 동일합니다. wasm 파일 구조를 살펴볼 수 있습니다.

> ./wasm-objdump -xd fail.wasm

fail.wasm:    file format wasm 0x1

Section Details:

Type[1]:
- type[0] () -> i32
Function[1]:
- func[0] sig=0 <fail_me>
Global[0]:
Export[1]:
- func[0] <fail_me> -> "fail_me"

Code Disassembly:

00003a <fail_me>:
000040: 41 01                      | i32.const 1
000042: 41 00                      | i32.const 0
000044: 6d                         | i32.div_s
000045: 0b                         | end

2. decompile
wasm2asm, wasm2c 등 wasm을 c코드나 어셈, json 등으로 다시 복원할 수 있습니다.

> ./wasm2c fail.wasm
#ifndef WASM_H_GENERATED_
#define WASM_H_GENERATED_
#ifdef __cplusplus
extern "C" {
#endif

#include <stdint.h>

#include "wasm-rt.h"

#ifndef WASM_RT_MODULE_PREFIX
#define WASM_RT_MODULE_PREFIX
#endif

#define WASM_RT_PASTE_(x, y) x ## y
#define WASM_RT_PASTE(x, y) WASM_RT_PASTE_(x, y)
#define WASM_RT_ADD_PREFIX(x) WASM_RT_PASTE(WASM_RT_MODULE_PREFIX, x)

/* TODO(binji): only use stdint.h types in header */
typedef uint8_t u8;
typedef int8_t s8;
typedef uint16_t u16;


C 코드로 복원이 가능하기 떄문에 코드단에서 체크해볼 수 있는게 많아지죠. 여기까지 보면 우리는 wasm에서 사용할 수 있는 function들과 어떤 기능을 하는지 알 수 있습니다. 이는 맨위에서 이야기드렸듯이 javascript와 wasm간 서로 function 호출이 가능하기 때문에 양쪽으로 테스틑 해볼 수 있습니다.

Third - Direct function call, xss, rce, etc... security testing!
이제부턴 진짜 SWF나 ActiveX랑 동일합니다. 우리가 wasm으로 들어가는 입력 구간을 Javascript에서 제어할 수 있기 때문에 웹 어셈블리의 instance를 받은 객체부터 하위 function을 실행해가며 체크하시면됩니다.



역으로 wasm에서 javascript로도 데이터를 줄 수 있기 때문에 SOP를 우회할 수 있는 CORS 적용 범위 여부, 동일 도메인에 파일 업로드가 가능한지?(wasm 올라가면 공격자가 재 구성한 웹 어셈 파일을 로드하게 되고, 사용자는 의도하지 않은 행위를 수행할 수 있으니깐?) 등 여러가지가 있을듯합니다.
보편적인 웹 공격, 바이너리 공격 방식에 여러분들의 Offensive한 생각들을 더하면 재미있는 취약점들을 찾아낼 수 있을거라 생각합니다.
(개인적인 생각으로 공격자의 마음으로 검수하면 희안한게 생각난다고...)

Conclusion

요즘들어 글쓰기가 뭔가 힘드네요(핑계), 요즘 자꾸 간단하고 메모성 글만 올려서 마음이 좀 그랬습니다..

원래 웹 어셈블리의 보안에 대한 이야기를 크게 쓸까 하다가.. 생각보다 고민해봐야할 여지가 많아서 분석 방법에 대한 글만 먼저 쓰게 되었습니다. (보안 관련은 그냥 블로그에서 다루긴 좀 버겁네요.. / 시간이 없음, 그냥 각자 회사에서 고민해보는걸로 ..ㅋㅋㅋㅋ)

아 물론 이런 방식이 분석 방법이다는 아닙니다(그냥 생각난대로 써본거고, 관련 분석을 계속 해봐야 프레임이 잡히지 않을까 싶네요). 여러가지 방법들이 있을테니 공유주시면 정말 감사하겠습니다.

블로그 그냥 취미로 하는거라 작은 시간들 모아가며 글을 씁니다. 이상하거나 잘못된 부분이 많을 수 있으니 양해 부탁드리며 댓글로 폭풍 지적질 부탁드려요..!!

조만간 테스트용 웹 어셈 하나랑 벡터들 조금 정리해서 글 올려보도록 하겠습니다.

Reference

https://webassembly.org/docs/security/
https://i.blackhat.com/us-18/Thu-August-9/us-18-Lukasiewicz-WebAssembly-A-New-World-of-Native_Exploits-On-The-Web-wp.pdf
https://developer.mozilla.org/ko/docs/WebAssembly

댓글 1개:

  1. 저도 최근 웹 어셈블리에 대해 관심이 생겨서 영어 자료만 스크랩하고 있었는데 이렇게 친절하게 포스팅 해주시니 너무 감사합니다.

    하울님 열정에 저도 동기부여가 되네요 ^^

    답글삭제