Thu, 07 Apr 2022 18:11:33 +0100
Initial commit
#!/usr/bin/env lua5.3 local ciphers = require "openssl.cipher"; local kdf = require "openssl.kdf"; local zlib = require "zlib"; local sqlite = require "lsqlite3"; local dbuffer = require "util.dbuffer"; local file_password = assert(arg[2], "no password specified"); local input_filename = assert(arg[1], "no ceb file specified"); local db_filename = input_filename:gsub("%.ceb$", "")..".sqlite3"; local function read_header(f) local function read_int() return (">i4"):unpack(f:read(4)); end local function read_short() return (">i2"):unpack(f:read(2)); end local function read_long() return (">i8"):unpack(f:read(8)); end local function read_string() local n = read_short(); return f:read(n); end return { version = read_int(); app_id = read_string(); jid = read_string(); timestamp = math.floor(read_long()/1000); iv = f:read(12); salt = f:read(16); }; end local f = io.open(input_filename); local header = read_header(f); print("version", header.version); print("app", header.app_id); print("jid", header.jid); print("timestamp", os.date("%c", header.timestamp)); local function generate_key(password, salt) return kdf.derive({ type = "PBKDF2"; md = "sha1"; pass = password; salt = salt; iter = 1024; outlen = 128/8; }); end print("k", #(generate_key(file_password, header.salt))); local decryption_key = generate_key(file_password, header.salt); local cipher = ciphers.new("AES-128-GCM"):decrypt(decryption_key, header.iv); local decompress = zlib.inflate(); local db = sqlite.open(db_filename); do local db_tables = { [[create table accounts (uuid text primary key, username text, server text, password text, display_name text, status number, status_message text, rosterversion text, options number, avatar text, keys text, hostname text, port number, resource text)]]; [[create table conversations (uuid text, accountUuid text, name text, contactUuid text, contactJid text, created number, status number, mode number, attributes text)]]; [[create table messages (uuid text, conversationUuid text, timeSent number, counterpart text, trueCounterpart text, body text, encryption number, status number, type number, relativeFilePath text, serverMsgId text, axolotl_fingerprint text, carbon number, edited number, read number, oob number, errorMsg text, readByMarkers text, markable number, remoteMsgId text, deleted number, bodyLanguage text)]]; [[create table prekeys (account text, id text, key text)]]; [[create table signed_prekeys (account text, id text, key text)]]; [[create table sessions (account text, name text, device_id text, key text)]]; [[create table identities (account text, name text, ownkey text, fingerprint text, certificate text, trust number, active number, last_activation number, key text)]]; }; for _, query in ipairs(db_tables) do db:exec(query); end end local buffer = dbuffer.new(1024*1024, 128); repeat local enc_data = f:read(4096); if not enc_data then break; end local gz_data = assert(cipher:update(enc_data)); local status, data, eof = pcall(decompress, gz_data); if not status then print("EE: Failed to decompress: "..tostring(data)); return 1; end buffer:write(data); local line = buffer:read_until("\n"); local query_buffer; while line do local balanced_quotes = select(2, line:gsub("'", "%0")) % 2 == 0; if query_buffer then table.insert(query_buffer, line); if not balanced_quotes then db:exec(table.concat(query_buffer, "\n")); query_buffer = nil; end else if balanced_quotes then db:exec(line); else query_buffer = { line }; end end line = buffer:read_until("\n"); end until eof print("Done");