Cryptix CTF'19 CrackIT

So to begin with, this was one of my well spent weekend as CTFs were lined up one after the another, and fortunately some of my team members were active. There were a variety of CTFs to choose from such as, HITCON Quals, Rooters, Cryptrix, etc I just had a taste of each of them.
Also I’m not that much of a pro so didn’t focus too much on HITCON.
Difficulty level of CTFs according to me could be arranged in the following manner :

HITCON Quals > Rooters > Hack-A-Bit > Cryptrix > Syskron

I didn’t like Syskron personally because It was full of guessing challenges.

I thought maybe post about my experience regarding all of them and maybe if possible a writeup too!! So I’ve been practicing level2/3 crackmes on crackmes.one lately and I was excited to test my skills.
Out of them only Cryptix and Rooters had some reversing challenges which I was able to solve.
But In this blog post I’m particularly interested in solving a crackme from Cryptix known as CrackIT.
PS I was not able to solve it during the CTF, there were many reasons for that.. one of them being that Hack-A-Bit was also live at that same time and we were doing great in it. (We ended #1 in Hack-A-Bit XD)

And yeah here’s a backup for y’all so u can follow along!

Download keygen

The name should’ve been keygenme according to me. IDK what did the author thought while naming it LOL
Well leaving that aside, we run the binary file and also check that it is 64 bit binary which is not stripped so we are lucky till now.

Well In this writeup I’ll try to as detailed as possible, So plz ignore if I’m emphasizing more on any particular topic.

└──╼ $./keygen
Usage: ./keygen <KEY>

└──╼ $./keygen 12345
Sorry no flag for you

I’ve been using Ghidra decompilation feature and I have to say that it has lived upto my expections almost everytime.

So I look over the code and found that there are 2 functions which do the actual work ie. validate_key() and Do_Something().
The main function doesn’t do anything actaully, it is solely dependent on the return value of validate_key()

undefined8 validate_key(char *pcParm1)

{
  size_t sVar1;
  undefined8 uVar2;
  long lVar3;
  double dVar4;
  int local_20;
  
  sVar1 = strlen(pcParm1);
  if ((int)sVar1 == 0x11) {
    lVar3 = Does_Something(pcParm1,0x11);
    local_20 = 0;
    while (local_20 < 0x18) {
      dVar4 = pow(2.00000000,(double)(int)*(char *)(lVar3 + (long)local_20));
      if(*(double *)(_Zproc_libc_fini + (long)local_20 * 8) != dVar4) {
        puts("Sorry no flag for you");
        return 0;
      }
      local_20 = local_20 + 1;
    }
    uVar2 = 1;
  }
  else {
    puts("Sorry no flag for you");
    uVar2 = 0;
  }
  return uVar2;
}

Get ghidra_source.c
So validate_key() checks if our input is 0x11(1710) chars long or not. and later passes our input and its length to the DoSomething() function. We see that the return value of DoSomething() will always be a 0x18(2410) char string and then their is a power operation with base as 2 on it’s each value whose result is being compared with a variable in memory, _Zproc_libc_fini. So I checked its content.

But It didn’t make any sense to me and also I was missing the most crucial part ie. what does DoSomething() do?
So I headed over to GDB and started debugging it.

disas validate_key

b *validate_key+72 
r aaaaaaaaaaaaaaaaa

We’ve set up a breakpoint just after the call to DoSomething() so that we can check the value it returns.
Now if we run it using a random 17 char string and we can see that the string returned from DoSomething() is stored in $eax and is simply base64 of our input. Oh great its just a base64 implementation function in C.

Now I set up another breakpoint on the ucomisd instruction which is a just like compare but is specifically used for doubles. You can read more about it from here. And we simply continue the execution.

b *validate_key+167 
c

Now lets see what is being compared… Ohh here we see that registers with double-precision are used. This was something new to me.
Reference

So register xmm0 now stores the value to which our b64 encrypted input char is being compared to.

print $xmm0

Ok so the double value looks like what we want. So I tried this short script to check if I am in the right direction.

for x in range(33,127):
    print chr(x),pow(2,x)

And to my surprise there was a result = 1.2676506002282294e+30 for character ‘d’.

Now I didn’t had more time to complete it as I told earlier.
But I afterwards when I continued with the ghidra’s source, here is what I deduced in python:

encoded = Does_Something(myinput,0x11)
while (i < 0x18):
  res = pow(2,encoded[i])
  if (Zproc_libc_fini[i]) != res):
    print "Sorry no flag for you"

So there will always be 0x18(2410) iterations on the encoded string because when a 17 char is base64 encoded it results in a string of length 24 everytime. And obviously if our encoded input is 24 char long the Zproc_libc_fini array should be of the same length.

But as I showed you I was not able to see any values as 1.2676506002282294e+30 in ghidra so I did it in gdb.
I repeated the previous process, ie. set 2 breakpoints and continued their execution and when it stopped at validate_key+167, I tried to get all the values stored in _Zproc_libc_fini. I was having a hard time doing this. Then I learnt some more things about how to examine doubles in memory. Also this question on stackoverflow helped a lot.

Now If we want to get the first value in the array we can try this :

print (double)_Zproc_libc_fini

For examining it in memory you can use:

x/25gf &_Zproc_libc_fini

where g = Giant words (8 bytes)
and f = floating value
Reference

But the most appropriate way here would be this.

print *(double*)(&_Zproc_libc_fini)@24

We can get the address of _Zproc_libc_fini in memory using & operator and specify the length of values we need ie. 24 using @ symbol after the address.
Reference

So its time to write a script to reverse the process of pow() using log() to get the base64 encoded key

from base64 import b64decode
from math import log

Zproc_libc_fini =[1.2676506002282294e+30, 5.3169119831396635e+36, 1.5111572745182865e+23, 281474976710656, 
  1.5845632502852868e+29, 562949953421312, 1.4411518807585587e+17, 5.3169119831396635e+36, 
  1.5111572745182865e+23, 1125899906842624, 3.0223145490365729e+23, 562949953421312, 
  6.338253001141147e+29, 8.1129638414606682e+31, 1.1805916207174113e+21, 281474976710656, 
  2.5353012004564588e+30, 7.7371252455336267e+25, 1.4411518807585587e+17, 4.1538374868278621e+34, 
  1.5111572745182865e+23, 2.3611832414348226e+21, 6.6461399789245794e+35, 2.305843009213694e+18]

b64encoded_str = ""

for item in Zproc_libc_fini:
  b64encoded_str += chr(int((log(item)/log(2))))

flag = b64decode(b64encoded_str)
print flag

By the way, we can also brute force the base64 encoded key and get it easily using the code below :

for item in Zproc_libc_fini:
  for x in range(33,127):
    if(pow(2,x)==item):
      b64encoded_str += chr(x)
└──╼ $./keygen w34k_s3cur1ty_l0l
Congratulations!! here is you flag: flag{w34k_s3cur1ty_l0l}

drawing

Also I got to know that we can change the values of Zproc_libc_fini to represent it in doubles.

Thanks to @arpox for sharing it.
You can just select all of the data in Zproc_libc_fini and right click-> data -> double.
Well I learnt many things through this challenge.

Also the challenge source was disclosed by the admin.

Download challenge_source.c

I’ll be posting about some of my favourite crackmes soon.

So Make sure to subscribe to the newsletter to learn together with me.
Also Comment your feedbacks and share this writeup with your friends.

And Keep Escalating the Priveleges Gang!!