Another way to step up your Wireshark game is to enhance Lua dissectors with features implemented in C/C++. Lua has a wonderful C API that allows to seamlessly leverage C libraries within Lua code. I found this especially helpful when working with cryptography or compression, as Wireshark does not provide such capabilities in its Lua API. With this post I want to explain how to wrap C libraries for using them in Lua and how to use pre-packaged Lua libraries from LuaRocks.

The first step when extending Lua is figuring out the Lua version used by Wireshark. You can find it through the dialogue Help > About Wireshark, in the paragraph “Compiled with…”:

About Wireshark dialogue indication compilation with Lua 5.2.4 support

In my case this was Lua version 5.2.4, and thus I’ll be using that version in the following examples. Make sure to adapt the commands and documentation for your version of Lua.

Native modules come in the form of shared libraries (.so files on Linux, .dll files on Windows). However, you will not be able to just call arbitrary shared libraries as they need to follow a few rules mandated by the Lua C API. That’s why you will usually have to wrap existing libraries so that they can be used with Lua. In the first section of this post, I want to show you how you can compile and use such a wrapper yourself, then in the second part I will show how to install and use wrappers that other people published as packages in the LuaRocks repository. If that’s all you need, feel free to skip ahead.

Writing and Compiling a Wrapper Yourself Link to heading

In order to develop a C wrapper you will header files corresponding to your Lua version. On Debian-based Linux distributions like Ubuntu or Kali Linux, you can install the development package for Lua version 5.2 through the apt package manager:

$ sudo apt install liblua5.2-dev

This will install lua.h and lauxlib.h which provide the signatures of all lua_ and luaL_ functions.

C Code Link to heading

Sorry to break it to you, but you might need to write some C code. As I want to focus on the benefits of integrating C libraries in Wireshark dissectors, I will not go into details on the C API. Instead, I highly encourage you to take a look at the corresponding chapter in the Lua documentation, as it is very well written and provides some background on the API.

The following C file implements a library that exposes a single function: lexample:greet("<name>") which prints Hello <name>! to stdout:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <stdio.h>

#include <lua.h>
#include <lauxlib.h>

static int lexample_greet(lua_State *L) {
    const char *name = luaL_checkstring(L, 1);
    if (name == NULL)
        luaL_error(L, "name cannot be empty");

    printf("Hello %s!\n", name);
    return 0;
}

static const struct luaL_Reg lexample_functions[] = {
    {"greet", lexample_greet},
    {NULL, NULL},
};

int luaopen_lexample(lua_State *L) {
    luaL_newlib(L, lexample_functions);
    return 1;
}

You might have noticed that the C code contains only one exported (i.e. non-static) function called luaopen_lexample. This function must be called luaopen_, followed by the shared libraries name (here: lexample.soluaopen_lexample). It registers an array of function pointers using luaL_newlib. The (only) registered function lexample_greet fetches the name from the first element of the stack, makes sure it’s a string and prints the greeting.

If you save the code as lexample.c, you can compile this library with the following command1:

$ gcc $(pkg-config --cflags --libs lua-5.2) -fPIC -shared -o lexample.so lexample.c

Or explicitly in two steps (also useful for working with Make):

$ gcc -c $(pkg-config --cflags --libs lua-5.2) -fPIC -o lexample.o lexample.c
$ ld -fPIC -shared -o lexample.so lexample.o
Info
It is not necessary to link against any Lua library, as this is done dynamically.

You can test your first library through the lua command-line prompt (which you might need to install with sudo apt install lua5.2):

$ lua
Lua 5.2.4  Copyright (C) 1994-2015 Lua.org, PUC-Rio
> example = require("lexample")
> example.greet("Jonas")
Hello Jonas!

Lua Tables / Userdata Link to heading

Quite often, you will want to keep state in an object instance created in C code and used by other C functions. In the Lua C API this is handled through userdata. At its heart, userdata is a memory region allocated through lua_newuserdata that is associated with a Lua metatable. For details about metatables, I again refer you to the documentation, but in this case metatables serve two purposes:

  1. They allow attaching methods to userdata objects.
  2. They can be used as an “identity” to prevent arbitrary userdata objects from being passed to methods.

I will spare you the details on how to implement this correctly in C and just present to you a finished implementation:

Full Source Code: lxor.c
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

#include <lua.h>
#include <lauxlib.h>

// this is the "crypto" function that is being wrapped
void xor(const uint8_t *key, const size_t key_length, const uint8_t *in, const size_t in_length, uint8_t **out, size_t *out_length) {
    *out_length = in_length;
    uint8_t *buffer = (uint8_t *)malloc(*out_length);

    for (size_t i = 0; i < in_length; i++)
    {
        buffer[i] = in[i] ^ key[i % key_length];
    }

    *out = buffer;
};

// this is the userdata struct that holds the state (i.e. the key)
typedef struct {
    uint8_t *key;
    size_t key_length;
} cipher_userdata_t;

// lxor.new("key"), constructor for cipher
static int cipher_new(lua_State *L)
{
    cipher_userdata_t *u;
    const uint8_t *key;
    size_t key_length;

    key = (uint8_t *)luaL_checklstring(L, 1, &key_length);
    if (key == NULL)
        luaL_error(L, "key cannot be empty");

    u = (cipher_userdata_t *)lua_newuserdata(L, sizeof(*u));
    u->key_length = 0;
    u->key = NULL;

    luaL_getmetatable(L, "LXorCipher");
    lua_setmetatable(L, -2);

    // important: make _copies_ of parameter values, as pointers will be invalid outside this function
    u->key_length = key_length;
    u->key = (uint8_t *)malloc(key_length);
    memcpy(u->key, key, key_length);

    return 1;
}

// cipher:encrypt("...")
static int cipher_encrypt(lua_State *L)
{
    cipher_userdata_t *u;
    const uint8_t *plaintext;
    uint8_t *ciphertext;
    size_t plaintext_length, ciphertext_length;

    u = (cipher_userdata_t *)luaL_checkudata(L, 1, "LXorCipher");
    plaintext = (uint8_t *)luaL_checklstring(L, 2, &plaintext_length);
    if (plaintext == NULL || plaintext_length == 0)
        luaL_error(L, "plaintext cannot be empty");

    xor(u->key, u->key_length, plaintext, plaintext_length, &ciphertext, &ciphertext_length);
    lua_pushlstring(L, ciphertext, ciphertext_length);

    return 1;
}

// cipher:decrypt("...")
static int cipher_decrypt(lua_State *L)
{
    // This is a special shortcut because xor is symmetric
    return cipher_encrypt(L);
}

// cipher "destructor"
static int cipher_destroy(lua_State *L)
{
    cipher_userdata_t *u;

    u = (cipher_userdata_t *)luaL_checkudata(L, 1, "LXorCipher");

    if (u->key != NULL)
    {
        memset(u->key, 0, u->key_length);
        free(u->key);
        u->key = NULL;
        u->key_length = 0;
    }

    return 0;
}

// userdata methods
static const struct luaL_Reg cipher_methods[] = {
    {"encrypt", cipher_encrypt},
    {"decrypt", cipher_decrypt},
    {"__gc", cipher_destroy},
    {NULL, NULL},
};

// library functions
static const struct luaL_Reg lxor_functions[] = {
    {"new", cipher_new},
    {NULL, NULL},
};

int luaopen_lxor(lua_State *L)
{
    // Create new metatable
    luaL_newmetatable(L, "LXorCipher");
    lua_pushvalue(L, -1);
    lua_setfield(L, -2, "__index");
    luaL_setfuncs(L, cipher_methods, 0);

    // Register library
    luaL_newlib(L, lxor_functions);

    return 1;
}

The code implements a library called lxor which you can compile and use like this:

$ gcc $(pkg-config --cflags --libs lua-5.2) -fPIC -shared -g -o lxor.so lxor.c
$ lua
Lua 5.2.4  Copyright (C) 1994-2015 Lua.org, PUC-Rio
> xor = require("lxor")
> cipher = xor.new("0AB1BC")
> print(cipher:decrypt("x$.]-cg.0]&b"))
Hello World!

Lua Path & CPath Link to heading

If you try to use a library from a different working directory than where the shared library is located, you will get the following error message:

$ lua            
Lua 5.2.4  Copyright (C) 1994-2015 Lua.org, PUC-Rio
> example = require("lexample")
stdin:1: module 'lexample' not found:
        no field package.preload['lexample']
        no file '/usr/local/share/lua/5.2/lexample.lua'
        no file '/usr/local/share/lua/5.2/lexample/init.lua'
        no file '/usr/local/lib/lua/5.2/lexample.lua'
        no file '/usr/local/lib/lua/5.2/lexample/init.lua'
        no file '/usr/share/lua/5.2/lexample.lua'
        no file '/usr/share/lua/5.2/lexample/init.lua'
        no file './lexample.lua'
        no file '/usr/local/lib/lua/5.2/lexample.so'
        no file '/usr/lib/x86_64-linux-gnu/lua/5.2/lexample.so'
        no file '/usr/lib/lua/5.2/lexample.so'
        no file '/usr/local/lib/lua/5.2/loadall.so'
        no file './lexample.so'
stack traceback:
        [C]: in function 'require'
        stdin:1: in main chunk
        [C]: in ?

You might encounter a similar error message in Wireshark:

Wireshark error message: module ’lexample’ not found

Lua searches for Lua libraries (which can also be required) and shared libraries in specific directories:

  • Lua Path: Used for finding .lua files, can be specified through the environment variable LUA_PATH or during runtime by modifying package.path
  • Lua C Path: Used for finding shared libraries, can be specified via LUA_CPATH or package.cpath

I have yet to find the perfect project structure. Currently, I’m copying the compiled .so files into the local user plugin path (~/.local/lib/wireshark/plugins) and then modify package.cpath accordingly at the beginning of my script:

package.cpath = package.cpath .. ";" .. Dir.personal_plugins_path() .. "/?.so"

Alternatively, you can put this line into init.lua, where it will be executed on each startup.

Passing Values Between Lua And C Link to heading

While it is (with a few hacks) possible to access Tvb userdata objects in C, I do not recommend doing so, as the C API for getting raw data given a Tvb userdata object is internal and may thus be subject to change. The Lua API for getting raw data given a Tvb userdata object however is documented. You can convert a Tvb or a TvbRange to a Lua string with the :raw() method:

local input = buffer:raw()
local output = mylibrary.myfunction(input)

Lua strings are simple sequences of eight-bit characters (including \x00 bytes) and therefore perfectly suited for passing binary data. You can then process the string in C code like this:

static int mylibrary_myfunction(lua_State *L) {
    const uint8_t *input;
    size_t input_length;
    uint8_t* output;
    size_t output_length;

    input = (uint8_t *)luaL_checklstring(L, 1, &input_length);
    // ... calculate output and output_length, then:
    lua_pushlstring(L, output, output_length);
    return 1;
}

If you need to do further processing in Wireshark, you can use the output to create a new Tvb like this:

local bytearray = ByteArray.new(output, true)
local buffer2 = bytearray:Tvb("<some name>")

I have already used this concept in the context a previous blog post.

Integrating with the LuaRocks Package Manager Link to heading

It is not always necessary to reinvent the wheel wrapper, sometimes you might get away with using other people’s packages instead. For Lua, LuaRocks is the go-to package manager with over 4600 modules at the time of writing. On Debian, Ubuntu and Kali Linux you can install the LuaRocks CLI via the apt package manager (make sure to match your Lua version):

$ sudo apt install luarocks-5.2

LuaRocks packages, aptly named rocks, can be installed via one of the following commands:

$ luarocks install <package> # for global installation
$ luarocks install --local <package> # for local/non-root installation

The paths where luarocks places the files are not in Wireshark’s default C path (LUA_CPATH/package.cpath). This can be fixed by placing the following line on top of your script:

require("luarocks.loader")

This line makes sure that subsequent require calls pick up the installed rocks. If you are really determined about using LuaRocks, you can even place this line in init.lua, allowing you to require arbitrary installed rocks in all your dissector scripts.

Conclusion Link to heading

During protocol analysis you might encounter complicated algorithms like encryption, compression, or encoding. Lua by default does not provide any helpers, but Lua’s C API allows to easily integrate libraries written in C. This post showed how to write a wrapper and especially how to use it in Wireshark. Additionally, it explained how to use existing Lua wrappers published via the LuaRocks package manager.

I hope that this post will make a few peoples’ lifes easier!


  1. For larger projects, I found it handy to work with CMake, you can find an example CMakeLists.txt file here, which I adapted from this blog post↩︎