Crackme 0x1d01ebcc

Published on Thursday, 18 April 2013 in Reverse Engineering ; tagged with trick, elf, challenge, crackme, reverse engineering, ida, sigsev, handler, exception ; text version

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.

Error from IDA because of the malformed
header

Error from IDA because of the
sigsev

Error from IDA because of the
sigtrap

Eventhough we have a segment fault, we can continue the execution and the GUI appears.

GUI crackme

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.

IDA convert to code

Below, the flow chart of the function.

Flow chart of the
crackme

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.

Hash the
nickname

It sums the hexadecimal values of each character composing the nickname.
For instance, with depierre we have:

Hash key

The second subroutine proceeds the key.

Hash 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:

Let's take 88d as the key:

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.

Xreferences from IDA

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.

IDA interrupt
handlers

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.

Break point when SIGSEV is
raised

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.

The new handle Trace's results on the
value

This new handle function is set up in the middle of the main. You can see the process below.

Init handle

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

Success Key

\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!


contactdepier.re License WTFPL2