├── .gitignore ├── README.md ├── dislib.py ├── file_details.py ├── hackers-grep.py ├── msdia80.dll └── pdbsymbols.py /.gitignore: -------------------------------------------------------------------------------- 1 | # vi lock 2 | *.swp 3 | 4 | # Byte-compiled / optimized / DLL files 5 | __pycache__/ 6 | *.py[cod] 7 | 8 | # C extensions 9 | *.so 10 | 11 | # Distribution / packaging 12 | .Python 13 | env/ 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | downloads/ 18 | eggs/ 19 | .eggs/ 20 | lib/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .coverage 43 | .coverage.* 44 | .cache 45 | nosetests.xml 46 | coverage.xml 47 | *,cover 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | 56 | # Sphinx documentation 57 | docs/_build/ 58 | 59 | # PyBuilder 60 | target/ 61 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # hackers-grep 2 | hackers-grep is a tool that enables you to search for strings in PE files. The tool is capable of searching strings, imports, exports, and public symbols (like woah) using regular expressions. 3 | 4 | I have used this tool to find functionality across Windows that I want to investigate further. It has allowed me to answer questions like "How many libraries process XML?". 5 | 6 | It's also fun to poke around with. 7 | 8 | # Installation 9 | 10 | 1. Install comtypes (https://pypi.python.org/pypi/comtypes) 11 | 2. Install pywin32 (http://sourceforge.net/projects/pywin32/files/pywin32/) 12 | 3. Install Microsoft debugging symbols (https://msdn.microsoft.com/en-us/windows/hardware/gg463028.aspx) 13 | 14 | # Usage 15 | 16 | The options for hackers-grep are listed below. Keep in mind that all regular expression strings should use pythons "re" module sytax and are case insensitive. For more information please see https://docs.python.org/2/library/re.html. 17 | 18 | ``` 19 | Z:\hackers-grep>hackers-grep.py -h 20 | Usage: hackers-grep.py [options] 21 | 22 | Options: 23 | -h, --help show this help message and exit 24 | -d MAX_DEPTH, --max-depth=MAX_DEPTH 25 | Maximum directory recursion depth (default: 1) 26 | -x, --exports-only Only search Export section strings 27 | -n, --imports-only Only search Import section strings 28 | -a, --all-the-things Search strings, import, exports 29 | -s, --symbols Include symbols in search 30 | -p SYMBOL_PATH, --symbol-path=SYMBOL_PATH 31 | Symbol path (default: C:\Windows\Symbols) 32 | -e EXPORT_FILTER, --export-filter=EXPORT_FILTER 33 | Search modules matching this Export regex 34 | -i IMPORT_FILTER, --import-filter=IMPORT_FILTER 35 | Search modules matching this Import regex 36 | -f, --show-info Display file details size and modification time 37 | -v, --verbose Verbose output 38 | ``` 39 | 40 | # Examples 41 | 42 | ### Search for every dll that imports wininet!InternetOpen 43 | This example demonstrates how to search imports. It will search C:\Windows\System32 for any file ending in .dll that imports InternetOpenA or InternetOpenW. 44 | ``` 45 | Z:\hackers-grep>hackers-grep.py -n c:\windows\system32 .*.dll "InternetOpen[A,W]" 46 | c:\windows\system32\msidcrl30.dll WININET.dll!InternetOpenA 47 | c:\windows\system32\msscp.dll WININET.dll!InternetOpenA 48 | c:\windows\system32\mssign32.dll WININET.dll!InternetOpenA 49 | c:\windows\system32\msnetobj.dll WININET.dll!InternetOpenA 50 | c:\windows\system32\oleprn.dll WININET.dll!InternetOpenW 51 | c:\windows\system32\urlmon.dll WININET.dll!InternetOpenW 52 | c:\windows\system32\winethc.dll WININET.dll!InternetOpenW 53 | c:\windows\system32\wuwebv.dll WININET.dll!InternetOpenW 54 | ``` 55 | 56 | By using the option "-f" we can also print information about the file, including the useful "File description" property. 57 | ``` 58 | Z:\hackers-grep>hackers-grep.py -f -n c:\windows\system32 .*.dll "InternetOpen[A,W]" 59 | c:\windows\system32\msidcrl30.dll [ 468 KB] [07/14/2009] [Microsoft Corporation] [IDCRL Dynamic Link Library] WININET.dll!InternetOpenA 60 | c:\windows\system32\msscp.dll [ 492 KB] [02/03/2015] [Microsoft Corporation] [Windows Media Secure Content Provider] WININET.dll!InternetOpenA 61 | c:\windows\system32\mssign32.dll [ 38 KB] [07/14/2009] [Microsoft Corporation] [Microsoft Trust Signing APIs] WININET.dll!InternetOpenA 62 | c:\windows\system32\msnetobj.dll [ 259 KB] [02/03/2015] [Microsoft Corporation] [DRM ActiveX Network Object] WININET.dll!InternetOpenA 63 | c:\windows\system32\oleprn.dll [ 104 KB] [07/14/2009] [Microsoft Corporation] [Oleprn DLL] WININET.dll!InternetOpenW 64 | c:\windows\system32\urlmon.dll [1280 KB] [07/16/2015] [Microsoft Corporation] [OLE32 Extensions for Win32] WININET.dll!InternetOpenW 65 | c:\windows\system32\winethc.dll [ 81 KB] [07/14/2009] [Microsoft Corporation] [WinInet Helper Class] WININET.dll!InternetOpenW 66 | c:\windows\system32\wuwebv.dll [ 169 KB] [07/20/2015] [Microsoft Corporation] [Windows Update Vista Web Control] WININET.dll!InternetOpenW 67 | ``` 68 | 69 | ### Search for every dll that exports DllGetClassObject 70 | This example demonstrates how to search exports. It will search C:\Windows\System32 for any file ending in .dll that exports the OLE function DllGetClassObject 71 | ``` 72 | Z:\hackers-grep>hackers-grep.py -x c:\windows\system32 .*.dll DllGetClassObject 73 | c:\windows\system32\accessibilitycpl.dll !DllGetClassObject 74 | c:\windows\system32\ActionCenterCPL.dll !DllGetClassObject 75 | c:\windows\system32\activeds.dll !DllGetClassObject 76 | c:\windows\system32\AdmTmpl.dll !DllGetClassObject 77 | c:\windows\system32\adsldp.dll !DllGetClassObject 78 | c:\windows\system32\adsmsext.dll !DllGetClassObject 79 | c:\windows\system32\amstream.dll !DllGetClassObject 80 | ... 81 | ``` 82 | 83 | ### Search for every occurrence of the string "xmlns" 84 | This example will search C:\Windows\System32 for every occurance of the string "xmlns" in a dll. 85 | ``` 86 | Z:\hackers-grep>hackers-grep.py c:\windows\system32 .*.dll xmlns.* 87 | ... 88 | c:\windows\system32\mscms.dll xmlns:wcs='http://schemas.microsoft.com/windows/2005/02/color/WcsCommonProfileTypes' 89 | c:\windows\system32\mscms.dll xmlns:gmm='http://schemas.microsoft.com/windows/2005/02/color/GamutMapModel' 90 | c:\windows\system32\mscms.dll xmlns:cam='http://schemas.microsoft.com/windows/2005/02/color/ColorAppearanceModel' 91 | 92 | c:\windows\system32\msmpeg2vdec.dll xmlns:c 93 | c:\windows\system32\msmpeg2vdec.dll xmlns:c 94 | c:\windows\system32\mstscax.dll xmlns:psf='http://schemas.microsoft.com/windows/2003/08/printing/printschemaframework' 95 | c:\windows\system32\mstscax.dll xmlns:psf='http://schemas.microsoft.com/windows/2003/08/printing/printschemaframework' 96 | 97 | c:\windows\system32\wlanpref.dll xmlns:p='http://www.microsoft.com/networking/WLAN/profile/v1' xmlns:wps='http://www.microsoft.com/networking/WPSSettings/v1' 98 | c:\windows\system32\wlanpref.dll xmlns:p='http://www.microsoft.com/networking/WLAN/profile/v1' xmlns:wps='http://www.microsoft.com/networking/WPSSettings/v1' 99 | c:\windows\system32\WlanMM.dll xmlns:an='http://www.microsoft.com/AvailableNetwork/Info' 100 | c:\windows\system32\WlanMM.dll xmlns:an='http://www.microsoft.com/AvailableNetwork/Info' 101 | 102 | ``` 103 | 104 | ### Search for every RPC server that also imports CreateFile 105 | This example shows how to use the import filter "-i" option. In this case, we want to find any dll that imports CreateFile, but filter only those that import RpcServerListen as well. 106 | 107 | ``` 108 | Z:\hackers-grep>hackers-grep.py -n -i RpcServerListen c:\windows\system32 .*.dll CreateFile 109 | c:\windows\system32\authui.dll KERNEL32.dll!CreateFileW 110 | c:\windows\system32\certprop.dll KERNEL32.dll!CreateFileW 111 | c:\windows\system32\FXSAPI.dll KERNEL32.dll!CreateFileW 112 | c:\windows\system32\FXSAPI.dll KERNEL32.dll!CreateFileMappingW 113 | c:\windows\system32\bthserv.dll KERNEL32.dll!CreateFileW 114 | c:\windows\system32\iscsiexe.dll KERNEL32.dll!CreateFileW 115 | c:\windows\system32\msdtcprx.dll KERNEL32.dll!CreateFileW 116 | c:\windows\system32\RpcEpMap.dll API-MS-Win-Core-File-L1-1-0.dll!CreateFileW 117 | c:\windows\system32\rpcss.dll KERNEL32.dll!CreateFileMappingW 118 | c:\windows\system32\bdesvc.dll KERNEL32.dll!CreateFileW 119 | c:\windows\system32\SCardSvr.dll KERNEL32.dll!CreateFileW 120 | c:\windows\system32\tapisrv.dll KERNEL32.dll!CreateFileMappingW 121 | c:\windows\system32\tapisrv.dll KERNEL32.dll!CreateFileW 122 | c:\windows\system32\termsrv.dll KERNEL32.dll!CreateFileW 123 | c:\windows\system32\wbiosrvc.dll API-MS-Win-Core-File-L1-1-0.dll!CreateFileW 124 | c:\windows\system32\wiaservc.dll KERNEL32.dll!CreateFileW 125 | c:\windows\system32\wiaservc.dll KERNEL32.dll!CreateFileMappingW 126 | c:\windows\system32\tbssvc.dll KERNEL32.dll!CreateFileA 127 | c:\windows\system32\wscsvc.dll KERNEL32.dll!CreateFileW 128 | c:\windows\system32\wscsvc.dll KERNEL32.dll!CreateFileMappingW 129 | c:\windows\system32\rdpcore.dll KERNEL32.dll!CreateFileW 130 | c:\windows\system32\wlansvc.dll KERNEL32.dll!CreateFileW 131 | c:\windows\system32\wwansvc.dll KERNEL32.dll!CreateFileW 132 | ``` 133 | 134 | ### Search symbols like a boss 135 | This example shows my favorite use of hackers-grep. The ability to combine some of the previously shown features and the verbose symbol information makes this a nice tool for finding that next bug :) 136 | 137 | In this example we want to use debugging symbols (.pdb) to search for every occurance of hfont. 138 | ``` 139 | Z:\hackers-grep>hackers-grep.py -s c:\windows\system32 .*.dll ".*HFONT.*" 140 | c:\windows\system32\advpack.dll struct HFONT__ * g_hFont 141 | c:\windows\system32\advpack.dll struct HFONT__ * g_hFont 142 | 143 | c:\windows\system32\dxtrans.dll public: virtual long __stdcall CDXTLabel::GetFontHandle(struct HFONT__ * *) 144 | c:\windows\system32\dxtrans.dll public: virtual long __stdcall CDXTLabel::SetFontHandle(struct HFONT__ *) 145 | c:\windows\system32\dxtrans.dll public: virtual long __stdcall CDX2D::GetFont(struct HFONT__ * *) 146 | c:\windows\system32\dxtrans.dll public: virtual long __stdcall CDX2D::SetFont(struct HFONT__ *) 147 | c:\windows\system32\dxtrans.dll public: virtual long __stdcall CDXTLabel::GetFontHandle(struct HFONT__ * *) 148 | c:\windows\system32\dxtrans.dll public: virtual long __stdcall CDXTLabel::SetFontHandle(struct HFONT__ *) 149 | c:\windows\system32\dxtrans.dll public: virtual long __stdcall CDX2D::GetFont(struct HFONT__ * *) 150 | c:\windows\system32\dxtrans.dll public: virtual long __stdcall CDX2D::SetFont(struct HFONT__ *) 151 | 152 | c:\windows\system32\XpsGdiConverter.dll protected: void __thiscall std::list > >,class std::allocator > > > >::_Incsize(unsigned int) 155 | c:\windows\system32\XpsGdiConverter.dll public: virtual void * __thiscall std::tr1::_Ref_count >::`vector deleting destructor'(unsigned int) 157 | 158 | ``` 159 | 160 | This example searches symbols for IStream::Read 161 | ``` 162 | Z:\hackers-grep>hackers-grep.py -s c:\windows\system32 .*.dll ".*IStream::Read.*" 163 | c:\windows\system32\apss.dll public: virtual long __stdcall CStream::CImpIStream::Read(void *,unsigned long,unsigned long *) 164 | c:\windows\system32\apss.dll public: virtual long __stdcall CStream::CImpIStream::Read(void *,unsigned long,unsigned long *) 165 | c:\windows\system32\avifil32.dll public: virtual long __stdcall CPrxAVIStream::Read(long,long,void *,long,long *,long *) 166 | c:\windows\system32\avifil32.dll public: virtual long __stdcall CPrxAVIStream::ReadFormat(long,void *,long *) 167 | c:\windows\system32\avifil32.dll public: virtual long __stdcall CPrxAVIStream::ReadData(unsigned long,void *,long *) 168 | c:\windows\system32\avifil32.dll public: virtual long __stdcall CPrxAVIStream::Read(long,long,void *,long,long *,long *) 169 | c:\windows\system32\avifil32.dll public: virtual long __stdcall CPrxAVIStream::ReadFormat(long,void *,long *) 170 | c:\windows\system32\avifil32.dll public: virtual long __stdcall CPrxAVIStream::ReadData(unsigned long,void *,long *) 171 | c:\windows\system32\cdosys.dll public: virtual long __stdcall CMMIStream::Read(void *,unsigned long,unsigned long *) 172 | c:\windows\system32\cdosys.dll public: virtual long __stdcall MemIStream::Read(void *,unsigned long,unsigned long *) 173 | c:\windows\system32\cdosys.dll public: virtual long __stdcall CMMIStream::Read(void *,unsigned long,unsigned long *) 174 | c:\windows\system32\cdosys.dll public: virtual long __stdcall MemIStream::Read(void *,unsigned long,unsigned long *) 175 | c:\windows\system32\imapi2fs.dll CFsiStream::Read 176 | c:\windows\system32\imapi2fs.dll CFsiStream::Read 177 | 178 | ``` 179 | 180 | # Known issues 181 | Its written in Python, so it can be slow, especially if you try and search the entire system drive while processing symbols 182 | 183 | -------------------------------------------------------------------------------- /dislib.py: -------------------------------------------------------------------------------- 1 | # 2 | # diSlib64 - diStorm PE library 3 | # Gil Dabah, 2007 4 | # http://ragestorm.net/distorm/ 5 | # 6 | # Thanks to Roee Shenberg for the OO. 7 | # Thanks to TFKyle for the Impors/Exports/Relocs property idea (loaded upon demand). 8 | # Thanks to Fred Male for the minor Exports bug fix. 9 | # 10 | #Copyright (c) 2007, Gil Dabah 11 | #All rights reserved. 12 | #Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 13 | # 14 | # * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 15 | # * 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. 16 | # * Neither the name of the diStorm nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 17 | # 18 | #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. 19 | 20 | 21 | import os 22 | import sys 23 | import struct 24 | import cStringIO 25 | 26 | def ReadAsciiz(buf): 27 | try: 28 | return buf[:buf.index('\x00')] 29 | except ValueError: 30 | return None 31 | 32 | MZ_HEADER_LENGTH = 0x40 33 | PE_HEADER_LENGTH = 0x18 34 | PE_HEADER_OFFSET = 0x3c 35 | OPTHDR_SIZE_OFFSET = 0x14 36 | SECTION_SIZE = 0x28 37 | 38 | IMAGE_FILE_MACHINE_I386 = 0x14c 39 | IMAGE_FILE_MACHINE_AMD64 = 0x8664 40 | 41 | MAGIC_PE32 = 0x10b 42 | MAGIC_PE32_PLUS = 0x20b 43 | 44 | class PEException(Exception): 45 | pass 46 | 47 | def SafeRead(f, length): 48 | """Tries to read and will raise an exception if there are not enough bytes.""" 49 | data = f.read(length) 50 | if len(data) != length: 51 | raise IOError("Couldn't read enough bytes!") 52 | return data 53 | 54 | def GetQword(s, offset, count = 1): 55 | if count == 1: 56 | return struct.unpack("= i.VA) and (VA < (i.VA + i.Size)): 268 | return i 269 | return None 270 | 271 | def LoadImports(self): 272 | ImpSec = self.GetSectionByVA(self.ImportsOff) 273 | VA = self.ImportsOff 274 | ImageBase = self.ImageBase 275 | if (ImpSec == None): 276 | raise PEException("Cannot find import table section.") 277 | Imports = [] 278 | VA -= ImpSec.VA 279 | while True: 280 | try: 281 | ModuleNameVA = GetDword(ImpSec.Data, VA + 0xc) 282 | except IndexError: 283 | raise PEException("Cannot read imported module name RVA.") 284 | try: 285 | FirstThunkVA = GetDword(ImpSec.Data, VA + 0x10) 286 | except IndexError: 287 | raise PEException("Cannot read imported functions addresses RVA.") 288 | # Stop parsing import descriptors on any of the next cases. 289 | if ModuleNameVA == 0 or FirstThunkVA == 0: 290 | break 291 | sec = self.GetSectionByVA(ModuleNameVA) 292 | if (sec == None): 293 | raise PEException("Cannot find imported module name.") 294 | 295 | ModuleName = ReadAsciiz(sec.Data[ModuleNameVA - sec.VA:]) 296 | if (ModuleName == None): 297 | raise PEException("Cannot read imported module name.") 298 | FunctionsNamesAddrs = [] 299 | try: 300 | OrigFirstThunkVA = GetDword(ImpSec.Data, VA) 301 | except IndexError: 302 | raise PEException("Cannot read imported functions names RVA.") 303 | PtrsSec = self.GetSectionByVA(FirstThunkVA) 304 | if (PtrsSec == None): 305 | raise PEException("Cannot find imported functions addresses.") 306 | 307 | # See if we can use Original Thunk or the First Thunk. 308 | if ((OrigFirstThunkVA == 0) or (OrigFirstThunkVA < self.SizeOfHeaders) or (OrigFirstThunkVA >= self.SizeOfImage)): 309 | # Continue anyways, some linkers use a zero OrigFirstThunkVA... 310 | # Prefer using First Thunk. 311 | OrigFirstThunkVA = FirstThunkVA 312 | 313 | sec = self.GetSectionByVA(OrigFirstThunkVA) 314 | if (sec == None): 315 | raise PEException("Cannot find imported functions names.") 316 | 317 | OrigFirstThunkVA -= sec.VA 318 | # Scan for the imported functions. 319 | while True: 320 | # Get an RVA to its ImportByName structure. 321 | try: 322 | if (self.OptMagic == MAGIC_PE32): 323 | ImportByNameVA = GetDword(sec.Data, OrigFirstThunkVA) 324 | OrdBit = 1L << 31 325 | else: 326 | ImportByNameVA = GetQword(sec.Data, OrigFirstThunkVA) 327 | OrdBit = 1L << 63 328 | except IndexError: 329 | raise PEException("Cannot read ImportByName RVA.") 330 | # Is it the end of list? 331 | if (ImportByNameVA == 0): 332 | break 333 | # Is this offset is actually an ordinal number? 334 | if (ImportByNameVA & OrdBit): 335 | FuncName = "Ord_0x%04X" % (ImportByNameVA & 0xffff) 336 | else: 337 | # Move next. 338 | ImportByNameVA += 2 339 | sec2 = self.GetSectionByVA(ImportByNameVA) 340 | if (sec2 == None): 341 | raise PEException("Cannot find imported function name section.") 342 | 343 | ImportByNameVA -= sec2.VA 344 | # Get the imported function name. 345 | FuncName = ReadAsciiz(sec2.Data[ImportByNameVA:]) 346 | if (FuncName == None): 347 | raise PEException("Cannot read imported function name.") 348 | 349 | if (len(FuncName) == 0): 350 | break 351 | # Address of pointer to function in the IAT. 352 | FunctionsNamesAddrs.append((FirstThunkVA + ImageBase, FuncName)) 353 | if (self.OptMagic == MAGIC_PE32): 354 | OrigFirstThunkVA += 4 355 | FirstThunkVA += 4 356 | else: 357 | OrigFirstThunkVA += 8 358 | FirstThunkVA += 8 359 | # Create them all as Import class. Add the module and its functions to the list. 360 | Imports += [Import(va, fname, ModuleName) for va, fname in FunctionsNamesAddrs] 361 | # Next module. 362 | VA += 0x14 363 | return Imports 364 | 365 | def LoadExports(self): 366 | ExpSec = self.GetSectionByVA(self.ExportsOff) 367 | VA = self.ExportsOff 368 | CodeBase = self.CodeBase 369 | ImageBase = self.ImageBase 370 | if (ExpSec == None): 371 | raise PEException("Cannot find export table section.") 372 | 373 | if (len(ExpSec.Data) < 0x28): 374 | raise PEException("Export table too small!") 375 | 376 | VA -= ExpSec.VA 377 | # Read basic information about the Export Table. 378 | OrdinalBase, FuncsCount, NamesCount, FuncsAddrsVA = GetDword(ExpSec.Data, VA + 0x10, 4) 379 | 380 | # Get the functions' addresses array page. 381 | FuncsSec = self.GetSectionByVA(FuncsAddrsVA) 382 | if (FuncsSec == None): 383 | raise PEException("Cannot find export table functions addresses.") 384 | 385 | FuncsAddrsVA -= FuncsSec.VA 386 | 387 | NamesAddrsVA = GetDword(ExpSec.Data, VA + 0x20) 388 | NamesSec = self.GetSectionByVA(NamesAddrsVA) 389 | if (NamesSec == None): 390 | raise PEException("Cannot find export table functions names.") 391 | 392 | NamesAddrsVA -= NamesSec.VA 393 | 394 | # Get the functions' ordinals array page. 395 | OrdsAddrsVA = GetDword(ExpSec.Data, VA + 0x24) 396 | OrdsSec = self.GetSectionByVA(OrdsAddrsVA) 397 | if (OrdsSec == None): 398 | raise PEException("Cannot find export table functions ordinals.") 399 | 400 | OrdsAddrsVA -= OrdsSec.VA 401 | 402 | ordnum = 0 403 | FuncNames = [] 404 | Names = [] 405 | Ords = [] 406 | # [addr, isUsed] 407 | if ((FuncsAddrsVA + FuncsCount*4) > len(FuncsSec.Data)): 408 | raise PEException("Exported functions count exceeds sections.") 409 | 410 | # Read in all addresses. 411 | if (FuncsCount == 1): 412 | Addrs = [[GetDword(FuncsSec.Data, FuncsAddrsVA, 1), 0]] 413 | else: 414 | Addrs = [[i, 0] for i in GetDword(FuncsSec.Data, FuncsAddrsVA, FuncsCount)] 415 | 416 | if ((NamesAddrsVA + NamesCount*4) > len(NamesSec.Data)): 417 | raise PEException("Exported functions count exceeds sections.") 418 | 419 | # Read in these functions' names. 420 | for i in xrange(NamesCount): 421 | NameVA = GetDword(NamesSec.Data, NamesAddrsVA + i*4) 422 | NameSec = self.GetSectionByVA(NameVA) 423 | if (NameSec == None): 424 | raise PEException("Cannot find exported function name.") 425 | 426 | NameVA -= NameSec.VA 427 | Name = ReadAsciiz(NameSec.Data[NameVA:]) 428 | if (Name == None): 429 | raise PEException("Cannot read exported function name.") 430 | 431 | Names.append(Name) 432 | 433 | if ((OrdsAddrsVA + NamesCount*2) > len(OrdsSec.Data)): 434 | raise PEException("Exported functions count exceeds sections.") 435 | 436 | # Read the ordinals of these functions. 437 | if (NamesCount == 1): 438 | Ords = [GetShort(OrdsSec.Data, OrdsAddrsVA, 1)] 439 | else: 440 | Ords = GetShort(OrdsSec.Data, OrdsAddrsVA, NamesCount) 441 | 442 | # [name, ord] 443 | # Mark the functions which have a name and an ordinal. 444 | for i in enumerate(Names): 445 | j = Ords[i[0]] 446 | FuncNames.append(Export(i[1], j + OrdinalBase, Addrs[j][0])) 447 | # Mark exported function address as used. 448 | Addrs[j][1] = 1 449 | 450 | # Add functions that exported by ordinals only by exclusion. 451 | # This is a bit tricky, that's why we had a flag indicating whether the function was already ready to go... 452 | for i in [i for i in enumerate(Addrs) if i[1][1] != 1]: 453 | try: 454 | j = i[0] + OrdinalBase 455 | FuncNames.append(Export("", j, Addrs[j][0])) 456 | except: 457 | # i dont care about unnamed exports 458 | pass 459 | return FuncNames 460 | 461 | def GetRelocations(self): 462 | VA = self.RelocationsOff 463 | Size = self.RelocationsSize 464 | Sections = self.Sections 465 | # Read a page header. 466 | RelocatedAddrs = [] 467 | RelocSec = self.GetSectionByVA(VA) 468 | if (RelocSec == None): 469 | raise PEException("Cannot find base relocations section.") 470 | 471 | VA -= RelocSec.VA 472 | Index = 0 473 | while VA < Size: 474 | if (VA + 8 > Size): 475 | raise PEException("Not enough bytes to read relocations header.") 476 | # Read a page header. 477 | PageVA, RelocCount = GetDword(RelocSec.Data, VA, 2) 478 | Index += 1 479 | # Calc the number of relocations in this page. 480 | RelocCount -= 8 #exclude header 481 | if (VA + RelocCount > Size): 482 | raise PEException("Relocations count exceeds section.") 483 | 484 | VA += 8 485 | # Read in the addresses. 486 | for i in xrange(RelocCount/2): 487 | # Is it the end of array? Halt. 488 | if (VA >= Size): 489 | break 490 | TypeOffset = GetShort(RelocSec.Data, VA) 491 | # Is it the end of array? Halt. 492 | if (TypeOffset == 0): 493 | # Skip ABSOLUTE relocation type, move to next page. 494 | VA += RelocCount-i*2 495 | break 496 | VA += 2 497 | # Extract the type and offset from the word sized variable. 498 | Type = TypeOffset >> 12 499 | Offset = TypeOffset & 0xfff 500 | # Executables of PE type support only HIGHLOW type. 501 | if (Type == 3): 502 | PageSec = self.GetSectionByVA(PageVA + Offset) 503 | if (PageSec != None): 504 | # Append the data into the list as a Relocation class with the original relocated dword as well. 505 | RelocatedAddrs.append(Relocation(PageSec, PageVA, Offset, GetDword(PageSec.Data, PageVA + Offset - PageSec.VA))) 506 | elif (Type == 10): 507 | PageSec = self.GetSectionByVA(PageVA + Offset) 508 | if (PageSec != None): 509 | # Append the data into the list as a Relocation class with the original relocated qword as well. 510 | RelocatedAddrs.append(Relocation(PageSec, PageVA, Offset, GetQword(PageSec.Data, PageVA + Offset - PageSec.VA))) 511 | 512 | return RelocatedAddrs 513 | 514 | def ApplyRelocations(self): 515 | # Don't assume VA's are sorted properly. 516 | # We sort them out, because the algorithm for rebuilding the relocated section assumes 517 | # the relocation's addresses are from low to high addresses. 518 | self._Relocs.sort(SortRelocCallback) 519 | # Hold relocations by their sections. 520 | Secs = [[] for i in xrange(len(self.Sections))] 521 | # Sort them into their corresponding section. 522 | for i in self._Relocs: 523 | Secs[i.Section.Index].append(i) 524 | # Eliminate empty sections (which don't have relocations). 525 | Secs = filter(len, Secs) 526 | # Now for every section apply its changes 527 | for Sec in Secs: 528 | OldVA = 0 529 | # TmpBin will be used for efficiency in order to apply the patch to Python buffers. 530 | TmpBin = cStringIO.StringIO() 531 | # Get its section. 532 | SecVA = Sec[0].Section.VA 533 | PageSec = self.GetSectionByVA(SecVA) 534 | # Patches the required addresses. 535 | for i in Sec: 536 | # Copy the buffer till this address. 537 | buf = PageSec.Data[OldVA:i.RelocAddr - SecVA] 538 | if len(buf): 539 | TmpBin.write(buf) 540 | if (self.OptMagic == MAGIC_PE32): 541 | # Calc next offset. 542 | OldVA = i.RelocAddr - SecVA + 4 543 | # Append the new relocated value. 544 | TmpBin.write(struct.pack(">" % (self.Name, self.VA, self.Flags, len(self.Data)) 568 | 569 | class Export(object): 570 | def __init__(self, fname, ord, va): 571 | # The VA in which the function sits. 572 | self.VA = va 573 | # Function name (if exists). 574 | if len(fname) == 0 or fname == None: 575 | self.Name = "" 576 | self.Name = fname 577 | # Function ordinal. 578 | self.Ordinal = ord 579 | def __repr__(self): 580 | return "" % (self.Name, self.Ordinal, self.VA) 581 | 582 | class Import(object): 583 | def __init__(self, va, fname, modname): 584 | self.VA = va 585 | self.Name = fname 586 | self.ModuleName = modname 587 | def __repr__(self): 588 | return "" % (self.ModuleName[:self.ModuleName.rfind('.')], self.Name, self.VA) 589 | 590 | class Relocation(object): 591 | def __init__(self, Section, PageVA, Offset, OriginalValue): 592 | self.Section = Section 593 | self.PageVA = PageVA 594 | self.Offset = Offset 595 | # Original value of the dword which isn't relocated yet. 596 | self.OriginalValue = OriginalValue 597 | def _get_reloc_address(self): 598 | return self.PageVA + self.Offset 599 | def __repr__(self): 600 | return "" % (self.RelocAddr, self.OriginalValue) 601 | RelocAddr = property(_get_reloc_address) 602 | 603 | def SortRelocCallback(R1, R2): 604 | " Helper function for sorting all relocations' addresses. " 605 | res = R1.RelocAddr - R2.RelocAddr 606 | if res < 0: 607 | return -1 608 | elif res > 0: 609 | return 1 610 | return 0 611 | 612 | def main(): 613 | if (len(sys.argv) != 2) and (len(sys.argv) != 3): 614 | print os.path.split(sys.argv[0])[1] + " " 615 | return None 616 | filename = sys.argv[1] 617 | NewImageBase = None 618 | if (len(sys.argv) == 3): 619 | NewImageBase = int(sys.argv[2], 16) 620 | PEObj = PEFile(filename, NewImageBase) 621 | print "diSlib, http://ragestorm.net/distorm/\n" 622 | print "Image Base: 0x%08x, Code Size: 0x%x" % (PEObj.ImageBase, PEObj.CodeSize) 623 | print "Entry Point RVA: %08x" % PEObj.EntryPoint 624 | print "Sections:" 625 | for i in PEObj.Sections[:-1]: 626 | print "%d.Name: %s, VA: %x, Size: %x, Flags: %x" % (i.Index + 1, i.Name, i.VA, i.Size, i.Flags) 627 | if PEObj.Imports: 628 | print "Imports:" 629 | for i in PEObj.Imports: 630 | print i 631 | try: 632 | if PEObj.Exports: 633 | print "Exports:" 634 | for i in PEObj.Exports: 635 | print i 636 | except: 637 | # Ignore corrupted exports. 638 | pass 639 | if PEObj.Relocs: 640 | print "Relocations:" 641 | for i in PEObj.Relocs: 642 | print i 643 | 644 | try: 645 | # If diStorm isn't available, we won't disassemble anything. 646 | import distorm3 647 | 648 | DecodeType = distorm3.Decode32Bits 649 | if (PEObj.MachineType == IMAGE_FILE_MACHINE_AMD64): 650 | DecodeType = distorm3.Decode64Bits 651 | 652 | # Find code section and disassemble entry point routine. 653 | TextSec = PEObj.GetSectionByVA(PEObj.EntryPoint) 654 | if TextSec == None: 655 | return 656 | l = distorm3.Decode(PEObj.ImageBase + PEObj.EntryPoint, TextSec.Data[PEObj.EntryPoint - TextSec.VA:][:4*1024], DecodeType) 657 | for i in l: 658 | print "0x%08x (%02x) %-20s %s" % (i[0], i[1], i[3], i[2]) 659 | if ((i[2][:3] == "RET") or (i[2] == "INT 3") or (i[2][:3] == "JMP")): 660 | break 661 | except: 662 | pass 663 | 664 | if (__name__ == "__main__"): 665 | main() 666 | -------------------------------------------------------------------------------- /file_details.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | import win32com.client 4 | 5 | # https://technet.microsoft.com/library/ee176615.aspx 6 | 7 | 8 | def getFileDetails(abs_path): 9 | details = {} 10 | base_dir = os.path.dirname(abs_path) 11 | file_name = os.path.basename(abs_path) 12 | 13 | if base_dir == "." or base_dir == "": 14 | base_dir = os.getcwd() 15 | 16 | sh = win32com.client.Dispatch("Shell.Application") 17 | ns = sh.NameSpace(base_dir) 18 | 19 | try: 20 | idx = ns.ParseName(file_name) 21 | except: 22 | return None 23 | 24 | try: 25 | details["vendor"] = ns.GetDetailsOf(idx, 33) 26 | details["description"] = ns.GetDetailsOf(idx, 34) 27 | except: 28 | return None 29 | 30 | return details 31 | -------------------------------------------------------------------------------- /hackers-grep.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | ''' 4 | 5 | hackers-grep.py: Search for regex patterns in the public symbols, 6 | exports, imports, and strings of PE files 7 | 8 | -Cody 9 | 10 | ''' 11 | 12 | import sys, os, re, time, string, dislib 13 | import logging 14 | import threading 15 | from multiprocessing import current_process, Process, Queue, Lock, Pool 16 | from optparse import OptionParser 17 | 18 | try: 19 | import win32com 20 | except ImportError: 21 | print "[!] Please install pywin32 (http://sourceforge.net/projects/pywin32/)" 22 | sys.exit(1) 23 | 24 | try: 25 | import comtypes 26 | except ImportError: 27 | print "[!] Please install comtypes (https://pypi.python.org/pypi/comtypes)" 28 | sys.exit(1) 29 | 30 | import dislib 31 | from pdbsymbols import PdbFile 32 | from file_details import getFileDetails 33 | 34 | LOGGER_NAME = "hackers_grep" 35 | g_logger = None 36 | g_log_level = logging.INFO 37 | 38 | TYPE_IMPORT = 1 39 | TYPE_EXPORT = 2 40 | 41 | def log_init(log_level): 42 | global g_logger 43 | global g_log_level 44 | 45 | g_log_level = log_level 46 | 47 | g_logger = logging.getLogger(LOGGER_NAME) 48 | g_logger.setLevel(log_level) 49 | 50 | log_handler = logging.StreamHandler() 51 | log_handler.setLevel(log_level) 52 | log_format = logging.Formatter("%(levelname)s - %(message)s") 53 | log_handler.setFormatter(log_format) 54 | 55 | g_logger.addHandler(log_handler) 56 | 57 | def debug(m): 58 | if g_logger == None: 59 | log_init() 60 | g_logger.debug(m) 61 | 62 | def error(m): 63 | if g_logger == None: 64 | log_init() 65 | g_logger.error(m) 66 | 67 | def info(m): 68 | if g_logger == None: 69 | log_init() 70 | g_logger.info(m) 71 | 72 | def get_public_symbols(pdb_file): 73 | symbols = [] 74 | p = PdbFile(pdb_file) 75 | p.setup() 76 | p.get_public() 77 | for sym in p.public: 78 | symbols.append(sym.undecorated_name) 79 | return symbols 80 | 81 | def walk_dir(top, files, max_depth=1): 82 | if os.path.exists(top) == False: 83 | raise Exception, "Unknown directory %s" % top 84 | if max_depth == 0: 85 | return False 86 | else: 87 | max_depth -= 1 88 | for dirpath, dirnames, filenames in os.walk(top): 89 | for f in filenames: 90 | files.append(os.path.join(top, f)) 91 | if max_depth > 0: 92 | rc = True 93 | for d in dirnames: 94 | try: 95 | rc = walk_dir(os.path.join(top, d), files, max_depth=max_depth) 96 | except: 97 | pass 98 | if rc == False: 99 | break 100 | return True 101 | 102 | def make_cstring(chars, nonprintable=False, min_length=3): 103 | if len(chars) >= min_length: 104 | if nonprintable == False: 105 | chars = ''.join(s for s in chars if s in string.printable) 106 | return chars 107 | 108 | return False 109 | 110 | def make_wstring(chars, nonprintable=False, min_length=3): 111 | c = chars.replace("\x00", "") 112 | if len(c) >= min_length: 113 | if nonprintable == False: 114 | c = ''.join(s for s in c if s in string.printable) 115 | return c 116 | 117 | return False 118 | 119 | # Matches the string regex to files exports 120 | def match_exports(pef, string_regex): 121 | sre = re.compile(string_regex, re.I) 122 | 123 | strings = [] 124 | if pef.Exports: 125 | for i in pef.Exports: 126 | if sre.match(i.Name): 127 | strings.append("!%s" % (i.Name)) 128 | return strings 129 | 130 | def get_dll_search_paths(abs_path, module_name): 131 | search_dirs = [] 132 | 133 | # https://msdn.microsoft.com/en-us/library/7d83bc18.aspx 134 | # module cwd 135 | search_dirs.append(os.path.dirname(abs_path)) 136 | 137 | # Windows system dir/root/path 138 | for p in os.environ["PATH"].split(';'): 139 | search_dirs.append(p) 140 | 141 | return search_dirs 142 | 143 | 144 | def get_pe(abs_path, module_name): 145 | search_dirs = get_dll_search_paths(abs_path, module_name) 146 | 147 | for dll_path in search_dirs: 148 | ap = os.path.join(dll_path, module_name) 149 | if not os.path.exists(ap): 150 | continue 151 | try: 152 | pe = dislib.PEFile(ap) 153 | pe.LoadExports() 154 | return pe 155 | except Exception, exc: 156 | return None 157 | return None 158 | 159 | # TODO: dislib doesnt understand delayed imports and has a bug loading some dlls ymmv 160 | imap = {} 161 | 162 | # Matches the string regex to files imports 163 | def match_imports(pef, pe_path, string_regex): 164 | global imap 165 | sre = re.compile(string_regex, re.I) 166 | 167 | strings = [] 168 | if pef.Imports: 169 | for i in pef.Imports: 170 | if i.ModuleName not in imap.keys(): 171 | imap[i.ModuleName] = {} 172 | im = get_pe(pe_path, i.ModuleName) 173 | imap[i.ModuleName]['im'] = im 174 | imap[i.ModuleName]['ords'] = {} 175 | if not im: 176 | debug("Cant find %s" % i.ModuleName) 177 | else: 178 | if im.Exports: 179 | for x in im.Exports: 180 | if isinstance(x.Ordinal, int) and x.Name != "": 181 | imap[i.ModuleName]['ords'][x.Ordinal] = x.Name 182 | 183 | if "Ord_" in i.Name: 184 | if imap[i.ModuleName]['im']: 185 | ordinal = int(i.Name.split('x')[1], 16) 186 | if ordinal in imap[i.ModuleName]['ords']: 187 | name = imap[i.ModuleName]['ords'][ordinal] 188 | if sre.match(name) or sre.match(i.ModuleName): 189 | strings.append("%s!%s" % (i.ModuleName, name)) 190 | elif sre.match(i.Name) or sre.match(i.ModuleName): 191 | strings.append("%s!%s" % (i.ModuleName, i.Name)) 192 | return strings 193 | 194 | def GetSectionByOrig(pe, ov): 195 | for s in pe.Sections: 196 | if ov >= (pe.ImageBase + s.VA) and ov <= (pe.ImageBase + s.VA + s.Size): 197 | return s 198 | return False 199 | 200 | def dump_section(s): 201 | print "name:\t%s" % s.Name 202 | print "va:\t%x" % s.VA 203 | print "size:\t%x" % s.Size 204 | print "data size:\t%x" % len(s.Data) 205 | 206 | def match_string(pef, pe_path, string_regex, import_filter=None, export_filter=None, symbols=False, symbol_path=None, min_string=3, max_string=1024): 207 | sre = re.compile(string_regex, re.I) 208 | 209 | if symbols: 210 | if not symbol_path: 211 | info("Asking to search symbols with no path, skipping symbol search") 212 | symbols = False 213 | elif os.path.isdir(symbol_path) == False: 214 | info("Symbol path %s is not a directory, skipping symbol search" % symbol_path) 215 | symbols = False 216 | 217 | c_strings = [] 218 | w_strings = [] 219 | 220 | # crack any strings in the relocations 221 | if pef.Relocs: 222 | relocs = pef.Relocs 223 | sec_dict = {} 224 | # pre pop for speed 225 | for s in pef.Sections: 226 | sec_dict[s.Name] = s 227 | seen_it = {} 228 | for r in relocs: 229 | offset = r.Offset 230 | original_value = r.OriginalValue 231 | section = r.Section 232 | addr = r.RelocAddr 233 | 234 | try: 235 | seen_it[original_value] += 1 236 | continue 237 | except: 238 | seen_it[original_value] = 1 239 | 240 | s = sec_dict[section.Name] 241 | str_offset = original_value - (pef.ImageBase + s.VA) 242 | if str_offset >= s.Size or str_offset < 0: 243 | # this is a hack 244 | real_sec = GetSectionByOrig(pef, original_value) 245 | if real_sec == False: 246 | continue 247 | if real_sec.Name != section.Name: 248 | s = sec_dict[real_sec.Name] 249 | str_offset = original_value - (pef.ImageBase + s.VA) 250 | if str_offset >= s.Size or str_offset < 0: 251 | continue 252 | else: 253 | continue 254 | 255 | # skip idata 256 | if s.Name in [".idata"]: 257 | continue 258 | 259 | try: 260 | sec_data = s.Data 261 | if sec_data[str_offset] in string.printable: 262 | # find null 263 | for x in xrange(16, max_string, 16): 264 | s = sec_data[str_offset:str_offset+x] 265 | n = s.find("\x00") 266 | if n == -1: 267 | continue 268 | if n <= min_string: 269 | # try wchar 270 | n = s.find("\x00\x00") 271 | if n == -1: 272 | continue 273 | else: 274 | s = sec_data[str_offset:str_offset+n].replace("\x00", "") 275 | w_strings.append(s) 276 | break 277 | else: 278 | s = sec_data[str_offset:str_offset+n] 279 | c_strings.append(s) 280 | break 281 | except IndexError: 282 | error("Index error in sec_data str_offset %x" % str_offset) 283 | break 284 | 285 | # Crack any strings from .text or .data or .rdata 286 | # XXX: refactor plz 287 | for sec in pef.Sections: 288 | if "text" in sec.Name or "data" in sec.Name: 289 | nulls = sec.Data.split("\x00") 290 | 291 | for n in nulls: 292 | s = make_cstring(n, nonprintable=False) 293 | if s: 294 | c_strings.append(s) 295 | 296 | nulls = sec.Data.split("\x00\x00") 297 | for n in nulls: 298 | s = make_wstring(n, nonprintable=False) 299 | if s: 300 | w_strings.append(s) 301 | 302 | # If we are also searching symbols lets parse those out 303 | symbol_strings = [] 304 | if symbols: 305 | pdb_files = [] 306 | public_symbols = None 307 | pdb_search_path = os.path.join(symbol_path, os.path.basename(pe_path).split(os.path.extsep)[0]) + os.path.extsep + "pdb" 308 | debug("Getting symbols from %s" % pdb_search_path) 309 | if os.path.exists(pdb_search_path): 310 | rc = walk_dir(pdb_search_path, pdb_files, max_depth=2) 311 | if rc == True: 312 | for p in pdb_files: 313 | if os.path.exists(p): 314 | break 315 | public_symbols = get_public_symbols(p) 316 | if public_symbols: 317 | for sym in public_symbols: 318 | symbol_strings.append(sym) 319 | else: 320 | debug("Invalid symbol path %s ignoring search" % pdb_search_path) 321 | 322 | match_strings = [] 323 | # check c strings 324 | for s in c_strings: 325 | if sre.match(s): 326 | match_strings.append(unicode(s, errors="ignore")) 327 | # check wchar strings 328 | for s in w_strings: 329 | if sre.match(s): 330 | match_strings.append(unicode(s, errors="ignore")) 331 | 332 | # check symbol strings 333 | for s in symbol_strings: 334 | if sre.match(s): 335 | match_strings.append(s) 336 | 337 | return match_strings 338 | 339 | def dump_match(file_path, match_string, justify=None, show_info=False): 340 | output = "" 341 | if justify: 342 | for x in range(justify - len(file_path)): file_path += " " 343 | 344 | if show_info: 345 | (mode, ino, dev, nlink, uid, gid, size, atime, mtime, ctime) = os.stat(file_path) 346 | if size < 1024: 347 | size_o = "B" 348 | else: 349 | size_o = "KB" 350 | size /= 1024 351 | details = getFileDetails(file_path) 352 | if details == None: 353 | vendor = "None" 354 | description = "None" 355 | else: 356 | vendor = details["vendor"] 357 | description = details["description"] 358 | 359 | output = u"%s [%5d %s] [%s] [%s] [%s] " % (file_path, size, size_o, time.strftime("%m/%d/%Y", time.gmtime(mtime)), vendor, description) 360 | else: 361 | output = u"%s " % (file_path) 362 | 363 | output += match_string 364 | 365 | try: 366 | print output.encode("ascii", errors="backslashreplace") 367 | except UnicodeEncodeError, e: 368 | error("Unicode exception printing match for %s" % file_path) 369 | 370 | def file_grep(pe_path, string_regex, log_level, att=False, exports_only=False, imports_only=False, import_filter=None, export_filter=None, symbols=False, symbol_path=None): 371 | # open PE file 372 | try: 373 | pef = dislib.PEFile(pe_path) 374 | except dislib.PEException, e: 375 | debug("%s: %s" % (pe_path, e)) 376 | return False 377 | 378 | # apply filters 379 | ire = None 380 | ere = None 381 | 382 | if import_filter: 383 | ire = re.compile(import_filter, re.I) 384 | if export_filter: 385 | ere = re.compile(export_filter, re.I) 386 | 387 | if ire: 388 | bail = True 389 | if pef.Imports: 390 | for i in pef.Imports: 391 | if ire.match(i.Name) or ire.match(i.ModuleName): 392 | bail = False 393 | if bail: 394 | return False 395 | 396 | if ere: 397 | bail = True 398 | if pef.Exports: 399 | for e in pef.Exports: 400 | if ere.match(e.Name): 401 | bail = False 402 | if bail: 403 | return False 404 | 405 | matched_strings = [] 406 | if att: 407 | matched = match_exports(pef, string_regex) 408 | if matched: 409 | matched_strings += matched 410 | 411 | matched = match_imports(pef, pe_path, string_regex) 412 | if matched: 413 | matched_strings += matched 414 | 415 | matched = match_string(pef, pe_path, string_regex, import_filter=import_filter, export_filter=export_filter, symbols=symbols, symbol_path=symbol_path) 416 | if matched: 417 | matched_strings += matched 418 | 419 | elif exports_only: 420 | matched_strings = match_exports(pef, string_regex) 421 | elif imports_only: 422 | matched_strings = match_imports(pef, pe_path, string_regex) 423 | else: 424 | matched_strings = match_string(pef, pe_path, string_regex, import_filter=import_filter, export_filter=export_filter, symbols=symbols, symbol_path=symbol_path) 425 | return matched_strings 426 | 427 | def run_grep(abs_path, string_regex, log_level, **kwargs): 428 | log_init(log_level) 429 | 430 | matched_strings = [] 431 | ms = file_grep(abs_path, string_regex, log_level, **kwargs) 432 | if ms: 433 | for matched_string in ms: 434 | matched_strings.append((abs_path, matched_string)) 435 | else: 436 | return None 437 | return matched_strings 438 | 439 | 440 | # If using this on 64 bit windows c:\windows\system32 is c:\windows\syswow64 in 32 bit python due 441 | # to the WOW64 file system redirector 442 | if __name__ == '__main__': 443 | # parse options 444 | usage = "usage: %prog [options] " 445 | parser = OptionParser(usage=usage) 446 | parser.add_option("-d", "--max-depth", dest="max_depth", type="int", default=1, help="Maximum directory recursion depth (default: 1)") 447 | parser.add_option("-x", "--exports-only", dest="exports_only", action="store_true", default=False, help="Only search Export section strings") 448 | parser.add_option("-n", "--imports-only", dest="imports_only", action="store_true", default=False, help="Only search Import section strings") 449 | parser.add_option("-a", "--all-the-things", dest="att", action="store_true", default=False, help="Search strings, import, exports") 450 | parser.add_option("-s", "--symbols", dest="symbols", action="store_true", default=False, help="Include symbols in search") 451 | parser.add_option("-p", "--symbol-path", dest="symbol_path", type="str", default=r'C:\Windows\Symbols', help="Symbol path (default: C:\Windows\Symbols)") 452 | parser.add_option("-e", "--export-filter", dest="export_filter", help="Search modules matching this Export regex") 453 | parser.add_option("-i", "--import-filter", dest="import_filter", help="Search modules matching this Import regex") 454 | parser.add_option("-f", "--show-info", dest="show_info", action="store_true", default=False, help="Display file details size and modification time") 455 | parser.add_option("-v", "--verbose", dest="verbose", action="store_true", default=False, help="Verbose output") 456 | #parser.add_option("-h", "--help") 457 | (options, args) = parser.parse_args() 458 | 459 | try: 460 | directory = args[0] 461 | file_regex = args[1] 462 | string_regex = args[2] 463 | except IndexError: 464 | parser.print_help() 465 | sys.exit(1) 466 | 467 | # set up logging 468 | log_level = logging.INFO 469 | if options.verbose: 470 | log_level = logging.DEBUG 471 | 472 | log_init(log_level) 473 | 474 | if os.path.isdir(directory) == False: 475 | raise Exception, "%s not a directory" % directory 476 | 477 | exports_only = False 478 | if options.exports_only: 479 | exports_only = options.exports_only 480 | 481 | imports_only = False 482 | if options.imports_only: 483 | imports_only = options.imports_only 484 | 485 | if exports_only and imports_only: 486 | error("Options --exports-only and --imports-only are mutually exclusive") 487 | parser.print_help() 488 | sys.exit(1) 489 | 490 | symbols = False 491 | if options.symbols: 492 | symbols = options.symbols 493 | 494 | symbol_path = None 495 | if options.symbol_path: 496 | symbol_path = options.symbol_path 497 | 498 | show_info = False 499 | if options.show_info: 500 | show_info = options.show_info 501 | 502 | att = False 503 | if options.att: 504 | att = options.att 505 | 506 | stime = int(time.time()) 507 | 508 | depth = 1 509 | if options.max_depth: 510 | depth = options.max_depth 511 | 512 | files = [] 513 | walk_dir(directory, files, max_depth=depth) 514 | 515 | try: 516 | fre = re.compile(file_regex) 517 | except Exception, e: 518 | error("Invalid File Regex") 519 | error("%s" % str(e)) 520 | sys.exit(1) 521 | 522 | file_list = [] 523 | for f in files: 524 | abs_filename = os.path.basename(f) 525 | if fre.match(abs_filename): 526 | debug("Adding file %s" % f) 527 | file_list.append(f) 528 | 529 | debug("Starting workers") 530 | pool = Pool() 531 | results = [] 532 | results = [pool.apply_async(run_grep, (f, string_regex, log_level), 533 | {'att': att, 534 | 'exports_only': exports_only, 535 | 'imports_only': imports_only, 536 | 'import_filter': options.import_filter, 537 | 'export_filter': options.export_filter, 538 | 'symbols': symbols, 539 | 'symbol_path': symbol_path}) for f in file_list] 540 | 541 | debug("Checking %d results" % len(results)) 542 | matches = [] 543 | while len(results) > 0: 544 | for result in results: 545 | if result.ready(): 546 | m = result.get() 547 | if m: 548 | matches += m 549 | results.remove(result) 550 | time.sleep(.1) 551 | 552 | 553 | debug("Processing matches") 554 | 555 | # get max file path length to pretty up the text 556 | adjust = 0 557 | for (f, s) in matches: 558 | if len(f) > adjust: 559 | adjust = len(f) 560 | 561 | for (f, s) in matches: 562 | dump_match(f, s, justify=adjust, show_info=show_info) 563 | 564 | etime = int(time.time()) 565 | debug("Finished in %d seconds" % (etime - stime)) 566 | 567 | -------------------------------------------------------------------------------- /msdia80.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codypierce/hackers-grep/2f6053217af64a5fff0389fa6a0b5c6a863b2aa9/msdia80.dll -------------------------------------------------------------------------------- /pdbsymbols.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # pdbsymbols.py modified from pdbdump 4 | # Copyright (C) 2009 https://code.google.com/p/pdbdump/ 5 | 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program. If not, see . 18 | 19 | import sys, os 20 | import comtypes 21 | import comtypes.client 22 | import winnt 23 | 24 | msdia = comtypes.client.GetModule( r'msdia80.dll' ) 25 | 26 | from comtypes.gen.Dia2Lib import * 27 | 28 | PTRSIZE = 4 # sizeof(void*)/sizeof(char) 29 | LOCATION_STR=('Null', 'Static', 'TLS', 'RegRel', 'ThisRel', 'Enregistered', 'BitField', 'Slot', 'IlRel', 'MetaData', 'Constant') 30 | 31 | SymTagNull, SymTagExe, SymTagCompiland, SymTagCompilandDetails, \ 32 | SymTagCompilandEnv, SymTagFunction, SymTagBlock, SymTagData, \ 33 | SymTagAnnotation, SymTagLabel, SymTagPublicSymbol, SymTagUDT, \ 34 | SymTagEnum, SymTagFunctionType, SymTagPointerType, SymTagArrayType, \ 35 | SymTagBaseType, SymTagTypedef, SymTagBaseClass, SymTagFriend, \ 36 | SymTagFunctionArgType, SymTagFuncDebugStart, SymTagFuncDebugEnd, SymTagUsingNamespace, \ 37 | SymTagVTableShape, SymTagVTable, SymTagCustom, SymTagThunk, \ 38 | SymTagCustomType, SymTagManagedType, SymTagDimension, SymTagMax = range(32) 39 | SYMTAG_STR =('Null', 'Exe', 'Compiland', 'CompilandDetails', 'CompilandEnv', 'Function', 40 | 'Block', 'Data', 'Annotation', 'Label', 'PublicSymbol', 'UDT', 'Enum', 41 | 'FunctionType', 'PointerType', 'ArrayType', 'BaseType', 'Typedef', 42 | 'BaseClass', 'Friend', 'FunctionArgType', 'FuncDebugStart', 'FuncDebugEnd', 43 | 'UsingNamespace', 'VTableShape', 'VTable', 'Custom', 'Thunk', 'CustomType', 44 | 'ManagedType', 'Dimension') 45 | 46 | DataIsUnknown, DataIsLocal, DataIsStaticLocal, DataIsParam, \ 47 | DataIsObjectPtr, DataIsFileStatic, DataIsGlobal, DataIsMember, \ 48 | DataIsStaticMember, DataIsConstant = range(10) 49 | DATAKIND_STR=('Unknown', 'Local', 'StaticLocal', 'Param', 'ObjectPtr', 'FileStatic', 'Global', 'Member', 'StaticMember', 'Constant') 50 | 51 | UdtStruct, UdtClass, UdtUnion = range(3) 52 | UDTKIND_STR = ('struct', 'class', 'union') 53 | 54 | LocIsNull, LocIsStatic, LocIsTLS, LocIsRegRel, LocIsThisRel, LocIsEnregistered, \ 55 | LocIsBitField, LocIsSlot, LocIsIlRel, LocInMetaData, LocIsConstant, LocTypeMax = range(12) 56 | 57 | btNoType = 0 58 | btVoid = 1 59 | btChar = 2 60 | btWChar = 3 61 | btInt = 6 62 | btUInt = 7 63 | btFloat = 8 64 | btBCD = 9 65 | btBool = 10 66 | btLong = 13 67 | btULong = 14 68 | btCurrency = 25 69 | btDate = 26 70 | btVariant = 27 71 | btComplex = 28 72 | btBit = 29 73 | btBSTR = 30 74 | btHresult = 31 75 | 76 | 77 | CV_ARM_R0 = 10 78 | CV_ARM_R1 = 11 79 | CV_ARM_R2 = 12 80 | CV_ARM_R3 = 13 81 | CV_ARM_R4 = 14 82 | CV_ARM_R5 = 15 83 | CV_ARM_R6 = 16 84 | CV_ARM_R7 = 17 85 | CV_ARM_R8 = 18 86 | CV_ARM_R9 = 19 87 | CV_ARM_R10 = 20 88 | CV_ARM_R11 = 21 # Frame pointer, if allocated 89 | CV_ARM_R12 = 22 90 | CV_ARM_SP = 23 # Stack pointer 91 | CV_ARM_LR = 24 # Link Register 92 | CV_ARM_PC = 25 # Program counter 93 | CV_ARM_CPSR = 26 # Current program status register 94 | 95 | CV_REG_EAX = 17 96 | CV_REG_ECX = 18 97 | CV_REG_EDX = 19 98 | CV_REG_EBX = 20 99 | CV_REG_ESP = 21 100 | CV_REG_EBP = 22 101 | CV_REG_ESI = 23 102 | CV_REG_EDI = 24 103 | CV_REG_EDXEAX = 212 104 | 105 | REGS_ARM={ CV_ARM_R0 :"r0", CV_ARM_R1 :"r1", CV_ARM_R2 :"r2", CV_ARM_R3 :"r3", 106 | CV_ARM_R4 :"r4", CV_ARM_R5 :"r5", CV_ARM_R6 :"r6", CV_ARM_R7 :"r7", 107 | CV_ARM_R8 :"r8", CV_ARM_R9 :"r9", CV_ARM_R10 :"r10", CV_ARM_R11 :"r11", 108 | CV_ARM_R12 :"r12", CV_ARM_SP :"sp", CV_ARM_LR :"lr", CV_ARM_PC :"pc", 109 | CV_ARM_CPSR :"cpsr"} 110 | REGS_386={ CV_REG_EAX: "eax", CV_REG_ECX: "ecx", CV_REG_EDX: "edx", CV_REG_EBX: "ebx", 111 | CV_REG_ESP: "esp", CV_REG_EBP: "ebp", CV_REG_ESI: "esi", CV_REG_EDI: "edi", 112 | CV_REG_EDXEAX: "edx:eax",} 113 | REGS_X64={} 114 | REG_NAMES={ 332:REGS_386, 448:REGS_ARM, 450:REGS_ARM, 512:REGS_X64} 115 | 116 | 117 | CV_CALL_NEAR_C = 0x00 # near right to left push, caller pops stack 118 | CV_CALL_NEAR_FAST = 0x04 # near left to right push with regs, callee pops stack 119 | CV_CALL_NEAR_STD = 0x07 # near standard call 120 | CV_CALL_NEAR_SYS = 0x09 # near sys call 121 | CV_CALL_THISCALL = 0x0b # this call (this passed in register) 122 | CALLCONV_STR = { 123 | CV_CALL_NEAR_C: "__cdecl", 124 | CV_CALL_NEAR_FAST: "__fastcall", 125 | CV_CALL_NEAR_STD: "__stdcall", 126 | CV_CALL_NEAR_SYS: "__syscall", 127 | CV_CALL_THISCALL: "__thiscall", } 128 | 129 | 130 | nsNone = 0 131 | nsfCaseSensitive = 0x1 # apply a case sensitive match 132 | nsfCaseInsensitive = 0x2 # apply a case insensitive match 133 | nsfFNameExt = 0x4 # treat names as paths and apply a filename.ext match 134 | nsfRegularExpression = 0x8 # regular expression 135 | nsfUndecoratedName = 0x10 # applies only to symbols that have both undecorated and decorated names 136 | # predefined names for backward source compatibility 137 | nsCaseSensitive = nsfCaseSensitive # apply a case sensitive match 138 | nsCaseInsensitive = nsfCaseInsensitive # apply a case insensitive match 139 | nsFNameExt = nsfCaseInsensitive | nsfFNameExt # treat names as paths and apply a filename.ext match 140 | nsRegularExpression = nsfRegularExpression | nsfCaseSensitive # regular expression (using only '*' and '?') 141 | nsCaseInRegularExpression = nsfRegularExpression | nsfCaseInsensitive # case insensitive regular expression 142 | 143 | class SymPublic: 144 | def __init__(self): 145 | self.location = None 146 | self.size = None 147 | self.undecorated_name = None 148 | self.name = None 149 | 150 | class PdbFile: 151 | def __init__(self, pdb_file): 152 | self.pdb_file = pdb_file 153 | try: 154 | self.ds = comtypes.client.CreateObject( msdia.DiaSource ) 155 | except: 156 | os.system('regsvr32 /s msdia80.dll') 157 | self.ds = comtypes.client.CreateObject( msdia.DiaSource ) 158 | self.session = None 159 | self.public = [] 160 | 161 | def __find_children(self, parent, symTag=SymTagNull, name=None, compareFlags=nsNone): 162 | for sym in parent.findChildren(symTag, name, compareFlags): 163 | yield sym.QueryInterface(IDiaSymbol) 164 | 165 | def __format_location(self, sym): 166 | if sym.locationType==LocIsStatic: return "%08X" % sym.virtualAddresss 167 | elif sym.locationType==LocIsTLS: pass # sym.virtualAddress 168 | elif sym.locationType==LocIsRegRel: pass # (RegisterStr(sym), offsetstr(sym.offset)) 169 | elif sym.locationType==LocIsThisRel: pass # offsetstr(sym.offset) 170 | elif sym.locationType==LocIsEnregistered: pass # RegisterStr(sym) 171 | elif sym.locationType==LocIsBitField: pass # (sym.offset, sym.bitPosition, sym.length) 172 | elif sym.locationType==LocIsSlot: pass # (sym.slot) 173 | elif sym.locationType==LocIsIlRel: pass # sym.offset 174 | elif sym.locationType==LocInMetaData: pass # sym.token 175 | elif sym.locationType==LocIsConstant: pass # sym.value 176 | elif sym.locationType==LocIsNull: pass 177 | else: pass 178 | return None 179 | 180 | def __dump_symbol(self, sym): 181 | if sym.symTag==SymTagEnum: 182 | print "Asked to dump SymTagEnum" 183 | elif sym.symTag==SymTagTypedef: 184 | print "Asked to dump SymTagTypedef" 185 | elif sym.symTag==SymTagData: 186 | print "Asked to dump SymTagData" 187 | elif sym.symTag==SymTagFunction: 188 | print "Asked to dump SymTagFunction" 189 | elif sym.symTag==SymTagBaseClass: 190 | print "Asked to dump SymTagBaseClase" 191 | elif sym.symTag==SymTagUDT: 192 | print "Asked to dump SymTagUDT" 193 | elif sym.symTag==SymTagPublicSymbol: 194 | p = SymPublic() 195 | p.location = "%08X" % sym.virtualAddress 196 | p.size = sym.length 197 | p.undecorated_name = sym.undecoratedName 198 | p.name = sym.name 199 | return p 200 | else: 201 | o += '?ACHTUNG '+SYMTAG_STR[sym.symTag] 202 | return None 203 | 204 | def setup(self): 205 | if self.ds == None: 206 | print "[!] DiaSource not created" 207 | return False 208 | 209 | try: 210 | self.ds.loadDataFromPdb(self.pdb_file) 211 | except: 212 | #ds.loadDataForExe(arg, '', None) 213 | pass 214 | 215 | self.session = self.ds.openSession() 216 | 217 | #ses.loadAddress = 0x00400000 218 | 219 | def dump_children(self, scope, symTag): 220 | o = '' 221 | for sym in self.__find_children(scope, symTag): #, None, nsfUndecoratedName 222 | self.public.append(self.__dump_symbol(sym)) 223 | return o 224 | 225 | def dump_props(self, sym): 226 | for d in dir(sym): 227 | try: 228 | a = sym.__getattribute__(d) 229 | except: 230 | continue 231 | if isinstance(a, int): 232 | print d, a 233 | elif isinstance(a, long): 234 | print d, a 235 | elif isinstance(a, str): 236 | print d, a 237 | elif isinstance(a, unicode): 238 | print d, a 239 | 240 | def get_public(self): 241 | if not self.session: 242 | print "[!] No session" 243 | return False 244 | 245 | # DUMP ALL PUBLICS 246 | self.dump_children(self.session.globalScope, SymTagPublicSymbol) 247 | for sym in self.__find_children(self.session.globalScope, SymTagPublicSymbol): 248 | #self.dump_props(sym) 249 | self.public.append(self.__dump_symbol(sym)) 250 | 251 | 252 | if __name__=='__main__': 253 | arg = sys.argv[1] 254 | pf = PdbFile(arg) 255 | pf.setup() 256 | pf.get_public() 257 | for p in pf.public: 258 | print "%s: %10d | %s" % (p.location, p.size, p.undecorated_name) 259 | --------------------------------------------------------------------------------