Introduction
Hello! Welcome back to the blog! Today we are going to solve another challenge from the website crackmes.one called "Half Twins." The author of this challenge didn't give us many instructions. We only receive the following message in the description: "Abby & Gabby created this crackme for you :D" Let's go ahead and run through this challenge. If you're interested, you can check out the corresponding YouTube video on my channel below:
Optional Materials to Follow Along
If you want to follow along feel free to download the VM I provide. You can find instructions on importing the VM here. If you don't want to use my VM that's fine, my feelings won't be shattered. But you will at least need the binary. You can download the binary here. The binary comes in a password protected zip file. The password is crackmes.one.
You'll also need a disassembler. I recommend IDA or Ghidra. With all of that out of the way, let's get reversing!
Initial Triage
Just as in our previous challenges let's start off by running file
.
There's nothing too special here. We do see the symbols weren't stripped so let's take a look at the symbols with the nm
command.
There aren't any user defined functions in this binary besides main
so we don't gain much information about the binary from the symbols. Let's go ahead and run strings
before we open this up in IDA.
So we gain a little more information. From the looks of this, there are plenty of ways to fail! I'm not sure what half twins are but I guess we are about to find out! Let's go ahead and open this in IDA!
Static Analysis in IDA
We notice right away the variables var_44
and var_50
are going to be our argc
and argv
variables respectively. We also see what appears to be an error string: "good luck next time" is stored in the variable s
. We can rename this variable to error_string
. Next, the argc
variable is compared to 3. If argc
isn't 3 we are presented with an error message: "hmm... i'm not sure you know what the w..." followed by our default error_string
. So, we know this binary takes 2 command line arguments (remember the name of the program is a default command line argument so I excluded that from the calculation). The above disassembly can roughly be translated to the following C code:
error_string = "good luck next time";
if (argc != 3)
{
printf("hmm... i'm not sure you what the word /"twins/" mean :/");
printf("%s", error_string);
exit(0);
}
If we provide the proper number of arguments, we jump to loc_118C
. Let's go ahead and analyze that block of code.
The first two instructions are grabbing argv[1]
and storing the result in the RAX
register. This is slightly different than how we are used to seeing indexing an array but that is all that is happening here. The next instruction we see argv[1]
gets stored in the var_20
variable. So this is going to be either Abby's or Gabby's age it isn't clear just yet. The instructions at address 0x1198
to 0x11A0
store argv[2]
into the var_18
variable. After that, we see our var_20
variable gets passed as an argument to the strlen
function which gets compared to 6. ja
stands for jump if above. So this is essentially saying, if the length of var_20
is greater than 6 we will jump to loc_11D8
. Otherwise, we will print the message "Abby: i'm older than that :(" This is good news. We now know that var_20
is Abby and var_18
is Gabby! Let's rename the variables to reflect what we just learned. Let's also assume we provided the correct amount of characters for Abby_age
and analyze loc_11D8
. We can translate the above disassembly to the following C code:
Abby_age = argv[1];
Gabby_age = argv[2];
if (Abby_age <= 6)
{
printf("Abby: i'm older than that :(");
printf("%s", error_string);
exit(0);
}
Now, we see Gabby's age is checked to ensure the length is greater than 6 and if it isn't we fail. Being these two are half-twins it makes sense they should be similar in age. We then check whether the length of Abby's age and Gabby's age are the same. If they aren't then we fail yet again. Something interesting to note is the ages are being treated as strings instead of numbers. So perhaps we need to find numbers that when spelled out are greater than six. The above disassembly roughly translates to the following C code:
if (Gabby_age <= 6)
{
printf("Gabby: i'm older than that :(\n");
printf("%s", error_string);
exit(0);
}
if (Abby_age != Gabby_age)
{
printf("Abby & Baggy: for god's sade we are TWINS!\n");
printf("%s", error_string);
exit(0);
}
Let's keep going to verify or refute this theory. Assuming we find such numbers we continue and come across even more checks.
We see Abby's age is checked to determine whether we entered an odd number. Something interesting, ANDing a number with 1 will result in 0 if it is an even number and 1 if the number is odd. So, ANDing the length of Abby's age followed by test rax, rax
will check whether we entered an odd or even number of characters. Then, the program does something interesting. At address 0x128B
, we see this shr rax, 1
instruction after we grab the length of Abby's age. SHR
stands for logical shift right and if effectively divides the first operand, RAX
in this case, by 2 raised to the power of the second operand. So the instruction here is essentially dividing RAX
by 2. We then see on the next line we store that number in a variable var_30
. Let's rename this variable to abby_age_divide_by_2
. Really rolls off the tongue doesn't. We then see var_34
is set to 0 and then we perform an unconditional jump to loc_12E6
. Let's go ahead and rename var_34
to i
since this will likely be our loop control variable. Let's examine what this loop is doing. The above disassembly can be roughly translated to the following C code:
if (abby_age % 2 != 0)
{
printf("Abby & Gabby: we are not "odd" years old\n");
printf("%s", error_string);
exit(0);
}
abby_age_divided_by_2 = abby_age / 2;
i = 0;
We see the program grabs Abby_age[i]
and Gabby_age[i]
and compares them. If they are not equal we are presented with we are presented with an error message: "Abby & Gabby: we're half twins you know..." and then we are presented with the error_string
. The loop control variable is compared to abby_age_divided_by_2
. After the loop completes, we grab the length of Abby_age
and store it in var_2C
. Then we perform an unconditional jump which will likely be the start of another loop. We can translate the disassembly to the following C code:
while (i < abby_age_divided_by_2)
{
if (Abby_age[i] != Gabby_age[i])
{
printf("Abby & Gabby: we're half twins you know...");
printf("%s", error_string);
}
i++;
}
Abby_age_len = strlen(Abby_age);
Let's examine what happens when we take the unconditional jump.
We are close to wrapping this challenge up! Something to note here is the i
variable is the same variable that was used in the previous loop. If you notice, i
was not reassigned so we are starting this loop from the index we left off at the end of the first loop. During this iteration, instead of checking if the characters are the same, the program is checking whether the characters are different. So, the loop will continue as long as Abby_age[i]
and Gabby_age[i]
are different. If we reach the end then we are presented with the success message: "Abby & Gabby: yaayy!! nice job! :D" Alright now that we have a better understanding we can solve this challenge. Let's first compile all of our C code and add in this little bit that we just learned.
error_string = "good luck next time";
if (argc != 3)
{
printf("hmm... i'm not sure you what the word /"twins/" mean :/");
printf("%s", error_string);
exit(0);
}
Abby_age = argv[1];
Gabby_age = argv[2];
if (Abby_age <= 6)
{
printf("Abby: i'm older than that :(");
printf("%s", error_string);
exit(0);
}
if (Gabby_age <= 6)
{
printf("Gabby: i'm older than that :(\n");
printf("%s", error_string);
exit(0);
}
if (Abby_age != Gabby_age)
{
printf("Abby & Baggy: for god's sade we are TWINS!\n");
printf("%s", error_string);
exit(0);
}
if (abby_age % 2 != 0)
{
printf("Abby & Gabby: we are not "odd" years old\n");
printf("%s", error_string);
exit(0);
}
abby_age_divided_by_2 = abby_age / 2;
i = 0;
while (i < abby_age_divided_by_2)
{
if (Abby_age[i] != Gabby_age[i])
{
printf("Abby & Gabby: we're half twins you know...");
printf("%s", error_string);
}
i++;
}
Abby_age_len = strlen(Abby_age);
while (i < Abby_age_len)
{
if(Abby_age[i] == Gabby_age[i])
{
printf("Abby & Gabby: we are only HALF twins...");
printf("%s", error_string);
exit(0);
}
i++;
}
printf("Abby & Gabby: yaayy!! nice job! :D");
So, we have to provide the binary with two strings that are even and greater than 6. Additionally, the first half of the two strings must be identical while the second half must be unique. So, let's run the binary with strings that meet this criteria.
Conclusion
Alright and that's it for this challenge. Not terribly difficult but interesting to say the least. I hope you enjoyed reading this blog post and as always feel free to ask any questions you may have regarding this challenge. You can reach me on Twitter: Jaybailey216, Instagram: Jaybailey_216, or Discord: jaybailey216#6540. I'll see you all next time!
Peace out! ✌🏾