Decompiling and deobfuscating a Zend Guard protected code base

Up untill PHP version 5.6 Zend Guard could be used to obfuscate / compile your PHP source code. It is possible to deobfuscate these code bases.

The easiest way to achieve deobfuscation is by using a PHP runtime that caches opcodes, translating these cache entries back to source code. The repository Zend-Decoder by Tools2 on Github hooks into the lighttpd xcache opcode cacher, and does exactly this.

For my own convenience, I have written a Dockerfile that sets up this workflow. Combined with some bash one liners it’s possible to deobfuscate an entire code base.

Steps to get this running are:

  1. Get the codebase with the Dockerfile: git clone
  2. Obtain a copy of and place it in the cloned repository
  3. Build the container docker build -t zenddecoder .
  4. Run the container, with the code base as a bind mount, and drop into a shell docker run -v /path/to/your/codebase:/src -it zenddecoder /bin/bash
  5. Now it’s possible to deobfuscate your entire code base with one-liners like this:
    for f in $(find /src/ -name '*.php'); do php index.php $f > ${f::-4}".dec.php"; done"

[Download] De Nederlandse familienamenbank

Hier staat een download van de Nederlandse familienamenbank. De dataset is gedownload door de webpagina’s voor alle achternamen te structureren. De familienamenbank verbergt het precieze aantal als dat onder de vijf ligt. Voor zulke gevallen is een poging gedaan het precieze aantal (of een interval) af te leiden, op basis van de kleur van gemeenten op de overzichtskaarten van Nederland. De code voor het downloaden en structureren van de familienamenbank staat in de Github-repository bartbroere/namenlijsten.

[Drone footage] Scheveningen, maar dan met trekkers

Adding a teleport to a SOF2 map: all the key-value pairs

The process of modding old games is mostly dead links. Some of these dead links are behind logins or paywalls. For an upcoming lanparty, we needed teleports in our map for Soldier of Fortune 2. After some googling and reading documentation, this post summarizes how we did it.

SOF2 runs the Quake 3 engine, meaning it can be modded using GTKradiant. Radiant’s file format is .map.

Here’s what we needed to add to our .map-file. Replace the two occurrences of 487 655 30, with the x y z coordinates of the target of your teleport. The other lines (after brush 0) are the six planes of the triggering area (the source of the teleport). It’s probably easier to move this area around using GTKradiant.

The three entities are numbered. The syntax may suggest that this is a comment, but I think it couldn’t hurt to make it a logical sequence with the rest of your file.

// entity 199
"classname" "target_teleporter"
"origin" "487 655 30"
"target" "1"
"targetname" "teleport"
// entity 200
"classname" "target_location"
"origin" "487 655 30"
"targetname" "1"
// entity 201
"classname" "trigger_multiple"
"target" "teleport"
// brush 0
( -728 504 56 ) ( -728 472 56 ) ( -728 472 48 ) tools/_trigger 0 0 0 0.125000 0.125000 0 7 0
( -672 504 56 ) ( -728 504 56 ) ( -728 504 48 ) tools/_trigger 0 0 0 0.125000 0.125000 0 7 0
( -672 472 56 ) ( -672 504 56 ) ( -672 504 48 ) tools/_trigger 0 0 0 0.125000 0.125000 0 7 0
( -728 472 56 ) ( -672 472 56 ) ( -672 472 48 ) tools/_trigger 0 0 0 0.125000 0.125000 0 7 0
( -728 472 56 ) ( -728 504 56 ) ( -672 504 56 ) tools/_trigger 0 0 0 0.125000 0.125000 0 7 0
( -672 504 48 ) ( -728 504 48 ) ( -728 472 48 ) tools/_trigger 0 0 0 0.125000 0.125000 0 7 0

Of course, it’s not necessary to edit the map in a text editor. You could add the same key-value pairs using the entities menu. This can be opened by pressing N in GTKradiant, while having an object selected in the interface.

For the source of the teleport, it’s important to use the _trigger texture. Give this the following key-value pairs:

"classname" "trigger_multiple"
"target" "teleport"

After compiling your map, with these lines added, you should have a working teleport.

Syncing a bokeh plot to a video

The Python library bokeh is great for plotting all kinds of data in the browser. Bokeh includes a serve command, which can host a document, having a per-user state in the associated Python code. When creating a visualisation, you can have callback functions in both Python and JavaScript code. I used this here to sync an HTML video-element to a bokeh line plot.

Accelerometer data synced to a dashcam video:

The HTML video element

<video src="bokeh-video-sync/static/20190912_041033_EF.mp4"

The JavaScript callback:

/* Select the video with the id frontcamera */
var v = document.getElementById("frontcamera");

/* If the time of the video element updates, run the callback */
v.addEventListener("timeupdate", function () {
    /* The selector below assumes there are no other input fields in your
       code */
    inputs = document.getElementsByTagName('input');
    for (index = 0; index < inputs.length; ++index) {
        /* Update the input field with the new time of the video */
        inputs[index].value = v.currentTime;
        /* Trigger a change of the input field, to call the Python code */
        var event = new Event('change', {bubbles: true});
}, true);

The Python code, with the update callback function:

import pandas

from bokeh.layouts import row
from bokeh.models import ColumnDataSource, TextInput
from bokeh.plotting import curdoc, figure

def update(_, old, new):
    """The callback function we want to invoke if the time in the video
    # Our data source is in milliseconds, but the callback receives seconds,
    # therefore we multiply the input by 1000
    new, old = float(new) * 1000, float(old) * 1000
    subset = df[(df['ms_since_start'] > old) & (df['ms_since_start'] < new)]{
        'ms': list(subset['ms_since_start']),
        'z': list(subset['z_int']),
        'x': list(subset['x_int']),
        'y': list(subset['y_int']),

# Read in the data using pandas
df = pandas.read_csv('./20190912_041033_EF.acc.csv')

# Create a new bokeh figure
p = figure(plot_width=1900, plot_height=400)

# Define the columns to plot later
data = ColumnDataSource({
    'ms': [],
    'z': [],
    'x': [],
    'y': [],

# Plot three lines, that listen to changes in the ColumnDataSource
p.line('ms', 'z', source=data, color='red')
p.line('ms', 'x', source=data, color='white')
p.line('ms', 'y', source=data, color='lightgreen')

# Add a TextInput() that we use to pass on the current time in the video
# This is hacky, and could potentially be done in a nicer way
current_time = TextInput()
# If the text field current_time changes, invoke the update callback function 
current_time.on_change('value', update)

# Add all elements to the bokeh document

Although I like this first try at syncing plots, there is still room for improvement. It would be nicer to have the video element call the Python function directly, instead of through an input field. This could be achieved by implementing the video element in bokeh.models. Currently, the line will not disappear when you rewind the video, or start it a second time. All these things can be achieved using the HTML5 media events. Maybe I’ll make a proper media player for bokeh one day…