diff --git a/Debugger.pas b/Debugger.pas index df73e00..9206e82 100644 --- a/Debugger.pas +++ b/Debugger.pas @@ -95,7 +95,7 @@ TDebugger = class(TThread) procedure RestoreStolenOEPForMSVC6(hThread: THandle; var OEP: NativeUInt); procedure FixupAPICallSites(IAT: NativeUInt); - function DetermineIATAddress(OEP: NativeUInt): NativeUInt; + function DetermineIATAddress(OEP: NativeUInt; Dumper: TDumper): NativeUInt; procedure TraceImports(IAT: NativeUInt); function TraceIsAtAPI(const C: TContext): Boolean; procedure FinishUnpacking(OEP: NativeUInt); @@ -1435,6 +1435,7 @@ procedure TDebugger.FinishUnpacking(OEP: NativeUInt); i: Integer; x: NativeUInt; FN: string; + Dumper: TDumper; begin // Remove EFL jumps from VM code for i := 0 to High(EFLs) do @@ -1447,15 +1448,11 @@ procedure TDebugger.FinishUnpacking(OEP: NativeUInt); Break; end; + Dumper := TDumper.Create(FProcess, FImageBase, OEP); + // Look for IAT by analyzing code near OEP - try - IAT := DetermineIATAddress(OEP); - Log(ltGood, 'IAT: ' + IntToHex(IAT, 8)); - except - // It's normal that this fails in samples that require FixupAPICallSites, because call dword ptr won't be found - IAT := FImageBase + FBaseOfData; - Log(ltInfo, 'DetermineIATAddress failed, fallback: ' + IntToHex(IAT, 8) + ' (MSVC only!)'); - end; + IAT := DetermineIATAddress(OEP, Dumper); + Log(ltGood, 'IAT: ' + IntToHex(IAT, 8)); // Themida v3: Weak traceable import protection if ThemidaV3 then @@ -1475,11 +1472,9 @@ procedure TDebugger.FinishUnpacking(OEP: NativeUInt); // Process the IAT into an import directory and dump the binary to disk FN := ExtractFilePath(FExecutable) + ChangeFileExt(ExtractFileName(FExecutable), 'U' + ExtractFileExt(FExecutable)); - with TDumper.Create(FProcess, FImageBase, OEP, IAT) do - begin - DumpToFile(FN, Process()); - Free; - end; + Dumper.IAT := IAT; + Dumper.DumpToFile(FN, Dumper.Process()); + Dumper.Free; FHideThreadEnd := True; TerminateProcess(FProcess.hProcess, 0); @@ -1487,7 +1482,7 @@ procedure TDebugger.FinishUnpacking(OEP: NativeUInt); Log(ltGood, 'Don''t forget to MakeDataSect.'); end; -function TDebugger.DetermineIATAddress(OEP: NativeUInt): NativeUInt; +function TDebugger.DetermineIATAddress(OEP: NativeUInt; Dumper: TDumper): NativeUInt; var TextBase, CodeSize: NativeUInt; CodeDump: PByte; @@ -1511,6 +1506,9 @@ function TDebugger.DetermineIATAddress(OEP: NativeUInt): NativeUInt; if PByte(Dis.EIP)^ = $E8 then // call begin + if Dis.Instruction.AddrValue > TextBase + CodeSize then + Exit(0); // Probably direct API call. Handled below via FGuardAddrs. + Result := FindCallOrJmpPtr(Dis.Instruction.AddrValue); if Result <> 0 then Exit; @@ -1525,48 +1523,88 @@ function TDebugger.DetermineIATAddress(OEP: NativeUInt): NativeUInt; end; end; - function IsImportDirArtifact(Address, Data: NativeUInt): Boolean; + function ScanData(ToFind: NativeUInt): NativeUInt; + var + DataSize: NativeUInt; + DataSect: PByte; + DataSectWalker, DataSectBound: PNativeUInt; begin - // Delphi has the import directory and import address directory crammed into one section next to each other - // The import directory has some address artifacts left that Themida doesn't touch - // All addresses before the IAT should be 0 or an RVA into the same section ($6000 is an arbitrarily chosen threshold) - Result := (Data = 0) or (Abs((Data + FImageBase) - Address) < $6000); + DataSize := FPESections[0].Misc.VirtualSize - CodeSize; + GetMem(DataSect, DataSize); + try + if not RPM(TextBase + CodeSize, DataSect, DataSize) then + raise Exception.Create('DetermineIATAddress.ScanData: RPM failed'); + + // We assume the table is machine-word aligned. + DataSectWalker := PNativeUInt(DataSect); + DataSectBound := PNativeUInt(DataSect + DataSize); + + while DataSectWalker < DataSectBound do + begin + if DataSectWalker^ = ToFind then + Exit(NativeUInt(PByte(DataSectWalker) - DataSect) + TextBase + CodeSize); + Inc(DataSectWalker); + end; + finally + FreeMem(DataSect); + end; + + raise Exception.Create('Unable to find API in data part of section 0'); end; var - IATRef, Seeker: NativeUInt; + IATRef, Seeker, Target: NativeUInt; IATData: array[0..1023] of NativeUInt; + Site: array[0..5] of Byte; i: Integer; begin - // For MSVC, the IAT usually resides at FImageBase + FBaseOfData + // For MSVC, the IAT often resides at FImageBase + FBaseOfData // Other compilers such as Delphi use a dedicated .idata section, but the IAT doesn't start directly at the beginning, so some guesswork is needed TextBase := FImageBase + FPESections[0].VirtualAddress; CodeSize := FBaseOfData - FPESections[0].VirtualAddress; + Log(ltInfo, Format('Text base: %.8X, code size: %X, data size: %X', [TextBase, CodeSize, FPESections[0].Misc.VirtualSize - CodeSize])); NumInstr := 0; IATRef := 0; GetMem(CodeDump, CodeSize); try if not RPM(TextBase, CodeDump, CodeSize) then - raise Exception.CreateFmt('DetermineIATAddress: RPM failed (size %X)', [CodeSize]); + raise Exception.Create('DetermineIATAddress: RPM failed'); IATRef := FindCallOrJmpPtr(OEP); if IATRef = 0 then - raise Exception.Create('No IAT reference found near OEP'); + begin + Log(ltInfo, 'No IAT reference found near OEP'); + if FGuardAddrs.Count > 0 then + begin + RPM(FGuardAddrs[0], @Site, 6); + if (Site[0] = $E8) or (Site[0] = $E9) then + Target := PCardinal(@Site[1])^ + FGuardAddrs[0] + 5 + else if (Site[1] = $E8) or (Site[1] = $E9) then + Target := PCardinal(@Site[2])^ + FGuardAddrs[0] + 6 + else + raise Exception.Create('First guard addr is not call/jmp'); + + Log(ltInfo, Format('First guard addr %.8X yielded API %.8X', [FGuardAddrs[0], Target])); + IATRef := ScanData(Target); + end + else + raise Exception.Create('Found no way to obtain IAT reference'); + end; Log(ltGood, 'First IAT ref: ' + IntToHex(IATRef, 8)); finally FreeMem(CodeDump); end; - // FIXME: This can be problematic in Delphi binaries where IATRef ends up as something like XXX2230 and IAT start is XXX1F74 - Seeker := IATRef and not $FFF; + // The IATRef we obtained points somewhere into the IAT area. Now we need to figure out the start of the table. + Seeker := IATRef - $1000; RPM(Seeker, @IATData, SizeOf(IATData)); for i := 0 to High(IATData) do begin - if IsImportDirArtifact(Seeker, IATData[i]) then + if not Dumper.IsAPIAddress(IATData[i]) then Inc(Seeker, SizeOf(NativeUInt)) else - Break; + Break; // Let's hope we didn't randomly stumble upon a valid API address somewhere before the IAT. end; Result := Seeker; @@ -1733,7 +1771,7 @@ procedure TDebugger.TraceImports(IAT: NativeUInt); if IATData[i] = 0 then begin Inc(Consecutive0); - if Consecutive0 = 3 then + if Consecutive0 = 3 then // This may vary Break; end; Consecutive0 := 0; @@ -1762,6 +1800,11 @@ procedure TDebugger.TraceImports(IAT: NativeUInt); else if FTracedAPI <> 0 then begin Log(ltInfo, '-> ' + IntToHex(FTracedAPI, 8)); + if (FTracedAPI < $10000) or ((FTracedAPI >= FImageBase) and (FTracedAPI < FImageBoundary)) then + begin + Log(ltInfo, 'Discarding result & aborting IAT tracing'); + Break; + end; IATData[i] := FTracedAPI; end else diff --git a/Dumper.pas b/Dumper.pas index b6894ab..bd2ee91 100644 --- a/Dumper.pas +++ b/Dumper.pas @@ -36,6 +36,7 @@ TDumper = class FForwardsType2: TForwardDict; // Key: NTDLL, Value: user32 (points to fwd-string) FForwardsOle32: TForwardDict; // Key: combase, Value: ole32 FForwardsNetapi32: TForwardDict; // Key: netutils, Value: netapi32 + FAllModules: TDictionary; FIATImage: PByte; FIATImageSize: Cardinal; @@ -45,14 +46,19 @@ TDumper = class procedure CollectNTFwd; overload; procedure CollectForwards(Fwds: TForwardDict; hModReal, hModScan: HMODULE); overload; procedure GatherModuleExportsFromRemoteProcess(M: PRemoteModule); + procedure TakeModuleSnapshot; function GetLocalProcAddr(hModule: HMODULE; ProcName: PAnsiChar): Pointer; function RPM(Address: NativeUInt; Buf: Pointer; BufSize: NativeUInt): Boolean; public - constructor Create(const AProcess: TProcessInformation; AImageBase, AOEP, AIAT: UIntPtr); + constructor Create(const AProcess: TProcessInformation; AImageBase, AOEP: UIntPtr); destructor Destroy; override; function Process: TPEHeader; procedure DumpToFile(const FileName: string; PE: TPEHeader); + + function IsAPIAddress(Address: NativeUInt): Boolean; + + property IAT: NativeUInt read FIAT write FIAT; // Virtual address of IAT in target end; implementation @@ -61,16 +67,12 @@ implementation { TDumper } -constructor TDumper.Create(const AProcess: TProcessInformation; AImageBase, AOEP, AIAT: UIntPtr); +constructor TDumper.Create(const AProcess: TProcessInformation; AImageBase, AOEP: UIntPtr); begin FProcess := AProcess; FOEP := AOEP; - FIAT := AIAT; FImageBase := AImageBase; - if FIAT > $70000000 then - raise Exception.Create('Wrong IAT address'); - if Win32MajorVersion > 5 then begin FUsrPath := PChar(ExtractFilePath(ParamStr(0)) + 'mmusr32.dll'); @@ -86,11 +88,24 @@ constructor TDumper.Create(const AProcess: TProcessInformation; AImageBase, AOEP end; destructor TDumper.Destroy; +var + RM: PRemoteModule; begin FForwards.Free; FForwardsType2.Free; FForwardsOle32.Free; FForwardsNetapi32.Free; + + if FAllModules <> nil then + begin + for RM in FAllModules.Values do + begin + RM.ExportTbl.Free; + Dispose(RM); + end; + FAllModules.Free; + end; + if FIATImage <> nil then FreeMem(FIATImage); @@ -203,21 +218,22 @@ function TDumper.Process: TPEHeader; IAT: PByte; i, j: Integer; IATSize, Diff: Cardinal; + LastValidOffset: NativeUInt; PE: TPEHeader; a: ^PByte; Fwd: Pointer; Thunks: TList; Thunk: TImportThunk; NeedNewThunk, Found: Boolean; - hSnap: THandle; - ME: TModuleEntry32; - Modules: TDictionary; RM: PRemoteModule; s: AnsiString; Section, Strs, RangeChecker: PByte; Descriptors: PImageImportDescriptor; ImportSect: PPESection; begin + if FIAT = 0 then + raise Exception.Create('Must set IAT before calling Process()'); + // Read header from memory GetMem(Section, $1000); RPM(FImageBase, Section, $1000); @@ -228,51 +244,27 @@ function TDumper.Process: TPEHeader; GetMem(IAT, MAX_IAT_SIZE); RPM(FIAT, IAT, MAX_IAT_SIZE); - IATSize := 0; - for i := 0 to MAX_IAT_SIZE - 9 do - if PUInt64(IAT + i)^ = 0 then - begin - IATSize := i; - Break; - end; - - if IATSize = 0 then + LastValidOffset := 0; + i := 0; + while i < MAX_IAT_SIZE do begin - for i := 0 to MAX_IAT_SIZE - 13 do - if (PCardinal(IAT + i)^ = 0) and (PCardinal(IAT + i + 8)^ = 0) and (PCardinal(IAT + i + 4)^ < FImageBase + PE.NTHeaders.OptionalHeader.SizeOfImage) then - begin - IATSize := i; - Break; - end; + if IsAPIAddress(PNativeUInt(IAT + i)^) then + LastValidOffset := NativeUInt(i); - if IATSize = 0 then - raise Exception.Create('IAT size could not be determined'); + Inc(i, SizeOf(Pointer)); end; + IATSize := LastValidOffset + SizeOf(Pointer); + Log(ltInfo, Format('Determined IAT size: %X', [IATSize])); + with PE.NTHeaders.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IAT] do begin VirtualAddress := FIAT - FImageBase; - Size := IATSize + 4; + Size := IATSize + SizeOf(Pointer); end; - Modules := TDictionary.Create; - hSnap := CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, FProcess.dwProcessId); - ME.dwSize := SizeOf(TModuleEntry32); - if not Module32First(hSnap, ME) then - raise Exception.Create('Module32First'); - repeat - if ME.hModule <> FImageBase then - begin - //Log(ltInfo, IntToHex(ME.hModule, 8) + ' : ' + IntToHex(ME.modBaseSize, 4) + ' : ' + string(ME.szModule)); - New(RM); - RM.Base := ME.modBaseAddr; - RM.EndOff := ME.modBaseAddr + ME.modBaseSize; - RM.Name := LowerCase(ME.szModule); - RM.ExportTbl := nil; - Modules.AddOrSetValue(RM.Name, RM); - end; - until not Module32Next(hSnap, ME); - CloseHandle(hSnap); + if FAllModules = nil then + TakeModuleSnapshot; Thunks := TObjectList.Create; a := Pointer(IAT); @@ -298,7 +290,7 @@ function TDumper.Process: TPEHeader; end; Found := False; - for RM in Modules.Values do + for RM in FAllModules.Values do if (RangeChecker > RM.Base) and (RangeChecker < RM.EndOff) then begin if RM.ExportTbl = nil then @@ -343,7 +335,7 @@ function TDumper.Process: TPEHeader; s := AnsiString(Thunk.Name); Move(s[1], Strs^, Length(s)); Inc(Strs, Length(s) + 1); - RM := Modules[Thunk.Name]; + RM := FAllModules[Thunk.Name]; Log(ltInfo, 'Thunk ' + Thunk.Name + ' - first import: ' + RM.ExportTbl[Thunk.Addresses.First^]); for j := 0 to Thunk.Addresses.Count - 1 do begin @@ -376,14 +368,7 @@ function TDumper.Process: TPEHeader; Size := Thunks.Count * SizeOf(TImageImportDescriptor); end; - Pointer(Descriptors) := nil; Thunks.Free; - for RM in Modules.Values do - begin - RM.ExportTbl.Free; - Dispose(RM); - end; - Modules.Free; FIATImage := IAT; FIATImageSize := IATSize; @@ -446,6 +431,51 @@ function TDumper.GetLocalProcAddr(hModule: HMODULE; ProcName: PAnsiChar): Pointe Result := nil; end; +function TDumper.IsAPIAddress(Address: NativeUInt): Boolean; +var + RM: PRemoteModule; +begin + if FAllModules = nil then + TakeModuleSnapshot; + + for RM in FAllModules.Values do + if (Address >= NativeUInt(RM.Base)) and (Address < NativeUInt(RM.EndOff)) then + begin + if RM.ExportTbl = nil then + GatherModuleExportsFromRemoteProcess(RM); + + Exit(RM.ExportTbl.ContainsKey(Pointer(Address))); + end; + + Result := False; +end; + +procedure TDumper.TakeModuleSnapshot; +var + hSnap: THandle; + ME: TModuleEntry32; + RM: PRemoteModule; +begin + FAllModules := TDictionary.Create; + hSnap := CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, FProcess.dwProcessId); + ME.dwSize := SizeOf(TModuleEntry32); + if not Module32First(hSnap, ME) then + raise Exception.Create('Module32First'); + repeat + if ME.hModule <> FImageBase then + begin + //Log(ltInfo, IntToHex(ME.hModule, 8) + ' : ' + IntToHex(ME.modBaseSize, 4) + ' : ' + string(ME.szModule)); + New(RM); + RM.Base := ME.modBaseAddr; + RM.EndOff := ME.modBaseAddr + ME.modBaseSize; + RM.Name := LowerCase(ME.szModule); + RM.ExportTbl := nil; + FAllModules.AddOrSetValue(RM.Name, RM); + end; + until not Module32Next(hSnap, ME); + CloseHandle(hSnap); +end; + function TDumper.RPM(Address: NativeUInt; Buf: Pointer; BufSize: NativeUInt): Boolean; begin Result := ReadProcessMemory(FProcess.hProcess, Pointer(Address), Buf, BufSize, BufSize);