2004. február 15., vasárnap

What's a buffer overflow and how to avoid it in Delphi?

Problem/Question/Abstract:
Answer:

This article tries to explain what a buffer overflow is and what countermeasures (or counterattacks;) can be taken to avoid it.

A buffer is a contiguous allocated block of memory, such as an array or a pointer in Pascal. In C and C++, there are no automatic bounds checking on the buffer (responsability by the programmer), which means a hacker can spoil the buffer.
for example:

int main () {
int buffer[5];
buffer[7] = 10;
}

In Delphi I mean as far as Delphi uses Pascal style strings, programs are much safer than those made in C/C++.  Strong types with internal checks of operations and ranges by the compiler at design-time can prevent an overflow.
Delphi can use Pascal strings as well as generic windows strings (PChar). When interfacing with Win API there is no other option except using Pchar.
This mean that potentially Delphi is a bit safer, because the potential problems can be isolated - more experienced developers are allowed to work with PChar, while less experienced allowed to work only with encapsulated functionality using Pascal String.

Internal represantation of a Delphi String:

------------------------I-------------I---------------I--------------I
reference counter 32bit  length(32bit)  payload(nbyte)  unused space
------------------------I-------------I---------------I--------------I
I--->string variable

The Delphi compiler hiddens the fact that the string variable is a heap pointer
to the above structure but setting the memory in advance is advisable:

path: string;
setLength(path, 1025);
setLength(path, getSystemDirectory(pChar(path),length(path)-1));

Is it really safer? Ok. we can say it's safer against character-array buffer overflows or Delphi isn't used enough to make attacking it interesting. But most attacks succeded on the STACK but not on a HEAP structure, cause overriding a heap is more difficult . This does not imply that the language is overall safer - there could be very well significant damage elsewhere, and char-based overflows are only one attack method.
For example you declare a PChar buffer, you have the possibility to check with GetMem() the capacitiy otherwise  you get an EOutOfMemory-exception:

Buffer: PChar;  //not a buffer before you use getmem
Size:= FileSize(F);
GetMem(Buffer, Size); //allocates n-Bytes on the heap
BlockRead(F, Buffer^, Size);


What's the danger?
------------------------------------------------------------
Most of "unsecure" programs are valid, and "almost" every compiler can compile it without any errors. However, the attacked program attempts to write beyond the allocated buffer memory, which might result in unexpected behavior. Because malicious code (assembly instructions to spawn a root shell) is an input argument to the program, it resides in the stack and not in the code segment. Therefore, the simplest solution is to invalidate the stack to execute any instructions.
Execution is done by overwriting a function's return address (in the stack), an intelligent hacker might want to spawn a shell (with root permissions) by jumping the execution path to such code.
A hacker or attacker places the code they are trying to execute in the buffer's overflowing area. With the  manipulated return address it points back to the buffer and executes the intended code.
That's it.


Avoid it
------------------------------------------------------------
So Delphi is mostly secure against buffer overflows (Imho), cause of compiler and conceptual basics.
Hence, the best way to deal with buffer overflow problems is to not allow them to occur in the first place. Developers should be educated about how to minimize the use of these vulnerable functions.
It is advisable to make a buffer too large in the first place or implement a circular buffer (that you may receive data quicker than you process it, resulting in a buffer overflow):

var
Source,
Dest: PChar;
CopyLen: Integer;
begin
Source:= aSource;
Dest:= @FData[FBufferEnd];
if BufferWriteSize < Count then
raise EFIFOStream.Create('Buffer over-run.');

Or you check with GetMem or a simple count-logic possible overflows:

GetMem(Buffer, BufferSize);
Ptr:= Buffer;
Count:= 0;
for I:= 0 to ListBox.Items.Count - 1 do begin
Line:= ListBox.Items.strings[I];
// Check buffer overflow
Count:= Count + Length(Line) + 3;
if Count = BufferSize then
Break;
//the function aborts immediately.

Or use try finally/raise to check buffer with a DefSize and free it:

begin
if FBufFixed then
BSize := FBufSize
else
BSize:= DefSize;
GetMem(Buffer, BSize);
try
BufEnd:= Buffer;
Count:= Stream.Read(Buffer[0], BSize);
BufEnd:= BufEnd + Count;
if Count < BSize then BufEnd[0]:= #0 else begin
raise EStreamError.Create(LoadStr(SLineTooLong));
end;
SetText(Buffer);
finally
FreeMem(Buffer, BSize);
end;
end;


Apart from education or experience, modern compiler like Delphi change the way a program is compiled, allowing bounds checking to go into compiled code automatically, without changing the source code. These compilers generate the code with built-in safeguards that try to prevent the use of illegal addresses. Any code that tries to access an illegal address is not allowed to execute.

Or a tool does it by protecting the return address on the stack from being altered. It places a canary word next to the return address whenever a function is called. If the canary word has been altered when the function returns, then some attacks or attempt has been made on the overflow buffers.

Furthermore, it may affect the application's performance to a great extent. In some case, executable size and execution time may increase a certain way. That's the prize for more code security.
Max Kleiner

Nincsenek megjegyzések:

Megjegyzés küldése