CMU15-213 Attack Lab
It’s been a while since I wrote posts about CMU’s renowned system course 15-213. Last month, I was primarily devoted to my research on SmartNICs and ML for failure detection, and traveling.
Let’s begin phase by phase.
Logistics
The writeup of this lab can be found here (the CS:APP website). You can download it from this blog as well.
The target file can be downloaded from my website as well. Open that using linux tar, or you may change the permissions of the file.
According to my experience, this lab does not work on virtual machines. Bare metal Ubuntu is preferred. I use NYU server instead.
Not working on my multi-tenant cloud server
Working on NYU bare metal Ubuntu20 server
Non-CMU student, when running the code, should add a -q flag, so that the program will not try to contact the grading service and then failed.
Phase 1: Simple Buffer Overflow Attack
Phase 1 is a very simple buffer overflow attack, and its main idea was went through during the lecture.
First use objdump -d to get the disassembled file, can be .txt or .asm. Then check the disas file.
Use Vim to search for
To inject the malicious return address, we first need to know how much buffer the
Ok, so the
1 | 00 00 00 00 |
Try it, works as expected.
Phase 2: Code injection
This level is a lot more interesting, I have got to say. The biggest challenge for me is to recall the fact that stack grows downwards (from bigger virtual addresses to smaller ones), while the binary code is executed bottom up (from smaller virtual addresses to bigger ones). So remember that in phase 1 we need to reverse the order of out input so that the malicious return address is in the correct order? In this phase, now that we are directly injecting malicious code, we do not need to reverse the order of the code, since they are executed bottom up, in accordance with the order that
Forgive me for speaking too much without giving you an idea what we are doing here.
In this level, we need to redirect the return address of the program to
Sounds challenging, right?
The basic idea is that: first we need to change the return address after the
Then we need to think about what code we would like to put in the stack we were given. I propose something like this, in assembly of course:
1 | mov $0x5561dc98, %rdi |
The first line puts the cookie into the register that holds the parameter value for procedure
The last piece of this puzzle is, what should the return address of the
So what is this lowest address? I suggest GDB again, your most faithful friend with Linux.
The answer is here, 0x5561dc78!
One last step before putting everything together. What is the binary representation of the assembly that we are injecting? To find out, first compile this assembly, and then objdump -d the output to find out.
Putting everything together, here is what we should input
this is indeed a valid answer for level 2 of the lab.
Phase 3: Advanced Code Injection
This phase is quite formidable, I have got to admit. Let’s first check out the requirement of the question, it gives us a lot of useful hints.
The code snippet is not entirely easy to understand, because it does seem a little bit whimsical. If you were able to break down the requirement, here is what we need to do:
jump to
, which is at address 0x4018fa of the virtual address space pass the address of the ASCII representation of our cookie to register %rdi, which serves as a parameter when we call the procedure
. Figure out a safe place within the stack to store the ASCII representation of our cookie, because some portion of the stack may be overwritten by the the code from
It is observable that we first need to figure out (3), i.e. a safe place that will not be overwritten, then figure out the code we inject to the buffer.
So what is the ASCII representation of the cookie, which for us is 0x59b997fa? How many bytes of space do we need to store the ASCII representation of it?
It seems that we need 8 bytes of space, and the ASCII code is 35 39 62 39 39 37 66 61.
So which place is safe to store these 8 bytes? In other words, which part of the stack will be overwritten when we call
First, try this input, then check how much of the 11111…1111 buffer space is changed by hexmatch in GDB. The return address is changed by buffer overflow to
1 | # convert hex to raw hex |
now that we are in
1 | (gdb) b \*0x040190b |
this is the buffer layout before calling
this is the buffer layout after calling
we can see that all 40 bytes have been overwritten. So we have to buffer overflow more, and store the address of the ASCII code at someplace after. Address starting at 0x5561dca8 seems unchanged after calling
So we want to overflow more than the typical 48 bytes. We want at least 56 bytes, and the last 8 bytes are for the ASCII.
Then we need to translate the code using the same technique: write the code, gcc -c, objdump -d
You see that we store the address of the ASCII to $rdi, change the return address to
We put the code at the beginning of the buffer, so the return address of
Putting everything together, here is the injection code:
1 | 48 c7 c7 a8 |
test it, it is indeed the answer.
Phase 4: Return-Oriented Attacks
This phase is quite easy, takes around 20 minutes to solve (the previous phase takes me hours…)
The key idea is to understand how to use return-oriented programming to inject malicious code. I think that the lab write-up did a pretty decent job, so let me just quote here.
The main difficulty then is to find the appropriate code segments and piece them together.
here is the requirement for this phase. If you are logical enough, here is the stuff that we need to do:
use popq instruction to put the cookie to %rdi
then jump to the procedure
As hinted by the author, here we only use popq and movq. Two gadgets are enough. Now let us take a look at the gadgets available!
We can only use the first 8 gadgets, from the <start_farm> to the <mid_farm>. Apparently, we need a popq to pop the cookie to a certain register, so we are looking for anything from 58 to 5f.
You can choose either <getval_280> or <addval_219>, which contains
1 | 58 90 c3 |
Note that there is no 5f in the code snippet farm, so we cannot pop directly to %rdi. We can however, later move the content from %rax to %rdi. So we are looking for 48 89 c7. <setval_426> provide the segment for that.
I choose <getval_280> and <setval_426>, the first at 0x4019cc, and the latter at 0x4019c5. The
1 | 00 00 00 00 |
in order to achieve that layout, we want out input to be
Test it out, and it is indeed the answer.
Phase5
CMU15-213 Attack Lab
http://peteryaonyu.github.io/2023/07/10/cmu15-213-attack-lab/