简体   繁体   English

fgets exploit - 使用 IDA 进行逆向工程

[英]fgets exploit - reverse engineering with IDA

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).我目前正在做一个练习,我必须找到一种方法来“通过”关卡(这是一个逆向工程练习,我用 IDA 对其进行了反编译)。

The level function consists of 3 while loops, from each I have to break to get to the next one.关卡 function 由 3 个 while 循环组成,我必须从每个循环中中断才能进入下一个循环。 To pass the level, I have to input something that will pass through the 3 checks.要通过关卡,我必须输入能够通过 3 次检查的内容。 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: 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: 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.对于第一个if ,我只需要输入一个长于1E或十进制 30 的字符串。

The second, I have to enter only az 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.第二,我必须只输入 az 字符,但只能输入它们的 ascii - 97 可以被 3 整除的字符。所以只有选项是: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.但是有一个问题——我必须至少输入 1019 个字符,否则fgets将得到换行符 0x0A,而first_check_string function 将不会通过。

So I enter 1019 "a"s for example.例如,我输入 1019 个“a”。

I pass the first 2 ifs.我通过了前 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.但是第三个 if function second_check_string要求我的字符串被 4 整除,如果我输入 1019 个字符则不能。 And if I enter less, the first_check_string function will encounter the 0x0A newline char and return 0.如果我输入 less, first_check_string function 将遇到0x0A换行符并返回 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.要输入NUL 0x00字节,我们需要重定向程序以从文件而不是从用户键盘读取。 To do so, we execute the program as follows: prog.exe < file this makes the standard input, which fgets uses, become the file.为此,我们按如下方式执行程序: prog.exe < file这使得fgets使用的标准输入成为文件。 In the file we can any bytes we want, including the NUL character.在文件中,我们可以包含任何我们想要的字节,包括NUL字符。 We can do so either by using a programming language to write to that file, or a hex editor (I used 010 editor).我们可以通过使用编程语言写入该文件或十六进制编辑器(我使用 010 编辑器)来做到这一点。

Cheers to EVERYONE who helped me!为所有帮助过我的人干杯!

Input a manual NUL character, '\0' at one past a multiple of 4 offset (so the apparent string length is a multiple of 4).在偏移量 4 的倍数后输入一个手动NUL字符, '\0' (因此表观字符串长度是 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. fgets会愉快地将它作为字符串的一部分检索而不会停止,但是使用 C 风格字符串定义的测试将在NUL处停止,并且永远不会看到后面的换行符,也不会看到任何其他违反被检查规则的字符。 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 .使其足够长以通过最内层循环中的基本break ,然后在输入四个字符的倍数后放置一个NUL ,之后您可以为剩余的 1019 个字符输入任何内容,因为不会检查规则过去NUL

Kudos to @Shadowranger for noting the that a strategic \0 simplifies the problem immensely!感谢@Shadowranger 指出战略性的\0极大地简化了问题!

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.第一个 32 字节后跟 0 满足最小字符串长度。 (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.) (编译时数组避开了从文件中读取最多 1020 个字节的 OP 问题,其中嵌入了 NULL。不管怎样,效果都是一样的。)

The XOR of those (even quantity) 'a' characters results in zero;那些(偶数个)“a”字符的异或结果为零; 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...字节 32-35(视为无符号整数)与接下来的 4 个字节的异或意味着v2仍然为零......

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.最后,隐藏在 NULL(感谢 Shadowranger)和所有那些平衡 XOR 后面的是文字无符号 integer(32 位),它是匹配的关键。 Note that 'endian-ness' comes into play, and the "u win" message must be reversed (on my hardware).请注意,“endian-ness”开始发挥作用,并且必须反转“u win”消息(在我的硬件上)。

And the next 4 NULL bytes will terminate the 2nd check, so anything in the buffer beyond that is ignored...接下来的 4 NULL 字节将终止第二次检查,因此缓冲区中超出该字节的任何内容都将被忽略...

Good fun!好开心!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM