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.
CodePudding user response:
Input a manual NUL
character, '\0'
at one past a multiple of 4 offset (so the apparent string length is a multiple of 4). fgets
will happily retrieve it as part of a string without stopping, but your tests using C-style string definition will stop at the NUL
, and never see a following newline, nor any other character violating the rules being checked. This dramatically relaxes the restrictions; make it long enough to pass the basic break
in the innermost loop, then put a NUL
after a multiple of four characters has been entered, and after that you can enter anything for the remainder of the 1019 characters because the rules won't be checked past the NUL
.
CodePudding user response:
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 X... Got X\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!