Testing for Buffer Overruns

来源:互联网 发布:湖南卫视 知乎 编辑:程序博客网 时间:2024/06/07 02:15

source from:http://archive.devx.com/upload/free/Features/zones/security/articles/2000/11nov00/mh1100%5B1%5D-1.asp

 

 

The buffer overrun is one of the most dangerous and prevalent vulnerabilities in system code. At its core is an unchecked, externally provided buffer that causes some form of buffer operation (such as CopyMemory, strcat, strcpy, wcscpy, and so on) to fail. If you're lucky, the application will crash with a core dump, segmentation fault, or access violation. If you're not, an attacker can exploit the buffer overrun by injecting and executing arbitrary code in your process. Copying a buffer into a stack-based buffer is the most common cause of exploitable attacks.

All software development houses should make the effort to vanquish these bugs. Testing is one way to detect and rectify buffer overruns. Other techniques include code reviews and inspections—indeed removing such vulnerabilities earlier in the development process is much more effective and cheaper—but if you weren't that diligent, testing is the way to go.

Before we get started, however, you need to be aware of an overriding philosophy: Your team has to put resources into fixing buffer overruns—testing can do only so much. You cannot make a product secure by simply testing it.

If security isn't a priority in the design and development process, then no amount of testing will help. A testing-based approach, even with good unit tests, is unlikely to eliminate more than 50 to 60 percent of the defects of any class—and buffer overruns are no exception.

The Testing Process
The following steps are a simple, yet effective, way to test for buffer overrun conditions in your applications:

  1. Document all interfaces into the application.
  2. Determine which interfaces are most vulnerable to attack.
  3. Prioritize testing.
  4. Test! Test! Test!

Step 1: Document All Interfaces into the Application
Just about all applications, especially server applications, have interfaces through which other applications can pass data. Example interface technologies include:

  • Sockets
  • Remote Procedure Calls (RPCs)
  • Distributed Component Object Model (DCOM)
  • Named Pipes
  • Shared Memory
  • Reading Files
  • Reading from the Registry

You should document all of these interfaces and the data they expect to receive. In the following socket example (could apply to named or anonymous pipes too), a socket listening on port TCP/1333 expects to receive the following buffer:

#define MAX_BUFF  32
typedef struct {
char cSize; // Size of buffer, bData
int iID; // Service type, 0 = read data, 1 = update data, 2 = shutdown
char bData[MAX_BUFF]; // Data
} MYBLOB;
IDL example (RPC and DCOM)Looking at an applications' Interface Description Language (IDL) file, you see this function:
HRESULT SignText([in] BSTR bstrText, 
[in] BSTR bstrCertificate,
[out, retval] BSTR *pbstrBase64SignedBlob);

The good news about IDL files is they are somewhat self-documenting. So you can see which data 'structures' are required.

Step 2: Determine Which Interfaces Are Most Vulnerable to Attack
Once you have these input points documented in a single location, it's time to determine which functions/methods are most vulnerable to attack. The process for determining the relative vulnerability of an interface is using a simple system based on Code Attribute Points:

  • The process hosting the interface or function runs as a high privileged account such as LocalSystem (Windows NT or Windows 2000) or root (Unix systems) or some other account with admin-type privileges. 2
  • The function handling the data is written in C/C++. 1
  • The function takes arbitrary-sized buffers or strings. 1
  • The recipient buffer is stack-based. 2
  • The method/function has no Access Control List (ACL) or weak ACLs. 1
  • The method/function does not require authentication. 1

Note that you may need to take indirectly called functions into account. If you have a vulnerability in some function that's called by a bunch of non-ACL'd functions, that's a problem.

Now, add up the points for each function and list them in descending order of point total. Those at the top of list are most vulnerable to attack or may be susceptible to damage.

Step 3: Prioritize Testing
Based on the list derived from Step 2, the functions at the top of the list need testing first.

Step 4: Test! Test! Test!
Testing for buffer overrun conditions is actually quite easy, especially if you have got a good list of functions and methods to test. Some examples of what to test for and some testing ideas include:

  • Force Windows to allocate memory at the end of a page using the gflags.exe tool.
  • This will help you find small buffer overruns faster. The gflags.exe file is available in the Windows 2000 installation CD, or the Windows NT Resource Kit.
  • Create buffers that are slightly larger (a couple of bytes or so) than the buffer allocated by the program.
  • Pass very large buffers to the interface, much larger than the buffer allocated by the program.
  • Use unterminated C-style strings.
  • Use Unicode instead of ANSI strings. This is a very common error.
  • Torture test the function or method by sending thousands of buffers, and thus possibly not giving the application a chance to free memory.
  • Set the buffer to point to NULL.
  • Set the buffer to point to a non-readable memory address (say, in kernel memory).
  • Lie about the buffer size if you are using a structure, or a function/method that receives a byte count and a buffer.
  • Create bogus HTTP headers if your application uses HTTP headers.

In short, lie, cheat, swindle, confuse, destroy, and be evil—think like an attacker!

原创粉丝点击