Extra - Integers


Lab Support Files

We will use this lab archive throughout the lab.

Please download the lab archive an then unpack it using the commands below:

student@asgard:~$ wget http://elf.cs.pub.ro/oss/res/labs/lab-12.tar.gz
student@asgard:~$ tar xzf lab-12.tar.gz

After unpacking we will get the lab-12/ folder that we will use for the lab:

student@asgard:~$ cd lab-12/
student@asgard:~/lab-12$ ls -F
1-overflow/  2-comparison/  3-sts7/  4-sts2/  5-sts2_alt/

0. Feedback

In order to improve the Computer Network Security course, your opinions and suggestions are important to us. The feedback is anonymous and the results will only become visible after the final exam. You will find the link to the feedback form on the main page of the curs.pub.ro instance for your master's program CNS class (either PDCS or SRIC). It's not in the meta-course for all students.


This lab illustrates how improper handling of integers can lead to bugs, and worse, to vulnerabilities and security issues. By integers we mean numeric datatypes without a fractional component, on which we perform arithmetic operations (addition, subtraction, division etc.). Some examples of integer types defined by the C90 standard are int, long, short and char. The following aspects are of interest to us when it comes to correct C programming using integers:

  • Representation: Integers are represented in two's complement on x86 architectures.
  • Width: Integers differ in size depending on the data type(s) used and on the hardware architecture we're compiling for. For example, sizeof(char) is different from sizeof(short). Furthermore, sizeof(long) == sizeof(int) might or might not be true depending on the architecture. For example this does not hold on x86_64. The C99 standard introduces fixed-length types such as int8_t or uint32_t, whose widths are architecture-independent.
  • Signedness: C90 integral types can be prepended by one of the signed or unsigned keywords, denoting the possibility of having negative numbers (types such as int, char etc. are considered signed by default). Note that since numbers are represented in two's complement on x86, negative numbers have the most significant bit 1. That is, the most significant bit is used as a sign bit, therefore, even though sizeof(int) == sizeof(unsigned int), the representation for the two types isn't the same. For example, 0 <= unsigned char x1 <= 255, while -128 <= signed char x2 <= 127.
  • Type conversion: C compilers usually perform implicit type casts on integers. Thus unsigned long i = -1; is a valid C statement! This is especially important for library functions such as atoi, which returns an int, i.e. a signed number.

Note: object sizes such as sizeof(int) have the type size_t, which is an unsigned integral type. It's recommended to use size_t for all operations involving object sizes.

Correct handling of integers is defined in chapter 4 of the CERT C Coding Standard. Some examples of real-life integer vulnerabilities were discovered using the KINT static analyzer.

1. [2.5p] Integer overflow

For our first task, let's investigate how improper handling of integers can lead to undesired program behavior. We will see what happens when an implicit data type conversion takes place.

Please access the 1-overflow/ subfolder in the lab archive. It consists of three files: a Makefile, a C source code file dubbed overflow.c and the resulting executable (ELF) file on a 32-bit system overflow.

First take a look at the source code in overflow.c. The program receives two arguments num1 and num2 that are then intepreted to integers using atoi. Use a different terminal tab to try some sample runs of the program:

student@asgard:~/lab-12/1-overflow$ ./overflow 10 50
student@asgard:~/lab-12/1-overflow$ ./overflow 100 200
Input number too small.
student@asgard:~/lab-12/1-overflow$ ./overflow 1500 1500
Input number too small.

Notice the output of the above runs. Inside the source code.and pay attention to the comparison lines (line 16, 22 and 29). Your first goal is to print the last string (on line 30)

	if (x < 0 || y < 0)
		printf("Oh no, I feel exploited!\n");

That is, you want the same comparison happening on line 22 to be false, but it would be true on line 29, right after y has been added to x.

[1p] Find two arguments that result in the overflow program printing Oh no, I feel exploited!.

Note that the comparison on line 16 will always turn up false. The two numbers x and y are of signed char type, and their maximum value is 127. The Input number too large. message will never get printed.

In order for the comparison on line 22 to be false, both x and y must be positive numbers. However, after the x += y; line, one of them turns negative, and it can only be x.

Think how you can turn a signed positive integer to a signed negative one by adding another positive integer. Think, integer overflow.

Your second task is to fix the program. Make sure that whatever input you feed to it, there won't be a situation where a signed positive integer turns into a negative integer simply by adding another positive integer to it. Any solution is OK as long as the program behaves correctly.

[1.5p] Update the overflow.c file accordigly, and then recompile it using

student@asgard:~/lab-12/1-overflow$ make

Then feed it different arguments and prove that now it behaves correctly.

One way to solve this is by adding an extra check that the sum fits in a char and returning a message when it doesn't. Note that overflows cause side effects on x86 (i.e. setting the overflow flag in the CPU), so your solution must avoid any overflows occuring in the code. Look through the CERT C Coding Standard for a compliant solution.

2. [2.5p] Signed/unsigned comparison

For our second task, let's investigate how comparison between signed and unsigned values results in unwelcomed program behavior.

Please access the 2-comparison/ subfolder in the lab archive. It consists of three files: a Makefile, a C source code file dubbed comparison.c and the resulting executable (ELF) file on a 32-bit system comparison.

First take a look at the source code in comparison.c. The program receives an argument x1 that is then intepreted as an integer using atoi. Use a different terminal tab to try some sample runs of the program:

student@asgard:~/lab-12/2-comparison$ ./comparison 10
student@asgard:~/lab-12/2-comparison$ ./comparison 100
student@asgard:~/lab-12/2-comparison$ ./comparison 1000
student@asgard:~/lab-12/2-comparison$ ./comparison -10
student@asgard:~/lab-12/2-comparison$ ./comparison -100
student@asgard:~/lab-12/2-comparison$ ./comparison -1000

Notice that even though we provide a negative argument the comparison on line 16 in the comparison.c source code file is false. Browse the program carefully and see why that happens.

Notice the hexadecimal representation of x1 (a (signed) int type). When compared to x2 (an unsigned int type) an implicit conversion to the unsigned int type occurs, messing everything up.

The hexadecimal representation of x2 may be shown using a Python script:

student@asgard:~/lab-12/2-comparison$ python -c 'print hex(2147483649)'

[1p] Using this, provide the proper argument to the program such that the program will find the condition on line 16 to be true and print the message on line 17: That looks quite odd, doesn't it?.

Think of the hexadecimal representation of x1 that would result in a true evaluation of the x1 < x2 comparison. Then create the signed integer in decimal.

[1.5p] Now, let's fix this. Without changing the data types of x1 and x2 (x1 is int and x2 is unsigned int), make sure the program behaves correctly, i.e. it will print out the message on line 17 whenever x1 is less than x2. The program semantics have to be the same.

There are multiple ways to achieve this. Choose any that results in the program behaving correcty, that is, getting the following output:

student@asgard:~/lab-12/2-comparison$ ./comparison -10
That looks quite odd, doesn't it?
student@asgard:~/lab-12/2-comparison$ ./comparison -100
That looks quite odd, doesn't it?
student@asgard:~/lab-12/2-comparison$ ./comparison -1000
That looks quite odd, doesn't it?

3. [3p] Smashthestack level7

First, let's take a quick look through the source code:

        int count = atoi(argv[1]);
        int buf[10];
        if(count >= 10 ) 
                return 1;
        memcpy(buf, argv[2], count * sizeof(int));
        if(count == 0x574f4c46) {
        return 0;

The program receives two arguments: a number which is stored into count as an integer and a string which is stored into buf, on the stack. Note that exactly count ints are stored into buf. At the same time, count must be less than 10. How can we exploit this?

Identifying the problem

First, notice that count is an int. What is the signature of memcpy? Let's have a look:

void * memcpy ( void * destination, const void * source, size_t num );

It seems that we're lucky, since the compiler will implicitly cast int to size_t and our value is multiplied by sizeof(int), which may result in an overflow. At the same time, we notice that the program doesn't check for negative values. The following short program adds a sign bit to the number 42 and prints it in hexadecimal and signed decimal:

#include <stdio.h>
int main(void)
	int x = 42 | 0x80000000;
	printf("%%x = %x\n%%d = %d\n", x, x);
	return 0;

This way we can generate the offset of count and feed it to the program as argv[1].

Looking through the binary

To find out where count resides on the stack, we'll look at the objdump of level07. First, we'll look at the assembly generated by if (count >= 10) and by if(count == 0x574f4c46):

 8048434:       89 45 f4                mov    %eax,-0xc(%ebp)
 8048437:       83 7d f4 09             cmpl   $0x9,-0xc(%ebp)
 804843b:       7e 09                   jle    8048446 <main+0x32>
 804845c:       8d 45 b8                lea    -0x48(%ebp),%eax
 804845f:       89 04 24                mov    %eax,(%esp)
 8048462:       e8 cd fe ff ff          call   8048334 <memcpy@plt>
 8048467:       81 7d f4 46 4c 4f 57    cmpl   $0x574f4c46,-0xc(%ebp)

We observe that count sits at $ebp - 0xc. We also see that buf is located at $ebp - 0x48. We also know that buf occupies 4 * 10 = 40 = 0x28 bytes on the stack. Therefore the layout of the stack looks something like the following:

$ebp - 0x00: oldebp (4 bytes)
$ebp - 0x04: ...
$ebp - 0x08: ...
$ebp - 0x0c: count  (4 bytes)
$ebp - 0x10: ...
$ebp - 0x20: buf[9]
$ebp - 0x48: buf    (40 bytes)

We can now compute the offset of count relative to buf:

$ echo $((0x48 - 0x0c))

The program wants ints, not bytes, so we divide that by 4:

$ echo $(((0x48 - 0x0c) / 4))

Obtaining a shell

Using the info from the previous sections, we can pass a negative number that encodes a malformed size (size | sign bit) and overflow the buffer into count and write the value 0x574f4c46 into it.

  1. [3p] Obtain a shell using level07

4. [2p] Smashthestack level2

The main function is really short:

int main(int argc, char **argv)
	puts("source code is available in level02.c\n");
        if (argc != 3 || !atoi(argv[2]))
                return 1;
        signal(SIGFPE, catcher);
        return abs(atoi(argv[1])) / atoi(argv[2]);

The program receives two parameters, argv[1] and argv[2], converted to numbers using atoi. It also sets the catcher function as a signal handler for SIGFPE. Our intention is thus causing a SIGFPE to enter catcher and obtain a shell.

The question becomes the following: how do we cause an erroneous arithmetic operation? Notice that we cannot cause a division by zero, due to the !atoi(argv[2]) sanity check.

Hint: this is more or less an architecture-dependent quirk. Take a second look on two's complement representation on x86 and a good look on how the x86 DIV instruction is specified. It's tricky, but not difficult!

  1. [2p] Obtain a shell using level02.

5. [BONUS - 2p] Smashthestack level2_alt

Similarly to the previous level, this is also more or less architecture dependent, or rather dependent on how floating-point numbers are specified. Since it's a bonus, you won't get any hints other than the fact that it's just plain tricky and it also relies on quirks.

Note: the program tries to execute /bin/sh with the -p flag (which would prevent bash from dropping privileges). If you get the following message:

sh: 0: Illegal option -p

then it's ok to recompile the program to run execve without passing -p.

  1. [2p] Bonus: Obtain a shell using level02_alt.
cns/labs/lab-12.txt ยท Last modified: 2019/12/08 15:18 by dennis.plosceanu
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0