=============================== Lec05: Bypassing stack canaries =============================== 1. Revisiting "crackme0x00" =========================== This is the reversed-engineered source code of the crackme0x00 challenge that we are familiar with: ------------------------------------------------------------ $ cat crackme0x00.c #include #include #include #include int main(int argc, char *argv[]) { char buf[16]; printf("IOLI Crackme Level 0x00\n"); printf("Password:"); scanf("%s", buf); if (!strcmp(buf, "250382")) printf("Password OK :)\n"); else printf("Invalid Password!\n"); return 0; } ------------------------------------------------------------ We are going to compile this source code into four different binaries with following options: $ make cc -m32 -g -O0 -fno-stack-protector -z execstack -o crackme0x00-nossp-exec crackme0x00.c bin/checksec.sh --file crackme0x00-nossp-exec RELRO STACK CANARY NX PIE RPATH RUNPATH FILE Partial RELRO No canary found NX disabled PIE enabled No RPATH No RUNPATH crackme0x00-nossp-exec --------------- ----------- cc -m32 -g -O0 -fno-stack-protector -o crackme0x00-nossp-noexec crackme0x00.c bin/checksec.sh --file crackme0x00-nossp-noexec RELRO STACK CANARY NX PIE RPATH RUNPATH FILE Partial RELRO No canary found NX enabled PIE enabled No RPATH No RUNPATH crackme0x00-nossp-noexec --------------- ---------- cc -m32 -g -O0 -o crackme0x00-ssp-exec -z execstack crackme0x00.c bin/checksec.sh --file crackme0x00-ssp-exec RELRO STACK CANARY NX PIE RPATH RUNPATH FILE Partial RELRO Canary found NX disabled PIE enabled No RPATH No RUNPATH crackme0x00-ssp-exec ------------ ----------- cc -m32 -g -O0 -o crackme0x00-ssp-noexec crackme0x00.c bin/checksec.sh --file crackme0x00-ssp-noexec RELRO STACK CANARY NX PIE RPATH RUNPATH FILE Partial RELRO Canary found NX enabled PIE enabled No RPATH No RUNPATH crackme0x00-ssp-noexec ------------ ---------- There are a few interesting compilation options that we used: 1. -fno-stack-protector: do not use a stack protector 2. -z execstack: make its stack "executable" So we name each binary with a following convention: crackme0x00-{ssp|nossp}-{exec|noexec} 2. Let's crash the "crackme0x00" binary ======================================= crackme0x00-nossp-exec behaves exactly same as "crackme0x00". Not sparingly, it crashes with a long input: $ echo aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | ./crackme0x00-nossp-exec IOLI Crackme Level 0x00 Password:Invalid Password! Segmentation fault (core dumped) What about crackme0x00-ssp-exec that is compiled with a stack protector? $ echo aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | ./crackme0x00-ssp-exec IOLI Crackme Level 0x00 Password:Invalid Password! *** stack smashing detected ***: ./crackme0x00-ssp-exec terminated Aborted (core dumped) The "stack smashing" is detected so the binary simply prevents itself from exploitation; resulting in a crash instead of being hijacked. You might want to run gdb to figure out what's going on this binary: $ gdb ./crackme0x00-ssp-noexec Reading symbols from ./crackme0x00-ssp-noexec...done. (gdb) r Starting program: crackme0x00-ssp-noexec IOLI Crackme Level 0x00 Password:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Invalid Password! *** stack smashing detected ***: crackme0x00-ssp-noexec terminated Program received signal SIGABRT, Aborted. 0xf7fdb440 in __kernel_vsyscall () (gdb) bt #0 0xf7fdb440 in __kernel_vsyscall () #1 0xf7e4d687 in raise () from /lib/i386-linux-gnu/libc.so.6 #2 0xf7e50ab3 in abort () from /lib/i386-linux-gnu/libc.so.6 #3 0xf7e87fd3 in ?? () from /lib/i386-linux-gnu/libc.so.6 #4 0xf7f1ab8b in __fortify_fail () from /lib/i386-linux-gnu/libc.so.6 #5 0xf7f1ab1a in __stack_chk_fail () from /lib/i386-linux-gnu/libc.so.6 #6 0x080485ae in main (argc=97, argv=0xffffd734) at crackme0x00.c:19 3. Let's analyze! ================= To figure out, how two binaries are different. You (so kind!) provide you a script, "./diff.sh" that can easily compare two binaries. $ $ ./diff.sh crackme0x00-nossp-noexec crackme0x00-ssp-noexec --- /dev/fd/63 2016-09-23 02:37:45.575626312 +0000 +++ /dev/fd/62 2016-09-23 02:37:45.575626312 +0000 @@ -2,16 +2,23 @@ push %ebp mov %esp,%ebp and $0xfffffff0,%esp - sub $0x20,%esp - movl $0x80485d0,(%esp) - movl $0x80485e8,(%esp) + sub $0x30,%esp + mov 0xc(%ebp),%eax + mov %eax,0xc(%esp) + mov %gs:0x14,%eax + mov %eax,0x2c(%esp) + xor %eax,%eax + movl $0x8048640,(%esp) + movl $0x8048658,(%esp) mov %eax,0x4(%esp) - movl $0x80485f2,(%esp) - movl $0x80485f5,0x4(%esp) + movl $0x8048662,(%esp) + movl $0x8048665,0x4(%esp) mov %eax,(%esp) test %eax,%eax - movl $0x80485fc,(%esp) - movl $0x804860b,(%esp) + movl $0x804866c,(%esp) + movl $0x804867b,(%esp) mov $0x0,%eax + mov 0x2c(%esp),%edx + xor %gs:0x14,%edx ret End of assembler dump. Two notable differences are at the function prologue and epilogue. There is an extra value (%gs:0x14) placed right after the frame pointer on the stack: + mov %gs:0x14,%eax + mov %eax,0x2c(%esp) + xor %eax,%eax And it validates if the inserted value is same right before returning to its caller: + mov 0x2c(%esp),%edx + xor %gs:0x14,%edx + je 0x80485ae + call 0x80483d0 <__stack_chk_fail@plt> __stack_chk_fail() is the function you observed in the gdb's backtrace. 4. Stack Canary =============== This extra value is called, "canary" (a bird, umm why?). More precisely, what are these values? $ gdb ./crackme0x00-ssp-exec (gdb) br *0x08048533 (gdb) r => 0x08048533 <+22>: mov %eax,0x2c(%esp) (gdb) info r eax eax 0x8a2db00 144890624 (gdb) r => 0x08048533 <+22>: mov %eax,0x2c(%esp) (gdb) info r eax eax 0x41d63500 1104557312 Did you notice the canary value keeps changing? This is great because attackers truly guess (i.e., bypass) the canary value before exploitation. 5. Bypassing Stack Canary ========================= However, what if the stack canary implementation is not "perfect", meaning that an attacker might be able to guess (i.e., %gs:0x14)? Let's check out this binary: $ objdump -d ./crackme0x00-ssp ... Instead of this: mov %gs:0x14,%eax mov %eax,0x2c(%esp) xor %eax,%eax What about this? This implementation uses a "known" value (i.e., 0xdeadbeef) as a stack canary. movl $0xdeadbeef,0x2c(%esp) So the stack should be like: |<-- 0x14 ------------>|+--- ebp top v [ [ ][canary][fp][ra][ ....] |<---- 0x30 ------------------->| How could we exploit this program? like last week's tutorial?