Reverse Engineering Challenge - Half-Twins Walkthrough

Reverse Engineering Challenge - Half-Twins Walkthrough

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:

Half-Twins Walkthrough

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.

Output of running file command on half-twins binary
Output of running file command on half-twins binary

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.

Symbols for the half-twins binary
Symbols for the half-twins binary

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.

Output of running strings on the half-twins binary
Output of running strings on the half-twins binary

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

Main function of the half-twins binary
Main function of the half-twins binary

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.

Analyzing section loc_118C
Analyzing section loc_118C

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);
}
Half-twins binary with updated variable names
Half-twins binary with updated variable names

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.

Continuing in the half-twins binary
Continuing in the half-twins binary

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;
Exploring the loop
Exploring the loop

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.

Wrapping up half-twins
Wrapping up half-twins

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.

Solving Half-Twins
Solving Half-Twins

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! ✌🏾

Show Comments