Detailed explanation of PWN protection mechanism

Keywords: pwn

Explain and summarize the protection mechanism encountered in the pwn process.

Stack Canaries

Put a well written: PWN Canary learning - sarace - blog Park (

brief introduction

stack canaries are canaries from underground coal mines. They can detect gas leakage faster than miners and have the function of early warning. When this concept is applied to stack protection, a random canary value is set at the bottom of the stack when initializing a stack frame. Before the stack frame is destroyed, test whether the value is "dead", that is, whether it is changed. If it is changed, it indicates that stack overflow occurs and the program ends in another process, so as to avoid successful exploitation of vulnerabilities.

It is mainly divided into three categories: Terminator, random and random XOR. The specific implementations include StackGuard, StackShied, ProPoliced, etc.

  • terminator canaries: considering that many stack overflows are caused by improper string operations, and these strings end in NULL \x00 and are truncated by \ x00, all terminators set the low order to \ x00 to prevent disclosure and forgery. Truncated characters also include CR(0x0d),LF(0x0a), EOF(0xff). In fact, the highest position of the last part is 00, \ x00ab1245

  • random canaries: in order to prevent canaries from being guessed by attackers, canaries are usually randomly generated during program initialization and stored in a safe place.

  • random canaries XOR: in fact, there is one more XOR operation than random canaries. Whether the data of canaries or XOR is tampered, it will be detected. xor eax,DWORD PTR gs:0x14

Implementation principle

Under Linux, there is a fs register to save the thread local storage TLS. TLS is mainly used to avoid the conflict caused by multiple threads accessing the same global variable or static variable. 64 bit uses fs register, offset at 0x28. 32 bits are registered with gs, offset at 0x14. This location stores the stack_guard, i.e. reserved and Canary, is finally compared with canary in the stack to detect overflow.

The specific process is to use_ dl_random to generate stack_chk_guard, and then use THREAD_SET_STACK_GUARD to set the stack_ The lowest order of guard and canary is set to \ x00. If_ dl_random==NULL, then canary is the fixed value.

If the program does not define THREAD_SET_STACK_GUARD macro, then it will be used directly_ stack_chk_guard, which is a global variable placed in the. bss section.

TLS structure

x86 32-bit

mov    eax,gs:0x14
mov    DWORD PTR [ebp-0xc],eax

mov    eax,DWORD PTR [ebp-0xc]
xor    eax,DWORD PTR gs:0x14
je     0x80492b2 <vuln+103> # Normal function return
call   0x8049380 <__stack_chk_fail_local> # Call error handler
        Address |                 |  
                | args            |
                | return address  |
                | old ebp         |
      ebp =>    +-----------------+
                | ebx             |
    ebp-4 =>    +-----------------+
                | unknown         |
    ebp-8 =>    +-----------------+
                | canary value    |
   ebp-12 =>    +-----------------+
                | local variable         |
        Low     |                 |

64 bit

mov    rax,QWORD PTR fs:0x28
mov    QWORD PTR [rbp-0x8],rax

mov    rax,QWORD PTR [rbp-0x8]
xor    rax,QWORD PTR fs:0x28
je     0x401232 <vuln+102> # Normal function return
call   0x401040 <__stack_chk_fail@plt> # Call error handler
        Address |                 |
                | args            |
                | return address  |
                | old ebp         |
      rbp =>    +-----------------+
                | canary value    |
    rbp-8 =>    +-----------------+
                | local variable         |
        Low     |                 |


Canary. C smash: smash, break, break

int main(){
  char buf[10];
gcc -fno-stack-protector canary.c -o canary_no.out
gcc -fstack-protector canary.c -o canary_pro.out

Bypass mode

  • Leak canary in memory, such as printing through format string vulnerability

  • One by one blasting, but it is generally a multithreaded program. canary does not change until a new thread is generated. The highest bit is 00.

  • Hijack_ stack_chk_fail function. If canary verification fails, this function will be performed__ stack_ chk_ The fail function is a common delay binding function, which can be hijacked by modifying the GOT table.

  • Overwrite the canary in the thread local storage TLS. The overflow size is relatively large and can be used. Modify Canary on the stack and canary in TLS at the same time


brief introduction

No execute (NX) means not executable. Its principle is to identify the memory page where the data is located as not executable.

In Linux, after the program is loaded into memory, the. text section is marked as executable, the. Data. BSS section is marked as non executable, and the stack is unknown. The traditional way of modifying the GOT table is no longer feasible. However, the code reuse attack ret2libc cannot be prevented


Through the compilation option, strcmp comparison is used in_ handle_option function setting link_ The execstack and noexecstack of info structure are true and false.

In BFD_ elf_ size_ dynamic_ In the sections function, according to link_info to set elf_stack_flags = PF_R | PF_W | PF_X

When NX is turned on, there are only two without PF_X.

In_ bfd_ elf_ map_ sections_ to_ In the segments function, set stuct elf_ segment_ P in map structure_ flags=elf_ stack_ flags. The compilation setting is completed.

At load time, elf is called_ load_ Binary function, according to the above p_flags to set the executable_stack=EXSTACK_ENABLE_X


Set executable_stack pass in setup_arg_pages, through vm_flags sets the virtual memory space vma of the process.

When the program counter points to an unknowable memory page, a page error is triggered.



void vuln_func(){
    char buf[128];
int main(int argc , char*argv[]){
    write(STDOUT_FILENO,"Hello world!\n",13);


brief introduction

Most attacks need to know the memory layout of the program. Introducing randomization of memory layout can increase the difficulty of vulnerability exploitation. Address space layout randomization ASLR (address space layout randomization)

ASLR /proc/sys/kernel/randomize_ va_ There are three cases of space:

ASLR Executable PLT Heap Stack Shared Libraries
0 unchanged unchanged unchanged unchanged unchanged
1 unchanged unchanged unchanged change change
2 unchanged unchanged change change change
2+pie change change change change change

PIE location independent executable is implemented on the compiler of the application layer. By compiling the program into location independent code PIC, the program can be loaded into any location, just like a special shared library. PIE can affect performance to some extent.


int main(){
    int stack;
    int *heap=malloc(sizeof(int));
    void *handle = dlopen("",RTLD_NOW | RTLD_GLOBAL);

    printf("heap: %p\n",heap);
    printf("stack: %p\n",&stack);
    printf("libc: %p\n",handle);
    return 0;

cat /proc/sys/kernel/randomize_va_space
echo 0/1/2 > /proc/sys/kernel/randomize_va_space

ASLR=2 and PIE is turned on


brief introduction

Buffer overflow often occurs when the program calls some dangerous functions, such as memcpy. When the length of the source string is greater than the destination buffer, buffer overflow will occur.

FORTIFY_SOURCE is essentially a check and replace mechanism, a security patch for GCC and glibc.

Checking the danger function and replacing it with a safety function will not have a great impact on the performance of the program. Currently, it supports memcpy, memmove, memset, strcpy, strncpy, strcat, strncat,sprintf, vsprintf, snprintf, vsnprintf, gets, etc.


Buffer overflow check to secure function_ strcpy_chk() as an example, you can see that this function determines whether the source data length is greater than the destination buffer. If yes, it is called_ chk_fail(), otherwise memcpy will be called to execute normally.

Format string check to secure function_ printf_chk() as an example, for% N and% n $format strings.


int main(int argc, char*argv[]){
    char buf1[10],buf2[10],*s;
    int num;

    memcpy(buf1,argv[1],10);           //safe
    printf("%s %s\n",buf1,buf2);

    memcpy(buf1,argv[2],atoi(argv[3])); //unknown
    printf("%s %s\n",buf1,buf2);

    //memcpy(buf1,argv[1],11);         //unsafe

    s=fgets(buf1,11,stdin);            //fmt unknown

Use GDB pwndbg to decompile main

Generate fortify0 1 2 using options 0 1 2

GDB pwndbg fortify1, you can see that the security function is replaced, but printf is not replaced.

Dump of assembler code for function main:
   0x0000000000001175 <+0>:     push   r12
   0x0000000000001177 <+2>:     push   rbp
   0x0000000000001178 <+3>:     push   rbx
   0x0000000000001179 <+4>:     sub    rsp,0x20
   0x000000000000117d <+8>:     mov    rbx,rsi
   0x0000000000001180 <+11>:    mov    rax,QWORD PTR [rsi+0x8]
   0x0000000000001184 <+15>:    mov    rdx,QWORD PTR [rax]
   0x0000000000001187 <+18>:    mov    QWORD PTR [rsp+0x16],rdx
   0x000000000000118c <+23>:    movzx  eax,WORD PTR [rax+0x8]
   0x0000000000001190 <+27>:    mov    WORD PTR [rsp+0x1e],ax
   0x0000000000001195 <+32>:    movabs rax,0x4242424241414141
   0x000000000000119f <+42>:    mov    QWORD PTR [rsp+0xc],rax
   0x00000000000011a4 <+47>:    mov    WORD PTR [rsp+0x14],0x43
   0x00000000000011ab <+54>:    lea    r12,[rsp+0xc]
   0x00000000000011b0 <+59>:    lea    rbp,[rsp+0x16]
   0x00000000000011b5 <+64>:    mov    rdx,r12
   0x00000000000011b8 <+67>:    mov    rsi,rbp
   0x00000000000011bb <+70>:    lea    rdi,[rip+0xe42]        # 0x2004
   0x00000000000011c2 <+77>:    mov    eax,0x0
   0x00000000000011c7 <+82>:    call   0x1030 <printf@plt>
   0x00000000000011cc <+87>:    mov    rdi,QWORD PTR [rbx+0x18]
   0x00000000000011d0 <+91>:    mov    edx,0xa
   0x00000000000011d5 <+96>:    mov    esi,0x0
   0x00000000000011da <+101>:   call   0x1050 <strtol@plt>
   0x00000000000011df <+106>:   movsxd rdx,eax
   0x00000000000011e2 <+109>:   mov    rsi,QWORD PTR [rbx+0x10]
   0x00000000000011e6 <+113>:   mov    ecx,0xa
   0x00000000000011eb <+118>:   mov    rdi,rbp
   0x00000000000011ee <+121>:   call   0x1040 <__memcpy_chk@plt>
   0x00000000000011f3 <+126>:   mov    rsi,QWORD PTR [rbx+0x8]
   0x00000000000011f7 <+130>:   mov    edx,0xa
   0x00000000000011fc <+135>:   mov    rdi,r12
   0x00000000000011ff <+138>:   call   0x1070 <__strcpy_chk@plt>
   0x0000000000001204 <+143>:   mov    rdx,r12
   0x0000000000001207 <+146>:   mov    rsi,rbp
   0x000000000000120a <+149>:   lea    rdi,[rip+0xdf3]        # 0x2004
   0x0000000000001211 <+156>:   mov    eax,0x0
   0x0000000000001216 <+161>:   call   0x1030 <printf@plt>
   0x000000000000121b <+166>:   mov    rsi,QWORD PTR [rbx+0x8]
   0x000000000000121f <+170>:   mov    ecx,0xa
   0x0000000000001224 <+175>:   mov    edx,0xb
   0x0000000000001229 <+180>:   mov    rdi,rbp
   0x000000000000122c <+183>:   call   0x1040 <__memcpy_chk@plt>
   0x0000000000001231 <+188>:   mov    edx,0xa
   0x0000000000001236 <+193>:   lea    rsi,[rip+0xdce]        # 0x200b
   0x000000000000123d <+200>:   mov    rdi,r12
   0x0000000000001240 <+203>:   call   0x1070 <__strcpy_chk@plt>
   0x0000000000001245 <+208>:   mov    rcx,QWORD PTR [rip+0x2e04]        # 0x4050 <stdin@GLIBC_2.2.5>
   0x000000000000124c <+215>:   mov    edx,0xb
   0x0000000000001251 <+220>:   mov    esi,0xa
   0x0000000000001256 <+225>:   mov    rdi,rbp
   0x0000000000001259 <+228>:   call   0x1060 <__fgets_chk@plt>
   0x000000000000125e <+233>:   lea    rsi,[rsp+0x8]
   0x0000000000001263 <+238>:   mov    rdi,rbp
   0x0000000000001266 <+241>:   mov    eax,0x0
   0x000000000000126b <+246>:   call   0x1030 <printf@plt>
   0x0000000000001270 <+251>:   mov    eax,0x0
   0x0000000000001275 <+256>:   add    rsp,0x20
   0x0000000000001279 <+260>:   pop    rbx
   0x000000000000127a <+261>:   pop    rbp
   0x000000000000127b <+262>:   pop    r12
   0x000000000000127d <+264>:   ret    
End of assembler dump.

GDB pwndbg fortify2 disas main, you can see that printf has also been replaced with a security function.

Dump of assembler code for function main:
   0x0000000000001175 <+0>:     push   r12
   0x0000000000001177 <+2>:     push   rbp
   0x0000000000001178 <+3>:     push   rbx
   0x0000000000001179 <+4>:     sub    rsp,0x20
   0x000000000000117d <+8>:     mov    rbx,rsi
   0x0000000000001180 <+11>:    mov    rax,QWORD PTR [rsi+0x8]
   0x0000000000001184 <+15>:    mov    rdx,QWORD PTR [rax]
   0x0000000000001187 <+18>:    mov    QWORD PTR [rsp+0x16],rdx
   0x000000000000118c <+23>:    movzx  eax,WORD PTR [rax+0x8]
   0x0000000000001190 <+27>:    mov    WORD PTR [rsp+0x1e],ax
   0x0000000000001195 <+32>:    movabs rax,0x4242424241414141
   0x000000000000119f <+42>:    mov    QWORD PTR [rsp+0xc],rax
   0x00000000000011a4 <+47>:    mov    WORD PTR [rsp+0x14],0x43
   0x00000000000011ab <+54>:    lea    r12,[rsp+0xc]
   0x00000000000011b0 <+59>:    lea    rbp,[rsp+0x16]
   0x00000000000011b5 <+64>:    mov    rcx,r12
   0x00000000000011b8 <+67>:    mov    rdx,rbp
   0x00000000000011bb <+70>:    lea    rsi,[rip+0xe42]        # 0x2004
   0x00000000000011c2 <+77>:    mov    edi,0x1
   0x00000000000011c7 <+82>:    mov    eax,0x0
   0x00000000000011cc <+87>:    call   0x1070 <__printf_chk@plt>
   0x00000000000011d1 <+92>:    mov    rdi,QWORD PTR [rbx+0x18]
   0x00000000000011d5 <+96>:    mov    edx,0xa
   0x00000000000011da <+101>:   mov    esi,0x0
   0x00000000000011df <+106>:   call   0x1040 <strtol@plt>
   0x00000000000011e4 <+111>:   movsxd rdx,eax
   0x00000000000011e7 <+114>:   mov    rsi,QWORD PTR [rbx+0x10]
   0x00000000000011eb <+118>:   mov    ecx,0xa
   0x00000000000011f0 <+123>:   mov    rdi,rbp
   0x00000000000011f3 <+126>:   call   0x1030 <__memcpy_chk@plt>
   0x00000000000011f8 <+131>:   mov    rsi,QWORD PTR [rbx+0x8]
   0x00000000000011fc <+135>:   mov    edx,0xa
   0x0000000000001201 <+140>:   mov    rdi,r12
   0x0000000000001204 <+143>:   call   0x1060 <__strcpy_chk@plt>
   0x0000000000001209 <+148>:   mov    rcx,r12
   0x000000000000120c <+151>:   mov    rdx,rbp
   0x000000000000120f <+154>:   lea    rsi,[rip+0xdee]        # 0x2004
   0x0000000000001216 <+161>:   mov    edi,0x1
   0x000000000000121b <+166>:   mov    eax,0x0
   0x0000000000001220 <+171>:   call   0x1070 <__printf_chk@plt>
   0x0000000000001225 <+176>:   mov    rsi,QWORD PTR [rbx+0x8]
   0x0000000000001229 <+180>:   mov    ecx,0xa
   0x000000000000122e <+185>:   mov    edx,0xb
   0x0000000000001233 <+190>:   mov    rdi,rbp
   0x0000000000001236 <+193>:   call   0x1030 <__memcpy_chk@plt>
   0x000000000000123b <+198>:   mov    edx,0xa
   0x0000000000001240 <+203>:   lea    rsi,[rip+0xdc4]        # 0x200b
   0x0000000000001247 <+210>:   mov    rdi,r12
   0x000000000000124a <+213>:   call   0x1060 <__strcpy_chk@plt>
   0x000000000000124f <+218>:   mov    rcx,QWORD PTR [rip+0x2dfa]        # 0x4050 <stdin@GLIBC_2.2.5>
   0x0000000000001256 <+225>:   mov    edx,0xb
   0x000000000000125b <+230>:   mov    esi,0xa
   0x0000000000001260 <+235>:   mov    rdi,rbp
   0x0000000000001263 <+238>:   call   0x1050 <__fgets_chk@plt>
   0x0000000000001268 <+243>:   lea    rdx,[rsp+0x8]
   0x000000000000126d <+248>:   mov    rsi,rbp
   0x0000000000001270 <+251>:   mov    edi,0x1
   0x0000000000001275 <+256>:   mov    eax,0x0
   0x000000000000127a <+261>:   call   0x1070 <__printf_chk@plt>
   0x000000000000127f <+266>:   mov    eax,0x0
   0x0000000000001284 <+271>:   add    rsp,0x20
   0x0000000000001288 <+275>:   pop    rbx
   0x0000000000001289 <+276>:   pop    rbp
   0x000000000000128a <+277>:   pop    r12
   0x000000000000128c <+279>:   ret    
End of assembler dump.

According to the fortify1 test result, an overflow occurred in strcpy and was detected. However, the format string vulnerability can still be used.

Using fortify2 experiment,% N and% n $were detected. And% n $needs to be continuously available after% 1$x. only one is printed in the figure below.


brief introduction

When delay binding is enabled, symbol parsing only occurs when it is used for the first time. This process is carried out through the PLT table. After the parsing is completed, the corresponding GOT table entry will be modified to the correct function address. Therefore, in the case of delayed binding,. got.plt must be writable. An attacker can hijack the program by tampering with the address.

RELRO (relocation read only) mechanism is proposed to solve the security problem of delayed binding. Set the symbol redirection table to read-only, or resolve and bind all dynamic symbols when the program starts, so as to prevent GOT from being tampered with. There are two forms of RELRO:

  • Partial RELRO: some segments (. Dynamic,. Got, etc.) will be marked as read-only after initialization. They are enabled by default.

  • Full relro: except for Partial RELRO, delayed binding is prohibited. All imported symbols will be parsed at the beginning. The. got.plt segment will be fully initialized as the final address of the objective function and marked as read-only by mprotect. However, the. got.plt will be merged into. got, and this segment will not be seen. Will affect performance.


relro.c means to enter a hexadecimal address and write 4141 to that address

int main(int argc,char*argv[]){
    size_t * p=(size_t*)strtol(argv[1],NULL,16);
    printf("RELRO: %x\n",(unsigned int )*p);
    return 0;

The experiment failed. According to the book, there was a problem

In the dynamic relocation table, main is always R_X86_64_GLOB_DAT, the book should be the same as printf.

Conclusion: norelro can modify. got and. got.plt

partial can modify. got.plt

None of full can be modified

The conditions that cannot be modified are as follows. Of course, there may be other reasons


When there is delayed binding, call will jump to printf@plt Then jmp to the. got.plt item, and then jump back for symbol binding. After completion, the. got.plt is modified to the real function address.

When there is no delay binding, all parsing work is completed when the program is loaded. Execute the call instruction to jump to the corresponding. item, and then jmp to the corresponding. Got item. The parsed function address has been saved.

Compilation options summary

stack canaries

-fstack-protector        yes alloca Series of functions and functions with internal buffers greater than 8 bytes enable protection
-fstack-protector-strong Added protection for functions containing local array and address references
-fstack-protector-all    Enable protection for all functions
-fstack-protector-explicit To include stack_protect Property to enable protection
-fno-stack-protector       Disable protection


-z execstack
-z no execstack




-fpic   Generate location independent code for shared libraries
-pie    The location of the generated dynamic link is independent of the executable file, which usually needs to be specified at the same time-fpie
-no-pie Do not generate dynamically linked location independent executables
-fpie   be similar to-fpic´╝îHowever, the generated location independent code can only be used for executable files
-fno-pie Do not generate location independent code


-D_FORTIFY_SOURCE=1   Enable buffer overflow attack check
-D_FORTIFY_SOURCE=2   Enable buffer overflow and format string attack checking


-z norelro              Disable relro
-z lazy                 open Partial RELRO
-z now                  FULL PARTIAL

Posted by future_man on Mon, 29 Nov 2021 09:08:32 -0800