Skip to content

Commit f4577be

Browse files
author
vdisasmdev
committed
* Try to detect incorrect Delayed Import.
+ TPEImage.ReadAnsiStringLen (to specify max possible string length).
1 parent cf3d55e commit f4577be

File tree

4 files changed

+118
-53
lines changed

4 files changed

+118
-53
lines changed

PE.Common.pas

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ interface
5656
TParserOptions = set of TParserOption;
5757

5858
const
59+
MAX_PATH_WIN = 260;
60+
5961
SUSPICIOUS_MIN_LIMIT_EXPORTS = $10000;
6062
DEFAULT_SECTOR_SIZE = 512;
6163
DEFAULT_PAGE_SIZE = 4096;

PE.Image.pas

Lines changed: 49 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,10 @@ TPEImage = class
204204
procedure Skip(Count: integer);
205205

206206
// Read 1-byte 0-terminated string.
207-
function ReadAnsiString: String; overload;
207+
function ReadAnsiString: String;
208+
209+
// MaxLen: 0 - no limit
210+
function ReadAnsiStringLen(MaxLen: integer; out Len: integer; out Str: string): boolean;
208211

209212
// Read 2-byte UTF-16 string with length prefix (2 bytes).
210213
function ReadUnicodeStringLenPfx2: String;
@@ -1183,28 +1186,56 @@ function TPEImage.ReadEx(var Buffer; Size: cardinal): boolean;
11831186

11841187
function TPEImage.ReadAnsiString: string;
11851188
var
1186-
len, available: uint32;
1189+
len: Integer;
1190+
begin
1191+
if not ReadAnsiStringLen(0, len, result) then
1192+
result := '';
1193+
end;
1194+
1195+
function TPEImage.ReadAnsiStringLen(MaxLen: integer; out Len: integer; out Str: string): boolean;
1196+
var
1197+
available: uint32;
1198+
pBegin, pCur, pEnd: PAnsiChar;
11871199
begin
1188-
if Assigned(FCurrentSec) then
1200+
Len := 0;
1201+
Str := '';
1202+
Result := false;
1203+
1204+
if not Assigned(FCurrentSec) then
1205+
exit;
1206+
1207+
available := FCurrentSec.AllocatedSize - FCurrentOfs;
1208+
1209+
if (MaxLen <> 0) and (MaxLen < available) then
1210+
available := MaxLen;
1211+
1212+
pBegin := @FCurrentSec.Mem[FCurrentOfs];
1213+
pCur := pBegin;
1214+
pEnd := pBegin + available;
1215+
while pCur < pEnd do
11891216
begin
1190-
available := FCurrentSec.AllocatedSize - FCurrentOfs;
1191-
len := 0;
1192-
while len < available do
1217+
if pCur^ = #0 then
11931218
begin
1194-
if FCurrentSec.Mem[FCurrentOfs + len] = 0 then
1195-
begin
1196-
// Get string.
1197-
Result := String(PAnsiChar(@FCurrentSec.Mem[FCurrentOfs]));
1198-
// Move next.
1199-
inc(len);
1200-
inc(FPositionRVA, len);
1201-
inc(FCurrentOfs, len);
1202-
exit;
1203-
end;
1204-
inc(len);
1219+
Result := true;
1220+
break;
12051221
end;
1222+
inc(pCur);
1223+
end;
1224+
1225+
Len := pCur - pBegin;
1226+
1227+
if Result then
1228+
begin
1229+
// String.
1230+
Str := string(pBegin);
1231+
1232+
// Include null at end.
1233+
inc(len);
1234+
1235+
// Move current position.
1236+
inc(FPositionRVA, Len);
1237+
inc(FCurrentOfs, Len);
12061238
end;
1207-
exit('');
12081239
end;
12091240

12101241
function TPEImage.ReadUnicodeStringLenPfx2: string;

PE.Parser.ImportDelayed.pas

Lines changed: 66 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,12 @@ implementation
2828
PE.Imports.Func,
2929
PE.Imports.Lib;
3030

31-
type
32-
TFuncs = TList<TPEImportFunctionDelayed>;
33-
34-
procedure ParseTable(
31+
// Testing mode: check if read fields are correct.
32+
function ParseTable(
3533
const PE: TPEImage;
3634
const Table: TDelayLoadDirectoryTable;
37-
const Funcs: TFuncs);
35+
Testing: boolean
36+
): boolean;
3837
var
3938
DllName: string;
4039
FnName: string;
@@ -43,23 +42,54 @@ procedure ParseTable(
4342
Ilt: TImportLookupTable;
4443
iFunc: uint32;
4544
var
45+
iLen: integer;
4646
Ordinal: UInt16;
4747
Hint: UInt16 absolute Ordinal;
4848
Iat: TRVA;
49-
SubValue: UInt32;
49+
SubValue: uint32;
5050
Lib: TPEImportLibrary;
5151
begin
5252
if Table.UsesVA then
5353
SubValue := PE.ImageBase
5454
else
5555
SubValue := 0;
5656

57+
if Testing then
58+
begin
59+
if (Table.Name = 0) or (Table.Name < SubValue) then
60+
begin
61+
PE.Msg.Write('Delayed Import: Name address incorrect.');
62+
exit(false);
63+
end;
64+
65+
if (Table.DelayImportNameTable = 0) or (Table.DelayImportNameTable < SubValue) then
66+
begin
67+
PE.Msg.Write('Delayed Import: Name table address incorrect.');
68+
exit(false);
69+
end;
70+
71+
if (Table.DelayImportAddressTable = 0) or (Table.DelayImportAddressTable < SubValue) then
72+
begin
73+
PE.Msg.Write('Delayed Import: Address table incorrect.');
74+
exit(false);
75+
end;
76+
end;
77+
5778
if not PE.SeekRVA(Table.Name - SubValue) then
58-
exit;
79+
exit(false);
5980

60-
DllName := PE.ReadANSIString;
61-
Lib := TPEImportLibrary.Create(DLLName);
62-
PE.ImportsDelayed.Add(Lib);
81+
if not PE.ReadAnsiStringLen(MAX_PATH_WIN, iLen, DllName) then
82+
exit(false);
83+
84+
if not Testing then
85+
begin
86+
Lib := TPEImportLibrary.Create(DllName);
87+
PE.ImportsDelayed.Add(Lib);
88+
end
89+
else
90+
begin
91+
Lib := nil; // compiler friendly
92+
end;
6393

6494
iFunc := 0;
6595
Iat := Table.DelayImportAddressTable - SubValue;
@@ -84,17 +114,26 @@ procedure ParseTable(
84114
begin
85115
// Import by name. Get hint/name
86116
if not PE.SeekRVA(HintNameRva - SubValue) then
87-
raise Exception.Create('Error reading delayed import hint/name.');
117+
begin
118+
PE.Msg.Write('Delayed Import: incorrect Hint/Name RVA encountered.');
119+
exit(false);
120+
end;
121+
88122
Hint := PE.ReadWord(2);
89123
FnName := PE.ReadANSIString;
90124
end;
91125

92-
Fn := TPEImportFunctionDelayed.Create(FnName, Ordinal);
93-
Lib.Functions.Add(Fn);
126+
if not Testing then
127+
begin
128+
Fn := TPEImportFunctionDelayed.Create(FnName, Ordinal);
129+
Lib.Functions.Add(Fn);
130+
end;
94131

95132
inc(Iat, PE.ImageWordSize);
96133
inc(iFunc);
97134
end;
135+
136+
exit(true);
98137
end;
99138

100139
function TPEImportDelayedParser.Parse: TParserResult;
@@ -104,33 +143,32 @@ function TPEImportDelayedParser.Parse: TParserResult;
104143
ofs: uint32;
105144
Table: TDelayLoadDirectoryTable;
106145
Tables: TList<TDelayLoadDirectoryTable>;
107-
Funcs: TFuncs;
108146
TablesUseRVA: boolean;
109147
begin
110148
PE := TPEImage(FPE);
111149

112-
Result := PR_ERROR;
150+
result := PR_ERROR;
113151

114152
// If no imports, it's ok.
115153
if not PE.DataDirectories.Get(DDIR_DELAYIMPORT, @ddir) then
116-
Exit(PR_OK);
154+
exit(PR_OK);
117155
if ddir.IsEmpty then
118-
Exit(PR_OK);
156+
exit(PR_OK);
119157

120158
// Seek import dir.
121159
if not PE.SeekRVA(ddir.VirtualAddress) then
122-
Exit;
160+
exit;
123161

124162
Tables := TList<TDelayLoadDirectoryTable>.Create;
125163
try
126164

127165
// Delay-load dir. tables.
128166
ofs := 0;
129-
TablesUseRVA := True; // default, compiler-friendly
130-
while True do
167+
TablesUseRVA := true; // default, compiler-friendly
168+
while true do
131169
begin
132170
if ofs > ddir.Size then
133-
Exit(PR_ERROR);
171+
exit(PR_ERROR);
134172

135173
if not PE.ReadEx(Table, SizeOf(Table)) then
136174
break;
@@ -150,7 +188,7 @@ function TPEImportDelayedParser.Parse: TParserResult;
150188
begin
151189
// Normally all tables must use either VA or RVA. No mix allowed.
152190
// If mix found it must be not real table.
153-
// For example, Delphi (some versions for sure) use(d) such optimization.
191+
// For example, some Delphi versions used such optimization.
154192
break;
155193
end;
156194

@@ -159,18 +197,13 @@ function TPEImportDelayedParser.Parse: TParserResult;
159197
end;
160198

161199
// Parse tables.
162-
if Tables.Count = 0 then
163-
Exit(PR_OK);
164-
165-
Funcs := TFuncs.Create;
166-
try
167-
for Table in Tables do
168-
ParseTable(PE, Table, Funcs);
169-
finally
170-
Funcs.Free;
171-
end;
200+
for Table in Tables do
201+
// First test if fields are correct
202+
if ParseTable(PE, Table, true) then
203+
// Then do real reading.
204+
ParseTable(PE, Table, false);
172205

173-
Result := PR_OK;
206+
exit(PR_OK);
174207
finally
175208
Tables.Free;
176209
end;

PE.Types.Directories.pas

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,9 +76,8 @@ function TImageDataDirectory.Contain(rva: uint32): boolean;
7676

7777
function TImageDataDirectory.IsEmpty: boolean;
7878
begin
79-
// Result := (VirtualAddress = 0) or (Size = 0);
80-
Result := (VirtualAddress = 0);
8179
// In some cases Size can be 0, but VirtualAddress will point to valid data.
80+
Result := (VirtualAddress = 0);
8281
end;
8382

8483

0 commit comments

Comments
 (0)