Wireshark TLS Decryption

The first step in many desktop network troubleshooting situations is to open a tool such as Wireshark to view live or recorded network traffic. Wireshark is traditionally most useful for lower level protocols, or those that send data without encryption - in ‘plain text.’

Many higher-level protocols are moving towards more and more encryption, however. With the rise of zero-trust security models, many companies are moving towards encryption schemes such as TLS to secure connections towards their network resources. Inspecting and troubleshooting traffic used in modern infrastructures is becoming more difficult, due to the design and security model of user-facing applications.

Even traditional protocols such as DNS aren’t necessarily easily inspected. Browsers such as Mozilla Firefox are starting to roll out DoH, DNS over HTTPS, as their main method of resolving network addresses. With the assumption that many devices will use the DNS server assigned to them by their ISP or company, using DoH to a supported external service such as Google’s Public DNS or Cloudflare’s 1.1.1.1 DNS resolver can bring easy confidentiality and integrity benefits to users both inside and outside their organization.

While this is great for users, it can be a nightmare for network professionals trying to get down to the root cause of an issue. With encryption comes both privacy and opacity. Luckily, applications embedding Chromium or curl (most of them) can make it easy for someone to decrypt their local TLS-encrypted traffic.

There are a few main steps:

  1. Collecting decryption keys
  2. Collecting network traffic
  3. Viewing network traffic

Collecting decryption keys

Set an environment variable SSLKEYLOGFILE to the location of a writable file. This file can be empty, existing, or even open by multiple applications at the same time.

It’s that simple. For cooperating programs.

For a Windows-based example, this value could be set to %USERPROFILE%\.sslkey.log to put the keys in your user’s home folder. This could be set within the main environment variable editor, the same one commonly used to adjust the system path, or for a specific application session through a command line.

In Powershell, for example, to log a new Chrome session’s keys to the Desktop:

$env:SSLKEYLOGFILE=$env:USERPROFILE+"\Desktop\ssl_keys.log"
& 'C:\Program Files\Google\Chrome\Application\chrome.exe'

Windows

I found that at least the following programs write to this file when the environment variable is set (Tested on Windows 21H1 x64 Build 19044, versions below are the ones I tested with).

Vendor Application Tested Version
Microsoft Microsoft Edge v99.0.1150.39 x64
Google Google Chrome v99.0.4844.51 x64
Mozilla Firefox* v98.0.1 x64
Spotify Spotify for Windows 1.1.80.699.gc3dac750
Discord Discord Stable 118205 (081ceba) Host 1.0.9004 Windows 10 64-Bit (10.0.19044)
Valve Steam** Built: 2022-14-03 at 12:51:31, API: v020, Package versions: 1647446817
Cisco Jabber v12.9.3.54813 Build 304813
Microsoft Visual Studio Code 1.67.0-insider (6f26fa1) with Chromium 98.0.4758.141

* Firefox’s networking library also supports many more environment variables that can be used to tune it’s TLS systems, including being able to use a predicable PRNG seed.

** May only be the website portions, as it embeds Chromium?

There’s a theme here: Programs using Chromium or libcurl for network traffic can dump their TLS session information for later decryption.

Linux, macOS

Neither have been tested by the author. Any application embeddeding Chromium or libcurl should be capable of this.

Java

Many Java applications do not use Chromium or libcurl, and instead use the built-in Java Secure Socket Extension (JSSE) library. It has other ways to debug traffic.

Users have published open-source tools to have the JVM dump variables to a key log file, however the JVM must be started with an explicit hook, rather than simply reading an environment variable.

Disclaimers

  • Make sure this file is only readable by you, otherwise other users capturing packets on the system could decrypt your private network traffic! This could include plaintext passwords for banking, etc!

  • Similarly, make sure you are aware this file is being constantly written to, and could be retrievable at any point by any system administrators, possibly without your knowledge. Network traffic could be captured at any point along the path, not just at your host, and combined with these keys later.

  • Simply deleting files does not erase the data on many systems. It is notoriously hard to permanently delete data from a disk after it has been written for many reasons. Once both the keys and packet capture have been written to disk, the data should be considered available to any administrators of the machine, forever.

    • One way to mitigate this may be to use a RAM-based disk, after disabling the system’s swap/page file.

libcurl

libcurl is a common network library for managing HTTP/HTTPS connections. Since it is used so commonly, this TLS inspection technique works for many programs. Not all.

For libcurl applications, curl’s manpage has this to say about the variable:

If you set this environment variable to a file name, curl will store TLS secrets from its connections in that file when invoked to enable you to analyze the TLS traffic in real time using network analyzing tools such as Wireshark. This works with the following TLS backends: OpenSSL, libressl, BoringSSL, GnuTLS, NSS and wolfSSL.

That last sentence tells use that most builds of curl/libcurl you see on a desktop computer would be configured with SSLKEYLOGFILE support. Some Windows builds of curl is built using Windows’ built-in ‘schannel’ TLS library which does not support this feature.

Collecting traffic

Any tool that can output a standard packet capture should work for Wireshark. Specifically, this could be done headlessly using tcpdump on a server, whose traffic could later be inspected in a desktop environment.

The easiest way would be to startup Wireshark before or after you startup your applications. If Wireshark is pointed to the SSLKEYLOGFILE before capture, it may live-update with decrypted packet dissections, as other applications update the file.

Decryption can only occur when the whole TLS connection up to a specific packet is available. Notably, the TLS Handshake and all previous packets must be included in the packet catpure, up to the decryption target.

Viewing traffic

You can load in the SSLKEYLOGFILE within Wireshark’s TLS Preferences. Put the file path within the (Pre)-Master-Secret log filename field.

Wireshark > Preferences > Protocols > TLS

After setting that up and collecting some traffic, it will automatically decode it for you within the dissector.

Wireshark dissector showing decrypted HTTP/2 traffic to example.org

Additional References:

Additional Notes

libcurl security

Setting SSLKEYLOGFILE to point to a single file, system-wide, would be a terrible idea. Not everyone may be able to read/write it, and everyone with packet-capturing permissions (by default: everyone on the machine) could decode anyone else’s encrypted traffic - passwords and all.

The libcurl security page has this to say about running curl (or applications embedding libcurl) as root privileges with SSLKEYLOGFILE set:

Giving setuid powers to the application means that libcurl can save files using those new rights (if for example the SSLKEYLOGFILE environment variable is set). Also: if the application wants these powers to read or manage secrets that the user is otherwise not able to view (like credentials for a login etc), it should be noted that libcurl still might understand proxy environment variables that allow the user to redirect libcurl operations to use a proxy controlled by the user.

QUIC

libcurl also supports dumping encryption keys used for QUIC, a relatively newer protocol used for HTTP/3. This uses a separate environment variable as the protocol discards the traditional encrypted-TCP model, and instead uses UDP-based connections that multiplex different data streams across it. Daniel Stenberg’s blog has more information on logging this.

Read More

SNMP and MRTG in the Home

MRTG - The Multi Router Traffic Grapher is a network bandwidth monitoring tool that can be easily ran as a service off a lightweight machine, that emits static HTML files for a lightweight webserver.

You may have seen similar graphs before, here is an example:

An example of a rendered MRTG page, showing 5 and 30 minute averages of bandwidth usage

I initially explored MRTG while taking a Scripting for Network Management class at DePaul University. One of our assignments (in a lab environment) was to setup a cron job to poll three switches with MRTG (which uses SNMP internally), and update a set of exposed HTML files/images that show bandwidth usage over 5m/30m/2hr/1d periods.

This post serves as both an installation guide for my future-self, and an exploration of my thoughts as I set it up on my home network.

In the home

Having network metrics is useful for even small, home networks. It allows one to easily see how much of a purchased Internet package is being used. Many commercial routers will support the SNMP configuration necessary (not sure about ISP-provided ones), and my OpenWRT router just runs Linux - SNMP should be no problem for it.

MRTG and an nginx webserver will be running off of a Raspberry Pi 4 Model B, running Raspbian 10.

The router will be an Espressobin running OpenWRT.


Installation of MRTG

Here’s a brief shell script documenting my installation journey.

Much of this is distilled from the HTML version of the MRTG Unix Guide.

Some commands are explicitly commented out (using two hashes) so this can be copy/pasted as it’s own shell script.

#!/bin/bash

INSTALL_PATH="/usr/local/mrtg-2"

# Just in case it's not already installed... (We'll need GCC specifically)
sudo apt install build-essential

# Create a hosting folder for MRTG
mkdir ~/mrtg
cd ~/mrtg

# download the latest version
wget https://oss.oetiker.ch/mrtg/pub/mrtg.tar.gz

# extract it, and rename the folder so it doesn't have a version number
tar xvf mrtg.tar.gz
mv mrtg-* src

cd src

# Read the unix guide...
## less doc/mrtg-unix-guide.txt

# Try to configure it!
## ./configure --prefix="$INSTALL_PATH"

# Oh... I needs libgd... Install that!
# -> libgd is a library meant for easier dynamic image creation.
# -> Like those made by MRTG!
sudo apt install libgd-dev --no-install-recommends

# Actually configure it this time...
./configure --prefix="$INSTALL_PATH"

# Time to build it!
make

# Great, it's built - ready to install
sudo make install

# Just to double check...
ls "$INSTALL_PATH/bin"

Now a global install of MRTG itself is setup and ready to be used by any user of the system.

At this point, I started a brief dive into how MRTG’s binary was built - see the ‘Aside’ section at the bottom of this post.


OpenWRT Configuration for SNMP

While not the focus of this post, here are the basic steps I took:

  1. Enable SSHing into the router
  2. Update opkg, and install the snmpd package
  3. Edit the /etc/config/snmpd file for your own contact details & customizations
  4. Restart the SNMPD service: /etc/init.d/snmpd restart

MRTG Configuration

Ideally, this would be placed within it’s own user profile, with limited permissions. For simplicity, I’m going to run it off of my own user profile.

mkdir -p ~/mrtg/static # dir where our HTML assets will be placed
mkdir -p ~/mrtg/cfg # where we will keep MRTG files
cd ~/mrtg/cfg

# Add our installed MRTG to our path
PATH="$PATH:/usr/local/mrtg-2/bin"

# Setup MRTG with our directories
cfgmaker --global "WorkDir: $HOME/mrtg/static"  \
    --global "Options[_]: bits,growright" \
    --output "$HOME/mrtg/cfg/mrtg.cfg"    \
    "public@192.168.0.1"

# Initially run it - this may error the first few times.
LANG=C mrtg "$HOME/mrtg/cfg/mrtg.cfg"
LANG=C indexmaker --output="$HOME/mrtg/static/index_192.168.0.1.html" "$HOME/mrtg.cfg/mrtg.cfg"

# Setup a log file
sudo touch /var/log/mrtg.log
sudo chmod 600 /var/log/mrtg.log
sudo chown "$USER" /var/log/mrtg.log

# Create an index.html for nginx to point to. If multiple hosts are being watched, this could be something pointing to each host's index file.
cd ~/mrtg/static/
ln -s index_192.168.0.1.html index.html

If that seems to run successfully, add the following to your user crontab (crontab -e)

*/5 * * * * env LANG=C /usr/local/mrtg-2/bin/mrtg $HOME/mrtg/cfg/mrtg.cfg --logging /var/log/mrtg.log && env LANG=C /usr/local/mrtg-2/bin/indexmaker --output="$HOME/mrtg/static/index_192.168.0.1.html" "$HOME/mrtg.cfg/mrtg.cfg"

And that should be it! Here is a summary of what we have:

Path Description
$HOME/mrtg/static/ Static HTML and image files, suitable to be presented from a web server such as Nginx or Apache. These files should update around every 5 minutes or so.
$HOME/mrtg/cfg/ Configuration and historical data files for MRTG use, to keep stock of current and past bandwidth data and device configurations
/var/log/mrtg.log A log file we can use to ensure MRTG ran, and examine possible issues. (/var/log/syslog can also be useful here for investigating cron issues!)
/usr/local/mrtg-2 A global install folder for MRTG binaries, libraries, and other assets

Webserver Configuration

MRTG emits files with relative paths, so any server capable of serving static files should work. I’m personally using Nginx, over a subdomain only accessible on my local network. The webserver should have minimal caching, since the files should be updated every five minutes.


Aside - How is MRTG built?

A reverse-engineering Desire path

Especially when installing from source, like we did with MRTG just now, I always like to see what programs have been installed. Lets take a look at the binary folder for our installation directory:

# What kinds of files did MRTG install?
$ file /usr/local/mrtg-2/bin/*
/usr/local/mrtg-2/bin/cfgmaker:         Perl script text executable
/usr/local/mrtg-2/bin/indexmaker:       Perl script text executable
/usr/local/mrtg-2/bin/mrtg:             Perl script text executable
/usr/local/mrtg-2/bin/mrtg-traffic-sum: Perl script text executable
/usr/local/mrtg-2/bin/rateup:           ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-armhf.so.3, for GNU/Linux 3.2.0, BuildID[sha1]=53b1955674edf0030abefb6f563bc018660d22db, with debug_info, not stripped

Huh - there is actually only one binary application. Most of it is written in Perl!

One thing that may be interesting is the number of libraries the main rateup binary links against. In other words - what common libraries does MRTG use? Lets take a look:

$ ldd /usr/local/mrtg-2/bin/rateup
        linux-vdso.so.1 (0xbefb4000)
        /usr/lib/arm-linux-gnueabihf/libarmmem-${PLATFORM}.so => /usr/lib/arm-linux-gnueabihf/libarmmem-v7l.so (0xb6f60000)
        libgd.so.3 => /lib/arm-linux-gnueabihf/libgd.so.3 (0xb6ef9000)
        libpng16.so.16 => /lib/arm-linux-gnueabihf/libpng16.so.16 (0xb6ebd000)
        libz.so.1 => /lib/arm-linux-gnueabihf/libz.so.1 (0xb6e92000)
        libm.so.6 => /lib/arm-linux-gnueabihf/libm.so.6 (0xb6e10000)
        libc.so.6 => /lib/arm-linux-gnueabihf/libc.so.6 (0xb6cc2000)
        libfontconfig.so.1 => /lib/arm-linux-gnueabihf/libfontconfig.so.1 (0xb6c7a000)
        libfreetype.so.6 => /lib/arm-linux-gnueabihf/libfreetype.so.6 (0xb6bd7000)
        libjpeg.so.62 => /lib/arm-linux-gnueabihf/libjpeg.so.62 (0xb6b91000)
        libXpm.so.4 => /lib/arm-linux-gnueabihf/libXpm.so.4 (0xb6b72000)
        libtiff.so.5 => /lib/arm-linux-gnueabihf/libtiff.so.5 (0xb6af2000)
        libwebp.so.6 => /lib/arm-linux-gnueabihf/libwebp.so.6 (0xb6a8e000)
        /lib/ld-linux-armhf.so.3 (0xb6f75000)
        libexpat.so.1 => /lib/arm-linux-gnueabihf/libexpat.so.1 (0xb6a4d000)
        libuuid.so.1 => /lib/arm-linux-gnueabihf/libuuid.so.1 (0xb6a36000)
        libpthread.so.0 => /lib/arm-linux-gnueabihf/libpthread.so.0 (0xb6a0c000)
        libX11.so.6 => /lib/arm-linux-gnueabihf/libX11.so.6 (0xb68ea000)
        libzstd.so.1 => /lib/arm-linux-gnueabihf/libzstd.so.1 (0xb6851000)
        liblzma.so.5 => /lib/arm-linux-gnueabihf/liblzma.so.5 (0xb6820000)
        libjbig.so.0 => /lib/arm-linux-gnueabihf/libjbig.so.0 (0xb6803000)
        libgcc_s.so.1 => /lib/arm-linux-gnueabihf/libgcc_s.so.1 (0xb67d6000)
        libxcb.so.1 => /lib/arm-linux-gnueabihf/libxcb.so.1 (0xb67a7000)
        libdl.so.2 => /lib/arm-linux-gnueabihf/libdl.so.2 (0xb6794000)
        libXau.so.6 => /lib/arm-linux-gnueabihf/libXau.so.6 (0xb6781000)
        libXdmcp.so.6 => /lib/arm-linux-gnueabihf/libXdmcp.so.6 (0xb676c000)
        libbsd.so.0 => /lib/arm-linux-gnueabihf/libbsd.so.0 (0xb6744000)
        librt.so.1 => /lib/arm-linux-gnueabihf/librt.so.1 (0xb672d000)

That’s a lot of libraries! Especially notice that we’re linking to libgd - Don’t uninstall it!

Many of these are common Linux/libc/gcc libraries that many programs use: libc, linux-vdso, libgcc_s, librt. Many of them though, are plain image or graphics libraries: libpng16, libjpeg, libtiff, libX11, etc.

It’s hard to believe that MRTG uses that many libraries directly. What if we ask rateup, our main MRTG binary, what external functions it uses?

# Get the 'relocations', and filter out any common C standard library functions
$ objdump -R /usr/local/mrtg-2/bin/rateup  | grep -v GLIBC

/usr/local/mrtg-2/bin/rateup:     file format elf32-littlearm

DYNAMIC RELOCATION RECORDS
OFFSET   TYPE              VALUE
0002a0e4 R_ARM_GLOB_DAT    __gmon_start__
0002a120 R_ARM_COPY        gdFontSmall
0002a014 R_ARM_JUMP_SLOT   gdImageDestroy
0002a020 R_ARM_JUMP_SLOT   __gmon_start__
0002a04c R_ARM_JUMP_SLOT   gdImageStringUp
0002a05c R_ARM_JUMP_SLOT   gdImageLine
0002a068 R_ARM_JUMP_SLOT   gdImageInterlace
0002a06c R_ARM_JUMP_SLOT   gdImageColorTransparent
0002a080 R_ARM_JUMP_SLOT   gdImageSetStyle
0002a098 R_ARM_JUMP_SLOT   gdImageRectangle
0002a0b0 R_ARM_JUMP_SLOT   gdImageString
0002a0b4 R_ARM_JUMP_SLOT   gdImagePng
0002a0c0 R_ARM_JUMP_SLOT   gdImageCreate
0002a0dc R_ARM_JUMP_SLOT   gdImageColorAllocate
0002a0e0 R_ARM_JUMP_SLOT   gdImageSetBrush

The C convention, which carries over to these ‘symbols’ (functions, variables, etc, that a program needs to access at runtime), typically includes putting the library name in front of the function. Since there isn’t sort of namespacing or heiarchy, everything is just out in the open. Prefixing the library name helps to contain the chaos.

That explains all these functions starting with gd - they must be from libgd. What happened to all the other libraries we’re using?

Let’s take a look at libgd itself:

$ ldd /lib/arm-linux-gnueabihf/libgd.so.3
        linux-vdso.so.1 (0xbef51000)
        /usr/lib/arm-linux-gnueabihf/libarmmem-${PLATFORM}.so => /usr/lib/arm-linux-gnueabihf/libarmmem-v7l.so (0xb6ee5000)
        libm.so.6 => /lib/arm-linux-gnueabihf/libm.so.6 (0xb6e63000)
        libpng16.so.16 => /lib/arm-linux-gnueabihf/libpng16.so.16 (0xb6e27000)
        libz.so.1 => /lib/arm-linux-gnueabihf/libz.so.1 (0xb6dfc000)
        libfontconfig.so.1 => /lib/arm-linux-gnueabihf/libfontconfig.so.1 (0xb6db4000)
        libfreetype.so.6 => /lib/arm-linux-gnueabihf/libfreetype.so.6 (0xb6d11000)
        libjpeg.so.62 => /lib/arm-linux-gnueabihf/libjpeg.so.62 (0xb6ccb000)
        libXpm.so.4 => /lib/arm-linux-gnueabihf/libXpm.so.4 (0xb6cac000)
        libtiff.so.5 => /lib/arm-linux-gnueabihf/libtiff.so.5 (0xb6c2c000)
        libwebp.so.6 => /lib/arm-linux-gnueabihf/libwebp.so.6 (0xb6bc8000)
        libc.so.6 => /lib/arm-linux-gnueabihf/libc.so.6 (0xb6a7a000)
        /lib/ld-linux-armhf.so.3 (0xb6f61000)
        libexpat.so.1 => /lib/arm-linux-gnueabihf/libexpat.so.1 (0xb6a39000)
        libuuid.so.1 => /lib/arm-linux-gnueabihf/libuuid.so.1 (0xb6a22000)
        libpthread.so.0 => /lib/arm-linux-gnueabihf/libpthread.so.0 (0xb69f8000)
        libX11.so.6 => /lib/arm-linux-gnueabihf/libX11.so.6 (0xb68d6000)
        libzstd.so.1 => /lib/arm-linux-gnueabihf/libzstd.so.1 (0xb683d000)
        liblzma.so.5 => /lib/arm-linux-gnueabihf/liblzma.so.5 (0xb680c000)
        libjbig.so.0 => /lib/arm-linux-gnueabihf/libjbig.so.0 (0xb67ef000)
        libgcc_s.so.1 => /lib/arm-linux-gnueabihf/libgcc_s.so.1 (0xb67c2000)
        libxcb.so.1 => /lib/arm-linux-gnueabihf/libxcb.so.1 (0xb6793000)
        libdl.so.2 => /lib/arm-linux-gnueabihf/libdl.so.2 (0xb6780000)
        libXau.so.6 => /lib/arm-linux-gnueabihf/libXau.so.6 (0xb676d000)
        libXdmcp.so.6 => /lib/arm-linux-gnueabihf/libXdmcp.so.6 (0xb6758000)
        libbsd.so.0 => /lib/arm-linux-gnueabihf/libbsd.so.0 (0xb6730000)
        librt.so.1 => /lib/arm-linux-gnueabihf/librt.so.1 (0xb6719000)

Thats many of the same libraries - how are they being used this time?

$ objdump -R /lib/arm-linux-gnueabihf/libgd.so.3 | grep -vE 'GLIBC|ABS| gd'

/lib/arm-linux-gnueabihf/libgd.so.3:     file format elf32-littlearm

DYNAMIC RELOCATION RECORDS
OFFSET   TYPE              VALUE
00043fb4 R_ARM_GLOB_DAT    GD_COLOR_MAP_X11@@Base
00043fbc R_ARM_GLOB_DAT    _ITM_deregisterTMCloneTable
00043fe4 R_ARM_GLOB_DAT    __gmon_start__
00043ff8 R_ARM_GLOB_DAT    _ITM_registerTMCloneTable
00043ffc R_ARM_GLOB_DAT    jpeg_resync_to_restart@LIBJPEG_6.2
00043bb0 R_ARM_JUMP_SLOT   png_set_strip_16@PNG16_0
00043bb8 R_ARM_JUMP_SLOT   png_write_info@PNG16_0
00043bec R_ARM_JUMP_SLOT   compress
00043bf0 R_ARM_JUMP_SLOT   png_read_image@PNG16_0
00043c04 R_ARM_JUMP_SLOT   FT_Load_Glyph
00043c08 R_ARM_JUMP_SLOT   FcNameParse
00043c0c R_ARM_JUMP_SLOT   FT_Get_Kerning
00043c20 R_ARM_JUMP_SLOT   FT_New_Size
00043c24 R_ARM_JUMP_SLOT   FT_Done_Size
00043c3c R_ARM_JUMP_SLOT   png_set_compression_level@PNG16_0
00043c40 R_ARM_JUMP_SLOT   png_error@PNG16_0
00043c48 R_ARM_JUMP_SLOT   png_read_end@PNG16_0
00043c5c R_ARM_JUMP_SLOT   TIFFGetField@LIBTIFF_4.0
00043c60 R_ARM_JUMP_SLOT   XpmReadFileToXpmImage
00043c78 R_ARM_JUMP_SLOT   png_set_PLTE@PNG16_0
00043c7c R_ARM_JUMP_SLOT   png_get_valid@PNG16_0
00043c88 R_ARM_JUMP_SLOT   FcPatternDestroy
00043c8c R_ARM_JUMP_SLOT   uncompress
00043c94 R_ARM_JUMP_SLOT   png_write_image@PNG16_0
00043c9c R_ARM_JUMP_SLOT   FT_Done_Face
00043ca0 R_ARM_JUMP_SLOT   FT_Done_FreeType
00043cbc R_ARM_JUMP_SLOT   jpeg_start_decompress@LIBJPEG_6.2
00043ce4 R_ARM_JUMP_SLOT   FT_Activate_Size
00043cf8 R_ARM_JUMP_SLOT   FT_New_Face
00043cfc R_ARM_JUMP_SLOT   FT_Init_FreeType
00043d04 R_ARM_JUMP_SLOT   TIFFSetField@LIBTIFF_4.0
00043d0c R_ARM_JUMP_SLOT   FcConfigSubstitute
00043d18 R_ARM_JUMP_SLOT   png_get_rowbytes@PNG16_0
00043d24 R_ARM_JUMP_SLOT   FT_Set_Charmap
00043d2c R_ARM_JUMP_SLOT   jpeg_simple_progression@LIBJPEG_6.2
00043d34 R_ARM_JUMP_SLOT   jpeg_destroy@LIBJPEG_6.2

Ah there we go - now see see many functions starting with the library/image format names from earlier - png_, jpeg_, FT must be a part of libfreetype, etc.

So when compiling/linking the rateup binary, the compiler must have also pulled all of libgd’s dependencies with it! Neat.

Read More