argv[0] leak - Stack Canary实现
Stack Canary
- gcc编译后,stack上存在一个canary
- 进入函数时,canary被随机赋值
- 退出函数时(return前),会检查canary是否被修改
简单测试
自己写一段简单代码,产生溢出,查看结果
- gcc加入了一个叫做SSP(ProPolice)的机制
- 溢出导致canary被修改,检测到之后产生这样的错误信息
栈的结构及溢出情况
大概是这样,当产生溢出时,一定会覆盖到canary
stack canary的检查机制
- 进入函数时,canary值从TLS区域(fs:0x28)到rax,再加载到stack上,之后清空rax
- 正常执行函数代码
- 退出函数前检查stack上canary与TLS区域的canary是否相等,检测到被修改则调用__stack_chk_fail()
argv[0] leak - __stack_chk_fail()实现
__stack_chk_fail()内部实现
- http://osxr.org/glibc/source/debug/stack_chk_fail.c
- http://osxr.org/glibc/source/debug/fortify_fail.c
- http://osxr.org/glibc/source/sysdeps/posix/libc_fatal.c
- http://osxr.org/glibc/source/sysdeps/generic/paths.h
__libc_argv
- http://osxr.org/glibc/source/csu/init-first.c
__libc_init_first()
是__libc_start_main()
掉哟ing的函数,也就是说,ELF启动时一定会调用
- 在stack上大概是这样
argv[0] leak - 运行测试
能否通过溢出修改栈上的argv[0]?
测试代码
- 定语一个测试字符串,确定它的地址,然后溢出写入很多这个地址
- 在检查canary时断点,检查状态,这时候栈上的argv[0]应该已经被修改为定义的字符串
- 继续运行,因为canary被修改,调用__stack_chk_fail(),错误信息显示的argv[0]也已经是定义的字符串
- 通过这种方式,就能够泄漏任意内存
- 但是,程序在此之后会结束,所以即使多次运行也只能泄漏”不变的字符串/数据”
这就是通过argv[0]进行内存泄漏的技术
- 假设在存在stack canary的函数中产生溢出
- stack canary被检测到修改
- 调用__stack_chk_fail()
- 产生错误信息,结束
- 错误信息中包含__libc_argv[0]
- __libc_argv[0]是存在于stack上的argv[0]
- 这个argv[0]也可以通过溢出修改
- 如果有足够的溢出空间,那么就能够同时做到”canary破坏”和”argv[0]重写”
- 泄漏目标可以是地址固定区域的值,例如.data或者.bss
- flag自身(如果在内存里)
- 秘密信息,例如密码,密钥信息
- func@GOT
- 根据低12比特位识别libc版本
- https://qiita.com/sei0o/items/55db337b0829367a2052
补充 - xinetd与socat的默认行为差异
xinetd环境下
- 会因为/dev/tty打开失败输出到stderr中
- 在xinetd中,stderr绑定到socket
- 因此argv[0]泄漏大体上是能成功的
socat环境下
- 能成功打开/dev/tty,输出到/dev/tty中
- /dev/tty表示终端屏幕,因此无法连接到socket
- 因此,不能使用argv[0]泄漏
- 在本地执行的情况下,如果想要socat的行为类似于xinetd,则需要手动添加stderr,setsid选项
- 如果远程环境是socat,并且没有stderr,setsid,伪造环境变量LIBC_FATAL_STDERR_=1也可以