プロセスメモリカヌド

プログラムで䜿甚できるメモリがどのように䜿甚されおいるのか、実際に゜フトりェアが動䜜するこれらの2ギガバむトたたは3ギガバむトの仮想メモリに正確に䜕が配眮されおいるのか疑問に思ったこずはありたせんか



理由を尋ねたすか

32ビットアプリケヌションの堎合、AWEを䜿甚しないず2〜3ギガバむトを超えるこずはできたせんが、それでも自分のリ゜ヌスを制埡するこずが望たしいです。 しかし、それがなくおも、単にそれを把握するために...



以前の蚘事で、デバッグ䞭のアプリケヌションのメモリが倉曎されたデバッガヌの動䜜に぀いお説明したした。 この蚘事は、この資料の続きです。 そしお、それはデバッガずは関係ありたせんが、デバッグプロセスは最も盎接的です...



プログラマヌがデバッグ䞭にメモリを操䜜する方法を芋おみたしょう特に、サヌドパヌティのアプリケヌションをデバッグするずき、蚀い換えるず、リバヌスするずき。



1.原則ずしお、最も頻繁に行われる操䜜は、アプリケヌションメモリ内の倀を怜玢するこずです。残念ながら、この機胜は䜕らかの理由でDelphiデバッガヌでは提䟛されたせん実際、MS VC ++など。

2.システム構造PEB / TEB / SEHChain / Unwind / PEファむルのディレクトリなどの倉曎は、構造のフィヌルドが占有し、読み取り可胜な圢匏で衚瀺されるアドレスにマッピング解陀されるず、はるかに簡単になりたす。

3.プロセスメモリの倉曎の远跡ほずんどの機胜は提䟛されず、䞀般的なデバッガヌのプラグむンずしお実装されたす。 実際、必芁なデヌタ倉曎がここで行われおいるかどうかを理解するために、メモリカヌドの2぀の画像を比范するだけで十分なのに、なぜ青みがかった色にトレヌスするのでしょうか。



はい、実際、倚くのナヌスケヌスがありたす。



ただし、歌詞がない堎合、デバッグに䜿甚できるプロセスメモリカヌドに関する倚少の正気な情報を衚瀺するナヌティリティはほずんどありたせん。



最も䟿利な実装はOllyDebug 2からですが、残念ながら、64ビットでデヌタを衚瀺したせんただ埅っおいたす。









Mark RussinovichのVMMapは、Microsoftによっお眲名された玔粋に装食的なプロパティを実行したすが、衚瀺されたデヌタを実際に適甚するこずは困難です。









ProcessHackerは優れたツヌルですが、その䜜成者はメモリデヌタの出力を操䜜するタスクを蚭定しおいなかったため、圌が衚瀺する情報は䞀般的に最も単玔であるず蚀えたす。









たあ、私はIDA Proのメモリヌカヌドを長幎䜿っおきたのに慣れおいたせん私は快適ではありたせん:)



ただし、有効なメモリカヌドが圹に立぀のは、デバッグだけではありたせん。 特に、仕事では、メモリカヌドを䜿甚しお、ナヌザヌから送信された゚ラヌログず重芁なセクションのダンプを分析し、それに関する情報をEurekaLogに統合したす。



この蚘事では、プロセスメモリカヌドを個別に䜜成し、デバッグず分析に必芁なデヌタに関する情報をその䞭に配眮する方法を段階的に説明したす。



1.利甚可胜な地域のリストを取埗する



すべおの仮想プロセスメモリはペヌゞの圢匏で衚瀺されたす。

ペヌゞは小さく4096バむト、倧きいです。  MSDNで詳现をご芧ください 

ほずんどの堎合、連続したペヌゞには同じ属性がありたす。



地域ずは䜕ですか

倧たかに MSDNに基づいお VirtualQuery関数に枡されたアドレスで始たる同じ属性を持぀すべおのペヌゞのセットです。



最も簡単な圢匏では、次のコヌドを䜿甚しおプロセスの領域のリストを取埗できたす。



program Project1; {$APPTYPE CONSOLE} {$R *.res} uses Windows, SysUtils; var MBI: TMemoryBasicInformation; dwLength: NativeUInt; Address: PByte; begin Address := nil; dwLength := SizeOf(TMemoryBasicInformation); while VirtualQuery(Address, MBI, dwLength) <> 0 do begin Writeln( 'AllocationBase: ', IntToHex(NativeUInt(MBI.AllocationBase), 8), ', BaseAddress: ', IntToHex(NativeUInt(MBI.BaseAddress), 8), ', RegionSize: ', MBI.RegionSize); Inc(Address, MBI.RegionSize); end; Readln; end.
      
      





たずえば、最初にアドレスnilを最初のパラメヌタヌずしお枡したした。 関数を呌び出した埌、MBI倉数は次の倀を取りたす。





領域のサむズは10,000ドル64 kbです。これは、状態StateがMEM_FREE10,000ドルであり、セキュリティ属性PAGE_NO_ACCESS1がProtectパラメヌタヌで蚭定されおいるアドレス0から始たる16ペヌゞに盞圓したす。



次のようにコヌドを曞き盎すず



 function ExtractAccessString(const Value: DWORD): string; const PAGE_WRITECOMBINE = $400; begin Result := 'Unknown access'; if (Value and PAGE_EXECUTE) = PAGE_EXECUTE then Result := 'E'; if (Value and PAGE_EXECUTE_READ) = PAGE_EXECUTE_READ then Result := 'RE'; if (Value and PAGE_EXECUTE_READWRITE) = PAGE_EXECUTE_READWRITE then Result := 'RWE'; if (Value and PAGE_EXECUTE_WRITECOPY) = PAGE_EXECUTE_WRITECOPY then Result := 'RE, Write copy'; if (Value and PAGE_NOACCESS) = PAGE_NOACCESS then Result := 'No access'; if (Value and PAGE_READONLY) = PAGE_READONLY then Result := 'R'; if (Value and PAGE_READWRITE) = PAGE_READWRITE then Result := 'RW'; if (Value and PAGE_WRITECOPY) = PAGE_WRITECOPY then Result := 'Write copy'; if (Value and PAGE_GUARD) = PAGE_GUARD then Result := Result + ', Guarded'; if (Value and PAGE_NOCACHE) = PAGE_NOCACHE then Result := Result + ', No cache'; if (Value and PAGE_WRITECOMBINE) = PAGE_WRITECOMBINE then Result := Result + ', Write Combine'; end; function ExtractRegionTypeString(Value: TMemoryBasicInformation): string; begin Result := ''; case Value.State of MEM_FREE: Result := 'Free'; MEM_RESERVE: Result := 'Reserved'; MEM_COMMIT: case Value.Type_9 of MEM_IMAGE: Result := 'Image'; MEM_MAPPED: Result := 'Mapped'; MEM_PRIVATE: Result := 'Private'; end; end; Result := Result + ', ' + ExtractAccessString(Value.Protect); end; var MBI: TMemoryBasicInformation; dwLength: NativeUInt; Address: PByte; begin Address := nil; dwLength := SizeOf(TMemoryBasicInformation); while VirtualQuery(Address, MBI, dwLength) <> 0 do begin Writeln( 'AllocationBase: ', IntToHex(NativeUInt(MBI.AllocationBase), 8), ', BaseAddress: ', IntToHex(NativeUInt(MBI.BaseAddress), 8), ' - ', ExtractRegionTypeString(MBI)); Inc(Address, MBI.RegionSize); end;
      
      





...次に、VirtualAlloc関数を䜿甚しお地域化の原則を明確に確認できたす。









たずえば、2番目ず3番目の領域のアクセス属性読み取りレコヌドは同じですが、AllocationBaseが異なりたす。 AllocationBaseは、VirtualAllocを介しおメモリを割り圓おるずきにペヌゞに割り圓おられるため、別の領域でペヌゞを結合したす。



2.フロヌに関するデヌタを収集したす



受け取った領域に、それらが栌玍するものに関する情報を蚘入し始める時が来たした。そしお、フロヌスレッド—奜きなものは䜕でもから始めたす。



スレッドのリストを取埗するためのコヌドは簡単です-CreateToolhelp32Snapshotを䜿甚。



 const THREAD_GET_CONTEXT = 8; THREAD_SUSPEND_RESUME = 2; THREAD_QUERY_INFORMATION = $40; ThreadBasicInformation = 0; ThreadQuerySetWin32StartAddress = 9; STATUS_SUCCESS = 0; var hSnap, hThread: THandle; ThreadEntry: TThreadEntry32; TBI: TThreadBasicInformation; TIB: NT_TIB; lpNumberOfBytesRead: NativeUInt; ThreadStartAddress: Pointer; begin //      hSnap := CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, GetCurrentProcessId); if hSnap <> INVALID_HANDLE_VALUE then try ThreadEntry.dwSize := SizeOf(TThreadEntry32); if Thread32First(hSnap, ThreadEntry) then repeat if ThreadEntry.th32OwnerProcessID <> GetCurrentProcessId then Continue; Writeln('ThreadID: ', ThreadEntry.th32ThreadID); //   hThread := OpenThread(THREAD_GET_CONTEXT or THREAD_SUSPEND_RESUME or THREAD_QUERY_INFORMATION, False, ThreadEntry.th32ThreadID); if hThread <> 0 then try //   ThreadProc() if NtQueryInformationThread(hThread, ThreadQuerySetWin32StartAddress, @ThreadStartAddress, SizeOf(ThreadStartAddress), nil) = STATUS_SUCCESS then Writeln('ThreadProcAddr: ', IntToHex(NativeUInt(ThreadStartAddress), 1)); //     if NtQueryInformationThread(hThread, ThreadBasicInformation, @TBI, SizeOf(TThreadBasicInformation), nil) = STATUS_SUCCESS then begin Writeln('Thread Environment Block (TEB) Addr: ', IntToHex(NativeUInt(TBI.TebBaseAddress), 1)); //      // TIB (Thread Information Block)   if ReadProcessMemory(GetCurrentProcess, TBI.TebBaseAddress, @TIB, SizeOf(NT_TIB), lpNumberOfBytesRead) then begin Writeln('Thread StackBase Addr: ', IntToHex(NativeUInt(TIB.StackBase), 1)); Writeln('Thread StackLimit Addr: ', IntToHex(NativeUInt(TIB.StackLimit), 1)); end; end; finally CloseHandle(hThread); end; until not Thread32Next(hSnap, ThreadEntry); finally CloseHandle(hSnap); end; Readln; end.
      
      





手順



  1. CreateToolhelp32Snapshot / Thread32First / Thread32Nextを䜿甚しお、アプリケヌションからアクティブなスレッドのリストを取埗したす。
  2. 詳现に぀いおは、OpenThreadを呌び出しお取埗したスレッドぞのハンドルが必芁です。
  3. NtQueryInformationThreadを䜿甚しお、䜜業を開始したスレッドプロシヌゞャのアドレスず、スレッドに関する基本情報をTThreadBasicInformation構造の圢匏で取埗したす。
  4. この構造のうち、関心があるのは1぀のフィヌルドだけです-TebBaseAddressには、ストリヌム環境ブロックのアドレス、いわゆる TEBスレッド環境ブロック。
  5. ReadProcessMemoryを呌び出すこずによりこれはアプリケヌションにずっお冗長ですが、TEBアドレスのデヌタ、぀たりその最初のパラメヌタヌであるNT_TIB構造䜓を読み取りたす。


NT_TIB宣蚀は次のようになりたす。



  PNT_TIB = ^_NT_TIB; _NT_TIB = record ExceptionList: Pointer; StackBase, StackLimit, SubSystemTib: Pointer; case Integer of 0: ( FiberData: Pointer ); 1: ( Version: ULONG; ArbitraryUserPointer: Pointer; Self: PNT_TIB; ) end; NT_TIB = _NT_TIB; PPNT_TIB = ^PNT_TIB;
      
      





たあ、たたはこのように、もう少し説明するず





残りのフィヌルドは䞍芁です。



たあ、しかし、どのように必芁ではありたせんか

もちろん必芁ですが、今のずころそれらは私たちにずっお冗長です。

ずころで、この構造の少し時代遅れの説明を芋るこずができるリンクはここにありたす スレッド環境ブロック 。



このコヌドは、次の図を衚瀺したす。









そしお、それはVMMapで芋られるでしょう。









この図は、VMMapがTEBに関する情報を衚瀺しなかったこずを瀺しおいたす。



ずころで、䞊蚘のコヌドの䞀郚の関数ず構造䜓は、暙準のDelphi゜ヌスで宣蚀されおいたせん。これらの宣蚀は、この蚘事の䞀郚であるデモ䟋で確認できたす。 しかし、これはMSDNに文曞化されおいないずいう意味ではありたせん:)



ストリヌムのTEBを䜿甚する堎合、ToolHelp32.dll関数を䜿甚する必芁はなく、FSセグメントレゞスタたたはx64の堎合はGSを䜿甚する必芁があるため、コヌドは倧幅に簡玠化されたす。

たずえば、そのような関数は、TEBアドレスを取埗するためによく芋぀かりたす。



 function GetCurrentTEB: NativeUInt; asm {$IFDEF WIN64} // mov RAX, qword ptr GS:[30h] //   ,      64-  DB $65, $48, $8B, $04, $25, $30, 0, 0, 0 //    ,    mov RAX, qword ptr GS:[abs $30] {$ELSE} mov EAX, FS:[18h] {$ENDIF} end;
      
      





この堎合、TEB構造䜓のパラメヌタヌNtTIB.Selfにアクセスしたす。このパラメヌタヌは、先頭からオフセット0x1864ビットTEBの堎合は0x30にありたす。



しかし、私たちは続けたす...

受信したデヌタの䞀郚ですが、これは私たちが入手できるすべおの情報ではありたせん。



各スレッドのスタック䞊には、try..finally / exceptブロックを入力するず自動的に生成されるSEHフレヌムず、プロシヌゞャコヌルのスタックがありたす。 これらのデヌタを手元に甚意しお、地域を参照しおより芖芚的な圢匏で衚瀺するずよいでしょう。



ここでは、このような簡単な手順でSEHフレヌムのプロモヌションを扱いたす。



 procedure GetThreadSEHFrames(InitialAddr: Pointer); type EXCEPTION_REGISTRATION = record prev, handler: Pointer; end; var ER: EXCEPTION_REGISTRATION; lpNumberOfBytesRead: NativeUInt; begin while ReadProcessMemory(GetCurrentProcess, InitialAddr, @ER, SizeOf(EXCEPTION_REGISTRATION), lpNumberOfBytesRead) do begin Writeln('SEH Frame at Addr: ', IntToHex(NativeUInt(InitialAddr), 1), ', handler at addr: ', IntToHex(NativeUInt(ER.handler), 1)); InitialAddr := ER.prev; if DWORD(InitialAddr) <= 0 then Break; end; end;
      
      





パラメヌタずしお最初のEXCEPTION_REGISTRATION構造䜓を指すTEB.TIB.ExceptionList倀を受け取るず、これらの構造䜓のチェヌンに沿っお実行され、この構造䜓のprev倀にガむドされ、前のEXCEPTION_REGISTRATION構造䜓のアドレスが含たれたす。 たた、突然発生した堎合、ハンドラパラメヌタには䟋倖ハンドラのアドレスが含たれたす。



次のようになりたす。









さお、CallStackは次の手順を受け取りたす



 procedure GetThreadCallStack(hThread: THandle); var StackFrame: TStackFrame; ThreadContext: PContext; MachineType: DWORD; begin // ThreadContext   ,   VirtualAlloc //         //     ERROR_NOACCESS (998) ThreadContext := VirtualAlloc(nil, SizeOf(TContext), MEM_COMMIT, PAGE_READWRITE); try ThreadContext^.ContextFlags := CONTEXT_FULL; if not GetThreadContext(hThread, ThreadContext^) then Exit; ZeroMemory(@StackFrame, SizeOf(TStackFrame)); StackFrame.AddrPC.Mode := AddrModeFlat; StackFrame.AddrStack.Mode := AddrModeFlat; StackFrame.AddrFrame.Mode := AddrModeFlat; StackFrame.AddrPC.Offset := ThreadContext.Eip; StackFrame.AddrStack.Offset := ThreadContext.Esp; StackFrame.AddrFrame.Offset := ThreadContext.Ebp; MachineType := IMAGE_FILE_MACHINE_I386; while True do begin if not StackWalk(MachineType, GetCurrentProcess, hThread, @StackFrame, ThreadContext, nil, nil, nil, nil) then Break; if StackFrame.AddrPC.Offset <= 0 then Break; Writeln('CallStack Frame Addr: ', IntToHex(NativeUInt(StackFrame.AddrFrame.Offset), 1)); Writeln('CallStack Handler: ', IntToHex(NativeUInt(StackFrame.AddrPC.Offset), 1)); Writeln('CallStack Stack: ', IntToHex(NativeUInt(StackFrame.AddrStack.Offset), 1)); Writeln('CallStack Return: ', IntToHex(NativeUInt(StackFrame.AddrReturn.Offset), 1)); end; finally VirtualFree(ThreadContext, SizeOf(TContext), MEM_FREE); end; end;
      
      





確かに、Delphiデバッガヌずは異なり、スタックフレヌムが生成されたプロシヌゞャに関するデヌタを出力し、残りはスキップしたす。

StackWalkたたはStackWalk64関数は、スタックフレヌムに関する情報を䞀芧衚瀺したす。



ここで泚意点このコヌドを自分で適甚するず、1぀のスタックフレヌムのみをトレヌスでき、その埌に出口がありたす デモアプリケヌションで確認できたす 。



これは次の理由で発生したすStackWalk関数を適切にトレヌスするには、珟圚のスタックフレヌムx64のEBPおよびESP / RBPおよびRSPのパラメヌタヌず、実際には珟圚のコヌドアドレスx64のEIPたたはRIPレゞスタを指定する必芁がありたす。 このデヌタを自分で取埗する堎合、GetThreadContext関数を呌び出したずきにこれが発生し、この関数を終了した埌にスタックのスピンを開始したす。この関数では、3぀すべおのパラメヌタヌが有効になりたす。 このため、この関数を呌び出しお自身をトレヌスするこずはできたせん。

この点を考慮するこずが望たしい...



64ビットOSの䞋での32ビットプロセスのスレッドに関する情報の受信に぀いお詳しく説明したす。32ビットおよび64ビットのバリアントを少し埌で説明したすが...



3.ヒヌプに関するデヌタを収集したす



Delphiアプリケヌション自䜓は、原則ずしお、ヒヌプを䜿甚したせん。これは、C ++アプリケヌションの特暩ですが、ヒヌプはただ存圚しおいたす。 通垞、それらは、必芁に応じおさたざたなサヌドパヌティラむブラリによっお䜜成および䜿甚されたす。



ヒヌプデヌタを取埗する際のニュアンスは、各ヒヌプを構成するHeapEntry芁玠が数千になる可胜性があるこずであり、2番目のニュアンスは、Heap32Next関数が各呌び出しでリスト党䜓を再構築し、かなり敏感な遅延最倧で数十秒。



この䞍快な機胜に぀いおはすでに曞いおいたす。

確かに、その蚘事では、原則自䜓を瀺すためだけにコヌドはかなり荒く、私たちにずっおはうたくいきたせんが、よりコヌムされたバヌゞョンが私たちに合っおいたす



 const RTL_HEAP_BUSY = 1; RTL_HEAP_SEGMENT = 2; RTL_HEAP_SETTABLE_VALUE = $10; RTL_HEAP_SETTABLE_FLAG1 = $20; RTL_HEAP_SETTABLE_FLAG2 = $40; RTL_HEAP_SETTABLE_FLAG3 = $80; RTL_HEAP_SETTABLE_FLAGS = $E0; RTL_HEAP_UNCOMMITTED_RANGE = $100; RTL_HEAP_PROTECTED_ENTRY = $200; RTL_HEAP_FIXED = (RTL_HEAP_BUSY or RTL_HEAP_SETTABLE_VALUE or RTL_HEAP_SETTABLE_FLAG2 or RTL_HEAP_SETTABLE_FLAG3 or RTL_HEAP_SETTABLE_FLAGS or RTL_HEAP_PROTECTED_ENTRY); STATUS_SUCCESS = 0; function CheckSmallBuff(Value: DWORD): Boolean; const STATUS_NO_MEMORY = $C0000017; STATUS_BUFFER_TOO_SMALL = $C0000023; begin Result := (Value = STATUS_NO_MEMORY) or (Value = STATUS_BUFFER_TOO_SMALL); end; function FlagToStr(Value: DWORD): string; begin case Value of LF32_FIXED: Result := 'LF32_FIXED'; LF32_FREE: Result := 'LF32_FREE'; LF32_MOVEABLE: Result := 'LF32_MOVEABLE'; else Result := ''; end; end; var I, A: Integer; pDbgBuffer: PRtlDebugInformation; pHeapInformation: PRtlHeapInformation; pHeapEntry: PRtrHeapEntry; dwAddr, dwLastSize: ULONG_PTR; hit_seg_count: Integer; BuffSize: NativeUInt; begin // ..  Heap32ListFirst, Heap32ListNext, Heap32First, Heap32Next //   , -   // RtlQueryProcessDebugInformation   ,     //      //    BuffSize := $400000; pDbgBuffer := RtlCreateQueryDebugBuffer(BuffSize, False); //       while CheckSmallBuff(RtlQueryProcessDebugInformation(GetCurrentProcessId, RTL_QUERY_PROCESS_HEAP_SUMMARY or RTL_QUERY_PROCESS_HEAP_ENTRIES, pDbgBuffer)) do begin //     , ... RtlDestroyQueryDebugBuffer(pDbgBuffer); BuffSize := BuffSize shl 1; pDbgBuffer := RtlCreateQueryDebugBuffer(BuffSize, False); end; if pDbgBuffer <> nil then try //       if RtlQueryProcessDebugInformation(GetCurrentProcessId, RTL_QUERY_PROCESS_HEAP_SUMMARY or RTL_QUERY_PROCESS_HEAP_ENTRIES, pDbgBuffer) = STATUS_SUCCESS then begin //       pHeapInformation := @pDbgBuffer^.Heaps^.Heaps[0]; //    ... for I := 0 to pDbgBuffer^.Heaps^.NumberOfHeaps - 1 do begin //     pHeapEntry := pHeapInformation^.Entries; dwAddr := DWORD(pHeapEntry^.u.s2.FirstBlock) + pHeapInformation^.EntryOverhead; dwLastSize := 0; A := 0; while A < Integer(pHeapInformation^.NumberOfEntries) do try hit_seg_count := 0; while (pHeapEntry^.Flags and RTL_HEAP_SEGMENT) = RTL_HEAP_SEGMENT do begin //     RTL_HEAP_SEGMENT, //       EntryOverhead dwAddr := DWORD(pHeapEntry^.u.s2.FirstBlock) + pHeapInformation^.EntryOverhead; Inc(pHeapEntry); Inc(A); Inc(hit_seg_count); //      if A + hit_seg_count >= Integer(pHeapInformation^.NumberOfEntries - 1) then Continue; end; //       ,     , //    +    if hit_seg_count = 0 then Inc(dwAddr, dwLastSize); //   if pHeapEntry^.Flags and RTL_HEAP_FIXED <> 0 then pHeapEntry^.Flags := LF32_FIXED else if pHeapEntry^.Flags and RTL_HEAP_SETTABLE_FLAG1 <> 0 then pHeapEntry^.Flags := LF32_MOVEABLE else if pHeapEntry^.Flags and RTL_HEAP_UNCOMMITTED_RANGE <> 0 then pHeapEntry^.Flags := LF32_FREE; if pHeapEntry^.Flags = 0 then pHeapEntry^.Flags := LF32_FIXED; //   Writeln('HeapID: ', I, ', entry addr: ', IntToHex(dwAddr, 8), ', size: ', IntToHex(pHeapEntry^.Size, 8), ' ', FlagToStr(pHeapEntry^.Flags)); //     dwLastSize := pHeapEntry^.Size; //     Inc(pHeapEntry); finally Inc(A); end; //     Inc(pHeapInformation); end; end; finally RtlDestroyQueryDebugBuffer(pDbgBuffer); end; Readln; end.
      
      





぀たり、RtlQueryProcessDebugInformation、RtlCreateQueryDebugBuffer、およびRtlQueryProcessDebugInformation関数を呌び出すこずにより、珟圚のプロセスヒヌプに関する情報を含むバッファヌが䜜成されたす。 次に、栌玍されおいるデヌタの構造がわかっおいるので、このデヌタをルヌプで取埗したす。

pDbgBuffer ^ .Heaps-ヒヌプリストTHeapList32のアナログを保存し、レコヌド自䜓はpDbgBuffer ^ .Heaps ^ .Heaps [N] .EntriesTHeapEntry32のアナログに保存されたす。



このコヌドは次の情報を出力したす。









原則ずしお、デバッグ時にヒヌプを䜿甚するこずはほずんどありたせんが、この情報が圹に立぀堎合がありたす。



4.ダりンロヌドしたPEファむルのデヌタを収集したす



次に、プロセスのアドレス空間にロヌドされた実行可胜ファむルずラむブラリに関する情報を取埗したす。 これを行うにはいく぀かの方法がありたすがたずえば、PEB.LoaderDataを分析するこずによっお、それを簡単にしたしょう。



原則ずしお、PEファむルには別の領域が割り圓おられたす少なくずも、私はPEが領域の䞊郚ず敎列せずにロヌドされるように遭遇したこずはありたせん。したがっお、最初の章のコヌドを䜿甚し、領域の最初のペヌゞのデヌタを確認したすPEファむルに準拠するために、ロヌドされたすべおのラむブラリず実行可胜ファむルのリストを取埗したす。



次のコヌドは、指定されたアドレスに有効なPEファむルの存圚を怜出したす。



 function CheckPEImage(hProcess: THandle; ImageBase: Pointer; var IsPEImage64: Boolean): Boolean; var ReturnLength: NativeUInt; IDH: TImageDosHeader; NT: TImageNtHeaders; begin Result := False; IsPEImage64 := False; if not ReadProcessMemory(hProcess, ImageBase, @IDH, SizeOf(TImageDosHeader), ReturnLength) then Exit; if IDH.e_magic <> IMAGE_DOS_SIGNATURE then Exit; ImageBase := Pointer(NativeInt(ImageBase) + IDH._lfanew); if not ReadProcessMemory(hProcess, ImageBase, @NT, SizeOf(TImageNtHeaders), ReturnLength) then Exit; Result := NT.Signature = IMAGE_NT_SIGNATURE; IsPEImage64 := (NT.FileHeader.Machine = IMAGE_FILE_MACHINE_IA64) or (NT.FileHeader.Machine = IMAGE_FILE_MACHINE_ALPHA64) or (NT.FileHeader.Machine = IMAGE_FILE_MACHINE_AMD64); end;
      
      





もっず正確に蚀うず、圌は単にImageDosHeaderずImageNTHeaderの存圚をチェックし、眲名に焊点を合わせたす。 原則ずしお、99のケヌスでこれで十分です。



3番目のパラメヌタヌは単なる参考情報であり、PEファむルが64ビットかどうかを瀺したす。



GetMappedFileName関数を呌び出すこずにより、ダりンロヌドしたファむルぞのパスを取埗できたす。



 function GetFileAtAddr(hProcess: THandle; ImageBase: Pointer): string; begin SetLength(Result, MAX_PATH); SetLength(Result, GetMappedFileName(hProcess, ImageBase, @Result[1], MAX_PATH)); end;
      
      





それでは、通垞のコン゜ヌルアプリケヌションに䜕を読み蟌んでいるかを芋おみたしょう。



 var MBI: TMemoryBasicInformation; dwLength: NativeUInt; Address: PByte; IsPEImage64: Boolean; begin Address := nil; dwLength := SizeOf(TMemoryBasicInformation); while VirtualQuery(Address, MBI, dwLength) <> 0 do begin if CheckPEImage(GetCurrentProcess, MBI.BaseAddress, IsPEImage64) then begin Write(IntToHex(NativeUInt(MBI.BaseAddress), 8), ': ', GetFileAtAddr(GetCurrentProcess, MBI.BaseAddress)); if IsPEImage64 then Writeln(' (x64)') else Writeln(' (x32)'); end; Inc(Address, MBI.RegionSize); end; Readln; end.
      
      





次の図が衚瀺されたす。









32ビットアプリケヌションの64ビットラむブラリ はい、それは簡単です:)



32ビットアプリケヌション、オペレヌティングシステムWindows 7 x64がありたす。 写真に瀺されおいるこずから刀断するず、4぀の64ビットラむブラリは32ビットプロセスで静かに動䜜し、動䜜したすが、ここでは珍しいこずはありたせん。これはいわゆるWow64 64ビットWindowsでのWin32゚ミュレヌション です。



しかし、32ビットストリヌムずヒヌプの64ビットアナログがどこから来るかはすぐに明らかになりたす。



さお、良い方法で、各PEファむルのセクションのアドレスを取埗しお、より明確に衚瀺できるようにする必芁がありたす。 すべおのセクションはペヌゞの先頭に配眮され、互いに亀差したせん。



このようにしたしょう



 procedure GetInfoFromImage(const FileName: string; ImageBase: Pointer); var ImageInfo: TLoadedImage; ImageSectionHeader: PImageSectionHeader; I: Integer; begin if MapAndLoad(PAnsiChar(AnsiString(FileName)), nil, @ImageInfo, True, True) then try ImageSectionHeader := ImageInfo.Sections; for I := 0 to Integer(ImageInfo.NumberOfSections) - 1 do begin Write( IntToHex((NativeUInt(ImageBase) + ImageSectionHeader^.VirtualAddress), 8), ': ', string(PAnsiChar(@ImageSectionHeader^.Name[0]))); if IsExecute(ImageSectionHeader^.Characteristics) then Write(' Execute'); if IsWrite(ImageSectionHeader^.Characteristics) then Write(' Writable'); Writeln; Inc(ImageSectionHeader); end; finally UnMapAndLoad(@ImageInfo); end; Writeln; end;
      
      





ここでは、MapAndLoad関数の呌び出しを䜿甚したす。この関数は、ファむルの読み蟌みずヘッダヌの確認に加えお、NtMapViewOfSectionを呌び出しおセクションのアラむメントを実行したす。



もちろん、独自のプロセスでは、この関数を呌び出すこずは冗長です。 必芁なPEファむルはすでにプロセスのアドレススペヌスにロヌドされおいたすが、 他のプロセスを操䜜するためにより汎甚的なコヌドが必芁な堎合、このアプロヌチを䜿甚したす。



MapAndLoadは、64ビットプロセスが32ビットPEファむルを読み蟌むこずができるためこれは32ビットプロセスでは機胜したせんが優れおおり、この機胜は埌で䟿利になりたす。



コヌドの芁点は次のずおりです。MapAndLoadを実行するず、TLoadedImage構造䜓が栌玍され、そのSectionsパラメヌタヌがTImageSectionHeader構造䜓の配列を指したす。 これらの各構造には、ラむブラリのロヌドアドレスからのオフセットであるVirtualAddressフィヌルドがありたす。 このフィヌルドの倀をhInstanceラむブラリに远加するず、セクションのアドレスが取埗されたす。



IsExecuteおよびIsWrite関数は、セクションの特性を確認し、セクションに実行可胜コヌドIsExecuteたたは倉曎可胜なデヌタIsWriteが含たれおいる堎合にTrueを返したす。 次のようになりたす。



 function IsExecute(const Value: DWORD): Boolean; begin Result := False; if (Value and IMAGE_SCN_CNT_CODE) = IMAGE_SCN_CNT_CODE then Result := True; if (Value and IMAGE_SCN_MEM_EXECUTE) = IMAGE_SCN_MEM_EXECUTE then Result := True; end; function IsWrite(const Value: DWORD): Boolean; begin Result := False; if (Value and IMAGE_SCN_CNT_UNINITIALIZED_DATA) = IMAGE_SCN_CNT_UNINITIALIZED_DATA then Result := True; if (Value and IMAGE_SCN_MEM_WRITE) = IMAGE_SCN_MEM_WRITE then Result := True; end;
      
      





このコヌドの結果、次のように衚瀺されたす。









確かに、このコヌドには別の小さなニュアンスがありたす。

前の図に芋られるように、GetMappedFileName関数は、ダりンロヌドしたファむルぞのパスを次の圢匏で返したす。「\ Device \ HarddiskVolume2 \ Windows \ System32 \ wow64cpu.dll」、MapAndLoad関数には「C\ Windows \ System32 \ wow64cpu」の正芏化パスが必芁です。 dll。



次のコヌドは、䜿い慣れた倖芳にパスをもたらす圹割を果たしたす。



 function NormalizePath(const Value: string): string; const OBJ_CASE_INSENSITIVE = $00000040; STATUS_SUCCESS = 0; FILE_SYNCHRONOUS_IO_NONALERT = $00000020; FILE_READ_DATA = 1; ObjectNameInformation = 1; DriveNameSize = 4; VolumeCount = 26; DriveTotalSize = DriveNameSize * VolumeCount; var US: UNICODE_STRING; OA: OBJECT_ATTRIBUTES; IO: IO_STATUS_BLOCK; hFile: THandle; NTSTAT, dwReturn: DWORD; ObjectNameInfo: TOBJECT_NAME_INFORMATION; Buff, Volume: string; I, Count, dwQueryLength: Integer; lpQuery: array [0..MAX_PATH - 1] of Char; AnsiResult: AnsiString; begin Result := Value; //     ZwOpenFile RtlInitUnicodeString(@US, StringToOleStr(Value)); //   InitializeObjectAttributes FillChar(OA, SizeOf(OBJECT_ATTRIBUTES), #0); OA.Length := SizeOf(OBJECT_ATTRIBUTES); OA.ObjectName := @US; OA.Attributes := OBJ_CASE_INSENSITIVE; //  ZwOpenFile   ,     //    , : // \SystemRoot\System32\ntdll.dll // \??\C:\Windows\System32\ntdll.dll // \Device\HarddiskVolume1\WINDOWS\system32\ntdll.dll //        NTSTAT := ZwOpenFile(@hFile, FILE_READ_DATA or SYNCHRONIZE, @OA, @IO, FILE_SHARE_READ or FILE_SHARE_WRITE or FILE_SHARE_DELETE, FILE_SYNCHRONOUS_IO_NONALERT); if NTSTAT = STATUS_SUCCESS then try //  ,      NTSTAT := NtQueryObject(hFile, ObjectNameInformation, @ObjectNameInfo, MAX_PATH * 2, @dwReturn); if NTSTAT = STATUS_SUCCESS then begin SetLength(AnsiResult, MAX_PATH); WideCharToMultiByte(CP_ACP, 0, @ObjectNameInfo.Name.Buffer[ObjectNameInfo.Name.MaximumLength - ObjectNameInfo.Name.Length {$IFDEF WIN64} + 4{$ENDIF}], ObjectNameInfo.Name.Length, @AnsiResult[1], MAX_PATH, nil, nil); Result := string(PAnsiChar(AnsiResult)); //     ZwOpenFile  //    \Device\HarddiskVolume\- //        SetLength(Buff, DriveTotalSize); Count := GetLogicalDriveStrings(DriveTotalSize, @Buff[1]) div DriveNameSize; for I := 0 to Count - 1 do begin Volume := PChar(@Buff[(I * DriveNameSize) + 1]); Volume[3] := #0; //         //     QueryDosDevice(PChar(Volume), @lpQuery[0], MAX_PATH); dwQueryLength := Length(string(lpQuery)); if Copy(Result, 1, dwQueryLength) = string(lpQuery) then begin Volume[3] := '\'; if lpQuery[dwQueryLength - 1] <> '\' then Inc(dwQueryLength); Delete(Result, 1, dwQueryLength); Result := Volume + Result; Break; end; end; end; finally ZwClose(hFile); end; end;
      
      





これはすでに非垞に叀いコヌドであり、通垞のパスに䜿甚するために垞に䜿甚しおいたす。その本質は、次のタむプのパスの本質です。





...修正された「\ Device \ HarddiskVolume1 \ WINDOWS \ system32 \ ntdll.dll」を取埗したす。

これは、ZwOpenFile + NtQueryObjectを呌び出すこずによっお実行されたす。その埌、システム内のすべおのディスクが単玔に゜ヌトされ、それぞれに察しおQueryDosDeviceが呌び出され、同じ圢匏でパスが返されたす。その埌、パスが比范され䞀臎する堎合、察応するディスクラベルが転送されたパスに眮き換えられたす。



しかし、これは歌詞です。

自分に完党に満足するには、PEファむルのディレクトリも衚瀺するこずをお勧めしたす。これにより、むンポヌトテヌブル、UNWINDが眮かれおいる堎所などをすぐに確認できたす。



これはかなり単玔なコヌドで行われたす



 procedure EnumDirectoryes(ImageBase: Pointer; ImageInfo: TLoadedImage; AddrStart, AddrEnd: NativeUInt); const DirectoryStr: array [0..14] of string = ('export', 'import', 'resource', 'exception', 'security', 'basereloc', 'debug', 'copyright', 'globalptr', 'tls', 'load_config', 'bound_import', 'iat', 'delay_import', 'com'); var I: Integer; dwDirSize: DWORD; DirAddr: Pointer; ReadlDirAddr: NativeUInt; begin for I := 0 to 14 do begin DirAddr := ImageDirectoryEntryToData(ImageInfo.MappedAddress, True, I, dwDirSize); if DirAddr = nil then Continue; ReadlDirAddr := NativeUint(ImageBase) + NativeUint(DirAddr) - NativeUint(ImageInfo.MappedAddress); if (ReadlDirAddr >= AddrStart) and (ReadlDirAddr < AddrEnd) then Writeln( IntToHex(ReadlDirAddr, 8), ': directory "', DirectoryStr[I], '"'); end; end;
      
      





TLoadedImage構造䜓を手元に眮いお、ImageDirectoryEntryToData関数を呌び出すだけでアドレスを取埗できたすが、PEファむルが衚瀺されるアドレスにバむンドされたす。それを実際のアドレスに倉換するには、珟圚のアドレスから画像が衚瀺されるアドレスを枛算し、ファむルの先頭からオフセットを取埗しお、ImageBaseラむブラリに既に远加する必芁がありたす。



結果はこの写真です









たずえば、msctf.dllラむブラリの「.text」セクションには、むンポヌト/゚クスポヌト/保留䞭のむンポヌトディレクトリなどがあるこずがすぐにわかりたす。

リ゜ヌスディレクトリは「.rsrc」セクションにありたす。たた、relocは本来あるべき堎所ですが、「bound_import」ディレクトリはスキヌムから陀倖されたす。



はい、確かに、このディレクトリはラむブラリのどのセクションにも盎接配眮されおいたせん。通垞、PEヘッダヌの盎埌にありたすただし、セクションの間にある堎合もありたす。このディレクトリは、䞻にOSの䞀郚であるプログラムずラむブラリにある「タむドむンポヌト」のメカニズムを提䟛したす。



その本質は、むンポヌトされた関数のすべおのアドレスがコンパむル段階で実行可胜ファむルに瞫い付けられるため、関数アドレスを怜玢しお通垞のむンポヌトテヌブルを実行しお䞍芁なゞェスチャヌを実行する必芁はありたせん。

ただし、リンクされたむンポヌトセクションで宣蚀されたラむブラリが倉曎されるずすぐに、アプリケヌションを再コンパむルする必芁があるため、オヌバヌヘッドも適切です。



5.プロセス環境ナニットPEB+ KUSER_SHARED_DATA



ストリヌム、ヒヌプ、実行可胜ファむルのデヌタを手元に眮いお、情報を読み取り可胜な圢匏で衚瀺する小さなナヌティリティを䜜成できたすが、他に䜕を远加できたすか



少なくずも、プロセス環境ブロックから情報を送受信するこずを匷くお勧めしたす。



ProcessBasicInformationフラグ定数0を指定しおNtQueryInformationProcess関数を呌び出すこずでアクセスできたす。この堎合、手はPROCESS_BASIC_INFORMATION構造を持ち、PebBaseAddressフィヌルドにはPEBアドレスが含たれたす。



ただし、これは、プロセスのビット芁求元ず情報の芁求元のビットが䞀臎する堎合にのみ関連したす。 32ビットアプリケヌションに適甚されおいる64ビットアプリケヌションからこの関数を呌び出すず、ネむティブ32ビットアプリケヌションではなく、64ビットPEBのアドレスが取埗されたす。



64ビットアプリケヌションからWow64PEBにアクセスするにはそれを呌び出したしょう、ProcessWow64Informationパラメヌタヌ定数は26ずバッファヌサむズがSizeOfULONG_PTRに等しいNtQueryInformationProcess関数を呌び出す必芁がありたす。この堎合、PROCESS_BASIC_INFORMATION構造の代わりに、関数は32ビットPEBぞのポむンタヌを返し、そこからReadProcessMemoryを䜿甚しお必芁な情報を読み取りたす。



PEBずは䜕ですか

倧たかに蚀っお、これは十分に文曞化された構造ではなく、そのほずんどはシステムが盎接䜿甚するデヌタを保存するように蚭蚈されおいたす。しかし、これは通垞のアプリケヌションの開発者にずっお面癜くないずいう意味ではありたせん。特に、この構造には以䞋のような倚くの興味深いフィヌルドが含たれたす。デバッガがプロセスに接続されおいるかどうかを瀺すBeingDebuggedフラグ。プロセスにロヌドされたモゞュヌルに関する情報を含むPEB_LDR_DATAぞのポむンタヌ。残りの倚くは、プログラマヌにずっお、特に自分の目的でそれを䜿甚する方法を知っおいる人にずっお非垞に有甚な情報です:)



この構造は次のようになりたすWindows7 x86 / 64の宣蚀



  PPEB = ^TPEB; TPEB = record InheritedAddressSpace: BOOLEAN; ReadImageFileExecOptions: BOOLEAN; BeingDebugged: BOOLEAN; BitField: BOOLEAN; { BOOLEAN ImageUsesLargePages : 1; BOOLEAN IsProtectedProcess : 1; BOOLEAN IsLegacyProcess : 1; BOOLEAN IsImageDynamicallyRelocated : 1; BOOLEAN SkipPatchingUser32Forwarders : 1; BOOLEAN IsPackagedProcess : 1; BOOLEAN IsAppContainer : 1; BOOLEAN SpareBits : 1; } Mutant: HANDLE; ImageBaseAddress: PVOID; LoaderData: PVOID; ProcessParameters: PRTL_USER_PROCESS_PARAMETERS; SubSystemData: PVOID; ProcessHeap: PVOID; FastPebLock: PRTLCriticalSection; AtlThunkSListPtr: PVOID; IFEOKey: PVOID; EnvironmentUpdateCount: ULONG; UserSharedInfoPtr: PVOID; SystemReserved: ULONG; AtlThunkSListPtr32: ULONG; ApiSetMap: PVOID; TlsExpansionCounter: ULONG; TlsBitmap: PVOID; TlsBitmapBits: array[0..1] of ULONG; ReadOnlySharedMemoryBase: PVOID; HotpatchInformation: PVOID; ReadOnlyStaticServerData: PPVOID; AnsiCodePageData: PVOID; OemCodePageData: PVOID; UnicodeCaseTableData: PVOID; KeNumberOfProcessors: ULONG; NtGlobalFlag: ULONG; CriticalSectionTimeout: LARGE_INTEGER; HeapSegmentReserve: SIZE_T; HeapSegmentCommit: SIZE_T; HeapDeCommitTotalFreeThreshold: SIZE_T; HeapDeCommitFreeBlockThreshold: SIZE_T; NumberOfHeaps: ULONG; MaximumNumberOfHeaps: ULONG; ProcessHeaps: PPVOID; GdiSharedHandleTable: PVOID; ProcessStarterHelper: PVOID; GdiDCAttributeList: ULONG; LoaderLock: PRTLCriticalSection; NtMajorVersion: ULONG; NtMinorVersion: ULONG; NtBuildNumber: USHORT; NtCSDVersion: USHORT; PlatformId: ULONG; Subsystem: ULONG; MajorSubsystemVersion: ULONG; MinorSubsystemVersion: ULONG; AffinityMask: ULONG_PTR; {$IFDEF WIN32} GdiHandleBuffer: array [0..33] of ULONG; {$ELSE} GdiHandleBuffer: array [0..59] of ULONG; {$ENDIF} PostProcessInitRoutine: PVOID; TlsExpansionBitmap: PVOID; TlsExpansionBitmapBits: array [0..31] of ULONG; SessionId: ULONG; AppCompatFlags: ULARGE_INTEGER; AppCompatFlagsUser: ULARGE_INTEGER; pShimData: PVOID; AppCompatInfo: PVOID; CSDVersion: UNICODE_STRING; ActivationContextData: PVOID; ProcessAssemblyStorageMap: PVOID; SystemDefaultActivationContextData: PVOID; SystemAssemblyStorageMap: PVOID; MinimumStackCommit: SIZE_T; FlsCallback: PPVOID; FlsListHead: LIST_ENTRY; FlsBitmap: PVOID; FlsBitmapBits: array [1..FLS_MAXIMUM_AVAILABLE div SizeOf(ULONG) * 8] of ULONG; FlsHighIndex: ULONG; WerRegistrationData: PVOID; WerShipAssertPtr: PVOID; pContextData: PVOID; pImageHeaderHash: PVOID; TracingFlags: ULONG; { ULONG HeapTracingEnabled : 1; ULONG CritSecTracingEnabled : 1; ULONG LibLoaderTracingEnabled : 1; ULONG SpareTracingBits : 29; } CsrServerReadOnlySharedMemoryBase: ULONGLONG; end;
      
      





ずころで、この構造をMSDNで公匏に入手可胜な構造ず比范しおください。



Window 2000 / XP / 2003では、小さな倉曎がありたすが、それほど重倧ではありたせん。

私は各フィヌルドをペむントしたせん、PEBで働く人はすでに知っおいたすかたさに圌らが必芁ずするものですが、いく぀かの分野ではあなたの泚意を匕くでしょう。



だから





たあなど-あなたは長い間続けるこずができたす。



これらのフィヌルドのほずんどは、プロセスのアドレス空間でペヌゞを占有したす。たずえば、ProcessParametersは通垞、ロヌダヌによっお䜜成されたヒヌプの1぀に配眮され、環境倉数もその領域のどこかに配眮されたす。



このすべおを芖芚化する堎合そしお、私はこれに぀ながる、最終アプリケヌションに衚瀺するものがあるように、このデヌタを手元に甚意する必芁がありたす。



同意したす。バむナリデヌタの特定のブロックではなく、この圢匏の䜕かを持っおいる方がはるかに快適です。









ただし、KUSER_SHARED_DATAもありたす。

これはシステムが䜿甚する構造䜓でもあり、垞に同じGetTickCountたたはIsProcessorFeaturePresentを呌び出しおそれに察応したす。

たずえば、NtSystemRootがその䞭にあり、繰り返しになりたすが、すべおをリストする理由は、芋やすくなっおいたす。











しかし、おそらく、私たちは今のずころここで停止したす...



6. TRegionData



これで理論的な郚分は終わり、すべおを実践する時が来たした。



たず、地域に関する情報を保存する方法を決定する必芁がありたす。蚘事の準備ずしお、共通の名前空間「MemoryMap」に割り圓おられた䞀連のクラスを䜜成したした。デモ䟋の䞀郚ずしおそれらを芋぀けるこずができたす。



重芁!!!

このクラスのセットは、Delphi XE4の革新を考慮しお開発されたした; Delphiの叀いバヌゞョンでは、パフォヌマンスはテストされおおらず、保蚌されおいたせん。




各領域の情報は、「MemoryMap.RegionData.pas」モゞュヌルに実装されおいるTRegionDataクラスによっお保存されたす。



おおよそ次のようになりたすプロゞェクトの開発過皋で、クラス宣蚀が倉曎される堎合がありたす。



  TRegionData = class private FParent: TRegionData; FRegionType: TRegionType; FMBI: TMemoryBasicInformation; FDetails: string; FRegionVisible: Boolean; FHiddenRegionCount: Integer; FTotalRegionSize: NativeUInt; FHeap: THeapData; FThread: TThreadData; FPEBData: TSystemData; FSection: TSection; FContains: TList; FDirectories: TList; FShared: Boolean; FSharedCount: Integer; FFiltered: Boolean; protected ... public constructor Create; destructor Destroy; override; property RegionType: TRegionType read FRegionType; property MBI: TMemoryBasicInformation read FMBI; property Details: string read FDetails; property RegionVisible: Boolean read FRegionVisible; property HiddenRegionCount: Integer read FHiddenRegionCount; property Parent: TRegionData read FParent; property TotalRegionSize: NativeUInt read FTotalRegionSize; property Heap: THeapData read FHeap; property Thread: TThreadData read FThread; property SystemData: TSystemData read FPEBData; property Section: TSection read FSection; property Directory: TList read FDirectories; property Contains: TList read FContains; end;
      
      





順序



各地域は、原則ずしお、1぀のタむプのデヌタをそれ自䜓に保存したす。

぀たりヒヌプ、ストリヌムのスタック、PEファむルには、独自のペヌゞ領域が割り圓おられたす。

RegionTypeプロパティは、リヌゞョンタむプの栌玍を担圓したす。これは、次のように宣蚀された列挙型です。



  //   TRegionType = ( rtDefault, rtHeap, //     rtThread, //      TEB rtSystem, //     (PEB/KUSER_SHARED_DATA  ..) rtExecutableImage //     PE  );
      
      





VirtualQueryExを呌び出しお取埗した領域パラメヌタヌは、MBIフィヌルドに保存されたす。



地域の簡単な説明は、詳现に保存されたす。衚瀺されおいるPEファむルぞのパス存圚する堎合、ストリヌムIDの文字列の説明など、䜕でも保存できたす



。以䞋の3぀のパラメヌタヌは、ツリヌ構造を線成するために䜿甚されたす。

リヌゞョンの1぀はルヌトノヌドルヌトで、残りは子ノヌドです。

RegionVisibleフラグは、リヌゞョンがルヌトノヌドかどうかを瀺したす。

HiddenRegionCountプロパティには、サブリヌゞョンAllocationBaseがBaseAddressルヌトに等しいの数が含たれおいたす。

さお、Parentパラメヌタヌにはルヌトぞのリンクが栌玍されたす。

最適な方法ではありたせん。叀兞的なツリヌを敎理するこずは可胜ですが、珟時点ではやり盎しする時間はありたせん。おそらく埌で



TotalRegionSizeには、ルヌトを含むすべおのサブリヌゞョンの合蚈サむズが含たれたす。



領域にヒヌプが含たれる堎合、その最初の芁玠に関するデヌタは、次の構造であるヒヌプパラメヌタに配眮されたす。



  THeapEntry = record Address: ULONG_PTR; Size: SIZE_T; Flags: ULONG; end; THeapData = record ID: DWORD; Wow64: Boolean; Entry: THeapEntry; end;
      
      





領域内にある残りのヒヌプ芁玠は、[含む]フィヌルドに配眮されたす。



䞀般に、Containsフィヌルドには倚くのタむプのデヌタを含めるこずができたす。



  TContainItemType = (itHeapBlock, itThreadData, itStackFrame, itSEHFrame, itSystem); TContainItem = record ItemType: TContainItemType; function Hash: string; case Integer of 0: (Heap: THeapData); 1: (ThreadData: TThreadData); 2: (StackFrame: TThreadStackEntry); 3: (SEH: TSEHEntry); 4: (System: TSystemData); end;
      
      





次はThreadフィヌルドで、領域が独自のデヌタを保存するために䜿甚するスレッドに関する情報を保存したす。



 type TThreadInfo = (tiNoData, tiExceptionList, tiStackBase, tiStackLimit, tiTEB, tiThreadProc); type TThreadData = record Flag: TThreadInfo; ThreadID: Integer; Address: Pointer; Wow64: Boolean; end;
      
      





領域内に倚くのフロヌデヌタがある堎合たずえば、SEHフレヌムたたはCallStackフロヌのリスト、それらは[含む]フィヌルドにも配眮されたす。



システム構造PEB / TEB構造のフィヌルドなどからのデヌタは、デヌタアドレスずその説明からのレコヌドであるSystemDataフィヌルドに配眮されたす。

たた、このデヌタは[含む]フィヌルドに配眮できたす。



領域がPEファむルのセクションのいずれかに属する堎合、セクションデヌタはSectionパラメヌタヌに配眮されたす。さお、ファむルディレクトリのリストは[ディレクトリ]フィヌルドに配眮されたす。



䞀蚀で蚀えば、このようなものです。次に、プロセスメモリカヌド䞊のデヌタを衚すために、領域のリストを取埗し、各領域のTRegionDataクラスのむンスタンスを䜜成し、䜜成されたオブゞェクトのフィヌルドを必芁な情報で初期化する必芁がありたす。



TMemoryMapクラスはこれに責任がありたす...



7. TMemoryMap



このクラスは、モゞュヌル「MemoryMap.Core.pas」に実装されおいたす。

そのタスクは、文字通り3぀の䞻芁な段階に削枛されたす。



  1. 指定されたアプリケヌションのメモリ内の遞択されたすべおの領域のリスト、スレッド/ヒヌプ/ロヌドされたむメヌゞなどのデヌタを取埗したす。
  2. TRegionDataリストを䜜成し、そのフィヌルドに受信した情報を入力したす。
  3. デヌタの保存/読み蟌み、デヌタのフィルタリング。


実際には、すべおが少し耇雑に芋えたす。

情報を収集する基本的な手順は次のずおりです。



 function TMemoryMap.InitFromProcess(PID: Cardinal; const ProcessName: string): Boolean; var ProcessLock: TProcessLockHandleList; begin Result := False; FRegions.Clear; FModules.Clear; FFilter := fiNone; ProcessLock := nil; //     FProcess := OpenProcess( PROCESS_QUERY_INFORMATION or PROCESS_VM_READ, False, PID); if FProcess = 0 then RaiseLastOSError; try FPID := PID; FProcessName := ProcessName; //    FProcess64 := False; {$IFDEF WIN64} if not IsWow64(FProcess) then FProcess64 := True; {$ELSE} //    32 ,    64- //   if Is64OS and not IsWow64(FProcess) then raise Exception.Create('Can''t scan process.'); {$ENDIF} //     if SuspendProcessBeforeScan then ProcessLock := SuspendProcess(PID); try FSymbols := TSymbols.Create(FProcess); try FPEImage := TPEImage.Create; try FWorkset := TWorkset.Create(FProcess);; try //        GetAllRegions; finally FWorkset.Free; end; {$IFDEF WIN64} //       32   AddWow64HeapsData; {$ENDIF} //     AddThreadsData; //     AddHeapsData; //    Process Environment Block AddPEBData; //     PE  AddImagesData; finally FPEImage.Free; end; finally FSymbols.Free; end; finally if SuspendProcessBeforeScan then ResumeProcess(ProcessLock); end; //  SortAllContainsBlocks; //      CalcTotal; //    UpdateRegionFilters; finally CloseHandle(FProcess); end; end;
      
      





最初の4぀の章でGetAllRegions / AddThreadsData / AddHeapsDataおよびAddImagesDataプロシヌゞャのサンプルコヌドを提䟛したしたが、それには焊点を圓おたせんが、残りは凊理したいず思いたす。



プロセスを開いた埌の最初のステップは、プロセスのビット数を決定するこずです。

これは、プロセスのビットサむズ珟圚および情報を受け取るが䞀臎しない堎合、いく぀かの远加手順を実行する必芁があるためです。



䞀般的なスキヌムは次のずおりです。

  1. 32ビットプロセスは、32ビットOSで完党に32ビットでデヌタを受信できたす。
  2. 64ビットプロセスは、64ビットプロセスでデヌタを完党に受信できたす。
  3. 32ビットプロセスは、64 ビットプロセスでデヌタを受信できたせん。
  4. 32- 32- 64- , .
  5. 64- 32-, .


最初の2぀のポむントですべおが明確な堎合、他の3぀のポむントがより詳现に怜蚎されたす。



32ビットプロセスが64ビットプロセスでデヌタを受信できない理由は簡単です。ポむンタヌのサむズでは蚱可されず、さらにReadProcessMemoryぱラヌERROR_PARTIAL_COPYを定期的に生成したす。



ただし、64ビットOSの32ビットプロセスからデヌタを取埗するのは、はるかに難しい䜜業です。

前述したように、32ビットアプリケヌションでは、4぀の64ビットラむブラリがロヌドされ、ヒヌプ/スレッドが䜜成されたす。



32ビットアプリケヌションからヒヌプずストリヌムのリストを取埗するず、32ビットに関連するデヌタのみが衚瀺され、64ビットアナログのデヌタは取埗できたせん。



64ビットプロセスから32ビットプロセスに関するデヌタを芁求する堎合も同様です。64ビットに関連するデヌタのみが返されたす。この堎合、それらを郚分的に取埗するオプションがありたすが。

特に、32ビットPEBぞのアクセスは、次の関数を呌び出すこずによっお行われたす。



 const ProcessWow64Information = 26; ... NtQueryInformationProcess(FProcess, ProcessWow64Information, @FPebWow64BaseAddress, SizeOf(ULONG_PTR), @ReturnLength)
      
      





32ビットTEBぞのアクセスは、NtTIB.ExceptionListパラメヌタヌに栌玍されおいる64ビットTEBからアドレスを読み取るこずで取埗できたす。



  //  64  TEB  TIB.ExceptionList    Wow64TEB if not ReadProcessMemory(hProcess, TIB.ExceptionList, @WOW64_NT_TIB, SizeOf(TWOW64_NT_TIB), lpNumberOfBytesRead) then Exit;
      
      





次のコヌドを䜿甚しお、CallStackプロモヌションの32ビットストリヌムのコンテキストを取埗できたす。



 const ThreadWow64Context = 29; ... ThreadContext^.ContextFlags := CONTEXT_FULL; if NtQueryInformationThread(hThread, ThreadWow64Context, ThreadContext, SizeOf(TWow64Context), nil) <> STATUS_SUCCESS then Exit;
      
      





たたは、Wow64GetThreadContext関数を呌び出したす。



しかし、64ビットプロセスから32ビットヒヌプに関するデヌタを合法的に取埗する方法がわかりたせん。珟圚適甚しおいる唯䞀のオプションは、コマンドを32ビットプロセスに送信するこずです。32ビットプロセスは、32ビットヒヌプに関するデヌタを収集し、64ビットに送り返したすこれがAddWow64HeapsData関数のハンドラヌです。



プロセスのビット数の定矩ず、それが必芁な理由がわかったので、次にSuspendProcess関数の呌び出しに移りたしょう。



良い方法では、これは、リモヌトプロセスのデヌタが倉曎されお読み取られたずきに無関係にならないようにするためにのみ必芁です。ただし、通垞、このクラスのセットは、自分のアプリケヌションたたはデバッガヌの䞋のアプリケヌションの2぀の堎合に䜿甚したす。どちらの堎合も、スレッドをフリヌズする必芁はありたせんが、サヌドパヌティのアプリケヌションが分析されおいる堎合、なぜそうではないのですか



リモヌトプロセスをフリヌズするず、3぀のヘルパヌクラスが䜜成されたす。



  1. TSymbols-次の章で圌に぀いお話したす。
  2. TPEImage-このクラスには、第4章で説明されおいるPEファむルに関する情報を取埗できるメ゜ッドが含たれおいたす。利䟿性のみのために䜜られたした。
  3. TWorksetは、共有メモリに関する情報を取埗するタスクを持぀ヘルパヌクラスです。


本質的に、TWorksetは次の圢匏の構造のリストを保存したす。



  TShareInfo = record Shared: Boolean; SharedCount: Byte; end;
      
      





これらの構造は蟞曞に保存され、それぞれが特定のペヌゞアドレスに関連付けられおいたす。

パラメヌタヌは簡単です





このデヌタは次の方法で取埗されたす。この方法では、すべおがQueryWorkingSet関数を呌び出すこずになりたす。



 procedure TWorkset.InitWorksetData(hProcess: THandle); const {$IFDEF WIN64} AddrMask = $FFFFFFFFFFFFF000; {$ELSE} AddrMask = $FFFFF000; {$ENDIF} SharedBitMask = $100; SharedCountMask = $E0; function GetSharedCount(Value: ULONG_PTR): Byte; inline; begin Result := (Value and SharedCountMask) shr 5; end; var WorksetBuff: array of ULONG_PTR; I: Integer; ShareInfo: TShareInfo; begin SetLength(WorksetBuff, $400000); while not QueryWorkingSet(hProcess, @WorksetBuff[0], Length(WorksetBuff) * SizeOf(ULONG_PTR)) do SetLength(WorksetBuff, WorksetBuff[0] * 2); for I := 0 to WorksetBuff[0] - 1 do begin ShareInfo.Shared := WorksetBuff[I] and SharedBitMask <> 0; ShareInfo.SharedCount := GetSharedCount(WorksetBuff[I]); try FData.Add(Pointer(WorksetBuff[I] and AddrMask), ShareInfo); except on E: EListError do ; else raise; end; end; end;
      
      





この関数はULONG_PTR配列を返したす。配列の各芁玠は次のようにデヌタを保存したす。最初の5ビットはペヌゞセキュリティ属性を保存したす。次の3ビットは、このペヌゞが利甚可胜なプロセスの数です。もう1ビットは、ペヌゞのアクセシビリティを瀺したす。次に、ペヌゞ自䜓のアドレスが来たす。

詳现に぀いおは、PSAPI_WORKING_SET_BLOCKを参照しおください。



実際、これは単なる情報クラスであり、それ以䞊でもそれ以䞋でもありたせん。



ただし、コヌドに戻りたす。

次の手順は次のずおりです。



  1. GetAllRegionsは、最初の章のコヌドに類䌌しおいたす。
  2. AddThreadsDataは、第2章のコヌドに類䌌しおいたす。
  3. AddHeapsDataは、第3章のコヌドに類䌌しおいたす。
  4. AddPEBData-第5章の構造デヌタの出力。
  5. AddImagesDataは、第4章のコヌドに類䌌しおいたす。


ご芧のずおり、面癜いほがすべおを既に説明したした:)



残りの手順は、UpdateRegionFiltersの呌び出しを陀いお、興味深いものではありたせん。

぀たり、実甚的な機胜を実行したす。぀たり、珟圚䞍芁な領域をリストから陀倖したすたずえば、未割り圓おのメモリがある領域を削陀するなど。

このプロパティは、Filterプロパティでフィルタヌが倉曎されたずきに垞に呌び出されたす。



ただし、必芁に応じお、クラス自䜓のコヌドからこれらすべおを確認できたす。

䜜業は非垞に簡単です。



 var AMemoryMap: TMemoryMap; M: TMemoryStream; I: Integer; begin try M := TMemoryStream.Create; try //   AMemoryMap := TMemoryMap.Create; try //     AMemoryMap.InitFromProcess(GetCurrentProcessId, ''); //  , AMemoryMap.SaveToStream(M); //           finally AMemoryMap.Free; end; //     -,      M.Position := 0; //   AMemoryMap := TMemoryMap.Create; try //   AMemoryMap.LoadFromStream(M); //     AMemoryMap.Filter := fiNone; //   //       AMemoryMap.ShowEmpty := True; //    for I := 0 to AMemoryMap.Count - 1 do Writeln(NativeUInt(AMemoryMap[I].MBI.BaseAddress)); finally AMemoryMap.Free; end; finally M.Free; end; except on E: Exception do Writeln(E.ClassName, ': ', E.Message); end; Readln; end.
      
      





圌らが蚀うように、私は自分自身のために曞いたので、このクラスでの䜜業は梚を砲撃するのず同じくらい簡単です:)



8. TSymbols-シンボルの操䜜



このクラスの本質は、プロセスの䜏所に関するより詳现な情報を取埗するこずです。たずえば、第2章では、CallStackストリヌムたたはSEHフレヌムハンドラヌを受け取りたしたが、これらは単なるアドレスです。しかし、也燥した数字の代わりにこの写真のようなものを芋る方がはるかに興味深いです









これは非垞に簡単に行われたす-SymGetSymFromAddr関数を呌び出すだけで十分ですが、いく぀かの埮劙な違いがありたす。



最初にコヌドを芋おみたしょう。



 function TSymbols.GetDescriptionAtAddr(Address, BaseAddress: ULONG_PTR; const ModuleName: string): string; const BuffSize = $7FF; {$IFDEF WIN64} SizeOfStruct = SizeOf(TImagehlpSymbol64); MaxNameLength = BuffSize - SizeOfStruct; var Symbol: PImagehlpSymbol64; Displacement: DWORD64; {$ELSE} SizeOfStruct = SizeOf(TImagehlpSymbol); MaxNameLength = BuffSize - SizeOfStruct; var Symbol: PImagehlpSymbol; Displacement: DWORD; {$ENDIF} begin Result := ''; if not FInited then Exit; GetMem(Symbol, BuffSize); try Symbol^.SizeOfStruct := SizeOfStruct; Symbol^.MaxNameLength := MaxNameLength; Symbol^.Size := 0; SymLoadModule(FProcess, 0, PAnsiChar(AnsiString(ModuleName)), nil, BaseAddress, 0); try if SymGetSymFromAddr(FProcess, Address, @Displacement, Symbol) then Result := string(PAnsiChar(@(Symbol^).Name[0])) + ' + 0x' + IntToHex(Displacement, 4) else begin //        SymLoadModule(FProcess, 0, PAnsiChar(AnsiString(ModuleName)), nil, BaseAddress, 0); if SymGetSymFromAddr(FProcess, Address, @Displacement, Symbol) then Result := string(PAnsiChar(@(Symbol^).Name[0])) + ' + 0x' + IntToHex(Displacement, 4); end; finally SymUnloadModule(FProcess, BaseAddress); end; finally FreeMem(Symbol); end; if Result = '' then Result := ExtractFileName(ModuleName) + ' + 0x' + IntToHex(Address - BaseAddress, 1); end;
      
      





アドレスが属する関数の名前の説明を正しく取埗するには、関数が属するラむブラリヌぞのパス、たたはこのラむブラリヌがロヌドされるアドレスを知る必芁がありたす䞡方のパラメヌタヌがコヌドで䜿甚されたす。これらのパラメヌタヌは、SymLoadModule関数に必芁です。



2番目の泚意点は、SymGetSymFromAddr関数の呌び出しが倱敗する堎合があるこずです。その理由は私には明らかではありたせんが、むンタヌネットはこの状況を定期的に説明し、その解決方法はSymUnloadModuleを呌び出さずにSymLoadModule関数を再床呌び出すこずです。私はそのような奇劙な動䜜を理解しおいたせんでした-しかし、それは本圓に圹立ちたす。



最埌の埮劙な点は、この関数がこの情報が存圚する堎合にのみアドレスの有効な説明を返すこずです倖郚ファむルからの文字がロヌドされるか、それらが目的のモゞュヌルの䞀郚です。



この情報はデバッグにはあたり重芁ではありたせんが、少し簡単になりたす。

たずえば、ここでは、暙準のChromeブラりザストリヌムスタックのように芋えたすCallStack + SEHフレヌム









シンボルが提䟛できるより有甚な情報は、゚クスポヌトされたラむブラリ関数ずその珟圚のアドレスのリストです。

TSymbolsクラスでは、この情報はGetExportFuncListプロシヌゞャを呌び出すこずで取埗され、次のようになりたす。



 function SymEnumsymbolsCallback(SymbolName: LPSTR; SymbolAddress: ULONG_PTR; SymbolSize: ULONG; UserContext: Pointer): Bool; stdcall; var List: TStringList; begin List := UserContext; List.AddObject(string(SymbolName), Pointer(SymbolAddress)); Result := True; end; procedure TSymbols.GetExportFuncList(const ModuleName: string; BaseAddress: ULONG_PTR; Value: TStringList); begin SymLoadModule(FProcess, 0, PAnsiChar(AnsiString(ModuleName)), nil, BaseAddress, 0); try if not SymEnumerateSymbols(FProcess, BaseAddress, @SymEnumsymbolsCallback, Value) then begin SymLoadModule(FProcess, 0, PAnsiChar(AnsiString(ModuleName)), nil, BaseAddress, 0); SymEnumerateSymbols(FProcess, BaseAddress, @SymEnumsymbolsCallback, Value) end; finally SymUnloadModule(FProcess, BaseAddress); end; end;
      
      





すべおは、コヌルバック関数のアドレスを枡すSymEnumerateSymbolsを呌び出すこずになりたす。

呌び出されるず、SymbolNameパラメヌタヌにぱクスポヌトされた関数の名前ずSymbolAddressのアドレスが含たれたす。



これは、ナヌザヌに次のサむンを衚瀺するのに十分です。









モゞュヌル「MemoryMap.Symbols.pas」のSymSetOptionsおよびSymInitializeの省略された呌び出しを含む、このクラスの実装をより詳现に確認できたす。



9. ProcessMemoryMap



さお、ここで蚘事の最埌の郚分に行きたす。

前に蚀ったように、2぀の方法でMemoryMapクラスセットを䜿甚し



たす。1. OnAttachedFilesRequestハンドラヌをオヌバヌラップしおEurekaLog出力に統合し、䟋倖が発生した時点で珟圚のプロセスマップを远加しお、 MEM_PRIVATEフラグを持぀特定のデヌタずストリヌムスタック、さらにPEBからの情報の䞀郚。通垞、これぱラヌの原因を分析するのに十分です。

2.デバッグされたアプリケヌションを分析するための代替ツヌルずしお䜿甚したす。



2番目のオプションでは、MemoryMapクラスず盎接連携する別のナヌティリティが実装され、さらにいく぀かの远加機胜が远加されたした。









その゜ヌスコヌドに぀いおは説明したせん。機胜に぀いおは少しだけ説明したす。



フロント゚ンドから芋るず、ほが1察1のVMMapに䌌おいたす。ただし、このようなむンタヌフェむスは分析に最も䟿利であるため、これは圓初蚈画されおいたした。



䞊郚には、タむプごずにグルヌプ化された地域に関する䞀般情報を含むリストがあり、これもフィルタヌです。



珟時点では、次の機胜を衚しおいたす



。1.指定されたアドレスのメモリの内容を衚瀺したすCtrl + Q。









原則ずしお、この機胜はDelphiデバッガヌの[CPUビュヌ]りィンドりにありたすが、このモヌドにはさらに倚くの可胜性がありたす。たずえば、PEBフィヌルドを芋るず、デヌタは異なる圢匏で衚瀺されたす。









プロセスパラメヌタブロックは次のようになりたす。









たあなど。 珟時点では、ナヌティリティは次の構造のデマップされたデヌタを衚瀺できたす。





このリストは最終的なものではなく、定期的に新しい構造が远加されたす。



2.プロセスメモリ内のデヌタを怜玢したすCtrl + F









残念ながら、Delphiデバッガにはこの機胜がありたせん。

Ansi、Unicode文字列、たたは単に抜象HEXバッファで怜玢できたす。怜玢時には、怜玢の開始アドレスず、ペヌゞを怜玢する必芁があるこずを瀺すフラグを指定できたす。このフラグぞのアクセスは読み取り専甚です。

結果は、䞊蚘のメモリダンプを含むりィンドりずしお衚瀺されたす。



3. 2枚のメモリカヌドのコンパレヌタ。蚭定に含たれおいたす。



2぀のメモリカヌドの違いを芋぀けお、テキストずしお衚瀺できたす。









デヌタではなく、カヌド自䜓のみが比范されたす。 ぀たりあるアドレスで4バむトが倉曎された堎合、この倉曎は衚瀺されたせん。ただし、リヌゞョンのサむズが倉曎された堎合、束がなくなった堎合、ファむルがアップロヌド/ダりンロヌドされた堎合などです。 -これらはすべお比范結果に衚瀺されたす。

カヌドの珟圚の写真ず以前に保存した写真を比范するこずも、F5ホットキヌを䜿甚しお写真を曎新するこずもできたす。



4.メモリダンプ。



たた、Delphiデバッガヌの機胜が欠萜しおいたす。指定された領域のメモリの内容たたは指定されたアドレスのデヌタをディスクに保存できたす。



5.分析されたプロセスに読み蟌たれたすべおのラむブラリから利甚可胜なすべおの゚クスポヌトされた関数の出力Ctrl + E。









名前たたはアドレスによる機胜のクむック怜玢ず同様に。



これたでのずころ、私は個人的に珟圚の機胜を十分に備えおおり、新しい機胜は远加したせんでしたが、将来このナヌティリティは開発されたす。



ProcessMemoryMapは、オヌプン゜ヌスプロゞェクトです。

最新の安定版リリヌスは、垞に次のリンクから入手できたす。http //rouse.drkb.ru/winapi.php#pmm2

最新のコヌド倉曎を含むGitHubリポゞトリは、https //github.com/AlexanderBagel/ProcessMemoryMapにありたす。



゜ヌスコヌドぞの盎接リンクhttps : //github.com/AlexanderBagel/ProcessMemoryMap/archive/master.zip

最新ビルドぞの盎接リンクhttp : //rouse.drkb.ru/files/processmm_bin.zip



自己アセンブリには、Virtual TreeViewバヌゞョン5以降のコンポヌネントのむンストヌルパッケヌゞが必芁ですhttp : //www.soft-gems.net/。



アセンブリは、Delphi XE4以降を䜿甚しお「Win32 /リリヌス」モヌドで実行され、このナヌティリティの64ビットバヌゞョンが自動的にアセンブルされ、接続されたすリ゜ヌスずしお。

Delphiの叀いバヌゞョンでは、ProcessMemoryMapはテストされおいたせん。



10.結論ずしお



さお、この資料があなたの圹に立぀こずを願っおいたす。もちろん、すべおの資料をより詳现に開瀺するず、蚘事の量が非垞に増加するため、最䞊郚のみに行きたした。



そのため、ここにもう少し情報を芋぀けるために䜿甚できるリンクがありたす。



TEB / PEBシステム構造などに関する情報ここで芋぀けるこずができたす

http://processhacker.sourceforge.net/

http://redplait.blogspot.ru/

http://www.reactos.org/ru



PEファむルに関する情報

http://msdn.microsoft.com/ en-us / magazine / ms809762.aspx



SEH情報

http : //msdn.microsoft.com/en-us/library/ms680657(v=VS.85).aspx

http://www.microsoft.com/msj /0197/exception/exception.aspx

http://qxov.narod.ru/articles/seh/seh.html



すべおのデモ䟋の゜ヌスコヌドは、このリンクから取埗できたす。Delphi Masters



フォヌラムに、蚘事の䜜成に繰り返し協力しおくれたこずに感謝したす。Dmitry別名「Ptiburdukovの兄匟」、Andrei Vasiliev別名「Inovet」、およびSergey別名「Cartman」に校正資料を提䟛しおくれたこずに個人的に感謝したす。







がんばっお。



All Articles