Removing PAUP's expiration date and version check
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.
Note: PAUP (as of version 4a166) now no longer expires. The version check still exists, though.
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
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 -e
xecute a line of code, -p
rinting the result of editing the file paup4a161_osx
-i
n 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 -o
nly 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.