As a follow up to the crypto course from earlier this year, I enrolled in the software security course from the University of Maryland for something a bit more hands-on.
I wanted to write up a short review of the course here, especially since I haven’t seen many (any?) other security courses like this offered on Coursera.
The syllabus is what you would expect in a security course:
- Low-level, memory-based attacks
- Defenses against memory-based attacks
- Web security
- Secure design
- Automated code review with static analysis and symbolic execution
- Penetration testing
You can see that the course is roughly divided up into two different sections: attacking, and defending. Each section goes over the ways certain areas of software can be exploited (say, via SQL injection), and then it is followed up by how you would defend against that problem (sanitizing user input in this case).
The workload for this course was surprisingly light; I would often sit down early Monday morning and work through the week’s content before lunch. The same can probably be said of other courses; with Software Security I enjoyed it enough that it was easy to just work through all of it at once.
Each week has the usual set of lecture videos paired with a quiz, and then every other week had an assignment to go with it. Each assignment involves firing up a Linux VM and running through some problems, like taking down a vulnerable web app or exploiting stack overflows via GDB.
With courses I’ve taken in the past, I’ve gone the lone wolf approach and tried to do the content myself without asking for any help (besides reading books).
This time, I jumped into the ##softwaresec IRC channel on Freenode and got to know people there. Having to explain concepts to people was far more helpful at making sure I understood the material than simply taking notes and answering quiz questions.
This was a great course, and I’d love to see a follow-up to it in the future.
In order to better understand the material from the Software Security course on Coursera, I decided to take a stab at the challenges on Exploit Exercises.
So far it has been an amazing way to really cement the concepts from the course, so I decided to document my progress here for anybody else interested in making their way through the levels.
I’ve been running through these challenges by setting up the downloadable ISOs from the Exploit Exercises downloads page in VirtualBox, then
sshing in so I don’t have to deal with VirtualBox as much.
stack0, the program defines an
int variable named
modified, followed by a 64 byte
char buffer. It then gets input from
buffer as its destination, before printing a string dependent on the contents of
By passing input with a length greater than 64 bytes to the program, the
buffer variable will overflow onto the
modified variable, giving you the target output. The first 64 bytes will fill the buffer, with the 65th byte overwriting the value of
echo aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | ./stack0
Typing all those characters is exhausting though. Let’s use Ruby.
ruby -e "puts('A' * 65)" | ./stack0
This program expands upon the previous example by requiring that you use a specific value as your overflow. It works similarly to
stack0, except the exploit only succeeds when
To fill the
buffer variable, we again need 64 bytes of padding, followed by the value to overwrite
modified with. The value
0x61626364 conveniently matches the first 4 lowercase alphabetical characters in the ASCII character set (
abcd). By adding that to the end of the padding string, the conditional should succeed.
There’s one caveat: the VM that Protostar is running in is little-endian, meaning that the order of the characters needs to be reversed before being appended to the string.
./stack1 `ruby -e "puts(('A' * 64) + 'dcba')"`
This challenge is very similar to the previous one, except that the malicious string needs to be set as an environment variable instead.
Note that I’m also using hex values directly, as these don’t nicely translate to ASCII characters.
GREENIE=`ruby -e "puts(('A' * 64) + \"\x0a\x0d\x0a\x0d\")"`
This challenge asks us to overwrite a pointer value stored on the stack with that of another function. The tricky aspect of this one is finding the address of the function we want to call.
objdump is a tool which prints information about binaries – time to put it to use by finding the address of the
$ objdump -d stack3 | grep win
The address of
win looks to be
0x08048424. Nice! That will serve as the value of our overflow, formatted as little-endian once again.
ruby -e 'puts(("A" * 64) + "\x24\x84\x04\x08")' | ./stack3
This was a pretty fun challenge to figure out. The idea is that you have to overwrite the
eip pointer on the stack, while dealing with the compiler which will insert an undetermined amount of padding between you and
eip on the stack.
After some research, I found a few mentions of a technique used to exploit code like this. The idea is to generate a unique, non-repeating string, and use that to find the offset by providing it as the value of the overflow.
I used this handy tool to generate a string. Pass that string to the program, and look at the result when it hits
eip. (A quick note on this part: you will need to open and run the program in
gdb to see the value that is being called.)
Take the value and pass it back to the buffer overflow tool to figure out the offset (76, in this case). Use
objdump to find the address of
win once more, and then you have yourself an exploit.
ruby -e 'puts(("A" * 76) + "\xf4\x83\x04\x08")' | ./stack4