5

I am reading an article on buffer overflow protection here.

For terminator canaries, I follow the part that if a terminator like a zero is used for a canary, the attacker would have a terminator in the overflow, which will cause the overflow to stop, preventing overflow of return address.

But the next sentence is not clear:

The undesirable result is that the canary is known. Even with the protection, an attacker could potentially overwrite the canary with its known value, and control information with mismatched values, thus passing the canary check code, this latter being executed soon before the specific processor's return-from-call instruction. 

I am not able to follow this argument.

Does it imply that terminator canary can be bypassed in any way ?

    2 Answers 2

    8

    The basic idea of a terminator canary is that when an attacker attempts a buffer overflow, they're forced to over-write the canary value. The program can then detect that the canary has changed value and take appropriate actions.

    The value 0 is somewhat special in programming: many languages use it as an end-of-text marker. If an attacker is trying to overflow a text buffer, the use of a 0 as a terminator canary means the attack will fail: in order to keep the canary from changing, they need to include a 0 in the oversized input in a location that will cause almost all of the excess input to be ignored.

    This has a problem, though: if the input is handled as binary data rather than as text, the fact that the canary has a known, fixed value means the attacker can simply over-write the canary with itself, producing an undetectable overflow.

    Edit: code examples

    /* This reads a length-tagged packet of up to 16 bytes length from an input stream. * * Note that since the programmer forgot to check the length of the input, * a packet of more than 20 bytes (give or take alignment) will overflow onto * sensitive parts of the stack. If bytes 17 through 20 of the outsized packet * are 0s, this overflow won't be detected. */ size_t readPacket(char *stream) { size_t length; char packet[16]; uint32_t canary = 0; length = (size_t)(*stream++); memcpy(packet, stream, length); processPacket(packet, length); if(canary != 0) exit(0); return length; } /* This reads a username from an input stream. * * Note that since the programmer used strcpy() rather than strlcpy(), a * string of more than 20 bytes (give or take alignment) will overflow onto * sensitive parts of the stack. However, since strcpy() stops copying once * it encounters a byte with the value 0, in order for overflow to reach a * sensitive part of the stack, it must change the value of the canary. If * this happens, exit() is called and the changed stack is never used. */ size_t readName(char *stream) { char userName[16]; uint32_t canary = 0; strcpy(userName, stream); processUserName(userName); if(canary != 0) exit(0); return strlen(userName); } 

    In a real-life example, the canaries and canary-checking code may be inserted automatically by the compiler rather than manually by the programmer.

    12
    • Can you please elaborate the meaning of "if the input is handled as binary data than as text" .. ? What copy mechanism does this include like strcpy, memcpy .. ?
      – Jake
      CommentedOct 3, 2014 at 20:25
    • 3
      Text is normally handled as "a series of non-0 bytes followed by the value 0". Binary data is handled as "a sequence of bytes of a certain length", or for input, it's sometimes "a sequence of bytes extending to the end of the file". Binary data doesn't have a special value indicating the end of the data, while text does.
      – Mark
      CommentedOct 3, 2014 at 20:31
    • 1
      @Jake consider memcpy or memset, or simply writing 'char' values at each offset of a region one by one, rather than str* functions which would only modify the memory region until a 0 is encountered.CommentedOct 3, 2014 at 20:36
    • 1
      @Jake I think this is what Mark means yes. With memcpy you might actually have an attack that changes the buffer size to be copied (any invalid write of 1 byte or more at a chosen location should suffice), and then be allowed to copy more of your arbitrary data than the program expected/had previously checked.CommentedOct 4, 2014 at 10:28
    • 1
      @Jake, that's a tricky question to answer. The compiler will usually lay out memory in the order the variables are declared, in which case canary will come after buffer in memory and be overwritten by an overflow, but it's not guaranteed. That's one of the reasons the canaries should be inserted by the compiler rather than the programmer.
      – Mark
      CommentedOct 4, 2014 at 21:12
    0

    The redhat Security Technologies: Stack Smashing Protection (StackGuard) blog has a good explanation under the Terminator canaries section

    Most buffer overflow attacks are based on certain string operations which end at string terminators. A terminator canary contains NULL(0x00), CR (0x0d), LF (0x0a), and EOF (0xff), four characters that should terminate most string operations, rendering the overflow attempt harmless. This prevents attacks using strcpy() and other methods that return upon copying a null character while the undesirable result is that the canary is known.

    This type of protection can be bypassed by an attacker overwriting the canary with its known values and the return address with specially-crafted value resulting in a code execution. This can be when non-string functions are used to copy buffers and both the buffer contents and the length of the buffer are attacker controlled.

      You must log in to answer this question.

      Start asking to get answers

      Find the answer to your question by asking.

      Ask question

      Explore related questions

      See similar questions with these tags.