Skip to content

Commit

Permalink
refactor: add ui of cli
Browse files Browse the repository at this point in the history
  • Loading branch information
imyelo committed May 24, 2019
1 parent 1a95c4d commit 861eaab
Show file tree
Hide file tree
Showing 7 changed files with 895 additions and 166 deletions.
97 changes: 59 additions & 38 deletions bin/padoracle.js
Original file line number Diff line number Diff line change
@@ -1,40 +1,61 @@
#!/usr/bin/env node

const log = require('log-update')
const { observe } = require('mobx')
const Cracker = require('..')
const EVENTS = Cracker.EVENTS

let cracker = new Cracker()

observe(cracker.state, 'intermediary', ({ newValue: intermediary }) => {
console.log('Intermediary value:', intermediary.toString('hex'))
})
observe(cracker.state, 'plain', ({ newValue: plain }) => {
console.log('Plain text:', plain.toString())
console.log('Plain text (hex):', plain.toString('hex'))
})

cracker.broadcast.addListener(EVENTS.CRACK_START, () => {
console.log('--- Crack start ---')
})
cracker.broadcast.addListener(EVENTS.CRACK_END, () => {
console.log('--- Crack end ---')
})
cracker.broadcast.addListener(EVENTS.ORIGINAL_KEY_FOUND, ({ block, padding, hex }) => {
log(`Current block: ${block}, padding: ${padding}\nkey found: (backup)`, `0x${hex}`)
})
cracker.broadcast.addListener(EVENTS.REPLACEMENT_KEY_FOUND, ({ block, padding, hex }) => {
log(`Current block: ${block}, padding: ${padding}\nkey found:`, `0x${hex}`)
})
cracker.broadcast.addListener(EVENTS.INVALID_KEY_FOUND, ({ block, padding, hex }) => {
log(`Current block: ${block}, padding: ${padding}\ninvalid:`, `0x${hex}`)
})
cracker.broadcast.addListener(EVENTS.TRAVERSING_KEY_END, () => {
log.done()
})

if (module.parent) {
module.exports = async (...args) => await cracker.crack(...args)
return
}
const { resolve } = require('path')
const React = require('react')
const { render } = require('ink')
const meow = require('meow')
const esm = require('esm')
const jsx = require('import-jsx')

const ui = jsx('./ui')

;(() => {
const cli = meow(`
Usage
$ padoracle <challenge-script> --iv-cipher <iv-cipher> --size 16
Options
challenge-script A script which sends the decryption challenge to the target system.
--iv-cipher An iv-cipher pair which can pass the padding check (with base64 encoded).
--size Size of each block (in bytes).
Examples
$ padoracle ./exmaples/crackme-challenge.js --iv-cipher UGFkT3JhY2xlOml2L2NiYyiFmLTj7lhu4mAJHakEqcIIoYU0lIUXKx+PmTaUHLV0 --size 16
`, {
flags: {
ivCipher: {
type: 'string',
},
size: {
type: 'string',
},
},
})

if (!cli.input.length) {
return cli.showHelp()
}

const script = esm(module)(resolve(process.cwd(), cli.input[0]))

if (!script || !script.default) {
throw new Error('Invalid challenge script.')
}

if (!cli.flags.ivCipher) {
throw new Error('<iv-cipher> is required.')
}

const token = Buffer.from(cli.flags.ivCipher, 'base64')
const size = +cli.flags.size

if (!size) {
throw new Error('<size> is required.')
}

const iv = token.slice(0, size)
const cipher = token.slice(size)

render(React.createElement(ui, { challenge: script.default, iv, cipher }))

})()
147 changes: 147 additions & 0 deletions bin/ui.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
const React = require('react')
const { observe } = require('mobx')
const { Box, Text, Static } = require('ink')
const pad = require('left-pad')
const pkcs7 = require('pkcs7')
const Cracker = require('..')
const EVENTS = Cracker.EVENTS

const KEY_STATUS = {
ORIGINAL: 'ORIGINAL',
REPlACEMENT: 'REPLACEMENT',
INVALID: 'INVALID',
}

function App ({ challenge, iv, cipher }) {
const [key, setKey] = React.useState({
block: -1,
padding: -1,
hex: '??',
sample: '',
status: KEY_STATUS.INVALID,
})
const [intermediary, setIntermediary] = React.useState([])
const [plain, setPlain] = React.useState()

React.useEffect(() => {
let cracker = new Cracker()
observe(cracker.state, 'intermediary', ({ newValue }) =>
setIntermediary(newValue)
)
observe(cracker.state, 'plain', ({ newValue }) =>
setPlain(newValue)
)

cracker.broadcast.addListener(
EVENTS.ORIGINAL_KEY_FOUND,
({ block, padding, hex, sample }) => setKey({
block,
padding,
hex,
sample,
status: KEY_STATUS.ORIGINAL
})
)
cracker.broadcast.addListener(
EVENTS.REPLACEMENT_KEY_FOUND,
({ block, padding, hex, sample }) => setKey({
block,
padding,
hex,
sample,
status: KEY_STATUS.REPlACEMENT
})
)
cracker.broadcast.addListener(
EVENTS.INVALID_KEY_FOUND,
({ block, padding, hex, sample }) =>setKey({
block,
padding,
hex,
sample,
status: KEY_STATUS.INVALID
})
)

cracker.crack(iv, cipher, challenge)
}, [])

const status = {
ORIGINAL: 'key found: (backup)',
REPLACEMENT: 'key found:',
INVALID: 'invalid:'
}[key.status]

const format = (buf) => {
const DIVIDER = '|'
return Array.from(buf)
.map((v) => v > 0
? pad(v.toString(16), 2, '0')
: '??'
)
.join(DIVIDER)
}

const slice = ((size) => (buf, block) =>
buf.slice(block * size, (block + 1) * size)
)(iv.length)

const tamperedPlain = ((size, padding) => {
let buf = Buffer.alloc(size)
return buf.map((v, i) => {
if (size - i > padding - 1) {
return v
}
return padding
})
})(iv.length, key.padding)

return (
<Box flexDirection="column">
<Box>
<Text>----- Block {key.block} -----</Text>
</Box>
<Box>
<Text>Cipher ({key.block}) : </Text>
<Text>{format(slice(cipher, key.block))}</Text>
</Box>
<Box>
<Text>Intermediary ({key.block}) : </Text>
<Text>{format(slice(intermediary, key.block))}</Text>
</Box>
<Box>
{
key.block > 0
? <Text>Cipher ({key.block - 1}) (Tampered) : </Text>
: <Text>Initial Vector (Tampered) : </Text>
}
<Text>{format(slice(key.sample, key.block))}</Text>
</Box>
<Box>
<Text>Plain ({key.block}) (Tampered) : </Text>
<Text>{format(tamperedPlain)}</Text>
</Box>
{
plain
? <div>
<Box>
Plain text: {pkcs7.unpad(plain).toString()}
</Box>
<Box>
Plain text (hex): {plain.toString('hex')}
</Box>
</div>
: <div>
<Box>
padding: {key.padding}
</Box>
<Box>
{status} {key.hex}
</Box>
</div>
}
</Box>
)
}

module.exports = App
12 changes: 12 additions & 0 deletions examples/crackme-challenge.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
const crackme = require('./crackme')

const challenge = async (iv, cipher) => {
try {
await crackme.api.auth(Buffer.concat([iv, cipher]).toString('base64'))
} catch (error) {
return false
}
return true
}

export default challenge
46 changes: 46 additions & 0 deletions examples/crackme/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
const { Crypto } = require('../../test/helpers/crypto')
const delay = require('delay')

const SPEED = 1

const crackme = (() => {
const KEY = Buffer.from('abcdefghijklmnopqrstuvwxyz123456')
const DEFAULT_IV = Buffer.from('PadOracle:iv/cbc')
const BLOCK_SIZE = DEFAULT_IV.length

const DEFAULT_SESSION = '{"id":100,"roleAdmin":false}'

const crypto = new Crypto(KEY)

function createToken (session = DEFAULT_SESSION, iv = DEFAULT_IV) {
const cipher = crypto.encrypt(iv, session)
return `${Buffer.concat([iv, cipher]).toString('base64')}`
}

function getSession (token) {
let buf = Buffer.from(token, 'base64')
let iv = buf.slice(0, BLOCK_SIZE)
let cipher = buf.slice(BLOCK_SIZE)
return crypto.decrypt(iv, cipher).toString('utf8')
}

return {
api: {
welcome () {
return createToken()
},
async auth (token) {
if (SPEED) {
await delay(SPEED)
}
return getSession(token)
},
BLOCK_SIZE,
},
verifyPlainText (session) {
return session === DEFAULT_SESSION
},
}
})()

module.exports = crackme
9 changes: 5 additions & 4 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,11 @@ class Cracker {
for (let block = 0; block * size < cipher.length; block++) {

for (let padding = 1; padding <= size; padding++) {
const broadcast = (name, hex) => this.broadcast.emit(name, {
const broadcast = (name, hex, sample) => this.broadcast.emit(name, {
block,
padding,
hex,
sample,
})

let input = Buffer.concat([iv, cipher.slice(0, size * (block + 1))])
Expand All @@ -61,16 +62,16 @@ class Cracker {
let hex = ALL_HEX[i]
let sample = replace(input, size * (block + 1) - padding, Buffer.from(hex, 'hex'))
if (sample.equals(original)) {
broadcast(EVENTS.ORIGINAL_KEY_FOUND, hex)
broadcast(EVENTS.ORIGINAL_KEY_FOUND, hex, sample)
found = hex
continue
}
if (await challenge(sample.slice(0, size), sample.slice(size))) {
broadcast(EVENTS.REPLACEMENT_KEY_FOUND, hex)
broadcast(EVENTS.REPLACEMENT_KEY_FOUND, hex, sample)
found = hex
break
}
broadcast(EVENTS.INVALID_KEY_FOUND, hex)
broadcast(EVENTS.INVALID_KEY_FOUND, hex, sample)
}
broadcast(EVENTS.TRAVERSING_KEY_END)

Expand Down
Loading

0 comments on commit 861eaab

Please sign in to comment.