One of the challenges I looked at during the last PlaidCTF was 'drmless', and since I didn't see a write-up yet I thought it'd be nice to publish one. Again this is cross-posted on my blog and int3pids blog.
For this challenge, we were provided with a drmless.tgz file containing a few things:
If we read the instructions, we see the following text:
So we have a file we can decrypt, and one we cannot. The objective is to decrypt the other one, presumably by altering the drmlicense or bypassing it in some way. Let's look at the binary to find out what's going on.
At startup, the binary loads the .drmlicense file and reads 16 bytes into the 'license' global buffer:
Also, it is interesting to note that the name indicates this is probably a whitebox implementation. This means the implementation is obfuscated in an attempt to withstand static/dynamic analysis, and most likely the key is mixed into the algorithm itself in some way.
In any case, I have read documentation on whitebox cryptography before, and also analyzed some implementations of it. Based on that experience, I had some ideas on how to approach a whitebox implementation, but I also knew that I should probably focus on the stuff around the whitebox before diving into the whitebox itself.
Just for fun, I look at the implementation and quickly saw that each round of AES is splitted into several functions, and they are all called sequentially.
The first thing I did was looking for cross-refs to 'undrm'. I found it is used in the 'drmprotected' function to decide whether a file is DRM-protected or not:
I did this using this vtrace script and running it with the cool and the cooler story. The script still requires you to go through the whole output by pressing 'space' until reaching the end, in the same way you'd navigate through a file with 'less'.
For this challenge, we were provided with a drmless.tgz file containing a few things:
- .drmlicense : A 16 byte file with some hexadecimal contents
- cool_story.enc : A 'cool story' encrypted.
- cooler_story.enc : An even 'cooler story' also encrypted.
- drmless : an ELF binary.
- readme.txt : challenge instructions.
If we read the instructions, we see the following text:
Here's a cool story from PPP! We wrote an even cooler story, but you need to pay us if you want to unlock it. TEN THOUSAND DOLLAR.If we run 'drmless cool_story.enc' after extracting the archive on a 32 bit Linux machine we get a nice decrypted file. If we attempt to do the same on the cooler_story.enc file, we are told that this is a binary file and asked whether we want to view it anyway. Sounds like the 'less' program, doesn't it?
So we have a file we can decrypt, and one we cannot. The objective is to decrypt the other one, presumably by altering the drmlicense or bypassing it in some way. Let's look at the binary to find out what's going on.
At startup, the binary loads the .drmlicense file and reads 16 bytes into the 'license' global buffer:
v2 = getenv("HOME"); snprintf(&v27, 1024, "%s/.drmlicense", v2); v3 = open((const char *)&v27, 0); if ( v3 >= 0 || (v3 = open("/.drmlicense", 0), v3 >= 0) || (v3 = open("./.drmlicense", 0), v3 >= 0) ) { read(v3, license, 16); close(v3); }If we search for cross-referneces to license, we see it is only used in the 'undrm' function, which looks like this:
int __cdecl undrm(int a1) { int result; // eax@1 aes_wb_decryptor(a1, a1); result = 0; do { *(_BYTE *)(a1 + result) ^= license[result]; ++result; } while ( result != 16 ); return result; }So apparently 'drmless' uses this aes_wb_decryptor function to decrypt the data, and then XORs it with the license. It is interesting to note that only the input buffer is passed to the decryptor, which means that it either uses a hardcoded key or it is stored in some global variable.
Also, it is interesting to note that the name indicates this is probably a whitebox implementation. This means the implementation is obfuscated in an attempt to withstand static/dynamic analysis, and most likely the key is mixed into the algorithm itself in some way.
In any case, I have read documentation on whitebox cryptography before, and also analyzed some implementations of it. Based on that experience, I had some ideas on how to approach a whitebox implementation, but I also knew that I should probably focus on the stuff around the whitebox before diving into the whitebox itself.
Just for fun, I look at the implementation and quickly saw that each round of AES is splitted into several functions, and they are all called sequentially.
.text:0829FF19 loc_829FF19: ; CODE XREF: aes_wb_decryptor+7089 j .text:0829FF19 ; aes_wb_decryptor+7094 j ... .text:0829FF19 mov eax, [ebp+var_24] .text:0829FF1C call r013 .text:0829FF21 mov [ebp+var_59], al .text:0829FF24 mov eax, [ebp+var_30] .text:0829FF27 call r020 .text:0829FF2C mov [ebp+var_7B], al .text:0829FF2F mov eax, [ebp+var_2C] .text:0829FF32 call r021 .text:0829FF37 mov [ebp+var_7A], al .text:0829FF3A mov eax, [ebp+var_28] .text:0829FF3D call r022 .text:0829FF42 mov [ebp+var_79], al .text:0829FF45 mov eax, [ebp+var_24] .text:0829FF48 call r023 .text:0829FF4D mov [ebp+var_78], al .text:0829FF50 mov eax, [ebp+var_30] .text:0829FF53 call r030I also saw no global key seems to be input to the algorithm, so I started treating it as a decryption oracle and turned into the more interesting XOR with the license and its possible implications.
The first thing I did was looking for cross-refs to 'undrm'. I found it is used in the 'drmprotected' function to decide whether a file is DRM-protected or not:
signed int __cdecl drmprotected(int a1) { int v1; // ebx@4 int v2; // eax@5 char v4[16]; // [sp+10h] [bp-48h]@4 char v5; // [sp+20h] [bp-38h]@4 char v6; // [sp+30h] [bp-28h]@4 char v7; // [sp+40h] [bp-18h]@4 if ( !old_bin_file(a1) || !seekable(a1) || lseek(a1, 0, 0) == -1 ) return 0; v1 = read(a1, v4, 64); undrm((int)v4); undrm((int)&v5); undrm((int)&v6); undrm((int)&v7); if ( v1 > 0 ) { v2 = 0; if ( v4[0] < 0 ) return 0; while ( 1 ) { ++v2; if ( v1 <= v2 ) break; if ( v4[v2] < 0 ) return 0; } } return 1; }This just decrypts the first 64 bytes of a file and performs some checks on it. If the checks pass, the function returns 1, otherwise it returns 0. At this point I strongly suspected this was all the protection that was to be found. So I decided to set my drm license to all-zeros, force the 'drmprotected' function to return 1 and dump the data into a file.
I did this using this vtrace script and running it with the cool and the cooler story. The script still requires you to go through the whole output by pressing 'space' until reaching the end, in the same way you'd navigate through a file with 'less'.
sfx@deb:~/drmless/$ python drmless.py cool_story.enc cool_story.dec sfx@deb:~/drmless/$ python drmless.py cooler_story.enc cooler_story.decAfter this, I used xortool from Hellman to analyze the output files. When run with the 'cool story' it lead to the original .drmlicense contents... so I ran it against the cooler story and used the resulting key to decrypt the output:
sfx@deb:~/drmless/xortool-master$ python xortool.py -l 16 ../cool_story.dec -c 20 1 possible key(s) of length 16: \x00\x11"3DUfw\x88\x99\xaa\xbb\xcc\xdd\xee\xff Found 1 plaintexts with 95.0%+ printable characters See files filename-key.csv, filename-char_used-perc_printable.csv sfx@deb:~/drmless/xortool-master$ python xortool.py -l 16 ../cooler_story.dec -c 20 1 possible key(s) of length 16: \xfe\xed\xa1\x07\x0f\xf0\rp\xde\xad\xbe\xef\xfa\xceUU Found 1 plaintexts with 95.0%+ printable characters See files filename-key.csv, filename-char_used-perc_printable.csv sfx@deb:~/drmless/xortool-master$ cd ... bash: cd: ...: No such file or directory sfx@deb:~/drmless/xortool-master$ ls args.py args.pyc colors.py colors.pyc libcolors.py libcolors.pyc README.md routine.py routine.pyc tests xortool_out xortool.py sfx@deb:~/drmless/xortool-master$ cd .. sfx@deb:~/drmless$ ls cooler_story.dec cool_story.dec drmless master.zip story.dec util.pyc cooler_story.enc cool_story.enc drmless.py readme.txt util.py xortool-master sfx@deb:~/drmless$ python Python 2.7.3 (default, Mar 5 2013, 01:19:40) [GCC 4.7.2] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> import util >>> x = open('cooler_story.dec').read() >>> key = "\xfe\xed\xa1\x07\x0f\xf0\rp\xde\xad\xbe\xef\xfa\xceUU" >>> y = util.repxor(x,key) >>> y[:100] 'TWELFTH NIGHT; OR, WHAT YOU WILL\r\n\r\nby PPP\r\n"freeShakespeare_downWithDRM"\r\n\r\n\r\n\r\nPERSONS REPRESENTED' >>> The key is: freeShakespeare_downWithDRMSo here it is, 250 points :)
No comments:
Post a Comment