From aldeid
Jump to navigation Jump to search


Download link:

The keygen is composed of a 32 bit executable (Arrow-keygenme.exe and an image folder, containing an image: arrows.jpg.

When you start the program, nothing happens :(



Let's open the executable in IDA-Pro. The start function pushes DialogFunc as lpDialogFunc parameter at offset 0x40100E:

.text:00401000 public start
.text:00401000 start proc near
.text:00401000 push    0               ; lpModuleName
.text:00401002 call    GetModuleHandleA
.text:00401007 mov     hInstance, eax
.text:0040100C push    0               ; dwInitParam
.text:0040100E push    offset DialogFunc ; lpDialogFunc
.text:00401013 push    0               ; hWndParent
.text:00401015 push    65h             ; lpTemplateName
.text:00401017 push    hInstance       ; hInstance
.text:0040101D call    DialogBoxParamA
.text:00401022 push    eax             ; uExitCode
.text:00401023 call    ExitProcess
.text:00401023 start endp


Graph Overview

The DialogFunc function graph overview is as follows:



A quick overview of the code shows that the program is trying to access an image (arrows.jpg):

.text:0040106C                 push    0               ; hTemplateFile
.text:0040106E                 push    FILE_FLAG_OVERLAPPED ; dwFlagsAndAttributes
.text:00401073                 push    OPEN_EXISTING   ; dwCreationDisposition
.text:00401075                 push    0               ; lpSecurityAttributes
.text:00401077                 push    FILE_SHARE_READ ; dwShareMode
.text:00401079                 push    GENERIC_READ    ; dwDesiredAccess
.text:0040107E                 push    offset FileName ; "arrows.jpg"
.text:00401083                 call    CreateFileA

If we move the file arrows.jpg from the image directory to the parent location, where the program has been copied, the program now shows a graphical interface:



Starting from offset 004011A1, the local time is saved to SystemTime via a call to GetLocalTime at offset 0x4011A6:

.text:004011A1                 push    offset SystemTime ; local time saved to SystemTime
.text:004011A6                 call    GetLocalTime

Starting from offset 0x4011AB, byte 0x40302F is pushed as argument to sub_401221 and the function is called at offset 0x4011B0.

.text:004011AB                 push    offset byte_40302F
.text:004011B0                 call    sub_401221

Below is the code of the sub_401221 function:

.text:00401221 sub_401221      proc near
.text:00401221 byte_40302F     = dword ptr  8
.text:00401221                 push    ebp
.text:00401222                 mov     ebp, esp
.text:00401224                 xor     eax, eax              ; eax = 0
.text:00401226                 mov     ax, SystemTime.wYear  ; ax = year
.text:0040122C                 rol     eax, 10h              ; eax = year << 0x10
.text:0040122F                 xor     ebx, ebx              ; ebx = 0
.text:00401231                 mov     bx, SystemTime.wMonth ; bx = month
.text:00401238                 rol     ebx, 8                ; ebx = month << 0x8
.text:0040123B                 or      eax, ebx              ; eax = eax | ebx
.text:0040123D                 mov     bx, SystemTime.wHour  ; bx = hour
.text:00401244                 or      eax, ebx              ; eax = eax | ebx
.text:00401246                 mov     ebx, [ebp+byte_40302F] ; \
.text:00401249                 mov     [ebx], eax             ; / eax saved to location 0x40302F
.text:0040124B                 leave
.text:0040124C                 retn    4
.text:0040124C sub_401221      endp

This function is actually building the following byte:

   Byte 0x40302F
│ 13 │ 02 │ E0 07 │ 
  │    │    │
  │    │    └─ 0x07E0: current year (2016)
  │    └─ 0x02: current month (February)
  └── 0x13: current hour (6pm)

Validate user format


Starting from offset 0x401045, GetDlgItemTextA gets the username, which is pushed as argument to sub_40124F and the function is called at offset 0x40105A:

.text:00401045                 push    20h             ; cchMax
.text:00401047                 push    offset String   ; lpString
.text:0040104C                 push    3EAh            ; nIDDlgItem   =>   ID #1006 (Name)
.text:00401051                 push    [ebp+hDlg]      ; hDlg
.text:00401054                 call    GetDlgItemTextA
.text:00401059                 push    eax             ; eax = my_user
.text:0040105A                 call    sub_40124F      ; validate_user_format


The function layout is as follows:


First, it checks that the username is 6 characters long:

.text:00401252                 cmp     [ebp+my_user], 6 ; len(my_user) = 6
.text:00401256                 jnz     short INVALID_FMT

Then, it checks whether the first 2 characters are in the range [a-z] and adds the sum of these characters to AL at offset 0x401273 (remember that EAX initially contains the length of the username, at offset 0x401059).

.text:00401258                 mov     ecx, 2
.text:0040125D loc_40125D:
.text:0040125D                 cmp     byte ptr [ecx+4030ABh], 61h ; 'a'
.text:00401264                 jge     short loc_401268
.text:00401266                 jmp     short INVALID_FMT
.text:00401268 ; ---------------------------------------------------------------------------
.text:00401268 loc_401268:
.text:00401268                 cmp     byte ptr [ecx+4030ABh], 7Ah ; 'z'
.text:0040126F                 jle     short loc_401273
.text:00401271                 jmp     short INVALID_FMT
.text:00401273 ; ---------------------------------------------------------------------------
.text:00401273 loc_401273:
.text:00401273                 add     al, [ecx+4030ABh] ; al += my_user[i]
.text:00401279                 loop    loc_40125D      ; my_user[0] and my_user[1]
.text:00401279                                         ; should be [a-z]

Starting from offset 0x40127B, the program now checks the last 4 characters (they should be numeric) and adds the value of these characters in AH at offset 0x401296.

.text:0040127B                 mov     ecx, 4
.text:00401280 loc_401280:
.text:00401280                 cmp     byte_4030AD[ecx], 30h ; '0'
.text:00401287                 jge     short loc_40128B
.text:00401289                 jmp     short INVALID_FMT
.text:0040128B ; ---------------------------------------------------------------------------
.text:0040128B loc_40128B:
.text:0040128B                 cmp     byte_4030AD[ecx], 39h ; '9'
.text:00401292                 jle     short loc_401296
.text:00401294                 jmp     short INVALID_FMT
.text:00401296 ; ---------------------------------------------------------------------------
.text:00401296 loc_401296:
.text:00401296                 add     ah, byte_4030AD[ecx] ; ah += my_user[i]
.text:0040129C                 loop    loc_401280      ; my_user[2], my_user[3],
.text:0040129C                                         ; my_user[4], my_user[5]
.text:0040129C                                         ; should be [0-9]

Below is an example with the username gj7400:

│                    │  AH  │  AL  │    AX   │
│                    │      │      │ (cumul) │
│ length  │ 0x06     │      │      │  0x0006 │
│ alpha   │ 0x6a (j) │      │ 0x6a │  0x0070 │
│         │ 0x67 (g) │      │ 0x67 │  0x00d7 │
│ numeric │ 0x30 (0) │ 0x30 │      │  0x30d7 │
│         │ 0x30 (0) │ 0x30 │      │  0x60d7 │
│         │ 0x34 (4) │ 0x34 │      │  0x94d7 │
│         │ 0x37 (7) │ 0x37 │      │  0xcbd7 │

In our example, 9xCBD7 is actually the 2nd part of the expected key, as we will see later.



The program then calls sub_4012A6 at offset 0x4010C3. The function looks like this:

.text:004012A6 make_key proc near
.text:004012A6 lea     edi, byte_40302F ; 0x1302E007
.text:004012AC mov     ah, [edi+3]      ; ah = 0x07
.text:004012AF mov     al, [edi+2]      ; al = 0xE0
.text:004012B2 xor     ebx, ebx         ; ebx = 0
.text:004012B4 mov     bx, ax           ; bx = 0x07E0
.text:004012B7 xor     eax, eax         ; eax = 0
.text:004012B9 mov     al, [edi+1]      ; al = 0x02
.text:004012BC imul    bx, ax           ; bx = 0x07E0 * 0x02 = 0x0FC0
.text:004012C0 xor     eax, eax         ; eax = 0
.text:004012C2 mov     al, [edi]        ; al = 0x13
.text:004012C4 add     ebx, eax         ; ebx = 0x0FC0 + 0x13 = 0xFD3
.text:004012C6 rol     ebx, 10h         ; ebx = 0xFD3 << 0x10 = 0xfd30000
.text:004012C9 mov     eax, key         ; eax = key (2nd part)
.text:004012CE mov     bh, ah
.text:004012D0 mov     bl, al
.text:004012D2 mov     key, ebx         ; ebx = concat(ebx, key_2ndpart)
.text:004012D8 retn
.text:004012D8 make_key endp

The code manipulates byte_40302F and then concatenates the result with the previous key (2nd part). In our example, it makes:

│ 0x0fd3 │ 0xcbd7 │ => FD3CBD7
    ▲        ▲
    │        └── sub_40124F
    └── sub_4012A6

Final check

The key is transformed to hexadecimal with capital letters at offset 0x4010D8 and the key is compared to the expected one at offset 0x4010FE:

.text:004010C8                 push    key
.text:004010CE                 push    offset aIx      ; "%IX"
.text:004010D3                 push    offset expected_key ; LPSTR
.text:004010D8                 call    wsprintfA       ; convert to Hex with capital characters
.text:004010DD                 add     esp, 0Ch
.text:004010E0                 push    20h             ; cchMax
.text:004010E2                 push    offset my_key   ; lpString
.text:004010E7                 push    3E9h            ; nIDDlgItem
.text:004010EC                 push    [ebp+hDlg]      ; hDlg
.text:004010EF                 call    GetDlgItemTextA
.text:004010F4                 push    offset my_key   ; lpString2
.text:004010F9                 push    offset expected_key ; lpString1
.text:004010FE                 call    lstrcmpA
.text:00401103                 or      eax, eax
.text:00401105                 jnz     short loc_401119
.text:00401107                 push    offset aCorrectNowYouK ; "Correct, Now you know what to do; don¦t"...
.text:0040110C                 push    3F1h            ; nIDDlgItem
.text:00401111                 push    [ebp+hDlg]      ; hDlg



#!/usr/bin/env python

import sys
import re
from datetime import datetime

def make_serial(username):
    eax = len(username)
    # AL
    for i in range(2):
        eax += ord(username[i])
    # AH
    for i in range(2,6):
        eax += ord(username[i]) * 0x100

    d =

    return "%X%X" % (d.year * d.month + d.hour, eax)

if __name__ == '__main__':
    if len(sys.argv) != 2:
        print "Usage: %s <username>" % sys.argv[0]
    if re.match('[a-z]{2}[0-9]{4}', sys.argv[1]):
        print make_serial(sys.argv[1])
        print "[ERROR] Invalid format ([a-z]{2}[0-9]{4})"

Usage examples

$ ./ aldeid
[ERROR] Invalid format ([a-z]{2}[0-9]{4})
$ ./ gJ2345
[ERROR] Invalid format ([a-z]{2}[0-9]{4})
$ ./ gj7400



Keywords: assembly reverse-engineering crackme vik3790