Using strace(1) on Linux

by Mike Melanson (mike at multimedia.cx)
Updated June 29, 2004

Introduction to strace(1)

strace(1) is a tool on Linux that allows the user to trace every system call that a program makes. To use it:
strace <program> [args...]
To get more details and options, 'man 1 strace'. This utility should be available on a standard Linux installation. Reportedly, Solaris and FreeBSD have a similar tool named truss.

Applying strace(1)

Unfortunately, there is little opportunity to apply strace(1) since it wants to operate on platform-native code. Using it to run Wine code would likely yield a lot of system call overhead with minimal useful intelligence.

One good target, however, is RadGameTools' Linux port of their Bink video player. The Bink file format has traditionally been very opaque and very little has been gained from merely examining the layout of different Bink files (though some information has been discovered and compiled in this document).

RGT has released Win32 versions of their standalone Bink video player. However, they go to great lengths to make it difficult to reverse engineer. The main program is self-decompressing to thwart static disassembly. When run, the main program then imports all of the external library functions it requires through a few special Win32 DLL calls, further frustrating static disassembly efforts. The RGT Linux port team clearly knows precious little about similar counter-RE techniques under Linux. Their Linux Bink player is not even strip'd which means that it retains a lot of debugging symbols. And, naturally, no self-decompressing code.

Let's apply strace against the BinkPlayer binary playing a file named 'original.bik':

strace BinkPlayer original.bik > output.txt 2>&1
The command generates a lot of output to stderr which is why I redirect stderr to stdout, and stdout to a file. I search through the output for the Bink filename:
[...]
open("original.bik", O_RDONLY)          = 8
read(8, "BIKil\220%\0\30\1\0\0\250}\0\0\30\1\0\0\0\2\0\0\200\1\0"..., 44) = 44
[...]
The open() call indicates that original.bik was opened and assigned to file handle 8. Any subsequent calls to 'read(8, ...' will pull data out of the Bink file until a 'close(8)' system call is encountered. The second parameter of the read() call indicates the data that was returned in the memory array. The third parameter reveals how much data was requested while the function returns the amount of data actually returned. In this case, 44 bytes were requested and the data returned began with 'BIK'. This is the Bink file signature. According to the format information that has already been determined, a Bink file begins with a 44-byte header. Further, if bytes 40-43 of the header contain 0x1 (little-endian), there are 12 more bytes for audio parameters. Sure enough, BinkPlayer is acting on this information soon afterwards:
read(8, "\0\34\2\0", 4)                 = 4
read(8, "D\254\0\340", 4)               = 4
read(8, "\0\0\0\0", 4)                  = 4
Next, BinkPlayer reads what is apparently the frame offset table, followed by a number of bytes whose meaning is unclear. After this, the player keeps reading 16-kilobyte chunks from the file until the end. Admittedly, this is about as far as this experiment will go as I have already figured out how the player reads data from a Bink file. After reading the header information, the player must read 16384 bytes at a time and organize them in memory later. It is also possible, based on further RE experiments on the actual program, that there is a RadGameTools abstraction layer that has its own layer of file caching.

Return to the main page