# 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.

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());
```