Thursday, February 23, 2023

Reverse Engineering Notes - Uninitialized Data


 

When memory is NOT initialized it take whatever values HAPPEN to ALREADY be in that memory location.  The vulnerability occurs if the values in memory are ACID.

Initialized data in C language refers to variables or data that are assigned a value at the time of their declaration. These variables are stored in the program's data section and have a predefined value that is determined by the programmer. For example:

int initialized_variable = 10; // initializing an integer variable with a value of 10 char initialized_string[] = "Hello, world!"; // initializing a string variable with a value

On the other hand, uninitialized data in C language refers to variables or data that are not assigned any value at the time of their declaration. These variables have undefined values and are stored in the program's data section. For example:

int uninitialized_variable; // declaring an integer variable without assigning a value char uninitialized_string[10]; // declaring a string variable without assigning a value

The calling convention for the C language specifies how function calls are made, how parameters are passed, and how return values are handled. There are several calling conventions, such as cdecl, stdcall, fastcall, and thiscall, which are used in different programming environments and architectures.

Here is an example of a function in C language using the cdecl calling convention:

c
#include <stdio.h> // function declaration int add_numbers(int a, int b); // main function int main() { int x = 10, y = 20, sum; // function call sum = add_numbers(x, y); printf("Sum of %d and %d is %d", x, y, sum); return 0; } // function definition int add_numbers(int a, int b) { return a + b; }

In this example, the add_numbers() function takes two integer parameters a and b, and returns their sum. The main() function calls add_numbers() and passes it two arguments x and y. The add_numbers() function then returns the sum of x and y, which is assigned to the sum variable in the main() function. Finally, the main() function prints the result using the printf() function.

The cdecl calling convention is used by default in many C compilers and follows the stack-based parameter passing mechanism. In this convention, the caller pushes the function arguments onto the stack in reverse order, and the callee pops them off the stack. The caller is responsible for cleaning up the stack after the function call.

Uninitialized data in C language refers to variables or data that are not assigned any value at the time of their declaration. Accessing uninitialized data can lead to undefined behavior, where the program's behavior becomes unpredictable and may result in crashes or errors.

Here are some examples of how uninitialized data access can occur in C:

Example 1: Accessing uninitialized integer variable

c
#include <stdio.h> int main() { int x; // declaring an integer variable without assigning a value printf("The value of x is %d", x); // accessing the uninitialized variable return 0; }

In this example, the integer variable x is declared without assigning a value. The program then attempts to print the value of x using the printf() function. Since x is uninitialized, the value printed may be garbage or random values from the memory location where x is stored.

Example 2: Accessing uninitialized pointer variable

c
#include <stdio.h> int main() { int* ptr; // declaring a pointer variable without assigning a value printf("The value of ptr is %p", ptr); // accessing the uninitialized pointer variable return 0; }

In this example, the pointer variable ptr is declared without assigning a value. The program then attempts to print the value of ptr using the printf() function. Since ptr is uninitialized, the value printed may be a garbage or random memory address.

Example 3: Accessing uninitialized array variable

c
#include <stdio.h> int main() { int arr[3]; // declaring an array without assigning values printf("The value of the first element in the array is %d", arr[0]); // accessing the uninitialized array variable return 0; }

In this example, an integer array arr is declared without assigning any values. The program then attempts to print the value of the first element of arr using the printf() function. Since arr is uninitialized, the value printed may be a garbage or random value from the memory location where the array is stored.

In all of these examples, accessing uninitialized variables leads to undefined behavior, which can cause the program to crash or produce unexpected results. It is important to always initialize variables before accessing them to avoid these types of errors.

 

 corrected examples with comments to prevent errors from occurring:

Example 1: Initializing integer variable

c
#include <stdio.h> int main() { int x = 0; // initializing an integer variable with a value of 0 printf("The value of x is %d", x); // accessing the initialized variable return 0; }

In this example, the integer variable x is initialized with a value of 0. The program then prints the value of x using the printf() function. Since x is now initialized, the value printed will always be 0.

Example 2: Initializing pointer variable

c
#include <stdio.h> int main() { int* ptr = NULL; // initializing a pointer variable with a null value printf("The value of ptr is %p", (void*)ptr); // accessing the initialized pointer variable return 0; }

In this example, the pointer variable ptr is initialized with a null value, which means it is not pointing to any valid memory location. The program then prints the value of ptr using the printf() function. Since ptr is now initialized with a null value, the value printed will always be 0.

Example 3: Initializing array variable

c
#include <stdio.h> int main() { int arr[3] = {0}; // initializing an array with a value of 0 printf("The value of the first element in the array is %d", arr[0]); // accessing the initialized array variable return 0; }

In this example, the integer array arr is initialized with a value of 0 using the {0} syntax. The program then prints the value of the first element of arr using the printf() function. Since arr is now initialized with a value of 0, the value printed will always be 0.

In all of these examples, initializing variables before accessing them prevents the errors that can occur when accessing uninitialized variables.

 When you free stuff and when you malloc() it  again depending on the implementation of the heap you could be getting back the same exact addresses you had before. ACID!

FREED DATA AND THEN MALLOC()




Common Culprits for uninitialized data access can be:
  • Not initializing local variables when they are declared.
  • Not initializing heap data at allocation time.
  • Only partially initializing structs and objects.
  • Failure to initialize down an uncommon control flow path (passing a pointer to an uninitialized struct to a function, and EXPECTING it to perform initialization, but it returns early before any initializing occurs.

Humans have limits sometimes it's better to tool!

MemorySanitizer, fuzzing, and backtracking is required for complex programs.  

https://yajin.org/papers/ccs19_timeplayer.pdf

Prevention

Initialize EVERYTHING!!!

Stack

Every single local variable you declare is initialized at declaration time.

If you are writing code in a pre-C99 style, then all declarations must be at the beginning of the function, and spotting a missing initialization should be very easy.

If, on the other hand, you are taking advantage of the post-C99 option to declare variables later on in the function, then you will need to scan the code more carefully for any missing manual initialization.

Heap

You should ideally use a zeroing allocator. If needed you can instead just manually initialize the entire heap allocation with a function like the POSIX memset(), Windows ZeroMemory(), or your environment's equivalent.

Globals

On all major OSes and compilers, global variables that are left uninitialized will tend to be combined into something like a "{.}BSS" section of the output binary. This section has default zero initialization.

 

Zeroing allocator options

For heap allocations, you can simply call different functions and ensure that the heap memory coming back will be initialized with zeros.

POSIX

The calloc() function is very similar to malloc(), but the allocation it returns is zeroed out. You can replace all instances of malloc(x) with calloc(1,x) (because calloc() multiplies the first argument times the second argument to get the total number of bytes to allocate.)

Windows

Windows generally has the POSIX functions available in userspace.

In kernelspace, Windows created new APIs such as ExAllocatePool2 and ExAllocatePool3 and wrappers such as ExAllocatePoolZero(), ExAllocatePoolQuotaZero(), and ExAllocatePoolPriorityZero() that can be used instead of traditional kernel pool (heap) allocation functions.

Additionally RtlAllocateHeap has a HEAP_ZERO_MEMORY flag that can be passed to ensure that the memory that it allocates is zeroed before usage.

macOS

macOS has the POSIX functions available in userspace.

In kernelspace, APIs like IOMalloc() can be replaced with IOMallocZero(), and OSAllocateMemory() can be replaced with OSAllocateMemoryZero()

Initialization in the absence of zeroing allocators

If for whatever reason a zeroing allocator is not available, you can always call a standard C library function like memset() or bzero() (or their OS-specific kernel equivalents) to manually initialize allocations before they are used.

Note that some OSes support functions like explicit_bzero() (sometimes also called explicit_memset()), which include a hint to the compiler to tell it not to drop the function if optimizations would suggest that the zeroization would serve no purpose (i.e. due to dead store elimination). This is usually used for explicitly zeroing secrets from memory, but there's no reason not to use it for zeroing to prevent UDA vulnerabilities too.


C++ initialization rules

It is generally better to program paranoid and manually initialize everything, as if you were writing code in C. If your initialization is redundant to what C++ would be providing by default anyway, then there's no difference. But if your initialization is causing something to occur that C++ wouldn't have by default, then you can be preventing UDA vulnerabilities.






No comments:

Post a Comment

A Guide to Multi-Level Pointer Analysis

  A Comprehensive Guide to Multi-Level Pointer Analysis   A regular pointer points to only one address, but when it's accompanied by a l...