Download essay, bruteforcer source (et cetera) here: f-yates1.zip.
Looking a bit a [yAtEs] crackme number 1. That's a bit of an executable size -- 581k. Anyway. The code is pretty straightforward, and no work has been done to hide what is going on. However, you need to do quite an amount of work to reach your goal, and this crackme is definitely not for the untrained or impatient cracker. I'll rate this 6/10, a bit close to a 5 since it's so straightforward (well, hardly no obfuscation). It was a joy reversing this crackme, [yAtEs] :).
Looking at the section headers, we notice that there's the usual sections, and a last section called "yates". I assume something has been done to the main app, which an extra section (and a redirected entrypoint) is going to rectify -- if you have a correct key, at least :).
Another interesting thing is the "unpacked with procdump32" string in the header section...
Looking through the executable, we see that it's all full of garbage until the spot with a "runtime error" string (delphi?). The imports seem to be intact, at least there's a few more than you see in typical compressed / encrypted apps. Resources seem to have been left intact, you can verify this by loading _crackme.exe into a resource editor (resource hacker, for instance. Tell me if you know of a better resource editor).
Perusing the resource section, it really looks like some delphi or BCB crap. "TLabel", "TCaption" etc. Blah :).
The added section looks clean in a hexeditor. Easy identifiable strings. "technics.dat" followed by "CORRECT KEY" - could this be the keyfile name? Run filemon, run the crackme. Well, at least it tries opening technics.dat, and that's the only file access the crackme does. Fine. Let's construct a rubbish keyfile, and have a look at a disassembly. I chose the following contents (ok, so the keyfile contents weren't RUBBISH, but probably not a good keyfile for crackme1 ;-). Yup, I created the keyfile before looking at the disassembly, just because I was in that mood.
appendix 1 - technics.dat
00000000 4F6E6520 52696E67 20746F20 72756C65 One Ring to rule
00000010 20746865 6D20616C 6C2C204F 6E652052 them all, One R
00000020 696E6720 746F2066 696E6420 7468656D ing to find them
00000030 2C0D0A4F 6E652052 696E6720 746F2062 ,..One Ring to b
00000040 72696E67 20746865 6D20616C 6C20616E ring them all an
00000050 6420696E 20746865 20646172 6B6E6573 d in the darknes
00000060 73206269 6E642074 68656D0D 0A496E20 s bind them..In
00000070 74686520 4C616E64 206F6620 4D6F7264 the Land of Mord
00000080 6F722077 68657265 20746865 20536861 or where the Sha
00000090 646F7773 206C6965 2E dows lie.
When disassembling the file, we can see that my assumption about redirected entrypoint was correct. Using IDA is a bit overkill, because of the encrypted (read on) code... I mean, IDA (of course) chokes on all the encrypted code, so using a faster disassembler would have been just as fine.
The code is quite simple. I have never been a fan of long dead listings, so
you will not see any unless I find them important. The keyfile is opened
GENERIC_READ - quite a logical
combination. Error checking (couldn't open file) is done. Note that there's
a bit of queerness right after the call to ReadFile; [yAtEs] gets the value
of numberOfBytesRead into eax, but does nothing with it before (next
instruction) he loads in a DWORD that was read from the file - the second
DWORD. After a little register/variable setup, a
Glancing quickly at the code snippet (which we can rightly call a function),
I immediately saw what was going on -- bitwise shifts, xor, adds, and the
0x9E3779B9h. What a freaky coincidence. The days
before I messed with this crackme1, I had been working on an ASM
implementation of TEA, because I couldn't think of anything better to waste
my time with :-). And this was definitely the good old TEA decryption
algorithm I was looking at. With a minor change, though. TEA usually has
a keysize of 128 bits, split in four DWORD components. This version uses
only a single DWORD, which is used in all four places where the
individual components should have been used, thus reducing keysize to 32
bits. Sounds like it might be feasible to bruteforce, eh? You might want to
find some TEA code in C on the net, or translate the asm yourself -- it
helps getting a better view of what's happening. As you can see, it is
pretty trivial, with very little setup, and no use of tables. But even
though it's functionally simple, reversing it requires some crypto
knowledge. Which I don't have. Sorry.
appendix 2 - decryption listing
00497198 tea_decrypt proc near ; CODE XREF: start+57
00497198 ; start+A7
00497199 mov ebp, 32 ; 32 rounds is the usual
0049719E mov eax, 0C6EF3720h; "delta" shl 5
004971A3 mov edx, ds:data_0
004971A9 mov ecx, ds:data_1
004971AF mov esi, ds:TEAkey
004971B5 @@round: ; CODE XREF: tea_decrypt+51
004971B5 mov edi, edx
004971B7 shr edi, 5
004971BA add edi, esi
004971BC mov ebx, edx
004971BE shl ebx, 4
004971C1 add ebx, esi
004971C3 xor edi, ebx
004971C5 lea ebx, [eax+edx]
004971C8 xor edi, ebx
004971CA sub ecx, edi
004971CC mov edi, ecx
004971CE shr edi, 5
004971D1 add edi, esi
004971D3 mov ebx, ecx
004971D5 shl ebx, 4
004971D8 add ebx, esi
004971DA xor edi, ebx
004971DC lea ebx, [eax+ecx]
004971DF xor edi, ebx
004971E1 sub edx, edi
004971E3 sub eax, 9E3779B9h ; delta value
004971E8 dec ebp
004971E9 jnz short @@round
004971EB mov ds:data_0, edx
004971F1 mov ds:data_1, ecx
004971F8 tea_decrypt endp
Well. If you run the crackme with a bogus keyfile, it doesn't crash. So
there has to be a keyfile validation check, or checksum, or something.
Quite simple, really.
funky values on program start. [yAtEs] decrypts these with the second
dword from the keyfile (the key), and checks for them being zero. If they
aren't, goto(badBoyMessageBoxA). Nice and efficient check. This means
bruteforcing will only have to be done on a single data item, 8 bytes, and
not on a whole range of code followed by a checksum check. Also, it means
we have known plaintext, but... I doubt that will help an average reverser
very much :-).
With this knowledge, we could stop analyzing the rest of the crackme. We
have enough information to write a bruteforcer and get ourselves a valid
keyfile. But it's always good to look a bit at what you're dealing with --
no lazy boy today. What follows is a decrypt loop and a jump to
OriginalEntryPoint. The decryptloop is trivial, but a bit queer at the same
time. I don't know if [yAtEs] has been using a very bad compiler, has been
smoking too many bad floppies, or if he has tried sprinkling a bit of
obfuscation. Probably the last, but not very successful imho :). It's
pretty straightforward. Fetch an encrypted datablock (8 bytes), stuff it
data_1 (and a few other spots as
well, just to confuse the cracker), call tea_decrypt, stuff decrypted
values back to memory, update pointer, increase number of blocks processed,
check if done. When done, display a good boy message, and finally jump
Okay, time to write a bruteforcer. Goal is to find a tea-key that decrypts
00000000.00000000. A single
datablock, thus [yAtEs] tea_decrypt seems to be efficient enough (however,
for use with larger blocks than a single data item, many improvements could
be made, also to security, to avoid that two identical data blocks are
encrypted to the same encrypted data blocks -- which can happen pretty
often in PE files, because of section
than just scanning from one end to another, I choose to scan from both ends
towards the middle. This is usually a big improvement over scanning from
one end to another, and simple enough that dull boys like me can implement
it in a hurry. You could also split the keyspace in two, and go towards
both middles, but that's more complicated and...who cares :). See
bruter.c for my bruteforcer.
On my athlon700, the initial revision of the bruteforcer took 2249 seconds - or ~37 minutes and 29 seconds. Not very good :). This was the LCC-compiled version. The speeds were around 1369019,1 tests per second for the LCC version, and surprisingly enough, 1396648,0 for VC6. Not a very big gain! I suppose a handwritten assembly implementation could bring down the execution time, but... hey...I've found the key now :). Oh yes, notice that your key doesn't have to be 12 bytes long, as [yAtEs] is not doing a check on the amount of bytes read. So just making it four dummy bytes followed by the four-byte key works like a charm.
I guess I should be satisfied now. I have brute-forced a decryption key, and thus I can run my target. But this somehow isn't enough. The thought of running a relatively slow decryption loop every time I start the app! On a large amount of data (bloated delphi apps ;). And, had this been a real world situation where I had bought the application, I would have been insulted by the thought of the program doubting my right to run it! Ok, so I'm mad as a hatter, but I decided to remove the TEA wrapper.
We know that the decryptor starts decrypting from address
00401000h. It decrypts
0D780h blocks, or 441344
bytes. (Look at your listing if you can't follow me). Looking at
_crackme.exe in a PE editor, we can see this range lies entirely within the
first section (and doesn't fill up the entire section - 48 bytes
PhysicalSize) are left untouched. Easy peasy, the steps
I will not go into details of how decrypt.c works - it should be
quite clear if you have understood the rest of this essay. And yes, the
code is a bit dirty. Okay, backup _crackme.exe. Run decrypt once (once!).
Now the code section should be decrypted. From our dead (or, if you use
IDA, interactive) listing, we see that the OEP is at
Find the RVA of this - either use an RVAtool, or manually subtract
ImageBase. Open the file in HIEW (or any other PE editor, or in a raw hex
editor if you're used to this stuff), and set the EntryPoint RVA to
0006CB70h - and do a run to see if it works. Mine did :). All
this is probably trivial if you have a good knowledge of the PE file format
(which is very important, imho. Iczelion PE-Tuts and the LUEVELSMEYER doc.)
Well, now we could be satisfied, I guess. No longer is the evil and slow
TEA code run, and we have an application that we can look directly into
with hex editors and disassemblers - no longer are we confined to a live
image (that could be dumped, btw). However, this is still not good enough!
Even though it's not executed, the TEA code is still there. Grumble. Good
thing that HIEW makes life so easy for us. Get the header info dialog up
(F8), switch to section/object view (F6), select the section named `yates'
and press enter. Now we are located in the beginning of that section. F3
to enter edit mode, followed by F9 to truncate the file. Now we must nuke
IMAGE_SECTION_HEADER. This is easiest done in HIEWs section
/ object table, by removing the name string and putting zeroes in all other
values. After that, you must decrease the count of sections in the header.
Does the file still run? Yes, that's fine. Now we are finished, but just to
be a really good citizen in the realm of windows :), you should update the
Size Of Image field in the header as well. Put the RVA of the last
0007C000h), plus it's PhysSize (
rounded up to Section Alignment (1000h, resulting in
- a final value of
00097000h. Phew :-), now we should finally
be completely done with this target. Time to add some CSS formatting to this
document (hopefully before you see it), wrap it all up, and go to bed. 01:06.
Yes, 24hr system, the real thing.
To quote [yAtEs]:
hehe, i think this protection is very strong to the average
cracker,..prove me wrong and mail me @ Jamesluton@hotmail.com
Yup, it's pretty good stuff, [yAtEs] :). And if you had used an uncrippled TEA, I doubt that anybody would have bothered bruteforcing this. It might have been possible by finding equal encrypted values, and guessing that they are zeroes. However, the CODE section doesn't have a large zero padding as sections usually have, so one would have to look pretty damn hard :). Besides, even if you could find multiple places with the same 8-byte crypted blocks, and guessing they were zeroes... would it help you? Cryptanalysis is still way over my head. Anyway, if the TEA algorithm was uncrippled, and also chained (correct terminology?), for instance not clearing the delta-sum for each block, this would result in equal input-data NOT resulting in equal output-data; I've tested this with a few K's of 42h, and I couldn't spot any easy to find repetitions... TEA is a simple and neat algorithm. I think that was just about what I had to say about this matter. There. 01:49 and it's a wrap.
This essay has been revised a bit compared to the version I sent to http://crackmes.cjb.net, as I've had a couple of nice persons doing some proofreading. Also, the filename has been changed from "essay02" to "essay01", since that's the correct name, and I was pretty tired when I wrote the original. Sigh.