# SECCON 2020 Online CTF - Capsule & Beginner's Capsule - Author's Writeup ## Author @hakatashi ## Challenge Summary The task is quite obvious from the appearence of the given website. ![](https://i.imgur.com/6WZBHgQ.png) You can arbitrarily change the code below the given header and the task is to get the value stored in ESNext's [Private Class Field](https://v8.dev/features/class-fields#private-class-fields). *Decapsulation.* ```js const fs = require('fs'); const {enableSeccompFilter} = require('./lib.js'); class Flag { #flag; constructor(flag) { this.#flag = flag; } } const flag = new Flag(fs.readFileSync('flag.txt').toString()); fs.unlinkSync('flag.txt'); enableSeccompFilter(); // input goes here ``` `enableSeccompFilter` function is just for preventing dumb solution to read out `/proc/self/mem` so you don't have to care about it. ### Beginner version Almost the same, but with TypeScript. ```typescript import * as fs from 'fs'; // @ts-ignore import {enableSeccompFilter} from './lib.js'; class Flag { #flag: string; constructor(flag: string) { this.#flag = flag; } } const flag = new Flag(fs.readFileSync('flag.txt').toString()); fs.unlinkSync('flag.txt'); enableSeccompFilter(); // input goes here ``` ## Beginner's Capsule The private field in TypeScript is [implemented with WeakMap](https://github.com/Microsoft/TypeScript/pull/30829). If you transpiled the given code to JavaScript, [the result](https://www.typescriptlang.org/play?#code/MYGwhgzhAEBi4HNoG8BQ1oGIBmiBc0EALgE4CWAdggNzrTAD2FxJArsEQyQBS5gIEWlBAEoUdDEQAWZCADociaAF5ofGnQC+qTUA) will be like the following. ```js var _flag; class Flag { constructor(flag) { _flag.set(this, void 0); __classPrivateFieldSet(this, _flag, flag); } } _flag = new WeakMap(); ``` `_flag` is easily accessible from the same scope, so you can get the flag by `console.log(eval('_flag.get(flag)'))`; ## Capsule: Intended Solution You can access [V8's inspector API](https://chromedevtools.github.io/devtools-protocol/) from [Node.js](https://nodejs.org/api/inspector.html) and get hidden property like this. Note that you must put `flag` variable to the global scope to inspect. ```js global.flag = flag; const inspector = require('inspector'); const session = new inspector.Session(); session.connect(); session.post('Runtime.evaluate', {expression: 'flag'}, (e, d) => { session.post('Runtime.getProperties', {objectId: d.result.objectId}, (e, d) => { console.log(d.privateProperties[0].value.value); }); }); ``` (I didn't know it, but this is mentioned [here](https://github.com/nodejs/node/issues/27404#issuecomment-569924796)) ## Capsule: Unintended Solution You can inject fake `require` function using [function hoisting](https://developer.mozilla.org/en-US/docs/Glossary/Hoisting) and read `flag.txt` before it is removed. ```js function require() { const fs = process.mainModule.require('fs'); console.log(fs.readFileSync('flag.txt').toString()); } ``` So basic... :man-facepalming: ## Capsule: Unintended Solution We expected Node.js cannot directly read memory without `/proc/self/mem`, but actually it is possible using [v8.getHeapSnapshot](https://nodejs.org/api/v8.html#v8_v8_getheapsnapshot). ```js const v8 = require('v8'); const memory = v8.getHeapSnapshot().read(); const index = memory.indexOf('SEC' + 'CON'); const len = memory.slice(index).indexOf('}'); const flagBuffer = memory.slice(index, index + len + 1); console.log(flagBuffer.toString()); ```