a_k_47's PwnMe Tutorial

July 29, 2015 - Reading time: 13 minutes

Target: a_k_47's PwnMe
URLhttp://crackmes.de/users/a_k_47/pwnme/
Protection: Keyfile
Description: Crackme with a keyfile protection
Tools: Immunity Debugger

Load the crackme in Immunity Debugger and start stepping. After a while we end up here:

0040369D  |. F3:AB          REP STOS DWORD PTR ES:[EDI]              ; |
0040369F  |. C78424 2C01000>MOV DWORD PTR SS:[ESP+12C],0             ; |
004036AA  |. 837D 08 02     CMP DWORD PTR SS:[EBP+8],2               ; | [EBP+8] = 1
004036AE  |. 74 16          JE SHORT pwnme.004036C6                  ; |
004036B0  |. C70424 9480400>MOV DWORD PTR SS:[ESP],pwnme.00408094    ; |ASCII "Gimme something!"
004036B7  |. E8 9C2D0000    CALL <JMP.&msvcrt.puts>                  ; \puts
004036BC  |. B8 00000000    MOV EAX,0
004036C1  |. E9 07020000    JMP pwnme.004038CD                       ; Jump to end of routine.
004036C6  |> 8B45 0C        MOV EAX,DWORD PTR SS:[EBP+C]             ; ||
004036C9  |. 83C0 04        ADD EAX,4                                ; ||
004036CC  |. 8B00           MOV EAX,DWORD PTR DS:[EAX]               ; ||
004036CE  |. C74424 04 A580>MOV DWORD PTR SS:[ESP+4],pwnme.004080A5  ; ||
004036D6  |. 890424         MOV DWORD PTR SS:[ESP],EAX               ; ||
004036D9  |. E8 822D0000    CALL <JMP.&msvcrt.fopen>                 ; |\fopen
004036DE  |. 898424 2801000>MOV DWORD PTR SS:[ESP+128],EAX           ; |
004036E5  |. 83BC24 2801000>CMP DWORD PTR SS:[ESP+128],0             ; |
004036ED  |. 75 16          JNZ SHORT pwnme.00403705                 ; |
004036EF  |. C70424 A780400>MOV DWORD PTR SS:[ESP],pwnme.004080A7    ; |ASCII "No key :("
004036F6  |. E8 5D2D0000    CALL <JMP.&msvcrt.puts>                  ; \puts
004036FB  |. B8 00000000    MOV EAX,0
00403700  |. E9 C8010000    JMP pwnme.004038CD                       ; Jump to end of routine.

It seems that the keyfile needed is supplied as an argument, create an empty text file and pass it as an argument.
Great! [EBP+8] = 2 and the fopen call succeeded. Lets move on. If we step on until we get a new message we end up here:

0040386E  |> 83BC24 2C01000>CMP DWORD PTR SS:[ESP+12C],1             ; |
00403876  |. 75 13          JNZ SHORT pwnme.0040388B                 ; |
00403878  |. C70424 F080400>MOV DWORD PTR SS:[ESP],pwnme.004080F0    ; |ASCII "Awesome, your key is valid!"
0040387F  |. E8 D42B0000    CALL <JMP.&msvcrt.puts>                  ; \puts
00403884  |. E8 6C030000    CALL pwnme.00403BF5
00403889  |. EB 2E          JMP SHORT pwnme.004038B9
0040388B  |> 83BC24 2C01000>CMP DWORD PTR SS:[ESP+12C],-1            ; |
00403893  |. 75 13          JNZ SHORT pwnme.004038A8                 ; |
00403895  |. C70424 0C81400>MOV DWORD PTR SS:[ESP],pwnme.0040810C    ; |ASCII "Invalid key"
0040389C  |. E8 B72B0000    CALL <JMP.&msvcrt.puts>                  ; \puts
004038A1  |. E8 FD040000    CALL pwnme.00403DA3
004038A6  |. EB 11          JMP SHORT pwnme.004038B9
004038A8  |> C70424 1881400>MOV DWORD PTR SS:[ESP],pwnme.00408118    ; |ASCII "Incomplete key"
004038AF  |. E8 A42B0000    CALL <JMP.&msvcrt.puts>                  ; \puts
004038B4  |. E8 EA040000    CALL pwnme.00403DA3
004038B9  |> 8B8424 2801000>MOV EAX,DWORD PTR SS:[ESP+128]           ; |
004038C0  |. 890424         MOV DWORD PTR SS:[ESP],EAX               ; |
004038C3  |. E8 B02B0000    CALL <JMP.&msvcrt.fclose>                ; \fclose
004038C8  |. B8 00000000    MOV EAX,0
004038CD  |> 8D65 F8        LEA ESP,DWORD PTR SS:[EBP-8]
004038D0  |. 5B             POP EBX
004038D1  |. 5F             POP EDI
004038D2  |. 5D             POP EBP
004038D3  \. C3             RETN

So [ESP+12C] should be 1 if our key is correct, but we got 0 so we end up at the "Incomplete key"-message.
Lets enter something in our keyfile and restart so we can step into some calls.
If we step on we finally get to this point where a string of the keyfile is read, if the fgets-call succeeds we reach the call at 00403804.

004037D8  |> 8B8424 2801000>|MOV EAX,DWORD PTR SS:[ESP+128]          ; |
004037DF  |. 894424 08      |MOV DWORD PTR SS:[ESP+8],EAX            ; |
004037E3  |. C74424 04 FF00>|MOV DWORD PTR SS:[ESP+4],0FF            ; |
004037EB  |. 8D4424 1C      |LEA EAX,DWORD PTR SS:[ESP+1C]           ; |
004037EF  |. 890424         |MOV DWORD PTR SS:[ESP],EAX              ; |
004037F2  |. E8 792C0000    |CALL <JMP.&msvcrt.fgets>                ; \fgets
004037F7  |. 85C0           |TEST EAX,EAX
004037F9  |. 75 02          |JNZ SHORT pwnme.004037FD                ; jump if fgets-call succeeded
004037FB  |. EB 71          |JMP SHORT pwnme.0040386E                ; else jump to check key part
004037FD  |> 8D4424 1C      |LEA EAX,DWORD PTR SS:[ESP+1C]
00403801  |. 890424         |MOV DWORD PTR SS:[ESP],EAX
00403804  |. E8 48010000    |CALL pwnme.00403951                     ; interesting call
00403809  |. 898424 1C01000>|MOV DWORD PTR SS:[ESP+11C],EAX
00403810  |. 83BC24 1C01000>|CMP DWORD PTR SS:[ESP+11C],-1
00403818  |. 74 1A          |JE SHORT pwnme.00403834

When we step into this call we'll see this routine:

00403951  /$ 55             PUSH EBP
00403952  |. 89E5           MOV EBP,ESP
00403954  |. 83EC 28        SUB ESP,28
00403957  |. 8B45 08        MOV EAX,DWORD PTR SS:[EBP+8]
0040395A  |. 0FB600         MOVZX EAX,BYTE PTR DS:[EAX]              ; EAX = first character of the read string
0040395D  |. 3C 60          CMP AL,60                                ; if al <= 0x60 ("`")
0040395F  |. 7E 58          JLE SHORT pwnme.004039B9                 ; jump to "Bad input" 
00403961  |. 8B45 08        MOV EAX,DWORD PTR SS:[EBP+8]
00403964  |. 0FB600         MOVZX EAX,BYTE PTR DS:[EAX]              ; EAX = first character of the read string
00403967  |. 3C 68          CMP AL,68                                ; if al > 0x68 ("h")
00403969  |. 7F 4E          JG SHORT pwnme.004039B9                  ; jump to "Bad input"
0040396B  |. 8B45 08        MOV EAX,DWORD PTR SS:[EBP+8]
0040396E  |. 83C0 01        ADD EAX,1
00403971  |. 0FB600         MOVZX EAX,BYTE PTR DS:[EAX]              ; EAX = second character of the read string
00403974  |. 3C 2F          CMP AL,2F                                ; if al <= 0x2F ("/")
00403976  |. 7E 41          JLE SHORT pwnme.004039B9                 ; jump to "Bad input"
00403978  |. 8B45 08        MOV EAX,DWORD PTR SS:[EBP+8]
0040397B  |. 83C0 01        ADD EAX,1
0040397E  |. 0FB600         MOVZX EAX,BYTE PTR DS:[EAX]              ; EAX = second character of the read string
00403981  |. 3C 39          CMP AL,39                                ; if al > 0x39 ("9")
00403983  |. 7F 34          JG SHORT pwnme.004039B9                  ; jump to "Bad input"
00403985  |. 8B45 08        MOV EAX,DWORD PTR SS:[EBP+8]
00403988  |. 83C0 02        ADD EAX,2
0040398B  |. 0FB600         MOVZX EAX,BYTE PTR DS:[EAX]              ; EAX = third character of the read string
0040398E  |. 3C 60          CMP AL,60                                ; if al <= 0x60 ("`")
00403990  |. 7E 27          JLE SHORT pwnme.004039B9                 ; jump to "Bad input"
00403992  |. 8B45 08        MOV EAX,DWORD PTR SS:[EBP+8]
00403995  |. 83C0 02        ADD EAX,2
00403998  |. 0FB600         MOVZX EAX,BYTE PTR DS:[EAX]              ; EAX = third character of the read string
0040399B  |. 3C 68          CMP AL,68                                ; if al > 0x68 ("h")
0040399D  |. 7F 1A          JG SHORT pwnme.004039B9                  ; jump to "Bad input"
0040399F  |. 8B45 08        MOV EAX,DWORD PTR SS:[EBP+8]
004039A2  |. 83C0 03        ADD EAX,3
004039A5  |. 0FB600         MOVZX EAX,BYTE PTR DS:[EAX]              ; EAX = fourth character of the read string
004039A8  |. 3C 2F          CMP AL,2F                                ; if al <= 0x2F ("/")
004039AA  |. 7E 0D          JLE SHORT pwnme.004039B9                 ; jump to "Bad input"
004039AC  |. 8B45 08        MOV EAX,DWORD PTR SS:[EBP+8]
004039AF  |. 83C0 03        ADD EAX,3
004039B2  |. 0FB600         MOVZX EAX,BYTE PTR DS:[EAX]              ; EAX = fourth character of the read string
004039B5  |. 3C 39          CMP AL,39                                ; if al <= 0x39 ("9")
004039B7  |. 7E 16          JLE SHORT pwnme.004039CF                 ; jump over "Bad input"
004039B9  |> C70424 2781400>MOV DWORD PTR SS:[ESP],pwnme.00408127    ; |ASCII "Bad input"
004039C0  |. E8 932A0000    CALL <JMP.&msvcrt.puts>                  ; \puts
004039C5  |. B8 FFFFFFFF    MOV EAX,-1
004039CA  |. E9 F1000000    JMP pwnme.00403AC0
004039CF  |> 8B45 08        MOV EAX,DWORD PTR SS:[EBP+8]
004039D2  |. 0FB600         MOVZX EAX,BYTE PTR DS:[EAX]              ; EAX = first character of the read string
004039D5  |. 0FBEC0         MOVSX EAX,AL
004039D8  |. 83E8 61        SUB EAX,61                               ; EAX = EAX - 0x61
004039DB  |. 8945 F0        MOV DWORD PTR SS:[EBP-10],EAX            ; [EBP-10] = EAX
004039DE  |. 8B45 08        MOV EAX,DWORD PTR SS:[EBP+8]
004039E1  |. 83C0 01        ADD EAX,1
004039E4  |. 0FB600         MOVZX EAX,BYTE PTR DS:[EAX]              ; EAX = second character of the read string
004039E7  |. 0FBEC0         MOVSX EAX,AL
004039EA  |. BA 38000000    MOV EDX,38                               ; EDX = 0x38
004039EF  |. 29C2           SUB EDX,EAX                              ; EDX = EDX - EAX
004039F1  |. 89D0           MOV EAX,EDX                              ; EAX = EDX
004039F3  |. C1E0 03        SHL EAX,3                                ; EAX = EAX << 3
004039F6  |. 0145 F0        ADD DWORD PTR SS:[EBP-10],EAX            ; [EBP-10] = [EBP-10] + eax
004039F9  |. 8B45 08        MOV EAX,DWORD PTR SS:[EBP+8]
004039FC  |. 83C0 02        ADD EAX,2
004039FF  |. 0FB600         MOVZX EAX,BYTE PTR DS:[EAX]              ; EAX = third character of the read string
00403A02  |. 0FBEC0         MOVSX EAX,AL
00403A05  |. 83E8 61        SUB EAX,61                               ; EAX = EAX - 0x61
00403A08  |. 8945 EC        MOV DWORD PTR SS:[EBP-14],EAX            ; [EBP-14] = EAX
00403A0B  |. 8B45 08        MOV EAX,DWORD PTR SS:[EBP+8]
00403A0E  |. 83C0 03        ADD EAX,3
00403A11  |. 0FB600         MOVZX EAX,BYTE PTR DS:[EAX]              ; EAX = fourth character of the read string
00403A14  |. 0FBEC0         MOVSX EAX,AL
00403A17  |. BA 38000000    MOV EDX,38                               ; EDX = 0x38
00403A1C  |. 29C2           SUB EDX,EAX                              ; EDX = EDX - EAX
00403A1E  |. 89D0           MOV EAX,EDX                              ; EAX = EDX
00403A20  |. C1E0 03        SHL EAX,3                                ; EAX = EAX << 3
00403A23  |. 0145 EC        ADD DWORD PTR SS:[EBP-14],EAX            ; [EBP-14] = [EBP-14] + EAX
00403A26  |. C745 F4 000000>MOV DWORD PTR SS:[EBP-C],0
00403A2D  |. EB 7E          JMP SHORT pwnme.00403AAD
00403A2F  |> 8B45 F4        /MOV EAX,DWORD PTR SS:[EBP-C]
00403A32  |. 0FB604C5 C0264>|MOVZX EAX,BYTE PTR DS:[EAX*8+4126C0]    ; 0x30
00403A3A  |. 0FBEC0         |MOVSX EAX,AL
00403A3D  |. 3B45 F0        |CMP EAX,DWORD PTR SS:[EBP-10]           ; if [EBP-10] != 0x30
00403A40  |. 75 67          |JNZ SHORT pwnme.00403AA9                ; jump
00403A42  |. 8B45 F4        |MOV EAX,DWORD PTR SS:[EBP-C]
00403A45  |. 0FB604C5 C1264>|MOVZX EAX,BYTE PTR DS:[EAX*8+4126C1]    ; 0x28
00403A4D  |. 0FBEC0         |MOVSX EAX,AL
00403A50  |. 3B45 EC        |CMP EAX,DWORD PTR SS:[EBP-14]           ; if [EBP-14] != 0x28
00403A53  |. 75 54          |JNZ SHORT pwnme.00403AA9                ; jump
00403A55  |. 8B45 F4        |MOV EAX,DWORD PTR SS:[EBP-C]
00403A58  |. 0FB604C5 C3264>|MOVZX EAX,BYTE PTR DS:[EAX*8+4126C3]    ; 0x10
00403A60  |. 0FBEC0         |MOVSX EAX,AL
00403A63  |. 83E0 20        |AND EAX,20
00403A66  |. 85C0           |TEST EAX,EAX
00403A68  |. 74 3A          |JE SHORT pwnme.00403AA4
00403A6A  |. 8B45 08        |MOV EAX,DWORD PTR SS:[EBP+8]
00403A6D  |. 83C0 04        |ADD EAX,4
00403A70  |. 0FB600         |MOVZX EAX,BYTE PTR DS:[EAX]
00403A73  |. 0FBEC0         |MOVSX EAX,AL
00403A76  |. 83F8 4E        |CMP EAX,4E
00403A79  |. 74 0C          |JE SHORT pwnme.00403A87
00403A7B  |. 83F8 52        |CMP EAX,52
00403A7E  |. 74 14          |JE SHORT pwnme.00403A94
00403A80  |. 83F8 42        |CMP EAX,42
00403A83  |. 74 07          |JE SHORT pwnme.00403A8C
00403A85  |. EB 15          |JMP SHORT pwnme.00403A9C
00403A87  |> 8B45 F4        |MOV EAX,DWORD PTR SS:[EBP-C]
00403A8A  |. EB 34          |JMP SHORT pwnme.00403AC0
00403A8C  |> 8B45 F4        |MOV EAX,DWORD PTR SS:[EBP-C]
00403A8F  |. 83C0 01        |ADD EAX,1
00403A92  |. EB 2C          |JMP SHORT pwnme.00403AC0
00403A94  |> 8B45 F4        |MOV EAX,DWORD PTR SS:[EBP-C]
00403A97  |. 83C0 02        |ADD EAX,2
00403A9A  |. EB 24          |JMP SHORT pwnme.00403AC0
00403A9C  |> 8B45 F4        |MOV EAX,DWORD PTR SS:[EBP-C]
00403A9F  |. 83C0 03        |ADD EAX,3
00403AA2  |. EB 1C          |JMP SHORT pwnme.00403AC0
00403AA4  |> 8B45 F4        |MOV EAX,DWORD PTR SS:[EBP-C]
00403AA7  |. EB 17          |JMP SHORT pwnme.00403AC0
00403AA9  |> 8345 F4 01     |ADD DWORD PTR SS:[EBP-C],1
00403AAD  |> A1 24A14000     MOV EAX,DWORD PTR DS:[40A124]
00403AB2  |. 3B45 F4        |CMP EAX,DWORD PTR SS:[EBP-C]
00403AB5  |.^0F8F 74FFFFFF  \JG pwnme.00403A2F
00403ABB  |. B8 FFFFFFFF    MOV EAX,-1
00403AC0  |> C9             LEAVE
00403AC1  \. C3             RETN

So our keyfile should be four characters long, first char is a-h followed by 0-8, third char is a-h followed by 0-8.
And the pairs should also be equal to 0x30 and 0x28 respectively. So lets enter something that will work in our keyfile.
After restarting with the new keyfile and stepping through the check call we end up in a loop. We got our key-pair in the
EBX register but when we step over the call at 00403763 we see that EAX contains another key-pair. Interesting.
If we step on we eventually end up here:

004038A8  |> C70424 1881400>MOV DWORD PTR SS:[ESP],pwnme.00408118    ; |ASCII "Incomplete key"
004038AF  |. E8 A42B0000    CALL <JMP.&msvcrt.puts>                  ; \puts
004038B4  |. E8 EA040000    CALL pwnme.00403DA3
004038B9  |> 8B8424 2801000>MOV EAX,DWORD PTR SS:[ESP+128]           ; |
004038C0  |. 890424         MOV DWORD PTR SS:[ESP],EAX               ; |
004038C3  |. E8 B02B0000    CALL <JMP.&msvcrt.fclose>                ; \fclose

If we add another row to our keyfile and get back in the previous loop we can see the same key-pair for our first key-pair,
and a new key-pair for our second key-pair. The pairs seems to be chess moves. So lets try to play a game of chess.

After adding a couple of chess moves I ended up with this keyfile (there are probably a better and shorter way of beating this):

d2d4
c2c4
g1f3
d1a4
e2e4
g2g3
f3e5
f1c4
e1f2
c4f7

And running the crackme with this keyfile generates the valid key-message:

Awesome, your key is valid!

8 r . . q k b . r
7 p p p . p B p p
6 . . n . . . . .
5 . . . . N . . .
4 Q . . P . . . .
3 . . . . . . P .
2 P P . . . K . P
1 R N B . . . . b

a b c d e f g h