To provide a context for discussing low-level attacks that is simpler that the upcoming hands-on assignment, I'll be doing some demos in class, and releasing information online, about a buggy program named "bcecho". "bcecho" belongs to the same family of programs inspired by Unix utilities but with poor security design and vulnerabilities. Like the standard Unix command "echo", "bcecho"'s normal behavior is to print its command-line arguments on the standard output. Unlike a standard echo, bcecho has a somewhat blatant buffer overflow bug which will cause the program to crash, or otherwise misbehave, if any of the arguments is too long. You may be able to see the vulnerability if you inspect the code for the function "print_arg". The function copies its argument into the small (20 byte) stack buffer named "buf". Instead of using "strcpy" which would not check the length at all, the program uses "strlcpy", a variation that includes length checking. However the program passes the incorrect length (can you see what buf_sz actually is?), which defeats the security purpose of the length checking. Once we have the HA1 VMs ready for you to use, you'll be able to compile and run a 32-bit binary of bcecho which will be more like the 32-bit binary you can attack in HA1. But the standard compiler on the CSELabs machines doesn't work well in 32-bit mode, so for this week we'll use a 64-bit binary, compiled with a command like this on a CSELabs Ubuntu 20.04 system: gcc -m64 -O -g -Wall -fno-stack-protector -z execstack -z norelro \ bcecho.c -o bcecho64 A good way to start approaching a potentially-vulnerable program like this if you want to try constructing an attack is to first just that you can make the program crash by supplying a large input, which is a sign the program has some sort of memory safety bug; and then to more specifically look for the information needed to construct an attack. For a simple stack buffer overflow like will turn out to be possible in this example, the key information you'll need as an attacker is the location of the return address relative to the start of the overflow-vulnerable buffer, which tells you where to put your overwrite jump target in the program input. Today we'll start with a purely experimental approach to triggering the crash. A standard trick when trying an overflow is to overflow using data with a particular bit pattern, so you can see where the overflow data ends up being stored. One traditional thing to use for string data is a long sequence of letters capital A, which have the hex value 0x41. When you are trying to see where different data goes, it can help to use slightly different data, to distinguish the different parts. I like to use groups of repeated letters; since the stack is mostly managed in 8-byte units it's convenient to use groups of 8 letters, as in: AAAAAAAA (8 bytes) AAAAAAAABBBBBBBB (16 bytes) AAAAAAAABBBBBBBBCCCCCCCC (24 bytes) AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDD (32 bytes) AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDDEEEEEEEE (40 bytes) AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDDEEEEEEEEFFFFFFFF (48 bytes) AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDDEEEEEEEEFFFFFFFFGGGGGGGG (56 bytes) AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDDEEEEEEEEFFFFFFFFGGGGGGGGHHHHHHHH (64 bytes) AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDDEEEEEEEEFFFFFFFFGGGGGGGGHHHHHHHHIIIIIIII (72 bytes) AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDDEEEEEEEEFFFFFFFFGGGGGGGGHHHHHHHHIIIIIIIIJJJJJJJJ (80 bytes) AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDDEEEEEEEEFFFFFFFFGGGGGGGGHHHHHHHHIIIIIIIIJJJJJJJJKKKKKKKK (88 bytes) AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDDEEEEEEEEFFFFFFFFGGGGGGGGHHHHHHHHIIIIIIIIJJJJJJJJKKKKKKKKLLLLLLLL (96 bytes) AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDDEEEEEEEEFFFFFFFFGGGGGGGGHHHHHHHHIIIIIIIIJJJJJJJJKKKKKKKKLLLLLLLLMMMMMMMM (104 bytes) You can see a few different kinds of behaviors on these inputs. 8 bytes and 16 bytes you would be expect to be fine, since they fit within the buffer size. 24 bytes and 32 bytes are overflows, so they wouldn't be guaranteed to be safe, but the program does seem to crash, which suggests that either the extra bytes are only replacing padding, or they are corrupting some data that is not critical for safe operation. The 40 byte input causes the program to go into what appears to be an infinite loop, which is a sign of something weird happening. The 48 byte input cases a "bus error" crash, which is related to a segmentation fault and signals something low-level going wrong, but typically comes from an invalid data pointer value. On one system I tested the 56 byte input causes a bunch of extra output include the environment variables, which is also a sign of something weird. All of the longer inputs from here onward eventually end with a segfault, but it's not until the 104-byte input that we finally see the message about overflowing the buffer. These results definitely confirm that the program has a buffer overflow problem when the input is too long, but the different crashes aren't sufficiently distinguishable to tell at what point we're overwriting the return address. We'll get a better answer to that question with some other techniques in the future.