15-213 (Spring 2005) Section A - Recitation #2
TA: Kun Gao
Office Hours: Tuesday 1-2pm, Wednesday, 2-3pm
Location: Doherty Hall 4302D
Section Webpage: http://www.cs.cmu.edu/~kgao/course/15213/
- Quizzes are now online. There is a one minute warning. Make sure to keep your own time, so you don't get surprised when there is only 1 minute left. The next quiz (Quiz #2) is going out tonight, and you have a day to work on it.
- I will pass back your graded quizzes and labs in class so you can look at the feedback.
- Lab #2 (Bomb Lab) is out. It is due next Tuesday (Feb 8th) at 11:59pm
More on Lab #2
Each person has a different bomb. Bomb can only be run on the fish machines. Each time you type incorrect password, and the bomb explodes, you will be deducted 1/2 a point. Bomb take password from stdin and file, so you don't have to retype the password for each phase everytime.
How to Defuse a Bomb? (using GDB and OBJDUMP)
You can find the sample bomb i'm using at:
It is only for practice, so you can play around, and even if it explodes, it doesn't count for anything
First, lets see what happens when we run the bomb?
You will be prompted for some password (no prompt is given in the real thing!)
After you enter in some password, the bomb will examine your password, and either explode, or not.
The bomb is a binary file that runs on the fish machines. We need to examine the assembly code in more detail at this point. To do so, we use 'objdump' on the binary to generate the disassembled code (where -d is for disassemble).
'objdump -d bomb > bomb.asm'
We will also dump out the symbol table for the binary. The symbol table will be useful in tracing code that is linked as a library (such as printf, etc).
'objdump -t bomb > bomb.tbl'
At this point we have generated an assembly file of the binary bomb.
Now all we need to do is completely understand the assembly code, and we can defuse the bomb.
But this is a lot of code, and at times hard to understand. Even if we do a good job, we might make a single mistake and things will blow up X(. This is where the GDB (GNU DeBugger) comes in. It lets us step through the assembly code as it is running, and examine the contents of registers and memory, as well as set breakpoints. Breakpoints are points in the code that we want to always stop at when running through the code.
First we open our bomb assembly with emacs
Next, we open run gdb. We tell gdb that we want it to debug our program 'bomb'.
We can run the bomb in the debugger just as we would outside the debugger. Any time we want to panic out, we can use (Ctrl-C) to stop the program. We can even kill the program and start over if things look bad
Now we can start debugging. We wouldn't be using gdb if all we did was run the program in gdb. Instead, we would like to set up a good breakpoint in the code thats close to what is relevant. How do we pick a good location? We can always set a breakpoint at 'main', since every C program has a function called 'main'. Luckily, Dr. Evil accidently gave us 'bomb.c'. By examining this code, we see that we can place a good breakpoint at 'phase_1', as this is where our password is examined (examine bomb.c).
So in the gdb, we type the following to set a breakpoint:
'(gdb) break phase_1'
We can also examine all the break points we set with the 'info' command
'(gdb) info break'
Now we run the bomb as before, and enter any password we want, except the breakpoint will cause the program to stop (and print out the location of the program that we stopped on). It will always stop before executing the instruction you set the breakpoint on. We can find the same location in the assembly code of the bomb to see what is exactly happening. We can look at the content of all registers using 'info', as well as examine the contents of the call stack (which functions called which).
'(gdb) info register'
'(gdb) info stack'
From bomb.c we know that our password was first read from input, and then passed to phase_1 as its only argument. From this, we can try to decipher what phase_1 is doing to our input.
'phase_1' does the following:
804852c: 55 push %ebp (save old frame pointer)
804852d: 89 e5 mov %esp,%ebp (set new frame pointer)
804852f: 83 ec 10 sub $0x10,%esp (grow the stack)
8048532: ff 35 e4 97 04 08 pushl 0x80497e4 (push 0x80497e4 onto stack)
8048538: ff 75 08 pushl 0x8(%ebp) (push the first argument onto stack)
804853b: e8 98 00 00 00 call 80485d8 (call 0x80485d8)
8048540: 83 c4 10 add $0x10,%esp (shrink the stack)
8048543: 85 c0 test %eax,%eax (test %eax) (
8048545: 75 05 jne 804854c (jump to 0x804854c if Z!=0)
8048547: e8 c0 00 00 00 call 804860c (otherwise call explode_bom)
804854c: c9 leave (restore stack, and pop frame pointers)
804854d: c3 ret (return to caller)
It looks like call explode_bomb is probably very bad. Just to be safe, we can set a breakpoint there, so things don't actually explode even if we make a mistake
'(gdb) break explode_bomb'
We can step through the program one instruction at a time. This can be used to follow each instruction of the program
From the assembly code, we see that phase_1 pushes 0x80497e4 and its first argument (our input string) onto the stack as arguments to the strings_equal, then it will call strings_equal. Afterwards, it checks if the return value is 0, and either returns, or calls explode_bomb.
Now we just need to trace when string_equal will return 0 given arguments 0x80497e4 and our input string.
'strings_equal' does the following:
80485d8: 55 push %ebp (save old frame pointer)
80485d9: 89 e5 mov %esp,%ebp (set new frame pointer)
80485db: 83 ec 10 sub $0x10,%esp (grow the stack)
80485de: ff 75 0c pushl 0xc(%ebp) (push second argument onto stack)
80485e1: ff 75 08 pushl 0x8(%ebp) (push first argument onto stack)
80485e4: e8 77 fd ff ff call 8048360 <_init+0x28> (call 8048360)
80485e9: 83 c4 10 add $0x10,%esp (shrink the stack)
80485ec: 85 c0 test %eax,%eax (test %eax)
80485ee: 0f 94 c0 sete %al (set %al)
80485f1: 0f b6 c0 movzbl %al,%eax (zero extend %al to %eax)
80485f4: c9 leave (restore stack, and pop frame pointer)
80485f5: c3 ret (return to caller)
strings_equal again pushes its first two arguments onto the stack as arguments to the function 0x8048360 (this is not a function in our code, or it would have a label name). By looking at the symbol table output, we can see that this is just a call to strcmp. Man tells just that strcmp will returen 0 if the two strings are the same.
08048360 F *UND* 0000003a strcmp@@GLIBC_2.0
Now we just need to figure out how to make the arguments to strcmp the same. In particular, if we know what the string at 0x80497e4 is. We can use gdb to tell us what this is.
(gdb) x 0x80497e4
0x80497e4 : 0x0804873f
(gdb) x 0x0804873f
0x804873f <_IO_stdin_used+123>: 0x73736170
(gdb) x 0x08048743
0x8048743 <_IO_stdin_used+127>: 0x64726f77
(gdb) x 0x08048747
0x8048747 <_IO_stdin_used+131>: 0x0a732500
Since this is little endian, we should switch the order of the bytes in the word to: 0x70617373 0x776f7264 0x0025730a.
We notice that (by using man ascii), that 0x73 = 's', 0x61 = 'a', 0x70 = 'p', 0x64 = 'd', 0x72 = 'r', 0x6f = 'o', and 0x77 = 'w'. 0x00 terminals the string. So we should type 'password'.
Another way to do this is to use 'x' with format strings to display the character automatically (an added bonus is this will take care of endian issues)
(gdb) x/4c 0x0804873f
0x804873f <_IO_stdin_used+123>: 112 'p' 97 'a' 115 's' 115 's'
(gdb) x/4c 0x08048743
0x8048743 <_IO_stdin_used+127>: 119 'w' 111 'o' 114 'r' 100 'd'
(gdb) x/4c 0x08048747
0x8048747 <_IO_stdin_used+131>: 0 '\0' 37 '%' 115 's' 10 '\n'
Lets run the bomb in gdb, just in case if we made a mistake, and it explodes, we are still okay.
We can do the same for phase_2 of the sample bomb (just as we did for phase 1).
Here are the commands I've used so far:
Refer to the gdb notes online for quick reference:
- break label (i.e. break main, break phase_1)
- break *addr (i.e. break *0x80)
- info break
- info register
- x (i.e. x 0x0804873f)
- x/4c (same as x, but formats output as 4 ascii characters)