July 24, 2012

Detecting Read Access to Uninitialized Stack Memory

As I mentioned in the previous post I developed a program that is able to trace when the stack memory is being accessed. This time, I improved this program so if there is a read access to the portion of the stack memory that was not written before it issues a read-before-write notification. This happens when for example an uninitialized variable is being read.

Here is one of the erroneous C program I tested my test tool with. The C program reads uninitialized variable both in process and process2 functions from the CONTEXT structure. Highlighted the lines accessing uninitialized memory.
// TestRBW.cpp : Example file to test read-before-write bugs.
// Compile with optimization disabled.

#include "stdafx.h"
#include <string.h>

#define MAX 16

typedef struct _CONTEXT
{
    int arr[MAX];
    int a;
    int b;
    int c;
} CONTEXT;

void init(CONTEXT* ctx)
{
    memset(ctx->arr, 0, sizeof(ctx->arr[0]) * (MAX-1));
    ctx->a = 1;
}

void process(CONTEXT* ctx)

{
    int trash;

    for (int i = 0; i < MAX; i++)

    {
        trash = ctx->arr[i];
    }
}

void process2(CONTEXT* ctx)

{
    ctx->b = ctx->c;
}

void process3(int num)

{
    int trash;

    if (num)

        trash = num;
}

int _tmain(int argc, _TCHAR* argv[])

{
    CONTEXT ctx;

    // Erroneously initializes context. The last element of arr member remains unitialized.

    // b and c members remain uninitialized, too.
    init(&ctx);

    // Accesses to each element of the array. Read-before-write error should be reported in this function.

    process(&ctx);

    // Copies c to b but c is uninitialized. Read-before-write error should be reported in this function.

    process2(&ctx);

    // This contains no read-before-write bug.

    process3(ctx.a);
}
And here is the result of the test. In the log you can see both of the bugs were detected.
0:000> !region 18 18f000 1000
[Data Access] Data at 0018ff28 is read before it is written
[Data Access] EIP=00401044 Data=0018ff28 R
[Data Access] 00401044 8b048a          mov     eax,dword ptr [edx+ecx*4]
[Data Access] Data at 0018ff34 is read before it is written
[Data Access] EIP=00401059 Data=0018ff34 R
[Data Access] 00401059 8b5148          mov     edx,dword ptr [ecx+48h]
Break reason: 00000010
Looking the functions of the two EIPs. Highlighted the lines accessing uninitialized memory.
TestRBW!process:
00401020 55              push    ebp
00401021 8bec            mov     ebp,esp
00401023 83ec08          sub     esp,8
00401026 c745f800000000  mov     dword ptr [ebp-8],0
0040102d eb09            jmp     TestRBW!process+0x18 (00401038)
0040102f 8b45f8          mov     eax,dword ptr [ebp-8]
00401032 83c001          add     eax,1
00401035 8945f8          mov     dword ptr [ebp-8],eax
00401038 837df810        cmp     dword ptr [ebp-8],10h
0040103c 7d0e            jge     TestRBW!process+0x2c (0040104c)
0040103e 8b4df8          mov     ecx,dword ptr [ebp-8]
00401041 8b5508          mov     edx,dword ptr [ebp+8]
00401044 8b048a          mov     eax,dword ptr [edx+ecx*4]
00401047 8945fc          mov     dword ptr [ebp-4],eax
0040104a ebe3            jmp     TestRBW!process+0xf (0040102f)
0040104c 8be5            mov     esp,ebp
0040104e 5d              pop     ebp
0040104f c3              ret
TestRBW!process2:
00401050 55              push    ebp
00401051 8bec            mov     ebp,esp
00401053 8b4508          mov     eax,dword ptr [ebp+8]
00401056 8b4d08          mov     ecx,dword ptr [ebp+8]
00401059 8b5148          mov     edx,dword ptr [ecx+48h]
0040105c 895044          mov     dword ptr [eax+44h],edx
0040105f 5d              pop     ebp
00401060 c3              ret
I built this new functionality on the top of one mentioned in the previous post. The additions are the followings:
  • We trace both read and write memory accesses to the stack memory.
  • We maintain a structure to flag what memory addresses on the stack have been written.
  • Before the program starts to trace the memory it considers that memory addresses greater or equal than ESP have been written and maintain the structure according to this.
  • If a write memory access occurs we maintain the structure to flag the memory region has been written.
  • If an element is popped from the stack (read access to stack memory) we maintain the structure to remove addresses belongs to the unused portion of the stack memory.
  • If a read access memory occurs we read the structure and check if the memory at the address has been written. Giving notification according to results.
  This blog is written and maintained by Attila Suszter. Read in Feed Reader.