Santa found a paper with some strange logical stuff on it. On the back of it there is the hint: "use 32 bit".
He has no clue what this means - can you show him, what "???" should be?
Solution
This seems like a series of boolean logical operators. As the hint tell use to use 32 bits, we will solve this problem with a quick C++ program so we can guarantee the data type used is 32 bits. Furthermore, we will try both signed and unsigned variants, it turns out that we need to use signed integers for this problem.
We come up with C++ code (splitting up the operations into 3 steps):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <iostream>
intmain(intargc,char**argv)
{
int32_t answer;
answer=(4|7)^(1337& 424242);
answer=~answer;
answer|=0xB055;
std::cout<<answer;
return0;
}
We run the program and the the printed result is:
1
-291
I enter this into the ball-o-matic and get the daily QR code and daily flag!
I download the binary and run it and am presented with the following program:
It turns out that this program will tell you (via a messagebox) if you enter in the correct daily nugget or not!
So all we have to do is check the binary to see what causes the successful message box to appear.
Note: You can do this challenge using IDA or a .NET disassembler like ILSpy (link).
ILSpy Approach
I decided to use ILSpy as it is apparently a very good .NET disassembler. I open the program and load the binary and it disassembles it into various classes as you would expect.
We are mainly interested in the hv15 class. By searching for strings like
yes, that is the key! we realise the only important functions we need to look at are
Button1_Click and
Encrypt .
It becomes super simple to solve this challenge at this stage. The input parameter is just the text we enter into the textbox and the pass parameter is
Form1.GlobalVariables.assembly which is defined to be the string
__ERROR_HANDLER. All we have to do is reverse the encryption starting with an input that equals
zV5/UFU8PUD3N2T49IBuCwvGzCLYz39tkMZts7rfBU4=. We first decode the base64 string into a byte array and then run the program again but with
rijndaelManaged.CreateEncryptor() changed to
rijndaelManaged.CreateDecryptor().
I wrote a small C# program that accomplishes what we want to do:
Clearly the issue here is that this program is very inefficient.
So what does the program do? First it sets the unsigned 64 bit integer variables
val and
i to 0. Then
0xC0DE42 iterations occur and the
val is recomputed each calculation. The previous
val and
i values are used to recompute the new
val value.
After all this, it appears as if the nugget we need is printed out! The 4 missing blocks in the nugget are based on 4 groups of 16 bits which come from the final
val value.
So we begin to optimize the code!
foo and bar
We start with the
foo and
bar functions which are very similar to each other.
It turns out that they simplify to:
1
2
3
4
5
6
7
uint64_t foo(uint64_ta){return(a-42)+23*2-3;}
uint64_t foo(uint64_ta){return(a-42)+43;}
uint64_t foo(uint64_ta){return(a+1);}
uint64_t bar(uint64_ta){return(a+42)-23*2-3;}
uint64_t bar(uint64_ta){return(a+42)-43;}
uint64_t bar(uint64_ta){return(a-1);}
So we have just optimised these two functions slightly. The next step is go through the code and replace all calls to
foo and
bar with simply increments or decrements. I’m not going to show the changes I made to each call as that would make this post too long but you get the idea.
Optimized functions:
1
2
3
4
5
6
7
uint64_t foo(uint64_ta){
returna+1;
}
uint64_t bar(uint64_ta){
returna-1;
}
baz
baz should look like this at this stage:
1
2
3
4
5
6
7
8
9
uint64_t baz(uint64_ta,uint64_tb){
uint64_tr=0;
for(uint64_ti=0;i<a;i++)
r+=1;
for(uint64_ti=0;i<b;i++)
r+=1;
returnr;
}
It is clear from the code that
baz can be optimised. First the variable
r is set to zero and then a 1 is added to
r ,
a times. Then 1 is added to
r,
b times.
This is simply the same as adding
a to
b and storing the result in
r .
Optimized function:
1
2
3
uint64_t baz(uint64_ta,uint64_tb){
returna+b;
}
spam
Spam should look like this at this stage:
1
2
3
4
5
6
7
uint64_t spam(uint64_ta,uint64_tb){
uint64_tr=a;
for(uint64_ti=0;i<b;i++)
r-=1;
returnr;
}
This function is simply setting
r to a and then taking away 1 from
r,
b times before returning the result in
r .
This is the same as returning
a - b .
Optimized function:
1
2
3
uint64_t spam(uint64_ta,uint64_tb){
returna-b;
}
eggs
eggs should look like this at this stage:
1
2
3
4
5
6
uint64_t eggs(uint64_ta,uint64_tb){
uint64_tr=0;
for(uint64_ti=0;i<a;i++)
r+=b;
returnr;
}
Well all that is happening here is
r is increasing by
b,
a times. That means we are adding
a number of
b’s to
r .
This is the same as returning
a * b .
Optimized function:
1
2
3
uint64_t eggs(uint64_ta,uint64_tb){
returna*b;
}
merry
merry should look like this at this stage:
1
2
3
4
5
6
uint64_t merry(uint64_ta,uint64_tb){
uint64_ti;
for(i=0;a>=b;i++)
a-=b;
returni;
}
So at this stage, we are taking away
b from
a while
a is still bigger than
b . The value of
i is then returned which is the number of times we took away
b from
a .
This is clearly a simple division operation:
a / b
Optimized function:
1
2
3
uint64_t merry(uint64_ta,uint64_tb){
returna/b;
}
xmas
xmas should look like this at this stage:
1
2
3
uint64_t xmas(uint64_ta,uint64_tb){
returna-(a/b)*b;
}
This one is a little more tough.
a is divided by
b then multiplied by
b and this result is subtracted from
a . You may be tempted to think that this returns 0 all the time but it does not.
This is because the division operation that occurs is integer division and multiplying that result by
b may not result in the original
a being restored.
A small example: 10/3=3.33 but is stored as 3 in an integer. However, 3*3 = 9 and not 10!
In this case, a modulo operation is happening:
a % b
Optimized function:
1
2
3
uint64_t xmas(uint64_ta,uint64_tb){
returna%b;
}
hackvent
hackvent should look like this at this stage:
1
2
3
4
5
6
uint64_t hackvent(uint64_ta,uint64_tb){
uint64_tr=1;
for(uint64_ti=0;i<a;i++)
r*=b;
returnr;
}
This is a fun one. Notice how
r is set to 1 initially, that is important.
r is then multiplied by
b,
a number of times.
This is the same as calculating
b to the power of
a.
However, we cannot just use the
pow function defined in
<math.h>.
We are dealing with uint64_t data types and must thus get a power function that can handle these larger numbers.
I decide to find one online and use it.
At this stage you can do two things.
You can either run your program as is to find the answer or you can inline your function or convert the function calls in main so you save making all those function frames.
In this case it doesn’t really matter but this is what the calculation of
val in main should look like with no function calls:
1
val=((ipow(3,i/42)+val)*i)%0x42DEADBABEC0FFEE;
Then you run your program and within a few seconds you get the flag!