C'mon, I Want Lolcats!


This EPUB 3 proof of concept demonstrates how one can obfuscate data inside an EPUB 3 container, and display it at runtime via JS. In this example, the base64 representation of an image is embedded into <p> identifiers, and a tiny JS is used to reassemble and display it at runtime.


You can download the EPUB 3 file from here.

This demo is released under the terms of the MIT License.


The goal is to obfuscate an image inside an EPUB 3 file so that it is difficult to detect it by just looking inside the EPUB 3 (ZIP) container. In particular, we do not want to include the image as a file (as one normally does, listing it in the OPF manifest), nor having it "plainly" coded in base64 inside an XHTML page (which would make it easily detectable). Note that the same technique can be applied to show text, links, images, audio, video, etc.

The idea is to break the base64 representation of the image into pieces, "hiding" them as element identifiers. A small JS will read these identifiers at runtime and it will manipulate the XHTML page DOM to display the obfuscated image.

The second chapter of the above eBook contains several dummy ("lorem ipsum") paragraphs, each of them looks like the following one:

<p><a class="pkey" id="__a9j_a4AAQSkZJRgABAgAAZABkAAD_a2wBDAAICAgICAgICAgIDAgICAwQDAgIDBAUEBAQEBAUGBQUF"/> Lorem ipsum dolor sit amet, ... </p>

Each paragraph id contains a piece of the base64 representation of an image. Unfortunately, just breaking this base64 representation into pieces is not enough, as they might contain \, +, and = (which are not valid characters for XHTML identifiers) or a digit [0-9] as the first character (which is not allowed in XHTML identifiers).

To make it a legal XHTML identifier, the first problem is solved by translating /, + and = into _a, _b, and _c, respectively (better camouflage might be achieved in several ways), while the second problem is solved by prepending an underscore (but it can be a randomly generated character in [A-Za-z0-9_] for better camouflage) which will always be discarded when reassembling the base64 representation.

The XHTML page contains the following code:

<?xml version="1.0" encoding="UTF-8"?>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:epub="http://www.idpf.org/2007/ops" lang="en" xml:lang="en">
  <meta http-equiv="default-style" content="text/html; charset=utf-8"/>
  <link rel="stylesheet" href="style.css" type="text/css"/>
  <script type="text/javascript" src="innocent.js"></script>
 <body onload="init_popups()">
  <section epub:type="bodymatter chapter">
   <h1>C'mon, I Want Lolcats!</h1>  
   <p> See above... </p>
   <p> See above... </p>

where init_popups() seems an innocent name, right? Well, try loading the eBook in a supported reading system (see below), and you will get a fluffy surprise!

Supported Reading Systems

This example has been tested and it runs "correctly" in Apple iBooks on iOS (EDIT 2014-02-06: it does not work in iBooks on Mac OS X), Readium, and Calibre. The image is also shown by Kobo iOS app, but it is appended at the very bottom of the page and only its upper half is displayed.

This example should work in any Webkit-based browser as well.