Jump to content
Registration disabled at the moment Read more... ×

Kas Ob.

Members
  • Content Count

    596
  • Joined

  • Last visited

  • Days Won

    10

Kas Ob. last won the day on July 5

Kas Ob. had the most liked content!

Community Reputation

151 Excellent

Recent Profile Visitors

The recent visitors block is disabled and is not being shown to other users.

  1. Kas Ob.

    Libreoffice integration struggles

    Here is the output of what seems working capture of all the events/actions from LB, though tested in one instance with text file Document loaded successfully Global event listener registered Document loaded. Press Enter to exit. Event: OnLayoutFinished Other event: OnLayoutFinished Event: OnTitleChanged Other event: OnTitleChanged Event: OnModifyChanged Other event: OnModifyChanged Event: OnLayoutFinished Other event: OnLayoutFinished Event: OnSave Save event detected! Event: OnCopyTo Other event: OnCopyTo Event: OnCopyToDone Other event: OnCopyToDone Captured data: Hi From Delphi !.1 Event: OnSaveFailed SaveFailed event detected! Event: OnModeChanged Other event: OnModeChanged Event: DialogExecute Other event: DialogExecute Event: OnModeChanged Other event: OnModeChanged Event: DialogClosed Other event: DialogClosed Event: OnLayoutFinished Other event: OnLayoutFinished Event: OnSave Save event detected! Event: OnCopyTo Other event: OnCopyTo Event: OnCopyToDone Other event: OnCopyToDone Captured data: Hi From Delphi !.13 Event: OnSaveFailed SaveFailed event detected! Event: OnModeChanged Other event: OnModeChanged Event: DialogExecute Other event: DialogExecute Event: OnModeChanged Other event: OnModeChanged Event: DialogClosed Other event: DialogClosed Event: OnPrepareViewClosing Other event: OnPrepareViewClosing Event: OnPrepareUnload Other event: OnPrepareUnload Event: OnModeChanged Other event: OnModeChanged Event: DialogExecute Other event: DialogExecute Event: OnModeChanged Other event: OnModeChanged Event: DialogClosed Other event: DialogClosed Event: OnViewClosed Other event: OnViewClosed Event: OnUnload Other event: OnUnload Event: OnUnfocus Other event: OnUnfocus Event: OnCloseApp Other event: OnCloseApp @Pierre le Riche i am DM my work, and can't guarantee it to be error/mistake free, yet it looks working and didn't dig more in the dialog handling, (like how to stop them ) and i expected the pipe for saving (output) is for one use.
  2. Kas Ob.

    Libreoffice integration struggles

    Nice to hear and you are welcome. The good news is that i solved the GetFuncDesc, use it, it is working now fine function TDocumentEventListener.GetFuncDesc(index: Integer; out pfuncdesc: PFuncDesc): HResult; var FuncDesc: TFuncDesc; ElemDescList: PElemDescList; begin Writeln('EventListener GetFuncDesc: index=', index); if index in [0, 1] then begin FillChar(FuncDesc, SizeOf(TFuncDesc), 0); // Allocate array of one ElemDescList := CoTaskMemAlloc(SizeOf(TElemDesc)); FillChar(ElemDescList^, SizeOf(TElemDesc), 0); ElemDescList^[0].tdesc.vt := VT_VARIANT; ElemDescList^[0].paramdesc.wParamFlags := PARAMFLAG_FIN; if index = 0 then begin FuncDesc.memid := 1; FuncDesc.lprgelemdescParam := ElemDescList; FuncDesc.cParams := 1; end else begin FuncDesc.memid := 4; FuncDesc.lprgelemdescParam := ElemDescList; FuncDesc.cParams := 1; end; FuncDesc.funckind := FUNC_DISPATCH; FuncDesc.invkind := INVOKE_FUNC; FuncDesc.elemdescFunc.tdesc.vt := VT_HRESULT; pfuncdesc := CoTaskMemAlloc(SizeOf(TFuncDesc)); Move(FuncDesc, pfuncdesc^, SizeOf(TFuncDesc)); Result := S_OK; end else begin pfuncdesc := nil; Result := DISP_E_BADINDEX; end; end; I feel like i am implementing the whole UNO interface with this one 🤬 Anyway stopped at handling Invoke (as it is called now smoothly) and HandleDocumentEvent (my own field in the eventlister), HandleDocumentEvent is hell to handle, and can't find a real nice documentation for events and their parameters, its like shooting darts in the dark. ps still using two separated CoTaskMemAlloc and they can be merged in one by aligning the memory, as will be released together, yet not touching it for now.
  3. Kas Ob.

    Libreoffice integration struggles

    I would leave this to the last, as from what i got once pipe is closed then it is closed and need replacing, but ... who knows. For few hours i also was trying to do the same, but the code is not working fully because i am stuck with again UNO layer, which is causing this, anyway you need to call ".addDocumentEventListener(EventListener as IDispatch);" on either 'com.sun.star.frame.GlobalEventBroadcaster' or LDocument, both working the same from what i see, yet the problem is in EventListener implementation It should be TDocumentEventListener = class(TInterfacedObject, IDispatch, ITypeInfo) And i see callbacks being triggering GetTypeInfo and GetFuncDesc, but filling and answering GetFuncDesc is really confusing, Invoke on other hand being called once at least but i thinkit is the one belongs to IDispathc, ... don't know what to say more, but hope that put you on the road and i might try again later but for today i am now in full hate mode for LibreOffice and its documentation, also its source, it is hard to read and follow. Also debugging is hard, on top of LB is crashing and stop responding, don't know if this happen with your setup, but after some crashes it stop to respond and i need full system restart to bring it up again, though it might be failure on OS side to clean OLE machine. I am stuck at GetFuncDesc, tried many things but it still causing AV, as it went all the way to execute Invoke, yet i am failing to point/pass/fill it right.
  4. Kas Ob.

    Libreoffice integration struggles

    At last ! 😎 @Pierre le Riche found a solution, by using Pipe (com.sun.star.io.Pipe) instead of stream, one can get your needed result. https://api.libreoffice.org/docs/idl/ref/servicecom_1_1sun_1_1star_1_1io_1_1Pipe.html program LibreOfficeLoadingFromStream; {$APPTYPE CONSOLE} uses System.SysUtils, System.Win.ComObj, Winapi.ActiveX; procedure LoadWriterDocumentFromBytes(const ADocumentData: TBytes; const aFilterName: string = 'Text'; aFilterOptions: string = ''); var LServiceManager, LDesktop, LProperties: Variant; LPipe: Variant; LPropertyArray: TArray<Variant>; begin {Connect to LibreOffice} LServiceManager := CreateOleObject('com.sun.star.ServiceManager'); LDesktop := LServiceManager.createInstance('com.sun.star.frame.Desktop'); LPipe := LServiceManager.createInstance('com.sun.star.io.Pipe'); LPipe.writeBytes(Variant(ADocumentData)); LPipe.closeOutput; // Important: close output to signal end of data (adding and simulating EOF) SetLength(LPropertyArray, 2); LPropertyArray[0] := LServiceManager.Bridge_GetStruct('com.sun.star.beans.PropertyValue'); LPropertyArray[0].Name := 'InputStream'; LPropertyArray[0].Value := LPipe; LPropertyArray[1] := LServiceManager.Bridge_GetStruct('com.sun.star.beans.PropertyValue'); LPropertyArray[1].Name := 'FilterName'; LPropertyArray[1].Value := aFilterName; if aFilterOptions <> '' then begin SetLength(LPropertyArray, 3); LPropertyArray[2] := LServiceManager.Bridge_GetStruct('com.sun.star.beans.PropertyValue'); LPropertyArray[2].Name := 'FilterOptions'; LPropertyArray[2].Value := aFilterOptions; end; LProperties := LPropertyArray; LDesktop.loadComponentFromURL('private:stream', '_blank', 0, LProperties); end; const {(*} TXTData = 'Hi From Delphi !.'; HTMLData = '<!DOCTYPE html><html><body><h1>My First Heading</h1><p>My first paragraph.</p></body></html>'; // don't know how ! RTFData = ' {\rtf1\ansi{\fonttbl\f0\fswiss Helvetica;}\f0\pard This is some {\b bold} text.\par }'; // also still missing something CSVData = 'Name,Age,City,Country' + #13#10 + 'John Doe,30,New York,USA' + #13#10 + 'Jane Smith,25,London,UK' + #13#10 + 'Bob Johnson,35,Toronto,Canada' + #13#10 + 'Alice Brown,28,Sydney,Australia';{*)} CSVFilterOption = '44,34,76,1,1/5/2/1/3/5/4/5,0,true,true,true'; // for the above CSVData begin CoInitialize(nil); try LoadWriterDocumentFromBytes(BytesOf(TXTData)); //LoadWriterDocumentFromBytes(BytesOf(HTMLData),'HTML Document (Writer)'); // missing something may be //LoadWriterDocumentFromBytes(BytesOf(RTFData),'Rich Text'); // also LoadWriterDocumentFromBytes(BytesOf(CSVData), 'Text - txt - csv (StarCalc)', CSVFilterOption); // for some reason 'csv' is not enough except on E: Exception do Writeln(E.ClassName, ': ', E.Message); end; Writeln('Done.'); Readln; end. The result is smooth loading of two instances SWriter and SCalc Tried to load HML and RTF but didn't work, and i really got bored with this, and believe this is a solution for your obstacle for now. ps if you managed to make it load html or pdf, then please share ! extra info the opened instances has document named as "private:stream" but this is easy to rename the most complete supported filters is this https://help.libreoffice.org/25.2/en-US/text/shared/guide/convertfilters.html Good luck !
  5. Kas Ob.

    Libreoffice integration struggles

    This is exactly what i do understand now, UNO only exist for Java and C++, and it is higher level abstraction, like there is no Initialize you tried to use in the API but it is only exist in UNO API. Also, the usage i mentioned about Input/Output is there, and it is confusing, as in my example above only Output can write and read, but the Input is to receive only. What get to mind, is the similarity between of this get/load from memory in Open/LibreOffice and embedded Internet Explorer, it doesn't allow to load/navigate to stream or memory data, but you should navigate to an blank then load the content form memory, this lead me now to this working example with SWriter program LibreOfficeFromDelphi; {$APPTYPE CONSOLE} {$R *.res} uses System.SysUtils, System.Win.ComObj, Winapi.ActiveX, Variants; procedure LoadSWriterDocumentFromTBytes(const ADocumentData: string); var LServiceManager, LDesktop, LDocument, LText, LTextCursor: Variant; begin {Connect to LibreOffice} LServiceManager := CreateOleObject('com.sun.star.ServiceManager'); LDesktop := LServiceManager.createInstance('com.sun.star.frame.Desktop'); LDocument := LDesktop.loadComponentFromURL('private:factory/swriter', '_blank', 0, VarArrayCreate([0, -1], varVariant)); LText := LDocument.Text; LTextCursor := LText.createTextCursor; LText.insertString(LTextCursor, ADocumentData, False); end; var SomeTextForWriter: string; begin CoInitialize(nil); try SomeTextForWriter := ' Hello, from Delphi'; LoadSWriterDocumentFromTBytes(SomeTextForWriter); except on E: Exception do Writeln(E.ClassName, ': ', E.Message); end; Writeln('Done.'); Readln; end. The result But the core is inserting text !, simple text, may be there is a way to insert formatted text !? And for SCalc, also opening blank is working fine, but there is no text but sheets and stuff. The whole thing is very similar to DOM, may be it is XComponent , but really can't find useful information here, all internet resources leads to the blocked road with UNO API.
  6. Kas Ob.

    Libreoffice integration struggles

    This works fine program LibreOfficeLoadingFromStream; {$APPTYPE CONSOLE} uses System.SysUtils, System.Win.ComObj, Winapi.ActiveX; procedure LoadWriterDocumentFromStream(const ADocumentData: TBytes); var LServiceManager, LDesktop, LStream, LDocData, LProperties: Variant; LPropertyArray: TArray<Variant>; WrittenBytes: TBytes; begin {Connect to LibreOffice} LServiceManager := CreateOleObject('com.sun.star.ServiceManager'); LDesktop := LServiceManager.createInstance('com.sun.star.frame.Desktop'); {Instantiate in input stream to load the document data from} //LInputStream := LServiceManager.createInstance('com.sun.star.io.SequenceInputStream'); LStream := LServiceManager.createInstance('com.sun.star.io.SequenceOutputStream'); // <- {Populate the input stream with the document data} LDocData := ADocumentData; //LInputStream.createStreamFromSequence(LDocData); // <- error here LStream.writeBytes(LDocData); WrittenBytes := LStream.getWrittenBytes; Writeln(StringOf(WrittenBytes)); // test again Writeln('Test again'); LStream.writeBytes(Variant(BytesOf(#13#10'Another TExt'))); Writeln(StringOf(LStream.getWrittenBytes)); end; var LDocData: TBytes; begin CoInitialize(nil); //SetLength(LDocData, 10000); //In an actual application LDocData would contain the .odt document file content LDocData := BytesOf('Sample text 123!@#'); try LoadWriterDocumentFromStream(LDocData); except on E: Exception do Writeln(E.ClassName, ': ', E.Message); end; Readln; end. the output is The problem is in API naming and assumption of simple logical naming, it seems the Output/Input words (and meaning) are reversed in the whole SDK, so you should look at them in reverse (from the binary/exe perspective) https://api.libreoffice.org/docs/idl/ref/namespacecom_1_1sun_1_1star_1_1io.html and the one you need is https://api.libreoffice.org/docs/idl/ref/servicecom_1_1sun_1_1star_1_1io_1_1SequenceOutputStream.html
  7. Kas Ob.

    Need help investigating an app crash

    Yes i don't like seeing these on the call stack captured and reported by OS This interpreted as the exception handling did fail and returned to the OS, hence the shut down. I don't have any experience with JclHookExcept, never used it, and looking at its code https://github.com/bematech/jcl/blob/master/windows/JclHookExcept.pas#L383 leading to https://github.com/bematech/jcl/blob/master/windows/JclHookExcept.pas#L322 Now what, did you tried to resolve or access the custom exception ? may be trying to report some buffer, return values, or any thing was on a thread stack that might be already gone ? This is very plausible scenario.
  8. Kas Ob.

    Need help investigating an app crash

    1) Windows version 2) Confirm if this crash happen with the same exact addresses/parameters , specially the "Fault Offset", it is important. 3) How frequent this happen ? can you reproduce or predict the moment of this crash ? 4) Get the ProcDump with full dump like this "procdump -ma -e 1 -t MyStuff.exe MyStuffCrash.dmp" , this is loadable with WinDBG then try to understand what exist at 0xf0a79e3d with "!address" Theories: 1) That fault offset is not out of thin air, it might be coming from hidden WOW64, yet the looks like 32bit hence the unexplained address, these emulator DLLs are 64bit and loaded in 32bit, though impossible to see or interact with them from the process itself, only you see their exception when they raised, as always their exceptions are fatal. 2) handling the exception did in fact corrupt the stack and triggered cascade of problems in Windows trying to unwind , so i don't like these two calls appears on the call stack they looks the cause, just remove them and see if things changes, but this has to do with the extra information above (3) how the above will help, well, you can't solve such crash without pinpoint specific operation, those are (form my experience) a conflict in IO operation combined with stack usage for buffer (or some return values) from different thread, missing or forgetting about these return values, so review your code and make sure your local variable are not passed to different threads, or handling the exception itself triggered extra corruption.
  9. Never used WSAConnectByList, i barely remember testing it, but blocking only without overlapped makes it usable only in specific cases. My suggestion is neither approaches, implement connect with multiple socket at the same time, one Select will do and can be used with up to 64 IPs, the first connect will be used the rest will be closed, this how i do it. lets say you resolved a host (they almost always returned in random order), i pair one IPv4 with IPv6 and connect to them both, the fastest kept and the other dropped without sending anyting. On side note; IPv6 sometimes outperform IPv4, but not always, my ISP doesn't provide IPv6 in my region, so i use 6in4 tunnels on my router, the tunnels from two providers http://tb.netassist.ua/ and https://tunnelbroker.net/ , the setting are on the router and have punch of end points, now; how they do perform is unpredictable, connecting to north America using NA is slow as turtle, and connecting to Asia using HE is slower then IPv4 by a lot like 2 seconds, yet with NA IPv6 to EU is faster than IPv4, this is just a suggestion to enhance the quality and the speed of the connection by selecting the faster SYN/ACK. Hope that help.
  10. Hi, Not really a clue but a guess reading the log above and the steps, the shut down triggered at 16kb of sending, i read this as clue, it looks like the pending response ( aka blocking request) translated into disconnect and wrongly handled as failure instead. So; 1) a question on side but might be important, were did these numbers 1476 and 1468 come from ?, did you calculate the MTU and decided it should be like that ? the lack of client side successful recv means the log entry wasn't from successful send event, track those, (but again of ICS have them, as i don't know) 2) i can't tell how ICS works for websocket, but i think Angus can tell if the blocking handled right in your version, yet he recommended that you update, then update, also reproducing such behavior should be fairly easy, @FrozenK if you can provide short project reproducing that shutdown trigger, then that behavior can be solved once and for all. 3) Have you missed around with TCP buffers sizes ? (namely sending buffer) , are you tweaking some socket options ? the default send buffer on Windows is 8kb, but this doesn't means you can't send more, it is just little more long story to explain. 4) Also, the problem in my opinion is that ACK hadn't been received and sending party reached some a limit around 16kb (limit but not receiving peer ACK), this triggered blocking request (pending), and this translated into error with lead to shut down, in other words handled, the socket is not checking if ready to send while handling WSAEWOULDBLOCK as fatal connection error, while it is not. Hope that helps.
  11. Kas Ob.

    LoadLibrary and FreeLibrary notification

    It just hit me, Don't cache them all, as some DLL will never show in the stack, like never, even very common libraries like "AdvApi32.dll" or "Ws2_32.dll" will not be on stack call unless they used callback (used once), so if cached only the found ones you will end up with short list and sorted by importance and appearance.
  12. Kas Ob.

    LoadLibrary and FreeLibrary notification

    Yes, it might be be slower, but the idea is even with your cache you will have to walk it, and if you re-refactored my code above it is really just loop over linked-list, In case you want to test it, then notice that PEB is immovable like its LdrData, so that part can be in initialization clause, this will leave the code close to function FindModuleByAddress(Address: Pointer): TModuleInfo; var ModuleEntry: PLDR_DATA_TABLE_ENTRY; CurrentEntry, FirstEntry: PLIST_ENTRY; ModuleBaseAddr, ModuleEndAddr: NativeUInt; FlinkPtr: PPointer; ProtCounter: Integer; // protection counter against looping over ring doubly linked list begin Result.IsValid := False; FlinkPtr := PPointer(NativeUInt(LdrData) + Flink_OFFSET); // Offset is 12 for 32bit and 24 for 64bit CurrentEntry := FlinkPtr^; if AddressViolateMemoryRange(CurrentEntry) then Exit; FirstEntry := CurrentEntry; // protection against endless looping over ring list ProtCounter := 0; // protection against endless looping over ring list in case FirstEntry is unloaded after the fact while Assigned(CurrentEntry) and (not AddressViolateMemoryRange(CurrentEntry)) and (CurrentEntry <> PList_Entry(NativeUInt(LdrData) + Flink_OFFSET)) do begin ModuleEntry := PLDR_DATA_TABLE_ENTRY(CurrentEntry); try if Assigned(ModuleEntry^.DllBase) then begin ModuleBaseAddr := NativeUInt(ModuleEntry^.DllBase); ModuleEndAddr := ModuleBaseAddr + ModuleEntry^.SizeOfImage; if (NativeUInt(Address) >= ModuleBaseAddr) and (NativeUInt(Address) < ModuleEndAddr) then begin // found module that have the address .. Result.IsValid := True; Exit; end; end; except // Ignore access violations, do not handle anything here end; if ProtCounter >= LOOPING_PROTECTION_LIMIT then Exit; Inc(ProtCounter); CurrentEntry := CurrentEntry^.Flink; if CurrentEntry = FirstEntry then Break; end; end; As for caching, i think you are the best one to decide if you want to cache few (may be 3 and that it) modules addresses that will be %90 of the times in any stack call, the EXE module itself and kernel and User32, saving these 3 will make the protection counter way better than random 1000, by comparing against loaded EXE address and trigger exit after second occurrence, this will prevent loop up to 1000. In all cases, you are welcome.
  13. Kas Ob.

    LoadLibrary and FreeLibrary notification

    Here a more precise and protected version will less redundant code unit lcAddressToModule; interface uses Windows; type TModuleInfo = record ModuleBase: Pointer; ModuleSize: Cardinal; ModuleName: string; FullPath: string; EntryPoint: Pointer; IsValid: Boolean; end; function FindModuleByAddress(Address: Pointer): TModuleInfo; implementation const Flink_OFFSET = {$IFDEF CPUX64} 24 {$ELSE} 12 {$ENDIF}; // Offset is 12 for 32bit and 24 for 64bit LOOPING_PROTECTION_LIMIT = 1000; type PLIST_ENTRY = ^LIST_ENTRY; LIST_ENTRY = packed record Flink: PLIST_ENTRY; Blink: PLIST_ENTRY; end; PUNICODE_STRING = ^UNICODE_STRING; UNICODE_STRING = packed record Length: Word; MaximumLength: Word; {$IFDEF CPUX64} Padding: array[0..3] of Byte; // Align Buffer to 8 bytes {$ENDIF} Buffer: PWideChar; end; PLDR_DATA_TABLE_ENTRY = ^LDR_DATA_TABLE_ENTRY; LDR_DATA_TABLE_ENTRY = packed record InLoadOrderLinks: LIST_ENTRY; InMemoryOrderLinks: LIST_ENTRY; InInitializationOrderLinks: LIST_ENTRY; DllBase: Pointer; EntryPoint: Pointer; SizeOfImage: ULONG; {$IFDEF CPUX64} Padding: array[0..3] of Byte; // Align FullDllName to 60 {$ENDIF} FullDllName: UNICODE_STRING; BaseDllName: UNICODE_STRING; end; PPEB_LDR_DATA = ^PEB_LDR_DATA; PEB_LDR_DATA = packed record Length: ULONG; Initialized: Boolean; {$IFDEF CPUX64} Padding: array[0..2] of Byte; // Align SsHandle to 8 bytes {$ENDIF} SsHandle: Pointer; InLoadOrderModuleList: LIST_ENTRY; end; PPEB = ^PEB; PEB = packed record Reserved: array[0..2] of Byte; BeingDebugged: Byte; {$IFDEF CPUX64} Reserved2: array[0..11] of Byte; // Align Ldr to 24 {$ELSE} Reserved2: array[0..3] of Byte; // Align Ldr to 12 {$ENDIF} ImageBaseAddress: Pointer; Ldr: PPEB_LDR_DATA; end; function RtlGetCurrentPeb: Pointer; stdcall; external 'ntdll.dll'; function GetCurrentPEB: PPEB; begin Result := RtlGetCurrentPeb; end; function AddressViolateMemoryRange(Address: Pointer): Boolean; begin Result := (NativeUInt(Address) < $10000) or // always in user mode then it shold be above 64kb // $BFFFFFFF when (/3G) and/or IMAGE_FILE_LARGE_ADDRESS_AWARE is enabled, when disabled it should be $7FFFFFFF (NativeUInt(Address) > {$IFDEF CPUX64} $7FFFFFFFFFFFFFFF {$ELSE} $BFFFFFFF {$ENDIF}); end; function FindModuleByAddress(Address: Pointer): TModuleInfo; var PEB: PPEB; LdrData: PPEB_LDR_DATA; ModuleEntry: PLDR_DATA_TABLE_ENTRY; CurrentEntry, FirstEntry: PLIST_ENTRY; ModuleBaseAddr, ModuleEndAddr: NativeUInt; FlinkPtr: PPointer; ProtCounter: Integer; // protection counter against looping over ring doubly linked list begin FillChar(Result, SizeOf(Result), 0); Result.IsValid := False; if not Assigned(Address) or AddressViolateMemoryRange(Address) then Exit; PEB := GetCurrentPEB; if not Assigned(PEB) or not Assigned(PEB^.Ldr) then Exit; LdrData := PEB^.Ldr; if not LdrData^.Initialized then Exit; FlinkPtr := PPointer(NativeUInt(LdrData) + Flink_OFFSET); // Offset is 12 for 32bit and 24 for 64bit CurrentEntry := FlinkPtr^; if AddressViolateMemoryRange(CurrentEntry) then Exit; FirstEntry := CurrentEntry; // protection against endless looping over ring list ProtCounter := 0; // protection against endless looping over ring list in case FirstEntry is unloaded after the fact while Assigned(CurrentEntry) and (not AddressViolateMemoryRange(CurrentEntry)) and (CurrentEntry <> PList_Entry(NativeUInt(LdrData) + Flink_OFFSET)) do begin ModuleEntry := PLDR_DATA_TABLE_ENTRY(CurrentEntry); try if Assigned(ModuleEntry^.DllBase) then begin ModuleBaseAddr := NativeUInt(ModuleEntry^.DllBase); ModuleEndAddr := ModuleBaseAddr + ModuleEntry^.SizeOfImage; if (NativeUInt(Address) >= ModuleBaseAddr) and (NativeUInt(Address) < ModuleEndAddr) then begin // found module that have the address Result.ModuleBase := ModuleEntry^.DllBase; Result.ModuleSize := ModuleEntry^.SizeOfImage; Result.EntryPoint := ModuleEntry^.EntryPoint; if Assigned(ModuleEntry^.BaseDllName.Buffer) and (ModuleEntry^.BaseDllName.Length > 0) then Result.ModuleName := ModuleEntry^.BaseDllName.Buffer; if Assigned(ModuleEntry^.FullDllName.Buffer) and (ModuleEntry^.FullDllName.Length > 0) then Result.FullPath := ModuleEntry^.FullDllName.Buffer; Result.IsValid := True; Exit; end; end; except // Ignore access violations, do not handle anything here end; if ProtCounter >= LOOPING_PROTECTION_LIMIT then Exit; Inc(ProtCounter); CurrentEntry := CurrentEntry^.Flink; if CurrentEntry = FirstEntry then Break; end; end; end.
  14. Kas Ob.

    LoadLibrary and FreeLibrary notification

    This may be better than using Assembly function RtlGetCurrentPeb: Pointer; stdcall; external 'ntdll.dll'; function GetCurrentPEB: PPEB; begin Result := RtlGetCurrentPeb; end;
  15. Kas Ob.

    LoadLibrary and FreeLibrary notification

    @Pierre le Riche I searched for an article i read sometimes ago, scrapped few lines based on its idea, which is using PEB to walk the loaded modules, so no callback notification and no caching is needed, you can resolve on spot and when needed, also you can extract the table in-place to use per one stack walk operation. the article https://exploit-notes.hdks.org/exploit/reverse-engineering/debugger/windows-process-internals-with-windbg/ PEB structure https://www.geoffchappell.com/studies/windows/km/ntoskrnl/inc/api/pebteb/peb/index.htm PEB_LDR_DATA https://www.geoffchappell.com/studies/windows/km/ntoskrnl/inc/api/ntpsapi_x/peb_ldr_data.htm?tx=186 LDR_DATA_TABLE_ENTRY https://www.geoffchappell.com/studies/windows/km/ntoskrnl/inc/api/ntldr/ldr_data_table_entry/index.htm?ta=10&amp;tx=96,97,105,106,109,114,173,177,179,186,192,195 the unit unit lcAddressToModule; interface uses Windows; type PLIST_ENTRY = ^LIST_ENTRY; LIST_ENTRY = packed record Flink: PLIST_ENTRY; Blink: PLIST_ENTRY; end; PUNICODE_STRING = ^UNICODE_STRING; UNICODE_STRING = packed record Length: Word; MaximumLength: Word; {$IFDEF CPUX64} Padding: array[0..3] of Byte; // Align Buffer to 8 bytes {$ENDIF} Buffer: PWideChar; end; PLDR_DATA_TABLE_ENTRY = ^LDR_DATA_TABLE_ENTRY; LDR_DATA_TABLE_ENTRY = packed record InLoadOrderLinks: LIST_ENTRY; InMemoryOrderLinks: LIST_ENTRY; InInitializationOrderLinks: LIST_ENTRY; DllBase: Pointer; EntryPoint: Pointer; SizeOfImage: ULONG; {$IFDEF CPUX64} Padding: array[0..3] of Byte; // Align FullDllName to 60 {$ENDIF} FullDllName: UNICODE_STRING; BaseDllName: UNICODE_STRING; end; PPEB_LDR_DATA = ^PEB_LDR_DATA; PEB_LDR_DATA = packed record Length: ULONG; Initialized: Boolean; {$IFDEF CPUX64} Padding: array[0..2] of Byte; // Align SsHandle to 8 bytes {$ENDIF} SsHandle: Pointer; InLoadOrderModuleList: LIST_ENTRY; end; PPEB = ^PEB; PEB = packed record Reserved: array[0..2] of Byte; BeingDebugged: Byte; {$IFDEF CPUX64} Reserved2: array[0..11] of Byte; // Align Ldr to 24 {$ELSE} Reserved2: array[0..3] of Byte; // Align Ldr to 12 {$ENDIF} ImageBaseAddress: Pointer; Ldr: PPEB_LDR_DATA; end; TModuleInfo = record ModuleBase: Pointer; ModuleSize: ULONG; ModuleName: string; FullPath: string; EntryPoint: Pointer; IsValid: Boolean; end; function FindModuleByAddress(Address: Pointer): TModuleInfo; implementation function GetCurrentPEB: PPEB; asm {$IFDEF CPUX64} mov rax, gs:[$60] // PEB at offset 0x60 in TEB on x64 {$ELSE} mov eax, fs:[$30] // PEB at offset 0x30 in TEB on x86 {$ENDIF} end; function AddressViolateUpperRange(Address: Pointer): Boolean; begin Result := NativeUInt(Address) > {$IFDEF CPUX64} $7FFFFFFFFFFF {$ELSE} $7FFFFFFF {$ENDIF}; end; function AddressViolateLowerRange(Address: Pointer): Boolean; begin Result := NativeUInt(Address) < $10000; // always in user mode then it shold be above 64kb end; function FindModuleByAddress(Address: Pointer): TModuleInfo; var PEB: PPEB; LdrData: PPEB_LDR_DATA; ModuleEntry: PLDR_DATA_TABLE_ENTRY; CurrentEntry, FirstEntry: PLIST_ENTRY; ModuleBaseAddr, ModuleEndAddr: NativeUInt; FlinkPtr: PPointer; begin FillChar(Result, SizeOf(Result), 0); Result.IsValid := False; if not Assigned(Address) or AddressViolateLowerRange(Address) or AddressViolateUpperRange(Address) then Exit; PEB := GetCurrentPEB; if not Assigned(PEB) or not Assigned(PEB^.Ldr) then Exit; LdrData := PEB^.Ldr; if not LdrData^.Initialized then Exit; {$IFDEF CPUX64} FlinkPtr := PPointer(NativeUInt(LdrData) + 24); // InLoadOrderModuleList.Flink at offset 24 on x64 {$ELSE} FlinkPtr := PPointer(NativeUInt(LdrData) + 12); // InLoadOrderModuleList.Flink at offset 12 on x86 {$ENDIF} CurrentEntry := FlinkPtr^; if AddressViolateLowerRange(CurrentEntry) or AddressViolateUpperRange(CurrentEntry) then Exit; FirstEntry := CurrentEntry; while Assigned(CurrentEntry) and (CurrentEntry <> PList_Entry(NativeUInt(LdrData) + {$IFDEF CPUX64} 24 {$ELSE} 12 {$ENDIF})) do begin if AddressViolateLowerRange(CurrentEntry) or AddressViolateUpperRange(CurrentEntry) then Break; ModuleEntry := PLDR_DATA_TABLE_ENTRY(CurrentEntry); try if Assigned(ModuleEntry^.DllBase) then begin ModuleBaseAddr := NativeUInt(ModuleEntry^.DllBase); ModuleEndAddr := ModuleBaseAddr + ModuleEntry^.SizeOfImage; if (NativeUInt(Address) >= ModuleBaseAddr) and (NativeUInt(Address) < ModuleEndAddr) then begin Result.ModuleBase := ModuleEntry^.DllBase; Result.ModuleSize := ModuleEntry^.SizeOfImage; Result.EntryPoint := ModuleEntry^.EntryPoint; if Assigned(ModuleEntry^.BaseDllName.Buffer) then Result.ModuleName := ModuleEntry^.BaseDllName.Buffer else Result.ModuleName := ''; if Assigned(ModuleEntry^.FullDllName.Buffer) then Result.FullPath := ModuleEntry^.FullDllName.Buffer else Result.FullPath := ''; Result.IsValid := True; Exit; end; end; except // Ignore access violations end; CurrentEntry := CurrentEntry^.Flink; if (not Assigned(CurrentEntry)) or (CurrentEntry = FirstEntry) then Break; end; end; end. Test project for it program AddressToModuleTest; {$APPTYPE CONSOLE} {$R *.res} uses System.SysUtils, Windows, lcAddressToModule in 'lcAddressToModule.pas'; function PointerToHex(P: Pointer): string; begin Result := IntToHex(NativeUInt(P), 8); end; procedure FindModuleFromAddress(Address: Pointer); var ModuleInfo: TModuleInfo; begin Writeln('Resolving address : ' + PointerToHex(Address)); ModuleInfo := FindModuleByAddress(Address); if ModuleInfo.IsValid then begin Writeln(#9'Found module : ' + ModuleInfo.ModuleName); Writeln(#9'Full Path : ', ModuleInfo.FullPath); Writeln(#9'Base Address : ', PointerToHex(ModuleInfo.ModuleBase)); Writeln(#9'Module Size : ', ModuleInfo.ModuleSize); Writeln(#9'Entry Point : ', PointerToHex(ModuleInfo.EntryPoint)); end else begin Writeln('Failed to get module....'); end; Writeln; end; procedure ResolveAddressesIntoModules; var DLLHandle: HMODULE; Address: Pointer; begin Address := nil; FindModuleFromAddress(nil); FindModuleFromAddress(Addr(PointerToHex)); // address to local function FindModuleFromAddress(Addr(ResolveAddressesIntoModules)); // address to local function // test PsApi.dll DLLHandle := LoadLibrary('PsApi.dll'); if DLLHandle <> INVALID_HANDLE_VALUE then begin Address := GetProcAddress(DLLHandle, 'EnumProcessModules'); if Assigned(Address) then FindModuleFromAddress(Address); FreeLibrary(DLLHandle); end; // test Ws2_32.dll DLLHandle := LoadLibrary('Ws2_32.dll'); if DLLHandle <> INVALID_HANDLE_VALUE then begin Address := GetProcAddress(DLLHandle, 'WSAStartup'); if Assigned(Address) then FindModuleFromAddress(Address); FreeLibrary(DLLHandle); end; if Assigned(Address) then FindModuleFromAddress(Address); // should be "not module found" // invalid address FindModuleFromAddress(Pointer($DEADBEEF)); end; begin try ResolveAddressesIntoModules; except on E: Exception do Writeln(E.ClassName, ': ', E.Message); end; Writeln('Done.'); Readln; end. the output for 64bit (also works for 32bit) Resolving address : 00000000 Failed to get module.... Resolving address : 00426640 Found module : AddressToModuleTest.exe Full Path : D:\Projects Delphi\PEBDemo\Win64\Debug\AddressToModuleTest.exe Base Address : 00400000 Module Size : 1531904 Entry Point : 00429FB0 Resolving address : 00426A30 Found module : AddressToModuleTest.exe Full Path : D:\Projects Delphi\PEBDemo\Win64\Debug\AddressToModuleTest.exe Base Address : 00400000 Module Size : 1531904 Entry Point : 00429FB0 Resolving address : 7FFFB7411010 Found module : PsApi.dll Full Path : C:\WINDOWS\System32\PsApi.dll Base Address : 7FFFB7410000 Module Size : 32768 Entry Point : 7FFFB7411110 Resolving address : 7FFFB762EB10 Found module : Ws2_32.dll Full Path : C:\WINDOWS\System32\Ws2_32.dll Base Address : 7FFFB7620000 Module Size : 438272 Entry Point : 7FFFB7634300 Resolving address : 7FFFB762EB10 Failed to get module.... Resolving address : DEADBEEF Failed to get module.... Done. Here i want to point that the above is tested on few OSs, i used and manipulated PEB so many times and i know for fact that Geoff Chappell documentation is very accurate have no errors and no mistake, and based on his documentation the above code should work from Windows 2000 till Windows 11. Hope you find that helpful or useful and thank you for your great contribution over so many years ! ps: the structures was big and it hit me for this specific use we don't the complexity of that structures so i cut them short, and i think they can be trimmed even more or removed in whole, leaving few offsets in their places.
×