CSEC3616Cybersecurity Engineering

    Defences against buffer overflows

    The defences the lecture groups by level (language, code analysis, compiler, operating system, API choice), what each one stops, and how each can be bypassed.

    • Explain how StackGuard canaries and StackShield protect the return address, and why neither is complete.
    • Explain what ASLR and DEP each do and how return-oriented programming defeats DEP.
    • Describe taint analysis and why static analysis gives many false positives.
    • Explain what the n and l variants of C string functions fix and what failures remain.

    15 min readJump to the quiz ↓

    Each defence blocks a different part of an overflow

    A successful stack overflow needs unchecked input, a return address that can be overwritten, and executable attacker bytes at a predictable address. Each defence blocks part of that chain, and each has limits.

    The slide groups the defences by where they act.

    • Alternative languages. Memory-safe languages such as Java, .NET and Python do not allow direct access to memory addresses, and can prevent this entire class of vulnerability.
    • Code analysers. Static and dynamic code analysis.
    • Compiler level. StackGuard (canaries) and StackShield (a copy of the return address in a safe area).
    • Operating system level. Data Execution Prevention (DEP) and Address Space Layout Randomisation (ASLR).
    • Safer APIs. Library functions that make you state a length.

    Code analysis looks for unsafe paths before or during execution

    Static code analysis. This kind of tool is typically used during implementation, for instance a scan before a version is released. A key method is taint analysis. It finds variables that could have been tainted by user input, identifies commonly vulnerable functions, called sinks, and raises an alert if a tainted variable reaches a sink without going through sanitisation. It can find vulnerabilities that manual code review misses. Its limits are a usually high number of false positives and difficulty with problems such as broken authentication.

    Dynamic code analysis. The notes describe examining an application during or after execution. A specialised form is fuzz testing, which feeds malformed or random data to the software to trigger failures. It needs real resources and time, and it may miss code paths that the tests never run. The slide itself only names the category; the detail is from the notes.

    Compiler checks detect or repair return-address corruption

    StackGuard and canaries. When a function is called, a random value (the canary) is placed to mark the buffer boundary. Before the function exits, the value is checked. The reasoning: to overwrite the return address the attacker also has to overwrite the canary, so the overflow is detected. The trade-off is performance, because of the extra checks.

    It is not 100% effective. The code that handles a detected change of the canary is also in memory, so an attacker can try to reach it.

    StackShield. When a function is called, the return address is copied to another area of memory. At the end of the function the return address is checked, and if it differs, the original is used. Bypasses are possible here as well. Canaries can also detect corruption of other values, so they are considered a bit more useful.

    ASLR hides addresses; DEP restricts execution

    ASLR. Exploiting an overflow needs the address of the malicious code. ASLR randomises addresses across memory so that address is hard to guess. Advanced techniques can bypass even this. The notes add that the protection depends on how much randomness the system introduces: if only a small number of address bits are randomised, a guess is more likely to be right.

    DEP. DEP marks code on the stack as non-executable. If code does execute in those areas, the operating system raises an exception. The lecture says it can be defeated by return-oriented programming (ROP), for example a return-to-libc attack. Instead of relying on injected shellcode, the attacker uses code already within the operating system to invoke a shell, by pointing the return address at /bin/sh on the system.

    Compare

    StackGuard canary

    Places a random value at the buffer boundary and checks it before the function exits. Detects corruption of the return address and of other values. Costs performance.

    StackShield

    Copies the return address to a safe area and compares at exit, restoring the original if it changed. Protects the return address only.

    Length-aware APIs still need terminators and truncation checks

    Safer APIs. Some C and C++ functions have often led to exploits, and each has a variant that makes you think about buffer lengths. The notes list the dangerous gets(), strcat() and strcpy(), and the defensive fgets(), strncat() and strncpy(). For gets() the advice is simply not to use it. For the others, check the buffer length and the null characters.

    The slide gives two caveats.

    • The n versions only encourage defensive coding by requiring an explicit length. They can still omit the terminating null character and cause problems.
    • The l versions, for example strlcpy(), are offered by some UNIX derivatives such as OpenBSD. They avoid the overrun but silently omit data, which is another source of error.

    Pitfall

    “DEP” and “ASLR” are not interchangeable. DEP is about what may be executed (the stack is marked non-executable). ASLR is about what can be found (addresses are randomised). A return-to-libc attack beats DEP because it executes no injected code.

    Layering is the point. A language with bounds checking removes the problem. Where C is unavoidable, canaries or a return address backup detect a smashed return address, DEP stops injected code from running, ASLR makes the target address a guess, and the n and l functions reduce the chance of the overflow in the first place. The lecture states a bypass for every one of the compiler and OS measures, so none of them is a substitute for checking lengths in the code.

    Exam detail

    Be ready to describe each of StackGuard, StackShield, ASLR and DEP in one sentence of mechanism and one of limitation, as the slides do. For taint analysis, use the three words tainted, sink and sanitised. For the string functions, give the n caveat (may omit the terminating null) and the l caveat (silently omits data).

    Optional aside

    The notes make the cost argument for static analysis: it catches vulnerabilities early in development and reduces the cost of fixing them later, but the high false positive rate means developers have to review flagged issues by hand.

    Recall

    What does each overflow defence protect, and which limitations remain?
    • Memory-safe languages remove the class. Static analysis uses taint analysis from tainted input to sinks and gives many false positives.
    • Canaries detect an overwritten return address (and other values). StackShield backs up only the return address. Both can be bypassed.
    • ASLR randomises addresses. DEP makes the stack non-executable. ROP and return-to-libc beat DEP by reusing existing code.
    • n functions may leave strings unterminated. l functions silently truncate.

    Check yourself