Removing PAUP's expiration date and version check

  1. Bottom line
  2. Introduction
  3. PAUP phones home
  4. PAUP expires
  5. Conclusion

Bottom line

PAUP* phones home for a version check every time it is started. This function is undocumented and the software itself does not alert the user that this occurs.

PAUP* also has an expiration date, set to July 2018 at the time of this writing, after which the program cannot be used.

I would prefer to remove these features.

Copy and paste this into Terminal:

curl -L http://phylosolutions.com/paup-test/paup4a161_osx.gz | gzcat | perl -pe 's/p(hylosolutions.com)/_$1/g; s/\x{81}(\x{fa}\x{e2}\x{07})/\x{c3}$1/' > paup4a161_fixed
chmod a+x paup4a161_fixed
./paup4a161_fixed

This downloads PAUP for macOS and gently applies the Stick of Correction.

This won’t necessarily work on Linux so you’ll have to follow the detailed steps below to apply the specific correction. If you figure it out for Linux please tweet or email me and I’ll update this article.

Introduction

PAUP* is a great piece of software that has some misfeatures that I’d like to correct. Its silent version checks and the time-bomb function that make the software stop working after a certain date are pretty user-hostile. The closed-source nature of PAUP also means that we can’t just go into the source and delete the offending code.

But given enough know-how we can still modify this closed-source binary executable. The rest of this blog post will detail the general way I approached the problem and how I patched PAUP to get around these mandatory date and version checks.

PAUP phones home

Little Snitch alerts us to PAUP's version check

I recently bought and installed Little Snitch, which alerts you when software is connecting to the Internet, and lets you allow or deny the connection attempt. I started up PAUP* and it surprisingly asked to connect to the Internet. I forbade the connection and started to dig deeper to find out what was going on.

Note: Attempt to get current version info from server was not successful (no network connection available, or connection failed)

The Little Snitch dialog suggested a connection attempt to phylosolutions.com, which is where the download for PAUP is hosted. Let’s see if we can find that website encoded in the binary using strings, which takes a binary (or any other file) and tries to extract human-readable text out of it.

strings paup4a161_osx | grep phylosolutions

The output of which is:

http://phylosolutions.com/paup-distribution/current_dev_version
## (snipped)

That first output line is interesting, so let’s visit that site with curl:

$ curl http://phylosolutions.com/paup-distribution/current_dev_version

160	4.0a (build 160)

Looks like it’s returning the latest build number and a human readable version string, delimited by tabs.

This suggests that we can simply change the requested URL to an invalid one, so that this network connection is never made.

perl -pi -e 's/p(hylosolutions.com)/_$1/g' paup4a161_osx

This tells Perl to -execute a line of code, -printing the result of editing the file paup4a161_osx -in place, using a regular expression to change the first character of phylosolutions.com to an underscore.

Note that the replacement string must have the same number of characters as the input string since we’re patching an executable file. (Try excluding the underscore to see what happens).

We can confirm this worked since running the updated executable results in a new error message and no connection attempt:

./paup4a161_osx

Note: Attempt to get current version info from server was not successful (network connection failed with error code = 6)

PAUP expires

I was intrigued to see that PAUP announces an expiration date when you start the program:

This is an alpha-test version that is still changing rapidly.

It will expire on 1 Jul 2018.

When we ran strings above we also saw an expiration message:

This version of PAUP has expired. Visit http://paup.phylosolutions.com to obtain a newer version.

How does software “expire”? Let’s test this out by artificially setting our system time to August 1, 2018:

sudo date 0801000018

PAUP* now announces that it is expired and unceremoniously exits! We can’t run PAUP in August, but this can be fixed. First we should restore our original system time:

sudo ntpdate -u time.apple.com

We could take the same approach as before, looking for strings that correspond to dates. But it seems unlikely that the date would be coded as a string, and I’m not sure what the exact date would be (July 1? June 30? etc.) so I’m going to take a different approach and look for debugging symbols that might correspond to what we’re looking for. Luckily the PAUP binary hasn’t had these stripped out:

nm paup4a161_osx | grep -i expir

Output:

0000000100027cb6 T _checkIfExpired
00000001003ce998 b _gVersionHasExpired
0000000100027c44 T _isExpiredOrUnlicensed
0000000100027c3c T _isVersionExpired
0000000100027c92 T _showExpiredMessage
0000000100027c4c t _testExpiration

All very promising. The last function, testExpiration seems particularly meaty. I fire up LLDB, a debugger, and ask it to disassemble that function for me:

 $ lldb paup4a161_osx 
(lldb) target create "paup4a161_osx"
Current executable set to 'paup4a161_osx' (x86_64).
(lldb) di -n testExpiration
paup4a161_osx`testExpiration:
paup4a161_osx[0x100027c4c] <+0>:  xorl   %eax, %eax
paup4a161_osx[0x100027c4e] <+2>:  cmpl   $0x7e2, %edx              ; imm = 0x7E2 
paup4a161_osx[0x100027c54] <+8>:  jl     0x100027c86               ; <+58>
paup4a161_osx[0x100027c56] <+10>: jg     0x100027c86               ; <+58>
paup4a161_osx[0x100027c58] <+12>: je     0x100027c62               ; <+22>
paup4a161_osx[0x100027c5a] <+14>: movb   $0x0, 0x3a6d37(%rip)      ; gExportMissingSymbol + 7
paup4a161_osx[0x100027c61] <+21>: retq   

## (rest of the disassembly snipped)

This is the translated machine code that our computer is actually running when PAUP calls this function. The important line is cmpl $0x7e2, %edx followed by the jl jg je commands. This basically says “compare what’s in edx to the constant value 0x7e2, then jump to one memory location or another if they’re equal (je) or not (jl and jg).

I know I’m on the right track now since hexadecimal 0x7e2 is equal to decimal 2018 — the current year.

I’m still a novice at assembly but my interpretation is that PAUP only runs if the current date is between March 1, 2018 and July 1, 2018.

xorl   %eax, %eax                ;
cmpl   $0x7e2, %edx              ; 0x7e2 = 2018
jl     0x100027c86               ; if edx != 2018: return true
jg     0x100027c86               ;
je     0x100027c62               ; edx == 2018
movb   $0x0, 0x3a6d37(%rip)      ;
retq                             ;
cmpl   $0x3, %esi                ; 0x3 = 3
jl     0x100027c7b               ; if esi < 3: return true
jne    0x100027c6f               ;
testl  %edi, %edi                ;
jle    0x100027c7b               ;
jmp    0x100027c5a               ;
cmpl   $0x7, %esi                ; 0x7 = 7
jg     0x100027c7b               ; if esi > 7: return true
jne    0x100027c5a               ;
cmpl   $0x1, %edi                ; 0x1 = 1
jle    0x100027c5a               ; probably tests for day, hour, etc. in a loop
pushq  $0x1                      ;
popq   %rax                      ;
movb   $0x1, 0x3a6d13(%rip)      ; return true?
retq                             ;
pushq  $0x1                      ;
popq   %rax                      ;
movb   $0x1, 0x3a6d08(%rip)      ; return true?
retq
nop

Returning to lldb we ask for the exact disassembled bytes of this function:

(lldb) dis -b -n testExpiration
paup4a161_osx`testExpiration:
paup4a161_osx[0x100027c4c] <+0>:  33 c0                 xorl   %eax, %eax
paup4a161_osx[0x100027c4e] <+2>:  81 fa e2 07 00 00     cmpl   $0x7e2, %edx
paup4a161_osx[0x100027c54] <+8>:  7c 30                 jl     0x100027c86
paup4a161_osx[0x100027c56] <+10>: 7f 2e                 jg     0x100027c86
paup4a161_osx[0x100027c58] <+12>: 74 08                 je     0x100027c62
paup4a161_osx[0x100027c61] <+21>: c3                    retq

## (snipped)

We want to patch out the byte sequence 81 fa e2 07 since that corresponds to the first critical cmpl instruction. (N.B.: 0x7e2 is stored as e2 07, in little-endian order). Let’s use xxd to ensure that this sequence is unique:

xxd -p paup4a161_osx | grep -o '81fae207'

This confirms that the hex sequence 81 fa e2 07 only occurs once in the file and is safe to patch out.

Normally xxd will split its output in nice human-readable format (try running xxd paup4a161_osx | less to see), but we turn that off with xxd -p since the hex sequence we’re looking for might get split across multiple lines. We then tell grep to -only print out the matching sequence, so we’re not deluged with the entire binary file in hex!

perl -pi -e 's/\x{81}(\x{fa}\x{e2}\x{07})/\x{c3}$1/' paup4a161_osx

This replaces the first byte 0x81 with 0xc3, which is the opcode for ret. Basically it makes the comparison function return as soon as it is called.

We can verify that this worked by setting the date into the future again and running PAUP. Success!

Conclusion

I hope this was a useful tutorial on how to confidently navigate closed-source binaries and modify them to suit your own needs. I also hope that this encourages phylogenetics software developers to release their code as open-source, so that others can more easily change the software without resorting to these hacks.

I understand that the developers probably have good reasons to do version checks, though users should be informed and agree to such checks. I think there is less of an argument for including time-bombs that disable the software. Ultimately it’s on the users to use the software appropriately and attempting to restrict user freedoms in this way is both unnecessary and futile.