Results 1 to 3 of 3

Thread: Module Exploring..

  1. #1
    Join Date
    Feb 2011
    Location
    The Future.
    Posts
    5,600
    Mentioned
    396 Post(s)
    Quoted
    1598 Post(s)

    Default Module Exploring..

    The below code I wrote when creating a decompiler and injector. The first function will get all information about a process given the name. The second function will get all modules loaded by a process, their handles, names, paths, address, etc.. It can be taken further to find out all functions exported in a module.

    Finally, the third function reads any file and prints all information about that file. It's a PE reader. Took me an extremely long amount of time to write these and I hope they come in useful for some of you who wish to see things done at the lowest level.
    Originally, I wrote some of the functions in ASM but C++ will be easier for all of us to read and more useable.

    These functions work for BOTH x32 and x64 bit processes and any type of file/module. The functions can be modified to HIDE modules and files within a process.. Such is the works of a virus and I will not explain how to modify them to do such things.

    These API's are mostly undocumented API's so there won't be much information on it online at all. Most of it was guess work except for the PE reader.


    Examples on notepad:







    Enjoy!

    C++ Code:
    #include <winternl.h>
    #include <windows.h>
    #include <TlHelp32.h>
    #include <iostream>


    struct Library
    {
        HMODULE Module = nullptr;
        Library(const char* LibraryName)
        {
            Module = LoadLibrary(LibraryName);
        }
        ~Library()
        {
            FreeLibrary(Module);
        }
    };

    typedef struct _LDR_MODULE
    {
        LIST_ENTRY              InLoadOrderModuleList;
        LIST_ENTRY              InMemoryOrderModuleList;
        LIST_ENTRY              InInitializationOrderModuleList;
        PVOID                   BaseAddress;
        PVOID                   EntryPoint;
        ULONG                   SizeOfImage;
        UNICODE_STRING          FullDllName;
        UNICODE_STRING          BaseDllName;
        ULONG                   Flags;
        SHORT                   LoadCount;
        SHORT                   TlsIndex;
        LIST_ENTRY              HashTableEntry;
        ULONG                   TimeDateStamp;
    } LDR_MODULE, *PLDR_MODULE;

    typedef NTSTATUS NTAPI (*pNtQueryInformationProcess)(HANDLE, PROCESSINFOCLASS, PVOID, ULONG, PULONG);


    PROCESSENTRY32 GetProcessInfo(const char* ProcessName)
    {
        void* hSnap = nullptr;
        PROCESSENTRY32 Proc32 = {0};

        if((hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0)) == INVALID_HANDLE_VALUE)
            return Proc32;

        Proc32.dwSize = sizeof(PROCESSENTRY32);
        while(Process32Next(hSnap, &Proc32))
        {
            if(!stricmp(ProcessName, Proc32.szExeFile))
            {
                CloseHandle(hSnap);
                return Proc32;
            }
        }
        CloseHandle(hSnap);
        Proc32 = {0};
        return Proc32;
    }

    void ListModuleInfo(std::string Process)
    {
        SIZE_T dwBytesRead = 0;
        PROCESS_BASIC_INFORMATION PBI = {0};
        PROCESSENTRY32 ProcessInfo = GetProcessInfo(Process.c_str());
        HANDLE ProcessHandle = OpenProcess(PROCESS_ALL_ACCESS, false, ProcessInfo.th32ProcessID);

        Library NTDLL("ntdll.dll");
        pNtQueryInformationProcess NtQIP = (pNtQueryInformationProcess) GetProcAddress(NTDLL.Module, "NtQueryInformationProcess");

        if (NT_SUCCESS(NtQIP(ProcessHandle, ProcessBasicInformation, &PBI, sizeof(PBI), reinterpret_cast<DWORD*>(&dwBytesRead))))
        {
            PEB_LDR_DATA LdrData;
            LDR_MODULE LdrModule;
            PPEB_LDR_DATA pLdrData = nullptr;

            char* LdrDataOffset = reinterpret_cast<char*>(PBI.PebBaseAddress) + offsetof(PEB, Ldr);
            ReadProcessMemory(ProcessHandle, LdrDataOffset, &pLdrData, sizeof(pLdrData), &dwBytesRead);
            ReadProcessMemory(ProcessHandle, pLdrData, &LdrData, sizeof(LdrData), &dwBytesRead);

            LIST_ENTRY* Head = LdrData.InMemoryOrderModuleList.Flink;
            LIST_ENTRY* Node = Head;

            do
            {
                if (ReadProcessMemory(ProcessHandle, reinterpret_cast<char*>(Node) - sizeof(LIST_ENTRY), &LdrModule, sizeof(LdrModule), &dwBytesRead))
                {
                    if (LdrModule.BaseAddress)
                    {
                        std::wstring BaseDllName(LdrModule.BaseDllName.Length / sizeof(WCHAR), 0);
                        std::wstring FullDllName(LdrModule.FullDllName.Length / sizeof(WCHAR), 0);
                        ReadProcessMemory(ProcessHandle, LdrModule.BaseDllName.Buffer, &BaseDllName[0], LdrModule.BaseDllName.Length, &dwBytesRead);
                        ReadProcessMemory(ProcessHandle, LdrModule.FullDllName.Buffer, &FullDllName[0], LdrModule.FullDllName.Length, &dwBytesRead);

                        std::cout<<"BaseAddress:     "<< LdrModule.BaseAddress<<std::endl;
                        std::cout<<"Entry-Point:     " << LdrModule.EntryPoint<<std::endl;
                        std::wcout<<"Base DLL-Name:   "<< BaseDllName<<std::endl;
                        std::wcout<<"Full DLL-Name:   "<< FullDllName<<std::endl;
                        std::cout<<"Reference Count: "<< LdrModule.LoadCount<<std::endl;
                        std::cout<<"Image Size:      "<< LdrModule.SizeOfImage<<std::endl;
                        std::cout<<"Time Date Stamp: "<< LdrModule.TimeDateStamp<<std::endl;
                        std::cout<<std::endl;
                    }
                }

                Node = LdrModule.InMemoryOrderModuleList.Flink;
            }
            while(Head != Node);
        }
        CloseHandle(ProcessHandle);
    }

    void PrintPEInfo(std::string FilePath)
    {
        IMAGE_DOS_HEADER*       pDosHeader;
        IMAGE_NT_HEADERS*       pNtHeaders;
        IMAGE_SECTION_HEADER*   pSectionHeader;


        void* FileHandle = CreateFile(FilePath.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
        if (FileHandle == INVALID_HANDLE_VALUE) return;
        void* FileMapping = CreateFileMapping(FileHandle, nullptr, PAGE_READONLY, 0, 0, nullptr);
        void* MapView = MapViewOfFile(FileMapping, FILE_MAP_READ, 0, 0, 0);

        pDosHeader = reinterpret_cast<IMAGE_DOS_HEADER*>(MapView);
        if (pDosHeader->e_magic == IMAGE_DOS_SIGNATURE)
        {
            std::cout<<"Valid Dos-Exe File (IMAGE_DOS_HEADER)"<<std::endl;
            std::cout<<"----------------------------------------"<<std::endl;
            std::cout<<"Magic Number:                     "<<std::hex<<pDosHeader->e_magic<<std::endl;
            std::cout<<"Bytes On Last Page:               "<<pDosHeader->e_cblp<<std::endl;
            std::cout<<"No. Of Pages:                     "<<pDosHeader->e_crlc<<std::endl;
            std::cout<<"Size Of Header Paragraphs:        "<<pDosHeader->e_cparhdr<<std::endl;
            std::cout<<"Min. Extra Paragraphs:            "<<pDosHeader->e_minalloc<<std::endl;
            std::cout<<"Max. Extra Paragraphs:            "<<pDosHeader->e_maxalloc<<std::endl;
            std::cout<<"Initial SS-Value:                 "<<pDosHeader->e_ss<<std::endl;
            std::cout<<"Initial SP-Value:                 "<<pDosHeader->e_sp<<std::endl;
            std::cout<<"Checksum:                         "<<pDosHeader->e_csum<<std::endl;
            std::cout<<"Initial IP-Value:                 "<<pDosHeader->e_ip<<std::endl;
            std::cout<<"Initial CS-Value:                 "<<pDosHeader->e_cs<<std::endl;
            std::cout<<"File Address Relocation:          "<<pDosHeader->e_lfarlc<<std::endl;
            std::cout<<"Overlay Number:                   "<<pDosHeader->e_ovno<<std::endl;
            std::cout<<"OEM Identifier:                   "<<pDosHeader->e_oemid<<std::endl;
            std::cout<<"OEM Specific Information:         "<<pDosHeader->e_oeminfo<<std::endl;
            std::cout<<"RVA Address of PE-Header:         "<<pDosHeader->e_lfanew<<std::endl;
            std::cout<<std::endl<<std::endl;
        }

        pNtHeaders = reinterpret_cast<IMAGE_NT_HEADERS*>(((SIZE_T)(pDosHeader)) + pDosHeader->e_lfanew);
        if(pNtHeaders->Signature == IMAGE_NT_SIGNATURE)
        {
            std::cout<<"Valid PE-File (IMAGE_NT_HEADER)"<<std::endl;
            std::cout<<"----------------------------------------"<<std::endl;
            std::cout<<"Signature:                        "<<pNtHeaders->Signature<<std::endl;
            std::cout<<"Machine Architecture:             ";
            switch (pNtHeaders->FileHeader.Machine)
            {
                case IMAGE_FILE_MACHINE_I386:
                    std::cout<<"x86          "<<std::endl;
                    break;
                case IMAGE_FILE_MACHINE_IA64:
                    std::cout<<"Intel Itanium"<<std::endl;
                    break;
                case IMAGE_FILE_MACHINE_AMD64:
                    std::cout<<"x64          "<<std::endl;
                    break;
                default:
                    std::cout<<"Unknown      "<<std::endl;
                    break;

            }

            std::cout<<"Number Of Sections:               "<<pNtHeaders->FileHeader.NumberOfSections<<std::endl;
            std::cout<<"Time Date Stamp:                  "<<pNtHeaders->FileHeader.TimeDateStamp<<std::endl;
            std::cout<<"Pointer To Symbol Table:          "<<pNtHeaders->FileHeader.PointerToSymbolTable<<std::endl;
            std::cout<<"Number Of Symbols:                "<<pNtHeaders->FileHeader.NumberOfSymbols<<std::endl;
            std::cout<<"Size Of Optional-Header:          "<<pNtHeaders->FileHeader.SizeOfOptionalHeader<<std::endl;
            std::cout<<"Characteristics:                  "<<std::endl<<std::endl;


            WORD Characteristics[] = {IMAGE_FILE_RELOCS_STRIPPED, IMAGE_FILE_EXECUTABLE_IMAGE, IMAGE_FILE_LINE_NUMS_STRIPPED, IMAGE_FILE_LARGE_ADDRESS_AWARE,
                                      IMAGE_FILE_32BIT_MACHINE, IMAGE_FILE_DEBUG_STRIPPED, IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP, IMAGE_FILE_NET_RUN_FROM_SWAP, IMAGE_FILE_SYSTEM,
                                      IMAGE_FILE_DLL, IMAGE_FILE_UP_SYSTEM_ONLY
                                     };

            for (std::size_t I = 0; I < sizeof(Characteristics)/sizeof(WORD); ++I)
            {
                switch (pNtHeaders->FileHeader.Characteristics & Characteristics[I])
                {
                    case IMAGE_FILE_RELOCS_STRIPPED:
                        std::cout<<"    Relocation information was stripped from the file."<<std::endl;
                        break;
                    case IMAGE_FILE_EXECUTABLE_IMAGE:
                        std::cout<<"    The file is executable."<<std::endl;
                        break;
                    case IMAGE_FILE_LINE_NUMS_STRIPPED:
                        std::cout<<"    COFF line numbers were stripped from the file."<<std::endl;
                        break;
                    case IMAGE_FILE_LARGE_ADDRESS_AWARE:
                        std::cout<<"    The application can handle addresses larger than 2 GB."<<std::endl;
                        break;
                    case IMAGE_FILE_32BIT_MACHINE:
                        std::cout<<"    The computer supports 32-bit words."<<std::endl;
                        break;
                    case IMAGE_FILE_DEBUG_STRIPPED:
                        std::cout<<"    Debugging information was removed and stored separately in another file."<<std::endl;
                        break;
                    case IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP:
                        std::cout<<"    If the image is on removable media, copy it to and run it from the swap file."<<std::endl;
                        break;
                    case IMAGE_FILE_NET_RUN_FROM_SWAP:
                        std::cout<<"    If the image is on the network, copy it to and run it from the swap file."<<std::endl;
                        break;
                    case IMAGE_FILE_SYSTEM:
                        std::cout<<"    The image is a system file."<<std::endl;
                        break;
                    case IMAGE_FILE_DLL:
                        std::cout<<"    The image is a DLL file. It can't be run directly."<<std::endl;
                        break;
                    case IMAGE_FILE_UP_SYSTEM_ONLY:
                        std::cout<<"    The file should be run only on a uniprocessor computer."<<std::endl;
                        break;
                    default:
                        break;
                }
            }

            std::cout<<"\n\nOptional-Header (IMAGE_OPTIONAL_HEADER)"<<std::endl;
            std::cout<<"----------------------------------------"<<std::endl;
            std::cout<<"Magic Number:                     "<<pNtHeaders->OptionalHeader.Magic<<std::endl;
            std::cout<<"Major Linker Version:             "<<static_cast<int>(pNtHeaders->OptionalHeader.MajorLinkerVersion)<<std::endl;
            std::cout<<"Minor Linker Version:             "<<static_cast<int>(pNtHeaders->OptionalHeader.MajorLinkerVersion)<<std::endl;
            std::cout<<"Code Section Size (.text):        "<<pNtHeaders->OptionalHeader.SizeOfCode<<std::endl;
            std::cout<<"Size Of Initialized Data:         "<<pNtHeaders->OptionalHeader.SizeOfInitializedData<<std::endl;
            std::cout<<"Address Of Entry Point:           "<<pNtHeaders->OptionalHeader.AddressOfEntryPoint<<std::endl;
            std::cout<<"Address Of Code (.code):          "<<pNtHeaders->OptionalHeader.BaseOfCode<<std::endl;
            #ifndef __x86_64
                std::cout<<"Address Of Data (.data):          "<<pNtHeaders->OptionalHeader.BaseOfData<<std::endl;
            #endif
            std::cout<<"Base Address Of Image:            "<<pNtHeaders->OptionalHeader.ImageBase<<std::endl;
            std::cout<<"Section Alignment:                "<<pNtHeaders->OptionalHeader.SectionAlignment<<std::endl;
            std::cout<<"FileAlignment:                    "<<pNtHeaders->OptionalHeader.FileAlignment<<std::endl;
            std::cout<<"Major OS Version:                 "<<pNtHeaders->OptionalHeader.MajorOperatingSystemVersion<<std::endl;
            std::cout<<"Minor OS Version:                 "<<pNtHeaders->OptionalHeader.MinorOperatingSystemVersion<<std::endl;
            std::cout<<"Major Image Version:              "<<pNtHeaders->OptionalHeader.MajorImageVersion<<std::endl;
            std::cout<<"Minor Image Version:              "<<pNtHeaders->OptionalHeader.MinorImageVersion<<std::endl;
            std::cout<<"Major Subsystem Version:          "<<pNtHeaders->OptionalHeader.MajorSubsystemVersion<<std::endl;
            std::cout<<"Minor Subsystem Version:          "<<pNtHeaders->OptionalHeader.MinorSubsystemVersion<<std::endl;
            std::cout<<"Size Of Image:                    "<<pNtHeaders->OptionalHeader.SizeOfImage<<std::endl;
            std::cout<<"Size Of Headers:                  "<<pNtHeaders->OptionalHeader.SizeOfHeaders<<std::endl;
            std::cout<<"Checksum:                         "<<pNtHeaders->OptionalHeader.CheckSum<<std::endl;
            std::cout<<"Subsystem:                        ";

            switch (pNtHeaders->OptionalHeader.Subsystem)
            {
                case IMAGE_SUBSYSTEM_UNKNOWN:
                    std::cout<<"Unknown subsystem."<<std::endl;
                    break;
                case IMAGE_SUBSYSTEM_NATIVE:
                    std::cout<<"No subsystem required (device drivers and native system processes)."<<std::endl;
                    break;
                case IMAGE_SUBSYSTEM_WINDOWS_GUI:
                    std::cout<<"Windows graphical UI (GUI) subsystem."<<std::endl;
                    break;
                case IMAGE_SUBSYSTEM_WINDOWS_CUI:
                    std::cout<<"Windows character-mode UI (CUI) subsystem."<<std::endl;
                    break;
                case IMAGE_SUBSYSTEM_OS2_CUI:
                    std::cout<<"OS/2 CUI subsystem."<<std::endl;
                    break;
                case IMAGE_SUBSYSTEM_POSIX_CUI:
                    std::cout<<"POSIX CUI subsystem."<<std::endl;
                    break;
                case IMAGE_SUBSYSTEM_WINDOWS_CE_GUI:
                    std::cout<<"Windows CE system."<<std::endl;
                    break;
                case IMAGE_SUBSYSTEM_EFI_APPLICATION:
                    std::cout<<"Extensible Firmware Interface (EFI) application."<<std::endl;
                    break;
                case IMAGE_SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER:
                    std::cout<<"EFI driver with boot services."<<std::endl;
                    break;
                case IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER:
                    std::cout<<"EFI driver with run-time services."<<std::endl;
                    break;
                case IMAGE_SUBSYSTEM_EFI_ROM:
                    std::cout<<"EFI ROM image."<<std::endl;
                    break;
                case IMAGE_SUBSYSTEM_XBOX:
                    std::cout<<"Xbox system."<<std::endl;
                    break;
                case IMAGE_SUBSYSTEM_WINDOWS_BOOT_APPLICATION:
                    std::cout<<"Boot application."<<std::endl;
                    break;
                default:
                    break;
            }

            std::cout<<"Dll Characteristics:                 "<<std::endl<<std::endl;

            WORD DllCharacteristics[] = {IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE, IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY, IMAGE_DLLCHARACTERISTICS_NX_COMPAT,
                                         IMAGE_DLLCHARACTERISTICS_NO_ISOLATION, IMAGE_DLLCHARACTERISTICS_NO_SEH, IMAGE_DLLCHARACTERISTICS_NO_BIND, IMAGE_DLLCHARACTERISTICS_WDM_DRIVER,
                                         IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE
                                        };

            for (std::size_t I = 0; I < sizeof(DllCharacteristics)/sizeof(WORD); ++I)
            {
                switch (pNtHeaders->OptionalHeader.DllCharacteristics & DllCharacteristics[I])
                {
                    case IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE:
                        std::cout<<"    The DLL can be relocated at load time."<<std::endl;
                        break;
                    case IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY:
                        std::cout<<"    Code integrity checks are forced."<<std::endl;
                        break;
                    case IMAGE_DLLCHARACTERISTICS_NX_COMPAT:
                        std::cout<<"    The image is compatible with data execution prevention (DEP)."<<std::endl;
                        break;
                    case IMAGE_DLLCHARACTERISTICS_NO_ISOLATION:
                        std::cout<<"    The image is isolation aware, but should not be isolated."<<std::endl;
                        break;
                    case IMAGE_DLLCHARACTERISTICS_NO_SEH:
                        std::cout<<"    The image does not use structured exception handling (SEH)."<<std::endl;
                        break;
                    case IMAGE_DLLCHARACTERISTICS_NO_BIND:
                        std::cout<<"    Do not bind the image."<<std::endl;
                        break;
                    case IMAGE_DLLCHARACTERISTICS_WDM_DRIVER:
                        std::cout<<"    A WDM driver."<<std::endl;
                        break;
                    case IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE:
                        std::cout<<"    The image is terminal server aware."<<std::endl;
                        break;
                    default:
                        break;
                }
            }

            std::cout<<std::endl;
            std::cout<<"Size Of Stack Reserve:            "<<pNtHeaders->OptionalHeader.SizeOfStackReserve<<std::endl;
            std::cout<<"Size Of Stack Commit:             "<<pNtHeaders->OptionalHeader.SizeOfStackCommit<<std::endl;
            std::cout<<"Size Of Heap Reserve:             "<<pNtHeaders->OptionalHeader.SizeOfHeapReserve<<std::endl;
            std::cout<<"Size Of Heap Commit:              "<<pNtHeaders->OptionalHeader.SizeOfHeapCommit<<std::endl;
            std::cout<<"Number Of Rva And Sizes:          "<<pNtHeaders->OptionalHeader.NumberOfRvaAndSizes<<std::endl;

            int I = 0;
            std::cout<<"\n\nData Directory (IMPORT TABLE RVA):"<<std::endl;
            std::cout<<"----------------------------------"<<std::endl;
            for (pSectionHeader = IMAGE_FIRST_SECTION(pNtHeaders); I < pNtHeaders->FileHeader.NumberOfSections; ++pSectionHeader, ++I)
            {
                std::cout<<"\n\n    Section Info ("<<I + 1<<" of "<<pNtHeaders->FileHeader.NumberOfSections<<")"<<std::endl;
                std::cout<<"    ------------------------------------"<<std::endl;
                if ((pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress >= pSectionHeader->VirtualAddress) && ((pSectionHeader->VirtualAddress + pSectionHeader->Misc.VirtualSize) > pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress))
                {
                    std::cout<<"    IAT Entry Point:              "<<((pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress - pSectionHeader->VirtualAddress) + pSectionHeader->PointerToRawData)<<std::endl;
                }

                std::cout<<"    Section Header Name:          "<<pSectionHeader->Name<<std::endl;
                std::cout<<"    Actual .code or .data Size:   "<<pSectionHeader->Misc.VirtualSize<<std::endl;
                std::cout<<"    Virtual Address(RVA):         "<<pSectionHeader->VirtualAddress<<std::endl;
                std::cout<<"    Size Of Raw Data:             "<<pSectionHeader->SizeOfRawData<<std::endl;
                std::cout<<"    Pointer To Raw Data:          "<<pSectionHeader->PointerToRawData<<std::endl;
                std::cout<<"    Pointer To Relocations:       "<<pSectionHeader->PointerToRelocations<<std::endl;
                std::cout<<"    Pointer To Line Numbers:      "<<pSectionHeader->PointerToLinenumbers<<std::endl;
                std::cout<<"    Number Of Relocations:        "<<pSectionHeader->NumberOfRelocations<<std::endl;
                std::cout<<"    Number Of Line Numbers:       "<<pSectionHeader->NumberOfLinenumbers<<std::endl;
                std::cout<<"    Characteristics:              ";

                //For each characteristic.. switch.. sectionheader..
            }
            std::cout<<std::endl<<std::endl;
        }
        UnmapViewOfFile(MapView);
        CloseHandle(FileMapping);
        CloseHandle(FileHandle);
    }


    int main()
    {
        ListModuleInfo("notepad.exe");
        std::cout<<"Press Enter/Return To Continue.."<<std::endl;
        std::cin.get();

        PrintPEInfo("C:/windows/notepad.exe");
        std::cout<<"Press Enter/Return To Exit.."<<std::endl;
        std::cin.get();
    }
    Last edited by Brandon; 03-30-2013 at 07:41 PM.

  2. #2
    Join Date
    May 2007
    Posts
    527
    Mentioned
    12 Post(s)
    Quoted
    109 Post(s)

    Default

    Cool. When I wrote my poker bot for Microgaming network, I had to do a lot of similar stuff. Take a look at EasyHook, it's super-amazing library!

  3. #3
    Join Date
    Jul 2012
    Posts
    437
    Mentioned
    10 Post(s)
    Quoted
    165 Post(s)

    Default

    nice, I'll play with it when I get some free time.

Thread Information

Users Browsing this Thread

There are currently 1 users browsing this thread. (0 members and 1 guests)

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •