Debugger, memory layout, assembler
- Log in to the test system via ssh (hostname and logins provided on the blackboard).
- This test system has the following prerequisites already installed, this is just for reference when trying it on your own systems (install as root):
apt-get install gcc build-essential nasm gdb prelink perl
- Optional: set your locale if you wish some other language messages:
export LANG=en_US.UTF-8
- Download stack-demo.c: copy link location and use wget like this -
wget -O stack-demo.c https://courses.cs.ut.ee/2018/secprog/spring/uploads/Main/stack-demo.c
- Read, discuss and understand it: what functions are called by what functions, what instances of variables are there during recursion, what does each function return?
- Compile it:
gcc -o stack-demo stack-demo.c -g
- Run it under debugger:
gdb ./stack-demo
- Put breakpoints to all the functions:
b main b rec b stop_it
- Start the program:
run
- Useful gdb commands:
cont(inue) | continue execution |
r | restart execution of the program |
s | step (execution one line at a time) |
l | list the source (can take line number as argument) |
si | step into (execute one machine instruction at a time) |
set disassembly-flavor intel | selects disassembly syntax (you want to use this before first disassembly) |
disass symbolname | disassemble machine code |
disass 0xXXX 0xXXX | disassemble machine code in given address range |
info reg | contents of all the registers |
x/150x $esp | dump stack contents (amount of memory, $esp = register) |
b N | breakpoint on line N (source line) |
b *0xXXXX | breakpoint on memory address 0xXXXX |
set args XXXX | set command line arguments to the program |
- Your task: examine the contents of stack on a running program and find
- return address (also find the address of return address: 0xbf...... or 0xffff.... when running on 64-bit kernel like in test system)
- arguments to the function (find them by values)
- local variables of the function (find them by values)
- Disassemble all the functions and try to understand them. Find
- calls to libc functions printf and sprintf,
- initialization of local variables,
- how is the value returned from functions?
- To read x86 assembly language (Intel syntax):
push regname | push value of the register to stack (esp decreases automatically on push) |
push 0x1234 | push a imediate constant to stack |
push [reg+8] | push a value from memory, address = register + offset |
mov regname, value | assignment to a register |
mov DWROD PTR [reg2], reg1 | store reg1 to memory address shown in another register |
lea | same, for memory addresses |
sub,add,xor | arithmetics, to arguments, assigned to the second one! |
cmp* ... | comparision (sets flags) |
jle, jge | conditional goto |
jmp addr | unconditional goto |
call addr | function call (address of the next instruction pushed to stack!) |
pop regname | pop a value from stack (esp increases) |
leave | mov ebp,esp; pop ebp |
ret | return to address in stack |
- Some more broken programs: overflow1.c overflow2.c. Compile them and create core dump with huge input values:
- Allow creation of core files - max size in kB or unlimited; ulimit influences only this shell and subprocesses
ulimit -c unlimited
- Helpful constructions for large input (use larger sizes and real program names)!
./overflow1 $(perl -e 'print "A"x20') perl -e 'print "A"x20' | ./overflow2
- Examine core files (make sure the last core was generated from overflow1):
gdb overflow1 core
- What data was overwritten? What is the contents of registers?
- The next goal of the lab is to try to construct a working shellcode that just spawns a shell from a vulnerable program.
- (already done in lab environment) turn off address space randomization as root:
sudo sysctl -w kernel.randomize_va_space=0
This is needed if the attack code does not cope with randomization - like our code does at first. - Download and compile s-proc wrapper - source: s-proc.c
gcc -o s-proc s-proc.c
- Turn off nonexecutable stack protection on the resulting s-proc binary:
/usr/sbin/execstack -s s-proc
This is needed to turn off hardware NX bit on non-code areas to be able to execute the loaded code. - Download exit.asm, read and understand it (syscall exit, eax=syscall number, ebx=syscall argument) and assemble it to machine code:
nasm -o exit exit.asm
- Run the machine code fragment with the help of s-proc and look at the exit code:
./s-proc -e exit echo $?
- Download exit2.asm, read and understand why it does the same. Try if it really does.
- Compare the machine code dump of exit and exit2. Which would work for shellcode injection? Why?
ndisasm -b32 exit ndisasm -b32 exit2
- Download overflow3.c and compile it:
gcc -o overflow3 overflow3.c -g
- Turn off nonexecutable stack protection on the resulting binary:
/usr/sbin/execstack -s overflow3
This is needed to turn off hardware NX bit on non-code areas to simplify our exploit code. - Download exploit template stack.asm, read and understand it.
- The goal of the following items is to change NNNN so that crash EIP equals 0x60606060 - decrease if 0xff are there, increase otherwise. Start with slightly overflowing the buffer (look at overflow3 source to check buffer size). Repeat, including reassembling the code with nasm.
- Hint: running the program:
./overflow3 $(cat stack)
Examining the core:gdb ./overflow3 core
When done, examine the stack in gdb:x/1000x $esp-9000
Here the first column is the address of first data column in the same row. Find the address of our code and put it instead of 0x60606060 in the code. - To find the start of our code, disassemble the address range you suspect it to be in:
disass 0xffffXXXX,0xffffYYYY
- To see the bytes of your exploit program, see
ndisasm -b32 stack | less
You should get a sh prompt if you succeed - repeat until you do.
Based on http://www.safemode.org/files/zillion/shellcode/doc/Writing_shellcode.html