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