> For the complete documentation index, see [llms.txt](https://firmianay.gitbook.io/ctf-all-in-one/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://firmianay.gitbook.io/ctf-all-in-one/4_tips/4.12_stack_chk_fail.md).

# 4.12 利用 \_\_stack\_chk\_fail

* [回顾 canary](#回顾-canary)
* [libc 2.23](#libc-2.23)
* [CTF 实例](#ctf-实例)
* [libc 2.25](#libc-2.25)
* [参考资料](#参考资料)

## 回顾 canary

在章节 4.4 中我们已经知道了有一种叫做 canary 的漏洞缓解机制，用来判断是否发生了栈溢出。

这一节我们来看一下，在开启了 canary 的程序上，怎样利用 `__stack_chk_fail` 泄漏信息。

一个例子：

```c
#include <stdio.h>
void main(int argc, char **argv) {
    printf("argv[0]: %s\n", argv[0]);

    char buf[10];
    scanf("%s", buf);

    // argv[0] = "Hello World!";
}
```

我们先注释掉最后一行：

```
$ gcc chk_fail.c
$ python -c 'print "A"*50' | ./a.out
argv[0]: ./a.out
*** stack smashing detected ***: ./a.out terminated
Aborted (core dumped)
```

可以看到默认情况下 `argv[0]` 是指向程序路径及名称的指针，然后错误信息中打印出了这个字符串。

然后解掉注释再来看一看：

```
$ python -c 'print "A"*50' | ./a.out
argv[0]: ./a.out
*** stack smashing detected ***: Hello World! terminated
Aborted (core dumped)
```

由于程序中我们修改 `argv[0]`，此时错误信息就打印出了 `Hello World!`。是不是很神奇。

main 函数的反汇编结果如下：

```
gef➤  disassemble main
Dump of assembler code for function main:
   0x00000000004005f6 <+0>:	push   rbp
   0x00000000004005f7 <+1>:	mov    rbp,rsp
=> 0x00000000004005fa <+4>:	sub    rsp,0x30
   0x00000000004005fe <+8>:	mov    DWORD PTR [rbp-0x24],edi
   0x0000000000400601 <+11>:	mov    QWORD PTR [rbp-0x30],rsi
   0x0000000000400605 <+15>:	mov    rax,QWORD PTR fs:0x28
   0x000000000040060e <+24>:	mov    QWORD PTR [rbp-0x8],rax
   0x0000000000400612 <+28>:	xor    eax,eax
   0x0000000000400614 <+30>:	mov    rax,QWORD PTR [rbp-0x30]
   0x0000000000400618 <+34>:	mov    rax,QWORD PTR [rax]
   0x000000000040061b <+37>:	mov    rsi,rax
   0x000000000040061e <+40>:	mov    edi,0x4006f4
   0x0000000000400623 <+45>:	mov    eax,0x0
   0x0000000000400628 <+50>:	call   0x4004c0 <printf@plt>
   0x000000000040062d <+55>:	lea    rax,[rbp-0x20]
   0x0000000000400631 <+59>:	mov    rsi,rax
   0x0000000000400634 <+62>:	mov    edi,0x400701
   0x0000000000400639 <+67>:	mov    eax,0x0
   0x000000000040063e <+72>:	call   0x4004e0 <__isoc99_scanf@plt>
   0x0000000000400643 <+77>:	mov    rax,QWORD PTR [rbp-0x30]
   0x0000000000400647 <+81>:	mov    QWORD PTR [rax],0x400704
   0x000000000040064e <+88>:	nop
   0x000000000040064f <+89>:	mov    rax,QWORD PTR [rbp-0x8]
   0x0000000000400653 <+93>:	xor    rax,QWORD PTR fs:0x28    # 检查 canary 是否相同
   0x000000000040065c <+102>:	je     0x400663 <main+109>      # 相同
   0x000000000040065e <+104>:	call   0x4004b0 <__stack_chk_fail@plt>  # 不相同
   0x0000000000400663 <+109>:	leave  
   0x0000000000400664 <+110>:	ret
End of assembler dump.
```

所以当 canary 检查失败的时候，即产生栈溢出，覆盖掉了原来的 canary 的时候，函数不能正常返回，而是执行 `__stack_chk_fail()` 函数，打印出 `argv[0]` 指向的字符串。

## libc 2.23

Ubuntu 16.04 使用的是 libc-2.23，其 `__stack_chk_fail()` 函数如下：

```c
// debug/stack_chk_fail.c

extern char **__libc_argv attribute_hidden;

void
__attribute__ ((noreturn))
__stack_chk_fail (void)
{
  __fortify_fail ("stack smashing detected");
}
```

调用函数 `__fortify_fail()`：

```c
// debug/fortify_fail.c

extern char **__libc_argv attribute_hidden;

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>");
}
libc_hidden_def (__fortify_fail)
```

`__fortify_fail()` 调用函数 `__libc_message()` 打印出错误信息和 `argv[0]`。

还有一个错误信息输出到哪儿的问题，再看一下 `__libc_message()`：

```c
// sysdeps/posix/libc_fatal.c

/* Abort with an error message.  */
void
__libc_message (int do_abort, const char *fmt, ...)
{
  va_list ap;
  int fd = -1;

  va_start (ap, fmt);

#ifdef FATAL_PREPARE
  FATAL_PREPARE;
#endif

  /* 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_FATAL_STDERR_` 通过函数 `__libc_secure_getenv` 来读取，如果该变量没有被设置或者为空，即 `\0` 或 `NULL`，错误信息 stderr 会被重定向到 `_PATH_TTY`，该值通常是 `/dev/tty`，因此会直接在当前终端打印出来，而不是传到 stderr。

## CTF 实例

CTF 中就有这样一种题目，需要我们把 `argv[0]` 覆盖为 flag 的地址，并利用 `__stack_chk_fail()` 把flag 给打印出来。

实例可以查看章节 6.1.13 和 6.1.14。

## libc 2.25

最后我们来看一下 libc-2.25 里的 `__stack_chk_fail`：

```c
extern char **__libc_argv attribute_hidden;
void
__attribute__ ((noreturn))
__stack_chk_fail (void)
{
  __fortify_fail_abort (false, "stack smashing detected");
}
strong_alias (__stack_chk_fail, __stack_chk_fail_local)
```

它使用了新函数 `__fortify_fail_abort()`，这个函数是在 [BZ #12189](https://sourceware.org/git/?p=glibc.git;a=commit;h=ed421fca42fd9b4cab7c66e77894b8dd7ca57ed0) 这次提交中新增的：

```c
extern char **__libc_argv attribute_hidden;

void
__attribute__ ((noreturn))
__fortify_fail_abort (_Bool need_backtrace, const char *msg)
{
  /* The loop is added only to keep gcc happy.  Don't pass down
     __libc_argv[0] if we aren't doing backtrace since __libc_argv[0]
     may point to the corrupted stack.  */
  while (1)
    __libc_message (need_backtrace ? (do_abort | do_backtrace) : do_abort,
                    "*** %s ***: %s terminated\n",
                    msg,
                    (need_backtrace && __libc_argv[0] != NULL
                     ? __libc_argv[0] : "<unknown>"));
}

void
__attribute__ ((noreturn))
__fortify_fail (const char *msg)
{
  __fortify_fail_abort (true, msg);
}

libc_hidden_def (__fortify_fail)
libc_hidden_def (__fortify_fail_abort)
```

函数 `__fortify_fail_abort()` 在第一个参数为 `false` 时不再进行栈回溯，直接以打印出字符串 `<unknown>` 结束，也就没有办法输出 `argv[0]` 了。

就像下面这样：

```
$ python -c 'print("A"*50)' | ./a.out
argv[0]: ./a.out
*** stack smashing detected ***: <unknown> terminated
Aborted (core dumped)
```

## 参考资料

* [Adventure with Stack Smashing Protector (SSP)](http://site.pi3.com.pl/papers/ASSP.pdf)


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://firmianay.gitbook.io/ctf-all-in-one/4_tips/4.12_stack_chk_fail.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
