Yes, memory corruption vulns (buffer overflows, null pointers with large offsets, double-frees, arithmetic overflows used with a pointer, format string vulnerabilities, etc.) are still a thing, and are often still exploitable. There are a few ways, even on a system that enforces both DEP and ASLR and other modern protections. First of all, DEP (more generically, NX) has been defeated on its own for many years, through return-to-libc and more generically through return-oriented programming exploits. ASLR aims to defeat these by preventing malicious code from knowing the correct address to "aim at" for ROP gadgets or library functions. It's a strong protection, provided that it's enforced for all executable code in a process, but it's not always sufficient. A few ways around it:
- Leak the ASLR mask from a known address. Suppose your exploit involves controlling a pointer offset from a heap variable, as many do. Suppose further that there's another heap variable a known distance away - say, 16 bytes earlier - that points to a specific function or data structure in a library is. You know what, absent ASLR, the value of that other variable would be. If you can leak the value of the other variable back to the attacking code (which ASLR doesn't prevent, because relative offsets on the heap will still be as expected), then the actual value of the other variable can be XORed with the expected value to derive the ASLR mask, which can then be XORed with the offset to the target(s) you would normally aim for to find their actual locations under that system's ASLR. Obviously this is more complicated, requiring a multi-step attack and a way to both read and write at least a little memory, but it happens.
- Brute-force the ASLR mask. Until a few years ago, and even today on 32-bit processes, Windows used only fairly small masks (I believe just a single byte) for ASLR, which meant there were relatively few possible values by which addresses could be randomized. In other words, it was low entropy (8 bits, meaning 256 possible values). In the case that a process can be attacked many times (either it recovers from the memory failure, or more likely automatically restarts), the attacker could simply brute force all possible ASLR masks until they hit upon the correct one, at which point their payload runs instead of the vulnerable program getting an access violation exception and probably crashing. This doesn't work with 64-bit binaries linked with the (now-default)
/HIGHENTROPYVA
linker flag, which marks the binary as supporting 64-bit ASLR masks (a level of entropy that is almost certainly completely impractical to brute-force).
- The attacker might already know the mask. All processes on a given machine run with the same ASLR mask. While this protects against attacks from remote computers, or fixed attacks that don't take the mask into consideration, it doesn't work if the attacker is another process on the same machine (perhaps trying to elevate privileges by attacking another user's process, or break out of a sandbox). It's trivial for a process to learn its own ASLR mask - just hard-code the default value of a function pointer or similar, and XOR it with the actual value - and it can use that to automatically patch an exploit to aim at the masked addresses in the target process.
These are just examples; there might be other classes of attack, and there are definitely other implementation methods (for example, the first class is especially easy with format string vulnerabilities, since the attacker can generally observe the resulting formatted string and can use that to inspect the value of arbitrary offsets on the stack, before launching an attack that attempts to actually overwrite a value). ASLR drastically raises the bar, and can make a previously-practical exploit impractical. It's not guaranteed to do so, though.
Also, of course, any such memory corruption vulnerability is a denial-of-service risk, because a DEP violation, or just landing on the wrong address due to ASLR, will almost certainly crash the process (it's technically possible to catch the exception, at least for an access violation, but the program will be so corrupted it's not usually worthwhile). DoS is not as bad as arbitrary code execution, but in some contexts (like a server, or a kernel-mode component) it's still potentially a very big deal! So that's a way that you can definitely attack a system with buffer overflows and similar.