Testing the Program
Opening it up prompts us for correct input.
After entering the wrong input, you get a nice message box telling you just how wrong you are.
Control Flow
Instantly after putting the program into IDA, you can see exactly how easy this is going to be.
Doing The Obvious!
If we look at the instructions line by line, we can see how this actually works a lot easier.
.text:000000014000122F 028 85 C0 test eax, eax ; Logical Compare
.text:0000000140001231 028 75 1F jnz short success ; Jump if Not Zero (ZF=0)
.text:0000000140001233 028 44 8D 49 05 lea r9d, [rcx+5] ; uType
.text:0000000140001237 028 4C 8D 05 6E 10 00 00 lea r8, Caption ; "WRONG!"
.text:000000014000123E 028 48 8D 15 67 10 00 00 lea rdx, Caption ; "WRONG!"
.text:0000000140001245 028 FF 15 4D 0E 00 00 call cs:MessageBoxA ; Indirect Call Near Procedure
.text:000000014000124B 028 83 F8 04 cmp eax, 4 ; Compare Two Operands
.text:000000014000124E 028 74 C0 jz short loc_140001210 ; Jump if Zero (ZF=1)
.text:0000000140001250 028 EB 17 jmp short loc_140001269 ; Jump
.text:0000000140001252 ; ---------------------------------------------------------------------------
.text:0000000140001252
.text:0000000140001252 success: ; CODE XREF: main+41↑j
.text:0000000140001252 028 45 33 C9 xor r9d, r9d ; uType
.text:0000000140001255 028 4C 8D 05 44 10 00 00 lea r8, Text ; "CORRECT!"
.text:000000014000125C 028 48 8D 15 3D 10 00 00 lea rdx, Text ; "CORRECT!"
.text:0000000140001263 028 FF 15 2F 0E 00 00 call cs:MessageBoxA ; Indirect Call Near Procedure
As we can see, the program will jump past the wrong message completely if it is correct. If it was the reverse, you could just nop the jump to get to a success message. Either way, the way we can easily ensure the program gets to a success message with the wrong input is by changing the jnz instruction on line 2 of the above to a jz instruction. We can do this by changing the byte 75 to 74.
.text:0000000140001231 028 74 1F jz short success ; Jump if Zero (ZF=1)
We are a bit lucky the creator (David) didn't obfuscate their strings, but equally we could have figured it out without them just as easily. Side note to David, disable debug information next time.
Without Patching Instructions
I believe the real challenge of this crack me is to figure out the real correct input. If you look at image one, you can see it will call function 140001070 and check if the response is false.
__int64 __fastcall sub_140001070(_BYTE *a1)
{
_BYTE *v1; // rdx
unsigned __int64 v2; // rax
unsigned __int64 v3; // rax
char v4; // r8
_BYTE *v5; // r9
char v6; // cl
__int64 v7; // rcx
int v8; // ecx
char *v9; // rax
int v10; // er8
unsigned __int64 v11; // rbx
__int64 i; // r9
char v13; // r11
int v14; // er10
unsigned __int64 v15; // rax
int v16; // eax
v1 = a1;
v2 = -1i64;
do
++v2;
while ( a1[v2] );
if ( v2 >= 7 )
{
v3 = -1i64;
do
++v3;
while ( a1[v3] );
if ( v3 <= 0xF )
{
v4 = *a1;
v5 = a1;
if ( *a1 )
{
v6 = *a1;
while ( (unsigned __int8)(v6 - 48) <= 9u || v6 == 46 )
{
v6 = *++v5;
if ( !v6 )
goto LABEL_12;
}
}
else
{
LABEL_12:
v7 = -1i64;
do
++v7;
while ( v1[v7] );
if ( (unsigned __int8)(v4 - 48) <= 9u && (unsigned __int8)(v1[(int)v7 - 1] - 48) <= 9u )
{
v8 = 0;
if ( v4 )
{
v9 = v1 + 1;
do
{
if ( v4 == 46 )
{
if ( *(v9 - 2) == 46 || *v9 == 46 )
return 0i64;
++v8;
}
v4 = *v9++;
}
while ( v4 );
}
if ( v8 == 3 )
{
v10 = 0;
v11 = -1i64;
do
++v11;
while ( v1[v11] );
if ( !v11 )
return 1i64;
for ( i = 0i64; ; ++i )
{
v13 = v1[i];
v14 = 0;
if ( v13 != 46 )
{
do
{
v15 = -1i64;
do
++v15;
while ( v1[v15] );
if ( v10 >= v15 )
break;
++i;
v16 = v13;
++v10;
v13 = v1[i];
v14 = v16 + 2 * (5 * v14 - 24);
}
while ( v13 != 46 );
if ( v14 > 255 )
break;
}
if ( ++v10 >= v11 )
return 1i64;
}
}
}
}
}
}
return 0i64;
}
We could also just force the correct return, but again I do not believe this is what David wanted us to do. So, let's read through the code.
This function seems to just step through each character of the string, and check if it is a number below or equal to 9, or 46. If we convert 46 ASCII it is a period (.
). Sidenote, the reason the code does -48 is because that is 0 in ASCII. By removing 48 from the character, we are converting it to get the actual number from the character. So right away we can see that is checking if the character is a number below 10, or a full stop. Odd, but lets continue.
Once it confirms everything is correct, it will jump to LABEL_12. This code will run a similar check (below 10). If the character is 46 (period), it will check if the previous or next character is a period. If it is, then it will return 0, otherwise check the next character in the array. Once it has confirmed there is no 2 periods next to each other, and it has ran into a period, it continues.
Next, it will check if v8 is 3, which basically checks if there was 3 characters before the period. It will then get the full length of the character array, and store that in v11. If v11 is 0, then the array is empty and return 1. The code will then loop until it has looped more than v11, then return 1. Inside the loop, it will check if the character is not a period, and therefore if it is a number. If it is a number, it will loop the array again. Each character is stored in v13, then copied to v16 mid loop to save previous. Then, it will assign v13 the current character being checked.
v14 = v16 + 2 * (5 * v14 - 24);
v14 will be updated will the result of this equation. It will do 5 * v14 (previous result of this equation) then - 24 from the result. Then it will take v16 (the previous character that was checked) then add 2 to it, then times the result together.
What does this mean? Well we can try our own simulation of how this works.
sim 1: v14 = 0 char = 52 (4 dec) char + 2 * (5 * v14 - 24) = 4 sim 2: v14 = 4 char = 49 (1 dec) char + 2 * (5 * v14 - 24) = 41
Very quickly we can see this is just combining the numbers together. Neat. Once it has finished combining the numbers together, it will check if the number is bigger than 255. I think at this point we can all see it is wanting an IPv4 address.
Conclusion
I had a lot of fun picking apart this crack me. Despite the easy difficulty, I enjoyed every second of it. Thank you David, whoever you are.