I'm currently doing an exercise, where I have to find a way to "pass" the level (It's a reverse engineering exercise, I decompiled it with IDA).
The level function consists of 3 while loops, from each I have to break to get to the next one. To pass the level, I have to input something that will pass through the 3 checks. Code is as follows:
while ( 1 )
{
while ( 1 )
{
while ( 1 )
{
memset(Buffer, 0, sizeof(Buffer));
stream = _acrt_iob_func(0);
fgets(Buffer, 1020, stream);
if ( strlen(Buffer) >= 0x1E )
break;
}
if ( first_check_string(Buffer) )
break;
}
if ( second_check_string(Buffer) )
break;
}
return printf(aWellDone, Buffer[0]);
}
The first_check_string function:
int __cdecl first_check_string(_BYTE *str_addr)
{
while ( *str_addr )
{
if ( (char)*str_addr < 97 || (char)*str_addr > 122 )
return 0;
if ( ((char)*str_addr - 97) % 3 )
return 0;
++str_addr;
}
return 1;
}
The second_string_check function:
BOOL __cdecl second_check_string(char *Str)
{
int v2; // [esp+4h] [ebp-8h]
char *i; // [esp+8h] [ebp-4h]
if ( strlen(Str) % 4 )
return 0;
v2 = 0;
for ( i = Str; *(_DWORD *)i; i += 4 )
v2 ^= *(_DWORD *)i;
return v2 == 1970760046;
}
For the first if
, i just have to enter a string longer than 1E
, or 30 in decimal.
The second, I have to enter only a-z character, but only ones that their ascii - 97 is divisible by 3. So only options are: a, d, g, j, m, p, s, v, y.
But there's a catch - I have to enter at least 1019 characters, since otherwise, the fgets
will get the newline 0x0A character, and the first_check_string
function will not pass.
So I enter 1019 "a"s for example.
I pass the first 2 ifs. But the 3rd if function second_check_string
requires my string to be divisble by 4, which can't be if I enter 1019 chars. And if I enter less, the first_check_string
function will encounter the 0x0A
newline char and return 0.
If anyone got any idea of how could I approach this, I would be grateful.
GENERALIZED SOLUTION
To enter a NUL 0x00
byte, we need to redirect the program to read from a file instead from the user's keyboard.
To do so, we execute the program as follows:
prog.exe < file
this makes the standard input, which fgets
uses, become the file.
In the file we can any bytes we want, including the NUL
character.
We can do so either by using a programming language to write to that file, or a hex editor (I used 010 editor).
Cheers to EVERYONE who helped me!
Kudos to @Shadowranger for noting the that a strategic \0
simplifies the problem immensely!
The following is a minor adaptation of the code given in the original problem.
int first_check_string( char *cp ) {
while ( *cp ) {
if( !islower( *cp ) ) // 'a'- 'z'
return 0;
if ( (*cp - 'a') % 3 ) // but only every third of those pass muster
return 0;
++cp;
}
puts( "Passed 1st check" );
return 1;
}
bool second_check_string(char *Str) {
int v2; // [esp+4h] [ebp-8h]
char *i; // [esp+8h] [ebp-4h]
if ( strlen(Str) % 4 )
return 0;
v2 = 0;
for ( i = Str; *(uint32_t *)i; i += 4 )
v2 ^= *(uint32_t *)i;
printf( "Aiming for %08X... Got %08X\n", 1970760046, v2 );
return v2 == 1970760046;
// Hides 0x7577696E as a decimal value
// ASCII decoding: 0x75-'u', 0x77-'w', 0x69-'i', 0x6E-'n' ==> "uwin"... :-)
}
int main() {
char Buffer[1020] = {
'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a',
'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a',
'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a',
'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a',
0, 0, 0, 'd', 0, 0, 0, 'd',
'n', 'i', 'w', 'u', 0, 0, 0, 0, // 7577696E = 'u', 'w', 'i', 'n';
};
while( 1 ) {
while ( 1 ) {
while ( 1 ) {
/* Using compile-time array instead of loading binary file */
if ( strlen(Buffer) >= 0x1E )
break;
}
if ( first_check_string(Buffer) )
break;
}
if ( second_check_string(Buffer) )
break;
else {
puts( "Failed 2nd check" );
getchar();
}
}
puts( "Well Done" );
return 0;
}
Passed 1st check
Aiming for 7577696E... Got 7577696E
Well Done
The 1st 32 bytes followed by 0 satisfy the minimum string length. (The compile time array skirts the OP problem of reading up to 1020 bytes, with an embedded NULL, from a file. The effect is the same, regardless.)
The XOR of those (even quantity) 'a' characters results in zero; a clean start for more processing.
The XOR of bytes 32-35 (treated as an unsigned int) with the next 4 bytes means that v2
is still zero...
Finally, hidden behind that NULL (thanks Shadowranger), and all those balancing XOR's, is the literal unsigned integer (32 bits) that is the key to matching. Note that 'endian-ness' comes into play, and the "u win" message must be reversed (on my hardware).
And the next 4 NULL bytes will terminate the 2nd check, so anything in the buffer beyond that is ignored...
Good fun!