0x1d01ebcc
Last week, jvoisin came up with a home made crackme.
Since I'm really interested in reverse engineering but really lame, I took this
opportunity to learn some stuffs.
So let's take a look :)
Overview
Basic strategy, I apply file and readelf on the binary.
depierre$ file 0x1d01ebcc 0x1d01ebcc: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), stripped depierre$ readelf -h 0x1d01ebcc En-tête ELF: Magique: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 Classe: ELF32 Données: complément à 2, système à octets de poids faible d'abord (little endian) Version: 1 (current) OS/ABI: UNIX - System V Version ABI: 0 Type: EXEC (fichier exécutable) Machine: Intel 80386 Version: 0x1 Adresse du point d'entrée: 0x80492d0 Début des en-têtes de programme: 52 (octets dans le fichier) Début des en-têtes de section: 57005 (octets dans le fichier) Fanions: 0x0 Taille de cet en-tête: 52 (bytes) Taille de l'en-tête du programme: 32 (bytes) Nombre d'en-tête du programme: 9 Taille des en-têtes de section: 40 (bytes) Nombre d'en-têtes de section: 57007 Table d'indexes des chaînes d'en-tête de section: 47806 readelf: ERREUR: Incapable de lire 0x22cb58 octets de En-têtes de section
Well, it seems that the section headers are screwed. He might have applied
this trick from his blog.
He explains that with few modifications in the elf header, GDB will not be able
to debug the crackme.
"Qu'à cela ne tienne", I'm going to use IDA instead. Since I have the 6.4 version, I'm not concerned by this possible trick, so let's give a try!
Reverse with IDA
First, I start the crackme in debug mode and it's quite funny to see how many errors there are.
Eventhough we have a segment fault, we can continue the execution and the GUI appears.
Now, it's time to take a look at the code.
With the init function, we can find the main at the offset sub_8048D90.
We know that jvoisin used GTK+
and with the design of the crackme, we look for the callback
function of the
button 'Ok'.
; ... mov dword ptr [esp+14h], 0 mov dword ptr [esp+10h], 0 mov dword ptr [esp+0Ch], 0 mov dword ptr [esp+8], offset byte_8049400 ; callback function when 'Ok' is clicked mov dword ptr [esp+4], offset aClicked ; "clicked" mov [esp], eax call sub_8048C50 ; ...
The callback function of 'Ok' is byte_8049400. Wait, byte?!
LOAD:08049400 byte_8049400 db 83h, 0ECh, 3Ch ; DATA XREF: sub_8048D90+43Eo LOAD:08049403 dword_8049403 dd 14A165h ; DATA XREF: sub_8048D90+4FFr LOAD:08049407 align 4 LOAD:08049408 dd 24448900h, 0C7C0312Ch, 1C2444h, 0C7000000h, 202444h LOAD:08049408 dd 0E8000000h, 0FFFFF8DCh, 14158Bh, 44890807h, 14890424h LOAD:08049408 dd 0F74AE824h, 489FFFFh, 0F752E824h, 4489FFFFh, 1EB2424h LOAD:08049408 dd 2404C71Dh, 0Bh, 0FFF70FE8h, 1D01EBFFh, 0FFF8A7E8h, 10158BFFh LOAD:08049408 dd 89080700h, 89042444h, 15E82414h, 89FFFFF7h, 1DE82404h LOAD:08049408 dd 89FFFFF7h, 0C7282444h, 182444h, 0EB000000h, 24548B19h LOAD:08049408 dd 24448B18h, 0FD00124h, 0BE0F00B6h, 244401C0h, 24448320h LOAD:08049408 dd 548B0118h, 448B1824h, 0D0012424h, 8400B60Fh, 0EBD675C0h LOAD:08049408 dd 44C71D01h, 1824h, 1CEB0000h, 1824548Bh, 2824448Bh, 0B60FD001h LOAD:08049408 dd 0C0BE0F00h, 130E883h, 831C2444h, 1182444h, 1824548Bh LOAD:08049408 dd 2824448Bh, 0B60FD001h, 75C08400h, 0CA1D3h, 44290807h LOAD:08049408 dd 448B1C24h, 0C2892024h, 0C11FFAC1h, 0D0011AEAh, 293FE083h LOAD:08049408 dd 244489D0h, 24448B20h, 24443B1Ch, 0E8297520h, 0FFFFF63Ch ; ...
Hum... New trick here. IDA is not smart enough to see the code so let's help by
pressing 'c' then 'p'.
We precize to the debugger that we are dealing with code and not data and
everything comes back to normal.
Below, the flow chart of the function.
We have two subroutines (likely hash routines) and, depending on a comparison of their results, we fail or not.
Subroutines
Hash nickname
The first subroutine proceeds the nickname.
Since I don't have the skill to understand what it staticly does, I analysed it
dynamicaly.
It sums the hexadecimal values of each character composing the nickname.
For instance, with depierre we have:
- 64h + 65h + 70h + 69h + 65h + 72h + 72h + 65h = 350h
Hash key
The second subroutine proceeds the key.
It proceeds each digit of the key as it and sums them.
For instance, with '42' we have: 4h + 2h = 6h
Compare routine
The hashes of the nickname and the key are not compared yet.
Something happens just before that.
; ... mov eax, ds:dword_807000C sub [esp+3Ch+var_20], eax mov eax, [esp+3Ch+var_1C] mov edx, eax sar edx, 1Fh shr edx, 1Ah add eax, edx and eax, 3Fh sub eax, edx mov [esp+3Ch+var_1C], eax mov eax, [esp+3Ch+var_20] cmp eax, [esp+3Ch+var_1C] jnz short loc_8049538 ; ...
Right in the middle, we have a modulo!
I had some trouble to spot it. First because I'm a newbie and second because
the modulo has been weirdly translated.
I find the value of the module after a couple of dynamic tests: 40h (64d).
Inside IDA
In order to be sure of my assumptions, I first try to find one valid key for the nickname depierre:
- hexsum_nick = 350h
- hexsum_nick mod 40h = 10h
Let's take 88d as the key:
- sum_key = 8h + 8h = 10h
It works inside IDA but not outside :/
It seems that he has written a sneaky silent feature against debugging to
change the result when the crackme is debugged.
Sneaky handle!
Maybe you have noticed these two instructions in the compare routine.
mov eax, ds:dword_807000C sub [esp+3Ch+var_20], eax
When they are executed, the value saved in 807000C is always 0. It's weird
and useless to substract 0 to EAX.
I try to look for its references somewhere else in the code but nothing
interesting.
What is the difference between executing the binary with and without a
debugger?
The handlers! Do you remember the SIGSEV we have when we start the crackme? We
should take a look.
So this time, I precize to IDA that every SIGSEV raised will be handled by the application.
Also, I set up a trace on the offset 807000C to see when the value is
modified.
Last, I break on the SIGSEV interruption's call and I follow the instructions
to see when the value is modified.
A couple of instructions later, I finally found the new handler. According to the trace, this piece of code adds the interrupt's value to 807000C.
This new handle function is set up in the middle of the main. You can see the process below.
The trick he applied here is kind of cool, in my opinion. He wrote a handler
for the SIGSEV interrupt.
When the segmentation fault is raised, the handler will simply add the
interrupt value to 807000C.
Then, the compare routine will substract it to the the hash of the key (here
11d for SIGSEV).
Keygen
We have so far the algorithm to find if a key is correct or not, depending on the nickname.
On a hand, the crackme sums the hexadecimal value of the characters composing
the nickname.
Then it applies a modulo 40h (64d) on it.
On the other hand, it sums the digit composing the key and it substracts Bh
(11d).
Finally, it compares both hashes.
Hence I've written a little keygen for that crackme (in python).
#! /usr/bin/env python2 import sys import random from sets import Set def keygen(nick): diff = (sum([ord(c) for c in nick]) % 64) + 11 key = '' while diff > 0: newdigit = random.randint(1, min(diff, 9)) key += str(newdigit) diff -= newdigit return key def get_keys(nick, keys_count): keys = Set() while len(keys) < keys_count: keys.update([keygen(nick)]) return keys if __name__ == '__main__': print 'Keygen 0x1d01ebcc' keys_count = 1 if len(sys.argv) == 3: keys_count = int(sys.argv[2]) elif len(sys.argv) != 2: print 'Usage: %s nickname [count]' % (sys.argv[0]) sys.exit(1) print 'nickname %s' % (sys.argv[1]) random.seed() for key in get_keys(sys.argv[1], keys_count): print 'key: %s' % (key)
Which works like a charm :)
depierre$ ./keygen.py depierre 10
Keygen 0x1d01ebcc
nickname depierre
key: 9623412
key: 59661
key: 1247841
key: 31878
key: 3718116
key: 95712111
key: 72783
key: 67137111
key: 6428322
key: 1529411121
\o/
Conclusion
In my opinion, this crackme was really interesting for many reasons.
First, with the messed up header, I had to use IDA instead of GDB. I usually
prefer the second one so it was interesting to go 'deeper' with a tool that I
am not familiar with.
Second, the crackme were not too hard nor too easy. I mean that it was the
right level for someone like me (i.e. who is learning), even if jvoisin helped
me on many points (for the init handle for instance).
Finally, using the handle on SIGSEV to add its interrupt value was a really
nice idea. A sneaky silent one but still a nice one.
To be honest, I'm already waiting for the next one :)
jvoisin told me that he has increased a lot the difficulty so I'm kind of
impatient!
That's all folks!