Repository: vallejocc/Reverse-Engineering-Arsenal Branch: master Commit: e029ad79abef Files: 40 Total size: 144.9 KB Directory structure: gitextract_j6atbifn/ ├── .gitmodules ├── IDA/ │ ├── readme.md │ ├── set_symbols_for_addresses.py │ └── stack_strings_deobfuscator_1.py ├── README.md └── WinDbg/ ├── Readme.md ├── anti_antidebug_rdtsc.wdbg ├── break_each_new_process.wdbg ├── change_object_name.wdbg ├── change_process_name.wdbg ├── dump_injected_pe_rwemem.wdbg ├── dump_injected_pe_rwemem_fast.wdbg ├── dump_main_module.wdbg ├── dump_pe.wdbg ├── dump_process_symbols_to_file.wdbg ├── dump_process_symbols_to_file_preselect.wdbg ├── dump_process_symbols_to_file_preselect_curproc.wdbg ├── find_injected_pe_rmem.wdbg ├── find_injected_pe_rwemem.wdbg ├── find_injected_pe_search.wdbg ├── find_objects_by_name.wdbg ├── hang_exited_processes.wdbg ├── isx64.wdbg ├── lengthDisassembler.wdbg ├── load_code_to_kernel_memory.wdbg ├── load_swish.wdbg ├── log_processes.wdbg ├── monitoring_breakpoints.wdbg ├── pagein_range.wdbg ├── remove_device_acls.wdbg ├── search_bytes_all_processes.wdbg ├── search_bytes_target_process.wdbg ├── search_string_all_processes.wdbg ├── search_string_target_process.wdbg ├── secure_writemem.wdbg ├── show_address_info.wdbg ├── show_pe_headers.wdbg ├── show_proc_from_handle.wdbg ├── symbols.wdbg └── write_mem_dump.wdbg ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitmodules ================================================ [submodule "standalone/RevealPE"] path = standalone/RevealPE url = https://github.com/vallejocc/RevealPE.git [submodule "IDA/IdaDiscover"] path = IDA/IdaDiscover url = https://github.com/vallejocc/idaDiscover.git ================================================ FILE: IDA/readme.md ================================================ # set_symbols_for_addresses This scripts asks you for a file containing pairs address - symbol. It walks all segments searching for DWORDs matching the addresses of the given file of pairs address - symbols, and it will name the variable containing the address with the symbol name. This script is thought to be used together with the windbg script dump_process_symbols_to_file.wdbg. # stack_strings_deobfuscator_1 Some malware families construct strings into the stack, like this: mov dword ptr [ebp-18h], 61737376h ; vssa mov dword ptr [ebp-14h], 642E6970h ; pi.d mov word ptr [ebp-10h], 6C6Ch; ll In addition, i have found malware families permutating code (they split the code in portions and the mix these portions, adding jumps from a portion to the next one for getting the code being executed in the correct order), and constructing strings in stack, for example: loc_4751A4: nop mov dword ptr [ebp-18h], 61737376h ; vssa nop jmp loc_474E10 | v loc_474E10: nop mov dword ptr [ebp-14h], 642E6970h ; pi.d nop jmp loc_475532 | v loc_475532: mov word ptr [ebp-10h], 6C6Ch; ll jmp loc_4750C3 This script add coments at points of code where each part of the string is being reconstructed. In addition it tries to construct for each function the string being constructed into the function. For this purpose, it needs to follow basic blocks of each funcion in the same order that they are going to be executed (in this way the strings will be reconstructed in the same order in spite of the fact the code is permutated). When it constructs an string, the output is like this: "Function text constructed in stack: sub_474CA0 | vssapi.dllCreateVssBackupComponentsInternaVssFreeSnapshotPropertiesInternaupComponents@@@Zonents@@YGJPAPAVIVssBackreeSnapshotPropertiessBackupCompateVVssF?Cre" ================================================ FILE: IDA/set_symbols_for_addresses.py ================================================ ################################################ ## ## Author: Javier Vicente Vallejo ## Twitter: @vallejocc ## Web: http://www.vallejo.cc ## ################################################ # # This scripts asks you for a file containing pairs address - symbol. # # This script walks all segments searching for DWORDs matching the addresses of the given file of pairs address - symbols, # and it will name the variable containing the address with the symbol name. # # This script is thought to be used together with the windbg script dump_process_symbols_to_file.wdbg. # ################################################ import idaapi import idc import idautils import tkFileDialog ################################################ def binarySearch(alist, item): first = 0 last = len(alist)-1 found = False retval = 0 while first<=last and not found: midpoint = (first + last)//2 #algunos malware saltan al comienzo de la api mas algunas instrucciones, por ejemplo: #ADVAPI32!RegDeleteValueW: #77daedf1 8bff mov edi,edi #77daedf3 55 push ebp #77daedf4 8bec mov ebp,esp #77daedf6 83ec0c sub esp,0Ch <- malware salta aqui y ejecuta el push ebp, mov ebp, esp en su codigo #Por eso no comparamos la direccion dada con la de la lista, sino que aceptamos q sea la de la lista o hasta 10 posiciones mas alante if alist[midpoint][0] <= item and item < alist[midpoint][0]+10: found = True retval = midpoint else: if item < alist[midpoint][0]: last = midpoint-1 else: first = midpoint+1 return found, retval ################################################ symbols = [] imagebase = idaapi.get_imagebase() ea = here() symbols_file_path = tkFileDialog.askopenfilename() f = open(symbols_file_path, "r+b") lines = f.readlines() f.close() ##### Collect symbols by content and set symbols by rva for line in lines: print line linesplit = line.split(" ") if len(linesplit)>0: symbolstr = linesplit[1].strip() symbolstr = symbolstr.replace(" = ", "").replace("()", "").replace("__CARRIAGE_RETURN__", "\r").replace("__NEWLINE__", "\n") if " byrva" in symbolstr: symbolstr = symbolstr.replace(" byrva", "") if " comment" in symbolstr: symbolstr = symbolstr.replace(" comment", "") MakeComm(imagebase+int(linesplit[0],16), symbolstr) elif " rptcomment" in symbolstr: symbolstr = symbolstr.replace(" rptcomment", "") MakeRptCmt(imagebase+int(linesplit[0],16), symbolstr) else: MakeNameEx(imagebase+int(linesplit[0],16), symbolstr.replace("!", "_").replace(" ", ""), 0) else: symbol = (int(linesplit[0],16), symbolstr) symbols.append(symbol) ##### Set symbols by content if len(symbols): symbols = sorted(symbols, key=lambda symbols: symbols[0]) for seg_ea in Segments(): for ea in range(seg_ea, SegEnd(seg_ea)): vop1 = None vop2 = None bIsCode = isCode(GetFlags(ea)) if bIsCode: op1type = idc.GetOpType(ea, 0) op2type = idc.GetOpType(ea, 1) if op1type == 5 or op1type == 6 or op1type == 7: vop1 = GetOperandValue(ea,0) if op2type == 5 or op2type == 6 or op2type == 7: vop2 = GetOperandValue(ea,1) v = Dword(ea) isymbol = binarySearch(symbols, v) if vop1 and not isymbol[0]: isymbol = binarySearch(symbols, vop1) if vop2 and not isymbol[0]: isymbol = binarySearch(symbols, vop2) if isymbol[0]: i = isymbol[1] if bIsCode: print "Is code!! %x %s\n" % (ea, symbols[i][1]) MakeComm(ItemHead(ea),symbols[i][1]) else: print "%x %s\n" % (ea, symbols[i][1]) MakeUnkn(ea,4) MakeDword(ea) MakeNameEx(ea,symbols[i][1].replace("!", "_").replace(" ", ""),0) MakeComm(ea,symbols[i][1]) symbols[i] = (symbols[i][0], "_"+symbols[i][1]) ################################################ ================================================ FILE: IDA/stack_strings_deobfuscator_1.py ================================================ ################################################ ## ## Author: Javier Vicente Vallejo ## Twitter: @vallejocc ## Web: http://www.vallejo.cc ## ################################################ ## ## Some malware families construct strings into the stack, like this: ## ## mov dword ptr [ebp-18h], 61737376h ; vssa ## mov dword ptr [ebp-14h], 642E6970h ; pi.d ## mov word ptr [ebp-10h], 6C6Ch; ll ## ## In addition, i have found malware families permutating code (they split the code in portions and the mix these portions, ## adding jumps from a portion to the next one for getting the code being executed in the correct order), and constructing ## strings in stack, for example: ## ## loc_4751A4: ## nop ## mov dword ptr [ebp-18h], 61737376h ; vssa ## nop ## jmp loc_474E10 ## | ## v ## loc_474E10: ## nop ## mov dword ptr [ebp-14h], 642E6970h ; pi.d ## nop ## jmp loc_475532 ## | ## v ## loc_475532: ## mov word ptr [ebp-10h], 6C6Ch; ll ## jmp loc_4750C3 ## ## This script add coments at points of code where each part of the string is being reconstructed. In addition it tries to ## construct for each function the string being constructed into the function. For this purpose, it needs to follow basic ## blocks of each funcion in the same order that they are going to be executed (in this way the strings will be reconstructed ## in the same order in spite of the fact the code is permutated). When it constructs an string, the output is like this: ## ## "Function text constructed in stack: sub_474CA0 | vssapi.dllCreateVssBackupComponentsInternaVssFreeSnapshotPropertiesInternaupComponents@@@Zonents@@YGJPAPAVIVssBackreeSnapshotPropertiessBackupCompateVVssF?Cre" ## ## Other malware constructs strings on the stack by moving each byte to the stack, like this: ## ## mov [ebp+var_30], 48h ## mov [ebp+var_2F], 65h ## mov [ebp+var_2E], 6Ch ## mov [ebp+var_2D], 6Ch ## mov [ebp+var_2C], 6Fh ## ## For this cases i recommend to you to read this article (with ida script included): ## ## https://www.fireeye.com/blog/threat-research/2014/08/flare-ida-pro-script-series-automatic-recovery-of-constructed-strings-in-malware.html ## ################################################ import idaapi import idc import idautils loutput = [] for segea in Segments(): for funcea in Functions(SegStart(segea), SegEnd(segea)): functxt = "" functionName = GetFunctionName(funcea) #print "Current function: %s" % functionName f = idaapi.get_func(funcea) fc = idaapi.FlowChart(f) lblocks = [] for block in fc: lblocks.append(block) lorderedblocks = [] while len(lblocks): first = lblocks.pop(0) lorderedblocks.append(first) for head in Heads(first.startEA, first.endEA): ins = GetMnem(head) if len(ins) and ins[0]=='j': op0 = GetOpType(head, 0) if op0==5 or op0==6 or op0==7: v = GetOperandValue(head, 0) for i in range(0, len(lblocks)): if v == lblocks[i].startEA: #print "Moving block %x:%x" % (head, v) lblocks.insert(0, lblocks.pop(i)) break for block in lorderedblocks: for head in Heads(block.startEA, block.endEA): dism = GetDisasm(head) if "mov dword ptr [ebp" in dism or "mov word ptr [ebp" in dism: op1 = GetOpType(head, 1) if op1==5 or op1==6 or op1==7: v = GetOperandValue(head, 1) curtxt = None if ("mov dword ptr [ebp" in dism) and v>0xffffff: curtxt = chr(v&0xff) + chr((v&0xff00)>>8) + chr((v&0xff0000)>>16) + chr((v&0xff000000)>>24) if ("mov word ptr [ebp" in dism) and v>0xff: curtxt = chr(v&0xff) + chr((v&0xff00)>>8) if curtxt: print hex(head), ":", GetDisasm(head), "--->", curtxt MakeRptCmt(head, curtxt) functxt += curtxt if len(functxt): loutput.append("Function text constructed in stack: " + functionName + " | " + functxt) for e in loutput: print e ================================================ FILE: README.md ================================================ Useful Scripts for helping in reverse engeenering. ================================================ FILE: WinDbg/Readme.md ================================================ $$>a -------------------------------------------------------- This windbg script will walk the results of !address command for each process in the debuggee machine, searching for RWE memory containing PE files (based on the analysis of PE header). When a PE file in RWE memory is found, the script will dump it. In addition to dump it, it will fix some fields of PE header: imagebase will be set to the address where the PE is loaded, and section[i].PointerToRawData = section[i].VirtualAddress (because we are dumping a mapped PE to disk and, if we want to analyze the dumped PE with a disassembler for example, we need to fix the sections). Article talking about this script: https://vallejo.cc/2017/08/13/tools-for-unpacking-malware-part-1-dumping-executables-from-rwe-memory/ $$>a --------------------------------------------------------------- This windbg script will walk the results of !address command for each process in the debuggee machine, searching for RWE memory containing PE files (based on the analysis of PE header). When a PE file in RWE memory is found, the script will dump it. In addition to dump it, it will fix some fields of PE header: imagebase will be set to the address where the PE is loaded, and section[i].PointerToRawData = section[i].VirtualAddress (because we are dumping a mapped PE to disk and, if we want to analyze the dumped PE with a disassembler for example, we need to fix the sections). The difference with the script dump_injected_pe_rwemem.wdbg (the non fast version) it is this script is not paging-in each page before trying to dump a PE file. Usually pages will be there, but it could fail to dump the entire file if a page is not mapped. Anyway, for debugging, i recommend to disable swapping, for having pages always in memory. Article talking about this script: https://vallejo.cc/2017/08/13/tools-for-unpacking-malware-part-1-dumping-executables-from-rwe-memory/ $$>aa privileged instruction. In this way, when rdtsc is executed by an application, an exception will occur and windbg will catch the exception. In that moment, the script checks the ins code of rdtsc, 0x310f. If it is a rdtsc instruction, it skips the instruction ip = ip+2. Finally it sets edx = 0, and eax = last_counter+1. Applications execution rdtsc will see an increment of 1 each rdtsc execution. $$>a ------------------------------------------------------ i.e. pafish tries to open vmware devices "\\\\.\\HGFS" and "\\\\.\\vmci", if can use this script to rename these devices in this way: change_object_name.wdbg \\global??\\hgfs (in this case we rename the symboliclink) \\global??\\hgfs -> \\global??\\agfs change_object_name.wdbg \\devices\\vmci (in this case we rename the deviceobject) \\devices\\vmci -> \\devices\\amci The script changes the first letter of the name (setting 'a'). If you need other letter or additional modifications, it is easy to modify the script. $$>a ------------------------------------------------------------------------ i.e. if we want to rename vmtoolsd.exe: $$>a it will rename the process to vmtoolse The script increase +1 the last letter of the name. If you need other or additional modifications, it is easy to modify the script. $$>a ---------------------------------------------------- This simple script will dump to a file all the symbols of the given process. If you dump a PE from memory, it could have variables pointing to symbols (for example, api addresses that it got with GetProcAddress, etc...). It is useful to have a list of pairs (symbol, address) because in this way if we open the dumped PE with IDA we can search for that addresses and set a name for the variable containing them. Article talking about this script: https://vallejo.cc/2017/08/13/tools-for-unpacking-malware-part-1-dumping-executables-from-rwe-memory/ $$>a --------------------------------------------------------------------------------- Allocates kernel memory and load a block of data to that kernel memory. Later it creates a kernel thread starting to run on the given offset. $$>a ----------------------------------------------- Log running processes to a given file. $$>a -------------------------------------------------------------- Page into memory a range of memory of the given process. $$>a ... (max 16 bytes) ------------------------------------------------------------------------------------- This script is useful for search a max of 16 given bytes through all the running processes. $$>a .. ------------------------------------------------------------------------ This script is useful for search a max of 16 given bytes in the given process. $$>a ---------------------------------------------- This script is useful for search a given string through all the running processes. $$>a ----------------------------------------------------- This script is useful for search a given string in a given process. $$>a ------------------------------------------------------------------- this script tries to dump a range of memory. If its not possible to dump a part of the range, that part if filled with random data (really its filled with "\x11\x11\x11......\x11\x20\x0d\x0a" (total length 0x1000 for each page filled), but we must not assume it will always contain this value. $$>a ----------------------------------------------- Show info about a given address of a given process. $$>a ---------------------------------------- Show a process info from a given handle. $$>aa privileged instruction. $$ In this way, when rdtsc is executed by an application, an exception will occur and windbg will catch the exception. $$ In that moment, the script checks the ins code of rdtsc, 0x310f. If it is a rdtsc instruction, it skips the instruction ip = ip+2. $$ Finally it sets edx = 0, and eax = last_counter+1. $$ Applications execution rdtsc will see an increment of 1 each rdtsc execution. $$ $$set rdtsc as priv instruction, then catch exceptions for priv instructions and skip rdtsc(eip=eip+2) and set edx:eax = last rdtsc returned value +1 $$use $t9 for counter r $t9 = 0 $$rdtsc = privileged instruction r cr4 = cr4 | 4 $$Stop on exception !gflag +soe $$Stop on unhandled user-mode exception !gflag +sue $$disable access violation (we have enabled exception in user mode, and access violation will cause lot of exceptions) sxd av $$we enable to catch privileged instructions execution (we have converted rdtsc in priv ins with cr4) $$in this moment we check if it is rdtsc, and in this case, we jump over the instruction and we set eax=0 edx=0 sxe -c ".if((poi(eip)&0x0000ffff)==0x310f){.printf \"rdtsc\r\n\";r eip = eip+2;r eax=@$t9;r edx=0;r $t9=@$t9+1; gh;}" c0000096 ================================================ FILE: WinDbg/break_each_new_process.wdbg ================================================ $$ $$ Author: Javier Vicente Vallejo $$ Twitter: @vallejocc $$ Web: http://www.vallejo.cc $$ $$ $$>aa $$ $$ i.e. pafish tries to open vmware devices "\\\\.\\HGFS" and "\\\\.\\vmci", if can use this script to rename these devices in this way: $$ $$ change_object_name.wdbg \\global??\\hgfs (in this case we rename the symboliclink) \\global??\\hgfs -> \\global??\\agfs $$ change_object_name.wdbg \\devices\\vmci (in this case we rename the deviceobject) \\devices\\vmci -> \\devices\\amci $$ $$ The script changes the first letter of the name (setting 'a'). If you need other letter or additional modifications, it is easy to modify the script $$ aS stage @$t19 aS x64arch $t18 aS objhnameinfodisp $t17 .block { .sympath "SRV*c:\symcache*http://msdl.microsoft.com/download/symbols"; .reload } .block { $$is x64? r x64arch = 0; r objhnameinfodisp = 0x10; .foreach( tok { .effmach } ) { .if($scmp("${tok}","x64")==0) { r x64arch = 1; r objhnameinfodisp = 0x20; .break; }; }; } r stage = 0 .foreach( tok { !object "${$arg1}" } ) { .printf "${tok}\r\n" .if(${stage}==1) { .echo ${tok} dt _OBJECT_HEADER ${tok} r $t0 = ${tok} dt _OBJECT_HEADER_NAME_INFO (@$t0-${objhnameinfodisp}) $$ $t0 -> OBJECT_HEADER_NAME_INFO r $t0 = @$t0 - ${objhnameinfodisp} $$ $t0 -> OBJECT_HEADER_NAME_INFO.UNICODE_STRING r $t0 = @$t0 + @@c++(#FIELD_OFFSET(_OBJECT_HEADER_NAME_INFO, Name)) $$ $t0 -> OBJECT_HEADER_NAME_INFO.UNICODE_STRING.Buffer r $t0 = @$t0 + @@c++(#FIELD_OFFSET(_UNICODE_STRING, Buffer)) db poi $t0 $$change the first letter for 'a' eb (poi $t0) 'a' .printf "--------------------\r\n" db poi $t0 .break } .if(${stage}==0) { .if($scmp("${tok}","ObjectHeader:")==0) { r stage = 1 } } } ================================================ FILE: WinDbg/change_process_name.wdbg ================================================ $$ $$ Author: Javier Vicente Vallejo $$ Twitter: @vallejocc $$ Web: http://www.vallejo.cc $$ $$ $$>a $$ $$ i.e. if we want to rename vmtoolsd.exe: $$ $$ $$>a it will rename the process to vmtoolse $$ $$ The script increase +1 the last letter of the name. If you need other or additional modifications, it is easy to modify the script .logopen ${$arg2}\change_process_name.start .printf "start" .logclose aS stage @$t19 .block { .sympath "SRV*c:\symcache*http://msdl.microsoft.com/download/symbols"; .reload } .block { r stage = 2 .printf "xxxx" .foreach (processes_tok { !process /m ${$arg1} 0 0 }) { .if($scmp("${processes_tok}","PROCESS")==0) { .if(${stage}==2) { $$stage==2 is used to skip the first apparition of PROCESS string in the results of !process 0 0 r stage = 0 } .else { r stage = 1 } } .elsif(${stage}==1) { .printf /D "Renaming process ${processes_tok}\n" r stage = 0 r $t4 = ${processes_tok} r $t0 = @@c++( ( ( nt!_EPROCESS * ) @$t4 )->SeAuditProcessCreationInfo.ImageFileName ) r $t1 = (poi @$t0)&0xffff r $t2 = (poi (@$t0+2))&0xffff r $t3 = (poi (@$t0+@@c++(#FIELD_OFFSET(nt!_UNICODE_STRING, Buffer)))) db ($t3 + $t1 - a) $$go to end of buffer of _UNICODE_STRING, and go back 0xa bytes. For example .exe. We locate on lastbyte, and we increase 1 the value of last byte $$For example \vmtoolsd.exe, will be modified to \vmtoolse.exe eb ($t3 + $t1 - a) ((poi($t3 + $t1 - a)&0xff)+1) !process @$t4 0 } } } .logopen ${$arg2}\change_process_name.end .printf "end" .logclose ================================================ FILE: WinDbg/dump_injected_pe_rwemem.wdbg ================================================ $$ $$ Author: Javier Vicente Vallejo $$ Twitter: @vallejocc $$ Web: http://www.vallejo.cc $$ $$ $$>a $$ $$ This windbg script will walk the results of !address command for each process in the debuggee machine, $$ searching for RWE memory containing PE files (based on the analysis of PE header). $$ $$ When a PE file in RWE memory is found, the script will dump it. In addition to dump it, it will fix $$ some fields of PE header: imagebase will be set to the address where the PE is loaded, and $$ section[i].PointerToRawData = section[i].VirtualAddress (because we are dumping a mapped PE to disk and, $$ if we want to analyze the dumped PE with a disassembler for example, we need to fix the sections). $$ $$.sympath SRV*c:\symcache*http://msdl.microsoft.com/download/symbols $$.reload .logopen ${$arg1}\dump_injected_pe_rwemem.start .printf "start" .logclose aS stage @$t19 aS temp @$t18 aS temp2 @$t17 aS temp3 @$t16 aS isPossiblePE @$t15 aS isDosMessageBased @$t14 aS pe @$t13 aS fileheader @$t12 aS optionalheader @$t11 aS sections @$t10 aS nsections @$t9 aS lastsect @$t8 aS prev1 @$t7 aS prev2 @$t6 aS baseaddr @$t5 aS baseaddrlen @$t4 .block { .sympath "SRV*c:\symcache*http://msdl.microsoft.com/download/symbols"; .reload } .block { r stage = 2 .printf "xxxx" .foreach (processes_tok { !process 0 0 }) { .if($scmp("${processes_tok}","PROCESS")==0) { .if(${stage}==2) { $$stage==2 is used to skip the first apparition of PROCESS string in the results of !process 0 0 r stage = 0 } .else { r stage = 1 } } .elsif(${stage}==1) { .printf /D "Analyzing process ${processes_tok}\n" r stage = 0 .process /i ${processes_tok} g .block { .reload /user } $$search for memory blocks with ReadWriteExecute protection $$careful: $$ when the baseaddress is over 0x10000000 findstr tokens will be: $$ 93:7640:20010000 $$ 2002c000 $$ 1c000 $$ UserRange $$ ... $$ however if the addess is under 0x10000000: $$ 25:1364: $$ 60000 $$ 61000 $$ 1000 $$ UserRange $$ The reason for this its windbg puts spaces before the base address when it hasnt 8 characters to complete 8 characters, but if the address is $$ ???????? then it doesnt put spaces. We need to have in mind both case, and this is the reason of the stages of the next code $$ .foreach (tok { .shell -ci "!address" findstr /N /O /R /C:"UserRange.*ExecuteReadWrite" /C:"UserRange.*ReadWriteExecute" }) { r isPossiblePE = 0 r isDosMessageBased = 0 .printf "${tok}\n" .if($spat("${tok}","*:*:*")!=0) { r stage = 1 } .elsif(${stage}==1) { r prev1 = ${tok} r stage = 2 } .elsif(${stage}==2) { r prev2 = ${tok} r stage = 3 } .elsif(${stage}==3) { .if($spat("${tok}","*UserRange*")!=0) { r baseaddr = prev1 - prev2 r baseaddrlen = prev2 r stage = 5 } .else { r baseaddr = prev1 r baseaddrlen = ${tok} r stage = 4 } } .elsif(${stage}==4) { r stage = 5 } .elsif(${stage}==5) { $$for each block with ReadWriteExecute protection, check MZ / PE r @$t0 = baseaddr .if(@$t0!=0) { .printf "base %x\n", @$t0 .pagein /p ${processes_tok} @$t0 g $$!address @$t0 .if($vvalid(@$t0, 2)==1) { .printf "valid base address\n" .printf "PE_header %x\n", @$t0+@@c++(((nt!_IMAGE_DOS_HEADER * )@$t0)->e_lfanew) .if($vvalid(@$t0+@@c++(((nt!_IMAGE_DOS_HEADER * )@$t0)->e_lfanew), 2)==1) { .printf "valid PE_header address\n" .if(wo(@$t0)==0x5a4d & dwo(@$t0+@@c++(((nt!_IMAGE_DOS_HEADER * )@$t0)->e_lfanew))==0x454E) { $$We can find MZ / NE images, we ignore them } .elsif(wo(@$t0)==0x5a4d & dwo(@$t0+@@c++(((nt!_IMAGE_DOS_HEADER * )@$t0)->e_lfanew))==0x4550) { $$if MZ and PE signatures, valid pe header .printf "valid PE_header\n" r isPossiblePE = 1 } .else { $$if not MZ or not PE signature, but msdos message is found, its a possible pe header r temp = 0 .foreach (tok2 { s -a @$t0 L 0x80 "This program cannot " }) { r temp = 1 } .if(${temp}==1) { .printf "possible pe header\n" r isPossiblePE = 1 r isDosMessageBased = 1 } } } .else { .printf "not valid PE_header address\n" } } .else { .printf "not valid base address\n" } $$if we have found a possible PE in a memory zone with ReadWriteExecute protection, we will check if the base address is in the list of loaded module .if(${isPossiblePE}==1) { .printf "is possible module %x\n", @$t0 $$ r temp = 0 $$ $$search for valid from "is not valid address" $$ .foreach (tok3 { .shell -ci "!lmi @$t0" findstr /N /O "valid.address" }) $$ { $$ r temp = ${temp} + 1 $$ } $$ .printf "%x\n", ${temp} $$ $$ We are going to disable !lmi check. I have found malware that is unmapping $$ the main module of a executable when it does process hollowing, and it loads $$ its injected PE in that address. In this cases, !lmi is not answering not valid $$ address for that address. However !address command is containing the path $$ of the module (for example Section [\Windows\explorer.exe]) when the module is $$ a valid loaded module, and it wont contain the path when it is an injected module $$ in RWE mem. So, we will skip !lmi check, and we will check !address results $$ vvvvvvvv DISABLE !LMI CHECK vvvvvvvvv r temp = 4 $$ ^^^^^^^^ DISABLE !LMI CHECK ^^^^^^^^^ $$ "is not valid address" was found .if(${temp} > 3) { $$ there are some modules that !lmi command is answering: is not valid address, however they seems to be valid loaded modules (not interesing for us). $$ However if we consults information about the address with !address we find things as: $$ Memory Usage: Section [\WINDOWS\System32\blablabla.mui] (it happens usually with .mui files, but not only with them $$ We will discard results of !address with .dll], .mui] and .exe] r temp = 0 .foreach (tok4 { .shell -ci "!address @$t0" findstr /N /O /R /I "\.mui\]" }) { r temp = ${temp} + 1 } r temp2 = 0 .foreach (tok5 { .shell -ci "!address @$t0" findstr /N /O /R /I "\.dll\]" }) { r temp2 = ${temp2} + 1 } r temp3 = 0 .foreach (tok6 { .shell -ci "!address @$t0" findstr /N /O /R /I "\.exe\]" }) { r temp3 = ${temp3} + 1 } .printf "search !address .mui %x\n", ${temp} .printf "search !address .dll %x\n", ${temp2} .printf "search !address .exe %x\n", ${temp3} .if(${temp} < 4 and ${temp2} < 4 and ${temp3} < 4) { .if(${isDosMessageBased}==0) { .printf /D "---------------------------------------------------------------------------\n" .printf /D "Process: ${processes_tok} base: %x -> Possible injected or unpacked PE\n", @$t0 .printf /D "---------------------------------------------------------------------------\n" } .else { .printf /D "-------------------------------------------------------------------------------------------------------------------------------------\n" .printf /D "Process: ${processes_tok} base: %x -> Possible injected or unpacked PE, based on the dos header message: This program cannot...\n", @$t0 .printf /D "--------------------------------------------------------------------------------------------------------------------------------------\n" } .printf "xxxx1\n" r pe = @$t0 + poi(@$t0 + @@(#FIELD_OFFSET(nt!_IMAGE_DOS_HEADER, e_lfanew))) .printf "xxxx2 %x\n", ${pe} r fileheader = ${pe} + @@(#FIELD_OFFSET(nt!_IMAGE_NT_HEADERS, FileHeader)) .printf "xxxx3 %x\n", ${fileheader} r optionalheader = ${pe} + @@(#FIELD_OFFSET(nt!_IMAGE_NT_HEADERS, OptionalHeader)) .printf "xxxx4 %x\n", ${optionalheader} r nsections = poi(${fileheader} + @@(#FIELD_OFFSET(nt!_IMAGE_FILE_HEADER, NumberOfSections )))&0xffff .printf "xxxx5 %x\n", ${nsections} r sections = ${pe} + @@c++(sizeof(nt!_IMAGE_NT_HEADERS)) .printf "xxxx6 %x\n", ${sections} $$ reach the end of the PE file (last section rva + last section vsize) r lastsect = ${sections} + ( ${nsections} - 1 ) * @@c++(sizeof(nt!_IMAGE_SECTION_HEADER)) r @$t1 = @$t0 + @@c++( ( ( nt!_IMAGE_SECTION_HEADER * ) ${lastsect} )->VirtualAddress ) + @@c++( ( ( nt!_IMAGE_SECTION_HEADER * ) ${lastsect} )->Misc.VirtualSize ) r @$t2 = @$t0 + @@c++( ( ( nt!_IMAGE_SECTION_HEADER * ) ${lastsect} )->VirtualAddress ) + @@c++( ( ( nt!_IMAGE_SECTION_HEADER * ) ${lastsect} )->SizeOfRawData ) r @$t3 = @$t0 .if (@$t2 > @$t1) { r @$t1 = @$t2 } $$limit to dump = 0x100000 bytes .if (@$t1-@$t0 > 0x100000) { r @$t1 = @$t0+0x100000 } .printf "xxxx7" $$ before dumping the PE, we are going to modify sections' raw offset = virtual address for coherency in the dumped PE when we analyze it with IDA or any other tool r $t2 = ${sections} r lastsect = ${sections} + ( ${nsections} ) * @@c++(sizeof(nt!_IMAGE_SECTION_HEADER)) .while (@$t2 < ${lastsect}) { ed (@$t2+@@( #FIELD_OFFSET(nt!_IMAGE_SECTION_HEADER, PointerToRawData))) @@c++((( nt!_IMAGE_SECTION_HEADER *)@$t2)->VirtualAddress) .if( @@c++((( nt!_IMAGE_SECTION_HEADER *)@$t2)->SizeOfRawData) < @@c++((( nt!_IMAGE_SECTION_HEADER *)@$t2)->Misc.VirtualSize)) { ed (@$t2+@@( #FIELD_OFFSET(nt!_IMAGE_SECTION_HEADER, SizeOfRawData))) @@c++((( nt!_IMAGE_SECTION_HEADER *)@$t2)->Misc.VirtualSize) } .else { ed (@$t2+@@( #FIELD_OFFSET(nt!_IMAGE_SECTION_HEADER, Misc))) @@c++((( nt!_IMAGE_SECTION_HEADER *)@$t2)->SizeOfRawData) } r $t2 = @$t2 + @@c++(sizeof(nt!_IMAGE_SECTION_HEADER)) } $$update sizeofimage .block { ed (${optionalheader} + @@(#FIELD_OFFSET(nt!_IMAGE_OPTIONAL_HEADER, SizeOfImage))) (@$t1-@$t0)&0x7fffffff } .printf "xxxx8" $$update imagebase .block { ed (${optionalheader} + @@(#FIELD_OFFSET(nt!_IMAGE_OPTIONAL_HEADER, ImageBase))) ${baseaddr} } .printf "xxxx9" .printf "dumping file address start %x end %x", @$t0, @$t1 .while (@$t0 < @$t1) { .printf "paging in: %x\n", @$t0 .pagein /p ${processes_tok} @$t0 g r @$t0 = @$t0 + 0x1000; }; .foreach /pS 4 (baseaddr_tok { ? baseaddr }) { .foreach /pS 4 (baseaddrlen_tok { ? baseaddrlen }) { .printf "${processes_tok}_${baseaddr_tok}_${baseaddrlen_tok}.pedmp" .writemem ${$arg1}\${processes_tok}_${baseaddr_tok}_${baseaddrlen_tok}.pedmp @$t3 L (@$t1 - @$t3) } } } } } r stage = 0 } } } r stage = 0 } } .logopen ${$arg1}\dump_injected_pe_rwemem.end .printf "end" .logclose ad stage ad temp ad temp2 ad temp3 ad isPossiblePE ad isDosMessageBased ad pe ad fileheader ad optionalheader ad sections ad nsections ad lastsect ad prev1 ad prev2 ad baseaddr ad baseaddrlen } ================================================ FILE: WinDbg/dump_injected_pe_rwemem_fast.wdbg ================================================ $$ $$ Author: Javier Vicente Vallejo $$ Twitter: @vallejocc $$ Web: http://www.vallejo.cc $$ $$ $$>a $$ $$ This windbg script will walk the results of !address command for each process in the debuggee machine, $$ searching for RWE memory containing PE files (based on the analysis of PE header). $$ $$ When a PE file in RWE memory is found, the script will dump it. In addition to dump it, it will fix $$ some fields of PE header: imagebase will be set to the address where the PE is loaded, and $$ section[i].PointerToRawData = section[i].VirtualAddress (because we are dumping a mapped PE to disk and, $$ if we want to analyze the dumped PE with a disassembler for example, we need to fix the sections). $$ $$ The difference with the script dump_injected_pe_rwemem.wdbg (the non fast version) it is this script is $$ not paging-in each page before trying to dump a PE file. Usually pages will be there, but it could fail $$ to dump the entire file if a page is not mapped. $$ $$ Anyway, for debugging, i recommend to disable swapping, for having pages always in memory. $$ $$.sympath SRV*c:\symcache*http://msdl.microsoft.com/download/symbols $$.reload .logopen ${$arg1}\dump_injected_pe_rwemem_fast.start .printf "start" .logclose aS stage @$t19 aS temp @$t18 aS temp2 @$t17 aS temp3 @$t16 aS isPossiblePE @$t15 aS isDosMessageBased @$t14 aS pe @$t13 aS fileheader @$t12 aS optionalheader @$t11 aS sections @$t10 aS nsections @$t9 aS lastsect @$t8 aS prev1 @$t7 aS prev2 @$t6 aS baseaddr @$t5 aS baseaddrlen @$t4 .block { .sympath "SRV*c:\symcache*http://msdl.microsoft.com/download/symbols"; .reload } .block { r stage = 2 .printf "xxxx" .foreach (processes_tok { !process 0 0 }) { .if($scmp("${processes_tok}","PROCESS")==0) { .if(${stage}==2) { $$stage==2 is used to skip the first apparition of PROCESS string in the results of !process 0 0 r stage = 0 } .else { r stage = 1 } } .elsif(${stage}==1) { .printf /D "Analyzing process ${processes_tok}\n" r stage = 0 .process /p /r ${processes_tok} $$search for memory blocks with ReadWriteExecute protection $$careful: $$ when the baseaddress is over 0x10000000 findstr tokens will be: $$ 93:7640:20010000 $$ 2002c000 $$ 1c000 $$ UserRange $$ ... $$ however if the addess is under 0x10000000: $$ 25:1364: $$ 60000 $$ 61000 $$ 1000 $$ UserRange $$ The reason for this its windbg puts spaces before the base address when it hasnt 8 characters to complete 8 characters, but if the address is $$ ???????? then it doesnt put spaces. We need to have in mind both case, and this is the reason of the stages of the next code $$ .foreach (tok { .shell -ci "!address" findstr /N /O /R /C:"UserRange.*ExecuteReadWrite" /C:"UserRange.*ReadWriteExecute" }) { r isPossiblePE = 0 r isDosMessageBased = 0 .printf "${tok}\n" .if($spat("${tok}","*:*:*")!=0) { r stage = 1 } .elsif(${stage}==1) { r prev1 = ${tok} r stage = 2 } .elsif(${stage}==2) { r prev2 = ${tok} r stage = 3 } .elsif(${stage}==3) { .if($spat("${tok}","*UserRange*")!=0) { r baseaddr = prev1 - prev2 r baseaddrlen = prev2 r stage = 5 } .else { r baseaddr = prev1 r baseaddrlen = ${tok} r stage = 4 } } .elsif(${stage}==4) { r stage = 5 } .elsif(${stage}==5) { $$for each block with ReadWriteExecute protection, check MZ / PE r @$t0 = baseaddr .if(@$t0!=0) { .printf "base %x\n", @$t0 $$.pagein /p ${processes_tok} @$t0 $$g $$!address @$t0 .if($vvalid(@$t0, 2)==1) { .printf "valid base address\n" .printf "PE_header %x\n", @$t0+@@c++(((nt!_IMAGE_DOS_HEADER * )@$t0)->e_lfanew) .if($vvalid(@$t0+@@c++(((nt!_IMAGE_DOS_HEADER * )@$t0)->e_lfanew), 2)==1) { .printf "valid PE_header address\n" .if(wo(@$t0)==0x5a4d & dwo(@$t0+@@c++(((nt!_IMAGE_DOS_HEADER * )@$t0)->e_lfanew))==0x454E) { $$We can find MZ / NE images, we ignore them } .elsif(wo(@$t0)==0x5a4d & dwo(@$t0+@@c++(((nt!_IMAGE_DOS_HEADER * )@$t0)->e_lfanew))==0x4550) { $$if MZ and PE signatures, valid pe header .printf "valid PE_header\n" r isPossiblePE = 1 } .else { $$if not MZ or not PE signature, but msdos message is found, its a possible pe header r temp = 0 .foreach (tok2 { s -a @$t0 L 0x80 "This program cannot " }) { r temp = 1 } .if(${temp}==1) { .printf "possible pe header\n" r isPossiblePE = 1 r isDosMessageBased = 1 } } } .else { .printf "not valid PE_header address\n" } } .else { .printf "not valid base address\n" } $$if we have found a possible PE in a memory zone with ReadWriteExecute protection, we will check if the base address is in the list of loaded module .if(${isPossiblePE}==1) { .printf "is possible module %x\n", @$t0 $$ r temp = 0 $$ $$search for valid from "is not valid address" $$ .foreach (tok3 { .shell -ci "!lmi @$t0" findstr /N /O "valid.address" }) $$ { $$ r temp = ${temp} + 1 $$ } $$ .printf "%x\n", ${temp} $$ $$ We are going to disable !lmi check. I have found malware that is unmapping $$ the main module of a executable when it does process hollowing, and it loads $$ its injected PE in that address. In this cases, !lmi is not answering not valid $$ address for that address. However !address command is containing the path $$ of the module (for example Section [\Windows\explorer.exe]) when the module is $$ a valid loaded module, and it wont contain the path when it is an injected module $$ in RWE mem. So, we will skip !lmi check, and we will check !address results $$ vvvvvvvv DISABLE !LMI CHECK vvvvvvvvv r temp = 4 $$ ^^^^^^^^ DISABLE !LMI CHECK ^^^^^^^^^ $$ "is not valid address" was found .if(${temp} > 3) { $$ there are some modules that !lmi command is answering: is not valid address, however they seems to be valid loaded modules (not interesing for us). $$ However if we consults information about the address with !address we find things as: $$ Memory Usage: Section [\WINDOWS\System32\blablabla.mui] (it happens usually with .mui files, but not only with them $$ We will discard results of !address with .dll], .mui] and .exe] r temp = 0 .foreach (tok4 { .shell -ci "!address @$t0" findstr /N /O /R /I "\.mui\]" }) { r temp = ${temp} + 1 } r temp2 = 0 .foreach (tok5 { .shell -ci "!address @$t0" findstr /N /O /R /I "\.dll\]" }) { r temp2 = ${temp2} + 1 } r temp3 = 0 .foreach (tok6 { .shell -ci "!address @$t0" findstr /N /O /R /I "\.exe\]" }) { r temp3 = ${temp3} + 1 } .printf "search !address .mui %x\n", ${temp} .printf "search !address .dll %x\n", ${temp2} .printf "search !address .exe %x\n", ${temp3} .if(${temp} < 4 and ${temp2} < 4 and ${temp3} < 4) { .if(${isDosMessageBased}==0) { .printf /D "---------------------------------------------------------------------------\n" .printf /D "Process: ${processes_tok} base: %x -> Possible injected or unpacked PE\n", @$t0 .printf /D "---------------------------------------------------------------------------\n" } .else { .printf /D "-------------------------------------------------------------------------------------------------------------------------------------\n" .printf /D "Process: ${processes_tok} base: %x -> Possible injected or unpacked PE, based on the dos header message: This program cannot...\n", @$t0 .printf /D "--------------------------------------------------------------------------------------------------------------------------------------\n" } .printf "xxxx1\n" r pe = @$t0 + poi(@$t0 + @@(#FIELD_OFFSET(nt!_IMAGE_DOS_HEADER, e_lfanew))) .printf "xxxx2 %x\n", ${pe} r fileheader = ${pe} + @@(#FIELD_OFFSET(nt!_IMAGE_NT_HEADERS, FileHeader)) .printf "xxxx3 %x\n", ${fileheader} r optionalheader = ${pe} + @@(#FIELD_OFFSET(nt!_IMAGE_NT_HEADERS, OptionalHeader)) .printf "xxxx4 %x\n", ${optionalheader} r nsections = poi(${fileheader} + @@(#FIELD_OFFSET(nt!_IMAGE_FILE_HEADER, NumberOfSections )))&0xffff .printf "xxxx5 %x\n", ${nsections} r sections = ${pe} + @@c++(sizeof(nt!_IMAGE_NT_HEADERS)) .printf "xxxx6 %x\n", ${sections} $$ reach the end of the PE file (last section rva + last section vsize) r lastsect = ${sections} + ( ${nsections} - 1 ) * @@c++(sizeof(nt!_IMAGE_SECTION_HEADER)) r @$t1 = @$t0 + @@c++( ( ( nt!_IMAGE_SECTION_HEADER * ) ${lastsect} )->VirtualAddress ) + @@c++( ( ( nt!_IMAGE_SECTION_HEADER * ) ${lastsect} )->Misc.VirtualSize ) r @$t2 = @$t0 + @@c++( ( ( nt!_IMAGE_SECTION_HEADER * ) ${lastsect} )->VirtualAddress ) + @@c++( ( ( nt!_IMAGE_SECTION_HEADER * ) ${lastsect} )->SizeOfRawData ) r @$t3 = @$t0 .if (@$t2 > @$t1) { r @$t1 = @$t2 } $$limit to dump = 0x100000 bytes .if (@$t1-@$t0 > 0x100000) { r @$t1 = @$t0+0x100000 } .printf "xxxx7" $$ before dumping the PE, we are going to modify sections' raw offset = virtual address for coherency in the dumped PE when we analyze it with IDA or any other tool r $t2 = ${sections} r lastsect = ${sections} + ( ${nsections} ) * @@c++(sizeof(nt!_IMAGE_SECTION_HEADER)) .while (@$t2 < ${lastsect}) { ed (@$t2+@@( #FIELD_OFFSET(nt!_IMAGE_SECTION_HEADER, PointerToRawData))) @@c++((( nt!_IMAGE_SECTION_HEADER *)@$t2)->VirtualAddress) .if( @@c++((( nt!_IMAGE_SECTION_HEADER *)@$t2)->SizeOfRawData) < @@c++((( nt!_IMAGE_SECTION_HEADER *)@$t2)->Misc.VirtualSize)) { ed (@$t2+@@( #FIELD_OFFSET(nt!_IMAGE_SECTION_HEADER, SizeOfRawData))) @@c++((( nt!_IMAGE_SECTION_HEADER *)@$t2)->Misc.VirtualSize) } .else { ed (@$t2+@@( #FIELD_OFFSET(nt!_IMAGE_SECTION_HEADER, Misc))) @@c++((( nt!_IMAGE_SECTION_HEADER *)@$t2)->SizeOfRawData) } r $t2 = @$t2 + @@c++(sizeof(nt!_IMAGE_SECTION_HEADER)) } $$update sizeofimage .block { ed (${optionalheader} + @@(#FIELD_OFFSET(nt!_IMAGE_OPTIONAL_HEADER, SizeOfImage))) (@$t1-@$t0)&0x7fffffff } .printf "xxxx8" $$update imagebase .block { ed (${optionalheader} + @@(#FIELD_OFFSET(nt!_IMAGE_OPTIONAL_HEADER, ImageBase))) ${baseaddr} } .printf "xxxx9" .printf "dumping file address start %x end %x", @$t0, @$t1 .foreach /pS 4 (baseaddr_tok { ? baseaddr }) { .foreach /pS 4 (baseaddrlen_tok { ? baseaddrlen }) { .printf "${processes_tok}_${baseaddr_tok}_${baseaddrlen_tok}.pedmp" .writemem ${$arg1}\${processes_tok}_${baseaddr_tok}_${baseaddrlen_tok}.pedmp @$t3 L (@$t1 - @$t3) } } } } } r stage = 0 } } } r stage = 0 } } .logopen ${$arg1}\dump_injected_pe_rwemem_fast.end .printf "end" .logclose ad stage ad temp ad temp2 ad temp3 ad isPossiblePE ad isDosMessageBased ad pe ad fileheader ad optionalheader ad sections ad nsections ad lastsect ad prev1 ad prev2 ad baseaddr ad baseaddrlen } ================================================ FILE: WinDbg/dump_main_module.wdbg ================================================ $$ $$ Author: Javier Vicente Vallejo $$ Twitter: @vallejocc $$ Web: http://www.vallejo.cc $$ $$ $$>a .logopen ${$arg1}\dump_main_module.start .printf "start" .logclose aS stage @$t19 aS base @$t18 aS end @$t17 .block { .sympath "SRV*c:\symcache*http://msdl.microsoft.com/download/symbols"; .reload /f } .block { r stage = 2 .printf "xxxx" .foreach (processes_tok { !process /m ${$arg2} 0 0 }) { .if($scmp("${processes_tok}","PROCESS")==0) { .if(${stage}==2) { $$stage==2 is used to skip the first apparition of PROCESS string in the results of !process 0 0 r stage = 0 } .else { r stage = 1 } } .elsif(${stage}==1) { .printf /D "Dumping main module for process ${processes_tok}\n" r stage = 0 .process /i ${processes_tok} g .reload /f .foreach (lmi_tok { lmi m ${$arg3} }) { r stage = stage + 1 .if(${stage}==5) { .printf "${lmi_tok}\r\n" r base = ${lmi_tok} } .if(${stage}==6) { .printf "${lmi_tok}\r\n" r end = ${lmi_tok} } } .if (${end} - ${base} > 0x100000) { r end = ${base} + 0x100000 } .foreach /pS 4 (baseaddr_tok { ? ${base} }) { .foreach /pS 4 (endaddr_tok { ? ${end} }) { .printf "[${processes_tok}] ${baseaddr_tok} - ${endaddr_tok}\r\n" .block { $$>aa $$ it uses t0, t1 and t2 $$.sympath SRV*c:\symcache*http://msdl.microsoft.com/download/symbols $$.reload .printf "dump pe base address %x, process %x\n", ${$arg1}, ${$arg2} $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $$ change the context to the target process and page in the page containing PE headers .process /i ${$arg2} g $$.reload /user .pagein /p ${$arg2} ${$arg1} g .block { $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $$ define some useful aliases ad /q ${/v:$imagebase}; ad /q ${/v:$pe_header}; ad /q ${/v:$file_header}; ad /q ${/v:$optional_header}; ad /q ${/v:$number_of_sections}; ad /q ${/v:$sections}; aS /x ${/v:$imagebase} ( ${$arg1} ) aS /x ${/v:$pe_header} ( $imagebase + ( poi( $imagebase + @@( #FIELD_OFFSET( nt!_IMAGE_DOS_HEADER, e_lfanew ) ) ) ) ) aS /x ${/v:$file_header} ( $pe_header + @@( #FIELD_OFFSET( nt!_IMAGE_NT_HEADERS, FileHeader ) ) ) aS /x ${/v:$optional_header} ( $pe_header + @@( #FIELD_OFFSET( nt!_IMAGE_NT_HEADERS, OptionalHeader ) ) ) aS /x ${/v:$number_of_sections} ( poi ( $file_header + @@( #FIELD_OFFSET( nt!_IMAGE_FILE_HEADER, NumberOfSections ) ) ) & 0xffff ) aS /x ${/v:$sections} ( $pe_header + @@c++( sizeof ( nt!_IMAGE_NT_HEADERS ) ) ) $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $$ we are going to set sections' raw offset = virtual address for coherency in the dumped PE r $t0 = $sections r $t1 = 0 r $t2 = $number_of_sections .while (@$t1 < @$t2) { r $t3 = $sections + @$t1 * @@c++(sizeof(nt!_IMAGE_SECTION_HEADER)) $$.printf "%x\n", @@c++( ( ( nt!_IMAGE_SECTION_HEADER * ) @$t3 )->VirtualAddress ) $$.printf "%x\n", @@c++( ( ( nt!_IMAGE_SECTION_HEADER * ) @$t3 )->PointerToRawData ) r $t4 = @@c++( ( ( nt!_IMAGE_SECTION_HEADER * ) @$t3 )->VirtualAddress ) r $t5 = @$t3 + @@( #FIELD_OFFSET( nt!_IMAGE_SECTION_HEADER, PointerToRawData ) ) ed $t5 $t4 r $t1 = @$t1 + 1 } $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $$ now we are going to page in all the pages of the PE file in memory r $t0 = $sections + ( $number_of_sections - 1 ) * @@c++(sizeof(nt!_IMAGE_SECTION_HEADER)) r $t1 = $imagebase + @@c++( ( ( nt!_IMAGE_SECTION_HEADER * ) @$t0 )->VirtualAddress ) + @@c++( ( ( nt!_IMAGE_SECTION_HEADER * ) @$t0 )->Misc.VirtualSize ) r $t2 = $imagebase + @@c++( ( ( nt!_IMAGE_SECTION_HEADER * ) @$t0 )->VirtualAddress ) + @@c++( ( ( nt!_IMAGE_SECTION_HEADER * ) @$t0 )->SizeOfRawData ) .if ($t2 > $t1) { r $t1 = $t2 } r $t0 = $imagebase ? $t0 ? $t1 .while (@$t0 < @$t1) { .printf "paging in: %x\n", @$t0 .pagein /p ${$arg2} @$t0 g r $t0 = @$t0 + 0x1000; }; ? $t1 - $imagebase $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $$ finally we are going to write the entire PE file to disk .writemem ${$arg3} $imagebase L $t1 - $imagebase } ================================================ FILE: WinDbg/dump_process_symbols_to_file.wdbg ================================================ $$ $$ Author: Javier Vicente Vallejo $$ Twitter: @vallejocc $$ Web: http://www.vallejo.cc $$ $$ $$ $$>a $$ $$ This simple script will dump to a file all the symbols of the given process. $$ If you dump a PE from memory, it could have variables pointing to symbols (for example, api addresses that it got with GetProcAddress, etc...). $$ It is useful to have a list of pairs (symbol, address) because in this way if we open the dumped PE with IDA we can search for that addresses $$ and set a name for the variable containing them. $$ .block { .sympath "SRV*c:\symcache*http://msdl.microsoft.com/download/symbols"; .reload } .block { .process /i ${$arg2} g .block { .reload } .block { .reload /user } .block { .shell -i- -ci "x *!*" findstr "!">${$arg1} } } ================================================ FILE: WinDbg/dump_process_symbols_to_file_preselect.wdbg ================================================ $$ $$ Author: Javier Vicente Vallejo $$ Twitter: @vallejocc $$ Web: http://www.vallejo.cc $$ $$ $$ $$>a $$ $$ This simple script will dump to a file symbols of a preselection of common dlls of the given process. $$ If you dump a PE from memory, it could have variables pointing to symbols (for example, api addresses that it got with GetProcAddress, etc...). $$ It is useful to have a list of pairs (symbol, address) because in this way if we open the dumped PE with IDA we can search for that addresses $$ and set a name for the variable containing them. $$ .block { .sympath "SRV*c:\symcache*http://msdl.microsoft.com/download/symbols"; .reload } .block { .process /i ${$arg2} g .block { .reload } .block { .reload /user } r $t0 = 0; .foreach ( tok { lm m kernel32 } ){ r $t0 = @$t0 + 1; }; .if( $t0 > 8 ) { .printf "kernel32"; .block { .shell -i- -ci "x kernel32!*" findstr "!">${$arg1} }; } r $t0 = 0; .foreach ( tok { lm m user32 } ){ r $t0 = @$t0 + 1; }; .if( $t0 > 8 ) { .printf "user32"; .block { .shell -i- -ci "x user32!*" findstr "!">>${$arg1} }; } r $t0 = 0; .foreach ( tok { lm m advapi32 } ){ r $t0 = @$t0 + 1; }; .if( $t0 > 8 ) { .printf "advapi32"; .block { .shell -i- -ci "x advapi32!*" findstr "!">>${$arg1} }; } r $t0 = 0; .foreach ( tok { lm m ws2_32 } ){ r $t0 = @$t0 + 1; }; .if( $t0 > 8 ) { .printf "ws2_32"; .block { .shell -i- -ci "x ws2_32!*" findstr "!">>${$arg1} }; } r $t0 = 0; .foreach ( tok { lm m wininet } ){ r $t0 = @$t0 + 1; }; .if( $t0 > 8 ) { .printf "wininet"; .block { .shell -i- -ci "x wininet!*" findstr "!">>${$arg1} }; } r $t0 = 0; .foreach ( tok { lm m ntdll } ){ r $t0 = @$t0 + 1; }; .if( $t0 > 8 ) { .printf "ntdll"; .block { .shell -i- -ci "x ntdll!*" findstr "!">>${$arg1} }; } r $t0 = 0; .foreach ( tok { lm m crypt32 } ){ r $t0 = @$t0 + 1; }; .if( $t0 > 8 ) { .printf "crypt32"; .block { .shell -i- -ci "x crypt32!*" findstr "!">>${$arg1} }; } r $t0 = 0; .foreach ( tok { lm m dnsapi } ){ r $t0 = @$t0 + 1; }; .if( $t0 > 8 ) { .printf "dnsapi"; .block { .shell -i- -ci "x dnsapi!*" findstr "!">>${$arg1} }; } r $t0 = 0; .foreach ( tok { lm m shlwapi } ){ r $t0 = @$t0 + 1; }; .if( $t0 > 8 ) { .printf "shlwapi"; .block { .shell -i- -ci "x shlwapi!*" findstr "!">>${$arg1} }; } r $t0 = 0; .foreach ( tok { lm m winhttp } ){ r $t0 = @$t0 + 1; }; .if( $t0 > 8 ) { .printf "winhttp"; .block { .shell -i- -ci "x winhttp!*" findstr "!">>${$arg1} }; } r $t0 = 0; .foreach ( tok { lm m sysdm } ){ r $t0 = @$t0 + 1; }; .if( $t0 > 8 ) { .printf "sysdm"; .block { .shell -i- -ci "x sysdm!*" findstr "!">>${$arg1} }; } r $t0 = 0; .foreach ( tok { lm m rpcrt4 } ){ r $t0 = @$t0 + 1; }; .if( $t0 > 8 ) { .printf "rpcrt4"; .block { .shell -i- -ci "x rpcrt4!*" findstr "!">>${$arg1} }; } r $t0 = 0; .foreach ( tok { lm m iphlpapi } ){ r $t0 = @$t0 + 1; }; .if( $t0 > 8 ) { .printf "iphlpapi"; .block { .shell -i- -ci "x iphlpapi!*" findstr "!">>${$arg1} }; } r $t0 = 0; .foreach ( tok { lm m powrprof } ){ r $t0 = @$t0 + 1; }; .if( $t0 > 8 ) { .printf "powrprof"; .block { .shell -i- -ci "x powrprof!*" findstr "!">>${$arg1} }; } r $t0 = 0; .foreach ( tok { lm m gdi32 } ){ r $t0 = @$t0 + 1; }; .if( $t0 > 8 ) { .printf "gdi32"; .block { .shell -i- -ci "x gdi32!*" findstr "!">>${$arg1} }; } r $t0 = 0; .foreach ( tok { lm m msvcrt } ){ r $t0 = @$t0 + 1; }; .if( $t0 > 8 ) { .printf "msvcrt"; .block { .shell -i- -ci "x msvcrt*!*" findstr "!">>${$arg1} }; } r $t0 = 0; .foreach ( tok { lm m msvcp } ){ r $t0 = @$t0 + 1; }; .if( $t0 > 8 ) { .printf "msvcp"; .block { .shell -i- -ci "x msvcp*!*" findstr "!">>${$arg1} }; } r $t0 = 0; .foreach ( tok { lm m winmm } ){ r $t0 = @$t0 + 1; }; .if( $t0 > 8 ) { .printf "winmn"; .block { .shell -i- -ci "x winmm!*" findstr "!">>${$arg1} }; } r $t0 = 0; .foreach ( tok { lm m gdiplus } ){ r $t0 = @$t0 + 1; }; .if( $t0 > 8 ) { .printf "gdiplus"; .block { .shell -i- -ci "x gdiplus!*" findstr "!">>${$arg1} }; } r $t0 = 0; .foreach ( tok { lm m psapi } ){ r $t0 = @$t0 + 1; }; .if( $t0 > 8 ) { .printf "psapi"; .block { .shell -i- -ci "x psapi!*" findstr "!">>${$arg1} }; } r $t0 = 0; .foreach ( tok { lm m urlmon } ){ r $t0 = @$t0 + 1; }; .if( $t0 > 8 ) { .printf "urlmon"; .block { .shell -i- -ci "x urlmon!*" findstr "!">>${$arg1} }; } r $t0 = 0; .foreach ( tok { lm m netapi } ){ r $t0 = @$t0 + 1; }; .if( $t0 > 8 ) { .printf "netapi"; .block { .shell -i- -ci "x netapi!*" findstr "!">>${$arg1} }; } r $t0 = 0; .foreach ( tok { lm m ole32 } ){ r $t0 = @$t0 + 1; }; .if( $t0 > 8 ) { .printf "netapi"; .block { .shell -i- -ci "x ole32!*" findstr "!">>${$arg1} }; } } ================================================ FILE: WinDbg/dump_process_symbols_to_file_preselect_curproc.wdbg ================================================ $$ $$ Author: Javier Vicente Vallejo $$ Twitter: @vallejocc $$ Web: http://www.vallejo.cc $$ $$ $$ $$>a $$ $$ This simple script will dump to a file symbols of a preselection of common dlls of the given process. $$ If you dump a PE from memory, it could have variables pointing to symbols (for example, api addresses that it got with GetProcAddress, etc...). $$ It is useful to have a list of pairs (symbol, address) because in this way if we open the dumped PE with IDA we can search for that addresses $$ and set a name for the variable containing them. $$ .block { .sympath "SRV*c:\symcache*http://msdl.microsoft.com/download/symbols"; .reload } .block { .block { .reload } .block { .reload /user } r $t0 = 0; .foreach ( tok { lm m kernel32 } ){ r $t0 = @$t0 + 1; }; .if( $t0 > 8 ) { .printf "kernel32"; .block { .shell -i- -ci "x kernel32!*" findstr "!">${$arg1} }; } r $t0 = 0; .foreach ( tok { lm m user32 } ){ r $t0 = @$t0 + 1; }; .if( $t0 > 8 ) { .printf "user32"; .block { .shell -i- -ci "x user32!*" findstr "!">>${$arg1} }; } r $t0 = 0; .foreach ( tok { lm m advapi32 } ){ r $t0 = @$t0 + 1; }; .if( $t0 > 8 ) { .printf "advapi32"; .block { .shell -i- -ci "x advapi32!*" findstr "!">>${$arg1} }; } r $t0 = 0; .foreach ( tok { lm m ws2_32 } ){ r $t0 = @$t0 + 1; }; .if( $t0 > 8 ) { .printf "ws2_32"; .block { .shell -i- -ci "x ws2_32!*" findstr "!">>${$arg1} }; } r $t0 = 0; .foreach ( tok { lm m wininet } ){ r $t0 = @$t0 + 1; }; .if( $t0 > 8 ) { .printf "wininet"; .block { .shell -i- -ci "x wininet!*" findstr "!">>${$arg1} }; } r $t0 = 0; .foreach ( tok { lm m ntdll } ){ r $t0 = @$t0 + 1; }; .if( $t0 > 8 ) { .printf "ntdll"; .block { .shell -i- -ci "x ntdll!*" findstr "!">>${$arg1} }; } r $t0 = 0; .foreach ( tok { lm m crypt32 } ){ r $t0 = @$t0 + 1; }; .if( $t0 > 8 ) { .printf "crypt32"; .block { .shell -i- -ci "x crypt32!*" findstr "!">>${$arg1} }; } r $t0 = 0; .foreach ( tok { lm m dnsapi } ){ r $t0 = @$t0 + 1; }; .if( $t0 > 8 ) { .printf "dnsapi"; .block { .shell -i- -ci "x dnsapi!*" findstr "!">>${$arg1} }; } r $t0 = 0; .foreach ( tok { lm m shlwapi } ){ r $t0 = @$t0 + 1; }; .if( $t0 > 8 ) { .printf "shlwapi"; .block { .shell -i- -ci "x shlwapi!*" findstr "!">>${$arg1} }; } r $t0 = 0; .foreach ( tok { lm m winhttp } ){ r $t0 = @$t0 + 1; }; .if( $t0 > 8 ) { .printf "winhttp"; .block { .shell -i- -ci "x winhttp!*" findstr "!">>${$arg1} }; } r $t0 = 0; .foreach ( tok { lm m sysdm } ){ r $t0 = @$t0 + 1; }; .if( $t0 > 8 ) { .printf "sysdm"; .block { .shell -i- -ci "x sysdm!*" findstr "!">>${$arg1} }; } r $t0 = 0; .foreach ( tok { lm m rpcrt4 } ){ r $t0 = @$t0 + 1; }; .if( $t0 > 8 ) { .printf "rpcrt4"; .block { .shell -i- -ci "x rpcrt4!*" findstr "!">>${$arg1} }; } r $t0 = 0; .foreach ( tok { lm m iphlpapi } ){ r $t0 = @$t0 + 1; }; .if( $t0 > 8 ) { .printf "iphlpapi"; .block { .shell -i- -ci "x iphlpapi!*" findstr "!">>${$arg1} }; } r $t0 = 0; .foreach ( tok { lm m powrprof } ){ r $t0 = @$t0 + 1; }; .if( $t0 > 8 ) { .printf "powrprof"; .block { .shell -i- -ci "x powrprof!*" findstr "!">>${$arg1} }; } r $t0 = 0; .foreach ( tok { lm m gdi32 } ){ r $t0 = @$t0 + 1; }; .if( $t0 > 8 ) { .printf "gdi32"; .block { .shell -i- -ci "x gdi32!*" findstr "!">>${$arg1} }; } r $t0 = 0; .foreach ( tok { lm m msvcrt } ){ r $t0 = @$t0 + 1; }; .if( $t0 > 8 ) { .printf "msvcrt"; .block { .shell -i- -ci "x msvcrt*!*" findstr "!">>${$arg1} }; } r $t0 = 0; .foreach ( tok { lm m msvcp } ){ r $t0 = @$t0 + 1; }; .if( $t0 > 8 ) { .printf "msvcp"; .block { .shell -i- -ci "x msvcp*!*" findstr "!">>${$arg1} }; } r $t0 = 0; .foreach ( tok { lm m winmm } ){ r $t0 = @$t0 + 1; }; .if( $t0 > 8 ) { .printf "winmn"; .block { .shell -i- -ci "x winmm!*" findstr "!">>${$arg1} }; } r $t0 = 0; .foreach ( tok { lm m gdiplus } ){ r $t0 = @$t0 + 1; }; .if( $t0 > 8 ) { .printf "gdiplus"; .block { .shell -i- -ci "x gdiplus!*" findstr "!">>${$arg1} }; } r $t0 = 0; .foreach ( tok { lm m psapi } ){ r $t0 = @$t0 + 1; }; .if( $t0 > 8 ) { .printf "psapi"; .block { .shell -i- -ci "x psapi!*" findstr "!">>${$arg1} }; } r $t0 = 0; .foreach ( tok { lm m urlmon } ){ r $t0 = @$t0 + 1; }; .if( $t0 > 8 ) { .printf "urlmon"; .block { .shell -i- -ci "x urlmon!*" findstr "!">>${$arg1} }; } r $t0 = 0; .foreach ( tok { lm m netapi } ){ r $t0 = @$t0 + 1; }; .if( $t0 > 8 ) { .printf "netapi"; .block { .shell -i- -ci "x netapi!*" findstr "!">>${$arg1} }; } r $t0 = 0; .foreach ( tok { lm m ole32 } ){ r $t0 = @$t0 + 1; }; .if( $t0 > 8 ) { .printf "netapi"; .block { .shell -i- -ci "x ole32!*" findstr "!">>${$arg1} }; } } ================================================ FILE: WinDbg/find_injected_pe_rmem.wdbg ================================================ $$ $$ Author: Javier Vicente Vallejo $$ Twitter: @vallejocc $$ Web: http://www.vallejo.cc $$ $$ $$>aAnalyzing process ${processes_tok}\n" r stage = 0 .process /i ${processes_tok} g .block { .reload /user } $$search for memory blocks $$careful: $$ when the baseaddress is over 0x10000000 findstr tokens will be: $$ 93:7640:20010000 $$ 2002c000 $$ 1c000 $$ UserRange $$ ... $$ however if the addess is under 0x10000000: $$ 25:1364: $$ 60000 $$ 61000 $$ 1000 $$ UserRange $$ The reason for this its windbg puts spaces before the base address when it hasnt 8 characters to complete 8 characters, but if the address is $$ ???????? then it doesnt put spaces. We need to have in mind both case, and this is the reason of the stages of the next code $$ .foreach (tok { .shell -ci "!address" findstr /N /O "UserRange.*Read" }) { r isPossiblePE = 0 r isDosMessageBased = 0 .printf "${tok}\n" .if($spat("${tok}","*:*:*")!=0) { r stage = 1 } .elsif(${stage}==1) { r prev1 = ${tok} r stage = 2 } .elsif(${stage}==2) { r prev2 = ${tok} r stage = 3 } .elsif(${stage}==3) { .if($spat("${tok}","*UserRange*")!=0) { r baseaddr = prev1 - prev2 r baseaddrlen = prev2 r stage = 5 } .else { r baseaddr = prev1 r baseaddrlen = ${tok} r stage = 4 } } .elsif(${stage}==4) { r stage = 5 } .elsif(${stage}==5) { $$for each block, check MZ / PE r @$t0 = baseaddr .if(@$t0!=0) { .printf "base %x\n", @$t0 .pagein /p ${processes_tok} @$t0 g $$!address @$t0 .if($vvalid(@$t0, 2)==1) { .printf "valid base address\n" .printf "pe %x\n", @$t0+@@c++(((nt!_IMAGE_DOS_HEADER * )@$t0)->e_lfanew) .if($vvalid(@$t0+@@c++(((nt!_IMAGE_DOS_HEADER * )@$t0)->e_lfanew), 2)==1) { .printf "valid pe header address\n" .if(wo(@$t0)==0x5a4d & dwo(@$t0+@@c++(((nt!_IMAGE_DOS_HEADER * )@$t0)->e_lfanew))==0x454E) { $$We can find MZ / NE images, we ignore them } .elsif(wo(@$t0)==0x5a4d & dwo(@$t0+@@c++(((nt!_IMAGE_DOS_HEADER * )@$t0)->e_lfanew))==0x4550) { $$if MZ and PE signatures, valid pe header .printf "valid pe header\n" r isPossiblePE = 1 } .else { $$if not MZ or not PE signature, but msdos message is found, its a possible pe header r temp = 0 .foreach (tok2 { s -a @$t0 L 0x80 "This program cannot " }) { r temp = 1 } .if(${temp}==1) { .printf "possible pe header\n" r isPossiblePE = 1 r isDosMessageBased = 1 } } } } $$if we have found a possible PE in a memory zone with ReadWriteExecute protection, we will check if the base address is in the list of loaded module .if(${isPossiblePE}==1) { .printf "is possible module %x\n", @$t0 r temp = 0 $$search for valid from "is not valid address" .foreach (tok3 { .shell -ci "!lmi @$t0" findstr /N /O "valid.address" }) { r temp = ${temp} + 1 } .printf "%x\n", ${temp} $$ "is not valid address" was found .if(${temp} > 3) { $$ there are some modules that !lmi command is answering: is not valid address, however they seems to be valid loaded modules (not interesing for us). $$ However if we consults information about the address with !address we find things as: $$ Memory Usage: Section [\WINDOWS\System32\blablabla.mui] (it happens usually with .mui files, but not only with them $$ We will discard results of !address with .dll], .mui] and .exe] r temp = 0 .foreach (tok4 { .shell -ci "!address @$t0" findstr /N /O /R /I "\.mui\]" }) { r temp = ${temp} + 1 } r temp2 = 0 .foreach (tok5 { .shell -ci "!address @$t0" findstr /N /O /R /I "\.dll\]" }) { r temp2 = ${temp2} + 1 } r temp3 = 0 .foreach (tok6 { .shell -ci "!address @$t0" findstr /N /O /R /I "\.exe\]" }) { r temp3 = ${temp3} + 1 } .printf "search !address .mui %x\n", ${temp} .printf "search !address .dll %x\n", ${temp2} .printf "search !address .exe %x\n", ${temp3} .if(${temp} < 4 and ${temp2} < 4 and ${temp3} < 4) { .if(${isDosMessageBased}==0) { .printf /D "---------------------------------------------------------------------------\n" .printf /D "Process: ${processes_tok} base: %x -> Possible injected or unpacked PE\n", @$t0 .printf /D "---------------------------------------------------------------------------\n" } .else { .printf /D "-------------------------------------------------------------------------------------------------------------------------------------\n" .printf /D "Process: ${processes_tok} base: %x -> Possible injected or unpacked PE, based on the dos header message: This program cannot...\n", @$t0 .printf /D "--------------------------------------------------------------------------------------------------------------------------------------\n" } } } } r stage = 0 } } } r stage = 0 } } ad stage ad temp ad temp2 ad temp3 ad isPossiblePE ad isDosMessageBased ad prev1 ad prev2 ad baseaddr ad baseaddrlen } ================================================ FILE: WinDbg/find_injected_pe_rwemem.wdbg ================================================ $$ $$ Author: Javier Vicente Vallejo $$ Twitter: @vallejocc $$ Web: http://www.vallejo.cc $$ $$ $$>aAnalyzing process ${processes_tok}\n" r stage = 0 .process /i ${processes_tok} g .block { .reload /user } $$search for memory blocks with ReadWriteExecute protection $$careful: $$ when the baseaddress is over 0x10000000 findstr tokens will be: $$ 93:7640:20010000 $$ 2002c000 $$ 1c000 $$ UserRange $$ ... $$ however if the addess is under 0x10000000: $$ 25:1364: $$ 60000 $$ 61000 $$ 1000 $$ UserRange $$ The reason for this its windbg puts spaces before the base address when it hasnt 8 characters to complete 8 characters, but if the address is $$ ???????? then it doesnt put spaces. We need to have in mind both case, and this is the reason of the stages of the next code $$ .foreach (tok { .shell -ci "!address" findstr /N /O /R /C:"UserRange.*ExecuteReadWrite" /C:"UserRange.*ReadWriteExecute" }) { r isPossiblePE = 0 r isDosMessageBased = 0 .printf "${tok}\n" .if($spat("${tok}","*:*:*")!=0) { r stage = 1 } .elsif(${stage}==1) { r prev1 = ${tok} r stage = 2 } .elsif(${stage}==2) { r prev2 = ${tok} r stage = 3 } .elsif(${stage}==3) { .if($spat("${tok}","*UserRange*")!=0) { r baseaddr = prev1 - prev2 r baseaddrlen = prev2 r stage = 5 } .else { r baseaddr = prev1 r baseaddrlen = ${tok} r stage = 4 } } .elsif(${stage}==4) { r stage = 5 } .elsif(${stage}==5) { $$for each block with ReadWriteExecute protection, check MZ / PE r @$t0 = baseaddr .if(@$t0!=0) { .printf "base %x\n", @$t0 .pagein /p ${processes_tok} @$t0 g $$!address @$t0 .if($vvalid(@$t0, 2)==1) { .printf "valid base address\n" .printf "pe %x\n", @$t0+@@c++(((nt!_IMAGE_DOS_HEADER * )@$t0)->e_lfanew) .if($vvalid(@$t0+@@c++(((nt!_IMAGE_DOS_HEADER * )@$t0)->e_lfanew), 2)==1) { .printf "valid pe header address\n" .if(wo(@$t0)==0x5a4d & dwo(@$t0+@@c++(((nt!_IMAGE_DOS_HEADER * )@$t0)->e_lfanew))==0x454E) { $$We can find MZ / NE images, we ignore them } .elsif(wo(@$t0)==0x5a4d & dwo(@$t0+@@c++(((nt!_IMAGE_DOS_HEADER * )@$t0)->e_lfanew))==0x4550) { $$if MZ and PE signatures, valid pe header .printf "valid pe header\n" r isPossiblePE = 1 } .else { $$if not MZ or not PE signature, but msdos message is found, its a possible pe header r temp = 0 .foreach (tok2 { s -a @$t0 L 0x80 "This program cannot " }) { r temp = 1 } .if(${temp}==1) { .printf "possible pe header\n" r isPossiblePE = 1 r isDosMessageBased = 1 } } } } $$if we have found a possible PE in a memory zone with ReadWriteExecute protection, we will check if the base address is in the list of loaded module .if(${isPossiblePE}==1) { .printf "is possible module %x\n", @$t0 r temp = 0 $$search for valid from "is not valid address" .foreach (tok3 { .shell -ci "!lmi @$t0" findstr /N /O "valid.address" }) { r temp = ${temp} + 1 } .printf "%x\n", ${temp} $$ "is not valid address" was found .if(${temp} > 3) { $$ there are some modules that !lmi command is answering: is not valid address, however they seems to be valid loaded modules (not interesing for us). $$ However if we consults information about the address with !address we find things as: $$ Memory Usage: Section [\WINDOWS\System32\blablabla.mui] (it happens usually with .mui files, but not only with them $$ We will discard results of !address with .dll], .mui] and .exe] r temp = 0 .foreach (tok4 { .shell -ci "!address @$t0" findstr /N /O /R /I "\.mui\]" }) { r temp = ${temp} + 1 } r temp2 = 0 .foreach (tok5 { .shell -ci "!address @$t0" findstr /N /O /R /I "\.dll\]" }) { r temp2 = ${temp2} + 1 } r temp3 = 0 .foreach (tok6 { .shell -ci "!address @$t0" findstr /N /O /R /I "\.exe\]" }) { r temp3 = ${temp3} + 1 } .printf "search !address .mui %x\n", ${temp} .printf "search !address .dll %x\n", ${temp2} .printf "search !address .exe %x\n", ${temp3} .if(${temp} < 4 and ${temp2} < 4 and ${temp3} < 4) { .if(${isDosMessageBased}==0) { .printf /D "---------------------------------------------------------------------------\n" .printf /D "Process: ${processes_tok} base: %x -> Possible injected or unpacked PE\n", @$t0 .printf /D "---------------------------------------------------------------------------\n" } .else { .printf /D "-------------------------------------------------------------------------------------------------------------------------------------\n" .printf /D "Process: ${processes_tok} base: %x -> Possible injected or unpacked PE, based on the dos header message: This program cannot...\n", @$t0 .printf /D "--------------------------------------------------------------------------------------------------------------------------------------\n" } } } } r stage = 0 } } } r stage = 0 } } ad stage ad temp ad temp2 ad temp3 ad isPossiblePE ad isDosMessageBased ad prev1 ad prev2 ad baseaddr ad baseaddrlen } ================================================ FILE: WinDbg/find_injected_pe_search.wdbg ================================================ $$ $$ Author: Javier Vicente Vallejo $$ Twitter: @vallejocc $$ Web: http://www.vallejo.cc $$ $$ $$>aAnalyzing process ${processes_tok}\n" r stage = 0 .process /i ${processes_tok} g .block { .reload } .block { .reload /user } r ${stop} = 0 r ${baseSearch} = 0 .while(${stop} == 0) { .printf "searching base %x\n", ${baseSearch} r @$t0 = ${baseSearch} .foreach /ps 0x10 ( header {s @$t0 L?0x10000000 4d 5a 90 00} ) { r temp = 0 r @$t0 = ${header} .printf "MZ at ${header}\n" .if(poi(${header}+3c) < 0x2ff) { .if(poi(${header}+poi(${header}+3c)) == 0x00004550) { .printf "PE signature found\n" $$search for valid from: is not valid address .foreach (tok { .shell -ci "!lmi ${header}" findstr /N /O /R /I "valid.address" }) { r temp = ${temp} + 1 } .printf "search valid address %x\n", ${temp} $$ "is not valid address was found" .if(${temp} > 3) { $$ there are some modules that !lmi command is answering: is not valid address, however they seems to be valid loaded modules (not interesing for us). $$ However if we consults information about the address with !address we find things as: $$ Memory Usage: Section [\WINDOWS\System32\blablabla.mui] (it happens usually with .mui files, but not only with them $$ We will discard results of !address with .dll], .mui] and .exe] r temp = 0 .foreach (tok2 { .shell -ci "!address ${header}" findstr /N /O /R /I "\.mui\]" }) { r temp = ${temp} + 1 } r temp2 = 0 .foreach (tok3 { .shell -ci "!address ${header}" findstr /N /O /R /I "\.dll\]" }) { r temp2 = ${temp2} + 1 } r temp3 = 0 .foreach (tok4 { .shell -ci "!address ${header}" findstr /N /O /R /I "\.exe\]" }) { r temp3 = ${temp3} + 1 } .printf "search !address .mui %x\n", ${temp} .printf "search !address .dll %x\n", ${temp2} .printf "search !address .exe %x\n", ${temp3} .if(${temp} < 4 and ${temp2} < 4 and ${temp3} < 4) { .printf /D "---------------------------------------------------------------------------\n" .printf /D "Process: ${processes_tok} base: %x -> Possible injected or unpacked PE\n", @$t0 .printf /D "---------------------------------------------------------------------------\n" } } } .else { .printf "PE signature not found\n" } } .else { .printf "Invalid lfanew\n" } } .if(${baseSearch} >= 0x70000000) { .printf "next process\n" r ${stop} = 1 } r ${baseSearch} = ${baseSearch} + 0x10000000 } } } ad stage ad temp ad baseSearch ad stop ad temp2 ad temp3 } ================================================ FILE: WinDbg/find_objects_by_name.wdbg ================================================ $$ $$ Author: Javier Vicente Vallejo $$ Twitter: @vallejocc $$ Web: http://www.vallejo.cc $$ $$ $$>a $$ $$ Types: Event, Section, File, Port, Directory, SymbolicLink, Mutant, WindowStation, Semaphore, Key, Token, Process, Thread, Desktop, IoCompletion, Timer, Job, and WaitablePort $$ $$ If process == 0, all processes $$ If type == All, search all types $$ If name matching word == *, any name aS stage @$t19 aS lastproc @$t18 aS lastobject @$t17 aS lasttypevalid @$t16 .block { .sympath "SRV*c:\symcache*http://msdl.microsoft.com/download/symbols"; .reload } .block { $$ STAGE == 1 last token was PROCESS $$ STAGE == 2 last token was Directory $$ STAGE == 3 last token was Object: $$ STAGE == 4 last token was Name: $$ STAGE == 5 last token was Type: r stage = 0 .foreach (handle_tok { !handle 0 3 ${$arg1} ${$arg2} }) { $$ .printf "token = ${handle_tok}\n" .if($scmp("\${handle_tok}","\\")==0) { } .else { .if(${stage}==1) { .if($scmp("${handle_tok}","HANDLE")!=0) { $$ .printf "stage1 ${handle_tok}\n" r ${lastproc} = ${handle_tok} } r stage = 0 } .elsif(${stage}==2) { $$ if Directory found, skip next Object: word $$ .printf "stage2\n" r stage = 0 } .elsif(${stage}==3) { $$ .printf "stage3 ${handle_tok}\n" r ${lastobject} = ${handle_tok} r stage = 0 } .elsif(${stage}==4) { $$ .printf "stage4\n" .if(${lasttypevalid}==1) { r @$t0 = 0 .if($scmp("*","${$arg3}")==0) { r @$t0 = 4 } .else { .foreach (toktemp { .shell -ci "!object ${lastobject}" findstr /N /O "${$arg3}" }) { r @$t0 = @$t0 + 1 } } .if(@$t0 > 3) { .printf "Process %x ", ${lastproc} !object ${lastobject} } } r stage = 0 } .elsif(${stage}==5) { r stage = 6 } .elsif(${stage}==6) { $$ .printf "stage6 ${handle_tok}\n" r lasttypevalid = 0 .if($scmp("${handle_tok}","${$arg2}")==0) { r lasttypevalid = 1 } .if($scmp("All","${$arg2}")==0) { r lasttypevalid = 1 } r stage = 0 } .elsif($scmp("${handle_tok}","PROCESS")==0) { $$ .printf "PROCESS\n" r stage = 1 } .elsif($scmp("${handle_tok}","Directory")==0) { $$ .printf "Image:\n" r stage = 2 } .elsif($scmp("${handle_tok}","Object:")==0) { $$ .printf "Object:\n" r stage = 3 } .elsif($scmp("${handle_tok}","Name:")==0) { $$ .printf "Name:\n" r stage = 4 } .elsif($scmp("${handle_tok}","Type:")==0) { $$ .printf "Type:\n" r stage = 5 } } } ad stage ad lastproc ad lastobject ad lasttypevalid } ================================================ FILE: WinDbg/hang_exited_processes.wdbg ================================================ $$ $$ Author: Javier Vicente Vallejo $$ Twitter: @vallejocc $$ Web: http://www.vallejo.cc $$ $$ $$>a .logopen ${$arg1}\hang_exited_processes.start .printf "start" .logclose .block { .sympath "SRV*c:\symcache*http://msdl.microsoft.com/download/symbols"; .reload } r @$t0 = nt!KeDelayExecutionThread r @$t1 = nt!NtTerminateProcess $$push [interval] eb @$t1 0x68 ed @$t1+1 @$t1+0x24 $$push FALSE eb @$t1+5 0x68 ed @$t1+6 0x0 $$push UserMode eb @$t1+0xa 0x68 ed @$t1+0xb 0x1 $$push 0 eb @$t1+0xf 0x68 ed @$t1+0x10 0x0 $$push nt!KeDelayExecutionThread / ret eb @$t1+0x14 0x68 ed @$t1+0x15 @$t0 eb @$t1+0x19 0xc3 $$interval ed @$t1+0x24 0x00000000 ed @$t1+0x28 0xf0000000 .logopen ${$arg1}\hang_exited_processes.end .printf "end" .logclose ================================================ FILE: WinDbg/isx64.wdbg ================================================ $$ $$ Author: Javier Vicente Vallejo $$ Twitter: @vallejocc $$ Web: http://www.vallejo.cc $$ aS x64arch $t0 .block { .sympath "SRV*c:\symcache*http://msdl.microsoft.com/download/symbols"; .reload } .block { $$is x64? r x64arch = 0; .foreach( tok { .effmach } ) { .if($scmp("${tok}","x64")==0) { r x64arch = 1; .break; }; }; } ================================================ FILE: WinDbg/lengthDisassembler.wdbg ================================================ $$ $$ Author: Javier Vicente Vallejo $$ Twitter: @vallejocc $$ Web: http://www.vallejo.cc $$ u ${$arg1} L 1 r @$t0 = 0 .foreach(myVariable {u ${$arg1} L 1}) { r @$t0 = @$t0 + 1 .if(@$t0==3) { .if($spat("${myVariable}", "??")==1){.printf "1"} .if($spat("${myVariable}", "????")==1){.printf "2"} .if($spat("${myVariable}", "??????")==1){.printf "3"} .if($spat("${myVariable}", "????????")==1){.printf "4"} .if($spat("${myVariable}", "??????????")==1){.printf "5"} .if($spat("${myVariable}", "????????????")==1){.printf "6"} .if($spat("${myVariable}", "??????????????")==1){.printf "7"} .if($spat("${myVariable}", "????????????????")==1){.printf "8"} .if($spat("${myVariable}", "??????????????????")==1){.printf "9"} } } ================================================ FILE: WinDbg/load_code_to_kernel_memory.wdbg ================================================ $$ $$ Author: Javier Vicente Vallejo $$ Twitter: @vallejocc $$ Web: http://www.vallejo.cc $$ $$ $$>a .block { .sympath "SRV*c:\symcache*http://msdl.microsoft.com/download/symbols"; .reload /f } $$set a breakpoint on a common function that is executed frequently (NtCreateFile for example) for hijacking the thread for a while ba e1 nt!NtCreateFile g .printf "${$arg1}" .printf "${$arg2}" .printf "${$arg3}" bc * $$save original esp register and stack parameters that we are going to overwrite when calling ExAllocatePool and PsCreateSystemThread r @$t19 = (poi esp) r @$t18 = (poi esp+4) r @$t17 = (poi esp+8) r @$t16 = (poi esp+c) r @$t15 = (poi esp+10) r @$t14 = (poi esp+14) r @$t13 = (poi esp+18) r @$t12 = (poi esp+1c) r @$t11 = esp $$change the stack with the parameters that we need for ExAllocatePool ed (esp+4) 0 ed (esp+8) ${$arg2} $$hijack the thread running on NtCreateFile to execute ExAllocatePool u nt!ExAllocatePool dd esp r eip = nt!ExAllocatePool $$steps until the ret instruction is found. We cant execute step out (gu) because we would get a 0x30 bugcheck, this is the reason: $$ "This bug check occurs when there's a stack underflow detected when restoring context coming out of a trap. There's a check to $$ validate that the current ESP is less than the ESP saved in the trap frame. If current_esp < saved_esp, bugcheck. " .while (1) { p r @$t10 = (poi eip) r @$t10 = @$t10 & 0x000000ff .if (@$t10 == 0xc2) { .break } } r @$t0 = eax .printf "allocated mem: %x\n", @$t0 $$load code from file to allocated memory $$careful: .readmem will read blocks of 0x1000 bytes. For example, if your file to load has 0x2800 bytes, .readmem will load 0x2000 bytes only. $$You will need to complete the file with 0x800 additional trash bytes to load the full code .readmem ${$arg1} @$t0 $$ @$t1 = allocated mem membase, code is read r @$t1 = @$t0 $$Now we are going to create a kernel thread at @$t1 + arg3 (membase + startroutine_offset) $$ NTSTATUS PsCreateSystemThread( $$ _Out_ PHANDLE ThreadHandle, $$ _In_ ULONG DesiredAccess, $$ _In_opt_ POBJECT_ATTRIBUTES ObjectAttributes, $$ _In_opt_ HANDLE ProcessHandle, $$ _Out_opt_ PCLIENT_ID ClientId, $$ _In_ PKSTART_ROUTINE StartRoutine, $$ _In_opt_ PVOID StartContext $$ ); ed (esp+1c) 0 ed (esp+18) @$t1+${$arg3} ed (esp+14) 0 ed (esp+10) 0 ed (esp+c) 0 ed (esp+8) 0 $$ThreadHandle inout, we use the memory of the parameter StartContext that we dont need ed (esp+4) (esp+1c) $$set a breakpoint where the thread is going to be created ba e1 @$t1+${$arg3} u nt!PsCreateSystemThread dd esp r eip = nt!PsCreateSystemThread $$again steps until ret instruction is found .while (1) { p r @$t10 = (poi eip) r @$t10 = @$t10 & 0x000000ff .if (@$t10 == 0xc2) { .break } } $$restore original registers and stack to continue the execution with no problems r eip = nt!NtCreateFile r esp = @$t11 ed esp @$t19 ed (esp+4) @$t18 ed (esp+8) @$t17 ed (esp+c) @$t16 ed (esp+10) @$t15 ed (esp+14) @$t14 ed (esp+18) @$t13 ed (esp+1c) @$t12 g ================================================ FILE: WinDbg/load_swish.wdbg ================================================ $$ $$ Author: Javier Vicente Vallejo $$ Twitter: @vallejocc $$ Web: http://www.vallejo.cc $$ !load C:\tools\@scripts\windbg\SwishDbgExt-master\Debug\x64\SwishDbgExt.dll ================================================ FILE: WinDbg/log_processes.wdbg ================================================ $$ $$ Author: Javier Vicente Vallejo $$ Twitter: @vallejocc $$ Web: http://www.vallejo.cc $$ $$ $$>a .logopen ${$arg1}\log_processes.start .logclose .block { .sympath "SRV*c:\symcache*http://msdl.microsoft.com/download/symbols"; .reload } .logopen ${$arg1}\processes_wdbglog.txt !process 0 0 .logclose .logopen ${$arg1}\log_processes.end .logclose ================================================ FILE: WinDbg/monitoring_breakpoints.wdbg ================================================ $$ $$ Author: Javier Vicente Vallejo $$ Twitter: @vallejocc $$ Web: http://www.vallejo.cc $$ $$ $$>a%x - CreateFileA('%ma')\\n\", poi($csp), poi($csp+4) ; gc" bp /p ${$arg1} kernel32!CreateFileW ".printf /D \"%x - CreateFileW('%mu')\\n\", poi($csp), poi($csp+4) ; gc" bp /p ${$arg1} advapi32!RegOpenKeyA ".printf /D \"%x - RegOpenKeyA(%x,%ma)\\n\", poi($csp), poi($csp+4), poi($csp+2*4) ; gc" bp /p ${$arg1} advapi32!RegOpenKeyW ".printf /D \"%x - RegOpenKeyW(%x,%mu)\\n\", poi($csp), poi($csp+4), poi($csp+2*4) ; gc" bp /p ${$arg1} advapi32!RegCreateKeyA ".printf /D \"%x - RegCreateKeyA(%x,%ma)\\n\", poi($csp), poi($csp+4), poi($csp+2*4) ; gc" bp /p ${$arg1} advapi32!RegCreateKeyW ".printf /D \"%x - RegCreateKeyW(%x,%mu)\\n\", poi($csp), poi($csp+4), poi($csp+2*4) ; gc" bp /p ${$arg1} advapi32!RegSetValueA ".printf /D \"%x - RegSetValueA(%x,%ma)\\n\", poi($csp), poi($csp+4), poi($csp+2*4) ; gc" bp /p ${$arg1} advapi32!RegSetValueW ".printf /D \"%x - RegSetValueW(%x,%mu)\\n\", poi($csp), poi($csp+4), poi($csp+2*4) ; gc" bp /p ${$arg1} advapi32!RegSetValueExA ".printf /D \"%x - RegSetValueExA(%x,%ma)\\n\", poi($csp), poi($csp+4), poi($csp+2*4) ; gc" bp /p ${$arg1} advapi32!RegSetValueExW ".printf /D \"%x - RegSetValueExW(%x,%mu)\\n\", poi($csp), poi($csp+4), poi($csp+2*4) ; gc" bp /p ${$arg1} kernel32!LoadLibraryW ".printf /D \"%x - LoadLibraryW('%mu')\\n\", poi($csp), poi($csp+4) ; gc" bp /p ${$arg1} kernel32!WriteProcessMemory ".printf /D \"%x - WriteProcessMemory(%x, %x, %ma)->\", poi($csp), poi($csp+4), poi($csp+4*2), poi($csp+3*4); $$>a<$$>a%x - CreateRemoteThread(%x, %x)->\", poi($csp), poi($csp+4), poi($csp+4*4); $$>a%x - CreateProcessA(%ma,%ma)\\n\", poi($csp), poi($csp+4), poi($csp+2*4) ; gc" bp /p ${$arg1} kernel32!CreateProcessW ".printf /D \"%x - CreateProcessW(%mu,%mu)\\n\", poi($csp), poi($csp+4), poi($csp+2*4) ; gc" bp /p ${$arg1} wininet!InternetOpenA ".printf /D \"%x - InternetOpenA()\\n\", poi($csp); gc" bp /p ${$arg1} wininet!InternetOpenW ".printf /D \"%x - InternetOpenW()\\n\", poi($csp); gc" bp /p ${$arg1} wininet!InternetOpenUrlA ".printf /D \"%x - InternetOpenA(%ma,%ma)\\n\", poi($csp), poi($csp+2*4), poi($csp+3*4) ; gc" bp /p ${$arg1} wininet!InternetOpenUrlW ".printf /D \"%x - InternetOpenW(%mu,%mu)\\n\", poi($csp), poi($csp+2*4), poi($csp+3*4) ; gc" $$bp /p ${$arg1} kernel32!CreateWaitableTimerW ".printf /D \"%x - CreateWaitableTimerW('%mu')\\n\", poi($csp), poi($csp+3*4) ; gc" $$bp /p ${$arg1} kernel32!GetTickCount "$$ .printf /D \"%x - GetTickCount()\\n\" , poi($csp); r eax = 0 ; r eip = poi($csp) ; r esp = esp + 4 ; gc" $$bp /p ${$arg1} kernel32!Sleep ".printf /D \"%x - Sleep(%ds)\\n\", poi($csp), poi($csp+4)/0n1000 ; r eip = poi($csp) ; r esp = esp + 8 ; gc" bp /p ${$arg1} kernel32!ExitProcess ".printf /D \"%x - ExitProcess()\\n\", poi($csp); gc" bp /p ${$arg1} kernel32!VirtualProtect ".printf /D \"%x - VirtualProtect(%p, %x, %x)\\n\", poi($csp), poi($csp+4), poi($csp+2*4), poi($csp+3*4) ; gc" bp /p ${$arg1} kernel32!VirtualProtectEx ".printf /D \"%x - VirtualProtectEx(%p, %x, %x)->\", poi($csp), poi($csp+2*4), poi($csp+3*4), poi($csp+4*4) ; $$>a%x - CryptGenKey(%x, %x)\\n\", poi($csp), poi($csp+2*4), poi($csp+3*4) ; gc" bp /p ${$arg1} advapi32!CryptImportKey ".printf /D \"%x - CryptImportKey(%p, %x, %x)\\n\", poi($csp), poi($csp+2*4), poi($csp+3*4), poi($csp+5*4); gc" bp /p ${$arg1} advapi32!CryptDecrypt ".printf /D \"%x - CryptDecrypt(%x, %p, %p)\\n\", poi($csp), poi($csp+4*4), poi($csp+5*4), poi($csp+6*4); gc" bp /p ${$arg1} advapi32!CryptEncrypt ".printf /D \"%x - CryptEncrypt(%x, %p, %p, %x)\\n\", poi($csp), poi($csp+4*4), poi($csp+5*4), poi($csp+6*4), poi($csp+7*4); gc" $$bp /p ${$arg1} kernel32!CreateToolhelp32Snapshot ".printf /D \"%x - CreateToolhelp32Snapshot(%x, %x)\\n\", poi($csp), poi($csp+4), poi($csp+2*4) ; gc" $$bp /p ${$arg1} kernel32!Process32First ".printf /D \"%x - Process32First()\\n\", poi($csp); gc" $$bp /p ${$arg1} kernel32!Process32Next ".printf /D \"%x - Process32Next()\\n\", poi($csp); gc" $$bp /p ${$arg1} kernel32!Thread32First ".printf /D \"%x - Thread32First()\\n\", poi($csp); gc" $$bp /p ${$arg1} kernel32!Thread32Next ".printf /D \"%x - Thread32Next()\\n\", poi($csp); gc" $$bp /p ${$arg1} kernel32!Module32First ".printf /D \"%x - Module32First()\\n\", poi($csp); gc" $$bp /p ${$arg1} kernel32!Module32Next ".printf /D \"%x - Module32Next()\\n\", poi($csp); gc" sxd sse } ================================================ FILE: WinDbg/pagein_range.wdbg ================================================ $$ $$ Author: Javier Vicente Vallejo $$ Twitter: @vallejocc $$ Web: http://www.vallejo.cc $$ $$ $$ $$>a $$ $$ Page into memory a range of memory of the given process $$ r $t0 = ${$arg1}; .while (@$t0 < ${$arg2}) { .if($vvalid(@$t0, 1)==0) { .printf "paging in: %x\n", @$t0 .pagein /p ${$arg3} @$t0 g .if($vvalid(@$t0, 1)==0) { .printf "!!!failed to page in: %x\n", @$t0 } } .else { .printf "already paged in: %x\n", @$t0 } r $t0 = @$t0 + 0x1000; }; ================================================ FILE: WinDbg/remove_device_acls.wdbg ================================================ $$ $$ Author: Javier Vicente Vallejo $$ Twitter: @vallejocc $$ Web: http://www.vallejo.cc $$ aS stage @$t19 .block { .sympath "SRV*c:\symcache*http://msdl.microsoft.com/download/symbols"; .reload } r stage = 0 .foreach( tok { !devobj "${$arg1}" } ) { .printf "${tok}\r\n" .if(${stage}==1) { .echo ${tok} dt _DEVICE_OBJECT ${tok} r $t0 = ${tok} dt _SECURITY_DESCRIPTOR @@c++( ( ( nt!_DEVICE_OBJECT * ) @$t0 )->SecurityDescriptor ) ep @@c++( ( ( nt!_DEVICE_OBJECT * ) @$t0 )->SecurityDescriptor ) + @@c++( #FIELD_OFFSET( _SECURITY_DESCRIPTOR, Sacl ) ) 0 ep @@c++( ( ( nt!_DEVICE_OBJECT * ) @$t0 )->SecurityDescriptor ) + @@c++( #FIELD_OFFSET( _SECURITY_DESCRIPTOR, Dacl ) ) 0 dt _SECURITY_DESCRIPTOR @@c++( ( ( nt!_DEVICE_OBJECT * ) @$t0 )->SecurityDescriptor ) .break } .if(${stage}==0) { .if($scmp("${tok}","object")==0) { r stage = 1 } } } ================================================ FILE: WinDbg/search_bytes_all_processes.wdbg ================================================ $$ $$ Author: Javier Vicente Vallejo $$ Twitter: @vallejocc $$ Web: http://www.vallejo.cc $$ $$ $$>a ... (max 16 bytes) $$ $$ This script is useful for search a max of 16 given bytes through all the running processes $$ aS stage @$t19 aS temp @$t18 aS baseSearch @$t17 aS stop @$t16 .block { .sympath "SRV*c:\symcache*http://msdl.microsoft.com/download/symbols"; .reload } .block { r stage = 2 .foreach (processes_tok { !process 0 0 }) { .if($scmp("${processes_tok}","PROCESS")==0) { .if(${stage}==2) { $$stage==2 is used to skip the first apparition of PROCESS string in the results of !process 0 0 r stage = 0 } .else { r stage = 1 } } .elsif(${stage}==1) { .printf /D "Analyzing process ${processes_tok}\n" r stage = 0 .process /i ${processes_tok} g .block { .reload } .block { .reload /user } r ${stop} = 0 r ${baseSearch} = 0 .while(${stop} == 0) { .printf "searching base %x\n", ${baseSearch} r @$t0 = ${baseSearch} s @$t0 L?0x10000000 ${$arg1} ${$arg2} ${$arg3} ${$arg4} ${$arg5} ${$arg6} ${$arg7} ${$arg8} ${$arg9} ${$arg10} ${$arg11} ${$arg12} ${$arg13} ${$arg14} ${$arg15} ${$arg16} .if(${baseSearch} >= 0x70000000) { .printf "next process\n" r ${stop} = 1 } r ${baseSearch} = ${baseSearch} + 0x10000000 } } } ad stage ad temp ad baseSearch ad stop } ================================================ FILE: WinDbg/search_bytes_target_process.wdbg ================================================ $$ $$ Author: Javier Vicente Vallejo $$ Twitter: @vallejocc $$ Web: http://www.vallejo.cc $$ $$ $$>a .. $$ $$ This script is useful for search a max of 16 given bytes in the given process $$ aS baseSearch @$t17 aS stop @$t16 .block { .sympath "SRV*c:\symcache*http://msdl.microsoft.com/download/symbols"; .reload } .block { .process /i ${$arg1} g .block { .reload } .block { .reload /user } r ${stop} = 0 r ${baseSearch} = 0 .while(${stop} == 0) { .printf "searching base %x\n", ${baseSearch} r @$t0 = ${baseSearch} s @$t0 L?0x10000000 ${$arg2} ${$arg3} ${$arg4} ${$arg5} ${$arg6} ${$arg7} ${$arg8} ${$arg9} ${$arg10} ${$arg11} ${$arg12} ${$arg13} ${$arg14} ${$arg15} ${$arg16} ${$arg17} .if(${baseSearch} >= 0x70000000) { r ${stop} = 1 } r ${baseSearch} = ${baseSearch} + 0x10000000 } ad baseSearch ad stop } ================================================ FILE: WinDbg/search_string_all_processes.wdbg ================================================ $$ $$ Author: Javier Vicente Vallejo $$ Twitter: @vallejocc $$ Web: http://www.vallejo.cc $$ $$ $$>a $$ $$ This script is useful for search a given string through all the running processes $$ aS stage @$t19 aS temp @$t18 aS baseSearch @$t17 aS stop @$t16 .block { .sympath "SRV*c:\symcache*http://msdl.microsoft.com/download/symbols"; .reload } .block { r stage = 2 .foreach (processes_tok { !process 0 0 }) { .if($scmp("${processes_tok}","PROCESS")==0) { .if(${stage}==2) { $$stage==2 is used to skip the first apparition of PROCESS string in the results of !process 0 0 r stage = 0 } .else { r stage = 1 } } .elsif(${stage}==1) { .printf /D "Analyzing process ${processes_tok}\n" r stage = 0 .process /i ${processes_tok} g .block { .reload } .block { .reload /user } r ${stop} = 0 r ${baseSearch} = 0 .while(${stop} == 0) { .printf "searching base %x\n", ${baseSearch} r @$t0 = ${baseSearch} s -a @$t0 L?0x10000000 "${$arg1}" .if(${baseSearch} >= 0x70000000) { .printf "next process\n" r ${stop} = 1 } r ${baseSearch} = ${baseSearch} + 0x10000000 } } } ad stage ad temp ad baseSearch ad stop } ================================================ FILE: WinDbg/search_string_target_process.wdbg ================================================ $$ $$ Author: Javier Vicente Vallejo $$ Twitter: @vallejocc $$ Web: http://www.vallejo.cc $$ $$ $$>a $$ $$ This script is useful for search a given string in a given process $$ aS baseSearch @$t17 aS stop @$t16 .block { .sympath "SRV*c:\symcache*http://msdl.microsoft.com/download/symbols"; .reload } .block { .process /i ${$arg1} g .block { .reload } .block { .reload /user } r ${stop} = 0 r ${baseSearch} = 0 .while(${stop} == 0) { .printf "searching base %x\n", ${baseSearch} r @$t0 = ${baseSearch} s -a @$t0 L?0x10000000 "${$arg2}" .if(${baseSearch} >= 0x70000000) { r ${stop} = 1 } r ${baseSearch} = ${baseSearch} + 0x10000000 } ad baseSearch ad stop } ================================================ FILE: WinDbg/secure_writemem.wdbg ================================================ $$ $$ Author: Javier Vicente Vallejo $$ Twitter: @vallejocc $$ Web: http://www.vallejo.cc $$ $$ $$>a $$ $$ this script tries to dump a range of memory. If its not possible to dump a part of the range, that part if filled with random data $$ (really its filled with "\x11\x11\x11......\x11\x20\x0d\x0a" (total length 0x1000 for each page filled), but we must not assume it $$ will always contain this value .printf "secure_writemem.wdbg\r\n" .printf "${$arg1}\r\n" .printf "${$arg2}\r\n" .printf "${$arg3}\r\n" .printf "${$arg4}\r\n" .printf "${$arg5}\r\n" aS swmbase @$t18 aS swmend @$t17 aS swmtemp @$t16 .block { .printf "secure_writemem_1\r\n" r ${swmbase} = ${$arg1} r ${swmend} = ${$arg2} r ${swmtemp} = ${swmbase} .printf "secure_writemem_2\r\n" .foreach /pS 4 (swmlen_tok { ? (${swmend}-${swmbase}) }) { .printf "secure_writemem_3\r\n" .block { .shell -x del ${$arg4}\${$arg3}_${$arg1}_${swmlen_tok}.${$arg5} } .printf "secure_writemem_4\r\n" .while (${swmtemp} < ${swmend}) { .printf "paging in: %x\n", ${swmtemp} .pagein /p ${$arg3} ${swmtemp} g .foreach /pS 4 (swmtemp_tok { ? swmtemp }) { .printf "secure_writemem_temp_${swmtemp_tok}.bin > ${$arg3}_${$arg1}_${len_tok}.${$arg5}\n" .if($vvalid(${swmtemp}, 0x1000)==1) { .writemem ${$arg4}\secure_writemem_temp_${swmtemp_tok}.bin ${swmtemp} L 0x1000 .shell -x type ${$arg4}\secure_writemem_temp_${swmtemp_tok}.bin >> ${$arg4}\${$arg3}_${$arg1}_${swmlen_tok}.${$arg5} && del ${$arg4}\secure_writemem_temp_${swmtemp_tok}.bin } .else { .printf "invalid address: %x\n", ${swmtemp} .shell -x echo  >> ${$arg4}\${$arg3}_${$arg1}_${swmlen_tok}.${$arg5} && x } } r ${swmtemp} = ${swmtemp} + 0x1000 } .printf "secure_writemem_5\r\n" } } .printf "secure_writemem_6\r\n" ================================================ FILE: WinDbg/show_address_info.wdbg ================================================ $$ $$ Author: Javier Vicente Vallejo $$ Twitter: @vallejocc $$ Web: http://www.vallejo.cc $$ $$ $$>a $$ aS stage @$t19 aS basealloc @$t18 aS stop @$t17 aS baseSearch @$t16 .block { .sympath "SRV*c:\symcache*http://msdl.microsoft.com/download/symbols"; .reload .process /i ${$arg2} g .reload } .block { $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $$print info related to the address .printf /D "\nAddress info for address %x\n", ${$arg1} .printf /D "--------------------------------------------\n" !address ${$arg1} $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $$print info related to the heap (if the address belongs to a heap) .printf /D "\nHeap related info to address %x\n", ${$arg1} .printf /D "--------------------------------------------\n" !heap -p -a ${$arg1} $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $$search for references to the arg1 address .printf /D "\nReferences to address %x\n", ${$arg1} .printf /D "--------------------------------------------\n" r ${stop} = 0 r ${baseSearch} = 0 .while(${stop} == 0) { $$.printf "searching %x\n", ${baseSearch} r @$t0 = ${baseSearch} r @$t1 = ${$arg1} s @$t0 L 10000000 (@$t1 & ff) ((@$t1 & ff00) >> 8) ((@$t1 & ff0000) >> 10) ((@$t1 & ff000000) >> 18) .if(${baseSearch} >= 0x70000000) { r ${stop} = 1 } r ${baseSearch} = ${baseSearch} + 10000000 } $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $$ use !heap extension to guess the base of the allocated memory related to the arg1 address r stage = 0 r basealloc = -1 .foreach ( tok {!heap -p -a ${$arg1}} ) { .if(${stage} == 2) { r basealloc = ${tok} .break } .if(${stage} == 1) { .if($scmp("${tok}0","state0")==0) { r stage = 2 } } .if(${stage} == 0) { .if($scmp("${tok}0","HEAP_ENTRY0")==0) { r stage = 1 } } } $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $$search for references to the allocated memory base .if(${basealloc} != -1) { .printf /D "\nReferences to allocation base %x\n", ${basealloc}+8 .printf /D "--------------------------------------------\n" r ${stop} = 0 r ${baseSearch} = 0 .while(${stop} == 0) { $$.printf "searching %x\n", ${baseSearch} r @$t0 = ${baseSearch} r @$t1 = ${basealloc}+8 s @$t0 L 10000000 (@$t1 & ff) ((@$t1 & ff00) >> 8) ((@$t1 & ff0000) >> 10) ((@$t1 & ff000000) >> 18) .if(${baseSearch} >= 70000000) { r ${stop} = 1 } r ${baseSearch} = ${baseSearch} + 10000000 } } ad stage ad basealloc ad stop ad baseSearch } ================================================ FILE: WinDbg/show_pe_headers.wdbg ================================================ $$ $$ Author: Javier Vicente Vallejo $$ Twitter: @vallejocc $$ Web: http://www.vallejo.cc $$ $$ $$>a $$.sympath SRV*c:\symcache*http://msdl.microsoft.com/download/symbols $$.reload $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $$ change the context to the target process and page in the page containing PE headers .process /i ${$arg2} g $$.reload /user .pagein /p ${$arg2} ${$arg1} g $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $$ define some useful aliases ad /q ${/v:$imagebase}; ad /q ${/v:$pe_header}; ad /q ${/v:$file_header}; ad /q ${/v:$optional_header}; ad /q ${/v:$number_of_sections}; ad /q ${/v:$sections}; aS /x ${/v:$imagebase} ( ${$arg1} ) aS /x ${/v:$pe_header} ( $imagebase + ( poi( $imagebase + @@( #FIELD_OFFSET( nt!_IMAGE_DOS_HEADER, e_lfanew ) ) ) ) ) aS /x ${/v:$file_header} ( $pe_header + @@( #FIELD_OFFSET( nt!_IMAGE_NT_HEADERS, FileHeader ) ) ) aS /x ${/v:$optional_header} ( $pe_header + @@( #FIELD_OFFSET( nt!_IMAGE_NT_HEADERS, OptionalHeader ) ) ) aS /x ${/v:$number_of_sections} ( poi ( $file_header + @@( #FIELD_OFFSET( nt!_IMAGE_FILE_HEADER, NumberOfSections ) ) ) & 0xffff ) aS /x ${/v:$data_directory} ( $optional_header + @@( #FIELD_OFFSET( nt!_IMAGE_OPTIONAL_HEADER, DataDirectory ) ) ) aS /x ${/v:$sections} ( $pe_header + @@c++( sizeof ( nt!_IMAGE_NT_HEADERS ) ) ) .printf "DOS HEADER:\n" .printf "----------:\n" dt nt!_IMAGE_DOS_HEADER $imagebase .printf "NT HEADERS:\n" .printf "----------:\n" dt nt!_IMAGE_NT_HEADERS $pe_header .printf "FILE HEADER:\n" .printf "-----------:\n" dt nt!_IMAGE_FILE_HEADER $file_header .printf "OPTIONAL HEADER:\n" .printf "---------------:\n" dt nt!_IMAGE_OPTIONAL_HEADER $optional_header .printf "DATA DIRECTORY 0 EXPORT:\n" .printf "-----------------------:\n" dt nt!_IMAGE_DATA_DIRECTORY $data_directory + @@c++( sizeof ( nt!_IMAGE_DATA_DIRECTORY ) ) * 0 .printf "DATA DIRECTORY 1 IMPORT:\n" .printf "-----------------------:\n" dt nt!_IMAGE_DATA_DIRECTORY $data_directory + @@c++( sizeof ( nt!_IMAGE_DATA_DIRECTORY ) ) * 1 .printf "DATA DIRECTORY 2 RESOURCE:\n" .printf "-------------------------:\n" dt nt!_IMAGE_DATA_DIRECTORY $data_directory + @@c++( sizeof ( nt!_IMAGE_DATA_DIRECTORY ) ) * 2 .printf "DATA DIRECTORY 3 EXCEPTION:\n" .printf "--------------------------:\n" dt nt!_IMAGE_DATA_DIRECTORY $data_directory + @@c++( sizeof ( nt!_IMAGE_DATA_DIRECTORY ) ) * 3 .printf "DATA DIRECTORY 4 SECURITY:\n" .printf "-------------------------:\n" dt nt!_IMAGE_DATA_DIRECTORY $data_directory + @@c++( sizeof ( nt!_IMAGE_DATA_DIRECTORY ) ) * 4 .printf "DATA DIRECTORY 5 FIXUPS:\n" .printf "-----------------------:\n" dt nt!_IMAGE_DATA_DIRECTORY $data_directory + @@c++( sizeof ( nt!_IMAGE_DATA_DIRECTORY ) ) * 5 .printf "DATA DIRECTORY 6 DEBUG:\n" .printf "----------------------:\n" dt nt!_IMAGE_DATA_DIRECTORY $data_directory + @@c++( sizeof ( nt!_IMAGE_DATA_DIRECTORY ) ) * 6 .printf "DATA DIRECTORY 7 DESCRIPTION:\n" .printf "----------------------------:\n" dt nt!_IMAGE_DATA_DIRECTORY $data_directory + @@c++( sizeof ( nt!_IMAGE_DATA_DIRECTORY ) ) * 7 .printf "DATA DIRECTORY 8 MIPS GP:\n" .printf "------------------------:\n" dt nt!_IMAGE_DATA_DIRECTORY $data_directory + @@c++( sizeof ( nt!_IMAGE_DATA_DIRECTORY ) ) * 8 .printf "DATA DIRECTORY 9 TLS:\n" .printf "--------------------:\n" dt nt!_IMAGE_DATA_DIRECTORY $data_directory + @@c++( sizeof ( nt!_IMAGE_DATA_DIRECTORY ) ) * 9 .printf "DATA DIRECTORY 10 LOAD CONFIG:\n" .printf "-----------------------------:\n" dt nt!_IMAGE_DATA_DIRECTORY $data_directory + @@c++( sizeof ( nt!_IMAGE_DATA_DIRECTORY ) ) * 10 .printf "DATA DIRECTORY 11 BOUND IMPORT:\n" .printf "------------------------------:\n" dt nt!_IMAGE_DATA_DIRECTORY $data_directory + @@c++( sizeof ( nt!_IMAGE_DATA_DIRECTORY ) ) * 11 .printf "DATA DIRECTORY 12 IMPORT TABLE:\n" .printf "------------------------------:\n" dt nt!_IMAGE_DATA_DIRECTORY $data_directory + @@c++( sizeof ( nt!_IMAGE_DATA_DIRECTORY ) ) * 12 .printf "DATA DIRECTORY 13 DELAY IMPORT:\n" .printf "------------------------------:\n" dt nt!_IMAGE_DATA_DIRECTORY $data_directory + @@c++( sizeof ( nt!_IMAGE_DATA_DIRECTORY ) ) * 13 .printf "DATA DIRECTORY 14 COM RUNTIME:\n" .printf "-----------------------------:\n" dt nt!_IMAGE_DATA_DIRECTORY $data_directory + @@c++( sizeof ( nt!_IMAGE_DATA_DIRECTORY ) ) * 14 .printf "DATA DIRECTORY 15 RESERVED:\n" .printf "--------------------------:\n" dt nt!_IMAGE_DATA_DIRECTORY $data_directory + @@c++( sizeof ( nt!_IMAGE_DATA_DIRECTORY ) ) * 15 r $t0 = $sections r $t1 = 0 r $t2 = $number_of_sections .while (@$t1 < @$t2) { .printf "SECTION HEADER %d:\n", @$t1 .printf "-----------------:\n" r $t3 = $sections + @$t1 * @@c++(sizeof(nt!_IMAGE_SECTION_HEADER)) dt -b nt!_IMAGE_SECTION_HEADER @$t3 r $t1 = @$t1 + 1 } ================================================ FILE: WinDbg/show_proc_from_handle.wdbg ================================================ $$ $$ Author: Javier Vicente Vallejo $$ Twitter: @vallejocc $$ Web: http://www.vallejo.cc $$ $$ $$>a $$ $$.sympath SRV*c:\symcache*http://msdl.microsoft.com/download/symbols $$.reload r @$t0 = ${$arg1} .if(@$t0!=0 & @$t0!=0xffffffff) { r @$t1 = 0; .foreach (tok { !handle @$t0 }) { .if(@$t1==1) { r @$t1 = 0; .printf /D "${tok} " .foreach (tok2 { !process ${tok} 0 }) { .if(@$t1==1) { .printf /D "${tok2}\n" .break; } .elsif($scmp("${tok2}","Image:")==0) { r @$t1 = 1; } } .break; } .elsif($scmp("${tok}","Object:")==0) { r @$t1 = 1; }; }; }; ================================================ FILE: WinDbg/symbols.wdbg ================================================ $$ $$ Author: Javier Vicente Vallejo $$ Twitter: @vallejocc $$ Web: http://www.vallejo.cc $$ $$ $$>aa .logopen ${$arg1}\write_mem_dump.start .logclose .block { .sympath "SRV*c:\symcache*http://msdl.microsoft.com/download/symbols"; .reload } .dump /f ${$arg1}\memory.dmp .logopen ${$arg1}\write_mem_dump.end .logclose