Solution and more. Crackme (Linux/C)

(pico) #1

So far, looks like only @dtm has provided a solution the my little challenger (not really a surprise :slight_smile:) . He also gave us a couple of hints on how to find a solution. I have already given a couple of hints as well in the comments but, in case you haven’t manage to solve it and you are wondering how the hell this is done, keep reading…

Let’s start with the binary with the symbols. This is the one I posted as a comment to the original crackme post and I will be referring to it as c2. I will name the original one (without symbols) c1.

Cracking the binary with symbols

The one with the symbols is easier, in the sense that debugging tools can parse some information in the file and provide a more human friendly view of the binary.

The output of objdump -d c2 will show us the different functions on the binary and will help us to understand what it does. Let’s dump the asm into a file and open it with your preferred editor.

$ objdump -d c1 > c1.dump
$ vim c1.dump

Now, we can look for the main function (press ESC + :/main + ENTER). You will see some calls to different functions. Namely: my_print, my_getpass, obfuscate and strncmp. This is an excerpt of the interesting part:

  400374:       bf 35 0b 40 00          mov    $0x400b35,%edi
  400379:       e8 44 ff ff ff          callq  4002c2 <my_print>
  40037e:       48 8b 0d 83 0c 20 00    mov    0x200c83(%rip),%rcx        # 601008 <pass>
  400385:       48 8b 45 f0             mov    -0x10(%rbp),%rax
  400389:       ba 20 00 00 00          mov    $0x20,%edx
  40038e:       48 89 ce                mov    %rcx,%rsi
  400391:       48 89 c7                mov    %rax,%rdi
  400394:       e8 c4 05 00 00          callq  40095d <strncmp>
  400399:       85 c0                   test   %eax,%eax
  40039b:       75 0c                   jne    4003a9 <main+0xb9>
  40039d:       bf 4b 0b 40 00          mov    $0x400b4b,%edi
  4003a2:       e8 1b ff ff ff          callq  4002c2 <my_print>

There is a first my_print function call, that is actually printing the message Checking password, and after that, there is a call to strncmp, a C function to compare two strings :o.

The combination of test and jne will make the program jump if the value returned by strncmp is not zero (the return value is in the register EAX). In this case, if we just remove the jne instruction we should get our OK message.

Patching the Binary like a PRO

You can patch the binary file using many different tools, I’m going to use a nifty trick I’ve recently learned and that I think is pretty hackish.

So, open the binary file in vim. Yes, that’s right. If vim is not your preferred text editor, what follows may change your mind.

Then type the following:

ESC + :%!xxd + ENTER

Pressing the ESC key instructs vim to go in the so-called command mode. The colon : will allow us to enter a command. The % is a wildcard meaning that, whatever command follows, has to be applied to the whole text. Finally the ! will run a shell command, passing as input the current vim buffer. The result of the shell command will be read back in vim, substituting the old data.

So, try that, and you will get an hexdump of the binary that you can edit within vim as a normal text file.

Now, back to our patching task, look for the instruction we where interested on: 0x75 0x0c (check the objdump output above).

For doing that type /75 0c to get to the instruction. You should see something like this

0000390: ce48 89c7 e8c4 0500 0085 c075 0cbf 4b0b  .H.........u..K.
00003a0: 4000 e81b ffff ffeb 0abf 5f0b 4000 e80f  @[email protected]

In the hexdump area change 75 and 0c to 90 90 (reverse conversion will not work if you change the ASCII dump on the right).

0000390: ce48 89c7 e8c4 0500 0085 c090 90bf 4b0b  .H.........u..K.
00003a0: 4000 e81b ffff ffeb 0abf 5f0b 4000 e80f  @[email protected]

The value 90 is the opcode for the nop instruction. That means that we are just removing the conditional jump and letting the program go on and execute the success part of the comparison. We are effectively removing the check.

Now we have to write the binary data back to the file. First convert back the hexdump into binary data using ESC + :%!xxd -r + ENTER. Then you just need to save and exit with the traditional :wq.

Try to run the program now!

Cracking the Binary without Symbols

For this one (the one in the original post), we need to work a little bit more… but not much. Now, an objdump, will not shown any information about functions or its name… Just one single assembly block. So, for this one we are going to use the string analysis method as suggested by @dtm.

He may had done it in a different way, but this is one way of doing it.

The idea is to locate the string containing the success message in the program, and then, figure out how the program ends up printing this string. In this case we are looking for the print and then we are moving back from there to find the condition that fires that print. So there we go!

First thing you have to know. String literals are read only data and, therefore, they get stored in the .rodata section (ro stands for read only). So, let’s ask objdump to show us what is in there.

$ objdump -s c1 | grep -A 15 .rodata

  Contents of section .rodata:
   40098c 30783030 20436861 6c6c656e 6765720a  0x00 Challenger.
   40099c 0a005061 7373776f 72643a00 0a2b2043  ..Password:..+ C
   4009ac 6865636b 696e6720 50617373 776f7264  hecking Password
   4009bc 2000204f 4b2e0a2b 20596f75 20646964   . OK..+ You did
   4009cc 20697421 0a002046 61696c2e 0a2d2054   it!.. Fail..- T
   4009dc 72792061 6761696e 2e0a002b 20444f4e  ry again...+ DON
   4009ec 450a0062 cbb18628 7880b223 89f2c26d  E..b...(x..#...m
   4009fc 3ec7b223 89f2c26d 3ec7b223 89f2c26d  >..#...m>..#...m
   400a0c 3ec7b200 2389f2c2 6d3ec7b2 00002f64  >...#...m>..../d
   400a1c 65762f75 72616e64 6f6d004c 445f5052  ev/urandom.LD_PR
   400a2c 454c4f41 44007661 6c677269 6e6400    ELOAD.valgrind.

We can easily find the string " OK\n+ YOu did it!". This string is located at 0x4009bc + 2 = 0x4009be. Now we just have to search for that address in the assembly code.

NOTE: If you do not want to add hex numbers and you have installed radare, try the command iz

$  objdump -d c1 | grep -C 5 4009be
  400198:	48 8b 35 61 0e 20 00 	mov    0x200e61(%rip),%rsi        # 0x601000
  40019f:	48 89 df             	mov    %rbx,%rdi
  4001a2:	ba 20 00 00 00       	mov    $0x20,%edx
  4001a7:	e8 5a 06 00 00       	callq  0x400806
  4001ac:	85 c0                	test   %eax,%eax
  4001ae:	bf be 09 40 00       	mov    $0x4009be,%edi
  4001b3:	74 05                	je     0x4001ba
  4001b5:	bf d2 09 40 00       	mov    $0x4009d2,%edi
  4001ba:	e8 ed 00 00 00       	callq  0x4002ac
  4001bf:	bf e7 09 40 00       	mov    $0x4009e7,%edi
  4001c4:	e8 e3 00 00 00       	callq  0x4002ac

We can see how address 0x4001ae loads the edi register with the pointer to the string we are interested on (the success message or good boy). And just below, we find a jump if Equal (je), otherwise, we load edi with a pointer to the fail message (check the .rodata dump above if you do not believe me… that’s the bad boy).

To crack/patch this one we can proceed in two different ways:

  1. Change the je conditional jump into a normal jump… changing the byte 0x74 to 0xeb (this actually convers je in jmp.
  2. NOP the mov loading edi with the fail message.

In this case both will work, but in the general case, solution 1 is preferred. The reason is that the code to be executed is probably more complex that just loading a register with a string address and we may not be done with just writing 5 NOPs.

So… go an patch the binary. Now you know how to do it with vim!

Wrapping up

The title of this post said, “Solution and more”. You may be wondering what was the more in the title. Well, by now :

  • … you have already learned how to patch binaries with vim. That is pretty awesome, and also useful
  • … you have also learned how to convert a binary into a text file and recover it… I have an curious idea on how to take advantage of this.

There is a third thing, that I will save for another post, but just in case you haven’t noticed it… we had build a 5Kb STATIC binary. Just for you to realise what this means:

  $  cat << EOP > a.c; gcc -static -o a a.c && ls -lh a.out
  > int main(){return 0;}
  > EOP
  -rwxrwxr-x 1 pico pico 859K May 24 17:22 a.out

Yes. A static binary that does nothing takes more than 800Kb!!!

Happy Hacking!

(Jakub) #2

Nice tutorial! Today I’m done this thanks to your tutorial and this is perfect experience. Yesterday I’m tried to find password, but not successful.
But I’m not understand. Is there some password? Or it’s can be done with patch only?

(pico) #3

It is. If you want to try, it is ABCDEFG.

You can try to reverse the obfuscate function works and obtain the key, instead of cracking the program. That usually requires more effort that just breaking the check… that computationally speaking is simpler.


I’ll try to reverse the password but it might be a bit harder for me since I haven’t really touched 64-bit assembly.

(pico) #5

The function itself is very simple… Looking forward to your solutions

(Jakub) #6

I forgot to say, It’s looks like je to jmp binary representation not correct. Because if I’m changing 0x74 to 0xbf I’m get error something like ‘Segmentation fault’. So I’ve found some table assembly opcodes with their binary representation, where jmp represented with 0xeb and this works for me.

(pico) #7

Good catch @JaCube. It was indeed a mistake. I’m glad to see people around that first try to solve the problem before starting to ask like crazy. Well done!


And here I was busy reverse engineering the entire program to C (I failed kinda).
Well I didn’t think of a binary patch, this VIM stuff is really cool.
I’m still amazed at all functionalities VIM has.
Thanks for sharing :slight_smile:

(pico) #9

Reverse engineering the whole thing is quite a task, even for this small program. And I fully agree… vim is really cool


Okay, I’ve been reversing your program and I think I’ve got it, though there are some things I do not understand. Your my_getpass function is pretty unclear to me since I have no idea what tcgetattr and tcsetattr do (I think it has to do with modifying the input mechanism from the user), all I could recognize within the function was the read but that wasn’t really helpful (not sure why fd was 0x1). After debugging it, I know that it returns 7. In your obfuscate function, I could identify the overall structure of the routine, something along the lines of

int obfuscate(malloc_var, ipass, 0x20) {
    int i;
    for (i = 0; i < 0x20; i++) {
        // something in here to do with XORing
        // the malloc (user input) and ipass variables.
        malloc_var[i] ^= ipass[i]
    return i;

which I then assumed you were obfuscating the user’s input with the bytes of ipass which will be compared later with the already obfuscated original password in the pass variable using strcmp. That then lead me to XOR ipass and pass together like so:

ipass XOR pass
0x23  XOR 0x62 = 0x41 (A)
0x89  XOR 0xCB = 0x42 (B)
0xF2  XOR 0xB1 = 0x43 (C)
0xC2  XOR 0x86 = 0x44 (D)
0x6D  XOR 0x28 = 0x45 (E)
0x3E  XOR 0x78 = 0x46 (F)
0xC7  XOR 0x80 = 0x47 (G)
0xB2  XOR 0xB2 = 0x00 (0)

which is coincidentally the length of what I got from the my_getpass function and also the password you have already provided above.

Thanks for providing this challenge. I had fun reversing it and I hope that I’ve gotten quite a bit of experience from doing it.

(pico) #11

Congrats! @dtm you did it again!

I will post the original code and some hints later, but answering your questions.

  • The my_getpass function disables the console echo, so the password is not shown while you type
  • File descriptor 1 is the Standard Input

Glad to hear you had fun!


Yeah, I thought it did that to the terminal.

That’s strange. When I looked it up, STDIN is 0.
GNU - Descriptors and Streams

(pico) #13

My fault. You are completely right. Now you already know what is file descriptor 1 :wink:


Still doesn’t address why it’s 1 instead of 0?

Anyway, I’ll be writing up my reversed version of your program in a bit. See how accurate I am.

(pico) #15

I see what you mean. Indeed it should be 0. That was a mistake. I haven’t catch it because, for some reason it works… Reading for stdout actually works as reading from stdin…

I had just made a quick test and I can also write to stdin…

I’ll wait then for your writting and I will post mine own after.

May be related to the terminal/pseudo terminal devices… never played with those at low level. But you are right, it should be 0