Analysis of CVE-2012-0711 (IBM DB2 Integer Signedness Error)

It this post I’m going to analyse the details of CVE-2012-0711 (IBM’s security bulettin), an integer signedness bug, I’ve found in IBM DB2 Express-C a while ago.

The description of the bug:

“Integer signedness error in the db2dasrrm process in the DB2 Administration Server (DAS) in IBM DB2 9.1 through FP11, 9.5 before FP9, and 9.7 through FP5 on UNIX platforms allows remote attackers to execute arbitrary code via a crafted request that triggers a heap-based buffer overflow.”

I’m always curious how a vulnerability was found, and this details are almost never public. After I’ve analysed ZDI-11-036, I’ve learned the format of DB2 DAS communication protocol. I’ve created a simple (byte-replacer) protocol-aware fuzzer which only modified the “data” blocks during the communication, and wrote a simple client sending SysCmd requests to the database. After having a lot of crashes, it turned out, that the replaced bytes were always in the encrypted username. (You can see the dump of a communication here.) I wanted to create a client which gives me more fine-grained control above the communication (my client used through JNI, so I had only a toplevel interface). After trying to understand and implement the Diffie-Hellman key exchange for two weeks (as it turned out later, it’s not needed to control the Diffie-Hellman part), I give up this idea, and started to check the details of the crash.

Lets start with the backtrace:

(gdb) ba
#0  0xb7336102 in DasHashTable::get(DasHashKey*) () from /opt/ibm/db2/V9.7/lib32/
#1  0x08057398 in validateUser(rrmRequestContext*) ()
#2  0x08056fd0 in authenticateRequest ()
#3  0x0805632c in handleServerEncryptAuthRequest(rrmRequestContext*, dasRecvParam*) ()
#4  0x08054c48 in handleRequest ()
#5  0xb73a74f6 in db2dasThreadMain () from /opt/ibm/db2/V9.7/lib32/
#6  0xb6c76e99 in start_thread (arg=0x661e2b70) at pthread_create.c:304
#7  0xb6aae73e in clone () at ../sysdeps/unix/sysv/linux/i386/clone.S:130

Lets have a look at DasHashTable::get(DasHashKey*) with some comments:

   0x005cd0db <+77>:	mov    0xc(%ebp),%eax
   0x005cd0de <+80>:	push   %eax
   0x005cd0df <+81>:	mov    (%eax),%edx
   0x005cd0e1 <+83>:	call   *(%edx)
   # this is a call of UidKey::getHashCode [_ZN6UidKey11getHashCodeEv], throught vtable

   0x005cd0e3 <+85>:	add    $0x4,%esp
   # eax contains the calculated hash code of the username

   0x005cd0e6 <+88>:	mov    0x8(%ebx),%ecx		#0x64
   0x005cd0e9 <+91>:	mov    (%ebx),%ebx		
   0x005cd0eb <+93>:	cltd
   0x005cd0ec <+94>:	idiv   %ecx
   # after this division edx will contain the remainder in the range -0x63..0x63

   0x005cd0ee <+96>:	mov    (%ebx,%edx,4),%ebx
   # ebx <- *(ebx + 4 * (-0x63 .. 0x63 ))

We can see, that this part of the function takes a string from the DasHashKey structure, calculates it’s hash, and after a modulo 0x64 operation it uses the value, to index an array. The string is the supplied username, and most probably this is a hashtable implementation, with fixed (0x64) size. The problem is, that if the remainder is a negative number, we underrun the buffer.

Lets look into the implementation of UidKey::getHashCode:

0x08063b78 <+12>:	mov    0x8(%ebp),%ebx	#0:[ptr] 4:[username]
0x08063b7b <+15>:	lea    0x4(%ebx),%ecx	#ecx <- pointer to the username string
#this block calculates the length of the username
0x08063b7e <+18>:	xor    %eax,%eax
0x08063b80 <+20>:	movzbl (%ecx,%eax,1),%edx
0x08063b84 <+24>:	test   %edx,%edx
0x08063b86 <+26>:	je     0x8063b97 <_ZN6UidKey11getHashCodeEv+43>
0x08063b88 <+28>:	movzbl 0x1(%ecx,%eax,1),%edx
0x08063b8d <+33>:	add    $0x2,%eax
0x08063b90 <+36>:	test   %edx,%edx
0x08063b92 <+38>:	jne    0x8063b80 <_ZN6UidKey11getHashCodeEv+20>
0x08063b94 <+40>:	add    $0xffffffff,%eax
0x08063b97 <+43>:	mov    %eax,%esi	# esi <- strlen(username)
#after this block, eax will contain the sum of the character codes in the username string
0x08063bba <+78>:	test   %esi,%esi
0x08063bbc <+80>:	jle    0x8063bf7 <_ZN6UidKey11getHashCodeEv+139>
0x08063bbe <+82>:	mov    %edi,-0xc(%ebp)
0x08063bc1 <+85>:	xor    %eax,%eax
0x08063bc3 <+87>:	xor    %edx,%edx
0x08063bc5 <+89>:	movsbl 0x4(%edx,%ebx,1),%edi
0x08063bca <+94>:	add    %edi,%eax
0x08063bcc <+96>:	add    $0x1,%edx
0x08063bcf <+99>:	cmp    %esi,%edx
0x08063bd1 <+101>:	jl     0x8063bc5 <_ZN6UidKey11getHashCodeEv+89>
0x08063bd3 <+103>:	mov    -0xc(%ebp),%edi			

Looking into the code, we can easily see, that it first calculates the length of the specified username, then fetches it character by character and calculates the hashcode, as the sum of the charactercodes. Its interesting, that it handles the characters as unsigned (movzbl) when calculating the length, but signed (mobsbl) when calculating the sum. Later turned out, that the problem affects only Linux versions of DB2, because the Windows versions use unsigned chars when calculating the hash.

Lets get back to our “hijacked” pointer:

#if ebx is NULL we exit
0x005cd0f1 <+99>:	test   %ebx,%ebx
0x005cd0f3 <+101>:	je     0x5cd123 <_ZN12DasHashTable3getEP10DasHashKey+149>

#if *ebx is not mapped memory, a SEGFAULT happens
0x005cd0f5 <+103>:	mov    0xc(%ebp),%edi
0x005cd0f8 <+106>:	mov    (%ebx),%eax	
#if *ebx is NULL, we exit						
0x005cd0fa <+108>:	test   %eax,%eax
0x005cd0fc <+110>:	je     0x5cd11c <_ZN12DasHashTable3getEP10DasHashKey+142>

#if  *(*(ebx)) points to nonmapped memory, we SEGFAULT
0x005cd0fe <+112>:	push   %edi
0x005cd0ff <+113>:	push   %eax
0x005cd100 <+114>:	mov    (%eax),%edx	

#we call *(*(*(ebx))+4)
0x005cd102 <+116>:	call   *0x4(%edx)

We can move the ebx pointer backward up to 100 words. ebx is originally pointing to an object so with this we can make the pointer to point into a different object’s memory area. If we can control a word in this area, we can build up a dereference chain, which will enable us to call an arbitrary function. I didn’t checked that controlling of one word in this area is possible or not, but I found this video, and I suppose its about the same vulnerability.

The simplest example of triggering the bug is to specify a one character username, because with one character usernames from range (128..255) we can generate all the possibilities of ebx overwrites (-0x63 .. -1).

For the test case I’ve created a simple example. The code uses the DasSysCmd command, but it is irrelevant, because the crash happens in the authentication stage, before sending the command. Every other command would be appropriate, which requires authentication. You can reach it here.

This entry was posted in Bugs, Security and tagged , , , , , . Bookmark the permalink.

Leave a Reply

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

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

Google photo

You are commenting using your Google 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 )

Connecting to %s