Windows Password, geygen, password reverseme

Sat for an evenin’ o’ fun this holiday season.  I like these easy ones.  Last month I tried a harder one and found it discouraging.  I don’t have the sort of time to work on these for a full day, so these couple hour ones are a lot more fun to me at this point.

I found the binary on crackmes.de, here is a mirror.

Part 1: Find the Password.

Really easy.  Even me a beginner figured this out in about half an hour.  First thing it does is make sure the input is 8 characters long.  If it’s not it exits the program.

004013B7  |. 890424         MOV DWORD PTR SS:[ESP],EAX               ; |
004013BA  |. E8 B1060000    CALL <JMP.&msvcrt.strlen>                ; strlen
004013BF  |. 83F8 08        CMP EAX,8
004013C2  |. 74 05          JE SHORT CrackMe#.004013C9               ;  if strlen(pass) == 8 (if it's not, 'something went wrong')
004013C4  |. E9 2C010000    JMP CrackMe#.004014F5

Next it goes through a little loop which compares each letter of the password you entered with the real password

004013C9  |> C745 F4 000000>MOV DWORD PTR SS:[EBP-C],0
004013D0  |> 837D F4 07     /CMP DWORD PTR SS:[EBP-C],7              ;  looks like for (i =0; i<7; i++) probably
004013D4  |. 7F 20          |JG SHORT CrackMe#.004013F6
004013D6  |. 8D45 F8        |LEA EAX,DWORD PTR SS:[EBP-8]
004013D9  |. 0345 F4        |ADD EAX,DWORD PTR SS:[EBP-C]
004013DC  |. 8D50 E0        |LEA EDX,DWORD PTR DS:[EAX-20]           ;  edx = ptr(ebp - 8) + ptr(ebp - c)
004013DF  |. 8D45 F8        |LEA EAX,DWORD PTR SS:[EBP-8]
004013E2  |. 0345 F4        |ADD EAX,DWORD PTR SS:[EBP-C]
004013E5  |. 83E8 20        |SUB EAX,20                              ;  eax = edx - 20
004013E8  |. 0FB600         |MOVZX EAX,BYTE PTR DS:[EAX]
004013EB  |. FEC0           |INC AL
004013ED  |. 8802           |MOV BYTE PTR DS:[EDX],AL
004013EF  |. 8D45 F4        |LEA EAX,DWORD PTR SS:[EBP-C]
004013F2  |. FF00           |INC DWORD PTR DS:[EAX]
004013F4  |.^EB DA          \JMP SHORT CrackMe#.004013D0
004013F6  |> 8D45 C8        LEA EAX,DWORD PTR SS:[EBP-38]            ; ||||||||
004013F9  |. 8D55 D8        LEA EDX,DWORD PTR SS:[EBP-28]            ; ||||||||
004013FC  |. 894424 04      MOV DWORD PTR SS:[ESP+4],EAX             ; ||||||||
00401400  |. 891424         MOV DWORD PTR SS:[ESP],EDX               ; ||||||||
00401403  |. E8 58060000    CALL <JMP.&msvcrt.strcmp>                ; |||||||\strcmp
00401408  |. 85C0           TEST EAX,EAX                             ; |||||||if this doesn't = 0 exit 'something went wrong'
0040140A  |. 0F85 E5000000  JNZ CrackMe#.004014F5                    ; |||||||
00401410  |. C70424 FE31400>MOV DWORD PTR SS:[ESP],CrackMe#.004031FE ; |||||||ASCII 0A,"Stage 1 co"

If you look closely it compares every letter + 1 to QbTTx1sE.  Meaning of course that the password is simply: PaSSw0rD

Part 2: KeyGen

The next part was a little more tricky for me.

On the screen you enter a name and a corresponding serial.

STAGE 2
00401423  |. E8 68060000    CALL <JMP.&msvcrt.printf>                ; |||||\printf
00401428  |. C70424 D331400>MOV DWORD PTR SS:[ESP],CrackMe#.004031D3 ; |||||ASCII "*******
0040142F  |. E8 5C060000    CALL <JMP.&msvcrt.printf>                ; ||||\printf
00401434  |. C70424 1E32400>MOV DWORD PTR SS:[ESP],CrackMe#.0040321E ; ||||ASCII 0A,"Name [2<=c"
0040143B  |. E8 50060000    CALL <JMP.&msvcrt.printf>                ; |||\printf
00401440  |. 8D45 B8        LEA EAX,DWORD PTR SS:[EBP-48]            ; |||
00401443  |. 894424 04      MOV DWORD PTR SS:[ESP+4],EAX             ; |||ebp -48 or esp +4 = name
00401447  |. C70424 FB31400>MOV DWORD PTR SS:[ESP],CrackMe#.004031FB ; |||ASCII "%s"
0040144E  |. E8 2D060000    CALL <JMP.&msvcrt.scanf>                 ; ||\scanf
00401453  |. C70424 3632400>MOV DWORD PTR SS:[ESP],CrackMe#.00403236 ; ||ASCII "Serial : "
0040145A  |. E8 31060000    CALL <JMP.&msvcrt.printf>                ; |\printf
0040145F  |. 8D45 B0        LEA EAX,DWORD PTR SS:[EBP-50]            ; |
00401462  |. 894424 04      MOV DWORD PTR SS:[ESP+4],EAX             ; |ebp -50 = serial
00401466  |. C70424 4132400>MOV DWORD PTR SS:[ESP],CrackMe#.00403241 ; |ASCII "%d"
0040146D  |. E8 0E060000    CALL <JMP.&msvcrt.scanf>                 ; \scanf
00401472  |. C745 F4 000000>MOV DWORD PTR SS:[EBP-C],0
00401479  |. C745 F4 000000>MOV DWORD PTR SS:[EBP-C],0
00401480  |> 8D45 B8        /LEA EAX,DWORD PTR SS:[EBP-48]           ; |
00401483  |. 890424         |MOV DWORD PTR SS:[ESP],EAX              ; |strlen (name)
00401486  |. E8 E5050000    |CALL <JMP.&msvcrt.strlen>               ; \strlen
0040148B  |. 3945 F4        |CMP DWORD PTR SS:[EBP-C],EAX            ;  while loop (for i = len(name)
0040148E  |. 77 1A          |JA SHORT CrackMe#.004014AA
00401490  |. 8D45 F8        |LEA EAX,DWORD PTR SS:[EBP-8]
00401493  |. 0345 F4        |ADD EAX,DWORD PTR SS:[EBP-C]
00401496  |. 83E8 40        |SUB EAX,40
00401499  |. 0FBE00         |MOVSX EAX,BYTE PTR DS:[EAX]
0040149C  |. 0345 B4        |ADD EAX,DWORD PTR SS:[EBP-4C]
0040149F  |. 48             |DEC EAX
004014A0  |. 8945 B4        |MOV DWORD PTR SS:[EBP-4C],EAX
004014A3  |. 8D45 F4        |LEA EAX,DWORD PTR SS:[EBP-C]
004014A6  |. FF00           |INC DWORD PTR DS:[EAX]                  ;  pointer to 0,1,...,length
004014A8  |.^EB D6          \JMP SHORT CrackMe#.00401480
004014AA  |> 8B45 B4        MOV EAX,DWORD PTR SS:[EBP-4C]            ; |||||eax calculated in loop, compared with serial
004014AD  |. 3B45 B0        CMP EAX,DWORD PTR SS:[EBP-50]            ; |||||
004014B0  |. 75 43          JNZ SHORT CrackMe#.004014F5              ; |||||
004014B2  |. C70424 4432400>MOV DWORD PTR SS:[ESP],CrackMe#.00403244 ; |||||ASCII 0A,"Stage 2 Co"

Although it can be a bit hard to read because of all the pointers (at least for me), it is definitely possible to discern the for loop and figure out it’s computing some value based on the name and writing to the same space (ebp – 4c) at every iteration.  This value is then compared to the serial to determine if the serial is valid or not.  After setting some breakpoints it becomes clear that  every value of the name -1 is added together and compared to the serial.  Here is a working keygen.

#include <iostream>
#include <string>

using namespace std;

int main()
{
    string name = "";
    cout << "name: ";
    cin >> name;
    int total = 0;
    for(int i=0; i<name.length(); i++)
    {
            total += name[i];
    }
    total -= (name.length() + 1);
    cout << "Serial should be " << total << endl;

    system("PAUSE");
    return 0;
}

Part 3

This part was theoretically easy, but a bit hard for me to execute.  The goal is to remove a console nag.

004014DD  |. E8 AE050000    CALL <JMP.&msvcrt.printf>                ; |\printf

That line needs to turn to a nop.  In ollydbg just right click as say fill with nop, and it should be good.

Reverseme – very very easy Linux

This would probably be a good place to start if you’ve never reversed anything before.  Too easy for my taste though, and I’m just a beginner.

To try it, see http://crackmes.de/users/cyrex/linux_crackme/

With an objdump, you can see the strcmp is called right after scanf asks for the password.

The password is hard coded.  It is pushed from 0x80486a3.  Revealing what it is is as simple as starting up gdb and typing x/s 0x80486a4.  It is also revealed with the strings command or a hexdump.

Reverseme – Trivial Linux

This one is pretty darn easy. It’s written in assembly, it’s very small.

You can download the executable at this link:  crackme2

The tricky part on this one was all the weird assembly instruction which I’m not used to. For example, there’s no printf, it’s done with system calls. Anyway, witha debugger it was really easy to figure out what the password is, because it loads it into memory right before it compares it to what you entered. I basically used the same tools as in this one.

Anyway, here’s a disassembly with notes:

Disassembly of section .text:

08048080 <.text>:
8048080: b8 04 00 00 00 mov $0x4,%eax
8048085: bb 01 00 00 00 mov $0x1,%ebx
804808a: b9 f8 90 04 08 mov $0x80490f8,%ecx ; password
804808f: ba 0d 00 00 00 mov $0xd,%edx
8048094: cd 80 int $0x80 ;print it out probably
8048096: ba 00 01 00 00 mov $0x100,%edx
804809b: b9 1b 91 04 08 mov $0x804911b,%ecx
80480a0: bb 00 00 00 00 mov $0x0,%ebx
80480a5: b8 03 00 00 00 mov $0x3,%eax
80480aa: cd 80 int $0x80 ;input the junk probably
80480ac: be 26 91 04 08 mov $0x8049126,%esi ;”QTBXCTU”
80480b1: 89 f7 mov %esi,%edi
80480b3: 31 db xor %ebx,%ebx
80480b5: fc cld ;clear the direction flag
#break
80480b6: ac lods %ds:(%esi),%al ;load string, 81 to al
80480b7: 34 21 xor $0x21,%al
80480b9: aa stos %al,%es:(%edi) ; string store
80480ba: 43 inc %ebx
80480bb: 81 fb 07 00 00 00 cmp $0x7,%ebx
#while ebx!=7
80480c1: 74 02 je 0x80480c5
80480c3: e2 f1 loop 0x80480b6
#break 7
80480c5: be 1b 91 04 08 mov $0x804911b,%esi ;user pass
80480ca: bf 26 91 04 08 mov $0x8049126,%edi ;pucybut
80480cf: b9 07 00 00 00 mov $0x7,%ecx
80480d4: fc cld
80480d5: f3 a6 repz cmpsb %es:(%edi),%ds:(%esi)
#break 8
80480d7: 75 16 jne 0x80480ef
80480d9: b8 04 00 00 00 mov $0x4,%eax
80480de: bb 01 00 00 00 mov $0x1,%ebx
80480e3: b9 05 91 04 08 mov $0x8049105,%ecx
80480e8: ba 16 00 00 00 mov $0x16,%edx
80480ed: cd 80 int $0x80
80480ef: b8 01 00 00 00 mov $0x1,%eax
80480f4: cd 80 int $0x80

I didn’t read through most of this very carefully. The important stuff is here:
80480c5: be 1b 91 04 08 mov $0x804911b,%esi ;user pass
80480ca: bf 26 91 04 08 mov $0x8049126,%edi ;pucybut
80480cf: b9 07 00 00 00 mov $0x7,%ecx
80480d4: fc cld
80480d5: f3 a6 repz cmpsb %es:(%edi),%ds:(%esi)

In gdb, you can see this by going x 0x8049126, which contains pucybut.

Readelf – a sexy little elf

Problem

From crackmes.de

  • Get a working key/keygen.
  • Allowed are only GPLed-tools.
  • Patching/Hijacking prohibited ;)

You can get the executable at http://crackmes.de/users/lagalopex/cm1/ or I’ve mirrored it here.

Now, I know what you’re thinking, but no cheating and looking at the solutions without giving it a whirl or I WILL murder ball you. You DONT want to find out what that means.

objdump

objdump is pretty limited, but it does what you need. Fine for dissasembly.

$objdump -D ./cm1 > textfileiusetomakenotes

If you’re hard core you’ll only use objdump.

gdb

gdb is a debugger. You also could just use this as a disassembler since it disassembles, but that’s just not the way I roll.

Some useful commands off the top of my head:

-break <memlocation> – set breakpoints throughout your program
-run/continue – ironically these actually mean walk/stop
-disassemble <memlocation> – disasemble the code at a memory location or label
-info registers – display the values in most the registers
-x *0x1234567 – display the contents of what’s at 0x1234567. Also related is x $eax which will display eax. Useful because you can do things like x $ebp+8 to view offsets of different registers. Another thing you can do is specify a data type, like for ints it would be x/i.

Assembly Notes

Now I have written a bit of code in assembly before, but reversing is a whole new ballgame.

Some things that took me awhile to wrap my mind around:

-Interleaving instructions are everywhere, although it makes sense when considering an ia36 processor
-Weird (to me) optimizations. Some ones that are extremely common ones I noticed are:
-xor %eax, %eax (set eax to 0) this is apparently faster than mov 0x0,%eax
-The only difference between cmp and sub is that sub stores a value. They both can set the ZF
-test %eax,%eax is just short for see if eax is 0
-It’s easier to try to guess at conditionals than it is to follow the gotos

Solution

Where to begin?

cm1 is looking at a file called /home/username/.key_username for a valid key. A valid key is ascii text that has the following properties:

esi must equal the ascii value of the current letter in the username, username[i]
esi is modified by the following symbols

* means esi+=5
/ means esi-=5
– means esi–
+ means esi++

In the keyfile, you will have a series of these symbols to make esi equal to username[i], then a comma, then username[i]. Some caveats are:

a symbol repeated > 5 times makes the key invalid. ie ***** is not allowed.
after every iteration, esi = esi/username[i]

So a valid key for a user lundeen is (with no white space):

****+-****+-****+-****+-****+-*+++,l****+-****+-****+-****+
-****+-*++,u****+-****+-****+-****+-***++++,n****+-****+-
****+-****+-*++++,d****+-****+-****+-****+-**+,e****+-****+-
****+-****+-**+,e****+-****+-****+-****+-***+++++,n

Easy as pie.

Here is my disassembly with the notes I made as I figured out these facts. My comments are hashed #

080484d4 <main>:
80484d4: 8d 4c 24 04 lea 0x4(%esp),%ecx
80484d8: 83 e4 f0 and $0xfffffff0,%esp
80484db: ff 71 fc pushl 0xfffffffc(%ecx)
80484de: 55 push %ebp
80484df: 89 e5 mov %esp,%ebp
80484e1: 57 push %edi
80484e2: 56 push %esi
80484e3: 53 push %ebx
80484e4: 51 push %ecx
80484e5: 81 ec 28 10 00 00 sub $0x1028,%esp

#Get the username, return to %eax
80484eb: e8 f0 fe ff ff call 80483e0 <getuid@plt>
80484f0: 83 ec 0c sub $0xc,%esp

#call getpwuid with %eax as an arg returns a struct <password>
80484f3: 50 push %eax
80484f4: e8 a7 fe ff ff call 80483a0 <getpwuid@plt>

80484f9: 89 85 d8 ef ff ff mov %eax,0xffffefd8(%ebp)

80484ff: 5e pop %esi
8048500: 5f pop %edi

#push what %eax is pointing to, the username
8048501: ff 30 pushl (%eax)
8048503: 31 f6 xor %esi,%esi

#Hello, %s, let’s seen what you’ve done so far
8048505: 68 50 87 04 08 push $0x8048750
804850a: e8 c1 fe ff ff call 80483d0 <printf@plt>

804850f: 8b 85 d8 ef ff ff mov 0xffffefd8(%ebp),%eax
8048515: 5b pop %ebx

#%ebx is 0x8048750

8048516: 8d 9d ee ef ff ff lea 0xffffefee(%ebp),%ebx

#%ebx is now 0xbfdcc6a6 or which is a memory address that contains a “”
#This is a negative number
#below, ebx is sort of an iterator. It’s initially set to 1 below

804851c: ff 30 pushl (%eax); the username?
804851e: ff 70 14 pushl 0x14(%eax); working directory
8048521: 68 7f 87 04 08 push $0x804877f ; %s/.key_%s
8048526: 68 01 10 00 00 push $0x1001 ;1001
804852b: 53 push %ebx


#snprintf(*str (ebx), 1001(size),/home/lundeen/.key_lundeen)
804852c: e8 bf fe ff ff call 80483f0 <snprintf@plt>

8048531: 83 c4 18 add $0x18,%esp
8048534: 6a 00 push $0x0
8048536: 53 push %ebx
8048537: e8 c4 fe ff ff call 8048400 <open@plt>

#open(/home/lundeen/.key_lundeen,0)
#the 0 flag indicates read only
#the open function returns a -1 if an error occurs
804853c: 83 c4 10 add $0x10,%esp

#if there is no file or permissions are not set, this cmp is true
804853f: 83 f8 ff cmp $0xffffffff,%eax

8048542: 89 c7 mov %eax,%edi
8048544: ba 01 00 00 00 mov $0x1,%edx
8048549: c6 85 d3 ef ff ff 20 movb $0x20,0xffffefd3(%ebp)
8048550: c7 85 d4 ef ff ff 00 movl $0x0,0xffffefd4(%ebp)
8048557: 00 00 00

#if -1 != eax meaning if there is no error, jump to 804863c
804855a: 0f 85 dc 00 00 00 jne 804863c <main+0x168>
#else jump to 8048682
8048560: e9 1d 01 00 00 jmp 8048682 <main+0x1ae>

—->jmp if there is more than the end of file in the file
#From below, if there is something in the file, jump to here
#eax should be 0 to begin with

#move the last character on the file to %dl then the eax
8048565: 8a 55 ef mov 0xffffffef(%ebp),%dl
8048568: 89 d0 mov %edx,%eax
804856a: 88 95 df ef ff ff mov %dl,0xffffefdf(%ebp)
8048570: 83 e8 2a sub $0x2a,%eax

8048573: 3c 05 cmp $0x5,%al

#ja is jump if greater than (unsigned)
#804867d is the end of the program
#if(eax is * + , – . or /)
8048575: 0f 87 02 01 00 00 ja 804867d <main+0x1a9>
804857b: 0f b6 c0 movzbl %al,%eax
#this jump is hit
#%eax is * + , – . / because ja is unsighned
#jum at the 0x0487a4 + eax*4
#x/x6 *0x80487a4 =
#0x80487a4: (*)0x0804858f (+)0x08048585 (,)0x080485bc (-)0x0804859b
#0x80487b4: (.)0x0804867d (/)0x080485a5

#esi seems to be given an initial value of 0
804857e: ff 24 85 a4 87 04 08 jmp *0x80487a4(,%eax,4)
#if the char is +
#esi++
8048585: 46 inc %esi
#compare to the previous byte read 0xfffefd3
8048586: 80 bd d3 ef ff ff 2b cmpb $0x2b,0xffffefd3(%ebp)
804858d: eb 20 jmp 80485af <main+0xdb>

#if the char is *
#esi +=5
804858f: 83 c6 05 add $0x5,%esi
8048592: 80 bd d3 ef ff ff 2a cmpb $0x2a,0xffffefd3(%ebp)
8048599: eb 14 jmp 80485af <main+0xdb>

#if the char is –
#esi–
804859b: 4e dec %esi
804859c: 80 bd d3 ef ff ff 2d cmpb $0x2d,0xffffefd3(%ebp)
80485a3: eb 0a jmp 80485af <main+0xdb>

#if the char is /
#esi -=5
80485a5: 83 ee 05 sub $0x5,%esi
80485a8: 80 bd d3 ef ff ff 2f cmpb $0x2f,0xffffefd3(%ebp)

–>all punctuation ends up here

#if the value above is not the same as the previous byte?
#if it’s not the same jump to the start of the loop below
#AND set ebx (the counter) back to 1. This means you can’t have > 5
#of the same thing in a row
80485af: 75 7a jne 804862b <main+0x157>

80485b1: 43 inc %ebx
80485b2: 83 fb 05 cmp $0x5,%ebx

#if 5 >= ebx (ebx is incremented above and set to 1 below)
#looks like this reads another char and repeats
80485b5: 76 79 jbe 8048630 <main+0x15c>
#else if 5<ebx
#then exit
80485b7: e9 c1 00 00 00 jmp 804867d <main+0x1a9>

#if the char is a comma, we’re done manipulating edi
#break 3
80485bc: 51 push %ecx
80485bd: 8d 4d ef lea 0xffffffef(%ebp),%ecx
80485c0: 6a 01 push $0x1
80485c2: 51 push %ecx
80485c3: 57 push %edi
#read another byte from the file
80485c4: e8 47 fe ff ff call 8048410 <read@plt>
80485c9: 83 c4 10 add $0x10,%esp
80485cc: 48 dec %eax
#eax is now equal to zero if it’s not the end of the file
#if it is the end of the file then end the program
80485cd: 0f 85 aa 00 00 00 jne 804867d <main+0x1a9>

#eax should be the last char just read
80485d3: 0f b6 45 ef movzbl 0xffffffef(%ebp),%eax

#if esi is == to char just read (else exit)
80485d7: 39 f0 cmp %esi,%eax
80485d9: 0f 85 9e 00 00 00 jne 804867d <main+0x1a9>
#eax and ecx have something to do with getpuid(), probably
#pointers to the username
80485df: 8b 85 d8 ef ff ff mov 0xffffefd8(%ebp),%eax
80485e5: 8b 8d d4 ef ff ff mov 0xffffefd4(%ebp),%ecx
80485eb: 8b 10 mov (%eax),%edx
80485ed: 0f be 04 0a movsbl (%edx,%ecx,1),%eax

#if eax isequal to esi else exit
#eax is the current username letter
80485f1: 39 c6 cmp %eax,%esi
80485f3: 0f 85 84 00 00 00 jne 804867d <main+0x1a9>

80485f9: 41 inc %ecx
80485fa: 89 8d d4 ef ff ff mov %ecx,0xffffefd4(%ebp)

#if there are no more letters in the username
8048600: 80 3c 0a 00 cmpb $0x0,(%edx,%ecx,1)
8048604: 75 14 jne 804861a <main+0x146>
8048606: 52 push %edx
8048607: 8d 45 ef lea 0xffffffef(%ebp),%eax
804860a: 6a 01 push $0x1
804860c: 50 push %eax
804860d: 57 push %edi
804860e: e8 fd fd ff ff call 8048410 <read@plt>
#break 5
8048613: 83 c4 10 add $0x10,%esp
#break 4
8048616: 85 c0 test %eax,%eax
#break 2
#if eax is not equal to zero then end program
#if there are no more stuff in the file
8048618: 75 63 jne 804867d <main+0x1a9>

#else
804861a: ba 0a 00 00 00 mov $0xa,%edx
#break 3
804861f: 89 f0 mov %esi,%eax
8048621: 89 d1 mov %edx,%ecx
8048623: 31 d2 xor %edx,%edx
8048625: f7 f1 div %ecx
8048627: 89 c6 mov %eax,%esi
#edx ->0; ecx-> 10; eax ->esi
#esi = esi/10
8048629: eb 05 jmp 8048630 <main+0x15c>
#this may needs to be hit at least once every five times so the loop can repeat
804862b: bb 01 00 00 00 mov $0x1,%ebx
8048630: 8a 85 df ef ff ff mov 0xffffefdf(%ebp),%al
8048636: 88 85 d3 ef ff ff mov %al,0xffffefd3(%ebp)

—->
#jump here if the file exists
804863c: 50 push %eax
804863d: 8d 45 ef lea 0xffffffef(%ebp),%eax
#%eax is 0xbfdcd6a7 which is the buffer where the previous byte will be stored

#the read function
8048640: 6a 01 push $0x1
8048642: 50 push %eax

#eax is 0xbfa20767
8048643: 57 push %edi
8048644: e8 c7 fd ff ff call 8048410 <read@plt>
#read(file discriptor(6),0xbfa20767 or whatever(buffer),size(1 byte))
#number of bytes read is returned, 0 indicates eof

#eax is now the size of the number of bytes returned
#the value read is stored in (hopefully 0xbfdcd6a7)

8048649: 83 c4 10 add $0x10,%esp
804864c: 48 dec %eax
#numbytes read–, it should probably be zero at this point, and ZF is set

#if the file was empty or an eof marker was received continue
#else je is true, so jump on up
804864d: 0f 84 12 ff ff ff je 8048565 <main+0x91>

8048653: 8b 95 d8 ef ff ff mov 0xffffefd8(%ebp),%edx
8048659: 8b 8d d4 ef ff ff mov 0xffffefd4(%ebp),%ecx
804865f: 8b 02 mov (%edx),%eax
8048661: 31 d2 xor %edx,%edx
8048663: 80 3c 08 00 cmpb $0x0,(%eax,%ecx,1)
8048667: 75 19 jne 8048682 <main+0x1ae>
8048669: 83 ec 0c sub $0xc,%esp
804866c: 68 8a 87 04 08 push $0x804878a
8048671: e8 3a fd ff ff call 80483b0 <puts@plt>
8048676: 31 d2 xor %edx,%edx
8048678: 83 c4 10 add $0x10,%esp
804867b: eb 05 jmp 8048682 <main+0x1ae>
804867d: ba 02 00 00 00 mov $0x2,%edx

8048682: 8d 65 f0 lea 0xfffffff0(%ebp),%esp
8048685: 89 d0 mov %edx,%eax
8048687: 59 pop %ecx
8048688: 5b pop %ebx
8048689: 5e pop %esi
804868a: 5f pop %edi
804868b: c9 leave
804868c: 8d 61 fc lea 0xfffffffc(%ecx),%esp
804868f: c3 ret

After figuring out how it works, it really is easy to make a keygen. I wrote this in C++

#include <fstream>
#include <iostream>
#include <string>
using namespace std;

#include <unistd.h>
#include <sys/types.h>
#include <pwd.h>


int main()
{
string username = getlogin();
cout<<username<<endl;
string key = “”;

int esi = 0;
for(int i=0; i<username.length(); i++)
{
int num = 0;
num += esi;
for(;num < username[i]-20; num+=20){
key += “****+-“;}
for(;num < username[i]-5; num+=5){
key += “*”;}
for(;num < username[i]; num++){
key += “+”;}

key = key + “,” + username[i];

esi = username[i]/10;
}

string writefile = “/home/” + username + “/.key_” + username;
//cout<<writefile<<endl;
ofstream fout;
fout.open(writefile.c_str());
fout<<key;
fout.close();

return 0;
}

Conclusions

This was a lot of fun! I think I’ll try to do one of these from crackmes.de more often. A little hard though. This was rated at 1 which is the easiest, and I think 1 should be easier. cm1 definitely took me a long time to figure out. I’d say this is more like a 3 (the ranking is out of 10).