A few months ago, I downloaded an evaluation copy of IBM’s XLC compiler to try it out on FFmpeg. The trial licence has now expired, so what better way to spend a few minutes than by cracking it?
The installation script, as expected, copied a number of files into a directory under /opt. More unusually, it also created a small shared library, libxlc101e.so.1, and placed it in /usr/lib. No other files from the installation package were modified, so this must be where the licence is hiding. Without further ado, we proceed to take it apart.
We begin by looking at the symbol table using readelf -s:
Symbol table '.symtab' contains 44 entries: Num: Value Size Type Bind Vis Ndx Name 0: 00000000 0 NOTYPE LOCAL DEFAULT UND 1: 000000b4 0 SECTION LOCAL DEFAULT 1 2: 0000017c 0 SECTION LOCAL DEFAULT 2 3: 0000036c 0 SECTION LOCAL DEFAULT 3 4: 0000055c 0 SECTION LOCAL DEFAULT 4 5: 00000580 0 SECTION LOCAL DEFAULT 5 6: 000005c4 0 SECTION LOCAL DEFAULT 6 7: 00010a3c 0 SECTION LOCAL DEFAULT 7 8: 00010a50 0 SECTION LOCAL DEFAULT 8 9: 00010ac8 0 SECTION LOCAL DEFAULT 9 10: 00010ad8 0 SECTION LOCAL DEFAULT 10 11: 00000000 0 SECTION LOCAL DEFAULT 11 12: 00000000 0 SECTION LOCAL DEFAULT 12 13: 00000000 0 SECTION LOCAL DEFAULT 13 14: 00000000 0 SECTION LOCAL DEFAULT 14 15: 00000000 0 FILE LOCAL DEFAULT ABS xleval.c 16: 00010a50 0 OBJECT LOCAL HIDDEN ABS _DYNAMIC 17: 00010acc 0 OBJECT LOCAL HIDDEN ABS _GLOBAL_OFFSET_TABLE_ 18: 000005f0 24 OBJECT GLOBAL DEFAULT 6 xlc_extended_eval_lic_dir 19: 00000660 22 OBJECT GLOBAL DEFAULT 6 libxlfextendeval_name 20: 00000738 42 OBJECT GLOBAL DEFAULT 6 stm_compiler_name 21: 00000640 16 OBJECT GLOBAL DEFAULT 6 libupclicense_name 22: 00000764 32 OBJECT GLOBAL DEFAULT 6 xlf_compiler_name 23: 00000580 68 FUNC GLOBAL DEFAULT 5 _xlgetevalbeta 24: 00000678 22 OBJECT GLOBAL DEFAULT 6 libxlcextendeval_name 25: 00000608 24 OBJECT GLOBAL DEFAULT 6 xlf_extended_eval_lic_dir 26: 000006d8 17 OBJECT GLOBAL DEFAULT 6 xlcmp_name 27: 000006c0 12 OBJECT GLOBAL DEFAULT 6 xlc_package_name 28: 00000650 16 OBJECT GLOBAL DEFAULT 6 libstmlicense_name 29: 000005e0 16 OBJECT GLOBAL DEFAULT 6 xlf_extend_eval_env_var 30: 000005d0 16 OBJECT GLOBAL DEFAULT 6 xlc_extend_eval_env_var 31: 00000620 16 OBJECT GLOBAL DEFAULT 6 libxlflicense_name 32: 00010cd0 0 NOTYPE GLOBAL DEFAULT ABS __bss_start 33: 00010a3c 20 OBJECT GLOBAL DEFAULT 7 _xlevalbeta 34: 000005c4 10 OBJECT GLOBAL DEFAULT 6 liblicense_dir 35: 00000690 22 OBJECT GLOBAL DEFAULT 6 libupcextendeval_name 36: 000006ec 30 OBJECT GLOBAL DEFAULT 6 xlc_compiler_name 37: 00000630 16 OBJECT GLOBAL DEFAULT 6 libxlclicense_name 38: 00010cd0 0 NOTYPE GLOBAL DEFAULT ABS _edata 39: 00010cd0 0 NOTYPE GLOBAL DEFAULT ABS _end 40: 000006cc 12 OBJECT GLOBAL DEFAULT 6 xlf_package_name 41: 000006a8 22 OBJECT GLOBAL DEFAULT 6 libstmextendeval_name 42: 00010ad8 504 OBJECT GLOBAL DEFAULT 10 versionString 43: 0000070c 42 OBJECT GLOBAL DEFAULT 6 upc_compiler_name
Notice the lone function at position 23, _xlgetevalbeta, which we proceed to disassemble:
00000580 <_xlgetevalbeta>: 580: 94 21 ff f0 stwu r1,-16(r1) 584: 93 c1 00 08 stw r30,8(r1) 588: 93 e1 00 0c stw r31,12(r1) 58c: 7c 3f 0b 78 mr r31,r1 590: 7d 88 02 a6 mflr r12 594: 42 9f 00 05 bcl- 20,4*cr7+so,598 <_xlgetevalbeta+0x18> 598: 7f c8 02 a6 mflr r30 59c: 3f de 00 01 addis r30,r30,1 5a0: 3b de 05 34 addi r30,r30,1332 5a4: 7d 88 03 a6 mtlr r12 5a8: 80 1e ff fc lwz r0,-4(r30) 5ac: 7c 03 03 78 mr r3,r0 5b0: 81 61 00 00 lwz r11,0(r1) 5b4: 83 cb ff f8 lwz r30,-8(r11) 5b8: 83 eb ff fc lwz r31,-4(r11) 5bc: 7d 61 5b 78 mr r1,r11 5c0: 4e 80 00 20 blr
This is fairly standard, unoptimised code. After saving a few registers on the stack, it computes the address of the global offset table: 0x598 + 0x10000 + 1332 = 0x10acc, matching _GLOBAL_OFFSET_TABLE_ from the symbol table. Next, a value is loaded from the GOT, forming the return value of the function after the stack has been restored.
To find out what this return value really is, we look at the relocation table (by means of readelf -r):
Relocation section '.rela.dyn' at offset 0x55c contains 3 entries: Offset Info Type Sym.Value Sym. Name + Addend 00010a40 00000016 R_PPC_RELATIVE 00000784 00010a44 00000016 R_PPC_RELATIVE 0000097c 00010ac8 00001414 R_PPC_GLOB_DAT 00010a3c _xlevalbeta + 0
The third entry is the one we are looking for: its offset matches the location read by the code at 0x5a8. This means the _xlgetevalbeta function is returning a pointer to _xlevalbeta, which makes some kind of sense.
Another quick look at the symbol table tells us _xlevalbeta lives at address 0x10a3c and is 20 bytes in size. The section header (provided by readelf -S) helps us find the corresponding location in the file:
Section Headers: [Nr] Name Type Addr Off Size ES Flg [ 0] NULL 00000000 000000 000000 00 [ 1] .hash HASH 000000b4 0000b4 0000c8 04 A [ 2] .dynsym DYNSYM 0000017c 00017c 0001f0 10 A [ 3] .dynstr STRTAB 0000036c 00036c 0001ee 00 A [ 4] .rela.dyn RELA 0000055c 00055c 000024 0c A [ 5] .text PROGBITS 00000580 000580 000044 00 AX [ 6] .rodata PROGBITS 000005c4 0005c4 000475 00 A [ 7] .data.rel.ro PROGBITS 00010a3c 000a3c 000014 00 WA [ 8] .dynamic DYNAMIC 00010a50 000a50 000078 08 WA [ 9] .got PROGBITS 00010ac8 000ac8 000010 04 WA [10] .data PROGBITS 00010ad8 000ad8 0001f8 00 WA [11] .comment PROGBITS 00000000 000cd0 000028 00 [12] .shstrtab STRTAB 00000000 000cf8 000073 00 [13] .symtab SYMTAB 00000000 000fc4 0002c0 10 [14] .strtab STRTAB 00000000 001284 000206 00 Key to Flags: W (write), A (alloc), X (execute), M (merge), S (strings) I (info), L (link order), G (group), x (unknown) O (extra OS processing required) o (OS specific), p (processor specific)
The address we are looking for is at the start of the .data.rel.ro section, which can be found at offset 0xa3c in the file. It is time for the hexdump tool:
00000a30 69 62 69 74 65 64 2e 00 00 00 00 00 00 00 00 01 00000a40 00 00 00 00 00 00 00 00 00 00 24 05 4a 0c 65 c8
The last four bytes here, 4a 0c 65 c8, are interesting. Taken as a 32-bit big endian value, they are exactly equal to the modification time of the file, or in other words, the time the compiler was installed. This cannot be a coincidence, so using a hex editor, we replace this with the current time, 4a 80 74 31.
Lo and behold, the compiler is working again.
One hopes the engineers at IBM developing the compiler are not the same ones thinking this copy protection method was a good idea. Then again, perhaps they are; it failed miserably at compiling FFmpeg.
Wow – that seemed relatively easy. I always thought cracking software was hard.
Grumblings I’ve heard from “people that know” say the XL C is a pretty good compiler and sticks to the standards. Generally, you need to do some light patching and usually need to use some extra compiler flags since many projects use nonstandard GNU stuff.
This may help a bit http://www.perzl.org/aix/index.php?n=Main.Instructions, also look through his patches to see common things.